from enum import Enum from pathlib import Path from typing import Callable, Optional from lupa import LuaRuntime # type: ignore import logging import shutil import textwrap import sys import pre_edited_cs CSVERSION = 5 class CaverException(Exception): pass class CSPlatform(Enum): FREEWARE = "freeware" TWEAKED = "tweaked" def get_path() -> Path: if getattr(sys, "frozen", False): file_dir = Path(getattr(sys, "_MEIPASS")) else: file_dir = Path(__file__).parent.parent return file_dir.joinpath("caver") def patch_files(patch_data: dict, output_dir: Path, platform: CSPlatform, progress_update: Callable[[str, float], None]): progress_update("Copying base files...", -1) ensure_base_files_exist(platform, output_dir) total = len(patch_data["maps"].keys()) + len(patch_data["other_tsc"].keys()) + 3 lua_file = get_path().joinpath("tsc_file.lua").read_text() TscFile = LuaRuntime().execute(lua_file) for i, (mapname, mapdata) in enumerate(patch_data["maps"].items()): progress_update(f"Patching {mapname}...", i/total) patch_map(mapname, mapdata, TscFile, output_dir) for filename, scripts in patch_data["other_tsc"].items(): i += 1 progress_update(f"Patching {filename}.tsc...", i/total) patch_other(filename, scripts, TscFile, output_dir) i += 1 progress_update("Copying MyChar...", i/total) patch_mychar(patch_data["mychar"], output_dir, platform is CSPlatform.TWEAKED) i += 1 progress_update("Copying hash...", i/total) patch_hash(patch_data["hash"], output_dir) i += 1 progress_update("Copying UUID...", i/total) patch_uuid(patch_data["uuid"], output_dir) def ensure_base_files_exist(platform: CSPlatform, output_dir: Path): internal_copy = pre_edited_cs.get_path() version = output_dir.joinpath("data", "Stage", "_version.txt") keep_existing_files = version.exists() and int(version.read_text()) >= CSVERSION def should_ignore(path: str, names: list[str]): base = ["__init__.py", "__pycache__", "ScriptSource", "__pyinstaller"] if keep_existing_files: p = Path(path) base.extend([str(p.joinpath(name)) for name in names if p.joinpath(name).exists() and p.joinpath(name).is_file()]) return base try: shutil.copytree(internal_copy.joinpath(platform.value), output_dir, ignore=should_ignore, dirs_exist_ok=True) shutil.copytree(internal_copy.joinpath("data"), output_dir.joinpath("data"), ignore=should_ignore, dirs_exist_ok=True) except shutil.Error: raise CaverException("Error copying base files. Ensure the directory is not read-only, and that Doukutsu.exe is closed") output_dir.joinpath("data", "Plaintext").mkdir(exist_ok=True) def patch_map(mapname: str, mapdata: dict[str, dict], TscFile, output_dir: Path): mappath = output_dir.joinpath("data", "Stage", f"{mapname}.tsc") tsc_file = TscFile.new(TscFile, mappath.read_bytes(), logging.getLogger("caver")) for event, script in mapdata["pickups"].items(): TscFile.placeScriptAtEvent(tsc_file, script, event, mapname) for event, song in mapdata["music"].items(): TscFile.placeSongAtCue(tsc_file, song["song_id"], event, song["original_id"], mapname) for event, script in mapdata["entrances"].items(): needle = " str: hard_limit = 35 msgbox_limit = 26 if facepic else hard_limit max_lines = max_text_boxes * 3 if max_text_boxes is not None else None lines = textwrap.wrap(text, width=msgbox_limit, max_lines=max_lines) text = "" for i, l in enumerate(lines): text += l if i < len(lines)-1: if i % 3 == 2: text += " str: """ A desperate attempt to generate valid