This R Notebook is the complement to my blog post Analyzing IMDb Data The Intended Way, with R and ggplot2.
This notebook is licensed under the MIT License. If you use the code or data visualization designs contained within this notebook, it would be greatly appreciated if proper attribution is given back to this notebook and/or myself. Thanks! :)
IMDb data retrieved on July 4th 2018.
Information courtesy of IMDb (http://www.imdb.com). Used with permission.
[30m── [1mAttaching packages[22m ───────────────────────────────────── tidyverse 1.2.1 ──[39m
[30m[32m✔[30m [34mggplot2[30m 3.0.0 [32m✔[30m [34mpurrr [30m 0.2.5
[32m✔[30m [34mtibble [30m 1.4.2 [32m✔[30m [34mdplyr [30m 0.7.6
[32m✔[30m [34mtidyr [30m 0.8.1 [32m✔[30m [34mstringr[30m 1.3.1
[32m✔[30m [34mreadr [30m 1.1.1 [32m✔[30m [34mforcats[30m 0.3.0[39m
package ‘dplyr’ was built under R version 3.5.1[30m── [1mConflicts[22m ──────────────────────────────────────── tidyverse_conflicts() ──
[31m✖[30m [34mdplyr[30m::[32mfilter()[30m masks [34mstats[30m::filter()
[31m✖[30m [34mdplyr[30m::[32mlag()[30m masks [34mstats[30m::lag()[39m
library(ggridges) # unused in final blog post
Attaching package: ‘ggridges’
The following object is masked from ‘package:ggplot2’:
scale_discrete_manual
library(tidytext) # unused in final blog post
library(scales)
Attaching package: ‘scales’
The following object is masked from ‘package:purrr’:
discard
The following object is masked from ‘package:readr’:
col_factor
R version 3.5.0 (2018-04-23)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.5
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] scales_0.5.0 tidytext_0.1.9 ggridges_0.5.0 forcats_0.3.0
[5] stringr_1.3.1 dplyr_0.7.6 purrr_0.2.5 readr_1.1.1
[9] tidyr_0.8.1 tibble_1.4.2 ggplot2_3.0.0 tidyverse_1.2.1
loaded via a namespace (and not attached):
[1] tidyselect_0.2.4 reshape2_1.4.3 haven_1.1.2 lattice_0.20-35
[5] colorspace_1.3-2 SnowballC_0.5.1 yaml_2.1.19 rlang_0.2.1
[9] pillar_1.2.3 foreign_0.8-70 glue_1.2.0 withr_2.1.2
[13] modelr_0.1.2 readxl_1.1.0 bindrcpp_0.2.2 bindr_0.1.1
[17] plyr_1.8.4 munsell_0.5.0 gtable_0.2.0 cellranger_1.1.0
[21] rvest_0.3.2 psych_1.8.4 knitr_1.20 parallel_3.5.0
[25] broom_0.4.5 tokenizers_0.2.1 Rcpp_0.12.17 jsonlite_1.5
[29] mnormt_1.5-5 hms_0.4.2 stringi_1.2.3 grid_3.5.0
[33] cli_1.0.0 tools_3.5.0 magrittr_1.5 lazyeval_0.2.1
[37] janeaustenr_0.1.5 crayon_1.3.4 pkgconfig_2.0.1 Matrix_1.2-14
[41] xml2_1.2.0 lubridate_1.7.4 assertthat_0.2.0 httr_1.3.1
[45] rstudioapi_0.7 R6_2.2.2 nlme_3.1-137 compiler_3.5.0
Helper function to read IMDB files given filename.
read_imdb <- function(data_path) {
path <- "/Volumes/Extreme 510/Data/imdb/"
read_tsv(paste0(path, data_path), na = "\\N", quote='', progress=F)
}
Helper function to pretty print the size of a dataframe for charts/notebook.
ppdf <- function(df) {
df %>% nrow() %>% comma()
}
Ratings
df_ratings <- read_imdb("title.ratings.tsv")
Parsed with column specification:
cols(
tconst = col_character(),
averageRating = col_double(),
numVotes = col_integer()
)
There are 847,394 ratings in the dataset.
Plot every point. (note: very slow!)
plot <- ggplot(df_ratings, aes(x = numVotes, y = averageRating)) +
geom_point()
ggsave("imdb-0.png", plot, width=4, height=3)
Plot a 2D histogram and clean up axes.
plot <- ggplot(df_ratings, aes(x = numVotes, y = averageRating)) +
geom_bin2d() +
scale_x_log10(labels = comma) +
scale_y_continuous(breaks = 1:10) +
scale_fill_viridis_c(labels = comma)
ggsave("imdb-1.png", plot, width=4, height=3)
Title Basics
df_basics <- read_imdb("title.basics.tsv")
Parsed with column specification:
cols(
tconst = col_character(),
titleType = col_character(),
primaryTitle = col_character(),
originalTitle = col_character(),
isAdult = col_integer(),
startYear = col_integer(),
endYear = col_character(),
runtimeMinutes = col_integer(),
genres = col_character()
)
There are 5,114,944 titles in the dataset.
Merge df_ratings
and df_basics
to perform ratings/vote analysis using more metadata.
df_ratings <- df_ratings %>% left_join(df_basics)
Joining, by = "tconst"
plot <- ggplot(df_ratings, aes(x = runtimeMinutes, y = averageRating)) +
geom_bin2d() +
scale_x_continuous(labels = comma) +
scale_y_continuous(breaks = 1:10) +
scale_fill_viridis_c()
ggsave("imdb-2a.png", plot, width=4, height=3)
Which movies have the superhigh runtimes?
df_ratings %>% arrange(desc(runtimeMinutes)) %>% head(10)
plot <- ggplot(df_ratings %>% filter(runtimeMinutes < 180, titleType=="movie", numVotes >= 10), aes(x = runtimeMinutes, y = averageRating)) +
geom_bin2d() +
scale_x_continuous(breaks = seq(0, 180, 60), labels = 0:3) +
scale_y_continuous(breaks = 0:10) +
scale_fill_viridis_c(option = "inferno", labels = comma) +
theme_minimal(base_family = "Source Sans Pro", base_size=8) +
labs(title="Relationship between Movie Runtime and Average Movie Rating",
subtitle="Data from IMDb retrieved July 4th, 2018",
x="Runtime (Hours)",
y="Average User Rating",
caption="Max Woolf — minimaxir.com",
fill="# Movies")
ggsave("imdb-2b.png", plot, width=4, height=3)
## Unnesting Genres
How to facet by genre; this was removed from the post since a little complicated.
NB: you cannot use default tokenization on unnest_tokens
since some tokens have dashes. (e.g. film-noir
)
df_ratings_unnest <- df_ratings %>%
filter(runtimeMinutes < 180, titleType=="movie", numVotes >= 10) %>%
select(runtimeMinutes, averageRating, genres) %>%
unnest_tokens(genre, genres, token = str_split, pattern = ",")
df_ratings_unnest %>% head(10)
plot <- ggplot(df_ratings_unnest, aes(x = runtimeMinutes, y = averageRating)) +
geom_bin2d() +
scale_x_continuous(breaks = seq(0, 180, 60), labels = 0:3) +
scale_y_continuous(breaks = 1:10) +
scale_fill_viridis_c(option = "inferno", labels = comma) +
theme_minimal(base_family = "Source Sans Pro", base_size=9) +
labs(title="Relationship between Movie Runtime and Average Mobie Rating",
subtitle="Data from IMDb retrieved July 4th, 2018",
x="Runtime (Hours)",
y="Average User Rating",
caption="Max Woolf — minimaxir.com",
fill="# Movies") +
facet_wrap(~ genre)
ggsave("imdb-3.png", plot, width=6, height=6)
Normalize by facet. There are two approaches:
- Do a weighted sum of the points in the spatial area, where the weight is the reciprocol of the # of points in the facet.
- Manually calculate bins/counts and scale to
[0, 1]
Option 1 is a bit easier to implement. Additionally, remove facets with little data.
squish
trick via https://stackoverflow.com/a/23655697/9314418
df_temp <- df_ratings_unnest %>%
filter(!(genre %in% c("game-show", "reality-tv", "short", "talk-show", NA))) %>%
group_by(genre) %>%
mutate(prop = 1/n())
plot <- ggplot(df_temp, aes(x = runtimeMinutes, y = averageRating, z=prop)) +
stat_summary_2d(fun=sum) +
scale_x_continuous(breaks = seq(0, 180, 60), labels = 0:3) +
scale_y_continuous(breaks = 1:10) +
scale_fill_viridis_c(option = "inferno", labels = comma, limits=c(0, 0.02), oob=squish, guide=F) +
theme_minimal(base_family = "Source Sans Pro", base_size=9) +
labs(title="Relationship between Movie Runtime and Average Mobie Rating",
subtitle="Data from IMDb retrieved July 4th, 2018",
x="Runtime (Hours)",
y="Average User Rating",
caption="Max Woolf — minimaxir.com") +
facet_wrap(~ genre)
ggsave("imdb-3b.png", plot, width=6, height=6)
Rating vs. Movie Year
Set theme to custom theme based on theme_minimal
for the rest of the notebook.
theme_set(theme_minimal(base_size=9, base_family="Source Sans Pro") +
theme(plot.title = element_text(size=8, family="Source Sans Pro Bold", margin=margin(t = -0.1, b = 0.1, unit='cm')),
axis.title.x = element_text(size=8),
axis.title.y = element_text(size=8),
plot.subtitle = element_text(family="Source Sans Pro Semibold", color="#969696", size=6),
plot.caption = element_text(size=6, color="#969696"),
legend.title = element_text(size=8),
legend.key.width = unit(0.25, unit='cm')))
plot <- ggplot(df_ratings %>% filter(titleType=="movie", numVotes >= 10), aes(x = startYear, y = averageRating)) +
geom_bin2d() +
geom_smooth(color="black") +
scale_x_continuous() +
scale_y_continuous(breaks = 1:10) +
scale_fill_viridis_c(option = "plasma", labels = comma, trans='log10') +
labs(title="Relationship between Movie Release Year and Average Rating",
subtitle=sprintf("For %s Movies/Ratings. Data from IMDb retrieved 7/4/2018", df_ratings %>% filter(titleType=="movie", numVotes >= 10) %>% ppdf),
x="Year Movie was Released",
y="Average User Rating For Movie",
caption="Max Woolf — minimaxir.com",
fill="# Movies")
ggsave("imdb-4.png", plot, width=4, height=3)
Work with Ridge plots; not included in post because it doesn’t offer much insight different from the chart above.
NB: For ridge plots, the y-axis must be a factor
, not a numeric
; this is what tripped me up in the stream.
plot <- ggplot(df_ratings %>% filter(startYear >= 2000, titleType=="movie", numVotes >= 10) %>% mutate(startYear = factor(startYear)), aes(x = averageRating, y = startYear, fill=startYear)) +
geom_density_ridges() +
scale_fill_hue(guide=F) +
scale_x_continuous(breaks = 1:10) +
theme_minimal(base_family = "Source Sans Pro", base_size=9)
ggsave("imdb-5.png", plot, width=4, height=3)
Bucket by decades.
df_ratings_decades <- df_ratings %>%
filter(startYear>=1950, titleType=="movie", numVotes >= 10) %>%
mutate(decade = fct_rev(factor(cut_width(startYear, 10, boundary=0), labels = paste0(seq(1950, 2010, 10), "s"))))
df_ratings_decades %>% head()
plot <- ggplot(df_ratings_decades, aes(x = averageRating, y = decade, fill=0.5 - abs(0.5-..ecdf..))) +
geom_density_ridges_gradient(calc_ecdf=T, quantile_lines=T) +
scale_fill_viridis_c(option = "plasma", guide=F) +
scale_x_continuous(breaks = 1:10) +
#scale_y_discrete(expand = c(0,01, 0)) +
theme_minimal(base_family = "Source Sans Pro", base_size=9)
ggsave("imdb-6.png", plot, width=4, height=3)
plot <- ggplot(df_ratings_decades, aes(x = runtimeMinutes, y = decade, fill=0.5 - abs(0.5-..ecdf..))) +
geom_density_ridges_gradient(calc_ecdf=T, quantile_lines=T) +
scale_fill_viridis_c(option = "plasma", guide=F) +
scale_x_continuous(breaks = seq(0, 180, 60), limits=c(0,180), labels = 0:3) +
theme_minimal(base_family = "Source Sans Pro", base_size=9)
ggsave("imdb-7.png", plot, width=4, height=3)
Episode Analysis
For reference; not included in post. (Too much bad data to clean.)
df_episode <- read_imdb("title.episode.tsv") %>% filter(!is.na(seasonNumber))
Parsed with column specification:
cols(
tconst = col_character(),
parentTconst = col_character(),
seasonNumber = col_integer(),
episodeNumber = col_integer()
)
There are 2,664,673 episodes in the dataset.
df_episode_count <- df_episode %>%
group_by(parentTconst, seasonNumber) %>%
tally() %>%
left_join(df_basics, c("parentTconst" = "tconst"))
df_episode_count %>% head()
Putting It All Together
Merge actor information onto the full ratings dataframe.
df_ratings <- df_ratings %>% left_join(df_principals)
Joining, by = "tconst"
Filter down to movies w/ actor info. (only if the birth year is present in the data)
df_ratings_movies <- df_ratings %>%
filter(titleType == "movie", !is.na(birthYear), numVotes >= 10) %>%
mutate(age_lead = startYear - birthYear) %>%
arrange(desc(numVotes))
df_ratings_movies %>% head(100)
Aggregate lead-actor/actress ages by movie year w/ percentiles.
df_actor_ages <- df_ratings_movies %>%
group_by(startYear) %>%
summarize(low_age = quantile(age_lead, 0.25, na.rm=T),
med_age = quantile(age_lead, 0.50, na.rm=T),
high_age = quantile(age_lead, 0.75, na.rm=T)) %>%
arrange(startYear)
df_actor_ages %>% head()
Create a ribbon plot.
NB: Plot the ribbon before the line, so the line is on top.
plot <- ggplot(df_actor_ages %>% filter(startYear >= 1920) , aes(x = startYear)) +
geom_ribbon(aes(ymin=low_age, ymax=high_age), alpha=0.2) +
geom_line(aes(y=med_age)) +
labs(title="Change in Ages of Movie Lead Actors/Actress Over Time",
subtitle=sprintf("For %s Actors. Line represents median age.\nRibbon bounds represent 25th — 75th Percentiles. Data from IMDb retrieved 7/4/2018",df_ratings_movies %>% filter(startYear >= 1920) %>% ppdf()),
x="Year Movie was Released",
y="Age of Lead Actor/Actress",
caption="Max Woolf — minimaxir.com",
fill="# Movies")
ggsave("imdb-8.png", plot, width=4, height=3)
Create a plot comparing actors/actresses. Same code, except adding an aggregation and aestetic on category
.
df_actor_ages_lead <- df_ratings_movies %>%
group_by(startYear, category) %>%
summarize(low_age = quantile(age_lead, 0.25, na.rm=T),
med_age = quantile(age_lead, 0.50, na.rm=T),
high_age = quantile(age_lead, 0.75, na.rm=T)) %>%
arrange(startYear)
df_actor_ages_lead %>% head()
plot <- ggplot(df_actor_ages_lead %>% filter(startYear >= 1920), aes(x = startYear, fill=category, color=category)) +
geom_ribbon(aes(ymin=low_age, ymax=high_age), alpha=0.2, size=0) +
geom_line(aes(y=med_age)) +
scale_fill_brewer(palette="Set1") +
scale_color_brewer(palette="Set1") +
labs(title="Change in Ages of Movie Lead Actors/Actress Over Time",
subtitle=sprintf("For %s Actors. Line represents median age.\nRibbon bounds represent 25th — 75th Percentiles. Data from IMDb retrieved 7/4/2018",df_ratings_movies %>% filter(startYear >= 1920) %>% ppdf()),
x="Year Movie was Released",
y="Age of Lead Actor/Actress",
caption="Max Woolf — minimaxir.com",
fill='',
color='')
ggsave("imdb-9.png", plot, width=4, height=3)
Same plot, but facet. (unused in final post since may not be enough data/similar accross all genres)
df_actor_ages_lead <- df_ratings_movies %>%
select(startYear, category, genres, age_lead) %>%
unnest_tokens(genre, genres, token = str_split, pattern = ",") %>%
filter(!(genre %in% c("game-show", "reality-tv", "short", "talk-show", "film-noir", NA))) %>%
group_by(startYear, category, genre) %>%
summarize(low_age = quantile(age_lead, 0.25, na.rm=T),
med_age = quantile(age_lead, 0.50, na.rm=T),
high_age = quantile(age_lead, 0.75, na.rm=T)) %>%
arrange(startYear)
df_actor_ages_lead %>% head()
plot <- ggplot(df_actor_ages_lead %>% filter(startYear >= 1950), aes(x = startYear, fill=category, color=category)) +
geom_ribbon(aes(ymin=low_age, ymax=high_age), alpha=0.2, size=0) +
geom_line(aes(y=med_age), size=0.5) +
theme_minimal(base_family = "Source Sans Pro", base_size=9) +
scale_fill_brewer(palette="Set1") +
scale_color_brewer(palette="Set1") +
facet_wrap(~ genre)
ggsave("imdb-10.png", plot, width=6, height=6)
Lead Gender Balance
Unused in post since a bit more complicated to explain and results need double-checking.
plot <- ggplot(df_ratings_movies %>% filter(startYear >= 1950), aes(x = startYear, fill=category)) +
geom_bar(position="fill", width=1) +
theme_minimal(base_family = "Source Sans Pro", base_size=9) +
scale_fill_brewer(palette="Set1") +
scale_color_brewer(palette="Set1")
ggsave("imdb-11.png", plot, width=4, height=3)
nth time lead
df_ratings_movies_nth <- df_ratings_movies %>%
group_by(nconst) %>%
arrange(startYear) %>%
mutate(nth_lead = row_number()) %>%
#filter(nth_lead <= 50) %>%
ungroup() %>%
arrange(desc(startYear), desc(numVotes))
df_ratings_movies_nth %>% select(primaryTitle, primaryName, nth_lead) %>% head(100)
df_actor_ages <- df_ratings_movies_nth %>%
group_by(startYear) %>%
summarize(low_nth = quantile(nth_lead, 0.25),
med_nth = quantile(nth_lead, 0.50),
high_nth = quantile(nth_lead, 0.75)) %>%
arrange(startYear)
df_actor_ages %>% head()
plot <- ggplot(df_actor_ages %>% filter(startYear >= 1950) , aes(x = startYear)) +
geom_ribbon(aes(ymin=low_nth, ymax=high_nth), alpha=0.2) +
geom_line(aes(y=med_nth)) +
scale_y_continuous(breaks=c(1:5, 10)) +
labs(title="#th Time Lead Actor of Movie Was A Lead Actor, Over Time",
subtitle=sprintf("For %s Lead Actors. Line represents median #.\nRibbon bounds represent 25th — 75th Percentiles. Data from IMDb retrieved 7/4/2018",df_ratings_movies_nth %>% filter(startYear >= 1950) %>% ppdf()),
x="Year",
y="#th Time Lead Actor was a Lead Actor",
caption="Max Woolf — minimaxir.com",
fill="# Movies") +
theme(panel.grid.minor = element_blank())
ggsave("imdb-12.png", plot, width=4, height=3)
LICENSE
The MIT License (MIT)
Copyright (c) 2018 Max Woolf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LS0tCnRpdGxlOiAiQW5hbHl6aW5nIElNRGIgRGF0YSBUaGUgSW50ZW5kZWQgV2F5LCB3aXRoIFIgYW5kIGdncGxvdDIiCmF1dGhvcjogIk1heCBXb29sZiAoQG1pbmltYXhpcikiCmRhdGU6ICIyMDE4LTA3LTE1IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIG1hdGhqYXg6IG51bGwKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogVHJ1ZQotLS0KClRoaXMgUiBOb3RlYm9vayBpcyB0aGUgY29tcGxlbWVudCB0byBteSBibG9nIHBvc3QgW0FuYWx5emluZyBJTURiIERhdGEgVGhlIEludGVuZGVkIFdheSwgd2l0aCBSIGFuZCBnZ3Bsb3QyXShodHRwOi8vbWluaW1heGlyLmNvbS8yMDE4LzA3L2ltZGItZGF0YS1hbmFseXNpcy8pLgoKVGhpcyBub3RlYm9vayBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIElmIHlvdSB1c2UgdGhlIGNvZGUgb3IgZGF0YSB2aXN1YWxpemF0aW9uIGRlc2lnbnMgY29udGFpbmVkIHdpdGhpbiB0aGlzIG5vdGVib29rLCBpdCB3b3VsZCBiZSBncmVhdGx5IGFwcHJlY2lhdGVkIGlmIHByb3BlciBhdHRyaWJ1dGlvbiBpcyBnaXZlbiBiYWNrIHRvIHRoaXMgbm90ZWJvb2sgYW5kL29yIG15c2VsZi4gVGhhbmtzISA6KQoKSU1EYiBkYXRhIHJldHJpZXZlZCBvbiBKdWx5IDR0aCAyMDE4LgoKCioqSW5mb3JtYXRpb24gY291cnRlc3kgb2YKSU1EYgooaHR0cDovL3d3dy5pbWRiLmNvbSkuClVzZWQgd2l0aCBwZXJtaXNzaW9uLioqCgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncmlkZ2VzKSAgICMgdW51c2VkIGluIGZpbmFsIGJsb2cgcG9zdApsaWJyYXJ5KHRpZHl0ZXh0KSAgICMgdW51c2VkIGluIGZpbmFsIGJsb2cgcG9zdApsaWJyYXJ5KHNjYWxlcykKCnNlc3Npb25JbmZvKCkKYGBgCgpIZWxwZXIgZnVuY3Rpb24gdG8gcmVhZCBJTURCIGZpbGVzIGdpdmVuIGZpbGVuYW1lLgoKYGBge3J9CnJlYWRfaW1kYiA8LSBmdW5jdGlvbihkYXRhX3BhdGgpIHsKICBwYXRoIDwtICIvVm9sdW1lcy9FeHRyZW1lIDUxMC9EYXRhL2ltZGIvIgogIHJlYWRfdHN2KHBhc3RlMChwYXRoLCBkYXRhX3BhdGgpLCBuYSA9ICJcXE4iLCBxdW90ZT0nJywgcHJvZ3Jlc3M9RikKfQpgYGAKCkhlbHBlciBmdW5jdGlvbiB0byBwcmV0dHkgcHJpbnQgdGhlIHNpemUgb2YgYSBkYXRhZnJhbWUgZm9yIGNoYXJ0cy9ub3RlYm9vay4KCmBgYHtyfQpwcGRmIDwtIGZ1bmN0aW9uKGRmKSB7CiAgZGYgJT4lIG5yb3coKSAlPiUgY29tbWEoKQp9CmBgYAoKCiMgUmF0aW5ncwoKYGBge3J9CmRmX3JhdGluZ3MgPC0gcmVhZF9pbWRiKCJ0aXRsZS5yYXRpbmdzLnRzdiIpCmRmX3JhdGluZ3MgJT4lIGhlYWQoKQpgYGAKClRoZXJlIGFyZSAqKmByIGRmX3JhdGluZ3MgJT4lIHBwZGYoKWAqKiByYXRpbmdzIGluIHRoZSBkYXRhc2V0LgoKUGxvdCBldmVyeSBwb2ludC4gKG5vdGU6IHZlcnkgc2xvdyEpCgpgYGB7ciBldmFsPUZBTFNFfQpwbG90IDwtIGdncGxvdChkZl9yYXRpbmdzLCBhZXMoeCA9IG51bVZvdGVzLCB5ID0gYXZlcmFnZVJhdGluZykpICsKICAgICAgICAgIGdlb21fcG9pbnQoKQoKZ2dzYXZlKCJpbWRiLTAucG5nIiwgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTMpCmBgYAoKIVtdKGltZGItMC5wbmcpCgpQbG90IGEgMkQgaGlzdG9ncmFtIGFuZCBjbGVhbiB1cCBheGVzLgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3MsIGFlcyh4ID0gbnVtVm90ZXMsIHkgPSBhdmVyYWdlUmF0aW5nKSkgKwogICAgICAgICAgZ2VvbV9iaW4yZCgpICsKICAgICAgICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gY29tbWEpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSAxOjEwKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhsYWJlbHMgPSBjb21tYSkKCmdnc2F2ZSgiaW1kYi0xLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTEucG5nKQoKCgojIFRpdGxlIEJhc2ljcwoKYGBge3J9CmRmX2Jhc2ljcyA8LSByZWFkX2ltZGIoInRpdGxlLmJhc2ljcy50c3YiKQpkZl9iYXNpY3MgJT4lIGhlYWQoKQpgYGAKClRoZXJlIGFyZSAqKmByIGRmX2Jhc2ljcyAlPiUgcHBkZigpYCoqIHRpdGxlcyBpbiB0aGUgZGF0YXNldC4KCk1lcmdlIGBkZl9yYXRpbmdzYCBhbmQgYGRmX2Jhc2ljc2AgdG8gcGVyZm9ybSByYXRpbmdzL3ZvdGUgYW5hbHlzaXMgdXNpbmcgbW9yZSBtZXRhZGF0YS4KCmBgYHtyfQpkZl9yYXRpbmdzIDwtIGRmX3JhdGluZ3MgJT4lIGxlZnRfam9pbihkZl9iYXNpY3MpCgpkZl9yYXRpbmdzICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfcmF0aW5ncywgYWVzKHggPSBydW50aW1lTWludXRlcywgeSA9IGF2ZXJhZ2VSYXRpbmcpKSArCiAgICAgICAgICBnZW9tX2JpbjJkKCkgKwogICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gMToxMCkgKwogICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQoKZ2dzYXZlKCJpbWRiLTJhLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTJhLnBuZykKCldoaWNoIG1vdmllcyBoYXZlIHRoZSBzdXBlcmhpZ2ggcnVudGltZXM/CgpgYGB7cn0KZGZfcmF0aW5ncyAlPiUgYXJyYW5nZShkZXNjKHJ1bnRpbWVNaW51dGVzKSkgJT4lIGhlYWQoMTApCmBgYAoKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9yYXRpbmdzICU+JSBmaWx0ZXIocnVudGltZU1pbnV0ZXMgPCAxODAsIHRpdGxlVHlwZT09Im1vdmllIiwgbnVtVm90ZXMgPj0gMTApLCBhZXMoeCA9IHJ1bnRpbWVNaW51dGVzLCB5ID0gYXZlcmFnZVJhdGluZykpICsKICAgICAgICAgIGdlb21fYmluMmQoKSArCiAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE4MCwgNjApLCBsYWJlbHMgPSAwOjMpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSAwOjEwKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiaW5mZXJubyIsIGxhYmVscyA9IGNvbW1hKSArCiAgICAgICAgICB0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gIlNvdXJjZSBTYW5zIFBybyIsIGJhc2Vfc2l6ZT04KSArCiAgICAgICAgICBsYWJzKHRpdGxlPSJSZWxhdGlvbnNoaXAgYmV0d2VlbiBNb3ZpZSBSdW50aW1lIGFuZCBBdmVyYWdlIE1vdmllIFJhdGluZyIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlPSJEYXRhIGZyb20gSU1EYiByZXRyaWV2ZWQgSnVseSA0dGgsIDIwMTgiLAogICAgICAgICAgICAgICB4PSJSdW50aW1lIChIb3VycykiLAogICAgICAgICAgICAgICB5PSJBdmVyYWdlIFVzZXIgUmF0aW5nIiwKICAgICAgICAgICAgICAgY2FwdGlvbj0iTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIiwKICAgICAgICAgICAgICAgZmlsbD0iIyBNb3ZpZXMiKQoKZ2dzYXZlKCJpbWRiLTJiLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTJiLnBuZykKIyMgVW5uZXN0aW5nIEdlbnJlcwoKSG93IHRvIGZhY2V0IGJ5IGdlbnJlOyB0aGlzIHdhcyByZW1vdmVkIGZyb20gdGhlIHBvc3Qgc2luY2UgYSBsaXR0bGUgY29tcGxpY2F0ZWQuCgpOQjogeW91IGNhbm5vdCB1c2UgZGVmYXVsdCB0b2tlbml6YXRpb24gb24gYHVubmVzdF90b2tlbnNgIHNpbmNlIHNvbWUgdG9rZW5zIGhhdmUgZGFzaGVzLiAoZS5nLiBgZmlsbS1ub2lyYCkKCmBgYHtyfQpkZl9yYXRpbmdzX3VubmVzdCA8LSBkZl9yYXRpbmdzICU+JQogICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIocnVudGltZU1pbnV0ZXMgPCAxODAsIHRpdGxlVHlwZT09Im1vdmllIiwgbnVtVm90ZXMgPj0gMTApICU+JQogICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QocnVudGltZU1pbnV0ZXMsIGF2ZXJhZ2VSYXRpbmcsIGdlbnJlcykgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIHVubmVzdF90b2tlbnMoZ2VucmUsIGdlbnJlcywgdG9rZW4gPSBzdHJfc3BsaXQsIHBhdHRlcm4gPSAiLCIpCgpkZl9yYXRpbmdzX3VubmVzdCAlPiUgaGVhZCgxMCkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfcmF0aW5nc191bm5lc3QsIGFlcyh4ID0gcnVudGltZU1pbnV0ZXMsIHkgPSBhdmVyYWdlUmF0aW5nKSkgKwogICAgICAgICAgZ2VvbV9iaW4yZCgpICsKICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTgwLCA2MCksIGxhYmVscyA9IDA6MykgKwogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IDE6MTApICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJpbmZlcm5vIiwgbGFiZWxzID0gY29tbWEpICsKICAgICAgICAgIHRoZW1lX21pbmltYWwoYmFzZV9mYW1pbHkgPSAiU291cmNlIFNhbnMgUHJvIiwgYmFzZV9zaXplPTkpICsKICAgICAgICAgIGxhYnModGl0bGU9IlJlbGF0aW9uc2hpcCBiZXR3ZWVuIE1vdmllIFJ1bnRpbWUgYW5kIEF2ZXJhZ2UgTW9iaWUgUmF0aW5nIiwKICAgICAgICAgICAgICAgc3VidGl0bGU9IkRhdGEgZnJvbSBJTURiIHJldHJpZXZlZCBKdWx5IDR0aCwgMjAxOCIsCiAgICAgICAgICAgICAgIHg9IlJ1bnRpbWUgKEhvdXJzKSIsCiAgICAgICAgICAgICAgIHk9IkF2ZXJhZ2UgVXNlciBSYXRpbmciLAogICAgICAgICAgICAgICBjYXB0aW9uPSJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICBmaWxsPSIjIE1vdmllcyIpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBnZW5yZSkKCmdnc2F2ZSgiaW1kYi0zLnBuZyIsIHBsb3QsIHdpZHRoPTYsIGhlaWdodD02KQpgYGAKCiFbXShpbWRiLTMucG5nKQoKTm9ybWFsaXplIGJ5IGZhY2V0LiBUaGVyZSBhcmUgdHdvIGFwcHJvYWNoZXM6CgoxLiBEbyBhIHdlaWdodGVkIHN1bSBvZiB0aGUgcG9pbnRzIGluIHRoZSBzcGF0aWFsIGFyZWEsIHdoZXJlIHRoZSB3ZWlnaHQgaXMgdGhlIHJlY2lwcm9jb2wgb2YgdGhlICMgb2YgcG9pbnRzIGluIHRoZSBmYWNldC4KMi4gTWFudWFsbHkgY2FsY3VsYXRlIGJpbnMvY291bnRzIGFuZCBzY2FsZSB0byBgWzAsIDFdYAoKT3B0aW9uIDEgaXMgYSBiaXQgZWFzaWVyIHRvIGltcGxlbWVudC4gQWRkaXRpb25hbGx5LCByZW1vdmUgZmFjZXRzIHdpdGggbGl0dGxlIGRhdGEuCgpgc3F1aXNoYCB0cmljayB2aWEgaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9hLzIzNjU1Njk3LzkzMTQ0MTgKCmBgYHtyfQpkZl90ZW1wIDwtIGRmX3JhdGluZ3NfdW5uZXN0ICU+JQogICAgICAgICAgICBmaWx0ZXIoIShnZW5yZSAlaW4lIGMoImdhbWUtc2hvdyIsICJyZWFsaXR5LXR2IiwgInNob3J0IiwgInRhbGstc2hvdyIsIE5BKSkpICU+JQogICAgICAgICAgICBncm91cF9ieShnZW5yZSkgJT4lCiAgICAgICAgICAgIG11dGF0ZShwcm9wID0gMS9uKCkpCgpwbG90IDwtIGdncGxvdChkZl90ZW1wLCBhZXMoeCA9IHJ1bnRpbWVNaW51dGVzLCB5ID0gYXZlcmFnZVJhdGluZywgej1wcm9wKSkgKwogICAgICAgICAgc3RhdF9zdW1tYXJ5XzJkKGZ1bj1zdW0pICsKICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTgwLCA2MCksIGxhYmVscyA9IDA6MykgKwogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IDE6MTApICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJpbmZlcm5vIiwgbGFiZWxzID0gY29tbWEsIGxpbWl0cz1jKDAsIDAuMDIpLCBvb2I9c3F1aXNoLCBndWlkZT1GKSArCiAgICAgICAgICB0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gIlNvdXJjZSBTYW5zIFBybyIsIGJhc2Vfc2l6ZT05KSArCiAgICAgICAgICBsYWJzKHRpdGxlPSJSZWxhdGlvbnNoaXAgYmV0d2VlbiBNb3ZpZSBSdW50aW1lIGFuZCBBdmVyYWdlIE1vYmllIFJhdGluZyIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlPSJEYXRhIGZyb20gSU1EYiByZXRyaWV2ZWQgSnVseSA0dGgsIDIwMTgiLAogICAgICAgICAgICAgICB4PSJSdW50aW1lIChIb3VycykiLAogICAgICAgICAgICAgICB5PSJBdmVyYWdlIFVzZXIgUmF0aW5nIiwKICAgICAgICAgICAgICAgY2FwdGlvbj0iTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGdlbnJlKQoKZ2dzYXZlKCJpbWRiLTNiLnBuZyIsIHBsb3QsIHdpZHRoPTYsIGhlaWdodD02KQpgYGAKCiFbXShpbWRiLTNiLnBuZykKCiMgUmF0aW5nIHZzLiBNb3ZpZSBZZWFyCgpTZXQgdGhlbWUgdG8gY3VzdG9tIHRoZW1lIGJhc2VkIG9uIGB0aGVtZV9taW5pbWFsYCBmb3IgdGhlIHJlc3Qgb2YgdGhlIG5vdGVib29rLgoKYGBge3J9CnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZT05LCBiYXNlX2ZhbWlseT0iU291cmNlIFNhbnMgUHJvIikgKwogICAgICAgICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCwgZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gQm9sZCIsIG1hcmdpbj1tYXJnaW4odCA9IC0wLjEsIGIgPSAwLjEsIHVuaXQ9J2NtJykpLAogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gU2VtaWJvbGQiLCBjb2xvcj0iIzk2OTY5NiIsIHNpemU9NiksCiAgICAgICAgICAgICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplPTYsIGNvbG9yPSIjOTY5Njk2IiksCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgICAgICAgICAgICBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgwLjI1LCB1bml0PSdjbScpKSkKYGBgCgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3MgJT4lIGZpbHRlcih0aXRsZVR5cGU9PSJtb3ZpZSIsIG51bVZvdGVzID49IDEwKSwgYWVzKHggPSBzdGFydFllYXIsIHkgPSBhdmVyYWdlUmF0aW5nKSkgKwogICAgICAgICAgZ2VvbV9iaW4yZCgpICsKICAgICAgICAgIGdlb21fc21vb3RoKGNvbG9yPSJibGFjayIpICsKICAgICAgICAgIHNjYWxlX3hfY29udGludW91cygpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSAxOjEwKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAicGxhc21hIiwgbGFiZWxzID0gY29tbWEsIHRyYW5zPSdsb2cxMCcpICsKICAgICAgICAgIGxhYnModGl0bGU9IlJlbGF0aW9uc2hpcCBiZXR3ZWVuIE1vdmllIFJlbGVhc2UgWWVhciBhbmQgQXZlcmFnZSBSYXRpbmciLAogICAgICAgICAgICAgICBzdWJ0aXRsZT1zcHJpbnRmKCJGb3IgJXMgTW92aWVzL1JhdGluZ3MuIERhdGEgZnJvbSBJTURiIHJldHJpZXZlZCA3LzQvMjAxOCIsIGRmX3JhdGluZ3MgJT4lIGZpbHRlcih0aXRsZVR5cGU9PSJtb3ZpZSIsIG51bVZvdGVzID49IDEwKSAlPiUgcHBkZiksCiAgICAgICAgICAgICAgIHg9IlllYXIgTW92aWUgd2FzIFJlbGVhc2VkIiwKICAgICAgICAgICAgICAgeT0iQXZlcmFnZSBVc2VyIFJhdGluZyBGb3IgTW92aWUiLAogICAgICAgICAgICAgICBjYXB0aW9uPSJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICBmaWxsPSIjIE1vdmllcyIpCgpnZ3NhdmUoImltZGItNC5wbmciLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MykKYGBgCgohW10oaW1kYi00LnBuZykKCldvcmsgd2l0aCBSaWRnZSBwbG90czsgbm90IGluY2x1ZGVkIGluIHBvc3QgYmVjYXVzZSBpdCBkb2Vzbid0IG9mZmVyIG11Y2ggaW5zaWdodCBkaWZmZXJlbnQgZnJvbSB0aGUgY2hhcnQgYWJvdmUuCgpOQjogRm9yIHJpZGdlIHBsb3RzLCB0aGUgeS1heGlzIG11c3QgYmUgYSBgZmFjdG9yYCwgbm90IGEgYG51bWVyaWNgOyB0aGlzIGlzIHdoYXQgdHJpcHBlZCBtZSB1cCBpbiB0aGUgc3RyZWFtLgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3MgJT4lIGZpbHRlcihzdGFydFllYXIgPj0gMjAwMCwgdGl0bGVUeXBlPT0ibW92aWUiLCAgbnVtVm90ZXMgPj0gMTApICU+JSBtdXRhdGUoc3RhcnRZZWFyID0gZmFjdG9yKHN0YXJ0WWVhcikpLCBhZXMoeCA9IGF2ZXJhZ2VSYXRpbmcsIHkgPSBzdGFydFllYXIsIGZpbGw9c3RhcnRZZWFyKSkgKwogICAgICAgICAgZ2VvbV9kZW5zaXR5X3JpZGdlcygpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfaHVlKGd1aWRlPUYpICsKICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOjEwKSArCiAgICAgICAgICB0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gIlNvdXJjZSBTYW5zIFBybyIsIGJhc2Vfc2l6ZT05KQoKZ2dzYXZlKCJpbWRiLTUucG5nIiwgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTMpCmBgYAoKIVtdKGltZGItNS5wbmcpCgpCdWNrZXQgYnkgZGVjYWRlcy4KCmBgYHtyfQpkZl9yYXRpbmdzX2RlY2FkZXMgPC0gZGZfcmF0aW5ncyAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHN0YXJ0WWVhcj49MTk1MCwgdGl0bGVUeXBlPT0ibW92aWUiLCAgbnVtVm90ZXMgPj0gMTApICU+JQogIG11dGF0ZShkZWNhZGUgPSBmY3RfcmV2KGZhY3RvcihjdXRfd2lkdGgoc3RhcnRZZWFyLCAxMCwgYm91bmRhcnk9MCksIGxhYmVscyA9IHBhc3RlMChzZXEoMTk1MCwgMjAxMCwgMTApLCAicyIpKSkpCgpkZl9yYXRpbmdzX2RlY2FkZXMgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9yYXRpbmdzX2RlY2FkZXMsIGFlcyh4ID0gYXZlcmFnZVJhdGluZywgeSA9IGRlY2FkZSwgZmlsbD0wLjUgLSBhYnMoMC41LS4uZWNkZi4uKSkpICsKICAgICAgICAgIGdlb21fZGVuc2l0eV9yaWRnZXNfZ3JhZGllbnQoY2FsY19lY2RmPVQsIHF1YW50aWxlX2xpbmVzPVQpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMToxMCkgKwogICAgICAgICAgI3NjYWxlX3lfZGlzY3JldGUoZXhwYW5kID0gYygwLDAxLCAwKSkgKwogICAgICAgICAgdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICJTb3VyY2UgU2FucyBQcm8iLCBiYXNlX3NpemU9OSkKCmdnc2F2ZSgiaW1kYi02LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTYucG5nKQoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3NfZGVjYWRlcywgYWVzKHggPSBydW50aW1lTWludXRlcywgeSA9IGRlY2FkZSwgZmlsbD0wLjUgLSBhYnMoMC41LS4uZWNkZi4uKSkpICsKICAgICAgICAgIGdlb21fZGVuc2l0eV9yaWRnZXNfZ3JhZGllbnQoY2FsY19lY2RmPVQsIHF1YW50aWxlX2xpbmVzPVQpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE4MCwgNjApLCBsaW1pdHM9YygwLDE4MCksIGxhYmVscyA9IDA6MykgKwogICAgICAgICAgdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICJTb3VyY2UgU2FucyBQcm8iLCBiYXNlX3NpemU9OSkKCmdnc2F2ZSgiaW1kYi03LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTcucG5nKQoKIyMgRXBpc29kZSBBbmFseXNpcwoKRm9yIHJlZmVyZW5jZTsgbm90IGluY2x1ZGVkIGluIHBvc3QuIChUb28gbXVjaCBiYWQgZGF0YSB0byBjbGVhbi4pCgpgYGB7cn0KZGZfZXBpc29kZSA8LSByZWFkX2ltZGIoInRpdGxlLmVwaXNvZGUudHN2IikgJT4lIGZpbHRlcighaXMubmEoc2Vhc29uTnVtYmVyKSkKZGZfZXBpc29kZSAlPiUgaGVhZCgpCmBgYAoKVGhlcmUgYXJlIGByIGRmX2VwaXNvZGUgJT4lIHBwZGYoKWAgZXBpc29kZXMgaW4gdGhlIGRhdGFzZXQuCgpgYGB7cn0KZGZfZXBpc29kZV9jb3VudCA8LSBkZl9lcGlzb2RlICU+JQogICAgICAgICAgICAgICAgZ3JvdXBfYnkocGFyZW50VGNvbnN0LCBzZWFzb25OdW1iZXIpICU+JQogICAgICAgICAgICAgICAgdGFsbHkoKSAlPiUKICAgICAgICAgICAgICAgIGxlZnRfam9pbihkZl9iYXNpY3MsICBjKCJwYXJlbnRUY29uc3QiID0gInRjb25zdCIpKQoKZGZfZXBpc29kZV9jb3VudCAlPiUgaGVhZCgpCmBgYAoKCiMgQWN0b3IgSW5mb3JtYXRpb24KCmBzdHJfZGV0ZWN0YCBpcyB2ZWN0b3JpemVkIGFuZCAqbXVjaCogZmFzdGVyIHRoYW4gdXNpbmcgYSBsb29wL2BsYXBwbHlgLiBVc2luZyBhIHJlZ3VsYXIgZXhwcmVzc2lvbiB0byBzZWFyY2ggZm9yIGFjdG9yICpvciogYWN0cmVzcyBpcyBhbm90aGVyIHNwZWVkIGluY3JlYXNlLgoKYGBge3J9CmRmX2FjdG9ycyA8LSByZWFkX2ltZGIoIm5hbWUuYmFzaWNzLnRzdiIpICU+JQogICAgICAgICAgICAgICAgZmlsdGVyKHN0cl9kZXRlY3QocHJpbWFyeVByb2Zlc3Npb24sICJhY3RvcnxhY3RyZXNzIikpICAlPiUKICAgICAgICAgICAgICAgIHNlbGVjdChuY29uc3QsIHByaW1hcnlOYW1lLCBiaXJ0aFllYXIpCgpkZl9hY3RvcnMgJT4lIGhlYWQoKQpgYGAKClRoZXJlIGFyZSAqKmByIGRmX2FjdG9ycyAlPiUgcHBkZigpYCoqIGFjdG9ycyBpbiB0aGUgZGF0YXNldC4KCmBgYHtyfQpkZl9wcmluY2lwYWxzIDwtIHJlYWRfaW1kYigidGl0bGUucHJpbmNpcGFscy50c3YiKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yeSwgImFjdG9yfGFjdHJlc3MiKSkgJT4lCiAgc2VsZWN0KHRjb25zdCwgb3JkZXJpbmcsIG5jb25zdCwgY2F0ZWdvcnkpICU+JQogIGdyb3VwX2J5KHRjb25zdCkgJT4lCiAgZmlsdGVyKG9yZGVyaW5nID09IG1pbihvcmRlcmluZykpCgpkZl9wcmluY2lwYWxzICU+JSBoZWFkKCkKYGBgCgpUaGVyZSBhcmUgKipgciBkZl9wcmluY2lwYWxzICU+JSBwcGRmKClgKiogcHJpbmNpcGFscy9yb3dzIGluIHRoZSBkYXRhc2V0LgoKSm9pbiB0aGUgMiBkYXRhZnJhbWVzLiAgKG9udG8gYHByaW5jaXBhbHNgLCBzaW5jZSBNYW55LXRvLU9uZSkKCmBgYHtyfQpkZl9wcmluY2lwYWxzIDwtIGRmX3ByaW5jaXBhbHMgJT4lIGxlZnRfam9pbihkZl9hY3RvcnMpCgpkZl9wcmluY2lwYWxzICU+JSBoZWFkKCkKYGBgCgojIFB1dHRpbmcgSXQgQWxsIFRvZ2V0aGVyCgpNZXJnZSBhY3RvciBpbmZvcm1hdGlvbiBvbnRvIHRoZSBmdWxsIHJhdGluZ3MgZGF0YWZyYW1lLgoKYGBge3J9CmRmX3JhdGluZ3MgPC0gZGZfcmF0aW5ncyAlPiUgbGVmdF9qb2luKGRmX3ByaW5jaXBhbHMpCgpkZl9yYXRpbmdzICU+JSBoZWFkKCkKYGBgCgpGaWx0ZXIgZG93biB0byBtb3ZpZXMgdy8gYWN0b3IgaW5mby4gKG9ubHkgaWYgdGhlIGJpcnRoIHllYXIgaXMgcHJlc2VudCBpbiB0aGUgZGF0YSkKCmBgYHtyfQpkZl9yYXRpbmdzX21vdmllcyA8LSBkZl9yYXRpbmdzICU+JQogICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIodGl0bGVUeXBlID09ICJtb3ZpZSIsICFpcy5uYShiaXJ0aFllYXIpLCBudW1Wb3RlcyA+PSAxMCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShhZ2VfbGVhZCA9IHN0YXJ0WWVhciAtIGJpcnRoWWVhcikgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyhudW1Wb3RlcykpCgpkZl9yYXRpbmdzX21vdmllcyAlPiUgaGVhZCgxMDApCmBgYAoKQWdncmVnYXRlIGxlYWQtYWN0b3IvYWN0cmVzcyBhZ2VzIGJ5IG1vdmllIHllYXIgdy8gcGVyY2VudGlsZXMuCgpgYGB7cn0KZGZfYWN0b3JfYWdlcyA8LSBkZl9yYXRpbmdzX21vdmllcyAlPiUKICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRZZWFyKSAlPiUKICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKGxvd19hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC4yNSwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRfYWdlID0gcXVhbnRpbGUoYWdlX2xlYWQsIDAuNTAsIG5hLnJtPVQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlnaF9hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC43NSwgbmEucm09VCkpICU+JQogICAgICAgICAgICAgICAgICBhcnJhbmdlKHN0YXJ0WWVhcikKCmRmX2FjdG9yX2FnZXMgJT4lIGhlYWQoKQpgYGAKCkNyZWF0ZSBhIHJpYmJvbiBwbG90LgoKTkI6IFBsb3QgdGhlIHJpYmJvbiBiZWZvcmUgdGhlIGxpbmUsIHNvIHRoZSBsaW5lIGlzIG9uIHRvcC4KCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9hY3Rvcl9hZ2VzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5MjApICwgYWVzKHggPSBzdGFydFllYXIpKSArCiAgICAgICAgICBnZW9tX3JpYmJvbihhZXMoeW1pbj1sb3dfYWdlLCB5bWF4PWhpZ2hfYWdlKSwgYWxwaGE9MC4yKSArCiAgICAgICAgICBnZW9tX2xpbmUoYWVzKHk9bWVkX2FnZSkpICsKICAgICAgICAgIGxhYnModGl0bGU9IkNoYW5nZSBpbiBBZ2VzIG9mIE1vdmllIExlYWQgQWN0b3JzL0FjdHJlc3MgT3ZlciBUaW1lIiwKICAgICAgICAgICAgICAgc3VidGl0bGU9c3ByaW50ZigiRm9yICVzIEFjdG9ycy4gTGluZSByZXByZXNlbnRzIG1lZGlhbiBhZ2UuXG5SaWJib24gYm91bmRzIHJlcHJlc2VudCAyNXRoIOKAlCA3NXRoIFBlcmNlbnRpbGVzLiBEYXRhIGZyb20gSU1EYiByZXRyaWV2ZWQgNy80LzIwMTgiLGRmX3JhdGluZ3NfbW92aWVzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5MjApICU+JSBwcGRmKCkpLAogICAgICAgICAgICAgICB4PSJZZWFyIE1vdmllIHdhcyBSZWxlYXNlZCIsCiAgICAgICAgICAgICAgIHk9IkFnZSBvZiBMZWFkIEFjdG9yL0FjdHJlc3MiLAogICAgICAgICAgICAgICBjYXB0aW9uPSJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICBmaWxsPSIjIE1vdmllcyIpCgpnZ3NhdmUoImltZGItOC5wbmciLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MykKYGBgCgohW10oaW1kYi04LnBuZykKCkNyZWF0ZSBhIHBsb3QgY29tcGFyaW5nIGFjdG9ycy9hY3RyZXNzZXMuIFNhbWUgY29kZSwgZXhjZXB0IGFkZGluZyBhbiBhZ2dyZWdhdGlvbiBhbmQgYWVzdGV0aWMgb24gYGNhdGVnb3J5YC4KCmBgYHtyfQpkZl9hY3Rvcl9hZ2VzX2xlYWQgPC0gZGZfcmF0aW5nc19tb3ZpZXMgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0WWVhciwgY2F0ZWdvcnkpICU+JQogICAgICAgICAgICAgICAgICBzdW1tYXJpemUobG93X2FnZSA9IHF1YW50aWxlKGFnZV9sZWFkLCAwLjI1LCBuYS5ybT1UKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZF9hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC41MCwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWdoX2FnZSA9IHF1YW50aWxlKGFnZV9sZWFkLCAwLjc1LCBuYS5ybT1UKSkgJT4lCiAgICAgICAgICAgICAgICAgIGFycmFuZ2Uoc3RhcnRZZWFyKQoKZGZfYWN0b3JfYWdlc19sZWFkICU+JSBoZWFkKCkKCnBsb3QgPC0gZ2dwbG90KGRmX2FjdG9yX2FnZXNfbGVhZCAlPiUgZmlsdGVyKHN0YXJ0WWVhciA+PSAxOTIwKSwgYWVzKHggPSBzdGFydFllYXIsIGZpbGw9Y2F0ZWdvcnksIGNvbG9yPWNhdGVnb3J5KSkgKwogICAgICAgICAgZ2VvbV9yaWJib24oYWVzKHltaW49bG93X2FnZSwgeW1heD1oaWdoX2FnZSksIGFscGhhPTAuMiwgc2l6ZT0wKSArCiAgICAgICAgICBnZW9tX2xpbmUoYWVzKHk9bWVkX2FnZSkpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgICAgICAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICAgICAgICAgIGxhYnModGl0bGU9IkNoYW5nZSBpbiBBZ2VzIG9mIE1vdmllIExlYWQgQWN0b3JzL0FjdHJlc3MgT3ZlciBUaW1lIiwKICAgICAgICAgICAgICAgc3VidGl0bGU9c3ByaW50ZigiRm9yICVzIEFjdG9ycy4gTGluZSByZXByZXNlbnRzIG1lZGlhbiBhZ2UuXG5SaWJib24gYm91bmRzIHJlcHJlc2VudCAyNXRoIOKAlCA3NXRoIFBlcmNlbnRpbGVzLiBEYXRhIGZyb20gSU1EYiByZXRyaWV2ZWQgNy80LzIwMTgiLGRmX3JhdGluZ3NfbW92aWVzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5MjApICU+JSBwcGRmKCkpLAogICAgICAgICAgICAgICB4PSJZZWFyIE1vdmllIHdhcyBSZWxlYXNlZCIsCiAgICAgICAgICAgICAgIHk9IkFnZSBvZiBMZWFkIEFjdG9yL0FjdHJlc3MiLAogICAgICAgICAgICAgICBjYXB0aW9uPSJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICBmaWxsPScnLAogICAgICAgICAgICAgICBjb2xvcj0nJykKCmdnc2F2ZSgiaW1kYi05LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTkucG5nKQoKU2FtZSBwbG90LCBidXQgZmFjZXQuICh1bnVzZWQgaW4gZmluYWwgcG9zdCBzaW5jZSBtYXkgbm90IGJlIGVub3VnaCBkYXRhL3NpbWlsYXIgYWNjcm9zcyBhbGwgZ2VucmVzKQoKYGBge3J9CmRmX2FjdG9yX2FnZXNfbGVhZCA8LSBkZl9yYXRpbmdzX21vdmllcyAlPiUKICAgICAgICAgICAgICAgICAgc2VsZWN0KHN0YXJ0WWVhciwgY2F0ZWdvcnksIGdlbnJlcywgYWdlX2xlYWQpICU+JQogICAgICAgICAgICAgICAgICB1bm5lc3RfdG9rZW5zKGdlbnJlLCBnZW5yZXMsIHRva2VuID0gc3RyX3NwbGl0LCBwYXR0ZXJuID0gIiwiKSAlPiUKICAgICAgICAgICAgICAgICAgZmlsdGVyKCEoZ2VucmUgJWluJSBjKCJnYW1lLXNob3ciLCAicmVhbGl0eS10diIsICJzaG9ydCIsICJ0YWxrLXNob3ciLCAiZmlsbS1ub2lyIiwgTkEpKSkgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0WWVhciwgY2F0ZWdvcnksIGdlbnJlKSAlPiUKICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKGxvd19hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC4yNSwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRfYWdlID0gcXVhbnRpbGUoYWdlX2xlYWQsIDAuNTAsIG5hLnJtPVQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlnaF9hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC43NSwgbmEucm09VCkpICU+JQogICAgICAgICAgICAgICAgICBhcnJhbmdlKHN0YXJ0WWVhcikKCmRmX2FjdG9yX2FnZXNfbGVhZCAlPiUgaGVhZCgpCgpwbG90IDwtIGdncGxvdChkZl9hY3Rvcl9hZ2VzX2xlYWQgJT4lIGZpbHRlcihzdGFydFllYXIgPj0gMTk1MCksIGFlcyh4ID0gc3RhcnRZZWFyLCBmaWxsPWNhdGVnb3J5LCBjb2xvcj1jYXRlZ29yeSkpICsKICAgICAgICAgIGdlb21fcmliYm9uKGFlcyh5bWluPWxvd19hZ2UsIHltYXg9aGlnaF9hZ2UpLCBhbHBoYT0wLjIsIHNpemU9MCkgKwogICAgICAgICAgZ2VvbV9saW5lKGFlcyh5PW1lZF9hZ2UpLCBzaXplPTAuNSkgKwogICAgICAgICAgdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICJTb3VyY2UgU2FucyBQcm8iLCBiYXNlX3NpemU9OSkgKwogICAgICAgICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGdlbnJlKQoKZ2dzYXZlKCJpbWRiLTEwLnBuZyIsIHBsb3QsIHdpZHRoPTYsIGhlaWdodD02KQpgYGAKCiFbXShpbWRiLTEwLnBuZykKCiMgTGVhZCBHZW5kZXIgQmFsYW5jZQoKVW51c2VkIGluIHBvc3Qgc2luY2UgYSBiaXQgbW9yZSBjb21wbGljYXRlZCB0byBleHBsYWluIGFuZCByZXN1bHRzIG5lZWQgZG91YmxlLWNoZWNraW5nLgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3NfbW92aWVzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5NTApLCBhZXMoeCA9IHN0YXJ0WWVhciwgZmlsbD1jYXRlZ29yeSkpICsKICAgICAgICAgIGdlb21fYmFyKHBvc2l0aW9uPSJmaWxsIiwgd2lkdGg9MSkgKwogICAgICAgICAgdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICJTb3VyY2UgU2FucyBQcm8iLCBiYXNlX3NpemU9OSkgKwogICAgICAgICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikKCmdnc2F2ZSgiaW1kYi0xMS5wbmciLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MykKYGBgCgohW10oaW1kYi0xMS5wbmcpCgojIG50aCB0aW1lIGxlYWQKCmBgYHtyfQpkZl9yYXRpbmdzX21vdmllc19udGggPC0gZGZfcmF0aW5nc19tb3ZpZXMgJT4lCiAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieShuY29uc3QpICU+JQogICAgICAgICAgICAgICAgICAgICAgYXJyYW5nZShzdGFydFllYXIpICU+JQogICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG50aF9sZWFkID0gcm93X251bWJlcigpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyhzdGFydFllYXIpLCBkZXNjKG51bVZvdGVzKSkKCmRmX3JhdGluZ3NfbW92aWVzX250aCAlPiUgc2VsZWN0KHByaW1hcnlUaXRsZSwgcHJpbWFyeU5hbWUsIG50aF9sZWFkKSAlPiUgaGVhZCgxMDApCmBgYAoKYGBge3J9CmRmX2FjdG9yX2FnZXMgPC0gZGZfcmF0aW5nc19tb3ZpZXNfbnRoICU+JQogICAgICAgICAgICAgICAgICBncm91cF9ieShzdGFydFllYXIpICU+JQogICAgICAgICAgICAgICAgICBzdW1tYXJpemUobG93X250aCA9IHF1YW50aWxlKG50aF9sZWFkLCAwLjI1KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZF9udGggPSBxdWFudGlsZShudGhfbGVhZCwgMC41MCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWdoX250aCA9IHF1YW50aWxlKG50aF9sZWFkLCAwLjc1KSkgJT4lCiAgICAgICAgICAgICAgICAgIGFycmFuZ2Uoc3RhcnRZZWFyKQoKZGZfYWN0b3JfYWdlcyAlPiUgaGVhZCgpCgpwbG90IDwtIGdncGxvdChkZl9hY3Rvcl9hZ2VzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5NTApICwgYWVzKHggPSBzdGFydFllYXIpKSArCiAgICAgICAgICBnZW9tX3JpYmJvbihhZXMoeW1pbj1sb3dfbnRoLCB5bWF4PWhpZ2hfbnRoKSwgYWxwaGE9MC4yKSArCiAgICAgICAgICBnZW9tX2xpbmUoYWVzKHk9bWVkX250aCkpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3M9YygxOjUsIDEwKSkgKwogICAgICAgICAgbGFicyh0aXRsZT0iI3RoIFRpbWUgTGVhZCBBY3RvciBvZiBNb3ZpZSBXYXMgQSBMZWFkIEFjdG9yLCBPdmVyIFRpbWUiLAogICAgICAgICAgICAgICBzdWJ0aXRsZT1zcHJpbnRmKCJGb3IgJXMgTGVhZCBBY3RvcnMuIExpbmUgcmVwcmVzZW50cyBtZWRpYW4gIy5cblJpYmJvbiBib3VuZHMgcmVwcmVzZW50IDI1dGgg4oCUIDc1dGggUGVyY2VudGlsZXMuIERhdGEgZnJvbSBJTURiIHJldHJpZXZlZCA3LzQvMjAxOCIsZGZfcmF0aW5nc19tb3ZpZXNfbnRoICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5NTApICU+JSBwcGRmKCkpLAogICAgICAgICAgICAgICB4PSJZZWFyIiwKICAgICAgICAgICAgICAgeT0iI3RoIFRpbWUgTGVhZCBBY3RvciB3YXMgYSBMZWFkIEFjdG9yIiwKICAgICAgICAgICAgICAgY2FwdGlvbj0iTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIiwKICAgICAgICAgICAgICAgZmlsbD0iIyBNb3ZpZXMiKSArCiAgICAgICAgICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCJpbWRiLTEyLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTEyLnBuZykKCiMgTElDRU5TRQoKVGhlIE1JVCBMaWNlbnNlIChNSVQpCgpDb3B5cmlnaHQgKGMpIDIwMTggTWF4IFdvb2xmCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMgdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbCBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IgSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSIExJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLg==