From 052a6ae6401b9f8248aec20e01be8ba5f3889d9f Mon Sep 17 00:00:00 2001 From: Eamonn Rea Date: Sat, 3 Aug 2024 13:10:19 +0300 Subject: [PATCH] Add deserialization support for appinfo.vdf V29 appinfo.vdf V29 was introduced in Steam beta. This new version introduces a space-saving optimization: instead of encoding each key name in the binary VDF segment directly, an int64 identifier is instead used for each key, with a table at the end of the 'appinfo.vdf' file providing the mapping to actual key names. Also see SteamDatabase/SteamAppInfo@56b1fec7f5ce6be961c3e44cf9baf117e363ad91 Fixes ValvePython/steam#462 Co-authored-by: Eamonn Rea --- steam/utils/appcache.py | 51 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/steam/utils/appcache.py b/steam/utils/appcache.py index 8760bd21..3a5d98ca 100644 --- a/steam/utils/appcache.py +++ b/steam/utils/appcache.py @@ -7,7 +7,7 @@ >>> 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 @@ uint32 = struct.Struct('= 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 apps_iter(): '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