fix: read DepotDownloader's compressed protobuf depot.config

This commit is contained in:
Kurt Himebauch committed 2026-11-03 15:05:04 -05:00
1 parent 0cc187025f
commit 4cc117b45b
2 files changed
+66 -5

No files matched your search

@@ -11,6 +11,7 @@ import time
import unicodedata
import urllib.parse
import urllib.request
import zlib
STATE = ".droiddeck-games.json"
TRIED = ".droiddeck-import-tried.json"
@@ -301,17 +302,62 @@ def installed_build(folder, appid, installdir):
if not (folder / ".download_complete").is_file():
return None
try:
ids = json.loads((folder / ".DepotDownloader/depot.config").read_text())["installedManifestIDs"]
depots = sorted((int(depot), int(gid)) for depot, gid in ids.items())
except FileNotFoundError:
depots = config_depots((folder / ".DepotDownloader/depot.config").read_bytes())
except OSError:
depots = None
if depots is None:
depots = completed_depots(folder / ".DepotDownloader/completed")
except (OSError, ValueError, KeyError, TypeError, AttributeError):
return None
if not depots or not all(0 < depot < 0x80000000 and 0 < gid < 0x7FFFFFFFFFFFFFFF for depot, gid in depots):
return None
return "".join(f' "{depot}"\n {{\n "manifest" "{gid}"\n }}\n' for depot, gid in depots)
def varint(data, i):
value = shift = 0
while True:
byte = data[i]
i += 1
value |= (byte & 0x7F) << shift
shift += 7
if byte < 0x80:
return value, i
def proto_fields(data):
i = 0
while i < len(data):
key, i = varint(data, i)
kind = key & 7
if kind == 0:
value, i = varint(data, i)
elif kind == 2:
size, i = varint(data, i)
value, i = data[i:i + size], i + size
elif kind in (1, 5):
value, i = None, i + (8 if kind == 1 else 4)
else:
raise ValueError
yield key >> 3, kind, value
def config_depots(data):
try:
ids = json.loads(data.decode())["installedManifestIDs"]
return sorted((int(depot), int(gid)) for depot, gid in ids.items())
except (ValueError, KeyError, TypeError, AttributeError):
pass
try:
depots = {}
for number, kind, entry in proto_fields(zlib.decompress(data, -15)):
if number == 1 and kind == 2:
fields = {n: v for n, k, v in proto_fields(entry) if k == 0}
if 1 in fields and 2 in fields:
depots[fields[1]] = fields[2]
return sorted(depots.items()) or None
except (zlib.error, ValueError, IndexError):
return None
def completed_depots(completed):
newest = {}
try:
+15
View File
@@ -6,6 +6,7 @@ from pathlib import Path
import subprocess
import tempfile
import unittest
import zlib
from unittest.mock import Mock, patch
BIN = Path(__file__).resolve().parents[1] / 'linuxfs/overlay/usr/local/bin'
@@ -247,6 +248,20 @@ class ImportsTest(unittest.TestCase):
config.write_text(json.dumps({'installedManifestIDs': {'43': 0x7FFFFFFFFFFFFFFF}}))
self.assertIsNone(imports.installed_build(self.folder, 42, 'DroidDeck-42'))
def test_compressed_protobuf_depot_config_records_installed_depots(self):
config = self.folder / '.DepotDownloader/depot.config'
config.parent.mkdir()
(self.folder / '.download_complete').touch()
entry = bytes([0x08, 43, 0x10, 99])
packed = zlib.compressobj(wbits=-15)
config.write_bytes(packed.compress(bytes([0x0A, len(entry)]) + entry) + packed.flush())
self.assertIn('"43"\n {\n "manifest" "99"', imports.installed_build(self.folder, 42, 'DroidDeck-42'))
config.write_bytes(b'not a config')
self.assertIsNone(imports.installed_build(self.folder, 42, 'DroidDeck-42'))
(config.parent / 'completed').mkdir()
(config.parent / 'completed/43_98.manifest').write_bytes(b'x')
self.assertIn('"manifest" "98"', imports.installed_build(self.folder, 42, 'DroidDeck-42'))
def test_completed_manifests_record_installed_depots(self):
completed = self.folder / '.DepotDownloader/completed'
completed.mkdir(parents=True)