@@ -462,6 +462,7 @@ def __init__(self, client):
462462 self .cell_id = self .steam .cell_id
463463
464464 self .web = make_requests_session ()
465+ self .cdn_auth_tokens = {} #: CDN authentication token
465466 self .depot_keys = {} #: depot decryption keys
466467 self .manifests = {} #: CDNDepotManifest instances
467468 self .app_depots = {} #: app depot info
@@ -526,6 +527,51 @@ def get_content_server(self, rotate=False):
526527 self .servers .rotate (- 1 )
527528 return self .servers [0 ]
528529
530+ def get_cdn_auth_token (self , app_id , depot_id , hostname ):
531+ """Get CDN authentication token
532+
533+ :param app_id: app id
534+ :type app_id: :class:`int`
535+ :param depot_id: depot id
536+ :type depot_id: :class:`int`
537+ :param hostname: cdn hostname
538+ :type hostname: :class:`str`
539+ :return: CDN authentication token
540+ :rtype: str
541+ """
542+ def update_cdn_auth_tokens ():
543+ resp = self .steam .send_um_and_wait ('ContentServerDirectory.GetCDNAuthToken#1' , {
544+ 'app_id' : app_id ,
545+ 'depot_id' : depot_id ,
546+ 'host_name' : hostname
547+ }, timeout = 10 )
548+
549+ if resp is None or resp .header .eresult != EResult .OK :
550+ if resp .header .eresult == EResult .Fail :
551+ # no need authtoken?
552+ pass
553+ else :
554+ raise SteamError (f"Failed to get CDNAuthToken for { app_id } , { depot_id } , { hostname } " ,
555+ EResult .Timeout if resp is None else EResult (resp .header .eresult ))
556+
557+ self .cdn_auth_tokens .update ({app_id :{depot_id :{hostname : {
558+ 'eresult' : resp .header .eresult ,
559+ 'token' : resp .body .token or '' ,
560+ 'expiration_time' : resp .body .expiration_time or 0
561+ }}}})
562+
563+ if app_id not in self .cdn_auth_tokens or \
564+ depot_id not in self .cdn_auth_tokens [app_id ] or \
565+ hostname not in self .cdn_auth_tokens [app_id ][depot_id ]:
566+ update_cdn_auth_tokens ()
567+ else :
568+ if self .cdn_auth_tokens [app_id ][depot_id ][hostname ]['eresult' ] != EResult .OK :
569+ pass
570+ elif datetime .fromtimestamp (self .cdn_auth_tokens [app_id ][depot_id ][hostname ]['expiration_time' ] - 60 ) < datetime .now ():
571+ update_cdn_auth_tokens ()
572+
573+ return self .cdn_auth_tokens [app_id ][depot_id ][hostname ]['token' ]
574+
529575 def get_depot_key (self , app_id , depot_id ):
530576 """Get depot key, which is needed to decrypt files
531577
@@ -548,26 +594,31 @@ def get_depot_key(self, app_id, depot_id):
548594
549595 return self .depot_keys [depot_id ]
550596
551- def cdn_cmd (self , command , args ):
597+ def cdn_cmd (self , command , args , app_id = None , depot_id = None ):
552598 """Run CDN command request
553599
554600 :param command: command name
555601 :type command: str
556602 :param args: args
557603 :type args: str
604+ :param args: app_id: (optional) required for CDN authentication token
605+ :type args: int
606+ :param args: depot_id: (optional) required for CDN authentication token
607+ :type args: int
558608 :returns: requests response
559609 :rtype: :class:`requests.Response`
560610 :raises SteamError: on error
561611 """
562612 server = self .get_content_server ()
563613
564614 while True :
565- url = "{}://{}:{}/{}/{}" .format (
615+ url = "{}://{}:{}/{}/{}{} " .format (
566616 'https' if server .https else 'http' ,
567617 server .host ,
568618 server .port ,
569619 command ,
570620 args ,
621+ self .get_cdn_auth_token (app_id , depot_id , str (server .host ))
571622 )
572623
573624 try :
@@ -598,7 +649,7 @@ def get_chunk(self, app_id, depot_id, chunk_id):
598649 :raises SteamError: error message
599650 """
600651 if (depot_id , chunk_id ) not in self ._chunk_cache :
601- resp = self .cdn_cmd ('depot' , f'{ depot_id } /chunk/{ chunk_id } ' )
652+ resp = self .cdn_cmd ('depot' , f'{ depot_id } /chunk/{ chunk_id } ' , app_id , depot_id )
602653
603654 data = symmetric_decrypt (resp .content , self .get_depot_key (app_id , depot_id ))
604655
@@ -684,9 +735,9 @@ def get_manifest(self, app_id, depot_id, manifest_gid, decrypt=True, manifest_re
684735 """
685736 if (app_id , depot_id , manifest_gid ) not in self .manifests :
686737 if manifest_request_code :
687- resp = self .cdn_cmd ('depot' , f'{ depot_id } /manifest/{ manifest_gid } /5/{ manifest_request_code } ' )
738+ resp = self .cdn_cmd ('depot' , f'{ depot_id } /manifest/{ manifest_gid } /5/{ manifest_request_code } ' , app_id , depot_id )
688739 else :
689- resp = self .cdn_cmd ('depot' , f'{ depot_id } /manifest/{ manifest_gid } /5' )
740+ resp = self .cdn_cmd ('depot' , f'{ depot_id } /manifest/{ manifest_gid } /5' , app_id , depot_id )
690741
691742 if resp .ok :
692743 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
776827 def async_fetch_manifest (
777828 app_id , depot_id , manifest_gid , decrypt , depot_name , branch_name , branch_pass
778829 ):
830+ if isinstance (manifest_gid , dict ):
831+ # For some depots, Steam has started returning a dict
832+ # {"public": {"gid": GID, "size": ..., "download": ...}, ...}
833+ # instead of a simple map {"public": GID, ...}
834+ manifest_gid = manifest_gid ['gid' ]
779835 try :
780836 manifest_code = self .get_manifest_request_code (
781837 app_id , depot_id , int (manifest_gid ), branch_name , branch_pass
0 commit comments