2 Commits
Author SHA1 Message Date
Gary Riches e01f5db170 README: nothing left of the builder kit
Claude-Session: https://claude.ai/code/session_016xMYBaR9GfxtETyVf6er1P
2026-10-14 10:28:35 +01:00
Gary Riches 768a56cc43 0.10.0: a built title instead of a builder kit; the HD pack halved on the console
The game's code is compiled from the decompilation, as in the other
decompilation ports this workspace releases, so the release is the built
title folder (ps5/tools/package-release.sh) with SOURCE.txt and the
licences. The builder kit and the PC texture tools are gone.

SMS_TEXTURE_PACK_MAX_SIZE (3072 on the PS5) halves a pack image longer
than that until it fits, from the file's own levels where it has them,
else by nextBlockLevel, kept on disk as <name>.top so each large file is
read once. The UHD pack now goes in as published: Delfino Plaza loads in
8.9 s the first time and 5.8 s after, at 60 fps.

Claude-Session: https://claude.ai/code/session_016xMYBaR9GfxtETyVf6er1P
2026-10-14 10:22:13 +01:00
19 changed files with 433 additions and 1409 deletions

No files matched your search

+11 -25
View File
@@ -9,7 +9,7 @@ header, and may also be used under that licence alone.
| --- | --- | --- |
| `ps5/tools/png-to-title-dds.py` (encoder from PS5 RetroArch, by Mihawk) | GPL-3.0-or-later | [`licenses/GPL-3.0.txt`](licenses/GPL-3.0.txt) |
| `ps5/src/platform.c`, `ps5/src/platform.h` and the tools taken from the PS5 Vulkan Template (by Mihawk) | MIT | [`licenses/MIT.txt`](licenses/MIT.txt) |
| The rest of `ps5/src/`, `ps5/tools/`, `ps5/cmake/`, `ps5/kit/`, `ps5/opengl/`, `ps5/probe/` and `ps5/art/` (written for this port) | MIT | [`licenses/MIT.txt`](licenses/MIT.txt) |
| The rest of `ps5/src/`, `ps5/tools/`, `ps5/cmake/`, `ps5/opengl/`, `ps5/probe/` and `ps5/art/` (written for this port) | MIT | [`licenses/MIT.txt`](licenses/MIT.txt) |
| `ps5/sdl/` (SDL2 drivers for the console) and `ps5/patches/sdl.patch` | Zlib, as SDL | [`licenses/Zlib.txt`](licenses/Zlib.txt) |
| `ps5/patches/sms-pc-port.patch` | MIT, as sms-pc-port | [`licenses/MIT.txt`](licenses/MIT.txt) |
| `ps5/art/kenney/` and the button prompts in `ps5/res/prompts/` made from it (Kenney's input prompts) | CC0-1.0 | in `ps5/art/kenney/` |
@@ -27,37 +27,23 @@ project, under that project's licence.
| Windowing, input and sound | [SDL2](https://github.com/libsdl-org/SDL) | Zlib |
| The link recipe, the C runtime start-up and the fake-SELF tool | [PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan) | GPL-3.0 |
| The platform layer and SDK | [PS5_PayloadSDK](https://github.com/mihawk-99/PS5_PayloadSDK), a fork of ps5-payload-dev/sdk | GPL-3.0 |
| Smaller libraries | stb, bcdec, xxHash; zlib, in the kit's Linux build of the `eboot.bin` tool | each its own, all permissive |
| Smaller libraries | stb, bcdec, xxHash | each its own, all permissive |
The builder kit fetches its compiler and build tools the first time it runs,
from their projects' own releases: [LLVM](https://github.com/llvm/llvm-project)
(Apache-2.0 with LLVM exceptions), [CMake](https://github.com/Kitware/CMake)
(BSD-3-Clause) and [Ninja](https://github.com/ninja-build/ninja) (Apache-2.0),
and for the optional texture pack [Pillow](https://github.com/python-pillow/Pillow)
(MIT-CMU), [etcpak](https://github.com/K0lb3/etcpak) (MIT and BSD-3-Clause),
[archspec](https://github.com/archspec/archspec) (Apache-2.0 or MIT) and, on
Linux, [7-Zip](https://github.com/ip7z/7zip) (LGPL-2.1, with its unRAR restriction). None is in the kit.
## The release
## A built title is not for distribution
A built `eboot.bin` holds the game's own code, compiled from the
decompilation. Whatever the licences above say about the rest, and whatever
licence the decompilation is published under, that code is a reconstruction of
Nintendo's program: a built title stays on the machine that made it.
The builder kit (`ps5/tools/package-kit.sh`) is what is shared: the same build
without that part. It holds compiled forms of the projects above, under their
licences, with `SOURCES.txt` naming each one's repository and revision, and
nothing of the game. Whoever uses it compiles the game's code on their own
machine, from the public sources, and plays it with their own disc image.
A release (`ps5/tools/package-release.sh`) is the built title folder: its
`eboot.bin` links the parts above and is distributed under GPL-3.0 as a whole,
with `SOURCE.txt` naming the repository and revision of each part and
`licenses/` holding their licence texts. The game's code in it is compiled from
the decompilation; nothing from a disc is in it.
## What is not covered
- **The game.** Super Mario Sunshine, its disc image and everything on it
belong to Nintendo. None of it is in this repository or in a kit, and no
licence above covers it.
belong to Nintendo. None of it is in this repository or in a release, and no
licence above covers it; the title plays your own disc image.
- **Texture packs.** The port can load Dolphin-format texture packs. None is in
this repository or in a kit; each belongs to its authors.
this repository or in a release; each belongs to its authors.
- **Artwork.** `ps5/art/icon0-source.png` and `ps5/art/pic-source.png` (a harbour
town on a sunny island), and the `ps5/sce_sys/icon0.png`, `pic0.dds` and
`pic1.dds` made from them, are the port's own: made for it by Gary Riches with
+114 -37
View File
@@ -24,14 +24,15 @@ drawn with OpenGL through the [PS5 OpenGL SDK](https://github.com/blackbearreloa
(Mesa over the console's own GPU interface).
It needs a PS5 that already runs homebrew, and **your own disc image of the
game** (North America, `GMSE01`). No game code, game data, Nintendo artwork,
Sony software or keys are included here.
game** (North America, `GMSE01`): the title is compiled from the
decompilation and reads everything else (models, levels, sound, movies) from
your disc. No game data, Nintendo artwork, Sony software or keys are included.
This is an unofficial port. It is not part of sms-pc-port and is not supported
by it: **please do not take PS5 problems to chasem-dev.** Report them
[here](https://github.com/griches/PS5_Sunshine/issues).
## This is an early release: 0.9.0
## This is an early release: 0.10.0
**It is not fully tested.** It was brought up and played on one console over a
single day (2026-10-13), through the opening, Delfino Plaza and the first
@@ -39,33 +40,114 @@ episode of Bianco Hills. Everything after that is unplayed on a PS5. Expect
faults; several serious ones were found and fixed only by playing, and there
will be more further into the game. Keep a copy of your save.
New in 0.9.0: sms-pc-port's save states (L1 + R1) and its faster wipe into the
map, from its revision of 2026-10-14, and a builder kit that no longer needs
Docker.
New in 0.10.0: a ready-made title (copy it over and add your disc), and the HD
texture pack goes in as its authors publish it, with the console sizing its
largest images itself.
## How to get it: the builder kit
## Installing
A built title holds the game's own code, so none is offered here. The
[release](https://github.com/griches/PS5_Sunshine/releases/latest) is a
**builder kit**: everything of a build except that code. On your own computer
it downloads the public source code, compiles it and links the title. One
command, about 5 to 20 minutes, on a Mac with Apple silicon, on Linux, or on
Windows in WSL. No Docker: on Ubuntu and WSL the compiler, LLVM 22, comes from
LLVM's own packages (apt.llvm.org); on a Mac it and the build tools are fetched
from their own releases the first time, checked, and kept for next time.
You need a PS5 that runs homebrew, with etaHEN (or an equivalent),
ShadowMountPlus (which shows folders in `/data/homebrew` as games) and an FTP
server, and **your own disc image** of the North American release (`GMSE01`,
revision 0) as `.iso`, `.gcm` or Dolphin's `.ciso`. Other regions are not
supported.
1. Download `sunshine-ps5-kit-<version>.zip` from the
1. Download `Sunshine-PS5-v<version>.zip` from the
[releases page](https://github.com/griches/PS5_Sunshine/releases/latest) and unzip it.
2. Get your computer ready once (a Mac: Apple's Command Line Tools; Windows: WSL
and LLVM 22; the kit's README has the commands), then open a terminal in the
kit's folder and run `./make-title.sh`.
3. Copy the folder it makes, `out/PPSA99312`, to `/data/homebrew/` on the console (FTP).
4. Copy your own disc image (`.iso`, `.gcm` or `.ciso`) into that folder, beside `eboot.bin`.
2. With an FTP program (FileZilla, Cyberduck, WinSCP), copy the **whole
`PPSA99312` folder** into `/data/homebrew/` on the console.
3. Copy your disc image into that folder, beside `eboot.bin`:
The kit's own [README](ps5/kit/README.md) has every step, the controls, the
settings menu, the steps for Windows and what to do when something goes wrong.
```text
/data/homebrew/PPSA99312/eboot.bin
/data/homebrew/PPSA99312/sce_sys/
/data/homebrew/PPSA99312/sce_module/
/data/homebrew/PPSA99312/res/
/data/homebrew/PPSA99312/licenses/
/data/homebrew/PPSA99312/GMSE01.iso (your disc image; any name)
```
**Never share the folder the kit makes, or its `eboot.bin`.**
"Super Mario Sunshine" appears on the home screen. If it does not, check the
folder is exactly `/data/homebrew/PPSA99312/` with `eboot.bin` directly inside
it (not `PPSA99312/PPSA99312/`), and that ShadowMountPlus is running. If it
starts and closes at once, the disc image is missing or is not the North
American one.
To update, copy the new release's `PPSA99312` folder over the old one. Your
saves, settings, disc image and texture packs are left alone.
## Playing
| DualSense | In the game |
| --- | --- |
| Cross | A (jump, confirm) |
| Circle | B (dive, back) |
| Square | X |
| Triangle | Y (first-person view) |
| R1 | Z |
| L1 + R1 | **Save state** (in a level); in the pause menu, the list of save states to load |
| L2, R2 | L, R (R sprays FLUDD) |
| Options | Start (pause, map) |
| Touch pad button | **Settings** |
| Sticks, D-pad | as on a GameCube |
The prompts the game shows on screen are the DualSense's own.
**Settings.** Press the touch pad's button at any time. The D-pad moves and
changes, Cross steps through a row's choices, Circle or the touch pad closes it.
| Row | What it does |
| --- | --- |
| Invert FLUDD Aim X / Y | flips the left stick while you aim FLUDD |
| Invert Camera X / Y | flips the right stick's camera |
| Camera Shake | OFF, HALF or FULL: the game's earthquakes (off to begin with) |
| Performance Overlay | OFF, MINIMAL (the frame rate alone) or FULL |
| Heat Haze | the heat shimmer |
| Resolution \* | 960p, 1440p, 1920p or 2160p |
| Frame Rate \* | 30 or 60 |
| Picture \* | 4:3 or 16:9 |
| Smoothing (FXAA) \* | on or off |
Rows marked \* take effect the next time you start the game. Below them are
the PC port's cheats (god mode, infinite lives and so on). Saves and settings
are kept in `/data/homebrew/PPSA99312/userdata/`.
**Save states.** Hold L1 and press R1 in a level to save the moment. Pause
(Options) and press L1 + R1 again to see your save states, with a picture of
each, and load one. They are kept in `userdata/savestates`. A state loads
exactly only in the release that saved it; with a newer release, older states put
Mario back where he was in that level and keep your progress, but the level
starts afresh.
## HD textures (optional)
The port loads texture packs made for the Dolphin emulator. The one it was
tested with is the
[Super Mario Sunshine UHD Texture Pack](https://github.com/qashto/Super_Mario_Sunshine_UHD_Texture_Pack)
(by qashto and razius), with chasem-dev's
[HD texture extras](https://github.com/chasem-dev/sms-hd-texture-extras):
1. Download `GMS.7z` from the UHD pack's
[release 2.1.1](https://github.com/qashto/Super_Mario_Sunshine_UHD_Texture_Pack/releases/tag/2.1.1)
and `sms-hd-texture-extras-1.1.1.zip` from the extras'
[release 1.1.1](https://github.com/chasem-dev/sms-hd-texture-extras/releases/tag/v1.1.1).
2. Unpack both on your computer (7-Zip on Windows; a Mac opens the `.7z` with
The Unarchiver or `tar -xf GMS.7z`).
3. Copy them to the console so that you have:
```text
/data/homebrew/PPSA99312/mods/textures/GMS/ (the GMS folder from GMS.7z's Textures folder)
/data/homebrew/PPSA99312/mods/textures/sms-hd-texture-extras/ (the extras' zip, unpacked)
```
The packs are not part of this project and are not stored by it: they belong
to their authors.
**The first visit to each area is slower.** The UHD pack's largest images
(4096 to 16384 pixels) are more than the console's graphics driver can hold,
so the title halves each one to 2048 pixels or less the first time it is used
and keeps the result in `userdata/.cache`. After that, loads read the small
copy. Deleting that folder only means the work is done again.
## Status
@@ -79,31 +161,26 @@ read in the title's log. Nothing else is claimed.
| Start-up | The logos, the opening movie, the title screen, file select; a saved file loaded | A new game from an empty memory card |
| Rendering | 3840x2160; Delfino Plaza and Bianco Hills episode 1 look right; the plaza's gates show their movie of the level beyond | Everything past Bianco Hills episode 1; a comparison with a GameCube |
| Frame rate | A locked 60 frames a second (16.7 ms) in the plaza and Bianco Hills, at 2160p, with the HD texture pack | Later, heavier areas; a long session |
| Loading | Delfino Plaza in about 5 seconds with the console-sized HD pack; a "LOADING..." notice during long loads | |
| Loading | Delfino Plaza in about 6 seconds with the HD pack (9 the first time, while its largest images are halved); a "LOADING..." notice during long loads | |
| Input | Walking, jumping, the camera, FLUDD, menus, the DualSense's own button prompts | Every button in play; rumble; a second controller |
| Settings | The touch pad's menu: inverted FLUDD aim and camera, camera shake, the performance overlay, resolution, frame rate; choices kept across a restart | Every row; the cheats below them |
| Saving | A save made earlier loads | Saving in play, checked after a restart |
| Save states | Saving with L1 + R1 in a level, the pause menu's list with pictures, loading a state, and loading after a restart | States across many levels; a state from another build (loads the position only, by design) |
| The map (R1) | The wipe into it, now quick | |
| Sound | Music, effects and the movies' sound play | A careful listen for faults |
| Texture packs | The UHD pack and the HD extras, sized for the console (see below) | Other packs |
| Memory | Two levels and back without running out, with the console-sized pack | Many levels in one session |
| Builder kit | Built without Docker on a Mac (Apple silicon, macOS's own tools) and on Linux (arm64), and the Mac's title played on the console. On x86-64 Linux: LLVM's build fetched and run, a game file compiled identically, `eboot.bin` made byte for byte the same | A whole build on Windows (WSL) or x86-64 Linux; the `--docker` route |
| Texture packs | The UHD pack and the HD extras as published, their largest images halved on the console (see below) | Other packs |
| Memory | Two levels and back without running out, with the HD pack at the sizes the console uses | Bianco Hills since the console does the halving itself; many levels in one session |
### Known problems
- **Only the start of the game has been played.** Anything from Bianco Hills
episode 2 onward is untested on a PS5.
- **Large texture packs must be shrunk.** The console's driver needs about ten
times a texture's size in memory. The UHD pack as published made loads take
17 seconds and ran the console out of memory in the second level. The kit
makes a console-sized copy for you.
- **Camera shake is off to begin with.** The game's earthquakes (a boss
landing, a building rising) are strong on a large screen. Turn them back on
in the settings menu if you want the original feel.
- **Save states belong to the build that saved them.** L1 + R1 saves one in a
- **Save states belong to the release that saved them.** L1 + R1 saves one in a
level, and loads one from the pause menu (sms-pc-port's own). A state loads
exactly only in the build that saved it: after a rebuild it puts Mario back
exactly only in the release that saved it: in a newer one it puts Mario back
where he was and keeps the progress, but the level starts afresh. Keep a
copy of your memory card save as well.
@@ -149,11 +226,11 @@ tools; no Vulkan driver is linked) and the PS5 OpenGL SDK 1.0.2.
```sh
git clone https://github.com/griches/PS5_Sunshine.git && cd PS5_Sunshine
git clone https://github.com/chasem-dev/sms-pc-port.git vendor/sms-pc-port
git -C vendor/sms-pc-port checkout c8fb4a3e81a2176588574395302ecf862f9a440a # see ps5/kit: REVISIONS
git -C vendor/sms-pc-port checkout c8fb4a3e81a2176588574395302ecf862f9a440a # the revision SOURCE.txt names
git -C vendor/sms-pc-port submodule update --init decomp
export PS5_VULKAN_DIR=/path/to/PS5_Vulkan PS5_OPENGL_PREFIX=/path/to/ps5-opengl-sdk/sdk
ps5/tools/build.sh # applies the patch, builds dist/PPSA99312/
ps5/tools/package-kit.sh # the builder kit, dist/kit/
ps5/tools/build.sh # applies the patch, builds dist/PPSA99312/
ps5/tools/package-release.sh # the release archive, dist/Sunshine-PS5-v<version>.zip
```
`ps5/tools/setup-source.sh --save` writes changes made under
-29
View File
@@ -1,29 +0,0 @@
# Super Mario Sunshine for the PlayStation 5 - the builder kit's share of the build: the game's code alone.
#
# kit/build-title.sh configures the PC port (fetched and patched on the machine
# that runs it) with the payload SDK's toolchain and this file as
# CMAKE_PROJECT_sms_port_INCLUDE, then builds the one target sms_game: the
# archive of the game's own code, which is the part a kit cannot bring. The
# flags are the ones ps5-game.cmake gives the full build, so the archive is the
# one the kit's other objects were linked with when it was tested.
#
# SPDX-License-Identifier: MIT
if(NOT PS5_KIT_DIR)
message(FATAL_ERROR "ps5-kit-game.cmake: PS5_KIT_DIR is not set (use the kit's build-title.sh)")
endif()
add_compile_options(-ffunction-sections -fdata-sections -fno-omit-frame-pointer)
set(CMAKE_C_COMPILER_LAUNCHER sh ${PS5_KIT_DIR}/ps5/tools/cc-init-array.sh)
set(CMAKE_CXX_COMPILER_LAUNCHER sh ${PS5_KIT_DIR}/ps5/tools/cc-init-array.sh)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(BUILD_SHARED_LIBS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
set(SMS_ARCH 64 CACHE STRING "" FORCE)
set(SMS_GX_BUILD_TESTS OFF CACHE BOOL "" FORCE)
# The port's other targets are configured but never built here: SDL2 only has
# to exist by name for them.
if(NOT TARGET SDL2::SDL2)
add_library(SDL2::SDL2 INTERFACE IMPORTED GLOBAL)
endif()
-25
View File
@@ -1,25 +0,0 @@
# Super Mario Sunshine for the PlayStation 5 - the builder kit's container: a Linux with what
# build-title.sh needs (LLVM 22, CMake, Ninja, git, Python 3, a host C++ compiler) and the kit.
#
# docker build -t sunshine-ps5-kit .
# docker run --rm -v "$PWD/out":/out sunshine-ps5-kit /out
#
# make-title.sh does both. Nothing of the game is in the image: its source code
# is fetched when the container runs, and what it makes goes to /out.
#
# SPDX-License-Identifier: MIT
FROM ubuntu:24.04
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates wget git patch python3 g++ zlib1g-dev cmake ninja-build \
python3-pip python3-pil 7zip \
&& pip3 install --break-system-packages --no-cache-dir etcpak \
&& wget -qO /etc/apt/trusted.gpg.d/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key \
&& echo "deb https://apt.llvm.org/noble/ llvm-toolchain-noble-22 main" > /etc/apt/sources.list.d/llvm.list \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends clang-22 lld-22 llvm-22 \
&& rm -rf /var/lib/apt/lists/*
COPY . /kit
RUN bash /kit/build-title.sh --tools
WORKDIR /out
ENTRYPOINT ["bash", "/kit/build-title.sh"]
-319
View File
@@ -1,319 +0,0 @@
# Super Mario Sunshine for the PlayStation 5: builder kit
This kit builds a native PS5 version of Super Mario Sunshine (GameCube) on
your own computer, for a PS5 that has homebrew enabled. It plays **your own
copy** of the game, from your own disc image, at 4K and 60 frames a second.
You run one command. Your computer does the rest in about 5 to 20 minutes.
Nothing needs installing first on a Mac or Linux beyond what the steps below
say; on Windows it runs in WSL.
> **There is no game in this kit.** No game code, no game data, nothing of
> Nintendo's. The build fetches the public source code it needs and compiles
> it on your machine, and you supply the disc image. What the kit makes is
> yours alone: **never share the folder it produces, or its `eboot.bin`.**
---
## Before you start
You need three things.
**1. A PS5 that runs homebrew**, with:
- etaHEN (or an equivalent)
- ShadowMountPlus, which shows folders in `/data/homebrew` as games
- an FTP server running on the console
**2. Your disc image of the game.** The North American release (`GMSE01`,
revision 0), as `.iso`, `.gcm` or Dolphin's `.ciso`. Other regions are not
supported.
**3. A computer**, one of:
- a Mac with Apple silicon (M1 or later), macOS 13 or later
- Windows 10 or 11, with WSL (Windows' own Linux, installed in step 2)
- Linux on x86_64 or arm64 (Ubuntu, Debian, Fedora, ...)
with an internet connection, about 6 GB of free disk space and 8 GB of memory
or more. (An Intel Mac works too if you install LLVM 22 yourself: see
*If something goes wrong*.)
---
## Step 1: Unzip the kit
Unzip `sunshine-ps5-kit-<version>.zip` anywhere. You get a folder with this
`README.md`, `make-title.sh` and the rest of the kit in it.
## Step 2: Get your computer ready (once)
**Mac.** Open Terminal and run:
```sh
xcode-select --install
```
and click Install. These are Apple's Command Line Tools (git, Python and a
compiler). If it says they are already installed, you are ready.
**Windows.** Open PowerShell **as administrator** and run:
```powershell
wsl --install
```
Restart when it asks, then open **Ubuntu** from the Start menu and choose a
user name and password. Then, in Ubuntu, install what the build uses and the
compiler, LLVM 22, from [apt.llvm.org](https://apt.llvm.org) (LLVM's own
packages for Ubuntu):
```sh
sudo apt update && sudo apt install -y git python3 patch curl xz-utils unzip lsb-release wget gnupg software-properties-common
curl -fsSL https://apt.llvm.org/llvm.sh -o /tmp/llvm.sh && sudo bash /tmp/llvm.sh 22 && sudo apt install -y llvm-22
```
Move the kit into Ubuntu's own disk (building from the Windows drive is
several times slower). If you unzipped it to your Downloads folder:
```sh
cp -r /mnt/c/Users/<you>/Downloads/sunshine-ps5-kit-<version> ~/
cd ~/sunshine-ps5-kit-<version>
```
**Linux.** You need git, Python 3, `patch` and curl, and LLVM 22 is best
installed from [apt.llvm.org](https://apt.llvm.org). On Ubuntu or Debian:
```sh
sudo apt install -y git python3 patch curl xz-utils lsb-release wget gnupg software-properties-common
curl -fsSL https://apt.llvm.org/llvm.sh -o /tmp/llvm.sh && sudo bash /tmp/llvm.sh 22 && sudo apt install -y llvm-22
```
On another Linux, the build fetches LLVM's own build of LLVM 22 instead, if
the libraries it needs are there (it says what to do if they are not).
## Step 3: Build the title
Open a terminal **in the kit's folder** (on Windows, the Ubuntu window from
step 2) and run:
```sh
./make-title.sh
```
The first time, it fetches what it needs and your computer does not have: on a
Mac the compiler (LLVM 22, about 1.5 GB), and CMake and Ninja (small build
tools), from their projects' own releases. It checks each download and keeps
it for next time (in `~/Library/Caches/sunshine-ps5-kit` on a Mac,
`~/.cache/sunshine-ps5-kit` on Linux). It installs nothing system-wide and
never asks for your password.
### What you will see
| Stage | What it does | How long |
| --- | --- | --- |
| First run only | Fetches what is missing: on a Mac the compiler, CMake and Ninja (about 1.6 GB) | a few minutes |
| `fetching` | Downloads the PC port and the decompilation from GitHub | a minute |
| `configuring` | Prepares the build | under a minute |
| `compiling the game's code` | Compiles about 600 files for the PS5. **This is the long one.** | 3 to 15 minutes |
| `linking` | Joins it to the kit's parts and makes `eboot.bin` | under a minute |
It is finished when it prints the path of a folder ending in `PPSA99312`.
## Step 4: Copy it to the PS5
The result is the folder `out/PPSA99312` inside the kit's folder (about 50 MB).
On Windows, open it from File Explorer at
`\\wsl$\Ubuntu\home\<you>\sunshine-ps5-kit-<version>\out`.
With an FTP program (FileZilla, Cyberduck, WinSCP), connect to your PS5 and
copy that **whole folder** into `/data/homebrew/`. Then copy **your disc
image** into it, beside `eboot.bin`, so that you end up with:
```text
/data/homebrew/PPSA99312/eboot.bin
/data/homebrew/PPSA99312/sce_sys/
/data/homebrew/PPSA99312/sce_module/
/data/homebrew/PPSA99312/res/
/data/homebrew/PPSA99312/licenses/
/data/homebrew/PPSA99312/GMSE01.iso (your disc image; any name)
```
## Step 5: Play
"Super Mario Sunshine" appears on the PS5's home screen. Start it like any game.
| DualSense | In the game |
| --- | --- |
| Cross | A (jump, confirm) |
| Circle | B (dive, back) |
| Square | X |
| Triangle | Y (first-person view) |
| R1 | Z |
| L1 + R1 | **Save state** (in a level); in the pause menu, the list of save states to load |
| L2, R2 | L, R (R sprays FLUDD) |
| Options | Start (pause, map) |
| Touch pad button | **Settings** |
| Sticks, D-pad | as on a GameCube |
The prompts the game shows on screen are the DualSense's own.
### The settings menu
Press the touch pad's button at any time. The D-pad moves and changes, Cross
steps through a row's choices, Circle or the touch pad closes it.
| Row | What it does |
| --- | --- |
| Invert FLUDD Aim X / Y | flips the left stick while you aim FLUDD |
| Invert Camera X / Y | flips the right stick's camera |
| Camera Shake | OFF, HALF or FULL: the game's earthquakes (off to begin with) |
| Performance Overlay | OFF, MINIMAL (the frame rate alone) or FULL |
| Heat Haze | the heat shimmer |
| Resolution \* | 960p, 1440p, 1920p or 2160p |
| Frame Rate \* | 30 or 60 |
| Picture \* | 4:3 or 16:9 |
| Smoothing (FXAA) \* | on or off |
Rows marked \* take effect the next time you start the game. Below them are
the PC port's cheats (god mode, infinite lives and so on).
Your saves and settings are kept on the console in
`/data/homebrew/PPSA99312/userdata/`.
### Save states
Hold L1 and press R1 in a level to save the moment. Pause (Options) and press
L1 + R1 again to see your save states, with a picture of each, and load one.
They are kept beside your memory card, in a `savestates` folder in
`/data/homebrew/PPSA99312/userdata/`. A state loads exactly only in the build
that saved it; after you rebuild, older states put Mario back where he was in
that level and keep your progress, but the level starts afresh.
---
## Optional: HD textures
The port loads texture packs made for the Dolphin emulator. One command
downloads the [Super Mario Sunshine UHD Texture Pack](https://github.com/qashto/Super_Mario_Sunshine_UHD_Texture_Pack)
(by qashto and razius) and the [HD texture extras](https://github.com/chasem-dev/sms-hd-texture-extras)
(by chasem-dev) from their authors and makes a copy sized for the console:
```sh
./make-texture-pack.sh
```
(On Windows, in the Ubuntu window, as in step 3.) The first time it fetches
the two Python packages it uses, Pillow and etcpak, and on Linux 7-Zip, into
the same cache as the compiler.
It downloads about 1 GB, needs about 5 GB of free disk space while it works,
and leaves `out/textures` (1.3 GB). Copy that folder to the console as:
```text
/data/homebrew/PPSA99312/mods/textures/
```
The packs are not part of this kit and are not stored with it: they belong to
their authors, and the copy you make is for your own use.
**Why a console-sized copy?** The console's graphics driver needs about ten
times a texture's size in memory and takes texture data slowly. With the pack
as published (images up to 8192 pixels), Delfino Plaza took 17 seconds to load
and a second level ran the console out of memory. With nothing larger than
2048 pixels the plaza loads in about 5 seconds. Any other Dolphin pack can be
shrunk the same way with `tools/shrink-texture-pack.py SOURCE DEST`.
---
## If something goes wrong
**"this Mac needs Apple's Command Line Tools"**
Run `xcode-select --install`, wait for it to finish, and run the command
again.
**"missing: git python3 ..."**
Install what it names (step 2 has the command for Ubuntu and Debian).
**"LLVM's own Linux build would not run here: it needs libxml2.so.2"**
Newer Linux releases (Ubuntu 26.04, Debian 13) no longer have that library.
Install LLVM 22 from apt.llvm.org as in step 2; the build then uses it.
**"LLVM publishes no build for Darwin on x86_64"** (an Intel Mac)
Install LLVM 22 with Homebrew, then point the kit at it:
```sh
brew install llvm@22 lld@22
PS5_CLANG=$(brew --prefix llvm@22)/bin/clang ./make-title.sh
```
**The fetch fails**
The build needs to reach github.com the first time. Check the connection and
run the command again.
**The build stops part way with "Killed"**
It ran out of memory. Run fewer compilers at once:
```sh
JOBS=4 ./make-title.sh
```
On Windows, WSL may also need more memory: see Microsoft's notes on
`.wslconfig`.
**It was interrupted**
Run the same command again. It carries on from where it stopped, including a
download.
**A download fails or "did not download correctly"**
Run the command again. Every download is checked against the checksum the kit
expects, and a bad one is thrown away.
**The game does not appear on the PS5**
Check the folder is exactly `/data/homebrew/PPSA99312/` with `eboot.bin`
directly inside it (not `PPSA99312/PPSA99312/`), and that ShadowMountPlus is
running.
**It starts and closes at once**
The disc image is missing, or is not the North American release. It must be
in `/data/homebrew/PPSA99312/`, beside `eboot.bin`.
## Updating to a newer kit
Unzip the new kit, build again, and copy the new `out/PPSA99312` over the old
one on the console. Your saves, settings, disc image and texture packs are
left alone.
## With Docker instead
If you would rather build in a container (Docker Desktop or Podman), the kit
still can: `./make-title.sh --docker` and `./make-texture-pack.sh --docker`.
On Windows without WSL, in PowerShell:
```powershell
docker build -t sunshine-ps5-kit .
docker run --rm -v ${PWD}\out:/out sunshine-ps5-kit /out
```
---
## What is in the kit
| Path | What |
| --- | --- |
| `make-title.sh` | Builds the title (`--docker`: in a container, with `Dockerfile`). |
| `build-title.sh` | The build itself: fetch, patch, compile, link. |
| `tools.sh` | Finds LLVM 22, CMake and Ninja, or fetches them once, checked. |
| `lib/` | The port's own compiled parts: the platform layer, the graphics layer, SDL2. |
| `opengl/` | The PS5 OpenGL SDK's driver, as this port links it. |
| `vulkan/` | PS5_Vulkan's link recipe and the tool that makes `eboot.bin` (no Vulkan driver), built for Linux in `vulkan/prebuilt/`. |
| `sdk/` | The payload SDK's compiler wrappers, headers, libraries and link scripts. |
| `ps5/` | The console's patch to the PC port, the link script, the title's icon and pictures. |
| `make-texture-pack.sh`, `tools/` | Downloads an HD texture pack and sizes it for the console. |
| `REVISIONS`, `SOURCES.txt`, `LICENSING.md`, `licenses/` | What the build fetches, where each part's source is, and its licence. |
The project, with what works, what does not, and how the port is made:
<https://github.com/griches/PS5_Sunshine>
This project is not affiliated with, endorsed or sponsored by Nintendo or Sony
Interactive Entertainment.
-154
View File
@@ -1,154 +0,0 @@
#!/usr/bin/env bash
# Super Mario Sunshine for the PlayStation 5 - the builder kit: make the title on your own machine.
#
# build-title.sh [OUT] OUT/PPSA99312/ (OUT defaults to ./out)
# build-title.sh --tools only build the kit's host tools (the container's image does)
#
# The kit is a build of the port without the game's own code. This script
# fetches the two public source trees that code is compiled from (the PC port,
# chasem-dev/sms-pc-port, and the decompilation it builds, chasem-dev/sms-english,
# both at the revisions SOURCES.txt names), applies the console's patch,
# compiles the game's code with the kit's SDK and links it with the kit's
# objects, by the recipe the kit was tested with. Nothing of the game's data is
# involved: the title plays your own disc image, which you copy beside
# eboot.bin on the console.
#
# Runs on macOS (Apple silicon; with Apple's Command Line Tools) and on Linux
# (x86_64 or arm64; on Windows, in WSL), with git, python3, patch and curl.
# LLVM 22, CMake and Ninja come from this machine if it has them, and are
# otherwise fetched once into a cache (tools.sh says from where). On Linux the
# kit's own ps5-native-tool is used; elsewhere it is compiled here.
# make-title.sh --docker runs this in a container instead.
#
# JOBS compilers at once (default: one a processor)
# PS5_CLANG LLVM 22's clang, to use instead of looking for one
# KIT_CACHE where fetched tools are kept
#
# SPDX-License-Identifier: MIT
set -euo pipefail
kit=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
sdk="$kit/sdk" vulkan="$kit/vulkan"
say() { printf '==> %s\n' "$*"; }
fail() { echo "build-title.sh: $*" >&2; exit 2; }
# shellcheck source=tools.sh
source "$kit/tools.sh"
# ELF -> the console's SELF: the kit's own build for Linux, or compiled here
build_tools() {
local host_cxx=${CXX:-c++} native="$vulkan/tooling/native"
local prebuilt="$vulkan/prebuilt/$kit_os-$kit_arch/ps5-native-tool"
[[ $kit_arch == arm64 && $kit_os == Linux ]] && prebuilt="$vulkan/prebuilt/Linux-aarch64/ps5-native-tool"
if [[ ! -x $vulkan/build/host/ps5-native-tool && -f $prebuilt ]]; then
mkdir -p "$vulkan/build/host"
cp "$prebuilt" "$vulkan/build/host/ps5-native-tool"
chmod +x "$vulkan/build/host/ps5-native-tool"
fi
if [[ ! -x $vulkan/build/host/ps5-native-tool ]]; then
command -v "$host_cxx" > /dev/null || fail "a C++ compiler (c++) is needed to build the kit's ps5-native-tool"
mkdir -p "$vulkan/build/host"
"$host_cxx" -std=c++20 -O2 "$native/native_app_builder.cpp" "$native/self_container.cpp" \
"$native/elf_object.cpp" "$native/sce_module_writer.cpp" -lz -o "$vulkan/build/host/ps5-native-tool"
fi
}
if [[ ${1:-} == --tools ]]; then
build_tools
exit 0
fi
case ${1:-} in -h | --help) sed -n '2,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
out=${1:-$PWD/out}
mkdir -p "$out"
out=$(cd -- "$out" && pwd)
work="$out/.work"
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)}
title_id=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["titleId"])' "$kit/ps5/sce_sys/param.json")
app="$out/$title_id"
mkdir -p "$work/bin" "$app"
# ---- the tools: this machine's, or fetched once (tools.sh)
# (an unzip that dropped the executable bits: the SDK's wrappers are run directly)
chmod +x "$sdk"/bin/* 2>/dev/null || true
kit_check_basics || exit 2
kit_find_llvm || exit 2
kit_find_cmake || exit 2
kit_find_ninja || exit 2
real_clang=$kit_clang
llvm_bin=$(dirname "$real_clang")
say "compiler: $real_clang; $(cmake --version | head -1); ninja $(ninja --version)"
# ---- the compiler: LLVM 22, with the kit's builtins for the console's processor
export LLVM_CONFIG="$llvm_bin/llvm-config"
cat > "$work/bin/clang" <<WRAPPER
#!/bin/sh
if [ "\$#" -eq 1 ] && [ "\$1" = "--print-resource-dir" ]; then echo "$kit/clang-rt"; exit 0; fi
exec "$real_clang" "\$@"
WRAPPER
chmod +x "$work/bin/clang"
export PS5_CLANG="$work/bin/clang"
for tool in git cmake ninja patch python3; do
command -v "$tool" > /dev/null || fail "$tool was not found"
done
build_tools
# ---- the sources of the game's code, at the revisions the kit was built from
port_url=$(sed -n 's/^port_url=//p' "$kit/REVISIONS")
port_rev=$(sed -n 's/^port_rev=//p' "$kit/REVISIONS")
decomp_rev=$(sed -n 's/^decomp_rev=//p' "$kit/REVISIONS")
source="$work/sms-pc-port"
stamp="$port_rev $decomp_rev $(kit_sha256 "$kit/ps5/patches/sms-pc-port.patch")"
if [[ $(cat "$work/.source-stamp" 2>/dev/null) != "$stamp" ]]; then
say "fetching the PC port and the decompilation (about 150 MB)"
rm -rf -- "$source" "$work/game"
git init -q "$source"
git -C "$source" remote add origin "$port_url"
git -C "$source" fetch -q --depth 1 origin "$port_rev"
git -C "$source" -c advice.detachedHead=false checkout -q FETCH_HEAD
git -C "$source" submodule update -q --init --depth 1 decomp
[[ $(git -C "$source/decomp" rev-parse HEAD) == "$decomp_rev" ]] ||
fail "the decompilation is at $(git -C "$source/decomp" rev-parse HEAD), not $decomp_rev"
say "applying the console's patch"
git -C "$source" apply --whitespace=nowarn "$kit/ps5/patches/sms-pc-port.patch"
echo "$stamp" > "$work/.source-stamp"
fi
# ---- the game's code
build="$work/game"
if [[ ! -f $build/build.ninja ]]; then
say "configuring"
mkdir -p "$build"
cmake -S "$source" -B "$build" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE="$sdk/toolchain/prospero.cmake" \
-DCMAKE_PROJECT_sms_port_INCLUDE="$kit/ps5/cmake/ps5-kit-game.cmake" \
-DPS5_KIT_DIR="$kit" > "$build/configure.log" 2>&1 ||
{ tail -40 "$build/configure.log" >&2; mv "$build" "$build.failed-$$"; rm -rf "$build.failed-$$"; fail "the configuration failed"; }
fi
say "compiling the game's code, $jobs files at a time (this is the long step)"
ninja -C "$build" -j "$jobs" sms_game
[[ -f $build/libsms_game.a ]] || fail "the game's code did not build"
# ---- the link: the kit's objects in the order they were tested in, with that archive
say "linking"
inputs=()
while IFS= read -r line; do
case $line in
@GAMECODE@) inputs+=("$build/libsms_game.a") ;;
-*) inputs+=("$line") ;;
*) inputs+=("$kit/lib/$line") ;;
esac
done < "$kit/lib/link.txt"
PS5_DIST_DIR="$out" PS5_OPENGL_PREFIX="$kit/opengl/sdk" SMS_SAVESTATE_RANGES="$source/tools/savestate_ranges.py" \
bash "$kit/ps5/tools/link-title.sh" "$work" "$vulkan" "$sdk" "${inputs[@]}"
mkdir -p "$app/res"
cp -R "$kit/ps5/res/." "$app/res/"
cp "$kit/LICENSING.md" "$app/licenses/"
# a container runs as root: hand the result to whoever owns the output folder
if [[ $(id -u) -eq 0 && $kit_os == Linux ]]; then
owner=$(stat -c '%u:%g' "$out")
[[ $owner == 0:0 ]] || chown -R "$owner" "$out" 2>/dev/null || true
fi
say "$app"
echo " copy the folder $title_id to /data/homebrew/ on the console (FTP), and your own"
echo " disc image (USA, GMSE01: .iso, .gcm or .ciso) into it, beside eboot.bin."
echo " The title you built holds the game's code: it is yours to play, not to share."
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
# Super Mario Sunshine for the PlayStation 5 - an HD texture pack for the console.
#
# make-texture-pack.sh [OUT] OUT/textures/ (OUT defaults to ./out), made on this machine
# make-texture-pack.sh --docker [OUT] the same, in the kit's container (Docker, or Podman: CONTAINER=podman)
#
# Optional. Downloads the UHD texture pack and the HD extras from their
# authors, makes a copy sized for the console (tools/get-texture-pack.py) and
# leaves it in OUT/textures: copy that to the console as
# /data/homebrew/PPSA99312/mods/textures. Needs Python 3, an internet
# connection and about 5 GB of free disk space. The Python packages it uses
# (Pillow, etcpak) and, on Linux, 7-Zip are fetched once into the kit's cache
# (tools.sh); a Mac's own tar opens the pack.
#
# SPDX-License-Identifier: MIT
set -euo pipefail
kit=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
if [[ ${1:-} == --docker ]]; then
shift
out=${1:-$PWD/out}
mkdir -p "$out"
out=$(cd -- "$out" && pwd)
container=${CONTAINER:-docker}
command -v "$container" > /dev/null || { echo "make-texture-pack.sh: $container was not found: install Docker Desktop (or Podman), or run without --docker" >&2; exit 2; }
tag=sunshine-ps5-kit:$(<"$kit/VERSION")
"$container" image inspect "$tag" > /dev/null 2>&1 || "$container" build -t "$tag" "$kit"
exec "$container" run --rm --entrypoint python3 -v "$out":/out "$tag" /kit/tools/get-texture-pack.py /out
fi
out=${1:-$PWD/out}
mkdir -p "$out"
out=$(cd -- "$out" && pwd)
# shellcheck source=tools.sh
source "$kit/tools.sh"
command -v python3 > /dev/null || { echo "make-texture-pack.sh: Python 3 is needed" >&2; exit 2; }
if [[ $kit_os == Darwin ]] && ! xcode-select -p > /dev/null 2>&1; then
echo "make-texture-pack.sh: this Mac needs Apple's Command Line Tools (for Python): run xcode-select --install" >&2
exit 2
fi
# Pillow and etcpak, for this Python
packages=$(python3 "$kit/tools/fetch-wheels.py" "$kit_cache/python") || exit 2
export PYTHONPATH="$packages${PYTHONPATH:+:$PYTHONPATH}"
# 7-Zip on Linux (GNU tar cannot open a .7z; a Mac's tar can)
if [[ $kit_os == Linux ]] && ! command -v 7z > /dev/null && ! command -v 7zz > /dev/null && ! command -v 7za > /dev/null; then
case $kit_arch in
x86_64) asset=7z2604-linux-x64.tar.xz sha=fc0327ba27e89bd086cf426dff17d77de582953cdbbc10a6576540a06853ffcd ;;
aarch64 | arm64) asset=7z2604-linux-arm64.tar.xz sha=5b0ac3aa91c1e3499f011d6af69d7f6b4de3ef2f011d9e2ff44c58ebd04aa388 ;;
*) echo "make-texture-pack.sh: 7-Zip is needed (7z, 7zz or 7za)" >&2; exit 2 ;;
esac
dest="$kit_cache/7zip-26.04"
if [[ ! -x $dest/7zz ]]; then
echo "==> fetching 7-Zip 26.04"
kit_fetch "https://github.com/ip7z/7zip/releases/download/26.04/$asset" "$sha" "$kit_cache/$asset" || exit 2
mkdir -p "$dest"
tar -xJf "$kit_cache/$asset" -C "$dest" 7zz
rm -f "$kit_cache/$asset"
fi
PATH=$dest:$PATH
fi
exec python3 "$kit/tools/get-texture-pack.py" "$out"
-31
View File
@@ -1,31 +0,0 @@
#!/usr/bin/env bash
# Super Mario Sunshine for the PlayStation 5 - the builder kit: make the title.
#
# make-title.sh [OUT] OUT/PPSA99312/ (OUT defaults to ./out), built on this machine
# make-title.sh --docker [OUT] the same, built in a container (Docker, or Podman: CONTAINER=podman)
#
# On this machine (macOS on Apple silicon, Linux, or WSL on Windows) it runs
# build-title.sh, which uses the compiler and build tools this machine has, or
# fetches them once (LLVM 22 is about 1.5 GB). Each title build fetches the
# game's source code from GitHub the first time (it is kept in OUT/.work
# afterwards). See README.md.
#
# JOBS compilers at once (default: one a processor)
#
# SPDX-License-Identifier: MIT
set -euo pipefail
kit=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
if [[ ${1:-} != --docker ]]; then
exec bash "$kit/build-title.sh" "$@"
fi
shift
out=${1:-$PWD/out}
mkdir -p "$out"
out=$(cd -- "$out" && pwd)
container=${CONTAINER:-docker}
command -v "$container" > /dev/null || { echo "make-title.sh: $container was not found: install Docker Desktop (or Podman), or build without --docker" >&2; exit 2; }
tag=sunshine-ps5-kit:$(<"$kit/VERSION")
"$container" image inspect "$tag" > /dev/null 2>&1 || "$container" build -t "$tag" "$kit"
exec "$container" run --rm ${JOBS:+-e JOBS="$JOBS"} -v "$out":/out "$tag" /out
-243
View File
@@ -1,243 +0,0 @@
# Super Mario Sunshine for the PlayStation 5 - the builder kit's tools: found on this machine, or fetched once.
#
# Sourced by build-title.sh. Sets kit_clang (LLVM 22's clang, with lld and the
# LLVM tools beside it), and puts CMake and Ninja on PATH. Each comes from this
# machine when it has a version that works:
#
# LLVM 22 PS5_CLANG, /usr/lib/llvm-22 (apt.llvm.org), clang-22 on PATH, or
# Homebrew's llvm@22 with lld beside it
# CMake 3.20 to 3.x on PATH (CMake 4 drops what SDL's build still asks for)
# Ninja on PATH
#
# and otherwise from its project's own release for this kind of machine, checked
# against the SHA-256 below and kept in the kit's cache (KIT_CACHE; by default
# ~/Library/Caches/sunshine-ps5-kit on a Mac, ~/.cache/sunshine-ps5-kit on Linux),
# so it is fetched once for every kit. LLVM's release is the large one: about
# 1.5 GB to fetch (1.9 GB for Linux), of which about 450 MB is kept.
#
# On Ubuntu and Debian (and WSL), apt.llvm.org's packages are the better way to
# LLVM 22, and the kit's README says so: LLVM's own Linux build needs
# libxml2.so.2, which Ubuntu 26.04 and Debian 13 no longer have (they moved to
# libxml2.so.16). It is fetched only where the libraries it needs are there.
#
# Bash 3.2 (a Mac's) runs this: no associative arrays.
#
# SPDX-License-Identifier: MIT
kit_llvm_version=22.1.8
kit_cmake_version=3.31.12
kit_ninja_version=1.13.2
kit_os=$(uname -s)
kit_arch=$(uname -m)
case $kit_os-$kit_arch in
Darwin-arm64)
kit_llvm_asset=LLVM-$kit_llvm_version-macOS-ARM64.tar.xz
kit_llvm_sha=f260f4f7c0d430828a81ae8a3826a1d63fc0963ec2459489308cc23b1f7eab4f
;;
Linux-x86_64)
kit_llvm_asset=LLVM-$kit_llvm_version-Linux-X64.tar.xz
kit_llvm_sha=df0e1ecf16caf3489a272a5eea4eec9b0d82878f6477fa309504f918a0006384
;;
Linux-aarch64 | Linux-arm64)
kit_llvm_asset=LLVM-$kit_llvm_version-Linux-ARM64.tar.xz
kit_llvm_sha=805efad2bb91cb4967fa569e0881d10c0f69c04461cf671cccbae19f547acc34
;;
*) kit_llvm_asset= ;; # (an Intel Mac: LLVM publishes no build for it)
esac
case $kit_os-$kit_arch in
Darwin-*)
kit_cmake_asset=cmake-$kit_cmake_version-macos-universal.tar.gz
kit_cmake_sha=799af7fd545db9bf1b9cfe72f8095880e727a2d4e0df0e3dffc3bc7b95c2d3b0
kit_cmake_bin=CMake.app/Contents/bin
kit_ninja_asset=ninja-mac.zip
kit_ninja_sha=c99048673aa765960a99cf10c6ddb9f1fad506099ff0a0e137ad8960a88f321b
;;
Linux-x86_64)
kit_cmake_asset=cmake-$kit_cmake_version-linux-x86_64.tar.gz
kit_cmake_sha=0dc2e9a6860f06bf10bd8fadc03e35d9eeb4df46e33763a7e480e987758f385c
kit_cmake_bin=bin
kit_ninja_asset=ninja-linux.zip
kit_ninja_sha=5749cbc4e668273514150a80e387a957f933c6ed3f5f11e03fb30955e2bbead6
;;
Linux-aarch64 | Linux-arm64)
kit_cmake_asset=cmake-$kit_cmake_version-linux-aarch64.tar.gz
kit_cmake_sha=83f8fd91d2038a56556e1400390fcfe42f79602940c494f6c6f1cdae7f9e7f40
kit_cmake_bin=bin
kit_ninja_asset=ninja-linux-aarch64.zip
kit_ninja_sha=fd2cacc8050a7f12a16a2e48f9e06fca5c14fc4c2bee2babb67b58be17a607fc
;;
*) kit_cmake_asset= kit_ninja_asset= ;;
esac
if [[ -n ${KIT_CACHE:-} ]]; then
kit_cache=$KIT_CACHE
elif [[ $kit_os == Darwin ]]; then
kit_cache=$HOME/Library/Caches/sunshine-ps5-kit
else
kit_cache=${XDG_CACHE_HOME:-$HOME/.cache}/sunshine-ps5-kit
fi
kit_sha256() { python3 -c 'import hashlib,sys; h=hashlib.sha256(); f=open(sys.argv[1],"rb"); [h.update(b) for b in iter(lambda: f.read(1<<20), b"")]; print(h.hexdigest())' "$1"; }
# kit_fetch URL SHA256 FILE: download, check, and keep it only if it matches
kit_fetch() {
local url=$1 sha=$2 file=$3
mkdir -p "$(dirname "$file")"
# (one already here, from a run stopped while unpacking it)
[[ -f $file && $(kit_sha256 "$file") == "$sha" ]] && return 0
if command -v curl > /dev/null; then
curl -fL --retry 3 --progress-bar -o "$file.part" "$url" || { rm -f "$file.part"; return 1; }
elif command -v wget > /dev/null; then
wget -q --show-progress -O "$file.part" "$url" || { rm -f "$file.part"; return 1; }
else
echo "tools.sh: curl or wget is needed to download $url" >&2
return 1
fi
if [[ $(kit_sha256 "$file.part") != "$sha" ]]; then
rm -f "$file.part"
echo "tools.sh: $url did not download correctly (its SHA-256 is not the expected one); try again" >&2
return 1
fi
mv "$file.part" "$file"
}
kit_is_llvm22() {
[[ -x $1 ]] && "$1" --version 2>/dev/null | head -1 | grep -q "version 22\." || return 1
local bin
bin=$(dirname "$(kit_realpath "$1")")
local tool
for tool in ld.lld llvm-ar llvm-nm llvm-config llvm-objcopy llvm-strip; do
[[ -x $bin/$tool ]] || return 1
done
# (lld needs a library clang does not: it has to run too)
"$bin/ld.lld" --version > /dev/null 2>&1
}
# The shared libraries LLVM's own Linux build needs, that this machine lacks
kit_llvm_missing_libraries() {
[[ $kit_os == Linux ]] || return 0
local cache library
cache=$( (ldconfig -p || /sbin/ldconfig -p) 2>/dev/null)
for library in libz.so.1 libstdc++.so.6 libgcc_s.so.1 libxml2.so.2; do
grep -q "^[[:space:]]*$library " <<< "$cache" || printf '%s ' "$library"
done
}
kit_llvm_advice() {
echo "tools.sh: install LLVM 22 from apt.llvm.org (about 150 MB), then run this again:" >&2
echo " sudo apt install -y lsb-release wget gnupg software-properties-common" >&2
echo " curl -fsSL https://apt.llvm.org/llvm.sh -o /tmp/llvm.sh && sudo bash /tmp/llvm.sh 22 && sudo apt install -y llvm-22" >&2
}
kit_realpath() { python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$1"; }
kit_find_llvm() {
local candidate
if [[ -n ${PS5_CLANG:-} ]]; then
kit_is_llvm22 "$PS5_CLANG" && { kit_clang=$(kit_realpath "$PS5_CLANG"); return 0; }
echo "tools.sh: PS5_CLANG ($PS5_CLANG) is not LLVM 22's clang with lld, llvm-ar, llvm-nm, llvm-config, llvm-objcopy and llvm-strip beside it" >&2
return 1
fi
for candidate in /usr/lib/llvm-22/bin/clang "$(command -v clang-22 || true)" \
/opt/homebrew/opt/llvm@22/bin/clang /usr/local/opt/llvm@22/bin/clang \
"$kit_cache/llvm-$kit_llvm_version/bin/clang"; do
[[ -n $candidate ]] && kit_is_llvm22 "$candidate" && { kit_clang=$(kit_realpath "$candidate"); return 0; }
done
[[ -n $kit_llvm_asset ]] || {
echo "tools.sh: LLVM 22 was not found, and LLVM publishes no build for $kit_os on $kit_arch to fetch." >&2
echo "tools.sh: install LLVM 22 with lld (Homebrew: brew install llvm@22 lld@22) and set PS5_CLANG to its clang." >&2
return 1
}
local missing
missing=$(kit_llvm_missing_libraries)
if [[ -n $missing ]]; then
echo "tools.sh: LLVM 22 was not found, and LLVM's own Linux build would not run here: it needs ${missing% }." >&2
kit_llvm_advice
return 1
fi
local dest="$kit_cache/llvm-$kit_llvm_version" top=${kit_llvm_asset%.tar.xz}
echo "==> fetching LLVM $kit_llvm_version, the compiler (about 1.5 GB, once: it is kept in $kit_cache)"
kit_fetch "https://github.com/llvm/llvm-project/releases/download/llvmorg-$kit_llvm_version/$kit_llvm_asset" \
"$kit_llvm_sha" "$kit_cache/$kit_llvm_asset" || return 1
echo "==> unpacking the parts of it the build uses"
rm -rf "$dest.part"
mkdir -p "$dest.part"
local members=() name
for name in clang clang-22 clang++ lld ld.lld llvm-ar llvm-ranlib llvm-nm llvm-config llvm-objcopy llvm-strip \
llvm-readobj llvm-readelf; do
members+=("$top/bin/$name")
done
members+=("$top/lib/clang/22/include")
tar -xJf "$kit_cache/$kit_llvm_asset" -C "$dest.part" "${members[@]}" || return 1
rm -rf "$dest"
mv "$dest.part/$top" "$dest"
rm -rf "$dest.part" "$kit_cache/$kit_llvm_asset"
kit_is_llvm22 "$dest/bin/clang" || {
echo "tools.sh: the LLVM fetched into $dest does not run here" >&2
[[ $kit_os == Linux ]] && kit_llvm_advice
return 1
}
kit_clang=$dest/bin/clang
}
kit_cmake_works() {
local version
version=$("$1" --version 2>/dev/null | head -1 | sed -n 's/^cmake version \([0-9]*\)\.\([0-9]*\).*/\1 \2/p')
[[ -n $version ]] || return 1
set -- $version
[[ $1 -eq 3 && $2 -ge 20 ]]
}
kit_find_cmake() {
local found
found=$(command -v cmake || true)
[[ -n $found ]] && kit_cmake_works "$found" && return 0
local dest="$kit_cache/cmake-$kit_cmake_version"
if [[ ! -x $dest/$kit_cmake_bin/cmake ]]; then
[[ -n $kit_cmake_asset ]] || { echo "tools.sh: CMake 3.20 or later (but not 4) is needed" >&2; return 1; }
echo "==> fetching CMake $kit_cmake_version (about 60 MB, once)"
kit_fetch "https://github.com/Kitware/CMake/releases/download/v$kit_cmake_version/$kit_cmake_asset" \
"$kit_cmake_sha" "$kit_cache/$kit_cmake_asset" || return 1
rm -rf "$dest" "$dest.part"
mkdir -p "$dest.part"
tar -xzf "$kit_cache/$kit_cmake_asset" -C "$dest.part" || return 1
mv "$dest.part/${kit_cmake_asset%.tar.gz}" "$dest"
rm -rf "$dest.part" "$kit_cache/$kit_cmake_asset"
fi
PATH=$dest/$kit_cmake_bin:$PATH
}
kit_find_ninja() {
ninja --version > /dev/null 2>&1 && return 0
local dest="$kit_cache/ninja-$kit_ninja_version"
if [[ ! -x $dest/ninja ]]; then
[[ -n $kit_ninja_asset ]] || { echo "tools.sh: Ninja is needed" >&2; return 1; }
echo "==> fetching Ninja $kit_ninja_version"
kit_fetch "https://github.com/ninja-build/ninja/releases/download/v$kit_ninja_version/$kit_ninja_asset" \
"$kit_ninja_sha" "$kit_cache/$kit_ninja_asset" || return 1
mkdir -p "$dest"
python3 -m zipfile -e "$kit_cache/$kit_ninja_asset" "$dest" && chmod +x "$dest/ninja" || return 1
rm -f "$kit_cache/$kit_ninja_asset"
fi
PATH=$dest:$PATH
}
# What no download supplies: say what to install, for this machine
kit_check_basics() {
local missing=() tool
for tool in git python3 patch tar; do
command -v "$tool" > /dev/null || missing+=("$tool")
done
# a Mac's python3 and git are stand-ins until the Command Line Tools are installed
if [[ $kit_os == Darwin ]] && ! xcode-select -p > /dev/null 2>&1; then
echo "build-title.sh: this Mac needs Apple's Command Line Tools (git, Python and a C++ compiler)." >&2
echo "build-title.sh: run xcode-select --install and start this again once they are installed." >&2
return 1
fi
if [[ ${#missing[@]} -gt 0 ]]; then
echo "build-title.sh: missing: ${missing[*]}" >&2
[[ $kit_os == Linux ]] && echo "build-title.sh: on Ubuntu or Debian (and WSL): sudo apt install git python3 patch xz-utils curl" >&2
return 1
fi
}
+212 -7
View File
@@ -706,10 +706,215 @@ index 393b8f9..16f82e6 100644
signalFlag(*ready);
uint64_t tail = s_tail.load();
diff --git a/platform/gx/src/gx_hires.cpp b/platform/gx/src/gx_hires.cpp
index bcd4174..3887f46 100644
index bcd4174..937d1c2 100644
--- a/platform/gx/src/gx_hires.cpp
+++ b/platform/gx/src/gx_hires.cpp
@@ -1049,6 +1049,9 @@ static bool beginUpload(Upload& u) {
@@ -348,6 +348,8 @@ static void queryFormats() {
// file's own mip levels, or 32-bit RGBA/BGRA. Blocks the GL cannot take are
// decoded here; the levels a file leaves out are made by completeChain.
enum { DDS_BC1 = BLOCK_BC1, DDS_BC2 = BLOCK_BC2, DDS_BC3 = BLOCK_BC3, DDS_BC7 = BLOCK_BC7, DDS_RGBA, DDS_BGRA };
+static const GLenum kGl[] = {0, 0x83F1, 0x83F2, 0x83F3, 0x8E8C}; // S3TC DXT1/3/5 RGBA, BPTC UNORM
+static bool gpuTakes(int fmt) { return (fmt >= DDS_BC1 && fmt <= DDS_BC3 && s_s3tc) || (fmt == DDS_BC7 && s_bptc); }
static bool readFile(const std::string& path, std::vector<uint8_t>& out) {
FILE* f = fopen(path.c_str(), "rb");
@@ -392,8 +394,7 @@ static bool decodeDds(const std::string& path, Loaded* L) {
for (int m = std::max(w, h); m > 1; m >>= 1) full++;
// Supported blocks stay compressed whatever levels the file has: the
// missing ones are encoded in the same format (completeChain).
- bool gpu = (fmt <= DDS_BC3 && s_s3tc) || (fmt == DDS_BC7 && s_bptc);
- static const GLenum kGl[] = {0, 0x83F1, 0x83F2, 0x83F3, 0x8E8C}; // S3TC DXT1/3/5 RGBA, BPTC UNORM
+ bool gpu = gpuTakes(fmt);
L->w = w;
L->h = h;
L->compressed = gpu ? kGl[fmt] : 0;
@@ -660,6 +661,23 @@ static std::vector<uint8_t> nextBlockLevel(int fmt, const std::vector<uint8_t>&
return dst;
}
+// The next level of an RGBA one (lw x lh), by the same 2x2 box filter.
+static std::vector<uint8_t> nextRgbaLevel(const std::vector<uint8_t>& src, int lw, int lh) {
+ int nw = std::max(1, lw / 2), nh = std::max(1, lh / 2);
+ std::vector<uint8_t> dst(size_t(nw) * nh * 4);
+ for (int y = 0; y < nh; y++)
+ for (int x = 0; x < nw; x++) {
+ int x0 = std::min(2 * x, lw - 1), x1 = std::min(2 * x + 1, lw - 1);
+ int y0 = std::min(2 * y, lh - 1), y1 = std::min(2 * y + 1, lh - 1);
+ for (int c = 0; c < 4; c++) {
+ int sum = src[(size_t(y0) * lw + x0) * 4 + c] + src[(size_t(y0) * lw + x1) * 4 + c] +
+ src[(size_t(y1) * lw + x0) * 4 + c] + src[(size_t(y1) * lw + x1) * 4 + c];
+ dst[(size_t(y) * nw + x) * 4 + c] = uint8_t((sum + 2) / 4);
+ }
+ }
+ return dst;
+}
+
// Complete every replacement's mip chain down to 1x1 with 2x2 box filtering,
// whatever levels the pack supplied: the sampler minifies a replacement as far
// as its original (samplerFor), and without the lower levels a far wall
@@ -683,22 +701,9 @@ static void completeChain(Loaded* L, bool idle = false) {
return;
}
while (lw > 1 || lh > 1) {
- int nw = std::max(1, lw / 2), nh = std::max(1, lh / 2);
- const std::vector<uint8_t>& src = L->levels.back();
- std::vector<uint8_t> dst(size_t(nw) * nh * 4);
- for (int y = 0; y < nh; y++)
- for (int x = 0; x < nw; x++) {
- int x0 = std::min(2 * x, lw - 1), x1 = std::min(2 * x + 1, lw - 1);
- int y0 = std::min(2 * y, lh - 1), y1 = std::min(2 * y + 1, lh - 1);
- for (int c = 0; c < 4; c++) {
- int sum = src[(size_t(y0) * lw + x0) * 4 + c] + src[(size_t(y0) * lw + x1) * 4 + c] +
- src[(size_t(y1) * lw + x0) * 4 + c] + src[(size_t(y1) * lw + x1) * 4 + c];
- dst[(size_t(y) * nw + x) * 4 + c] = uint8_t((sum + 2) / 4);
- }
- }
- L->levels.push_back(std::move(dst));
- lw = nw;
- lh = nh;
+ L->levels.push_back(nextRgbaLevel(L->levels.back(), lw, lh));
+ lw = std::max(1, lw / 2);
+ lh = std::max(1, lh / 2);
}
}
@@ -783,18 +788,20 @@ static void loadCachedLevels(const std::string& name, const PackFile& f, Loaded*
for (auto& lv : levels) L->levels.push_back(std::move(lv));
}
-// Keeps the levels a completion made (L->levels from L->first on).
-static void storeCachedLevels(const std::string& name, const PackFile& f, const Loaded& L) {
+// Keeps the levels a completion made (L->levels from L->first on); with
+// ".top", a shrunk replacement's top level (shrinkToLimit).
+static const char kTopMagic[8] = {'S', 'M', 'S', 'T', 'O', 'P', '0', '1'};
+static void storeCachedLevels(const std::string& name, const PackFile& f, const Loaded& L, const char* ext = ".mips") {
const std::string& dir = mipCacheDir();
MipsHeader hd{};
if (dir.empty() || L.levels.empty() || !sourceStamp(f.path, &hd.size, &hd.mtime)) return;
- memcpy(hd.magic, kMipsMagic, 8);
+ memcpy(hd.magic, strcmp(ext, ".top") ? kMipsMagic : kTopMagic, 8);
hd.w = uint32_t(L.w);
hd.h = uint32_t(L.h);
hd.blocks = uint32_t(L.blocks);
hd.first = uint32_t(L.first);
hd.count = uint32_t(L.levels.size());
- const std::string path = dir + "/" + name + ".mips", part = path + ".part";
+ const std::string path = dir + "/" + name + ext, part = path + ".part";
FILE* out = fopen(part.c_str(), "wb");
if (!out) return;
bool ok = fwrite(&hd, sizeof hd, 1, out) == 1;
@@ -822,6 +829,85 @@ static void storeCachedLevels(const std::string& name, const PackFile& f, const
if (!ok || ec) std::filesystem::remove(part, ec);
}
+// ------------------------------------------------------------------ replacements too large to hold
+// SMS_TEXTURE_PACK_MAX_SIZE=n halves a replacement longer than n pixels until
+// it is not, for a GPU that cannot hold a pack's largest images (the PS5's
+// driver: the UHD pack's 4096 to 16384 pixel files ran it out of memory, and
+// took 17 seconds to send in one load). The file's own lower levels serve
+// where it has them; otherwise the top level is made from the largest, by
+// nextBlockLevel, and kept on disk as <name>.top beside the completed levels,
+// so each large file is read once. Unset or 0 keeps every replacement whole.
+static int maxSize() {
+ static const int n = getenv("SMS_TEXTURE_PACK_MAX_SIZE") ? std::max(0, atoi(getenv("SMS_TEXTURE_PACK_MAX_SIZE"))) : 0;
+ return n;
+}
+
+static int halvings(int w, int h) {
+ int k = 0;
+ while (maxSize() > 0 && std::max(w, h) > maxSize()) {
+ w = std::max(1, w / 2);
+ h = std::max(1, h / 2);
+ k++;
+ }
+ return k;
+}
+
+static bool ddsSize(const std::string& path, int* w, int* h) {
+ uint8_t d[20];
+ FILE* f = fopen(path.c_str(), "rb");
+ if (!f) return false;
+ bool ok = fread(d, sizeof d, 1, f) == 1 && !memcmp(d, "DDS ", 4);
+ fclose(f);
+ if (!ok) return false;
+ *h = int(rd32(d + 12));
+ *w = int(rd32(d + 16));
+ return *w > 0 && *h > 0;
+}
+
+// The top level an earlier shrinkToLimit kept, for a file of w x h halved k times.
+static bool loadShrunk(const std::string& name, const PackFile& f, int w, int h, int k, Loaded* L) {
+ const std::string& dir = mipCacheDir();
+ uint64_t size;
+ int64_t mtime;
+ if (dir.empty() || !sourceStamp(f.path, &size, &mtime)) return false;
+ FILE* in = fopen((dir + "/" + name + ".top").c_str(), "rb");
+ if (!in) return false;
+ MipsHeader hd;
+ std::vector<uint8_t> top;
+ const int sw = std::max(1, w >> k), sh = std::max(1, h >> k);
+ if (fread(&hd, sizeof hd, 1, in) == 1 && !memcmp(hd.magic, kTopMagic, 8) && hd.size == size && hd.mtime == mtime &&
+ hd.w == uint32_t(sw) && hd.h == uint32_t(sh) && hd.first == 0 && hd.count == 1 && gpuTakes(int(hd.blocks))) {
+ top.resize(blockLevelBytes(int(hd.blocks), sw, sh));
+ if (fread(top.data(), top.size(), 1, in) != 1) top.clear();
+ }
+ fclose(in);
+ if (top.empty()) return false;
+ L->w = sw;
+ L->h = sh;
+ L->blocks = int(hd.blocks);
+ L->compressed = kGl[L->blocks];
+ L->levels.push_back(std::move(top));
+ return true;
+}
+
+// Halves L k times, from the deepest of the levels it has that is still too
+// large. True if that made a level (rather than only dropping the file's own).
+static bool shrinkToLimit(Loaded* L, int k) {
+ const int drop = std::min(k, int(L->levels.size()) - 1);
+ L->levels.erase(L->levels.begin(), L->levels.begin() + drop);
+ L->w = std::max(1, L->w >> drop);
+ L->h = std::max(1, L->h >> drop);
+ for (int i = drop; i < k; i++) {
+ std::vector<uint8_t> next = L->compressed ? nextBlockLevel(L->blocks, L->levels[0], L->w, L->h, false)
+ : nextRgbaLevel(L->levels[0], L->w, L->h);
+ L->levels.clear();
+ L->levels.push_back(std::move(next));
+ L->w = std::max(1, L->w / 2);
+ L->h = std::max(1, L->h / 2);
+ }
+ return drop < k;
+}
+
// `whole`: also the compressed levels completeChain makes, else only the
// pack's and the cached ones (RGBA images are always completed: they are
// decoded anyway).
@@ -830,6 +916,13 @@ static Loaded* decode(const std::string& name, bool whole, bool cached = true) {
Loaded* L = new Loaded;
L->arbitrary = f.arbitrary;
if (endsWith(f.path, ".dds") || endsWith(f.path, ".DDS")) {
+ int fw = 0, fh = 0;
+ const int k = maxSize() > 0 && ddsSize(f.path, &fw, &fh) ? halvings(fw, fh) : 0;
+ if (k && loadShrunk(name, f, fw, fh, k, L)) {
+ if (cached) loadCachedLevels(name, f, L);
+ if (whole) completeChain(L);
+ return L;
+ }
if (!decodeDds(f.path, L)) {
logmsg("texture pack: cannot read %s (not a DDS of a supported format)", f.path.c_str());
L->failed = true;
@@ -847,6 +940,10 @@ static Loaded* decode(const std::string& name, bool whole, bool cached = true) {
L->levels.push_back(std::move(mip.levels.front()));
}
}
+ if (k && !L->failed && shrinkToLimit(L, k)) {
+ logmsg("texture pack: %s is %dx%d, used at %dx%d", f.path.c_str(), fw, fh, L->w, L->h);
+ if (L->compressed) storeCachedLevels(name, f, *L, ".top");
+ }
if (cached && !L->failed && L->compressed) loadCachedLevels(name, f, L);
if (whole || !L->compressed) completeChain(L);
return L;
@@ -1049,6 +1146,9 @@ static bool beginUpload(Upload& u) {
// Sends u's levels, at least one piece and then while `sent` is under
// `allowance`; true once they are all in. A completion's levels already have
// their storage (finishUpload), so they only fill it.
@@ -719,7 +924,7 @@ index bcd4174..3887f46 100644
static bool sendLevels(Upload& u, int unit, size_t allowance, size_t* sent) {
Loaded* L = u.item.image.get();
const bool fill = u.item.request.kind == REST;
@@ -1062,7 +1065,7 @@ static bool sendLevels(Upload& u, int unit, size_t allowance, size_t* sent) {
@@ -1062,7 +1162,7 @@ static bool sendLevels(Upload& u, int unit, size_t allowance, size_t* sent) {
const int n = L->first + int(u.level);
const int lw = std::max(1, L->w >> n), lh = std::max(1, L->h >> n);
size_t bytes = lv.size();
@@ -728,7 +933,7 @@ index bcd4174..3887f46 100644
if (L->compressed)
glCompressedTexImage2D(GL_TEXTURE_2D, n, L->compressed, lw, lh, 0, GLsizei(bytes), lv.data());
else
@@ -1212,13 +1215,23 @@ static bool takeDecoded(Upload& u, bool pack) { // with s_mu held
@@ -1212,13 +1312,23 @@ static bool takeDecoded(Upload& u, bool pack) { // with s_mu held
// Sends decoded textures into GL: all of the ones resources asked for when
// `wait` (a load: until none is left to decode), else up to a frame's
// allowance, or 2 ms. The whole pack's wait for loads (uploadPack).
@@ -752,7 +957,7 @@ index bcd4174..3887f46 100644
if (s_uploads.empty()) {
Upload u;
{
@@ -1226,10 +1239,14 @@ static void uploadPending(bool wait) {
@@ -1226,10 +1336,14 @@ static void uploadPending(bool wait) {
if (wait) lk.lock();
else if (!lk.try_lock()) break;
auto loaded = [] { return s_decodedDemand == 0 && s_queue.empty() && !s_busy; };
@@ -767,7 +972,7 @@ index bcd4174..3887f46 100644
continue;
}
Upload& u = s_uploads.front();
@@ -1237,11 +1254,14 @@ static void uploadPending(bool wait) {
@@ -1237,11 +1351,14 @@ static void uploadPending(bool wait) {
s_uploads.pop_front();
continue;
}
@@ -783,7 +988,7 @@ index bcd4174..3887f46 100644
if (!wait && (sent >= allowance || Clock::now() - start >= std::chrono::milliseconds(2))) break;
}
}
@@ -1325,20 +1345,32 @@ static void touchFresh() {
@@ -1325,20 +1442,32 @@ static void touchFresh() {
glcInvalidate();
}
+1 -1
View File
@@ -15,7 +15,7 @@
"conceptId": "99312",
"contentBadgeType": 1,
"contentId": "UP9000-PPSA99312_00-SUNSHINE00000000",
"contentVersion": "00.009.000",
"contentVersion": "00.010.000",
"downloadDataSize": 256,
"gameIntent": {
"permittedIntents": [
+4
View File
@@ -149,6 +149,10 @@ ps5_startup(void)
PC's budget of 1536 cannot fit: the title died at about 1,070. From 512
the oldest are let go, which keeps about 3 GiB spare through a load. */
set("SMS_TEXTURE_PACK_MB", "512", 0);
/* ... and the largest images it can take: the UHD pack's 4096 to 16384
pixel files are halved to 2048 (or less) the first time each is used, and
kept so in userdata/.cache, so the pack goes in as its authors publish it */
set("SMS_TEXTURE_PACK_MAX_SIZE", "3072", 0);
if (stat("/app0/mods/hd-cutscenes", &status) == 0)
set("SMS_HD_CUTSCENES", "/app0/mods/hd-cutscenes", 0);
find_disc();
-90
View File
@@ -1,90 +0,0 @@
#!/usr/bin/env python3
"""Fetches the texture tools' Python packages for this Python, without pip.
fetch-wheels.py DIR
Downloads Pillow and etcpak (and archspec, which etcpak imports) as wheels from PyPI, the ones built for this
Python and this machine, checks each against PyPI's SHA-256, and unpacks them
into DIR/<python version>, which it prints: put that on PYTHONPATH. A fresh
Ubuntu (or WSL) has neither pip nor venv, and a Mac's Command Line Tools have
Python 3.9, which newer Pillows no longer support. Run again, it does nothing.
SPDX-License-Identifier: MIT
"""
import hashlib, json, os, platform, re, sys, urllib.request, zipfile
# (name, version): Pillow 12 needs Python 3.10, so 3.9 gets the last that supports it
PACKAGES = [("archspec", "0.2.6"), ("etcpak", "0.9.15"), ("pillow", "12.3.0" if sys.version_info >= (3, 10) else "11.3.0")]
def compatible(filename):
"""A wheel this Python can import here: its CPython tag (or abi3 from an
older one) and its platform; or pure Python."""
if filename.endswith("-py3-none-any.whl"):
return True
m = re.match(r"[^-]+-[^-]+-(cp(\d)(\d+))-(cp\d+m?|abi3)-(.+)\.whl$", filename)
if not m:
return False
major, minor, abi, plats = int(m.group(2)), int(m.group(3)), m.group(4), m.group(5).split(".")
here = sys.version_info
if abi == "abi3":
if (major, minor) > (here.major, here.minor):
return False
elif (major, minor) != (here.major, here.minor):
return False
machine = platform.machine().lower()
for plat in plats:
if sys.platform == "darwin":
want = "arm64" if machine == "arm64" else "x86_64"
if plat.startswith("macosx_") and (plat.endswith("_" + want) or plat.endswith("_universal2")):
return True
elif sys.platform.startswith("linux"):
want = {"arm64": "aarch64"}.get(machine, machine)
g = re.match(r"manylinux_(\d+)_(\d+)_(.+)$", plat) or re.match(r"manylinux(2014)_(.+)$", plat)
if not g:
continue
if g.re.pattern.startswith("manylinux(2014)"):
need, arch = (2, 17), g.group(2)
else:
need, arch = (int(g.group(1)), int(g.group(2))), g.group(3)
libc = os.confstr("CS_GNU_LIBC_VERSION").split()[-1].split(".") if hasattr(os, "confstr") else ["0", "0"]
if arch == want and (int(libc[0]), int(libc[1])) >= need:
return True
return False
def main():
root = os.path.join(sys.argv[1], "python%d.%d" % sys.version_info[:2])
done = os.path.join(root, ".done-" + "-".join(n + v for n, v in PACKAGES))
if not os.path.exists(done):
os.makedirs(root, exist_ok=True)
for name, version in PACKAGES:
with urllib.request.urlopen(f"https://pypi.org/pypi/{name}/{version}/json") as r:
files = json.load(r)["urls"]
wheel = next((f for f in files if compatible(f["filename"])), None)
if not wheel:
sys.exit(f"fetch-wheels.py: no {name} {version} for Python {platform.python_version()} on "
f"{sys.platform} {platform.machine()}")
print(f"==> fetching {wheel['filename']}", file=sys.stderr)
with urllib.request.urlopen(wheel["url"]) as r:
data = r.read()
if hashlib.sha256(data).hexdigest() != wheel["digests"]["sha256"]:
sys.exit(f"fetch-wheels.py: {wheel['filename']} did not download correctly; try again")
path = os.path.join(root, wheel["filename"])
with open(path, "wb") as f:
f.write(data)
with zipfile.ZipFile(path) as z:
for member in z.infolist():
z.extract(member, root)
# a zip keeps the executable bit in its Unix attributes; extract() drops it
mode = member.external_attr >> 16
if mode:
os.chmod(os.path.join(root, member.filename), mode)
os.remove(path)
open(done, "w").close()
print(root)
if __name__ == "__main__":
main()
-108
View File
@@ -1,108 +0,0 @@
#!/usr/bin/env python3
"""Super Mario Sunshine for the PlayStation 5 - an HD texture pack sized for the console.
get-texture-pack.py [OUT] OUT/textures/ (OUT defaults to ./out)
Downloads the Super Mario Sunshine UHD Texture Pack (qashto, razius) and the HD
texture extras (chasem-dev) from where their authors publish them, checks each
against the release this was tested with, and makes a copy with nothing larger
than 2048 pixels (shrink-texture-pack.py: the console's driver cannot hold the
4096 and 8192 pixel images). Copy OUT/textures to the console as
/data/homebrew/PPSA99312/mods/textures.
Neither pack is part of this project or stored by it; each belongs to its authors.
Needs Python with Pillow and etcpak, and 7-Zip (7z, 7zz or 7za) or bsdtar to
unpack. About 1 GB to download, 5 GB of disk while it works, 1.3 GB at the end.
SPDX-License-Identifier: MIT
"""
import hashlib, os, shutil, subprocess, sys, urllib.request, zipfile
HERE = os.path.dirname(os.path.abspath(__file__))
PACKS = [
{"name": "Super Mario Sunshine UHD Texture Pack v2.1.1 (qashto, razius)",
"page": "https://github.com/qashto/Super_Mario_Sunshine_UHD_Texture_Pack",
"url": "https://github.com/qashto/Super_Mario_Sunshine_UHD_Texture_Pack/releases/download/2.1.1/GMS.7z",
"file": "GMS.7z", "md5": "274985dba9214b857b3e5605d2ee49b2", "inside": "GMS/Textures/GMS", "as": "GMS"},
{"name": "Super Mario Sunshine HD texture extras v1.1.1 (chasem-dev)",
"page": "https://github.com/chasem-dev/sms-hd-texture-extras",
"url": "https://github.com/chasem-dev/sms-hd-texture-extras/releases/download/v1.1.1/sms-hd-texture-extras-1.1.1.zip",
"file": "sms-hd-texture-extras-1.1.1.zip", "md5": "a658c43cce0751e585e991f8563e5f41", "inside": "", "as": "sms-hd-texture-extras"},
]
def say(text):
print("==> " + text, flush=True)
def md5_of(path):
digest = hashlib.md5()
with open(path, "rb") as source:
for chunk in iter(lambda: source.read(1 << 20), b""):
digest.update(chunk)
return digest.hexdigest()
def fetch(pack, folder):
path = os.path.join(folder, pack["file"])
if os.path.isfile(path) and md5_of(path) == pack["md5"]:
return path
say("downloading %s" % pack["name"])
request = urllib.request.Request(pack["url"], headers={"User-Agent": "PS5_Sunshine texture pack installer"})
with urllib.request.urlopen(request) as reply, open(path + ".part", "wb") as out:
shutil.copyfileobj(reply, out, 1 << 20)
os.replace(path + ".part", path)
got = md5_of(path)
if got != pack["md5"]:
os.remove(path)
sys.exit("get-texture-pack.py: %s is not the release this was tested with (MD5 %s, expected %s); see %s"
% (pack["file"], got, pack["md5"], pack["page"]))
return path
def unpack(archive, folder):
shutil.rmtree(folder, ignore_errors=True)
os.makedirs(folder)
if archive.endswith(".zip"):
with zipfile.ZipFile(archive) as source:
source.extractall(folder)
return
for tool in ("7z", "7zz", "7za"):
if shutil.which(tool):
subprocess.run([tool, "x", "-y", "-o" + folder, archive], check=True, stdout=subprocess.DEVNULL)
return
for tool in ("bsdtar", "tar"): # libarchive reads 7z; GNU tar does not, and says so
if shutil.which(tool) and subprocess.run([tool, "-xf", archive, "-C", folder], stderr=subprocess.DEVNULL).returncode == 0:
return
sys.exit("get-texture-pack.py: 7-Zip (7z, 7zz or 7za) or bsdtar is needed to unpack " + os.path.basename(archive))
def main():
out = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else "out")
downloads, original, result = (os.path.join(out, name) for name in (".texture-downloads", ".texture-original", "textures"))
os.makedirs(downloads, exist_ok=True)
shutil.rmtree(original, ignore_errors=True)
for pack in PACKS:
archive = fetch(pack, downloads)
say("unpacking %s" % pack["file"])
scratch = os.path.join(out, ".texture-unpack")
unpack(archive, scratch)
os.makedirs(original, exist_ok=True)
shutil.move(os.path.join(scratch, pack["inside"]) if pack["inside"] else scratch, os.path.join(original, pack["as"]))
shutil.rmtree(scratch, ignore_errors=True)
# only the textures: the UHD pack also carries an archive of its sources
for root, _, files in os.walk(original):
for name in files:
if not name.lower().endswith((".dds", ".png")):
os.remove(os.path.join(root, name))
say("making the console's copy (nothing larger than 2048 pixels)")
shutil.rmtree(result, ignore_errors=True)
subprocess.run([sys.executable, os.path.join(HERE, "shrink-texture-pack.py"), original, result, "2048"], check=True)
shutil.rmtree(original, ignore_errors=True)
say(result)
print(" copy it to the console as /data/homebrew/PPSA99312/mods/textures (FTP)")
print(" the packs belong to their authors: this copy is for your own use")
if __name__ == "__main__":
main()
+2 -2
View File
@@ -12,7 +12,7 @@
# console's own GPU interface, EGL for the screen), a static archive group and
# its two import stubs. The title folder, dist/<TITLE_ID>/, gets the signed
# eboot.bin, sce_sys/ and the licences. OBJECT may be an archive.
# PS5_DIST_DIR is where the title folder goes instead of dist/ (the builder kit).
# PS5_DIST_DIR is where the title folder goes instead of dist/.
# PS5_OPENGL_PREFIX is the OpenGL SDK's sdk/ directory.
#
# Copyright (C) 2026 Mihawk
@@ -180,7 +180,7 @@ cp "$param" "$app/sce_sys/param.json"
for asset in icon0.png pic0.dds pic1.dds snd0.at9; do
[[ -f $ps5/sce_sys/$asset ]] && cp "$ps5/sce_sys/$asset" "$app/sce_sys/$asset"
done
# (Python, not sha256sum: the builder kit runs this on a Mac too)
# (Python, not sha256sum, which a Mac does not have)
python3 - "$vulkan/runtime/libc.prx" "$vulkan/runtime/libc.prx.sha256" <<'CHECK'
import hashlib, sys
want = open(sys.argv[2]).read().split()[0]
-189
View File
@@ -1,189 +0,0 @@
#!/usr/bin/env bash
# Super Mario Sunshine for the PlayStation 5 - the builder kit: everything of a build but the game.
#
# ps5/tools/package-kit.sh [VERSION] dist/kit/sunshine-ps5-kit-<VERSION>/ and its .zip
#
# A built title is not shared: eboot.bin holds the game's code, compiled from
# the decompilation. The kit is the rest of one, as it stands after
# tools/build.sh: the port's own objects (the platform layer, the graphics
# layer, SDL2), the OpenGL driver, the SDK's headers, libraries and link
# scripts, and kit/build-title.sh, which on somebody else's machine fetches the
# PC port and the decompilation, compiles the game's code and links the same
# title with the same recipe (tools/link-title.sh). What goes in is listed
# here, file by file; the game's code (libsms_game.a) and anything from a disc
# never do, and the script checks that before it finishes.
#
# The link line is the build's own (ninja's), so the kit links what was tested.
#
# SPDX-License-Identifier: MIT
set -euo pipefail
ps5=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
root=$(dirname "$ps5")
vulkan=$(cd -- "${PS5_VULKAN_DIR:-$root/../PS5_Vulkan}" && pwd)
opengl=${PS5_OPENGL_PREFIX:?set PS5_OPENGL_PREFIX to the sdk/ directory of the PS5 OpenGL SDK}
sdk=$(bash "$ps5/tools/setup-sdk.sh")
build=${PS5_BUILD_DIR:-$root/build/game}
ninja=$(command -v ninja || echo "$HOME/.local/bin/ninja")
export PS5_CLANG=${PS5_CLANG:-$(command -v clang || true)}
version=${1:-$(python3 -c 'import json,sys; v=json.load(open(sys.argv[1]))["contentVersion"].split("."); print("%d.%d.%d" % tuple(int(x) for x in v))' "$ps5/sce_sys/param.json")}
name=sunshine-ps5-kit-$version
kit="$root/dist/kit/$name"
source="$root/vendor/sms-pc-port"
# the kit is the build as the sources stand now: bring it up to date first
bash "$ps5/tools/build.sh"
rm -rf -- "$kit"
mkdir -p "$kit/lib" "$kit/ps5/tools"
# ---- the title's link inputs, in the build's order, without the game's code
# (ninja's command: ... link-title.sh BUILD VULKAN SDK INPUT... && :)
mapfile -t inputs < <("$ninja" -C "$build" -t commands sms | tail -1 |
sed -e 's/.*link-title\.sh //' -e 's/ && :$//' | tr ' ' '\n' | sed -n '4,$p' | sed '/^$/d')
strip="$sdk/bin/llvm-strip"
: > "$kit/lib/link.txt"
game_code=0
for input in "${inputs[@]}"; do
case $input in
-*) echo "$input" >> "$kit/lib/link.txt"; continue ;;
libsms_game.a | */libsms_game.a) echo "@GAMECODE@" >> "$kit/lib/link.txt"; game_code=$((game_code + 1)); continue ;;
esac
file=$input
[[ $file == /* ]] || file="$build/$input"
[[ -f $file ]] || { echo "package-kit.sh: link input $input is missing" >&2; exit 2; }
relative=${file#"$build"/}
[[ $relative != /* ]] || { echo "package-kit.sh: link input $input is outside the build tree" >&2; exit 2; }
if [[ ! -f $kit/lib/$relative ]]; then
mkdir -p "$(dirname "$kit/lib/$relative")"
# no debug information: it names this machine's folders, and is most of the size
"$strip" --strip-debug -o "$kit/lib/$relative" "$file"
fi
echo "$relative" >> "$kit/lib/link.txt"
done
[[ $game_code -eq 1 ]] || { echo "package-kit.sh: expected the game's code once in the link line, found it $game_code times" >&2; exit 2; }
# ---- the port: the link, the title's own files, and what the game's code is built with
cp "$ps5/tools/link-title.sh" "$ps5/tools/cc-init-array.sh" "$kit/ps5/tools/"
cp -R "$ps5/sce_sys" "$kit/ps5/sce_sys"
cp -R "$ps5/res" "$kit/ps5/res"
mkdir -p "$kit/ps5/stubs" "$kit/ps5/cmake" "$kit/ps5/patches" "$kit/ps5/opengl"
cp "$ps5/stubs/libSceMouse_stub.c" "$kit/ps5/stubs/"
cp "$ps5/cmake/ps5-kit-game.cmake" "$kit/ps5/cmake/"
cp "$ps5/patches/sms-pc-port.patch" "$ps5/patches/sdl.patch" "$kit/ps5/patches/"
cp "$ps5/opengl/build-runtime.sh" "$kit/ps5/opengl/"
cp "$ps5/kit/build-title.sh" "$ps5/kit/make-title.sh" "$ps5/kit/tools.sh" "$ps5/kit/Dockerfile" "$kit/"
cp "$ps5/kit/README.md" "$kit/README.md"
chmod +x "$kit/build-title.sh" "$kit/make-title.sh"
mkdir -p "$kit/tools"
cp "$ps5/tools/shrink-texture-pack.py" "$ps5/tools/get-texture-pack.py" "$ps5/tools/fetch-wheels.py" "$kit/tools/"
cp "$ps5/kit/make-texture-pack.sh" "$kit/"
chmod +x "$kit/make-texture-pack.sh"
# the revisions build-title.sh fetches: the ones this build was made from
port_rev=$(git -C "$source" rev-parse HEAD)
decomp_rev=$(git -C "$source/decomp" rev-parse HEAD)
{
echo "port_url=$(git -C "$source" remote get-url origin)"
echo "port_rev=$port_rev"
echo "decomp_rev=$decomp_rev"
} > "$kit/REVISIONS"
# ... and the patch must be the tree's whole difference from that revision
bash "$ps5/tools/setup-source.sh" > /dev/null
# ---- the payload SDK: compiler wrappers, link scripts, the CMake toolchain, headers, libraries
mkdir -p "$kit/sdk/target"
cp -R "$sdk/bin" "$sdk/ldscripts" "$sdk/toolchain" "$kit/sdk/"
cp -R "$sdk/target/include" "$sdk/target/lib" "$kit/sdk/target/"
cp "$sdk/.ps5-sdk-revision" "$kit/sdk/"
# ---- PS5_Vulkan: the link recipe, the CRT, the fake-SELF tool's source, libc.prx
mkdir -p "$kit/vulkan/tools" "$kit/vulkan/tooling/psbc" "$kit/vulkan/tooling/native" "$kit/vulkan/runtime"
cp "$vulkan/LICENSE" "$vulkan/NOTICE.md" "$kit/vulkan/"
cp "$vulkan/tools/radv-link.sh" "$kit/vulkan/tools/"
cp "$vulkan/tooling/prospero-clang18" "$kit/vulkan/tooling/"
cp "$vulkan/tooling/psbc/ps5-pie-unwind.ld" "$kit/vulkan/tooling/psbc/"
cp "$vulkan"/tooling/native/*.cpp "$vulkan"/tooling/native/*.hpp "$vulkan"/tooling/native/*.map \
"$vulkan"/tooling/native/*.ld "$kit/vulkan/tooling/native/"
cp "$vulkan/runtime/libc.prx" "$vulkan/runtime/libc.prx.sha256" "$vulkan/runtime/README.md" "$kit/vulkan/runtime/"
# ---- the fake-SELF tool built for Linux, static, so a Linux (or WSL) needs no C++
# compiler: x86_64 and arm64, with zlib compiled in. A Mac builds its own from the
# source above (its Command Line Tools have a compiler and zlib).
zlib_version=1.3.2
zlib="$root/.deps/zlib-$zlib_version"
if [[ ! -f $zlib/zlib.h ]]; then
mkdir -p "$root/.deps"
curl -fsSL -o "$zlib.tar.gz" "https://github.com/madler/zlib/releases/download/v$zlib_version/zlib-$zlib_version.tar.gz"
echo "bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16 $zlib.tar.gz" | sha256sum --check --quiet
tar -xzf "$zlib.tar.gz" -C "$root/.deps"
rm -f "$zlib.tar.gz"
fi
for target in x86_64:x86_64-linux-gnu- aarch64:aarch64-linux-gnu-; do
arch=${target%%:*} prefix=${target#*:}
# the host's own compiler for its own processor
[[ $(uname -m) == "$arch" ]] && prefix=
command -v "${prefix}g++" > /dev/null ||
{ echo "package-kit.sh: ${prefix}g++ is missing (apt install g++-${arch//_/-}-linux-gnu)" >&2; exit 2; }
objects="$build/native-tool-$arch"
rm -rf "$objects"
mkdir -p "$objects" "$kit/vulkan/prebuilt/Linux-$arch"
for file in adler32 crc32 deflate inflate inffast inftrees trees zutil; do
"${prefix}gcc" -O2 -c "$zlib/$file.c" -o "$objects/$file.o"
done
"${prefix}g++" -std=c++20 -O2 -static -I"$zlib" "$vulkan"/tooling/native/{native_app_builder,self_container,elf_object,sce_module_writer}.cpp \
"$objects"/*.o -o "$kit/vulkan/prebuilt/Linux-$arch/ps5-native-tool"
"${prefix}strip" "$kit/vulkan/prebuilt/Linux-$arch/ps5-native-tool"
rm -rf "$objects"
done
cp "$zlib/LICENSE" "$kit/vulkan/prebuilt/zlib-LICENSE.txt"
# ---- the PS5 OpenGL SDK: the driver's archive and the console's two GPU import stubs
mkdir -p "$kit/opengl/sdk/lib"
cp "$opengl/../LICENSE" "$opengl/../THIRD_PARTY_NOTICES.md" "$kit/opengl/"
# (as released: libPS5OpenGL.a is a linker script naming the others)
cp -R "$opengl"/lib/. "$kit/opengl/sdk/lib/"
rm -rf "$kit/opengl/sdk/lib/cmake" "$kit/opengl/sdk/lib/pkgconfig"
[[ -f $opengl/sunshine-runtime-defines.txt ]] && cp "$opengl/sunshine-runtime-defines.txt" "$kit/opengl/"
# ---- Clang's builtins for the console's processor: a Linux on arm64 does not install x86-64's
builtins="$("$PS5_CLANG" --print-resource-dir)/lib/linux/libclang_rt.builtins-x86_64.a"
[[ -f $builtins ]] || { echo "package-kit.sh: missing $builtins" >&2; exit 2; }
mkdir -p "$kit/clang-rt/lib/linux"
cp "$builtins" "$kit/clang-rt/lib/linux/"
# ---- licences, and where each part's source is
mkdir -p "$kit/licenses"
cp "$root/LICENSE" "$kit/LICENSE"
cp "$root/LICENSING.md" "$kit/"
cp "$root"/licenses/* "$kit/licenses/"
cp "$source/LICENSE" "$kit/licenses/sms-pc-port-LICENSE.txt"
sdl_revision=$(git -C "$root/.deps/SDL2" describe --tags --always 2>/dev/null || echo "release-2.32.10")
own_revision=$(git -C "$root" rev-parse HEAD 2>/dev/null || echo "(this kit's own files: ps5/ in the kit)")
{
echo "The builder kit $version of Super Mario Sunshine for the PlayStation 5 was built from:"
echo
echo "PS5_Sunshine https://github.com/griches/PS5_Sunshine $own_revision"
echo "sms-pc-port https://github.com/chasem-dev/sms-pc-port $port_rev (+ ps5/patches/sms-pc-port.patch)"
echo "sms-english https://github.com/chasem-dev/sms-english $decomp_rev (fetched by build-title.sh, not in the kit)"
echo "PS5 OpenGL SDK https://github.com/blackbearreloaded/ps5-opengl release 1.0.2, with three runtime objects rebuilt (ps5/opengl/build-runtime.sh; defines in opengl/sunshine-runtime-defines.txt)"
echo "PS5_PayloadSDK https://github.com/mihawk-99/PS5_PayloadSDK $(<"$sdk/.ps5-sdk-revision")"
echo "PS5_Vulkan https://github.com/mihawk-99/PS5_Vulkan $(git -C "$vulkan" rev-parse HEAD) (its link recipe, CRT and native tool; no Vulkan driver is linked)"
echo "SDL2 https://github.com/libsdl-org/SDL $sdl_revision (+ ps5/patches/sdl.patch and the console's drivers)"
echo "Clang builtins https://github.com/llvm/llvm-project $("$PS5_CLANG" --version | head -1)"
echo "zlib https://github.com/madler/zlib $zlib_version (in vulkan/prebuilt's Linux tools)"
echo
echo "LICENSING.md says which licence covers which part."
} > "$kit/SOURCES.txt"
echo "$version" > "$kit/VERSION"
# ---- nothing of the game: no game code archive or object, no decompilation, no disc image or save
leaks=$(find "$kit" \( -name 'libsms_game*' -o -path '*/sms_game.dir/*' -o -path '*/decomp/*' -o -path '*/patched/*' \
-o -iname '*.iso' -o -iname '*.gcm' -o -iname '*.ciso' -o -iname '*.rvz' -o -iname '*.gci' -o -iname '*.szs' -o -iname '*.thp' \
-o -iname '*.dol' -o -iname '*.arc' \) -print)
[[ -z $leaks ]] || { echo "package-kit.sh: these must not be in a kit:" >&2; echo "$leaks" >&2; exit 1; }
# (the files symlinks point to, not the links: Windows' own unzip, and Python's,
# make a link a file holding its target's name, and the SDK has header links)
(cd "$root/dist/kit" && rm -f "$name.zip" && zip -qr "$name.zip" "$name")
printf '==> %s (%s unpacked, %s zipped)\n' "$root/dist/kit/$name.zip" \
"$(du -sh "$kit" | cut -f1)" "$(du -sh "$root/dist/kit/$name.zip" | cut -f1)"
sha256sum "$root/dist/kit/$name.zip"
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Super Mario Sunshine for the PlayStation 5 - pack the built title folder as a release archive.
#
# ps5/tools/package-release.sh [VERSION] (through tools/vm.sh package-release)
#
# Writes dist/Sunshine-PS5-vVERSION.zip: the title folder dist/<TITLE_ID>/ as the
# console takes it (eboot.bin, sce_sys/, sce_module/, res/, licenses/), with
# SOURCE.txt beside eboot.bin naming the revision of every part the build
# linked. A built title is under GPL-3.0: that file is how a release says where
# its source is. No disc image, save or texture pack goes in, and it refuses if
# one is in the folder. VERSION defaults to param.json's contentVersion.
#
# After PS5_SwitchWaker's package-release.sh.
# SPDX-License-Identifier: MIT
set -euo pipefail
ps5=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
root=$(dirname "$ps5")
vulkan=$(cd -- "${PS5_VULKAN_DIR:-$root/../PS5_Vulkan}" && pwd)
opengl=${PS5_OPENGL_PREFIX:?set PS5_OPENGL_PREFIX to the sdk/ directory of the PS5 OpenGL SDK}
source="$root/vendor/sms-pc-port"
param="$ps5/sce_sys/param.json"
version=${1:-$(python3 -c 'import json,sys; v=json.load(open(sys.argv[1]))["contentVersion"].split("."); print("%d.%d.%d" % tuple(int(x) for x in v))' "$param")}
title_id=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["titleId"])' "$param")
app="$root/dist/$title_id"
for file in eboot.bin sce_sys/param.json sce_sys/icon0.png sce_sys/pic0.dds sce_sys/pic1.dds sce_module/libc.prx \
res/prompts/playstation.png; do
[[ -e $app/$file ]] || { echo "missing $app/$file: run build.sh first" >&2; exit 2; }
done
cmp -s "$param" "$app/sce_sys/param.json" || { echo "the title's param.json is not this tree's: run build.sh first" >&2; exit 2; }
# a console's own files, a disc, a save or a pack are not for a release
stray=$(find "$app" \( -iname '*.iso' -o -iname '*.ciso' -o -iname '*.gcm' -o -iname '*.rvz' -o -iname '*.gci' \
-o -iname '*.dol' -o -iname '*.szs' -o -path "$app/userdata" -o -path "$app/mods" -o -name env.txt \
-o -name settings.txt -o -name bindings.txt \) -print -quit)
[[ -z $stray ]] || { echo "game data or a save in the title folder ($stray): a release never carries it" >&2; exit 2; }
# the submodule's working tree carries the console's patch: that is not a change here
[[ -z $(git -C "$root" status --porcelain --ignore-submodules=dirty) ]] ||
{ echo "uncommitted changes: a release is built from a commit" >&2; exit 2; }
sdk=$(bash "$ps5/tools/setup-sdk.sh")
sdl_revision=$(git -C "$root/.deps/SDL2" describe --tags --always 2>/dev/null || echo "release-2.32.10")
mkdir -p "$app/licenses"
cp "$root/licenses/GPL-3.0.txt" "$root/licenses/MIT.txt" "$root/licenses/Zlib.txt" "$app/licenses/"
cp "$source/LICENSE" "$app/licenses/sms-pc-port-LICENSE.txt"
cp "$source/decomp/LICENSE" "$app/licenses/sms-english-LICENSE.txt"
cp "$root/LICENSING.md" "$app/licenses/LICENSING.md"
cat > "$app/SOURCE.txt" <<SOURCE
Super Mario Sunshine for PlayStation 5, version $version
An unofficial homebrew port of chasem-dev's sms-pc-port. Not affiliated with
Nintendo or Sony Interactive Entertainment.
This title is distributed under the GNU General Public License, version 3
(licenses/GPL-3.0.txt). Its source is these projects at these revisions:
The port (patch, SDL2 drivers, platform layer, build)
$(git -C "$root" remote get-url origin 2>/dev/null || echo "this repository")
$(git -C "$root" rev-parse HEAD)
The PC port: sms-pc-port (MIT), with ps5/patches/sms-pc-port.patch
https://github.com/chasem-dev/sms-pc-port
$(git -C "$source" rev-parse HEAD)
The game's code: the sms-english decompilation it builds (CC0-1.0)
https://github.com/chasem-dev/sms-english
$(git -C "$source/decomp" rev-parse HEAD)
OpenGL: the PS5 OpenGL SDK (GPL-3.0-or-later), release 1.0.2, with three runtime
objects rebuilt by ps5/opengl/build-runtime.sh
https://github.com/blackbearreloaded/ps5-opengl
SDL2 (Zlib), with ps5/patches/sdl.patch and the drivers in ps5/sdl
https://github.com/libsdl-org/SDL
$sdl_revision
The link recipe, CRT, libc.prx and packaging: PS5_Vulkan (GPL-3.0)
https://github.com/mihawk-99/PS5_Vulkan
$(git -C "$vulkan" rev-parse HEAD)
The platform layer and SDK: PS5_PayloadSDK (GPL-3.0), over ps5-payload-dev/sdk
https://github.com/mihawk-99/PS5_PayloadSDK
$(<"$sdk/.ps5-sdk-revision")
Compiler: $(${PS5_CLANG:-clang} --version | head -1)
No game data is included. Super Mario Sunshine belongs to Nintendo; this title
does nothing without a GameCube disc image of the USA disc (GMSE01, revision 0,
.iso, .gcm or .ciso) that you provide, placed beside eboot.bin. The pictures in
sce_sys/ are the port's own, and res/prompts holds button symbols drawn from
Kenney's CC0 Input Prompts.
eboot.bin sha256: $(sha256sum "$app/eboot.bin" | cut -d' ' -f1)
SOURCE
out="$root/dist/Sunshine-PS5-v$version.zip"
rm -f "$out"
(cd "$root/dist" && zip -q -r -X "$out" "$title_id" -x '*/.DS_Store')
printf '==> %s (%s bytes)\n' "$out" "$(stat -c %s "$out")"
sha256sum "$out"
-84
View File
@@ -1,84 +0,0 @@
#!/usr/bin/env python3
"""Super Mario Sunshine for the PlayStation 5 - a texture pack sized for the console.
shrink-texture-pack.py SOURCE DEST [MAX]
Copies a Dolphin texture pack, making every BC7 .dds wider or taller than MAX
(2048) that size with a full mipmap chain. The PS5's OpenGL driver takes
texture data slowly (about 35 MiB a second) and a title's graphics memory is
limited: the UHD pack's 4096 and 8192 pixel images are two thirds of its 3 GB,
cost Delfino Plaza a 17 second load, and ran Bianco Hills out of memory.
Needs Python with Pillow and etcpak.
SPDX-License-Identifier: MIT
"""
import os, shutil, struct, sys
from multiprocessing import Pool
from PIL import Image
import etcpak
Image.MAX_IMAGE_PIXELS = None
def blocks(image):
w, h = image.size
pw, ph = (w + 3) // 4 * 4, (h + 3) // 4 * 4
if (pw, ph) != (w, h): # the small levels: repeat the edge into the 4x4 block
image = image.resize((pw, ph), Image.NEAREST)
# the parameters are not optional: without them etcpak crashes
return etcpak.compress_bc7(image.tobytes(), pw, ph, etcpak.BC7CompressBlockParams())
def convert(job):
source, dest, limit = job
header = bytearray(open(source, "rb").read(148))
height, width = struct.unpack_from("<II", header, 12)
dxgi = struct.unpack_from("<I", header, 128)[0] if header[84:88] == b"DX10" else 0
if max(width, height) <= limit or dxgi not in (98, 99):
shutil.copyfile(source, dest)
return 0
image = Image.open(source).convert("RGBA")
scale = limit / max(width, height)
w, h = max(4, round(width * scale)), max(4, round(height * scale))
levels = []
while True:
level = image.resize((w, h), Image.LANCZOS)
levels.append(blocks(level))
if w == 1 and h == 1:
break
w, h = max(1, w // 2), max(1, h // 2)
w, h = max(4, round(width * scale)), max(4, round(height * scale))
struct.pack_into("<I", header, 8, struct.unpack_from("<I", header, 8)[0] | 0x20000 | 0x80000) # mip count, linear size
struct.pack_into("<II", header, 12, h, w)
struct.pack_into("<I", header, 20, len(levels[0]))
struct.pack_into("<I", header, 28, len(levels))
struct.pack_into("<I", header, 108, 0x401008) # complex, mipmapped texture
with open(dest, "wb") as out:
out.write(header)
for level in levels:
out.write(level)
return 1
def main():
source, dest = sys.argv[1], sys.argv[2]
limit = int(sys.argv[3]) if len(sys.argv) > 3 else 2048
jobs = []
for root, _, files in os.walk(source):
target = os.path.join(dest, os.path.relpath(root, source))
os.makedirs(target, exist_ok=True)
for name in files:
if name.startswith("."):
continue
path = os.path.join(root, name)
if name.endswith(".dds"):
jobs.append((path, os.path.join(target, name), limit))
else:
shutil.copyfile(path, os.path.join(target, name))
with Pool() as pool:
made = sum(pool.imap_unordered(convert, jobs, chunksize=4))
print("%d images, %d made smaller" % (len(jobs), made))
if __name__ == "__main__":
main()
+1 -1
View File
@@ -6,7 +6,7 @@
# ps5/tools/vm.sh console-files ps5/tools/console-files.sh (your sm64.o2r, over FTP)
# ps5/tools/vm.sh deploy ps5/tools/deploy.sh (FTP to the console)
# ps5/tools/vm.sh run ps5/tools/run.sh (launch and follow klog)
# ps5/tools/vm.sh package-kit [V] ps5/tools/package-kit.sh (the builder kit)
# ps5/tools/vm.sh package-release [V] ps5/tools/package-release.sh (the release archive)
#
# The stack (PS5_Vulkan with its RADV archive, PS5_Mesa, PS5_PayloadSDK) lives in
# the Lima VM "ps5" under ~/ps5, built with LLVM 22 (~/ps5/env.sh); this folder is