|
|
|
|
@ -28,10 +28,11 @@ async def _download_file(file_url: str, file_name: str, file_path: Path | str, f
|
|
|
|
|
if overwrite:
|
|
|
|
|
file_path.unlink(missing_ok=True)
|
|
|
|
|
if file_path.exists():
|
|
|
|
|
cur_len = (file_path.stat()).st_size
|
|
|
|
|
headers |= {
|
|
|
|
|
with file_path.open("rb") as f:
|
|
|
|
|
cur_len = f.tell()
|
|
|
|
|
headers |= {
|
|
|
|
|
"Range": f"bytes={cur_len}-{file_len if file_len else ''}"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else:
|
|
|
|
|
file_path.touch()
|
|
|
|
|
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:
|
|
|
|
|
return
|
|
|
|
|
rsp.raise_for_status()
|
|
|
|
|
while True:
|
|
|
|
|
chunk = await rsp.content.read(chunks)
|
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
|
if not chunk:
|
|
|
|
|
break
|
|
|
|
|
with file_path.open("ab") as f:
|
|
|
|
|
f.write(chunk)
|
|
|
|
|
with file_path.open("ab") as f:
|
|
|
|
|
async for data in rsp.content.iter_chunked(chunks):
|
|
|
|
|
f.write(data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def calculate_md5(file_to_calculate):
|
|
|
|
|
@ -457,25 +454,26 @@ class Installer:
|
|
|
|
|
|
|
|
|
|
async def download_full_game(self, pre_download=False):
|
|
|
|
|
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
|
|
|
|
|
base_archive = None
|
|
|
|
|
for i, segment in enumerate(game.latest.segments):
|
|
|
|
|
# base_archive = None
|
|
|
|
|
for _, segment in enumerate(game.latest.segments):
|
|
|
|
|
archive_name = segment["path"].split("/")[-1]
|
|
|
|
|
if i == 0:
|
|
|
|
|
base_archive = archive_name = Path(archive_name).stem # Remove .001
|
|
|
|
|
if self.temp_path.joinpath(archive_name + ".downloaded").exists():
|
|
|
|
|
if self.temp_path.joinpath(archive_name + ".downloaded").exists() and self.temp_path.joinpath(archive_name).exists():
|
|
|
|
|
print(f"calculating md5 for downloaded game archive: {archive_name}")
|
|
|
|
|
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
|
|
|
|
|
await self._download_file(segment["path"], archive_name)
|
|
|
|
|
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:
|
|
|
|
|
f.write(f2.read())
|
|
|
|
|
self.temp_path.joinpath(archive_name).unlink()
|
|
|
|
|
print(f"calculating md5 for downloaded game archive: {archive_name}")
|
|
|
|
|
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...")
|
|
|
|
|
# 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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -489,13 +487,21 @@ class Installer:
|
|
|
|
|
async def uninstall_game(self):
|
|
|
|
|
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):
|
|
|
|
|
archive = Path(archive).resolve()
|
|
|
|
|
if not archive.exists():
|
|
|
|
|
raise FileNotFoundError(f"'{archive}' not found")
|
|
|
|
|
with zipfile.ZipFile(archive, 'r') as f:
|
|
|
|
|
await asyncio.to_thread(f.extractall, path=self._gamedir)
|
|
|
|
|
game_dir_str = str(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):
|
|
|
|
|
# Since Voiceover packages are unclear about diff package or full package
|
|
|
|
|
@ -513,7 +519,9 @@ class Installer:
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
raise ValueError(f"Game is already installed in {self._gamedir}")
|
|
|
|
|
await self.uninstall_game()
|
|
|
|
|
|