aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan Jones <dylanjones2011@gmail.com>2020-11-29 18:08:06 -0500
committerDylan Jones <dylanjones2011@gmail.com>2020-11-29 18:08:06 -0500
commitb29b1bb338e8745c24d5348de8f65f4667b9e22f (patch)
treee1252ee15b60fd76b695fe029d3c667291a56b87
parentf3fafd67f085265a02881ca01474e2b4555495c2 (diff)
downloadmodpackman-b29b1bb338e8745c24d5348de8f65f4667b9e22f.tar.gz
modpackman-b29b1bb338e8745c24d5348de8f65f4667b9e22f.zip
Begin installer code
-rw-r--r--.gitignore2
-rw-r--r--installer.py28
-rwxr-xr-xmodpackman.py105
-rw-r--r--util.py153
-rw-r--r--version.txt8
5 files changed, 178 insertions, 118 deletions
diff --git a/.gitignore b/.gitignore
index e2fcdcb..77e38bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@ geckodriver
geckodriver.exe
geckodriver.log
__pycache__/
+*.swp
+*.swo
diff --git a/installer.py b/installer.py
new file mode 100644
index 0000000..2df64a4
--- /dev/null
+++ b/installer.py
@@ -0,0 +1,28 @@
+import subprocess
+import os
+import requests
+from util import config
+
+
+def install_forge(java_path):
+ """
+ :param java_path: path to a working Java executable
+ Downloads and runs the Forge installer specified in pack.ini.
+ """
+ pass
+
+
+def create_profile():
+ """
+ Automatically create a launcher profile for the modpack.
+ """
+ pass
+
+
+
+def main():
+ pass
+
+
+if __name__ == '__main__':
+ main()
diff --git a/modpackman.py b/modpackman.py
index 55ebea6..77b0c2c 100755
--- a/modpackman.py
+++ b/modpackman.py
@@ -3,8 +3,106 @@ import argparse
import os
import sys
import shutil
+import requests
+import pathlib
+import hashlib
import util
+from util import config
+
+# Apply updates to the actual mod pack
+def install(version_file, whitelist, mods_location):
+ pack_version = util.get_version_from_file(version_file)
+ print("Updating pack with version " + str(pack_version) + "...")
+ print()
+
+ # create the mods folder if it doesn't already exist
+ pathlib.Path(mods_location).mkdir(parents=True, exist_ok=True)
+
+ # (fname, checksum, url)
+ mods = util.read_file(version_file)
+ names = [mod[0] for mod in mods]
+ # whitelist client mods (e.g. optifine)
+ names += whitelist
+
+ i = 0
+ for mod in mods:
+ mod_path = os.path.join(mods_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(mods_location):
+ if jar not in names and os.path.splitext(jar)[1] == ".jar":
+ os.remove(os.path.join(mods_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(mods, version_file, game_version=(2, 0, 0)):
+ pack_version = util.get_version_from_file(version_file)
+ print("Populating version file...")
+ print("Getting new versions of all mods...")
+ mod_urls = util.find_updated_urls([x for x in mods.values()], game_version, threads=3)
+ print("Downloading and checksumming all mods...")
+ checksums = util.find_checksums(mod_urls)
+
+ # Write information out to version.txt
+ with open(version_file, 'w') as f:
+ f.write('# Format: <jarname> <hex digested sha1> <direct download url>\n')
+ f.write("#VERSION " + str(pack_version + 1) + "\n")
+ for name, checksum, url in zip((k+'.jar' for k in mods.keys()), checksums, mod_urls):
+ f.write(f'{name} {checksum} {url}\n')
+
+ print()
+ print("Done!")
+ print(f"Updates applied to {version_file}")
+ print("New pack version is " + str(pack_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(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(
@@ -24,17 +122,16 @@ parser.add_argument('command',
if __name__ == "__main__":
args = parser.parse_args()
- config = util.load_config()
mods = config['mods']
pack = config['pack']
# run the command
if args.command == "install":
- util.install("version.txt", pack["whitelist"], os.path.join(pack['location'], 'mods'))
+ install("version.txt", config['pack']["whitelist"], os.path.join(config['pack']['location'], 'mods'))
elif args.command == "apply_updates":
- util.apply_updates(mods, "version.txt", pack["game_version"])
+ apply_updates(config['mods'], "version.txt", config['pack']["game_version"])
elif args.command == "check_updates":
- util.check_updates(mods, "version.txt", pack["game_version"])
+ check_updates(config['mods'], "version.txt", config['pack']["game_version"])
else:
print("Error: command \"" + args.command + "\" does not exist")
parser.print_help()
diff --git a/util.py b/util.py
index 3e9e918..fba40fe 100644
--- a/util.py
+++ b/util.py
@@ -23,6 +23,9 @@ def load_config():
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:
@@ -30,19 +33,41 @@ def load_config():
config["pack"]["game_version"] = game_version_from_string(config["pack"]["game_version"])
- if "location" not in config["pack"]:
- if sys.platform == "linux":
- config["pack"]["location"] = os.path.join(os.path.expanduser('~'), ".minecraft", config['pack']['sanitized_name'])
- elif sys.platform == "win32":
- config["pack"]["location"] = os.path.join(os.environ["APPDATA"], ".minecraft", config['pack']['sanitized_name'])
- elif sys.platform == "darwin":
- config["pack"]["location"] = os.path.join(os.path.expanduser('~'), "Library", "Application Support", "minecraft", config['pack']['sanitized_name'])
- else:
- raise RuntimeError(f"Unsupported operating system `{sys.platform}`. Please define a location for the pack in your `local-config.ini` file")
-
# return the whole config file, pack configuration and modlist
return 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.")
+
+
# take a string and only keep filename-friendly parts
def sanitize_text(text):
sanitized = ""
@@ -87,100 +112,6 @@ def game_version_from_string(string):
return (2, 0, 0)
-# Apply updates to the actual mod pack
-def install(version_file, whitelist, mods_location):
- pack_version = get_version_from_file(version_file)
- print("Updating pack with version " + str(pack_version) + "...")
- print()
-
- # create the mods folder if it doesn't already exist
- pathlib.Path(mods_location).mkdir(parents=True, exist_ok=True)
-
- # (fname, checksum, url)
- mods = read_file(version_file)
- names = [mod[0] for mod in mods]
- # whitelist client mods (e.g. optifine)
- names += whitelist
-
- i = 0
- for mod in mods:
- mod_path = os.path.join(mods_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(mods_location):
- if jar not in names and os.path.splitext(jar)[1] == ".jar":
- os.remove(os.path.join(mods_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(mods, version_file, game_version=(2, 0, 0)):
- pack_version = get_version_from_file(version_file)
- print("Populating version file...")
- print("Getting new versions of all mods...")
- mod_urls = find_updated_urls([x for x in mods.values()], game_version, threads=3)
- print("Downloading and checksumming all mods...")
- checksums = find_checksums(mod_urls)
-
- # Write information out to version.txt
- with open(version_file, 'w') as f:
- f.write('# Format: <jarname> <hex digested sha1> <direct download url>\n')
- f.write("#VERSION " + str(pack_version + 1) + "\n")
- for name, checksum, url in zip((k+'.jar' for k in mods.keys()), checksums, mod_urls):
- f.write(f'{name} {checksum} {url}\n')
-
- print()
- print("Done!")
- print(f"Updates applied to {version_file}")
- print("New pack version is " + str(pack_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(mods, version_file, version=(2, 0, 0)):
- pack_version = 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 = read_file(version_file)
- old_urls = [mod[2] for mod in old]
- num_updates = 0
-
- print("Checking updates...")
- ffx = firefox()
-
- for mod in latest:
- print("Checking for updates to {mod[0]}...".format(mod=mod), end="")
- sys.stdout.flush() # takes care of line-buffered terminals
- if 'curseforge' in mod[1]:
- url = find_cdn(ffx, mod[1], version)
- else:
- url = requests.get(mod[1]).url
- if url in old_urls:
- print(" No updates")
- else:
- print(" Found update: " + url.split('/')[-1])
- num_updates += 1
- ffx.close()
-
- print("Finished checking for updates. {num} mods can be updated".format(num=num_updates))
- if num_updates >= 0:
- print("Run 'python update.py apply_updates' to create a new version with these updates applied.")
-
def threaded_find_url(homepage_url, game_version):
"""
@@ -292,9 +223,7 @@ def find_cdn(ffx, url, version):
return f'https://media.forgecdn.net/files/{int(best_row.cdn_id[:4])}/{int(best_row.cdn_id[4:])}/{filename}'
except:
- print(url)
- open('temp.txt', 'a').write(url)
- import traceback; traceback.print_exc()
+ # import traceback; traceback.print_exc()
return None
@@ -313,7 +242,11 @@ def firefox():
options.add_argument('-headless')
options.add_argument('--window-size 1920,1080')
#for ~~cursed~~ windows people, put geckodriver in this folder
- if(os.path.exists("./geckodriver")):
- return Firefox(executable_path='./geckodriver', options=options)
- return Firefox(options=options)
+ if(os.path.exists("./geckodriver.exe")):
+ return Firefox(executable_path='./geckodriver')
+ return Firefox()
+
+# Configuration is automatically loaded from pack.ini and local-config.ini,
+# and made accessible here as a global
+config = load_config()
diff --git a/version.txt b/version.txt
index 710244e..c874116 100644
--- a/version.txt
+++ b/version.txt
@@ -1,5 +1,5 @@
# Format: <jarname> <hex digested sha1> <direct download url>
-#VERSION 35
+#VERSION 36
building-gadgets.jar 13e89349f2929c205c6c80615c6e927f70c9b1e0 https://media.forgecdn.net/files/3097/299/buildinggadgets-3.7.2.jar
item-collectors.jar a4b3eeb92dd79736ce622e92f49bd5ff2b0b84f0 https://media.forgecdn.net/files/3101/223/itemcollectors-1.0.6-mc1.16.4.jar
natures-compass.jar 30ae4fe62fbfdff8d81f84a821588ba882851a04 https://media.forgecdn.net/files/3123/531/NaturesCompass-1.16.4-1.8.5.jar
@@ -27,7 +27,7 @@ immersive-engineering.jar a1263bd875531ee5bf70937fa207f4509efb1cfe https://media
botania.jar c890bd2ec0d13ead2627b0bf0c7599de6b2f6194 https://media.forgecdn.net/files/3086/502/Botania-1.16.3-409.jar
jei.jar 2bbcffbbe7ac7c93e75783637a0a78fddcb4109d https://media.forgecdn.net/files/3109/181/jei-1.16.4-7.6.0.58.jar
tetra.jar 6e097ef3053cc56b95973607db632d08ebf6c554 https://media.forgecdn.net/files/3104/240/tetra-1.16.4-3.3.1.jar
-immersive-portals.jar 3fc06df79a96fc9e695c3f1091772e8cd2b175cd https://github-production-release-asset-2e65be.s3.amazonaws.com/316149911/5d2ca300-2f8e-11eb-8586-1e03f0825404?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20201127%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201127T234755Z&X-Amz-Expires=300&X-Amz-Signature=2aa1740e6eb705062090827613c00ac60066b159a2530448633dddaf86d6093d&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=316149911&response-content-disposition=attachment%3B%20filename%3Dimmersive-portals-0.9.jar&response-content-type=application%2Foctet-stream
+immersive-portals.jar 3fc06df79a96fc9e695c3f1091772e8cd2b175cd https://github-production-release-asset-2e65be.s3.amazonaws.com/316149911/5d2ca300-2f8e-11eb-8586-1e03f0825404?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20201129%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201129T065251Z&X-Amz-Expires=300&X-Amz-Signature=181cc92add82b065cda2a9988f1a4d277f66e6a1e9554ed64187006e371e4ba6&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=316149911&response-content-disposition=attachment%3B%20filename%3Dimmersive-portals-0.9.jar&response-content-type=application%2Foctet-stream
inventory-sorter.jar fb538c5a8eb1e8a7a2bc35f3344b816634d53e4e https://media.forgecdn.net/files/3077/903/inventorysorter-1.16.1-18.1.0.jar
creeper-confetti.jar c1d06621ba453680fe31dd30fe11f6ea9bcc8e93 https://media.forgecdn.net/files/3063/500/creeperconfetti-3.4.jar
hwyla.jar 79123ef4c447affe57e77ee241d78c494bd8948d https://media.forgecdn.net/files/3033/593/Hwyla-forge-1.10.11-B78_1.16.2.jar
@@ -59,10 +59,10 @@ recipe-buffers.jar b6a5af3c63651ae8f5434cec4fa753a5bfc5fdbb https://media.forgec
harvest.jar f9618079d9456c23133ab89909fdd1c9fbae56b8 https://media.forgecdn.net/files/3087/381/harvest-1.16.3-1.0.3.jar
light-overlay.jar a3caff7768581d5d97b5433dd834dfa6d676aea9 https://media.forgecdn.net/files/3091/376/light-overlay-5.5.4.jar
morpheus.jar da30b18acd23c270a7da07ed8feb693291efcf90 https://media.forgecdn.net/files/3114/135/Morpheus-1.16.4-4.2.68.jar
-extreme-sound-muffler.jar 8144221087d309afcf68926849d4095becf9e5ab https://media.forgecdn.net/files/3117/97/extremeSoundMuffler-3.0_Forge-1.16.4.jar
+extreme-sound-muffler.jar 056a06346f1129b5dcdafe335a508c0e8254a7de https://media.forgecdn.net/files/3125/582/extremeSoundMuffler-3.1_Forge-1.16.4.jar
fast-workbench.jar c7f0296b5c569f1fe11aa6368b87bebfa01c9ddc https://media.forgecdn.net/files/3112/661/FastWorkbench-1.16.3-4.4.1.jar
findme.jar 7f18ff3af51b46c388d780c0b0afb9b400cc7962 https://media.forgecdn.net/files/3073/336/findme-1.16.3-2.1.0.0.jar
-wawla.jar d9a4dbdfe97fa58e591aa0c4c714c13abb93c318 https://media.forgecdn.net/files/3113/92/WAWLA-1.16.4-7.0.1.jar
+wawla.jar 9aeea4cc8ae5bfa89701435b7c71bffdc43cc49d https://media.forgecdn.net/files/3124/964/WAWLA-1.16.4-7.0.2.jar
iron-chests.jar 3e7a9131d1ea836527d5cf7a1761d8b4dc7026a3 https://media.forgecdn.net/files/3105/315/ironchest-1.16.4-11.2.10.jar
reauth.jar f5bdf682d64bcde250c8a2a9c1261e285540ac9b https://media.forgecdn.net/files/3105/779/ReAuth-1.16-Forge-3.9.3.jar
yungs-caves.jar 6b4603a8fc2cb2aa13e8cf745865b25e10e25bde https://media.forgecdn.net/files/3110/318/BetterCaves-1.16.3-1.0.5.jar