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
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"])
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
r
r #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.
r
r #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)

r
r 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
r
r #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
r
r 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
r
r table(events_sf\(event_hash %in% georef_all_dt\)event_hash) #All event hashes show up in here
TRUE
10469
r
r 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