Skip to content

Commit

Permalink
Add decode support for new binary VDF
Browse files Browse the repository at this point in the history
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 are stored at the end of the `appinfo.vdf` file.

This also means the binary VDF segments are no longer self-contained.

Also see SteamDatabase/SteamAppinfo#56b1fec7f5ce6be961c3e44cf9baf117e363ad91

Refs ValvePython/steam#462
  • Loading branch information
Matoking committed Jun 28, 2024
1 parent d762926 commit 636b477
Showing 1 changed file with 24 additions and 5 deletions.
29 changes: 24 additions & 5 deletions vdf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ class COLOR(BASE_INT):
BIN_INT64 = b'\x0A'
BIN_END_ALT = b'\x0B'

def binary_loads(b, mapper=dict, merge_duplicate_keys=True, alt_format=False, raise_on_remaining=True):
def binary_loads(b, mapper=dict, merge_duplicate_keys=True, alt_format=False, field_names=None, raise_on_remaining=True):
"""
Deserialize ``b`` (``bytes`` containing a VDF in "binary form")
to a Python object.
Expand All @@ -307,13 +307,17 @@ def binary_loads(b, mapper=dict, merge_duplicate_keys=True, alt_format=False, ra
``merge_duplicate_keys`` when ``True`` will merge multiple KeyValue lists with the
same key into one instead of overwriting. You can se this to ``False`` if you are
using ``VDFDict`` and need to preserve the duplicates.
``field_names`` contains a list of field keys. Newer `appinfo.vdf` format
stores this at the very end of the file, and it is needed to deserialize
the binary VDF segments in that file
"""
if not isinstance(b, bytes):
raise TypeError("Expected s to be bytes, got %s" % type(b))

return binary_load(BytesIO(b), mapper, merge_duplicate_keys, alt_format, raise_on_remaining)
return binary_load(BytesIO(b), mapper, merge_duplicate_keys, alt_format, field_names, raise_on_remaining)

def binary_load(fp, mapper=dict, merge_duplicate_keys=True, alt_format=False, raise_on_remaining=False):
def binary_load(fp, mapper=dict, merge_duplicate_keys=True, alt_format=False, field_names=None, raise_on_remaining=False):
"""
Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
binary VDF) to a Python object.
Expand All @@ -325,7 +329,13 @@ def binary_load(fp, mapper=dict, merge_duplicate_keys=True, alt_format=False, ra
``merge_duplicate_keys`` when ``True`` will merge multiple KeyValue lists with the
same key into one instead of overwriting. You can se this to ``False`` if you are
using ``VDFDict`` and need to preserve the duplicates.
``field_names`` contains a list of field keys. Newer `appinfo.vdf` format
stores this at the very end of the file, and it is needed to deserialize
the binary VDF segments in that file
"""
use_field_indices = bool(field_names)

if not hasattr(fp, 'read') or not hasattr(fp, 'tell') or not hasattr(fp, 'seek'):
raise TypeError("Expected fp to be a file-like object with tell()/seek() and read() returning bytes")
if not issubclass(mapper, Mapping):
Expand All @@ -337,7 +347,14 @@ def binary_load(fp, mapper=dict, merge_duplicate_keys=True, alt_format=False, ra
int64 = struct.Struct('<q')
float32 = struct.Struct('<f')

def read_string(fp, wide=False):
def read_string(fp, wide=False, use_field_indices=False):
if use_field_indices:
# Newer appinfo.vdf has the list of field names in an out-of-band
# table
index = int32.unpack(fp.read(int32.size))[0]

return field_names[index]

buf, end = b'', -1
offset = fp.tell()

Expand Down Expand Up @@ -382,7 +399,9 @@ def read_string(fp, wide=False):
continue
break

key = read_string(fp)
# If 'field_names' was provided, each key is an int32 value that
# corresponds to an entry in the field name array.
key = read_string(fp, use_field_indices=use_field_indices)

if t == BIN_NONE:
if merge_duplicate_keys and key in stack[-1]:
Expand Down

0 comments on commit 636b477

Please sign in to comment.