133 lines
4.7 KiB
Python
133 lines
4.7 KiB
Python
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 Game when needed
|
|
"""
|
|
|
|
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)
|