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

Extract climate data for global scale modelling


global.data <- sdmData(species~.,train=global_presabs, predictors=globalclimpreds)
Warning: package ‘earth’ was built under R version 4.2.3Warning: package ‘plotmo’ was built under R version 4.2.3Warning: package ‘TeachingDemos’ was built under R version 4.2.3Warning: package ‘randomForest’ was built under R version 4.2.3
global.data.df<-as.data.frame(global.data)

Identify highly correlated predictors

highlyCorrelated
CHELSA_meantemp
CHELSA_minTmpColdestMon
CHELSA_precipWettestMon
CHELSA_annPrecip
CHELSA_temp_seasonality

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) 

Transform eu occurrence dataset with unique presences back to a SpatialPoints dataframe.

euocc<-euocc.SDMtable[c("longitude", "latitude")]
coordinates(euocc)<- c("longitude", "latitude")
euocc$occ<- rep(1,length(euocc$latitude))#adds columns indicating species presence needed for modeling
proj4string(euocc)<-CRS("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0")#specify here the existing projection of the data

rmiclipreds<-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 ")
rmiproj<-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")
euocc1<-spTransform(euocc,rmiproj)

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 

Identify and remove highly correlated predictors from RMI rasterstack

# convert eu data to dataframe
occeu.sdmdata.df<-as.data.frame(occeu.sdmdata)

#identify highly correlated predictors drop first two columns which always be rID and occ
correlationMatrix <- cor(occeu.sdmdata.df[,-c(1:2)])

# find attributes that are highly corrected 
highlyCorrelated <- findCorrelation(correlationMatrix, cutoff=0.7,exact=TRUE,names=TRUE)
eupreds<-as.data.frame(highlyCorrelated)
kable(eupreds) %>%
kable_styling(bootstrap_options = c("striped"))
highlyCorrelated
mintemp
tempseas
anntemp_eea
pet100
SolRad100
annprecip_eea
maxtemp
rmiclimpreds_uncor<-dropLayer(rmiclimpreds,highlyCorrelated)

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 

Remove highly correlated predictors from the habitat/anthropogenic/climate stack (full stack)

# first remove and identify low variance predictors
nzv_preds<-nearZeroVar(occeu.full.data.df,names=TRUE)
occeu.full.data.df1<-select (occeu.full.data.df,-c(nzv_preds))

# find attributes that are highly corrected 
correlationMatrix <- cor(occeu.full.data.df1[,-1])
highlyCorrelated <- findCorrelation(correlationMatrix, cutoff=0.7,exact=TRUE,names=TRUE)
eupreds1<-as.data.frame(highlyCorrelated)
kable(eupreds1) %>%
kable_styling(bootstrap_options = c("striped"))
highlyCorrelated

# print names of highly correlated attributes
print(highlyCorrelated)
character(0)
occeu.full.data.df1<-select (occeu.full.data.df1,-c(highlyCorrelated))

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

Quantify confidence of predicted values using class conformal prediction

# confidence map for Belgium for predicted risk under historical climate
pvalsdf_hist<-classConformalPrediction(lm_ens_hab,ens_pred_hab)
# Confidence maps for Belgium under RCP scenarios of climate change
pvalsdf_rcp26<-classConformalPrediction(lm_ens_hab,ens_pred_hab26)
pvalsdf_rcp45<-classConformalPrediction(lm_ens_hab,ens_pred_hab45)
pvalsdf_rcp85<-classConformalPrediction(lm_ens_hab,ens_pred_hab85)

# option to export confidence and pvals as csv 
write.csv(pvalsdf_hist,file=paste(genOutput,"confidence_",taxonkey, "_hist.csv",sep=""))

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==