diff --git a/Launcher with autoupdater.py b/Launcher with autoupdater.py index 69a62cc..094d7a0 100644 --- a/Launcher with autoupdater.py +++ b/Launcher with autoupdater.py @@ -8,8 +8,19 @@ from datetime import datetime from os.path import exists from pathlib import Path +from appdirs import AppDirs import subprocess -AppdataPATH = os.getenv('APPDATA') + "\\OpenGOAL-UnofficialModLauncher\\" +from utils import launcherUtils + +dirs = AppDirs(roaming=True) +LauncherInstallPATH = Path(dirs.user_data_dir) / "OpenGOAL-UnofficialModLauncher" + +OpengoalModLauncher_exe = launcherUtils.get_exe("OpengoalModLauncher") +gk_exe = launcherUtils.get_exe("gk") +decompiler_exe = launcherUtils.get_exe("decompiler") +goalc_exe = launcherUtils.get_exe("goalc") +extractor_exe = launcherUtils.get_exe("extractor") + def show_progress(block_num, block_size, total_size): if total_size > 0: @@ -18,17 +29,25 @@ def show_progress(block_num, block_size, total_size): except Exception as e: pass # Handle the exception if the window or element does not exist + def try_remove_file(file): if exists(file): os.remove(file) + def try_remove_dir(dir): if exists(dir): shutil.rmtree(dir) -def check_for_updates(): - +# TODO: +# The logic will need to be adjusted to use different criteria to return the latest executable +# with regards to the user's platform. +# Currently, it determines the executable using only the latest release at the launch_url +# (assuming no network error, otherwise it goes into the else statement) +# However, at the moment this leads to only the Windows exe being chosen and latest_release_assets_url gets +# assigned accordingly +def get_latest_release_info(): launch_url = "https://api.github.com/repos/OpenGOAL-Unofficial-Mods/OpenGoal-ModLauncher-dev/releases" response = requests.get(url=launch_url, params={'address': "yolo"}) @@ -42,18 +61,31 @@ def check_for_updates(): else: print("WARNING: Failed to query GitHub API, you might be rate-limited. Using default fallback release instead.") latest_release = datetime(2023, 7, 23) - latest_release_assets_url = "https://github.com/OpenGOAL-Unofficial-Mods/OpenGoal-ModLauncher-dev/releases/download/v1.10fixoldpckernel/openGOALModLauncher.exe" + latest_release_assets_url = "https://github.com/OpenGOAL-Unofficial-Mods/OpenGoal-ModLauncher-dev/releases/download/latest/" + OpengoalModLauncher_exe + + return latest_release_assets_url, latest_release + +def get_latest_write_date(): last_write = datetime(2020, 5, 17) - if os.path.exists(AppdataPATH + "\\OpengoalModLauncher.exe"): - last_write = datetime.utcfromtimestamp(Path(AppdataPATH + "\\OpengoalModLauncher.exe").stat().st_mtime) + if (LauncherInstallPATH / OpengoalModLauncher_exe).exists(): + last_write = datetime.utcfromtimestamp((LauncherInstallPATH / OpengoalModLauncher_exe).stat().st_mtime) + return last_write - need_update = bool((last_write < latest_release)) - window['installed_version'].update(f"Currently installed version created on: {last_write.strftime('%Y-%m-%d %H:%M:%S')}") +def need_update(last_write, latest_release): + return bool((last_write < latest_release)) + + +def check_for_updates(): + latest_release_assets_url, latest_release = get_latest_release_info() + last_write = get_latest_write_date() + + window['installed_version'].update( + f"Currently installed version created on: {last_write.strftime('%Y-%m-%d %H:%M:%S')}") window['newest_version'].update(f"Newest version created on: {latest_release.strftime('%Y-%m-%d %H:%M:%S')}") - if need_update: + if need_update(last_write, latest_release): window['update_status'].update("An update is available. Click 'Update' to install.") window['update_button'].update(visible=True) window['launch_button'].update(visible=False) @@ -62,66 +94,51 @@ def check_for_updates(): window['update_button'].update(visible=False) window['launch_button'].update(visible=True) -def download_newest_mod(): - AppdataPATH = os.getenv('APPDATA') + "\\OpenGOAL-UnofficialModLauncher\\" - - launch_url = "https://api.github.com/repos/OpenGOAL-Unofficial-Mods/OpenGoal-ModLauncher-dev/releases" - response = requests.get(url=launch_url, params={'address': "yolo"}) - - if response is not None and response.status_code == 200: - r = json.loads(json.dumps(response.json())) - latest_release = datetime.strptime(r[0].get("published_at").replace("T", " ").replace("Z", ""), - '%Y-%m-%d %H:%M:%S') - latest_release_assets_url = (json.loads( - json.dumps(requests.get(url=r[0].get("assets_url"), params={'address': "yolo"}).json())))[0].get( - "browser_download_url") - else: - print("WARNING: Failed to query GitHub API, you might be rate-limited. Using default fallback release instead.") - latest_release = datetime(2023, 7, 23) - latest_release_assets_url = "https://github.com/OpenGOAL-Unofficial-Mods/OpenGoal-ModLauncher-dev/releases/download/v1.10fixoldpckernel/openGOALModLauncher.exe" - last_write = datetime(2020, 5, 17) - if os.path.exists(AppdataPATH + "\\OpengoalModLauncher.exe"): - last_write = datetime.utcfromtimestamp(Path(AppdataPATH + "\\OpengoalModLauncher.exe").stat().st_mtime) +def download_newest_mod(): + latest_release_assets_url, latest_release = get_latest_release_info() + last_write = get_latest_write_date() - need_update = bool((last_write < latest_release)) + if need_update(last_write, latest_release): + temp_dir = LauncherInstallPATH / "temp" - if need_update: window['update_status'].update("Starting Update...") - try_remove_dir(AppdataPATH + "/temp") - if not os.path.exists(AppdataPATH + "/temp"): - os.makedirs(AppdataPATH + "/temp") + try_remove_dir(temp_dir) + if not temp_dir.exists(): + temp_dir.mkdir() window['update_status'].update("Downloading update from " + latest_release_assets_url) file = urllib.request.urlopen(latest_release_assets_url) - urllib.request.urlretrieve(latest_release_assets_url, AppdataPATH + "/temp/OpengoalModLauncher.exe", show_progress) + urllib.request.urlretrieve(latest_release_assets_url, temp_dir / OpengoalModLauncher_exe, show_progress) window['update_status'].update("Done downloading") - window['update_status'].update("Removing previous installation " + AppdataPATH) - try_remove_dir(AppdataPATH + "/data") - try_remove_file(AppdataPATH + "/gk.exe") - try_remove_file(AppdataPATH + "/goalc.exe") - try_remove_file(AppdataPATH + "/extractor.exe") + window['update_status'].update(f"Removing previous installation {LauncherInstallPATH}") + try_remove_dir(LauncherInstallPATH / "data") + try_remove_file(LauncherInstallPATH / gk_exe) + try_remove_file(LauncherInstallPATH / goalc_exe) + try_remove_file(LauncherInstallPATH / extractor_exe) window['update_status'].update("Extracting update") - temp_dir = AppdataPATH + "/temp" - try_remove_file(temp_dir + "/updateDATA.zip") + + try_remove_file(temp_dir / "updateDATA.zip") sub_dir = temp_dir all_files = os.listdir(sub_dir) for f in all_files: - shutil.move(sub_dir + "/" + f, AppdataPATH + "/" + f) + shutil.move(sub_dir / f, LauncherInstallPATH / f) try_remove_dir(temp_dir) window['update_status'].update("Update complete") window['update_button'].update(visible=False) window['launch_button'].update(visible=True) + layout = [ [sg.Text("OpenGOAL Mod Updater", font=("Helvetica", 16))], [sg.Text("Installed Version:", size=(20, 1)), sg.Text("", size=(20, 1), key='installed_version')], [sg.Text("Newest Version:", size=(20, 1)), sg.Text("", size=(20, 1), key='newest_version')], [sg.ProgressBar(100, orientation='h', size=(20, 20), key='progress_bar')], [sg.Text("", size=(40, 1), key='update_status')], - [sg.Button("Check for Updates"), sg.Button("Update", visible=False, key='update_button'), sg.Button("Launch", visible=False, key='launch_button'), sg.Button("Exit")] + [sg.Button("Check for Updates"), sg.Button("Update", visible=False, key='update_button'), + sg.Button("Launch", visible=False, key='launch_button'), sg.Button("Exit")] ] window = sg.Window("OpenGOAL Mod Updater", layout, finalize=True) @@ -136,6 +153,6 @@ def download_newest_mod(): download_newest_mod() elif event == "launch_button": window.close() - subprocess.call([ AppdataPATH + "openGOALModLauncher.exe"]) + subprocess.call([str(LauncherInstallPATH / OpengoalModLauncher_exe)]) window.close() diff --git a/buildlaunchercoreEXE b/buildlaunchercoreEXE new file mode 100755 index 0000000..377631b --- /dev/null +++ b/buildlaunchercoreEXE @@ -0,0 +1,17 @@ +#!/bin/bash + +# These three lines set the working path to this script's path. +# It is equivalent to %~dp0 used in Windows batch files +# https://stackoverflow.com/questions/207959/equivalent-of-dp0-retrieving-source-file-name-in-sh +cd `dirname $0` +mypath=`pwd` +cd "$mypath" + + +pyinstaller --onefile openGOALModLauncher.py --icon resources/appicon.ico + +mv "${mypath}/dist/openGOALModLauncher" "${mypath}" + +rm -rf "${mypath}/dist" +rm -rf "${mypath}/build" +rm "${mypath}/openGOALModLauncher.spec" diff --git a/buildlauncherupdaterexe b/buildlauncherupdaterexe new file mode 100755 index 0000000..90073b7 --- /dev/null +++ b/buildlauncherupdaterexe @@ -0,0 +1,16 @@ +#!/bin/bash + +# These three lines set the working path to this script's path. +# It is equivalent to %~dp0 used in Windows batch files +# https://stackoverflow.com/questions/207959/equivalent-of-dp0-retrieving-source-file-name-in-sh +cd `dirname $0` +mypath=`pwd` +cd "$mypath" + +# --hide-console is Windows only +pyinstaller --onefile "Launcher with autoupdater.py" --icon resources/appicon.ico + +mv "${mypath}/dist/Launcher with autoupdater" "${mypath}" + +rm -rf "${mypath}/dist" +rm -rf "${mypath}/build" diff --git a/openGOALModLauncher.py b/openGOALModLauncher.py index dbb4861..cc90914 100644 --- a/openGOALModLauncher.py +++ b/openGOALModLauncher.py @@ -8,7 +8,7 @@ import threading from PIL import Image -from utils import launcherUtils, githubUtils +from utils import launcherUtils, githubUtils, layoutUtils import PySimpleGUI as sg import cloudscraper import io @@ -31,9 +31,21 @@ from pathlib import Path import random + +dirs = AppDirs(roaming=True) + +# C:\Users\USERNAME\AppData\Roaming\OpenGOAL-Mods\ +ModFolderPATH = Path(dirs.user_data_dir) / "OpenGOAL-Mods" + sg.theme("DarkBlue3") +# executables + +gk_exe = launcherUtils.get_exe("gk") + + + def openLauncherWebsite(): webbrowser.open("https://jakmods.dev") @@ -45,13 +57,13 @@ def exitWithError(): # Folder where script is placed, It looks in this for the Exectuable if getattr(sys, "frozen", False): # If we are a pyinstaller exe get the path of this file, not python - LauncherDir = os.path.dirname(os.path.realpath(sys.executable)) + LauncherDir = Path(sys.executable).resolve().parent # Detect if a user has downloaded a release directly, if so point them to the autoupdater if ( - LauncherDir != os.getenv("APPDATA") + "\\OpenGOAL-UnofficalModLauncher" + LauncherDir != Path(dirs.user_data_dir) / "OpenGOAL-UnofficalModLauncher" and os.getlogin() != "NinjaPC" - and os.environ["COMPUTERNAME"] != "DESKTOP-BBN1CMN" + and os.environ.get("COMPUTERNAME",) != "DESKTOP-BBN1CMN" ): # Creating the tkinter window root = tkinter.Tk() @@ -80,15 +92,10 @@ def exitWithError(): # if we are running the .py directly use this path LauncherDir = os.path.dirname(__file__) -installpath = str(LauncherDir + "\\resources\\") +installpath = Path(LauncherDir) / "resources" # intialize default variables so they are never null -dirs = AppDirs(roaming=True) - -# C:\Users\USERNAME\AppData\Roaming\OpenGOAL-Mods\ -ModFolderPATH = os.path.join(dirs.user_data_dir, "OpenGOAL-Mods", "") - # grab images from web # url to splash screen image @@ -131,8 +138,8 @@ def fetch_image(url): # Accept the URL parameter loadingimage = getPNGFromURL("https://raw.githubusercontent.com/OpenGOAL-Unofficial-Mods/OpenGoal-ModLauncher-dev/main/resources/modlauncher-loading-0.png") # make the modfolderpath if first install -if not os.path.exists(ModFolderPATH): - os.makedirs(ModFolderPATH) +if not ModFolderPATH.exists(): + ModFolderPATH.mkdir() table_headings = [ "id", @@ -210,8 +217,9 @@ def fetch_image(url): # Accept the URL parameter def getRefreshedTableData(sort_col_idx): # Load data from the local file if it exists - local_file_path = "resources/jak1_mods.json" - if os.path.exists(local_file_path): + local_file_path = Path("resources") / "jak1_mods.json" + if local_file_path.exists(): + # TODO: don't leak fd local_mods = json.loads(open(local_file_path, "r").read()) # Load data from the remote URL @@ -221,7 +229,7 @@ def getRefreshedTableData(sort_col_idx): # Initialize an empty dictionary to store the combined data mod_dict = {} - if os.path.exists(local_file_path): + if local_file_path.exists(): # Merge the remote and local data while removing duplicates mod_dict = {**remote_mods, **local_mods} else: @@ -243,30 +251,25 @@ def getRefreshedTableData(sort_col_idx): mod["install_date"] = "Not Installed" mod["access_date"] = "Not Installed" + mod_path = ModFolderPATH/mod_id + mod_gk_path = mod_path / gk_exe # determine local install/access datetime if mod_id in installed_mod_subfolders: mod[ "install_date" ] = f"{datetime.fromtimestamp(installed_mod_subfolders[mod_id]):%Y-%m-%d %H:%M}" - - if exists(f"{ModFolderPATH}/{mod_id}/gk.exe"): - gk_stat = os.stat(f"{ModFolderPATH}/{mod_id}/gk.exe") - mod[ - "access_date" - ] = f"{datetime.fromtimestamp(gk_stat.st_atime):%Y-%m-%d %H:%M}" elif mod_name in installed_mod_subfolders: # previous installation using mod_name (will migrate after this step) mod[ "install_date" ] = f"{datetime.fromtimestamp(installed_mod_subfolders[mod_name]):%Y-%m-%d %H:%M}" # migrate folder to use mod_id instead of mod_name - shutil.move(ModFolderPATH + "/" + mod_name, ModFolderPATH + "/" + mod_id) + (ModFolderPATH/mod_name).rename(mod_path) - if exists(f"{ModFolderPATH}/{mod_id}/gk.exe"): - gk_stat = os.stat(f"{ModFolderPATH}/{mod_id}/gk.exe") - mod[ - "access_date" - ] = f"{datetime.fromtimestamp(gk_stat.st_atime):%Y-%m-%d %H:%M}" + if mod_gk_path.exists(): + mod[ + "access_date" + ] = f"{datetime.fromtimestamp(mod_gk_path.stat().st_atime):%Y-%m-%d %H:%M}" mod["contributors"] = ", ".join(mod["contributors"]) mod["tags"].sort() @@ -362,258 +365,7 @@ def getRefreshedTableData(sort_col_idx): LATEST_TABLE_DATA = [] # ----- Full layout ----- -layout = [ - [ - sg.Frame( - title="", - key="-SPLASHFRAME-", - border_width=0, # Set border_width to 0 - visible=True, - element_justification="center", - vertical_alignment="center", - layout=[ - [ - sg.Image( - key="-SPLASHIMAGE-", - source=githubUtils.resize_image(splashfile, 970, 607), - pad=(0, 0), # Set padding to 0 - expand_x=True, - expand_y=True, - ) - ] - ], - ) - ], - [ - sg.Frame( - title="", - key="-LOADINGFRAME-", - border_width=0, # Set border_width to 0 - visible=False, - element_justification="center", - vertical_alignment="center", - layout=[ - [ - sg.Image( - key="-LOADINGIMAGE-", - source=githubUtils.resize_image(loadingimage, 970, 607), - pad=(0, 0), # Set padding to 0 - expand_x=True, - expand_y=True, - ) - ] - ], - ) - ], - [ - sg.Frame( - title="", - key="-MAINFRAME-", - border_width=0, - visible=False, - layout=[ - [ - sg.Column( # nav sidebar - [ - [sg.Text("JAK 1", font=("Helvetica", 16, "bold"))], - [ - sg.Radio( - "Mods", - "filter", - font=("Helvetica", 12), - enable_events=True, - key="jak1/mods", - default=True, - ) - ], - [ - sg.Radio( - "Texture Packs", - "filter", - enable_events=True, - font=("Helvetica", 12), - key="jak1/tex", - ) - ], - [sg.Text("")], - [sg.Text("JAK 2", font=("Helvetica", 16, "bold"))], - [ - sg.Radio( - "Mods", - "filter", - font=("Helvetica", 12), - enable_events=True, - key="jak2/mods", - ) - ], - [ - sg.Radio( - "Texture Packs", - "filter", - font=("Helvetica", 12), - enable_events=True, - key="jak2/tex", - ) - ], - [sg.VPush()], - [ - sg.Btn( - button_text="View iso_data Folder", - key="-VIEWISOFOLDER-", - expand_x=True, - ) - ], - [ - sg.Btn( - button_text="jakmods.dev", - key="-JAKMODSWEB-", - expand_x=True, - ) - ], - ], - expand_y=True, - ), - sg.VerticalSeparator(), - sg.Column( - [ - [ - sg.Column( - [ - [ - sg.Text( - "", - key="-SELECTEDMODNAME-", - font=("Helvetica", 13), - metadata={"id": "", "url": ""}, - ) - ], - [ - sg.Text( - "", - key="-SELECTEDMODDESC-", - size=(45, 7), - ) - ], - [sg.Text("Tags:", key="-SELECTEDMODTAGS-")], - [ - sg.Text( - "Contributors:", - key="-SELECTEDMODCONTRIBUTORS-", - ) - ], - [sg.Text("")], - [ - sg.Btn( - button_text="Launch", - key="-LAUNCH-", - expand_x=True, - ), - sg.Btn( - button_text="Re-extract", - key="-REEXTRACT-", - expand_x=True, - ), - sg.Btn( - button_text="Recompile", - key="-RECOMPILE-", - expand_x=True, - ), - sg.Btn( - button_text="Uninstall", - key="-UNINSTALL-", - expand_x=True, - ), - ], - [ - sg.Btn( - button_text="View Folder", - key="-VIEWFOLDER-", - expand_x=True, - ), - sg.Btn( - button_text="Website", - key="-WEBSITE-", - expand_x=True, - metadata={"url": ""}, - ), - sg.Btn( - button_text="Video(s)", - key="-VIDEOS-", - expand_x=True, - metadata={"url": ""}, - ), - # sg.Btn( - # button_text="Photo(s)", - # key="-PHOTOS-", - # expand_x=True, - # metadata={"url": ""}, - # ), - ], - ], - size=(200, 300), - expand_x=True, - expand_y=True, - ), - sg.Frame( - title="", - element_justification="center", - vertical_alignment="center", - border_width=0, - layout=[ - [ - sg.Image( - key="-SELECTEDMODIMAGE-", expand_y=True - ) - ] - ], - size=(450, 300), - ), - ], - [sg.HorizontalSeparator()], - [ - sg.Text("Search"), - sg.Input( - expand_x=True, enable_events=True, key="-FILTER-" - ), - sg.Checkbox( - text="Show Installed", - default=True, - enable_events=True, - key="-SHOWINSTALLED-", - ), - sg.Checkbox( - text="Show Uninstalled", - default=True, - enable_events=True, - key="-SHOWUNINSTALLED-", - ), - ], - [ - sg.Table( - values=LATEST_TABLE_DATA, - headings=table_headings, - visible_column_map=col_vis, - col_widths=col_width, - auto_size_columns=False, - num_rows=15, - text_color="black", - background_color="lightblue", - alternating_row_color="white", - justification="left", - selected_row_colors="black on yellow", - key="-MODTABLE-", - expand_x=True, - expand_y=True, - enable_click_events=True, - ) - ], - ] - ), - ] - ], - ) - ], -] +layout = layoutUtils.generate(splashfile, loadingimage, LATEST_TABLE_DATA, table_headings, col_vis, col_width) window = sg.Window( "OpenGOAL Mod Launcher", layout, icon=iconfile, border_depth=0, finalize=True @@ -846,7 +598,7 @@ def loading_screen_with_thread(thread): subfolders = [f.name for f in os.scandir(ModFolderPATH) if f.is_dir()] if tmpModSelected in subfolders: - dir = dirs.user_data_dir + "\\OpenGOAL-Mods\\" + tmpModSelected + dir = ModFolderPATH/tmpModSelected launcherUtils.openFolder(dir) else: sg.Popup("Selected mod is not installed", keep_on_top=True, icon=iconfile) @@ -858,10 +610,9 @@ def loading_screen_with_thread(thread): [linkType, tmpModURL] = githubUtils.identifyLinkType(tmpModURL) subfolders = [f.name for f in os.scandir(ModFolderPATH) if f.is_dir()] if tmpModSelected in subfolders: - dir = dirs.user_data_dir + "\\OpenGOAL-Mods\\" + tmpModSelected + dir = ModFolderPATH/tmpModSelected ans = sg.popup_ok_cancel( - "Confirm: re-extracting " - + dir, + f"Confirm: re-extracting {dir}", icon=iconfile, ) if ans == "OK": @@ -878,10 +629,9 @@ def loading_screen_with_thread(thread): [linkType, tmpModURL] = githubUtils.identifyLinkType(tmpModURL) subfolders = [f.name for f in os.scandir(ModFolderPATH) if f.is_dir()] if tmpModSelected in subfolders: - dir = dirs.user_data_dir + "\\OpenGOAL-Mods\\" + tmpModSelected + dir = ModFolderPATH/tmpModSelected ans = sg.popup_ok_cancel( - "Confirm: recompiling " - + dir, + f"Confirm: recompiling {dir}", icon=iconfile, ) if ans == "OK": @@ -895,8 +645,8 @@ def loading_screen_with_thread(thread): subfolders = [f.name for f in os.scandir(ModFolderPATH) if f.is_dir()] if tmpModSelected in subfolders: - dir = dirs.user_data_dir + "\\OpenGOAL-Mods\\" + tmpModSelected - ans = sg.popup_ok_cancel("Confirm: uninstalling " + dir, icon=iconfile) + dir = ModFolderPATH/tmpModSelected + ans = sg.popup_ok_cancel(f"Confirm: uninstalling {dir}", icon=iconfile) if ans == "OK": launcherUtils.try_remove_dir(dir) reset() @@ -940,7 +690,7 @@ def loading_screen_with_thread(thread): LATEST_TABLE_DATA = getRefreshedTableData(None) window["-MODTABLE-"].update(values=LATEST_TABLE_DATA) elif event == "-VIEWISOFOLDER-": - dir = dirs.user_data_dir + "\\OpenGOAL-Mods\\_iso_data" + dir = ModFolderPATH/"_iso_data" launcherUtils.ensure_jak_folders_exist() launcherUtils.openFolder(dir) elif event == "-JAKMODSWEB-": diff --git a/utils/launcherUtils.py b/utils/launcherUtils.py index 2bb6954..1137e04 100644 --- a/utils/launcherUtils.py +++ b/utils/launcherUtils.py @@ -22,42 +22,45 @@ import zipfile from zipfile import BadZipFile from appdirs import AppDirs -import platform import stat from pathlib import Path import time import ctypes -FILE_DATE_TO_CHECK = "gk.exe" -UPDATE_FILE_EXTENTION = ".zip" -# Folder where script is placed, It looks in this for the Exectuable -if getattr(sys, "frozen", False): - LauncherDir = os.path.dirname(os.path.realpath(sys.executable)) -elif __file__: - LauncherDir = os.path.dirname(__file__) +# This function expects the name of the executable without .exe at the end +# For windows systems, it appends ".exe" to the executable name and returns this value +# For linux and mac, it returns it as-is +# It is meant to be called at the beginning of a script, where the results are stored +# into a string variable. The string variable can then be used in followup logic +def get_exe(executable): + return executable + ".exe" if sys.platform == "win32" else executable + + +gk_exe = get_exe("gk") +decompiler_exe = get_exe("decompiler") +goalc_exe = get_exe("goalc") +extractor_exe = get_exe("extractor") + +FILE_DATE_TO_CHECK = gk_exe +UPDATE_FILE_EXTENTION = ".zip" +# Executable we're checking the 'modified' time of ExecutableName = str( FILE_DATE_TO_CHECK -) # Executable we're checking the 'modified' time of +) # content_type of the .deb release is also "application\zip", so rely on file ext FileExt = str( UPDATE_FILE_EXTENTION -) # content_type of the .deb release is also "application\zip", so rely on file ext +) FileIdent = "" # If we ever get to multiple .zip files in a release, include other identifying information from the name -dirs = AppDirs(roaming=True) -currentOS = platform.system() -ModFolderPATH = os.path.join(dirs.user_data_dir, "OpenGOAL-Mods", "") -AppdataPATH = dirs.user_data_dir +AppdataPATH = Path(AppDirs(roaming=True).user_data_dir) +ModFolderPATH = AppdataPATH / "OpenGOAL-Mods" +ISO_PATH = ModFolderPATH / "_iso_data" pbar = None - - - - - def installedlist(PATH): print(PATH) scanDir = PATH @@ -89,33 +92,46 @@ def show_progress(block_num, block_size, total_size): def process_exists(process_name): - call = "TASKLIST", "/FI", "imagename eq %s" % process_name - try: - # use buildin check_output right away - output = subprocess.check_output(call).decode() - # check in last line for process name - last_line = output.strip().split("\r\n")[-1] - # because Fail message could be translated - return last_line.lower().startswith(process_name.lower()) - except: - return False + if sys.platform == "win32": + call = "TASKLIST", "/FI", "imagename eq %s" % process_name + try: + # use buildin check_output right away + output = subprocess.check_output(call).decode() + # check in last line for process name + last_line = output.strip().split("\r\n")[-1] + # because Fail message could be translated + return last_line.lower().startswith(process_name.lower()) + except: + return False + else: + call = ["pgrep", "--list-name", "^" + process_name + "$"] + try: + output = subprocess.check_output(call).decode() + return len(output) > 1 + except: + return False def try_kill_process(process_name): if process_exists(process_name): - os.system("taskkill /f /im " + process_name) + if sys.platform == "win32": + os.system("taskkill /f /im " + process_name) + else: + os.system("pkill " + "^" + process_name + "$") def try_remove_file(file): if exists(file): os.remove(file) + def is_junction(path: str) -> bool: try: return bool(os.readlink(path)) except OSError: return False + def try_remove_dir(dir): if exists(dir): print(f"found dir {dir}, attempting to remove") @@ -125,29 +141,36 @@ def try_remove_dir(dir): def local_mod_image(MOD_ID): - path = ModFolderPATH + MOD_ID + "\\ModImage.png" + path = ModFolderPATH / MOD_ID / "ModImage.png" if exists(path): return path return None - def moveDirContents(src, dest): - # moves all files from src to dest, without moving src dir itself - for f in os.listdir(src): - src_path = os.path.join(src, f) - dst_path = os.path.join(dest, f) - shutil.move(src_path, dst_path) + # moves all files from src to dest, without moving src dir itself + for f in os.listdir(src): + src_path = os.path.join(src, f) + dst_path = os.path.join(dest, f) + shutil.move(src_path, dst_path) + def makeDirSymlink(link, target): - subprocess.check_call('mklink /J "%s" "%s"' % (link, target), shell=True) + if sys.platform == "win32": + subprocess.check_call('mklink /J "%s" "%s"' % (link, target), shell=True) + else: + subprocess.check_call('ln -s "%s" "%s"' % (target, link), shell=True) def makeFileSymlink(link, target): # if ctypes.windll.shell32.IsUserAnAdmin(): # subprocess.check_call('mklink "%s" "%s"' % (link, target), shell=True) # else: - subprocess.check_call('mklink /H "%s" "%s"' % (link, target), shell=True) + if sys.platform == "win32": + subprocess.check_call('mklink /H "%s" "%s"' % (link, target), shell=True) + else: + subprocess.check_call('ln -s "%s" "%s"' % (target, link), shell=True) + def link_files_by_extension(source_dir, destination_dir): # Ensure the source directory exists @@ -174,15 +197,17 @@ def link_files_by_extension(source_dir, destination_dir): os.remove(destination_path) # Create a symbolic link from the source location to the destination location. - #print("making " + destination_path + "<-des source ->" + file_path) + # print("making " + destination_path + "<-des source ->" + file_path) makeFileSymlink(destination_path, file_path) + def openFolder(path): - if not exists(dirs.user_data_dir + "\\OpenGOAL\\" + "mods\\data\\iso_data\\jak2"): - os.makedirs(dirs.user_data_dir + "\\OpenGOAL\\" + "mods\\data\\iso_data\\jak2") - FILEBROWSER_PATH = os.path.join(os.getenv("WINDIR"), "explorer.exe") - print(path) - subprocess.run([FILEBROWSER_PATH, path]) + if sys.platform == "win32": + os.startfile(path) + else: + opener = "open" if sys.platform == "darwin" else "xdg-open" + subprocess.call([opener, path]) + def replaceText(path, search_text, replace_text): # Check if the file exists @@ -203,11 +228,12 @@ def replaceText(path, search_text, replace_text): print(f"Text replaced successfully in file '{path}'.") + def ensure_dir(path): path.mkdir(parents=True, exist_ok=True) -def open_browser_link(): - url = "https://google.com" # Replace with the desired URL + +def open_browser_link(url): if sys.platform.startswith('linux'): subprocess.Popen(["xdg-open", url]) elif sys.platform.startswith('win'): @@ -217,10 +243,11 @@ def open_browser_link(): else: print("Unsupported platform") + def open_folder(path): folder_path = path # Replace with the desired folder path if not os.path.exists(path): - # Create the directory + # Create the directory try: os.makedirs(path) print(f"Directory '{path}' created successfully.") @@ -228,11 +255,13 @@ def open_folder(path): print(f"Error creating directory '{path}': {e}") os.startfile(folder_path) + def divide_by_zero(): 1 / 0 + def ensure_jak_folders_exist(): - directory = dirs.user_data_dir + "\OpenGOAL-Mods\_iso_data" + directory = ISO_PATH jak1_path = os.path.join(directory, "jak1") jak2_path = os.path.join(directory, "jak2") @@ -244,10 +273,10 @@ def ensure_jak_folders_exist(): os.makedirs(jak2_path) print(f"Created 'jak2' folder at {jak2_path}") -#check if we have decompiler in the path, if not check if we have a backup, if so use it, if not download a backup then use it + +# check if we have decompiler in the path, if not check if we have a backup, if so use it, if not download a backup then use it def getDecompiler(path): - decompiler_exe = "decompiler.exe" - decompiler_url = "https://github.com/OpenGOAL-Mods/OG-Mod-Base/raw/main/out/build/Release/bin/decompiler.exe" + decompiler_url = "https://github.com/OpenGOAL-Mods/OG-Mod-Base/raw/main/out/build/Release/bin/" + decompiler_exe # Check if the decompiler exists in the provided path if os.path.exists(os.path.join(path, decompiler_exe)): @@ -263,57 +292,61 @@ def getDecompiler(path): return + + def launch_local(MOD_ID, GAME): try: # Close Gk and goalc if they were open. - try_kill_process("gk.exe") - try_kill_process("goalc.exe") + try_kill_process(gk_exe) + try_kill_process(goalc_exe) time.sleep(1) - InstallDir = ModFolderPATH + MOD_ID - + InstallDir = ModFolderPATH / MOD_ID + if GAME == "jak2": - GKCOMMANDLINElist = [ - InstallDir + "\gk.exe", - "--proj-path", - InstallDir + "\\data", - "-v", - "--game", - "jak2", - "--", - "-boot", - "-fakeiso" - ] - else: # if GAME == "jak1": - GKCOMMANDLINElist = [ - os.path.abspath(InstallDir + "\gk.exe"), # Using os.path.abspath to get the absolute path. - "--proj-path", - os.path.abspath(InstallDir + "\\data"), # Using absolute path for data folder too. - "-boot", - "-fakeiso", - "-v", - ] - + GKCOMMANDLINElist = [ + InstallDir / gk_exe, + "--proj-path", + InstallDir / "data", + "-v", + "--game", + "jak2", + "--", + "-boot", + "-fakeiso" + ] + else: # if GAME == "jak1": + GKCOMMANDLINElist = [ + os.path.abspath(InstallDir / gk_exe), # Using os.path.abspath to get the absolute path. + "--proj-path", + os.path.abspath(InstallDir / "data"), # Using absolute path for data folder too. + "-boot", + "-fakeiso", + "-v", + ] + print("running: ", GKCOMMANDLINElist) subprocess.run(GKCOMMANDLINElist, shell=True, cwd=os.path.abspath(InstallDir)) except Exception as e: # Catch all exceptions and print the error message. return str(e) - + + + def download_and_unpack_mod(URL, MOD_ID, MOD_NAME, LINK_TYPE, InstallDir, LatestRelAssetsURL): - #start the actual update method if needUpdate is true + # start the actual update method if needUpdate is true print("\nNeed to update") print("Starting Update...") # Close Gk and goalc if they were open. - try_kill_process("gk.exe") - try_kill_process("goalc.exe") + try_kill_process(gk_exe) + try_kill_process(goalc_exe) # download update from github # Create a new directory because it does not exist - try_remove_dir(InstallDir + "/temp") - if not os.path.exists(InstallDir + "/temp"): - print("Creating install dir: " + InstallDir) - os.makedirs(InstallDir + "/temp", exist_ok=True) - + try_remove_dir(InstallDir / "temp") + if not os.path.exists(InstallDir / "temp"): + print(f"Creating install dir: {InstallDir}") + os.makedirs(InstallDir / "temp", exist_ok=True) + response = requests.get(LatestRelAssetsURL) if response.history: print("Request was redirected") @@ -331,105 +364,107 @@ def download_and_unpack_mod(URL, MOD_ID, MOD_NAME, LINK_TYPE, InstallDir, Latest print() print(str("File size is ") + str(file.length)) urllib.request.urlretrieve( - LatestRelAssetsURL, InstallDir + "/temp/updateDATA.zip", show_progress + LatestRelAssetsURL, InstallDir / "temp" / "updateDATA.zip", show_progress ) print("Done downloading") r = requests.head(LatestRelAssetsURL, allow_redirects=True) # delete any previous installation - print("Removing previous installation " + InstallDir) - try_remove_dir(InstallDir + "/data") - try_remove_dir(InstallDir + "/.github") - try_remove_dir(InstallDir + "/SND") - try_remove_file(InstallDir + "/gk.exe") - try_remove_file(InstallDir + "/goalc.exe") - try_remove_file(InstallDir + "/extractor.exe") - #jak2hack - try_remove_file(InstallDir + "/decompiler.exe") + print(f"Removing previous installation {InstallDir}") + try_remove_dir(InstallDir / "data") + try_remove_dir(InstallDir / ".github") + try_remove_dir(InstallDir / "SND") + try_remove_file(InstallDir / gk_exe) + try_remove_file(InstallDir / goalc_exe) + try_remove_file(InstallDir / extractor_exe) + # jak2hack + try_remove_file(InstallDir / decompiler_exe) # extract mod zipped update print("Extracting update") - TempDir = InstallDir + "/temp" + TempDir = InstallDir / "temp" try: - with zipfile.ZipFile(TempDir + "/updateDATA.zip", "r") as zip_ref: - zip_ref.extractall(TempDir) + with zipfile.ZipFile(TempDir / "updateDATA.zip", "r") as zip_ref: + zip_ref.extractall(TempDir) except BadZipFile as e: - print("Error while extracting from zip: ", e) - return + print("Error while extracting from zip: ", e) + return # delete the mod zipped update archive - try_remove_file(TempDir + "/updateDATA.zip") + try_remove_file(TempDir / "updateDATA.zip") SubDir = TempDir if LINK_TYPE == githubUtils.LinkTypes.BRANCH or len(os.listdir(SubDir)) == 1: - # for branches, the downloaded zip puts all files one directory down - SubDir = SubDir + "/" + os.listdir(SubDir)[0] + # for branches, the downloaded zip puts all files one directory down + SubDir = SubDir / os.listdir(SubDir)[0] - print("Moving files from " + SubDir + " up to " + InstallDir) + print(f"Moving files from {SubDir} up to {InstallDir}") allfiles = os.listdir(SubDir) for f in allfiles: - shutil.move(SubDir + "/" + f, InstallDir + "/" + f) + shutil.move(SubDir / f, InstallDir / f) try_remove_dir(TempDir) - #replace the settings and discord RPC texts automatically before we build the game. + # replace the settings and discord RPC texts automatically before we build the game. replaceText( - InstallDir + r"\data\goal_src\jak1\pc\pckernel.gc", - "Playing Jak and Daxter: The Precursor Legacy", - "Playing " + MOD_NAME, + InstallDir / "data" / "goal_src" / "jak1" / "pc" / "pckernel.gc", + "Playing Jak and Daxter: The Precursor Legacy", + "Playing " + MOD_NAME, ) replaceText( - InstallDir + r"\data\goal_src\jak1\pc\pckernel.gc", - "/pc-settings.gc", - r"/" + MOD_ID + "-settings.gc", + InstallDir / "data" / "goal_src" / "jak1" / "pc" / "pckernel.gc", + "/pc-settings.gc", + r"/" + MOD_ID + "-settings.gc", ) replaceText( - InstallDir + r"\data\goal_src\jak1\pc\pckernel-common.gc", - "/pc-settings.gc", - r"/" + MOD_ID + "-settings.gc", + InstallDir / "data" / "goal_src" / "jak1" / "pc" / "pckernel-common.gc", + "/pc-settings.gc", + r"/" + MOD_ID + "-settings.gc", ) replaceText( - InstallDir + r"\data\goal_src\jak1\pc\pckernel-common.gc", - "/pc-settings.gc", - r"/" + MOD_ID + "-settings.gc", + InstallDir / "data" / "goal_src" / "jak1" / "pc" / "pckernel-common.gc", + "/pc-settings.gc", + r"/" + MOD_ID + "-settings.gc", ) replaceText( - InstallDir + r"\data\decompiler\config\jak1_ntsc_black_label.jsonc", - "\"process_tpages\": true,", - "\"process_tpages\": false,", + InstallDir / "data" / "decompiler" / "config" / "jak1_ntsc_black_label.jsonc", + "\"process_tpages\": true,", + "\"process_tpages\": false,", ) replaceText( - InstallDir + r"\data\decompiler\config\jak1_pal.jsonc", - "\"process_tpages\": true,", - - "\"process_tpages\": false,", + InstallDir / "data" / "decompiler" / "config" / "jak1_pal.jsonc", + "\"process_tpages\": true,", + "\"process_tpages\": false,", ) + + def rebuild(URL, MOD_ID, MOD_NAME, LINK_TYPE, GAME, should_extract): - InstallDir = ModFolderPATH + MOD_ID - UniversalIsoPath = AppdataPATH + "\OpenGOAL-Mods\_iso_data" + InstallDir = ModFolderPATH / MOD_ID + UniversalIsoPath = ISO_PATH - print("Looking for some ISO data in " + UniversalIsoPath + "//" + GAME + "//") - found_universal_iso = exists(UniversalIsoPath +"//" + GAME + "//" + "Z6TAIL.DUP") + print(f"Looking for some ISO data in {UniversalIsoPath / GAME}") + found_universal_iso = exists(UniversalIsoPath / GAME / "Z6TAIL.DUP") - #if ISO_DATA has content, store this path to pass to the extractor + # if ISO_DATA has content, store this path to pass to the extractor if found_universal_iso: print("We found ISO data from a previous mod installation! Lets use it!") - print("Found in " + UniversalIsoPath +"//" + GAME + "//" + "Z6TAIL.DUP") - iso_path = UniversalIsoPath + "\\" + GAME - - if not is_junction(InstallDir + "\\data\\iso_data"): - # we have iso extracted to universal folder already, just symlink it. otherwise we'll copy it there and symlink after extractor closes - try_remove_dir(InstallDir + "\\data\\iso_data/") - makeDirSymlink(InstallDir + "\\data\\iso_data\\", UniversalIsoPath) + print(f"Found in {UniversalIsoPath / GAME / 'Z6TAIL.DUP'}") + iso_path = UniversalIsoPath / GAME + + if not is_junction(InstallDir / "data" / "iso_data"): + # we have iso extracted to universal folder already, just symlink it. otherwise we'll copy it there and symlink after extractor closes + try_remove_dir(InstallDir / "data" / "iso_data") + makeDirSymlink(InstallDir / "data" / "iso_data", UniversalIsoPath) else: - print("We did not find " + GAME + " ISO data from a previous mod, lets ask for some!") - + print("We did not find " + GAME + " ISO data from a previous mod, lets ask for some!") + # cleanup and remove a corrupted iso - if os.path.exists(UniversalIsoPath + "//" + GAME) and os.path.isdir(UniversalIsoPath) and not (exists((UniversalIsoPath + "//" + GAME + "//" + "Z6TAIL.DUP"))): + if os.path.exists(UniversalIsoPath / GAME) and os.path.isdir(UniversalIsoPath) and not ( + exists((UniversalIsoPath / GAME / "Z6TAIL.DUP"))): print("Removing corrupted iso destination...") - shutil.rmtree(UniversalIsoPath + "//" + GAME) + shutil.rmtree(UniversalIsoPath / GAME) ensure_jak_folders_exist() - + # prompt for their ISO and store its path root = tk.Tk() prompt = "Please select your " + GAME + " ISO" @@ -440,35 +475,35 @@ def rebuild(URL, MOD_ID, MOD_NAME, LINK_TYPE, GAME, should_extract): iso_path = filedialog.askopenfilename(parent=root, title=prompt) root.destroy() if iso_path == "": - print("user closed popup") - return + print("user closed popup") + return if pathlib.Path(iso_path).is_file: if not (pathlib.Path(iso_path).suffix).lower() == ".iso": print("yo, this is not an ISO: " + (pathlib.Path(iso_path).suffix).lower()) return # Close Gk and goalc if they were open. - try_kill_process("gk.exe") - try_kill_process("goalc.exe") + try_kill_process(gk_exe) + try_kill_process(goalc_exe) print("Done update starting extractor This one can take a few moments! \n") - - #Extract and compile + + # Extract and compile if GAME == "jak1": - extractor_command_list = [InstallDir + "\extractor.exe", "-f", iso_path, "-v", "-c"] + extractor_command_list = [InstallDir / extractor_exe, "-f", iso_path, "-v", "-c"] if should_extract: extractor_command_list.append("-e") extractor_command_list.append("-d") print(extractor_command_list) - extractor_result = subprocess.run(extractor_command_list, shell=True, cwd=os.path.abspath(InstallDir) ) + extractor_result = subprocess.run(extractor_command_list, shell=True, cwd=os.path.abspath(InstallDir)) if extractor_result.returncode == 0: print("done extracting!") else: print("Extractor error!") return - + elif GAME == "jak2": - extractor_command_list = [InstallDir + "\extractor.exe", "-f", iso_path, "-v", "-c", "-g", "jak2"] + extractor_command_list = [InstallDir / extractor_exe, "-f", iso_path, "-v", "-c", "-g", "jak2"] if should_extract: extractor_command_list.append("-e") extractor_command_list.append("-d") @@ -489,14 +524,16 @@ def rebuild(URL, MOD_ID, MOD_NAME, LINK_TYPE, GAME, should_extract): # move the extrated contents to the universal launchers directory for next time. if not found_universal_iso: ensure_jak_folders_exist() - moveDirContents(InstallDir + "\\data\\iso_data/" + GAME, UniversalIsoPath + "//" + GAME) + moveDirContents(InstallDir / "data" / "iso_data" / GAME, UniversalIsoPath / GAME) # replace iso_data with symlink - try_remove_dir(InstallDir + "\\data\\iso_data/") - makeDirSymlink(InstallDir + "\\data\\iso_data", UniversalIsoPath) - + try_remove_dir(InstallDir / "data" / "iso_data") + makeDirSymlink(InstallDir / "data" / "iso_data", UniversalIsoPath) + launch_local(MOD_ID, GAME) return + + def update_and_launch(URL, MOD_ID, MOD_NAME, LINK_TYPE, GAME): if URL is None: return @@ -505,7 +542,7 @@ def update_and_launch(URL, MOD_ID, MOD_NAME, LINK_TYPE, GAME): # Github API Call launchUrl = URL if LINK_TYPE == githubUtils.LinkTypes.BRANCH: - launchUrl = githubUtils.branchToApiURL(URL) + launchUrl = githubUtils.branchToApiURL(URL) LatestRelAssetsURL = "" print("\nlaunching from " + launchUrl) @@ -513,87 +550,84 @@ def update_and_launch(URL, MOD_ID, MOD_NAME, LINK_TYPE, GAME): r = json.loads(json.dumps(requests.get(url=launchUrl, params=PARAMS).json())) # paths - InstallDir = ModFolderPATH + MOD_ID - UniversalIsoPath = AppdataPATH + "\OpenGOAL-Mods\_iso_data" + InstallDir = ModFolderPATH / MOD_ID + UniversalIsoPath = ISO_PATH ensure_jak_folders_exist() # store Latest Release and check our local date too. if LINK_TYPE == githubUtils.LinkTypes.BRANCH: - LatestRel = datetime.strptime( - r.get("commit") - .get("commit") - .get("author") - .get("date") - .replace("T", " ") - .replace("Z", ""), - "%Y-%m-%d %H:%M:%S", - ) - LatestRelAssetsURL = githubUtils.branchToArchiveURL(URL) + LatestRel = datetime.strptime( + r.get("commit") + .get("commit") + .get("author") + .get("date") + .replace("T", " ") + .replace("Z", ""), + "%Y-%m-%d %H:%M:%S", + ) + LatestRelAssetsURL = githubUtils.branchToArchiveURL(URL) elif LINK_TYPE == githubUtils.LinkTypes.RELEASE: - LatestRel = datetime.strptime( - r[0].get("published_at").replace("T", " ").replace("Z", ""), - "%Y-%m-%d %H:%M:%S", - ) - assets = json.loads(json.dumps(requests.get(url=r[0].get("assets_url"), params=PARAMS).json())) - for asset in assets: - # TODO: fork here based on sys.platform - if "linux" in asset.get("name") or "macos" in asset.get("name") or "json" in asset.get("name"): - print("Release asset " + asset.get("name") + " is not for windows - SKIPPING!") - else: - print("USING: Release asset " + asset.get("name") + "! Downloading from " + asset.get("browser_download_url")) - LatestRelAssetsURL = asset.get("browser_download_url") - break - - # response = requests.get(url=LatestRelAssetsURL, params=PARAMS) - # content_type = response.headers["content-type"] + LatestRel = datetime.strptime( + r[0].get("published_at").replace("T", " ").replace("Z", ""), + "%Y-%m-%d %H:%M:%S", + ) + assets = json.loads(json.dumps(requests.get(url=r[0].get("assets_url"), params=PARAMS).json())) + for asset in assets: + # TODO: fork here based on sys.platform + if "linux" in asset.get("name") or "macos" in asset.get("name") or "json" in asset.get("name"): + print("Release asset " + asset.get("name") + " is not for windows - SKIPPING!") + else: + print("USING: Release asset " + asset.get("name") + "! Downloading from " + asset.get( + "browser_download_url")) + LatestRelAssetsURL = asset.get("browser_download_url") + break + + # response = requests.get(url=LatestRelAssetsURL, params=PARAMS) + # content_type = response.headers["content-type"] LastWrite = datetime(2020, 5, 17) - if exists(InstallDir + "/" + ExecutableName): - LastWrite = datetime.utcfromtimestamp( - pathlib.Path(InstallDir + "/" + ExecutableName).stat().st_mtime - ) + exe_path = InstallDir / ExecutableName + if exe_path.exists(): + LastWrite = datetime.utcfromtimestamp(exe_path.stat().st_mtime) # update checks Outdated = bool(LastWrite < LatestRel) - NotExtracted = bool(not (exists(UniversalIsoPath + "//" + GAME + "//" + "Z6TAIL.DUP"))) - NotCompiled = bool(not (exists(InstallDir + r"\data\out" + "//" + GAME + "//" + "fr3\GAME.fr3"))) + NotExtracted = bool(not (exists(UniversalIsoPath / GAME / "Z6TAIL.DUP"))) + NotCompiled = bool(not (exists(InstallDir / "data" / "out" / GAME / "fr3" / "GAME.fr3"))) needUpdate = bool(Outdated or NotExtracted or NotCompiled) print("Currently installed version created on: " + LastWrite.strftime('%Y-%m-%d %H:%M:%S')) print("Newest version created on: " + LatestRel.strftime('%Y-%m-%d %H:%M:%S')) - if(NotExtracted): - print("Error! Iso data does not appear to be extracted to " + UniversalIsoPath +"//" + GAME + "//" + "Z6TAIL.DUP") + if (NotExtracted): + print(f"Error! Iso data does not appear to be extracted to {UniversalIsoPath / GAME / 'Z6TAIL.DUP'}") print("Will ask user to provide ISO") - if(NotCompiled): + if (NotCompiled): print("Error! The game is not compiled") - if((LastWrite < LatestRel)): + if ((LastWrite < LatestRel)): print("Looks like we need to download a new update!") print(LastWrite) print(LatestRel) print("Is newest posted update older than what we have installed? " + str((LastWrite < LatestRel))) # attempt to migrate any old settings files from using MOD_NAME to MOD_ID - if exists(AppdataPATH + "\OpenGOAL" + "//" + GAME + "//" + "settings\\" + MOD_NAME + "-settings.gc"): + mod_name_settings_path = AppdataPATH / "OpenGOAL" / GAME / "settings" / (MOD_NAME + "-settings.gc") + if mod_name_settings_path.exists(): # just to be safe delete the migrated settings file if it already exists (shouldn't happen but prevents rename from failing below) - if exists(AppdataPATH + "\OpenGOAL" + "//" + GAME + "//" + "settings\\" + MOD_ID + "-settings.gc"): - os.remove( - AppdataPATH + "\OpenGOAL" + "//" + GAME + "//" + "settings\\" + MOD_ID + "-settings.gc" - ) + mod_id_settings_path = AppdataPATH / "OpenGOAL" / GAME / "settings" / (MOD_ID + "-settings.gc") + if mod_id_settings_path.exists(): + mod_id_settings_path.unlink() # rename settings file - os.rename( - AppdataPATH + "\OpenGOAL" + "//" + GAME + "//" + "settings\\" + MOD_NAME + "-settings.gc", - AppdataPATH + "\OpenGOAL" + "//" + GAME + "//" + "settings\\" + MOD_ID + "-settings.gc", - ) + mod_name_settings_path.rename(mod_id_settings_path) # force update to ensure we recompile with adjusted settings filename in pckernel.gc needUpdate = True - + if needUpdate: download_and_unpack_mod(URL, MOD_ID, MOD_NAME, LINK_TYPE, InstallDir, LatestRelAssetsURL) rebuild(URL, MOD_ID, MOD_NAME, LINK_TYPE, GAME, True) else: - # dont need to update, close any open instances of the game and just launch it - print("Game is up to date!") - print("Launching now!") - launch_local(MOD_ID, GAME) + # dont need to update, close any open instances of the game and just launch it + print("Game is up to date!") + print("Launching now!") + launch_local(MOD_ID, GAME) diff --git a/utils/layoutUtils.py b/utils/layoutUtils.py new file mode 100644 index 0000000..449998b --- /dev/null +++ b/utils/layoutUtils.py @@ -0,0 +1,215 @@ +import PySimpleGUI as sg +from utils import githubUtils + + +# TODO: Set sizes of columns, frames, and other elements +# based on the user's screen resolution and screen size. Using hard coded +# values can cause elements to not be displayed on large screens with +# high resolution + +def generate(splashfile, loadingimage, LATEST_TABLE_DATA, table_headings, col_vis, col_width): + return \ + [ + [ + sg.Frame( + title="", + key="-SPLASHFRAME-", + border_width=0, # Set border_width to 0 + visible=True, + element_justification="center", + vertical_alignment="center", + layout=[ + [ + sg.Image( + key="-SPLASHIMAGE-", + source=githubUtils.resize_image(splashfile, 970, 607), + pad=(0, 0), # Set padding to 0 + expand_x=True, + expand_y=True, + ) + ] + ], + ) + ], + [ + sg.Frame( + title="", + key="-LOADINGFRAME-", + border_width=0, # Set border_width to 0 + visible=False, + element_justification="center", + vertical_alignment="center", + layout=[ + [ + sg.Image( + key="-LOADINGIMAGE-", + source=githubUtils.resize_image(loadingimage, 970, 607), + pad=(0, 0), # Set padding to 0 + expand_x=True, + expand_y=True, + ) + ] + ], + ) + ], + [ + sg.Frame( + title="", + key="-MAINFRAME-", + border_width=0, + visible=False, + layout=[ + [ + __side_panel(), + sg.VerticalSeparator(), + __main_panel(LATEST_TABLE_DATA, table_headings, col_vis, col_width) + ] + ] + ) + ], + ] + + +def __side_panel(): + return sg.Column( # nav sidebar + [ + [sg.Text("JAK 1", font=("Helvetica", 16, "bold"))], + [__default_radio("Mods", "filter", "jak1/mods")], + [__radio("Texture Packs", "filter", "jak1/tex")], + + [sg.Text("")], + + [sg.Text("JAK 2", font=("Helvetica", 16, "bold"))], + [__radio("Mods", "filter", "jak2/mods")], + [__radio("Texture Packs", "filter", "jak2/tex")], + + [sg.VPush()], + + [__button("View iso_data Folder", "-VIEWISOFOLDER-")], + [__button("jakmods.dev", "-JAKMODSWEB-")], + ], + expand_y=True, + ) + + +def __main_panel(LATEST_TABLE_DATA, table_headings, col_vis, col_width): + install_filter = sg.Checkbox( + text="Show Installed", + default=True, + enable_events=True, + key="-SHOWINSTALLED-", + ) + + uninstall_filter = sg.Checkbox( + text="Show Uninstalled", + default=True, + enable_events=True, + key="-SHOWUNINSTALLED-", + ) + + search_bar = sg.Input( + expand_x=True, + enable_events=True, + key="-FILTER-" + ) + + mod_table = sg.Table( + values=LATEST_TABLE_DATA, + headings=table_headings, + visible_column_map=col_vis, + col_widths=col_width, + auto_size_columns=False, + num_rows=15, + text_color="black", + background_color="lightblue", + alternating_row_color="white", + justification="left", + selected_row_colors="black on yellow", + key="-MODTABLE-", + expand_x=True, + expand_y=True, + enable_click_events=True + ) + + mod_image = sg.Frame( + title="", + element_justification="center", + vertical_alignment="center", + border_width=0, + layout=[ + [ + sg.Image( + key="-SELECTEDMODIMAGE-", expand_y=True + ) + ] + ], + size=(450, 300), + ) + + button_panel_with_mod_info = sg.Column( + [ + [sg.Text("", key="-SELECTEDMODNAME-", font=("Helvetica", 13), metadata={"id": "", "url": ""})], + [sg.Text("", key="-SELECTEDMODDESC-", size=(45, 7))], + [sg.Text("Tags:", key="-SELECTEDMODTAGS-")], + [sg.Text("Contributors:", key="-SELECTEDMODCONTRIBUTORS-")], + [sg.Text("")], + [__button("Launch", "-LAUNCH-"), __button("Re-extract", "-REEXTRACT-"), + __button("Recompile", "-RECOMPILE-"), __button("Uninstall", "-UNINSTALL-")], + [__button("View Folder", "-VIEWFOLDER-"), __button_with_metadata("Website", "-WEBSITE-"), + __button_with_metadata("Video(s)", "-VIDEOS-")] + ], + # Increased y from 300 to 425, since higher display screens do not + # have room to render these buttons. This is a temp fix until + # a programmatic approach is taken to determine element sizes. + size=(200, 425), + expand_x=True, + expand_y=True, + ) + + return sg.Column( + [ + [button_panel_with_mod_info, mod_image], + [sg.HorizontalSeparator()], + [sg.Text("Search"), search_bar, install_filter, uninstall_filter], + [mod_table] + + ] + ) + + +def __button(button_text, key): + return sg.Btn( + button_text=button_text, + key=key, + expand_x=True, + ) + + +def __button_with_metadata(button_text, key): + return sg.Btn( + button_text=button_text, + key=key, + expand_x=True, + metadata={"url": ""} + ) + + +def __radio(text, group_id, key): + return sg.Radio( + text=text, + group_id=group_id, + font=("Helvetica", 12), + enable_events=True, + key=key + ) + + +def __default_radio(text, group_id, key): + return sg.Radio( + text=text, + group_id=group_id, + font=("Helvetica", 12), + enable_events=True, + key=key, + default=True + )