diff --git a/host/psxrecomp_codegen_host.c b/host/psxrecomp_codegen_host.c index 32fb0a5c..a9741e78 100644 --- a/host/psxrecomp_codegen_host.c +++ b/host/psxrecomp_codegen_host.c @@ -538,6 +538,7 @@ static int resolve_toolchain_bin(char* out, size_t cap) { } static void activate_toolchain_path(void) { + char pack_root[1400]; g_toolchain_bin[0] = '\0'; if (!resolve_toolchain_bin(g_toolchain_bin, sizeof(g_toolchain_bin))) return; @@ -553,6 +554,39 @@ static void activate_toolchain_path(void) { old ? old : ""); setenv("PATH", neu, 1); #endif + /* Pack root (parent of bin/) — Windows cmake-clang-v1 ships zlib here. */ + if (dirname_copy(pack_root, sizeof(pack_root), g_toolchain_bin) && + pack_root[0]) { +#if defined(_WIN32) + _putenv_s("ZLIB_ROOT", pack_root); + { + const char* prev = getenv("CMAKE_PREFIX_PATH"); + char pref[8192]; + if (prev && prev[0] && !strstr(prev, pack_root)) + snprintf(pref, sizeof(pref), "%s;%s", pack_root, prev); + else if (!prev || !prev[0]) + snprintf(pref, sizeof(pref), "%s", pack_root); + else + pref[0] = '\0'; + if (pref[0]) + _putenv_s("CMAKE_PREFIX_PATH", pref); + } +#else + setenv("ZLIB_ROOT", pack_root, 1); + { + const char* prev = getenv("CMAKE_PREFIX_PATH"); + char pref[8192]; + if (prev && prev[0] && !strstr(prev, pack_root)) + snprintf(pref, sizeof(pref), "%s:%s", pack_root, prev); + else if (!prev || !prev[0]) + snprintf(pref, sizeof(pref), "%s", pack_root); + else + pref[0] = '\0'; + if (pref[0]) + setenv("CMAKE_PREFIX_PATH", pref, 1); + } +#endif + } } static int find_cmake(char* out, size_t cap) { @@ -1623,6 +1657,12 @@ static int write_windows_deferred_rebuild_helper(char* err_msg, size_t err_cap) snprintf(err_msg, err_cap, "Failed to form helper path."); return 0; } + /* Setup zips omit build-release/; create it before writing the .cmd. */ + if (!mkdir_p(g_build_dir)) { + snprintf(err_msg, err_cap, "Failed to create build dir: %s", + g_build_dir); + return 0; + } FILE* f = fopen(g_helper_path, "wb"); if (!f) { snprintf(err_msg, err_cap, "Failed to write rebuild helper: %s", diff --git a/psxrecomp_cli.py b/psxrecomp_cli.py index 15820a3a..6c2d9f2e 100755 --- a/psxrecomp_cli.py +++ b/psxrecomp_cli.py @@ -375,7 +375,7 @@ def ensure_framework( if need_seed and (ROOT / "bios").is_dir(): if progress: progress.log( - f"Seeding psxrecomp BIOS profiles from SDK → {fw}" + f"Seeding psxrecomp BIOS profiles from SDK -> {fw}" ) fw.mkdir(parents=True, exist_ok=True) _copy_missing(ROOT / "bios", fw / "bios") @@ -533,7 +533,7 @@ def run_prepare_disc( script = ROOT / "tools" / "prepare_disc.py" if not script.is_file(): raise RuntimeError(f"missing {script}") - progress.phase("prepare_disc", pct=0.15, message="Normalizing disc image…") + progress.phase("prepare_disc", pct=0.15, message="Normalizing disc image...") cmd = [ sys.executable, str(script), @@ -633,7 +633,7 @@ def cmd_generate(args: argparse.Namespace, progress: ProgressReporter) -> int: progress.error(f"BIOS not found: {bios_path}", code=EXIT_USAGE) return EXIT_USAGE dest = fw / "bios" / "SCPH1001.BIN" - progress.phase("bios", pct=0.15, message="Staging retail BIOS dump…") + progress.phase("bios", pct=0.15, message="Staging retail BIOS dump...") try: dest.parent.mkdir(parents=True, exist_ok=True) if dest.resolve() != bios_path.resolve(): @@ -642,7 +642,7 @@ def cmd_generate(args: argparse.Namespace, progress: ProgressReporter) -> int: progress.error(f"failed to stage BIOS: {exc}", code=EXIT_ERROR) return EXIT_ERROR try: - progress.phase("bios", pct=0.2, message="Generating SCPH1001 BIOS C…") + progress.phase("bios", pct=0.2, message="Generating SCPH1001 BIOS C...") regen_bios_profile(project_root, "bios/SCPH1001.toml", progress=progress) staged_retail = True except Exception as exc: # noqa: BLE001 @@ -661,7 +661,7 @@ def cmd_generate(args: argparse.Namespace, progress: ProgressReporter) -> int: ): try: progress.phase( - "bios", pct=0.25, message="Generating OpenBIOS backend C…" + "bios", pct=0.25, message="Generating OpenBIOS backend C..." ) regen_bios_profile(project_root, "bios/OpenBIOS.toml", progress=progress) except Exception as exc: # noqa: BLE001 @@ -682,7 +682,7 @@ def cmd_generate(args: argparse.Namespace, progress: ProgressReporter) -> int: progress.phase( "emit", pct=0.35, - message=f"Running psxrecomp-game → {out_dir}", + message=f"Running psxrecomp-game -> {out_dir}", ) cmd = [str(game_tool), "--config", str(config)] proc = subprocess.run( @@ -1060,7 +1060,7 @@ def cmd_rebuild(args: argparse.Namespace, progress: ProgressReporter) -> int: try: if not pgo_enabled: - progress.phase("build", pct=0.2, message="cmake Release build…") + progress.phase("build", pct=0.2, message="cmake Release build...") _cmake_configure( project_root, build_dir, pgo="", extra=cmake_extra, progress=progress ) @@ -1076,7 +1076,7 @@ def cmd_rebuild(args: argparse.Namespace, progress: ProgressReporter) -> int: progress.phase( "pgo_instrument", pct=0.1, - message="PGO instrumented build…", + message="PGO instrumented build...", ) _cmake_configure( project_root, @@ -1103,7 +1103,7 @@ def cmd_rebuild(args: argparse.Namespace, progress: ProgressReporter) -> int: hide_video=hide_video, progress=progress, ) - progress.phase("pgo_use", pct=0.85, message="PGO optimized rebuild…") + progress.phase("pgo_use", pct=0.85, message="PGO optimized rebuild...") _cmake_configure( project_root, build_dir, @@ -1126,7 +1126,7 @@ def cmd_rebuild(args: argparse.Namespace, progress: ProgressReporter) -> int: prune_raw = (getattr(args, "prune_after", None) or "").strip() if prune_raw: modes = {m.strip() for m in prune_raw.split(",") if m.strip()} - progress.phase("prune", pct=0.97, message="Pruning toolchain / build bulk…") + progress.phase("prune", pct=0.97, message="Pruning toolchain / build bulk...") prune_after_rebuild(project_root, build_dir, modes, progress) progress.phase("done", pct=1.0, message="Rebuild complete") @@ -1314,7 +1314,19 @@ def build_parser() -> argparse.ArgumentParser: return ap +def _configure_stdio() -> None: + """Best-effort: don't crash on Windows cp1252 when logging Unicode.""" + for stream in (sys.stdout, sys.stderr): + reconf = getattr(stream, "reconfigure", None) + if callable(reconf): + try: + reconf(errors="replace") + except Exception: # noqa: BLE001 + pass + + def main() -> int: + _configure_stdio() args = build_parser().parse_args() progress = ProgressReporter(json_progress=bool(getattr(args, "json_progress", False))) try: diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 1f195d2d..98be7c3a 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -488,6 +488,60 @@ if(NOT PSXRECOMP_SKIP_BIOS_STALE_CHECK AND _psxrt_bios_linked) endif() endif() +# zlib for boot_state v4. System packages first; otherwise FetchContent a pinned +# release so portable Windows cmake-clang-v1 setups can configure without MSYS2. +option(PSX_ZLIB_FETCH + "Fetch zlib when no system ZLIB package is found" + ON) +function(psxrecomp_ensure_zlib) + if(TARGET ZLIB::ZLIB) + return() + endif() + find_package(ZLIB QUIET) + if(TARGET ZLIB::ZLIB) + return() + endif() + if(NOT PSX_ZLIB_FETCH) + message(FATAL_ERROR + "ZLIB was not found. On Windows, use a cmake-clang-v1 pack that " + "ships include/zlib.h + lib/libz.a (sets ZLIB_ROOT), or install " + "zlib-devel / mingw-w64-zlib, or configure with -DPSX_ZLIB_FETCH=ON.") + endif() + message(STATUS + "psxrecomp: ZLIB not in CMAKE_PREFIX_PATH/ZLIB_ROOT; " + "fetching zlib 1.3.1 (prefer a toolchain pack that bundles it)") + include(FetchContent) + set(_psx_zlib_timestamp_args "") + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + list(APPEND _psx_zlib_timestamp_args DOWNLOAD_EXTRACT_TIMESTAMP TRUE) + endif() + FetchContent_Declare(psx_zlib + URL + "https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz" + URL_HASH + "SHA256=9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23" + ${_psx_zlib_timestamp_args}) + FetchContent_MakeAvailable(psx_zlib) + if(TARGET ZLIB::ZLIB) + message(STATUS "psxrecomp: ZLIB via FetchContent (ZLIB::ZLIB)") + return() + endif() + # madler/zlib builds zlibstatic even when a shared zlib target exists. + if(TARGET zlibstatic) + add_library(ZLIB::ZLIB ALIAS zlibstatic) + message(STATUS "psxrecomp: ZLIB via FetchContent (zlibstatic)") + return() + endif() + if(TARGET zlib) + add_library(ZLIB::ZLIB ALIAS zlib) + message(STATUS "psxrecomp: ZLIB via FetchContent (zlib)") + return() + endif() + message(FATAL_ERROR + "Fetched zlib but no linkable target was produced " + "(expected zlibstatic or zlib).") +endfunction() + function(psxrecomp_add_runtime_target target) set(options ORACLE COSIM) set(oneValueArgs @@ -732,7 +786,9 @@ function(psxrecomp_add_runtime_target target) endif() # zlib: boot_state v4 savestate compression (RAM/VRAM/SPU blobs). - find_package(ZLIB REQUIRED) + # Portable Windows toolchains (cmake-clang-v1) have no system zlib — + # fetch a pinned release when find_package fails (same pattern as SDL3). + psxrecomp_ensure_zlib() target_link_libraries(${target} PRIVATE ZLIB::ZLIB) # Build identity: stamp the psxrecomp commit into the binary so a crash report diff --git a/tools/prepare_disc.py b/tools/prepare_disc.py index 9aede8ef..92ca89c6 100644 --- a/tools/prepare_disc.py +++ b/tools/prepare_disc.py @@ -33,6 +33,17 @@ USER_OFF = 24 SYNC = bytes([0x00] + [0xFF] * 10 + [0x00]) +def _configure_stdio() -> None: + """Avoid UnicodeEncodeError on Windows consoles (cp1252 / Store Python).""" + for stream in (sys.stdout, sys.stderr): + reconf = getattr(stream, "reconfigure", None) + if callable(reconf): + try: + reconf(errors="replace") + except Exception: # noqa: BLE001 — best-effort stdio harden + pass + + def _build_edc_table() -> list[int]: table = [] for i in range(256): @@ -388,7 +399,7 @@ def raw2448_to_bin(src: Path) -> bytes: raise SystemExit(f"truncated 2448 sector {n}: got {len(sec)} bytes") out += sec[:DST_SEC] n += 1 - print(f" trimmed {n} sectors 2448 → 2352") + print(f" trimmed {n} sectors 2448 -> 2352") return bytes(out) @@ -408,8 +419,9 @@ def matches_known(cfg: PrepareConfig, size: int, md5: str, sha1: str) -> bool: def main() -> int: + _configure_stdio() here = Path(__file__).resolve().parent - # tools/ → psxrecomp/ → often game root is parent of psxrecomp + # tools/ -> psxrecomp/ -> often game root is parent of psxrecomp fw_root = here.parent ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("source", nargs="?", default="", help="path to .iso / .cue / .bin") @@ -489,7 +501,7 @@ def main() -> int: if kind == "cue": src = resolve_cue_bin(src) src_md5, src_sha1, src_size = file_hashes(src) - print(f"cue → bin: {src}") + print(f"cue -> bin: {src}") print(f" size {src_size}") print(f" md5 {src_md5}") print(f" sha1 {src_sha1}") @@ -512,7 +524,7 @@ def main() -> int: "verifying boot EXE only." ) elif not cfg.known and not cfg.skip_hash_check and kind == "bin2352": - print(" no prepare_disc.known_* configured — verifying boot EXE only") + print(" no prepare_disc.known_* configured - verifying boot EXE only") if kind == "bin2352": bin_data = src.read_bytes() @@ -520,11 +532,11 @@ def main() -> int: elif kind == "iso": iso_data = src.read_bytes() entries, files = extract_via(read_user_iso, iso_data, cfg.boot_exe) - print(f" converting {len(iso_data) // USER} sectors ISO → MODE2/2352…") + print(f" converting {len(iso_data) // USER} sectors ISO -> MODE2/2352...") bin_data = iso_to_bin(iso_data) extract_via(read_user_bin, bin_data, cfg.boot_exe) elif kind == "raw2448": - print(" converting 2448 → 2352…") + print(" converting 2448 -> 2352...") bin_data = raw2448_to_bin(src) entries, files = extract_via(read_user_bin, bin_data, cfg.boot_exe) else: @@ -563,7 +575,7 @@ def main() -> int: print(f" md5 {out_md5}") print(f" sha1 {out_sha1}") if kind == "iso": - print(" rebuilt from ISO (EDC set; ECC zeroed — fine for software readers)") + print(" rebuilt from ISO (EDC set; ECC zeroed - fine for software readers)") elif kind == "raw2448": print(" trimmed from 2448-byte/sector dump") diff --git a/tools/toolchain_pack.py b/tools/toolchain_pack.py index d91e5724..ff13ee40 100644 --- a/tools/toolchain_pack.py +++ b/tools/toolchain_pack.py @@ -231,10 +231,19 @@ def resolve_toolchain_bin(project_root: Optional[Path] = None) -> Optional[Path] def activate_toolchain_bin(bin_dir: Path, log=None) -> None: prefix = str(bin_dir) + pack_root = bin_dir.parent cur = os.environ.get("PATH", "") parts = cur.split(os.pathsep) if cur else [] if not parts or Path(parts[0]) != bin_dir: os.environ["PATH"] = prefix + (os.pathsep + cur if cur else "") + # Windows cmake-clang-v1 ships zlib under the pack root; help FindZLIB. + pack_s = str(pack_root) + os.environ["ZLIB_ROOT"] = pack_s + prev_prefix = os.environ.get("CMAKE_PREFIX_PATH", "") + if not prev_prefix: + os.environ["CMAKE_PREFIX_PATH"] = pack_s + elif pack_s not in prev_prefix.split(os.pathsep): + os.environ["CMAKE_PREFIX_PATH"] = pack_s + os.pathsep + prev_prefix for name, env_key in ( ("clang", "CC"), ("clang++", "CXX"), @@ -306,7 +315,7 @@ def download_latest_pack( token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") or "" url = f"https://github.com/{repo}/releases/latest/download/{asset}" if log: - log(f"Downloading {asset} from {repo}…") + log(f"Downloading {asset} from {repo}...") with tempfile.TemporaryDirectory(prefix="psxrecomp-tc-") as tmp: zpath = Path(tmp) / asset try: