def setdefault(am, account): """Set the account as default.""" try: default = am.get(account) am.set_default(default) except AccountError as e: logger.error("Could not set default account: %s", e)
def assert_java(java, wanted): try: jinfo = get_java_info(java) bitness = jinfo.get("sun.arch.data.model", None) if bitness and bitness != "64": logger.warning( "You are not using 64-bit java. Things will probably not work." ) logger.info("Using java version: {} ({})".format( jinfo["java.version"], jinfo["java.vm.name"])) if not check_version_against(jinfo["java.version"], wanted): logger.warning("The version of Minecraft you are launching " "uses java {} by default.".format( wanted_to_str(wanted))) logger.warning( "You may experience issues, especially with older versions of Minecraft." ) major = get_major_version(jinfo["java.version"]) if int(major) < wanted["majorVersion"]: logger.error( "Note that at least java {} is required to launch at all.". format(wanted_to_str(wanted))) return jinfo except FileNotFoundError: die("Could not execute java at: {}. Have you installed it? Is it in yout PATH?" .format(java))
def download_libraries(self, java_info, verify_hashes=False, force=False): """Downloads missing libraries.""" logger.info("Checking libraries.") q = DownloadQueue() for library in self.get_libraries(java_info): if not library.available: continue basedir = self.launcher.get_path(Directory.LIBRARIES) abspath = library.get_abspath(basedir) ok = abspath.is_file() and os.path.getsize(abspath) > 0 if verify_hashes and library.sha1 is not None: ok = ok and file_sha1(abspath) == library.sha1 if not ok and not library.url: logger.error( f"Library {library.filename} is missing or corrupt " "and has no download url.") continue if force or not ok: q.add(library.url, library.get_abspath(basedir), library.size) jardl = self.get_jarfile_dl(verify_hashes, force) if jardl is not None: url, size = jardl q.add(url, self.jarfile, size=size) if len(q) > 0: logger.info("Downloading {} libraries.".format(len(q))) if not q.download(): logger.error( "Some libraries failed to download. If they are part of a non-vanilla " "profile, the original installer may need to be used.")
def refresh(am, account): """Refresh access token with Mojang servers.""" try: a = am.get(account) a.refresh() except (AccountError, RefreshError) as e: logger.error("Could not refresh account: %s", e)
def download(self): logger.debug("Downloading {} files.".format(self.total)) disable_progressbar = picomc.logging.debug if self.known_size: cm_progressbar = tqdm( total=self.total_size, disable=disable_progressbar, unit_divisor=1024, unit="iB", unit_scale=True, ) else: cm_progressbar = tqdm(total=self.total, disable=disable_progressbar) with cm_progressbar as tq, ThreadPoolExecutor(max_workers=self.workers) as tpe: for i, (url, dest) in enumerate(self.queue, start=1): cb = tq.update if self.known_size else (lambda x: None) fut = tpe.submit(self.download_file, i, url, dest, cb) self.fut_to_url[fut] = url try: for fut in concurrent.futures.as_completed(self.fut_to_url.keys()): self.reap_future(fut, tq) except KeyboardInterrupt as ex: self.cancel(tq, tpe) raise ex from None # Do this at the end in order to not break the progress bar. for error in self.errors: logger.error(error) return not self.errors
def version_cli(forge_version, game, latest): """Resolve version without installing.""" try: game_version, forge_version, version = resolve_version( game, forge_version, latest) logger.info( f"Found Forge version {forge_version} for Minecraft {game_version}" ) except VersionResolutionError as e: logger.error(e)
def launch(am, im, instance_name, account, version_override, verify): """Launch the instance.""" if account is None: account = am.get_default() else: account = am.get(account) if not im.exists(instance_name): logger.error("No such instance exists.") return inst = im.get(instance_name) try: inst.launch(account, version_override, verify_hashes=verify) except AccountError as e: logger.error("Not launching due to account error: {}".format(e))
def subproc(obj): args = [] for a in obj: if isinstance(a, str): args.append(a) else: if "rules" in a and not match_ruleset(a["rules"], java_info): continue if isinstance(a["value"], list): args.extend(a["value"]) elif isinstance(a["value"], str): args.append(a["value"]) else: logger.error("Unknown type of value field.") return args
def create(am, account, mojang_username, microsoft): """Create an account.""" try: if mojang_username: if microsoft: logger.error( "Do not use --microsoft with mojang_username argument") return acc = OnlineAccount.new(am, account, mojang_username) elif microsoft: acc = MicrosoftAccount.new(am, account) else: acc = OfflineAccount.new(am, account) am.add(acc) except AccountError as e: logger.error("Could not create account: %s", e)
def refresh(self, force=False): if self.fresh and not force: return False if self.is_authenticated: if self.validate(): return else: try: refresh = self._am.yggdrasil.refresh(self.access_token) self.access_token, self.uuid, self.gname = refresh self.fresh = True return True except RefreshError as e: logger.error( "Failed to refresh access_token," " please authenticate again." ) self.is_authenticated = False raise e finally: self.save() else: raise AccountError("Not authenticated.")
def install_cli(launcher, name, forge_version, game, latest): """Installs Forge. The best version is selected automatically based on the given parameters. By default, only stable Forge versions are considered, use --latest to enable beta versions as well. You can install a specific version of forge using the FORGE_VERSION argument. You can also choose the newest version for a specific version of Minecraft using --game.""" try: install( launcher.get_path(Directory.VERSIONS), launcher.get_path(Directory.LIBRARIES), game, forge_version, latest, version_name=name, ) except (VersionResolutionError, InstallationError, AlreadyInstalledError) as e: logger.error(e)
def authenticate(am, account): """Retrieve access token from Mojang servers using password.""" try: a = am.get(account) except AccountError: logger.error("AccountError", exc_info=True) return try: if isinstance(a, OfflineAccount): logger.error("Offline accounts cannot be authenticated") elif isinstance(a, OnlineAccount): import getpass p = getpass.getpass("Password: "******"Unknown account type") except AuthenticationError as e: logger.error("Authentication failed: %s", e)
def create(im, instance_name, version): """Create a new instance.""" if im.exists(instance_name): logger.error("An instance with that name already exists.") return im.create(instance_name, version)
def install_from_zip(zipfileobj, launcher, instance_manager, instance_name=None): with ZipFile(zipfileobj) as pack_zf: for fileinfo in pack_zf.infolist(): fpath = PurePath(fileinfo.filename) if fpath.parts[-1] == "manifest.json" and len(fpath.parts) <= 2: manifest_zipinfo = fileinfo archive_prefix = fpath.parent break else: raise ValueError("Zip file does not contain manifest") with pack_zf.open(manifest_zipinfo) as fd: manifest = json.load(fd) assert manifest["manifestType"] == "minecraftModpack" assert manifest["manifestVersion"] == 1 assert len(manifest["minecraft"]["modLoaders"]) == 1 forge_ver = manifest["minecraft"]["modLoaders"][0]["id"] assert forge_ver.startswith(FORGE_PREFIX) forge_ver = forge_ver[len(FORGE_PREFIX):] packname = manifest["name"] packver = manifest["version"] if instance_name is None: instance_name = "{}-{}".format(sanitize_name(packname), sanitize_name(packver)) logger.info(f"Installing {packname} version {packver}") else: logger.info( f"Installing {packname} version {packver} as instance {instance_name}" ) if instance_manager.exists(instance_name): die("Instace {} already exists".format(instance_name)) try: forge.install( versions_root=launcher.get_path(Directory.VERSIONS), libraries_root=launcher.get_path(Directory.LIBRARIES), forge_version=forge_ver, ) except forge.AlreadyInstalledError: pass # Trusting the game version from the manifest may be a bad idea inst = instance_manager.create( instance_name, "{}-forge-{}".format(manifest["minecraft"]["version"], forge_ver), ) # This is a random guess, but better than the vanilla 1G inst.config["java.memory.max"] = "4G" project_files = { mod["projectID"]: mod["fileID"] for mod in manifest["files"] } headers = {"User-Agent": "curl"} dq = DownloadQueue() logger.info("Retrieving mod metadata from curse") modcount = len(project_files) mcdir: Path = inst.get_minecraft_dir() moddir = mcdir / "mods" with tqdm(total=modcount) as tq: # Try to get as many file_infos as we can in one request # This endpoint only provides a few "latest" files for each project, # so it's not guaranteed that the response will contain the fileID # we are looking for. It's a gamble, but usually worth it in terms # of request count. The time benefit is not that great, as the endpoint # is slow. resp = requests.post(ADDON_URL, json=list(project_files.keys()), headers=headers) resp.raise_for_status() projects_meta = resp.json() for proj in projects_meta: proj_id = proj["id"] want_file = project_files[proj_id] for file_info in proj["latestFiles"]: if want_file == file_info["id"]: dq.add( file_info["downloadUrl"], moddir / file_info["fileName"], size=file_info["fileLength"], ) del project_files[proj_id] batch_recvd = modcount - len(project_files) logger.debug("Got {} batched".format(batch_recvd)) tq.update(batch_recvd) with ThreadPoolExecutor(max_workers=16) as tpe: def dl(pid, fid): resp = requests.get(GETINFO_URL.format(pid, fid), headers=headers) resp.raise_for_status() file_info = resp.json() assert file_info["id"] == fid dq.add( file_info["downloadUrl"], moddir / file_info["fileName"], size=file_info["fileLength"], ) # Get remaining individually futmap = {} for pid, fid in project_files.items(): fut = tpe.submit(dl, pid, fid) futmap[fut] = (pid, fid) for fut in concurrent.futures.as_completed(futmap.keys()): try: fut.result() except Exception as ex: pid, fid = futmap[fut] logger.error( "Could not get metadata for {}/{}: {}".format( pid, fid, ex)) else: tq.update(1) logger.info("Downloading mod jars") dq.download() logger.info("Copying overrides") overrides = archive_prefix / manifest["overrides"] for fileinfo in pack_zf.infolist(): if fileinfo.is_dir(): continue fname = fileinfo.filename try: outpath = mcdir / PurePath(fname).relative_to(overrides) except ValueError: continue if not outpath.parent.exists(): outpath.parent.mkdir(parents=True, exist_ok=True) with pack_zf.open(fileinfo) as infile, open(outpath, "wb") as outfile: shutil.copyfileobj(infile, outfile) logger.info("Done installing {}".format(instance_name))
def die(mesg, code=1): logger.error(mesg) sys.exit(code)
def delete(im, instance_name): """Delete the instance (from disk).""" if im.exists(instance_name): im.delete(instance_name) else: logger.error("No such instance exists.")
def remove(am, account): """Remove the account.""" try: am.remove(account) except AccountError as e: logger.error("Could not remove account: %s", e)
def _exec_mc(self, account, v, java, java_info, gamedir, libraries, natives, verify_hashes): libs = [lib.get_abspath(self.libraries_root) for lib in libraries] libs.append(v.jarfile) classpath = join_classpath(*libs) version_type, user_type = (("picomc", "mojang") if account.online else ("picomc/offline", "offline")) mc = v.vspec.mainClass if hasattr(v.vspec, "minecraftArguments"): mcargs = shlex.split(v.vspec.minecraftArguments) sjvmargs = [ "-Djava.library.path={}".format(natives), "-cp", classpath ] elif hasattr(v.vspec, "arguments"): mcargs, jvmargs = process_arguments(v.vspec.arguments, java_info) sjvmargs = [] for a in jvmargs: tmpl = Template(a) res = tmpl.substitute( natives_directory=natives, launcher_name="picomc", launcher_version=picomc.__version__, classpath=classpath, version_name=v.version_name, jar_name=v.jarname, library_directory=self.libraries_root, classpath_separator=os.pathsep, ) sjvmargs.append(res) if not account.can_launch_game(): logger.error( "Account is not ready to launch game. Online accounts need to be authenticated at least once" ) return try: account.refresh() except RefreshError as e: logger.warning(f"Failed to refresh account due to an error: {e}") smcargs = [] for a in mcargs: tmpl = Template(a) res = tmpl.substitute( auth_player_name=account.gname, auth_uuid=account.uuid, auth_access_token=account.access_token, # Only used in old versions. auth_session="token:{}:{}".format(account.access_token, account.uuid), user_type=user_type, user_properties={}, version_type=version_type, version_name=v.version_name, game_directory=gamedir, assets_root=self.assets_root, assets_index_name=v.vspec.assets, game_assets=v.get_virtual_asset_path(), clientid="", # TODO fill these out properly auth_xuid="", ) smcargs.append(res) my_jvm_args = [ "-Xms{}".format(self.config["java.memory.min"]), "-Xmx{}".format(self.config["java.memory.max"]), ] if verify_hashes: my_jvm_args.append("-Dpicomc.verify=true") my_jvm_args += shlex.split(self.config["java.jvmargs"]) fargs = [java] + sjvmargs + my_jvm_args + [mc] + smcargs if logging.debug: logger.debug("Launching: " + shlex.join(fargs)) else: logger.info("Launching the game") subprocess.run(fargs, cwd=gamedir)