PAIDAMOYO SIMBA

468074

Clustering with K-means

Introduction

“Over the past few decades, the video game business has experienced tremendous development and transformation, emerging as a major force in the worldwide entertainment sector. Comprehending the elements that lead to video game success is becoming increasingly important for developers, publishers, and other stakeholders as the industry grows. With the use of global sales data, we may examine market dynamics, spot new trends, and arrive at a well-informed conclusions.

In order to find trends and insights that propel industry success, we examine the data set of worldwide video game sales in this research. Through data preparation, clustering analysis, and visualizations, we explore the links between several features, including genre, platform, publisher, and sales numbers, to obtain a more detailed understanding. This analysis will use two unsupervised learning techniques that k-means algorithm to find clusters from the data set and the number of the clusters will be determined by Elbow Method.”

Data Loading

The first step was to load the data and determine the structure and the class of the data. So I determined that the dataset consists of numeric characters and integers. Henceforth, there is a need for data processing.

library(readr)
Warning: package ‘readr’ was built under R version 4.3.2
library(readr)
videogameglobalsales <- read_csv("clustering final/videogameglobalsales.csv")
Rows: 16598 Columns: 11── Column specification ───────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (4): name, platform, genre, publisher
dbl (7): rank, year, na_sales, eu_sales, jp_sales, other_sales, global_sales
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
View(videogameglobalsales)
View(videogameglobalsales)
class(videogameglobalsales)
[1] "spec_tbl_df" "tbl_df"      "tbl"         "data.frame" 
colnames(videogameglobalsales)
 [1] "rank"         "name"         "platform"     "year"         "genre"        "publisher"   
 [7] "na_sales"     "eu_sales"     "jp_sales"     "other_sales"  "global_sales"
head(videogameglobalsales)

Missing values

I then checked for missing values in the data set.The only column with missing data was the year column and i decided to omit all rows with the missing value as it was insignificant to the data set

missing_values_per_column <- colSums(is.na(videogameglobalsales))
print(missing_values_per_column)
        rank         name     platform         year        genre    publisher     na_sales 
           0            0            0          271            0            0            0 
    eu_sales     jp_sales  other_sales global_sales 
           0            0            0            0 
video_game_global_sales1 <- na.omit(videogameglobalsales)
missing_values_per_column <- colSums(is.na(video_game_global_sales1))
print(missing_values_per_column)
        rank         name     platform         year        genre    publisher     na_sales 
           0            0            0            0            0            0            0 
    eu_sales     jp_sales  other_sales global_sales 
           0            0            0            0 

Data type

In this step i checked my data type variables and also the head of my data set so that i can have a clear view of what each column contains

print(sapply(video_game_global_sales1, class))
        rank         name     platform         year        genre    publisher     na_sales 
   "numeric"  "character"  "character"    "numeric"  "character"  "character"    "numeric" 
    eu_sales     jp_sales  other_sales global_sales 
   "numeric"    "numeric"    "numeric"    "numeric" 
head(video_game_global_sales1)

Standardizing

I identified all characters and integers columns in my data set and converted it to factors so as to standardize and prepare my data for clustering. Also on this stage i also summarized my data set

character_columns <- sapply(video_game_global_sales1, is.character)
integer_columns <- sapply(video_game_global_sales1, is.integer)
video_game_global_sales1[character_columns] <-lapply(video_game_global_sales1[character_columns], as.factor)
video_game_global_sales1[integer_columns] <- lapply(video_game_global_sales1[integer_columns], as.factor)

print(sapply(video_game_global_sales1, class))
        rank         name     platform         year        genre    publisher     na_sales 
   "numeric"     "factor"     "factor"    "numeric"     "factor"     "factor"    "numeric" 
    eu_sales     jp_sales  other_sales global_sales 
   "numeric"    "numeric"    "numeric"    "numeric" 
summary(video_game_global_sales1)
      rank                                name          platform         year     
 Min.   :    1   Need for Speed: Most Wanted:   12   DS     :2133   Min.   :1980  
 1st Qu.: 4136   FIFA 14                    :    9   PS2    :2127   1st Qu.:2003  
 Median : 8295   LEGO Marvel Super Heroes   :    9   PS3    :1304   Median :2007  
 Mean   : 8293   Ratatouille                :    9   Wii    :1290   Mean   :2006  
 3rd Qu.:12442   Angry Birds Star Wars      :    8   X360   :1235   3rd Qu.:2010  
 Max.   :16600   Cars                       :    8   PSP    :1197   Max.   :2020  
                 (Other)                    :16272   (Other):7041                 
          genre                             publisher        na_sales          eu_sales      
 Action      :3253   Electronic Arts             : 1339   Min.   : 0.0000   Min.   : 0.0000  
 Sports      :2304   Activision                  :  966   1st Qu.: 0.0000   1st Qu.: 0.0000  
 Misc        :1710   Namco Bandai Games          :  928   Median : 0.0800   Median : 0.0200  
 Role-Playing:1471   Ubisoft                     :  918   Mean   : 0.2654   Mean   : 0.1476  
 Shooter     :1282   Konami Digital Entertainment:  823   3rd Qu.: 0.2400   3rd Qu.: 0.1100  
 Adventure   :1276   THQ                         :  712   Max.   :41.4900   Max.   :29.0200  
 (Other)     :5031   (Other)                     :10641                                      
    jp_sales         other_sales        global_sales    
 Min.   : 0.00000   Min.   : 0.00000   Min.   : 0.0100  
 1st Qu.: 0.00000   1st Qu.: 0.00000   1st Qu.: 0.0600  
 Median : 0.00000   Median : 0.01000   Median : 0.1700  
 Mean   : 0.07866   Mean   : 0.04832   Mean   : 0.5402  
 3rd Qu.: 0.04000   3rd Qu.: 0.04000   3rd Qu.: 0.4800  
 Max.   :10.22000   Max.   :10.57000   Max.   :82.7400  
                                                        

Outliers

At this stage we determined outliers using the z-score and removed it from all numerical columns.In this data set a total of 1150 outliers where identified and removed from the data set.

numeric_columns <- c("na_sales", "eu_sales", "jp_sales", "other_sales", "global_sales")
z_scores <- scale(video_game_global_sales1[numeric_columns])
outliers <- apply(abs(z_scores) > 3, 1, any)
cat("Number of outliers identified:", sum(outliers), "\n")
Number of outliers identified: 470 
video_game_global_sales1 <- video_game_global_sales1[!outliers, ]
summary(video_game_global_sales1)
      rank                                 name          platform         year     
 Min.   :  204   Need for Speed: Most Wanted :   11   DS     :2090   Min.   :1980  
 1st Qu.: 4498   LEGO Marvel Super Heroes    :    9   PS2    :2060   1st Qu.:2003  
 Median : 8534   Ratatouille                 :    9   Wii    :1262   Median :2007  
 Mean   : 8525   Angry Birds Star Wars       :    8   PS3    :1261   Mean   :2007  
 3rd Qu.:12562   Cars                        :    8   X360   :1195   3rd Qu.:2010  
 Max.   :16600   Lego Batman 3: Beyond Gotham:    8   PSP    :1183   Max.   :2020  
                 (Other)                     :15804   (Other):6806                 
          genre                             publisher        na_sales         eu_sales     
 Action      :3176   Electronic Arts             : 1292   Min.   :0.0000   Min.   :0.0000  
 Sports      :2240   Activision                  :  932   1st Qu.:0.0000   1st Qu.:0.0000  
 Misc        :1670   Namco Bandai Games          :  913   Median :0.0700   Median :0.0200  
 Role-Playing:1382   Ubisoft                     :  900   Mean   :0.1957   Mean   :0.1012  
 Adventure   :1270   Konami Digital Entertainment:  804   3rd Qu.:0.2200   3rd Qu.:0.1000  
 Shooter     :1232   THQ                         :  709   Max.   :2.7100   Max.   :1.6600  
 (Other)     :4887   (Other)                     :10307                                    
    jp_sales        other_sales       global_sales   
 Min.   :0.00000   Min.   :0.00000   Min.   :0.0100  
 1st Qu.:0.00000   1st Qu.:0.00000   1st Qu.:0.0600  
 Median :0.00000   Median :0.01000   Median :0.1600  
 Mean   :0.04761   Mean   :0.03213   Mean   :0.3769  
 3rd Qu.:0.03000   3rd Qu.:0.03000   3rd Qu.:0.4300  
 Max.   :1.01000   Max.   :0.61000   Max.   :5.0200  
                                                     
dim(video_game_global_sales1)
[1] 15857    11

Encoding

This is the last step of data cleaning and processing when i standardized all numeric columns and also encoded genre so that it can be used for clustering purposes as my main goal in this clustering is to identify the effect of the video game genre with Global Sales


video_game_global_sales1$genre_code <- as.numeric(factor(video_game_global_sales1$genre))


unique_genres <- levels(factor(video_game_global_sales1$genre))
unique_genre_codes <- unique(video_game_global_sales1$genre_code)


cat("Genre Codes:\n")
Genre Codes:
for (i in seq_along(unique_genres)) {
  cat(sprintf("%s: %d\n", unique_genres[i], unique_genre_codes[i]))
}
Action: 3
Adventure: 7
Fighting: 1
Misc: 4
Platform: 11
Puzzle: 8
Racing: 9
Role-Playing: 5
Shooter: 10
Simulation: 6
Sports: 2
Strategy: 12
summary(video_game_global_sales1)
      rank                                 name          platform         year               genre     
 Min.   :  204   Need for Speed: Most Wanted :   11   DS     :2090   Min.   :1980   Action      :3176  
 1st Qu.: 4498   LEGO Marvel Super Heroes    :    9   PS2    :2060   1st Qu.:2003   Sports      :2240  
 Median : 8534   Ratatouille                 :    9   Wii    :1262   Median :2007   Misc        :1670  
 Mean   : 8525   Angry Birds Star Wars       :    8   PS3    :1261   Mean   :2007   Role-Playing:1382  
 3rd Qu.:12562   Cars                        :    8   X360   :1195   3rd Qu.:2010   Adventure   :1270  
 Max.   :16600   Lego Batman 3: Beyond Gotham:    8   PSP    :1183   Max.   :2020   Shooter     :1232  
                 (Other)                     :15804   (Other):6806                  (Other)     :4887  
                        publisher        na_sales           eu_sales            jp_sales      
 Electronic Arts             : 1292   Min.   :-0.58000   Min.   :-0.502985   Min.   :-0.3941  
 Activision                  :  932   1st Qu.:-0.58000   1st Qu.:-0.502985   1st Qu.:-0.3941  
 Namco Bandai Games          :  913   Median :-0.37251   Median :-0.403545   Median :-0.3941  
 Ubisoft                     :  900   Mean   : 0.00000   Mean   : 0.000000   Mean   : 0.0000  
 Konami Digital Entertainment:  804   3rd Qu.: 0.07211   3rd Qu.:-0.005785   3rd Qu.:-0.1457  
 THQ                         :  709   Max.   : 7.45291   Max.   : 7.750540   Max.   : 7.9658  
 (Other)                     :10307                                                           
  other_sales        global_sales        genre_code     cluster 
 Min.   :-0.50462   Min.   :-0.64902   Min.   : 1.000   1:4969  
 1st Qu.:-0.50462   1st Qu.:-0.56056   1st Qu.: 2.000   2:3954  
 Median :-0.34759   Median :-0.38365   Median : 6.000   3:6934  
 Mean   : 0.00000   Mean   : 0.00000   Mean   : 5.911           
 3rd Qu.:-0.03352   3rd Qu.: 0.09402   3rd Qu.: 9.000           
 Max.   : 9.07441   Max.   : 8.21437   Max.   :12.000           
                                                                
video_game_global_sales1[numeric_columns] <- scale(video_game_global_sales1[numeric_columns])
video_game_global_sales1$genre_code <- as.numeric(factor(video_game_global_sales1$genre))
summary(video_game_global_sales1)
      rank                                 name          platform         year               genre     
 Min.   :  204   Need for Speed: Most Wanted :   11   DS     :2090   Min.   :1980   Action      :3176  
 1st Qu.: 4498   LEGO Marvel Super Heroes    :    9   PS2    :2060   1st Qu.:2003   Sports      :2240  
 Median : 8534   Ratatouille                 :    9   Wii    :1262   Median :2007   Misc        :1670  
 Mean   : 8525   Angry Birds Star Wars       :    8   PS3    :1261   Mean   :2007   Role-Playing:1382  
 3rd Qu.:12562   Cars                        :    8   X360   :1195   3rd Qu.:2010   Adventure   :1270  
 Max.   :16600   Lego Batman 3: Beyond Gotham:    8   PSP    :1183   Max.   :2020   Shooter     :1232  
                 (Other)                     :15804   (Other):6806                  (Other)     :4887  
                        publisher        na_sales           eu_sales            jp_sales      
 Electronic Arts             : 1292   Min.   :-0.58000   Min.   :-0.502985   Min.   :-0.3941  
 Activision                  :  932   1st Qu.:-0.58000   1st Qu.:-0.502985   1st Qu.:-0.3941  
 Namco Bandai Games          :  913   Median :-0.37251   Median :-0.403545   Median :-0.3941  
 Ubisoft                     :  900   Mean   : 0.00000   Mean   : 0.000000   Mean   : 0.0000  
 Konami Digital Entertainment:  804   3rd Qu.: 0.07211   3rd Qu.:-0.005785   3rd Qu.:-0.1457  
 THQ                         :  709   Max.   : 7.45291   Max.   : 7.750540   Max.   : 7.9658  
 (Other)                     :10307                                                           
  other_sales        global_sales        genre_code     cluster 
 Min.   :-0.50462   Min.   :-0.64902   Min.   : 1.000   1:4969  
 1st Qu.:-0.50462   1st Qu.:-0.56056   1st Qu.: 2.000   2:3954  
 Median :-0.34759   Median :-0.38365   Median : 6.000   3:6934  
 Mean   : 0.00000   Mean   : 0.00000   Mean   : 5.911           
 3rd Qu.:-0.03352   3rd Qu.: 0.09402   3rd Qu.: 9.000           
 Max.   : 9.07441   Max.   : 8.21437   Max.   :12.000           
                                                                

Clustering

Elbow Method

Now that i have cleaned and standardized my data i will proceed and go to clustering. I will use Elbow method to determine the number of clusters for the selected feature to achieve the goal of my project

library(ggplot2)
Warning: package ‘ggplot2’ was built under R version 4.3.2
elbow_point <- function(wcss_values) {
  deltas <- c(0, diff(wcss_values))
 
  elbow <- which(deltas == max(deltas))
  return(elbow)
}

calculate_wcss <- function(data, k) {
  kmeans_result <- kmeans(data, centers = k, nstart = 10)
  return(kmeans_result$tot.withinss)
}

features_for_clustering <- video_game_global_sales1[, c("global_sales", "genre_code")]
k_values <- 1:10  
wcss_values <- sapply(k_values, function(k) calculate_wcss(features_for_clustering, k))


plot(k_values, wcss_values, type = "b", pch = 19, frame = FALSE, 
     xlab = "Number of Clusters (k)", ylab = "Within-Cluster Sum of Squares (WCSS)",
     main = "Elbow Method for Optimal Number of Clusters")

Based of my elbow graph i will decide to use 3 clusters based on observing a more gradual stabilization of SSD after 3 clusters, suggesting that the additional cluster captures meaningful variation in the data.

K means summary

Based on the number of clusters I clustered the main features of this project which is global sales and genre.


kmeans_result <- kmeans(features_for_clustering, centers = 3, nstart = 10)


str(kmeans_result$cluster)
 int [1:15857] 1 2 1 1 1 1 1 3 2 3 ...
length(kmeans_result$cluster)
[1] 15857
video_game_global_sales1$cluster <- as.factor(kmeans_result$cluster)


summary(video_game_global_sales1$cluster)
   1    2    3 
6934 3954 4969 

According to the results my data is grouped into three clusters with my first cluster containing 3608 observation and 6511 and 4588 for the second and third cluster. The clustering plot are below showed by a scatter plot for clarity.

K-means scatter plot

Scatter plot of ‘global_sales’ vs. ‘genre_code’ with cluster coloring


plot(features_for_clustering, col = c("black", "red", "green")[kmeans_result$cluster], pch = 16,
     main = "K-Means Clustering", xlab = "Global Sales", ylab = "Genre Code")

From the above scatter plot it shows that clustering was divided into 3 groups depending on their genres.Further clarity will then be taken to determine which genres are in each cluster without suing codes but the actual genres

Understanding the Clusters

Understanding which type of genres falls in which cluster.

genre_mapping <- unique(video_game_global_sales1[, c("genre_code", "genre")])
table_summary_with_genres <- table(video_game_global_sales1$cluster, video_game_global_sales1$genre)
print(table_summary_with_genres)
   
    Action Adventure Fighting Misc Platform Puzzle Racing Role-Playing Shooter Simulation Sports
  1   3176      1270      818 1670        0      0      0            0       0          0      0
  2      0         0        0    0      820    557   1195         1382       0          0      0
  3      0         0        0    0        0      0      0            0    1232        835   2240
   
    Strategy
  1        0
  2        0
  3      662

From this analysis you can determine that clusters where equaly grouped into 4 genres per each cluster This was further presented graphical using a plot


library(ggplot2)
plot_data <- as.data.frame(table_summary_with_genres)
plot_data$Var1 <- factor(plot_data$Var1, levels = unique(video_game_global_sales1$cluster))
ggplot(plot_data, aes(x = Var1, y = Freq, fill = Var2)) +
  geom_bar(stat = "identity") +
  labs(title = "Genre Distribution in Each Cluster",
       x = "Cluster",
       y = "Count") +
  scale_fill_brewer(palette = "Set3") +  # Adjust the color palette as needed
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))  # Rotate x-axis labels

NA
NA

The graph above shows how each genre where distributed in each cluster also quantifying the amount of genre as we can see there is a high count of Action genres in this data set which is clustered in cluster 2,as well as high count of fighting games which is clustered in cluster 3

Summary

Summary of the cluster

cluster_summary <- aggregate(. ~ cluster, data = video_game_global_sales1[, c("global_sales", "genre_code", "cluster")], mean)
print(cluster_summary)
NA

Global Sales:

Clusters 2 :contains genres with the lowest Global Sales most likely the games in this cluster are cheap considering it has a high count of genres Cluster 1:indicates an overall moderate positive trend in sales within these cluster and genres count in this cluster are low justifying the global sales Cluster 3 has the highest value for global_sales, suggesting a high sales trend of genres in this cluster though it has a low genre count most likely genres code in this cluster are quite expensive and unique.

Centroids Plot

This was also explained by the centroids plot below

library(ggplot2)
centroids <- as.data.frame(kmeans_result$centers)
centroids$cluster <- factor(1:nrow(centroids))  # Add a factor column for cluster assignments
ggplot(centroids, aes(x = genre_code, y = global_sales, color = cluster)) +
  geom_point(size = 3) +
  labs(title = "Cluster Centroids", x = "Genre Code", y = "Global Sales") +
  theme_minimal()

Cluster 1: This cluster has a relatively moderate average global sales compared to cluster 2 Cluster 2:This cluster has the lowest average global sales compared to all other clusters Cluster 3:This cluster has the highest average global sales as compared to all clusters

Explanatory Data

Analysis However the only variable that was considered in this clustering was Genre so before concluding will look at other relationships in the original data according to clusters

Publisher Relationship

Firstly will do a count to determine how many publishers are in each cluster.


  cluster_comparison <- aggregate(. ~ cluster, data = video_game_global_sales1[, c("cluster","publisher")], mean)
  print(cluster_comparison)
  library(ggplot2)
  ggplot(cluster_comparison, aes(x = cluster, fill = publisher)) +
    geom_bar(stat = "count", position = "dodge") +
    labs(title = "Count of Publishers in Each Cluster",
         x = "Cluster", y = "Count") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))

When the count was done we noticed that almost every publisher was there in each cluster with only a few not available in some clusters .Henceforth i further anaylsed this relationship by determining the most 10 popular publisher in each cluster

Publisher Analysis

Identifying the publishers that where used in each cluster considering only the top 10 most popular publisher in each cluster

# Load necessary packages
library(dplyr)
Warning: package ‘dplyr’ was built under R version 4.3.2
Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(ggplot2)


top_publishers_by_cluster <- video_game_global_sales1 %>%
  group_by(cluster, publisher) %>%
  summarise(count = n()) %>%
  arrange(cluster, desc(count)) %>%
  group_by(cluster) %>%
  top_n(10, wt = count) %>%
  ungroup()
`summarise()` has grouped output by 'cluster'. You can override using the `.groups` argument.
ggplot(top_publishers_by_cluster, aes(x = reorder(publisher, -count), y = count, fill = as.factor(cluster))) +
  geom_bar(stat = "identity") +
  labs(title = "Top 10 Publishers in Each Cluster",
       x = "Publisher",
       y = "Count") +
  scale_fill_brewer(palette = "Set3") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) 

NA
NA
NA
NA

Cluster 3:This cluster is dominated by a variety of publisher but mainly Electronic Arts and Konami digital entertainment and it is the only cluster has games published by Take two interactive Publishers making it unique in that way.It spans over a range of publisher however one can conclude that publishers sales the most sophisticated games in this cluster and explains the high sales despite the low count

Cluster 2:This cluster is mainly dominated by Namo Bandai Games arts however its dominants spans over many clusters. Uniquely its the only cluster that sales games published by capcom and Tecmo Koei.Despite its low sales one can conclude that publishers in this cluster sale their cheapest games.

Cluster 1:Cluster 1 contains sales from most publishers however it it the only cluster that sales games published by Square Enix and Nitendo .Cluster 1 has the highest count of all publishers and one can conclude that most publishers charge moderate prices for their games ,justifying the moderate global sales.

Platform

Identifying the platform that where used in each cluster

cluster_comparison <- aggregate(. ~ cluster, data = video_game_global_sales1[, c("cluster","platform")], mean)
  print(cluster_comparison)
  library(ggplot2)
  ggplot(cluster_comparison, aes(x = cluster, fill = platform)) +
    geom_bar(stat = "count", position = "dodge") +
    labs(title = "Count of Platform in Each Cluster",
         x = "Cluster", y = "Count") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))

From the above details we can conclude that almost every platform is there in each cluster but we would like to further analyse which where the most popular 10 platforms in each cluster #Platform Analysis Using the below plot i identified the most 10 popular platforms in each group




top_platforms_by_cluster <- video_game_global_sales1 %>%
  group_by(cluster, platform) %>%
  summarise(count = n()) %>%
  arrange(cluster, desc(count)) %>%
  group_by(cluster) %>%
  top_n(10, wt = count) %>%
  ungroup()  
`summarise()` has grouped output by 'cluster'. You can override using the `.groups` argument.
ggplot(top_platforms_by_cluster, aes(x = reorder(platform, -count), y = count, fill = as.factor(cluster))) +
  geom_bar(stat = "identity") +
  labs(title = "Top 10 Platforms in Each Cluster",
       x = "Platform",
       y = "Count") +
  scale_fill_brewer(palette = "Set3") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))  # Rotate x-axis labels

NA
NA
NA
NA
NA

Cluster 1 This cluster uniquely contains a variety of platforms but mainly dominated by DS and PS games.Also a high count explaing that most platforms are in cluster 1 contains games that have average sales Cluster 2 The cluster contains a variety of platforms but mainly dominating on PS2 and PSP games .Also the cluster is uniquely identified my by 3DS and PSV games.Mostly games in this cluster are considered cheap and not complex

Cluster 3 The clusters is basically dominated by PC platform games however its the only cluster that has GC games.Mostly platforms like gc contains complex and expensive games as despite the low count in this cluster it still has the highest global sales.

Sales trend by Year

Identifying the year sale trend of video games


names(video_game_global_sales1)
 [1] "rank"         "name"         "platform"     "year"         "genre"        "publisher"   
 [7] "na_sales"     "eu_sales"     "jp_sales"     "other_sales"  "global_sales" "genre_code"  
[13] "cluster"     
ggplot(video_game_global_sales1, aes(x = year, y = global_sales, color = as.factor(cluster))) +
  geom_point() +
  stat_smooth(method = "lm", se = FALSE, linetype = "dashed", color = "black") +
  facet_wrap(~cluster, scales = "free_y", ncol = 1) +
  labs(title = "Global Sales Trend by Year Group Within Each Cluster",
       x = "Year Group", y = "Global Sales") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

NA
NA
NA
NA
NA

Cluster 1:Cluster 1 sales are mainly from year 1990 to 2017 and afterwards it has no any presence of sale.However we can also tell that there was a little presence of sales was there in the years 1980 to 1990.

Cluster 2:Cluster 2 sales are mainly from year 1996 to 2017 and afterwards it has no any presence of sale .However we can conclude that genres in cluster 2 where only popular in the earlier years from 1996 to 2017.This will give us a conlusion that Vedio Game Sales has decreased from 2017 up-to now.Also in this cluster there was some presence in the 1980’s

Cluster 3:Cluster 3 sales are from year 1995 to 2017 and afterwards it has no any presence of sale expect for little presence between in 2020.However we can conclude that genres in cluster 3 continued to show its presence during the year frame.

NA Sales

library(ggplot2)

create_clustered_na_trend_graph <- function(cluster) {
  current_data <- video_game_global_sales1[video_game_global_sales1$cluster == cluster, ]
  ggplot(current_data, aes(x = year, y = na_sales, color = as.factor(cluster))) +
    geom_point() +
    stat_smooth(method = "lm", se = FALSE, linetype = "dashed", color = "black") +
    labs(title = paste("Cluster", cluster, "- Trend of NA Sales"),
         x = "Year Group", y = "NA Sales") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
}


for (clust in unique(video_game_global_sales1$cluster)) {
  plot <- create_clustered_na_trend_graph(clust)
  print(plot)
}



combined_plot <- ggplot(video_game_global_sales1, aes(x = year, y = na_sales, color = as.factor(cluster))) +
  geom_point() +
  stat_smooth(method = "lm", se = FALSE, linetype = "dashed", color = "black") +
  labs(title = "Trend of NA Sales by Cluster",
       x = "Year Group", y = "NA Sales") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  facet_wrap(~as.factor(cluster), scales = "free_y", ncol = 2)


print(combined_plot)

NA sales are has high presence in cluster 3 followed by cluster 2 than 1.However one can conclude that people in NA prefer complex games that are in cluster 3.

EU Sales

library(ggplot2)
library(ggplot2)


create_clustered_eu_trend_graph <- function(cluster) {
  current_data <- video_game_global_sales1[video_game_global_sales1$cluster == cluster, ]
  ggplot(current_data, aes(x = year, y = eu_sales, color = as.factor(cluster))) +
    geom_point() +
    stat_smooth(method = "lm", se = FALSE, linetype = "dashed", color = "black") +
    labs(title = paste("Cluster", cluster, "- Trend of EU Sales"),
         x = "Year Group", y = "EU Sales") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
}


for (clust in unique(video_game_global_sales1$cluster)) {
  plot <- create_clustered_eu_trend_graph(clust)
  print(plot)
}



combined_plot_eu <- ggplot(video_game_global_sales1, aes(x = year, y = eu_sales, color = as.factor(cluster))) +
  geom_point() +
  stat_smooth(method = "lm", se = FALSE, linetype = "dashed", color = "black") +
  labs(title = "Trend of EU Sales by Cluster",
       x = "Year Group", y = "EU Sales") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  facet_wrap(~as.factor(cluster), scales = "free_y", ncol = 2)


print(combined_plot_eu)

NA
NA

EU sales are more dominant in cluster 2 followed by cluster 1 then lastly cluster 3 .This concludes that mostly EU sales are in cluster 2.This entails us that residents of the European Union prefer simpler games which are also not costly.

JP Sales

library(ggplot2)

create_clustered_jp_trend_graph <- function(cluster) {
  current_data <- video_game_global_sales1[video_game_global_sales1$cluster == cluster, ]
  ggplot(current_data, aes(x = year, y = jp_sales, color = as.factor(cluster))) +
    geom_point() +
    stat_smooth(method = "lm", se = FALSE, linetype = "dashed", color = "black") +
    labs(title = paste("Cluster", cluster, "- Trend of JP Sales"),
         x = "Year Group", y = "JP Sales") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
}

for (clust in unique(video_game_global_sales1$cluster)) {
  plot <- create_clustered_jp_trend_graph(clust)
  print(plot)
}



combined_plot_jp <- ggplot(video_game_global_sales1, aes(x = year, y = jp_sales, color = as.factor(cluster))) +
  geom_point() +
  stat_smooth(method = "lm", se = FALSE, linetype = "dashed", color = "black") +
  labs(title = "Trend of JP Sales by Cluster",
       x = "Year Group", y = "JP Sales") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  facet_wrap(~as.factor(cluster), scales = "free_y", ncol = 2)


print(combined_plot_jp)

NA
NA

JP sales are more dominant in cluster 2 followed by cluster 1 then lastly cluster 3 .This concludes that mostly JP sales are in cluster 2.This concludes that people in Japan prefer genres in cluster 2 and least prefer those in cluster 1.Opting for less sophisticated games and cheap as well.

CONCLUSION

In summary, this analysis goes into determining the global video game sales according to genres, employing clustering techniques to identify distinct patterns and relationships. The clustering based on global sales and genre revealed three clusters, each with its unique characteristics. Cluster 3 emerged as the leader in global sales followed by cluster 1 and lastly cluster 3, .The decline in sales post-2016 was observed across all clusters. Beyond genre, exploration into publishers and platforms unveiled intriguing insights, showcasing the dominance of specific publishers and platforms in each cluster. The examination of regional sales trends underscored the diverse preferences of North American, European, and Japanese audiences. This analysis not only sheds light on the market dynamics of the video game industry but also offers valuable insights for developers, publishers, and stakeholders to tailor their strategies based on genre, region, and platform preferences. Continued exploration and adaptation to emerging trends are essential for navigating the ever-evolving landscape of the video game market and to determine why the market is decreasing.

LS0tDQp0aXRsZTogIkFuIGFuYWx5c2lzIG9mIEdlbnJlIEdsb2JhbCBzYWxlcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KIyMgUEFJREFNT1lPIFNJTUJBDQojIyA0NjgwNzQNCiMgQ2x1c3RlcmluZyB3aXRoIEstbWVhbnMNCg0KDQojIyBJbnRyb2R1Y3Rpb24NCg0KIk92ZXIgdGhlIHBhc3QgZmV3IGRlY2FkZXMsIHRoZSB2aWRlbyBnYW1lIGJ1c2luZXNzIGhhcyBleHBlcmllbmNlZA0KdHJlbWVuZG91cyBkZXZlbG9wbWVudCBhbmQgdHJhbnNmb3JtYXRpb24sIGVtZXJnaW5nIGFzIGEgbWFqb3IgZm9yY2UgaW4NCnRoZSB3b3JsZHdpZGUgZW50ZXJ0YWlubWVudCBzZWN0b3IuIENvbXByZWhlbmRpbmcgdGhlIGVsZW1lbnRzIHRoYXQgbGVhZA0KdG8gdmlkZW8gZ2FtZSBzdWNjZXNzIGlzIGJlY29taW5nIGluY3JlYXNpbmdseSBpbXBvcnRhbnQgZm9yIGRldmVsb3BlcnMsDQpwdWJsaXNoZXJzLCBhbmQgb3RoZXIgc3Rha2Vob2xkZXJzIGFzIHRoZSBpbmR1c3RyeSBncm93cy4gV2l0aCB0aGUgdXNlDQpvZiBnbG9iYWwgc2FsZXMgZGF0YSwgd2UgbWF5IGV4YW1pbmUgbWFya2V0IGR5bmFtaWNzLCBzcG90IG5ldyB0cmVuZHMsDQphbmQgYXJyaXZlIGF0IGEgd2VsbC1pbmZvcm1lZCBjb25jbHVzaW9ucy4NCg0KSW4gb3JkZXIgdG8gZmluZCB0cmVuZHMgYW5kIGluc2lnaHRzIHRoYXQgcHJvcGVsIGluZHVzdHJ5IHN1Y2Nlc3MsIHdlDQpleGFtaW5lIHRoZSBkYXRhIHNldCBvZiB3b3JsZHdpZGUgdmlkZW8gZ2FtZSBzYWxlcyBpbiB0aGlzIHJlc2VhcmNoLg0KVGhyb3VnaCBkYXRhIHByZXBhcmF0aW9uLCBjbHVzdGVyaW5nIGFuYWx5c2lzLCBhbmQgdmlzdWFsaXphdGlvbnMsIHdlDQpleHBsb3JlIHRoZSBsaW5rcyBiZXR3ZWVuIHNldmVyYWwgZmVhdHVyZXMsIGluY2x1ZGluZyBnZW5yZSwgcGxhdGZvcm0sDQpwdWJsaXNoZXIsIGFuZCBzYWxlcyBudW1iZXJzLCB0byBvYnRhaW4gYSBtb3JlIGRldGFpbGVkIHVuZGVyc3RhbmRpbmcuDQpUaGlzIGFuYWx5c2lzIHdpbGwgdXNlIHR3byB1bnN1cGVydmlzZWQgbGVhcm5pbmcgdGVjaG5pcXVlcyB0aGF0IGstbWVhbnMNCmFsZ29yaXRobSB0byBmaW5kIGNsdXN0ZXJzIGZyb20gdGhlIGRhdGEgc2V0IGFuZCB0aGUgbnVtYmVyIG9mIHRoZQ0KY2x1c3RlcnMgd2lsbCBiZSBkZXRlcm1pbmVkIGJ5IEVsYm93IE1ldGhvZC4iDQoNCiMjIyBEYXRhIExvYWRpbmcNCg0KVGhlIGZpcnN0IHN0ZXAgd2FzIHRvIGxvYWQgdGhlIGRhdGEgYW5kIGRldGVybWluZSB0aGUgc3RydWN0dXJlIGFuZCB0aGUNCmNsYXNzIG9mIHRoZSBkYXRhLiBTbyBJIGRldGVybWluZWQgdGhhdCB0aGUgZGF0YXNldCBjb25zaXN0cyBvZiBudW1lcmljDQpjaGFyYWN0ZXJzIGFuZCBpbnRlZ2Vycy4gSGVuY2Vmb3J0aCwgdGhlcmUgaXMgYSBuZWVkIGZvciBkYXRhDQpwcm9jZXNzaW5nLg0KDQpgYGB7cn0NCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHJlYWRyKQ0KdmlkZW9nYW1lZ2xvYmFsc2FsZXMgPC0gcmVhZF9jc3YoImNsdXN0ZXJpbmcgZmluYWwvdmlkZW9nYW1lZ2xvYmFsc2FsZXMuY3N2IikNClZpZXcodmlkZW9nYW1lZ2xvYmFsc2FsZXMpDQpWaWV3KHZpZGVvZ2FtZWdsb2JhbHNhbGVzKQ0KY2xhc3ModmlkZW9nYW1lZ2xvYmFsc2FsZXMpDQpjb2xuYW1lcyh2aWRlb2dhbWVnbG9iYWxzYWxlcykNCmhlYWQodmlkZW9nYW1lZ2xvYmFsc2FsZXMpDQpgYGANCg0KIyMjIE1pc3NpbmcgdmFsdWVzDQoNCkkgdGhlbiBjaGVja2VkIGZvciBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgZGF0YSBzZXQuVGhlIG9ubHkgY29sdW1uIHdpdGgNCm1pc3NpbmcgZGF0YSB3YXMgdGhlIHllYXIgY29sdW1uIGFuZCBpIGRlY2lkZWQgdG8gb21pdCBhbGwgcm93cyB3aXRoIHRoZQ0KbWlzc2luZyB2YWx1ZSBhcyBpdCB3YXMgaW5zaWduaWZpY2FudCB0byB0aGUgZGF0YSBzZXQNCg0KYGBge3J9DQptaXNzaW5nX3ZhbHVlc19wZXJfY29sdW1uIDwtIGNvbFN1bXMoaXMubmEodmlkZW9nYW1lZ2xvYmFsc2FsZXMpKQ0KcHJpbnQobWlzc2luZ192YWx1ZXNfcGVyX2NvbHVtbikNCg0KdmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxIDwtIG5hLm9taXQodmlkZW9nYW1lZ2xvYmFsc2FsZXMpDQptaXNzaW5nX3ZhbHVlc19wZXJfY29sdW1uIDwtIGNvbFN1bXMoaXMubmEodmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxKSkNCnByaW50KG1pc3NpbmdfdmFsdWVzX3Blcl9jb2x1bW4pDQpgYGANCg0KIyMjIERhdGEgdHlwZQ0KDQpJbiB0aGlzIHN0ZXAgaSBjaGVja2VkIG15IGRhdGEgdHlwZSB2YXJpYWJsZXMgYW5kIGFsc28gdGhlIGhlYWQgb2YgbXkNCmRhdGEgc2V0IHNvIHRoYXQgaSBjYW4gaGF2ZSBhIGNsZWFyIHZpZXcgb2Ygd2hhdCBlYWNoIGNvbHVtbiBjb250YWlucw0KDQpgYGB7cn0NCnByaW50KHNhcHBseSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEsIGNsYXNzKSkNCmhlYWQodmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxKQ0KYGBgDQoNCiMgU3RhbmRhcmRpemluZw0KDQpJIGlkZW50aWZpZWQgYWxsIGNoYXJhY3RlcnMgYW5kIGludGVnZXJzIGNvbHVtbnMgaW4gbXkgZGF0YSBzZXQgYW5kDQpjb252ZXJ0ZWQgaXQgdG8gZmFjdG9ycyBzbyBhcyB0byBzdGFuZGFyZGl6ZSBhbmQgcHJlcGFyZSBteSBkYXRhIGZvcg0KY2x1c3RlcmluZy4gQWxzbyBvbiB0aGlzIHN0YWdlIGkgYWxzbyBzdW1tYXJpemVkIG15IGRhdGEgc2V0DQoNCmBgYHtyfQ0KY2hhcmFjdGVyX2NvbHVtbnMgPC0gc2FwcGx5KHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSwgaXMuY2hhcmFjdGVyKQ0KaW50ZWdlcl9jb2x1bW5zIDwtIHNhcHBseSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEsIGlzLmludGVnZXIpDQp2aWRlb19nYW1lX2dsb2JhbF9zYWxlczFbY2hhcmFjdGVyX2NvbHVtbnNdIDwtbGFwcGx5KHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMVtjaGFyYWN0ZXJfY29sdW1uc10sIGFzLmZhY3RvcikNCnZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMVtpbnRlZ2VyX2NvbHVtbnNdIDwtIGxhcHBseSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczFbaW50ZWdlcl9jb2x1bW5zXSwgYXMuZmFjdG9yKQ0KDQpwcmludChzYXBwbHkodmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxLCBjbGFzcykpDQpzdW1tYXJ5KHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSkNCmBgYA0KDQojIE91dGxpZXJzDQoNCkF0IHRoaXMgc3RhZ2Ugd2UgZGV0ZXJtaW5lZCBvdXRsaWVycyB1c2luZyB0aGUgei1zY29yZSBhbmQgcmVtb3ZlZCBpdA0KZnJvbSBhbGwgbnVtZXJpY2FsIGNvbHVtbnMuSW4gdGhpcyBkYXRhIHNldCBhIHRvdGFsIG9mIDExNTAgb3V0bGllcnMNCndoZXJlIGlkZW50aWZpZWQgYW5kIHJlbW92ZWQgZnJvbSB0aGUgZGF0YSBzZXQuDQoNCmBgYHtyfQ0KbnVtZXJpY19jb2x1bW5zIDwtIGMoIm5hX3NhbGVzIiwgImV1X3NhbGVzIiwgImpwX3NhbGVzIiwgIm90aGVyX3NhbGVzIiwgImdsb2JhbF9zYWxlcyIpDQp6X3Njb3JlcyA8LSBzY2FsZSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczFbbnVtZXJpY19jb2x1bW5zXSkNCm91dGxpZXJzIDwtIGFwcGx5KGFicyh6X3Njb3JlcykgPiAzLCAxLCBhbnkpDQpjYXQoIk51bWJlciBvZiBvdXRsaWVycyBpZGVudGlmaWVkOiIsIHN1bShvdXRsaWVycyksICJcbiIpDQp2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEgPC0gdmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxWyFvdXRsaWVycywgXQ0Kc3VtbWFyeSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEpDQpkaW0odmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxKQ0KYGBgDQoNCiMgRW5jb2RpbmcNCg0KVGhpcyBpcyB0aGUgbGFzdCBzdGVwIG9mIGRhdGEgY2xlYW5pbmcgYW5kIHByb2Nlc3Npbmcgd2hlbiBpDQpzdGFuZGFyZGl6ZWQgYWxsIG51bWVyaWMgY29sdW1ucyBhbmQgYWxzbyBlbmNvZGVkIGdlbnJlIHNvIHRoYXQgaXQgY2FuDQpiZSB1c2VkIGZvciBjbHVzdGVyaW5nIHB1cnBvc2VzIGFzIG15IG1haW4gZ29hbCBpbiB0aGlzIGNsdXN0ZXJpbmcgaXMgdG8NCmlkZW50aWZ5IHRoZSBlZmZlY3Qgb2YgdGhlIHZpZGVvIGdhbWUgZ2VucmUgd2l0aCBHbG9iYWwgU2FsZXMNCg0KYGBge3J9DQoNCnZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSRnZW5yZV9jb2RlIDwtIGFzLm51bWVyaWMoZmFjdG9yKHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSRnZW5yZSkpDQoNCg0KdW5pcXVlX2dlbnJlcyA8LSBsZXZlbHMoZmFjdG9yKHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSRnZW5yZSkpDQp1bmlxdWVfZ2VucmVfY29kZXMgPC0gdW5pcXVlKHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSRnZW5yZV9jb2RlKQ0KDQoNCmNhdCgiR2VucmUgQ29kZXM6XG4iKQ0KZm9yIChpIGluIHNlcV9hbG9uZyh1bmlxdWVfZ2VucmVzKSkgew0KICBjYXQoc3ByaW50ZigiJXM6ICVkXG4iLCB1bmlxdWVfZ2VucmVzW2ldLCB1bmlxdWVfZ2VucmVfY29kZXNbaV0pKQ0KfQ0KDQpzdW1tYXJ5KHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSkNCg0KYGBgDQoNCmBgYHtyfQ0KdmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxW251bWVyaWNfY29sdW1uc10gPC0gc2NhbGUodmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxW251bWVyaWNfY29sdW1uc10pDQp2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEkZ2VucmVfY29kZSA8LSBhcy5udW1lcmljKGZhY3Rvcih2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEkZ2VucmUpKQ0Kc3VtbWFyeSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEpDQoNCmBgYA0KDQojIENsdXN0ZXJpbmcNCg0KIyMgRWxib3cgTWV0aG9kDQoNCk5vdyB0aGF0IGkgaGF2ZSBjbGVhbmVkIGFuZCBzdGFuZGFyZGl6ZWQgbXkgZGF0YSBpIHdpbGwgcHJvY2VlZCBhbmQgZ28NCnRvIGNsdXN0ZXJpbmcuIEkgd2lsbCB1c2UgRWxib3cgbWV0aG9kIHRvIGRldGVybWluZSB0aGUgbnVtYmVyIG9mDQpjbHVzdGVycyBmb3IgdGhlIHNlbGVjdGVkIGZlYXR1cmUgdG8gYWNoaWV2ZSB0aGUgZ29hbCBvZiBteSBwcm9qZWN0DQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQoNCmVsYm93X3BvaW50IDwtIGZ1bmN0aW9uKHdjc3NfdmFsdWVzKSB7DQogIGRlbHRhcyA8LSBjKDAsIGRpZmYod2Nzc192YWx1ZXMpKQ0KIA0KICBlbGJvdyA8LSB3aGljaChkZWx0YXMgPT0gbWF4KGRlbHRhcykpDQogIHJldHVybihlbGJvdykNCn0NCg0KY2FsY3VsYXRlX3djc3MgPC0gZnVuY3Rpb24oZGF0YSwgaykgew0KICBrbWVhbnNfcmVzdWx0IDwtIGttZWFucyhkYXRhLCBjZW50ZXJzID0gaywgbnN0YXJ0ID0gMTApDQogIHJldHVybihrbWVhbnNfcmVzdWx0JHRvdC53aXRoaW5zcykNCn0NCg0KZmVhdHVyZXNfZm9yX2NsdXN0ZXJpbmcgPC0gdmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxWywgYygiZ2xvYmFsX3NhbGVzIiwgImdlbnJlX2NvZGUiKV0NCmtfdmFsdWVzIDwtIDE6MTAgIA0Kd2Nzc192YWx1ZXMgPC0gc2FwcGx5KGtfdmFsdWVzLCBmdW5jdGlvbihrKSBjYWxjdWxhdGVfd2NzcyhmZWF0dXJlc19mb3JfY2x1c3RlcmluZywgaykpDQoNCg0KcGxvdChrX3ZhbHVlcywgd2Nzc192YWx1ZXMsIHR5cGUgPSAiYiIsIHBjaCA9IDE5LCBmcmFtZSA9IEZBTFNFLCANCiAgICAgeGxhYiA9ICJOdW1iZXIgb2YgQ2x1c3RlcnMgKGspIiwgeWxhYiA9ICJXaXRoaW4tQ2x1c3RlciBTdW0gb2YgU3F1YXJlcyAoV0NTUykiLA0KICAgICBtYWluID0gIkVsYm93IE1ldGhvZCBmb3IgT3B0aW1hbCBOdW1iZXIgb2YgQ2x1c3RlcnMiKQ0KDQpgYGANCg0KQmFzZWQgb2YgbXkgZWxib3cgZ3JhcGggaSB3aWxsIGRlY2lkZSB0byB1c2UgMyBjbHVzdGVycyBiYXNlZCBvbg0Kb2JzZXJ2aW5nIGEgbW9yZSBncmFkdWFsIHN0YWJpbGl6YXRpb24gb2YgU1NEIGFmdGVyIDMgY2x1c3RlcnMsDQpzdWdnZXN0aW5nIHRoYXQgdGhlIGFkZGl0aW9uYWwgY2x1c3RlciBjYXB0dXJlcyBtZWFuaW5nZnVsIHZhcmlhdGlvbiBpbg0KdGhlIGRhdGEuDQoNCiMgSyBtZWFucyBzdW1tYXJ5DQoNCkJhc2VkIG9uIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgSSBjbHVzdGVyZWQgdGhlIG1haW4gZmVhdHVyZXMgb2YgdGhpcw0KcHJvamVjdCB3aGljaCBpcyBnbG9iYWwgc2FsZXMgYW5kIGdlbnJlLg0KDQpgYGB7cn0NCg0Ka21lYW5zX3Jlc3VsdCA8LSBrbWVhbnMoZmVhdHVyZXNfZm9yX2NsdXN0ZXJpbmcsIGNlbnRlcnMgPSAzLCBuc3RhcnQgPSAxMCkNCg0KDQpzdHIoa21lYW5zX3Jlc3VsdCRjbHVzdGVyKQ0KbGVuZ3RoKGttZWFuc19yZXN1bHQkY2x1c3RlcikNCg0KDQp2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEkY2x1c3RlciA8LSBhcy5mYWN0b3Ioa21lYW5zX3Jlc3VsdCRjbHVzdGVyKQ0KDQoNCnN1bW1hcnkodmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxJGNsdXN0ZXIpDQoNCg0KYGBgDQoNCkFjY29yZGluZyB0byB0aGUgcmVzdWx0cyBteSBkYXRhIGlzIGdyb3VwZWQgaW50byB0aHJlZSBjbHVzdGVycyB3aXRoIG15DQpmaXJzdCBjbHVzdGVyIGNvbnRhaW5pbmcgMzYwOCBvYnNlcnZhdGlvbiBhbmQgNjUxMSBhbmQgNDU4OCBmb3IgdGhlDQpzZWNvbmQgYW5kIHRoaXJkIGNsdXN0ZXIuIFRoZSBjbHVzdGVyaW5nIHBsb3QgYXJlIGJlbG93IHNob3dlZCBieSBhDQpzY2F0dGVyIHBsb3QgZm9yIGNsYXJpdHkuIA0KDQojIEstbWVhbnMgc2NhdHRlciBwbG90DQoNClNjYXR0ZXIgcGxvdCBvZiAnZ2xvYmFsX3NhbGVzJyB2cy4gJ2dlbnJlX2NvZGUnIHdpdGggY2x1c3RlciBjb2xvcmluZw0KDQpgYGB7cn0NCg0KcGxvdChmZWF0dXJlc19mb3JfY2x1c3RlcmluZywgY29sID0gYygiYmxhY2siLCAicmVkIiwgImdyZWVuIilba21lYW5zX3Jlc3VsdCRjbHVzdGVyXSwgcGNoID0gMTYsDQogICAgIG1haW4gPSAiSy1NZWFucyBDbHVzdGVyaW5nIiwgeGxhYiA9ICJHbG9iYWwgU2FsZXMiLCB5bGFiID0gIkdlbnJlIENvZGUiKQ0KDQpgYGANCg0KRnJvbSB0aGUgYWJvdmUgc2NhdHRlciBwbG90IGl0IHNob3dzIHRoYXQgY2x1c3RlcmluZyB3YXMgZGl2aWRlZCBpbnRvIDMNCmdyb3VwcyBkZXBlbmRpbmcgb24gdGhlaXIgZ2VucmVzLkZ1cnRoZXIgY2xhcml0eSB3aWxsIHRoZW4gYmUgdGFrZW4gdG8NCmRldGVybWluZSB3aGljaCBnZW5yZXMgYXJlIGluIGVhY2ggY2x1c3RlciB3aXRob3V0IHN1aW5nIGNvZGVzIGJ1dCB0aGUNCmFjdHVhbCBnZW5yZXMgDQoNCiMjIFVuZGVyc3RhbmRpbmcgdGhlIENsdXN0ZXJzDQoNClVuZGVyc3RhbmRpbmcgd2hpY2ggdHlwZSBvZiBnZW5yZXMgZmFsbHMgaW4gd2hpY2ggY2x1c3Rlci4NCg0KYGBge3J9DQpnZW5yZV9tYXBwaW5nIDwtIHVuaXF1ZSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczFbLCBjKCJnZW5yZV9jb2RlIiwgImdlbnJlIildKQ0KdGFibGVfc3VtbWFyeV93aXRoX2dlbnJlcyA8LSB0YWJsZSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEkY2x1c3RlciwgdmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxJGdlbnJlKQ0KcHJpbnQodGFibGVfc3VtbWFyeV93aXRoX2dlbnJlcykNCmBgYA0KDQpGcm9tIHRoaXMgYW5hbHlzaXMgeW91IGNhbiBkZXRlcm1pbmUgdGhhdCBjbHVzdGVycyB3aGVyZSBlcXVhbHkgZ3JvdXBlZA0KaW50byA0IGdlbnJlcyBwZXIgZWFjaCBjbHVzdGVyIFRoaXMgd2FzIGZ1cnRoZXIgcHJlc2VudGVkIGdyYXBoaWNhbA0KdXNpbmcgYSBwbG90DQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KGdncGxvdDIpDQpwbG90X2RhdGEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZV9zdW1tYXJ5X3dpdGhfZ2VucmVzKQ0KcGxvdF9kYXRhJFZhcjEgPC0gZmFjdG9yKHBsb3RfZGF0YSRWYXIxLCBsZXZlbHMgPSB1bmlxdWUodmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxJGNsdXN0ZXIpKQ0KZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHggPSBWYXIxLCB5ID0gRnJlcSwgZmlsbCA9IFZhcjIpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGxhYnModGl0bGUgPSAiR2VucmUgRGlzdHJpYnV0aW9uIGluIEVhY2ggQ2x1c3RlciIsDQogICAgICAgeCA9ICJDbHVzdGVyIiwNCiAgICAgICB5ID0gIkNvdW50IikgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDMiKSArICAjIEFkanVzdCB0aGUgY29sb3IgcGFsZXR0ZSBhcyBuZWVkZWQNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgICMgUm90YXRlIHgtYXhpcyBsYWJlbHMNCg0KDQpgYGANCg0KVGhlIGdyYXBoIGFib3ZlIHNob3dzIGhvdyBlYWNoIGdlbnJlIHdoZXJlIGRpc3RyaWJ1dGVkIGluIGVhY2ggY2x1c3Rlcg0KYWxzbyBxdWFudGlmeWluZyB0aGUgYW1vdW50IG9mIGdlbnJlIGFzIHdlIGNhbiBzZWUgdGhlcmUgaXMgYSBoaWdoIGNvdW50DQpvZiBBY3Rpb24gZ2VucmVzIGluIHRoaXMgZGF0YSBzZXQgd2hpY2ggaXMgY2x1c3RlcmVkIGluIGNsdXN0ZXIgMixhcw0Kd2VsbCBhcyBoaWdoIGNvdW50IG9mIGZpZ2h0aW5nIGdhbWVzIHdoaWNoIGlzIGNsdXN0ZXJlZCBpbiBjbHVzdGVyIDMNCg0KIyMgU3VtbWFyeQ0KDQpTdW1tYXJ5IG9mIHRoZSBjbHVzdGVyDQoNCmBgYHtyfQ0KY2x1c3Rlcl9zdW1tYXJ5IDwtIGFnZ3JlZ2F0ZSguIH4gY2x1c3RlciwgZGF0YSA9IHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMVssIGMoImdsb2JhbF9zYWxlcyIsICJnZW5yZV9jb2RlIiwgImNsdXN0ZXIiKV0sIG1lYW4pDQpwcmludChjbHVzdGVyX3N1bW1hcnkpDQoNCmBgYA0KDQpHbG9iYWwgU2FsZXM6DQoNCkNsdXN0ZXJzIDIgOmNvbnRhaW5zIGdlbnJlcyB3aXRoIHRoZSBsb3dlc3QgR2xvYmFsIFNhbGVzIG1vc3QgbGlrZWx5IHRoZQ0KZ2FtZXMgaW4gdGhpcyBjbHVzdGVyIGFyZSBjaGVhcCBjb25zaWRlcmluZyBpdCBoYXMgYSBoaWdoIGNvdW50IG9mDQpnZW5yZXMgQ2x1c3RlciAxOmluZGljYXRlcyBhbiBvdmVyYWxsIG1vZGVyYXRlIHBvc2l0aXZlIHRyZW5kIGluIHNhbGVzDQp3aXRoaW4gdGhlc2UgY2x1c3RlciBhbmQgZ2VucmVzIGNvdW50IGluIHRoaXMgY2x1c3RlciBhcmUgbG93IGp1c3RpZnlpbmcNCnRoZSBnbG9iYWwgc2FsZXMgQ2x1c3RlciAzIGhhcyB0aGUgaGlnaGVzdCB2YWx1ZSBmb3IgZ2xvYmFsX3NhbGVzLA0Kc3VnZ2VzdGluZyBhIGhpZ2ggc2FsZXMgdHJlbmQgb2YgZ2VucmVzIGluIHRoaXMgY2x1c3RlciB0aG91Z2ggaXQgaGFzIGENCmxvdyBnZW5yZSBjb3VudCBtb3N0IGxpa2VseSBnZW5yZXMgY29kZSBpbiB0aGlzIGNsdXN0ZXIgYXJlIHF1aXRlDQpleHBlbnNpdmUgYW5kIHVuaXF1ZS4gDQoNCiMgQ2VudHJvaWRzIFBsb3QgDQoNClRoaXMgd2FzIGFsc28gZXhwbGFpbmVkIGJ5IHRoZQ0KY2VudHJvaWRzIHBsb3QgIGJlbG93DQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KY2VudHJvaWRzIDwtIGFzLmRhdGEuZnJhbWUoa21lYW5zX3Jlc3VsdCRjZW50ZXJzKQ0KY2VudHJvaWRzJGNsdXN0ZXIgPC0gZmFjdG9yKDE6bnJvdyhjZW50cm9pZHMpKSAgIyBBZGQgYSBmYWN0b3IgY29sdW1uIGZvciBjbHVzdGVyIGFzc2lnbm1lbnRzDQpnZ3Bsb3QoY2VudHJvaWRzLCBhZXMoeCA9IGdlbnJlX2NvZGUsIHkgPSBnbG9iYWxfc2FsZXMsIGNvbG9yID0gY2x1c3RlcikpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMykgKw0KICBsYWJzKHRpdGxlID0gIkNsdXN0ZXIgQ2VudHJvaWRzIiwgeCA9ICJHZW5yZSBDb2RlIiwgeSA9ICJHbG9iYWwgU2FsZXMiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCkNsdXN0ZXIgMTogVGhpcyBjbHVzdGVyIGhhcyBhIHJlbGF0aXZlbHkgbW9kZXJhdGUgYXZlcmFnZSBnbG9iYWwgc2FsZXMNCmNvbXBhcmVkIHRvIGNsdXN0ZXIgMiBDbHVzdGVyIDI6VGhpcyBjbHVzdGVyIGhhcyB0aGUgbG93ZXN0IGF2ZXJhZ2UNCmdsb2JhbCBzYWxlcyBjb21wYXJlZCB0byBhbGwgb3RoZXIgY2x1c3RlcnMgQ2x1c3RlciAzOlRoaXMgY2x1c3RlciBoYXMNCnRoZSBoaWdoZXN0IGF2ZXJhZ2UgZ2xvYmFsIHNhbGVzIGFzIGNvbXBhcmVkIHRvIGFsbCBjbHVzdGVycw0KDQojIyBFeHBsYW5hdG9yeSBEYXRhDQoNCkFuYWx5c2lzIEhvd2V2ZXIgdGhlIG9ubHkgdmFyaWFibGUgdGhhdCB3YXMgY29uc2lkZXJlZCBpbiB0aGlzDQpjbHVzdGVyaW5nIHdhcyBHZW5yZSBzbyBiZWZvcmUgY29uY2x1ZGluZyB3aWxsIGxvb2sgYXQgb3RoZXINCnJlbGF0aW9uc2hpcHMgaW4gdGhlIG9yaWdpbmFsIGRhdGEgYWNjb3JkaW5nIHRvIGNsdXN0ZXJzDQoNCiMgUHVibGlzaGVyIFJlbGF0aW9uc2hpcA0KDQpGaXJzdGx5IHdpbGwgZG8gYSBjb3VudCB0byBkZXRlcm1pbmUgaG93IG1hbnkgcHVibGlzaGVycyBhcmUgaW4gZWFjaA0KY2x1c3Rlci4NCg0KYGBge3J9DQoNCiAgY2x1c3Rlcl9jb21wYXJpc29uIDwtIGFnZ3JlZ2F0ZSguIH4gY2x1c3RlciwgZGF0YSA9IHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMVssIGMoImNsdXN0ZXIiLCJwdWJsaXNoZXIiKV0sIG1lYW4pDQogIHByaW50KGNsdXN0ZXJfY29tcGFyaXNvbikNCiAgbGlicmFyeShnZ3Bsb3QyKQ0KICBnZ3Bsb3QoY2x1c3Rlcl9jb21wYXJpc29uLCBhZXMoeCA9IGNsdXN0ZXIsIGZpbGwgPSBwdWJsaXNoZXIpKSArDQogICAgZ2VvbV9iYXIoc3RhdCA9ICJjb3VudCIsIHBvc2l0aW9uID0gImRvZGdlIikgKw0KICAgIGxhYnModGl0bGUgPSAiQ291bnQgb2YgUHVibGlzaGVycyBpbiBFYWNoIENsdXN0ZXIiLA0KICAgICAgICAgeCA9ICJDbHVzdGVyIiwgeSA9ICJDb3VudCIpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQpgYGANCg0KV2hlbiB0aGUgY291bnQgd2FzIGRvbmUgd2Ugbm90aWNlZCB0aGF0IGFsbW9zdCBldmVyeSBwdWJsaXNoZXIgd2FzIHRoZXJlDQppbiBlYWNoIGNsdXN0ZXIgd2l0aCBvbmx5IGEgZmV3IG5vdCBhdmFpbGFibGUgaW4gc29tZSBjbHVzdGVycw0KLkhlbmNlZm9ydGggaSBmdXJ0aGVyIGFuYXlsc2VkIHRoaXMgcmVsYXRpb25zaGlwIGJ5IGRldGVybWluaW5nIHRoZSBtb3N0DQoxMCBwb3B1bGFyIHB1Ymxpc2hlciBpbiBlYWNoIGNsdXN0ZXINCg0KIyBQdWJsaXNoZXIgQW5hbHlzaXMNCg0KSWRlbnRpZnlpbmcgdGhlIHB1Ymxpc2hlcnMgdGhhdCB3aGVyZSB1c2VkIGluIGVhY2ggY2x1c3RlciBjb25zaWRlcmluZw0Kb25seSB0aGUgdG9wIDEwIG1vc3QgcG9wdWxhciBwdWJsaXNoZXIgaW4gZWFjaCBjbHVzdGVyDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQoNCnRvcF9wdWJsaXNoZXJzX2J5X2NsdXN0ZXIgPC0gdmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxICU+JQ0KICBncm91cF9ieShjbHVzdGVyLCBwdWJsaXNoZXIpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGNsdXN0ZXIsIGRlc2MoY291bnQpKSAlPiUNCiAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lDQogIHRvcF9uKDEwLCB3dCA9IGNvdW50KSAlPiUNCiAgdW5ncm91cCgpDQoNCg0KZ2dwbG90KHRvcF9wdWJsaXNoZXJzX2J5X2NsdXN0ZXIsIGFlcyh4ID0gcmVvcmRlcihwdWJsaXNoZXIsIC1jb3VudCksIHkgPSBjb3VudCwgZmlsbCA9IGFzLmZhY3RvcihjbHVzdGVyKSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgbGFicyh0aXRsZSA9ICJUb3AgMTAgUHVibGlzaGVycyBpbiBFYWNoIENsdXN0ZXIiLA0KICAgICAgIHggPSAiUHVibGlzaGVyIiwNCiAgICAgICB5ID0gIkNvdW50IikgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDMiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpIA0KDQoNCg0KDQpgYGANCg0KQ2x1c3RlciAzOlRoaXMgY2x1c3RlciBpcyBkb21pbmF0ZWQgYnkgYSB2YXJpZXR5IG9mIHB1Ymxpc2hlciBidXQgbWFpbmx5DQpFbGVjdHJvbmljIEFydHMgYW5kIEtvbmFtaSBkaWdpdGFsIGVudGVydGFpbm1lbnQgYW5kIGl0IGlzIHRoZSBvbmx5DQpjbHVzdGVyIGhhcyBnYW1lcyBwdWJsaXNoZWQgYnkgVGFrZSB0d28gaW50ZXJhY3RpdmUgUHVibGlzaGVycyBtYWtpbmcgaXQNCnVuaXF1ZSBpbiB0aGF0IHdheS5JdCBzcGFucyBvdmVyIGEgcmFuZ2Ugb2YgcHVibGlzaGVyIGhvd2V2ZXIgb25lIGNhbg0KY29uY2x1ZGUgdGhhdCBwdWJsaXNoZXJzIHNhbGVzIHRoZSBtb3N0IHNvcGhpc3RpY2F0ZWQgZ2FtZXMgaW4gdGhpcw0KY2x1c3RlciBhbmQgZXhwbGFpbnMgdGhlIGhpZ2ggc2FsZXMgZGVzcGl0ZSB0aGUgbG93IGNvdW50DQoNCkNsdXN0ZXIgMjpUaGlzIGNsdXN0ZXIgaXMgbWFpbmx5IGRvbWluYXRlZCBieSBOYW1vIEJhbmRhaSBHYW1lcyBhcnRzDQpob3dldmVyIGl0cyBkb21pbmFudHMgc3BhbnMgb3ZlciBtYW55IGNsdXN0ZXJzLiBVbmlxdWVseSBpdHMgdGhlIG9ubHkNCmNsdXN0ZXIgdGhhdCBzYWxlcyBnYW1lcyBwdWJsaXNoZWQgYnkgY2FwY29tIGFuZCBUZWNtbyBLb2VpLkRlc3BpdGUgaXRzDQpsb3cgc2FsZXMgb25lIGNhbiBjb25jbHVkZSB0aGF0IHB1Ymxpc2hlcnMgaW4gdGhpcyBjbHVzdGVyIHNhbGUgdGhlaXINCmNoZWFwZXN0IGdhbWVzLg0KDQpDbHVzdGVyIDE6Q2x1c3RlciAxIGNvbnRhaW5zIHNhbGVzIGZyb20gbW9zdCBwdWJsaXNoZXJzIGhvd2V2ZXIgaXQgaXQNCnRoZSBvbmx5IGNsdXN0ZXIgdGhhdCBzYWxlcyBnYW1lcyBwdWJsaXNoZWQgYnkgU3F1YXJlIEVuaXggYW5kIE5pdGVuZG8NCi5DbHVzdGVyIDEgaGFzIHRoZSBoaWdoZXN0IGNvdW50IG9mIGFsbCBwdWJsaXNoZXJzIGFuZCBvbmUgY2FuIGNvbmNsdWRlDQp0aGF0IG1vc3QgcHVibGlzaGVycyBjaGFyZ2UgbW9kZXJhdGUgcHJpY2VzIGZvciB0aGVpciBnYW1lcyAsanVzdGlmeWluZw0KdGhlIG1vZGVyYXRlIGdsb2JhbCBzYWxlcy4NCg0KIyBQbGF0Zm9ybQ0KDQpJZGVudGlmeWluZyB0aGUgcGxhdGZvcm0gdGhhdCB3aGVyZSB1c2VkIGluIGVhY2ggY2x1c3Rlcg0KDQpgYGB7cn0NCmNsdXN0ZXJfY29tcGFyaXNvbiA8LSBhZ2dyZWdhdGUoLiB+IGNsdXN0ZXIsIGRhdGEgPSB2aWRlb19nYW1lX2dsb2JhbF9zYWxlczFbLCBjKCJjbHVzdGVyIiwicGxhdGZvcm0iKV0sIG1lYW4pDQogIHByaW50KGNsdXN0ZXJfY29tcGFyaXNvbikNCiAgbGlicmFyeShnZ3Bsb3QyKQ0KICBnZ3Bsb3QoY2x1c3Rlcl9jb21wYXJpc29uLCBhZXMoeCA9IGNsdXN0ZXIsIGZpbGwgPSBwbGF0Zm9ybSkpICsNCiAgICBnZW9tX2JhcihzdGF0ID0gImNvdW50IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArDQogICAgbGFicyh0aXRsZSA9ICJDb3VudCBvZiBQbGF0Zm9ybSBpbiBFYWNoIENsdXN0ZXIiLA0KICAgICAgICAgeCA9ICJDbHVzdGVyIiwgeSA9ICJDb3VudCIpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQpgYGANCg0KRnJvbSB0aGUgYWJvdmUgZGV0YWlscyB3ZSBjYW4gY29uY2x1ZGUgdGhhdCBhbG1vc3QgZXZlcnkgcGxhdGZvcm0gaXMNCnRoZXJlIGluIGVhY2ggY2x1c3RlciBidXQgd2Ugd291bGQgbGlrZSB0byBmdXJ0aGVyIGFuYWx5c2Ugd2hpY2ggd2hlcmUNCnRoZSBtb3N0IHBvcHVsYXIgMTAgcGxhdGZvcm1zIGluIGVhY2ggY2x1c3RlciAjUGxhdGZvcm0gQW5hbHlzaXMgVXNpbmcNCnRoZSBiZWxvdyBwbG90IGkgaWRlbnRpZmllZCB0aGUgbW9zdCAxMCBwb3B1bGFyIHBsYXRmb3JtcyBpbiBlYWNoIGdyb3VwDQoNCmBgYHtyfQ0KDQoNCg0KdG9wX3BsYXRmb3Jtc19ieV9jbHVzdGVyIDwtIHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSAlPiUNCiAgZ3JvdXBfYnkoY2x1c3RlciwgcGxhdGZvcm0pICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGNsdXN0ZXIsIGRlc2MoY291bnQpKSAlPiUNCiAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lDQogIHRvcF9uKDEwLCB3dCA9IGNvdW50KSAlPiUNCiAgdW5ncm91cCgpICANCg0KDQpnZ3Bsb3QodG9wX3BsYXRmb3Jtc19ieV9jbHVzdGVyLCBhZXMoeCA9IHJlb3JkZXIocGxhdGZvcm0sIC1jb3VudCksIHkgPSBjb3VudCwgZmlsbCA9IGFzLmZhY3RvcihjbHVzdGVyKSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgbGFicyh0aXRsZSA9ICJUb3AgMTAgUGxhdGZvcm1zIGluIEVhY2ggQ2x1c3RlciIsDQogICAgICAgeCA9ICJQbGF0Zm9ybSIsDQogICAgICAgeSA9ICJDb3VudCIpICsNCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSAgIyBSb3RhdGUgeC1heGlzIGxhYmVscw0KDQoNCg0KDQoNCmBgYA0KDQpDbHVzdGVyIDEgVGhpcyBjbHVzdGVyIHVuaXF1ZWx5IGNvbnRhaW5zIGEgdmFyaWV0eSBvZiBwbGF0Zm9ybXMgYnV0DQptYWlubHkgZG9taW5hdGVkIGJ5IERTIGFuZCBQUyBnYW1lcy5BbHNvIGEgaGlnaCBjb3VudCBleHBsYWluZyB0aGF0IG1vc3QNCnBsYXRmb3JtcyBhcmUgaW4gY2x1c3RlciAxIGNvbnRhaW5zIGdhbWVzIHRoYXQgaGF2ZSBhdmVyYWdlIHNhbGVzDQpDbHVzdGVyIDIgVGhlIGNsdXN0ZXIgY29udGFpbnMgYSB2YXJpZXR5IG9mIHBsYXRmb3JtcyBidXQgbWFpbmx5DQpkb21pbmF0aW5nIG9uIFBTMiBhbmQgUFNQIGdhbWVzIC5BbHNvIHRoZSBjbHVzdGVyIGlzIHVuaXF1ZWx5IGlkZW50aWZpZWQNCm15IGJ5IDNEUyBhbmQgUFNWIGdhbWVzLk1vc3RseSBnYW1lcyBpbiB0aGlzIGNsdXN0ZXIgYXJlIGNvbnNpZGVyZWQNCmNoZWFwIGFuZCBub3QgY29tcGxleA0KDQpDbHVzdGVyIDMgVGhlIGNsdXN0ZXJzIGlzIGJhc2ljYWxseSBkb21pbmF0ZWQgYnkgUEMgcGxhdGZvcm0gZ2FtZXMNCmhvd2V2ZXIgaXRzIHRoZSBvbmx5IGNsdXN0ZXIgdGhhdCBoYXMgR0MgZ2FtZXMuTW9zdGx5IHBsYXRmb3JtcyBsaWtlIGdjDQpjb250YWlucyBjb21wbGV4IGFuZCBleHBlbnNpdmUgZ2FtZXMgYXMgZGVzcGl0ZSB0aGUgbG93IGNvdW50IGluIHRoaXMNCmNsdXN0ZXIgaXQgc3RpbGwgaGFzIHRoZSBoaWdoZXN0IGdsb2JhbCBzYWxlcy4NCg0KIyBTYWxlcyB0cmVuZCBieSBZZWFyDQoNCklkZW50aWZ5aW5nIHRoZSB5ZWFyIHNhbGUgdHJlbmQgb2YgdmlkZW8gZ2FtZXMNCg0KYGBge3J9DQoNCm5hbWVzKHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSkNCg0KZ2dwbG90KHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSwgYWVzKHggPSB5ZWFyLCB5ID0gZ2xvYmFsX3NhbGVzLCBjb2xvciA9IGFzLmZhY3RvcihjbHVzdGVyKSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgc3RhdF9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGZhY2V0X3dyYXAofmNsdXN0ZXIsIHNjYWxlcyA9ICJmcmVlX3kiLCBuY29sID0gMSkgKw0KICBsYWJzKHRpdGxlID0gIkdsb2JhbCBTYWxlcyBUcmVuZCBieSBZZWFyIEdyb3VwIFdpdGhpbiBFYWNoIENsdXN0ZXIiLA0KICAgICAgIHggPSAiWWVhciBHcm91cCIsIHkgPSAiR2xvYmFsIFNhbGVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQoNCg0KDQoNCmBgYA0KDQpDbHVzdGVyIDE6Q2x1c3RlciAxIHNhbGVzIGFyZSBtYWlubHkgZnJvbSB5ZWFyIDE5OTAgdG8gMjAxNyBhbmQNCmFmdGVyd2FyZHMgaXQgaGFzIG5vIGFueSBwcmVzZW5jZSBvZiBzYWxlLkhvd2V2ZXIgd2UgY2FuIGFsc28gdGVsbCB0aGF0DQp0aGVyZSB3YXMgYSBsaXR0bGUgcHJlc2VuY2Ugb2Ygc2FsZXMgd2FzIHRoZXJlIGluIHRoZSB5ZWFycyAxOTgwIHRvDQoxOTkwLg0KDQpDbHVzdGVyIDI6Q2x1c3RlciAyIHNhbGVzIGFyZSBtYWlubHkgZnJvbSB5ZWFyIDE5OTYgdG8gMjAxNyBhbmQNCmFmdGVyd2FyZHMgaXQgaGFzIG5vIGFueSBwcmVzZW5jZSBvZiBzYWxlIC5Ib3dldmVyIHdlIGNhbiBjb25jbHVkZSB0aGF0DQpnZW5yZXMgaW4gY2x1c3RlciAyIHdoZXJlIG9ubHkgcG9wdWxhciBpbiB0aGUgZWFybGllciB5ZWFycyBmcm9tIDE5OTYgdG8NCjIwMTcuVGhpcyB3aWxsIGdpdmUgdXMgYSBjb25sdXNpb24gdGhhdCBWZWRpbyBHYW1lIFNhbGVzIGhhcyBkZWNyZWFzZWQNCmZyb20gMjAxNyB1cC10byBub3cuQWxzbyBpbiB0aGlzIGNsdXN0ZXIgdGhlcmUgd2FzIHNvbWUgcHJlc2VuY2UgaW4gdGhlDQoxOTgwJ3MNCg0KQ2x1c3RlciAzOkNsdXN0ZXIgMyBzYWxlcyBhcmUgZnJvbSB5ZWFyIDE5OTUgdG8gMjAxNyBhbmQgYWZ0ZXJ3YXJkcyBpdA0KaGFzIG5vIGFueSBwcmVzZW5jZSBvZiBzYWxlIGV4cGVjdCBmb3IgbGl0dGxlIHByZXNlbmNlIGJldHdlZW4gaW4NCjIwMjAuSG93ZXZlciB3ZSBjYW4gY29uY2x1ZGUgdGhhdCBnZW5yZXMgaW4gY2x1c3RlciAzIGNvbnRpbnVlZCB0byBzaG93DQppdHMgcHJlc2VuY2UgZHVyaW5nIHRoZSB5ZWFyIGZyYW1lLg0KDQojIyBSZWdpb25hbCBzYWxlIFRyZW5kcw0KDQojIE5BIFNhbGVzDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQpjcmVhdGVfY2x1c3RlcmVkX25hX3RyZW5kX2dyYXBoIDwtIGZ1bmN0aW9uKGNsdXN0ZXIpIHsNCiAgY3VycmVudF9kYXRhIDwtIHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMVt2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEkY2x1c3RlciA9PSBjbHVzdGVyLCBdDQogIGdncGxvdChjdXJyZW50X2RhdGEsIGFlcyh4ID0geWVhciwgeSA9IG5hX3NhbGVzLCBjb2xvciA9IGFzLmZhY3RvcihjbHVzdGVyKSkpICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIHN0YXRfc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsYWNrIikgKw0KICAgIGxhYnModGl0bGUgPSBwYXN0ZSgiQ2x1c3RlciIsIGNsdXN0ZXIsICItIFRyZW5kIG9mIE5BIFNhbGVzIiksDQogICAgICAgICB4ID0gIlllYXIgR3JvdXAiLCB5ID0gIk5BIFNhbGVzIikgKw0KICAgIHRoZW1lX21pbmltYWwoKSArDQogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCn0NCg0KDQpmb3IgKGNsdXN0IGluIHVuaXF1ZSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEkY2x1c3RlcikpIHsNCiAgcGxvdCA8LSBjcmVhdGVfY2x1c3RlcmVkX25hX3RyZW5kX2dyYXBoKGNsdXN0KQ0KICBwcmludChwbG90KQ0KfQ0KDQoNCmNvbWJpbmVkX3Bsb3QgPC0gZ2dwbG90KHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSwgYWVzKHggPSB5ZWFyLCB5ID0gbmFfc2FsZXMsIGNvbG9yID0gYXMuZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBzdGF0X3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibGFjayIpICsNCiAgbGFicyh0aXRsZSA9ICJUcmVuZCBvZiBOQSBTYWxlcyBieSBDbHVzdGVyIiwNCiAgICAgICB4ID0gIlllYXIgR3JvdXAiLCB5ID0gIk5BIFNhbGVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArDQogIGZhY2V0X3dyYXAofmFzLmZhY3RvcihjbHVzdGVyKSwgc2NhbGVzID0gImZyZWVfeSIsIG5jb2wgPSAyKQ0KDQoNCnByaW50KGNvbWJpbmVkX3Bsb3QpDQoNCmBgYA0KDQpOQSBzYWxlcyBhcmUgaGFzIGhpZ2ggcHJlc2VuY2UgaW4gY2x1c3RlciAzIGZvbGxvd2VkIGJ5IGNsdXN0ZXIgMiB0aGFuDQoxLkhvd2V2ZXIgb25lIGNhbiBjb25jbHVkZSB0aGF0IHBlb3BsZSBpbiBOQSBwcmVmZXIgY29tcGxleCBnYW1lcyB0aGF0DQphcmUgaW4gY2x1c3RlciAzLg0KDQojIEVVIFNhbGVzDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQoNCg0KY3JlYXRlX2NsdXN0ZXJlZF9ldV90cmVuZF9ncmFwaCA8LSBmdW5jdGlvbihjbHVzdGVyKSB7DQogIGN1cnJlbnRfZGF0YSA8LSB2aWRlb19nYW1lX2dsb2JhbF9zYWxlczFbdmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxJGNsdXN0ZXIgPT0gY2x1c3RlciwgXQ0KICBnZ3Bsb3QoY3VycmVudF9kYXRhLCBhZXMoeCA9IHllYXIsIHkgPSBldV9zYWxlcywgY29sb3IgPSBhcy5mYWN0b3IoY2x1c3RlcikpKSArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICBzdGF0X3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibGFjayIpICsNCiAgICBsYWJzKHRpdGxlID0gcGFzdGUoIkNsdXN0ZXIiLCBjbHVzdGVyLCAiLSBUcmVuZCBvZiBFVSBTYWxlcyIpLA0KICAgICAgICAgeCA9ICJZZWFyIEdyb3VwIiwgeSA9ICJFVSBTYWxlcyIpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQp9DQoNCg0KZm9yIChjbHVzdCBpbiB1bmlxdWUodmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxJGNsdXN0ZXIpKSB7DQogIHBsb3QgPC0gY3JlYXRlX2NsdXN0ZXJlZF9ldV90cmVuZF9ncmFwaChjbHVzdCkNCiAgcHJpbnQocGxvdCkNCn0NCg0KDQpjb21iaW5lZF9wbG90X2V1IDwtIGdncGxvdCh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEsIGFlcyh4ID0geWVhciwgeSA9IGV1X3NhbGVzLCBjb2xvciA9IGFzLmZhY3RvcihjbHVzdGVyKSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgc3RhdF9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnModGl0bGUgPSAiVHJlbmQgb2YgRVUgU2FsZXMgYnkgQ2x1c3RlciIsDQogICAgICAgeCA9ICJZZWFyIEdyb3VwIiwgeSA9ICJFVSBTYWxlcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKw0KICBmYWNldF93cmFwKH5hcy5mYWN0b3IoY2x1c3RlciksIHNjYWxlcyA9ICJmcmVlX3kiLCBuY29sID0gMikNCg0KDQpwcmludChjb21iaW5lZF9wbG90X2V1KQ0KDQoNCmBgYA0KDQpFVSBzYWxlcyBhcmUgbW9yZSBkb21pbmFudCBpbiBjbHVzdGVyIDIgZm9sbG93ZWQgYnkgY2x1c3RlciAxIHRoZW4NCmxhc3RseSBjbHVzdGVyIDMgLlRoaXMgY29uY2x1ZGVzIHRoYXQgbW9zdGx5IEVVIHNhbGVzIGFyZSBpbiBjbHVzdGVyDQoyLlRoaXMgZW50YWlscyB1cyB0aGF0IHJlc2lkZW50cyBvZiB0aGUgRXVyb3BlYW4gVW5pb24gcHJlZmVyIHNpbXBsZXINCmdhbWVzIHdoaWNoIGFyZSBhbHNvIG5vdCBjb3N0bHkuDQoNCiMgSlAgU2FsZXMNCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQoNCmNyZWF0ZV9jbHVzdGVyZWRfanBfdHJlbmRfZ3JhcGggPC0gZnVuY3Rpb24oY2x1c3Rlcikgew0KICBjdXJyZW50X2RhdGEgPC0gdmlkZW9fZ2FtZV9nbG9iYWxfc2FsZXMxW3ZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSRjbHVzdGVyID09IGNsdXN0ZXIsIF0NCiAgZ2dwbG90KGN1cnJlbnRfZGF0YSwgYWVzKHggPSB5ZWFyLCB5ID0ganBfc2FsZXMsIGNvbG9yID0gYXMuZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgc3RhdF9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArDQogICAgbGFicyh0aXRsZSA9IHBhc3RlKCJDbHVzdGVyIiwgY2x1c3RlciwgIi0gVHJlbmQgb2YgSlAgU2FsZXMiKSwNCiAgICAgICAgIHggPSAiWWVhciBHcm91cCIsIHkgPSAiSlAgU2FsZXMiKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KfQ0KDQpmb3IgKGNsdXN0IGluIHVuaXF1ZSh2aWRlb19nYW1lX2dsb2JhbF9zYWxlczEkY2x1c3RlcikpIHsNCiAgcGxvdCA8LSBjcmVhdGVfY2x1c3RlcmVkX2pwX3RyZW5kX2dyYXBoKGNsdXN0KQ0KICBwcmludChwbG90KQ0KfQ0KDQoNCmNvbWJpbmVkX3Bsb3RfanAgPC0gZ2dwbG90KHZpZGVvX2dhbWVfZ2xvYmFsX3NhbGVzMSwgYWVzKHggPSB5ZWFyLCB5ID0ganBfc2FsZXMsIGNvbG9yID0gYXMuZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBzdGF0X3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibGFjayIpICsNCiAgbGFicyh0aXRsZSA9ICJUcmVuZCBvZiBKUCBTYWxlcyBieSBDbHVzdGVyIiwNCiAgICAgICB4ID0gIlllYXIgR3JvdXAiLCB5ID0gIkpQIFNhbGVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArDQogIGZhY2V0X3dyYXAofmFzLmZhY3RvcihjbHVzdGVyKSwgc2NhbGVzID0gImZyZWVfeSIsIG5jb2wgPSAyKQ0KDQoNCnByaW50KGNvbWJpbmVkX3Bsb3RfanApDQoNCg0KYGBgDQoNCkpQIHNhbGVzIGFyZSBtb3JlIGRvbWluYW50IGluIGNsdXN0ZXIgMiBmb2xsb3dlZCBieSBjbHVzdGVyIDEgdGhlbg0KbGFzdGx5IGNsdXN0ZXIgMyAuVGhpcyBjb25jbHVkZXMgdGhhdCBtb3N0bHkgSlAgc2FsZXMgYXJlIGluIGNsdXN0ZXINCjIuVGhpcyBjb25jbHVkZXMgdGhhdCBwZW9wbGUgaW4gSmFwYW4gcHJlZmVyIGdlbnJlcyBpbiBjbHVzdGVyIDIgYW5kDQpsZWFzdCBwcmVmZXIgdGhvc2UgaW4gY2x1c3RlciAxLk9wdGluZyBmb3IgbGVzcyBzb3BoaXN0aWNhdGVkIGdhbWVzIGFuZA0KY2hlYXAgYXMgd2VsbC4NCg0KIyBDT05DTFVTSU9ODQoNCkluIHN1bW1hcnksIHRoaXMgYW5hbHlzaXMgZ29lcyBpbnRvIGRldGVybWluaW5nIHRoZSBnbG9iYWwNCnZpZGVvIGdhbWUgc2FsZXMgYWNjb3JkaW5nIHRvIGdlbnJlcywgZW1wbG95aW5nIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcyB0byBpZGVudGlmeSBkaXN0aW5jdA0KcGF0dGVybnMgYW5kIHJlbGF0aW9uc2hpcHMuIFRoZSBjbHVzdGVyaW5nIGJhc2VkIG9uIGdsb2JhbCBzYWxlcyBhbmQNCmdlbnJlIHJldmVhbGVkIHRocmVlIGNsdXN0ZXJzLCBlYWNoIHdpdGggaXRzIHVuaXF1ZSBjaGFyYWN0ZXJpc3RpY3MuDQpDbHVzdGVyIDMgZW1lcmdlZCBhcyB0aGUgbGVhZGVyIGluIGdsb2JhbCBzYWxlcyBmb2xsb3dlZCBieSBjbHVzdGVyIDEgYW5kIGxhc3RseSBjbHVzdGVyIDMsDQouVGhlIGRlY2xpbmUgaW4gc2FsZXMgcG9zdC0yMDE2IHdhcyBvYnNlcnZlZCBhY3Jvc3MgYWxsIGNsdXN0ZXJzLg0KQmV5b25kIGdlbnJlLCBleHBsb3JhdGlvbiBpbnRvIHB1Ymxpc2hlcnMgYW5kIHBsYXRmb3JtcyB1bnZlaWxlZA0KaW50cmlndWluZyBpbnNpZ2h0cywgc2hvd2Nhc2luZyB0aGUgZG9taW5hbmNlIG9mIHNwZWNpZmljIHB1Ymxpc2hlcnMgYW5kDQpwbGF0Zm9ybXMgaW4gZWFjaCBjbHVzdGVyLiBUaGUgZXhhbWluYXRpb24gb2YgcmVnaW9uYWwgc2FsZXMgdHJlbmRzDQp1bmRlcnNjb3JlZCB0aGUgZGl2ZXJzZSBwcmVmZXJlbmNlcyBvZiBOb3J0aCBBbWVyaWNhbiwgRXVyb3BlYW4sIGFuZA0KSmFwYW5lc2UgYXVkaWVuY2VzLiBUaGlzIGFuYWx5c2lzIG5vdCBvbmx5IHNoZWRzIGxpZ2h0IG9uIHRoZSBtYXJrZXQNCmR5bmFtaWNzIG9mIHRoZSB2aWRlbyBnYW1lIGluZHVzdHJ5IGJ1dCBhbHNvIG9mZmVycyB2YWx1YWJsZSBpbnNpZ2h0cw0KZm9yIGRldmVsb3BlcnMsIHB1Ymxpc2hlcnMsIGFuZCBzdGFrZWhvbGRlcnMgdG8gdGFpbG9yIHRoZWlyIHN0cmF0ZWdpZXMNCmJhc2VkIG9uIGdlbnJlLCByZWdpb24sIGFuZCBwbGF0Zm9ybSBwcmVmZXJlbmNlcy4gQ29udGludWVkIGV4cGxvcmF0aW9uDQphbmQgYWRhcHRhdGlvbiB0byBlbWVyZ2luZyB0cmVuZHMgYXJlIGVzc2VudGlhbCBmb3IgbmF2aWdhdGluZyB0aGUNCmV2ZXItZXZvbHZpbmcgbGFuZHNjYXBlIG9mIHRoZSB2aWRlbyBnYW1lIG1hcmtldCBhbmQgdG8gZGV0ZXJtaW5lIHdoeSB0aGUgbWFya2V0IGlzIGRlY3JlYXNpbmcuDQo=