Preamble
Install
Libraries
#install.packages("remotes")
#remotes::install_github("DevPsyLab/petersenlab")
Load Libraries
library("tidyverse")
library("psych")
library("mice")
library("micemd")
library("miceadds")
library("mitml")
library("Amelia")
library("jomo")
library("parallel")
library("future")
library("furrr")
library("nlme")
library("MplusAutomation")
Data
data(Oxboys, package = "nlme")
Oxboys_addNA <- data.frame(Oxboys)
Oxboys_addNA <- Oxboys_addNA %>%
rename(id = Subject)
Oxboys_addNA$id <- as.integer(Oxboys_addNA$id)
Oxboys_addNA$Occasion <- as.integer(Oxboys_addNA$Occasion)
set.seed(52242)
Oxboys_addNA[sample(1:nrow(Oxboys_addNA), 25), "height"] <- NA
dataToImpute <- Oxboys_addNA
varsToImpute <- c("height")
Y <- varsToImpute
Types of
Missingness
Missing Completely at Random (MCAR)
the probability of being missing is the same for all cases
missingness is not related to variables in the model
(Conditionally) Missing at Random (MAR)
missingness is related to variables in the model, but once we
condition on the variables in the model, the missingness is
haphazard
the unobserved values do not play a role
Missing Not at Random (MNAR)
missingness is related to variables that are not in the model (i.e.,
for reasons that are unknown)
unobserved variables carry information about whether a case will
have missing data
Approaches to Handle
Missing Data
For MCAR/MAR
Missingness
Full Information Maximum Likelihood (FIML)
Multiple Imputation
Approaches to Multiple
Imputation
multiple imputation by joint modeling
assumes that the variables in follow a joint distribution, e.g.:
multivariate normal (i.e., multivariate normal imputation)
R
packages:
multiple imputation by chained equations
aka:
fully conditional specification multiple imputation
sequential regression multiple imputation
R
packages:
mice
predictive mean matching (?mice::mice.impute.pmm
) can
be useful to obtain bounded imputations for non-normally distributed
variables
Describe Missing
Data
describe(dataToImpute)
md.pattern(dataToImpute, rotate.names = TRUE)
id age Occasion height
209 1 1 1 1 0
25 1 1 1 0 1
0 0 0 25 25
Pre-Imputation
Setup
Specify Number of
Imputations
numImputations <- 5 # generally use 100 imputations; this example uses 5 for speed
Detect Cores
numCores <- parallel::detectCores() - 1
Multilevel Multiple
Imputation
Methods
mice
Types
Continuous
Outcomes
https://stefvanbuuren.name/fimd/sec-multioutcome.html#methods
(archived at https://perma.cc/8CDA-TS3K )
?mice::mice.impute.2l.norm
?mice::mice.impute.2l.pan
?mice::mice.impute.2l.lmer
?miceadds::mice.impute.2l.pmm
?miceadds::mice.impute.2l.contextual.pmm
?miceadds::mice.impute.2l.continuous
?micemd::mice.impute.2l.2stage.norm
?micemd::mice.impute.2l.2stage.pmm
?micemd::mice.impute.2l.glm.norm
?micemd::mice.impute.2l.jomo
Specifying the
Imputation Method
meth <- make.method(dataToImpute)
meth[1:length(meth)] <- ""
meth[Y] <- "2l.pmm" # specify the imputation method here; this can differ by outcome variable
Specifying the
Predictor Matrix
A predictor matrix is a matrix of values, where:
columns with non-zero values are predictors of the variable
specified in the given row
the diagonal of the predictor matrix should be zero because a
variable cannot predict itself
The values are:
NOT a predictor of the outcome: 0
cluster variable: -2
fixed effect of predictor: 1
fixed effect and random effect of predictor: 2
include cluster mean of predictor in addition to fixed effect of
predictor: 3
include cluster mean of predictor in addition to fixed effect and
random effect of predictor: 4
pred <- make.predictorMatrix(dataToImpute)
pred[1:nrow(pred), 1:ncol(pred)] <- 0
pred[Y, "id"] <- (-2) # cluster variable
pred[Y, "Occasion"] <- 1 # fixed effect predictor
pred[Y, "age"] <- 2 # random effect predictor
pred[Y, Y] <- 1 # fixed effect predictor
diag(pred) <- 0
pred
id age height Occasion
id 0 0 0 0
age 0 0 0 0
height -2 2 0 1
Occasion 0 0 0 0
Syntax
mi_mice <- mice(
as.data.frame(dataToImpute),
method = meth,
predictorMatrix = pred,
m = numImputations,
maxit = 5, # generally use 100 maximum iterations; this example uses 5 for speed
seed = 52242)
iter imp variable
1 1 height
1 2 height
1 3 height
1 4 height
1 5 height
2 1 height
2 2 height
2 3 height
2 4 height
2 5 height
3 1 height
3 2 height
3 3 height
3 4 height
3 5 height
4 1 height
4 2 height
4 3 height
4 4 height
4 5 height
5 1 height
5 2 height
5 3 height
5 4 height
5 5 height
jomo
level1Vars <- c("height")
level2Vars <- c("v3","v4")
clusterVars <- c("id")
fullyObservedCovariates <- c("age","Occasion")
set.seed(52242)
mi_jomo <- jomo(
Y = data.frame(dataToImpute[, level1Vars]),
#Y2 = data.frame(dataToImpute[, level2Vars]),
X = data.frame(dataToImpute[, fullyObservedCovariates]),
clus = data.frame(dataToImpute[, clusterVars]),
nimp = numImputations,
meth = "random"
)
Clustered data, using functions for two-level imputation.
Found 1 continuous outcomes and no categorical. Using function jomo1ranconhr with random cluster-specific covariance matrices.
..................................................
..................................................
First imputation registered.
..................................................
..................................................
Imputation number 2 registered
..................................................
..................................................
Imputation number 3 registered
..................................................
..................................................
Imputation number 4 registered
..................................................
..................................................
Imputation number 5 registered
The posterior mean of the fixed effects estimates is:
age Occasion
dataToImpute...level1Vars. -113.0757 30.04838
The posterior mean of the random effects estimates is:
dataToImpute...level1Vars..Z1
1 -16.20902733
2 -8.65212774
3 -6.73361991
4 -8.48037088
5 -4.01165035
6 -0.82193207
7 -1.06273621
8 -4.25225948
9 -0.08327231
10 -3.34891121
11 2.57532592
12 3.66401069
13 0.48966344
14 3.47497904
15 3.90412653
16 3.19938056
17 2.40548040
18 3.27865186
19 6.09683973
20 5.55557617
21 6.75778040
22 8.90157501
23 8.52601382
24 10.62696736
25 16.84089878
26 17.23746838
The posterior mean of the level 1 covariance matrices is:
dataToImpute...level1Vars.
dataToImpute...level1Vars..1 20.55420
dataToImpute...level1Vars..2 21.18287
dataToImpute...level1Vars..3 15.70618
dataToImpute...level1Vars..4 25.93674
dataToImpute...level1Vars..5 20.32710
dataToImpute...level1Vars..6 25.51296
dataToImpute...level1Vars..7 23.47148
dataToImpute...level1Vars..8 22.76608
dataToImpute...level1Vars..9 24.07566
dataToImpute...level1Vars..10 26.87790
dataToImpute...level1Vars..11 20.55911
dataToImpute...level1Vars..12 21.56772
dataToImpute...level1Vars..13 24.82955
dataToImpute...level1Vars..14 17.19218
dataToImpute...level1Vars..15 21.30695
dataToImpute...level1Vars..16 25.49398
dataToImpute...level1Vars..17 21.73770
dataToImpute...level1Vars..18 20.28903
dataToImpute...level1Vars..19 22.74839
dataToImpute...level1Vars..20 24.93717
dataToImpute...level1Vars..21 19.27750
dataToImpute...level1Vars..22 17.73270
dataToImpute...level1Vars..23 21.87620
dataToImpute...level1Vars..24 20.96218
dataToImpute...level1Vars..25 18.61259
dataToImpute...level1Vars..26 20.21037
The posterior mean of the level 2 covariance matrix is:
dataToImpute...level1Vars..Z1
dataToImpute...level1Vars.*Z1 66.59035
Amelia
!
in the console output indicates that the current
estimated complete data covariance matrix is not invertible
*
in the console output indicates that the likelihood
has not monotonically increased in that step
boundVars <- c("height")
boundCols <- which(names(dataToImpute) %in% boundVars)
boundLower <- 100
boundUpper <- 200
varBounds <- cbind(boundCols, boundLower, boundUpper)
set.seed(52242)
mi_amelia <- amelia(
dataToImpute,
m = numImputations,
ts = "age",
cs = "id",
polytime = 1,
intercs = TRUE,
#ords = ordinalVars,
#bounds = varBounds,
parallel = "snow",
#ncpus = numCores,
empri = .01*nrow(dataToImpute)) # ridge prior for numerical stability
-- Imputation 1 --
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21
-- Imputation 2 --
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
-- Imputation 3 --
1 2 3 4 5 6 7 8
-- Imputation 4 --
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
121 122 123
-- Imputation 5 --
1 2 3 4 5 6 7 8 9 10 11 12
Mplus
Save
Mplus
Data File
Save R
object as Mplus
data file:
prepareMplusData(dataToImpute, file.path("dataToImpute.dat"))
TITLE: Your title goes here
DATA: FILE = "dataToImpute.dat";
VARIABLE:
NAMES = id age height Occasion;
MISSING=.;
Mplus
Syntax for Multilevel Imputation
Mplus
syntax for multilevel imputation:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!! MPLUS SYNTAX LINES CANNOT EXCEED 90 CHARACTERS;
!!!!! VARIABLE NAMES AND PARAMETER LABELS CANNOT EXCEED 8 CHARACTERS EACH;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
TITLE: Model Title
DATA: FILE = "dataToImpute.dat";
VARIABLE:
NAMES = id age height Occasion;
MISSING = .;
USEVARIABLES ARE age height Occasion;
!CATEGORICAL ARE INSERT_NAMES_OF_CATEGORICAL_VARIABLES_HERE;
CLUSTER = id;
ANALYSIS:
TYPE = twolevel basic;
bseed = 52242;
PROCESSORS = 2;
DATA IMPUTATION:
IMPUTE = age(0-100) height Occasion; !put ' (c)' after categorical vars
NDATASETS = 100;
SAVE = imp*.dat
Putting a range of values after a variable (e.g., 0-100
)
sets the lower and upper bounds of the imputed values. This would save a
implist.dat
file that can be used to run the model on the
multiply imputed data, as shown here .
Imputation
Diagnostics
Logged Events
mi_mice$loggedEvents
NULL
Trace Plots
On convergence, the streams of the trace plots should intermingle and
be free of any trend (at the later iterations). Convergence is diagnosed
when the variance between different sequences is no larger than the
variance within each individual sequence.
plot(mi_mice, c("height"))
Density Plots
densityplot(mi_mice)
densityplot(mi_mice, ~ height)
Strip Plots
stripplot(mi_mice)
stripplot(mi_mice, height ~ .imp)
Post-Processing
Modify/Create New
Variables
mice
mi_mice_long <- complete(
mi_mice,
action = "long",
include = TRUE)
mi_mice_long$newVar <- mi_mice_long$age * mi_mice_long$height
jomo
mi_jomo <- mi_jomo %>%
rename(height = dataToImpute...level1Vars.)
mi_jomo$newVar <- mi_jomo$age * mi_jomo$height
Amelia
for(i in 1:length(mi_amelia$imputations)){
mi_amelia$imputations[[i]]$newVar <- mi_amelia$imputations[[i]]$age * mi_amelia$imputations[[i]]$height
}
Convert to
mids
object
mice
mi_mice_mids <- as.mids(mi_mice_long)
jomo
mi_jomo_mids <- miceadds::jomo2mids(mi_jomo)
Warning: Number of logged events: 1
Amelia
mi_amelia_mids <- miceadds::datlist2mids(mi_amelia$imputations)
Export for
Mplus
mids2mplus(mi_mice_mids, path = file.path("InsertFilePathHere", fsep = ""))
mids2mplus(mi_jomo_mids, path = file.path("InsertFilePathHere", fsep = ""))
mids2mplus(mi_amelia_mids, path = file.path("InsertFilePathHere", fsep = ""))
LS0tCnRpdGxlOiAiTXVsdGlwbGUgSW1wdXRhdGlvbiIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgZXJyb3IgPSBUUlVFLAogIGNvbW1lbnQgPSAiIiwKICBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IikKYGBgCgojIFByZWFtYmxlCgojIyBJbnN0YWxsIExpYnJhcmllcwoKYGBge3J9CiNpbnN0YWxsLnBhY2thZ2VzKCJyZW1vdGVzIikKI3JlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJEZXZQc3lMYWIvcGV0ZXJzZW5sYWIiKQpgYGAKCiMjIExvYWQgTGlicmFyaWVzCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmxpYnJhcnkoInRpZHl2ZXJzZSIpCmxpYnJhcnkoInBzeWNoIikKbGlicmFyeSgibWljZSIpCmxpYnJhcnkoIm1pY2VtZCIpCmxpYnJhcnkoIm1pY2VhZGRzIikKbGlicmFyeSgibWl0bWwiKQpsaWJyYXJ5KCJBbWVsaWEiKQpsaWJyYXJ5KCJqb21vIikKbGlicmFyeSgicGFyYWxsZWwiKQpsaWJyYXJ5KCJmdXR1cmUiKQpsaWJyYXJ5KCJmdXJyciIpCmxpYnJhcnkoIm5sbWUiKQpsaWJyYXJ5KCJNcGx1c0F1dG9tYXRpb24iKQpgYGAKCiMjIERhdGEKCmBgYHtyfQpkYXRhKE94Ym95cywgcGFja2FnZSA9ICJubG1lIikKCk94Ym95c19hZGROQSA8LSBkYXRhLmZyYW1lKE94Ym95cykKT3hib3lzX2FkZE5BIDwtIE94Ym95c19hZGROQSAlPiUgCiAgcmVuYW1lKGlkID0gU3ViamVjdCkKCk94Ym95c19hZGROQSRpZCA8LSBhcy5pbnRlZ2VyKE94Ym95c19hZGROQSRpZCkKT3hib3lzX2FkZE5BJE9jY2FzaW9uIDwtIGFzLmludGVnZXIoT3hib3lzX2FkZE5BJE9jY2FzaW9uKQoKc2V0LnNlZWQoNTIyNDIpCk94Ym95c19hZGROQVtzYW1wbGUoMTpucm93KE94Ym95c19hZGROQSksIDI1KSwgImhlaWdodCJdIDwtIE5BCgpkYXRhVG9JbXB1dGUgPC0gT3hib3lzX2FkZE5BCnZhcnNUb0ltcHV0ZSA8LSBjKCJoZWlnaHQiKQpZIDwtIHZhcnNUb0ltcHV0ZQpgYGAKCiMgVHlwZXMgb2YgTWlzc2luZ25lc3MKCjEuIE1pc3NpbmcgQ29tcGxldGVseSBhdCBSYW5kb20gKE1DQVIpCiAgICAtIHRoZSBwcm9iYWJpbGl0eSBvZiBiZWluZyBtaXNzaW5nIGlzIHRoZSBzYW1lIGZvciBhbGwgY2FzZXMKICAgIC0gbWlzc2luZ25lc3MgaXMgbm90IHJlbGF0ZWQgdG8gdmFyaWFibGVzIGluIHRoZSBtb2RlbAoxLiAoQ29uZGl0aW9uYWxseSkgTWlzc2luZyBhdCBSYW5kb20gKE1BUikKICAgIC0gbWlzc2luZ25lc3MgaXMgcmVsYXRlZCB0byB2YXJpYWJsZXMgaW4gdGhlIG1vZGVsLCBidXQgb25jZSB3ZSBjb25kaXRpb24gb24gdGhlIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwsIHRoZSBtaXNzaW5nbmVzcyBpcyBoYXBoYXphcmQKICAgIC0gdGhlIHVub2JzZXJ2ZWQgdmFsdWVzIGRvIG5vdCBwbGF5IGEgcm9sZQoxLiBNaXNzaW5nIE5vdCBhdCBSYW5kb20gKE1OQVIpCiAgICAtIG1pc3NpbmduZXNzIGlzIHJlbGF0ZWQgdG8gdmFyaWFibGVzIHRoYXQgYXJlIG5vdCBpbiB0aGUgbW9kZWwgKGkuZS4sIGZvciByZWFzb25zIHRoYXQgYXJlIHVua25vd24pCiAgICAtIHVub2JzZXJ2ZWQgdmFyaWFibGVzIGNhcnJ5IGluZm9ybWF0aW9uIGFib3V0IHdoZXRoZXIgYSBjYXNlIHdpbGwgaGF2ZSBtaXNzaW5nIGRhdGEKCiMgQXBwcm9hY2hlcyB0byBIYW5kbGUgTWlzc2luZyBEYXRhCgojIyBGb3IgTUNBUi9NQVIgTWlzc2luZ25lc3MKCjEuIEZ1bGwgSW5mb3JtYXRpb24gTWF4aW11bSBMaWtlbGlob29kIChGSU1MKQoxLiBNdWx0aXBsZSBJbXB1dGF0aW9uCgojIyBGb3IgTU5BUiBNaXNzaW5nbmVzcwoKaHR0cHM6Ly9zdGVmdmFuYnV1cmVuLm5hbWUvZmltZC9zZWMtbm9uaWdub3JhYmxlLmh0bWwgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvTjdXVy1IRFpGKQoKaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL21pc3NpbmdIRS92aWduZXR0ZXMvRml0dGluZ19NTkFSX21vZGVsc19pbl9taXNzaW5nSEUuaHRtbCAoYXJjaGl2ZWQgYXQgaHR0cHM6Ly9wZXJtYS5jYy85WDI1LTVEOEcpCgoxLiBmaW5kIG1vcmUgZGF0YSBhYm91dCB0aGUgY2F1c2VzIGZvciB0aGUgbWlzc2luZ25lc3MKMS4gc2Vuc2l0aXZpdHkgYW5hbHlzZXMgKHdoYXQtaWYgYW5hbHlzZXMpIHRvIHNlZSBob3cgc2Vuc2l0aXZlIHRoZSByZXN1bHRzIGFyZSB1bmRlciB2YXJpb3VzIHNjZW5hcmlvcwogICAgLSBodHRwczovL3N0ZWZ2YW5idXVyZW4ubmFtZS9maW1kL3NlYy1NQ0FSLmh0bWwgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvOUtNOS0zTlgzKQoxLiBzZWxlY3Rpb24gbW9kZWxzCiAgICAtIHNpbXVsdGFuZW91c2x5IGVzdGltYXRlIHRoZSBmb2NhbCBtb2RlbCBhbmQgYSBtaXNzaW5nbmVzcyBtb2RlbCwgd2hlcmUgdGhlIG1pc3NpbmduZXNzIG1vZGVsIGhhcyB0aGUgbWlzc2luZyBkYXRhIGluZGljYXRvciBhcyBhIGRlcGVuZGVudCB2YXJpYWJsZSwgYXMgcHJlZGljdGVkIGJ5IHRoZSBvcmlnaW5hbCBkZXBlbmRlbnQgdmFyaWFibGUsIHRoZSBvcmlnaW5hbCBwcmVkaWN0b3JzLCBhbmQgYW55IGFkZGl0aW9uYWwgY292YXJpYXRlcyBldGMuCiAgICAtIGlmIHRoZSBtaXNzaW5nbmVzcyBtb2RlbCBpcyBhcHByb3hpbWF0ZWx5IGNvcnJlY3QsIHRoZSBmb2NhbCBtb2RlbCBhZGp1c3RzIGluIHdheSB0aGF0IHJlbW92ZXMgbm9ucmVzcG9uc2UgYmlhcwogICAgLSBzaW1pbGFyIHRvIGEgbWVkaWF0aW9uIHByb2Nlc3MKICAgICAgICAtIFgg4oaSIFkKICAgICAgICAtIFkg4oaSIG1pc3NpbmduZXNzCiAgICAgICAgLSBYIOKGkiBtaXNzaW5nbmVzcwogICAgLSBodHRwczovL3F1YW50aXR1ZGVwb2Qub3JnL3M0ZTA4LWNyYWlnLWVuZGVycy8gKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvRlk5TC1MM0Y3KQoxLiBwYXR0ZXJuLW1peHR1cmUgbW9kZWxzCiAgICAtIG1pc3NpbmcgZGF0YSBpbmRpY2F0b3IgaXMgYSBwcmVkaWN0b3IgdmFyaWFibGUKICAgIC0gc2ltaWxhciB0byBhIG1vZGVyYXRpb24gcHJvY2Vzczsgc3ViZ3JvdXBzIG9mIGNhc2VzIGhhdmUgZGlmZmVyZW50IHBhcmFtZXRlciBlc3RpbWF0ZXMKCiMgQXBwcm9hY2hlcyB0byBNdWx0aXBsZSBJbXB1dGF0aW9uCgotIG11bHRpcGxlIGltcHV0YXRpb24gYnkgam9pbnQgbW9kZWxpbmcKICAgIC0gYXNzdW1lcyB0aGF0IHRoZSB2YXJpYWJsZXMgaW4gZm9sbG93IGEgam9pbnQgZGlzdHJpYnV0aW9uLCBlLmcuOgogICAgICAgIC0gbXVsdGl2YXJpYXRlIG5vcm1hbCAoaS5lLiwgbXVsdGl2YXJpYXRlIG5vcm1hbCBpbXB1dGF0aW9uKQogICAgLSBgUmAgcGFja2FnZXM6CiAgICAgICAgLSBgam9tb2AKICAgICAgICAtIGBwYW5gCiAgICAgICAgLSBgQW1lbGlhYAogICAgICAgIC0gYE1wbHVzYAotIG11bHRpcGxlIGltcHV0YXRpb24gYnkgY2hhaW5lZCBlcXVhdGlvbnMKICAgIC0gYWthOgogICAgICAgIC0gZnVsbHkgY29uZGl0aW9uYWwgc3BlY2lmaWNhdGlvbiBtdWx0aXBsZSBpbXB1dGF0aW9uCiAgICAgICAgLSBzZXF1ZW50aWFsIHJlZ3Jlc3Npb24gbXVsdGlwbGUgaW1wdXRhdGlvbgogICAgLSBgUmAgcGFja2FnZXM6CiAgICAgICAgLSBgbWljZWAKICAgICAgICAgICAgLSBwcmVkaWN0aXZlIG1lYW4gbWF0Y2hpbmcgKGA/bWljZTo6bWljZS5pbXB1dGUucG1tYCkgY2FuIGJlIHVzZWZ1bCB0byBvYnRhaW4gYm91bmRlZCBpbXB1dGF0aW9ucyBmb3Igbm9uLW5vcm1hbGx5IGRpc3RyaWJ1dGVkIHZhcmlhYmxlcwoKIyBEZXNjcmliZSBNaXNzaW5nIERhdGEKCmBgYHtyfQpkZXNjcmliZShkYXRhVG9JbXB1dGUpCm1kLnBhdHRlcm4oZGF0YVRvSW1wdXRlLCByb3RhdGUubmFtZXMgPSBUUlVFKQpgYGAKCiMgUHJlLUltcHV0YXRpb24gU2V0dXAKCiMjIFNwZWNpZnkgTnVtYmVyIG9mIEltcHV0YXRpb25zCgpgYGB7cn0KbnVtSW1wdXRhdGlvbnMgPC0gNSAjIGdlbmVyYWxseSB1c2UgMTAwIGltcHV0YXRpb25zOyB0aGlzIGV4YW1wbGUgdXNlcyA1IGZvciBzcGVlZApgYGAKCiMjIERldGVjdCBDb3JlcwoKYGBge3J9Cm51bUNvcmVzIDwtIHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpIC0gMQpgYGAKCiMgTXVsdGlsZXZlbCBNdWx0aXBsZSBJbXB1dGF0aW9uCgojIyBUaHJlZS1MZXZlbCBhbmQgQ3Jvc3MtQ2xhc3NpZmllZCBEYXRhCgpodHRwczovL3NpbW9uZ3J1bmQxLmdpdGh1Yi5pby9wb3N0cy9tdWx0aXBsZS1pbXB1dGF0aW9uLWZvci10aHJlZS1sZXZlbC1hbmQtY3Jvc3MtY2xhc3NpZmllZC1kYXRhLyAoYXJjaGl2ZWQgYXQgaHR0cHM6Ly9wZXJtYS5jYy9ONFBQLUEzVjYpCgojIyBNZXRob2RzCgojIyMgYG1pY2VgIHsjbWljZX0KCiMjIyMgVHlwZXMKCiMjIyMjIENvbnRpbnVvdXMgT3V0Y29tZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQvc2VjLW11bHRpb3V0Y29tZS5odG1sI21ldGhvZHMgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvOENEQS1UUzNLKQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KP21pY2U6Om1pY2UuaW1wdXRlLjJsLm5vcm0KP21pY2U6Om1pY2UuaW1wdXRlLjJsLnBhbgo/bWljZTo6bWljZS5pbXB1dGUuMmwubG1lcgo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLnBtbQo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLmNvbnRleHR1YWwucG1tCj9taWNlYWRkczo6bWljZS5pbXB1dGUuMmwuY29udGludW91cwo/bWljZW1kOjptaWNlLmltcHV0ZS4ybC4yc3RhZ2Uubm9ybQo/bWljZW1kOjptaWNlLmltcHV0ZS4ybC4yc3RhZ2UucG1tCj9taWNlbWQ6Om1pY2UuaW1wdXRlLjJsLmdsbS5ub3JtCj9taWNlbWQ6Om1pY2UuaW1wdXRlLjJsLmpvbW8KYGBgCgojIyMjIyBCaW5hcnkgT3V0Y29tZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQvc2VjLWNhdG91dGNvbWUuaHRtbCNtZXRob2RzLTEgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvNVFIRi1ZUlA2KQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KP21pY2U6Om1pY2UuaW1wdXRlLjJsLmJpbgo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLmJpbmFyeQo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLnBtbQo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLmNvbnRleHR1YWwucG1tCj9taWNlbWQ6Om1pY2UuaW1wdXRlLjJsLjJzdGFnZS5iaW4KP21pY2VtZDo6bWljZS5pbXB1dGUuMmwuZ2xtLmJpbgpgYGAKCiMjIyMjIE9yZGluYWwgT3V0Y29tZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQvc2VjLW11bHRpb3V0Y29tZS5odG1sI21ldGhvZHMgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvOENEQS1UUzNLKQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KP21pY2VhZGRzOjptaWNlLmltcHV0ZS4ybC5wbW0KP21pY2VhZGRzOjptaWNlLmltcHV0ZS4ybC5jb250ZXh0dWFsLnBtbQo/bWljZW1kOjptaWNlLmltcHV0ZS4ybC4yc3RhZ2UucG1tCmBgYAoKIyMjIyMgQ291bnQgT3V0Y29tZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQvc2VjLWNhdG91dGNvbWUuaHRtbCNtZXRob2RzLTEgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvNVFIRi1ZUlA2KQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KP21pY2VtZDo6bWljZS5pbXB1dGUuMmwuMnN0YWdlLnBvaXMKP21pY2VtZDo6bWljZS5pbXB1dGUuMmwuZ2xtLnBvaXMKP2NvdW50aW1wOjptaWNlLmltcHV0ZS4ybC5wb2lzc29uCj9jb3VudGltcDo6bWljZS5pbXB1dGUuMmwubmIyCj9jb3VudGltcDo6bWljZS5pbXB1dGUuMmwuemlobmIKYGBgCgojIyMjIFNwZWNpZnlpbmcgdGhlIEltcHV0YXRpb24gTWV0aG9kCgpgYGB7cn0KbWV0aCA8LSBtYWtlLm1ldGhvZChkYXRhVG9JbXB1dGUpCm1ldGhbMTpsZW5ndGgobWV0aCldIDwtICIiCm1ldGhbWV0gPC0gIjJsLnBtbSIgIyBzcGVjaWZ5IHRoZSBpbXB1dGF0aW9uIG1ldGhvZCBoZXJlOyB0aGlzIGNhbiBkaWZmZXIgYnkgb3V0Y29tZSB2YXJpYWJsZQpgYGAKCiMjIyMgU3BlY2lmeWluZyB0aGUgUHJlZGljdG9yIE1hdHJpeAoKQSBwcmVkaWN0b3IgbWF0cml4IGlzIGEgbWF0cml4IG9mIHZhbHVlcywgd2hlcmU6CgotIGNvbHVtbnMgd2l0aCBub24temVybyB2YWx1ZXMgYXJlIHByZWRpY3RvcnMgb2YgdGhlIHZhcmlhYmxlIHNwZWNpZmllZCBpbiB0aGUgZ2l2ZW4gcm93Ci0gdGhlIGRpYWdvbmFsIG9mIHRoZSBwcmVkaWN0b3IgbWF0cml4IHNob3VsZCBiZSB6ZXJvIGJlY2F1c2UgYSB2YXJpYWJsZSBjYW5ub3QgcHJlZGljdCBpdHNlbGYKClRoZSB2YWx1ZXMgYXJlOgoKLSBOT1QgYSBwcmVkaWN0b3Igb2YgdGhlIG91dGNvbWU6IGAwYAotIGNsdXN0ZXIgdmFyaWFibGU6IGAtMmAKLSBmaXhlZCBlZmZlY3Qgb2YgcHJlZGljdG9yOiBgMWAKLSBmaXhlZCBlZmZlY3QgYW5kIHJhbmRvbSBlZmZlY3Qgb2YgcHJlZGljdG9yOiBgMmAKLSBpbmNsdWRlIGNsdXN0ZXIgbWVhbiBvZiBwcmVkaWN0b3IgaW4gYWRkaXRpb24gdG8gZml4ZWQgZWZmZWN0IG9mIHByZWRpY3RvcjogYDNgCi0gaW5jbHVkZSBjbHVzdGVyIG1lYW4gb2YgcHJlZGljdG9yIGluIGFkZGl0aW9uIHRvIGZpeGVkIGVmZmVjdCBhbmQgcmFuZG9tIGVmZmVjdCBvZiBwcmVkaWN0b3I6IGA0YAoKYGBge3J9CnByZWQgPC0gbWFrZS5wcmVkaWN0b3JNYXRyaXgoZGF0YVRvSW1wdXRlKQpwcmVkWzE6bnJvdyhwcmVkKSwgMTpuY29sKHByZWQpXSA8LSAwCnByZWRbWSwgImlkIl0gPC0gKC0yKSAjIGNsdXN0ZXIgdmFyaWFibGUKcHJlZFtZLCAiT2NjYXNpb24iXSA8LSAxICMgZml4ZWQgZWZmZWN0IHByZWRpY3RvcgpwcmVkW1ksICJhZ2UiXSA8LSAyICMgcmFuZG9tIGVmZmVjdCBwcmVkaWN0b3IKcHJlZFtZLCBZXSA8LSAxICMgZml4ZWQgZWZmZWN0IHByZWRpY3RvcgoKZGlhZyhwcmVkKSA8LSAwCnByZWQKYGBgCgojIyMjIFN5bnRheAoKYGBge3J9Cm1pX21pY2UgPC0gbWljZSgKICBhcy5kYXRhLmZyYW1lKGRhdGFUb0ltcHV0ZSksCiAgbWV0aG9kID0gbWV0aCwKICBwcmVkaWN0b3JNYXRyaXggPSBwcmVkLAogIG0gPSBudW1JbXB1dGF0aW9ucywKICBtYXhpdCA9IDUsICMgZ2VuZXJhbGx5IHVzZSAxMDAgbWF4aW11bSBpdGVyYXRpb25zOyB0aGlzIGV4YW1wbGUgdXNlcyA1IGZvciBzcGVlZAogIHNlZWQgPSA1MjI0MikKYGBgCgojIyMgYGpvbW9gIHsjam9tb30KCmBgYHtyfQpsZXZlbDFWYXJzIDwtIGMoImhlaWdodCIpCmxldmVsMlZhcnMgPC0gYygidjMiLCJ2NCIpCmNsdXN0ZXJWYXJzIDwtIGMoImlkIikKZnVsbHlPYnNlcnZlZENvdmFyaWF0ZXMgPC0gYygiYWdlIiwiT2NjYXNpb24iKQoKc2V0LnNlZWQoNTIyNDIpCgptaV9qb21vIDwtIGpvbW8oCiAgWSA9IGRhdGEuZnJhbWUoZGF0YVRvSW1wdXRlWywgbGV2ZWwxVmFyc10pLAogICNZMiA9IGRhdGEuZnJhbWUoZGF0YVRvSW1wdXRlWywgbGV2ZWwyVmFyc10pLAogIFggPSBkYXRhLmZyYW1lKGRhdGFUb0ltcHV0ZVssIGZ1bGx5T2JzZXJ2ZWRDb3ZhcmlhdGVzXSksCiAgY2x1cyA9IGRhdGEuZnJhbWUoZGF0YVRvSW1wdXRlWywgY2x1c3RlclZhcnNdKSwKICBuaW1wID0gbnVtSW1wdXRhdGlvbnMsCiAgbWV0aCA9ICJyYW5kb20iCikKYGBgCgojIyMgYEFtZWxpYWAgeyNhbWVsaWF9CgotIGAhYCBpbiB0aGUgY29uc29sZSBvdXRwdXQgaW5kaWNhdGVzIHRoYXQgdGhlIGN1cnJlbnQgZXN0aW1hdGVkIGNvbXBsZXRlIGRhdGEgY292YXJpYW5jZSBtYXRyaXggaXMgbm90IGludmVydGlibGUKLSBgKmAgaW4gdGhlIGNvbnNvbGUgb3V0cHV0IGluZGljYXRlcyB0aGF0IHRoZSBsaWtlbGlob29kIGhhcyBub3QgbW9ub3RvbmljYWxseSBpbmNyZWFzZWQgaW4gdGhhdCBzdGVwCgpgYGB7cn0KYm91bmRWYXJzIDwtIGMoImhlaWdodCIpCmJvdW5kQ29scyA8LSB3aGljaChuYW1lcyhkYXRhVG9JbXB1dGUpICVpbiUgYm91bmRWYXJzKQpib3VuZExvd2VyIDwtIDEwMApib3VuZFVwcGVyIDwtIDIwMAoKdmFyQm91bmRzIDwtIGNiaW5kKGJvdW5kQ29scywgYm91bmRMb3dlciwgYm91bmRVcHBlcikKCnNldC5zZWVkKDUyMjQyKQoKbWlfYW1lbGlhIDwtIGFtZWxpYSgKICBkYXRhVG9JbXB1dGUsCiAgbSA9IG51bUltcHV0YXRpb25zLAogIHRzID0gImFnZSIsCiAgY3MgPSAiaWQiLAogIHBvbHl0aW1lID0gMSwKICBpbnRlcmNzID0gVFJVRSwKICAjb3JkcyA9IG9yZGluYWxWYXJzLAogICNib3VuZHMgPSB2YXJCb3VuZHMsCiAgcGFyYWxsZWwgPSAic25vdyIsCiAgI25jcHVzID0gbnVtQ29yZXMsCiAgZW1wcmkgPSAuMDEqbnJvdyhkYXRhVG9JbXB1dGUpKSAjIHJpZGdlIHByaW9yIGZvciBudW1lcmljYWwgc3RhYmlsaXR5CmBgYAoKIyMjIGBNcGx1c2AgeyNtcGx1c30KCiMjIyMgU2F2ZSBgTXBsdXNgIERhdGEgRmlsZQoKU2F2ZSBgUmAgb2JqZWN0IGFzIGBNcGx1c2AgZGF0YSBmaWxlOgoKYGBge3J9CnByZXBhcmVNcGx1c0RhdGEoZGF0YVRvSW1wdXRlLCBmaWxlLnBhdGgoImRhdGFUb0ltcHV0ZS5kYXQiKSkKYGBgCgojIyMjIGBNcGx1c2AgU3ludGF4IGZvciBNdWx0aWxldmVsIEltcHV0YXRpb24KCmBNcGx1c2Agc3ludGF4IGZvciBtdWx0aWxldmVsIGltcHV0YXRpb246CgpgYGBNcGx1cwohISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhCiEhISEhICBNUExVUyBTWU5UQVggTElORVMgQ0FOTk9UIEVYQ0VFRCA5MCBDSEFSQUNURVJTOwohISEhISAgVkFSSUFCTEUgTkFNRVMgQU5EIFBBUkFNRVRFUiBMQUJFTFMgQ0FOTk9UIEVYQ0VFRCA4IENIQVJBQ1RFUlMgRUFDSDsKISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhIQoKVElUTEU6IE1vZGVsIFRpdGxlCgpEQVRBOiBGSUxFID0gImRhdGFUb0ltcHV0ZS5kYXQiOwoKVkFSSUFCTEU6CiAgTkFNRVMgPSBpZCBhZ2UgaGVpZ2h0IE9jY2FzaW9uOwogIE1JU1NJTkcgPSAuOwogIFVTRVZBUklBQkxFUyBBUkUgYWdlIGhlaWdodCBPY2Nhc2lvbjsKICAhQ0FURUdPUklDQUwgQVJFIElOU0VSVF9OQU1FU19PRl9DQVRFR09SSUNBTF9WQVJJQUJMRVNfSEVSRTsKICBDTFVTVEVSID0gaWQ7CgpBTkFMWVNJUzoKICBUWVBFID0gdHdvbGV2ZWwgYmFzaWM7CiAgYnNlZWQgPSA1MjI0MjsKICBQUk9DRVNTT1JTID0gMjsKICAgIApEQVRBIElNUFVUQVRJT046CiAgSU1QVVRFID0gYWdlKDAtMTAwKSBoZWlnaHQgT2NjYXNpb247ICFwdXQgJyAoYyknIGFmdGVyIGNhdGVnb3JpY2FsIHZhcnMKICBOREFUQVNFVFMgPSAxMDA7CiAgU0FWRSA9IGltcCouZGF0CmBgYAoKUHV0dGluZyBhIHJhbmdlIG9mIHZhbHVlcyBhZnRlciBhIHZhcmlhYmxlIChlLmcuLCBgMC0xMDBgKSBzZXRzIHRoZSBsb3dlciBhbmQgdXBwZXIgYm91bmRzIG9mIHRoZSBpbXB1dGVkIHZhbHVlcy4KVGhpcyB3b3VsZCBzYXZlIGEgYGltcGxpc3QuZGF0YCBmaWxlIHRoYXQgY2FuIGJlIHVzZWQgdG8gcnVuIHRoZSBtb2RlbCBvbiB0aGUgbXVsdGlwbHkgaW1wdXRlZCBkYXRhLCBhcyBzaG93biBbaGVyZV0obXBsdXMuaHRtbCNtdWx0aXBsZUltcHV0YXRpb24pLgoKIyBQYXJhbGxlbCBQcm9jZXNzaW5nCgpodHRwczovL3d3dy5nZXJrb3ZpbmsuY29tL21pY2VWaWduZXR0ZXMvZnV0dXJlbWljZS9WaWduZXR0ZV9mdXR1cmVtaWNlLmh0bWwgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvNFNORS1SQ1NSKQoKYGBge3J9Cm1pX3BhcmFsbGVsX21pY2UgPC0gZnV0dXJlbWljZSgKICBkYXRhVG9JbXB1dGUsCiAgbWV0aG9kID0gbWV0aCwKICBwcmVkaWN0b3JNYXRyaXggPSBwcmVkLAogIG0gPSBudW1JbXB1dGF0aW9ucywKICBtYXhpdCA9IDUsICMgZ2VuZXJhbGx5IHVzZSAxMDAgbWF4aW11bSBpdGVyYXRpb25zOyB0aGlzIGV4YW1wbGUgdXNlcyA1IGZvciBzcGVlZAogIHBhcmFsbGVsc2VlZCA9IDUyMjQyLAogIG4uY29yZSA9IG51bUNvcmVzLAogIHBhY2thZ2VzID0gIm1pY2VhZGRzIikKYGBgCgojIEltcHV0YXRpb24gRGlhZ25vc3RpY3MKCiMjIExvZ2dlZCBFdmVudHMKCmBgYHtyfQptaV9taWNlJGxvZ2dlZEV2ZW50cwpgYGAKCiMjIFRyYWNlIFBsb3RzCgpPbiBjb252ZXJnZW5jZSwgdGhlIHN0cmVhbXMgb2YgdGhlIHRyYWNlIHBsb3RzIHNob3VsZCBpbnRlcm1pbmdsZSBhbmQgYmUgZnJlZSBvZiBhbnkgdHJlbmQgKGF0IHRoZSBsYXRlciBpdGVyYXRpb25zKS4KQ29udmVyZ2VuY2UgaXMgZGlhZ25vc2VkIHdoZW4gdGhlIHZhcmlhbmNlIGJldHdlZW4gZGlmZmVyZW50IHNlcXVlbmNlcyBpcyBubyBsYXJnZXIgdGhhbiB0aGUgdmFyaWFuY2Ugd2l0aGluIGVhY2ggaW5kaXZpZHVhbCBzZXF1ZW5jZS4KCi0gaHR0cHM6Ly9zdGVmdmFuYnV1cmVuLm5hbWUvZmltZC9zZWMtYWxnb3B0aW9ucy5odG1sIChhcmNoaXZlZCBhdCBodHRwczovL3Blcm1hLmNjLzRTNTQtNDY1UikKCmBgYHtyfQpwbG90KG1pX21pY2UsIGMoImhlaWdodCIpKQpgYGAKCiMjIERlbnNpdHkgUGxvdHMKCmBgYHtyfQpkZW5zaXR5cGxvdChtaV9taWNlKQpkZW5zaXR5cGxvdChtaV9taWNlLCB+IGhlaWdodCkKYGBgCgojIyBTdHJpcCBQbG90cwoKYGBge3J9CnN0cmlwcGxvdChtaV9taWNlKQpzdHJpcHBsb3QobWlfbWljZSwgaGVpZ2h0IH4gLmltcCkKYGBgCgojIFBvc3QtUHJvY2Vzc2luZwoKIyMgTW9kaWZ5L0NyZWF0ZSBOZXcgVmFyaWFibGVzCgojIyMgYG1pY2VgCgpgYGB7cn0KbWlfbWljZV9sb25nIDwtIGNvbXBsZXRlKAogIG1pX21pY2UsCiAgYWN0aW9uID0gImxvbmciLAogIGluY2x1ZGUgPSBUUlVFKQoKbWlfbWljZV9sb25nJG5ld1ZhciA8LSBtaV9taWNlX2xvbmckYWdlICogbWlfbWljZV9sb25nJGhlaWdodApgYGAKCiMjIyBgam9tb2AKCmBgYHtyfQptaV9qb21vIDwtIG1pX2pvbW8gJT4lIAogIHJlbmFtZShoZWlnaHQgPSBkYXRhVG9JbXB1dGUuLi5sZXZlbDFWYXJzLikKCm1pX2pvbW8kbmV3VmFyIDwtIG1pX2pvbW8kYWdlICogbWlfam9tbyRoZWlnaHQKYGBgCgojIyMgYEFtZWxpYWAKCmBgYHtyfQpmb3IoaSBpbiAxOmxlbmd0aChtaV9hbWVsaWEkaW1wdXRhdGlvbnMpKXsKICBtaV9hbWVsaWEkaW1wdXRhdGlvbnNbW2ldXSRuZXdWYXIgPC0gbWlfYW1lbGlhJGltcHV0YXRpb25zW1tpXV0kYWdlICogbWlfYW1lbGlhJGltcHV0YXRpb25zW1tpXV0kaGVpZ2h0Cn0KYGBgCgojIyBDb252ZXJ0IHRvIGBtaWRzYCBvYmplY3QKCiMjIyBgbWljZWAKCmBgYHtyfQptaV9taWNlX21pZHMgPC0gYXMubWlkcyhtaV9taWNlX2xvbmcpCmBgYAoKIyMjIGBqb21vYAoKYGBge3J9Cm1pX2pvbW9fbWlkcyA8LSBtaWNlYWRkczo6am9tbzJtaWRzKG1pX2pvbW8pCmBgYAoKIyMjIGBBbWVsaWFgCgpgYGB7cn0KbWlfYW1lbGlhX21pZHMgPC0gbWljZWFkZHM6OmRhdGxpc3QybWlkcyhtaV9hbWVsaWEkaW1wdXRhdGlvbnMpCmBgYAoKIyMgRXhwb3J0IGZvciBgTXBsdXNgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQptaWRzMm1wbHVzKG1pX21pY2VfbWlkcywgcGF0aCA9IGZpbGUucGF0aCgiSW5zZXJ0RmlsZVBhdGhIZXJlIiwgZnNlcCA9ICIiKSkKbWlkczJtcGx1cyhtaV9qb21vX21pZHMsIHBhdGggPSBmaWxlLnBhdGgoIkluc2VydEZpbGVQYXRoSGVyZSIsIGZzZXAgPSAiIikpCm1pZHMybXBsdXMobWlfYW1lbGlhX21pZHMsIHBhdGggPSBmaWxlLnBhdGgoIkluc2VydEZpbGVQYXRoSGVyZSIsIGZzZXAgPSAiIikpCmBgYAoKIyBSZXNvdXJjZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvNDZVMi1RVE02KQoKaHR0cHM6Ly93d3cuZ2Vya292aW5rLmNvbS9taWNlVmlnbmV0dGVzL011bHRpX2xldmVsL011bHRpX2xldmVsX2RhdGEuaHRtbCAoYXJjaGl2ZWQgYXQgaHR0cHM6Ly9wZXJtYS5jYy9TRjMyLUQ3WkYpCg==