You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

194 lines
6.0 KiB

from collections import OrderedDict, deque
from six import itervalues
import vdf
from steam import webapi
from steam.enums import EResult, EServerType
from steam.util.web import make_requests_session
from stema.core.crypto symmetric_decrypt
from steam.core.manifest import DepotManifest
def get_content_servers_from_cs(host, port, cell_id, num_servers=20, session=None):
proto = 'https' if port == 443 else 'http'
url = '%s://%s:%s/serverlist/%s/%s/' % (proto, host, port, cell_id, num_servers)
session = make_requests_session() if session is None else session
resp = session.get(url)
if resp.status_code != 200:
return []
kv = vdf.loads(resp.text, mapper=OrderedDict)
if kv.get('deferred') == '1':
return []
servers = []
for entry in itervalues(kv['serverlist']):
server = ContentServer()
server.type = entry['type']
server.https = True if entry['https_support'] == 'mandatory' else False
server.host = entry['Host']
server.vhost = entry['vhost']
server.port = 443 if server.https else 80
server.cell_id = entry['cell']
server.load = entry['load']
server.weighted_load = entry['weightedload']
servers.append(server)
return servers
def get_content_servers_from_webapi(cell_id, num_servers=20):
params = {'cellid': cell_id, 'max_servers': num_servers}
resp = webapi.get('IContentServerDirectoryService', 'GetServersForSteamPipe', params=params)
servers = []
for entry in resp['response']['servers']:
server = ContentServer()
server.type = entry['type']
server.https = True if entry['https_support'] == 'mandatory' else False
server.host = entry['host']
server.vhost = entry['vhost']
server.port = 443 if server.https else 80
server.cell_id = entry.get('cell_id', 0)
server.load = entry['load']
server.weighted_load = entry['weighted_load']
servers.append(server)
return servers
class CDNClient(object):
def __init__(self, client, app_id):
self.steam = client
self.app_id = app_id
self.web = make_requests_session()
self.servers = deque()
self.cdn_auth_tokens = {}
self.depot_keys = {}
@property
def cell_id(self):
return self.steam.cell_id
def init_servers(self, num_servers=20):
self.servers.clear()
for ip, port in self.steam.servers[EServerType.CS]:
servers = get_content_servers_from_cs(ip, port, self.cell_id, num_servers, self.web)
if servers:
self.servers.extend(servers)
break
if not self.servers:
raise RuntimeError("No content servers on SteamClient instance. Is it logged in?")
def get_content_server(self, rotate=True):
if rotate:
self.servers.rotate(-1)
return self.servers[0]
def get_cdn_auth_token(self, depot_id):
if depot_id not in self.cdn_auth_tokens:
msg = self.steam.get_cdn_auth_token(depot_id, 'steampipe.steamcontent.com')
if msg.eresult == EResult.OK:
self.cdn_auth_tokens[depot_id] = msg.token
elif msg is None:
raise Exception("Failed getting depot key: %s" % repr(EResult.Timeout))
else:
raise Exception("Failed getting depot key: %s" % repr(EResult(msg.eresult)))
return self.cdn_auth_tokens[depot_id]
def get_depot_key(self, depot_id):
if depot_id not in self.depot_keys:
msg = self.steam.get_depot_key(self.app_id, depot_id)
if msg.eresult == EResult.OK:
self.depot_keys[depot_id] = msg.depot_encryption_key
elif msg is None:
raise Exception("Failed getting depot key: %s" % repr(EResult.Timeout))
else:
raise Exception("Failed getting depot key: %s" % repr(EResult(msg.eresult)))
return self.depot_keys[depot_id]
def get(self, command, args, auth_token=''):
server = self.get_content_server()
while True:
url = "%s://%s:%s/%s/%s%s" % (
'https' if server.https else 'http',
server.host,
server.port,
command,
args,
auth_token,
)
resp = self.web.get(url)
if resp.ok:
return resp
elif resp.status_code in (401, 403, 404):
resp.raise_for_status()
server = self.get_content_server(rotate=True)
def get_manifest(self, depot_id, manifest_id, cdn_auth_token=None, decrypt=True):
if cdn_auth_token is None:
cdn_auth_token = self.get_cdn_auth_token(depot_id)
resp = self.get('depot', '%s/manifest/%s/5' % (depot_id, manifest_id), cdn_auth_token)
if resp.ok:
manifest = DepotManifest(resp.content)
if decrypt:
manifest.decrypt_filenames(self.get_depot_key(depot_id))
return manifest
def get_chunk(self, depot_id, chunk_id, cdn_auth_token=None):
if cdn_auth_token is None:
cdn_auth_token = self.get_cdn_auth_token(depot_id)
resp = self.get('depot', '%s/chunk/%s' % (depot_id, chunk_id), cdn_auth_token)
if resp.ok:
data = symmetric_decrypt(resp.content, self.get_depot_key(depot_id))
if data[:2] == b'VZ':
raise Exception("Implement LZMA lol")
else:
with ZipFile(BytesIO(data)) as zf:
data = zf.read(zf.filelist[0])
return data
class ContentServer(object):
https = False
host = None
vhost = None
port = None
type = None
cell_id = 0
load = None
weighted_load = None
def __repr__(self):
return "<%s('%s://%s:%s', type=%s, cell_id=%s)>" % (
self.__class__.__name__,
'https' if self.https else 'http',
self.host,
self.port,
repr(self.type),
repr(self.cell_id),
)