The Messenger: improve automated installation (#3083)

* add deck support to the messenger mod setup

* Add tkinter cleanup because it's janky

* prompt about launching the game instead of just doing it

* add "better" file validation to courier checking

* make it a bit more palatable

* make it a bit more palatable

* add the executable's md5 to ensure the correct file is selected

* handle a bad md5 and show a message

* make the utils wrapper snake_case and add a docstring

* use stored archive instead of head

* don't give other people the convenience method ig
This commit is contained in:
Aaron Wagener 2024-09-08 12:55:17 -05:00 committed by GitHub
parent cf375cbcc4
commit 5a5162c9d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 80 additions and 27 deletions

View File

@ -27,6 +27,7 @@ class MessengerSettings(Group):
class GamePath(FilePath): class GamePath(FilePath):
description = "The Messenger game executable" description = "The Messenger game executable"
is_exe = True is_exe = True
md5s = ["1b53534569060bc06179356cd968ed1d"]
game_path: GamePath = GamePath("TheMessenger.exe") game_path: GamePath = GamePath("TheMessenger.exe")

View File

@ -5,7 +5,6 @@ import os.path
import subprocess import subprocess
import urllib.request import urllib.request
from shutil import which from shutil import which
from tkinter.messagebox import askyesnocancel
from typing import Any, Optional from typing import Any, Optional
from zipfile import ZipFile from zipfile import ZipFile
from Utils import open_file from Utils import open_file
@ -18,11 +17,33 @@ from Utils import is_windows, messagebox, tuplize_version
MOD_URL = "https://api.github.com/repos/alwaysintreble/TheMessengerRandomizerModAP/releases/latest" MOD_URL = "https://api.github.com/repos/alwaysintreble/TheMessengerRandomizerModAP/releases/latest"
def ask_yes_no_cancel(title: str, text: str) -> Optional[bool]:
"""
Wrapper for tkinter.messagebox.askyesnocancel, that creates a popup dialog box with yes, no, and cancel buttons.
:param title: Title to be displayed at the top of the message box.
:param text: Text to be displayed inside the message box.
:return: Returns True if yes, False if no, None if cancel.
"""
from tkinter import Tk, messagebox
root = Tk()
root.withdraw()
ret = messagebox.askyesnocancel(title, text)
root.update()
return ret
def launch_game(*args) -> None: def launch_game(*args) -> None:
"""Check the game installation, then launch it""" """Check the game installation, then launch it"""
def courier_installed() -> bool: def courier_installed() -> bool:
"""Check if Courier is installed""" """Check if Courier is installed"""
return os.path.exists(os.path.join(game_folder, "TheMessenger_Data", "Managed", "Assembly-CSharp.Courier.mm.dll")) assembly_path = os.path.join(game_folder, "TheMessenger_Data", "Managed", "Assembly-CSharp.dll")
with open(assembly_path, "rb") as assembly:
for line in assembly:
if b"Courier" in line:
return True
return False
def mod_installed() -> bool: def mod_installed() -> bool:
"""Check if the mod is installed""" """Check if the mod is installed"""
@ -57,27 +78,34 @@ def launch_game(*args) -> None:
if not is_windows: if not is_windows:
mono_exe = which("mono") mono_exe = which("mono")
if not mono_exe: if not mono_exe:
# steam deck support but doesn't currently work # download and use mono kickstart
messagebox("Failure", "Failed to install Courier", True) # this allows steam deck support
raise RuntimeError("Failed to install Courier") mono_kick_url = "https://github.com/flibitijibibo/MonoKickstart/archive/716f0a2bd5d75138969090494a76328f39a6dd78.zip"
# # download and use mono kickstart files = []
# # this allows steam deck support with urllib.request.urlopen(mono_kick_url) as download:
# mono_kick_url = "https://github.com/flibitijibibo/MonoKickstart/archive/refs/heads/master.zip" with ZipFile(io.BytesIO(download.read()), "r") as zf:
# target = os.path.join(folder, "monoKickstart") for member in zf.infolist():
# os.makedirs(target, exist_ok=True) if "precompiled/" not in member.filename or member.filename.endswith("/"):
# with urllib.request.urlopen(mono_kick_url) as download: continue
# with ZipFile(io.BytesIO(download.read()), "r") as zf: member.filename = member.filename.split("/")[-1]
# for member in zf.infolist(): if member.filename.endswith("bin.x86_64"):
# zf.extract(member, path=target) member.filename = "MiniInstaller.bin.x86_64"
# installer = subprocess.Popen([os.path.join(target, "precompiled"), zf.extract(member, path=game_folder)
# os.path.join(folder, "MiniInstaller.exe")], shell=False) files.append(member.filename)
# os.remove(target) mono_installer = os.path.join(game_folder, "MiniInstaller.bin.x86_64")
os.chmod(mono_installer, 0o755)
installer = subprocess.Popen(mono_installer, shell=False)
failure = installer.wait()
for file in files:
os.remove(file)
else: else:
installer = subprocess.Popen([mono_exe, os.path.join(game_folder, "MiniInstaller.exe")], shell=False) installer = subprocess.Popen([mono_exe, os.path.join(game_folder, "MiniInstaller.exe")], shell=True)
failure = installer.wait()
else: else:
installer = subprocess.Popen(os.path.join(game_folder, "MiniInstaller.exe"), shell=False) installer = subprocess.Popen(os.path.join(game_folder, "MiniInstaller.exe"), shell=True)
failure = installer.wait()
failure = installer.wait() print(failure)
if failure: if failure:
messagebox("Failure", "Failed to install Courier", True) messagebox("Failure", "Failed to install Courier", True)
os.chdir(working_directory) os.chdir(working_directory)
@ -125,18 +153,35 @@ def launch_game(*args) -> None:
return "alpha" in latest_version or tuplize_version(latest_version) > tuplize_version(installed_version) return "alpha" in latest_version or tuplize_version(latest_version) > tuplize_version(installed_version)
from . import MessengerWorld from . import MessengerWorld
game_folder = os.path.dirname(MessengerWorld.settings.game_path) try:
game_folder = os.path.dirname(MessengerWorld.settings.game_path)
except ValueError as e:
logging.error(e)
messagebox("Invalid File", "Selected file did not match expected hash. "
"Please try again and ensure you select The Messenger.exe.")
return
working_directory = os.getcwd() working_directory = os.getcwd()
# setup ssl context
try:
import certifi
import ssl
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=certifi.where())
context.set_alpn_protocols(["http/1.1"])
https_handler = urllib.request.HTTPSHandler(context=context)
opener = urllib.request.build_opener(https_handler)
urllib.request.install_opener(opener)
except ImportError:
pass
if not courier_installed(): if not courier_installed():
should_install = askyesnocancel("Install Courier", should_install = ask_yes_no_cancel("Install Courier",
"No Courier installation detected. Would you like to install now?") "No Courier installation detected. Would you like to install now?")
if not should_install: if not should_install:
return return
logging.info("Installing Courier") logging.info("Installing Courier")
install_courier() install_courier()
if not mod_installed(): if not mod_installed():
should_install = askyesnocancel("Install Mod", should_install = ask_yes_no_cancel("Install Mod",
"No randomizer mod detected. Would you like to install now?") "No randomizer mod detected. Would you like to install now?")
if not should_install: if not should_install:
return return
logging.info("Installing Mod") logging.info("Installing Mod")
@ -144,17 +189,24 @@ def launch_game(*args) -> None:
else: else:
latest = request_data(MOD_URL)["tag_name"] latest = request_data(MOD_URL)["tag_name"]
if available_mod_update(latest): if available_mod_update(latest):
should_update = askyesnocancel("Update Mod", should_update = ask_yes_no_cancel("Update Mod",
f"New mod version detected. Would you like to update to {latest} now?") f"New mod version detected. Would you like to update to {latest} now?")
if should_update: if should_update:
logging.info("Updating mod") logging.info("Updating mod")
install_mod() install_mod()
elif should_update is None: elif should_update is None:
return return
if not args:
should_launch = ask_yes_no_cancel("Launch Game",
"Mod installed and up to date. Would you like to launch the game now?")
if not should_launch:
return
parser = argparse.ArgumentParser(description="Messenger Client Launcher") parser = argparse.ArgumentParser(description="Messenger Client Launcher")
parser.add_argument("url", type=str, nargs="?", help="Archipelago Webhost uri to auto connect to.") parser.add_argument("url", type=str, nargs="?", help="Archipelago Webhost uri to auto connect to.")
args = parser.parse_args(args) args = parser.parse_args(args)
if not is_windows: if not is_windows:
if args.url: if args.url:
open_file(f"steam://rungameid/764790//{args.url}/") open_file(f"steam://rungameid/764790//{args.url}/")