# 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()