Required packages

# Loading Necessary packages
library(dplyr)
library(lubridate)
library(tidyr)
library(Hmisc)
library(editrules)
library(deducorrect)
library(magrittr)
library(outliers)
library(forecast)
library(mlr)
library(MVN)

Executive Summary

Data Pre-processing refers to the the process of transforming raw/ unprocessed data into a more refined and structured format before using it for any analysis. In this assignment we will be showcasing the complete process of data cleaning process on superhero dataset from Kraggle which will include all essential steps such as merging two datasets, removing unrequired observations and variables, tidy dataset according to tidy principles, checking for missing values checking for outliners and appling other processes as required in the assignment.

Data

Assignment is based on Super Heroes Dataset from Kaggle. Dataset includes information of super heros and villain of various publisher comic house such as DC, Marvel and many others. Dataset comprise of two datasets one containing personal information of each character and other having ability/ superpower information of each character.

The two datasets contains the following variables :

heroes_information.csv:

name[Character] : Name of Hero/ Villain

Gender[Factor] : Male or Female

Eyecolor[Factor]: Color of Eye

Race [Factor] : Species the Hero/ villain belongs to

HairColor[Factor]: Color of Hair

Height [numeric]: Height Measurement in cm

Publisher[Factor]: Comic Publisher House

Skincolor[Factor]: Colour of Skin

Alignment[Factor]: Deposition of the person

Weight[numeric] : Weight of person in pound

super_hero_powers.csv :

hero_name[character]: Name of Hero/ Villain

column 2 - 168 (Agility - omniscient)[logical] - Superpower exist for the character

Importing datasets

  • Datasets used in this assignment are csv files so data is imported using read.csv() method which reads the csv file from the directory.
  • While loading data “stringsAsFactors” set to false from its default value, this will store string variables as character vectors rather than factors.
Hero_info <- read.csv('heroes_information.csv', stringsAsFactors = FALSE ) 
Hero_powers<- read.csv('super_hero_powers.csv', stringsAsFactors = FALSE)

Datasets Display

  • We have used dim() method for getting the dimentions of imported datasets. and head() method to display few rows of dataset.
dim(Hero_info)
[1] 734  11
head(Hero_info[,1:6],3)
head(Hero_info[,6:11],3)

dim(Hero_powers)
[1] 667 168
head(Hero_powers[,1:6],3)
head(Hero_powers[,163:168],3)
  • Hero_info Dataset comprise of 734 observations and 11 Variables.

  • Hero_power Dataset comprise of 667 observations and 168 Variables.

Merging two Datasets

  • We will be joining the two sets via inner_join() method over “name”, so that only those obserervations are taken which has common characters.
hero_stats <- inner_join(Hero_info,Hero_powers, c("name"= "hero_names"))
  • After Joining two Datasets to hero_stats, we will remove the variable X as it doesn’t provide any useful information.
# Removing unrequired S No. Column
hero_stats <- hero_stats[,-1]
  • New Dataset hero_stats display
  • We have used dim() method for getting the dimentions of join datasets. and head() method to display few rows of dataset.
# Dimention of hero_stats
dim(hero_stats)
[1] 660 177
head(hero_stats[1:8],3)
head(hero_stats[9:15],3)
head(hero_stats[173:177],3)
  • Dataset hero_stats comprise of 660 observations and 177 variables

Understand


# introducting NA's for special characters before Data Type Conversion 

hero_stats[hero_stats == '-']<- NA
# Proper Data Type COnversion

## converting from character to logical format
hero_stats[,11:177] <- sapply(hero_stats[,11:177], as.logical)
## convertion from character to factor
hero_stats$Gender <- factor(hero_stats$Gender)
hero_stats$Eye.color <- factor(hero_stats$Eye.color)
hero_stats$Race <- factor(hero_stats$Race)
hero_stats$Hair.color <- factor(hero_stats$Hair.color)
hero_stats$Publisher <- factor(hero_stats$Publisher)
hero_stats$Skin.color <- factor(hero_stats$Skin.color)
hero_stats$Alignment <- factor(hero_stats$Alignment)
# displaying structure of data set
str(hero_stats) %>% head(20)
'data.frame':   660 obs. of  177 variables:
 $ name                        : chr  "A-Bomb" "Abe Sapien" "Abin Sur" "Abomination" ...
 $ Gender                      : Factor w/ 2 levels "Female","Male": 2 2 2 2 2 2 2 2 2 2 ...
 $ Eye.color                   : Factor w/ 22 levels "amber","black",..: 19 3 3 8 3 3 3 3 6 NA ...
 $ Race                        : Factor w/ 60 levels "Alien","Alpha",..: 23 32 54 31 11 23 NA 23 23 NA ...
 $ Hair.color                  : Factor w/ 29 levels "Auburn","black",..: 17 17 17 17 3 17 6 6 8 NA ...
 $ Height                      : num  203 191 185 203 -99 193 -99 185 178 191 ...
 $ Publisher                   : Factor w/ 25 levels "","ABC Studios",..: 13 3 4 13 13 13 15 4 13 13 ...
 $ Skin.color                  : Factor w/ 16 levels "black","blue",..: NA 2 12 NA NA NA NA NA NA NA ...
 $ Alignment                   : Factor w/ 3 levels "bad","good","neutral": 2 2 2 1 1 1 2 2 2 2 ...
 $ Weight                      : num  441 65 90 441 -99 122 -99 88 81 104 ...
 $ Agility                     : logi  FALSE TRUE FALSE FALSE FALSE FALSE ...
 $ Accelerated.Healing         : logi  TRUE TRUE FALSE TRUE FALSE FALSE ...
 $ Lantern.Power.Ring          : logi  FALSE FALSE TRUE FALSE FALSE FALSE ...
 $ Dimensional.Awareness       : logi  FALSE FALSE FALSE FALSE TRUE FALSE ...
 $ Cold.Resistance             : logi  FALSE TRUE FALSE FALSE FALSE TRUE ...
 $ Durability                  : logi  TRUE TRUE FALSE FALSE FALSE TRUE ...
 $ Stealth                     : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Energy.Absorption           : logi  FALSE FALSE FALSE FALSE FALSE TRUE ...
 $ Flight                      : logi  FALSE FALSE FALSE FALSE TRUE FALSE ...
 $ Danger.Sense                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Underwater.breathing        : logi  FALSE TRUE FALSE FALSE FALSE FALSE ...
 $ Marksmanship                : logi  FALSE TRUE FALSE FALSE FALSE FALSE ...
 $ Weapons.Master              : logi  FALSE TRUE FALSE FALSE FALSE FALSE ...
 $ Power.Augmentation          : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Animal.Attributes           : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Longevity                   : logi  TRUE TRUE FALSE FALSE FALSE FALSE ...
 $ Intelligence                : logi  FALSE TRUE FALSE TRUE TRUE FALSE ...
 $ Super.Strength              : logi  TRUE TRUE FALSE TRUE TRUE TRUE ...
 $ Cryokinesis                 : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Telepathy                   : logi  FALSE TRUE FALSE FALSE FALSE FALSE ...
 $ Energy.Armor                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Energy.Blasts               : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Duplication                 : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Size.Changing               : logi  FALSE FALSE FALSE FALSE TRUE FALSE ...
 $ Density.Control             : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Stamina                     : logi  TRUE TRUE FALSE TRUE FALSE FALSE ...
 $ Astral.Travel               : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Audio.Control               : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Dexterity                   : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Omnitrix                    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Super.Speed                 : logi  FALSE FALSE FALSE TRUE TRUE FALSE ...
 $ Possession                  : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Animal.Oriented.Powers      : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Weapon.based.Powers         : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Electrokinesis              : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Darkforce.Manipulation      : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Death.Touch                 : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Teleportation               : logi  FALSE FALSE FALSE FALSE TRUE FALSE ...
 $ Enhanced.Senses             : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Telekinesis                 : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Energy.Beams                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Magic                       : logi  FALSE FALSE FALSE FALSE TRUE FALSE ...
 $ Hyperkinesis                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Jump                        : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Clairvoyance                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Dimensional.Travel          : logi  FALSE FALSE FALSE FALSE TRUE FALSE ...
 $ Power.Sense                 : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Shapeshifting               : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Peak.Human.Condition        : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Immortality                 : logi  FALSE TRUE FALSE FALSE TRUE FALSE ...
 $ Camouflage                  : logi  TRUE FALSE FALSE FALSE FALSE FALSE ...
 $ Element.Control             : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Phasing                     : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Astral.Projection           : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Electrical.Transport        : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Fire.Control                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Projection                  : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Summoning                   : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Enhanced.Memory             : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Reflexes                    : logi  FALSE TRUE FALSE FALSE FALSE FALSE ...
 $ Invulnerability             : logi  FALSE FALSE FALSE TRUE TRUE TRUE ...
 $ Energy.Constructs           : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Force.Fields                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Self.Sustenance             : logi  TRUE FALSE FALSE FALSE FALSE FALSE ...
 $ Anti.Gravity                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Empathy                     : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Power.Nullifier             : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Radiation.Control           : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Psionic.Powers              : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Elasticity                  : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Substance.Secretion         : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Elemental.Transmogrification: logi  FALSE FALSE FALSE FALSE FALSE TRUE ...
 $ Technopath.Cyberpath        : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Photographic.Reflexes       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Seismic.Power               : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Animation                   : logi  FALSE FALSE FALSE TRUE FALSE FALSE ...
 $ Precognition                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Mind.Control                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Fire.Resistance             : logi  FALSE FALSE FALSE FALSE FALSE TRUE ...
 $ Power.Absorption            : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Enhanced.Hearing            : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Nova.Force                  : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Insanity                    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Hypnokinesis                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Animal.Control              : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Natural.Armor               : logi  FALSE FALSE FALSE FALSE FALSE TRUE ...
 $ Intangibility               : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ Enhanced.Sight              : logi  FALSE TRUE FALSE FALSE FALSE FALSE ...
 $ Molecular.Manipulation      : logi  FALSE FALSE FALSE FALSE TRUE TRUE ...
  [list output truncated]
NULL

Scan I

Scan the data for missing values, special values and obvious errors (i.e. inconsistencies).

Before tidy up the dataset it would be better to deal with missing value and obvious errors we will doing so by first identifying special values and missing values.

# Check for missing values for every variables
hero_stats %>% is.na() %>% colSums() %>% head(30)
                 name                Gender             Eye.color                  Race            Hair.color                Height 
                    0                    18                   131                   247                   132                     0 
            Publisher            Skin.color             Alignment                Weight               Agility   Accelerated.Healing 
                    0                   588                     7                     2                     0                     0 
   Lantern.Power.Ring Dimensional.Awareness       Cold.Resistance            Durability               Stealth     Energy.Absorption 
                    0                     0                     0                     0                     0                     0 
               Flight          Danger.Sense  Underwater.breathing          Marksmanship        Weapons.Master    Power.Augmentation 
                    0                     0                     0                     0                     0                     0 
    Animal.Attributes             Longevity          Intelligence        Super.Strength           Cryokinesis             Telepathy 
                    0                     0                     0                     0                     0                     0 
# Check for Special Values

##checking for NaN values
sapply(hero_stats, function(x) sum(is.nan(x))) %>% sum()
[1] 0
##checking for Infite values
sapply(hero_stats, function(x) sum( is.infinite(x))) %>% sum()
[1] 0

Dealing with missing Values

  • We can Observe that all of the missing values are present in 7 variables, where 6 of them are catagorical and 1 is numerical. We will deal with each variable is the most suitable way possible.

  • For Gender, Eye Color, Hair Color, Skin color we will replace the missing value with the most occuring value.

  • For variable Race since many of these characters are from space, the characters with missing race can be considered to be Alien.

  • Since Alignment of a perticular character can be good, bad or neutral, we will consider that the individual with missing alignment are neutral .

  • for two missing weight observations we will replace them with the mean weight for all the observation


# For Gender we will observe using table function that about 72 percent of character are Male.
hero_stats$Gender %>% table() %>% prop.table()
.
   Female      Male 
0.2772586 0.7227414 
  # We are safely assuming that 13 missing gender characters are male as well
  hero_stats$Gender[is.na(hero_stats$Gender)]<- 'Male'

# For Eye Color
hero_stats$Eye.color <- Hmisc::impute(hero_stats$Eye.color, fun = mode)

# For Hair Color
hero_stats$Hair.color <- Hmisc::impute(hero_stats$Hair.color, fun = mode)

# for skin Color
hero_stats$Skin.color <- Hmisc::impute(hero_stats$Skin.color, fun = mode)

# For Race
hero_stats$Race[is.na(hero_stats$Race)] <- 'Alien'

# For Alignment
hero_stats$Alignment[is.na(hero_stats$Alignment)] <- 'neutral'

# For Weight

hero_stats$Weight <- Hmisc::impute(hero_stats$Weight, fun = mean)


## check for Na's

hero_stats %>% is.na() %>% sum()
[1] 0
 

Check for obvious inconsistencies

For checking these common inconsistencies in data set we will be using edithrules and deducorrect packages.

  • Rules for inconsistency are:
  1. Height > 0 If Height < 0 multiply it by -1

  2. Weight > 0 If Weight < 0 multiply it by -1

Check for Violation of rules:

Rule1 <- editset(c("Weight > 0", "Height > 0"))
Rule1

Edit set:
num1 : 0 < Weight
num2 : 0 < Height 
violations <- violatedEdits(Rule1,hero_stats)
violations %>% summary()
Edit violations, 660 observations, 0 completely missing (0%):

Edit violations per record:
  • We can observe presence of multiple instances of such violations. We can correct these instances using deducorrect module

Setting Rules

# Loading Rules from File
Rules <- correctionRules('Rules.txt')

Rules
Object of class 'correctionRules'
##  1-------
  if (Height < 0) {
      Height <- Height * -1
  } 
##  2-------
  if (Weight < 0) {
      Weight <- Weight * -1
  } 

Corecting with Rules

cor <- correctWithRules(Rules, hero_stats)

hero_stats <- cor$corrected

Re-check for violations

violations <- violatedEdits(Rule1,hero_stats)
violations %>% summary()
No violations detected, 0 checks evaluated to NA
NULL

Tidy & Manipulate Data I

hero_stats <- hero_stats %>% gather(11:177,key = 'Ability Type', value = 'Exist')
# Dimention of re-organised Dataset
dim(hero_stats)
[1] 110220     12
head(hero_stats[,1:8])
head(hero_stats[,8:12])

Tidy & Manipulate Data II

Create/mutate at least one variable from the existing variables (minimum requirement #6). In addition to the R codes and outputs, explain everything that you do in this step.

# Height in meters
hero_stats$Height <- hero_stats$Height * 0.01
# Weight in Kg
hero_stats$Weight <- hero_stats$Weight * 0.453592
# Adding variable BMI
hero_stats <- hero_stats %>% mutate(BMI = Weight/(Height*Height))

range(hero_stats$BMI)
[1] 3.692235e-02 1.138692e+03

Scan II


# Checking for normality
par(mfrow=c(1,2))

hero_stats$Height %>% hist(main='Height distribution', xlab = 'Height in meters')
hero_stats$Weight %>% hist(main='Weight distribution', xlab = 'Weight in Kg')


# Outliners Detection using Tukey's method 
par(mfrow=c(1,2))
hero_stats$Height %>% boxplot(main =' Boxplot for Height')

hero_stats$Weight %>% as.numeric() %>% boxplot(main =' Boxplot for Weight')

# Defining function for capping outliners to nearest quantile
cap <- function(x){
quantiles <- quantile( x, c(.05, 0.25, 0.75, .95 ) )
x[ x < quantiles[2] - 1.5*IQR(x) ] <- quantiles[1]
x[ x > quantiles[3] + 1.5*IQR(x) ] <- quantiles[4]
x
}
hero_stats$Height <- hero_stats$Height %>%  cap() 
hero_stats$Weight <- hero_stats$Weight %>% cap()
hero_stats$Height %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.152   0.990   1.780   1.611   1.880   3.050 
hero_stats$Weight %>% summary()

 2 values imputed to 23.69915 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  16.33   32.66   44.91   49.03   44.91  122.47 
hero_stats$BMI <- hero_stats$Weight/(hero_stats$Height^2)
range(hero_stats$BMI)
[1]    4.620109 1138.691828
# check for outlinears in BMI

par(mfrow=c(1,2))

hero_stats$BMI %>% as.numeric() %>% boxplot(main ='BMI Boxplot with outliners', ylab='Body Mass Index')
hero_stats$BMI %>% as.numeric() %>% boxplot(main ='BMI Boxplot without outliners', ylab='Body Mass Index',outline = FALSE)

Since there still exist some outlinears in BMI lets explore these cases.

# check cases for cases with high BMI
hero_stats[hero_stats$BMI >100,]$name %>% unique()
[1] "Anti-Monitor" "Bloodwraith"  "Giganta"      "King Kong"    "T-800"        "T-850"        "T-X"          "Utgard-Loki" 
hero_stats[hero_stats$BMI >100,]$Race %>% unique()
[1] God / Eternal Alien         Animal        Cyborg        Frost Giant  
60 Levels: Alien Alpha Amazon Android Animal Asgardian Atlantean Bizarro Bolovaxian Clone Cosmic Entity Cyborg ... Zombie

Transform

plot(hero_stats$Height, hero_stats$Weight)

par(mfrow=c(1,2))
height <- BoxCox(hero_stats$Height, lambda = 'auto')
height %>% hist(main='Transformed Height Data')

weight <- BoxCox(hero_stats$Weight, lambda = 'auto')
weight %>% hist(main='Transformed Weight Data')


plot(height,weight)

Extra

# Filtering observation 
hero_stats<- hero_stats %>% filter(Exist == TRUE)
# Removing variable Exist
hero_stats <- hero_stats[-12]
# dimention of Dataset
dim(hero_stats)
[1] 5966   12
# dataset head 
head(hero_stats[,1:8])
head(hero_stats[,8:12])

Getting Super Power Visualisation

  • We can further determine most common abilities among super heros and super villain.
# Barplot for superpower count
Power_stats<- hero_stats %>% group_by(`Ability Type`) %>% summarise( PowerUsers = n()) %>% arrange(desc(PowerUsers) )
barplot(Power_stats$PowerUsers[1:5], names.arg = Power_stats$`Ability Type`[1:5], main = '5 Most Common Ability')

LS0tDQp0aXRsZTogIk1BVEgyMzQ5IEFzc2lnbm1lbnQgMiAsU2VtZXN0ZXIgMSwgMjAyMCINCmF1dGhvcjogVmFzdSBHdXB0YSBTMzg1MzYxNTxici8+DQpzdWJ0aXRsZTogQXNzaWdubWVudCAyDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCg0KDQojIyBSZXF1aXJlZCBwYWNrYWdlcw0KYGBge3IsY29sbGFwc2U9VFJVRX0NCiMgTG9hZGluZyBOZWNlc3NhcnkgcGFja2FnZXMNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KEhtaXNjKQ0KbGlicmFyeShlZGl0cnVsZXMpDQpsaWJyYXJ5KGRlZHVjb3JyZWN0KQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkob3V0bGllcnMpDQpsaWJyYXJ5KGZvcmVjYXN0KQ0KbGlicmFyeShtbHIpDQpsaWJyYXJ5KE1WTikNCmBgYA0KDQoNCiMjIEV4ZWN1dGl2ZSBTdW1tYXJ5DQoNCkRhdGEgUHJlLXByb2Nlc3NpbmcgcmVmZXJzIHRvIHRoZSB0aGUgcHJvY2VzcyBvZiB0cmFuc2Zvcm1pbmcgcmF3LyB1bnByb2Nlc3NlZCBkYXRhIGludG8gYSBtb3JlIHJlZmluZWQgYW5kIHN0cnVjdHVyZWQgZm9ybWF0IGJlZm9yZSB1c2luZyBpdCBmb3IgYW55IGFuYWx5c2lzLiBJbiB0aGlzIGFzc2lnbm1lbnQgd2Ugd2lsbCBiZSBzaG93Y2FzaW5nIHRoZSBjb21wbGV0ZSBwcm9jZXNzIG9mIGRhdGEgY2xlYW5pbmcgcHJvY2VzcyBvbiBzdXBlcmhlcm8gZGF0YXNldCBmcm9tIEtyYWdnbGUgd2hpY2ggd2lsbCBpbmNsdWRlIGFsbCBlc3NlbnRpYWwgc3RlcHMgc3VjaCBhcyBtZXJnaW5nIHR3byBkYXRhc2V0cywgcmVtb3ZpbmcgdW5yZXF1aXJlZCBvYnNlcnZhdGlvbnMgYW5kIHZhcmlhYmxlcywgdGlkeSBkYXRhc2V0IGFjY29yZGluZyB0byB0aWR5IHByaW5jaXBsZXMsIGNoZWNraW5nIGZvciBtaXNzaW5nIHZhbHVlcyBjaGVja2luZyBmb3Igb3V0bGluZXJzIGFuZCBhcHBsaW5nIG90aGVyIHByb2Nlc3NlcyBhcyByZXF1aXJlZCBpbiB0aGUgYXNzaWdubWVudC4NCg0KKiBXZSBtZXJnZWQgdGhlIHRoZSBkYXRhc2V0ICoqaGVyb2VzX2luZm9ybWF0aW9uLmNzdioqIGFuZCAqKnN1cGVyX2hlcm9fcG93ZXJzLmNzdioqIG9uIGNvbW1vbiB2YXJpYWJsZSAqTmFtZSogYW5kIHJlbW92ZSB1bnJlcXVpcmVkIHZhcmlhYmxlcy4NCg0KKiBBZnRlciBhbmFseXNpbmcgYWxsIHRoZSB2YXJpYWJsZXMgaW4gdGhlIG1lcmdlZCBkYXRhc2V0ICoqaGVyb19zdGF0cyoqLCB3ZSB3aWxsIGNvbnZlcnQgcmVxdWlyZWQgdmFyaWFibGVzIGludG8gdGhlaXIgc3VpdGFibGUgZGF0YSBmb3JtYXQuIA0KDQoqIEJlZm9yZSB0aWR5IHVwIHRoZSAgbWVyZ2VkIGRhdGFzZXQgaXQgd291bGQgYmUgYmV0dGVyIHRvIGRlYWwgd2l0aCBtaXNzaW5nIHZhbHVlIGFuZCBvYnZpb3VzIGVycm9ycyBhcyBsZXNzIGl0ZXJhdGlvbnMgd2lsbCBiZSByZXF1aXJlZC4NCg0KKiBXZSB3aWxsIGNoZWNrIGZvciBkYXRhIHZhbGlkYXRpb25zIGFuZCBpbmNvbnNpc3RlbmNpZXMsIGluY2FzZSBvZiBwcmVzZW5jZSBvZiBhbnkgdmlvbGF0aW9uIHdlIHdpbGwgZGVhbCB3aXRoIHRoZW0gdXNpbmcgcHJvcGVyIHJ1bGVzLg0KDQoqIEFmdGVyIGRlYWxpbmcgd2l0aCBtaXNzaW5nIHZhbHVlcyBhbmQgaW5jb25zaXN0ZW5jaWVzIHdlIHdpbGwgcHJvY2VlZCB0b3dhcmRzIHRpZHkgdXAgdGhlIGRhdGEuIA0KDQoqIFdlIHdpbGwgY3JlYXRlIG5ldyB2YXJpYWJsZSAqKkJNSSoqICBieSBtdXRhdGlvbiBvZiBoZWlnaHQgYW5kIFdlaWdodCBvZiBldmVyeSByZWNvcmQuIEZvciB0aGlzIHdlIHdpbGwgZmlyc3QgY29udmVydCBIZWlnaHQgYW5kIFdlaWdodCB2YXJpYWJsZSBpbiBNZXRlcnMgYW5kIEtnIGZvcm1hdCByZXNwZWN0aXZpbHkuDQoNCiogRnVydGhlciB3ZSB3aWxsIGFsc28gY2hlY2sgZm9yIG91dGxpZXJzIHByZXNlbmNlIGluIGhlaWdodCBhbmQgd2VpZ2h0IGFuZCBoYW5kbGUgdGhlbSB1c2luZyBzdWl0YWJsZSBtZXRob2QuIA0KDQoqIFdlIHRyYW5mb3JtZWQgdmFyaWJsZXMgc3VjaCBhcyBoZWlnaHQgYW5kIHdlaWdodCB1c2luZyBCb3hDb3ggdHJhbnNmb3JtYXRpb24gdG8gcmVkdWNlIHRoZSBza2V3bmVzcyBvZiB0aGUgZGlzdHJpYnV0aW9uLg0KDQoNCiMjIERhdGENCg0KQXNzaWdubWVudCBpcyBiYXNlZCBvbiBbKipTdXBlciBIZXJvZXMgRGF0YXNldCAqKl0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jbGF1ZGlvZGF2aS9zdXBlcmhlcm8tc2V0KSBmcm9tIEthZ2dsZS4gRGF0YXNldCBpbmNsdWRlcyBpbmZvcm1hdGlvbiBvZiBzdXBlciBoZXJvcyBhbmQgdmlsbGFpbiBvZiB2YXJpb3VzIHB1Ymxpc2hlciBjb21pYyBob3VzZSBzdWNoIGFzIERDLCBNYXJ2ZWwgYW5kIG1hbnkgb3RoZXJzLiBEYXRhc2V0IGNvbXByaXNlIG9mIHR3byBkYXRhc2V0cyBvbmUgY29udGFpbmluZyBwZXJzb25hbCBpbmZvcm1hdGlvbiBvZiBlYWNoIGNoYXJhY3RlciBhbmQgb3RoZXIgaGF2aW5nIGFiaWxpdHkvIHN1cGVycG93ZXIgaW5mb3JtYXRpb24gb2YgZWFjaCBjaGFyYWN0ZXIuDQoNCiMjIyMgKipUaGUgdHdvIGRhdGFzZXRzIGNvbnRhaW5zIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIDoqKg0KDQoqKmhlcm9lc19pbmZvcm1hdGlvbi5jc3YqKjoNCg0KbmFtZVtDaGFyYWN0ZXJdIDogTmFtZSBvZiBIZXJvLyBWaWxsYWluDQoNCkdlbmRlcltGYWN0b3JdICA6IE1hbGUgb3IgRmVtYWxlDQoNCkV5ZWNvbG9yW0ZhY3Rvcl06IENvbG9yIG9mIEV5ZQ0KDQpSYWNlIFtGYWN0b3JdICAgOiBTcGVjaWVzIHRoZSBIZXJvLyB2aWxsYWluIGJlbG9uZ3MgdG8NCg0KSGFpckNvbG9yW0ZhY3Rvcl06IENvbG9yIG9mIEhhaXINCg0KSGVpZ2h0IFtudW1lcmljXTogSGVpZ2h0IE1lYXN1cmVtZW50IGluIGNtDQoNClB1Ymxpc2hlcltGYWN0b3JdOiBDb21pYyBQdWJsaXNoZXIgSG91c2UNCg0KU2tpbmNvbG9yW0ZhY3Rvcl06IENvbG91ciBvZiBTa2luDQoNCkFsaWdubWVudFtGYWN0b3JdOiBEZXBvc2l0aW9uIG9mIHRoZSBwZXJzb24NCg0KV2VpZ2h0W251bWVyaWNdIDogV2VpZ2h0IG9mIHBlcnNvbiBpbiBwb3VuZA0KDQoqKnN1cGVyX2hlcm9fcG93ZXJzLmNzdiAqKjoNCg0KaGVyb19uYW1lW2NoYXJhY3Rlcl06IE5hbWUgb2YgSGVyby8gVmlsbGFpbg0KDQpjb2x1bW4gMiAtIDE2OCAoQWdpbGl0eSAtIG9tbmlzY2llbnQpW2xvZ2ljYWxdIC0gU3VwZXJwb3dlciBleGlzdCBmb3IgdGhlIGNoYXJhY3Rlcg0KDQojIyMjIEltcG9ydGluZyBkYXRhc2V0cw0KDQoqIERhdGFzZXRzIHVzZWQgaW4gdGhpcyBhc3NpZ25tZW50IGFyZSBjc3YgZmlsZXMgc28gZGF0YSBpcyBpbXBvcnRlZCB1c2luZyAqKnJlYWQuY3N2KCkqKiBtZXRob2Qgd2hpY2gNCnJlYWRzIHRoZSBjc3YgZmlsZSBmcm9tIHRoZSBkaXJlY3RvcnkuDQoqIFdoaWxlIGxvYWRpbmcgZGF0YSDigJxzdHJpbmdzQXNGYWN0b3Jz4oCdIHNldCB0byBmYWxzZSBmcm9tIGl0cyBkZWZhdWx0IHZhbHVlLCB0aGlzIHdpbGwgc3RvcmUgc3RyaW5nIHZhcmlhYmxlcyBhcyBjaGFyYWN0ZXIgdmVjdG9ycyByYXRoZXIgdGhhbiBmYWN0b3JzLg0KDQpgYGB7cn0NCkhlcm9faW5mbyA8LSByZWFkLmNzdignaGVyb2VzX2luZm9ybWF0aW9uLmNzdicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSApIA0KSGVyb19wb3dlcnM8LSByZWFkLmNzdignc3VwZXJfaGVyb19wb3dlcnMuY3N2Jywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQpgYGANCg0KIyMjIyBEYXRhc2V0cyBEaXNwbGF5IA0KDQoqIFdlIGhhdmUgdXNlZCBkaW0oKSBtZXRob2QgZm9yIGdldHRpbmcgdGhlIGRpbWVudGlvbnMgb2YgaW1wb3J0ZWQgZGF0YXNldHMuIGFuZCBoZWFkKCkgbWV0aG9kIHRvIGRpc3BsYXkgZmV3IHJvd3Mgb2YgZGF0YXNldC4NCmBgYHtyfQ0KZGltKEhlcm9faW5mbykNCmhlYWQoSGVyb19pbmZvWywxOjZdLDMpDQpoZWFkKEhlcm9faW5mb1ssNjoxMV0sMykNCg0KZGltKEhlcm9fcG93ZXJzKQ0KaGVhZChIZXJvX3Bvd2Vyc1ssMTo2XSwzKQ0KaGVhZChIZXJvX3Bvd2Vyc1ssMTYzOjE2OF0sMykNCmBgYA0KDQoqICoqSGVyb19pbmZvKiogRGF0YXNldCBjb21wcmlzZSBvZiA3MzQgb2JzZXJ2YXRpb25zIGFuZCAxMSBWYXJpYWJsZXMuDQoNCiogKipIZXJvX3Bvd2VyKiogRGF0YXNldCBjb21wcmlzZSBvZiA2Njcgb2JzZXJ2YXRpb25zIGFuZCAxNjggVmFyaWFibGVzLg0KDQojIyMjIE1lcmdpbmcgdHdvIERhdGFzZXRzDQoNCiogV2Ugd2lsbCBiZSBqb2luaW5nIHRoZSB0d28gc2V0cyB2aWEgKippbm5lcl9qb2luKCkqKiBtZXRob2Qgb3ZlciAibmFtZSIsIHNvIHRoYXQgb25seSB0aG9zZSBvYnNlcmVydmF0aW9ucyBhcmUgdGFrZW4gd2hpY2ggaGFzIGNvbW1vbiBjaGFyYWN0ZXJzLg0KYGBge3J9DQpoZXJvX3N0YXRzIDwtIGlubmVyX2pvaW4oSGVyb19pbmZvLEhlcm9fcG93ZXJzLCBjKCJuYW1lIj0gImhlcm9fbmFtZXMiKSkNCmBgYA0KKiBBZnRlciBKb2luaW5nIHR3byBEYXRhc2V0cyB0byAqKmhlcm9fc3RhdHMqKiwgd2Ugd2lsbCByZW1vdmUgdGhlIHZhcmlhYmxlICpYKiBhcyBpdCBkb2Vzbid0IHByb3ZpZGUgYW55IHVzZWZ1bCBpbmZvcm1hdGlvbi4NCg0KYGBge3J9DQojIFJlbW92aW5nIHVucmVxdWlyZWQgUyBOby4gQ29sdW1uDQpoZXJvX3N0YXRzIDwtIGhlcm9fc3RhdHNbLC0xXQ0KDQpgYGANCg0KKiBOZXcgRGF0YXNldCAqKmhlcm9fc3RhdHMqKiBkaXNwbGF5DQoqIFdlIGhhdmUgdXNlZCBkaW0oKSBtZXRob2QgZm9yIGdldHRpbmcgdGhlIGRpbWVudGlvbnMgb2Ygam9pbiBkYXRhc2V0cy4gYW5kIGhlYWQoKSBtZXRob2QgdG8gZGlzcGxheSBmZXcgcm93cyBvZiBkYXRhc2V0Lg0KDQpgYGB7cn0NCiMgRGltZW50aW9uIG9mIGhlcm9fc3RhdHMNCmRpbShoZXJvX3N0YXRzKQ0KDQpoZWFkKGhlcm9fc3RhdHNbMTo4XSwzKQ0KaGVhZChoZXJvX3N0YXRzWzk6MTVdLDMpDQpoZWFkKGhlcm9fc3RhdHNbMTczOjE3N10sMykNCmBgYA0KDQoqIERhdGFzZXQgKipoZXJvX3N0YXRzKiogY29tcHJpc2Ugb2YgNjYwIG9ic2VydmF0aW9ucyBhbmQgMTc3IHZhcmlhYmxlcw0KDQoNCiMjIFVuZGVyc3RhbmQNCg0KKiBBcyBtb3N0IG9mIHRoZSBzdHJpbmcgdmFyaWFibGVzIHN1Y2ggYXMgKipHZW5kZXIsIEV5ZWNvbG9yLCBSYWNlLCBIYWlyY29sb3IsIHNraW5jb2xvciwgcHVibGlzaGVyIGFuZCBhbmQgYWxpZ25tZW50KiogYXJlIHJlcWlyZWQgdG8gYmUgaW4gY2F0YWdvcmljYWwgZm9ybWF0LiBJdCBpcyByZXF1aXJlZCB0byBmaXJzdCBpbnRyb2R1Y2UgTkEsIHdoZXJlICItIiBvciBhbnkgb3RoZXIgc3BlcmNpYWwgY2hhcmFjdGVyIGlzIHByZXNlbnQgYmVmb3JlIGNhdG9nb3Jpc2luZyBpdC4gT25jZSBOQSdzIGFyZSBpbmR1Y2VzIHdlIHdpbGwgcHJvY2VlZCB0b3dhcmRzIHR5cGUgY29udmVydGlvbi4NCg0KKiBTaW5jZSBhbGwgdGhlIGFiaWxpdGllcyBmb3IgZWFjaCBzdXBlcmhlcm8gaXMgYmlhbmFyeSBjaG9pY2UsIHdlIHdpbGwgY29udmVydCBhbGwgc3VwZXJwb3dlciB2YXJpYWJsZXMoIGNvbHVtbiAxMSB0byBjb2x1bW4gMTc3ICkgZnJvbSBjaGFyYWN0ZXIgdG8gbG9naWNhbCBmb3JtYXQuDQoNCg0KYGBge3J9DQoNCiMgaW50cm9kdWN0aW5nIE5BJ3MgZm9yIHNwZWNpYWwgY2hhcmFjdGVycyBiZWZvcmUgRGF0YSBUeXBlIENvbnZlcnNpb24gDQoNCmhlcm9fc3RhdHNbaGVyb19zdGF0cyA9PSAnLSddPC0gTkENCg0KDQpgYGANCg0KYGBge3J9DQojIFByb3BlciBEYXRhIFR5cGUgQ09udmVyc2lvbg0KDQojIyBjb252ZXJ0aW5nIGZyb20gY2hhcmFjdGVyIHRvIGxvZ2ljYWwgZm9ybWF0DQpoZXJvX3N0YXRzWywxMToxNzddIDwtIHNhcHBseShoZXJvX3N0YXRzWywxMToxNzddLCBhcy5sb2dpY2FsKQ0KDQpgYGANCmBgYHtyfQ0KIyMgY29udmVydGlvbiBmcm9tIGNoYXJhY3RlciB0byBmYWN0b3INCmhlcm9fc3RhdHMkR2VuZGVyIDwtIGZhY3RvcihoZXJvX3N0YXRzJEdlbmRlcikNCmhlcm9fc3RhdHMkRXllLmNvbG9yIDwtIGZhY3RvcihoZXJvX3N0YXRzJEV5ZS5jb2xvcikNCmhlcm9fc3RhdHMkUmFjZSA8LSBmYWN0b3IoaGVyb19zdGF0cyRSYWNlKQ0KaGVyb19zdGF0cyRIYWlyLmNvbG9yIDwtIGZhY3RvcihoZXJvX3N0YXRzJEhhaXIuY29sb3IpDQpoZXJvX3N0YXRzJFB1Ymxpc2hlciA8LSBmYWN0b3IoaGVyb19zdGF0cyRQdWJsaXNoZXIpDQpoZXJvX3N0YXRzJFNraW4uY29sb3IgPC0gZmFjdG9yKGhlcm9fc3RhdHMkU2tpbi5jb2xvcikNCmhlcm9fc3RhdHMkQWxpZ25tZW50IDwtIGZhY3RvcihoZXJvX3N0YXRzJEFsaWdubWVudCkNCg0KYGBgDQoNCg0KKiBUTyBzdW1tYXJpc2luZyB0aGUgdmFyaWFibGVzIHR5cGUgYW5kIGRhdGEgc3RydWN0dXJlIGZvciB0aGUgRGF0YXNldCAqKmhlcm9fc3RhdHMqKiB3ZSBoYXZlIHVzZWQgc3RyKCkgbWV0aG9kIG9uIHRoZSBkYXRhc2V0Lg0KYGBge3J9DQojIGRpc3BsYXlpbmcgc3RydWN0dXJlIG9mIGRhdGEgc2V0DQpzdHIoaGVyb19zdGF0cykgJT4lIGhlYWQoMjApDQpgYGANCg0KIyMgU2NhbiBJDQoNClNjYW4gdGhlIGRhdGEgZm9yIG1pc3NpbmcgdmFsdWVzLCBzcGVjaWFsIHZhbHVlcyBhbmQgb2J2aW91cyBlcnJvcnMgKGkuZS4gaW5jb25zaXN0ZW5jaWVzKS4gDQoNCg0KQmVmb3JlIHRpZHkgdXAgdGhlIGRhdGFzZXQgaXQgd291bGQgYmUgYmV0dGVyIHRvIGRlYWwgd2l0aCBtaXNzaW5nIHZhbHVlIGFuZCBvYnZpb3VzIGVycm9ycyB3ZSB3aWxsIGRvaW5nIHNvIGJ5IGZpcnN0IGlkZW50aWZ5aW5nIHNwZWNpYWwgdmFsdWVzIGFuZCBtaXNzaW5nIHZhbHVlcy4NCg0KYGBge3J9DQojIENoZWNrIGZvciBtaXNzaW5nIHZhbHVlcyBmb3IgZXZlcnkgdmFyaWFibGVzDQpoZXJvX3N0YXRzICU+JSBpcy5uYSgpICU+JSBjb2xTdW1zKCkgJT4lIGhlYWQoMzApDQpgYGANCg0KYGBge3J9DQojIENoZWNrIGZvciBTcGVjaWFsIFZhbHVlcw0KDQojI2NoZWNraW5nIGZvciBOYU4gdmFsdWVzDQpzYXBwbHkoaGVyb19zdGF0cywgZnVuY3Rpb24oeCkgc3VtKGlzLm5hbih4KSkpICU+JSBzdW0oKQ0KDQpgYGANCmBgYHtyfQ0KIyNjaGVja2luZyBmb3IgSW5maXRlIHZhbHVlcw0Kc2FwcGx5KGhlcm9fc3RhdHMsIGZ1bmN0aW9uKHgpIHN1bSggaXMuaW5maW5pdGUoeCkpKSAlPiUgc3VtKCkNCmBgYA0KDQojIyMjIERlYWxpbmcgd2l0aCBtaXNzaW5nIFZhbHVlcw0KDQoqIFdlIGNhbiBPYnNlcnZlIHRoYXQgYWxsIG9mIHRoZSBtaXNzaW5nIHZhbHVlcyBhcmUgcHJlc2VudCBpbiA3IHZhcmlhYmxlcywgd2hlcmUgNiBvZiB0aGVtIGFyZSBjYXRhZ29yaWNhbCBhbmQgMSBpcyBudW1lcmljYWwuIFdlIHdpbGwgZGVhbCB3aXRoIGVhY2ggdmFyaWFibGUgaXMgdGhlIG1vc3Qgc3VpdGFibGUgd2F5IHBvc3NpYmxlLg0KDQoqIEZvciAqKkdlbmRlciwgRXllIENvbG9yLCBIYWlyIENvbG9yLCBTa2luIGNvbG9yICoqIHdlIHdpbGwgcmVwbGFjZSB0aGUgbWlzc2luZyB2YWx1ZSB3aXRoIHRoZSBtb3N0IG9jY3VyaW5nIHZhbHVlLg0KDQoqIEZvciB2YXJpYWJsZSAqKlJhY2UqKiBzaW5jZSBtYW55IG9mIHRoZXNlIGNoYXJhY3RlcnMgYXJlIGZyb20gc3BhY2UsIHRoZSBjaGFyYWN0ZXJzIHdpdGggbWlzc2luZyByYWNlIGNhbiBiZSBjb25zaWRlcmVkIHRvIGJlICoqQWxpZW4qKi4NCg0KKiBTaW5jZSAqKkFsaWdubWVudCoqIG9mIGEgcGVydGljdWxhciBjaGFyYWN0ZXIgY2FuIGJlIGdvb2QsIGJhZCBvciBuZXV0cmFsLCB3ZSB3aWxsIGNvbnNpZGVyIHRoYXQgdGhlIGluZGl2aWR1YWwgd2l0aCBtaXNzaW5nIGFsaWdubWVudCBhcmUgKipuZXV0cmFsKiogLg0KDQoqIGZvciB0d28gbWlzc2luZyB3ZWlnaHQgb2JzZXJ2YXRpb25zIHdlIHdpbGwgcmVwbGFjZSB0aGVtIHdpdGggdGhlIG1lYW4gd2VpZ2h0IGZvciBhbGwgdGhlIG9ic2VydmF0aW9uDQoNCmBgYHtyfQ0KDQojIEZvciBHZW5kZXIgd2Ugd2lsbCBvYnNlcnZlIHVzaW5nIHRhYmxlIGZ1bmN0aW9uIHRoYXQgYWJvdXQgNzIgcGVyY2VudCBvZiBjaGFyYWN0ZXIgYXJlIE1hbGUuDQpoZXJvX3N0YXRzJEdlbmRlciAlPiUgdGFibGUoKSAlPiUgcHJvcC50YWJsZSgpDQogICMgV2UgYXJlIHNhZmVseSBhc3N1bWluZyB0aGF0IDEzIG1pc3NpbmcgZ2VuZGVyIGNoYXJhY3RlcnMgYXJlIG1hbGUgYXMgd2VsbA0KICBoZXJvX3N0YXRzJEdlbmRlcltpcy5uYShoZXJvX3N0YXRzJEdlbmRlcildPC0gJ01hbGUnDQoNCiMgRm9yIEV5ZSBDb2xvcg0KaGVyb19zdGF0cyRFeWUuY29sb3IgPC0gSG1pc2M6OmltcHV0ZShoZXJvX3N0YXRzJEV5ZS5jb2xvciwgZnVuID0gbW9kZSkNCg0KIyBGb3IgSGFpciBDb2xvcg0KaGVyb19zdGF0cyRIYWlyLmNvbG9yIDwtIEhtaXNjOjppbXB1dGUoaGVyb19zdGF0cyRIYWlyLmNvbG9yLCBmdW4gPSBtb2RlKQ0KDQojIGZvciBza2luIENvbG9yDQpoZXJvX3N0YXRzJFNraW4uY29sb3IgPC0gSG1pc2M6OmltcHV0ZShoZXJvX3N0YXRzJFNraW4uY29sb3IsIGZ1biA9IG1vZGUpDQoNCiMgRm9yIFJhY2UNCmhlcm9fc3RhdHMkUmFjZVtpcy5uYShoZXJvX3N0YXRzJFJhY2UpXSA8LSAnQWxpZW4nDQoNCiMgRm9yIEFsaWdubWVudA0KaGVyb19zdGF0cyRBbGlnbm1lbnRbaXMubmEoaGVyb19zdGF0cyRBbGlnbm1lbnQpXSA8LSAnbmV1dHJhbCcNCg0KIyBGb3IgV2VpZ2h0DQoNCmhlcm9fc3RhdHMkV2VpZ2h0IDwtIEhtaXNjOjppbXB1dGUoaGVyb19zdGF0cyRXZWlnaHQsIGZ1biA9IG1lYW4pDQoNCg0KIyMgY2hlY2sgZm9yIE5hJ3MNCg0KaGVyb19zdGF0cyAlPiUgaXMubmEoKSAlPiUgc3VtKCkNCiANCmBgYA0KDQojIyMjIENoZWNrIGZvciBvYnZpb3VzIGluY29uc2lzdGVuY2llcw0KDQpGb3IgY2hlY2tpbmcgdGhlc2UgY29tbW9uIGluY29uc2lzdGVuY2llcyBpbiBkYXRhIHNldCAgd2Ugd2lsbCBiZSB1c2luZyAqKmVkaXRocnVsZXMqKiBhbmQgKipkZWR1Y29ycmVjdCoqIHBhY2thZ2VzLg0KDQoqIFJ1bGVzIGZvciBpbmNvbnNpc3RlbmN5IGFyZToNCg0KMS4gSGVpZ2h0ID4gMA0KICBJZiBIZWlnaHQgPCAwIG11bHRpcGx5IGl0IGJ5IC0xDQoNCjIuIFdlaWdodCA+IDANCiAgSWYgV2VpZ2h0IDwgMCBtdWx0aXBseSBpdCBieSAtMQ0KDQpDaGVjayBmb3IgVmlvbGF0aW9uIG9mIHJ1bGVzOg0KYGBge3J9DQpSdWxlMSA8LSBlZGl0c2V0KGMoIldlaWdodCA+IDAiLCAiSGVpZ2h0ID4gMCIpKQ0KUnVsZTENCg0KdmlvbGF0aW9ucyA8LSB2aW9sYXRlZEVkaXRzKFJ1bGUxLGhlcm9fc3RhdHMpDQp2aW9sYXRpb25zICU+JSBzdW1tYXJ5KCkNCg0KYGBgDQoNCiogV2UgY2FuIG9ic2VydmUgcHJlc2VuY2Ugb2YgbXVsdGlwbGUgaW5zdGFuY2VzIG9mIHN1Y2ggdmlvbGF0aW9ucy4gV2UgY2FuIGNvcnJlY3QgdGhlc2UgaW5zdGFuY2VzIHVzaW5nIGRlZHVjb3JyZWN0IG1vZHVsZQ0KDQpTZXR0aW5nIFJ1bGVzDQpgYGB7cn0NCiMgTG9hZGluZyBSdWxlcyBmcm9tIEZpbGUNClJ1bGVzIDwtIGNvcnJlY3Rpb25SdWxlcygnUnVsZXMudHh0JykNCg0KUnVsZXMNCmBgYA0KDQpDb3JlY3Rpbmcgd2l0aCBSdWxlcw0KYGBge3J9DQpjb3IgPC0gY29ycmVjdFdpdGhSdWxlcyhSdWxlcywgaGVyb19zdGF0cykNCg0KaGVyb19zdGF0cyA8LSBjb3IkY29ycmVjdGVkDQpgYGANCg0KDQpSZS1jaGVjayBmb3IgdmlvbGF0aW9ucw0KYGBge3J9DQp2aW9sYXRpb25zIDwtIHZpb2xhdGVkRWRpdHMoUnVsZTEsaGVyb19zdGF0cykNCnZpb2xhdGlvbnMgJT4lIHN1bW1hcnkoKQ0KYGBgDQoNCiMjIFRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSQ0KDQoNCg0KKiBJbiBkYXRhc2V0ICoqaGVyb19zdGF0cyoqLCBjb2x1bW4gMTEgdG8gY29sdW1uIDE3NyBjb21wcmlzZSBvZiBkaWZmZXJlbnQgYWJpbGl0aWVzIGEgaGVyby8gdmlsbGFpbiBjYW4gaGF2ZS4gc2luY2UgYWxsIHRoZXNlIHZhcmlhYmxlcyBhcmUgbW9yZSBvZiBhIGNhdGFnb3JpZXMgb2Ygc3VwZXJwb3dlci8gYWJpbGl0aWVzIHJhdGhlciB0aGFuIGEgdmFyaWFibGUgdGhlbXNlbGYsIEl0IHZpb2xhdGVzIHRoZSB0aWR5IGRhdGEgcHJpbmNpcGxlLg0KDQoqIEZvciB0aWR5LXVwIHRoZSBkYXRhIHdlIHdpbGwgdXNlIGdhdGhlciBtZXRob2Qgb24gY29sdW1uIDExIHRvIGNvbHVtbiAxNzcsIHdoZXJlIGtleSB3aWxsIGJlICoqQWJpbGl0eSBUeXBlKiogYW5kIHZhbHVlIHdpbGwgYmUgKipFeGlzdCoqLg0KDQpgYGB7cn0NCmhlcm9fc3RhdHMgPC0gaGVyb19zdGF0cyAlPiUgZ2F0aGVyKDExOjE3NyxrZXkgPSAnQWJpbGl0eSBUeXBlJywgdmFsdWUgPSAnRXhpc3QnKQ0KYGBgDQoNCmBgYHtyfQ0KIyBEaW1lbnRpb24gb2YgcmUtb3JnYW5pc2VkIERhdGFzZXQNCmRpbShoZXJvX3N0YXRzKQ0KYGBgDQpgYGB7cn0NCmhlYWQoaGVyb19zdGF0c1ssMTo4XSkNCmhlYWQoaGVyb19zdGF0c1ssODoxMl0pDQpgYGANCg0KDQoNCiMjIFRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSUkNCg0KQ3JlYXRlL211dGF0ZSBhdCBsZWFzdCBvbmUgdmFyaWFibGUgZnJvbSB0aGUgZXhpc3RpbmcgdmFyaWFibGVzIChtaW5pbXVtIHJlcXVpcmVtZW50ICM2KS4gSW4gYWRkaXRpb24gdG8gdGhlIFIgY29kZXMgYW5kIG91dHB1dHMsIGV4cGxhaW4gZXZlcnl0aGluZyB0aGF0IHlvdSBkbyBpbiB0aGlzIHN0ZXAuDQoNCiogQmVmb3JlIGNhbGN1bGF0aW5nIEJNSSB3ZSB3aWxsIGZpcnN0IGNvbnZlcnQgaGVpZ2h0IGZvcm0gY20gdG8gbWV0ZXJzIGFuZCB3ZWlnaHQgZnJvbSBwb3VuZHMgdG8ga2cNCmBgYHtyfQ0KIyBIZWlnaHQgaW4gbWV0ZXJzDQpoZXJvX3N0YXRzJEhlaWdodCA8LSBoZXJvX3N0YXRzJEhlaWdodCAqIDAuMDENCmBgYA0KDQpgYGB7cn0NCiMgV2VpZ2h0IGluIEtnDQpoZXJvX3N0YXRzJFdlaWdodCA8LSBoZXJvX3N0YXRzJFdlaWdodCAqIDAuNDUzNTkyDQpgYGANCg0KDQoqIEJNSSBpcyBjYWxjdWxhdGVkIGJ5IHdlaWdodC9oZWlnaHReMl4gLiB3ZSB3aWxsIG1lIHVzaW5nIG1ldGhvZCBtdXRhdGUgdG8gYWRkIGFkZGl0aW9uYWwgdmFyaWFibGUgaW4gdGhlIGRhdGFzZXQuDQpgYGB7cn0NCiMgQWRkaW5nIHZhcmlhYmxlIEJNSQ0KaGVyb19zdGF0cyA8LSBoZXJvX3N0YXRzICU+JSBtdXRhdGUoQk1JID0gV2VpZ2h0LyhIZWlnaHQqSGVpZ2h0KSkNCg0KcmFuZ2UoaGVyb19zdGF0cyRCTUkpDQpgYGANCg0KIyMgU2NhbiBJSQ0KDQoqIEZvciBjaG9vc2luZyB0aGUgYXBwcm9wcmlhdGUgbWV0aG9kIG9mIG91dGxpbmVhcnMgZGV0ZWN0aW9uIHdlIHdpbGwgZmlyc3QgY2hlY2sgdGhlIG5vcm1hbGl0eSBvZiB0aGUgZGlzdHJpYnV0aW9uLiAgDQoNCmBgYHtyfQ0KDQojIENoZWNraW5nIGZvciBub3JtYWxpdHkNCnBhcihtZnJvdz1jKDEsMikpDQoNCmhlcm9fc3RhdHMkSGVpZ2h0ICU+JSBoaXN0KG1haW49J0hlaWdodCBkaXN0cmlidXRpb24nLCB4bGFiID0gJ0hlaWdodCBpbiBtZXRlcnMnKQ0KaGVyb19zdGF0cyRXZWlnaHQgJT4lIGhpc3QobWFpbj0nV2VpZ2h0IGRpc3RyaWJ1dGlvbicsIHhsYWIgPSAnV2VpZ2h0IGluIEtnJykNCg0KYGBgDQoNCiogRnJvbSB0aGUgYWJvdmUgZGlzdHJpYnV0aW9ucyB3ZSBjYW4gY29uY2x1ZGUgdGhhdCBub24gb2YgdGhlIG51bWVyaWNhbCB2YXJpYWJsZSBoYXMgZGF0YSBub3JtYWxseSBkaXN0cmlidXRlZC4gVGh1cyB3ZSBzaGFsbCBub3QgdXNlIFotc2NvcmUgbWV0aG9kIGZvciBvdXRsaW5lcnMgZGV0ZWN0aW9uIGJ1dCBnbyBmb3IgVHVrZXkncyBtZXRob2QuDQoNCmBgYHtyfQ0KDQojIE91dGxpbmVycyBEZXRlY3Rpb24gdXNpbmcgVHVrZXkncyBtZXRob2QgDQpwYXIobWZyb3c9YygxLDIpKQ0KaGVyb19zdGF0cyRIZWlnaHQgJT4lIGJveHBsb3QobWFpbiA9JyBCb3hwbG90IGZvciBIZWlnaHQnKQ0KDQpoZXJvX3N0YXRzJFdlaWdodCAlPiUgYXMubnVtZXJpYygpICU+JSBib3hwbG90KG1haW4gPScgQm94cGxvdCBmb3IgV2VpZ2h0JykNCmBgYA0KDQoNCiogRnJvbSB0aGUgYWJvdmUgZ3JhcGggd2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSBtYW55IG91dGxpbmVycyBwcmVzZW50LiBGb3IgSGVpZ2h0IGFuZCB3ZWlnaHQgd2Ugd2lsbCBiZSByZXBsYWNpbmcgb3V0bGluZWFycyB3aXRoIHF1YW50aWxlIHZhbHVlcywgc28gdGhhdCB2YWx1ZSByZW1haW4gYXMgY2xvc2UgYXMgcG9zc2libGUuDQoNCmBgYHtyfQ0KIyBEZWZpbmluZyBmdW5jdGlvbiBmb3IgY2FwcGluZyBvdXRsaW5lcnMgdG8gbmVhcmVzdCBxdWFudGlsZQ0KY2FwIDwtIGZ1bmN0aW9uKHgpew0KcXVhbnRpbGVzIDwtIHF1YW50aWxlKCB4LCBjKC4wNSwgMC4yNSwgMC43NSwgLjk1ICkgKQ0KeFsgeCA8IHF1YW50aWxlc1syXSAtIDEuNSpJUVIoeCkgXSA8LSBxdWFudGlsZXNbMV0NCnhbIHggPiBxdWFudGlsZXNbM10gKyAxLjUqSVFSKHgpIF0gPC0gcXVhbnRpbGVzWzRdDQp4DQp9DQoNCmBgYA0KDQoNCiogQ2FwcGluZyBvdXRsaW5lYXJzIGZvciBIZWlnaHQgYW5kIFdlaWdodCB2YXJpYWJsZQ0KDQpgYGB7cn0NCmhlcm9fc3RhdHMkSGVpZ2h0IDwtIGhlcm9fc3RhdHMkSGVpZ2h0ICU+JSAgY2FwKCkgDQpoZXJvX3N0YXRzJFdlaWdodCA8LSBoZXJvX3N0YXRzJFdlaWdodCAlPiUgY2FwKCkNCg0KYGBgDQoNCiogQ2hlY2tpbmcgU3VtbWFyaXNpbmcgc3RhdGlzdGljcyBmb3Igd2VpZ2h0IGFuZCBoZWlnaHQgDQoNCmBgYHtyfQ0KaGVyb19zdGF0cyRIZWlnaHQgJT4lIHN1bW1hcnkoKQ0KaGVyb19zdGF0cyRXZWlnaHQgJT4lIHN1bW1hcnkoKQ0KYGBgDQoNCiogUmUtY2FsY3VsYXRpbmcgQk1JIGFmdGVyIGhhbmRlbGluZyBvdXRsaW5lcnMNCmBgYHtyfQ0KaGVyb19zdGF0cyRCTUkgPC0gaGVyb19zdGF0cyRXZWlnaHQvKGhlcm9fc3RhdHMkSGVpZ2h0XjIpDQpyYW5nZShoZXJvX3N0YXRzJEJNSSkNCmBgYA0KDQoNCg0KYGBge3J9DQojIGNoZWNrIGZvciBvdXRsaW5lYXJzIGluIEJNSQ0KDQpwYXIobWZyb3c9YygxLDIpKQ0KDQpoZXJvX3N0YXRzJEJNSSAlPiUgYXMubnVtZXJpYygpICU+JSBib3hwbG90KG1haW4gPSdCTUkgQm94cGxvdCB3aXRoIG91dGxpbmVycycsIHlsYWI9J0JvZHkgTWFzcyBJbmRleCcpDQpoZXJvX3N0YXRzJEJNSSAlPiUgYXMubnVtZXJpYygpICU+JSBib3hwbG90KG1haW4gPSdCTUkgQm94cGxvdCB3aXRob3V0IG91dGxpbmVycycsIHlsYWI9J0JvZHkgTWFzcyBJbmRleCcsb3V0bGluZSA9IEZBTFNFKQ0KDQpgYGANCg0KU2luY2UgdGhlcmUgc3RpbGwgZXhpc3QgIHNvbWUgb3V0bGluZWFycyBpbiBCTUkgbGV0cyBleHBsb3JlIHRoZXNlIGNhc2VzLg0KDQpgYGB7cn0NCiMgY2hlY2sgY2FzZXMgZm9yIGNhc2VzIHdpdGggaGlnaCBCTUkNCmhlcm9fc3RhdHNbaGVyb19zdGF0cyRCTUkgPjEwMCxdJG5hbWUgJT4lIHVuaXF1ZSgpDQpoZXJvX3N0YXRzW2hlcm9fc3RhdHMkQk1JID4xMDAsXSRSYWNlICU+JSB1bmlxdWUoKQ0KYGBgDQoNCiogU2luY2UgdGhyZWUgb2YgdGhlIGFib3ZlIGNoYXJhY3RlciAqKlQtWCoqLCAqKlQtODUwKiogYW5kICoqVC04MDAqKiBhcmUgY3lib3JnLCBpdCBtYWtlIHNlbnNlIGZvciB0aGVtIHRvIGhhdmUgYWJub3JtYWxseSBoaWdoIEJNSS4gQXMgZm9yICoqR2lnYW50YSoqIGFuZCAqKkFudGkgTW9uaXRvcioqIGFyZSBjaGFyYWN0ZXJzIGZyb20gKipEQyBjb21pY3MqKiB3aXRoIHNpemUgbWFuaXB1bGF0aW9uIHBvd2VyLCB3aGljaCBtYWtlcyBzZW5zZSBmb3IgaXRzIGV4dHJlZW1seSBoaWdoIEJNSS4NCioqS2luZyBLb25nKiogYmVsb25naW5nIHRvIGFuaW1hbCBraW5nZG9tIG9mIGFwZXMgdGh1cyBoYXZlIGhpZ2ggQk1JIHZhbHVlIGFzIHdlbGwuDQoNCiMjIFRyYW5zZm9ybQ0KDQoqIENoZWNrIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBoZWlnaHQgYW5kIHdlaWd0aA0KDQpgYGB7cn0NCnBsb3QoaGVyb19zdGF0cyRIZWlnaHQsIGhlcm9fc3RhdHMkV2VpZ2h0KQ0KYGBgDQoNCiogQXMgd2UgY2FuIHNlZSB0aGUgSGVpZ2h0IGFuZCBXZWlnaHQgdmFyaWFibGUgZG8gaGF2ZSBsaW5lYXJpdHkgc3BlY2lhbGx5IGZvciBoZWlnaHQgYmV0d2VlbiAxLjUgbWV0ZXJzIHRvIDIuMiBtZXRlcnMgdG8gLCBidXQgY2FuIGJlIGZ1cnRoZXIgaW1wcm92ZWQuDQoNCiogV2Ugd2lsbCBiZSB1c2luZyBCb3hjb3ggdHJhbnNmb3JtYXRpb24gZm9yIG5vcm1hbGlzaW5nIHRoZSBoZWlnaHQgYW5kIHdlaWdodCB2YXJpYWJsZSBkYXRhDQpgYGB7cn0NCnBhcihtZnJvdz1jKDEsMikpDQpoZWlnaHQgPC0gQm94Q294KGhlcm9fc3RhdHMkSGVpZ2h0LCBsYW1iZGEgPSAnYXV0bycpDQpoZWlnaHQgJT4lIGhpc3QobWFpbj0nVHJhbnNmb3JtZWQgSGVpZ2h0IERhdGEnKQ0KDQp3ZWlnaHQgPC0gQm94Q294KGhlcm9fc3RhdHMkV2VpZ2h0LCBsYW1iZGEgPSAnYXV0bycpDQp3ZWlnaHQgJT4lIGhpc3QobWFpbj0nVHJhbnNmb3JtZWQgV2VpZ2h0IERhdGEnKQ0KDQpgYGANCg0KDQoqIENoZWNrIGZvciBsaW5lYXJpdHkgZm9yIHRyYW5mb3JtZWQgZGF0YQ0KYGBge3J9DQoNCnBsb3QoaGVpZ2h0LHdlaWdodCkNCmBgYA0KDQoqIEZyb20gdGhlIHNjYXR0ZXIgcGxvdCBhYm92ZSwgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIEhlaWdodCBhbmQgV2VpZ2h0IGNhbiBiZSAgb2JzZXJ2ZWQgdG8gYmUgbGluZWFyIGZvciBhIHN1aXRhYmxlIHJhbmdlLg0KDQojIyBFeHRyYQ0KDQoqIFdlIGNhbiBmdXJ0aGVyIHJlbW92ZSB0aGUgZGVwZW5kZW5jeSBvZiAqKkV4aXN0KiogdmFyaWFibGUgYnkgcmVtb3Zpbmcgb2JzZXJ2YXRpb25zIGZvciB3aGljaCBhYmlsaXR5IGRvZXNuJ3QgZXhpc3QuDQoNCmBgYHtyfQ0KIyBGaWx0ZXJpbmcgb2JzZXJ2YXRpb24gDQpoZXJvX3N0YXRzPC0gaGVyb19zdGF0cyAlPiUgZmlsdGVyKEV4aXN0ID09IFRSVUUpDQoNCmBgYA0KYGBge3J9DQojIFJlbW92aW5nIHZhcmlhYmxlIEV4aXN0DQpoZXJvX3N0YXRzIDwtIGhlcm9fc3RhdHNbLTEyXQ0KYGBgDQoNCmBgYHtyfQ0KIyBkaW1lbnRpb24gb2YgRGF0YXNldA0KZGltKGhlcm9fc3RhdHMpDQoNCiMgZGF0YXNldCBoZWFkIA0KaGVhZChoZXJvX3N0YXRzWywxOjhdKQ0KaGVhZChoZXJvX3N0YXRzWyw4OjEyXSkNCmBgYA0KIyMjIyBHZXR0aW5nIFN1cGVyIFBvd2VyIFZpc3VhbGlzYXRpb24NCg0KKiBXZSBjYW4gZnVydGhlciBkZXRlcm1pbmUgbW9zdCBjb21tb24gYWJpbGl0aWVzIGFtb25nIHN1cGVyIGhlcm9zIGFuZCBzdXBlciB2aWxsYWluLg0KYGBge3J9DQojIEJhcnBsb3QgZm9yIHN1cGVycG93ZXIgY291bnQNClBvd2VyX3N0YXRzPC0gaGVyb19zdGF0cyAlPiUgZ3JvdXBfYnkoYEFiaWxpdHkgVHlwZWApICU+JSBzdW1tYXJpc2UoIFBvd2VyVXNlcnMgPSBuKCkpICU+JSBhcnJhbmdlKGRlc2MoUG93ZXJVc2VycykgKQ0KYmFycGxvdChQb3dlcl9zdGF0cyRQb3dlclVzZXJzWzE6NV0sIG5hbWVzLmFyZyA9IFBvd2VyX3N0YXRzJGBBYmlsaXR5IFR5cGVgWzE6NV0sIG1haW4gPSAnNSBNb3N0IENvbW1vbiBBYmlsaXR5JykNCmBgYA0K