I. Raster Data
#first, preview the metadata. It is ideal to do this before importing your data.
beforereadingdata <- describe("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif")
#you can store the information above from the tif file by using the next code
HARV_dsmCrop_info <- capture.output(describe("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif"))
Visualize
A: Terra Package
You can use the terra package to convert tif to dataframe. Once you
do this conversion, your map will be plotable.
DSM_HARV_df <- as.data.frame(DSM_HARV, xy = T)
Now you can view the structure of the data and see that it is
formatted as a dataframe.
str(DSM_HARV_df)
## 'data.frame': 2319799 obs. of 3 variables:
## $ x : num 731454 731455 731456 731457 731458 ...
## $ y : num 4713838 4713838 4713838 4713838 4713838 ...
## $ HARV_dsmCrop: num 409 408 407 407 409 ...
B: ggplot()
Now we can use ggplot to view the data.
ggplot() +
geom_raster(data = DSM_HARV_df , aes(x = x, y = y, fill = HARV_dsmCrop)) +
scale_fill_viridis_c() +
coord_quickmap()

C: Terra’s plot function
plot(DSM_HARV)

CRS
A: Check out project string
We can view the CRS string associated with our R object using the
crs() function.
crs(DSM_HARV, proj = T)
## [1] "+proj=utm +zone=18 +datum=WGS84 +units=m +no_defs"
#this project string has a zone - so we automatically know it uses a UTM
#our units are in meters
Min & Max
It is useful to know the minimum or maximum values of a raster
dataset. In this case, given we are working with elevation data, these
values represent the min/max elevation range at our site.
Raster statistics are often calculated and embedded in a GeoTIFF for
us. We can view these values:
minmax(DSM_HARV)
## HARV_dsmCrop
## min 305.07
## max 416.07
If the min and max have not been calulcated yet, we can do that by
using the following code:
DSM_HARV <- setMinMax(DSM_HARV)
We can see that the elevation at our site ranges from 305.0700073 m
to 416.0699768 m.
Bands
A: How many bands?
To check how many bands your raster dataset contains, use the
nly() function.
nlyr(DSM_HARV)
## [1] 1
B: Multi-band
Raster data can also be multi-band, meaning that one raster file
contains data for more than one variable or time period for each cell.
By default the raster() function only imports the first band in a raster
regardless of whether it has one or more bands. Jump to a later episode
in this series for information on working with multi-band rasters: “Work
with Multi-band Rasters in R”.
Missing Data
To highlight NA values in ggplot, alter the scale_fill_*() layer to
contain a colour instruction for NA values, like
scale_fill_viridis_c(na.value = ‘deeppink’).
Find what the no data value is within a tif by using the following
code:
moredescribe <- describe("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif")
showfilesource <- sources(DSM_HARV)
Output of describe prints things like:
[1] “Driver: GTiff/GeoTIFF”
[2] “Files:
data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_dsmCrop.tif”
[3] “Size is 1697, 1367”
[4] “Coordinate System is:”
[5] “PROJCRS["WGS 84 / UTM zone 18N",”
[6] ”
Bad Data
Sometimes a raster’s metadata will tell us the range of expected
values for a raster. Values outside of this range are suspect and we
need to consider that when we analyze the data. Sometimes, we need to
use some common sense and scientific insight as we examine the data -
just as we would for field data to identify questionable values.
Plotting data with appropriate highlighting can help reveal patterns
in bad values and may suggest a solution.
Histograms
We can explore the distribution of values contained within our raster
using the geom_histogram() function which produces a histogram.
Histograms are often useful in identifying outliers and bad data values
in our raster data.
ggplot() +
geom_histogram(data = DSM_HARV_df, aes(HARV_dsmCrop), bins = 40)

Note that the shape of this histogram looks similar to the previous
one that was created using the default of 30 bins. The distribution of
elevation values for our Digital Surface Model (DSM) looks reasonable.
It is likely there are no bad data values in this particular raster.
practice <- describe("data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_DSMhill.tif")
II. Plotting Rasters
Setting Breaks
A: Using Data Breaks
In the previous episode, we viewed our data using a continuous color
ramp. For clarity and visibility of the plot, we may prefer to view the
data “symbolized” or colored according to ranges of values. This is
comparable to a “classified” map.
To do this, we need to tell ggplot how many groups to break our data
into, and where those breaks should be. To make these decisions, it is
useful to first explore the distribution of the data using a bar plot.
To begin with, we will use dplyr’s mutate() function
combined with cut() to split the data into 3 bins.
DSM_HARV_df_mutated <- DSM_HARV_df %>%
mutate(fct_elevation = cut(HARV_dsmCrop, breaks = 6))
ggplot(data = DSM_HARV_df_mutated, aes(fct_elevation, fill=fct_elevation)) +
geom_bar(show.legend = F)

plot(DSM_HARV_df_mutated$HARV_dsmCrop)

If we want to know the cutoff values for the groups, we can ask for
the unique values of fct_elevation:
unique(DSM_HARV_df_mutated$fct_elevation)
## [1] (398,416] (379,398] (361,379] (342,361] (324,342] (305,324]
## Levels: (305,324] (324,342] (342,361] (361,379] (379,398] (398,416]
And we can get the count of values in each group using dplyr’s
group_by() and count() functions:
DSM_HARV_df_mutated %>%
group_by(fct_elevation) %>%
count()
## # A tibble: 6 × 2
## # Groups: fct_elevation [6]
## fct_elevation n
## <fct> <int>
## 1 (305,324] 30211
## 2 (324,342] 388680
## 3 (342,361] 777816
## 4 (361,379] 752257
## 5 (379,398] 349651
## 6 (398,416] 21184
Make it colorful!
Use groups with different colors to make cute maps!
ggplot()+
geom_raster(data = DSM_HARV_df_mutated, aes(x, y, fill = fct_elevation))+
coord_quickmap()

DSM_HARV_df_mutated <- DSM_HARV_df %>%
mutate(fct_elevation = cut(HARV_dsmCrop, breaks = 10))
ggplot(data = DSM_HARV_df_mutated, aes(fct_elevation, fill=fct_elevation)) +
geom_bar(show.legend = F)

ggplot()+
geom_raster(data = DSM_HARV_df_mutated, aes(x, y, fill = fct_elevation))+
coord_quickmap()+
theme(legend.position = "bottom")

The plots above use ggplots default colors – we can use our own color
palletes
my_col <- terrain.colors(10)
ggplot() +
geom_raster(data = DSM_HARV_df_mutated , aes(x = x, y = y,
fill = fct_elevation)) +
scale_fill_manual(values = terrain.colors(10)) +
coord_quickmap()

Or we can also turn off the labels of both axes by passing
element_blank() to the relevant part of the theme() function.
ggplot() +
geom_raster(data = DSM_HARV_df_mutated , aes(x = x, y = y,
fill = fct_elevation)) +
scale_fill_manual(values = my_col, name = "Elevation") +
theme(axis.title = element_blank()) +
coord_quickmap()

Axes Labels
Or we can also turn off the labels of both axes by passing
element_blank() to the relevant part of the
theme() function.
ggplot() +
geom_raster(data = DSM_HARV_df_mutated , aes(x = x, y = y,
fill = fct_elevation)) +
scale_fill_manual(values = my_col, name = "Elevation") +
theme(axis.title = element_blank()) +
coord_quickmap()

Challenge
Challenge! Using Custom Breaks
Six classified ranges of values (break points) that are evenly
divided among the range of pixel values.
Axis labels.
A plot title.
DSM_HARV_df_mutated <- DSM_HARV_df %>%
mutate(fct_elevation = cut(HARV_dsmCrop, breaks = 6))
my_col <- terrain.colors(6)
ggplot() +
geom_raster(data = DSM_HARV_df_mutated, aes(x = x, y = y, fill = fct_elevation)) +
scale_fill_manual(values = my_col, name = "Elevation") +
xlab("UTM Easting Coordinate (m)")+
ylab("UTM Northing Coordinate (m)")+
ggtitle("Classified Elevation Map - NEON Harvard Forest Field Site")+
theme() +
coord_quickmap()

III. Layering Rasters
Adding Hillshade
We can layer a raster on top of a hillshade raster for the same area,
and use a transparency factor to create a 3-dimensional shaded effect. A
hillshade is a raster that maps the shadows and texture that you would
see from above when viewing terrain. We will add a custom color, making
the plot grey. |
DSM_hill_HARV <- rast('data/NEON-DS-Airborne-Remote-Sensing/HARV/DSM/HARV_DSMhill.tif')
DSM_hill_HARV
## class : SpatRaster
## dimensions : 1367, 1697, 1 (nrow, ncol, nlyr)
## resolution : 1, 1 (x, y)
## extent : 731453, 733150, 4712471, 4713838 (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 18N (EPSG:32618)
## source : HARV_DSMhill.tif
## name : HARV_DSMhill
## min value : -0.7136298
## max value : 0.9999997
Convert to DF
DSM_hill_HARV_df <- as.data.frame(DSM_hill_HARV, xy = TRUE)
str(DSM_hill_HARV_df)
## 'data.frame': 2313675 obs. of 3 variables:
## $ x : num 731455 731456 731457 731458 731459 ...
## $ y : num 4713837 4713837 4713837 4713837 4713837 ...
## $ HARV_DSMhill: num -0.15567 0.00743 0.86989 0.9791 0.96283 ...
Plot Hillshade
ggplot() +
geom_raster(data = DSM_hill_HARV_df,
aes(x=x, y=y, alpha=HARV_DSMhill))+
scale_alpha(range=c(0.15, 0.65),
guide = "none")+
coord_quickmap()

Layering
ggplot()+
geom_raster(data=DSM_HARV_df,
aes(x=x, y=y,
fill= HARV_dsmCrop))+
geom_raster(data = DSM_hill_HARV_df,
aes(x=x, y=y,
alpha = HARV_DSMhill))+
scale_fill_viridis_c()+
scale_alpha(range=c(0.15, 0.65), guide="none") +
ggtitle("Elevation with hillshade") +
coord_quickmap()

Challenge
Use the files in the data/NEON-DS-Airborne-Remote-Sensing/SJER/
directory to create BOTH a Digital Terrain Model map
and Digital Surface Model map of the San Joaquin
Experimental Range field site.
Make sure to:
include hillshade in the maps
label axes on the DSM map and exclude them from the DTM
map
include a title for each map
experiment with various alpha values and color palettes to
represent the data
DSM
#DSM Data
DSM_SJER <- rast("data/NEON-DS-Airborne-Remote-Sensing/SJER/DSM/SJER_dsmCrop.tif")
R_df <- as.data.frame(DSM_SJER, xy=T)
# DSM Hillshade
DSM_hill_SJER <-
rast("data/NEON-DS-Airborne-Remote-Sensing/SJER/DSM/SJER_dsmHill.tif")
DSM_hill_SJER_df <- as.data.frame(DSM_hill_SJER, xy=T)
#plot
ggplot()+
geom_raster(data=R_df, aes(x, y, fill=SJER_dsmCrop, alpha=0.8))+
geom_raster(data=DSM_hill_SJER_df, aes(x, y, alpha = SJER_dsmHill))+
scale_fill_viridis_c()+
guides(fill=guide_colorbar())+
scale_alpha(range=c(0.4, 0.7), guide = "none")+
theme_bw()+
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())+
xlab("UTM Easting Coordinate (m)")+
ylab("UTM Northing Coordinate (m)")+
ggtitle("DSM with Hillshade")

coord_quickmap()
## <ggproto object: Class CoordQuickmap, CoordCartesian, Coord, gg>
## aspect: function
## backtransform_range: function
## clip: on
## default: FALSE
## distance: function
## expand: TRUE
## is_free: function
## is_linear: function
## labels: function
## limits: list
## modify_scales: function
## range: function
## render_axis_h: function
## render_axis_v: function
## render_bg: function
## render_fg: function
## setup_data: function
## setup_layout: function
## setup_panel_guides: function
## setup_panel_params: function
## setup_params: function
## train_panel_guides: function
## transform: function
## super: <ggproto object: Class CoordQuickmap, CoordCartesian, Coord, gg>
DTM
DTM_SJER <- rast("data/NEON-DS-Airborne-Remote-Sensing/SJER/DTM/SJER_dtmCrop.tif")
DTM_hill_SJER <- rast("data/NEON-DS-Airborne-Remote-Sensing/SJER/DTM/SJER_dtmHill.tif")
#dataframe em
DTM_hill_SJER_df <- as.data.frame(DTM_hill_SJER, xy=T)
DTM_SJER_df <- as.data.frame(DTM_SJER, xy=T)
#ggplot it
ggplot()+
geom_raster(data=DTM_SJER_df, aes(x, y, fill=SJER_dtmCrop, alpha=2.0))+
geom_raster(data=DTM_hill_SJER_df, aes(x, y, alpha=SJER_dtmHill))+
xlab("")+
ylab("")+
scale_fill_viridis_c()+
guides(fill=guide_colorbar())+
scale_alpha(range=c(0.4, 0.7), guide = "none")+
theme_bw()+
theme(panel.grid.major = element_blank(),
panel.grid.minor=element_blank())+
ggtitle("DTM with Hillshade")+
coord_quickmap()

Key Points
Continuous data ranges can be grouped into categories using
mutate() and cut().
Use built-in terrain.colors() or set your preferred color scheme
manually.
Layer rasters on top of one another by using the alpha
aesthetic.
LS0tDQp0aXRsZTogIiAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY3NzOiBzdHlsZS5jc3MNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQoNCmxpYnJhcnkodGVycmEpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KDQpgYGANCg0KIyMjIyMgKipEYXRhY2FycGVudHJpZXMgMDEqKg0KDQpJbnRyb2R1Y3Rpb24gdG8gR2Vvc3BhdGlhbCBSYXN0ZXIgYW5kIFZlY3RvciBEYXRhIHdpdGggUiANCg0KPGJyPg0KDQoqKk1hcmtkb3duIEF1dGhvcjoqKiBKZXNzaWUgQmVsbA0KDQp8DQp8DQoNCiMjIyMgKipJLiBSYXN0ZXIgRGF0YSoqIHsudGFic2V0fQ0KDQpgYGB7ciByZWFkIGluIGRhdGF9DQoNCiNmaXJzdCwgcHJldmlldyB0aGUgbWV0YWRhdGEuIEl0IGlzIGlkZWFsIHRvIGRvIHRoaXMgYmVmb3JlIGltcG9ydGluZyB5b3VyIGRhdGEuDQoNCmJlZm9yZXJlYWRpbmdkYXRhIDwtIGRlc2NyaWJlKCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvSEFSVi9EU00vSEFSVl9kc21Dcm9wLnRpZiIpDQoNCg0KI3lvdSBjYW4gc3RvcmUgdGhlIGluZm9ybWF0aW9uIGFib3ZlIGZyb20gdGhlIHRpZiBmaWxlIGJ5IHVzaW5nIHRoZSBuZXh0IGNvZGUNCg0KSEFSVl9kc21Dcm9wX2luZm8gPC0gY2FwdHVyZS5vdXRwdXQoZGVzY3JpYmUoImRhdGEvTkVPTi1EUy1BaXJib3JuZS1SZW1vdGUtU2Vuc2luZy9IQVJWL0RTTS9IQVJWX2RzbUNyb3AudGlmIikpDQoNCmBgYA0KDQojIyMjIyAqKk1ldGFkYXRhKioNCg0KKipBOiBOb21lbmNsYXR1cmUqKg0KDQpVc2UgdGhlIGZvbGxvd2luZyBub21lbmNsYXR1cmUgZm9yIG5hbWluZyB5b3VyIHN0cmluZ3MsIGV0Yy4uLg0KDQpbKipmaWxldHlwZSoqXXtzdHlsZT0iY29sb3I6I2E1OGFmZiJ9IF8gPHNwYW4gc3R5bGU9ImNvbG9yOiMwMGJmYzQiPiAqKmxvY2F0aW9uKiogPC9zcGFuPg0KDQoNCg0KICAqIFtGaWxlIFR5cGUgPSBEaWdpdGFsIFN1cmZhY2UgTW9kZWwgKERTTSlde3N0eWxlPSJjb2xvcjojYTU4YWZmIn0NCiAgKiA8c3BhbiBzdHlsZT0iY29sb3I6IzAwYmZjNCI+IExvY2F0aW9uID0gSGFydmFyZCBGb3Jlc3QgPC9zcGFuPg0KICANCnwgICZyYXJyOyBbKipEU00qKl17c3R5bGU9ImNvbG9yOiNhNThhZmYifV88c3BhbiBzdHlsZT0iY29sb3I6IzAwYmZjNCI+KipIQVJWKio8L3NwYW4+DQoNCnwNCnwNCg0KKipCLiBTdW1tYXJpZXMqKiANCg0KVXNlIHN1bW1hcnkocmFzdGVyKSB0byBnZXQgZGF0YSByYW5nZSBzdGF0aXN0aWNzLiBUaGlzIGZ1bmN0aW9uIHdpbGwgb25seSBzdW1tYXJpemUgYSAqcmFuZG9tKiAxMDAsMDAwIGNlbGxzIGZyb20gdGhlIHJhc3RlciB0aG91Z2guIFRvIGdldCBhbGwgY2VsbHMgaW4gc3VtbWFyeSBzdGF0LCB1c2UgdGhlIGZvbGxvd2luZyBmdW5jdGlvbnMgdG9nZXRoZXI6IA0KDQpbKipzdW1tYXJ5Kipde3N0eWxlPSJjb2xvcjojNTNiZTAyIn0oWyoqdmFsdWVzKipde3N0eWxlPSJjb2xvcjojZGI3MmZiIn0oWyoqRFNNKipde3N0eWxlPSJjb2xvcjojYTU4YWZmIn1fPHNwYW4gc3R5bGU9ImNvbG9yOiMwMGJmYzQiPioqSEFSVioqPC9zcGFuPikNCg0KDQoNCmBgYHtyIGV4cGxvcmUgcmFzdGVyfQ0KDQojIHJlYWQgaW4gdGhlIHJhc3RlciB1c2luZyByYXN0KCkgZnVuY3Rpb24NCg0KI25hbWluZyBiZWxvdyBpcyBkYXRhdHlwZV9IQVJWLiBIQVJWIGlzIGJlY2F1c2UgdGhlIGRhdGEgaXMgZnJvbSBIYXJ2YXJkIEZvcmVzdC4gVGhlIERTTSBiZWNhdXNlIHRoZSBkYXRhIGZpbGV0eXBlIGlzIERTTQ0KDQpEU01fSEFSViA8LSByYXN0KCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvSEFSVi9EU00vSEFSVl9kc21Dcm9wLnRpZiIpDQoNCkRTTV9IQVJWDQoNCmBgYA0KDQoNCiMjIyMjICoqVmlzdWFsaXplKioNCg0KKipBOiBUZXJyYSBQYWNrYWdlKioNCg0KWW91IGNhbiB1c2UgdGhlIHRlcnJhIHBhY2thZ2UgdG8gY29udmVydCB0aWYgdG8gZGF0YWZyYW1lLiBPbmNlIHlvdSBkbyB0aGlzIGNvbnZlcnNpb24sIHlvdXIgbWFwIHdpbGwgYmUgcGxvdGFibGUuIA0KDQpgYGB7ciB2aXN1YWxpemUgcmFzdGVyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93In0NCg0KRFNNX0hBUlZfZGYgPC0gYXMuZGF0YS5mcmFtZShEU01fSEFSViwgeHkgPSBUKQ0KDQpgYGANCg0KTm93IHlvdSBjYW4gdmlldyB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhIGFuZCBzZWUgdGhhdCBpdCBpcyBmb3JtYXR0ZWQgYXMgYSBkYXRhZnJhbWUuIA0KDQpgYGB7ciB2aWV3ZGF0YX0NCg0Kc3RyKERTTV9IQVJWX2RmKQ0KYGBgDQp8DQp8DQoNCioqQjogZ2dwbG90KCkqKg0KDQpOb3cgd2UgY2FuIHVzZSBnZ3Bsb3QgdG8gdmlldyB0aGUgZGF0YS4gDQoNCmBgYHtyIGdncGxvdCwgY2xhc3Muc291cmNlPSJmb2xkLXNob3ciLCBjYWNoZT1UUlVFfQ0KDQpnZ3Bsb3QoKSArDQogICAgZ2VvbV9yYXN0ZXIoZGF0YSA9IERTTV9IQVJWX2RmICwgYWVzKHggPSB4LCB5ID0geSwgZmlsbCA9IEhBUlZfZHNtQ3JvcCkpICsNCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsNCiAgICBjb29yZF9xdWlja21hcCgpDQoNCmBgYA0KDQp8DQp8DQoNCioqQzogVGVycmEncyBwbG90IGZ1bmN0aW9uKioNCg0KYGBge3IgcGxvdHRlcnJhLCBjYWNoZT1UUlVFLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCnBsb3QoRFNNX0hBUlYpDQoNCmBgYA0KDQoNCiMjIyMjICoqQ1JTKioNCg0KKipBOiBDaGVjayBvdXQgcHJvamVjdCBzdHJpbmcqKg0KDQp8DQoNCldlIGNhbiB2aWV3IHRoZSBDUlMgc3RyaW5nIGFzc29jaWF0ZWQgd2l0aCBvdXIgUiBvYmplY3QgdXNpbmcgdGhlICoqY3JzKCkqKiBmdW5jdGlvbi4NCg0KYGBge3Igdmlld2NycywgY2xhc3Muc291cmNlPSJmb2xkLXNob3cifQ0KDQpjcnMoRFNNX0hBUlYsIHByb2ogPSBUKQ0KDQojdGhpcyBwcm9qZWN0IHN0cmluZyBoYXMgYSB6b25lIC0gc28gd2UgYXV0b21hdGljYWxseSBrbm93IGl0IHVzZXMgYSBVVE0gDQoNCiNvdXIgdW5pdHMgYXJlIGluIG1ldGVycw0KDQpgYGANCg0KIyMjIyMgKipNaW4gJiBNYXgqKg0KDQpJdCBpcyB1c2VmdWwgdG8ga25vdyB0aGUgbWluaW11bSBvciBtYXhpbXVtIHZhbHVlcyBvZiBhIHJhc3RlciBkYXRhc2V0LiBJbiB0aGlzIGNhc2UsIGdpdmVuIHdlIGFyZSB3b3JraW5nIHdpdGggZWxldmF0aW9uIGRhdGEsIHRoZXNlIHZhbHVlcyByZXByZXNlbnQgdGhlIG1pbi9tYXggZWxldmF0aW9uIHJhbmdlIGF0IG91ciBzaXRlLg0KDQpSYXN0ZXIgc3RhdGlzdGljcyBhcmUgb2Z0ZW4gY2FsY3VsYXRlZCBhbmQgZW1iZWRkZWQgaW4gYSBHZW9USUZGIGZvciB1cy4gV2UgY2FuIHZpZXcgdGhlc2UgdmFsdWVzOg0KDQpgYGB7ciBtaW5tYXgsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCm1pbm1heChEU01fSEFSVikNCg0KYGBgDQoNCklmIHRoZSBtaW4gYW5kIG1heCBoYXZlIG5vdCBiZWVuIGNhbHVsY2F0ZWQgeWV0LCB3ZSBjYW4gZG8gdGhhdCBieSB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGU6IA0KDQpgYGB7ciBjYWxjIG1pbm1heCwgY2xhc3Muc291cmNlPSJmb2xkLXNob3cifQ0KDQpEU01fSEFSViA8LSBzZXRNaW5NYXgoRFNNX0hBUlYpDQoNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgdGhlIGVsZXZhdGlvbiBhdCBvdXIgc2l0ZSByYW5nZXMgZnJvbSAzMDUuMDcwMDA3MyBtIHRvIDQxNi4wNjk5NzY4IG0uDQoNCiMjIyMjICoqQmFuZHMqKg0KDQoqKkE6IEhvdyBtYW55IGJhbmRzPyoqDQoNClRvIGNoZWNrIGhvdyBtYW55IGJhbmRzIHlvdXIgcmFzdGVyIGRhdGFzZXQgY29udGFpbnMsIHVzZSB0aGUgKipubHkoKSoqIGZ1bmN0aW9uLg0KDQpgYGB7ciBubHksIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0Kbmx5cihEU01fSEFSVikNCg0KYGBgDQoNCnwNCnwNCg0KKipCOiBNdWx0aS1iYW5kKioNCg0KUmFzdGVyIGRhdGEgY2FuIGFsc28gYmUgbXVsdGktYmFuZCwgbWVhbmluZyB0aGF0IG9uZSByYXN0ZXIgZmlsZSBjb250YWlucyBkYXRhIGZvciBtb3JlIHRoYW4gb25lIHZhcmlhYmxlIG9yIHRpbWUgcGVyaW9kIGZvciBlYWNoIGNlbGwuIEJ5IGRlZmF1bHQgdGhlIHJhc3RlcigpIGZ1bmN0aW9uIG9ubHkgaW1wb3J0cyB0aGUgZmlyc3QgYmFuZCBpbiBhIHJhc3RlciByZWdhcmRsZXNzIG9mIHdoZXRoZXIgaXQgaGFzIG9uZSBvciBtb3JlIGJhbmRzLiBKdW1wIHRvIGEgbGF0ZXIgZXBpc29kZSBpbiB0aGlzIHNlcmllcyBmb3IgaW5mb3JtYXRpb24gb24gd29ya2luZyB3aXRoIG11bHRpLWJhbmQgcmFzdGVyczogIldvcmsgd2l0aCBNdWx0aS1iYW5kIFJhc3RlcnMgaW4gUiIuDQoNCg0KIyMjIyMgKipNaXNzaW5nIERhdGEqKg0KDQpUbyBoaWdobGlnaHQgTkEgdmFsdWVzIGluIGdncGxvdCwgYWx0ZXIgdGhlIHNjYWxlX2ZpbGxfKigpIGxheWVyIHRvIGNvbnRhaW4gYSBjb2xvdXIgaW5zdHJ1Y3Rpb24gZm9yIE5BIHZhbHVlcywgbGlrZSBzY2FsZV9maWxsX3ZpcmlkaXNfYyhuYS52YWx1ZSA9ICdkZWVwcGluaycpLiANCg0KRmluZCB3aGF0IHRoZSBubyBkYXRhIHZhbHVlIGlzIHdpdGhpbiBhIHRpZiBieSB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGU6DQoNCmBgYHtyIGRlc2NyaWJlLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCm1vcmVkZXNjcmliZSA8LSBkZXNjcmliZSgiZGF0YS9ORU9OLURTLUFpcmJvcm5lLVJlbW90ZS1TZW5zaW5nL0hBUlYvRFNNL0hBUlZfZHNtQ3JvcC50aWYiKQ0KDQpzaG93ZmlsZXNvdXJjZSA8LSBzb3VyY2VzKERTTV9IQVJWKQ0KYGBgDQp8DQp8DQpPdXRwdXQgb2YgZGVzY3JpYmUgcHJpbnRzIHRoaW5ncyBsaWtlOiANCg0KIFsxXSAiRHJpdmVyOiBHVGlmZi9HZW9USUZGIiAgICAgICAgICAgIA0KIFsyXSAiRmlsZXM6IGRhdGEvTkVPTi1EUy1BaXJib3JuZS1SZW1vdGUtU2Vuc2luZy9IQVJWL0RTTS9IQVJWX2RzbUNyb3AudGlmIiAgICAgICAgICAgICAgICAgICAgIA0KIA0KIFszXSAiU2l6ZSBpcyAxNjk3LCAxMzY3IiAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiBbNF0gIkNvb3JkaW5hdGUgU3lzdGVtIGlzOiIgICAgICAgICAgICAgICAgICAgICAgICAgIA0KIFs1XSAiUFJPSkNSU1tcIldHUyA4NCAvIFVUTSB6b25lIDE4TlwiLCINCiANCiBbNl0gIiAgICANCg0KIyMjIyMjICoqQmFkIERhdGEqKg0KDQpTb21ldGltZXMgYSByYXN0ZXLigJlzIG1ldGFkYXRhIHdpbGwgdGVsbCB1cyB0aGUgcmFuZ2Ugb2YgZXhwZWN0ZWQgdmFsdWVzIGZvciBhIHJhc3Rlci4gVmFsdWVzIG91dHNpZGUgb2YgdGhpcyByYW5nZSBhcmUgc3VzcGVjdCBhbmQgd2UgbmVlZCB0byBjb25zaWRlciB0aGF0IHdoZW4gd2UgYW5hbHl6ZSB0aGUgZGF0YS4gU29tZXRpbWVzLCB3ZSBuZWVkIHRvIHVzZSBzb21lIGNvbW1vbiBzZW5zZSBhbmQgc2NpZW50aWZpYyBpbnNpZ2h0IGFzIHdlIGV4YW1pbmUgdGhlIGRhdGEgLSBqdXN0IGFzIHdlIHdvdWxkIGZvciBmaWVsZCBkYXRhIHRvIGlkZW50aWZ5IHF1ZXN0aW9uYWJsZSB2YWx1ZXMuDQoNClBsb3R0aW5nIGRhdGEgd2l0aCBhcHByb3ByaWF0ZSBoaWdobGlnaHRpbmcgY2FuIGhlbHAgcmV2ZWFsIHBhdHRlcm5zIGluIGJhZCB2YWx1ZXMgYW5kIG1heSBzdWdnZXN0IGEgc29sdXRpb24uIA0KDQoNCiMjIyMjIyAqKkhpc3RvZ3JhbXMqKg0KDQpXZSBjYW4gZXhwbG9yZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHZhbHVlcyBjb250YWluZWQgd2l0aGluIG91ciByYXN0ZXIgdXNpbmcgdGhlIGdlb21faGlzdG9ncmFtKCkgZnVuY3Rpb24gd2hpY2ggcHJvZHVjZXMgYSBoaXN0b2dyYW0uIEhpc3RvZ3JhbXMgYXJlIG9mdGVuIHVzZWZ1bCBpbiBpZGVudGlmeWluZyBvdXRsaWVycyBhbmQgYmFkIGRhdGEgdmFsdWVzIGluIG91ciByYXN0ZXIgZGF0YS4NCg0KYGBge3IgaGlzdH0NCg0KZ2dwbG90KCkgKw0KICAgIGdlb21faGlzdG9ncmFtKGRhdGEgPSBEU01fSEFSVl9kZiwgYWVzKEhBUlZfZHNtQ3JvcCksIGJpbnMgPSA0MCkNCg0KYGBgDQoNCk5vdGUgdGhhdCB0aGUgc2hhcGUgb2YgdGhpcyBoaXN0b2dyYW0gbG9va3Mgc2ltaWxhciB0byB0aGUgcHJldmlvdXMgb25lIHRoYXQgd2FzIGNyZWF0ZWQgdXNpbmcgdGhlIGRlZmF1bHQgb2YgMzAgYmlucy4gVGhlIGRpc3RyaWJ1dGlvbiBvZiBlbGV2YXRpb24gdmFsdWVzIGZvciBvdXIgRGlnaXRhbCBTdXJmYWNlIE1vZGVsIChEU00pIGxvb2tzIHJlYXNvbmFibGUuIEl0IGlzIGxpa2VseSB0aGVyZSBhcmUgbm8gYmFkIGRhdGEgdmFsdWVzIGluIHRoaXMgcGFydGljdWxhciByYXN0ZXIuDQoNCmBgYHtyIGNoYWxsZW5nZX0NCnByYWN0aWNlIDwtIGRlc2NyaWJlKCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvSEFSVi9EU00vSEFSVl9EU01oaWxsLnRpZiIpDQpgYGANCg0KDQojIyMjICoqSUkuIFBsb3R0aW5nIFJhc3RlcnMqKiB7LnRhYnNldH0NCg0KDQojIyMjIyAqKlNldHRpbmcgQnJlYWtzKioNCg0KKipBOiBVc2luZyBEYXRhIEJyZWFrcyoqDQoNCkluIHRoZSBwcmV2aW91cyBlcGlzb2RlLCB3ZSB2aWV3ZWQgb3VyIGRhdGEgdXNpbmcgYSBjb250aW51b3VzIGNvbG9yIHJhbXAuIEZvciBjbGFyaXR5IGFuZCB2aXNpYmlsaXR5IG9mIHRoZSBwbG90LCB3ZSBtYXkgcHJlZmVyIHRvIHZpZXcgdGhlIGRhdGEg4oCcc3ltYm9saXplZOKAnSBvciBjb2xvcmVkIGFjY29yZGluZyB0byByYW5nZXMgb2YgdmFsdWVzLiBUaGlzIGlzIGNvbXBhcmFibGUgdG8gYSDigJxjbGFzc2lmaWVk4oCdIG1hcC4gDQoNCnwNCg0KVG8gZG8gdGhpcywgd2UgbmVlZCB0byB0ZWxsIGdncGxvdCBob3cgbWFueSBncm91cHMgdG8gYnJlYWsgb3VyIGRhdGEgaW50bywgYW5kIHdoZXJlIHRob3NlIGJyZWFrcyBzaG91bGQgYmUuIFRvIG1ha2UgdGhlc2UgZGVjaXNpb25zLCBpdCBpcyB1c2VmdWwgdG8gZmlyc3QgZXhwbG9yZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkYXRhIHVzaW5nIGEgYmFyIHBsb3QuIFRvIGJlZ2luIHdpdGgsIHdlIHdpbGwgdXNlIGRwbHly4oCZcyAqKm11dGF0ZSgpKiogZnVuY3Rpb24gY29tYmluZWQgd2l0aCAqKmN1dCgpKiogdG8gc3BsaXQgdGhlIGRhdGEgaW50byAzIGJpbnMuDQoNCmBgYHtyIGN1dCwgY2FjaGU9VH0NCg0KRFNNX0hBUlZfZGZfbXV0YXRlZCA8LSBEU01fSEFSVl9kZiAlPiUNCiAgbXV0YXRlKGZjdF9lbGV2YXRpb24gPSBjdXQoSEFSVl9kc21Dcm9wLCBicmVha3MgPSA2KSkNCg0KDQpnZ3Bsb3QoZGF0YSA9IERTTV9IQVJWX2RmX211dGF0ZWQsIGFlcyhmY3RfZWxldmF0aW9uLCBmaWxsPWZjdF9lbGV2YXRpb24pKSArDQogICAgZ2VvbV9iYXIoc2hvdy5sZWdlbmQgPSBGKQ0KDQpwbG90KERTTV9IQVJWX2RmX211dGF0ZWQkSEFSVl9kc21Dcm9wKQ0KDQoNCmBgYA0KDQp8DQp8DQoNCklmIHdlIHdhbnQgdG8ga25vdyB0aGUgY3V0b2ZmIHZhbHVlcyBmb3IgdGhlIGdyb3Vwcywgd2UgY2FuIGFzayBmb3IgdGhlIHVuaXF1ZSB2YWx1ZXMgb2YgZmN0X2VsZXZhdGlvbjoNCg0KYGBge3IgdW5pcXVlLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCnVuaXF1ZShEU01fSEFSVl9kZl9tdXRhdGVkJGZjdF9lbGV2YXRpb24pDQpgYGANCg0KfA0KfCANCg0KQW5kIHdlIGNhbiBnZXQgdGhlIGNvdW50IG9mIHZhbHVlcyBpbiBlYWNoIGdyb3VwIHVzaW5nIGRwbHly4oCZcyAqKmdyb3VwX2J5KCkqKiBhbmQgKipjb3VudCgpKiogZnVuY3Rpb25zOg0KDQpgYGB7ciBncm91cGJ5LCBjYWNoZT1UUlVFLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCkRTTV9IQVJWX2RmX211dGF0ZWQgJT4lDQogIGdyb3VwX2J5KGZjdF9lbGV2YXRpb24pICU+JQ0KICBjb3VudCgpDQoNCmBgYA0KDQoNCnwNCnwNCg0KIyMjIyMgKipNYWtlIGl0IGNvbG9yZnVsISoqDQoNClVzZSBncm91cHMgd2l0aCBkaWZmZXJlbnQgY29sb3JzIHRvIG1ha2UgY3V0ZSBtYXBzIQ0KDQpgYGB7ciBwcmV0dHkgcGxvdHMgb2YgeW91ciBjb2xvciBjaG9pY2UsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0KZ2dwbG90KCkrDQogIGdlb21fcmFzdGVyKGRhdGEgPSBEU01fSEFSVl9kZl9tdXRhdGVkLCBhZXMoeCwgeSwgZmlsbCA9IGZjdF9lbGV2YXRpb24pKSsNCiAgY29vcmRfcXVpY2ttYXAoKQ0KDQoNCmBgYA0KDQoNCg0KYGBge3IgbW9yZSBicmVha3MgYW5kIGNvbG9ycywgY2FjaGU9VFJVRX0NCg0KRFNNX0hBUlZfZGZfbXV0YXRlZCA8LSBEU01fSEFSVl9kZiAlPiUNCiAgICAgICAgICAgICAgICBtdXRhdGUoZmN0X2VsZXZhdGlvbiA9IGN1dChIQVJWX2RzbUNyb3AsIGJyZWFrcyA9IDEwKSkNCg0KDQpnZ3Bsb3QoZGF0YSA9IERTTV9IQVJWX2RmX211dGF0ZWQsIGFlcyhmY3RfZWxldmF0aW9uLCBmaWxsPWZjdF9lbGV2YXRpb24pKSArDQogICAgZ2VvbV9iYXIoc2hvdy5sZWdlbmQgPSBGKQ0KDQoNCmdncGxvdCgpKw0KICBnZW9tX3Jhc3RlcihkYXRhID0gRFNNX0hBUlZfZGZfbXV0YXRlZCwgYWVzKHgsIHksIGZpbGwgPSBmY3RfZWxldmF0aW9uKSkrDQogIGNvb3JkX3F1aWNrbWFwKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KDQpgYGANCg0KVGhlIHBsb3RzIGFib3ZlIHVzZSBnZ3Bsb3RzIGRlZmF1bHQgY29sb3JzIC0tIHdlIGNhbiB1c2Ugb3VyIG93biBjb2xvciBwYWxsZXRlcyANCg0KYGBge3IgY3VzdG9tIHBhbGV0dGUsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0KbXlfY29sIDwtIHRlcnJhaW4uY29sb3JzKDEwKQ0KDQpnZ3Bsb3QoKSArDQogZ2VvbV9yYXN0ZXIoZGF0YSA9IERTTV9IQVJWX2RmX211dGF0ZWQgLCBhZXMoeCA9IHgsIHkgPSB5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gZmN0X2VsZXZhdGlvbikpICsgDQogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gdGVycmFpbi5jb2xvcnMoMTApKSArIA0KICAgIGNvb3JkX3F1aWNrbWFwKCkNCg0KYGBgDQoNCk9yIHdlIGNhbiBhbHNvIHR1cm4gb2ZmIHRoZSBsYWJlbHMgb2YgYm90aCBheGVzIGJ5IHBhc3NpbmcgZWxlbWVudF9ibGFuaygpIHRvIHRoZSByZWxldmFudCBwYXJ0IG9mIHRoZSB0aGVtZSgpIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCg0KZ2dwbG90KCkgKw0KIGdlb21fcmFzdGVyKGRhdGEgPSBEU01fSEFSVl9kZl9tdXRhdGVkICwgYWVzKHggPSB4LCB5ID0geSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGZjdF9lbGV2YXRpb24pKSArIA0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbCwgbmFtZSA9ICJFbGV2YXRpb24iKSArDQogICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgICBjb29yZF9xdWlja21hcCgpDQoNCmBgYA0KDQoNCiMjIyMjICoqQXhlcyBMYWJlbHMqKg0KDQoNCk9yIHdlIGNhbiBhbHNvIHR1cm4gb2ZmIHRoZSBsYWJlbHMgb2YgYm90aCBheGVzIGJ5IHBhc3NpbmcgKiplbGVtZW50X2JsYW5rKCkqKiB0byB0aGUgcmVsZXZhbnQgcGFydCBvZiB0aGUgKip0aGVtZSgpKiogZnVuY3Rpb24uDQoNCmBgYHtyIHR1cm4gb2ZmIGxhYmVsc30NCg0KZ2dwbG90KCkgKw0KIGdlb21fcmFzdGVyKGRhdGEgPSBEU01fSEFSVl9kZl9tdXRhdGVkICwgYWVzKHggPSB4LCB5ID0geSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGZjdF9lbGV2YXRpb24pKSArIA0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbCwgbmFtZSA9ICJFbGV2YXRpb24iKSArDQogICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgICBjb29yZF9xdWlja21hcCgpDQoNCmBgYA0KDQp8DQp8DQoNCiMjIyMjICoqQ2hhbGxlbmdlKioNCg0KDQoqKkNoYWxsZW5nZSEgVXNpbmcgQ3VzdG9tIEJyZWFrcyoqDQoNCjEuIFNpeCBjbGFzc2lmaWVkIHJhbmdlcyBvZiB2YWx1ZXMgKGJyZWFrIHBvaW50cykgdGhhdCBhcmUgZXZlbmx5IGRpdmlkZWQgYW1vbmcgdGhlIHJhbmdlIG9mIHBpeGVsIHZhbHVlcy4NCg0KMi4gQXhpcyBsYWJlbHMuDQoNCjMuIEEgcGxvdCB0aXRsZS4NCg0KYGBge3IgY2hhbGxlbmdlIDF9DQpEU01fSEFSVl9kZl9tdXRhdGVkIDwtIERTTV9IQVJWX2RmICU+JQ0KICAgICAgICAgICAgICAgIG11dGF0ZShmY3RfZWxldmF0aW9uID0gY3V0KEhBUlZfZHNtQ3JvcCwgYnJlYWtzID0gNikpDQoNCm15X2NvbCA8LSB0ZXJyYWluLmNvbG9ycyg2KQ0KDQpnZ3Bsb3QoKSArIA0KICBnZW9tX3Jhc3RlcihkYXRhID0gRFNNX0hBUlZfZGZfbXV0YXRlZCwgYWVzKHggPSB4LCB5ID0geSwgZmlsbCA9IGZjdF9lbGV2YXRpb24pKSArIA0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbCwgbmFtZSA9ICJFbGV2YXRpb24iKSArDQogIHhsYWIoIlVUTSBFYXN0aW5nIENvb3JkaW5hdGUgKG0pIikrDQogIHlsYWIoIlVUTSBOb3J0aGluZyBDb29yZGluYXRlIChtKSIpKw0KICBnZ3RpdGxlKCJDbGFzc2lmaWVkIEVsZXZhdGlvbiBNYXAgLSBORU9OIEhhcnZhcmQgRm9yZXN0IEZpZWxkIFNpdGUiKSsNCiAgICB0aGVtZSgpICsgDQogICAgY29vcmRfcXVpY2ttYXAoKQ0KYGBgDQoNCg0KIyMjIyAqKklJSS4gTGF5ZXJpbmcgUmFzdGVycyoqIHsudGFic2V0fQ0KDQojIyMjIyAqKkFkZGluZyBIaWxsc2hhZGUqKg0KDQp8DQoNCldlIGNhbiBsYXllciBhIHJhc3RlciBvbiB0b3Agb2YgYSBoaWxsc2hhZGUgcmFzdGVyIGZvciB0aGUgc2FtZSBhcmVhLCBhbmQgdXNlIGEgdHJhbnNwYXJlbmN5IGZhY3RvciB0byBjcmVhdGUgYSAzLWRpbWVuc2lvbmFsIHNoYWRlZCBlZmZlY3QuIEEgaGlsbHNoYWRlIGlzIGEgcmFzdGVyIHRoYXQgbWFwcyB0aGUgc2hhZG93cyBhbmQgdGV4dHVyZSB0aGF0IHlvdSB3b3VsZCBzZWUgZnJvbSBhYm92ZSB3aGVuIHZpZXdpbmcgdGVycmFpbi4gV2Ugd2lsbCBhZGQgYSBjdXN0b20gY29sb3IsIG1ha2luZyB0aGUgcGxvdCBncmV5Lg0KfA0KYGBge3IgaGlsbHNoYWRlLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCkRTTV9oaWxsX0hBUlYgPC0gcmFzdCgnZGF0YS9ORU9OLURTLUFpcmJvcm5lLVJlbW90ZS1TZW5zaW5nL0hBUlYvRFNNL0hBUlZfRFNNaGlsbC50aWYnKQ0KDQoNCkRTTV9oaWxsX0hBUlYNCmBgYA0KDQojIyMjIyAqKkNvbnZlcnQgdG8gREYqKg0KDQpgYGB7ciwgY2xhc3Muc291cmNlPSJmb2xkLXNob3cifQ0KDQpEU01faGlsbF9IQVJWX2RmIDwtIGFzLmRhdGEuZnJhbWUoRFNNX2hpbGxfSEFSViwgeHkgPSBUUlVFKSANCg0Kc3RyKERTTV9oaWxsX0hBUlZfZGYpDQoNCmBgYA0KDQojIyMjIyAqKlBsb3QgSGlsbHNoYWRlKioNCg0KYGBge3IgcGxvdCBoaWxsc2hhZGUsIGNsYXNzLnNvdXJjZT0iZm9sZC1zaG93In0NCg0KZ2dwbG90KCkgKw0KICBnZW9tX3Jhc3RlcihkYXRhID0gRFNNX2hpbGxfSEFSVl9kZiwgDQogICAgICAgICAgICAgIGFlcyh4PXgsIHk9eSwgYWxwaGE9SEFSVl9EU01oaWxsKSkrDQogICAgICBzY2FsZV9hbHBoYShyYW5nZT1jKDAuMTUsIDAuNjUpLCANCiAgICAgICAgICAgICAgICAgIGd1aWRlID0gIm5vbmUiKSsNCiAgY29vcmRfcXVpY2ttYXAoKQ0KDQpgYGANCg0KIyMjIyMgKipMYXllcmluZyoqDQoNCmBgYHtyIGxheWVyaW5nLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCmdncGxvdCgpKw0KICBnZW9tX3Jhc3RlcihkYXRhPURTTV9IQVJWX2RmLCANCiAgICAgICAgICAgICAgYWVzKHg9eCwgeT15LCANCiAgICAgICAgICAgICAgICAgIGZpbGw9IEhBUlZfZHNtQ3JvcCkpKw0KICBnZW9tX3Jhc3RlcihkYXRhID0gRFNNX2hpbGxfSEFSVl9kZiwgDQogICAgICAgICAgICAgIGFlcyh4PXgsIHk9eSwgDQogICAgICAgICAgICAgICAgICBhbHBoYSA9IEhBUlZfRFNNaGlsbCkpKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpKw0KICBzY2FsZV9hbHBoYShyYW5nZT1jKDAuMTUsIDAuNjUpLCBndWlkZT0ibm9uZSIpICsNCiAgZ2d0aXRsZSgiRWxldmF0aW9uIHdpdGggaGlsbHNoYWRlIikgKw0KICBjb29yZF9xdWlja21hcCgpDQoNCmBgYA0KDQojIyMjIyAqKkNoYWxsZW5nZSoqDQoNClVzZSB0aGUgZmlsZXMgaW4gdGhlIGRhdGEvTkVPTi1EUy1BaXJib3JuZS1SZW1vdGUtU2Vuc2luZy9TSkVSLyBkaXJlY3RvcnkgdG8gY3JlYXRlIEJPVEggYSAqKkRpZ2l0YWwgVGVycmFpbiBNb2RlbCoqIG1hcCBhbmQgKipEaWdpdGFsIFN1cmZhY2UgTW9kZWwqKiBtYXAgb2YgdGhlIFNhbiBKb2FxdWluIEV4cGVyaW1lbnRhbCBSYW5nZSBmaWVsZCBzaXRlLg0KDQpNYWtlIHN1cmUgdG86DQoNCiogaW5jbHVkZSBoaWxsc2hhZGUgaW4gdGhlIG1hcHMNCg0KKiBsYWJlbCBheGVzIG9uIHRoZSBEU00gbWFwIGFuZCBleGNsdWRlIHRoZW0gZnJvbSB0aGUgRFRNIG1hcA0KDQoqIGluY2x1ZGUgYSB0aXRsZSBmb3IgZWFjaCBtYXANCg0KKiBleHBlcmltZW50IHdpdGggdmFyaW91cyBhbHBoYSB2YWx1ZXMgYW5kIGNvbG9yIHBhbGV0dGVzIHRvIHJlcHJlc2VudCB0aGUgZGF0YQ0KDQojIyMjIyMgRFNNDQoNCmBgYHtyIGNoYWxsZW5nZSAyLCBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9DQoNCiNEU00gRGF0YQ0KDQpEU01fU0pFUiA8LSByYXN0KCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvU0pFUi9EU00vU0pFUl9kc21Dcm9wLnRpZiIpDQoNClJfZGYgPC0gYXMuZGF0YS5mcmFtZShEU01fU0pFUiwgeHk9VCkNCg0KIyBEU00gSGlsbHNoYWRlDQoNCkRTTV9oaWxsX1NKRVIgPC0gDQogIHJhc3QoImRhdGEvTkVPTi1EUy1BaXJib3JuZS1SZW1vdGUtU2Vuc2luZy9TSkVSL0RTTS9TSkVSX2RzbUhpbGwudGlmIikNCg0KRFNNX2hpbGxfU0pFUl9kZiA8LSBhcy5kYXRhLmZyYW1lKERTTV9oaWxsX1NKRVIsIHh5PVQpDQoNCiNwbG90DQoNCmdncGxvdCgpKw0KICBnZW9tX3Jhc3RlcihkYXRhPVJfZGYsIGFlcyh4LCB5LCBmaWxsPVNKRVJfZHNtQ3JvcCwgYWxwaGE9MC44KSkrDQogIGdlb21fcmFzdGVyKGRhdGE9RFNNX2hpbGxfU0pFUl9kZiwgYWVzKHgsIHksIGFscGhhID0gU0pFUl9kc21IaWxsKSkrDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkrDQogIGd1aWRlcyhmaWxsPWd1aWRlX2NvbG9yYmFyKCkpKw0KICBzY2FsZV9hbHBoYShyYW5nZT1jKDAuNCwgMC43KSwgZ3VpZGUgPSAibm9uZSIpKw0KICB0aGVtZV9idygpKw0KICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSsNCiAgeGxhYigiVVRNIEVhc3RpbmcgQ29vcmRpbmF0ZSAobSkiKSsNCiAgeWxhYigiVVRNIE5vcnRoaW5nIENvb3JkaW5hdGUgKG0pIikrDQogIGdndGl0bGUoIkRTTSB3aXRoIEhpbGxzaGFkZSIpDQogIGNvb3JkX3F1aWNrbWFwKCkNCg0KYGBgDQoNCiMjIyMjIyBEVE0NCg0KYGBge3IgRFRNfQ0KDQpEVE1fU0pFUiA8LSByYXN0KCJkYXRhL05FT04tRFMtQWlyYm9ybmUtUmVtb3RlLVNlbnNpbmcvU0pFUi9EVE0vU0pFUl9kdG1Dcm9wLnRpZiIpDQoNCkRUTV9oaWxsX1NKRVIgPC0gcmFzdCgiZGF0YS9ORU9OLURTLUFpcmJvcm5lLVJlbW90ZS1TZW5zaW5nL1NKRVIvRFRNL1NKRVJfZHRtSGlsbC50aWYiKQ0KDQojZGF0YWZyYW1lIGVtDQoNCkRUTV9oaWxsX1NKRVJfZGYgPC0gYXMuZGF0YS5mcmFtZShEVE1faGlsbF9TSkVSLCB4eT1UKQ0KDQpEVE1fU0pFUl9kZiA8LSBhcy5kYXRhLmZyYW1lKERUTV9TSkVSLCB4eT1UKQ0KDQojZ2dwbG90IGl0DQoNCmdncGxvdCgpKw0KICBnZW9tX3Jhc3RlcihkYXRhPURUTV9TSkVSX2RmLCBhZXMoeCwgeSwgZmlsbD1TSkVSX2R0bUNyb3AsIGFscGhhPTIuMCkpKw0KICBnZW9tX3Jhc3RlcihkYXRhPURUTV9oaWxsX1NKRVJfZGYsIGFlcyh4LCB5LCBhbHBoYT1TSkVSX2R0bUhpbGwpKSsNCiAgeGxhYigiIikrDQogIHlsYWIoIiIpKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpKw0KICBndWlkZXMoZmlsbD1ndWlkZV9jb2xvcmJhcigpKSsNCiAgc2NhbGVfYWxwaGEocmFuZ2U9YygwLjQsIDAuNyksIGd1aWRlID0gIm5vbmUiKSsNCiAgdGhlbWVfYncoKSsNCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpKSsNCiAgZ2d0aXRsZSgiRFRNIHdpdGggSGlsbHNoYWRlIikrDQogIGNvb3JkX3F1aWNrbWFwKCkNCg0KYGBgDQoNCiMjIyMjICoqS2V5IFBvaW50cyoqDQoNCjEuIENvbnRpbnVvdXMgZGF0YSByYW5nZXMgY2FuIGJlIGdyb3VwZWQgaW50byBjYXRlZ29yaWVzIHVzaW5nICoqbXV0YXRlKCkqKiBhbmQgKipjdXQoKSoqLg0KDQoyLiBVc2UgYnVpbHQtaW4gdGVycmFpbi5jb2xvcnMoKSBvciBzZXQgeW91ciBwcmVmZXJyZWQgY29sb3Igc2NoZW1lIG1hbnVhbGx5Lg0KDQozLiBMYXllciByYXN0ZXJzIG9uIHRvcCBvZiBvbmUgYW5vdGhlciBieSB1c2luZyB0aGUgYWxwaGEgYWVzdGhldGljLg0KDQo=