Create a global SDM for weighting pseudoabsences
Read in the global occurrence data for the target species that was
downloaded using “global_download.Rmd”
[1] "Vaccinium corymbosum"
[1] "2882849"
Specify paths for output (defaults to file structure in ReadMe)
Filter global occurrence data
#remove unverified records
identificationVerificationStatus_to_discard <- c("unverified", "unvalidated","not able to validate","control could not be conclusive due to insufficient knowledge")
#enter value for max coordinate uncertainty in meters.
global.occ<-global %>%
filter(speciesKey==taxonkey) %>% #using taxonKey filters out accepted synonyms
filter(is.na(coordinateUncertaintyInMeters)| coordinateUncertaintyInMeters< 1000) %>%
filter(!str_to_lower(identificationVerificationStatus) %in% identificationVerificationStatus_to_discard)
global.occ$lon_dplaces<-sapply(global.occ$decimalLongitude, function(x) decimalplaces(x))
global.occ$lat_dplaces<-sapply(global.occ$decimalLatitude, function(x) decimalplaces(x))
global.occ[global.occ$lon_dplaces < 4& global.occ$lat_dplaces < 4 , ]<-NA
global.occ<-global.occ[ which(!is.na(global.occ$lon_dplaces)),]
global.occ<-within(global.occ,rm("lon_dplaces","lat_dplaces"))
global.occ<-global.occ[which( global.occ$year > 1975 & global.occ$year < 2020),]
Convert global occurrences to spatial points needed for
modelling

Flag and remove centroids and invalid georeferenced points
Testing coordinate validity
Flagged 0 records.
Testing zero coordinates
Warning: GEOS support is provided by the sf and terra packages among othersFlagged 0 records.
Testing country capitals
Flagged 8 records.
Testing country centroids
Flagged 0 records.
Testing sea coordinates
trying URL 'https://naturalearth.s3.amazonaws.com/50m_physical/ne_50m_land.zip'
Content type 'application/zip' length 457183 bytes (446 KB)
downloaded 446 KB
Flagged 44 records.
Testing GBIF headquarters, flagging records around Copenhagen
Flagged 0 records.
Testing biodiversity institutions
Flagged 3 records.
Flagged 54 of 1400 records, EQ = 0.04.
Testing coordinate validity
Flagged 0 records.
Testing zero coordinates
Warning: GEOS support is provided by the sf and terra packages among othersFlagged 0 records.
Testing country capitals
Flagged 8 records.
Testing country centroids
Flagged 0 records.
Testing sea coordinates
trying URL 'https://naturalearth.s3.amazonaws.com/50m_physical/ne_50m_land.zip'
Content type 'application/zip' length 457183 bytes (446 KB)
downloaded 446 KB
Flagged 44 records.
Testing GBIF headquarters, flagging records around Copenhagen
Flagged 0 records.
Testing biodiversity institutions
Flagged 3 records.
Flagged 54 of 1400 records, EQ = 0.04.
Create global rasterstack using CHELSA data for model building
globalclimrasters <- list.files((here("./data/external/climate/trias_CHELSA")),pattern='tif',full.names = T) #import CHELSA data
globalclimpreds <- stack(globalclimrasters)
Use SDMtab command from the SDMPlay package to remove duplicates per
grid cell
global.SDMtable<- SDMPlay:::SDMtab(global.occ.LL.cleaned, globalclimpreds, unique.data = TRUE,background.nb= 0) #
numb.pseudoabs <-length(global.SDMtable$id) #sets the number of pseudoabsences equal to number of unique presences
global.occ.sp<-global.SDMtable[c("longitude", "latitude")]
coordinates(global.occ.sp)<- c("longitude", "latitude")
global.occ.sp$species<- rep(1,length(global.occ.sp$latitude)) #adds columns indicating species presence needed for modeling
Select wwf ecoregions that contain global occurrence points
Specify and import bias grids for relevant taxonomic group (e.g
vascular plants)
biasgrid<-raster(here("./data/external/bias_grids/final/trias/plants_1deg_min5.tif"))### specify appropriate bias grid here
Subset bias grid by ecoregions containing occurrence points
ext_wwf_ecoSub<-extent(wwf_ecoSub1)
biasgrid_crop<-crop(biasgrid,ext_wwf_ecoSub)
biasgrid_sub<-mask(biasgrid_crop,wwf_ecoSub1)
plot(biasgrid_sub)
plot(wwf_ecoSub1,add=TRUE)

NA
Use randomPoints function from dismo package to locate
pseduobasences within the bias grid subset
set.seed(728)
global_points<-randomPoints(biasgrid_sub, numb.pseudoabs, global.occ.sp, ext=NULL, extf=1.1, excludep=TRUE, prob=FALSE, cellnumbers=FALSE, tryf=70, warn=2, lonlatCorrection=TRUE)
# will throw a warning if randomPoints generated is less than numb.pseudoabs. If this happens, increase the number of tryf or ignore bias grid and sample from ecoregion only.
OPTIONAL: Sample from ecoregion only
# wwf_grid<-raster(here("./data/external/GIS/wwf_ecoregions_v1.tif"))
# ecoregions_raster<-mask(wwf_grid,wwf_ecoSub1)
# set.seed(768)
# global_points<-randomPoints(ecoregions_raster, numb.pseudoabs, global.occ.sp, ext=NULL, extf=1.1, excludep=TRUE, prob=FALSE, cellnumbers=FALSE, tryf=150, warn=2, lonlatCorrection=TRUE)
Extract generated pseudo absences and create presence-pseudobasence
dataset
Warning: OGR support is provided by the sf and terra packages among othersWarning: OGR support is provided by the sf and terra packages among others
Remove highly correlated predictors from dataframe
Correct global clim preds values from integer format
Use caretList from Caret package to run multiple machine learning
models
control <- trainControl(method="repeatedcv",number=10, repeats=10, savePredictions="final", preProc=c("center","scale"),classProbs=TRUE)
classList1 <- c("glm","gbm","rf","knn", "earth")
set.seed(457)
hideoutput<-capture.output(
global_train <- caretList(
species~., data= global.data.df.uncor,
trControl=control,
methodList=classList1
))
modelResults<-resamples(global_train)
summary(modelResults)# displays accuracy of each model
Call:
summary.resamples(object = modelResults)
Models: glm, gbm, rf, knn, earth
Number of resamples: 100
Accuracy
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
glm 0.6009852 0.6403941 0.6600985 0.6601555 0.6798030 0.7450980 0
gbm 0.7623762 0.8027118 0.8168317 0.8144422 0.8316832 0.8719212 0
rf 0.8078818 0.8323050 0.8465347 0.8471209 0.8627451 0.8970588 0
knn 0.7772277 0.8137255 0.8366337 0.8319377 0.8514851 0.8916256 0
earth 0.7029703 0.7496921 0.7728381 0.7720725 0.7948457 0.8423645 0
Kappa
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
glm 0.2022995 0.2806485 0.3199825 0.3203036 0.3597457 0.4901961 0
gbm 0.5247525 0.6053303 0.6336634 0.6288757 0.6633663 0.7438113 0
rf 0.6157356 0.6644820 0.6930693 0.6942432 0.7254902 0.7941176 0
knn 0.5544554 0.6274510 0.6732673 0.6638629 0.7029703 0.7832249 0
earth 0.4059406 0.4994847 0.5454726 0.5441390 0.5896913 0.6848132 0
modelCor(resamples(global_train))# shows correlation among models.Weakly correlated algorithms are persuasive for stacking them in ensemble.
glm gbm rf knn earth
glm 1.0000000 0.2949202 0.1911769 0.1942191 0.2081093
gbm 0.2949202 1.0000000 0.5516553 0.5781952 0.6029141
rf 0.1911769 0.5516553 1.0000000 0.5889041 0.3030091
knn 0.1942191 0.5781952 0.5889041 1.0000000 0.3025351
earth 0.2081093 0.6029141 0.3030091 0.3025351 1.0000000
Create ensemble model (combine individual models into one)
set.seed(478)
global_stack <- caretStack(
global_train,
method="glm",
trControl=trainControl(method="cv",
number=10,
savePredictions= "final" ))
print(global_stack)
A glm ensemble of 5 base models: glm, gbm, rf, knn, earth
Ensemble results:
Generalized Linear Model
20280 samples
5 predictor
2 classes: 'present', 'absent'
No pre-processing
Resampling: Cross-Validated (10 fold)
Summary of sample sizes: 18252, 18252, 18252, 18252, 18252, 18252, ...
Resampling results:
Accuracy Kappa
0.8476824 0.6953649
Create rasterstack of CHELSA climate data clipped to European
modeling extent for prediction
euclimrasters <- list.files((here("./data/external/climate/chelsa_eu_clips")),pattern='tif',full.names = T)
eu_climpreds<-stack(euclimrasters)
eu_climpreds.10<-divide10(eu_climpreds) # correct for integer format of Chelsa preds
Restrict global model prediction to the extent of Europe

Create European subset

Create RasterStack of European climate variables from RMI
rmiclimrasters <- list.files((here("./data/external/climate/rmi_corrected")),pattern='tif',full.names = T)
rmiclimpreds <- stack(rmiclimrasters)
Use SDMtab command from the SDMPlay package to remove duplicates per
1km grid cell
# convert a single raster to LL for use in SDMtab
LLproj<-CRS("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0")
rmiclimpredLL<-projectRaster(rmiclimpreds$anntemp_eea, crs=LLproj)
rmiclimpredLL2<-projectRaster(rmiclimpreds$annprecip_eea, crs=LLproj)
rmipredstackLL<-stack(rmiclimpredLL,rmiclimpredLL2)
# Create SpatialPoints dataframe needed for SDMtab command.
euocc<-occ.eu@coords
euocc1<-data.frame(euocc)[c(1:2)]
names(euocc1)<-c("longitude", "latitude")
#run SDMtab command to remove duplicates
euocc.SDMtable<- SDMPlay:::SDMtab(euocc1, rmipredstackLL, unique.data = TRUE,same=TRUE)
numb.pseudoabs <- length(euocc.SDMtable$id)
Clip bias grid to European extent
studyextent<-euboundary
ecoregions_eu<-crop(biasgrid_sub,studyextent)
biasgrid_eu<-projectRaster(ecoregions_eu,rmiclimpreds)
plot(biasgrid_eu)
plot(studyextent,add=TRUE)

Mask areas of high habitat suitability from global climate
model
#Read in global raster from earlier step if needed
#globalfilename<-paste(here("./data/processed/geotiffs/GlobalEnsEU_"), taxonkey, ".tif",sep="")
#global_model<-raster("C:/Users/Amy.Davis/OneDrive - Ipsos/Desktop/xps15/risk-modelling-and-mapping/data/processed/geotiffs/GlobalEnsEU_2882849.tif")
wgs84_gcs<-CRS("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0")
crs(global_model)<-wgs84_gcs
m<-global_model >.5
global_mask<-mask(global_model,m,maskvalue=TRUE)
global_masked_proj<-projectRaster(global_mask,biasgrid_eu)
Overlay low predicted habitat suitability on bias grid to exclude
low sampled areas
pseudoSamplingArea<-mask(biasgrid_eu,global_masked_proj)
plot(pseudoSamplingArea)

Randomly locate pseudo absences within “pseudoSamplingArea”
set.seed=120
euocc_points<-randomPoints(pseudoSamplingArea, numb.pseudoabs, euocc, ext=NULL, extf=1.1, excludep=TRUE, prob=FALSE,cellnumbers=FALSE, tryf=50, warn=2, lonlatCorrection=TRUE)
euocc_pseudoAbs<-as.data.frame(euocc_points)
coordinates(euocc_pseudoAbs)<-c("x","y")
euocc_pseudoAbs$occ<-rep(0,length(euocc_pseudoAbs$x))
crs(euocc_pseudoAbs)<-CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,-0,-0,-0,0 +units=m +no_defs ")
Join eu occurrences with pseudo absences to create eu level
presence-pseudoabsence dataset
eu_presabs<- spRbind(euocc1,euocc_pseudoAbs)
crs(eu_presabs)<-CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,-0,-0,-0,0 +units=m +no_defs ")
#confirm equal number of presences and absences
table(eu_presabs@data$occ)
0 1
234 234
#export occ points as shapefile
writeOGR(eu_presabs, dsn=(here("./data/processed/sdm_occ_pts/europe")), layer=paste(taxonName,"_EUpresabs",sep=""), driver="ESRI Shapefile", overwrite_layer = TRUE)
Warning: OGR support is provided by the sf and terra packages among othersWarning: OGR support is provided by the sf and terra packages among others
Create SDM data object for European data
class : sdmdata
===========================================================
number of species : 1
species names : occ
number of features : 13
feature names : anngdd100, annprecip_eea, annpvarrecip_eea, ...
type : Presence-Absence
has independet test data? : FALSE
number of records : 455
has Coordinates? : TRUE
Add habitat and anthropogenic predictors
class : sdmdata
===========================================================
number of species : 1
species names : occ
number of features : 12
feature names : anngdd100, annpvarrecip_eea, dristprec, ...
type : Presence-Absence
has independet test data? : FALSE
number of records : 454
has Coordinates? : TRUE
Build models with climate and habitat data
# uncomment 2nd control options for LOOCV (leave one out cross validation, which is aka as "jacknife" ) which should be used when occurrences are smaller than n=10 for each predictor in the model)
occeu.full.data.df1$occ<-as.factor(occeu.full.data.df1$occ)
levels(occeu.full.data.df1$occ)<-c("absent","present")
#control<-trainControl(method="LOOCV",savePredictions="final", preProc=c("center","scale"),classProbs=TRUE)
control <- trainControl(method="repeatedcv",number=10, repeats=10,savePredictions="final",classProbs=TRUE)
mylist<-list(
glm =caretModelSpec(method = "glm",maxit=100),
gbm= caretModelSpec(method = "gbm"),
rf = caretModelSpec(method = "rf", importance = TRUE),
knn = caretModelSpec(method = "knn"),
earth= caretModelSpec(method = "earth"))
set.seed(157)
hideout<-capture.output(model_train_habitat <- caretList(
occ~., data=occeu.full.data.df1,
trControl=control,
tuneList=mylist))
Display model evaluation statistics
modelResults1<-resamples(model_train_habitat)
summary(modelResults1)
Call:
summary.resamples(object = modelResults1)
Models: glm, gbm, rf, knn, earth
Number of resamples: 100
Accuracy
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
glm 0.5217391 0.5869565 0.6222222 0.6308180 0.6739130 0.8043478 0
gbm 0.5777778 0.6666667 0.7111111 0.7107276 0.7555556 0.8695652 0
rf 0.6000000 0.6956522 0.7391304 0.7406948 0.7789855 0.9148936 0
knn 0.4000000 0.5777778 0.6263285 0.6312433 0.6756475 0.8478261 0
earth 0.5000000 0.6666667 0.7173913 0.7189498 0.7608696 0.9130435 0
Kappa
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
glm 0.043478261 0.1739130 0.2463054 0.2625767 0.3478261 0.6145251 0
gbm 0.152626363 0.3375859 0.4247788 0.4224862 0.5103858 0.7396226 0
rf 0.201183432 0.3910161 0.4782609 0.4830374 0.5604220 0.8303249 0
knn -0.194690265 0.1584621 0.2558021 0.2634112 0.3534978 0.6956522 0
earth 0.003766478 0.3372602 0.4347826 0.4392122 0.5221895 0.8250951 0
modelCor(resamples(model_train_habitat))
glm gbm rf knn earth
glm 1.0000000 0.2923840 0.3033874 0.1371341 0.2154149
gbm 0.2923840 1.0000000 0.6348932 0.2323403 0.5495479
rf 0.3033874 0.6348932 1.0000000 0.2509761 0.4975596
knn 0.1371341 0.2323403 0.2509761 1.0000000 0.1037231
earth 0.2154149 0.5495479 0.4975596 0.1037231 1.0000000
Create ensemble model
set.seed(456)
hideoutput<-capture.output(
lm_ens_hab<-caretEnsemble(model_train_habitat1, trControl=trainControl(method="cv", number=10,savePredictions= "final",classProbs = TRUE)))
lm_ens_hab
A glm ensemble of 5 base models: glm, gbm, rf, knn, earth
Ensemble results:
Generalized Linear Model
454 samples
5 predictor
2 classes: 'absent', 'present'
No pre-processing
Resampling: Cross-Validated (10 fold)
Summary of sample sizes: 409, 408, 408, 409, 407, 409, ...
Resampling results:
Accuracy Kappa
0.7550961 0.511276
variableImportance<-varImp(lm_ens_hab)
kable(variableImportance,digits=2,caption="Variable Importance") %>%
kable_styling(bootstrap_options = c("striped"))
Variable Importance
| |
overall |
glm |
gbm |
rf |
knn |
earth |
| corine_perWetland |
0.63 |
4.05 |
0.00 |
0.00 |
2.81 |
0.00 |
| corine_perdeciduous |
1.49 |
6.13 |
3.13 |
0.83 |
0.00 |
0.00 |
| corine_perConiferous |
1.93 |
1.72 |
1.66 |
2.35 |
2.79 |
0.00 |
| ESM1000m_singleINT_ext |
5.24 |
0.00 |
3.85 |
7.04 |
10.00 |
0.00 |
| corine_perAgriculture |
6.05 |
17.53 |
3.75 |
6.01 |
5.92 |
0.00 |
| corine_pergrass |
6.34 |
14.96 |
2.14 |
6.73 |
10.47 |
0.00 |
| varSolRad100 |
8.56 |
4.88 |
8.52 |
11.44 |
6.89 |
0.00 |
| wettprec |
8.67 |
1.44 |
10.94 |
11.76 |
6.20 |
0.00 |
| dristprec |
9.46 |
3.95 |
15.08 |
11.39 |
8.32 |
0.00 |
| annpvarrecip_eea |
11.19 |
11.58 |
12.14 |
12.79 |
15.87 |
0.00 |
| anngdd100 |
17.00 |
25.53 |
17.10 |
14.98 |
13.44 |
21.35 |
| temprang |
23.43 |
8.22 |
21.69 |
14.69 |
17.28 |
78.65 |
write.csv(variableImportance,file = paste0(genOutput,taxonkey,"_varImp.csv"))
Use EU level ensemble model (ensModel) to predict for Europe
ens_pred_hab_eu<-raster::predict(fullstack,lm_ens_hab,type="prob")
ens_pred_hab_eu1<-1-ens_pred_hab_eu
writeRaster(ens_pred_hab_eu1,filename=file.path(rasterOutput,paste("eu_",taxonkey, "_hist.tif",sep="")) , format="GTiff",overwrite=TRUE)
exportPNG(ens_pred_hab_eu1,taxonkey,taxonName=taxonName,"hist.png")
brks <- seq(0, 1, by=0.1)
nb <- length(brks)-1
pal <- colorRampPalette(rev(brewer.pal(8, 'Spectral')))
cols<-pal(nb)
plot(ens_pred_hab_eu1, breaks=brks, col=cols,lab.breaks=brks)

NA
Predict for Belgium only using EU-level ensemble model to forecast
risk under historical climate conditions
laea_grs80<-CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,-0,-0,-0,0 +units=m +no_defs ")
ens_pred_hab<-raster::predict(fullstack_be,lm_ens_hab,type="prob")
ens_pred_hab1<-1-ens_pred_hab
crs(ens_pred_hab1)<-laea_grs80
writeRaster(ens_pred_hab1, filename=file.path(rasterOutput,paste("be_",taxonkey, "_hist.tif",sep="")), format="GTiff",overwrite=TRUE)
exportPDF(ens_pred_hab1,taxonkey,taxonName=taxonName,"eu_hist.pdf")
exportPNG(ens_pred_hab1,taxonkey,taxonName=taxonName,"eu_hist.png")
brks <- seq(0, 1, by=0.1)
nb <- length(brks)-1
pal <- colorRampPalette(rev(brewer.pal(11, 'Spectral')))
cols<-pal(nb)
plot(ens_pred_hab1, breaks=brks, col=cols,lab.breaks=brks)
plot(euocc,pch=3,cex=0.75,add=TRUE)

Clip habitat stack to Belgium
use this stack for vertebrates or anything likely to be influenced
by water availability
# habitat_stack<-stack(habitat,dist2water)
# habitat_only_stack<-crop(habitat_stack,country)
# habitat_only_stack_be<-crop(habitat_only_stack,country)
use this stack for plants
habitat_stack<-stack(habitat)
habitat_only_stack<-crop(habitat_stack,country)
habitat_only_stack_be<-crop(habitat_only_stack,country)
Create individual RCP (2.6, 4.5, 8.5) climate data stacks for
Belgium
be26 <- list.files((here("./data/external/climate/byEEA_finalRCP/belgium_rcps/rcp26")),pattern='tif',full.names = T)
belgium_stack26 <- stack(be26)
be45 <- list.files((here("./data/external/climate/byEEA_finalRCP/belgium_rcps/rcp45")),pattern='tif',full.names = T)
belgium_stack45 <- stack(be45)
be85 <- list.files((here("./data/external/climate/byEEA_finalRCP/belgium_rcps/rcp85")),pattern='tif',full.names = T)
belgium_stack85 <- stack(be85)
Combine habitat stacks with climate stacks for each RCP
scenario
fullstack26<-stack(be26,habitat_only_stack_be)
fullstack45<-stack(be45,habitat_only_stack_be)
fullstack85<-stack(be85,habitat_only_stack_be)
Create and export RCP risk maps for each RCP scenario
ens_pred_hab26<-raster::predict(fullstack26,lm_ens_hab,type="prob")
ens_pred_hab26_1<-1-ens_pred_hab26
crs(ens_pred_hab26_1)<-laea_grs80
writeRaster(ens_pred_hab26_1, filename=file.path(rasterOutput,paste("be_",taxonkey, "_rcp26.tif",sep="")), format="GTiff",overwrite=TRUE)
exportPDF(ens_pred_hab26_1,taxonkey,taxonName=taxonName,"rcp26.pdf")
null device
1
ens_pred_hab45<-raster::predict(fullstack45,lm_ens_hab,type="prob")
ens_pred_hab45_1<-1-ens_pred_hab45
crs(ens_pred_hab45_1)<-laea_grs80
writeRaster(ens_pred_hab45_1, filename=file.path(rasterOutput,paste("be_",taxonkey, "_rcp45.tif",sep="")), format="GTiff",overwrite=TRUE)
exportPDF(ens_pred_hab45_1,taxonkey,taxonName=taxonName,"rcp45.pdf")
null device
1
ens_pred_hab85<-raster::predict(fullstack85,lm_ens_hab,type="prob")
ens_pred_hab85_1<-1-ens_pred_hab85
crs(ens_pred_hab85_1)<-laea_grs80
writeRaster(ens_pred_hab85_1, filename=file.path(rasterOutput,paste("be_",taxonkey, "_rcp85.tif",sep="")), format="GTiff",overwrite=TRUE)
exportPDF(ens_pred_hab85_1,taxonkey,taxonName=taxonName,"rcp85.pdf")
null device
1
Display RCP risk maps
plot(ens_pred_hab26_1,breaks=brks, col=cols,lab.breaks=brks)
plot(ens_pred_hab45_1,breaks=brks, col=cols,lab.breaks=brks)
plot(ens_pred_hab85_1,breaks=brks, col=cols,lab.breaks=brks)
Create and export “difference maps”: the difference between
predicted risk by each RCP scenario and historical climate
null device
1
png
2

png
2


Output predictor data used for SDM
df4export<-cbind(occeu.full.data.df1,coordinates(occ.full.data))
write.csv(df4export, paste(genOutput,taxonkey,"_sdmdata.csv",sep=""))
Check spatial autocorrelation of residuals to assess whether
occurrence data should be thinned
derive residuals from unthinned model
predEns1<-lm_ens_hab$ens_model$pred
obs.numeric<-ifelse(predEns1$obs == "absent",0,1)
standardize residuals
stdres<-function(obs.numeric, yhat){
num<-obs.numeric-yhat
denom<-sqrt(yhat*(1-yhat))
return(num/denom)
}
hab.res<-stdres(obs.numeric,predEns1$present)
res.best.coords<-cbind(coordinates(occ.full.data),hab.res)
res.best.geo<-as.geodata(res.best.coords,coords.col=1:2,data.col = 3)
summary(res.best.geo) #note distance is in meters
Number of data points: 454
Coordinates summary
longitude latitude
min 2733429 1653675
max 5754770 5247500
Distance summary
min max
966.5814 4034634.4597
Data summary
Min. 1st Qu. Median Mean 3rd Qu. Max.
-2.60157202 -0.70462124 0.19909308 0.03617965 0.65557610 3.21251457
Check Morans I.
#If Moran's I is very low (<0.10), or not significant, do not need to thin occurrences.
library(ape)
res.best.df<-as.data.frame(res.best.coords)
occ.dists <- as.matrix(dist(cbind(res.best.df[1], res.best.df[2])))
occ.dists.inv <- 1/occ.dists
diag(occ.dists.inv) <- 0
Moran.I(res.best.df$hab.res,occ.dists.inv,scaled=TRUE,alternative="greater")
$observed
[1] -0.01447212
$expected
[1] -0.002207506
$sd
[1] 0.008547577
$p.value
[1] 0.9243372
Code for class conformal prediction function
Create confidence maps
brks <- seq(0, 1, by=0.1)
nb <- length(brks)-1
pal <- colorRampPalette(rev(brewer.pal(4, 'Spectral')))
cols<-pal(nb)
confidenceMaps<-function(x,taxonkey,taxonName,maptype){
pvals_dataframe<-get("x")
data.xyz <- pvals_dataframe[c("x","y","conf")]
rst <- rasterFromXYZ(data.xyz)
crs(rst)<-CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs")
plot(rst,breaks=brks, col=cols,lab.breaks=brks)
writeRaster(rst, filename=file.path(rasterOutput,paste("be_",taxonkey, "_",maptype,".tif",sep="")), format="GTiff",overwrite=TRUE)
exportPDF(rst,taxonkey,taxonName=taxonName,nameextension= paste(maptype,".pdf",sep=""))
}
hist.conf.map<-confidenceMaps(pvalsdf_hist,taxonkey,taxonName,maptype="hist_conf")

rcp26.conf.map<-confidenceMaps(pvalsdf_rcp26,taxonkey,taxonName,maptype="rcp26_conf")

rcp45.conf.map<-confidenceMaps(pvalsdf_rcp45,taxonkey,taxonName,maptype="rcp45_conf")

rcp85.conf.map<-confidenceMaps(pvalsdf_rcp85,taxonkey,taxonName,maptype="rcp85_conf")

Model evalution summary
meanResults<-summary(modelResults1)
eu_accuracy<-meanResults$statistics$Accuracy
eu_kappa<-meanResults$statistics$Kappa
write.csv(eu_accuracy,file=paste0(genOutput,taxonkey,"_accuracy.csv"))
write.csv(eu_kappa,file=paste0(genOutput,taxonkey,"_kappa.csv"))
# derive sensitivity and specificity
table(predEns1$pred,predEns1$obs)
absent present
absent 181 70
present 41 162
sensitivity(predEns1$pred,predEns1$obs)
[1] 0.8153153
specificity(predEns1$pred,predEns1$obs)
[1] 0.6982759
sens<-sensitivity(predEns1$pred,predEns1$obs)
spec<-specificity(predEns1$pred,predEns1$obs)
moransI<-Moran.I(res.best.df$hab.res,occ.dists.inv,scaled=TRUE,alternative="greater")
MoransI<-moransI$observed
ensembleEvaluation<-(lm_ens_hab$ens_model$results[2:5])
modelEvaluation<-cbind(sens,spec,MoransI,ensembleEvaluation)
write.csv(modelEvaluation,file=paste0(genOutput,taxonkey,"_ModelEval.csv"))
Generate and export response curves in order of variable
importance
topPreds <- variableImportance[with(variableImportance,order(-overall)),]
varNames<-rownames(topPreds)
# combine predictions from each model for each variable
partial_gbm<-function(x){
m.gbm<-pdp::partial(model_train_habitat$gbm$finalModel,pred.var=paste(x),train = occeu.full.data.df1,type="classification",
prob=TRUE,n.trees= model_train_habitat$gbm$finalModel$n.trees, which.class = 2,grid.resolution=nrow(occeu.full.data.df1))
}
gbm.partial.list<-lapply(varNames,partial_gbm)
partial_glm<-function(x){
m.glm<-pdp::partial(model_train_habitat$glm$finalModel,pred.var=paste(x),train = occeu.full.data.df1,type="classification",
prob=TRUE,which.class = 2,grid.resolution=nrow(occeu.full.data.df1))
}
glm.partial.list<-lapply(varNames,partial_glm)
partial_rf<-function(x){
pdp::partial(model_train_habitat$rf$finalModel,pred.var=paste(x),train = occeu.full.data.df1,type="classification",
prob=TRUE,which.class = 2,grid.resolution=nrow(occeu.full.data.df1))
}
rf.partial.list<-lapply(varNames,partial_rf)
partial_knn<-function(x){
m.knn<-pdp::partial(model_train_habitat$knn,pred.var=paste(x),train = occeu.full.data.df1,type="classification",
prob=TRUE,which.class = 2,grid.resolution=nrow(occeu.full.data.df1))
}
knn.partial.list<-lapply(varNames,partial_knn)
partial_mars<-function(x){
m.mars<-pdp::partial(model_train_habitat$earth$finalModel,pred.var=paste(x),train = occeu.full.data.df1,type="classification",
prob=TRUE,which.class = 2,grid.resolution=nrow(occeu.full.data.df1))
}
mars.partial.list<-lapply(varNames,partial_mars)
names(glm.partial.list)<-varNames
names(gbm.partial.list)<-varNames
names(rf.partial.list)<-varNames
names(knn.partial.list)<-varNames
names(mars.partial.list)<-varNames
glm.partial.df<-as.data.frame(glm.partial.list)
gbm.partial.df<-as.data.frame(gbm.partial.list)
rf.partial.df<-as.data.frame(rf.partial.list)
knn.partial.df<-as.data.frame(knn.partial.list)
mars.partial.df<-as.data.frame(mars.partial.list)
predx<-data.frame()
predy<-data.frame()
for (i in varNames){
predx <- rbind(predx, as.data.frame(paste(i,i,sep=".")))
predy<- rbind(predy,as.data.frame(paste(i,"yhat",sep=".")))
}
names(predx)<-""
names(predy)<-""
predx1<-t(predx)
predy1<-t(predy)
glm.partial.df$data<-'GLM'
gbm.partial.df$data<-'GBM'
rf.partial.df$data<-'RF'
knn.partial.df$data<-'KNN'
mars.partial.df$data<-'MARS'
all_dfs<-rbind.data.frame(glm.partial.df,gbm.partial.df,rf.partial.df,knn.partial.df,mars.partial.df)
responseCurves<-function(x,y) {
colors <- c("GLM" = "gray", "GBM"="red","RF"="blueviolet","KNN"="chartreuse", "MARS"= "hotpink")
ggplot(all_dfs,(aes(x=.data[[x]],y=.data[[y]]))) +
geom_line(aes(color = data), size =1.2, position=position_dodge(width=0.2))+
theme_bw()+
labs(y="Partial probability", x= gsub("//..*","",x),color="Legend") +
scale_color_manual(values = colors)
}
allplots<-map2(predx1,predy1, ~responseCurves(.x,.y))
#export plots as PNGs
for(i in seq_along(allplots)){
png(paste0(genOutput,taxonkey,"_",i,".png"),width = 5, height = 5, units = "in",res=300)
print(allplots[[i]])
dev.off()
}
Plot response curves
par(mfrow=c(3,3))
for(i in seq_along(allplots)){
print(allplots[[i]])
}












LS0tDQp0aXRsZTogIldvcmtmbG93IHRvIGNyZWF0ZSByaXNrIGFuZCBjb25maWRlbmNlIG1hcHMgZm9yIGByIHBhcmFtcyRzcGVjaWVzYCINCmF1dGhvcjogIkFteSBKLlMgRGF2aXMiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogICAgaHRtbF9ub3RlYm9vazoNCiAgICAgdG9jOiB0cnVlDQogICAgIHRvY19kZXB0aDogMw0KICAgICAgDQpwYXJhbXM6DQogIHNwZWNpZXM6ICJWYWNjaW5pdW0gY29yeW1ib3N1bSINCiAgR0JJRl90YXhvbmtleTogIjI4ODI4NDkiDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCg0KICANCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSxlcnJvciA9IFRSVUUpDQpgYGANCg0KDQoNCmBgYHtyIGxvYWQgbGlicmFyaWVzLGVjaG89RkFMU0UsbWVzc2FnZT1GQUxTRX0NCm9wdGlvbnMoInJnZGFsX3Nob3dfZXhwb3J0VG9Qcm9qNF93YXJuaW5ncyI9Im5vbmUiKQ0KbGlicmFyeShzZG0pDQpsaWJyYXJ5KHJnZGFsKQ0KbGlicmFyeShyYXN0ZXIpDQpsaWJyYXJ5KHNwb2NjKQ0KbGlicmFyeShzcCkNCmxpYnJhcnkocmdlb3MpDQpsaWJyYXJ5KFNETVBsYXkpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KGNhcmV0RW5zZW1ibGUpDQpsaWJyYXJ5KGdibSkNCmxpYnJhcnkodHJpYXMpDQpsaWJyYXJ5KHJnYmlmKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KG1hcHRvb2xzKQ0KbGlicmFyeShkaXNtbykNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGdlb1IpDQpsaWJyYXJ5KHBkcCkNCmxpYnJhcnkocHVycnIpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KENvb3JkaW5hdGVDbGVhbmVyKQ0KbGlicmFyeShodW1ib2xkdCkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQoNCmBgYA0KDQojIyMgQ3JlYXRlIGEgZ2xvYmFsIFNETSBmb3Igd2VpZ2h0aW5nIHBzZXVkb2Fic2VuY2VzIA0KDQojIyMjIFJlYWQgaW4gdGhlIGdsb2JhbCBvY2N1cnJlbmNlIGRhdGEgZm9yIHRoZSB0YXJnZXQgc3BlY2llcyB0aGF0IHdhcyBkb3dubG9hZGVkIHVzaW5nICJnbG9iYWxfZG93bmxvYWQuUm1kIg0KDQpgYGB7ciByZXRyaWV2ZSBnbG9iYWwgb2NjdXJyZW5jZSBmaWxlLCBlY2hvPUZBTFNFfQ0KI3RheG9uTmFtZSA8LSByZWFkbGluZShwcm9tcHQ9IkVudGVyIHNwZWNpZXMgbmFtZTogIikNCiANCiByZWFkbmFtZSA9IGZ1bmN0aW9uKCkjIEdldCB0aGUgc3BlY2llcyBuYW1lDQogeyANCiAgIHBhcmFtcyRzcGVjaWVzDQogfQ0KIHNwZWNpZXM9cmVhZG5hbWUoKQ0KIHNwZWNpZXMNCiANCiByZWFka2V5ID0gZnVuY3Rpb24oKSMgR2V0IHRoZSB0YXhvbiBrZXkNCiB7IA0KICAgcGFyYW1zJEdCSUZfdGF4b25rZXkNCiB9DQogR0JJRl90YXhvbmtleT1yZWFka2V5KCkNCiBHQklGX3RheG9ua2V5DQoNCiMjIyNpZiBub3QgcmVuZGVyaW5nIGh0bWwgcHV0IHNwZWNpZXMgaW5mbyBoZXJlDQogICBzcGVjaWVzPC0gIlZhY2Npbml1bSBjb3J5bWJvc3VtIg0KICAgR0JJRl90YXhvbmtleTwtICIyODgyODQ5Ig0KDQp0YXhvbk5hbWU8LXNwZWNpZXMNCnRheG9ua2V5PC1HQklGX3RheG9ua2V5DQpnYmlmX2ZpbGVuYW1lPC0gcGFzdGUodGF4b25OYW1lLCIuY3N2IixzZXA9IiIpICNhZGQgbmFtZSBvZiBzcGVjaWVzIGJlaW5nIG1vZGVsZWQgKHRoaXMgc2hvdWxkIGJlIHRoZSBzYW1lIG5hbWUgaW4geW91ciBvcmlnaW5hbCBzcGVjaWVzIGxpc3QpDQogICAgICAgICAgICAgICAgICAgICAgDQoNCmdsb2JhbCA8LSByZWFkLmNzdihmaWxlID0gaGVyZSgiZGF0YSIsICJyYXciLCBnYmlmX2ZpbGVuYW1lKSkNCiANCmBgYA0KIyMjIyBTcGVjaWZ5IHBhdGhzIGZvciBvdXRwdXQgKGRlZmF1bHRzIHRvIGZpbGUgc3RydWN0dXJlIGluIFJlYWRNZSkNCmBgYHtyIGRlZmluZU91dHB1dFBhdGhzLCBlY2hvPUZBTFNFfQ0KcmFzdGVyT3V0cHV0PC1oZXJlKCJkYXRhL3Byb2Nlc3NlZC9nZW90aWZmcy8iKQ0KcGRmT3V0cHV0PC1oZXJlKCJkYXRhL3Byb2Nlc3NlZC9wZGYvIikNCmdlbk91dHB1dDwtaGVyZSgiZGF0YS9wcm9jZXNzZWQvZ2VuZXJhbC8vIikNCmBgYA0KIyMjIEZpbHRlciBnbG9iYWwgb2NjdXJyZW5jZSBkYXRhIA0KDQpgYGB7ciBkZWNpbWFsIHBsYWNlcyxlY2hvPUZBTFNFfQ0KZGVjaW1hbHBsYWNlcyA8LSBmdW5jdGlvbih4KSB7DQogIGlmIChhYnMoeCAtIHJvdW5kKHgpKSA+IC5NYWNoaW5lJGRvdWJsZS5lcHNeMC41KSB7DQogICAgbmNoYXIoc3Ryc3BsaXQoc3ViKCcwKyQnLCAnJywgYXMuY2hhcmFjdGVyKHgpKSwgIi4iLCBmaXhlZCA9IFRSVUUpW1sxXV1bWzJdXSkNCiAgfSBlbHNlIHsNCiAgICByZXR1cm4oMCkNCiAgfQ0KfQ0KYGBgDQoNCmBgYHtyIGZpbHRlciBnbG9iYWwgb2NjdXJyZW5jZSBkYXRhfQ0KDQojcmVtb3ZlIHVudmVyaWZpZWQgcmVjb3Jkcw0KaWRlbnRpZmljYXRpb25WZXJpZmljYXRpb25TdGF0dXNfdG9fZGlzY2FyZCA8LSBjKCJ1bnZlcmlmaWVkIiwgInVudmFsaWRhdGVkIiwibm90IGFibGUgdG8gdmFsaWRhdGUiLCJjb250cm9sIGNvdWxkIG5vdCBiZSBjb25jbHVzaXZlIGR1ZSB0byBpbnN1ZmZpY2llbnQga25vd2xlZGdlIikNCg0KI2VudGVyIHZhbHVlIGZvciBtYXggY29vcmRpbmF0ZSB1bmNlcnRhaW50eSBpbiBtZXRlcnMuDQoNCmdsb2JhbC5vY2M8LWdsb2JhbCAlPiUNCiAgZmlsdGVyKHNwZWNpZXNLZXk9PXRheG9ua2V5KSAlPiUgICAjdXNpbmcgdGF4b25LZXkgZmlsdGVycyBvdXQgYWNjZXB0ZWQgc3lub255bXMNCiAgZmlsdGVyKGlzLm5hKGNvb3JkaW5hdGVVbmNlcnRhaW50eUluTWV0ZXJzKXwgY29vcmRpbmF0ZVVuY2VydGFpbnR5SW5NZXRlcnM8IDEwMDApICU+JQ0KICBmaWx0ZXIoIXN0cl90b19sb3dlcihpZGVudGlmaWNhdGlvblZlcmlmaWNhdGlvblN0YXR1cykgJWluJSBpZGVudGlmaWNhdGlvblZlcmlmaWNhdGlvblN0YXR1c190b19kaXNjYXJkKQ0KDQpnbG9iYWwub2NjJGxvbl9kcGxhY2VzPC1zYXBwbHkoZ2xvYmFsLm9jYyRkZWNpbWFsTG9uZ2l0dWRlLCBmdW5jdGlvbih4KSBkZWNpbWFscGxhY2VzKHgpKQ0KZ2xvYmFsLm9jYyRsYXRfZHBsYWNlczwtc2FwcGx5KGdsb2JhbC5vY2MkZGVjaW1hbExhdGl0dWRlLCBmdW5jdGlvbih4KSBkZWNpbWFscGxhY2VzKHgpKQ0KZ2xvYmFsLm9jY1tnbG9iYWwub2NjJGxvbl9kcGxhY2VzIDwgNCYgZ2xvYmFsLm9jYyRsYXRfZHBsYWNlcyA8IDQgLCBdPC1OQQ0KZ2xvYmFsLm9jYzwtZ2xvYmFsLm9jY1sgd2hpY2goIWlzLm5hKGdsb2JhbC5vY2MkbG9uX2RwbGFjZXMpKSxdDQpnbG9iYWwub2NjPC13aXRoaW4oZ2xvYmFsLm9jYyxybSgibG9uX2RwbGFjZXMiLCJsYXRfZHBsYWNlcyIpKQ0KZ2xvYmFsLm9jYzwtZ2xvYmFsLm9jY1t3aGljaCggZ2xvYmFsLm9jYyR5ZWFyID4gMTk3NSAmIGdsb2JhbC5vY2MkeWVhciA8IDIwMjApLF0NCmBgYCANCg0KIyMjIyBDb252ZXJ0IGdsb2JhbCBvY2N1cnJlbmNlcyB0byBzcGF0aWFsIHBvaW50cyBuZWVkZWQgZm9yIG1vZGVsbGluZw0KDQpgYGB7ciBvY2MgdG8gc3BhdGlhbERhdGEsZWNobz1GQUxTRX0NCmdsb2JhbC5vY2M8LWdsb2JhbC5vY2NbYygiZGVjaW1hbExvbmdpdHVkZSIsICJkZWNpbWFsTGF0aXR1ZGUiKV0NCmNvb3JkaW5hdGVzKGdsb2JhbC5vY2MpPC0gYygiZGVjaW1hbExvbmdpdHVkZSIsICJkZWNpbWFsTGF0aXR1ZGUiKQ0KcGxvdChnbG9iYWwub2NjKQ0KDQpnbG9iYWwub2NjLkxMPC1kYXRhLmZyYW1lKGdsb2JhbC5vY2MpW2MoMToyKV0gI2V4dHJhY3QgbG9uZyBhbmQgbGF0IA0KDQpgYGANCiMjIyMgRmxhZyBhbmQgcmVtb3ZlIGNlbnRyb2lkcyBhbmQgaW52YWxpZCBnZW9yZWZlcmVuY2VkIHBvaW50cw0KYGBge3IgcmVtb3ZlIGludmFsaWRfcHRzX2FuZF9jZW50cm9pZHMsZWNobz1GQUxTRX0NCmdsb2JhbC5vY2MuTEwkc3BlY2llczwtcmVwKCJWYWNjaW5pdW0gY29yeW1ib3N1bSIsbnJvdyhnbG9iYWwub2NjLkxMKSkgIA0KDQpmbGFnc19yZXBvcnQ8LWNsZWFuX2Nvb3JkaW5hdGVzKHggPSBnbG9iYWwub2NjLkxMLCBsb249ICJkZWNpbWFsTG9uZ2l0dWRlIiwgbGF0PSAiZGVjaW1hbExhdGl0dWRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdHMgPSBjKCJjYXBpdGFscyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAiY2VudHJvaWRzIiwiZ2JpZiIsICJpbnN0aXR1dGlvbnMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZWFzIiwgInplcm9zIikpDQoNCmNsZWFuZWQ8LWNsZWFuX2Nvb3JkaW5hdGVzKHggPSBnbG9iYWwub2NjLkxMLCBsb249ICJkZWNpbWFsTG9uZ2l0dWRlIiwgbGF0PSAiZGVjaW1hbExhdGl0dWRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdHMgPSBjKCJjYXBpdGFscyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAiY2VudHJvaWRzIiwiZ2JpZiIsICJpbnN0aXR1dGlvbnMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZWFzIiwgInplcm9zIiksdmFsdWU9ImNsZWFuIikNCmdsb2JhbC5vY2MuTEwuY2xlYW5lZDwtc3Vic2V0KGNsZWFuZWQsc2VsZWN0PSAtYyhzcGVjaWVzKSkNCmBgYA0KIyMjIyBDcmVhdGUgZ2xvYmFsIHJhc3RlcnN0YWNrIHVzaW5nIENIRUxTQSBkYXRhIGZvciBtb2RlbCBidWlsZGluZw0KDQpgYGB7ciBpbXBvcnRDaGVsc2FEYXRhLGVjaG89VFJVRX0NCmdsb2JhbGNsaW1yYXN0ZXJzIDwtIGxpc3QuZmlsZXMoKGhlcmUoIi4vZGF0YS9leHRlcm5hbC9jbGltYXRlL3RyaWFzX0NIRUxTQSIpKSxwYXR0ZXJuPSd0aWYnLGZ1bGwubmFtZXMgPSBUKSAjaW1wb3J0IENIRUxTQSBkYXRhDQpnbG9iYWxjbGltcHJlZHMgPC0gc3RhY2soZ2xvYmFsY2xpbXJhc3RlcnMpDQpgYGANCg0KIyMjIyBVc2UgU0RNdGFiIGNvbW1hbmQgZnJvbSB0aGUgU0RNUGxheSBwYWNrYWdlIHRvIHJlbW92ZSBkdXBsaWNhdGVzIHBlciBncmlkIGNlbGwNCg0KYGBge3IgcmVtb3ZlIGdsb2JhbCBkdXBsaWNhdGVzLGVjaG89VFJVRX0NCg0KZ2xvYmFsLlNETXRhYmxlPC0gU0RNUGxheTo6OlNETXRhYihnbG9iYWwub2NjLkxMLmNsZWFuZWQsIGdsb2JhbGNsaW1wcmVkcywgdW5pcXVlLmRhdGEgPSBUUlVFLGJhY2tncm91bmQubmI9IDApICMNCm51bWIucHNldWRvYWJzIDwtbGVuZ3RoKGdsb2JhbC5TRE10YWJsZSRpZCkgI3NldHMgdGhlIG51bWJlciBvZiBwc2V1ZG9hYnNlbmNlcyBlcXVhbCB0byBudW1iZXIgb2YgdW5pcXVlIHByZXNlbmNlcw0KDQoNCmdsb2JhbC5vY2Muc3A8LWdsb2JhbC5TRE10YWJsZVtjKCJsb25naXR1ZGUiLCAibGF0aXR1ZGUiKV0NCmNvb3JkaW5hdGVzKGdsb2JhbC5vY2Muc3ApPC0gYygibG9uZ2l0dWRlIiwgImxhdGl0dWRlIikNCmdsb2JhbC5vY2Muc3Akc3BlY2llczwtIHJlcCgxLGxlbmd0aChnbG9iYWwub2NjLnNwJGxhdGl0dWRlKSkgI2FkZHMgY29sdW1ucyBpbmRpY2F0aW5nIHNwZWNpZXMgcHJlc2VuY2UgbmVlZGVkIGZvciBtb2RlbGluZw0KYGBgDQoNCiMjIyBTZWxlY3Qgd3dmIGVjb3JlZ2lvbnMgdGhhdCBjb250YWluIGdsb2JhbCBvY2N1cnJlbmNlIHBvaW50cw0KDQpgYGB7ciBzZWxlY3QgZWNvcmVnaW9ucyxjYWNoZT0gVFJVRSxlY2hvPUZBTFNFfQ0Kd3dmX2Vjbzwtc2hhcGVmaWxlKGhlcmUoIi4vZGF0YS9leHRlcm5hbC9HSVMvb2ZmaWNpYWwvd3dmX3RlcnJfZWNvcy5zaHAiKSkNCmNycyhnbG9iYWwub2NjLnNwKTwtQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyArZWxscHM9V0dTODQgK3Rvd2dzODQ9MCwwLDAiKQ0Kb2NjX2Vjb0ludGVyc2VjdCA8LSBvdmVyKHd3Zl9lY28sZ2xvYmFsLm9jYy5zcCkgDQp3d2ZfZWNvU3ViMSA8LSB3d2ZfZWNvWyFpcy5uYShvY2NfZWNvSW50ZXJzZWN0JHNwZWNpZXMpLF0NCmBgYA0KDQojIyMgU3BlY2lmeSBhbmQgaW1wb3J0IGJpYXMgZ3JpZHMgZm9yIHJlbGV2YW50IHRheG9ub21pYyBncm91cCAoZS5nIHZhc2N1bGFyIHBsYW50cykgDQoNCmBgYHtyIGltcG9ydEJpYXNncmlkfQ0KYmlhc2dyaWQ8LXJhc3RlcihoZXJlKCIuL2RhdGEvZXh0ZXJuYWwvYmlhc19ncmlkcy9maW5hbC90cmlhcy9wbGFudHNfMWRlZ19taW41LnRpZiIpKSMjIyBzcGVjaWZ5IGFwcHJvcHJpYXRlIGJpYXMgZ3JpZCBoZXJlDQoNCmBgYA0KDQojIyMgU3Vic2V0IGJpYXMgZ3JpZCBieSBlY29yZWdpb25zIGNvbnRhaW5pbmcgb2NjdXJyZW5jZSBwb2ludHMNCg0KYGBge3Igc3Vic2V0Qmlhc2dyaWR9DQpleHRfd3dmX2Vjb1N1YjwtZXh0ZW50KHd3Zl9lY29TdWIxKQ0KYmlhc2dyaWRfY3JvcDwtY3JvcChiaWFzZ3JpZCxleHRfd3dmX2Vjb1N1YikNCmJpYXNncmlkX3N1YjwtbWFzayhiaWFzZ3JpZF9jcm9wLHd3Zl9lY29TdWIxKQ0KIHBsb3QoYmlhc2dyaWRfc3ViKQ0KIHBsb3Qod3dmX2Vjb1N1YjEsYWRkPVRSVUUpDQogDQpgYGANCg0KIyMjICBVc2UgcmFuZG9tUG9pbnRzIGZ1bmN0aW9uIGZyb20gZGlzbW8gcGFja2FnZSB0byBsb2NhdGUgcHNlZHVvYmFzZW5jZXMgd2l0aGluIHRoZSBiaWFzIGdyaWQgc3Vic2V0IA0KDQpgYGB7ciBsb2NhdGVQc2V1ZG9fYWJzZW5jZXN9DQpzZXQuc2VlZCg3MjgpDQpnbG9iYWxfcG9pbnRzPC1yYW5kb21Qb2ludHMoYmlhc2dyaWRfc3ViLCBudW1iLnBzZXVkb2FicywgZ2xvYmFsLm9jYy5zcCwgZXh0PU5VTEwsIGV4dGY9MS4xLCBleGNsdWRlcD1UUlVFLCBwcm9iPUZBTFNFLCBjZWxsbnVtYmVycz1GQUxTRSwgdHJ5Zj03MCwgd2Fybj0yLCBsb25sYXRDb3JyZWN0aW9uPVRSVUUpIA0KIyB3aWxsIHRocm93IGEgd2FybmluZyBpZiByYW5kb21Qb2ludHMgZ2VuZXJhdGVkIGlzIGxlc3MgdGhhbiBudW1iLnBzZXVkb2Ficy4gSWYgdGhpcyBoYXBwZW5zLCBpbmNyZWFzZSB0aGUgbnVtYmVyIG9mIHRyeWYgb3IgaWdub3JlIGJpYXMgZ3JpZCBhbmQgc2FtcGxlIGZyb20gZWNvcmVnaW9uIG9ubHkuDQpgYGANCg0KIyMjIE9QVElPTkFMOiBTYW1wbGUgZnJvbSBlY29yZWdpb24gb25seSANCmBgYHtyIGxvY2F0ZVBzZXVkb19hYnNlbmNlc19pbkVjb3JlZ2lvbnN9DQojICB3d2ZfZ3JpZDwtcmFzdGVyKGhlcmUoIi4vZGF0YS9leHRlcm5hbC9HSVMvd3dmX2Vjb3JlZ2lvbnNfdjEudGlmIikpDQojICBlY29yZWdpb25zX3Jhc3RlcjwtbWFzayh3d2ZfZ3JpZCx3d2ZfZWNvU3ViMSkNCiMgIHNldC5zZWVkKDc2OCkNCiMgIGdsb2JhbF9wb2ludHM8LXJhbmRvbVBvaW50cyhlY29yZWdpb25zX3Jhc3RlciwgbnVtYi5wc2V1ZG9hYnMsIGdsb2JhbC5vY2Muc3AsIGV4dD1OVUxMLCBleHRmPTEuMSwgZXhjbHVkZXA9VFJVRSwgcHJvYj1GQUxTRSwgY2VsbG51bWJlcnM9RkFMU0UsIHRyeWY9MTUwLCB3YXJuPTIsIGxvbmxhdENvcnJlY3Rpb249VFJVRSkgDQpgYGANCg0KIyMjIEV4dHJhY3QgZ2VuZXJhdGVkIHBzZXVkbyBhYnNlbmNlcyBhbmQgY3JlYXRlIHByZXNlbmNlLXBzZXVkb2Jhc2VuY2UgZGF0YXNldCANCg0KYGBge3IgY3JlYXRlX3ByZXNlbmNlX2Fic2VuY2VfZGF0YXNldCxlY2hvPUZBTFNFfQ0KZ2xvYmFsX3BzZXVkb0FiczwtYXMuZGF0YS5mcmFtZShnbG9iYWxfcG9pbnRzKQ0KY29vcmRpbmF0ZXMoZ2xvYmFsX3BzZXVkb0Ficyk8LWMoIngiLCJ5IikNCmNycyhnbG9iYWxfcHNldWRvQWJzKTwtQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyArZWxscHM9V0dTODQgK3Rvd2dzODQ9MCwwLDAiKQ0KZ2xvYmFsX3BzZXVkb0FicyRzcGVjaWVzPC1yZXAoMCxsZW5ndGgoZ2xvYmFsX3BzZXVkb0FicyR4KSkNCmdsb2JhbF9wcmVzYWJzPC0gc3BSYmluZChnbG9iYWwub2NjLnNwLGdsb2JhbF9wc2V1ZG9BYnMpICMgam9pbiBwc2V1ZG9hYnNlbmNlcyB3aXRoIHByZXNlbmNlcyAob2NjdXJyZW5jZXMpDQp3cml0ZU9HUihnbG9iYWxfcHJlc2FicywgZHNuPShoZXJlKCIuL2RhdGEvcHJvY2Vzc2VkL3NkbV9vY2NfcHRzL2dsb2JhbCIpKSwgbGF5ZXI9cGFzdGUodGF4b25OYW1lLCJfZ2xvYmFsU0RNIiksIGRyaXZlciA9ICJFU1JJIFNoYXBlZmlsZSIsIG92ZXJ3cml0ZV9sYXllcj1UUlVFKQ0KYGBgDQoNCiMjIyBFeHRyYWN0IGNsaW1hdGUgZGF0YSBmb3IgZ2xvYmFsIHNjYWxlIG1vZGVsbGluZw0KDQpgYGB7ciBleHRyYWN0Q2xpbWF0ZURhdGEsbWVzc2FnZT1GQUxTRX0NCg0KZ2xvYmFsLmRhdGEgPC0gc2RtRGF0YShzcGVjaWVzfi4sdHJhaW49Z2xvYmFsX3ByZXNhYnMsIHByZWRpY3RvcnM9Z2xvYmFsY2xpbXByZWRzKQ0KZ2xvYmFsLmRhdGEuZGY8LWFzLmRhdGEuZnJhbWUoZ2xvYmFsLmRhdGEpDQpgYGANCg0KIyMjIElkZW50aWZ5IGhpZ2hseSBjb3JyZWxhdGVkIHByZWRpY3RvcnMNCg0KYGBge3IgaWRlbnRpZnlDb3JyZWxhdGVkUHJlZHMsZWNobz1GQUxTRX0NCmNvcnJlbGF0aW9uTWF0cml4PC1jb3IoZ2xvYmFsLmRhdGEuZGZbLC1jKDEpXSkNCmhpZ2hseUNvcnJlbGF0ZWQgPC0gZmluZENvcnJlbGF0aW9uKGNvcnJlbGF0aW9uTWF0cml4LCBjdXRvZmY9MC43LGV4YWN0PVRSVUUsbmFtZXM9VFJVRSkNCnByZWRzPC1hcy5kYXRhLmZyYW1lKGhpZ2hseUNvcnJlbGF0ZWQpDQprYWJsZShwcmVkcykgJT4lDQprYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIpKQ0KYGBgDQoNCiMjIyBSZW1vdmUgaGlnaGx5IGNvcnJlbGF0ZWQgcHJlZGljdG9ycyBmcm9tIGRhdGFmcmFtZSANCg0KYGBge3IgcmVtb3ZlQ29ycmVsYXRlZFByZWRzLGVjaG89RkFMU0UsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZ2xvYmFsLmRhdGEuZGYuc3Vic2V0PC1zZWxlY3QgKGdsb2JhbC5kYXRhLmRmLC1jKGhpZ2hseUNvcnJlbGF0ZWQpKQ0KZ2xvYmFsLmRhdGEuZGYuc3Vic2V0PC13aXRoaW4oZ2xvYmFsLmRhdGEuZGYuc3Vic2V0LHJtKCJySUQiKSkNCmdsb2JhbC5kYXRhLmRmLnN1YnNldCRzcGVjaWVzPC1hcy5mYWN0b3IoZ2xvYmFsLmRhdGEuZGYuc3Vic2V0JHNwZWNpZXMpICNsYXRlciBzdGVwcyByZXF1aXJlIG5vbiBudW1lcmljIGRlcGVuZGVudCB2YXJpYWJsZQ0KbGV2ZWxzKGdsb2JhbC5kYXRhLmRmLnN1YnNldCRzcGVjaWVzKTwtYygiYWJzZW50IiwicHJlc2VudCIpDQpnbG9iYWwuZGF0YS5kZi5zdWJzZXQkc3BlY2llcyA8LSByZWxldmVsKGdsb2JhbC5kYXRhLmRmLnN1YnNldCRzcGVjaWVzLCByZWYgPSAicHJlc2VudCIpDQpgYGANCg0KIyMjIENvcnJlY3QgZ2xvYmFsIGNsaW0gcHJlZHMgdmFsdWVzIGZyb20gaW50ZWdlciBmb3JtYXQNCg0KYGBge3IgY29ycmVjdFByZWRzLGVjaG89RkFMU0V9DQpkaXZpZGUxMDwtZnVuY3Rpb24oeCl7DQogIHZhbHVlPC14LzEwDQogIHJldHVybih2YWx1ZSkNCn0NCg0KDQpnbG9iYWwuZGF0YS5kZi51bmNvcjwtY2JpbmQoInNwZWNpZXMiPSAgZ2xvYmFsLmRhdGEuZGYuc3Vic2V0JHNwZWNpZXMsZGl2aWRlMTAoZ2xvYmFsLmRhdGEuZGYuc3Vic2V0WywtYygxKV0pKQ0KYGBgDQoNCiMjIyBVc2UgY2FyZXRMaXN0IGZyb20gQ2FyZXQgcGFja2FnZSB0byBydW4gbXVsdGlwbGUgbWFjaGluZSBsZWFybmluZyBtb2RlbHMNCg0KYGBge3IgcnVuX2dsb2JhbE1vZGVsLGNhY2hlPVRSVUUscmVzdWx0cz0gJ2hpZGUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0icmVwZWF0ZWRjdiIsbnVtYmVyPTEwLCByZXBlYXRzPTEwLCBzYXZlUHJlZGljdGlvbnM9ImZpbmFsIiwgcHJlUHJvYz1jKCJjZW50ZXIiLCJzY2FsZSIpLGNsYXNzUHJvYnM9VFJVRSkNCmNsYXNzTGlzdDEgPC0gYygiZ2xtIiwiZ2JtIiwicmYiLCJrbm4iLCAiZWFydGgiKQ0Kc2V0LnNlZWQoNDU3KQ0KaGlkZW91dHB1dDwtY2FwdHVyZS5vdXRwdXQoDQpnbG9iYWxfdHJhaW4gPC0gY2FyZXRMaXN0KA0KICBzcGVjaWVzfi4sIGRhdGE9IGdsb2JhbC5kYXRhLmRmLnVuY29yLA0KICB0ckNvbnRyb2w9Y29udHJvbCwNCiAgbWV0aG9kTGlzdD1jbGFzc0xpc3QxDQopKQ0KYGBgDQpgYGB7ciBwcmludF9nbG9iYWxNb2RlbFJlc3VsdHMsd2FybmluZz1GQUxTRX0NCm1vZGVsUmVzdWx0czwtcmVzYW1wbGVzKGdsb2JhbF90cmFpbikNCnN1bW1hcnkobW9kZWxSZXN1bHRzKSMgZGlzcGxheXMgYWNjdXJhY3kgb2YgZWFjaCBtb2RlbA0KbW9kZWxDb3IocmVzYW1wbGVzKGdsb2JhbF90cmFpbikpIyBzaG93cyBjb3JyZWxhdGlvbiBhbW9uZyBtb2RlbHMuV2Vha2x5IGNvcnJlbGF0ZWQgYWxnb3JpdGhtcyBhcmUgcGVyc3Vhc2l2ZSBmb3Igc3RhY2tpbmcgdGhlbSBpbiBlbnNlbWJsZS4NCmBgYA0KDQojIyMgQ3JlYXRlIGVuc2VtYmxlIG1vZGVsIChjb21iaW5lIGluZGl2aWR1YWwgbW9kZWxzIGludG8gb25lKSANCg0KYGBge3IgcnVuIGdsb2JhbF9lbnNlbWJsZX0NCnNldC5zZWVkKDQ3OCkNCmdsb2JhbF9zdGFjayA8LSBjYXJldFN0YWNrKA0KICBnbG9iYWxfdHJhaW4sIA0KICBtZXRob2Q9ImdsbSIsDQogIHRyQ29udHJvbD10cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgbnVtYmVyPTEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmVQcmVkaWN0aW9ucz0gImZpbmFsIiAgKSkNCnByaW50KGdsb2JhbF9zdGFjaykNCmBgYA0KDQojIyMgQ3JlYXRlIHJhc3RlcnN0YWNrIG9mIENIRUxTQSBjbGltYXRlIGRhdGEgY2xpcHBlZCB0byBFdXJvcGVhbiBtb2RlbGluZyBleHRlbnQgZm9yIHByZWRpY3Rpb24NCg0KYGBge3IgcHJlcGFyZUVVX2NoZWxzYURhdGEsZWNobz1UUlVFfQ0KZXVjbGltcmFzdGVycyA8LSBsaXN0LmZpbGVzKChoZXJlKCIuL2RhdGEvZXh0ZXJuYWwvY2xpbWF0ZS9jaGVsc2FfZXVfY2xpcHMiKSkscGF0dGVybj0ndGlmJyxmdWxsLm5hbWVzID0gVCkNCmV1X2NsaW1wcmVkczwtc3RhY2soZXVjbGltcmFzdGVycykNCmV1X2NsaW1wcmVkcy4xMDwtZGl2aWRlMTAoZXVfY2xpbXByZWRzKSAjIGNvcnJlY3QgZm9yIGludGVnZXIgZm9ybWF0IG9mIENoZWxzYSBwcmVkcyANCmBgYA0KDQojIyMgUmVzdHJpY3QgZ2xvYmFsIG1vZGVsICBwcmVkaWN0aW9uIHRvIHRoZSBleHRlbnQgb2YgRXVyb3BlDQoNCmBgYHtyIHByZWRpY3RHbG9iYWwsIGNhY2hlPVRSVUUsZWNobz1GQUxTRX0NCiBnbG9iYWxfbW9kZWw8LXJhc3Rlcjo6cHJlZGljdChldV9jbGltcHJlZHMuMTAsZ2xvYmFsX3N0YWNrLHR5cGU9InByb2IiKQ0KICANCmJya3MgPC0gc2VxKDAsIDEsIGJ5PTAuMSkgDQogIG5iIDwtIGxlbmd0aChicmtzKS0xIA0KICBwYWwgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbCg4LCAnU3BlY3RyYWwnKSkpDQogIGNvbHM8LXBhbChuYikNCiAgcGxvdChnbG9iYWxfbW9kZWwsIGJyZWFrcz1icmtzLCBjb2w9Y29scyxsYWIuYnJlYWtzPWJya3MpIA0KIA0KICB3cml0ZVJhc3RlcihnbG9iYWxfbW9kZWwsIGZpbGVuYW1lPWZpbGUucGF0aChyYXN0ZXJPdXRwdXQscGFzdGUoIkdsb2JhbEVuc0VVXyIsdGF4b25rZXksICIudGlmIixzZXA9IiIpKSwgZm9ybWF0PSJHVGlmZiIsb3ZlcndyaXRlPVRSVUUpIA0KYGBgDQojIyMgQ3JlYXRlIEV1cm9wZWFuIHN1YnNldA0KYGBge3Igc3Vic2V0X2V1X29jY3VycmVuY2VzLGVjaG89RkFMU0V9DQpldWJvdW5kYXJ5PC1zaGFwZWZpbGUoaGVyZSgiLi9kYXRhL2V4dGVybmFsL0dJUy9FVVJPUEUuc2hwIikpIA0KIGV4dDwtZXh0ZW50KGV1Ym91bmRhcnkpDQogI2NycyhnbG9iYWwub2NjLnNwKTwtQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyArZWxscHM9V0dTODQgK3Rvd2dzODQ9MCwwLDAiKQ0KIG9jY19ldUludGVyc2VjdCA8LSBvdmVyKGdsb2JhbC5vY2Muc3AsZXVib3VuZGFyeSxyZXR1cm5MaXN0PVRSVUUpIA0KIG9jYy5ldSAgPC0gZ2xvYmFsLm9jYy5zcFshaXMubmEob2NjX2V1SW50ZXJzZWN0KSxdDQpwbG90KGV1Ym91bmRhcnkpDQpwbG90KG9jYy5ldSxhZGQ9VFJVRSkNCmBgYA0KDQojIyMgQ3JlYXRlIFJhc3RlclN0YWNrIG9mIEV1cm9wZWFuIGNsaW1hdGUgdmFyaWFibGVzIGZyb20gUk1JDQoNCmBgYHtyIGNyZWF0ZSBldWNsaW1hdGVfc3RhY2t9ICANCnJtaWNsaW1yYXN0ZXJzIDwtIGxpc3QuZmlsZXMoKGhlcmUoIi4vZGF0YS9leHRlcm5hbC9jbGltYXRlL3JtaV9jb3JyZWN0ZWQiKSkscGF0dGVybj0ndGlmJyxmdWxsLm5hbWVzID0gVCkgDQpybWljbGltcHJlZHMgPC0gc3RhY2socm1pY2xpbXJhc3RlcnMpDQoNCmBgYA0KDQoNCiMjIyBVc2UgU0RNdGFiIGNvbW1hbmQgZnJvbSB0aGUgU0RNUGxheSBwYWNrYWdlIHRvIHJlbW92ZSBkdXBsaWNhdGVzIHBlciAxa20gZ3JpZCBjZWxsIA0KYGBge3IgcmVtb3ZlX2V1X2R1cGxpY2F0ZXMsbWVzc2FnZT1GQUxTRSxlcnJvcj1UUlVFfQ0KICMgY29udmVydCBhIHNpbmdsZSByYXN0ZXIgdG8gTEwgZm9yIHVzZSBpbiBTRE10YWIgDQpMTHByb2o8LUNSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQgK25vX2RlZnMgK2VsbHBzPVdHUzg0ICt0b3dnczg0PTAsMCwwIikNCnJtaWNsaW1wcmVkTEw8LXByb2plY3RSYXN0ZXIocm1pY2xpbXByZWRzJGFubnRlbXBfZWVhLCBjcnM9TExwcm9qKQ0Kcm1pY2xpbXByZWRMTDI8LXByb2plY3RSYXN0ZXIocm1pY2xpbXByZWRzJGFubnByZWNpcF9lZWEsIGNycz1MTHByb2opDQpybWlwcmVkc3RhY2tMTDwtc3RhY2socm1pY2xpbXByZWRMTCxybWljbGltcHJlZExMMikNCg0KIyBDcmVhdGUgU3BhdGlhbFBvaW50cyBkYXRhZnJhbWUgbmVlZGVkIGZvciBTRE10YWIgY29tbWFuZC4gDQpldW9jYzwtb2NjLmV1QGNvb3Jkcw0KIGV1b2NjMTwtZGF0YS5mcmFtZShldW9jYylbYygxOjIpXSANCiBuYW1lcyhldW9jYzEpPC1jKCJsb25naXR1ZGUiLCAibGF0aXR1ZGUiKQ0KDQogDQojcnVuIFNETXRhYiBjb21tYW5kIHRvIHJlbW92ZSBkdXBsaWNhdGVzDQpldW9jYy5TRE10YWJsZTwtIFNETVBsYXk6OjpTRE10YWIoZXVvY2MxLCBybWlwcmVkc3RhY2tMTCwgdW5pcXVlLmRhdGEgPSBUUlVFLHNhbWU9VFJVRSkNCm51bWIucHNldWRvYWJzIDwtIGxlbmd0aChldW9jYy5TRE10YWJsZSRpZCkgDQoNCmBgYA0KDQojIyMgVHJhbnNmb3JtIGV1IG9jY3VycmVuY2UgZGF0YXNldCB3aXRoIHVuaXF1ZSBwcmVzZW5jZXMgYmFjayB0byBhIFNwYXRpYWxQb2ludHMgZGF0YWZyYW1lLiANCg0KYGBge3IgZXVfb2NjdXJlbmNlc190b19zcGF0aWFsREZ9DQpldW9jYzwtZXVvY2MuU0RNdGFibGVbYygibG9uZ2l0dWRlIiwgImxhdGl0dWRlIildDQpjb29yZGluYXRlcyhldW9jYyk8LSBjKCJsb25naXR1ZGUiLCAibGF0aXR1ZGUiKQ0KZXVvY2Mkb2NjPC0gcmVwKDEsbGVuZ3RoKGV1b2NjJGxhdGl0dWRlKSkjYWRkcyBjb2x1bW5zIGluZGljYXRpbmcgc3BlY2llcyBwcmVzZW5jZSBuZWVkZWQgZm9yIG1vZGVsaW5nDQpwcm9qNHN0cmluZyhldW9jYyk8LUNSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQgK25vX2RlZnMgK2VsbHBzPVdHUzg0ICt0b3dnczg0PTAsMCwwIikjc3BlY2lmeSBoZXJlIHRoZSBleGlzdGluZyBwcm9qZWN0aW9uIG9mIHRoZSBkYXRhDQoNCnJtaWNsaXByZWRzPC1DUlMoIitwcm9qPWxhZWEgK2xhdF8wPTUyICtsb25fMD0xMCAreF8wPTQzMjEwMDAgK3lfMD0zMjEwMDAwICtlbGxwcz1HUlM4MCArdG93Z3M4ND0wLDAsMCwtMCwtMCwtMCwwICt1bml0cz1tICtub19kZWZzICIpDQpybWlwcm9qPC1DUlMoIitwcm9qPWxhZWEgK2xhdF8wPTUyICtsb25fMD0xMCAreF8wPTQzMjEwMDAgK3lfMD0zMjEwMDAwICtlbGxwcz1HUlM4MCArdG93Z3M4ND0wLDAsMCwtMCwtMCwtMCwwICt1bml0cz1tICtub19kZWZzIikNCmV1b2NjMTwtc3BUcmFuc2Zvcm0oZXVvY2Mscm1pcHJvaikNCmBgYA0KDQojIyMgQ2xpcCBiaWFzIGdyaWQgdG8gRXVyb3BlYW4gZXh0ZW50DQoNCmBgYHtyIGNsaXBfYmlhc2dyaWR9DQpzdHVkeWV4dGVudDwtZXVib3VuZGFyeQ0KZWNvcmVnaW9uc19ldTwtY3JvcChiaWFzZ3JpZF9zdWIsc3R1ZHlleHRlbnQpDQpiaWFzZ3JpZF9ldTwtcHJvamVjdFJhc3RlcihlY29yZWdpb25zX2V1LHJtaWNsaW1wcmVkcykNCnBsb3QoYmlhc2dyaWRfZXUpDQpwbG90KHN0dWR5ZXh0ZW50LGFkZD1UUlVFKQ0KYGBgDQoNCiMjIyBNYXNrIGFyZWFzIG9mIGhpZ2ggaGFiaXRhdCBzdWl0YWJpbGl0eSBmcm9tIGdsb2JhbCBjbGltYXRlIG1vZGVsDQpgYGB7ciBtYXNrIGhpZ2hTdWl0YWJpbGl0eX0NCiNSZWFkIGluIGdsb2JhbCByYXN0ZXIgZnJvbSBlYXJsaWVyIHN0ZXAgaWYgbmVlZGVkDQoNCiNnbG9iYWxmaWxlbmFtZTwtcGFzdGUoaGVyZSgiLi9kYXRhL3Byb2Nlc3NlZC9nZW90aWZmcy9HbG9iYWxFbnNFVV8iKSwgdGF4b25rZXksICIudGlmIixzZXA9IiIpDQojZ2xvYmFsX21vZGVsPC1yYXN0ZXIoIkM6L1VzZXJzL0FteS5EYXZpcy9PbmVEcml2ZSAtIElwc29zL0Rlc2t0b3AveHBzMTUvcmlzay1tb2RlbGxpbmctYW5kLW1hcHBpbmcvZGF0YS9wcm9jZXNzZWQvZ2VvdGlmZnMvR2xvYmFsRW5zRVVfMjg4Mjg0OS50aWYiKQ0KDQoNCndnczg0X2djczwtQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyArZWxscHM9V0dTODQgK3Rvd2dzODQ9MCwwLDAiKQ0KY3JzKGdsb2JhbF9tb2RlbCk8LXdnczg0X2djcw0KbTwtZ2xvYmFsX21vZGVsID4uNQ0KZ2xvYmFsX21hc2s8LW1hc2soZ2xvYmFsX21vZGVsLG0sbWFza3ZhbHVlPVRSVUUpDQpnbG9iYWxfbWFza2VkX3Byb2o8LXByb2plY3RSYXN0ZXIoZ2xvYmFsX21hc2ssYmlhc2dyaWRfZXUpDQpgYGANCg0KIyMjIE92ZXJsYXkgbG93IHByZWRpY3RlZCBoYWJpdGF0IHN1aXRhYmlsaXR5IG9uIGJpYXMgZ3JpZCB0byBleGNsdWRlIGxvdyBzYW1wbGVkIGFyZWFzIA0KDQpgYGB7ciBjcmVhdGVfcHNldWRvU2FtcGxpbmdBcmVhfQ0KcHNldWRvU2FtcGxpbmdBcmVhPC1tYXNrKGJpYXNncmlkX2V1LGdsb2JhbF9tYXNrZWRfcHJvaikNCnBsb3QocHNldWRvU2FtcGxpbmdBcmVhKQ0KYGBgDQoNCiMjIyBSYW5kb21seSBsb2NhdGUgcHNldWRvIGFic2VuY2VzIHdpdGhpbiAicHNldWRvU2FtcGxpbmdBcmVhIiANCmBgYHtyIGNyZWF0ZV9ldV9wc2V1ZG9BYnNlbmNlc30NCnNldC5zZWVkPTEyMA0KZXVvY2NfcG9pbnRzPC1yYW5kb21Qb2ludHMocHNldWRvU2FtcGxpbmdBcmVhLCBudW1iLnBzZXVkb2FicywgZXVvY2MsIGV4dD1OVUxMLCBleHRmPTEuMSwgZXhjbHVkZXA9VFJVRSwgcHJvYj1GQUxTRSxjZWxsbnVtYmVycz1GQUxTRSwgdHJ5Zj01MCwgd2Fybj0yLCBsb25sYXRDb3JyZWN0aW9uPVRSVUUpDQoNCg0KZXVvY2NfcHNldWRvQWJzPC1hcy5kYXRhLmZyYW1lKGV1b2NjX3BvaW50cykNCmNvb3JkaW5hdGVzKGV1b2NjX3BzZXVkb0Ficyk8LWMoIngiLCJ5IikNCmV1b2NjX3BzZXVkb0FicyRvY2M8LXJlcCgwLGxlbmd0aChldW9jY19wc2V1ZG9BYnMkeCkpDQoNCmNycyhldW9jY19wc2V1ZG9BYnMpPC1DUlMoIitwcm9qPWxhZWEgK2xhdF8wPTUyICtsb25fMD0xMCAreF8wPTQzMjEwMDAgK3lfMD0zMjEwMDAwICtlbGxwcz1HUlM4MCArdG93Z3M4ND0wLDAsMCwtMCwtMCwtMCwwICt1bml0cz1tICtub19kZWZzICIpDQoNCmBgYA0KDQojIyMgSm9pbiBldSBvY2N1cnJlbmNlcyB3aXRoIHBzZXVkbyBhYnNlbmNlcyB0byBjcmVhdGUgZXUgbGV2ZWwgcHJlc2VuY2UtcHNldWRvYWJzZW5jZSBkYXRhc2V0DQoNCmBgYHtyIGNyZWF0ZSBldV9wcmVzZW5jZV9hYnNlbmNlIGRhdGFzZXR9DQpldV9wcmVzYWJzPC0gc3BSYmluZChldW9jYzEsZXVvY2NfcHNldWRvQWJzKQ0KDQpjcnMoZXVfcHJlc2Ficyk8LUNSUygiK3Byb2o9bGFlYSArbGF0XzA9NTIgK2xvbl8wPTEwICt4XzA9NDMyMTAwMCAreV8wPTMyMTAwMDAgK2VsbHBzPUdSUzgwICt0b3dnczg0PTAsMCwwLC0wLC0wLC0wLDAgK3VuaXRzPW0gK25vX2RlZnMgIikNCiNjb25maXJtIGVxdWFsIG51bWJlciBvZiBwcmVzZW5jZXMgYW5kIGFic2VuY2VzDQp0YWJsZShldV9wcmVzYWJzQGRhdGEkb2NjKQ0KDQojZXhwb3J0IG9jYyBwb2ludHMgYXMgc2hhcGVmaWxlIA0Kd3JpdGVPR1IoZXVfcHJlc2FicywgZHNuPShoZXJlKCIuL2RhdGEvcHJvY2Vzc2VkL3NkbV9vY2NfcHRzL2V1cm9wZSIpKSwgbGF5ZXI9cGFzdGUodGF4b25OYW1lLCJfRVVwcmVzYWJzIixzZXA9IiIpLCBkcml2ZXI9IkVTUkkgU2hhcGVmaWxlIiwgb3ZlcndyaXRlX2xheWVyID0gVFJVRSkgDQoNCmBgYA0KDQojIyMgQ3JlYXRlIFNETSBkYXRhIG9iamVjdCBmb3IgRXVyb3BlYW4gZGF0YSANCg0KYGBge3IgY3JlYXRlIGV1X3NkbWRhdGEsZWNobz1GQUxTRX0NCm9jY2V1LnNkbWRhdGEgPC0gc2RtRGF0YShvY2N+Lix0cmFpbj1ldV9wcmVzYWJzLCBwcmVkaWN0b3JzPXJtaWNsaW1wcmVkcykgDQpvY2NldS5zZG1kYXRhDQpgYGANCg0KIyMjIElkZW50aWZ5IGFuZCByZW1vdmUgaGlnaGx5IGNvcnJlbGF0ZWQgcHJlZGljdG9ycyBmcm9tIFJNSSByYXN0ZXJzdGFjaw0KDQpgYGB7ciByZW1vdmVDb3JyZWxhdGVkX1JNSV9QcmVkc30NCiMgY29udmVydCBldSBkYXRhIHRvIGRhdGFmcmFtZQ0Kb2NjZXUuc2RtZGF0YS5kZjwtYXMuZGF0YS5mcmFtZShvY2NldS5zZG1kYXRhKQ0KDQojaWRlbnRpZnkgaGlnaGx5IGNvcnJlbGF0ZWQgcHJlZGljdG9ycyBkcm9wIGZpcnN0IHR3byBjb2x1bW5zIHdoaWNoIGFsd2F5cyBiZSBySUQgYW5kIG9jYw0KY29ycmVsYXRpb25NYXRyaXggPC0gY29yKG9jY2V1LnNkbWRhdGEuZGZbLC1jKDE6MildKQ0KDQojIGZpbmQgYXR0cmlidXRlcyB0aGF0IGFyZSBoaWdobHkgY29ycmVjdGVkIA0KaGlnaGx5Q29ycmVsYXRlZCA8LSBmaW5kQ29ycmVsYXRpb24oY29ycmVsYXRpb25NYXRyaXgsIGN1dG9mZj0wLjcsZXhhY3Q9VFJVRSxuYW1lcz1UUlVFKQ0KZXVwcmVkczwtYXMuZGF0YS5mcmFtZShoaWdobHlDb3JyZWxhdGVkKQ0Ka2FibGUoZXVwcmVkcykgJT4lDQprYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIpKQ0Kcm1pY2xpbXByZWRzX3VuY29yPC1kcm9wTGF5ZXIocm1pY2xpbXByZWRzLGhpZ2hseUNvcnJlbGF0ZWQpDQpgYGANCg0KIyMjIEFkZCBoYWJpdGF0IGFuZCBhbnRocm9wb2dlbmljIHByZWRpY3RvcnMNCg0KYGBge3IgYWRkX2hhYml0YXRQcmVkcyxlY2hvPUZBTFNFfQ0KaGFiaXRhdDwtbGlzdC5maWxlcygoaGVyZSgiLi9kYXRhL2V4dGVybmFsL2hhYml0YXQvbGFuZGNvdmVyIikpLHBhdHRlcm49J3RpZicsZnVsbC5uYW1lcyA9IFQpDQpoYWJpdGF0X3N0YWNrPC1zdGFjayhoYWJpdGF0KQ0KZnVsbHN0YWNrPC1zdGFjayhybWljbGltcHJlZHNfdW5jb3IsaGFiaXRhdF9zdGFjaykgI2NvbWJpbmUgdW5jb3JyZWxhdGVkIGNsaW1hdGUgdmFyaWFibGUgc2VsZWN0ZWQgZWFybGllciB3aXRoIGhhYml0YXQNCg0KIyB1bmNvbW1lbnQgYmVsb3cgZm9yIHZlcnRlYnJhdGVzIGFuZCBpbnZlcnRlYnJhdGVzIHRvIGluY2x1ZGUgZGlzdGFuY2UgdG8gd2F0ZXINCiNkaXN0MndhdGVyPC1yYXN0ZXIoaGVyZSgiLi9kYXRhL2V4dGVybmFsL2hhYml0YXQvZGlzdGFuY2Uyd2F0ZXJfRUVBXzFrbS50aWYiKSkNCiNkaXN0MndhdGVyPC1leHRlbmQoZGlzdDJ3YXRlcixoYWJpdGF0X3N0YWNrKQ0KI2Z1bGxzdGFjazwtc3RhY2socm1pY2xpbXByZWRzX3VuY29yLGhhYml0YXRfc3RhY2ssZGlzdDJ3YXRlcikNCg0KIyBjbGlwIGZ1bGxzdGFjayB0byBiZWxnaXVtIGV4dGVudCAoaWYgdXNpbmcgYW5vdGhlciBjb3VudHJ5LCByZXBsYWNlIHdpdGggY291bnRyeSBib3VuZGFyeSBzaGFwZWZpbGUpDQpjb3VudHJ5PC1zaGFwZWZpbGUoaGVyZSgiLi9kYXRhL2V4dGVybmFsL0dJUy9iZWxnaXVtX2JvdW5kYXJ5LnNocCIpKQ0KZnVsbHN0YWNrX2Nyb3A8LWNyb3AoZnVsbHN0YWNrLGNvdW50cnkpDQpmdWxsc3RhY2tfYmU8LW1hc2soZnVsbHN0YWNrX2Nyb3AsY291bnRyeSkNCg0Kb2NjLmZ1bGwuZGF0YSA8LSBzZG1EYXRhKG9jY34uLHRyYWluPWV1X3ByZXNhYnMsIHByZWRpY3RvcnM9ZnVsbHN0YWNrKSANCm9jYy5mdWxsLmRhdGENCg0KIyBjb252ZXJ0IGV1IGRhdGEgdG8gZGF0YWZyYW1lDQpvY2NldS5mdWxsLmRhdGEuZGY8LWFzLmRhdGEuZnJhbWUob2NjLmZ1bGwuZGF0YSkNCm9jY2V1LmZ1bGwuZGF0YS5kZjwtb2NjZXUuZnVsbC5kYXRhLmRmWywtMV0NCg0KYGBgDQoNCiMjIyBSZW1vdmUgaGlnaGx5IGNvcnJlbGF0ZWQgcHJlZGljdG9ycyBmcm9tIHRoZSBoYWJpdGF0L2FudGhyb3BvZ2VuaWMvY2xpbWF0ZSBzdGFjayAoZnVsbCBzdGFjaykNCg0KYGBge3IgcmVtb3ZlQ29ycmVsYXRlZFByZWRzX2Zyb21fZnVsbFN0YWNrfQ0KIyBmaXJzdCByZW1vdmUgYW5kIGlkZW50aWZ5IGxvdyB2YXJpYW5jZSBwcmVkaWN0b3JzDQpuenZfcHJlZHM8LW5lYXJaZXJvVmFyKG9jY2V1LmZ1bGwuZGF0YS5kZixuYW1lcz1UUlVFKQ0Kb2NjZXUuZnVsbC5kYXRhLmRmMTwtc2VsZWN0IChvY2NldS5mdWxsLmRhdGEuZGYsLWMobnp2X3ByZWRzKSkNCg0KIyBmaW5kIGF0dHJpYnV0ZXMgdGhhdCBhcmUgaGlnaGx5IGNvcnJlY3RlZCANCmNvcnJlbGF0aW9uTWF0cml4IDwtIGNvcihvY2NldS5mdWxsLmRhdGEuZGYxWywtMV0pDQpoaWdobHlDb3JyZWxhdGVkIDwtIGZpbmRDb3JyZWxhdGlvbihjb3JyZWxhdGlvbk1hdHJpeCwgY3V0b2ZmPTAuNyxleGFjdD1UUlVFLG5hbWVzPVRSVUUpDQpldXByZWRzMTwtYXMuZGF0YS5mcmFtZShoaWdobHlDb3JyZWxhdGVkKQ0Ka2FibGUoZXVwcmVkczEpICU+JQ0Ka2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiKSkNCg0KIyBwcmludCBuYW1lcyBvZiBoaWdobHkgY29ycmVsYXRlZCBhdHRyaWJ1dGVzDQpwcmludChoaWdobHlDb3JyZWxhdGVkKQ0Kb2NjZXUuZnVsbC5kYXRhLmRmMTwtc2VsZWN0IChvY2NldS5mdWxsLmRhdGEuZGYxLC1jKGhpZ2hseUNvcnJlbGF0ZWQpKQ0KYGBgDQoNCiMjIyBCdWlsZCBtb2RlbHMgd2l0aCBjbGltYXRlIGFuZCBoYWJpdGF0IGRhdGENCg0KDQpgYGB7ciBydW5fZXVNb2RlbCxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQojIHVuY29tbWVudCAybmQgY29udHJvbCBvcHRpb25zIGZvciAgTE9PQ1YgKGxlYXZlIG9uZSBvdXQgY3Jvc3MgdmFsaWRhdGlvbiwgd2hpY2ggaXMgYWthIGFzICJqYWNrbmlmZSIgKSB3aGljaCBzaG91bGQgYmUgdXNlZCB3aGVuIG9jY3VycmVuY2VzIGFyZSBzbWFsbGVyIHRoYW4gbj0xMCBmb3IgZWFjaCBwcmVkaWN0b3IgaW4gdGhlIG1vZGVsKQ0Kb2NjZXUuZnVsbC5kYXRhLmRmMSRvY2M8LWFzLmZhY3RvcihvY2NldS5mdWxsLmRhdGEuZGYxJG9jYykNCmxldmVscyhvY2NldS5mdWxsLmRhdGEuZGYxJG9jYyk8LWMoImFic2VudCIsInByZXNlbnQiKQ0KI2NvbnRyb2w8LXRyYWluQ29udHJvbChtZXRob2Q9IkxPT0NWIixzYXZlUHJlZGljdGlvbnM9ImZpbmFsIiwgcHJlUHJvYz1jKCJjZW50ZXIiLCJzY2FsZSIpLGNsYXNzUHJvYnM9VFJVRSkNCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0icmVwZWF0ZWRjdiIsbnVtYmVyPTEwLCByZXBlYXRzPTEwLHNhdmVQcmVkaWN0aW9ucz0iZmluYWwiLGNsYXNzUHJvYnM9VFJVRSkNCm15bGlzdDwtbGlzdCgNCiAgZ2xtID1jYXJldE1vZGVsU3BlYyhtZXRob2QgPSAiZ2xtIixtYXhpdD0xMDApLA0KICBnYm09IGNhcmV0TW9kZWxTcGVjKG1ldGhvZCA9ICJnYm0iKSwNCiAgcmYgPSBjYXJldE1vZGVsU3BlYyhtZXRob2QgPSAicmYiLCBpbXBvcnRhbmNlID0gVFJVRSksDQogIGtubiA9IGNhcmV0TW9kZWxTcGVjKG1ldGhvZCA9ICJrbm4iKSwNCiAgZWFydGg9IGNhcmV0TW9kZWxTcGVjKG1ldGhvZCA9ICJlYXJ0aCIpKQ0KDQpzZXQuc2VlZCgxNTcpDQpoaWRlb3V0PC1jYXB0dXJlLm91dHB1dChtb2RlbF90cmFpbl9oYWJpdGF0IDwtIGNhcmV0TGlzdCgNCiAgb2Njfi4sIGRhdGE9b2NjZXUuZnVsbC5kYXRhLmRmMSwNCiAgdHJDb250cm9sPWNvbnRyb2wsDQogICB0dW5lTGlzdD1teWxpc3QpKQ0KDQoNCg0KYGBgDQojIyMgRGlzcGxheSBtb2RlbCBldmFsdWF0aW9uIHN0YXRpc3RpY3MNCg0KYGBge3Igc2hvd19ldU1vZGVsX2FjY3VyYWN5fQ0KbW9kZWxSZXN1bHRzMTwtcmVzYW1wbGVzKG1vZGVsX3RyYWluX2hhYml0YXQpDQpzdW1tYXJ5KG1vZGVsUmVzdWx0czEpDQptb2RlbENvcihyZXNhbXBsZXMobW9kZWxfdHJhaW5faGFiaXRhdCkpDQpgYGANCg0KDQoNCg0KDQojIyMgQ3JlYXRlIGVuc2VtYmxlIG1vZGVsIA0KDQpgYGB7ciBydW4gZXVfZW5zZW1ibGV9DQoNCnNldC5zZWVkKDQ1OCkNCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLG51bWJlcj0xMCwgc2F2ZVByZWRpY3Rpb25zPSJmaW5hbCIsY2xhc3NQcm9icz1UUlVFKQ0KbW9kZWxfdHJhaW5faGFiaXRhdDEgPC0gY2FyZXRMaXN0KA0KICBvY2N+LiwgZGF0YT1vY2NldS5mdWxsLmRhdGEuZGYxLA0KICB0ckNvbnRyb2w9Y29udHJvbCwNCiAgIHR1bmVMaXN0PW15bGlzdCkNCg0KbW9kZWxSZXN1bHRzMTwtcmVzYW1wbGVzKG1vZGVsX3RyYWluX2hhYml0YXQxKQ0Kc3VtbWFyeShtb2RlbFJlc3VsdHMxKQ0KbW9kZWxDb3IocmVzYW1wbGVzKG1vZGVsX3RyYWluX2hhYml0YXQxKSkNCg0Kc2V0LnNlZWQoNDU2KQ0KaGlkZW91dHB1dDwtY2FwdHVyZS5vdXRwdXQoDQpsbV9lbnNfaGFiPC1jYXJldEVuc2VtYmxlKG1vZGVsX3RyYWluX2hhYml0YXQxLCB0ckNvbnRyb2w9dHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXI9MTAsc2F2ZVByZWRpY3Rpb25zPSAiZmluYWwiLGNsYXNzUHJvYnMgPSBUUlVFKSkpDQoNCmxtX2Vuc19oYWINCg0KDQpgYGANCg0KYGBge3IgZ2V0X3Zhcl9pbXBvcnRhbmNlLGVjaG89VFJVRX0NCnZhcmlhYmxlSW1wb3J0YW5jZTwtdmFySW1wKGxtX2Vuc19oYWIpDQprYWJsZSh2YXJpYWJsZUltcG9ydGFuY2UsZGlnaXRzPTIsY2FwdGlvbj0iVmFyaWFibGUgSW1wb3J0YW5jZSIpICU+JQ0Ka2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiKSkNCndyaXRlLmNzdih2YXJpYWJsZUltcG9ydGFuY2UsZmlsZSA9IHBhc3RlMChnZW5PdXRwdXQsdGF4b25rZXksIl92YXJJbXAuY3N2IikpDQpgYGANCg0KDQpgYGB7ciBleHBvcnRfdG9QREYsZWNobz1GQUxTRX0NCmV4cG9ydFBERjwtZnVuY3Rpb24ocnN0LHRheG9ua2V5LHRheG9uTmFtZSxuYW1lZXh0ZW5zaW9uLGlzLmRpZmY9IkZBTFNFIil7DQogIGZpbGVuYW1lPWZpbGUucGF0aChwZGZPdXRwdXQscGFzdGUoImJlXyIsdGF4b25rZXksICJfIixuYW1lZXh0ZW5zaW9uLHNlcD0iIikpDQogIHBkZihmaWxlPWZpbGVuYW1lLHdpZHRoPTEwLGhlaWdodD04LHBhcGVyPSJhNHIiKQ0KICBwYXIoYnR5PSJuIikjdG8gdHVybiBvZmYgYm94IGFyb3VuZCBwbG90DQogIGlmZWxzZShpcy5kaWZmPT0iVFJVRSIsIGJya3M8LXNlcSgtMSwgMSwgYnk9MC4yNSksIGJya3MgPC0gc2VxKDAsIDEsIGJ5PTAuMSkpIA0KICBuYiA8LSBsZW5ndGgoYnJrcyktMSANCiAgcGFsIDwtIGNvbG9yUmFtcFBhbGV0dGUocmV2KGJyZXdlci5wYWwoMTEsICdTcGVjdHJhbCcpKSkNCiAgY29sczwtcGFsKG5iKQ0KICBtYWludGl0bGU8LXBhc3RlKHRheG9uTmFtZSx0YXhvbmtleSwiXyIsbmFtZWV4dGVuc2lvbiwgc2VwPSAiICIpDQogIHBsb3QocnN0LCBicmVha3M9YnJrcywgY29sPWNvbHMsbWFpbj1tYWludGl0bGUsIGxhYi5icmVha3M9YnJrcyxheGVzPUZBTFNFKQ0KICBkZXYub2ZmKCkgDQp9IA0KYGBgDQoNCmBgYHtyIGV4cG9ydF90b1BORyxlY2hvPUZBTFNFfQ0KZXhwb3J0UE5HPC1mdW5jdGlvbihyc3QsdGF4b25rZXksdGF4b25OYW1lLG5hbWVleHRlbnNpb24saXMuZGlmZj0iRkFMU0UiKXsNCiAgZmlsZW5hbWU9ZmlsZS5wYXRoKHBkZk91dHB1dCxwYXN0ZSgiYmVfIix0YXhvbmtleSwgIl8iLG5hbWVleHRlbnNpb24sc2VwPSIiKSkNCiAgcG5nKGZpbGU9ZmlsZW5hbWUpDQogIHBhcihidHk9Im4iKSN0byB0dXJuIG9mZiBib3ggYXJvdW5kIHBsb3QNCiAgaWZlbHNlKGlzLmRpZmY9PSJUUlVFIiwgYnJrczwtc2VxKC0xLCAxLCBieT0wLjI1KSwgYnJrcyA8LSBzZXEoMCwgMSwgYnk9MC4xKSkgDQogIG5iIDwtIGxlbmd0aChicmtzKS0xIA0KICBwYWwgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbCgxMSwgJ1NwZWN0cmFsJykpKQ0KICBjb2xzPC1wYWwobmIpDQogIG1haW50aXRsZTwtcGFzdGUodGF4b25OYW1lLHRheG9ua2V5LCJfIixuYW1lZXh0ZW5zaW9uLCBzZXA9ICIgIikNCiAgcGxvdChyc3QsIGJyZWFrcz1icmtzLCBjb2w9Y29scyxtYWluPW1haW50aXRsZSwgbGFiLmJyZWFrcz1icmtzLGF4ZXM9RkFMU0UpDQogIGRldi5vZmYoKSANCn0gDQpgYGANCg0KIyMjICBVc2UgRVUgbGV2ZWwgZW5zZW1ibGUgbW9kZWwgKGVuc01vZGVsKSB0byBwcmVkaWN0IGZvciBFdXJvcGUNCg0KYGBge3IgZW5zTW9kZWxfcHJlZGljdEVVLHJlc3VsdHM9ICJoaWRlIiwgY2FjaGU9VFJVRX0NCiBlbnNfcHJlZF9oYWJfZXU8LXJhc3Rlcjo6cHJlZGljdChmdWxsc3RhY2ssbG1fZW5zX2hhYix0eXBlPSJwcm9iIikNCiBlbnNfcHJlZF9oYWJfZXUxPC0xLWVuc19wcmVkX2hhYl9ldQ0KIA0KIHdyaXRlUmFzdGVyKGVuc19wcmVkX2hhYl9ldTEsZmlsZW5hbWU9ZmlsZS5wYXRoKHJhc3Rlck91dHB1dCxwYXN0ZSgiZXVfIix0YXhvbmtleSwgIl9oaXN0LnRpZiIsc2VwPSIiKSkgLCBmb3JtYXQ9IkdUaWZmIixvdmVyd3JpdGU9VFJVRSkNCg0KZXhwb3J0UE5HKGVuc19wcmVkX2hhYl9ldTEsdGF4b25rZXksdGF4b25OYW1lPXRheG9uTmFtZSwiaGlzdC5wbmciKQ0KICANCmBgYA0KYGBge3IgUGxvdF9lbnNNb2RlbF9ldX0NCg0KICBicmtzIDwtIHNlcSgwLCAxLCBieT0wLjEpIA0KICBuYiA8LSBsZW5ndGgoYnJrcyktMSANCiAgcGFsIDwtIGNvbG9yUmFtcFBhbGV0dGUocmV2KGJyZXdlci5wYWwoOCwgJ1NwZWN0cmFsJykpKQ0KICAgY29sczwtcGFsKG5iKQ0KICBwbG90KGVuc19wcmVkX2hhYl9ldTEsIGJyZWFrcz1icmtzLCBjb2w9Y29scyxsYWIuYnJlYWtzPWJya3MpDQogDQpgYGANCg0KIyMjIFByZWRpY3QgZm9yIEJlbGdpdW0gb25seSB1c2luZyBFVS1sZXZlbCBlbnNlbWJsZSBtb2RlbCB0byBmb3JlY2FzdCByaXNrIHVuZGVyIGhpc3RvcmljYWwgY2xpbWF0ZSBjb25kaXRpb25zDQoNCmBgYHtyIGVuc01vZGVsX3ByZWRpY3RCRSxyZXN1bHRzPSJoaWRlIn0NCmxhZWFfZ3JzODA8LUNSUygiK3Byb2o9bGFlYSArbGF0XzA9NTIgK2xvbl8wPTEwICt4XzA9NDMyMTAwMCAreV8wPTMyMTAwMDAgK2VsbHBzPUdSUzgwICt0b3dnczg0PTAsMCwwLC0wLC0wLC0wLDAgK3VuaXRzPW0gK25vX2RlZnMgIikNCmVuc19wcmVkX2hhYjwtcmFzdGVyOjpwcmVkaWN0KGZ1bGxzdGFja19iZSxsbV9lbnNfaGFiLHR5cGU9InByb2IiKQ0KZW5zX3ByZWRfaGFiMTwtMS1lbnNfcHJlZF9oYWINCmNycyhlbnNfcHJlZF9oYWIxKTwtbGFlYV9ncnM4MA0Kd3JpdGVSYXN0ZXIoZW5zX3ByZWRfaGFiMSwgZmlsZW5hbWU9ZmlsZS5wYXRoKHJhc3Rlck91dHB1dCxwYXN0ZSgiYmVfIix0YXhvbmtleSwgIl9oaXN0LnRpZiIsc2VwPSIiKSksICBmb3JtYXQ9IkdUaWZmIixvdmVyd3JpdGU9VFJVRSkNCmV4cG9ydFBERihlbnNfcHJlZF9oYWIxLHRheG9ua2V5LHRheG9uTmFtZT10YXhvbk5hbWUsImV1X2hpc3QucGRmIikNCmV4cG9ydFBORyhlbnNfcHJlZF9oYWIxLHRheG9ua2V5LHRheG9uTmFtZT10YXhvbk5hbWUsImV1X2hpc3QucG5nIikNCmBgYA0KDQpgYGB7ciBQbG90X2Vuc01vZGVsX2JlfQ0KYnJrcyA8LSBzZXEoMCwgMSwgYnk9MC4xKSANCiAgbmIgPC0gbGVuZ3RoKGJya3MpLTEgDQogIHBhbCA8LSBjb2xvclJhbXBQYWxldHRlKHJldihicmV3ZXIucGFsKDExLCAnU3BlY3RyYWwnKSkpDQogIGNvbHM8LXBhbChuYikNCiAgcGxvdChlbnNfcHJlZF9oYWIxLCBicmVha3M9YnJrcywgY29sPWNvbHMsbGFiLmJyZWFrcz1icmtzKQ0KICAgcGxvdChldW9jYyxwY2g9MyxjZXg9MC43NSxhZGQ9VFJVRSkNCg0KYGBgDQoNCiMjIyBDbGlwIGhhYml0YXQgc3RhY2sgdG8gQmVsZ2l1bSANCiMjIyMgdXNlIHRoaXMgc3RhY2sgZm9yIHZlcnRlYnJhdGVzIG9yIGFueXRoaW5nIGxpa2VseSB0byBiZSBpbmZsdWVuY2VkIGJ5IHdhdGVyIGF2YWlsYWJpbGl0eQ0KYGBge3IgY3JlYXRlIEJFX2hhYml0YXRTdGFja30NCiMgaGFiaXRhdF9zdGFjazwtc3RhY2soaGFiaXRhdCxkaXN0MndhdGVyKQ0KIyBoYWJpdGF0X29ubHlfc3RhY2s8LWNyb3AoaGFiaXRhdF9zdGFjayxjb3VudHJ5KQ0KIyBoYWJpdGF0X29ubHlfc3RhY2tfYmU8LWNyb3AoaGFiaXRhdF9vbmx5X3N0YWNrLGNvdW50cnkpDQpgYGANCiMjIHVzZSB0aGlzIHN0YWNrIGZvciBwbGFudHMNCmBgYHtyIGNyZWF0ZSBCRV9oYWJpdGF0U3RhY2tfcGxhbnRzfQ0KaGFiaXRhdF9zdGFjazwtc3RhY2soaGFiaXRhdCkNCmhhYml0YXRfb25seV9zdGFjazwtY3JvcChoYWJpdGF0X3N0YWNrLGNvdW50cnkpDQpoYWJpdGF0X29ubHlfc3RhY2tfYmU8LWNyb3AoaGFiaXRhdF9vbmx5X3N0YWNrLGNvdW50cnkpDQpgYGANCg0KIyMjIENyZWF0ZSBpbmRpdmlkdWFsIFJDUCAoMi42LCA0LjUsIDguNSkgY2xpbWF0ZSBkYXRhIHN0YWNrcyBmb3IgQmVsZ2l1bQ0KDQpgYGB7ciBjcmVhdGUgQkVfUkNQX3N0YWNrc30NCmJlMjYgPC0gbGlzdC5maWxlcygoaGVyZSgiLi9kYXRhL2V4dGVybmFsL2NsaW1hdGUvYnlFRUFfZmluYWxSQ1AvYmVsZ2l1bV9yY3BzL3JjcDI2IikpLHBhdHRlcm49J3RpZicsZnVsbC5uYW1lcyA9IFQpDQpiZWxnaXVtX3N0YWNrMjYgPC0gc3RhY2soYmUyNikNCg0KYmU0NSA8LSBsaXN0LmZpbGVzKChoZXJlKCIuL2RhdGEvZXh0ZXJuYWwvY2xpbWF0ZS9ieUVFQV9maW5hbFJDUC9iZWxnaXVtX3JjcHMvcmNwNDUiKSkscGF0dGVybj0ndGlmJyxmdWxsLm5hbWVzID0gVCkNCmJlbGdpdW1fc3RhY2s0NSA8LSBzdGFjayhiZTQ1KQ0KDQpiZTg1IDwtIGxpc3QuZmlsZXMoKGhlcmUoIi4vZGF0YS9leHRlcm5hbC9jbGltYXRlL2J5RUVBX2ZpbmFsUkNQL2JlbGdpdW1fcmNwcy9yY3A4NSIpKSxwYXR0ZXJuPSd0aWYnLGZ1bGwubmFtZXMgPSBUKQ0KYmVsZ2l1bV9zdGFjazg1IDwtIHN0YWNrKGJlODUpDQpgYGANCg0KIyMjIENvbWJpbmUgaGFiaXRhdCBzdGFja3Mgd2l0aCBjbGltYXRlIHN0YWNrcyBmb3IgZWFjaCBSQ1Agc2NlbmFyaW8NCg0KYGBge3IgY29tYmluZSBoYWJpdGF0X1JDUF9zdGFja3N9DQpmdWxsc3RhY2syNjwtc3RhY2soYmUyNixoYWJpdGF0X29ubHlfc3RhY2tfYmUpDQpmdWxsc3RhY2s0NTwtc3RhY2soYmU0NSxoYWJpdGF0X29ubHlfc3RhY2tfYmUpDQpmdWxsc3RhY2s4NTwtc3RhY2soYmU4NSxoYWJpdGF0X29ubHlfc3RhY2tfYmUpDQpgYGANCg0KIyMjIENyZWF0ZSBhbmQgZXhwb3J0IFJDUCByaXNrIG1hcHMgZm9yIGVhY2ggUkNQIHNjZW5hcmlvDQoNCmBgYHtyIGNyZWF0ZV9SQ1Bfcmlza19tYXBzfQ0KZW5zX3ByZWRfaGFiMjY8LXJhc3Rlcjo6cHJlZGljdChmdWxsc3RhY2syNixsbV9lbnNfaGFiLHR5cGU9InByb2IiKQ0KZW5zX3ByZWRfaGFiMjZfMTwtMS1lbnNfcHJlZF9oYWIyNg0KY3JzKGVuc19wcmVkX2hhYjI2XzEpPC1sYWVhX2dyczgwDQp3cml0ZVJhc3RlcihlbnNfcHJlZF9oYWIyNl8xLCBmaWxlbmFtZT1maWxlLnBhdGgocmFzdGVyT3V0cHV0LHBhc3RlKCJiZV8iLHRheG9ua2V5LCAiX3JjcDI2LnRpZiIsc2VwPSIiKSksIGZvcm1hdD0iR1RpZmYiLG92ZXJ3cml0ZT1UUlVFKSANCmV4cG9ydFBERihlbnNfcHJlZF9oYWIyNl8xLHRheG9ua2V5LHRheG9uTmFtZT10YXhvbk5hbWUsInJjcDI2LnBkZiIpDQoNCmVuc19wcmVkX2hhYjQ1PC1yYXN0ZXI6OnByZWRpY3QoZnVsbHN0YWNrNDUsbG1fZW5zX2hhYix0eXBlPSJwcm9iIikNCmVuc19wcmVkX2hhYjQ1XzE8LTEtZW5zX3ByZWRfaGFiNDUNCmNycyhlbnNfcHJlZF9oYWI0NV8xKTwtbGFlYV9ncnM4MA0Kd3JpdGVSYXN0ZXIoZW5zX3ByZWRfaGFiNDVfMSwgZmlsZW5hbWU9ZmlsZS5wYXRoKHJhc3Rlck91dHB1dCxwYXN0ZSgiYmVfIix0YXhvbmtleSwgIl9yY3A0NS50aWYiLHNlcD0iIikpLCBmb3JtYXQ9IkdUaWZmIixvdmVyd3JpdGU9VFJVRSkgDQpleHBvcnRQREYoZW5zX3ByZWRfaGFiNDVfMSx0YXhvbmtleSx0YXhvbk5hbWU9dGF4b25OYW1lLCJyY3A0NS5wZGYiKQ0KZW5zX3ByZWRfaGFiODU8LXJhc3Rlcjo6cHJlZGljdChmdWxsc3RhY2s4NSxsbV9lbnNfaGFiLHR5cGU9InByb2IiKQ0KZW5zX3ByZWRfaGFiODVfMTwtMS1lbnNfcHJlZF9oYWI4NQ0KY3JzKGVuc19wcmVkX2hhYjg1XzEpPC1sYWVhX2dyczgwDQp3cml0ZVJhc3RlcihlbnNfcHJlZF9oYWI4NV8xLCBmaWxlbmFtZT1maWxlLnBhdGgocmFzdGVyT3V0cHV0LHBhc3RlKCJiZV8iLHRheG9ua2V5LCAiX3JjcDg1LnRpZiIsc2VwPSIiKSksIGZvcm1hdD0iR1RpZmYiLG92ZXJ3cml0ZT1UUlVFKSANCmV4cG9ydFBERihlbnNfcHJlZF9oYWI4NV8xLHRheG9ua2V5LHRheG9uTmFtZT10YXhvbk5hbWUsInJjcDg1LnBkZiIpDQoNCmBgYA0KDQojIyMgRGlzcGxheSBSQ1AgcmlzayBtYXBzDQoNCmBgYHtyLCByY3Bfcmlza01hcHN9DQpwbG90KGVuc19wcmVkX2hhYjI2XzEsYnJlYWtzPWJya3MsIGNvbD1jb2xzLGxhYi5icmVha3M9YnJrcykNCnBsb3QoZW5zX3ByZWRfaGFiNDVfMSxicmVha3M9YnJrcywgY29sPWNvbHMsbGFiLmJyZWFrcz1icmtzKQ0KcGxvdChlbnNfcHJlZF9oYWI4NV8xLGJyZWFrcz1icmtzLCBjb2w9Y29scyxsYWIuYnJlYWtzPWJya3MpDQpgYGANCg0KIyMjIENyZWF0ZSBhbmQgZXhwb3J0ICJkaWZmZXJlbmNlIG1hcHMiOiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHByZWRpY3RlZCByaXNrIGJ5IGVhY2ggUkNQIHNjZW5hcmlvIGFuZCBoaXN0b3JpY2FsIGNsaW1hdGUNCg0KYGBge3IgY3JlYXRlX2RpZmZlcmVuY2VfbWFwcyxlY2hvPUZBTFNFfQ0KaGlzdDI2X2RpZmZfaGFiPC1vdmVybGF5KGVuc19wcmVkX2hhYjI2XzEsIGVuc19wcmVkX2hhYjEsIGZ1bj1mdW5jdGlvbihyMSxyMil7cmV0dXJuKHIxLXIyKX0pDQp3cml0ZVJhc3RlcihoaXN0MjZfZGlmZl9oYWIsZmlsZW5hbWU9ZmlsZS5wYXRoKHJhc3Rlck91dHB1dCxwYXN0ZSgiYmVfIix0YXhvbmtleSwgIl9yY3AyNl9kaWZmLnRpZiIsc2VwPSIiKSkgLCBmb3JtYXQ9IkdUaWZmIixvdmVyd3JpdGU9VFJVRSkgDQpleHBvcnRQREYoaGlzdDI2X2RpZmZfaGFiLHRheG9ua2V5LHRheG9uTmFtZT10YXhvbk5hbWUsInJjcDI2X2RpZmYucGRmIiwiVFJVRSIpDQpwbG90KGhpc3QyNl9kaWZmX2hhYikNCg0KaGlzdDQ1X2RpZmZfaGFiPC1vdmVybGF5KGVuc19wcmVkX2hhYjQ1XzEsIGVuc19wcmVkX2hhYjEsIGZ1bj1mdW5jdGlvbihyMSxyMil7cmV0dXJuKHIxLXIyKX0pDQp3cml0ZVJhc3RlcihoaXN0NDVfZGlmZl9oYWIsZmlsZW5hbWU9ZmlsZS5wYXRoKHJhc3Rlck91dHB1dCxwYXN0ZSgiYmVfIix0YXhvbmtleSwgIl9yY3A0NV9kaWZmLnRpZiIsc2VwPSIiKSksIGZvcm1hdD0iR1RpZmYiLG92ZXJ3cml0ZT1UUlVFKSANCmV4cG9ydFBERihoaXN0NDVfZGlmZl9oYWIsdGF4b25rZXksdGF4b25OYW1lPXRheG9uTmFtZSwicmNwNDVfZGlmZi5wZGYiLCJUUlVFIikNCnBsb3QoaGlzdDQ1X2RpZmZfaGFiKQ0KDQpoaXN0ODVfZGlmZl9oYWI8LW92ZXJsYXkoZW5zX3ByZWRfaGFiODVfMSwgZW5zX3ByZWRfaGFiMSwgZnVuPWZ1bmN0aW9uKHIxLHIyKXtyZXR1cm4ocjEtcjIpfSkNCndyaXRlUmFzdGVyKGhpc3Q4NV9kaWZmX2hhYiwgZmlsZW5hbWU9ZmlsZS5wYXRoKHJhc3Rlck91dHB1dCxwYXN0ZSgiYmVfIix0YXhvbmtleSwgIl9yY3BfODVfZGlmZi50aWYiLHNlcD0iIikpLCBmb3JtYXQ9IkdUaWZmIixvdmVyd3JpdGU9VFJVRSkgDQpleHBvcnRQREYoaGlzdDg1X2RpZmZfaGFiLHRheG9ua2V5LHRheG9uTmFtZT10YXhvbk5hbWUsInJjcDg1X2RpZmYucGRmIiwiVFJVRSIpDQpwbG90KGhpc3Q4NV9kaWZmX2hhYikNCg0KYGBgDQoNCg0KIyMjIE91dHB1dCBwcmVkaWN0b3IgZGF0YSB1c2VkIGZvciBTRE0NCg0KYGBge3IgZXhwb3J0X1NETV9wcmVkaWN0b3JEYXRhfQ0KZGY0ZXhwb3J0PC1jYmluZChvY2NldS5mdWxsLmRhdGEuZGYxLGNvb3JkaW5hdGVzKG9jYy5mdWxsLmRhdGEpKQ0Kd3JpdGUuY3N2KGRmNGV4cG9ydCwgcGFzdGUoZ2VuT3V0cHV0LHRheG9ua2V5LCJfc2RtZGF0YS5jc3YiLHNlcD0iIikpDQpgYGANCg0KIyMjIENoZWNrIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIG9mIHJlc2lkdWFscyB0byBhc3Nlc3Mgd2hldGhlciBvY2N1cnJlbmNlIGRhdGEgc2hvdWxkIGJlIHRoaW5uZWQNCiMjIyMgZGVyaXZlIHJlc2lkdWFscyBmcm9tIHVudGhpbm5lZCBtb2RlbA0KYGBge3IgZGVyaXZlUmVzaWR1YWxzfQ0KcHJlZEVuczE8LWxtX2Vuc19oYWIkZW5zX21vZGVsJHByZWQNCm9icy5udW1lcmljPC1pZmVsc2UocHJlZEVuczEkb2JzID09ICJhYnNlbnQiLDAsMSkNCmBgYA0KDQojIyMjIHN0YW5kYXJkaXplIHJlc2lkdWFscw0KDQpgYGB7ciBzdGFuZGFyZGl6ZV9yZXNpZHVhbHN9DQpzdGRyZXM8LWZ1bmN0aW9uKG9icy5udW1lcmljLCB5aGF0KXsNCiAgbnVtPC1vYnMubnVtZXJpYy15aGF0DQogIGRlbm9tPC1zcXJ0KHloYXQqKDEteWhhdCkpDQogIHJldHVybihudW0vZGVub20pDQp9DQpoYWIucmVzPC1zdGRyZXMob2JzLm51bWVyaWMscHJlZEVuczEkcHJlc2VudCkNCg0KDQpyZXMuYmVzdC5jb29yZHM8LWNiaW5kKGNvb3JkaW5hdGVzKG9jYy5mdWxsLmRhdGEpLGhhYi5yZXMpDQpyZXMuYmVzdC5nZW88LWFzLmdlb2RhdGEocmVzLmJlc3QuY29vcmRzLGNvb3Jkcy5jb2w9MToyLGRhdGEuY29sID0gMykNCnN1bW1hcnkocmVzLmJlc3QuZ2VvKSAjbm90ZSBkaXN0YW5jZSBpcyBpbiBtZXRlcnMNCmBgYA0KDQojIyMgQ2hlY2sgTW9yYW5zIEkuDQoNCmBgYHtyIHJlc2lkdWFsX01vcmFuc0l9DQojSWYgTW9yYW4ncyBJIGlzIHZlcnkgbG93ICg8MC4xMCksIG9yIG5vdCBzaWduaWZpY2FudCwgZG8gbm90IG5lZWQgdG8gdGhpbiBvY2N1cnJlbmNlcy4NCmxpYnJhcnkoYXBlKQ0KcmVzLmJlc3QuZGY8LWFzLmRhdGEuZnJhbWUocmVzLmJlc3QuY29vcmRzKQ0Kb2NjLmRpc3RzIDwtIGFzLm1hdHJpeChkaXN0KGNiaW5kKHJlcy5iZXN0LmRmWzFdLCByZXMuYmVzdC5kZlsyXSkpKQ0Kb2NjLmRpc3RzLmludiA8LSAxL29jYy5kaXN0cw0KZGlhZyhvY2MuZGlzdHMuaW52KSA8LSAwDQpNb3Jhbi5JKHJlcy5iZXN0LmRmJGhhYi5yZXMsb2NjLmRpc3RzLmludixzY2FsZWQ9VFJVRSxhbHRlcm5hdGl2ZT0iZ3JlYXRlciIpDQpgYGANCg0KYGBge3IgY29uZm9ybWFsUHJlZGljdGlvbmZ1bmN0aW9ucyxlY2hvPUZBTFNFfQ0KIyBmdW5jdGlvbnMgbmVlZGVkIGZvciBjb25mb3JtYWwgcHJlZGljdGlvbiBmdW5jdGlvbg0KDQoNCkdldExlbmd0aDwtZnVuY3Rpb24oeCx5KXsNCmxlbmd0aCh4W3doaWNoKHggPj0geSldKQ0KfQ0KDQoNCg0KDQpDUGNvbmY8LWZ1bmN0aW9uKHBBLHBCLGNvbmZpZGVuY2Upew0KICBpZihwQSA+IGNvbmZpZGVuY2UgJiYgcEI8IGNvbmZpZGVuY2Upew0KICAgIHByZWRDbGFzczwtImNsYXNzQSINCiAgfWVsc2UgaWYocEEgPCBjb25maWRlbmNlICYmIHBCPiBjb25maWRlbmNlKXsNCiAgICBwcmVkQ2xhc3M8LSJjbGFzc0IiDQogIH1lbHNlIGlmKHBBPCBjb25maWRlbmNlICYmIHBCPCBjb25maWRlbmNlKXsNCiAgICBwcmVkQ2xhc3M8LSJub0NsYXNzIg0KICB9ZWxzZXsNCiAgICBwcmVkQ2xhc3M8LSJib3RoQ2xhc3NlcyINCiAgICANCiAgICByZXR1cm4ocHJlZENsYXNzKQ0KICB9fQ0KDQoNCiNmdW5jdGlvbiB0byBjYWxjdWxhdGUgY29uZmlkZW5jZSBvZiBlYWNoIHByZWRpY3Rpb24NCmdldC5jb25maWRlbmNlPC1mdW5jdGlvbihwdmFsQSxwdmFsQil7DQogIHNlY29uZEhpZ2hlc3Q8LWlmZWxzZShwdmFsQT5wdmFsQixwdmFsQixwdmFsQSkNCiAgY29uZjwtKDEtc2Vjb25kSGlnaGVzdCkNCiAgcmV0dXJuKGNvbmYpDQp9DQoNCmZvcmNlZENwPC1mdW5jdGlvbihwdmFsQSxwdmFsQil7DQogIGlmZWxzZShwdmFsQT5wdmFsQiwicHJlc2VuY2UiLCJhYnNlbmNlIikNCn0NCg0KZXh0cmFjdFZhbHM8LWZ1bmN0aW9uKHByZWRyYXMpew0KICBsaWJyYXJ5KHJhc3RlcikNCiAgdmFscyA8LSAgcmFzdGVyOjp2YWx1ZXMocHJlZHJhcykNCiAgY29vcmQgPC0gIHJhc3Rlcjo6eHlGcm9tQ2VsbChwcmVkcmFzLDE6bmNlbGwocHJlZHJhcykpDQogIHJhc3Rlcl9maXR0ZWQgPC0gY2JpbmQoY29vcmQsdmFscykNCiAgcmFzdGVyX2ZpdHRlZC5kZjwtYXMuZGF0YS5mcmFtZShyYXN0ZXJfZml0dGVkKQ0KICByYXN0ZXJfZml0dGVkLmRmMTwtbmEub21pdChyYXN0ZXJfZml0dGVkLmRmKQ0KICByYXN0ZXJfZml0dGVkLmRmMSRhYnNlbmNlPC1yYXN0ZXJfZml0dGVkLmRmMSR2YWxzDQogIHJhc3Rlcl9maXR0ZWQuZGYxJHByZXNlbmNlPC0gKDEtcmFzdGVyX2ZpdHRlZC5kZjEkYWJzZW5jZSkNCiAgcmV0dXJuKHJhc3Rlcl9maXR0ZWQuZGYxKQ0KfQ0KYGBgDQoNCiMjIyBDb2RlIGZvciBjbGFzcyBjb25mb3JtYWwgcHJlZGljdGlvbiBmdW5jdGlvbg0KDQpgYGB7ciBDbGFzc0NvbmZvcm1hbFByZWRpY3Rpb24sZWNobz1GQUxTRX0NCmNsYXNzQ29uZm9ybWFsUHJlZGljdGlvbjwtZnVuY3Rpb24oeCx5KXsNCmVuc19yZXN1bHRzPC0gZ2V0KCJ4IikNCmVuc19jYWxpYjwtZW5zX3Jlc3VsdHMkZW5zX21vZGVsJHByZWQNCmNhbGliUHJlc2VuY2U8LXN1YnNldChlbnNfY2FsaWIkcHJlc2VudCxlbnNfY2FsaWIkb2JzPT0ncHJlc2VudCcsKQ0KY2FsaWJBYnNlbmNlPC1zdWJzZXQoZW5zX2NhbGliJGFic2VudCxlbnNfY2FsaWIkb2JzPT0nYWJzZW50JywpDQpwcmVkaWN0ZWQudmFsdWVzPC1leHRyYWN0VmFscyh5KQ0KDQp0ZXN0UHJlc2VuY2U8LXByZWRpY3RlZC52YWx1ZXMkcHJlc2VuY2UNCnRlc3RBYnNlbmNlPC1wcmVkaWN0ZWQudmFsdWVzJGFic2VuY2UNCg0KI2Rlcml2ZSBwLlZhbHVlcyBmb3IgY2xhc3MgQQ0Kc21hbGxyQTwtbGFwcGx5KHRlc3RQcmVzZW5jZSxmdW5jdGlvbih4KSBHZXRMZW5ndGgoY2FsaWJQcmVzZW5jZSx4KSkNCnNtYWxsckFfMTwtIHVubGlzdCAoc21hbGxyQSkNCm5DYWxpYlNldDwtbGVuZ3RoKGNhbGliUHJlc2VuY2UpKzENCnB2YWxBPC1zbWFsbHJBXzEvbkNhbGliU2V0DQoNCiMgZGVyaXZlIHAuVmFsdWVzIGZvciBDbGFzcyBCDQpzbWFsbHJCPC1sYXBwbHkodGVzdEFic2VuY2UsZnVuY3Rpb24oeCkgR2V0TGVuZ3RoKGNhbGliQWJzZW5jZSx4KSkNCnNtYWxsckJfMTwtIHVubGlzdCAoc21hbGxyQikNCm5DYWxpYlNldEI8LWxlbmd0aChjYWxpYkFic2VuY2UpKzENCnB2YWxCPC1zbWFsbHJCXzEvbkNhbGliU2V0Qg0KDQogcHZhbHNkZjwtYXMuZGF0YS5mcmFtZShjYmluZChwdmFsQSxwdmFsQiwwLjIwKSkNCiMgcmFzdGVyX2NwXzIwPC1tYXBwbHkoQ1Bjb25mLHB2YWxzZGYkcHZhbEEscHZhbHNkZiRwdmFsQixwdmFsc2RmWzNdKQ0KIyB0YWJsZShyYXN0ZXJfY3BfMjApDQoNCnB2YWxzZGYkY29uZjwtZ2V0LmNvbmZpZGVuY2UocHZhbHNkZiRwdmFsQSxwdmFsc2RmJHB2YWxCKQ0KcHZhbHNkZl8xPC1jYmluZChwdmFsc2RmLHByZWRpY3RlZC52YWx1ZXMpDQp9DQpgYGANCg0KIyMjIFF1YW50aWZ5IGNvbmZpZGVuY2Ugb2YgcHJlZGljdGVkIHZhbHVlcyB1c2luZyBjbGFzcyBjb25mb3JtYWwgcHJlZGljdGlvbg0KDQpgYGB7ciBxdWFudGlmeUNvbmZpZGVuY2V9DQojIGNvbmZpZGVuY2UgbWFwIGZvciBCZWxnaXVtIGZvciBwcmVkaWN0ZWQgcmlzayB1bmRlciBoaXN0b3JpY2FsIGNsaW1hdGUNCnB2YWxzZGZfaGlzdDwtY2xhc3NDb25mb3JtYWxQcmVkaWN0aW9uKGxtX2Vuc19oYWIsZW5zX3ByZWRfaGFiKQ0KIyBDb25maWRlbmNlIG1hcHMgZm9yIEJlbGdpdW0gdW5kZXIgUkNQIHNjZW5hcmlvcyBvZiBjbGltYXRlIGNoYW5nZQ0KcHZhbHNkZl9yY3AyNjwtY2xhc3NDb25mb3JtYWxQcmVkaWN0aW9uKGxtX2Vuc19oYWIsZW5zX3ByZWRfaGFiMjYpDQpwdmFsc2RmX3JjcDQ1PC1jbGFzc0NvbmZvcm1hbFByZWRpY3Rpb24obG1fZW5zX2hhYixlbnNfcHJlZF9oYWI0NSkNCnB2YWxzZGZfcmNwODU8LWNsYXNzQ29uZm9ybWFsUHJlZGljdGlvbihsbV9lbnNfaGFiLGVuc19wcmVkX2hhYjg1KQ0KDQojIG9wdGlvbiB0byBleHBvcnQgY29uZmlkZW5jZSBhbmQgcHZhbHMgYXMgY3N2IA0Kd3JpdGUuY3N2KHB2YWxzZGZfaGlzdCxmaWxlPXBhc3RlKGdlbk91dHB1dCwiY29uZmlkZW5jZV8iLHRheG9ua2V5LCAiX2hpc3QuY3N2IixzZXA9IiIpKQ0KYGBgDQoNCiMjIyBDcmVhdGUgY29uZmlkZW5jZSBtYXBzDQoNCmBgYHtyIGNyZWF0ZUNvbmZpZGVuY2VNYXBzLGZpZy5zaG93PSJob2xkIn0NCmJya3MgPC0gc2VxKDAsIDEsIGJ5PTAuMSkgDQogIG5iIDwtIGxlbmd0aChicmtzKS0xIA0KICBwYWwgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbCg0LCAnU3BlY3RyYWwnKSkpDQogIGNvbHM8LXBhbChuYikNCiAgDQoNCmNvbmZpZGVuY2VNYXBzPC1mdW5jdGlvbih4LHRheG9ua2V5LHRheG9uTmFtZSxtYXB0eXBlKXsNCnB2YWxzX2RhdGFmcmFtZTwtZ2V0KCJ4IikNCmRhdGEueHl6IDwtIHB2YWxzX2RhdGFmcmFtZVtjKCJ4IiwieSIsImNvbmYiKV0NCnJzdCA8LSByYXN0ZXJGcm9tWFlaKGRhdGEueHl6KQ0KY3JzKHJzdCk8LUNSUygiK3Byb2o9bGFlYSArbGF0XzA9NTIgK2xvbl8wPTEwICt4XzA9NDMyMTAwMCAreV8wPTMyMTAwMDAgK2VsbHBzPUdSUzgwICt1bml0cz1tICtub19kZWZzIikgDQpwbG90KHJzdCxicmVha3M9YnJrcywgY29sPWNvbHMsbGFiLmJyZWFrcz1icmtzKQ0Kd3JpdGVSYXN0ZXIocnN0LCBmaWxlbmFtZT1maWxlLnBhdGgocmFzdGVyT3V0cHV0LHBhc3RlKCJiZV8iLHRheG9ua2V5LCAiXyIsbWFwdHlwZSwiLnRpZiIsc2VwPSIiKSksIGZvcm1hdD0iR1RpZmYiLG92ZXJ3cml0ZT1UUlVFKQ0KZXhwb3J0UERGKHJzdCx0YXhvbmtleSx0YXhvbk5hbWU9dGF4b25OYW1lLG5hbWVleHRlbnNpb249IHBhc3RlKG1hcHR5cGUsIi5wZGYiLHNlcD0iIikpDQp9DQoNCmhpc3QuY29uZi5tYXA8LWNvbmZpZGVuY2VNYXBzKHB2YWxzZGZfaGlzdCx0YXhvbmtleSx0YXhvbk5hbWUsbWFwdHlwZT0iaGlzdF9jb25mIikNCnJjcDI2LmNvbmYubWFwPC1jb25maWRlbmNlTWFwcyhwdmFsc2RmX3JjcDI2LHRheG9ua2V5LHRheG9uTmFtZSxtYXB0eXBlPSJyY3AyNl9jb25mIikNCnJjcDQ1LmNvbmYubWFwPC1jb25maWRlbmNlTWFwcyhwdmFsc2RmX3JjcDQ1LHRheG9ua2V5LHRheG9uTmFtZSxtYXB0eXBlPSJyY3A0NV9jb25mIikNCnJjcDg1LmNvbmYubWFwPC1jb25maWRlbmNlTWFwcyhwdmFsc2RmX3JjcDg1LHRheG9ua2V5LHRheG9uTmFtZSxtYXB0eXBlPSJyY3A4NV9jb25mIikNCmBgYA0KDQoNCg0KDQojIyMgTW9kZWwgZXZhbHV0aW9uIHN1bW1hcnkNCmBgYHtyIGV4cG9ydCBNb2RlbEV2YWx1YXRpb259DQptZWFuUmVzdWx0czwtc3VtbWFyeShtb2RlbFJlc3VsdHMxKQ0KZXVfYWNjdXJhY3k8LW1lYW5SZXN1bHRzJHN0YXRpc3RpY3MkQWNjdXJhY3kNCmV1X2thcHBhPC1tZWFuUmVzdWx0cyRzdGF0aXN0aWNzJEthcHBhDQoNCndyaXRlLmNzdihldV9hY2N1cmFjeSxmaWxlPXBhc3RlMChnZW5PdXRwdXQsdGF4b25rZXksIl9hY2N1cmFjeS5jc3YiKSkNCndyaXRlLmNzdihldV9rYXBwYSxmaWxlPXBhc3RlMChnZW5PdXRwdXQsdGF4b25rZXksIl9rYXBwYS5jc3YiKSkNCg0KIyBkZXJpdmUgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5DQogdGFibGUocHJlZEVuczEkcHJlZCxwcmVkRW5zMSRvYnMpDQogc2Vuc2l0aXZpdHkocHJlZEVuczEkcHJlZCxwcmVkRW5zMSRvYnMpDQogc3BlY2lmaWNpdHkocHJlZEVuczEkcHJlZCxwcmVkRW5zMSRvYnMpDQogc2Vuczwtc2Vuc2l0aXZpdHkocHJlZEVuczEkcHJlZCxwcmVkRW5zMSRvYnMpDQogc3BlYzwtc3BlY2lmaWNpdHkocHJlZEVuczEkcHJlZCxwcmVkRW5zMSRvYnMpDQoNCm1vcmFuc0k8LU1vcmFuLkkocmVzLmJlc3QuZGYkaGFiLnJlcyxvY2MuZGlzdHMuaW52LHNjYWxlZD1UUlVFLGFsdGVybmF0aXZlPSJncmVhdGVyIikNCk1vcmFuc0k8LW1vcmFuc0kkb2JzZXJ2ZWQNCmVuc2VtYmxlRXZhbHVhdGlvbjwtKGxtX2Vuc19oYWIkZW5zX21vZGVsJHJlc3VsdHNbMjo1XSkNCg0KbW9kZWxFdmFsdWF0aW9uPC1jYmluZChzZW5zLHNwZWMsTW9yYW5zSSxlbnNlbWJsZUV2YWx1YXRpb24pDQp3cml0ZS5jc3YobW9kZWxFdmFsdWF0aW9uLGZpbGU9cGFzdGUwKGdlbk91dHB1dCx0YXhvbmtleSwiX01vZGVsRXZhbC5jc3YiKSkNCmBgYA0KDQojIyMgR2VuZXJhdGUgYW5kIGV4cG9ydCByZXNwb25zZSBjdXJ2ZXMgaW4gb3JkZXIgb2YgdmFyaWFibGUgaW1wb3J0YW5jZQ0KDQpgYGB7ciByZXNwb25zZUN1cnZlc30NCnRvcFByZWRzIDwtIHZhcmlhYmxlSW1wb3J0YW5jZVt3aXRoKHZhcmlhYmxlSW1wb3J0YW5jZSxvcmRlcigtb3ZlcmFsbCkpLF0NCnZhck5hbWVzPC1yb3duYW1lcyh0b3BQcmVkcykNCiMgY29tYmluZSBwcmVkaWN0aW9ucyBmcm9tIGVhY2ggbW9kZWwgZm9yIGVhY2ggdmFyaWFibGUNCnBhcnRpYWxfZ2JtPC1mdW5jdGlvbih4KXsNCiAgbS5nYm08LXBkcDo6cGFydGlhbChtb2RlbF90cmFpbl9oYWJpdGF0JGdibSRmaW5hbE1vZGVsLHByZWQudmFyPXBhc3RlKHgpLHRyYWluID0gb2NjZXUuZnVsbC5kYXRhLmRmMSx0eXBlPSJjbGFzc2lmaWNhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgICAgcHJvYj1UUlVFLG4udHJlZXM9IG1vZGVsX3RyYWluX2hhYml0YXQkZ2JtJGZpbmFsTW9kZWwkbi50cmVlcywgd2hpY2guY2xhc3MgPSAyLGdyaWQucmVzb2x1dGlvbj1ucm93KG9jY2V1LmZ1bGwuZGF0YS5kZjEpKQ0KfQ0KDQoNCg0KZ2JtLnBhcnRpYWwubGlzdDwtbGFwcGx5KHZhck5hbWVzLHBhcnRpYWxfZ2JtKQ0KDQpwYXJ0aWFsX2dsbTwtZnVuY3Rpb24oeCl7DQptLmdsbTwtcGRwOjpwYXJ0aWFsKG1vZGVsX3RyYWluX2hhYml0YXQkZ2xtJGZpbmFsTW9kZWwscHJlZC52YXI9cGFzdGUoeCksdHJhaW4gPSBvY2NldS5mdWxsLmRhdGEuZGYxLHR5cGU9ImNsYXNzaWZpY2F0aW9uIiwNCiAgICAgICAgICAgICAgcHJvYj1UUlVFLHdoaWNoLmNsYXNzID0gMixncmlkLnJlc29sdXRpb249bnJvdyhvY2NldS5mdWxsLmRhdGEuZGYxKSkNCn0NCg0KZ2xtLnBhcnRpYWwubGlzdDwtbGFwcGx5KHZhck5hbWVzLHBhcnRpYWxfZ2xtKQ0KDQpwYXJ0aWFsX3JmPC1mdW5jdGlvbih4KXsNCiAgcGRwOjpwYXJ0aWFsKG1vZGVsX3RyYWluX2hhYml0YXQkcmYkZmluYWxNb2RlbCxwcmVkLnZhcj1wYXN0ZSh4KSx0cmFpbiA9IG9jY2V1LmZ1bGwuZGF0YS5kZjEsdHlwZT0iY2xhc3NpZmljYXRpb24iLA0KICAgICAgICAgICAgICBwcm9iPVRSVUUsd2hpY2guY2xhc3MgPSAyLGdyaWQucmVzb2x1dGlvbj1ucm93KG9jY2V1LmZ1bGwuZGF0YS5kZjEpKQ0KfQ0KDQpyZi5wYXJ0aWFsLmxpc3Q8LWxhcHBseSh2YXJOYW1lcyxwYXJ0aWFsX3JmKQ0KDQpwYXJ0aWFsX2tubjwtZnVuY3Rpb24oeCl7DQptLmtubjwtcGRwOjpwYXJ0aWFsKG1vZGVsX3RyYWluX2hhYml0YXQka25uLHByZWQudmFyPXBhc3RlKHgpLHRyYWluID0gb2NjZXUuZnVsbC5kYXRhLmRmMSx0eXBlPSJjbGFzc2lmaWNhdGlvbiIsDQogICAgICAgICAgICAgICBwcm9iPVRSVUUsd2hpY2guY2xhc3MgPSAyLGdyaWQucmVzb2x1dGlvbj1ucm93KG9jY2V1LmZ1bGwuZGF0YS5kZjEpKQ0KfQ0KDQprbm4ucGFydGlhbC5saXN0PC1sYXBwbHkodmFyTmFtZXMscGFydGlhbF9rbm4pDQoNCg0KDQoNCnBhcnRpYWxfbWFyczwtZnVuY3Rpb24oeCl7DQptLm1hcnM8LXBkcDo6cGFydGlhbChtb2RlbF90cmFpbl9oYWJpdGF0JGVhcnRoJGZpbmFsTW9kZWwscHJlZC52YXI9cGFzdGUoeCksdHJhaW4gPSBvY2NldS5mdWxsLmRhdGEuZGYxLHR5cGU9ImNsYXNzaWZpY2F0aW9uIiwNCiAgICAgICAgICAgICAgcHJvYj1UUlVFLHdoaWNoLmNsYXNzID0gMixncmlkLnJlc29sdXRpb249bnJvdyhvY2NldS5mdWxsLmRhdGEuZGYxKSkNCn0NCg0KbWFycy5wYXJ0aWFsLmxpc3Q8LWxhcHBseSh2YXJOYW1lcyxwYXJ0aWFsX21hcnMpDQoNCg0KbmFtZXMoZ2xtLnBhcnRpYWwubGlzdCk8LXZhck5hbWVzDQpuYW1lcyhnYm0ucGFydGlhbC5saXN0KTwtdmFyTmFtZXMNCm5hbWVzKHJmLnBhcnRpYWwubGlzdCk8LXZhck5hbWVzDQpuYW1lcyhrbm4ucGFydGlhbC5saXN0KTwtdmFyTmFtZXMNCm5hbWVzKG1hcnMucGFydGlhbC5saXN0KTwtdmFyTmFtZXMNCg0KZ2xtLnBhcnRpYWwuZGY8LWFzLmRhdGEuZnJhbWUoZ2xtLnBhcnRpYWwubGlzdCkNCmdibS5wYXJ0aWFsLmRmPC1hcy5kYXRhLmZyYW1lKGdibS5wYXJ0aWFsLmxpc3QpDQpyZi5wYXJ0aWFsLmRmPC1hcy5kYXRhLmZyYW1lKHJmLnBhcnRpYWwubGlzdCkNCmtubi5wYXJ0aWFsLmRmPC1hcy5kYXRhLmZyYW1lKGtubi5wYXJ0aWFsLmxpc3QpDQoNCm1hcnMucGFydGlhbC5kZjwtYXMuZGF0YS5mcmFtZShtYXJzLnBhcnRpYWwubGlzdCkNCg0KcHJlZHg8LWRhdGEuZnJhbWUoKQ0KcHJlZHk8LWRhdGEuZnJhbWUoKQ0KDQpmb3IgKGkgaW4gdmFyTmFtZXMpew0KICBwcmVkeCA8LSByYmluZChwcmVkeCwgYXMuZGF0YS5mcmFtZShwYXN0ZShpLGksc2VwPSIuIikpKQ0KICBwcmVkeTwtIHJiaW5kKHByZWR5LGFzLmRhdGEuZnJhbWUocGFzdGUoaSwieWhhdCIsc2VwPSIuIikpKQ0KfQ0KbmFtZXMocHJlZHgpPC0iIg0KbmFtZXMocHJlZHkpPC0iIg0KDQpwcmVkeDE8LXQocHJlZHgpDQpwcmVkeTE8LXQocHJlZHkpDQoNCg0KZ2xtLnBhcnRpYWwuZGYkZGF0YTwtJ0dMTScNCmdibS5wYXJ0aWFsLmRmJGRhdGE8LSdHQk0nDQpyZi5wYXJ0aWFsLmRmJGRhdGE8LSdSRicNCmtubi5wYXJ0aWFsLmRmJGRhdGE8LSdLTk4nDQptYXJzLnBhcnRpYWwuZGYkZGF0YTwtJ01BUlMnDQoNCmFsbF9kZnM8LXJiaW5kLmRhdGEuZnJhbWUoZ2xtLnBhcnRpYWwuZGYsZ2JtLnBhcnRpYWwuZGYscmYucGFydGlhbC5kZixrbm4ucGFydGlhbC5kZixtYXJzLnBhcnRpYWwuZGYpDQoNCg0KcmVzcG9uc2VDdXJ2ZXM8LWZ1bmN0aW9uKHgseSkgew0KICBjb2xvcnMgPC0gYygiR0xNIiA9ICJncmF5IiwgIkdCTSI9InJlZCIsIlJGIj0iYmx1ZXZpb2xldCIsIktOTiI9ImNoYXJ0cmV1c2UiLCAiTUFSUyI9ICJob3RwaW5rIikgDQogIGdncGxvdChhbGxfZGZzLChhZXMoeD0uZGF0YVtbeF1dLHk9LmRhdGFbW3ldXSkpKSArDQogICAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IGRhdGEpLCBzaXplID0xLjIsIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuMikpKw0KICAgdGhlbWVfYncoKSsNCiAgICBsYWJzKHk9IlBhcnRpYWwgcHJvYmFiaWxpdHkiLCB4PSBnc3ViKCIvLy4uKiIsIiIseCksY29sb3I9IkxlZ2VuZCIpICsNCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JzKQ0KfSAgDQoNCmFsbHBsb3RzPC1tYXAyKHByZWR4MSxwcmVkeTEsIH5yZXNwb25zZUN1cnZlcygueCwueSkpDQoNCiNleHBvcnQgcGxvdHMgYXMgUE5Hcw0KZm9yKGkgaW4gc2VxX2Fsb25nKGFsbHBsb3RzKSl7DQogIHBuZyhwYXN0ZTAoZ2VuT3V0cHV0LHRheG9ua2V5LCJfIixpLCIucG5nIiksd2lkdGggPSA1LCBoZWlnaHQgPSA1LCB1bml0cyA9ICJpbiIscmVzPTMwMCkNCiAgcHJpbnQoYWxscGxvdHNbW2ldXSkNCiAgZGV2Lm9mZigpDQp9DQpgYGANCg0KDQojIyMgUGxvdCByZXNwb25zZSBjdXJ2ZXMNCmBgYHtyIHBsb3RSZXNwb25zZUN1cnZlcyxmaWcuc2hvdz0iaG9sZCJ9DQoNCnBhcihtZnJvdz1jKDMsMykpDQpmb3IoaSBpbiBzZXFfYWxvbmcoYWxscGxvdHMpKXsNCiAgcHJpbnQoYWxscGxvdHNbW2ldXSkNCn0NCmBgYA==