commit bce7314e43c42941ea3e27e20827bd15114ee047 Author: harmacist Date: Sat Feb 11 16:19:12 2023 -0600 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7cd30a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +mods/ +venv/ +.idea/ +*.jar +*.log \ No newline at end of file diff --git a/StickyPiston/StickyPiston.py b/StickyPiston/StickyPiston.py new file mode 100644 index 0000000..d058d62 --- /dev/null +++ b/StickyPiston/StickyPiston.py @@ -0,0 +1,38 @@ +import requests +import datetime + + +class StickyPiston: + def __init__(self): + self.url = "" + self.version = "v0.0.1a" + self.last_version_check = datetime.datetime.utcnow() + self.up_to_date = None + + def version_url(self) -> str: + sep = '/' if self.url[-1] != '/' else '' + return self.url + sep + 'piston_version' + + def check_for_updates(self): + """Checks for any updates to the StickyPiston module.""" + self.last_version_check = datetime.datetime.utcnow() + + resp = requests.get(self.version_url()) + resp.raise_for_status() + + current_version = resp.json()['version'] + self.up_to_date = current_version.strip() == self.version.strip() + + def up_to_date(self) -> str: + if not self.up_to_date: + self.check_for_updates() + if self.up_to_date(): + return "Up to date" + return "New version available" + + def update(self, force: bool = False): + if force or not self.up_to_date(): + # perform the update + pass + else: + return 0 diff --git a/StickyPiston/__init__.py b/StickyPiston/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyPiston.py b/pyPiston.py new file mode 100644 index 0000000..320fb88 --- /dev/null +++ b/pyPiston.py @@ -0,0 +1,251 @@ +# find minecraft installation +# install forge client +# create install if one doesn't already exist +# copy mods to folder + +import os +from sys import stdout +import json +import uuid +import logging +import webbrowser +from pathlib import Path +from datetime import datetime +from distutils.dir_util import copy_tree +import zipfile +# from StickyPiston import StickyPiston + + +DT_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' +PROFILE_NAME = "William's Modded 1.19.2" + + +class MinecraftProfile: + def __init__( + self, + profile_id: str, + dt_created: datetime, + icon: str, + last_used: datetime, + last_version_id: str, + profile_name: str = '', + profile_type: str = 'custom', + java_args: str = '', + game_dir: Path = None + ): + self.profile_id = profile_id + self.dt_created = dt_created + self.icon = icon + self.last_used = last_used + self.last_version_id = last_version_id + self.profile_name = profile_name + self.profile_type = profile_type + self.java_args = java_args + self.game_dir = game_dir + + @classmethod + def from_json_obj(cls, obj, profile_id): + return cls( + profile_id=profile_id, + dt_created=datetime.strptime(obj['created'], DT_FORMAT) if obj.get('created') else None, + icon=obj.get('icon', 'Crafting_Table'), + last_used=datetime.strptime(obj['lastUsed'], DT_FORMAT) if obj.get('lastUsed') else None, + last_version_id=obj.get('lastVersionId', 'latest-release'), + profile_name=obj.get('name', ''), + profile_type=obj.get('type', 'latest-release'), + java_args=obj.get('javaArgs', ''), + game_dir=Path(obj['gameDir']) if obj.get('gameDir') else None + ) + + def to_json(self) -> dict: + return { + "created": self.dt_created.strftime(DT_FORMAT), + "gameDir": str(self.game_dir), + 'icon': self.icon, + 'javaArgs': self.java_args, + 'lastUsed': self.last_used.strftime(DT_FORMAT), + 'lastVersionId': self.last_version_id, + 'name': self.profile_name, + 'type': self.profile_type + } + + +def get_minecraft_folder(**kwargs): + + mc_path = kwargs.get('minecraft_root', Path(os.getenv('APPDATA')) / '.minecraft') + + if mc_path.exists(): + return mc_path + else: + return None + + +def load_profiles(**kwargs): + + mc_path = kwargs.get('minecraft_path', get_minecraft_folder()) + + profiles_file = mc_path / 'launcher_profiles.json' + + profiles_list = [] + if profiles_file.exists(): + with open(profiles_file, 'r') as json_file: + profiles_json = json.load(json_file) + for profile in profiles_json['profiles']: + profile_obj = profiles_json['profiles'][profile] + profiles_list.append( + MinecraftProfile.from_json_obj(profile_obj, profile) + ) + + return profiles_list + + +def add_profile(profile: MinecraftProfile): + mc_path = get_minecraft_folder() + + profiles_file = mc_path / 'launcher_profiles.json' + + profiles_list = load_profiles() + + if profile.profile_id in [p.profile_id for p in profiles_list]: + return 0 + + with open(profiles_file, 'r') as json_file: + json_raw = json.load(json_file) + + json_raw['profiles'][profile.profile_id] = profile.to_json() + + with open(profiles_file, 'w') as json_file: + json.dump(json_raw, json_file, indent=2) + + return 1 + + +def any_key(): + return input("Press enter to continue...") + + +def package_mods(profile_name: str): + """ + Packages mods into a zip file for distribution + :param profile_name: The name of the profile to be deployed + :return: + """ + + profiles_list = load_profiles() + + profile_to_load = [profile for profile in profiles_list if profile.profile_name == profile_name] + + if not profile_to_load: + raise IndexError(f"Profile {profile_name} does not exist!") + + if len(profile_to_load) == 1: + profile_to_load = profile_to_load[0] + else: + raise IndexError(f"Multiple profiles match the name {profile_name}!") + + # Create the zip + zip_file_name = profile_name.strip().lower().replace(' ', '_') + '.zip' + with zipfile.ZipFile(zip_file_name, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk(profile_to_load.game_dir): + for file in files: + if not file.endswith('.jar'): + # Skip all non-jar files + continue + zipf.write( + os.path.join(root, file), + os.path.relpath( + os.path.join(root, file), + os.getcwd() + ) + ) + + +def main(): + log = logging.getLogger() + log.setLevel(logging.DEBUG) + log.addHandler(logging.StreamHandler(stream=stdout)) + + profiles = load_profiles() + forge_profiles = [profile for profile in profiles if 'forge' in profile.last_version_id] + log.info(f"Hello and welcome! I'm going to install the modpack this script was packaged with.") + log.info(f"You shouldn't have to do anything - I'll let you know if I found anything weird that I need help with.") + log.debug(f"Found {len(forge_profiles)} modded profiles") + + if PROFILE_NAME in [profile_name.profile_name for profile_name in forge_profiles]: + log.info(f"Modpack is already installed, exiting!") + return 0 + + # Before we add the profile, we need to make sure forge is installed + forge_profile = [profile for profile in profiles if profile.profile_name == 'forge' + and profile.last_version_id == '1.19.2-forge-43.1.42'] + if not forge_profile: + # We need to install the forge version we were packaged with! + # First, make sure java is installed and on the path: + java_version_output = os.system('java --version') + if java_version_output != 0: + # Java is not installed! + log.info(f"Java is not installed! Please install OpenJDK and run this script again.") + webbrowser.open("https://aka.ms/download-jdk/microsoft-jdk-17.0.4.1-windows-x64.msi") + any_key() + return 1 + + log.info(f"Forge needs to be installed - select \"install client\" and follow the on-screen prompts.") + log.info(f"Check back here when you're done!") + forge_install_output = os.system('java -jar forge-1.19.2-43.1.42-installer.jar') + if forge_install_output != 0: + log.error(f"The install command failed! Is java installed?") + any_key() + return 1 + + log.info(f"Modpack is not already installed - just needed to check that before continuing!") + modded_folder = Path(os.getenv('APPDATA')) / '.moddedminecraft' / '1.19.2' / PROFILE_NAME + if modded_folder.exists(): + log.warning(f"Profile path already exists! Continuing, but this is weird!") + else: + try: + modded_folder.mkdir(parents=True, exist_ok=True) + except BaseException as e: + log.error(f"I ran into an error while trying to make the profile path... can you make it for me?\n" + f"The path needs to be here: {modded_folder}\n" + f"Once you create it, run this script again, and I should be good to go.", + exc_info=e) + any_key() + raise e + log.info(f"Created profile path successfully!") + modded_profile = MinecraftProfile( + profile_id=str(uuid.uuid4()).replace('-', ''), + profile_name=PROFILE_NAME, + dt_created=datetime.utcnow(), + icon='Furnace_On', + java_args=" ".join([ + "-Xmx8G", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=32M" + ]), + last_used=datetime.utcnow(), + last_version_id='1.19.2-forge-43.1.42', + profile_type='custom', + game_dir=modded_folder + ) + + log.debug(f"Profile to be added:\n{json.dumps(modded_profile.to_json(), indent=4)}") + + add_profile(modded_profile) + log.info(f"Profile added! Copying mod files...") + + mods_folder = Path('mods') + dest_mods_folder = modded_profile.game_dir / 'mods' + dest_mods_folder.mkdir(parents=True, exist_ok=True) + + copy_tree(str(mods_folder), str(dest_mods_folder)) + + log.info("All done!") + any_key() + + +if __name__ == '__main__': + main()