From 21b7cd9be59bb8ad70ca5a968d5dfde5d25c9a1a Mon Sep 17 00:00:00 2001 From: MHunterG <48920434+MHunterG@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:08:46 +0300 Subject: [PATCH 1/2] Let the macOS release script pack more than a game's own files release_macos.sh packed exactly one executable and the files of the game's repository. It now also takes, all optional and off by default: - --packaging DIR: release.env, the notices and macos/ from another directory than /packaging, such as the desktop app's or a downstream build's own; - --license NAME=FILE, --notices FILE and --extra PATH (repeatable): a licence and a notices file for a component the build added, and files beside the app in the disk image and the zip; - in release.env, RELEASE_EXTRA_EXECUTABLES (more programs beside the executable, signed with it), RELEASE_RESOURCES (folders of the build's bin/ for Contents/Resources) and RELEASE_PROFILE_DIR (where the profile whose game/ a developer build falls back to lives). A missing icon is now a note instead of an error, and a checkout that is PortableKit itself does not ship its licence twice. A game's release is packed as before. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/RELEASING.md | 4 +++ scripts/release_macos.sh | 78 ++++++++++++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 0907fca..bbe9ab7 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -15,6 +15,8 @@ In its own repository: | `packaging/icon.png` (256×256), `packaging/linux/README.txt`, `packaging/linux/.desktop`, `packaging/linux/.metainfo.xml` | The Linux icon, read-me, desktop entry and AppStream data (`@VERSION@`, `@DATE@` are filled in) | | `packaging/android/res/` | For Android, the name and launcher icons; see `packaging/android/README.md` | +`release.env` may also set, for a program with more than one executable file: `RELEASE_EXTRA_EXECUTABLES` (programs of the build's `bin/` to put beside the executable), `RELEASE_RESOURCES` (folders of `bin/` to ship in `Contents/Resources`, as `=`) and `RELEASE_PROFILE_DIR` (where the profile is in the checkout, `.` by default). The desktop app uses all three. + ## macOS On an Apple Silicon Mac with the macOS 13 SDK installed (the deployment target in `packaging/sources.sh`): @@ -27,6 +29,8 @@ cmake --build out-release --target It builds SDL3 and the Vulkan loader from pinned sources for the deployment target, takes MoltenVK's pinned release, assembles `.app`, links everything inside the bundle, checks the system imports against the macOS 13 SDK, signs ad hoc (no notarization) and packs a disk image into `out/release-macos/dist`, with the FFmpeg source archive and `SHA256SUMS`. +`--packaging DIR` reads `release.env`, the notices and `macos/` from another directory than `/packaging`. `--license NAME=FILE`, `--notices FILE` and `--extra PATH` (each repeatable) add what a build brings beyond the game and the framework: a licence as `licenses/NAME-LICENSE.txt`, a notices file in `licenses/`, and files or folders beside the app in the disk image and the zip. + ## Linux `scripts/release_linux.sh --game ` builds inside the Steam Runtime 3 "sniper" SDK container (podman or docker, x86-64) from the game's generated code, and packs a tarball and a Flatpak bundle. It needs flatpak with Flathub, ostree and curl. diff --git a/scripts/release_macos.sh b/scripts/release_macos.sh index 011942d..133aa15 100644 --- a/scripts/release_macos.sh +++ b/scripts/release_macos.sh @@ -2,14 +2,18 @@ # Package a PortableKit game's macOS release (Apple Silicon) from a finished # build directory: .app in a disk image, and optionally in a zip archive. # -# release_macos.sh [--game DIR] [--version VERSION] [--executable FILE] -# [--jobs N] [--zip] [--no-dmg] BUILD_DIR +# release_macos.sh [--game DIR] [--packaging DIR] [--version VERSION] +# [--executable FILE] [--jobs N] [--zip] [--no-dmg] +# [--license NAME=FILE]... [--notices FILE]... [--extra PATH]... +# BUILD_DIR # # The game's repository (--game, by default the checkout the current # directory is in) describes its release in packaging/release.env (see # docs/RELEASING.md) and brings its own notices, icon and read-me: # packaging/THIRD_PARTY_NOTICES.md, packaging/macos/.icns and -# packaging/macos/README.txt. +# packaging/macos/README.txt. --packaging names another directory holding +# them, such as apps/portablekit/packaging for the desktop app, or a +# downstream build's own. # # BUILD_DIR is a build of that checkout configured with # -DPORTABLEKIT_RELEASE=ON whose executable and overlay libraries are built @@ -34,6 +38,8 @@ # # --game DIR the game's checkout (default: the one around the # current directory) +# --packaging DIR release.env, the notices and macos/ (default: +# /packaging) # --version VERSION name the artifacts after VERSION instead of git describe # --executable FILE package FILE, built with -DPORTABLEKIT_RELEASE=ON, # instead of BUILD_DIR/bin/ (for a build @@ -41,6 +47,11 @@ # --jobs N parallel compile jobs for SDL3 and the loader (default 4) # --zip also pack .app as a zip archive # --no-dmg do not build the disk image +# --license NAME=FILE also ship FILE as licenses/NAME-LICENSE.txt, for a +# component the build added (repeatable) +# --notices FILE also ship FILE in licenses/ as it is named (repeatable) +# --extra PATH also put PATH (a file or a folder) beside the app in +# the disk image and the zip (repeatable) set -euo pipefail # Byte-wise text tools: the checks read binary files. export LC_ALL=C @@ -51,6 +62,10 @@ kit_dir="$(cd "$(dirname "$0")/.." && pwd)" source "$kit_dir/packaging/sources.sh" repo_dir="" +packaging_dir="" +extra_licenses=() +extra_notices=() +extra_paths=() version="" executable="" jobs=4 @@ -60,6 +75,10 @@ build_dir="" while [[ $# -gt 0 ]]; do case "$1" in --game) repo_dir="${2:?--game needs a value}"; shift 2 ;; + --packaging) packaging_dir="${2:?--packaging needs a value}"; shift 2 ;; + --license) extra_licenses+=("${2:?--license needs NAME=FILE}"); shift 2 ;; + --notices) extra_notices+=("${2:?--notices needs a file}"); shift 2 ;; + --extra) extra_paths+=("${2:?--extra needs a path}"); shift 2 ;; --version) version="${2:?--version needs a value}"; shift 2 ;; --executable) executable="${2:?--executable needs a value}"; shift 2 ;; --jobs) jobs="${2:?--jobs needs a value}"; shift 2 ;; @@ -74,13 +93,22 @@ done build_dir="$(cd "$build_dir" && pwd)" [[ -n "$repo_dir" ]] || repo_dir="$(git rev-parse --show-toplevel)" repo_dir="$(cd "$repo_dir" && pwd)" -[[ -f "$repo_dir/packaging/release.env" ]] || { echo "error: no packaging/release.env in $repo_dir" >&2; exit 1; } +[[ -n "$packaging_dir" ]] || packaging_dir="$repo_dir/packaging" +packaging_dir="$(cd "$packaging_dir" && pwd)" +[[ -f "$packaging_dir/release.env" ]] || { echo "error: no release.env in $packaging_dir" >&2; exit 1; } # shellcheck disable=SC1091 -source "$repo_dir/packaging/release.env" +source "$packaging_dir/release.env" : "${RELEASE_NAME:?release.env sets RELEASE_NAME}" "${RELEASE_SLUG:?release.env sets RELEASE_SLUG}" : "${RELEASE_TARGET:?release.env sets RELEASE_TARGET}" "${RELEASE_APP_ID:?release.env sets RELEASE_APP_ID}" RELEASE_OVERLAYS="${RELEASE_OVERLAYS:-0}" -packaging="$repo_dir/packaging/macos" +# Optional: more programs from BUILD_DIR/bin to put beside the executable, and +# folders of BUILD_DIR/bin to ship in Contents/Resources as =. +RELEASE_EXTRA_EXECUTABLES="${RELEASE_EXTRA_EXECUTABLES:-}" +RELEASE_RESOURCES="${RELEASE_RESOURCES:-}" +# Where the profile lives in the checkout, whose game/ a developer build +# falls back to. +RELEASE_PROFILE_DIR="${RELEASE_PROFILE_DIR:-.}" +packaging="$packaging_dir/macos" work="${PORTABLEKIT_RELEASE_WORK:-$repo_dir/out/release-macos}" sources="$work/sources" @@ -132,7 +160,7 @@ FFMPEG_FLAGS="$(sed -n '/^set(PORTABLEKIT_FFMPEG_CONFIGURE_FLAGS/,/)/p' "$ffmpeg [[ -n "$FFMPEG_VERSION" && -n "$FFMPEG_FLAGS" ]] || fail "cannot read the FFmpeg pins from $ffmpeg_cmake" # The notices must describe exactly what is bundled. -notices="$repo_dir/packaging/THIRD_PARTY_NOTICES.md" +notices="$packaging_dir/THIRD_PARTY_NOTICES.md" for pinned in "SDL3 $SDL3_VERSION" "FFmpeg $FFMPEG_VERSION" "$FFMPEG_URL" "$SDL3_URL" \ "./configure --prefix= $FFMPEG_FLAGS" \ "MoltenVK $MOLTENVK_VERSION" "$MOLTENVK_URL" "$MOLTENVK_SHA256" \ @@ -226,9 +254,20 @@ fi lipo -archs "$executable" | grep -qx arm64 || fail "$executable is not an arm64 executable" # A developer build falls back to its checkout's game directory; a release # must not depend on the machine it was built on. -if strings -a "$executable" | grep -qF "$repo_dir/game"; then +if strings -a "$executable" | grep -qF "$(cd "$repo_dir/$RELEASE_PROFILE_DIR" && pwd)/game"; then fail "$executable is a developer build; configure with -DPORTABLEKIT_RELEASE=ON, build $RELEASE_TARGET and pass it with --executable" fi +for program in $RELEASE_EXTRA_EXECUTABLES; do + [[ -x "$build_dir/bin/$program" ]] || fail "no $program in $build_dir/bin" +done +for resource in $RELEASE_RESOURCES; do + [[ -d "$build_dir/bin/${resource%%=*}" ]] || fail "no ${resource%%=*} in $build_dir/bin" +done +for entry in ${extra_licenses[@]+"${extra_licenses[@]}"}; do + [[ "$entry" == *=* && -f "${entry#*=}" ]] || fail "--license $entry: expected NAME=FILE with an existing file" +done +for file in ${extra_notices[@]+"${extra_notices[@]}"}; do [[ -f "$file" ]] || fail "--notices $file: no such file"; done +for path in ${extra_paths[@]+"${extra_paths[@]}"}; do [[ -e "$path" ]] || fail "--extra $path: no such file or folder"; done overlay_count=0 [[ -d "$build_dir/bin/overlays" ]] && overlay_count="$(find "$build_dir/bin/overlays" -name '*.dylib' | wc -l | tr -d ' ')" [[ "$overlay_count" -eq "$RELEASE_OVERLAYS" ]] || @@ -323,6 +362,13 @@ frameworks="$contents/Frameworks" resources="$contents/Resources" install -m 755 "$executable" "$contents/MacOS/$RELEASE_NAME" +for program in $RELEASE_EXTRA_EXECUTABLES; do + install -m 755 "$build_dir/bin/$program" "$contents/MacOS/$program" +done +for resource in $RELEASE_RESOURCES; do + mkdir -p "$resources/${resource#*=}" + ditto "$build_dir/bin/${resource%%=*}" "$resources/${resource#*=}" +done [[ "$overlay_count" -eq 0 ]] || cp "$build_dir/bin/overlays/"*.dylib "$frameworks/overlays/" for lib in libavcodec.61.dylib libavutil.59.dylib; do cp "$build_dir/bin/lib/$lib" "$frameworks/" @@ -356,11 +402,15 @@ sed -e "s/@VERSION@/$version/g" -e "s/@SHORT_VERSION@/$short_version/" \ "$kit_dir/packaging/macos/Info.plist.in" > "$contents/Info.plist" plutil -lint "$contents/Info.plist" > /dev/null printf 'APPL????' > "$contents/PkgInfo" -cp "$packaging/$RELEASE_NAME.icns" "$resources/$RELEASE_NAME.icns" +if [[ -f "$packaging/$RELEASE_NAME.icns" ]]; then + cp "$packaging/$RELEASE_NAME.icns" "$resources/$RELEASE_NAME.icns" +else + echo "note: no $packaging/$RELEASE_NAME.icns; the app gets the system's default icon" +fi cp "$sources/NotoSansCJKjp-Regular.otf" "$resources/fonts/" licenses="$resources/licenses" -cp "$repo_dir/LICENSE" "$licenses/$RELEASE_NAME-LICENSE.txt" +[[ "$repo_dir" -ef "$kit_dir" ]] || cp "$repo_dir/LICENSE" "$licenses/$RELEASE_NAME-LICENSE.txt" cp "$kit_dir/LICENSE" "$licenses/PortableKit-LICENSE.txt" cp "$notices" "$licenses/THIRD_PARTY_NOTICES.md" cp "$deps_build/SDL3-$SDL3_VERSION/LICENSE.txt" "$licenses/SDL3-LICENSE.txt" @@ -371,6 +421,10 @@ cp "$kit_dir/third_party/imgui/LICENSE.txt" "$licenses/DearImGui-LICENSE.txt" cp "$kit_dir/third_party/tiny_aes/UNLICENSE" "$licenses/tiny-AES-c-UNLICENSE.txt" cp "$kit_dir/third_party/xxhash/LICENSE" "$licenses/xxHash-LICENSE.txt" cp "$sources/NotoSansCJK-LICENSE.txt" "$licenses/NotoSansCJK-OFL.txt" +for entry in ${extra_licenses[@]+"${extra_licenses[@]}"}; do + cp "${entry#*=}" "$licenses/${entry%%=*}-LICENSE.txt" +done +for file in ${extra_notices[@]+"${extra_notices[@]}"}; do cp "$file" "$licenses/"; done # --------------------------------------------------------------------------- step "Linking inside the bundle" @@ -396,6 +450,7 @@ done # Symbols the executable exports to the overlay libraries stay; only local # symbols and debug information go. strip -x "$exe" "$frameworks/"*.dylib 2> /dev/null +for program in $RELEASE_EXTRA_EXECUTABLES; do strip -x "$contents/MacOS/$program" 2> /dev/null; done [[ "$overlay_count" -eq 0 ]] || strip -x "$frameworks/overlays/"*.dylib 2> /dev/null # --------------------------------------------------------------------------- @@ -429,6 +484,7 @@ step "Signing (ad hoc)" # No hardened runtime: it would refuse to load the ad hoc signed overlays. sign() { codesign --force --sign - --timestamp=none "$@" 2>&1 | { grep -v ': replacing existing signature$' || true; }; } while IFS= read -r -d '' lib; do sign "$lib"; done < <(find "$frameworks" -name '*.dylib' -print0) +for program in $RELEASE_EXTRA_EXECUTABLES; do sign "$contents/MacOS/$program"; done sign "$app" step "Checking the bundle" @@ -464,6 +520,7 @@ if [[ $make_zip -eq 1 ]]; then mkdir -p "$zip_root" ditto "$app" "$zip_root/$RELEASE_NAME.app" cp "$readme" "$zip_root/" + for path in ${extra_paths[@]+"${extra_paths[@]}"}; do ditto "$path" "$zip_root/$(basename "$path")"; done (cd "$work/zip" && ditto -c -k --sequesterRsrc --keepParent "$name" "$dist/$name.zip") rm -rf "$work/zip" @@ -483,6 +540,7 @@ if [[ $make_dmg -eq 1 ]]; then mkdir -p "$dmg_root" ditto "$app" "$dmg_root/$RELEASE_NAME.app" cp "$readme" "$dmg_root/" + for path in ${extra_paths[@]+"${extra_paths[@]}"}; do ditto "$path" "$dmg_root/$(basename "$path")"; done ln -s /Applications "$dmg_root/Applications" hdiutil create -quiet -volname "$RELEASE_NAME $version" -srcfolder "$dmg_root" -fs APFS \ -format ULMO -ov "$dist/$name.dmg" From b5fead40ba24a5a4710f2dc2d526903ecd529774 Mon Sep 17 00:00:00 2001 From: MHunterG <48920434+MHunterG@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:19:20 +0300 Subject: [PATCH 2/2] Package the desktop app for macOS and Windows The desktop app needs more than one executable in its release: the recompiler beside the program, the headers the generated code includes, and on Windows the compiler it builds games with. - apps/portablekit/packaging: the app's release.env, third-party notices for both systems and a read-me for each. - macOS: scripts/release_macos.sh --game . --packaging apps/portablekit/packaging makes PortableKit.app (psp_recomp in Contents/MacOS, the headers in Contents/Resources/include, where the program looks inside a bundle), a disk image and a zip. - Windows: scripts/package_desktop_windows.ps1 assembles the portable folder from an llvm-mingw build: the program, the recompiler and their DLLs, the headers, an empty data folder and the part of llvm-mingw that compiling for x86_64 needs (clang's x86_64 runtime only). It checks for game data and paths of the build machine, starts the packed program, links a probe library with the packed toolchain, and writes the zip, SHA256SUMS and BUILDINFO.txt. - Both take a downstream build's extra licences, notices and files (--license/-License, --notices/-Notices, --extra/-Extra) and another release description (--packaging/-Packaging), so such a build reuses the scripts instead of copying them. - docs/RELEASING.md, "The desktop app". Co-Authored-By: Claude Opus 5.5 (1M context) --- .../packaging/THIRD_PARTY_NOTICES.md | 99 ++++++ apps/portablekit/packaging/macos/README.txt | 60 ++++ apps/portablekit/packaging/release.env | 20 ++ apps/portablekit/packaging/windows/README.txt | 69 ++++ docs/RELEASING.md | 26 ++ scripts/package_desktop_windows.ps1 | 323 ++++++++++++++++++ 6 files changed, 597 insertions(+) create mode 100644 apps/portablekit/packaging/THIRD_PARTY_NOTICES.md create mode 100644 apps/portablekit/packaging/macos/README.txt create mode 100644 apps/portablekit/packaging/release.env create mode 100644 apps/portablekit/packaging/windows/README.txt create mode 100644 scripts/package_desktop_windows.ps1 diff --git a/apps/portablekit/packaging/THIRD_PARTY_NOTICES.md b/apps/portablekit/packaging/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..869ac76 --- /dev/null +++ b/apps/portablekit/packaging/THIRD_PARTY_NOTICES.md @@ -0,0 +1,99 @@ +# Third-party notices + +PortableKit is distributed under the MIT License (`PortableKit-LICENSE.txt`). Its released builds also contain the third-party software listed here, each under its own license. The license texts are in the same directory as this file: `PortableKit.app/Contents/Resources/licenses/` on macOS, `licenses\` in the Windows folder. + +PortableKit does not include any game, game assets or keys. You provide the disc images of games you own; what it makes from them (the prepared executable, the recompiled code and the libraries compiled from it) stays on your computer and is for your own use only. + +A build that also contains other components, such as HLE extension modules (see PortableKit's `docs/HLE_EXTENSIONS.md`), lists them and ships their licenses beside this file, and is distributed under the terms that combination requires. + +## Compiled into the program + +### Dear ImGui 1.92.9b + +The library window, the in-game menu and the setup screens. Copyright (c) 2014-2026 Omar Cornut. MIT License: `DearImGui-LICENSE.txt`. + +### tiny-AES-c + +AES for preparing the executable from the disc image and for save data. Commit `23856752fbd139da0b8ca6e471a13d5bcc99a08d`, released into the public domain under the Unlicense: `tiny-AES-c-UNLICENSE.txt`. + +### stb_truetype 1.26 + +Draws the game's text. Copyright (c) 2017 Sean Barrett. Used under the public-domain dedication it offers as an alternative to the MIT License: + +> This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. + + + +### stb_image 2.30 and stb_image_write 1.16 + +Read and write the PNG images of HD texture packs. Copyright (c) 2017 Sean Barrett. Used under the same public-domain dedication as stb_truetype above. + +### xxHash 0.8.3 + +Hashes textures for HD texture packs. Copyright (c) 2012-2021 Yann Collet. BSD 2-Clause License: `xxHash-LICENSE.txt`. + +## Shipped as separate libraries + +These are dynamically linked shared libraries: in `PortableKit.app/Contents/Frameworks/` on macOS, next to `portablekit.exe` on Windows. They are unmodified builds of the upstream releases below. You may replace them with your own builds of the same or a compatible version. + +### SDL3 3.4.16 + +Window, input and audio output. Copyright (C) 1997-2026 Sam Lantinga. zlib License: `SDL3-LICENSE.txt`. + +- Library: `libSDL3.0.dylib` on macOS, `SDL3.dll` on Windows (the unmodified `SDL3-devel-3.4.16-mingw.zip` release build, ) +- Source: (SHA-256 `7322236cd12090c3eb40b9728be4d49c76f66ad17d04369584d4ecad5cf77c68`) + +### FFmpeg 7.1.5 + +Decodes games' ATRAC3 and ATRAC3plus audio and their H.264 movies. FFmpeg is licensed under the GNU Lesser General Public License, version 2.1 or later: `FFmpeg-COPYING.LGPLv2.1.txt`, with the source and configuration also noted in `FFmpeg-SOURCE.txt`. This build contains only FFmpeg's LGPL parts and nothing non-free. + +- Libraries on macOS: `libavcodec.61.dylib` and `libavutil.59.dylib` +- Source: (SHA-256 `de668509caf9e35e3cd162473441fdb29538c6d96ed080292b3cf9e6fc5d558f`), not modified +- Configured with: + + ```text + ./configure --prefix= --enable-shared --disable-static --disable-programs --disable-doc --disable-avdevice --disable-avformat --disable-avfilter --disable-swscale --disable-swresample --disable-network --disable-autodetect --disable-everything --enable-decoder=atrac3,atrac3p,h264 --disable-x86asm --disable-debug + ``` + +- On macOS also with `--install-name-dir=@rpath`, which only sets where the libraries expect each other. +- Built by the build itself, `cmake/FFmpeg.cmake` in PortableKit (), which pins the version, checksum and configuration above. + +**Source offer.** The exact FFmpeg source archive above is published on the same release page as every PortableKit build that contains it. For at least three years after we distribute a build, we will also provide that source to anyone who asks through the project's issue tracker, , at no more than the cost of providing it. + +### Vulkan loader 1.4.357 (macOS only) + +Finds and loads the Vulkan driver, MoltenVK. Copyright (c) 2015-2026 The Khronos Group Inc., Valve Corporation and LunarG, Inc. Apache License 2.0: `Vulkan-Loader-LICENSE.txt`. Built for macOS 13 and Apple Silicon, with its Vulkan-Headers of the same tag. + +- Library: `libvulkan.1.dylib` +- Source: (SHA-256 `54f2537df22313768da0317dda2abdaaab7711b4081c48c869a79db343d0ae70`) + +### MoltenVK 1.4.2 (macOS only) + +Vulkan on Apple's Metal. Copyright (c) 2015-2026 The Brenwill Workshop Ltd. Apache License 2.0: `MoltenVK-LICENSE.txt`. The Khronos release build; only its Apple Silicon (arm64) part is kept. + +- Library: `libMoltenVK.dylib`, with its driver manifest in `PortableKit.app/Contents/Resources/vulkan/icd.d/` +- Release: (SHA-256 `f95765a6229cb7b915990a2890ce12ebe36a730b021545d3d52ae69ce4c4024e`); source: + +## Font + +### Noto Sans CJK JP (Noto CJK Sans 2.004) + +A fallback for text on systems without a CJK font: `PortableKit.app/Contents/Resources/fonts/NotoSansCJKjp-Regular.otf` (macOS only). Copyright 2014-2021 Adobe (), with Reserved Font Name 'Source'. SIL Open Font License, version 1.1: `NotoSansCJK-OFL.txt`. + +## Windows only + +### FFmpeg 7.1.5, prebuilt (Windows) + +The Windows build uses the unmodified prebuilt LGPL shared build of FFmpeg 7.1.5 (commit `7d0e842004`) from BtbN/FFmpeg-Builds, built from FFmpeg's LGPL parts only and with `--enable-version3`, so it is licensed under the GNU Lesser General Public License, version 3 or later: `FFmpeg-LICENSE.txt`, with the source location in `FFmpeg-SOURCE.txt`. You may replace these libraries with your own builds of a compatible version. + +- Libraries: `avcodec-61.dll`, `avutil-59.dll` and `swresample-5.dll` +- Release: (SHA-256 `03a8003e245c08df4277d7b0adc50b93a97ddd4a3aaafea21943c4384df59895`), pinned in `cmake/FFmpeg.cmake` +- Source: + +### llvm-mingw 20260922 + +The compiler PortableKit compiles games with on the player's computer, in `toolchain\`, and the C++ runtime the program itself uses (`libc++.dll`, `libunwind.dll`). An unmodified subset of the llvm-mingw release `llvm-mingw-20260922-ucrt-x86_64.zip` (): clang, lld, the LLVM and clang libraries, the headers and the x86_64 runtime libraries. + +- LLVM, clang, lld, libc++, libc++abi and libunwind: Apache License 2.0 with LLVM Exceptions, `llvm-mingw-LICENSE.txt` (also `toolchain\LICENSE.TXT`). Source: +- mingw-w64 headers, runtime and winpthreads: their own permissive licenses and public-domain dedications, `mingw-w64-COPYING*.txt`. Source: +- The llvm-mingw build scripts: diff --git a/apps/portablekit/packaging/macos/README.txt b/apps/portablekit/packaging/macos/README.txt new file mode 100644 index 0000000..0b1e7e0 --- /dev/null +++ b/apps/portablekit/packaging/macos/README.txt @@ -0,0 +1,60 @@ +PortableKit for macOS (Apple Silicon) +===================================== + +PortableKit plays PSP games you own by recompiling them on this Mac. It ships +no game and no keys. + +Needs: a Mac with Apple Silicon (M1 or newer) and macOS @MINIMUM_SYSTEM_VERSION@ or newer. +Compiling games needs Apple's free Command Line Tools; PortableKit says how +to install them when they are missing, and plays games under its interpreter +(slower) until then. + + +Install +------- + +Drag PortableKit into a folder you can write to, such as Applications or a +folder of its own. Everything it writes goes to "PortableKit Data" next to +PortableKit.app: move or delete both and nothing is left behind. + + +First start +----------- + +This build is not notarized by Apple, so the first time you open it macOS +says that it cannot verify the developer and offers only Done or Move to +Trash. Choose Done, then: + + 1. Open System Settings > Privacy & Security. + 2. Scroll down to the message about PortableKit and click "Open Anyway". + 3. Confirm with your password or Touch ID, then "Open Anyway" once more. + +Or, from Terminal: + + xattr -dr com.apple.quarantine /Applications/PortableKit.app + +The library window then opens. "Add a game..." takes the .iso of your own +PSP game disc; Play starts it under the interpreter at once and compiles it +in the background. Esc in the game opens the menu. + + +Keys (optional) +--------------- + +Without keys, a game can be added when its disc carries an unencrypted +executable (BOOT.BIN; many do), and saves are kept unencrypted. The keys +file's format is described in docs/DESKTOP_APP.md of the PortableKit +repository. This program contains no keys and does not say where to get them. + + +Command line +------------ + + PortableKit.app/Contents/MacOS/PortableKit help + +lists the commands (add, list, compile, run, export, keys, version). + + +Report a problem: https://github.com/TeamGDB/PortableKit/issues (never attach +game files). Third-party software and its licenses: +PortableKit.app/Contents/Resources/licenses/ diff --git a/apps/portablekit/packaging/release.env b/apps/portablekit/packaging/release.env new file mode 100644 index 0000000..3d80c4a --- /dev/null +++ b/apps/portablekit/packaging/release.env @@ -0,0 +1,20 @@ +# The desktop app's release, for PortableKit's release scripts: +# scripts/release_macos.sh --game . --packaging apps/portablekit/packaging +# scripts/package_desktop_windows.ps1 -BuildDir -Toolchain +# (see docs/RELEASING.md, "The desktop app"). +RELEASE_NAME=PortableKit +RELEASE_SLUG=portablekit +RELEASE_TARGET=portablekit +RELEASE_APP_ID=io.github.teamgdb.PortableKit +RELEASE_SUMMARY="Play the PSP games you own by recompiling them on your computer" +RELEASE_URL=https://github.com/TeamGDB/PortableKit +RELEASE_OVERLAYS=0 +# The profile directory, whose game/ a developer build falls back to. +RELEASE_PROFILE_DIR=apps/portablekit +# The recompiler runs beside the program, and the headers the code it makes +# includes go in Contents/Resources/include, where the program looks for them. +RELEASE_EXTRA_EXECUTABLES=psp_recomp +RELEASE_RESOURCES="share/portablekit/include=include" +# The app knows no game, so no name of one is listed; the scripts' own checks +# for disc images, executables and saves still apply. +RELEASE_GAME_NAMES="" diff --git a/apps/portablekit/packaging/windows/README.txt b/apps/portablekit/packaging/windows/README.txt new file mode 100644 index 0000000..32f3e06 --- /dev/null +++ b/apps/portablekit/packaging/windows/README.txt @@ -0,0 +1,69 @@ +PortableKit for Windows x64 +=========================== + +PortableKit plays PSP games you own by recompiling them on this PC. It ships +no game and no keys. Everything it writes stays in the "data" folder next to +portablekit.exe: move or delete this whole folder and nothing is left behind. +Keep it in a folder you can write to (not Program Files). + + +Start +----- + +Double-click portablekit.exe. The library window opens. + + Add a game... choose the .iso of your own PSP game disc (or drop it on + the window). + Play starts the game at once under the interpreter (slow) and + compiles it in the background; the running game switches to + compiled code when that is done (first a quick build, then + an optimised one). Esc in the game opens the menu. + +The compiler it uses is in the "toolchain" folder (LLVM/Clang, llvm-mingw); +nothing needs installing. + + +Keys (optional) +--------------- + +Without keys, a game can be added when its disc carries an unencrypted +executable (BOOT.BIN; many do), and saves are kept unencrypted. With keys you +provide yourself, encrypted executables can be decrypted and saves are +written in the PSP's own format. Import a keys file with + + portablekit keys import + +Its format is described in docs/DESKTOP_APP.md of the PortableKit +repository. This program contains no keys and does not say where to get them. + + +Command line +------------ + + portablekit add [--executable ] + portablekit list | info | status + portablekit compile [--opt 0|2|tiered] + portablekit run [--interpreter] + portablekit export [--source-only | --library-only] + portablekit keys import | keys status + portablekit cache clear + portablekit toolchain + portablekit version + +Add --json for machine-readable output, --data-dir for another data +folder. + + +Your data +--------- + + data\games\\ the game's executable prepared from your disc, + settings, saves (ms0) + data\cache\\ the recompiled and compiled game code, made on this PC + +The compiled code and anything "export" writes are made from your copy of the +game and are for your own use only. Never share them. + + +Report a problem: https://github.com/TeamGDB/PortableKit/issues (never attach +game files). Third-party software and its licenses: licenses\ diff --git a/docs/RELEASING.md b/docs/RELEASING.md index bbe9ab7..1a9ec48 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -31,6 +31,32 @@ It builds SDL3 and the Vulkan loader from pinned sources for the deployment targ `--packaging DIR` reads `release.env`, the notices and `macos/` from another directory than `/packaging`. `--license NAME=FILE`, `--notices FILE` and `--extra PATH` (each repeatable) add what a build brings beyond the game and the framework: a licence as `licenses/NAME-LICENSE.txt`, a notices file in `licenses/`, and files or folders beside the app in the disk image and the zip. +## The desktop app + +The desktop app (`apps/portablekit`, see [DESKTOP_APP.md](DESKTOP_APP.md)) is packed by the same macOS script and by a Windows script of its own. Its release description is `apps/portablekit/packaging`: `release.env`, `THIRD_PARTY_NOTICES.md` for both systems, and a read-me for each. + +On macOS: + +```sh +cmake -S apps/portablekit -B out-app-release -G Ninja -DCMAKE_BUILD_TYPE=Release -DPORTABLEKIT_RELEASE=ON +cmake --build out-app-release +scripts/release_macos.sh --game . --packaging apps/portablekit/packaging out-app-release +``` + +`PortableKit.app` then holds the program, the recompiler beside it in `Contents/MacOS`, and the headers the generated code includes in `Contents/Resources/include`, which is where the program looks for them inside a bundle. + +On Windows, with a build of the app made with llvm-mingw (see [DESKTOP_APP.md](DESKTOP_APP.md#windows-ship-an-llvm-toolchain-done)) and configured with `-DPORTABLEKIT_RELEASE=ON`: + +```powershell +scripts\package_desktop_windows.ps1 -BuildDir C:\path\to\build -Toolchain C:\path\to\llvm-mingw +``` + +It assembles the portable folder -- the program, the recompiler, their DLLs, the headers, an empty `data\` and the part of llvm-mingw that compiling a game for x86_64 needs, in `toolchain\` -- checks it for game data and for paths of the build machine, starts the packed program to check that it runs and finds its toolchain, and packs `portablekit--windows-x64.zip` with `SHA256SUMS` and `BUILDINFO.txt` into `out\package-windows\dist`. `-License NAME=FILE`, `-Notices FILE` and `-Extra PATH` work as on macOS, and `-Packaging DIR` names another release description. + +### A build with other components + +A build that links more than PortableKit's own code, such as [HLE extension modules](HLE_EXTENSIONS.md), is packed with the same scripts: set `PORTABLEKIT_BUILD_LABEL` so it is recognisable (window title, library header, `portablekit --version`), pass each component's licence with `--license`/`-License`, and point `--packaging`/`-Packaging` at a copy of `apps/portablekit/packaging` whose notices list the components. Such a build is distributed under the terms its combination of licences requires. + ## Linux `scripts/release_linux.sh --game ` builds inside the Steam Runtime 3 "sniper" SDK container (podman or docker, x86-64) from the game's generated code, and packs a tarball and a Flatpak bundle. It needs flatpak with Flathub, ostree and curl. diff --git a/scripts/package_desktop_windows.ps1 b/scripts/package_desktop_windows.ps1 new file mode 100644 index 0000000..84fe2f6 --- /dev/null +++ b/scripts/package_desktop_windows.ps1 @@ -0,0 +1,323 @@ +<# +.SYNOPSIS +Packs the desktop app (apps/portablekit) for Windows x64 as a portable folder +and a zip archive, with the trimmed llvm-mingw toolchain it compiles games with. + +.DESCRIPTION + package_desktop_windows.ps1 -BuildDir -Toolchain + [-Output ] [-Version ] [-Packaging ] + [-License NAME=FILE,...] [-Notices FILE,...] [-Extra PATH,...] + [-Sdl3License FILE] [-NoZip] + +BuildDir is a build of apps/portablekit made with that llvm-mingw toolchain, +configured with -DPORTABLEKIT_RELEASE=ON (see docs/RELEASING.md, "The desktop +app"). Nothing in it is changed. The result is + + \PortableKit\ the folder a player unpacks + portablekit.exe psp_recomp.exe the program and the recompiler + *.dll SDL3, FFmpeg, libc++ and libunwind + share\portablekit\include\ the headers the generated code includes + toolchain\ the part of llvm-mingw the app compiles with + data\ empty until the player adds a game + README.txt LICENSE.txt licenses\ + \dist\portablekit--windows-x64.zip, SHA256SUMS, BUILDINFO.txt + +Packaging (default apps\portablekit\packaging) holds THIRD_PARTY_NOTICES.md +and windows\README.txt; a downstream build may point it at its own copy. +-License, -Notices and -Extra add what such a build brings: a licence as +licenses\NAME-LICENSE.txt, a notices file in licenses\, and files or folders +at the top of the package. + +Before packing it checks that the package holds no game data and no path of +this machine in the program's own files, and that the packed program starts +and reports the expected version. +#> +param( + [Parameter(Mandatory = $true)][string]$BuildDir, + [Parameter(Mandatory = $true)][string]$Toolchain, + [string]$Output = "", + [string]$Version = "", + [string]$Packaging = "", + [string[]]$License = @(), + [string[]]$Notices = @(), + [string[]]$Extra = @(), + [string]$Sdl3License = "", + [switch]$NoZip +) +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 3 + +function Fail([string]$message) { Write-Error "error: $message"; exit 1 } +# Git's answer, or null outside a checkout: Windows PowerShell turns a native +# command's error output into an error, which Stop would make fatal. +function Get-GitOutput([string[]]$arguments) { + $saved = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + try { + $output = & git @arguments 2>$null + if ($LASTEXITCODE -eq 0 -and $output) { return ($output | Select-Object -First 1) } + } catch { + } finally { $ErrorActionPreference = $saved } + return $null +} +function Step([string]$message) { Write-Host ""; Write-Host "=== $message" } + +$kitDir = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$BuildDir = (Resolve-Path $BuildDir).Path +$Toolchain = (Resolve-Path $Toolchain).Path +if ($Packaging -eq "") { $Packaging = Join-Path $kitDir 'apps\portablekit\packaging' } +$Packaging = (Resolve-Path $Packaging).Path +if ($Output -eq "") { $Output = Join-Path $kitDir 'out\package-windows' } +$bin = Join-Path $BuildDir 'bin' +$name = 'PortableKit' +$stage = Join-Path $Output $name +$dist = Join-Path $Output 'dist' + +# --- What the build must have made ------------------------------------------ +Step "Checking $BuildDir" +$programFiles = @('portablekit.exe', 'psp_recomp.exe', 'SDL3.dll', 'avcodec-61.dll', 'avutil-59.dll', + 'swresample-5.dll', 'FFmpeg-LICENSE.txt', 'FFmpeg-SOURCE.txt') +foreach ($file in $programFiles) { + if (-not (Test-Path (Join-Path $bin $file))) { Fail "$bin\$file is missing; build the portablekit target" } +} +if (-not (Test-Path (Join-Path $bin 'share\portablekit\include\psprecomp\corpus_abi.hpp'))) { + Fail "$bin\share\portablekit\include is missing; build the portablekit target" +} +foreach ($file in @('bin\clang++.exe', 'bin\ld.lld.exe', 'bin\libc++.dll', 'bin\libunwind.dll', 'LICENSE.TXT', + 'x86_64-w64-mingw32\lib\libc++.a')) { + if (-not (Test-Path (Join-Path $Toolchain $file))) { Fail "$Toolchain is not an llvm-mingw toolchain (no $file)" } +} +$notices = Join-Path $Packaging 'THIRD_PARTY_NOTICES.md' +$readme = Join-Path $Packaging 'windows\README.txt' +foreach ($file in @($notices, $readme)) { if (-not (Test-Path $file)) { Fail "$file is missing" } } + +# A developer build falls back to its checkout's game folder; a release must +# not depend on the machine it was built on. +function Test-BinaryContains([string]$path, [string]$text) { + $bytes = [IO.File]::ReadAllBytes($path) + $haystack = [Text.Encoding]::GetEncoding(28591).GetString($bytes) + return $haystack.IndexOf($text, [StringComparison]::OrdinalIgnoreCase) -ge 0 +} +$cache = Join-Path $BuildDir 'CMakeCache.txt' +if (-not (Test-Path $cache)) { Fail "$BuildDir has no CMakeCache.txt" } +$cacheText = Get-Content $cache +if (-not ($cacheText -match '^PORTABLEKIT_RELEASE:BOOL=ON$')) { + Fail "$BuildDir is a developer build; configure it with -DPORTABLEKIT_RELEASE=ON and build again" +} +$sourceLine = $cacheText | Where-Object { $_ -like 'CMAKE_HOME_DIRECTORY:*' } | Select-Object -First 1 +$sourceDir = if ($sourceLine) { $sourceLine.Split('=', 2)[1] } else { $kitDir } + +# SDL3's licence comes with the development package the build found. +if ($Sdl3License -eq "") { + $sdlLine = $cacheText | Where-Object { $_ -like 'SDL3_DIR:*' } | Select-Object -First 1 + if ($sdlLine) { + $dir = $sdlLine.Split('=', 2)[1] + for ($i = 0; $i -lt 6 -and $dir; $i++) { + if (Test-Path (Join-Path $dir 'LICENSE.txt')) { $Sdl3License = Join-Path $dir 'LICENSE.txt'; break } + $dir = Split-Path $dir -Parent + } + } +} +if ($Sdl3License -eq "" -or -not (Test-Path $Sdl3License)) { Fail "cannot find SDL3's LICENSE.txt; give it with -Sdl3License" } + +foreach ($entry in $License) { + $parts = $entry.Split('=', 2) + if ($parts.Count -ne 2 -or -not (Test-Path $parts[1])) { Fail "-License $entry`: expected NAME=FILE with an existing file" } +} +foreach ($file in $Notices) { if (-not (Test-Path $file)) { Fail "-Notices $file`: no such file" } } +foreach ($path in $Extra) { if (-not (Test-Path $path)) { Fail "-Extra $path`: no such file or folder" } } + +# The notices must name what is bundled. +$ffmpegCmake = Get-Content (Join-Path $kitDir 'cmake\FFmpeg.cmake') -Raw +$ffmpegUrl = [regex]::Match($ffmpegCmake, '"(https://[^"]*win64-lgpl-shared[^"]*)"').Groups[1].Value +$sources = Get-Content (Join-Path $kitDir 'packaging\sources.sh') -Raw +$sdlVersion = [regex]::Match($sources, '(?m)^SDL3_VERSION=(\S+)').Groups[1].Value +$llvmMingw = (& (Join-Path $Toolchain 'bin\clang++.exe') --version | Select-Object -First 1) +$noticeText = Get-Content $notices -Raw +foreach ($pinned in @("SDL3 $sdlVersion", $ffmpegUrl, 'llvm-mingw')) { + if (-not $noticeText.Contains($pinned)) { Fail "THIRD_PARTY_NOTICES.md does not mention: $pinned" } +} + +# --- The folder --------------------------------------------------------------- +Step "Assembling $stage" +foreach ($old in @($stage, $dist)) { if (Test-Path $old) { Remove-Item -Recurse -Force $old } } +New-Item -ItemType Directory -Force $stage, $dist | Out-Null +foreach ($file in $programFiles) { Copy-Item (Join-Path $bin $file) $stage } +Copy-Item (Join-Path $Toolchain 'bin\libc++.dll'), (Join-Path $Toolchain 'bin\libunwind.dll') $stage +New-Item -ItemType Directory (Join-Path $stage 'share') | Out-Null +Copy-Item -Recurse (Join-Path $bin 'share\portablekit') (Join-Path $stage 'share\portablekit') +New-Item -ItemType Directory (Join-Path $stage 'data') | Out-Null + +# The toolchain, cut to what compiling a game for x86_64 needs: the compiler +# driver and its libraries, the linker, the headers, clang's runtime and the +# x86_64 sysroot. Other targets, tools and documentation stay out. +$tool = Join-Path $stage 'toolchain' +New-Item -ItemType Directory (Join-Path $tool 'bin'), (Join-Path $tool 'lib') | Out-Null +$toolBin = Join-Path $Toolchain 'bin' +$wanted = @('clang.exe', 'clang++.exe', 'clang-target-wrapper.exe', 'ld.lld.exe', 'x86_64-w64-mingw32-clang.exe', + 'x86_64-w64-mingw32-clang++.exe', 'libc++.dll', 'libunwind.dll', 'libwinpthread-1.dll', + 'libclang-cpp.dll', 'mingw32-common.cfg', 'x86_64-w64-windows-gnu.cfg') +foreach ($file in $wanted) { + $path = Join-Path $toolBin $file + if (-not (Test-Path $path)) { Fail "$path is missing from the toolchain" } + Copy-Item $path (Join-Path $tool 'bin') +} +# The versioned compiler (clang-23.exe) and LLVM library (libLLVM-23.dll). +$versioned = @(Get-ChildItem $toolBin | Where-Object { $_.Name -match '^(clang-\d+\.exe|libLLVM-\d+\.dll)$' }) +if ($versioned.Count -lt 2) { Fail "no clang-.exe and libLLVM-.dll in $toolBin" } +$versioned | ForEach-Object { Copy-Item $_.FullName (Join-Path $tool 'bin') } +Copy-Item (Join-Path $Toolchain 'LICENSE.TXT') $tool +Copy-Item -Recurse (Join-Path $Toolchain 'include') (Join-Path $tool 'include') +Copy-Item -Recurse (Join-Path $Toolchain 'lib\clang') (Join-Path $tool 'lib\clang') +# Of clang's runtime libraries, only Windows x86_64's: lib\x86_64-w64-windows-gnu +# (which the linker reads), and the x86_64 files of lib\windows. +# (A wildcard in the path would list the matching folders themselves, not +# their contents, so each version's lib\ is named in full.) +$runtimeDirs = @(Get-ChildItem -Directory (Join-Path $tool 'lib\clang') | + ForEach-Object { Join-Path $_.FullName 'lib' } | Where-Object { Test-Path $_ } | + ForEach-Object { Get-ChildItem -Directory -LiteralPath $_ }) +if ($runtimeDirs.Count -eq 0) { Fail "no clang runtime libraries in $Toolchain\lib\clang" } +foreach ($runtime in $runtimeDirs) { + if ($runtime.Name -like 'x86_64-*windows*') { continue } + if ($runtime.Name -ne 'windows') { Remove-Item -Recurse -Force $runtime.FullName; continue } + Get-ChildItem -File -LiteralPath $runtime.FullName | Where-Object { $_.Name -notmatch 'x86_64' } | Remove-Item -Force +} +if (-not (Get-ChildItem -Recurse -File (Join-Path $tool 'lib\clang') -Filter 'libclang_rt.builtins*x86_64*')) { + Fail "the trimmed toolchain lost clang's x86_64 runtime" +} +Copy-Item -Recurse (Join-Path $Toolchain 'x86_64-w64-mingw32') (Join-Path $tool 'x86_64-w64-mingw32') + +Copy-Item $readme (Join-Path $stage 'README.txt') +Copy-Item (Join-Path $kitDir 'LICENSE') (Join-Path $stage 'LICENSE.txt') +$licenses = Join-Path $stage 'licenses' +New-Item -ItemType Directory $licenses | Out-Null +Copy-Item $notices $licenses +Copy-Item (Join-Path $kitDir 'LICENSE') (Join-Path $licenses 'PortableKit-LICENSE.txt') +Copy-Item $Sdl3License (Join-Path $licenses 'SDL3-LICENSE.txt') +Copy-Item (Join-Path $bin 'FFmpeg-LICENSE.txt'), (Join-Path $bin 'FFmpeg-SOURCE.txt') $licenses +Copy-Item (Join-Path $kitDir 'third_party\imgui\LICENSE.txt') (Join-Path $licenses 'DearImGui-LICENSE.txt') +Copy-Item (Join-Path $kitDir 'third_party\tiny_aes\UNLICENSE') (Join-Path $licenses 'tiny-AES-c-UNLICENSE.txt') +Copy-Item (Join-Path $kitDir 'third_party\xxhash\LICENSE') (Join-Path $licenses 'xxHash-LICENSE.txt') +Copy-Item (Join-Path $Toolchain 'LICENSE.TXT') (Join-Path $licenses 'llvm-mingw-LICENSE.txt') +$mingwNotices = Join-Path $Toolchain 'x86_64-w64-mingw32\share\mingw32' +if (Test-Path $mingwNotices) { + Get-ChildItem $mingwNotices -Filter 'COPYING*' | ForEach-Object { + Copy-Item $_.FullName (Join-Path $licenses ("mingw-w64-" + $_.Name + $(if ($_.Extension -eq '') { '.txt' } else { '' }))) + } +} +foreach ($entry in $License) { + $parts = $entry.Split('=', 2) + Copy-Item $parts[1] (Join-Path $licenses ($parts[0] + '-LICENSE.txt')) +} +foreach ($file in $Notices) { Copy-Item $file $licenses } +foreach ($path in $Extra) { Copy-Item -Recurse $path $stage } + +# --- Checks ------------------------------------------------------------------- +Step "Checking the package" +$bad = @() +foreach ($item in Get-ChildItem -Recurse -File $stage) { + $lower = $item.Name.ToLowerInvariant() + if ($lower -match '^(eboot.*|data\.bin|param\.sfo|umd_data.*)$' -or + $lower -match '\.(iso|cso|pbp|prx|elf|ovl)$') { $bad += "$($item.FullName) (name)"; continue } + if ($item.Length -lt 20) { continue } + $stream = [IO.File]::OpenRead($item.FullName) + try { + $head = New-Object byte[] 20 + [void]$stream.Read($head, 0, 20) + if ($head[0] -eq 0x7F -and $head[1] -eq 0x45 -and $head[2] -eq 0x4C -and $head[3] -eq 0x46 -and + $head[18] -eq 8 -and $head[19] -eq 0) { $bad += "$($item.FullName) (PSP executable)" } + elseif ($head[0] -eq 0 -and $head[1] -eq 0x50 -and $head[2] -eq 0x42 -and $head[3] -eq 0x50) { $bad += "$($item.FullName) (PBP)" } + elseif ($head[0] -eq 0x7E -and $head[1] -eq 0x50 -and $head[2] -eq 0x53 -and $head[3] -eq 0x50) { $bad += "$($item.FullName) (encrypted PSP module)" } + elseif ($head[0] -eq 0 -and $head[1] -eq 0x50 -and $head[2] -eq 0x53 -and $head[3] -eq 0x46) { $bad += "$($item.FullName) (PARAM.SFO)" } + elseif ($item.Length -gt 32774) { + $iso = New-Object byte[] 5 + [void]$stream.Seek(32769, 'Begin') + [void]$stream.Read($iso, 0, 5) + if ([Text.Encoding]::ASCII.GetString($iso) -eq 'CD001') { $bad += "$($item.FullName) (disc image)" } + } + } finally { $stream.Dispose() } +} +if ($bad.Count -gt 0) { $bad | ForEach-Object { Write-Host " $_" }; Fail "the package contains game data" } +Write-Host "no game data in the package" + +# The program's own files name no path of this machine. The toolchain is +# llvm-mingw's release as it is, so it is not searched. +$leakChecks = @($env:USERPROFILE, $BuildDir, $sourceDir, $kitDir) | Where-Object { $_ } | + ForEach-Object { $_; $_.Replace('\', '/') } | Select-Object -Unique +$ownFiles = @(Get-ChildItem $stage -File | Where-Object { $_.Extension -in '.exe' }) + + @(Get-ChildItem -Recurse -File (Join-Path $stage 'share')) +foreach ($file in $ownFiles) { + foreach ($text in $leakChecks) { + if (Test-BinaryContains $file.FullName $text) { Fail "$($file.FullName) names a path of this machine: $text" } + } +} +Write-Host "no paths of this machine in the program's files" + +# The packed program starts, with only what the package holds. +$savedPath = $env:PATH +try { + $env:PATH = "$stage;$env:SystemRoot\System32;$env:SystemRoot" + $report = & (Join-Path $stage 'portablekit.exe') version --json | ConvertFrom-Json +} finally { $env:PATH = $savedPath } +if (-not $report.ok) { Fail "the packed portablekit.exe did not start" } +$toolchainReport = & (Join-Path $stage 'portablekit.exe') --data-dir (Join-Path $Output 'check-data') toolchain --json | ConvertFrom-Json +Remove-Item -Recurse -Force (Join-Path $Output 'check-data') -ErrorAction SilentlyContinue +if (-not $toolchainReport.ok -or $toolchainReport.kind -ne 'llvm-mingw') { Fail "the packed program does not find its toolchain" } +# The packed toolchain compiles and links a library the way the app links a +# game's code (-shared -static), with nothing from outside the package. +$probe = Join-Path $Output 'check-toolchain' +if (Test-Path $probe) { Remove-Item -Recurse -Force $probe } +New-Item -ItemType Directory $probe | Out-Null +Set-Content -Encoding ascii (Join-Path $probe 'probe.cpp') @' +#include +#include +extern "C" __declspec(dllexport) int probe(int n) { std::vector v(n, "x"); return static_cast(v.size()); } +'@ +$savedPath = $env:PATH +try { + $env:PATH = "$env:SystemRoot\System32;$env:SystemRoot" + & (Join-Path $tool 'bin\clang++.exe') -std=c++20 -O1 -shared -static -static-libgcc -static-libstdc++ ` + -o (Join-Path $probe 'probe.dll') (Join-Path $probe 'probe.cpp') + $probeStatus = $LASTEXITCODE +} finally { $env:PATH = $savedPath } +if ($probeStatus -ne 0 -or -not (Test-Path (Join-Path $probe 'probe.dll'))) { + Fail "the packed toolchain cannot compile and link a library" +} +Remove-Item -Recurse -Force $probe +Write-Host "the packed toolchain compiles and links a library" +Write-Host "portablekit $($report.version)$(if ($report.label) { " ($($report.label))" }), toolchain: $($toolchainReport.version)" +foreach ($module in $report.hle_extensions) { Write-Host " HLE extension: $($module.title) $($module.version) ($($module.license))" } + +if ($Version -eq "") { + $Version = $report.version + if ($Version -eq 'unknown') { + $described = Get-GitOutput @('-C', $sourceDir, 'describe', '--tags', '--always', '--dirty') + if ($described) { $Version = $described } + } + $Version = $Version.TrimStart('v') +} +$size = (Get-ChildItem -Recurse -File $stage | Measure-Object Length -Sum).Sum +Write-Host ("{0}: {1:N0} MB" -f $stage, ($size / 1MB)) + +# --- Archive -------------------------------------------------------------------- +$archive = "portablekit-$Version-windows-x64" +if (-not $NoZip) { + Step "Zip" + $zip = Join-Path $dist "$archive.zip" + if (Test-Path $zip) { Remove-Item -Force $zip } + # Windows' own tar writes zip archives much faster than Compress-Archive. + & tar.exe -a -c -f $zip -C $Output $name + if ($LASTEXITCODE -ne 0) { Fail "tar could not write $zip" } + $hash = (Get-FileHash -Algorithm SHA256 $zip).Hash.ToLowerInvariant() + Set-Content -Encoding ascii (Join-Path $dist 'SHA256SUMS') "$hash $archive.zip" + Write-Host "$hash $archive.zip" +} +$commit = Get-GitOutput @('-C', $sourceDir, 'rev-parse', 'HEAD') +@( + "PortableKit $Version for Windows x64$(if ($report.label) { " ($($report.label))" })", + "Built from: $(if ($commit) { $commit } else { 'a source tree without Git history' })", + "Toolchain shipped and used to build: $llvmMingw", + "Corpus ABI: $($report.abi)", + "HLE extension modules: $(if ($report.hle_extensions.Count -gt 0) { ($report.hle_extensions | ForEach-Object { "$($_.title) $($_.version) ($($_.license))" }) -join ', ' } else { 'none' })" +) | Set-Content -Encoding utf8 (Join-Path $dist 'BUILDINFO.txt') +Get-ChildItem $dist | Format-Table Name, Length -AutoSize