Compare commits
8 Commits
f1eb1fe2c6
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| e3c256200a | |||
| 19ce16f095 | |||
| 2477ed1a06 | |||
| f0c51530c4 | |||
| df00e6b679 | |||
| 07f6ce6317 | |||
| fba2063bce | |||
| 78df1d242a |
4
setup.py
4
setup.py
@ -10,9 +10,9 @@ README = (HERE / "README.md").read_text()
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='worthless',
|
name='worthless',
|
||||||
version='2.2.20',
|
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',
|
||||||
|
|||||||
@ -4,4 +4,4 @@ Launcher = launcher.Launcher
|
|||||||
Installer = installer.Installer
|
Installer = installer.Installer
|
||||||
|
|
||||||
|
|
||||||
__version__ = "2.2.20"
|
__version__ = "2.2.22"
|
||||||
|
|||||||
3
worthless/cli.py
Executable file → Normal file
3
worthless/cli.py
Executable file → Normal 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()
|
||||||
|
|||||||
@ -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()
|
||||||
@ -397,7 +406,10 @@ class Installer:
|
|||||||
print("Failed to patch {}, reverting and let the in-game updater do the job...".format(
|
print("Failed to patch {}, reverting and let the in-game updater do the job...".format(
|
||||||
old_file.with_suffix(old_suffix))
|
old_file.with_suffix(old_suffix))
|
||||||
)
|
)
|
||||||
old_file.rename(old_file.with_suffix(old_suffix))
|
try:
|
||||||
|
old_file.rename(old_file.with_suffix(old_suffix))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
files.remove(patch_file)
|
files.remove(patch_file)
|
||||||
# Limit to 8 process running so it doesn't hang the PC.
|
# Limit to 8 process running so it doesn't hang the PC.
|
||||||
@ -442,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()
|
||||||
|
|
||||||
|
|
||||||
@ -474,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
|
||||||
@ -498,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()
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user