1 Getting started

To copy the code, click the button in the upper right corner of the code-chunks.

1.1 clean up

rm(list = ls())
gc()


1.2 custom functions

We defined a number custom functions, at Download custom_functions.R.

source("./custom_functions.R")


1.3 necessary packages

  • tidyverse: data wrangling
  • igraph: generate and visualize graphs
  • parallel: parallel computing to speed up simulation
  • foreach: looping in parallel
  • doParallel: parallel backend for foreach
  • ggplot2: data visualization
  • ggh4x: hacks for ggplot2
  • ggpubr: make visualizations publication-ready
packages = c("tidyverse", "igraph", "ggplot2", "parallel", "doParallel", "foreach", "ggh4x", "ggpubr",
    "plotly", "RColorBrewer", "grid", "gridExtra", "patchwork", "ggplotify", "ggraph", "gganimate", "RColorBrewer",
    "ggtext", "magick", "jsonlite", "lubridate", "ggtext")

invisible(fpackage.check(packages))
rm(packages)

2 import data

data <- read.csv("./rawdata/all_apps_wide_2026-02-12.csv")
times <- read.csv("./rawdata/PageTimes-2026-02-12.csv")

# cbind(data$participant.node,data$participant.role)[complete.cases(
# #cbind(data$participant.node,data$participant.role)),]

# data[, c('participant.bonus', 'participant.label')][!is.na(data$participant.bonus),]

3 results

On 12-2-2026, I recruited 100 Prolific participants, to populate a network of N=50 (with a 10% minority group).

net <- jsonlite::fromJSON("./networks/network_test_n50.json")

g <- graph_from_adjacency_matrix(net$adj_matrix, mode = "undirected")
V(g)$role <- ifelse(net$role_vector == 1, "trendsetter", "conformist")

fplot_graph(g)


3.1 diagnostics

3.1.1 arrival from Prolific

# subset experimental session
data <- data[data$session.code == "bg6q9igc", ]
times <- times[times$session_code == "bg6q9igc", ]

# clean
test <- data %>%
    transmute(participant_id = participant.code, participant_label = participant.label, id_in_session = participant.id_in_session,
        consent_given = consent.1.player.consent, consent_timestamp = consent.1.player.consent_timestamp,
        role = participant.role, is_dropout = participant.is_dropout, dropout_app = participant._current_app_name,
        comprehension_retries = comprehension.1.player.comprehension_retries, passed_comprehension = !participant._current_app_name %in%
            c("consent", "comprehension"), choice = unpop.1.player.choice, failed_checks = participant.failed_checks,
        exit_early = participant.exit_early, participated = participant._current_page_name == "PaymentInfo") %>%
    filter(!is.na(consent_given)) %>%
    mutate(bot = ifelse(participant_label == "", 1, 0), bot = factor(bot, levels = c(0, 1), labels = c("Prolific participant",
        "Bot")), consent_timestamp = ymd_hms(consent_timestamp), final_state = case_when(failed_checks >
        0 ~ "Failed comprehension", exit_early == 1 ~ "Could not be grouped", participated ~ "Participated",
        TRUE ~ "Too late")) %>%
    arrange(consent_timestamp) %>%
    mutate(arrival_order = row_number())

ggplot(test, aes(x = consent_timestamp, y = arrival_order)) + geom_point(aes(color = role, shape = bot),
    size = 3, alpha = 0.5) + scale_shape_manual(values = c(16, 2)) + scale_color_manual(values = c("blue",
    "red")) + labs(x = "Arrival time", y = "Arrival order", color = "Role", shape = "Type", title = "Participant arrivals from Prolific over time") +
    theme_minimal()

ggplot(test, aes(x = consent_timestamp, y = arrival_order)) + geom_point(aes(color = final_state), size = 3,
    alpha = 0.6) + scale_color_manual(values = c(Participated = "green", `Failed comprehension` = "orange",
    `Could not be grouped` = "red", `Too late` = "gray")) + facet_wrap(~role) + labs(x = "Arrival time",
    y = "Arrival order", color = "Final state", title = "Participant arrivals by role and final state") +
    theme_minimal()

fshowdf(table(test$final_state, test$role), caption = "participant status by role")
participant status by role
Blue Red
Could not be grouped 17 7
Failed comprehension 8 35
Participated 5 45
Too late 0 2


3.1.2 progression through the experiment

times <- times %>%
    mutate(timestamp = as_datetime(epoch_time_completed))

arrival_times <- times %>%
    group_by(participant_id_in_session) %>%
    summarize(arrival_time = min(timestamp), .groups = "drop")

times <- times %>%
    left_join(arrival_times, by = "participant_id_in_session") %>%
    mutate(participant_ordered = factor(participant_id_in_session, levels = arrival_times %>%
        arrange(arrival_time) %>%
        pull(participant_id_in_session)))

times_roles <- times %>%
    left_join(test %>%
        select(id_in_session, role), by = c(participant_id_in_session = "id_in_session"))

page_levels <- unique(times$page_name)

times_roles <- times_roles %>%
    mutate(page_name = factor(page_name, levels = page_levels))

custom_colors <- c(InitializeParticipant = "#c6dbef", ConsentPage = "#9ecae1", IntroductionPage = "#6baed6",
    ComprehensionPage = "#3182bd", NetworkFormationWaitPage = "#ffcc99", DecisionPage = "#ff9966", ResultsWaitPage = "#ff6666",
    ResultsPage = "#cc0033", FinalGameResults = "#660000")

# colored y-axis labels based on role
y_labels_colored <- times_roles %>%
    select(participant_ordered, role) %>%
    distinct() %>%
    arrange(participant_ordered) %>%
    mutate(label_colored = case_when(role == "Red" ~ paste0("<span style='color:red'>", participant_ordered,
        "</span>"), role == "Blue" ~ paste0("<span style='color:blue'>", participant_ordered, "</span>"),
        TRUE ~ paste0("<span style='color:darkgrey'>", participant_ordered, "</span>")))

# create a named vector for scale_y_discrete labels
y_labels_vector <- y_labels_colored$label_colored
names(y_labels_vector) <- y_labels_colored$participant_ordered

ggplot(times_roles[times_roles$round_number == 1, ], aes(x = timestamp, y = participant_ordered, color = page_name)) +
    geom_line(aes(group = participant_id_in_session), size = 1) + geom_point(size = 2) + scale_color_manual(values = custom_colors) +
    scale_y_discrete(labels = y_labels_vector) + labs(x = "Time", y = "Participant (ordered by arrival)",
    color = "Stage/Page", title = "Participant progression through experiment stages (by arrival)") +
    theme_minimal() + theme(axis.text.y = element_markdown(size = 6))

3.1.3 dropout

dropout_long <- data %>%
  filter(participant._current_page_name == "PaymentInfo") %>% # only completed participants
  filter(participant.label != "") %>% # exclude bots (participants controlled by experimener have no label)
  select(
    participant.label,
    participant.role,
    matches("unpop\\.[0-9]+\\.player\\.is_dropout")
  ) %>%
  pivot_longer(
    cols = matches("unpop\\.[0-9]+\\.player\\.is_dropout"),
    names_to = "round",
    values_to = "is_dropout"
  ) %>%
  mutate(
    round = str_extract(round, "[0-9]+"),
    round = as.numeric(round)
  )


# get first dropout round per participant
dropout_summary <- dropout_long %>%
  group_by(participant.label, participant.role) %>%
  summarise(
    event = any(is_dropout == 1, na.rm = TRUE),
    dropout_round = ifelse(
      event,
      min(round[is_dropout == 1], na.rm = TRUE),
      30
    ),
    .groups = "drop"
  )

# count cumulative dropouts
cum_dropout_role <- dropout_summary %>%
  filter(event == TRUE) %>%
  count(participant.role, dropout_round) %>%
  group_by(participant.role) %>%
  complete(dropout_round = 1:30, fill = list(n = 0)) %>%
  arrange(participant.role, dropout_round) %>%
  mutate(
    cumulative_dropout = cumsum(n)
  ) %>%
  ungroup()

ggplot(cum_dropout_role,
       aes(x = dropout_round,
           y = cumulative_dropout,
           color = participant.role)) +
  geom_line(linewidth = 1.2) +
  geom_point() +
  scale_x_continuous(breaks = 1:30) +
  scale_color_manual(
    values = c(
      "Red" = "red",
      "Blue" = "blue"
    )) +
  labs(
    x = "Round",
    y = "Dropout",
    color = "Role",
    title = "Cumulative dropout (by role)"
  ) +
  theme_minimal()


3.2 unpopular norm spread

# choice behavior over rounds:
df_long <- data %>%
  filter(participant._current_page_name == "PaymentInfo") %>% # filter actual participants
  select(participant.label, participant.role, participant.node, starts_with("unpop.")) %>% #also include network node.
  
  pivot_longer(
    cols = matches("unpop\\.\\d+\\.player\\.choice$"),  # only choice columns
    names_to = "round",
    values_to = "choice"
  ) %>%
  
  mutate(
    round = as.integer(gsub("unpop\\.(\\d+)\\.player\\.choice", "\\1", round)),
    is_bot = ifelse(participant.label == "", TRUE, FALSE)
  ) %>%
  select(participant.label, participant.node, participant.role, round, choice, is_bot)

# identify round of dropout:
first_dropout <- dropout_long %>%
  filter(is_dropout == 1) %>%
  group_by(participant.label) %>%
  summarise(dropout_round = min(round),
            .groups = "drop")

# and add to the df:
df_long <- df_long %>%
  left_join(first_dropout, by = "participant.label")

# aggregated
df_plot <- df_long %>%
  group_by(round) %>%
  summarise(
    pct_choice1 = mean(choice, na.rm = TRUE) * 100,  # proportion * 100
    n = n()
  )

ggplot(df_plot, aes(x = round, y = pct_choice1)) +
  geom_line(group = 1, color = "steelblue", size = .5) +
  geom_point(color = "steelblue", size = 2) +
   geom_hline(yintercept = 10, linetype = "longdash", color = "darkgrey", size = 0.8) +  # dashed line at 10
  scale_x_continuous(breaks = df_plot$round) +
  scale_y_continuous(limits = c(0, 100)) +  
  labs(
    x = "Round",
    y = "% agents choosing 'blue'",
    title = "Evolution of an unpopular norm"
  )

#sort by node
df_long <- df_long %>%
  arrange(participant.node) %>%
  select(-participant.label)

df_long$dropout_round[is.na(df_long$dropout_round)] <- 30 #non dropouts, set to 30.
# make roles consistent with utility function roles
df_long$role <- ifelse(df_long$participant.role == "Blue", "trendsetter", "conformist")

# specificy incentive structure parameters
params = list(s = 15, e = 10, w = 40, z = 50, lambda1 = 5, lambda2 = 1.8)

df_long$id <- df_long$participant.node + 1 #nodes are 0-indexed

#add degree
deg <- degree(g)
df_long <- df_long %>%
  mutate(degree = deg[id])

calculate_round_utilities <- function(current_round, df_long, network, params) {
  # previous round data
  df_prev <- df_long %>%
    filter(round == current_round - 1) %>%
    select(id, role, choice)
  
  # current round data
  df_curr <- df_long %>%
    filter(round == current_round)
  
  # compute utilities
  df_curr <- df_curr %>%
    rowwise() %>%
    mutate(
      util_0 = futility(agent_id = id, choice = 0,
                         agents = df_prev,
                         network = network,
                         params = params)$utility,
      util_1 = futility(agent_id = id, choice = 1,
                         agents = df_prev,
                         network = network,
                         params = params)$utility
    ) %>%
    ungroup()
  
  return(df_curr)
}

##calculate_round_utilities(2, df_long, network = g, params = params)

#compute utilities for all rounsd (except round 1)
max_round <- max(df_long$round)
df <- map_dfr(2:max_round, ~calculate_round_utilities(.x, df_long, g, params))

# identify best replies
df <- df %>%
  mutate(
    predicted_choice = ifelse(util_1 > util_0, 1, 0),
    best_reply = (choice == predicted_choice)  # TRUE if agent picked the choice with highest utility
  )


df_summary <- df %>%
  mutate(
    preferred_choice = ifelse(role == "trendsetter", 1, 0),    # define preferred option by role
    chose_preferred = (choice == preferred_choice)            # TRUE if they picked their preferred option
  ) %>%
  group_by(round, role) %>%
  summarize(
    n_agents = n(),
    prop_preferred = mean(chose_preferred),   # fraction that chose their preferred option
    prop_best_reply = mean(best_reply),       # fraction that picked the highest-utility choice
    .groups = "drop"
  )


ggplot(df_summary, aes(x = round)) +
  geom_line(aes(y = prop_preferred, color = "Preference"), size = 1) +
  geom_line(aes(y = prop_best_reply, color = "Best reply"), size = 1, linetype = "dashed") +
  facet_wrap(~role) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1), limits = c(0,1)) +
  scale_color_manual(values = c("Preference" = "darkgreen", "Best reply" = "steelblue")) +
  labs(
    x = "Round",
    y = "Proportion of agents",
    color = "Metric",
    title = "Following preference vs best-reply over rounds"
  )

LS0tDQp0aXRsZTogIkV4cGVyaW1lbnQiDQpiaWJsaW9ncmFwaHk6IHJlZmVyZW5jZXMuYmliDQpsaW5rLWNpdGF0aW9uczogdHJ1ZQ0KZGF0ZTogIkxhc3QgY29tcGlsZWQgb24gYHIgZm9ybWF0KFN5cy50aW1lKCksICclZC0lbS0lWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgc2VsZl9jb250YWluZWQ6IHRydWUNCiAgICBjc3M6IHR3ZWFrcy5jc3MNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCi0tLQ0KDQpgYGB7ciwgZ2xvYmFsc2V0dGluZ3MsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkodGlkeXZlcnNlKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0Kb3B0c19jaHVuayRzZXQodGlkeS5vcHRzPWxpc3Qod2lkdGguY3V0b2ZmPTEwMCksdGlkeT1UUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSxjb21tZW50ID0gIiM+IiwgY2FjaGU9VFJVRSwgY2xhc3Muc291cmNlPWMoInRlc3QiKSwgY2xhc3Mub3V0cHV0PWMoInRlc3QzIikpDQpvcHRpb25zKHdpZHRoID0gMTAwKQ0KcmdsOjpzZXR1cEtuaXRyKCkNCg0KY29sb3JpemUgPC0gZnVuY3Rpb24oeCwgY29sb3IpIHtzcHJpbnRmKCI8c3BhbiBzdHlsZT0nY29sb3I6ICVzOyc+JXM8L3NwYW4+IiwgY29sb3IsIHgpIH0NCmBgYA0KDQpgYGB7ciBrbGlwcHksIGVjaG89RkFMU0UsIGluY2x1ZGU9VFJVRX0NCmtsaXBweTo6a2xpcHB5KHBvc2l0aW9uID0gYygndG9wJywgJ3JpZ2h0JykpDQoja2xpcHB5OjprbGlwcHkoY29sb3IgPSAnZGFya3JlZCcpDQoja2xpcHB5OjprbGlwcHkodG9vbHRpcF9tZXNzYWdlID0gJ0NsaWNrIHRvIGNvcHknLCB0b29sdGlwX3N1Y2Nlc3MgPSAnRG9uZScpDQpgYGANCg0KLS0tDQoNCiMgR2V0dGluZyBzdGFydGVkDQoNClRvIGNvcHkgdGhlIGNvZGUsIGNsaWNrIHRoZSBidXR0b24gaW4gdGhlIHVwcGVyIHJpZ2h0IGNvcm5lciBvZiB0aGUgY29kZS1jaHVua3MuDQoNCiMjIGNsZWFuIHVwDQoNCmBgYHtyLCBjbGVhbl91cCwgcmVzdWx0cz0naGlkZSd9DQpybShsaXN0PWxzKCkpDQpnYygpDQpgYGANCg0KPGJyPg0KDQojIyBjdXN0b20gZnVuY3Rpb25zDQoNCldlIGRlZmluZWQgYSBudW1iZXIgY3VzdG9tIGZ1bmN0aW9ucywgYXQgYHIgeGZ1bjo6ZW1iZWRfZmlsZSgiLi9jdXN0b21fZnVuY3Rpb25zLlIiKWAuDQoNCmBgYHtyLCBjdXN0b21fZnVuY3Rpb25zfQ0Kc291cmNlKCIuL2N1c3RvbV9mdW5jdGlvbnMuUiIpDQpgYGANCg0KPGJyPg0KDQojIyBuZWNlc3NhcnkgcGFja2FnZXMNCg0KLSBgdGlkeXZlcnNlYDogZGF0YSB3cmFuZ2xpbmcNCi0gYGlncmFwaGA6IGdlbmVyYXRlIGFuZCB2aXN1YWxpemUgZ3JhcGhzDQotIGBwYXJhbGxlbGA6IHBhcmFsbGVsIGNvbXB1dGluZyB0byBzcGVlZCB1cCBzaW11bGF0aW9uDQotIGBmb3JlYWNoYDogbG9vcGluZyBpbiBwYXJhbGxlbA0KLSBgZG9QYXJhbGxlbGA6IHBhcmFsbGVsIGJhY2tlbmQgZm9yIGBmb3JlYWNoYA0KLSBgZ2dwbG90MmA6IGRhdGEgdmlzdWFsaXphdGlvbg0KLSBgZ2doNHhgOiBoYWNrcyBmb3IgYGdncGxvdDJgDQotIGBnZ3B1YnJgOiBtYWtlIHZpc3VhbGl6YXRpb25zIHB1YmxpY2F0aW9uLXJlYWR5DQoNCg0KYGBge3IsIHBhY2thZ2VzfQ0KcGFja2FnZXMgPSBjKCJ0aWR5dmVyc2UiLCAiaWdyYXBoIiwgImdncGxvdDIiLCAicGFyYWxsZWwiLCAiZG9QYXJhbGxlbCIsICJmb3JlYWNoIiwgImdnaDR4IiwgImdncHViciIsICJwbG90bHkiLCAiUkNvbG9yQnJld2VyIiwgImdyaWQiLCAiZ3JpZEV4dHJhIiwgInBhdGNod29yayIsICJnZ3Bsb3RpZnkiLCAiZ2dyYXBoIiwgImdnYW5pbWF0ZSIsICJSQ29sb3JCcmV3ZXIiLA0KICAgICJnZ3RleHQiLCAibWFnaWNrIiwgImpzb25saXRlIiwgImx1YnJpZGF0ZSIsICJnZ3RleHQiKQ0KDQppbnZpc2libGUoZnBhY2thZ2UuY2hlY2socGFja2FnZXMpKQ0Kcm0ocGFja2FnZXMpDQpgYGANCg0KLS0tDQoNCiMgaW1wb3J0IGRhdGENCg0KYGBge3J9DQpkYXRhIDwtIHJlYWQuY3N2KCIuL3Jhd2RhdGEvYWxsX2FwcHNfd2lkZV8yMDI2LTAyLTEyLmNzdiIpDQp0aW1lcyA8LSByZWFkLmNzdigiLi9yYXdkYXRhL1BhZ2VUaW1lcy0yMDI2LTAyLTEyLmNzdiIpDQoNCiNjYmluZChkYXRhJHBhcnRpY2lwYW50Lm5vZGUsZGF0YSRwYXJ0aWNpcGFudC5yb2xlKVtjb21wbGV0ZS5jYXNlcyggI2NiaW5kKGRhdGEkcGFydGljaXBhbnQubm9kZSxkYXRhJHBhcnRpY2lwYW50LnJvbGUpKSxdDQoNCiNkYXRhWywgYygicGFydGljaXBhbnQuYm9udXMiLCAicGFydGljaXBhbnQubGFiZWwiKV1bIWlzLm5hKGRhdGEkcGFydGljaXBhbnQuYm9udXMpLF0NCmBgYA0KDQotLS0NCg0KIyByZXN1bHRzDQoNCk9uIDEyLTItMjAyNiwgSSByZWNydWl0ZWQgMTAwIFByb2xpZmljIHBhcnRpY2lwYW50cywgdG8gcG9wdWxhdGUgYSBuZXR3b3JrIG9mIE49NTAgKHdpdGggYSAxMCUgbWlub3JpdHkgZ3JvdXApLg0KDQpgYGB7ciwgY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpuZXQgPC0ganNvbmxpdGU6OmZyb21KU09OKCIuL25ldHdvcmtzL25ldHdvcmtfdGVzdF9uNTAuanNvbiIpDQoNCmcgPC0gZ3JhcGhfZnJvbV9hZGphY2VuY3lfbWF0cml4KG5ldCRhZGpfbWF0cml4LCBtb2RlID0gInVuZGlyZWN0ZWQiKQ0KVihnKSRyb2xlIDwtIGlmZWxzZShuZXQkcm9sZV92ZWN0b3IgPT0gMSwgInRyZW5kc2V0dGVyIiwgImNvbmZvcm1pc3QiKQ0KDQpmcGxvdF9ncmFwaChnICkNCmBgYA0KDQoNCjxicj4NCg0KIyMgZGlhZ25vc3RpY3MNCg0KIyMjIGFycml2YWwgZnJvbSBQcm9saWZpYw0KDQpgYGB7ciwgZmlnLndpZHRoPTEwLCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCiMgc3Vic2V0IGV4cGVyaW1lbnRhbCBzZXNzaW9uDQpkYXRhIDwtIGRhdGFbZGF0YSRzZXNzaW9uLmNvZGUgPT0gImJnNnE5aWdjIixdDQp0aW1lcyA8LSB0aW1lc1t0aW1lcyRzZXNzaW9uX2NvZGUgPT0gImJnNnE5aWdjIixdDQoNCiNjbGVhbg0KdGVzdCA8LSBkYXRhICU+JQ0KICB0cmFuc211dGUoDQogICAgcGFydGljaXBhbnRfaWQgPSBwYXJ0aWNpcGFudC5jb2RlLA0KICAgIHBhcnRpY2lwYW50X2xhYmVsID0gcGFydGljaXBhbnQubGFiZWwsDQogICAgaWRfaW5fc2Vzc2lvbiAgPSBwYXJ0aWNpcGFudC5pZF9pbl9zZXNzaW9uLA0KICAgIGNvbnNlbnRfZ2l2ZW4gPSBjb25zZW50LjEucGxheWVyLmNvbnNlbnQsDQogICAgY29uc2VudF90aW1lc3RhbXAgPSBjb25zZW50LjEucGxheWVyLmNvbnNlbnRfdGltZXN0YW1wLA0KICAgIHJvbGUgPSBwYXJ0aWNpcGFudC5yb2xlLA0KICAgIGlzX2Ryb3BvdXQgPSBwYXJ0aWNpcGFudC5pc19kcm9wb3V0LA0KICAgIGRyb3BvdXRfYXBwID0gcGFydGljaXBhbnQuX2N1cnJlbnRfYXBwX25hbWUsDQogICAgY29tcHJlaGVuc2lvbl9yZXRyaWVzID0gY29tcHJlaGVuc2lvbi4xLnBsYXllci5jb21wcmVoZW5zaW9uX3JldHJpZXMsDQogICAgcGFzc2VkX2NvbXByZWhlbnNpb24gPSAhcGFydGljaXBhbnQuX2N1cnJlbnRfYXBwX25hbWUgJWluJSBjKCJjb25zZW50IiwgImNvbXByZWhlbnNpb24iKSwNCiAgICBjaG9pY2UgPSB1bnBvcC4xLnBsYXllci5jaG9pY2UsDQogICAgZmFpbGVkX2NoZWNrcyA9IHBhcnRpY2lwYW50LmZhaWxlZF9jaGVja3MsDQogICAgZXhpdF9lYXJseSA9IHBhcnRpY2lwYW50LmV4aXRfZWFybHksDQogICAgcGFydGljaXBhdGVkID0gcGFydGljaXBhbnQuX2N1cnJlbnRfcGFnZV9uYW1lID09ICJQYXltZW50SW5mbyINCiAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShjb25zZW50X2dpdmVuKSkgJT4lDQogIG11dGF0ZSgNCiAgICBib3QgPSBpZmVsc2UocGFydGljaXBhbnRfbGFiZWwgPT0gIiIsIDEsIDApLA0KICAgIGJvdCA9IGZhY3Rvcihib3QsIGxldmVscyA9IGMoMCwgMSksIGxhYmVscyA9IGMoIlByb2xpZmljIHBhcnRpY2lwYW50IiwgIkJvdCIpKSwNCiAgICBjb25zZW50X3RpbWVzdGFtcCA9IHltZF9obXMoY29uc2VudF90aW1lc3RhbXApLA0KICAgIGZpbmFsX3N0YXRlID0gY2FzZV93aGVuKA0KICAgICAgZmFpbGVkX2NoZWNrcyA+IDAgfiAiRmFpbGVkIGNvbXByZWhlbnNpb24iLA0KICAgICAgZXhpdF9lYXJseSA9PSAxIH4gIkNvdWxkIG5vdCBiZSBncm91cGVkIiwNCiAgICAgIHBhcnRpY2lwYXRlZCB+ICJQYXJ0aWNpcGF0ZWQiLA0KICAgICAgVFJVRSB+ICJUb28gbGF0ZSINCiAgICApDQogICkgJT4lDQogIGFycmFuZ2UoY29uc2VudF90aW1lc3RhbXApICU+JQ0KICBtdXRhdGUoYXJyaXZhbF9vcmRlciA9IHJvd19udW1iZXIoKSkNCg0KZ2dwbG90KHRlc3QsIGFlcyh4ID0gY29uc2VudF90aW1lc3RhbXAsIHkgPSBhcnJpdmFsX29yZGVyKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHJvbGUsIHNoYXBlID0gYm90KSwgc2l6ZSA9IDMsIGFscGhhID0gMC41KSArDQogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXMgPSBjKDE2LCAyKSkgKyAgICAgICAgICAgICANCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgICANCiAgbGFicygNCiAgICB4ID0gIkFycml2YWwgdGltZSIsDQogICAgeSA9ICJBcnJpdmFsIG9yZGVyIiwNCiAgICBjb2xvciA9ICJSb2xlIiwNCiAgICBzaGFwZSA9ICJUeXBlIiwNCiAgICB0aXRsZSA9ICJQYXJ0aWNpcGFudCBhcnJpdmFscyBmcm9tIFByb2xpZmljIG92ZXIgdGltZSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpnZ3Bsb3QodGVzdCwgYWVzKHggPSBjb25zZW50X3RpbWVzdGFtcCwgeSA9IGFycml2YWxfb3JkZXIpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gZmluYWxfc3RhdGUpLCBzaXplID0gMywgYWxwaGEgPSAwLjYpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoDQogICAgIlBhcnRpY2lwYXRlZCIgPSAiZ3JlZW4iLA0KICAgICJGYWlsZWQgY29tcHJlaGVuc2lvbiIgPSAib3JhbmdlIiwNCiAgICAiQ291bGQgbm90IGJlIGdyb3VwZWQiID0gInJlZCIsDQogICAgIlRvbyBsYXRlIiA9ICJncmF5Ig0KICApKSArDQogIGZhY2V0X3dyYXAofnJvbGUpICsNCiAgbGFicygNCiAgICB4ID0gIkFycml2YWwgdGltZSIsDQogICAgeSA9ICJBcnJpdmFsIG9yZGVyIiwNCiAgICBjb2xvciA9ICJGaW5hbCBzdGF0ZSIsDQogICAgdGl0bGUgPSAiUGFydGljaXBhbnQgYXJyaXZhbHMgYnkgcm9sZSBhbmQgZmluYWwgc3RhdGUiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZnNob3dkZih0YWJsZSh0ZXN0JGZpbmFsX3N0YXRlLCB0ZXN0JHJvbGUpLCBjYXB0aW9uID0gInBhcnRpY2lwYW50IHN0YXR1cyBieSByb2xlIikNCmBgYA0KDQo8YnI+DQoNCiMjIyBwcm9ncmVzc2lvbiB0aHJvdWdoIHRoZSBleHBlcmltZW50DQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCnRpbWVzIDwtIHRpbWVzICU+JQ0KICBtdXRhdGUoDQogICAgdGltZXN0YW1wID0gYXNfZGF0ZXRpbWUoZXBvY2hfdGltZV9jb21wbGV0ZWQpDQogICkNCg0KYXJyaXZhbF90aW1lcyA8LSB0aW1lcyAlPiUNCiAgZ3JvdXBfYnkocGFydGljaXBhbnRfaWRfaW5fc2Vzc2lvbikgJT4lDQogIHN1bW1hcml6ZShhcnJpdmFsX3RpbWUgPSBtaW4odGltZXN0YW1wKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KdGltZXMgPC0gdGltZXMgJT4lDQogIGxlZnRfam9pbihhcnJpdmFsX3RpbWVzLCBieSA9ICJwYXJ0aWNpcGFudF9pZF9pbl9zZXNzaW9uIikgJT4lDQogIG11dGF0ZShwYXJ0aWNpcGFudF9vcmRlcmVkID0gZmFjdG9yKHBhcnRpY2lwYW50X2lkX2luX3Nlc3Npb24sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBhcnJpdmFsX3RpbWVzICU+JSBhcnJhbmdlKGFycml2YWxfdGltZSkgJT4lIHB1bGwocGFydGljaXBhbnRfaWRfaW5fc2Vzc2lvbikpKQ0KDQp0aW1lc19yb2xlcyA8LSB0aW1lcyAlPiUNCiAgbGVmdF9qb2luKA0KICAgIHRlc3QgJT4lIHNlbGVjdChpZF9pbl9zZXNzaW9uLCByb2xlKSwNCiAgICBieSA9IGMoInBhcnRpY2lwYW50X2lkX2luX3Nlc3Npb24iID0gImlkX2luX3Nlc3Npb24iKQ0KICApDQoNCnBhZ2VfbGV2ZWxzIDwtIHVuaXF1ZSh0aW1lcyRwYWdlX25hbWUpDQoNCnRpbWVzX3JvbGVzIDwtIHRpbWVzX3JvbGVzICU+JQ0KICBtdXRhdGUocGFnZV9uYW1lID0gZmFjdG9yKHBhZ2VfbmFtZSwgbGV2ZWxzID0gcGFnZV9sZXZlbHMpKQ0KDQpjdXN0b21fY29sb3JzIDwtIGMoDQogICJJbml0aWFsaXplUGFydGljaXBhbnQiID0gIiNjNmRiZWYiLCANCiAgIkNvbnNlbnRQYWdlIiA9ICIjOWVjYWUxIiwNCiAgIkludHJvZHVjdGlvblBhZ2UiID0gIiM2YmFlZDYiLA0KICAiQ29tcHJlaGVuc2lvblBhZ2UiID0gIiMzMTgyYmQiLA0KICAiTmV0d29ya0Zvcm1hdGlvbldhaXRQYWdlIiA9ICIjZmZjYzk5IiwNCiAgIkRlY2lzaW9uUGFnZSIgPSAiI2ZmOTk2NiIsDQogICJSZXN1bHRzV2FpdFBhZ2UiID0gIiNmZjY2NjYiLA0KICAiUmVzdWx0c1BhZ2UiID0gIiNjYzAwMzMiLA0KICAiRmluYWxHYW1lUmVzdWx0cyIgPSAiIzY2MDAwMCIgICAgICAgDQopDQoNCiNjb2xvcmVkIHktYXhpcyBsYWJlbHMgYmFzZWQgb24gcm9sZQ0KeV9sYWJlbHNfY29sb3JlZCA8LSB0aW1lc19yb2xlcyAlPiUNCiAgc2VsZWN0KHBhcnRpY2lwYW50X29yZGVyZWQsIHJvbGUpICU+JQ0KICBkaXN0aW5jdCgpICU+JQ0KICBhcnJhbmdlKHBhcnRpY2lwYW50X29yZGVyZWQpICU+JQ0KICBtdXRhdGUoDQogICAgbGFiZWxfY29sb3JlZCA9IGNhc2Vfd2hlbigNCiAgICAgIHJvbGUgPT0gIlJlZCIgfiBwYXN0ZTAoIjxzcGFuIHN0eWxlPSdjb2xvcjpyZWQnPiIsIHBhcnRpY2lwYW50X29yZGVyZWQsICI8L3NwYW4+IiksDQogICAgICByb2xlID09ICJCbHVlIiB+IHBhc3RlMCgiPHNwYW4gc3R5bGU9J2NvbG9yOmJsdWUnPiIsIHBhcnRpY2lwYW50X29yZGVyZWQsICI8L3NwYW4+IiksDQogICAgICBUUlVFIH4gcGFzdGUwKCI8c3BhbiBzdHlsZT0nY29sb3I6ZGFya2dyZXknPiIsIHBhcnRpY2lwYW50X29yZGVyZWQsICI8L3NwYW4+IikNCiAgICApDQogICkNCg0KIyBjcmVhdGUgYSBuYW1lZCB2ZWN0b3IgZm9yIHNjYWxlX3lfZGlzY3JldGUgbGFiZWxzDQp5X2xhYmVsc192ZWN0b3IgPC0geV9sYWJlbHNfY29sb3JlZCRsYWJlbF9jb2xvcmVkDQpuYW1lcyh5X2xhYmVsc192ZWN0b3IpIDwtIHlfbGFiZWxzX2NvbG9yZWQkcGFydGljaXBhbnRfb3JkZXJlZA0KDQpnZ3Bsb3QodGltZXNfcm9sZXNbdGltZXNfcm9sZXMkcm91bmRfbnVtYmVyID09IDEsXSwgYWVzKHggPSB0aW1lc3RhbXAsIHkgPSBwYXJ0aWNpcGFudF9vcmRlcmVkLCBjb2xvciA9IHBhZ2VfbmFtZSkpICsNCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IHBhcnRpY2lwYW50X2lkX2luX3Nlc3Npb24pLCBzaXplID0gMSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21fY29sb3JzKSArDQogIHNjYWxlX3lfZGlzY3JldGUobGFiZWxzID0geV9sYWJlbHNfdmVjdG9yKSArDQogIGxhYnMoDQogICAgeCA9ICJUaW1lIiwNCiAgICB5ID0gIlBhcnRpY2lwYW50IChvcmRlcmVkIGJ5IGFycml2YWwpIiwNCiAgICBjb2xvciA9ICJTdGFnZS9QYWdlIiwNCiAgICB0aXRsZSA9ICJQYXJ0aWNpcGFudCBwcm9ncmVzc2lvbiB0aHJvdWdoIGV4cGVyaW1lbnQgc3RhZ2VzIChieSBhcnJpdmFsKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9tYXJrZG93bihzaXplID0gNikpDQpgYGAgDQoNCiMjIyBkcm9wb3V0DQoNCg0KYGBge3IsIGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZHJvcG91dF9sb25nIDwtIGRhdGEgJT4lDQogIGZpbHRlcihwYXJ0aWNpcGFudC5fY3VycmVudF9wYWdlX25hbWUgPT0gIlBheW1lbnRJbmZvIikgJT4lICMgb25seSBjb21wbGV0ZWQgcGFydGljaXBhbnRzDQogIGZpbHRlcihwYXJ0aWNpcGFudC5sYWJlbCAhPSAiIikgJT4lICMgZXhjbHVkZSBib3RzIChwYXJ0aWNpcGFudHMgY29udHJvbGxlZCBieSBleHBlcmltZW5lciBoYXZlIG5vIGxhYmVsKQ0KICBzZWxlY3QoDQogICAgcGFydGljaXBhbnQubGFiZWwsDQogICAgcGFydGljaXBhbnQucm9sZSwNCiAgICBtYXRjaGVzKCJ1bnBvcFxcLlswLTldK1xcLnBsYXllclxcLmlzX2Ryb3BvdXQiKQ0KICApICU+JQ0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IG1hdGNoZXMoInVucG9wXFwuWzAtOV0rXFwucGxheWVyXFwuaXNfZHJvcG91dCIpLA0KICAgIG5hbWVzX3RvID0gInJvdW5kIiwNCiAgICB2YWx1ZXNfdG8gPSAiaXNfZHJvcG91dCINCiAgKSAlPiUNCiAgbXV0YXRlKA0KICAgIHJvdW5kID0gc3RyX2V4dHJhY3Qocm91bmQsICJbMC05XSsiKSwNCiAgICByb3VuZCA9IGFzLm51bWVyaWMocm91bmQpDQogICkNCg0KDQojIGdldCBmaXJzdCBkcm9wb3V0IHJvdW5kIHBlciBwYXJ0aWNpcGFudA0KZHJvcG91dF9zdW1tYXJ5IDwtIGRyb3BvdXRfbG9uZyAlPiUNCiAgZ3JvdXBfYnkocGFydGljaXBhbnQubGFiZWwsIHBhcnRpY2lwYW50LnJvbGUpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgZXZlbnQgPSBhbnkoaXNfZHJvcG91dCA9PSAxLCBuYS5ybSA9IFRSVUUpLA0KICAgIGRyb3BvdXRfcm91bmQgPSBpZmVsc2UoDQogICAgICBldmVudCwNCiAgICAgIG1pbihyb3VuZFtpc19kcm9wb3V0ID09IDFdLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgMzANCiAgICApLA0KICAgIC5ncm91cHMgPSAiZHJvcCINCiAgKQ0KDQojIGNvdW50IGN1bXVsYXRpdmUgZHJvcG91dHMNCmN1bV9kcm9wb3V0X3JvbGUgPC0gZHJvcG91dF9zdW1tYXJ5ICU+JQ0KICBmaWx0ZXIoZXZlbnQgPT0gVFJVRSkgJT4lDQogIGNvdW50KHBhcnRpY2lwYW50LnJvbGUsIGRyb3BvdXRfcm91bmQpICU+JQ0KICBncm91cF9ieShwYXJ0aWNpcGFudC5yb2xlKSAlPiUNCiAgY29tcGxldGUoZHJvcG91dF9yb3VuZCA9IDE6MzAsIGZpbGwgPSBsaXN0KG4gPSAwKSkgJT4lDQogIGFycmFuZ2UocGFydGljaXBhbnQucm9sZSwgZHJvcG91dF9yb3VuZCkgJT4lDQogIG11dGF0ZSgNCiAgICBjdW11bGF0aXZlX2Ryb3BvdXQgPSBjdW1zdW0obikNCiAgKSAlPiUNCiAgdW5ncm91cCgpDQoNCmdncGxvdChjdW1fZHJvcG91dF9yb2xlLA0KICAgICAgIGFlcyh4ID0gZHJvcG91dF9yb3VuZCwNCiAgICAgICAgICAgeSA9IGN1bXVsYXRpdmVfZHJvcG91dCwNCiAgICAgICAgICAgY29sb3IgPSBwYXJ0aWNpcGFudC5yb2xlKSkgKw0KICBnZW9tX2xpbmUobGluZXdpZHRoID0gMS4yKSArDQogIGdlb21fcG9pbnQoKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOjMwKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCgNCiAgICB2YWx1ZXMgPSBjKA0KICAgICAgIlJlZCIgPSAicmVkIiwNCiAgICAgICJCbHVlIiA9ICJibHVlIg0KICAgICkpICsNCiAgbGFicygNCiAgICB4ID0gIlJvdW5kIiwNCiAgICB5ID0gIkRyb3BvdXQiLA0KICAgIGNvbG9yID0gIlJvbGUiLA0KICAgIHRpdGxlID0gIkN1bXVsYXRpdmUgZHJvcG91dCAoYnkgcm9sZSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQotLS0tDQoNCg0KIyMgdW5wb3B1bGFyIG5vcm0gc3ByZWFkDQoNCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQojIGNob2ljZSBiZWhhdmlvciBvdmVyIHJvdW5kczoNCmRmX2xvbmcgPC0gZGF0YSAlPiUNCiAgZmlsdGVyKHBhcnRpY2lwYW50Ll9jdXJyZW50X3BhZ2VfbmFtZSA9PSAiUGF5bWVudEluZm8iKSAlPiUgIyBmaWx0ZXIgYWN0dWFsIHBhcnRpY2lwYW50cw0KICBzZWxlY3QocGFydGljaXBhbnQubGFiZWwsIHBhcnRpY2lwYW50LnJvbGUsIHBhcnRpY2lwYW50Lm5vZGUsIHN0YXJ0c193aXRoKCJ1bnBvcC4iKSkgJT4lICNhbHNvIGluY2x1ZGUgbmV0d29yayBub2RlLg0KICANCiAgcGl2b3RfbG9uZ2VyKA0KICAgIGNvbHMgPSBtYXRjaGVzKCJ1bnBvcFxcLlxcZCtcXC5wbGF5ZXJcXC5jaG9pY2UkIiksICAjIG9ubHkgY2hvaWNlIGNvbHVtbnMNCiAgICBuYW1lc190byA9ICJyb3VuZCIsDQogICAgdmFsdWVzX3RvID0gImNob2ljZSINCiAgKSAlPiUNCiAgDQogIG11dGF0ZSgNCiAgICByb3VuZCA9IGFzLmludGVnZXIoZ3N1YigidW5wb3BcXC4oXFxkKylcXC5wbGF5ZXJcXC5jaG9pY2UiLCAiXFwxIiwgcm91bmQpKSwNCiAgICBpc19ib3QgPSBpZmVsc2UocGFydGljaXBhbnQubGFiZWwgPT0gIiIsIFRSVUUsIEZBTFNFKQ0KICApICU+JQ0KICBzZWxlY3QocGFydGljaXBhbnQubGFiZWwsIHBhcnRpY2lwYW50Lm5vZGUsIHBhcnRpY2lwYW50LnJvbGUsIHJvdW5kLCBjaG9pY2UsIGlzX2JvdCkNCg0KIyBpZGVudGlmeSByb3VuZCBvZiBkcm9wb3V0Og0KZmlyc3RfZHJvcG91dCA8LSBkcm9wb3V0X2xvbmcgJT4lDQogIGZpbHRlcihpc19kcm9wb3V0ID09IDEpICU+JQ0KICBncm91cF9ieShwYXJ0aWNpcGFudC5sYWJlbCkgJT4lDQogIHN1bW1hcmlzZShkcm9wb3V0X3JvdW5kID0gbWluKHJvdW5kKSwNCiAgICAgICAgICAgIC5ncm91cHMgPSAiZHJvcCIpDQoNCiMgYW5kIGFkZCB0byB0aGUgZGY6DQpkZl9sb25nIDwtIGRmX2xvbmcgJT4lDQogIGxlZnRfam9pbihmaXJzdF9kcm9wb3V0LCBieSA9ICJwYXJ0aWNpcGFudC5sYWJlbCIpDQoNCiMgYWdncmVnYXRlZA0KZGZfcGxvdCA8LSBkZl9sb25nICU+JQ0KICBncm91cF9ieShyb3VuZCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBwY3RfY2hvaWNlMSA9IG1lYW4oY2hvaWNlLCBuYS5ybSA9IFRSVUUpICogMTAwLCAgIyBwcm9wb3J0aW9uICogMTAwDQogICAgbiA9IG4oKQ0KICApDQoNCmdncGxvdChkZl9wbG90LCBhZXMoeCA9IHJvdW5kLCB5ID0gcGN0X2Nob2ljZTEpKSArDQogIGdlb21fbGluZShncm91cCA9IDEsIGNvbG9yID0gInN0ZWVsYmx1ZSIsIHNpemUgPSAuNSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gInN0ZWVsYmx1ZSIsIHNpemUgPSAyKSArDQogICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxMCwgbGluZXR5cGUgPSAibG9uZ2Rhc2giLCBjb2xvciA9ICJkYXJrZ3JleSIsIHNpemUgPSAwLjgpICsgICMgZGFzaGVkIGxpbmUgYXQgMTANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGRmX3Bsb3Qkcm91bmQpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMTAwKSkgKyAgDQogIGxhYnMoDQogICAgeCA9ICJSb3VuZCIsDQogICAgeSA9ICIlIGFnZW50cyBjaG9vc2luZyAnYmx1ZSciLA0KICAgIHRpdGxlID0gIkV2b2x1dGlvbiBvZiBhbiB1bnBvcHVsYXIgbm9ybSINCiAgKQ0KDQojc29ydCBieSBub2RlDQpkZl9sb25nIDwtIGRmX2xvbmcgJT4lDQogIGFycmFuZ2UocGFydGljaXBhbnQubm9kZSkgJT4lDQogIHNlbGVjdCgtcGFydGljaXBhbnQubGFiZWwpDQoNCmRmX2xvbmckZHJvcG91dF9yb3VuZFtpcy5uYShkZl9sb25nJGRyb3BvdXRfcm91bmQpXSA8LSAzMCAjbm9uIGRyb3BvdXRzLCBzZXQgdG8gMzAuDQpgYGANCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KIyBtYWtlIHJvbGVzIGNvbnNpc3RlbnQgd2l0aCB1dGlsaXR5IGZ1bmN0aW9uIHJvbGVzDQpkZl9sb25nJHJvbGUgPC0gaWZlbHNlKGRmX2xvbmckcGFydGljaXBhbnQucm9sZSA9PSAiQmx1ZSIsICJ0cmVuZHNldHRlciIsICJjb25mb3JtaXN0IikNCg0KIyBzcGVjaWZpY3kgaW5jZW50aXZlIHN0cnVjdHVyZSBwYXJhbWV0ZXJzDQpwYXJhbXMgPSBsaXN0KHMgPSAxNSwgZSA9IDEwLCB3ID0gNDAsIHogPSA1MCwgbGFtYmRhMSA9IDUsIGxhbWJkYTIgPSAxLjgpDQoNCmRmX2xvbmckaWQgPC0gZGZfbG9uZyRwYXJ0aWNpcGFudC5ub2RlICsgMSAjbm9kZXMgYXJlIDAtaW5kZXhlZA0KDQojYWRkIGRlZ3JlZQ0KZGVnIDwtIGRlZ3JlZShnKQ0KZGZfbG9uZyA8LSBkZl9sb25nICU+JQ0KICBtdXRhdGUoZGVncmVlID0gZGVnW2lkXSkNCg0KY2FsY3VsYXRlX3JvdW5kX3V0aWxpdGllcyA8LSBmdW5jdGlvbihjdXJyZW50X3JvdW5kLCBkZl9sb25nLCBuZXR3b3JrLCBwYXJhbXMpIHsNCiAgIyBwcmV2aW91cyByb3VuZCBkYXRhDQogIGRmX3ByZXYgPC0gZGZfbG9uZyAlPiUNCiAgICBmaWx0ZXIocm91bmQgPT0gY3VycmVudF9yb3VuZCAtIDEpICU+JQ0KICAgIHNlbGVjdChpZCwgcm9sZSwgY2hvaWNlKQ0KICANCiAgIyBjdXJyZW50IHJvdW5kIGRhdGENCiAgZGZfY3VyciA8LSBkZl9sb25nICU+JQ0KICAgIGZpbHRlcihyb3VuZCA9PSBjdXJyZW50X3JvdW5kKQ0KICANCiAgIyBjb21wdXRlIHV0aWxpdGllcw0KICBkZl9jdXJyIDwtIGRmX2N1cnIgJT4lDQogICAgcm93d2lzZSgpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIHV0aWxfMCA9IGZ1dGlsaXR5KGFnZW50X2lkID0gaWQsIGNob2ljZSA9IDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgYWdlbnRzID0gZGZfcHJldiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBuZXR3b3JrID0gbmV0d29yaywNCiAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbXMgPSBwYXJhbXMpJHV0aWxpdHksDQogICAgICB1dGlsXzEgPSBmdXRpbGl0eShhZ2VudF9pZCA9IGlkLCBjaG9pY2UgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGFnZW50cyA9IGRmX3ByZXYsDQogICAgICAgICAgICAgICAgICAgICAgICAgbmV0d29yayA9IG5ldHdvcmssDQogICAgICAgICAgICAgICAgICAgICAgICAgcGFyYW1zID0gcGFyYW1zKSR1dGlsaXR5DQogICAgKSAlPiUNCiAgICB1bmdyb3VwKCkNCiAgDQogIHJldHVybihkZl9jdXJyKQ0KfQ0KDQojI2NhbGN1bGF0ZV9yb3VuZF91dGlsaXRpZXMoMiwgZGZfbG9uZywgbmV0d29yayA9IGcsIHBhcmFtcyA9IHBhcmFtcykNCg0KI2NvbXB1dGUgdXRpbGl0aWVzIGZvciBhbGwgcm91bnNkIChleGNlcHQgcm91bmQgMSkNCm1heF9yb3VuZCA8LSBtYXgoZGZfbG9uZyRyb3VuZCkNCmRmIDwtIG1hcF9kZnIoMjptYXhfcm91bmQsIH5jYWxjdWxhdGVfcm91bmRfdXRpbGl0aWVzKC54LCBkZl9sb25nLCBnLCBwYXJhbXMpKQ0KDQojIGlkZW50aWZ5IGJlc3QgcmVwbGllcw0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZSgNCiAgICBwcmVkaWN0ZWRfY2hvaWNlID0gaWZlbHNlKHV0aWxfMSA+IHV0aWxfMCwgMSwgMCksDQogICAgYmVzdF9yZXBseSA9IChjaG9pY2UgPT0gcHJlZGljdGVkX2Nob2ljZSkgICMgVFJVRSBpZiBhZ2VudCBwaWNrZWQgdGhlIGNob2ljZSB3aXRoIGhpZ2hlc3QgdXRpbGl0eQ0KICApDQoNCg0KZGZfc3VtbWFyeSA8LSBkZiAlPiUNCiAgbXV0YXRlKA0KICAgIHByZWZlcnJlZF9jaG9pY2UgPSBpZmVsc2Uocm9sZSA9PSAidHJlbmRzZXR0ZXIiLCAxLCAwKSwgICAgIyBkZWZpbmUgcHJlZmVycmVkIG9wdGlvbiBieSByb2xlDQogICAgY2hvc2VfcHJlZmVycmVkID0gKGNob2ljZSA9PSBwcmVmZXJyZWRfY2hvaWNlKSAgICAgICAgICAgICMgVFJVRSBpZiB0aGV5IHBpY2tlZCB0aGVpciBwcmVmZXJyZWQgb3B0aW9uDQogICkgJT4lDQogIGdyb3VwX2J5KHJvdW5kLCByb2xlKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIG5fYWdlbnRzID0gbigpLA0KICAgIHByb3BfcHJlZmVycmVkID0gbWVhbihjaG9zZV9wcmVmZXJyZWQpLCAgICMgZnJhY3Rpb24gdGhhdCBjaG9zZSB0aGVpciBwcmVmZXJyZWQgb3B0aW9uDQogICAgcHJvcF9iZXN0X3JlcGx5ID0gbWVhbihiZXN0X3JlcGx5KSwgICAgICAgIyBmcmFjdGlvbiB0aGF0IHBpY2tlZCB0aGUgaGlnaGVzdC11dGlsaXR5IGNob2ljZQ0KICAgIC5ncm91cHMgPSAiZHJvcCINCiAgKQ0KDQoNCmdncGxvdChkZl9zdW1tYXJ5LCBhZXMoeCA9IHJvdW5kKSkgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBwcm9wX3ByZWZlcnJlZCwgY29sb3IgPSAiUHJlZmVyZW5jZSIpLCBzaXplID0gMSkgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBwcm9wX2Jlc3RfcmVwbHksIGNvbG9yID0gIkJlc3QgcmVwbHkiKSwgc2l6ZSA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgZmFjZXRfd3JhcCh+cm9sZSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpLCBsaW1pdHMgPSBjKDAsMSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIlByZWZlcmVuY2UiID0gImRhcmtncmVlbiIsICJCZXN0IHJlcGx5IiA9ICJzdGVlbGJsdWUiKSkgKw0KICBsYWJzKA0KICAgIHggPSAiUm91bmQiLA0KICAgIHkgPSAiUHJvcG9ydGlvbiBvZiBhZ2VudHMiLA0KICAgIGNvbG9yID0gIk1ldHJpYyIsDQogICAgdGl0bGUgPSAiRm9sbG93aW5nIHByZWZlcmVuY2UgdnMgYmVzdC1yZXBseSBvdmVyIHJvdW5kcyINCiAgKQ0KDQpgYGAgDQo=


Copyright © Rob Franken