About


Sys.setenv(LANG = "en")
#library("rstudioapi") #to grab local position of the script
#setwd(dirname(rstudioapi::getActiveDocumentContext()$path))
knitr::opts_knit$set(root.dir = '.')

#library("rvest") # to handle html stuff

library(lubridate) # to handle dates

library(ggplot2) # for plotting
library(cowplot) # for plotting
library(RColorBrewer) # for choosing colors

custompalette <- brewer.pal(n=8, name = 'Dark2')

library(knitr) # for tables
library(kableExtra) # for tables

library(lubridate) # for dates

library(plyr) # ddply, to summarize number of words by author

load('TDP_worksData.RData')

This is a document detailing analysis of The Dragon Prince (Cartoon) Ao3 tag data, collected on the 7 Aug 2020. I haven’t figured out a way to get my scrapper to log in into Ao3 (yet? rvest seems to have some trouble with page redirects), so results here are based on the works visible without authentication, which likely filters out preferentially explicit/problemantic works from the selection.


plot_bar <- function (data, columnX, legendPosition) {
    ggplot(data, aes_string(x = columnX)) + 
    geom_bar(alpha=1)+
    theme_half_open() +
    background_grid() +
    theme(legend.title=element_blank(),
          axis.title.x = element_blank(),
          axis.text.x = element_text(angle = 90, vjust = 1, hjust=1))+
    labs(y="Number of works")
}

plot_bar_color <- function (data, columnX, colColor, legendPosition) {
    ggplot(data, aes_string(x = columnX, fill=colColor)) + 
    geom_bar(alpha=0.7)+
    scale_fill_manual(values = custompalette) +
    theme_half_open() +
    background_grid() +
    theme(legend.title=element_blank(),
          axis.title.x = element_blank(),
          axis.text.x = element_text(angle = 90, vjust = 1, hjust=1))+
    labs(y="Number of works")
}

plot_col <- function (data, columnX, columnY, legendPosition) {
    ggplot(data, aes_string(x = columnX, y = columnY)) + 
    geom_col(alpha=1)+
    theme_half_open() +
    background_grid() +
    theme(legend.title=element_blank(),
          axis.title.x = element_blank(),
          axis.text.x = element_text(angle = 90, vjust = 1, hjust=1))+
    labs(y=gsub('\\.', ' ', columnY))
  
}

plot_col_color <- function (data, columnX, columnY, colColor, legendPosition) {
    ggplot(data, aes_string(x = columnX, y = columnY, fill=colColor)) + 
    geom_col(alpha=0.7)+
    scale_fill_manual(values = custompalette) +
    theme_half_open() +
    background_grid() +
    theme(legend.title=element_blank(),
          axis.title.x = element_blank(),
          axis.text.x = element_text(angle = 90, vjust = 1, hjust=1))+
    labs(y=gsub('\\.', ' ', columnY))
  
}

plot_percentiles <- function (data, columnX, columnY, legendPosition) {
    ggplot(data, aes_string(x = columnX, y = columnY)) + 
    geom_point(alpha=0.3)+
    scale_y_log10(breaks = 10^c(0:15))+
    scale_x_continuous(breaks = c(0, 25, 50, 75, 100))+ #scale_x_continuous(breaks = c(0:10)*10)+
    theme_half_open() +
    background_grid() +
    theme(legend.title=element_blank())+
    labs(x=gsub('\\.', ' ', columnX))
}

plot_density <- function (data, column, color_column, legendPosition) {
    ggplot(data, aes_string(x = column, col=color_column)) + 
    geom_density(alpha = 0.1)+
    scale_x_log10()+
    theme_half_open() +
    background_grid() +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
          legend.position = legendPosition)
}

plot_points <- function (data, columnX, columnY, color_column, legendPosition) {
    ggplot(data, aes_string(x = columnX, y = columnY, col=color_column)) + 
    geom_point(alpha=0.3)+
    scale_x_log10()+
    scale_y_log10()+
    facet_wrap(color_column)+
    theme_half_open() +
    background_grid() +
    theme(legend.title=element_blank(),
          axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
}
#title <- lapply(worksData, function(x) {x$Title})
author <- lapply(worksData, function(x) {x$Author})
fandom <- lapply(worksData, function(x) {x$Fandom})
rating <- lapply(worksData, function(x) {x$Rating})
warnings <- lapply(worksData, function(x) {x$Warnings})
category <- lapply(worksData, function(x) {x$Category})
WIP <- lapply(worksData, function(x) {x$WIP})
date <-lapply(worksData, function(x) {x$Date})
relationships <-lapply(worksData, function(x) {x$Relationships})
character <-lapply(worksData, function(x) {x$Character})
freeform <-lapply(worksData, function(x) {x$Freeform})
language <-lapply(worksData, function(x) {x$Language})
words <-lapply(worksData, function(x) {x$Words})
kudos <-lapply(worksData, function(x) {x$Kudos})
comments <-lapply(worksData, function(x) {x$Comments})
bookmarks<-lapply(worksData, function(x) {x$Bookmarks})
hits <-lapply(worksData, function(x) {x$Hits})

stats <- data.frame(Words = unlist(words, recursive = FALSE),
                    Comments= as.numeric(as.character(comments)),
                    Kudos = as.numeric(as.character(kudos)),
                    Bookmarks = as.numeric(as.character(bookmarks)),
                    Hits = as.numeric(as.character(hits)),
                    WIP = unlist(WIP, recursive = FALSE),
                    Rating = unlist(rating, recursive = FALSE),
                    Date = do.call("c", date))

stats$Rating <- factor(stats$Rating, levels = c("Not Rated", "General Audiences", "Teen And Up Audiences", "Mature", "Explicit"))

total <- 1000
percentile <- c(1:total)
percentileData <- data.frame(Works.Percentile = 100*(total - percentile)/total,
                             Words = unlist(lapply(percentile/total, quantile, x = unlist(words))) + 1,
                             Hits = unlist(lapply(percentile/total, quantile, x = unlist(hits))) + 1,
                             Kudos = unlist(lapply(percentile/total, quantile, x = unlist(kudos))) + 1,
                             Comments = unlist(lapply(percentile/total, quantile, x = unlist(comments))) + 1,
                             Bookmarks = unlist(lapply(percentile/total, quantile, x = unlist(bookmarks))) + 1 )

rm(rating, kudos, comments, bookmarks, hits)

Timeline

Vertical lines on the graph indicate season release dates according to Wiki article. As expected, after each new season, there’s a peak of activity which fades in about 2 months. Curiously, a few works were posted before official season 1 release. This may be attributed to the series trailer drop in July 2018 at the San Diego Comic-Con.


#data$Timestamp <- parse_date_time2(as.character(data$Timestamp), orders = "%d/%m/%Y %H:%M:%S")
#data$day <- as.Date(data$Timestamp)

seasons <- c("2018-09-14", "2019-02-15", "2019-11-22")
seasons <- as.Date(seasons)

dates <- data.frame(date = do.call("c", date),
                    WIP = unlist(WIP))

plotDatesDensityTotal <- ggplot(stats, aes(x = Date)) + 
                    geom_density(alpha = 0.1)+
                    geom_vline(xintercept=seasons)+
                    scale_x_date(date_breaks="2 months")+
                    theme_half_open() +
                    background_grid() +
                    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
                          legend.position = 'right')
plotDatesDensityTotal


rm(plotDatesDensityTotal)

It’s important to note, that I collect data from the Ao3 search page (rather than works pages, as it’s less disruptive), so I don’t have access to initial postage dates, only the latest updates. This means that the upward trend in works over time can be an artifact of series getting more popular, but also could be attributed to multichapter works drifting further in time due to updates.

If we plot Complete Works and Works in Progress separately, we still see an upward trend in both, but the slope characterising the growth of Work In progress peaks seems steeper than for Complete Works, which to me indicates at least partial effect of the multichapter drift.


plotDatesDensity <- ggplot(stats, aes(x = Date, col=WIP)) + 
                    geom_density(alpha = 0.1)+
                    geom_vline(xintercept=seasons)+
                    scale_x_date(date_breaks="2 months")+
                    theme_half_open() +
                    background_grid() +
                    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
                          legend.position = 'right')
plotDatesDensity


rm(plotDatesDensity)

Engagement percentiles

Small plotting cheat: all the numbers on the Y axis are increased by 1 to include the case of 0 into the plot (otherwise excluded because of log scale).

wordsPercentiles <- plot_percentiles(percentileData, 'Works.Percentile', 'Words', 'right')
hitsPercentiles <- plot_percentiles(percentileData, 'Works.Percentile', 'Hits', 'right')
kudosPercentiles <- plot_percentiles(percentileData, 'Works.Percentile', 'Kudos', 'right')
commentsPercentiles <- plot_percentiles(percentileData, 'Works.Percentile', 'Comments', 'right')
bookmarksPercentiles <- plot_percentiles(percentileData, 'Works.Percentile', 'Bookmarks', 'right')

plot_grid(wordsPercentiles + theme(legend.position="none"),
          hitsPercentiles + theme(legend.position="none"),
          kudosPercentiles + theme(legend.position="none"),
          commentsPercentiles + theme(legend.position="none"),
          bookmarksPercentiles + theme(legend.position="none"),
          get_legend(kudosPercentiles +
                     theme(legend.title=element_blank())))


rm(total, percentile, percentileData, wordsPercentiles, hitsPercentiles, kudosPercentiles, commentsPercentiles, bookmarksPercentiles)

Complete Work vs Work in Progress distributions


statsWIP <- stats
statsWIP$Divisor <- unlist(lapply(statsWIP$WIP, function(x) summary(statsWIP$WIP)[names(summary(statsWIP$WIP)) == x]))
statsWIP$Words.per.Work <- statsWIP$Words/statsWIP$Divisor
statsWIP$Hits.per.Work <- statsWIP$Hits/statsWIP$Divisor
statsWIP$Kudos.per.Work <- statsWIP$Kudos/statsWIP$Divisor
statsWIP$Comments.per.Work <- statsWIP$Comments/statsWIP$Divisor
statsWIP$Bookmarks.per.Work <- statsWIP$Bookmarks/statsWIP$Divisor

barWorksWIP <- plot_bar(statsWIP, 'WIP', 'right')
barWordsWIP <- plot_col(statsWIP, 'WIP', 'Words.per.Work', 'right')
barHitsWIP <- plot_col(statsWIP, 'WIP', 'Hits.per.Work', 'right')
barKudosWIP <- plot_col(statsWIP, 'WIP', 'Kudos.per.Work', 'right')
barCommentsWIP <- plot_col(statsWIP, 'WIP', 'Comments.per.Work', 'right')
barBookmarksWIP <- plot_col(statsWIP, 'WIP', 'Bookmarks.per.Work', 'right')

# plot_grid(plot_grid( barWorksWIP + theme(legend.position="none"),
#                      barWordsWIP + theme(legend.position="none"),
#                      barHitsWIP + theme(legend.position="none"),
#                      barKudosWIP + theme(legend.position="none"),
#                      barCommentsWIP + theme(legend.position="none"),
#                      barBookmarksWIP + theme(legend.position="none"),
#                      align = 'hv'),
#           get_legend(barWorksWIP + theme(legend.title=element_blank())),
#           rel_widths = c(4,1),
#           align = 'hv')
plot_grid( barWorksWIP + theme(legend.position="none"),
           barWordsWIP + theme(legend.position="none"),
           barHitsWIP + theme(legend.position="none"),
           barKudosWIP + theme(legend.position="none"),
           barCommentsWIP + theme(legend.position="none"),
           barBookmarksWIP + theme(legend.position="none"),
           align = 'hv')


rm(statsWIP, barWorksWIP, barWordsWIP, barHitsWIP, barKudosWIP, barCommentsWIP, barBookmarksWIP)
wordsDensityWIP <- plot_density(stats, 'Words', 'WIP', 'right')
hitsDensityWIP <- plot_density(stats, 'Hits', 'WIP', 'right')
kudosDensityWIP <- plot_density(stats, 'Kudos', 'WIP', 'right')
commentsDensityWIP <- plot_density(stats, 'Comments', 'WIP', 'right')
bookmarksDensityWIP <- plot_density(stats, 'Bookmarks', 'WIP', 'right')
  
plot_grid(wordsDensityWIP + theme(legend.position="none"),
          hitsDensityWIP + theme(legend.position="none"),
          kudosDensityWIP + theme(legend.position="none"),
          commentsDensityWIP + theme(legend.position="none"),
          bookmarksDensityWIP + theme(legend.position="none"),
          get_legend(kudosDensityWIP +
                     theme(legend.title=element_blank())))

rm(wordsDensityWIP, hitsDensityWIP, kudosDensityWIP, commentsDensityWIP, bookmarksDensityWIP)
wordsHitsWIP <- plot_points(stats, 'Words', 'Hits', 'WIP', 'right')
wordsHitsWIP
wordsKudosWIP <- plot_points(stats, 'Words', 'Kudos', 'WIP', 'right')
wordsKudosWIP
wordsCommentsWIP <- plot_points(stats, 'Words', 'Comments', 'WIP', 'right')
wordsCommentsWIP
wordsBookmarksWIP <- plot_points(stats, 'Words', 'Bookmarks', 'WIP', 'right')
wordsBookmarksWIP

rm(wordsHitsWIP, wordsKudosWIP, wordsCommentsWIP, wordsBookmarksWIP)

Rating distributions


statsRating <- stats
statsRating$Divisor <- unlist(lapply(statsRating$Rating, function(x) summary(statsRating$Rating)[names(summary(statsRating$Rating)) == x]))
statsRating$Words.per.Work <- statsRating$Words/statsRating$Divisor
statsRating$Hits.per.Work <- statsRating$Hits/statsRating$Divisor
statsRating$Kudos.per.Work <- statsRating$Kudos/statsRating$Divisor
statsRating$Comments.per.Work <- statsRating$Comments/statsRating$Divisor
statsRating$Bookmarks.per.Work <- statsRating$Bookmarks/statsRating$Divisor

barWorksRating <- plot_bar(statsRating, 'Rating', 'right')
barWordsRating <- plot_col(statsRating, 'Rating', 'Words.per.Work', 'right')
barHitsRating <- plot_col(statsRating, 'Rating', 'Hits.per.Work', 'right')
barKudosRating <- plot_col(statsRating, 'Rating', 'Kudos.per.Work', 'right')
barCommentsRating <- plot_col(statsRating, 'Rating', 'Comments.per.Work', 'right')
barBookmarksRating <- plot_col(statsRating, 'Rating', 'Bookmarks.per.Work', 'right')

plot_grid( barWorksRating + theme(legend.position="none"),
           barWordsRating + theme(legend.position="none"),
           barHitsRating + theme(legend.position="none"),
           barKudosRating + theme(legend.position="none"),
           barCommentsRating + theme(legend.position="none"),
           barBookmarksRating + theme(legend.position="none"),
           align = 'hv')


rm(statsRating, barWorksRating, barWordsRating, barHitsRating, barKudosRating, barCommentsRating, barBookmarksRating)
wordsDensityRating <- plot_density(stats, 'Words', 'Rating', 'right')
hitsDensityRating <- plot_density(stats, 'Hits', 'Rating', 'right')
kudosDensityRating <- plot_density(stats, 'Kudos', 'Rating', 'right')
commentsDensityRating <- plot_density(stats, 'Comments', 'Rating', 'right')
bookmarksDensityRating <- plot_density(stats, 'Bookmarks', 'Rating', 'right')

plot_grid(wordsDensityRating + theme(legend.position="none"),
          hitsDensityRating + theme(legend.position="none"),
          kudosDensityRating + theme(legend.position="none"),
          commentsDensityRating + theme(legend.position="none"),
          bookmarksDensityRating + theme(legend.position="none"),
          get_legend(kudosDensityRating +
                     theme(legend.title=element_blank())))

rm(wordsDensityRating, hitsDensityRating, kudosDensityRating, commentsDensityRating, bookmarksDensityRating)
wordsHitsRating <- plot_points(stats, 'Words', 'Hits', 'Rating', 'right')
wordsHitsRating
wordsKudosRating <- plot_points(stats, 'Words', 'Kudos', 'Rating', 'right')
wordsKudosRating
wordsCommentsRating <- plot_points(stats, 'Words', 'Comments', 'Rating', 'right')
wordsCommentsRating
wordsBookmarksRating <- plot_points(stats, 'Words', 'Bookmarks', 'Rating', 'right')
wordsBookmarksRating

rm(wordsHitsRating, wordsKudosRating, wordsCommentsRating, wordsBookmarksRating)

Categories

There are 2604 works tagged with a single category, and 444 tagged with 2 or more (up until all 6).

‘F/M’ is the most popular category, followed by ‘M/M’, ‘Gen’, and ‘F/F’.

Multiple category fics strongly contribute towards ‘F/M’ count, then to ‘M/M’, ‘Gen’, and ‘F/F’, and only marginally to ‘Multi’ and ‘Other’.


singleCategorySummary <- summary(as.factor(unlist(category[unlist(lapply(category, function(x) length(x))) == 1])))
singleCategorySummary <- data.frame(Category = names(singleCategorySummary),
                                    Number.of.Works = singleCategorySummary)
singleCategorySummary$Split <- "Single category"

multipleCategorySummary <- data.frame(Category = c('Gen', 'F/F', 'F/M', 'M/M', 'Multi', 'Other', 'No category'),
                              Number.of.Works = c(sum(grepl('Gen',category)),
                                                  sum(grepl('F/F',category)),
                                                  sum(grepl('F/M',category)),
                                                  sum(grepl('M/M',category)),
                                                  sum(grepl('Multi',category)),
                                                  sum(grepl('Other',category)),
                                                  sum(grepl('No category',category))) )
multipleCategorySummary$Split <- "All works"

categorySummary <- rbind(singleCategorySummary, multipleCategorySummary)
categorySummary$Category <- factor(categorySummary$Category, levels = c('Gen', 'F/F', 'F/M', 'M/M', 'Multi', 'Other', 'No category'))
categorySummary$Split <- factor(categorySummary$Split, levels = c("Single category", "All works"))

plotCategories <- ggplot(categorySummary, aes(x = Category, y = Number.of.Works)) + 
                  geom_col(alpha=1)+
                  theme_half_open() +
                  background_grid() +
                  facet_wrap(.~Split) +
                  theme(legend.title=element_blank(),
                        axis.title.x = element_blank(),
                        axis.text.x = element_text(angle = 90, vjust = 1, hjust=1))+
                  labs(y="Number of Works")
plotCategories


rm(singleCategorySummary, multipleCategorySummary, categorySummary, plotCategories)

Engagement by a single category

For simplicity I’m only looking at works tagged with a single category here.

“Multi” seems to have most words, despite being a rather small category, and collects quite a bit of Hits and Comments. It’s possible that a number of those works are collections of stories for many fandoms, which amplifies the number of Hits and Comments, but that requires further investigation.


statsCategory <- stats[unlist(lapply(category, function(x) length(x))) == 1,]
statsCategory$Category <- as.factor(unlist(category[unlist(lapply(category, function(x) length(x))) == 1]))
statsCategory$Category <- factor(statsCategory$Category, levels = c('Gen', 'F/F', 'F/M', 'M/M', 'Multi', 'Other', 'No category'))
statsCategory$Divisor <- unlist(lapply(statsCategory$Category, function(x) summary(statsCategory$Category)[names(summary(statsCategory$Category)) == x]))
statsCategory$Words.per.Work <- statsCategory$Words/statsCategory$Divisor
statsCategory$Hits.per.Work <- statsCategory$Hits/statsCategory$Divisor
statsCategory$Kudos.per.Work <- statsCategory$Kudos/statsCategory$Divisor
statsCategory$Comments.per.Work <- statsCategory$Comments/statsCategory$Divisor
statsCategory$Bookmarks.per.Work <- statsCategory$Bookmarks/statsCategory$Divisor
statsCategory$Works.Percent <- 1/statsCategory$Divisor

barWorksCategory <- plot_bar_color(statsCategory, 'Category', 'Rating', 'right')
barWordsCategory <- plot_col_color(statsCategory, 'Category', 'Words.per.Work', 'Rating', 'right')
barHitsCategory <- plot_col_color(statsCategory, 'Category', 'Hits.per.Work', 'Rating', 'right')
barKudosCategory <- plot_col_color(statsCategory, 'Category', 'Kudos.per.Work', 'Rating', 'right')
barCommentsCategory <- plot_col_color(statsCategory, 'Category', 'Comments.per.Work', 'Rating', 'right')
barBookmarksCategory <- plot_col_color(statsCategory, 'Category', 'Bookmarks.per.Work','Rating', 'right')

plot_grid(plot_grid( barWorksCategory + theme(legend.position="none"),
           barWordsCategory + theme(legend.position="none"),
           barHitsCategory + theme(legend.position="none"),
           barKudosCategory + theme(legend.position="none"),
           barCommentsCategory + theme(legend.position="none"),
           barBookmarksCategory + theme(legend.position="none"),
           align = 'hv'),
          get_legend(barWorksCategory + theme(legend.title=element_blank())),
          rel_widths = c(4,1))

Ratings percentages by a single category

Out of the 3 main shipping categories in absolute numbers “M/M” has most E rated works, and “F/F” has the least, but in percentages of total works “F/F” and “F/M” are distributed almost identically, while “M/M” skews more towards M and E rated works.


plotWorksCategoryNormalized <- plot_col_color(statsCategory, 'Rating', 'Works.Percent', 'Rating', 'none')+
                               scale_y_continuous(labels=scales::percent)+
                               facet_wrap(.~Category)
plotWorksCategoryNormalized


rm(barWorksCategory, barWordsCategory, barHitsCategory, barKudosCategory, barCommentsCategory, barBookmarksCategory, plotWorksCategoryNormalized)

Single Category through time

Interestingly, season 1 sees a peak of ‘Gen’ category. Season 2 gives higher rise to ‘M/M’ (possibly related to Aaravos reveal and ‘Aaravos/Viren (The Dragon Prince)’ shipping) and ‘F/M’ (the rise of ‘Callum/Rayla (The Dragon Prince)’?), and season 3 is followed by a high rise of ‘F/F’ (following the development of ‘Amaya/Janai (The Dragon Prince)’ relationship) and a modest ‘M/M’ peak (‘Ethari/Runaan (The Dragon Prince)’ due to Ethari finally getting official name?).


plotDatesCategoryDensity <- ggplot(statsCategory, aes(x = Date, col=Category)) + 
                    geom_density(alpha = 0.1)+
                    geom_vline(xintercept=seasons)+
                    scale_x_date(date_breaks="2 months")+
                    scale_color_manual(values = custompalette) +
                    theme_half_open() +
                    background_grid() +
                    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
                          legend.position = 'right')
plotDatesCategoryDensity


rm(plotDatesCategoryDensity)

Ship tags through time

“Amaya/Janai (The Dragon Prince)” sharply took of in popularity after season 3. Ethari’s name hasn’t been revealed until season 3, so authors were using “Runaan/Tinker | Necklace Elf (The Dragon Prince)” instead of “Ethari/Runaan (The Dragon Prince)”, but Ao3 would consider them synonymous. Romance tag “Callum/Rayla (The Dragon Prince)” is significantly more popular than platonic/frienship tag “Callum & Rayla (The Dragon Prince)”, however it’s worth noting that out of 230 stories tagged with a friendship/platonic one 178 are tagged with both, possibly making specifically friendship/platonic content more difficult to find.


plotRelationships <- ggplot() +
    geom_density(data = relationshipsStats[relationshipsStats$relationship1 > 0,], mapping=aes(x = Date), colour=custompalette[1])+
    geom_density(data = relationshipsStats[relationshipsStats$relationship2 > 0,], mapping=aes(x = Date), colour=custompalette[2])+
    geom_density(data = relationshipsStats[relationshipsStats$relationship3 > 0,], mapping=aes(x = Date), colour=custompalette[3])+
    geom_density(data = relationshipsStats[relationshipsStats$relationship4 > 0,], mapping=aes(x = Date), colour=custompalette[4])+
    geom_density(data = relationshipsStats[relationshipsStats$relationship5 > 0,], mapping=aes(x = Date), colour=custompalette[5])+
    geom_density(data = relationshipsStats[relationshipsStats$relationship6 > 0,], mapping=aes(x = Date), colour=custompalette[6])+
    geom_density(data = relationshipsStats[relationshipsStats$relationship7 > 0,], mapping=aes(x = Date), colour=custompalette[7])+
    geom_density(data = relationshipsStats[relationshipsStats$relationship8 > 0,], mapping=aes(x = Date), colour=custompalette[8])+
    geom_vline(xintercept=seasons)+
    scale_x_date(date_breaks="2 months")+
    scale_color_manual(values = custompalette) +
    theme_half_open() +
    background_grid() +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

mylegend <- get_legend(plotLegendRelationships)

plot_grid(plotRelationships, mylegend,
          rel_widths = c(2,1), nrow=1)

#plotRelationships

#rm(seasons, plotDatesRatingDensity)

Archive Warnings

Majority of works are tagged with “No Archive Warnings Apply”, followed by a sizable fraction of “Creator Chose Not To Use Archive Warnings”. It seems to be a common matter of confusion between the usage of those two warnings, so it’s possible that a lot of “Creator Chose Not To Use Archive Warnings” are mistagged “No Archive Warnings Apply”.


multipleWarningSummary <- data.frame(Warning = c("No Archive Warnings Apply",
                                                  "Graphic Depictions Of Violence",
                                                  "Major Character Death",
                                                  "Rape/Non-Con",
                                                  "Underage",
                                                  "Creator Chose Not To Use Archive Warnings"),
                              Number.of.Works = c(sum(grepl("No Archive Warnings Apply",warnings)),
                                                  sum(grepl("Graphic Depictions Of Violence",warnings)),
                                                  sum(grepl("Major Character Death",warnings)),
                                                  sum(grepl("Rape/Non-Con",warnings)),
                                                  sum(grepl("Underage",warnings)),
                                                  sum(grepl("Creator Chose Not To Use Archive Warnings",warnings))) )

multipleWarningSummary$Warning <- factor(multipleWarningSummary$Warning, levels = c("No Archive Warnings Apply",
                                                                                    "Graphic Depictions Of Violence",
                                                                                    "Major Character Death",
                                                                                    "Rape/Non-Con",
                                                                                    "Underage",
                                                                                    "Creator Chose Not To Use Archive Warnings"))

plotWarnings <- plot_col(multipleWarningSummary, 'Warning', 'Number.of.Works', 'right')
plotWarnings


rm(multipleWarningSummary, plotWarnings)

Multiple Fandoms


multiFandoms <- fandom[category == "Multi"]

multiFandomsAll <- fandom[grepl('Multi', category)]

severalFandoms <- fandom[unlist(lapply(fandom, length)) > 1]

crossovers <- fandom[grep('crossover',freeform, ignore.case=TRUE)]

Total number of works tagged only as ‘Multi’ is 63, but only 7 are tagged with more than one fandom. Among these, median number of fandoms tagged is 6, and at least one work is tagged with 15.

Number of works tagged with ‘Multi’ and/or other categories is 139, but only 21 are tagged with more than one fandom. Among these, median number of fandoms tagged is 14, and at least one work is tagged with 56.

Number of works tagged with more than 1 fandom is 133, however in some cases fandom tags used by the author are synonymous with The Dragon Prince (Cartoon), for example “The Dragon Prince”, “rayllum - Fandom”, “rayla x callum”, “TDP - Fandom”, “callum x rayla”.

Number of works explicitly tagged as ‘crossover’ is lower: 30. Out of multiple fandom tag works a significant amount are tagged with 2: 80, which, upon inspection, don’t seem overwhelmingly synonymous, so perhaps some authors simply don’t tag for crossovers.

Authors by Works

Top 30 of most prolific authors in the tag by the number of stories as of data collection date:

topList <- 30

AuthorTable <- data.frame('Author' = names(summary(as.factor(unlist(author)))[1:topList]),
                          'Number of Stories' = summary(as.factor(unlist(author)))[1:topList])
row.names(AuthorTable) <- c()

kable(AuthorTable,
      col.names = c('Author', 'Number of Stories'))

Author Number of Stories
planetundersiege 244
6Husbandos 40
Lady_Talla_Doe 37
orphan_account 33
brightsmoon 31
HootHalycon 30
beautifulterriblequeen 26
Lonespark_the_friendly_kraken 26
Symphoenae 26
propheticfire 23
Jelly 22
DelicateDragons 21
wordswithdragons 21
Porg_Master 19
Midnightdragon2 18
CuddlyCookie1360 17
UnsubstantiatedAssertion 17
AWillfullDroll 16
zuppi 16
Nightworldlove 15
im_the_king_of_the_ocean 14
Khaleesi_0f_Trolls 14
Mydarlingwriter 14
poetroe 14
spiritypowers 14
Aaravosa (Lokiiwood) 13
Anima_princess_1 13
Anonymous 13
Erratus 13
his_valentine 13


rm(AuthorTable)

Authors by Words

Only 48 works have more than one author. In cases where works had more than one author, I assumed that each of them contributed an equal amounts of words.

Top 30 of most prolific authors in the tag by the number of words written as of data collection date:


wordsByAuthor <- c()

for (i in 1:length(words)){
  if (length(author[[i]]) > 1) {
    wordsByAuthor <- c(wordsByAuthor, rep(words[[i]]/length(author[[5]]), length(author[[i]]) ) )
  } else {
    wordsByAuthor <- c(wordsByAuthor, words[[i]])
  }
}

AuthorWordsTable <- data.frame('Author' = as.factor(unlist(author)),
                               'Words' = wordsByAuthor)

AuthorWordsSummary <- ddply(AuthorWordsTable, .(Author), 
                            summarize, 
                            Total.Words = sum(Words))
AuthorWordsSummary <- AuthorWordsSummary[order(AuthorWordsSummary$Total.Words, decreasing = TRUE),]
row.names(AuthorWordsSummary) <- c()

topList <- 30

kable(AuthorWordsSummary[1:topList,],
      col.names = c('Author', 'Total Words'))

Author Total Words
Decorated 500506.5
beautifulterriblequeen 313169.0
spontaneite 294914.0
Lodke 248405.0
Jelly 225759.0
DeeTheTeaDrinkingDragon 217848.2
NumptyPylon 184380.0
nonameforhire 175498.0
Khaleesi_0f_Trolls 166320.0
genericfanatic 162468.5
enbyred 158300.0
MagiesHeartLove 156268.0
prolixdreams 156264.0
The_Eternal_Winter 154539.0
wordswithdragons 144711.5
Symphoenae 144698.2
Kuno 144402.0
AWillfullDroll 139935.0
CuddlyCookie1360 137388.0
nautiscarader 134518.0
DelphinusDelphis 134126.0
Erratus 127890.0
Captain_Azoren 126415.0
Ocaj 125066.0
iwillhaveamoonbase 122299.0
DelicateDragons 118327.0
assassiinikissa 114339.0
BlehBlahBluh 113585.0
zuppi 112691.0
RosettaStarlight 109028.0


rm(wordsByAuthor, i, AuthorWordsTable, AuthorWordsSummary)

Characters

Top 30 of the most popular characters:

topList <- 30
CharacterTable<- data.frame('Character' = names(summary(as.factor(unlist(character)))[1:topList]),
                          'Number of Stories' = summary(as.factor(unlist(character)))[1:topList])
row.names(CharacterTable) <- c()

kable(CharacterTable,
      col.names = c('Character', 'Number of Stories'))

Character Number of Stories
Rayla (The Dragon Prince) 1455
Callum (The Dragon Prince) 1434
Viren (The Dragon Prince) 782
Ezran (The Dragon Prince) 735
Soren (The Dragon Prince) 687
Claudia (The Dragon Prince) 665
Runaan (The Dragon Prince) 601
Amaya (The Dragon Prince) 558
Aaravos (The Dragon Prince) 472
Harrow (The Dragon Prince) 399
Gren (The Dragon Prince) 340
Ethari (The Dragon Prince) 334
Janai (The Dragon Prince) 333
Azymondias (The Dragon Prince) 297
Sarai (The Dragon Prince) 179
Bait (The Dragon Prince) 160
Tinker | Necklace Elf (The Dragon Prince) 135
Corvus (The Dragon Prince) 121
Opeli (The Dragon Prince) 107
Original Characters 102
Marcos (The Dragon Prince) 85
Lain (The Dragon Prince) 75
Tiadrin (The Dragon Prince) 75
Original Female Character(s) 74
Kazi (The Dragon Prince) 71
Lujanne (The Dragon Prince) 70
Ibis (The Dragon Prince) 60
Reader 57
Ellis (The Dragon Prince) 53
Aanya (The Dragon Prince) 51


rm(CharacterTable)

Relationships

Top 30 of the most popular relationships:

I don’t have access to Ao3’s system of synonymous tags, so by virtue of text processing some relationship tags here are repeated.

Overwhelmingly, “Callum/Rayla (The Dragon Prince)” is the most popular relationship in TDP. They are followed by “Ethari/Runaan (The Dragon Prince)”, which is not immediately obvious due to common use of synonymous tags such as “Runaan/Tinker | Necklace Elf (The Dragon Prince)” and “Runaan/Ethari”. Third most popular relationship is “Amaya/Janai (The Dragon Prince)”.

topList <- 30
RelationshipsTable<- data.frame('Relationship' = names(summary(as.factor(unlist(relationships)))[1:topList]),
                          'Number of Stories' = summary(as.factor(unlist(relationships)))[1:topList])
row.names(RelationshipsTable) <- c()

kable(RelationshipsTable,
      col.names = c('Relationship', 'Number of Stories'))

Relationship Number of Stories
Callum/Rayla (The Dragon Prince) 1009
Amaya/Janai (The Dragon Prince) 273
Callum & Rayla (The Dragon Prince) 230
Ethari/Runaan (The Dragon Prince) 222
Aaravos/Viren (The Dragon Prince) 182
Runaan/Tinker | Necklace Elf (The Dragon Prince) 155
Harrow/Viren (The Dragon Prince) 119
Claudia/Rayla (The Dragon Prince) 113
Callum & Ezran & Rayla (The Dragon Prince) 100
Harrow/Sarai (The Dragon Prince) 87
Claudia & Soren (The Dragon Prince) 82
Callum & Ezran (The Dragon Prince) 77
Rayla & Runaan (The Dragon Prince) 67
Aaravos (The Dragon Prince)/Reader 55
Lain/Tiadrin (The Dragon Prince) 52
Aaravos & Viren (The Dragon Prince) 47
Amaya/Gren (The Dragon Prince) 47
Amaya & Gren (The Dragon Prince) 46
Callum & Soren (The Dragon Prince) 45
Callum & Claudia (The Dragon Prince) 40
Marcos/Soren (The Dragon Prince) 40
Ezran & Rayla (The Dragon Prince) 36
Rayllum - Relationship 32
Runaan/Ethari (The Dragon Prince) 32
Claudia & Rayla (The Dragon Prince) 31
Claudia & Viren (The Dragon Prince) 31
Callum & King Harrow (The Dragon Prince) 30
Gren & Runaan (The Dragon Prince) 29
Runaan/Ethari 29
Callum & Ezran & King Harrow (The Dragon Prince) 28


rm(RelationshipsTable)

Freeform tags

Top 30 of the most popular freeform tags

topList <- 30
FreeformTable<- data.frame('Freeform' = names(summary(as.factor(unlist(freeform)))[1:topList]),
                          'Number of Stories' = summary(as.factor(unlist(freeform)))[1:topList])
row.names(FreeformTable) <- c()

kable(FreeformTable,
      col.names = c('Freeform Tag', 'Number of Stories'))

Freeform Tag Number of Stories
Fluff 637
Angst 301
Rayllum 260
Hurt/Comfort 229
Drabble 215
tdp 189
Alternate Universe - Canon Divergence 151
Alternate Universe - Modern Setting 137
Wordcount: 100-500 131
Fluff and Angst 124
Established Relationship 120
Post-Canon 117
Romance 109
Pre-Canon 105
Magic 97
Slow Burn 96
Canon Compliant 95
Alternate Universe 92
Humor 82
Friendship 75
Smut 75
Light Angst 74
Kissing 72
Angst with a Happy Ending 67
Cute 64
One Shot 64
Other Additional Tags to Be Added 64
Emotional Hurt/Comfort 62
Family 62
Dark Magic 61


rm(FreeformTable)

Languages

Unsurprisingly, most works are written in English. Apologies for U+. kable package for whatever reason murders unicode characters. The two languages in question are Russian (Русский) and Chinese (中文).

#topList <- 30

languagesList <- summary(as.factor(unlist(language)))

LanguageTable <- data.frame('Language' = names(languagesList),
                            'Number of Stories' = languagesList )
LanguageTable <- LanguageTable[order(LanguageTable$Number.of.Stories, decreasing=TRUE),]
row.names(LanguageTable) <- c()

kable(LanguageTable,
      col.names = c('Language', 'Number of Stories'))

Language Number of Stories
English 2998
<U+0420><U+0443><U+0441><U+0441><U+043A><U+0438><U+0439> 12
Español 10
<U+4E2D><U+6587> 8
Français 8
Polski 6
Português brasileiro 3
Deutsch 1
Italiano 1
Suomi 1


#languagesList

#rm(LanguageTable)
LS0tDQp0aXRsZTogIkFvMyBkYXRhIGFuYWx5c2lzIGZvciBUaGUgRHJhZ29uIFByaW5jZSAoQ2FydG9vbikiDQphdXRob3I6ICJkYXJ0aGFsaW5lIg0KZGF0ZTogIjcgQXVnIDIwMjAiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9mb2xkaW5nOiAiaGlkZSINCiAgICB0b2M6IHRydWUNCi0tLQ0KDQojIEFib3V0DQoNCmBgYHtyIHNldHVwLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNClN5cy5zZXRlbnYoTEFORyA9ICJlbiIpDQojbGlicmFyeSgicnN0dWRpb2FwaSIpICN0byBncmFiIGxvY2FsIHBvc2l0aW9uIG9mIHRoZSBzY3JpcHQNCiNzZXR3ZChkaXJuYW1lKHJzdHVkaW9hcGk6OmdldEFjdGl2ZURvY3VtZW50Q29udGV4dCgpJHBhdGgpKQ0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAnLicpDQoNCiNsaWJyYXJ5KCJydmVzdCIpICMgdG8gaGFuZGxlIGh0bWwgc3R1ZmYNCg0KbGlicmFyeShsdWJyaWRhdGUpICMgdG8gaGFuZGxlIGRhdGVzDQoNCmxpYnJhcnkoZ2dwbG90MikgIyBmb3IgcGxvdHRpbmcNCmxpYnJhcnkoY293cGxvdCkgIyBmb3IgcGxvdHRpbmcNCmxpYnJhcnkoUkNvbG9yQnJld2VyKSAjIGZvciBjaG9vc2luZyBjb2xvcnMNCg0KY3VzdG9tcGFsZXR0ZSA8LSBicmV3ZXIucGFsKG49OCwgbmFtZSA9ICdEYXJrMicpDQoNCmxpYnJhcnkoa25pdHIpICMgZm9yIHRhYmxlcw0KbGlicmFyeShrYWJsZUV4dHJhKSAjIGZvciB0YWJsZXMNCg0KbGlicmFyeShsdWJyaWRhdGUpICMgZm9yIGRhdGVzDQoNCmxpYnJhcnkocGx5cikgIyBkZHBseSwgdG8gc3VtbWFyaXplIG51bWJlciBvZiB3b3JkcyBieSBhdXRob3INCg0KbG9hZCgnVERQX3dvcmtzRGF0YS5SRGF0YScpDQoNCmBgYA0KDQpUaGlzIGlzIGEgZG9jdW1lbnQgZGV0YWlsaW5nIGFuYWx5c2lzIG9mIFtgciB0YWdWYWx1ZWAgQW8zIHRhZ10oaHR0cHM6Ly9hcmNoaXZlb2ZvdXJvd24ub3JnL3RhZ3MvVGhlJTIwRHJhZ29uJTIwUHJpbmNlJTIwKENhcnRvb24pL3dvcmtzKSBkYXRhLCBjb2xsZWN0ZWQgb24gdGhlIDcgQXVnIDIwMjAuIEkgaGF2ZW4ndCBmaWd1cmVkIG91dCBhIHdheSB0byBnZXQgbXkgc2NyYXBwZXIgdG8gbG9nIGluIGludG8gQW8zICh5ZXQ/IHJ2ZXN0IHNlZW1zIHRvIGhhdmUgc29tZSB0cm91YmxlIHdpdGggcGFnZSByZWRpcmVjdHMpLCBzbyByZXN1bHRzIGhlcmUgYXJlIGJhc2VkIG9uIHRoZSB3b3JrcyB2aXNpYmxlIHdpdGhvdXQgYXV0aGVudGljYXRpb24sIHdoaWNoIGxpa2VseSBmaWx0ZXJzIG91dCBwcmVmZXJlbnRpYWxseSBleHBsaWNpdC9wcm9ibGVtYW50aWMgd29ya3MgZnJvbSB0aGUgc2VsZWN0aW9uLg0KDQpgYGB7ciBwbG90dGluZ0Z1bmN0aW9ucywgY29sbGFwc2U9VFJVRSwgd2FybmluZz1GQUxTRX0NCg0KcGxvdF9iYXIgPC0gZnVuY3Rpb24gKGRhdGEsIGNvbHVtblgsIGxlZ2VuZFBvc2l0aW9uKSB7DQogICAgZ2dwbG90KGRhdGEsIGFlc19zdHJpbmcoeCA9IGNvbHVtblgpKSArIA0KICAgIGdlb21fYmFyKGFscGhhPTEpKw0KICAgIHRoZW1lX2hhbGZfb3BlbigpICsNCiAgICBiYWNrZ3JvdW5kX2dyaWQoKSArDQogICAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAxLCBoanVzdD0xKSkrDQogICAgbGFicyh5PSJOdW1iZXIgb2Ygd29ya3MiKQ0KfQ0KDQpwbG90X2Jhcl9jb2xvciA8LSBmdW5jdGlvbiAoZGF0YSwgY29sdW1uWCwgY29sQ29sb3IsIGxlZ2VuZFBvc2l0aW9uKSB7DQogICAgZ2dwbG90KGRhdGEsIGFlc19zdHJpbmcoeCA9IGNvbHVtblgsIGZpbGw9Y29sQ29sb3IpKSArIA0KICAgIGdlb21fYmFyKGFscGhhPTAuNykrDQogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY3VzdG9tcGFsZXR0ZSkgKw0KICAgIHRoZW1lX2hhbGZfb3BlbigpICsNCiAgICBiYWNrZ3JvdW5kX2dyaWQoKSArDQogICAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAxLCBoanVzdD0xKSkrDQogICAgbGFicyh5PSJOdW1iZXIgb2Ygd29ya3MiKQ0KfQ0KDQpwbG90X2NvbCA8LSBmdW5jdGlvbiAoZGF0YSwgY29sdW1uWCwgY29sdW1uWSwgbGVnZW5kUG9zaXRpb24pIHsNCiAgICBnZ3Bsb3QoZGF0YSwgYWVzX3N0cmluZyh4ID0gY29sdW1uWCwgeSA9IGNvbHVtblkpKSArIA0KICAgIGdlb21fY29sKGFscGhhPTEpKw0KICAgIHRoZW1lX2hhbGZfb3BlbigpICsNCiAgICBiYWNrZ3JvdW5kX2dyaWQoKSArDQogICAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAxLCBoanVzdD0xKSkrDQogICAgbGFicyh5PWdzdWIoJ1xcLicsICcgJywgY29sdW1uWSkpDQogIA0KfQ0KDQpwbG90X2NvbF9jb2xvciA8LSBmdW5jdGlvbiAoZGF0YSwgY29sdW1uWCwgY29sdW1uWSwgY29sQ29sb3IsIGxlZ2VuZFBvc2l0aW9uKSB7DQogICAgZ2dwbG90KGRhdGEsIGFlc19zdHJpbmcoeCA9IGNvbHVtblgsIHkgPSBjb2x1bW5ZLCBmaWxsPWNvbENvbG9yKSkgKyANCiAgICBnZW9tX2NvbChhbHBoYT0wLjcpKw0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGN1c3RvbXBhbGV0dGUpICsNCiAgICB0aGVtZV9oYWxmX29wZW4oKSArDQogICAgYmFja2dyb3VuZF9ncmlkKCkgKw0KICAgIHRoZW1lKGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICAgIGxhYnMoeT1nc3ViKCdcXC4nLCAnICcsIGNvbHVtblkpKQ0KICANCn0NCg0KcGxvdF9wZXJjZW50aWxlcyA8LSBmdW5jdGlvbiAoZGF0YSwgY29sdW1uWCwgY29sdW1uWSwgbGVnZW5kUG9zaXRpb24pIHsNCiAgICBnZ3Bsb3QoZGF0YSwgYWVzX3N0cmluZyh4ID0gY29sdW1uWCwgeSA9IGNvbHVtblkpKSArIA0KICAgIGdlb21fcG9pbnQoYWxwaGE9MC4zKSsNCiAgICBzY2FsZV95X2xvZzEwKGJyZWFrcyA9IDEwXmMoMDoxNSkpKw0KICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDAsIDI1LCA1MCwgNzUsIDEwMCkpKyAjc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMDoxMCkqMTApKw0KICAgIHRoZW1lX2hhbGZfb3BlbigpICsNCiAgICBiYWNrZ3JvdW5kX2dyaWQoKSArDQogICAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSkrDQogICAgbGFicyh4PWdzdWIoJ1xcLicsICcgJywgY29sdW1uWCkpDQp9DQoNCmBgYA0KDQpgYGB7ciBwbG90dGluZ0Z1bmN0aW9uc1VudXNlZCwgY29sbGFwc2U9VFJVRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IEZBTFNFfQ0KDQpwbG90X2RlbnNpdHkgPC0gZnVuY3Rpb24gKGRhdGEsIGNvbHVtbiwgY29sb3JfY29sdW1uLCBsZWdlbmRQb3NpdGlvbikgew0KICAgIGdncGxvdChkYXRhLCBhZXNfc3RyaW5nKHggPSBjb2x1bW4sIGNvbD1jb2xvcl9jb2x1bW4pKSArIA0KICAgIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuMSkrDQogICAgc2NhbGVfeF9sb2cxMCgpKw0KICAgIHRoZW1lX2hhbGZfb3BlbigpICsNCiAgICBiYWNrZ3JvdW5kX2dyaWQoKSArDQogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpLA0KICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGxlZ2VuZFBvc2l0aW9uKQ0KfQ0KDQpwbG90X3BvaW50cyA8LSBmdW5jdGlvbiAoZGF0YSwgY29sdW1uWCwgY29sdW1uWSwgY29sb3JfY29sdW1uLCBsZWdlbmRQb3NpdGlvbikgew0KICAgIGdncGxvdChkYXRhLCBhZXNfc3RyaW5nKHggPSBjb2x1bW5YLCB5ID0gY29sdW1uWSwgY29sPWNvbG9yX2NvbHVtbikpICsgDQogICAgZ2VvbV9wb2ludChhbHBoYT0wLjMpKw0KICAgIHNjYWxlX3hfbG9nMTAoKSsNCiAgICBzY2FsZV95X2xvZzEwKCkrDQogICAgZmFjZXRfd3JhcChjb2xvcl9jb2x1bW4pKw0KICAgIHRoZW1lX2hhbGZfb3BlbigpICsNCiAgICBiYWNrZ3JvdW5kX2dyaWQoKSArDQogICAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpDQp9DQoNCmBgYA0KDQpgYGB7ciBmbGF0dGVuaW5nRGF0YSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI3RpdGxlIDwtIGxhcHBseSh3b3Jrc0RhdGEsIGZ1bmN0aW9uKHgpIHt4JFRpdGxlfSkNCmF1dGhvciA8LSBsYXBwbHkod29ya3NEYXRhLCBmdW5jdGlvbih4KSB7eCRBdXRob3J9KQ0KZmFuZG9tIDwtIGxhcHBseSh3b3Jrc0RhdGEsIGZ1bmN0aW9uKHgpIHt4JEZhbmRvbX0pDQpyYXRpbmcgPC0gbGFwcGx5KHdvcmtzRGF0YSwgZnVuY3Rpb24oeCkge3gkUmF0aW5nfSkNCndhcm5pbmdzIDwtIGxhcHBseSh3b3Jrc0RhdGEsIGZ1bmN0aW9uKHgpIHt4JFdhcm5pbmdzfSkNCmNhdGVnb3J5IDwtIGxhcHBseSh3b3Jrc0RhdGEsIGZ1bmN0aW9uKHgpIHt4JENhdGVnb3J5fSkNCldJUCA8LSBsYXBwbHkod29ya3NEYXRhLCBmdW5jdGlvbih4KSB7eCRXSVB9KQ0KZGF0ZSA8LWxhcHBseSh3b3Jrc0RhdGEsIGZ1bmN0aW9uKHgpIHt4JERhdGV9KQ0KcmVsYXRpb25zaGlwcyA8LWxhcHBseSh3b3Jrc0RhdGEsIGZ1bmN0aW9uKHgpIHt4JFJlbGF0aW9uc2hpcHN9KQ0KY2hhcmFjdGVyIDwtbGFwcGx5KHdvcmtzRGF0YSwgZnVuY3Rpb24oeCkge3gkQ2hhcmFjdGVyfSkNCmZyZWVmb3JtIDwtbGFwcGx5KHdvcmtzRGF0YSwgZnVuY3Rpb24oeCkge3gkRnJlZWZvcm19KQ0KbGFuZ3VhZ2UgPC1sYXBwbHkod29ya3NEYXRhLCBmdW5jdGlvbih4KSB7eCRMYW5ndWFnZX0pDQp3b3JkcyA8LWxhcHBseSh3b3Jrc0RhdGEsIGZ1bmN0aW9uKHgpIHt4JFdvcmRzfSkNCmt1ZG9zIDwtbGFwcGx5KHdvcmtzRGF0YSwgZnVuY3Rpb24oeCkge3gkS3Vkb3N9KQ0KY29tbWVudHMgPC1sYXBwbHkod29ya3NEYXRhLCBmdW5jdGlvbih4KSB7eCRDb21tZW50c30pDQpib29rbWFya3M8LWxhcHBseSh3b3Jrc0RhdGEsIGZ1bmN0aW9uKHgpIHt4JEJvb2ttYXJrc30pDQpoaXRzIDwtbGFwcGx5KHdvcmtzRGF0YSwgZnVuY3Rpb24oeCkge3gkSGl0c30pDQoNCnN0YXRzIDwtIGRhdGEuZnJhbWUoV29yZHMgPSB1bmxpc3Qod29yZHMsIHJlY3Vyc2l2ZSA9IEZBTFNFKSwNCiAgICAgICAgICAgICAgICAgICAgQ29tbWVudHM9IGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGNvbW1lbnRzKSksDQogICAgICAgICAgICAgICAgICAgIEt1ZG9zID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoa3Vkb3MpKSwNCiAgICAgICAgICAgICAgICAgICAgQm9va21hcmtzID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYm9va21hcmtzKSksDQogICAgICAgICAgICAgICAgICAgIEhpdHMgPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihoaXRzKSksDQogICAgICAgICAgICAgICAgICAgIFdJUCA9IHVubGlzdChXSVAsIHJlY3Vyc2l2ZSA9IEZBTFNFKSwNCiAgICAgICAgICAgICAgICAgICAgUmF0aW5nID0gdW5saXN0KHJhdGluZywgcmVjdXJzaXZlID0gRkFMU0UpLA0KICAgICAgICAgICAgICAgICAgICBEYXRlID0gZG8uY2FsbCgiYyIsIGRhdGUpKQ0KDQpzdGF0cyRSYXRpbmcgPC0gZmFjdG9yKHN0YXRzJFJhdGluZywgbGV2ZWxzID0gYygiTm90IFJhdGVkIiwgIkdlbmVyYWwgQXVkaWVuY2VzIiwgIlRlZW4gQW5kIFVwIEF1ZGllbmNlcyIsICJNYXR1cmUiLCAiRXhwbGljaXQiKSkNCg0KdG90YWwgPC0gMTAwMA0KcGVyY2VudGlsZSA8LSBjKDE6dG90YWwpDQpwZXJjZW50aWxlRGF0YSA8LSBkYXRhLmZyYW1lKFdvcmtzLlBlcmNlbnRpbGUgPSAxMDAqKHRvdGFsIC0gcGVyY2VudGlsZSkvdG90YWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdvcmRzID0gdW5saXN0KGxhcHBseShwZXJjZW50aWxlL3RvdGFsLCBxdWFudGlsZSwgeCA9IHVubGlzdCh3b3JkcykpKSArIDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIEhpdHMgPSB1bmxpc3QobGFwcGx5KHBlcmNlbnRpbGUvdG90YWwsIHF1YW50aWxlLCB4ID0gdW5saXN0KGhpdHMpKSkgKyAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBLdWRvcyA9IHVubGlzdChsYXBwbHkocGVyY2VudGlsZS90b3RhbCwgcXVhbnRpbGUsIHggPSB1bmxpc3Qoa3Vkb3MpKSkgKyAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb21tZW50cyA9IHVubGlzdChsYXBwbHkocGVyY2VudGlsZS90b3RhbCwgcXVhbnRpbGUsIHggPSB1bmxpc3QoY29tbWVudHMpKSkgKyAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCb29rbWFya3MgPSB1bmxpc3QobGFwcGx5KHBlcmNlbnRpbGUvdG90YWwsIHF1YW50aWxlLCB4ID0gdW5saXN0KGJvb2ttYXJrcykpKSArIDEgKQ0KDQpybShyYXRpbmcsIGt1ZG9zLCBjb21tZW50cywgYm9va21hcmtzLCBoaXRzKQ0KDQpgYGANCg0KIyBUaW1lbGluZQ0KDQpWZXJ0aWNhbCBsaW5lcyBvbiB0aGUgZ3JhcGggaW5kaWNhdGUgc2Vhc29uIHJlbGVhc2UgZGF0ZXMgYWNjb3JkaW5nIHRvIFtXaWtpIGFydGljbGVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RoZV9EcmFnb25fUHJpbmNlI0VwaXNvZGVzKS4gQXMgZXhwZWN0ZWQsIGFmdGVyIGVhY2ggbmV3IHNlYXNvbiwgdGhlcmUncyBhIHBlYWsgb2YgYWN0aXZpdHkgd2hpY2ggZmFkZXMgaW4gYWJvdXQgMiBtb250aHMuIEN1cmlvdXNseSwgYSBmZXcgd29ya3Mgd2VyZSBwb3N0ZWQgYmVmb3JlIG9mZmljaWFsIHNlYXNvbiAxIHJlbGVhc2UuIFRoaXMgbWF5IGJlIGF0dHJpYnV0ZWQgdG8gdGhlIHNlcmllcyB0cmFpbGVyIGRyb3AgaW4gSnVseSAyMDE4IGF0IHRoZSBTYW4gRGllZ28gQ29taWMtQ29uLg0KDQpgYGB7ciB0aW1lbGluZVRvdGFsLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQ0KDQojZGF0YSRUaW1lc3RhbXAgPC0gcGFyc2VfZGF0ZV90aW1lMihhcy5jaGFyYWN0ZXIoZGF0YSRUaW1lc3RhbXApLCBvcmRlcnMgPSAiJWQvJW0vJVkgJUg6JU06JVMiKQ0KI2RhdGEkZGF5IDwtIGFzLkRhdGUoZGF0YSRUaW1lc3RhbXApDQoNCnNlYXNvbnMgPC0gYygiMjAxOC0wOS0xNCIsICIyMDE5LTAyLTE1IiwgIjIwMTktMTEtMjIiKQ0Kc2Vhc29ucyA8LSBhcy5EYXRlKHNlYXNvbnMpDQoNCmRhdGVzIDwtIGRhdGEuZnJhbWUoZGF0ZSA9IGRvLmNhbGwoImMiLCBkYXRlKSwNCiAgICAgICAgICAgICAgICAgICAgV0lQID0gdW5saXN0KFdJUCkpDQoNCnBsb3REYXRlc0RlbnNpdHlUb3RhbCA8LSBnZ3Bsb3Qoc3RhdHMsIGFlcyh4ID0gRGF0ZSkpICsgDQogICAgICAgICAgICAgICAgICAgIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuMSkrDQogICAgICAgICAgICAgICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1zZWFzb25zKSsNCiAgICAgICAgICAgICAgICAgICAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzPSIyIG1vbnRocyIpKw0KICAgICAgICAgICAgICAgICAgICB0aGVtZV9oYWxmX29wZW4oKSArDQogICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRfZ3JpZCgpICsNCiAgICAgICAgICAgICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAncmlnaHQnKQ0KcGxvdERhdGVzRGVuc2l0eVRvdGFsDQoNCnJtKHBsb3REYXRlc0RlbnNpdHlUb3RhbCkNCmBgYA0KDQpJdCdzIGltcG9ydGFudCB0byBub3RlLCB0aGF0IEkgY29sbGVjdCBkYXRhIGZyb20gdGhlIEFvMyBzZWFyY2ggcGFnZSAocmF0aGVyIHRoYW4gd29ya3MgcGFnZXMsIGFzIGl0J3MgbGVzcyBkaXNydXB0aXZlKSwgc28gSSBkb24ndCBoYXZlIGFjY2VzcyB0byBpbml0aWFsIHBvc3RhZ2UgZGF0ZXMsIG9ubHkgdGhlIGxhdGVzdCB1cGRhdGVzLiBUaGlzIG1lYW5zIHRoYXQgdGhlIHVwd2FyZCB0cmVuZCBpbiB3b3JrcyBvdmVyIHRpbWUgY2FuIGJlIGFuIGFydGlmYWN0IG9mIHNlcmllcyBnZXR0aW5nIG1vcmUgcG9wdWxhciwgYnV0IGFsc28gY291bGQgYmUgYXR0cmlidXRlZCB0byBtdWx0aWNoYXB0ZXIgd29ya3MgZHJpZnRpbmcgZnVydGhlciBpbiB0aW1lIGR1ZSB0byB1cGRhdGVzLg0KDQpJZiB3ZSBwbG90IENvbXBsZXRlIFdvcmtzIGFuZCBXb3JrcyBpbiBQcm9ncmVzcyBzZXBhcmF0ZWx5LCB3ZSBzdGlsbCBzZWUgYW4gdXB3YXJkIHRyZW5kIGluIGJvdGgsIGJ1dCB0aGUgc2xvcGUgY2hhcmFjdGVyaXNpbmcgdGhlIGdyb3d0aCBvZiBXb3JrIEluIHByb2dyZXNzIHBlYWtzIHNlZW1zIHN0ZWVwZXIgdGhhbiBmb3IgQ29tcGxldGUgV29ya3MsIHdoaWNoIHRvIG1lIGluZGljYXRlcyBhdCBsZWFzdCBwYXJ0aWFsIGVmZmVjdCBvZiB0aGUgbXVsdGljaGFwdGVyIGRyaWZ0Lg0KDQpgYGB7ciB0aW1lbGluZVdJUCwgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Nn0NCg0KcGxvdERhdGVzRGVuc2l0eSA8LSBnZ3Bsb3Qoc3RhdHMsIGFlcyh4ID0gRGF0ZSwgY29sPVdJUCkpICsgDQogICAgICAgICAgICAgICAgICAgIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuMSkrDQogICAgICAgICAgICAgICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1zZWFzb25zKSsNCiAgICAgICAgICAgICAgICAgICAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzPSIyIG1vbnRocyIpKw0KICAgICAgICAgICAgICAgICAgICB0aGVtZV9oYWxmX29wZW4oKSArDQogICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRfZ3JpZCgpICsNCiAgICAgICAgICAgICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAncmlnaHQnKQ0KcGxvdERhdGVzRGVuc2l0eQ0KDQpybShwbG90RGF0ZXNEZW5zaXR5KQ0KDQpgYGANCg0KDQojIEVuZ2FnZW1lbnQgcGVyY2VudGlsZXMNCg0KU21hbGwgcGxvdHRpbmcgY2hlYXQ6IGFsbCB0aGUgbnVtYmVycyBvbiB0aGUgWSBheGlzIGFyZSBpbmNyZWFzZWQgYnkgMSB0byBpbmNsdWRlIHRoZSBjYXNlIG9mIDAgaW50byB0aGUgcGxvdCAob3RoZXJ3aXNlIGV4Y2x1ZGVkIGJlY2F1c2Ugb2YgbG9nIHNjYWxlKS4NCg0KKiBBYm91dCA3NSUgb2Ygd29ya3MgaGF2ZSBtb3JlIHRoYW4gYSAxMDAwIHdvcmRzLCBidXQgb25seSBhYm91dCAxMCUgaGF2ZSBtb3JlIHRoYW4gMTAwMDAgd29yZHMuDQoqIE9ubHkgYWJvdXQgMzAlIG9mIHdvcmtzIGhhdmUgb3ZlciBhIDEwMDAgaGl0cy4NCiogT25seSBhYm91dCAyNSUgb2Ygd29ya3MgaGF2ZSBtb3JlIHRoYW4gYSAxMDAga3Vkb3MuDQoqIE9ubHkgYWJvdXQgNDAlIG9mIHdvcmtzIGdldCBtb3JlIHRoYW4gMTAgY29tbWVudHMuDQoqIEFwcHJveGltYXRlbHkgMTAlIG9mIHdvcmtzIGhhdmUgbm8gY29tbWVudHMgKHRhaWwgZW5kKS4NCiogT25seSBhcHByb3hpbWF0ZWx5IDI1JSBvZiB3b3JrcyBnZXQgbW9yZSB0aGFuIDEwIGJvb2ttYXJrcy4NCiogQXBwcm94aW1hdGVseSAyMCUgb2Ygd29ya3MgaGF2ZSBubyBib29rbWFya3MgKHRhaWwgZW5kKS4NCg0KYGBge3IgcGVyY2VudGlsZXMsIG1lc3NhZ2UgPSBGQUxTRX0NCndvcmRzUGVyY2VudGlsZXMgPC0gcGxvdF9wZXJjZW50aWxlcyhwZXJjZW50aWxlRGF0YSwgJ1dvcmtzLlBlcmNlbnRpbGUnLCAnV29yZHMnLCAncmlnaHQnKQ0KaGl0c1BlcmNlbnRpbGVzIDwtIHBsb3RfcGVyY2VudGlsZXMocGVyY2VudGlsZURhdGEsICdXb3Jrcy5QZXJjZW50aWxlJywgJ0hpdHMnLCAncmlnaHQnKQ0Ka3Vkb3NQZXJjZW50aWxlcyA8LSBwbG90X3BlcmNlbnRpbGVzKHBlcmNlbnRpbGVEYXRhLCAnV29ya3MuUGVyY2VudGlsZScsICdLdWRvcycsICdyaWdodCcpDQpjb21tZW50c1BlcmNlbnRpbGVzIDwtIHBsb3RfcGVyY2VudGlsZXMocGVyY2VudGlsZURhdGEsICdXb3Jrcy5QZXJjZW50aWxlJywgJ0NvbW1lbnRzJywgJ3JpZ2h0JykNCmJvb2ttYXJrc1BlcmNlbnRpbGVzIDwtIHBsb3RfcGVyY2VudGlsZXMocGVyY2VudGlsZURhdGEsICdXb3Jrcy5QZXJjZW50aWxlJywgJ0Jvb2ttYXJrcycsICdyaWdodCcpDQoNCnBsb3RfZ3JpZCh3b3Jkc1BlcmNlbnRpbGVzICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgaGl0c1BlcmNlbnRpbGVzICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAga3Vkb3NQZXJjZW50aWxlcyArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgIGNvbW1lbnRzUGVyY2VudGlsZXMgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICBib29rbWFya3NQZXJjZW50aWxlcyArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgIGdldF9sZWdlbmQoa3Vkb3NQZXJjZW50aWxlcyArDQogICAgICAgICAgICAgICAgICAgICB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKSkpDQoNCnJtKHRvdGFsLCBwZXJjZW50aWxlLCBwZXJjZW50aWxlRGF0YSwgd29yZHNQZXJjZW50aWxlcywgaGl0c1BlcmNlbnRpbGVzLCBrdWRvc1BlcmNlbnRpbGVzLCBjb21tZW50c1BlcmNlbnRpbGVzLCBib29rbWFya3NQZXJjZW50aWxlcykNCg0KYGBgDQoNCiMgQ29tcGxldGUgV29yayB2cyBXb3JrIGluIFByb2dyZXNzIGRpc3RyaWJ1dGlvbnMNCg0KKiBUaGVyZSBhcmUgYXBwcm94aW1hdGVseSA0IHRpbWVzIGFzIG1hbnkgQ29tcGxldGUgV29ya3MgYXMgdGhlcmUgYXJlIFdvcmtzIGluIFByb2dyZXNzLg0KKiBXb3JrcyBpbiBQcm9ncmVzcyBhcmUgYXBwcm94aW1hdGVseSA0IHRpbWVzIGxvbmdlciB0aGFuIENvbXBsZXRlIG9uZXMuDQoqIFdvcmtzIGluIFByb2dyZXNzIGdldCBhcHByb3hpbWF0ZWx5IHR3aWNlIGFzIG1hbnkgaGl0cyBhcyBDb21wbGV0ZSBvbmVzLg0KKiBXb3JrcyBpbiBQcm9ncmVzcyBnZXQgYXBwcm94aW1hdGVseSAyNSUgbW9yZSBrdWRvcyB0aGFuIENvbXBsZXRlIG9uZXMuDQoqIFdvcmtzIGluIFByb2dyZXNzIGdldCBhcHByb3hpbWF0ZWx5IDMgdGltZXMgYXMgbWFueSBjb21tZW50cyBhcyBDb21wbGV0ZSBvbmVzIChob3dldmVyIHRoZXJlJ3Mgbm8gd2F5IHRvIGZpbHRlciBvdXQgYXV0aG9yJ3MgY29tbWVudHMgaW4gdGhlIHNlYXJjaC4gc2VsZWN0aW9uLCBzbyB0aGlzIHN0YXRpc3RpYyBzaG91bGQgYmUgdGFrZW4gd2l0aCBhIGdyYWluIG9mIHNhbHQpLg0KKiBXb3JrcyBpbiBQcm9ncmVzcyBnZXQgYXBwcm94aW1hdGVseSB0d2ljZSBhcyBtYW55IGJvb2ttYXJrcyBhcyBDb21wbGV0ZSBvbmVzLg0KDQpgYGB7ciB0b3RhbFdvcmtzV0lQLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9DQoNCnN0YXRzV0lQIDwtIHN0YXRzDQpzdGF0c1dJUCREaXZpc29yIDwtIHVubGlzdChsYXBwbHkoc3RhdHNXSVAkV0lQLCBmdW5jdGlvbih4KSBzdW1tYXJ5KHN0YXRzV0lQJFdJUClbbmFtZXMoc3VtbWFyeShzdGF0c1dJUCRXSVApKSA9PSB4XSkpDQpzdGF0c1dJUCRXb3Jkcy5wZXIuV29yayA8LSBzdGF0c1dJUCRXb3Jkcy9zdGF0c1dJUCREaXZpc29yDQpzdGF0c1dJUCRIaXRzLnBlci5Xb3JrIDwtIHN0YXRzV0lQJEhpdHMvc3RhdHNXSVAkRGl2aXNvcg0Kc3RhdHNXSVAkS3Vkb3MucGVyLldvcmsgPC0gc3RhdHNXSVAkS3Vkb3Mvc3RhdHNXSVAkRGl2aXNvcg0Kc3RhdHNXSVAkQ29tbWVudHMucGVyLldvcmsgPC0gc3RhdHNXSVAkQ29tbWVudHMvc3RhdHNXSVAkRGl2aXNvcg0Kc3RhdHNXSVAkQm9va21hcmtzLnBlci5Xb3JrIDwtIHN0YXRzV0lQJEJvb2ttYXJrcy9zdGF0c1dJUCREaXZpc29yDQoNCmJhcldvcmtzV0lQIDwtIHBsb3RfYmFyKHN0YXRzV0lQLCAnV0lQJywgJ3JpZ2h0JykNCmJhcldvcmRzV0lQIDwtIHBsb3RfY29sKHN0YXRzV0lQLCAnV0lQJywgJ1dvcmRzLnBlci5Xb3JrJywgJ3JpZ2h0JykNCmJhckhpdHNXSVAgPC0gcGxvdF9jb2woc3RhdHNXSVAsICdXSVAnLCAnSGl0cy5wZXIuV29yaycsICdyaWdodCcpDQpiYXJLdWRvc1dJUCA8LSBwbG90X2NvbChzdGF0c1dJUCwgJ1dJUCcsICdLdWRvcy5wZXIuV29yaycsICdyaWdodCcpDQpiYXJDb21tZW50c1dJUCA8LSBwbG90X2NvbChzdGF0c1dJUCwgJ1dJUCcsICdDb21tZW50cy5wZXIuV29yaycsICdyaWdodCcpDQpiYXJCb29rbWFya3NXSVAgPC0gcGxvdF9jb2woc3RhdHNXSVAsICdXSVAnLCAnQm9va21hcmtzLnBlci5Xb3JrJywgJ3JpZ2h0JykNCg0KIyBwbG90X2dyaWQocGxvdF9ncmlkKCBiYXJXb3Jrc1dJUCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KIyAgICAgICAgICAgICAgICAgICAgICBiYXJXb3Jkc1dJUCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KIyAgICAgICAgICAgICAgICAgICAgICBiYXJIaXRzV0lQICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQojICAgICAgICAgICAgICAgICAgICAgIGJhckt1ZG9zV0lQICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQojICAgICAgICAgICAgICAgICAgICAgIGJhckNvbW1lbnRzV0lQICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQojICAgICAgICAgICAgICAgICAgICAgIGJhckJvb2ttYXJrc1dJUCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KIyAgICAgICAgICAgICAgICAgICAgICBhbGlnbiA9ICdodicpLA0KIyAgICAgICAgICAgZ2V0X2xlZ2VuZChiYXJXb3Jrc1dJUCArIHRoZW1lKGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCkpKSwNCiMgICAgICAgICAgIHJlbF93aWR0aHMgPSBjKDQsMSksDQojICAgICAgICAgICBhbGlnbiA9ICdodicpDQpwbG90X2dyaWQoIGJhcldvcmtzV0lQICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgIGJhcldvcmRzV0lQICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgIGJhckhpdHNXSVAgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICAgYmFyS3Vkb3NXSVAgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICAgYmFyQ29tbWVudHNXSVAgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICAgYmFyQm9va21hcmtzV0lQICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgIGFsaWduID0gJ2h2JykNCg0Kcm0oc3RhdHNXSVAsIGJhcldvcmtzV0lQLCBiYXJXb3Jkc1dJUCwgYmFySGl0c1dJUCwgYmFyS3Vkb3NXSVAsIGJhckNvbW1lbnRzV0lQLCBiYXJCb29rbWFya3NXSVApDQpgYGANCg0KDQpgYGB7ciBzdGF0c0RlbnNpdGllc1dJUCwgbWVzc2FnZSA9IEZBTFNFLCBldmFsPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQp3b3Jkc0RlbnNpdHlXSVAgPC0gcGxvdF9kZW5zaXR5KHN0YXRzLCAnV29yZHMnLCAnV0lQJywgJ3JpZ2h0JykNCmhpdHNEZW5zaXR5V0lQIDwtIHBsb3RfZGVuc2l0eShzdGF0cywgJ0hpdHMnLCAnV0lQJywgJ3JpZ2h0JykNCmt1ZG9zRGVuc2l0eVdJUCA8LSBwbG90X2RlbnNpdHkoc3RhdHMsICdLdWRvcycsICdXSVAnLCAncmlnaHQnKQ0KY29tbWVudHNEZW5zaXR5V0lQIDwtIHBsb3RfZGVuc2l0eShzdGF0cywgJ0NvbW1lbnRzJywgJ1dJUCcsICdyaWdodCcpDQpib29rbWFya3NEZW5zaXR5V0lQIDwtIHBsb3RfZGVuc2l0eShzdGF0cywgJ0Jvb2ttYXJrcycsICdXSVAnLCAncmlnaHQnKQ0KICANCnBsb3RfZ3JpZCh3b3Jkc0RlbnNpdHlXSVAgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICBoaXRzRGVuc2l0eVdJUCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgIGt1ZG9zRGVuc2l0eVdJUCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgIGNvbW1lbnRzRGVuc2l0eVdJUCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgIGJvb2ttYXJrc0RlbnNpdHlXSVAgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICBnZXRfbGVnZW5kKGt1ZG9zRGVuc2l0eVdJUCArDQogICAgICAgICAgICAgICAgICAgICB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKSkpDQoNCnJtKHdvcmRzRGVuc2l0eVdJUCwgaGl0c0RlbnNpdHlXSVAsIGt1ZG9zRGVuc2l0eVdJUCwgY29tbWVudHNEZW5zaXR5V0lQLCBib29rbWFya3NEZW5zaXR5V0lQKQ0KYGBgDQoNCmBgYHtyIHN0YXRzV29yZHNXSVAsIG1lc3NhZ2UgPSBGQUxTRSwgZXZhbD1GQUxTRSwgd2FybmluZz1GQUxTRX0NCndvcmRzSGl0c1dJUCA8LSBwbG90X3BvaW50cyhzdGF0cywgJ1dvcmRzJywgJ0hpdHMnLCAnV0lQJywgJ3JpZ2h0JykNCndvcmRzSGl0c1dJUA0Kd29yZHNLdWRvc1dJUCA8LSBwbG90X3BvaW50cyhzdGF0cywgJ1dvcmRzJywgJ0t1ZG9zJywgJ1dJUCcsICdyaWdodCcpDQp3b3Jkc0t1ZG9zV0lQDQp3b3Jkc0NvbW1lbnRzV0lQIDwtIHBsb3RfcG9pbnRzKHN0YXRzLCAnV29yZHMnLCAnQ29tbWVudHMnLCAnV0lQJywgJ3JpZ2h0JykNCndvcmRzQ29tbWVudHNXSVANCndvcmRzQm9va21hcmtzV0lQIDwtIHBsb3RfcG9pbnRzKHN0YXRzLCAnV29yZHMnLCAnQm9va21hcmtzJywgJ1dJUCcsICdyaWdodCcpDQp3b3Jkc0Jvb2ttYXJrc1dJUA0KDQpybSh3b3Jkc0hpdHNXSVAsIHdvcmRzS3Vkb3NXSVAsIHdvcmRzQ29tbWVudHNXSVAsIHdvcmRzQm9va21hcmtzV0lQKQ0KYGBgDQoNCiMgUmF0aW5nIGRpc3RyaWJ1dGlvbnMNCg0KKiBXb3JrcyByYXRlZCBHIG1ha2UgdXAgdGhlIG1vc3QgbnVtZXJvdXMgY2F0ZWdvcnkgKH4xMzAwKSwgYnV0IHRoZXkgYXJlIG9uIGF2ZXJhZ2UgdGhlIHNob3J0ZXN0ICh+MjUwMCB3b3JkcykgYW5kIGdldCBmZXdlc3QgaGl0cyg+MTAwMCksIGFuZCBmZXdlc3QgY29tbWVudHMgKH4xMCkuDQoqIFdvcmtzIHJhdGVkIFQgZm9sbG93IGluIG51bWJlcnMgKH45MDApLCBidXQgYXJlIHNpZ25pZmljYW50bHkgbG9uZ2VyICh+OTAwMCB3b3JkcykuDQoqIFdvcmtzIHJhdGVkIE0gbWFrZSBmb3IgdGhlIHNtYWxsZXN0IGNhdGVnb3J5ICh0aWVkIHdpdGggTm90IFJhdGVkLCBhdCB+IDIwMCksIGJ1dCBhcmUgdGhlIGxvbmdlc3QgKH4xM2sgd29yZHMpLg0KKiBXb3JrcyByYXRlZCBFIGFyZSBmZXcgKH4zNTApLCBsb25nZXIgdGhhbiBHIGJ1dCBzaG9ydGVyIHRoYW4gVCByYXRlZCB3b3JrcyAofjYwMDAgd29yZHMpLCBhbmQgZ2V0IHRoZSBtb3N0IGhpdHMsIGJ1dCBmZXdlciBrdWRvcyBhbmQgc2lnbmlmaWNhbnRseSBsZXNzIGNvbW1lbnRzIHRoYW4gVCBhbmQgTSByYXRlZCB3b3Jrcy4NCg0KDQpgYGB7ciB0b3RhbFdvcmtzUmF0aW5nLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9DQoNCnN0YXRzUmF0aW5nIDwtIHN0YXRzDQpzdGF0c1JhdGluZyREaXZpc29yIDwtIHVubGlzdChsYXBwbHkoc3RhdHNSYXRpbmckUmF0aW5nLCBmdW5jdGlvbih4KSBzdW1tYXJ5KHN0YXRzUmF0aW5nJFJhdGluZylbbmFtZXMoc3VtbWFyeShzdGF0c1JhdGluZyRSYXRpbmcpKSA9PSB4XSkpDQpzdGF0c1JhdGluZyRXb3Jkcy5wZXIuV29yayA8LSBzdGF0c1JhdGluZyRXb3Jkcy9zdGF0c1JhdGluZyREaXZpc29yDQpzdGF0c1JhdGluZyRIaXRzLnBlci5Xb3JrIDwtIHN0YXRzUmF0aW5nJEhpdHMvc3RhdHNSYXRpbmckRGl2aXNvcg0Kc3RhdHNSYXRpbmckS3Vkb3MucGVyLldvcmsgPC0gc3RhdHNSYXRpbmckS3Vkb3Mvc3RhdHNSYXRpbmckRGl2aXNvcg0Kc3RhdHNSYXRpbmckQ29tbWVudHMucGVyLldvcmsgPC0gc3RhdHNSYXRpbmckQ29tbWVudHMvc3RhdHNSYXRpbmckRGl2aXNvcg0Kc3RhdHNSYXRpbmckQm9va21hcmtzLnBlci5Xb3JrIDwtIHN0YXRzUmF0aW5nJEJvb2ttYXJrcy9zdGF0c1JhdGluZyREaXZpc29yDQoNCmJhcldvcmtzUmF0aW5nIDwtIHBsb3RfYmFyKHN0YXRzUmF0aW5nLCAnUmF0aW5nJywgJ3JpZ2h0JykNCmJhcldvcmRzUmF0aW5nIDwtIHBsb3RfY29sKHN0YXRzUmF0aW5nLCAnUmF0aW5nJywgJ1dvcmRzLnBlci5Xb3JrJywgJ3JpZ2h0JykNCmJhckhpdHNSYXRpbmcgPC0gcGxvdF9jb2woc3RhdHNSYXRpbmcsICdSYXRpbmcnLCAnSGl0cy5wZXIuV29yaycsICdyaWdodCcpDQpiYXJLdWRvc1JhdGluZyA8LSBwbG90X2NvbChzdGF0c1JhdGluZywgJ1JhdGluZycsICdLdWRvcy5wZXIuV29yaycsICdyaWdodCcpDQpiYXJDb21tZW50c1JhdGluZyA8LSBwbG90X2NvbChzdGF0c1JhdGluZywgJ1JhdGluZycsICdDb21tZW50cy5wZXIuV29yaycsICdyaWdodCcpDQpiYXJCb29rbWFya3NSYXRpbmcgPC0gcGxvdF9jb2woc3RhdHNSYXRpbmcsICdSYXRpbmcnLCAnQm9va21hcmtzLnBlci5Xb3JrJywgJ3JpZ2h0JykNCg0KcGxvdF9ncmlkKCBiYXJXb3Jrc1JhdGluZyArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgICBiYXJXb3Jkc1JhdGluZyArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgICBiYXJIaXRzUmF0aW5nICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgIGJhckt1ZG9zUmF0aW5nICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgIGJhckNvbW1lbnRzUmF0aW5nICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgIGJhckJvb2ttYXJrc1JhdGluZyArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgICBhbGlnbiA9ICdodicpDQoNCnJtKHN0YXRzUmF0aW5nLCBiYXJXb3Jrc1JhdGluZywgYmFyV29yZHNSYXRpbmcsIGJhckhpdHNSYXRpbmcsIGJhckt1ZG9zUmF0aW5nLCBiYXJDb21tZW50c1JhdGluZywgYmFyQm9va21hcmtzUmF0aW5nKQ0KYGBgDQoNCmBgYHtyIHN0YXRzRGVuc2l0aWVzUmF0aW5nLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWw9RkFMU0V9DQp3b3Jkc0RlbnNpdHlSYXRpbmcgPC0gcGxvdF9kZW5zaXR5KHN0YXRzLCAnV29yZHMnLCAnUmF0aW5nJywgJ3JpZ2h0JykNCmhpdHNEZW5zaXR5UmF0aW5nIDwtIHBsb3RfZGVuc2l0eShzdGF0cywgJ0hpdHMnLCAnUmF0aW5nJywgJ3JpZ2h0JykNCmt1ZG9zRGVuc2l0eVJhdGluZyA8LSBwbG90X2RlbnNpdHkoc3RhdHMsICdLdWRvcycsICdSYXRpbmcnLCAncmlnaHQnKQ0KY29tbWVudHNEZW5zaXR5UmF0aW5nIDwtIHBsb3RfZGVuc2l0eShzdGF0cywgJ0NvbW1lbnRzJywgJ1JhdGluZycsICdyaWdodCcpDQpib29rbWFya3NEZW5zaXR5UmF0aW5nIDwtIHBsb3RfZGVuc2l0eShzdGF0cywgJ0Jvb2ttYXJrcycsICdSYXRpbmcnLCAncmlnaHQnKQ0KDQpwbG90X2dyaWQod29yZHNEZW5zaXR5UmF0aW5nICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgaGl0c0RlbnNpdHlSYXRpbmcgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICBrdWRvc0RlbnNpdHlSYXRpbmcgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICBjb21tZW50c0RlbnNpdHlSYXRpbmcgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICBib29rbWFya3NEZW5zaXR5UmF0aW5nICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgZ2V0X2xlZ2VuZChrdWRvc0RlbnNpdHlSYXRpbmcgKw0KICAgICAgICAgICAgICAgICAgICAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSkpKQ0KDQpybSh3b3Jkc0RlbnNpdHlSYXRpbmcsIGhpdHNEZW5zaXR5UmF0aW5nLCBrdWRvc0RlbnNpdHlSYXRpbmcsIGNvbW1lbnRzRGVuc2l0eVJhdGluZywgYm9va21hcmtzRGVuc2l0eVJhdGluZykNCmBgYA0KDQoNCmBgYHtyIHN0YXRzV29yZHNSYXRpbmcsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbD1GQUxTRX0NCndvcmRzSGl0c1JhdGluZyA8LSBwbG90X3BvaW50cyhzdGF0cywgJ1dvcmRzJywgJ0hpdHMnLCAnUmF0aW5nJywgJ3JpZ2h0JykNCndvcmRzSGl0c1JhdGluZw0Kd29yZHNLdWRvc1JhdGluZyA8LSBwbG90X3BvaW50cyhzdGF0cywgJ1dvcmRzJywgJ0t1ZG9zJywgJ1JhdGluZycsICdyaWdodCcpDQp3b3Jkc0t1ZG9zUmF0aW5nDQp3b3Jkc0NvbW1lbnRzUmF0aW5nIDwtIHBsb3RfcG9pbnRzKHN0YXRzLCAnV29yZHMnLCAnQ29tbWVudHMnLCAnUmF0aW5nJywgJ3JpZ2h0JykNCndvcmRzQ29tbWVudHNSYXRpbmcNCndvcmRzQm9va21hcmtzUmF0aW5nIDwtIHBsb3RfcG9pbnRzKHN0YXRzLCAnV29yZHMnLCAnQm9va21hcmtzJywgJ1JhdGluZycsICdyaWdodCcpDQp3b3Jkc0Jvb2ttYXJrc1JhdGluZw0KDQpybSh3b3Jkc0hpdHNSYXRpbmcsIHdvcmRzS3Vkb3NSYXRpbmcsIHdvcmRzQ29tbWVudHNSYXRpbmcsIHdvcmRzQm9va21hcmtzUmF0aW5nKQ0KDQpgYGANCg0KIyBDYXRlZ29yaWVzDQoNClRoZXJlIGFyZSBgciBsZW5ndGgoY2F0ZWdvcnlbdW5saXN0KGxhcHBseShjYXRlZ29yeSwgZnVuY3Rpb24oeCkgbGVuZ3RoKHgpKSkgPT0gMV0pYCB3b3JrcyB0YWdnZWQgd2l0aCBhIHNpbmdsZSBjYXRlZ29yeSwgYW5kIGByIGxlbmd0aChjYXRlZ29yeVt1bmxpc3QobGFwcGx5KGNhdGVnb3J5LCBmdW5jdGlvbih4KSBsZW5ndGgoeCkpKSA+IDFdKWAgdGFnZ2VkIHdpdGggMiBvciBtb3JlICh1cCB1bnRpbCBhbGwgNikuDQoNCidGL00nIGlzIHRoZSBtb3N0IHBvcHVsYXIgY2F0ZWdvcnksIGZvbGxvd2VkIGJ5ICdNL00nLCAnR2VuJywgYW5kICdGL0YnLg0KDQpNdWx0aXBsZSBjYXRlZ29yeSBmaWNzIHN0cm9uZ2x5IGNvbnRyaWJ1dGUgdG93YXJkcyAnRi9NJyBjb3VudCwgdGhlbiB0byAnTS9NJywgJ0dlbicsIGFuZCAnRi9GJywgYW5kIG9ubHkgbWFyZ2luYWxseSB0byAnTXVsdGknIGFuZCAnT3RoZXInLg0KDQpgYGB7ciBjYXRlZ29yaWVzQmFycywgbWVzc2FnZSA9IEZBTFNFfQ0KDQpzaW5nbGVDYXRlZ29yeVN1bW1hcnkgPC0gc3VtbWFyeShhcy5mYWN0b3IodW5saXN0KGNhdGVnb3J5W3VubGlzdChsYXBwbHkoY2F0ZWdvcnksIGZ1bmN0aW9uKHgpIGxlbmd0aCh4KSkpID09IDFdKSkpDQpzaW5nbGVDYXRlZ29yeVN1bW1hcnkgPC0gZGF0YS5mcmFtZShDYXRlZ29yeSA9IG5hbWVzKHNpbmdsZUNhdGVnb3J5U3VtbWFyeSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOdW1iZXIub2YuV29ya3MgPSBzaW5nbGVDYXRlZ29yeVN1bW1hcnkpDQpzaW5nbGVDYXRlZ29yeVN1bW1hcnkkU3BsaXQgPC0gIlNpbmdsZSBjYXRlZ29yeSINCg0KbXVsdGlwbGVDYXRlZ29yeVN1bW1hcnkgPC0gZGF0YS5mcmFtZShDYXRlZ29yeSA9IGMoJ0dlbicsICdGL0YnLCAnRi9NJywgJ00vTScsICdNdWx0aScsICdPdGhlcicsICdObyBjYXRlZ29yeScpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTnVtYmVyLm9mLldvcmtzID0gYyhzdW0oZ3JlcGwoJ0dlbicsY2F0ZWdvcnkpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtKGdyZXBsKCdGL0YnLGNhdGVnb3J5KSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bShncmVwbCgnRi9NJyxjYXRlZ29yeSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW0oZ3JlcGwoJ00vTScsY2F0ZWdvcnkpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtKGdyZXBsKCdNdWx0aScsY2F0ZWdvcnkpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtKGdyZXBsKCdPdGhlcicsY2F0ZWdvcnkpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtKGdyZXBsKCdObyBjYXRlZ29yeScsY2F0ZWdvcnkpKSkgKQ0KbXVsdGlwbGVDYXRlZ29yeVN1bW1hcnkkU3BsaXQgPC0gIkFsbCB3b3JrcyINCg0KY2F0ZWdvcnlTdW1tYXJ5IDwtIHJiaW5kKHNpbmdsZUNhdGVnb3J5U3VtbWFyeSwgbXVsdGlwbGVDYXRlZ29yeVN1bW1hcnkpDQpjYXRlZ29yeVN1bW1hcnkkQ2F0ZWdvcnkgPC0gZmFjdG9yKGNhdGVnb3J5U3VtbWFyeSRDYXRlZ29yeSwgbGV2ZWxzID0gYygnR2VuJywgJ0YvRicsICdGL00nLCAnTS9NJywgJ011bHRpJywgJ090aGVyJywgJ05vIGNhdGVnb3J5JykpDQpjYXRlZ29yeVN1bW1hcnkkU3BsaXQgPC0gZmFjdG9yKGNhdGVnb3J5U3VtbWFyeSRTcGxpdCwgbGV2ZWxzID0gYygiU2luZ2xlIGNhdGVnb3J5IiwgIkFsbCB3b3JrcyIpKQ0KDQpwbG90Q2F0ZWdvcmllcyA8LSBnZ3Bsb3QoY2F0ZWdvcnlTdW1tYXJ5LCBhZXMoeCA9IENhdGVnb3J5LCB5ID0gTnVtYmVyLm9mLldvcmtzKSkgKyANCiAgICAgICAgICAgICAgICAgIGdlb21fY29sKGFscGhhPTEpKw0KICAgICAgICAgICAgICAgICAgdGhlbWVfaGFsZl9vcGVuKCkgKw0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZF9ncmlkKCkgKw0KICAgICAgICAgICAgICAgICAgZmFjZXRfd3JhcCguflNwbGl0KSArDQogICAgICAgICAgICAgICAgICB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAxLCBoanVzdD0xKSkrDQogICAgICAgICAgICAgICAgICBsYWJzKHk9Ik51bWJlciBvZiBXb3JrcyIpDQpwbG90Q2F0ZWdvcmllcw0KDQpybShzaW5nbGVDYXRlZ29yeVN1bW1hcnksIG11bHRpcGxlQ2F0ZWdvcnlTdW1tYXJ5LCBjYXRlZ29yeVN1bW1hcnksIHBsb3RDYXRlZ29yaWVzKQ0KDQpgYGANCg0KIyBFbmdhZ2VtZW50IGJ5IGEgc2luZ2xlIGNhdGVnb3J5DQoNCkZvciBzaW1wbGljaXR5IEknbSBvbmx5IGxvb2tpbmcgYXQgd29ya3MgdGFnZ2VkIHdpdGggYSBzaW5nbGUgY2F0ZWdvcnkgaGVyZS4NCg0KIk11bHRpIiBzZWVtcyB0byBoYXZlIG1vc3Qgd29yZHMsIGRlc3BpdGUgYmVpbmcgYSByYXRoZXIgc21hbGwgY2F0ZWdvcnksIGFuZCBjb2xsZWN0cyBxdWl0ZSBhIGJpdCBvZiBIaXRzIGFuZCBDb21tZW50cy4gSXQncyBwb3NzaWJsZSB0aGF0IGEgbnVtYmVyIG9mIHRob3NlIHdvcmtzIGFyZSBjb2xsZWN0aW9ucyBvZiBzdG9yaWVzIGZvciBtYW55IGZhbmRvbXMsIHdoaWNoIGFtcGxpZmllcyB0aGUgbnVtYmVyIG9mIEhpdHMgYW5kIENvbW1lbnRzLCBidXQgdGhhdCByZXF1aXJlcyBmdXJ0aGVyIGludmVzdGlnYXRpb24uDQoNCmBgYHtyIGNhdGVnb3JpZXNTaW5nbGVFbmdhZ2VtZW50LCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9DQoNCnN0YXRzQ2F0ZWdvcnkgPC0gc3RhdHNbdW5saXN0KGxhcHBseShjYXRlZ29yeSwgZnVuY3Rpb24oeCkgbGVuZ3RoKHgpKSkgPT0gMSxdDQpzdGF0c0NhdGVnb3J5JENhdGVnb3J5IDwtIGFzLmZhY3Rvcih1bmxpc3QoY2F0ZWdvcnlbdW5saXN0KGxhcHBseShjYXRlZ29yeSwgZnVuY3Rpb24oeCkgbGVuZ3RoKHgpKSkgPT0gMV0pKQ0Kc3RhdHNDYXRlZ29yeSRDYXRlZ29yeSA8LSBmYWN0b3Ioc3RhdHNDYXRlZ29yeSRDYXRlZ29yeSwgbGV2ZWxzID0gYygnR2VuJywgJ0YvRicsICdGL00nLCAnTS9NJywgJ011bHRpJywgJ090aGVyJywgJ05vIGNhdGVnb3J5JykpDQpzdGF0c0NhdGVnb3J5JERpdmlzb3IgPC0gdW5saXN0KGxhcHBseShzdGF0c0NhdGVnb3J5JENhdGVnb3J5LCBmdW5jdGlvbih4KSBzdW1tYXJ5KHN0YXRzQ2F0ZWdvcnkkQ2F0ZWdvcnkpW25hbWVzKHN1bW1hcnkoc3RhdHNDYXRlZ29yeSRDYXRlZ29yeSkpID09IHhdKSkNCnN0YXRzQ2F0ZWdvcnkkV29yZHMucGVyLldvcmsgPC0gc3RhdHNDYXRlZ29yeSRXb3Jkcy9zdGF0c0NhdGVnb3J5JERpdmlzb3INCnN0YXRzQ2F0ZWdvcnkkSGl0cy5wZXIuV29yayA8LSBzdGF0c0NhdGVnb3J5JEhpdHMvc3RhdHNDYXRlZ29yeSREaXZpc29yDQpzdGF0c0NhdGVnb3J5JEt1ZG9zLnBlci5Xb3JrIDwtIHN0YXRzQ2F0ZWdvcnkkS3Vkb3Mvc3RhdHNDYXRlZ29yeSREaXZpc29yDQpzdGF0c0NhdGVnb3J5JENvbW1lbnRzLnBlci5Xb3JrIDwtIHN0YXRzQ2F0ZWdvcnkkQ29tbWVudHMvc3RhdHNDYXRlZ29yeSREaXZpc29yDQpzdGF0c0NhdGVnb3J5JEJvb2ttYXJrcy5wZXIuV29yayA8LSBzdGF0c0NhdGVnb3J5JEJvb2ttYXJrcy9zdGF0c0NhdGVnb3J5JERpdmlzb3INCnN0YXRzQ2F0ZWdvcnkkV29ya3MuUGVyY2VudCA8LSAxL3N0YXRzQ2F0ZWdvcnkkRGl2aXNvcg0KDQpiYXJXb3Jrc0NhdGVnb3J5IDwtIHBsb3RfYmFyX2NvbG9yKHN0YXRzQ2F0ZWdvcnksICdDYXRlZ29yeScsICdSYXRpbmcnLCAncmlnaHQnKQ0KYmFyV29yZHNDYXRlZ29yeSA8LSBwbG90X2NvbF9jb2xvcihzdGF0c0NhdGVnb3J5LCAnQ2F0ZWdvcnknLCAnV29yZHMucGVyLldvcmsnLCAnUmF0aW5nJywgJ3JpZ2h0JykNCmJhckhpdHNDYXRlZ29yeSA8LSBwbG90X2NvbF9jb2xvcihzdGF0c0NhdGVnb3J5LCAnQ2F0ZWdvcnknLCAnSGl0cy5wZXIuV29yaycsICdSYXRpbmcnLCAncmlnaHQnKQ0KYmFyS3Vkb3NDYXRlZ29yeSA8LSBwbG90X2NvbF9jb2xvcihzdGF0c0NhdGVnb3J5LCAnQ2F0ZWdvcnknLCAnS3Vkb3MucGVyLldvcmsnLCAnUmF0aW5nJywgJ3JpZ2h0JykNCmJhckNvbW1lbnRzQ2F0ZWdvcnkgPC0gcGxvdF9jb2xfY29sb3Ioc3RhdHNDYXRlZ29yeSwgJ0NhdGVnb3J5JywgJ0NvbW1lbnRzLnBlci5Xb3JrJywgJ1JhdGluZycsICdyaWdodCcpDQpiYXJCb29rbWFya3NDYXRlZ29yeSA8LSBwbG90X2NvbF9jb2xvcihzdGF0c0NhdGVnb3J5LCAnQ2F0ZWdvcnknLCAnQm9va21hcmtzLnBlci5Xb3JrJywnUmF0aW5nJywgJ3JpZ2h0JykNCg0KcGxvdF9ncmlkKHBsb3RfZ3JpZCggYmFyV29ya3NDYXRlZ29yeSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgICBiYXJXb3Jkc0NhdGVnb3J5ICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgIGJhckhpdHNDYXRlZ29yeSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICAgICAgICAgICBiYXJLdWRvc0NhdGVnb3J5ICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogICAgICAgICAgIGJhckNvbW1lbnRzQ2F0ZWdvcnkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICAgYmFyQm9va21hcmtzQ2F0ZWdvcnkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgICAgICAgICAgYWxpZ24gPSAnaHYnKSwNCiAgICAgICAgICBnZXRfbGVnZW5kKGJhcldvcmtzQ2F0ZWdvcnkgKyB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKSksDQogICAgICAgICAgcmVsX3dpZHRocyA9IGMoNCwxKSkNCg0KYGBgDQoNCiMgUmF0aW5ncyBwZXJjZW50YWdlcyBieSBhIHNpbmdsZSBjYXRlZ29yeQ0KDQpPdXQgb2YgdGhlIDMgbWFpbiBzaGlwcGluZyBjYXRlZ29yaWVzIGluIGFic29sdXRlIG51bWJlcnMgIk0vTSIgaGFzIG1vc3QgRSByYXRlZCB3b3JrcywgYW5kICJGL0YiIGhhcyB0aGUgbGVhc3QsIGJ1dCBpbiBwZXJjZW50YWdlcyBvZiB0b3RhbCB3b3JrcyAiRi9GIiBhbmQgIkYvTSIgYXJlIGRpc3RyaWJ1dGVkIGFsbW9zdCBpZGVudGljYWxseSwgd2hpbGUgIk0vTSIgc2tld3MgbW9yZSB0b3dhcmRzIE0gYW5kIEUgcmF0ZWQgd29ya3MuDQoNCmBgYHtyIGNhdGVnb3JpZXNTaW5nbGVFbmdhZ2VtZW50UGVyY2VudCwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQ0KDQpwbG90V29ya3NDYXRlZ29yeU5vcm1hbGl6ZWQgPC0gcGxvdF9jb2xfY29sb3Ioc3RhdHNDYXRlZ29yeSwgJ1JhdGluZycsICdXb3Jrcy5QZXJjZW50JywgJ1JhdGluZycsICdub25lJykrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OnBlcmNlbnQpKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhY2V0X3dyYXAoLn5DYXRlZ29yeSkNCnBsb3RXb3Jrc0NhdGVnb3J5Tm9ybWFsaXplZA0KDQpybShiYXJXb3Jrc0NhdGVnb3J5LCBiYXJXb3Jkc0NhdGVnb3J5LCBiYXJIaXRzQ2F0ZWdvcnksIGJhckt1ZG9zQ2F0ZWdvcnksIGJhckNvbW1lbnRzQ2F0ZWdvcnksIGJhckJvb2ttYXJrc0NhdGVnb3J5LCBwbG90V29ya3NDYXRlZ29yeU5vcm1hbGl6ZWQpDQoNCmBgYA0KDQojIFNpbmdsZSBDYXRlZ29yeSB0aHJvdWdoIHRpbWUNCg0KSW50ZXJlc3RpbmdseSwgc2Vhc29uIDEgc2VlcyBhIHBlYWsgb2YgJ0dlbicgY2F0ZWdvcnkuIFNlYXNvbiAyIGdpdmVzIGhpZ2hlciByaXNlIHRvICdNL00nIChwb3NzaWJseSByZWxhdGVkIHRvIEFhcmF2b3MgcmV2ZWFsIGFuZCAnQWFyYXZvcy9WaXJlbiAoVGhlIERyYWdvbiBQcmluY2UpJyBzaGlwcGluZykgYW5kICdGL00nICh0aGUgcmlzZSBvZiAnQ2FsbHVtL1JheWxhIChUaGUgRHJhZ29uIFByaW5jZSknPyksIGFuZCBzZWFzb24gMyBpcyBmb2xsb3dlZCBieSBhIGhpZ2ggcmlzZSBvZiAnRi9GJyAoZm9sbG93aW5nIHRoZSBkZXZlbG9wbWVudCBvZiAnQW1heWEvSmFuYWkgKFRoZSBEcmFnb24gUHJpbmNlKScgcmVsYXRpb25zaGlwKSBhbmQgYSBtb2Rlc3QgJ00vTScgcGVhayAoJ0V0aGFyaS9SdW5hYW4gKFRoZSBEcmFnb24gUHJpbmNlKScgZHVlIHRvIEV0aGFyaSBmaW5hbGx5IGdldHRpbmcgb2ZmaWNpYWwgbmFtZT8pLg0KDQpgYGB7ciBzaW5nbGVDYXRlZ29yeVRpbWUsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9DQoNCnBsb3REYXRlc0NhdGVnb3J5RGVuc2l0eSA8LSBnZ3Bsb3Qoc3RhdHNDYXRlZ29yeSwgYWVzKHggPSBEYXRlLCBjb2w9Q2F0ZWdvcnkpKSArIA0KICAgICAgICAgICAgICAgICAgICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjEpKw0KICAgICAgICAgICAgICAgICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9c2Vhc29ucykrDQogICAgICAgICAgICAgICAgICAgIHNjYWxlX3hfZGF0ZShkYXRlX2JyZWFrcz0iMiBtb250aHMiKSsNCiAgICAgICAgICAgICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbXBhbGV0dGUpICsNCiAgICAgICAgICAgICAgICAgICAgdGhlbWVfaGFsZl9vcGVuKCkgKw0KICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kX2dyaWQoKSArDQogICAgICAgICAgICAgICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ3JpZ2h0JykNCnBsb3REYXRlc0NhdGVnb3J5RGVuc2l0eQ0KDQpybShwbG90RGF0ZXNDYXRlZ29yeURlbnNpdHkpDQpgYGANCg0KIyBNb3N0IHBvcHVsYXIgc2hpcCB0YWdzDQoNClVuZGVuaWFibHkgdGhlIG1vc3QgcG9wdWxhciBzaGlwIHRhZyBpcyAiQ2FsbHVtL1JheWxhIChUaGUgRHJhZ29uIFByaW5jZSkiLCBzY29yaW5nIGFsbW9zdCA0IHRpbWVzIGFzIG1hbnkgc3RvcmllcyBhcyB0aGUgbmV4dCBtb3N0IHBvcHVsYXIgc2hpcCB0YWcgIkFtYXlhL0phbmFpIChUaGUgRHJhZ29uIFByaW5jZSkiLg0KDQpgYGB7ciBzaGlwc0hpc3RvZ3JhbSwgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQ0KDQp0b3BMaXN0IDwtIDgNCnRvcFJlbGF0aW9uc2hpcHNUYWJsZTwtIGRhdGEuZnJhbWUoJ1JlbGF0aW9uc2hpcCcgPSBuYW1lcyhzdW1tYXJ5KGFzLmZhY3Rvcih1bmxpc3QocmVsYXRpb25zaGlwcykpKVsxOnRvcExpc3RdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgJ051bWJlciBvZiBTdG9yaWVzJyA9IHN1bW1hcnkoYXMuZmFjdG9yKHVubGlzdChyZWxhdGlvbnNoaXBzKSkpWzE6dG9wTGlzdF0pDQpyb3cubmFtZXModG9wUmVsYXRpb25zaGlwc1RhYmxlKSA8LSBjKCkNCnRvcFJlbGF0aW9uc2hpcHNUYWJsZSA8LSB0b3BSZWxhdGlvbnNoaXBzVGFibGVbb3JkZXIodG9wUmVsYXRpb25zaGlwc1RhYmxlJE51bWJlci5vZi5TdG9yaWVzLCBkZWNyZWFzaW5nID0gVFJVRSksXQ0KdG9wUmVsYXRpb25zaGlwc1RhYmxlJFJlbGF0aW9uc2hpcCA8LSBmYWN0b3IoYXMuY2hhcmFjdGVyKHRvcFJlbGF0aW9uc2hpcHNUYWJsZSRSZWxhdGlvbnNoaXApLCBsZXZlbHM9YXMuY2hhcmFjdGVyKHRvcFJlbGF0aW9uc2hpcHNUYWJsZSRSZWxhdGlvbnNoaXApKQ0KDQpyZWxhdGlvbnNoaXBzU3RhdHMgPC0gZGF0YS5mcmFtZShEYXRlID0gc3RhdHMkRGF0ZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcDEgPSByZXAoMCwgbGVuZ3RoKHN0YXRzJERhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcDIgPSByZXAoMCwgbGVuZ3RoKHN0YXRzJERhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcDMgPSByZXAoMCwgbGVuZ3RoKHN0YXRzJERhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcDQgPSByZXAoMCwgbGVuZ3RoKHN0YXRzJERhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcDUgPSByZXAoMCwgbGVuZ3RoKHN0YXRzJERhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcDYgPSByZXAoMCwgbGVuZ3RoKHN0YXRzJERhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcDcgPSByZXAoMCwgbGVuZ3RoKHN0YXRzJERhdGUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aW9uc2hpcDggPSByZXAoMCwgbGVuZ3RoKHN0YXRzJERhdGUpKSkNCmZvciAoaSBpbiAxOnRvcExpc3Qpew0KICBtYXRjaGluZ1ZlY3RvciA8LSBsYXBwbHkocmVsYXRpb25zaGlwcywgbWF0Y2gsIHRhYmxlPWFzLmNoYXJhY3Rlcih0b3BSZWxhdGlvbnNoaXBzVGFibGUkUmVsYXRpb25zaGlwW2ldKSkNCiAgbWF0Y2hpbmdWZWN0b3IgPC0gdW5saXN0KGxhcHBseShtYXRjaGluZ1ZlY3Rvciwgc3VtLCBuYS5ybT1UUlVFKSkNCiAgcmVsYXRpb25zaGlwc1N0YXRzW2krMV0gPC0gbWF0Y2hpbmdWZWN0b3INCn0NCg0KI2NvbG5hbWVzKHJlbGF0aW9uc2hpcHNTdGF0cylbMjo5XSA8LSBnc3ViKCcvJywgJ1xcLycsIHRvcFJlbGF0aW9uc2hpcHNUYWJsZSRSZWxhdGlvbnNoaXApDQoNCnBsb3RMZWdlbmRSZWxhdGlvbnNoaXBzIDwtIGdncGxvdCh0b3BSZWxhdGlvbnNoaXBzVGFibGUsIGFlcyh4PVJlbGF0aW9uc2hpcCwgeT1OdW1iZXIub2YuU3RvcmllcywgZmlsbD1SZWxhdGlvbnNoaXApKSsNCiAgZ2VvbV9jb2woYWxwaGE9MC43KSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY3VzdG9tcGFsZXR0ZSkrDQogIHRoZW1lX2hhbGZfb3BlbigpICsNCiAgYmFja2dyb3VuZF9ncmlkKCkgKw0KICBsYWJzKHg9IiIseT0nTnVtYmVyIG9mIFN0b3JpZXMnKSsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQ0KcGxvdExlZ2VuZFJlbGF0aW9uc2hpcHMrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKQ0KYGBgDQoNCiMgU2hpcCB0YWdzIHRocm91Z2ggdGltZQ0KDQoiQW1heWEvSmFuYWkgKFRoZSBEcmFnb24gUHJpbmNlKSIgc2hhcnBseSB0b29rIG9mIGluIHBvcHVsYXJpdHkgYWZ0ZXIgc2Vhc29uIDMuIEV0aGFyaSdzIG5hbWUgaGFzbid0IGJlZW4gcmV2ZWFsZWQgdW50aWwgc2Vhc29uIDMsIHNvIGF1dGhvcnMgd2VyZSB1c2luZyAiUnVuYWFuL1RpbmtlciB8IE5lY2tsYWNlIEVsZiAoVGhlIERyYWdvbiBQcmluY2UpIiBpbnN0ZWFkIG9mICJFdGhhcmkvUnVuYWFuIChUaGUgRHJhZ29uIFByaW5jZSkiLCBidXQgQW8zIHdvdWxkIGNvbnNpZGVyIHRoZW0gc3lub255bW91cy4gUm9tYW5jZSB0YWcgIkNhbGx1bS9SYXlsYSAoVGhlIERyYWdvbiBQcmluY2UpIiBpcyBzaWduaWZpY2FudGx5IG1vcmUgcG9wdWxhciB0aGFuIHBsYXRvbmljL2ZyaWVuc2hpcCB0YWcgIkNhbGx1bSAmIFJheWxhIChUaGUgRHJhZ29uIFByaW5jZSkiLCBob3dldmVyIGl0J3Mgd29ydGggbm90aW5nIHRoYXQgb3V0IG9mIGByIHRvcFJlbGF0aW9uc2hpcHNUYWJsZVt0b3BSZWxhdGlvbnNoaXBzVGFibGUkUmVsYXRpb25zaGlwID09ICJDYWxsdW0gJiBSYXlsYSAoVGhlIERyYWdvbiBQcmluY2UpIixdJE51bWJlci5vZi5TdG9yaWVzYCBzdG9yaWVzIHRhZ2dlZCB3aXRoIGEgZnJpZW5kc2hpcC9wbGF0b25pYyBvbmUgYHIgc3VtKChyZWxhdGlvbnNoaXBzU3RhdHMkcmVsYXRpb25zaGlwMStyZWxhdGlvbnNoaXBzU3RhdHMkcmVsYXRpb25zaGlwMyk+MSlgIGFyZSB0YWdnZWQgd2l0aCBib3RoLCBwb3NzaWJseSBtYWtpbmcgc3BlY2lmaWNhbGx5IGZyaWVuZHNoaXAvcGxhdG9uaWMgY29udGVudCBtb3JlIGRpZmZpY3VsdCB0byBmaW5kLg0KDQpgYGB7ciBzaGlwVGFnc1RpbWUsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9DQoNCnBsb3RSZWxhdGlvbnNoaXBzIDwtIGdncGxvdCgpICsNCiAgICBnZW9tX2RlbnNpdHkoZGF0YSA9IHJlbGF0aW9uc2hpcHNTdGF0c1tyZWxhdGlvbnNoaXBzU3RhdHMkcmVsYXRpb25zaGlwMSA+IDAsXSwgbWFwcGluZz1hZXMoeCA9IERhdGUpLCBjb2xvdXI9Y3VzdG9tcGFsZXR0ZVsxXSkrDQogICAgZ2VvbV9kZW5zaXR5KGRhdGEgPSByZWxhdGlvbnNoaXBzU3RhdHNbcmVsYXRpb25zaGlwc1N0YXRzJHJlbGF0aW9uc2hpcDIgPiAwLF0sIG1hcHBpbmc9YWVzKHggPSBEYXRlKSwgY29sb3VyPWN1c3RvbXBhbGV0dGVbMl0pKw0KICAgIGdlb21fZGVuc2l0eShkYXRhID0gcmVsYXRpb25zaGlwc1N0YXRzW3JlbGF0aW9uc2hpcHNTdGF0cyRyZWxhdGlvbnNoaXAzID4gMCxdLCBtYXBwaW5nPWFlcyh4ID0gRGF0ZSksIGNvbG91cj1jdXN0b21wYWxldHRlWzNdKSsNCiAgICBnZW9tX2RlbnNpdHkoZGF0YSA9IHJlbGF0aW9uc2hpcHNTdGF0c1tyZWxhdGlvbnNoaXBzU3RhdHMkcmVsYXRpb25zaGlwNCA+IDAsXSwgbWFwcGluZz1hZXMoeCA9IERhdGUpLCBjb2xvdXI9Y3VzdG9tcGFsZXR0ZVs0XSkrDQogICAgZ2VvbV9kZW5zaXR5KGRhdGEgPSByZWxhdGlvbnNoaXBzU3RhdHNbcmVsYXRpb25zaGlwc1N0YXRzJHJlbGF0aW9uc2hpcDUgPiAwLF0sIG1hcHBpbmc9YWVzKHggPSBEYXRlKSwgY29sb3VyPWN1c3RvbXBhbGV0dGVbNV0pKw0KICAgIGdlb21fZGVuc2l0eShkYXRhID0gcmVsYXRpb25zaGlwc1N0YXRzW3JlbGF0aW9uc2hpcHNTdGF0cyRyZWxhdGlvbnNoaXA2ID4gMCxdLCBtYXBwaW5nPWFlcyh4ID0gRGF0ZSksIGNvbG91cj1jdXN0b21wYWxldHRlWzZdKSsNCiAgICBnZW9tX2RlbnNpdHkoZGF0YSA9IHJlbGF0aW9uc2hpcHNTdGF0c1tyZWxhdGlvbnNoaXBzU3RhdHMkcmVsYXRpb25zaGlwNyA+IDAsXSwgbWFwcGluZz1hZXMoeCA9IERhdGUpLCBjb2xvdXI9Y3VzdG9tcGFsZXR0ZVs3XSkrDQogICAgZ2VvbV9kZW5zaXR5KGRhdGEgPSByZWxhdGlvbnNoaXBzU3RhdHNbcmVsYXRpb25zaGlwc1N0YXRzJHJlbGF0aW9uc2hpcDggPiAwLF0sIG1hcHBpbmc9YWVzKHggPSBEYXRlKSwgY29sb3VyPWN1c3RvbXBhbGV0dGVbOF0pKw0KICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1zZWFzb25zKSsNCiAgICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3M9IjIgbW9udGhzIikrDQogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbXBhbGV0dGUpICsNCiAgICB0aGVtZV9oYWxmX29wZW4oKSArDQogICAgYmFja2dyb3VuZF9ncmlkKCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkNCg0KbXlsZWdlbmQgPC0gZ2V0X2xlZ2VuZChwbG90TGVnZW5kUmVsYXRpb25zaGlwcykNCg0KcGxvdF9ncmlkKHBsb3RSZWxhdGlvbnNoaXBzLCBteWxlZ2VuZCwNCiAgICAgICAgICByZWxfd2lkdGhzID0gYygyLDEpLCBucm93PTEpDQojcGxvdFJlbGF0aW9uc2hpcHMNCg0KI3JtKHNlYXNvbnMsIHBsb3REYXRlc1JhdGluZ0RlbnNpdHkpDQpgYGANCg0KIyBBcmNoaXZlIFdhcm5pbmdzDQoNCk1ham9yaXR5IG9mIHdvcmtzIGFyZSB0YWdnZWQgd2l0aCAiTm8gQXJjaGl2ZSBXYXJuaW5ncyBBcHBseSIsIGZvbGxvd2VkIGJ5IGEgc2l6YWJsZSBmcmFjdGlvbiBvZiAiQ3JlYXRvciBDaG9zZSBOb3QgVG8gVXNlIEFyY2hpdmUgV2FybmluZ3MiLiBJdCBzZWVtcyB0byBiZSBhIGNvbW1vbiBtYXR0ZXIgb2YgY29uZnVzaW9uIGJldHdlZW4gdGhlIHVzYWdlIG9mIHRob3NlIHR3byB3YXJuaW5ncywgc28gaXQncyBwb3NzaWJsZSB0aGF0IGEgbG90IG9mICJDcmVhdG9yIENob3NlIE5vdCBUbyBVc2UgQXJjaGl2ZSBXYXJuaW5ncyIgYXJlIG1pc3RhZ2dlZCAiTm8gQXJjaGl2ZSBXYXJuaW5ncyBBcHBseSIuDQoNCmBgYHtyIHdhcm5pbmdCYXJzLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTZ9DQoNCm11bHRpcGxlV2FybmluZ1N1bW1hcnkgPC0gZGF0YS5mcmFtZShXYXJuaW5nID0gYygiTm8gQXJjaGl2ZSBXYXJuaW5ncyBBcHBseSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHcmFwaGljIERlcGljdGlvbnMgT2YgVmlvbGVuY2UiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWFqb3IgQ2hhcmFjdGVyIERlYXRoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJhcGUvTm9uLUNvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJVbmRlcmFnZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDcmVhdG9yIENob3NlIE5vdCBUbyBVc2UgQXJjaGl2ZSBXYXJuaW5ncyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTnVtYmVyLm9mLldvcmtzID0gYyhzdW0oZ3JlcGwoIk5vIEFyY2hpdmUgV2FybmluZ3MgQXBwbHkiLHdhcm5pbmdzKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bShncmVwbCgiR3JhcGhpYyBEZXBpY3Rpb25zIE9mIFZpb2xlbmNlIix3YXJuaW5ncykpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW0oZ3JlcGwoIk1ham9yIENoYXJhY3RlciBEZWF0aCIsd2FybmluZ3MpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtKGdyZXBsKCJSYXBlL05vbi1Db24iLHdhcm5pbmdzKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bShncmVwbCgiVW5kZXJhZ2UiLHdhcm5pbmdzKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bShncmVwbCgiQ3JlYXRvciBDaG9zZSBOb3QgVG8gVXNlIEFyY2hpdmUgV2FybmluZ3MiLHdhcm5pbmdzKSkpICkNCg0KbXVsdGlwbGVXYXJuaW5nU3VtbWFyeSRXYXJuaW5nIDwtIGZhY3RvcihtdWx0aXBsZVdhcm5pbmdTdW1tYXJ5JFdhcm5pbmcsIGxldmVscyA9IGMoIk5vIEFyY2hpdmUgV2FybmluZ3MgQXBwbHkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkdyYXBoaWMgRGVwaWN0aW9ucyBPZiBWaW9sZW5jZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWFqb3IgQ2hhcmFjdGVyIERlYXRoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSYXBlL05vbi1Db24iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlVuZGVyYWdlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDcmVhdG9yIENob3NlIE5vdCBUbyBVc2UgQXJjaGl2ZSBXYXJuaW5ncyIpKQ0KDQpwbG90V2FybmluZ3MgPC0gcGxvdF9jb2wobXVsdGlwbGVXYXJuaW5nU3VtbWFyeSwgJ1dhcm5pbmcnLCAnTnVtYmVyLm9mLldvcmtzJywgJ3JpZ2h0JykNCnBsb3RXYXJuaW5ncw0KDQpybShtdWx0aXBsZVdhcm5pbmdTdW1tYXJ5LCBwbG90V2FybmluZ3MpDQoNCmBgYA0KDQojIE11bHRpcGxlIEZhbmRvbXMNCg0KYGBge3IgbXVsdGlwbGVGYW5kb21zLCBtZXNzYWdlID0gRkFMU0V9DQoNCm11bHRpRmFuZG9tcyA8LSBmYW5kb21bY2F0ZWdvcnkgPT0gIk11bHRpIl0NCg0KbXVsdGlGYW5kb21zQWxsIDwtIGZhbmRvbVtncmVwbCgnTXVsdGknLCBjYXRlZ29yeSldDQoNCnNldmVyYWxGYW5kb21zIDwtIGZhbmRvbVt1bmxpc3QobGFwcGx5KGZhbmRvbSwgbGVuZ3RoKSkgPiAxXQ0KDQpjcm9zc292ZXJzIDwtIGZhbmRvbVtncmVwKCdjcm9zc292ZXInLGZyZWVmb3JtLCBpZ25vcmUuY2FzZT1UUlVFKV0NCg0KYGBgDQoNClRvdGFsIG51bWJlciBvZiB3b3JrcyB0YWdnZWQgb25seSBhcyAnTXVsdGknIGlzIGByIGxlbmd0aChtdWx0aUZhbmRvbXMpYCwgYnV0IG9ubHkgYHIgc3VtKGxhcHBseShtdWx0aUZhbmRvbXMsIGxlbmd0aCkgPiAxKWAgYXJlIHRhZ2dlZCB3aXRoIG1vcmUgdGhhbiBvbmUgZmFuZG9tLiBBbW9uZyB0aGVzZSwgbWVkaWFuIG51bWJlciBvZiBmYW5kb21zIHRhZ2dlZCBpcyBgciBzdW1tYXJ5KCB1bmxpc3QobGFwcGx5KG11bHRpRmFuZG9tc1tsYXBwbHkobXVsdGlGYW5kb21zLCBsZW5ndGgpID4gMV0sIGxlbmd0aCkpIClbM11gLCBhbmQgYXQgbGVhc3Qgb25lIHdvcmsgaXMgdGFnZ2VkIHdpdGggYHIgc3VtbWFyeSggdW5saXN0KGxhcHBseShtdWx0aUZhbmRvbXNbbGFwcGx5KG11bHRpRmFuZG9tcywgbGVuZ3RoKSA+IDFdLCBsZW5ndGgpKSApWzZdYC4NCg0KTnVtYmVyIG9mIHdvcmtzIHRhZ2dlZCB3aXRoICdNdWx0aScgYW5kL29yIG90aGVyIGNhdGVnb3JpZXMgaXMgYHIgbGVuZ3RoKG11bHRpRmFuZG9tc0FsbClgLCBidXQgb25seSBgciBzdW0obGFwcGx5KG11bHRpRmFuZG9tc0FsbCwgbGVuZ3RoKSA+IDEpYCBhcmUgdGFnZ2VkIHdpdGggbW9yZSB0aGFuIG9uZSBmYW5kb20uIEFtb25nIHRoZXNlLCBtZWRpYW4gbnVtYmVyIG9mIGZhbmRvbXMgdGFnZ2VkIGlzIGByIHN1bW1hcnkoIHVubGlzdChsYXBwbHkobXVsdGlGYW5kb21zQWxsW2xhcHBseShtdWx0aUZhbmRvbXNBbGwsIGxlbmd0aCkgPiAxXSwgbGVuZ3RoKSkgKVszXWAsIGFuZCBhdCBsZWFzdCBvbmUgd29yayBpcyB0YWdnZWQgd2l0aCBgciBzdW1tYXJ5KCB1bmxpc3QobGFwcGx5KG11bHRpRmFuZG9tc0FsbFtsYXBwbHkobXVsdGlGYW5kb21zQWxsLCBsZW5ndGgpID4gMV0sIGxlbmd0aCkpIClbNl1gLg0KDQpOdW1iZXIgb2Ygd29ya3MgdGFnZ2VkIHdpdGggbW9yZSB0aGFuIDEgZmFuZG9tIGlzIGByIGxlbmd0aChzZXZlcmFsRmFuZG9tcylgLCBob3dldmVyIGluIHNvbWUgY2FzZXMgZmFuZG9tIHRhZ3MgdXNlZCBieSB0aGUgYXV0aG9yIGFyZSBzeW5vbnltb3VzIHdpdGggYHIgdGFnVmFsdWVgLCBmb3IgZXhhbXBsZSAiVGhlIERyYWdvbiBQcmluY2UiLCAicmF5bGx1bSAtIEZhbmRvbSIsICJyYXlsYSB4IGNhbGx1bSIsICJURFAgLSBGYW5kb20iLCAiY2FsbHVtIHggcmF5bGEiLg0KDQpOdW1iZXIgb2Ygd29ya3MgZXhwbGljaXRseSB0YWdnZWQgYXMgJ2Nyb3Nzb3ZlcicgaXMgbG93ZXI6IGByIGxlbmd0aChjcm9zc292ZXJzKWAuIE91dCBvZiBtdWx0aXBsZSBmYW5kb20gdGFnIHdvcmtzIGEgc2lnbmlmaWNhbnQgYW1vdW50IGFyZSB0YWdnZWQgd2l0aCAyOiBgciBsZW5ndGgoZmFuZG9tW3VubGlzdChsYXBwbHkoZmFuZG9tLCBsZW5ndGgpKSA9PSAyXSlgLCB3aGljaCwgdXBvbiBpbnNwZWN0aW9uLCBkb24ndCBzZWVtIG92ZXJ3aGVsbWluZ2x5IHN5bm9ueW1vdXMsIHNvIHBlcmhhcHMgc29tZSBhdXRob3JzIHNpbXBseSBkb24ndCB0YWcgZm9yIGNyb3Nzb3ZlcnMuDQoNCiMgQXV0aG9ycyBieSBXb3Jrcw0KDQpUb3AgMzAgb2YgbW9zdCBwcm9saWZpYyBhdXRob3JzIGluIHRoZSB0YWcgYnkgdGhlIG51bWJlciBvZiBzdG9yaWVzIGFzIG9mIGRhdGEgY29sbGVjdGlvbiBkYXRlOg0KDQpgYGB7ciBhdXRob3JzV29ya3MsIG1lc3NhZ2UgPSBGQUxTRX0NCnRvcExpc3QgPC0gMzANCg0KQXV0aG9yVGFibGUgPC0gZGF0YS5mcmFtZSgnQXV0aG9yJyA9IG5hbWVzKHN1bW1hcnkoYXMuZmFjdG9yKHVubGlzdChhdXRob3IpKSlbMTp0b3BMaXN0XSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICdOdW1iZXIgb2YgU3RvcmllcycgPSBzdW1tYXJ5KGFzLmZhY3Rvcih1bmxpc3QoYXV0aG9yKSkpWzE6dG9wTGlzdF0pDQpyb3cubmFtZXMoQXV0aG9yVGFibGUpIDwtIGMoKQ0KDQprYWJsZShBdXRob3JUYWJsZSwNCiAgICAgIGNvbC5uYW1lcyA9IGMoJ0F1dGhvcicsICdOdW1iZXIgb2YgU3RvcmllcycpKQ0KDQpybShBdXRob3JUYWJsZSkNCmBgYA0KDQojIEF1dGhvcnMgYnkgV29yZHMNCg0KT25seSBgciBzdW0odW5saXN0KGxhcHBseShhdXRob3IsIGxlbmd0aCkpPjEpYCB3b3JrcyBoYXZlIG1vcmUgdGhhbiBvbmUgYXV0aG9yLiBJbiBjYXNlcyB3aGVyZSB3b3JrcyBoYWQgbW9yZSB0aGFuIG9uZSBhdXRob3IsIEkgYXNzdW1lZCB0aGF0IGVhY2ggb2YgdGhlbSBjb250cmlidXRlZCBhbiBlcXVhbCBhbW91bnRzIG9mIHdvcmRzLg0KDQpUb3AgMzAgb2YgbW9zdCBwcm9saWZpYyBhdXRob3JzIGluIHRoZSB0YWcgYnkgdGhlIG51bWJlciBvZiB3b3JkcyB3cml0dGVuIGFzIG9mIGRhdGEgY29sbGVjdGlvbiBkYXRlOg0KDQpgYGB7ciBhdXRob3JzV29yZHMsIG1lc3NhZ2UgPSBGQUxTRX0NCg0Kd29yZHNCeUF1dGhvciA8LSBjKCkNCg0KZm9yIChpIGluIDE6bGVuZ3RoKHdvcmRzKSl7DQogIGlmIChsZW5ndGgoYXV0aG9yW1tpXV0pID4gMSkgew0KICAgIHdvcmRzQnlBdXRob3IgPC0gYyh3b3Jkc0J5QXV0aG9yLCByZXAod29yZHNbW2ldXS9sZW5ndGgoYXV0aG9yW1s1XV0pLCBsZW5ndGgoYXV0aG9yW1tpXV0pICkgKQ0KICB9IGVsc2Ugew0KICAgIHdvcmRzQnlBdXRob3IgPC0gYyh3b3Jkc0J5QXV0aG9yLCB3b3Jkc1tbaV1dKQ0KICB9DQp9DQoNCkF1dGhvcldvcmRzVGFibGUgPC0gZGF0YS5mcmFtZSgnQXV0aG9yJyA9IGFzLmZhY3Rvcih1bmxpc3QoYXV0aG9yKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1dvcmRzJyA9IHdvcmRzQnlBdXRob3IpDQoNCkF1dGhvcldvcmRzU3VtbWFyeSA8LSBkZHBseShBdXRob3JXb3Jkc1RhYmxlLCAuKEF1dGhvciksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcml6ZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgVG90YWwuV29yZHMgPSBzdW0oV29yZHMpKQ0KQXV0aG9yV29yZHNTdW1tYXJ5IDwtIEF1dGhvcldvcmRzU3VtbWFyeVtvcmRlcihBdXRob3JXb3Jkc1N1bW1hcnkkVG90YWwuV29yZHMsIGRlY3JlYXNpbmcgPSBUUlVFKSxdDQpyb3cubmFtZXMoQXV0aG9yV29yZHNTdW1tYXJ5KSA8LSBjKCkNCg0KdG9wTGlzdCA8LSAzMA0KDQprYWJsZShBdXRob3JXb3Jkc1N1bW1hcnlbMTp0b3BMaXN0LF0sDQogICAgICBjb2wubmFtZXMgPSBjKCdBdXRob3InLCAnVG90YWwgV29yZHMnKSkNCg0Kcm0od29yZHNCeUF1dGhvciwgaSwgQXV0aG9yV29yZHNUYWJsZSwgQXV0aG9yV29yZHNTdW1tYXJ5KQ0KYGBgDQoNCiMgQ2hhcmFjdGVycw0KDQpUb3AgMzAgb2YgdGhlIG1vc3QgcG9wdWxhciBjaGFyYWN0ZXJzOg0KDQpgYGB7ciBjaGFyYWN0ZXJzLCBtZXNzYWdlID0gRkFMU0V9DQp0b3BMaXN0IDwtIDMwDQpDaGFyYWN0ZXJUYWJsZTwtIGRhdGEuZnJhbWUoJ0NoYXJhY3RlcicgPSBuYW1lcyhzdW1tYXJ5KGFzLmZhY3Rvcih1bmxpc3QoY2hhcmFjdGVyKSkpWzE6dG9wTGlzdF0pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAnTnVtYmVyIG9mIFN0b3JpZXMnID0gc3VtbWFyeShhcy5mYWN0b3IodW5saXN0KGNoYXJhY3RlcikpKVsxOnRvcExpc3RdKQ0Kcm93Lm5hbWVzKENoYXJhY3RlclRhYmxlKSA8LSBjKCkNCg0Ka2FibGUoQ2hhcmFjdGVyVGFibGUsDQogICAgICBjb2wubmFtZXMgPSBjKCdDaGFyYWN0ZXInLCAnTnVtYmVyIG9mIFN0b3JpZXMnKSkNCg0Kcm0oQ2hhcmFjdGVyVGFibGUpDQpgYGANCg0KIyBSZWxhdGlvbnNoaXBzDQoNClRvcCAzMCBvZiB0aGUgbW9zdCBwb3B1bGFyIHJlbGF0aW9uc2hpcHM6DQoNCkkgZG9uJ3QgaGF2ZSBhY2Nlc3MgdG8gQW8zJ3Mgc3lzdGVtIG9mIHN5bm9ueW1vdXMgdGFncywgc28gYnkgdmlydHVlIG9mIHRleHQgcHJvY2Vzc2luZyBzb21lIHJlbGF0aW9uc2hpcCB0YWdzIGhlcmUgYXJlIHJlcGVhdGVkLiANCg0KT3ZlcndoZWxtaW5nbHksICJDYWxsdW0vUmF5bGEgKFRoZSBEcmFnb24gUHJpbmNlKSIgaXMgdGhlIG1vc3QgcG9wdWxhciByZWxhdGlvbnNoaXAgaW4gVERQLiBUaGV5IGFyZSBmb2xsb3dlZCBieSAiRXRoYXJpL1J1bmFhbiAoVGhlIERyYWdvbiBQcmluY2UpIiwgd2hpY2ggaXMgbm90IGltbWVkaWF0ZWx5IG9idmlvdXMgZHVlIHRvIGNvbW1vbiB1c2Ugb2Ygc3lub255bW91cyB0YWdzIHN1Y2ggYXMgIlJ1bmFhbi9UaW5rZXIgfCBOZWNrbGFjZSBFbGYgKFRoZSBEcmFnb24gUHJpbmNlKSIgYW5kICJSdW5hYW4vRXRoYXJpIi4gVGhpcmQgbW9zdCBwb3B1bGFyIHJlbGF0aW9uc2hpcCBpcyAiQW1heWEvSmFuYWkgKFRoZSBEcmFnb24gUHJpbmNlKSIuDQoNCmBgYHtyIHJlbGF0aW9uc2hpcHMsIG1lc3NhZ2UgPSBGQUxTRX0NCnRvcExpc3QgPC0gMzANClJlbGF0aW9uc2hpcHNUYWJsZTwtIGRhdGEuZnJhbWUoJ1JlbGF0aW9uc2hpcCcgPSBuYW1lcyhzdW1tYXJ5KGFzLmZhY3Rvcih1bmxpc3QocmVsYXRpb25zaGlwcykpKVsxOnRvcExpc3RdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgJ051bWJlciBvZiBTdG9yaWVzJyA9IHN1bW1hcnkoYXMuZmFjdG9yKHVubGlzdChyZWxhdGlvbnNoaXBzKSkpWzE6dG9wTGlzdF0pDQpyb3cubmFtZXMoUmVsYXRpb25zaGlwc1RhYmxlKSA8LSBjKCkNCg0Ka2FibGUoUmVsYXRpb25zaGlwc1RhYmxlLA0KICAgICAgY29sLm5hbWVzID0gYygnUmVsYXRpb25zaGlwJywgJ051bWJlciBvZiBTdG9yaWVzJykpDQoNCnJtKFJlbGF0aW9uc2hpcHNUYWJsZSkNCmBgYA0KDQojIEZyZWVmb3JtIHRhZ3MNCg0KVG9wIDMwIG9mIHRoZSBtb3N0IHBvcHVsYXIgZnJlZWZvcm0gdGFncw0KDQpgYGB7ciBmcmVlZm9ybSwgbWVzc2FnZSA9IEZBTFNFfQ0KdG9wTGlzdCA8LSAzMA0KRnJlZWZvcm1UYWJsZTwtIGRhdGEuZnJhbWUoJ0ZyZWVmb3JtJyA9IG5hbWVzKHN1bW1hcnkoYXMuZmFjdG9yKHVubGlzdChmcmVlZm9ybSkpKVsxOnRvcExpc3RdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgJ051bWJlciBvZiBTdG9yaWVzJyA9IHN1bW1hcnkoYXMuZmFjdG9yKHVubGlzdChmcmVlZm9ybSkpKVsxOnRvcExpc3RdKQ0Kcm93Lm5hbWVzKEZyZWVmb3JtVGFibGUpIDwtIGMoKQ0KDQprYWJsZShGcmVlZm9ybVRhYmxlLA0KICAgICAgY29sLm5hbWVzID0gYygnRnJlZWZvcm0gVGFnJywgJ051bWJlciBvZiBTdG9yaWVzJykpDQoNCnJtKEZyZWVmb3JtVGFibGUpDQpgYGANCg0KIyBMYW5ndWFnZXMNCg0KVW5zdXJwcmlzaW5nbHksIG1vc3Qgd29ya3MgYXJlIHdyaXR0ZW4gaW4gRW5nbGlzaC4gQXBvbG9naWVzIGZvciBVKy4ga2FibGUgcGFja2FnZSBmb3Igd2hhdGV2ZXIgcmVhc29uIG11cmRlcnMgdW5pY29kZSBjaGFyYWN0ZXJzLiBUaGUgdHdvIGxhbmd1YWdlcyBpbiBxdWVzdGlvbiBhcmUgUnVzc2lhbiAo0KDRg9GB0YHQutC40LkpIGFuZCBDaGluZXNlICjkuK3mlocpLg0KDQpgYGB7ciBsYW5ndWFnZXMsIG1lc3NhZ2UgPSBGQUxTRX0NCiN0b3BMaXN0IDwtIDMwDQoNCmxhbmd1YWdlc0xpc3QgPC0gc3VtbWFyeShhcy5mYWN0b3IodW5saXN0KGxhbmd1YWdlKSkpDQoNCkxhbmd1YWdlVGFibGUgPC0gZGF0YS5mcmFtZSgnTGFuZ3VhZ2UnID0gbmFtZXMobGFuZ3VhZ2VzTGlzdCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ051bWJlciBvZiBTdG9yaWVzJyA9IGxhbmd1YWdlc0xpc3QgKQ0KTGFuZ3VhZ2VUYWJsZSA8LSBMYW5ndWFnZVRhYmxlW29yZGVyKExhbmd1YWdlVGFibGUkTnVtYmVyLm9mLlN0b3JpZXMsIGRlY3JlYXNpbmc9VFJVRSksXQ0Kcm93Lm5hbWVzKExhbmd1YWdlVGFibGUpIDwtIGMoKQ0KDQprYWJsZShMYW5ndWFnZVRhYmxlLA0KICAgICAgY29sLm5hbWVzID0gYygnTGFuZ3VhZ2UnLCAnTnVtYmVyIG9mIFN0b3JpZXMnKSkNCg0KI2xhbmd1YWdlc0xpc3QNCg0KI3JtKExhbmd1YWdlVGFibGUpDQpgYGANCg0KIyBPdGhlciBsaW5rcw0KDQpBbzMgZGF0YSBhbmFseXNpcyBmb3IgVGhlIERyYWdvbiBQcmluY2UgKENhcnRvb24pDQoNCltBbzMgZGF0YSBhbmFseXNpcyBmb3IgQXZhdGFyOiBMZWdlbmQgb2YgS29ycmFdKGh0dHBzOi8vZGFydGhhbGluZS5naXRodWIuaW8vQW8zU2VhcmNoQW5hbHlzaXMvZmFuZG9tcy9MT0svTE9LX3Byb2Nlc3Npbmdfbm90ZWJvb2submIuaHRtbCkNCg0KW0FvMyBkYXRhIGFuYWx5c2lzIGZvciBBdmF0YXI6IFRoZSBMYXN0IEFpcmJlbmRlcl0oaHR0cHM6Ly9kYXJ0aGFsaW5lLmdpdGh1Yi5pby9BbzNTZWFyY2hBbmFseXNpcy9mYW5kb21zL0FUTEEvQVRMQV9wcm9jZXNzaW5nX25vdGVib29rLm5iLmh0bWwpDQoNCltBbzMgZGF0YSBhbmFseXNpcyBmb3IgQmxhY2sgU2FpbHNdKGh0dHBzOi8vZGFydGhhbGluZS5naXRodWIuaW8vQW8zU2VhcmNoQW5hbHlzaXMvZmFuZG9tcy9CU2FpbHMvQlNhaWxzX3Byb2Nlc3Npbmdfbm90ZWJvb2submIuaHRtbCkNCg0KSWYgeW91IGVuam95ZWQgbXkgYW5hbHlzaXMsIHBsZWFzZSwgY29uc2lkZXIgW2J1eWluZyBtZSBhIGNvZmZlZV0oaHR0cHM6Ly9rby1maS5jb20vRDFEOFJJRzUpIG9yIHNvbWUgb3RoZXIgYmV2ZXJhZ2Uu