diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..04afa2cd8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,111 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +env: + CONFIGURE: >- + -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON + -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ + -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + WINLIBS_RELEASE: 15.2.0posix-14.0.0-ucrt-r7 + WINLIBS_ZIP: winlibs-x86_64-posix-seh-gcc-15.2.0-mingw-w64ucrt-14.0.0-r7.zip + +jobs: + build: + name: Build and test (${{ matrix.platform }}) + strategy: + fail-fast: true + matrix: + include: + - platform: linux + runner: ubuntu-latest + timeout: 30 + - platform: windows + runner: windows-latest + timeout: 45 + runs-on: ${{ matrix.runner }} + timeout-minutes: ${{ matrix.timeout }} + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v7 + with: + submodules: true + - id: toolchain + if: matrix.platform == 'windows' + uses: actions/cache@v6 + with: + path: C:\winlibs + key: ${{ env.WINLIBS_ZIP }} + - if: matrix.platform == 'windows' && steps.toolchain.outputs.cache-hit != 'true' + run: | + curl -sSfL -o "$RUNNER_TEMP/winlibs.zip" "https://github.com/brechtsanders/winlibs_mingw/releases/download/$WINLIBS_RELEASE/$WINLIBS_ZIP" + 7z x "$RUNNER_TEMP/winlibs.zip" -oC:/winlibs + - if: matrix.platform == 'windows' + run: echo 'C:\winlibs\mingw64\bin' >> "$GITHUB_PATH" + - uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: ${{ matrix.platform }} + save: false + - name: Configure Linux + if: matrix.platform == 'linux' + run: cmake -S . -B build $CONFIGURE + - name: Configure Windows + if: matrix.platform == 'windows' + shell: cmd + run: cmake -S . -B build %CONFIGURE% + - name: Build project and tests + run: cmake --build build + - name: Run tests + run: ctest --test-dir build --output-on-failure --timeout 120 --stop-on-failure --no-tests=error + - name: Build patched libraries + run: cmake --build build --target libs + - name: Package release files + run: python tools/package_release.py --platform "${{ matrix.platform }}" --build build --output dist + - uses: actions/upload-artifact@v7 + with: + name: release-${{ matrix.platform }} + path: dist/* + if-no-files-found: error + compression-level: 0 + + publish: + name: Publish release + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + RELEASE_TAG: ${{ github.ref_name }} + steps: + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v8 + with: + pattern: release-* + merge-multiple: true + path: dist + - name: Check release files and collect documentation + shell: bash + run: | + for file in prx-windows.zip prx-windows.tar.gz prx-linux.zip prx-linux.tar.gz relinker.exe relinker; do + test -s "dist/$file" + done + python tools/package_release.py --docs docs/user --output dist + chmod +x dist/relinker + - name: Create draft and upload files + run: gh release create "$RELEASE_TAG" dist/* --verify-tag --draft --title "$RELEASE_TAG" --generate-notes + - name: Publish complete release + run: gh release edit "$RELEASE_TAG" --draft=false diff --git a/README.md b/README.md index 47e39fe68..4b658c471 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,21 @@ Tool for automatic executables porting to Linux and Windows. Includes a [relinker](core/relinker) that converts executable to the target system's native format and implementations of [system prx libraries](core/libs/prx) suitable for dynamic linking. No emulation or separate runtime process. + + +## Status + [![libraries](https://boykopovar.github.io/AnyPS5/badge-libraries.svg)](https://boykopovar.github.io/AnyPS5/) [![shaders](https://boykopovar.github.io/AnyPS5/badge-shaders.svg)](https://boykopovar.github.io/AnyPS5/) [![progress map](https://boykopovar.github.io/AnyPS5/progress.svg)](https://boykopovar.github.io/AnyPS5/) -* System libraries: percentage of the functions known to the project so far (declared in [core/libs/prx](core/libs/prx)), not of every PS5 system function. The total grows as more functions are declared. +[List of verified games](docs/user/COMPATIBILITY.md) -Releases will be published after the first full successful launch of at least one game. +Dreaming Sarah (2D platformer) runs at a stable 60 fps on a GTX 1050 Ti / i5-7500 3.4GHz. -## Status +Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates. -Execution reaches `_start`, [stack unwinding](core/libs/prx/libc/src/exception/Unwind.cpp) and exception handling tables are built, reaches main. Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates. - -The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V, validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`. - -The real game reaches the logo, main menu, and [gameplay](https://gist.github.com/user-attachments/assets/81d28e9b-c237-4545-b2ca-720071129816) with audio. +The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V (validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`). [Technical debt of the project](docs/dev/TechnicalDebt.md), [code style conventions](docs/dev/CONVENTIONS.md) diff --git a/docs/user/COMPATIBILITY.md b/docs/user/COMPATIBILITY.md index 45a370021..4b62cc293 100644 --- a/docs/user/COMPATIBILITY.md +++ b/docs/user/COMPATIBILITY.md @@ -1,5 +1,5 @@ -# Compatibility +# Tested compatibility -| Game | ID | Windows | Linux | GTX 1050 Ti / i5-7500 | -|----------------|-----------|-------------------|-------|-----------------------| -| Dreaming Sarah | PPSA02929 | In game, playable | ? | 60 FPS | +| Game | ID | Windows | Linux | GTX 1050 Ti / i5-7500 3.4GHz | Intel(R) HD Graphic 620 / i5-7200 2.5GHz | +|----------------|-----------|-------------------|-------|------------------------------|------------------------------------------| +| Dreaming Sarah | PPSA02929 | In game, playable | ? | 60 FPS | 36 FPS | diff --git a/tools/package_release.py b/tools/package_release.py new file mode 100644 index 000000000..c8b7d0c68 --- /dev/null +++ b/tools/package_release.py @@ -0,0 +1,66 @@ +import argparse +import shutil +import tarfile +import zipfile +from pathlib import Path + + +def package(platform, build, output): + libraries = sorted((build / "core/libs/libs").glob("*.prx")) + expected = {f"{directory.name}.prx" for directory in Path("core/libs/prx").iterdir() if directory.is_dir()} + expected.add("libcohtml.Prospero.prx") + missing = expected - {library.name for library in libraries} + if missing: + raise RuntimeError(f"Missing patched libraries: {', '.join(sorted(missing))}") + executable = "relinker.exe" if platform == "windows" else "relinker" + files = list(libraries) + if platform == "windows": + runtime = Path("C:/winlibs/mingw64/bin") + files.extend(runtime / name for name in ("libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll")) + binary = build / "core/relinker" / executable + for file in [*files, binary]: + if not file.is_file() or file.stat().st_size == 0: + raise RuntimeError(f"Missing or empty release file: {file}") + output.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(output / f"prx-{platform}.zip", "w", compression=zipfile.ZIP_DEFLATED) as archive: + for file in files: + archive.write(file, arcname=file.name) + with tarfile.open(output / f"prx-{platform}.tar.gz", "w:gz") as archive: + for file in files: + archive.add(file, arcname=file.name) + shutil.copy2(binary, output / executable) + + +def collect_docs(source, output): + documents = sorted(source.rglob("*.md")) + if not documents: + raise RuntimeError(f"No Markdown documents found in {source}") + names = set() + for document in documents: + if document.name in names or (output / document.name).exists(): + raise RuntimeError(f"Duplicate release asset name: {document.name}") + names.add(document.name) + output.mkdir(parents=True, exist_ok=True) + for document in documents: + shutil.copy2(document, output / document.name) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--platform", choices=("linux", "windows")) + parser.add_argument("--build", type=Path) + parser.add_argument("--docs", type=Path) + parser.add_argument("--output", required=True, type=Path) + args = parser.parse_args() + if args.docs is not None: + if args.platform is not None or args.build is not None: + parser.error("--docs cannot be combined with --platform or --build") + collect_docs(args.docs, args.output) + else: + if args.platform is None or args.build is None: + parser.error("--platform and --build are required when --docs is not specified") + package(args.platform, args.build, args.output) + + +if __name__ == "__main__": + main() diff --git a/tools/progress.py b/tools/progress.py index 2af9611d1..2af716e40 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -26,7 +26,7 @@ OPCODE_ALIASES = { } REPORT_ROWS = 100 PANEL_WIDTH, GAP, MAP_HEIGHT, HEADER = 495, 10, 280, 30 -DONE_COLOR, TODO_COLOR, BORDER, TEXT = "#2ea043", "#1f6feb", "#0d1117", "#ffffff" +DONE_COLOR, TODO_COLOR, BORDER, TEXT = "#2ea043", "#6e7681", "#0d1117", "#ffffff" def body_end(text, start):