Merge pull request #103 from boykopovar/feat/provide-release

Feat/provide release
Added a release workflow for v* tags: build Windows and Linux, run tests, and publish only if both platforms are successful. The release contains PRX archives, relinker executables, and documentation from docs/user. The build cache is reused.

- `prx-windows-v0.1.0.zip`
- `prx-windows-v0.1.0.tar.gz`
- `prx-linux-v0.1.0.zip`
- `prx-linux-v0.1.0.tar.gz`
- `relinker-v0.1.0.exe`
- `relinker-v0.1.0`
- `COMPATIBILITY.md`
- `INPUT_MAPPING.md`
This commit is contained in:
boykopovar authored and GitHub committed 2026-09-29 00:25:39 +03:00
commit 64066b6e9a
11 files changed
+245 -54

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
name: Release
on:
push:
tags: ['v*']
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
RELEASE_TAG: ${{ github.ref_name }}
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/[email protected]
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 --version "$RELEASE_TAG"
- 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 }}
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-$RELEASE_TAG.zip" "prx-windows-$RELEASE_TAG.tar.gz" "prx-linux-$RELEASE_TAG.zip" "prx-linux-$RELEASE_TAG.tar.gz" "relinker-$RELEASE_TAG.exe" "relinker-$RELEASE_TAG"; do
test -s "dist/$file"
done
python tools/package_release.py --docs docs/user --output dist
chmod +x "dist/relinker-$RELEASE_TAG"
- 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
+14 -10
View File
@@ -4,23 +4,23 @@ 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/)
<sub>* 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.</sub>
[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 [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.
[Technical debt of the project](docs/TechnicalDebt.md), [code style conventions](docs/CONVENTIONS.md)
[Technical debt of the project](docs/dev/TechnicalDebt.md), [code style conventions](docs/dev/CONVENTIONS.md)
## Build
@@ -30,9 +30,13 @@ The relinker uses only the C++20 standard library and should build with any conf
The project targets maximum compiler portability. Support for additional compilers will be addressed after the first successful game launch.
## Compatibility
See the [game compatibility list](docs/user/COMPATIBILITY.md) for tested games and known issues.
## Input mapping
SDL-mapped game controllers are supported, including analog sticks and triggers. Keyboard and mouse controls can be configured with an `anyps5-input.ini` file. See [input mapping](docs/INPUT_MAPPING.md) for the supported devices and configuration format.
SDL-mapped game controllers are supported, including analog sticks and triggers. Keyboard and mouse controls can be configured with an `anyps5-input.ini` file. See [input mapping](docs/user/INPUT_MAPPING.md) for the supported devices and configuration format.
## Disclaimer
+1 -1
View File
@@ -5,7 +5,7 @@ Game `DT_NEEDED` expects exactly: `libcohtml.Prospero.prx`.
Copy next to the relinked eboot (same directory):
- `build/core/libs/libs/libcohtml.Prospero.prx` (patched build output, 50 NIDs) — required.
- `libgcc_s_seh-1.dll`, `libstdc++-6.dll`, `libwinpthread-1.dll` from `winlibs-gcc15/mingw64/bin` — required on Windows (see `docs/TechnicalDebt.md`).
- `libgcc_s_seh-1.dll`, `libstdc++-6.dll`, `libwinpthread-1.dll` from `winlibs-gcc15/mingw64/bin` — required on Windows (see [technical debt](../../../../docs/dev/TechnicalDebt.md)).
Do NOT deploy:
+1 -1
View File
@@ -1635,7 +1635,7 @@ public:
} else {
// Compressed (Vorbis/MP3/FSB/AT9...) or unknown: audible
// placeholder so the mixer path can be heard. Real decoders
// (Vorbis/MP3/AT9) remain future work — see docs/TechnicalDebt.
// (Vorbis/MP3/AT9) remain future work — see docs/dev/TechnicalDebt.
FmodMakeSine(created->pcm, kFmodOutRate, 2, 440.0f, 1.0f);
created->pcmRate = kFmodOutRate;
created->pcmCh = 2;
-41
View File
@@ -1,41 +0,0 @@
# Project technical debt
### Build
- Building on Windows requires a specific version of mingw - MinGW-w64 GCC 15.2.0 (`winlibs-gcc15`, `x86_64-ucrt-posix-seh`)
- Even compiled prx libraries on Windows require nearby (static linking of these dependencies causes conflicts):
- libgcc_s_seh-1.dll
- libstdc++-6.dll
- libwinpthread-1.dll
### Silent stubs
Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except:
- [libSceSaveDataDialog.native](../core/libs/prx/libSceSaveDataDialog.native/Export.cpp)
- [libSceCommonDialog](../core/libs/prx/libSceCommonDialog/Export.cpp)
- The shader recompiler [skips baryctric coordinates](../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212).
### Unknown function info
- [zARR5aCmkoY](../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature
- [qj7QZpgr9Uw](../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name
- [fd5Bp5tGTgo](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
- [dolOmWH+huQ](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
- [V++UgBtQhn0](../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name
- [gQkqkLttcpw](../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature
- [sceKernelInternalMemoryGetModuleSegmentInfo](../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature
- [sceLibcInternalBacktraceForGame](../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
- [sceLibcInternalHeapErrorReportForGame](../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
- [__progname](../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export
### Functional
- [Shader recompilation](../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../core/shader/recompiler) was written completely independently from [libs/prx](../core/libs/prx).
- The executable file that [relinker](../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.
- [Relinker](../core/relinker/elfpatcher/src) doesn't add an icon to the generated executable. This should be done without adding dependencies (only standard).
- `--to-intel` does not lower the register form of INSERTQ (`F2 0F 79`) nor MONITORX/MWAITX/CLZERO/RDPRU/MCOMMIT; the [lowering](../core/relinker/codegen/src/x86/Sse4aLowering.cpp) fails the relink instead. SHA-NI is not substituted.
- The length-changing path of the [instruction rewriter](../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length.
- The Linux placement of the `--to-intel` stubs in the [ELF patcher](../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test.
- The libc [SSE4a trap emulation](../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it.
- `--to-intel` lowerings that need a stub section are rejected for `sce_module` guest modules: the [guest module builder](../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) does not pass stub sites to the [guest module writer](../core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp).
- The game can expect its modified prx from the `sce_module`/`sce_modules` folder - this support is not implemented.
File renamed without changes.
+41
View File
@@ -0,0 +1,41 @@
# Project technical debt
### Build
- Building on Windows requires a specific version of mingw - MinGW-w64 GCC 15.2.0 (`winlibs-gcc15`, `x86_64-ucrt-posix-seh`)
- Even compiled prx libraries on Windows require nearby (static linking of these dependencies causes conflicts):
- libgcc_s_seh-1.dll
- libstdc++-6.dll
- libwinpthread-1.dll
### Silent stubs
Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except:
- [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp)
- [libSceCommonDialog](../../core/libs/prx/libSceCommonDialog/Export.cpp)
- The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212).
### Unknown function info
- [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature
- [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name
- [fd5Bp5tGTgo](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
- [dolOmWH+huQ](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
- [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name
- [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature
- [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature
- [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
- [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
- [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export
### Functional
- [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx).
- The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.
- [Relinker](../../core/relinker/elfpatcher/src) doesn't add an icon to the generated executable. This should be done without adding dependencies (only standard).
- `--to-intel` does not lower the register form of INSERTQ (`F2 0F 79`) nor MONITORX/MWAITX/CLZERO/RDPRU/MCOMMIT; the [lowering](../../core/relinker/codegen/src/x86/Sse4aLowering.cpp) fails the relink instead. SHA-NI is not substituted.
- The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length.
- The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test.
- The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it.
- `--to-intel` lowerings that need a stub section are rejected for `sce_module` guest modules: the [guest module builder](../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) does not pass stub sites to the [guest module writer](../../core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp).
- The game can expect its modified prx from the `sce_module`/`sce_modules` folder - this support is not implemented.
+5
View File
@@ -0,0 +1,5 @@
# Tested compatibility
| 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 |
File renamed without changes.
+71
View File
@@ -0,0 +1,71 @@
import argparse
import re
import shutil
import tarfile
import zipfile
from pathlib import Path
def package(platform, build, output, version):
if not re.fullmatch(r"v[0-9A-Za-z][0-9A-Za-z._-]*", version) or version.endswith("."):
raise ValueError(f"Invalid release tag for asset filenames: {version}")
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}-{version}.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}-{version}.tar.gz", "w:gz") as archive:
for file in files:
archive.add(file, arcname=file.name)
asset = f"relinker-{version}.exe" if platform == "windows" else f"relinker-{version}"
shutil.copy2(binary, output / asset)
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("--version")
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 or args.version is not None:
parser.error("--docs cannot be combined with --platform, --build or --version")
collect_docs(args.docs, args.output)
else:
if args.platform is None or args.build is None or args.version is None:
parser.error("--platform, --build and --version are required when --docs is not specified")
package(args.platform, args.build, args.output, args.version)
if __name__ == "__main__":
main()
+1 -1
View File
@@ -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):