Compare commits

...

6 Commits

Author SHA1 Message Date
e3c256200a fix: use f.tell to get file size
yeah, it fixes continuous downloading
2024-06-07 23:59:47 +07:00
19ce16f095 chore: improve the download code 2024-06-07 18:59:55 +07:00
2477ed1a06 fix: don't merge zip files & use 7zip again
We return to cd2aabea60
2024-06-07 18:46:49 +07:00
f0c51530c4 fix: don't use AsyncPath.cwd()
async await
2024-06-05 09:49:00 +07:00
df00e6b679 chore: update url 2024-04-25 21:46:10 +07:00
07f6ce6317 fix: hdiffpatch download 2024-04-25 21:46:03 +07:00
5 changed files with 65 additions and 44 deletions

View File

@ -10,9 +10,9 @@ README = (HERE / "README.md").read_text()
setup( setup(
name='worthless', name='worthless',
version='2.2.21', version='2.2.22',
packages=['worthless', 'worthless.classes', 'worthless.classes.launcher', 'worthless.classes.installer'], packages=['worthless', 'worthless.classes', 'worthless.classes.launcher', 'worthless.classes.installer'],
url='https://git.froggi.es/tretrauit/worthless-launcher', url='https://git.tretrauit.me/tretrauit/worthless-launcher',
license='MIT License', license='MIT License',
author='tretrauit', author='tretrauit',
author_email='tretrauit@gmail.org', author_email='tretrauit@gmail.org',

View File

@ -4,4 +4,4 @@ Launcher = launcher.Launcher
Installer = installer.Installer Installer = installer.Installer
__version__ = "2.2.20" __version__ = "2.2.22"

View File

@ -169,7 +169,8 @@ class UI:
return return
await self.download_game() await self.download_game()
print("Game archive:", game.latest.get_name()) print("Game archive:", game.latest.get_name())
await self._install_from_archive(self._installer.temp_path.joinpath(game.latest.get_name()), forced) # I'm too lazy to properly fix things so here we are :D
await self._install_from_archive(self._installer.temp_path.joinpath(game.latest.segments[0]["path"].split("/")[-1]), forced)
async def install_voiceover(self, languages: str): async def install_voiceover(self, languages: str):
res_info = await self._launcher.get_resource_info() res_info = await self._launcher.get_resource_info()

View File

@ -26,12 +26,13 @@ async def _download_file(file_url: str, file_name: str, file_path: Path | str, f
headers = {} headers = {}
file_path = Path(file_path).joinpath(file_name) file_path = Path(file_path).joinpath(file_name)
if overwrite: if overwrite:
await file_path.unlink(missing_ok=True) file_path.unlink(missing_ok=True)
if file_path.exists(): if file_path.exists():
cur_len = (file_path.stat()).st_size with file_path.open("rb") as f:
headers |= { cur_len = f.tell()
headers |= {
"Range": f"bytes={cur_len}-{file_len if file_len else ''}" "Range": f"bytes={cur_len}-{file_len if file_len else ''}"
} }
else: else:
file_path.touch() file_path.touch()
print(f"Downloading {file_url} to {file_path}...") print(f"Downloading {file_url} to {file_path}...")
@ -40,13 +41,9 @@ async def _download_file(file_url: str, file_name: str, file_path: Path | str, f
if rsp.status == 416: if rsp.status == 416:
return return
rsp.raise_for_status() rsp.raise_for_status()
while True: with file_path.open("ab") as f:
chunk = await rsp.content.read(chunks) async for data in rsp.content.iter_chunked(chunks):
await asyncio.sleep(0) f.write(data)
if not chunk:
break
with file_path.open("ab") as f:
f.write(chunk)
def calculate_md5(file_to_calculate): def calculate_md5(file_to_calculate):
@ -78,19 +75,28 @@ class HDiffPatch:
@staticmethod @staticmethod
def _get_platform_arch(): def _get_platform_arch():
processor = platform.machine()
match platform.system(): match platform.system():
case "Windows": case "Windows":
match platform.architecture()[0]: match processor:
case "32bit": case "i386":
return "windows32" return "windows32"
case "64bit": case "x86_64":
return "windows64" return "windows64"
case "arm":
return "windows_arm32"
case "arm64":
return "windows_arm64"
case "Linux": case "Linux":
match platform.architecture()[0]: match processor:
case "32bit": case "i386":
return "linux32" return "linux32"
case "64bit": case "x86_64":
return "linux64" return "linux64"
case "arm":
return "linux_arm32"
case "arm64":
return "linux_arm64"
case "Darwin": case "Darwin":
return "macos" return "macos"
@ -140,10 +146,13 @@ class HDiffPatch:
rsp = await session.get("https://api.github.com/repos/{}/{}/releases/latest".format(owner, repo), rsp = await session.get("https://api.github.com/repos/{}/{}/releases/latest".format(owner, repo),
params={"Headers": "Accept: application/vnd.github.v3+json"}) params={"Headers": "Accept: application/vnd.github.v3+json"})
rsp.raise_for_status() rsp.raise_for_status()
archive_processor = self._get_platform_arch()
for asset in (await rsp.json())["assets"]: for asset in (await rsp.json())["assets"]:
if asset["name"].endswith(".zip") and "linux" not in asset["name"] and "windows" not in asset["name"] \ if not asset["name"].endswith(".zip"):
and "macos" not in asset["name"] and "android" not in asset["name"]: continue
return asset if archive_processor not in asset["name"]:
continue
return asset
async def get_latest_release_url(self): async def get_latest_release_url(self):
asset = await self._get_latest_release_info() asset = await self._get_latest_release_info()
@ -445,25 +454,26 @@ class Installer:
async def download_full_game(self, pre_download=False): async def download_full_game(self, pre_download=False):
game = await self._get_game(pre_download) game = await self._get_game(pre_download)
if not game.latest.path == "":
archive_name = game.latest.path.split("/")[-1]
if calculate_md5(self.temp_path.joinpath(archive_name)) != game.latest.md5:
raise RuntimeError("mismatch md5 for downloaded game archive")
return
# Segment download # Segment download
base_archive = None # base_archive = None
for i, segment in enumerate(game.latest.segments): for _, segment in enumerate(game.latest.segments):
archive_name = segment["path"].split("/")[-1] archive_name = segment["path"].split("/")[-1]
if i == 0: if self.temp_path.joinpath(archive_name + ".downloaded").exists() and self.temp_path.joinpath(archive_name).exists():
base_archive = archive_name = Path(archive_name).stem # Remove .001 print(f"calculating md5 for downloaded game archive: {archive_name}")
if self.temp_path.joinpath(archive_name + ".downloaded").exists(): md5 = calculate_md5(self.temp_path.joinpath(archive_name))
if md5 != segment["md5"]:
print(f"mismatch md5 for downloaded game archive: {archive_name} ({md5}; expected {segment['md5']}), continuing anyway...")
continue continue
await self._download_file(segment["path"], archive_name) await self._download_file(segment["path"], archive_name)
if i != 0: print(f"calculating md5 for downloaded game archive: {archive_name}")
with open(self.temp_path.joinpath(base_archive), 'ab') as f: md5 = calculate_md5(self.temp_path.joinpath(archive_name))
with open(self.temp_path.joinpath(archive_name), 'rb') as f2: if md5 != segment["md5"]:
f.write(f2.read()) print(f"mismatch md5 for downloaded game archive: {archive_name} ({md5}; expected {segment['md5']}), continuing anyway...")
self.temp_path.joinpath(archive_name).unlink() # if i != 0:
# with open(self.temp_path.joinpath(base_archive), 'ab') as f:
# with open(self.temp_path.joinpath(archive_name), 'rb') as f2:
# shutil.copyfileobj(f2, f)
# self.temp_path.joinpath(archive_name).unlink()
self.temp_path.joinpath(archive_name + ".downloaded").touch() self.temp_path.joinpath(archive_name + ".downloaded").touch()
@ -477,13 +487,21 @@ class Installer:
async def uninstall_game(self): async def uninstall_game(self):
await asyncio.to_thread(shutil.rmtree, self._gamedir, ignore_errors=True) await asyncio.to_thread(shutil.rmtree, self._gamedir, ignore_errors=True)
async def _extract_game_file(self, archive: str | Path | Path): async def _extract_game_file(self, archive: str | Path):
if isinstance(archive, str | Path): if isinstance(archive, str | Path):
archive = Path(archive).resolve() archive = Path(archive).resolve()
if not archive.exists(): if not archive.exists():
raise FileNotFoundError(f"'{archive}' not found") raise FileNotFoundError(f"'{archive}' not found")
with zipfile.ZipFile(archive, 'r') as f: game_dir_str = str(self._gamedir)
await asyncio.to_thread(f.extractall, path=self._gamedir) if not game_dir_str.endswith("/"):
game_dir_str += "/"
print(f'-o"{game_dir_str}"')
proc = await asyncio.create_subprocess_shell(f'7z x {str(archive)} -o"{game_dir_str}"')
await proc.wait()
if proc.returncode != 0:
raise RuntimeError("Extracting failed")
# with zipfile.ZipFile(archive, 'r') as f:
# await asyncio.to_thread(f.extractall, path=self._gamedir)
async def apply_voiceover(self, voiceover_archive: str | Path): async def apply_voiceover(self, voiceover_archive: str | Path):
# Since Voiceover packages are unclear about diff package or full package # Since Voiceover packages are unclear about diff package or full package
@ -501,7 +519,9 @@ class Installer:
If `force_reinstall` is True, the game will be uninstalled then reinstalled. If `force_reinstall` is True, the game will be uninstalled then reinstalled.
""" """
if self.get_game_data_path().exists(): if self.get_game_data_path().exists() \
and all([self._gamedir.joinpath(x).exists() for x in ["UnityPlayer.dll", "pkg_version"]]) \
and any([self._gamedir.joinpath(x).exists() for x in ["GenshinImpact.exe", "YuanShen.exe"]]):
if not force_reinstall: if not force_reinstall:
raise ValueError(f"Game is already installed in {self._gamedir}") raise ValueError(f"Game is already installed in {self._gamedir}")
await self.uninstall_game() await self.uninstall_game()

View File

@ -28,7 +28,7 @@ except ImportError:
class Patcher: class Patcher:
def __init__(self, gamedir: Path | AsyncPath | str = AsyncPath.cwd(), data_dir: str | Path | AsyncPath = None, def __init__(self, gamedir: Path | AsyncPath | str = Path.cwd(), data_dir: str | Path | AsyncPath = None,
patch_url: str = None, overseas=True, patch_provider="Krock"): patch_url: str = None, overseas=True, patch_provider="Krock"):
if isinstance(gamedir, str | Path): if isinstance(gamedir, str | Path):
gamedir = AsyncPath(gamedir) gamedir = AsyncPath(gamedir)