Cleans and combines a large number of gazetteers of place names for looking up locations by name and retrieving their coordinates.

rm(list=ls()); gc()
# Hiding output and warnings
# !diagnostics off
library(MeasuringLandscape)
library(tidyverse) #load independently just to make sure %>% gets imported
#devtools::load_all()
dir_figures <- glue::glue(getwd(), "/../paper/figures/")
dir_package_files <- glue::glue(getwd(), "/inst/extdata/")
gc()
knitr::opts_knit$set(progress = TRUE, verbose = TRUE)
knitr::opts_chunk$set(fig.width=8, warning=FALSE, message=FALSE, cache=TRUE)
options(width = 160)

Load Gazetteer Files

  1. NGA - National Geospatial Agency
  2. Geonames
  3. Historical gazetteer from the time period
  4. GoogleMaps
  5. BingMaps
  6. KEN_adm
  7. Livestock - International Livestock Research Institute map
  8. Kenya Districts 1962 - Polygons derived from 1962 district map
  9. Kenya Cadastral District - district polygons derived from the cadastral map
  10. Kenya Cadastral - A contemporary cadastral map
  11. Wikidata
  12. TGN - Getty Thesaurus of Geographic Names
  13. OpenStreetMap
fromscratch <- F
# Bounding box of ROI
long_min <- 35.67
long_max <- 38.19
lat_min <- -1.43285
lat_max <- 0.54543
# Bounding Box Spatial Object
region_of_interest_sf_utm <- MeasuringLandscape:::create_roi(
  bottom_left_x = long_min,
  bottom_left_y = lat_min,
  top_right_x = long_max,
  top_right_y = lat_max
)
#Event locations
events_sf <- readRDS(system.file("extdata", "events_sf.Rdata", package = "MeasuringLandscape")) 
events_sf_roi <- events_sf %>% 
                 MeasuringLandscape:::subset_roi(region_of_interest_sf_utm) %>% 
                 dplyr::select("name_cleaner","map_coordinate_clean_latitude","map_coordinate_clean_longitude","geometry") %>%
                 stats::setNames(c("name","latitude","longitude","geometry")) %>% 
                 dplyr::mutate(feature_code="event",
                        timeperiod="1952-01-01",
                        source_dataset="events") %>% 
                 dplyr::filter(!is.na(name) & name !="" & !is.na(latitude) & !is.na(longitude)) %>%
                 dplyr::distinct()
[1] 10469   104
although coordinates are longitude/latitude, st_intersects assumes that they are planar
# Moderate Size Point Sources
nga_sf_roi <- MeasuringLandscape:::load_nga(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
geonames_sf_roi <- MeasuringLandscape:::load_geonames(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
historical_sf_roi <- MeasuringLandscape:::load_historical(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
# Small API Sources
googlemaps_sf_roi <- MeasuringLandscape:::load_googlemaps(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
bingmaps_sf_roi <- MeasuringLandscape:::load_bingmaps(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
# Moderate Size mixed or polygon sources
KEN_adm_sf_roi <- MeasuringLandscape:::load_ken_adm(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
livestock_sf_roi <- MeasuringLandscape:::load_livestock(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
kenya_districts1962_sf_roi <- MeasuringLandscape:::load_kenya_districts1962(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
kenya_cadastral_district_sf_roi <- MeasuringLandscape:::load_kenya_cadastral_district(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
kenya_cadastral_sf_roi <- MeasuringLandscape:::load_kenya_cadastral(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
# Very Large Sources
wikidata_sf_roi <- MeasuringLandscape:::load_wikidata(long_min, long_max, lat_min, lat_max, fromscratch = fromscratch)
tgn_sf_roi <- MeasuringLandscape:::load_tgn(long_min, long_max, lat_min, lat_max, fromscratch = fromscratch)
openstreetmap_sf_roi <- MeasuringLandscape:::load_openstreetmap(roi = region_of_interest_sf_utm, fromscratch = fromscratch)
if (fromscratch) {
  # combinedlist <- lapply(combinedlist, FUN=function(x) x %>% mutate(hash=apply(x,1 ,digest))) #add an id
  # This takes way too long but is the only easy way I've found to merge sf frames
  # For speed and debugging purposes splitting this up
  # For some reason this works when you split it up but not when you put them together
  # Double check that they're all sf data.frames, if one of them is a data.table it'll get rbindlist involved which will breaking when rbinding point and multipoint geometries
  # points
  flatfiles1 <- list( 
    events_sf_roi %>% distinct(),
    # ellipse_sf_roi %>% distinct(),
    tgn_sf_roi %>% distinct(),
    historical_sf_roi %>% distinct(),
    geonames_sf_roi %>% distinct(),
    nga_sf_roi %>% distinct()
  ) %>% reduce(rbind_sf)
  table(flatfiles1$source_dataset)
  flatfiles1b <- list(
    googlemaps_sf_roi %>% distinct(),
    bingmaps_sf_roi %>% distinct(),
    wikidata_sf_roi %>% distinct()
  ) %>% reduce(rbind_sf)
  table(flatfiles1$source_dataset)
  # Polygons
  flatfiles2 <- list( # ellipse_sf %>% distinct(),
    KEN_adm_sf_roi %>% distinct(),
    openstreetmap_sf_roi %>% distinct(),
    livestock_sf_roi %>% distinct(),
    kenya_cadastral_sf_roi %>% distinct(),
    kenya_cadastral_district_sf_roi %>% distinct(),
    kenya_districts1962_sf_roi %>% distinct()
  ) %>% reduce(rbind_sf)
  table(flatfiles2$source_dataset)
  flatfiles <- list(
    flatfiles1 %>% distinct(),
    flatfiles1b %>% distinct(),
    flatfiles2 %>% distinct()
  ) %>% reduce(rbind_sf)
  flatfiles <- flatfiles %>% distinct() %>% st_cast()
  flatfiles$valid <- st_is_valid(flatfiles)
  flatfiles <- st_make_valid(flatfiles) # ok now we're going to fix the broken ones.
  # temp <- unique(flatfiles_sf_roi$name_cleaner_nospace)
  # flatfiles <- as.data.table(flatfiles)
  ## flatfiles[,livestock_kill:=F,]
  # flatfiles[source_dataset=="livestock" & name_cleaner %in% temp,livestock_kill:=T,] #Livestock doesn't have spaces unforutnately
  # flatfiles <- flatfiles[livestock_kill==F,]
  #library(stringr)
  flatfiles_sf_roi <- flatfiles %>%
    # mutate_all(funs(stri_enc_toascii)) %>% #rbindlist tries to convert to factors
    # mutate(name = strsplit(names, ";| see | SEE | check if same as ")) %>%
    # tidyr::unnest(name) %>% # This trick doesn't work when there's multiple list columns, which geometry is
    mutate(name = str_trim((name))) %>%
    mutate(name = gsub(",$", "", name)) %>% # remove trailing comma
    mutate(name = gsub("\032", "", name)) %>% # remove end of line character
    mutate(name = gsub("_", " ", name)) %>% # convert underscores to spaces
    mutate(name = gsub(" -|- | - ", "-", name)) %>% # convert dashes with weird spacing to just a dash
    filter(!is.na(name) & name != "NA" & name != "") %>% # Remove anything that might be a missing name
    # filter(!is.na(asciiname_either_clean) & asciiname_either_clean!="" & nchar(asciiname_either_clean)>2) %>%
    distinct()
  flatfiles_sf_roi$names <- NULL
  table(flatfiles_sf_roi$source_dataset)
  table(flatfiles_sf_roi$timeperiod)
  # Fix geospatial stuff
  # library(feather)
  # condition <- st_is_valid(flatfiles_sf_roi$geometry) ; table(condition) # takes a long while
  # flatfiles_sf_roi$geometry[!condition] <- st_make_valid(flatfiles_sf_roi$geometry[!condition])
  flatfiles_sf_roi$geometry_dimensions <- sapply(flatfiles_sf_roi$geometry, FUN = function(x) st_dimension(x)) # takes a while
  table(flatfiles_sf_roi$geometry_dimensions, useNA = "always")
  condition <- is.na(flatfiles_sf_roi$geometry_dimensions)
  table(flatfiles_sf_roi$source_dataset, flatfiles_sf_roi$geometry_dimensions, useNA = "always")
  flatfiles_sf_roi$geometry[condition] <- st_point() # Replace these empty polygons the make valid put in with empty points
  condition <- is.na(flatfiles_sf_roi$geometry_dimensions)
  table(flatfiles_sf_roi$source_dataset, flatfiles_sf_roi$geometry_dimensions, useNA = "always")
  flatfiles_sf_roi$geometry_type <- sapply(flatfiles_sf_roi$geometry, FUN = function(x) class(x)[2])
  table(flatfiles_sf_roi$source_dataset, flatfiles_sf_roi$geometry_type, useNA = "always")
  condition <- flatfiles_sf_roi$geometry_type %in% c("POLYGON", "MULTIPOLYGON")
  flatfiles_sf_roi$geometry_area <- NA
  flatfiles_sf_roi$geometry_area[condition] <- st_area(flatfiles_sf_roi$geometry[condition]) # ok is failing for empty polygons
  condition <- flatfiles_sf_roi$geometry_type %in% c("LINESTRING")
  flatfiles_sf_roi$geometry_length <- NA
  flatfiles_sf_roi$geometry_length[condition] <- st_length(flatfiles_sf_roi$geometry[condition])
  # By definition, all ROI matches are intersections with the ROI
  # flatfiles_sf_roi <- flatfiles_sf_roi %>% mutate(region_of_interest_overlap =
  #                                          as.vector(st_overlaps(  geometry, region_of_interest_sf_utm,   sparse=F) )  ) #Flag the Region of Interest
  # flatfiles_sf_roi <- flatfiles_sf_roi %>% mutate(region_of_interest_within =
  #                                          as.vector(st_within(    geometry, region_of_interest_sf_utm,   sparse=F) )  ) #Flag the Region of Interest
  # flatfiles_sf_roi <- flatfiles_sf_roi %>% mutate(region_of_interest_intersects =
  #                                          as.vector(st_intersects(geometry, region_of_interest_sf_utm,   sparse=F) )   ) #Flag the Region of Interest
  # table(flatfiles_sf_roi$region_of_interest_overlap) #I guess just for polygons
  # table(flatfiles_sf_roi$region_of_interest_within) #requires polys to be entirely inside
  # table(flatfiles_sf_roi$region_of_interest_intersects) #just requires polys to overlap a little bit
  # #Time to fix livestock names
  # temp <- flatfiles_sf_roi %>% filter(!source_dataset %in% c('livestock_boundaries',  'livestock_points')) %>% as.data.frame() %>%
  #   select(c("name_cleaner","name_cleaner_nospace")) %>%
  #         distinct() %>%
  #         filter(!duplicated(name_cleaner_nospace)) #for some reason multiple names are maping to the same name without spaces
  # rownames(temp) <- temp$name_cleaner_nospace
  #
  # flatfiles_sf_roi$name_cleaner_spaced <- temp[flatfiles_sf_roi$name_cleaner,"name_cleaner"] #
  # condition <- flatfiles_sf_roi$source_dataset %in% c('livestock_boundaries',  'livestock_points') & !is.na(flatfiles_sf_roi$name_cleaner_spaced) ; table(condition)
  # flatfiles_sf_roi$name_cleaner[condition] <- flatfiles_sf_roi$name_cleaner_spaced[condition]
  flatfiles_sf_roi$eventsource <- flatfiles_sf_roi$source_dataset %in% "events"
  # Get lat longs back
  cords <- st_coordinates(flatfiles_sf_roi %>% filter(geometry_type %in% "POINT"))
  condition1 <- flatfiles_sf_roi$geometry_type %in% "POINT" & is.na(flatfiles_sf_roi$longitude)
  condition2 <- is.na(flatfiles_sf_roi$longitude)[flatfiles_sf_roi$geometry_type %in% "POINT"]
  flatfiles_sf_roi$longitude[condition1] <- cords[condition2, 1]
  flatfiles_sf_roi$latitude[condition1] <- cords[condition2, 2]
  flatfiles_sf_roi <- flatfiles_sf_roi %>%
    # This distinct is very important, it correctly removes duplicates
    distinct(feature_code, latitude, longitude, name, source_dataset,
             geometry_type, region_of_interest_intersects, name_alternates, .keep_all = T) %>%
    mutate(name_clean = str_trim(tolower(name))) %>%
    mutate(name_clean_posessive = grepl("'s|`s", name_clean)) %>%
    mutate(name_cleaner = trimws(name_clean)) %>%
    mutate(name_cleaner = gsub("'s|`s", "", name_cleaner, fixed = T)) %>%
    mutate(name_cleaner = str_replace_all(name_cleaner, "[[:punct:]]|", "")) %>%
    mutate(name_cleaner = trimws(name_cleaner)) %>%
    mutate(name_cleaner_nospace = str_replace_all(name_cleaner, " ", ""))
  sort(unique(unlist(strsplit(flatfiles_sf_roi$name_clean, ""))))
  flatfiles_sf_roi$name_cleaner <- clean_noascii(flatfiles_sf_roi$name_cleaner)
  sort(unique(unlist(strsplit(flatfiles_sf_roi$name_cleaner, "")))) # only number and lowercase letters from now on
  # Geonames Code Descriptions
  # flatfiles_sf_roi$feature_code %>% janitor::tabyl( sort = TRUE)  #512 unique codes
  geonames_code_descriptions <- as.data.frame(read_csv(system.file("extdata", "geonames_code_descriptions.csv", package = "MeasuringLandscape"), col_names = F))
  names(geonames_code_descriptions) <- c("code", "code_txt", "description")
  rownames(geonames_code_descriptions) <- geonames_code_descriptions$code
  flatfiles_sf_roi$feature_code_txt <- tolower(geonames_code_descriptions[flatfiles_sf_roi$feature_code, "code_txt"])
  flatfiles_sf_roi$feature_code_txt[is.na(flatfiles_sf_roi$feature_code_txt)] <- tolower(flatfiles_sf_roi$feature_code[is.na(flatfiles_sf_roi$feature_code_txt)])
  table(flatfiles_sf_roi$feature_code[is.na(flatfiles_sf_roi$feature_code_txt)])
  flatfiles_sf_roi$feature_code_txt <- gsub("_", " ", flatfiles_sf_roi$feature_code_txt)
  flatfiles_sf_roi$feature_code_txt <- gsub("(-ies)", " ", flatfiles_sf_roi$feature_code_txt, fixed = T)
  flatfiles_sf_roi$feature_code_txt <- gsub("(s)", " ", flatfiles_sf_roi$feature_code_txt, fixed = T)
  flatfiles_sf_roi$feature_code_txt <- gsub("(es)", " ", flatfiles_sf_roi$feature_code_txt, fixed = T)
  flatfiles_sf_roi$feature_code_txt <- trimws(flatfiles_sf_roi$feature_code_txt)
  flatfiles_sf_roi$place_hash <- apply(flatfiles_sf_roi, 1, digest, algo = "xxhash64") # create a hash id to reference
  rownames(flatfiles_sf_roi) <- flatfiles_sf_roi$id_hash # actually getting collisions
  saveRDS(
    flatfiles_sf_roi,
    file = glue::glue(getwd(), "/../inst/extdata/flatfiles_sf_roi.Rdata") 
  )
  # st_write(obj=flatfiles_sf_roi,
  #   "/home/rexdouglass/Dropbox (rex)/Kenya Article Drafts/MeasuringLandscapeCivilWar/inst/extdata/flatfiles_sf_roi.gpkg",
  #   delete_layer = TRUE)
}
flatfiles_sf_roi <- readRDS(system.file("extdata", "flatfiles_sf_roi.Rdata", package = "MeasuringLandscape"))

Table comparing the gazetteers

flatfiles_sf_roi %>% janitor::tabyl(source_dataset) %>% janitor::adorn_crosstab(., denom = "col", show_n = T, digits = 1, show_totals = T)
'janitor::adorn_crosstab' is deprecated.
Use 'use the various adorn_ functions instead.  See the "tabyl" vignette for examples.' instead.
See help("Deprecated")

Handle types

Different gazetteers record what type of location a name belongs to slightly differently. We’ve partially merged some categories to provide greater overlap.

flatfiles_sf_roi$feature_code_txt %>% janitor::tabyl(sort = TRUE) %>% janitor::adorn_crosstab(., denom = "all", rounding = "half up", show_n = T, digits = 1, show_totals = T) # 459 unique codes
'janitor::adorn_crosstab' is deprecated.
Use 'use the various adorn_ functions instead.  See the "tabyl" vignette for examples.' instead.
See help("Deprecated")

Visually Comparing Spatial Coverage of each Sources

Visual inspection reveals differences between the spatial coverage of each source. (Appendix Figure 1 and Appendix Figure 3)

# flatfiles %>% ggplot(aes(x=longitude,y=latitude, col=as.factor(source_dataset))) + geom_point(alpha = 0.1)
p_points <- flatfiles_sf_roi %>%
  filter(geometry_type %in% "POINT") %>%
  ggplot(aes(x = longitude, y = latitude, col = as.factor(source_dataset))) + geom_point(alpha = 0.1) + facet_wrap(~as.factor(source_dataset), drop = T) + guides(color = FALSE) + theme_bw() +
  xlim(35.67, 38.19) + ylim(-1.43285, 0.54543)
ggsave(
  filename = glue::glue(dir_figures, "flatfiles_sf_roi_facet_source_dataset_points.png"),
  plot = p_points, width = 12, height = 10
)
p_points

# flatfiles_sf_roi %>% filter(region_of_interest_intersects==T) %>% ggplot(aes(col=as.factor(source_dataset))) + geom_sf(alpha = 0.1)
#devtools::install_github("tidyverse/ggplot2") # geom_sf requires ggplot installed off of the dev server
p_notpoints <- flatfiles_sf_roi %>%
  filter(!geometry_type %in% "POINT") %>%
  ggplot(aes(color = as.factor(source_dataset))) + ### geom_sf requires ggplot installed off of the dev server
  geom_sf(alpha = 0.1, size = 1) +
  facet_wrap(~as.factor(source_dataset), drop = T) +
  # ggtitle("Locations in Region of Interest Stratified by Source") +
  guides(color = FALSE) +
  xlim(35.67, 38.19) + ylim(-1.43285, 0.54543) + theme_bw() 
ggsave(
  filename = glue::glue(dir_figures, "flatfiles_sf_roi_facet_source_dataset_notpoints.png"),
  plot = p_notpoints, width = 12, height = 6
)
p_notpoints

Visually Comparing Spatial Precision of each Sources

(Appendix Figure 2 on Longitude)

# Events and historical have the smoothest distributions, but that's misleading because conversion from degrees to decimal
# Wikidata has lots of truncated precision entries, at 0 degrees
# TGN has so few it's hard to tell
# Open streetmap has a disproportionate number of business in the capital which leads to a weird distribution
# NGA, geonames and historical look like a comb
# livestock looks like comb with teeth missing
# Events look fairly continuous with some spikes
# It looks like a lot of these that don't match have truncated precision
p_combs_lat <- flatfiles_sf_roi %>%
  filter(geometry_type %in% "POINT") %>%
  as.data.frame() %>%
  select(latitude, source_dataset) %>%
  ggplot(aes(x = latitude - round(latitude), col = as.factor(source_dataset))) + geom_histogram(bins = 1000) + facet_wrap(~source_dataset, scales = "free")
ggsave(
  filename = glue::glue(dir_figures, "p_combs_lat.pdf"),
  plot = p_combs_lat, width = 12, height = 6
)
p_combs_lat

p_combs_long <- flatfiles_sf_roi %>%
  filter(geometry_type %in% "POINT") %>%
  as.data.frame() %>%
  select(longitude, source_dataset) %>%
  ggplot(aes(x = longitude - round(longitude), col = as.factor(source_dataset))) + geom_histogram(bins = 1000) + facet_wrap(~source_dataset, scales = "free")
ggsave(
  filename = glue::glue(dir_figures, "p_combs_long.pdf"),
  plot = p_combs_long, width = 12, height = 6
)
p_combs_long

temp <- flatfiles_sf_roi %>%
  filter(geometry_type %in% "POINT") %>%
  as.data.frame() %>%
  select(longitude, source_dataset) %>%
  group_by(source_dataset) %>%
  mutate(longitude_trunc = longitude - round(longitude))
#temp %>% filter(source_dataset %in% "historical") %>% ungroup() %>% select("longitude_trunc") %>% as.vector() %>% table()
#temp %>% filter(source_dataset %in% "geonames") %>% ungroup() %>% select("longitude_trunc") %>% as.vector() %>% round(5) %>% table() %>% sort(decreasing = T) %>% as.data.frame() %>% filter(Freq > 10)
temp <- flatfiles_sf_roi %>%
  filter(geometry_type %in% "POINT") %>%
  as.data.frame() %>%
  select(longitude, source_dataset) %>%
  group_by(source_dataset) %>%
  arrange(longitude) %>%
  mutate(longitude_l1 = lag(longitude, 1)) %>%
  mutate(longitude_fd = round(longitude - longitude_l1, 4))
d <- as.matrix(table(temp$longitude_fd, temp$source_dataset)) # ok so the most common difference between points is 0.016. That's about 1.776 km 
LS0tCnRpdGxlOiAiMDIgUHJlcCBHYXpldHRlZXJzIgphdXRob3I6ICJSZXggVy4gRG91Z2xhc3MgYW5kIEtyaXN0ZW4gSGFya25lc3MiCmRhdGU6ICJNYXJjaCA5LCAyMDE4IgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KPHN0eWxlPgogICAgYm9keSAubWFpbi1jb250YWluZXIgewogICAgICAgIG1heC13aWR0aDogMTAwJTsKICAgIH0KPC9zdHlsZT4KCkNsZWFucyBhbmQgY29tYmluZXMgYSBsYXJnZSBudW1iZXIgb2YgZ2F6ZXR0ZWVycyBvZiBwbGFjZSBuYW1lcyBmb3IgbG9va2luZyB1cCBsb2NhdGlvbnMgYnkgbmFtZSBhbmQgcmV0cmlldmluZyB0aGVpciBjb29yZGluYXRlcy4KIApgYGB7ciwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJtKGxpc3Q9bHMoKSk7IGdjKCkKIyBIaWRpbmcgb3V0cHV0IGFuZCB3YXJuaW5ncwojICFkaWFnbm9zdGljcyBvZmYKbGlicmFyeShNZWFzdXJpbmdMYW5kc2NhcGUpCmxpYnJhcnkodGlkeXZlcnNlKSAjbG9hZCBpbmRlcGVuZGVudGx5IGp1c3QgdG8gbWFrZSBzdXJlICU+JSBnZXRzIGltcG9ydGVkCgojZGV2dG9vbHM6OmxvYWRfYWxsKCkKZGlyX2ZpZ3VyZXMgPC0gZ2x1ZTo6Z2x1ZShnZXR3ZCgpLCAiLy4uL3BhcGVyL2ZpZ3VyZXMvIikKZGlyX3BhY2thZ2VfZmlsZXMgPC0gZ2x1ZTo6Z2x1ZShnZXR3ZCgpLCAiL2luc3QvZXh0ZGF0YS8iKQoKCmdjKCkKCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHByb2dyZXNzID0gVFJVRSwgdmVyYm9zZSA9IFRSVUUpCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGg9OCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRSkKb3B0aW9ucyh3aWR0aCA9IDE2MCkKCmBgYAoKIyBMb2FkIEdhemV0dGVlciBGaWxlcwoKMSkgTkdBIC0gTmF0aW9uYWwgR2Vvc3BhdGlhbCBBZ2VuY3kKMikgR2VvbmFtZXMgCjMpIEhpc3RvcmljYWwgZ2F6ZXR0ZWVyIGZyb20gdGhlIHRpbWUgcGVyaW9kCjQpIEdvb2dsZU1hcHMKNSkgQmluZ01hcHMKNikgS0VOX2FkbQo3KSBMaXZlc3RvY2sgLSBJbnRlcm5hdGlvbmFsIExpdmVzdG9jayBSZXNlYXJjaCBJbnN0aXR1dGUgbWFwCjgpIEtlbnlhIERpc3RyaWN0cyAxOTYyIC0gUG9seWdvbnMgZGVyaXZlZCBmcm9tIDE5NjIgZGlzdHJpY3QgbWFwCjkpIEtlbnlhIENhZGFzdHJhbCBEaXN0cmljdCAtIGRpc3RyaWN0IHBvbHlnb25zIGRlcml2ZWQgZnJvbSB0aGUgY2FkYXN0cmFsIG1hcAoxMCkgS2VueWEgQ2FkYXN0cmFsIC0gQSBjb250ZW1wb3JhcnkgY2FkYXN0cmFsIG1hcAoxMSkgV2lraWRhdGEKMTIpIFRHTiAtICBHZXR0eSBUaGVzYXVydXMgb2YgR2VvZ3JhcGhpYyBOYW1lcwoxMykgT3BlblN0cmVldE1hcAoKCmBgYHtyfQpmcm9tc2NyYXRjaCA8LSBGCgoKIyBCb3VuZGluZyBib3ggb2YgUk9JCmxvbmdfbWluIDwtIDM1LjY3CmxvbmdfbWF4IDwtIDM4LjE5CmxhdF9taW4gPC0gLTEuNDMyODUKbGF0X21heCA8LSAwLjU0NTQzCgojIEJvdW5kaW5nIEJveCBTcGF0aWFsIE9iamVjdApyZWdpb25fb2ZfaW50ZXJlc3Rfc2ZfdXRtIDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OmNyZWF0ZV9yb2koCiAgYm90dG9tX2xlZnRfeCA9IGxvbmdfbWluLAogIGJvdHRvbV9sZWZ0X3kgPSBsYXRfbWluLAogIHRvcF9yaWdodF94ID0gbG9uZ19tYXgsCiAgdG9wX3JpZ2h0X3kgPSBsYXRfbWF4CikKCiNFdmVudCBsb2NhdGlvbnMKZXZlbnRzX3NmIDwtIHJlYWRSRFMoc3lzdGVtLmZpbGUoImV4dGRhdGEiLCAiZXZlbnRzX3NmLlJkYXRhIiwgcGFja2FnZSA9ICJNZWFzdXJpbmdMYW5kc2NhcGUiKSkgCgpldmVudHNfc2Zfcm9pIDwtIGV2ZW50c19zZiAlPiUgCiAgICAgICAgICAgICAgICAgTWVhc3VyaW5nTGFuZHNjYXBlOjo6c3Vic2V0X3JvaShyZWdpb25fb2ZfaW50ZXJlc3Rfc2ZfdXRtKSAlPiUgCiAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdCgibmFtZV9jbGVhbmVyIiwibWFwX2Nvb3JkaW5hdGVfY2xlYW5fbGF0aXR1ZGUiLCJtYXBfY29vcmRpbmF0ZV9jbGVhbl9sb25naXR1ZGUiLCJnZW9tZXRyeSIpICU+JQogICAgICAgICAgICAgICAgIHN0YXRzOjpzZXROYW1lcyhjKCJuYW1lIiwibGF0aXR1ZGUiLCJsb25naXR1ZGUiLCJnZW9tZXRyeSIpKSAlPiUgCiAgICAgICAgICAgICAgICAgZHBseXI6Om11dGF0ZShmZWF0dXJlX2NvZGU9ImV2ZW50IiwKICAgICAgICAgICAgICAgICAgICAgICAgdGltZXBlcmlvZD0iMTk1Mi0wMS0wMSIsCiAgICAgICAgICAgICAgICAgICAgICAgIHNvdXJjZV9kYXRhc2V0PSJldmVudHMiKSAlPiUgCiAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcighaXMubmEobmFtZSkgJiBuYW1lICE9IiIgJiAhaXMubmEobGF0aXR1ZGUpICYgIWlzLm5hKGxvbmdpdHVkZSkpICU+JQogICAgICAgICAgICAgICAgIGRwbHlyOjpkaXN0aW5jdCgpCgojIE1vZGVyYXRlIFNpemUgUG9pbnQgU291cmNlcwpuZ2Ffc2Zfcm9pIDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OmxvYWRfbmdhKHJvaSA9IHJlZ2lvbl9vZl9pbnRlcmVzdF9zZl91dG0sIGZyb21zY3JhdGNoID0gZnJvbXNjcmF0Y2gpCmdlb25hbWVzX3NmX3JvaSA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojpsb2FkX2dlb25hbWVzKHJvaSA9IHJlZ2lvbl9vZl9pbnRlcmVzdF9zZl91dG0sIGZyb21zY3JhdGNoID0gZnJvbXNjcmF0Y2gpCmhpc3RvcmljYWxfc2Zfcm9pIDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OmxvYWRfaGlzdG9yaWNhbChyb2kgPSByZWdpb25fb2ZfaW50ZXJlc3Rfc2ZfdXRtLCBmcm9tc2NyYXRjaCA9IGZyb21zY3JhdGNoKQoKCiMgU21hbGwgQVBJIFNvdXJjZXMKZ29vZ2xlbWFwc19zZl9yb2kgPC0gTWVhc3VyaW5nTGFuZHNjYXBlOjo6bG9hZF9nb29nbGVtYXBzKHJvaSA9IHJlZ2lvbl9vZl9pbnRlcmVzdF9zZl91dG0sIGZyb21zY3JhdGNoID0gZnJvbXNjcmF0Y2gpCmJpbmdtYXBzX3NmX3JvaSA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojpsb2FkX2JpbmdtYXBzKHJvaSA9IHJlZ2lvbl9vZl9pbnRlcmVzdF9zZl91dG0sIGZyb21zY3JhdGNoID0gZnJvbXNjcmF0Y2gpCgoKIyBNb2RlcmF0ZSBTaXplIG1peGVkIG9yIHBvbHlnb24gc291cmNlcwpLRU5fYWRtX3NmX3JvaSA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojpsb2FkX2tlbl9hZG0ocm9pID0gcmVnaW9uX29mX2ludGVyZXN0X3NmX3V0bSwgZnJvbXNjcmF0Y2ggPSBmcm9tc2NyYXRjaCkKbGl2ZXN0b2NrX3NmX3JvaSA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojpsb2FkX2xpdmVzdG9jayhyb2kgPSByZWdpb25fb2ZfaW50ZXJlc3Rfc2ZfdXRtLCBmcm9tc2NyYXRjaCA9IGZyb21zY3JhdGNoKQprZW55YV9kaXN0cmljdHMxOTYyX3NmX3JvaSA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojpsb2FkX2tlbnlhX2Rpc3RyaWN0czE5NjIocm9pID0gcmVnaW9uX29mX2ludGVyZXN0X3NmX3V0bSwgZnJvbXNjcmF0Y2ggPSBmcm9tc2NyYXRjaCkKa2VueWFfY2FkYXN0cmFsX2Rpc3RyaWN0X3NmX3JvaSA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojpsb2FkX2tlbnlhX2NhZGFzdHJhbF9kaXN0cmljdChyb2kgPSByZWdpb25fb2ZfaW50ZXJlc3Rfc2ZfdXRtLCBmcm9tc2NyYXRjaCA9IGZyb21zY3JhdGNoKQprZW55YV9jYWRhc3RyYWxfc2Zfcm9pIDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OmxvYWRfa2VueWFfY2FkYXN0cmFsKHJvaSA9IHJlZ2lvbl9vZl9pbnRlcmVzdF9zZl91dG0sIGZyb21zY3JhdGNoID0gZnJvbXNjcmF0Y2gpCgoKIyBWZXJ5IExhcmdlIFNvdXJjZXMKd2lraWRhdGFfc2Zfcm9pIDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OmxvYWRfd2lraWRhdGEobG9uZ19taW4sIGxvbmdfbWF4LCBsYXRfbWluLCBsYXRfbWF4LCBmcm9tc2NyYXRjaCA9IGZyb21zY3JhdGNoKQp0Z25fc2Zfcm9pIDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OmxvYWRfdGduKGxvbmdfbWluLCBsb25nX21heCwgbGF0X21pbiwgbGF0X21heCwgZnJvbXNjcmF0Y2ggPSBmcm9tc2NyYXRjaCkKb3BlbnN0cmVldG1hcF9zZl9yb2kgPC0gTWVhc3VyaW5nTGFuZHNjYXBlOjo6bG9hZF9vcGVuc3RyZWV0bWFwKHJvaSA9IHJlZ2lvbl9vZl9pbnRlcmVzdF9zZl91dG0sIGZyb21zY3JhdGNoID0gZnJvbXNjcmF0Y2gpCgoKaWYgKGZyb21zY3JhdGNoKSB7CgoKICAjIGNvbWJpbmVkbGlzdCA8LSBsYXBwbHkoY29tYmluZWRsaXN0LCBGVU49ZnVuY3Rpb24oeCkgeCAlPiUgbXV0YXRlKGhhc2g9YXBwbHkoeCwxICxkaWdlc3QpKSkgI2FkZCBhbiBpZAoKICAjIFRoaXMgdGFrZXMgd2F5IHRvbyBsb25nIGJ1dCBpcyB0aGUgb25seSBlYXN5IHdheSBJJ3ZlIGZvdW5kIHRvIG1lcmdlIHNmIGZyYW1lcwogICMgRm9yIHNwZWVkIGFuZCBkZWJ1Z2dpbmcgcHVycG9zZXMgc3BsaXR0aW5nIHRoaXMgdXAKICAjIEZvciBzb21lIHJlYXNvbiB0aGlzIHdvcmtzIHdoZW4geW91IHNwbGl0IGl0IHVwIGJ1dCBub3Qgd2hlbiB5b3UgcHV0IHRoZW0gdG9nZXRoZXIKICAjIERvdWJsZSBjaGVjayB0aGF0IHRoZXkncmUgYWxsIHNmIGRhdGEuZnJhbWVzLCBpZiBvbmUgb2YgdGhlbSBpcyBhIGRhdGEudGFibGUgaXQnbGwgZ2V0IHJiaW5kbGlzdCBpbnZvbHZlZCB3aGljaCB3aWxsIGJyZWFraW5nIHdoZW4gcmJpbmRpbmcgcG9pbnQgYW5kIG11bHRpcG9pbnQgZ2VvbWV0cmllcwoKICAjIHBvaW50cwogIGZsYXRmaWxlczEgPC0gbGlzdCggCiAgICBldmVudHNfc2Zfcm9pICU+JSBkaXN0aW5jdCgpLAogICAgIyBlbGxpcHNlX3NmX3JvaSAlPiUgZGlzdGluY3QoKSwKICAgIHRnbl9zZl9yb2kgJT4lIGRpc3RpbmN0KCksCiAgICBoaXN0b3JpY2FsX3NmX3JvaSAlPiUgZGlzdGluY3QoKSwKICAgIGdlb25hbWVzX3NmX3JvaSAlPiUgZGlzdGluY3QoKSwKICAgIG5nYV9zZl9yb2kgJT4lIGRpc3RpbmN0KCkKICApICU+JSByZWR1Y2UocmJpbmRfc2YpCiAgdGFibGUoZmxhdGZpbGVzMSRzb3VyY2VfZGF0YXNldCkKCiAgZmxhdGZpbGVzMWIgPC0gbGlzdCgKICAgIGdvb2dsZW1hcHNfc2Zfcm9pICU+JSBkaXN0aW5jdCgpLAogICAgYmluZ21hcHNfc2Zfcm9pICU+JSBkaXN0aW5jdCgpLAogICAgd2lraWRhdGFfc2Zfcm9pICU+JSBkaXN0aW5jdCgpCiAgKSAlPiUgcmVkdWNlKHJiaW5kX3NmKQogIHRhYmxlKGZsYXRmaWxlczEkc291cmNlX2RhdGFzZXQpCgogICMgUG9seWdvbnMKICBmbGF0ZmlsZXMyIDwtIGxpc3QoICMgZWxsaXBzZV9zZiAlPiUgZGlzdGluY3QoKSwKICAgIEtFTl9hZG1fc2Zfcm9pICU+JSBkaXN0aW5jdCgpLAogICAgb3BlbnN0cmVldG1hcF9zZl9yb2kgJT4lIGRpc3RpbmN0KCksCiAgICBsaXZlc3RvY2tfc2Zfcm9pICU+JSBkaXN0aW5jdCgpLAogICAga2VueWFfY2FkYXN0cmFsX3NmX3JvaSAlPiUgZGlzdGluY3QoKSwKICAgIGtlbnlhX2NhZGFzdHJhbF9kaXN0cmljdF9zZl9yb2kgJT4lIGRpc3RpbmN0KCksCiAgICBrZW55YV9kaXN0cmljdHMxOTYyX3NmX3JvaSAlPiUgZGlzdGluY3QoKQogICkgJT4lIHJlZHVjZShyYmluZF9zZikKICB0YWJsZShmbGF0ZmlsZXMyJHNvdXJjZV9kYXRhc2V0KQoKICBmbGF0ZmlsZXMgPC0gbGlzdCgKICAgIGZsYXRmaWxlczEgJT4lIGRpc3RpbmN0KCksCiAgICBmbGF0ZmlsZXMxYiAlPiUgZGlzdGluY3QoKSwKICAgIGZsYXRmaWxlczIgJT4lIGRpc3RpbmN0KCkKICApICU+JSByZWR1Y2UocmJpbmRfc2YpCiAgZmxhdGZpbGVzIDwtIGZsYXRmaWxlcyAlPiUgZGlzdGluY3QoKSAlPiUgc3RfY2FzdCgpCgogIGZsYXRmaWxlcyR2YWxpZCA8LSBzdF9pc192YWxpZChmbGF0ZmlsZXMpCiAgZmxhdGZpbGVzIDwtIHN0X21ha2VfdmFsaWQoZmxhdGZpbGVzKSAjIG9rIG5vdyB3ZSdyZSBnb2luZyB0byBmaXggdGhlIGJyb2tlbiBvbmVzLgoKICAjIHRlbXAgPC0gdW5pcXVlKGZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyX25vc3BhY2UpCiAgIyBmbGF0ZmlsZXMgPC0gYXMuZGF0YS50YWJsZShmbGF0ZmlsZXMpCiAgIyMgZmxhdGZpbGVzWyxsaXZlc3RvY2tfa2lsbDo9RixdCiAgIyBmbGF0ZmlsZXNbc291cmNlX2RhdGFzZXQ9PSJsaXZlc3RvY2siICYgbmFtZV9jbGVhbmVyICVpbiUgdGVtcCxsaXZlc3RvY2tfa2lsbDo9VCxdICNMaXZlc3RvY2sgZG9lc24ndCBoYXZlIHNwYWNlcyB1bmZvcnV0bmF0ZWx5CiAgIyBmbGF0ZmlsZXMgPC0gZmxhdGZpbGVzW2xpdmVzdG9ja19raWxsPT1GLF0KCiAgI2xpYnJhcnkoc3RyaW5ncikKICBmbGF0ZmlsZXNfc2Zfcm9pIDwtIGZsYXRmaWxlcyAlPiUKICAgICMgbXV0YXRlX2FsbChmdW5zKHN0cmlfZW5jX3RvYXNjaWkpKSAlPiUgI3JiaW5kbGlzdCB0cmllcyB0byBjb252ZXJ0IHRvIGZhY3RvcnMKICAgICMgbXV0YXRlKG5hbWUgPSBzdHJzcGxpdChuYW1lcywgIjt8IHNlZSB8IFNFRSB8IGNoZWNrIGlmIHNhbWUgYXMgIikpICU+JQogICAgIyB0aWR5cjo6dW5uZXN0KG5hbWUpICU+JSAjIFRoaXMgdHJpY2sgZG9lc24ndCB3b3JrIHdoZW4gdGhlcmUncyBtdWx0aXBsZSBsaXN0IGNvbHVtbnMsIHdoaWNoIGdlb21ldHJ5IGlzCiAgICBtdXRhdGUobmFtZSA9IHN0cl90cmltKChuYW1lKSkpICU+JQogICAgbXV0YXRlKG5hbWUgPSBnc3ViKCIsJCIsICIiLCBuYW1lKSkgJT4lICMgcmVtb3ZlIHRyYWlsaW5nIGNvbW1hCiAgICBtdXRhdGUobmFtZSA9IGdzdWIoIlwwMzIiLCAiIiwgbmFtZSkpICU+JSAjIHJlbW92ZSBlbmQgb2YgbGluZSBjaGFyYWN0ZXIKICAgIG11dGF0ZShuYW1lID0gZ3N1YigiXyIsICIgIiwgbmFtZSkpICU+JSAjIGNvbnZlcnQgdW5kZXJzY29yZXMgdG8gc3BhY2VzCiAgICBtdXRhdGUobmFtZSA9IGdzdWIoIiAtfC0gfCAtICIsICItIiwgbmFtZSkpICU+JSAjIGNvbnZlcnQgZGFzaGVzIHdpdGggd2VpcmQgc3BhY2luZyB0byBqdXN0IGEgZGFzaAogICAgZmlsdGVyKCFpcy5uYShuYW1lKSAmIG5hbWUgIT0gIk5BIiAmIG5hbWUgIT0gIiIpICU+JSAjIFJlbW92ZSBhbnl0aGluZyB0aGF0IG1pZ2h0IGJlIGEgbWlzc2luZyBuYW1lCiAgICAjIGZpbHRlcighaXMubmEoYXNjaWluYW1lX2VpdGhlcl9jbGVhbikgJiBhc2NpaW5hbWVfZWl0aGVyX2NsZWFuIT0iIiAmIG5jaGFyKGFzY2lpbmFtZV9laXRoZXJfY2xlYW4pPjIpICU+JQogICAgZGlzdGluY3QoKQoKICBmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVzIDwtIE5VTEwKICB0YWJsZShmbGF0ZmlsZXNfc2Zfcm9pJHNvdXJjZV9kYXRhc2V0KQogIHRhYmxlKGZsYXRmaWxlc19zZl9yb2kkdGltZXBlcmlvZCkKCiAgIyBGaXggZ2Vvc3BhdGlhbCBzdHVmZgogICMgbGlicmFyeShmZWF0aGVyKQogICMgY29uZGl0aW9uIDwtIHN0X2lzX3ZhbGlkKGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnkpIDsgdGFibGUoY29uZGl0aW9uKSAjIHRha2VzIGEgbG9uZyB3aGlsZQogICMgZmxhdGZpbGVzX3NmX3JvaSRnZW9tZXRyeVshY29uZGl0aW9uXSA8LSBzdF9tYWtlX3ZhbGlkKGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnlbIWNvbmRpdGlvbl0pCiAgZmxhdGZpbGVzX3NmX3JvaSRnZW9tZXRyeV9kaW1lbnNpb25zIDwtIHNhcHBseShmbGF0ZmlsZXNfc2Zfcm9pJGdlb21ldHJ5LCBGVU4gPSBmdW5jdGlvbih4KSBzdF9kaW1lbnNpb24oeCkpICMgdGFrZXMgYSB3aGlsZQogIHRhYmxlKGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnlfZGltZW5zaW9ucywgdXNlTkEgPSAiYWx3YXlzIikKCgogIGNvbmRpdGlvbiA8LSBpcy5uYShmbGF0ZmlsZXNfc2Zfcm9pJGdlb21ldHJ5X2RpbWVuc2lvbnMpCiAgdGFibGUoZmxhdGZpbGVzX3NmX3JvaSRzb3VyY2VfZGF0YXNldCwgZmxhdGZpbGVzX3NmX3JvaSRnZW9tZXRyeV9kaW1lbnNpb25zLCB1c2VOQSA9ICJhbHdheXMiKQogIGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnlbY29uZGl0aW9uXSA8LSBzdF9wb2ludCgpICMgUmVwbGFjZSB0aGVzZSBlbXB0eSBwb2x5Z29ucyB0aGUgbWFrZSB2YWxpZCBwdXQgaW4gd2l0aCBlbXB0eSBwb2ludHMKICBjb25kaXRpb24gPC0gaXMubmEoZmxhdGZpbGVzX3NmX3JvaSRnZW9tZXRyeV9kaW1lbnNpb25zKQogIHRhYmxlKGZsYXRmaWxlc19zZl9yb2kkc291cmNlX2RhdGFzZXQsIGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnlfZGltZW5zaW9ucywgdXNlTkEgPSAiYWx3YXlzIikKICBmbGF0ZmlsZXNfc2Zfcm9pJGdlb21ldHJ5X3R5cGUgPC0gc2FwcGx5KGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnksIEZVTiA9IGZ1bmN0aW9uKHgpIGNsYXNzKHgpWzJdKQogIHRhYmxlKGZsYXRmaWxlc19zZl9yb2kkc291cmNlX2RhdGFzZXQsIGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnlfdHlwZSwgdXNlTkEgPSAiYWx3YXlzIikKCiAgY29uZGl0aW9uIDwtIGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnlfdHlwZSAlaW4lIGMoIlBPTFlHT04iLCAiTVVMVElQT0xZR09OIikKICBmbGF0ZmlsZXNfc2Zfcm9pJGdlb21ldHJ5X2FyZWEgPC0gTkEKICBmbGF0ZmlsZXNfc2Zfcm9pJGdlb21ldHJ5X2FyZWFbY29uZGl0aW9uXSA8LSBzdF9hcmVhKGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnlbY29uZGl0aW9uXSkgIyBvayBpcyBmYWlsaW5nIGZvciBlbXB0eSBwb2x5Z29ucwoKICBjb25kaXRpb24gPC0gZmxhdGZpbGVzX3NmX3JvaSRnZW9tZXRyeV90eXBlICVpbiUgYygiTElORVNUUklORyIpCiAgZmxhdGZpbGVzX3NmX3JvaSRnZW9tZXRyeV9sZW5ndGggPC0gTkEKICBmbGF0ZmlsZXNfc2Zfcm9pJGdlb21ldHJ5X2xlbmd0aFtjb25kaXRpb25dIDwtIHN0X2xlbmd0aChmbGF0ZmlsZXNfc2Zfcm9pJGdlb21ldHJ5W2NvbmRpdGlvbl0pCgogICMgQnkgZGVmaW5pdGlvbiwgYWxsIFJPSSBtYXRjaGVzIGFyZSBpbnRlcnNlY3Rpb25zIHdpdGggdGhlIFJPSQogICMgZmxhdGZpbGVzX3NmX3JvaSA8LSBmbGF0ZmlsZXNfc2Zfcm9pICU+JSBtdXRhdGUocmVnaW9uX29mX2ludGVyZXN0X292ZXJsYXAgPQogICMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy52ZWN0b3Ioc3Rfb3ZlcmxhcHMoICBnZW9tZXRyeSwgcmVnaW9uX29mX2ludGVyZXN0X3NmX3V0bSwgICBzcGFyc2U9RikgKSAgKSAjRmxhZyB0aGUgUmVnaW9uIG9mIEludGVyZXN0CiAgIyBmbGF0ZmlsZXNfc2Zfcm9pIDwtIGZsYXRmaWxlc19zZl9yb2kgJT4lIG11dGF0ZShyZWdpb25fb2ZfaW50ZXJlc3Rfd2l0aGluID0KICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMudmVjdG9yKHN0X3dpdGhpbiggICAgZ2VvbWV0cnksIHJlZ2lvbl9vZl9pbnRlcmVzdF9zZl91dG0sICAgc3BhcnNlPUYpICkgICkgI0ZsYWcgdGhlIFJlZ2lvbiBvZiBJbnRlcmVzdAogICMgZmxhdGZpbGVzX3NmX3JvaSA8LSBmbGF0ZmlsZXNfc2Zfcm9pICU+JSBtdXRhdGUocmVnaW9uX29mX2ludGVyZXN0X2ludGVyc2VjdHMgPQogICMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy52ZWN0b3Ioc3RfaW50ZXJzZWN0cyhnZW9tZXRyeSwgcmVnaW9uX29mX2ludGVyZXN0X3NmX3V0bSwgICBzcGFyc2U9RikgKSAgICkgI0ZsYWcgdGhlIFJlZ2lvbiBvZiBJbnRlcmVzdAoKICAjIHRhYmxlKGZsYXRmaWxlc19zZl9yb2kkcmVnaW9uX29mX2ludGVyZXN0X292ZXJsYXApICNJIGd1ZXNzIGp1c3QgZm9yIHBvbHlnb25zCiAgIyB0YWJsZShmbGF0ZmlsZXNfc2Zfcm9pJHJlZ2lvbl9vZl9pbnRlcmVzdF93aXRoaW4pICNyZXF1aXJlcyBwb2x5cyB0byBiZSBlbnRpcmVseSBpbnNpZGUKICAjIHRhYmxlKGZsYXRmaWxlc19zZl9yb2kkcmVnaW9uX29mX2ludGVyZXN0X2ludGVyc2VjdHMpICNqdXN0IHJlcXVpcmVzIHBvbHlzIHRvIG92ZXJsYXAgYSBsaXR0bGUgYml0CgoKICAjICNUaW1lIHRvIGZpeCBsaXZlc3RvY2sgbmFtZXMKICAjIHRlbXAgPC0gZmxhdGZpbGVzX3NmX3JvaSAlPiUgZmlsdGVyKCFzb3VyY2VfZGF0YXNldCAlaW4lIGMoJ2xpdmVzdG9ja19ib3VuZGFyaWVzJywgICdsaXZlc3RvY2tfcG9pbnRzJykpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgIyAgIHNlbGVjdChjKCJuYW1lX2NsZWFuZXIiLCJuYW1lX2NsZWFuZXJfbm9zcGFjZSIpKSAlPiUKICAjICAgICAgICAgZGlzdGluY3QoKSAlPiUKICAjICAgICAgICAgZmlsdGVyKCFkdXBsaWNhdGVkKG5hbWVfY2xlYW5lcl9ub3NwYWNlKSkgI2ZvciBzb21lIHJlYXNvbiBtdWx0aXBsZSBuYW1lcyBhcmUgbWFwaW5nIHRvIHRoZSBzYW1lIG5hbWUgd2l0aG91dCBzcGFjZXMKICAjIHJvd25hbWVzKHRlbXApIDwtIHRlbXAkbmFtZV9jbGVhbmVyX25vc3BhY2UKICAjCiAgIyBmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lcl9zcGFjZWQgPC0gdGVtcFtmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lciwibmFtZV9jbGVhbmVyIl0gIwogICMgY29uZGl0aW9uIDwtIGZsYXRmaWxlc19zZl9yb2kkc291cmNlX2RhdGFzZXQgJWluJSBjKCdsaXZlc3RvY2tfYm91bmRhcmllcycsICAnbGl2ZXN0b2NrX3BvaW50cycpICYgIWlzLm5hKGZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyX3NwYWNlZCkgOyB0YWJsZShjb25kaXRpb24pCiAgIyBmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lcltjb25kaXRpb25dIDwtIGZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyX3NwYWNlZFtjb25kaXRpb25dCgogIGZsYXRmaWxlc19zZl9yb2kkZXZlbnRzb3VyY2UgPC0gZmxhdGZpbGVzX3NmX3JvaSRzb3VyY2VfZGF0YXNldCAlaW4lICJldmVudHMiCgogICMgR2V0IGxhdCBsb25ncyBiYWNrCiAgY29yZHMgPC0gc3RfY29vcmRpbmF0ZXMoZmxhdGZpbGVzX3NmX3JvaSAlPiUgZmlsdGVyKGdlb21ldHJ5X3R5cGUgJWluJSAiUE9JTlQiKSkKICBjb25kaXRpb24xIDwtIGZsYXRmaWxlc19zZl9yb2kkZ2VvbWV0cnlfdHlwZSAlaW4lICJQT0lOVCIgJiBpcy5uYShmbGF0ZmlsZXNfc2Zfcm9pJGxvbmdpdHVkZSkKICBjb25kaXRpb24yIDwtIGlzLm5hKGZsYXRmaWxlc19zZl9yb2kkbG9uZ2l0dWRlKVtmbGF0ZmlsZXNfc2Zfcm9pJGdlb21ldHJ5X3R5cGUgJWluJSAiUE9JTlQiXQoKICBmbGF0ZmlsZXNfc2Zfcm9pJGxvbmdpdHVkZVtjb25kaXRpb24xXSA8LSBjb3Jkc1tjb25kaXRpb24yLCAxXQogIGZsYXRmaWxlc19zZl9yb2kkbGF0aXR1ZGVbY29uZGl0aW9uMV0gPC0gY29yZHNbY29uZGl0aW9uMiwgMl0KCiAgZmxhdGZpbGVzX3NmX3JvaSA8LSBmbGF0ZmlsZXNfc2Zfcm9pICU+JQogICAgIyBUaGlzIGRpc3RpbmN0IGlzIHZlcnkgaW1wb3J0YW50LCBpdCBjb3JyZWN0bHkgcmVtb3ZlcyBkdXBsaWNhdGVzCiAgICBkaXN0aW5jdChmZWF0dXJlX2NvZGUsIGxhdGl0dWRlLCBsb25naXR1ZGUsIG5hbWUsIHNvdXJjZV9kYXRhc2V0LAogICAgICAgICAgICAgZ2VvbWV0cnlfdHlwZSwgcmVnaW9uX29mX2ludGVyZXN0X2ludGVyc2VjdHMsIG5hbWVfYWx0ZXJuYXRlcywgLmtlZXBfYWxsID0gVCkgJT4lCiAgICBtdXRhdGUobmFtZV9jbGVhbiA9IHN0cl90cmltKHRvbG93ZXIobmFtZSkpKSAlPiUKICAgIG11dGF0ZShuYW1lX2NsZWFuX3Bvc2Vzc2l2ZSA9IGdyZXBsKCInc3xgcyIsIG5hbWVfY2xlYW4pKSAlPiUKICAgIG11dGF0ZShuYW1lX2NsZWFuZXIgPSB0cmltd3MobmFtZV9jbGVhbikpICU+JQogICAgbXV0YXRlKG5hbWVfY2xlYW5lciA9IGdzdWIoIidzfGBzIiwgIiIsIG5hbWVfY2xlYW5lciwgZml4ZWQgPSBUKSkgJT4lCiAgICBtdXRhdGUobmFtZV9jbGVhbmVyID0gc3RyX3JlcGxhY2VfYWxsKG5hbWVfY2xlYW5lciwgIltbOnB1bmN0Ol1dfCIsICIiKSkgJT4lCiAgICBtdXRhdGUobmFtZV9jbGVhbmVyID0gdHJpbXdzKG5hbWVfY2xlYW5lcikpICU+JQogICAgbXV0YXRlKG5hbWVfY2xlYW5lcl9ub3NwYWNlID0gc3RyX3JlcGxhY2VfYWxsKG5hbWVfY2xlYW5lciwgIiAiLCAiIikpCgogIHNvcnQodW5pcXVlKHVubGlzdChzdHJzcGxpdChmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW4sICIiKSkpKQogIGZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyIDwtIGNsZWFuX25vYXNjaWkoZmxhdGZpbGVzX3NmX3JvaSRuYW1lX2NsZWFuZXIpCiAgc29ydCh1bmlxdWUodW5saXN0KHN0cnNwbGl0KGZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyLCAiIikpKSkgIyBvbmx5IG51bWJlciBhbmQgbG93ZXJjYXNlIGxldHRlcnMgZnJvbSBub3cgb24KCiAgIyBHZW9uYW1lcyBDb2RlIERlc2NyaXB0aW9ucwogICMgZmxhdGZpbGVzX3NmX3JvaSRmZWF0dXJlX2NvZGUgJT4lIGphbml0b3I6OnRhYnlsKCBzb3J0ID0gVFJVRSkgICM1MTIgdW5pcXVlIGNvZGVzCiAgZ2VvbmFtZXNfY29kZV9kZXNjcmlwdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShyZWFkX2NzdihzeXN0ZW0uZmlsZSgiZXh0ZGF0YSIsICJnZW9uYW1lc19jb2RlX2Rlc2NyaXB0aW9ucy5jc3YiLCBwYWNrYWdlID0gIk1lYXN1cmluZ0xhbmRzY2FwZSIpLCBjb2xfbmFtZXMgPSBGKSkKICBuYW1lcyhnZW9uYW1lc19jb2RlX2Rlc2NyaXB0aW9ucykgPC0gYygiY29kZSIsICJjb2RlX3R4dCIsICJkZXNjcmlwdGlvbiIpCiAgcm93bmFtZXMoZ2VvbmFtZXNfY29kZV9kZXNjcmlwdGlvbnMpIDwtIGdlb25hbWVzX2NvZGVfZGVzY3JpcHRpb25zJGNvZGUKCiAgZmxhdGZpbGVzX3NmX3JvaSRmZWF0dXJlX2NvZGVfdHh0IDwtIHRvbG93ZXIoZ2VvbmFtZXNfY29kZV9kZXNjcmlwdGlvbnNbZmxhdGZpbGVzX3NmX3JvaSRmZWF0dXJlX2NvZGUsICJjb2RlX3R4dCJdKQogIGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dFtpcy5uYShmbGF0ZmlsZXNfc2Zfcm9pJGZlYXR1cmVfY29kZV90eHQpXSA8LSB0b2xvd2VyKGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlW2lzLm5hKGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dCldKQogIHRhYmxlKGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlW2lzLm5hKGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dCldKQoKICBmbGF0ZmlsZXNfc2Zfcm9pJGZlYXR1cmVfY29kZV90eHQgPC0gZ3N1YigiXyIsICIgIiwgZmxhdGZpbGVzX3NmX3JvaSRmZWF0dXJlX2NvZGVfdHh0KQogIGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dCA8LSBnc3ViKCIoLWllcykiLCAiICIsIGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dCwgZml4ZWQgPSBUKQogIGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dCA8LSBnc3ViKCIocykiLCAiICIsIGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dCwgZml4ZWQgPSBUKQogIGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dCA8LSBnc3ViKCIoZXMpIiwgIiAiLCBmbGF0ZmlsZXNfc2Zfcm9pJGZlYXR1cmVfY29kZV90eHQsIGZpeGVkID0gVCkKICBmbGF0ZmlsZXNfc2Zfcm9pJGZlYXR1cmVfY29kZV90eHQgPC0gdHJpbXdzKGZsYXRmaWxlc19zZl9yb2kkZmVhdHVyZV9jb2RlX3R4dCkKCiAgZmxhdGZpbGVzX3NmX3JvaSRwbGFjZV9oYXNoIDwtIGFwcGx5KGZsYXRmaWxlc19zZl9yb2ksIDEsIGRpZ2VzdCwgYWxnbyA9ICJ4eGhhc2g2NCIpICMgY3JlYXRlIGEgaGFzaCBpZCB0byByZWZlcmVuY2UKICByb3duYW1lcyhmbGF0ZmlsZXNfc2Zfcm9pKSA8LSBmbGF0ZmlsZXNfc2Zfcm9pJGlkX2hhc2ggIyBhY3R1YWxseSBnZXR0aW5nIGNvbGxpc2lvbnMKCiAgc2F2ZVJEUygKICAgIGZsYXRmaWxlc19zZl9yb2ksCiAgICBmaWxlID0gZ2x1ZTo6Z2x1ZShnZXR3ZCgpLCAiLy4uL2luc3QvZXh0ZGF0YS9mbGF0ZmlsZXNfc2Zfcm9pLlJkYXRhIikgCiAgKQoKICAjIHN0X3dyaXRlKG9iaj1mbGF0ZmlsZXNfc2Zfcm9pLAogICMgICAiL2hvbWUvcmV4ZG91Z2xhc3MvRHJvcGJveCAocmV4KS9LZW55YSBBcnRpY2xlIERyYWZ0cy9NZWFzdXJpbmdMYW5kc2NhcGVDaXZpbFdhci9pbnN0L2V4dGRhdGEvZmxhdGZpbGVzX3NmX3JvaS5ncGtnIiwKICAjICAgZGVsZXRlX2xheWVyID0gVFJVRSkKfQoKZmxhdGZpbGVzX3NmX3JvaSA8LSByZWFkUkRTKHN5c3RlbS5maWxlKCJleHRkYXRhIiwgImZsYXRmaWxlc19zZl9yb2kuUmRhdGEiLCBwYWNrYWdlID0gIk1lYXN1cmluZ0xhbmRzY2FwZSIpKQoKYGBgCgojIFRhYmxlIGNvbXBhcmluZyB0aGUgZ2F6ZXR0ZWVycwoKYGBge3J9CgpmbGF0ZmlsZXNfc2Zfcm9pICU+JSBqYW5pdG9yOjp0YWJ5bChzb3VyY2VfZGF0YXNldCkgJT4lIGphbml0b3I6OmFkb3JuX2Nyb3NzdGFiKC4sIGRlbm9tID0gImNvbCIsIHNob3dfbiA9IFQsIGRpZ2l0cyA9IDEsIHNob3dfdG90YWxzID0gVCkKYGBgCgoKIyBIYW5kbGUgdHlwZXMKCkRpZmZlcmVudCBnYXpldHRlZXJzIHJlY29yZCB3aGF0IHR5cGUgb2YgbG9jYXRpb24gYSBuYW1lIGJlbG9uZ3MgdG8gc2xpZ2h0bHkgZGlmZmVyZW50bHkuIFdlJ3ZlIHBhcnRpYWxseSBtZXJnZWQgc29tZSBjYXRlZ29yaWVzIHRvIHByb3ZpZGUgZ3JlYXRlciBvdmVybGFwLgoKYGBge3J9CgpmbGF0ZmlsZXNfc2Zfcm9pJGZlYXR1cmVfY29kZV90eHQgJT4lIGphbml0b3I6OnRhYnlsKHNvcnQgPSBUUlVFKSAlPiUgamFuaXRvcjo6YWRvcm5fY3Jvc3N0YWIoLiwgZGVub20gPSAiYWxsIiwgcm91bmRpbmcgPSAiaGFsZiB1cCIsIHNob3dfbiA9IFQsIGRpZ2l0cyA9IDEsIHNob3dfdG90YWxzID0gVCkgIyA0NTkgdW5pcXVlIGNvZGVzCmBgYAoKCiMgVmlzdWFsbHkgQ29tcGFyaW5nIFNwYXRpYWwgQ292ZXJhZ2Ugb2YgZWFjaCBTb3VyY2VzCgpWaXN1YWwgaW5zcGVjdGlvbiByZXZlYWxzIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHNwYXRpYWwgY292ZXJhZ2Ugb2YgZWFjaCBzb3VyY2UuIChBcHBlbmRpeCBGaWd1cmUgMSBhbmQgQXBwZW5kaXggRmlndXJlIDMpCgpgYGB7UiwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9CgojIGZsYXRmaWxlcyAlPiUgZ2dwbG90KGFlcyh4PWxvbmdpdHVkZSx5PWxhdGl0dWRlLCBjb2w9YXMuZmFjdG9yKHNvdXJjZV9kYXRhc2V0KSkpICsgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkKcF9wb2ludHMgPC0gZmxhdGZpbGVzX3NmX3JvaSAlPiUKICBmaWx0ZXIoZ2VvbWV0cnlfdHlwZSAlaW4lICJQT0lOVCIpICU+JQogIGdncGxvdChhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlLCBjb2wgPSBhcy5mYWN0b3Ioc291cmNlX2RhdGFzZXQpKSkgKyBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArIGZhY2V0X3dyYXAofmFzLmZhY3Rvcihzb3VyY2VfZGF0YXNldCksIGRyb3AgPSBUKSArIGd1aWRlcyhjb2xvciA9IEZBTFNFKSArIHRoZW1lX2J3KCkgKwogIHhsaW0oMzUuNjcsIDM4LjE5KSArIHlsaW0oLTEuNDMyODUsIDAuNTQ1NDMpCgpnZ3NhdmUoCiAgZmlsZW5hbWUgPSBnbHVlOjpnbHVlKGRpcl9maWd1cmVzLCAiZmxhdGZpbGVzX3NmX3JvaV9mYWNldF9zb3VyY2VfZGF0YXNldF9wb2ludHMucG5nIiksCiAgcGxvdCA9IHBfcG9pbnRzLCB3aWR0aCA9IDEyLCBoZWlnaHQgPSAxMAopCnBfcG9pbnRzCiMgZmxhdGZpbGVzX3NmX3JvaSAlPiUgZmlsdGVyKHJlZ2lvbl9vZl9pbnRlcmVzdF9pbnRlcnNlY3RzPT1UKSAlPiUgZ2dwbG90KGFlcyhjb2w9YXMuZmFjdG9yKHNvdXJjZV9kYXRhc2V0KSkpICsgZ2VvbV9zZihhbHBoYSA9IDAuMSkKCgojZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJ0aWR5dmVyc2UvZ2dwbG90MiIpICMgZ2VvbV9zZiByZXF1aXJlcyBnZ3Bsb3QgaW5zdGFsbGVkIG9mZiBvZiB0aGUgZGV2IHNlcnZlcgoKcF9ub3Rwb2ludHMgPC0gZmxhdGZpbGVzX3NmX3JvaSAlPiUKICBmaWx0ZXIoIWdlb21ldHJ5X3R5cGUgJWluJSAiUE9JTlQiKSAlPiUKICBnZ3Bsb3QoYWVzKGNvbG9yID0gYXMuZmFjdG9yKHNvdXJjZV9kYXRhc2V0KSkpICsgIyMjIGdlb21fc2YgcmVxdWlyZXMgZ2dwbG90IGluc3RhbGxlZCBvZmYgb2YgdGhlIGRldiBzZXJ2ZXIKICBnZW9tX3NmKGFscGhhID0gMC4xLCBzaXplID0gMSkgKwogIGZhY2V0X3dyYXAofmFzLmZhY3Rvcihzb3VyY2VfZGF0YXNldCksIGRyb3AgPSBUKSArCiAgIyBnZ3RpdGxlKCJMb2NhdGlvbnMgaW4gUmVnaW9uIG9mIEludGVyZXN0IFN0cmF0aWZpZWQgYnkgU291cmNlIikgKwogIGd1aWRlcyhjb2xvciA9IEZBTFNFKSArCiAgeGxpbSgzNS42NywgMzguMTkpICsgeWxpbSgtMS40MzI4NSwgMC41NDU0MykgKyB0aGVtZV9idygpIAoKZ2dzYXZlKAogIGZpbGVuYW1lID0gZ2x1ZTo6Z2x1ZShkaXJfZmlndXJlcywgImZsYXRmaWxlc19zZl9yb2lfZmFjZXRfc291cmNlX2RhdGFzZXRfbm90cG9pbnRzLnBuZyIpLAogIHBsb3QgPSBwX25vdHBvaW50cywgd2lkdGggPSAxMiwgaGVpZ2h0ID0gNgopCnBfbm90cG9pbnRzCgpgYGAKCiMgVmlzdWFsbHkgQ29tcGFyaW5nIFNwYXRpYWwgUHJlY2lzaW9uIG9mIGVhY2ggU291cmNlcwoKKEFwcGVuZGl4IEZpZ3VyZSAyIG9uIExvbmdpdHVkZSkKCgpgYGB7UiwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9CgojIEV2ZW50cyBhbmQgaGlzdG9yaWNhbCBoYXZlIHRoZSBzbW9vdGhlc3QgZGlzdHJpYnV0aW9ucywgYnV0IHRoYXQncyBtaXNsZWFkaW5nIGJlY2F1c2UgY29udmVyc2lvbiBmcm9tIGRlZ3JlZXMgdG8gZGVjaW1hbAojIFdpa2lkYXRhIGhhcyBsb3RzIG9mIHRydW5jYXRlZCBwcmVjaXNpb24gZW50cmllcywgYXQgMCBkZWdyZWVzCiMgVEdOIGhhcyBzbyBmZXcgaXQncyBoYXJkIHRvIHRlbGwKIyBPcGVuIHN0cmVldG1hcCBoYXMgYSBkaXNwcm9wb3J0aW9uYXRlIG51bWJlciBvZiBidXNpbmVzcyBpbiB0aGUgY2FwaXRhbCB3aGljaCBsZWFkcyB0byBhIHdlaXJkIGRpc3RyaWJ1dGlvbgojIE5HQSwgZ2VvbmFtZXMgYW5kIGhpc3RvcmljYWwgbG9vayBsaWtlIGEgY29tYgojIGxpdmVzdG9jayBsb29rcyBsaWtlIGNvbWIgd2l0aCB0ZWV0aCBtaXNzaW5nCiMgRXZlbnRzIGxvb2sgZmFpcmx5IGNvbnRpbnVvdXMgd2l0aCBzb21lIHNwaWtlcwoKIyBJdCBsb29rcyBsaWtlIGEgbG90IG9mIHRoZXNlIHRoYXQgZG9uJ3QgbWF0Y2ggaGF2ZSB0cnVuY2F0ZWQgcHJlY2lzaW9uCnBfY29tYnNfbGF0IDwtIGZsYXRmaWxlc19zZl9yb2kgJT4lCiAgZmlsdGVyKGdlb21ldHJ5X3R5cGUgJWluJSAiUE9JTlQiKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgc2VsZWN0KGxhdGl0dWRlLCBzb3VyY2VfZGF0YXNldCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbGF0aXR1ZGUgLSByb3VuZChsYXRpdHVkZSksIGNvbCA9IGFzLmZhY3Rvcihzb3VyY2VfZGF0YXNldCkpKSArIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwKSArIGZhY2V0X3dyYXAofnNvdXJjZV9kYXRhc2V0LCBzY2FsZXMgPSAiZnJlZSIpCmdnc2F2ZSgKICBmaWxlbmFtZSA9IGdsdWU6OmdsdWUoZGlyX2ZpZ3VyZXMsICJwX2NvbWJzX2xhdC5wZGYiKSwKICBwbG90ID0gcF9jb21ic19sYXQsIHdpZHRoID0gMTIsIGhlaWdodCA9IDYKKQpwX2NvbWJzX2xhdAoKcF9jb21ic19sb25nIDwtIGZsYXRmaWxlc19zZl9yb2kgJT4lCiAgZmlsdGVyKGdlb21ldHJ5X3R5cGUgJWluJSAiUE9JTlQiKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgc2VsZWN0KGxvbmdpdHVkZSwgc291cmNlX2RhdGFzZXQpICU+JQogIGdncGxvdChhZXMoeCA9IGxvbmdpdHVkZSAtIHJvdW5kKGxvbmdpdHVkZSksIGNvbCA9IGFzLmZhY3Rvcihzb3VyY2VfZGF0YXNldCkpKSArIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwKSArIGZhY2V0X3dyYXAofnNvdXJjZV9kYXRhc2V0LCBzY2FsZXMgPSAiZnJlZSIpCmdnc2F2ZSgKICBmaWxlbmFtZSA9IGdsdWU6OmdsdWUoZGlyX2ZpZ3VyZXMsICJwX2NvbWJzX2xvbmcucGRmIiksCiAgcGxvdCA9IHBfY29tYnNfbG9uZywgd2lkdGggPSAxMiwgaGVpZ2h0ID0gNgopCnBfY29tYnNfbG9uZwoKCnRlbXAgPC0gZmxhdGZpbGVzX3NmX3JvaSAlPiUKICBmaWx0ZXIoZ2VvbWV0cnlfdHlwZSAlaW4lICJQT0lOVCIpICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBzZWxlY3QobG9uZ2l0dWRlLCBzb3VyY2VfZGF0YXNldCkgJT4lCiAgZ3JvdXBfYnkoc291cmNlX2RhdGFzZXQpICU+JQogIG11dGF0ZShsb25naXR1ZGVfdHJ1bmMgPSBsb25naXR1ZGUgLSByb3VuZChsb25naXR1ZGUpKQoKI3RlbXAgJT4lIGZpbHRlcihzb3VyY2VfZGF0YXNldCAlaW4lICJoaXN0b3JpY2FsIikgJT4lIHVuZ3JvdXAoKSAlPiUgc2VsZWN0KCJsb25naXR1ZGVfdHJ1bmMiKSAlPiUgYXMudmVjdG9yKCkgJT4lIHRhYmxlKCkKCiN0ZW1wICU+JSBmaWx0ZXIoc291cmNlX2RhdGFzZXQgJWluJSAiZ2VvbmFtZXMiKSAlPiUgdW5ncm91cCgpICU+JSBzZWxlY3QoImxvbmdpdHVkZV90cnVuYyIpICU+JSBhcy52ZWN0b3IoKSAlPiUgcm91bmQoNSkgJT4lIHRhYmxlKCkgJT4lIHNvcnQoZGVjcmVhc2luZyA9IFQpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIGZpbHRlcihGcmVxID4gMTApCgoKdGVtcCA8LSBmbGF0ZmlsZXNfc2Zfcm9pICU+JQogIGZpbHRlcihnZW9tZXRyeV90eXBlICVpbiUgIlBPSU5UIikgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHNlbGVjdChsb25naXR1ZGUsIHNvdXJjZV9kYXRhc2V0KSAlPiUKICBncm91cF9ieShzb3VyY2VfZGF0YXNldCkgJT4lCiAgYXJyYW5nZShsb25naXR1ZGUpICU+JQogIG11dGF0ZShsb25naXR1ZGVfbDEgPSBsYWcobG9uZ2l0dWRlLCAxKSkgJT4lCiAgbXV0YXRlKGxvbmdpdHVkZV9mZCA9IHJvdW5kKGxvbmdpdHVkZSAtIGxvbmdpdHVkZV9sMSwgNCkpCmQgPC0gYXMubWF0cml4KHRhYmxlKHRlbXAkbG9uZ2l0dWRlX2ZkLCB0ZW1wJHNvdXJjZV9kYXRhc2V0KSkgIyBvayBzbyB0aGUgbW9zdCBjb21tb24gZGlmZmVyZW5jZSBiZXR3ZWVuIHBvaW50cyBpcyAwLjAxNi4gVGhhdCdzIGFib3V0IDEuNzc2IGttIAoKYGBgCgoKCg==