4 changed files with 171 additions and 1 deletions
@ -0,0 +1,161 @@ |
|||
"""" |
|||
Appache file parsing examples: |
|||
|
|||
.. code:: python |
|||
|
|||
>>> from steam.utils.appcache import parse_appinfo, parse_packageinfo |
|||
|
|||
>>> header, apps = parse_appinfo(open('/d/Steam/appcache/appinfo.vdf', 'rb')) |
|||
>>> header |
|||
{'magic': b"'DV\x07", 'universe': 1} |
|||
>>> next(apps) |
|||
{'appid': 5, |
|||
'size': 79, |
|||
'info_state': 1, |
|||
'last_updated': 1484735377, |
|||
'access_token': 0, |
|||
'sha1': b'\x87\xfaCg\x85\x80\r\xb4\x90Im\xdc}\xb4\x81\xeeQ\x8b\x825', |
|||
'change_number': 4603827, |
|||
'data': {'appinfo': {'appid': 5, 'public_only': 1}}} |
|||
|
|||
>>> header, pkgs = parse_packageinfo(open('/d/Steam/appcache/packageinfo.vdf', 'rb')) |
|||
>>> header |
|||
{'magic': b"'UV\x06", 'universe': 1} |
|||
|
|||
>>> next(pkgs) |
|||
{'packageid': 7, |
|||
'sha1': b's\x8b\xf7n\t\xe5 k#\xb6-\x82\xd2 \x14k@\xfeDQ', |
|||
'change_number': 7469765, |
|||
'data': {'7': {'packageid': 7, |
|||
'billingtype': 1, |
|||
'licensetype': 1, |
|||
'status': 0, |
|||
'extended': {'requirespreapproval': 'WithRedFlag'}, |
|||
'appids': {'0': 10, '1': 80, '2': 100, '3': 254430}, |
|||
'depotids': {'0': 0, '1': 95, '2': 101, '3': 102, '4': 103, '5': 254431}, |
|||
'appitems': {}}}} |
|||
|
|||
""" |
|||
|
|||
import struct |
|||
from vdf import binary_load |
|||
|
|||
uint32 = struct.Struct('<I') |
|||
uint64 = struct.Struct('<Q') |
|||
|
|||
def parse_appinfo(fp): |
|||
"""Parse appinfo.vdf from the Steam appcache folder |
|||
|
|||
:param fp: filelike object |
|||
:raises: SyntaxError |
|||
:rtype: (:class:`dict`, :class:`Generator`) |
|||
:return: (header, apps iterator) |
|||
""" |
|||
# format: |
|||
# uint32 - MAGIC: "'DV\x07" |
|||
# uint32 - UNIVERSE: 1 |
|||
# ---- repeated app sections ---- |
|||
# uint32 - AppID |
|||
# uint32 - size |
|||
# uint32 - infoState |
|||
# uint32 - lastUpdated |
|||
# uint64 - accessToken |
|||
# 20bytes - SHA1 |
|||
# uint32 - changeNumber |
|||
# variable - binary_vdf |
|||
# ---- end of section --------- |
|||
# uint32 - EOF: 0 |
|||
|
|||
magic = fp.read(4) |
|||
if magic != b"'DV\x07": |
|||
raise SyntaxError("Invalid magic, got %s" % repr(magic)) |
|||
|
|||
universe = uint32.unpack(fp.read(4))[0] |
|||
|
|||
def apps_iter(): |
|||
while True: |
|||
appid = uint32.unpack(fp.read(4))[0] |
|||
|
|||
if appid == 0: |
|||
break |
|||
|
|||
app = { |
|||
'appid': appid, |
|||
'size': uint32.unpack(fp.read(4))[0], |
|||
'info_state': uint32.unpack(fp.read(4))[0], |
|||
'last_updated': uint32.unpack(fp.read(4))[0], |
|||
'access_token': uint64.unpack(fp.read(8))[0], |
|||
'sha1': fp.read(20), |
|||
'change_number': uint32.unpack(fp.read(4))[0], |
|||
'data': binary_load(fp), |
|||
} |
|||
|
|||
yield app |
|||
|
|||
|
|||
return ({ |
|||
'magic': magic, |
|||
'universe': universe, |
|||
}, |
|||
apps_iter() |
|||
) |
|||
|
|||
def parse_packageinfo(fp): |
|||
"""Parse packageinfo.vdf from the Steam appcache folder |
|||
|
|||
:param fp: filelike object |
|||
:raises: SyntaxError |
|||
:rtype: (:class:`dict`, :class:`Generator`) |
|||
:return: (header, packages iterator) |
|||
""" |
|||
# format: |
|||
# uint32 - MAGIC: "'UV\x06" |
|||
# uint32 - UNIVERSE: 1 |
|||
# ---- repeated package sections ---- |
|||
# uint32 - PackageID |
|||
# 20bytes - SHA1 |
|||
# uint32 - changeNumber |
|||
# uint64 - token |
|||
# variable - binary_vdf |
|||
# ---- end of section --------- |
|||
# uint32 - EOF: 0xFFFFFFFF |
|||
|
|||
magic = fp.read(4) |
|||
if magic != b"'UV\x06": |
|||
raise SyntaxError("Invalid magic, got %s" % repr(magic)) |
|||
|
|||
universe = uint32.unpack(fp.read(4))[0] |
|||
|
|||
def pkgs_iter(): |
|||
while True: |
|||
packageid = uint32.unpack(fp.read(4))[0] |
|||
|
|||
if packageid == 0xFFFFFFFF: |
|||
break |
|||
|
|||
pkg = { |
|||
'packageid': packageid, |
|||
'sha1': fp.read(20), |
|||
'change_number': uint32.unpack(fp.read(4))[0], |
|||
} |
|||
|
|||
offset = fp.tell() |
|||
|
|||
try: |
|||
pkg['data'] = binary_load(fp) |
|||
except: |
|||
# steam beta client has introduced new token field |
|||
# there is no indicator, so we resolve to hackery |
|||
fp.seek(offset) |
|||
pkg['token'] = uint64.unpack(fp.read(8))[0] |
|||
pkg['data'] = binary_load(fp) |
|||
|
|||
yield pkg |
|||
|
|||
|
|||
return ({ |
|||
'magic': magic, |
|||
'universe': universe, |
|||
}, |
|||
pkgs_iter() |
|||
) |
Loading…
Reference in new issue