1.0 Introduction

Excel and two of its most popular free counterparts, Google Sheets and Calc, offer lookup functions and pivot tables. In this notebook we take a look at what they can do for us and what are their equivalents in R.

2.0 Getting some data.

I wanted to use tables from the Lahman database, which contains Major League Baseball’s statistics from 1871(!) to 2016. I downloaded the SQL database and setup a server using XAMPP. After you install it, fire up the XAMPP Control Panel and start the Apache and MySQL modules.


The Lahman database is ~65 MB, so I had to change some of the settings in the php.ini file to allow for larger files to be uploaded to the server. To make the changes, click on the Config button on the Apache module row and select php.ini

Once the server is up and running, we can connect to it using R via the RMySQL package. Herein we load the RMySQL package, connect to the database, and list the tables in it:

library(RMySQL)
drv <- dbDriver("MySQL")
db <- dbConnect(drv, user="root", dbname = "lahman2016", host="127.0.0.1")
dbListTables(db)
 [1] "allstarfull"         "appearances"         "awardsmanagers"      "awardsplayers"      
 [5] "awardssharemanagers" "awardsshareplayers"  "batting"             "battingpost"        
 [9] "collegeplaying"      "fielding"            "fieldingof"          "fieldingofsplit"    
[13] "fieldingpost"        "halloffame"          "homegames"           "managers"           
[17] "managershalf"        "master"              "parks"               "pitching"           
[21] "pitchingpost"        "salaries"            "schools"             "seriespost"         
[25] "teams"               "teamsfranchises"     "teamshalf"          

Using RMySQL, we can execute a SQL query and store the table it returns as a dataframe inside R. We can then take the dataframe and write it to a csv file. Let’s do that with the individual batting statistics for the 1980 MLB season.

batting1980 <- dbGetQuery(db,"SELECT *
                              FROM Batting
                              WHERE yearID = 1980")
write.csv(batting1980, file = "batting1980.csv")

No output is returned, but the csv file has been written.

The batting table has two fields named playerID and teamID, which are identifiers for the players and teams rather than their actual names. The teams and the master tables have the real names along with the identifiers. We can do similarly for both of these tables. First the teams’ table, filtered to include only teams that participated in the 1980 MLB season.

teams1980 <- dbGetQuery(db,"SELECT *
                              FROM teams
                              WHERE yearID = 1980")
write.csv(teams1980, file = "teams1980.csv")

The master table has all the players’ natural information, including first and last name, along with their identifiers. This table has every player that has ever played in the major leagues. There’s no way to filter it by year, so we have to write the whole thing to the csv file

master <- dbGetQuery(db,"SELECT *
                              FROM master")
write.csv(master, file = "master.csv")

Now we can load the tables as separate tabs in Calc.

3.0 Lookup functions

3.1 VLOOKUP()

Like Excel, Calcl has a VLOOKUP() function that allows users to look for an item in a table. Its syntax is as follows:

VLOOKUP(lookup_value, lookup_range, return_value, match_type)

  • lookup_value Required. What you are looking for.
  • lookup_range Required. Where you want to look for it, i.e., a table.
  • return_value Required. What you want returned, that is, the column value in the lookup_range you want returned. It is a number, so if the lookup_range has 4 columns and you want the 3rd column returned, you would put 3 in your return value.
  • match_type Optional. TRUE (default) if you are ok with a partial match, or FALSE if only an exact match will do. I think we would almost always want FALSE, i.e., an exact match.


Here are relevant segments of the batting table (left) and the teams table (right)

          

We use VLOOKUP() to look up the teamID (in the “D”" column in the batting table) in the relevant portion of the teams table (columns “D” through “AP”), and return the teams’ names, the 39th column(“AP”), counting from the “D”" column.

=VLOOKUP(D2, Teams.$D$2:$AP$27,39, 0)

and get:

3.2 INDEX-MATCH

VLOOKUP() works ok, but it has two disadvantages: it’s slow, and it only searches for lookup_value in the leftmost column of lookup_range. We overcome both of these limitations by combining INDEX() and MATCH().

3.2.1 MATCH()

Let’s look at the syntax for MATCH()

MATCH(lookup_value, lookup_range, match_type)

Searches for lookup_value and returns the relative position of the item in the lookup_range.

  • lookup_value Required. What you are looking for.

  • lookup_range Required. Where you want to look for it. It has to be a column or a row.

  • match_type Optional. Similarly to VLOOKUP(), 1 (default), if you are ok with partial matches, or 0 if you want an exact match. There’s also a -1 option that returns the position of the smallest value that is greater or equal than the lookup_value.

If lookup_range is a column, MATCH() returns the row number where lookup_value was found.

3.2.2 INDEX()

Now let’s take a look at the syntax for INDEX().

INDEX(lookup_range, row_num, column_num)

  • lookup_range Required. Where you want to look for it, i.e., a table.

  • row_num Required (if column_num is omitted). Selects the row in the lookup_range from which to return a value. If row_num is omitted, then column_num is required.

  • column_num Required (if row_num is omitted). Selects the column in the lookup_range from which to return a value. If column_num is omitted, then row_num is required.

  • If the array has more than one column and more than one row, and only row_num or column_num is used, INDEX()returns an array of an entire row or column, and places it in the cell.

INDEX() returns the item located at (row_num, column_num) in the lookup_range.

3.2.3 INDEX-MATCH combo

INDEX() can be used in conjunction with MATCH() as a faster, less limited alternative to VLOOKUP() by using MATCH() as the row_num argument of INDEX().

Here are again the relevant segments of the batting table (left) and the teams table (right)

          

We can use MATCH() to find the row number where the teamID is found in the teams table, and then use that as the row_num parameter of INDEX().

=INDEX(Teams.$D$2:$AP$27,MATCH(D2,Teams.$D$2:$D$27,0), 39)

and get:


Now use INDEX-MATCH to find the players’ full names using the players’ playerID fields in the batting table.

Here are the relevant segments of the batting table (left) and the master table (right)

          

Here’s the INDEX-MATCH index for the player’s last name:

=INDEX(Master.$B$2:$P$19106,MATCH(A2,Master.$B$2:$B$19106,0), 14)

In this case, MATCH() returns the row number in the master table where the playerID was found. That row number is used as the row_num argument in INDEX(), which returns the value in the 14th column in the lookup range.

For the player’s first name, we simply change 14 to 15:

=INDEX(Master.$B$2:$P$19106,MATCH(A2,Master.$B$2:$B$19106,0), 15)

Result:

3.3 Lookup in R

The easisest way to do lookups in R, if you are familiar with SQL, might be to use the sqldf package. To look up the teams’ names on the teams1980 table, we can do an inner join on teamID between batting1980 and teams1980.

library(sqldf)
# Doing a lookup in R via a JOIN of batting1980 and teams1980 ON teamID
batting1980_join <- sqldf("SELECT batting1980.*, teams1980.name AS teamName
                           FROM batting1980
                           JOIN teams1980
                           ON batting1980.teamID = teams1980.teamID")

We can also do it using merge(), and then we wouln’t have to bring in any external libraries.

# https://stackoverflow.com/questions/24191497/left-join-only-selected-columns-in-r-with-the-merge-function
batting1980_join_merge <- merge(x = batting1980, y = teams1980[, c("teamID", "name")], by = "teamID")
# If we want to change the column "name" to "teamName" like we did above, 
# we have to do it separately, which is a bit cumbersome
# http://www.cookbook-r.com/Manipulating_data/Renaming_columns_in_a_data_frame/
names(batting1980_join_merge)[names(batting1980_join_merge) == "name"] <- "teamName"

Looking up the players’ full names in the master table using sqldf():

# Doing a lookup in R via a JOIN of batting1980 and master ON playerID
batting1980_join <- sqldf("SELECT batting1980_join.*, CONCAT(nameFirst, ' ', nameLast) AS fullName
                           FROM batting1980_join
                           JOIN master
                           ON batting1980_join.playerID = master.playerID")

We can do it using merge() as well, it just takes a couple more steps to do the same thing:

batting1980_join_merge <- merge(x = batting1980_join_merge, y = master[, c("playerID", "nameFirst", "nameLast")], by = "playerID")
# Creating the full name column
batting1980_join_merge$fullName <- paste(batting1980_join_merge$nameFirst, batting1980_join_merge$nameLast, sep = " ")
# Removing the separate nameFirst and Last columns
batting1980_join_merge <- within(batting1980_join_merge, rm(nameFirst, nameLast))

4.0 Pivot tables

4.1 Basics

Pivot tables allow us to “summarize, analyze, explore, and present” our data. Let’s take the batting table again. Suppose we want to find out how many home runs each team hit in 1980. Calc can group the players by team and sum up the number of home runs. Here’s the relevant data we would use for the pivot table.


After we select the data, we go to the “Data” menu and click on “Pivot Table -> Create…”“. The following dialog box pops up:


I have selected the team names as the rows and SUM of home runs as the fields. Here is a section of the resulting pivot table:


So it has computed the total home runs by team. Note that you can edit the layout of a pivot table by right-clicking anywhere inside it and selecting “Edit Layout…”.

We can click on the “Filter” button to select only some of the teams. For example, we can tell it to show only American League teams:

and the result:


4.2 Multiple row fields

We can also group at a higher hierarchy level by “stacking” fields in the Rows section of the layout. For example, we can group the teams by league:

resulting in:


4.3 Using the columns field

What if we want to sum home runs by teams and nationalities? We would put the teams (and the leagues, too, while we are at it) in the Rows field as before, then we put the players’ nationalities in the Columns field of the pivot table. First we have to bring over the players’ countries of birth from the master table (right) into the batting table (left), once again using INDEX-MATCH.

          

The syntax is:

=INDEX(Master.$B$2:$P$19106,MATCH(A2,Master.$B$2:$B$19106,0), 5)

That is, we are bringing over the 5th column, birthCountry, from the selected range.

Now that we have the country of birth in the batting table, we can add it to the Columns section of the pivot table:

and get the home runs grouped by league, team, and country:

I have filtered out some of the countries to save space. Also not shown: the pivot table adds up totals in both directions: at the righmost end is again the total number of home runs hit by each team, and at the bottom is the number of home runs hit by American players, Cuban players, etc. Pretty cool.

4.4 Using COUNT

The default summarizing function is Sum, but we can use others, such as Average or Count. Suppose we want to find out how many players bat right, left, or are ambidextrous, on each team. The batting preference for each field is also in the master table (right, below), so we once again need to use INDEX-MATCH to bring it over to the batting table (left, below) with INDEX-MATCH.

          

The syntax is:

=INDEX(Master.$B$2:$T$19106,MATCH(A2,Master.$B$2:$B$19106,0), 19)

That is, we are retrieving the 19th column counting from the B column, which is the T column, from the master table, and bringing it over to the batting table.

Then we set up the pivot table. We put the leagues (AL, NL) and the teams in the rows, the batting preference (Left, Right, Both) in the columns, and the COUNT of such preferences in the data field, so that Calc computes how many players bat left, right, or both, on each team.

The result:

4.5 Pivot tables in R

4.5.1 Summing up home runs by team

In R, we can use tapply() to summarize variables in a dataframe. For example, we can use it to add up the number of home runs by team, as we did earlier in the spreadsheet’s pivot table.

# To get the home run sums by team
homeruns_by_team <- tapply(batting1980_join$HR, batting1980_join$teamName, sum)
homeruns_by_team
       Atlanta Braves     Baltimore Orioles        Boston Red Sox     California Angels 
                  144                   156                   162                   106 
         Chicago Cubs     Chicago White Sox       Cincinnati Reds     Cleveland Indians 
                  107                    91                   113                    89 
       Detroit Tigers        Houston Astros    Kansas City Royals   Los Angeles Dodgers 
                  143                    75                   115                   148 
    Milwaukee Brewers       Minnesota Twins        Montreal Expos         New York Mets 
                  203                    99                   114                    61 
     New York Yankees     Oakland Athletics Philadelphia Phillies    Pittsburgh Pirates 
                  189                   137                   117                   116 
     San Diego Padres  San Francisco Giants      Seattle Mariners   St. Louis Cardinals 
                   67                    80                   104                   101 
        Texas Rangers     Toronto Blue Jays 
                  124                   126 

We can get the result into a dataframe:

teamNames <- names(homeruns_by_team)
# Using unname()
# https://stackoverflow.com/questions/15736719/how-do-i-extract-just-the-number-from-a-named-number-without-the-name
homeRunsByTeam <- as.vector(unname(homeruns_by_team))
homeruns_by_team_df <- data.frame("teamName" = teamNames, "HR" = homeRunsByTeam)
homeruns_by_team_df

Then we can do a join with the teams1980 table to get the league.

homeruns_by_team_df_league <- sqldf("SELECT teams1980.lgID, homeruns_by_team_df.*
                                   FROM homeruns_by_team_df
                                   JOIN
                                   teams1980
                                   ON homeruns_by_team_df.teamName = teams1980.name
                                   ORDER BY teams1980.lgID")
homeruns_by_team_df_league

4.5.2 Summing up home runs by team and country

Looking up the players’ countries of origin in the master table:

batting1980_join <- sqldf("SELECT batting1980_join.*, master.birthCountry
                           FROM batting1980_join
                           JOIN master
                           ON batting1980_join.playerID = master.playerID")

Tabulating home runs by country and team using tapply() again:

x <- with(batting1980_join, tapply(HR, list(teamName, birthCountry), FUN=sum))
y <- as.data.frame(x)
# Converting row names to a column and removing the row names afterwards
y$teamName <- rownames(y)
rownames(y) <- NULL
# Replacing NA's with 0's. An NA means a team did not have any player of that nationality.
y[is.na(y)] <- 0
# Showing only some of them to save space
keeps <- c("teamName", "USA", "Cuba", "D.R.", "P.R.", "Panama")
y[keeps]

4.5.3 Counting batting preferences by team

Now we can try to replicate the batting preferences pivot table we did earlier here in R. Start by looking up the players’ batting preferences in the master table:

batting1980_join <- sqldf("SELECT batting1980_join.*, master.bats
                           FROM batting1980_join
                           JOIN master
                           ON batting1980_join.playerID = master.playerID")

Then we can count batting preferences by team with table():

batting_pref_table <- with(batting1980_join, table(teamName, bats))
# Convert the table to dataframe
# https://stackoverflow.com/questions/10758961/how-to-convert-a-table-to-a-data-frame
batting_pref_df <- as.data.frame.matrix(batting_pref_table)
# Converting rownames (the team names) to a column, and removing the rownames
batting_pref_df$teamName <- rownames(batting_pref_df)
rownames(batting_pref_df) <- NULL
# Re-ordering columns
batting_pref_df <- batting_pref_df[c(4, 1:3)]
batting_pref_df
LS0tDQp0aXRsZTogIkxvb2t1cCB0YWJsZXMgYW5kIHBpdm90IHRhYmxlcyBpbiBzcHJlYWRzaGVldHMgKGFuZCBSKSINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAnNScNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNQ0KICAgIHRvY19mbG9hdDogeWVzDQotLS0NCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KYm9keSwgdGQgew0KICAgZm9udC1zaXplOiAxOHB4Ow0KfQ0KaDEgew0KICBmb250LXNpemU6IDMycHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KaDIgew0KICBmb250LXNpemU6IDI4cHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KaDMgew0KICBmb250LXNpemU6IDI0cHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KaDQgew0KICBmb250LXNpemU6IDIwcHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KY29kZS5yew0KICBmb250LXNpemU6IDE2cHg7DQp9DQpwcmUgew0KICBmb250LXNpemU6IDE2cHgNCn0NCjwvc3R5bGU+DQoNCiMjIDEuMCBJbnRyb2R1Y3Rpb24NCg0KRXhjZWwgYW5kIHR3byBvZiBpdHMgbW9zdCBwb3B1bGFyIGZyZWUgY291bnRlcnBhcnRzLCBHb29nbGUgU2hlZXRzIGFuZCBDYWxjLCBvZmZlciBsb29rdXAgZnVuY3Rpb25zIGFuZCBwaXZvdCB0YWJsZXMuIEluIHRoaXMgbm90ZWJvb2sgd2UgdGFrZSBhIGxvb2sgYXQgd2hhdCB0aGV5IGNhbiBkbyBmb3IgdXMgYW5kIHdoYXQgYXJlIHRoZWlyIGVxdWl2YWxlbnRzIGluIFIuDQoNCiMjIDIuMCBHZXR0aW5nIHNvbWUgZGF0YS4NCg0KSSB3YW50ZWQgdG8gdXNlIHRhYmxlcyBmcm9tIHRoZSBbTGFobWFuIGRhdGFiYXNlXShodHRwOi8vd3d3LnNlYW5sYWhtYW4uY29tL2Jhc2ViYWxsLWFyY2hpdmUvc3RhdGlzdGljcy8pLCB3aGljaCBjb250YWlucyBNYWpvciBMZWFndWUgQmFzZWJhbGwncyBzdGF0aXN0aWNzIGZyb20gMTg3MSghKSB0byAyMDE2LiBJIGRvd25sb2FkZWQgdGhlIFNRTCBkYXRhYmFzZSBhbmQgc2V0dXAgYSBzZXJ2ZXIgdXNpbmcgW1hBTVBQXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9YQU1QUCkuIEFmdGVyIHlvdSBpbnN0YWxsIGl0LCBmaXJlIHVwIHRoZSBYQU1QUCBDb250cm9sIFBhbmVsIGFuZCBzdGFydCB0aGUgQXBhY2hlIGFuZCBNeVNRTCBtb2R1bGVzLg0KDQohW10oWEFNUFBfQ29udHJvbF9QYW5lbC5QTkcgIlhBTVBQIENvbnRyb2wgUGFuZWwiKQ0KPGJyPg0KDQpUaGUgTGFobWFuIGRhdGFiYXNlIGlzIH42NSBNQiwgc28gSSBoYWQgdG8gY2hhbmdlIHNvbWUgb2YgdGhlIHNldHRpbmdzIGluIHRoZSBwaHAuaW5pIGZpbGUgdG8gYWxsb3cgZm9yIGxhcmdlciBmaWxlcyB0byBiZSB1cGxvYWRlZCB0byB0aGUgc2VydmVyLiBUbyBtYWtlIHRoZSBjaGFuZ2VzLCBjbGljayBvbiB0aGUgQ29uZmlnIGJ1dHRvbiBvbiB0aGUgQXBhY2hlIG1vZHVsZSByb3cgYW5kIHNlbGVjdCBwaHAuaW5pDQoNCk9uY2UgdGhlIHNlcnZlciBpcyB1cCBhbmQgcnVubmluZywgd2UgY2FuIGNvbm5lY3QgdG8gaXQgdXNpbmcgUiB2aWEgdGhlIFtSTXlTUUxdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9STXlTUUwvUk15U1FMLnBkZikgcGFja2FnZS4gSGVyZWluIHdlIGxvYWQgdGhlIFJNeVNRTCBwYWNrYWdlLCBjb25uZWN0IHRvIHRoZSBkYXRhYmFzZSwgYW5kIGxpc3QgdGhlIHRhYmxlcyBpbiBpdDoNCg0KYGBge3J9DQpsaWJyYXJ5KFJNeVNRTCkNCmRydiA8LSBkYkRyaXZlcigiTXlTUUwiKQ0KZGIgPC0gZGJDb25uZWN0KGRydiwgdXNlcj0icm9vdCIsIGRibmFtZSA9ICJsYWhtYW4yMDE2IiwgaG9zdD0iMTI3LjAuMC4xIikNCmRiTGlzdFRhYmxlcyhkYikNCmBgYA0KDQpVc2luZyBSTXlTUUwsIHdlIGNhbiBleGVjdXRlIGEgU1FMIHF1ZXJ5IGFuZCBzdG9yZSB0aGUgdGFibGUgaXQgcmV0dXJucyBhcyBhIGRhdGFmcmFtZSBpbnNpZGUgUi4gV2UgY2FuIHRoZW4gdGFrZSB0aGUgZGF0YWZyYW1lIGFuZCB3cml0ZSBpdCB0byBhIGNzdiBmaWxlLiBMZXQncyBkbyB0aGF0IHdpdGggdGhlIGluZGl2aWR1YWwgYmF0dGluZyBzdGF0aXN0aWNzIGZvciB0aGUgWzE5ODAgTUxCIHNlYXNvbl0oaHR0cDovL3d3dy5lc3BuLmNvbS9tbGIvaGlzdG9yeS9zZWFzb24vXy95ZWFyLzE5ODApLiANCg0KYGBge3J9DQpiYXR0aW5nMTk4MCA8LSBkYkdldFF1ZXJ5KGRiLCJTRUxFQ1QgKg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBCYXR0aW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVSRSB5ZWFySUQgPSAxOTgwIikNCg0Kd3JpdGUuY3N2KGJhdHRpbmcxOTgwLCBmaWxlID0gImJhdHRpbmcxOTgwLmNzdiIpDQpgYGANCg0KTm8gb3V0cHV0IGlzIHJldHVybmVkLCBidXQgdGhlIGNzdiBmaWxlIGhhcyBiZWVuIHdyaXR0ZW4uDQo8YnI+DQoNClRoZSBiYXR0aW5nIHRhYmxlIGhhcyB0d28gZmllbGRzIG5hbWVkIHBsYXllcklEIGFuZCB0ZWFtSUQsIHdoaWNoIGFyZSBpZGVudGlmaWVycyBmb3IgdGhlIHBsYXllcnMgYW5kIHRlYW1zIHJhdGhlciB0aGFuIHRoZWlyIGFjdHVhbCBuYW1lcy4gVGhlIHRlYW1zIGFuZCB0aGUgbWFzdGVyIHRhYmxlcyBoYXZlIHRoZSByZWFsIG5hbWVzIGFsb25nIHdpdGggdGhlIGlkZW50aWZpZXJzLiBXZSBjYW4gZG8gc2ltaWxhcmx5IGZvciBib3RoIG9mIHRoZXNlIHRhYmxlcy4gRmlyc3QgdGhlIHRlYW1zJyB0YWJsZSwgZmlsdGVyZWQgdG8gaW5jbHVkZSBvbmx5IHRlYW1zIHRoYXQgcGFydGljaXBhdGVkIGluIHRoZSAxOTgwIE1MQiBzZWFzb24uDQoNCmBgYHtyfQ0KdGVhbXMxOTgwIDwtIGRiR2V0UXVlcnkoZGIsIlNFTEVDVCAqDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIHRlYW1zDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVSRSB5ZWFySUQgPSAxOTgwIikNCg0Kd3JpdGUuY3N2KHRlYW1zMTk4MCwgZmlsZSA9ICJ0ZWFtczE5ODAuY3N2IikNCmBgYA0KDQpUaGUgbWFzdGVyIHRhYmxlIGhhcyBhbGwgdGhlIHBsYXllcnMnIG5hdHVyYWwgaW5mb3JtYXRpb24sIGluY2x1ZGluZyBmaXJzdCBhbmQgbGFzdCBuYW1lLCBhbG9uZyB3aXRoIHRoZWlyIGlkZW50aWZpZXJzLiBUaGlzIHRhYmxlIGhhcyAqZXZlcnkqIHBsYXllciB0aGF0IGhhcyBldmVyIHBsYXllZCBpbiB0aGUgbWFqb3IgbGVhZ3Vlcy4gVGhlcmUncyBubyB3YXkgdG8gZmlsdGVyIGl0IGJ5IHllYXIsIHNvIHdlIGhhdmUgdG8gd3JpdGUgdGhlIHdob2xlIHRoaW5nIHRvIHRoZSBjc3YgZmlsZQ0KDQpgYGB7cn0NCm1hc3RlciA8LSBkYkdldFF1ZXJ5KGRiLCJTRUxFQ1QgKg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBtYXN0ZXIiKQ0KDQp3cml0ZS5jc3YobWFzdGVyLCBmaWxlID0gIm1hc3Rlci5jc3YiKQ0KYGBgDQoNCk5vdyB3ZSBjYW4gbG9hZCB0aGUgdGFibGVzIGFzIHNlcGFyYXRlIHRhYnMgaW4gQ2FsYy4NCg0KIyMgMy4wIExvb2t1cCBmdW5jdGlvbnMNCg0KIyMjIDMuMSBgVkxPT0tVUCgpYA0KDQpMaWtlIEV4Y2VsLCBDYWxjbCBoYXMgYSBbYFZMT09LVVAoKWBdKGh0dHBzOi8vc3VwcG9ydC5vZmZpY2UuY29tL2VuLXVzL2FydGljbGUvdmxvb2t1cC1mdW5jdGlvbi0wYmJjODA4My0yNmZlLTQ5NjMtOGFiOC05M2ExOGFkMTg4YTEpIGZ1bmN0aW9uIHRoYXQgYWxsb3dzIHVzZXJzIHRvIGxvb2sgZm9yIGFuIGl0ZW0gaW4gYSB0YWJsZS4gSXRzIHN5bnRheCBpcyBhcyBmb2xsb3dzOg0KDQpgVkxPT0tVUChsb29rdXBfdmFsdWUsIGxvb2t1cF9yYW5nZSwgcmV0dXJuX3ZhbHVlLCBtYXRjaF90eXBlKWANCg0KLSBgbG9va3VwX3ZhbHVlYCBSZXF1aXJlZC4gKldoYXQqIHlvdSBhcmUgbG9va2luZyBmb3IuDQotIGBsb29rdXBfcmFuZ2VgIFJlcXVpcmVkLiAqV2hlcmUqIHlvdSB3YW50IHRvIGxvb2sgZm9yIGl0LCBpLmUuLCBhIHRhYmxlLg0KLSBgcmV0dXJuX3ZhbHVlYCBSZXF1aXJlZC4gV2hhdCB5b3Ugd2FudCAqcmV0dXJuZWQqLCB0aGF0IGlzLCB0aGUgY29sdW1uIHZhbHVlIGluIHRoZSBgbG9va3VwX3JhbmdlYCB5b3Ugd2FudCByZXR1cm5lZC4gSXQgaXMgYSBudW1iZXIsIHNvIGlmIHRoZSBgbG9va3VwX3JhbmdlYCBoYXMgNCBjb2x1bW5zIGFuZCB5b3Ugd2FudCB0aGUgM3JkIGNvbHVtbiByZXR1cm5lZCwgeW91IHdvdWxkIHB1dCAzIGluIHlvdXIgcmV0dXJuIHZhbHVlLg0KLSBgbWF0Y2hfdHlwZWAgT3B0aW9uYWwuIGBUUlVFYCAoZGVmYXVsdCkgaWYgeW91IGFyZSBvayB3aXRoIGEgcGFydGlhbCBtYXRjaCwgb3IgYEZBTFNFYCBpZiBvbmx5IGFuIGV4YWN0IG1hdGNoIHdpbGwgZG8uIEkgdGhpbmsgd2Ugd291bGQgYWxtb3N0IGFsd2F5cyB3YW50IGBGQUxTRWAsIGkuZS4sIGFuIGV4YWN0IG1hdGNoLg0KDQo8YnI+DQoNCkhlcmUgYXJlIHJlbGV2YW50IHNlZ21lbnRzIG9mIHRoZSBiYXR0aW5nIHRhYmxlIChsZWZ0KSBhbmQgdGhlIHRlYW1zIHRhYmxlIChyaWdodCkNCg0KIVtdKGJhdHRpbmdfdGVhbS5QTkcgImJhdHRpbmcgdGVhbSIpIFwgXCBcIFwgXCBcIFwgXCBcIFwgIVtdKHRlYW1zMTk4MC5QTkcgInRlYW1zIikNCg0KV2UgdXNlIGBWTE9PS1VQKClgIHRvIGxvb2sgdXAgdGhlIHRlYW1JRCAoaW4gdGhlICJEIiIgY29sdW1uIGluIHRoZSBiYXR0aW5nIHRhYmxlKSBpbiB0aGUgcmVsZXZhbnQgcG9ydGlvbiBvZiB0aGUgdGVhbXMgdGFibGUgKGNvbHVtbnMgIkQiIHRocm91Z2ggIkFQIiksIGFuZCByZXR1cm4gdGhlIHRlYW1zJyBuYW1lcywgdGhlIDM5dGggY29sdW1uKCJBUCIpLCBjb3VudGluZyBmcm9tIHRoZSAiRCIiIGNvbHVtbi4NCg0KYD1WTE9PS1VQKEQyLCBUZWFtcy4kRCQyOiRBUCQyNywzOSwgMClgDQoNCmFuZCBnZXQ6DQoNCiFbXSh2bG9va3VwX3Jlc3VsdC5QTkcgInZsb29rdXAiKQ0KDQojIyMgMy4yIGBJTkRFWC1NQVRDSGANCg0KYFZMT09LVVAoKWAgd29ya3Mgb2ssIGJ1dCBpdCBoYXMgdHdvIGRpc2FkdmFudGFnZXM6IGl0J3Mgc2xvdywgYW5kIGl0IG9ubHkgc2VhcmNoZXMgZm9yIGBsb29rdXBfdmFsdWVgIGluIHRoZSBsZWZ0bW9zdCBjb2x1bW4gb2YgYGxvb2t1cF9yYW5nZWAuIFdlIG92ZXJjb21lIGJvdGggb2YgdGhlc2UgbGltaXRhdGlvbnMgYnkgY29tYmluaW5nIGBJTkRFWCgpYCBhbmQgYE1BVENIKClgLg0KDQojIyMjIDMuMi4xIGBNQVRDSCgpYA0KDQpMZXQncyBsb29rIGF0IHRoZSBzeW50YXggZm9yIGBNQVRDSCgpYA0KDQpgTUFUQ0gobG9va3VwX3ZhbHVlLCBsb29rdXBfcmFuZ2UsIG1hdGNoX3R5cGUpIGANCg0KU2VhcmNoZXMgZm9yIGxvb2t1cF92YWx1ZSBhbmQgcmV0dXJucyB0aGUgcmVsYXRpdmUgcG9zaXRpb24gb2YgdGhlIGl0ZW0gaW4gdGhlIGxvb2t1cF9yYW5nZS4NCg0KLSBgbG9va3VwX3ZhbHVlYCBSZXF1aXJlZC4gV2hhdCB5b3UgYXJlIGxvb2tpbmcgZm9yLg0KDQotIGBsb29rdXBfcmFuZ2VgIFJlcXVpcmVkLiBXaGVyZSB5b3Ugd2FudCB0byBsb29rIGZvciBpdC4gSXQgaGFzIHRvIGJlIGEgY29sdW1uIG9yIGEgcm93Lg0KDQotIGBtYXRjaF90eXBlYCBPcHRpb25hbC4gU2ltaWxhcmx5IHRvIGBWTE9PS1VQKClgLCBgMWAgKGRlZmF1bHQpLCBpZiB5b3UgYXJlIG9rIHdpdGggcGFydGlhbCBtYXRjaGVzLCBvciBgMGAgaWYgeW91IHdhbnQgYW4gZXhhY3QgbWF0Y2guIFRoZXJl4oCZcyBhbHNvIGEgLTEgb3B0aW9uIHRoYXQgcmV0dXJucyB0aGUgcG9zaXRpb24gb2YgdGhlIHNtYWxsZXN0IHZhbHVlIHRoYXQgaXMgZ3JlYXRlciBvciBlcXVhbCB0aGFuIHRoZSBsb29rdXBfdmFsdWUuDQoNCklmIGBsb29rdXBfcmFuZ2VgIGlzIGEgY29sdW1uLCBgTUFUQ0goKWAgcmV0dXJucyB0aGUgcm93IG51bWJlciB3aGVyZSBgbG9va3VwX3ZhbHVlYCB3YXMgZm91bmQuDQoNCiMjIyMgMy4yLjIgYElOREVYKClgDQoNCk5vdyBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgc3ludGF4IGZvciBgSU5ERVgoKWAuDQoNCmBJTkRFWChsb29rdXBfcmFuZ2UsIHJvd19udW0sIGNvbHVtbl9udW0pYA0KDQotIGBsb29rdXBfcmFuZ2VgIFJlcXVpcmVkLiBXaGVyZSB5b3Ugd2FudCB0byBsb29rIGZvciBpdCwgaS5lLiwgYSB0YWJsZS4NCg0KLSBgcm93X251bWAgUmVxdWlyZWQgKGlmIGBjb2x1bW5fbnVtYCBpcyBvbWl0dGVkKS4gU2VsZWN0cyB0aGUgcm93IGluIHRoZSBsb29rdXBfcmFuZ2UgZnJvbSB3aGljaCB0byByZXR1cm4gYSB2YWx1ZS4gSWYgcm93X251bSBpcyBvbWl0dGVkLCB0aGVuIGNvbHVtbl9udW0gaXMgcmVxdWlyZWQuDQoNCi0gYGNvbHVtbl9udW1gIFJlcXVpcmVkIChpZiBgcm93X251bWAgaXMgb21pdHRlZCkuIFNlbGVjdHMgdGhlIGNvbHVtbiBpbiB0aGUgbG9va3VwX3JhbmdlIGZyb20gd2hpY2ggdG8gcmV0dXJuIGEgdmFsdWUuIElmIGNvbHVtbl9udW0gaXMgb21pdHRlZCwgdGhlbiByb3dfbnVtIGlzIHJlcXVpcmVkLg0KDQotIElmIHRoZSBhcnJheSBoYXMgbW9yZSB0aGFuIG9uZSBjb2x1bW4gYW5kIG1vcmUgdGhhbiBvbmUgcm93LCBhbmQgb25seSByb3dfbnVtIG9yIGNvbHVtbl9udW0gaXMgdXNlZCwgSU5ERVgoKXJldHVybnMgYW4gYXJyYXkgb2YgYW4gZW50aXJlIHJvdyBvciBjb2x1bW4sIGFuZCBwbGFjZXMgaXQgaW4gdGhlIGNlbGwuIA0KDQpgSU5ERVgoKWAgcmV0dXJucyB0aGUgaXRlbSBsb2NhdGVkIGF0IChgcm93X251bWAsIGBjb2x1bW5fbnVtYCkgaW4gdGhlIGBsb29rdXBfcmFuZ2VgLg0KDQojIyMjIDMuMi4zIGBJTkRFWC1NQVRDSGAgY29tYm8NCg0KYElOREVYKClgIGNhbiBiZSB1c2VkIGluIGNvbmp1bmN0aW9uIHdpdGggYE1BVENIKClgIGFzIGEgZmFzdGVyLCBsZXNzIGxpbWl0ZWQgYWx0ZXJuYXRpdmUgdG8gYFZMT09LVVAoKWAgYnkgdXNpbmcgYE1BVENIKClgIGFzIHRoZSByb3dfbnVtIGFyZ3VtZW50IG9mIGBJTkRFWCgpYC4NCg0KSGVyZSBhcmUgYWdhaW4gdGhlIHJlbGV2YW50IHNlZ21lbnRzIG9mIHRoZSBiYXR0aW5nIHRhYmxlIChsZWZ0KSBhbmQgdGhlIHRlYW1zIHRhYmxlIChyaWdodCkNCg0KIVtdKGJhdHRpbmdfdGVhbS5QTkcgImJhdHRpbmcgdGVhbSIpIFwgXCBcIFwgXCBcIFwgXCBcIFwgIVtdKHRlYW1zMTk4MC5QTkcgInRlYW1zIikNCg0KV2UgY2FuIHVzZSBgTUFUQ0goKWAgdG8gZmluZCB0aGUgcm93IG51bWJlciB3aGVyZSB0aGUgdGVhbUlEIGlzIGZvdW5kIGluIHRoZSB0ZWFtcyB0YWJsZSwgYW5kIHRoZW4gdXNlIHRoYXQgYXMgdGhlIGByb3dfbnVtYCBwYXJhbWV0ZXIgb2YgYElOREVYKClgLg0KDQpgPUlOREVYKFRlYW1zLiREJDI6JEFQJDI3LE1BVENIKEQyLFRlYW1zLiREJDI6JEQkMjcsMCksIDM5KWANCg0KYW5kIGdldDoNCg0KIVtdKGluZGV4LW1hdGNoX3Jlc3VsdF8xLlBORyAiaW5kZXgtbWF0Y2hfMSIpDQoNCjxicj4NCg0KDQpOb3cgdXNlIGBJTkRFWC1NQVRDSGAgdG8gZmluZCB0aGUgcGxheWVycycgZnVsbCBuYW1lcyB1c2luZyB0aGUgcGxheWVycycgcGxheWVySUQgZmllbGRzIGluIHRoZSBiYXR0aW5nIHRhYmxlLg0KDQpIZXJlIGFyZSB0aGUgcmVsZXZhbnQgc2VnbWVudHMgb2YgdGhlIGJhdHRpbmcgdGFibGUgKGxlZnQpIGFuZCB0aGUgbWFzdGVyIHRhYmxlIChyaWdodCkNCg0KIVtdKGJhdHRpbmdfcGxheWVySUQuUE5HICJiYXR0aW5nIHBsYXllciIpIFwgXCBcIFwgXCBcIFwgXCBcIFwgIVtdKG1hc3Rlci5QTkcgIm1hc3RlciIpDQoNCkhlcmUncyB0aGUgYElOREVYLU1BVENIYCBpbmRleCBmb3IgdGhlIHBsYXllcidzIGxhc3QgbmFtZToNCg0KYD1JTkRFWChNYXN0ZXIuJEIkMjokUCQxOTEwNixNQVRDSChBMixNYXN0ZXIuJEIkMjokQiQxOTEwNiwwKSwgMTQpYA0KDQpJbiB0aGlzIGNhc2UsIGBNQVRDSCgpYCByZXR1cm5zIHRoZSByb3cgbnVtYmVyIGluIHRoZSBtYXN0ZXIgdGFibGUgd2hlcmUgdGhlIHBsYXllcklEIHdhcyBmb3VuZC4gVGhhdCByb3cgbnVtYmVyIGlzIHVzZWQgYXMgdGhlIGByb3dfbnVtYCBhcmd1bWVudCBpbiBgSU5ERVgoKWAsIHdoaWNoIHJldHVybnMgdGhlIHZhbHVlIGluIHRoZSAxNHRoIGNvbHVtbiBpbiB0aGUgbG9va3VwIHJhbmdlLg0KDQpGb3IgdGhlIHBsYXllcidzIGZpcnN0IG5hbWUsIHdlIHNpbXBseSBjaGFuZ2UgMTQgdG8gMTU6DQoNCmA9SU5ERVgoTWFzdGVyLiRCJDI6JFAkMTkxMDYsTUFUQ0goQTIsTWFzdGVyLiRCJDI6JEIkMTkxMDYsMCksIDE1KWANCg0KUmVzdWx0Og0KDQohW10oaW5kZXgtbWF0Y2hfcmVzdWx0XzIuUE5HICJpbmRleC1tYXRjaF8yIikNCg0KIyMjIDMuMyBMb29rdXAgaW4gUg0KDQpUaGUgZWFzaXNlc3Qgd2F5IHRvIFtkbyBsb29rdXBzIGluIFJdKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzE1MzAzMjgzL2hvdy10by1kby12bG9va3VwLWFuZC1maWxsLWRvd24tbGlrZS1pbi1leGNlbC1pbi1yP25vcmVkaXJlY3Q9MSZscT0xKSwgaWYgeW91IGFyZSBmYW1pbGlhciB3aXRoIFNRTCwgbWlnaHQgYmUgdG8gdXNlIHRoZSBbc3FsZGYgcGFja2FnZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NxbGRmL3NxbGRmLnBkZikuIFRvIGxvb2sgdXAgdGhlIHRlYW1zJyBuYW1lcyBvbiB0aGUgdGVhbXMxOTgwIHRhYmxlLCB3ZSBjYW4gZG8gYW4gW2lubmVyIGpvaW5dKGh0dHBzOi8vd3d3Lnczc2Nob29scy5jb20vc3FsL3NxbF9qb2luX2lubmVyLmFzcCkgb24gYHRlYW1JRGAgYmV0d2VlbiBiYXR0aW5nMTk4MCBhbmQgdGVhbXMxOTgwLg0KDQpgYGB7cn0NCmxpYnJhcnkoc3FsZGYpDQojIERvaW5nIGEgbG9va3VwIGluIFIgdmlhIGEgSk9JTiBvZiBiYXR0aW5nMTk4MCBhbmQgdGVhbXMxOTgwIE9OIHRlYW1JRA0KYmF0dGluZzE5ODBfam9pbiA8LSBzcWxkZigiU0VMRUNUIGJhdHRpbmcxOTgwLiosIHRlYW1zMTk4MC5uYW1lIEFTIHRlYW1OYW1lDQogICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIGJhdHRpbmcxOTgwDQogICAgICAgICAgICAgICAgICAgICAgICAgICBKT0lOIHRlYW1zMTk4MA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgT04gYmF0dGluZzE5ODAudGVhbUlEID0gdGVhbXMxOTgwLnRlYW1JRCIpDQpgYGANCg0KV2UgY2FuIGFsc28gZG8gaXQgdXNpbmcgW2BtZXJnZSgpYF0oIyBEb2luZyBhIGxvb2t1cCBpbiBSIHZpYSBhIEpPSU4gb2YgYmF0dGluZzE5ODAgYW5kIHRlYW1zMTk4MCBPTiB0ZWFtSUQpLCBhbmQgdGhlbiB3ZSB3b3Vsbid0IGhhdmUgdG8gYnJpbmcgaW4gYW55IGV4dGVybmFsIGxpYnJhcmllcy4gDQoNCmBgYHtyfQ0KIyBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yNDE5MTQ5Ny9sZWZ0LWpvaW4tb25seS1zZWxlY3RlZC1jb2x1bW5zLWluLXItd2l0aC10aGUtbWVyZ2UtZnVuY3Rpb24NCmJhdHRpbmcxOTgwX2pvaW5fbWVyZ2UgPC0gbWVyZ2UoeCA9IGJhdHRpbmcxOTgwLCB5ID0gdGVhbXMxOTgwWywgYygidGVhbUlEIiwgIm5hbWUiKV0sIGJ5ID0gInRlYW1JRCIpDQojIElmIHdlIHdhbnQgdG8gY2hhbmdlIHRoZSBjb2x1bW4gIm5hbWUiIHRvICJ0ZWFtTmFtZSIgbGlrZSB3ZSBkaWQgYWJvdmUsIA0KIyB3ZSBoYXZlIHRvIGRvIGl0IHNlcGFyYXRlbHksIHdoaWNoIGlzIGEgYml0IGN1bWJlcnNvbWUNCiMgaHR0cDovL3d3dy5jb29rYm9vay1yLmNvbS9NYW5pcHVsYXRpbmdfZGF0YS9SZW5hbWluZ19jb2x1bW5zX2luX2FfZGF0YV9mcmFtZS8NCm5hbWVzKGJhdHRpbmcxOTgwX2pvaW5fbWVyZ2UpW25hbWVzKGJhdHRpbmcxOTgwX2pvaW5fbWVyZ2UpID09ICJuYW1lIl0gPC0gInRlYW1OYW1lIg0KYGBgDQoNCg0KTG9va2luZyB1cCB0aGUgcGxheWVycycgZnVsbCBuYW1lcyBpbiB0aGUgbWFzdGVyIHRhYmxlIHVzaW5nIGBzcWxkZigpYDoNCg0KYGBge3J9DQojIERvaW5nIGEgbG9va3VwIGluIFIgdmlhIGEgSk9JTiBvZiBiYXR0aW5nMTk4MCBhbmQgbWFzdGVyIE9OIHBsYXllcklEDQpiYXR0aW5nMTk4MF9qb2luIDwtIHNxbGRmKCJTRUxFQ1QgYmF0dGluZzE5ODBfam9pbi4qLCBDT05DQVQobmFtZUZpcnN0LCAnICcsIG5hbWVMYXN0KSBBUyBmdWxsTmFtZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBiYXR0aW5nMTk4MF9qb2luDQogICAgICAgICAgICAgICAgICAgICAgICAgICBKT0lOIG1hc3Rlcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgT04gYmF0dGluZzE5ODBfam9pbi5wbGF5ZXJJRCA9IG1hc3Rlci5wbGF5ZXJJRCIpDQpgYGANCg0KV2UgY2FuIGRvIGl0IHVzaW5nIGBtZXJnZSgpYCBhcyB3ZWxsLCBpdCBqdXN0IHRha2VzIGEgY291cGxlIG1vcmUgc3RlcHMgdG8gZG8gdGhlIHNhbWUgdGhpbmc6DQoNCmBgYHtyfQ0KYmF0dGluZzE5ODBfam9pbl9tZXJnZSA8LSBtZXJnZSh4ID0gYmF0dGluZzE5ODBfam9pbl9tZXJnZSwgeSA9IG1hc3RlclssIGMoInBsYXllcklEIiwgIm5hbWVGaXJzdCIsICJuYW1lTGFzdCIpXSwgYnkgPSAicGxheWVySUQiKQ0KIyBDcmVhdGluZyB0aGUgZnVsbCBuYW1lIGNvbHVtbg0KYmF0dGluZzE5ODBfam9pbl9tZXJnZSRmdWxsTmFtZSA8LSBwYXN0ZShiYXR0aW5nMTk4MF9qb2luX21lcmdlJG5hbWVGaXJzdCwgYmF0dGluZzE5ODBfam9pbl9tZXJnZSRuYW1lTGFzdCwgc2VwID0gIiAiKQ0KIyBSZW1vdmluZyB0aGUgc2VwYXJhdGUgbmFtZUZpcnN0IGFuZCBMYXN0IGNvbHVtbnMNCmJhdHRpbmcxOTgwX2pvaW5fbWVyZ2UgPC0gd2l0aGluKGJhdHRpbmcxOTgwX2pvaW5fbWVyZ2UsIHJtKG5hbWVGaXJzdCwgbmFtZUxhc3QpKQ0KYGBgDQoNCg0KIyMgNC4wIFBpdm90IHRhYmxlcw0KDQojIyMgNC4xIEJhc2ljcw0KDQpbUGl2b3QgdGFibGVzXShodHRwczovL3N1cHBvcnQub2ZmaWNlLmNvbS9lbi11cy9hcnRpY2xlL2NyZWF0ZS1hLXBpdm90dGFibGUtdG8tYW5hbHl6ZS13b3Jrc2hlZXQtZGF0YS1hOWE4NDUzOC1iZmU5LTQwYTktYThlOS1mOTkxMzQ0NTY1NzYpIGFsbG93IHVzIHRvICJzdW1tYXJpemUsIGFuYWx5emUsIGV4cGxvcmUsIGFuZCBwcmVzZW50IiBvdXIgZGF0YS4gTGV0J3MgdGFrZSB0aGUgYmF0dGluZyB0YWJsZSBhZ2Fpbi4gU3VwcG9zZSB3ZSB3YW50IHRvIGZpbmQgb3V0IGhvdyBtYW55IGhvbWUgcnVucyBlYWNoIHRlYW0gaGl0IGluIDE5ODAuIENhbGMgY2FuIGdyb3VwIHRoZSBwbGF5ZXJzIGJ5IHRlYW0gYW5kIHN1bSB1cCB0aGUgbnVtYmVyIG9mIGhvbWUgcnVucy4gSGVyZSdzIHRoZSByZWxldmFudCBkYXRhIHdlIHdvdWxkIHVzZSBmb3IgdGhlIHBpdm90IHRhYmxlLg0KDQohW10ocGl2b3RfdGFibGVfMV9kYXRhLlBORyAicGl2b3QgdGFibGUgMSBkYXRhIikNCg0KPGJyPg0KDQpBZnRlciB3ZSBzZWxlY3QgdGhlIGRhdGEsIHdlIGdvIHRvIHRoZSAiRGF0YSIgbWVudSBhbmQgY2xpY2sgb24gIlBpdm90IFRhYmxlIC0+IENyZWF0ZS4uLiIiLiBUaGUgZm9sbG93aW5nIGRpYWxvZyBib3ggcG9wcyB1cDoNCg0KIVtdKHBpdm90X3RhYmxlXzFfbGF5b3V0LlBORyAicGl2b3QgdGFibGUgMSBsYXlvdXQiKQ0KDQo8YnI+DQoNCkkgaGF2ZSBzZWxlY3RlZCB0aGUgdGVhbSBuYW1lcyBhcyB0aGUgcm93cyBhbmQgU1VNIG9mIGhvbWUgcnVucyBhcyB0aGUgZmllbGRzLiBIZXJlIGlzIGEgc2VjdGlvbiBvZiB0aGUgcmVzdWx0aW5nIHBpdm90IHRhYmxlOg0KDQohW10ocGl2b3RfdGFibGVfMV9yZXN1bHQuUE5HICJwaXZvdCB0YWJsZSAxIHJlc3VsdCIpDQoNCjxicj4NCg0KU28gaXQgaGFzIGNvbXB1dGVkIHRoZSB0b3RhbCBob21lIHJ1bnMgYnkgdGVhbS4gTm90ZSB0aGF0IHlvdSBjYW4gZWRpdCB0aGUgbGF5b3V0IG9mIGEgcGl2b3QgdGFibGUgYnkgcmlnaHQtY2xpY2tpbmcgYW55d2hlcmUgaW5zaWRlIGl0IGFuZCBzZWxlY3RpbmcgIkVkaXQgTGF5b3V0Li4uIi4gDQoNCldlIGNhbiBjbGljayBvbiB0aGUgIkZpbHRlciIgYnV0dG9uIHRvIHNlbGVjdCBvbmx5IHNvbWUgb2YgdGhlIHRlYW1zLiBGb3IgZXhhbXBsZSwgd2UgY2FuIHRlbGwgaXQgdG8gc2hvdyBvbmx5IEFtZXJpY2FuIExlYWd1ZSB0ZWFtczoNCg0KIVtdKHBpdm90X3RhYmxlXzFfZmlsdGVyLlBORyAicGl2b3QgdGFibGUgMSBmaWx0ZXIgMiIpDQoNCmFuZCB0aGUgcmVzdWx0Og0KDQohW10ocGl2b3RfdGFibGVfMV9yZXN1bHRfMi5QTkcgInBpdm90IHRhYmxlIDEgcmVzdWx0IDIiKQ0KDQo8YnI+DQoNCiMjIyA0LjIgTXVsdGlwbGUgcm93IGZpZWxkcw0KDQpXZSBjYW4gYWxzbyBncm91cCBhdCBhIGhpZ2hlciBoaWVyYXJjaHkgbGV2ZWwgYnkgInN0YWNraW5nIiBmaWVsZHMgaW4gdGhlIFJvd3Mgc2VjdGlvbiBvZiB0aGUgbGF5b3V0LiBGb3IgZXhhbXBsZSwgd2UgY2FuIGdyb3VwIHRoZSB0ZWFtcyBieSBsZWFndWU6DQoNCiFbXShwaXZvdF90YWJsZV8xX2xheW91dF8yLlBORyAicGl2b3QgdGFibGUgMSBsYXlvdXQgMiIpDQoNCnJlc3VsdGluZyBpbjoNCg0KIVtdKHBpdm90X3RhYmxlXzFfcmVzdWx0XzMuUE5HICJwaXZvdCB0YWJsZSAxIHJlc3VsdCAzIikNCg0KPGJyPg0KDQojIyMgNC4zIFVzaW5nIHRoZSBjb2x1bW5zIGZpZWxkDQoNCldoYXQgaWYgd2Ugd2FudCB0byBzdW0gaG9tZSBydW5zIGJ5IHRlYW1zIGFuZCBuYXRpb25hbGl0aWVzPyBXZSB3b3VsZCBwdXQgdGhlIHRlYW1zIChhbmQgdGhlIGxlYWd1ZXMsIHRvbywgd2hpbGUgd2UgYXJlIGF0IGl0KSBpbiB0aGUgUm93cyBmaWVsZCBhcyBiZWZvcmUsIHRoZW4gd2UgcHV0IHRoZSBwbGF5ZXJzJyBuYXRpb25hbGl0aWVzIGluIHRoZSBDb2x1bW5zIGZpZWxkIG9mIHRoZSBwaXZvdCB0YWJsZS4gRmlyc3Qgd2UgaGF2ZSB0byBicmluZyBvdmVyIHRoZSBwbGF5ZXJzJyBjb3VudHJpZXMgb2YgYmlydGggZnJvbSB0aGUgbWFzdGVyIHRhYmxlIChyaWdodCkgaW50byB0aGUgYmF0dGluZyB0YWJsZSAobGVmdCksIG9uY2UgYWdhaW4gdXNpbmcgYElOREVYLU1BVENIYC4NCg0KIVtdKHBpdm90X3RhYmxlX2luZGV4LW1hdGNoX2JhdHRpbmcuUE5HICJwaXZvdCB0YWJsZSBpbmRleC1tYXRjaCBiYXR0aW5nIikgXCBcIFwgXCBcIFwgXCBcIFwgXCAhW10ocGl2b3RfdGFibGVfaW5kZXgtbWF0Y2hfbWFzdGVyLlBORyAicGl2b3QgdGFibGUgaW5kZXgtbWF0Y2ggbWFzdGVyIikNCg0KVGhlIHN5bnRheCBpczoNCg0KYD1JTkRFWChNYXN0ZXIuJEIkMjokUCQxOTEwNixNQVRDSChBMixNYXN0ZXIuJEIkMjokQiQxOTEwNiwwKSwgNSlgDQoNClRoYXQgaXMsIHdlIGFyZSBicmluZ2luZyBvdmVyIHRoZSA1dGggY29sdW1uLCBiaXJ0aENvdW50cnksIGZyb20gdGhlIHNlbGVjdGVkIHJhbmdlLg0KDQpOb3cgdGhhdCB3ZSBoYXZlIHRoZSBjb3VudHJ5IG9mIGJpcnRoIGluIHRoZSBiYXR0aW5nIHRhYmxlLCB3ZSBjYW4gYWRkIGl0IHRvIHRoZSBDb2x1bW5zIHNlY3Rpb24gb2YgdGhlIHBpdm90IHRhYmxlOg0KDQohW10ocGl2b3RfdGFibGVfMV9sYXlvdXRfMy5QTkcgInBpdm90IHRhYmxlIDEgbGF5b3V0IDMiKQ0KDQphbmQgZ2V0IHRoZSBob21lIHJ1bnMgZ3JvdXBlZCBieSBsZWFndWUsIHRlYW0sIGFuZCBjb3VudHJ5Og0KDQohW10ocGl2b3RfdGFibGVfMV9yZXN1bHRfNC5QTkcgInBpdm90IHRhYmxlIDEgcmVzdWx0IDQiKQ0KDQpJIGhhdmUgZmlsdGVyZWQgb3V0IHNvbWUgb2YgdGhlIGNvdW50cmllcyB0byBzYXZlIHNwYWNlLiBBbHNvIG5vdCBzaG93bjogdGhlIHBpdm90IHRhYmxlIGFkZHMgdXAgdG90YWxzIGluIGJvdGggZGlyZWN0aW9uczogYXQgdGhlIHJpZ2htb3N0IGVuZCBpcyBhZ2FpbiB0aGUgdG90YWwgbnVtYmVyIG9mIGhvbWUgcnVucyBoaXQgYnkgZWFjaCB0ZWFtLCBhbmQgYXQgdGhlIGJvdHRvbSBpcyB0aGUgbnVtYmVyIG9mIGhvbWUgcnVucyBoaXQgYnkgQW1lcmljYW4gcGxheWVycywgQ3ViYW4gcGxheWVycywgZXRjLiBQcmV0dHkgY29vbC4NCg0KIyMjIDQuNCBVc2luZyBDT1VOVA0KDQpUaGUgZGVmYXVsdCBzdW1tYXJpemluZyBmdW5jdGlvbiBpcyBTdW0sIGJ1dCB3ZSBjYW4gdXNlIG90aGVycywgc3VjaCBhcyBBdmVyYWdlIG9yIENvdW50LiBTdXBwb3NlIHdlIHdhbnQgdG8gZmluZCBvdXQgaG93IG1hbnkgcGxheWVycyBiYXQgcmlnaHQsIGxlZnQsIG9yIGFyZSBhbWJpZGV4dHJvdXMsIG9uIGVhY2ggdGVhbS4gVGhlIGJhdHRpbmcgcHJlZmVyZW5jZSBmb3IgZWFjaCBmaWVsZCBpcyBhbHNvIGluIHRoZSBtYXN0ZXIgdGFibGUgKHJpZ2h0LCBiZWxvdyksIHNvIHdlIG9uY2UgYWdhaW4gbmVlZCB0byB1c2UgYElOREVYLU1BVENIYCB0byBicmluZyBpdCBvdmVyIHRvIHRoZSBiYXR0aW5nIHRhYmxlIChsZWZ0LCBiZWxvdykgd2l0aCBgSU5ERVgtTUFUQ0hgLg0KDQohW10ocGl2b3RfdGFibGVfMl9pbmRleC1tYXRjaF9iYXR0aW5nLlBORyAicGl2b3QgdGFibGUgMiBpbmRleC1tYXRjaCBiYXR0aW5nIikgXCBcIFwgXCBcIFwgXCBcIFwgXCAhW10ocGl2b3RfdGFibGVfMl9pbmRleC1tYXRjaF9tYXN0ZXIuUE5HICJwaXZvdCB0YWJsZSAyIGluZGV4LW1hdGNoIG1hc3RlciIpDQoNClRoZSBzeW50YXggaXM6DQoNCmA9SU5ERVgoTWFzdGVyLiRCJDI6JFQkMTkxMDYsTUFUQ0goQTIsTWFzdGVyLiRCJDI6JEIkMTkxMDYsMCksIDE5KWANCg0KVGhhdCBpcywgd2UgYXJlIHJldHJpZXZpbmcgdGhlIDE5dGggY29sdW1uIGNvdW50aW5nIGZyb20gdGhlIEIgY29sdW1uLCB3aGljaCBpcyB0aGUgVCBjb2x1bW4sIGZyb20gdGhlIG1hc3RlciB0YWJsZSwgYW5kIGJyaW5naW5nIGl0IG92ZXIgdG8gdGhlIGJhdHRpbmcgdGFibGUuDQoNClRoZW4gd2Ugc2V0IHVwIHRoZSBwaXZvdCB0YWJsZS4gV2UgcHV0IHRoZSBsZWFndWVzIChBTCwgTkwpIGFuZCB0aGUgdGVhbXMgaW4gdGhlIHJvd3MsIHRoZSBiYXR0aW5nIHByZWZlcmVuY2UgKExlZnQsIFJpZ2h0LCBCb3RoKSBpbiB0aGUgY29sdW1ucywgYW5kIHRoZSBDT1VOVCBvZiBzdWNoIHByZWZlcmVuY2VzIGluIHRoZSBkYXRhIGZpZWxkLCBzbyB0aGF0IENhbGMgY29tcHV0ZXMgaG93IG1hbnkgcGxheWVycyBiYXQgbGVmdCwgcmlnaHQsIG9yIGJvdGgsIG9uIGVhY2ggdGVhbS4NCg0KIVtdKHBpdm90X3RhYmxlXzJfbGF5b3V0LlBORyAicGl2b3QgdGFibGUgMiBsYXlvdXQiKQ0KDQpUaGUgcmVzdWx0Og0KDQohW10ocGl2b3RfdGFibGVfMl9yZXN1bHQuUE5HICJwaXZvdCB0YWJsZSAyIHJlc3VsdCIpDQoNCiMjIyA0LjUgUGl2b3QgdGFibGVzIGluIFINCg0KIyMjIyA0LjUuMSBTdW1taW5nIHVwIGhvbWUgcnVucyBieSB0ZWFtDQoNCkluIFIsIHdlIGNhbiB1c2UgW2B0YXBwbHkoKWBdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9iYXNlL3ZlcnNpb25zLzMuNC4zL3RvcGljcy90YXBwbHkpIHRvIHN1bW1hcml6ZSB2YXJpYWJsZXMgaW4gYSBkYXRhZnJhbWUuIEZvciBleGFtcGxlLCB3ZSBjYW4gdXNlIGl0IHRvIGFkZCB1cCB0aGUgbnVtYmVyIG9mIGhvbWUgcnVucyBieSB0ZWFtLCBhcyB3ZSBkaWQgZWFybGllciBpbiB0aGUgc3ByZWFkc2hlZXQncyBwaXZvdCB0YWJsZS4gDQoNCmBgYHtyfQ0KIyBUbyBnZXQgdGhlIGhvbWUgcnVuIHN1bXMgYnkgdGVhbQ0KaG9tZXJ1bnNfYnlfdGVhbSA8LSB0YXBwbHkoYmF0dGluZzE5ODBfam9pbiRIUiwgYmF0dGluZzE5ODBfam9pbiR0ZWFtTmFtZSwgc3VtKQ0KaG9tZXJ1bnNfYnlfdGVhbQ0KYGBgDQoNCldlIGNhbiBnZXQgdGhlIHJlc3VsdCBpbnRvIGEgZGF0YWZyYW1lOg0KDQpgYGB7cn0NCnRlYW1OYW1lcyA8LSBuYW1lcyhob21lcnVuc19ieV90ZWFtKQ0KIyBVc2luZyB1bm5hbWUoKQ0KIyBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xNTczNjcxOS9ob3ctZG8taS1leHRyYWN0LWp1c3QtdGhlLW51bWJlci1mcm9tLWEtbmFtZWQtbnVtYmVyLXdpdGhvdXQtdGhlLW5hbWUNCmhvbWVSdW5zQnlUZWFtIDwtIGFzLnZlY3Rvcih1bm5hbWUoaG9tZXJ1bnNfYnlfdGVhbSkpDQpob21lcnVuc19ieV90ZWFtX2RmIDwtIGRhdGEuZnJhbWUoInRlYW1OYW1lIiA9IHRlYW1OYW1lcywgIkhSIiA9IGhvbWVSdW5zQnlUZWFtKQ0KaG9tZXJ1bnNfYnlfdGVhbV9kZg0KYGBgDQoNClRoZW4gd2UgY2FuIGRvIGEgam9pbiB3aXRoIHRoZSB0ZWFtczE5ODAgdGFibGUgdG8gZ2V0IHRoZSBsZWFndWUuDQoNCmBgYHtyfQ0KaG9tZXJ1bnNfYnlfdGVhbV9kZl9sZWFndWUgPC0gc3FsZGYoIlNFTEVDVCB0ZWFtczE5ODAubGdJRCwgaG9tZXJ1bnNfYnlfdGVhbV9kZi4qDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZST00gaG9tZXJ1bnNfYnlfdGVhbV9kZg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBKT0lODQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlYW1zMTk4MA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPTiBob21lcnVuc19ieV90ZWFtX2RmLnRlYW1OYW1lID0gdGVhbXMxOTgwLm5hbWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT1JERVIgQlkgdGVhbXMxOTgwLmxnSUQiKQ0KDQpob21lcnVuc19ieV90ZWFtX2RmX2xlYWd1ZQ0KYGBgDQoNCiMjIyMgNC41LjIgU3VtbWluZyB1cCBob21lIHJ1bnMgYnkgdGVhbSBhbmQgY291bnRyeQ0KDQpMb29raW5nIHVwIHRoZSBwbGF5ZXJzJyBjb3VudHJpZXMgb2Ygb3JpZ2luIGluIHRoZSBtYXN0ZXIgdGFibGU6DQoNCmBgYHtyfQ0KYmF0dGluZzE5ODBfam9pbiA8LSBzcWxkZigiU0VMRUNUIGJhdHRpbmcxOTgwX2pvaW4uKiwgbWFzdGVyLmJpcnRoQ291bnRyeQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBiYXR0aW5nMTk4MF9qb2luDQogICAgICAgICAgICAgICAgICAgICAgICAgICBKT0lOIG1hc3Rlcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgT04gYmF0dGluZzE5ODBfam9pbi5wbGF5ZXJJRCA9IG1hc3Rlci5wbGF5ZXJJRCIpDQpgYGANCg0KVGFidWxhdGluZyBob21lIHJ1bnMgYnkgY291bnRyeSBhbmQgdGVhbSB1c2luZyBgdGFwcGx5KClgIGFnYWluOg0KDQpgYGB7cn0NCnggPC0gd2l0aChiYXR0aW5nMTk4MF9qb2luLCB0YXBwbHkoSFIsIGxpc3QodGVhbU5hbWUsIGJpcnRoQ291bnRyeSksIEZVTj1zdW0pKQ0KeSA8LSBhcy5kYXRhLmZyYW1lKHgpDQojIENvbnZlcnRpbmcgcm93IG5hbWVzIHRvIGEgY29sdW1uIGFuZCByZW1vdmluZyB0aGUgcm93IG5hbWVzIGFmdGVyd2FyZHMNCnkkdGVhbU5hbWUgPC0gcm93bmFtZXMoeSkNCnJvd25hbWVzKHkpIDwtIE5VTEwNCiMgUmVwbGFjaW5nIE5BJ3Mgd2l0aCAwJ3MuIEFuIE5BIG1lYW5zIGEgdGVhbSBkaWQgbm90IGhhdmUgYW55IHBsYXllciBvZiB0aGF0IG5hdGlvbmFsaXR5Lg0KeVtpcy5uYSh5KV0gPC0gMA0KIyBTaG93aW5nIG9ubHkgc29tZSBvZiB0aGVtIHRvIHNhdmUgc3BhY2UNCmtlZXBzIDwtIGMoInRlYW1OYW1lIiwgIlVTQSIsICJDdWJhIiwgIkQuUi4iLCAiUC5SLiIsICJQYW5hbWEiKQ0KeVtrZWVwc10NCmBgYA0KDQojIyMjIDQuNS4zIENvdW50aW5nIGJhdHRpbmcgcHJlZmVyZW5jZXMgYnkgdGVhbQ0KDQpOb3cgd2UgY2FuIHRyeSB0byByZXBsaWNhdGUgdGhlIGJhdHRpbmcgcHJlZmVyZW5jZXMgcGl2b3QgdGFibGUgd2UgZGlkIGVhcmxpZXIgaGVyZSBpbiBSLiBTdGFydCBieSBsb29raW5nIHVwIHRoZSBwbGF5ZXJzJyBiYXR0aW5nIHByZWZlcmVuY2VzIGluIHRoZSBtYXN0ZXIgdGFibGU6DQoNCmBgYHtyfQ0KYmF0dGluZzE5ODBfam9pbiA8LSBzcWxkZigiU0VMRUNUIGJhdHRpbmcxOTgwX2pvaW4uKiwgbWFzdGVyLmJhdHMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEZST00gYmF0dGluZzE5ODBfam9pbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgSk9JTiBtYXN0ZXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgIE9OIGJhdHRpbmcxOTgwX2pvaW4ucGxheWVySUQgPSBtYXN0ZXIucGxheWVySUQiKQ0KYGBgDQoNClRoZW4gd2UgY2FuIGNvdW50IGJhdHRpbmcgcHJlZmVyZW5jZXMgYnkgdGVhbSB3aXRoIGB0YWJsZSgpYDoNCg0KYGBge3J9DQpiYXR0aW5nX3ByZWZfdGFibGUgPC0gd2l0aChiYXR0aW5nMTk4MF9qb2luLCB0YWJsZSh0ZWFtTmFtZSwgYmF0cykpDQojIENvbnZlcnQgdGhlIHRhYmxlIHRvIGRhdGFmcmFtZQ0KIyBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xMDc1ODk2MS9ob3ctdG8tY29udmVydC1hLXRhYmxlLXRvLWEtZGF0YS1mcmFtZQ0KYmF0dGluZ19wcmVmX2RmIDwtIGFzLmRhdGEuZnJhbWUubWF0cml4KGJhdHRpbmdfcHJlZl90YWJsZSkNCiMgQ29udmVydGluZyByb3duYW1lcyAodGhlIHRlYW0gbmFtZXMpIHRvIGEgY29sdW1uLCBhbmQgcmVtb3ZpbmcgdGhlIHJvd25hbWVzDQpiYXR0aW5nX3ByZWZfZGYkdGVhbU5hbWUgPC0gcm93bmFtZXMoYmF0dGluZ19wcmVmX2RmKQ0Kcm93bmFtZXMoYmF0dGluZ19wcmVmX2RmKSA8LSBOVUxMDQojIFJlLW9yZGVyaW5nIGNvbHVtbnMNCmJhdHRpbmdfcHJlZl9kZiA8LSBiYXR0aW5nX3ByZWZfZGZbYyg0LCAxOjMpXQ0KYmF0dGluZ19wcmVmX2RmDQpgYGANCg0KDQojIyA1LjAgUmVmZXJlbmNlcw0KDQoxLiBIZXJtYW5zLCBGZWxpZW5uZS4gWyoqKkRhdGEgQW5hbHlzaXM6IFRha2UgSXQgdG8gdGhlIE1BWCgpKioqXShodHRwczovL3d3dy5lZHgub3JnL2NvdXJzZS9kYXRhLWFuYWx5c2lzLXRha2UtaXQtbWF4LWRlbGZ0eC1leDEwMXgtMikuIFNwcmluZyAyMDE1Lg0KDQoyLiBLbG9wZmVyLCBCZW4uIFsqKipTYXkgR29vZGJ5ZSB0byBWTE9PS1VQLCBhbmQgSGVsbG8gdG8gSU5ERVgtTUFUQ0gqKipdKGh0dHA6Ly9laW1hZ2luZS5jb20vc2F5LWdvb2RieWUtdG8tdmxvb2t1cC1hbmQtaGVsbG8tdG8taW5kZXgtbWF0Y2gvKQ0KDQozLiB6eDg3NTQgYW5kIEJlbi4gWyoqKkhvdyB0byBkbyB2bG9va3VwIGFuZCBmaWxsIGRvd24gKGxpa2UgaW4gRXhjZWwpIGluIFI/KioqXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xNTMwMzI4My9ob3ctdG8tZG8tdmxvb2t1cC1hbmQtZmlsbC1kb3duLWxpa2UtaW4tZXhjZWwtaW4tcj9ub3JlZGlyZWN0PTEmbHE9MSkNCg0KNC4gQ2lybyBTYW50aWxsaSDljIXlrZDpnLLlrqog5YWt5Zub5LqL5Lu2IOazlei9ruWKnyBhbmQgVGF0dSBVbG1hbmVuLiBbKioqTXlTUUwgU2VsZWN0IGFsbCBjb2x1bW5zIGZyb20gb25lIHRhYmxlIGFuZCBzb21lIGZyb20gYW5vdGhlciB0YWJsZSoqKl0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMzQ5MjkwNC9teXNxbC1zZWxlY3QtYWxsLWNvbHVtbnMtZnJvbS1vbmUtdGFibGUtYW5kLXNvbWUtZnJvbS1hbm90aGVyLXRhYmxlKQ0KDQo1LiBlcmlrLiBbKioqUiBGdW5jdGlvbiBvZiB0aGUgRGF5OiB0YXBwbHkqKipdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL3ItZnVuY3Rpb24tb2YtdGhlLWRheS10YXBwbHktMi8pDQoNCjYuIEFncmF3YWwsIEFiaGluYXYuIFsqKipQcm9ncmFtbWluZyBpbiBSIC0gdHV0b3JpYWwgOiB0YXBwbHkoKSBmdW5jdGlvbiBpbiBSKioqXShodHRwOi8vcnN0dWRpby1wdWJzLXN0YXRpYy5zMy5hbWF6b25hd3MuY29tLzIxMzQ3XzQxOGJjMjI4MDM4ZDRlOTQ4MTUwMThhZDQxNWJiYTQ5Lmh0bWwpDQoNCjcuIEFuZHJlIE1pa3VsZWMgYW5kIEpvc2ggTydCcmllbi4gWyoqKkhvdyBkbyBJIGV4dHJhY3QganVzdCB0aGUgbnVtYmVyIGZyb20gYSBuYW1lZCBudW1iZXIgKHdpdGhvdXQgdGhlIG5hbWUpPyoqKl0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMTU3MzY3MTkvaG93LWRvLWktZXh0cmFjdC1qdXN0LXRoZS1udW1iZXItZnJvbS1hLW5hbWVkLW51bWJlci13aXRob3V0LXRoZS1uYW1lKQ0KDQo4LiBCdGliZXJ0MyBhbmQgSm9yaXMgTWV5cy4gWyoqKkRyb3AgZGF0YSBmcmFtZSBjb2x1bW5zIGJ5IG5hbWUqKipdKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQ2MDUyMDYvZHJvcC1kYXRhLWZyYW1lLWNvbHVtbnMtYnktbmFtZSkNCg0KOS4gSmF5IGtoYW4gYW5kIGFrcnVuLiBbKioqUiB0YWJsZSBmdW5jdGlvbjogaG93IHRvIHN1bSBpbnN0ZWFkIG9mIGNvdW50aW5nPyoqKl0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMzIzMjU4NTgvci10YWJsZS1mdW5jdGlvbi1ob3ctdG8tc3VtLWluc3RlYWQtb2YtY291bnRpbmcpDQoNCjEwLiBWaWN0b3IgVmFuIEhlZS4gWyoqKkhvdyB0byBjb252ZXJ0IGEgdGFibGUgdG8gYSBkYXRhIGZyYW1lKioqXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xMDc1ODk2MS9ob3ctdG8tY29udmVydC1hLXRhYmxlLXRvLWEtZGF0YS1mcmFtZSkNCg0KMTEuIENoYW5nLCBXaW5zdG9uLiBbKioqUmVvcmRlcmluZyB0aGUgY29sdW1ucyBpbiBhIGRhdGEgZnJhbWUqKipdKGh0dHA6Ly93d3cuY29va2Jvb2stci5jb20vTWFuaXB1bGF0aW5nX2RhdGEvUmVvcmRlcmluZ190aGVfY29sdW1uc19pbl9hX2RhdGFfZnJhbWUvKQ0KDQoxMi4gUmVuYXRvIERpbmhhbmkgYW5kIGFMM3hhLiBbKioqSG93IGRvIEkgcmVwbGFjZSBOQSB2YWx1ZXMgd2l0aCB6ZXJvcyBpbiBhbiBSIGRhdGFmcmFtZT8qKipdKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzgxNjE4MzYvaG93LWRvLWktcmVwbGFjZS1uYS12YWx1ZXMtd2l0aC16ZXJvcy1pbi1hbi1yLWRhdGFmcmFtZSkNCg0KMTMuIFNhbWVyIE5hY2hhYsOpIGFuZCBzdGFuZWthbS4gWyoqKkxlZnQgam9pbiBvbmx5IHNlbGVjdGVkIGNvbHVtbnMgaW4gUiB3aXRoIHRoZSBtZXJnZSgpIGZ1bmN0aW9uKioqXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yNDE5MTQ5Ny9sZWZ0LWpvaW4tb25seS1zZWxlY3RlZC1jb2x1bW5zLWluLXItd2l0aC10aGUtbWVyZ2UtZnVuY3Rpb24pDQoNCjE0LiBDaGFuZywgV2luc3Rvbi4gWyoqKlJlbmFtaW5nIGNvbHVtbnMgaW4gYSBkYXRhIGZyYW1lKioqXShodHRwOi8vd3d3LmNvb2tib29rLXIuY29tL01hbmlwdWxhdGluZ19kYXRhL1JlbmFtaW5nX2NvbHVtbnNfaW5fYV9kYXRhX2ZyYW1lLykNCg0KMTUuIENhdGhlcmluZSBhbmQgY3NnaWxsZXNwaWUuIFsqKipIb3cgdG8gbWVyZ2UgdHdvIGNvbHVtbnMgaW4gUiB3aXRoIGEgc3BlY2lmaWMgc3ltYm9sPw0KKioqXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy81NTU5NDY3L2hvdy10by1tZXJnZS10d28tY29sdW1ucy1pbi1yLXdpdGgtYS1zcGVjaWZpYy1zeW1ib2wpDQoNCjE2LiBuYW4gYW5kIEtvbnJhZCBSdWRvbHBoLiBbKioqV2hhdCBpcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHdpdGggYW5kIHdpdGhpbiBpbiBSPyoqKl0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjE4Mjc1NzIvd2hhdC1pcy10aGUtZGlmZmVyZW5jZS1iZXR3ZWVuLXdpdGgtYW5kLXdpdGhpbi1pbi1yKQ==