Skip to content

Commit 9631f24

Browse files
committed
Support appinfo.vdf V29
Steam beta introduced a new version of appinfo.vdf with a space-saving optimization. Field keys are stored in a separate table at the end of the file, with the actual VDF segments having to be parsed using the table to map the indices to actual field names. `vdf` library does not support serializing appinfo.vdf using this format, at least yet, so just use appinfo.vdf V28 in tests for the time being. This might need to be fixed in the future once appinfo.vdf V28 is phased out. Fixes #304
1 parent f2d605d commit 9631f24

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

src/protontricks/steam.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ def find_legacy_steam_runtime_path(steam_root):
471471

472472
APPINFO_STRUCT_HEADER = "<4sL"
473473
APPINFO_V28_STRUCT_SECTION = "<LLLLQ20sL20s"
474+
APPINFO_V29_STRUCT_SECTION = "<LLLLQ20sL20s"
474475

475476

476477
def iter_appinfo_sections(path):
@@ -506,6 +507,57 @@ def _iter_v28_appinfo(data, start):
506507
if i == len(data) - 4:
507508
return
508509

510+
def _iter_v29_appinfo(data, start):
511+
"""
512+
Parse and iterate appinfo.vdf version 29.
513+
"""
514+
i = start
515+
516+
# The header contains the offset to the key table
517+
key_table_offset = struct.unpack("<q", data[i:i+8])[0]
518+
key_table = []
519+
520+
key_count = struct.unpack(
521+
"<i", data[key_table_offset:key_table_offset+4]
522+
)[0]
523+
524+
table_i = key_table_offset + 4
525+
for _ in range(0, key_count):
526+
key = bytearray()
527+
while True:
528+
key.append(data[table_i])
529+
table_i += 1
530+
531+
if key[-1] == 0:
532+
key_table.append(key[0:-1].decode("utf-8"))
533+
break
534+
535+
i += 8
536+
537+
section_size = struct.calcsize(APPINFO_V29_STRUCT_SECTION)
538+
while True:
539+
# We don't need any of the fields besides 'entry_size',
540+
# which is used to determine the length of the variable-length VDF
541+
# field.
542+
# Still, here they are for posterity's sake.
543+
(appid, entry_size, infostate, last_updated, access_token,
544+
sha_hash, change_number, vdf_sha_hash) = struct.unpack(
545+
APPINFO_V29_STRUCT_SECTION, data[i:i+section_size])
546+
vdf_section_size = entry_size - (section_size - 8)
547+
548+
i += section_size
549+
550+
vdf_d = vdf.binary_loads(
551+
data[i:i+vdf_section_size], key_table=key_table
552+
)
553+
vdf_d = lower_dict(vdf_d)
554+
yield vdf_d
555+
556+
i += vdf_section_size
557+
558+
if i == key_table_offset - 4:
559+
return
560+
509561
logger.debug("Loading appinfo.vdf in %s", path)
510562

511563
# appinfo.vdf is not actually a (binary) VDF file, but a binary file
@@ -527,6 +579,8 @@ def _iter_v28_appinfo(data, start):
527579

528580
if magic == b'(DV\x07':
529581
yield from _iter_v28_appinfo(data, i)
582+
elif magic == b')DV\x07':
583+
yield from _iter_v29_appinfo(data, i)
530584
else:
531585
raise SyntaxError(
532586
"Invalid file magic number. The appinfo.vdf version might not be "

0 commit comments

Comments
 (0)