Takes in locations of events described as text, and returns all possible matches across different gazetteers.

Dependencies: events_sf.Rdata, flatfiles_sf_roi.Rdata Products: georef_all_dt.Rds

rm(list=ls()); gc()
# Hiding output and warnings
# !diagnostics off
library(MeasuringLandscape)
library(tidyverse)
dir_figures <- paste0(here::here(), "/paper/figures/")
gc()
knitr::opts_knit$set(progress = TRUE, verbose = TRUE)
knitr::opts_chunk$set(fig.width=12, fig.height=8,  warning=FALSE, message=FALSE, cache=TRUE)
options(width = 160)

Load Files

We load the events file to be geolocated.

#Load Events
events_sf <- readRDS(system.file("extdata", "events_sf.Rdata", package = "MeasuringLandscape")) 
events_sf_text_coord_unique <- plyr::ddply(events_sf[,c('location_text','name_clean','name_cleaner','document_district_clean','map_coordinate_clean_latitude','map_coordinate_clean_longitude')],
                                     "location_text", transform,
      map_coordinate_has =sum(!is.na(map_coordinate_clean_latitude))
      )

We load the combined gazetteer files.

#Load Gazetteers
flatfiles_sf_roi <- readRDS(system.file("extdata", "flatfiles_sf_roi.Rdata", package = "MeasuringLandscape")) 
Warning message:
Unknown or uninitialised column: 'name_cleaner_stem'. 
dim(flatfiles_sf_roi)
[1] 55966    22
flatfiles_dt <- data.table::as.data.table(flatfiles_sf_roi)
data.table::setkey(flatfiles_dt,place_hash)

We load a pretrained toponym matcher based on XGBoost and many string distance features.

#Load toponym model
#p_load(xgboost)
toponym_xb_model <- xgboost::xgb.load(system.file("extdata", "toponym_xb_onlystring.bin", package = "MeasuringLandscape"))

Summary Statistics

When we start, we have 2939 unique location strings in the events data.

rr #events_sf$name_cleaner %>% tabyl(sort=T)

When we start, we have 22735 unique location strings in the gazetteer data.

rr #flatfiles_sf_roi$name_cleaner %>% tabyl(sort=T)

If we considered every single pair of possible matches it would require about 62 million comparisons (66818165). And that is just for our relatively small number of event location strings and restricting our gazetteers to a relatively small region of interest. Obviously, any approach we implement is going to have to scale sub-quadratic in the number of locations or it won’t be applicable to any problem of interesting size.

Stem

Our first move is to stem each location string of prefixes and common suffixes so that we can match slight variations of the same place to a single identifier.

#Step 1  Get Suggestions for Stems
#Stem the gazetteer names
temp <- MeasuringLandscape:::strip_postfixes(flatfiles_sf_roi$name_cleaner)
[1] "Loading Preexisting"
flatfiles_sf_roi$name_cleaner_stem <- temp[[1]]
flatfiles_sf_roi$name_cleaner_postfix <- temp[[2]]
#flatfiles_sf_roi$name_cleaner_stem %>% tabyl(sort=T)

There are 17231 unique location stems in the flatfiles data.

#Stem the event names
temp <- MeasuringLandscape:::strip_postfixes(events_sf$name_cleaner)
[1] "Loading Preexisting"
events_sf$name_cleaner_stem <- temp[[1]]
events_sf$name_cleaner_postfix <- temp[[2]]
#events_sf$name_cleaner %>% tabyl(sort=T)

There are 2465 unique location stems in the events data.

#Unique stems
stemmed_ab <- unique(c(flatfiles_sf_roi$name_cleaner_stem, events_sf$name_cleaner_stem))
length(stemmed_ab) #17,880 unique stems
[1] 17876
#Unique name-stem pairs
names_and_stems <- rbind(
                          as.data.frame(flatfiles_sf_roi)[,c('name_cleaner','name_cleaner_stem')],
                          as.data.frame(events_sf)[,c('name_cleaner','name_cleaner_stem')]
                        ) %>% unique() ; dim(names_and_stems)
[1] 23590     2

LSH

Our next move is hash each stemmed location in a way that locations that are somewhat similar to one another are returned as possible matches and the vast majority of pairs that are too dissimilar to ever possibly match are excluded. This requires only a single pass through the data, and so scales linearly in the number of locations. We select parameters which perform well on our hand-labeled training dataset of toponym matches and mismatches. Where performing well at this stage means a low false negative rate (missing few genuine matches) and a moderate false positive rate (returning many irrelevant matches, but not an overwhelming number). Here we are only trying to reject as many obvious nonmatches as possible.

minhash_count=100
bands=25
fromscratch=F # For some reason these parallel functions hang in rmarkdown. Copy and paste them into console to run.
if(fromscratch){
  #Optimal settings derived in 04_fuzzy_matcher_stage_1
  
  library(textreuse)
  stemmed_ab_spaced <- sapply(strsplit(stemmed_ab, split="") , paste, collapse=" ")
  minhash <- minhash_generator(n = minhash_count, seed = 1)
  options("mc.cores" = parallel::detectCores()) #Much faster when paralized
  #options("mc.cores" = 1) #Much faster when paralized
  #This doesn't like to be ran in rmarkdown, it'll spawn processes and hang. Maybe see if it'll work as a function?
  
  corpus_ab_spaced <- TextReuseCorpus(text = stemmed_ab_spaced,
                                      tokenizer = tokenize_ngrams,
                                      n = 2, #wow if you pass this as a variable instead of a number it crashes. Wtf?
                                      minhash_func = minhash,
                                      keep_tokens = TRUE,
                                      progress = T)
  
  #buckets <- lsh(corpus_ab_spaced, bands = 80, progress = T) #Single threaded but should be parallizable
  
  n.cores <- parallel::detectCores()
  cuts <- cut(1:length(corpus_ab_spaced), n.cores)
  #buckets_list <- mclapply(levels(cuts),
  #               function(q) lsh(corpus_ab_spaced[cuts==q], bands = bands, progress = T) ,
  #               mc.cores = n.cores)
  #buckets <- do.call(rbind, buckets_list)
  
  library(doParallel)
  library(foreach)
  cl<- makeCluster(parallel::detectCores()) #change the 2 to your number of CPU cores
  registerDoParallel(cl)
  buckets <- foreach(q=levels(cuts), .combine='rbind', .packages=c('textreuse')) %dopar%  
                lsh(corpus_ab_spaced[cuts==q], bands = bands, progress =F)
  stopCluster(cl)
  
  candidates <- lsh_candidates(buckets)
  dim(candidates) #842,288 
  candidates$a_numeric <- as.numeric( gsub("doc-","",candidates$a) )
  candidates$b_numeric <- as.numeric( gsub("doc-","",candidates$b) )
  candidates$stemmed_a <- stemmed_ab[candidates$a_numeric]
  candidates$stemmed_b <- stemmed_ab[candidates$b_numeric]
  candidates$stemmed_ab <- paste(candidates$stemmed_a,candidates$stemmed_b, sep="_")
  candidates$stemmed_ba <- paste(candidates$stemmed_b,candidates$stemmed_a, sep="_")
  
  saveRDS(candidates, paste0(here::here(), "inst/extdata/candidates.Rdata")) #Leaving off the .. because you should only be running this by hand
  
}
candidates <- readRDS(system.file("extdata",
                                 "candidates.Rdata",
                                 package = "MeasuringLandscape"))
#head(candidates) %>% DT::datatable()

There are 1004273 candidate stem matches return by locality sensitive hashing with these parameters. This is a tiny 0.0031 fraction of all the possible stem to stem matches we could have considered.

Expand Stem matches into full matches

We consider any two strings to be a possible match if we consider their stems to be a possible match. This bit of code expands and merges to get the suggestions in terms of event string- gazetteer string possible matches. Throughout we use the example of the stem “gura” to show possible matches and how they’re reduced over various steps.

ab_suggestions_events <- subset(ab_suggestions_events, name_cleaner_a %in% unique( events_sf$name_cleaner)  ) ; #dim(ab_suggestions_events)  #
glue::glue(stemmed_ab_suggestions_events %>% nrow() , " string matches")
152864 string matches
#DT::datatable(ab_suggestions_events[stemmed_a=="gura"])

Screen pairs that are too dissimilar

ab_suggestions_events[,temp_q_cos:= stringdist::stringsim(name_cleaner_a,name_cleaner_b,"cos", nthread=parallel::detectCores(),q=2),] #both fast and very good at sorting
ab_suggestions_events <- ab_suggestions_events[temp_q_cos>.3] ; #dim(ab_suggestions_events) #less than .3 is never match
glue::glue(ab_suggestions_events %>% nrow() , " string matches after removing those with too small a ngram2 cosine distance")
103311 string matches after removing those with too small a ngram2 cosine distance

XGBoost Toponym Matching

Next, we refine the remaining matches using additional string distance features and an XGBoost model trained earlier on the labeled match/no match toponym training examples.

ab_suggestions_events$a <- ab_suggestions_events$stemmed_a
ab_suggestions_events$b <- ab_suggestions_events$stemmed_b
ab_suggestions_events_features <- MeasuringLandscape:::toponym_add_features(ab_suggestions_events)
[1] "1 of 24"
[1] "2 of 24"
[1] "3 of 24"
[1] "4 of 24"
[1] "5 of 24"
[1] "6 of 24"
[1] "7 of 24"
[1] "8 of 24"
[1] "9 of 24"
[1] "10 of 24"
[1] "11 of 24"
[1] "12 of 24"
[1] "13 of 24"
[1] "14 of 24"
[1] "15 of 24"
[1] "16 of 24"
[1] "17 of 24"
[1] "18 of 24"
[1] "19 of 24"
[1] "20 of 24"
[1] "21 of 24"
[1] "22 of 24"
[1] "23 of 24"
[1] "24 of 24"
vars_x_string <- c(
    "Jaro",
    "Optimal_String_Alignment"    ,
    "Levenshtein",
    "Damerau_Levenshtein"    ,
     "Longest_Common_Substring"     ,
    "q_gram_1",
    "q_gram_2",
    "q_gram_3",
    "q_gram_4",
    "q_gram_5",
    'Cosine_1',
    'Cosine_2',
    'Cosine_3',
    'Cosine_4',
    'Cosine_5',
    "Jaccard"              ,
     "First_Mistmatch"         ,
    "a_nchar"     ,
    "b_nchar"   ,
    "ab_nchar_diff"       ,             
    "dJaro",
    "dOptimal_String_Alignment"      ,
    "dLevenshtein"     ,
    "dDamerau_Levenshtein"  ,           
    "dLongest_Common_Substring",
    "dq_gram",
    "dCosine",
    "dJaccard"
) 
dpredict<-xgboost::xgb.DMatrix(data= as.matrix(ab_suggestions_events_features[,vars_x_string, with=F]), missing = NA)
#Step 4 Predict the likelihood that two strings are same
ab_suggestions_events_features$toponym_xb_model_prediction <- predict( toponym_xb_model, dpredict )
ab_suggestions_events_features$toponym_xb_model_prediction  <- 1/(1 + exp(-ab_suggestions_events_features$toponym_xb_model_prediction ))
# why does this work now but not in 06
hist(ab_suggestions_events_features$toponym_xb_model_prediction)

ab_suggestions_small <- ab_suggestions_events_features[,c('a','b','name_cleaner_a','name_cleaner_b','toponym_xb_model_prediction',"N")]
ab_suggestions_directed <- ab_suggestions_small
ab_suggestions_directed$a_event <- ab_suggestions_directed$name_cleaner_a %in% events_sf$name_cleaner
ab_suggestions_directed <- subset(ab_suggestions_directed, a_event) #just to double check, should be no change
ab_suggestions_directed$b_event <- ab_suggestions_directed$name_cleaner_b %in% events_sf$name_cleaner
ab_suggestions_directed$b_event_coord <- ab_suggestions_directed$name_cleaner_b %in% flatfiles_sf_roi$name_cleaner[flatfiles_sf_roi$source_dataset %in% c('events') & !is.na(flatfiles_sf_roi$latitude)]
ab_suggestions_directed$b_gaz_coord   <- ab_suggestions_directed$name_cleaner_b %in% flatfiles_sf_roi$name_cleaner[!flatfiles_sf_roi$source_dataset %in% c('events')  & !is.na(flatfiles_sf_roi$latitude) ]
hist(ab_suggestions_directed$toponym_xb_model_prediction) #before subsetting

ab_suggestions_directed <- ab_suggestions_directed[a_event & (b_event_coord|b_gaz_coord)]
ab_suggestions_directed <- ab_suggestions_directed[toponym_xb_model_prediction>.5 | name_cleaner_a==name_cleaner_b] #Only keep those with greater .5 chance of match
hist(ab_suggestions_directed$toponym_xb_model_prediction) #after subsetting

ab_suggestions_directed[,toponym_xb_model_prediction_max:=max(toponym_xb_model_prediction),
                        by=c('name_cleaner_a','b_gaz_coord')]
ab_suggestions_directed_max_gaz_coord <- subset(ab_suggestions_directed,
                                                b_gaz_coord &
                                                  toponym_xb_model_prediction==toponym_xb_model_prediction_max
                                                )
ab_suggestions_directed_max_event_coord <- subset(ab_suggestions_directed,
                                                  b_event_coord &
                                                    toponym_xb_model_prediction==toponym_xb_model_prediction_max
                                                  )
#A non missing coordinate
condition <- !is.na(sf::st_coordinates(events_sf)[,1])
table(condition)
condition
FALSE  TRUE 
 4877  5592 
#An exact match to the gaz
condition2 <- events_sf$name_cleaner %in% flatfiles_sf_roi$name_cleaner[!flatfiles_sf_roi$source_dataset %in% c('events','events_poly')]
table(condition2)
condition2
FALSE  TRUE 
 6120  4349 
table(condition | condition2)

FALSE  TRUE 
 2881  7588 
#Fuzzy match to a gaz
condition3 <- events_sf$name_cleaner %in% ab_suggestions_directed_max_gaz_coord$name_cleaner_a 
table(condition3)
condition3
FALSE  TRUE 
 4090  6379 
table(condition | condition2 | condition3)

FALSE  TRUE 
 1959  8510 
table(condition | condition2 | condition3) / length(condition)

    FALSE      TRUE 
0.1871239 0.8128761 
#Exact match to another event
condition4 <- events_sf$name_cleaner %in% flatfiles_sf_roi$name_cleaner[flatfiles_sf_roi$source_dataset %in% c('events') & !is.na(flatfiles_sf_roi$latitude) ]
table(condition4)
condition4
FALSE  TRUE 
 3682  6787 
table(condition4) / length(condition)
condition4
   FALSE     TRUE 
0.351705 0.648295 
table(condition | condition2 | condition3 | condition4)

FALSE  TRUE 
 1772  8697 
table(condition | condition2 | condition3 | condition4) / length(condition)

    FALSE      TRUE 
0.1692616 0.8307384 
#Fuzzy match to event with coordinate
condition5 <- events_sf$name_cleaner %in% ab_suggestions_directed_max_event_coord$name_cleaner_a 
table(condition5)
condition5
FALSE  TRUE 
 3631  6838 
table(condition5) / length(condition)
condition5
    FALSE      TRUE 
0.3468335 0.6531665 
table(condition | condition2 | condition3 | condition4 | condition5)

FALSE  TRUE 
 1690  8779 
table(condition | condition2 | condition3 | condition4 | condition5) / length(condition)

   FALSE     TRUE 
0.161429 0.838571 
condition6 <- tolower(events_sf$document_district_clean) %in% flatfiles_sf_roi$name_cleaner[!flatfiles_sf_roi$source_dataset %in% c('events')]
table(condition6)
condition6
FALSE  TRUE 
   11 10458 
saveRDS(ab_suggestions_directed,
        file=glue::glue(getwd(), "/../inst/extdata/ab_suggestions_directed.Rds") 
        )

Given all of the above we want to produce a matching dataset broken out across

exact or fuzzy match place description or district/province

#For when this inevitably crashes
ab_suggestions_directed <-  readRDS(system.file("extdata", "ab_suggestions_directed.Rds", package = "MeasuringLandscape")) 
flatfiles_sf_roi_centroids <- flatfiles_sf_roi %>% filter(!is.na(latitude) | geometry_type != "POINT") %>% sf::st_centroid()
st_centroid assumes attributes are constant over geometries of xst_centroid does not give correct centroids for longitude/latitude data
#Do some quick accounting
#Create new dataset that includes
#1) Diads chosen by fuzzy ab_suggestions_directed 
#2) Diads of identical
georef <- data.table::setnames( ab_suggestions_directed[,c('name_cleaner_a','name_cleaner_b','toponym_xb_model_prediction')] , c('georef_a','georef_b','toponym_xb_model_prediction') ) #So this is now a list of names that are either identical or fuzzy matches for one another
georef$georef_a <- trimws(georef$georef_a)
georef$georef_b <- trimws(georef$georef_b)
georef <- unique(georef)
#georef$georef_ab_identical <- georef$georef_a==georef$georef_b 
table(events_sf$name_cleaner %in% georef$georef_a) #7,742 events with at least one gazetteer suggestion

FALSE  TRUE 
 2527  7942 
#This is mapping of events to identical and fuzzy
#georef2 is a combination of events, and all the gazetteer names they might match to
georef2 <- as.data.frame(events_sf)[,c('event_hash', 'name_cleaner','document_district_clean','document_unit_type','document_date_best_year')] %>% 
  dplyr::left_join(georef, by = c("name_cleaner" = "georef_a")) %>% unique()  
dim(georef2)
[1] 92002     7
table(events_sf$name_cleaner %in% georef2$name_cleaner) #All events are in here

 TRUE 
10469 
table(events_sf$name_cleaner %in% georef2$name_cleaner[!is.na(georef2$georef_b)]) #

FALSE  TRUE 
 2527  7942 
table(events_sf$event_hash %in% georef2$event_hash) #All event hashes show up in here

 TRUE 
10469 
#This is mapping of gaz places to identical and fuzzy possible
#georef3 is a combination of gazetteers and all gazetteer names they might match to
#georef3 <- as.data.frame(flatfiles_sf_roi_centroids)[,c('place_hash','source_dataset','name_cleaner','geometry_type')] %>% left_join(georef, by = c("name_cleaner" = "georef_b")) %>% unique() 
#dim(georef3)
#This is a mapping of events to gaz where either their exact or fuzzy counterparts matched each other
#georef all is a mapping of events, to gazetteer names, to 
georef_all <- georef2 %>% 
             dplyr::left_join(as.data.frame(flatfiles_sf_roi_centroids)[,c('place_hash','source_dataset','name_cleaner','geometry_type','feature_code')],
                       by = c("georef_b" = "name_cleaner")) %>% unique()
dim(georef_all)   #303,174
[1] 532711     11
  
table(events_sf$name_cleaner %in% georef_all$name_cleaner) #All events are in here

 TRUE 
10469 
table(events_sf$name_cleaner %in% georef_all$name_cleaner[!is.na(georef_all$georef_b)]) #

FALSE  TRUE 
 2527  7942 
table(events_sf$event_hash %in% georef_all$event_hash) #All event hashes show up in here

 TRUE 
10469 
table(georef_all$source_dataset)

                    bing                   events                     gadm                 geonames                   google               historical          kenya_cadastral kenya_cadastral_district 
                    9436                   254162                    10729                    60988                    10591                    49962                      412                     1033 
      kenya_district1962     livestock_boundaries         livestock_points                      nga            openstreetmap                      tgn                 wikidata 
                     676                     8738                    35978                    57180                    16040                     2830                    11429 
length(unique(georef_all$event_hash))
[1] 10469
georef_all$georef_a <- NULL

rr #Ok next up we add distance #crs_m <- +proj=utm +zone=27 +datum=NAD83 +units=m +no_defs  #this is toally wrong, stop using it #https://epsg.io/21037 #EPSG:21037 #Projected coordinate system #Arc 1960 / UTM zone 37S rownames(events_sf) <- events_sf$event_hash

Setting row names on a tibble is deprecated.

rr #events_sf_utm <- st_transform(events_sf[,c(‘event_hash’,)], crs=21037) # #events_sf_utm <- st_sf(as.data.frame(events_sf_utm)) #rownames(events_sf_utm) <- events_sf_utm\(event_hash #It was slow as hell as a tbl, covert to just sf #flatfiles_sf_roi_centroids_utm <- st_transform(flatfiles_sf_roi_centroids[,c('place_hash',\geometry\)], crs=21037) #rownames(flatfiles_sf_roi_centroids_utm) <- flatfiles_sf_roi_centroids_utm\)place_hash #Try some smaller experiments and verify if and how long it takes to calculate distance since we’ve got a million of them #This takes a long time to calculate because there’s a million of them #Instead, add a column for whether dimensions are zero and then queue that column with the hash, much much better events_sf\(isvalid <- !is.na(st_dimension(events_sf) ) flatfiles_sf_roi_centroids\)isvalid <- !is.na(st_dimension(flatfiles_sf_roi_centroids) ) cords_events <- as.data.frame( st_coordinates(events_sf) ) rownames(cords_events) <- rownames(events_sf) cords_flatfiles <- as.data.frame( st_coordinates(flatfiles_sf_roi_centroids) ) rownames(flatfiles_sf_roi_centroids) <- flatfiles_sf_roi_centroids\(place_hash rownames(cords_flatfiles) <- rownames(flatfiles_sf_roi_centroids) coords_dt <- as.data.table( cbind(cords_events[georef_all\)event_hash,], cords_flatfiles[georef_all$place_hash,])) names(coords_dt) <- c(1,1, 2, 2) coords_dt[,distance_km:=sqrt((X2-X1)2+(Y2-Y1)2) * 111] hist(coords_dt$distance)

rr georef_all$distance <- NULL georef_all_dt <- as.data.table(cbind(georef_all, coords_dt)) georef_all_dt <- unique(georef_all_dt); dim(georef_all_dt)

[1] 532711     16

rr #georef_all_dt <- subset(georef_all_dt, !is.na(name_cleaner) & !is.na(georef_b)) #maybe don’t drop unmatched ones? If we want events as a source? table(events_sf\(name_cleaner %in% georef_all_dt\)name_cleaner) #All events are in here


 TRUE 
10469 

rr table(events_sf\(name_cleaner %in% georef_all_dt\)name_cleaner[!is.na(georef_all_dt$georef_b)]) #7,742 events with at least one gazetteer suggestion


FALSE  TRUE 
 2527  7942 

rr table(events_sf\(event_hash %in% georef_all_dt\)event_hash) #All event hashes show up in here


 TRUE 
10469 

rr saveRDS(georef_all_dt, file=paste0(here::here(), /inst/extdata/georef_all_dt.Rds) )

LS0tCnRpdGxlOiAiMDUgR2VvcmVmZXJlbmNlciIKYXV0aG9yOiAiUmV4IFcuIERvdWdsYXNzIGFuZCBLcmlzdGVuIEhhcmtuZXNzIgpkYXRlOiAiTWFyY2ggOSwgMjAxOCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCjxzdHlsZT4KICAgIGJvZHkgLm1haW4tY29udGFpbmVyIHsKICAgICAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICB9Cjwvc3R5bGU+CgoKVGFrZXMgaW4gbG9jYXRpb25zIG9mIGV2ZW50cyBkZXNjcmliZWQgYXMgdGV4dCwgYW5kIHJldHVybnMgYWxsIHBvc3NpYmxlIG1hdGNoZXMgYWNyb3NzIGRpZmZlcmVudCBnYXpldHRlZXJzLgoKRGVwZW5kZW5jaWVzOiBldmVudHNfc2YuUmRhdGEsIGZsYXRmaWxlc19zZl9yb2kuUmRhdGEKUHJvZHVjdHM6IGdlb3JlZl9hbGxfZHQuUmRzCgpgYGB7ciwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJtKGxpc3Q9bHMoKSk7IGdjKCkKIyBIaWRpbmcgb3V0cHV0IGFuZCB3YXJuaW5ncwojICFkaWFnbm9zdGljcyBvZmYKbGlicmFyeShNZWFzdXJpbmdMYW5kc2NhcGUpCmxpYnJhcnkodGlkeXZlcnNlKQoKZGlyX2ZpZ3VyZXMgPC0gcGFzdGUwKGhlcmU6OmhlcmUoKSwgIi9wYXBlci9maWd1cmVzLyIpCgpnYygpCgprbml0cjo6b3B0c19rbml0JHNldChwcm9ncmVzcyA9IFRSVUUsIHZlcmJvc2UgPSBUUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTgsICB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFKQpvcHRpb25zKHdpZHRoID0gMTYwKQoKYGBgCgojIExvYWQgRmlsZXMKCldlIGxvYWQgdGhlIGV2ZW50cyBmaWxlIHRvIGJlIGdlb2xvY2F0ZWQuCgpgYGB7cn0KCiNMb2FkIEV2ZW50cwpldmVudHNfc2YgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXh0ZGF0YSIsICJldmVudHNfc2YuUmRhdGEiLCBwYWNrYWdlID0gIk1lYXN1cmluZ0xhbmRzY2FwZSIpKSAKCmV2ZW50c19zZl90ZXh0X2Nvb3JkX3VuaXF1ZSA8LSBwbHlyOjpkZHBseShldmVudHNfc2ZbLGMoJ2xvY2F0aW9uX3RleHQnLCduYW1lX2NsZWFuJywnbmFtZV9jbGVhbmVyJywnZG9jdW1lbnRfZGlzdHJpY3RfY2xlYW4nLCdtYXBfY29vcmRpbmF0ZV9jbGVhbl9sYXRpdHVkZScsJ21hcF9jb29yZGluYXRlX2NsZWFuX2xvbmdpdHVkZScpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsb2NhdGlvbl90ZXh0IiwgdHJhbnNmb3JtLAogICAgICBtYXBfY29vcmRpbmF0ZV9oYXMgPXN1bSghaXMubmEobWFwX2Nvb3JkaW5hdGVfY2xlYW5fbGF0aXR1ZGUpKQogICAgICApCgoKYGBgCgpXZSBsb2FkIHRoZSBjb21iaW5lZCBnYXpldHRlZXIgZmlsZXMuCgpgYGB7cn0KI0xvYWQgR2F6ZXR0ZWVycwpmbGF0ZmlsZXNfc2Zfcm9pIDwtIHJlYWRSRFMoc3lzdGVtLmZpbGUoImV4dGRhdGEiLCAiZmxhdGZpbGVzX3NmX3JvaS5SZGF0YSIsIHBhY2thZ2UgPSAiTWVhc3VyaW5nTGFuZHNjYXBlIikpIApkaW0oZmxhdGZpbGVzX3NmX3JvaSkKZmxhdGZpbGVzX2R0IDwtIGRhdGEudGFibGU6OmFzLmRhdGEudGFibGUoZmxhdGZpbGVzX3NmX3JvaSkKZGF0YS50YWJsZTo6c2V0a2V5KGZsYXRmaWxlc19kdCxwbGFjZV9oYXNoKQoKYGBgCgpXZSBsb2FkIGEgcHJldHJhaW5lZCB0b3BvbnltIG1hdGNoZXIgYmFzZWQgb24gWEdCb29zdCBhbmQgbWFueSBzdHJpbmcgZGlzdGFuY2UgZmVhdHVyZXMuCgpgYGB7cn0KCiNMb2FkIHRvcG9ueW0gbW9kZWwKI3BfbG9hZCh4Z2Jvb3N0KQp0b3BvbnltX3hiX21vZGVsIDwtIHhnYm9vc3Q6OnhnYi5sb2FkKHN5c3RlbS5maWxlKCJleHRkYXRhIiwgInRvcG9ueW1feGJfb25seXN0cmluZy5iaW4iLCBwYWNrYWdlID0gIk1lYXN1cmluZ0xhbmRzY2FwZSIpKQoKYGBgCgoKIyBTdW1tYXJ5IFN0YXRpc3RpY3MKCldoZW4gd2Ugc3RhcnQsIHdlIGhhdmUgYHIgbGVuZ3RoKHVuaXF1ZShldmVudHNfc2YkbmFtZV9jbGVhbmVyKSlgIHVuaXF1ZSBsb2NhdGlvbiBzdHJpbmdzIGluIHRoZSBldmVudHMgZGF0YS4KCmBgYHtyfQojZXZlbnRzX3NmJG5hbWVfY2xlYW5lciAlPiUgdGFieWwoc29ydD1UKQpgYGAKCldoZW4gd2Ugc3RhcnQsIHdlIGhhdmUgYHIgbGVuZ3RoKHVuaXF1ZShmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lcikpYCB1bmlxdWUgbG9jYXRpb24gc3RyaW5ncyBpbiB0aGUgZ2F6ZXR0ZWVyIGRhdGEuCgpgYGB7cn0KI2ZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyICU+JSB0YWJ5bChzb3J0PVQpCgpgYGAKCklmIHdlIGNvbnNpZGVyZWQgZXZlcnkgc2luZ2xlIHBhaXIgb2YgcG9zc2libGUgbWF0Y2hlcyBpdCB3b3VsZCByZXF1aXJlIGFib3V0IDYyIG1pbGxpb24gY29tcGFyaXNvbnMgKGByIGxlbmd0aCh1bmlxdWUoZXZlbnRzX3NmJG5hbWVfY2xlYW5lcikpICogbGVuZ3RoKHVuaXF1ZShmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lcikpYCkuIEFuZCB0aGF0IGlzIGp1c3QgZm9yIG91ciByZWxhdGl2ZWx5IHNtYWxsIG51bWJlciBvZiBldmVudCBsb2NhdGlvbiBzdHJpbmdzIGFuZCByZXN0cmljdGluZyBvdXIgZ2F6ZXR0ZWVycyB0byBhIHJlbGF0aXZlbHkgc21hbGwgcmVnaW9uIG9mIGludGVyZXN0LiBPYnZpb3VzbHksIGFueSBhcHByb2FjaCB3ZSBpbXBsZW1lbnQgaXMgZ29pbmcgdG8gaGF2ZSB0byBzY2FsZSBzdWItcXVhZHJhdGljIGluIHRoZSBudW1iZXIgb2YgbG9jYXRpb25zIG9yIGl0IHdvbid0IGJlIGFwcGxpY2FibGUgdG8gYW55IHByb2JsZW0gb2YgaW50ZXJlc3Rpbmcgc2l6ZS4KCiMgU3RlbQoKT3VyIGZpcnN0IG1vdmUgaXMgdG8gc3RlbSBlYWNoIGxvY2F0aW9uIHN0cmluZyBvZiBwcmVmaXhlcyBhbmQgY29tbW9uIHN1ZmZpeGVzIHNvIHRoYXQgd2UgY2FuIG1hdGNoIHNsaWdodCB2YXJpYXRpb25zIG9mIHRoZSBzYW1lIHBsYWNlIHRvIGEgc2luZ2xlIGlkZW50aWZpZXIuIAoKYGBge3J9CgojU3RlcCAxICBHZXQgU3VnZ2VzdGlvbnMgZm9yIFN0ZW1zCgojU3RlbSB0aGUgZ2F6ZXR0ZWVyIG5hbWVzCnRlbXAgPC0gTWVhc3VyaW5nTGFuZHNjYXBlOjo6c3RyaXBfcG9zdGZpeGVzKGZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyKQpmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lcl9zdGVtIDwtIHRlbXBbWzFdXQpmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lcl9wb3N0Zml4IDwtIHRlbXBbWzJdXQoKI2ZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyX3N0ZW0gJT4lIHRhYnlsKHNvcnQ9VCkKCmBgYAoKVGhlcmUgYXJlIGByIGxlbmd0aCh1bmlxdWUoZmxhdGZpbGVzX3NmX3JvaSRuYW1lX2NsZWFuZXJfc3RlbSkpYCB1bmlxdWUgbG9jYXRpb24gc3RlbXMgaW4gdGhlIGZsYXRmaWxlcyBkYXRhLgoKYGBge3J9CiNTdGVtIHRoZSBldmVudCBuYW1lcwp0ZW1wIDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OnN0cmlwX3Bvc3RmaXhlcyhldmVudHNfc2YkbmFtZV9jbGVhbmVyKQpldmVudHNfc2YkbmFtZV9jbGVhbmVyX3N0ZW0gPC0gdGVtcFtbMV1dCmV2ZW50c19zZiRuYW1lX2NsZWFuZXJfcG9zdGZpeCA8LSB0ZW1wW1syXV0KCiNldmVudHNfc2YkbmFtZV9jbGVhbmVyICU+JSB0YWJ5bChzb3J0PVQpCgpgYGAKClRoZXJlIGFyZSBgciBsZW5ndGgodW5pcXVlKGV2ZW50c19zZiRuYW1lX2NsZWFuZXJfc3RlbSkpYCB1bmlxdWUgbG9jYXRpb24gc3RlbXMgaW4gdGhlIGV2ZW50cyBkYXRhLgogCgpgYGB7cn0KI1VuaXF1ZSBzdGVtcwpzdGVtbWVkX2FiIDwtIHVuaXF1ZShjKGZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyX3N0ZW0sIGV2ZW50c19zZiRuYW1lX2NsZWFuZXJfc3RlbSkpCmxlbmd0aChzdGVtbWVkX2FiKSAjMTcsODgwIHVuaXF1ZSBzdGVtcwoKI1VuaXF1ZSBuYW1lLXN0ZW0gcGFpcnMKbmFtZXNfYW5kX3N0ZW1zIDwtIHJiaW5kKAogICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoZmxhdGZpbGVzX3NmX3JvaSlbLGMoJ25hbWVfY2xlYW5lcicsJ25hbWVfY2xlYW5lcl9zdGVtJyldLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoZXZlbnRzX3NmKVssYygnbmFtZV9jbGVhbmVyJywnbmFtZV9jbGVhbmVyX3N0ZW0nKV0KICAgICAgICAgICAgICAgICAgICAgICAgKSAlPiUgdW5pcXVlKCkgOyBkaW0obmFtZXNfYW5kX3N0ZW1zKQoKYGBgCgojIExTSCAKCk91ciBuZXh0IG1vdmUgaXMgaGFzaCBlYWNoIHN0ZW1tZWQgbG9jYXRpb24gaW4gYSB3YXkgdGhhdCBsb2NhdGlvbnMgdGhhdCBhcmUgc29tZXdoYXQgc2ltaWxhciB0byBvbmUgYW5vdGhlciBhcmUgcmV0dXJuZWQgYXMgcG9zc2libGUgbWF0Y2hlcyBhbmQgdGhlIHZhc3QgbWFqb3JpdHkgb2YgcGFpcnMgdGhhdCBhcmUgdG9vIGRpc3NpbWlsYXIgdG8gZXZlciBwb3NzaWJseSBtYXRjaCBhcmUgZXhjbHVkZWQuIFRoaXMgcmVxdWlyZXMgb25seSBhIHNpbmdsZSBwYXNzIHRocm91Z2ggdGhlIGRhdGEsIGFuZCBzbyBzY2FsZXMgbGluZWFybHkgaW4gdGhlIG51bWJlciBvZiBsb2NhdGlvbnMuIFdlIHNlbGVjdCBwYXJhbWV0ZXJzIHdoaWNoIHBlcmZvcm0gd2VsbCBvbiBvdXIgaGFuZC1sYWJlbGVkIHRyYWluaW5nIGRhdGFzZXQgb2YgdG9wb255bSBtYXRjaGVzIGFuZCBtaXNtYXRjaGVzLiBXaGVyZSBwZXJmb3JtaW5nIHdlbGwgYXQgdGhpcyBzdGFnZSBtZWFucyBhIGxvdyBmYWxzZSBuZWdhdGl2ZSByYXRlIChtaXNzaW5nIGZldyBnZW51aW5lIG1hdGNoZXMpIGFuZCBhIG1vZGVyYXRlIGZhbHNlIHBvc2l0aXZlIHJhdGUgKHJldHVybmluZyBtYW55IGlycmVsZXZhbnQgbWF0Y2hlcywgYnV0IG5vdCBhbiBvdmVyd2hlbG1pbmcgbnVtYmVyKS4gSGVyZSB3ZSBhcmUgb25seSB0cnlpbmcgdG8gcmVqZWN0IGFzIG1hbnkgb2J2aW91cyBub25tYXRjaGVzIGFzIHBvc3NpYmxlLgoKYGBge3J9CgptaW5oYXNoX2NvdW50PTEwMApiYW5kcz0yNQoKZnJvbXNjcmF0Y2g9RiAjIEZvciBzb21lIHJlYXNvbiB0aGVzZSBwYXJhbGxlbCBmdW5jdGlvbnMgaGFuZyBpbiBybWFya2Rvd24uIENvcHkgYW5kIHBhc3RlIHRoZW0gaW50byBjb25zb2xlIHRvIHJ1bi4KaWYoZnJvbXNjcmF0Y2gpewogICNPcHRpbWFsIHNldHRpbmdzIGRlcml2ZWQgaW4gMDRfZnV6enlfbWF0Y2hlcl9zdGFnZV8xCiAgCiAgbGlicmFyeSh0ZXh0cmV1c2UpCiAgc3RlbW1lZF9hYl9zcGFjZWQgPC0gc2FwcGx5KHN0cnNwbGl0KHN0ZW1tZWRfYWIsIHNwbGl0PSIiKSAsIHBhc3RlLCBjb2xsYXBzZT0iICIpCiAgbWluaGFzaCA8LSBtaW5oYXNoX2dlbmVyYXRvcihuID0gbWluaGFzaF9jb3VudCwgc2VlZCA9IDEpCiAgb3B0aW9ucygibWMuY29yZXMiID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkpICNNdWNoIGZhc3RlciB3aGVuIHBhcmFsaXplZAogICNvcHRpb25zKCJtYy5jb3JlcyIgPSAxKSAjTXVjaCBmYXN0ZXIgd2hlbiBwYXJhbGl6ZWQKICAjVGhpcyBkb2Vzbid0IGxpa2UgdG8gYmUgcmFuIGluIHJtYXJrZG93biwgaXQnbGwgc3Bhd24gcHJvY2Vzc2VzIGFuZCBoYW5nLiBNYXliZSBzZWUgaWYgaXQnbGwgd29yayBhcyBhIGZ1bmN0aW9uPwogIAogIGNvcnB1c19hYl9zcGFjZWQgPC0gVGV4dFJldXNlQ29ycHVzKHRleHQgPSBzdGVtbWVkX2FiX3NwYWNlZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b2tlbml6ZXIgPSB0b2tlbml6ZV9uZ3JhbXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbiA9IDIsICN3b3cgaWYgeW91IHBhc3MgdGhpcyBhcyBhIHZhcmlhYmxlIGluc3RlYWQgb2YgYSBudW1iZXIgaXQgY3Jhc2hlcy4gV3RmPwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbmhhc2hfZnVuYyA9IG1pbmhhc2gsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VlcF90b2tlbnMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzID0gVCkKICAKICAjYnVja2V0cyA8LSBsc2goY29ycHVzX2FiX3NwYWNlZCwgYmFuZHMgPSA4MCwgcHJvZ3Jlc3MgPSBUKSAjU2luZ2xlIHRocmVhZGVkIGJ1dCBzaG91bGQgYmUgcGFyYWxsaXphYmxlCiAgCiAgbi5jb3JlcyA8LSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKQogIGN1dHMgPC0gY3V0KDE6bGVuZ3RoKGNvcnB1c19hYl9zcGFjZWQpLCBuLmNvcmVzKQogICNidWNrZXRzX2xpc3QgPC0gbWNsYXBwbHkobGV2ZWxzKGN1dHMpLAogICMgICAgICAgICAgICAgICBmdW5jdGlvbihxKSBsc2goY29ycHVzX2FiX3NwYWNlZFtjdXRzPT1xXSwgYmFuZHMgPSBiYW5kcywgcHJvZ3Jlc3MgPSBUKSAsCiAgIyAgICAgICAgICAgICAgIG1jLmNvcmVzID0gbi5jb3JlcykKICAjYnVja2V0cyA8LSBkby5jYWxsKHJiaW5kLCBidWNrZXRzX2xpc3QpCiAgCiAgbGlicmFyeShkb1BhcmFsbGVsKQogIGxpYnJhcnkoZm9yZWFjaCkKICBjbDwtIG1ha2VDbHVzdGVyKHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpKSAjY2hhbmdlIHRoZSAyIHRvIHlvdXIgbnVtYmVyIG9mIENQVSBjb3JlcwogIHJlZ2lzdGVyRG9QYXJhbGxlbChjbCkKICBidWNrZXRzIDwtIGZvcmVhY2gocT1sZXZlbHMoY3V0cyksIC5jb21iaW5lPSdyYmluZCcsIC5wYWNrYWdlcz1jKCd0ZXh0cmV1c2UnKSkgJWRvcGFyJSAgCiAgICAgICAgICAgICAgICBsc2goY29ycHVzX2FiX3NwYWNlZFtjdXRzPT1xXSwgYmFuZHMgPSBiYW5kcywgcHJvZ3Jlc3MgPUYpCiAgc3RvcENsdXN0ZXIoY2wpCiAgCiAgY2FuZGlkYXRlcyA8LSBsc2hfY2FuZGlkYXRlcyhidWNrZXRzKQogIGRpbShjYW5kaWRhdGVzKSAjODQyLDI4OCAKICBjYW5kaWRhdGVzJGFfbnVtZXJpYyA8LSBhcy5udW1lcmljKCBnc3ViKCJkb2MtIiwiIixjYW5kaWRhdGVzJGEpICkKICBjYW5kaWRhdGVzJGJfbnVtZXJpYyA8LSBhcy5udW1lcmljKCBnc3ViKCJkb2MtIiwiIixjYW5kaWRhdGVzJGIpICkKICBjYW5kaWRhdGVzJHN0ZW1tZWRfYSA8LSBzdGVtbWVkX2FiW2NhbmRpZGF0ZXMkYV9udW1lcmljXQogIGNhbmRpZGF0ZXMkc3RlbW1lZF9iIDwtIHN0ZW1tZWRfYWJbY2FuZGlkYXRlcyRiX251bWVyaWNdCiAgY2FuZGlkYXRlcyRzdGVtbWVkX2FiIDwtIHBhc3RlKGNhbmRpZGF0ZXMkc3RlbW1lZF9hLGNhbmRpZGF0ZXMkc3RlbW1lZF9iLCBzZXA9Il8iKQogIGNhbmRpZGF0ZXMkc3RlbW1lZF9iYSA8LSBwYXN0ZShjYW5kaWRhdGVzJHN0ZW1tZWRfYixjYW5kaWRhdGVzJHN0ZW1tZWRfYSwgc2VwPSJfIikKICAKICBzYXZlUkRTKGNhbmRpZGF0ZXMsIHBhc3RlMChoZXJlOjpoZXJlKCksICJpbnN0L2V4dGRhdGEvY2FuZGlkYXRlcy5SZGF0YSIpKSAjTGVhdmluZyBvZmYgdGhlIC4uIGJlY2F1c2UgeW91IHNob3VsZCBvbmx5IGJlIHJ1bm5pbmcgdGhpcyBieSBoYW5kCiAgCn0KCmBgYAoKCmBgYHtyfQoKY2FuZGlkYXRlcyA8LSByZWFkUkRTKHN5c3RlbS5maWxlKCJleHRkYXRhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNhbmRpZGF0ZXMuUmRhdGEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWNrYWdlID0gIk1lYXN1cmluZ0xhbmRzY2FwZSIpKQoKCgojaGVhZChjYW5kaWRhdGVzKSAlPiUgRFQ6OmRhdGF0YWJsZSgpCgpgYGAKClRoZXJlIGFyZSBgciBucm93KGNhbmRpZGF0ZXMpYCBjYW5kaWRhdGUgc3RlbSBtYXRjaGVzIHJldHVybiBieSBsb2NhbGl0eSBzZW5zaXRpdmUgaGFzaGluZyB3aXRoIHRoZXNlIHBhcmFtZXRlcnMuIFRoaXMgaXMgYSB0aW55IGByIHJvdW5kKG5yb3coY2FuZGlkYXRlcykvbGVuZ3RoKHN0ZW1tZWRfYWIpXjIsNClgIGZyYWN0aW9uIG9mIGFsbCB0aGUgcG9zc2libGUgc3RlbSB0byBzdGVtIG1hdGNoZXMgd2UgY291bGQgaGF2ZSBjb25zaWRlcmVkLgoKCiMjIEV4cGFuZCBTdGVtIG1hdGNoZXMgaW50byBmdWxsIG1hdGNoZXMKCldlIGNvbnNpZGVyIGFueSB0d28gc3RyaW5ncyB0byBiZSBhIHBvc3NpYmxlIG1hdGNoIGlmIHdlIGNvbnNpZGVyIHRoZWlyIHN0ZW1zIHRvIGJlIGEgcG9zc2libGUgbWF0Y2guIFRoaXMgYml0IG9mIGNvZGUgZXhwYW5kcyBhbmQgbWVyZ2VzIHRvIGdldCB0aGUgc3VnZ2VzdGlvbnMgaW4gdGVybXMgb2YgZXZlbnQgc3RyaW5nLSBnYXpldHRlZXIgc3RyaW5nIHBvc3NpYmxlIG1hdGNoZXMuIFRocm91Z2hvdXQgd2UgdXNlIHRoZSBleGFtcGxlIG9mIHRoZSBzdGVtICJndXJhIiB0byBzaG93IHBvc3NpYmxlIG1hdGNoZXMgYW5kIGhvdyB0aGV5J3JlIHJlZHVjZWQgb3ZlciB2YXJpb3VzIHN0ZXBzLgoKYGBge3J9CgpzdGVtbWVkX2FiX3N1Z2dlc3Rpb25zIDwtIHN1YnNldChjYW5kaWRhdGVzICwgc3RlbW1lZF9hIT0iIiAmICBzdGVtbWVkX2IhPSIiKSA7ICNkaW0oc3RlbW1lZF9hYl9zdWdnZXN0aW9ucykgI2Ryb3AgZW1wdHkgc3RlbXMuIElmIG5vdCBpdCdsbCBjcmFzaCBmaXJzdCBtYXRjaCBsYXRlciBvbgpzdGVtbWVkX2FiX3N1Z2dlc3Rpb25zJE4gPC0gMQpnbHVlOjpnbHVlKHN0ZW1tZWRfYWJfc3VnZ2VzdGlvbnMgJT4lIG5yb3coKSAsICIgc3RyaW5nIG1hdGNoZXMiKQoKI0xpbmsgYSBzdGVtbWVkX2EgdG8gc3RlbW1lZCBiCnN0ZW1tZWRfYWJfc3VnZ2VzdGlvbnNfZXZlbnRzIDwtIGRhdGEudGFibGU6OnJiaW5kbGlzdChsaXN0KAogIHN0ZW1tZWRfYWJfc3VnZ2VzdGlvbnNbLGMoJ3N0ZW1tZWRfYScsJ3N0ZW1tZWRfYicsJ04nKV0sCiAgZGF0YS50YWJsZTo6c2V0bmFtZXMoc3RlbW1lZF9hYl9zdWdnZXN0aW9uc1ssYygnc3RlbW1lZF9hJywnc3RlbW1lZF9iJywnTicpXSwgYygnc3RlbW1lZF9iJywnc3RlbW1lZF9hJywnTicpICApLCAKICBkYXRhLnRhYmxlOjpzZXRuYW1lcyhzdGVtbWVkX2FiX3N1Z2dlc3Rpb25zWyxjKCdzdGVtbWVkX2EnLCdzdGVtbWVkX2EnLCdOJyldLCBjKCdzdGVtbWVkX2InLCdzdGVtbWVkX2InLCdOJykgICkgI21ha2Ugc3VyZSBpdCdzIGEgc3VnZ2VzdGlvbiBmb3IgaXRzZWxmCikpIDsgZGltKHN0ZW1tZWRfYWJfc3VnZ2VzdGlvbnNfZXZlbnRzKQoKc3RlbW1lZF9hYl9zdWdnZXN0aW9uc19ldmVudHMgPC0gdW5pcXVlKHN0ZW1tZWRfYWJfc3VnZ2VzdGlvbnNfZXZlbnRzKSA7ICNkaW0oc3RlbW1lZF9hYl9zdWdnZXN0aW9uc19ldmVudHMpICM4NTksMTE4CgoKI09ubHkga2VlcCBpZiB0aGUgYSBpcyBhIHN0ZW0gZm91bmQgaW4gdGhlIGV2ZW50cyBkYXRhCnN0ZW1tZWRfYWJfc3VnZ2VzdGlvbnNfZXZlbnRzIDwtIHN1YnNldChzdGVtbWVkX2FiX3N1Z2dlc3Rpb25zX2V2ZW50cywgc3RlbW1lZF9hICVpbiUgdW5pcXVlKGV2ZW50c19zZiRuYW1lX2NsZWFuZXJfc3RlbSkpIDsgI2RpbShzdGVtbWVkX2FiX3N1Z2dlc3Rpb25zX2V2ZW50cykgICMxNDgsNDE2CmdsdWU6OmdsdWUoc3RlbW1lZF9hYl9zdWdnZXN0aW9uc19ldmVudHMgJT4lIG5yb3coKSAsICIgc3RyaW5nIG1hdGNoZXMiKQoKI1N0ZXAgMiBQdWxsIGFsbCBmdWxsCmFiX3N1Z2dlc3Rpb25zX2V2ZW50cyA8LSBtZXJnZShzdGVtbWVkX2FiX3N1Z2dlc3Rpb25zX2V2ZW50cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEudGFibGU6OnNldG5hbWVzKG5hbWVzX2FuZF9zdGVtcywgYygibmFtZV9jbGVhbmVyX2EiLCJuYW1lX2NsZWFuZXJfc3RlbSIpKSAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieS54PSJzdGVtbWVkX2EiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkueT0ibmFtZV9jbGVhbmVyX3N0ZW0iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxsLng9VCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsbG93LmNhcnRlc2lhbj1UUlVFKSA7ICNkaW0oYWJfc3VnZ2VzdGlvbnNfZXZlbnRzKQojRFQ6OmRhdGF0YWJsZShhYl9zdWdnZXN0aW9uc19ldmVudHNbc3RlbW1lZF9hPT0iZ3VyYSJdKQoKI01ha2Ugc3VyZSB0aGUgZnVsbCBuYW1lIGFwcGVhcnMgaW4gdGhlIGV2ZW50IGRhdGEKYWJfc3VnZ2VzdGlvbnNfZXZlbnRzIDwtIHN1YnNldChhYl9zdWdnZXN0aW9uc19ldmVudHMsIG5hbWVfY2xlYW5lcl9hICVpbiUgdW5pcXVlKCBldmVudHNfc2YkbmFtZV9jbGVhbmVyKSAgKSA7ICNkaW0oYWJfc3VnZ2VzdGlvbnNfZXZlbnRzKSAgIwpnbHVlOjpnbHVlKHN0ZW1tZWRfYWJfc3VnZ2VzdGlvbnNfZXZlbnRzICU+JSBucm93KCkgLCAiIHN0cmluZyBtYXRjaGVzIikKI0RUOjpkYXRhdGFibGUoYWJfc3VnZ2VzdGlvbnNfZXZlbnRzW3N0ZW1tZWRfYT09Imd1cmEiXSkKCmFiX3N1Z2dlc3Rpb25zX2V2ZW50cyA8LSBtZXJnZShhYl9zdWdnZXN0aW9uc19ldmVudHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLnRhYmxlOjpzZXRuYW1lcyhuYW1lc19hbmRfc3RlbXMsIGMoIm5hbWVfY2xlYW5lcl9iIiwibmFtZV9jbGVhbmVyX3N0ZW0iKSkgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkueD0ic3RlbW1lZF9iIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5Lnk9Im5hbWVfY2xlYW5lcl9zdGVtIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsbC54PVQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGxvdy5jYXJ0ZXNpYW49VFJVRSkgIDsgI2RpbShhYl9zdWdnZXN0aW9uc19ldmVudHMpICM0LDg0NiwwNTQKCmFiX3N1Z2dlc3Rpb25zX2V2ZW50cyA8LSBzdWJzZXQoYWJfc3VnZ2VzdGlvbnNfZXZlbnRzLCBuYW1lX2NsZWFuZXJfYSAlaW4lIHVuaXF1ZSggZXZlbnRzX3NmJG5hbWVfY2xlYW5lcikgICkgOyAjZGltKGFiX3N1Z2dlc3Rpb25zX2V2ZW50cykgICMKZ2x1ZTo6Z2x1ZShzdGVtbWVkX2FiX3N1Z2dlc3Rpb25zX2V2ZW50cyAlPiUgbnJvdygpICwgIiBzdHJpbmcgbWF0Y2hlcyIpCiNEVDo6ZGF0YXRhYmxlKGFiX3N1Z2dlc3Rpb25zX2V2ZW50c1tzdGVtbWVkX2E9PSJndXJhIl0pCgpgYGAKCgojIFNjcmVlbiBwYWlycyB0aGF0IGFyZSB0b28gZGlzc2ltaWxhcgoKYGBge3J9CmFiX3N1Z2dlc3Rpb25zX2V2ZW50c1ssdGVtcF9xX2Nvczo9IHN0cmluZ2Rpc3Q6OnN0cmluZ3NpbShuYW1lX2NsZWFuZXJfYSxuYW1lX2NsZWFuZXJfYiwiY29zIiwgbnRocmVhZD1wYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSxxPTIpLF0gI2JvdGggZmFzdCBhbmQgdmVyeSBnb29kIGF0IHNvcnRpbmcKYWJfc3VnZ2VzdGlvbnNfZXZlbnRzIDwtIGFiX3N1Z2dlc3Rpb25zX2V2ZW50c1t0ZW1wX3FfY29zPi4zXSA7ICNkaW0oYWJfc3VnZ2VzdGlvbnNfZXZlbnRzKSAjbGVzcyB0aGFuIC4zIGlzIG5ldmVyIG1hdGNoCmdsdWU6OmdsdWUoYWJfc3VnZ2VzdGlvbnNfZXZlbnRzICU+JSBucm93KCkgLCAiIHN0cmluZyBtYXRjaGVzIGFmdGVyIHJlbW92aW5nIHRob3NlIHdpdGggdG9vIHNtYWxsIGEgbmdyYW0yIGNvc2luZSBkaXN0YW5jZSIpCgojRFQ6OmRhdGF0YWJsZShhYl9zdWdnZXN0aW9uc19ldmVudHNbc3RlbW1lZF9hPT0iZ3VyYSJdKQoKI2xpYnJhcnkoRFQpCiNEVDo6ZGF0YXRhYmxlKGFiX3N1Z2dlc3Rpb25zX2V2ZW50c1tzdGVtbWVkX2E9PSJndXJhIl0pCgpgYGAKCgojIFhHQm9vc3QgVG9wb255bSBNYXRjaGluZwoKTmV4dCwgd2UgcmVmaW5lIHRoZSByZW1haW5pbmcgbWF0Y2hlcyB1c2luZyBhZGRpdGlvbmFsIHN0cmluZyBkaXN0YW5jZSBmZWF0dXJlcyBhbmQgYW4gWEdCb29zdCBtb2RlbCB0cmFpbmVkIGVhcmxpZXIgb24gdGhlIGxhYmVsZWQgbWF0Y2gvbm8gbWF0Y2ggdG9wb255bSB0cmFpbmluZyBleGFtcGxlcy4KCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OH0KYWJfc3VnZ2VzdGlvbnNfZXZlbnRzJGEgPC0gYWJfc3VnZ2VzdGlvbnNfZXZlbnRzJHN0ZW1tZWRfYQphYl9zdWdnZXN0aW9uc19ldmVudHMkYiA8LSBhYl9zdWdnZXN0aW9uc19ldmVudHMkc3RlbW1lZF9iCgphYl9zdWdnZXN0aW9uc19ldmVudHNfZmVhdHVyZXMgPC0gTWVhc3VyaW5nTGFuZHNjYXBlOjo6dG9wb255bV9hZGRfZmVhdHVyZXMoYWJfc3VnZ2VzdGlvbnNfZXZlbnRzKQoKdmFyc194X3N0cmluZyA8LSBjKAogICAgIkphcm8iLAogICAgIk9wdGltYWxfU3RyaW5nX0FsaWdubWVudCIgICAgLAogICAgIkxldmVuc2h0ZWluIiwKICAgICJEYW1lcmF1X0xldmVuc2h0ZWluIiAgICAsCiAgICAgIkxvbmdlc3RfQ29tbW9uX1N1YnN0cmluZyIgICAgICwKICAgICJxX2dyYW1fMSIsCiAgICAicV9ncmFtXzIiLAogICAgInFfZ3JhbV8zIiwKICAgICJxX2dyYW1fNCIsCiAgICAicV9ncmFtXzUiLAogICAgJ0Nvc2luZV8xJywKICAgICdDb3NpbmVfMicsCiAgICAnQ29zaW5lXzMnLAogICAgJ0Nvc2luZV80JywKICAgICdDb3NpbmVfNScsCiAgICAiSmFjY2FyZCIgICAgICAgICAgICAgICwKICAgICAiRmlyc3RfTWlzdG1hdGNoIiAgICAgICAgICwKICAgICJhX25jaGFyIiAgICAgLAogICAgImJfbmNoYXIiICAgLAogICAgImFiX25jaGFyX2RpZmYiICAgICAgICwgICAgICAgICAgICAgCiAgICAiZEphcm8iLAogICAgImRPcHRpbWFsX1N0cmluZ19BbGlnbm1lbnQiICAgICAgLAogICAgImRMZXZlbnNodGVpbiIgICAgICwKICAgICJkRGFtZXJhdV9MZXZlbnNodGVpbiIgICwgICAgICAgICAgIAogICAgImRMb25nZXN0X0NvbW1vbl9TdWJzdHJpbmciLAogICAgImRxX2dyYW0iLAogICAgImRDb3NpbmUiLAogICAgImRKYWNjYXJkIgopIAoKZHByZWRpY3Q8LXhnYm9vc3Q6OnhnYi5ETWF0cml4KGRhdGE9IGFzLm1hdHJpeChhYl9zdWdnZXN0aW9uc19ldmVudHNfZmVhdHVyZXNbLHZhcnNfeF9zdHJpbmcsIHdpdGg9Rl0pLCBtaXNzaW5nID0gTkEpCgojU3RlcCA0IFByZWRpY3QgdGhlIGxpa2VsaWhvb2QgdGhhdCB0d28gc3RyaW5ncyBhcmUgc2FtZQphYl9zdWdnZXN0aW9uc19ldmVudHNfZmVhdHVyZXMkdG9wb255bV94Yl9tb2RlbF9wcmVkaWN0aW9uIDwtIHByZWRpY3QoIHRvcG9ueW1feGJfbW9kZWwsIGRwcmVkaWN0ICkKYWJfc3VnZ2VzdGlvbnNfZXZlbnRzX2ZlYXR1cmVzJHRvcG9ueW1feGJfbW9kZWxfcHJlZGljdGlvbiAgPC0gMS8oMSArIGV4cCgtYWJfc3VnZ2VzdGlvbnNfZXZlbnRzX2ZlYXR1cmVzJHRvcG9ueW1feGJfbW9kZWxfcHJlZGljdGlvbiApKQoKIyB3aHkgZG9lcyB0aGlzIHdvcmsgbm93IGJ1dCBub3QgaW4gMDYKaGlzdChhYl9zdWdnZXN0aW9uc19ldmVudHNfZmVhdHVyZXMkdG9wb255bV94Yl9tb2RlbF9wcmVkaWN0aW9uKQoKCmFiX3N1Z2dlc3Rpb25zX3NtYWxsIDwtIGFiX3N1Z2dlc3Rpb25zX2V2ZW50c19mZWF0dXJlc1ssYygnYScsJ2InLCduYW1lX2NsZWFuZXJfYScsJ25hbWVfY2xlYW5lcl9iJywndG9wb255bV94Yl9tb2RlbF9wcmVkaWN0aW9uJywiTiIpXQphYl9zdWdnZXN0aW9uc19kaXJlY3RlZCA8LSBhYl9zdWdnZXN0aW9uc19zbWFsbAphYl9zdWdnZXN0aW9uc19kaXJlY3RlZCRhX2V2ZW50IDwtIGFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkJG5hbWVfY2xlYW5lcl9hICVpbiUgZXZlbnRzX3NmJG5hbWVfY2xlYW5lcgphYl9zdWdnZXN0aW9uc19kaXJlY3RlZCA8LSBzdWJzZXQoYWJfc3VnZ2VzdGlvbnNfZGlyZWN0ZWQsIGFfZXZlbnQpICNqdXN0IHRvIGRvdWJsZSBjaGVjaywgc2hvdWxkIGJlIG5vIGNoYW5nZQphYl9zdWdnZXN0aW9uc19kaXJlY3RlZCRiX2V2ZW50IDwtIGFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkJG5hbWVfY2xlYW5lcl9iICVpbiUgZXZlbnRzX3NmJG5hbWVfY2xlYW5lcgphYl9zdWdnZXN0aW9uc19kaXJlY3RlZCRiX2V2ZW50X2Nvb3JkIDwtIGFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkJG5hbWVfY2xlYW5lcl9iICVpbiUgZmxhdGZpbGVzX3NmX3JvaSRuYW1lX2NsZWFuZXJbZmxhdGZpbGVzX3NmX3JvaSRzb3VyY2VfZGF0YXNldCAlaW4lIGMoJ2V2ZW50cycpICYgIWlzLm5hKGZsYXRmaWxlc19zZl9yb2kkbGF0aXR1ZGUpXQphYl9zdWdnZXN0aW9uc19kaXJlY3RlZCRiX2dhel9jb29yZCAgIDwtIGFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkJG5hbWVfY2xlYW5lcl9iICVpbiUgZmxhdGZpbGVzX3NmX3JvaSRuYW1lX2NsZWFuZXJbIWZsYXRmaWxlc19zZl9yb2kkc291cmNlX2RhdGFzZXQgJWluJSBjKCdldmVudHMnKSAgJiAhaXMubmEoZmxhdGZpbGVzX3NmX3JvaSRsYXRpdHVkZSkgXQoKYGBgCgpgYGB7cn0KCmhpc3QoYWJfc3VnZ2VzdGlvbnNfZGlyZWN0ZWQkdG9wb255bV94Yl9tb2RlbF9wcmVkaWN0aW9uKSAjYmVmb3JlIHN1YnNldHRpbmcKYWJfc3VnZ2VzdGlvbnNfZGlyZWN0ZWQgPC0gYWJfc3VnZ2VzdGlvbnNfZGlyZWN0ZWRbYV9ldmVudCAmIChiX2V2ZW50X2Nvb3JkfGJfZ2F6X2Nvb3JkKV0KYWJfc3VnZ2VzdGlvbnNfZGlyZWN0ZWQgPC0gYWJfc3VnZ2VzdGlvbnNfZGlyZWN0ZWRbdG9wb255bV94Yl9tb2RlbF9wcmVkaWN0aW9uPi41IHwgbmFtZV9jbGVhbmVyX2E9PW5hbWVfY2xlYW5lcl9iXSAjT25seSBrZWVwIHRob3NlIHdpdGggZ3JlYXRlciAuNSBjaGFuY2Ugb2YgbWF0Y2gKaGlzdChhYl9zdWdnZXN0aW9uc19kaXJlY3RlZCR0b3BvbnltX3hiX21vZGVsX3ByZWRpY3Rpb24pICNhZnRlciBzdWJzZXR0aW5nCgoKCmBgYAoKYGBge3J9CgphYl9zdWdnZXN0aW9uc19kaXJlY3RlZFssdG9wb255bV94Yl9tb2RlbF9wcmVkaWN0aW9uX21heDo9bWF4KHRvcG9ueW1feGJfbW9kZWxfcHJlZGljdGlvbiksCiAgICAgICAgICAgICAgICAgICAgICAgIGJ5PWMoJ25hbWVfY2xlYW5lcl9hJywnYl9nYXpfY29vcmQnKV0KCmFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkX21heF9nYXpfY29vcmQgPC0gc3Vic2V0KGFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiX2dhel9jb29yZCAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9wb255bV94Yl9tb2RlbF9wcmVkaWN0aW9uPT10b3BvbnltX3hiX21vZGVsX3ByZWRpY3Rpb25fbWF4CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKCmFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkX21heF9ldmVudF9jb29yZCA8LSBzdWJzZXQoYWJfc3VnZ2VzdGlvbnNfZGlyZWN0ZWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYl9ldmVudF9jb29yZCAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3BvbnltX3hiX21vZGVsX3ByZWRpY3Rpb249PXRvcG9ueW1feGJfbW9kZWxfcHJlZGljdGlvbl9tYXgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCgojQSBub24gbWlzc2luZyBjb29yZGluYXRlCmNvbmRpdGlvbiA8LSAhaXMubmEoc2Y6OnN0X2Nvb3JkaW5hdGVzKGV2ZW50c19zZilbLDFdKQp0YWJsZShjb25kaXRpb24pCgojQW4gZXhhY3QgbWF0Y2ggdG8gdGhlIGdhegpjb25kaXRpb24yIDwtIGV2ZW50c19zZiRuYW1lX2NsZWFuZXIgJWluJSBmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lclshZmxhdGZpbGVzX3NmX3JvaSRzb3VyY2VfZGF0YXNldCAlaW4lIGMoJ2V2ZW50cycsJ2V2ZW50c19wb2x5JyldCnRhYmxlKGNvbmRpdGlvbjIpCgp0YWJsZShjb25kaXRpb24gfCBjb25kaXRpb24yKQoKI0Z1enp5IG1hdGNoIHRvIGEgZ2F6CmNvbmRpdGlvbjMgPC0gZXZlbnRzX3NmJG5hbWVfY2xlYW5lciAlaW4lIGFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkX21heF9nYXpfY29vcmQkbmFtZV9jbGVhbmVyX2EgCnRhYmxlKGNvbmRpdGlvbjMpCgp0YWJsZShjb25kaXRpb24gfCBjb25kaXRpb24yIHwgY29uZGl0aW9uMykKdGFibGUoY29uZGl0aW9uIHwgY29uZGl0aW9uMiB8IGNvbmRpdGlvbjMpIC8gbGVuZ3RoKGNvbmRpdGlvbikKCiNFeGFjdCBtYXRjaCB0byBhbm90aGVyIGV2ZW50CmNvbmRpdGlvbjQgPC0gZXZlbnRzX3NmJG5hbWVfY2xlYW5lciAlaW4lIGZsYXRmaWxlc19zZl9yb2kkbmFtZV9jbGVhbmVyW2ZsYXRmaWxlc19zZl9yb2kkc291cmNlX2RhdGFzZXQgJWluJSBjKCdldmVudHMnKSAmICFpcy5uYShmbGF0ZmlsZXNfc2Zfcm9pJGxhdGl0dWRlKSBdCnRhYmxlKGNvbmRpdGlvbjQpCnRhYmxlKGNvbmRpdGlvbjQpIC8gbGVuZ3RoKGNvbmRpdGlvbikKCnRhYmxlKGNvbmRpdGlvbiB8IGNvbmRpdGlvbjIgfCBjb25kaXRpb24zIHwgY29uZGl0aW9uNCkKdGFibGUoY29uZGl0aW9uIHwgY29uZGl0aW9uMiB8IGNvbmRpdGlvbjMgfCBjb25kaXRpb240KSAvIGxlbmd0aChjb25kaXRpb24pCgojRnV6enkgbWF0Y2ggdG8gZXZlbnQgd2l0aCBjb29yZGluYXRlCmNvbmRpdGlvbjUgPC0gZXZlbnRzX3NmJG5hbWVfY2xlYW5lciAlaW4lIGFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkX21heF9ldmVudF9jb29yZCRuYW1lX2NsZWFuZXJfYSAKdGFibGUoY29uZGl0aW9uNSkKdGFibGUoY29uZGl0aW9uNSkgLyBsZW5ndGgoY29uZGl0aW9uKQoKdGFibGUoY29uZGl0aW9uIHwgY29uZGl0aW9uMiB8IGNvbmRpdGlvbjMgfCBjb25kaXRpb240IHwgY29uZGl0aW9uNSkKdGFibGUoY29uZGl0aW9uIHwgY29uZGl0aW9uMiB8IGNvbmRpdGlvbjMgfCBjb25kaXRpb240IHwgY29uZGl0aW9uNSkgLyBsZW5ndGgoY29uZGl0aW9uKQoKY29uZGl0aW9uNiA8LSB0b2xvd2VyKGV2ZW50c19zZiRkb2N1bWVudF9kaXN0cmljdF9jbGVhbikgJWluJSBmbGF0ZmlsZXNfc2Zfcm9pJG5hbWVfY2xlYW5lclshZmxhdGZpbGVzX3NmX3JvaSRzb3VyY2VfZGF0YXNldCAlaW4lIGMoJ2V2ZW50cycpXQp0YWJsZShjb25kaXRpb242KQoKc2F2ZVJEUyhhYl9zdWdnZXN0aW9uc19kaXJlY3RlZCwKICAgICAgICBmaWxlPWdsdWU6OmdsdWUoZ2V0d2QoKSwgIi8uLi9pbnN0L2V4dGRhdGEvYWJfc3VnZ2VzdGlvbnNfZGlyZWN0ZWQuUmRzIikgCiAgICAgICAgKQoKYGBgCgpHaXZlbiBhbGwgb2YgdGhlIGFib3ZlIHdlIHdhbnQgdG8gcHJvZHVjZSBhIG1hdGNoaW5nIGRhdGFzZXQgYnJva2VuIG91dCBhY3Jvc3MgCgpleGFjdCBvciBmdXp6eSBtYXRjaApwbGFjZSBkZXNjcmlwdGlvbiBvciBkaXN0cmljdC9wcm92aW5jZQoKCmBgYHtyfQoKI0ZvciB3aGVuIHRoaXMgaW5ldml0YWJseSBjcmFzaGVzCmFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkIDwtICByZWFkUkRTKHN5c3RlbS5maWxlKCJleHRkYXRhIiwgImFiX3N1Z2dlc3Rpb25zX2RpcmVjdGVkLlJkcyIsIHBhY2thZ2UgPSAiTWVhc3VyaW5nTGFuZHNjYXBlIikpIApmbGF0ZmlsZXNfc2Zfcm9pX2NlbnRyb2lkcyA8LSBmbGF0ZmlsZXNfc2Zfcm9pICU+JSBmaWx0ZXIoIWlzLm5hKGxhdGl0dWRlKSB8IGdlb21ldHJ5X3R5cGUgIT0gIlBPSU5UIikgJT4lIHNmOjpzdF9jZW50cm9pZCgpCgojRG8gc29tZSBxdWljayBhY2NvdW50aW5nCgojQ3JlYXRlIG5ldyBkYXRhc2V0IHRoYXQgaW5jbHVkZXMKIzEpIERpYWRzIGNob3NlbiBieSBmdXp6eSBhYl9zdWdnZXN0aW9uc19kaXJlY3RlZCAKIzIpIERpYWRzIG9mIGlkZW50aWNhbAoKZ2VvcmVmIDwtIGRhdGEudGFibGU6OnNldG5hbWVzKCBhYl9zdWdnZXN0aW9uc19kaXJlY3RlZFssYygnbmFtZV9jbGVhbmVyX2EnLCduYW1lX2NsZWFuZXJfYicsJ3RvcG9ueW1feGJfbW9kZWxfcHJlZGljdGlvbicpXSAsIGMoJ2dlb3JlZl9hJywnZ2VvcmVmX2InLCd0b3BvbnltX3hiX21vZGVsX3ByZWRpY3Rpb24nKSApICNTbyB0aGlzIGlzIG5vdyBhIGxpc3Qgb2YgbmFtZXMgdGhhdCBhcmUgZWl0aGVyIGlkZW50aWNhbCBvciBmdXp6eSBtYXRjaGVzIGZvciBvbmUgYW5vdGhlcgpnZW9yZWYkZ2VvcmVmX2EgPC0gdHJpbXdzKGdlb3JlZiRnZW9yZWZfYSkKZ2VvcmVmJGdlb3JlZl9iIDwtIHRyaW13cyhnZW9yZWYkZ2VvcmVmX2IpCmdlb3JlZiA8LSB1bmlxdWUoZ2VvcmVmKQojZ2VvcmVmJGdlb3JlZl9hYl9pZGVudGljYWwgPC0gZ2VvcmVmJGdlb3JlZl9hPT1nZW9yZWYkZ2VvcmVmX2IgCgp0YWJsZShldmVudHNfc2YkbmFtZV9jbGVhbmVyICVpbiUgZ2VvcmVmJGdlb3JlZl9hKSAjNyw3NDIgZXZlbnRzIHdpdGggYXQgbGVhc3Qgb25lIGdhemV0dGVlciBzdWdnZXN0aW9uCgojVGhpcyBpcyBtYXBwaW5nIG9mIGV2ZW50cyB0byBpZGVudGljYWwgYW5kIGZ1enp5CiNnZW9yZWYyIGlzIGEgY29tYmluYXRpb24gb2YgZXZlbnRzLCBhbmQgYWxsIHRoZSBnYXpldHRlZXIgbmFtZXMgdGhleSBtaWdodCBtYXRjaCB0bwpnZW9yZWYyIDwtIGFzLmRhdGEuZnJhbWUoZXZlbnRzX3NmKVssYygnZXZlbnRfaGFzaCcsICduYW1lX2NsZWFuZXInLCdkb2N1bWVudF9kaXN0cmljdF9jbGVhbicsJ2RvY3VtZW50X3VuaXRfdHlwZScsJ2RvY3VtZW50X2RhdGVfYmVzdF95ZWFyJyldICU+JSAKICBkcGx5cjo6bGVmdF9qb2luKGdlb3JlZiwgYnkgPSBjKCJuYW1lX2NsZWFuZXIiID0gImdlb3JlZl9hIikpICU+JSB1bmlxdWUoKSAgCmRpbShnZW9yZWYyKQoKdGFibGUoZXZlbnRzX3NmJG5hbWVfY2xlYW5lciAlaW4lIGdlb3JlZjIkbmFtZV9jbGVhbmVyKSAjQWxsIGV2ZW50cyBhcmUgaW4gaGVyZQp0YWJsZShldmVudHNfc2YkbmFtZV9jbGVhbmVyICVpbiUgZ2VvcmVmMiRuYW1lX2NsZWFuZXJbIWlzLm5hKGdlb3JlZjIkZ2VvcmVmX2IpXSkgIwp0YWJsZShldmVudHNfc2YkZXZlbnRfaGFzaCAlaW4lIGdlb3JlZjIkZXZlbnRfaGFzaCkgI0FsbCBldmVudCBoYXNoZXMgc2hvdyB1cCBpbiBoZXJlCgojVGhpcyBpcyBtYXBwaW5nIG9mIGdheiBwbGFjZXMgdG8gaWRlbnRpY2FsIGFuZCBmdXp6eSBwb3NzaWJsZQojZ2VvcmVmMyBpcyBhIGNvbWJpbmF0aW9uIG9mIGdhemV0dGVlcnMgYW5kIGFsbCBnYXpldHRlZXIgbmFtZXMgdGhleSBtaWdodCBtYXRjaCB0bwojZ2VvcmVmMyA8LSBhcy5kYXRhLmZyYW1lKGZsYXRmaWxlc19zZl9yb2lfY2VudHJvaWRzKVssYygncGxhY2VfaGFzaCcsJ3NvdXJjZV9kYXRhc2V0JywnbmFtZV9jbGVhbmVyJywnZ2VvbWV0cnlfdHlwZScpXSAlPiUgbGVmdF9qb2luKGdlb3JlZiwgYnkgPSBjKCJuYW1lX2NsZWFuZXIiID0gImdlb3JlZl9iIikpICU+JSB1bmlxdWUoKSAKI2RpbShnZW9yZWYzKQoKI1RoaXMgaXMgYSBtYXBwaW5nIG9mIGV2ZW50cyB0byBnYXogd2hlcmUgZWl0aGVyIHRoZWlyIGV4YWN0IG9yIGZ1enp5IGNvdW50ZXJwYXJ0cyBtYXRjaGVkIGVhY2ggb3RoZXIKI2dlb3JlZiBhbGwgaXMgYSBtYXBwaW5nIG9mIGV2ZW50cywgdG8gZ2F6ZXR0ZWVyIG5hbWVzLCB0byAKZ2VvcmVmX2FsbCA8LSBnZW9yZWYyICU+JSAKICAgICAgICAgICAgIGRwbHlyOjpsZWZ0X2pvaW4oYXMuZGF0YS5mcmFtZShmbGF0ZmlsZXNfc2Zfcm9pX2NlbnRyb2lkcylbLGMoJ3BsYWNlX2hhc2gnLCdzb3VyY2VfZGF0YXNldCcsJ25hbWVfY2xlYW5lcicsJ2dlb21ldHJ5X3R5cGUnLCdmZWF0dXJlX2NvZGUnKV0sCiAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJnZW9yZWZfYiIgPSAibmFtZV9jbGVhbmVyIikpICU+JSB1bmlxdWUoKQpkaW0oZ2VvcmVmX2FsbCkgICAjMzAzLDE3NAoKICAKdGFibGUoZXZlbnRzX3NmJG5hbWVfY2xlYW5lciAlaW4lIGdlb3JlZl9hbGwkbmFtZV9jbGVhbmVyKSAjQWxsIGV2ZW50cyBhcmUgaW4gaGVyZQp0YWJsZShldmVudHNfc2YkbmFtZV9jbGVhbmVyICVpbiUgZ2VvcmVmX2FsbCRuYW1lX2NsZWFuZXJbIWlzLm5hKGdlb3JlZl9hbGwkZ2VvcmVmX2IpXSkgIwp0YWJsZShldmVudHNfc2YkZXZlbnRfaGFzaCAlaW4lIGdlb3JlZl9hbGwkZXZlbnRfaGFzaCkgI0FsbCBldmVudCBoYXNoZXMgc2hvdyB1cCBpbiBoZXJlCgp0YWJsZShnZW9yZWZfYWxsJHNvdXJjZV9kYXRhc2V0KQpsZW5ndGgodW5pcXVlKGdlb3JlZl9hbGwkZXZlbnRfaGFzaCkpCmdlb3JlZl9hbGwkZ2VvcmVmX2EgPC0gTlVMTAoKYGBgCgoKCgpgYGB7cn0KCiNPayBuZXh0IHVwIHdlIGFkZCBkaXN0YW5jZQojY3JzX20gPC0gIitwcm9qPXV0bSArem9uZT0yNyArZGF0dW09TkFEODMgK3VuaXRzPW0gK25vX2RlZnMiICAjdGhpcyBpcyB0b2FsbHkgd3JvbmcsIHN0b3AgdXNpbmcgaXQKI2h0dHBzOi8vZXBzZy5pby8yMTAzNwojRVBTRzoyMTAzNwojUHJvamVjdGVkIGNvb3JkaW5hdGUgc3lzdGVtCiNBcmMgMTk2MCAvIFVUTSB6b25lIDM3Uwpyb3duYW1lcyhldmVudHNfc2YpIDwtIGV2ZW50c19zZiRldmVudF9oYXNoCiNldmVudHNfc2ZfdXRtIDwtIHN0X3RyYW5zZm9ybShldmVudHNfc2ZbLGMoJ2V2ZW50X2hhc2gnLCJnZW9tZXRyeSIpXSwgY3JzPTIxMDM3KSAjCiNldmVudHNfc2ZfdXRtIDwtIHN0X3NmKGFzLmRhdGEuZnJhbWUoZXZlbnRzX3NmX3V0bSkpCiNyb3duYW1lcyhldmVudHNfc2ZfdXRtKSA8LSBldmVudHNfc2ZfdXRtJGV2ZW50X2hhc2gKI0l0IHdhcyBzbG93IGFzIGhlbGwgYXMgYSB0YmwsIGNvdmVydCB0byBqdXN0IHNmCgoKI2ZsYXRmaWxlc19zZl9yb2lfY2VudHJvaWRzX3V0bSA8LSBzdF90cmFuc2Zvcm0oZmxhdGZpbGVzX3NmX3JvaV9jZW50cm9pZHNbLGMoJ3BsYWNlX2hhc2gnLCJnZW9tZXRyeSIpXSwgY3JzPTIxMDM3KQojcm93bmFtZXMoZmxhdGZpbGVzX3NmX3JvaV9jZW50cm9pZHNfdXRtKSA8LSBmbGF0ZmlsZXNfc2Zfcm9pX2NlbnRyb2lkc191dG0kcGxhY2VfaGFzaAoKI1RyeSBzb21lIHNtYWxsZXIgZXhwZXJpbWVudHMgYW5kIHZlcmlmeSBpZiBhbmQgaG93IGxvbmcgaXQgdGFrZXMgdG8gY2FsY3VsYXRlIGRpc3RhbmNlIHNpbmNlIHdlJ3ZlIGdvdCBhIG1pbGxpb24gb2YgdGhlbQojVGhpcyB0YWtlcyBhIGxvbmcgdGltZSB0byBjYWxjdWxhdGUgYmVjYXVzZSB0aGVyZSdzIGEgbWlsbGlvbiBvZiB0aGVtCgojSW5zdGVhZCwgYWRkIGEgY29sdW1uIGZvciB3aGV0aGVyIGRpbWVuc2lvbnMgYXJlIHplcm8gYW5kIHRoZW4gcXVldWUgdGhhdCBjb2x1bW4gd2l0aCB0aGUgaGFzaCwgbXVjaCBtdWNoIGJldHRlcgpldmVudHNfc2YkaXN2YWxpZCA8LSAhaXMubmEoc3RfZGltZW5zaW9uKGV2ZW50c19zZikgKQpmbGF0ZmlsZXNfc2Zfcm9pX2NlbnRyb2lkcyRpc3ZhbGlkIDwtICFpcy5uYShzdF9kaW1lbnNpb24oZmxhdGZpbGVzX3NmX3JvaV9jZW50cm9pZHMpICkKCmNvcmRzX2V2ZW50cyA8LSBhcy5kYXRhLmZyYW1lKCBzdF9jb29yZGluYXRlcyhldmVudHNfc2YpICkKcm93bmFtZXMoY29yZHNfZXZlbnRzKSA8LSByb3duYW1lcyhldmVudHNfc2YpCmNvcmRzX2ZsYXRmaWxlcyA8LSBhcy5kYXRhLmZyYW1lKCBzdF9jb29yZGluYXRlcyhmbGF0ZmlsZXNfc2Zfcm9pX2NlbnRyb2lkcykgKQpyb3duYW1lcyhmbGF0ZmlsZXNfc2Zfcm9pX2NlbnRyb2lkcykgPC0gZmxhdGZpbGVzX3NmX3JvaV9jZW50cm9pZHMkcGxhY2VfaGFzaApyb3duYW1lcyhjb3Jkc19mbGF0ZmlsZXMpIDwtIHJvd25hbWVzKGZsYXRmaWxlc19zZl9yb2lfY2VudHJvaWRzKQoKY29vcmRzX2R0IDwtIGFzLmRhdGEudGFibGUoIGNiaW5kKGNvcmRzX2V2ZW50c1tnZW9yZWZfYWxsJGV2ZW50X2hhc2gsXSwgY29yZHNfZmxhdGZpbGVzW2dlb3JlZl9hbGwkcGxhY2VfaGFzaCxdKSkKbmFtZXMoY29vcmRzX2R0KSA8LSBjKCJYMSIsIlkxIiwgIlgyIiwgIlkyIikKY29vcmRzX2R0WyxkaXN0YW5jZV9rbTo9c3FydCgoWDItWDEpXjIrKFkyLVkxKV4yKSAqIDExMV0KCmhpc3QoY29vcmRzX2R0JGRpc3RhbmNlKQpnZW9yZWZfYWxsJGRpc3RhbmNlIDwtIE5VTEwKZ2VvcmVmX2FsbF9kdCA8LSBhcy5kYXRhLnRhYmxlKGNiaW5kKGdlb3JlZl9hbGwsIGNvb3Jkc19kdCkpCgpnZW9yZWZfYWxsX2R0IDwtIHVuaXF1ZShnZW9yZWZfYWxsX2R0KTsgZGltKGdlb3JlZl9hbGxfZHQpCiNnZW9yZWZfYWxsX2R0IDwtIHN1YnNldChnZW9yZWZfYWxsX2R0LCAhaXMubmEobmFtZV9jbGVhbmVyKSAmICFpcy5uYShnZW9yZWZfYikpICNtYXliZSBkb24ndCBkcm9wIHVubWF0Y2hlZCBvbmVzPyBJZiB3ZSB3YW50IGV2ZW50cyBhcyBhIHNvdXJjZT8KCnRhYmxlKGV2ZW50c19zZiRuYW1lX2NsZWFuZXIgJWluJSBnZW9yZWZfYWxsX2R0JG5hbWVfY2xlYW5lcikgI0FsbCBldmVudHMgYXJlIGluIGhlcmUKdGFibGUoZXZlbnRzX3NmJG5hbWVfY2xlYW5lciAlaW4lIGdlb3JlZl9hbGxfZHQkbmFtZV9jbGVhbmVyWyFpcy5uYShnZW9yZWZfYWxsX2R0JGdlb3JlZl9iKV0pICM3LDc0MiBldmVudHMgd2l0aCBhdCBsZWFzdCBvbmUgZ2F6ZXR0ZWVyIHN1Z2dlc3Rpb24KdGFibGUoZXZlbnRzX3NmJGV2ZW50X2hhc2ggJWluJSBnZW9yZWZfYWxsX2R0JGV2ZW50X2hhc2gpICNBbGwgZXZlbnQgaGFzaGVzIHNob3cgdXAgaW4gaGVyZQoKc2F2ZVJEUyhnZW9yZWZfYWxsX2R0LAogICAgICAgIGZpbGU9cGFzdGUwKGhlcmU6OmhlcmUoKSwgIi9pbnN0L2V4dGRhdGEvZ2VvcmVmX2FsbF9kdC5SZHMiKQopCgpgYGAKCgoKCgoK