1 Datentransformation - Teil 2

Zuerst müssen wir erneut tidyverse und nycflights13 laden, da wir eine neue Session angefangen haben:

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.3.6      ✔ purrr   0.3.4 
✔ tibble  3.1.8      ✔ dplyr   1.0.10
✔ tidyr   1.2.1      ✔ stringr 1.4.1 
✔ readr   2.1.3      ✔ forcats 0.5.2 ── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(nycflights13)

1.1 Spalten auswählen

Wenn Sie einen sehr großen Datensatz haben und sich auf nur wenige Variablen fokussieren wollen, bietet sich die Funktion select() an, mit dessen Hilfe sich einzelne oder mehrere Variablen aus einem Dataframe entnehmen lasen:

select(flights, year, month, day)

Sie sehen hier, dass sich aus flights mit der select() Funktion die drei Spalten für das Datum entnehmen lassen.

Wenn Sie mehrere Spalten zwischen zwei Variablen entnehmen wollen, wird : verwendet (= von:bis):

select(flights, year:day)

Ebenfalls lassen sich alle Spalten außer eine Auswahl betrachten:

select(flights, -(year:day))

Hier werden alle Variablen außer year, month und day betrachtet.

Es kann auch eine konkrete Auswahl an Variablen entfernt werden:

# Variablen einzeln entfernen:
select(flights, -arr_delay, -year, -day)
# oder Variablen gemeinsam entfernen:
select(flights, -c(sched_dep_time, arr_delay, year))

Je nach Bedarf oder Präferenz gibt es hier mehrere Möglichkeiten, Spalten des Dataframes auszuwählen.

Zusätzlich gibt es noch weitere Hilfsfunktionen für select():

  • starts_with("x"): Entnehme Variablen, die mit "x" beginnen
select(flights, starts_with("time")) # Wähle alle Variablen, die mit "time" beginnen
  • ends_with("x"): Entnehme Variablen, die mit "x" enden
select(flights, ends_with("delay")) # Wähle alle Variablen, die mit "delay" enden
  • contains("x"): Entnehme Variablen, die "x" enthalten
select(flights, contains("time")) # Wähle alle Variablen, in denen irgendwo "time" steht
  • matches("(.)\\1"): Entnehme Variablen, die einem bestimmten Pattern entsprechen (hier: wiederholte Zeichen). Zeichenketten behandeln wir hier nicht explizit.

  • num_range("x", 1:4): Entnehme Variablen, die x1, x2 und x3 entsprechen. Besonders wichtig, wenn Spalten fortlaufend benannt wurden:

billboard
select(billboard, num_range("wk", 8:12))

Um Variablen umzubenennen lässt sich neben select() die Funktion rename() verwenden:

rename(flights, jahr = year)

Wenn Sie Veränderungen durch filter(), select(), arrange() oder rename() übernehmen wollen, können Sie entweder Ihren alten Dataframe überschreiben oder das Ergebnis in neuen Variablen speichern:

fluege <- rename(flights, jahr = year, monat = month, tag = day) # und so weiter ...
fluege # die Variable fluege enthält nun die Änderungen

Ein weiterer nützlicher Aspekt ist, dass sich mit select() auch die Reihenfolge der Spalten ändern lässt.

Wenn wir beispielsweise mehrere Variablen an die ersten Stellen bringen wollen, hilft uns select() und everything():

select(flights, time_hour, air_time, distance, everything())

Die Funktion everything() fügt die restlichen Spalten hinzu.

1.2 Einschub: Reihen auswählen

Neben der Auswahl von Spalten mit select() und dessen Filterung mit filter() können Sie auch Reihen direkt anhand eines Indexes auswählen:

# Reihen direkt durch Index auswählen
slice(flights, 1, 3, 5)
# Reihen durch Vektor auswählen
rows <- c(55, 123, 21)
slice(flights, rows)
# Alle Reihen (möglichen) auswählen:
rows <- c(1, 99999999, 5)
slice(flights, rows)

Alle slice() Operationen lassen sich durch filter() und row_number() ersetzen:

# Eine Reihe:
slice(flights, 1)
filter(flights, row_number() == 1)

# Mehrere Reihen:
slice(flights, 11, 33, 12301)
filter(flights, row_number() %in% c(11, 33, 12301))

Nützlich sind auch die weiteren slice Operationen:

  • slice_min(n = x): Entnehme die x kleinsten Reihen

  • slice_max(n = x): Entnehme die x kleinsten Reihen

  • slice_sample(n = x): Entnehme x zufällige Reihen

  • slice_head(n = x): Entnehme die x ersten Reihen

  • slice_tail(n = x): Entnehme die x letzten Reihen

1.3 Spalten einfügen

Mit mutate() lassen sich im Gegensatz zu select() neue Spalten hinzufügen. In den meisten Fällen sind diese neuen Variablen Berechnungen aus anderen Variablen. Hierzu betrachten wir eine Kurzfassung des Datensatzes flights:

# Variablen aus flights entnehmen:
fluege_kurz <- select(flights, year:day, ends_with("delay"), distance, air_time)
# Variablen umbenennen:
fluege_kurz <- rename(fluege_kurz, jahr = year, monat = month, tag = day,
                                            abflug_delay = dep_delay, ankunft_delay = arr_delay,
                                            distanz = distance, flugzeit = air_time)
fluege_kurz

Mit mutate() lassen sich dann wie folgt neue Variablen anfügen:

# Zwei neue Variablen anhängen:
mutate(fluege_kurz,
             zeitgewinn = abflug_delay - ankunft_delay, # wie viel Zeit wird durch den Flug "gewonnen"?
             geschwindigkeit = distanz / flugzeit * 60 # durchschnittliche Geschwindigkeit des Flugzeugs (Meilen/Stunde)
            )

Beide Variablen zeitgewinn und geschwindigkeit liegen nun als neue Spalten vor.

Es lassen sich ebenfalls mit mutate() schrittweise neue Variablen erstellen:

mutate(fluege_kurz,
             zeitgewinn = abflug_delay - ankunft_delay,
             stunden = flugzeit / 60,
             zeitgewinn_pro_stunde = zeitgewinn / stunden
            )

In diesem Beispiel hängt die neue Spalte zeitgewinn_pro_stunde von den vorherigen Spalten zeitgewinn und stunden ab, obwohl diese noch nicht in dem Dataframe existieren.

Um nur die neuen Spalten zu behalten, wird an Stelle der mutate() Funktion transmute() verwendet:

transmute(fluege_kurz,
             zeitgewinn = abflug_delay - ankunft_delay,
             stunden = flugzeit / 60,
             zeitgewinn_pro_stunde = zeitgewinn / stunden
            )

1.3.1 Nützliche Operatoren für mutate()

Für mutate() können eine Vielzahl an Operationen und Funktionen verwendet werden, um neue Spalten einzufügen. Die Voraussetzung hierfür ist jedoch, dass diese Funktionen/Operationen Vektoren entgegennehmen (Variablen) und Vektoren derselben Größe wiedergeben:

  • Arithmetische Operatoren +, -, *, /, ^.
    Ein Hinweis an dieser Stelle: bei vielen Operationen werden Werte “recycled”, d. h. wenn ein Parameter kürzer ist, wird dieser zur selben Länge erweitert (durch Wiederholung der bisherigen Werte). Das passiert inbesondere auch bei Skalaren (einzelne Zahlen): flugzeit / 60:
# Hier wird der einzelne Wert erweitert:
vektor <- c(1, 3, 5)
3 * vektor
[1]  3  9 15
# Hier wird dasselbe als vollständige Vektoren durchgeführt:
drei <- c(3, 3, 3)
drei * vektor
[1]  3  9 15
  • Ganzzahlige Division %/% und dessen Rest %%:
transmute(flights,
                    dep_time,
                    stunde = dep_time %/% 100, # Gibt den (abgerundeten) ganzzahligen Wert der Division wieder
                    minute = dep_time %% 100   # Der Rest der ganzzahligen Division 
                    )
  • Logarithmen: log(), log2(), log10(). Sie sind insbesondere nützlich, um Datensätze anzuzeigen, bei denen Werte sehr stark auseinanderliegen:
ggplot(data = diamonds) +
    geom_point(mapping = aes(x = carat, y = price))

ggplot(data = diamonds) +
    geom_point(mapping = aes(x = carat, y = log2(price)))

  • Offsets (Verschiebung): lead() und lag() können die Werte aus Vektoren um eine Position nach vorne bzw. hinten ziehen.
    Somit lassen sich laufende Differenzen berechnen, was besonders hilfreich bei Zeitfolgen ist:
(x <- c(1, 2, 3, 5, 8, 13, 21))
[1]  1  2  3  5  8 13 21
lag(x)
[1] NA  1  2  3  5  8 13
lead(x)
[1]  2  3  5  8 13 21 NA
# Differenzen zum vorherigen Wert (delta):
x - lag(x)
[1] NA  1  1  2  3  5  8
  • Kumulationen (Ansammlung) und Aggregationen (eine Verbindung zwischen Daten): für laufegende Summen, Produkte, Minima und Maxima gibt es die folgenden Funktionen: cumsum(), cumprod(), cummin(), cummax() und cummean().
x
[1]  1  2  3  5  8 13 21
cumsum(x)
[1]  1  3  6 11 19 32 53
cummean(x)
[1] 1.000000 1.500000 2.000000 2.750000 3.800000 5.333333 7.571429
  • Logische Vergleiche: <, <=, >, >=, != und ==. Für komplexere Vergleiche lohnt es sich Zwischenergebnisse zu speichern.

  • Rangordnungen: den Rang eines Wertes gemäß der Größe bestimmen (größter, zweitgrößter, usw.). Hierfür kann min_rank() verwendet werden, wobei der kleinste Wert den Rang 1 hat. In Kombination mit desc() hat umgekehrt der kleinste Rang den größten Wert:

(irgendein_name <- c(3, 2, NA, 5, 4))
[1]  3  2 NA  5  4
min_rank(irgendein_name)
[1]  2  1 NA  4  3
min_rank(desc(irgendein_name))
[1]  3  4 NA  1  2

1.4 Spalten gruppieren und zusammenfassen

Der letzte Befehl, den wir hier behandeln wollen ist summarise(). Dem Namen entsprechend werden hierbei Spalten zusammengefasst durch einen einzelnen Wert wie z. B. den Mittelwert:

summarise(flights, durchschittlicher_ankunfts_delay = mean(arr_delay, na.rm = TRUE))

In diesem Beispiel sehen wir nun die durchschnittliche Verspätung bei der Ankunft, wenn wir alle Flüge zusammen betrachten. Das ist erst einmal nicht allzu nützlich und hätte wohlmöglich einfacher so berechnet werden können:

mean(flights$arr_delay, na.rm = TRUE)
[1] 6.895377

summarise() ist erst dann nützlich, wenn es zusammen mit group_by() verwendet wird. Durch die Verwendung von group_by() wird der Dataframe bezüglich der einzelnen Gruppen einer oder mehrere Variablen betrachtet (gruppiert). Nachdem der Dataframe gruppiert wurde, werden die darauf folgenden Operationen immer auf alle Gruppen separat angewendet:

nach_datum_gruppiert <- group_by(flights, year, month, day)
nach_datum_gruppiert
summarise(nach_datum_gruppiert, avg_arr_delay = mean(arr_delay, na.rm = TRUE))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.

Nun erhalten wir für jeden Tag den Durchschnittswert der Ankunftsverspätungen.

Um die Nützlichkeit hervorzuheben, überlegen Sie sich, wie Sie ohne die Hilfe von group_by() diese Durchschnittswerte erhalten würden:

# Der erste Tag:
tag_eins <- filter(flights, month == 1, day == 1)
mean(tag_eins$arr_delay, na.rm = TRUE)
[1] 12.65102
# Der zweite Tag:
tag_zwei <- filter(flights, month == 1, day == 2)
mean(tag_zwei$arr_delay, na.rm = TRUE)
[1] 12.69289
# Und so weiter
  • Naiv müssten Sie für jeden einzelnen Tag die filter() Funktion verwenden …

  • Es gibt zwar verschiedene Ansätze, um dies zu lösen, aber group_by() und summarise() sind hierfür sehr angenehm

1.4.1 Der Pipe Operator %>%:

Sie haben bereits gesehen, dass in Analysen mit mehreren Schritten Zwischenergebnisse gespeichert werden müssen:

  • In manchen Fällen ist das sehr sinnvoll, wenn die Zwischenergebnisse neue Informationen liefern

  • Häufig verlangsamt das jedoch die Datenanalyse, wenn

    • Zwischenschritte nicht relevant sind

    • Namen für Variablen der Zwischenschritte gegeben werden müssen

# Erst Verkleinern:
flights_kurz <- select(flights, year:day, contains("delay"), distance, air_time)
# Dann Umbenennen:
fluege_kurz <- rename(flights_kurz, jahr = year, monat = month, tag = day,
                                                            abflug_delay = dep_delay, ankunft_delay = arr_delay,
                                                            distanz = distance, flugzeit = air_time)
# Dann Gruppieren:
fluege_kurz_nach_datum_gruppiert <- group_by(fluege_kurz, jahr, monat, tag)
# Dann Zusammenfassen:
summarise(fluege_kurz_nach_datum_gruppiert, durchschnit_ankunft_delay = mean(ankunft_delay, na.rm = TRUE))
`summarise()` has grouped output by 'jahr', 'monat'. You can override using the `.groups` argument.

Obwohl einige der Zwischenergebnisse (Variablen) vermutlich nicht weiter benutzt werden würden, wurden sie trotzdem erstellt. Das macht die Analyse etwas mühsamer und zeitaufwendiger.

Stattdessen bietet es sich an, nur die Variablen zwischenzuspeichern, die auch tatsächlich weiter verwendet werden.

Eine Möglichkeit wäre das Zusammenbringen der Funktionen, dessen Zwischenergebnisse nicht gebraucht werden.

Aus dem vorherigen Beispiel mit summarise() und group_by() lassen sich auch beide Funktion wie folgt verbinden:

# Innerhalb von summarise
summarise(group_by(fluege_kurz, jahr, monat, tag), durchschnit_ankunft_delay = mean(ankunft_delay, na.rm = TRUE))
`summarise()` has grouped output by 'jahr', 'monat'. You can override using the `.groups` argument.
  • Zuerst wird group_by() ausgeführt und das Ergebnis direkt an summarise() weitergegeben

  • Das wird schnell sehr unübersichtlich und unverständlich

  • Die Lösung hierfür ist der Pipe-Operator %>%

Funktionen lassen sich mit Hilfe von %>% nacheinander ausführen:

fluege_kurz <- select(flights, year:day, contains("delay"), distance, air_time) %>%
    rename(jahr = year, monat = month, tag = day,
                 abflug_delay = dep_delay, ankunft_delay = arr_delay,
                 distanz = distance, flugzeit = air_time)
fluege_kurz

Sie sehen, dass nacheinander erst die Variablen aus flights ausgewählt wurden und diese dann direkt umbenannt wurden ohne Zwischenergebnis:

  • Der Pipe-Operator %>% lässt sich umgangssprachlich auch als “und dann” formulieren: wähle erst die Variablen aus und dann bennene die Variablen um.

  • Dieser Operator ist eine der wichtigsten Komponenten von tidyverse (mit Ausnahme von ggplot2, welches + verwendet)

  • %>% im Detail: durch den Operator wird der Teil, der links steht, in den ersten Parameter der Funktion, welche rechts steht “eingefügt”. Zuerst wird der linke Teil ausgewertet, dann das Ergebnis an den rechten Teil weitergegeben und ausgewertet. Das Verfahren lässt sich beliebig fortsetzen. Somit sind x %>% f(y) und f(x, y) dasselbe aber auch x %>% f(y) %>% g(z) und g(f(x, y), z), usw.

Betrachten wir ein weiteres Beispiel. Angenommen es soll die durchschnittliche Distanz mit der durchschnittlichen Verspätung für die einzelnen Zielflughäfen verglichen werden. Dann könnten die Schritte zur Analyse so aussehen:

  1. Gruppiere den Datensatz nach den Zielflughäfen

  2. Fasse die Daten zusammen und berechne die durchschnittliche Distanz, die durchschnittliche Verspätung und zähle die Anzahl der Flüge pro Zielflughafen

  3. Entferne störende Datenpunkte wie z. B. den Flughafen von Honolulu, welcher besonders weit entfernt ist

Schritt für Schritt können diese Punkte “abgearbeitet werden”:

# Gruppierung nach dem Zielflughafen:
by_dest <- group_by(flights, dest)
# Für jeden Flughafen die Werte zusammenfassen:
delay <- summarise(by_dest,
  count = n(), # = Anzahl der Einträge zählen
  dist = mean(distance, na.rm = TRUE),
  delay = mean(arr_delay, na.rm = TRUE)
)
delay
# Ausreißer filtern:
delay <- filter(delay, count > 20, dest != "HNL")
delay
# Die Verspätung scheint bis zu ~700 Meilen zu steigen und danach zu fallen
# Ob Verspätungen sich durch einen schnelleren Flug bei längerer Strecke ausgleichen lassen können?
ggplot(data = delay, mapping = aes(x = dist, y = delay)) +
  geom_point(aes(size = count), alpha = 1/3) +
  geom_smooth(se = FALSE)

Mit dem %>% können wir hierbei den Fokus auf die Transformationen legen, denn die Zwischenergebnisse interessieren uns nicht:

delays <- flights %>% 
  group_by(dest) %>% 
  summarise(
    count = n(),
    dist = mean(distance, na.rm = TRUE),
    delay = mean(arr_delay, na.rm = TRUE)
  ) %>% 
  filter(count > 20, dest != "HNL")
delays

Auch hier werden die einzelnen Schritte durch %>% nacheinander ausgeführt.

2 Aufgaben

Wie gewohnt soll in diesem Abschnitt ein weiterer Datensatz betrachtet werden.

Der Datensatz weather aus der Bibliothek nycflights13 beschreibt Wetterdaten aus dem Jahr 2013 bezogen auf drei Flughäfen:

library(nycflights13)
library(tidyverse)
weather

Wie gewohnt erfahren Sie mehr über den Datensatz, wenn Sie ?weather eingeben.

2.1 Übungsaufgaben

  1. Finden Sie verschiedene Möglichkeiten, die Variablen year, month, day, hour und time_hour zu entnehmen.
  2. Entnehmen Sie alle Variablen, die wind im Namen haben.
  3. Versuchen Sie mehrfach die Variable temp auszuwählen. Was stellen Sie fest?
  4. Schlagen Sie die Bedeutung von any_of() und all_of() in Bezug zu select() nach. Wie könnte Ihnen any_of() mit dem folgenden Vektor helfen?
vars <- c("jahr", "month", "day", "Wind")
  1. Lassen Sie den folgenden Code laufen. Entspricht das Ergebnis Ihren Erwartungen?
select(weather, contains("hOuR"))
  1. Die Geschwindigkeiten liegen in Meilen pro Stunde vor. Wandeln Sie diese in Kilometer pro Stunde um (Faktor 1,609344).
  2. Die Temperaturen liegen in Fahrenheit vor. Wandeln Sie diese in Grad Celsius um (°C = (°F - 32) / 1,8).
  3. Es liegen Daten bezüglich der Windgeschwindigkeit und der Geschwindigkeit von Windstößen vor. Wo könnten hier die Unterschiede liegen? Berechnen Sie die Differenz von wind_speed und wind_gust. Welche Information erhalten Sie hieraus?
  4. Finden Sie die Einträge mit den 15 stärksten Windstößen. Versuchen Sie hierbei die min_rank() Funktion zu verwenden. Überlegen Sie sich mehrere Wege, dieses Problem anzugehen. Wie handhaben Sie Einträge mit denselben Rängen (gleiche Windgeschwindigkeit)?
  5. Geben Sie für jeden Flughafen (origin) die höchste Temperatur, die höchste Luftfeuchtigkeit und den kleinsten Luftdruck aus.
  6. Geben Sie für jedes Datum die durchschnittliche Windgeschwindigkeit (wind_speed) aus.
LS0tCnRpdGxlOiAiU3RhdGlzdGlrIHVuZCBEYXRhIFNjaWVuY2UgLSBHcnVuZGxhZ2VuIGRlciBEYXRlbmFuYWx5c2UgMiIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIG51bWJlcl9zZWN0aW9uczogeWVzCi0tLQoKIyBEYXRlbnRyYW5zZm9ybWF0aW9uIC0gVGVpbCAyCgpadWVyc3QgbcO8c3NlbiB3aXIgZXJuZXV0IGB0aWR5dmVyc2VgIHVuZCBgbnljZmxpZ2h0czEzYCBsYWRlbiwgZGEgd2lyCmVpbmUgbmV1ZSBTZXNzaW9uIGFuZ2VmYW5nZW4gaGFiZW46CgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobnljZmxpZ2h0czEzKQpgYGAKCiMjIFNwYWx0ZW4gYXVzd8OkaGxlbgoKV2VubiBTaWUgZWluZW4gc2VociBncm/Dn2VuIERhdGVuc2F0eiBoYWJlbiB1bmQgc2ljaCBhdWYgbnVyIHdlbmlnZQpWYXJpYWJsZW4gZm9rdXNzaWVyZW4gd29sbGVuLCBiaWV0ZXQgc2ljaCBkaWUgRnVua3Rpb24gYHNlbGVjdCgpYCBhbiwKbWl0IGRlc3NlbiBIaWxmZSBzaWNoIGVpbnplbG5lIG9kZXIgbWVocmVyZSBWYXJpYWJsZW4gYXVzIGVpbmVtCkRhdGFmcmFtZSBlbnRuZWhtZW4gbGFzZW46CgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpCmBgYAoKU2llIHNlaGVuIGhpZXIsIGRhc3Mgc2ljaCBhdXMgYGZsaWdodHNgIG1pdCBkZXIgYHNlbGVjdCgpYCBGdW5rdGlvbiBkaWUKZHJlaSBTcGFsdGVuIGbDvHIgZGFzIERhdHVtIGVudG5laG1lbiBsYXNzZW4uCgpXZW5uIFNpZSBtZWhyZXJlIFNwYWx0ZW4gendpc2NoZW4gendlaSBWYXJpYWJsZW4gZW50bmVobWVuIHdvbGxlbiwgd2lyZApgOmAgdmVyd2VuZGV0ICg9IGB2b246YmlzYCk6CgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIHllYXI6ZGF5KQpgYGAKCkViZW5mYWxscyBsYXNzZW4gc2ljaCBhbGxlIFNwYWx0ZW4gYXXDn2VyIGVpbmUgQXVzd2FobCBiZXRyYWNodGVuOgoKYGBge3J9CnNlbGVjdChmbGlnaHRzLCAtKHllYXI6ZGF5KSkKYGBgCgpIaWVyIHdlcmRlbiBhbGxlIFZhcmlhYmxlbiBhdcOfZXIgYHllYXJgLCBgbW9udGhgIHVuZCBgZGF5YCBiZXRyYWNodGV0LgoKRXMga2FubiBhdWNoIGVpbmUga29ua3JldGUgQXVzd2FobCBhbiBWYXJpYWJsZW4gZW50ZmVybnQgd2VyZGVuOgoKYGBge3J9CiMgVmFyaWFibGVuIGVpbnplbG4gZW50ZmVybmVuOgpzZWxlY3QoZmxpZ2h0cywgLWFycl9kZWxheSwgLXllYXIsIC1kYXkpCiMgb2RlciBWYXJpYWJsZW4gZ2VtZWluc2FtIGVudGZlcm5lbjoKc2VsZWN0KGZsaWdodHMsIC1jKHNjaGVkX2RlcF90aW1lLCBhcnJfZGVsYXksIHllYXIpKQpgYGAKCkplIG5hY2ggQmVkYXJmIG9kZXIgUHLDpGZlcmVueiBnaWJ0IGVzIGhpZXIgbWVocmVyZSBNw7ZnbGljaGtlaXRlbiwKU3BhbHRlbiBkZXMgRGF0YWZyYW1lcyBhdXN6dXfDpGhsZW4uCgpadXPDpHR6bGljaCBnaWJ0IGVzIG5vY2ggd2VpdGVyZSBIaWxmc2Z1bmt0aW9uZW4gZsO8ciBgc2VsZWN0KClgOgoKLSAgIGBzdGFydHNfd2l0aCgieCIpYDogRW50bmVobWUgVmFyaWFibGVuLCBkaWUgbWl0IGAieCJgIGJlZ2lubmVuCgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIHN0YXJ0c193aXRoKCJ0aW1lIikpICMgV8OkaGxlIGFsbGUgVmFyaWFibGVuLCBkaWUgbWl0ICJ0aW1lIiBiZWdpbm5lbgpgYGAKCi0gICBgZW5kc193aXRoKCJ4IilgOiBFbnRuZWhtZSBWYXJpYWJsZW4sIGRpZSBtaXQgYCJ4ImAgZW5kZW4KCmBgYHtyfQpzZWxlY3QoZmxpZ2h0cywgZW5kc193aXRoKCJkZWxheSIpKSAjIFfDpGhsZSBhbGxlIFZhcmlhYmxlbiwgZGllIG1pdCAiZGVsYXkiIGVuZGVuCmBgYAoKLSAgIGBjb250YWlucygieCIpYDogRW50bmVobWUgVmFyaWFibGVuLCBkaWUgYCJ4ImAgZW50aGFsdGVuCgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIGNvbnRhaW5zKCJ0aW1lIikpICMgV8OkaGxlIGFsbGUgVmFyaWFibGVuLCBpbiBkZW5lbiBpcmdlbmR3byAidGltZSIgc3RlaHQKYGBgCgotICAgYG1hdGNoZXMoIiguKVxcMSIpYDogRW50bmVobWUgVmFyaWFibGVuLCBkaWUgZWluZW0gYmVzdGltbXRlbgogICAgUGF0dGVybiBlbnRzcHJlY2hlbiAoaGllcjogd2llZGVyaG9sdGUgWmVpY2hlbikuIFplaWNoZW5rZXR0ZW4KICAgIGJlaGFuZGVsbiB3aXIgaGllciBuaWNodCBleHBsaXppdC4KCi0gICBgbnVtX3JhbmdlKCJ4IiwgMTo0KWA6IEVudG5laG1lIFZhcmlhYmxlbiwgZGllIGB4MWAsIGB4MmAgdW5kIGB4M2AKICAgIGVudHNwcmVjaGVuLiBCZXNvbmRlcnMgd2ljaHRpZywgd2VubiBTcGFsdGVuIGZvcnRsYXVmZW5kIGJlbmFubnQKICAgIHd1cmRlbjoKCmBgYHtyfQpiaWxsYm9hcmQKc2VsZWN0KGJpbGxib2FyZCwgbnVtX3JhbmdlKCJ3ayIsIDg6MTIpKQpgYGAKClVtIFZhcmlhYmxlbiB1bXp1YmVuZW5uZW4gbMOkc3N0IHNpY2ggbmViZW4gYHNlbGVjdCgpYCBkaWUgRnVua3Rpb24KYHJlbmFtZSgpYCB2ZXJ3ZW5kZW46CgpgYGB7cn0KcmVuYW1lKGZsaWdodHMsIGphaHIgPSB5ZWFyKQpgYGAKCldlbm4gU2llIFZlcsOkbmRlcnVuZ2VuIGR1cmNoIGBmaWx0ZXIoKWAsIGBzZWxlY3QoKWAsIGBhcnJhbmdlKClgIG9kZXIKYHJlbmFtZSgpYCDDvGJlcm5laG1lbiB3b2xsZW4sIGvDtm5uZW4gU2llIGVudHdlZGVyIElocmVuIGFsdGVuIERhdGFmcmFtZQrDvGJlcnNjaHJlaWJlbiBvZGVyIGRhcyBFcmdlYm5pcyBpbiBuZXVlbiBWYXJpYWJsZW4gc3BlaWNoZXJuOgoKYGBge3J9CmZsdWVnZSA8LSByZW5hbWUoZmxpZ2h0cywgamFociA9IHllYXIsIG1vbmF0ID0gbW9udGgsIHRhZyA9IGRheSkgIyB1bmQgc28gd2VpdGVyIC4uLgpmbHVlZ2UgIyBkaWUgVmFyaWFibGUgZmx1ZWdlIGVudGjDpGx0IG51biBkaWUgw4RuZGVydW5nZW4KYGBgCgpFaW4gd2VpdGVyZXIgbsO8dHpsaWNoZXIgQXNwZWt0IGlzdCwgZGFzcyBzaWNoIG1pdCBgc2VsZWN0KClgIGF1Y2ggZGllClJlaWhlbmZvbGdlIGRlciBTcGFsdGVuIMOkbmRlcm4gbMOkc3N0LgoKV2VubiB3aXIgYmVpc3BpZWxzd2Vpc2UgbWVocmVyZSBWYXJpYWJsZW4gYW4gZGllIGVyc3RlbiBTdGVsbGVuIGJyaW5nZW4Kd29sbGVuLCBoaWxmdCB1bnMgYHNlbGVjdCgpYCB1bmQgYGV2ZXJ5dGhpbmcoKWA6CgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIHRpbWVfaG91ciwgYWlyX3RpbWUsIGRpc3RhbmNlLCBldmVyeXRoaW5nKCkpCmBgYAoKRGllIEZ1bmt0aW9uIGBldmVyeXRoaW5nKClgIGbDvGd0IGRpZSByZXN0bGljaGVuIFNwYWx0ZW4gaGluenUuCgojIyBFaW5zY2h1YjogUmVpaGVuIGF1c3fDpGhsZW4KCk5lYmVuIGRlciBBdXN3YWhsIHZvbiBTcGFsdGVuIG1pdCBgc2VsZWN0KClgIHVuZCBkZXNzZW4gRmlsdGVydW5nIG1pdApgZmlsdGVyKClgIGvDtm5uZW4gU2llIGF1Y2ggUmVpaGVuIGRpcmVrdCBhbmhhbmQgZWluZXMgSW5kZXhlcyBhdXN3w6RobGVuOgoKYGBge3J9CiMgUmVpaGVuIGRpcmVrdCBkdXJjaCBJbmRleCBhdXN3w6RobGVuCnNsaWNlKGZsaWdodHMsIDEsIDMsIDUpCiMgUmVpaGVuIGR1cmNoIFZla3RvciBhdXN3w6RobGVuCnJvd3MgPC0gYyg1NSwgMTIzLCAyMSkKc2xpY2UoZmxpZ2h0cywgcm93cykKIyBBbGxlIFJlaWhlbiAobcO2Z2xpY2hlbikgYXVzd8OkaGxlbjoKcm93cyA8LSBjKDEsIDk5OTk5OTk5LCA1KQpzbGljZShmbGlnaHRzLCByb3dzKQpgYGAKCkFsbGUgYHNsaWNlKClgIE9wZXJhdGlvbmVuIGxhc3NlbiBzaWNoIGR1cmNoIGBmaWx0ZXIoKWAgdW5kCmByb3dfbnVtYmVyKClgIGVyc2V0emVuOgoKYGBge3J9CiMgRWluZSBSZWloZToKc2xpY2UoZmxpZ2h0cywgMSkKZmlsdGVyKGZsaWdodHMsIHJvd19udW1iZXIoKSA9PSAxKQoKIyBNZWhyZXJlIFJlaWhlbjoKc2xpY2UoZmxpZ2h0cywgMTEsIDMzLCAxMjMwMSkKZmlsdGVyKGZsaWdodHMsIHJvd19udW1iZXIoKSAlaW4lIGMoMTEsIDMzLCAxMjMwMSkpCmBgYAoKTsO8dHpsaWNoIHNpbmQgYXVjaCBkaWUgd2VpdGVyZW4gc2xpY2UgT3BlcmF0aW9uZW46CgotICAgYHNsaWNlX21pbihuID0geClgOiBFbnRuZWhtZSBkaWUgYHhgIGtsZWluc3RlbiBSZWloZW4KCi0gICBgc2xpY2VfbWF4KG4gPSB4KWA6IEVudG5laG1lIGRpZSBgeGAga2xlaW5zdGVuIFJlaWhlbgoKLSAgIGBzbGljZV9zYW1wbGUobiA9IHgpYDogRW50bmVobWUgYHhgIHp1ZsOkbGxpZ2UgUmVpaGVuCgotICAgYHNsaWNlX2hlYWQobiA9IHgpYDogRW50bmVobWUgZGllIGB4YCBlcnN0ZW4gUmVpaGVuCgotICAgYHNsaWNlX3RhaWwobiA9IHgpYDogRW50bmVobWUgZGllIGB4YCBsZXR6dGVuIFJlaWhlbgoKIyMgU3BhbHRlbiBlaW5mw7xnZW4KCk1pdCBgbXV0YXRlKClgIGxhc3NlbiBzaWNoIGltIEdlZ2Vuc2F0eiB6dSBgc2VsZWN0KClgIG5ldWUgU3BhbHRlbgpoaW56dWbDvGdlbi4gSW4gZGVuIG1laXN0ZW4gRsOkbGxlbiBzaW5kIGRpZXNlIG5ldWVuIFZhcmlhYmxlbgpCZXJlY2hudW5nZW4gYXVzIGFuZGVyZW4gVmFyaWFibGVuLiBIaWVyenUgYmV0cmFjaHRlbiB3aXIgZWluZQpLdXJ6ZmFzc3VuZyBkZXMgRGF0ZW5zYXR6ZXMgYGZsaWdodHNgOgoKYGBge3J9CiMgVmFyaWFibGVuIGF1cyBmbGlnaHRzIGVudG5laG1lbjoKZmx1ZWdlX2t1cnogPC0gc2VsZWN0KGZsaWdodHMsIHllYXI6ZGF5LCBlbmRzX3dpdGgoImRlbGF5IiksIGRpc3RhbmNlLCBhaXJfdGltZSkKIyBWYXJpYWJsZW4gdW1iZW5lbm5lbjoKZmx1ZWdlX2t1cnogPC0gcmVuYW1lKGZsdWVnZV9rdXJ6LCBqYWhyID0geWVhciwgbW9uYXQgPSBtb250aCwgdGFnID0gZGF5LAoJCQkJCQkJCQkJCWFiZmx1Z19kZWxheSA9IGRlcF9kZWxheSwgYW5rdW5mdF9kZWxheSA9IGFycl9kZWxheSwKCQkJCQkJCQkJCQlkaXN0YW56ID0gZGlzdGFuY2UsIGZsdWd6ZWl0ID0gYWlyX3RpbWUpCmZsdWVnZV9rdXJ6CmBgYAoKTWl0IGBtdXRhdGUoKWAgbGFzc2VuIHNpY2ggZGFubiB3aWUgZm9sZ3QgbmV1ZSBWYXJpYWJsZW4gYW5mw7xnZW46CgpgYGB7cn0KIyBad2VpIG5ldWUgVmFyaWFibGVuIGFuaMOkbmdlbjoKbXV0YXRlKGZsdWVnZV9rdXJ6LAoJCQkgemVpdGdld2lubiA9IGFiZmx1Z19kZWxheSAtIGFua3VuZnRfZGVsYXksICMgd2llIHZpZWwgWmVpdCB3aXJkIGR1cmNoIGRlbiBGbHVnICJnZXdvbm5lbiI/CgkJCSBnZXNjaHdpbmRpZ2tlaXQgPSBkaXN0YW56IC8gZmx1Z3plaXQgKiA2MCAjIGR1cmNoc2Nobml0dGxpY2hlIEdlc2Nod2luZGlna2VpdCBkZXMgRmx1Z3pldWdzIChNZWlsZW4vU3R1bmRlKQoJCQkpCmBgYAoKQmVpZGUgVmFyaWFibGVuIGB6ZWl0Z2V3aW5uYCB1bmQgYGdlc2Nod2luZGlna2VpdGAgbGllZ2VuIG51biBhbHMgbmV1ZQpTcGFsdGVuIHZvci4KCkVzIGxhc3NlbiBzaWNoIGViZW5mYWxscyBtaXQgYG11dGF0ZSgpYCBzY2hyaXR0d2Vpc2UgbmV1ZSBWYXJpYWJsZW4KZXJzdGVsbGVuOgoKYGBge3J9Cm11dGF0ZShmbHVlZ2Vfa3VyeiwKCQkJIHplaXRnZXdpbm4gPSBhYmZsdWdfZGVsYXkgLSBhbmt1bmZ0X2RlbGF5LAoJCQkgc3R1bmRlbiA9IGZsdWd6ZWl0IC8gNjAsCgkJCSB6ZWl0Z2V3aW5uX3Byb19zdHVuZGUgPSB6ZWl0Z2V3aW5uIC8gc3R1bmRlbgoJCQkpCmBgYAoKSW4gZGllc2VtIEJlaXNwaWVsIGjDpG5ndCBkaWUgbmV1ZSBTcGFsdGUgYHplaXRnZXdpbm5fcHJvX3N0dW5kZWAgdm9uIGRlbgp2b3JoZXJpZ2VuIFNwYWx0ZW4gYHplaXRnZXdpbm5gIHVuZCBgc3R1bmRlbmAgYWIsIG9id29obCBkaWVzZSBub2NoCm5pY2h0IGluIGRlbSBEYXRhZnJhbWUgZXhpc3RpZXJlbi4KClVtIG51ciBkaWUgbmV1ZW4gU3BhbHRlbiB6dSBiZWhhbHRlbiwgd2lyZCBhbiBTdGVsbGUgZGVyIGBtdXRhdGUoKWAKRnVua3Rpb24gdHJhbnNtdXRlKCkgdmVyd2VuZGV0OgoKYGBge3J9CnRyYW5zbXV0ZShmbHVlZ2Vfa3VyeiwKCQkJIHplaXRnZXdpbm4gPSBhYmZsdWdfZGVsYXkgLSBhbmt1bmZ0X2RlbGF5LAoJCQkgc3R1bmRlbiA9IGZsdWd6ZWl0IC8gNjAsCgkJCSB6ZWl0Z2V3aW5uX3Byb19zdHVuZGUgPSB6ZWl0Z2V3aW5uIC8gc3R1bmRlbgoJCQkpCmBgYAoKIyMjIE7DvHR6bGljaGUgT3BlcmF0b3JlbiBmw7xyIGBtdXRhdGUoKWAKCkbDvHIgYG11dGF0ZSgpYCBrw7ZubmVuIGVpbmUgVmllbHphaGwgYW4gT3BlcmF0aW9uZW4gdW5kIEZ1bmt0aW9uZW4KdmVyd2VuZGV0IHdlcmRlbiwgdW0gbmV1ZSBTcGFsdGVuIGVpbnp1ZsO8Z2VuLiBEaWUgVm9yYXVzc2V0enVuZyBoaWVyZsO8cgppc3QgamVkb2NoLCBkYXNzIGRpZXNlIEZ1bmt0aW9uZW4vT3BlcmF0aW9uZW4gVmVrdG9yZW4gZW50Z2VnZW5uZWhtZW4KKFZhcmlhYmxlbikgdW5kIFZla3RvcmVuIGRlcnNlbGJlbiBHcsO2w59lIHdpZWRlcmdlYmVuOgoKLSAgIEFyaXRobWV0aXNjaGUgT3BlcmF0b3JlbgogICAgW2ArYF0oaHR0cHM6Ly9yZHJyLmlvL3IvYmFzZS9Bcml0aG1ldGljLmh0bWwpLAogICAgW2AtYF0oaHR0cHM6Ly9yZHJyLmlvL3IvYmFzZS9Bcml0aG1ldGljLmh0bWwpLAogICAgW2AqYF0oaHR0cHM6Ly9yZHJyLmlvL3IvYmFzZS9Bcml0aG1ldGljLmh0bWwpLAogICAgW2AvYF0oaHR0cHM6Ly9yZHJyLmlvL3IvYmFzZS9Bcml0aG1ldGljLmh0bWwpLAogICAgW2BeYF0oaHR0cHM6Ly9yZHJyLmlvL3IvYmFzZS9Bcml0aG1ldGljLmh0bWwpLlwKICAgIEVpbiBIaW53ZWlzIGFuIGRpZXNlciBTdGVsbGU6IGJlaSB2aWVsZW4gT3BlcmF0aW9uZW4gd2VyZGVuIFdlcnRlCiAgICAicmVjeWNsZWQiLCBkLiBoLiB3ZW5uIGVpbiBQYXJhbWV0ZXIga8O8cnplciBpc3QsIHdpcmQgZGllc2VyIHp1cgogICAgc2VsYmVuIEzDpG5nZSBlcndlaXRlcnQgKGR1cmNoIFdpZWRlcmhvbHVuZyBkZXIgYmlzaGVyaWdlbiBXZXJ0ZSkuCiAgICBEYXMgcGFzc2llcnQgaW5iZXNvbmRlcmUgYXVjaCBiZWkgU2thbGFyZW4gKGVpbnplbG5lIFphaGxlbik6CiAgICBgZmx1Z3plaXQgLyA2MGA6CgpgYGB7cn0KIyBIaWVyIHdpcmQgZGVyIGVpbnplbG5lIFdlcnQgZXJ3ZWl0ZXJ0Ogp2ZWt0b3IgPC0gYygxLCAzLCA1KQozICogdmVrdG9yCiMgSGllciB3aXJkIGRhc3NlbGJlIGFscyB2b2xsc3TDpG5kaWdlIFZla3RvcmVuIGR1cmNoZ2Vmw7xocnQ6CmRyZWkgPC0gYygzLCAzLCAzKQpkcmVpICogdmVrdG9yCmBgYAoKLSAgIEdhbnp6YWhsaWdlIERpdmlzaW9uIGAlLyVgIHVuZCBkZXNzZW4gUmVzdCBgJSU6YAoKYGBge3J9CnRyYW5zbXV0ZShmbGlnaHRzLAoJCQkJCWRlcF90aW1lLAoJCQkJCXN0dW5kZSA9IGRlcF90aW1lICUvJSAxMDAsICMgR2lidCBkZW4gKGFiZ2VydW5kZXRlbikgZ2FuenphaGxpZ2VuIFdlcnQgZGVyIERpdmlzaW9uIHdpZWRlcgoJCQkJCW1pbnV0ZSA9IGRlcF90aW1lICUlIDEwMCAgICMgRGVyIFJlc3QgZGVyIGdhbnp6YWhsaWdlbiBEaXZpc2lvbiAKCQkJCQkpCmBgYAoKLSAgIExvZ2FyaXRobWVuOiBgbG9nKClgLCBgbG9nMigpYCwgYGxvZzEwKClgLiBTaWUgc2luZCBpbnNiZXNvbmRlcmUKICAgIG7DvHR6bGljaCwgdW0gRGF0ZW5zw6R0emUgYW56dXplaWdlbiwgYmVpIGRlbmVuIFdlcnRlIHNlaHIgc3RhcmsKICAgIGF1c2VpbmFuZGVybGllZ2VuOgoKYGBge3J9CmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsKCWdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpCmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsKCWdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gY2FyYXQsIHkgPSBsb2cyKHByaWNlKSkpCmBgYAoKLSAgIE9mZnNldHMgKFZlcnNjaGllYnVuZyk6IGBsZWFkKClgIHVuZCBgbGFnKClgIGvDtm5uZW4gZGllIFdlcnRlIGF1cwogICAgVmVrdG9yZW4gdW0gZWluZSBQb3NpdGlvbiBuYWNoIHZvcm5lIGJ6dy4gaGludGVuIHppZWhlbi5cCiAgICBTb21pdCBsYXNzZW4gc2ljaCBsYXVmZW5kZSBEaWZmZXJlbnplbiBiZXJlY2huZW4sIHdhcyBiZXNvbmRlcnMKICAgIGhpbGZyZWljaCBiZWkgWmVpdGZvbGdlbiBpc3Q6CgpgYGB7cn0KKHggPC0gYygxLCAyLCAzLCA1LCA4LCAxMywgMjEpKQpsYWcoeCkKbGVhZCh4KQojIERpZmZlcmVuemVuIHp1bSB2b3JoZXJpZ2VuIFdlcnQgKGRlbHRhKToKeCAtIGxhZyh4KQpgYGAKCi0gICBLdW11bGF0aW9uZW4gKEFuc2FtbWx1bmcpIHVuZCBBZ2dyZWdhdGlvbmVuIChlaW5lIFZlcmJpbmR1bmcKICAgIHp3aXNjaGVuIERhdGVuKTogZsO8ciBsYXVmZWdlbmRlIFN1bW1lbiwgUHJvZHVrdGUsIE1pbmltYSB1bmQgTWF4aW1hCiAgICBnaWJ0IGVzIGRpZSBmb2xnZW5kZW4gRnVua3Rpb25lbjogYGN1bXN1bSgpYCwgYGN1bXByb2QoKWAsCiAgICBgY3VtbWluKClgLCBgY3VtbWF4KClgIHVuZCBgY3VtbWVhbigpYC4KCmBgYHtyfQp4CmN1bXN1bSh4KQpjdW1tZWFuKHgpCmBgYAoKLSAgIExvZ2lzY2hlIFZlcmdsZWljaGU6IGA8YCwgYDw9YCwgYD5gLCBgPj1gLCBgIT1gIHVuZCBgPT1gLiBGw7xyCiAgICBrb21wbGV4ZXJlIFZlcmdsZWljaGUgbG9obnQgZXMgc2ljaCBad2lzY2hlbmVyZ2Vibmlzc2UgenUgc3BlaWNoZXJuLgoKLSAgIFJhbmdvcmRudW5nZW46IGRlbiBSYW5nIGVpbmVzIFdlcnRlcyBnZW3DpMOfIGRlciBHcsO2w59lIGJlc3RpbW1lbgogICAgKGdyw7bDn3RlciwgendlaXRncsO2w590ZXIsIHVzdy4pLiBIaWVyZsO8ciBrYW5uIGBtaW5fcmFuaygpYCB2ZXJ3ZW5kZXQKICAgIHdlcmRlbiwgd29iZWkgZGVyIGtsZWluc3RlIFdlcnQgZGVuIFJhbmcgMSBoYXQuIEluIEtvbWJpbmF0aW9uIG1pdAogICAgYGRlc2MoKWAgaGF0IHVtZ2VrZWhydCBkZXIga2xlaW5zdGUgUmFuZyBkZW4gZ3LDtsOfdGVuIFdlcnQ6CgpgYGB7cn0KKGlyZ2VuZGVpbl9uYW1lIDwtIGMoMywgMiwgTkEsIDUsIDQpKQptaW5fcmFuayhpcmdlbmRlaW5fbmFtZSkKbWluX3JhbmsoZGVzYyhpcmdlbmRlaW5fbmFtZSkpCmBgYAoKIyMgU3BhbHRlbiBncnVwcGllcmVuIHVuZCB6dXNhbW1lbmZhc3NlbgoKRGVyIGxldHp0ZSBCZWZlaGwsIGRlbiB3aXIgaGllciBiZWhhbmRlbG4gd29sbGVuIGlzdCBgc3VtbWFyaXNlKClgLiBEZW0KTmFtZW4gZW50c3ByZWNoZW5kIHdlcmRlbiBoaWVyYmVpIFNwYWx0ZW4genVzYW1tZW5nZWZhc3N0IGR1cmNoIGVpbmVuCmVpbnplbG5lbiBXZXJ0IHdpZSB6LiBCLiBkZW4gTWl0dGVsd2VydDoKCmBgYHtyfQpzdW1tYXJpc2UoZmxpZ2h0cywgZHVyY2hzY2hpdHRsaWNoZXJfYW5rdW5mdHNfZGVsYXkgPSBtZWFuKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKSkKYGBgCgpJbiBkaWVzZW0gQmVpc3BpZWwgc2VoZW4gd2lyIG51biBkaWUgZHVyY2hzY2huaXR0bGljaGUgVmVyc3DDpHR1bmcgYmVpCmRlciBBbmt1bmZ0LCB3ZW5uIHdpciBhbGxlIEZsw7xnZSB6dXNhbW1lbiBiZXRyYWNodGVuLiBEYXMgaXN0IGVyc3QKZWlubWFsIG5pY2h0IGFsbHp1IG7DvHR6bGljaCB1bmQgaMOkdHRlIHdvaGxtw7ZnbGljaCBlaW5mYWNoZXIgc28gYmVyZWNobmV0CndlcmRlbiBrw7ZubmVuOgoKYGBge3J9Cm1lYW4oZmxpZ2h0cyRhcnJfZGVsYXksIG5hLnJtID0gVFJVRSkKYGBgCgpgc3VtbWFyaXNlKClgIGlzdCBlcnN0IGRhbm4gbsO8dHpsaWNoLCB3ZW5uIGVzIHp1c2FtbWVuIG1pdCBgZ3JvdXBfYnkoKWAKdmVyd2VuZGV0IHdpcmQuIER1cmNoIGRpZSBWZXJ3ZW5kdW5nIHZvbiBgZ3JvdXBfYnkoKWAgd2lyZCBkZXIgRGF0YWZyYW1lCmJlesO8Z2xpY2ggZGVyIGVpbnplbG5lbiBHcnVwcGVuIGVpbmVyIG9kZXIgbWVocmVyZSBWYXJpYWJsZW4gYmV0cmFjaHRldAooZ3J1cHBpZXJ0KS4gTmFjaGRlbSBkZXIgRGF0YWZyYW1lIGdydXBwaWVydCB3dXJkZSwgd2VyZGVuIGRpZSBkYXJhdWYKZm9sZ2VuZGVuIE9wZXJhdGlvbmVuIGltbWVyIGF1ZiBhbGxlIEdydXBwZW4gc2VwYXJhdCBhbmdld2VuZGV0OgoKYGBge3J9Cm5hY2hfZGF0dW1fZ3J1cHBpZXJ0IDwtIGdyb3VwX2J5KGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpCm5hY2hfZGF0dW1fZ3J1cHBpZXJ0CnN1bW1hcmlzZShuYWNoX2RhdHVtX2dydXBwaWVydCwgYXZnX2Fycl9kZWxheSA9IG1lYW4oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpKQpgYGAKCk51biBlcmhhbHRlbiB3aXIgZsO8ciBqZWRlbiBUYWcgZGVuIER1cmNoc2Nobml0dHN3ZXJ0IGRlcgpBbmt1bmZ0c3ZlcnNww6R0dW5nZW4uCgpVbSBkaWUgTsO8dHpsaWNoa2VpdCBoZXJ2b3J6dWhlYmVuLCDDvGJlcmxlZ2VuIFNpZSBzaWNoLCB3aWUgU2llIG9obmUgZGllCkhpbGZlIHZvbiBgZ3JvdXBfYnkoKWAgZGllc2UgRHVyY2hzY2huaXR0c3dlcnRlIGVyaGFsdGVuIHfDvHJkZW46CgpgYGB7cn0KIyBEZXIgZXJzdGUgVGFnOgp0YWdfZWlucyA8LSBmaWx0ZXIoZmxpZ2h0cywgbW9udGggPT0gMSwgZGF5ID09IDEpCm1lYW4odGFnX2VpbnMkYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpCiMgRGVyIHp3ZWl0ZSBUYWc6CnRhZ196d2VpIDwtIGZpbHRlcihmbGlnaHRzLCBtb250aCA9PSAxLCBkYXkgPT0gMikKbWVhbih0YWdfendlaSRhcnJfZGVsYXksIG5hLnJtID0gVFJVRSkKIyBVbmQgc28gd2VpdGVyCmBgYAoKLSAgIE5haXYgbcO8c3N0ZW4gU2llIGbDvHIgamVkZW4gZWluemVsbmVuIFRhZyBkaWUgYGZpbHRlcigpYCBGdW5rdGlvbgogICAgdmVyd2VuZGVuIC4uLgoKLSAgIEVzIGdpYnQgendhciB2ZXJzY2hpZWRlbmUgQW5zw6R0emUsIHVtIGRpZXMgenUgbMO2c2VuLCBhYmVyCiAgICBgZ3JvdXBfYnkoKWAgdW5kIGBzdW1tYXJpc2UoKWAgc2luZCBoaWVyZsO8ciBzZWhyIGFuZ2VuZWhtCgojIyMgRGVyIFBpcGUgT3BlcmF0b3IgYCU+JWA6CgpTaWUgaGFiZW4gYmVyZWl0cyBnZXNlaGVuLCBkYXNzIGluIEFuYWx5c2VuIG1pdCBtZWhyZXJlbiBTY2hyaXR0ZW4KWndpc2NoZW5lcmdlYm5pc3NlIGdlc3BlaWNoZXJ0IHdlcmRlbiBtw7xzc2VuOgoKLSAgIEluIG1hbmNoZW4gRsOkbGxlbiBpc3QgZGFzIHNlaHIgc2lubnZvbGwsIHdlbm4gZGllIFp3aXNjaGVuZXJnZWJuaXNzZQogICAgbmV1ZSBJbmZvcm1hdGlvbmVuIGxpZWZlcm4KCi0gICBIw6R1ZmlnIHZlcmxhbmdzYW10IGRhcyBqZWRvY2ggZGllIERhdGVuYW5hbHlzZSwgd2VubgoKICAgIC0gICBad2lzY2hlbnNjaHJpdHRlIG5pY2h0IHJlbGV2YW50IHNpbmQKCiAgICAtICAgTmFtZW4gZsO8ciBWYXJpYWJsZW4gZGVyIFp3aXNjaGVuc2Nocml0dGUgZ2VnZWJlbiB3ZXJkZW4gbcO8c3NlbgoKYGBge3J9CiMgRXJzdCBWZXJrbGVpbmVybjoKZmxpZ2h0c19rdXJ6IDwtIHNlbGVjdChmbGlnaHRzLCB5ZWFyOmRheSwgY29udGFpbnMoImRlbGF5IiksIGRpc3RhbmNlLCBhaXJfdGltZSkKIyBEYW5uIFVtYmVuZW5uZW46CmZsdWVnZV9rdXJ6IDwtIHJlbmFtZShmbGlnaHRzX2t1cnosIGphaHIgPSB5ZWFyLCBtb25hdCA9IG1vbnRoLCB0YWcgPSBkYXksCgkJCQkJCQkJCQkJCQkJCWFiZmx1Z19kZWxheSA9IGRlcF9kZWxheSwgYW5rdW5mdF9kZWxheSA9IGFycl9kZWxheSwKCQkJCQkJCQkJCQkJCQkJZGlzdGFueiA9IGRpc3RhbmNlLCBmbHVnemVpdCA9IGFpcl90aW1lKQojIERhbm4gR3J1cHBpZXJlbjoKZmx1ZWdlX2t1cnpfbmFjaF9kYXR1bV9ncnVwcGllcnQgPC0gZ3JvdXBfYnkoZmx1ZWdlX2t1cnosIGphaHIsIG1vbmF0LCB0YWcpCiMgRGFubiBadXNhbW1lbmZhc3NlbjoKc3VtbWFyaXNlKGZsdWVnZV9rdXJ6X25hY2hfZGF0dW1fZ3J1cHBpZXJ0LCBkdXJjaHNjaG5pdF9hbmt1bmZ0X2RlbGF5ID0gbWVhbihhbmt1bmZ0X2RlbGF5LCBuYS5ybSA9IFRSVUUpKQpgYGAKCk9id29obCBlaW5pZ2UgZGVyIFp3aXNjaGVuZXJnZWJuaXNzZSAoVmFyaWFibGVuKSB2ZXJtdXRsaWNoIG5pY2h0IHdlaXRlcgpiZW51dHp0IHdlcmRlbiB3w7xyZGVuLCB3dXJkZW4gc2llIHRyb3R6ZGVtIGVyc3RlbGx0LiBEYXMgbWFjaHQgZGllCkFuYWx5c2UgZXR3YXMgbcO8aHNhbWVyIHVuZCB6ZWl0YXVmd2VuZGlnZXIuCgpTdGF0dGRlc3NlbiBiaWV0ZXQgZXMgc2ljaCBhbiwgbnVyIGRpZSBWYXJpYWJsZW4gendpc2NoZW56dXNwZWljaGVybiwKZGllIGF1Y2ggdGF0c8OkY2hsaWNoIHdlaXRlciB2ZXJ3ZW5kZXQgd2VyZGVuLgoKRWluZSBNw7ZnbGljaGtlaXQgd8OkcmUgZGFzIFp1c2FtbWVuYnJpbmdlbiBkZXIgRnVua3Rpb25lbiwgZGVzc2VuClp3aXNjaGVuZXJnZWJuaXNzZSBuaWNodCBnZWJyYXVjaHQgd2VyZGVuLgoKQXVzIGRlbSB2b3JoZXJpZ2VuIEJlaXNwaWVsIG1pdCBgc3VtbWFyaXNlKClgIHVuZCBgZ3JvdXBfYnkoKWAgbGFzc2VuCnNpY2ggYXVjaCBiZWlkZSBGdW5rdGlvbiB3aWUgZm9sZ3QgdmVyYmluZGVuOgoKYGBge3J9CiMgSW5uZXJoYWxiIHZvbiBzdW1tYXJpc2UKc3VtbWFyaXNlKGdyb3VwX2J5KGZsdWVnZV9rdXJ6LCBqYWhyLCBtb25hdCwgdGFnKSwgZHVyY2hzY2huaXRfYW5rdW5mdF9kZWxheSA9IG1lYW4oYW5rdW5mdF9kZWxheSwgbmEucm0gPSBUUlVFKSkKYGBgCgotICAgWnVlcnN0IHdpcmQgYGdyb3VwX2J5KClgIGF1c2dlZsO8aHJ0IHVuZCBkYXMgRXJnZWJuaXMgZGlyZWt0IGFuCiAgICBgc3VtbWFyaXNlKClgIHdlaXRlcmdlZ2ViZW4KCi0gICBEYXMgd2lyZCBzY2huZWxsIHNlaHIgdW7DvGJlcnNpY2h0bGljaCB1bmQgdW52ZXJzdMOkbmRsaWNoCgotICAgRGllIEzDtnN1bmcgaGllcmbDvHIgaXN0IGRlciBQaXBlLU9wZXJhdG9yIGAlPiVgCgpGdW5rdGlvbmVuIGxhc3NlbiBzaWNoIG1pdCBIaWxmZSB2b24gYCU+JWAgbmFjaGVpbmFuZGVyIGF1c2bDvGhyZW46CgpgYGB7cn0KZmx1ZWdlX2t1cnogPC0gc2VsZWN0KGZsaWdodHMsIHllYXI6ZGF5LCBjb250YWlucygiZGVsYXkiKSwgZGlzdGFuY2UsIGFpcl90aW1lKSAlPiUKCXJlbmFtZShqYWhyID0geWVhciwgbW9uYXQgPSBtb250aCwgdGFnID0gZGF5LAoJCQkJIGFiZmx1Z19kZWxheSA9IGRlcF9kZWxheSwgYW5rdW5mdF9kZWxheSA9IGFycl9kZWxheSwKCQkJCSBkaXN0YW56ID0gZGlzdGFuY2UsIGZsdWd6ZWl0ID0gYWlyX3RpbWUpCmZsdWVnZV9rdXJ6CmBgYAoKU2llIHNlaGVuLCBkYXNzIG5hY2hlaW5hbmRlciBlcnN0IGRpZSBWYXJpYWJsZW4gYXVzIGBmbGlnaHRzYCBhdXNnZXfDpGhsdAp3dXJkZW4gdW5kIGRpZXNlIGRhbm4gZGlyZWt0IHVtYmVuYW5udCB3dXJkZW4gb2huZSBad2lzY2hlbmVyZ2VibmlzOgoKLSAgIERlciBQaXBlLU9wZXJhdG9yIGAlPiVgIGzDpHNzdCBzaWNoIHVtZ2FuZ3NzcHJhY2hsaWNoIGF1Y2ggYWxzICJ1bmQKICAgIGRhbm4iIGZvcm11bGllcmVuOiBgd8OkaGxlYCBlcnN0IGRpZSBWYXJpYWJsZW4gYXVzIGB1bmQgZGFubmAKICAgIGBiZW5uZW5lYCBkaWUgVmFyaWFibGVuIHVtLgoKLSAgIERpZXNlciBPcGVyYXRvciBpc3QgZWluZSBkZXIgd2ljaHRpZ3N0ZW4gS29tcG9uZW50ZW4gdm9uIGB0aWR5dmVyc2VgCiAgICAobWl0IEF1c25haG1lIHZvbiBgZ2dwbG90MmAsIHdlbGNoZXMgYCtgIHZlcndlbmRldCkKCi0gICBgJT4lYCBpbSBEZXRhaWw6IGR1cmNoIGRlbiBPcGVyYXRvciB3aXJkIGRlciBUZWlsLCBkZXIgbGlua3Mgc3RlaHQsCiAgICBpbiBkZW4gZXJzdGVuIFBhcmFtZXRlciBkZXIgRnVua3Rpb24sIHdlbGNoZSByZWNodHMgc3RlaHQKICAgICJlaW5nZWbDvGd0Ii4gWnVlcnN0IHdpcmQgZGVyIGxpbmtlIFRlaWwgYXVzZ2V3ZXJ0ZXQsIGRhbm4gZGFzCiAgICBFcmdlYm5pcyBhbiBkZW4gcmVjaHRlbiBUZWlsIHdlaXRlcmdlZ2ViZW4gdW5kIGF1c2dld2VydGV0LiBEYXMKICAgIFZlcmZhaHJlbiBsw6Rzc3Qgc2ljaCBiZWxpZWJpZyBmb3J0c2V0emVuLiBTb21pdCBzaW5kIGB4ICU+JSBmKHkpYAogICAgdW5kIGBmKHgsIHkpYCBkYXNzZWxiZSBhYmVyIGF1Y2ggYHggJT4lIGYoeSkgJT4lIGcoeilgIHVuZAogICAgYGcoZih4LCB5KSwgeilgLCB1c3cuCgpCZXRyYWNodGVuIHdpciBlaW4gd2VpdGVyZXMgQmVpc3BpZWwuIEFuZ2Vub21tZW4gZXMgc29sbCBkaWUKZHVyY2hzY2huaXR0bGljaGUgRGlzdGFueiBtaXQgZGVyIGR1cmNoc2Nobml0dGxpY2hlbiBWZXJzcMOkdHVuZyBmw7xyIGRpZQplaW56ZWxuZW4gWmllbGZsdWdow6RmZW4gdmVyZ2xpY2hlbiB3ZXJkZW4uIERhbm4ga8O2bm50ZW4gZGllIFNjaHJpdHRlIHp1cgpBbmFseXNlIHNvIGF1c3NlaGVuOgoKMS4gIEdydXBwaWVyZSBkZW4gRGF0ZW5zYXR6IG5hY2ggZGVuIFppZWxmbHVnaMOkZmVuCgoyLiAgRmFzc2UgZGllIERhdGVuIHp1c2FtbWVuIHVuZCBiZXJlY2huZSBkaWUgZHVyY2hzY2huaXR0bGljaGUgRGlzdGFueiwKICAgIGRpZSBkdXJjaHNjaG5pdHRsaWNoZSBWZXJzcMOkdHVuZyB1bmQgesOkaGxlIGRpZSBBbnphaGwgZGVyIEZsw7xnZSBwcm8KICAgIFppZWxmbHVnaGFmZW4KCjMuICBFbnRmZXJuZSBzdMO2cmVuZGUgRGF0ZW5wdW5rdGUgd2llIHouIEIuIGRlbiBGbHVnaGFmZW4gdm9uIEhvbm9sdWx1LAogICAgd2VsY2hlciBiZXNvbmRlcnMgd2VpdCBlbnRmZXJudCBpc3QKClNjaHJpdHQgZsO8ciBTY2hyaXR0IGvDtm5uZW4gZGllc2UgUHVua3RlICJhYmdlYXJiZWl0ZXQgd2VyZGVuIjoKCmBgYHtyfQojIEdydXBwaWVydW5nIG5hY2ggZGVtIFppZWxmbHVnaGFmZW46CmJ5X2Rlc3QgPC0gZ3JvdXBfYnkoZmxpZ2h0cywgZGVzdCkKIyBGw7xyIGplZGVuIEZsdWdoYWZlbiBkaWUgV2VydGUgenVzYW1tZW5mYXNzZW46CmRlbGF5IDwtIHN1bW1hcmlzZShieV9kZXN0LAogIGNvdW50ID0gbigpLCAjID0gQW56YWhsIGRlciBFaW50csOkZ2UgesOkaGxlbgogIGRpc3QgPSBtZWFuKGRpc3RhbmNlLCBuYS5ybSA9IFRSVUUpLAogIGRlbGF5ID0gbWVhbihhcnJfZGVsYXksIG5hLnJtID0gVFJVRSkKKQpkZWxheQojIEF1c3JlacOfZXIgZmlsdGVybjoKZGVsYXkgPC0gZmlsdGVyKGRlbGF5LCBjb3VudCA+IDIwLCBkZXN0ICE9ICJITkwiKQpkZWxheQojIERpZSBWZXJzcMOkdHVuZyBzY2hlaW50IGJpcyB6dSB+NzAwIE1laWxlbiB6dSBzdGVpZ2VuIHVuZCBkYW5hY2ggenUgZmFsbGVuCiMgT2IgVmVyc3DDpHR1bmdlbiBzaWNoIGR1cmNoIGVpbmVuIHNjaG5lbGxlcmVuIEZsdWcgYmVpIGzDpG5nZXJlciBTdHJlY2tlIGF1c2dsZWljaGVuIGxhc3NlbiBrw7ZubmVuPwpnZ3Bsb3QoZGF0YSA9IGRlbGF5LCBtYXBwaW5nID0gYWVzKHggPSBkaXN0LCB5ID0gZGVsYXkpKSArCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IGNvdW50KSwgYWxwaGEgPSAxLzMpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKQpgYGAKCk1pdCBkZW0gYCU+JWAga8O2bm5lbiB3aXIgaGllcmJlaSBkZW4gRm9rdXMgYXVmIGRpZSBUcmFuc2Zvcm1hdGlvbmVuCmxlZ2VuLCBkZW5uIGRpZSBad2lzY2hlbmVyZ2Vibmlzc2UgaW50ZXJlc3NpZXJlbiB1bnMgbmljaHQ6CgpgYGB7cn0KZGVsYXlzIDwtIGZsaWdodHMgJT4lIAogIGdyb3VwX2J5KGRlc3QpICU+JSAKICBzdW1tYXJpc2UoCiAgICBjb3VudCA9IG4oKSwKICAgIGRpc3QgPSBtZWFuKGRpc3RhbmNlLCBuYS5ybSA9IFRSVUUpLAogICAgZGVsYXkgPSBtZWFuKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKQogICkgJT4lIAogIGZpbHRlcihjb3VudCA+IDIwLCBkZXN0ICE9ICJITkwiKQpkZWxheXMKYGBgCgpBdWNoIGhpZXIgd2VyZGVuIGRpZSBlaW56ZWxuZW4gU2Nocml0dGUgZHVyY2ggYCU+JWAgbmFjaGVpbmFuZGVyCmF1c2dlZsO8aHJ0LgoKIyBBdWZnYWJlbgoKV2llIGdld29obnQgc29sbCBpbiBkaWVzZW0gQWJzY2huaXR0IGVpbiB3ZWl0ZXJlciBEYXRlbnNhdHogYmV0cmFjaHRldAp3ZXJkZW4uCgpEZXIgRGF0ZW5zYXR6IGB3ZWF0aGVyYCBhdXMgZGVyIEJpYmxpb3RoZWsgYG55Y2ZsaWdodHMxM2AgYmVzY2hyZWlidApXZXR0ZXJkYXRlbiBhdXMgZGVtIEphaHIgMjAxMyBiZXpvZ2VuIGF1ZiBkcmVpIEZsdWdow6RmZW46CgpgYGB7cn0KbGlicmFyeShueWNmbGlnaHRzMTMpCmxpYnJhcnkodGlkeXZlcnNlKQp3ZWF0aGVyCmBgYAoKV2llIGdld29obnQgZXJmYWhyZW4gU2llIG1laHIgw7xiZXIgZGVuIERhdGVuc2F0eiwgd2VubiBTaWUgYD93ZWF0aGVyYAplaW5nZWJlbi4KCiMjIMOcYnVuZ3NhdWZnYWJlbgoKMS4gIEZpbmRlbiBTaWUgdmVyc2NoaWVkZW5lIE3DtmdsaWNoa2VpdGVuLCBkaWUgVmFyaWFibGVuIGB5ZWFyYCwKICAgIGBtb250aGAsIGBkYXlgLCBgaG91cmAgdW5kIGB0aW1lX2hvdXJgIHp1IGVudG5laG1lbi4KMi4gIEVudG5laG1lbiBTaWUgYWxsZSBWYXJpYWJsZW4sIGRpZSBgd2luZGAgaW0gTmFtZW4gaGFiZW4uCjMuICBWZXJzdWNoZW4gU2llIG1laHJmYWNoIGRpZSBWYXJpYWJsZSBgdGVtcGAgYXVzenV3w6RobGVuLiBXYXMgc3RlbGxlbgogICAgU2llIGZlc3Q/CjQuICBTY2hsYWdlbiBTaWUgZGllIEJlZGV1dHVuZyB2b24gYGFueV9vZigpYCB1bmQgYGFsbF9vZigpYCBpbiBCZXp1ZyB6dQogICAgYHNlbGVjdCgpYCBuYWNoLiBXaWUga8O2bm50ZSBJaG5lbiBgYW55X29mKClgIG1pdCBkZW0gZm9sZ2VuZGVuCiAgICBWZWt0b3IgaGVsZmVuPwoKYGBge3J9CnZhcnMgPC0gYygiamFociIsICJtb250aCIsICJkYXkiLCAiV2luZCIpCmBgYAoKNS4gIExhc3NlbiBTaWUgZGVuIGZvbGdlbmRlbiBDb2RlIGxhdWZlbi4gRW50c3ByaWNodCBkYXMgRXJnZWJuaXMgSWhyZW4KICAgIEVyd2FydHVuZ2VuPwoKYGBge3IsIGV2YWwgPSBGQUxTRX0Kc2VsZWN0KHdlYXRoZXIsIGNvbnRhaW5zKCJoT3VSIikpCmBgYAoKNi4gIERpZSBHZXNjaHdpbmRpZ2tlaXRlbiBsaWVnZW4gaW4gTWVpbGVuIHBybyBTdHVuZGUgdm9yLiBXYW5kZWxuIFNpZQogICAgZGllc2UgaW4gS2lsb21ldGVyIHBybyBTdHVuZGUgdW0gKEZha3RvciBgMSw2MDkzNDRgKS4KNy4gIERpZSBUZW1wZXJhdHVyZW4gbGllZ2VuIGluIEZhaHJlbmhlaXQgdm9yLiBXYW5kZWxuIFNpZSBkaWVzZSBpbiBHcmFkCiAgICBDZWxzaXVzIHVtICjCsEMgPSAowrBGIC0gMzIpIC8gMSw4KS4KOC4gIEVzIGxpZWdlbiBEYXRlbiBiZXrDvGdsaWNoIGRlciBXaW5kZ2VzY2h3aW5kaWdrZWl0IHVuZCBkZXIKICAgIEdlc2Nod2luZGlna2VpdCB2b24gV2luZHN0w7bDn2VuIHZvci4gV28ga8O2bm50ZW4gaGllciBkaWUgVW50ZXJzY2hpZWRlCiAgICBsaWVnZW4/IEJlcmVjaG5lbiBTaWUgZGllIERpZmZlcmVueiB2b24gYHdpbmRfc3BlZWRgIHVuZAogICAgYHdpbmRfZ3VzdGAuIFdlbGNoZSBJbmZvcm1hdGlvbiBlcmhhbHRlbiBTaWUgaGllcmF1cz8KOS4gIEZpbmRlbiBTaWUgZGllIEVpbnRyw6RnZSBtaXQgZGVuIDE1IHN0w6Rya3N0ZW4gV2luZHN0w7bDn2VuLiBWZXJzdWNoZW4KICAgIFNpZSBoaWVyYmVpIGRpZSBgbWluX3JhbmsoKWAgRnVua3Rpb24genUgdmVyd2VuZGVuLiDDnGJlcmxlZ2VuIFNpZQogICAgc2ljaCBtZWhyZXJlIFdlZ2UsIGRpZXNlcyBQcm9ibGVtIGFuenVnZWhlbi4gV2llIGhhbmRoYWJlbiBTaWUKICAgIEVpbnRyw6RnZSBtaXQgZGVuc2VsYmVuIFLDpG5nZW4gKGdsZWljaGUgV2luZGdlc2Nod2luZGlna2VpdCk/CjEwLiBHZWJlbiBTaWUgZsO8ciBqZWRlbiBGbHVnaGFmZW4gKGBvcmlnaW5gKSBkaWUgaMO2Y2hzdGUgVGVtcGVyYXR1ciwgZGllCiAgICBow7ZjaHN0ZSBMdWZ0ZmV1Y2h0aWdrZWl0IHVuZCBkZW4ga2xlaW5zdGVuIEx1ZnRkcnVjayBhdXMuCjExLiBHZWJlbiBTaWUgZsO8ciBqZWRlcyBEYXR1bSBkaWUgZHVyY2hzY2huaXR0bGljaGUgV2luZGdlc2Nod2luZGlna2VpdAogICAgKGB3aW5kX3NwZWVkYCkgYXVzLgo=