# 分析文集為《封神演義》-作者:陳仲琳(明)
# 載入各packages
require(dplyr)
載入需要的套件:dplyr
警告: 套件 ‘dplyr’ 是用 R 版本 4.3.1 來建造的
載入套件:‘dplyr’
下列物件被遮斷自 ‘package:stats’:
filter, lag
下列物件被遮斷自 ‘package:base’:
intersect, setdiff, setequal, union
require(tidytext)
載入需要的套件:tidytext
警告: 套件 ‘tidytext’ 是用 R 版本 4.3.1 來建造的
require(jiebaR)
載入需要的套件:jiebaR
警告: 套件 ‘jiebaR’ 是用 R 版本 4.3.1 來建造的載入需要的套件:jiebaRD
警告: 套件 ‘jiebaRD’ 是用 R 版本 4.3.1 來建造的
library(stringr)
警告: 套件 ‘stringr’ 是用 R 版本 4.3.1 來建造的
library(wordcloud2)
警告: 套件 ‘wordcloud2’ 是用 R 版本 4.3.1 來建造的Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
library(ggplot2)
警告: 套件 ‘ggplot2’ 是用 R 版本 4.3.1 來建造的
library(tidyr)
警告: 套件 ‘tidyr’ 是用 R 版本 4.3.1 來建造的
library(scales)
警告: 套件 ‘scales’ 是用 R 版本 4.3.1 來建造的
library(jiebaR)
library(readr)
警告: 套件 ‘readr’ 是用 R 版本 4.3.1 來建造的
載入套件:‘readr’
下列物件被遮斷自 ‘package:scales’:
col_factor
library(devtools)
警告: 套件 ‘devtools’ 是用 R 版本 4.3.1 來建造的載入需要的套件:usethis
警告: 套件 ‘usethis’ 是用 R 版本 4.3.1 來建造的
library(stringi)
library(pbapply)
警告: 套件 ‘pbapply’ 是用 R 版本 4.3.1 來建造的
library(Rcpp)
警告: 套件 ‘Rcpp’ 是用 R 版本 4.3.1 來建造的
library(RcppProgress)
警告: 套件 ‘RcppProgress’ 是用 R 版本 4.3.1 來建造的
library(cidian) # devtools::install_github("gn01830657/cidian")
library(ropencc) # devtools::install_github("Lchiffon/ropencc")
library(showtext) # C:\Users\boche\Downloads\showtext_0.9-6.zip直接使用R Tools->Install Packages...
警告: 套件 ‘showtext’ 是用 R 版本 4.3.1 來建造的載入需要的套件:sysfonts
警告: 套件 ‘sysfonts’ 是用 R 版本 4.3.1 來建造的載入需要的套件:showtextdb
警告: 套件 ‘showtextdb’ 是用 R 版本 4.3.1 來建造的
# 古騰堡網站下載《封神演義》繁體文集
library(gutenbergr)
警告: 套件 ‘gutenbergr’ 是用 R 版本 4.3.1 來建造的
Fengshen <- gutenberg_download(23910) %>% filter(text!="") %>% distinct(gutenberg_id, text)
Determining mirror for Project Gutenberg from https://www.gutenberg.org/robot/harvest
Using mirror http://aleph.gutenberg.org
View(Fengshen)
# 使用正規表示式,將句子區分章節並斷出共1~100章回
Fengshen <- Fengshen %>% mutate(chapter = cumsum(str_detect(Fengshen$text, regex("第.*回(\u00a0|$)"))))
# 文集已經完成斷句了
head(Fengshen, 20)
# 文集斷詞,設定斷詞function與停用字stop_word
jieba_tokenizer <- worker(user="Fengshen.traditional.dict", stop_word="D:/R/20200318_bookclub_1/stop_words.txt")
# stop_word="C:/Users/Sean/Documents/jiebar/stop_words.txt")
Fengshen_tokenizer <- function(t) {
lapply(t, function(x) {
tokens <- segment(x, jieba_tokenizer)
return(tokens)
})
}
tokens <- Fengshen %>% unnest_tokens(word, text, token=Fengshen_tokenizer)
str(tokens)
tibble [223,872 × 3] (S3: tbl_df/tbl/data.frame)
$ gutenberg_id: int [1:223872] 23910 23910 23910 23910 23910 23910 23910 23910 23910 23910 ...
$ chapter : int [1:223872] 0 1 1 1 1 1 1 1 1 1 ...
$ word : chr [1:223872] "p" "第一回" "紂王" "女媧" ...
head(tokens, 20) # 全文斷詞結果
# 重點一: 計算每一章節出現字數大於1的詞彙出現的總次數
tokens_count_chapter <- tokens %>%
filter(nchar(.$word)>1) %>%
group_by(chapter,word) %>%
dplyr::summarise(sum = n()) %>%
# summarise(sum = n()) %>%
arrange(chapter)
`summarise()` has grouped output by 'chapter'. You can override using the `.groups` argument.
tokens_count_chapter <- top_n(tokens_count_chapter,3) # 只選出前三名的字彙
Selecting by sum
tokens_count_chapter
# 結果可以發現第一章回出現最多的是女媧、天子、紂王、諸侯,推斷可能第一回在講女媧和紂王的故事
# 到 https://zh.wikisource.org/zh-hant/%E5%B0%81%E7%A5%9E%E6%BC%94%E7%BE%A9 可以看到第一回標題是:紂王女媧宮進香
# 繪製散布圖X軸是章節,Y軸是詞彙出現的次數
ggplot(tokens_count_chapter, aes(x = chapter, y = sum, color = abs(sum))) +
geom_jitter(alpha = 0.1, size = 5, width = 0.5, height = 0.5) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) +
scale_y_log10()+
ggtitle("各章節的出現前三多次數的字") +
ylab(label="出現次數")+
xlab(label="回數")

# 結果可以看出第一回是由女媧作為故事的開頭,最後則是以武王作為故事的結束,也可以發現各個角色大概出現的章回在哪
# 重點二:
# 封神演義內容包含了許多道教的元素
# 分析出現最多的道教詞彙
# 匯入道教詞語辭典
# fileEncoding ="UTF-8":使用R讀取文字檔時, 有時會遇到資料匯入有錯誤訊息或中文亂碼問題
taoism <- read.table("./taoism.txt",sep = "\n" ,header = FALSE, fileEncoding ="UTF-8")
# taoism <- read.table("./20200318_bookclub_1/taoism.txt",sep = "\n" ,header = FALSE, fileEncoding ="UTF-8")
taoism_count <- tokens %>%
filter(nchar(.$word) > 1) %>%
mutate(total = nrow(tokens)) %>%
group_by(word) %>%
filter(word %in% taoism$V1) %>%
dplyr::summarise(count = n(), total = mean(total)) %>%
# summarise(count = n(), total = mean(total)) %>%
mutate(proportion = count / total) %>%
arrange(desc(count))
head(taoism_count, 20)
# 結果可以發現出現最多的道教詞彙為"廣成子",廣成子為中國道教傳說的神仙
# 推斷封神演義內除了有許多歷史人物外,廣成子是代表道教的重要角色
# 計算道教詞彙在各章節出現的總數,推斷那些章節可能在講道教的故事
taoism_chapter <- tokens %>%
filter(nchar(.$word)>1) %>%
filter(word %in% taoism$V1) %>%
group_by(chapter) %>%
dplyr::summarise(count = n(), type = "words")%>%
# summarise(count = n(), type = "words")%>%
arrange(desc(count))
taoism_chapter
# 結果可以看出在72回是講到最多道教相關詞彙的章節
# 計算道教詞彙出現在各章節的長條圖
taoism_plot = tokens %>%
filter(nchar(.$word)>1) %>%
filter(word %in% taoism$V1) %>%
group_by(chapter) %>%
dplyr::summarise(count = n()) %>%
# summarise(count = n()) %>%
ggplot(aes(x = chapter, y=count)) +
geom_col() +
ggtitle("各章節的道教詞彙出現總數") +
xlab("章節") +
ylab("道教詞彙數量")
taoism_plot

# 結果可以看出在75回前後有講到大量的道教相關詞彙
# 重點三: 文集中角色人物-哪吒的描述
# 為了找出哪吒武器,將原本沒有區分出來的斷詞加入
new_user_word(jieba_tokenizer, c("兩根火尖槍", "乾坤圈", "混天綾", "九龍神火罩","陰陽劍"))
[1] TRUE
chi_tokenizer <- function (t) {
lapply(t, function(x){
tokens <- segment(x, jieba_tokenizer)
tokens <- tokens[nchar(tokens) > 1]
return(tokens)
})
}
# 斷詞
tokens <- Fengshen %>%
unnest_tokens(word, text, token = chi_tokenizer)
# 將哪吒武器的等同詞保留成變數
#《封神演義》中武器是:兩根火尖槍、風火輪、乾坤圈、混天綾、金磚、九龍神火罩、陰陽劍,共八件兵器(因為封神中哪吒是三頭八臂)
# 但是原本封神演義斷詞字典中,對於哪吒武器並沒有特別區別出來
weapon_alias = c("金磚","風火輪","乾坤圈","混天綾","兩根火尖槍","九龍神火罩","陰陽劍")
# 計算哪吒武器在整本書中出現的頻率及比例,可以發現哪吒使用風火輪次數最多
weapon_count = tokens %>%
filter(nchar(.$word)>1) %>% # 保留非空白資料
mutate(total = nrow(tokens)) %>% # 加入一個total (資料總列數)
group_by(word) %>%
filter(word %in% weapon_alias) %>% # 保留詞
dplyr::summarise(count = n(), total = mean(total)) %>%
# summarise(count = n(), total = mean(total)) %>%
mutate(proportion = count / total) %>% # 加入比例欄位
arrange(desc(count)) # 根據count欄位,由大至小排列
head(weapon_count, 20)
# 計算哪吒武器在各章節出現的總數,推斷哪吒出場抄傢伙最多的章節
weapon_chapter <- tokens %>%
filter(nchar(.$word)>1) %>%
filter(word %in% weapon_alias) %>%
group_by(chapter) %>%
dplyr::summarise(count = n(), type = "words")%>%
# summarise(count = n(), type = "words")%>%
arrange(desc(count))
weapon_chapter
# 可以看出第13章及第34章總量最多
# 第十三章講哪吒年輕時連續闖禍,殺害龍王親人、石磯娘娘的弟子,把自己的父母師父都拖下水,一陣大亂鬥之後,
# 眼見四海龍王要來取他父母的性命,哪吒只好自殺,留下一縷幽魂,結束這場鬧劇。
# 第三十四章講成年的哪吒奉太乙真人之命,大亂鬥打敗韓榮、余化,救出黃飛虎、黃滾父子,並打通汜水關讓武成王黃飛虎一家,
# 可以奔走到西岐投靠周武王,增加周王朝的軍事力量。
# 從這兩章可以看到哪吒性格從讓父母頭痛、只會闖禍鬧事的流氓,蛻變為成熟並懂得運用自身的力量成就建國大業的神仙。
# 可直接使用字型檔並且將圖形輸出至各種圖檔
# Loading Google fonts (https://fonts.google.com/)
# font_families_google() 函數查看google提供的字型列表
font_add_google("Noto Sans TC", "NotoSansTC") # 安裝字體為思源黑體
showtext_auto(enable = TRUE, record = TRUE) # 啟動字體
# 計算哪吒武器出現最多的章節統計圖
weapon_plot = tokens %>%
filter(nchar(.$word)>1) %>%
filter(word %in% weapon_alias) %>%
group_by(chapter) %>%
dplyr::summarise(count = n()) %>%
# summarise(count = n()) %>%
ggplot(aes(x = chapter, y=count)) +
geom_col() +
ggtitle("各章節的哪吒武器出現總數") +
xlab("章節") +
ylab("哪吒武器數量") +
theme(text = element_text(family = "NotoSansTC")) # 加入中文字型設定,避免中文字顯示錯誤
weapon_plot

# 重點四: 情緒分析
# 準備LIWC字典(utf8)
p<-read_file("D:/R/20200318_bookclub_1/dic/liwc/positive.txt")
n<-read_file("D:/R/20200318_bookclub_1//dic/liwc/negative.txt")
# p<-read_file("C:/Users/Sean/Documents/20200318_bookclub_1/dic/liwc/positive.txt")
# n<-read_file("C:/Users/Sean/Documents/20200318_bookclub_1/dic/liwc/negative.txt")
positive <- strsplit(p, "[,]")[[1]]
negative <- strsplit(n, "[,]")[[1]]
positive <- data.frame(word = positive, sentiments = "positive")
negative <- data.frame(word = negative, sentiemtns = "negative")
colnames(negative) = c("word","sentiment")
colnames(positive) = c("word","sentiment")
LIWC_ch <- rbind(positive, negative)
head(LIWC_ch, 20)
# LIWC字典中有多少正面單詞和負面單詞
LIWC_ch %>% filter(sentiment %in% c("positive", "negative")) %>% count(sentiment)
# 以LIWC字典判斷文集中的word屬於正面字還是負面字
# 先將tokens斷詞後的文集過濾詞彙,只有一個字則不列入計算(也過濾掉空格)
all_fengshen_words <- tokens %>% filter(nchar(.$word)>1)
# 計算所有各詞彙的出現總數
all_word_count <- all_fengshen_words %>% group_by(word) %>% dplyr::summarise(sum = n()) %>% arrange(desc(sum))
# 計算所有字在文集中出現的總數
word_count <- all_word_count %>%
select(word,sum) %>%
group_by(word) %>%
summarise(sum = sum(sum)) %>%
filter(sum>3)
print(word_count)
# 與LIWC情緒字典join,文集中的字出現在LIWC字典中是屬於positive還是negative
liwch_ch_word_counts<-word_count %>% inner_join(LIWC_ch)
Joining with `by = join_by(word)`
print(liwch_ch_word_counts)
liwch_ch_word_counts %>% filter(sentiment %in% c("positive", "negative")) %>% count(sentiment)
# 繪圖出以LIWC字典統計的文集情緒字數,觀察兩種情緒值的差異
liwch_ch_word_counts %>%
group_by(sentiment) %>%
top_n(10,wt = sum) %>%
ungroup() %>%
mutate(word = reorder(word, sum)) %>%
ggplot(aes(word, sum, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to sentiment",
x = NULL) +
theme(text=element_text(size=14))+
coord_flip()

# 情緒分析(續)
# 準備NTUSD字典(utf8)
pn<-read_file("./dic/ntusd/positive.txt")
nn<-read_file("./dic/ntusd/negative.txt")
# pn<-read_file("C:/Users/Sean/Documents/20200318_bookclub_1/dic/ntusd/positive.txt")
# nn<-read_file("C:/Users/Sean/Documents/20200318_bookclub_1/dic/ntusd/negative.txt")
ps<-strsplit(pn, "[\n]")[[1]]
positive_ntusd<-strsplit(ps, "[\r]")
ns<-strsplit(nn, "[\n]")[[1]]
negative_ntusd<-strsplit(ns, "[\r]")
# 用unlist拆分list後重構矩陣然後轉換為dataframe
positive_ntusd<-data.frame(matrix(unlist(positive_ntusd), nrow=2812, ncol=1, byrow=F),sentiments="positive", stringsAsFactors=FALSE)
colnames(positive_ntusd)<-c("word", "sentiment")
negative_ntusd<-data.frame(matrix(unlist(negative_ntusd), nrow=8276, ncol=1, byrow=F), sentiments="negative", stringsAsFactors = FALSE)
colnames(negative_ntusd)<-c("word","sentiment")
NTUSD_ch<-rbind(positive_ntusd, negative_ntusd)
head(NTUSD_ch, 20)
# NTUSD字典中有多少正面單詞和負面單詞
NTUSD_ch %>% filter(sentiment %in% c("positive", "negative")) %>% count(sentiment)
# 以NTUSD字典判斷文集中的word屬於正面字還是負面字
# 先將tokens斷詞後的文本過濾詞彙,只有一個字則不列入計算(也過濾掉空格)
all_fengshen_words_ntusd <- tokens %>% filter(nchar(.$word)>1)
# 計算所有各詞彙的出現總數
all_word_count_ntusd <- all_fengshen_words_ntusd %>% group_by(word) %>% summarise(sum = n()) %>% arrange(desc(sum))
# 計算所有字在文集中出現的總數
word_count_ntusd <- all_word_count_ntusd %>%
select(word,sum) %>%
group_by(word) %>%
summarise(sum = sum(sum)) %>%
filter(sum>3)
print(word_count_ntusd)
# 與NTUSD情緒字典join,文集中的字出現在NTUSD字典中是屬於positive還是negative
ntusd_ch_word_counts<-word_count_ntusd %>% inner_join(NTUSD_ch)
Joining with `by = join_by(word)`
print(ntusd_ch_word_counts)
ntusd_ch_word_counts %>% filter(sentiment %in% c("positive", "negative")) %>% count(sentiment)
# 繪圖出以NTUSD字典統計的文集情緒字數,觀察兩種情緒值的差異
ntusd_ch_word_counts %>%
group_by(sentiment) %>%
top_n(10,wt = sum) %>%
ungroup() %>%
mutate(word = reorder(word, sum)) %>%
ggplot(aes(word, sum, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to sentiment",
x = NULL) +
theme(text=element_text(size=14))+
coord_flip()

# 重點五: 文字雲
# 計算詞彙的出現次數,如果詞彙只有一個字則不列入計算
tokens_count <- tokens %>%
filter(nchar(.$word)>1) %>%
group_by(word) %>%
summarise(sum = n()) %>%
filter(sum>10) %>%
arrange(desc(sum))
head(tokens_count, 20)
# 文字雲分布詞彙 (全文集)
tokens_count %>% wordcloud2()
# 繪圖出《封神演義》各章節長度,以語句數來計算
fengshen_plot <- bind_rows(Fengshen %>% group_by(chapter) %>% summarise(count = n(), type="sentences"), tokens %>% group_by(chapter) %>% summarise(count = n(), type="words")) %>% group_by(type) %>% ggplot(aes(x = chapter, y=count, fill="type", color=factor(type))) + geom_line() +
ggtitle("各章節的句子總數") + xlab("章節") + ylab("句子數量")
print(fengshen_plot)

# 計算前五十回和後五十回的詞彙在全文中出現比率的差異
frequency1 <- tokens %>% mutate(part = ifelse(chapter<=50, "First50", "Last50")) %>%
filter(nchar(.$word)>1) %>%
mutate(word = str_extract(word, "[^0-9a-z']+")) %>%
mutate(word = str_extract(word, "[^一二三四五六七八九十]+"))
attach(frequency1)
frequency2 <- frequency1 %>%
count(part, word) %>%
group_by(part) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
spread(part, proportion) %>%
gather(part, proportion, `Last50`)
# 匯出圖表
ggplot(frequency2, aes(x = proportion, y = `First50`, color = abs(`First50` - proportion))) +
geom_abline(color = "gray50", lty = 2) +
geom_jitter(alpha = 0.1, size = 2.5, width = 0.3, height = 0.3) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) +
scale_x_log10(labels = percent_format()) +
scale_y_log10(labels = percent_format()) +
scale_color_gradient(limits = c(0, 0.001), low = "darkslategray4", high = "gray75") +
theme(legend.position="none") +
labs(y = "Firs50", x = "Last50")

# 重點六: 比較《封神演義》前五十回和後五十回最常出現的前100個詞彙的差異
often_words <- tokens %>% mutate(part = ifelse(chapter<=50, "First50", "Last50")) %>% filter(nchar(.$word)>1)
first_50<-as_tibble(head(often_words, 80223))
last_50<-as_tibble(tail(often_words, 86764))
# 計算詞彙的出現次數,如果詞彙只有一個字則不列入計算
first50_count <- first_50 %>% filter(nchar(.$word)>1) %>% group_by(word) %>% summarise(sum = n()) %>% filter(sum>10) %>% arrange(desc(sum))
last50_count <- last_50 %>% filter(nchar(.$word)>1) %>% group_by(word) %>% summarise(sum = n()) %>% filter(sum>10) %>% arrange(desc(sum))
print(first50_count);print(last50_count)
# 視覺化長條圖
first50_bar <- first50_count %>% filter(sum>100) %>% mutate(word=reorder(word, sum)) %>% ggplot(aes(word, sum)) + geom_col() + xlab(NULL) + coord_flip()
last50_bar <- last50_count %>% filter(sum>100) %>% mutate(word=reorder(word, sum)) %>% ggplot(aes(word, sum)) + geom_col() + xlab(NULL) + coord_flip()
# 將兩張ggplot圖合併為一張圖
library(Rmisc)
警告: 套件 ‘Rmisc’ 是用 R 版本 4.3.1 來建造的載入需要的套件:lattice
載入需要的套件:plyr
警告: 套件 ‘plyr’ 是用 R 版本 4.3.1 來建造的-------------------------------------------------------------------------------------------------------
You have loaded plyr after dplyr - this is likely to cause problems.
If you need functions from both plyr and dplyr, please load plyr first, then dplyr:
library(plyr); library(dplyr)
-------------------------------------------------------------------------------------------------------
載入套件:‘plyr’
下列物件被遮斷自 ‘package:dplyr’:
arrange, count, desc, failwith, id, mutate, rename, summarise, summarize
multiplot(first50_bar, last50_bar, cols=2)

LS0tDQp0aXRsZTogIuesrOS5nee1hF/oroDmm7jmnIPoqI7oq5bkuIAo5pa35Y+l5pa36Kme6IiH5oOF57eS5YiG5p6QKSINCm1lbWJlcnM6ICJCMDU0MDIwMDQyIOmDreWul+e/sA0KICAgICAgICAgIEIwNjQwMjAwMTQg6YSt5a2Q5am3DQogICAgICAgICAgTTA4NDAyMDAyMyDpmbPpnZbkuK0NCiAgICAgICAgICBNMDg0MDIwMDQ2IOiRieWQm+iJrw0KICAgICAgICAgIE4wNzQyMjAwMDIg6Zmz5p+P57+UIA0KICAgICAgICAgIE4wNzQyMjAwMjIg6buD5ae/5qaVIg0KZGF0ZTogImByIFN5cy50aW1lKClgIg0Kb3V0cHV0OiANCiAgICAgICAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICAgICAgICBodG1sX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCmBgYHtyfQ0KIyDliIbmnpDmlofpm4bngrrjgIrlsIHnpZ7mvJTnvqnjgIst5L2c6ICFOumZs+S7sueQsyjmmI4pDQojIOi8ieWFpeWQhHBhY2thZ2VzDQpyZXF1aXJlKGRwbHlyKQ0KcmVxdWlyZSh0aWR5dGV4dCkNCnJlcXVpcmUoamllYmFSKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeSh3b3JkY2xvdWQyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShqaWViYVIpDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShkZXZ0b29scykNCmxpYnJhcnkoc3RyaW5naSkNCmxpYnJhcnkocGJhcHBseSkNCmxpYnJhcnkoUmNwcCkNCmxpYnJhcnkoUmNwcFByb2dyZXNzKQ0KbGlicmFyeShjaWRpYW4pICMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJnbjAxODMwNjU3L2NpZGlhbiIpDQpsaWJyYXJ5KHJvcGVuY2MpICMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJMY2hpZmZvbi9yb3BlbmNjIikNCmxpYnJhcnkoc2hvd3RleHQpICMgQzpcVXNlcnNcYm9jaGVcRG93bmxvYWRzXHNob3d0ZXh0XzAuOS02LnppcOebtOaOpeS9v+eUqFIgVG9vbHMtPkluc3RhbGwgUGFja2FnZXMuLi4NCiMg5Y+k6aiw5aCh57ay56uZ5LiL6LyJ44CK5bCB56We5ryU576p44CL57mB6auU5paH6ZuGDQpsaWJyYXJ5KGd1dGVuYmVyZ3IpDQpGZW5nc2hlbiA8LSBndXRlbmJlcmdfZG93bmxvYWQoMjM5MTApICU+JSBmaWx0ZXIodGV4dCE9IiIpICU+JSBkaXN0aW5jdChndXRlbmJlcmdfaWQsIHRleHQpDQpWaWV3KEZlbmdzaGVuKQ0KIyDkvb/nlKjmraPopo/ooajnpLrlvI/vvIzlsIflj6XlrZDljYDliIbnq6Dnr4DkuKbmlrflh7rlhbExfjEwMOeroOWbng0KRmVuZ3NoZW4gPC0gRmVuZ3NoZW4gJT4lIG11dGF0ZShjaGFwdGVyID0gY3Vtc3VtKHN0cl9kZXRlY3QoRmVuZ3NoZW4kdGV4dCwgcmVnZXgoIuesrC4q5ZueKFx1MDBhMHwkKSIpKSkpDQojIOaWh+mbhuW3sue2k+WujOaIkOaWt+WPpeS6hg0KaGVhZChGZW5nc2hlbiwgMjApDQojIOaWh+mbhuaWt+ipnu+8jOioreWumuaWt+ipnmZ1bmN0aW9u6IiH5YGc55So5a2Xc3RvcF93b3JkDQpqaWViYV90b2tlbml6ZXIgPC0gd29ya2VyKHVzZXI9IkZlbmdzaGVuLnRyYWRpdGlvbmFsLmRpY3QiLCBzdG9wX3dvcmQ9IkQ6L1IvMjAyMDAzMThfYm9va2NsdWJfMS9zdG9wX3dvcmRzLnR4dCIpDQojIHN0b3Bfd29yZD0iQzovVXNlcnMvU2Vhbi9Eb2N1bWVudHMvamllYmFyL3N0b3Bfd29yZHMudHh0IikNCkZlbmdzaGVuX3Rva2VuaXplciA8LSBmdW5jdGlvbih0KSB7DQogIGxhcHBseSh0LCBmdW5jdGlvbih4KSB7DQogICAgdG9rZW5zIDwtIHNlZ21lbnQoeCwgamllYmFfdG9rZW5pemVyKQ0KICAgIHJldHVybih0b2tlbnMpDQogIH0pDQp9DQp0b2tlbnMgPC0gRmVuZ3NoZW4gJT4lIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCwgdG9rZW49RmVuZ3NoZW5fdG9rZW5pemVyKQ0Kc3RyKHRva2VucykNCmhlYWQodG9rZW5zLCAyMCkgIyDlhajmlofmlrfoqZ7ntZDmnpwNCmBgYA0KYGBge3J9DQojIOmHjem7nuS4gDog6KiI566X5q+P5LiA56ug56+A5Ye654++5a2X5pW45aSn5pa8MeeahOipnuW9meWHuuePvueahOe4veasoeaVuA0KdG9rZW5zX2NvdW50X2NoYXB0ZXIgPC0gdG9rZW5zICU+JSANCiAgZmlsdGVyKG5jaGFyKC4kd29yZCk+MSkgJT4lIA0KICBncm91cF9ieShjaGFwdGVyLHdvcmQpICU+JSANCiAgZHBseXI6OnN1bW1hcmlzZShzdW0gPSBuKCkpICU+JQ0KICAjIHN1bW1hcmlzZShzdW0gPSBuKCkpICU+JQ0KICBhcnJhbmdlKGNoYXB0ZXIpIA0KdG9rZW5zX2NvdW50X2NoYXB0ZXIgPC0gdG9wX24odG9rZW5zX2NvdW50X2NoYXB0ZXIsMykgIyDlj6rpgbjlh7rliY3kuInlkI3nmoTlrZflvZkNCnRva2Vuc19jb3VudF9jaGFwdGVyDQojIOe1kOaenOWPr+S7peeZvOePvuesrOS4gOeroOWbnuWHuuePvuacgOWkmueahOaYr+Wls+Wqp+OAgeWkqeWtkOOAgee0gueOi+OAgeiruOS+r++8jOaOqOaWt+WPr+iDveesrOS4gOWbnuWcqOism+Wls+Wqp+WSjOe0gueOi+eahOaVheS6iw0KIyDliLAgaHR0cHM6Ly96aC53aWtpc291cmNlLm9yZy96aC1oYW50LyVFNSVCMCU4MSVFNyVBNSU5RSVFNiVCQyU5NCVFNyVCRSVBOSDlj6/ku6XnnIvliLDnrKzkuIDlm57mqJnpoYzmmK/vvJrntILnjovlpbPlqqflrq7pgLLpppkNCg0KIyDnuaroo73mlaPluIPlnJZY6Lu45piv56ug56+A77yMWei7uOaYr+ipnuW9meWHuuePvueahOasoeaVuA0KZ2dwbG90KHRva2Vuc19jb3VudF9jaGFwdGVyLCBhZXMoeCA9IGNoYXB0ZXIsIHkgPSBzdW0sICBjb2xvciA9IGFicyhzdW0pKSkgKyAgDQogIGdlb21faml0dGVyKGFscGhhID0gMC4xLCBzaXplID0gNSwgd2lkdGggPSAwLjUsIGhlaWdodCA9IDAuNSkgKyAgDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSB3b3JkKSwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHZqdXN0ID0gMS41KSArICANCiAgc2NhbGVfeV9sb2cxMCgpKw0KICBnZ3RpdGxlKCLlkITnq6Dnr4DnmoTlh7rnj77liY3kuInlpJrmrKHmlbjnmoTlrZciKSArIA0KICB5bGFiKGxhYmVsPSLlh7rnj77mrKHmlbgiKSsgIA0KICB4bGFiKGxhYmVsPSLlm57mlbgiKSANCiMg57WQ5p6c5Y+v5Lul55yL5Ye656ys5LiA5Zue5piv55Sx5aWz5aqn5L2c54K65pWF5LqL55qE6ZaL6aCt77yM5pyA5b6M5YmH5piv5Lul5q2m546L5L2c54K65pWF5LqL55qE57WQ5p2f77yM5Lmf5Y+v5Lul55m854++5ZCE5YCL6KeS6Imy5aSn5qaC5Ye654++55qE56ug5Zue5Zyo5ZOqDQpgYGANCmBgYHtyfQ0KIyDph43pu57kuow6DQojIOWwgeelnua8lOe+qeWFp+WuueWMheWQq+S6huioseWkmumBk+aVmeeahOWFg+e0oA0KIyDliIbmnpDlh7rnj77mnIDlpJrnmoTpgZPmlZnoqZ7lvZkNCiMg5Yyv5YWl6YGT5pWZ6Kme6Kqe6L6t5YW4DQojIGZpbGVFbmNvZGluZyA9IlVURi04Iu+8muS9v+eUqFLoroDlj5bmloflrZfmqpTmmYIsIOacieaZguacg+mBh+WIsOizh+aWmeWMr+WFpeaciemMr+iqpOioiuaBr+aIluS4reaWh+S6gueivOWVj+mhjA0KdGFvaXNtIDwtIHJlYWQudGFibGUoIi4vdGFvaXNtLnR4dCIsc2VwID0gIlxuIiAsaGVhZGVyID0gRkFMU0UsIGZpbGVFbmNvZGluZyA9IlVURi04IikNCiMgdGFvaXNtIDwtIHJlYWQudGFibGUoIi4vMjAyMDAzMThfYm9va2NsdWJfMS90YW9pc20udHh0IixzZXAgPSAiXG4iICxoZWFkZXIgPSBGQUxTRSwgZmlsZUVuY29kaW5nID0iVVRGLTgiKQ0KdGFvaXNtX2NvdW50IDwtIHRva2VucyAlPiUgDQogIGZpbHRlcihuY2hhciguJHdvcmQpID4gMSkgJT4lIA0KICBtdXRhdGUodG90YWwgPSBucm93KHRva2VucykpICU+JSANCiAgZ3JvdXBfYnkod29yZCkgJT4lIA0KICBmaWx0ZXIod29yZCAlaW4lIHRhb2lzbSRWMSkgJT4lIA0KICBkcGx5cjo6c3VtbWFyaXNlKGNvdW50ID0gbigpLCB0b3RhbCA9IG1lYW4odG90YWwpKSAlPiUNCiAgIyBzdW1tYXJpc2UoY291bnQgPSBuKCksIHRvdGFsID0gbWVhbih0b3RhbCkpICU+JQ0KICBtdXRhdGUocHJvcG9ydGlvbiA9IGNvdW50IC8gdG90YWwpICU+JSANCiAgYXJyYW5nZShkZXNjKGNvdW50KSkNCmhlYWQodGFvaXNtX2NvdW50LCAyMCkNCiMg57WQ5p6c5Y+v5Lul55m854++5Ye654++5pyA5aSa55qE6YGT5pWZ6Kme5b2Z54K6IuW7o+aIkOWtkCLvvIzlu6PmiJDlrZDngrrkuK3lnIvpgZPmlZnlgrPoqqrnmoTnpZ7ku5kNCiMg5o6o5pa35bCB56We5ryU576p5YWn6Zmk5LqG5pyJ6Kix5aSa5q235Y+y5Lq654mp5aSW77yM5buj5oiQ5a2Q5piv5Luj6KGo6YGT5pWZ55qE6YeN6KaB6KeS6ImyDQpgYGANCmBgYHtyfQ0KIyDoqIjnrpfpgZPmlZnoqZ7lvZnlnKjlkITnq6Dnr4Dlh7rnj77nmoTnuL3mlbjvvIzmjqjmlrfpgqPkupvnq6Dnr4Dlj6/og73lnKjorJvpgZPmlZnnmoTmlYXkuosNCnRhb2lzbV9jaGFwdGVyIDwtIHRva2VucyAlPiUgDQogIGZpbHRlcihuY2hhciguJHdvcmQpPjEpICU+JQ0KICBmaWx0ZXIod29yZCAlaW4lIHRhb2lzbSRWMSkgJT4lDQogIGdyb3VwX2J5KGNoYXB0ZXIpICU+JSAgDQogIGRwbHlyOjpzdW1tYXJpc2UoY291bnQgPSBuKCksIHR5cGUgPSAid29yZHMiKSU+JQ0KICAjIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgdHlwZSA9ICJ3b3JkcyIpJT4lDQogIGFycmFuZ2UoZGVzYyhjb3VudCkpIA0KdGFvaXNtX2NoYXB0ZXINCiMg57WQ5p6c5Y+v5Lul55yL5Ye65ZyoNzLlm57mmK/orJvliLDmnIDlpJrpgZPmlZnnm7jpl5zoqZ7lvZnnmoTnq6Dnr4ANCmBgYA0KYGBge3J9DQojIOioiOeul+mBk+aVmeipnuW9meWHuuePvuWcqOWQhOeroOevgOeahOmVt+aineWclg0KdGFvaXNtX3Bsb3QgPSB0b2tlbnMgJT4lIA0KICBmaWx0ZXIobmNoYXIoLiR3b3JkKT4xKSAlPiUNCiAgZmlsdGVyKHdvcmQgJWluJSB0YW9pc20kVjEpICU+JQ0KICBncm91cF9ieShjaGFwdGVyKSAlPiUgIA0KICBkcGx5cjo6c3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgIyBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBjaGFwdGVyLCB5PWNvdW50KSkgKw0KICBnZW9tX2NvbCgpICsgDQogIGdndGl0bGUoIuWQhOeroOevgOeahOmBk+aVmeipnuW9meWHuuePvue4veaVuCIpICsgDQogIHhsYWIoIueroOevgCIpICsgDQogIHlsYWIoIumBk+aVmeipnuW9meaVuOmHjyIpDQp0YW9pc21fcGxvdA0KIyDntZDmnpzlj6/ku6XnnIvlh7rlnKg3NeWbnuWJjeW+jOacieism+WIsOWkp+mHj+eahOmBk+aVmeebuOmXnOipnuW9mQ0KYGBgDQpgYGB7cn0NCiMg6YeN6bue5LiJOiDmlofpm4bkuK3op5LoibLkurrniakt5ZOq5ZCS55qE5o+P6L+wDQojIOeCuuS6huaJvuWHuuWTquWQkuatpuWZqO+8jOWwh+WOn+acrOaykuacieWNgOWIhuWHuuS+hueahOaWt+ipnuWKoOWFpQ0KbmV3X3VzZXJfd29yZChqaWViYV90b2tlbml6ZXIsIGMoIuWFqeagueeBq+WwluanjSIsICLkub7lnaTlnIgiLCAi5re35aSp57a+IiwgIuS5nem+jeelnueBq+e9qSIsIumZsOmZveWKjSIpKQ0KY2hpX3Rva2VuaXplciA8LSBmdW5jdGlvbiAodCkgew0KICBsYXBwbHkodCwgZnVuY3Rpb24oeCl7DQogICAgdG9rZW5zIDwtIHNlZ21lbnQoeCwgamllYmFfdG9rZW5pemVyKQ0KICAgIHRva2VucyA8LSB0b2tlbnNbbmNoYXIodG9rZW5zKSA+IDFdDQogICAgcmV0dXJuKHRva2VucykNCiAgfSkNCn0NCiMg5pa36KmeDQp0b2tlbnMgPC0gRmVuZ3NoZW4gJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCwgdG9rZW4gPSBjaGlfdG9rZW5pemVyKQ0KIyDlsIflk6rlkJLmrablmajnmoTnrYnlkIzoqZ7kv53nlZnmiJDorormlbgNCiPjgIrlsIHnpZ7mvJTnvqnjgIvkuK3mrablmajmmK/vvJrlhanmoLnngavlsJbmp43jgIHpoqjngavovKrjgIHkub7lnaTlnIjjgIHmt7flpKnntr7jgIHph5Hno5rjgIHkuZ3pvo3npZ7ngavnvanjgIHpmbDpmb3lio3vvIzlhbHlhavku7blhbXlmajvvIjlm6DngrrlsIHnpZ7kuK3lk6rlkJLmmK/kuInpoK3lhavoh4IpDQojIOS9huaYr+WOn+acrOWwgeelnua8lOe+qeaWt+ipnuWtl+WFuOS4re+8jOWwjeaWvOWTquWQkuatpuWZqOS4puaykuacieeJueWIpeWNgOWIpeWHuuS+hg0Kd2VhcG9uX2FsaWFzID0gYygi6YeR56OaIiwi6aKo54Gr6LyqIiwi5Lm+5Z2k5ZyIIiwi5re35aSp57a+Iiwi5YWp5qC554Gr5bCW5qeNIiwi5Lmd6b6N56We54Gr572pIiwi6Zmw6Zm95YqNIikNCiMg6KiI566X5ZOq5ZCS5q2m5Zmo5Zyo5pW05pys5pu45Lit5Ye654++55qE6aC7546H5Y+K5q+U5L6L77yM5Y+v5Lul55m854++5ZOq5ZCS5L2/55So6aKo54Gr6Lyq5qyh5pW45pyA5aSaDQp3ZWFwb25fY291bnQgPSB0b2tlbnMgJT4lIA0KICBmaWx0ZXIobmNoYXIoLiR3b3JkKT4xKSAlPiUgICAgICAgICAgICAgICAgICAgICAjIOS/neeVmemdnuepuueZveizh+aWmQ0KICBtdXRhdGUodG90YWwgPSBucm93KHRva2VucykpICU+JSAgICAgICAgICAgICAgICAjIOWKoOWFpeS4gOWAi3RvdGFsICjos4fmlpnnuL3liJfmlbgpICAgDQogIGdyb3VwX2J5KHdvcmQpICU+JSANCiAgZmlsdGVyKHdvcmQgJWluJSB3ZWFwb25fYWxpYXMpICU+JSAgICAgICAgICAgICAgIyDkv53nlZnoqZ4NCiAgZHBseXI6OnN1bW1hcmlzZShjb3VudCA9IG4oKSwgdG90YWwgPSBtZWFuKHRvdGFsKSkgJT4lDQogICMgc3VtbWFyaXNlKGNvdW50ID0gbigpLCB0b3RhbCA9IG1lYW4odG90YWwpKSAlPiUNCiAgbXV0YXRlKHByb3BvcnRpb24gPSBjb3VudCAvIHRvdGFsKSAlPiUgICAgICAgICAgIyDliqDlhaXmr5TkvovmrITkvY0NCiAgYXJyYW5nZShkZXNjKGNvdW50KSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyDmoLnmk5pjb3VudOashOS9je+8jOeUseWkp+iHs+Wwj+aOkuWIlw0KaGVhZCh3ZWFwb25fY291bnQsIDIwKQ0KYGBgDQpgYGB7cn0NCiMg6KiI566X5ZOq5ZCS5q2m5Zmo5Zyo5ZCE56ug56+A5Ye654++55qE57i95pW477yM5o6o5pa35ZOq5ZCS5Ye65aC05oqE5YKi5LyZ5pyA5aSa55qE56ug56+ADQp3ZWFwb25fY2hhcHRlciA8LSB0b2tlbnMgJT4lIA0KICBmaWx0ZXIobmNoYXIoLiR3b3JkKT4xKSAlPiUNCiAgZmlsdGVyKHdvcmQgJWluJSB3ZWFwb25fYWxpYXMpICU+JQ0KICBncm91cF9ieShjaGFwdGVyKSAlPiUgIA0KICBkcGx5cjo6c3VtbWFyaXNlKGNvdW50ID0gbigpLCB0eXBlID0gIndvcmRzIiklPiUNCiAgIyBzdW1tYXJpc2UoY291bnQgPSBuKCksIHR5cGUgPSAid29yZHMiKSU+JQ0KICBhcnJhbmdlKGRlc2MoY291bnQpKSANCndlYXBvbl9jaGFwdGVyDQojIOWPr+S7peeci+WHuuesrDEz56ug5Y+K56ysMzTnq6DnuL3ph4/mnIDlpJoNCiMg56ys5Y2B5LiJ56ug6Kyb5ZOq5ZCS5bm06LyV5pmC6YCj57qM6ZeW56aN77yM5q665a6z6b6N546L6Kaq5Lq644CB55+z56Ov5aiY5aiY55qE5byf5a2Q77yM5oqK6Ieq5bex55qE54i25q+N5bir54i26YO95ouW5LiL5rC077yM5LiA6Zmj5aSn5LqC6ayl5LmL5b6M77yMDQojIOecvOimi+Wbm+a1t+m+jeeOi+imgeS+huWPluS7lueItuavjeeahOaAp+WRve+8jOWTquWQkuWPquWlveiHquauuu+8jOeVmeS4i+S4gOe4t+W5vemtgu+8jOe1kOadn+mAmeWgtOmsp+WKh+OAgg0KIyDnrKzkuInljYHlm5vnq6DorJvmiJDlubTnmoTlk6rlkJLlpYnlpKrkuZnnnJ/kurrkuYvlkb3vvIzlpKfkuoLprKXmiZPmlZfpn5Pmpq7jgIHkvZnljJbvvIzmlZHlh7rpu4Ppo5vomY7jgIHpu4Pmu77niLblrZDvvIzkuKbmiZPpgJrmsZzmsLTpl5zorpPmrabmiJDnjovpu4Ppo5vomY7kuIDlrrbvvIwNCiMg5Y+v5Lul5aWU6LWw5Yiw6KW/5bKQ5oqV6Z2g5ZGo5q2m546L77yM5aKe5Yqg5ZGo546L5pyd55qE6LuN5LqL5Yqb6YeP44CCDQojIOW+numAmeWFqeeroOWPr+S7peeci+WIsOWTquWQkuaAp+agvOW+nuiuk+eItuavjemgreeXm+OAgeWPquacg+mXluemjemsp+S6i+eahOa1geawk++8jOibu+iuiueCuuaIkOeGn+S4puaHguW+l+mBi+eUqOiHqui6q+eahOWKm+mHj+aIkOWwseW7uuWci+Wkp+alreeahOelnuS7meOAgg0KYGBgDQpgYGB7cn0NCiMg5Y+v55u05o6l5L2/55So5a2X5Z6L5qqU5Lim5LiU5bCH5ZyW5b2i6Ly45Ye66Iez5ZCE56iu5ZyW5qqUDQojIExvYWRpbmcgR29vZ2xlIGZvbnRzIChodHRwczovL2ZvbnRzLmdvb2dsZS5jb20vKQ0KIyBmb250X2ZhbWlsaWVzX2dvb2dsZSgpIOWHveaVuOafpeeci2dvb2dsZeaPkOS+m+eahOWtl+Wei+WIl+ihqA0KDQpmb250X2FkZF9nb29nbGUoIk5vdG8gU2FucyBUQyIsICJOb3RvU2Fuc1RDIikgIyDlronoo53lrZfpq5TngrrmgJ3mupDpu5Hpq5QNCnNob3d0ZXh0X2F1dG8oZW5hYmxlID0gVFJVRSwgcmVjb3JkID0gVFJVRSkgIyDllZ/li5XlrZfpq5QNCg0KIyDoqIjnrpflk6rlkJLmrablmajlh7rnj77mnIDlpJrnmoTnq6Dnr4DntbHoqIjlnJYNCndlYXBvbl9wbG90ID0gdG9rZW5zICU+JSANCiAgZmlsdGVyKG5jaGFyKC4kd29yZCk+MSkgJT4lDQogIGZpbHRlcih3b3JkICVpbiUgd2VhcG9uX2FsaWFzKSAlPiUNCiAgZ3JvdXBfYnkoY2hhcHRlcikgJT4lICANCiAgZHBseXI6OnN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lDQogICMgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gY2hhcHRlciwgeT1jb3VudCkpICsNCiAgZ2VvbV9jb2woKSArIA0KICBnZ3RpdGxlKCLlkITnq6Dnr4DnmoTlk6rlkJLmrablmajlh7rnj77nuL3mlbgiKSArIA0KICB4bGFiKCLnq6Dnr4AiKSArIA0KICB5bGFiKCLlk6rlkJLmrablmajmlbjph48iKSArDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIk5vdG9TYW5zVEMiKSkgIyDliqDlhaXkuK3mloflrZflnovoqK3lrprvvIzpgb/lhY3kuK3mloflrZfpoa/npLrpjK/oqqQNCndlYXBvbl9wbG90DQpgYGANCmBgYHtyfQ0KIyDph43pu57lm5s6IOaDhee3kuWIhuaekA0KIyDmupblgplMSVdD5a2X5YW4KHV0ZjgpDQpwPC1yZWFkX2ZpbGUoIkQ6L1IvMjAyMDAzMThfYm9va2NsdWJfMS9kaWMvbGl3Yy9wb3NpdGl2ZS50eHQiKQ0KbjwtcmVhZF9maWxlKCJEOi9SLzIwMjAwMzE4X2Jvb2tjbHViXzEvL2RpYy9saXdjL25lZ2F0aXZlLnR4dCIpDQojIHA8LXJlYWRfZmlsZSgiQzovVXNlcnMvU2Vhbi9Eb2N1bWVudHMvMjAyMDAzMThfYm9va2NsdWJfMS9kaWMvbGl3Yy9wb3NpdGl2ZS50eHQiKQ0KIyBuPC1yZWFkX2ZpbGUoIkM6L1VzZXJzL1NlYW4vRG9jdW1lbnRzLzIwMjAwMzE4X2Jvb2tjbHViXzEvZGljL2xpd2MvbmVnYXRpdmUudHh0IikNCnBvc2l0aXZlIDwtIHN0cnNwbGl0KHAsICJbLF0iKVtbMV1dDQpuZWdhdGl2ZSA8LSBzdHJzcGxpdChuLCAiWyxdIilbWzFdXQ0KcG9zaXRpdmUgPC0gZGF0YS5mcmFtZSh3b3JkID0gcG9zaXRpdmUsIHNlbnRpbWVudHMgPSAicG9zaXRpdmUiKQ0KbmVnYXRpdmUgPC0gZGF0YS5mcmFtZSh3b3JkID0gbmVnYXRpdmUsIHNlbnRpZW10bnMgPSAibmVnYXRpdmUiKQ0KY29sbmFtZXMobmVnYXRpdmUpID0gYygid29yZCIsInNlbnRpbWVudCIpDQpjb2xuYW1lcyhwb3NpdGl2ZSkgPSBjKCJ3b3JkIiwic2VudGltZW50IikNCkxJV0NfY2ggPC0gcmJpbmQocG9zaXRpdmUsIG5lZ2F0aXZlKQ0KaGVhZChMSVdDX2NoLCAyMCkNCiMgTElXQ+Wtl+WFuOS4reacieWkmuWwkeato+mdouWWruipnuWSjOiyoOmdouWWruipng0KTElXQ19jaCAlPiUgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSBjb3VudChzZW50aW1lbnQpDQojIOS7pUxJV0PlrZflhbjliKTmlrfmlofpm4bkuK3nmoR3b3Jk5bGs5pa85q2j6Z2i5a2X6YKE5piv6LKg6Z2i5a2XDQojIOWFiOWwh3Rva2Vuc+aWt+ipnuW+jOeahOaWh+mbhumBjua/vuipnuW9me+8jOWPquacieS4gOWAi+Wtl+WJh+S4jeWIl+WFpeioiOeulyjkuZ/pgY7mv77mjonnqbrmoLwpDQphbGxfZmVuZ3NoZW5fd29yZHMgPC0gdG9rZW5zICU+JSBmaWx0ZXIobmNoYXIoLiR3b3JkKT4xKQ0KIyDoqIjnrpfmiYDmnInlkIToqZ7lvZnnmoTlh7rnj77nuL3mlbgNCmFsbF93b3JkX2NvdW50IDwtIGFsbF9mZW5nc2hlbl93b3JkcyAlPiUgZ3JvdXBfYnkod29yZCkgJT4lIGRwbHlyOjpzdW1tYXJpc2Uoc3VtID0gbigpKSAlPiUgYXJyYW5nZShkZXNjKHN1bSkpDQojIOioiOeul+aJgOacieWtl+WcqOaWh+mbhuS4reWHuuePvueahOe4veaVuA0Kd29yZF9jb3VudCA8LSBhbGxfd29yZF9jb3VudCAlPiUNCiAgc2VsZWN0KHdvcmQsc3VtKSAlPiUgDQogIGdyb3VwX2J5KHdvcmQpICU+JSANCiAgc3VtbWFyaXNlKHN1bSA9IHN1bShzdW0pKSAlPiUNCiAgZmlsdGVyKHN1bT4zKQ0KcHJpbnQod29yZF9jb3VudCkNCiMg6IiHTElXQ+aDhee3kuWtl+WFuGpvaW7vvIzmlofpm4bkuK3nmoTlrZflh7rnj77lnKhMSVdD5a2X5YW45Lit5piv5bGs5pa8cG9zaXRpdmXpgoTmmK9uZWdhdGl2ZQ0KbGl3Y2hfY2hfd29yZF9jb3VudHM8LXdvcmRfY291bnQgJT4lIGlubmVyX2pvaW4oTElXQ19jaCkNCnByaW50KGxpd2NoX2NoX3dvcmRfY291bnRzKQ0KbGl3Y2hfY2hfd29yZF9jb3VudHMgJT4lIGZpbHRlcihzZW50aW1lbnQgJWluJSBjKCJwb3NpdGl2ZSIsICJuZWdhdGl2ZSIpKSAlPiUgY291bnQoc2VudGltZW50KQ0KIyDnuarlnJblh7rku6VMSVdD5a2X5YW457Wx6KiI55qE5paH6ZuG5oOF57eS5a2X5pW477yM6KeA5a+f5YWp56iu5oOF57eS5YC855qE5beu55WwDQpsaXdjaF9jaF93b3JkX2NvdW50cyAlPiUNCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUNCiAgdG9wX24oMTAsd3QgPSBzdW0pICU+JQ0KICB1bmdyb3VwKCkgJT4lIA0KICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgc3VtKSkgJT4lDQogIGdncGxvdChhZXMod29yZCwgc3VtLCBmaWxsID0gc2VudGltZW50KSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGZhY2V0X3dyYXAofnNlbnRpbWVudCwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgbGFicyh5ID0gIkNvbnRyaWJ1dGlvbiB0byBzZW50aW1lbnQiLA0KICAgICAgIHggPSBOVUxMKSArDQogIHRoZW1lKHRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTQpKSsNCiAgY29vcmRfZmxpcCgpDQpgYGANCmBgYHtyfQ0KIyDmg4Xnt5LliIbmnpAo57qMKQ0KIyDmupblgplOVFVTROWtl+WFuCh1dGY4KQ0KcG48LXJlYWRfZmlsZSgiLi9kaWMvbnR1c2QvcG9zaXRpdmUudHh0IikNCm5uPC1yZWFkX2ZpbGUoIi4vZGljL250dXNkL25lZ2F0aXZlLnR4dCIpDQojIHBuPC1yZWFkX2ZpbGUoIkM6L1VzZXJzL1NlYW4vRG9jdW1lbnRzLzIwMjAwMzE4X2Jvb2tjbHViXzEvZGljL250dXNkL3Bvc2l0aXZlLnR4dCIpDQojIG5uPC1yZWFkX2ZpbGUoIkM6L1VzZXJzL1NlYW4vRG9jdW1lbnRzLzIwMjAwMzE4X2Jvb2tjbHViXzEvZGljL250dXNkL25lZ2F0aXZlLnR4dCIpDQpwczwtc3Ryc3BsaXQocG4sICJbXG5dIilbWzFdXQ0KcG9zaXRpdmVfbnR1c2Q8LXN0cnNwbGl0KHBzLCAiW1xyXSIpDQpuczwtc3Ryc3BsaXQobm4sICJbXG5dIilbWzFdXQ0KbmVnYXRpdmVfbnR1c2Q8LXN0cnNwbGl0KG5zLCAiW1xyXSIpDQojIOeUqHVubGlzdOaLhuWIhmxpc3Tlvozph43mp4vnn6npmaPnhLblvozovYnmj5vngrpkYXRhZnJhbWUNCnBvc2l0aXZlX250dXNkPC1kYXRhLmZyYW1lKG1hdHJpeCh1bmxpc3QocG9zaXRpdmVfbnR1c2QpLCBucm93PTI4MTIsIG5jb2w9MSwgYnlyb3c9Riksc2VudGltZW50cz0icG9zaXRpdmUiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KY29sbmFtZXMocG9zaXRpdmVfbnR1c2QpPC1jKCJ3b3JkIiwgInNlbnRpbWVudCIpDQpuZWdhdGl2ZV9udHVzZDwtZGF0YS5mcmFtZShtYXRyaXgodW5saXN0KG5lZ2F0aXZlX250dXNkKSwgbnJvdz04Mjc2LCBuY29sPTEsIGJ5cm93PUYpLCBzZW50aW1lbnRzPSJuZWdhdGl2ZSIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCmNvbG5hbWVzKG5lZ2F0aXZlX250dXNkKTwtYygid29yZCIsInNlbnRpbWVudCIpDQpOVFVTRF9jaDwtcmJpbmQocG9zaXRpdmVfbnR1c2QsIG5lZ2F0aXZlX250dXNkKQ0KaGVhZChOVFVTRF9jaCwgMjApDQojIE5UVVNE5a2X5YW45Lit5pyJ5aSa5bCR5q2j6Z2i5Zau6Kme5ZKM6LKg6Z2i5Zau6KmeDQpOVFVTRF9jaCAlPiUgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSBjb3VudChzZW50aW1lbnQpDQojIOS7pU5UVVNE5a2X5YW45Yik5pa35paH6ZuG5Lit55qEd29yZOWxrOaWvOato+mdouWtl+mChOaYr+iyoOmdouWtlw0KIyDlhYjlsId0b2tlbnPmlrfoqZ7lvoznmoTmlofmnKzpgY7mv77oqZ7lvZnvvIzlj6rmnInkuIDlgIvlrZfliYfkuI3liJflhaXoqIjnrpco5Lmf6YGO5r++5o6J56m65qC8KQ0KYWxsX2ZlbmdzaGVuX3dvcmRzX250dXNkIDwtIHRva2VucyAlPiUgZmlsdGVyKG5jaGFyKC4kd29yZCk+MSkNCiMg6KiI566X5omA5pyJ5ZCE6Kme5b2Z55qE5Ye654++57i95pW4DQphbGxfd29yZF9jb3VudF9udHVzZCA8LSBhbGxfZmVuZ3NoZW5fd29yZHNfbnR1c2QgJT4lIGdyb3VwX2J5KHdvcmQpICU+JSBzdW1tYXJpc2Uoc3VtID0gbigpKSAlPiUgYXJyYW5nZShkZXNjKHN1bSkpDQojIOioiOeul+aJgOacieWtl+WcqOaWh+mbhuS4reWHuuePvueahOe4veaVuA0Kd29yZF9jb3VudF9udHVzZCA8LSBhbGxfd29yZF9jb3VudF9udHVzZCAlPiUNCiAgc2VsZWN0KHdvcmQsc3VtKSAlPiUgDQogIGdyb3VwX2J5KHdvcmQpICU+JSANCiAgc3VtbWFyaXNlKHN1bSA9IHN1bShzdW0pKSAlPiUNCiAgZmlsdGVyKHN1bT4zKQ0KcHJpbnQod29yZF9jb3VudF9udHVzZCkNCiMg6IiHTlRVU0Tmg4Xnt5LlrZflhbhqb2lu77yM5paH6ZuG5Lit55qE5a2X5Ye654++5ZyoTlRVU0TlrZflhbjkuK3mmK/lsazmlrxwb3NpdGl2ZemChOaYr25lZ2F0aXZlDQpudHVzZF9jaF93b3JkX2NvdW50czwtd29yZF9jb3VudF9udHVzZCAlPiUgaW5uZXJfam9pbihOVFVTRF9jaCkNCnByaW50KG50dXNkX2NoX3dvcmRfY291bnRzKQ0KbnR1c2RfY2hfd29yZF9jb3VudHMgJT4lIGZpbHRlcihzZW50aW1lbnQgJWluJSBjKCJwb3NpdGl2ZSIsICJuZWdhdGl2ZSIpKSAlPiUgY291bnQoc2VudGltZW50KQ0KIyDnuarlnJblh7rku6VOVFVTROWtl+WFuOe1seioiOeahOaWh+mbhuaDhee3kuWtl+aVuO+8jOingOWvn+WFqeeoruaDhee3kuWAvOeahOW3rueVsA0KbnR1c2RfY2hfd29yZF9jb3VudHMgJT4lDQogIGdyb3VwX2J5KHNlbnRpbWVudCkgJT4lDQogIHRvcF9uKDEwLHd0ID0gc3VtKSAlPiUNCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIHN1bSkpICU+JQ0KICBnZ3Bsb3QoYWVzKHdvcmQsIHN1bSwgZmlsbCA9IHNlbnRpbWVudCkpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH5zZW50aW1lbnQsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIGxhYnMoeSA9ICJDb250cmlidXRpb24gdG8gc2VudGltZW50IiwNCiAgICAgICB4ID0gTlVMTCkgKw0KICB0aGVtZSh0ZXh0PWVsZW1lbnRfdGV4dChzaXplPTE0KSkrDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQpgYGB7cn0NCiMg6YeN6bue5LqUOiDmloflrZfpm7INCiMg6KiI566X6Kme5b2Z55qE5Ye654++5qyh5pW477yM5aaC5p6c6Kme5b2Z5Y+q5pyJ5LiA5YCL5a2X5YmH5LiN5YiX5YWl6KiI566XDQp0b2tlbnNfY291bnQgPC0gdG9rZW5zICU+JSANCiAgZmlsdGVyKG5jaGFyKC4kd29yZCk+MSkgJT4lDQogIGdyb3VwX2J5KHdvcmQpICU+JSANCiAgc3VtbWFyaXNlKHN1bSA9IG4oKSkgJT4lIA0KICBmaWx0ZXIoc3VtPjEwKSAlPiUNCiAgYXJyYW5nZShkZXNjKHN1bSkpDQpoZWFkKHRva2Vuc19jb3VudCwgMjApDQojIOaWh+Wtl+mbsuWIhuW4g+ipnuW9mSAo5YWo5paH6ZuGKQ0KdG9rZW5zX2NvdW50ICU+JSB3b3JkY2xvdWQyKCkNCiMg57mq5ZyW5Ye644CK5bCB56We5ryU576p44CL5ZCE56ug56+A6ZW35bqm77yM5Lul6Kqe5Y+l5pW45L6G6KiI566XDQpmZW5nc2hlbl9wbG90IDwtIGJpbmRfcm93cyhGZW5nc2hlbiAlPiUgZ3JvdXBfYnkoY2hhcHRlcikgJT4lIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgdHlwZT0ic2VudGVuY2VzIiksIHRva2VucyAlPiUgZ3JvdXBfYnkoY2hhcHRlcikgJT4lIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgdHlwZT0id29yZHMiKSkgJT4lIGdyb3VwX2J5KHR5cGUpICU+JSBnZ3Bsb3QoYWVzKHggPSBjaGFwdGVyLCB5PWNvdW50LCBmaWxsPSJ0eXBlIiwgY29sb3I9ZmFjdG9yKHR5cGUpKSkgKyBnZW9tX2xpbmUoKSArIA0KZ2d0aXRsZSgi5ZCE56ug56+A55qE5Y+l5a2Q57i95pW4IikgKyB4bGFiKCLnq6Dnr4AiKSArIHlsYWIoIuWPpeWtkOaVuOmHjyIpIA0KcHJpbnQoZmVuZ3NoZW5fcGxvdCkNCiMg6KiI566X5YmN5LqU5Y2B5Zue5ZKM5b6M5LqU5Y2B5Zue55qE6Kme5b2Z5Zyo5YWo5paH5Lit5Ye654++5q+U546H55qE5beu55WwDQpmcmVxdWVuY3kxIDwtIHRva2VucyAlPiUgbXV0YXRlKHBhcnQgPSBpZmVsc2UoY2hhcHRlcjw9NTAsICJGaXJzdDUwIiwgIkxhc3Q1MCIpKSAlPiUNCiAgZmlsdGVyKG5jaGFyKC4kd29yZCk+MSkgJT4lDQogIG11dGF0ZSh3b3JkID0gc3RyX2V4dHJhY3Qod29yZCwgIlteMC05YS16J10rIikpICU+JQ0KICBtdXRhdGUod29yZCA9IHN0cl9leHRyYWN0KHdvcmQsICJbXuS4gOS6jOS4ieWbm+S6lOWFreS4g+WFq+S5neWNgV0rIikpDQphdHRhY2goZnJlcXVlbmN5MSkNCmZyZXF1ZW5jeTIgPC0gZnJlcXVlbmN5MSAlPiUgDQogIGNvdW50KHBhcnQsIHdvcmQpICU+JQ0KICBncm91cF9ieShwYXJ0KSAlPiUNCiAgbXV0YXRlKHByb3BvcnRpb24gPSBuIC8gc3VtKG4pKSAlPiUgDQogIHNlbGVjdCgtbikgJT4lIA0KICBzcHJlYWQocGFydCwgcHJvcG9ydGlvbikgJT4lIA0KICBnYXRoZXIocGFydCwgcHJvcG9ydGlvbiwgYExhc3Q1MGApDQojIOWMr+WHuuWcluihqA0KZ2dwbG90KGZyZXF1ZW5jeTIsIGFlcyh4ID0gcHJvcG9ydGlvbiwgeSA9IGBGaXJzdDUwYCwgY29sb3IgPSBhYnMoYEZpcnN0NTBgIC0gcHJvcG9ydGlvbikpKSArDQogIGdlb21fYWJsaW5lKGNvbG9yID0gImdyYXk1MCIsIGx0eSA9IDIpICsNCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjEsIHNpemUgPSAyLjUsIHdpZHRoID0gMC4zLCBoZWlnaHQgPSAwLjMpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHdvcmQpLCBjaGVja19vdmVybGFwID0gVFJVRSwgdmp1c3QgPSAxLjUpICsNCiAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBwZXJjZW50X2Zvcm1hdCgpKSArDQogIHNjYWxlX3lfbG9nMTAobGFiZWxzID0gcGVyY2VudF9mb3JtYXQoKSkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsaW1pdHMgPSBjKDAsIDAuMDAxKSwgbG93ID0gImRhcmtzbGF0ZWdyYXk0IiwgaGlnaCA9ICJncmF5NzUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsNCiAgbGFicyh5ID0gIkZpcnM1MCIsIHggPSAiTGFzdDUwIikNCiMg6YeN6bue5YWtOiDmr5TovIPjgIrlsIHnpZ7mvJTnvqnjgIvliY3kupTljYHlm57lkozlvozkupTljYHlm57mnIDluLjlh7rnj77nmoTliY0xMDDlgIvoqZ7lvZnnmoTlt67nlbANCm9mdGVuX3dvcmRzIDwtIHRva2VucyAlPiUgbXV0YXRlKHBhcnQgPSBpZmVsc2UoY2hhcHRlcjw9NTAsICJGaXJzdDUwIiwgIkxhc3Q1MCIpKSAlPiUgZmlsdGVyKG5jaGFyKC4kd29yZCk+MSkNCmZpcnN0XzUwPC1hc190aWJibGUoaGVhZChvZnRlbl93b3JkcywgODAyMjMpKQ0KbGFzdF81MDwtYXNfdGliYmxlKHRhaWwob2Z0ZW5fd29yZHMsIDg2NzY0KSkNCiMg6KiI566X6Kme5b2Z55qE5Ye654++5qyh5pW477yM5aaC5p6c6Kme5b2Z5Y+q5pyJ5LiA5YCL5a2X5YmH5LiN5YiX5YWl6KiI566XDQpmaXJzdDUwX2NvdW50IDwtIGZpcnN0XzUwICU+JSBmaWx0ZXIobmNoYXIoLiR3b3JkKT4xKSAlPiUgZ3JvdXBfYnkod29yZCkgJT4lIHN1bW1hcmlzZShzdW0gPSBuKCkpICU+JSBmaWx0ZXIoc3VtPjEwKSAlPiUgYXJyYW5nZShkZXNjKHN1bSkpDQpsYXN0NTBfY291bnQgPC0gbGFzdF81MCAlPiUgZmlsdGVyKG5jaGFyKC4kd29yZCk+MSkgJT4lIGdyb3VwX2J5KHdvcmQpICU+JSBzdW1tYXJpc2Uoc3VtID0gbigpKSAlPiUgZmlsdGVyKHN1bT4xMCkgJT4lIGFycmFuZ2UoZGVzYyhzdW0pKQ0KcHJpbnQoZmlyc3Q1MF9jb3VudCk7cHJpbnQobGFzdDUwX2NvdW50KQ0KIyDoppboprrljJbplbfmop3lnJYNCmZpcnN0NTBfYmFyIDwtIGZpcnN0NTBfY291bnQgJT4lIGZpbHRlcihzdW0+MTAwKSAlPiUgbXV0YXRlKHdvcmQ9cmVvcmRlcih3b3JkLCBzdW0pKSAlPiUgZ2dwbG90KGFlcyh3b3JkLCBzdW0pKSArIGdlb21fY29sKCkgKyB4bGFiKE5VTEwpICsgY29vcmRfZmxpcCgpDQpsYXN0NTBfYmFyIDwtIGxhc3Q1MF9jb3VudCAlPiUgZmlsdGVyKHN1bT4xMDApICU+JSBtdXRhdGUod29yZD1yZW9yZGVyKHdvcmQsIHN1bSkpICU+JSBnZ3Bsb3QoYWVzKHdvcmQsIHN1bSkpICsgZ2VvbV9jb2woKSArIHhsYWIoTlVMTCkgKyBjb29yZF9mbGlwKCkNCiMg5bCH5YWp5by1Z2dwbG905ZyW5ZCI5L2154K65LiA5by15ZyWDQpsaWJyYXJ5KFJtaXNjKQ0KbXVsdGlwbG90KGZpcnN0NTBfYmFyLCBsYXN0NTBfYmFyLCBjb2xzPTIpDQpgYGA=