import platform import shutil import asyncio import zipfile import aiohttp from pathlib import Path from worthless import constants, helper class HDiffPatch: """ Contains legacy HDiffPatch support for worthless You should not use this class directly, since it's automatically used by GameManager """ def __init__(self, git_url=None, data_dir=None): if not git_url: git_url = constants.HDIFFPATCH_GIT_URL self._git_url = git_url if not data_dir: self._appdirs = constants.APPDIRS self.temp_path = Path(self._appdirs.user_cache_dir).joinpath("HDiffPatch") self.data_path = Path(self._appdirs.user_data_dir).joinpath( "Tools/HDiffPatch" ) else: if not isinstance(data_dir, Path): data_dir = Path(data_dir) self.data_path = Path(data_dir).joinpath("Tools/HDiffPatch") self.temp_path = data_dir.joinpath("Temp/HDiffPatch") self.temp_path.mkdir(parents=True, exist_ok=True) @staticmethod def _get_platform_arch(): match platform.system(): case "Windows": match platform.architecture()[0]: case "32bit": return "windows32" case "64bit": return "windows64" case "Linux": match platform.architecture()[0]: case "32bit": return "linux32" case "64bit": return "linux64" case "Darwin": return "macos" raise RuntimeError("Unsupported platform") def _get_hdiffpatch_exec(self, exec_name): if shutil.which(exec_name): return exec_name if not self.data_path.exists(): return None if not any(self.data_path.iterdir()): return None platform_arch_path = self.data_path.joinpath(self._get_platform_arch()) file = platform_arch_path.joinpath(exec_name) if file.exists(): file.chmod(0o755) return str(file) return None def get_hpatchz_executable(self): hpatchz_name = "hpatchz" + (".exe" if platform.system() == "Windows" else "") return self._get_hdiffpatch_exec(hpatchz_name) async def patch_file(self, in_file, out_file, patch_file, error=False, wait=False): hpatchz = self.get_hpatchz_executable() if not hpatchz: raise RuntimeError("hpatchz executable not found") proc = await asyncio.create_subprocess_exec( hpatchz, "-f", in_file, patch_file, out_file ) if not wait: return proc await proc.wait() if error and proc.returncode != 0: raise RuntimeError(f"Patching failed, return code is {proc.returncode}") return proc def get_hdiffz_executable(self): hdiffz_name = "hdiffz" + (".exe" if platform.system() == "Windows" else "") return self._get_hdiffpatch_exec(hdiffz_name) async def _get_latest_release_info(self): async with aiohttp.ClientSession() as session: split = self._git_url.split("/") repo = split[-1] owner = split[-2] rsp = await session.get( "https://api.github.com/repos/{}/{}/releases/latest".format( owner, repo ), params={"Headers": "Accept: application/vnd.github.v3+json"}, ) rsp.raise_for_status() for asset in (await rsp.json())["assets"]: if ( asset["name"].endswith(".zip") and "linux" not in asset["name"] and "windows" not in asset["name"] and "macos" not in asset["name"] and "android" not in asset["name"] ): return asset async def get_latest_release_url(self): asset = await self._get_latest_release_info() return asset["browser_download_url"] async def get_latest_release_name(self): asset = await self._get_latest_release_info() return asset["name"] async def download_latest_release(self, extract=True): url = await self.get_latest_release_url() name = await self.get_latest_release_name() if not url: raise RuntimeError("Unable to find latest release") await helper.download_file( url, name, self.temp_path, overwrite=True, threads_num=1 ) if not extract: return with zipfile.ZipFile(self.temp_path.joinpath(name), "r") as f: await asyncio.to_thread(f.extractall, path=self.data_path)