diff --git a/protobufs/steammessages_contentsystem.proto b/protobufs/steammessages_contentsystem.proto index 3d6d000d..333c8bf0 100644 --- a/protobufs/steammessages_contentsystem.proto +++ b/protobufs/steammessages_contentsystem.proto @@ -68,6 +68,17 @@ message CContentServerDirectory_GetManifestRequestCode_Response { optional uint64 manifest_request_code = 1; } +message CContentServerDirectory_GetCDNAuthToken_Request { + optional uint32 depot_id = 1; + optional string host_name = 2; + optional uint32 app_id = 3; +} + +message CContentServerDirectory_GetCDNAuthToken_Response { + optional string token = 1; + optional uint32 expiration_time = 2; +} + service ContentServerDirectory { option (service_description) = "Content Server and CDN directory"; @@ -75,4 +86,5 @@ service ContentServerDirectory { rpc GetDepotPatchInfo (.CContentServerDirectory_GetDepotPatchInfo_Request) returns (.CContentServerDirectory_GetDepotPatchInfo_Response); rpc GetClientUpdateHosts (.CContentServerDirectory_GetClientUpdateHosts_Request) returns (.CContentServerDirectory_GetClientUpdateHosts_Response); rpc GetManifestRequestCode (.CContentServerDirectory_GetManifestRequestCode_Request) returns (.CContentServerDirectory_GetManifestRequestCode_Response); + rpc GetCDNAuthToken (.CContentServerDirectory_GetCDNAuthToken_Request) returns (.CContentServerDirectory_GetCDNAuthToken_Response); } diff --git a/requirements.txt b/requirements.txt index b5ec7b6f..dcff5333 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ cachetools>=3.0.0 gevent>=1.3.0 protobuf~=3.0 gevent-eventemitter~=2.1 +wsproto~=1.2.0 diff --git a/setup.py b/setup.py index af3fdbaa..fb673b4c 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ 'gevent>=1.3.0', 'protobuf~=3.0', 'gevent-eventemitter~=2.1', + 'wsproto~=1.2.0', ], } diff --git a/steam/client/__init__.py b/steam/client/__init__.py index ea691248..d77d25db 100644 --- a/steam/client/__init__.py +++ b/steam/client/__init__.py @@ -43,8 +43,8 @@ class SteamClient(CMClient, BuiltinBase): username = None #: username when logged on chat_mode = 2 #: chat mode (0=old chat, 2=new chat) - def __init__(self): - CMClient.__init__(self) + def __init__(self, protocol=CMClient.PROTOCOL_TCP): + CMClient.__init__(self, protocol=protocol) # register listners self.on(self.EVENT_DISCONNECTED, self._handle_disconnect) diff --git a/steam/client/builtins/friends.py b/steam/client/builtins/friends.py index 340d113c..6a02c1b1 100644 --- a/steam/client/builtins/friends.py +++ b/steam/client/builtins/friends.py @@ -1,6 +1,6 @@ import logging from eventemitter import EventEmitter -from steam.steamid import SteamID, intBase +from steam.steamid import SteamID from steam.enums import EResult, EFriendRelationship from steam.enums.emsg import EMsg from steam.core.msg import MsgProto @@ -156,7 +156,7 @@ def add(self, steamid_or_accountname_or_email): """ m = MsgProto(EMsg.ClientAddFriend) - if isinstance(steamid_or_accountname_or_email, (intBase, int)): + if isinstance(steamid_or_accountname_or_email, int): m.body.steamid_to_add = steamid_or_accountname_or_email elif isinstance(steamid_or_accountname_or_email, SteamUser): m.body.steamid_to_add = steamid_or_accountname_or_email.steam_id diff --git a/steam/client/builtins/leaderboards.py b/steam/client/builtins/leaderboards.py index 9fa28590..636feb57 100644 --- a/steam/client/builtins/leaderboards.py +++ b/steam/client/builtins/leaderboards.py @@ -5,7 +5,7 @@ from steam.core.msg import MsgProto from steam.enums import EResult, ELeaderboardDataRequest, ELeaderboardSortMethod, ELeaderboardDisplayType from steam.enums.emsg import EMsg -from steam.utils import _range, chunks +from steam.utils import chunks from steam.utils.throttle import ConstantRateLimit @@ -158,7 +158,7 @@ def __getitem__(self, x): entries = self.get_entries(start+1, stop) if isinstance(x, slice): - return [entries[i] for i in _range(0, len(entries), step)] + return [entries[i] for i in range(0, len(entries), step)] else: return entries[0] diff --git a/steam/client/cdn.py b/steam/client/cdn.py index da2f4776..003e8862 100644 --- a/steam/client/cdn.py +++ b/steam/client/cdn.py @@ -462,6 +462,7 @@ def __init__(self, client): self.cell_id = self.steam.cell_id self.web = make_requests_session() + self.cdn_auth_tokens = {} #: CDN authentication token self.depot_keys = {} #: depot decryption keys self.manifests = {} #: CDNDepotManifest instances self.app_depots = {} #: app depot info @@ -526,6 +527,51 @@ def get_content_server(self, rotate=False): self.servers.rotate(-1) return self.servers[0] + def get_cdn_auth_token(self, app_id, depot_id, hostname): + """Get CDN authentication token + + :param app_id: app id + :type app_id: :class:`int` + :param depot_id: depot id + :type depot_id: :class:`int` + :param hostname: cdn hostname + :type hostname: :class:`str` + :return: CDN authentication token + :rtype: str + """ + def update_cdn_auth_tokens(): + resp = self.steam.send_um_and_wait('ContentServerDirectory.GetCDNAuthToken#1', { + 'app_id': app_id, + 'depot_id': depot_id, + 'host_name': hostname + }, timeout=10) + + if resp is None or resp.header.eresult != EResult.OK: + if resp.header.eresult == EResult.Fail: + # no need authtoken? + pass + else: + raise SteamError(f"Failed to get CDNAuthToken for {app_id}, {depot_id}, {hostname}", + EResult.Timeout if resp is None else EResult(resp.header.eresult)) + + self.cdn_auth_tokens.update({app_id:{depot_id:{hostname: { + 'eresult': resp.header.eresult, + 'token': resp.body.token or '', + 'expiration_time': resp.body.expiration_time or 0 + }}}}) + + if app_id not in self.cdn_auth_tokens or \ + depot_id not in self.cdn_auth_tokens[app_id] or \ + hostname not in self.cdn_auth_tokens[app_id][depot_id]: + update_cdn_auth_tokens() + else: + if self.cdn_auth_tokens[app_id][depot_id][hostname]['eresult'] != EResult.OK: + pass + elif datetime.fromtimestamp(self.cdn_auth_tokens[app_id][depot_id][hostname]['expiration_time'] - 60) < datetime.now(): + update_cdn_auth_tokens() + + return self.cdn_auth_tokens[app_id][depot_id][hostname]['token'] + def get_depot_key(self, app_id, depot_id): """Get depot key, which is needed to decrypt files @@ -548,13 +594,17 @@ def get_depot_key(self, app_id, depot_id): return self.depot_keys[depot_id] - def cdn_cmd(self, command, args): + def cdn_cmd(self, command, args, app_id=None, depot_id=None): """Run CDN command request :param command: command name :type command: str :param args: args :type args: str + :param args: app_id: (optional) required for CDN authentication token + :type args: int + :param args: depot_id: (optional) required for CDN authentication token + :type args: int :returns: requests response :rtype: :class:`requests.Response` :raises SteamError: on error @@ -562,12 +612,13 @@ def cdn_cmd(self, command, args): server = self.get_content_server() while True: - url = "{}://{}:{}/{}/{}".format( + url = "{}://{}:{}/{}/{}{}".format( 'https' if server.https else 'http', server.host, server.port, command, args, + self.get_cdn_auth_token(app_id, depot_id, str(server.host)) ) try: @@ -598,7 +649,7 @@ def get_chunk(self, app_id, depot_id, chunk_id): :raises SteamError: error message """ if (depot_id, chunk_id) not in self._chunk_cache: - resp = self.cdn_cmd('depot', f'{depot_id}/chunk/{chunk_id}') + resp = self.cdn_cmd('depot', f'{depot_id}/chunk/{chunk_id}', app_id, depot_id) data = symmetric_decrypt(resp.content, self.get_depot_key(app_id, depot_id)) @@ -684,9 +735,9 @@ def get_manifest(self, app_id, depot_id, manifest_gid, decrypt=True, manifest_re """ if (app_id, depot_id, manifest_gid) not in self.manifests: if manifest_request_code: - resp = self.cdn_cmd('depot', f'{depot_id}/manifest/{manifest_gid}/5/{manifest_request_code}') + resp = self.cdn_cmd('depot', f'{depot_id}/manifest/{manifest_gid}/5/{manifest_request_code}', app_id, depot_id) else: - resp = self.cdn_cmd('depot', f'{depot_id}/manifest/{manifest_gid}/5') + resp = self.cdn_cmd('depot', f'{depot_id}/manifest/{manifest_gid}/5', app_id, depot_id) if resp.ok: manifest = self.DepotManifestClass(self, app_id, resp.content) @@ -776,6 +827,11 @@ def get_manifests(self, app_id, branch='public', password=None, filter_func=None def async_fetch_manifest( app_id, depot_id, manifest_gid, decrypt, depot_name, branch_name, branch_pass ): + if isinstance(manifest_gid, dict): + # For some depots, Steam has started returning a dict + # {"public": {"gid": GID, "size": ..., "download": ...}, ...} + # instead of a simple map {"public": GID, ...} + manifest_gid = manifest_gid['gid'] try: manifest_code = self.get_manifest_request_code( app_id, depot_id, int(manifest_gid), branch_name, branch_pass diff --git a/steam/core/cm.py b/steam/core/cm.py index 7eb5b50b..57887dc8 100644 --- a/steam/core/cm.py +++ b/steam/core/cm.py @@ -16,7 +16,7 @@ from steam.enums import EResult, EUniverse from steam.enums.emsg import EMsg from steam.core import crypto -from steam.core.connection import TCPConnection +from steam.core.connection import TCPConnection, WebsocketConnection from steam.core.msg import Msg, MsgProto from eventemitter import EventEmitter from steam.utils import ip4_from_int @@ -59,6 +59,7 @@ class CMClient(EventEmitter): PROTOCOL_TCP = 0 #: TCP protocol enum PROTOCOL_UDP = 1 #: UDP protocol enum + PROTOCOL_WEBSOCKET = 2 #: WEBSOCKET protocol enum verbose_debug = False #: print message connects in debug auto_discovery = True #: enables automatic CM discovery @@ -83,10 +84,12 @@ class CMClient(EventEmitter): def __init__(self, protocol=PROTOCOL_TCP): self.cm_servers = CMServerList() - if protocol == CMClient.PROTOCOL_TCP: + if protocol == CMClient.PROTOCOL_WEBSOCKET: + self.connection = WebsocketConnection() + elif protocol == CMClient.PROTOCOL_TCP: self.connection = TCPConnection() else: - raise ValueError("Only TCP is supported") + raise ValueError("Only Websocket and TCP are supported") self.on(EMsg.ChannelEncryptRequest, self.__handle_encrypt_request), self.on(EMsg.Multi, self.__handle_multi), @@ -132,8 +135,11 @@ def connect(self, retry=0, delay=0): self._connecting = False return False - if not self.cm_servers.bootstrap_from_webapi(): - self.cm_servers.bootstrap_from_dns() + if isinstance(self.connection, WebsocketConnection): + self.cm_servers.bootstrap_from_webapi(cmtype='websockets') + elif isinstance(self.connection, TCPConnection): + if not self.cm_servers.bootstrap_from_webapi(): + self.cm_servers.bootstrap_from_dns() for i, server_addr in enumerate(cycle(self.cm_servers), start=next(i)-1): if retry and i >= retry: @@ -154,6 +160,12 @@ def connect(self, retry=0, delay=0): self.current_server_addr = server_addr self.connected = True self.emit(self.EVENT_CONNECTED) + + # WebsocketConnection secures itself + if isinstance(self.connection, WebsocketConnection): + self.channel_secured = True + self.emit(self.EVENT_CHANNEL_SECURED) + self._recv_loop = gevent.spawn(self._recv_messages) self._connecting = False return True @@ -472,26 +484,33 @@ def bootstrap_from_dns(self): self._LOG.error("DNS boostrap: cm0.steampowered.com resolved no A records") return False - def bootstrap_from_webapi(self, cell_id=0): + def bootstrap_from_webapi(self, cell_id=0, cmtype='netfilter'): """ Fetches CM server list from WebAPI and replaces the current one :param cellid: cell id (0 = global) :type cellid: :class:`int` + :param cmtype: CM type filter + :type cellid: :class:`str` :return: booststrap success :rtype: :class:`bool` """ - self._LOG.debug("Attempting bootstrap via WebAPI") + self._LOG.debug("Attempting bootstrap via WebAPI for %s" % cmtype) from steam import webapi try: - resp = webapi.get('ISteamDirectory', 'GetCMList', 1, params={'cellid': cell_id, - 'http_timeout': 3}) + resp = webapi.get('ISteamDirectory', 'GetCMListForConnect', 1, + params={ + 'cellid': cell_id, + 'cmtype': cmtype, + 'http_timeout': 3 + } + ) except Exception as exp: self._LOG.error("WebAPI boostrap failed: %s" % str(exp)) return False - result = EResult(resp['response']['result']) + result = EResult(resp['response']['success']) if result != EResult.OK: self._LOG.error("GetCMList failed with %s" % repr(result)) @@ -500,8 +519,8 @@ def bootstrap_from_webapi(self, cell_id=0): serverlist = resp['response']['serverlist'] self._LOG.debug("Received %d servers from WebAPI" % len(serverlist)) - def str_to_tuple(serveraddr): - ip, port = serveraddr.split(':') + def str_to_tuple(serverinfo): + ip, port = serverinfo['endpoint'].split(':') return str(ip), int(port) self.clear() @@ -509,7 +528,7 @@ def str_to_tuple(serveraddr): self.merge_list(map(str_to_tuple, serverlist)) return True - + def __iter__(self): def cm_server_iter(): if not self.list: diff --git a/steam/core/connection.py b/steam/core/connection.py index 5ae18b95..277a1dd8 100644 --- a/steam/core/connection.py +++ b/steam/core/connection.py @@ -7,13 +7,16 @@ from gevent import event from gevent.select import select as gselect +import ssl +import certifi + +from wsproto import WSConnection, events as wsevents +from wsproto.connection import ConnectionType, ConnectionState + logger = logging.getLogger("Connection") class Connection: - MAGIC = b'VT01' - FMT = ' header_size: - message_length, magic = struct.unpack_from(Connection.FMT, buf) + message_length, magic = struct.unpack_from(TCPConnection.FMT, buf) - if magic != Connection.MAGIC: + if magic != TCPConnection.MAGIC: logger.debug("invalid magic, got %s" % repr(magic)) self.disconnect() return @@ -131,33 +169,112 @@ def _read_packets(self): self._readbuf = buf +class WebsocketConnection(Connection): + + def __init__(self): + super(WebsocketConnection, self).__init__() + self.ws = WSConnection(ConnectionType.CLIENT) + self.ssl_ctx = ssl.create_default_context(cafile=certifi.where()) + self.event_wsdisconnected = event.Event() + self._readbuf = b'' -class TCPConnection(Connection): def _new_socket(self): - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.raw_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) def _connect(self, server_addr): - self.socket.connect(server_addr) - def _read_data(self): - try: - return self.socket.recv(16384) - except OSError: - return '' + host, port = server_addr + + for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): + try: + # tcp socket + _, _, _, _, sa = res + self.raw_socket.connect(sa) + self.socket = self.ssl_ctx.wrap_socket(self.raw_socket, server_hostname=host) + # websocket + ws_host = ':'.join(map(str,server_addr)) + ws_send = self.ws.send(wsevents.Request(host=ws_host, target="/cmsocket/")) + self.socket.sendall(ws_send) + return + except OSError: + if self.socket is not None: + self.socket.close() + + def _writer_loop(self): + while True: + message = self.send_queue.get() + try: + logger.debug("sending message of length {}".format(len(message))) + self.socket.sendall(self.ws.send(wsevents.Message(data=message))) + except: + logger.debug("Connection error (writer).") + self.disconnect() + return + + def _reader_loop(self): + while True: + rlist, _, _ = gselect([self.socket], [], []) - def _write_data(self, data): - self.socket.sendall(data) + if self.socket in rlist: + try: + data = self.socket.recv(16384) + except OSError: + data = '' + + if not data: + logger.debug("Connection error (reader).") + # A receive of zero bytes indicates the TCP socket has been closed. We + # need to pass None to wsproto to update its internal state. + logger.debug("Received 0 bytes (connection closed)") + self.ws.receive_data(None) + # now disconnect + self.disconnect() + return + + logger.debug("Received {} bytes".format(len(data))) + self._readbuf += data + self.ws.receive_data(self._readbuf) + self._readbuf = b'' + self._handle_events() + + def _handle_events(self): + for event in self.ws.events(): + if isinstance(event, wsevents.AcceptConnection): + logger.debug("WebSocket negotiation complete. Connected.") + self.event_connected.set() + elif isinstance(event, wsevents.RejectConnection): + logger.debug("WebSocket connection was rejected. That's probably not good.") + elif isinstance(event, wsevents.TextMessage): + logger.debug("Received websocket text message of length: {}".format(len(event.data))) + elif isinstance(event, wsevents.BytesMessage): + logger.debug("Received websocket bytes message of length: {}".format(len(event.data))) + self.recv_queue.put(event.data) + elif isinstance(event, wsevents.Pong): + logger.debug("Received pong: {}".format(repr(event.payload))) + elif isinstance(event, wsevents.CloseConnection): + logger.debug('Connection closed: code={} reason={}'.format( + event.code, event.reason + )) + if self.ws.state == ConnectionState.REMOTE_CLOSING: + self.socket.send(self.ws.send(event.response())) + self.event_wsdisconnected.set() + else: + raise TypeError("Do not know how to handle event: {}".format((event))) + + def disconnect(self): + self.event_wsdisconnected.clear() + + # WebSocket closing handshake + if self.ws.state == ConnectionState.OPEN: + logger.debug("Disconnect called. Sending CloseConnection message.") + self.socket.sendall(self.ws.send(wsevents.CloseConnection(code=1000, reason="sample reason"))) + self.socket.shutdown(socket.SHUT_WR) + # wait for notification from _reader_loop that the closing response was received + self.event_wsdisconnected.wait() + + super(WebsocketConnection, self).disconnect() class UDPConnection(Connection): def _new_socket(self): self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - def _connect(self, server_addr): - pass - - def _read_data(self): - pass - - def _write_data(self, data): - pass diff --git a/steam/protobufs/steammessages_contentsystem_pb2.py b/steam/protobufs/steammessages_contentsystem_pb2.py index 58cb8062..f41c7ba3 100644 --- a/steam/protobufs/steammessages_contentsystem_pb2.py +++ b/steam/protobufs/steammessages_contentsystem_pb2.py @@ -22,7 +22,7 @@ syntax='proto2', serialized_options=b'\220\001\001', create_key=_descriptor._internal_create_key, - serialized_pb=b'\n!steammessages_contentsystem.proto\x1a\x18steammessages_base.proto\x1a steammessages_unified_base.proto\"\xb5\x02\n6CContentServerDirectory_GetServersForSteamPipe_Request\x12#\n\x07\x63\x65ll_id\x18\x01 \x01(\rB\x12\x82\xb5\x18\x0e\x63lient Cell ID\x12\x39\n\x0bmax_servers\x18\x02 \x01(\r:\x02\x32\x30\x42 \x82\xb5\x18\x1cmax servers in response list\x12*\n\x0bip_override\x18\x03 \x01(\tB\x15\x82\xb5\x18\x11\x63lient IP address\x12+\n\rlauncher_type\x18\x04 \x01(\x05:\x01\x30\x42\x11\x82\xb5\x18\rlauncher type\x12\x42\n\x0bipv6_public\x18\x05 \x01(\tB-\x82\xb5\x18)client public ipv6 address if it knows it\"\xdb\x02\n\"CContentServerDirectory_ServerInfo\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tsource_id\x18\x02 \x01(\x05\x12\x0f\n\x07\x63\x65ll_id\x18\x03 \x01(\x05\x12\x0c\n\x04load\x18\x04 \x01(\x05\x12\x15\n\rweighted_load\x18\x05 \x01(\x02\x12\"\n\x1anum_entries_in_client_list\x18\x06 \x01(\x05\x12\x18\n\x10steam_china_only\x18\x07 \x01(\x08\x12\x0c\n\x04host\x18\x08 \x01(\t\x12\r\n\x05vhost\x18\t \x01(\t\x12\x14\n\x0cuse_as_proxy\x18\n \x01(\x08\x12#\n\x1bproxy_request_path_template\x18\x0b \x01(\t\x12\x15\n\rhttps_support\x18\x0c \x01(\t\x12\x17\n\x0f\x61llowed_app_ids\x18\r \x03(\r\x12\x18\n\x10preferred_server\x18\x0e \x01(\x08\"o\n7CContentServerDirectory_GetServersForSteamPipe_Response\x12\x34\n\x07servers\x18\x01 \x03(\x0b\x32#.CContentServerDirectory_ServerInfo\"\x89\x01\n1CContentServerDirectory_GetDepotPatchInfo_Request\x12\r\n\x05\x61ppid\x18\x01 \x01(\r\x12\x0f\n\x07\x64\x65potid\x18\x02 \x01(\r\x12\x19\n\x11source_manifestid\x18\x03 \x01(\x04\x12\x19\n\x11target_manifestid\x18\x04 \x01(\x04\"{\n2CContentServerDirectory_GetDepotPatchInfo_Response\x12\x14\n\x0cis_available\x18\x01 \x01(\x08\x12\x12\n\npatch_size\x18\x02 \x01(\x04\x12\x1b\n\x13patched_chunks_size\x18\x03 \x01(\x04\"P\n4CContentServerDirectory_GetClientUpdateHosts_Request\x12\x18\n\x10\x63\x61\x63hed_signature\x18\x01 \x01(\t\"w\n5CContentServerDirectory_GetClientUpdateHosts_Response\x12\x10\n\x08hosts_kv\x18\x01 \x01(\t\x12\x18\n\x10valid_until_time\x18\x02 \x01(\x04\x12\x12\n\nip_country\x18\x03 \x01(\t\"\xa1\x01\n6CContentServerDirectory_GetManifestRequestCode_Request\x12\x0e\n\x06\x61pp_id\x18\x01 \x01(\r\x12\x10\n\x08\x64\x65pot_id\x18\x02 \x01(\r\x12\x13\n\x0bmanifest_id\x18\x03 \x01(\x04\x12\x12\n\napp_branch\x18\x04 \x01(\t\x12\x1c\n\x14\x62ranch_password_hash\x18\x05 \x01(\t\"X\n7CContentServerDirectory_GetManifestRequestCode_Response\x12\x1d\n\x15manifest_request_code\x18\x01 \x01(\x04\x32\xe0\x04\n\x16\x43ontentServerDirectory\x12\x8b\x01\n\x16GetServersForSteamPipe\x12\x37.CContentServerDirectory_GetServersForSteamPipe_Request\x1a\x38.CContentServerDirectory_GetServersForSteamPipe_Response\x12|\n\x11GetDepotPatchInfo\x12\x32.CContentServerDirectory_GetDepotPatchInfo_Request\x1a\x33.CContentServerDirectory_GetDepotPatchInfo_Response\x12\x85\x01\n\x14GetClientUpdateHosts\x12\x35.CContentServerDirectory_GetClientUpdateHosts_Request\x1a\x36.CContentServerDirectory_GetClientUpdateHosts_Response\x12\x8b\x01\n\x16GetManifestRequestCode\x12\x37.CContentServerDirectory_GetManifestRequestCode_Request\x1a\x38.CContentServerDirectory_GetManifestRequestCode_Response\x1a$\x82\xb5\x18 Content Server and CDN directoryB\x03\x90\x01\x01' + serialized_pb=b'\n!steammessages_contentsystem.proto\x1a\x18steammessages_base.proto\x1a steammessages_unified_base.proto\"\xb5\x02\n6CContentServerDirectory_GetServersForSteamPipe_Request\x12#\n\x07\x63\x65ll_id\x18\x01 \x01(\rB\x12\x82\xb5\x18\x0e\x63lient Cell ID\x12\x39\n\x0bmax_servers\x18\x02 \x01(\r:\x02\x32\x30\x42 \x82\xb5\x18\x1cmax servers in response list\x12*\n\x0bip_override\x18\x03 \x01(\tB\x15\x82\xb5\x18\x11\x63lient IP address\x12+\n\rlauncher_type\x18\x04 \x01(\x05:\x01\x30\x42\x11\x82\xb5\x18\rlauncher type\x12\x42\n\x0bipv6_public\x18\x05 \x01(\tB-\x82\xb5\x18)client public ipv6 address if it knows it\"\xdb\x02\n\"CContentServerDirectory_ServerInfo\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x11\n\tsource_id\x18\x02 \x01(\x05\x12\x0f\n\x07\x63\x65ll_id\x18\x03 \x01(\x05\x12\x0c\n\x04load\x18\x04 \x01(\x05\x12\x15\n\rweighted_load\x18\x05 \x01(\x02\x12\"\n\x1anum_entries_in_client_list\x18\x06 \x01(\x05\x12\x18\n\x10steam_china_only\x18\x07 \x01(\x08\x12\x0c\n\x04host\x18\x08 \x01(\t\x12\r\n\x05vhost\x18\t \x01(\t\x12\x14\n\x0cuse_as_proxy\x18\n \x01(\x08\x12#\n\x1bproxy_request_path_template\x18\x0b \x01(\t\x12\x15\n\rhttps_support\x18\x0c \x01(\t\x12\x17\n\x0f\x61llowed_app_ids\x18\r \x03(\r\x12\x18\n\x10preferred_server\x18\x0e \x01(\x08\"o\n7CContentServerDirectory_GetServersForSteamPipe_Response\x12\x34\n\x07servers\x18\x01 \x03(\x0b\x32#.CContentServerDirectory_ServerInfo\"\x89\x01\n1CContentServerDirectory_GetDepotPatchInfo_Request\x12\r\n\x05\x61ppid\x18\x01 \x01(\r\x12\x0f\n\x07\x64\x65potid\x18\x02 \x01(\r\x12\x19\n\x11source_manifestid\x18\x03 \x01(\x04\x12\x19\n\x11target_manifestid\x18\x04 \x01(\x04\"{\n2CContentServerDirectory_GetDepotPatchInfo_Response\x12\x14\n\x0cis_available\x18\x01 \x01(\x08\x12\x12\n\npatch_size\x18\x02 \x01(\x04\x12\x1b\n\x13patched_chunks_size\x18\x03 \x01(\x04\"P\n4CContentServerDirectory_GetClientUpdateHosts_Request\x12\x18\n\x10\x63\x61\x63hed_signature\x18\x01 \x01(\t\"w\n5CContentServerDirectory_GetClientUpdateHosts_Response\x12\x10\n\x08hosts_kv\x18\x01 \x01(\t\x12\x18\n\x10valid_until_time\x18\x02 \x01(\x04\x12\x12\n\nip_country\x18\x03 \x01(\t\"\xa1\x01\n6CContentServerDirectory_GetManifestRequestCode_Request\x12\x0e\n\x06\x61pp_id\x18\x01 \x01(\r\x12\x10\n\x08\x64\x65pot_id\x18\x02 \x01(\r\x12\x13\n\x0bmanifest_id\x18\x03 \x01(\x04\x12\x12\n\napp_branch\x18\x04 \x01(\t\x12\x1c\n\x14\x62ranch_password_hash\x18\x05 \x01(\t\"X\n7CContentServerDirectory_GetManifestRequestCode_Response\x12\x1d\n\x15manifest_request_code\x18\x01 \x01(\x04\"f\n/CContentServerDirectory_GetCDNAuthToken_Request\x12\x10\n\x08\x64\x65pot_id\x18\x01 \x01(\r\x12\x11\n\thost_name\x18\x02 \x01(\t\x12\x0e\n\x06\x61pp_id\x18\x03 \x01(\r\"Z\n0CContentServerDirectory_GetCDNAuthToken_Response\x12\r\n\x05token\x18\x01 \x01(\t\x12\x17\n\x0f\x65xpiration_time\x18\x02 \x01(\r2\xd8\x05\n\x16\x43ontentServerDirectory\x12\x8b\x01\n\x16GetServersForSteamPipe\x12\x37.CContentServerDirectory_GetServersForSteamPipe_Request\x1a\x38.CContentServerDirectory_GetServersForSteamPipe_Response\x12|\n\x11GetDepotPatchInfo\x12\x32.CContentServerDirectory_GetDepotPatchInfo_Request\x1a\x33.CContentServerDirectory_GetDepotPatchInfo_Response\x12\x85\x01\n\x14GetClientUpdateHosts\x12\x35.CContentServerDirectory_GetClientUpdateHosts_Request\x1a\x36.CContentServerDirectory_GetClientUpdateHosts_Response\x12\x8b\x01\n\x16GetManifestRequestCode\x12\x37.CContentServerDirectory_GetManifestRequestCode_Request\x1a\x38.CContentServerDirectory_GetManifestRequestCode_Response\x12v\n\x0fGetCDNAuthToken\x12\x30.CContentServerDirectory_GetCDNAuthToken_Request\x1a\x31.CContentServerDirectory_GetCDNAuthToken_Response\x1a$\x82\xb5\x18 Content Server and CDN directoryB\x03\x90\x01\x01' , dependencies=[steammessages__base__pb2.DESCRIPTOR,steammessages__unified__base__pb2.DESCRIPTOR,]) @@ -512,6 +512,91 @@ serialized_end=1592, ) + +_CCONTENTSERVERDIRECTORY_GETCDNAUTHTOKEN_REQUEST = _descriptor.Descriptor( + name='CContentServerDirectory_GetCDNAuthToken_Request', + full_name='CContentServerDirectory_GetCDNAuthToken_Request', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='depot_id', full_name='CContentServerDirectory_GetCDNAuthToken_Request.depot_id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='host_name', full_name='CContentServerDirectory_GetCDNAuthToken_Request.host_name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='app_id', full_name='CContentServerDirectory_GetCDNAuthToken_Request.app_id', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1594, + serialized_end=1696, +) + + +_CCONTENTSERVERDIRECTORY_GETCDNAUTHTOKEN_RESPONSE = _descriptor.Descriptor( + name='CContentServerDirectory_GetCDNAuthToken_Response', + full_name='CContentServerDirectory_GetCDNAuthToken_Response', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='token', full_name='CContentServerDirectory_GetCDNAuthToken_Response.token', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='expiration_time', full_name='CContentServerDirectory_GetCDNAuthToken_Response.expiration_time', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1698, + serialized_end=1788, +) + _CCONTENTSERVERDIRECTORY_GETSERVERSFORSTEAMPIPE_RESPONSE.fields_by_name['servers'].message_type = _CCONTENTSERVERDIRECTORY_SERVERINFO DESCRIPTOR.message_types_by_name['CContentServerDirectory_GetServersForSteamPipe_Request'] = _CCONTENTSERVERDIRECTORY_GETSERVERSFORSTEAMPIPE_REQUEST DESCRIPTOR.message_types_by_name['CContentServerDirectory_ServerInfo'] = _CCONTENTSERVERDIRECTORY_SERVERINFO @@ -522,6 +607,8 @@ DESCRIPTOR.message_types_by_name['CContentServerDirectory_GetClientUpdateHosts_Response'] = _CCONTENTSERVERDIRECTORY_GETCLIENTUPDATEHOSTS_RESPONSE DESCRIPTOR.message_types_by_name['CContentServerDirectory_GetManifestRequestCode_Request'] = _CCONTENTSERVERDIRECTORY_GETMANIFESTREQUESTCODE_REQUEST DESCRIPTOR.message_types_by_name['CContentServerDirectory_GetManifestRequestCode_Response'] = _CCONTENTSERVERDIRECTORY_GETMANIFESTREQUESTCODE_RESPONSE +DESCRIPTOR.message_types_by_name['CContentServerDirectory_GetCDNAuthToken_Request'] = _CCONTENTSERVERDIRECTORY_GETCDNAUTHTOKEN_REQUEST +DESCRIPTOR.message_types_by_name['CContentServerDirectory_GetCDNAuthToken_Response'] = _CCONTENTSERVERDIRECTORY_GETCDNAUTHTOKEN_RESPONSE _sym_db.RegisterFileDescriptor(DESCRIPTOR) CContentServerDirectory_GetServersForSteamPipe_Request = _reflection.GeneratedProtocolMessageType('CContentServerDirectory_GetServersForSteamPipe_Request', (_message.Message,), { @@ -587,6 +674,20 @@ }) _sym_db.RegisterMessage(CContentServerDirectory_GetManifestRequestCode_Response) +CContentServerDirectory_GetCDNAuthToken_Request = _reflection.GeneratedProtocolMessageType('CContentServerDirectory_GetCDNAuthToken_Request', (_message.Message,), { + 'DESCRIPTOR' : _CCONTENTSERVERDIRECTORY_GETCDNAUTHTOKEN_REQUEST, + '__module__' : 'steammessages_contentsystem_pb2' + # @@protoc_insertion_point(class_scope:CContentServerDirectory_GetCDNAuthToken_Request) + }) +_sym_db.RegisterMessage(CContentServerDirectory_GetCDNAuthToken_Request) + +CContentServerDirectory_GetCDNAuthToken_Response = _reflection.GeneratedProtocolMessageType('CContentServerDirectory_GetCDNAuthToken_Response', (_message.Message,), { + 'DESCRIPTOR' : _CCONTENTSERVERDIRECTORY_GETCDNAUTHTOKEN_RESPONSE, + '__module__' : 'steammessages_contentsystem_pb2' + # @@protoc_insertion_point(class_scope:CContentServerDirectory_GetCDNAuthToken_Response) + }) +_sym_db.RegisterMessage(CContentServerDirectory_GetCDNAuthToken_Response) + DESCRIPTOR._options = None _CCONTENTSERVERDIRECTORY_GETSERVERSFORSTEAMPIPE_REQUEST.fields_by_name['cell_id']._options = None @@ -602,8 +703,8 @@ index=0, serialized_options=b'\202\265\030 Content Server and CDN directory', create_key=_descriptor._internal_create_key, - serialized_start=1595, - serialized_end=2203, + serialized_start=1791, + serialized_end=2519, methods=[ _descriptor.MethodDescriptor( name='GetServersForSteamPipe', @@ -645,6 +746,16 @@ serialized_options=None, create_key=_descriptor._internal_create_key, ), + _descriptor.MethodDescriptor( + name='GetCDNAuthToken', + full_name='ContentServerDirectory.GetCDNAuthToken', + index=4, + containing_service=None, + input_type=_CCONTENTSERVERDIRECTORY_GETCDNAUTHTOKEN_REQUEST, + output_type=_CCONTENTSERVERDIRECTORY_GETCDNAUTHTOKEN_RESPONSE, + serialized_options=None, + create_key=_descriptor._internal_create_key, + ), ]) _sym_db.RegisterServiceDescriptor(_CONTENTSERVERDIRECTORY)