@ -7,7 +7,7 @@ Appache file parsing examples:
>> > header , apps = parse_appinfo ( open ( ' /d/Steam/appcache/appinfo.vdf ' , ' rb ' ) )
>> > header
{ ' magic ' : b " ( DV\\ x07 " , ' universe ' : 1 }
{ ' magic ' : b " ) DV\\ x07 " , ' universe ' : 1 }
>> > next ( apps )
{ ' appid ' : 5 ,
' size ' : 79 ,
@ -43,6 +43,7 @@ from vdf import binary_load
uint32 = struct . Struct ( ' <I ' )
uint64 = struct . Struct ( ' <Q ' )
int64 = struct . Struct ( ' <q ' )
def parse_appinfo ( fp ) :
""" Parse appinfo.vdf from the Steam appcache folder
@ -53,8 +54,9 @@ def parse_appinfo(fp):
: return : ( header , apps iterator )
"""
# format:
# uint32 - MAGIC: "'DV\x07" or "(DV\x07"
# uint32 - MAGIC: "'DV\x07" or "(DV\x07" or b")DV\x07"
# uint32 - UNIVERSE: 1
# int64 - OFFSET TO KEY TABLE (added in ")DV\x07")
# ---- repeated app sections ----
# uint32 - AppID
# uint32 - size
@ -63,17 +65,52 @@ def parse_appinfo(fp):
# uint64 - accessToken
# 20bytes - SHA1
# uint32 - changeNumber
# 20bytes - binary_vdf SHA1 (added in "(DV\x07"
# 20bytes - binary_vdf SHA1 (added in "(DV\x07")
# variable - binary_vdf
# ---- end of section ---------
# uint32 - EOF: 0
#
# ---- key table ----
# uint32 - Count of keys
# char[] - Null-terminated strings corresponding to field names
magic = fp . read ( 4 )
if magic not in ( b " ' DV \x07 " , b " (DV \x07 " ) :
if magic not in ( b " ' DV \x07 " , b " (DV \x07 " , b " )DV \x07 " ) :
raise SyntaxError ( " Invalid magic, got %s " % repr ( magic ) )
universe = uint32 . unpack ( fp . read ( 4 ) ) [ 0 ]
key_table = None
appinfo_version = magic [ 0 ]
if appinfo_version > = 41 : # b')'
# appinfo.vdf V29 and newer store list of keys in separate table at the
# end of the file to reduce size. Retrieve it and pass it to the VDF
# parser later.
key_table = [ ]
key_table_offset = struct . unpack ( ' q ' , fp . read ( 8 ) ) [ 0 ]
offset = fp . tell ( )
fp . seek ( key_table_offset )
key_count = uint32 . unpack ( fp . read ( 4 ) ) [ 0 ]
# Read all null-terminated strings into a list
for _ in range ( 0 , key_count ) :
field_name = bytearray ( )
while True :
field_name + = fp . read ( 1 )
if field_name [ - 1 ] == 0 :
field_name = field_name [ 0 : - 1 ]
field_name = field_name . decode ( ' utf-8 ' , ' replace ' )
key_table . append ( field_name )
break
# Rewind to the beginning of the file after the header:
# we can now parse the rest of the file.
fp . seek ( offset )
def apps_iter ( ) :
while True :
appid = uint32 . unpack ( fp . read ( 4 ) ) [ 0 ]
@ -91,10 +128,12 @@ def parse_appinfo(fp):
' change_number ' : uint32 . unpack ( fp . read ( 4 ) ) [ 0 ] ,
}
if magic == b " ( DV\x07 " :
if magic != b " ' DV\x07 " :
app [ ' data_sha1 ' ] = fp . read ( 20 )
app [ ' data ' ] = binary_load ( fp )
# 'key_table' will be None for older 'appinfo.vdf' files which
# use self-contained binary VDFs.
app [ ' data ' ] = binary_load ( fp , key_table = key_table )
yield app