ggplot2 cheatsheet.pdf
图形分层语法

图形语法(grammar of graphic, gg):将任何图形精确地描述为以下要素的组合,使绘图过程模块化
- 图形架构
- 数据集 (data)
- 从变量到坐标轴的映射 (mapping)
- 几何图层 (geom)
- 统计变换 (stat)
- 位置调整 (position)
- 比例尺 (scale)
- 坐标系模式 (coordinate system)
- 分面模式 (facet)
- 图形美化
- 绘图区外部的标签 (labs)
- 绘图区内部的文本标签和说明 (geom_label, geom_text)
- 参考线、箭头和方框
- 注释 (annotations)
- 图例 (guides)
- 主题 (theme)
- 颜色
按照上述模板,可以使用代码构建能够想象到的任何图形
qplot()
quickplot 语法糖 qplot(x = cty, y = hwy, data = mpg, geom = "point")
last_plot()
return the last plot
绑定数据集
ggplot(data=NULL, mapping=aes(), ...,environment=parent.frame())
ggplot()创建了一个坐标系,可以通过 geom_function 函数向其中添加需要的各种图层。
# aes()定义了如何将数据中的变量映射为图形属性
# 本例中,将mtcars中的wt变量映射为x轴,将mpg变量映射为y轴
p <- mtcars %>%
ggplot(aes(x = wt, y = mpg)) +
geom_point(pch = 17, color = "blue", size = 2) +
geom_smooth(
method = "lm",
se = FALSE,
color = "red",
linetype = 2
) +
labs(
title = "Automobile",
x = "Weight",
y = "Miles Per Gallon"
)
ggplotly(p) %>%
saveWidget("./Figures/Automobile.html",
selfcontained = F,
libdir = "lib"
)
p

几何图层
常用几何图层
下表列出了比较常见的几何函数,以及经常使用的选项。 
单变量
# 直方图
data(singer, package = "lattice")
p <- ggplot(singer, aes(x = height)) +
geom_histogram()
# geom_histgrom()函数在y变量没有指定时默认对x轴变量统计频数
ggplotly(p) %>%
saveWidget("./Figures/Histogram.html",
selfcontained = F,
libdir = "lib"
)
p

双变量
# 箱线图
p <- ggplot(singer, aes(x = voice.part, y = height)) +
geom_boxplot()
ggplotly(p) %>%
saveWidget("./Figures/Boxplot.html",
selfcontained = F,
libdir = "lib"
)
p

几何图层的参数

data(car::Salaries)
p <- ggplot(Salaries, aes(x = rank, y = salary)) +
geom_boxplot(
fill = "cornflowerblue", # fill为填充色,color为外框色
color = "black"
) +
geom_point(position = "jitter", color = "blue", alpha = .5) + # jitter将重叠数据打散,alpha表示透明度
geom_rug(side = "l", color = "black") # rug为轴须图,左侧,黑色
ggplotly(p) %>%
saveWidget("./Figures/Boxplot2.html",
selfcontained = F,
libdir = "lib"
)
p

拟合曲线
data(Salaries, package = "car")
# 默认拟合和线性拟合
p1 <- ggplot(data = Salaries, aes(x = yrs.since.phd, y = salary)) +
geom_point() +
geom_smooth()
p2 <- ggplot(data = Salaries, aes(x = yrs.since.phd, y = salary)) +
geom_point() +
geom_smooth(method = lm)
grid.arrange(p1, p2, ncol = 2)

# 按性别拟合二次多项式线性回归
p3 <- ggplot(
data = Salaries,
aes(
x = yrs.since.phd, y = salary,
linetype = sex, shape = sex, color = sex
)
) +
geom_smooth(method = lm, formula = y ~ poly(x, 2), se = FALSE, size = 1) +
geom_point(size = 1)
ggplotly(p3) %>%
saveWidget("./Figures/smooth.html",
selfcontained = F,
libdir = "lib"
)
p3

多个几何图层的叠加
当几何图层组合形成新类型的图时,ggplot2包的真正力量就会得到展现:实现多图层的叠加。
此时不宜转换为 plotly 对象,显示会出错。
data(singer, package = "lattice")
ggplot(singer, aes(x = voice.part, y = height)) +
geom_violin(fill = "lightblue") +
geom_boxplot(fill = "lightgreen", width = .2)

映射
ggplot() 和几何图层函数的 mapping 参数 aes()
用以定义从变量到图形属性(表示数据信息的可视化属性)的映射。
要注意,图形属性不仅包括数据点的大小、形状和颜色等,数据点的x轴位置和y轴位置本身也是图形属性。
全局与局部映射
如果将一组映射传递给ggplot()函数。这些映射会作为全局映射应用到图中的每个几何图层中;如果将映射放在几何图层函数中,则会成为局部映射,会扩展或覆盖全局映射,仅对该图层有效。这样一来,我们就可以在不同的图层中显示不同的图形属性。
# x和y的映射是全局的,两个几何图层共享之
p <- ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
# color的映射是局部的,仅对这一句有效
geom_point(mapping = aes(color = class)) +
geom_smooth()
ggplotly(p) %>%
saveWidget("./Figures/smooth1.html",
selfcontained = F,
libdir = "lib"
)
p

全局与局部数据
甚至可以为不同的图层指定不同的数据。
p <- ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point(mapping = aes(color = class)) +
# geom_smooth()中的局部数据参数覆盖了ggplot()中的全局数据参数
geom_smooth(data = filter(mpg, class == "subcompact"), se = FALSE)
ggplotly(p) %>%
saveWidget("./Figures/smooth2.html",
selfcontained = F,
libdir = "lib"
)
p

映射与分组
几何图层函数的mapping参数/aes()函数是一个分配分组变量的自然的地方。最基本的分组语法是将分组变量映射为图形的group属性,即aes(group = variable)
,也可以将分组变量映射为其他更具视觉效果的图形属性(图中对象的可视化属性)。
在函数aes()中将图形属性名称和变量名称关联起来后,ggplot2会自动为每个变量值分配唯一的图形属性水平,这个过程称为scale变换。ggplot2还会添加一个图例,以表示图形属性水平和变量值之间的对应关系。
data(Salaries, package = "car")
# 在aes()中用rank分组,将rank映射为图形的fill(填充色)属性
p <- ggplot(data = Salaries, aes(x = salary, fill = rank)) +
geom_density(alpha = .3)
ggplotly(p) %>%
saveWidget("./Figures/group1.html",
selfcontained = F,
libdir = "lib"
)
p

# 根据sex和rank分组,共分为6组
p <- ggplot(
Salaries,
aes(
x = yrs.since.phd, y = salary,
color = rank, shape = sex
)
) +
geom_point()
ggplotly(p) %>%
saveWidget("./Figures/group2.html",
selfcontained = F,
libdir = "lib"
)
p

统计变换:ggplot2的绘图原理
很多图形绘制的是数据集的原数据,比如散点图;另外一些图形则可以绘制那些计算出的新数据,比如条形图。
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut))

绘图时用来计算新数据的算法称为stat(statistical transformation,统计变换)。通过查看stat参数的默认值,你可以知道几何图层函数使用了哪种统计变换。
geom_bar(
mapping = NULL, data = NULL,
stat = "count", position = "stack", ...,
width = NULL, binwidth = NULL, na.rm = FALSE, show.legend = NA, inherit.aes = TRUE
)
geom_bar
中stat参数的默认值是count,这说明geom_bar()
使用stat_count()
函数进行统计变换(stat_count()
在文档中与geom_bar()
位于同一页)。想要找出由统计变换计算出的变量,可以查看帮助文件中的 Computed variables 节,它告诉我们stat_count()
会计算出两个新变量:count和prop
通常来说,几何图层函数和统计变换函数可以互换使用。可以这样做的原因是,每个几何图层函数都有一个默认统计变换,每个统计变换函数都有一个默认几何图层。例如,可以使用stat_count()
替换geom_bar()
来重新生成前面那张图。
ggplot(data = diamonds) +
stat_count(mapping = aes(x = cut))

显式使用统计变换函数的3个原因如下。
覆盖默认的统计变换
demo <- tribble(
~a, ~b,
"bar_1", 20,
"bar_2", 30,
"bar_3", 40
)
# 将geom_bar()函数的统计变换从默认的'count'改为'identity',这样就可以将条形的高度映射为y轴变量的初始值。
ggplot(data = demo) +
geom_bar(mapping = aes(x = a, y = b), stat = "identity")

覆盖从统计变换生成的变量到图形属性的默认映射
# 显示一张表示比例(而不是计数)的条形图。
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut, y = ..prop.., group = 1))

在代码中强调统计变换
可以使用stat_summary()函数将人们的注意力吸引到你计算出的那些摘要统计量上。stat_summary()函数为x的每个唯一值计算y值的摘要统计。
ggplot(data = diamonds) +
stat_summary(mapping = aes(x = cut, y = depth), fun.ymin = min, fun.ymax = max, fun.y = median)

ggplot2提供了20多个统计变换以供你使用。每个统计变换都是一个函数,因此你可以按照通用方式获得帮助,例如?stat_bin。如果想要查看全部的统计变换,可以使用ggplot2速查表。
位置调整
identity |
不调整位置 |
dodge |
并列放置,如簇状条形图 |
stack |
堆叠放置 |
fill |
堆叠且显示比例 |
jitter |
加入随机扰动防止图形重合 |
# position='identity',将每个对象直接显示在图中,原地不动,不调整位置。
ggplot(diamonds, aes(x = cut, fill = clarity)) + # 没有映射y轴,geom_bar()默认计数
geom_bar(position = "identity")

ggplot(diamonds, aes(x = cut, color = clarity)) +
geom_bar(fill = NA, position = "identity")

# position="fill",堆叠显示比例(计数后求比例)
ggplot(diamonds) +
geom_bar(aes(cut, fill = clarity), position = "fill")

# position="dodge",并列显示
ggplot(diamonds) +
geom_bar(aes(cut, fill = clarity), position = "dodge")

# position="stack",堆叠显示
ggplot(diamonds) +
geom_bar(aes(cut, fill = clarity), position = "stack")

# position="jitter",添加随机扰动,防止散点重叠
ggplot(data = mpg) +
geom_point(aes(displ, hwy))

ggplot(data = mpg) +
geom_point(aes(displ, hwy), position = "jitter")

# 或用函数geom_jitter()实现
ggplot(data = mpg, aes(x = displ, y = hwy)) +
geom_point() +
geom_jitter()

Scale 比例尺
scale 的命名模式和用途
输入以下代码时:
ggplot(mpg, aes(displ, hwy)) +
geom_point(aes(color = class))
ggplot2 会自动在后台为代码添加默认 scale
ggplot(mpg, aes(displ, hwy)) +
geom_point(aes(color = class)) +
scale_x_continuous() +
scale_y_continuous() +
scale_color_discrete()
scale 的命名模式:scale_图形属性_类型。scale 的常用类型有连续型、离散型、日期时间型或日期型。
基于以下两种原因,需要使用非默认 scale:
- 你或许要对默认 scale 的一些参数进行调整。例如,当想要修改坐标轴刻度或图例中的项目标签时,就需要进行这些调整。
- 你或许想要整体替换默认 scale,从而使用一种完全不同的算法。因为你对数据更加了解,所以使用与默认方式不同的 scale 通常能达到更好的效果。
scale*()
函数族
最常用
scale_*_continuous()
scale_*_discrete()
scale_*_identity()
scale_*_manual()
日期时间
scale_*_date()
scale_*_datetime()
scale*()
的参数
limits
接收一个向量,取其两端作为一个范围。仅当数据的坐标在这个范围内时,才会在图中显示。
本质上相当于对数据进行了筛选,取了子集,此外的数据会变成 NA
df <- data.frame(x = 1:3, y = 1:3)
base <- ggplot(df, aes(x, y)) +
geom_point(size = 4)
base

base + scale_x_continuous(limits = c(1.5, 2.5))

base + scale_x_continuous(limits = c(0, 4))

由于更改坐标轴范围是非常常见的,因此 ggplot2 提供了语法糖:xlim()
, ylim()
, lims()


base + lims(x = c(0,4),y = c(4,0))

坐标轴范围一般比指定的稍大一点,以确保数据不会出现在图的边缘。scale*()
中的expand=c(0,0)
参数可以使得坐标轴范围恰好等于指定边界。
breaks
接收一个向量作为集合,规定显示轴须和标签的刻度
如果不希望显示任何轴须,可以规定 breaks=NULL
minor_breaks
ggplot 默认的图形中含有网格线,除了显示轴须处会生成网格线,两个轴须之间也会生成一条次要网格线。
minor_breaks
参数可以规定生成次要网格线的位置。如果不希望显示次要网格线,可以规定 minor_breaks=NULL
labels
labels
设置要显示的文本标签
# breaks最常见用途是替换默认的刻度
data(Salaries, package = "car")
ggplot(data = Salaries, aes(x = rank, y = salary, fill = sex)) +
geom_boxplot() +
scale_x_discrete(
breaks = c("AsstProf", "AssocProf", "Prof"),
labels = c(
"Assistant\nProfessor",
"Associate\nProfessor",
"Full\nProfessor"
)
) +
scale_y_continuous(
breaks = c(50000, 100000, 150000, 200000),
labels = c("$50K", "$100K", "$150K", "$200K")
) +
labs(title = "Faculty Salary by Rank and Sex", x = "", y = "")

还可以将labels设置为 NULL,这样可以不显示刻度标签,对于地图或不适合展示数值的图表来说,这种方式是非常有用的。
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
scale_x_continuous(breaks = NULL, labels = NULL) +
scale_y_continuous(breaks = NULL, labels = NULL)

date_breaks
和date_labels
日期型和日期时间型 scale 的两个参数
date_labels
接受一个格式说明,说明的形式与 parse_datetime() 函数中的相同
date_breaks
(示例中没有出现)接收类似“2 weeks”或“1 month”这样的字符串,每隔这么长时间标识一个刻度
# 使用 breaks 的另一种情况:数据点相对较少,又想要强调观测的确切位置
# 例如,以下图形展示了每位美国总统任期的开始时间和结束时间:
presidential %>%
mutate(id = 33 + row_number()) %>%
ggplot(aes(start, id)) +
geom_point() +
geom_segment(aes(xend = end, yend = id)) + # geom_segment()绘制箭头
scale_x_date(
name = NULL,
breaks = presidential$start,
date_labels = "'%y"
)

trans
对 scale 进行函数变换,可选值均为字符串,常用的包括 exp, log, log10, logit, pow10, probit, reciprocal, sqrt 等。
其中最常用的一些,有专门的 scale 函数(语法糖),如 scale_x_log10()
, scale_x_sqrt()
, scale_x_reverse()
ggplot(diamonds, aes(carat, price)) +
geom_bin2d()

ggplot(diamonds, aes(carat, price)) +
geom_bin2d() +
scale_x_log10() +
scale_y_log10()

color and fill scale
将变量映射为颜色时,默认以一种非常均匀的方式在色环上选择颜色;如果不喜欢,可以设置颜色 scale 进行替换
discrete scale
可以设置为某套调色板,用RColorBrewer::display.brewer.all()
可查看所有的调色板
scale_color_brewer()
和scale_color_grey()
对应 color 属性
scale_fill_brewer()
和scale_fill_grey()
对应 fill 属性
# 常用的一种配色方式是使用ColorBrewer标度。
# 以下的两幅图非常相似
# 但右边图中(Set1调色板)的红色和绿色的对比更加强烈
# 即使患有红绿色盲症的人也可以区别出来
p1 <- ggplot(mpg, aes(displ, hwy)) +
geom_point(aes(color = drv))
p2 <- ggplot(mpg, aes(displ, hwy)) +
geom_point(aes(color = drv)) +
scale_color_brewer(palette = "Set1")
grid.arrange(p1, p2, ncol = 2)

# 如果只有很少几种颜色,可以再添加一种形状映射
# 虽然有些冗余,但这样可以确保图表在黑白方式下也可以为人所理解
ggplot(mpg, aes(displ, hwy)) +
geom_point(aes(color = drv, shape = drv)) +
scale_color_brewer(palette = "Set1")

continuous scale
连续渐变色,有几种函数(单色、双色、三色):
如果想区分位于平均值以上和以下的点,可以使用scale_color_gradient2()
函数。
另一个可以选用的函数是由viridis包提供的scale_color_viridis()
,它是对ColorBrewer分类标度的一种连续模拟。以下是来自于 viridis 使用指南中的一个示例:
df <- tibble(
x = rnorm(10000),
y = rnorm(10000)
)
p1 <- ggplot(df, aes(x, y)) +
geom_hex() +
coord_fixed()
p2 <- ggplot(df, aes(x, y)) +
geom_hex() +
viridis::scale_fill_viridis() +
coord_fixed()
grid.arrange(p1, p2, ncol = 2)

手动指定颜色
通过scale_color_manual()
,可以自己指定颜色
presidential %>%
mutate(id = 33 + row_number()) %>%
ggplot(aes(start, id, color = party)) +
geom_point() +
geom_segment(aes(xend = end, yend = id)) +
scale_colour_manual(values = c(Republican = "red", Democratic = "blue"))

坐标系
交换 x、y 轴 coord_flip()
当想要绘制水平箱线图时,这非常有用。它也非常适合变量长标签的使用,否则在x轴上不重叠地安排好它们是非常困难的。
ggplot(mpg, mapping = aes(x = class, y = hwy)) +
geom_boxplot()

ggplot(mpg, mapping = aes(x = class, y = hwy)) +
geom_boxplot() +
coord_flip()

为地图设置合适的纵横比 coord_quickmap()
nz <- map_data("nz")
ggplot(nz, aes(long, lat, group = group)) +
geom_polygon(fill = "white", color = "black")

ggplot(nz, aes(long, lat, group = group)) +
geom_polygon(fill = "white", color = "black") +
coord_quickmap()

极坐标系 coord_polar()
bar <- ggplot(data = diamonds) +
geom_bar(aes(x = cut, fill = cut),
show.legend = FALSE, width = 1
) +
theme(aspect.ratio = 1) +
labs(x = NULL, y = NULL)
bar + coord_flip()


coord_cartesian()
:设置坐标轴显示的范围
三种方法:
# 比较两种方法
mpg %>%
filter(displ >= 5, displ <= 7, hwy >= 10, hwy <= 30) %>%
ggplot(aes(displ, hwy)) +
geom_point(aes(color = class)) +
geom_smooth() # 数据取子集使拟合曲线的置信区间变得很宽

ggplot(mpg, mapping = aes(displ, hwy)) +
geom_point(aes(color = class)) +
geom_smooth() +
coord_cartesian(xlim = c(5, 7), ylim = c(10, 30))

# 在两张图形中使用相同的scale
suv <- mpg %>% filter(class == "suv")
compact <- mpg %>% filter(class == "compact")
range_displ <- range(mpg$displ)
range_hwy <- range(mpg$hwy)
# color使用全集的scale,保证颜色的一致性
col_scale <- scale_color_discrete(limits = unique(mpg$drv))
ggplot(suv, aes(displ, hwy, color = drv)) +
geom_point() +
col_scale +
coord_cartesian(xlim = range_displ, ylim = range_hwy)

ggplot(compact, aes(displ, hwy, color = drv)) +
geom_point() +
col_scale +
coord_cartesian(xlim = range_displ, ylim = range_hwy)

分面模式
分面模式(faceting)把几幅图拼接在一起,特别适合比较分类变量。
# 刻面图
data(singer, package = "lattice")
ggplot(data = singer, aes(x = height)) +
geom_histogram() +
facet_wrap(~voice.part, ncol = 4) # voice.part变量共有8个值,因列数为4,行数自动为2。先行后列排列

# 分组+刻面图+散点图
p <- ggplot(
Salaries,
aes(x = yrs.since.phd, y = salary, color = rank, shape = rank)
) +
geom_point() +
facet_grid(. ~ sex)
p

p <- ggplot(
Salaries,
aes(x = yrs.since.phd, y = salary, color = rank, shape = rank)
) +
geom_point() +
facet_grid(sex ~ .)
p

# 分组+刻面+密度图
data(singer, package = "lattice")
p <- ggplot(data = singer, aes(x = height, fill = voice.part)) +
geom_density() +
facet_grid(voice.part ~ .)
ggplotly(p) %>%
saveWidget("./Figures/facet-density.html",
selfcontained = F,
libdir = "lib"
)
p

LS0tDQp0aXRsZTogImdncGxvdDItMSINCnN1YnRpdGxlOiAn5YaF5a655p625p6EJw0KYXV0aG9yOiAiSHVtb29uIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNzczogLi4vY3NzL3N0eWxlLmNzcw0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICB0aGVtZTogdW5pdGVkDQogICAgaGlnaGxpZ2h0OiBoYWRkb2NrDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB5ZXMNCiAgICAgIHNtb290aF9zY3JvbGw6IHllcw0KZG9jdW1lbnRjbGFzczogY3RleGFydA0KY2xhc3NvcHRpb246IGh5cGVycmVmLA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9DQpzb3VyY2UoIi4uL1JtYXJrZG93bi10ZW1wbGF0ZS9SbWFya2Rvd25fY29uZmlnLlIiKQ0KDQojIyBnbG9iYWwgb3B0aW9ucyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICB3aWR0aCA9IGNvbmZpZyR3aWR0aCwNCiAgZmlnLndpZHRoID0gY29uZmlnJGZpZy53aWR0aCwNCiAgZmlnLmFzcCA9IGNvbmZpZyRmaWcuYXNwLA0KICBvdXQud2lkdGggPSBjb25maWckb3V0LndpZHRoLA0KICBmaWcuYWxpZ24gPSBjb25maWckZmlnLmFsaWduLA0KICBmaWcucGF0aCA9IGNvbmZpZyRmaWcucGF0aCwNCiAgZmlnLnNob3cgPSBjb25maWckZmlnLnNob3csDQogIHdhcm4gPSBjb25maWckd2FybiwNCiAgd2FybmluZyA9IGNvbmZpZyR3YXJuaW5nLA0KICBtZXNzYWdlID0gY29uZmlnJG1lc3NhZ2UsDQogIGVjaG8gPSBjb25maWckZWNobywgDQogIGV2YWwgPSBjb25maWckZXZhbCwgDQogIHRpZHkgPSBjb25maWckdGlkeSwgDQogIGNvbW1lbnQgPSBjb25maWckY29tbWVudCwgDQogIGNvbGxhcHNlID0gY29uZmlnJGNvbGxhcHNlLCANCiAgY2FjaGUgPSBjb25maWckY2FjaGUsDQogIGNhY2hlLmNvbW1lbnRzID0gY29uZmlnJGNhY2hlLmNvbW1lbnRzLA0KICBhdXRvZGVwID0gY29uZmlnJGF1dG9kZXANCikNCg0KIyMgdXNlIG5lY2Vzc2FyeSBwYWNrYWdlcyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShodG1sd2lkZ2V0cykNCg0KcGFjbWFuOjpwX2xvYWQoDQogIGx1YnJpZGF0ZSwNCiAgZ2d0aGVtZXMsDQogIGdyaWRFeHRyYSwgIyDmi7zmjqUgZ2dwbG90MiDlm77lg4/nmoTljIUNCiAgc2hvd3RleHQsDQogIGNhciwgDQogIHJlYWR4bCwgDQogIHJlc2hhcGUyLCANCiAgUkNvbG9yQnJld2VyDQopDQoNCiMjIHBsb3R0aW5nID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQojIOWMheWQq+WbvueahOS7o+eggeWdl+mcgOimgWZpZy5zaG93dGV4dCA9IFRSVUXpgInpobkNCnNob3d0ZXh0X2F1dG8oZW5hYmxlID0gVFJVRSkNCmBgYA0KDQo8YSBocmVmPSIuLi9wZGYvY2hlYXRzaGVldC1nZ3Bsb3QucGRmIj4qZ2dwbG90MiBjaGVhdHNoZWV0LnBkZio8L2E+DQoNCjxvYmplY3QgZGF0YT0iLi4vcGRmL2NoZWF0c2hlZXQtZ2dwbG90LnBkZiIgdHlwZT0iYXBwbGljYXRpb24vcGRmIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIj48L29iamVjdD4NCg0KIyMg5Zu+5b2i5YiG5bGC6K+t5rOVDQoNCjxpbWcgc3JjPSJodHRwOi8vaHVtb29uLWltYWdlLWhvc3Rpbmctc2VydmljZS5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vaW1nL3R5cG9yYS8yMDIyLzIwMjIwMzIzLWdyYXBoaWMtZ3JhbW1hci5wbmciIGFsdD0iMjAyMjAzMjMtZ3JhcGhpYy1ncmFtbWFyIiBzdHlsZT0iem9vbTogNTAlOyIgLz4NCg0K5Zu+5b2i6K+t5rOV77yIZ3JhbW1hciBvZiBncmFwaGljLCBnZ++8ie+8muWwhuS7u+S9leWbvuW9oueyvuehruWcsOaPj+i/sOS4uuS7peS4i+imgee0oOeahOe7hOWQiO+8jOS9v+e7mOWbvui/h+eoi+aooeWdl+WMlg0KDQoNCi0g5Zu+5b2i5p625p6EDQogIC0g5pWw5o2u6ZuGIChkYXRhKQ0KICAtIOS7juWPmOmHj+WIsOWdkOagh+i9tOeahOaYoOWwhCAobWFwcGluZykNCiAgLSDlh6DkvZXlm77lsYIgKGdlb20pDQogIC0g57uf6K6h5Y+Y5o2iIChzdGF0KQ0KICAtIOS9jee9ruiwg+aVtCAocG9zaXRpb24pDQogIC0g5q+U5L6L5bC6IChzY2FsZSkNCiAgLSDlnZDmoIfns7vmqKHlvI8gKGNvb3JkaW5hdGUgc3lzdGVtKQ0KICAtIOWIhumdouaooeW8jyAoZmFjZXQpDQotIOWbvuW9oue+juWMlg0KICAtIOe7mOWbvuWMuuWklumDqOeahOagh+etviAobGFicykNCiAgLSDnu5jlm77ljLrlhoXpg6jnmoTmlofmnKzmoIfnrb7lkozor7TmmI4gKGdlb21fbGFiZWwsIGdlb21fdGV4dCkNCiAgLSDlj4LogIPnur/jgIHnrq3lpLTlkozmlrnmoYYNCiAgLSDms6jph4ogKGFubm90YXRpb25zKQ0KICAtIOWbvuS+iyAoZ3VpZGVzKQ0KICAtIOS4u+mimCAodGhlbWUpDQogIC0g6aKc6ImyDQoNCg0K5oyJ54Wn5LiK6L+w5qih5p2/77yM5Y+v5Lul5L2/55So5Luj56CB5p6E5bu66IO95aSf5oOz6LGh5Yiw55qE5Lu75L2V5Zu+5b2iDQoNCiMjIyBxcGxvdCgpDQoNCnF1aWNrcGxvdCDor63ms5Xns5YgYHFwbG90KHggPSBjdHksIHkgPSBod3ksIGRhdGEgPSBtcGcsIGdlb20gPSAicG9pbnQiKWANCg0KIyMjIGxhc3RfcGxvdCgpDQoNCnJldHVybiB0aGUgbGFzdCBwbG90DQoNCg0KIyMg57uR5a6a5pWw5o2u6ZuGDQoNCmBnZ3Bsb3QoZGF0YT1OVUxMLCBtYXBwaW5nPWFlcygpLCAuLi4sZW52aXJvbm1lbnQ9cGFyZW50LmZyYW1lKCkpYCANCg0KZ2dwbG90KCnliJvlu7rkuobkuIDkuKrlnZDmoIfns7vvvIzlj6/ku6XpgJrov4cgZ2VvbV9mdW5jdGlvbiDlh73mlbDlkJHlhbbkuK3mt7vliqDpnIDopoHnmoTlkITnp43lm77lsYLjgIINCmBgYHtyfQ0KIyBhZXMoKeWumuS5ieS6huWmguS9leWwhuaVsOaNruS4reeahOWPmOmHj+aYoOWwhOS4uuWbvuW9ouWxnuaApw0KIyDmnKzkvovkuK3vvIzlsIZtdGNhcnPkuK3nmoR3dOWPmOmHj+aYoOWwhOS4unjovbTvvIzlsIZtcGflj5jph4/mmKDlsITkuLp56L20DQpwIDwtIG10Y2FycyAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gd3QsIHkgPSBtcGcpKSArDQogIGdlb21fcG9pbnQocGNoID0gMTcsIGNvbG9yID0gImJsdWUiLCBzaXplID0gMikgKw0KICBnZW9tX3Ntb290aCgNCiAgICBtZXRob2QgPSAibG0iLA0KICAgIHNlID0gRkFMU0UsDQogICAgY29sb3IgPSAicmVkIiwNCiAgICBsaW5ldHlwZSA9IDINCiAgKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQXV0b21vYmlsZSIsDQogICAgeCA9ICJXZWlnaHQiLA0KICAgIHkgPSAiTWlsZXMgUGVyIEdhbGxvbiINCiAgKQ0KDQpnZ3Bsb3RseShwKSAlPiUNCiAgc2F2ZVdpZGdldCgiLi9GaWd1cmVzL0F1dG9tb2JpbGUuaHRtbCIsDQogICAgc2VsZmNvbnRhaW5lZCA9IEYsDQogICAgbGliZGlyID0gImxpYiINCiAgKQ0KDQpwDQpgYGANCg0KIyMg5Yeg5L2V5Zu+5bGCDQoNCiMjIyDluLjnlKjlh6DkvZXlm77lsYINCg0K5LiL6KGo5YiX5Ye65LqG5q+U6L6D5bi46KeB55qE5Yeg5L2V5Ye95pWw77yM5Lul5Y+K57uP5bi45L2/55So55qE6YCJ6aG544CCDQohW2EgcGxhY2Vob2xkZXIgb2YgYSBwaWN0dXJlXShodHRwOi8vaHVtb29uLWltYWdlLWhvc3Rpbmctc2VydmljZS5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vaW1nL3R5cG9yYS8yMDIyLzIwMjIwMzIzLeWHoOS9leWbvuWxguWHveaVsC5wbmcpDQoNCiMjIyMg5Z+65pys5Zu+5b2iDQoNCiFbZ3JhcGhpY2FsLXByaW1pdGl2ZXNdKGh0dHA6Ly9odW1vb24taW1hZ2UtaG9zdGluZy1zZXJ2aWNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS9pbWcvdHlwb3JhLzIwMjEwNi9ncmFwaGljYWwtcHJpbWl0aXZlcy5wbmcpDQoNCiMjIyMg6L6F5Yqp57q/DQoNCiFbbGluZS1zZWdtZW50c10oaHR0cDovL2h1bW9vbi1pbWFnZS1ob3N0aW5nLXNlcnZpY2Uub3NzLWNuLWJlaWppbmcuYWxpeXVuY3MuY29tL2ltZy90eXBvcmEvMjAyMTA2L2xpbmUtc2VnbWVudHMucG5nKQ0KDQojIyMjIOWNleWPmOmHjw0KDQohW29uZS12YXJpYWJsZV0oaHR0cDovL2h1bW9vbi1pbWFnZS1ob3N0aW5nLXNlcnZpY2Uub3NzLWNuLWJlaWppbmcuYWxpeXVuY3MuY29tL2ltZy90eXBvcmEvMjAyMTA2L29uZS12YXJpYWJsZS5wbmcpDQoNCmBgYHtyfQ0KIyDnm7Tmlrnlm74NCmRhdGEoc2luZ2VyLCBwYWNrYWdlID0gImxhdHRpY2UiKQ0KDQpwIDwtIGdncGxvdChzaW5nZXIsIGFlcyh4ID0gaGVpZ2h0KSkgKw0KICBnZW9tX2hpc3RvZ3JhbSgpDQojIGdlb21faGlzdGdyb20oKeWHveaVsOWcqHnlj5jph4/msqHmnInmjIflrprml7bpu5jorqTlr7l46L205Y+Y6YeP57uf6K6h6aKR5pWwDQoNCmdncGxvdGx5KHApICU+JQ0KICBzYXZlV2lkZ2V0KCIuL0ZpZ3VyZXMvSGlzdG9ncmFtLmh0bWwiLA0KICAgIHNlbGZjb250YWluZWQgPSBGLA0KICAgIGxpYmRpciA9ICJsaWIiDQogICkNCg0KcA0KYGBgDQoNCiMjIyMg5Y+M5Y+Y6YePDQoNCiFbdHdvLXZhcmlhYmxlc10oaHR0cDovL2h1bW9vbi1pbWFnZS1ob3N0aW5nLXNlcnZpY2Uub3NzLWNuLWJlaWppbmcuYWxpeXVuY3MuY29tL2ltZy90eXBvcmEvMjAyMTA2L3R3by12YXJpYWJsZXMucG5nKQ0KDQpgYGB7cn0NCiMg566x57q/5Zu+DQpwIDwtIGdncGxvdChzaW5nZXIsIGFlcyh4ID0gdm9pY2UucGFydCwgeSA9IGhlaWdodCkpICsNCiAgZ2VvbV9ib3hwbG90KCkNCg0KZ2dwbG90bHkocCkgJT4lDQogIHNhdmVXaWRnZXQoIi4vRmlndXJlcy9Cb3hwbG90Lmh0bWwiLA0KICAgIHNlbGZjb250YWluZWQgPSBGLA0KICAgIGxpYmRpciA9ICJsaWIiDQogICkNCg0KcA0KYGBgDQoNCiMjIyMg5LiJ5Y+Y6YePDQoNCiFbdGhyZWUtdmFyaWFibGVzXShodHRwOi8vaHVtb29uLWltYWdlLWhvc3Rpbmctc2VydmljZS5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vaW1nL3R5cG9yYS8yMDIxMDYvdGhyZWUtdmFyaWFibGVzLnBuZykNCg0KIyMjIOWHoOS9leWbvuWxgueahOWPguaVsA0KDQohW2EgcGxhY2Vob2xkZXIgb2YgYSBwaWN0dXJlXShodHRwOi8vaHVtb29uLWltYWdlLWhvc3Rpbmctc2VydmljZS5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vaW1nL3R5cG9yYS8yMDIyLzIwMjIwMzIzLeWHoOS9leWHveaVsOeahOmAiemhuV8xLnBuZykNCiFbYSBwbGFjZWhvbGRlciBvZiBhIHBpY3R1cmVdKGh0dHA6Ly9odW1vb24taW1hZ2UtaG9zdGluZy1zZXJ2aWNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS9pbWcvdHlwb3JhLzIwMjIvMjAyMjAzMjMt5Yeg5L2V5Ye95pWw55qE6YCJ6aG5XzIucG5nKQ0KDQpgYGB7cn0NCmRhdGEoY2FyOjpTYWxhcmllcykNCnAgPC0gZ2dwbG90KFNhbGFyaWVzLCBhZXMoeCA9IHJhbmssIHkgPSBzYWxhcnkpKSArDQogIGdlb21fYm94cGxvdCgNCiAgICBmaWxsID0gImNvcm5mbG93ZXJibHVlIiwgIyBmaWxs5Li65aGr5YWF6Imy77yMY29sb3LkuLrlpJbmoYboibINCiAgICBjb2xvciA9ICJibGFjayINCiAgKSArDQogIGdlb21fcG9pbnQocG9zaXRpb24gPSAiaml0dGVyIiwgY29sb3IgPSAiYmx1ZSIsIGFscGhhID0gLjUpICsgIyBqaXR0ZXLlsIbph43lj6DmlbDmja7miZPmlaPvvIxhbHBoYeihqOekuumAj+aYjuW6pg0KICBnZW9tX3J1ZyhzaWRlID0gImwiLCBjb2xvciA9ICJibGFjayIpICMgcnVn5Li66L206aG75Zu+77yM5bem5L6n77yM6buR6ImyDQoNCmdncGxvdGx5KHApICU+JQ0KICBzYXZlV2lkZ2V0KCIuL0ZpZ3VyZXMvQm94cGxvdDIuaHRtbCIsDQogICAgc2VsZmNvbnRhaW5lZCA9IEYsDQogICAgbGliZGlyID0gImxpYiINCiAgKQ0KDQpwDQpgYGANCg0KDQojIyMg5ouf5ZCI5puy57q/DQoNCiFbYSBwbGFjZWhvbGRlciBvZiBhIHBpY3R1cmVdKGh0dHA6Ly9odW1vb24taW1hZ2UtaG9zdGluZy1zZXJ2aWNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS9pbWcvdHlwb3JhLzIwMjIvMjAyMjAzMjMtZ2VvbV9zbW9vdGgucG5nKQ0KDQpgYGB7ciwgZmlnLnNob3c9J2FzaXMnfQ0KZGF0YShTYWxhcmllcywgcGFja2FnZSA9ICJjYXIiKQ0KDQojIOm7mOiupOaLn+WQiOWSjOe6v+aAp+aLn+WQiA0KcDEgPC0gZ2dwbG90KGRhdGEgPSBTYWxhcmllcywgYWVzKHggPSB5cnMuc2luY2UucGhkLCB5ID0gc2FsYXJ5KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpDQpwMiA8LSBnZ3Bsb3QoZGF0YSA9IFNhbGFyaWVzLCBhZXMoeCA9IHlycy5zaW5jZS5waGQsIHkgPSBzYWxhcnkpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtKQ0KZ3JpZC5hcnJhbmdlKHAxLCBwMiwgbmNvbCA9IDIpDQoNCiMg5oyJ5oCn5Yir5ouf5ZCI5LqM5qyh5aSa6aG55byP57q/5oCn5Zue5b2SDQpwMyA8LSBnZ3Bsb3QoDQogIGRhdGEgPSBTYWxhcmllcywNCiAgYWVzKA0KICAgIHggPSB5cnMuc2luY2UucGhkLCB5ID0gc2FsYXJ5LA0KICAgIGxpbmV0eXBlID0gc2V4LCBzaGFwZSA9IHNleCwgY29sb3IgPSBzZXgNCiAgKQ0KKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtLCBmb3JtdWxhID0geSB+IHBvbHkoeCwgMiksIHNlID0gRkFMU0UsIHNpemUgPSAxKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDEpDQoNCmdncGxvdGx5KHAzKSAlPiUNCiAgc2F2ZVdpZGdldCgiLi9GaWd1cmVzL3Ntb290aC5odG1sIiwNCiAgICBzZWxmY29udGFpbmVkID0gRiwNCiAgICBsaWJkaXIgPSAibGliIg0KICApDQoNCnAzDQpgYGANCg0KIyMjIOWkmuS4quWHoOS9leWbvuWxgueahOWPoOWKoA0KDQoqKuW9k+WHoOS9leWbvuWxgue7hOWQiOW9ouaIkOaWsOexu+Wei+eahOWbvuaXtu+8jGdncGxvdDLljIXnmoTnnJ/mraPlipvph4/lsLHkvJrlvpfliLDlsZXnjrDvvJrlrp7njrDlpJrlm77lsYLnmoTlj6DliqDjgIIqKg0KDQrmraTml7bkuI3lrpzovazmjaLkuLogcGxvdGx5IOWvueixoe+8jOaYvuekuuS8muWHuumUmeOAgg0KDQpgYGB7cn0NCmRhdGEoc2luZ2VyLCBwYWNrYWdlID0gImxhdHRpY2UiKQ0KZ2dwbG90KHNpbmdlciwgYWVzKHggPSB2b2ljZS5wYXJ0LCB5ID0gaGVpZ2h0KSkgKw0KICBnZW9tX3Zpb2xpbihmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAibGlnaHRncmVlbiIsIHdpZHRoID0gLjIpDQpgYGANCg0KIyMg5pig5bCEDQoNCmdncGxvdCgpIOWSjOWHoOS9leWbvuWxguWHveaVsOeahCBtYXBwaW5nIOWPguaVsCBgYWVzKClgIOeUqOS7peWumuS5ieS7juWPmOmHj+WIsOWbvuW9ouWxnuaAp++8iOihqOekuuaVsOaNruS/oeaBr+eahOWPr+inhuWMluWxnuaAp++8ieeahOaYoOWwhOOAgg0KDQropoHms6jmhI/vvIzlm77lvaLlsZ7mgKfkuI3ku4XljIXmi6zmlbDmja7ngrnnmoTlpKflsI/jgIHlvaLnirblkozpopzoibLnrYnvvIzmlbDmja7ngrnnmoR46L205L2N572u5ZKMeei9tOS9jee9ruacrOi6q+S5n+aYr+WbvuW9ouWxnuaAp+OAgg0KDQojIyMg5YWo5bGA5LiO5bGA6YOo5pig5bCEDQoNCuWmguaenOWwhuS4gOe7hOaYoOWwhOS8oOmAkue7mWdncGxvdCgp5Ye95pWw44CC6L+Z5Lqb5pig5bCE5Lya5L2c5Li65YWo5bGA5pig5bCE5bqU55So5Yiw5Zu+5Lit55qE5q+P5Liq5Yeg5L2V5Zu+5bGC5Lit77yb5aaC5p6c5bCG5pig5bCE5pS+5Zyo5Yeg5L2V5Zu+5bGC5Ye95pWw5Lit77yM5YiZ5Lya5oiQ5Li65bGA6YOo5pig5bCE77yM5Lya5omp5bGV5oiW6KaG55uW5YWo5bGA5pig5bCE77yM5LuF5a+56K+l5Zu+5bGC5pyJ5pWI44CC6L+Z5qC35LiA5p2l77yM5oiR5Lus5bCx5Y+v5Lul5Zyo5LiN5ZCM55qE5Zu+5bGC5Lit5pi+56S65LiN5ZCM55qE5Zu+5b2i5bGe5oCn44CCDQoNCmBgYHtyfQ0KIyB45ZKMeeeahOaYoOWwhOaYr+WFqOWxgOeahO+8jOS4pOS4quWHoOS9leWbvuWxguWFseS6q+S5iw0KcCA8LSBnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKSArDQogICMgY29sb3LnmoTmmKDlsITmmK/lsYDpg6jnmoTvvIzku4Xlr7nov5nkuIDlj6XmnInmlYgNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKGNvbG9yID0gY2xhc3MpKSArDQogIGdlb21fc21vb3RoKCkNCg0KZ2dwbG90bHkocCkgJT4lDQogIHNhdmVXaWRnZXQoIi4vRmlndXJlcy9zbW9vdGgxLmh0bWwiLA0KICAgIHNlbGZjb250YWluZWQgPSBGLA0KICAgIGxpYmRpciA9ICJsaWIiDQogICkNCg0KcA0KYGBgDQoNCiMjIyDlhajlsYDkuI7lsYDpg6jmlbDmja4NCg0K55Sa6Iez5Y+v5Lul5Li65LiN5ZCM55qE5Zu+5bGC5oyH5a6a5LiN5ZCM55qE5pWw5o2u44CCDQpgYGB7cn0NCnAgPC0gZ2dwbG90KGRhdGEgPSBtcGcsIG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoY29sb3IgPSBjbGFzcykpICsNCiAgIyBnZW9tX3Ntb290aCgp5Lit55qE5bGA6YOo5pWw5o2u5Y+C5pWw6KaG55uW5LqGZ2dwbG90KCnkuK3nmoTlhajlsYDmlbDmja7lj4LmlbANCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IGZpbHRlcihtcGcsIGNsYXNzID09ICJzdWJjb21wYWN0IiksIHNlID0gRkFMU0UpDQoNCmdncGxvdGx5KHApICU+JQ0KICBzYXZlV2lkZ2V0KCIuL0ZpZ3VyZXMvc21vb3RoMi5odG1sIiwNCiAgICBzZWxmY29udGFpbmVkID0gRiwNCiAgICBsaWJkaXIgPSAibGliIg0KICApDQoNCnANCmBgYA0KDQojIyMg5pig5bCE5LiO5YiG57uEDQoNCuWHoOS9leWbvuWxguWHveaVsOeahG1hcHBpbmflj4LmlbAvYWVzKCnlh73mlbDmmK/kuIDkuKrliIbphY3liIbnu4Tlj5jph4/nmoToh6rnhLbnmoTlnLDmlrnjgILmnIDln7rmnKznmoTliIbnu4Tor63ms5XmmK/lsIbliIbnu4Tlj5jph4/mmKDlsITkuLrlm77lvaLnmoRncm91cOWxnuaAp++8jOWNs2BhZXMoZ3JvdXAgPSB2YXJpYWJsZSlg77yM5Lmf5Y+v5Lul5bCG5YiG57uE5Y+Y6YeP5pig5bCE5Li65YW25LuW5pu05YW36KeG6KeJ5pWI5p6c55qE5Zu+5b2i5bGe5oCnKOWbvuS4reWvueixoeeahOWPr+inhuWMluWxnuaApynjgIINCg0K5Zyo5Ye95pWwYWVzKCnkuK3lsIblm77lvaLlsZ7mgKflkI3np7Dlkozlj5jph4/lkI3np7DlhbPogZTotbfmnaXlkI7vvIxnZ3Bsb3Qy5Lya6Ieq5Yqo5Li65q+P5Liq5Y+Y6YeP5YC85YiG6YWN5ZSv5LiA55qE5Zu+5b2i5bGe5oCn5rC05bmz77yM6L+Z5Liq6L+H56iL56ew5Li6c2NhbGXlj5jmjaLjgIJnZ3Bsb3Qy6L+Y5Lya5re75Yqg5LiA5Liq5Zu+5L6L77yM5Lul6KGo56S65Zu+5b2i5bGe5oCn5rC05bmz5ZKM5Y+Y6YeP5YC85LmL6Ze055qE5a+55bqU5YWz57O744CCDQoNCmBgYHtyfQ0KZGF0YShTYWxhcmllcywgcGFja2FnZSA9ICJjYXIiKQ0KDQojIOWcqGFlcygp5Lit55SocmFua+WIhue7hO+8jOWwhnJhbmvmmKDlsITkuLrlm77lvaLnmoRmaWxs77yI5aGr5YWF6Imy77yJ5bGe5oCnDQpwIDwtIGdncGxvdChkYXRhID0gU2FsYXJpZXMsIGFlcyh4ID0gc2FsYXJ5LCBmaWxsID0gcmFuaykpICsNCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gLjMpDQoNCmdncGxvdGx5KHApICU+JQ0KICBzYXZlV2lkZ2V0KCIuL0ZpZ3VyZXMvZ3JvdXAxLmh0bWwiLA0KICAgIHNlbGZjb250YWluZWQgPSBGLA0KICAgIGxpYmRpciA9ICJsaWIiDQogICkNCg0KcA0KDQojIOagueaNrnNleOWSjHJhbmvliIbnu4TvvIzlhbHliIbkuLo257uEDQpwIDwtIGdncGxvdCgNCiAgU2FsYXJpZXMsDQogIGFlcygNCiAgICB4ID0geXJzLnNpbmNlLnBoZCwgeSA9IHNhbGFyeSwNCiAgICBjb2xvciA9IHJhbmssIHNoYXBlID0gc2V4DQogICkNCikgKw0KICBnZW9tX3BvaW50KCkNCg0KZ2dwbG90bHkocCkgJT4lDQogIHNhdmVXaWRnZXQoIi4vRmlndXJlcy9ncm91cDIuaHRtbCIsDQogICAgc2VsZmNvbnRhaW5lZCA9IEYsDQogICAgbGliZGlyID0gImxpYiINCiAgKQ0KDQpwDQpgYGANCg0KDQojIyDnu5/orqHlj5jmjaLvvJpnZ3Bsb3Qy55qE57uY5Zu+5Y6f55CGDQoNCuW+iOWkmuWbvuW9oue7mOWItueahOaYr+aVsOaNrumbhueahOWOn+aVsOaNru+8jOavlOWmguaVo+eCueWbvu+8m+WPpuWkluS4gOS6m+WbvuW9ouWImeWPr+S7pee7mOWItumCo+S6m+iuoeeul+WHuueahOaWsOaVsOaNru+8jOavlOWmguadoeW9ouWbvuOAgg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gY3V0KSkNCmBgYA0KDQoNCioq57uY5Zu+5pe255So5p2l6K6h566X5paw5pWw5o2u55qE566X5rOV56ew5Li6c3RhdO+8iHN0YXRpc3RpY2FsIHRyYW5zZm9ybWF0aW9u77yM57uf6K6h5Y+Y5o2i77yJ44CCKirpgJrov4fmn6XnnItzdGF05Y+C5pWw55qE6buY6K6k5YC877yM5L2g5Y+v5Lul55+l6YGT5Yeg5L2V5Zu+5bGC5Ye95pWw5L2/55So5LqG5ZOq56eN57uf6K6h5Y+Y5o2i44CCDQoNCmBgYHt9DQpnZW9tX2JhcigNCiAgbWFwcGluZyA9IE5VTEwsIGRhdGEgPSBOVUxMLCANCiAgc3RhdCA9ICJjb3VudCIsIHBvc2l0aW9uID0gInN0YWNrIiwgLi4uLCANCiAgd2lkdGggPSBOVUxMLCBiaW53aWR0aCA9IE5VTEwsIG5hLnJtID0gRkFMU0UsIHNob3cubGVnZW5kID0gTkEsIGluaGVyaXQuYWVzID0gVFJVRQ0KKQ0KYGBgDQoNCmBnZW9tX2JhcmDkuK1zdGF05Y+C5pWw55qE6buY6K6k5YC85pivY291bnTvvIzov5nor7TmmI5gZ2VvbV9iYXIoKWDkvb/nlKhgc3RhdF9jb3VudCgpYOWHveaVsOi/m+ihjOe7n+iuoeWPmOaNou+8iGBzdGF0X2NvdW50KClg5Zyo5paH5qGj5Lit5LiOYGdlb21fYmFyKClg5L2N5LqO5ZCM5LiA6aG177yJ44CC5oOz6KaB5om+5Ye655Sx57uf6K6h5Y+Y5o2i6K6h566X5Ye655qE5Y+Y6YeP77yM5Y+v5Lul5p+l55yL5biu5Yqp5paH5Lu25Lit55qEIENvbXB1dGVkIHZhcmlhYmxlcyDoioLvvIzlroPlkYror4nmiJHku6xgc3RhdF9jb3VudCgpYOS8muiuoeeul+WHuuS4pOS4quaWsOWPmOmHj++8mmNvdW505ZKMcHJvcA0KDQrpgJrluLjmnaXor7TvvIzlh6DkvZXlm77lsYLlh73mlbDlkoznu5/orqHlj5jmjaLlh73mlbDlj6/ku6XkupLmjaLkvb/nlKjjgILlj6/ku6Xov5nmoLflgZrnmoTljp/lm6DmmK/vvIzmr4/kuKrlh6DkvZXlm77lsYLlh73mlbDpg73mnInkuIDkuKrpu5jorqTnu5/orqHlj5jmjaLvvIzmr4/kuKrnu5/orqHlj5jmjaLlh73mlbDpg73mnInkuIDkuKrpu5jorqTlh6DkvZXlm77lsYLjgILkvovlpoLvvIzlj6/ku6Xkvb/nlKhgc3RhdF9jb3VudCgpYOabv+aNomBnZW9tX2JhcigpYOadpemHjeaWsOeUn+aIkOWJjemdoumCo+W8oOWbvuOAgg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsNCiAgc3RhdF9jb3VudChtYXBwaW5nID0gYWVzKHggPSBjdXQpKQ0KYGBgDQoNCuaYvuW8j+S9v+eUqOe7n+iuoeWPmOaNouWHveaVsOeahDPkuKrljp/lm6DlpoLkuIvjgIINCg0KIyMjIOimhueblum7mOiupOeahOe7n+iuoeWPmOaNog0KYGBge3J9DQpkZW1vIDwtIHRyaWJibGUoDQogIH5hLCB+YiwNCiAgImJhcl8xIiwgMjAsDQogICJiYXJfMiIsIDMwLA0KICAiYmFyXzMiLCA0MA0KKQ0KDQojIOWwhmdlb21fYmFyKCnlh73mlbDnmoTnu5/orqHlj5jmjaLku47pu5jorqTnmoQnY291bnQn5pS55Li6J2lkZW50aXR5J++8jOi/meagt+WwseWPr+S7peWwhuadoeW9oueahOmrmOW6puaYoOWwhOS4unnovbTlj5jph4/nmoTliJ3lp4vlgLzjgIINCmdncGxvdChkYXRhID0gZGVtbykgKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBhLCB5ID0gYiksIHN0YXQgPSAiaWRlbnRpdHkiKQ0KYGBgDQoNCiMjIyDopobnm5bku47nu5/orqHlj5jmjaLnlJ/miJDnmoTlj5jph4/liLDlm77lvaLlsZ7mgKfnmoTpu5jorqTmmKDlsIQNCmBgYHtyfQ0KIyDmmL7npLrkuIDlvKDooajnpLrmr5TkvovvvIjogIzkuI3mmK/orqHmlbDvvInnmoTmnaHlvaLlm77jgIINCmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gY3V0LCB5ID0gLi5wcm9wLi4sIGdyb3VwID0gMSkpDQpgYGANCg0KIyMjIOWcqOS7o+eggeS4reW8uuiwg+e7n+iuoeWPmOaNog0K5Y+v5Lul5L2/55Soc3RhdF9zdW1tYXJ5KCnlh73mlbDlsIbkurrku6znmoTms6jmhI/lipvlkLjlvJXliLDkvaDorqHnrpflh7rnmoTpgqPkupvmkZjopoHnu5/orqHph4/kuIrjgIJzdGF0X3N1bW1hcnkoKeWHveaVsOS4unjnmoTmr4/kuKrllK/kuIDlgLzorqHnrpd55YC855qE5pGY6KaB57uf6K6h44CCDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsNCiAgc3RhdF9zdW1tYXJ5KG1hcHBpbmcgPSBhZXMoeCA9IGN1dCwgeSA9IGRlcHRoKSwgZnVuLnltaW4gPSBtaW4sIGZ1bi55bWF4ID0gbWF4LCBmdW4ueSA9IG1lZGlhbikNCmBgYA0KDQpnZ3Bsb3Qy5o+Q5L6b5LqGMjDlpJrkuKrnu5/orqHlj5jmjaLku6XkvpvkvaDkvb/nlKjjgILmr4/kuKrnu5/orqHlj5jmjaLpg73mmK/kuIDkuKrlh73mlbDvvIzlm6DmraTkvaDlj6/ku6XmjInnhafpgJrnlKjmlrnlvI/ojrflvpfluK7liqnvvIzkvovlpoI/c3RhdF9iaW7jgILlpoLmnpzmg7PopoHmn6XnnIvlhajpg6jnmoTnu5/orqHlj5jmjaLvvIzlj6/ku6Xkvb/nlKhnZ3Bsb3Qy6YCf5p+l6KGo44CCDQoNCiMjIOS9jee9ruiwg+aVtA0KDQpwb3NpdGlvbuWPguaVsHzlkKvkuYkNCi0tLS0tLS18LS0tLS0NCmlkZW50aXR5fOS4jeiwg+aVtOS9jee9rg0KZG9kZ2V85bm25YiX5pS+572u77yM5aaC57CH54q25p2h5b2i5Zu+DQpzdGFja3zloIblj6DmlL7nva4NCmZpbGx85aCG5Y+g5LiU5pi+56S65q+U5L6LDQpqaXR0ZXJ85Yqg5YWl6ZqP5py65omw5Yqo6Ziy5q2i5Zu+5b2i6YeN5ZCIDQoNCg0KYGBge3J9DQojIHBvc2l0aW9uPSdpZGVudGl0eScs5bCG5q+P5Liq5a+56LGh55u05o6l5pi+56S65Zyo5Zu+5Lit77yM5Y6f5Zyw5LiN5Yqo77yM5LiN6LCD5pW05L2N572u44CCDQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyh4ID0gY3V0LCBmaWxsID0gY2xhcml0eSkpICsgIyDmsqHmnInmmKDlsIR56L2077yMZ2VvbV9iYXIoKem7mOiupOiuoeaVsA0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJpZGVudGl0eSIpDQoNCmdncGxvdChkaWFtb25kcywgYWVzKHggPSBjdXQsIGNvbG9yID0gY2xhcml0eSkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9IE5BLCBwb3NpdGlvbiA9ICJpZGVudGl0eSIpDQpgYGANCg0KYGBge3J9DQojIHBvc2l0aW9uPSJmaWxsIu+8jOWghuWPoOaYvuekuuavlOS+iyjorqHmlbDlkI7msYLmr5TkvospDQpnZ3Bsb3QoZGlhbW9uZHMpICsNCiAgZ2VvbV9iYXIoYWVzKGN1dCwgZmlsbCA9IGNsYXJpdHkpLCBwb3NpdGlvbiA9ICJmaWxsIikNCmBgYA0KDQpgYGB7cn0NCiMgcG9zaXRpb249ImRvZGdlIu+8jOW5tuWIl+aYvuekug0KZ2dwbG90KGRpYW1vbmRzKSArDQogIGdlb21fYmFyKGFlcyhjdXQsIGZpbGwgPSBjbGFyaXR5KSwgcG9zaXRpb24gPSAiZG9kZ2UiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBwb3NpdGlvbj0ic3RhY2si77yM5aCG5Y+g5pi+56S6DQpnZ3Bsb3QoZGlhbW9uZHMpICsNCiAgZ2VvbV9iYXIoYWVzKGN1dCwgZmlsbCA9IGNsYXJpdHkpLCBwb3NpdGlvbiA9ICJzdGFjayIpDQpgYGANCg0KYGBge3IsIGZpZy5zaG93PSdhc2lzJ30NCiMgcG9zaXRpb249ImppdHRlciLvvIzmt7vliqDpmo/mnLrmibDliqjvvIzpmLLmraLmlaPngrnph43lj6ANCmdncGxvdChkYXRhID0gbXBnKSArDQogIGdlb21fcG9pbnQoYWVzKGRpc3BsLCBod3kpKQ0KDQpnZ3Bsb3QoZGF0YSA9IG1wZykgKw0KICBnZW9tX3BvaW50KGFlcyhkaXNwbCwgaHd5KSwgcG9zaXRpb24gPSAiaml0dGVyIikNCg0KIyDmiJbnlKjlh73mlbBnZW9tX2ppdHRlcigp5a6e546wDQpnZ3Bsb3QoZGF0YSA9IG1wZywgYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9qaXR0ZXIoKQ0KYGBgDQoNCiMjIFNjYWxlIOavlOS+i+Wwug0KDQohW2EgcGxhY2Vob2xkZXIgb2YgYSBwaWN0dXJlXShodHRwOi8vaHVtb29uLWltYWdlLWhvc3Rpbmctc2VydmljZS5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vaW1nL3R5cG9yYS8yMDIyLzIwMjIwMzIzLXNjYWxlLnBuZykNCg0KIyMjIHNjYWxlIOeahOWRveWQjeaooeW8j+WSjOeUqOmAlA0KDQrovpPlhaXku6XkuIvku6PnoIHml7bvvJoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZ2dwbG90KG1wZywgYWVzKGRpc3BsLCBod3kpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2xhc3MpKQ0KYGBgDQoNCmdncGxvdDIg5Lya6Ieq5Yqo5Zyo5ZCO5Y+w5Li65Luj56CB5re75Yqg6buY6K6kIHNjYWxlDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZ2dwbG90KG1wZywgYWVzKGRpc3BsLCBod3kpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2xhc3MpKSArDQogIHNjYWxlX3hfY29udGludW91cygpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKCkgKw0KICBzY2FsZV9jb2xvcl9kaXNjcmV0ZSgpDQpgYGANCg0Kc2NhbGUg55qE5ZG95ZCN5qih5byP77yac2NhbGVf5Zu+5b2i5bGe5oCnX+exu+Wei+OAgnNjYWxlIOeahOW4uOeUqOexu+Wei+aciei/nue7reWei+OAgeemu+aVo+Wei+OAgeaXpeacn+aXtumXtOWei+aIluaXpeacn+Wei+OAgg0KDQrln7rkuo7ku6XkuIvkuKTnp43ljp/lm6DvvIzpnIDopoHkvb/nlKjpnZ7pu5jorqQgc2NhbGXvvJoNCg0KMS4g5L2g5oiW6K646KaB5a+56buY6K6kIHNjYWxlIOeahOS4gOS6m+WPguaVsOi/m+ihjOiwg+aVtOOAguS+i+Wmgu+8jOW9k+aDs+imgeS/ruaUueWdkOagh+i9tOWIu+W6puaIluWbvuS+i+S4reeahOmhueebruagh+etvuaXtu+8jOWwsemcgOimgei/m+ihjOi/meS6m+iwg+aVtOOAgg0KMi4g5L2g5oiW6K645oOz6KaB5pW05L2T5pu/5o2i6buY6K6kIHNjYWxl77yM5LuO6ICM5L2/55So5LiA56eN5a6M5YWo5LiN5ZCM55qE566X5rOV44CC5Zug5Li65L2g5a+55pWw5o2u5pu05Yqg5LqG6Kej77yM5omA5Lul5L2/55So5LiO6buY6K6k5pa55byP5LiN5ZCM55qEIHNjYWxlIOmAmuW4uOiDvei+vuWIsOabtOWlveeahOaViOaenOOAgg0KDQojIyMgYHNjYWxlKigpYOWHveaVsOaXjw0KDQojIyMjIOacgOW4uOeUqA0KDQotIGBzY2FsZV8qX2NvbnRpbnVvdXMoKWANCi0gYHNjYWxlXypfZGlzY3JldGUoKWANCi0gYHNjYWxlXypfaWRlbnRpdHkoKWANCi0gYHNjYWxlXypfbWFudWFsKClgDQoNCiMjIyMg5pel5pyf5pe26Ze0DQoNCi0gYHNjYWxlXypfZGF0ZSgpYA0KLSBgc2NhbGVfKl9kYXRldGltZSgpYA0KDQojIyMgYHNjYWxlKigpYOeahOWPguaVsA0KDQojIyMjIGBuYW1lYA0KDQrlrprkuYnlnZDmoIfovbTlkI3np7DvvIzlj5blgLzkuLpgTlVMTGDml7bkuI3mmL7npLoNCg0KIyMjIyBgbGltaXRzYA0KDQrmjqXmlLbkuIDkuKrlkJHph4/vvIzlj5blhbbkuKTnq6/kvZzkuLrkuIDkuKrojIPlm7TjgILku4XlvZPmlbDmja7nmoTlnZDmoIflnKjov5nkuKrojIPlm7TlhoXml7bvvIzmiY3kvJrlnKjlm77kuK3mmL7npLrjgIINCg0KKirmnKzotKjkuIrnm7jlvZPkuo7lr7nmlbDmja7ov5vooYzkuobnrZvpgInvvIzlj5bkuoblrZDpm4YqKu+8jOatpOWklueahOaVsOaNruS8muWPmOaIkCBOQQ0KDQpgYGB7cn0NCmRmIDwtIGRhdGEuZnJhbWUoeCA9IDE6MywgeSA9IDE6MykNCmJhc2UgPC0gZ2dwbG90KGRmLCBhZXMoeCwgeSkpICsNCiAgZ2VvbV9wb2ludChzaXplID0gNCkNCmJhc2UNCmJhc2UgKyBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygxLjUsIDIuNSkpDQpiYXNlICsgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNCkpDQpgYGANCg0K55Sx5LqO5pu05pS55Z2Q5qCH6L206IyD5Zu05piv6Z2e5bi45bi46KeB55qE77yM5Zug5q2kIGdncGxvdDIg5o+Q5L6b5LqG6K+t5rOV57OW77yaYHhsaW0oKWAsIGB5bGltKClgLCBgbGltcygpYA0KDQpgYGB7cn0NCmJhc2UgKyB4bGltKDAsNCkNCmJhc2UgKyB4bGltKDQsMCkNCmJhc2UgKyBsaW1zKHggPSBjKDAsNCkseSA9IGMoNCwwKSkNCmBgYA0KDQrlnZDmoIfovbTojIPlm7TkuIDoiKzmr5TmjIflrprnmoTnqI3lpKfkuIDngrnvvIzku6Xnoa7kv53mlbDmja7kuI3kvJrlh7rnjrDlnKjlm77nmoTovrnnvJjjgIJgc2NhbGUqKClg5Lit55qEYGV4cGFuZD1jKDAsMClg5Y+C5pWw5Y+v5Lul5L2/5b6X5Z2Q5qCH6L206IyD5Zu05oGw5aW9562J5LqO5oyH5a6a6L6555WM44CCDQoNCiMjIyMgYGJyZWFrc2ANCg0K5o6l5pS25LiA5Liq5ZCR6YeP5L2c5Li66ZuG5ZCI77yM6KeE5a6a5pi+56S66L206aG75ZKM5qCH562+55qE5Yi75bqmDQoNCuWmguaenOS4jeW4jOacm+aYvuekuuS7u+S9lei9tOmhu++8jOWPr+S7peinhOWumiBgYnJlYWtzPU5VTExgDQoNCiMjIyMgYG1pbm9yX2JyZWFrc2ANCg0KZ2dwbG90IOm7mOiupOeahOWbvuW9ouS4reWQq+aciee9keagvOe6v++8jOmZpOS6huaYvuekuui9tOmhu+WkhOS8mueUn+aIkOe9keagvOe6v++8jOS4pOS4qui9tOmhu+S5i+mXtOS5n+S8mueUn+aIkOS4gOadoeasoeimgee9keagvOe6v+OAgg0KDQpgbWlub3JfYnJlYWtzYOWPguaVsOWPr+S7peinhOWumueUn+aIkOasoeimgee9keagvOe6v+eahOS9jee9ruOAguWmguaenOS4jeW4jOacm+aYvuekuuasoeimgee9keagvOe6v++8jOWPr+S7peinhOWumiBgbWlub3JfYnJlYWtzPU5VTExgDQoNCiMjIyMgYGxhYmVsc2ANCg0KYGxhYmVsc2Ag6K6+572u6KaB5pi+56S655qE5paH5pys5qCH562+DQoNCmBgYHtyfQ0KIyBicmVha3PmnIDluLjop4HnlKjpgJTmmK/mm7/mjaLpu5jorqTnmoTliLvluqYNCmRhdGEoU2FsYXJpZXMsIHBhY2thZ2UgPSAiY2FyIikNCmdncGxvdChkYXRhID0gU2FsYXJpZXMsIGFlcyh4ID0gcmFuaywgeSA9IHNhbGFyeSwgZmlsbCA9IHNleCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKA0KICAgIGJyZWFrcyA9IGMoIkFzc3RQcm9mIiwgIkFzc29jUHJvZiIsICJQcm9mIiksDQogICAgbGFiZWxzID0gYygNCiAgICAgICJBc3Npc3RhbnRcblByb2Zlc3NvciIsDQogICAgICAiQXNzb2NpYXRlXG5Qcm9mZXNzb3IiLA0KICAgICAgIkZ1bGxcblByb2Zlc3NvciINCiAgICApDQogICkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoDQogICAgYnJlYWtzID0gYyg1MDAwMCwgMTAwMDAwLCAxNTAwMDAsIDIwMDAwMCksDQogICAgbGFiZWxzID0gYygiJDUwSyIsICIkMTAwSyIsICIkMTUwSyIsICIkMjAwSyIpDQogICkgKw0KICBsYWJzKHRpdGxlID0gIkZhY3VsdHkgU2FsYXJ5IGJ5IFJhbmsgYW5kIFNleCIsIHggPSAiIiwgeSA9ICIiKQ0KYGBgDQoNCui/mOWPr+S7peWwhmxhYmVsc+iuvue9ruS4uiBOVUxM77yM6L+Z5qC35Y+v5Lul5LiN5pi+56S65Yi75bqm5qCH562+77yM5a+55LqO5Zyw5Zu+5oiW5LiN6YCC5ZCI5bGV56S65pWw5YC855qE5Zu+6KGo5p2l6K+077yM6L+Z56eN5pa55byP5piv6Z2e5bi45pyJ55So55qE44CCDQoNCmBgYHtyfQ0KZ2dwbG90KG1wZywgYWVzKGRpc3BsLCBod3kpKSArDQogIGdlb21fcG9pbnQoKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBOVUxMLCBsYWJlbHMgPSBOVUxMKSArDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBOVUxMLCBsYWJlbHMgPSBOVUxMKQ0KYGBgDQoNCiMjIyMgYGRhdGVfYnJlYWtzYOWSjGBkYXRlX2xhYmVsc2ANCg0K5pel5pyf5Z6L5ZKM5pel5pyf5pe26Ze05Z6LIHNjYWxlIOeahOS4pOS4quWPguaVsCANCg0KLSBgZGF0ZV9sYWJlbHNgIOaOpeWPl+S4gOS4quagvOW8j+ivtOaYju+8jOivtOaYjueahOW9ouW8j+S4jiBwYXJzZV9kYXRldGltZSgpIOWHveaVsOS4reeahOebuOWQjCAgIA0KLSBgZGF0ZV9icmVha3Ng77yI56S65L6L5Lit5rKh5pyJ5Ye6546w77yJ5o6l5pS257G75Ly84oCcMiB3ZWVrc+KAneaIluKAnDEgbW9udGjigJ3ov5nmoLfnmoTlrZfnrKbkuLLvvIzmr4/pmpTov5nkuYjplb/ml7bpl7TmoIfor4bkuIDkuKrliLvluqYNCg0KYGBge3J9DQojIOS9v+eUqCBicmVha3Mg55qE5Y+m5LiA56eN5oOF5Ya177ya5pWw5o2u54K555u45a+56L6D5bCR77yM5Y+I5oOz6KaB5by66LCD6KeC5rWL55qE56Gu5YiH5L2N572uDQojIOS+i+Wmgu+8jOS7peS4i+WbvuW9ouWxleekuuS6huavj+S9jee+juWbveaAu+e7n+S7u+acn+eahOW8gOWni+aXtumXtOWSjOe7k+adn+aXtumXtO+8mg0KcHJlc2lkZW50aWFsICU+JQ0KICBtdXRhdGUoaWQgPSAzMyArIHJvd19udW1iZXIoKSkgJT4lDQogIGdncGxvdChhZXMoc3RhcnQsIGlkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3NlZ21lbnQoYWVzKHhlbmQgPSBlbmQsIHllbmQgPSBpZCkpICsgIyBnZW9tX3NlZ21lbnQoKee7mOWItueureWktA0KICBzY2FsZV94X2RhdGUoDQogICAgbmFtZSA9IE5VTEwsDQogICAgYnJlYWtzID0gcHJlc2lkZW50aWFsJHN0YXJ0LA0KICAgIGRhdGVfbGFiZWxzID0gIicleSINCiAgKQ0KYGBgDQoNCiMjIyMgYHRyYW5zYA0KDQrlr7kgc2NhbGUg6L+b6KGM5Ye95pWw5Y+Y5o2i77yM5Y+v6YCJ5YC85Z2H5Li65a2X56ym5Liy77yM5bi455So55qE5YyF5ousIGV4cCwgbG9nLCBsb2cxMCwgbG9naXQsIHBvdzEwLCBwcm9iaXQsIHJlY2lwcm9jYWwsIHNxcnQg562J44CCDQoNCuWFtuS4reacgOW4uOeUqOeahOS4gOS6m++8jOacieS4k+mXqOeahCBzY2FsZSDlh73mlbDvvIjor63ms5Xns5bvvInvvIzlpoIgYHNjYWxlX3hfbG9nMTAoKWAsIGBzY2FsZV94X3NxcnQoKWAsIGBzY2FsZV94X3JldmVyc2UoKWANCg0KYGBge3J9DQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyhjYXJhdCwgcHJpY2UpKSArDQogIGdlb21fYmluMmQoKQ0KDQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyhjYXJhdCwgcHJpY2UpKSArDQogIGdlb21fYmluMmQoKSArDQogIHNjYWxlX3hfbG9nMTAoKSArDQogIHNjYWxlX3lfbG9nMTAoKQ0KYGBgDQoNCiMjIyBzaGFwZSBhbmQgc2l6ZSBzY2FsZQ0KDQohW2EgcGxhY2Vob2xkZXIgb2YgYSBwaWN0dXJlXShodHRwOi8vaHVtb29uLWltYWdlLWhvc3Rpbmctc2VydmljZS5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vaW1nL3R5cG9yYS8yMDIyLzIwMjIwMzIzLXNoYXBlLXNpemUtc2NhbGUucG5nKQ0KDQojIyMgY29sb3IgYW5kIGZpbGwgc2NhbGUNCg0K5bCG5Y+Y6YeP5pig5bCE5Li66aKc6Imy5pe277yM6buY6K6k5Lul5LiA56eN6Z2e5bi45Z2H5YyA55qE5pa55byP5Zyo6Imy546v5LiK6YCJ5oup6aKc6Imy77yb5aaC5p6c5LiN5Zac5qyi77yM5Y+v5Lul6K6+572u6aKc6ImyIHNjYWxlIOi/m+ihjOabv+aNog0KDQojIyMjIGRpc2NyZXRlIHNjYWxlDQoNCuWPr+S7peiuvue9ruS4uuafkOWll+iwg+iJsuadv++8jOeUqGBSQ29sb3JCcmV3ZXI6OmRpc3BsYXkuYnJld2VyLmFsbCgpYOWPr+afpeeci+aJgOacieeahOiwg+iJsuadvw0KDQohW2EgcGxhY2Vob2xkZXIgb2YgYSBwaWN0dXJlXShodHRwOi8vaHVtb29uLWltYWdlLWhvc3Rpbmctc2VydmljZS5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vaW1nL3R5cG9yYS8yMDIyLzIwMjIwMzIzLWNvbG9yLXNjYWxlLWRpc2NyZXRlLnBuZykNCg0KYHNjYWxlX2NvbG9yX2JyZXdlcigpYOWSjGBzY2FsZV9jb2xvcl9ncmV5KClg5a+55bqUIGNvbG9yIOWxnuaApw0KDQpgc2NhbGVfZmlsbF9icmV3ZXIoKWDlkoxgc2NhbGVfZmlsbF9ncmV5KClg5a+55bqUIGZpbGwg5bGe5oCnDQoNCmBgYHtyfQ0KIyDluLjnlKjnmoTkuIDnp43phY3oibLmlrnlvI/mmK/kvb/nlKhDb2xvckJyZXdlcuagh+W6puOAgg0KIyDku6XkuIvnmoTkuKTluYXlm77pnZ7luLjnm7jkvLwNCiMg5L2G5Y+z6L655Zu+5Lit77yIU2V0Meiwg+iJsuadv++8ieeahOe6ouiJsuWSjOe7v+iJsueahOWvueavlOabtOWKoOW8uueDiA0KIyDljbPkvb/mgqPmnInnuqLnu7/oibLnm7Lnl4fnmoTkurrkuZ/lj6/ku6XljLrliKvlh7rmnaUNCnAxIDwtIGdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5KSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGRydikpDQoNCnAyIDwtIGdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5KSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGRydikpICsNCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpDQoNCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2wgPSAyKQ0KYGBgDQoNCmBgYHtyfQ0KIyDlpoLmnpzlj6rmnInlvojlsJHlh6Dnp43popzoibLvvIzlj6/ku6Xlho3mt7vliqDkuIDnp43lvaLnirbmmKDlsIQNCiMg6Jm954S25pyJ5Lqb5YaX5L2Z77yM5L2G6L+Z5qC35Y+v5Lul56Gu5L+d5Zu+6KGo5Zyo6buR55m95pa55byP5LiL5Lmf5Y+v5Lul5Li65Lq65omA55CG6KejDQpnZ3Bsb3QobXBnLCBhZXMoZGlzcGwsIGh3eSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBkcnYsIHNoYXBlID0gZHJ2KSkgKw0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikNCmBgYA0KDQojIyMjIGNvbnRpbnVvdXMgc2NhbGUNCg0K6L+e57ut5riQ5Y+Y6Imy77yM5pyJ5Yeg56eN5Ye95pWw77yI5Y2V6Imy44CB5Y+M6Imy44CB5LiJ6Imy77yJ77yaDQoNCiFbYSBwbGFjZWhvbGRlciBvZiBhIHBpY3R1cmVdKGh0dHA6Ly9odW1vb24taW1hZ2UtaG9zdGluZy1zZXJ2aWNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS9pbWcvdHlwb3JhLzIwMjIvMjAyMjAzMjMtY29sb3Itc2NhbGUtY29udGludW91cy5wbmcpDQoNCuWmguaenOaDs+WMuuWIhuS9jeS6juW5s+Wdh+WAvOS7peS4iuWSjOS7peS4i+eahOeCue+8jOWPr+S7peS9v+eUqGBzY2FsZV9jb2xvcl9ncmFkaWVudDIoKWDlh73mlbDjgIINCg0K5Y+m5LiA5Liq5Y+v5Lul6YCJ55So55qE5Ye95pWw5piv55SxdmlyaWRpc+WMheaPkOS+m+eahGBzY2FsZV9jb2xvcl92aXJpZGlzKClg77yM5a6D5piv5a+5Q29sb3JCcmV3ZXLliIbnsbvmoIfluqbnmoTkuIDnp43ov57nu63mqKHmi5/jgILku6XkuIvmmK/mnaXoh6rkuo4gdmlyaWRpcyDkvb/nlKjmjIfljZfkuK3nmoTkuIDkuKrnpLrkvovvvJoNCg0KDQpgYGB7cn0NCmRmIDwtIHRpYmJsZSgNCiAgeCA9IHJub3JtKDEwMDAwKSwNCiAgeSA9IHJub3JtKDEwMDAwKQ0KKQ0KDQpwMSA8LSBnZ3Bsb3QoZGYsIGFlcyh4LCB5KSkgKw0KICBnZW9tX2hleCgpICsNCiAgY29vcmRfZml4ZWQoKQ0KDQpwMiA8LSBnZ3Bsb3QoZGYsIGFlcyh4LCB5KSkgKw0KICBnZW9tX2hleCgpICsNCiAgdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKCkgKw0KICBjb29yZF9maXhlZCgpDQoNCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2wgPSAyKQ0KYGBgDQoNCiMjIyMg5omL5Yqo5oyH5a6a6aKc6ImyDQoNCumAmui/h2BzY2FsZV9jb2xvcl9tYW51YWwoKWDvvIzlj6/ku6Xoh6rlt7HmjIflrprpopzoibINCg0KYGBge3J9DQpwcmVzaWRlbnRpYWwgJT4lDQogIG11dGF0ZShpZCA9IDMzICsgcm93X251bWJlcigpKSAlPiUNCiAgZ2dwbG90KGFlcyhzdGFydCwgaWQsIGNvbG9yID0gcGFydHkpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc2VnbWVudChhZXMoeGVuZCA9IGVuZCwgeWVuZCA9IGlkKSkgKw0KICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoUmVwdWJsaWNhbiA9ICJyZWQiLCBEZW1vY3JhdGljID0gImJsdWUiKSkNCmBgYA0KDQoNCiMjIOWdkOagh+ezuw0KDQojIyMg5Lqk5o2iIHjjgIF5IOi9tCBgY29vcmRfZmxpcCgpYA0K5b2T5oOz6KaB57uY5Yi25rC05bmz566x57q/5Zu+5pe277yM6L+Z6Z2e5bi45pyJ55So44CC5a6D5Lmf6Z2e5bi46YCC5ZCI5Y+Y6YeP6ZW/5qCH562+55qE5L2/55So77yM5ZCm5YiZ5ZyoeOi9tOS4iuS4jemHjeWPoOWcsOWuieaOkuWlveWug+S7rOaYr+mdnuW4uOWbsOmavueahOOAgg0KYGBge3IsIGZpZy5zaG93PSdhc2lzJ30NCmdncGxvdChtcGcsIG1hcHBpbmcgPSBhZXMoeCA9IGNsYXNzLCB5ID0gaHd5KSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KDQpnZ3Bsb3QobXBnLCBtYXBwaW5nID0gYWVzKHggPSBjbGFzcywgeSA9IGh3eSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQojIyMg5Li65Zyw5Zu+6K6+572u5ZCI6YCC55qE57q15qiq5q+UIGBjb29yZF9xdWlja21hcCgpYCANCg0KYGBge3IsIGZpZy5zaG93PSdhc2lzJ30NCm56IDwtIG1hcF9kYXRhKCJueiIpDQpnZ3Bsb3QobnosIGFlcyhsb25nLCBsYXQsIGdyb3VwID0gZ3JvdXApKSArDQogIGdlb21fcG9seWdvbihmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKQ0KZ2dwbG90KG56LCBhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSkgKw0KICBnZW9tX3BvbHlnb24oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBjb29yZF9xdWlja21hcCgpDQpgYGANCg0KIyMjIOaegeWdkOagh+ezuyBjb29yZF9wb2xhcigpIA0KYGBge3IsIGZpZy5zaG93PSdhc2lzJ30NCmJhciA8LSBnZ3Bsb3QoZGF0YSA9IGRpYW1vbmRzKSArDQogIGdlb21fYmFyKGFlcyh4ID0gY3V0LCBmaWxsID0gY3V0KSwNCiAgICBzaG93LmxlZ2VuZCA9IEZBTFNFLCB3aWR0aCA9IDENCiAgKSArDQogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpICsNCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwpDQoNCmJhciArIGNvb3JkX2ZsaXAoKQ0KYmFyICsgY29vcmRfcG9sYXIoKQ0KYGBgDQoNCiMjIyBgY29vcmRfY2FydGVzaWFuKClg77ya6K6+572u5Z2Q5qCH6L205pi+56S655qE6IyD5Zu0DQoNCuS4ieenjeaWueazle+8mg0KDQoqIOiwg+aVtOe7mOWbvuaJgOeUqOaVsOaNru+8iOWPluWtkOmbhu+8iQ0KDQoqIOiuvue9riBzY2FsZSDojIPlm7TvvIjnvKnlsI8gc2NhbGUg6IyD5Zu055qE5pWI5p6c5Z+65pys562J5ZCM5LqO5a+55pWw5o2u5Y+W5a2Q6ZuG77yMc2NhbGUg6IyD5Zu05LmL5aSW55qE5pWw5o2u5Lya5Y+Y5oiQIE5B77yJDQoNCiogKirlnKhjb29yZF9jYXJ0ZXNpYW4oKeWHveaVsOS4reiuvue9riB4bGltIOWSjCB5bGltIOWPguaVsOWAvO+8iOaOqOiNkO+8iSoq77yM6L+Z5piv55yf5q2j55qE57yp5pS+KirmmL7npLoqKuiMg+WbtA0KDQpgYGB7cn0NCiMg5q+U6L6D5Lik56eN5pa55rOVDQptcGcgJT4lDQogIGZpbHRlcihkaXNwbCA+PSA1LCBkaXNwbCA8PSA3LCBod3kgPj0gMTAsIGh3eSA8PSAzMCkgJT4lDQogIGdncGxvdChhZXMoZGlzcGwsIGh3eSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjbGFzcykpICsNCiAgZ2VvbV9zbW9vdGgoKSAjIOaVsOaNruWPluWtkOmbhuS9v+aLn+WQiOabsue6v+eahOe9ruS/oeWMuumXtOWPmOW+l+W+iOWuvQ0KDQpnZ3Bsb3QobXBnLCBtYXBwaW5nID0gYWVzKGRpc3BsLCBod3kpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2xhc3MpKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoNSwgNyksIHlsaW0gPSBjKDEwLCAzMCkpDQpgYGANCg0KDQpgYGB7cn0NCiMg5Zyo5Lik5byg5Zu+5b2i5Lit5L2/55So55u45ZCM55qEc2NhbGUNCnN1diA8LSBtcGcgJT4lIGZpbHRlcihjbGFzcyA9PSAic3V2IikNCmNvbXBhY3QgPC0gbXBnICU+JSBmaWx0ZXIoY2xhc3MgPT0gImNvbXBhY3QiKQ0KDQpyYW5nZV9kaXNwbCA8LSByYW5nZShtcGckZGlzcGwpDQpyYW5nZV9od3kgPC0gcmFuZ2UobXBnJGh3eSkNCiMgY29sb3Lkvb/nlKjlhajpm4bnmoRzY2FsZe+8jOS/neivgeminOiJsueahOS4gOiHtOaApw0KY29sX3NjYWxlIDwtIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKGxpbWl0cyA9IHVuaXF1ZShtcGckZHJ2KSkNCg0KZ2dwbG90KHN1diwgYWVzKGRpc3BsLCBod3ksIGNvbG9yID0gZHJ2KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBjb2xfc2NhbGUgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IHJhbmdlX2Rpc3BsLCB5bGltID0gcmFuZ2VfaHd5KQ0KDQpnZ3Bsb3QoY29tcGFjdCwgYWVzKGRpc3BsLCBod3ksIGNvbG9yID0gZHJ2KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBjb2xfc2NhbGUgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IHJhbmdlX2Rpc3BsLCB5bGltID0gcmFuZ2VfaHd5KQ0KYGBgDQoNCiMjIOWIhumdouaooeW8jw0KDQrliIbpnaLmqKHlvI8oZmFjZXRpbmcp5oqK5Yeg5bmF5Zu+5ou85o6l5Zyo5LiA6LW377yM54m55Yir6YCC5ZCI5q+U6L6D5YiG57G75Y+Y6YeP44CCDQoNCiFbYSBwbGFjZWhvbGRlciBvZiBhIHBpY3R1cmVdKGh0dHA6Ly9odW1vb24taW1hZ2UtaG9zdGluZy1zZXJ2aWNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS9pbWcvdHlwb3JhLzIwMjIvMjAyMjAzMjMt5Yi76Z2i5Zu+5Ye95pWwLnBuZykNCg0KYGBge3J9DQojIOWIu+mdouWbvg0KZGF0YShzaW5nZXIsIHBhY2thZ2UgPSAibGF0dGljZSIpDQoNCmdncGxvdChkYXRhID0gc2luZ2VyLCBhZXMoeCA9IGhlaWdodCkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKSArDQogIGZhY2V0X3dyYXAofnZvaWNlLnBhcnQsIG5jb2wgPSA0KSAjIHZvaWNlLnBhcnTlj5jph4/lhbHmnIk45Liq5YC877yM5Zug5YiX5pWw5Li6NO+8jOihjOaVsOiHquWKqOS4ujLjgILlhYjooYzlkI7liJfmjpLliJcNCmBgYA0KDQpgYGB7cn0NCiMg5YiG57uEK+WIu+mdouWbvivmlaPngrnlm74NCnAgPC0gZ2dwbG90KA0KICBTYWxhcmllcywNCiAgYWVzKHggPSB5cnMuc2luY2UucGhkLCB5ID0gc2FsYXJ5LCBjb2xvciA9IHJhbmssIHNoYXBlID0gcmFuaykNCikgKw0KICBnZW9tX3BvaW50KCkgKw0KICBmYWNldF9ncmlkKC4gfiBzZXgpDQpwDQoNCnAgPC0gZ2dwbG90KA0KICBTYWxhcmllcywNCiAgYWVzKHggPSB5cnMuc2luY2UucGhkLCB5ID0gc2FsYXJ5LCBjb2xvciA9IHJhbmssIHNoYXBlID0gcmFuaykNCikgKw0KICBnZW9tX3BvaW50KCkgKw0KICBmYWNldF9ncmlkKHNleCB+IC4pDQpwDQoNCiMg5YiG57uEK+WIu+mdoivlr4bluqblm74NCmRhdGEoc2luZ2VyLCBwYWNrYWdlID0gImxhdHRpY2UiKQ0KcCA8LSBnZ3Bsb3QoZGF0YSA9IHNpbmdlciwgYWVzKHggPSBoZWlnaHQsIGZpbGwgPSB2b2ljZS5wYXJ0KSkgKw0KICBnZW9tX2RlbnNpdHkoKSArDQogIGZhY2V0X2dyaWQodm9pY2UucGFydCB+IC4pDQoNCmdncGxvdGx5KHApICU+JQ0KICBzYXZlV2lkZ2V0KCIuL0ZpZ3VyZXMvZmFjZXQtZGVuc2l0eS5odG1sIiwNCiAgICBzZWxmY29udGFpbmVkID0gRiwNCiAgICBsaWJkaXIgPSAibGliIg0KICApDQoNCnANCmBgYA0K