diff options
-rw-r--r-- | .gitignore | 11 | ||||
-rwxr-xr-x | installer.py | 111 | ||||
-rw-r--r-- | installer.spec | 48 | ||||
-rwxr-xr-x | modpackman.py | 171 | ||||
-rw-r--r-- | mods.txt | 48 | ||||
-rw-r--r-- | packs/jeffrey-2/icon.ico | bin | 0 -> 1662 bytes | |||
-rw-r--r-- | packs/jeffrey-2/icon.png | bin | 0 -> 1195 bytes | |||
-rw-r--r-- | packs/jeffrey-2/pack-lock.ini | 52 | ||||
-rw-r--r-- | packs/jeffrey-2/pack.ini | 54 | ||||
-rw-r--r-- | packs/jeffrey-2/version.txt (renamed from version.txt) | 0 | ||||
-rw-r--r-- | packs/jeffrey-3/.gitignore | 8 | ||||
-rw-r--r-- | packs/jeffrey-3/config/creeperconfetti-common.toml | 8 | ||||
-rw-r--r-- | packs/jeffrey-3/icon.ico | bin | 0 -> 9662 bytes | |||
-rw-r--r-- | packs/jeffrey-3/icon.png | bin | 0 -> 923 bytes | |||
-rw-r--r-- | packs/jeffrey-3/local-config.ini | 9 | ||||
-rw-r--r-- | packs/jeffrey-3/pack-lock.ini | 86 | ||||
-rw-r--r-- | packs/jeffrey-3/pack.ini | 100 | ||||
-rw-r--r-- | readme.md | 63 | ||||
-rw-r--r-- | todo.md | 7 | ||||
-rwxr-xr-x | update.py | 248 | ||||
-rw-r--r-- | util.py | 309 | ||||
-rw-r--r-- | whitelist.txt | 1 |
22 files changed, 1028 insertions, 306 deletions
@@ -1 +1,10 @@ -pack-location.txt +local-config.ini +geckodriver +geckodriver.exe +geckodriver.log +__pycache__/ +*.swp +*.swo +*.log +build/ +dist/ diff --git a/installer.py b/installer.py new file mode 100755 index 0000000..bdad7a6 --- /dev/null +++ b/installer.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +import os +import sys + +if hasattr(sys, '_MEIPASS'): # we're running in a bundle, go where we have our bundled assets + os.chdir(sys._MEIPASS) + +import subprocess +import requests +import tempfile +import shutil +import subprocess +import json +import uuid +import pathlib + +from modpackman import install +from util import config +import util + + +def install_forge(): + """ + :param java_path: path to a working Java executable + Downloads and runs the Forge installer specified in pack.ini. + """ + with tempfile.TemporaryDirectory() as working_dir: + forge_path = os.path.join(working_dir, "forge_installer.jar") + 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': + # if we can't find java, see if Windows can... + subprocess.check_output([f'cmd /C start "" "{forge_path}"']) + else: + raise + + +def setup_forge(profile_id): + path_to_profiles = os.path.join(util.find_minecraft_directory(), "launcher_profiles.json") + # first, find current profiles so we can figure out which forge installs + with open(path_to_profiles, "r") as f: + profiles = json.load(f) + old_profile_ids = set(profiles["profiles"].keys()) + + # install forge, should add a new profile + install_forge() + + with open(path_to_profiles, "r") as f: + profiles = json.load(f) + difference = set(profiles["profiles"].keys()) - old_profile_ids + if difference: + forge_profile_id = next(difference) + forge_game_version = profiles["profiles"][forge_profile_id]["lastVersionId"] + del profiles["profiles"][forge_profile_id] + else: + # this will probably break soon :( + game_version, forge_version = config["pack"]["forge_url"].split("/")[-2].split('-') + forge_game_version = f"{game_version}-forge-{forge_version}" + + if profile_id not in profiles["profiles"]: + profile = { + "name": config["pack"]["name"], + "gameDir": config["pack"]["location"], + "lastVersionId": forge_game_version, + "type": "custom", + "javaArgs": config["pack"]["java_args"], + "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("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"): + util.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"]) + persistent_data["last_forge_url"] = config["pack"]["forge_url"] + with open(persistent_data_path, "w") as f: + json.dump(persistent_data, f, indent=2) + + ##todo install mods + install() + + + + + +if __name__ == '__main__': + main() diff --git a/installer.spec b/installer.spec new file mode 100644 index 0000000..dca793c --- /dev/null +++ b/installer.spec @@ -0,0 +1,48 @@ +# -*- mode: python ; coding: utf-8 -*- +import os +import sys + +# included data is based on cwd +cwd = os.getcwd() +pack_name = cwd.split(os.path.sep)[-1] +j = os.path.join + +block_cipher = None + + +a = Analysis(['installer.py'], + pathex=[cwd], + binaries=[], + datas=[ + (j(cwd, 'pack-lock.ini'), '.'), + (j(cwd, 'pack.ini'), '.'), + (j(cwd, 'icon.png'), '.'), + (j(cwd, 'config'), 'config') + ], + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) + +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name=f'{pack_name}-installer', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + icon=j(cwd, 'icon.ico') + ",0") diff --git a/modpackman.py b/modpackman.py new file mode 100755 index 0000000..e5e5fdc --- /dev/null +++ b/modpackman.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +import os +import sys + +if hasattr(sys, '_MEIPASS'): # we're running in a bundle, go to where we have our bundled assets + os.chdir(sys._MEIPASS) + +import argparse +import shutil +import requests +import pathlib +import hashlib +from configparser import RawConfigParser + +import util +from util import config + +# Apply updates to the actual mod pack +def install(): + mods_location = os.path.join(config["pack"]["location"], "mods") + whitelist = config["pack"]["whitelist"] + blacklist = config["pack"]["blacklist"] + + # Actual download links are stored in pack-lock.ini, so we need to read it in here + pack_lock = RawConfigParser() + pack_lock.read('pack-lock.ini') + + print(f"Updating pack with version {pack_lock['global']['pack_version']}...") + + # create the mods folder if it doesn't already exist + pathlib.Path(mods_location).mkdir(parents=True, exist_ok=True) + + names = set(f'{mod}.jar' for mod in pack_lock['mod_versions'].keys()) + # whitelist client mods (e.g. optifine) + names.update(whitelist) + + i = 0 + for entry in pack_lock['mod_versions'].items(): + name = f'{entry[0]}.jar' + checksum, url = entry[1].split(',') + mod_path = os.path.join(mods_location, name) + i += 1 + if (os.path.exists(mod_path) and os.path.isfile(mod_path) and \ + hashlib.sha1(open(mod_path, 'rb').read()).hexdigest() == checksum) or \ + name in blacklist: + print(f"Skipping {name}, already up to date") + else: + print(f'Installing {name} from {url}...') + print(f' ({i} of {len(pack_lock["mod_versions"])})', end='\r') + util.download_file(url, mod_path) + print("Done!" + " " * 8) + + # Remove any old mods that might be stuck in the mods folder + print() + print("Removing old mods...") + for jar in os.listdir(mods_location): + if jar not in names and os.path.splitext(jar)[1] == ".jar": + os.remove(os.path.join(mods_location, jar)) + print(f"Removing '{jar}'") + + + print('\nInstalling configs...') + # For config files, we don't need to remove any extras, as they + # will simply be ignored by Forge + if not os.path.exists('config'): + raise RuntimeError("Error: config folder must exist!") + + mc_config_folder = os.path.join(config['pack']['location'], 'config') + if not os.path.exists(mc_config_folder): + os.mkdir(mc_config_folder) + for cfg in os.listdir('config'): + shutil.copyfile(os.path.join('config', cfg), os.path.join(mc_config_folder, cfg)) + + print() + print("Finished installing mods!") + + + +def apply_updates(): + """ + Using the URLs defined in pack.ini, update all mods to the latest + compatible version and write them out to pack-lock.ini + """ + print("Populating version file...") + print("Getting new versions of all mods...") + mod_urls = util.find_updated_urls([x for x in config['mods'].values()], config['pack']['game_version'], threads=16) + print("Downloading and checksumming all mods...") + if None in mod_urls: + print("[!] Checksum generation aborted due to invalid URLs. Please fix them and try again.") + exit(1) + checksums = util.find_checksums(mod_urls) + + # Write the updated mods list out to pack-lock.ini + pack_lock = RawConfigParser() + pack_lock.read('pack-lock.ini') + pack_lock['global']['pack_version'] = str(int(pack_lock['global']['pack_version']) + 1) + # This is only needed for the self-update feature + pack_lock['global']['config_files'] = ','.join(os.listdir('config')) + pack_lock['mod_versions'] = {name: f'{checksum},{url}' for name, checksum, url in zip(config['mods'].keys(), checksums, mod_urls)} + with open('pack-lock.ini', 'w') as f: + pack_lock.write(f) + + print() + print("Done!") + print("Updates applied to pack-lock.ini") + print(f"New pack version is {pack_lock['global']['pack_version']}") + print("[!] No mods were installed. To update your mods folder, run 'update.py install'") + + +# Find if any updates are available +def check_updates(mods, version_file, version=(2, 0, 0)): + pack_version = util.get_version_from_file(version_file) + print("Checking for updates to version " + str(pack_version) + "...") + latest = [(k, mods[k]) for k in mods.keys()] + old = util.read_file(version_file) + old_urls = [mod[2] for mod in old] + num_updates = 0 + + print("Checking updates...") + ffx = util.firefox() + + for mod in latest: + print("Checking for updates to {mod[0]}...".format(mod=mod), end="") + sys.stdout.flush() # takes care of line-buffered terminals + if 'curseforge' in mod[1]: + url = util.find_cdn(ffx, mod[1], version) + else: + url = requests.get(mod[1]).url + if url in old_urls: + print(" No updates") + else: + print(" Found update: " + url.split('/')[-1]) + num_updates += 1 + ffx.close() + + print("Finished checking for updates. {num} mods can be updated".format(num=num_updates)) + if num_updates >= 0: + print("Run 'python update.py apply_updates' to create a new version with these updates applied.") + + +parser = argparse.ArgumentParser( + description="A Simple Git-Based Modpack Manager", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog='''\ +Available commands: + install : Downloads mods listed in version.txt and populates the mods folder specified in pack-location.txt + apply_updates : Using the urls in mods.txt, repopulates version.txt to reflect the most recent mod versions + check_updates : Compares version.txt and mods.txt to see if any mods can be updated +''') + +parser.add_argument('command', + nargs='?', + default='install', + help="The action to perform (default: install)") + +if __name__ == "__main__": + args = parser.parse_args() + mods = config['mods'] + pack = config['pack'] + + # run the command + if args.command == "install": + install() + elif args.command == "apply_updates": + apply_updates() + elif args.command == "check_updates": + check_updates(config['mods'], "version.txt", config['pack']["game_version"]) + else: + print("Error: command \"" + args.command + "\" does not exist") + parser.print_help() + sys.exit(1) diff --git a/mods.txt b/mods.txt deleted file mode 100644 index ee5337d..0000000 --- a/mods.txt +++ /dev/null @@ -1,48 +0,0 @@ -# fill this file with the download links for mods, or the curseforge mod main page -# e.g. -twilightforest.jar https://www.curseforge.com/minecraft/mc-mods/the-twilight-forest -tconstruct.jar https://www.curseforge.com/minecraft/mc-mods/tinkers-construct -baubles.jar https://www.curseforge.com/minecraft/mc-mods/baubles -jei.jar https://www.curseforge.com/minecraft/mc-mods/jei -psi.jar https://www.curseforge.com/minecraft/mc-mods/psi -opencomputers.jar https://www.curseforge.com/minecraft/mc-mods/opencomputers -chisel-and-bits.jar https://www.curseforge.com/minecraft/mc-mods/chisels-bits -chisel.jar https://www.curseforge.com/minecraft/mc-mods/chisel -dynamictrees.jar https://www.curseforge.com/minecraft/mc-mods/dynamictrees -dynamictrees-pams-compat.jar https://www.curseforge.com/minecraft/mc-mods/dtphc -dynamictreestraverse.jar https://f-1.karel.pw/dttraverse-1.4.1e.jar -traverse.jar https://media.forgecdn.net/files/2613/657/Traverse-1.12.2-1.6.0-69.jar -roots.jar https://www.curseforge.com/minecraft/mc-mods/roots -ae2.jar https://www.curseforge.com/minecraft/mc-mods/applied-energistics-2 -gravestones.jar https://www.curseforge.com/minecraft/mc-mods/gravestone-mod -enderstorage.jar https://www.curseforge.com/minecraft/mc-mods/ender-storage-1-8 -waila.jar https://media.forgecdn.net/files/2568/751/Hwyla-1.8.26-B41_1.12.2.jar -harvestcraft.jar https://www.curseforge.com/minecraft/mc-mods/pams-harvestcraft -akashictome.jar https://www.curseforge.com/minecraft/mc-mods/akashic-tome -railcraft.jar https://www.curseforge.com/minecraft/mc-mods/railcraft -optifine.jar https://karel.pw/optifine-1.12.2.jar -autoreglib.jar https://media.forgecdn.net/files/2746/11/AutoRegLib-1.3-32.jar -ctm.jar https://www.curseforge.com/minecraft/mc-mods/ctm -codechickenlib.jar https://www.curseforge.com/minecraft/mc-mods/codechicken-lib-1-8 -mysticallib.jar https://media.forgecdn.net/files/2865/499/mysticallib-1.12.2-1.6.0.jar -patchouli.jar https://media.forgecdn.net/files/2731/963/Patchouli-1.0-20.jar -mysticalworld.jar https://media.forgecdn.net/files/2838/234/mysticalworld-1.12.2-1.6.1.jar -diet-hoppers.jar https://www.curseforge.com/minecraft/mc-mods/diet-hoppers -worsebarrels.jar https://www.curseforge.com/minecraft/mc-mods/worse-barrels -wearable-backpacks.jar https://www.curseforge.com/minecraft/mc-mods/wearable-backpacks -eerie-entities.jar https://www.curseforge.com/minecraft/mc-mods/eerie-entities -bookshelf.jar https://media.forgecdn.net/files/2836/960/Bookshelf-1.12.2-2.3.590.jar -veining.jar https://www.curseforge.com/minecraft/mc-mods/veining -minecoprocessors.jar https://www.curseforge.com/minecraft/mc-mods/minecoprocessors -librarianlib.jar https://www.curseforge.com/minecraft/mc-mods/librarianlib -forgelin.jar https://media.forgecdn.net/files/2785/465/Forgelin-1.8.4.jar -dimdoors.jar https://www.curseforge.com/minecraft/mc-mods/dimensionaldoors -translocators.jar https://www.curseforge.com/minecraft/mc-mods/translocators-1-8 -forgemultipartcbe.jar https://www.curseforge.com/minecraft/mc-mods/forge-multipart-cbe -magneticraft.jar https://www.curseforge.com/minecraft/mc-mods/magneticraft -ic2.jar https://www.curseforge.com/minecraft/mc-mods/industrial-craft -modelloader.jar https://www.curseforge.com/minecraft/mc-mods/modelloader -mtlib.jar https://www.curseforge.com/minecraft/mc-mods/mtlib -appleskin.jar https://media.forgecdn.net/files/2496/585/AppleSkin-mc1.12-1.0.9.jar -mantle.jar https://www.curseforge.com/minecraft/mc-mods/mantle -worleys-caves.jar https://www.curseforge.com/minecraft/mc-mods/worleys-caves diff --git a/packs/jeffrey-2/icon.ico b/packs/jeffrey-2/icon.ico Binary files differnew file mode 100644 index 0000000..0525305 --- /dev/null +++ b/packs/jeffrey-2/icon.ico diff --git a/packs/jeffrey-2/icon.png b/packs/jeffrey-2/icon.png Binary files differnew file mode 100644 index 0000000..0dfbb45 --- /dev/null +++ b/packs/jeffrey-2/icon.png diff --git a/packs/jeffrey-2/pack-lock.ini b/packs/jeffrey-2/pack-lock.ini new file mode 100644 index 0000000..abaf013 --- /dev/null +++ b/packs/jeffrey-2/pack-lock.ini @@ -0,0 +1,52 @@ +[global] +pack_version = 33 +config_files = + +[mod_versions] +twilightforest = 23521ca2f42916cc6fe112464b6647d91b0e22cb,https://media.forgecdn.net/files/2756/932/twilightforest-1.12.2-3.9.984-universal.jar +tconstruct = e37e1f05ad0eaf567497ce655bc877dc1778bd0f,https://media.forgecdn.net/files/2902/483/TConstruct-1.12.2-2.13.0.183.jar +baubles = cb13fcfb18a9cb0cbd825fd5fe8d813c77368549,https://media.forgecdn.net/files/2518/667/Baubles-1.12-1.5.2.jar +jei = 3e88d2896ca868c3cedb65e117ad3a1b82488fa8,https://media.forgecdn.net/files/3043/174/jei_1.12.2-4.16.1.302.jar +psi = c76503880249a7e92f99d0ef68637076c6844218,https://media.forgecdn.net/files/3085/917/Psi-r1.1-78.2.jar +opencomputers = 8eff5dc6b00a50b13ad6f3e6a838049bbf8306f8,https://media.forgecdn.net/files/2828/357/OpenComputers-MC1.12.2-1.7.5.192.jar +chisel-bits = 0e6f159254e6899651087e5b1464bac91698d1d3,https://media.forgecdn.net/files/2720/655/chiselsandbits-14.33.jar +chisel = 1dda45074e17128451b3c8f66172bfaddf84f443,https://media.forgecdn.net/files/2619/468/Chisel-MC1.12.2-0.2.1.35.jar +dynamictrees = 7598b342c7585a64ab57a1bb7348bc95114a7d75,https://media.forgecdn.net/files/2960/958/DynamicTrees-1.12.2-0.9.8.jar +dynamictrees-compat = 2e429e9705cb49afb72e2650001d003fd755ce42,https://media.forgecdn.net/files/2656/685/DynamicTreesPHC-1.12.2-1.4.2.jar +dynamictreestraverse = 19c1e2e6b9c6ee4a9a93b8b50969be7bb6e13011,https://f-1.karel.pw/dttraverse-1.4.1e.jar +traverse = b16aaff09a6e77ba193e42f88f5d5bb8a85e063a,https://media.forgecdn.net/files/2613/657/Traverse-1.12.2-1.6.0-69.jar +roots = f954f42522ad35355e5215caa0a8b61904f29cd2,https://media.forgecdn.net/files/3056/896/Roots-1.12.2-3.0.32.jar +ae2 = e5c3c11eafc5daf73652a2dabe855e3759caa8b5,https://media.forgecdn.net/files/2747/63/appliedenergistics2-rv6-stable-7.jar +gravestones = 3daa7d4563965f6ec1954c8176e11fa3ba0b85ee,https://media.forgecdn.net/files/2608/278/gravestone-1.10.2.jar +enderstorage = 7a872baf72b1da038704056a0cf7bbcc40bfa4d6,https://media.forgecdn.net/files/2755/787/EnderStorage-1.12.2-2.4.6.137-universal.jar +waila = 7280d5c0dab42436549bcefc63ff64a1049e5501,https://media.forgecdn.net/files/2568/751/Hwyla-1.8.26-B41_1.12.2.jar +harvestcraft = fb4df84de5f52125d0339b614787222b9ee45442,https://media.forgecdn.net/files/2751/199/Pam%27s%20HarvestCraft%201.12.2ze.jar +akashictome = b11bf9d93f4bd7a2eeb8cfe49c1b30ce1a2f5a37,https://media.forgecdn.net/files/2648/656/AkashicTome-1.2-12.jar +railcraft = ea2085a509b816bb9a3cdd79f2f44175b588737a,https://media.forgecdn.net/files/2687/757/railcraft-12.0.0.jar +optifine = e805d4be5c2a3343488c573145606e90bb13816d,https://karel.pw/optifine-1.12.2.jar +autoreglib = 267269ca7f1a71fb3bb35bdb8e61702a4da6263e,https://media.forgecdn.net/files/2746/11/AutoRegLib-1.3-32.jar +ctm = 03be3e20dacf6b52abcee09436b2d06c06f2add0,https://media.forgecdn.net/files/2642/375/CTM-MC1.12.2-0.3.3.22.jar +codechickenlib = b6a7e3b889c354216059a3bfad298e30a1e46a2d,https://media.forgecdn.net/files/2779/848/CodeChickenLib-1.12.2-3.2.3.358-universal.jar +mysticallib = cb8c57761ca503c7ca4985991106d5df5ae2dd1a,https://media.forgecdn.net/files/3040/592/mysticallib-1.12.2-1.9.0.jar +patchouli = 9804876a655365926757eda750189cd97b5bad69,https://media.forgecdn.net/files/2731/963/Patchouli-1.0-20.jar +mysticalworld = df31d6c6777ff03a643425fbb9114421e58aef95,https://media.forgecdn.net/files/3054/945/mysticalworld-1.12.2-1.9.2.jar +diet = 2aea3d9a64551cefe0a1b6f5c5edf57959796b66,https://media.forgecdn.net/files/2482/543/diethopper-1.1.jar +worsebarrels = bd81a26550b4dc107f6781768973b650b4ffd6f4,https://media.forgecdn.net/files/2729/49/worsebarrels-1.2.0.jar +wearable = 195614d96ebab5758605c7d89f95877bd2b7bbcc,https://media.forgecdn.net/files/2576/699/WearableBackpacks-1.12.2-3.1.3.jar +eerie-entities = 30713af2e103899250239cbdfd2f7afb75e29f81,https://media.forgecdn.net/files/2872/906/EerieEntities-1.12.2-1.0.8.jar +bookshelf = 83a1864dd78f48102609849dd36866d6cf32b907,https://media.forgecdn.net/files/2836/960/Bookshelf-1.12.2-2.3.590.jar +veining = a0dbc6ad8021c8fa2f7d00de8058499268000232,https://media.forgecdn.net/files/2578/505/veining-1.3.2-1.12.x.jar +minecoprocessors = aa953ffcfb6935669392c95cab68f552f9b1942c,https://media.forgecdn.net/files/2599/694/minecoprocessors-1.12.2-5.jar +librarianlib = f2e75d6899a26fe32b0b0ee2ad33c68a70bd91e6,https://media.forgecdn.net/files/3041/340/librarianlib-1.12.2-4.22.jar +forgelin = 7a87553fcb808a45d9c7e03f113b355ac7fd10d7,https://media.forgecdn.net/files/2785/465/Forgelin-1.8.4.jar +dimdoors = 30c939f2305b862ed70b2144912ba25418430c36,https://media.forgecdn.net/files/2558/528/DimensionalDoors-3.0.9-287.jar +translocators = b008cc099c15e0cb121c43c889504fc367b3dedd,https://media.forgecdn.net/files/2755/795/Translocators-1.12.2-2.5.2.81-universal.jar +forgemultipartcbe = 3306ea22380bc9b6a0170b23fa0251085d5a6e25,https://media.forgecdn.net/files/2755/790/ForgeMultipart-1.12.2-2.6.2.83-universal.jar +magneticraft = 1e32b4d2e0c38a3f129605ed945e55af19d17d13,https://media.forgecdn.net/files/2807/901/Magneticraft_1.12-2.8.2-dev.jar +ic2 = 43e4af33528087ac448b9fcb5b31c6a6cd3a10ce,https://media.forgecdn.net/files/3078/604/industrialcraft-2-2.8.221-ex112.jar +modelloader = 8f77152980cafb1be5a4204f5773daef74de8627,https://media.forgecdn.net/files/2744/735/modelloader-1.1.7.jar +mtlib = a4625a61c9ef53412e0e467d23f25c5543658677,https://media.forgecdn.net/files/2684/561/MTLib-3.0.6.jar +appleskin = 23162a97cab0adb4be2fc6d3937c613929d1d5c7,https://media.forgecdn.net/files/2496/585/AppleSkin-mc1.12-1.0.9.jar +mantle = a1e5d5c197dae3e92637cafb8cd996185191165b,https://media.forgecdn.net/files/2713/386/Mantle-1.12-1.3.3.55.jar +worleys0caves = ad8d5845683a65981b54d9737d9dcccaf8ae9b9c,https://media.forgecdn.net/files/3038/801/worleycaves-1.12.2-1.5.2.jar + diff --git a/packs/jeffrey-2/pack.ini b/packs/jeffrey-2/pack.ini new file mode 100644 index 0000000..445cfa5 --- /dev/null +++ b/packs/jeffrey-2/pack.ini @@ -0,0 +1,54 @@ +[pack] +name = J.E.F.F.R.E.Y. 2 +pack_base_url = https://gitlab +forge_url = https://files.minecraftforge.net/maven/net/minecraftforge/forge/1.12.2-14.23.5.2854/forge-1.12.2-14.23.5.2854-installer.jar +game_version = 1.12.2 +java_args = -Xmx6G -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M + +[mods] +twilightforest = https://www.curseforge.com/minecraft/mc-mods/the-twilight-forest +tconstruct = https://www.curseforge.com/minecraft/mc-mods/tinkers-construct +baubles = https://www.curseforge.com/minecraft/mc-mods/baubles +jei = https://www.curseforge.com/minecraft/mc-mods/jei +psi = https://www.curseforge.com/minecraft/mc-mods/psi +opencomputers = https://www.curseforge.com/minecraft/mc-mods/opencomputers +chisel-bits = https://www.curseforge.com/minecraft/mc-mods/chisels-bits +chisel = https://www.curseforge.com/minecraft/mc-mods/chisel +dynamictrees = https://www.curseforge.com/minecraft/mc-mods/dynamictrees +dynamictrees-compat = https://www.curseforge.com/minecraft/mc-mods/dtphc +dynamictreestraverse = https://f-1.karel.pw/dttraverse-1.4.1e.jar +traverse = https://media.forgecdn.net/files/2613/657/Traverse-1.12.2-1.6.0-69.jar +roots = https://www.curseforge.com/minecraft/mc-mods/roots +ae2 = https://www.curseforge.com/minecraft/mc-mods/applied-energistics-2 +gravestones = https://media.forgecdn.net/files/2608/278/gravestone-1.10.2.jar +enderstorage = https://www.curseforge.com/minecraft/mc-mods/ender-storage-1-8 +waila = https://media.forgecdn.net/files/2568/751/Hwyla-1.8.26-B41_1.12.2.jar +harvestcraft = https://www.curseforge.com/minecraft/mc-mods/pams-harvestcraft +akashictome = https://www.curseforge.com/minecraft/mc-mods/akashic-tome +railcraft = https://www.curseforge.com/minecraft/mc-mods/railcraft +optifine = https://karel.pw/optifine-1.12.2.jar +autoreglib = https://media.forgecdn.net/files/2746/11/AutoRegLib-1.3-32.jar +ctm = https://www.curseforge.com/minecraft/mc-mods/ctm +codechickenlib = https://www.curseforge.com/minecraft/mc-mods/codechicken-lib-1-8 +mysticallib = https://media.forgecdn.net/files/3040/592/mysticallib-1.12.2-1.9.0.jar +patchouli = https://media.forgecdn.net/files/2731/963/Patchouli-1.0-20.jar +mysticalworld = https://media.forgecdn.net/files/3054/945/mysticalworld-1.12.2-1.9.2.jar +diet = https://www.curseforge.com/minecraft/mc-mods/diet-hoppers +worsebarrels = https://www.curseforge.com/minecraft/mc-mods/worse-barrels +wearable = https://www.curseforge.com/minecraft/mc-mods/wearable-backpacks +eerie-entities = https://www.curseforge.com/minecraft/mc-mods/eerie-entities +bookshelf = https://media.forgecdn.net/files/2836/960/Bookshelf-1.12.2-2.3.590.jar +veining = https://www.curseforge.com/minecraft/mc-mods/veining +minecoprocessors = https://www.curseforge.com/minecraft/mc-mods/minecoprocessors +librarianlib = https://www.curseforge.com/minecraft/mc-mods/librarianlib +forgelin = https://media.forgecdn.net/files/2785/465/Forgelin-1.8.4.jar +dimdoors = https://www.curseforge.com/minecraft/mc-mods/dimensionaldoors +translocators = https://www.curseforge.com/minecraft/mc-mods/translocators-1-8 +forgemultipartcbe = https://www.curseforge.com/minecraft/mc-mods/forge-multipart-cbe +magneticraft = https://www.curseforge.com/minecraft/mc-mods/magneticraft +ic2 = https://www.curseforge.com/minecraft/mc-mods/industrial-craft +modelloader = https://www.curseforge.com/minecraft/mc-mods/modelloader +mtlib = https://www.curseforge.com/minecraft/mc-mods/mtlib +appleskin = https://media.forgecdn.net/files/2496/585/AppleSkin-mc1.12-1.0.9.jar +mantle = https://www.curseforge.com/minecraft/mc-mods/mantle +worleys0caves = https://www.curseforge.com/minecraft/mc-mods/worleys-caves diff --git a/version.txt b/packs/jeffrey-2/version.txt index 1cc2ba4..1cc2ba4 100644 --- a/version.txt +++ b/packs/jeffrey-2/version.txt diff --git a/packs/jeffrey-3/.gitignore b/packs/jeffrey-3/.gitignore new file mode 100644 index 0000000..8f39806 --- /dev/null +++ b/packs/jeffrey-3/.gitignore @@ -0,0 +1,8 @@ +local-config.ini +geckodriver +geckodriver.exe +geckodriver.log +__pycache__/ +*.swp +*.swo +*.log diff --git a/packs/jeffrey-3/config/creeperconfetti-common.toml b/packs/jeffrey-3/config/creeperconfetti-common.toml new file mode 100644 index 0000000..d049bda --- /dev/null +++ b/packs/jeffrey-3/config/creeperconfetti-common.toml @@ -0,0 +1,8 @@ + +[General] + #The %chance that any given creeper will explode into confetti [0..100|default:100] + #Range: 0 ~ 100 + confettiChance = 5 + #Confetti Explosions Damage Players [false/true|default:false] + damagePlayers = false + diff --git a/packs/jeffrey-3/icon.ico b/packs/jeffrey-3/icon.ico Binary files differnew file mode 100644 index 0000000..3a75d9c --- /dev/null +++ b/packs/jeffrey-3/icon.ico diff --git a/packs/jeffrey-3/icon.png b/packs/jeffrey-3/icon.png Binary files differnew file mode 100644 index 0000000..d60084b --- /dev/null +++ b/packs/jeffrey-3/icon.png diff --git a/packs/jeffrey-3/local-config.ini b/packs/jeffrey-3/local-config.ini new file mode 100644 index 0000000..5781ccf --- /dev/null +++ b/packs/jeffrey-3/local-config.ini @@ -0,0 +1,9 @@ +# this file is local configuration merged into the pack configuration at runtime +# (values here override those in pack.ini) +[pack] +# Uncomment this to override the default automatic selection of the mod install directory. +#location = /home/example/.minecraft/instances/jeffrey +# A comma-separated list of mods that won't be deleted during the install process. +whitelist = example_mod.jar,example_extra_mod.jar +# A comma-separated list of mods that won't be installed during the install process +blacklist = server_only_mod.jar,client_only_mod.jar diff --git a/packs/jeffrey-3/pack-lock.ini b/packs/jeffrey-3/pack-lock.ini new file mode 100644 index 0000000..3bffe20 --- /dev/null +++ b/packs/jeffrey-3/pack-lock.ini @@ -0,0 +1,86 @@ +[global] +config_files = creeperconfetti-common.toml +pack_version = 7 + +[mod_versions] +building-gadgets = 6ec24f6a51cb8e0d4ef53b2ed7167328fcae3738,https://media.forgecdn.net/files/3144/138/buildinggadgets-1.16.4-3.7.3.jar +item-collectors = bb5f774df00908859f9d66883c9c822c9b2f5d88,https://media.forgecdn.net/files/3145/217/itemcollectors-1.0.7-mc1.16.4.jar +natures-compass = 8e4c83dcdcb088382e47ce858818504ef053ced2,https://media.forgecdn.net/files/3133/521/NaturesCompass-1.16.4-1.8.6.jar +packing-tape = 46060f5793a82a87967bf8137cbff73f34c40ac7,https://media.forgecdn.net/files/3082/552/PackingTape-1.16.3-0.10.0.jar +seals = 8f83c9e18cae5929a24c528c4a0703d4f5202cac,https://media.forgecdn.net/files/3073/361/seals-1.16.3-2.0.0.jar +simple-planes = 016f91f2c8b21620061f001008310b397fd63806,https://media.forgecdn.net/files/3134/471/simpleplanes-1.16.3-3.2.0.3.jar +ender-chests = c30300af94311a46cc533cf9549f93432bbbcbe1,https://media.forgecdn.net/files/3106/960/enderchests-1.16-1.7.5.jar +ender-tanks = a750b0d7fe4d50c23c647818b2bd11ea63b95ce9,https://media.forgecdn.net/files/3055/892/endertanks-1.16-1.9.3.jar +gauges-and-switches = dd07cf0b5716d782cf9ebf2ea5e43c8c2f5a74d1,https://media.forgecdn.net/files/3142/879/rsgauges-1.16.4-1.2.6.jar +moving-elevators = 72976360ddb40cd49dcb93c3036b281c13d693c2,https://media.forgecdn.net/files/3145/251/movingelevators-1.2.30-mc1.16.4.jar +cooking-for-blockheads = cb9d33c55eefc4d876b2590a9bd099c0ef1cea77,https://media.forgecdn.net/files/3098/223/CookingForBlockheads_1.16.3-9.2.2.jar +discord-presence = cf7250f8cdd6dcbd0a3048a4bc992cb0f3964671,https://media.forgecdn.net/files/3103/834/SimpleDiscordRichPresence-1.16.4-1.3.5.jar +shetiphiancore = b9e612714fc2bb7a6bd1be8b04a143b45d76b121,https://media.forgecdn.net/files/3090/382/shetiphiancore-1.16-3.8.4.jar +quark = 5aa63e44e03b0e5bef944169a7287134ce375504,https://media.forgecdn.net/files/3146/131/Quark-r2.4-283.jar +blood-magic = 85f1d49cd69cefe67b86a80e47f16124f43bd7c5,https://media.forgecdn.net/files/3132/991/BloodMagic-1.16.3-3.0.2-7.jar +astral-sorcery = 167558fd1ae9f9605fb7c1c9dd3c80e77f55174d,https://media.forgecdn.net/files/3144/866/astralsorcery-1.16.4-1.13.8.jar +ae2 = 7d2b29fceaed530813aae3b60bef27a28ecc3257,https://media.forgecdn.net/files/3118/473/appliedenergistics2-8.2.0-alpha.2.jar +mekanism = 8b2ffd61cc121d5e342d49633a0696c34877a273,https://media.forgecdn.net/files/3134/211/Mekanism-1.16.4-10.0.18.445.jar +mekanism-tools = f72c31c26785cd867e32dbde30fdfe65429df6c2,https://media.forgecdn.net/files/3134/214/MekanismTools-1.16.4-10.0.18.445.jar +mekanism-generators = 140700335ae7ccca3e4a78fd0199e90429aabeca,https://media.forgecdn.net/files/3134/213/MekanismGenerators-1.16.4-10.0.18.445.jar +mekanism-additions = b3abe12303f57967b37f391830742becfe9401ae,https://media.forgecdn.net/files/3134/215/MekanismAdditions-1.16.4-10.0.18.445.jar +psi = 35ff69060bbdf7514da60d224ffd9db8bdd0f772,https://media.forgecdn.net/files/3106/707/Psi%201.16-88.jar +cc-tweaked = 569a119e39aa59d03d10189bec829f7bc8d6fb9b,https://media.forgecdn.net/files/3104/639/cc-tweaked-1.16.4-1.94.0.jar +immersive-engineering = 74051c20e6f1d9713daac60fda0852016544b9dd,https://media.forgecdn.net/files/3141/693/ImmersiveEngineering-1.16.4-4.1.2-129.jar +botania = 2fcf9ca0f76a5e9ab3c09b5140c94555b7514ba9,https://media.forgecdn.net/files/3134/409/Botania-1.16.4-410.jar +jei = 153ba78db1142db5dd0b4e3a5cd10ab9d2141f45,https://media.forgecdn.net/files/3136/600/jei-1.16.4-7.6.0.62.jar +tetra = 4b309ec6a02c024baff730c363ac5d6a1071b0a6,https://media.forgecdn.net/files/3147/835/tetra-1.16.4-3.4.0.jar +chickenchunks = 0b7110a4668da041debb43b8a1ec200b61c13d82,https://media.forgecdn.net/files/3125/159/ChickenChunks-1.16.4-2.7.0.85-universal.jar +chisel-and-bits = 6922303111dfc8d3b12f29372f35f000276e329a,https://media.forgecdn.net/files/3133/859/chiselsandbits-0.2.8-RELEASE.jar +inventory-sorter = fb538c5a8eb1e8a7a2bc35f3344b816634d53e4e,https://media.forgecdn.net/files/3077/903/inventorysorter-1.16.1-18.1.0.jar +creeper-confetti = c1d06621ba453680fe31dd30fe11f6ea9bcc8e93,https://media.forgecdn.net/files/3063/500/creeperconfetti-3.4.jar +hwyla = 79123ef4c447affe57e77ee241d78c494bd8948d,https://media.forgecdn.net/files/3033/593/Hwyla-forge-1.10.11-B78_1.16.2.jar +jer = dcede97c594d1f89a071bd2f904f232f789aea44,https://media.forgecdn.net/files/3109/962/JustEnoughResources-1.16.4-0.12.0.103.jar +ender-tendril = caa72968398bf124e5c2fcb03833547b74b68056,https://media.forgecdn.net/files/3069/322/EnderTendril-1.16.3-1.1.1%2B4.jar +discord-integration = d144c5a7918f3ccd7ab01b8768893c0562388cb3,https://media.forgecdn.net/files/3118/761/dcintegration-2.0.2-1.16.jar +step = dcc4854053a27c8c893fb3809b6d2a2cda0741ab,https://media.forgecdn.net/files/3100/371/step-1.16.4-1.0.3.jar +simply-backpacks = d4bb5d2d4d8078debef3e7f0788f21db3f637bfa,https://media.forgecdn.net/files/3076/926/simplybackpacks-1.16.3-1.4.13.jar +better-mineshafts = 6ba857968964a3479a8df4c15a1ba120f2c3e978,https://media.forgecdn.net/files/3145/910/BetterMineshafts-Forge-1.16.3-1.1.1.jar +comforts = e3e827f0c6fe892c88dc865b4c6a4f0924f034ec,https://media.forgecdn.net/files/3143/312/comforts-forge-1.16.4-4.0.0.3.jar +better-than-llamas = 29cecac936279cc87cc4a957acfbcca8d5551b2e,https://media.forgecdn.net/files/3062/115/BetterThanLlamas-1.16.3-1.1.1.jar +better-than-bunnies = 58640158f2aedbb0547d37568c0ccca66fd49944,https://media.forgecdn.net/files/3062/114/BetterThanBunnies-1.16.3-1.2.0.jar +corpse = d5bf41cad1a7c7d90972697e3f5494dd0444e15c,https://media.forgecdn.net/files/3123/623/corpse-1.16.4-1.0.4.jar +gilded-armor = 2eae828bcf1ef89c302f7100fe02505ac048c5e1,https://media.forgecdn.net/files/3069/303/gildedarmor-1.16.3-1.0.3.jar +jei-integration = e9368114e66bf4fc23cd21dd97b95c469115dd55,https://media.forgecdn.net/files/3122/292/jeiintegration_1.16.4-6.1.1.11.jar +openblocks-elevator = 6e3e440447a57b7b2b3256d4acbf1519be3a09c8,https://media.forgecdn.net/files/3110/386/elevatorid-1.16.4-1.7.8.jar +ranged-pumps = 202c2aba870b852638d6ed28074dd0e91b2f76c1,https://media.forgecdn.net/files/3065/697/rangedpumps-0.8.2.jar +the-conjurer = 3dd29349f2190d2f5b40cebed4fa67f6bb716324,https://media.forgecdn.net/files/3107/953/the-conjurer-1.16.4-1.0.13.jar +trashcans = c251a09d6244c821bbd5d9b46cd2ad5c7b5a0c9e,https://media.forgecdn.net/files/3144/215/trashcans-1.0.5-mc1.16.4.jar +controlling = d7d04f585795b3d3040a6ad863a8737cf2efa6ac,https://media.forgecdn.net/files/3110/995/Controlling-7.0.0.11.jar +ding = d984f82495e1ed12236aeea6e6be20d2bd094d72,https://media.forgecdn.net/files/3062/74/Ding-1.16.3-1.2.0.jar +appleskin = d0e2d9d9bf17806d3fbd263a8702822ab33edeb1,https://media.forgecdn.net/files/3035/787/AppleSkin-mc1.16.2-forge-1.0.14.jar +enchantment-descriptions = 30439e14507a5b942d4b696af8a1dbc463134199,https://media.forgecdn.net/files/3112/901/EnchantmentDescriptions-1.16.4-6.0.2.jar +emojiful = 994b1e4de03a22880a904339c64d35db93d09732,https://media.forgecdn.net/files/3099/65/emojiful-1.16.3-2.1.1.jar +clumps = f84965303d0c8b6320435cf88dd0dcf403a7098d,https://media.forgecdn.net/files/3137/103/Clumps-6.0.0.13.jar +toast-control = 6ebe524115df53b9d9a8068e1490ce20b9e139dc,https://media.forgecdn.net/files/3069/51/Toast-Control-1.16.3-4.3.0.jar +mouse-tweaks = b9c5ac6c2183eee2c4acda20dc1dd7fc2c387fa2,https://media.forgecdn.net/files/3035/780/MouseTweaks-2.13-mc1.16.2.jar +recipe-buffers = 03a01185d7ab325ba14f56a41b0a424b2d6a580c,https://media.forgecdn.net/files/3126/130/recipebuffers-1.1.jar +harvest = f9618079d9456c23133ab89909fdd1c9fbae56b8,https://media.forgecdn.net/files/3087/381/harvest-1.16.3-1.0.3.jar +light-overlay = a3caff7768581d5d97b5433dd834dfa6d676aea9,https://media.forgecdn.net/files/3091/376/light-overlay-5.5.4.jar +morpheus = da30b18acd23c270a7da07ed8feb693291efcf90,https://media.forgecdn.net/files/3114/135/Morpheus-1.16.4-4.2.68.jar +extreme-sound-muffler = 0e7d143dac36cb1e744b450217313e7abb6ea24f,https://media.forgecdn.net/files/3136/803/extremeSoundMuffler-3.2_Forge-1.16.4.jar +fast-workbench = c7f0296b5c569f1fe11aa6368b87bebfa01c9ddc,https://media.forgecdn.net/files/3112/661/FastWorkbench-1.16.3-4.4.1.jar +findme = 7f18ff3af51b46c388d780c0b0afb9b400cc7962,https://media.forgecdn.net/files/3073/336/findme-1.16.3-2.1.0.0.jar +wawla = 9aeea4cc8ae5bfa89701435b7c71bffdc43cc49d,https://media.forgecdn.net/files/3124/964/WAWLA-1.16.4-7.0.2.jar +iron-chests = 3e7a9131d1ea836527d5cf7a1761d8b4dc7026a3,https://media.forgecdn.net/files/3105/315/ironchest-1.16.4-11.2.10.jar +reauth = f5bdf682d64bcde250c8a2a9c1261e285540ac9b,https://media.forgecdn.net/files/3105/779/ReAuth-1.16-Forge-3.9.3.jar +yungs-caves = 159c5a81f6cdcf9a541ea499152cbe8aedcfcfad,https://media.forgecdn.net/files/3128/132/BetterCaves-1.16.3-1.0.6.jar +mgui = 847a29f1913b3957d384e57be8ee415a51c3e8ff,https://media.forgecdn.net/files/3104/239/mgui-1.16.4-3.1.3.jar +obfuscate = 6065de3d3cfda408db086ca1bbb2a0882630dee4,https://media.forgecdn.net/files/3148/131/obfuscate-0.5.1-1.16.3.jar +placebo = 1513d08e468654c8893f00f6b844fa374fd56620,https://media.forgecdn.net/files/3092/113/Placebo-1.16.3-4.3.3.jar +cloth-config = 1ee61a4974ce5c720b495a22d1221a4906a4334b,https://media.forgecdn.net/files/3112/227/cloth-config-forge-4.1.1.jar +structure-jel = 3145fde7a369c5214e375501d98a5d9a2b4c44bd,https://media.forgecdn.net/files/3133/625/structure-gel-api-1.16.4-1.7.2.jar +bookshelf = eadd284eee4e26bcdf301f5b03d66abbc58e199f,https://media.forgecdn.net/files/3133/712/Bookshelf-1.16.4-9.3.18.jar +ichunutil = 902ee274d713242aae0339a3ddefabc277b65632,https://media.forgecdn.net/files/3062/89/iChunUtil-1.16.3-10.0.0.jar +titanium = 5d266ccfedd28a7831b7491cd59a8fa4f7c2eb69,https://media.forgecdn.net/files/3143/177/titanium-1.16.4-3.2.3.jar +curios = 744de4d8889fbb0c5af43416e3bec16df056fe70,https://media.forgecdn.net/files/3122/651/curios-forge-1.16.4-4.0.3.0.jar +autoreglib = b581dda2655a47c84ef15013784160b07f6d5c97,https://media.forgecdn.net/files/3128/555/AutoRegLib-1.6-47.jar +observerlib = c3154cb3f0e78ff9ef526f6de80dca802a70d31a,https://media.forgecdn.net/files/3123/187/observerlib-1.16.4-1.4.4.jar +patchouli = 5d27235ad0e644dcfafadf439f15134c966b54e2,https://media.forgecdn.net/files/3126/931/Patchouli-1.16.4-48.jar +codechickenlib = 67e93ac61ae70eec8fa3086032d3a47a0cfd52e6,https://media.forgecdn.net/files/3125/157/CodeChickenLib-1.16.4-3.5.0.398-universal.jar + diff --git a/packs/jeffrey-3/pack.ini b/packs/jeffrey-3/pack.ini new file mode 100644 index 0000000..9d6b892 --- /dev/null +++ b/packs/jeffrey-3/pack.ini @@ -0,0 +1,100 @@ +[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 +java_args = -Xmx6G -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M + +[mods] +# Possibly controversial mods - subject to further review +building-gadgets = https://www.curseforge.com/minecraft/mc-mods/building-gadgets +item-collectors = https://www.curseforge.com/minecraft/mc-mods/item-collectors +natures-compass = https://www.curseforge.com/minecraft/mc-mods/natures-compass +packing-tape = https://www.curseforge.com/minecraft/mc-mods/packing-tape +seals = https://www.curseforge.com/minecraft/mc-mods/seals +simple-planes = https://www.curseforge.com/minecraft/mc-mods/simple-planes +ender-chests = https://www.curseforge.com/minecraft/mc-mods/enderchests +ender-tanks = https://www.curseforge.com/minecraft/mc-mods/endertanks +gauges-and-switches = https://www.curseforge.com/minecraft/mc-mods/redstone-gauges-and-switches +moving-elevators = https://www.curseforge.com/minecraft/mc-mods/moving-elevators +cooking-for-blockheads = https://www.curseforge.com/minecraft/mc-mods/cooking-for-blockheads +discord-presence = https://www.curseforge.com/minecraft/mc-mods/simple-discord-rich-presence +# Minecolonies doesn't work, citing version incompatibilites with the latest structurize +#minecolonies = https://www.curseforge.com/minecraft/mc-mods/minecolonies +# library mod for minecolonies +#structurize = https://www.curseforge.com/minecraft/mc-mods/structurize +# library mod for enderchests/endertanks +shetiphiancore = https://www.curseforge.com/minecraft/mc-mods/shetiphiancore +quark = https://www.curseforge.com/minecraft/mc-mods/quark +blood-magic = https://www.curseforge.com/minecraft/mc-mods/blood-magic +astral-sorcery = https://www.curseforge.com/minecraft/mc-mods/astral-sorcery + +# Fight me mods +ae2 = https://www.curseforge.com/minecraft/mc-mods/applied-energistics-2 +mekanism = https://www.curseforge.com/minecraft/mc-mods/mekanism +mekanism-tools = https://www.curseforge.com/minecraft/mc-mods/mekanism-tools +mekanism-generators = https://www.curseforge.com/minecraft/mc-mods/mekanism-generators +mekanism-additions = https://www.curseforge.com/minecraft/mc-mods/mekanism-additions +psi = https://www.curseforge.com/minecraft/mc-mods/psi +cc-tweaked = https://www.curseforge.com/minecraft/mc-mods/cc-tweaked +immersive-engineering = https://www.curseforge.com/minecraft/mc-mods/immersive-engineering +botania = https://www.curseforge.com/minecraft/mc-mods/botania +jei = https://www.curseforge.com/minecraft/mc-mods/jei +tetra = https://www.curseforge.com/minecraft/mc-mods/tetra + +# Smaller yes mods +chickenchunks = https://www.curseforge.com/minecraft/mc-mods/chicken-chunks-1-8 +chisel-and-bits = https://www.curseforge.com/minecraft/mc-mods/chisels-bits +inventory-sorter = https://www.curseforge.com/minecraft/mc-mods/inventory-sorter +creeper-confetti = https://www.curseforge.com/minecraft/mc-mods/creeper-confetti +hwyla = https://www.curseforge.com/minecraft/mc-mods/hwyla +jer = https://www.curseforge.com/minecraft/mc-mods/just-enough-resources-jer +ender-tendril = https://www.curseforge.com/minecraft/mc-mods/ender-tendril +discord-integration = https://www.curseforge.com/minecraft/mc-mods/dcintegration +step = https://www.curseforge.com/minecraft/mc-mods/step +simply-backpacks = https://www.curseforge.com/minecraft/mc-mods/simply-backpacks +better-mineshafts = https://www.curseforge.com/minecraft/mc-mods/yungs-better-mineshafts-forge +comforts = https://www.curseforge.com/minecraft/mc-mods/comforts +better-than-llamas = https://www.curseforge.com/minecraft/mc-mods/better-than-llamas +better-than-bunnies = https://www.curseforge.com/minecraft/mc-mods/better-than-bunnies +corpse = https://www.curseforge.com/minecraft/mc-mods/corpse +gilded-armor = https://www.curseforge.com/minecraft/mc-mods/gildedarmor +jei-integration = https://www.curseforge.com/minecraft/mc-mods/jei-integration +openblocks-elevator = https://www.curseforge.com/minecraft/mc-mods/openblocks-elevator +ranged-pumps = https://www.curseforge.com/minecraft/mc-mods/ranged-pumps +the-conjurer = https://www.curseforge.com/minecraft/mc-mods/the-conjurer +trashcans = https://www.curseforge.com/minecraft/mc-mods/trash-cans +controlling = https://www.curseforge.com/minecraft/mc-mods/controlling +ding = https://www.curseforge.com/minecraft/mc-mods/ding +appleskin = https://www.curseforge.com/minecraft/mc-mods/appleskin +enchantment-descriptions = https://www.curseforge.com/minecraft/mc-mods/enchantment-descriptions +emojiful = https://www.curseforge.com/minecraft/mc-mods/emojiful +clumps = https://www.curseforge.com/minecraft/mc-mods/clumps +toast-control = https://www.curseforge.com/minecraft/mc-mods/toast-control +mouse-tweaks = https://www.curseforge.com/minecraft/mc-mods/mouse-tweaks +recipe-buffers = https://www.curseforge.com/minecraft/mc-mods/recipebuffers +harvest = https://www.curseforge.com/minecraft/mc-mods/harvest +light-overlay = https://www.curseforge.com/minecraft/mc-mods/light-overlay +morpheus = https://www.curseforge.com/minecraft/mc-mods/morpheus +extreme-sound-muffler = https://www.curseforge.com/minecraft/mc-mods/extreme-sound-muffler +fast-workbench = https://www.curseforge.com/minecraft/mc-mods/fastworkbench +findme = https://www.curseforge.com/minecraft/mc-mods/findme +wawla = https://www.curseforge.com/minecraft/mc-mods/wawla +iron-chests = https://www.curseforge.com/minecraft/mc-mods/iron-chests +reauth = https://www.curseforge.com/minecraft/mc-mods/reauth +yungs-caves = https://www.curseforge.com/minecraft/mc-mods/yungs-better-caves + +# Library mods +mgui = https://www.curseforge.com/minecraft/mc-mods/mgui +obfuscate = https://www.curseforge.com/minecraft/mc-mods/obfuscate +placebo = https://www.curseforge.com/minecraft/mc-mods/placebo +cloth-config = https://www.curseforge.com/minecraft/mc-mods/cloth-config-forge +structure-jel = https://www.curseforge.com/minecraft/mc-mods/structure-gel-api +bookshelf = https://www.curseforge.com/minecraft/mc-mods/bookshelf +ichunutil = https://www.curseforge.com/minecraft/mc-mods/ichunutil +titanium = https://www.curseforge.com/minecraft/mc-mods/titanium +curios = https://www.curseforge.com/minecraft/mc-mods/curios +autoreglib = https://www.curseforge.com/minecraft/mc-mods/autoreglib +observerlib = https://www.curseforge.com/minecraft/mc-mods/observerlib +patchouli = https://www.curseforge.com/minecraft/mc-mods/patchouli +codechickenlib = https://www.curseforge.com/minecraft/mc-mods/codechicken-lib-1-8 @@ -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. + + @@ -0,0 +1,7 @@ + - version.txt -> pack-lock.ini + - also add file_names for config + - pack_version in there as well + - copy config folder + - download config from url + - download everything from specified URL + - only when in a bundle diff --git a/update.py b/update.py deleted file mode 100755 index 2851c43..0000000 --- a/update.py +++ /dev/null @@ -1,248 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import os -import sys -import hashlib -import shutil -import re -import collections -import urllib.parse - -import requests - -parser = argparse.ArgumentParser( - description="A Simple Git-Based Modpack Manager", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog='''\ -Available commands: - install : Downloads mods listed in version.txt and populates the mods folder specified in pack-location.txt - apply_updates : Using the urls in mods.txt, repopulates version.txt to reflect the most recent mod versions - check_updates : Compares version.txt and mods.txt to see if any mods can be updated -''') - -parser.add_argument('command', - nargs='?', - default='install', - help="The action to perform (default: install)") -parser.add_argument('filename', - nargs='?', - default="mods.txt", - help="Optional filename to specify latest mods (default: mods.txt)") -parser.add_argument('--version-file', - type=str, - default="version.txt", - help="Optional custom version file to download mods from (default: version.txt)") -parser.add_argument('--pack-location', - type=str, - help="Optional custom modpack folder location (default: read from pack-location.txt)") -parser.add_argument('--whitelist-file', - type=str, - default="whitelist.txt", - help="Optional custom whitelist file that tells 'install' which files not to remove (default: whitelist.txt)") -parser.add_argument("--game-version", - type=str, - default=None, - help="The maximum game version to update mods to") - -## loaded from version.txt -VERSION = 0 - -def read_file(fil): - strings = [] - with open(fil) as f: - for line in f: - string = line.strip().split() - if len(line) > 1 and line[0] != '#': - # run strip on each element - string = tuple(map(lambda x: x.strip(), string)) - strings.append(string) - - return strings - -# Apply updates to the actual mod pack -def install(args): - print("Updating pack with version " + str(VERSION) + "...") - print() - # (fname, checksum, url) - mods = read_file(args.version_file) - names = [mod[0] for mod in mods] - # whitelist client mods (e.g. optifine) - names += [line[0] for line in read_file(args.whitelist_file)] - - i = 0 - for mod in mods: - mod_path = os.path.join(args.pack_location, mod[0]) - i += 1 - if os.path.exists(mod_path) and os.path.isfile(mod_path) and \ - hashlib.sha1(open(mod_path, 'rb').read()).hexdigest() == mod[1]: - print("Skipping {mod[0]}, already up to date".format(mod=mod)) - else: - print('Installing {mod[0]} from {mod[2]}...'.format(mod=mod)) - print(' ({i} of {x})'.format(i=i,x=len(mods)), end='\r') - download_obj = requests.get(mod[2], stream=True) - with open(mod_path, "wb") as write_file: - shutil.copyfileobj(download_obj.raw, write_file) - print("Done!" + " " * 8) - - print() - print("Removing old mods...") - for jar in os.listdir(args.pack_location): - if jar not in names and os.path.splitext(jar)[1] == ".jar": - os.remove(os.path.join(args.pack_location, jar)) - print("Removing '{jar}'".format(jar=jar)) - - print() - print("Finished installing mods!") - - -# Using the latest urls, update downloads.txt to match and have the correct sha1 -def apply_updates(args): - if args.game_version is not None: - version = tuple(int(x) for x in args.game_version.split('.')) - else: - version = (2, 0, 0) - print("Populating version File...") - mods = read_file(args.filename) - print("Getting new versions of all mods...") - ffx = firefox() - with open(args.version_file, 'w') as f: - f.write('# Format: <jarname> <hex digested sha1> <direct download url>\n') - f.write("#VERSION " + str(VERSION + 1) + "\n") - for mod in mods: - print("Fetching {mod[0]}...".format(mod=mod)) - if 'curseforge' in mod[1]: - url = find_cdn(ffx, mod[1], version) - else: - url = requests.get(mod[1]).url - if url is None: - print('[!]Failed to fetch {mod[0]}!'.format(mod=mod)) - continue - resp = requests.get(url) - hsh = hashlib.sha1(resp.content).hexdigest() - f.write('{mod[0]} {hsh} {resp.url}\n'.format(mod=mod, hsh=hsh, resp=resp)) - ffx.close() - print() - print("Done!") - print("Updates applied to {args.version_file}".format(args=args)) - print("New pack version is " + str(VERSION + 1)) - print("[!] No mods were installed. To update your mods folder, run 'update.py install'") - -# Find if any updates are available -def check_updates(args): - if args.game_version is not None: - version = tuple(int(x) for x in args.game_version.split('.')) - else: - version = (2, 0, 0) - print("Checking for updates to version " + str(VERSION) + "...") - latest = read_file(args.filename) - old = read_file(args.version_file) - old_urls = [mod[2] for mod in old] - num_updates = 0 - - print("Checking updates...") - ffx = firefox() - - for mod in latest: - print("Checking for updates to {mod[0]}...".format(mod=mod), end="") - sys.stdout.flush() # takes care of line-buffered terminals - if 'curseforge' in mod[1]: - url = find_cdn(ffx, mod[1], version) - else: - url = requests.get(mod[1]).url - if url in old_urls: - print(" No updates") - else: - print(" Found update: " + url.split('/')[-1]) - num_updates += 1 - ffx.close() - - print("Finished checking for updates. {num} mods can be updated".format(num=num_updates)) - if num_updates >= 0: - print("Run 'python update.py apply_updates' to create a new version with these updates applied.") - - -def find_cdn(ffx, url, version): - """ - Given a mod home URL, finds the most up-to-date mod version compatible with the given game version. - Returns the direct Forge CDN download URL - """ - #TODO filter mods by forge/fabric compatibility - try: - ffx.get(url + '/files/all') - mod_versions = ffx.find_elements_by_class_name("listing")[0].find_elements_by_xpath("tbody/tr") # extract the table of files from the page - row_info = collections.namedtuple("row_info", ["type", "filename", "cdn_id", "game_version"]) # create a custom tuple because data - rows = [] - for version_entry in mod_versions: - # parse out the four fields that we use - entry_cells = version_entry.find_elements_by_tag_name("td") - release_type = entry_cells[0].text - filename = urllib.parse.quote(entry_cells[1].find_elements_by_tag_name("a")[0].text) - try: - game_version = tuple([int(x) for x in entry_cells[4].find_element_by_class_name("mr-2").text.split(".")]) # get game version and convert to tuple - except: - game_version = (0, 0, 0) - cdn_id = entry_cells[1].find_element_by_tag_name("a").get_property("href").split("/")[-1] - rows.append(row_info(release_type, filename, cdn_id, game_version)) - rows.sort(key=lambda x: x.game_version, reverse=True) - best_row = next(x for x in rows if x.game_version <= version) - - return f'https://media.forgecdn.net/files/{best_row.cdn_id[:4]}/{best_row.cdn_id[4:]}/{best_row.filename}' - - except: - import traceback; traceback.print_exc() - return None - - -def firefox(): - print("Starting Selenium...") - try: - from selenium.webdriver import Firefox - except: - print("Applying updates requires the `selenium` package") - sys.exit(0) - return Firefox() - -COMMAND_MAP = { - 'install': install, - 'apply_updates': apply_updates, - 'check_updates': check_updates, -} - -if __name__ == "__main__": - args = parser.parse_args() - - if not args.pack_location: - # initialize from config - with open("pack-location.txt", "r") as f: - args.pack_location = f.read().strip() - - if not os.path.exists(args.version_file): - print("Error: version file\"" + args.version_file + "\" does not exist.") - parser.print_help() - sys.exit(1) - if not os.path.exists(args.pack_location): - print("Error: mod folder \"" + args.pack_location + "\" does not exist.") - parser.print_help() - sys.exit(1) - elif not os.path.isdir(args.pack_location): - print("Error: mod folder \"" + args.pack_location + "\" is not actually a folder.") - parser.print_help() - sys.exit(1) - if not os.path.exists(args.whitelist_file): - print("Error: whitelist file \"" + args.whitelist_file + "\" does not exist.") - sys.exit(1) - - if not (args.command in COMMAND_MAP): - print("Error: command \"" + args.command + "\" does not exist") - parser.print_help() - sys.exit(1) - - # fetch version - with open(args.version_file) as f: - for line in f: - if line.strip().split()[0] == "#VERSION": - VERSION = int(line.strip().split()[1]) - break - # run the command - COMMAND_MAP[args.command](args) @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +import os +import sys +import hashlib +import shutil +import re +import collections +import urllib.parse +import multiprocessing +import pathlib +import base64 +from configparser import RawConfigParser + +import requests + + +def load_config(): + """ + Load configuarion from pack and local configuration files + Fill in reasonable defaults where applicable. + """ + config_p = RawConfigParser() + config_p.read(["pack.ini", "local-config.ini"]) + config = config_p._sections + config["pack"]["sanitized_name"] = sanitize_text(config["pack"]["name"]) + + if "location" not in config["pack"]: + config['pack']['location'] = os.path.join(find_minecraft_directory(), config['pack']['sanitized_name']) + + if "whitelist" not in config["pack"]: + config["pack"]["whitelist"] = [] + else: + config["pack"]["whitelist"] = config["pack"]["whitelist"].split(",") + + if "blacklist" not in config["pack"]: + config["pack"]["blacklist"] = [] + else: + config["pack"]["blacklist"] = config["pack"]["blacklist"].split(",") + + config["pack"]["game_version"] = game_version_from_string(config["pack"]["game_version"]) + + # return the whole config file, pack configuration and modlist + return config + + +def update_self(): + """ + Try to download new versions of all of the pack configuration and data files + 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_text_file(base_url + "pack.ini?inline=false", "pack.ini") + download_text_file(base_url + "pack-lock.ini?inline=false", "pack-lock.ini") + download_file(base_url + "icon.png?inline=false", "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_text_file(f"{base_url}config/{path}", os.path.join("config", path)) + + config = load_config() + + +def find_minecraft_directory(): + """ + Find the location of the user's .minecraft folder based on + their operating system. + :returns: the absolute path to the .minecraft directory + """ + if sys.platform == "linux": + return os.path.join(os.path.expanduser('~'), ".minecraft") + elif sys.platform == "win32": + return os.path.join(os.environ["APPDATA"], ".minecraft") + elif sys.platform == "darwin": + return os.path.join(os.path.expanduser('~'), "Library", "Application Support", "minecraft") + else: + raise RuntimeError(f"Unsupported operating system `{sys.platform}`. Please define a location for the pack in your `local-config.ini` file") + + +def find_jre(): + """ + Find a usable install of Java, either from a user-installed JRE or + from the Minecraft Launcher's integrated JRE. + + :return: the absolute path of a working Java executable + """ + if shutil.which("java") is not None: + return shutil.which("java") + if sys.platform == 'win32': # We can try and use the Minecraft Launcher's integrated JRE on Windows + if os.path.exists("C:\\Program Files (x86)\\Minecraft Launcher\\runtime\\jre-x64\\java.exe"): + 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. + Note that this only works on binary files. + """ + with open(destination, "wb") as f: + with requests.get(url, stream=True) as dl: + shutil.copyfileobj(dl.raw, f) + +def download_text_file(url, destination): + """ + Given the URL to a text file, download it to the file named + by `destination`. Note that this only works for text files, not binary files. + """ + with open(destination, "w") as f: + f.write(requests.get(url).text) + + +# take a string and only keep filename-friendly parts +def sanitize_text(text): + sanitized = "" + replacement_map = {" ": "-"} + for char in text: + if char.isalnum(): + sanitized += char.lower() + elif char in replacement_map: + sanitized += replacement_map[char] + return sanitized + + +def generate_base64_icon(filename): + with open(filename, "rb") as f: + return "data:image/png;base64," + base64.b64encode(f.read()).decode("utf8") + + +def read_file(fil): + """ + Given a filename, read its contents in as a list of tuples. + This function strips out comment lines and whitespaces. + """ + strings = [] + with open(fil) as f: + for line in f: + string = line.strip().split() + if len(line) > 1 and line[0] != '#': + # run strip on each element + string = tuple(map(lambda x: x.strip(), string)) + strings.append(string) + + return strings + + +def game_version_from_string(string): + if string is not None: + try: + return tuple(int(x) for x in string.split('.')) + except: + pass + return (2, 0, 0) + + + +def threaded_find_url(homepage_url, game_version): + """ + Helper function that finds a single mod URL based on the homepage. + """ + if 'curseforge' in homepage_url: + ffx = firefox() + final_url = find_cdn(ffx, homepage_url, game_version) + ffx.close() + else: + final_url = requests.get(homepage_url).url + return final_url + + +def find_updated_urls(forge_urls, game_version, threads=20): + """ + Given a list of mod homepage URLs, find all of their direct download links in parallel. + """ + + # First, check that we can successfully open a Firefox instance in the main thread. + # This provides us with a much nicer error message and quicker feedback. + f = firefox() + f.close() + + with multiprocessing.Pool(threads) as pool: + # No progress indicator possible + # return pool.map(threaded_find_url, forge_urls) + + # Much longer, but allows us to do a nice progress indicator + result_futures = [] + for url in forge_urls: + result_futures.append(pool.apply_async(threaded_find_url, (url, game_version))) + + results = [] + for i,f in enumerate(result_futures): + results.append(f.get()) + print(f'\r{i+1}/{len(result_futures)} URLs updated ({round((i+1)/len(result_futures)*100)}%)', end='') + print() + + return results + + +def threaded_calc_sha1(direct_url): + """ + Helper function that downloads and calculates a single SHA1 hash from a direct download URL. + """ + resp = requests.get(direct_url) + hsh = hashlib.sha1(resp.content).hexdigest() + return hsh + + +def find_checksums(direct_urls, threads=8): + """ + Given a list of direct download URLs, download them all and calculate the SHA1 checksum of the file at that location. + """ + + with multiprocessing.Pool(threads) as pool: + # Much longer, but allows us to do a nice progress indicator + result_futures = [] + for url in direct_urls: + result_futures.append(pool.apply_async(threaded_calc_sha1, (url,))) + + results = [] + for i,f in enumerate(result_futures): + results.append(f.get()) + print(f'\r{i+1}/{len(result_futures)} checksums calculated ({round((i+1)/len(result_futures)*100)}%)', end='') + print() + + return results + + +def find_cdn(ffx, url, version): + """ + Given a mod home URL, finds the most up-to-date mod version compatible with the given game version. + Returns the direct Forge CDN download URL + """ + try: + # This goes to the "all files" page, where we get a table view of all + page_index = 0; + while True: + ffx.get(url + f'/files/all?page={page_index}') + mod_versions = ffx.find_elements_by_class_name("listing")[0].find_elements_by_xpath("tbody/tr") # extract the table of files from the page + row_info = collections.namedtuple("row_info", ["type", "filename", "cdn_id", "game_version"]) # create a custom tuple because data + rows = [] + for version_entry in mod_versions: + # parse out the four fields that we use + entry_cells = version_entry.find_elements_by_tag_name("td") + release_type = entry_cells[0].text + # Note that this is NOT the final filename - this is just the "release name". + filename = urllib.parse.quote(entry_cells[1].find_elements_by_tag_name("a")[0].text) + try: + game_version = tuple([int(x) for x in entry_cells[4].find_element_by_class_name("mr-2").text.split(".")]) # get game version and convert to tuple + except: + game_version = (0, 0, 0) + cdn_id = entry_cells[1].find_element_by_tag_name("a").get_property("href").split("/")[-1] + + #TODO make this configurable + if 'fabric' not in filename.lower() or 'forge' in filename.lower(): + rows.append(row_info(release_type, filename, cdn_id, game_version)) + rows.sort(key=lambda x: x.game_version, reverse=True) + try: + best_row = next(x for x in rows if x.game_version <= version) + break + except StopIteration: + if len(ffx.find_elements_by_class_name("pagination-next--inactive")) != 0: + raise + page_index += 1 + + + # We need to find the real, ForgeCDN compatible filename now by going to the file page. + ffx.get(f'{url}/files/{best_row.cdn_id}') + # This will probably break in the future + filename = ffx.find_elements_by_xpath("html/body/div/main/div/div/section/div/div/div/section/section/article/div/div/span")[1].text + # URL escape the filename! + filename = urllib.parse.quote(filename) + + # ForgeCDN requires that the leading zeroes are stripped from each portion of the CDN ID, hence the int() cast. + return f'https://media.forgecdn.net/files/{int(best_row.cdn_id[:4])}/{int(best_row.cdn_id[4:])}/{filename}' + + except: + # import traceback; traceback.print_exc() + print(f"[!] Failed to retrieve valid CDN URL for {url}") + return None + + +def firefox(): + """ + Start a headless Firefox instance and return the Selenium refrence to it. + """ + try: + from selenium.webdriver import Firefox + from selenium.webdriver.firefox.options import Options + except: + print("Applying updates requires the `selenium` package") + exit(0) + options = Options() + options.add_argument('-headless') + options.add_argument('--window-size 1920,1080') + + # for ~~cursed~~ windows people, put geckodriver in the folder next to modpackman.py + if(os.path.exists("../../geckodriver.exe")): + return Firefox(executable_path='../../geckodriver', options=options) + return Firefox(options=options) + + +# Configuration is automatically loaded from pack.ini and local-config.ini, +# and made accessible here as a global +config = load_config() diff --git a/whitelist.txt b/whitelist.txt deleted file mode 100644 index 76c8d1f..0000000 --- a/whitelist.txt +++ /dev/null @@ -1 +0,0 @@ -# file names in here are NOT removed from mods folder during 'install' |