Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд Быстренький скрипт для запуска Hytale бесплатно

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
15 Май 2024
Сообщения
78
Реакции
6
Крч увидел Hytale захотелось поиграть подумал покупать нахуй нужно качать лаунчеры в рот ебал ну и решил скриптик написать да очень простенько без онлайн фикса но если кому то интересно чисто затестить Hytale понять что это то вот


code:
Expand Collapse Copy
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tarfile
import time
import uuid
import zipfile
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError

USER_AGENT = "HytaleTT/1.0"


def get_appdata_dir() -> Path:
    appdata = os.environ.get("APPDATA")
    if appdata:
        return Path(appdata)
    return Path.home() / "AppData" / "Roaming"


def http_head(url: str):
    req = Request(url, method="HEAD", headers={"User-Agent": USER_AGENT})
    try:
        with urlopen(req, timeout=15) as resp:
            size = resp.headers.get("Content-Length")
            return True, int(size) if size else None
    except HTTPError as e:
        if e.code == 404:
            return False, None
        raise


def http_get_text(url: str) -> str:
    req = Request(url, headers={"User-Agent": USER_AGENT})
    with urlopen(req, timeout=30) as resp:
        return resp.read().decode("utf-8")


def download_file(url: str, dest: Path, expected_size=None, expected_sha256=None):
    dest.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = dest.with_suffix(dest.suffix + ".tmp")

    req = Request(url, headers={"User-Agent": USER_AGENT})
    with urlopen(req, timeout=60) as resp:
        total = expected_size
        if total is None:
            length = resp.headers.get("Content-Length")
            total = int(length) if length else 0

        downloaded = 0
        start_time = time.time()
        with open(tmp_path, "wb") as f:
            while True:
                chunk = resp.read(1024 * 1024)
                if not chunk:
                    break
                f.write(chunk)
                downloaded += len(chunk)
                if total > 0:
                    pct = downloaded / total * 100
                    elapsed = max(time.time() - start_time, 0.001)
                    speed = downloaded / elapsed / (1024 * 1024)
                    print(f"\rDownloading... {pct:5.1f}% ({speed:.1f} MB/s)", end="")
        if total > 0:
            print()

    if expected_size is not None and expected_size > 0:
        actual = tmp_path.stat().st_size
        if actual != expected_size:
            tmp_path.unlink(missing_ok=True)
            raise RuntimeError(f"Size mismatch: expected {expected_size}, got {actual}")

    if expected_sha256:
        actual_hash = compute_sha256(tmp_path)
        if actual_hash.lower() != expected_sha256.lower():
            tmp_path.unlink(missing_ok=True)
            raise RuntimeError("SHA256 mismatch for downloaded file")

    tmp_path.replace(dest)


def compute_sha256(path: Path) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            h.update(chunk)
    return h.hexdigest()


def extract_archive(archive_path: Path, dest_dir: Path):
    dest_dir.mkdir(parents=True, exist_ok=True)
    if zipfile.is_zipfile(archive_path):
        with zipfile.ZipFile(archive_path) as zf:
            zf.extractall(dest_dir)
        return
    if tarfile.is_tarfile(archive_path):
        with tarfile.open(archive_path, "r:*") as tf:
            tf.extractall(dest_dir)
        return
    raise RuntimeError("Unsupported archive format")


def flatten_directory(dir_path: Path):
    subdirs = [p for p in dir_path.iterdir() if p.is_dir()]
    if len(subdirs) == 1:
        subdir = subdirs[0]
        for item in subdir.iterdir():
            target = dir_path / item.name
            if target.exists():
                if target.is_dir():
                    shutil.rmtree(target)
                else:
                    target.unlink()
            shutil.move(str(item), str(target))
        shutil.rmtree(subdir)


def find_butler_exe(butler_dir: Path) -> Path:
    direct = butler_dir / "butler.exe"
    if direct.exists():
        return direct
    for path in butler_dir.rglob("butler.exe"):
        # Move to root for simpler invocation
        target = butler_dir / "butler.exe"
        if path != target:
            shutil.copy2(path, target)
        return target
    raise RuntimeError("butler.exe not found after extraction")


def ensure_butler(launcher_dir: Path) -> Path:
    butler_dir = launcher_dir / "butler"
    butler_dir.mkdir(parents=True, exist_ok=True)
    butler_exe = butler_dir / "butler.exe"
    if butler_exe.exists():
        return butler_exe

    cache_dir = launcher_dir / "cache"
    cache_dir.mkdir(parents=True, exist_ok=True)
    zip_path = cache_dir / "butler.zip"

    url = "https://broth.itch.zone/butler/windows-amd64/LATEST/archive/default"
    print("Downloading butler...")
    download_file(url, zip_path)

    print("Extracting butler...")
    extract_archive(zip_path, butler_dir)
    zip_path.unlink(missing_ok=True)

    return find_butler_exe(butler_dir)


def apply_pwr(butler_exe: Path, pwr_path: Path, game_dir: Path, launcher_dir: Path):
    staging_dir = game_dir / "staging-temp"
    if staging_dir.exists():
        shutil.rmtree(staging_dir, ignore_errors=True)
    game_dir.mkdir(parents=True, exist_ok=True)
    staging_dir.mkdir(parents=True, exist_ok=True)

    args = [
        str(butler_exe),
        "apply",
        "--staging-dir",
        str(staging_dir),
        str(pwr_path),
        str(game_dir),
    ]

    log_path = launcher_dir / "butler.log"
    with open(log_path, "a", encoding="utf-8") as log:
        log.write(f"\n[{time.strftime('%Y-%m-%d %H:%M:%S')}] Running: {' '.join(args)}\n")
        log.write(f"PWR size: {pwr_path.stat().st_size} bytes\n")
        log.write(f"Game dir exists: {game_dir.exists()}\n")

    proc = subprocess.run(
        args,
        cwd=str(launcher_dir),
        capture_output=True,
        text=True,
        timeout=600,
    )

    with open(log_path, "a", encoding="utf-8") as log:
        log.write(f"Exit code: {proc.returncode}\n")
        log.write("Stdout:\n" + (proc.stdout or "") + "\n")
        log.write("Stderr:\n" + (proc.stderr or "") + "\n")

    shutil.rmtree(staging_dir, ignore_errors=True)

    if proc.returncode != 0:
        msg = (proc.stderr or proc.stdout or "Unknown error").strip()
        raise RuntimeError(f"Butler error: {msg}")


def find_latest_version(base_url: str, branch: str) -> int:
    consecutive_misses = 0
    ver = 1
    max_ver = 0
    while consecutive_misses < 5:
        url = f"{base_url}/{branch}/0/{ver}.pwr"
        try:
            exists, _ = http_head(url)
        except Exception:
            exists = False
        if exists:
            max_ver = ver
            consecutive_misses = 0
        else:
            consecutive_misses += 1
        ver += 1
    if max_ver == 0:
        raise RuntimeError("No versions found")
    return max_ver


def download_jre(branch: str, launcher_dir: Path, game_base_dir: Path):
    jre_dir = game_base_dir / "install" / branch / "package" / "jre" / "latest"
    java_exe = jre_dir / "bin" / "java.exe"
    if java_exe.exists():
        print("JRE already installed")
        return

    url = f"https://launcher.hytale.com/version/{branch}/jre.json"
    print(f"Fetching JRE info: {url}")
    data = json.loads(http_get_text(url))

    download_url = data.get("download_url") or data.get("downloadUrl")
    if not download_url:
        raise RuntimeError("download_url not found in jre.json")

    os_key = "windows"
    arch_key = "amd64"
    platform = download_url.get(os_key, {}).get(arch_key)
    if not platform:
        raise RuntimeError("JRE not available for windows/amd64")

    jre_url = platform.get("url")
    jre_sha = platform.get("sha256")
    if not jre_url:
        raise RuntimeError("JRE url missing")

    cache_dir = launcher_dir / "cache"
    cache_dir.mkdir(parents=True, exist_ok=True)
    file_name = Path(jre_url).name
    archive_path = cache_dir / file_name

    print("Downloading JRE...")
    download_file(jre_url, archive_path, expected_sha256=jre_sha)

    print("Extracting JRE...")
    extract_archive(archive_path, jre_dir)
    flatten_directory(jre_dir)
    archive_path.unlink(missing_ok=True)


def get_or_create_uuid(players_path: Path, player_name: str) -> str:
    players = {}
    if players_path.exists():
        try:
            players = json.loads(players_path.read_text(encoding="utf-8"))
        except Exception:
            players = {}

    key = player_name.lower()
    if key in players:
        u = players[key]
        return u.replace("-", "")

    new_uuid = uuid.uuid4().hex
    players[key] = new_uuid
    players_path.parent.mkdir(parents=True, exist_ok=True)
    players_path.write_text(json.dumps(players, indent=2), encoding="utf-8")
    return new_uuid


def launch_game(branch: str, game_base_dir: Path, launcher_dir: Path, player_name: str):
    game_dir = game_base_dir / "install" / branch / "package" / "game" / "latest"
    client_path = game_dir / "Client" / "HytaleClient.exe"
    if not client_path.exists():
        raise RuntimeError("Game client not found")

    java_exe = game_base_dir / "install" / branch / "package" / "jre" / "latest" / "bin" / "java.exe"
    if not java_exe.exists():
        java_exe = Path("java")

    user_data = game_base_dir / "UserData"
    user_data.mkdir(parents=True, exist_ok=True)

    players_path = launcher_dir / "players.json"
    player_uuid = get_or_create_uuid(players_path, player_name)

    args = [
        str(client_path),
        "--app-dir",
        str(game_dir),
        "--java-exec",
        str(java_exe),
        "--user-dir",
        str(user_data),
        "--auth-mode",
        "offline",
        "--uuid",
        player_uuid,
        "--name",
        player_name,
    ]

    print("Launching game...")
    subprocess.Popen(args, cwd=str(client_path.parent))


def main():
    parser = argparse.ArgumentParser(description="Download and run Hytale (release) without the launcher")
    parser.add_argument("--branch", default="release", help="Branch name (default: release)")
    parser.add_argument("--name", default="Giddyk", help="Player name (default: Giddyk)")
    parser.add_argument("--no-launch", action="store_true", help="Only download and install")
    parser.add_argument("--force", action="store_true", help="Force re-download even if installed")
    args = parser.parse_args()

    if os.name != "nt":
        print("This script is intended for Windows")
        return 1

    base_url = "https://game-patches.hytale.com/patches/windows/amd64"
    branch = args.branch

    appdata = get_appdata_dir()
    launcher_dir = appdata / "HyTaLauncher"
    game_base_dir = appdata / "Hytale"
    cache_dir = launcher_dir / "cache"

    launcher_dir.mkdir(parents=True, exist_ok=True)
    cache_dir.mkdir(parents=True, exist_ok=True)

    print("Finding latest version...")
    latest_version = find_latest_version(base_url, branch)
    print(f"Latest version: {latest_version}")

    game_dir = game_base_dir / "install" / branch / "package" / "game" / "latest"
    client_path = game_dir / "Client" / "HytaleClient.exe"
    version_file = game_dir / ".version"

    if not args.force and client_path.exists() and version_file.exists():
        try:
            installed_version = int(version_file.read_text().strip())
        except Exception:
            installed_version = 0
        if installed_version == latest_version:
            print("Game already installed at latest version")
        else:
            print(f"Installed version: {installed_version}, will update")
    
    download_jre(branch, launcher_dir, game_base_dir)

    pwr_url = f"{base_url}/{branch}/0/{latest_version}.pwr"
    pwr_path = cache_dir / f"{branch}_0_{latest_version}.pwr"

    if args.force or not pwr_path.exists():
        print(f"Downloading full patch: {pwr_url}")
        exists, size = http_head(pwr_url)
        if not exists:
            raise RuntimeError("PWR file not found for latest version")
        download_file(pwr_url, pwr_path, expected_size=size)
    else:
        print("Using cached PWR file")

    butler_exe = ensure_butler(launcher_dir)
    print("Applying patch...")
    apply_pwr(butler_exe, pwr_path, game_dir, launcher_dir)

    version_file.parent.mkdir(parents=True, exist_ok=True)
    version_file.write_text(str(latest_version), encoding="utf-8")

    if not args.no_launch:
        launch_game(branch, game_base_dir, launcher_dir, args.name)

    print("Done")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
 
Крч увидел Hytale захотелось поиграть подумал покупать нахуй нужно качать лаунчеры в рот ебал ну и решил скриптик написать да очень простенько без онлайн фикса но если кому то интересно чисто затестить Hytale понять что это то вот


code:
Expand Collapse Copy
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tarfile
import time
import uuid
import zipfile
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError

USER_AGENT = "HytaleTT/1.0"


def get_appdata_dir() -> Path:
    appdata = os.environ.get("APPDATA")
    if appdata:
        return Path(appdata)
    return Path.home() / "AppData" / "Roaming"


def http_head(url: str):
    req = Request(url, method="HEAD", headers={"User-Agent": USER_AGENT})
    try:
        with urlopen(req, timeout=15) as resp:
            size = resp.headers.get("Content-Length")
            return True, int(size) if size else None
    except HTTPError as e:
        if e.code == 404:
            return False, None
        raise


def http_get_text(url: str) -> str:
    req = Request(url, headers={"User-Agent": USER_AGENT})
    with urlopen(req, timeout=30) as resp:
        return resp.read().decode("utf-8")


def download_file(url: str, dest: Path, expected_size=None, expected_sha256=None):
    dest.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = dest.with_suffix(dest.suffix + ".tmp")

    req = Request(url, headers={"User-Agent": USER_AGENT})
    with urlopen(req, timeout=60) as resp:
        total = expected_size
        if total is None:
            length = resp.headers.get("Content-Length")
            total = int(length) if length else 0

        downloaded = 0
        start_time = time.time()
        with open(tmp_path, "wb") as f:
            while True:
                chunk = resp.read(1024 * 1024)
                if not chunk:
                    break
                f.write(chunk)
                downloaded += len(chunk)
                if total > 0:
                    pct = downloaded / total * 100
                    elapsed = max(time.time() - start_time, 0.001)
                    speed = downloaded / elapsed / (1024 * 1024)
                    print(f"\rDownloading... {pct:5.1f}% ({speed:.1f} MB/s)", end="")
        if total > 0:
            print()

    if expected_size is not None and expected_size > 0:
        actual = tmp_path.stat().st_size
        if actual != expected_size:
            tmp_path.unlink(missing_ok=True)
            raise RuntimeError(f"Size mismatch: expected {expected_size}, got {actual}")

    if expected_sha256:
        actual_hash = compute_sha256(tmp_path)
        if actual_hash.lower() != expected_sha256.lower():
            tmp_path.unlink(missing_ok=True)
            raise RuntimeError("SHA256 mismatch for downloaded file")

    tmp_path.replace(dest)


def compute_sha256(path: Path) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            h.update(chunk)
    return h.hexdigest()


def extract_archive(archive_path: Path, dest_dir: Path):
    dest_dir.mkdir(parents=True, exist_ok=True)
    if zipfile.is_zipfile(archive_path):
        with zipfile.ZipFile(archive_path) as zf:
            zf.extractall(dest_dir)
        return
    if tarfile.is_tarfile(archive_path):
        with tarfile.open(archive_path, "r:*") as tf:
            tf.extractall(dest_dir)
        return
    raise RuntimeError("Unsupported archive format")


def flatten_directory(dir_path: Path):
    subdirs = [p for p in dir_path.iterdir() if p.is_dir()]
    if len(subdirs) == 1:
        subdir = subdirs[0]
        for item in subdir.iterdir():
            target = dir_path / item.name
            if target.exists():
                if target.is_dir():
                    shutil.rmtree(target)
                else:
                    target.unlink()
            shutil.move(str(item), str(target))
        shutil.rmtree(subdir)


def find_butler_exe(butler_dir: Path) -> Path:
    direct = butler_dir / "butler.exe"
    if direct.exists():
        return direct
    for path in butler_dir.rglob("butler.exe"):
        # Move to root for simpler invocation
        target = butler_dir / "butler.exe"
        if path != target:
            shutil.copy2(path, target)
        return target
    raise RuntimeError("butler.exe not found after extraction")


def ensure_butler(launcher_dir: Path) -> Path:
    butler_dir = launcher_dir / "butler"
    butler_dir.mkdir(parents=True, exist_ok=True)
    butler_exe = butler_dir / "butler.exe"
    if butler_exe.exists():
        return butler_exe

    cache_dir = launcher_dir / "cache"
    cache_dir.mkdir(parents=True, exist_ok=True)
    zip_path = cache_dir / "butler.zip"

    url = "https://broth.itch.zone/butler/windows-amd64/LATEST/archive/default"
    print("Downloading butler...")
    download_file(url, zip_path)

    print("Extracting butler...")
    extract_archive(zip_path, butler_dir)
    zip_path.unlink(missing_ok=True)

    return find_butler_exe(butler_dir)


def apply_pwr(butler_exe: Path, pwr_path: Path, game_dir: Path, launcher_dir: Path):
    staging_dir = game_dir / "staging-temp"
    if staging_dir.exists():
        shutil.rmtree(staging_dir, ignore_errors=True)
    game_dir.mkdir(parents=True, exist_ok=True)
    staging_dir.mkdir(parents=True, exist_ok=True)

    args = [
        str(butler_exe),
        "apply",
        "--staging-dir",
        str(staging_dir),
        str(pwr_path),
        str(game_dir),
    ]

    log_path = launcher_dir / "butler.log"
    with open(log_path, "a", encoding="utf-8") as log:
        log.write(f"\n[{time.strftime('%Y-%m-%d %H:%M:%S')}] Running: {' '.join(args)}\n")
        log.write(f"PWR size: {pwr_path.stat().st_size} bytes\n")
        log.write(f"Game dir exists: {game_dir.exists()}\n")

    proc = subprocess.run(
        args,
        cwd=str(launcher_dir),
        capture_output=True,
        text=True,
        timeout=600,
    )

    with open(log_path, "a", encoding="utf-8") as log:
        log.write(f"Exit code: {proc.returncode}\n")
        log.write("Stdout:\n" + (proc.stdout or "") + "\n")
        log.write("Stderr:\n" + (proc.stderr or "") + "\n")

    shutil.rmtree(staging_dir, ignore_errors=True)

    if proc.returncode != 0:
        msg = (proc.stderr or proc.stdout or "Unknown error").strip()
        raise RuntimeError(f"Butler error: {msg}")


def find_latest_version(base_url: str, branch: str) -> int:
    consecutive_misses = 0
    ver = 1
    max_ver = 0
    while consecutive_misses < 5:
        url = f"{base_url}/{branch}/0/{ver}.pwr"
        try:
            exists, _ = http_head(url)
        except Exception:
            exists = False
        if exists:
            max_ver = ver
            consecutive_misses = 0
        else:
            consecutive_misses += 1
        ver += 1
    if max_ver == 0:
        raise RuntimeError("No versions found")
    return max_ver


def download_jre(branch: str, launcher_dir: Path, game_base_dir: Path):
    jre_dir = game_base_dir / "install" / branch / "package" / "jre" / "latest"
    java_exe = jre_dir / "bin" / "java.exe"
    if java_exe.exists():
        print("JRE already installed")
        return

    url = f"https://launcher.hytale.com/version/{branch}/jre.json"
    print(f"Fetching JRE info: {url}")
    data = json.loads(http_get_text(url))

    download_url = data.get("download_url") or data.get("downloadUrl")
    if not download_url:
        raise RuntimeError("download_url not found in jre.json")

    os_key = "windows"
    arch_key = "amd64"
    platform = download_url.get(os_key, {}).get(arch_key)
    if not platform:
        raise RuntimeError("JRE not available for windows/amd64")

    jre_url = platform.get("url")
    jre_sha = platform.get("sha256")
    if not jre_url:
        raise RuntimeError("JRE url missing")

    cache_dir = launcher_dir / "cache"
    cache_dir.mkdir(parents=True, exist_ok=True)
    file_name = Path(jre_url).name
    archive_path = cache_dir / file_name

    print("Downloading JRE...")
    download_file(jre_url, archive_path, expected_sha256=jre_sha)

    print("Extracting JRE...")
    extract_archive(archive_path, jre_dir)
    flatten_directory(jre_dir)
    archive_path.unlink(missing_ok=True)


def get_or_create_uuid(players_path: Path, player_name: str) -> str:
    players = {}
    if players_path.exists():
        try:
            players = json.loads(players_path.read_text(encoding="utf-8"))
        except Exception:
            players = {}

    key = player_name.lower()
    if key in players:
        u = players[key]
        return u.replace("-", "")

    new_uuid = uuid.uuid4().hex
    players[key] = new_uuid
    players_path.parent.mkdir(parents=True, exist_ok=True)
    players_path.write_text(json.dumps(players, indent=2), encoding="utf-8")
    return new_uuid


def launch_game(branch: str, game_base_dir: Path, launcher_dir: Path, player_name: str):
    game_dir = game_base_dir / "install" / branch / "package" / "game" / "latest"
    client_path = game_dir / "Client" / "HytaleClient.exe"
    if not client_path.exists():
        raise RuntimeError("Game client not found")

    java_exe = game_base_dir / "install" / branch / "package" / "jre" / "latest" / "bin" / "java.exe"
    if not java_exe.exists():
        java_exe = Path("java")

    user_data = game_base_dir / "UserData"
    user_data.mkdir(parents=True, exist_ok=True)

    players_path = launcher_dir / "players.json"
    player_uuid = get_or_create_uuid(players_path, player_name)

    args = [
        str(client_path),
        "--app-dir",
        str(game_dir),
        "--java-exec",
        str(java_exe),
        "--user-dir",
        str(user_data),
        "--auth-mode",
        "offline",
        "--uuid",
        player_uuid,
        "--name",
        player_name,
    ]

    print("Launching game...")
    subprocess.Popen(args, cwd=str(client_path.parent))


def main():
    parser = argparse.ArgumentParser(description="Download and run Hytale (release) without the launcher")
    parser.add_argument("--branch", default="release", help="Branch name (default: release)")
    parser.add_argument("--name", default="Giddyk", help="Player name (default: Giddyk)")
    parser.add_argument("--no-launch", action="store_true", help="Only download and install")
    parser.add_argument("--force", action="store_true", help="Force re-download even if installed")
    args = parser.parse_args()

    if os.name != "nt":
        print("This script is intended for Windows")
        return 1

    base_url = "https://game-patches.hytale.com/patches/windows/amd64"
    branch = args.branch

    appdata = get_appdata_dir()
    launcher_dir = appdata / "HyTaLauncher"
    game_base_dir = appdata / "Hytale"
    cache_dir = launcher_dir / "cache"

    launcher_dir.mkdir(parents=True, exist_ok=True)
    cache_dir.mkdir(parents=True, exist_ok=True)

    print("Finding latest version...")
    latest_version = find_latest_version(base_url, branch)
    print(f"Latest version: {latest_version}")

    game_dir = game_base_dir / "install" / branch / "package" / "game" / "latest"
    client_path = game_dir / "Client" / "HytaleClient.exe"
    version_file = game_dir / ".version"

    if not args.force and client_path.exists() and version_file.exists():
        try:
            installed_version = int(version_file.read_text().strip())
        except Exception:
            installed_version = 0
        if installed_version == latest_version:
            print("Game already installed at latest version")
        else:
            print(f"Installed version: {installed_version}, will update")
   
    download_jre(branch, launcher_dir, game_base_dir)

    pwr_url = f"{base_url}/{branch}/0/{latest_version}.pwr"
    pwr_path = cache_dir / f"{branch}_0_{latest_version}.pwr"

    if args.force or not pwr_path.exists():
        print(f"Downloading full patch: {pwr_url}")
        exists, size = http_head(pwr_url)
        if not exists:
            raise RuntimeError("PWR file not found for latest version")
        download_file(pwr_url, pwr_path, expected_size=size)
    else:
        print("Using cached PWR file")

    butler_exe = ensure_butler(launcher_dir)
    print("Applying patch...")
    apply_pwr(butler_exe, pwr_path, game_dir, launcher_dir)

    version_file.parent.mkdir(parents=True, exist_ok=True)
    version_file.write_text(str(latest_version), encoding="utf-8")

    if not args.no_launch:
        launch_game(branch, game_base_dir, launcher_dir, args.name)

    print("Done")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
hytale это ратка
 
чувак ну ты пишешь "они пиздят лицухи на майн а потом продают их на фп ?"
Тип они пошутили про ратник вообще нечего они не пиздят
Крч увидел Hytale захотелось поиграть подумал покупать нахуй нужно качать лаунчеры в рот ебал ну и решил скриптик написать да очень простенько без онлайн фикса но если кому то интересно чисто затестить Hytale понять что это то вот


code:
Expand Collapse Copy
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tarfile
import time
import uuid
import zipfile
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError

USER_AGENT = "HytaleTT/1.0"


def get_appdata_dir() -> Path:
    appdata = os.environ.get("APPDATA")
    if appdata:
        return Path(appdata)
    return Path.home() / "AppData" / "Roaming"


def http_head(url: str):
    req = Request(url, method="HEAD", headers={"User-Agent": USER_AGENT})
    try:
        with urlopen(req, timeout=15) as resp:
            size = resp.headers.get("Content-Length")
            return True, int(size) if size else None
    except HTTPError as e:
        if e.code == 404:
            return False, None
        raise


def http_get_text(url: str) -> str:
    req = Request(url, headers={"User-Agent": USER_AGENT})
    with urlopen(req, timeout=30) as resp:
        return resp.read().decode("utf-8")


def download_file(url: str, dest: Path, expected_size=None, expected_sha256=None):
    dest.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = dest.with_suffix(dest.suffix + ".tmp")

    req = Request(url, headers={"User-Agent": USER_AGENT})
    with urlopen(req, timeout=60) as resp:
        total = expected_size
        if total is None:
            length = resp.headers.get("Content-Length")
            total = int(length) if length else 0

        downloaded = 0
        start_time = time.time()
        with open(tmp_path, "wb") as f:
            while True:
                chunk = resp.read(1024 * 1024)
                if not chunk:
                    break
                f.write(chunk)
                downloaded += len(chunk)
                if total > 0:
                    pct = downloaded / total * 100
                    elapsed = max(time.time() - start_time, 0.001)
                    speed = downloaded / elapsed / (1024 * 1024)
                    print(f"\rDownloading... {pct:5.1f}% ({speed:.1f} MB/s)", end="")
        if total > 0:
            print()

    if expected_size is not None and expected_size > 0:
        actual = tmp_path.stat().st_size
        if actual != expected_size:
            tmp_path.unlink(missing_ok=True)
            raise RuntimeError(f"Size mismatch: expected {expected_size}, got {actual}")

    if expected_sha256:
        actual_hash = compute_sha256(tmp_path)
        if actual_hash.lower() != expected_sha256.lower():
            tmp_path.unlink(missing_ok=True)
            raise RuntimeError("SHA256 mismatch for downloaded file")

    tmp_path.replace(dest)


def compute_sha256(path: Path) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            h.update(chunk)
    return h.hexdigest()


def extract_archive(archive_path: Path, dest_dir: Path):
    dest_dir.mkdir(parents=True, exist_ok=True)
    if zipfile.is_zipfile(archive_path):
        with zipfile.ZipFile(archive_path) as zf:
            zf.extractall(dest_dir)
        return
    if tarfile.is_tarfile(archive_path):
        with tarfile.open(archive_path, "r:*") as tf:
            tf.extractall(dest_dir)
        return
    raise RuntimeError("Unsupported archive format")


def flatten_directory(dir_path: Path):
    subdirs = [p for p in dir_path.iterdir() if p.is_dir()]
    if len(subdirs) == 1:
        subdir = subdirs[0]
        for item in subdir.iterdir():
            target = dir_path / item.name
            if target.exists():
                if target.is_dir():
                    shutil.rmtree(target)
                else:
                    target.unlink()
            shutil.move(str(item), str(target))
        shutil.rmtree(subdir)


def find_butler_exe(butler_dir: Path) -> Path:
    direct = butler_dir / "butler.exe"
    if direct.exists():
        return direct
    for path in butler_dir.rglob("butler.exe"):
        # Move to root for simpler invocation
        target = butler_dir / "butler.exe"
        if path != target:
            shutil.copy2(path, target)
        return target
    raise RuntimeError("butler.exe not found after extraction")


def ensure_butler(launcher_dir: Path) -> Path:
    butler_dir = launcher_dir / "butler"
    butler_dir.mkdir(parents=True, exist_ok=True)
    butler_exe = butler_dir / "butler.exe"
    if butler_exe.exists():
        return butler_exe

    cache_dir = launcher_dir / "cache"
    cache_dir.mkdir(parents=True, exist_ok=True)
    zip_path = cache_dir / "butler.zip"

    url = "https://broth.itch.zone/butler/windows-amd64/LATEST/archive/default"
    print("Downloading butler...")
    download_file(url, zip_path)

    print("Extracting butler...")
    extract_archive(zip_path, butler_dir)
    zip_path.unlink(missing_ok=True)

    return find_butler_exe(butler_dir)


def apply_pwr(butler_exe: Path, pwr_path: Path, game_dir: Path, launcher_dir: Path):
    staging_dir = game_dir / "staging-temp"
    if staging_dir.exists():
        shutil.rmtree(staging_dir, ignore_errors=True)
    game_dir.mkdir(parents=True, exist_ok=True)
    staging_dir.mkdir(parents=True, exist_ok=True)

    args = [
        str(butler_exe),
        "apply",
        "--staging-dir",
        str(staging_dir),
        str(pwr_path),
        str(game_dir),
    ]

    log_path = launcher_dir / "butler.log"
    with open(log_path, "a", encoding="utf-8") as log:
        log.write(f"\n[{time.strftime('%Y-%m-%d %H:%M:%S')}] Running: {' '.join(args)}\n")
        log.write(f"PWR size: {pwr_path.stat().st_size} bytes\n")
        log.write(f"Game dir exists: {game_dir.exists()}\n")

    proc = subprocess.run(
        args,
        cwd=str(launcher_dir),
        capture_output=True,
        text=True,
        timeout=600,
    )

    with open(log_path, "a", encoding="utf-8") as log:
        log.write(f"Exit code: {proc.returncode}\n")
        log.write("Stdout:\n" + (proc.stdout or "") + "\n")
        log.write("Stderr:\n" + (proc.stderr or "") + "\n")

    shutil.rmtree(staging_dir, ignore_errors=True)

    if proc.returncode != 0:
        msg = (proc.stderr or proc.stdout or "Unknown error").strip()
        raise RuntimeError(f"Butler error: {msg}")


def find_latest_version(base_url: str, branch: str) -> int:
    consecutive_misses = 0
    ver = 1
    max_ver = 0
    while consecutive_misses < 5:
        url = f"{base_url}/{branch}/0/{ver}.pwr"
        try:
            exists, _ = http_head(url)
        except Exception:
            exists = False
        if exists:
            max_ver = ver
            consecutive_misses = 0
        else:
            consecutive_misses += 1
        ver += 1
    if max_ver == 0:
        raise RuntimeError("No versions found")
    return max_ver


def download_jre(branch: str, launcher_dir: Path, game_base_dir: Path):
    jre_dir = game_base_dir / "install" / branch / "package" / "jre" / "latest"
    java_exe = jre_dir / "bin" / "java.exe"
    if java_exe.exists():
        print("JRE already installed")
        return

    url = f"https://launcher.hytale.com/version/{branch}/jre.json"
    print(f"Fetching JRE info: {url}")
    data = json.loads(http_get_text(url))

    download_url = data.get("download_url") or data.get("downloadUrl")
    if not download_url:
        raise RuntimeError("download_url not found in jre.json")

    os_key = "windows"
    arch_key = "amd64"
    platform = download_url.get(os_key, {}).get(arch_key)
    if not platform:
        raise RuntimeError("JRE not available for windows/amd64")

    jre_url = platform.get("url")
    jre_sha = platform.get("sha256")
    if not jre_url:
        raise RuntimeError("JRE url missing")

    cache_dir = launcher_dir / "cache"
    cache_dir.mkdir(parents=True, exist_ok=True)
    file_name = Path(jre_url).name
    archive_path = cache_dir / file_name

    print("Downloading JRE...")
    download_file(jre_url, archive_path, expected_sha256=jre_sha)

    print("Extracting JRE...")
    extract_archive(archive_path, jre_dir)
    flatten_directory(jre_dir)
    archive_path.unlink(missing_ok=True)


def get_or_create_uuid(players_path: Path, player_name: str) -> str:
    players = {}
    if players_path.exists():
        try:
            players = json.loads(players_path.read_text(encoding="utf-8"))
        except Exception:
            players = {}

    key = player_name.lower()
    if key in players:
        u = players[key]
        return u.replace("-", "")

    new_uuid = uuid.uuid4().hex
    players[key] = new_uuid
    players_path.parent.mkdir(parents=True, exist_ok=True)
    players_path.write_text(json.dumps(players, indent=2), encoding="utf-8")
    return new_uuid


def launch_game(branch: str, game_base_dir: Path, launcher_dir: Path, player_name: str):
    game_dir = game_base_dir / "install" / branch / "package" / "game" / "latest"
    client_path = game_dir / "Client" / "HytaleClient.exe"
    if not client_path.exists():
        raise RuntimeError("Game client not found")

    java_exe = game_base_dir / "install" / branch / "package" / "jre" / "latest" / "bin" / "java.exe"
    if not java_exe.exists():
        java_exe = Path("java")

    user_data = game_base_dir / "UserData"
    user_data.mkdir(parents=True, exist_ok=True)

    players_path = launcher_dir / "players.json"
    player_uuid = get_or_create_uuid(players_path, player_name)

    args = [
        str(client_path),
        "--app-dir",
        str(game_dir),
        "--java-exec",
        str(java_exe),
        "--user-dir",
        str(user_data),
        "--auth-mode",
        "offline",
        "--uuid",
        player_uuid,
        "--name",
        player_name,
    ]

    print("Launching game...")
    subprocess.Popen(args, cwd=str(client_path.parent))


def main():
    parser = argparse.ArgumentParser(description="Download and run Hytale (release) without the launcher")
    parser.add_argument("--branch", default="release", help="Branch name (default: release)")
    parser.add_argument("--name", default="Giddyk", help="Player name (default: Giddyk)")
    parser.add_argument("--no-launch", action="store_true", help="Only download and install")
    parser.add_argument("--force", action="store_true", help="Force re-download even if installed")
    args = parser.parse_args()

    if os.name != "nt":
        print("This script is intended for Windows")
        return 1

    base_url = "https://game-patches.hytale.com/patches/windows/amd64"
    branch = args.branch

    appdata = get_appdata_dir()
    launcher_dir = appdata / "HyTaLauncher"
    game_base_dir = appdata / "Hytale"
    cache_dir = launcher_dir / "cache"

    launcher_dir.mkdir(parents=True, exist_ok=True)
    cache_dir.mkdir(parents=True, exist_ok=True)

    print("Finding latest version...")
    latest_version = find_latest_version(base_url, branch)
    print(f"Latest version: {latest_version}")

    game_dir = game_base_dir / "install" / branch / "package" / "game" / "latest"
    client_path = game_dir / "Client" / "HytaleClient.exe"
    version_file = game_dir / ".version"

    if not args.force and client_path.exists() and version_file.exists():
        try:
            installed_version = int(version_file.read_text().strip())
        except Exception:
            installed_version = 0
        if installed_version == latest_version:
            print("Game already installed at latest version")
        else:
            print(f"Installed version: {installed_version}, will update")
   
    download_jre(branch, launcher_dir, game_base_dir)

    pwr_url = f"{base_url}/{branch}/0/{latest_version}.pwr"
    pwr_path = cache_dir / f"{branch}_0_{latest_version}.pwr"

    if args.force or not pwr_path.exists():
        print(f"Downloading full patch: {pwr_url}")
        exists, size = http_head(pwr_url)
        if not exists:
            raise RuntimeError("PWR file not found for latest version")
        download_file(pwr_url, pwr_path, expected_size=size)
    else:
        print("Using cached PWR file")

    butler_exe = ensure_butler(launcher_dir)
    print("Applying patch...")
    apply_pwr(butler_exe, pwr_path, game_dir, launcher_dir)

    version_file.parent.mkdir(parents=True, exist_ok=True)
    version_file.write_text(str(latest_version), encoding="utf-8")

    if not args.no_launch:
        launch_game(branch, game_base_dir, launcher_dir, args.name)

    print("Done")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
И можно вопрос как это работает?
 
Тип они пошутили про ратник вообще нечего они не пиздят

И можно вопрос как это работает?
Создай .py файл закинь туда код который в теме и запусти и все оно само подгрузить все ну файлы хайтецла и можешь играть захочешь сменить ник то в коде вот тут смени
parser.add_argument("--name", default="Giddyk", help="Player name (default: Giddyk)")
Или запускай с --name (твой ник)
Если не выйдет говори я забилджу скрипт что бы его нужно было только открыть
 
Создай .py файл закинь туда код который в теме и запусти и все оно само подгрузить все ну файлы хайтецла и можешь играть захочешь сменить ник то в коде вот тут смени
parser.add_argument("--name", default="Giddyk", help="Player name (default: Giddyk)")
Или запускай с --name (твой ник)
Если не выйдет говори я забилджу скрипт что бы его нужно было только открыть
То есть погоди если ты так сделал значит можно такую любую игру скачать ну как сказать если у них есть свой лаунчер и ты типо ссылку знаешь берёшь закидываешь в код то что надо подкачать и игрулька будет работать?
 
То есть погоди если ты так сделал значит можно такую любую игру скачать ну как сказать если у них есть свой лаунчер и ты типо ссылку знаешь берёшь закидываешь в код то что надо подкачать и игрулька будет работать?
Нет можешь скачать файлы которые в коде и сам поймёшь что и как и почему
 
pycharm64_xlKiWacCZa.png

Япи япи япи пи пи я дор я дор я дор
 
Назад
Сверху Снизу