-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathkeyvalue.nsh
More file actions
470 lines (371 loc) · 13.6 KB
/
keyvalue.nsh
File metadata and controls
470 lines (371 loc) · 13.6 KB
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
/*
Parser for Valve Data Format, also known as the KeyValues
format: https://developer.valvesoftware.com/wiki/KeyValues
Written by soupcan for GoldenEye: Source
---------------------------------------------------------------
This header provides an easy syntax for reading VDF files.
Usage: ${ReadVDFStr} $outvar vdf_filename key_name
Or...: ${ReadVDFStrMultiple} $outvar vdf_filename key_name index
$outvar: The output will be returned to this var.
vdf_filename is the path to the VDF file to be read.
key_name is the full "path" to the key which you want to read.
It can contain multiple keys and subkeys, delimited by '>'
Note: You can only query keys that do not have any children.
Index: If using ${ReadVDFStrMultiple}, it will read the Nth
matching key, unlike ${ReadVDFStr} which will return the 1st.
This is useful for reading multiple values with the same key
name.
To use ${ReadVDFStrMultiple}, you can keep querying it with an
incrementing index, until the errors flag is set, indicating
that no more matching keys could be found.
If there is an error, the errors flag will be set and the error
message will be copied to $vdfError
*/
Var vdfOut ; $outvar
Var vdfError
Var vdfFile ; File name
Var vdfFileHandle ; File handle
Var vdfIndex ; If parsing multiple keys, which index are we looking for?
; This is simply the number of matching keys we've hit so far.
Var vdfCurIndex ; Which index are we currently on?
Var vdfKey ; Full "path" to the key/subkey of the VDF
Var vdfCurrentKey ; Current VDF key being processed
Var vdfLevel ; Current "level" (how many curley brackets deep)
Var vdfDepth ; Depth/level of the target key
Var vdfToken ; The current token returned by the vdfGetNextToken function.
Var vdfBracket ; Used by vdfGetNextToken to signal to vdfMain when an opening or
; closing bracket is found. We do it this way instead of using
; vdfToken, because a quoted curley bracket could otherwise fool
; our parser.
!define ReadVDFStr "!insertmacro ReadVDFStr"
!define ReadVDFStrMultiple "!insertmacro ReadVDFStrMultiple"
!macro ReadVDFStr outvar filename key
StrCpy $vdfFile "${filename}"
StrCpy $vdfKey "${key}"
StrCpy $vdfIndex ''
Call vdfMain
StrCpy "${outvar}" $vdfOut
!macroend
!macro ReadVDFStrMultiple outvar filename key index
StrCpy $vdfFile "${filename}"
StrCpy $vdfKey "${key}"
StrCpy $vdfIndex "${index}"
Call vdfMain
StrCpy "${outvar}" $vdfOut
!macroend
; Get depth of the provided $vdfKey and stores it in $vdfDepth
; Depth starts at 0, e.g. "ges_version" is 0 while "ges_version>text" is 1
Function vdfGetKeyDepth
Push $0 ; $0 = length of string
Push $1 ; $1 = numeric index of char in string
Push $2 ; $2 = current char
StrLen $0 $vdfKey
StrCpy $vdfDepth 0
StrCpy $1 1
ReadNextChar:
; Read char at index ($1) of $vdfKey and store it in $2
StrCpy $2 $vdfKey 1 $1
; If current char $2 == '>', increment $vdfDepth
StrCmp $2 ">" +1 NoMatch
IntOp $vdfDepth $vdfDepth + 1
NoMatch:
; Increment index
IntOp $1 $1 + 1
; If index >= StrLen, return
; Otherwise, restart loop
IntCmp $0 $1 +1 +1 ReadNextChar
Pop $2
Pop $1
Pop $0
FunctionEnd
; This function gets the VDF key at $vdfLevel
; and stores it in $vdfCurrentKey
Function vdfGetCurrentKey
Push $0 ; $0 = length of string
Push $1 ; $1 = numeric index of char in string
Push $2 ; $2 = current char
Push $3 ; $3 = current level being processed
StrLen $0 $vdfKey
StrCpy $vdfCurrentKey ''
StrCpy $1 0
StrCpy $3 0
ReadNextChar:
; Read char at index ($1) of $vdfKey and store it in $2
StrCpy $2 $vdfKey 1 $1
; If we're at our target level...
StrCmp $3 $vdfLevel +1 NotEqual
; Return immediately if current char is >
StrCmp $2 ">" RReturn +1
; Otherwise, copy current char $2 to $vdfCurrentKey
StrCpy $vdfCurrentKey "$vdfCurrentKey$2"
NotEqual:
; If current char $2 == '>', increment level
StrCmp $2 ">" +1 NoMatch
IntOp $3 $3 + 1
NoMatch:
; Increment index
IntOp $1 $1 + 1
; If index >= StrLen, return
; Otherwise, restart loop
IntCmp $0 $1 +1 +1 ReadNextChar
RReturn:
Pop $3
Pop $2
Pop $1
Pop $0
FunctionEnd
; This function gets the next token. A token is any text in the file,
; including key names, values and brackets. Comments are skipped.
;
; This function handles double-quotes for keys/values with spaces in
; them. It also handles the escape sequences \n, \t, \\, and \".
Function vdfGetNextToken
Var /GLOBAL vdfJunk ; /dev/null
Var /GLOBAL vdfCurrentChar ; current char in file
Var /GLOBAL vdfIsQuoted ; Are we currently in an open double quote?
Var /GLOBAL vdfIsEscaped ; Was the previous character the escape character '\'?
Var /GLOBAL vdfIsLastCharFwdSlash ; Is the previous char an (unquoted) forward slash? Two of these in a row is a comment.
StrCpy $vdfToken ''
StrCpy $vdfBracket ''
StrCpy $vdfCurrentChar ''
StrCpy $vdfIsQuoted ''
StrCpy $vdfIsEscaped ''
StrCpy $vdfIsLastCharFwdSlash ''
ReadNextChar:
; Read the char into $vdfCurrentChar and advance offset by 1
FileRead $vdfFileHandle $vdfCurrentChar 1
IfErrors ReachedEOF
; If we are in an open quote, don't check for comments
StrCmp $vdfIsQuoted '' +1 CommentEnd
; If this char, and previous char, were forward slashes
; skip to next line as this is a comment
StrCmp $vdfCurrentChar '/' +1 NotFSlash
; If the last char was also a forward slash, skip to next line
; Also unset the var since we will want a fresh start for the next line.
StrCmp $vdfIsLastCharFwdSlash '1' +1 NotFSlash
; Calling FileRead without a maxlen will take us to the next line.
; We don't care about the output so we just write to our junk var
FileRead $vdfFileHandle $vdfJunk
StrCpy $vdfIsLastCharFwdSlash ''
; Subtract last char (fwd slash) from vdfToken
StrCpy $vdfToken $vdfToken -1
Goto ReadNextChar
NotFSlash:
; Set the vdfIsLastCharFwdSlash variable
StrCpy $vdfIsLastCharFwdSlash ''
StrCmp $vdfCurrentChar '/' +1 StillNotAFSlash
StrCpy $vdfIsLastCharFwdSlash 1
StillNotAFSlash:
CommentEnd:
; Opening/closing brackets --
; These are not supposed to be at the beginning or end of another token
; unless it's quoted. So, if we find one while unquoted, we return its
; result immediately.
StrCmp $vdfIsQuoted '' +1 SkipBracket
StrCmp $vdfCurrentChar '{' +1 NotOpeningBracket
StrCmp $vdfToken '' +1 ReturnCurrentToken
StrCpy $vdfBracket '{'
Return
NotOpeningBracket:
; Processing for closing bracket
StrCmp $vdfCurrentChar '}' +1 SkipBracket
StrCmp $vdfToken '' +1 ReturnCurrentToken
StrCpy $vdfBracket '}'
Return
ReturnCurrentToken:
; Rewind back 1 char, to make sure we don't
; skip the bracket next time
FileSeek $vdfFileHandle -1 CUR
Return
SkipBracket:
; Skip all quote parsing if we're escaped
StrCmp $vdfIsEscaped '' +1 SkipAllQuoteParsing
; If current char is a quote, toggle the $vdfIsQuoted flag
StrCmp $vdfCurrentChar '"' +1 NotQuote
StrCmp $vdfIsQuoted '' +1 IsCurrentlyQuoted
StrCpy $vdfIsQuoted 1
Goto QuoteFlagEnd
IsCurrentlyQuoted:
StrCpy $vdfIsQuoted ''
QuoteFlagEnd:
; If current char is a quote, and the $vdfIsQuoted flag is
; unset (meaning we're ending a quote), return
;
; If current char is a quote, and the $vdfIsQuoted flag is
; set (meaning we're beginning a quote), goto ReadNextChar
; if we haven't read a token yet. If we have, return.
StrCmp $vdfCurrentChar '"' +1 NotQuote
StrCmp $vdfIsQuoted '' +1 IsOpeningQuote
Return
IsOpeningQuote:
StrCmp $vdfToken '' ReadNextChar
Return
NotQuote:
; If current char is a whitespace, and we are NOT in an open
; quote block, read next char UNLESS we already wrote something
; into $vdfToken
; Skip if quoted.
StrCmp $vdfIsQuoted '' +1 SkipWhitespace
; Space
StrCmp $vdfCurrentChar " " CharIsWhitespace +1
; Carriage return
StrCmp $vdfCurrentChar "$\r" CharIsWhitespace +1
; Newline
StrCmp $vdfCurrentChar "$\n" CharIsWhitespace +1
; Tab
StrCmp $vdfCurrentChar "$\t" CharIsWhitespace +1
Goto SkipWhitespace
CharIsWhitespace:
StrCmp $vdfToken '' ReadNextChar +1
Return
SkipWhitespace:
SkipAllQuoteParsing:
; Handle escape sequences if vdfIsEscaped is set
StrCmp $vdfIsEscaped '' ParseEscapeEnd
; Newline
StrCmp $vdfCurrentChar "n" +1 Tab
StrCpy $vdfCurrentChar "$\n"
Goto RemoveLastChar
Tab:
StrCmp $vdfCurrentChar "t" +1 Backslash
StrCpy $vdfCurrentChar "$\t"
Goto RemoveLastChar
Backslash:
StrCmp $vdfCurrentChar "\" +1 Quote
StrCpy $vdfCurrentChar '\'
Goto RemoveLastChar
Quote:
StrCmp $vdfCurrentChar '"' +1
StrCpy $vdfCurrentChar '"'
Goto RemoveLastChar
; If the char isn't a recognized escape sequence, finish
Goto ParseEscapeEnd
RemoveLastChar:
; Remove backslash
StrCpy $vdfToken $vdfToken -1
ParseEscapeEnd:
; Is this char a back slash?
StrCmp $vdfCurrentChar '\' +1 Unescape
; Set vdfIsEscaped, but only if isEscaped isn't already set
; if it is, unset it
StrCmp $vdfIsEscaped "" +1 Unescape
StrCpy $vdfIsEscaped '1'
Goto SetEscapedEnd
Unescape:
StrCpy $vdfIsEscaped ''
SetEscapedEnd:
StrCpy $vdfToken "$vdfToken$vdfCurrentChar"
Goto ReadNextChar
ReachedEOF:
SetErrors
Return
FunctionEnd
!macro vdfError text
StrCpy $vdfOut ''
StrCpy $vdfError "${text}"
StrCpy $vdfErrorState 1
Goto RReturn
!macroend
Function vdfMain
Var /GLOBAL vdfFileSize ; file size of input file
Var /GLOBAL vdfPreExistingErrorState ; error state when function was called
Var /GLOBAL vdfErrorState ; whether to SetErrors at the end of the function, regardless of pre-existing error state
Var /GLOBAL vdfIsValue ; set if processing a value. this is so we know if we're processing a value, or some other token type.
Var /GLOBAL vdfCurLevel ; current level of the token being processed
StrCpy $vdfPreExistingErrorState ''
StrCpy $vdfErrorState ''
StrCpy $vdfError ''
StrCpy $vdfFileSize ''
StrCpy $vdfLevel 0
StrCpy $vdfIsValue ''
StrCpy $vdfCurLevel 0
StrCpy $vdfCurIndex 0
; Get current error state, to restore it later.
StrCpy $vdfPreExistingErrorState 0
IfErrors +1 NoErrors
StrCpy $vdfPreExistingErrorState 1
NoErrors:
ClearErrors
IfFileExists $vdfFile FileExists
!insertmacro vdfError "File doesn't exist."
FileExists:
FileOpen $vdfFileHandle $vdfFile r
; Get file size
FileSeek $vdfFileHandle 0 END $vdfFileSize
; Check that file isn't more than 4MiB in length
IntCmp $vdfFileSize 4194304 FileSizeOk FileSizeOk
!insertmacro vdfError "File too large."
FileSizeOk:
; Rewind back to starting position
FileSeek $vdfFileHandle 0
Call vdfGetKeyDepth ; Initializes $vdfDepth
Call vdfGetCurrentKey ; Sets $vdfCurrentKey to its initial value
GetToken:
ClearErrors
Call vdfGetNextToken
IfErrors +1 NoParserErrors
!insertmacro vdfError "Reached EOF without finding key."
NoParserErrors:
StrCmp $vdfBracket '{' +1 SkipLevelUp
IntOp $vdfCurLevel $vdfCurLevel + 1
StrCpy $vdfIsValue ''
Goto GetToken
SkipLevelUp:
StrCmp $vdfBracket '}' +1 SkipLevelDn
IntOp $vdfCurLevel $vdfCurLevel - 1
StrCpy $vdfIsValue ''
Goto GetToken
SkipLevelDn:
; If this is a value, rather than key, get next token
StrCmp $vdfIsValue '' TokenIsKey
StrCpy $vdfIsValue ''
Goto GetToken
TokenIsKey:
; Set isValue, so that next token isn't treated like a key
StrCpy $vdfIsValue 1
; Get next token if we arent at our target depth for our next key
StrCmp $vdfCurLevel $vdfLevel +1 GetToken
; If the token == our key name, get the next token (the value)
StrCmp $vdfToken $vdfCurrentKey +1 GetToken
Call vdfGetNextToken
StrCpy $vdfIsValue ''
; If current level == key depth, treat next token like it's the value we're looking for
StrCmp $vdfLevel $vdfDepth +1 ExpectingBracket
; Check if we actually got a value, or a bracket
StrCmp $vdfBracket '' GotValue
!insertmacro vdfError "Got '$vdfBracket', expecting value string"
GotValue:
; Check if we're reading multiple keys, or just this one...
StrCmp $vdfIndex '' ReturnToken
; Check if we're at our destination index
StrCmp $vdfIndex $vdfCurIndex ReturnToken
; And if we aren't, increment our current index and try again
IntOp $vdfCurIndex $vdfCurIndex + 1
Goto GetToken
ReturnToken:
StrCpy $vdfOut $vdfToken
Goto RReturn
; Otherwise, we're expecting an opening bracket...
ExpectingBracket:
; Increment levels and depth, get next key
StrCmp $vdfBracket '{' +1 NoBracketMatch
IntOp $vdfCurLevel $vdfCurLevel + 1
IntOp $vdfLevel $vdfLevel + 1
StrCpy $vdfIsValue ''
Call vdfGetCurrentKey
Goto GetToken
NoBracketMatch:
!insertmacro vdfError "Got '$vdfBracket$vdfToken', expecting '{'"
RReturn:
FileClose $vdfFileHandle
; Restore prior error state
ClearErrors
StrCmp $vdfPreExistingErrorState 1 +1 DontSetErrors
SetErrors
DontSetErrors:
; If our internal error flag was set, then set errors
; regardless of prior error state.
StrCmp $vdfErrorState "" ReallyDontSetErrors
SetErrors
ReallyDontSetErrors:
FunctionEnd