7
7
8
8
>>> header, apps = parse_appinfo(open('/d/Steam/appcache/appinfo.vdf', 'rb'))
9
9
>>> header
10
- {'magic': b"( DV\\ x07", 'universe': 1}
10
+ {'magic': b") DV\\ x07", 'universe': 1}
11
11
>>> next(apps)
12
12
{'appid': 5,
13
13
'size': 79,
43
43
44
44
uint32 = struct .Struct ('<I' )
45
45
uint64 = struct .Struct ('<Q' )
46
+ int64 = struct .Struct ('<q' )
46
47
47
48
def parse_appinfo (fp ):
48
49
"""Parse appinfo.vdf from the Steam appcache folder
@@ -53,8 +54,9 @@ def parse_appinfo(fp):
53
54
:return: (header, apps iterator)
54
55
"""
55
56
# format:
56
- # uint32 - MAGIC: "'DV\x07" or "(DV\x07"
57
+ # uint32 - MAGIC: "'DV\x07" or "(DV\x07" or b")DV\x07"
57
58
# uint32 - UNIVERSE: 1
59
+ # int64 - OFFSET TO KEY TABLE (added in ")DV\x07")
58
60
# ---- repeated app sections ----
59
61
# uint32 - AppID
60
62
# uint32 - size
@@ -63,17 +65,52 @@ def parse_appinfo(fp):
63
65
# uint64 - accessToken
64
66
# 20bytes - SHA1
65
67
# uint32 - changeNumber
66
- # 20bytes - binary_vdf SHA1 (added in "(DV\x07"
68
+ # 20bytes - binary_vdf SHA1 (added in "(DV\x07")
67
69
# variable - binary_vdf
68
70
# ---- end of section ---------
69
71
# uint32 - EOF: 0
72
+ #
73
+ # ---- key table ----
74
+ # uint32 - Count of keys
75
+ # char[] - Null-terminated strings corresponding to field names
70
76
71
77
magic = fp .read (4 )
72
- if magic not in (b"'DV\x07 " , b"(DV\x07 " ):
78
+ if magic not in (b"'DV\x07 " , b"(DV\x07 " , b")DV \x07 " ):
73
79
raise SyntaxError ("Invalid magic, got %s" % repr (magic ))
74
80
75
81
universe = uint32 .unpack (fp .read (4 ))[0 ]
76
82
83
+ key_table = None
84
+
85
+ appinfo_version = magic [0 ]
86
+ if appinfo_version >= 41 : # b')'
87
+ # appinfo.vdf V29 and newer store list of keys in separate table at the
88
+ # end of the file to reduce size. Retrieve it and pass it to the VDF
89
+ # parser later.
90
+ key_table = []
91
+
92
+ key_table_offset = struct .unpack ('q' , fp .read (8 ))[0 ]
93
+ offset = fp .tell ()
94
+ fp .seek (key_table_offset )
95
+ key_count = uint32 .unpack (fp .read (4 ))[0 ]
96
+
97
+ # Read all null-terminated strings into a list
98
+ for _ in range (0 , key_count ):
99
+ field_name = bytearray ()
100
+ while True :
101
+ field_name += fp .read (1 )
102
+
103
+ if field_name [- 1 ] == 0 :
104
+ field_name = field_name [0 :- 1 ]
105
+ field_name = field_name .decode ('utf-8' , 'replace' )
106
+
107
+ key_table .append (field_name )
108
+ break
109
+
110
+ # Rewind to the beginning of the file after the header:
111
+ # we can now parse the rest of the file.
112
+ fp .seek (offset )
113
+
77
114
def apps_iter ():
78
115
while True :
79
116
appid = uint32 .unpack (fp .read (4 ))[0 ]
@@ -91,10 +128,12 @@ def apps_iter():
91
128
'change_number' : uint32 .unpack (fp .read (4 ))[0 ],
92
129
}
93
130
94
- if magic == b"( DV\x07 " :
131
+ if magic != b"' DV\x07 " :
95
132
app ['data_sha1' ] = fp .read (20 )
96
133
97
- app ['data' ] = binary_load (fp )
134
+ # 'key_table' will be None for older 'appinfo.vdf' files which
135
+ # use self-contained binary VDFs.
136
+ app ['data' ] = binary_load (fp , key_table = key_table )
98
137
99
138
yield app
100
139
0 commit comments