#################################################################################
# This file is part of Mana Launcher.
# Copyright (C) 2021 Jesusalva <jesusalva@tmw2.org>
#
# Distributed under the MIT license.
#################################################################################
init -3 python:
renpy.add_python_directory("python-extra")
import requests, zlib, base64, sys, copy, uuid, time, json, traceback
import os.path, os, shutil, subprocess, hashlib, zipfile, pbkdf2
import hmac, struct
# non-free imports
import discord_rpc
print("\n[STDBY] Loading Basic functions.......")
#############################################################################
## Compatibility
# set PYTHON_VERSION variable (e.g. 2715, 3605 etc.)
PYTHON_VERSION="%d%d%02d" % (sys.version_info.major, sys.version_info.minor, sys.version_info.micro)
PYTHON_VERSION=int(PYTHON_VERSION)
# Ren'Py should come with Python 2.7.10 (2710), but just in case
# After all, I only tested with 2.7.10, 2.7.13 and 2.7.15
if (PYTHON_VERSION < 2700):
raise KeyboardInterrupt("Unsupported Python version: %d" % PYTHON_VERSION)
elif (PYTHON_VERSION > 3000):
# This is Python 3.x
execute=subprocess.run
LEGACY = False
if config.developer:
import mwclient
else:
mwclient = None
print("Python 3.x detected! Version is %d." % PYTHON_VERSION)
else:
# This is Python 2.7
execute=subprocess.call
LEGACY = True
mwclient = None
print("Python 2.7 detected! Compatibility mode enabled! (ver: %d)" % PYTHON_VERSION)
#############################################################################
# Functions
# Encodes something to md5
def md5(string):
return hashlib.md5(string.encode()).hexdigest()
def md5sum(f):
md5=hashlib.md5()
fp=open(f, "rb")
ct=fp.read()
md5.update(ct)
rt=copy.copy(md5.hexdigest())
fp.close()
del ct
return rt
# Sanitize a command (strip some flow control chars)
# While it covers all control operators and most metacharacters,
# it doesn't covers well the reserved words.
# ...Of course, it relies on this client not being compromised.
def san(cmd):
return cmd.replace(";", "").replace("|", "").replace(">", "").replace("<", "").replace("&", "").replace("(", "").replace(")", "").replace("\n", "").replace("[[", "").replace("]]", "")
# Smart Print command
def stdout(message, bd=False):
if config.developer:
if renpy.android:
if not renpy.is_init_phase():
renpy.notify(message)
else:
if bd:
print("\033[1m%s\033[0m" % message)
else:
print(message)
renpy.write_log("[DEBUG] %s" % message)
else:
renpy.write_log("[GAME] %s" % message)
return
# Smart wait
def sdelay(delta=0.02):
try:
renpy.pause(delta, hard=True)
except:
time.sleep(delta)
return
# IF Then Else (IFTE)
def ifte(ifs, then, elses):
if (ifs):
return then
else:
return elses
# Returns number of seconds since UNIX EPOCH
def now():
return int(time.time())
# File Managment Functions
def get_path(path):
# Not all systems can record on the game folder
if renpy.android:
path=path.replace("/", "_")
return renpy.config.savedir + "/" + path
else:
return renpy.config.basedir + "/" + path
#############################################################################
# Global classes
# We need to override standard list method. Original by Triptych (stackoverflow)
class dlist(list):
def __setitem__(self, index, value):
size = len(self)
if index >= size:
self.extend(None for _ in range(size, index + 1))
list.__setitem__(self, index, value)
# Search for array[?][key]==search in an array of dicts
# Returns the dictionary, or returns None
def dl_search(array, key, search):
try:
r=(item for item in array if item[key] == search).next()
except:
r=None
if r is None:
stdout("dlsearch: r is None")
return r
# Search for array[?][key]==search in an array of dicts
# Returns the index, or returns -1
def dl_search_idx(array, key, search):
try:
r=next((i for i, item in enumerate(array) if item[key] == search), None)
except:
traceback.print_exc()
r=-1
return ifte(r is None, -1, r)
# Calculates a 2FA Token
def calcOTP(key):
if not LEGACY and not isinstance(key, bytes):
key = bytes(key, 'ascii')
msg = struct.pack(">Q", int(time.time()/30))
h = hmac.new(key, msg, hashlib.sha1).digest()
if LEGACY:
o = ord(h[19]) & 15
else:
o = (h[19] & 15)
_return = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
_return = "%06d" % _return
print("TOTP: %s" % _return)
del msg, h, o
return _return
#############################################################################
## Some other stuff
if renpy.linux:
os.environ["APPIMAGELAUNCHER_DISABLE"]="1"
#############################################################################
## Configuration and Defaults
if (persistent.discord is None):
persistent.discord = True
if (persistent.steam is None):
persistent.steam = False
if (persistent.evol2cli is None):
persistent.evol2cli = "manaverse"
if (persistent.iconify is None):
persistent.iconify = ifte(renpy.windows, False, True)
if (persistent.autologin is None):
persistent.autologin = True
if (persistent.maskpass is None):
persistent.maskpass = True
#############################################################################
## Conditional imports
if persistent.steam:
try:
import _renpysteam as steam
except:
persistent.steam = False
#############################################################################
# ["themanaworld.org", "germantmw.de", "moubootaurlegends.org"]
if config.developer:
VAULT_HOST = "https://localhost:13370"
VAULT_CERT = "http://localhost/launcher/cert.pem"
else:
VAULT_HOST = "https://api.themanaworld.org:13370"
VAULT_CERT = False#"https://tmw2.org/launcher/cert.pem"
###################
# Vault SSL wrapper
vault=requests.Session()
if VAULT_CERT:
vault.cert = get_path("cert.pem")
vault.verify = False #get_path("cert.pem")
############################################################################
## Retrieve the Vault certificate. Otherwise, we cannot proceed.
## From here and until the end of this code block,
## ALL ERRORS ARE FATAL
def build_vault():
while VAULT_CERT:
try:
######################################
# Fetch a new PEM Certificate
stdout("Fetching SSL Certificate from %s" % VAULT_CERT)
r = requests.get(VAULT_CERT)
if (r.status_code != 200):
raise Exception("\nFailed to fetch Vault SSL Certificate.\n\n Returned HTTP error %d.\n\nClick \"Ignore\" to retry.\n\n" % r.status_code)
# Save it
with open(get_path("cert.pem"), 'wb') as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)
# Presumably all good, so do not continue and break the loop
stdout("PEM Download OK")
break
except:
traceback.print_exc()
raise
stdout("Automatically retry in 5 seconds...")
time.sleep(5.0)
## End FATAL mode
#############################################################
init 10 python:
########
## Force Update
#if persistent.version != config.version:
# persistent.version = config.version
# if (persistent.evol2cli is not None and
# persistent.host is not None and
# handle_client(launch=True, download=False)):
# md5check_client(silent=True)
persistent.last_run = now()
mp = MultiPersistent("vault.themanaworld.org", True)
########
## Fix keymap
config.keymap['screenshot']=[]
#config.keymap['screenshot'].remove('s')
config.keymap['screenshot'].append('K_F10')
config.keymap['screenshot'].append('K_F12')
config.keymap['toggle_skip'].remove('K_TAB')
config.keymap['fast_skip'].remove('>')
config.keymap['toggle_skip'].append('>')
config.keymap['focus_down'].append('K_TAB')
config.keymap['toggle_fullscreen'].remove('f')
######### Done with pre-init
label splashscreen:
show TMW2 at truecenter with None #fade
python:
if persistent.hello is None:
p1=2.5
p2=1.5
else:
p1=0.5
p2=0.5
renpy.pause(p1)
hide TMW2 with None #Dissolve(p2)
call before_main_menu
if persistent.hello is None:
call screen preferences
$ persistent.hello=True
return
label die:
$ stdout("Program died.")
pause
return
####### Defaults
default statusmsg = "Not yet initialized"
default progress = 0
default responsive = True
default has_steam = False
default SCR_PROMPT = None
default SCR_RESULT = None
default SCR_CUTIN = ""
default MLP_DEST = None
default running = False
default mySoul = None
## Command Line Interface
init -4 python:
USE_DUMMY_DATA = False
AUTOLAUNCH = True
AUTOWORLD = 0
def parse_command():
global USE_DUMMY_DATA, AUTOLAUNCH, AUTOWORLD
global vault, VAULT_HOST, VAULT_CERT
parser = renpy.arguments.ArgumentParser()
## Collect args
parser.add_argument('args', nargs='+', help="Command-line arguments.")
targs = parser.parse_args()
args = targs.args
## Debug information
if config.developer:
print("Args: %s" % str(args))
print("")
## Command Line Defaults
for cmd in args:
if (cmd == "steam"):
print("Steam Mode Enabled")
persistent.steam = True
elif (cmd == "dummy"):
print("Dummy Mode Enabled")
USE_DUMMY_DATA = True
elif (cmd == "launch"):
print("Auto-launch mode Enabled")
AUTOLAUNCH = True
elif (cmd == "select"):
print("Auto-launch mode Disabled")
AUTOLAUNCH = True
elif (cmd == "remote"):
print("Using remote servers...")
VAULT_HOST = "https://api.themanaworld.org:13370"
VAULT_CERT = False
vault=requests.Session()
elif (cmd == "local"):
print("Using local servers...")
VAULT_HOST = "https://localhost:13370"
VAULT_CERT = "http://localhost/launcher/cert.pem"
vault=requests.Session()
vault.cert = get_path("cert.pem")
vault.verify = get_path("cert.pem")
elif (cmd == "version"):
print("%s v%s %s" % (config.name, config.version, "Mirror Lake"))
print("%s %s" % (str(renpy.version()), renpy.version_name))
if renpy.linux:
print("Linux")
elif renpy.windows:
print("Windows")
else:
print("Unsupported plataform")
renpy.quit()
elif (cmd == "help"):
print("=========================================================")
print("Commands:")
print("steam - Enables Steam login")
print("dummy - do not login, use a dummy data for testing")
print("launch - skip main menu screen")
print("select - show main menu screen")
print("version - shows the program version and exits")
print("help - shows this help screen and exits")
print("")
print("Advanced:")
print("w<id> - logins to world <id> upon launching")
print("")
print("Latter commands have precedence over earlier ones.")
renpy.quit()
elif (cmd.startswith("w")):
try:
AUTOWORLD=int(cmd[1:])
except:
pass # Invalid world id
else:
print("\033[31;1mWARNING:\033[0m Unrecognized argument: %s" % cmd)
return True
renpy.arguments.register_command('adv', parse_command)
label main_menu:
## Autolaunch - Skip main menu
if AUTOLAUNCH and persistent.last_run in range(now()-5, now()+1):
$stdout("Auto-launching...")
$AUTOLAUNCH=False
return
call screen main_menu()
$ renpy.quit()