PyPiston/pyPiston.py
2023-02-11 16:19:12 -06:00

252 lines
8.4 KiB
Python

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