Skip to content

Commit 981cad2

Browse files
committed
Add deserialize support for binary VDF w/ table
The new Steam beta introduced a new `appinfo.vdf` version. This appinfo.vdf V29 introduces a new binary VDF format which does not include field keys in binary VDF segments as-is. Instead, each key is represented by a 32-bit integer which needs to be mapped to an actual string using a table stored at the end of the `appinfo.vdf` file. This also means the binary VDF segments in this case are no longer self-contained. The developer can parse and provide the table themselves or instead use the `ValvePython/steam` library which contains a function to parse `appinfo.vdf` files. Also see SteamDatabase/SteamAppInfo@56b1fec Refs ValvePython/steam#462
1 parent d762926 commit 981cad2

File tree

2 files changed

+28
-4
lines changed

2 files changed

+28
-4
lines changed

tests/test_binary_vdf.py

+6
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ def test_raise_on_remaining_with_file(self):
166166
vdf.binary_load(buf, raise_on_remaining=True)
167167
self.assertEqual(buf.read(), b'aaaa')
168168

169+
def test_key_table(self):
170+
test = b'\x01\x00\x00\x00\x00value\x00\x01\x02\x00\x00\x00value3\x00\x08'
171+
key_table = ['key', 'key2', 'key3', 'key4']
172+
173+
self.assertEqual({'key': 'value', 'key3': 'value3'}, vdf.binary_loads(test, key_table=key_table))
174+
169175
def test_vbkv_loads_empty(self):
170176
with self.assertRaises(ValueError):
171177
vdf.vbkv_loads(b'')

vdf/__init__.py

+22-4
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ class COLOR(BASE_INT):
295295
BIN_INT64 = b'\x0A'
296296
BIN_END_ALT = b'\x0B'
297297

298-
def binary_loads(b, mapper=dict, merge_duplicate_keys=True, alt_format=False, raise_on_remaining=True):
298+
def binary_loads(b, mapper=dict, merge_duplicate_keys=True, alt_format=False, key_table=None, raise_on_remaining=True):
299299
"""
300300
Deserialize ``b`` (``bytes`` containing a VDF in "binary form")
301301
to a Python object.
@@ -307,13 +307,18 @@ def binary_loads(b, mapper=dict, merge_duplicate_keys=True, alt_format=False, ra
307307
``merge_duplicate_keys`` when ``True`` will merge multiple KeyValue lists with the
308308
same key into one instead of overwriting. You can se this to ``False`` if you are
309309
using ``VDFDict`` and need to preserve the duplicates.
310+
311+
``key_table`` will be used to translate keys in binary VDF objects
312+
which do not encode strings directly but instead store them in an out-of-band
313+
table. Newer `appinfo.vdf` format stores this table the end of the file,
314+
and it is needed to deserialize the binary VDF objects in that file.
310315
"""
311316
if not isinstance(b, bytes):
312317
raise TypeError("Expected s to be bytes, got %s" % type(b))
313318

314-
return binary_load(BytesIO(b), mapper, merge_duplicate_keys, alt_format, raise_on_remaining)
319+
return binary_load(BytesIO(b), mapper, merge_duplicate_keys, alt_format, key_table, raise_on_remaining)
315320

316-
def binary_load(fp, mapper=dict, merge_duplicate_keys=True, alt_format=False, raise_on_remaining=False):
321+
def binary_load(fp, mapper=dict, merge_duplicate_keys=True, alt_format=False, key_table=None, raise_on_remaining=False):
317322
"""
318323
Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
319324
binary VDF) to a Python object.
@@ -325,6 +330,11 @@ def binary_load(fp, mapper=dict, merge_duplicate_keys=True, alt_format=False, ra
325330
``merge_duplicate_keys`` when ``True`` will merge multiple KeyValue lists with the
326331
same key into one instead of overwriting. You can se this to ``False`` if you are
327332
using ``VDFDict`` and need to preserve the duplicates.
333+
334+
``key_table`` will be used to translate keys in binary VDF objects
335+
which do not encode strings directly but instead store them in an out-of-band
336+
table. Newer `appinfo.vdf` format stores this table the end of the file,
337+
and it is needed to deserialize the binary VDF objects in that file.
328338
"""
329339
if not hasattr(fp, 'read') or not hasattr(fp, 'tell') or not hasattr(fp, 'seek'):
330340
raise TypeError("Expected fp to be a file-like object with tell()/seek() and read() returning bytes")
@@ -382,7 +392,15 @@ def read_string(fp, wide=False):
382392
continue
383393
break
384394

385-
key = read_string(fp)
395+
if key_table:
396+
# If 'key_table' was provided, each key is an int32 value that
397+
# needs to be mapped to an actual field name using a key table.
398+
# Newer appinfo.vdf (V29+) stores this table at the end of the file.
399+
index = int32.unpack(fp.read(int32.size))[0]
400+
401+
key = key_table[index]
402+
else:
403+
key = read_string(fp)
386404

387405
if t == BIN_NONE:
388406
if merge_duplicate_keys and key in stack[-1]:

0 commit comments

Comments
 (0)