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): | 
