Compare commits

...

6 Commits

Author SHA1 Message Date
197dc767a8 feat: hdiffpatch 2023-06-20 23:02:26 +07:00
3d44bfe63a xdelta3: downloading binary perform in-memory 2023-06-20 19:14:25 +07:00
45a9953eb2 fix: make constants constants
ALL CAPS
2023-06-20 18:55:03 +07:00
a3a6cfdbe3 cli: Y/n instead of y/n 2023-06-20 18:51:21 +07:00
0d52b53172 xdelta3: delete archive file after extract 2023-06-20 18:51:02 +07:00
7ca33c62b2 cli: fix jadeite name
It's jadeite
2023-06-20 18:50:17 +07:00
9 changed files with 144 additions and 16 deletions

View File

@ -38,14 +38,14 @@ class HSR:
def __patch_jadeite(self): def __patch_jadeite(self):
try: try:
print("Installing patch...", end=" ") print("Installing patch...", end=" ")
jadelte_dir = self._patcher.patch_game(game=self._game) jadeite_dir = self._patcher.patch_game(game=self._game)
except PatcherError as e: except PatcherError as e:
print("FAILED") print("FAILED")
print(f"Patching failed with following error: {e}") print(f"Patching failed with following error: {e}")
return return
print("OK") print("OK")
exe_path = jadelte_dir.joinpath("jadeite.exe") exe_path = jadeite_dir.joinpath("jadeite.exe")
print("Jadelte executable is located at:", exe_path) print("jadeite executable is located at:", exe_path)
print( print(
"Installation succeeded, but note that you need to run the game using " "Installation succeeded, but note that you need to run the game using "
+ "Jadeite to use the patch." + "Jadeite to use the patch."

View File

@ -6,8 +6,8 @@ def ask(question: str):
print(question + " [Y/n] Y") print(question + " [Y/n] Y")
return True return True
while True: while True:
answer = input(question + " [y/n] ") answer = input(question + " [Y/n] ")
if answer.lower() in ["y", "yes"]: if answer.lower().strip() in ["y", "yes", ""]:
return True return True
# Pacman way, treat all other answers as no # Pacman way, treat all other answers as no
else: else:

View File

@ -1,5 +1,4 @@
# Common TELEMETRY_HOSTS = [
telemetry_hosts = [
# Global # Global
"log-upload-os.hoyoverse.com", "log-upload-os.hoyoverse.com",
"sg-public-data-api.hoyoverse.com", "sg-public-data-api.hoyoverse.com",
@ -8,3 +7,4 @@ telemetry_hosts = [
"log-upload.mihoyo.com", "log-upload.mihoyo.com",
"public-data-api.mihoyo.com", "public-data-api.mihoyo.com",
] ]
HDIFFPATCH_GIT_URL = "https://github.com/sisong/HDiffPatch"

View File

@ -1,5 +1,5 @@
latest_version = (1, 1, 0) LATEST_VERSION = (1, 1, 0)
md5sums = { MD5SUMS = {
"1.0.5": { "1.0.5": {
"cn": { "cn": {
"StarRailBase.dll": "66c42871ce82456967d004ccb2d7cf77", "StarRailBase.dll": "66c42871ce82456967d004ccb2d7cf77",
@ -12,5 +12,5 @@ md5sums = {
} }
} }
# Patches # Patches
astra_repo = "https://notabug.org/mkrsym1/astra" ASTRA_REPO = "https://notabug.org/mkrsym1/astra"
jadeite_repo = "https://codeberg.org/mkrsym1/jadeite/" JADEITE_REPO = "https://codeberg.org/mkrsym1/jadeite/"

View File

@ -11,7 +11,7 @@ from vollerei.exceptions.patcher import (
from vollerei.hsr.launcher.game import Game, GameChannel from vollerei.hsr.launcher.game import Game, GameChannel
from vollerei.utils import download_and_extract, Git, Xdelta3 from vollerei.utils import download_and_extract, Git, Xdelta3
from vollerei.paths import tools_data_path from vollerei.paths import tools_data_path
from vollerei.hsr.constants import astra_repo, jadeite_repo from vollerei.hsr.constants import ASTRA_REPO, JADEITE_REPO
class PatchType(Enum): class PatchType(Enum):
@ -49,10 +49,10 @@ class Patcher(PatcherABC):
self._patch_type = value self._patch_type = value
def _update_astra(self): def _update_astra(self):
self._git.pull_or_clone(astra_repo, self._astra) self._git.pull_or_clone(ASTRA_REPO, self._astra)
def _update_jadeite(self): def _update_jadeite(self):
release_info = self._git.get_latest_release(jadeite_repo) release_info = self._git.get_latest_release(JADEITE_REPO)
file = self._git.get_latest_release_dl(release_info)[0] file = self._git.get_latest_release_dl(release_info)[0]
file_version = release_info["tag_name"][1:] # Remove "v" prefix file_version = release_info["tag_name"][1:] # Remove "v" prefix
current_version = None current_version = None

View File

@ -0,0 +1,104 @@
import platform
import subprocess
from zipfile import ZipFile
import requests
from io import BytesIO
from shutil import which
from vollerei.constants import HDIFFPATCH_GIT_URL
from vollerei.paths import tools_data_path
class HDiffPatch:
def __init__(self):
self._data = tools_data_path.joinpath("hdiffpatch")
self._data.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"
# Rip BSD they need to use Linux compatibility layer to run this
# (or use Wine if they prefer that)
raise RuntimeError("Only Windows, Linux and macOS are supported by HDiffPatch")
def _get_hdiffpatch_exec(self, exec_name) -> str | None:
if 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)
def _hpatchz(self):
hpatchz_name = "hpatchz" + (".exe" if platform.system() == "Windows" else "")
return self._get_hdiffpatch_exec(hpatchz_name)
def patch_file(self, in_file, out_file, patch_file):
hpatchz = self.get_hpatchz_executable()
if not hpatchz:
raise RuntimeError("hpatchz executable not found")
subprocess.check_call([hpatchz, "-f", in_file, patch_file, out_file])
async def _get_latest_release_info(self) -> dict:
split = HDIFFPATCH_GIT_URL.split("/")
repo = split[-1]
owner = split[-2]
rsp = requests.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 not asset["name"].endswith(".zip"):
continue
if "linux" in asset["name"]:
continue
if "windows" in asset["name"]:
continue
if "macos" in asset["name"]:
continue
if "android" in asset["name"]:
continue
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(self):
"""
Download the latest release of HDiffPatch.
"""
url = await self.get_latest_release_url()
if not url:
raise RuntimeError("Unable to find latest release")
file = BytesIO()
with requests.get(url, stream=True) as r:
with open(file, "wb") as f:
for chunk in r.iter_content(chunk_size=32768):
f.write(chunk)
with ZipFile(file) as z:
z.extractall(self._data)

View File

@ -0,0 +1,22 @@
class HDiffPatchError(Exception):
"""Base class for HDiffPatch errors"""
pass
class HPatchZError(HDiffPatchError):
"""Raised when hpatchz fails"""
pass
class NotInstalledError(HPatchZError):
"""Raised when HDiffPatch is not installed"""
pass
class HPatchZPatchError(HPatchZError):
"""Raised when hpatchz patch fails"""
pass

View File

@ -2,6 +2,7 @@ import platform
import subprocess import subprocess
import requests import requests
from os import PathLike from os import PathLike
from io import BytesIO
from zipfile import ZipFile from zipfile import ZipFile
from shutil import which from shutil import which
from vollerei.paths import tools_cache_path from vollerei.paths import tools_cache_path
@ -60,11 +61,12 @@ class Xdelta3:
url = "https://github.com/jmacd/xdelta-gpl/releases/download/v3.1.0/xdelta3-3.1.0-i686.exe.zip" url = "https://github.com/jmacd/xdelta-gpl/releases/download/v3.1.0/xdelta3-3.1.0-i686.exe.zip"
case "i686": case "i686":
url = "https://github.com/jmacd/xdelta-gpl/releases/download/v3.1.0/xdelta3-3.1.0-i686.exe.zip" url = "https://github.com/jmacd/xdelta-gpl/releases/download/v3.1.0/xdelta3-3.1.0-i686.exe.zip"
file = BytesIO()
with requests.get(url, stream=True) as r: with requests.get(url, stream=True) as r:
with open(self._xdelta3_path.joinpath("xdelta3.zip"), "wb") as f: with open(file, "wb") as f:
for chunk in r.iter_content(chunk_size=32768): for chunk in r.iter_content(chunk_size=32768):
f.write(chunk) f.write(chunk)
with ZipFile(self._xdelta3_path.joinpath("xdelta3.zip")) as z: with ZipFile(file) as z:
z.extractall(self._xdelta3_path) z.extractall(self._xdelta3_path)
def patch_file(self, patch: PathLike, target: PathLike, output: PathLike): def patch_file(self, patch: PathLike, target: PathLike, output: PathLike):