summaryrefslogblamecommitdiff
path: root/game/update.rpy
blob: b3146297bc2801fe051972a47360503140c86292 (plain) (tree)
1
2
3
4
5
6
7
8



                                                                                 
                                                                


                                                                                 





                                      
                        
 














                                                        

                                        







                                                                          


                              


                                                        
                
                                                                        
                                                         
                                              




                                                                    
           


                 

                                











                                                                                                      







                                                                                                                                                                 
                                                                      












                                                                        

                                                                                 
                             
                                                                              
                                                 
                                                                                 





                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
                                                                           

                                                                                 



                                                               
                                                                     













                                                                                                                                                                                   
                                                                                  










                                                                          
                                                                                                    
                           
 


                                                          

                                                                  
                         
                       

                                                                                       
 
                                                 
                                         
                                                         
                                                                                                                                 

                                

                                         
 


                                                                                 
                                                                 
 
                         



                           
                              
                           
                                                                                                                    

                  
 
                            
 
                        
 


                                                                                 
                                                        
            

                                                            






                                                                               
                                                                      

                                                         
 
                                                                          
                                              
                                                                              
                            
                                                              

                                                          
                                                                             
 

                                         
                                                                                                                                                 


                                
                                                           
                                                              
                                                                         
                               
                                                                         
                               
                                                                        
                               
                                                                     
                                                                                 
 
                                                          
                                      

                                                                         
                                      
                                                              
                                                                                                    

                                      
                                       
                                   


                                                                 

                                                                  
                                                                          

                                                                  
               
                                                                               




                                                                                                                                                         
 



                                                                            
                         









                                                                                 
 
 
                             
              
 


































                                                                                                                               
 











                                                                                 
                                                                                                                                                                                                                                                                                          





                                      

                                                          
 
                                       



















                                                                            

                              
 


















                                                                            
               
                            
 



                                                                                          
                                                                                      



                                                                                                                           







                                                              



                                                                                                                                      




                                                                                                     
                                     
                  

                  

                                   
                                      

                                      
 
                          
                           



                           

                      
                                                                         
                            



                                                                              
                          


                                              
               
                   

                                
                                                                                                                               

                                                                            

                              
                                   
                                                                                                                                                                                                           
                               
             
                                                                                        



                                   
                                     
                  
                                                            


                                    



                                                                                                                                      


                                  






                                                                                                   
                                   


                                                  
 








                                                                   

                     


                
                
               
               
                                           
         
                                                                  

          

                                                                                 
                                                                                                                                                                                                                                                                       













                                                                                                                         







                                      
             





                             
                   

          
#################################################################################
#     This file is part of Mana Launcher.
#     Copyright (C) 2021  Jesusalva <jesusalva@tmw2.org>
#
#     Distributed under the MIT license, except for Steam parts.
#################################################################################

init python:
    def status_update(smsg="", pc=-1):
        global progress, statusmsg
        if pc > 0 and pc != progress:
            progress=min(pc, 100)
        if (smsg != ""):
            statusmsg=smsg
            stdout(smsg)

    def validate_server(srv):
        name="Unknown Server"; ok=False
        try:
            name=srv["Name"]
            stdout("Validating server \"%s\"..." % name)
            print("Host: %s" % srv["Host"])
            print("Port: %04d" % srv["Port"])
            print("Desc: %s" % srv["Desc"])
            print("Link: %s" % srv["Link"])
            print("News: %s" % srv["News"])
            print("Back: %s" % srv["Back"])
            print("UUID: %s" % srv["UUID"])
            print("Help: %s" % srv["Help"])
            print("Online List: %s" % srv["Online"])
            print("Policy: %s" % srv["Policy"])
            if not "Type" in srv.keys():
                srv["Type"]="evol2"
            stdout("Server \"%s\" is valid! Downloading assets..." % name)
            ok=True
        except:
            traceback.print_exc()
            stdout("Validation for server \"%s\" FAILED!" % name)

        ## Fetch server background (optional) if successful
        if (ok):
            srv["onlcnt"] = -1
            srv["onlist"] = []
        """
            bgname="bg_%s.png" % (name.replace(" ", ""))
            if os.path.exists(get_path(bgname)):
                return ok
            try:
                stdout("Fetching background for server \"%s\"" % bgname)
                r=requests.get(srv["Back"], timeout=10.0)
                with open(bgname, 'wb') as fd:
                    for chunk in r.iter_content(chunk_size=128):
                        fd.write(chunk)
            except:
                traceback.print_exc()
                stdout("Background error for server \"%s\"." % name)
        """

        return ok

    def update_serverlist(host):
        try:
            r=None
            ## Attempt for a localized version first
            if _preferences.language is not None:
                r=requests.get("%s/server_list.%s.json" % (host, _preferences.language), timeout=10.0)

                if (r.status_code == 404):
                    stdout("No server list for language: %s" % _preferences.language)
                    r=None

            ## Obtain regular version if localized version failed/skipped
            if r is None:
                r=requests.get("%s/server_list.json" % host, timeout=10.0)

            if (r.status_code != 200):
                raise AssertionError("Mirror %s seems to be down!\nReturned error %03d\n" % (host.replace("https://", "").replace("http://", ""), r.status_code))

            j=json.loads(r.text)

            ## If we reached here, then everything is likely fine
            ## We can now build the persistent data
            status_update(_("Fetching server list and assets..."), 20)
            persistent.serverlist = []
            slist=len(j)
            for server in j:
                if (validate_server(server)):
                    persistent.serverlist.append(server)
                status_update(pc=int(10+(70.0/slist)))

            ## If we were truly successful, save this host as our mirror
            persistent.host = host
            return True
        except:
            traceback.print_exc()
            return False

    #############################################################################
    def CONFIGURE_LAUNCHER():
        global progress, statusmsg, responsive, has_steam, vaultId, vaultToken
        statusmsg="Loading user configuration..."
        #########################################################################
        ## Strictly for debugging purposes, skip everything
        if USE_DUMMY_DATA:
            if persistent.host is None:
                persistent.host=HOST_LIST[0]
            if persistent.serverlist is None or persistent.serverlist == []:
                persistent.serverlist=[{"Name": "Moubootaur Legends", "Host": "server.tmw2.org", "Port": 6901, "Desc": "Ready to become a Moubootaur Legend now?\n\nIn Moubootaur Legends universe, a being known as the Monster King threatens humanity, sending hordes of monsters and causing havoc for purposes unknown for anyone. And yet, in the shadows, a greater threat looms over the horizon, waiting for the time of its ressurection...\nTake arms, for heroes are not born but forged with sweat and effort, and become a Moubootaur Legend now.", "Link": "https://moubootaurlegends.org/", "News": "https://updates.tmw2.org/news.txt", "Back": "tmw2", "UUID": "5936870052ae411c8f0271907f8cf2e4", "Help": "https://discord.gg/J4gcaqM", "Online": "https://tmw2.org/online.json", "Policy": "https://tmw2.org/legal"}]
            vaultId = 99; vaultToken = "token"; vaultOTP=""; progress = 100
            return
        #########################################################################
        # If persistent data is not yet set, it must be created
        # This block is 1~60%
        # But first we check for updates
        if persistent.host is not None:
            stdout("Fetching data from %s\n" % persistent.host, True)
            retry=3
            while retry > 0:
                try:
                    retry-=1
                    rv=requests.get("%s/version.txt" % persistent.host, timeout=10.0)

                    if (rv.status_code != 200):
                        raise AssertionError("Mirror %s seems to be down!\nReturned error %03d" % (persistent.host.replace("https://", "").replace("http://", ""), rv.status_code))

                    # Everything went fine! Update server list if needed
                    if (persistent.version != rv.text):
                        if not update_serverlist(persistent.host):
                            raise Exception("Mirror serverlist error")
                        persistent.version=rv.text
                        stdout("Version updated to %s" % persistent.version, True)
                    retry=0
                except:
                    traceback.print_exc()
                    if retry < 1:
                        persistent.host=None
                        stdout("Too many failures, seeking a new mirror.")

        ## Either our original mirror died, or it was never a thing
        ## Maybe you're offline but in any case:
        ## Seek a new mirror and update server list accordingly.
        if persistent.host is None:
            status_update(_("Welcome to the Mana Launcher.\nBuilding user data, please wait..."), 1)
            time.sleep(1.0)

            # Determine from where we should fetch updates
            for host in HOST_LIST:
                try:
                    if not update_serverlist(host):
                        raise Exception("Mirror serverlist error")
                    break
                except:
                    stdout("An error happened on mirror \"%s\"." % host.split("//")[1])
                    stdout("\nTrying next mirror...\n")

            ## If we don't have a host - DIE HARD
            if (persistent.host is None):
                stdout("No mirror could be found.", True)
                status_update(_("{color=#f00}{b}ERROR: No active mirror found.\nMana Launcher needs to be updated.{/b}{/color}"))
                responsive=False
                return

            ## Everything was successful!


        #########################################################################
        # Check for clients, this block is 60~80%
        status_update(_("Checking for installed clients..."), 60)

        r=handle_client()

        ## Oh sh--
        if not r:
            time.sleep(0.4)
            responsive = False
            time.sleep(2.6)
            status_update(_("{color=#F00}ERROR: \"%s\" client is NOT supported!{/color}" % persistent.evol2cli), 70)
            return


        status_update(pc=70)

        #time.sleep(0.1)

        #########################################################################
        # Before starting, we must check for Vault or Steam credentials
        # This block is 1~20%
        status_update(_("Verifying credentials..."), 80)
        try:
            if not persistent.steam:
                raise Exception("Steam login was disabled!")
            if steam is None:
                raise Exception("Steam Status was disabled!")
            #if steam.periodic is None:
            #    raise Exception("Steam is crazy!")
            #assert config.periodic_callbacks.index(steam.periodic) is not None
            #if sys.modules["_renpysteam"] is None:
            #    raise Exception("Steam is not running!")
            status_update(_("Attempting Steam authentication..."), 81)
            accId = steam.get_account_id()
            stdout("Steam login active, user %d" % accId)

            ## Retrieve new ticket ("token"), send it in Base64 to the API
            token = steam.get_session_ticket()
            auth = {"accId": accId, "token": base64.b64encode(token).decode()}
            time.sleep(0.75)
            status_update(_("Waiting for Vault reply..."), 85)

            ## Request the Vault for the ticket validation
            r = vault.post(VAULT_HOST+"/steam_auth", json=auth, timeout=15.0)

            ## Ticket error, we die here.
            if (r.status_code == 406):
                status_update(_("{color=#f00}{b}STEAM AUTHENTICATION ERROR.\nSteam refused authentication. Try restarting the app.{/b}{/color}"))
                responsive=False
                return

            ## Intercept rate-limiting (429) and retry once
            if (r.status_code == 429 or r.status_code == 500):
                status_update(_("Rate limited! Trying again 15s..."), 85)
                time.sleep(5.0)
                status_update(_("Rate limited! Trying again 10s..."), 85)
                time.sleep(5.0)
                status_update(_("Rate limited! Trying again 5s..."), 85)
                time.sleep(5.0)
                status_update(_("Rate limited! Trying again..."), 85)
                r = vault.post(VAULT_HOST+"/steam_auth", json=auth, timeout=15.0)

            ## If still unsucessful, give up on Steam Auth
            if (r.status_code != 200):
                raise Exception("Vault returned code %d" % r.status_code)

            ## Receive the Vault Token
            status_update(_("Waiting for Vault reply..."), 90)
            stdout("Steam result: (%d) %s" % (r.status_code, ifte(config.developer, r.text, accId)))
            auth2 = r.json()
            vaultId = auth2["vaultId"]
            vaultToken = auth2["token"]
            vaultOTP = auth2["otp"]

            # If everything went well, inform Steam support is ON
            # Enable all Steam features and skip Vault-only auth
            has_steam = True
            stdout("Steam session initialized successfully", True)
            status_update(_("Steam session initialized successfully"), 99)
            renpy.notify("Welcome, %s" % steam.get_persona_name())
            time.sleep(0.25)
        except:
            # NO FALLBACK: if Steam Login is on, do not try vault (no multiacc)
            if persistent.steam:
                traceback.print_exc()
                status_update(_("{color=#f00}{b}STEAM AUTHENTICATION ERROR.\nIf the issue persists, try closing the app and opening again.{/b}{/color}"))
                responsive=False
                return

            # Prepare to do Vault authentication
            status_update(_("Steam auth disabled, logging on Vault..."), 80)
            vaultId = 0
            vaultToken = "MANUAL"
            vaultOTP = ""
            #########################################
            ####### TODO FIXME
            # Must return and let a prompt for username & password
            # (Or Email, in the case of the modern Vault)
            # If vaultId is zero
            #status_update("{color=#F00}VaultError: Not yet implemented{/color}")
            #responsive = False
            #return
            ####### TODO FIXME
            #########################################


        status_update(pc=100)
        return

    def ONLINE_LISTING():
        global running, responsive
        while (running and responsive):
            for s in persistent.serverlist:
                try:
                    r=requests.get("%s" % s["Online"], timeout=5.0)

                    if (r.status_code != 200):
                        raise AssertionError("Online list for %s is down!\nReturned error %03d\n" % (s["Name"], r.status_code))

                    j=json.loads(r.text)
                    s["onlcnt"]=len(j)
                    s["onlist"]=list(j)
                except:
                    s["onlcnt"]=-1
                    s["onlist"]=[]
                    pass
            time.sleep(60)
        return

    def onl_cnt(ent):
        if ent["onlcnt"] < 0:
            return "?"
        else:
            return "%d" % ent["onlcnt"]


    def onl_list(ent):
        s=""; i=0; l=len(ent["onlist"])
        while i < l:
            s+=str(ent["onlist"][i])
            i+=1
            if (i < l):
                s+=", "
        return s

#################################################################################
screen register_method():
    ## Ensure other screens do not get input while this screen is displayed.
    modal True
    zorder 200
    style_prefix "confirm"
    add "gui/overlay/confirm.png"
    frame:
        vbox:
            xalign .5
            yalign .5
            spacing 30
            label _("{b}Vault Authentication{/b}\n\nWhich method do you want to use to login on TMW Vault?\n{size=14}An account will be automatically created if it does not exists already. This implies accepting the {a=%s}Terms of Service.{/a}{/size}" % ("https://tmw2.org/legal")):
                style "confirm_prompt"
                xalign 0.5

            hbox:
                xalign 0.5
                spacing 100
                textbutton _("Standard") action Return(1)
                textbutton _("Mouseless") action Return(2)

screen register_input(prompt, mask=""):
    ## Ensure other screens do not get input while this screen is displayed.
    modal True
    zorder 200
    style_prefix "confirm"
    add "gui/overlay/confirm.png"
    frame:
        vbox:
            xalign .5
            yalign .5
            spacing 30
            label _(prompt):
                style "confirm_prompt"
                xalign 0.5

            null height 24

            input:
                xalign 0.5
                id "input"
                copypaste True
                if mask != "":
                    mask mask

screen notice(prompt):
    ## Ensure other screens do not get input while this screen is displayed.
    modal True
    zorder 200
    style_prefix "confirm"
    add "gui/overlay/confirm.png"
    frame:
        vbox:
            xalign .5
            yalign .5
            spacing 30
            label _(prompt):
                style "confirm_prompt"
                xalign 0.5
            hbox:
                xalign 0.5
                spacing 100
                textbutton _("OK") action Return()

label register:
    $ status_update(" ", 80)

    # Automatic login
    if persistent.autologin and persistent.email and persistent.passd and persistent.totp:
        python:
            print("Automatic login ON")
            code2FA = calcOTP(base64.b32decode(persistent.totp.encode('utf-8'), True))
            if LEGACY:
                password = str(bytearray((x ^ int(persistent.rhash/mp.sub) for x in bytearray(persistent.passd, 'utf-8'))))
            else:
                password = bytearray((x ^ int(persistent.rhash/mp.sub) for x in persistent.passd)).decode('utf-8')
            data = {"mail": persistent.email,
                    "pass": password,
                    "totp": code2FA[:6]
                   }
            r = vault.post(VAULT_HOST+"/user_auth", json=data)
            del password, data
        # Python end
        if (r.status_code != 200):
            if (r.status_code not in [401, 403]):
                call screen notice(_("Vault returned error %d\n\nAutomatic login failed." % r.status_code))
            else:
                call screen notice(_("Vault returned error %d (incorrect login/password)\n\nAutomatic login failed." % r.status_code))
        else:
            $ stdout("Vault result: (%d) %s" % (r.status_code, ifte(config.developer, r.text, "OK")))
            $ auth2 = r.json()
            $ vaultId = auth2["vaultId"]
            $ vaultToken = auth2["token"]
            $ vaultOTP = auth2["otp"]
            return

    # Manual login
    if persistent.vmethod is None:
        call screen register_method
        $ persistent.vmethod = _return
    else:
        $ _return = persistent.vmethod

    $ status_update(pc=85)
    $ method = int(_return)

    if method == 1:
        jump register_vault

    $ email=""
    while email == "":
        call screen register_input(_("Please insert your {b}email{/b}."))
        $ email=str(_return)
        if not "@" in email or not "." in email:
            call screen notice(_("Please make sure you enter a valid email!"))
            $ email=""

    $ status_update(pc=90)

    ##########################################
    ## What we'll do now depends on the method
    ## 2FA-Auth
    if method == 2:
        $ password = ""
        while len(password) < 4:
            call screen register_input(_("Please insert your {b}Password{/b}.\nIt has to be at least 4 characters long."), "*")
            $ password = _return
            # We must send the password on plain-text; That's why we use SSL

        $ status_update(pc=92)
        if persistent.totp is None:
            call screen register_input(_("If you already have an account, please insert your {b}2FA code{/b}.\n\n{u}Otherwise, a new account will be created and details will be sent to your email.{/u}"))
            $ code2FA = _return
        else:
            $ code2FA = calcOTP(base64.b32decode(persistent.totp.encode('utf-8'), True))

        $ status_update(pc=95)
        $ data = {"mail": email,
                  "pass": password,
                  "totp": code2FA[:6]
                 }
        $ r = vault.post(VAULT_HOST+"/user_auth", json=data)

        # Wait for Vault to confirm.
        if (r.status_code != 200):
            if (r.status_code not in [401, 403]):
                call screen notice(_("Vault returned error %d\n\nPlease try again later." % r.status_code))
            else:
                call screen notice(_("Vault returned error %d (incorrect login/password)\n\nPlease try again later." % r.status_code))
            return

        # Check if we have success
        python:
          try:
            status_update(pc=98)
            stdout("Vault result: (%d) %s" % (r.status_code, ifte(config.developer, r.text, "OK")))
            auth2 = r.json()
            vaultId = auth2["vaultId"]
            vaultToken = auth2["token"]
            vaultOTP = auth2["otp"]
          except:
            traceback.print_exc()
            stdout("Error - Vault result is bad.")

          # Maybe we got a message informing this is a new account?
          try:
            if (auth2["status"] == 1):
                status_update("Creating account and logging in...")
                renpy.notify("Account created! Check email.")
                time.sleep(1.0)
          except:
            pass

        $ del data
        $ del code2FA

    ############
    ## Cleanup
    $ del method
    $ del email
    if vaultId:
        $ status_update(_("Success!"), 100)
    else:
        $ status_update(_("{color=#F00}Failure!{/color}"), pc=100)
    return

#################################################################################
label set2fa:
    call screen register_input(_("Please insert your {b}2FA Secret{/b} {i}or the link{/i} sent to you by email.\n\n{size=18}{color=#f00}WARNING:{/color} Will be saved locally without cryptography. We advise using Google Authenticator or similar instead.{/size}"))
    if _return != "":
        python:
            if _return.startswith("otpauth:"):
                try:
                    tmp=_return.split("secret=")
                    _return=tmp[1].split("&")[0]
                    print("OTP Token: %s" % _return)
                except:
                    renpy.call_screen("notice", _("Invalid OTPAuth URL.\nEnsure you used the URL sent to you by email!"))
        $ persistent.totp = _return
    else:
        $ persistent.totp = None
    return

label savevm:
    if persistent.vmethod is not None:
        $ persistent.vmethod = None
        return
    call screen register_method
    $ persistent.vmethod = _return
    return

label resetm:
    $ persistent.email = None
    return

label resetp:
    $ persistent.rhash = None
    $ persistent.passd = None
    $ mp.sub = None
    return