Add support for hdiffpatch files
Game now apply update properly (hopefully) Signed-off-by: tretrauit <tretrauit@gmail.com>
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import re
|
||||
import shutil
|
||||
import platform
|
||||
@ -11,9 +12,11 @@ from configparser import ConfigParser
|
||||
from aiopath import AsyncPath
|
||||
from worthless import constants
|
||||
from worthless.launcher import Launcher
|
||||
from worthless.launcherconfig import LauncherConfig
|
||||
|
||||
|
||||
async def _download_file(file_url: str, file_name: str, file_path: Path | str, file_len: int = None, overwrite=False, chunks=8192):
|
||||
async def _download_file(file_url: str, file_name: str, file_path: Path | str, file_len: int = None, overwrite=False,
|
||||
chunks=8192):
|
||||
"""
|
||||
Download file name to temporary directory,
|
||||
:param file_url:
|
||||
@ -45,7 +48,7 @@ async def _download_file(file_url: str, file_name: str, file_path: Path | str, f
|
||||
class HDiffPatch:
|
||||
def __init__(self, git_url=None, data_dir=None):
|
||||
if not git_url:
|
||||
repo_url = constants.HDIFFPATCH_GIT_URL
|
||||
git_url = constants.HDIFFPATCH_GIT_URL
|
||||
self._git_url = git_url
|
||||
if not data_dir:
|
||||
self._appdirs = appdirs.AppDirs(constants.APP_NAME, constants.APP_AUTHOR)
|
||||
@ -82,6 +85,8 @@ class HDiffPatch:
|
||||
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())
|
||||
@ -92,6 +97,12 @@ class HDiffPatch:
|
||||
def get_hpatchz_executable(self):
|
||||
return self._get_hdiffpatch_exec("hpatchz")
|
||||
|
||||
async def patch_file(self, in_file, out_file, patch_file):
|
||||
hpatchz = self.get_hpatchz_executable()
|
||||
if not hpatchz:
|
||||
raise RuntimeError("hpatchz executable not found")
|
||||
return await asyncio.create_subprocess_exec(hpatchz, "-f", in_file, patch_file, out_file)
|
||||
|
||||
def get_hdiffz_executable(self):
|
||||
return self._get_hdiffpatch_exec("hdiffz")
|
||||
|
||||
@ -103,9 +114,9 @@ class HDiffPatch:
|
||||
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 not "linux" in asset["name"] and not "windows" in asset["name"] \
|
||||
and not "macos" in asset["name"] and not "android" in asset["name"]:
|
||||
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):
|
||||
@ -201,11 +212,11 @@ class Installer:
|
||||
config_file = self._gamedir.joinpath("config.ini")
|
||||
self._config_file = config_file.resolve()
|
||||
self._download_chunk = 8192
|
||||
self._version = None
|
||||
self._overseas = overseas
|
||||
self._version = self.get_game_version()
|
||||
self._launcher = Launcher(self._gamedir, overseas=self._overseas)
|
||||
self._hdiffpatch = HDiffPatch(data_dir=data_dir)
|
||||
self._version = self.get_game_version()
|
||||
self._config = LauncherConfig(self._config_file, self._version)
|
||||
|
||||
def set_download_chunk(self, chunk: int):
|
||||
self._download_chunk = chunk
|
||||
@ -280,7 +291,7 @@ class Installer:
|
||||
archive.extractall(self._gamedir)
|
||||
archive.close()
|
||||
|
||||
def update_game(self, game_archive: str | Path):
|
||||
async def update_game(self, game_archive: str | Path):
|
||||
if not self.get_game_data_path().exists():
|
||||
raise FileNotFoundError(f"Game not found in {self._gamedir}")
|
||||
if isinstance(game_archive, str):
|
||||
@ -288,6 +299,10 @@ class Installer:
|
||||
if not game_archive.exists():
|
||||
raise FileNotFoundError(f"Update archive {game_archive} not found")
|
||||
archive = zipfile.ZipFile(game_archive, 'r')
|
||||
|
||||
if not self._hdiffpatch.get_hpatchz_executable():
|
||||
await self._hdiffpatch.download_latest_release()
|
||||
|
||||
files = archive.namelist()
|
||||
# Don't extract these files (they're useless and if the game isn't patched then it'll
|
||||
# raise 31-4xxx error ingame)
|
||||
@ -297,6 +312,35 @@ class Installer:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# hdiffpatch implementation
|
||||
hdifffiles = []
|
||||
for x in archive.read("hdifffiles.txt").decode().split("\n"):
|
||||
if x:
|
||||
hdifffiles.append(json.loads(x)["remoteName"])
|
||||
patch_jobs = []
|
||||
for file in hdifffiles:
|
||||
current_game_file = self._gamedir.joinpath(file)
|
||||
if not current_game_file.exists():
|
||||
# Not patching since we don't have the file
|
||||
continue
|
||||
|
||||
patch_file = str(file) + ".hdiff"
|
||||
|
||||
async def extract_and_patch(old_file, diff_file):
|
||||
await asyncio.to_thread(archive.extract, diff_file, self.temp_path)
|
||||
patch_path = self.temp_path.joinpath(diff_file)
|
||||
old_suffix = old_file.suffix
|
||||
old_file = old_file.rename(old_file.with_suffix(".bak"))
|
||||
proc = await self._hdiffpatch.patch_file(old_file, old_file.with_suffix(old_suffix),
|
||||
patch_path)
|
||||
await proc.wait()
|
||||
patch_path.unlink()
|
||||
old_file.unlink()
|
||||
|
||||
files.remove(patch_file)
|
||||
patch_jobs.append(extract_and_patch(current_game_file, patch_file))
|
||||
await asyncio.gather(*patch_jobs)
|
||||
|
||||
deletefiles = archive.read("deletefiles.txt").decode().split("\n")
|
||||
for file in deletefiles:
|
||||
current_game_file = self._gamedir.joinpath(file)
|
||||
@ -310,6 +354,12 @@ class Installer:
|
||||
# Update game version on local variable.
|
||||
self._version = self.get_game_version()
|
||||
|
||||
def set_version_config(self, version: str = None):
|
||||
if not version:
|
||||
version = self._version
|
||||
self._config.set_game_version(version)
|
||||
self._config.save()
|
||||
|
||||
async def download_full_game(self, overwrite: bool = False):
|
||||
if self._version and not overwrite:
|
||||
raise ValueError("Game already exists")
|
||||
|
||||
Reference in New Issue
Block a user