This note is intended to use for geospatial data in R

Load packages


library(sf); library(raster); library(ggplot2); library(rgdal); library(geojsonsf); library(tidyr); library(dplyr); library(mlr)

Load training data in R

setwd("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Training_Sample")
The working directory was changed to C:/Users/DELL/OneDrive - tuaf.edu.vn/Seasonal_Rice_Cropland_Mapping_Paper/Data/Training_Sample inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
train_st<-st_read(dsn = "C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Landsat\\Training", layer="Training19")
Error: Cannot open "C:\Users\DELL\OneDrive - tuaf.edu.vn\Seasonal_Rice_Cropland_Mapping_Paper\Data\Landsat\Training"; The source could be corrupt or not supported. See `st_drivers()` for a list of supported formats.

Removing NA values and organize training data and create spatial point shapefile

2013

# 2013
setwd("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Rice_training")

LS_2013<-read.csv("LS_2013.csv",header = T)

LS_2013<-na.omit(LS_2013)

dim(LS_2013); table(LS_2013$Class)

LS_2013$Class[LS_2013$Class=="Cropland"]<-"Rice"

ggplot(LS_2013)+geom_boxplot(aes(x=Class,y=NDVI,fill=Class))

table(LS_2013$Class)

Urban<-sample_n(LS_2013[LS_2013$Class=="Urban",],1300) 

Forest<-sample_n(LS_2013[LS_2013$Class=="Forest",],1300) 

LS_2013$Class[LS_2013$Class=="Urban"]<-NA

LS_2013$Class[LS_2013$Class=="Forest"]<-NA

LS_2013<-na.omit(LS_2013)

LS_2013<-rbind(LS_2013,Urban, Forest); table(LS_2013$Class)

LS_2013$Class[LS_2013$Class=="Forest"]<-"NonRice"
LS_2013$Class[LS_2013$Class=="Urban"]<-"NonRice"
LS_2013$Class[LS_2013$Class=="Water"]<-"NonRice"
table(LS_2013$Class)
# Create point shapefile

LS_2013<-st_as_sf(cbind.data.frame(LS_2013,geojsonsf::geojson_sf(LS_2013$.geo)))

st_write(LS_2013,dsn= "Training_2013.gpkg",driver = "ESRI Shapefile", append = T)

2014

# 2014
setwd("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Rice_training")

LS_2014<-read.csv("LS_2014.csv",header = T)

LS_2014$Class[LS_2014$Class=="Cropland"]<-"Rice"

LS_2014<-na.omit(LS_2014)

ggplot(LS_2014)+geom_boxplot(aes(x=Class,y=NDVI,fill=Class)); table(LS_2014$Class)

Urban<-sample_n(LS_2014[LS_2014$Class=="Urban",],1200) # Sample 2100 points from urban feature 
Forest<-sample_n(LS_2014[LS_2014$Class=="Forest",],1300) 

LS_2014$Class[LS_2014$Class=="Urban"]<-NA

LS_2014$Class[LS_2014$Class=="Forest"]<-NA

LS_2014<-na.omit(LS_2014)

LS_2014<-rbind(LS_2014,Urban, Forest); table(LS_2014$Class)

LS_2014$Class[LS_2014$Class=="Forest"]<-"NonRice"
LS_2014$Class[LS_2014$Class=="Urban"]<-"NonRice"
LS_2014$Class[LS_2014$Class=="Water"]<-"NonRice"
table(LS_2014$Class)

# Create point shapefile

LS_2014<-st_as_sf(cbind.data.frame(LS_2014,geojsonsf::geojson_sf(LS_2014$.geo)))

st_write(LS_2014,dsn= "Training_2014.gpkg",driver = "ESRI Shapefile", append = T)

##2015

# 2015
setwd("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Rice_training")

LS_2015<-read.csv("LS_2015.csv",header = T)

LS_2015<-na.omit(LS_2015)

LS_2015$Class[LS_2015$Class=="Cropland"]<-"Rice"

ggplot(LS_2015)+geom_boxplot(aes(x=Class,y=NDVI,fill=Class))

Urban<-sample_n(LS_2015[LS_2015$Class=="Urban",],1200) # Sample 2100 points from urban feature 

Forest<-sample_n(LS_2015[LS_2015$Class=="Forest",],1300) 

LS_2015$Class[LS_2015$Class=="Urban"]<-NA

LS_2015$Class[LS_2015$Class=="Forest"]<-NA

LS_2015<-na.omit(LS_2015)

LS_2015<-rbind(LS_2015,Urban, Forest); table(LS_2015$Class)

LS_2015$Class[LS_2015$Class=="Forest"]<-"NonRice"
LS_2015$Class[LS_2015$Class=="Urban"]<-"NonRice"
LS_2015$Class[LS_2015$Class=="Water"]<-"NonRice"
table(LS_2015$Class)

# Create point shapefile

LS_2015<-st_as_sf(cbind.data.frame(LS_2015,geojsonsf::geojson_sf(LS_2015$.geo)))

st_write(LS_2015,dsn= "Training_2015.gpkg",driver = "ESRI Shapefile", append = T)

##2016

# 2016
setwd("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Rice_training")
LS_2016<-read.csv("LS_2016.csv",header = T)

LS_2016<-na.omit(LS_2016)

LS_2016$Class[LS_2016$Class=="Cropland"]<-"Rice"

ggplot(LS_2016)+geom_boxplot(aes(x=Class,y=NDVI,fill=Class))

Urban<-sample_n(LS_2016[LS_2016$Class=="Urban",],1300) # Sample 2100 points from urban feature 

Forest<-sample_n(LS_2016[LS_2016$Class=="Forest",],1300) 

LS_2016$Class[LS_2016$Class=="Urban"]<-NA

LS_2016$Class[LS_2016$Class=="Forest"]<-NA

LS_2016<-na.omit(LS_2016)

LS_2016<-rbind(LS_2016,Urban, Forest); table(LS_2016$Class)

LS_2016$Class[LS_2016$Class=="Forest"]<-"NonRice"
LS_2016$Class[LS_2016$Class=="Urban"]<-"NonRice"
LS_2016$Class[LS_2016$Class=="Water"]<-"NonRice"
table(LS_2016$Class)
# Create point shapefile

LS_2016<-st_as_sf(cbind.data.frame(LS_2016,geojsonsf::geojson_sf(LS_2016$.geo)))

st_write(LS_2016,dsn= "Training_2016.gpkg",driver = "ESRI Shapefile", append = T)

2017

# 2017
setwd("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Rice_training")
LS_2017<-read.csv("LS_2017.csv",header = T)

LS_2017<-na.omit(LS_2017)

LS_2017$Class[LS_2017$Class=="Cropland"]<-"Rice"

ggplot(LS_2017)+geom_boxplot(aes(x=Class,y=NDVI,fill=Class))

Urban<-sample_n(LS_2017[LS_2017$Class=="Urban",],1200) # Sample 2100 points from urban feature 

Forest<-sample_n(LS_2017[LS_2017$Class=="Forest",],1200) 

LS_2017$Class[LS_2017$Class=="Urban"]<-NA

LS_2017$Class[LS_2017$Class=="Forest"]<-NA

LS_2017<-na.omit(LS_2017)

LS_2017<-rbind(LS_2017,Urban, Forest); table(LS_2017$Class)

LS_2017$Class[LS_2017$Class=="Forest"]<-"NonRice"
LS_2017$Class[LS_2017$Class=="Urban"]<-"NonRice"
LS_2017$Class[LS_2017$Class=="Water"]<-"NonRice"
table(LS_2017$Class)

# Create point shapefile

LS_2017<-st_as_sf(cbind.data.frame(LS_2017,geojsonsf::geojson_sf(LS_2017$.geo)))

st_write(LS_2017,dsn= "Training_2017.gpkg",driver = "ESRI Shapefile", append = T)

2018

# 2018
setwd("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Rice_training")
LS_2018<-read.csv("LS_2018.csv",header = T)

LS_2018<-na.omit(LS_2018)

LS_2018$Class[LS_2018$Class=="Cropland"]<-"Rice"

ggplot(LS_2018)+geom_boxplot(aes(x=Class,y=NDVI,fill=Class))

Urban<-sample_n(LS_2018[LS_2018$Class=="Urban",],1200) # Sample 2100 points from urban feature 

Forest<-sample_n(LS_2018[LS_2018$Class=="Forest",],1100) 

LS_2018$Class[LS_2018$Class=="Urban"]<-NA

LS_2018$Class[LS_2018$Class=="Forest"]<-NA

LS_2018<-na.omit(LS_2018)

LS_2018<-rbind(LS_2018,Urban, Forest)

LS_2018$Class[LS_2018$Class=="Forest"]<-"NonRice"
LS_2018$Class[LS_2018$Class=="Urban"]<-"NonRice"
LS_2018$Class[LS_2018$Class=="Water"]<-"NonRice"
table(LS_2018$Class)
# Create point shapefile

LS_2018<-st_as_sf(cbind.data.frame(LS_2018,geojsonsf::geojson_sf(LS_2018$.geo)))

st_write(LS_2018,dsn= "Training_2018.gpkg",driver = "ESRI Shapefile", append = T)

2019

# 2019
setwd("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Rice_training")
LS_2019<-read.csv("LS_2019.csv",header = T)

LS_2019<-na.omit(LS_2019)

LS_2019$Class[LS_2019$Class=="Cropland"]<-"Rice"

ggplot(LS_2019)+geom_boxplot(aes(x=Class,y=NDVI,fill=Class))

Urban<-sample_n(LS_2019[LS_2019$Class=="Urban",],1400) # Sample 2100 points from urban feature 

Forest<-sample_n(LS_2019[LS_2019$Class=="Forest",],1093) 

LS_2019$Class[LS_2019$Class=="Urban"]<-NA

LS_2019$Class[LS_2019$Class=="Forest"]<-NA

LS_2019<-na.omit(LS_2019)

LS_2019<-rbind(LS_2019,Urban, Forest); table(LS_2019$Class)

LS_2019$Class[LS_2019$Class=="Forest"]<-"NonRice"
LS_2019$Class[LS_2019$Class=="Urban"]<-"NonRice"
LS_2019$Class[LS_2019$Class=="Water"]<-"NonRice"
table(LS_2019$Class)
# Create point shapefile

LS_2019<-st_as_sf(cbind.data.frame(LS_2019,geojsonsf::geojson_sf(LS_2019$.geo)))

st_write(LS_2019,dsn= "Training_2019.gpkg",driver = "ESRI Shapefile", append = T)
                                    

Random Forest with mlr package

2013

library(mlr); library(randomForest); library(parallel); library(parallelMap)
# Preprocessing data 
RF_2013<-LS_2013 %>% data.frame() %>% select(1:9,-6,16,17,24)

RF_2013$Class<-as.factor(RF_2013$Class)
# Create a task and learner 
LS_Task <- makeClassifTask(data = RF_2013, target = "Class")

LS_learner<-makeLearner("classif.randomForest")

# Search for hyperparameter turnings 

forestParamSpace <- makeParamSet(
  makeIntegerParam("ntree", lower = 100, upper = 500),
  makeIntegerParam("mtry", lower = 4, upper = 12),
  makeIntegerParam("nodesize", lower = 1, upper =10),
  makeIntegerParam("maxnodes", lower = 5, upper = 30)) # Set a range of values to search for.

randSearch <- makeTuneControlRandom(maxit = 100)

cvForTuning <- makeResampleDesc("CV", iters = 5)

parallelStartSocket(cpus = detectCores())

tunedForestPars <- tuneParams(LS_learner, task = LS_Task,
                            resampling = cvForTuning,
                            par.set = forestParamSpace,
                            control = randSearch)
parallelStop()

tunedForestPars
# Traing the random forest
tunedForest <- setHyperPars(LS_learner, par.vals = tunedForestPars$x)

tunedForestModel <- train(tunedForest, LS_Task)

# Cross-validate the model using the hyperparameter turnings 
## outer <- makeResampleDesc("CV", iters = 5)

## forestWrapper <- makeTuneWrapper("classif.randomForest", resampling = cvForTuning, par.set = forestParamSpace, control = randSearch)

## parallelStartSocket(cpus = detectCores())

## cvWithTuning <- resample(forestWrapper, LS_Task, resampling = outer)

## parallelStop()

## cvWithTuning
# Cross-validating the model without hyperparameter turnings 

# kFold <- makeResampleDesc(method = "RepCV", folds = 10, reps = 50, stratify = TRUE)

# kFoldCV <- resample(learner = LS_learner, task = LS_Task, resampling = kFold, measures = list(mmce, acc))
# Check the confusion matrix 

# calculateConfusionMatrix(kFoldCV$pred, relative = TRUE)

Tune result: Op. pars: ntree=182; mtry=9; nodesize=2; maxnodes=30 mmce.test.mean=0.1148977

Try

# Preprocessing data 
library(caTools)

RF_2013<-LS_2018 %>% data.frame() %>% select(1:30,-6)

RF_2013$Class<-as.factor(RF_2013$Class)

# Split data in two sets 
split<-sample.split(RF_2013$Class, SplitRatio = 0.8)

# training set
training_set<-subset(RF_2013, split==T)

test_set<-subset(RF_2013, split==F)

table(test_set$Class)
# Create a task and learner 
RF_Task <- makeClassifTask(data = training_set, target = "Class")

RF_learner<-makeLearner("classif.randomForest")

RF_train<-train(RF_learner,RF_Task) 

# Holdout cross-validation
set.seed(234)
RF_10_folds<-makeResampleDesc(method="RepCV", stratify = T, folds=10, reps=5)

RF_cross_validation <- resample(learner = RF_learner, task = RF_Task, resampling = RF_10_folds, measures = list(mmce, acc)) 
# Cross-validation 

calculateConfusionMatrix(RF_cross_validation$pred,relative = T)

# Independent testing
test_set$Class<-as.factor(test_set$Class)

LS_pred1<- predict(RF_train, newdata=test_set[,-1],type="class")

LS<-data.frame(Class=LS_pred1$data)

names(LS)<-"Class"

library(e1071)
library(caret)
test_set$Class<-as.factor(test_set$Class)

cm2<-confusionMatrix(LS$Class,test_set$Class)
cm2
library(raster)

library(sp)

library(rgeos)

library(rgdal)

Calculate the areas

2013

library(raster)
# provinces shapefiles

folder<-"C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Areas"

provinces<-st_read(dsn=folder,layer = "Red_River_Delta")

pro<-st_transform(provinces,crs = 32648)

# Read raster 
LS<-raster("C:\\Users\\DELL\\OneDrive - tuaf.edu.vn\\Seasonal_Rice_Cropland_Mapping_Paper\\Data\\Landsat\\Classified_Nomasked\\map2019.tif")

NAvalue(LS)<-0

names(LS)<-"Classified_2013"
# Clip raster by pprovinces

myarea<-function(image,vectors){
  for (i in 1:nrow(vectors)){
    if (vectors$VARNAME_1[i]=="Bac Ninh"){
      BN<-vectors[i,2]
      BN_crop<-crop(image,BN,snap="out")
      BN_mask<-mask(BN_crop,BN)
      df1<-as.data.frame(BN_mask) %>%   group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(BN_mask)[1] * res(BN_mask)[2]/10000)
      df1<-df1[1,]
      df1$Province<-"Bac Ninh"
      print(df1)

    } else if(vectors$VARNAME_1[i]=="Ha Nam"){
      Hanam<-vectors[i,2]
      Hanam_crop<-crop(image,Hanam,snap="out")
      Hanam_mask<-mask(Hanam_crop,Hanam)
      df2<-as.data.frame(Hanam_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(Hanam_mask)[1] * res(Hanam_mask)[2]/10000)
      df2<-df2[1,]
      df2$Province<-"Ha Nam"
      print(df2)
    }else if(vectors$VARNAME_1[i]=="Ha Noi"){
      HN<-vectors[i,2]
      HN_crop<-crop(image,HN,snap="out")
      HN_mask<-mask(HN_crop,HN)
      df3<-as.data.frame(HN_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(HN_mask)[1] * res(HN_mask)[2]/10000)
      df3<-df3[1,]
      df3$Province<-"Ha Noi"
      print(df3)
    }else if(vectors$VARNAME_1[i]=="Hai Duong"){
      HD<-vectors[i,2]
      HD_crop<-crop(image,HD,snap="out")
      HD_mask<-mask(HD_crop,HD)
      df4<-as.data.frame(HD_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(HD_mask)[1] * res(HD_mask)[2]/10000)
      df4<-df4[1,]
      df4$Province<-"Hai Duong"
      print(df4)
    } else if (vectors$VARNAME_1[i]=="Hai Phong"){
      HP<-vectors[i,2]
      HP_crop<-crop(image,HP,snap="out")
      HP_mask<-mask(HP_crop,HP)
      df5<-as.data.frame(HP_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(HP_mask)[1] * res(HP_mask)[2]/10000)
      df5<-df5[1,]
      df5$Province<-"Hai Phong"
      print(df5)
    }else if(vectors$VARNAME_1[i]=="Hung Yen"){
      HY<-vectors[i,2]
      HY_crop<-crop(image,HY,snap="out")
      HY_mask<-mask(HY_crop,HY)
      df6<-as.data.frame(HY_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(HY_mask)[1] * res(HY_mask)[2]/10000)
      df6<-df6[1,]
      df6$Province<-"Hung Yen"
      print(df6)
    }else if(vectors$VARNAME_1[i]=="Nam Dinh"){
     ND<-vectors[i,2]
     ND_crop<-crop(image,ND,snap="out")
     ND_mask<-mask(ND_crop,ND)
     df7<-as.data.frame(ND_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(ND_mask)[1] * res(ND_mask)[2]/10000) 
     df7<-df7[1,]
     df7$Province<-"Nam Dinh"
     print(df7)
    }else if(vectors$VARNAME_1[i]=="Ninh Binh"){
     NB<-vectors[i,2]
     NB_crop<-crop(image,NB,snap="out")
     NB_mask<-mask(NB_crop,NB)
     df8<-as.data.frame(NB_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(NB_mask)[1] * res(NB_mask)[2]/10000) 
     df8<-df8[1,]
     df8$Province<-"Ninh Binh"
     print(df8)
    }else if(vectors$VARNAME_1[i]=="Thai Binh"){
     TB<-vectors[i,2]
     TB_crop<-crop(image,TB,snap="out")
     TB_mask<-mask(TB_crop,TB)
     df9<-as.data.frame(TB_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(TB_mask)[1] * res(TB_mask)[2]/10000) 
     df9<-df9[1,]
     df9$Province<-"Thai Binh"
     print(df9)
    }  else{
      VP<-vectors[i,2]
      VP_crop<-crop(image,VP,snap="out")
      VP_mask<-mask(VP_crop,VP)
      df10<-as.data.frame(VP_mask) %>% group_by(Classified_2013) %>% tally() %>% mutate(area = n * res(VP_mask)[1] * res(VP_mask)[2]/10000) 
      df10<-df10[1,]
      df10$Province<-"Vinh Phuc"
      print(df10)
    }}} 

df<-myarea(LS,pro)
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGlzIG5vdGUgaXMgaW50ZW5kZWQgdG8gdXNlIGZvciBnZW9zcGF0aWFsIGRhdGEgaW4gUg0KDQojIExvYWQgcGFja2FnZXMgDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KHNmKTsgbGlicmFyeShyYXN0ZXIpOyBsaWJyYXJ5KGdncGxvdDIpOyBsaWJyYXJ5KHJnZGFsKTsgbGlicmFyeShnZW9qc29uc2YpOyBsaWJyYXJ5KHRpZHlyKTsgbGlicmFyeShkcGx5cik7IGxpYnJhcnkobWxyKQ0KYGBgDQoNCiMgTG9hZCB0cmFpbmluZyBkYXRhIGluIFINCg0KYGBge3J9DQpzZXR3ZCgiQzpcXFVzZXJzXFxERUxMXFxPbmVEcml2ZSAtIHR1YWYuZWR1LnZuXFxTZWFzb25hbF9SaWNlX0Nyb3BsYW5kX01hcHBpbmdfUGFwZXJcXERhdGFcXFRyYWluaW5nX1NhbXBsZSIpDQoNCnRyYWluX3N0PC1zdF9yZWFkKGRzbiA9ICJDOlxcVXNlcnNcXERFTExcXE9uZURyaXZlIC0gdHVhZi5lZHUudm5cXFNlYXNvbmFsX1JpY2VfQ3JvcGxhbmRfTWFwcGluZ19QYXBlclxcRGF0YVxcTGFuZHNhdFxcVHJhaW5pbmciLCBsYXllcj0iVHJhaW5pbmcxOSIpDQoNCnRyYWluX3N0JENsYXNzPC1hcy5mYWN0b3IodHJhaW5fc3QkQ2xhc3MpDQoNCiMgQ29udmVydCBzcCBzaGFwZWZpbGUgdG8gc2Ygc2hhcGVmaWxlIGZvcm1hdHMgDQojICB0cmFpbl9zdDwtc3RfYXNfc2YodHJhaW4pDQojIG1ha2luZyBhbm90aGVyIHZhbHVlcyBpbmRpY2F0aW5nIGNsYXNzZXMNCg0KZm9yIChpIGluIDE6bnJvdyh0cmFpbl9zdCkpew0KICBpZih0cmFpbl9zdCRWQUxVRVtpXT09IjEiKXsNCiAgICB0cmFpbl9zdCRDbGFzc1tpXTwtIlJpY2UiDQogIH1lbHNlIGlmKHRyYWluX3N0JFZBTFVFW2ldPT0iMiIpew0KICAgIHRyYWluX3N0JENsYXNzW2ldPC0iRm9yZXN0Ig0KICB9ZWxzZSBpZiAodHJhaW5fc3QkVkFMVUVbaV09PSIzIil7DQogICAgdHJhaW5fc3QkQ2xhc3NbaV08LSJXYXRlciINCiAgfWVsc2UgaWYodHJhaW5fc3QkVkFMVUVbaV09PSI0Iil7DQogICAgdHJhaW5fc3QkQ2xhc3NbaV08LSJDcm9wbGFuZCINCiAgfWVsc2Ugew0KICAgIHRyYWluX3N0JENsYXNzW2ldPC0iVXJiYW4iDQogIH0NCn0NCg0KdHJhaW5fc3QkQ2xhc3M8LWFzLmZhY3Rvcih0cmFpbl9zdCRDbGFzcykNCg0KIyBXcml0ZSB0aGUgc2hhcGVmaWxlIHRvIGxvY2FsIGNvbXB1dGVyIA0Kc3Rfd3JpdGUodHJhaW5fc3QsZHNuPSAiVHJhaW5pbmdfUGFwZXIuZ3BrZyIsZHJpdmVyID0gIkVTUkkgU2hhcGVmaWxlIiwgYXBwZW5kID0gVCkNCg0KdGFibGUodHJhaW5fc3QkQ2xhc3MpDQpgYGANCg0KIyBSZW1vdmluZyBOQSB2YWx1ZXMgYW5kIG9yZ2FuaXplIHRyYWluaW5nIGRhdGEgYW5kIGNyZWF0ZSBzcGF0aWFsIHBvaW50IHNoYXBlZmlsZSANCiMjIDIwMTMNCg0KYGBge3J9DQojIDIwMTMNCnNldHdkKCJDOlxcVXNlcnNcXERFTExcXE9uZURyaXZlIC0gdHVhZi5lZHUudm5cXFNlYXNvbmFsX1JpY2VfQ3JvcGxhbmRfTWFwcGluZ19QYXBlclxcRGF0YVxcUmljZV90cmFpbmluZyIpDQoNCkxTXzIwMTM8LXJlYWQuY3N2KCJMU18yMDEzLmNzdiIsaGVhZGVyID0gVCkNCg0KTFNfMjAxMzwtbmEub21pdChMU18yMDEzKQ0KDQpkaW0oTFNfMjAxMyk7IHRhYmxlKExTXzIwMTMkQ2xhc3MpDQoNCkxTXzIwMTMkQ2xhc3NbTFNfMjAxMyRDbGFzcz09IkNyb3BsYW5kIl08LSJSaWNlIg0KDQpnZ3Bsb3QoTFNfMjAxMykrZ2VvbV9ib3hwbG90KGFlcyh4PUNsYXNzLHk9TkRWSSxmaWxsPUNsYXNzKSkNCg0KdGFibGUoTFNfMjAxMyRDbGFzcykNCg0KVXJiYW48LXNhbXBsZV9uKExTXzIwMTNbTFNfMjAxMyRDbGFzcz09IlVyYmFuIixdLDEzMDApIA0KDQpGb3Jlc3Q8LXNhbXBsZV9uKExTXzIwMTNbTFNfMjAxMyRDbGFzcz09IkZvcmVzdCIsXSwxMzAwKSANCg0KTFNfMjAxMyRDbGFzc1tMU18yMDEzJENsYXNzPT0iVXJiYW4iXTwtTkENCg0KTFNfMjAxMyRDbGFzc1tMU18yMDEzJENsYXNzPT0iRm9yZXN0Il08LU5BDQoNCkxTXzIwMTM8LW5hLm9taXQoTFNfMjAxMykNCg0KTFNfMjAxMzwtcmJpbmQoTFNfMjAxMyxVcmJhbiwgRm9yZXN0KTsgdGFibGUoTFNfMjAxMyRDbGFzcykNCg0KTFNfMjAxMyRDbGFzc1tMU18yMDEzJENsYXNzPT0iRm9yZXN0Il08LSJOb25SaWNlIg0KTFNfMjAxMyRDbGFzc1tMU18yMDEzJENsYXNzPT0iVXJiYW4iXTwtIk5vblJpY2UiDQpMU18yMDEzJENsYXNzW0xTXzIwMTMkQ2xhc3M9PSJXYXRlciJdPC0iTm9uUmljZSINCnRhYmxlKExTXzIwMTMkQ2xhc3MpDQojIENyZWF0ZSBwb2ludCBzaGFwZWZpbGUNCg0KTFNfMjAxMzwtc3RfYXNfc2YoY2JpbmQuZGF0YS5mcmFtZShMU18yMDEzLGdlb2pzb25zZjo6Z2VvanNvbl9zZihMU18yMDEzJC5nZW8pKSkNCg0Kc3Rfd3JpdGUoTFNfMjAxMyxkc249ICJUcmFpbmluZ18yMDEzLmdwa2ciLGRyaXZlciA9ICJFU1JJIFNoYXBlZmlsZSIsIGFwcGVuZCA9IFQpDQoNCmBgYA0KIyMgMjAxNA0KDQpgYGB7cn0NCiMgMjAxNA0Kc2V0d2QoIkM6XFxVc2Vyc1xcREVMTFxcT25lRHJpdmUgLSB0dWFmLmVkdS52blxcU2Vhc29uYWxfUmljZV9Dcm9wbGFuZF9NYXBwaW5nX1BhcGVyXFxEYXRhXFxSaWNlX3RyYWluaW5nIikNCg0KTFNfMjAxNDwtcmVhZC5jc3YoIkxTXzIwMTQuY3N2IixoZWFkZXIgPSBUKQ0KDQpMU18yMDE0JENsYXNzW0xTXzIwMTQkQ2xhc3M9PSJDcm9wbGFuZCJdPC0iUmljZSINCg0KTFNfMjAxNDwtbmEub21pdChMU18yMDE0KQ0KDQpnZ3Bsb3QoTFNfMjAxNCkrZ2VvbV9ib3hwbG90KGFlcyh4PUNsYXNzLHk9TkRWSSxmaWxsPUNsYXNzKSk7IHRhYmxlKExTXzIwMTQkQ2xhc3MpDQoNClVyYmFuPC1zYW1wbGVfbihMU18yMDE0W0xTXzIwMTQkQ2xhc3M9PSJVcmJhbiIsXSwxMjAwKSAjIFNhbXBsZSAyMTAwIHBvaW50cyBmcm9tIHVyYmFuIGZlYXR1cmUgDQpGb3Jlc3Q8LXNhbXBsZV9uKExTXzIwMTRbTFNfMjAxNCRDbGFzcz09IkZvcmVzdCIsXSwxMzAwKSANCg0KTFNfMjAxNCRDbGFzc1tMU18yMDE0JENsYXNzPT0iVXJiYW4iXTwtTkENCg0KTFNfMjAxNCRDbGFzc1tMU18yMDE0JENsYXNzPT0iRm9yZXN0Il08LU5BDQoNCkxTXzIwMTQ8LW5hLm9taXQoTFNfMjAxNCkNCg0KTFNfMjAxNDwtcmJpbmQoTFNfMjAxNCxVcmJhbiwgRm9yZXN0KTsgdGFibGUoTFNfMjAxNCRDbGFzcykNCg0KTFNfMjAxNCRDbGFzc1tMU18yMDE0JENsYXNzPT0iRm9yZXN0Il08LSJOb25SaWNlIg0KTFNfMjAxNCRDbGFzc1tMU18yMDE0JENsYXNzPT0iVXJiYW4iXTwtIk5vblJpY2UiDQpMU18yMDE0JENsYXNzW0xTXzIwMTQkQ2xhc3M9PSJXYXRlciJdPC0iTm9uUmljZSINCnRhYmxlKExTXzIwMTQkQ2xhc3MpDQoNCiMgQ3JlYXRlIHBvaW50IHNoYXBlZmlsZQ0KDQpMU18yMDE0PC1zdF9hc19zZihjYmluZC5kYXRhLmZyYW1lKExTXzIwMTQsZ2VvanNvbnNmOjpnZW9qc29uX3NmKExTXzIwMTQkLmdlbykpKQ0KDQpzdF93cml0ZShMU18yMDE0LGRzbj0gIlRyYWluaW5nXzIwMTQuZ3BrZyIsZHJpdmVyID0gIkVTUkkgU2hhcGVmaWxlIiwgYXBwZW5kID0gVCkNCg0KYGBgDQojIzIwMTUNCg0KYGBge3J9DQojIDIwMTUNCnNldHdkKCJDOlxcVXNlcnNcXERFTExcXE9uZURyaXZlIC0gdHVhZi5lZHUudm5cXFNlYXNvbmFsX1JpY2VfQ3JvcGxhbmRfTWFwcGluZ19QYXBlclxcRGF0YVxcUmljZV90cmFpbmluZyIpDQoNCkxTXzIwMTU8LXJlYWQuY3N2KCJMU18yMDE1LmNzdiIsaGVhZGVyID0gVCkNCg0KTFNfMjAxNTwtbmEub21pdChMU18yMDE1KQ0KDQpMU18yMDE1JENsYXNzW0xTXzIwMTUkQ2xhc3M9PSJDcm9wbGFuZCJdPC0iUmljZSINCg0KZ2dwbG90KExTXzIwMTUpK2dlb21fYm94cGxvdChhZXMoeD1DbGFzcyx5PU5EVkksZmlsbD1DbGFzcykpDQoNClVyYmFuPC1zYW1wbGVfbihMU18yMDE1W0xTXzIwMTUkQ2xhc3M9PSJVcmJhbiIsXSwxMjAwKSAjIFNhbXBsZSAyMTAwIHBvaW50cyBmcm9tIHVyYmFuIGZlYXR1cmUgDQoNCkZvcmVzdDwtc2FtcGxlX24oTFNfMjAxNVtMU18yMDE1JENsYXNzPT0iRm9yZXN0IixdLDEzMDApIA0KDQpMU18yMDE1JENsYXNzW0xTXzIwMTUkQ2xhc3M9PSJVcmJhbiJdPC1OQQ0KDQpMU18yMDE1JENsYXNzW0xTXzIwMTUkQ2xhc3M9PSJGb3Jlc3QiXTwtTkENCg0KTFNfMjAxNTwtbmEub21pdChMU18yMDE1KQ0KDQpMU18yMDE1PC1yYmluZChMU18yMDE1LFVyYmFuLCBGb3Jlc3QpOyB0YWJsZShMU18yMDE1JENsYXNzKQ0KDQpMU18yMDE1JENsYXNzW0xTXzIwMTUkQ2xhc3M9PSJGb3Jlc3QiXTwtIk5vblJpY2UiDQpMU18yMDE1JENsYXNzW0xTXzIwMTUkQ2xhc3M9PSJVcmJhbiJdPC0iTm9uUmljZSINCkxTXzIwMTUkQ2xhc3NbTFNfMjAxNSRDbGFzcz09IldhdGVyIl08LSJOb25SaWNlIg0KdGFibGUoTFNfMjAxNSRDbGFzcykNCg0KIyBDcmVhdGUgcG9pbnQgc2hhcGVmaWxlDQoNCkxTXzIwMTU8LXN0X2FzX3NmKGNiaW5kLmRhdGEuZnJhbWUoTFNfMjAxNSxnZW9qc29uc2Y6Omdlb2pzb25fc2YoTFNfMjAxNSQuZ2VvKSkpDQoNCnN0X3dyaXRlKExTXzIwMTUsZHNuPSAiVHJhaW5pbmdfMjAxNS5ncGtnIixkcml2ZXIgPSAiRVNSSSBTaGFwZWZpbGUiLCBhcHBlbmQgPSBUKQ0KYGBgDQojIzIwMTYNCg0KYGBge3J9DQojIDIwMTYNCnNldHdkKCJDOlxcVXNlcnNcXERFTExcXE9uZURyaXZlIC0gdHVhZi5lZHUudm5cXFNlYXNvbmFsX1JpY2VfQ3JvcGxhbmRfTWFwcGluZ19QYXBlclxcRGF0YVxcUmljZV90cmFpbmluZyIpDQpMU18yMDE2PC1yZWFkLmNzdigiTFNfMjAxNi5jc3YiLGhlYWRlciA9IFQpDQoNCkxTXzIwMTY8LW5hLm9taXQoTFNfMjAxNikNCg0KTFNfMjAxNiRDbGFzc1tMU18yMDE2JENsYXNzPT0iQ3JvcGxhbmQiXTwtIlJpY2UiDQoNCmdncGxvdChMU18yMDE2KStnZW9tX2JveHBsb3QoYWVzKHg9Q2xhc3MseT1ORFZJLGZpbGw9Q2xhc3MpKQ0KDQpVcmJhbjwtc2FtcGxlX24oTFNfMjAxNltMU18yMDE2JENsYXNzPT0iVXJiYW4iLF0sMTMwMCkgIyBTYW1wbGUgMjEwMCBwb2ludHMgZnJvbSB1cmJhbiBmZWF0dXJlIA0KDQpGb3Jlc3Q8LXNhbXBsZV9uKExTXzIwMTZbTFNfMjAxNiRDbGFzcz09IkZvcmVzdCIsXSwxMzAwKSANCg0KTFNfMjAxNiRDbGFzc1tMU18yMDE2JENsYXNzPT0iVXJiYW4iXTwtTkENCg0KTFNfMjAxNiRDbGFzc1tMU18yMDE2JENsYXNzPT0iRm9yZXN0Il08LU5BDQoNCkxTXzIwMTY8LW5hLm9taXQoTFNfMjAxNikNCg0KTFNfMjAxNjwtcmJpbmQoTFNfMjAxNixVcmJhbiwgRm9yZXN0KTsgdGFibGUoTFNfMjAxNiRDbGFzcykNCg0KTFNfMjAxNiRDbGFzc1tMU18yMDE2JENsYXNzPT0iRm9yZXN0Il08LSJOb25SaWNlIg0KTFNfMjAxNiRDbGFzc1tMU18yMDE2JENsYXNzPT0iVXJiYW4iXTwtIk5vblJpY2UiDQpMU18yMDE2JENsYXNzW0xTXzIwMTYkQ2xhc3M9PSJXYXRlciJdPC0iTm9uUmljZSINCnRhYmxlKExTXzIwMTYkQ2xhc3MpDQojIENyZWF0ZSBwb2ludCBzaGFwZWZpbGUNCg0KTFNfMjAxNjwtc3RfYXNfc2YoY2JpbmQuZGF0YS5mcmFtZShMU18yMDE2LGdlb2pzb25zZjo6Z2VvanNvbl9zZihMU18yMDE2JC5nZW8pKSkNCg0Kc3Rfd3JpdGUoTFNfMjAxNixkc249ICJUcmFpbmluZ18yMDE2Lmdwa2ciLGRyaXZlciA9ICJFU1JJIFNoYXBlZmlsZSIsIGFwcGVuZCA9IFQpDQoNCmBgYA0KIyMgMjAxNw0KDQpgYGB7cn0NCiMgMjAxNw0Kc2V0d2QoIkM6XFxVc2Vyc1xcREVMTFxcT25lRHJpdmUgLSB0dWFmLmVkdS52blxcU2Vhc29uYWxfUmljZV9Dcm9wbGFuZF9NYXBwaW5nX1BhcGVyXFxEYXRhXFxSaWNlX3RyYWluaW5nIikNCkxTXzIwMTc8LXJlYWQuY3N2KCJMU18yMDE3LmNzdiIsaGVhZGVyID0gVCkNCg0KTFNfMjAxNzwtbmEub21pdChMU18yMDE3KQ0KDQpMU18yMDE3JENsYXNzW0xTXzIwMTckQ2xhc3M9PSJDcm9wbGFuZCJdPC0iUmljZSINCg0KZ2dwbG90KExTXzIwMTcpK2dlb21fYm94cGxvdChhZXMoeD1DbGFzcyx5PU5EVkksZmlsbD1DbGFzcykpDQoNClVyYmFuPC1zYW1wbGVfbihMU18yMDE3W0xTXzIwMTckQ2xhc3M9PSJVcmJhbiIsXSwxMjAwKSAjIFNhbXBsZSAyMTAwIHBvaW50cyBmcm9tIHVyYmFuIGZlYXR1cmUgDQoNCkZvcmVzdDwtc2FtcGxlX24oTFNfMjAxN1tMU18yMDE3JENsYXNzPT0iRm9yZXN0IixdLDEyMDApIA0KDQpMU18yMDE3JENsYXNzW0xTXzIwMTckQ2xhc3M9PSJVcmJhbiJdPC1OQQ0KDQpMU18yMDE3JENsYXNzW0xTXzIwMTckQ2xhc3M9PSJGb3Jlc3QiXTwtTkENCg0KTFNfMjAxNzwtbmEub21pdChMU18yMDE3KQ0KDQpMU18yMDE3PC1yYmluZChMU18yMDE3LFVyYmFuLCBGb3Jlc3QpOyB0YWJsZShMU18yMDE3JENsYXNzKQ0KDQpMU18yMDE3JENsYXNzW0xTXzIwMTckQ2xhc3M9PSJGb3Jlc3QiXTwtIk5vblJpY2UiDQpMU18yMDE3JENsYXNzW0xTXzIwMTckQ2xhc3M9PSJVcmJhbiJdPC0iTm9uUmljZSINCkxTXzIwMTckQ2xhc3NbTFNfMjAxNyRDbGFzcz09IldhdGVyIl08LSJOb25SaWNlIg0KdGFibGUoTFNfMjAxNyRDbGFzcykNCg0KIyBDcmVhdGUgcG9pbnQgc2hhcGVmaWxlDQoNCkxTXzIwMTc8LXN0X2FzX3NmKGNiaW5kLmRhdGEuZnJhbWUoTFNfMjAxNyxnZW9qc29uc2Y6Omdlb2pzb25fc2YoTFNfMjAxNyQuZ2VvKSkpDQoNCnN0X3dyaXRlKExTXzIwMTcsZHNuPSAiVHJhaW5pbmdfMjAxNy5ncGtnIixkcml2ZXIgPSAiRVNSSSBTaGFwZWZpbGUiLCBhcHBlbmQgPSBUKQ0KYGBgDQojIyAyMDE4DQpgYGB7cn0NCiMgMjAxOA0Kc2V0d2QoIkM6XFxVc2Vyc1xcREVMTFxcT25lRHJpdmUgLSB0dWFmLmVkdS52blxcU2Vhc29uYWxfUmljZV9Dcm9wbGFuZF9NYXBwaW5nX1BhcGVyXFxEYXRhXFxSaWNlX3RyYWluaW5nIikNCkxTXzIwMTg8LXJlYWQuY3N2KCJMU18yMDE4LmNzdiIsaGVhZGVyID0gVCkNCg0KTFNfMjAxODwtbmEub21pdChMU18yMDE4KQ0KDQpMU18yMDE4JENsYXNzW0xTXzIwMTgkQ2xhc3M9PSJDcm9wbGFuZCJdPC0iUmljZSINCg0KZ2dwbG90KExTXzIwMTgpK2dlb21fYm94cGxvdChhZXMoeD1DbGFzcyx5PU5EVkksZmlsbD1DbGFzcykpDQoNClVyYmFuPC1zYW1wbGVfbihMU18yMDE4W0xTXzIwMTgkQ2xhc3M9PSJVcmJhbiIsXSwxMjAwKSAjIFNhbXBsZSAyMTAwIHBvaW50cyBmcm9tIHVyYmFuIGZlYXR1cmUgDQoNCkZvcmVzdDwtc2FtcGxlX24oTFNfMjAxOFtMU18yMDE4JENsYXNzPT0iRm9yZXN0IixdLDExMDApIA0KDQpMU18yMDE4JENsYXNzW0xTXzIwMTgkQ2xhc3M9PSJVcmJhbiJdPC1OQQ0KDQpMU18yMDE4JENsYXNzW0xTXzIwMTgkQ2xhc3M9PSJGb3Jlc3QiXTwtTkENCg0KTFNfMjAxODwtbmEub21pdChMU18yMDE4KQ0KDQpMU18yMDE4PC1yYmluZChMU18yMDE4LFVyYmFuLCBGb3Jlc3QpDQoNCkxTXzIwMTgkQ2xhc3NbTFNfMjAxOCRDbGFzcz09IkZvcmVzdCJdPC0iTm9uUmljZSINCkxTXzIwMTgkQ2xhc3NbTFNfMjAxOCRDbGFzcz09IlVyYmFuIl08LSJOb25SaWNlIg0KTFNfMjAxOCRDbGFzc1tMU18yMDE4JENsYXNzPT0iV2F0ZXIiXTwtIk5vblJpY2UiDQp0YWJsZShMU18yMDE4JENsYXNzKQ0KIyBDcmVhdGUgcG9pbnQgc2hhcGVmaWxlDQoNCkxTXzIwMTg8LXN0X2FzX3NmKGNiaW5kLmRhdGEuZnJhbWUoTFNfMjAxOCxnZW9qc29uc2Y6Omdlb2pzb25fc2YoTFNfMjAxOCQuZ2VvKSkpDQoNCnN0X3dyaXRlKExTXzIwMTgsZHNuPSAiVHJhaW5pbmdfMjAxOC5ncGtnIixkcml2ZXIgPSAiRVNSSSBTaGFwZWZpbGUiLCBhcHBlbmQgPSBUKQ0KYGBgDQojIyAyMDE5DQoNCmBgYHtyfQ0KIyAyMDE5DQpzZXR3ZCgiQzpcXFVzZXJzXFxERUxMXFxPbmVEcml2ZSAtIHR1YWYuZWR1LnZuXFxTZWFzb25hbF9SaWNlX0Nyb3BsYW5kX01hcHBpbmdfUGFwZXJcXERhdGFcXFJpY2VfdHJhaW5pbmciKQ0KTFNfMjAxOTwtcmVhZC5jc3YoIkxTXzIwMTkuY3N2IixoZWFkZXIgPSBUKQ0KDQpMU18yMDE5PC1uYS5vbWl0KExTXzIwMTkpDQoNCkxTXzIwMTkkQ2xhc3NbTFNfMjAxOSRDbGFzcz09IkNyb3BsYW5kIl08LSJSaWNlIg0KDQpnZ3Bsb3QoTFNfMjAxOSkrZ2VvbV9ib3hwbG90KGFlcyh4PUNsYXNzLHk9TkRWSSxmaWxsPUNsYXNzKSkNCg0KVXJiYW48LXNhbXBsZV9uKExTXzIwMTlbTFNfMjAxOSRDbGFzcz09IlVyYmFuIixdLDE0MDApICMgU2FtcGxlIDIxMDAgcG9pbnRzIGZyb20gdXJiYW4gZmVhdHVyZSANCg0KRm9yZXN0PC1zYW1wbGVfbihMU18yMDE5W0xTXzIwMTkkQ2xhc3M9PSJGb3Jlc3QiLF0sMTA5MykgDQoNCkxTXzIwMTkkQ2xhc3NbTFNfMjAxOSRDbGFzcz09IlVyYmFuIl08LU5BDQoNCkxTXzIwMTkkQ2xhc3NbTFNfMjAxOSRDbGFzcz09IkZvcmVzdCJdPC1OQQ0KDQpMU18yMDE5PC1uYS5vbWl0KExTXzIwMTkpDQoNCkxTXzIwMTk8LXJiaW5kKExTXzIwMTksVXJiYW4sIEZvcmVzdCk7IHRhYmxlKExTXzIwMTkkQ2xhc3MpDQoNCkxTXzIwMTkkQ2xhc3NbTFNfMjAxOSRDbGFzcz09IkZvcmVzdCJdPC0iTm9uUmljZSINCkxTXzIwMTkkQ2xhc3NbTFNfMjAxOSRDbGFzcz09IlVyYmFuIl08LSJOb25SaWNlIg0KTFNfMjAxOSRDbGFzc1tMU18yMDE5JENsYXNzPT0iV2F0ZXIiXTwtIk5vblJpY2UiDQp0YWJsZShMU18yMDE5JENsYXNzKQ0KIyBDcmVhdGUgcG9pbnQgc2hhcGVmaWxlDQoNCkxTXzIwMTk8LXN0X2FzX3NmKGNiaW5kLmRhdGEuZnJhbWUoTFNfMjAxOSxnZW9qc29uc2Y6Omdlb2pzb25fc2YoTFNfMjAxOSQuZ2VvKSkpDQoNCnN0X3dyaXRlKExTXzIwMTksZHNuPSAiVHJhaW5pbmdfMjAxOS5ncGtnIixkcml2ZXIgPSAiRVNSSSBTaGFwZWZpbGUiLCBhcHBlbmQgPSBUKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpgYGANCg0KIyBSYW5kb20gRm9yZXN0IHdpdGggYG1scmAgcGFja2FnZQ0KDQojIyMgMjAxMw0KDQpgYGB7cn0NCmxpYnJhcnkobWxyKTsgbGlicmFyeShyYW5kb21Gb3Jlc3QpOyBsaWJyYXJ5KHBhcmFsbGVsKTsgbGlicmFyeShwYXJhbGxlbE1hcCkNCiMgUHJlcHJvY2Vzc2luZyBkYXRhIA0KUkZfMjAxMzwtTFNfMjAxMyAlPiUgZGF0YS5mcmFtZSgpICU+JSBzZWxlY3QoMTo5LC02LDE2LDE3LDI0KQ0KDQpSRl8yMDEzJENsYXNzPC1hcy5mYWN0b3IoUkZfMjAxMyRDbGFzcykNCiMgQ3JlYXRlIGEgdGFzayBhbmQgbGVhcm5lciANCkxTX1Rhc2sgPC0gbWFrZUNsYXNzaWZUYXNrKGRhdGEgPSBSRl8yMDEzLCB0YXJnZXQgPSAiQ2xhc3MiKQ0KDQpMU19sZWFybmVyPC1tYWtlTGVhcm5lcigiY2xhc3NpZi5yYW5kb21Gb3Jlc3QiKQ0KDQojIFNlYXJjaCBmb3IgaHlwZXJwYXJhbWV0ZXIgdHVybmluZ3MgDQoNCmZvcmVzdFBhcmFtU3BhY2UgPC0gbWFrZVBhcmFtU2V0KA0KICBtYWtlSW50ZWdlclBhcmFtKCJudHJlZSIsIGxvd2VyID0gMTAwLCB1cHBlciA9IDUwMCksDQogIG1ha2VJbnRlZ2VyUGFyYW0oIm10cnkiLCBsb3dlciA9IDQsIHVwcGVyID0gMTIpLA0KICBtYWtlSW50ZWdlclBhcmFtKCJub2Rlc2l6ZSIsIGxvd2VyID0gMSwgdXBwZXIgPTEwKSwNCiAgbWFrZUludGVnZXJQYXJhbSgibWF4bm9kZXMiLCBsb3dlciA9IDUsIHVwcGVyID0gMzApKSAjIFNldCBhIHJhbmdlIG9mIHZhbHVlcyB0byBzZWFyY2ggZm9yLg0KDQpyYW5kU2VhcmNoIDwtIG1ha2VUdW5lQ29udHJvbFJhbmRvbShtYXhpdCA9IDEwMCkNCg0KY3ZGb3JUdW5pbmcgPC0gbWFrZVJlc2FtcGxlRGVzYygiQ1YiLCBpdGVycyA9IDUpDQoNCnBhcmFsbGVsU3RhcnRTb2NrZXQoY3B1cyA9IGRldGVjdENvcmVzKCkpDQoNCnR1bmVkRm9yZXN0UGFycyA8LSB0dW5lUGFyYW1zKExTX2xlYXJuZXIsIHRhc2sgPSBMU19UYXNrLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc2FtcGxpbmcgPSBjdkZvclR1bmluZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXIuc2V0ID0gZm9yZXN0UGFyYW1TcGFjZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9sID0gcmFuZFNlYXJjaCkNCnBhcmFsbGVsU3RvcCgpDQoNCnR1bmVkRm9yZXN0UGFycw0KIyBUcmFpbmcgdGhlIHJhbmRvbSBmb3Jlc3QNCnR1bmVkRm9yZXN0IDwtIHNldEh5cGVyUGFycyhMU19sZWFybmVyLCBwYXIudmFscyA9IHR1bmVkRm9yZXN0UGFycyR4KQ0KDQp0dW5lZEZvcmVzdE1vZGVsIDwtIHRyYWluKHR1bmVkRm9yZXN0LCBMU19UYXNrKQ0KDQojIENyb3NzLXZhbGlkYXRlIHRoZSBtb2RlbCB1c2luZyB0aGUgaHlwZXJwYXJhbWV0ZXIgdHVybmluZ3MgDQojIyBvdXRlciA8LSBtYWtlUmVzYW1wbGVEZXNjKCJDViIsIGl0ZXJzID0gNSkNCg0KIyMgZm9yZXN0V3JhcHBlciA8LSBtYWtlVHVuZVdyYXBwZXIoImNsYXNzaWYucmFuZG9tRm9yZXN0IiwgcmVzYW1wbGluZyA9IGN2Rm9yVHVuaW5nLCBwYXIuc2V0ID0gZm9yZXN0UGFyYW1TcGFjZSwgY29udHJvbCA9IHJhbmRTZWFyY2gpDQoNCiMjIHBhcmFsbGVsU3RhcnRTb2NrZXQoY3B1cyA9IGRldGVjdENvcmVzKCkpDQoNCiMjIGN2V2l0aFR1bmluZyA8LSByZXNhbXBsZShmb3Jlc3RXcmFwcGVyLCBMU19UYXNrLCByZXNhbXBsaW5nID0gb3V0ZXIpDQoNCiMjIHBhcmFsbGVsU3RvcCgpDQoNCiMjIGN2V2l0aFR1bmluZw0KIyBDcm9zcy12YWxpZGF0aW5nIHRoZSBtb2RlbCB3aXRob3V0IGh5cGVycGFyYW1ldGVyIHR1cm5pbmdzIA0KDQojIGtGb2xkIDwtIG1ha2VSZXNhbXBsZURlc2MobWV0aG9kID0gIlJlcENWIiwgZm9sZHMgPSAxMCwgcmVwcyA9IDUwLCBzdHJhdGlmeSA9IFRSVUUpDQoNCiMga0ZvbGRDViA8LSByZXNhbXBsZShsZWFybmVyID0gTFNfbGVhcm5lciwgdGFzayA9IExTX1Rhc2ssIHJlc2FtcGxpbmcgPSBrRm9sZCwgbWVhc3VyZXMgPSBsaXN0KG1tY2UsIGFjYykpDQojIENoZWNrIHRoZSBjb25mdXNpb24gbWF0cml4IA0KDQojIGNhbGN1bGF0ZUNvbmZ1c2lvbk1hdHJpeChrRm9sZENWJHByZWQsIHJlbGF0aXZlID0gVFJVRSkNCg0KDQpgYGANClR1bmUgcmVzdWx0Og0KT3AuIHBhcnM6IG50cmVlPTE4MjsgbXRyeT05OyBub2Rlc2l6ZT0yOyBtYXhub2Rlcz0zMA0KbW1jZS50ZXN0Lm1lYW49MC4xMTQ4OTc3DQoNCiMgVHJ5IA0KYGBge3J9DQojIFByZXByb2Nlc3NpbmcgZGF0YSANCmxpYnJhcnkoY2FUb29scykNCg0KUkZfMjAxMzwtTFNfMjAxOCAlPiUgZGF0YS5mcmFtZSgpICU+JSBzZWxlY3QoMTozMCwtNikNCg0KUkZfMjAxMyRDbGFzczwtYXMuZmFjdG9yKFJGXzIwMTMkQ2xhc3MpDQoNCiMgU3BsaXQgZGF0YSBpbiB0d28gc2V0cyANCnNwbGl0PC1zYW1wbGUuc3BsaXQoUkZfMjAxMyRDbGFzcywgU3BsaXRSYXRpbyA9IDAuOCkNCg0KIyB0cmFpbmluZyBzZXQNCnRyYWluaW5nX3NldDwtc3Vic2V0KFJGXzIwMTMsIHNwbGl0PT1UKQ0KDQp0ZXN0X3NldDwtc3Vic2V0KFJGXzIwMTMsIHNwbGl0PT1GKQ0KDQp0YWJsZSh0ZXN0X3NldCRDbGFzcykNCiMgQ3JlYXRlIGEgdGFzayBhbmQgbGVhcm5lciANClJGX1Rhc2sgPC0gbWFrZUNsYXNzaWZUYXNrKGRhdGEgPSB0cmFpbmluZ19zZXQsIHRhcmdldCA9ICJDbGFzcyIpDQoNClJGX2xlYXJuZXI8LW1ha2VMZWFybmVyKCJjbGFzc2lmLnJhbmRvbUZvcmVzdCIpDQoNClJGX3RyYWluPC10cmFpbihSRl9sZWFybmVyLFJGX1Rhc2spIA0KDQojIEhvbGRvdXQgY3Jvc3MtdmFsaWRhdGlvbg0Kc2V0LnNlZWQoMjM0KQ0KUkZfMTBfZm9sZHM8LW1ha2VSZXNhbXBsZURlc2MobWV0aG9kPSJSZXBDViIsIHN0cmF0aWZ5ID0gVCwgZm9sZHM9MTAsIHJlcHM9NSkNCg0KUkZfY3Jvc3NfdmFsaWRhdGlvbiA8LSByZXNhbXBsZShsZWFybmVyID0gUkZfbGVhcm5lciwgdGFzayA9IFJGX1Rhc2ssIHJlc2FtcGxpbmcgPSBSRl8xMF9mb2xkcywgbWVhc3VyZXMgPSBsaXN0KG1tY2UsIGFjYykpIA0KIyBDcm9zcy12YWxpZGF0aW9uIA0KDQpjYWxjdWxhdGVDb25mdXNpb25NYXRyaXgoUkZfY3Jvc3NfdmFsaWRhdGlvbiRwcmVkLHJlbGF0aXZlID0gVCkNCg0KIyBJbmRlcGVuZGVudCB0ZXN0aW5nDQp0ZXN0X3NldCRDbGFzczwtYXMuZmFjdG9yKHRlc3Rfc2V0JENsYXNzKQ0KDQpMU19wcmVkMTwtIHByZWRpY3QoUkZfdHJhaW4sIG5ld2RhdGE9dGVzdF9zZXRbLC0xXSx0eXBlPSJjbGFzcyIpDQoNCkxTPC1kYXRhLmZyYW1lKENsYXNzPUxTX3ByZWQxJGRhdGEpDQoNCm5hbWVzKExTKTwtIkNsYXNzIg0KDQpsaWJyYXJ5KGUxMDcxKQ0KbGlicmFyeShjYXJldCkNCnRlc3Rfc2V0JENsYXNzPC1hcy5mYWN0b3IodGVzdF9zZXQkQ2xhc3MpDQoNCmNtMjwtY29uZnVzaW9uTWF0cml4KExTJENsYXNzLHRlc3Rfc2V0JENsYXNzKQ0KY20yDQpsaWJyYXJ5KHJhc3RlcikNCg0KbGlicmFyeShzcCkNCg0KbGlicmFyeShyZ2VvcykNCg0KbGlicmFyeShyZ2RhbCkNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCg0KIyBDYWxjdWxhdGUgdGhlIGFyZWFzIA0KIyMgMjAxMw0KDQpgYGB7cn0NCmxpYnJhcnkocmFzdGVyKQ0KIyBwcm92aW5jZXMgc2hhcGVmaWxlcw0KDQpmb2xkZXI8LSJDOlxcVXNlcnNcXERFTExcXE9uZURyaXZlIC0gdHVhZi5lZHUudm5cXFNlYXNvbmFsX1JpY2VfQ3JvcGxhbmRfTWFwcGluZ19QYXBlclxcRGF0YVxcQXJlYXMiDQoNCnByb3ZpbmNlczwtc3RfcmVhZChkc249Zm9sZGVyLGxheWVyID0gIlJlZF9SaXZlcl9EZWx0YSIpDQoNCnBybzwtc3RfdHJhbnNmb3JtKHByb3ZpbmNlcyxjcnMgPSAzMjY0OCkNCg0KIyBSZWFkIHJhc3RlciANCkxTPC1yYXN0ZXIoIkM6XFxVc2Vyc1xcREVMTFxcT25lRHJpdmUgLSB0dWFmLmVkdS52blxcU2Vhc29uYWxfUmljZV9Dcm9wbGFuZF9NYXBwaW5nX1BhcGVyXFxEYXRhXFxMYW5kc2F0XFxDbGFzc2lmaWVkX05vbWFza2VkXFxtYXAyMDE5LnRpZiIpDQoNCk5BdmFsdWUoTFMpPC0wDQoNCm5hbWVzKExTKTwtIkNsYXNzaWZpZWRfMjAxMyINCiMgQ2xpcCByYXN0ZXIgYnkgcHByb3ZpbmNlcw0KDQpteWFyZWE8LWZ1bmN0aW9uKGltYWdlLHZlY3RvcnMpew0KICBmb3IgKGkgaW4gMTpucm93KHZlY3RvcnMpKXsNCiAgICBpZiAodmVjdG9ycyRWQVJOQU1FXzFbaV09PSJCYWMgTmluaCIpew0KICAgICAgQk48LXZlY3RvcnNbaSwyXQ0KICAgICAgQk5fY3JvcDwtY3JvcChpbWFnZSxCTixzbmFwPSJvdXQiKQ0KICAgICAgQk5fbWFzazwtbWFzayhCTl9jcm9wLEJOKQ0KICAgICAgZGYxPC1hcy5kYXRhLmZyYW1lKEJOX21hc2spICU+JSAgIGdyb3VwX2J5KENsYXNzaWZpZWRfMjAxMykgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShhcmVhID0gbiAqIHJlcyhCTl9tYXNrKVsxXSAqIHJlcyhCTl9tYXNrKVsyXS8xMDAwMCkNCiAgICAgIGRmMTwtZGYxWzEsXQ0KICAgICAgZGYxJFByb3ZpbmNlPC0iQmFjIE5pbmgiDQogICAgICBwcmludChkZjEpDQoNCiAgICB9IGVsc2UgaWYodmVjdG9ycyRWQVJOQU1FXzFbaV09PSJIYSBOYW0iKXsNCiAgICAgIEhhbmFtPC12ZWN0b3JzW2ksMl0NCiAgICAgIEhhbmFtX2Nyb3A8LWNyb3AoaW1hZ2UsSGFuYW0sc25hcD0ib3V0IikNCiAgICAgIEhhbmFtX21hc2s8LW1hc2soSGFuYW1fY3JvcCxIYW5hbSkNCiAgICAgIGRmMjwtYXMuZGF0YS5mcmFtZShIYW5hbV9tYXNrKSAlPiUgZ3JvdXBfYnkoQ2xhc3NpZmllZF8yMDEzKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKGFyZWEgPSBuICogcmVzKEhhbmFtX21hc2spWzFdICogcmVzKEhhbmFtX21hc2spWzJdLzEwMDAwKQ0KICAgICAgZGYyPC1kZjJbMSxdDQogICAgICBkZjIkUHJvdmluY2U8LSJIYSBOYW0iDQogICAgICBwcmludChkZjIpDQogICAgfWVsc2UgaWYodmVjdG9ycyRWQVJOQU1FXzFbaV09PSJIYSBOb2kiKXsNCiAgICAgIEhOPC12ZWN0b3JzW2ksMl0NCiAgICAgIEhOX2Nyb3A8LWNyb3AoaW1hZ2UsSE4sc25hcD0ib3V0IikNCiAgICAgIEhOX21hc2s8LW1hc2soSE5fY3JvcCxITikNCiAgICAgIGRmMzwtYXMuZGF0YS5mcmFtZShITl9tYXNrKSAlPiUgZ3JvdXBfYnkoQ2xhc3NpZmllZF8yMDEzKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKGFyZWEgPSBuICogcmVzKEhOX21hc2spWzFdICogcmVzKEhOX21hc2spWzJdLzEwMDAwKQ0KICAgICAgZGYzPC1kZjNbMSxdDQogICAgICBkZjMkUHJvdmluY2U8LSJIYSBOb2kiDQogICAgICBwcmludChkZjMpDQogICAgfWVsc2UgaWYodmVjdG9ycyRWQVJOQU1FXzFbaV09PSJIYWkgRHVvbmciKXsNCiAgICAgIEhEPC12ZWN0b3JzW2ksMl0NCiAgICAgIEhEX2Nyb3A8LWNyb3AoaW1hZ2UsSEQsc25hcD0ib3V0IikNCiAgICAgIEhEX21hc2s8LW1hc2soSERfY3JvcCxIRCkNCiAgICAgIGRmNDwtYXMuZGF0YS5mcmFtZShIRF9tYXNrKSAlPiUgZ3JvdXBfYnkoQ2xhc3NpZmllZF8yMDEzKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKGFyZWEgPSBuICogcmVzKEhEX21hc2spWzFdICogcmVzKEhEX21hc2spWzJdLzEwMDAwKQ0KICAgICAgZGY0PC1kZjRbMSxdDQogICAgICBkZjQkUHJvdmluY2U8LSJIYWkgRHVvbmciDQogICAgICBwcmludChkZjQpDQogICAgfSBlbHNlIGlmICh2ZWN0b3JzJFZBUk5BTUVfMVtpXT09IkhhaSBQaG9uZyIpew0KICAgICAgSFA8LXZlY3RvcnNbaSwyXQ0KICAgICAgSFBfY3JvcDwtY3JvcChpbWFnZSxIUCxzbmFwPSJvdXQiKQ0KICAgICAgSFBfbWFzazwtbWFzayhIUF9jcm9wLEhQKQ0KICAgICAgZGY1PC1hcy5kYXRhLmZyYW1lKEhQX21hc2spICU+JSBncm91cF9ieShDbGFzc2lmaWVkXzIwMTMpICU+JSB0YWxseSgpICU+JSBtdXRhdGUoYXJlYSA9IG4gKiByZXMoSFBfbWFzaylbMV0gKiByZXMoSFBfbWFzaylbMl0vMTAwMDApDQogICAgICBkZjU8LWRmNVsxLF0NCiAgICAgIGRmNSRQcm92aW5jZTwtIkhhaSBQaG9uZyINCiAgICAgIHByaW50KGRmNSkNCiAgICB9ZWxzZSBpZih2ZWN0b3JzJFZBUk5BTUVfMVtpXT09Ikh1bmcgWWVuIil7DQogICAgICBIWTwtdmVjdG9yc1tpLDJdDQogICAgICBIWV9jcm9wPC1jcm9wKGltYWdlLEhZLHNuYXA9Im91dCIpDQogICAgICBIWV9tYXNrPC1tYXNrKEhZX2Nyb3AsSFkpDQogICAgICBkZjY8LWFzLmRhdGEuZnJhbWUoSFlfbWFzaykgJT4lIGdyb3VwX2J5KENsYXNzaWZpZWRfMjAxMykgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShhcmVhID0gbiAqIHJlcyhIWV9tYXNrKVsxXSAqIHJlcyhIWV9tYXNrKVsyXS8xMDAwMCkNCiAgICAgIGRmNjwtZGY2WzEsXQ0KICAgICAgZGY2JFByb3ZpbmNlPC0iSHVuZyBZZW4iDQogICAgICBwcmludChkZjYpDQogICAgfWVsc2UgaWYodmVjdG9ycyRWQVJOQU1FXzFbaV09PSJOYW0gRGluaCIpew0KICAgICBORDwtdmVjdG9yc1tpLDJdDQogICAgIE5EX2Nyb3A8LWNyb3AoaW1hZ2UsTkQsc25hcD0ib3V0IikNCiAgICAgTkRfbWFzazwtbWFzayhORF9jcm9wLE5EKQ0KICAgICBkZjc8LWFzLmRhdGEuZnJhbWUoTkRfbWFzaykgJT4lIGdyb3VwX2J5KENsYXNzaWZpZWRfMjAxMykgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShhcmVhID0gbiAqIHJlcyhORF9tYXNrKVsxXSAqIHJlcyhORF9tYXNrKVsyXS8xMDAwMCkgDQogICAgIGRmNzwtZGY3WzEsXQ0KICAgICBkZjckUHJvdmluY2U8LSJOYW0gRGluaCINCiAgICAgcHJpbnQoZGY3KQ0KICAgIH1lbHNlIGlmKHZlY3RvcnMkVkFSTkFNRV8xW2ldPT0iTmluaCBCaW5oIil7DQogICAgIE5CPC12ZWN0b3JzW2ksMl0NCiAgICAgTkJfY3JvcDwtY3JvcChpbWFnZSxOQixzbmFwPSJvdXQiKQ0KICAgICBOQl9tYXNrPC1tYXNrKE5CX2Nyb3AsTkIpDQogICAgIGRmODwtYXMuZGF0YS5mcmFtZShOQl9tYXNrKSAlPiUgZ3JvdXBfYnkoQ2xhc3NpZmllZF8yMDEzKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKGFyZWEgPSBuICogcmVzKE5CX21hc2spWzFdICogcmVzKE5CX21hc2spWzJdLzEwMDAwKSANCiAgICAgZGY4PC1kZjhbMSxdDQogICAgIGRmOCRQcm92aW5jZTwtIk5pbmggQmluaCINCiAgICAgcHJpbnQoZGY4KQ0KICAgIH1lbHNlIGlmKHZlY3RvcnMkVkFSTkFNRV8xW2ldPT0iVGhhaSBCaW5oIil7DQogICAgIFRCPC12ZWN0b3JzW2ksMl0NCiAgICAgVEJfY3JvcDwtY3JvcChpbWFnZSxUQixzbmFwPSJvdXQiKQ0KICAgICBUQl9tYXNrPC1tYXNrKFRCX2Nyb3AsVEIpDQogICAgIGRmOTwtYXMuZGF0YS5mcmFtZShUQl9tYXNrKSAlPiUgZ3JvdXBfYnkoQ2xhc3NpZmllZF8yMDEzKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKGFyZWEgPSBuICogcmVzKFRCX21hc2spWzFdICogcmVzKFRCX21hc2spWzJdLzEwMDAwKSANCiAgICAgZGY5PC1kZjlbMSxdDQogICAgIGRmOSRQcm92aW5jZTwtIlRoYWkgQmluaCINCiAgICAgcHJpbnQoZGY5KQ0KICAgIH0gIGVsc2V7DQogICAgICBWUDwtdmVjdG9yc1tpLDJdDQogICAgICBWUF9jcm9wPC1jcm9wKGltYWdlLFZQLHNuYXA9Im91dCIpDQogICAgICBWUF9tYXNrPC1tYXNrKFZQX2Nyb3AsVlApDQogICAgICBkZjEwPC1hcy5kYXRhLmZyYW1lKFZQX21hc2spICU+JSBncm91cF9ieShDbGFzc2lmaWVkXzIwMTMpICU+JSB0YWxseSgpICU+JSBtdXRhdGUoYXJlYSA9IG4gKiByZXMoVlBfbWFzaylbMV0gKiByZXMoVlBfbWFzaylbMl0vMTAwMDApIA0KICAgICAgZGYxMDwtZGYxMFsxLF0NCiAgICAgIGRmMTAkUHJvdmluY2U8LSJWaW5oIFBodWMiDQogICAgICBwcmludChkZjEwKQ0KICAgIH19fSANCg0KZGY8LW15YXJlYShMUyxwcm8pDQoNCmBgYA0KDQoNCg==