Projections using Hypertuned model through XGboost

All data is from FanGraphs. I have no affiliation with FanGraphs, but please consider contributing to their website if you found this project informative.

1 Project Scope

1.1 Objective

This project is designed to showcase how Using a Percentile Based Worth System values Fantasy Baseball Players through a Inning Pitched (IP) weighted projection

The Categories used for prediction valuation are year-end rankings for the following metrics: - HRs - Runs - RBIs - Batting Average - Stolen Bases - OPS


2 Processing the Data

2.1 Getting Data Into R

2.1.1 Load Libraries

First we need to load the packages that R needs to run the analysis

library(sqldf) #SQL in R
library(skimr) #Summaries and useful for removing low % data
library(ggplot2) #Plotting Functions
library(plyr) #slightly deprecated data cleaning
library(dplyr) #slightly updated data cleaning
library(tidyverse) #tidyverse data cleaning universe
library(caret) #wrapper for creating, tuning and validating models
library(xgboost) #package for creating regression tree model
library(vtreat) # useful package for treating data before modeling 
library(Matrix)
library(Boruta)
library(mgcv)
library(moments) #for measuring skewness
library(data.table) #alternative to dplyr we use to create lags
library(pdp) #partial dependence graphs
library(vip) #variable importance 
library(grid) #put multiple plots on one grid
library(gridExtra) #additional grid functionality
library(janitor) #one function used to clean transposed data set
library(ggpubr) #for qq plot
library(tableHTML)
library(kableExtra)

The # comments generally explain what additional functionality each library adds to R

2.1.2 Load in Data

All data is downloaded from Fan Graphs. From this location. The data is also available on my Github here. There are player level and team data sets


#data read-in
Batter_data <- read_csv("FanGraphs Leaderboard_Hitting50PA.csv")
#Team datasets
FDG_Team = read_csv("FanGraphs Leaderboard_Team.csv")
#Create a prefix for all team stats that starts with T_
FDG_Team2 <- FDG_Team %>% 
  rename_with( ~ paste0("T_", .x))

2.1.3 Checking Team Data

str give information about an object, while skim provides a customizable summary


#Output not shown for space
#str(FDG_Team2)

skim(FDG_Team2) %>%  
  tibble::as_tibble()

2.2 Understanding the Dataset

2.2.1 Exploring the dataset

skim let’s us see how the data was imported into R. Documentation can be found here


#Full Dataset dimensions

skimr::skim(Batter_data) %>% 
  tibble::as_tibble() %>% 
  select(skim_type,skim_variable,complete_rate) %>% 
  filter(complete_rate >0.30) #288 Variables

#skim_type - character or numeric
#skim_variable - name of variable
#complete_rate - % of data that is not missing
#filter - only keep variables that have 30% of data populated

Additionally let’s look at how variables vary by year to see if there are any discrepancies there


#It looks like one year, there were fewer games played, and there is a clear drop off in home runs
Batter_data_dist =
Batter_data %>% 
 group_by(Season) %>% 
  summarize (Games_played = max(G),
             Avg_HR= mean(HR)
             )
Batter_data_dist

ggplot(Batter_data_dist, aes(Season, Avg_HR)) +
  geom_col()+
  ggtitle("Average Home Runs by Year")+
  theme(plot.title = element_text(hjust = 0.5,size = 22,color ="steel blue"))


2.3 Cleaning and Creating Initial Dataset for Model

What are some issues with the data?

  1. Many of Variables, such as K%, are being read in as characters

    • Only Team and Player Name should be characters
  2. There is spotty data coverage in some of the variables (~Variables have less than 30% Coverage)

  3. 2020 Data only includes 60 games worth of data

    • This was a season shortened due to Covid-19
  4. Team Data needs to be appended to Batter Data by Team Name


2.3.1 Cleanly Changing all Variables that are characters to numeric.

There are several ways to do this, we will identify the variables we want to change that are mis-identified. parse_number can be used to pull numbers from these variables. Additional ways to tackle this can be found here


#Select Column names that are characters but not Team or Name, These should be percentages
Batter_data_chars_to_convert <- Batter_data %>% 
  select_if(is.character)%>% select(-Team,-Name) %>% 
  mutate_all (function(x) as.numeric(readr::parse_number(x))/100)
#Note : There are additional ways to do this, this is just one solution


#We can exclude the variables we converted and reintroduce them
Batter_data_num <- Batter_data %>% select(-colnames(Batter_data_chars_to_convert))

Batter_data2 = cbind(Batter_data_num,Batter_data_chars_to_convert) %>% 
  select (colnames(Batter_data)) %>%  #preserve original order 
  dplyr::rename(flyball_perc = `FB%...46`,fastball_perc = `FB%...73`) #rename two ambiguous columns
  
skim(Batter_data2) %>% 
  as_tibble() %>% 
  group_by(skim_type) %>% 
  count()


#Logical variables are R's best guess, in our case they are all NA's and will be removed

The same can be done for the Team Data that is loaded


#Select Column names that are characters but not Team or Name, These should be percentages
FDG_Team2_chars_to_convert <- FDG_Team2 %>% 
  select_if(is.character)%>% select(-T_Team) %>% 
  mutate_all (function(x) as.numeric(readr::parse_number(x))/100)
#Keep in mind, parse number may make actual characters into numerical variables so carefully check your data before using

#We can exclude the variables we converted and reintroduce them
FDG_Team2_num <- FDG_Team2 %>% select(-colnames(FDG_Team2_chars_to_convert))

FDG_Team3 = cbind(FDG_Team2_num,FDG_Team2_chars_to_convert) %>% 
  select (colnames(FDG_Team2)) %>%  #preserve original order
dplyr::rename(T_flyball_perc = `T_FB%...45`,T_fastball_perc = `T_FB%...72`) 

skim(FDG_Team3) %>% 
  as_tibble() %>% 
  group_by(skim_type) %>% 
  count()

2.3.2 Filtering Data with Low Coverage

I choose 30% coverage of data necessary but this can be adjusted up or down. This will also get rid of columns that are all NA.


# Keep variables with enough values (Need 30% data coverage rate here)
Player_cols_to_keep =
skim(Batter_data2) %>% 
  dplyr::select(skim_type, skim_variable, complete_rate) %>% 
  filter (complete_rate > 0.30)

#Transpose Rows to get column names as skim melts the data
Player_cols_to_keep_transpose = t(Player_cols_to_keep) 

#extract the colnames we would like to keep
Player_cols_to_keep = colnames(janitor::row_to_names(Player_cols_to_keep_transpose,row_number = 2))

#Only keep the columns designated to have over 30% of their data populated or greater
Batter_data3 = Batter_data2 %>% 
  select(one_of(Player_cols_to_keep)) 

Repeat the process for Team Variables

Team_cols_to_keep =
skim(FDG_Team3) %>% 
  dplyr::select(skim_type, skim_variable, complete_rate) %>% 
  filter (complete_rate > 0.30)


#Transpose Rows to get column names as skim melts the data
Team_cols_to_keep_transpose = t(Team_cols_to_keep) 

#extract the colnames we would like to keep
Team_cols_to_keep = colnames(janitor::row_to_names(Team_cols_to_keep_transpose,row_number = 2))

#Only keep the columns designated to have over 30% of their data populated or greater
FDG_Team4 = FDG_Team3 %>% 
  select(one_of(Team_cols_to_keep)) 

2.3.3 Creating Variables Normalized by Year

Some Variables will need to be normalized by Innings_Pitched (IP) if they aren’t a percentage already. Remaining Variables are percentages or indices so will not need to be transformed. The full data dictionary for these variables can be found on FanGraph’s website here. for pitching variables and here. for hitting variables.


Batter_data4 = Batter_data3 %>% 
  mutate( #create new variables based on existing variables
    H_PA = H/PA,
    x1B_PA = `1B`/PA, #note: R can't have variables start with a number
    x2b_PA = `2B`/PA,
    x3b_PA = `3B`/PA,
    HR_PA = HR/PA,
    R_PA = R/PA,
    RBI_PA = RBI/PA,
    BB_PA = BB/PA,
    IBB_PA = IBB/PA,
    SO_PA=SO/PA,
    HBP_PA=HBP/PA,
    SF_PA=SF/PA,
    SH_PA=SH/PA,
    GDP_PA= GDP/PA,#ground into double play
    SB_PA=SB/PA,
    CS_PA=CS/PA,
    GB_PA = GB/PA,   #Groundballs
    FB_PA =  FB/PA,  #FlyBalls
    LD_PA = LD/PA,   #LineDrives
    IFFB_PA = IFFB/PA,  #Infield Fly balls
    Pitches_PA= Pitches/PA,
    Balls_PA= Balls/PA,
    Strikes_PA= Strikes/PA,
    IFH_PA= IFH/PA,
    BU_PA= BU/PA,
    BUH_PA= BUH/PA,
    PH_PA= PH/PA,
    Barrels_PA= Barrels/PA,
    HardHits_PA= HardHit/PA
  ) %>% select(-(H:CS),-(GB:BUH),-PH,-Barrels,-HardHit,-Events) #Drop the old variables

#skim(Batter_data4) %>% as_tibble()

Repeat the process for Team Variables


FDG_Team5 = FDG_Team4 %>% 
  mutate( #create new variables based on existing variables
    T_H_T_PA = T_H/T_PA,
    T_x1B_T_PA = T_1B/T_PA, #note: R can't have variables start with a number
    T_x2b_T_PA = T_2B/T_PA,
    T_x3b_T_PA = T_3B/T_PA,
    T_HR_T_PA = T_HR/T_PA,
    T_R_T_PA = T_R/T_PA,
    T_RBI_T_PA = T_RBI/T_PA,
    T_BB_T_PA = T_BB/T_PA,
    T_IBB_T_PA = T_IBB/T_PA,
    T_SO_T_PA=T_SO/T_PA,
    T_HBP_T_PA=T_HBP/T_PA,
    T_SF_T_PA=T_SF/T_PA,
    T_SH_T_PA=T_SH/T_PA,
    T_GDP_T_PA= T_GDP/T_PA,#ground into double play
    T_SB_T_PA=T_SB/T_PA,
    T_CS_T_PA=T_CS/T_PA,
    T_GB_T_PA = T_GB/T_PA,   #Groundballs
    T_FB_T_PA =  T_FB/T_PA,  #FlyBalls
    T_LD_T_PA = T_LD/T_PA,   #LineDrives
    T_IFFB_T_PA = T_IFFB/T_PA,  #Infield Fly balls
    T_Pitches_T_PA= T_Pitches/T_PA,
    T_Balls_T_PA= T_Balls/T_PA,
    T_Strikes_T_PA= T_Strikes/T_PA,
    T_IFH_T_PA= T_IFH/T_PA,
    T_BU_T_PA= T_BU/T_PA,
    T_BUH_T_PA= T_BUH/T_PA,
    T_PH_T_PA= T_PH/T_PA,
    T_Barrels_T_PA= T_Barrels/T_PA,
    T_HardHits_T_PA= T_HardHit/T_PA
  ) %>% select(-(T_H:T_CS),-(T_GB:T_BUH),-T_PH,-T_Barrels,-T_HardHit,-T_Events) #Drop the old variables


#skim(FDG_Team5) %>% as_tibble()

2.3.4 Creating Lagged Variables

There are several ways to lag a dataset BY GROUP.
* Dplyr way is here..
* The data.table (the method used below) is here.

#Note we will only be lagging the player level data, as the previous year's team performance shouldn't impact current performance


#Order the dataset by lag columns
Batter_data5 =  arrange(Batter_data4, playerid,Season) #playerid is the Fangraph id assigned to each player

# Convert dataframe to data.table format
DT_batter = data.table(Batter_data5)

#designate columns to lag - which is all of them
cols1 = colnames(Batter_data5)
anscols = paste("lag", cols1, sep="_")
DT_batter[, (anscols) := data.table::shift(.SD, 1, NA, "lag"),by ='playerid', .SDcols=cols1] #Create 1 period lags by year

Batter_data6 = as.data.frame(DT_batter) %>% select(-lag_playerid, -lag_Team, -lag_Season, -lag_Age,-lag_Name)

ncol(Batter_data5) #287 - no lags
[1] 259
ncol(Batter_data6) #574 - lagged data ~ (287 * 2)-5
[1] 513

2.3.5 Merging Team and Player Data

We can use either the merge function or the SQL functionality provided by the sqldf package to join the lagged player level data to the Team level data


df_batting_init = sqldf(
  "
  select a.*, b.*
  from Batter_data6 a
  left join FDG_Team5 b
  on a.Team = b.T_Team and a.Season = b.T_Season
  
  "
)  %>% select(-T_Team,-T_Season,T_Age,T_G,T_AB)# Unncessary Team Variables


nrow(df_batting_init) - nrow(Batter_data6) #check if any rows are duplicated
[1] 0

3 Creating Rankings for Players Based On Percentiles

We can use Percentile based ranking to get rankings for players from the 2021 season.

3.1 Worth of each stat

3.1.1 Calculating past performance

Each player goes from a 0% to 100% on each percentile stat that is used for creating a scoring opportunity. Data is not yet normalized by PA as certain stats such as HRs and SBs will be worth more when we do.


#Categories I include are:
#Runs (R), Home Runs (HR), Runs Batted In (RBI), Stolen Bases (SB), Batting Average (AVG)
df_batting_init2 =  df_batting_init %>%
#  arrange(player_id,year) %>% 
  group_by(Season) %>% 
  mutate(
    Runs_share = order(order(rank(R_PA,ties.method = 'average'),decreasing = FALSE))/n(),
     HR_share = order(order(rank(HR_PA,ties.method = 'average'),decreasing = FALSE))/n(),
     RBI_share = order(order(rank(RBI_PA,ties.method = 'average'),decreasing = FALSE))/n(),
     SB_share = order(order(rank(SB_PA,ties.method = 'average'),decreasing = FALSE))/n(),
     AVG_share = order(order(rank(AVG,ties.method = 'average'),decreasing = FALSE))/n(),
    OPS_share = order(order(rank(OPS,ties.method = 'average'),decreasing = FALSE))/n(),
    Worth = Runs_share+HR_share+RBI_share+SB_share+AVG_share+OPS_share
    ) %>% 
  ungroup() 

Chart of the Distribution of initial percentiles
As the chart below shows, the data is roughly normal.


skewness((df_batting_init2$Worth))
[1] -0.2
ggplot2::qplot(df_batting_init2$Worth, main="Total Dataset") + geom_histogram(colour="black", fill="lime green")
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

min(df_batting_init2$Worth)
[1] 0.031
max(df_batting_init2$Worth)
[1] 5.8
ggpubr::ggqqplot(df_batting_init2$Worth)


shapiro.test(df_batting_init2$Worth)

    Shapiro-Wilk normality test

data:  df_batting_init2$Worth
W = 1, p-value <0.0000000000000002

3.2 2021 Player Rankings - Per PA performance

3.2.1 2021 Player Rankings - Top Worth Player

There are per PA rankings. Players like Byron Buxton which had a great per PA score but can’t stay healthy for a season will be adjusted down.


options(digits=2)

df_batting_init2021 =
df_batting_init2 %>% 
  group_by(Name) %>% 
  filter(Season == 2021) %>% 
  arrange(desc(Worth)) %>% 
  select(Name,Runs_share,HR_share,RBI_share, SB_share,OPS_share,AVG_share,Worth)


df_batting_init2021 %>%
  filter (Worth>3.9) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)
Name Runs_share HR_share RBI_share SB_share OPS_share AVG_share Worth
Fernando Tatis Jr. 0.99 1.00 0.98 0.96 0.99 0.89 5.8
Ronald Acuna Jr. 1.00 0.98 0.82 0.96 0.99 0.90 5.6
Byron Buxton 1.00 0.99 0.70 0.92 0.99 0.97 5.6
Tyler O'Neill 0.96 0.97 0.86 0.88 0.96 0.91 5.5
Jose Ramirez 0.98 0.93 0.94 0.94 0.94 0.78 5.5
Bryce Harper 0.96 0.95 0.80 0.82 1.00 0.97 5.5
Teoscar Hernandez 0.92 0.90 1.00 0.80 0.93 0.94 5.5
Kyle Tucker 0.87 0.89 0.94 0.86 0.97 0.94 5.5
Shohei Ohtani 0.95 0.99 0.90 0.94 0.98 0.69 5.4
Vladimir Guerrero Jr. 0.99 0.98 0.92 0.49 0.99 0.98 5.3
Frank Schwindel 0.97 0.90 0.95 0.55 0.98 0.99 5.3
Bo Bichette 0.99 0.76 0.85 0.92 0.87 0.95 5.3
Trea Turner 0.96 0.78 0.63 0.97 0.96 1.00 5.3
Nick Castellanos 0.95 0.94 0.97 0.47 0.98 0.97 5.3
Juan Soto 0.96 0.80 0.83 0.70 0.99 0.99 5.3
Brandon Crawford 0.85 0.78 0.94 0.80 0.94 0.95 5.3
Brandon Belt 0.97 0.99 0.89 0.56 0.99 0.85 5.2
Luis Robert 0.84 0.79 0.83 0.81 0.98 1.00 5.2
Javier Baez 0.87 0.93 0.92 0.91 0.85 0.77 5.2
Marcus Semien 0.94 0.96 0.80 0.81 0.93 0.77 5.2
Mike Trout 0.93 0.91 0.67 0.69 1.00 1.00 5.2
Paul Goldschmidt 0.90 0.83 0.84 0.76 0.94 0.94 5.2
Rafael Devers 0.91 0.93 0.97 0.55 0.94 0.88 5.2
George Springer 0.98 0.97 0.84 0.66 0.96 0.76 5.2
Aaron Judge 0.83 0.96 0.88 0.60 0.97 0.92 5.2
A.J. Pollock 0.64 0.87 0.94 0.82 0.94 0.95 5.2
Manny Machado 0.85 0.79 0.95 0.78 0.89 0.88 5.1
Jorge Polanco 0.90 0.88 0.88 0.76 0.87 0.80 5.1
Matt Olson 0.89 0.94 0.94 0.50 0.96 0.83 5.1
Brandon Lowe 0.93 0.97 0.93 0.66 0.92 0.59 5.0
Freddie Freeman 0.98 0.80 0.64 0.66 0.95 0.95 5.0
Ozzie Albies 0.90 0.78 0.88 0.89 0.82 0.71 5.0
Adam Engel 0.89 0.88 0.72 0.97 0.88 0.64 5.0
Yordan Alvarez 0.91 0.91 0.98 0.34 0.93 0.87 5.0
Thairo Estrada 0.85 0.89 0.96 0.55 0.85 0.85 4.9
Jesse Winker 0.93 0.87 0.84 0.36 0.98 0.96 4.9
Avisail Garcia 0.73 0.92 0.96 0.72 0.86 0.74 4.9
Kyle Schwarber 0.95 0.98 0.87 0.36 0.97 0.78 4.9
Joey Votto 0.78 0.98 0.99 0.35 0.97 0.78 4.9
Bryan Reynolds 0.85 0.69 0.79 0.55 0.96 0.96 4.8
Tim Anderson 0.97 0.57 0.54 0.90 0.83 0.98 4.8
Salvador Perez 0.74 0.99 0.99 0.33 0.91 0.84 4.8
Rhys Hoskins 0.86 0.95 0.93 0.53 0.92 0.58 4.8
Jose Altuve 0.98 0.83 0.66 0.54 0.89 0.87 4.8
Max Muncy 0.94 0.95 0.92 0.40 0.95 0.61 4.8
Austin Riley 0.79 0.87 0.93 0.26 0.95 0.96 4.8
Randy Arozarena 0.92 0.62 0.59 0.91 0.85 0.86 4.8
Xander Bogaerts 0.89 0.70 0.73 0.57 0.92 0.94 4.7
Kris Bryant 0.88 0.77 0.68 0.75 0.88 0.78 4.7
Patrick Wisdom 0.85 0.99 0.94 0.64 0.87 0.44 4.7
Starling Marte 0.96 0.41 0.46 1.00 0.89 0.98 4.7
Hunter Renfroe 0.92 0.91 0.96 0.35 0.85 0.71 4.7
C.J. Cron 0.67 0.88 0.96 0.35 0.95 0.89 4.7
Will Smith 0.83 0.87 0.87 0.50 0.91 0.70 4.7
Franmil Reyes 0.59 0.97 0.99 0.58 0.90 0.66 4.7
Trevor Story 0.88 0.73 0.70 0.91 0.83 0.63 4.7
LaMonte Wade Jr. 0.78 0.84 0.84 0.73 0.84 0.65 4.7
Cedric Mullins II 0.77 0.80 0.29 0.95 0.93 0.93 4.7
Darin Ruf 0.72 0.88 0.78 0.51 0.95 0.82 4.7
Chris Owings 0.99 0.34 0.41 0.94 1.00 0.99 4.7
Chas McCormick 0.88 0.79 0.90 0.68 0.72 0.69 4.7
Ketel Marte 0.80 0.69 0.74 0.48 0.96 0.99 4.7
Justin Turner 0.84 0.79 0.81 0.46 0.88 0.87 4.6
Evan Longoria 0.92 0.81 0.91 0.41 0.88 0.73 4.6
Nelson Cruz 0.77 0.91 0.85 0.47 0.88 0.77 4.6
Jake Meyers 0.77 0.68 0.98 0.77 0.72 0.72 4.6
Mookie Betts 0.96 0.76 0.47 0.77 0.91 0.76 4.6
Pete Alonso 0.65 0.94 0.85 0.46 0.92 0.74 4.6
Jared Walsh 0.55 0.87 0.96 0.40 0.91 0.87 4.5
Chris Taylor 0.93 0.65 0.69 0.83 0.78 0.66 4.5
Corey Seager 0.73 0.72 0.79 0.37 0.97 0.97 4.5
Matt Beaty 0.89 0.56 0.97 0.57 0.72 0.82 4.5
Jonathan India 0.92 0.62 0.52 0.78 0.89 0.81 4.5
Willy Adames 0.80 0.81 0.73 0.59 0.86 0.74 4.5
Ryan Mountcastle 0.72 0.92 0.88 0.53 0.81 0.67 4.5
Mitch Haniger 0.94 0.92 0.83 0.33 0.83 0.65 4.5
Rafael Ortega 0.74 0.62 0.41 0.92 0.86 0.93 4.5
Tyrone Taylor 0.59 0.80 0.91 0.82 0.77 0.58 4.5
Brett Phillips 0.97 0.80 0.87 0.97 0.60 0.26 4.5
Jose Abreu 0.70 0.82 0.98 0.33 0.88 0.73 4.4
Carlos Correa 0.95 0.74 0.81 0.15 0.90 0.88 4.4
J.D. Martinez 0.86 0.79 0.90 0.05 0.92 0.91 4.4
Tyler Naquin 0.49 0.76 0.88 0.65 0.84 0.81 4.4
Gavin Sheets 0.67 0.96 1.00 0.29 0.87 0.62 4.4
Yasmani Grandal 0.94 0.96 0.95 0.09 0.97 0.51 4.4
Seth Brown 0.82 0.97 0.90 0.69 0.69 0.32 4.4
Adolis Garcia 0.62 0.87 0.82 0.87 0.66 0.55 4.4
Wander Franco 0.98 0.41 0.71 0.52 0.85 0.92 4.4
Sam Hilliard 0.76 0.95 0.81 0.81 0.70 0.33 4.4
Ryan McMahon 0.76 0.71 0.82 0.62 0.77 0.66 4.3
Nolan Arenado 0.62 0.88 0.93 0.39 0.84 0.67 4.3
Adalberto Mondesi 0.81 0.79 0.69 1.00 0.58 0.44 4.3
Adam Duvall 0.57 0.98 1.00 0.59 0.74 0.42 4.3
Austin Meadows 0.75 0.83 0.99 0.53 0.74 0.47 4.3
Mike Zunino 0.97 1.00 0.95 0.13 0.91 0.34 4.3
Harrison Bader 0.46 0.73 0.69 0.83 0.80 0.79 4.3
Matt Vierling 0.84 0.47 0.21 0.87 0.90 0.99 4.3
Steven Duggar 0.90 0.49 0.62 0.85 0.73 0.69 4.3
J.T. Realmuto 0.54 0.58 0.77 0.85 0.78 0.75 4.3
Kolten Wong 0.84 0.52 0.43 0.85 0.79 0.83 4.3
Mitch Garver 0.54 0.90 0.79 0.43 0.93 0.68 4.3
Lorenzo Cain 0.81 0.51 0.70 0.96 0.60 0.69 4.3
Austin Hays 0.79 0.75 0.76 0.55 0.74 0.68 4.3
Buster Posey 0.89 0.72 0.67 0.07 0.94 0.96 4.2
Luis Urias 0.77 0.73 0.73 0.58 0.81 0.61 4.2
Jake Rogers 0.75 0.85 0.75 0.56 0.83 0.50 4.2
Akil Baddoo 0.70 0.52 0.64 0.94 0.73 0.72 4.2
Yuli Gurriel 0.78 0.45 0.75 0.34 0.90 0.99 4.2
Josh Bell 0.73 0.85 0.89 0.12 0.86 0.73 4.2
Garrett Cooper 0.56 0.67 0.73 0.42 0.90 0.90 4.2
Andrew McCutchen 0.78 0.84 0.79 0.63 0.76 0.38 4.2
Brad Miller 0.82 0.89 0.72 0.56 0.75 0.42 4.2
Joey Gallo 0.87 0.96 0.69 0.61 0.84 0.21 4.2
Eduardo Escobar 0.67 0.84 0.87 0.34 0.80 0.64 4.2
Andrew Benintendi 0.51 0.58 0.76 0.71 0.72 0.86 4.2
Giancarlo Stanton 0.44 0.95 0.96 0.03 0.92 0.84 4.1
Jake Cronenworth 0.87 0.61 0.54 0.51 0.83 0.79 4.1
Lourdes Gurriel Jr. 0.49 0.71 0.89 0.35 0.80 0.86 4.1
Jose Rondon 0.86 0.62 0.41 0.83 0.63 0.75 4.1
Connor Joe 0.42 0.70 0.95 0.22 0.90 0.91 4.1
Bobby Dalbec 0.44 0.92 0.98 0.45 0.81 0.51 4.1
Eric Haase 0.64 0.94 0.92 0.47 0.67 0.44 4.1
Jean Segura 0.75 0.45 0.44 0.73 0.79 0.93 4.1
Mike Yastrzemski 0.83 0.84 0.74 0.54 0.73 0.40 4.1
Eddie Rosario 0.34 0.64 0.87 0.88 0.65 0.71 4.1
Jazz Chisholm Jr. 0.79 0.66 0.46 0.95 0.60 0.60 4.1
Dansby Swanson 0.55 0.75 0.76 0.70 0.71 0.60 4.1
Alex Bregman 0.77 0.56 0.78 0.38 0.76 0.81 4.1
Daulton Varsho 0.70 0.66 0.65 0.79 0.70 0.57 4.1
Taylor Ward 0.80 0.63 0.78 0.44 0.74 0.62 4.0
Francisco Lindor 0.81 0.70 0.64 0.79 0.63 0.43 4.0
Austin Slater 0.66 0.72 0.46 0.97 0.67 0.52 4.0
Alex Verdugo 0.86 0.37 0.46 0.62 0.76 0.93 4.0
Wilmer Flores 0.71 0.75 0.65 0.37 0.78 0.74 4.0
Tony Kemp 0.78 0.35 0.35 0.80 0.82 0.88 4.0
Miguel Sano 0.67 0.92 0.80 0.42 0.77 0.39 4.0
Danny Jansen 0.92 0.90 0.77 0.22 0.75 0.39 4.0
Christian Arroyo 0.58 0.62 0.78 0.48 0.73 0.74 3.9
Wil Myers 0.46 0.64 0.70 0.73 0.73 0.67 3.9
Willson Contreras 0.65 0.78 0.62 0.62 0.77 0.49 3.9
Joey Wendle 0.87 0.38 0.50 0.73 0.66 0.77 3.9
Max Schrock 0.83 0.39 0.46 0.54 0.76 0.92 3.9
Dustin Garneau 0.74 1.00 0.93 0.07 0.87 0.29 3.9

4 Creating Model File

4.1 Additional Data Prep

4.1.1 Remove Variables which are based off current hitting numbers

Not all variables can be used for predictive modeling. Variables that go into the percentile ranking or are non-normalized metrics created after the fact (such as WAR - Wins above Replacement or RE24) should be removed. However, metrics that are normalized by a per pitch basis (such as HR/FB%+) can remain as we expect batters to have similar performance in these metrics one year out.

#Creating a new dataset to keep original intact
df_batting_init3 = df_batting_init2

Lagged Percentile (_share) Variables can be used for predictive modeling. However since these variables were created for the Worth metric they must also be removed for modeling purposes.


#Order the dataset by lag columns
df_batting_init4 =  arrange(df_batting_init3, playerid,Season) #playerid is the Fangraph id assigned to each player

# Convert dataframe to data.table format
DT_batter2 = data.table(df_batting_init4)

#designate columns to lag - which is all of them
cols1 = (c('Runs_share','HR_share','RBI_share', 'SB_share','OPS_share','AVG_share','Worth'))
anscols = paste("lag", cols1, sep="_")
DT_batter2[, (anscols) := data.table::shift(.SD, 1, NA, "lag"),by ='playerid', .SDcols=cols1] #Create 1 period lags by year

#names(DT_batter2)

df_batting_final = as.data.frame(DT_batter2) %>% 
  select(-c(Runs_share,HR_share,RBI_share, SB_share,OPS_share,AVG_share))%>% 
  select(-(G:AVG),-(OBP:BABIP),-(wOBA:Dol),-(`wRC+`:REW),-(`WPA/LI`),-(wFB:wSF),-BsR,-(Def:wGDP),-(`wCH (pi)`:`wCH/C (pi)`),-(`AVG+`),-(`OBP+`:`BABIP+`),-(H_PA:PH_PA)) %>% select (-Name)

4.1.2 Creating Training/Test Split

We split the data into Training Data (which is used to create the model) and test data (which is used to validate the model)


set.seed(15674)  # For reproducibility
# Create index for testing and training data
inTrain <- createDataPartition(y = df_batting_final$Worth, p = 0.80, list = FALSE)
# subset pitching data for training
tr_2021 <- df_batting_final[inTrain,]
# subset the rest to test and validate trained model
te_2021 <- df_batting_final[-inTrain,]

nrow(tr_2021)/nrow(df_batting_final) #check if split is 0.8
[1] 0.8

4.1.3 Treat Missing Data by Imputing Mean Value

Vtreat Package in R is excellent for treating data before using for modeling. Additional documentation can be found here.
Note: The treatment plan also fixes variables names likeHR/FB%+ (which R doesn’t always handle the best) to HR_slash_FB_percent_plus_

treat_plan_2021 <- vtreat::designTreatmentsZ(
  dframe = tr_2021, # training data
  varlist = colnames(tr_2021) %>% .[. != "hitting_score1"], # input variables = all training data columns, except random
  codeRestriction = c("clean", "isBAD", "lev"), # derived variables types (drop cat_P)
  verbose = FALSE) # suppress messages

#clean stands for cleaned numerical variable, isBAD indicates that a value replacement has occurred (which indicates a missing value in this case), and lev is a binary indicator whether a particular value of that categorical variable was present.  

#### Checking Scoreframe

score_frame <- treat_plan_2021$scoreFrame %>% 
  select(varName, origName, code)

head(score_frame)


tr_treated_2021 <- vtreat::prepare(treat_plan_2021, tr_2021)
te_treated_2021 <- vtreat::prepare(treat_plan_2021, te_2021)


Total_dataset1_untreat = as.data.frame(DT_batter2) %>% select(-Name)

treat_plan_2021 <- vtreat::designTreatmentsZ(
  dframe = Total_dataset1_untreat, # training data
  varlist = colnames(Total_dataset1_untreat) %>% .[. != "hitting_score1"], # input variables = all training data columns, except random
  codeRestriction = c("clean", "isBAD", "lev"), # derived variables types (drop cat_P)
  verbose = FALSE) # suppress messages


total_treated_2021_hitting <- vtreat::prepare(treat_plan_2021, Total_dataset1_untreat)



#tr_treated = tr
#te_treated = te

dim(tr_treated_2021) #note there are dummies for each player and team
[1] 3424 1397

4.1.4 Check Distribution of Training Population

The population used for Training should be indicative of Total Population


ggplot2::qplot(tr_treated_2021$Worth, main="Training Set") + geom_histogram(colour="black", fill="limegreen") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

skewness(tr_treated_2021$Worth) #The skewness is the same as the overall
[1] -0.2

5 Running XGboost Model

To keep things simple with modeling, we’ll turn the training data into simple input variables for caret::train, dropping the response variable and converting the data frame to a matrix. Documentation for this approach to XGboost can be found here.

5.1 Tuning the Model

5.1.1 Initial Non-Tuned Model

Break the data set into x and y inputs with x being a matrix

input_x <- as.matrix(((tr_treated_2021))%>%
   select(-Worth) %>%                      
   select(!ends_with ("_isBAD")))

input_y <- tr_treated_2021$Worth

XGBoost with Default Hyperparameters
The Variable Importance (caret::varImp(xgb_base_2021, scale = F )) from the caret package shows the contribution of each variable to the initial model. As you can see SLG_plus_ (SLG+) takes up much of the importance as it is derived from SLG (one of the key contributors to Worth). These types of variables will be removed during variable selection in the next step.
XGBoost documentation can be found for more general models here.


#Defaults for xgboost model
grid_default <- expand.grid(
  nrounds = 100,
  max_depth = 6,
  eta = 0.3,
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = 1,
  subsample = 1
)

#This is a blank train_control set, this will be updated after
train_control <- caret::trainControl(
  method = "none",
  verboseIter = FALSE, # no training log
  allowParallel = TRUE # FALSE for reproducible results 
)

xgb_base_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = grid_default,
  method = "xgbTree",
  verbose = TRUE
)

caret::varImp(xgb_base_2021, scale = F  )
xgbTree variable importance

  only 20 most important variables shown (out of 765)

5.2 Further Variable Selection

5.2.1 Remove redundant and highly correlated variables

Selection Removal Step 1: Check for high correlations
Normally, this step is done early, but those steps were reserved for preparing the data


dep_cor1 <- t(as.data.frame(cor(tr_treated_2021[ , colnames(tr_treated_2021) != "Worth"],
                tr_treated_2021$Worth)))
dep_cor1 <-
as.data.frame(t(as.data.frame(dep_cor1)%>% 
  select(!starts_with("lag")) %>% #remove lag variables
  select(!contains("_isBAD")))) 

dep_cor1 <- tibble::rownames_to_column(dep_cor1,"VARIABLES")%>% #remove indicators for missing data
  filter(V1 > 0.70|V1 < -0.5)

dep_cor1

dep_cor2 <- colnames(row_to_names(t(dep_cor1),row_number = 1))

Let’s Remove variables with high correlation to worth metric (such as wFB/C)


input_x <- as.matrix(((tr_treated_2021))%>%
   select(-Worth) %>% #Remove dependent variable
     select (-all_of(dep_cor2) ) %>%      
select(!ends_with ("_isBAD"))) #indicator variable for missing data

input_y <- tr_treated_2021$Worth

Run the model on the new dataset to make sure the variable importances look fine


#Note Training parameters were set in initial model set up
xgb_base_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = grid_default,
  method = "xgbTree",
  verbose = TRUE
)

caret::varImp(xgb_base_2021, scale = F  )
xgbTree variable importance

  only 20 most important variables shown (out of 764)

5.3 Model with new data

5.3.1 Tuning All Hyperparameters

A tune grid allows us to test a large amount of hyper-parameters and find the model with the lowest RMSE for predictions.
However, The more values you want to test and the greater the amount of Cross-Fold Validations (method = "cv"), the greater the computational time it will take. More information on the specific parameters can be found here.


# maximum number of trees
nrounds <- 1000

# note to start nrounds from 200, as smaller learning rates result in errors so
# big with lower starting points that they'll mess the scales
tune_grid <- expand.grid(
  nrounds = seq(from = 100, to = nrounds, by = 50),
  eta = c(0.01, 0.025, 0.05, 0.1),
  max_depth = c(2, 4, 6, 8),
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = 1,
  subsample = 1
)

tune_control <- caret::trainControl(
  method = "cv", # cross-validation
  number = 5, # with n folds 
  ## Note this was # out in the original code
  #index = createFolds(tr_treated$Id_clean), # fix the folds
  verboseIter = FALSE, # no training log
  allowParallel = TRUE # FALSE for reproducible results 
)

Running the initial tuning model

#Note I will be timing these runs to give an estimate on how long this model takes to run
start_time <- Sys.time()

xgb_tune_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid,
  method = "xgbTree",
  verbose = FALSE
  ,verbosity = 0
)

end_time <- Sys.time()

end_time - start_time
Time difference of 13 mins

Tuning Plot and Variable Importance

varImp(xgb_tune_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 764)
# helper function for the plots
tuneplot <- function(x, probs = .90) {
  ggplot(x) +
    coord_cartesian(ylim = c(quantile(x$results$RMSE, probs = probs), min(x$results$RMSE))) +
    theme_bw()
}

tuneplot(xgb_tune_2021)

5.3.2 Fine Tuning Model

5.3.2.1 Second Tuning: Maximum Depth and Minimum Child Weight

After fixing the learning rate to 0.1 and we’ll also set maximum depth to 3 +-1 (or +2 if max_depth == 2) to experiment a bit around the suggested best tune in previous step. Then, well fix maximum depth and minimum child weigh

tune_grid2 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = ifelse(xgb_tune_2021$bestTune$max_depth == 2,
    c(xgb_tune_2021$bestTune$max_depth:4),
    xgb_tune_2021$bestTune$max_depth - 1:xgb_tune_2021$bestTune$max_depth + 1),
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = c(1, 2, 3),
  subsample = 1
)

xgb_tune2_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid2,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune2_2021)


xgb_tune2_2021$bestTune

varImp(xgb_tune2_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 764)

5.3.2.2 Third Tuning: Column and Row Sampling


tune_grid3 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = 0,
  colsample_bytree = c(0.4, 0.6, 0.8, 1.0),
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = c(0.5, 0.75, 1.0)
)

xgb_tune3_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid3,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune3_2021, probs = .95)


xgb_tune3_2021$bestTune

varImp(xgb_tune3_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 764)

5.3.2.3 Fourth Tuning: Gamma

Next, we again pick the best values from previous step, and now will see whether changing the gamma has any effect on the model fit:

tune_grid4 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = c(0, 0.05,0.1, 0.2,0.4, 0.5, 0.7, 0.9, 1.0),
  colsample_bytree = xgb_tune3_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = xgb_tune3_2021$bestTune$subsample
)

xgb_tune4_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid4,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune4_2021)
Warning: The shape palette can deal with a maximum of 6 discrete values because more than 6 becomes difficult to discriminate; you have 9.
Consider specifying shapes manually if you must have them.
Warning: Removed 60 rows containing missing values (geom_point).

xgb_tune4_2021$bestTune

varImp(xgb_tune4_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 764)

5.3.2.4 Fifth Tuning: Reducing the Learning Rate

Now, we have tuned the hyperparameters and can start reducing the learning rate to get to the final model:

start_time <- Sys.time()

tune_grid5 <- expand.grid(
  nrounds = seq(from = 100, to = 10000, by = 75),
   eta = c(0.01, 0.015, 0.025,0.035, 0.05,0.75, 0.1),
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = xgb_tune4_2021$bestTune$gamma,
  colsample_bytree = xgb_tune3_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = xgb_tune3_2021$bestTune$subsample
)



xgb_tune5_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid5,
  method = "xgbTree",
  verbose = TRUE
)

#tuneplot(xgb_tune5_2021)

end_time <- Sys.time()

end_time - start_time
Time difference of 29 mins
xgb_tune5_2021$bestTune

varImp(xgb_tune5_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 764)

5.3.2.5 Fitting Final Model


(final_grid_2021 <- expand.grid(
  nrounds = xgb_tune5_2021$bestTune$nrounds,
  eta = xgb_tune5_2021$bestTune$eta,
  max_depth = xgb_tune5_2021$bestTune$max_depth,
  gamma = xgb_tune5_2021$bestTune$gamma,
  colsample_bytree = xgb_tune5_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune5_2021$bestTune$min_child_weight,
  subsample = xgb_tune5_2021$bestTune$subsample
))

(xgb_model_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = final_grid_2021,
  method = "xgbTree",
  verbose = TRUE
))
eXtreme Gradient Boosting 

3424 samples
 764 predictor

No pre-processing
Resampling: None 
varImp(xgb_model_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 764)

5.3.3 Model Performance

5.3.3.1 Checking Model on Test Split Data



y_pred_test <- predict(xgb_model_2021, data.matrix(te_treated_2021))

test_stats= cbind((te_treated_2021$Worth),y_pred_test)

test_statsR2 = cor(test_stats[,1],test_stats[,2])^2

print(test_statsR2)
[1] 0.92
y_pred_train <- predict(xgb_model_2021, data.matrix(tr_treated_2021))

train_stats = cbind((tr_treated_2021$Worth),y_pred_train)

train_statsR2 = cor(train_stats[,1],train_stats[,2])^2

print(train_statsR2)
[1] 0.99
#test dataset
x <- select(te_treated_2021, -Worth)
y <- (te_treated_2021$Worth)

(xgb_model_rmse <- ModelMetrics::rmse(y, predict(xgb_model_2021, newdata = x)))
[1] 0.37
holdout_x <- select(tr_treated_2021, -Worth)
holdout_y <- tr_treated_2021$Worth

(xgb_model_rmse <- ModelMetrics::rmse(holdout_y, predict(xgb_model_2021, newdata = holdout_x)))
[1] 0.11

5.3.3.2 Graphical Representation of Model


ggplot2::ggplot() +
  aes(x = test_stats[,1], y = test_stats[,2]) +
  geom_jitter() +
  xlab("Predicted Values") +
  ylab("Actual Values") +
  ggtitle("Results of Hitting Model on Test Data")+
  theme(plot.title = element_text(hjust = 0.5,size = 22,color ="steel blue"))+
  geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

6 Creating 2022 Projections from Model

6.1 Re-fit model for Important Variables

Now that we have an acceptable model, we can use it to create projections for how well we think players should do in 2022 based on their hitting statistics in 2021. First let’s reduce

  1. Only keep variables with high enough importance in model


vip(xgb_model_2021, num_features = 30)  # 10 is the default, 30 gives a visual on the top 30 most important features of the model


unscalevi = vi(xgb_model_2021, method="model") #shows the numbers behind the plot

unscalevi$Importance_perc = with(unscalevi,Importance/sum(Importance)) #adds percentages 

unscalevi # importance by variables

variables_to_keep_2021 = subset(unscalevi, Importance_perc > 0.0010) %>% select(Variable) #Keep Variables that explain at least a small amount [0.1%] of the model. This is a low threshold for inclusion ,but you can adjust this

variables_to_keep_2021b = t(variables_to_keep_2021)

variables_to_keep_2022 = colnames(row_to_names(variables_to_keep_2021b,row_number = 1))

tr_treated_2022 = tr_treated_2021 %>%  select(Worth,one_of(variables_to_keep_2022),playerid,starts_with("Team_lev_x_")) #keep modeled important variables along with team indicator variables

te_treated_2022 = te_treated_2021 %>%  select(Worth,one_of(variables_to_keep_2022),playerid,starts_with("Team_lev_x_"))

input_x_2022 = as.matrix(select(tr_treated_2022, -Worth))

input_y_2022 = tr_treated_2022$Worth
  1. Re-fit model with reduced variable scope


(final_grid_2021 <- expand.grid(
  nrounds = xgb_tune5_2021$bestTune$nrounds,
  eta = xgb_tune5_2021$bestTune$eta,
  max_depth = xgb_tune5_2021$bestTune$max_depth,
  gamma = xgb_tune5_2021$bestTune$gamma,
  colsample_bytree = xgb_tune5_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune5_2021$bestTune$min_child_weight,
  subsample = xgb_tune5_2021$bestTune$subsample
))

(xgb_model_2022 <- caret::train(
  x = input_x_2022,
  y = input_y_2022,
  trControl = train_control,
  tuneGrid = final_grid_2021,
  method = "xgbTree",
  verbose = TRUE
))
eXtreme Gradient Boosting 

3424 samples
  79 predictor

No pre-processing
Resampling: None 
vip(xgb_model_2022, num_features = 30)


unscalevi24 = vi(xgb_model_2022, method="model")

unscalevi24$Importance_perc = with(unscalevi24,Importance/sum(Importance)) 

unscalevi24

save(xgb_model_2022,file = '2022_Hitting6x6_Model.Rdata')

hitting6x6 = xgb_model_2022

hittinginput6X6 = input_x_2022

#For anything above breaking_IP we need to create projection table by age or age bucket

#write_csv(unscalevi24,"unscalevi24.csv")
# 2022 Projections Full
First let’s prepare a file for predicting based on our model object
```r
variableslag6xb= row_to_names(as.data.frame(t(variables_to_keep_2022)),row_number = 1) %>% select (starts_with(“lag”))
variables_nolag6xb = (owmr::remove_prefix(variableslag6xb,“lag” , sep = “_“))
Data_Predict_2022a6xb = total_treated_2021_hitting %>% select (one_of(colnames(variables_nolag6xb)),Season,playerid)
colnames(Data_Predict_2022a6xb) <- paste0(“lag_”, colnames(Data_Predict_2022a6xb))
Data_Predict_2022b6xb = total_treated_2021_hitting %>% select (one_of(colnames(variables_nolag6xb))) colnames(Data_Predict_2022b6xb) = colnames(variableslag6xb)
variables_to_keep_2022_nolag6xb = total_treated_2021_hitting %>% select(one_of(variables_to_keep_2022),Season,playerid,starts_with(“Team_lev_x_”))%>% select(-one_of(colnames(Data_Predict_2022b6xb)))
Data_predict_20226xb = sqldf( ” select a.,b. from Data_Predict_2022a6xb a, variables_to_keep_2022_nolag6xb b on b.playerid = a.lag_playerid and b.Season = a.lag_Season ” ) %>% select(-lag_playerid,lag_Season) %>% filter(Season == 2021) %>% select(one_of(variables_to_keep_2022),playerid,starts_with(“Team_lev_x_”))
```

6.2 Create Predictions for Model

6.2.1 Run Projections on Players who Played in 2021

This is the raw prediction score per IP for each pitcher


hitting_predictions6xb = as.data.frame(predict(xgb_model_2022,Data_predict_20226xb))

names(hitting_predictions6xb) = c("Predict_Score")

Data_predict_2022_w_hitting_Predictions6xb = cbind(Data_predict_20226xb,hitting_predictions6xb) %>% select(playerid,Predict_Score)

head(Data_predict_2022_w_hitting_Predictions6xb)
NA


Latest_2022_hittingdata_FP = read_csv("FanGraph_Fantasy_Baseball_Hitting.csv")
Rows: 625 Columns: 28
-- Column specification ----------------------------------------------------------------------------------------------------------------------------------
Delimiter: ","
chr  (3): Name, Team, playerid
dbl (25): G, PA, AB, H, 2B, 3B, HR, R, RBI, BB, SO, HBP, SB, CS, AVG, OBP, SLG, OPS, wOBA, wRC+, WAR, ADP, InterSD, InterSK, IntraSD

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Latest_2022_hittingdata_FP
NA
NA



hitting_Data_NonAdj_Projections6xb = sqldf(
  "
  select a.*,b.Predict_Score
  from Latest_2022_hittingdata_FP a 
  left join 
  Data_predict_2022_w_hitting_Predictions6xb b
  on a.playerid = b.playerid
  "
) %>% filter(ADP<370 | is.na(Predict_Score)==F)


hitting_Data_Adj_Projections6xb =
hitting_Data_NonAdj_Projections6xb %>% 
  mutate(
    Avg_PA = 300,
    AdjPredict_Score_raw = ifelse(is.na(Predict_Score),NA,Predict_Score*(PA/Avg_PA)),
    max_predscore= max(AdjPredict_Score_raw,na.rm = T),
    AdjPredict_Score = ifelse (is.na(AdjPredict_Score_raw),NA,AdjPredict_Score_raw *100/max_predscore),
    WAR_rank = order(order(rank(WAR,ties.method = 'average'),decreasing = TRUE)),
    AdjPredict_Score_Rank = order(order(rank(AdjPredict_Score,ties.method = 'average'),decreasing = TRUE))-sum(is.na(AdjPredict_Score)),
        Ranks_Above_ADP = ADP - AdjPredict_Score_Rank
  ) %>% select (Name,ADP,WAR, WAR_rank,AdjPredict_Score ,AdjPredict_Score_Rank,Ranks_Above_ADP)


  

ggplot2::qplot(hitting_Data_Adj_Projections6xb$AdjPredict_Score, main="Predictions") + geom_histogram(colour="black", fill="grey") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.


7 2022 Projections Full

7.1 Table of hitting Projections (Players who Didn’t Play in 2021 - Recieve an NA)

AdjPredict_Score are normalized to 100


tableexport =
hitting_Data_Adj_Projections6xb %>%
  arrange (ADP,WAR) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)

save_kable(tableexport,file = "hitting6x6.html")

#tableexport

This is a better formatted Table



ft_dt <- hitting_Data_Adj_Projections6xb[1:nrow(hitting_Data_Adj_Projections6xb), 1:ncol(hitting_Data_Adj_Projections6xb)] %>% 
  filter(AdjPredict_Score_Rank>0)%>%  arrange((AdjPredict_Score_Rank))

ft_dt$ADP <- color_tile("white", "red")(ft_dt$ADP)

ft_dt$WAR <- color_bar("lightblue")(ft_dt$WAR)

ft_dt$AdjPredict_Score<- color_bar("lightblue")(ft_dt$AdjPredict_Score)

ft_dt$WAR_Rank <- color_tile("green","orange")(ft_dt$WAR_rank)

ft_dt$Predict_Rank <- color_tile("green","orange")(ft_dt$AdjPredict_Score_Rank) 


ft_dt$Ranks_Above_ADP <- 
  ifelse(
  ft_dt$Ranks_Above_ADP < 0,
  cell_spec(round(ft_dt$Ranks_Above_ADP,2), color = "red", italic = T),
  cell_spec(round(ft_dt$Ranks_Above_ADP,2), color = "green", italic = T)
)


ft_dt2 <- ft_dt[c("Name", "ADP", "WAR", "AdjPredict_Score", "WAR_Rank","Predict_Rank","Ranks_Above_ADP")]



table_export = 
kbl(ft_dt2, escape = F) %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T) %>%   column_spec(6, width = "3cm") %>%
  add_header_above(c(" ", "Scores" = 3, "Ranks" = 2," "))
save_kable(table_export,file = "Hitting6x6_updated.html")
  
table_export  
Scores
Ranks
Name ADP WAR AdjPredict_Score WAR_Rank Predict_Rank Ranks_Above_ADP
Marcus Semien 39.0 4.7 100.00 14 1 38
Bryce Harper 9.1 5.2 99.95 8 2 7.1
Vladimir Guerrero Jr. 5.6 5.6 98.11 5 3 2.6
Trea Turner 1.4 5.5 97.08 6 4 -2.6
Paul Goldschmidt 47.6 3.5 96.51 44 5 42.6
José Ramírez 3.4 5.9 96.06 3 6 -2.6
Bo Bichette 4.9 4.7 94.81 13 7 -2.1
Freddie Freeman 19.2 5.1 94.63 9 8 11.2
Rafael Devers 14.5 4.8 94.54 11 9 5.5
Shohei Ohtani 9.2 3.9 94.39 34 10 -0.8
Juan Soto 3.9 7.6 94.04 1 11 -7.1
Nick Castellanos 60.6 2.5 93.49 81 12 48.6
Luis Robert 16.2 4.6 93.25 16 13 3.2
Kyle Tucker 12.7 4.6 93.18 15 14 -1.3
Teoscar Hernández 27.8 2.3 92.29 98 15 12.8
Mookie Betts 15.4 5.8 89.86 4 16 -0.6
Jorge Polanco 77.8 3.1 89.62 58 17 60.8
Cedric Mullins 32.8 3.3 89.55 52 18 14.8
Aaron Judge 36.0 5.3 89.23 7 19 17
Matt Olson 39.0 4.2 88.97 28 20 19
Ozzie Albies 17.6 4.1 88.83 32 21 -3.4
Pete Alonso 46.2 3.5 88.13 43 22 24.2
Mike Trout 13.0 6.6 87.97 2 23 -10
Jose Altuve 73.3 3.8 87.86 36 24 49.3
Manny Machado 22.1 4.5 87.73 20 25 -2.9
Bryan Reynolds 93.0 4.2 87.46 30 26 67
George Springer 57.8 4.3 87.14 26 27 30.8
Tim Anderson 30.9 3.2 87.03 55 28 2.9
Tyler O'Neill 47.1 2.8 86.93 67 29 18.1
Xander Bogaerts 45.8 4.5 85.46 19 30 15.8
Ronald Acuña Jr. 10.4 5.0 84.52 10 31 -20.6
Yordan Alvarez 26.3 4.1 84.50 31 32 -5.7
Trevor Story 36.8 4.2 84.37 29 33 3.8
Javier Báez 62.5 2.8 83.60 68 34 28.5
Jonathan India 90.7 3.8 83.60 35 35 55.7
Brandon Lowe 75.4 4.3 82.47 24 36 39.4
Kris Bryant 84.9 2.4 82.30 86 37 47.9
Austin Riley 51.8 3.1 82.28 59 38 13.8
Frank Schwindel 233.8 0.8 81.82 262 39 194.8
Byron Buxton 48.0 4.4 80.75 23 40 8
Jesse Winker 111.8 3.4 80.71 45 41 70.8
José Abreu 78.4 2.1 80.56 114 42 36.4
Ketel Marte 77.2 3.7 80.38 37 43 34.2
Mitch Haniger 109.0 2.4 80.23 89 44 65
Starling Marte 31.2 2.4 80.18 84 45 -13.8
Salvador Perez 32.2 3.7 79.95 38 46 -13.8
Joey Votto 141.9 1.9 79.69 130 47 94.9
Nolan Arenado 67.7 3.7 79.03 39 48 19.7
Kyle Schwarber 114.5 2.7 79.00 71 49 65.5
J.D. Martinez 88.9 2.0 78.49 116 50 38.9
Rhys Hoskins 126.3 3.0 77.54 61 51 75.3
Randy Arozarena 65.4 2.1 77.53 113 52 13.4
Franmil Reyes 120.9 1.8 77.34 132 53 67.9
Avisaíl García 170.2 2.3 77.03 93 54 116.2
Wander Franco 58.9 4.5 76.70 21 55 3.9
Ryan Mountcastle 119.7 1.6 76.37 161 56 63.7
Brandon Crawford 219.2 2.7 75.82 70 57 162.2
Carlos Correa 100.9 4.7 75.22 12 58 42.9
Brandon Belt 226.3 2.5 74.80 80 59 167.3
Corey Seager 78.3 4.5 74.79 22 60 18.3
Jared Walsh 119.8 2.2 74.41 101 61 58.8
Willy Adames 123.2 3.3 73.96 49 62 61.2
Dansby Swanson 119.1 2.7 73.62 72 63 56.1
C.J. Cron 124.7 1.7 73.33 154 64 60.7
Max Muncy 134.9 3.2 72.27 54 65 69.9
Jake Cronenworth 127.1 3.4 72.23 48 66 61.1
Hunter Renfroe 161.4 1.7 71.92 153 67 94.4
Francisco Lindor 53.5 4.2 71.53 27 68 -14.5
J.T. Realmuto 48.8 3.6 71.20 40 69 -20.2
Joey Gallo 177.0 3.5 71.12 42 70 107
Josh Bell 127.6 1.9 71.02 129 71 56.6
Justin Turner 152.7 3.0 70.54 62 72 80.7
Nelson Cruz 171.9 1.4 70.54 181 73 98.9
Eduardo Escobar 200.5 1.9 69.73 131 74 126.5
Austin Meadows 145.5 1.5 69.72 168 75 70.5
Whit Merrifield 32.5 2.3 69.04 97 76 -43.5
Giancarlo Stanton 97.7 2.3 68.42 99 77 20.7
Kolten Wong 190.8 2.5 68.12 79 78 112.8
Anthony Rizzo 193.6 2.1 67.38 109 79 114.6
Ty France 154.6 2.4 67.17 88 80 74.6
Jean Segura 202.3 2.4 67.02 91 81 121.3
Jazz Chisholm Jr. 76.0 1.8 66.56 138 82 -6
Dylan Carlson 172.0 2.5 66.20 77 83 89
Will Smith 53.2 4.6 65.75 17 84 -30.8
Chris Taylor 139.9 2.3 65.74 95 85 54.9
Austin Hays 243.2 1.7 65.74 150 86 157.2
Adolis García 166.0 1.3 65.73 201 87 79
Enrique Hernández 226.7 2.8 65.73 66 88 138.7
Ryan McMahon 161.2 2.0 65.39 119 89 72.2
Mark Canha 283.4 1.8 65.26 141 90 193.4
Luis Urías 173.3 2.5 64.82 75 91 82.3
Alex Bregman 88.3 4.5 64.79 18 92 -3.7
Lourdes Gurriel Jr. 145.2 1.4 64.25 187 93 52.2
Jonathan Schoop 213.2 1.7 64.03 145 94 119.2
Andrew Benintendi 192.7 1.9 64.02 125 95 97.7
Alex Verdugo 168.6 2.4 63.99 85 96 72.6
Yuli Gurriel 210.6 1.4 63.75 189 97 113.6
Akil Baddoo 156.9 1.6 63.45 158 98 58.9
Yasmani Grandal 101.1 4.0 63.38 33 99 2.1
Jesús Sánchez 226.3 2.0 62.86 115 100 126.3
Mike Yastrzemski 286.5 2.2 62.81 108 101 185.5
AJ Pollock 218.6 1.8 62.28 136 102 116.6
Nathaniel Lowe 251.7 1.8 62.25 133 103 148.7
Miguel Sanó 280.4 0.8 62.04 271 104 176.4
Robbie Grossman 191.3 1.7 61.68 149 105 86.3
Max Kepler 294.6 2.5 61.62 76 106 188.6
Trent Grisham 143.5 3.1 61.50 57 107 36.5
Jeimer Candelario 239.6 3.2 61.14 56 108 131.6
Ian Happ 213.2 2.2 61.05 104 109 104.2
Yoán Moncada 158.7 3.4 60.86 47 110 48.7
Harrison Bader 235.8 2.9 60.62 63 111 124.8
Tyler Naquin 352.7 0.7 60.57 274 112 240.7
Charlie Blackmon 236.7 0.6 59.83 301 113 123.7
Brendan Rodgers 169.9 1.6 59.65 160 114 55.9
Tommy Edman 90.2 2.3 59.59 94 115 -24.8
Christian Yelich 97.2 2.9 59.12 64 116 -18.8
Ramón Laureano 247.0 2.6 58.58 74 117 130
LaMonte Wade Jr. 343.8 1.0 58.23 233 118 225.8
Patrick Wisdom 358.3 1.2 57.95 214 119 239.3
Amed Rosario 170.7 2.0 57.87 118 120 50.7
Jorge Soler 192.2 1.7 57.55 147 121 71.2
Josh Donaldson 187.1 3.3 57.21 51 122 65.1
Eloy Jiménez 65.1 2.2 57.01 105 123 -57.9
Andrew McCutchen 348.9 0.9 56.86 254 124 224.9
Bobby Dalbec 229.2 1.6 56.81 157 125 104.2
Adam Frazier 400.3 2.0 56.78 117 126 274.3
Trey Mancini 205.0 1.4 56.69 180 127 78
Eddie Rosario 181.7 0.9 56.52 251 128 53.7
Brandon Nimmo 324.3 3.2 56.45 53 129 195.3
Willson Contreras 109.9 2.7 56.44 69 130 -20.1
Adalberto Mondesi 57.3 2.0 56.27 123 131 -73.7
Adam Duvall 212.8 1.7 56.11 148 132 80.8
Wil Myers 288.9 1.0 56.03 231 133 155.9
Rafael Ortega 337.3 1.3 55.28 193 134 203.3
Seth Brown 502.2 0.6 55.16 293 135 367.2
Nicky Lopez 250.4 1.8 55.05 140 136 114.4
Matt Chapman 181.5 3.6 54.39 41 137 44.5
Tony Kemp 459.8 1.6 54.22 155 138 321.8
Fernando Tatis Jr. 27.5 3.4 54.02 46 139 -111.5
Josh Rojas 247.6 1.5 53.63 167 140 107.6
Jo Adell 228.4 0.6 53.30 300 141 87.4
Michael Brantley 251.4 2.0 52.69 122 142 109.4
Miguel Rojas 475.9 2.3 52.48 96 143 332.9
Joey Wendle 403.7 1.3 51.66 194 144 259.7
Jesús Aguilar 300.5 1.2 51.03 215 145 155.5
Gavin Sheets 525.8 0.8 50.88 265 146 379.8
Daulton Varsho 93.3 2.1 50.85 110 147 -53.7
J.P. Crawford 452.2 2.5 50.81 83 148 304.2
Tyler Stephenson 138.3 2.9 50.42 65 149 -10.7
Ke'Bryan Hayes 142.6 3.3 49.79 50 150 -7.4
Tommy Pham 274.8 1.9 49.76 126 151 123.8
Anthony Santander 298.7 1.3 49.58 197 152 146.7
Mike Zunino 253.5 2.0 49.34 124 153 100.5
Eugenio Suárez 198.5 2.2 49.10 107 154 44.5
César Hernández 403.0 1.7 48.99 152 155 248
DJ LeMahieu 119.2 2.4 48.80 87 156 -36.8
Lorenzo Cain 451.3 1.5 47.99 176 157 294.3
Alex Kirilloff 177.6 1.5 47.90 165 158 19.6
Evan Longoria 455.5 1.5 47.66 174 159 296.5
Mitch Garver 166.1 2.4 47.43 90 160 6.1
Isiah Kiner-Falefa 296.8 1.6 47.29 163 161 135.8
Michael Conforto 207.5 2.7 46.72 73 162 45.5
Jonathan Villar 253.1 1.1 46.42 225 163 90.1
Jake Meyers 593.2 1.1 46.36 217 164 429.2
Myles Straw 127.0 2.3 46.30 100 165 -38
Eric Hosmer 470.1 0.4 46.29 329 166 304.1
Raimel Tapia 273.9 0.1 46.23 384 167 106.9
Andrew Vaughn 250.3 1.2 46.13 207 168 82.3
Randal Grichuk 419.6 0.6 45.75 292 169 250.6
Luke Voit 253.5 1.3 45.06 199 170 83.5
Connor Joe 341.2 0.8 44.99 255 171 170.2
Gleyber Torres 154.9 2.5 44.96 78 172 -17.1
Brad Miller 551.8 1.4 44.72 179 173 378.8
Pavin Smith 476.9 0.6 44.58 286 174 302.9
Wilmer Flores 472.5 1.2 44.38 208 175 297.5
Nick Madrigal 342.1 1.8 44.36 139 176 166.1
Yandy Díaz 464.5 1.6 43.94 156 177 287.5
Odúbel Herrera 583.2 1.1 43.70 228 178 405.2
Ramón Urías 529.7 1.6 43.62 162 179 350.7
Lane Thomas 269.2 1.1 43.28 226 180 89.2
Paul DeJong 491.4 2.1 43.27 112 181 310.4
Sam Hilliard 477.8 0.5 43.17 309 182 295.8
Joc Pederson 480.8 0.8 43.03 270 183 297.8
David Peralta 518.1 1.1 42.92 221 184 334.1
Luis Arraez 351.3 1.5 42.86 177 185 166.3
Bobby Bradley 491.0 0.5 42.52 308 186 305
Adam Engel 597.3 0.6 42.42 290 187 410.3
Manuel Margot 416.6 1.1 42.41 229 188 228.6
Josh Harrison 412.3 1.1 42.33 224 189 223.3
Jarred Kelenic 141.5 1.5 41.87 169 190 -48.5
Alcides Escobar 586.4 0.2 41.77 374 191 395.4
David Fletcher 417.0 1.3 41.64 191 192 225
Darin Ruf 454.5 0.9 41.61 247 193 261.5
Jeff McNeil 323.0 1.9 41.47 128 194 129
José Iglesias 547.7 1.2 41.30 210 195 352.7
Keibert Ruiz 145.4 2.2 41.24 106 196 -50.6
Garrett Cooper 527.1 1.0 41.17 238 197 330.1
Ji-Man Choi 577.3 0.7 40.70 282 198 379.3
Ben Gamel 579.0 0.9 40.53 242 199 380
Michael A. Taylor 516.5 1.2 40.05 212 200 316.5
Kyle Lewis 399.0 1.5 40.01 170 201 198
Kyle Farmer 528.0 1.1 39.87 220 202 326
Chas McCormick 495.8 1.1 39.85 218 203 292.8
Tyrone Taylor 526.6 1.1 39.84 222 204 322.6
Brian Anderson 495.9 1.8 39.79 137 205 290.9
Anthony Rendon 110.2 4.3 39.62 25 206 -95.8
Elias Díaz 229.7 1.3 39.11 198 207 22.7
Max Stassi 300.6 1.8 38.90 135 208 92.6
Jake Fraley 547.6 0.9 38.79 241 209 338.6
Hunter Dozier 443.8 0.6 38.60 288 210 233.8
Gio Urshela 306.8 1.3 38.29 192 211 95.8
Alec Bohm 311.6 1.4 38.25 188 212 99.6
Kevin Kiermaier 588.0 1.2 38.24 203 213 375
Chad Pinder 570.3 0.6 37.37 291 214 356.3
Leury García 536.2 0.7 37.13 281 215 321.2
Eric Haase 302.9 0.7 36.82 278 216 86.9
Christian Walker 506.6 0.7 36.67 276 217 289.6
Carlos Santana 511.8 0.7 36.64 272 218 293.8
Danny Jansen 403.1 1.9 36.18 127 219 184.1
Carson Kelly 280.7 2.0 36.18 120 220 60.7
Steven Duggar 597.8 0.7 35.95 275 221 376.8
Gavin Lux 266.4 1.5 35.79 178 222 44.4
Rowdy Tellez 342.4 0.8 35.59 268 223 119.4
Marcell Ozuna 174.3 2.2 35.55 103 224 -49.7
Yadiel Hernandez 583.5 0.1 35.52 400 225 358.5
Sean Murphy 252.4 2.5 35.18 82 226 26.4
Abraham Toro 323.7 1.4 35.13 190 227 96.7
Andy Ibáñez 563.1 1.5 35.07 175 228 335.1
Edmundo Sosa 501.9 1.5 34.71 172 229 272.9
Miguel Cabrera 539.9 -0.4 34.53 500 230 309.9
Gary Sánchez 245.3 1.3 34.33 196 231 14.3
Tommy La Stella 561.3 1.2 34.17 213 232 329.3
Kyle Isbel 542.5 0.6 34.00 299 233 309.5
Yoshi Tsutsugo 431.9 0.3 33.96 336 234 197.9
Jordan Luplow 600.6 0.8 33.83 263 235 365.6
Justin Upton 565.1 0.3 33.82 349 236 329.1
Andrés Giménez 282.5 1.7 33.51 146 237 45.5
Brandon Marsh 418.4 1.2 33.43 209 238 180.4
Cody Bellinger 98.9 3.1 33.43 60 239 -140.1
Garrett Hampson 299.6 0.3 33.38 340 240 59.6
Austin Slater 575.2 1.0 33.27 232 241 334.2
Christian Vázquez 201.1 1.7 33.26 142 242 -40.9
Bradley Zimmer 505.3 0.7 32.70 280 243 262.3
Nick Solak 494.8 0.7 32.51 284 244 250.8
Yadier Molina 322.4 1.2 32.43 211 245 77.4
Kole Calhoun 522.9 1.1 32.23 227 246 276.9
Anthony Alford 555.4 0.0 32.21 468 247 308.4
Kevin Pillar 590.7 0.4 32.16 314 248 342.7
Alejandro Kirk 237.1 1.7 32.10 144 249 -11.9
Nick Ahmed 581.5 1.1 31.97 223 250 331.5
Jurickson Profar 540.1 0.3 31.88 341 251 289.1
Bryan De La Cruz 534.7 0.9 31.53 240 252 282.7
Colin Moran 582.8 -0.1 31.44 494 253 329.8
Jed Lowrie 597.2 0.8 31.04 267 254 343.2
Cavan Biggio 319.3 1.4 30.99 184 255 64.3
Didi Gregorius 513.0 0.9 30.82 252 256 257
Lars Nootbaar 572.6 0.2 30.61 375 257 315.6
Ha-Seong Kim 349.8 1.5 30.60 173 258 91.8
Nick Senzel 422.0 1.0 30.27 234 259 163
Corey Dickerson 581.1 0.1 30.04 388 260 321.1
Willie Calhoun 451.5 0.2 30.00 359 261 190.5
Jacob Stallings 421.9 2.2 29.89 102 262 159.9
J.D. Davis 511.6 0.9 29.81 253 263 248.6
Omar Narváez 251.8 1.7 29.56 143 264 -12.2
Santiago Espinal 557.5 0.7 29.54 273 265 292.5
Luis García 556.8 0.7 29.43 277 266 290.8
Yan Gomes 442.1 1.5 29.31 166 267 175.1
Nico Hoerner 506.9 1.4 29.19 182 268 238.9
Aristides Aquino 585.4 0.4 29.07 333 269 316.4
Dominic Smith 456.4 0.5 29.02 304 270 186.4
Elvis Andrus 566.4 0.9 29.00 248 271 295.4
Michael Chavis 569.3 0.0 28.96 477 272 297.3
Ryan Jeffers 480.5 1.6 28.59 159 273 207.5
Thairo Estrada 600.3 0.8 28.55 259 274 326.3
Mike Moustakas 374.8 0.9 28.39 245 275 99.8
Josh Naylor 587.4 0.5 28.26 306 276 311.4
Rougned Odor 504.9 0.6 28.11 289 277 227.9
Jorge Mateo 472.9 0.5 28.09 312 278 194.9
Jake McCarthy 600.8 0.0 27.82 429 279 321.8
Travis d'Arnaud 224.4 1.4 27.34 183 280 -55.6
Oscar Mercado 593.3 0.7 26.97 285 281 312.3
Austin Nola 377.8 1.5 26.90 171 282 95.8
Luis Torrens 446.4 0.7 26.55 279 283 163.4
Victor Reyes 592.8 0.4 26.27 316 284 308.8
Aledmys Díaz 573.3 0.9 25.96 243 285 288.3
Niko Goodrum 571.4 0.7 25.90 283 286 285.4
Alex Dickerson 597.9 0.2 25.34 357 287 310.9
Francisco Mejía 492.9 1.3 25.26 202 288 204.9
James McCann 351.5 1.4 24.39 186 289 62.5
Brett Phillips 590.7 0.3 24.18 337 290 300.7
Daniel Vogelbach 586.6 0.6 23.82 298 291 295.6
Aaron Hicks 524.7 1.6 23.74 164 292 232.7
Tucker Barnhart 471.5 1.3 23.71 200 293 178.5
Brett Gardner 598.6 0.6 23.57 297 294 304.6
Tyler Wade 505.0 0.4 23.51 331 295 210
Victor Robles 474.9 1.0 23.47 237 296 178.9
Stephen Piscotty 594.4 -0.2 23.41 497 297 297.4
Roberto Pérez 545.7 1.3 23.39 195 298 247.7
Donovan Solano 594.0 0.4 23.28 324 299 295
Jason Heyward 594.8 0.8 23.20 269 300 294.8
Kevin Newman 597.1 0.5 22.44 303 301 296.1
Manny Piña 570.7 1.2 22.42 205 302 268.7
Jarren Duran 497.2 0.4 22.24 327 303 194.2
Mitch Moreland 600.9 0.3 21.77 342 304 296.9
Jorge Alfaro 454.9 0.0 21.75 474 305 149.9
Matt Vierling 578.6 0.5 21.62 313 306 272.6
Yu Chang 596.6 0.4 21.45 334 307 289.6
Kyle Higashioka 555.9 1.0 21.33 236 308 247.9
DJ Stewart 600.3 0.5 21.32 305 309 291.3
Cole Tucker 586.6 0.4 20.09 325 310 276.6
Jace Peterson 579.4 0.2 20.02 361 311 268.4
Matt Beaty 591.6 0.2 19.96 358 312 279.6
Dom Nuñez 586.9 0.5 19.74 311 313 273.9
Matt Duffy 586.2 0.5 19.74 310 314 272.2
Pedro Severino 522.6 0.9 19.66 244 315 207.6
Josh VanMeter 597.6 0.4 19.59 323 316 281.6
Martín Maldonado 562.4 1.2 19.36 216 317 245.4
Trevor Larnach 565.1 0.1 19.26 390 318 247.1
Brent Rooker 597.9 0.3 19.16 348 319 278.9
Clint Frazier 472.5 0.3 19.12 345 320 152.5
Lewin Díaz 585.3 0.3 19.00 344 321 264.3
Carter Kieboom 539.8 0.8 18.82 258 322 217.8
Austin Hedges 587.0 0.5 18.76 307 323 264
Mike Brosseau 597.1 0.4 18.20 320 324 273.1
Albert Pujols 597.5 -0.3 17.99 499 325 272.5
Taylor Walls 587.7 1.1 17.93 219 326 261.7
Tom Murphy 539.9 1.2 17.55 204 327 212.9
Brian Goodwin 600.6 0.1 17.21 399 328 272.6
Asdrúbal Cabrera 599.6 0.0 17.10 425 329 270.6
Robinson Chirinos 595.5 0.6 17.08 294 330 265.5
Max Schrock 599.8 0.0 16.79 467 331 268.8
Ehire Adrianza 999.0 0.1 16.42 403 332 667
Derek Hill 999.0 0.1 16.30 387 333 666
Dylan Moore 509.0 0.4 16.21 326 334 175
Jason Castro 599.2 0.8 16.17 256 335 264.2
Keston Hiura 465.4 0.2 16.11 364 336 129.4
Andrew Stevenson 597.8 -0.1 15.79 482 337 260.8
Christian Arroyo 594.0 0.2 15.56 354 338 256
Riley Adams 593.0 0.8 15.52 257 339 254
Jackie Bradley Jr. 589.7 0.2 15.46 379 340 249.7
Kevin Plawecki 581.7 0.8 15.38 261 341 240.7
Austin Barnes 592.5 0.8 15.06 264 342 250.5
Jose Barrero 478.7 1.2 15.06 206 343 135.7
Andrelton Simmons 586.5 1.1 14.85 230 344 242.5
Yonathan Daza 598.7 -0.1 14.83 480 345 253.7
Kelvin Gutierrez 597.6 0.3 14.80 351 346 251.6
Curt Casali 592.1 0.9 14.52 250 347 245.1
Edward Olivares 584.8 0.3 14.31 346 348 236.8
Taylor Ward 600.9 0.4 14.18 322 349 251.9
Danny Santana 999.0 0.1 14.08 406 350 649
Adam Eaton 999.0 0.2 13.97 355 351 648
Nick Gordon 584.3 0.4 13.90 330 352 232.3
Jonah Heim 531.7 0.9 13.76 246 353 178.7
Daz Cameron 599.3 0.2 13.64 378 354 245.3
Harold Castro 599.7 -0.1 13.58 492 355 244.7
Ben Rortvedt 999.0 1.0 12.93 235 356 643
Jake Marisnick 999.0 0.2 12.48 381 357 642
Victor Caratini 582.5 0.8 12.48 260 358 224.5
Guillermo Heredia 999.0 -0.1 12.45 491 359 640
Sheldon Neuse 999.0 0.2 11.95 362 360 639
Shed Long Jr. 999.0 0.2 11.67 367 361 638
Harold Ramirez 574.0 0.2 11.51 373 362 212
Charlie Culberson 597.2 0.0 11.21 432 363 234.2
Jarrod Dyson 999.0 -0.2 11.19 495 364 635
Willi Castro 563.7 0.3 11.01 352 365 198.7
Jon Berti 576.3 0.4 10.83 328 366 210.3
Juan Lagares 999.0 0.1 10.83 417 367 632
Luis Rengifo 600.2 0.4 10.62 317 368 232.2
Wilson Ramos 600.9 0.6 10.62 296 369 231.9
Mauricio Dubón 596.2 0.3 10.62 335 370 226.2
Nomar Mazara 600.4 0.1 10.48 416 371 229.4
Leody Taveras 573.9 0.6 10.47 287 372 201.9
Starlin Castro 597.3 0.4 10.42 332 373 224.3
Jack Mayfield 600.9 0.1 10.36 407 374 226.9
Cristian Pache 573.0 0.8 10.35 266 375 198
Khris Davis 999.0 0.0 10.12 448 376 623
Tomás Nido 601.0 0.2 10.11 360 377 224
Hoy Park 600.7 0.4 9.80 315 378 222.7
Owen Miller 600.7 0.2 9.57 365 379 221.7
Hanser Alberto 600.3 0.2 9.53 356 380 220.3
Maikel Franco 590.0 0.0 9.42 444 381 209
Jose Trevino 596.6 0.3 9.31 343 382 214.6
Josh Reddick 999.0 -0.2 9.29 498 383 616
Zack Collins 598.5 0.6 9.26 302 384 214.5
Shogo Akiyama 600.8 0.0 9.24 449 385 215.8
Zach McKinstry 596.2 0.1 9.22 382 386 210.2
Seby Zavala 598.5 -0.1 9.14 485 387 211.5
David Bote 597.7 0.4 9.11 319 388 209.7
Ronald Torreyes 600.9 0.0 9.06 453 389 211.9
Eli White 599.7 0.1 8.92 414 390 209.7
Michael Perez 999.0 0.4 8.54 318 391 608
Andrew Velazquez 600.7 0.2 8.46 368 392 208.7
Orlando Arcia 600.8 0.3 8.44 350 393 207.8
Wilmer Difo 999.0 -0.1 8.38 487 394 605
Kurt Suzuki 599.5 0.2 8.26 369 395 204.5
Travis Shaw 600.5 0.1 8.16 418 396 204.5
Jake Rogers 600.8 0.3 8.14 347 397 203.8
Renato Núñez 599.6 0.2 8.00 380 398 201.6
Ryan O'Hearn 599.8 0.0 7.85 461 399 200.8
Miguel Andújar 595.5 0.0 7.73 439 400 195.5
Matt Carpenter 597.1 0.0 7.68 458 401 196.1
Kyle Garlick 999.0 0.0 7.67 471 402 597
Jake Cave 999.0 -0.1 7.45 490 403 596
Cal Raleigh 560.8 0.9 7.42 249 404 156.8
Joe Panik 999.0 0.1 7.29 422 405 594
Jose Rojas 600.4 0.0 7.09 450 406 194.4
Andrew Knizner 598.8 0.6 7.00 295 407 191.8
Sergio Alcántara 600.5 0.2 6.99 371 408 192.5
Emmanuel Rivera 999.0 0.1 6.96 393 409 590
Billy McKinney 599.2 0.0 6.74 438 410 189.2
Chris Owings 600.7 0.0 6.73 435 411 189.7
Danny Mendick 999.0 0.1 6.59 383 412 587
Stephen Vogt 999.0 0.1 6.55 409 413 586
Eric Sogard 999.0 0.0 6.49 430 414 585
Daniel Johnson 999.0 0.0 6.46 442 415 584
Ernie Clement 999.0 0.2 6.32 370 416 583
Luis Guillorme 999.0 0.3 6.29 353 417 582
Billy Hamilton 597.8 -0.1 6.28 489 418 179.8
Lewis Brinson 600.5 0.0 6.26 441 419 181.5
Rob Refsnyder 999.0 0.0 6.20 447 420 579
Yonny Hernandez 599.8 0.3 6.19 339 421 178.8
Marwin Gonzalez 600.4 0.2 6.05 376 422 178.4
Phil Gosselin 594.3 0.0 6.04 460 423 171.3
Tim Locastro 598.6 0.1 5.93 401 424 174.6
Jordy Mercer 999.0 -0.1 5.89 483 425 574
Brock Holt 999.0 0.1 5.77 423 426 573
Nick Maton 999.0 0.0 5.73 434 427 572
Sandy León 999.0 0.0 5.67 443 428 571
Isaac Paredes 600.3 0.4 5.55 321 429 171.3
José Peraza 600.8 0.1 5.45 410 430 170.8
Tres Barrera 999.0 0.1 5.38 398 431 568
Travis Jankowski 999.0 0.0 5.38 427 432 567
Taylor Trammell 600.9 0.1 5.18 396 433 167.9
Rodolfo Castro 999.0 0.1 5.14 386 434 565
Pablo Reyes 999.0 0.1 4.97 408 435 564
Taylor Jones 999.0 0.0 4.78 436 436 563
William Contreras 593.2 0.2 4.75 363 437 156.2
Rafael Marchan 999.0 0.1 4.62 392 438 561
Dustin Garneau 999.0 0.1 4.50 402 439 560
José Rondón 999.0 0.0 4.42 446 440 559
Cam Gallagher 600.6 0.3 4.23 338 441 159.6
Reese McGuire 599.1 0.2 4.21 372 442 157.1
DJ Peters 600.5 0.0 4.11 464 443 157.5
Jahmai Jones 600.7 0.1 4.00 411 444 156.7
Yermín Mercedes 596.9 0.1 3.88 415 445 151.9
Luke Williams 999.0 0.0 3.86 455 446 553
Drew Ellis 999.0 0.1 3.84 389 447 552
Roman Quinn 999.0 0.0 3.75 454 448 551
Matt Joyce 999.0 -0.1 3.67 484 449 550
Jake Lamb 999.0 0.1 3.65 404 450 549
Evan White 600.2 0.0 3.59 437 451 149.2
Isan Díaz 600.5 0.1 3.57 421 452 148.5
Richie Martin 999.0 -0.1 3.57 479 453 546
Alan Trejo 999.0 0.0 3.53 426 454 545
Andrew Knapp 600.9 0.0 3.53 462 455 145.9
Alex Jackson 999.0 0.2 3.40 366 456 543
Alex Blandino 999.0 -0.2 3.36 496 457 542
Jake Bauers 600.5 -0.1 3.27 486 458 142.5
Ryan McKenna 999.0 0.1 3.25 391 459 540
Zack Short 999.0 0.1 3.14 385 460 539
Aramis Garcia 999.0 0.0 3.14 457 461 538
Delino DeShields 999.0 0.0 3.13 451 462 537
Magneuris Sierra 999.0 -0.1 3.01 493 463 536
Donovan Walton 999.0 0.1 2.98 405 464 535
Ender Inciarte 600.0 0.0 2.98 465 465 135
Grayson Greiner 999.0 0.0 2.95 456 466 533
Jason Vosler 999.0 0.1 2.85 419 467 532
David Dahl 599.6 0.0 2.65 476 468 131.6
Skye Bolt 999.0 0.2 2.65 377 469 530
Daniel Robertson 999.0 0.0 2.61 440 470 529
John Nogowski 600.8 0.0 2.58 433 471 129.8
Gilberto Celestino 999.0 0.1 2.49 413 472 527
Austin Romine 999.0 0.1 2.47 395 473 526
Luke Raley 999.0 0.1 2.26 420 474 525
Abraham Almonte 999.0 0.0 2.26 459 475 524
Tucupita Marcano 600.6 0.0 2.13 428 476 124.6
Pat Valaika 999.0 0.0 1.99 469 477 522
Rio Ruiz 999.0 0.0 1.80 452 478 521
Jonathan Araúz 999.0 0.0 1.77 424 479 520
Webster Rivas 999.0 0.0 1.71 470 480 519
Wyatt Mathisen 999.0 0.0 1.68 431 481 518
Phillip Evans 999.0 0.0 1.59 478 482 517
Willians Astudillo 601.0 0.0 1.59 466 483 118
Jonathan Davis 999.0 -0.1 1.51 481 484 515
Patrick Mazeika 999.0 0.1 1.49 412 485 514
Ildemaro Vargas 999.0 0.0 1.41 445 486 513
Franchy Cordero 999.0 0.0 1.20 463 487 512
Chad Wallach 999.0 0.0 1.19 475 488 511
Justin Williams 999.0 -0.1 1.03 488 489 510
Edwin Ríos 587.4 0.0 0.91 472 490 97.4
Chance Sisco 999.0 0.1 0.80 397 491 508
Albert Almora Jr. 999.0 0.1 0.64 394 492 507
Robel García 999.0 0.0 0.50 473 493 506
NA
NA
NA
NA
NA
NA
LS0tDQp0aXRsZTogIldlbGNvbWUgdG8gbXkgMjAyMiBQcm9qZWN0aW9ucyBmb3IgSGl0dGVycyA2eDYiDQphdXRob3I6ICJEYXJzaGFuIFBhdGVsIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0aGVtZTogc2FuZHN0b25lDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIGZpZ19jYXB0aW9uOiB0cnVlDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KPGh0bWw+DQoNCjxwPg0KDQpQcm9qZWN0aW9ucyB1c2luZyBIeXBlcnR1bmVkIG1vZGVsIHRocm91Z2ggWEdib29zdA0KDQo8L3A+DQoNCjxwPg0KDQpBbGwgZGF0YSBpcyBmcm9tIFtGYW5HcmFwaHMuXShodHRwczovL3d3dy5mYW5ncmFwaHMuY29tLykgSSBoYXZlIG5vIGFmZmlsaWF0aW9uIHdpdGggRmFuR3JhcGhzLCBidXQgcGxlYXNlIGNvbnNpZGVyIGNvbnRyaWJ1dGluZyB0byB0aGVpciBbd2Vic2l0ZV0oaHR0cHM6Ly9wbHVzLmZhbmdyYXBocy5jb20vc2hvcC8pIGlmIHlvdSBmb3VuZCB0aGlzIHByb2plY3QgaW5mb3JtYXRpdmUuDQoNCjwvcD4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQprbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9ICdDOi9Vc2Vycy9BZG1pbi9Eb2N1bWVudHMvTGVhcm5pbmcgUHl0aG9uIEZvbGRlcjEvUHl0aG9uIEVzc2VuY2UgVHJhaW5pbmcvRmFudGFzeS1CYXNlYmFsbC9EYXRhJykNCm9wdGlvbnMoa25pdHIudGFibGUuZm9ybWF0ID0gImh0bWwiKSANCm9wdGlvbnMoZGlnaXRzPTIpDQpvcHRpb25zKHNjaXBlbiA9IDEwMCkNCmBgYA0KDQoNCiMgUHJvamVjdCBTY29wZSB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQojIyBPYmplY3RpdmUNCg0KVGhpcyBwcm9qZWN0IGlzIGRlc2lnbmVkIHRvIHNob3djYXNlIGhvdyBVc2luZyBhIFBlcmNlbnRpbGUgQmFzZWQgV29ydGggU3lzdGVtIHZhbHVlcyBGYW50YXN5IEJhc2ViYWxsIFBsYXllcnMgdGhyb3VnaCBhIElubmluZyBQaXRjaGVkIChJUCkgd2VpZ2h0ZWQgcHJvamVjdGlvbg0KDQpUaGUgQ2F0ZWdvcmllcyB1c2VkIGZvciBwcmVkaWN0aW9uIHZhbHVhdGlvbiBhcmUgeWVhci1lbmQgcmFua2luZ3MgZm9yIHRoZSBmb2xsb3dpbmcgbWV0cmljczoNCi0gICBIUnMNCi0gICBSdW5zDQotICAgUkJJcw0KLSAgIEJhdHRpbmcgQXZlcmFnZQ0KLSAgIFN0b2xlbiBCYXNlcw0KLSAgIE9QUw0KDQohW10oSW50cm9DaGFydDZ4Ni5wbmcpDQoNCg0KKioqICANCg0KIyBQcm9jZXNzaW5nIHRoZSBEYXRhIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCiMjIEdldHRpbmcgRGF0YSBJbnRvIFINCg0KIyMjIExvYWQgTGlicmFyaWVzDQoNCjxwIHN0eWxlPSJjb2xvcjpibGFjazsiPg0KDQoqRmlyc3Qgd2UgbmVlZCB0byBsb2FkIHRoZSBwYWNrYWdlcyB0aGF0IFIgbmVlZHMgdG8gcnVuIHRoZSBhbmFseXNpcyoNCg0KPC9wPg0KDQpgYGB7ciBsb2FkIGxpYnJhcnksbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHNxbGRmKSAjU1FMIGluIFINCmxpYnJhcnkoc2tpbXIpICNTdW1tYXJpZXMgYW5kIHVzZWZ1bCBmb3IgcmVtb3ZpbmcgbG93ICUgZGF0YQ0KbGlicmFyeShnZ3Bsb3QyKSAjUGxvdHRpbmcgRnVuY3Rpb25zDQpsaWJyYXJ5KHBseXIpICNzbGlnaHRseSBkZXByZWNhdGVkIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkoZHBseXIpICNzbGlnaHRseSB1cGRhdGVkIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkodGlkeXZlcnNlKSAjdGlkeXZlcnNlIGRhdGEgY2xlYW5pbmcgdW5pdmVyc2UNCmxpYnJhcnkoY2FyZXQpICN3cmFwcGVyIGZvciBjcmVhdGluZywgdHVuaW5nIGFuZCB2YWxpZGF0aW5nIG1vZGVscw0KbGlicmFyeSh4Z2Jvb3N0KSAjcGFja2FnZSBmb3IgY3JlYXRpbmcgcmVncmVzc2lvbiB0cmVlIG1vZGVsDQpsaWJyYXJ5KHZ0cmVhdCkgIyB1c2VmdWwgcGFja2FnZSBmb3IgdHJlYXRpbmcgZGF0YSBiZWZvcmUgbW9kZWxpbmcgDQpsaWJyYXJ5KE1hdHJpeCkNCmxpYnJhcnkoQm9ydXRhKQ0KbGlicmFyeShtZ2N2KQ0KbGlicmFyeShtb21lbnRzKSAjZm9yIG1lYXN1cmluZyBza2V3bmVzcw0KbGlicmFyeShkYXRhLnRhYmxlKSAjYWx0ZXJuYXRpdmUgdG8gZHBseXIgd2UgdXNlIHRvIGNyZWF0ZSBsYWdzDQpsaWJyYXJ5KHBkcCkgI3BhcnRpYWwgZGVwZW5kZW5jZSBncmFwaHMNCmxpYnJhcnkodmlwKSAjdmFyaWFibGUgaW1wb3J0YW5jZSANCmxpYnJhcnkoZ3JpZCkgI3B1dCBtdWx0aXBsZSBwbG90cyBvbiBvbmUgZ3JpZA0KbGlicmFyeShncmlkRXh0cmEpICNhZGRpdGlvbmFsIGdyaWQgZnVuY3Rpb25hbGl0eQ0KbGlicmFyeShqYW5pdG9yKSAjb25lIGZ1bmN0aW9uIHVzZWQgdG8gY2xlYW4gdHJhbnNwb3NlZCBkYXRhIHNldA0KbGlicmFyeShnZ3B1YnIpICNmb3IgcXEgcGxvdA0KbGlicmFyeSh0YWJsZUhUTUwpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpgYGANCg0KVGhlIFwjIGNvbW1lbnRzIGdlbmVyYWxseSBleHBsYWluIHdoYXQgYWRkaXRpb25hbCBmdW5jdGlvbmFsaXR5IGVhY2ggbGlicmFyeSBhZGRzIHRvIFINCg0KIyMjIExvYWQgaW4gRGF0YQ0KDQpBbGwgZGF0YSBpcyBkb3dubG9hZGVkIGZyb20gRmFuIEdyYXBocy4gRnJvbSB0aGlzIFtsb2NhdGlvbl0oaHR0cHM6Ly93d3cuZmFuZ3JhcGhzLmNvbS9sZWFkZXJzLmFzcHg/cG9zPWFsbCZzdGF0cz1iYXQmbGc9YWxsJnF1YWw9eSZ0eXBlPTgmc2Vhc29uPTIwMjEmbW9udGg9MCZzZWFzb24xPTIwMjEmaW5kPTApLiBUaGUgZGF0YSBpcyBhbHNvIGF2YWlsYWJsZSBvbiBteSBHaXRodWIgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9kaXNzaXBhdGlvbi9GYW50YXN5LUJhc2ViYWxsKS4gVGhlcmUgYXJlIHBsYXllciBsZXZlbCBhbmQgdGVhbSBkYXRhIHNldHMNCg0KYGBge3IgZGF0YSByZWFkLWluLCByZXN1bHRzPSAnaGlkZScsbWVzc2FnZT1GQUxTRX0NCg0KI2RhdGEgcmVhZC1pbg0KQmF0dGVyX2RhdGEgPC0gcmVhZF9jc3YoIkZhbkdyYXBocyBMZWFkZXJib2FyZF9IaXR0aW5nNTBQQS5jc3YiKQ0KDQojVGVhbSBkYXRhc2V0cw0KRkRHX1RlYW0gPSByZWFkX2NzdigiRmFuR3JhcGhzIExlYWRlcmJvYXJkX1RlYW0uY3N2IikNCg0KDQojQ3JlYXRlIGEgcHJlZml4IGZvciBhbGwgdGVhbSBzdGF0cyB0aGF0IHN0YXJ0cyB3aXRoIFRfDQpGREdfVGVhbTIgPC0gRkRHX1RlYW0gJT4lIA0KICByZW5hbWVfd2l0aCggfiBwYXN0ZTAoIlRfIiwgLngpKQ0KDQpgYGANCg0KIyMjIENoZWNraW5nIFRlYW0gRGF0YQ0KDQpgc3RyYCBnaXZlIGluZm9ybWF0aW9uIGFib3V0IGFuIG9iamVjdCwgd2hpbGUgYHNraW1gIHByb3ZpZGVzIGEgY3VzdG9taXphYmxlIHN1bW1hcnkgIA0KDQpgYGB7ciBjaGVja2luZyB0ZWFtIGRhdGF9DQoNCiNPdXRwdXQgbm90IHNob3duIGZvciBzcGFjZQ0KI3N0cihGREdfVGVhbTIpDQoNCnNraW0oRkRHX1RlYW0yKSAlPiUgIA0KICB0aWJibGU6OmFzX3RpYmJsZSgpDQpgYGANCioqKiAgDQoNCiMjIFVuZGVyc3RhbmRpbmcgdGhlIERhdGFzZXQNCg0KIyMjIEV4cGxvcmluZyB0aGUgZGF0YXNldA0KDQpgc2tpbWAgbGV0J3MgdXMgc2VlIGhvdyB0aGUgZGF0YSB3YXMgaW1wb3J0ZWQgaW50byBSLiBEb2N1bWVudGF0aW9uIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NraW1yL3ZpZ25ldHRlcy9za2ltci5odG1sKQ0KDQpgYGB7cn0NCg0KI0Z1bGwgRGF0YXNldCBkaW1lbnNpb25zDQoNCnNraW1yOjpza2ltKEJhdHRlcl9kYXRhKSAlPiUgDQogIHRpYmJsZTo6YXNfdGliYmxlKCkgJT4lIA0KICBzZWxlY3Qoc2tpbV90eXBlLHNraW1fdmFyaWFibGUsY29tcGxldGVfcmF0ZSkgJT4lIA0KICBmaWx0ZXIoY29tcGxldGVfcmF0ZSA+MC4zMCkgIzI4OCBWYXJpYWJsZXMNCg0KI3NraW1fdHlwZSAtIGNoYXJhY3RlciBvciBudW1lcmljDQojc2tpbV92YXJpYWJsZSAtIG5hbWUgb2YgdmFyaWFibGUNCiNjb21wbGV0ZV9yYXRlIC0gJSBvZiBkYXRhIHRoYXQgaXMgbm90IG1pc3NpbmcNCiNmaWx0ZXIgLSBvbmx5IGtlZXAgdmFyaWFibGVzIHRoYXQgaGF2ZSAzMCUgb2YgZGF0YSBwb3B1bGF0ZWQNCmBgYA0KDQpBZGRpdGlvbmFsbHkgbGV0J3MgbG9vayBhdCBob3cgdmFyaWFibGVzIHZhcnkgYnkgeWVhciB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBkaXNjcmVwYW5jaWVzIHRoZXJlICANCg0KYGBge3J9DQoNCiNJdCBsb29rcyBsaWtlIG9uZSB5ZWFyLCB0aGVyZSB3ZXJlIGZld2VyIGdhbWVzIHBsYXllZCwgYW5kIHRoZXJlIGlzIGEgY2xlYXIgZHJvcCBvZmYgaW4gaG9tZSBydW5zDQpCYXR0ZXJfZGF0YV9kaXN0ID0NCkJhdHRlcl9kYXRhICU+JSANCiBncm91cF9ieShTZWFzb24pICU+JSANCiAgc3VtbWFyaXplIChHYW1lc19wbGF5ZWQgPSBtYXgoRyksDQogICAgICAgICAgICAgQXZnX0hSPSBtZWFuKEhSKQ0KICAgICAgICAgICAgICkNCkJhdHRlcl9kYXRhX2Rpc3QNCg0KZ2dwbG90KEJhdHRlcl9kYXRhX2Rpc3QsIGFlcyhTZWFzb24sIEF2Z19IUikpICsNCiAgZ2VvbV9jb2woKSsNCiAgZ2d0aXRsZSgiQXZlcmFnZSBIb21lIFJ1bnMgYnkgWWVhciIpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LHNpemUgPSAyMixjb2xvciA9InN0ZWVsIGJsdWUiKSkNCmBgYA0KDQoqKiogIA0KDQojIyBDbGVhbmluZyBhbmQgQ3JlYXRpbmcgSW5pdGlhbCBEYXRhc2V0IGZvciBNb2RlbA0KDQpXaGF0IGFyZSBzb21lIGlzc3VlcyB3aXRoIHRoZSBkYXRhPw0KDQoxLiAgTWFueSBvZiBWYXJpYWJsZXMsIHN1Y2ggYXMgSyUsIGFyZSBiZWluZyByZWFkIGluIGFzIGNoYXJhY3RlcnMNCg0KICAgIC0gICBPbmx5IFRlYW0gYW5kIFBsYXllciBOYW1lIHNob3VsZCBiZSBjaGFyYWN0ZXJzDQoNCjIuICBUaGVyZSBpcyBzcG90dHkgZGF0YSBjb3ZlcmFnZSBpbiBzb21lIG9mIHRoZSB2YXJpYWJsZXMgKFx+VmFyaWFibGVzIGhhdmUgbGVzcyB0aGFuIDMwJSBDb3ZlcmFnZSkNCg0KMy4gIDIwMjAgRGF0YSBvbmx5IGluY2x1ZGVzIDYwIGdhbWVzIHdvcnRoIG9mIGRhdGENCg0KICAgIC0gICBUaGlzIHdhcyBhIHNlYXNvbiBzaG9ydGVuZWQgZHVlIHRvIENvdmlkLTE5DQoNCjQuICBUZWFtIERhdGEgbmVlZHMgdG8gYmUgYXBwZW5kZWQgdG8gQmF0dGVyIERhdGEgYnkgVGVhbSBOYW1lIA0KDQoqKiogIA0KDQojIyMgQ2xlYW5seSBDaGFuZ2luZyBhbGwgVmFyaWFibGVzIHRoYXQgYXJlIGNoYXJhY3RlcnMgdG8gbnVtZXJpYy4gIA0KVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBkbyB0aGlzLCB3ZSB3aWxsIGlkZW50aWZ5IHRoZSB2YXJpYWJsZXMgd2Ugd2FudCB0byBjaGFuZ2UgdGhhdCBhcmUgbWlzLWlkZW50aWZpZWQuIGBwYXJzZV9udW1iZXJgIGNhbiBiZSB1c2VkIHRvIHB1bGwgbnVtYmVycyBmcm9tIHRoZXNlIHZhcmlhYmxlcy4gQWRkaXRpb25hbCB3YXlzIHRvIHRhY2tsZSB0aGlzIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvODMyOTA1OS9ob3ctdG8tY29udmVydC1jaGFyYWN0ZXItb2YtcGVyY2VudGFnZS1pbnRvLW51bWVyaWMtaW4tcikNCg0KYGBge3J9DQoNCiNTZWxlY3QgQ29sdW1uIG5hbWVzIHRoYXQgYXJlIGNoYXJhY3RlcnMgYnV0IG5vdCBUZWFtIG9yIE5hbWUsIFRoZXNlIHNob3VsZCBiZSBwZXJjZW50YWdlcw0KQmF0dGVyX2RhdGFfY2hhcnNfdG9fY29udmVydCA8LSBCYXR0ZXJfZGF0YSAlPiUgDQogIHNlbGVjdF9pZihpcy5jaGFyYWN0ZXIpJT4lIHNlbGVjdCgtVGVhbSwtTmFtZSkgJT4lIA0KICBtdXRhdGVfYWxsIChmdW5jdGlvbih4KSBhcy5udW1lcmljKHJlYWRyOjpwYXJzZV9udW1iZXIoeCkpLzEwMCkNCiNOb3RlIDogVGhlcmUgYXJlIGFkZGl0aW9uYWwgd2F5cyB0byBkbyB0aGlzLCB0aGlzIGlzIGp1c3Qgb25lIHNvbHV0aW9uDQoNCg0KI1dlIGNhbiBleGNsdWRlIHRoZSB2YXJpYWJsZXMgd2UgY29udmVydGVkIGFuZCByZWludHJvZHVjZSB0aGVtDQpCYXR0ZXJfZGF0YV9udW0gPC0gQmF0dGVyX2RhdGEgJT4lIHNlbGVjdCgtY29sbmFtZXMoQmF0dGVyX2RhdGFfY2hhcnNfdG9fY29udmVydCkpDQoNCkJhdHRlcl9kYXRhMiA9IGNiaW5kKEJhdHRlcl9kYXRhX251bSxCYXR0ZXJfZGF0YV9jaGFyc190b19jb252ZXJ0KSAlPiUgDQogIHNlbGVjdCAoY29sbmFtZXMoQmF0dGVyX2RhdGEpKSAlPiUgICNwcmVzZXJ2ZSBvcmlnaW5hbCBvcmRlciANCiAgZHBseXI6OnJlbmFtZShmbHliYWxsX3BlcmMgPSBgRkIlLi4uNDZgLGZhc3RiYWxsX3BlcmMgPSBgRkIlLi4uNzNgKSAjcmVuYW1lIHR3byBhbWJpZ3VvdXMgY29sdW1ucw0KICANCnNraW0oQmF0dGVyX2RhdGEyKSAlPiUgDQogIGFzX3RpYmJsZSgpICU+JSANCiAgZ3JvdXBfYnkoc2tpbV90eXBlKSAlPiUgDQogIGNvdW50KCkNCg0KDQojTG9naWNhbCB2YXJpYWJsZXMgYXJlIFIncyBiZXN0IGd1ZXNzLCBpbiBvdXIgY2FzZSB0aGV5IGFyZSBhbGwgTkEncyBhbmQgd2lsbCBiZSByZW1vdmVkDQpgYGANCg0KVGhlIHNhbWUgY2FuIGJlIGRvbmUgZm9yIHRoZSBUZWFtIERhdGEgdGhhdCBpcyBsb2FkZWQgIA0KDQoNCmBgYHtyfQ0KDQojU2VsZWN0IENvbHVtbiBuYW1lcyB0aGF0IGFyZSBjaGFyYWN0ZXJzIGJ1dCBub3QgVGVhbSBvciBOYW1lLCBUaGVzZSBzaG91bGQgYmUgcGVyY2VudGFnZXMNCkZER19UZWFtMl9jaGFyc190b19jb252ZXJ0IDwtIEZER19UZWFtMiAlPiUgDQogIHNlbGVjdF9pZihpcy5jaGFyYWN0ZXIpJT4lIHNlbGVjdCgtVF9UZWFtKSAlPiUgDQogIG11dGF0ZV9hbGwgKGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMocmVhZHI6OnBhcnNlX251bWJlcih4KSkvMTAwKQ0KI0tlZXAgaW4gbWluZCwgcGFyc2UgbnVtYmVyIG1heSBtYWtlIGFjdHVhbCBjaGFyYWN0ZXJzIGludG8gbnVtZXJpY2FsIHZhcmlhYmxlcyBzbyBjYXJlZnVsbHkgY2hlY2sgeW91ciBkYXRhIGJlZm9yZSB1c2luZw0KDQojV2UgY2FuIGV4Y2x1ZGUgdGhlIHZhcmlhYmxlcyB3ZSBjb252ZXJ0ZWQgYW5kIHJlaW50cm9kdWNlIHRoZW0NCkZER19UZWFtMl9udW0gPC0gRkRHX1RlYW0yICU+JSBzZWxlY3QoLWNvbG5hbWVzKEZER19UZWFtMl9jaGFyc190b19jb252ZXJ0KSkNCg0KRkRHX1RlYW0zID0gY2JpbmQoRkRHX1RlYW0yX251bSxGREdfVGVhbTJfY2hhcnNfdG9fY29udmVydCkgJT4lIA0KICBzZWxlY3QgKGNvbG5hbWVzKEZER19UZWFtMikpICU+JSAgI3ByZXNlcnZlIG9yaWdpbmFsIG9yZGVyDQpkcGx5cjo6cmVuYW1lKFRfZmx5YmFsbF9wZXJjID0gYFRfRkIlLi4uNDVgLFRfZmFzdGJhbGxfcGVyYyA9IGBUX0ZCJS4uLjcyYCkgDQoNCnNraW0oRkRHX1RlYW0zKSAlPiUgDQogIGFzX3RpYmJsZSgpICU+JSANCiAgZ3JvdXBfYnkoc2tpbV90eXBlKSAlPiUgDQogIGNvdW50KCkNCmBgYA0KKioqICANCg0KDQojIyMgRmlsdGVyaW5nIERhdGEgd2l0aCBMb3cgQ292ZXJhZ2UgICAgDQpJIGNob29zZSAzMCUgY292ZXJhZ2Ugb2YgZGF0YSBuZWNlc3NhcnkgYnV0IHRoaXMgY2FuIGJlIGFkanVzdGVkIHVwIG9yIGRvd24uIFRoaXMgd2lsbCBhbHNvIGdldCByaWQgb2YgY29sdW1ucyB0aGF0IGFyZSBhbGwgYE5BYC4gIA0KYGBge3J9DQoNCiMgS2VlcCB2YXJpYWJsZXMgd2l0aCBlbm91Z2ggdmFsdWVzIChOZWVkIDMwJSBkYXRhIGNvdmVyYWdlIHJhdGUgaGVyZSkNClBsYXllcl9jb2xzX3RvX2tlZXAgPQ0Kc2tpbShCYXR0ZXJfZGF0YTIpICU+JSANCiAgZHBseXI6OnNlbGVjdChza2ltX3R5cGUsIHNraW1fdmFyaWFibGUsIGNvbXBsZXRlX3JhdGUpICU+JSANCiAgZmlsdGVyIChjb21wbGV0ZV9yYXRlID4gMC4zMCkNCg0KI1RyYW5zcG9zZSBSb3dzIHRvIGdldCBjb2x1bW4gbmFtZXMgYXMgc2tpbSBtZWx0cyB0aGUgZGF0YQ0KUGxheWVyX2NvbHNfdG9fa2VlcF90cmFuc3Bvc2UgPSB0KFBsYXllcl9jb2xzX3RvX2tlZXApIA0KDQojZXh0cmFjdCB0aGUgY29sbmFtZXMgd2Ugd291bGQgbGlrZSB0byBrZWVwDQpQbGF5ZXJfY29sc190b19rZWVwID0gY29sbmFtZXMoamFuaXRvcjo6cm93X3RvX25hbWVzKFBsYXllcl9jb2xzX3RvX2tlZXBfdHJhbnNwb3NlLHJvd19udW1iZXIgPSAyKSkNCg0KI09ubHkga2VlcCB0aGUgY29sdW1ucyBkZXNpZ25hdGVkIHRvIGhhdmUgb3ZlciAzMCUgb2YgdGhlaXIgZGF0YSBwb3B1bGF0ZWQgb3IgZ3JlYXRlcg0KQmF0dGVyX2RhdGEzID0gQmF0dGVyX2RhdGEyICU+JSANCiAgc2VsZWN0KG9uZV9vZihQbGF5ZXJfY29sc190b19rZWVwKSkgDQpgYGANCg0KDQoqUmVwZWF0IHRoZSBwcm9jZXNzIGZvciBUZWFtIFZhcmlhYmxlcyoNCmBgYHtyfQ0KVGVhbV9jb2xzX3RvX2tlZXAgPQ0Kc2tpbShGREdfVGVhbTMpICU+JSANCiAgZHBseXI6OnNlbGVjdChza2ltX3R5cGUsIHNraW1fdmFyaWFibGUsIGNvbXBsZXRlX3JhdGUpICU+JSANCiAgZmlsdGVyIChjb21wbGV0ZV9yYXRlID4gMC4zMCkNCg0KDQojVHJhbnNwb3NlIFJvd3MgdG8gZ2V0IGNvbHVtbiBuYW1lcyBhcyBza2ltIG1lbHRzIHRoZSBkYXRhDQpUZWFtX2NvbHNfdG9fa2VlcF90cmFuc3Bvc2UgPSB0KFRlYW1fY29sc190b19rZWVwKSANCg0KI2V4dHJhY3QgdGhlIGNvbG5hbWVzIHdlIHdvdWxkIGxpa2UgdG8ga2VlcA0KVGVhbV9jb2xzX3RvX2tlZXAgPSBjb2xuYW1lcyhqYW5pdG9yOjpyb3dfdG9fbmFtZXMoVGVhbV9jb2xzX3RvX2tlZXBfdHJhbnNwb3NlLHJvd19udW1iZXIgPSAyKSkNCg0KI09ubHkga2VlcCB0aGUgY29sdW1ucyBkZXNpZ25hdGVkIHRvIGhhdmUgb3ZlciAzMCUgb2YgdGhlaXIgZGF0YSBwb3B1bGF0ZWQgb3IgZ3JlYXRlcg0KRkRHX1RlYW00ID0gRkRHX1RlYW0zICU+JSANCiAgc2VsZWN0KG9uZV9vZihUZWFtX2NvbHNfdG9fa2VlcCkpIA0KDQoNCg0KYGBgDQoNCg0KDQoqKiogIA0KDQojIyMgQ3JlYXRpbmcgVmFyaWFibGVzIE5vcm1hbGl6ZWQgYnkgWWVhciAgDQpTb21lIFZhcmlhYmxlcyB3aWxsIG5lZWQgdG8gYmUgbm9ybWFsaXplZCBieSBJbm5pbmdzX1BpdGNoZWQgKElQKSBpZiB0aGV5IGFyZW4ndCBhIHBlcmNlbnRhZ2UgYWxyZWFkeS4gUmVtYWluaW5nIFZhcmlhYmxlcyBhcmUgcGVyY2VudGFnZXMgb3IgaW5kaWNlcyBzbyB3aWxsIG5vdCBuZWVkIHRvIGJlIHRyYW5zZm9ybWVkLiBUaGUgZnVsbCBkYXRhIGRpY3Rpb25hcnkgZm9yIHRoZXNlIHZhcmlhYmxlcyBjYW4gYmUgZm91bmQgb24gRmFuR3JhcGgncyB3ZWJzaXRlIFtoZXJlLl0oaHR0cHM6Ly9saWJyYXJ5LmZhbmdyYXBocy5jb20vcGl0Y2hpbmcvY29tcGxldGUtbGlzdC1waXRjaGluZy8pIGZvciBwaXRjaGluZyB2YXJpYWJsZXMgYW5kIFtoZXJlLl0oaHR0cHM6Ly9saWJyYXJ5LmZhbmdyYXBocy5jb20vb2ZmZW5zZS9vZmZlbnNpdmUtc3RhdGlzdGljcy1saXN0LykgZm9yIGhpdHRpbmcgdmFyaWFibGVzLiAgDQpgYGB7cn0NCg0KQmF0dGVyX2RhdGE0ID0gQmF0dGVyX2RhdGEzICU+JSANCiAgbXV0YXRlKCAjY3JlYXRlIG5ldyB2YXJpYWJsZXMgYmFzZWQgb24gZXhpc3RpbmcgdmFyaWFibGVzDQogICAgSF9QQSA9IEgvUEEsDQogICAgeDFCX1BBID0gYDFCYC9QQSwgI25vdGU6IFIgY2FuJ3QgaGF2ZSB2YXJpYWJsZXMgc3RhcnQgd2l0aCBhIG51bWJlcg0KICAgIHgyYl9QQSA9IGAyQmAvUEEsDQogICAgeDNiX1BBID0gYDNCYC9QQSwNCiAgICBIUl9QQSA9IEhSL1BBLA0KICAgIFJfUEEgPSBSL1BBLA0KICAgIFJCSV9QQSA9IFJCSS9QQSwNCiAgICBCQl9QQSA9IEJCL1BBLA0KICAgIElCQl9QQSA9IElCQi9QQSwNCiAgICBTT19QQT1TTy9QQSwNCiAgICBIQlBfUEE9SEJQL1BBLA0KICAgIFNGX1BBPVNGL1BBLA0KICAgIFNIX1BBPVNIL1BBLA0KICAgIEdEUF9QQT0gR0RQL1BBLCNncm91bmQgaW50byBkb3VibGUgcGxheQ0KICAgIFNCX1BBPVNCL1BBLA0KICAgIENTX1BBPUNTL1BBLA0KICAgIEdCX1BBID0gR0IvUEEsICAgI0dyb3VuZGJhbGxzDQogICAgRkJfUEEgPSAgRkIvUEEsICAjRmx5QmFsbHMNCiAgICBMRF9QQSA9IExEL1BBLCAgICNMaW5lRHJpdmVzDQogICAgSUZGQl9QQSA9IElGRkIvUEEsICAjSW5maWVsZCBGbHkgYmFsbHMNCiAgICBQaXRjaGVzX1BBPSBQaXRjaGVzL1BBLA0KICAgIEJhbGxzX1BBPSBCYWxscy9QQSwNCiAgICBTdHJpa2VzX1BBPSBTdHJpa2VzL1BBLA0KICAgIElGSF9QQT0gSUZIL1BBLA0KICAgIEJVX1BBPSBCVS9QQSwNCiAgICBCVUhfUEE9IEJVSC9QQSwNCiAgICBQSF9QQT0gUEgvUEEsDQogICAgQmFycmVsc19QQT0gQmFycmVscy9QQSwNCiAgICBIYXJkSGl0c19QQT0gSGFyZEhpdC9QQQ0KICApICU+JSBzZWxlY3QoLShIOkNTKSwtKEdCOkJVSCksLVBILC1CYXJyZWxzLC1IYXJkSGl0LC1FdmVudHMpICNEcm9wIHRoZSBvbGQgdmFyaWFibGVzDQoNCiNza2ltKEJhdHRlcl9kYXRhNCkgJT4lIGFzX3RpYmJsZSgpDQoNCg0KYGBgDQoNCipSZXBlYXQgdGhlIHByb2Nlc3MgZm9yIFRlYW0gVmFyaWFibGVzKg0KYGBge3J9DQoNCkZER19UZWFtNSA9IEZER19UZWFtNCAlPiUgDQogIG11dGF0ZSggI2NyZWF0ZSBuZXcgdmFyaWFibGVzIGJhc2VkIG9uIGV4aXN0aW5nIHZhcmlhYmxlcw0KICAgIFRfSF9UX1BBID0gVF9IL1RfUEEsDQogICAgVF94MUJfVF9QQSA9IFRfMUIvVF9QQSwgI25vdGU6IFIgY2FuJ3QgaGF2ZSB2YXJpYWJsZXMgc3RhcnQgd2l0aCBhIG51bWJlcg0KICAgIFRfeDJiX1RfUEEgPSBUXzJCL1RfUEEsDQogICAgVF94M2JfVF9QQSA9IFRfM0IvVF9QQSwNCiAgICBUX0hSX1RfUEEgPSBUX0hSL1RfUEEsDQogICAgVF9SX1RfUEEgPSBUX1IvVF9QQSwNCiAgICBUX1JCSV9UX1BBID0gVF9SQkkvVF9QQSwNCiAgICBUX0JCX1RfUEEgPSBUX0JCL1RfUEEsDQogICAgVF9JQkJfVF9QQSA9IFRfSUJCL1RfUEEsDQogICAgVF9TT19UX1BBPVRfU08vVF9QQSwNCiAgICBUX0hCUF9UX1BBPVRfSEJQL1RfUEEsDQogICAgVF9TRl9UX1BBPVRfU0YvVF9QQSwNCiAgICBUX1NIX1RfUEE9VF9TSC9UX1BBLA0KICAgIFRfR0RQX1RfUEE9IFRfR0RQL1RfUEEsI2dyb3VuZCBpbnRvIGRvdWJsZSBwbGF5DQogICAgVF9TQl9UX1BBPVRfU0IvVF9QQSwNCiAgICBUX0NTX1RfUEE9VF9DUy9UX1BBLA0KICAgIFRfR0JfVF9QQSA9IFRfR0IvVF9QQSwgICAjR3JvdW5kYmFsbHMNCiAgICBUX0ZCX1RfUEEgPSAgVF9GQi9UX1BBLCAgI0ZseUJhbGxzDQogICAgVF9MRF9UX1BBID0gVF9MRC9UX1BBLCAgICNMaW5lRHJpdmVzDQogICAgVF9JRkZCX1RfUEEgPSBUX0lGRkIvVF9QQSwgICNJbmZpZWxkIEZseSBiYWxscw0KICAgIFRfUGl0Y2hlc19UX1BBPSBUX1BpdGNoZXMvVF9QQSwNCiAgICBUX0JhbGxzX1RfUEE9IFRfQmFsbHMvVF9QQSwNCiAgICBUX1N0cmlrZXNfVF9QQT0gVF9TdHJpa2VzL1RfUEEsDQogICAgVF9JRkhfVF9QQT0gVF9JRkgvVF9QQSwNCiAgICBUX0JVX1RfUEE9IFRfQlUvVF9QQSwNCiAgICBUX0JVSF9UX1BBPSBUX0JVSC9UX1BBLA0KICAgIFRfUEhfVF9QQT0gVF9QSC9UX1BBLA0KICAgIFRfQmFycmVsc19UX1BBPSBUX0JhcnJlbHMvVF9QQSwNCiAgICBUX0hhcmRIaXRzX1RfUEE9IFRfSGFyZEhpdC9UX1BBDQogICkgJT4lIHNlbGVjdCgtKFRfSDpUX0NTKSwtKFRfR0I6VF9CVUgpLC1UX1BILC1UX0JhcnJlbHMsLVRfSGFyZEhpdCwtVF9FdmVudHMpICNEcm9wIHRoZSBvbGQgdmFyaWFibGVzDQoNCg0KI3NraW0oRkRHX1RlYW01KSAlPiUgYXNfdGliYmxlKCkNCg0KDQpgYGANCg0KKioqICANCg0KIyMjIENyZWF0aW5nIExhZ2dlZCBWYXJpYWJsZXMgIA0KDQpUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIGxhZyBhIGRhdGFzZXQgKipCWSBHUk9VUCoqLlwNClwqIGBEcGx5cmAgd2F5IGlzIFtoZXJlLl0oaHR0cHM6Ly9zdGF0aXN0aWNzZ2xvYmUuY29tL2NyZWF0ZS1sYWdnZWQtdmFyaWFibGUtYnktZ3JvdXAtaW4tcikuXA0KXCogVGhlIGBkYXRhLnRhYmxlYCAodGhlIG1ldGhvZCB1c2VkIGJlbG93KSBpcyBbaGVyZS5dKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzI2MjkxOTg4L2hvdy10by1jcmVhdGUtYS1sYWctdmFyaWFibGUtd2l0aGluLWVhY2gtZ3JvdXApICANCmBgYHtyfQ0KI05vdGUgd2Ugd2lsbCBvbmx5IGJlIGxhZ2dpbmcgdGhlIHBsYXllciBsZXZlbCBkYXRhLCBhcyB0aGUgcHJldmlvdXMgeWVhcidzIHRlYW0gcGVyZm9ybWFuY2Ugc2hvdWxkbid0IGltcGFjdCBjdXJyZW50IHBlcmZvcm1hbmNlDQoNCg0KI09yZGVyIHRoZSBkYXRhc2V0IGJ5IGxhZyBjb2x1bW5zDQpCYXR0ZXJfZGF0YTUgPSAgYXJyYW5nZShCYXR0ZXJfZGF0YTQsIHBsYXllcmlkLFNlYXNvbikgI3BsYXllcmlkIGlzIHRoZSBGYW5ncmFwaCBpZCBhc3NpZ25lZCB0byBlYWNoIHBsYXllcg0KDQojIENvbnZlcnQgZGF0YWZyYW1lIHRvIGRhdGEudGFibGUgZm9ybWF0DQpEVF9iYXR0ZXIgPSBkYXRhLnRhYmxlKEJhdHRlcl9kYXRhNSkNCg0KI2Rlc2lnbmF0ZSBjb2x1bW5zIHRvIGxhZyAtIHdoaWNoIGlzIGFsbCBvZiB0aGVtDQpjb2xzMSA9IGNvbG5hbWVzKEJhdHRlcl9kYXRhNSkNCmFuc2NvbHMgPSBwYXN0ZSgibGFnIiwgY29sczEsIHNlcD0iXyIpDQpEVF9iYXR0ZXJbLCAoYW5zY29scykgOj0gZGF0YS50YWJsZTo6c2hpZnQoLlNELCAxLCBOQSwgImxhZyIpLGJ5ID0ncGxheWVyaWQnLCAuU0Rjb2xzPWNvbHMxXSAjQ3JlYXRlIDEgcGVyaW9kIGxhZ3MgYnkgeWVhcg0KDQpCYXR0ZXJfZGF0YTYgPSBhcy5kYXRhLmZyYW1lKERUX2JhdHRlcikgJT4lIHNlbGVjdCgtbGFnX3BsYXllcmlkLCAtbGFnX1RlYW0sIC1sYWdfU2Vhc29uLCAtbGFnX0FnZSwtbGFnX05hbWUpDQoNCm5jb2woQmF0dGVyX2RhdGE1KSAjMjg3IC0gbm8gbGFncw0KbmNvbChCYXR0ZXJfZGF0YTYpICM1NzQgLSBsYWdnZWQgZGF0YSB+ICgyODcgKiAyKS01DQoNCmBgYA0KDQoqKiogICAgDQoNCiMjIyBNZXJnaW5nIFRlYW0gYW5kIFBsYXllciBEYXRhICANCldlIGNhbiB1c2UgZWl0aGVyIHRoZSBgbWVyZ2VgIGZ1bmN0aW9uIG9yIHRoZSBTUUwgZnVuY3Rpb25hbGl0eSBwcm92aWRlZCBieSB0aGUgYHNxbGRmYCBwYWNrYWdlIHRvIGpvaW4gdGhlIGxhZ2dlZCBwbGF5ZXIgbGV2ZWwgZGF0YSB0byB0aGUgVGVhbSBsZXZlbCBkYXRhDQoNCmBgYHtyfQ0KDQpkZl9iYXR0aW5nX2luaXQgPSBzcWxkZigNCiAgIg0KICBzZWxlY3QgYS4qLCBiLioNCiAgZnJvbSBCYXR0ZXJfZGF0YTYgYQ0KICBsZWZ0IGpvaW4gRkRHX1RlYW01IGINCiAgb24gYS5UZWFtID0gYi5UX1RlYW0gYW5kIGEuU2Vhc29uID0gYi5UX1NlYXNvbg0KICANCiAgIg0KKSAgJT4lIHNlbGVjdCgtVF9UZWFtLC1UX1NlYXNvbixUX0FnZSxUX0csVF9BQikjIFVubmNlc3NhcnkgVGVhbSBWYXJpYWJsZXMNCg0KDQpucm93KGRmX2JhdHRpbmdfaW5pdCkgLSBucm93KEJhdHRlcl9kYXRhNikgI2NoZWNrIGlmIGFueSByb3dzIGFyZSBkdXBsaWNhdGVkDQoNCg0KYGBgDQoNCg0KKioqICANCg0KDQojIENyZWF0aW5nIFJhbmtpbmdzIGZvciBQbGF5ZXJzIEJhc2VkIE9uIFBlcmNlbnRpbGVzIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCldlIGNhbiB1c2UgUGVyY2VudGlsZSBiYXNlZCByYW5raW5nIHRvIGdldCByYW5raW5ncyBmb3IgcGxheWVycyBmcm9tIHRoZSAyMDIxIHNlYXNvbi4NCg0KIyMgV29ydGggb2YgZWFjaCBzdGF0DQoNCiMjIyBDYWxjdWxhdGluZyBwYXN0IHBlcmZvcm1hbmNlDQoNCkVhY2ggcGxheWVyIGdvZXMgZnJvbSBhIDAlIHRvIDEwMCUgb24gZWFjaCBwZXJjZW50aWxlIHN0YXQgdGhhdCBpcyB1c2VkIGZvciBjcmVhdGluZyBhIHNjb3Jpbmcgb3Bwb3J0dW5pdHkuIERhdGEgaXMgbm90IHlldCBub3JtYWxpemVkIGJ5IFBBIGFzIGNlcnRhaW4gc3RhdHMgc3VjaCBhcyBIUnMgYW5kIFNCcyB3aWxsIGJlIHdvcnRoIG1vcmUgd2hlbiB3ZSBkby5cDQoNCmBgYHtyfQ0KDQojQ2F0ZWdvcmllcyBJIGluY2x1ZGUgYXJlOg0KI1J1bnMgKFIpLCBIb21lIFJ1bnMgKEhSKSwgUnVucyBCYXR0ZWQgSW4gKFJCSSksIFN0b2xlbiBCYXNlcyAoU0IpLCBCYXR0aW5nIEF2ZXJhZ2UgKEFWRykNCmRmX2JhdHRpbmdfaW5pdDIgPSAgZGZfYmF0dGluZ19pbml0ICU+JQ0KIyAgYXJyYW5nZShwbGF5ZXJfaWQseWVhcikgJT4lIA0KICBncm91cF9ieShTZWFzb24pICU+JSANCiAgbXV0YXRlKA0KICAgIFJ1bnNfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKFJfUEEsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgSFJfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKEhSX1BBLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgIFJCSV9zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoUkJJX1BBLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgIFNCX3NoYXJlID0gb3JkZXIob3JkZXIocmFuayhTQl9QQSx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgICBBVkdfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKEFWRyx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgIE9QU19zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoT1BTLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgV29ydGggPSBSdW5zX3NoYXJlK0hSX3NoYXJlK1JCSV9zaGFyZStTQl9zaGFyZStBVkdfc2hhcmUrT1BTX3NoYXJlDQogICAgKSAlPiUgDQogIHVuZ3JvdXAoKSANCg0KYGBgDQoNCkNoYXJ0IG9mIHRoZSBEaXN0cmlidXRpb24gb2YgaW5pdGlhbCBwZXJjZW50aWxlcyAgDQpBcyB0aGUgY2hhcnQgYmVsb3cgc2hvd3MsIHRoZSBkYXRhIGlzIHJvdWdobHkgbm9ybWFsLg0KYGBge3J9DQoNCnNrZXduZXNzKChkZl9iYXR0aW5nX2luaXQyJFdvcnRoKSkNCg0KZ2dwbG90Mjo6cXBsb3QoZGZfYmF0dGluZ19pbml0MiRXb3J0aCwgbWFpbj0iVG90YWwgRGF0YXNldCIpICsgZ2VvbV9oaXN0b2dyYW0oY29sb3VyPSJibGFjayIsIGZpbGw9ImxpbWUgZ3JlZW4iKQ0KDQptaW4oZGZfYmF0dGluZ19pbml0MiRXb3J0aCkNCg0KbWF4KGRmX2JhdHRpbmdfaW5pdDIkV29ydGgpDQoNCmdncHVicjo6Z2dxcXBsb3QoZGZfYmF0dGluZ19pbml0MiRXb3J0aCkNCg0Kc2hhcGlyby50ZXN0KGRmX2JhdHRpbmdfaW5pdDIkV29ydGgpDQpgYGANCg0KDQoqKiogIA0KIyMgMjAyMSBQbGF5ZXIgUmFua2luZ3MgLSBQZXIgUEEgcGVyZm9ybWFuY2UNCg0KIyMjIDIwMjEgUGxheWVyIFJhbmtpbmdzIC0gVG9wIFdvcnRoIFBsYXllcg0KDQpUaGVyZSBhcmUgcGVyIFBBIHJhbmtpbmdzLiBQbGF5ZXJzIGxpa2UgQnlyb24gQnV4dG9uIHdoaWNoIGhhZCBhIGdyZWF0IHBlciBQQSBzY29yZSBidXQgY2FuJ3Qgc3RheSBoZWFsdGh5IGZvciBhIHNlYXNvbiB3aWxsIGJlIGFkanVzdGVkIGRvd24uDQoNCmBgYHtyLHdhcm5pbmc9RkFMU0V9DQoNCm9wdGlvbnMoZGlnaXRzPTIpDQoNCmRmX2JhdHRpbmdfaW5pdDIwMjEgPQ0KZGZfYmF0dGluZ19pbml0MiAlPiUgDQogIGdyb3VwX2J5KE5hbWUpICU+JSANCiAgZmlsdGVyKFNlYXNvbiA9PSAyMDIxKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhXb3J0aCkpICU+JSANCiAgc2VsZWN0KE5hbWUsUnVuc19zaGFyZSxIUl9zaGFyZSxSQklfc2hhcmUsIFNCX3NoYXJlLE9QU19zaGFyZSxBVkdfc2hhcmUsV29ydGgpDQoNCg0KZGZfYmF0dGluZ19pbml0MjAyMSAlPiUNCiAgZmlsdGVyIChXb3J0aD4zLjkpICU+JSANCiAga2JsKCkgJT4lIA0KIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiLCJjb25kZW5zZWQiLCJyZXNwb25zaXZlIiksZnVsbF93aWR0aCA9IEYsZml4ZWRfdGhlYWQgPSBUKQ0KYGBgDQoNCg0KKioqICANCg0KDQojIENyZWF0aW5nIE1vZGVsIEZpbGUgey50YWJzZXQgLnRhYnNldC1waWxsc30gIA0KDQojIyBBZGRpdGlvbmFsIERhdGEgUHJlcCAgDQoNCiMjIyBSZW1vdmUgVmFyaWFibGVzIHdoaWNoIGFyZSBiYXNlZCBvZmYgY3VycmVudCBoaXR0aW5nIG51bWJlcnMgIA0KDQpOb3QgYWxsIHZhcmlhYmxlcyBjYW4gYmUgdXNlZCBmb3IgcHJlZGljdGl2ZSBtb2RlbGluZy4gIFZhcmlhYmxlcyB0aGF0IGdvIGludG8gdGhlIHBlcmNlbnRpbGUgcmFua2luZyBvciBhcmUgbm9uLW5vcm1hbGl6ZWQgbWV0cmljcyBjcmVhdGVkIGFmdGVyIHRoZSBmYWN0IChzdWNoIGFzIGBXQVJgIC0gV2lucyBhYm92ZSBSZXBsYWNlbWVudCBvciBgUkUyNGApIHNob3VsZCBiZSByZW1vdmVkLiBIb3dldmVyLCBtZXRyaWNzIHRoYXQgYXJlIG5vcm1hbGl6ZWQgYnkgYSBwZXIgcGl0Y2ggYmFzaXMgKHN1Y2ggYXMgYEhSL0ZCJStgKSBjYW4gcmVtYWluIGFzIHdlIGV4cGVjdCBiYXR0ZXJzIHRvIGhhdmUgc2ltaWxhciBwZXJmb3JtYW5jZSBpbiB0aGVzZSBtZXRyaWNzIG9uZSB5ZWFyIG91dC4gIA0KDQpgYGB7cn0NCiNDcmVhdGluZyBhIG5ldyBkYXRhc2V0IHRvIGtlZXAgb3JpZ2luYWwgaW50YWN0DQpkZl9iYXR0aW5nX2luaXQzID0gZGZfYmF0dGluZ19pbml0Mg0KYGBgDQoNCkxhZ2dlZCBQZXJjZW50aWxlIChgX3NoYXJlYCkgVmFyaWFibGVzIGNhbiBiZSB1c2VkIGZvciBwcmVkaWN0aXZlIG1vZGVsaW5nLiBIb3dldmVyIHNpbmNlIHRoZXNlIHZhcmlhYmxlcyB3ZXJlIGNyZWF0ZWQgZm9yIHRoZSBXb3J0aCBtZXRyaWMgdGhleSBtdXN0IGFsc28gYmUgcmVtb3ZlZCBmb3IgbW9kZWxpbmcgcHVycG9zZXMuICANCg0KYGBge3J9DQoNCiNPcmRlciB0aGUgZGF0YXNldCBieSBsYWcgY29sdW1ucw0KZGZfYmF0dGluZ19pbml0NCA9ICBhcnJhbmdlKGRmX2JhdHRpbmdfaW5pdDMsIHBsYXllcmlkLFNlYXNvbikgI3BsYXllcmlkIGlzIHRoZSBGYW5ncmFwaCBpZCBhc3NpZ25lZCB0byBlYWNoIHBsYXllcg0KDQojIENvbnZlcnQgZGF0YWZyYW1lIHRvIGRhdGEudGFibGUgZm9ybWF0DQpEVF9iYXR0ZXIyID0gZGF0YS50YWJsZShkZl9iYXR0aW5nX2luaXQ0KQ0KDQojZGVzaWduYXRlIGNvbHVtbnMgdG8gbGFnIC0gd2hpY2ggaXMgYWxsIG9mIHRoZW0NCmNvbHMxID0gKGMoJ1J1bnNfc2hhcmUnLCdIUl9zaGFyZScsJ1JCSV9zaGFyZScsICdTQl9zaGFyZScsJ09QU19zaGFyZScsJ0FWR19zaGFyZScsJ1dvcnRoJykpDQphbnNjb2xzID0gcGFzdGUoImxhZyIsIGNvbHMxLCBzZXA9Il8iKQ0KRFRfYmF0dGVyMlssIChhbnNjb2xzKSA6PSBkYXRhLnRhYmxlOjpzaGlmdCguU0QsIDEsIE5BLCAibGFnIiksYnkgPSdwbGF5ZXJpZCcsIC5TRGNvbHM9Y29sczFdICNDcmVhdGUgMSBwZXJpb2QgbGFncyBieSB5ZWFyDQoNCiNuYW1lcyhEVF9iYXR0ZXIyKQ0KDQpkZl9iYXR0aW5nX2ZpbmFsID0gYXMuZGF0YS5mcmFtZShEVF9iYXR0ZXIyKSAlPiUgDQogIHNlbGVjdCgtYyhSdW5zX3NoYXJlLEhSX3NoYXJlLFJCSV9zaGFyZSwgU0Jfc2hhcmUsT1BTX3NoYXJlLEFWR19zaGFyZSkpJT4lIA0KICBzZWxlY3QoLShHOkFWRyksLShPQlA6QkFCSVApLC0od09CQTpEb2wpLC0oYHdSQytgOlJFVyksLShgV1BBL0xJYCksLSh3RkI6d1NGKSwtQnNSLC0oRGVmOndHRFApLC0oYHdDSCAocGkpYDpgd0NIL0MgKHBpKWApLC0oYEFWRytgKSwtKGBPQlArYDpgQkFCSVArYCksLShIX1BBOlBIX1BBKSkgJT4lIHNlbGVjdCAoLU5hbWUpDQoNCg0KYGBgDQoNCg0KDQojIyMgQ3JlYXRpbmcgVHJhaW5pbmcvVGVzdCBTcGxpdCAgDQpXZSBzcGxpdCB0aGUgZGF0YSBpbnRvIFRyYWluaW5nIERhdGEgKHdoaWNoIGlzIHVzZWQgdG8gY3JlYXRlIHRoZSBtb2RlbCkgYW5kIHRlc3QgZGF0YSAod2hpY2ggaXMgdXNlZCB0byB2YWxpZGF0ZSB0aGUgbW9kZWwpICAgDQpgYGB7cn0NCg0Kc2V0LnNlZWQoMTU2NzQpICAjIEZvciByZXByb2R1Y2liaWxpdHkNCiMgQ3JlYXRlIGluZGV4IGZvciB0ZXN0aW5nIGFuZCB0cmFpbmluZyBkYXRhDQppblRyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGRmX2JhdHRpbmdfZmluYWwkV29ydGgsIHAgPSAwLjgwLCBsaXN0ID0gRkFMU0UpDQojIHN1YnNldCBwaXRjaGluZyBkYXRhIGZvciB0cmFpbmluZw0KdHJfMjAyMSA8LSBkZl9iYXR0aW5nX2ZpbmFsW2luVHJhaW4sXQ0KIyBzdWJzZXQgdGhlIHJlc3QgdG8gdGVzdCBhbmQgdmFsaWRhdGUgdHJhaW5lZCBtb2RlbA0KdGVfMjAyMSA8LSBkZl9iYXR0aW5nX2ZpbmFsWy1pblRyYWluLF0NCg0KbnJvdyh0cl8yMDIxKS9ucm93KGRmX2JhdHRpbmdfZmluYWwpICNjaGVjayBpZiBzcGxpdCBpcyAwLjgNCg0KYGBgDQoNCiMjIyBUcmVhdCBNaXNzaW5nIERhdGEgYnkgSW1wdXRpbmcgTWVhbiBWYWx1ZSAgDQpWdHJlYXQgUGFja2FnZSBpbiBSIGlzIGV4Y2VsbGVudCBmb3IgdHJlYXRpbmcgZGF0YSBiZWZvcmUgdXNpbmcgZm9yIG1vZGVsaW5nLiBBZGRpdGlvbmFsIGRvY3VtZW50YXRpb24gY2FuIGJlIGZvdW5kIFtoZXJlLl0oaHR0cHM6Ly93aW52ZWN0b3IuZ2l0aHViLmlvL3Z0cmVhdC9pbmRleC5odG1sKSAgDQoqTm90ZTogVGhlIHRyZWF0bWVudCBwbGFuIGFsc28gZml4ZXMgdmFyaWFibGVzIG5hbWVzICBsaWtlYEhSL0ZCJStgICh3aGljaCBSIGRvZXNuJ3QgYWx3YXlzIGhhbmRsZSB0aGUgYmVzdCkgdG8gYEhSX3NsYXNoX0ZCX3BlcmNlbnRfcGx1c19gKiAgDQpgYGB7cn0NCnRyZWF0X3BsYW5fMjAyMSA8LSB2dHJlYXQ6OmRlc2lnblRyZWF0bWVudHNaKA0KICBkZnJhbWUgPSB0cl8yMDIxLCAjIHRyYWluaW5nIGRhdGENCiAgdmFybGlzdCA9IGNvbG5hbWVzKHRyXzIwMjEpICU+JSAuWy4gIT0gImhpdHRpbmdfc2NvcmUxIl0sICMgaW5wdXQgdmFyaWFibGVzID0gYWxsIHRyYWluaW5nIGRhdGEgY29sdW1ucywgZXhjZXB0IHJhbmRvbQ0KICBjb2RlUmVzdHJpY3Rpb24gPSBjKCJjbGVhbiIsICJpc0JBRCIsICJsZXYiKSwgIyBkZXJpdmVkIHZhcmlhYmxlcyB0eXBlcyAoZHJvcCBjYXRfUCkNCiAgdmVyYm9zZSA9IEZBTFNFKSAjIHN1cHByZXNzIG1lc3NhZ2VzDQoNCiNjbGVhbiBzdGFuZHMgZm9yIGNsZWFuZWQgbnVtZXJpY2FsIHZhcmlhYmxlLCBpc0JBRCBpbmRpY2F0ZXMgdGhhdCBhIHZhbHVlIHJlcGxhY2VtZW50IGhhcyBvY2N1cnJlZCAod2hpY2ggaW5kaWNhdGVzIGEgbWlzc2luZyB2YWx1ZSBpbiB0aGlzIGNhc2UpLCBhbmQgbGV2IGlzIGEgYmluYXJ5IGluZGljYXRvciB3aGV0aGVyIGEgcGFydGljdWxhciB2YWx1ZSBvZiB0aGF0IGNhdGVnb3JpY2FsIHZhcmlhYmxlIHdhcyBwcmVzZW50LiAgDQoNCiMjIyMgQ2hlY2tpbmcgU2NvcmVmcmFtZQ0KDQpzY29yZV9mcmFtZSA8LSB0cmVhdF9wbGFuXzIwMjEkc2NvcmVGcmFtZSAlPiUgDQogIHNlbGVjdCh2YXJOYW1lLCBvcmlnTmFtZSwgY29kZSkNCg0KaGVhZChzY29yZV9mcmFtZSkNCg0KDQp0cl90cmVhdGVkXzIwMjEgPC0gdnRyZWF0OjpwcmVwYXJlKHRyZWF0X3BsYW5fMjAyMSwgdHJfMjAyMSkNCnRlX3RyZWF0ZWRfMjAyMSA8LSB2dHJlYXQ6OnByZXBhcmUodHJlYXRfcGxhbl8yMDIxLCB0ZV8yMDIxKQ0KDQoNClRvdGFsX2RhdGFzZXQxX3VudHJlYXQgPSBhcy5kYXRhLmZyYW1lKERUX2JhdHRlcjIpICU+JSBzZWxlY3QoLU5hbWUpDQoNCnRyZWF0X3BsYW5fMjAyMSA8LSB2dHJlYXQ6OmRlc2lnblRyZWF0bWVudHNaKA0KICBkZnJhbWUgPSBUb3RhbF9kYXRhc2V0MV91bnRyZWF0LCAjIHRyYWluaW5nIGRhdGENCiAgdmFybGlzdCA9IGNvbG5hbWVzKFRvdGFsX2RhdGFzZXQxX3VudHJlYXQpICU+JSAuWy4gIT0gImhpdHRpbmdfc2NvcmUxIl0sICMgaW5wdXQgdmFyaWFibGVzID0gYWxsIHRyYWluaW5nIGRhdGEgY29sdW1ucywgZXhjZXB0IHJhbmRvbQ0KICBjb2RlUmVzdHJpY3Rpb24gPSBjKCJjbGVhbiIsICJpc0JBRCIsICJsZXYiKSwgIyBkZXJpdmVkIHZhcmlhYmxlcyB0eXBlcyAoZHJvcCBjYXRfUCkNCiAgdmVyYm9zZSA9IEZBTFNFKSAjIHN1cHByZXNzIG1lc3NhZ2VzDQoNCg0KdG90YWxfdHJlYXRlZF8yMDIxX2hpdHRpbmcgPC0gdnRyZWF0OjpwcmVwYXJlKHRyZWF0X3BsYW5fMjAyMSwgVG90YWxfZGF0YXNldDFfdW50cmVhdCkNCg0KDQoNCiN0cl90cmVhdGVkID0gdHINCiN0ZV90cmVhdGVkID0gdGUNCg0KZGltKHRyX3RyZWF0ZWRfMjAyMSkgI25vdGUgdGhlcmUgYXJlIGR1bW1pZXMgZm9yIGVhY2ggcGxheWVyIGFuZCB0ZWFtDQoNCmBgYA0KDQoNCioqKiAgICANCg0KDQojIyMgQ2hlY2sgRGlzdHJpYnV0aW9uIG9mIFRyYWluaW5nIFBvcHVsYXRpb24gIA0KVGhlIHBvcHVsYXRpb24gdXNlZCBmb3IgVHJhaW5pbmcgc2hvdWxkIGJlIGluZGljYXRpdmUgb2YgVG90YWwgUG9wdWxhdGlvbg0KYGBge3J9DQoNCmdncGxvdDI6OnFwbG90KHRyX3RyZWF0ZWRfMjAyMSRXb3J0aCwgbWFpbj0iVHJhaW5pbmcgU2V0IikgKyBnZW9tX2hpc3RvZ3JhbShjb2xvdXI9ImJsYWNrIiwgZmlsbD0ibGltZWdyZWVuIikgKyB0aGVtZV9idygpDQoNCnNrZXduZXNzKHRyX3RyZWF0ZWRfMjAyMSRXb3J0aCkgI1RoZSBza2V3bmVzcyBpcyB0aGUgc2FtZSBhcyB0aGUgb3ZlcmFsbA0KYGBgDQoNCg0KIyBSdW5uaW5nIFhHYm9vc3QgTW9kZWwgey50YWJzZXQgLnRhYnNldC1waWxsc30gDQpUbyBrZWVwIHRoaW5ncyBzaW1wbGUgd2l0aCBtb2RlbGluZywgd2XigJlsbCB0dXJuIHRoZSB0cmFpbmluZyBkYXRhIGludG8gc2ltcGxlIGlucHV0IHZhcmlhYmxlcyBmb3IgYGNhcmV0Ojp0cmFpbmAsIGRyb3BwaW5nIHRoZSByZXNwb25zZSB2YXJpYWJsZSBhbmQgY29udmVydGluZyB0aGUgZGF0YSBmcmFtZSB0byBhIG1hdHJpeC4gRG9jdW1lbnRhdGlvbiBmb3IgdGhpcyBhcHByb2FjaCB0byBYR2Jvb3N0IGNhbiBiZSBmb3VuZCBbaGVyZS5dKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vcGVsa29qYS92aXN1YWwteGdib29zdC10dW5pbmctd2l0aC1jYXJldCkgICAgDQoNCiMjIFR1bmluZyB0aGUgTW9kZWwNCg0KIyMjIEluaXRpYWwgTm9uLVR1bmVkIE1vZGVsDQpCcmVhayB0aGUgZGF0YSBzZXQgaW50byB4IGFuZCB5IGlucHV0cyB3aXRoIHggYmVpbmcgYSBtYXRyaXggIA0KYGBge3J9DQppbnB1dF94IDwtIGFzLm1hdHJpeCgoKHRyX3RyZWF0ZWRfMjAyMSkpJT4lDQogICBzZWxlY3QoLVdvcnRoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgDQogICBzZWxlY3QoIWVuZHNfd2l0aCAoIl9pc0JBRCIpKSkNCg0KaW5wdXRfeSA8LSB0cl90cmVhdGVkXzIwMjEkV29ydGgNCg0KYGBgDQoNClhHQm9vc3Qgd2l0aCBEZWZhdWx0IEh5cGVycGFyYW1ldGVycyAgICANClRoZSBWYXJpYWJsZSBJbXBvcnRhbmNlIChgY2FyZXQ6OnZhckltcCh4Z2JfYmFzZV8yMDIxLCBzY2FsZSA9IEYgIClgKSBmcm9tIHRoZSBjYXJldCBwYWNrYWdlIHNob3dzIHRoZSBjb250cmlidXRpb24gb2YgZWFjaCB2YXJpYWJsZSB0byB0aGUgaW5pdGlhbCBtb2RlbC4gQXMgeW91IGNhbiBzZWUgU0xHX3BsdXNfIChTTEcrKSB0YWtlcyB1cCBtdWNoIG9mIHRoZSBpbXBvcnRhbmNlIGFzIGl0IGlzIGRlcml2ZWQgZnJvbSBTTEcgKG9uZSBvZiB0aGUga2V5IGNvbnRyaWJ1dG9ycyB0byBXb3J0aCkuIFRoZXNlIHR5cGVzIG9mIHZhcmlhYmxlcyB3aWxsIGJlIHJlbW92ZWQgZHVyaW5nIHZhcmlhYmxlIHNlbGVjdGlvbiBpbiB0aGUgbmV4dCBzdGVwLiAgDQoqWEdCb29zdCBkb2N1bWVudGF0aW9uIGNhbiBiZSBmb3VuZCBmb3IgbW9yZSBnZW5lcmFsIG1vZGVscyBbaGVyZS5dKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vY29kZS9ydGF0bWFuL21hY2hpbmUtbGVhcm5pbmctd2l0aC14Z2Jvb3N0LWluLXIvbm90ZWJvb2spKg0KDQpgYGB7cn0NCg0KI0RlZmF1bHRzIGZvciB4Z2Jvb3N0IG1vZGVsDQpncmlkX2RlZmF1bHQgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSAxMDAsDQogIG1heF9kZXB0aCA9IDYsDQogIGV0YSA9IDAuMywNCiAgZ2FtbWEgPSAwLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gMSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IDEsDQogIHN1YnNhbXBsZSA9IDENCikNCg0KI1RoaXMgaXMgYSBibGFuayB0cmFpbl9jb250cm9sIHNldCwgdGhpcyB3aWxsIGJlIHVwZGF0ZWQgYWZ0ZXINCnRyYWluX2NvbnRyb2wgPC0gY2FyZXQ6OnRyYWluQ29udHJvbCgNCiAgbWV0aG9kID0gIm5vbmUiLA0KICB2ZXJib3NlSXRlciA9IEZBTFNFLCAjIG5vIHRyYWluaW5nIGxvZw0KICBhbGxvd1BhcmFsbGVsID0gVFJVRSAjIEZBTFNFIGZvciByZXByb2R1Y2libGUgcmVzdWx0cyANCikNCg0KeGdiX2Jhc2VfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgdHVuZUdyaWQgPSBncmlkX2RlZmF1bHQsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikNCg0KY2FyZXQ6OnZhckltcCh4Z2JfYmFzZV8yMDIxLCBzY2FsZSA9IEYgICkNCg0KDQoNCg0KYGBgDQoNCiMjIEZ1cnRoZXIgVmFyaWFibGUgU2VsZWN0aW9uDQoNCiMjIyBSZW1vdmUgcmVkdW5kYW50IGFuZCBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMNCg0KU2VsZWN0aW9uIFJlbW92YWwgU3RlcCAxOiBDaGVjayBmb3IgaGlnaCBjb3JyZWxhdGlvbnNcDQpOb3JtYWxseSwgdGhpcyBzdGVwIGlzIGRvbmUgZWFybHksIGJ1dCB0aG9zZSBzdGVwcyB3ZXJlIHJlc2VydmVkIGZvciBwcmVwYXJpbmcgdGhlIGRhdGENCg0KYGBge3J9DQoNCmRlcF9jb3IxIDwtIHQoYXMuZGF0YS5mcmFtZShjb3IodHJfdHJlYXRlZF8yMDIxWyAsIGNvbG5hbWVzKHRyX3RyZWF0ZWRfMjAyMSkgIT0gIldvcnRoIl0sDQogICAgICAgICAgICAgICAgdHJfdHJlYXRlZF8yMDIxJFdvcnRoKSkpDQpkZXBfY29yMSA8LQ0KYXMuZGF0YS5mcmFtZSh0KGFzLmRhdGEuZnJhbWUoZGVwX2NvcjEpJT4lIA0KICBzZWxlY3QoIXN0YXJ0c193aXRoKCJsYWciKSkgJT4lICNyZW1vdmUgbGFnIHZhcmlhYmxlcw0KICBzZWxlY3QoIWNvbnRhaW5zKCJfaXNCQUQiKSkpKSANCg0KZGVwX2NvcjEgPC0gdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oZGVwX2NvcjEsIlZBUklBQkxFUyIpJT4lICNyZW1vdmUgaW5kaWNhdG9ycyBmb3IgbWlzc2luZyBkYXRhDQogIGZpbHRlcihWMSA+IDAuNzB8VjEgPCAtMC41KQ0KDQpkZXBfY29yMQ0KDQpkZXBfY29yMiA8LSBjb2xuYW1lcyhyb3dfdG9fbmFtZXModChkZXBfY29yMSkscm93X251bWJlciA9IDEpKQ0KDQoNCg0KYGBgDQpMZXQncyBSZW1vdmUgdmFyaWFibGVzIHdpdGggaGlnaCBjb3JyZWxhdGlvbiB0byB3b3J0aCBtZXRyaWMgKHN1Y2ggYXMgYHdGQi9DYCkNCg0KYGBge3J9DQoNCmlucHV0X3ggPC0gYXMubWF0cml4KCgodHJfdHJlYXRlZF8yMDIxKSklPiUNCiAgIHNlbGVjdCgtV29ydGgpICU+JSAjUmVtb3ZlIGRlcGVuZGVudCB2YXJpYWJsZQ0KICAgICBzZWxlY3QgKC1hbGxfb2YoZGVwX2NvcjIpICkgJT4lICAgICAgDQpzZWxlY3QoIWVuZHNfd2l0aCAoIl9pc0JBRCIpKSkgI2luZGljYXRvciB2YXJpYWJsZSBmb3IgbWlzc2luZyBkYXRhDQoNCmlucHV0X3kgPC0gdHJfdHJlYXRlZF8yMDIxJFdvcnRoDQoNCg0KDQoNCg0KYGBgDQoNClJ1biB0aGUgbW9kZWwgb24gdGhlIG5ldyBkYXRhc2V0IHRvIG1ha2Ugc3VyZSB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZXMgbG9vayBmaW5lDQpgYGB7cn0NCg0KI05vdGUgVHJhaW5pbmcgcGFyYW1ldGVycyB3ZXJlIHNldCBpbiBpbml0aWFsIG1vZGVsIHNldCB1cA0KeGdiX2Jhc2VfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgdHVuZUdyaWQgPSBncmlkX2RlZmF1bHQsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikNCg0KY2FyZXQ6OnZhckltcCh4Z2JfYmFzZV8yMDIxLCBzY2FsZSA9IEYgICkNCg0KDQpgYGANCg0KDQojIyBNb2RlbCB3aXRoIG5ldyBkYXRhICANCg0KIyMjIFR1bmluZyBBbGwgSHlwZXJwYXJhbWV0ZXJzDQpBIHR1bmUgZ3JpZCBhbGxvd3MgdXMgdG8gdGVzdCBhIGxhcmdlIGFtb3VudCBvZiBoeXBlci1wYXJhbWV0ZXJzIGFuZCBmaW5kIHRoZSBtb2RlbCB3aXRoIHRoZSBsb3dlc3QgUk1TRSBmb3IgcHJlZGljdGlvbnMuICAgDQpIb3dldmVyLCBUaGUgbW9yZSB2YWx1ZXMgeW91IHdhbnQgdG8gdGVzdCBhbmQgdGhlIGdyZWF0ZXIgdGhlIGFtb3VudCBvZiBDcm9zcy1Gb2xkIFZhbGlkYXRpb25zIChgbWV0aG9kID0gImN2ImApLCB0aGUgZ3JlYXRlciB0aGUgY29tcHV0YXRpb25hbCB0aW1lIGl0IHdpbGwgdGFrZS4gTW9yZSBpbmZvcm1hdGlvbiBvbiB0aGUgc3BlY2lmaWMgcGFyYW1ldGVycyBjYW4gYmUgZm91bmQgW2hlcmUuXShodHRwczovL3d3dy5oYWNrZXJlYXJ0aC5jb20vcHJhY3RpY2UvbWFjaGluZS1sZWFybmluZy9tYWNoaW5lLWxlYXJuaW5nLWFsZ29yaXRobXMvYmVnaW5uZXJzLXR1dG9yaWFsLW9uLXhnYm9vc3QtcGFyYW1ldGVyLXR1bmluZy1yL3R1dG9yaWFsLykNCg0KYGBge3J9DQoNCiMgbWF4aW11bSBudW1iZXIgb2YgdHJlZXMNCm5yb3VuZHMgPC0gMTAwMA0KDQojIG5vdGUgdG8gc3RhcnQgbnJvdW5kcyBmcm9tIDIwMCwgYXMgc21hbGxlciBsZWFybmluZyByYXRlcyByZXN1bHQgaW4gZXJyb3JzIHNvDQojIGJpZyB3aXRoIGxvd2VyIHN0YXJ0aW5nIHBvaW50cyB0aGF0IHRoZXknbGwgbWVzcyB0aGUgc2NhbGVzDQp0dW5lX2dyaWQgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSBzZXEoZnJvbSA9IDEwMCwgdG8gPSBucm91bmRzLCBieSA9IDUwKSwNCiAgZXRhID0gYygwLjAxLCAwLjAyNSwgMC4wNSwgMC4xKSwNCiAgbWF4X2RlcHRoID0gYygyLCA0LCA2LCA4KSwNCiAgZ2FtbWEgPSAwLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gMSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IDEsDQogIHN1YnNhbXBsZSA9IDENCikNCg0KdHVuZV9jb250cm9sIDwtIGNhcmV0Ojp0cmFpbkNvbnRyb2woDQogIG1ldGhvZCA9ICJjdiIsICMgY3Jvc3MtdmFsaWRhdGlvbg0KICBudW1iZXIgPSA1LCAjIHdpdGggbiBmb2xkcyANCiAgIyMgTm90ZSB0aGlzIHdhcyAjIG91dCBpbiB0aGUgb3JpZ2luYWwgY29kZQ0KICAjaW5kZXggPSBjcmVhdGVGb2xkcyh0cl90cmVhdGVkJElkX2NsZWFuKSwgIyBmaXggdGhlIGZvbGRzDQogIHZlcmJvc2VJdGVyID0gRkFMU0UsICMgbm8gdHJhaW5pbmcgbG9nDQogIGFsbG93UGFyYWxsZWwgPSBUUlVFICMgRkFMU0UgZm9yIHJlcHJvZHVjaWJsZSByZXN1bHRzIA0KKQ0KDQoNCg0KYGBgDQoNCipSdW5uaW5nIHRoZSBpbml0aWFsIHR1bmluZyBtb2RlbCogIA0KYGBge3J9DQojTm90ZSBJIHdpbGwgYmUgdGltaW5nIHRoZXNlIHJ1bnMgdG8gZ2l2ZSBhbiBlc3RpbWF0ZSBvbiBob3cgbG9uZyB0aGlzIG1vZGVsIHRha2VzIHRvIHJ1bg0Kc3RhcnRfdGltZSA8LSBTeXMudGltZSgpDQoNCnhnYl90dW5lXzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHR1bmVfY29udHJvbCwNCiAgdHVuZUdyaWQgPSB0dW5lX2dyaWQsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IEZBTFNFDQogICx2ZXJib3NpdHkgPSAwDQopDQoNCmVuZF90aW1lIDwtIFN5cy50aW1lKCkNCg0KZW5kX3RpbWUgLSBzdGFydF90aW1lDQoNCmBgYA0KDQoqVHVuaW5nIFBsb3QgYW5kIFZhcmlhYmxlIEltcG9ydGFuY2UqDQpgYGB7cn0NCnZhckltcCh4Z2JfdHVuZV8yMDIxLCBzY2FsZSA9IEYgICkgDQoNCg0KIyBoZWxwZXIgZnVuY3Rpb24gZm9yIHRoZSBwbG90cw0KdHVuZXBsb3QgPC0gZnVuY3Rpb24oeCwgcHJvYnMgPSAuOTApIHsNCiAgZ2dwbG90KHgpICsNCiAgICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMocXVhbnRpbGUoeCRyZXN1bHRzJFJNU0UsIHByb2JzID0gcHJvYnMpLCBtaW4oeCRyZXN1bHRzJFJNU0UpKSkgKw0KICAgIHRoZW1lX2J3KCkNCn0NCg0KdHVuZXBsb3QoeGdiX3R1bmVfMjAyMSkNCmBgYA0KDQojIyMgRmluZSBUdW5pbmcgTW9kZWwgIA0KIyMjIyBTZWNvbmQgVHVuaW5nOiBNYXhpbXVtIERlcHRoIGFuZCBNaW5pbXVtIENoaWxkIFdlaWdodCAgDQpBZnRlciBmaXhpbmcgdGhlIGxlYXJuaW5nIHJhdGUgdG8gMC4xIGFuZCB3ZeKAmWxsIGFsc28gc2V0IG1heGltdW0gZGVwdGggdG8gMyArLTEgKG9yICsyIGlmIG1heF9kZXB0aCA9PSAyKSB0byBleHBlcmltZW50IGEgYml0IGFyb3VuZCB0aGUgc3VnZ2VzdGVkIGJlc3QgdHVuZSBpbiBwcmV2aW91cyBzdGVwLiBUaGVuLCB3ZWxsIGZpeCBtYXhpbXVtIGRlcHRoIGFuZCBtaW5pbXVtIGNoaWxkIHdlaWdoDQoNCmBgYHtyfQ0KdHVuZV9ncmlkMiA8LSBleHBhbmQuZ3JpZCgNCiAgbnJvdW5kcyA9IHNlcShmcm9tID0gNTAsIHRvID0gbnJvdW5kcywgYnkgPSA1MCksDQogIGV0YSA9IHhnYl90dW5lXzIwMjEkYmVzdFR1bmUkZXRhLA0KICBtYXhfZGVwdGggPSBpZmVsc2UoeGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGggPT0gMiwNCiAgICBjKHhnYl90dW5lXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoOjQpLA0KICAgIHhnYl90dW5lXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoIC0gMTp4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCArIDEpLA0KICBnYW1tYSA9IDAsDQogIGNvbHNhbXBsZV9ieXRyZWUgPSAxLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAyLCAzKSwNCiAgc3Vic2FtcGxlID0gMQ0KKQ0KDQp4Z2JfdHVuZTJfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHVuZV9jb250cm9sLA0KICB0dW5lR3JpZCA9IHR1bmVfZ3JpZDIsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikNCg0KdHVuZXBsb3QoeGdiX3R1bmUyXzIwMjEpDQoNCnhnYl90dW5lMl8yMDIxJGJlc3RUdW5lDQoNCnZhckltcCh4Z2JfdHVuZTJfMjAyMSwgc2NhbGUgPSBGICApIA0KYGBgDQoNCiMjIyMgVGhpcmQgVHVuaW5nOiBDb2x1bW4gYW5kIFJvdyBTYW1wbGluZw0KDQpgYGB7cn0NCg0KdHVuZV9ncmlkMyA8LSBleHBhbmQuZ3JpZCgNCiAgbnJvdW5kcyA9IHNlcShmcm9tID0gNTAsIHRvID0gbnJvdW5kcywgYnkgPSA1MCksDQogIGV0YSA9IHhnYl90dW5lXzIwMjEkYmVzdFR1bmUkZXRhLA0KICBtYXhfZGVwdGggPSB4Z2JfdHVuZTJfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGgsDQogIGdhbW1hID0gMCwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC40LCAwLjYsIDAuOCwgMS4wKSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1pbl9jaGlsZF93ZWlnaHQsDQogIHN1YnNhbXBsZSA9IGMoMC41LCAwLjc1LCAxLjApDQopDQoNCnhnYl90dW5lM18yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0dW5lX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gdHVuZV9ncmlkMywNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQp0dW5lcGxvdCh4Z2JfdHVuZTNfMjAyMSwgcHJvYnMgPSAuOTUpDQoNCnhnYl90dW5lM18yMDIxJGJlc3RUdW5lDQoNCnZhckltcCh4Z2JfdHVuZTNfMjAyMSwgc2NhbGUgPSBGICApIA0KYGBgDQoNCiMjIyMgRm91cnRoIFR1bmluZzogR2FtbWEgIA0KTmV4dCwgd2UgYWdhaW4gcGljayB0aGUgYmVzdCB2YWx1ZXMgZnJvbSBwcmV2aW91cyBzdGVwLCBhbmQgbm93IHdpbGwgc2VlIHdoZXRoZXIgY2hhbmdpbmcgdGhlIGdhbW1hIGhhcyBhbnkgZWZmZWN0IG9uIHRoZSBtb2RlbCBmaXQ6DQpgYGB7cn0NCnR1bmVfZ3JpZDQgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSBzZXEoZnJvbSA9IDUwLCB0byA9IG5yb3VuZHMsIGJ5ID0gNTApLA0KICBldGEgPSB4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJGV0YSwNCiAgbWF4X2RlcHRoID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoLA0KICBnYW1tYSA9IGMoMCwgMC4wNSwwLjEsIDAuMiwwLjQsIDAuNSwgMC43LCAwLjksIDEuMCksDQogIGNvbHNhbXBsZV9ieXRyZWUgPSB4Z2JfdHVuZTNfMjAyMSRiZXN0VHVuZSRjb2xzYW1wbGVfYnl0cmVlLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWluX2NoaWxkX3dlaWdodCwNCiAgc3Vic2FtcGxlID0geGdiX3R1bmUzXzIwMjEkYmVzdFR1bmUkc3Vic2FtcGxlDQopDQoNCnhnYl90dW5lNF8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0dW5lX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gdHVuZV9ncmlkNCwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQp0dW5lcGxvdCh4Z2JfdHVuZTRfMjAyMSkNCg0KeGdiX3R1bmU0XzIwMjEkYmVzdFR1bmUNCg0KdmFySW1wKHhnYl90dW5lNF8yMDIxLCBzY2FsZSA9IEYgICkgDQpgYGANCg0KIyMjIyBGaWZ0aCBUdW5pbmc6IFJlZHVjaW5nIHRoZSBMZWFybmluZyBSYXRlICANCk5vdywgd2UgaGF2ZSB0dW5lZCB0aGUgaHlwZXJwYXJhbWV0ZXJzIGFuZCBjYW4gc3RhcnQgcmVkdWNpbmcgdGhlIGxlYXJuaW5nIHJhdGUgdG8gZ2V0IHRvIHRoZSBmaW5hbCBtb2RlbDogIA0KDQpgYGB7cn0NCnN0YXJ0X3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQp0dW5lX2dyaWQ1IDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0gc2VxKGZyb20gPSAxMDAsIHRvID0gMTAwMDAsIGJ5ID0gNzUpLA0KICAgZXRhID0gYygwLjAxLCAwLjAxNSwgMC4wMjUsMC4wMzUsIDAuMDUsMC43NSwgMC4xKSwNCiAgbWF4X2RlcHRoID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoLA0KICBnYW1tYSA9IHhnYl90dW5lNF8yMDIxJGJlc3RUdW5lJGdhbW1hLA0KICBjb2xzYW1wbGVfYnl0cmVlID0geGdiX3R1bmUzXzIwMjEkYmVzdFR1bmUkY29sc2FtcGxlX2J5dHJlZSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1pbl9jaGlsZF93ZWlnaHQsDQogIHN1YnNhbXBsZSA9IHhnYl90dW5lM18yMDIxJGJlc3RUdW5lJHN1YnNhbXBsZQ0KKQ0KDQoNCg0KeGdiX3R1bmU1XzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHR1bmVfY29udHJvbCwNCiAgdHVuZUdyaWQgPSB0dW5lX2dyaWQ1LA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCiN0dW5lcGxvdCh4Z2JfdHVuZTVfMjAyMSkNCg0KZW5kX3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQplbmRfdGltZSAtIHN0YXJ0X3RpbWUNCg0KeGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUNCg0KdmFySW1wKHhnYl90dW5lNV8yMDIxLCBzY2FsZSA9IEYgICkgDQpgYGANCg0KDQojIyMjIEZpdHRpbmcgRmluYWwgTW9kZWwNCg0KYGBge3J9DQoNCihmaW5hbF9ncmlkXzIwMjEgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRucm91bmRzLA0KICBldGEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRnYW1tYSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikpDQoNCih4Z2JfbW9kZWxfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgdHVuZUdyaWQgPSBmaW5hbF9ncmlkXzIwMjEsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikpDQoNCnZhckltcCh4Z2JfbW9kZWxfMjAyMSwgc2NhbGUgPSBGICApIA0KDQpgYGANCg0KIyMjIE1vZGVsIFBlcmZvcm1hbmNlICANCg0KDQojIyMjIENoZWNraW5nIE1vZGVsIG9uIFRlc3QgU3BsaXQgRGF0YSAgDQoNCmBgYHtyfQ0KDQoNCnlfcHJlZF90ZXN0IDwtIHByZWRpY3QoeGdiX21vZGVsXzIwMjEsIGRhdGEubWF0cml4KHRlX3RyZWF0ZWRfMjAyMSkpDQoNCnRlc3Rfc3RhdHM9IGNiaW5kKCh0ZV90cmVhdGVkXzIwMjEkV29ydGgpLHlfcHJlZF90ZXN0KQ0KDQp0ZXN0X3N0YXRzUjIgPSBjb3IodGVzdF9zdGF0c1ssMV0sdGVzdF9zdGF0c1ssMl0pXjINCg0KcHJpbnQodGVzdF9zdGF0c1IyKQ0KDQoNCnlfcHJlZF90cmFpbiA8LSBwcmVkaWN0KHhnYl9tb2RlbF8yMDIxLCBkYXRhLm1hdHJpeCh0cl90cmVhdGVkXzIwMjEpKQ0KDQp0cmFpbl9zdGF0cyA9IGNiaW5kKCh0cl90cmVhdGVkXzIwMjEkV29ydGgpLHlfcHJlZF90cmFpbikNCg0KdHJhaW5fc3RhdHNSMiA9IGNvcih0cmFpbl9zdGF0c1ssMV0sdHJhaW5fc3RhdHNbLDJdKV4yDQoNCnByaW50KHRyYWluX3N0YXRzUjIpDQoNCiN0ZXN0IGRhdGFzZXQNCnggPC0gc2VsZWN0KHRlX3RyZWF0ZWRfMjAyMSwgLVdvcnRoKQ0KeSA8LSAodGVfdHJlYXRlZF8yMDIxJFdvcnRoKQ0KDQooeGdiX21vZGVsX3Jtc2UgPC0gTW9kZWxNZXRyaWNzOjpybXNlKHksIHByZWRpY3QoeGdiX21vZGVsXzIwMjEsIG5ld2RhdGEgPSB4KSkpDQoNCmhvbGRvdXRfeCA8LSBzZWxlY3QodHJfdHJlYXRlZF8yMDIxLCAtV29ydGgpDQpob2xkb3V0X3kgPC0gdHJfdHJlYXRlZF8yMDIxJFdvcnRoDQoNCih4Z2JfbW9kZWxfcm1zZSA8LSBNb2RlbE1ldHJpY3M6OnJtc2UoaG9sZG91dF95LCBwcmVkaWN0KHhnYl9tb2RlbF8yMDIxLCBuZXdkYXRhID0gaG9sZG91dF94KSkpDQoNCg0KYGBgDQoNCiMjIyMgR3JhcGhpY2FsIFJlcHJlc2VudGF0aW9uIG9mIE1vZGVsICAgDQoNCg0KYGBge3J9DQoNCmdncGxvdDI6OmdncGxvdCgpICsNCiAgYWVzKHggPSB0ZXN0X3N0YXRzWywxXSwgeSA9IHRlc3Rfc3RhdHNbLDJdKSArDQogIGdlb21faml0dGVyKCkgKw0KICB4bGFiKCJQcmVkaWN0ZWQgVmFsdWVzIikgKw0KICB5bGFiKCJBY3R1YWwgVmFsdWVzIikgKw0KICBnZ3RpdGxlKCJSZXN1bHRzIG9mIEhpdHRpbmcgTW9kZWwgb24gVGVzdCBEYXRhIikrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsc2l6ZSA9IDIyLGNvbG9yID0ic3RlZWwgYmx1ZSIpKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikNCmBgYA0KDQoNCiMgQ3JlYXRpbmcgMjAyMiBQcm9qZWN0aW9ucyBmcm9tIE1vZGVsICB7LnRhYnNldCAudGFic2V0LXBpbGxzfSANCg0KDQojIyBSZS1maXQgbW9kZWwgZm9yIEltcG9ydGFudCBWYXJpYWJsZXMNCk5vdyB0aGF0IHdlIGhhdmUgYW4gYWNjZXB0YWJsZSBtb2RlbCwgd2UgY2FuIHVzZSBpdCB0byBjcmVhdGUgcHJvamVjdGlvbnMgZm9yIGhvdyB3ZWxsIHdlIHRoaW5rIHBsYXllcnMgc2hvdWxkIGRvIGluIDIwMjIgYmFzZWQgb24gdGhlaXIgaGl0dGluZyBzdGF0aXN0aWNzIGluIDIwMjEuIEZpcnN0IGxldCdzIHJlZHVjZQ0KDQoxLiBPbmx5IGtlZXAgdmFyaWFibGVzIHdpdGggaGlnaCBlbm91Z2ggaW1wb3J0YW5jZSBpbiBtb2RlbCAgDQoNCmBgYHtyfQ0KDQoNCnZpcCh4Z2JfbW9kZWxfMjAyMSwgbnVtX2ZlYXR1cmVzID0gMzApICAjIDEwIGlzIHRoZSBkZWZhdWx0LCAzMCBnaXZlcyBhIHZpc3VhbCBvbiB0aGUgdG9wIDMwIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzIG9mIHRoZSBtb2RlbA0KDQp1bnNjYWxldmkgPSB2aSh4Z2JfbW9kZWxfMjAyMSwgbWV0aG9kPSJtb2RlbCIpICNzaG93cyB0aGUgbnVtYmVycyBiZWhpbmQgdGhlIHBsb3QNCg0KdW5zY2FsZXZpJEltcG9ydGFuY2VfcGVyYyA9IHdpdGgodW5zY2FsZXZpLEltcG9ydGFuY2Uvc3VtKEltcG9ydGFuY2UpKSAjYWRkcyBwZXJjZW50YWdlcyANCg0KdW5zY2FsZXZpICMgaW1wb3J0YW5jZSBieSB2YXJpYWJsZXMNCg0KdmFyaWFibGVzX3RvX2tlZXBfMjAyMSA9IHN1YnNldCh1bnNjYWxldmksIEltcG9ydGFuY2VfcGVyYyA+IDAuMDAxMCkgJT4lIHNlbGVjdChWYXJpYWJsZSkgI0tlZXAgVmFyaWFibGVzIHRoYXQgZXhwbGFpbiBhdCBsZWFzdCBhIHNtYWxsIGFtb3VudCBbMC4xJV0gb2YgdGhlIG1vZGVsLiBUaGlzIGlzIGEgbG93IHRocmVzaG9sZCBmb3IgaW5jbHVzaW9uICxidXQgeW91IGNhbiBhZGp1c3QgdGhpcw0KDQp2YXJpYWJsZXNfdG9fa2VlcF8yMDIxYiA9IHQodmFyaWFibGVzX3RvX2tlZXBfMjAyMSkNCg0KdmFyaWFibGVzX3RvX2tlZXBfMjAyMiA9IGNvbG5hbWVzKHJvd190b19uYW1lcyh2YXJpYWJsZXNfdG9fa2VlcF8yMDIxYixyb3dfbnVtYmVyID0gMSkpDQoNCnRyX3RyZWF0ZWRfMjAyMiA9IHRyX3RyZWF0ZWRfMjAyMSAlPiUgIHNlbGVjdChXb3J0aCxvbmVfb2YodmFyaWFibGVzX3RvX2tlZXBfMjAyMikscGxheWVyaWQsc3RhcnRzX3dpdGgoIlRlYW1fbGV2X3hfIikpICNrZWVwIG1vZGVsZWQgaW1wb3J0YW50IHZhcmlhYmxlcyBhbG9uZyB3aXRoIHRlYW0gaW5kaWNhdG9yIHZhcmlhYmxlcw0KDQp0ZV90cmVhdGVkXzIwMjIgPSB0ZV90cmVhdGVkXzIwMjEgJT4lICBzZWxlY3QoV29ydGgsb25lX29mKHZhcmlhYmxlc190b19rZWVwXzIwMjIpLHBsYXllcmlkLHN0YXJ0c193aXRoKCJUZWFtX2xldl94XyIpKQ0KDQppbnB1dF94XzIwMjIgPSBhcy5tYXRyaXgoc2VsZWN0KHRyX3RyZWF0ZWRfMjAyMiwgLVdvcnRoKSkNCg0KaW5wdXRfeV8yMDIyID0gdHJfdHJlYXRlZF8yMDIyJFdvcnRoDQoNCg0KDQpgYGANCg0KMi4gUmUtZml0IG1vZGVsIHdpdGggcmVkdWNlZCB2YXJpYWJsZSBzY29wZSAgDQoNCmBgYHtyfQ0KDQoNCihmaW5hbF9ncmlkXzIwMjEgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRucm91bmRzLA0KICBldGEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRnYW1tYSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikpDQoNCih4Z2JfbW9kZWxfMjAyMiA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94XzIwMjIsDQogIHkgPSBpbnB1dF95XzIwMjIsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gZmluYWxfZ3JpZF8yMDIxLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopKQ0KDQoNCnZpcCh4Z2JfbW9kZWxfMjAyMiwgbnVtX2ZlYXR1cmVzID0gMzApDQoNCnVuc2NhbGV2aTI0ID0gdmkoeGdiX21vZGVsXzIwMjIsIG1ldGhvZD0ibW9kZWwiKQ0KDQp1bnNjYWxldmkyNCRJbXBvcnRhbmNlX3BlcmMgPSB3aXRoKHVuc2NhbGV2aTI0LEltcG9ydGFuY2Uvc3VtKEltcG9ydGFuY2UpKSANCg0KdW5zY2FsZXZpMjQNCg0Kc2F2ZSh4Z2JfbW9kZWxfMjAyMixmaWxlID0gJzIwMjJfSGl0dGluZzZ4Nl9Nb2RlbC5SZGF0YScpDQoNCmhpdHRpbmc2eDYgPSB4Z2JfbW9kZWxfMjAyMg0KDQpoaXR0aW5naW5wdXQ2WDYgPSBpbnB1dF94XzIwMjINCg0KI0ZvciBhbnl0aGluZyBhYm92ZSBicmVha2luZ19JUCB3ZSBuZWVkIHRvIGNyZWF0ZSBwcm9qZWN0aW9uIHRhYmxlIGJ5IGFnZSBvciBhZ2UgYnVja2V0DQoNCiN3cml0ZV9jc3YodW5zY2FsZXZpMjQsInVuc2NhbGV2aTI0LmNzdiIpDQoNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgMjAyMiBQcm9qZWN0aW9ucyBGdWxsICANCg0KRmlyc3QgbGV0J3MgcHJlcGFyZSBhIGZpbGUgZm9yIHByZWRpY3RpbmcgYmFzZWQgb24gb3VyIG1vZGVsIG9iamVjdA0KDQpgYGB7cn0NCg0KDQp2YXJpYWJsZXNsYWc2eGI9IHJvd190b19uYW1lcyhhcy5kYXRhLmZyYW1lKHQodmFyaWFibGVzX3RvX2tlZXBfMjAyMikpLHJvd19udW1iZXIgPSAxKSAgJT4lIHNlbGVjdCAoc3RhcnRzX3dpdGgoImxhZyIpKQ0KDQp2YXJpYWJsZXNfbm9sYWc2eGIgPSAob3dtcjo6cmVtb3ZlX3ByZWZpeCh2YXJpYWJsZXNsYWc2eGIsImxhZyIgLCBzZXAgPSAiXyIpKQ0KDQpEYXRhX1ByZWRpY3RfMjAyMmE2eGIgPSB0b3RhbF90cmVhdGVkXzIwMjFfaGl0dGluZyAlPiUgc2VsZWN0IChvbmVfb2YoY29sbmFtZXModmFyaWFibGVzX25vbGFnNnhiKSksU2Vhc29uLHBsYXllcmlkKQ0KDQpjb2xuYW1lcyhEYXRhX1ByZWRpY3RfMjAyMmE2eGIpIDwtIHBhc3RlMCgibGFnXyIsIGNvbG5hbWVzKERhdGFfUHJlZGljdF8yMDIyYTZ4YikpDQoNCkRhdGFfUHJlZGljdF8yMDIyYjZ4YiA9IHRvdGFsX3RyZWF0ZWRfMjAyMV9oaXR0aW5nICU+JSBzZWxlY3QgKG9uZV9vZihjb2xuYW1lcyh2YXJpYWJsZXNfbm9sYWc2eGIpKSkNCmNvbG5hbWVzKERhdGFfUHJlZGljdF8yMDIyYjZ4YikgPSBjb2xuYW1lcyh2YXJpYWJsZXNsYWc2eGIpDQoNCnZhcmlhYmxlc190b19rZWVwXzIwMjJfbm9sYWc2eGIgPSB0b3RhbF90cmVhdGVkXzIwMjFfaGl0dGluZyAlPiUgc2VsZWN0KG9uZV9vZih2YXJpYWJsZXNfdG9fa2VlcF8yMDIyKSxTZWFzb24scGxheWVyaWQsc3RhcnRzX3dpdGgoIlRlYW1fbGV2X3hfIikpJT4lIHNlbGVjdCgtb25lX29mKGNvbG5hbWVzKERhdGFfUHJlZGljdF8yMDIyYjZ4YikpKQ0KDQoNCkRhdGFfcHJlZGljdF8yMDIyNnhiID0gc3FsZGYoDQogICINCiAgc2VsZWN0IGEuKixiLiogZnJvbQ0KICBEYXRhX1ByZWRpY3RfMjAyMmE2eGIgYSwNCiAgdmFyaWFibGVzX3RvX2tlZXBfMjAyMl9ub2xhZzZ4YiBiDQogIG9uIGIucGxheWVyaWQgPSBhLmxhZ19wbGF5ZXJpZA0KICBhbmQgYi5TZWFzb24gPSBhLmxhZ19TZWFzb24NCiAgIg0KKSAlPiUgc2VsZWN0KC1sYWdfcGxheWVyaWQsbGFnX1NlYXNvbikgJT4lDQogIGZpbHRlcihTZWFzb24gPT0gMjAyMSkgJT4lIA0KICBzZWxlY3Qob25lX29mKHZhcmlhYmxlc190b19rZWVwXzIwMjIpLHBsYXllcmlkLHN0YXJ0c193aXRoKCJUZWFtX2xldl94XyIpKQ0KDQoNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBDcmVhdGUgUHJlZGljdGlvbnMgZm9yIE1vZGVsDQoNCiMjIyBSdW4gUHJvamVjdGlvbnMgb24gUGxheWVycyB3aG8gUGxheWVkIGluIDIwMjENCg0KVGhpcyBpcyB0aGUgcmF3IHByZWRpY3Rpb24gc2NvcmUgcGVyIElQIGZvciBlYWNoIHBpdGNoZXINCg0KYGBge3J9DQoNCmhpdHRpbmdfcHJlZGljdGlvbnM2eGIgPSBhcy5kYXRhLmZyYW1lKHByZWRpY3QoeGdiX21vZGVsXzIwMjIsRGF0YV9wcmVkaWN0XzIwMjI2eGIpKQ0KDQpuYW1lcyhoaXR0aW5nX3ByZWRpY3Rpb25zNnhiKSA9IGMoIlByZWRpY3RfU2NvcmUiKQ0KDQpEYXRhX3ByZWRpY3RfMjAyMl93X2hpdHRpbmdfUHJlZGljdGlvbnM2eGIgPSBjYmluZChEYXRhX3ByZWRpY3RfMjAyMjZ4YixoaXR0aW5nX3ByZWRpY3Rpb25zNnhiKSAlPiUgc2VsZWN0KHBsYXllcmlkLFByZWRpY3RfU2NvcmUpDQoNCmhlYWQoRGF0YV9wcmVkaWN0XzIwMjJfd19oaXR0aW5nX1ByZWRpY3Rpb25zNnhiKQ0KDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCg0KYGBge3J9DQoNCkxhdGVzdF8yMDIyX2hpdHRpbmdkYXRhX0ZQID0gcmVhZF9jc3YoIkZhbkdyYXBoX0ZhbnRhc3lfQmFzZWJhbGxfSGl0dGluZy5jc3YiKQ0KDQpMYXRlc3RfMjAyMl9oaXR0aW5nZGF0YV9GUA0KDQoNCmBgYA0KDQoNCg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KYGBge3IsIHdhcm5pbmcgPSBGYWxzZX0NCg0KDQpoaXR0aW5nX0RhdGFfTm9uQWRqX1Byb2plY3Rpb25zNnhiID0gc3FsZGYoDQogICINCiAgc2VsZWN0IGEuKixiLlByZWRpY3RfU2NvcmUNCiAgZnJvbSBMYXRlc3RfMjAyMl9oaXR0aW5nZGF0YV9GUCBhIA0KICBsZWZ0IGpvaW4gDQogIERhdGFfcHJlZGljdF8yMDIyX3dfaGl0dGluZ19QcmVkaWN0aW9uczZ4YiBiDQogIG9uIGEucGxheWVyaWQgPSBiLnBsYXllcmlkDQogICINCikgJT4lIGZpbHRlcihBRFA8MzcwIHwgaXMubmEoUHJlZGljdF9TY29yZSk9PUYpDQoNCg0KaGl0dGluZ19EYXRhX0Fkal9Qcm9qZWN0aW9uczZ4YiA9DQpoaXR0aW5nX0RhdGFfTm9uQWRqX1Byb2plY3Rpb25zNnhiICU+JSANCiAgbXV0YXRlKA0KICAgIEF2Z19QQSA9IDMwMCwNCiAgICBBZGpQcmVkaWN0X1Njb3JlX3JhdyA9IGlmZWxzZShpcy5uYShQcmVkaWN0X1Njb3JlKSxOQSxQcmVkaWN0X1Njb3JlKihQQS9BdmdfUEEpKSwNCiAgICBtYXhfcHJlZHNjb3JlPSBtYXgoQWRqUHJlZGljdF9TY29yZV9yYXcsbmEucm0gPSBUKSwNCiAgICBBZGpQcmVkaWN0X1Njb3JlID0gaWZlbHNlIChpcy5uYShBZGpQcmVkaWN0X1Njb3JlX3JhdyksTkEsQWRqUHJlZGljdF9TY29yZV9yYXcgKjEwMC9tYXhfcHJlZHNjb3JlKSwNCiAgICBXQVJfcmFuayA9IG9yZGVyKG9yZGVyKHJhbmsoV0FSLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gVFJVRSkpLA0KICAgIEFkalByZWRpY3RfU2NvcmVfUmFuayA9IG9yZGVyKG9yZGVyKHJhbmsoQWRqUHJlZGljdF9TY29yZSx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IFRSVUUpKS1zdW0oaXMubmEoQWRqUHJlZGljdF9TY29yZSkpLA0KICAgICAgICBSYW5rc19BYm92ZV9BRFAgPSBBRFAgLSBBZGpQcmVkaWN0X1Njb3JlX1JhbmsNCiAgKSAlPiUgc2VsZWN0IChOYW1lLEFEUCxXQVIsIFdBUl9yYW5rLEFkalByZWRpY3RfU2NvcmUgLEFkalByZWRpY3RfU2NvcmVfUmFuayxSYW5rc19BYm92ZV9BRFApDQoNCg0KICANCg0KZ2dwbG90Mjo6cXBsb3QoaGl0dGluZ19EYXRhX0Fkal9Qcm9qZWN0aW9uczZ4YiRBZGpQcmVkaWN0X1Njb3JlLCBtYWluPSJQcmVkaWN0aW9ucyIpICsgZ2VvbV9oaXN0b2dyYW0oY29sb3VyPSJibGFjayIsIGZpbGw9ImdyZXkiKSArIHRoZW1lX2J3KCkNCg0KDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgMjAyMiBQcm9qZWN0aW9ucyBGdWxsDQoNCiMjIFRhYmxlIG9mIGhpdHRpbmcgUHJvamVjdGlvbnMgKFBsYXllcnMgd2hvIERpZG4ndCBQbGF5IGluIDIwMjEgLSBSZWNpZXZlIGFuIE5BKQ0KDQpBZGpQcmVkaWN0X1Njb3JlIGFyZSBub3JtYWxpemVkIHRvIDEwMA0KDQpgYGB7cn0NCg0KdGFibGVleHBvcnQgPQ0KaGl0dGluZ19EYXRhX0Fkal9Qcm9qZWN0aW9uczZ4YiAlPiUNCiAgYXJyYW5nZSAoQURQLFdBUikgJT4lIA0KICBrYmwoKSAlPiUgDQoga2FibGVfbWF0ZXJpYWwoYygic3RyaXBlZCIsICJob3ZlciIsImNvbmRlbnNlZCIsInJlc3BvbnNpdmUiKSxmdWxsX3dpZHRoID0gRixmaXhlZF90aGVhZCA9IFQpDQoNCnNhdmVfa2FibGUodGFibGVleHBvcnQsZmlsZSA9ICJoaXR0aW5nNng2Lmh0bWwiKQ0KDQojdGFibGVleHBvcnQNCg0KDQoNCmBgYA0KDQpUaGlzIGlzIGEgYmV0dGVyIGZvcm1hdHRlZCBUYWJsZQ0KDQpgYGB7ciAsIHdhcm5pbmc9RkFMU0V9DQoNCg0KZnRfZHQgPC0gaGl0dGluZ19EYXRhX0Fkal9Qcm9qZWN0aW9uczZ4YlsxOm5yb3coaGl0dGluZ19EYXRhX0Fkal9Qcm9qZWN0aW9uczZ4YiksIDE6bmNvbChoaXR0aW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNnhiKV0gJT4lIA0KICBmaWx0ZXIoQWRqUHJlZGljdF9TY29yZV9SYW5rPjApJT4lICBhcnJhbmdlKChBZGpQcmVkaWN0X1Njb3JlX1JhbmspKQ0KDQpmdF9kdCRBRFAgPC0gY29sb3JfdGlsZSgid2hpdGUiLCAicmVkIikoZnRfZHQkQURQKQ0KDQpmdF9kdCRXQVIgPC0gY29sb3JfYmFyKCJsaWdodGJsdWUiKShmdF9kdCRXQVIpDQoNCmZ0X2R0JEFkalByZWRpY3RfU2NvcmU8LSBjb2xvcl9iYXIoImxpZ2h0Ymx1ZSIpKGZ0X2R0JEFkalByZWRpY3RfU2NvcmUpDQoNCmZ0X2R0JFdBUl9SYW5rIDwtIGNvbG9yX3RpbGUoImdyZWVuIiwib3JhbmdlIikoZnRfZHQkV0FSX3JhbmspDQoNCmZ0X2R0JFByZWRpY3RfUmFuayA8LSBjb2xvcl90aWxlKCJncmVlbiIsIm9yYW5nZSIpKGZ0X2R0JEFkalByZWRpY3RfU2NvcmVfUmFuaykgDQoNCg0KZnRfZHQkUmFua3NfQWJvdmVfQURQIDwtIA0KICBpZmVsc2UoDQogIGZ0X2R0JFJhbmtzX0Fib3ZlX0FEUCA8IDAsDQogIGNlbGxfc3BlYyhyb3VuZChmdF9kdCRSYW5rc19BYm92ZV9BRFAsMiksIGNvbG9yID0gInJlZCIsIGl0YWxpYyA9IFQpLA0KICBjZWxsX3NwZWMocm91bmQoZnRfZHQkUmFua3NfQWJvdmVfQURQLDIpLCBjb2xvciA9ICJncmVlbiIsIGl0YWxpYyA9IFQpDQopDQoNCg0KZnRfZHQyIDwtIGZ0X2R0W2MoIk5hbWUiLCAiQURQIiwgIldBUiIsICJBZGpQcmVkaWN0X1Njb3JlIiwgIldBUl9SYW5rIiwiUHJlZGljdF9SYW5rIiwiUmFua3NfQWJvdmVfQURQIildDQoNCg0KDQp0YWJsZV9leHBvcnQgPSANCmtibChmdF9kdDIsIGVzY2FwZSA9IEYpICU+JSANCiBrYWJsZV9tYXRlcmlhbChjKCJzdHJpcGVkIiwgImhvdmVyIiwiY29uZGVuc2VkIiwicmVzcG9uc2l2ZSIpLGZ1bGxfd2lkdGggPSBGLGZpeGVkX3RoZWFkID0gVCkgJT4lICAgY29sdW1uX3NwZWMoNiwgd2lkdGggPSAiM2NtIikgJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICIsICJTY29yZXMiID0gMywgIlJhbmtzIiA9IDIsIiAiKSkNCnNhdmVfa2FibGUodGFibGVfZXhwb3J0LGZpbGUgPSAiSGl0dGluZzZ4Nl91cGRhdGVkLmh0bWwiKQ0KICANCnRhYmxlX2V4cG9ydCAgDQoNCg0KDQoNCg0KDQpgYGANCg0KDQoNCg0KDQoNCg0KPC9odG1sPg0K