library(httr)
library(jsonlite)
library(dotenv)
library(ggplot2)
library(stringr)
library(spacyr)
library(dplyr)
# HuggingFace API key needs to be defined as API_KEY in .env
load_dot_env()
wd <- "/Users/tspri/Documents/COGS307"
setwd(wd)

Setup

Utility methods

Entropy for type and token frequencies

entropy <- function(counts) {
  perc <- counts$n / sum(counts$n)
  # Compute Hnorm
  Hnorm <- -sum(perc * log2(perc)) / log2(length(counts))
  return(Hnorm/10)
}

Get type and token frequencies

tt_freqs <- function(df) {
  df_count <- df %>%
    count(lemma, sort=TRUE)
  type_freq <- nrow(df_count)
  token_freq <- sum(df_count$n)
  df_entropy <- entropy(df_count)
  ratio <- type_freq / token_freq
  return(data.frame(type_f=type_freq,token_f=token_freq,tt_ratio=ratio,entropy=df_entropy))
}

Type and token frequencies for regular and irregular past tense forms. ‘ed’ stands for ‘-ed’ since all regular past tense forms end with ‘-ed’

ed_type_token <- function(past_verbs) {
  ed_verbs <- past_verbs %>%
    filter(grepl("ed$", token))
  print(ed_verbs)
  ed_ratio <- (count(ed_verbs) / count(past_verbs))$n
  ed_df <- tt_freqs(ed_verbs)
  ed_df$ed_ratio <- ed_ratio
  return(ed_df)
}
non_ed_type_token <- function(past_verbs) {
  non_ed_verbs <- past_verbs %>%
    filter(!grepl("ed$", token))
  #print(ed_verbs)
  non_ed_ratio <- (count(non_ed_verbs) / count(past_verbs))$n
  non_ed_df <- tt_freqs(non_ed_verbs)
  non_ed_df$non_ed_ratio <- non_ed_ratio
  return(non_ed_df)
}

Segmenting past tense verb information, from a list of verbs

past_tense_seg <- function(verbs) {
  past_tense_verbs <- verbs %>%
    filter(tag == "VBN" | tag == "VBD") %>% 
    mutate(ends_with_ed = grepl("ed$", token))
  past_count <- past_tense_verbs %>%
    count(lemma, sort=TRUE)
  ed_values <- ed_type_token(past_tense_verbs)
  print(count(past_tense_verbs))
  print(count(verbs))
  ed_values$past_ratio <- (count(past_tense_verbs) / count(verbs))$n
  return(ed_values)
}

Sentence segmentation using HuggingFace model and further cleaning of that model

add_punctuation <- function(input_text) {
  # Load API key from .env file
  api_key <- Sys.getenv("API_KEY")
  # Define API endpoint and headers
  api_url <- "https://api-inference.huggingface.co/models/oliverguhr/fullstop-punctuation-multilang-large"
  headers <- add_headers(Authorization = paste("Bearer", api_key))
  
  # Define payload as a list
  payload <- list(inputs = input_text)
  
  # Make POST request and return result
  response <- POST(api_url, headers = headers, body = toJSON(payload))
  result <- content(response, as = "text")
  json_str <- result[[1]]
  # Remove the escaped quotes to make the string valid JSON
  json_str <- gsub('\\"', '"', json_str, fixed = TRUE)
  # Parse the JSON into a data frame
  output <- fromJSON(json_str)[1]
  return(parse_output(output[1]))
}
parse_output <- function(output) {
  df <- data.frame(text = sapply(output, function(x) x$word), entity = sapply(output, function(x) ifelse(x$entity_group == "0", "", x$entity_group)))
  output$word_entity <- paste(output$word, ifelse(output$entity_group == "0", "", output$entity_group), sep = "")
  df$word_entity <- paste(df$text, ifelse(df$entity != "", df$entity, ""), sep="")
  result <- paste(df$word_entity, collapse = " ")
  return(result)
}

Getting the parts of speech from a list of lines of text using Spacy

pos_spacy <- function(lines) {
  spacy_initialize(model = "en_core_web_md")
  sentences <- spacy_parse(parsed_lines, "sentencizer",pos=TRUE, tag=TRUE, lemma=TRUE,entity=FALSE, dependency=TRUE, nounphrase=TRUE)
  df <- as.data.frame(sentences)
  parsed_text <- sentences
  pos <- df$pos
  tokens <- df$lemma
  return(df)
}

Initial data stored for every data source

further_parsing <- function(parsed_lines) {
  df <- pos_spacy(parsed_lines)
  num_words <- nrow(df)
  num_utterances <- length(unique(df$sentence))
  df_count <- df %>%
    count(lemma, sort=TRUE)
  word_type_freq <- nrow(df_count)
  word_token_freq <- sum(df_count$n)
  mlu <- num_words / num_utterances
  new_df <- data.frame(
    link, video_name, file_name, viewcount, length_in_secs, metadata, programmed, educational, youtubekids, live, 
    type_frequency=c(word_type_freq), token_frequency=c(word_token_freq),num_utterances,mlu, output_file_name
  )
  current_data <- adding_info(new_df)
  saveRDS(df, file=output_file_name)
  return(current_data)
}

Further analyzing the parts of speech to get transitive verbs, intransitive verbs, nouns and verbs

pos_list <- function(df) {
  all_verbs <- df %>%
    mutate(next_pos = lead(pos),
           next_word = lead(lemma),
           next_pos2 = lead(pos, n = 2),
           next_word2 = lead(lemma, n = 2),
           next_pos3 = lead(pos, n = 3),
           next_word3 = lead(lemma, n = 3)) %>%
    filter(pos %in% c("VERB"))
  #verb_freqs <- tt_freqs(all_verbs)
  #verb_past_tense <- past_tense_seg(all_verbs)
  all_nouns <- df %>%
    filter(pos %in% c("NOUN", "PRON"))
  transitive_verbs <- all_verbs %>%
    filter(lemma != 's') %>%
    filter(next_pos %in% c("NOUN", "PRON") |
             (next_pos == "DET" & next_pos2 == "NOUN") |
             (next_pos == "ADJ" & next_pos2 == "NOUN") |
             (next_pos == "DET" & next_pos2 == "ADJ" & next_pos3 == "NOUN")) 
  intransitive_verbs <- all_verbs %>%
    filter(!(next_pos %in% c("NOUN", "PRON") |
               (next_pos == "DET" & next_pos2 == "NOUN") |
               (next_pos == "ADJ" & next_pos2 == "NOUN") |
               (next_pos == "DET" & next_pos2 == "ADJ" & next_pos3 == "NOUN")))
  return(list(tv=transitive_verbs, iv=intransitive_verbs, av=all_verbs, an=all_nouns))
}

Adding new information to the main Rda file

adding_info <- function(df, full_file="youtube.Rda") {
  if(file.exists(full_file)) {
    olddata <- readRDS(file=full_file)
    new_df <- rbind(df, olddata)
    saveRDS(new_df,file=full_file)
  }
  else {
    saveRDS(df, file=full_file)
  }
  current_data <- readRDS(file=full_file)
  return(current_data)
}
update_info <- function(df, val, curr="ryan.vtt", full_file="youtube.Rda") {
  row_index <- which(df$file_name == curr)
  df[row_index, "mlu"] <- val
  saveRDS(df, file=full_file)
  return(df)
}

Cleaning the input data from CHILDES

parse_mot_data <- function(lines) {
  results <- list()
  in_mot <- FALSE
  mot_text <- ""
  for (line in lines) {
    if (startsWith(line, "*CHI")) {
      in_mot <- TRUE
      mot_text <- paste(mot_text, str_replace(line, "^\\*CHI:\\s+", ""))
    }
    else if (startsWith(line, "*") || startsWith(line, "%")) {
      in_mot <- FALSE
    }
    else if (in_mot) {
      mot_text <- paste(mot_text, line)
    }
  }
  mot_text <- str_replace_all(mot_text, "@g", "")
  mot_text <- gsub("[^[:alnum:][:space:]?!.]", "", mot_text)
  return(mot_text)
}

Cleaning the input data from YouTube captions. good_cc_parsing accounts for actual captions that are formatted differently than the automated ones

parse_vtt_w_punctuation <- function(input_text) {
  indices <- grep("<c>", input_text)
  spoken_lines <- gsub("<[^>]+>", "", input_text[indices])
  pasted_lines <- paste(spoken_lines, collapse=" ")
  if (length(pasted_lines) <= 1) {
    pasted_lines = good_cc_parsing(input_text)
  }
  punctuated_lines <- add_punctuation(pasted_lines)
  return(punctuated_lines)
}
good_cc_parsing <- function(input_text) {
  modified_text <- ""
  # loop through each line of the input text
  for (i in seq_along(input_text)) {
    
    # check if the line starts with a timestamp
    if (grepl("^\\d{2}:\\d{2}:\\d{2}.\\d{3}", input_text[i])) {
      
      # if the line starts with a timestamp, skip it and the next line
      i <- i + 1
      
    } else {
      input_text[i] = gsub('"', '', input_text[i])
      # if the line does not start with a timestamp, check if it starts with a narrator's name
      if (grepl("^\\w+:", input_text[i])) {
        
        # if the line starts with a narrator's name, remove it
        modified_text <- paste(modified_text, sub("^\\w+:\\s*", "", input_text[i]))
        
      } else {
        # if the line does not start with a narrator's name, append it to the modified text
        modified_text <- paste(modified_text, input_text[i])
      }
      
    }
    
  }
  return(modified_text)
}

Util method used to merge frames. Initial versions of frames did not include information so this had to be used.

mergeFrames <- function(df, final_file="youtube.Rda") {
  df1 <- readRDS(file=final_file)
  df2 <- df
  commonNames <- names(df1)[which(colnames(df1) %in% colnames(df2))]
  commonNames <- commonNames[commonNames != "file_name"]
  dfmerge<- merge(df1,df2,by="file_name",all=T)
  for(i in commonNames){
    left <- paste(i, ".x", sep="")
    right <- paste(i, ".y", sep="")
    dfmerge[is.na(dfmerge[left]),left] <- dfmerge[is.na(dfmerge[left]),right]
    dfmerge[right]<- NULL
    colnames(dfmerge)[colnames(dfmerge) == left] <- i
  }
  saveRDS(dfmerge, file=final_file)
  return(readRDS(file=final_file))
}

This was a use-case for the mergeFrames method above

file_name <- "Nadig/135.cha"
output_file_name <- "135.Rda"
new_df <- data.frame(
  file_name, output_file_name, link="N/A")
new_df <- mergeFrames(new_df)

Quick util for when the last column added is incorrect

remove_last_column <- function() {
  current_data <- readRDS("youtube.Rda")
  current_data <- current_data[-c(1),]
  saveRDS(current_data, "youtube.Rda")
}
plot_distribution <- function(top_words, name) {
  # Create a bar graph of the top 25 words
  ggplot(top_words, aes(x = reorder(lemma, -n), y = n)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  xlab("Word") +
  ylab("Frequency") +
  theme(axis.text.y = element_text(size = 10)) +
  theme(axis.text.x = element_text(size = 5)) +
  ggtitle(name)
}

Workflow

Below is the process followed to add every file to the dataframe.
The following text is changed and is the basis of every video/transcript added.

output_file_name <- "cocomelon.Rda"
file_name <- "example/cocomelon.vtt"
video_name <- "You Can Ride a Bike Song | @CoComelon & Kids Songs | Learning Videos For Toddlers"
viewcount <- 154000000
link <- "https://www.youtube.com/watch?v=zj3UYhSsrwU"
programmed <- TRUE
youtubekids <- TRUE
educational <- TRUE
live <- FALSE
length_in_secs <- 2040
metadata <- ""
add_file_info <- function () {
  lines <- readLines(file_name)
  # If it is a video and not a transcript
  if (viewcount > 0) {
    parsed_lines <- parse_vtt_w_punctuation(lines)
  } else {
    parsed_lines <- parse_mot_data(lines)
  }
  current_data <- further_parsing(parsed_lines)
  return(current_data)
}
#df <- pos_spacy(parsed_lines)
#saveRDS(df, file=output_file_name)
#add_file_info()
current_data <- readRDS(file="youtube.Rda")
current_data

Data analysis

The main category for data analysis was direct comparison of YouTube Kids to maternal input. Further categories included comparing educational videos to non-educational ones and comparing programmed videos to non-programmed ones. The data pipeline is set up in a way where it can be extended to look at live videos vs non-live videos and add other input sources such as YouTube or a TV show.

df <- readRDS(file="youtube.Rda")
df_filtered <- subset(df, !(grepl("output", video_name) | (youtubekids == FALSE & viewcount > 0)))

Video accuracy

The video that was manually assessed for accuracy was Ryan’s Mystery Playdate Episode 1 for international RTR Fans.

ryan_vid <- readRDS(df_filtered[c(9),]$output_file_name)
wrong_tokens <- 11
right_tokens <- count(ryan_vid)$n - wrong_tokens
missing_tokens <- 22
right_sentence_segmentations <- 51
# Sentence segmentation too late
fwd_sentence_segmentations <- 13
# Sentence segmentation too early
back_sentence_seg <- 3
total_sentences <- df_filtered[c(9),]$num_utterances
new_sentences <- total_sentences + fwd_sentence_segmentations - back_sentence_seg
total_tokens <- df_filtered[c(9),]$token_frequency
new_total_tokens <- total_tokens + missing_tokens
orig_mlu <- df_filtered[c(9),]$mlu
new_mlu <- new_total_tokens / new_sentences

Precision and recall calculations

TP_sent <- right_sentence_segmentations
FP_sent <- fwd_sentence_segmentations + back_sentence_seg
FN_sent <- total_sentences - TP_sent

precision_sent <- TP_sent / (TP_sent + FP_sent)
recall_sent <- TP_sent / (TP_sent + FN_sent)

# Token recognition
TP_token <- right_tokens
FP_token <- wrong_tokens
FN_token <- new_total_tokens - TP_token

precision_token <- TP_token / (TP_token + FP_token)
recall_token <- TP_token / (TP_token + FN_token)

Accuracy table

df <- data.frame(
  word_precision=precision_token,
  word_recall=recall_token,
  sentence_precision=precision_sent,
  sentence_recall=recall_sent,
  original_mlu = orig_mlu,
  new_mlu = new_mlu
)

# Print the data frame
df

Mean lengths of utterances

# calculate weighted mean length of utterance by youtubekids
mlu_table <- aggregate(df_filtered[,c("mlu","num_utterances")], by=list(df_filtered$youtubekids), FUN=function(x) sum(x[1]*x[2])/sum(x[2]))

# add column names to the table
colnames(mlu_table) <- c("youtubekids", "mlu")

# print the table
print(mlu_table)

Datasets

df_youtubekids <- subset(df_filtered, (youtubekids == TRUE))
df_maternal <- subset(df_filtered, (youtubekids == FALSE))
df_programmed <- subset(df_youtubekids, (programmed == TRUE))
df_nonprogrammed <- subset(df_youtubekids, (programmed == FALSE))
df_educational <- subset(df_youtubekids, (educational == TRUE))
df_uneducational <- subset(df_youtubekids, (educational == FALSE))

Get the aggregated list of tokens with part of speech tags

combined <- function(df) {
  df_list <- lapply(df$output_file_name, function(name) {
  readRDS(name)
})
  # Use do.call() to combine the data frames into a single data frame
  combined_list <- do.call(rbind, df_list)
  return(combined_list)
}
youtubekids_data <- combined(df_youtubekids)
maternal_data <- combined(df_maternal)
yk_pos <- pos_list(youtubekids_data)
m_pos <- pos_list(maternal_data)
prog_pos <- pos_list(combined(df_programmed))
nonprog_pos <- pos_list(combined(df_nonprogrammed))
edu_pos <- pos_list(combined(df_educational))
nonedu_pos <- pos_list(combined(df_uneducational))

Verbs and nouns

Type and token frequency

yk_freqs <- tt_freqs(yk_pos$av)
m_freqs <- tt_freqs(m_pos$av)
yk_freqs_nouns <- tt_freqs(yk_pos$an)
m_freqs_nouns <- tt_freqs(m_pos$an)
nv <- data.frame(verbs=rbind(yk_freqs, m_freqs), nouns=rbind(yk_freqs_nouns, m_freqs_nouns))
rownames(nv) <- c("YouTube Kids", "Maternal Input")
nv

Very similar entropy, but more types seen in general for both nouns and verbs on YouTube Kids

Transitive and intransitive verbs

Type and token frequency

yk_freqs <- tt_freqs(yk_pos$tv)
m_freqs <- tt_freqs(m_pos$tv)
yk_freqs_iv <- tt_freqs(yk_pos$iv)
m_freqs_iv <- tt_freqs(m_pos$iv)
edu_pos <- pos_list(combined(df_educational))
nonedu_pos <- pos_list(combined(df_uneducational))
ti <- data.frame(transitive=rbind(yk_freqs, m_freqs, tt_freqs(prog_pos$tv), tt_freqs(nonprog_pos$tv), tt_freqs(edu_pos$tv), tt_freqs(nonedu_pos$tv)), intransitive=rbind(yk_freqs_iv, m_freqs_iv, tt_freqs(prog_pos$iv), tt_freqs(nonprog_pos$iv), tt_freqs(edu_pos$iv), tt_freqs(nonedu_pos$iv)))
rownames(ti) <- c("YouTube Kids", "Maternal Input", "Programmed", "Non-programmed", "Educational", "Non-educational")
ti

Slightly higher entropy with maternal input and much lower ratio
Programmed content seems to have a much lower entropy

yktv_df <- yk_pos$tv %>%
    count(lemma, sort=TRUE)
top_words <- yktv_df[order(-yktv_df$n),][1:25,]

# Create a bar graph of the top 25 words
plot_distribution(top_words, "Top 25 Most Popular YouTube Kids Transitive Verbs")

mtv_df <- m_pos$tv %>%
    count(lemma, sort=TRUE)
top_words <- mtv_df[order(-mtv_df$n),][1:25,]

# Create a bar graph of the top 25 words
plot_distribution(top_words, "Top 25 Most Popular Maternal Input Transitive Verbs")

Collostructional analysis

Collostructional analysis was used to measure the verb-transitive contingency, the strength of a verb being present in a transitive slot

Flach, Susanne. 2021. Collostructions: An R implementation for the family of collostructional methods. Package version v.0.2.0, https://sfla.ch/collostructions/.

# Collostructions package installation:
#install.packages(file.choose(), repos = NULL)
library(collostructions)

Formatting data to be accepted by collostructional parser

yk_counts <- youtubekids_data %>%
    count(lemma, sort=TRUE)
m_counts <- maternal_data %>%
    count(lemma, sort=TRUE)
yktv_df <- subset(yktv_df, lemma %in% mtv_df$lemma)
mtv_df <- subset(mtv_df, lemma %in% yktv_df$lemma)
colnames(yk_counts)[2] <- "CORP.FREQ"
colnames(yktv_df)[2] <- "CXN.FREQ"
c_yk <- merge(yk_counts, yktv_df, by="lemma")
c_yk_all <- data.frame(WORD=c_yk$lemma, CXN.FREQ=c_yk$CXN.FREQ, CORP.FREQ=c_yk$CORP.FREQ)

Running it through

collex(c_yk_all, 2547) -> c_yk_all.out
data.frame(word=c_yk_all.out$COLLEX, observation=c_yk_all.out$OBS, frequency=c_yk_all.out$CORP.FREQ, coll_strength=c_yk_all.out$COLL.STR.LOGL, association=c_yk_all.out$ASSOC, significance=c_yk_all.out$SIGNIF)
colnames(m_counts)[2] <- "CORP.FREQ"
colnames(mtv_df)[2] <- "CXN.FREQ"
c_m <- merge(m_counts, mtv_df, by="lemma")
c_m_all <- data.frame(WORD=c_m$lemma, CXN.FREQ=c_m$CXN.FREQ, CORP.FREQ=c_m$CORP.FREQ)
collex(c_m_all, 2547) -> c_m_all.out
data.frame(word=c_m_all.out$COLLEX, observation=c_m_all.out$OBS, frequency=c_m_all.out$CORP.FREQ, coll_strength=c_m_all.out$COLL.STR.LOGL, association=c_m_all.out$ASSOC, significance=c_m_all.out$SIGNIF)

Regular and irregular past tense forms

Type and token frequency

yk_freqs <- past_tense_seg(yk_pos$av)
m_freqs <- past_tense_seg(m_pos$av)
ti <- data.frame(ed_form=rbind(yk_freqs, m_freqs, past_tense_seg(prog_pos$av), past_tense_seg(nonprog_pos$av), past_tense_seg(edu_pos$av), past_tense_seg(nonedu_pos$av)))
rownames(ti) <- c("YouTube Kids", "Maternal Input", "Programmed", "Non-programmed", "Educational", "Non-educational")
ti
past_tense_verb_counts <- function(df) {
  verbs <- df$av %>%
    filter(tag == "VBN" | tag == "VBD") %>%
    filter(grepl("ed$", token))
  return(verbs %>%
    count(lemma, sort=TRUE))
}
past_tense_verbs_yk  <- past_tense_verb_counts(yk_pos)
past_tense_verbs <- past_tense_verb_counts(m_pos)
top_words_yk <- past_tense_verbs_yk[order(-past_tense_verbs_yk$n),][1:5,]
top_words_m <- past_tense_verbs [order(-past_tense_verbs$n),][1:5,]
data.frame(maternal_top_words=top_words_m, youtubekids_top_words=top_words_yk)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkoaHR0cikKbGlicmFyeShqc29ubGl0ZSkKbGlicmFyeShkb3RlbnYpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHNwYWN5cikKbGlicmFyeShkcGx5cikKIyBIdWdnaW5nRmFjZSBBUEkga2V5IG5lZWRzIHRvIGJlIGRlZmluZWQgYXMgQVBJX0tFWSBpbiAuZW52CmxvYWRfZG90X2VudigpCndkIDwtICIvVXNlcnMvdHNwcmkvRG9jdW1lbnRzL0NPR1MzMDciCnNldHdkKHdkKQpgYGAKCiMjIFNldHVwCgojIyMgVXRpbGl0eSBtZXRob2RzCgpFbnRyb3B5IGZvciB0eXBlIGFuZCB0b2tlbiBmcmVxdWVuY2llcwoKYGBge3J9CmVudHJvcHkgPC0gZnVuY3Rpb24oY291bnRzKSB7CiAgcGVyYyA8LSBjb3VudHMkbiAvIHN1bShjb3VudHMkbikKICAjIENvbXB1dGUgSG5vcm0KICBIbm9ybSA8LSAtc3VtKHBlcmMgKiBsb2cyKHBlcmMpKSAvIGxvZzIobGVuZ3RoKGNvdW50cykpCiAgcmV0dXJuKEhub3JtLzEwKQp9CmBgYAoKR2V0IHR5cGUgYW5kIHRva2VuIGZyZXF1ZW5jaWVzCgpgYGB7cn0KdHRfZnJlcXMgPC0gZnVuY3Rpb24oZGYpIHsKICBkZl9jb3VudCA8LSBkZiAlPiUKICAgIGNvdW50KGxlbW1hLCBzb3J0PVRSVUUpCiAgdHlwZV9mcmVxIDwtIG5yb3coZGZfY291bnQpCiAgdG9rZW5fZnJlcSA8LSBzdW0oZGZfY291bnQkbikKICBkZl9lbnRyb3B5IDwtIGVudHJvcHkoZGZfY291bnQpCiAgcmF0aW8gPC0gdHlwZV9mcmVxIC8gdG9rZW5fZnJlcQogIHJldHVybihkYXRhLmZyYW1lKHR5cGVfZj10eXBlX2ZyZXEsdG9rZW5fZj10b2tlbl9mcmVxLHR0X3JhdGlvPXJhdGlvLGVudHJvcHk9ZGZfZW50cm9weSkpCn0KYGBgCgpUeXBlIGFuZCB0b2tlbiBmcmVxdWVuY2llcyBmb3IgcmVndWxhciBhbmQgaXJyZWd1bGFyIHBhc3QgdGVuc2UgZm9ybXMuICdlZCcgc3RhbmRzIGZvciAnLWVkJyBzaW5jZSBhbGwgcmVndWxhciBwYXN0IHRlbnNlIGZvcm1zIGVuZCB3aXRoICctZWQnCgpgYGB7cn0KZWRfdHlwZV90b2tlbiA8LSBmdW5jdGlvbihwYXN0X3ZlcmJzKSB7CiAgZWRfdmVyYnMgPC0gcGFzdF92ZXJicyAlPiUKICAgIGZpbHRlcihncmVwbCgiZWQkIiwgdG9rZW4pKQogIHByaW50KGVkX3ZlcmJzKQogIGVkX3JhdGlvIDwtIChjb3VudChlZF92ZXJicykgLyBjb3VudChwYXN0X3ZlcmJzKSkkbgogIGVkX2RmIDwtIHR0X2ZyZXFzKGVkX3ZlcmJzKQogIGVkX2RmJGVkX3JhdGlvIDwtIGVkX3JhdGlvCiAgcmV0dXJuKGVkX2RmKQp9CmBgYAoKYGBge3J9Cm5vbl9lZF90eXBlX3Rva2VuIDwtIGZ1bmN0aW9uKHBhc3RfdmVyYnMpIHsKICBub25fZWRfdmVyYnMgPC0gcGFzdF92ZXJicyAlPiUKICAgIGZpbHRlcighZ3JlcGwoImVkJCIsIHRva2VuKSkKICAjcHJpbnQoZWRfdmVyYnMpCiAgbm9uX2VkX3JhdGlvIDwtIChjb3VudChub25fZWRfdmVyYnMpIC8gY291bnQocGFzdF92ZXJicykpJG4KICBub25fZWRfZGYgPC0gdHRfZnJlcXMobm9uX2VkX3ZlcmJzKQogIG5vbl9lZF9kZiRub25fZWRfcmF0aW8gPC0gbm9uX2VkX3JhdGlvCiAgcmV0dXJuKG5vbl9lZF9kZikKfQpgYGAKClNlZ21lbnRpbmcgcGFzdCB0ZW5zZSB2ZXJiIGluZm9ybWF0aW9uLCBmcm9tIGEgbGlzdCBvZiB2ZXJicwoKYGBge3J9CnBhc3RfdGVuc2Vfc2VnIDwtIGZ1bmN0aW9uKHZlcmJzKSB7CiAgcGFzdF90ZW5zZV92ZXJicyA8LSB2ZXJicyAlPiUKICAgIGZpbHRlcih0YWcgPT0gIlZCTiIgfCB0YWcgPT0gIlZCRCIpICU+JSAKICAgIG11dGF0ZShlbmRzX3dpdGhfZWQgPSBncmVwbCgiZWQkIiwgdG9rZW4pKQogIHBhc3RfY291bnQgPC0gcGFzdF90ZW5zZV92ZXJicyAlPiUKICAgIGNvdW50KGxlbW1hLCBzb3J0PVRSVUUpCiAgZWRfdmFsdWVzIDwtIGVkX3R5cGVfdG9rZW4ocGFzdF90ZW5zZV92ZXJicykKICBwcmludChjb3VudChwYXN0X3RlbnNlX3ZlcmJzKSkKICBwcmludChjb3VudCh2ZXJicykpCiAgZWRfdmFsdWVzJHBhc3RfcmF0aW8gPC0gKGNvdW50KHBhc3RfdGVuc2VfdmVyYnMpIC8gY291bnQodmVyYnMpKSRuCiAgcmV0dXJuKGVkX3ZhbHVlcykKfQpgYGAKClNlbnRlbmNlIHNlZ21lbnRhdGlvbiB1c2luZyBIdWdnaW5nRmFjZSBtb2RlbCBhbmQgZnVydGhlciBjbGVhbmluZyBvZiB0aGF0IG1vZGVsCgpgYGB7cn0KYWRkX3B1bmN0dWF0aW9uIDwtIGZ1bmN0aW9uKGlucHV0X3RleHQpIHsKICAjIExvYWQgQVBJIGtleSBmcm9tIC5lbnYgZmlsZQogIGFwaV9rZXkgPC0gU3lzLmdldGVudigiQVBJX0tFWSIpCiAgIyBEZWZpbmUgQVBJIGVuZHBvaW50IGFuZCBoZWFkZXJzCiAgYXBpX3VybCA8LSAiaHR0cHM6Ly9hcGktaW5mZXJlbmNlLmh1Z2dpbmdmYWNlLmNvL21vZGVscy9vbGl2ZXJndWhyL2Z1bGxzdG9wLXB1bmN0dWF0aW9uLW11bHRpbGFuZy1sYXJnZSIKICBoZWFkZXJzIDwtIGFkZF9oZWFkZXJzKEF1dGhvcml6YXRpb24gPSBwYXN0ZSgiQmVhcmVyIiwgYXBpX2tleSkpCiAgCiAgIyBEZWZpbmUgcGF5bG9hZCBhcyBhIGxpc3QKICBwYXlsb2FkIDwtIGxpc3QoaW5wdXRzID0gaW5wdXRfdGV4dCkKICAKICAjIE1ha2UgUE9TVCByZXF1ZXN0IGFuZCByZXR1cm4gcmVzdWx0CiAgcmVzcG9uc2UgPC0gUE9TVChhcGlfdXJsLCBoZWFkZXJzID0gaGVhZGVycywgYm9keSA9IHRvSlNPTihwYXlsb2FkKSkKICByZXN1bHQgPC0gY29udGVudChyZXNwb25zZSwgYXMgPSAidGV4dCIpCiAganNvbl9zdHIgPC0gcmVzdWx0W1sxXV0KICAjIFJlbW92ZSB0aGUgZXNjYXBlZCBxdW90ZXMgdG8gbWFrZSB0aGUgc3RyaW5nIHZhbGlkIEpTT04KICBqc29uX3N0ciA8LSBnc3ViKCdcXCInLCAnIicsIGpzb25fc3RyLCBmaXhlZCA9IFRSVUUpCiAgIyBQYXJzZSB0aGUgSlNPTiBpbnRvIGEgZGF0YSBmcmFtZQogIG91dHB1dCA8LSBmcm9tSlNPTihqc29uX3N0cilbMV0KICByZXR1cm4ocGFyc2Vfb3V0cHV0KG91dHB1dFsxXSkpCn0KcGFyc2Vfb3V0cHV0IDwtIGZ1bmN0aW9uKG91dHB1dCkgewogIGRmIDwtIGRhdGEuZnJhbWUodGV4dCA9IHNhcHBseShvdXRwdXQsIGZ1bmN0aW9uKHgpIHgkd29yZCksIGVudGl0eSA9IHNhcHBseShvdXRwdXQsIGZ1bmN0aW9uKHgpIGlmZWxzZSh4JGVudGl0eV9ncm91cCA9PSAiMCIsICIiLCB4JGVudGl0eV9ncm91cCkpKQogIG91dHB1dCR3b3JkX2VudGl0eSA8LSBwYXN0ZShvdXRwdXQkd29yZCwgaWZlbHNlKG91dHB1dCRlbnRpdHlfZ3JvdXAgPT0gIjAiLCAiIiwgb3V0cHV0JGVudGl0eV9ncm91cCksIHNlcCA9ICIiKQogIGRmJHdvcmRfZW50aXR5IDwtIHBhc3RlKGRmJHRleHQsIGlmZWxzZShkZiRlbnRpdHkgIT0gIiIsIGRmJGVudGl0eSwgIiIpLCBzZXA9IiIpCiAgcmVzdWx0IDwtIHBhc3RlKGRmJHdvcmRfZW50aXR5LCBjb2xsYXBzZSA9ICIgIikKICByZXR1cm4ocmVzdWx0KQp9CmBgYAoKR2V0dGluZyB0aGUgcGFydHMgb2Ygc3BlZWNoIGZyb20gYSBsaXN0IG9mIGxpbmVzIG9mIHRleHQgdXNpbmcgU3BhY3kKCmBgYHtyfQpwb3Nfc3BhY3kgPC0gZnVuY3Rpb24obGluZXMpIHsKICBzcGFjeV9pbml0aWFsaXplKG1vZGVsID0gImVuX2NvcmVfd2ViX21kIikKICBzZW50ZW5jZXMgPC0gc3BhY3lfcGFyc2UocGFyc2VkX2xpbmVzLCAic2VudGVuY2l6ZXIiLHBvcz1UUlVFLCB0YWc9VFJVRSwgbGVtbWE9VFJVRSxlbnRpdHk9RkFMU0UsIGRlcGVuZGVuY3k9VFJVRSwgbm91bnBocmFzZT1UUlVFKQogIGRmIDwtIGFzLmRhdGEuZnJhbWUoc2VudGVuY2VzKQogIHBhcnNlZF90ZXh0IDwtIHNlbnRlbmNlcwogIHBvcyA8LSBkZiRwb3MKICB0b2tlbnMgPC0gZGYkbGVtbWEKICByZXR1cm4oZGYpCn0KYGBgCgpJbml0aWFsIGRhdGEgc3RvcmVkIGZvciBldmVyeSBkYXRhIHNvdXJjZQoKYGBge3J9CmZ1cnRoZXJfcGFyc2luZyA8LSBmdW5jdGlvbihwYXJzZWRfbGluZXMpIHsKICBkZiA8LSBwb3Nfc3BhY3kocGFyc2VkX2xpbmVzKQogIG51bV93b3JkcyA8LSBucm93KGRmKQogIG51bV91dHRlcmFuY2VzIDwtIGxlbmd0aCh1bmlxdWUoZGYkc2VudGVuY2UpKQogIGRmX2NvdW50IDwtIGRmICU+JQogICAgY291bnQobGVtbWEsIHNvcnQ9VFJVRSkKICB3b3JkX3R5cGVfZnJlcSA8LSBucm93KGRmX2NvdW50KQogIHdvcmRfdG9rZW5fZnJlcSA8LSBzdW0oZGZfY291bnQkbikKICBtbHUgPC0gbnVtX3dvcmRzIC8gbnVtX3V0dGVyYW5jZXMKICBuZXdfZGYgPC0gZGF0YS5mcmFtZSgKICAgIGxpbmssIHZpZGVvX25hbWUsIGZpbGVfbmFtZSwgdmlld2NvdW50LCBsZW5ndGhfaW5fc2VjcywgbWV0YWRhdGEsIHByb2dyYW1tZWQsIGVkdWNhdGlvbmFsLCB5b3V0dWJla2lkcywgbGl2ZSwgCiAgICB0eXBlX2ZyZXF1ZW5jeT1jKHdvcmRfdHlwZV9mcmVxKSwgdG9rZW5fZnJlcXVlbmN5PWMod29yZF90b2tlbl9mcmVxKSxudW1fdXR0ZXJhbmNlcyxtbHUsIG91dHB1dF9maWxlX25hbWUKICApCiAgY3VycmVudF9kYXRhIDwtIGFkZGluZ19pbmZvKG5ld19kZikKICBzYXZlUkRTKGRmLCBmaWxlPW91dHB1dF9maWxlX25hbWUpCiAgcmV0dXJuKGN1cnJlbnRfZGF0YSkKfQpgYGAKCkZ1cnRoZXIgYW5hbHl6aW5nIHRoZSBwYXJ0cyBvZiBzcGVlY2ggdG8gZ2V0IHRyYW5zaXRpdmUgdmVyYnMsIGludHJhbnNpdGl2ZSB2ZXJicywgbm91bnMgYW5kIHZlcmJzCgpgYGB7cn0KcG9zX2xpc3QgPC0gZnVuY3Rpb24oZGYpIHsKICBhbGxfdmVyYnMgPC0gZGYgJT4lCiAgICBtdXRhdGUobmV4dF9wb3MgPSBsZWFkKHBvcyksCiAgICAgICAgICAgbmV4dF93b3JkID0gbGVhZChsZW1tYSksCiAgICAgICAgICAgbmV4dF9wb3MyID0gbGVhZChwb3MsIG4gPSAyKSwKICAgICAgICAgICBuZXh0X3dvcmQyID0gbGVhZChsZW1tYSwgbiA9IDIpLAogICAgICAgICAgIG5leHRfcG9zMyA9IGxlYWQocG9zLCBuID0gMyksCiAgICAgICAgICAgbmV4dF93b3JkMyA9IGxlYWQobGVtbWEsIG4gPSAzKSkgJT4lCiAgICBmaWx0ZXIocG9zICVpbiUgYygiVkVSQiIpKQogICN2ZXJiX2ZyZXFzIDwtIHR0X2ZyZXFzKGFsbF92ZXJicykKICAjdmVyYl9wYXN0X3RlbnNlIDwtIHBhc3RfdGVuc2Vfc2VnKGFsbF92ZXJicykKICBhbGxfbm91bnMgPC0gZGYgJT4lCiAgICBmaWx0ZXIocG9zICVpbiUgYygiTk9VTiIsICJQUk9OIikpCiAgdHJhbnNpdGl2ZV92ZXJicyA8LSBhbGxfdmVyYnMgJT4lCiAgICBmaWx0ZXIobGVtbWEgIT0gJ3MnKSAlPiUKICAgIGZpbHRlcihuZXh0X3BvcyAlaW4lIGMoIk5PVU4iLCAiUFJPTiIpIHwKICAgICAgICAgICAgIChuZXh0X3BvcyA9PSAiREVUIiAmIG5leHRfcG9zMiA9PSAiTk9VTiIpIHwKICAgICAgICAgICAgIChuZXh0X3BvcyA9PSAiQURKIiAmIG5leHRfcG9zMiA9PSAiTk9VTiIpIHwKICAgICAgICAgICAgIChuZXh0X3BvcyA9PSAiREVUIiAmIG5leHRfcG9zMiA9PSAiQURKIiAmIG5leHRfcG9zMyA9PSAiTk9VTiIpKSAKICBpbnRyYW5zaXRpdmVfdmVyYnMgPC0gYWxsX3ZlcmJzICU+JQogICAgZmlsdGVyKCEobmV4dF9wb3MgJWluJSBjKCJOT1VOIiwgIlBST04iKSB8CiAgICAgICAgICAgICAgIChuZXh0X3BvcyA9PSAiREVUIiAmIG5leHRfcG9zMiA9PSAiTk9VTiIpIHwKICAgICAgICAgICAgICAgKG5leHRfcG9zID09ICJBREoiICYgbmV4dF9wb3MyID09ICJOT1VOIikgfAogICAgICAgICAgICAgICAobmV4dF9wb3MgPT0gIkRFVCIgJiBuZXh0X3BvczIgPT0gIkFESiIgJiBuZXh0X3BvczMgPT0gIk5PVU4iKSkpCiAgcmV0dXJuKGxpc3QodHY9dHJhbnNpdGl2ZV92ZXJicywgaXY9aW50cmFuc2l0aXZlX3ZlcmJzLCBhdj1hbGxfdmVyYnMsIGFuPWFsbF9ub3VucykpCn0KYGBgCgpBZGRpbmcgbmV3IGluZm9ybWF0aW9uIHRvIHRoZSBtYWluIFJkYSBmaWxlCgpgYGB7cn0KYWRkaW5nX2luZm8gPC0gZnVuY3Rpb24oZGYsIGZ1bGxfZmlsZT0ieW91dHViZS5SZGEiKSB7CiAgaWYoZmlsZS5leGlzdHMoZnVsbF9maWxlKSkgewogICAgb2xkZGF0YSA8LSByZWFkUkRTKGZpbGU9ZnVsbF9maWxlKQogICAgbmV3X2RmIDwtIHJiaW5kKGRmLCBvbGRkYXRhKQogICAgc2F2ZVJEUyhuZXdfZGYsZmlsZT1mdWxsX2ZpbGUpCiAgfQogIGVsc2UgewogICAgc2F2ZVJEUyhkZiwgZmlsZT1mdWxsX2ZpbGUpCiAgfQogIGN1cnJlbnRfZGF0YSA8LSByZWFkUkRTKGZpbGU9ZnVsbF9maWxlKQogIHJldHVybihjdXJyZW50X2RhdGEpCn0KdXBkYXRlX2luZm8gPC0gZnVuY3Rpb24oZGYsIHZhbCwgY3Vycj0icnlhbi52dHQiLCBmdWxsX2ZpbGU9InlvdXR1YmUuUmRhIikgewogIHJvd19pbmRleCA8LSB3aGljaChkZiRmaWxlX25hbWUgPT0gY3VycikKICBkZltyb3dfaW5kZXgsICJtbHUiXSA8LSB2YWwKICBzYXZlUkRTKGRmLCBmaWxlPWZ1bGxfZmlsZSkKICByZXR1cm4oZGYpCn0KYGBgCgpDbGVhbmluZyB0aGUgaW5wdXQgZGF0YSBmcm9tIENISUxERVMKCmBgYHtyfQpwYXJzZV9tb3RfZGF0YSA8LSBmdW5jdGlvbihsaW5lcykgewogIHJlc3VsdHMgPC0gbGlzdCgpCiAgaW5fbW90IDwtIEZBTFNFCiAgbW90X3RleHQgPC0gIiIKICBmb3IgKGxpbmUgaW4gbGluZXMpIHsKICAgIGlmIChzdGFydHNXaXRoKGxpbmUsICIqQ0hJIikpIHsKICAgICAgaW5fbW90IDwtIFRSVUUKICAgICAgbW90X3RleHQgPC0gcGFzdGUobW90X3RleHQsIHN0cl9yZXBsYWNlKGxpbmUsICJeXFwqQ0hJOlxccysiLCAiIikpCiAgICB9CiAgICBlbHNlIGlmIChzdGFydHNXaXRoKGxpbmUsICIqIikgfHwgc3RhcnRzV2l0aChsaW5lLCAiJSIpKSB7CiAgICAgIGluX21vdCA8LSBGQUxTRQogICAgfQogICAgZWxzZSBpZiAoaW5fbW90KSB7CiAgICAgIG1vdF90ZXh0IDwtIHBhc3RlKG1vdF90ZXh0LCBsaW5lKQogICAgfQogIH0KICBtb3RfdGV4dCA8LSBzdHJfcmVwbGFjZV9hbGwobW90X3RleHQsICJAZyIsICIiKQogIG1vdF90ZXh0IDwtIGdzdWIoIlteWzphbG51bTpdWzpzcGFjZTpdPyEuXSIsICIiLCBtb3RfdGV4dCkKICByZXR1cm4obW90X3RleHQpCn0KYGBgCgpDbGVhbmluZyB0aGUgaW5wdXQgZGF0YSBmcm9tIFlvdVR1YmUgY2FwdGlvbnMuIGdvb2RfY2NfcGFyc2luZyBhY2NvdW50cyBmb3IgYWN0dWFsIGNhcHRpb25zIHRoYXQgYXJlIGZvcm1hdHRlZCBkaWZmZXJlbnRseSB0aGFuIHRoZSBhdXRvbWF0ZWQgb25lcwoKYGBge3J9CnBhcnNlX3Z0dF93X3B1bmN0dWF0aW9uIDwtIGZ1bmN0aW9uKGlucHV0X3RleHQpIHsKICBpbmRpY2VzIDwtIGdyZXAoIjxjPiIsIGlucHV0X3RleHQpCiAgc3Bva2VuX2xpbmVzIDwtIGdzdWIoIjxbXj5dKz4iLCAiIiwgaW5wdXRfdGV4dFtpbmRpY2VzXSkKICBwYXN0ZWRfbGluZXMgPC0gcGFzdGUoc3Bva2VuX2xpbmVzLCBjb2xsYXBzZT0iICIpCiAgaWYgKGxlbmd0aChwYXN0ZWRfbGluZXMpIDw9IDEpIHsKICAgIHBhc3RlZF9saW5lcyA9IGdvb2RfY2NfcGFyc2luZyhpbnB1dF90ZXh0KQogIH0KICBwdW5jdHVhdGVkX2xpbmVzIDwtIGFkZF9wdW5jdHVhdGlvbihwYXN0ZWRfbGluZXMpCiAgcmV0dXJuKHB1bmN0dWF0ZWRfbGluZXMpCn0KZ29vZF9jY19wYXJzaW5nIDwtIGZ1bmN0aW9uKGlucHV0X3RleHQpIHsKICBtb2RpZmllZF90ZXh0IDwtICIiCiAgIyBsb29wIHRocm91Z2ggZWFjaCBsaW5lIG9mIHRoZSBpbnB1dCB0ZXh0CiAgZm9yIChpIGluIHNlcV9hbG9uZyhpbnB1dF90ZXh0KSkgewogICAgCiAgICAjIGNoZWNrIGlmIHRoZSBsaW5lIHN0YXJ0cyB3aXRoIGEgdGltZXN0YW1wCiAgICBpZiAoZ3JlcGwoIl5cXGR7Mn06XFxkezJ9OlxcZHsyfS5cXGR7M30iLCBpbnB1dF90ZXh0W2ldKSkgewogICAgICAKICAgICAgIyBpZiB0aGUgbGluZSBzdGFydHMgd2l0aCBhIHRpbWVzdGFtcCwgc2tpcCBpdCBhbmQgdGhlIG5leHQgbGluZQogICAgICBpIDwtIGkgKyAxCiAgICAgIAogICAgfSBlbHNlIHsKICAgICAgaW5wdXRfdGV4dFtpXSA9IGdzdWIoJyInLCAnJywgaW5wdXRfdGV4dFtpXSkKICAgICAgIyBpZiB0aGUgbGluZSBkb2VzIG5vdCBzdGFydCB3aXRoIGEgdGltZXN0YW1wLCBjaGVjayBpZiBpdCBzdGFydHMgd2l0aCBhIG5hcnJhdG9yJ3MgbmFtZQogICAgICBpZiAoZ3JlcGwoIl5cXHcrOiIsIGlucHV0X3RleHRbaV0pKSB7CiAgICAgICAgCiAgICAgICAgIyBpZiB0aGUgbGluZSBzdGFydHMgd2l0aCBhIG5hcnJhdG9yJ3MgbmFtZSwgcmVtb3ZlIGl0CiAgICAgICAgbW9kaWZpZWRfdGV4dCA8LSBwYXN0ZShtb2RpZmllZF90ZXh0LCBzdWIoIl5cXHcrOlxccyoiLCAiIiwgaW5wdXRfdGV4dFtpXSkpCiAgICAgICAgCiAgICAgIH0gZWxzZSB7CiAgICAgICAgIyBpZiB0aGUgbGluZSBkb2VzIG5vdCBzdGFydCB3aXRoIGEgbmFycmF0b3IncyBuYW1lLCBhcHBlbmQgaXQgdG8gdGhlIG1vZGlmaWVkIHRleHQKICAgICAgICBtb2RpZmllZF90ZXh0IDwtIHBhc3RlKG1vZGlmaWVkX3RleHQsIGlucHV0X3RleHRbaV0pCiAgICAgIH0KICAgICAgCiAgICB9CiAgICAKICB9CiAgcmV0dXJuKG1vZGlmaWVkX3RleHQpCn0KYGBgCgpVdGlsIG1ldGhvZCB1c2VkIHRvIG1lcmdlIGZyYW1lcy4gSW5pdGlhbCB2ZXJzaW9ucyBvZiBmcmFtZXMgZGlkIG5vdCBpbmNsdWRlIGluZm9ybWF0aW9uIHNvIHRoaXMgaGFkIHRvIGJlIHVzZWQuCgpgYGB7cn0KbWVyZ2VGcmFtZXMgPC0gZnVuY3Rpb24oZGYsIGZpbmFsX2ZpbGU9InlvdXR1YmUuUmRhIikgewogIGRmMSA8LSByZWFkUkRTKGZpbGU9ZmluYWxfZmlsZSkKICBkZjIgPC0gZGYKICBjb21tb25OYW1lcyA8LSBuYW1lcyhkZjEpW3doaWNoKGNvbG5hbWVzKGRmMSkgJWluJSBjb2xuYW1lcyhkZjIpKV0KICBjb21tb25OYW1lcyA8LSBjb21tb25OYW1lc1tjb21tb25OYW1lcyAhPSAiZmlsZV9uYW1lIl0KICBkZm1lcmdlPC0gbWVyZ2UoZGYxLGRmMixieT0iZmlsZV9uYW1lIixhbGw9VCkKICBmb3IoaSBpbiBjb21tb25OYW1lcyl7CiAgICBsZWZ0IDwtIHBhc3RlKGksICIueCIsIHNlcD0iIikKICAgIHJpZ2h0IDwtIHBhc3RlKGksICIueSIsIHNlcD0iIikKICAgIGRmbWVyZ2VbaXMubmEoZGZtZXJnZVtsZWZ0XSksbGVmdF0gPC0gZGZtZXJnZVtpcy5uYShkZm1lcmdlW2xlZnRdKSxyaWdodF0KICAgIGRmbWVyZ2VbcmlnaHRdPC0gTlVMTAogICAgY29sbmFtZXMoZGZtZXJnZSlbY29sbmFtZXMoZGZtZXJnZSkgPT0gbGVmdF0gPC0gaQogIH0KICBzYXZlUkRTKGRmbWVyZ2UsIGZpbGU9ZmluYWxfZmlsZSkKICByZXR1cm4ocmVhZFJEUyhmaWxlPWZpbmFsX2ZpbGUpKQp9CmBgYAoKVGhpcyB3YXMgYSB1c2UtY2FzZSBmb3IgdGhlIG1lcmdlRnJhbWVzIG1ldGhvZCBhYm92ZQoKYGBge3J9CmZpbGVfbmFtZSA8LSAiTmFkaWcvMTM1LmNoYSIKb3V0cHV0X2ZpbGVfbmFtZSA8LSAiMTM1LlJkYSIKbmV3X2RmIDwtIGRhdGEuZnJhbWUoCiAgZmlsZV9uYW1lLCBvdXRwdXRfZmlsZV9uYW1lLCBsaW5rPSJOL0EiKQpuZXdfZGYgPC0gbWVyZ2VGcmFtZXMobmV3X2RmKQpgYGAKClF1aWNrIHV0aWwgZm9yIHdoZW4gdGhlIGxhc3QgY29sdW1uIGFkZGVkIGlzIGluY29ycmVjdAoKYGBge3J9CnJlbW92ZV9sYXN0X2NvbHVtbiA8LSBmdW5jdGlvbigpIHsKICBjdXJyZW50X2RhdGEgPC0gcmVhZFJEUygieW91dHViZS5SZGEiKQogIGN1cnJlbnRfZGF0YSA8LSBjdXJyZW50X2RhdGFbLWMoMSksXQogIHNhdmVSRFMoY3VycmVudF9kYXRhLCAieW91dHViZS5SZGEiKQp9CmBgYAoKYGBge3J9CnBsb3RfZGlzdHJpYnV0aW9uIDwtIGZ1bmN0aW9uKHRvcF93b3JkcywgbmFtZSkgewogICMgQ3JlYXRlIGEgYmFyIGdyYXBoIG9mIHRoZSB0b3AgMjUgd29yZHMKICBnZ3Bsb3QodG9wX3dvcmRzLCBhZXMoeCA9IHJlb3JkZXIobGVtbWEsIC1uKSwgeSA9IG4pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKwogIHhsYWIoIldvcmQiKSArCiAgeWxhYigiRnJlcXVlbmN5IikgKwogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gNSkpICsKICBnZ3RpdGxlKG5hbWUpCn0KYGBgCgojIyMgV29ya2Zsb3cKCkJlbG93IGlzIHRoZSBwcm9jZXNzIGZvbGxvd2VkIHRvIGFkZCBldmVyeSBmaWxlIHRvIHRoZSBkYXRhZnJhbWUuXApUaGUgZm9sbG93aW5nIHRleHQgaXMgY2hhbmdlZCBhbmQgaXMgdGhlIGJhc2lzIG9mIGV2ZXJ5IHZpZGVvL3RyYW5zY3JpcHQgYWRkZWQuCgpgYGB7cn0Kb3V0cHV0X2ZpbGVfbmFtZSA8LSAiY29jb21lbG9uLlJkYSIKZmlsZV9uYW1lIDwtICJleGFtcGxlL2NvY29tZWxvbi52dHQiCnZpZGVvX25hbWUgPC0gIllvdSBDYW4gUmlkZSBhIEJpa2UgU29uZyDvvZwgQENvQ29tZWxvbiAmIEtpZHMgU29uZ3Mg772cIExlYXJuaW5nIFZpZGVvcyBGb3IgVG9kZGxlcnMiCnZpZXdjb3VudCA8LSAxNTQwMDAwMDAKbGluayA8LSAiaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj16ajNVWWhTc3J3VSIKcHJvZ3JhbW1lZCA8LSBUUlVFCnlvdXR1YmVraWRzIDwtIFRSVUUKZWR1Y2F0aW9uYWwgPC0gVFJVRQpsaXZlIDwtIEZBTFNFCmxlbmd0aF9pbl9zZWNzIDwtIDIwNDAKbWV0YWRhdGEgPC0gIiIKYGBgCgpgYGB7cn0KYWRkX2ZpbGVfaW5mbyA8LSBmdW5jdGlvbiAoKSB7CiAgbGluZXMgPC0gcmVhZExpbmVzKGZpbGVfbmFtZSkKICAjIElmIGl0IGlzIGEgdmlkZW8gYW5kIG5vdCBhIHRyYW5zY3JpcHQKICBpZiAodmlld2NvdW50ID4gMCkgewogICAgcGFyc2VkX2xpbmVzIDwtIHBhcnNlX3Z0dF93X3B1bmN0dWF0aW9uKGxpbmVzKQogIH0gZWxzZSB7CiAgICBwYXJzZWRfbGluZXMgPC0gcGFyc2VfbW90X2RhdGEobGluZXMpCiAgfQogIGN1cnJlbnRfZGF0YSA8LSBmdXJ0aGVyX3BhcnNpbmcocGFyc2VkX2xpbmVzKQogIHJldHVybihjdXJyZW50X2RhdGEpCn0KI2RmIDwtIHBvc19zcGFjeShwYXJzZWRfbGluZXMpCiNzYXZlUkRTKGRmLCBmaWxlPW91dHB1dF9maWxlX25hbWUpCmBgYAoKYGBge3J9CiNhZGRfZmlsZV9pbmZvKCkKY3VycmVudF9kYXRhIDwtIHJlYWRSRFMoZmlsZT0ieW91dHViZS5SZGEiKQpjdXJyZW50X2RhdGEKYGBgCgojIyBEYXRhIGFuYWx5c2lzCgpUaGUgbWFpbiBjYXRlZ29yeSBmb3IgZGF0YSBhbmFseXNpcyB3YXMgZGlyZWN0IGNvbXBhcmlzb24gb2YgWW91VHViZSBLaWRzIHRvIG1hdGVybmFsIGlucHV0LiBGdXJ0aGVyIGNhdGVnb3JpZXMgaW5jbHVkZWQgY29tcGFyaW5nIGVkdWNhdGlvbmFsIHZpZGVvcyB0byBub24tZWR1Y2F0aW9uYWwgb25lcyBhbmQgY29tcGFyaW5nIHByb2dyYW1tZWQgdmlkZW9zIHRvIG5vbi1wcm9ncmFtbWVkIG9uZXMuIFRoZSBkYXRhIHBpcGVsaW5lIGlzIHNldCB1cCBpbiBhIHdheSB3aGVyZSBpdCBjYW4gYmUgZXh0ZW5kZWQgdG8gbG9vayBhdCBsaXZlIHZpZGVvcyB2cyBub24tbGl2ZSB2aWRlb3MgYW5kIGFkZCBvdGhlciBpbnB1dCBzb3VyY2VzIHN1Y2ggYXMgWW91VHViZSBvciBhIFRWIHNob3cuCgpgYGB7cn0KZGYgPC0gcmVhZFJEUyhmaWxlPSJ5b3V0dWJlLlJkYSIpCmRmX2ZpbHRlcmVkIDwtIHN1YnNldChkZiwgIShncmVwbCgib3V0cHV0IiwgdmlkZW9fbmFtZSkgfCAoeW91dHViZWtpZHMgPT0gRkFMU0UgJiB2aWV3Y291bnQgPiAwKSkpCmBgYAoKIyMjIFZpZGVvIGFjY3VyYWN5CgpUaGUgdmlkZW8gdGhhdCB3YXMgbWFudWFsbHkgYXNzZXNzZWQgZm9yIGFjY3VyYWN5IHdhcyBSeWFuJ3MgTXlzdGVyeSBQbGF5ZGF0ZSBFcGlzb2RlIDEgZm9yIGludGVybmF0aW9uYWwgUlRSIEZhbnMuCgpgYGB7cn0Kcnlhbl92aWQgPC0gcmVhZFJEUyhkZl9maWx0ZXJlZFtjKDkpLF0kb3V0cHV0X2ZpbGVfbmFtZSkKd3JvbmdfdG9rZW5zIDwtIDExCnJpZ2h0X3Rva2VucyA8LSBjb3VudChyeWFuX3ZpZCkkbiAtIHdyb25nX3Rva2VucwptaXNzaW5nX3Rva2VucyA8LSAyMgpyaWdodF9zZW50ZW5jZV9zZWdtZW50YXRpb25zIDwtIDUxCiMgU2VudGVuY2Ugc2VnbWVudGF0aW9uIHRvbyBsYXRlCmZ3ZF9zZW50ZW5jZV9zZWdtZW50YXRpb25zIDwtIDEzCiMgU2VudGVuY2Ugc2VnbWVudGF0aW9uIHRvbyBlYXJseQpiYWNrX3NlbnRlbmNlX3NlZyA8LSAzCnRvdGFsX3NlbnRlbmNlcyA8LSBkZl9maWx0ZXJlZFtjKDkpLF0kbnVtX3V0dGVyYW5jZXMKbmV3X3NlbnRlbmNlcyA8LSB0b3RhbF9zZW50ZW5jZXMgKyBmd2Rfc2VudGVuY2Vfc2VnbWVudGF0aW9ucyAtIGJhY2tfc2VudGVuY2Vfc2VnCnRvdGFsX3Rva2VucyA8LSBkZl9maWx0ZXJlZFtjKDkpLF0kdG9rZW5fZnJlcXVlbmN5Cm5ld190b3RhbF90b2tlbnMgPC0gdG90YWxfdG9rZW5zICsgbWlzc2luZ190b2tlbnMKb3JpZ19tbHUgPC0gZGZfZmlsdGVyZWRbYyg5KSxdJG1sdQpuZXdfbWx1IDwtIG5ld190b3RhbF90b2tlbnMgLyBuZXdfc2VudGVuY2VzCmBgYAoKUHJlY2lzaW9uIGFuZCByZWNhbGwgY2FsY3VsYXRpb25zCgpgYGB7cn0KVFBfc2VudCA8LSByaWdodF9zZW50ZW5jZV9zZWdtZW50YXRpb25zCkZQX3NlbnQgPC0gZndkX3NlbnRlbmNlX3NlZ21lbnRhdGlvbnMgKyBiYWNrX3NlbnRlbmNlX3NlZwpGTl9zZW50IDwtIHRvdGFsX3NlbnRlbmNlcyAtIFRQX3NlbnQKCnByZWNpc2lvbl9zZW50IDwtIFRQX3NlbnQgLyAoVFBfc2VudCArIEZQX3NlbnQpCnJlY2FsbF9zZW50IDwtIFRQX3NlbnQgLyAoVFBfc2VudCArIEZOX3NlbnQpCgojIFRva2VuIHJlY29nbml0aW9uClRQX3Rva2VuIDwtIHJpZ2h0X3Rva2VucwpGUF90b2tlbiA8LSB3cm9uZ190b2tlbnMKRk5fdG9rZW4gPC0gbmV3X3RvdGFsX3Rva2VucyAtIFRQX3Rva2VuCgpwcmVjaXNpb25fdG9rZW4gPC0gVFBfdG9rZW4gLyAoVFBfdG9rZW4gKyBGUF90b2tlbikKcmVjYWxsX3Rva2VuIDwtIFRQX3Rva2VuIC8gKFRQX3Rva2VuICsgRk5fdG9rZW4pCmBgYAoKQWNjdXJhY3kgdGFibGUKCmBgYHtyfQpkZiA8LSBkYXRhLmZyYW1lKAogIHdvcmRfcHJlY2lzaW9uPXByZWNpc2lvbl90b2tlbiwKICB3b3JkX3JlY2FsbD1yZWNhbGxfdG9rZW4sCiAgc2VudGVuY2VfcHJlY2lzaW9uPXByZWNpc2lvbl9zZW50LAogIHNlbnRlbmNlX3JlY2FsbD1yZWNhbGxfc2VudCwKICBvcmlnaW5hbF9tbHUgPSBvcmlnX21sdSwKICBuZXdfbWx1ID0gbmV3X21sdQopCgojIFByaW50IHRoZSBkYXRhIGZyYW1lCmRmCmBgYAoKIyMjIE1lYW4gbGVuZ3RocyBvZiB1dHRlcmFuY2VzCgpgYGB7cn0KIyBjYWxjdWxhdGUgd2VpZ2h0ZWQgbWVhbiBsZW5ndGggb2YgdXR0ZXJhbmNlIGJ5IHlvdXR1YmVraWRzCm1sdV90YWJsZSA8LSBhZ2dyZWdhdGUoZGZfZmlsdGVyZWRbLGMoIm1sdSIsIm51bV91dHRlcmFuY2VzIildLCBieT1saXN0KGRmX2ZpbHRlcmVkJHlvdXR1YmVraWRzKSwgRlVOPWZ1bmN0aW9uKHgpIHN1bSh4WzFdKnhbMl0pL3N1bSh4WzJdKSkKCiMgYWRkIGNvbHVtbiBuYW1lcyB0byB0aGUgdGFibGUKY29sbmFtZXMobWx1X3RhYmxlKSA8LSBjKCJ5b3V0dWJla2lkcyIsICJtbHUiKQoKIyBwcmludCB0aGUgdGFibGUKcHJpbnQobWx1X3RhYmxlKQpgYGAKCiMjIyBEYXRhc2V0cwoKYGBge3J9CmRmX3lvdXR1YmVraWRzIDwtIHN1YnNldChkZl9maWx0ZXJlZCwgKHlvdXR1YmVraWRzID09IFRSVUUpKQpkZl9tYXRlcm5hbCA8LSBzdWJzZXQoZGZfZmlsdGVyZWQsICh5b3V0dWJla2lkcyA9PSBGQUxTRSkpCmRmX3Byb2dyYW1tZWQgPC0gc3Vic2V0KGRmX3lvdXR1YmVraWRzLCAocHJvZ3JhbW1lZCA9PSBUUlVFKSkKZGZfbm9ucHJvZ3JhbW1lZCA8LSBzdWJzZXQoZGZfeW91dHViZWtpZHMsIChwcm9ncmFtbWVkID09IEZBTFNFKSkKZGZfZWR1Y2F0aW9uYWwgPC0gc3Vic2V0KGRmX3lvdXR1YmVraWRzLCAoZWR1Y2F0aW9uYWwgPT0gVFJVRSkpCmRmX3VuZWR1Y2F0aW9uYWwgPC0gc3Vic2V0KGRmX3lvdXR1YmVraWRzLCAoZWR1Y2F0aW9uYWwgPT0gRkFMU0UpKQpgYGAKCkdldCB0aGUgYWdncmVnYXRlZCBsaXN0IG9mIHRva2VucyB3aXRoIHBhcnQgb2Ygc3BlZWNoIHRhZ3MKCmBgYHtyfQpjb21iaW5lZCA8LSBmdW5jdGlvbihkZikgewogIGRmX2xpc3QgPC0gbGFwcGx5KGRmJG91dHB1dF9maWxlX25hbWUsIGZ1bmN0aW9uKG5hbWUpIHsKICByZWFkUkRTKG5hbWUpCn0pCiAgIyBVc2UgZG8uY2FsbCgpIHRvIGNvbWJpbmUgdGhlIGRhdGEgZnJhbWVzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZQogIGNvbWJpbmVkX2xpc3QgPC0gZG8uY2FsbChyYmluZCwgZGZfbGlzdCkKICByZXR1cm4oY29tYmluZWRfbGlzdCkKfQp5b3V0dWJla2lkc19kYXRhIDwtIGNvbWJpbmVkKGRmX3lvdXR1YmVraWRzKQptYXRlcm5hbF9kYXRhIDwtIGNvbWJpbmVkKGRmX21hdGVybmFsKQp5a19wb3MgPC0gcG9zX2xpc3QoeW91dHViZWtpZHNfZGF0YSkKbV9wb3MgPC0gcG9zX2xpc3QobWF0ZXJuYWxfZGF0YSkKcHJvZ19wb3MgPC0gcG9zX2xpc3QoY29tYmluZWQoZGZfcHJvZ3JhbW1lZCkpCm5vbnByb2dfcG9zIDwtIHBvc19saXN0KGNvbWJpbmVkKGRmX25vbnByb2dyYW1tZWQpKQplZHVfcG9zIDwtIHBvc19saXN0KGNvbWJpbmVkKGRmX2VkdWNhdGlvbmFsKSkKbm9uZWR1X3BvcyA8LSBwb3NfbGlzdChjb21iaW5lZChkZl91bmVkdWNhdGlvbmFsKSkKYGBgCgojIyMgVmVyYnMgYW5kIG5vdW5zCgojIyMjIFR5cGUgYW5kIHRva2VuIGZyZXF1ZW5jeQoKYGBge3J9CnlrX2ZyZXFzIDwtIHR0X2ZyZXFzKHlrX3BvcyRhdikKbV9mcmVxcyA8LSB0dF9mcmVxcyhtX3BvcyRhdikKeWtfZnJlcXNfbm91bnMgPC0gdHRfZnJlcXMoeWtfcG9zJGFuKQptX2ZyZXFzX25vdW5zIDwtIHR0X2ZyZXFzKG1fcG9zJGFuKQpudiA8LSBkYXRhLmZyYW1lKHZlcmJzPXJiaW5kKHlrX2ZyZXFzLCBtX2ZyZXFzKSwgbm91bnM9cmJpbmQoeWtfZnJlcXNfbm91bnMsIG1fZnJlcXNfbm91bnMpKQpyb3duYW1lcyhudikgPC0gYygiWW91VHViZSBLaWRzIiwgIk1hdGVybmFsIElucHV0IikKbnYKYGBgCgpWZXJ5IHNpbWlsYXIgZW50cm9weSwgYnV0IG1vcmUgdHlwZXMgc2VlbiBpbiBnZW5lcmFsIGZvciBib3RoIG5vdW5zIGFuZCB2ZXJicyBvbiBZb3VUdWJlIEtpZHMKCiMjIyBUcmFuc2l0aXZlIGFuZCBpbnRyYW5zaXRpdmUgdmVyYnMKCiMjIyMgVHlwZSBhbmQgdG9rZW4gZnJlcXVlbmN5CgpgYGB7ciwgcmVzdWx0cz0naGlkZSd9CnlrX2ZyZXFzIDwtIHR0X2ZyZXFzKHlrX3BvcyR0dikKbV9mcmVxcyA8LSB0dF9mcmVxcyhtX3BvcyR0dikKeWtfZnJlcXNfaXYgPC0gdHRfZnJlcXMoeWtfcG9zJGl2KQptX2ZyZXFzX2l2IDwtIHR0X2ZyZXFzKG1fcG9zJGl2KQplZHVfcG9zIDwtIHBvc19saXN0KGNvbWJpbmVkKGRmX2VkdWNhdGlvbmFsKSkKbm9uZWR1X3BvcyA8LSBwb3NfbGlzdChjb21iaW5lZChkZl91bmVkdWNhdGlvbmFsKSkKdGkgPC0gZGF0YS5mcmFtZSh0cmFuc2l0aXZlPXJiaW5kKHlrX2ZyZXFzLCBtX2ZyZXFzLCB0dF9mcmVxcyhwcm9nX3BvcyR0diksIHR0X2ZyZXFzKG5vbnByb2dfcG9zJHR2KSwgdHRfZnJlcXMoZWR1X3BvcyR0diksIHR0X2ZyZXFzKG5vbmVkdV9wb3MkdHYpKSwgaW50cmFuc2l0aXZlPXJiaW5kKHlrX2ZyZXFzX2l2LCBtX2ZyZXFzX2l2LCB0dF9mcmVxcyhwcm9nX3BvcyRpdiksIHR0X2ZyZXFzKG5vbnByb2dfcG9zJGl2KSwgdHRfZnJlcXMoZWR1X3BvcyRpdiksIHR0X2ZyZXFzKG5vbmVkdV9wb3MkaXYpKSkKcm93bmFtZXModGkpIDwtIGMoIllvdVR1YmUgS2lkcyIsICJNYXRlcm5hbCBJbnB1dCIsICJQcm9ncmFtbWVkIiwgIk5vbi1wcm9ncmFtbWVkIiwgIkVkdWNhdGlvbmFsIiwgIk5vbi1lZHVjYXRpb25hbCIpCmBgYAoKYGBge3J9CnRpCmBgYAoKU2xpZ2h0bHkgaGlnaGVyIGVudHJvcHkgd2l0aCBtYXRlcm5hbCBpbnB1dCBhbmQgbXVjaCBsb3dlciByYXRpb1wKUHJvZ3JhbW1lZCBjb250ZW50IHNlZW1zIHRvIGhhdmUgYSBtdWNoIGxvd2VyIGVudHJvcHkKCmBgYHtyfQp5a3R2X2RmIDwtIHlrX3BvcyR0diAlPiUKICAgIGNvdW50KGxlbW1hLCBzb3J0PVRSVUUpCnRvcF93b3JkcyA8LSB5a3R2X2RmW29yZGVyKC15a3R2X2RmJG4pLF1bMToyNSxdCgojIENyZWF0ZSBhIGJhciBncmFwaCBvZiB0aGUgdG9wIDI1IHdvcmRzCnBsb3RfZGlzdHJpYnV0aW9uKHRvcF93b3JkcywgIlRvcCAyNSBNb3N0IFBvcHVsYXIgWW91VHViZSBLaWRzIFRyYW5zaXRpdmUgVmVyYnMiKQoKYGBgCgpgYGB7cn0KbXR2X2RmIDwtIG1fcG9zJHR2ICU+JQogICAgY291bnQobGVtbWEsIHNvcnQ9VFJVRSkKdG9wX3dvcmRzIDwtIG10dl9kZltvcmRlcigtbXR2X2RmJG4pLF1bMToyNSxdCgojIENyZWF0ZSBhIGJhciBncmFwaCBvZiB0aGUgdG9wIDI1IHdvcmRzCnBsb3RfZGlzdHJpYnV0aW9uKHRvcF93b3JkcywgIlRvcCAyNSBNb3N0IFBvcHVsYXIgTWF0ZXJuYWwgSW5wdXQgVHJhbnNpdGl2ZSBWZXJicyIpCmBgYAoKIyMjIyBDb2xsb3N0cnVjdGlvbmFsIGFuYWx5c2lzCgpDb2xsb3N0cnVjdGlvbmFsIGFuYWx5c2lzIHdhcyB1c2VkIHRvIG1lYXN1cmUgdGhlIHZlcmItdHJhbnNpdGl2ZSBjb250aW5nZW5jeSwgdGhlIHN0cmVuZ3RoIG9mIGEgdmVyYiBiZWluZyBwcmVzZW50IGluIGEgdHJhbnNpdGl2ZSBzbG90CgpGbGFjaCwgU3VzYW5uZS4gMjAyMS4gKkNvbGxvc3RydWN0aW9uczogQW4gUiBpbXBsZW1lbnRhdGlvbiBmb3IgdGhlIGZhbWlseSBvZiBjb2xsb3N0cnVjdGlvbmFsIG1ldGhvZHMqLiBQYWNrYWdlIHZlcnNpb24gdi4wLjIuMCwgPGh0dHBzOi8vc2ZsYS5jaC9jb2xsb3N0cnVjdGlvbnMvLj4KCmBgYHtyfQojIENvbGxvc3RydWN0aW9ucyBwYWNrYWdlIGluc3RhbGxhdGlvbjoKI2luc3RhbGwucGFja2FnZXMoZmlsZS5jaG9vc2UoKSwgcmVwb3MgPSBOVUxMKQpsaWJyYXJ5KGNvbGxvc3RydWN0aW9ucykKYGBgCgpGb3JtYXR0aW5nIGRhdGEgdG8gYmUgYWNjZXB0ZWQgYnkgY29sbG9zdHJ1Y3Rpb25hbCBwYXJzZXIKCmBgYHtyfQp5a19jb3VudHMgPC0geW91dHViZWtpZHNfZGF0YSAlPiUKICAgIGNvdW50KGxlbW1hLCBzb3J0PVRSVUUpCm1fY291bnRzIDwtIG1hdGVybmFsX2RhdGEgJT4lCiAgICBjb3VudChsZW1tYSwgc29ydD1UUlVFKQp5a3R2X2RmIDwtIHN1YnNldCh5a3R2X2RmLCBsZW1tYSAlaW4lIG10dl9kZiRsZW1tYSkKbXR2X2RmIDwtIHN1YnNldChtdHZfZGYsIGxlbW1hICVpbiUgeWt0dl9kZiRsZW1tYSkKY29sbmFtZXMoeWtfY291bnRzKVsyXSA8LSAiQ09SUC5GUkVRIgpjb2xuYW1lcyh5a3R2X2RmKVsyXSA8LSAiQ1hOLkZSRVEiCmNfeWsgPC0gbWVyZ2UoeWtfY291bnRzLCB5a3R2X2RmLCBieT0ibGVtbWEiKQpjX3lrX2FsbCA8LSBkYXRhLmZyYW1lKFdPUkQ9Y195ayRsZW1tYSwgQ1hOLkZSRVE9Y195ayRDWE4uRlJFUSwgQ09SUC5GUkVRPWNfeWskQ09SUC5GUkVRKQpgYGAKClJ1bm5pbmcgaXQgdGhyb3VnaAoKYGBge3J9CmNvbGxleChjX3lrX2FsbCwgMjU0NykgLT4gY195a19hbGwub3V0CmRhdGEuZnJhbWUod29yZD1jX3lrX2FsbC5vdXQkQ09MTEVYLCBvYnNlcnZhdGlvbj1jX3lrX2FsbC5vdXQkT0JTLCBmcmVxdWVuY3k9Y195a19hbGwub3V0JENPUlAuRlJFUSwgY29sbF9zdHJlbmd0aD1jX3lrX2FsbC5vdXQkQ09MTC5TVFIuTE9HTCwgYXNzb2NpYXRpb249Y195a19hbGwub3V0JEFTU09DLCBzaWduaWZpY2FuY2U9Y195a19hbGwub3V0JFNJR05JRikKYGBgCgpgYGB7cn0KY29sbmFtZXMobV9jb3VudHMpWzJdIDwtICJDT1JQLkZSRVEiCmNvbG5hbWVzKG10dl9kZilbMl0gPC0gIkNYTi5GUkVRIgpjX20gPC0gbWVyZ2UobV9jb3VudHMsIG10dl9kZiwgYnk9ImxlbW1hIikKY19tX2FsbCA8LSBkYXRhLmZyYW1lKFdPUkQ9Y19tJGxlbW1hLCBDWE4uRlJFUT1jX20kQ1hOLkZSRVEsIENPUlAuRlJFUT1jX20kQ09SUC5GUkVRKQpgYGAKCmBgYHtyfQpjb2xsZXgoY19tX2FsbCwgMjU0NykgLT4gY19tX2FsbC5vdXQKZGF0YS5mcmFtZSh3b3JkPWNfbV9hbGwub3V0JENPTExFWCwgb2JzZXJ2YXRpb249Y19tX2FsbC5vdXQkT0JTLCBmcmVxdWVuY3k9Y19tX2FsbC5vdXQkQ09SUC5GUkVRLCBjb2xsX3N0cmVuZ3RoPWNfbV9hbGwub3V0JENPTEwuU1RSLkxPR0wsIGFzc29jaWF0aW9uPWNfbV9hbGwub3V0JEFTU09DLCBzaWduaWZpY2FuY2U9Y19tX2FsbC5vdXQkU0lHTklGKQpgYGAKCiMjIyBSZWd1bGFyIGFuZCBpcnJlZ3VsYXIgcGFzdCB0ZW5zZSBmb3JtcwoKIyMjIyBUeXBlIGFuZCB0b2tlbiBmcmVxdWVuY3kKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KeWtfZnJlcXMgPC0gcGFzdF90ZW5zZV9zZWcoeWtfcG9zJGF2KQptX2ZyZXFzIDwtIHBhc3RfdGVuc2Vfc2VnKG1fcG9zJGF2KQp0aSA8LSBkYXRhLmZyYW1lKGVkX2Zvcm09cmJpbmQoeWtfZnJlcXMsIG1fZnJlcXMsIHBhc3RfdGVuc2Vfc2VnKHByb2dfcG9zJGF2KSwgcGFzdF90ZW5zZV9zZWcobm9ucHJvZ19wb3MkYXYpLCBwYXN0X3RlbnNlX3NlZyhlZHVfcG9zJGF2KSwgcGFzdF90ZW5zZV9zZWcobm9uZWR1X3BvcyRhdikpKQpyb3duYW1lcyh0aSkgPC0gYygiWW91VHViZSBLaWRzIiwgIk1hdGVybmFsIElucHV0IiwgIlByb2dyYW1tZWQiLCAiTm9uLXByb2dyYW1tZWQiLCAiRWR1Y2F0aW9uYWwiLCAiTm9uLWVkdWNhdGlvbmFsIikKYGBgCgpgYGB7cn0KdGkKYGBgCgpgYGB7cn0KcGFzdF90ZW5zZV92ZXJiX2NvdW50cyA8LSBmdW5jdGlvbihkZikgewogIHZlcmJzIDwtIGRmJGF2ICU+JQogICAgZmlsdGVyKHRhZyA9PSAiVkJOIiB8IHRhZyA9PSAiVkJEIikgJT4lCiAgICBmaWx0ZXIoZ3JlcGwoImVkJCIsIHRva2VuKSkKICByZXR1cm4odmVyYnMgJT4lCiAgICBjb3VudChsZW1tYSwgc29ydD1UUlVFKSkKfQpwYXN0X3RlbnNlX3ZlcmJzX3lrICA8LSBwYXN0X3RlbnNlX3ZlcmJfY291bnRzKHlrX3BvcykKcGFzdF90ZW5zZV92ZXJicyA8LSBwYXN0X3RlbnNlX3ZlcmJfY291bnRzKG1fcG9zKQp0b3Bfd29yZHNfeWsgPC0gcGFzdF90ZW5zZV92ZXJic195a1tvcmRlcigtcGFzdF90ZW5zZV92ZXJic195ayRuKSxdWzE6NSxdCnRvcF93b3Jkc19tIDwtIHBhc3RfdGVuc2VfdmVyYnMgW29yZGVyKC1wYXN0X3RlbnNlX3ZlcmJzJG4pLF1bMTo1LF0KZGF0YS5mcmFtZShtYXRlcm5hbF90b3Bfd29yZHM9dG9wX3dvcmRzX20sIHlvdXR1YmVraWRzX3RvcF93b3Jkcz10b3Bfd29yZHNfeWspCmBgYAo=