77
88 >>> header, apps = parse_appinfo(open('/d/Steam/appcache/appinfo.vdf', 'rb'))
99 >>> header
10- {'magic': b"( DV\\ x07", 'universe': 1}
10+ {'magic': b") DV\\ x07", 'universe': 1}
1111 >>> next(apps)
1212 {'appid': 5,
1313 'size': 79,
4343
4444uint32 = struct .Struct ('<I' )
4545uint64 = struct .Struct ('<Q' )
46+ int64 = struct .Struct ('<q' )
4647
4748def parse_appinfo (fp ):
4849 """Parse appinfo.vdf from the Steam appcache folder
@@ -53,8 +54,9 @@ def parse_appinfo(fp):
5354 :return: (header, apps iterator)
5455 """
5556# format:
56- # uint32 - MAGIC: "'DV\x07" or "(DV\x07"
57+ # uint32 - MAGIC: "'DV\x07" or "(DV\x07" or b")DV\x07"
5758# uint32 - UNIVERSE: 1
59+ # int64 - OFFSET TO KEY TABLE (added in ")DV\x07")
5860# ---- repeated app sections ----
5961# uint32 - AppID
6062# uint32 - size
@@ -63,17 +65,52 @@ def parse_appinfo(fp):
6365# uint64 - accessToken
6466# 20bytes - SHA1
6567# uint32 - changeNumber
66- # 20bytes - binary_vdf SHA1 (added in "(DV\x07"
68+ # 20bytes - binary_vdf SHA1 (added in "(DV\x07")
6769# variable - binary_vdf
6870# ---- end of section ---------
6971# uint32 - EOF: 0
72+ #
73+ # ---- key table ----
74+ # uint32 - Count of keys
75+ # char[] - Null-terminated strings corresponding to field names
7076
7177 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 " ):
7379 raise SyntaxError ("Invalid magic, got %s" % repr (magic ))
7480
7581 universe = uint32 .unpack (fp .read (4 ))[0 ]
7682
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+
77114 def apps_iter ():
78115 while True :
79116 appid = uint32 .unpack (fp .read (4 ))[0 ]
@@ -91,10 +128,12 @@ def apps_iter():
91128 'change_number' : uint32 .unpack (fp .read (4 ))[0 ],
92129 }
93130
94- if magic == b"( DV\x07 " :
131+ if magic != b"' DV\x07 " :
95132 app ['data_sha1' ] = fp .read (20 )
96133
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 )
98137
99138 yield app
100139
0 commit comments