From 4edbe7e91072793d3ddd327b51f0264c80aa4e82 Mon Sep 17 00:00:00 2001 From: Sashegdev Date: Sun, 5 Apr 2026 22:25:43 +0000 Subject: [PATCH] =?UTF-8?q?Server=20BugFixes=20+=20=D1=83=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20sevrer=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D1=8B=20?= =?UTF-8?q?=D1=82.=D0=BA=20=D1=8D=D1=82=D0=BE=20=D1=83=D0=B6=D0=B5=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=D0=B5=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/main.py | 77 ------------------------------------------ server/models.py | 20 ----------- server/pack_manager.py | 46 +++++++++++++++---------- 3 files changed, 29 insertions(+), 114 deletions(-) diff --git a/server/main.py b/server/main.py index 55de0ab..6a896f8 100644 --- a/server/main.py +++ b/server/main.py @@ -1,4 +1,3 @@ -# main.py import os from fastapi import FastAPI, HTTPException, Request @@ -11,7 +10,6 @@ from cachetools import TTLCache import asyncio import logging from datetime import datetime -import asyncio from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol # Import local modules @@ -25,10 +23,6 @@ from log_manager import init_logging, get_logger from models import MinecraftVersion from pack_manager import get_minecraft_versions, download_minecraft_version -import minecraft_launcher_lib.command as mcl_command -import minecraft_launcher_lib.utils as mcl_utils -from pathlib import Path - logger = structlog.get_logger(__name__) # Cache for manifests - expires after 5 minutes @@ -301,77 +295,6 @@ async def get_pack_manifest(pack_name: str, request: Request): headers={"X-Pack-Version": str(meta.version), "X-Cached": "false"} ) -@app.post("/pack/{pack_name}/launch") -async def get_launch_command( - pack_name: str, - request: Request, - options: dict # клиент присылает: {"username": "...", "uuid": "...", "token": "...", "gameDir": "...", "javaPath": "...", "maxMemory": 4096} -): - client_ip = request.client.host if request.client else "unknown" - logger.info("Launch command requested", pack=pack_name, client_ip=client_ip) - - try: - meta = get_cached_manifest(pack_name) or await scan_pack(pack_name) - except Exception as e: - raise HTTPException(404, "Pack not found") from e - - # Базовые опции для minecraft-launcher-lib - launch_options = { - "username": options.get("username", "Player"), - "uuid": options.get("uuid", "00000000-0000-0000-0000-000000000000"), - "token": options.get("token", "token"), - "gameDirectory": options.get("gameDir", str(Path("."))), # относительный - "executablePath": options.get("javaPath", "java"), # или полный путь - "maxMemory": options.get("maxMemory", 4096), - "customResolution": options.get("customResolution", False), - # можно добавить width/height и т.д. - } - - # minecraft_directory — это корень пака у клиента (куда скачаны файлы) - minecraft_dir = launch_options["gameDirectory"] - - try: - # Генерируем команду (библиотека сама обработает Fabric/Forge через version.json) - command_list = mcl_command.get_minecraft_command( - version=meta.minecraft_version, - minecraft_directory=minecraft_dir, - options=launch_options - ) - - # Превращаем абсолютные пути в относительные (очень важно для портативности) - relative_command = [] - game_dir_path = Path(minecraft_dir).resolve() - - for arg in command_list: - try: - arg_path = Path(arg) - if arg_path.is_absolute() and game_dir_path in arg_path.parents: - rel = arg_path.relative_to(game_dir_path).as_posix() - relative_command.append(f"./{rel}" if os.name != "nt" else rel.replace("/", "\\")) - else: - relative_command.append(arg) - except Exception: - relative_command.append(arg) - - # Дополнительно: если в PackMeta есть свои jvmArgs/gameArgs — мерджим - if hasattr(meta, 'launch') and meta.launch: - # Добавляем кастомные jvmArgs в начало и т.д. - pass - - return { - "version": meta.version, - "minecraftVersion": meta.minecraft_version, - "loaderType": meta.loader_type, - "command": relative_command, # список строк — готов к subprocess - "workingDirectory": ".", # относительный - "mainClass": meta.launch.mainClass if hasattr(meta, 'launch') else None, - "classpath": meta.launch.classpath if hasattr(meta, 'launch') else [] - } - - except Exception as e: - logger.error("Failed to generate launch command", pack=pack_name, error=str(e), exc_info=True) - raise HTTPException(500, f"Failed to generate launch command: {str(e)}") - @app.get("/pack/{pack_name}/file/{file_path:path}") async def get_pack_file(pack_name: str, file_path: str, request: Request): full_path = PACKS_DIR / pack_name / file_path diff --git a/server/models.py b/server/models.py index ed1a75f..99f4a58 100644 --- a/server/models.py +++ b/server/models.py @@ -12,20 +12,6 @@ class FileEntry(BaseModel): added_at: datetime modified_at: datetime - -class LaunchConfig(BaseModel): - mainClass: str - classpath: List[str] = Field(default_factory=list) # относительные пути от gameDir - jvmArgs: List[str] = Field(default_factory=list) - gameArgs: List[str] = Field(default_factory=list) - nativesPath: Optional[str] = None # например: "natives" - assetIndex: str = "1.20" # или актуальная версия - minecraftVersion: str - loaderType: str # "vanilla" | "fabric" | "forge" | "neoforge" | "quilt" - loaderVersion: Optional[str] = None - gameDirectory: str = "." # "." = корень инсталляции пака (рекомендую) - - class PackMeta(BaseModel): pack_name: str version: int = 1 @@ -39,22 +25,16 @@ class PackMeta(BaseModel): ] ) - # Основные поля (один раз!) minecraft_version: str loader_type: str loader_version: Optional[str] = None - # Конфигурация запуска (обязательна) - launch: LaunchConfig - - class MinecraftVersion(BaseModel): version: str type: str # release, snapshot, old_alpha, old_beta release_time: datetime url: Optional[str] = None - class ModLoader(BaseModel): type: str version: str diff --git a/server/pack_manager.py b/server/pack_manager.py index 89fcb13..8bf00d4 100644 --- a/server/pack_manager.py +++ b/server/pack_manager.py @@ -1,4 +1,3 @@ -# pack_manager.py (updated) import hashlib import os from datetime import datetime @@ -22,7 +21,6 @@ DATA_DIR.mkdir(exist_ok=True) MINECRAFT_VERSION_MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json" FABRIC_META_URL = "https://meta.fabricmc.net/v2/versions" FORGE_META_URL = "https://files.minecraftforge.net/net/minecraftforge/forge/" -MinecraftVersion=[] # Cache for loaded manifests _manifest_cache: Dict[str, PackMeta] = {} @@ -41,8 +39,9 @@ async def calculate_sha256(file_path: Path) -> str: hash_sha.update(chunk) return hash_sha.hexdigest() -async def get_minecraft_versions() -> List[MinecraftVersion]: +async def get_minecraft_versions(): """Fetch available Minecraft versions from Mojang""" + from models import MinecraftVersion async with aiohttp.ClientSession() as session: async with session.get(MINECRAFT_VERSION_MANIFEST_URL) as response: data = await response.json() @@ -72,21 +71,17 @@ async def get_forge_versions(minecraft_version: str) -> List[str]: async with aiohttp.ClientSession() as session: async with session.get(FORGE_META_URL) as response: # Forge API is more complex, simplified for now - # You might want to use a proper forge API - return [] # Placeholder + return [] async def download_minecraft_version(version: str, target_path: Path) -> bool: """Download Minecraft version JSON and client jar""" - # Get version manifest async with aiohttp.ClientSession() as session: async with session.get(f"https://piston-meta.mojang.com/mc/game/{version}/{version}.json") as response: if response.status == 200: version_data = await response.json() - # Save version JSON async with aiofiles.open(target_path / f"{version}.json", 'w') as f: await f.write(json.dumps(version_data, indent=2)) - # Download client jar downloads = version_data.get("downloads", {}) client_info = downloads.get("client", {}) if client_info: @@ -109,20 +104,22 @@ async def scan_pack(pack_name: str, force_rescan: bool = False) -> PackMeta: meta_path = get_meta_path(pack_name) current_meta: Optional[PackMeta] = None - # Check if we have cached version and force_rescan is False if not force_rescan and pack_name in _manifest_cache: return _manifest_cache[pack_name] if meta_path.exists(): async with aiofiles.open(meta_path, 'r', encoding='utf-8') as f: data = json.loads(await f.read()) - current_meta = PackMeta.model_validate(data) + try: + current_meta = PackMeta.model_validate(data) + except Exception as e: + logger.warning(f"Failed to validate existing meta for pack {pack_name}", error=str(e)) + current_meta = None new_files: Dict[str, FileEntry] = {} changed = False for root, dirs, files in os.walk(pack_path): - # Filter ignored directories ignored = current_meta.ignored_dirs if current_meta else [] dirs[:] = [d for d in dirs if d not in ignored] @@ -130,7 +127,6 @@ async def scan_pack(pack_name: str, force_rescan: bool = False) -> PackMeta: file_path = Path(root) / file rel_path = file_path.relative_to(pack_path).as_posix() - # Skip files in ignored directories if any(ignored_dir in rel_path.split('/') for ignored_dir in ignored): continue @@ -151,27 +147,43 @@ async def scan_pack(pack_name: str, force_rescan: bool = False) -> PackMeta: current_meta.files[rel_path].hash != file_hash): changed = True - # Update manifest if changes detected or new pack if not current_meta or changed or len(new_files) != len(current_meta.files if current_meta else 0): version = (current_meta.version + 1) if current_meta else 1 + + pack_config_path = pack_path / "instance.json" + minecraft_version = "1.20.4" + loader_type = "vanilla" + loader_version = None + + if pack_config_path.exists(): + try: + async with aiofiles.open(pack_config_path, 'r', encoding='utf-8') as f: + config = json.loads(await f.read()) + minecraft_version = config.get("minecraftVersion", minecraft_version) + loader_type = config.get("loaderType", loader_type) + loader_version = config.get("loaderVersion") + except Exception as e: + logger.warning(f"Failed to load instance.json for {pack_name}", error=str(e)) + new_meta = PackMeta( pack_name=pack_name, version=version, files=new_files, updated_at=datetime.utcnow(), - ignored_dirs=current_meta.ignored_dirs if current_meta else [] + ignored_dirs=current_meta.ignored_dirs if current_meta else [], + minecraft_version=minecraft_version, + loader_type=loader_type, + loader_version=loader_version ) async with aiofiles.open(meta_path, 'w', encoding='utf-8') as f: await f.write(new_meta.model_dump_json(indent=2)) - # Update memory cache _manifest_cache[pack_name] = new_meta - logger.info("Pack updated", pack=pack_name, new_version=version, files_count=len(new_files)) + logger.info(f"Pack updated: {pack_name} v{version}, {len(new_files)} files") return new_meta - # Update cache with existing manifest if current_meta: _manifest_cache[pack_name] = current_meta