Compare commits
6 Commits
abbf94b21a
...
b85cc5db4b
Author | SHA1 | Date | |
---|---|---|---|
b85cc5db4b | |||
80e7c2640e | |||
4be8ffecec | |||
4b01c90bf0 | |||
59da35f1df | |||
f6c9f2ddfe |
41
poetry.lock
generated
41
poetry.lock
generated
@ -143,6 +143,20 @@ files = [
|
|||||||
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
||||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
|
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fire"
|
||||||
|
version = "0.5.0"
|
||||||
|
description = "A library for automatically generating command line interfaces."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "fire-0.5.0.tar.gz", hash = "sha256:a6b0d49e98c8963910021f92bba66f65ab440da2982b78eb1bbf95a0a34aacc6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = "*"
|
||||||
|
termcolor = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "identify"
|
name = "identify"
|
||||||
version = "2.5.24"
|
version = "2.5.24"
|
||||||
@ -358,6 +372,31 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-g
|
|||||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.16.0"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
files = [
|
||||||
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "2.3.0"
|
||||||
|
description = "ANSI color formatting for output in terminal"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"},
|
||||||
|
{file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["pytest", "pytest-cov"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.0.3"
|
version = "2.0.3"
|
||||||
@ -398,4 +437,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "7d2bf3678e910680af7a343064533ccd713d5583786eef1d7d8fa2d930da6128"
|
content-hash = "a96832cd8f35bc83d3041fd4ec46972877becf824e5a307207a48459260b5289"
|
||||||
|
@ -15,6 +15,10 @@ requests = "^2.31.0"
|
|||||||
pytest = "^7.3.1"
|
pytest = "^7.3.1"
|
||||||
pre-commit = "^3.3.3"
|
pre-commit = "^3.3.3"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.group.cli.dependencies]
|
||||||
|
fire = "^0.5.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
__version__ = "0.1.0"
|
5
vollerei/__main__.py
Normal file
5
vollerei/__main__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from vollerei.cli import CLI
|
||||||
|
from fire import Fire
|
||||||
|
|
||||||
|
|
||||||
|
Fire(CLI, name="vollerei")
|
@ -34,3 +34,11 @@ class PatcherABC(ABC):
|
|||||||
unpatching fails then it'll raise `UnpatchingFailedError`.
|
unpatching fails then it'll raise `UnpatchingFailedError`.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def check_telemetry(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def block_telemetry(self):
|
||||||
|
pass
|
||||||
|
@ -1,6 +1,22 @@
|
|||||||
from typing import Any
|
from pathlib import Path
|
||||||
|
from vollerei import __version__
|
||||||
|
from vollerei.cli.hsr import HSR
|
||||||
|
from vollerei.hsr import PatchType
|
||||||
|
|
||||||
|
|
||||||
class CLI:
|
class CLI:
|
||||||
def __init__(self):
|
def __init__(self, game_path: str = None, patch_type=None) -> None:
|
||||||
pass
|
"""
|
||||||
|
Vollerei CLI
|
||||||
|
"""
|
||||||
|
print(f"Vollerei v{__version__}")
|
||||||
|
if not game_path:
|
||||||
|
game_path = Path.cwd()
|
||||||
|
game_path = Path(game_path)
|
||||||
|
if patch_type is None:
|
||||||
|
patch_type = PatchType.Jadeite
|
||||||
|
elif isinstance(patch_type, str):
|
||||||
|
patch_type = PatchType[patch_type]
|
||||||
|
elif isinstance(patch_type, int):
|
||||||
|
patch_type = PatchType(patch_type)
|
||||||
|
self.hsr = HSR(game_path=game_path, patch_type=patch_type)
|
||||||
|
51
vollerei/cli/hsr.py
Normal file
51
vollerei/cli/hsr.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from traceback import print_exc
|
||||||
|
from vollerei.hsr import Game, Patcher
|
||||||
|
from vollerei.exceptions.patcher import PatcherError, PatchUpdateError
|
||||||
|
from vollerei.hsr.patcher import PatchType
|
||||||
|
|
||||||
|
|
||||||
|
class HSR:
|
||||||
|
def __init__(
|
||||||
|
self, game_path=None, patch_type: PatchType = PatchType.Jadeite
|
||||||
|
) -> None:
|
||||||
|
self._game = Game(game_path)
|
||||||
|
print("Game directory:", self._game.path)
|
||||||
|
print("Game version:", self._game.get_version_str())
|
||||||
|
self._patcher = Patcher()
|
||||||
|
self._patcher.patch_type = patch_type
|
||||||
|
|
||||||
|
# Double _ means private to prevent Fire from invoking it
|
||||||
|
def patch_type(self):
|
||||||
|
print("Patch type:", self._patcher.patch_type.name)
|
||||||
|
|
||||||
|
def __update_patch(self):
|
||||||
|
self.patch_type()
|
||||||
|
print("Updating patch...", end=" ")
|
||||||
|
try:
|
||||||
|
self._patcher.update_patch()
|
||||||
|
except PatchUpdateError as e:
|
||||||
|
print("FAILED")
|
||||||
|
print(f"Patch update failed with following error: {e} ({e.__context__})")
|
||||||
|
print_exc()
|
||||||
|
return False
|
||||||
|
print("OK")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_patch(self):
|
||||||
|
self.__update_patch()
|
||||||
|
|
||||||
|
def patch(self):
|
||||||
|
if not self.__update_patch():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
print("Patching game...", end=" ")
|
||||||
|
jadelte_dir = self._patcher.patch_game(game=self._game)
|
||||||
|
except PatcherError as e:
|
||||||
|
print("FAILED")
|
||||||
|
print(f"Patching failed with following error: {e}")
|
||||||
|
return
|
||||||
|
print("OK")
|
||||||
|
print("Jadelte executable is located at:", jadelte_dir.joinpath("jadelte.exe"))
|
||||||
|
print(
|
||||||
|
"Patching succeeded, but note that you need to run the game using Jadelte to use the patch."
|
||||||
|
)
|
@ -1,6 +1,6 @@
|
|||||||
# Re-exports
|
# Re-exports
|
||||||
from vollerei.hsr.patcher import Patcher
|
from vollerei.hsr.patcher import Patcher, PatchType
|
||||||
from vollerei.hsr.launcher import Game, GameChannel
|
from vollerei.hsr.launcher import Game, GameChannel
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Patcher", "Game", "GameChannel"]
|
__all__ = ["Patcher", "PatchType", "Game", "GameChannel"]
|
||||||
|
@ -56,6 +56,10 @@ class Game(GameABC):
|
|||||||
tuple[int, int, int]: The version as a tuple of integers.
|
tuple[int, int, int]: The version as a tuple of integers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
data_file = self.data_folder().joinpath("data.unity3d")
|
||||||
|
if not data_file.exists():
|
||||||
|
return (0, 0, 0)
|
||||||
|
|
||||||
def bytes_to_int(byte_array: list[bytes]) -> int:
|
def bytes_to_int(byte_array: list[bytes]) -> int:
|
||||||
bytes_as_int = int.from_bytes(byte_array, byteorder="big")
|
bytes_as_int = int.from_bytes(byte_array, byteorder="big")
|
||||||
actual_int = bytes_as_int - 48 # 48 is the ASCII code for 0
|
actual_int = bytes_as_int - 48 # 48 is the ASCII code for 0
|
||||||
|
@ -3,7 +3,11 @@ from shutil import copy2
|
|||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
from vollerei.abc.patcher import PatcherABC
|
from vollerei.abc.patcher import PatcherABC
|
||||||
from vollerei.exceptions.game import GameNotInstalledError
|
from vollerei.exceptions.game import GameNotInstalledError
|
||||||
from vollerei.exceptions.patcher import VersionNotSupportedError
|
from vollerei.exceptions.patcher import (
|
||||||
|
VersionNotSupportedError,
|
||||||
|
PatcherError,
|
||||||
|
PatchUpdateError,
|
||||||
|
)
|
||||||
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
|
||||||
@ -63,11 +67,14 @@ class Patcher(PatcherABC):
|
|||||||
f.write(file_version)
|
f.write(file_version)
|
||||||
|
|
||||||
def update_patch(self):
|
def update_patch(self):
|
||||||
|
try:
|
||||||
match self._patch_type:
|
match self._patch_type:
|
||||||
case PatchType.Astra:
|
case PatchType.Astra:
|
||||||
self._update_astra()
|
self._update_astra()
|
||||||
case PatchType.Jadeite:
|
case PatchType.Jadeite:
|
||||||
self._update_jadeite()
|
self._update_jadeite()
|
||||||
|
except Exception as e:
|
||||||
|
raise PatchUpdateError("Failed to update patch.") from e
|
||||||
|
|
||||||
def _patch_astra(self, game: Game):
|
def _patch_astra(self, game: Game):
|
||||||
if game.get_version() != (1, 0, 5):
|
if game.get_version() != (1, 0, 5):
|
||||||
@ -119,7 +126,7 @@ class Patcher(PatcherABC):
|
|||||||
|
|
||||||
def patch_game(self, game: Game):
|
def patch_game(self, game: Game):
|
||||||
if not game.is_installed():
|
if not game.is_installed():
|
||||||
raise GameNotInstalledError("Game is not installed")
|
raise PatcherError(GameNotInstalledError("Game is not installed"))
|
||||||
match self._patch_type:
|
match self._patch_type:
|
||||||
case PatchType.Astra:
|
case PatchType.Astra:
|
||||||
self._patch_astra(game)
|
self._patch_astra(game)
|
||||||
@ -128,3 +135,9 @@ class Patcher(PatcherABC):
|
|||||||
|
|
||||||
def unpatch_game(self, game: Game):
|
def unpatch_game(self, game: Game):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def check_telemetry(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def block_telemetry(self):
|
||||||
|
pass
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import errno
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import stat
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
@ -145,16 +148,39 @@ class Git:
|
|||||||
rmtree(path)
|
rmtree(path)
|
||||||
try:
|
try:
|
||||||
if not path_as_path.exists():
|
if not path_as_path.exists():
|
||||||
raise subprocess.CalledProcessError
|
raise subprocess.CalledProcessError(0, cmd="Vollerei-generated error")
|
||||||
origin_url = subprocess.check_output(
|
origin_url = (
|
||||||
|
subprocess.check_output(
|
||||||
["git", "config", "--get", "remote.origin.url"], cwd=path
|
["git", "config", "--get", "remote.origin.url"], cwd=path
|
||||||
).decode()
|
)
|
||||||
|
.decode()
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
if origin_url != url:
|
if origin_url != url:
|
||||||
raise subprocess.CalledProcessError
|
raise subprocess.CalledProcessError(0, cmd="Vollerei-generated error")
|
||||||
subprocess.check_call(["git", "pull"], cwd=path)
|
subprocess.check_call(["git", "pull"], cwd=path)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
if path_as_path.exists():
|
if path_as_path.exists():
|
||||||
rmtree(path)
|
try:
|
||||||
|
|
||||||
|
def handle_error(func, path, exc):
|
||||||
|
excvalue = exc[1]
|
||||||
|
if (
|
||||||
|
func in (os.rmdir, os.remove)
|
||||||
|
and excvalue.errno == errno.EACCES
|
||||||
|
):
|
||||||
|
os.chmod(
|
||||||
|
path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO
|
||||||
|
) # 0777
|
||||||
|
func(path)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
rmtree(path, ignore_errors=False, onerror=handle_error)
|
||||||
|
except OSError as e:
|
||||||
|
raise GitCloneError(
|
||||||
|
f"Failed to delete existing repository {path_as_path}"
|
||||||
|
) from e
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(["git", "clone", url, path])
|
subprocess.check_call(["git", "clone", url, path])
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
|
Loading…
Reference in New Issue
Block a user