pythonhacktoberfeststeamauthenticationauthenticatorsteam-authenticatorsteam-clientsteam-guard-codessteam-websteamworksvalvewebapi
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
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),
|
|
)
|
|
|
|
|
|
|
|
|