aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore11
-rwxr-xr-xinstaller.py111
-rw-r--r--installer.spec48
-rwxr-xr-xmodpackman.py171
-rw-r--r--mods.txt109
-rw-r--r--packs/jeffrey-2/icon.icobin0 -> 1662 bytes
-rw-r--r--packs/jeffrey-2/icon.pngbin0 -> 1195 bytes
-rw-r--r--packs/jeffrey-2/pack-lock.ini52
-rw-r--r--packs/jeffrey-2/pack.ini54
-rw-r--r--packs/jeffrey-2/version.txt48
-rw-r--r--packs/jeffrey-3/.gitignore8
-rw-r--r--packs/jeffrey-3/config/creeperconfetti-common.toml8
-rw-r--r--packs/jeffrey-3/icon.icobin0 -> 9662 bytes
-rw-r--r--packs/jeffrey-3/icon.pngbin0 -> 923 bytes
-rw-r--r--packs/jeffrey-3/local-config.ini9
-rw-r--r--packs/jeffrey-3/pack-lock.ini86
-rw-r--r--packs/jeffrey-3/pack.ini100
-rw-r--r--packs/jeffrey/config/tconstruct.cfg143
-rw-r--r--packs/jeffrey/icon.icobin0 -> 1662 bytes
-rw-r--r--packs/jeffrey/icon.pngbin0 -> 1156 bytes
-rw-r--r--packs/jeffrey/pack-lock.ini103
-rw-r--r--packs/jeffrey/pack.ini118
-rw-r--r--packs/jeffrey/version.txt (renamed from version.txt)0
-rw-r--r--readme.md65
-rw-r--r--todo.md7
-rwxr-xr-xupdate.py222
-rw-r--r--util.py312
-rw-r--r--whitelist.txt1
28 files changed, 1445 insertions, 341 deletions
diff --git a/.gitignore b/.gitignore
index f14d830..fcb831d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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 bc124e0..0000000
--- a/mods.txt
+++ /dev/null
@@ -1,109 +0,0 @@
-# fill this file with the same as downloads.txt but with links to the latest urls instead
-# e.g.
-thaumcraft.jar https://minecraft.curseforge.com/projects/thaumcraft/files/latest
-baubles.jar https://minecraft.curseforge.com/projects/baubles/files/latest
-opencomputers.jar https://minecraft.curseforge.com/projects/opencomputers/files/latest
-twilightforest.jar https://minecraft.curseforge.com/projects/the-twilight-forest/files/latest
-connectedtextures.jar https://minecraft.curseforge.com/projects/ctm/files/latest
-traverse.jar https://minecraft.curseforge.com/projects/traverse/files/latest
-actuallyadditions.jar https://minecraft.curseforge.com/projects/actually-additions/files/latest
-advancedrocketry.jar https://minecraft.curseforge.com/projects/advanced-rocketry/files/latest
-ae2.jar https://minecraft.curseforge.com/projects/applied-energistics-2/files/latest
-ae2stuff.jar https://minecraft.curseforge.com/projects/ae2-stuff/files/latest
-ae2wtl.jar https://minecraft.curseforge.com/projects/ae2-wireless-terminal-library/files/latest
-applecore.jar https://minecraft.curseforge.com/projects/applecore/files/latest
-appleskin.jar https://minecraft.curseforge.com/projects/appleskin/files/latest
-bdlib.jar https://minecraft.curseforge.com/projects/bdlib/files/latest
-betterbuilderwands.jar https://minecraft.curseforge.com/projects/better-builders-wands/files/latest
-chisel.jar https://minecraft.curseforge.com/projects/chisel/files/latest
-libvulpes.jar https://minecraft.curseforge.com/projects/libvulpes/files/latest
-p455w0rdlib.jar https://minecraft.curseforge.com/projects/p455w0rds-library/files/latest
-mysticallib.jar https://minecraft.curseforge.com/projects/mysticallib/files/latest
-immersiveengineering.jar https://minecraft.curseforge.com/projects/immersive-engineering/files/latest
-pamharvestcraft.jar https://minecraft.curseforge.com/projects/pams-harvestcraft/files/latest
-deepresonance.jar https://minecraft.curseforge.com/projects/deep-resonance/files/latest
-jei.jar https://minecraft.curseforge.com/projects/jei/files/latest
-waystones.jar https://minecraft.curseforge.com/projects/waystones/files/latest
-eleccore.jar https://minecraft.curseforge.com/projects/eleccore/files/latest
-mcjtylib.jar https://minecraft.curseforge.com/projects/mcjtylib/files/latest
-dynamictrees.jar https://minecraft.curseforge.com/projects/dynamictrees/files/latest
-natura.jar https://minecraft.curseforge.com/projects/natura/files/latest
-tconstruct.jar https://minecraft.curseforge.com/projects/tinkers-construct/files/latest
-mantle.jar https://minecraft.curseforge.com/projects/mantle/files/latest
-treecapitator.jar https://minecraft.curseforge.com/projects/treecapitator-port/files/latest
-ironchests.jar https://minecraft.curseforge.com/projects/iron-chests/files/latest
-ironbackpacks.jar https://minecraft.curseforge.com/projects/iron-backpacks/files/latest
-hwyla.jar https://minecraft.curseforge.com/projects/hwyla/files/latest
-# super fancy custom version to make it actually work
-dynamictrees-traverse-compat.jar https://f-1.karel.pw/dttraverse-1.4.1e.jar
-dynamictrees-thaum-compat.jar https://minecraft.curseforge.com/projects/dttc/files/latest
-dynamictrees-pams-compat.jar https://minecraft.curseforge.com/projects/dtphc/files/latest
-enderstorage.jar https://minecraft.curseforge.com/projects/ender-storage-1-8/files/latest
-chickenchunks.jar https://minecraft.curseforge.com/projects/chicken-chunks-1-8/files/latest
-morpheus.jar https://minecraft.curseforge.com/projects/morpheus/files/latest
-codechickenlib.jar https://minecraft.curseforge.com/projects/codechicken-lib-1-8/files/latest
-extra-utilities.jar https://minecraft.curseforge.com/projects/extra-utilities/files/latest
-openblocks.jar https://minecraft.curseforge.com/projects/openblocks/files/latest
-openmodslib.jar https://minecraft.curseforge.com/projects/openmodslib/files/latest
-
-# many more mods from FTB packs
-bibliocraft.jar https://minecraft.curseforge.com/projects/bibliocraft/files/latest
-akashic-tome.jar https://minecraft.curseforge.com/projects/akashic-tome/files/latest
-diet-hoppers.jar https://minecraft.curseforge.com/projects/diet-hoppers/files/latest
-gravestones.jar https://minecraft.curseforge.com/projects/gravestone-mod/files/latest
-foamfix.jar https://minecraft.curseforge.com/projects/foamfix-for-minecraft/files/latest
-autoreglib.jar https://minecraft.curseforge.com/projects/autoreglib/files/latest
-# danger: possible quark conflict
-inventory-tweaks.jar https://minecraft.curseforge.com/projects/inventory-tweaks/files/latest
-cyclops-core.jar https://minecraft.curseforge.com/projects/cyclops-core/files/latest
-common-capabilities.jar https://minecraft.curseforge.com/projects/common-capabilities/files/latest
-integrated-dynamics.jar https://minecraft.curseforge.com/projects/integrated-dynamics/files/latest
-integrated-tunnels.jar https://minecraft.curseforge.com/projects/integrated-tunnels/files/latest
-integrated-crafting.jar https://minecraft.curseforge.com/projects/integrated-crafting/files/latest
-jer.jar https://minecraft.curseforge.com/projects/just-enough-resources-jer/files/latest
-roots.jar https://minecraft.curseforge.com/projects/roots/files/latest
-lightlevel.jar https://minecraft.curseforge.com/projects/light-level-overlay-reloaded/files/latest
-morph-o-tool.jar https://minecraft.curseforge.com/projects/morph-o-tool/files/latest
-botania.jar https://minecraft.curseforge.com/projects/botania/files/latest
-storage-drawers.jar https://minecraft.curseforge.com/projects/storage-drawers/files/latest
-chameleon.jar https://minecraft.curseforge.com/projects/chameleon/files/latest
-xnet.jar https://minecraft.curseforge.com/projects/xnet/files/latest
-yabba.jar https://minecraft.curseforge.com/projects/yabba/files/latest
-wanionlib.jar https://minecraft.curseforge.com/projects/wanionlib/files/latest
-unidict.jar https://minecraft.curseforge.com/projects/unidict/files/latest
-reauth.jar https://minecraft.curseforge.com/projects/reauth/files/latest
-#Latest danknull is bad. 1.4.43 until further notice.
-danknull.jar https://minecraft.curseforge.com/projects/dank-null/files/2624277/download
-psi.jar https://minecraft.curseforge.com/projects/psi/files/latest
-thaumic-jei.jar https://minecraft.curseforge.com/projects/thaumic-jei/files/latest
-quark.jar https://minecraft.curseforge.com/projects/quark/files/latest
-ftblib.jar https://minecraft.curseforge.com/projects/ftblib/files/latest
-redstone-flux.jar https://minecraft.curseforge.com/projects/redstone-flux/files/latest
-brandonscore.jar https://minecraft.curseforge.com/projects/brandons-core/files/latest
-nowither.jar https://minecraft.curseforge.com/projects/bad-wither-no-cookie-reloaded/files/latest
-optifine.jar https://karel.pw/optifine-1.12.2.jar
-
-# alexander black's redstone mods
-# causing a crash, so commented out for now
-mrtjpcore.jar https://minecraft.curseforge.com/projects/mrtjpcore/files/latest
-project-red-base.jar https://minecraft.curseforge.com/projects/project-red-base/files/latest
-project-red-integration.jar https://minecraft.curseforge.com/projects/project-red-integration/files/latest
-project-red-fabrication.jar https://minecraft.curseforge.com/projects/project-red-fabrication/files/latest
-project-red-mechanical.jar https://minecraft.curseforge.com/projects/project-red-mechanical/files/latest
-project-red-lighting.jar https://minecraft.curseforge.com/projects/project-red-lighting/files/latest
-project-red-world.jar https://minecraft.curseforge.com/projects/project-red-world/files/latest
-project-red-compat.jar https://minecraft.curseforge.com/projects/project-red-compat/files/latest
-fmp-cbe.jar https://minecraft.curseforge.com/projects/forge-multipart-cbe/files/latest
-# urbad
-# forge-relocation.jar https://minecraft.curseforge.com/projects/forge-relocation/files/latest
-# forge-relocation-fmp.jar https://minecraft.curseforge.com/projects/forge-relocation-fmp-plugin/files/latest
-uppers.jar https://minecraft.curseforge.com/projects/uppers/files/latest
-redstone-gauges-and-switches.jar https://minecraft.curseforge.com/projects/redstone-gauges-and-switches/files/latest
-redstone-plus-plus.jar https://minecraft.curseforge.com/projects/redstoneplusplus/files/latest
-torch-levers.jar https://minecraft.curseforge.com/projects/torch-lever/files/latest
-plated.jar https://minecraft.curseforge.com/projects/plated/files/latest
-sign-button.jar https://minecraft.curseforge.com/projects/sign-button/files/latest
-floodlights.jar https://minecraft.curseforge.com/projects/floodlights/files/latest
-chisel-bits.jar https://minecraft.curseforge.com/projects/chisels-bits/files/latest
-#Causes crash on server :(
-#futureminecraft.jar https://minecraft.curseforge.com/projects/future-minecraft/files/latest
diff --git a/packs/jeffrey-2/icon.ico b/packs/jeffrey-2/icon.ico
new file mode 100644
index 0000000..0525305
--- /dev/null
+++ b/packs/jeffrey-2/icon.ico
Binary files differ
diff --git a/packs/jeffrey-2/icon.png b/packs/jeffrey-2/icon.png
new file mode 100644
index 0000000..0dfbb45
--- /dev/null
+++ b/packs/jeffrey-2/icon.png
Binary files differ
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..54966d0
--- /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.com/1F335/modpackman/-/raw/master/packs/jeffrey-2/
+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/packs/jeffrey-2/version.txt b/packs/jeffrey-2/version.txt
new file mode 100644
index 0000000..1cc2ba4
--- /dev/null
+++ b/packs/jeffrey-2/version.txt
@@ -0,0 +1,48 @@
+# Format: <jarname> <hex digested sha1> <direct download url>
+#VERSION 29
+twilightforest.jar 9a4afa9c65bc13045d07823433fa6b25372bb481 https://media.forgecdn.net/files/2818/55/twilightforest-1.12.2-3.10.1013-universal.jar
+tconstruct.jar e27b77377aa9e9b8dbe7dcd33d01cf22a989112b https://media.forgecdn.net/files/2809/406/TConstruct-1.12.2-2.13.0.171.jar
+baubles.jar cb13fcfb18a9cb0cbd825fd5fe8d813c77368549 https://media.forgecdn.net/files/2518/667/Baubles-1.12-1.5.2.jar
+jei.jar b7ce5f00e9709e08e81c597dc2d7336f58b7fe8e https://media.forgecdn.net/files/2803/400/jei_1.12.2-4.15.0.291.jar
+psi.jar f8a0d27b42d639101b39632d2a309dd7a45f0593 https://media.forgecdn.net/files/2724/967/Psi-r1.1-77.jar
+opencomputers.jar 8eff5dc6b00a50b13ad6f3e6a838049bbf8306f8 https://media.forgecdn.net/files/2828/357/OpenComputers-MC1.12.2-1.7.5.192.jar
+chisel-and-bits.jar 0e6f159254e6899651087e5b1464bac91698d1d3 https://media.forgecdn.net/files/2720/655/chiselsandbits-14.33.jar
+chisel.jar d9a74dde6aafade73328dbf45bf7ec8aca30e038 https://media.forgecdn.net/files/2813/538/Chisel-MC1.12.2-1.0.1.44.jar
+dynamictrees.jar 835d7e360839e59186aa72b73ab5d592a68e0b0a https://media.forgecdn.net/files/2822/231/DynamicTrees-1.12.2-0.9.6.jar
+dynamictrees-pams-compat.jar 2e429e9705cb49afb72e2650001d003fd755ce42 https://media.forgecdn.net/files/2656/685/DynamicTreesPHC-1.12.2-1.4.2.jar
+dynamictreestraverse.jar 19c1e2e6b9c6ee4a9a93b8b50969be7bb6e13011 https://f-1.karel.pw/dttraverse-1.4.1e.jar
+traverse.jar b16aaff09a6e77ba193e42f88f5d5bb8a85e063a https://media.forgecdn.net/files/2613/657/Traverse-1.12.2-1.6.0-69.jar
+roots.jar 7baac411d3cda10d55e80718ea2e3bb707c2cc89 https://media.forgecdn.net/files/2865/501/Roots-1.12.2-3.0.24.jar
+ae2.jar e5c3c11eafc5daf73652a2dabe855e3759caa8b5 https://media.forgecdn.net/files/2747/63/appliedenergistics2-rv6-stable-7.jar
+gravestones.jar b2cc9a25054fd6878ba242a6080a2866140bbeb0 https://media.forgecdn.net/files/2744/766/gravestone-1.10.3.jar
+enderstorage.jar 7a872baf72b1da038704056a0cf7bbcc40bfa4d6 https://media.forgecdn.net/files/2755/787/EnderStorage-1.12.2-2.4.6.137-universal.jar
+waila.jar 7280d5c0dab42436549bcefc63ff64a1049e5501 https://media.forgecdn.net/files/2568/751/Hwyla-1.8.26-B41_1.12.2.jar
+harvestcraft.jar ffeb8369801e898c8a1598b37d124c251f40ba93 https://media.forgecdn.net/files/2771/840/Pam's%20HarvestCraft%201.12.2zf.jar
+akashictome.jar b11bf9d93f4bd7a2eeb8cfe49c1b30ce1a2f5a37 https://media.forgecdn.net/files/2648/656/AkashicTome-1.2-12.jar
+railcraft.jar ea2085a509b816bb9a3cdd79f2f44175b588737a https://media.forgecdn.net/files/2687/757/railcraft-12.0.0.jar
+optifine.jar e805d4be5c2a3343488c573145606e90bb13816d https://karel.pw/optifine-1.12.2.jar
+autoreglib.jar 267269ca7f1a71fb3bb35bdb8e61702a4da6263e https://media.forgecdn.net/files/2746/11/AutoRegLib-1.3-32.jar
+ctm.jar d9133571636ec2c4c590dd69ac41ef813c63a8ab https://media.forgecdn.net/files/2832/564/CTM-MC1.12.2-1.0.1.30.jar
+codechickenlib.jar b6a7e3b889c354216059a3bfad298e30a1e46a2d https://media.forgecdn.net/files/2779/848/CodeChickenLib-1.12.2-3.2.3.358-universal.jar
+mysticallib.jar 6b08bddd8c2f4e08a60b8bc995f60becb6d4fcaa https://media.forgecdn.net/files/2865/499/mysticallib-1.12.2-1.6.0.jar
+patchouli.jar 9804876a655365926757eda750189cd97b5bad69 https://media.forgecdn.net/files/2731/963/Patchouli-1.0-20.jar
+mysticalworld.jar 43dc67caef3bff6fd4ecae0b2bde8e12a5ba4cf7 https://media.forgecdn.net/files/2838/234/mysticalworld-1.12.2-1.6.1.jar
+diet-hoppers.jar 2aea3d9a64551cefe0a1b6f5c5edf57959796b66 https://media.forgecdn.net/files/2482/543/diethopper-1.1.jar
+worsebarrels.jar 2a5a0b83b503d7b8b2e995fc1669f54e15fe62f2 https://media.forgecdn.net/files/2769/265/worsebarrels-1.3.0.jar
+wearable-backpacks.jar 3774558b2abb31af680bc169d5058b80db5184c2 https://media.forgecdn.net/files/2800/372/WearableBackpacks-1.12.2-3.1.4.jar
+eerie-entities.jar d0011e4888ddd5f95cd3677ea1fc8cffb723c696 https://media.forgecdn.net/files/2728/340/EerieEntities-1.12.2-1.0.7.jar
+bookshelf.jar 83a1864dd78f48102609849dd36866d6cf32b907 https://media.forgecdn.net/files/2836/960/Bookshelf-1.12.2-2.3.590.jar
+veining.jar a0dbc6ad8021c8fa2f7d00de8058499268000232 https://media.forgecdn.net/files/2578/505/veining-1.3.2-1.12.x.jar
+minecoprocessors.jar aa953ffcfb6935669392c95cab68f552f9b1942c https://media.forgecdn.net/files/2599/694/minecoprocessors-1.12.2-5.jar
+librarianlib.jar 8cab3fe9c5cd969625cd8655d17163f1f1617f64 https://media.forgecdn.net/files/2836/225/librarianlib-1.12.2-4.20-release.jar
+forgelin.jar 7a87553fcb808a45d9c7e03f113b355ac7fd10d7 https://media.forgecdn.net/files/2785/465/Forgelin-1.8.4.jar
+dimdoors.jar 30c939f2305b862ed70b2144912ba25418430c36 https://media.forgecdn.net/files/2558/528/DimensionalDoors-3.0.9-287.jar
+translocators.jar b008cc099c15e0cb121c43c889504fc367b3dedd https://media.forgecdn.net/files/2755/795/Translocators-1.12.2-2.5.2.81-universal.jar
+forgemultipartcbe.jar 3306ea22380bc9b6a0170b23fa0251085d5a6e25 https://media.forgecdn.net/files/2755/790/ForgeMultipart-1.12.2-2.6.2.83-universal.jar
+magneticraft.jar 1e32b4d2e0c38a3f129605ed945e55af19d17d13 https://media.forgecdn.net/files/2807/901/Magneticraft_1.12-2.8.2-dev.jar
+ic2.jar 2f5597fc13866f8e88fbb7359046511cac2f5371 https://media.forgecdn.net/files/2746/892/industrialcraft-2-2.8.170-ex112.jar
+modelloader.jar 8f77152980cafb1be5a4204f5773daef74de8627 https://media.forgecdn.net/files/2744/735/modelloader-1.1.7.jar
+mtlib.jar a4625a61c9ef53412e0e467d23f25c5543658677 https://media.forgecdn.net/files/2684/561/MTLib-3.0.6.jar
+appleskin.jar 23162a97cab0adb4be2fc6d3937c613929d1d5c7 https://media.forgecdn.net/files/2496/585/AppleSkin-mc1.12-1.0.9.jar
+mantle.jar a1e5d5c197dae3e92637cafb8cd996185191165b https://media.forgecdn.net/files/2713/386/Mantle-1.12-1.3.3.55.jar
+worleys-caves.jar b0449312ce83d420b7f79c1444fd40891e278faf https://media.forgecdn.net/files/2781/861/worleycaves-1.5.1.jar
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
new file mode 100644
index 0000000..3a75d9c
--- /dev/null
+++ b/packs/jeffrey-3/icon.ico
Binary files differ
diff --git a/packs/jeffrey-3/icon.png b/packs/jeffrey-3/icon.png
new file mode 100644
index 0000000..d60084b
--- /dev/null
+++ b/packs/jeffrey-3/icon.png
Binary files differ
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..44f0dcf
--- /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/master/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
diff --git a/packs/jeffrey/config/tconstruct.cfg b/packs/jeffrey/config/tconstruct.cfg
new file mode 100644
index 0000000..476e44c
--- /dev/null
+++ b/packs/jeffrey/config/tconstruct.cfg
@@ -0,0 +1,143 @@
+# Configuration file
+
+~CONFIG_VERSION: 0.1
+
+clientside {
+ # If true all of Tinkers' blocks with contents (tables, basin, drying racks,...) will render their contents in the world
+ B:renderInventoryInWorld=true
+
+ # If true use a null render layer when building the models to render tables. Fixes an issue with chisel, but the config is provide in case it breaks something.
+ B:renderInventoryNullLayer=true
+
+ # If true tools will show additional info in their tooltips
+ B:extraTooltips=true
+
+ # If true all variants of the different tables will be listed in creative. Set to false to only have the oak variant for all tables.
+ B:listAllTables=true
+
+ # If true all material variants of the different tools will be listed in creative. Set to false to only have the first found material for all tools (usually wood).
+ B:listAllToolMaterials=true
+
+ # If true all material variants of the different parts will be listed in creative. Set to false to only have the first found material for all parts (usually wood).
+ B:listAllPartMaterials=true
+
+ # If true, temperatures in the smeltery and in JEI will display in celsius. If false they will use the internal units of Kelvin, which may be better for devs
+ B:temperatureCelsius=true
+
+ # If true tools will enable the forge bucket model on startup and then turn itself off. This is only there so that a fresh install gets the buckets turned on by default.
+ B:enableForgeBucketModel=false
+
+ # REQUIRES DEBUG MODULE. Will do nothing if debug module is disabled. If true the texture map will be dumped into the run directory, just like old forge did.
+ B:dumpTextureMap=false
+}
+
+
+gameplay {
+ # Fortune increases drops after harvesting a block with autosmelt
+ B:AutosmeltFortuneInteraction=true
+
+ # Adds a recipe that allows you to craft 3 gravel into a flint
+ B:addFlintRecipe=true
+
+ # Adds a recipe that allows you to get leather from drying cooked meat
+ B:addLeatherDryingRecipe=true
+
+ # Allows the creation of bricks from molten clay
+ B:allowBrickCasting=true
+
+ # Pattern and Part chests keep their inventory when harvested.
+ B:chestsKeepInventory=true
+
+ # Allows to craft all tool parts of all materials in the part builder, including materials that normally have to be cast with a smeltery.
+ B:craftCastableMaterials=false
+
+ # Blacklist of registry names or TE classnames for the crafting station to connect to. Mainly for compatibility.
+ S:craftingStationBlacklist <
+ de.ellpeck.actuallyadditions.mod.tile.TileEntityItemViewer
+ >
+
+ # Adds single-use clay casts.
+ B:enableClayCasts=true
+
+ # If true, piggybackpacks can only pick up players and mobs that can be leashed in vanilla. If false any mob can be picked up.
+ B:limitPiggybackpack=false
+
+ # If true, requires slimeballs in the vanilla slimeblock recipe to match in color, otherwise gives a pink slimeblock
+ B:matchVanillaSlimeblock=false
+
+ # Allows the creation of obsidian in the smeltery, using a bucket of lava and water.
+ B:obsidianAlloy=true
+
+ # Preferred mod ID for oredictionary outputs. Top most mod ID will be the preferred output ID, and if none is found the first output stack is used.
+ S:orePreference <
+ minecraft
+ tconstruct
+ thermalfoundation
+ forestry
+ immersiveengineering
+ embers
+ ic2
+ >
+
+ # Determines the ratio of ore to ingot, or in other words how many ingots you get out of an ore. This ratio applies to all ores (including poor and dense). The ratio can be any decimal, including 1.5 and the like, but can't go below 1. THIS ALSO AFFECTS MELTING TEMPERATURE!
+ D:oreToIngotRatio=2.0
+
+ # List of items to ignore when generating melting recipes from the crafting registry. For example, ignoring sticks allows metal pickaxes to melt down.
+ # Format: oreName or modid:item[:meta]. If meta is unset, uses wildcard
+ S:oredictMeltingIgnore <
+ dustRedstone
+ plankWood
+ stickWood
+ stickTreatedWood
+ string
+ minecraft:chest:0
+ >
+
+ # Enables all items, even if the Module needed to obtain them is not active
+ B:registerAllItems=false
+
+ # Allows to reuse stencils in the stencil table to turn them into other stencils
+ B:reuseStencils=true
+
+ # Players who enter the world for the first time get a Tinkers' Book
+ B:spawnWithBook=true
+
+ # REQUIRES DEBUG MODULE. Tests all IMC integrations with dummy recipes. May significantly impact gameplay, so its advised you disable this outside of dev environements.
+ B:testIMC=false
+}
+
+
+worldgen {
+ # If true slime islands will generate
+ B:generateSlimeIslands=true
+
+ # If true slime islands generate in superflat worlds
+ B:generateIslandsInSuperflat=false
+
+ # One in every X chunks will contain a slime island
+ I:slimeIslandRate=2500
+
+ # One in every X chunks will contain a magma island in the nether
+ I:magmaIslandRate=730
+
+ # Prevents generation of slime islands in the listed dimensions
+ I:slimeIslandBlacklist <
+ -1
+ 1
+ >
+
+ # If true, slime islands wont generate in dimensions which aren't of type surface. This means they wont generate in modded cave dimensions like the deep dark.
+ B:slimeIslandsOnlyGenerateInSurfaceWorlds=true
+
+ # If true, cobalt ore will generate in the nether
+ B:genCobalt=true
+
+ # If true, ardite ore will generate in the nether
+ B:genArdite=true
+
+ # Approx Ores per chunk
+ I:cobaltRate=20
+ I:arditeRate=20
+}
+
+
diff --git a/packs/jeffrey/icon.ico b/packs/jeffrey/icon.ico
new file mode 100644
index 0000000..c9378fc
--- /dev/null
+++ b/packs/jeffrey/icon.ico
Binary files differ
diff --git a/packs/jeffrey/icon.png b/packs/jeffrey/icon.png
new file mode 100644
index 0000000..2c14f75
--- /dev/null
+++ b/packs/jeffrey/icon.png
Binary files differ
diff --git a/packs/jeffrey/pack-lock.ini b/packs/jeffrey/pack-lock.ini
new file mode 100644
index 0000000..1d26bfc
--- /dev/null
+++ b/packs/jeffrey/pack-lock.ini
@@ -0,0 +1,103 @@
+[global]
+pack_version = 21
+config_files = tconstruct.cfg
+
+[mod_versions]
+thaumcraft = fe0899048f1796df04e9727bbf1898df30492a00,https://media.forgecdn.net/files/2629/23/Thaumcraft-1.12.2-6.1.BETA26.jar
+baubles = cb13fcfb18a9cb0cbd825fd5fe8d813c77368549,https://media.forgecdn.net/files/2518/667/Baubles-1.12-1.5.2.jar
+opencomputers = 8eff5dc6b00a50b13ad6f3e6a838049bbf8306f8,https://media.forgecdn.net/files/2828/357/OpenComputers-MC1.12.2-1.7.5.192.jar
+twilightforest = a4c2d01f98350fe41374666db8e1419f770a1609,https://media.forgecdn.net/files/3051/450/twilightforest-1.12.2-3.11.1021-universal.jar
+connectedtextures = 892e0788531269bfa2f356e4e12825e19d4e5412,https://media.forgecdn.net/files/2915/363/CTM-MC1.12.2-1.0.2.31.jar
+traverse = b16aaff09a6e77ba193e42f88f5d5bb8a85e063a,https://media.forgecdn.net/files/2613/657/Traverse-1.12.2-1.6.0-69.jar
+actuallyadditions = 6e5f7deda0d3cf196b38f57eb60ef18a85b0ca2d,https://media.forgecdn.net/files/3117/927/ActuallyAdditions-1.12.2-r152.jar
+advancedrocketry = e163aba936f0daf7ab32fb48de88ce7db268af36,https://media.forgecdn.net/files/3074/437/AdvancedRocketry-1.12.2-1.7.0-235-universal.jar
+ae2 = e5c3c11eafc5daf73652a2dabe855e3759caa8b5,https://media.forgecdn.net/files/2747/63/appliedenergistics2-rv6-stable-7.jar
+ae2stuff = 8edee35416790dca35f39911c3b0aaf4d6609a73,https://media.forgecdn.net/files/2491/32/ae2stuff-0.7.0.4-mc1.12.2.jar
+ae2wtl = d76ffb730d9e02aeb6dd5bb3c5dd5fa39f5fe1b9,https://media.forgecdn.net/files/2830/114/AE2WTLib-1.12.2-1.0.34.jar
+applecore = a6a57a76936a559088e9282aacde6f761d7e0b39,https://media.forgecdn.net/files/2969/118/AppleCore-mc1.12.2-3.4.0.jar
+appleskin = 23162a97cab0adb4be2fc6d3937c613929d1d5c7,https://media.forgecdn.net/files/2496/585/AppleSkin-mc1.12-1.0.9.jar
+bdlib = 96d4142448fe2fd1245caa4dcfde3cf73a00132d,https://media.forgecdn.net/files/2518/31/bdlib-1.14.3.12-mc1.12.2.jar
+betterbuilderwands = 3505169dfeea40d726b23072cb695d3c0e5014da,https://media.forgecdn.net/files/2691/84/BetterBuildersWands-1.12.2-0.13.2.271%2B5997513.jar
+chisel = d378cee9195841ea78bb9800f543d718ef2c60ef,https://media.forgecdn.net/files/2915/375/Chisel-MC1.12.2-1.0.2.45.jar
+libvulpes = 7a8c2f5e206d02779fb8d15ecca0b1c7316a64be,https://media.forgecdn.net/files/2949/886/LibVulpes-1.12.2-0.4.2-75-universal.jar
+p455w0rdlib = be56bc229b7f418675bf46277e37a8272def5327,https://media.forgecdn.net/files/2830/265/p455w0rdslib-1.12.2-2.3.161.jar
+mysticallib = cb8c57761ca503c7ca4985991106d5df5ae2dd1a,https://media.forgecdn.net/files/3040/592/mysticallib-1.12.2-1.9.0.jar
+immersiveengineering = bac46245037c40ab6b86eb2914519937ee9bd851,https://media.forgecdn.net/files/2676/501/ImmersiveEngineering-0.12-89.jar
+pamharvestcraft = a064b434253305424755943dd66f389855ade953,https://media.forgecdn.net/files/2904/825/Pam%27s%20HarvestCraft%201.12.2zg.jar
+deepresonance = d64e2821636a70fa182152420d7782f88707793e,https://media.forgecdn.net/files/2704/397/deepresonance-1.12-1.8.0.jar
+jei = 3e88d2896ca868c3cedb65e117ad3a1b82488fa8,https://media.forgecdn.net/files/3043/174/jei_1.12.2-4.16.1.302.jar
+waystones = 877a5cd3ed8e3f29e900b74c8c5400ee6e43c3bf,https://media.forgecdn.net/files/2859/589/Waystones_1.12.2-4.1.0.jar
+eleccore = 112cda98b049902b8c073ec37b96495ff5e0bdf6,https://media.forgecdn.net/files/2853/959/ElecCore-1.12.2-1.9.453.jar
+mcjtylib = 8f3e381c4aea651f55ed1cd35fd69613fcd7f9da,https://media.forgecdn.net/files/2745/846/mcjtylib-1.12-3.5.4.jar
+dynamictrees = c7da6d971f6eecd9420902a8ef739d69831eab6b,https://media.forgecdn.net/files/3105/281/DynamicTrees-1.12.2-0.9.21.jar
+natura = 1485d9f3378eae092fb6ba690f1cb4484a1ce42c,https://media.forgecdn.net/files/2711/439/natura-1.12.2-4.3.2.69.jar
+tconstruct = e37e1f05ad0eaf567497ce655bc877dc1778bd0f,https://media.forgecdn.net/files/2902/483/TConstruct-1.12.2-2.13.0.183.jar
+mantle = a1e5d5c197dae3e92637cafb8cd996185191165b,https://media.forgecdn.net/files/2713/386/Mantle-1.12-1.3.3.55.jar
+treecapitator = d58eb72603d7d58ad5a5982da8684fe2b9703bfb,https://media.forgecdn.net/files/2722/878/%5B1.12%5DTreeCapitator-client-1.43.0.jar
+bspkrscore = 50c64ec7245c3f76a563d64c85e6ca3d549b1828,https://media.forgecdn.net/files/2924/423/%5B1.12.2%5Dbspkrscore-universal-8.0.1.jar
+ironchests = adc4c785a484c5d5a4ab9a29e1937faeed4312dd,https://media.forgecdn.net/files/2747/935/ironchest-1.12.2-7.0.72.847.jar
+ironbackpacks = 50758105dabe8f0d4c6e311e14ad2c1f5673d2cd,https://media.forgecdn.net/files/2564/573/IronBackpacks-1.12.2-3.0.8-12.jar
+hwyla = 7280d5c0dab42436549bcefc63ff64a1049e5501,https://media.forgecdn.net/files/2568/751/Hwyla-1.8.26-B41_1.12.2.jar
+dynamictrees-traverse-compat = 6d0f2c009a8332c811e5654affca54114747d441,https://media.forgecdn.net/files/3093/214/DynamicTreesTraverse-1.12.2-2.1.jar
+dynamictrees-thaum-compat = d87ea63dad1c44b575c18806ab0876dbda0c5ca4,https://media.forgecdn.net/files/3053/187/DynamicTreesTC-1.12.2-1.4.2.jar
+dynamictrees-pams-compat = 1988ffec2a0ec0bae8df98425a8f17cce3fa5620,https://media.forgecdn.net/files/3108/742/DynamicTreesPHC-1.12.2-2.0.5.jar
+enderstorage = 7a872baf72b1da038704056a0cf7bbcc40bfa4d6,https://media.forgecdn.net/files/2755/787/EnderStorage-1.12.2-2.4.6.137-universal.jar
+chickenchunks = 68a2c2291477782fee4655e26227cc40fcda7067,https://media.forgecdn.net/files/2755/785/ChickenChunks-1.12.2-2.4.2.74-universal.jar
+morpheus = c948341a1b2243d4f8081c4db2cd727c2c4a7ed0,https://media.forgecdn.net/files/2664/449/Morpheus-1.12.2-3.5.106.jar
+codechickenlib = b6a7e3b889c354216059a3bfad298e30a1e46a2d,https://media.forgecdn.net/files/2779/848/CodeChickenLib-1.12.2-3.2.3.358-universal.jar
+extra-utilities = 9f9a217b23626cb338cf229054ed490d913610dc,https://media.forgecdn.net/files/2678/374/extrautils2-1.12-1.9.9.jar
+openblocks = 6c231ff28e0c80025b0ec11f86ce9de1e9d109db,https://media.forgecdn.net/files/2699/56/OpenBlocks-1.12.2-1.8.1.jar
+openmodslib = 210cc6b18b63ae0d3110b8e8ed843bc847627eb0,https://media.forgecdn.net/files/2699/55/OpenModsLib-1.12.2-0.12.2.jar
+bibliocraft = 74d9f70c5fead2c17054953fae26af8d9fb7fd84,https://media.forgecdn.net/files/2574/880/BiblioCraft%5Bv2.4.5%5D%5BMC1.12.2%5D.jar
+akashic-tome = b11bf9d93f4bd7a2eeb8cfe49c1b30ce1a2f5a37,https://media.forgecdn.net/files/2648/656/AkashicTome-1.2-12.jar
+diet-hoppers = 2aea3d9a64551cefe0a1b6f5c5edf57959796b66,https://media.forgecdn.net/files/2482/543/diethopper-1.1.jar
+gravestones = 3daa7d4563965f6ec1954c8176e11fa3ba0b85ee,https://media.forgecdn.net/files/2608/278/gravestone-1.10.2.jar
+foamfix = 6df0efeff2486f284ea76fe1b6e2c0831cde004a,https://media.forgecdn.net/files/3137/883/foamfix-0.10.11-1.12.2.jar
+autoreglib = 267269ca7f1a71fb3bb35bdb8e61702a4da6263e,https://media.forgecdn.net/files/2746/11/AutoRegLib-1.3-32.jar
+inventory-tweaks = 3ba1e59a5109f3e94a4a0170877006b72fa77b1e,https://media.forgecdn.net/files/2923/460/InventoryTweaks-1.64%2Bdev.151.jar
+cyclops-core = ae8461284c69f30bfc54085a4c8b8db151d2598d,https://media.forgecdn.net/files/3010/617/CyclopsCore-1.12.2-1.6.6.jar
+common-capabilities = 1f9554d8595d7833f983e5dcd973081bdd725825,https://media.forgecdn.net/files/3053/17/CommonCapabilities-1.12.2-2.4.8.jar
+integrated-dynamics = 1fe5ade8137eeee6c9029263519504ad53a5368c,https://media.forgecdn.net/files/3053/12/IntegratedDynamics-1.12.2-1.1.10.jar
+integrated-tunnels = 8628873a343fa3bbf67b721af2a8788caddc5933,https://media.forgecdn.net/files/3053/8/IntegratedTunnels-1.12.2-1.6.13.jar
+integrated-crafting = 6ab247bd91f99ebfc0f7f97ca39ca77c3ed2c125,https://media.forgecdn.net/files/3010/631/IntegratedCrafting-1.12.2-1.0.10.jar
+jer = 3d4c9b8bb4ad948c7e26b7becd1c836f8cb34d07,https://media.forgecdn.net/files/2728/585/JustEnoughResources-1.12.2-0.9.2.60.jar
+roots = f954f42522ad35355e5215caa0a8b61904f29cd2,https://media.forgecdn.net/files/3056/896/Roots-1.12.2-3.0.32.jar
+mysticalworld = df31d6c6777ff03a643425fbb9114421e58aef95,https://media.forgecdn.net/files/3054/945/mysticalworld-1.12.2-1.9.2.jar
+patchouli = 9804876a655365926757eda750189cd97b5bad69,https://media.forgecdn.net/files/2731/963/Patchouli-1.0-20.jar
+lightlevel = 38de949c94ac06b2fce94e642b729568ce7788af,https://media.forgecdn.net/files/2563/430/LLOverlayReloaded-1.1.6-mc1.12.2.jar
+morph-o-tool = 55a363ccd8e0614229991cd1f6bf831eaf874ff7,https://media.forgecdn.net/files/2658/176/Morph-o-Tool-1.2-21.jar
+botania = d14becd0f2e5d8ab7d22579ada8426c22a6629a1,https://media.forgecdn.net/files/2668/710/Botania+r1.10-359.jar
+storage-drawers = c3f370ed6c158726061211334cbb72fd53f30364,https://media.forgecdn.net/files/2952/606/StorageDrawers-1.12.2-5.4.2.jar
+chameleon = 5ed3dd5fd1ebded57bfe525b4ece11232bd14e5e,https://media.forgecdn.net/files/2450/900/Chameleon-1.12-4.1.3.jar
+xnet = 085f5aef1d407f815028e5ecc87265cc5d8142e6,https://media.forgecdn.net/files/2745/852/xnet-1.12-1.8.2.jar
+yabba = a55cc1892d54b37e3a6c6c59cbb6e3b18f2d3f00,https://media.forgecdn.net/files/2819/161/YABBA-1.1.2.54.jar
+wanionlib = 71bb7ba2feed94a3cad90615ddece2d2ed8a7ab0,https://media.forgecdn.net/files/3064/112/WanionLib-1.12.2-2.5.jar
+unidict = 0b15b8da2e5867ba07c88564dab56f03ae063c56,https://media.forgecdn.net/files/3142/968/UniDict-1.12.2-3.0.3.jar
+reauth = be3dedc6a808f13828373176d653d2e0bb629eeb,https://media.forgecdn.net/files/2560/638/reauth-3.6.0.jar
+danknull = d082f6a00a3a11bc2bc4c9f8aac40851a413b92c,https://media.forgecdn.net/files/2962/52/DankNull-1.12.2-1.7.101.jar
+psi = c76503880249a7e92f99d0ef68637076c6844218,https://media.forgecdn.net/files/3085/917/Psi-r1.1-78.2.jar
+thaumic-jei = 727a6135b172bc8b0f048fe0c2977f54a9d316f0,https://media.forgecdn.net/files/2705/304/ThaumicJEI-1.12.2-1.6.0-27.jar
+quark = 37999cc0b83f1acb1522cdbc257eafb2da772812,https://media.forgecdn.net/files/2889/332/Quark-r1.6-178.jar
+ftblib = 7c20cd223bc2334edf153e074f35acdbcab3c976,https://media.forgecdn.net/files/2985/811/FTBLib-5.4.7.2.jar
+redstone-flux = 7835c1dcc006e5d4a406a6b0615012b825e25044,https://media.forgecdn.net/files/2920/436/RedstoneFlux-1.12-2.1.1.1-universal.jar
+brandonscore = e1971bd2b1235e3970d7be4e71dd976e16e885ff,https://media.forgecdn.net/files/3051/539/BrandonsCore-1.12.2-2.4.19.214-universal.jar
+nowither = 11ab53b71d86a7da97e5fa1a1705b62aa8da2b9c,https://media.forgecdn.net/files/3045/651/badwithernocookiereloaded-1.12.2-3.4.18.jar
+optifine = e805d4be5c2a3343488c573145606e90bb13816d,https://karel.pw/optifine-1.12.2.jar
+mrtjpcore = 981382e6c623e8fe68cee07c8ee1f6f2c77b94dd,https://media.forgecdn.net/files/2735/197/MrTJPCore-1.12.2-2.1.4.43-universal.jar
+project-red-base = 11168221284c53b89363e93ab36110a29c26c1a2,https://media.forgecdn.net/files/2745/545/ProjectRed-1.12.2-4.9.4.120-Base.jar
+project-red-integration = f05cda479b14d41e02120295b4bcbb0ab25738d2,https://media.forgecdn.net/files/2745/548/ProjectRed-1.12.2-4.9.4.120-integration.jar
+project-red-fabrication = 7766aa672bc20a2a44a6a231efdc05a7bbdf0073,https://media.forgecdn.net/files/2745/547/ProjectRed-1.12.2-4.9.4.120-fabrication.jar
+project-red-mechanical = 2b8690aefe03ea47212001fadd2b6883b5284924,https://media.forgecdn.net/files/2745/550/ProjectRed-1.12.2-4.9.4.120-mechanical.jar
+project-red-lighting = 41e17cd9ad1a6a27767df0a2366972f181d9ff89,https://media.forgecdn.net/files/2745/549/ProjectRed-1.12.2-4.9.4.120-lighting.jar
+project-red-world = 7184d18c1511526f0e266ab92f4d05cd728c247f,https://media.forgecdn.net/files/2745/551/ProjectRed-1.12.2-4.9.4.120-world.jar
+project-red-compat = 406381fafac5900cfc8a9d397ec6111c85f5efbe,https://media.forgecdn.net/files/2745/546/ProjectRed-1.12.2-4.9.4.120-compat.jar
+fmp-cbe = 3306ea22380bc9b6a0170b23fa0251085d5a6e25,https://media.forgecdn.net/files/2755/790/ForgeMultipart-1.12.2-2.6.2.83-universal.jar
+uppers = 1c7e77d0e2f6667680e861c8c7cfac740b2c3f01,https://media.forgecdn.net/files/2558/337/Uppers-0.0.6.jar
+redstone-gauges-and-switches = a7ededa0f4d02889393ddf73aa227bd4d5cd7bfb,https://media.forgecdn.net/files/3097/911/rsgauges-1.12.2-1.2.5.jar
+redstone-plus-plus = be7d93c3e884a7013ce7f9c988992c9cea534b33,https://media.forgecdn.net/files/2912/448/Redstone%2B%2B%20ver1.3e.jar
+torch-levers = 9aa89a00c0bd67721772a7cefd25306e8795f685,https://media.forgecdn.net/files/2691/698/TorchLever1.12.2-1.0.jar
+plated = 5b61c98f688c919efeec239f819cc94a00c18942,https://media.forgecdn.net/files/2602/81/Plated-0.1.0.jar
+sign-button = 637582a457ccc2b0146b9ed614ab67d4bfc27137,https://media.forgecdn.net/files/2495/528/SignButton-1.12.2-1.0.1.jar
+floodlights = ef71a0922bfc455e8c11b17dcf950737bf4d6c0b,https://media.forgecdn.net/files/2975/510/FloodLights-1.12.2-1.4.4-22.jar
+chisel-bits = 0e6f159254e6899651087e5b1464bac91698d1d3,https://media.forgecdn.net/files/2720/655/chiselsandbits-14.33.jar
+
diff --git a/packs/jeffrey/pack.ini b/packs/jeffrey/pack.ini
new file mode 100644
index 0000000..7b8e642
--- /dev/null
+++ b/packs/jeffrey/pack.ini
@@ -0,0 +1,118 @@
+[pack]
+name = J.E.F.F.R.E.Y.
+pack_base_url = https://gitlab.com/1F335/modpackman/-/raw/master/packs/jeffrey/
+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]
+thaumcraft = https://www.curseforge.com/minecraft/mc-mods/thaumcraft
+baubles = https://www.curseforge.com/minecraft/mc-mods/baubles
+opencomputers = https://www.curseforge.com/minecraft/mc-mods/opencomputers
+twilightforest = https://www.curseforge.com/minecraft/mc-mods/the-twilight-forest
+connectedtextures = https://www.curseforge.com/minecraft/mc-mods/ctm
+traverse = https://www.curseforge.com/minecraft/mc-mods/traverse-reforged
+actuallyadditions = https://www.curseforge.com/minecraft/mc-mods/actually-additions
+advancedrocketry = https://www.curseforge.com/minecraft/mc-mods/advanced-rocketry
+ae2 = https://www.curseforge.com/minecraft/mc-mods/applied-energistics-2
+ae2stuff = https://www.curseforge.com/minecraft/mc-mods/ae2-stuff
+ae2wtl = https://www.curseforge.com/minecraft/mc-mods/ae2wtlib
+applecore = https://www.curseforge.com/minecraft/mc-mods/applecore
+appleskin = https://media.forgecdn.net/files/2496/585/AppleSkin-mc1.12-1.0.9.jar
+bdlib = https://www.curseforge.com/minecraft/mc-mods/bdlib
+betterbuilderwands = https://www.curseforge.com/minecraft/mc-mods/better-builders-wands
+chisel = https://www.curseforge.com/minecraft/mc-mods/chisel
+libvulpes = https://www.curseforge.com/minecraft/mc-mods/libvulpes
+p455w0rdlib = https://www.curseforge.com/minecraft/mc-mods/p455w0rds-library
+mysticallib = https://www.curseforge.com/minecraft/mc-mods/mysticallib
+immersiveengineering = https://www.curseforge.com/minecraft/mc-mods/immersive-engineering
+pamharvestcraft = https://www.curseforge.com/minecraft/mc-mods/pams-harvestcraft
+deepresonance = https://www.curseforge.com/minecraft/mc-mods/deep-resonance
+jei = https://www.curseforge.com/minecraft/mc-mods/jei
+waystones = https://www.curseforge.com/minecraft/mc-mods/waystones
+eleccore = https://www.curseforge.com/minecraft/mc-mods/eleccore
+mcjtylib = https://www.curseforge.com/minecraft/mc-mods/mcjtylib
+dynamictrees = https://www.curseforge.com/minecraft/mc-mods/dynamictrees
+natura = https://www.curseforge.com/minecraft/mc-mods/natura
+tconstruct = https://www.curseforge.com/minecraft/mc-mods/tinkers-construct
+mantle = https://www.curseforge.com/minecraft/mc-mods/mantle
+treecapitator = https://www.curseforge.com/minecraft/mc-mods/treecapitator-updated
+bspkrscore = https://www.curseforge.com/minecraft/mc-mods/bspkrscore
+ironchests = https://www.curseforge.com/minecraft/mc-mods/iron-chests
+ironbackpacks = https://www.curseforge.com/minecraft/mc-mods/iron-backpacks
+hwyla = https://media.forgecdn.net/files/2568/751/Hwyla-1.8.26-B41_1.12.2.jar
+# super fancy custom version to make it actually work
+dynamictrees-traverse-compat = https://media.forgecdn.net/files/3093/214/DynamicTreesTraverse-1.12.2-2.1.jar
+dynamictrees-thaum-compat = https://www.curseforge.com/minecraft/mc-mods/dttc
+dynamictrees-pams-compat = https://www.curseforge.com/minecraft/mc-mods/dtphc
+enderstorage = https://www.curseforge.com/minecraft/mc-mods/ender-storage-1-8
+chickenchunks = https://www.curseforge.com/minecraft/mc-mods/chicken-chunks-1-8
+morpheus = https://www.curseforge.com/minecraft/mc-mods/morpheus
+codechickenlib = https://www.curseforge.com/minecraft/mc-mods/codechicken-lib-1-8
+extra-utilities = https://www.curseforge.com/minecraft/mc-mods/extra-utilities
+openblocks = https://www.curseforge.com/minecraft/mc-mods/openblocks
+openmodslib = https://www.curseforge.com/minecraft/mc-mods/openmodslib
+
+# many more mods from FTB packs
+bibliocraft = https://www.curseforge.com/minecraft/mc-mods/bibliocraft
+akashic-tome = https://www.curseforge.com/minecraft/mc-mods/akashic-tome
+diet-hoppers = https://www.curseforge.com/minecraft/mc-mods/diet-hoppers
+gravestones = https://media.forgecdn.net/files/2608/278/gravestone-1.10.2.jar
+foamfix = https://www.curseforge.com/minecraft/mc-mods/foamfix-optimization-mod
+autoreglib = https://www.curseforge.com/minecraft/mc-mods/autoreglib
+# danger: possible quark conflict
+inventory-tweaks = https://www.curseforge.com/minecraft/mc-mods/inventory-tweaks
+cyclops-core = https://www.curseforge.com/minecraft/mc-mods/cyclops-core
+common-capabilities = https://www.curseforge.com/minecraft/mc-mods/common-capabilities
+integrated-dynamics = https://www.curseforge.com/minecraft/mc-mods/integrated-dynamics
+integrated-tunnels = https://www.curseforge.com/minecraft/mc-mods/integrated-tunnels
+integrated-crafting = https://www.curseforge.com/minecraft/mc-mods/integrated-crafting
+jer = https://www.curseforge.com/minecraft/mc-mods/just-enough-resources-jer
+roots = https://www.curseforge.com/minecraft/mc-mods/roots
+mysticalworld = https://media.forgecdn.net/files/3054/945/mysticalworld-1.12.2-1.9.2.jar
+patchouli = https://media.forgecdn.net/files/2731/963/Patchouli-1.0-20.jar
+lightlevel = https://www.curseforge.com/minecraft/mc-mods/light-level-overlay-reloaded
+morph-o-tool = https://www.curseforge.com/minecraft/mc-mods/morph-o-tool
+botania = https://media.forgecdn.net/files/2668/710/Botania+r1.10-359.jar
+storage-drawers = https://www.curseforge.com/minecraft/mc-mods/storage-drawers
+chameleon = https://www.curseforge.com/minecraft/mc-mods/chameleon
+xnet = https://www.curseforge.com/minecraft/mc-mods/xnet
+yabba = https://www.curseforge.com/minecraft/mc-mods/yabba
+wanionlib = https://www.curseforge.com/minecraft/mc-mods/wanionlib
+unidict = https://www.curseforge.com/minecraft/mc-mods/unidict
+reauth = https://www.curseforge.com/minecraft/mc-mods/reauth
+#Latest danknull is bad. 1.4.43 until further notice.
+danknull = https://www.curseforge.com/minecraft/mc-mods/dank-null
+psi = https://www.curseforge.com/minecraft/mc-mods/psi
+thaumic-jei = https://www.curseforge.com/minecraft/mc-mods/thaumic-jei
+quark = https://www.curseforge.com/minecraft/mc-mods/quark
+ftblib = https://www.curseforge.com/minecraft/mc-mods/ftb-library
+redstone-flux = https://www.curseforge.com/minecraft/mc-mods/redstone-flux
+brandonscore = https://www.curseforge.com/minecraft/mc-mods/brandons-core
+nowither = https://www.curseforge.com/minecraft/mc-mods/bad-wither-no-cookie-reloaded
+optifine = https://karel.pw/optifine-1.12.2.jar
+
+# alexander black's redstone mods
+# causing a crash, so commented out for now
+mrtjpcore = https://www.curseforge.com/minecraft/mc-mods/mrtjpcore
+project-red-base = https://www.curseforge.com/minecraft/mc-mods/project-red-base
+project-red-integration = https://www.curseforge.com/minecraft/mc-mods/project-red-integration
+project-red-fabrication = https://media.forgecdn.net/files/2745/547/ProjectRed-1.12.2-4.9.4.120-fabrication.jar
+project-red-mechanical = https://www.curseforge.com/minecraft/mc-mods/project-red-mechanical
+project-red-lighting = https://www.curseforge.com/minecraft/mc-mods/project-red-lighting
+project-red-world = https://www.curseforge.com/minecraft/mc-mods/project-red-world
+project-red-compat = https://www.curseforge.com/minecraft/mc-mods/project-red-compat
+fmp-cbe = https://www.curseforge.com/minecraft/mc-mods/forge-multipart-cbe
+# urbad
+# forge-relocation.jar https://minecraft.curseforge.com/projects/forge-relocation/files/latest
+# forge-relocation-fmp.jar https://minecraft.curseforge.com/projects/forge-relocation-fmp-plugin/files/latest
+uppers = https://www.curseforge.com/minecraft/mc-mods/uppers
+redstone-gauges-and-switches = https://www.curseforge.com/minecraft/mc-mods/redstone-gauges-and-switches
+redstone-plus-plus = https://www.curseforge.com/minecraft/mc-mods/redstoneplusplus
+torch-levers = https://www.curseforge.com/minecraft/mc-mods/torch-lever
+plated = https://www.curseforge.com/minecraft/mc-mods/plated
+sign-button = https://www.curseforge.com/minecraft/mc-mods/sign-button
+floodlights = https://www.curseforge.com/minecraft/mc-mods/floodlights
+chisel-bits = https://www.curseforge.com/minecraft/mc-mods/chisels-bits
+#Causes crash on server :(
+#futureminecraft.jar https://minecraft.curseforge.com/projects/future-minecraft/files/latest
diff --git a/version.txt b/packs/jeffrey/version.txt
index 4c370fc..4c370fc 100644
--- a/version.txt
+++ b/packs/jeffrey/version.txt
diff --git a/readme.md b/readme.md
index cf9835b..0e73d6d 100644
--- a/readme.md
+++ b/readme.md
@@ -3,17 +3,66 @@
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.
+
diff --git a/todo.md b/todo.md
new file mode 100644
index 0000000..1d7eb4c
--- /dev/null
+++ b/todo.md
@@ -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 a7d3ab2..0000000
--- a/update.py
+++ /dev/null
@@ -1,222 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-import os
-import sys
-import hashlib
-import shutil
-import re
-
-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)")
-
-## 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):
- 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])
- 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):
- 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])
- 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.")
-
-# Use selenium to find curseforge CDN links around cloudflare
-def find_cdn(ffx, url):
- try:
- #ffx.get(url + '/download')
- ffx.get(url + '/files')
- #page_src = ffx.page_source
- #dl = re.search('Elerium.PublicProjectDownload.countdown\(".*?"\);', page_src)
- #if not dl:
- # return None
- dl = ffx.find_element_by_xpath("html/body/div/main/div/div/section/div/div/div/section/article/div/div/a").get_attribute("href")
- dl = re.search('\d{7}', dl)
- dl = dl.group(0)
- four = str(int(dl[:4]))
- three = str(int(dl[4:]))
-
- file_name = ffx.find_elements_by_xpath("html/body/div/main/div/div/section/div/div/div/section/article/div/div/span[contains(@class, 'text-sm')]")[1].text
- return 'https://media.forgecdn.net/files/{four}/{three}/{jar}'.format(four=four,three=three,jar=file_name)
-
- except:
- return None
-
-def firefox():
- print("Starting Selenium...")
- try:
- from selenium.webdriver import Firefox
- except:
- print("Applying updates requires the `selenium` package")
- os.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)
diff --git a/util.py b/util.py
new file mode 100644
index 0000000..6d7feed
--- /dev/null
+++ b/util.py
@@ -0,0 +1,312 @@
+#!/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=8):
+ """
+ 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:
+ if ".".join(map(str, version)) in filename:
+ game_version = version
+ else:
+ game_version = (2, 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"\n[!] 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'