Skip to content

Commit bba794a

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 f51826f commit bba794a

File tree

1 file changed

+56
-0
lines changed

1 file changed

+56
-0
lines changed

src/protontricks/steam.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ def find_legacy_steam_runtime_path(steam_root):
483483

484484
APPINFO_STRUCT_HEADER = "<4sL"
485485
APPINFO_V28_STRUCT_SECTION = "<LLLLQ20sL20s"
486+
APPINFO_V29_STRUCT_SECTION = "<LLLLQ20sL20s"
486487

487488

488489
def iter_appinfo_sections(path):
@@ -518,6 +519,59 @@ def _iter_v28_appinfo(data, start):
518519
if i == len(data) - 4:
519520
return
520521

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

523577
# appinfo.vdf is not actually a (binary) VDF file, but a binary file
@@ -539,6 +593,8 @@ def _iter_v28_appinfo(data, start):
539593

540594
if magic == b'(DV\x07':
541595
yield from _iter_v28_appinfo(data, i)
596+
elif magic == b')DV\x07':
597+
yield from _iter_v29_appinfo(data, i)
542598
else:
543599
raise SyntaxError(
544600
"Invalid file magic number. The appinfo.vdf version might not be "

0 commit comments

Comments
 (0)