A couple of weeks ago, as part of a technical assessment for an application with a club, I was tasked with designing an algorithm to grade the quality of contact of batted balls. I came up with a metric called Quality of Metric Plus (QC+) (original, right?). I figured that it would be worth while to write up my process and apply a similar methodology to publicly available data and perhaps gain some insights on players. The dataset provided by the team had no information on who the batter or pitcher were or any other contextual variables, so this is not a direct replica of my previous work. However, I do think this will provide better results and can include some fun features such as defense shifting and spray angle. Mainly, I hope this can show one way of coming up with a metric and share what I learned along the way.

In this notebook, I will focus on data acquisition and cleaning. In subsequent posts, I will go through the rest of the data science process. Feature engineering and selection, model selection and validation, and then lastly metric creation, results, and further analysis.

I first start by loading in libraries.

library(baseballr)
library(magrittr)
library(tidyverse)
library(ggplot2)
library(ggthemes)
library(patchwork)
library(arm)
library(blme)
library(lme4)
library(xgboost)
library(ggrepel)

Data Acquisition

I already had downloaded a csv for each season prior to this project. All of the data is from Baseball Savant, and scraped using the baseballr package. If you’re looking to download Baseball Savant data in R, Robert Frey has a good walk-through tutorial on YouTube of doing so (youtube.com/watch?v=swJr4u-HYr0). For the purpose of this project, I’m going to be using the 2015-2019 regular seasons. There will eventually be a model for each season.

savant15 <- read_csv("savant15.csv") %>% dplyr::select(-c("X1"))
savant16 <- read_csv("savant16.csv") %>% dplyr::select(-c("X1"))
savant17 <- read_csv("savant17.csv") %>% dplyr::select(-c("X1"))
savant18 <- read_csv("savant18.csv") %>% dplyr::select(-c("X1"))
savant19 <- read_csv("savant19.csv")

Data Cleaning

Since I’m just focusing on quality of contact, I only need batted balls.

#ugly code b/c my R couldn't handle a dataframe the size of all five seasons
batted_ball_15 <- savant15 %>%
  filter(events == "field_out" | events == "single" | events == "double" | events == "triple" | events == "home_run" | events == "sac_fly" | 
           events == "grounded_into_double_play" | events == "double_play" | events == "sac_fly_double_play" | events == "fielders_choice" | 
           events == "fielders_choice_out" | events == "field_error" | events == "sac_bunt" | events == "triple_play" | events == "sac_bunt" | 
           events == "force_out" | events == "sac_bunt_double_play")
rm(savant15)

batted_ball_16 <- savant16 %>%
  filter(events == "field_out" | events == "single" | events == "double" | events == "triple" | events == "home_run" | events == "sac_fly" | 
           events == "grounded_into_double_play" | events == "double_play" | events == "sac_fly_double_play" | events == "fielders_choice" | 
           events == "fielders_choice_out" | events == "field_error" | events == "sac_bunt" | events == "triple_play" | events == "sac_bunt" | 
           events == "force_out" | events == "sac_bunt_double_play")
rm(savant16)

batted_ball_17 <- savant17 %>%
  filter(events == "field_out" | events == "single" | events == "double" | events == "triple" | events == "home_run" | events == "sac_fly" | 
           events == "grounded_into_double_play" | events == "double_play" | events == "sac_fly_double_play" | events == "fielders_choice" | 
           events == "fielders_choice_out" | events == "field_error" | events == "sac_bunt" | events == "triple_play" | events == "sac_bunt" | 
           events == "force_out" | events == "sac_bunt_double_play")
rm(savant17)

batted_ball_18 <- savant18 %>%
  filter(events == "field_out" | events == "single" | events == "double" | events == "triple" | events == "home_run" | events == "sac_fly" | 
           events == "grounded_into_double_play" | events == "double_play" | events == "sac_fly_double_play" | events == "fielders_choice" | 
           events == "fielders_choice_out" | events == "field_error" | events == "sac_bunt" | events == "triple_play" | events == "sac_bunt" | 
           events == "force_out" | events == "sac_bunt_double_play")
rm(savant18)

batted_ball_19 <- savant19 %>%
  filter(events == "field_out" | events == "single" | events == "double" | events == "triple" | events == "home_run" | events == "sac_fly" | 
           events == "grounded_into_double_play" | events == "double_play" | events == "sac_fly_double_play" | events == "fielders_choice" | 
           events == "fielders_choice_out" | events == "field_error" | events == "sac_bunt" | events == "triple_play" | events == "sac_bunt" | 
           events == "force_out" | events == "sac_bunt_double_play")
rm(savant19)

batted_ball_data <- rbind(batted_ball_15, batted_ball_16, batted_ball_17, batted_ball_18, batted_ball_19)
rm(batted_ball_15, batted_ball_16, batted_ball_17, batted_ball_18, batted_ball_19)

I’m first going to remove all batted balls that have null values for exit velocity or launch angle.

batted_ball_data %<>%
  filter(!is.na(launch_speed) | !is.na(launch_angle))

No Nulls

The majority of data cleaning has to deal with finding, and removing, ‘no nulls’ batted balls. Andrew Perpetua laid this out in an article in The Hardball Times (https://tht.fangraphs.com/43416-2/). Basically, TrackMan isn’t perfect. It has a tendency to not being able to track certain batted balls (mostly ground balls and popups, more on this later). Over the course of it’s five seasons as MLBAM’s main tracking device, TrackMan got better, but still missed about 10% of batted balls. As Perpetua says, those missing batted balls have their exit velocity and launch angle decided by an algorithm, not a direct TrackMan measurement. The solution to those missing batted balls are imputing the exit velocity and launch angle based on the batted ball type and where the ball was fielded (recorded by a stringer). Perpetua calls these batted balls ‘no nulls’ and he recommends removing those from your dataset.

To illustrate this process, here is the launch angle distribution of the original dataset (containing the ‘no nulls’).

ggplot(batted_ball_data, aes(x = launch_angle)) + 
  geom_density(color = "red") + geom_histogram(aes(y=..density..), color = "royalblue", alpha = .2, binwidth = 4) + 
  xlab("Launch Angle") + ylab("") + 
  ggtitle("Launch Angle Distribution") +
  theme_bw() +
  theme(axis.text.y=element_blank(), 
        axis.ticks.y=element_blank())

We can see that there are three local peaks that normally wouldn’t be expected from a continous variable in a datset of this size. You may ask why does the distribution look like this. The answer is the ‘no nulls’ batted balls.

Here is every combination of exit velocity and launch angle from 2015-2019. There are 306,301 unique combinations of exit velocity and launch angle, from a sample of 629,905 batted balls.

ev_la_combos <- batted_ball_data %>%
  group_by(launch_speed, launch_angle) %>%
  summarise(num = n()) %>%
  arrange(desc(num))
ev_la_combos

Out of those 306,301 combinations, 171,601 (56.02%) of the combinations only have one observation. So we can see that a majority of batted balls are at unique exit velocities and launch angles. From 2015-2019 there were 1,762 unique launch angles and 1,043 unique exit velocities. From a probabilitiy point of view, the likelihood of two batted balls having the exact same exit velocity and launch angle is pretty unlikely.

Five is the magic number that Perpetua chose in his article. Five or more observations of exit velocity and launch angle combination he deemed to be ‘no nulls.’ I’m following a similar process as 300,095 (97.97%) of the combinations have five or less observations. So, I’m going to remove all observations of launch angle and exit velocity that occur more than five times.

ev_la_bb.type_combos <- batted_ball_data %>%
  group_by(launch_speed, launch_angle, bb_type) %>%
  summarise(num = n()) %>%
  arrange(desc(num))
ev_la_bb.type_combos

Popups

popup_combos_to_remove <- ev_la_bb.type_combos %>%
  filter(bb_type == "popup") %>%
  arrange(desc(num)) %>%
  filter(num > 5)

popup_combos_to_remove

Only six popup combinations occur more than five times. Going back to the launch angle distribution earlier, we can see that the driving force of that huge spike on the right side of the center is the 20,000 popups that were imputed to be hit at 80 mph and 69 degrees.

Fly Balls

flyball_combos_to_remove <- ev_la_bb.type_combos %>%
  filter(bb_type == "fly_ball") %>%
  arrange(desc(num)) %>%
  filter(num > 5)

flyball_combos_to_remove

Line Drives

# Line Drives
linedrive_combos_to_remove <- ev_la_bb.type_combos %>%
  filter(bb_type == "line_drive") %>%
  arrange(desc(num)) %>%
  filter(num > 5)

linedrive_combos_to_remove

Ground Balls

gb_combos_to_remove <- ev_la_bb.type_combos %>%
  filter(bb_type == "ground_ball") %>%
  arrange(desc(num))  %>%
  filter(num > 5)

gb_combos_to_remove

And here, we can see why there is a huge spike at around -25 degrees.

A vast majority (~72%) of batted balls to be removed are either popups or ground balls. This confirms what Perpetua mentioned in his article, “… not only does TrackMan have a bias against certain types of batted balls, but these batted balls are generally very weakly hit ground balls and pop-ups.”

In total, I’m going to remove 98,746 batted balls from the dataset. This represents about 15% of the original dataset. I have no problem with people who would argue that this removal criteria is too strict or is too unexact. However, when working with unperfect data (like we are), sometimes it’s better to have an overarching philosphy rather than try to nit-pick about information that we don’t have.

# Removing 'no nulls'
batted_ball_cleaned <- dplyr::anti_join(batted_ball_data, to_be_removed, by = c("launch_speed", "launch_angle", "bb_type"))

Now, let’s look at the launch angle distribution after removing the ‘no nulls’ imputed batted balls.

ggplot(batted_ball_cleaned, aes(x = launch_angle)) + 
  geom_density(color = "red") + geom_histogram(aes(y=..density..), color = "royalblue", alpha = .2, binwidth = 4) + 
  xlab("Launch Angle") + ylab("") + labs(subtitle = "After removing 'no nulls'") +
  ggtitle("Launch Angle Distribution (2015-2019)") +
  theme_bw() +
  theme(axis.text.y=element_blank(), 
        axis.ticks.y=element_blank())

Much better! But not exactly the shape I was expecting.

before_cleaning <- ggplot(batted_ball_data, aes(x = launch_angle)) + 
  geom_density(color = "red") + geom_histogram(aes(y=..density..), color = "royalblue", alpha = .2, binwidth = 4) + 
  xlab("Launch Angle") + ylab("") + labs(subtitle = "Before removing 'no nulls'") +
  ggtitle("Launch Angle Distribution (2015-2019)") +
  theme_bw() +
  theme(axis.text.y=element_blank(), 
        axis.ticks.y=element_blank())
after_cleaning <- ggplot(batted_ball_cleaned, aes(x = launch_angle)) + 
  geom_density(color = "red") + geom_histogram(aes(y=..density..), color = "royalblue", alpha = .2, binwidth = 4) + 
  xlab("Launch Angle") + ylab("") + labs(subtitle = "After removing 'no nulls'") +
  ggtitle("Launch Angle Distribution (2015-2019)") +
  theme_bw() +
  theme(axis.text.y=element_blank(), 
        axis.ticks.y=element_blank())

la_comparison <- before_cleaning + after_cleaning
rm(before_cleaning, after_cleaning)
la_comparison

Here is another way of viewing the distribution changes. We can see that the three local peaks are removed and that the distribution is more centered.

ggplot(data = batted_ball_data, aes(x=launch_angle)) + 
  geom_density(color = "red") + 
  geom_density(data = batted_ball_cleaned, color = "blue") + 
  xlab("Launch Angle") + ylab("") + 
  ggtitle("Distribution of Launch Angle") +
  labs(subtitle = "Red: Original dataset\nBlue: Cleaned dataset") +
  theme_bw() +
  theme(axis.text.y=element_blank(), 
        axis.ticks.y=element_blank())

An astute reader might have noticed that a vast majority of the removed batted balls are poorly hit. Therefore, by removing these, generally, weaker hit balls then the creation of a quality of contact metric will favor the batters who had more weakly hit balls than those who didn’t. By removing a player’s worse hit balls they look a lot better. And that is true. However, the bias I’ve introduced is a necessary evil. We have to work with and accept it. It’s better to work with accurate (i.e. meassured) data than not. It’s important to keep that in mind when analyzing the results of the metric, but I believe this is the correct way of handling this analysis.

Here’s a quick little visualization showing the type of bias inflicted. Removing the ‘no nulls’ helps Alexi Amarista and J.J. Hardy a lot more than Aaron Judge and Franmil Reyes.

to_be_removed %<>%
  mutate(is_no_null = 1)

joined <- batted_ball_data %>%
  left_join(to_be_removed,
            by = c("launch_speed", "launch_angle", "bb_type")) %>%
  mutate(is_no_null = case_when(is_no_null == 1 ~ 1,
                                TRUE ~ 0))
joined %>%
  group_by(player_name) %>%
  summarise(num_bbs = n(),
            num_no_nulls = sum(is_no_null),
            pct_no_nulls = 100*(num_no_nulls / num_bbs),
            xwOBACON = mean(estimated_woba_using_speedangle,na.rm=T),
            wOBACON = mean(woba_value / woba_denom, na.rm=T)) %>%
  filter(num_bbs > 500) %>%
  ggplot(aes(x = pct_no_nulls, y = wOBACON)) + geom_point() + geom_smooth() + 
  labs(x = "Percentage of 'No Nulls' Hit", 
       y = "xwOBACON", 
       subtitle = "Batters with > 500 batted balls from 2015-2019", 
       title = "Percentage of 'No Nulls' Hit vs. Expected Weighted On-Base Average on Contact") + 
  theme_bw() + 
  geom_text_repel(aes(label = player_name), segment.size = 0.2)

LS0tCnRpdGxlOiAiQ3JlYXRpbmcgUUMrIFBhcnQgMSAtIERhdGEgQWNxdWlzaXRpb24gYW5kIENsZWFuaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCkEgY291cGxlIG9mIHdlZWtzIGFnbywgYXMgcGFydCBvZiBhIHRlY2huaWNhbCBhc3Nlc3NtZW50IGZvciBhbiBhcHBsaWNhdGlvbiB3aXRoIGEgY2x1YiwgSSB3YXMgdGFza2VkIHdpdGggZGVzaWduaW5nIGFuIGFsZ29yaXRobSB0byBncmFkZSB0aGUgcXVhbGl0eSBvZiBjb250YWN0IG9mIGJhdHRlZCBiYWxscy4gSSBjYW1lIHVwIHdpdGggYSBtZXRyaWMgY2FsbGVkIFF1YWxpdHkgb2YgTWV0cmljIFBsdXMgKFFDKykgKG9yaWdpbmFsLCByaWdodD8pLiBJIGZpZ3VyZWQgdGhhdCBpdCB3b3VsZCBiZSB3b3J0aCB3aGlsZSB0byB3cml0ZSB1cCBteSBwcm9jZXNzIGFuZCBhcHBseSBhIHNpbWlsYXIgbWV0aG9kb2xvZ3kgdG8gcHVibGljbHkgYXZhaWxhYmxlIGRhdGEgYW5kIHBlcmhhcHMgZ2FpbiBzb21lIGluc2lnaHRzIG9uIHBsYXllcnMuIFRoZSBkYXRhc2V0IHByb3ZpZGVkIGJ5IHRoZSB0ZWFtIGhhZCBubyBpbmZvcm1hdGlvbiBvbiB3aG8gdGhlIGJhdHRlciBvciBwaXRjaGVyIHdlcmUgb3IgYW55IG90aGVyIGNvbnRleHR1YWwgdmFyaWFibGVzLCBzbyB0aGlzIGlzIG5vdCBhIGRpcmVjdCByZXBsaWNhIG9mIG15IHByZXZpb3VzIHdvcmsuIEhvd2V2ZXIsIEkgZG8gdGhpbmsgdGhpcyB3aWxsIHByb3ZpZGUgYmV0dGVyIHJlc3VsdHMgYW5kIGNhbiBpbmNsdWRlIHNvbWUgZnVuIGZlYXR1cmVzIHN1Y2ggYXMgZGVmZW5zZSBzaGlmdGluZyBhbmQgc3ByYXkgYW5nbGUuIE1haW5seSwgSSBob3BlIHRoaXMgY2FuIHNob3cgb25lIHdheSBvZiBjb21pbmcgdXAgd2l0aCBhIG1ldHJpYyBhbmQgc2hhcmUgd2hhdCBJIGxlYXJuZWQgYWxvbmcgdGhlIHdheS4gCgpJbiB0aGlzIG5vdGVib29rLCBJIHdpbGwgZm9jdXMgb24gZGF0YSBhY3F1aXNpdGlvbiBhbmQgY2xlYW5pbmcuIEluIHN1YnNlcXVlbnQgcG9zdHMsIEkgd2lsbCBnbyB0aHJvdWdoIHRoZSByZXN0IG9mIHRoZSBkYXRhIHNjaWVuY2UgcHJvY2Vzcy4gRmVhdHVyZSBlbmdpbmVlcmluZyBhbmQgc2VsZWN0aW9uLCBtb2RlbCBzZWxlY3Rpb24gYW5kIHZhbGlkYXRpb24sIGFuZCB0aGVuIGxhc3RseSBtZXRyaWMgY3JlYXRpb24sIHJlc3VsdHMsIGFuZCBmdXJ0aGVyIGFuYWx5c2lzLgoKSSBmaXJzdCBzdGFydCBieSBsb2FkaW5nIGluIGxpYnJhcmllcy4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShiYXNlYmFsbHIpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGFybSkKbGlicmFyeShibG1lKQpsaWJyYXJ5KGxtZTQpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShnZ3JlcGVsKQpgYGAKCgojIERhdGEgQWNxdWlzaXRpb24KCkkgYWxyZWFkeSBoYWQgZG93bmxvYWRlZCBhIGNzdiBmb3IgZWFjaCBzZWFzb24gcHJpb3IgdG8gdGhpcyBwcm9qZWN0LiBBbGwgb2YgdGhlIGRhdGEgaXMgZnJvbSBCYXNlYmFsbCBTYXZhbnQsIGFuZCBzY3JhcGVkIHVzaW5nIHRoZSBiYXNlYmFsbHIgcGFja2FnZS4gSWYgeW91J3JlIGxvb2tpbmcgdG8gZG93bmxvYWQgQmFzZWJhbGwgU2F2YW50IGRhdGEgaW4gUiwgUm9iZXJ0IEZyZXkgaGFzIGEgZ29vZCB3YWxrLXRocm91Z2ggdHV0b3JpYWwgb24gWW91VHViZSBvZiBkb2luZyBzbyAoeW91dHViZS5jb20vd2F0Y2g/dj1zd0pyNHUtSFlyMCkuIEZvciB0aGUgcHVycG9zZSBvZiB0aGlzIHByb2plY3QsIEknbSBnb2luZyB0byBiZSB1c2luZyB0aGUgMjAxNS0yMDE5IHJlZ3VsYXIgc2Vhc29ucy4gVGhlcmUgd2lsbCBldmVudHVhbGx5IGJlIGEgbW9kZWwgZm9yIGVhY2ggc2Vhc29uLgpgYGB7cn0Kc2F2YW50MTUgPC0gcmVhZF9jc3YoInNhdmFudDE1LmNzdiIpICU+JSBkcGx5cjo6c2VsZWN0KC1jKCJYMSIpKQpzYXZhbnQxNiA8LSByZWFkX2Nzdigic2F2YW50MTYuY3N2IikgJT4lIGRwbHlyOjpzZWxlY3QoLWMoIlgxIikpCnNhdmFudDE3IDwtIHJlYWRfY3N2KCJzYXZhbnQxNy5jc3YiKSAlPiUgZHBseXI6OnNlbGVjdCgtYygiWDEiKSkKc2F2YW50MTggPC0gcmVhZF9jc3YoInNhdmFudDE4LmNzdiIpICU+JSBkcGx5cjo6c2VsZWN0KC1jKCJYMSIpKQpzYXZhbnQxOSA8LSByZWFkX2Nzdigic2F2YW50MTkuY3N2IikKYGBgCgojIERhdGEgQ2xlYW5pbmcKClNpbmNlIEknbSBqdXN0IGZvY3VzaW5nIG9uIHF1YWxpdHkgb2YgY29udGFjdCwgSSBvbmx5IG5lZWQgYmF0dGVkIGJhbGxzLiAKYGBge3J9CiN1Z2x5IGNvZGUgYi9jIG15IFIgY291bGRuJ3QgaGFuZGxlIGEgZGF0YWZyYW1lIHRoZSBzaXplIG9mIGFsbCBmaXZlIHNlYXNvbnMKYmF0dGVkX2JhbGxfMTUgPC0gc2F2YW50MTUgJT4lCiAgZmlsdGVyKGV2ZW50cyA9PSAiZmllbGRfb3V0IiB8IGV2ZW50cyA9PSAic2luZ2xlIiB8IGV2ZW50cyA9PSAiZG91YmxlIiB8IGV2ZW50cyA9PSAidHJpcGxlIiB8IGV2ZW50cyA9PSAiaG9tZV9ydW4iIHwgZXZlbnRzID09ICJzYWNfZmx5IiB8IAogICAgICAgICAgIGV2ZW50cyA9PSAiZ3JvdW5kZWRfaW50b19kb3VibGVfcGxheSIgfCBldmVudHMgPT0gImRvdWJsZV9wbGF5IiB8IGV2ZW50cyA9PSAic2FjX2ZseV9kb3VibGVfcGxheSIgfCBldmVudHMgPT0gImZpZWxkZXJzX2Nob2ljZSIgfCAKICAgICAgICAgICBldmVudHMgPT0gImZpZWxkZXJzX2Nob2ljZV9vdXQiIHwgZXZlbnRzID09ICJmaWVsZF9lcnJvciIgfCBldmVudHMgPT0gInNhY19idW50IiB8IGV2ZW50cyA9PSAidHJpcGxlX3BsYXkiIHwgZXZlbnRzID09ICJzYWNfYnVudCIgfCAKICAgICAgICAgICBldmVudHMgPT0gImZvcmNlX291dCIgfCBldmVudHMgPT0gInNhY19idW50X2RvdWJsZV9wbGF5IikKcm0oc2F2YW50MTUpCgpiYXR0ZWRfYmFsbF8xNiA8LSBzYXZhbnQxNiAlPiUKICBmaWx0ZXIoZXZlbnRzID09ICJmaWVsZF9vdXQiIHwgZXZlbnRzID09ICJzaW5nbGUiIHwgZXZlbnRzID09ICJkb3VibGUiIHwgZXZlbnRzID09ICJ0cmlwbGUiIHwgZXZlbnRzID09ICJob21lX3J1biIgfCBldmVudHMgPT0gInNhY19mbHkiIHwgCiAgICAgICAgICAgZXZlbnRzID09ICJncm91bmRlZF9pbnRvX2RvdWJsZV9wbGF5IiB8IGV2ZW50cyA9PSAiZG91YmxlX3BsYXkiIHwgZXZlbnRzID09ICJzYWNfZmx5X2RvdWJsZV9wbGF5IiB8IGV2ZW50cyA9PSAiZmllbGRlcnNfY2hvaWNlIiB8IAogICAgICAgICAgIGV2ZW50cyA9PSAiZmllbGRlcnNfY2hvaWNlX291dCIgfCBldmVudHMgPT0gImZpZWxkX2Vycm9yIiB8IGV2ZW50cyA9PSAic2FjX2J1bnQiIHwgZXZlbnRzID09ICJ0cmlwbGVfcGxheSIgfCBldmVudHMgPT0gInNhY19idW50IiB8IAogICAgICAgICAgIGV2ZW50cyA9PSAiZm9yY2Vfb3V0IiB8IGV2ZW50cyA9PSAic2FjX2J1bnRfZG91YmxlX3BsYXkiKQpybShzYXZhbnQxNikKCmJhdHRlZF9iYWxsXzE3IDwtIHNhdmFudDE3ICU+JQogIGZpbHRlcihldmVudHMgPT0gImZpZWxkX291dCIgfCBldmVudHMgPT0gInNpbmdsZSIgfCBldmVudHMgPT0gImRvdWJsZSIgfCBldmVudHMgPT0gInRyaXBsZSIgfCBldmVudHMgPT0gImhvbWVfcnVuIiB8IGV2ZW50cyA9PSAic2FjX2ZseSIgfCAKICAgICAgICAgICBldmVudHMgPT0gImdyb3VuZGVkX2ludG9fZG91YmxlX3BsYXkiIHwgZXZlbnRzID09ICJkb3VibGVfcGxheSIgfCBldmVudHMgPT0gInNhY19mbHlfZG91YmxlX3BsYXkiIHwgZXZlbnRzID09ICJmaWVsZGVyc19jaG9pY2UiIHwgCiAgICAgICAgICAgZXZlbnRzID09ICJmaWVsZGVyc19jaG9pY2Vfb3V0IiB8IGV2ZW50cyA9PSAiZmllbGRfZXJyb3IiIHwgZXZlbnRzID09ICJzYWNfYnVudCIgfCBldmVudHMgPT0gInRyaXBsZV9wbGF5IiB8IGV2ZW50cyA9PSAic2FjX2J1bnQiIHwgCiAgICAgICAgICAgZXZlbnRzID09ICJmb3JjZV9vdXQiIHwgZXZlbnRzID09ICJzYWNfYnVudF9kb3VibGVfcGxheSIpCnJtKHNhdmFudDE3KQoKYmF0dGVkX2JhbGxfMTggPC0gc2F2YW50MTggJT4lCiAgZmlsdGVyKGV2ZW50cyA9PSAiZmllbGRfb3V0IiB8IGV2ZW50cyA9PSAic2luZ2xlIiB8IGV2ZW50cyA9PSAiZG91YmxlIiB8IGV2ZW50cyA9PSAidHJpcGxlIiB8IGV2ZW50cyA9PSAiaG9tZV9ydW4iIHwgZXZlbnRzID09ICJzYWNfZmx5IiB8IAogICAgICAgICAgIGV2ZW50cyA9PSAiZ3JvdW5kZWRfaW50b19kb3VibGVfcGxheSIgfCBldmVudHMgPT0gImRvdWJsZV9wbGF5IiB8IGV2ZW50cyA9PSAic2FjX2ZseV9kb3VibGVfcGxheSIgfCBldmVudHMgPT0gImZpZWxkZXJzX2Nob2ljZSIgfCAKICAgICAgICAgICBldmVudHMgPT0gImZpZWxkZXJzX2Nob2ljZV9vdXQiIHwgZXZlbnRzID09ICJmaWVsZF9lcnJvciIgfCBldmVudHMgPT0gInNhY19idW50IiB8IGV2ZW50cyA9PSAidHJpcGxlX3BsYXkiIHwgZXZlbnRzID09ICJzYWNfYnVudCIgfCAKICAgICAgICAgICBldmVudHMgPT0gImZvcmNlX291dCIgfCBldmVudHMgPT0gInNhY19idW50X2RvdWJsZV9wbGF5IikKcm0oc2F2YW50MTgpCgpiYXR0ZWRfYmFsbF8xOSA8LSBzYXZhbnQxOSAlPiUKICBmaWx0ZXIoZXZlbnRzID09ICJmaWVsZF9vdXQiIHwgZXZlbnRzID09ICJzaW5nbGUiIHwgZXZlbnRzID09ICJkb3VibGUiIHwgZXZlbnRzID09ICJ0cmlwbGUiIHwgZXZlbnRzID09ICJob21lX3J1biIgfCBldmVudHMgPT0gInNhY19mbHkiIHwgCiAgICAgICAgICAgZXZlbnRzID09ICJncm91bmRlZF9pbnRvX2RvdWJsZV9wbGF5IiB8IGV2ZW50cyA9PSAiZG91YmxlX3BsYXkiIHwgZXZlbnRzID09ICJzYWNfZmx5X2RvdWJsZV9wbGF5IiB8IGV2ZW50cyA9PSAiZmllbGRlcnNfY2hvaWNlIiB8IAogICAgICAgICAgIGV2ZW50cyA9PSAiZmllbGRlcnNfY2hvaWNlX291dCIgfCBldmVudHMgPT0gImZpZWxkX2Vycm9yIiB8IGV2ZW50cyA9PSAic2FjX2J1bnQiIHwgZXZlbnRzID09ICJ0cmlwbGVfcGxheSIgfCBldmVudHMgPT0gInNhY19idW50IiB8IAogICAgICAgICAgIGV2ZW50cyA9PSAiZm9yY2Vfb3V0IiB8IGV2ZW50cyA9PSAic2FjX2J1bnRfZG91YmxlX3BsYXkiKQpybShzYXZhbnQxOSkKCmJhdHRlZF9iYWxsX2RhdGEgPC0gcmJpbmQoYmF0dGVkX2JhbGxfMTUsIGJhdHRlZF9iYWxsXzE2LCBiYXR0ZWRfYmFsbF8xNywgYmF0dGVkX2JhbGxfMTgsIGJhdHRlZF9iYWxsXzE5KQpybShiYXR0ZWRfYmFsbF8xNSwgYmF0dGVkX2JhbGxfMTYsIGJhdHRlZF9iYWxsXzE3LCBiYXR0ZWRfYmFsbF8xOCwgYmF0dGVkX2JhbGxfMTkpCmBgYAoKSSdtIGZpcnN0IGdvaW5nIHRvIHJlbW92ZSBhbGwgYmF0dGVkIGJhbGxzIHRoYXQgaGF2ZSBudWxsIHZhbHVlcyBmb3IgZXhpdCB2ZWxvY2l0eSBvciBsYXVuY2ggYW5nbGUuCmBgYHtyfQpiYXR0ZWRfYmFsbF9kYXRhICU8PiUKICBmaWx0ZXIoIWlzLm5hKGxhdW5jaF9zcGVlZCkgfCAhaXMubmEobGF1bmNoX2FuZ2xlKSkKYGBgCgoKKipObyBOdWxscyoqCgpUaGUgbWFqb3JpdHkgb2YgZGF0YSBjbGVhbmluZyBoYXMgdG8gZGVhbCB3aXRoIGZpbmRpbmcsIGFuZCByZW1vdmluZywgJ25vIG51bGxzJyBiYXR0ZWQgYmFsbHMuIEFuZHJldyBQZXJwZXR1YSBsYWlkIHRoaXMgb3V0IGluIGFuIGFydGljbGUgaW4gKlRoZSBIYXJkYmFsbCBUaW1lcyogKGh0dHBzOi8vdGh0LmZhbmdyYXBocy5jb20vNDM0MTYtMi8pLiBCYXNpY2FsbHksIFRyYWNrTWFuIGlzbid0IHBlcmZlY3QuIEl0IGhhcyBhIHRlbmRlbmN5IHRvIG5vdCBiZWluZyBhYmxlIHRvIHRyYWNrIGNlcnRhaW4gYmF0dGVkIGJhbGxzIChtb3N0bHkgZ3JvdW5kIGJhbGxzIGFuZCBwb3B1cHMsIG1vcmUgb24gdGhpcyBsYXRlcikuIE92ZXIgdGhlIGNvdXJzZSBvZiBpdCdzIGZpdmUgc2Vhc29ucyBhcyBNTEJBTSdzIG1haW4gdHJhY2tpbmcgZGV2aWNlLCBUcmFja01hbiBnb3QgYmV0dGVyLCBidXQgc3RpbGwgbWlzc2VkIGFib3V0IDEwJSBvZiBiYXR0ZWQgYmFsbHMuIEFzIFBlcnBldHVhIHNheXMsIHRob3NlIG1pc3NpbmcgYmF0dGVkIGJhbGxzIGhhdmUgdGhlaXIgZXhpdCB2ZWxvY2l0eSBhbmQgbGF1bmNoIGFuZ2xlIGRlY2lkZWQgYnkgYW4gYWxnb3JpdGhtLCBub3QgYSBkaXJlY3QgVHJhY2tNYW4gbWVhc3VyZW1lbnQuIFRoZSBzb2x1dGlvbiB0byB0aG9zZSBtaXNzaW5nIGJhdHRlZCBiYWxscyBhcmUgaW1wdXRpbmcgdGhlIGV4aXQgdmVsb2NpdHkgYW5kIGxhdW5jaCBhbmdsZSBiYXNlZCBvbiB0aGUgYmF0dGVkIGJhbGwgdHlwZSBhbmQgd2hlcmUgdGhlIGJhbGwgd2FzIGZpZWxkZWQgKHJlY29yZGVkIGJ5IGEgc3RyaW5nZXIpLiBQZXJwZXR1YSBjYWxscyB0aGVzZSBiYXR0ZWQgYmFsbHMgJ25vIG51bGxzJyBhbmQgaGUgcmVjb21tZW5kcyByZW1vdmluZyB0aG9zZSBmcm9tIHlvdXIgZGF0YXNldC4KCgpUbyBpbGx1c3RyYXRlIHRoaXMgcHJvY2VzcywgaGVyZSBpcyB0aGUgbGF1bmNoIGFuZ2xlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgb3JpZ2luYWwgZGF0YXNldCAoY29udGFpbmluZyB0aGUgJ25vIG51bGxzJykuCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmdncGxvdChiYXR0ZWRfYmFsbF9kYXRhLCBhZXMoeCA9IGxhdW5jaF9hbmdsZSkpICsgCiAgZ2VvbV9kZW5zaXR5KGNvbG9yID0gInJlZCIpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5kZW5zaXR5Li4pLCBjb2xvciA9ICJyb3lhbGJsdWUiLCBhbHBoYSA9IC4yLCBiaW53aWR0aCA9IDQpICsgCiAgeGxhYigiTGF1bmNoIEFuZ2xlIikgKyB5bGFiKCIiKSArIAogIGdndGl0bGUoIkxhdW5jaCBBbmdsZSBEaXN0cmlidXRpb24iKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpKQpgYGAKCldlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgdGhyZWUgbG9jYWwgcGVha3MgdGhhdCBub3JtYWxseSB3b3VsZG4ndCBiZSBleHBlY3RlZCBmcm9tIGEgY29udGlub3VzIHZhcmlhYmxlIGluIGEgZGF0c2V0IG9mIHRoaXMgc2l6ZS4gWW91IG1heSBhc2sgd2h5IGRvZXMgdGhlIGRpc3RyaWJ1dGlvbiBsb29rIGxpa2UgdGhpcy4gVGhlIGFuc3dlciBpcyB0aGUgJ25vIG51bGxzJyBiYXR0ZWQgYmFsbHMuCgoKSGVyZSBpcyBldmVyeSBjb21iaW5hdGlvbiBvZiBleGl0IHZlbG9jaXR5IGFuZCBsYXVuY2ggYW5nbGUgZnJvbSAyMDE1LTIwMTkuIFRoZXJlIGFyZSAzMDYsMzAxIHVuaXF1ZSBjb21iaW5hdGlvbnMgb2YgZXhpdCB2ZWxvY2l0eSBhbmQgbGF1bmNoIGFuZ2xlLCBmcm9tIGEgc2FtcGxlIG9mIDYyOSw5MDUgYmF0dGVkIGJhbGxzLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmV2X2xhX2NvbWJvcyA8LSBiYXR0ZWRfYmFsbF9kYXRhICU+JQogIGdyb3VwX2J5KGxhdW5jaF9zcGVlZCwgbGF1bmNoX2FuZ2xlKSAlPiUKICBzdW1tYXJpc2UobnVtID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtKSkKZXZfbGFfY29tYm9zCmBgYAoKCk91dCBvZiB0aG9zZSAzMDYsMzAxIGNvbWJpbmF0aW9ucywgMTcxLDYwMSAoNTYuMDIlKSBvZiB0aGUgY29tYmluYXRpb25zIG9ubHkgaGF2ZSBvbmUgb2JzZXJ2YXRpb24uIFNvIHdlIGNhbiBzZWUgdGhhdCBhIG1ham9yaXR5IG9mIGJhdHRlZCBiYWxscyBhcmUgYXQgdW5pcXVlIGV4aXQgdmVsb2NpdGllcyBhbmQgbGF1bmNoIGFuZ2xlcy4gRnJvbSAyMDE1LTIwMTkgdGhlcmUgd2VyZSAxLDc2MiB1bmlxdWUgbGF1bmNoIGFuZ2xlcyBhbmQgMSwwNDMgdW5pcXVlIGV4aXQgdmVsb2NpdGllcy4gRnJvbSBhIHByb2JhYmlsaXRpeSBwb2ludCBvZiB2aWV3LCB0aGUgbGlrZWxpaG9vZCBvZiB0d28gYmF0dGVkIGJhbGxzIGhhdmluZyB0aGUgZXhhY3Qgc2FtZSBleGl0IHZlbG9jaXR5IGFuZCBsYXVuY2ggYW5nbGUgaXMgcHJldHR5IHVubGlrZWx5LgoKRml2ZSBpcyB0aGUgbWFnaWMgbnVtYmVyIHRoYXQgUGVycGV0dWEgY2hvc2UgaW4gaGlzIGFydGljbGUuIEZpdmUgb3IgbW9yZSBvYnNlcnZhdGlvbnMgb2YgZXhpdCB2ZWxvY2l0eSBhbmQgbGF1bmNoIGFuZ2xlIGNvbWJpbmF0aW9uIGhlIGRlZW1lZCB0byBiZSAnbm8gbnVsbHMuJyBJJ20gZm9sbG93aW5nIGEgc2ltaWxhciBwcm9jZXNzIGFzIDMwMCwwOTUgKDk3Ljk3JSkgb2YgdGhlIGNvbWJpbmF0aW9ucyBoYXZlIGZpdmUgb3IgbGVzcyBvYnNlcnZhdGlvbnMuIFNvLCBJJ20gZ29pbmcgdG8gcmVtb3ZlIGFsbCBvYnNlcnZhdGlvbnMgb2YgbGF1bmNoIGFuZ2xlIGFuZCBleGl0IHZlbG9jaXR5IHRoYXQgb2NjdXIgKiptb3JlKiogdGhhbiBmaXZlIHRpbWVzLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmV2X2xhX2JiLnR5cGVfY29tYm9zIDwtIGJhdHRlZF9iYWxsX2RhdGEgJT4lCiAgZ3JvdXBfYnkobGF1bmNoX3NwZWVkLCBsYXVuY2hfYW5nbGUsIGJiX3R5cGUpICU+JQogIHN1bW1hcmlzZShudW0gPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhudW0pKQpldl9sYV9iYi50eXBlX2NvbWJvcwpgYGAKCipQb3B1cHMqCmBgYHtyfQpwb3B1cF9jb21ib3NfdG9fcmVtb3ZlIDwtIGV2X2xhX2JiLnR5cGVfY29tYm9zICU+JQogIGZpbHRlcihiYl90eXBlID09ICJwb3B1cCIpICU+JQogIGFycmFuZ2UoZGVzYyhudW0pKSAlPiUKICBmaWx0ZXIobnVtID4gNSkKCnBvcHVwX2NvbWJvc190b19yZW1vdmUKYGBgCk9ubHkgc2l4IHBvcHVwIGNvbWJpbmF0aW9ucyBvY2N1ciBtb3JlIHRoYW4gZml2ZSB0aW1lcy4gR29pbmcgYmFjayB0byB0aGUgbGF1bmNoIGFuZ2xlIGRpc3RyaWJ1dGlvbiBlYXJsaWVyLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIGRyaXZpbmcgZm9yY2Ugb2YgdGhhdCBodWdlIHNwaWtlIG9uIHRoZSByaWdodCBzaWRlIG9mIHRoZSBjZW50ZXIgaXMgdGhlIDIwLDAwMCBwb3B1cHMgdGhhdCB3ZXJlIGltcHV0ZWQgdG8gYmUgaGl0IGF0IDgwIG1waCBhbmQgNjkgZGVncmVlcy4gCgoqRmx5IEJhbGxzKgpgYGB7cn0KZmx5YmFsbF9jb21ib3NfdG9fcmVtb3ZlIDwtIGV2X2xhX2JiLnR5cGVfY29tYm9zICU+JQogIGZpbHRlcihiYl90eXBlID09ICJmbHlfYmFsbCIpICU+JQogIGFycmFuZ2UoZGVzYyhudW0pKSAlPiUKICBmaWx0ZXIobnVtID4gNSkKCmZseWJhbGxfY29tYm9zX3RvX3JlbW92ZQpgYGAKKkxpbmUgRHJpdmVzKgpgYGB7cn0KbGluZWRyaXZlX2NvbWJvc190b19yZW1vdmUgPC0gZXZfbGFfYmIudHlwZV9jb21ib3MgJT4lCiAgZmlsdGVyKGJiX3R5cGUgPT0gImxpbmVfZHJpdmUiKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtKSkgJT4lCiAgZmlsdGVyKG51bSA+IDUpCgpsaW5lZHJpdmVfY29tYm9zX3RvX3JlbW92ZQpgYGAKCipHcm91bmQgQmFsbHMqCmBgYHtyfQpnYl9jb21ib3NfdG9fcmVtb3ZlIDwtIGV2X2xhX2JiLnR5cGVfY29tYm9zICU+JQogIGZpbHRlcihiYl90eXBlID09ICJncm91bmRfYmFsbCIpICU+JQogIGFycmFuZ2UoZGVzYyhudW0pKSAgJT4lCiAgZmlsdGVyKG51bSA+IDUpCgpnYl9jb21ib3NfdG9fcmVtb3ZlCmBgYApBbmQgaGVyZSwgd2UgY2FuIHNlZSB3aHkgdGhlcmUgaXMgYSBodWdlIHNwaWtlIGF0IGFyb3VuZCAtMjUgZGVncmVlcy4KCmBgYHtyfQp0b19iZV9yZW1vdmVkIDwtIHJiaW5kKHBvcHVwX2NvbWJvc190b19yZW1vdmUsIGZseWJhbGxfY29tYm9zX3RvX3JlbW92ZSwgbGluZWRyaXZlX2NvbWJvc190b19yZW1vdmUsIGdiX2NvbWJvc190b19yZW1vdmUpCnJtKHBvcHVwX2NvbWJvc190b19yZW1vdmUsIGZseWJhbGxfY29tYm9zX3RvX3JlbW92ZSwgbGluZWRyaXZlX2NvbWJvc190b19yZW1vdmUsIGdiX2NvbWJvc190b19yZW1vdmUpCnRvX2JlX3JlbW92ZWQKYGBgCgpBIHZhc3QgbWFqb3JpdHkgKH43MiUpIG9mIGJhdHRlZCBiYWxscyB0byBiZSByZW1vdmVkIGFyZSBlaXRoZXIgcG9wdXBzIG9yIGdyb3VuZCBiYWxscy4gVGhpcyBjb25maXJtcyB3aGF0IFBlcnBldHVhIG1lbnRpb25lZCBpbiBoaXMgYXJ0aWNsZSwgIi4uLiBub3Qgb25seSBkb2VzIFRyYWNrTWFuIGhhdmUgYSBiaWFzIGFnYWluc3QgY2VydGFpbiB0eXBlcyBvZiBiYXR0ZWQgYmFsbHMsIGJ1dCB0aGVzZSBiYXR0ZWQgYmFsbHMgYXJlIGdlbmVyYWxseSB2ZXJ5IHdlYWtseSBoaXQgZ3JvdW5kIGJhbGxzIGFuZCBwb3AtdXBzLiIKCkluIHRvdGFsLCBJJ20gZ29pbmcgdG8gcmVtb3ZlIDk4LDc0NiBiYXR0ZWQgYmFsbHMgZnJvbSB0aGUgZGF0YXNldC4gVGhpcyByZXByZXNlbnRzIGFib3V0IDE1JSBvZiB0aGUgb3JpZ2luYWwgZGF0YXNldC4gSSBoYXZlIG5vIHByb2JsZW0gd2l0aCBwZW9wbGUgd2hvIHdvdWxkIGFyZ3VlIHRoYXQgdGhpcyByZW1vdmFsIGNyaXRlcmlhIGlzIHRvbyBzdHJpY3Qgb3IgaXMgdG9vIHVuZXhhY3QuIEhvd2V2ZXIsIHdoZW4gd29ya2luZyB3aXRoIHVucGVyZmVjdCBkYXRhIChsaWtlIHdlIGFyZSksIHNvbWV0aW1lcyBpdCdzIGJldHRlciB0byBoYXZlIGFuIG92ZXJhcmNoaW5nIHBoaWxvc3BoeSByYXRoZXIgdGhhbiB0cnkgdG8gbml0LXBpY2sgYWJvdXQgaW5mb3JtYXRpb24gdGhhdCB3ZSBkb24ndCBoYXZlLgoKCmBgYHtyfQojIHJlbW92aW5nICdubyBudWxscycKYmF0dGVkX2JhbGxfY2xlYW5lZCA8LSBkcGx5cjo6YW50aV9qb2luKGJhdHRlZF9iYWxsX2RhdGEsIHRvX2JlX3JlbW92ZWQsIGJ5ID0gYygibGF1bmNoX3NwZWVkIiwgImxhdW5jaF9hbmdsZSIsICJiYl90eXBlIikpCmBgYAoKCk5vdywgbGV0J3MgbG9vayBhdCB0aGUgbGF1bmNoIGFuZ2xlIGRpc3RyaWJ1dGlvbiBhZnRlciByZW1vdmluZyB0aGUgJ25vIG51bGxzJyBpbXB1dGVkIGJhdHRlZCBiYWxscy4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZ2dwbG90KGJhdHRlZF9iYWxsX2NsZWFuZWQsIGFlcyh4ID0gbGF1bmNoX2FuZ2xlKSkgKyAKICBnZW9tX2RlbnNpdHkoY29sb3IgPSAicmVkIikgKyBnZW9tX2hpc3RvZ3JhbShhZXMoeT0uLmRlbnNpdHkuLiksIGNvbG9yID0gInJveWFsYmx1ZSIsIGFscGhhID0gLjIsIGJpbndpZHRoID0gNCkgKyAKICB4bGFiKCJMYXVuY2ggQW5nbGUiKSArIHlsYWIoIiIpICsgbGFicyhzdWJ0aXRsZSA9ICJBZnRlciByZW1vdmluZyAnbm8gbnVsbHMnIikgKwogIGdndGl0bGUoIkxhdW5jaCBBbmdsZSBEaXN0cmlidXRpb24gKDIwMTUtMjAxOSkiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpKQpgYGAKTXVjaCBiZXR0ZXIhIEJ1dCBub3QgZXhhY3RseSB0aGUgc2hhcGUgSSB3YXMgZXhwZWN0aW5nLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmJlZm9yZV9jbGVhbmluZyA8LSBnZ3Bsb3QoYmF0dGVkX2JhbGxfZGF0YSwgYWVzKHggPSBsYXVuY2hfYW5nbGUpKSArIAogIGdlb21fZGVuc2l0eShjb2xvciA9ICJyZWQiKSArIGdlb21faGlzdG9ncmFtKGFlcyh5PS4uZGVuc2l0eS4uKSwgY29sb3IgPSAicm95YWxibHVlIiwgYWxwaGEgPSAuMiwgYmlud2lkdGggPSA0KSArIAogIHhsYWIoIkxhdW5jaCBBbmdsZSIpICsgeWxhYigiIikgKyBsYWJzKHN1YnRpdGxlID0gIkJlZm9yZSByZW1vdmluZyAnbm8gbnVsbHMnIikgKwogIGdndGl0bGUoIkxhdW5jaCBBbmdsZSBEaXN0cmlidXRpb24gKDIwMTUtMjAxOSkiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpKQphZnRlcl9jbGVhbmluZyA8LSBnZ3Bsb3QoYmF0dGVkX2JhbGxfY2xlYW5lZCwgYWVzKHggPSBsYXVuY2hfYW5nbGUpKSArIAogIGdlb21fZGVuc2l0eShjb2xvciA9ICJyZWQiKSArIGdlb21faGlzdG9ncmFtKGFlcyh5PS4uZGVuc2l0eS4uKSwgY29sb3IgPSAicm95YWxibHVlIiwgYWxwaGEgPSAuMiwgYmlud2lkdGggPSA0KSArIAogIHhsYWIoIkxhdW5jaCBBbmdsZSIpICsgeWxhYigiIikgKyBsYWJzKHN1YnRpdGxlID0gIkFmdGVyIHJlbW92aW5nICdubyBudWxscyciKSArCiAgZ2d0aXRsZSgiTGF1bmNoIEFuZ2xlIERpc3RyaWJ1dGlvbiAoMjAxNS0yMDE5KSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZShheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCkpCgpsYV9jb21wYXJpc29uIDwtIGJlZm9yZV9jbGVhbmluZyArIGFmdGVyX2NsZWFuaW5nCnJtKGJlZm9yZV9jbGVhbmluZywgYWZ0ZXJfY2xlYW5pbmcpCmxhX2NvbXBhcmlzb24KYGBgCgpIZXJlIGlzIGFub3RoZXIgd2F5IG9mIHZpZXdpbmcgdGhlIGRpc3RyaWJ1dGlvbiBjaGFuZ2VzLiBXZSBjYW4gc2VlIHRoYXQgdGhlIHRocmVlIGxvY2FsIHBlYWtzIGFyZSByZW1vdmVkIGFuZCB0aGF0IHRoZSBkaXN0cmlidXRpb24gaXMgbW9yZSBjZW50ZXJlZC4KYGBge3IsIHdhcm5pbmc9RkFMU0V9CmdncGxvdChkYXRhID0gYmF0dGVkX2JhbGxfZGF0YSwgYWVzKHg9bGF1bmNoX2FuZ2xlKSkgKyAKICBnZW9tX2RlbnNpdHkoY29sb3IgPSAicmVkIikgKyAKICBnZW9tX2RlbnNpdHkoZGF0YSA9IGJhdHRlZF9iYWxsX2NsZWFuZWQsIGNvbG9yID0gImJsdWUiKSArIAogIHhsYWIoIkxhdW5jaCBBbmdsZSIpICsgeWxhYigiIikgKyAKICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgTGF1bmNoIEFuZ2xlIikgKwogIGxhYnMoc3VidGl0bGUgPSAiUmVkOiBPcmlnaW5hbCBkYXRhc2V0XG5CbHVlOiBDbGVhbmVkIGRhdGFzZXQiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpKQpgYGAKCgpBbiBhc3R1dGUgcmVhZGVyIG1pZ2h0IGhhdmUgbm90aWNlZCB0aGF0IGEgdmFzdCBtYWpvcml0eSBvZiB0aGUgcmVtb3ZlZCBiYXR0ZWQgYmFsbHMgYXJlIHBvb3JseSBoaXQuIFRoZXJlZm9yZSwgYnkgcmVtb3ZpbmcgdGhlc2UsIGdlbmVyYWxseSwgd2Vha2VyIGhpdCBiYWxscyB0aGVuIHRoZSBjcmVhdGlvbiBvZiBhIHF1YWxpdHkgb2YgY29udGFjdCBtZXRyaWMgd2lsbCBmYXZvciB0aGUgYmF0dGVycyB3aG8gaGFkIG1vcmUgd2Vha2x5IGhpdCBiYWxscyB0aGFuIHRob3NlIHdobyBkaWRuJ3QuIEJ5IHJlbW92aW5nIGEgcGxheWVyJ3Mgd29yc2UgaGl0IGJhbGxzIHRoZXkgbG9vayBhIGxvdCBiZXR0ZXIuIEFuZCB0aGF0IGlzIHRydWUuIEhvd2V2ZXIsIHRoZSBiaWFzIEkndmUgaW50cm9kdWNlZCBpcyBhIG5lY2Vzc2FyeSBldmlsLiBXZSBoYXZlIHRvIHdvcmsgd2l0aCBhbmQgYWNjZXB0IGl0LiBJdCdzIGJldHRlciB0byB3b3JrIHdpdGggYWNjdXJhdGUgKGkuZS4gbWVhc3N1cmVkKSBkYXRhIHRoYW4gbm90LiBJdCdzIGltcG9ydGFudCB0byBrZWVwIHRoYXQgaW4gbWluZCB3aGVuIGFuYWx5emluZyB0aGUgcmVzdWx0cyBvZiB0aGUgbWV0cmljLCBidXQgSSBiZWxpZXZlIHRoaXMgaXMgdGhlIGNvcnJlY3Qgd2F5IG9mIGhhbmRsaW5nIHRoaXMgYW5hbHlzaXMuCgpIZXJlJ3MgYSBxdWljayBsaXR0bGUgdmlzdWFsaXphdGlvbiBzaG93aW5nIHRoZSB0eXBlIG9mIGJpYXMgaW5mbGljdGVkLiBSZW1vdmluZyB0aGUgJ25vIG51bGxzJyBoZWxwcyBBbGV4aSBBbWFyaXN0YSBhbmQgSi5KLiBIYXJkeSBhIGxvdCBtb3JlIHRoYW4gQWFyb24gSnVkZ2UgYW5kIEZyYW5taWwgUmV5ZXMuIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0b19iZV9yZW1vdmVkICU8PiUKICBtdXRhdGUoaXNfbm9fbnVsbCA9IDEpCgpqb2luZWQgPC0gYmF0dGVkX2JhbGxfZGF0YSAlPiUKICBsZWZ0X2pvaW4odG9fYmVfcmVtb3ZlZCwKICAgICAgICAgICAgYnkgPSBjKCJsYXVuY2hfc3BlZWQiLCAibGF1bmNoX2FuZ2xlIiwgImJiX3R5cGUiKSkgJT4lCiAgbXV0YXRlKGlzX25vX251bGwgPSBjYXNlX3doZW4oaXNfbm9fbnVsbCA9PSAxIH4gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gMCkpCmpvaW5lZCAlPiUKICBncm91cF9ieShwbGF5ZXJfbmFtZSkgJT4lCiAgc3VtbWFyaXNlKG51bV9iYnMgPSBuKCksCiAgICAgICAgICAgIG51bV9ub19udWxscyA9IHN1bShpc19ub19udWxsKSwKICAgICAgICAgICAgcGN0X25vX251bGxzID0gMTAwKihudW1fbm9fbnVsbHMgLyBudW1fYmJzKSwKICAgICAgICAgICAgeHdPQkFDT04gPSBtZWFuKGVzdGltYXRlZF93b2JhX3VzaW5nX3NwZWVkYW5nbGUsbmEucm09VCksCiAgICAgICAgICAgIHdPQkFDT04gPSBtZWFuKHdvYmFfdmFsdWUgLyB3b2JhX2Rlbm9tLCBuYS5ybT1UKSkgJT4lCiAgZmlsdGVyKG51bV9iYnMgPiA1MDApICU+JQogIGdncGxvdChhZXMoeCA9IHBjdF9ub19udWxscywgeSA9IHdPQkFDT04pKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKCkgKyAKICBsYWJzKHggPSAiUGVyY2VudGFnZSBvZiAnTm8gTnVsbHMnIEhpdCIsIAogICAgICAgeSA9ICJ4d09CQUNPTiIsIAogICAgICAgc3VidGl0bGUgPSAiQmF0dGVycyB3aXRoID4gNTAwIGJhdHRlZCBiYWxscyBmcm9tIDIwMTUtMjAxOSIsIAogICAgICAgdGl0bGUgPSAiUGVyY2VudGFnZSBvZiAnTm8gTnVsbHMnIEhpdCB2cy4gRXhwZWN0ZWQgV2VpZ2h0ZWQgT24tQmFzZSBBdmVyYWdlIG9uIENvbnRhY3QiKSArIAogIHRoZW1lX2J3KCkgKyAKICBnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsID0gcGxheWVyX25hbWUpLCBzZWdtZW50LnNpemUgPSAwLjIpCmBgYAoKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CndyaXRlLmNzdihiYXR0ZWRfYmFsbF9jbGVhbmVkLCAiYmF0dGVkX2JhbGxfY2xlYW5lZF92Mi5jc3YiKQpgYGA=