Various changes, block telemetry feature.

-Sp/--patch is now required to do block telemetry before patching.
Still preparing for hdiffpatch (will be coming at 1.10)
Ay yo hosty support coming soon xD
This commit is contained in:
tretrauit 2022-02-27 01:54:20 +07:00
parent dbf6cf6d21
commit aaf728445d
No known key found for this signature in database
GPG Key ID: 862760FF1903319E
6 changed files with 213 additions and 32 deletions

View File

@ -9,7 +9,7 @@ README = (HERE / "README.md").read_text()
setup(
name='worthless',
version='1.2.8-1',
version='1.2.9',
packages=['worthless', 'worthless.classes', 'worthless.classes.launcher', 'worthless.classes.installer'],
url='https://git.froggi.es/tretrauit/worthless-launcher',
license='MIT License',

View File

@ -2,11 +2,22 @@ APP_NAME="worthless"
APP_AUTHOR="tretrauit"
LAUNCHER_API_URL_OS = "https://sdk-os-static.hoyoverse.com/hk4e_global/mdk/launcher/api"
LAUNCHER_API_URL_CN = "https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api"
HDIFFPATCH_GIT_URL="https://github.com/sisong/HDiffPatch"
PATCH_GIT_URL = "https://notabug.org/Krock/dawn"
TELEMETRY_URL_LIST = [
"log-upload-os.mihoyo.com",
"log-upload-os.hoyoverse.com",
"log-upload.mihoyo.com",
"overseauspider.yuanshen.com"
"uspider.yuanshen.com"
"log-upload-os.mihoyo.com",
"log-upload-eur.mihoyo.com",
"log-upload-os.hoyoverse.com",
"overseauspider.yuanshen.com"
]
TELEMETRY_URL_CN_LIST = [
"log-upload.mihoyo.com",
"uspider.yuanshen.com"
]
TELEMETRY_OPTIONAL_URL_LIST = [
"prd-lender.cdp.internal.unity3d.com",
"thind-prd-knob.data.ie.unity3d.com",
"thind-gke-usc.prd.data.corp.unity3d.com",
"cdp.cloud.unity3d.com",
"remote-config-proxy-prd.uca.cloud.unity3d.com"
]

View File

@ -37,6 +37,24 @@ class UI:
def get_game_version(self):
print(self._installer.get_game_version())
def block_telemetry(self):
print("Checking for available telemetry to block...")
try:
asyncio.run(self._patcher.block_telemetry())
except ValueError:
print("No telemetry to block.")
else:
print("Telemetry blocked.")
def check_telemetry(self):
block_status = asyncio.run(self._patcher.is_telemetry_blocked())
if not block_status:
print("Telemetry is blocked.")
else:
print("Telemetry is not blocked, you need to block these hosts below.")
for block in block_status:
print(block)
def _update_from_archive(self, filepath):
print("Reverting patches if patched...")
self._patcher.revert_patch(True)
@ -72,11 +90,13 @@ class UI:
if not self._ask("Do you want to patch the game? (This will overwrite your game files!)"):
print("Aborting patch process.")
return
print("Patching game...")
self.block_telemetry()
print("Updating patches...")
asyncio.run(self._patcher.download_patch())
print("Patching game...")
self._patcher.apply_patch(login_fix)
print("Game patched.")
print("Please refrain from sharing this project to public especially official channels, thank you.")
print("Please refrain from sharing this project to public, thank you.")
def install_from_file(self, filepath):
gamever = self._installer.get_game_version()
@ -96,6 +116,10 @@ class UI:
self._install_from_archive(filepath, False)
print("Game installed successfully.")
def download_patch(self):
print("Downloading patches...")
asyncio.run(self._patcher.download_patch())
def download_game(self):
print("Downloading full game (This will take a long time)...")
asyncio.run(self._installer.download_full_game())
@ -235,6 +259,7 @@ def main():
parser.add_argument("-Rv", "--remove-voiceover", action="store_true", help="Remove a Voiceover pack (if installed)")
parser.add_argument("--get-game-version", action="store_true", help="Get the current game version")
parser.add_argument("--no-overseas", action="store_true", help="Don't use overseas server")
parser.add_argument("--check-telemetry", action="store_true", help="Check for the telemetry information")
parser.add_argument("--from-ver", action="store", help="Override the detected game version", type=str, default=None)
parser.add_argument("--noconfirm", action="store_true",
help="Do not ask any for confirmation. (Ignored in interactive mode)")
@ -243,7 +268,8 @@ def main():
args.remove and not args.remove_patch and not args.remove_voiceover and not args.get_game_version and not \
args.install_voiceover_from_file and not args.update_voiceover and not args.download_game and not \
args.download_voiceover and not args.download_game_update and not args.download_voiceover_update and not \
args.install_voiceover_from_file and not args.update_all and not args.login_fix
args.install_voiceover_from_file and not args.update_all and not args.login_fix and not args.check_telemetry\
and not args.from_ver
if args.temporary_dir:
args.temporary_dir.mkdir(parents=True, exist_ok=True)
@ -261,9 +287,15 @@ def main():
if args.install_from_file and args.install:
raise ValueError("Cannot specify both --install-from-file and --install arguments.")
if args.from_ver:
ui.override_game_version(args.from_ver)
if args.get_game_version:
ui.get_game_version()
if args.check_telemetry:
ui.check_telemetry()
if args.download_game:
ui.download_game()

View File

@ -1,6 +1,6 @@
import re
import shutil
import platform
import aiohttp
import appdirs
import zipfile
@ -13,6 +13,86 @@ from worthless import constants
from worthless.launcher import Launcher
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:
:param file_name:
:return:
"""
params = {}
file_path = AsyncPath(file_path).joinpath(file_name)
if overwrite:
await file_path.unlink(missing_ok=True)
if await file_path.exists():
cur_len = len(await file_path.read_bytes())
params |= {
"Range": f"bytes={cur_len}-{file_len if file_len else ''}"
}
else:
await file_path.touch()
async with aiohttp.ClientSession() as session:
rsp = await session.get(file_url, params=params, timeout=None)
rsp.raise_for_status()
while True:
chunk = await rsp.content.read(chunks)
if not chunk:
break
async with file_path.open("ab") as f:
await f.write(chunk)
class HDiffPatch:
def __init__(self, git_url=None, data_dir=None):
if not git_url:
repo_url = constants.HDIFFPATCH_GIT_URL
self._git_url = git_url
if not data_dir:
self._appdirs = appdirs.AppDirs(constants.APP_NAME, constants.APP_AUTHOR)
self.temp_path = Path(self._appdirs.user_cache_dir).joinpath("Tools/HDiffPatch")
else:
if not isinstance(data_dir, Path):
data_dir = Path(data_dir)
self.temp_path = data_dir.joinpath("Temp/Tools/HDiffPatch")
self.temp_path.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"
raise RuntimeError("Unsupported platform")
def _get_hdiffpatch_exec(self, exec_name):
if shutil.which(exec_name):
return exec_name
if not any(self.temp_path.iterdir()):
return None
platform_arch_path = self.temp_path.joinpath(self._get_platform_arch())
if platform_arch_path.joinpath(exec_name).exists():
return str(platform_arch_path.joinpath(exec_name))
return None
def get_hpatchz_executable(self):
return self._get_hdiffpatch_exec("hpatchz")
def get_hdiffz_executable(self):
return self._get_hdiffpatch_exec("hdiffz")
class Installer:
def _read_version_from_config(self):
warnings.warn("This function is not reliable as upgrading game version from worthless\
@ -88,6 +168,7 @@ class Installer:
self._version = None
self._overseas = overseas
self._launcher = Launcher(self._gamedir, overseas=self._overseas)
self._hdiffpatch = HDiffPatch(data_dir=data_dir)
self._version = self.get_game_version()
def set_download_chunk(self, chunk: int):
@ -100,26 +181,7 @@ class Installer:
:param file_name:
:return:
"""
params = {}
file_path = AsyncPath(self.temp_path).joinpath(file_name)
if overwrite:
await file_path.unlink(missing_ok=True)
if await file_path.exists():
cur_len = len(await file_path.read_bytes())
params |= {
"Range": f"bytes={cur_len}-{file_len if file_len else ''}"
}
else:
await file_path.touch()
async with aiohttp.ClientSession() as session:
rsp = await session.get(file_url, params=params, timeout=None)
rsp.raise_for_status()
while True:
chunk = await rsp.content.read(self._download_chunk)
if not chunk:
break
async with file_path.open("ab") as f:
await f.write(chunk)
await _download_file(file_url, file_name, self.temp_path, file_len=file_len, overwrite=overwrite)
def get_game_archive_version(self, game_archive: str | Path):
if not game_archive.exists():

35
worthless/linux.py Normal file
View File

@ -0,0 +1,35 @@
import asyncio
from pathlib import Path
class LinuxUtils:
"""Utilities for Linux-specific tasks.
"""
def __init__(self):
pass
async def _exec_command(self, *args):
"""Execute a command using pkexec (friendly gui)
"""
rsp = await asyncio.create_subprocess_exec(*args)
match rsp.returncode:
case 127:
raise OSError("Authentication failed.")
case 128:
raise RuntimeError("User cancelled the authentication.")
return rsp
async def write_text_to_file(self, text, file_path: str | Path):
"""Write text to a file using pkexec (friendly gui)
"""
if isinstance(file_path, Path):
file_path = str(file_path)
await self._exec_command('pkexec', 'echo', text, '>', file_path)
async def append_text_to_file(self, text, file_path: str | Path):
"""Append text to a file using pkexec (friendly gui)
"""
if isinstance(file_path, Path):
file_path = str(file_path)
await self._exec_command('pkexec', 'echo', text, '>>', file_path)

View File

@ -6,6 +6,7 @@ from pathlib import Path
import shutil
import aiohttp
import asyncio
from worthless import linux
from worthless import constants
from worthless.launcher import Launcher
from worthless.installer import Installer
@ -31,8 +32,11 @@ class Patcher:
self._patch_path = data_dir.joinpath("Patch")
self._temp_path = data_dir.joinpath("Temp/Patcher")
self._overseas = overseas
self._installer = Installer(self._gamedir, overseas=overseas, data_dir=self._temp_path)
self._installer = Installer(self._gamedir, overseas=overseas, data_dir=data_dir)
self._launcher = Launcher(self._gamedir, overseas=overseas)
match platform.system():
case "Linux":
self._linuxutils = linux.LinuxUtils()
@staticmethod
async def _get(url, **kwargs) -> aiohttp.ClientResponse:
@ -100,6 +104,40 @@ class Patcher:
"""
await self._download_repo()
async def is_telemetry_blocked(self):
"""
Check if the telemetry is blocked.
"""
if self._overseas:
telemetry_url = constants.TELEMETRY_URL_LIST
else:
telemetry_url = constants.TELEMETRY_URL_CN_LIST
unblocked_list = []
async with aiohttp.ClientSession() as session:
for url in telemetry_url:
try:
await session.get("https://" + url)
except (aiohttp.ClientResponseError, aiohttp.ClientConnectorError):
continue
else:
unblocked_list.append(url)
return None if unblocked_list == [] else unblocked_list
async def block_telemetry(self):
telemetry = await self.is_telemetry_blocked()
if not telemetry:
raise ValueError("All telemetry are blocked")
telemetry_hosts = ""
for url in telemetry:
telemetry_hosts += "0.0.0.0 " + url + "\n"
match platform.system():
case "Linux":
await self._linuxutils.append_text_to_file(telemetry_hosts)
return
# TODO: Windows and macOS
raise NotImplementedError("Platform not implemented.")
async def _patch_unityplayer_fallback(self):
# xdelta3-python doesn't work becuase it's outdated.
if self._overseas:
@ -115,7 +153,10 @@ class Patcher:
async def _patch_xlua_fallback(self):
# xdelta3-python doesn't work becuase it's outdated.
patch = "xlua_patch.vcdiff"
if self._overseas:
patch = "unityplayer_patch_os.vcdiff"
else:
patch = "unityplayer_patch_cn.vcdiff"
gamever = "".join(self._installer.get_game_version().split("."))
data_name = self._installer.get_game_data_name()
xlua_path = self._gamedir.joinpath("{}/Plugins/xlua.dll".format(data_name))