Note that the directories used to store data are likely different on your computer, and such references will need to be changed before using any such code.
library(knitr)
library(kableExtra)
html_df <- function(text, cols=NULL, col1=FALSE, full=F) {
if(!length(cols)) {
cols=colnames(text)
}
if(!col1) {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full)
} else {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full) %>%
column_spec(1,bold=T)
}
}
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
-- Attaching packages ---------------------------------------------------------------------------------------------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5 v purrr 0.3.4
v tibble 3.1.2 v dplyr 1.0.7
v tidyr 1.1.3 v stringr 1.4.0
v readr 1.4.0 v forcats 0.5.1
-- Conflicts ------------------------------------------------------------------------------------------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::group_rows() masks kableExtra::group_rows()
x dplyr::lag() masks stats::lag()
library(plotly)
Registered S3 method overwritten by 'data.table':
method from
print.data.table
Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
Attaching package: ‘plotly’
The following object is masked from ‘package:ggplot2’:
last_plot
The following object is masked from ‘package:stats’:
filter
The following object is masked from ‘package:graphics’:
layout
library(lubridate)
Attaching package: ‘lubridate’
The following objects are masked from ‘package:base’:
date, intersect, setdiff, union
df <- read.csv("../../Data/Session_5-1.csv", stringsAsFactors=FALSE)
df_ratings <- read.csv("../../Data/Session_5-2.csv", stringsAsFactors=FALSE)
df_mve <- read.csv("../../Data/Session_5-3.csv", stringsAsFactors=FALSE)
df_rf <- read.csv("../../Data/Session_5-4.csv", stringsAsFactors=FALSE)
df_stock <- read.csv("../../Data/Session_5-5.csv", stringsAsFactors=FALSE)
# initial cleaning
# 100338 is an outlier in the bonds distribution
df <- df %>% filter(at >= 1, revt >= 1, gvkey != 100338)
## Merge in stock value
df$date <- as.Date(df$datadate)
df_mve <- df_mve %>%
mutate(date = as.Date(datadate),
mve = csho * prcc_f) %>%
rename(gvkey=GVKEY)
df <- left_join(df, df_mve[,c("gvkey","date","mve")])
Joining, by = c("gvkey", "date")
df <- df %>%
group_by(gvkey) %>%
arrange(datadate) %>%
mutate(bankrupt = ifelse(row_number() == n() & dlrsn == 2 &
!is.na(dlrsn), 1, 0),
bankrupt_lead = lead(bankrupt)) %>%
ungroup() %>%
filter(!is.na(bankrupt_lead)) %>%
mutate(bankrupt_lead = factor(bankrupt_lead, levels=c(0,1)))
# Calculate the measures needed
df <- df %>%
mutate(wcap_at = wcap / at, # x1
re_at = re / at, # x2
ebit_at = ebit / at, # x3
mve_lt = mve / lt, # x4
revt_at = revt / at) # x5
# cleanup
df <- df %>%
mutate_if(is.numeric, list(~replace(., !is.finite(.), NA)))
# Calculate the score
df <- df %>%
mutate(Z = 1.2 * wcap_at + 1.4 * re_at + 3.3 * ebit_at + 0.6 * mve_lt +
0.999 * revt_at)
# Calculate date info for merging
df$date <- as.Date(df$datadate)
df$year <- year(df$date)
df$month <- month(df$date)
# df_ratings has ratings data in it
# Ratings, in order from worst to best
ratings <- c("D", "C", "CC", "CCC-", "CCC","CCC+", "B-", "B", "B+", "BB-",
"BB", "BB+", "BBB-", "BBB", "BBB+", "A-", "A", "A+", "AA-", "AA",
"AA+", "AAA-", "AAA", "AAA+")
# Convert string ratings (splticrm) to numeric ratings
df_ratings$rating <- factor(df_ratings$splticrm, levels=ratings, ordered=T)
df_ratings$date <- as.Date(df_ratings$datadate)
df_ratings$year <- year(df_ratings$date)
df_ratings$month <- month(df_ratings$date)
# Merge together data
df <- left_join(df, df_ratings[,c("gvkey", "year", "month", "rating")])
Joining, by = c("gvkey", "year", "month")
plot <- df %>%
filter(!is.na(Z), !is.na(rating)) %>%
group_by(rating) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(rating, mean_Z) %>%
ggplot(aes(y=mean_Z, x=rating)) +
geom_col() +
ylab('Mean Altman Z') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(Z),
!is.na(bankrupt)) %>%
group_by(bankrupt_lead) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt_lead, mean_Z) %>%
html_df()
| bankrupt_lead |
mean_Z |
| 0 |
3.993796 |
| 1 |
1.739039 |
plot <- df %>%
filter(!is.na(Z), !is.na(rating), year >= 2000) %>%
group_by(rating) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(rating, mean_Z) %>%
ggplot(aes(y=mean_Z, x=rating)) +
geom_col() +
ylab('Mean Altman Z') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(Z),
!is.na(bankrupt_lead),
year >= 2000) %>%
group_by(bankrupt_lead) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt_lead, mean_Z) %>%
html_df()
| bankrupt_lead |
mean_Z |
| 0 |
3.897392 |
| 1 |
1.670656 |
fit_Z <- glm(bankrupt_lead ~ Z, data=df, family=binomial)
summary(fit_Z)
Call:
glm(formula = bankrupt_lead ~ Z, family = binomial, data = df)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.3959 -0.0705 -0.0685 -0.0658 3.7421
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -5.87769 0.11741 -50.060 < 2e-16 ***
Z -0.05494 0.01235 -4.449 8.61e-06 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 1101.0 on 33372 degrees of freedom
Residual deviance: 1088.8 on 33371 degrees of freedom
(14245 observations deleted due to missingness)
AIC: 1092.8
Number of Fisher Scoring iterations: 9
lz <- df %>% filter(!is.na(bankrupt_lead), !is.na(Z)) %>% filter(Z < 1) %>% pull(bankrupt_lead) %>% table()
hz <- df %>% filter(!is.na(bankrupt_lead), !is.na(Z)) %>% filter(Z >= 1) %>% pull(bankrupt_lead) %>% table()
x <- matrix(c(lz, hz), nrow=2)
rownames(x) <- c('No bankruptcy', 'Bankruptcy')
colnames(x) <- c('Z < 1', 'Z >= 1')
x
Z < 1 Z >= 1
No bankruptcy 2654 30641
Bankruptcy 29 49
library(yardstick)
For binary classification, the first factor level is assumed to be the event.
Use the argument `event_level = "second"` to alter this as needed.
Attaching package: ‘yardstick’
The following object is masked from ‘package:readr’:
spec
df_Z <- df %>% filter(!is.na(Z), !is.na(bankrupt_lead))
df_Z$pred <- predict(fit_Z, df_Z, type="response")
df_Z %>% roc_curve(truth=bankrupt_lead, estimate=pred, event_level='second') %>%
autoplot()

df_Z %>% roc_curve(truth=bankrupt_lead,
estimate=pred,
event_level='second') %>%
autoplot()

auc_Z <- df_Z %>% roc_auc(truth=bankrupt_lead, estimate=pred, event_level='second')
auc_Z
score = 1
m = 0
std = 1
funcShaded <- function(x, lower_bound) {
y = dnorm(x, mean = m, sd = std)
y[x < lower_bound] <- NA
return(y)
}
ggplot(data.frame(x = c(-3, 3)), aes(x = x)) +
stat_function(fun = dnorm, args = list(mean = m, sd = std)) +
stat_function(fun = funcShaded, args = list(lower_bound = score),
geom = "area", fill = 'black', alpha = .2) +
scale_x_continuous(name = "Score", breaks = seq(-3, 3, std)) +
geom_text(data = data.frame(x=c(1.5), y=c(0.05)), aes(x=x, y=y, label="Prob(default)", size=30)) +
geom_line(data = data.frame(x=c(1,1), y=c(0,0.4)), aes(x=x,y=y)) +
geom_text(data = data.frame(x=c(1.3), y=c(0.4)), aes(x=x, y=y, label="DD", size=30)) +
theme(legend.position="none")

# df_stock is an already prepped csv from CRSP data
df_stock$date <- as.Date(df_stock$date)
df <- left_join(df, df_stock[,c("gvkey", "date", "ret", "ret.sd")])
Joining, by = c("gvkey", "date")
df_rf$date <- as.Date(df_rf$dateff)
df_rf$year <- year(df_rf$date)
df_rf$month <- month(df_rf$date)
df <- left_join(df, df_rf[,c("year", "month", "rf")])
Joining, by = c("year", "month")
df <- df %>%
mutate(DD = (log(mve / lt) + (rf - (ret.sd*sqrt(253))^2 / 2)) /
(ret.sd*sqrt(253)))
# Clean the measure
df <- df %>%
mutate_if(is.numeric, list(~replace(., !is.finite(.), NA)))
plot <- df %>%
filter(!is.na(DD),
!is.na(rating)) %>%
group_by(rating) %>%
mutate(mean_DD=mean(DD,na.rm=T),
prob_default = pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(rating, prob_default) %>%
ggplot(aes(y=prob_default, x=rating)) +
geom_col() +
ylab('Probability of default') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(DD),
!is.na(bankrupt_lead)) %>%
group_by(bankrupt_lead) %>%
mutate(mean_DD=mean(DD, na.rm=T),
prob_default =
pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt_lead, mean_DD,
prob_default) %>%
html_df()
| bankrupt_lead |
mean_DD |
prob_default |
| 0 |
0.6427281 |
0.2602003 |
| 1 |
-3.1423863 |
0.9991621 |
plot <- df %>%
filter(!is.na(DD),
!is.na(rating),
year >= 2000) %>%
group_by(rating) %>%
mutate(mean_DD=mean(DD,na.rm=T),
prob_default = pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(rating, prob_default) %>%
ggplot(aes(y=prob_default, x=rating)) +
geom_col() +
ylab('Probability of default') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(DD),
!is.na(bankrupt_lead),
year >= 2000) %>%
group_by(bankrupt_lead) %>%
mutate(mean_DD=mean(DD, na.rm=T),
prob_default =
pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt_lead, mean_DD,
prob_default) %>%
html_df()
| bankrupt_lead |
mean_DD |
prob_default |
| 0 |
0.8878013 |
0.1873238 |
| 1 |
-4.4289487 |
0.9999953 |
fit_DD <- glm(bankrupt_lead ~ DD, data=df, family=binomial)
summary(fit_DD)
Call:
glm(formula = bankrupt_lead ~ DD, family = binomial, data = df)
Deviance Residuals:
Min 1Q Median 3Q Max
-3.6531 -0.0730 -0.0596 -0.0451 3.7497
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -6.27408 0.16653 -37.676 < 2e-16 ***
DD -0.29783 0.03877 -7.682 1.57e-14 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 665.03 on 20455 degrees of freedom
Residual deviance: 608.65 on 20454 degrees of freedom
(31380 observations deleted due to missingness)
AIC: 612.65
Number of Fisher Scoring iterations: 9
df_DD <- df %>% filter(!is.na(Z), !is.na(bankrupt_lead))
df_DD$pred <- predict(fit_DD, df_DD, type="response")
df_DD %>% roc_curve(truth=bankrupt_lead, estimate=pred, event_level='second') %>%
autoplot()

df_DD %>% roc_auc(truth=bankrupt_lead, estimate=pred, event_level='second')
#AUC
auc_DD <- df_DD %>% roc_auc(truth=bankrupt_lead, estimate=pred, event_level='second')
AUCs <- c(auc_Z$.estimate, auc_DD$.estimate)
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.7498970 0.8434707
# calculate downgrade
df <- df %>%
group_by(gvkey) %>%
arrange(date) %>%
mutate(downgrade = factor(ifelse(lead(rating) < rating,1,0), levels=c(0,1)),
diff_Z = Z - lag(Z),
diff_DD = DD - lag(DD)) %>%
ungroup()
# training sample
train <- df %>% filter(year < 2014, !is.na(diff_Z), !is.na(diff_DD), !is.na(downgrade),
year > 1985)
test <- df %>% filter(year >= 2014, !is.na(diff_Z), !is.na(diff_DD), !is.na(downgrade))
# glms
fit_Z2 <- glm(downgrade ~ diff_Z, data=train, family=binomial)
fit_DD2 <- glm(downgrade ~ diff_DD, data=train, family=binomial)
summary(fit_Z2)
Call:
glm(formula = downgrade ~ diff_Z, family = binomial, data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.9418 -0.4313 -0.4311 -0.4254 2.6569
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.32925 0.06246 -37.294 <2e-16 ***
diff_Z -0.09426 0.04860 -1.939 0.0525 .
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 1913.6 on 3177 degrees of freedom
Residual deviance: 1908.7 on 3176 degrees of freedom
AIC: 1912.7
Number of Fisher Scoring iterations: 5
summary(fit_DD2)
Call:
glm(formula = downgrade ~ diff_DD, family = binomial, data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.5614 -0.4240 -0.4230 -0.3754 2.7957
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.36904 0.06452 -36.719 < 2e-16 ***
diff_DD -0.25536 0.03883 -6.576 4.82e-11 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 1913.6 on 3177 degrees of freedom
Residual deviance: 1871.4 on 3176 degrees of freedom
AIC: 1875.4
Number of Fisher Scoring iterations: 5
df_Z2 <- train %>% filter(!is.na(diff_Z), !is.na(downgrade))
df_Z2$pred <- predict(fit_Z2, df_Z2, type="response")
curve1 <- df_Z2 %>% roc_curve(truth=downgrade, estimate=pred, event_level='second')
auc_Z2 <- df_Z2 %>% roc_auc(truth=downgrade, estimate=pred, event_level='second')
df_DD2 <- train %>% filter(!is.na(diff_DD), !is.na(downgrade))
df_DD2$pred <- predict(fit_DD2, df_DD2, type="response")
curve2 <- df_DD2 %>% roc_curve(truth=downgrade, estimate=pred, event_level='second')
auc_DD2 <- df_DD2 %>% roc_auc(truth=downgrade, estimate=pred, event_level='second')
curve1 <- curve1 %>% group_by(sensitivity) %>% slice(c(1, n())) %>% ungroup()
curve2 <- curve2 %>% group_by(sensitivity) %>% slice(c(1, n())) %>% ungroup()
ggplot() +
geom_line(data=curve1, aes(y=sensitivity, x=1-specificity, color="Altman Z")) +
geom_line(data=curve2, aes(y=sensitivity, x=1-specificity, color="DD")) +
geom_abline(slope=1)

AUCs <- c(auc_Z2$.estimate, auc_DD2$.estimate)
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.6672852 0.6440596
df_Z2 <- test %>% filter(!is.na(diff_Z), !is.na(downgrade))
df_Z2$pred <- predict(fit_Z2, df_Z2, type="response")
curve1 <- df_Z2 %>% roc_curve(truth=downgrade, estimate=pred, event_level='second')
auc_Z2 <- df_Z2 %>% roc_auc(truth=downgrade, estimate=pred, event_level='second')
df_DD2 <- test %>% filter(!is.na(diff_DD), !is.na(downgrade))
df_DD2$pred <- predict(fit_DD2, df_DD2, type="response")
curve2 <- df_DD2 %>% roc_curve(truth=downgrade, estimate=pred, event_level='second')
auc_DD2 <- df_DD2 %>% roc_auc(truth=downgrade, estimate=pred, event_level='second')
curve1 <- curve1 %>% group_by(sensitivity) %>% slice(c(1, n())) %>% ungroup()
curve2 <- curve2 %>% group_by(sensitivity) %>% slice(c(1, n())) %>% ungroup()
ggplot() +
geom_line(data=curve1, aes(y=sensitivity, x=1-specificity, color="Altman Z")) +
geom_line(data=curve2, aes(y=sensitivity, x=1-specificity, color="DD")) +
geom_abline(slope=1)

AUCs <- c(auc_Z2$.estimate, auc_DD2$.estimate)
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.6464 0.5904
fit_comb <- glm(downgrade ~ diff_Z + diff_DD, data=train, family=binomial)
summary(fit_comb)
Call:
glm(formula = downgrade ~ diff_Z + diff_DD, family = binomial,
data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.1511 -0.4244 -0.4230 -0.3739 2.8181
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.36899 0.06457 -36.689 < 2e-16 ***
diff_Z 0.02886 0.04289 0.673 0.501
diff_DD -0.26787 0.04306 -6.220 4.97e-10 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 1913.6 on 3177 degrees of freedom
Residual deviance: 1871.0 on 3175 degrees of freedom
AIC: 1877
Number of Fisher Scoring iterations: 5
library(margins)
fit_comb %>%
margins() %>%
summary()
df_comb <- test %>% filter(!is.na(diff_DD), !is.na(diff_Z), !is.na(downgrade))
df_comb$pred <- predict(fit_comb, df_comb, type="response")
curve3 <- df_comb %>% roc_curve(truth=downgrade, estimate=pred, event_level='second')
auc_comb <- df_comb %>% roc_auc(truth=downgrade, estimate=pred, event_level='second')
curve3 <- curve3 %>% group_by(sensitivity) %>% slice(c(1, n())) %>% ungroup()
ggplot() +
geom_line(data=curve1, aes(y=sensitivity, x=1-specificity, color="Altman Z")) +
geom_line(data=curve2, aes(y=sensitivity, x=1-specificity, color="DD")) +
geom_line(data=curve3, aes(y=sensitivity, x=1-specificity, color="Combined")) +
geom_abline(slope=1)

AUCs <- c(auc_Z2$.estimate, auc_DD2$.estimate, auc_comb$.estimate)
names(AUCs) <- c("Z", "DD", "Combined")
AUCs
Z DD Combined
0.6464000 0.5904000 0.5959385
LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gNSINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpkYXRlOiAiIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KTm90ZSB0aGF0IHRoZSBkaXJlY3RvcmllcyB1c2VkIHRvIHN0b3JlIGRhdGEgYXJlIGxpa2VseSBkaWZmZXJlbnQgb24geW91ciBjb21wdXRlciwgYW5kIHN1Y2ggcmVmZXJlbmNlcyB3aWxsIG5lZWQgdG8gYmUgY2hhbmdlZCBiZWZvcmUgdXNpbmcgYW55IHN1Y2ggY29kZS4NCg0KYGBge3IgaGVscGVycywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GfQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmh0bWxfZGYgPC0gZnVuY3Rpb24odGV4dCwgY29scz1OVUxMLCBjb2wxPUZBTFNFLCBmdWxsPUYpIHsNCiAgaWYoIWxlbmd0aChjb2xzKSkgew0KICAgIGNvbHM9Y29sbmFtZXModGV4dCkNCiAgfQ0KICBpZighY29sMSkgew0KICAgIGthYmxlKHRleHQsImh0bWwiLCBjb2wubmFtZXMgPSBjb2xzLCBhbGlnbiA9IGMoImwiLHJlcCgnYycsbGVuZ3RoKGNvbHMpLTEpKSkgJT4lDQogICAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsImhvdmVyIiksIGZ1bGxfd2lkdGg9ZnVsbCkNCiAgfSBlbHNlIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpICU+JQ0KICAgICAgY29sdW1uX3NwZWMoMSxib2xkPVQpDQogIH0NCn0NCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmRmIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fNS0xLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpkZl9yYXRpbmdzIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fNS0yLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpkZl9tdmUgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvU2Vzc2lvbl81LTMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCmRmX3JmIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fNS00LmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpkZl9zdG9jayA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TZXNzaW9uXzUtNS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KIyBpbml0aWFsIGNsZWFuaW5nDQojIDEwMDMzOCBpcyBhbiBvdXRsaWVyIGluIHRoZSBib25kcyBkaXN0cmlidXRpb24NCmRmIDwtIGRmICU+JSBmaWx0ZXIoYXQgPj0gMSwgcmV2dCA+PSAxLCBndmtleSAhPSAxMDAzMzgpDQoNCiMjIE1lcmdlIGluIHN0b2NrIHZhbHVlDQpkZiRkYXRlIDwtIGFzLkRhdGUoZGYkZGF0YWRhdGUpDQpkZl9tdmUgPC0gZGZfbXZlICU+JQ0KICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0YWRhdGUpLA0KICAgICAgICAgbXZlID0gY3NobyAqIHByY2NfZikgJT4lDQogIHJlbmFtZShndmtleT1HVktFWSkNCg0KZGYgPC0gbGVmdF9qb2luKGRmLCBkZl9tdmVbLGMoImd2a2V5IiwiZGF0ZSIsIm12ZSIpXSkNCg0KZGYgPC0gZGYgJT4lDQogIGdyb3VwX2J5KGd2a2V5KSAlPiUNCiAgYXJyYW5nZShkYXRhZGF0ZSkgJT4lDQogIG11dGF0ZShiYW5rcnVwdCA9IGlmZWxzZShyb3dfbnVtYmVyKCkgPT0gbigpICYgZGxyc24gPT0gMiAmDQogICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEoZGxyc24pLCAxLCAwKSwNCiAgICAgICAgIGJhbmtydXB0X2xlYWQgPSBsZWFkKGJhbmtydXB0KSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShiYW5rcnVwdF9sZWFkKSkgJT4lDQogIG11dGF0ZShiYW5rcnVwdF9sZWFkID0gZmFjdG9yKGJhbmtydXB0X2xlYWQsIGxldmVscz1jKDAsMSkpKQ0KYGBgDQoNCmBgYHtyfQ0KDQojIENhbGN1bGF0ZSB0aGUgbWVhc3VyZXMgbmVlZGVkDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlKHdjYXBfYXQgPSB3Y2FwIC8gYXQsICAjIHgxDQogICAgICAgICByZV9hdCA9IHJlIC8gYXQsICAjIHgyDQogICAgICAgICBlYml0X2F0ID0gZWJpdCAvIGF0LCAgIyB4Mw0KICAgICAgICAgbXZlX2x0ID0gbXZlIC8gbHQsICAjIHg0DQogICAgICAgICByZXZ0X2F0ID0gcmV2dCAvIGF0KSAgIyB4NQ0KIyBjbGVhbnVwDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGxpc3QofnJlcGxhY2UoLiwgIWlzLmZpbml0ZSguKSwgTkEpKSkNCg0KIyBDYWxjdWxhdGUgdGhlIHNjb3JlDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlKFogPSAxLjIgKiB3Y2FwX2F0ICsgMS40ICogcmVfYXQgKyAzLjMgKiBlYml0X2F0ICsgMC42ICogbXZlX2x0ICsgDQogICAgICAgICAgIDAuOTk5ICogcmV2dF9hdCkNCg0KIyBDYWxjdWxhdGUgZGF0ZSBpbmZvIGZvciBtZXJnaW5nDQpkZiRkYXRlIDwtIGFzLkRhdGUoZGYkZGF0YWRhdGUpDQpkZiR5ZWFyIDwtIHllYXIoZGYkZGF0ZSkNCmRmJG1vbnRoIDwtIG1vbnRoKGRmJGRhdGUpDQpgYGANCg0KYGBge3J9DQojIGRmX3JhdGluZ3MgaGFzIHJhdGluZ3MgZGF0YSBpbiBpdA0KDQojIFJhdGluZ3MsIGluIG9yZGVyIGZyb20gd29yc3QgdG8gYmVzdA0KcmF0aW5ncyA8LSBjKCJEIiwgIkMiLCAiQ0MiLCAiQ0NDLSIsICJDQ0MiLCJDQ0MrIiwgIkItIiwgIkIiLCAiQisiLCAiQkItIiwNCiAgICAgICAgICAgICAiQkIiLCAiQkIrIiwgIkJCQi0iLCAiQkJCIiwgIkJCQisiLCAiQS0iLCAiQSIsICJBKyIsICJBQS0iLCAiQUEiLA0KICAgICAgICAgICAgICJBQSsiLCAiQUFBLSIsICJBQUEiLCAiQUFBKyIpDQojIENvbnZlcnQgc3RyaW5nIHJhdGluZ3MgKHNwbHRpY3JtKSB0byBudW1lcmljIHJhdGluZ3MNCmRmX3JhdGluZ3MkcmF0aW5nIDwtIGZhY3RvcihkZl9yYXRpbmdzJHNwbHRpY3JtLCBsZXZlbHM9cmF0aW5ncywgb3JkZXJlZD1UKQ0KDQpkZl9yYXRpbmdzJGRhdGUgPC0gYXMuRGF0ZShkZl9yYXRpbmdzJGRhdGFkYXRlKQ0KZGZfcmF0aW5ncyR5ZWFyIDwtIHllYXIoZGZfcmF0aW5ncyRkYXRlKQ0KZGZfcmF0aW5ncyRtb250aCA8LSBtb250aChkZl9yYXRpbmdzJGRhdGUpDQoNCiMgTWVyZ2UgdG9nZXRoZXIgZGF0YQ0KZGYgPC0gbGVmdF9qb2luKGRmLCBkZl9yYXRpbmdzWyxjKCJndmtleSIsICJ5ZWFyIiwgIm1vbnRoIiwgInJhdGluZyIpXSkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9Nn0NCnBsb3QgPC0gZGYgJT4lDQogIGZpbHRlcighaXMubmEoWiksICFpcy5uYShyYXRpbmcpKSAlPiUNCiAgZ3JvdXBfYnkocmF0aW5nKSAlPiUNCiAgbXV0YXRlKG1lYW5fWj1tZWFuKFosbmEucm09VCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QocmF0aW5nLCBtZWFuX1opICU+JQ0KICBnZ3Bsb3QoYWVzKHk9bWVhbl9aLCB4PXJhdGluZykpICsgDQogIGdlb21fY29sKCkgKyANCiAgeWxhYignTWVhbiBBbHRtYW4gWicpICsgeGxhYignQ3JlZGl0IHJhdGluZycpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KZ2dwbG90bHkocGxvdCkNCmBgYA0KDQpgYGB7cn0NCmRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFopLA0KICAgICAgICAgIWlzLm5hKGJhbmtydXB0KSkgJT4lDQogIGdyb3VwX2J5KGJhbmtydXB0X2xlYWQpICU+JQ0KICBtdXRhdGUobWVhbl9aPW1lYW4oWixuYS5ybT1UKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChiYW5rcnVwdF9sZWFkLCBtZWFuX1opICU+JQ0KICBodG1sX2RmKCkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9Nn0NCnBsb3QgPC0gZGYgJT4lDQogIGZpbHRlcighaXMubmEoWiksICFpcy5uYShyYXRpbmcpLCB5ZWFyID49IDIwMDApICU+JQ0KICBncm91cF9ieShyYXRpbmcpICU+JQ0KICBtdXRhdGUobWVhbl9aPW1lYW4oWixuYS5ybT1UKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChyYXRpbmcsIG1lYW5fWikgJT4lDQogIGdncGxvdChhZXMoeT1tZWFuX1osIHg9cmF0aW5nKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICB5bGFiKCdNZWFuIEFsdG1hbiBaJykgKyB4bGFiKCdDcmVkaXQgcmF0aW5nJykgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpnZ3Bsb3RseShwbG90KQ0KYGBgDQoNCmBgYHtyfQ0KZGYgJT4lDQogIGZpbHRlcighaXMubmEoWiksDQogICAgICAgICAhaXMubmEoYmFua3J1cHRfbGVhZCksDQogICAgICAgICB5ZWFyID49IDIwMDApICU+JQ0KICBncm91cF9ieShiYW5rcnVwdF9sZWFkKSAlPiUNCiAgbXV0YXRlKG1lYW5fWj1tZWFuKFosbmEucm09VCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QoYmFua3J1cHRfbGVhZCwgbWVhbl9aKSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9Rn0NCmZpdF9aIDwtIGdsbShiYW5rcnVwdF9sZWFkIH4gWiwgZGF0YT1kZiwgZmFtaWx5PWJpbm9taWFsKQ0Kc3VtbWFyeShmaXRfWikNCmBgYA0KDQpgYGB7cn0NCmx6IDwtIGRmICU+JSBmaWx0ZXIoIWlzLm5hKGJhbmtydXB0X2xlYWQpLCAhaXMubmEoWikpICU+JSBmaWx0ZXIoWiA8IDEpICU+JSBwdWxsKGJhbmtydXB0X2xlYWQpICU+JSB0YWJsZSgpDQpoeiA8LSBkZiAlPiUgZmlsdGVyKCFpcy5uYShiYW5rcnVwdF9sZWFkKSwgIWlzLm5hKFopKSAlPiUgZmlsdGVyKFogPj0gMSkgJT4lIHB1bGwoYmFua3J1cHRfbGVhZCkgJT4lIHRhYmxlKCkNCnggPC0gbWF0cml4KGMobHosIGh6KSwgbnJvdz0yKQ0Kcm93bmFtZXMoeCkgPC0gYygnTm8gYmFua3J1cHRjeScsICdCYW5rcnVwdGN5JykNCmNvbG5hbWVzKHgpIDwtIGMoJ1ogPCAxJywgJ1ogPj0gMScpDQp4DQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9RiwgZXJyb3I9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD01fQ0KbGlicmFyeSh5YXJkc3RpY2spDQpkZl9aIDwtIGRmICU+JSBmaWx0ZXIoIWlzLm5hKFopLCAhaXMubmEoYmFua3J1cHRfbGVhZCkpDQpkZl9aJHByZWQgPC0gcHJlZGljdChmaXRfWiwgZGZfWiwgdHlwZT0icmVzcG9uc2UiKQ0KZGZfWiAlPiUgcm9jX2N1cnZlKHRydXRoPWJhbmtydXB0X2xlYWQsIGVzdGltYXRlPXByZWQsIGV2ZW50X2xldmVsPSdzZWNvbmQnKSAlPiUNCiAgYXV0b3Bsb3QoKQ0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KZGZfWiAlPiUgcm9jX2N1cnZlKHRydXRoPWJhbmtydXB0X2xlYWQsDQogICAgICAgICAgICAgICAgICBlc3RpbWF0ZT1wcmVkLA0KICAgICAgICAgICAgICAgICAgZXZlbnRfbGV2ZWw9J3NlY29uZCcpICU+JQ0KICBhdXRvcGxvdCgpDQpgYGANCg0KYGBge3J9DQphdWNfWiA8LSBkZl9aICU+JSByb2NfYXVjKHRydXRoPWJhbmtydXB0X2xlYWQsIGVzdGltYXRlPXByZWQsIGV2ZW50X2xldmVsPSdzZWNvbmQnKQ0KYXVjX1oNCmBgYA0KDQpgYGB7cn0NCnNjb3JlID0gMQ0KbSA9IDANCnN0ZCA9IDENCg0KZnVuY1NoYWRlZCA8LSBmdW5jdGlvbih4LCBsb3dlcl9ib3VuZCkgew0KICAgIHkgPSBkbm9ybSh4LCBtZWFuID0gbSwgc2QgPSBzdGQpDQogICAgeVt4IDwgbG93ZXJfYm91bmRdIDwtIE5BDQogICAgcmV0dXJuKHkpDQp9DQoNCmdncGxvdChkYXRhLmZyYW1lKHggPSBjKC0zLCAzKSksIGFlcyh4ID0geCkpICsgDQogIHN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIGFyZ3MgPSBsaXN0KG1lYW4gPSBtLCBzZCA9IHN0ZCkpICsgDQogIHN0YXRfZnVuY3Rpb24oZnVuID0gZnVuY1NoYWRlZCwgYXJncyA9IGxpc3QobG93ZXJfYm91bmQgPSBzY29yZSksIA0KICAgICAgICAgICAgICAgIGdlb20gPSAiYXJlYSIsIGZpbGwgPSAnYmxhY2snLCBhbHBoYSA9IC4yKSArDQogIHNjYWxlX3hfY29udGludW91cyhuYW1lID0gIlNjb3JlIiwgYnJlYWtzID0gc2VxKC0zLCAzLCBzdGQpKSArIA0KICBnZW9tX3RleHQoZGF0YSA9IGRhdGEuZnJhbWUoeD1jKDEuNSksIHk9YygwLjA1KSksIGFlcyh4PXgsIHk9eSwgbGFiZWw9IlByb2IoZGVmYXVsdCkiLCBzaXplPTMwKSkgKyANCiAgZ2VvbV9saW5lKGRhdGEgPSBkYXRhLmZyYW1lKHg9YygxLDEpLCB5PWMoMCwwLjQpKSwgYWVzKHg9eCx5PXkpKSArIA0KICBnZW9tX3RleHQoZGF0YSA9IGRhdGEuZnJhbWUoeD1jKDEuMyksIHk9YygwLjQpKSwgYWVzKHg9eCwgeT15LCBsYWJlbD0iREQiLCBzaXplPTMwKSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBkZl9zdG9jayBpcyBhbiBhbHJlYWR5IHByZXBwZWQgY3N2IGZyb20gQ1JTUCBkYXRhDQpkZl9zdG9jayRkYXRlIDwtIGFzLkRhdGUoZGZfc3RvY2skZGF0ZSkNCmRmIDwtIGxlZnRfam9pbihkZiwgZGZfc3RvY2tbLGMoImd2a2V5IiwgImRhdGUiLCAicmV0IiwgInJldC5zZCIpXSkNCmBgYA0KDQpgYGB7cn0NCg0KZGZfcmYkZGF0ZSA8LSBhcy5EYXRlKGRmX3JmJGRhdGVmZikNCmRmX3JmJHllYXIgPC0geWVhcihkZl9yZiRkYXRlKQ0KZGZfcmYkbW9udGggPC0gbW9udGgoZGZfcmYkZGF0ZSkNCg0KZGYgPC0gbGVmdF9qb2luKGRmLCBkZl9yZlssYygieWVhciIsICJtb250aCIsICJyZiIpXSkNCg0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZShERCA9IChsb2cobXZlIC8gbHQpICsgKHJmIC0gKHJldC5zZCpzcXJ0KDI1MykpXjIgLyAyKSkgLw0KICAgICAgICAgICAgICAocmV0LnNkKnNxcnQoMjUzKSkpDQojIENsZWFuIHRoZSBtZWFzdXJlDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGxpc3QofnJlcGxhY2UoLiwgIWlzLmZpbml0ZSguKSwgTkEpKSkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NH0NCnBsb3QgPC0gZGYgJT4lDQogIGZpbHRlcighaXMubmEoREQpLA0KICAgICAgICAgIWlzLm5hKHJhdGluZykpICU+JQ0KICBncm91cF9ieShyYXRpbmcpICU+JQ0KICBtdXRhdGUobWVhbl9ERD1tZWFuKERELG5hLnJtPVQpLA0KICAgICAgICAgcHJvYl9kZWZhdWx0ID0gcG5vcm0oLTEgKiBtZWFuX0REKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChyYXRpbmcsIHByb2JfZGVmYXVsdCkgJT4lDQogIGdncGxvdChhZXMoeT1wcm9iX2RlZmF1bHQsIHg9cmF0aW5nKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICB5bGFiKCdQcm9iYWJpbGl0eSBvZiBkZWZhdWx0JykgKyB4bGFiKCdDcmVkaXQgcmF0aW5nJykgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpnZ3Bsb3RseShwbG90KQ0KYGBgDQoNCmBgYHtyfQ0KZGYgJT4lDQogIGZpbHRlcighaXMubmEoREQpLA0KICAgICAgICAgIWlzLm5hKGJhbmtydXB0X2xlYWQpKSAlPiUNCiAgZ3JvdXBfYnkoYmFua3J1cHRfbGVhZCkgJT4lDQogIG11dGF0ZShtZWFuX0REPW1lYW4oREQsIG5hLnJtPVQpLA0KICAgICAgICAgcHJvYl9kZWZhdWx0ID0NCiAgICAgICAgICAgcG5vcm0oLTEgKiBtZWFuX0REKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChiYW5rcnVwdF9sZWFkLCBtZWFuX0RELA0KICAgICAgICAgcHJvYl9kZWZhdWx0KSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTR9DQpwbG90IDwtIGRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKEREKSwNCiAgICAgICAgICFpcy5uYShyYXRpbmcpLA0KICAgICAgICAgeWVhciA+PSAyMDAwKSAlPiUNCiAgZ3JvdXBfYnkocmF0aW5nKSAlPiUNCiAgbXV0YXRlKG1lYW5fREQ9bWVhbihERCxuYS5ybT1UKSwNCiAgICAgICAgIHByb2JfZGVmYXVsdCA9IHBub3JtKC0xICogbWVhbl9ERCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QocmF0aW5nLCBwcm9iX2RlZmF1bHQpICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cHJvYl9kZWZhdWx0LCB4PXJhdGluZykpICsgDQogIGdlb21fY29sKCkgKyANCiAgeWxhYignUHJvYmFiaWxpdHkgb2YgZGVmYXVsdCcpICsgeGxhYignQ3JlZGl0IHJhdGluZycpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KZ2dwbG90bHkocGxvdCkNCmBgYA0KDQpgYGB7cn0NCmRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKEREKSwNCiAgICAgICAgICFpcy5uYShiYW5rcnVwdF9sZWFkKSwNCiAgICAgICAgIHllYXIgPj0gMjAwMCkgJT4lDQogIGdyb3VwX2J5KGJhbmtydXB0X2xlYWQpICU+JQ0KICBtdXRhdGUobWVhbl9ERD1tZWFuKERELCBuYS5ybT1UKSwNCiAgICAgICAgIHByb2JfZGVmYXVsdCA9DQogICAgICAgICAgIHBub3JtKC0xICogbWVhbl9ERCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QoYmFua3J1cHRfbGVhZCwgbWVhbl9ERCwNCiAgICAgICAgIHByb2JfZGVmYXVsdCkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KZml0X0REIDwtIGdsbShiYW5rcnVwdF9sZWFkIH4gREQsIGRhdGE9ZGYsIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZml0X0REKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTMuNX0NCmRmX0REIDwtIGRmICU+JSBmaWx0ZXIoIWlzLm5hKFopLCAhaXMubmEoYmFua3J1cHRfbGVhZCkpDQpkZl9ERCRwcmVkIDwtIHByZWRpY3QoZml0X0RELCBkZl9ERCwgdHlwZT0icmVzcG9uc2UiKQ0KZGZfREQgJT4lIHJvY19jdXJ2ZSh0cnV0aD1iYW5rcnVwdF9sZWFkLCBlc3RpbWF0ZT1wcmVkLCBldmVudF9sZXZlbD0nc2Vjb25kJykgJT4lDQogIGF1dG9wbG90KCkNCmRmX0REICU+JSByb2NfYXVjKHRydXRoPWJhbmtydXB0X2xlYWQsIGVzdGltYXRlPXByZWQsIGV2ZW50X2xldmVsPSdzZWNvbmQnKQ0KYGBgDQoNCmBgYHtyfQ0KI0FVQw0KYXVjX0REIDwtIGRmX0REICU+JSByb2NfYXVjKHRydXRoPWJhbmtydXB0X2xlYWQsIGVzdGltYXRlPXByZWQsIGV2ZW50X2xldmVsPSdzZWNvbmQnKQ0KQVVDcyA8LSBjKGF1Y19aJC5lc3RpbWF0ZSwgYXVjX0REJC5lc3RpbWF0ZSkNCm5hbWVzKEFVQ3MpIDwtIGMoIloiLCAiREQiKQ0KQVVDcw0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBjYWxjdWxhdGUgZG93bmdyYWRlDQpkZiA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoZ3ZrZXkpICU+JQ0KICBhcnJhbmdlKGRhdGUpICU+JQ0KICBtdXRhdGUoZG93bmdyYWRlID0gZmFjdG9yKGlmZWxzZShsZWFkKHJhdGluZykgPCByYXRpbmcsMSwwKSwgbGV2ZWxzPWMoMCwxKSksDQogICAgICAgICBkaWZmX1ogPSBaIC0gbGFnKFopLA0KICAgICAgICAgZGlmZl9ERCA9IEREIC0gbGFnKEREKSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQoNCiMgdHJhaW5pbmcgc2FtcGxlDQp0cmFpbiA8LSBkZiAlPiUgZmlsdGVyKHllYXIgPCAyMDE0LCAhaXMubmEoZGlmZl9aKSwgIWlzLm5hKGRpZmZfREQpLCAhaXMubmEoZG93bmdyYWRlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgeWVhciA+IDE5ODUpDQp0ZXN0IDwtIGRmICU+JSBmaWx0ZXIoeWVhciA+PSAyMDE0LCAhaXMubmEoZGlmZl9aKSwgIWlzLm5hKGRpZmZfREQpLCAhaXMubmEoZG93bmdyYWRlKSkNCg0KIyBnbG1zDQpmaXRfWjIgPC0gZ2xtKGRvd25ncmFkZSB+IGRpZmZfWiwgZGF0YT10cmFpbiwgZmFtaWx5PWJpbm9taWFsKQ0KZml0X0REMiA8LSBnbG0oZG93bmdyYWRlIH4gZGlmZl9ERCwgZGF0YT10cmFpbiwgZmFtaWx5PWJpbm9taWFsKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShmaXRfWjIpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGZpdF9ERDIpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTh9DQpkZl9aMiA8LSB0cmFpbiAlPiUgZmlsdGVyKCFpcy5uYShkaWZmX1opLCAhaXMubmEoZG93bmdyYWRlKSkNCmRmX1oyJHByZWQgPC0gcHJlZGljdChmaXRfWjIsIGRmX1oyLCB0eXBlPSJyZXNwb25zZSIpDQpjdXJ2ZTEgPC0gZGZfWjIgJT4lIHJvY19jdXJ2ZSh0cnV0aD1kb3duZ3JhZGUsIGVzdGltYXRlPXByZWQsIGV2ZW50X2xldmVsPSdzZWNvbmQnKQ0KYXVjX1oyIDwtIGRmX1oyICU+JSByb2NfYXVjKHRydXRoPWRvd25ncmFkZSwgZXN0aW1hdGU9cHJlZCwgZXZlbnRfbGV2ZWw9J3NlY29uZCcpDQoNCmRmX0REMiA8LSB0cmFpbiAlPiUgZmlsdGVyKCFpcy5uYShkaWZmX0REKSwgIWlzLm5hKGRvd25ncmFkZSkpDQpkZl9ERDIkcHJlZCA8LSBwcmVkaWN0KGZpdF9ERDIsIGRmX0REMiwgdHlwZT0icmVzcG9uc2UiKQ0KY3VydmUyIDwtIGRmX0REMiAlPiUgcm9jX2N1cnZlKHRydXRoPWRvd25ncmFkZSwgZXN0aW1hdGU9cHJlZCwgZXZlbnRfbGV2ZWw9J3NlY29uZCcpDQphdWNfREQyIDwtIGRmX0REMiAlPiUgcm9jX2F1Yyh0cnV0aD1kb3duZ3JhZGUsIGVzdGltYXRlPXByZWQsIGV2ZW50X2xldmVsPSdzZWNvbmQnKQ0KDQpjdXJ2ZTEgPC0gY3VydmUxICU+JSBncm91cF9ieShzZW5zaXRpdml0eSkgJT4lIHNsaWNlKGMoMSwgbigpKSkgJT4lIHVuZ3JvdXAoKQ0KY3VydmUyIDwtIGN1cnZlMiAlPiUgZ3JvdXBfYnkoc2Vuc2l0aXZpdHkpICU+JSBzbGljZShjKDEsIG4oKSkpICU+JSB1bmdyb3VwKCkNCg0KZ2dwbG90KCkgKw0KICBnZW9tX2xpbmUoZGF0YT1jdXJ2ZTEsIGFlcyh5PXNlbnNpdGl2aXR5LCB4PTEtc3BlY2lmaWNpdHksIGNvbG9yPSJBbHRtYW4gWiIpKSArIA0KICBnZW9tX2xpbmUoZGF0YT1jdXJ2ZTIsIGFlcyh5PXNlbnNpdGl2aXR5LCB4PTEtc3BlY2lmaWNpdHksIGNvbG9yPSJERCIpKSArIA0KICBnZW9tX2FibGluZShzbG9wZT0xKQ0KYGBgDQoNCmBgYHtyfQ0KQVVDcyA8LSBjKGF1Y19aMiQuZXN0aW1hdGUsIGF1Y19ERDIkLmVzdGltYXRlKQ0KbmFtZXMoQVVDcykgPC0gYygiWiIsICJERCIpDQpBVUNzDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NX0NCmRmX1oyIDwtIHRlc3QgJT4lIGZpbHRlcighaXMubmEoZGlmZl9aKSwgIWlzLm5hKGRvd25ncmFkZSkpDQpkZl9aMiRwcmVkIDwtIHByZWRpY3QoZml0X1oyLCBkZl9aMiwgdHlwZT0icmVzcG9uc2UiKQ0KY3VydmUxIDwtIGRmX1oyICU+JSByb2NfY3VydmUodHJ1dGg9ZG93bmdyYWRlLCBlc3RpbWF0ZT1wcmVkLCBldmVudF9sZXZlbD0nc2Vjb25kJykNCmF1Y19aMiA8LSBkZl9aMiAlPiUgcm9jX2F1Yyh0cnV0aD1kb3duZ3JhZGUsIGVzdGltYXRlPXByZWQsIGV2ZW50X2xldmVsPSdzZWNvbmQnKQ0KDQpkZl9ERDIgPC0gdGVzdCAlPiUgZmlsdGVyKCFpcy5uYShkaWZmX0REKSwgIWlzLm5hKGRvd25ncmFkZSkpDQpkZl9ERDIkcHJlZCA8LSBwcmVkaWN0KGZpdF9ERDIsIGRmX0REMiwgdHlwZT0icmVzcG9uc2UiKQ0KY3VydmUyIDwtIGRmX0REMiAlPiUgcm9jX2N1cnZlKHRydXRoPWRvd25ncmFkZSwgZXN0aW1hdGU9cHJlZCwgZXZlbnRfbGV2ZWw9J3NlY29uZCcpDQphdWNfREQyIDwtIGRmX0REMiAlPiUgcm9jX2F1Yyh0cnV0aD1kb3duZ3JhZGUsIGVzdGltYXRlPXByZWQsIGV2ZW50X2xldmVsPSdzZWNvbmQnKQ0KDQpjdXJ2ZTEgPC0gY3VydmUxICU+JSBncm91cF9ieShzZW5zaXRpdml0eSkgJT4lIHNsaWNlKGMoMSwgbigpKSkgJT4lIHVuZ3JvdXAoKQ0KY3VydmUyIDwtIGN1cnZlMiAlPiUgZ3JvdXBfYnkoc2Vuc2l0aXZpdHkpICU+JSBzbGljZShjKDEsIG4oKSkpICU+JSB1bmdyb3VwKCkNCg0KZ2dwbG90KCkgKw0KICBnZW9tX2xpbmUoZGF0YT1jdXJ2ZTEsIGFlcyh5PXNlbnNpdGl2aXR5LCB4PTEtc3BlY2lmaWNpdHksIGNvbG9yPSJBbHRtYW4gWiIpKSArIA0KICBnZW9tX2xpbmUoZGF0YT1jdXJ2ZTIsIGFlcyh5PXNlbnNpdGl2aXR5LCB4PTEtc3BlY2lmaWNpdHksIGNvbG9yPSJERCIpKSArIA0KICBnZW9tX2FibGluZShzbG9wZT0xKQ0KYGBgDQoNCmBgYHtyfQ0KQVVDcyA8LSBjKGF1Y19aMiQuZXN0aW1hdGUsIGF1Y19ERDIkLmVzdGltYXRlKQ0KbmFtZXMoQVVDcykgPC0gYygiWiIsICJERCIpDQpBVUNzDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9Rn0NCmZpdF9jb21iIDwtIGdsbShkb3duZ3JhZGUgfiBkaWZmX1ogKyBkaWZmX0RELCBkYXRhPXRyYWluLCBmYW1pbHk9Ymlub21pYWwpDQpzdW1tYXJ5KGZpdF9jb21iKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShtYXJnaW5zKQ0KZml0X2NvbWIgJT4lDQogIG1hcmdpbnMoKSAlPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NX0NCmRmX2NvbWIgPC0gdGVzdCAlPiUgZmlsdGVyKCFpcy5uYShkaWZmX0REKSwgIWlzLm5hKGRpZmZfWiksICFpcy5uYShkb3duZ3JhZGUpKQ0KZGZfY29tYiRwcmVkIDwtIHByZWRpY3QoZml0X2NvbWIsIGRmX2NvbWIsIHR5cGU9InJlc3BvbnNlIikNCmN1cnZlMyA8LSBkZl9jb21iICU+JSByb2NfY3VydmUodHJ1dGg9ZG93bmdyYWRlLCBlc3RpbWF0ZT1wcmVkLCBldmVudF9sZXZlbD0nc2Vjb25kJykNCmF1Y19jb21iIDwtIGRmX2NvbWIgJT4lIHJvY19hdWModHJ1dGg9ZG93bmdyYWRlLCBlc3RpbWF0ZT1wcmVkLCBldmVudF9sZXZlbD0nc2Vjb25kJykNCg0KY3VydmUzIDwtIGN1cnZlMyAlPiUgZ3JvdXBfYnkoc2Vuc2l0aXZpdHkpICU+JSBzbGljZShjKDEsIG4oKSkpICU+JSB1bmdyb3VwKCkNCg0KZ2dwbG90KCkgKw0KICBnZW9tX2xpbmUoZGF0YT1jdXJ2ZTEsIGFlcyh5PXNlbnNpdGl2aXR5LCB4PTEtc3BlY2lmaWNpdHksIGNvbG9yPSJBbHRtYW4gWiIpKSArIA0KICBnZW9tX2xpbmUoZGF0YT1jdXJ2ZTIsIGFlcyh5PXNlbnNpdGl2aXR5LCB4PTEtc3BlY2lmaWNpdHksIGNvbG9yPSJERCIpKSArDQogIGdlb21fbGluZShkYXRhPWN1cnZlMywgYWVzKHk9c2Vuc2l0aXZpdHksIHg9MS1zcGVjaWZpY2l0eSwgY29sb3I9IkNvbWJpbmVkIikpICsgDQogIGdlb21fYWJsaW5lKHNsb3BlPTEpDQoNCkFVQ3MgPC0gYyhhdWNfWjIkLmVzdGltYXRlLCBhdWNfREQyJC5lc3RpbWF0ZSwgYXVjX2NvbWIkLmVzdGltYXRlKQ0KbmFtZXMoQVVDcykgPC0gYygiWiIsICJERCIsICJDb21iaW5lZCIpDQpBVUNzDQpgYGANCg0K