diff --git a/functions.py b/functions.py new file mode 100644 index 0000000..14aa405 --- /dev/null +++ b/functions.py @@ -0,0 +1,152 @@ +import requests +import zipfile +import json +import os +import io +import platform +import subprocess +import shutil + +def load_json(file): + with open(file, 'r') as f: + return json.load(f) + +def open_settings(): + return load_json(".\\settings.json") + +def open_folder(path): + if path is None: + print(f"Error: Attempted to open a folder with a None path: {path}") + return + + if not os.path.exists(path): + print(f"Error: The specified path does not exist: {path}") + return + + if platform.system() == "Windows": + os.startfile(str(path)) + elif platform.system() == "Darwin": + subprocess.run(["open", str(path)]) + else: + subprocess.run(["xdg-open", str(path)]) + +def download_and_extract(url, extract_to): + response = requests.get(url) + if response.status_code == 200: + with zipfile.ZipFile(io.BytesIO(response.content)) as zip_ref: + zip_ref.extractall(extract_to) + print(f"Extracted {url} to {extract_to}") + return True + else: + print(f"Failed to download {url}") + return False + +def get_latest_bepinex_url(): + try: + response = requests.get("https://api.github.com/repos/BepInEx/BepInEx/releases/latest") + response.raise_for_status() + release_info = response.json() + assets = release_info.get('assets', []) + + user_os = platform.system().lower() + if user_os == "windows": + user_os = "win" + elif user_os == "darwin": + user_os = "macos" + else: + user_os = "linux" + + for asset in assets: + if f"{user_os}_x64" in asset['name'] and asset['name'].endswith('.zip'): + return asset['browser_download_url'] + except requests.exceptions.RequestException as e: + print(f"Failed to fetch release info from https://api.github.com/repos/BepInEx/BepInEx/releases/latest: {e}") + return None + +class bind_func: + settings = open_settings() + game_directory = settings["gameDir"] + + @staticmethod + def print(txt): + print(txt) + + @staticmethod + def open_folder(dir): + open_folder(dir) + + @staticmethod + def open_game_folder(): + if not bind_func.game_directory: + return False, "Game directory is not set" + if not os.path.exists(bind_func.game_directory): + return False, f"Game directory does not exist: {bind_func.game_directory}" + open_folder(bind_func.game_directory) + return True, f"Opened game folder: {bind_func.game_directory}" + + @staticmethod + def open_settings(): + return open_settings() + + @staticmethod + def get_os(): + return platform.system() + + @staticmethod + def set_game_directory(dir): + bind_func.game_directory = dir + return True + + @staticmethod + def install_modloader(): + success = True + messages = [] + + # Install BepInEx + bepinex_url = get_latest_bepinex_url() + if bepinex_url: + if download_and_extract(bepinex_url, bind_func.game_directory): + os.remove(f"{bind_func.game_directory}\\.doorstop_version") + os.remove(f"{bind_func.game_directory}\\doorstop_config.ini") + os.remove(f"{bind_func.game_directory}\\changelog.txt") + os.remove(f"{bind_func.game_directory}\\winhttp.dll") + messages.append("BepInEx installed successfully") + else: + success = False + messages.append("Failed to install BepInEx") + else: + success = False + messages.append("Failed to get the latest BepInEx download URL") + + # Install MelonLoader + melonloader_url = "https://github.com/LavaGang/MelonLoader/releases/latest/download/MelonLoader.x64.zip" + if download_and_extract(melonloader_url, bind_func.game_directory): + messages.append("MelonLoader installed successfully") + else: + success = False + messages.append("Failed to install MelonLoader") + + return success, "\n".join(messages) + + @staticmethod + def uninstall_modloader(): + folders = [ + f"{bind_func.game_directory}\\BepInEx", + f"{bind_func.game_directory}\\MelonLoader", + f"{bind_func.game_directory}\\Plugins", + f"{bind_func.game_directory}\\Mods", + f"{bind_func.game_directory}\\UserData", + f"{bind_func.game_directory}\\UserLibs" + ] + + files = [ + f"{bind_func.game_directory}\\dobby.dll", + f"{bind_func.game_directory}\\NOTICE.txt", + f"{bind_func.game_directory}\\version.dll" + ] + + for folder in folders: + shutil.rmtree(folder) + + for file in files: + os.remove(file) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..f117e48 --- /dev/null +++ b/main.py @@ -0,0 +1,64 @@ +import webview +import os +import sys +import ctypes +import json +import inspect +import platform + +def start_window(type, error=""): + import functions + if type == "normal": + window = webview.create_window(title='AL2 Factory Mod Manager', url='win/index.html', resizable=False, width=800, height=600) + for name, method in inspect.getmembers(functions.bind_func, predicate=inspect.isfunction): + window.expose(method) + webview.start() + elif type == "error": + ctypes.windll.user32.MessageBoxW(0, error, "AL2 Factory Mod Manager - Error", 0) + sys.exit(1) + +def check_program_files(): + programFiles = { + ".\\win\\": False, + ".\\settings.json": False + } + + if not os.path.exists(".\\win\\") or not os.listdir(".\\win\\"): + return programFiles, "Error: Missing essential files in 'win' directory! Please reinstall the application." + + for item in programFiles: + if not os.path.exists(item): + try: + if item == ".\\settings.json": + with open(item, 'w') as f: + gameDir = "" + if platform.system() == "Windows": + gameDir = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Assembly Line 2" + elif platform.system() == "Darwin": + gameDir = "/Applications/Assembly Line 2" + else: + gameDir = "/home/user/.steam/steam/steamapps/common/Assembly Line 2" + json.dump({"theme": "dark", "gameDir": f"{gameDir}"}, f, indent=4) + elif "." in os.path.basename(item): + with open(item, 'w') as f: + pass + else: + os.mkdir(item) + programFiles[item] = True + except Exception as e: + return programFiles, f"Error creating '{item}': {str(e)}" + else: + programFiles[item] = True + return programFiles, "" + +if __name__ == '__main__': + program_status, error_msg = check_program_files() + + if error_msg: + print(error_msg, file=sys.stderr) + start_window("error", error_msg) + elif all(program_status.values()): + start_window("normal") + else: + print("Error: Unknown error occurred during initialization.", file=sys.stderr) + start_window("error", "Unknown error occurred during initialization.") \ No newline at end of file diff --git a/win/css/index.css b/win/css/index.css new file mode 100644 index 0000000..b7ade96 --- /dev/null +++ b/win/css/index.css @@ -0,0 +1,19 @@ +html, +body { + width: 100%; + height: 100%; + padding: 0; + margin: 0; + display: block; + overflow: hidden; +} + +.content { + width: 100%; + height: 100%; +} + +#title { + width: 100%; + text-align: center; +} \ No newline at end of file diff --git a/win/index.html b/win/index.html new file mode 100644 index 0000000..a9fab60 --- /dev/null +++ b/win/index.html @@ -0,0 +1,16 @@ + + + + + + + +
+

AL2 Factory Mod Manager

+ + + +
+ + + \ No newline at end of file diff --git a/win/js/index.js b/win/js/index.js new file mode 100644 index 0000000..961e601 --- /dev/null +++ b/win/js/index.js @@ -0,0 +1,36 @@ +var settings = "" + +function print(txt) { + pywebview.api.print(txt).then(() => {}).catch((error) => {}) +} + +function get_settings() { + pywebview.api.open_settings().then((settingsFile) => { + settings = settingsFile + }).catch((error) => {}) +} + +function open_folder(dir) { + pywebview.api.open_folder(dir).then(() => {}).catch((error) => {}) +} + +function open_game_folder() { + pywebview.api.open_game_folder().then(() => {}).catch((error) => {}) +} + +function install_modloader() { + pywebview.api.install_modloader().then((result) => { + print(result[1]); + if (!result[0]) { + print("Failed to install one or more modloaders"); + } + }).catch((error) => { + print("Error during modloader installation:", error); + }); +} + +function uninstall_modloader() { + pywebview.api.uninstall_modloader().then(() => {}).catch((error) => {}) +} + +get_settings() \ No newline at end of file