diff options
-rwxr-xr-x | installer.py | 25 | ||||
-rwxr-xr-x | modpackman.py | 4 | ||||
-rw-r--r-- | packs/jeffrey-3/pack.ini | 2 | ||||
-rw-r--r-- | readme.md | 63 | ||||
-rw-r--r-- | util.py | 27 |
5 files changed, 100 insertions, 21 deletions
diff --git a/installer.py b/installer.py index deecd5b..6e2a758 100755 --- a/installer.py +++ b/installer.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import os import sys @@ -24,11 +25,16 @@ def install_forge(): Downloads and runs the Forge installer specified in pack.ini. """ with tempfile.TemporaryDirectory() as working_dir: - forge_dl = requests.get(config['pack']['forge_url'], stream=True) forge_path = os.path.join(working_dir, "forge_installer.jar") - with open(forge_path, 'wb') as f: - shutil.copyfileobj(forge_dl.raw, f) - subprocess.check_output([util.find_jre(), "-jar", forge_path]) + util.download_file(config['pack']['forge_url'], forge_path) + try: + subprocess.check_output([util.find_jre(), "-jar", forge_path]) + except RuntimeError: + if sys.platform == 'win32': + subprocess.check_output(["start", forge_path]) + else: + raise + def setup_forge(profile_id): path_to_profiles = os.path.join(util.find_minecraft_directory(), "launcher_profiles.json") @@ -59,26 +65,33 @@ def setup_forge(profile_id): "lastVersionId": forge_game_version, "type": "custom", "javaArgs": config["pack"]["java_args"], - "icon": util.generate_base64_icon(config["pack"]["icon_name"]) + "icon": util.generate_base64_icon("icon.png") } profiles["profiles"][profile_id] = profile else: profile = profiles["profiles"][profile_id] profile["lastVersionId"] = forge_game_version - profile["icon"] = util.generate_base64_icon(config["pack"]["icon_name"]) + profile["icon"] = util.generate_base64_icon("icon.png") with open(path_to_profiles, "w") as f: json.dump(profiles, f, indent=2) def main(): + # if we're in a bundle, download the latest pack data from remote source + if hasattr(sys, "_MEIPASS"): + update_self() + persistent_data_path = os.path.join(config["pack"]["location"], "modpackman.json") if os.path.exists(persistent_data_path): with open(persistent_data_path, "r") as f: persistent_data = json.load(f) else: + # this is the first time this pack is installed pathlib.Path(config["pack"]["location"]).mkdir(parents=True, exist_ok=True) persistent_data = {"last_forge_url": "no", "profile_id": str(uuid.uuid4()).replace('-', '')} + if os.path.exists(os.path.join(util.find_minecraft_directory(), 'options.txt')): + shutil.copyfile(os.path.join(util.find_minecraft_directory(), 'options.txt'), os.path.join(config["pack"]["location"], "options.txt")) if config["pack"]["forge_url"] != persistent_data["last_forge_url"]: setup_forge(persistent_data["profile_id"]) diff --git a/modpackman.py b/modpackman.py index f578db4..18cdecc 100755 --- a/modpackman.py +++ b/modpackman.py @@ -45,9 +45,7 @@ def install(): else: print(f'Installing {name} from {url}...') print(f' ({i} of {len(pack_lock["mod_versions"])})', end='\r') - download_obj = requests.get(url, stream=True) - with open(mod_path, "wb") as write_file: - shutil.copyfileobj(download_obj.raw, write_file) + util.download_file(url, mod_path) print("Done!" + " " * 8) # Remove any old mods that might be stuck in the mods folder diff --git a/packs/jeffrey-3/pack.ini b/packs/jeffrey-3/pack.ini index bb186fc..20ba576 100644 --- a/packs/jeffrey-3/pack.ini +++ b/packs/jeffrey-3/pack.ini @@ -1,8 +1,8 @@ [pack] name = J.E.F.F.R.E.Y. 3 +pack_base_url = https://gitlab.com/1F335/modpackman/-/raw/refactor-fix/packs/jeffrey-3/ forge_url = https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.16.4-35.1.13/forge-1.16.4-35.1.13-installer.jar game_version = 1.16.4 -icon_name = icon.png java_args = -Xmx6G -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M [mods] @@ -3,20 +3,67 @@ Script to update modpacks automatically -#### To Use +### Installation -First, install [Python 3](https://www.python.org/downloads/) and [Git](https://git-scm.com/downloads) and add them to your `$PATH`. -Then, run `pip install requests` to install the Python Requests module (required to run the script). +#### For *Windows* users: -Simply put the location of your `mods` folder in `pack-location.txt` and run `python update.py install` +1. Close *Minecraft* and *Minecraft Launcher*. -#### Maintenance: +2. Download the latest installer from [the releases page](https://gitlab.com/1F335/modpackman/-/releases) and run it, completing the Forge prompt. -To check `version.txt` modlist for updates against `mods.txt` modlist, run `python update.py check_updates`. +3. Start *Minecraft Launcher* and launch the newly installed modpack profile. -To automatically populate `version.txt` with the most recent versions of mods listed in `mods.txt` run `python update.py apply_updates`. -Finally, to actually install mods from the list in `version.txt`, run `python update.py install` +#### For other platforms: +1. Install Git, Python 3, Java, and *Minecraft*. + +2. Close *Minecraft* and *Minecraft Launcher*. + +3. Install the Python `requests` module with `pip install requests` (or `pip install --user requests` if the first one fails). + +4. Clone or download this repository. + +5. In a shell, navigate to the directory of your desired pack (e.g. `cd packs/jeffrey-3` for the J.E.F.F.R.E.Y. 3 modpack). + +6. Run the installer with `python ../../installer.py` + +7. Start *Minecraft Launcher* and launch the newly installed modpack profile. + + +### Maintenance: + +To select a pack to work on, navigate to its directory (e.g. `cd packs/jeffrey-3` for the J.E.F.F.R.E.Y. 3 modpack). + +Run `python ../../modpackman.py apply_updates` to update `pack-lock.ini` with the most recent compatible versions of every mod specified in this pack's `pack.ini`. This will also update the list of bundled config files and increment the pack version. + +Run `python ../../modpackman.py check_updates` to print out available updates for all the mods specified in this pack. + +To bypass everything except the mod downloads, run `python ../../modpackman.py install`. This will install all the mods specified in this pack's `pack-lock.ini` ***NOTE***: `check_updates` and `apply_updates` require you to have the `selenium` module installed + + +### Configuration: + +All of the data related to specific pack is stored in its folder in `packs/`. This includes: + - The icon for the launcher profile (`icon.png`) + - The icon for the executable installer (`icon.ico`) + - The default mod config files (`config/`) + - The modpackman pack configuration (`pack.ini`) + - The current pack version information (`pack-lock.ini`) + +Note: you can create a file `local-config.ini` in this folder on your local machine that will override any specified values in `pack.ini` + +`pack.ini` has two sections: + + - `pack`, with these options: + - `name`: the user-friendly name of the modpack + - `pack_base_url`: the web url from whence the pack's data may be retrieved + - `forge_url`: the download url for the forge installer for this pack. (note: this skips the Forge ads, consider supporting the project directly instead) + - `game_version`: the maximum *Minecraft* version to update mods to + - `java_args`: Java arguments to be added to the pack launcher profile when it is created. + + - `mods`, which is a collection of `mod_name = download_url` pairs. For mods hosted on curseforge, the download url is the project's homepage url. + + @@ -9,7 +9,7 @@ import urllib.parse import multiprocessing import pathlib import base64 -from configparser import ConfigParser +from configparser import RawConfigParser import requests @@ -19,7 +19,7 @@ def load_config(): Load configuarion from pack and local configuration files Fill in reasonable defaults where applicable. """ - config_p = ConfigParser() + config_p = RawConfigParser() config_p.read(["pack.ini", "local-config.ini"]) config = config_p._sections config["pack"]["sanitized_name"] = sanitize_text(config["pack"]["name"]) @@ -44,8 +44,21 @@ def update_self(): in order to update to the latest version. Will overwrite any existing pack.ini and other config files, so be careful! """ + global config + + base_url = config["pack"]["pack_base_url"].strip("/") + "/" + download_file(base_url + "pack.ini", "pack.ini") + download_file(base_url + "pack-lock.ini", "pack-lock.ini") + download_file(base_url + "icon.png", "icon.png") + + pack_lock = RawConfigParser() + pack_lock.read(["pack-lock.ini"]) + for path in pack_lock["global"]["config_files"].split(","): + if not path: + continue + download_file(f"{base_url}config/{path}", os.path.join("config", path)) - raise NotImplementedError() + config = load_config() def find_minecraft_directory(): @@ -78,6 +91,14 @@ def find_jre(): return "C:\\Program Files (x86)\\Minecraft Launcher\\runtime\\jre-x64\\java.exe" raise RuntimeError("Unable to detect an installed JRE. Please install Java in order to use modpackman.") +def download_file(url, destination): + """ + given a url, performs a requests request to get the remote object + and write it to destination + """ + with open(destination, "wb") as f: + dl = requests.get(url, stream=True) + shutil.copyfileobj(dl.raw, f) # take a string and only keep filename-friendly parts def sanitize_text(text): |