def get_raw_vspec(self): vspec_path = (self.versions_root / self.version_name / "{}.json".format(self.version_name)) if not self.version_manifest: if vspec_path.exists(): logger.debug("Found custom vspec ({})".format( self.version_name)) with open(vspec_path) as fp: return json.load(fp) else: die("Specified version ({}) not available".format( self.version_name)) url = self.version_manifest["url"] sha1 = self.version_manifest["sha1"] if vspec_path.exists() and file_sha1(vspec_path) == sha1: logger.debug( "Using cached vspec files, hash matches manifest ({})".format( self.version_name)) with open(vspec_path) as fp: return json.load(fp) try: logger.debug("Downloading vspec file") raw = requests.get(url).content vspec_path.parent.mkdir(parents=True, exist_ok=True) with open(vspec_path, "wb") as fp: fp.write(raw) j = json.loads(raw) return j except requests.ConnectionError: die("Failed to retrieve version json file. Check your internet connection." )
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 get_raw_asset_index_nodl(self, id_): fpath = self.launcher.get_path(Directory.ASSET_INDEXES, "{}.json".format(id_)) if fpath.exists(): with open(fpath) as fp: return json.load(fp) else: die("Asset index specified in 'assets' not available.")
def rename(im, instance_name, new_name): """Rename an instance.""" new_name = sanitize_name(new_name) if im.exists(instance_name): if im.exists(new_name): die("Instance with target name already exists.") im.rename(instance_name, new_name) else: die("No such instance exists.")
def install(pack_id, version, launcher, im, instance_name, use_beta): try: pack_manifest, version_manifest = resolve_pack_meta(pack_id, version, use_beta) except NotImplementedError as ex: die(ex) pack_name = pack_manifest["name"] pack_version = version_manifest["name"] if instance_name is None: instance_name = sanitize_name(f"{pack_name}-{pack_version}") if im.exists(instance_name): die("Instance {} already exists".format(instance_name)) logger.info(f"Installing {pack_name} {pack_version} as {instance_name}") forge_version_name = None game_version = None for target in version_manifest["targets"]: if target["name"] == "forge": try: forge_version_name = forge.install( versions_root=launcher.get_path(Directory.VERSIONS), libraries_root=launcher.get_path(Directory.LIBRARIES), forge_version=target["version"], ) except forge.AlreadyInstalledError as ex: forge_version_name = ex.args[0] elif target["name"] == "minecraft": game_version = target["version"] else: logger.warn(f"Skipping unsupported target {target['name']}") inst_version = forge_version_name or game_version inst = im.create(instance_name, inst_version) inst.config["java.memory.max"] = str(version_manifest["specs"]["recommended"]) + "M" mcdir: Path = inst.get_minecraft_dir() dq = DownloadQueue() for f in version_manifest["files"]: filepath: Path = mcdir / PurePath(f["path"]) / f["name"] filepath.parent.mkdir(exist_ok=True, parents=True) dq.add(f["url"], filepath, f["size"]) logger.info("Downloading modpack files") dq.download() logger.info(f"Installed successfully as {instance_name}")
def get_raw_asset_index(self, asset_index_spec): iid = asset_index_spec["id"] url = asset_index_spec["url"] sha1 = asset_index_spec["sha1"] fpath = self.launcher.get_path(Directory.ASSET_INDEXES, "{}.json".format(iid)) if fpath.exists() and file_sha1(fpath) == sha1: logger.debug("Using cached asset index, hash matches vspec") with open(fpath) as fp: return json.load(fp) try: logger.debug("Downloading new asset index") raw = requests.get(url).content with open(fpath, "wb") as fp: fp.write(raw) return json.loads(raw) except requests.ConnectionError: die("Failed to retrieve asset index.")
def jar(version, which, output): """Download the file and save.""" dlspec = version.vspec.downloads.get(which, None) if not dlspec: die("No such dlspec exists for version {}".format( version.version_name)) url = dlspec["url"] sha1 = dlspec["sha1"] ext = posixpath.basename(urllib.parse.urlsplit(url).path).split(".")[-1] if output is None: output = "{}_{}.{}".format(version.version_name, which, ext) if os.path.exists(output): die("Refusing to overwrite {}".format(output)) logger.info("Hash (sha1) should be {}".format(sha1)) logger.info("Downloading the {} file and saving to {}".format( which, output)) urllib.request.urlretrieve(dlspec["url"], output) if file_sha1(output) != sha1: logger.warning("Hash of downloaded file does not match")
def install_from_path(path, launcher, instance_manager, instance_name=None): if path.isascii() and path.isdecimal(): path = resolve_project_id(path) elif os.path.exists(path): if path.endswith(".ccip"): path = resolve_ccip(path) elif path.endswith(".zip"): with open(path, "rb") as fd: return install_from_zip(fd, launcher, instance_manager, instance_name) else: die("File must be .ccip or .zip") zipurl = resolve_packurl(path) with requests.get(zipurl, stream=True) as r: r.raise_for_status() with TemporaryFile() as tempfile: for chunk in r.iter_content(chunk_size=8192): tempfile.write(chunk) install_from_zip(tempfile, launcher, instance_manager, instance_name)
def get_jarfile_dl(self, verify_hashes=False, force=False): """Checks existence and hash of cached jar. Returns None if ok, otherwise returns download (url, size)""" logger.debug("Attempting to use jarfile: {}".format(self.jarfile)) dlspec = self.vspec.downloads.get("client", None) if dlspec is None: logger.debug("jarfile dlspec not availble, skipping hash check.") if not self.jarfile.exists(): die("jarfile does not exist and can not be downloaded.") return logger.debug("Checking jarfile.") if (force or not self.jarfile.exists() # The fabric-installer places an empty jarfile here, due to some # quirk of an old (git blame 2 years) version of the vanilla launcher. # https://github.com/FabricMC/fabric-installer/blob/master/src/main/java/net/fabricmc/installer/client/ClientInstaller.java#L49 or os.path.getsize(self.jarfile) == 0 or (verify_hashes and file_sha1(self.jarfile) != dlspec["sha1"])): logger.info( "Jar file ({}) will be downloaded with libraries.".format( self.jarname)) return dlspec["url"], dlspec.get("size", None)
def assert_java(java): try: jinfo = get_java_info(java) badjv = not jinfo["java.version"].startswith("1.8.0") 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 badjv: logger.warning( "Minecraft uses java 1.8.0 by default." " You may experience issues, especially with older versions of Minecraft." ) return jinfo except FileNotFoundError: die("Could not execute java at: {}. Have you installed it? Is it in yout PATH?" .format(java))
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 extract_natives(im, instance_name): """Extract natives and leave them on disk.""" if not im.exists(instance_name): die("No such instance exists.") inst = im.get(instance_name) inst.extract_natives()
def config_cli(ctx, im, instance_name): """Configure an instance.""" if im.exists(instance_name): ctx.obj = im.get(instance_name).config else: die("No such instance exists.")