mirror of
https://github.com/griches/PS5_Sunshine.git
synced 2026-10-17 15:00:42 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30ff84d01a |
No files matched your search
@@ -16,3 +16,4 @@ rom/
|
||||
*.szs
|
||||
# never: texture packs (they belong to their authors)
|
||||
mods/
|
||||
__pycache__/
|
||||
+10
-1
@@ -27,7 +27,16 @@ 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 | each its own, all permissive |
|
||||
| Smaller libraries | stb, bcdec, xxHash; zlib, in the kit's Linux build of the `eboot.bin` tool | 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.
|
||||
|
||||
## A built title is not for distribution
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ the native PC port of Super Mario Sunshine by **[chasem-dev](https://github.com/
|
||||
> platform, sound, input and graphics layers, 60 and 120 frames a second,
|
||||
> widescreen, texture packs and a great deal more, built on the
|
||||
> [decompilation](https://github.com/chasem-dev/sms-english) it carries. This
|
||||
> repository adds only what a PS5 needs on top of it: a patch of about 900
|
||||
> repository adds only what a PS5 needs on top of it: a patch of about 1,000
|
||||
> lines, the console's drivers and a build recipe. If you enjoy this, the
|
||||
> thanks belong there. **Please star and support
|
||||
> [chasem-dev/sms-pc-port](https://github.com/chasem-dev/sms-pc-port).**
|
||||
@@ -31,7 +31,7 @@ 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.8.0
|
||||
## This is an early release: 0.9.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,28 +39,37 @@ 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.
|
||||
|
||||
## How to get it: the builder kit
|
||||
|
||||
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, with Docker.
|
||||
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.
|
||||
|
||||
1. Download `sunshine-ps5-kit-<version>.zip` from the
|
||||
[releases page](https://github.com/griches/PS5_Sunshine/releases/latest) and unzip it.
|
||||
2. Start Docker Desktop, open a terminal in the kit's folder and run `./make-title.sh`.
|
||||
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`.
|
||||
|
||||
The kit's own [README](ps5/kit/README.md) has every step, the controls, the
|
||||
settings menu, Windows commands and what to do when something goes wrong.
|
||||
settings menu, the steps for Windows and what to do when something goes wrong.
|
||||
|
||||
**Never share the folder the kit makes, or its `eboot.bin`.**
|
||||
|
||||
## Status
|
||||
|
||||
Tested on one console (PS5, firmware 13.60) on 2026-10-13, with the North
|
||||
Tested on one console (PS5, firmware 13.60) on 2026-10-13 and 2026-10-14, with the North
|
||||
American disc (`GMSE01`, revision 0). "Seen" means it ran there and was
|
||||
watched by a person playing, looked at in the console's own screenshots, or
|
||||
read in the title's log. Nothing else is claimed.
|
||||
@@ -74,9 +83,12 @@ read in the title's log. Nothing else is claimed.
|
||||
| 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 |
|
||||
|
||||
### Known problems
|
||||
|
||||
@@ -89,8 +101,11 @@ read in the title's log. Nothing else is claimed.
|
||||
- **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.
|
||||
- **No save states.** sms-pc-port has since gained them; this port is built on
|
||||
the revision before, and has not tried them.
|
||||
- **Save states belong to the build 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
|
||||
where he was and keeps the progress, but the level starts afresh. Keep a
|
||||
copy of your memory card save as well.
|
||||
|
||||
## What the console needed
|
||||
|
||||
@@ -116,6 +131,11 @@ For the curious, and for anyone porting something similar. All of it is in
|
||||
- **Threads.** The game's video decoder runs below its main thread and was
|
||||
starved whenever a frame had no time to spare; the main thread now gives the
|
||||
others a turn.
|
||||
- **Save states.** sms-pc-port finds the game's global variables for a state
|
||||
in the link map after linking. The console's title is linked by lld and
|
||||
loaded at an address of its own choosing, so the map is read with lld's
|
||||
layout and the ranges are written at link addresses, which the game slides
|
||||
by where it was loaded.
|
||||
- **The console's own menu.** The touch pad's button opens settings a PS5 has
|
||||
no keyboard or launcher for.
|
||||
|
||||
@@ -129,7 +149,7 @@ 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 f3590911967886041f41586596b35ed124e8a43f # see ps5/kit: REVISIONS
|
||||
git -C vendor/sms-pc-port checkout c8fb4a3e81a2176588574395302ecf862f9a440a # see ps5/kit: REVISIONS
|
||||
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/
|
||||
|
||||
@@ -79,8 +79,9 @@ set(SMS_GX_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# The link: every object and archive CMake would hand the linker goes to the
|
||||
# console's recipe instead (the platform layer, the CRT, OpenGL, the fake SELF)
|
||||
# (and the port's save state tool, which writes the game's data ranges into the linked ELF)
|
||||
set(CMAKE_CXX_LINK_EXECUTABLE
|
||||
"bash ${ps5}/tools/link-title.sh ${CMAKE_BINARY_DIR} ${PS5_VULKAN_DIR} ${PS5_PAYLOAD_SDK} --whole-archive ${ps5_platform_archive} --no-whole-archive <LINK_FLAGS> <OBJECTS> <LINK_LIBRARIES>")
|
||||
"env SMS_SAVESTATE_RANGES=${CMAKE_SOURCE_DIR}/tools/savestate_ranges.py bash ${ps5}/tools/link-title.sh ${CMAKE_BINARY_DIR} ${PS5_VULKAN_DIR} ${PS5_PAYLOAD_SDK} --whole-archive ${ps5_platform_archive} --no-whole-archive <LINK_FLAGS> <OBJECTS> <LINK_LIBRARIES>")
|
||||
# (the start-up constructor in the platform archive has no caller: hence whole)
|
||||
|
||||
# The platform archive is named by its file in the link, so the game does not
|
||||
|
||||
+116
-40
@@ -5,6 +5,8 @@ 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
|
||||
@@ -27,12 +29,15 @@ You need three things.
|
||||
revision 0), as `.iso`, `.gcm` or Dolphin's `.ciso`. Other regions are not
|
||||
supported.
|
||||
|
||||
**3. A computer** (Windows, macOS or Linux) with:
|
||||
**3. A computer**, one of:
|
||||
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop/), installed and running
|
||||
- an internet connection (the build downloads a compiler and the source code)
|
||||
- about 4 GB of free disk space
|
||||
- 8 GB of memory or more
|
||||
- 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*.)
|
||||
|
||||
---
|
||||
|
||||
@@ -41,34 +46,73 @@ supported.
|
||||
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: Start Docker
|
||||
## Step 2: Get your computer ready (once)
|
||||
|
||||
Open Docker Desktop and wait until it says it is running.
|
||||
**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** and run the command for your system.
|
||||
|
||||
**macOS or Linux**
|
||||
Open a terminal **in the kit's folder** (on Windows, the Ubuntu window from
|
||||
step 2) and run:
|
||||
|
||||
```sh
|
||||
./make-title.sh
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
docker build -t sunshine-ps5-kit .
|
||||
docker run --rm -v ${PWD}\out:/out sunshine-ps5-kit /out
|
||||
```
|
||||
|
||||
(If you use WSL, the macOS and Linux command works there instead.)
|
||||
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 | Builds the container; downloads a compiler (about 1 GB) | a few minutes |
|
||||
| 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 |
|
||||
@@ -79,6 +123,8 @@ 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
|
||||
@@ -104,6 +150,7 @@ image** into it, beside `eboot.bin`, so that you end up with:
|
||||
| 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** |
|
||||
@@ -134,6 +181,15 @@ 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
|
||||
@@ -143,17 +199,13 @@ downloads the [Super Mario Sunshine UHD Texture Pack](https://github.com/qashto/
|
||||
(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:
|
||||
|
||||
**macOS or Linux**
|
||||
|
||||
```sh
|
||||
./make-texture-pack.sh
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
docker run --rm --entrypoint python3 -v ${PWD}\out:/out sunshine-ps5-kit /kit/tools/get-texture-pack.py /out
|
||||
```
|
||||
(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:
|
||||
@@ -176,25 +228,46 @@ shrunk the same way with `tools/shrink-texture-pack.py SOURCE DEST`.
|
||||
|
||||
## If something goes wrong
|
||||
|
||||
**"docker was not found"**
|
||||
Docker Desktop is not installed, or not running. Start it and try again.
|
||||
**"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", or Docker closes**
|
||||
It ran out of memory. Either give Docker more (Docker Desktop > Settings >
|
||||
Resources), or run fewer compilers at once:
|
||||
**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, add `-e JOBS=4` after `docker run --rm`.
|
||||
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.
|
||||
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`
|
||||
@@ -211,13 +284,15 @@ 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.
|
||||
|
||||
## Without Docker (Linux only)
|
||||
## With Docker instead
|
||||
|
||||
If you already have LLVM 22 (clang, lld, llvm tools), CMake, Ninja, git,
|
||||
Python 3, `patch`, a C++ compiler and the zlib development headers:
|
||||
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:
|
||||
|
||||
```sh
|
||||
./build-title.sh
|
||||
```powershell
|
||||
docker build -t sunshine-ps5-kit .
|
||||
docker run --rm -v ${PWD}\out:/out sunshine-ps5-kit /out
|
||||
```
|
||||
|
||||
---
|
||||
@@ -226,11 +301,12 @@ Python 3, `patch`, a C++ compiler and the zlib development headers:
|
||||
|
||||
| Path | What |
|
||||
| --- | --- |
|
||||
| `make-title.sh`, `Dockerfile` | Runs the build in a container. |
|
||||
| `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). |
|
||||
| `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. |
|
||||
|
||||
+36
-22
@@ -13,12 +13,16 @@
|
||||
# involved: the title plays your own disc image, which you copy beside
|
||||
# eboot.bin on the console.
|
||||
#
|
||||
# Needs: bash, git, python3, cmake, ninja, patch, a host C++ compiler with zlib,
|
||||
# and LLVM 22 (clang, lld, llvm-ar). make-title.sh runs it in a container that
|
||||
# has them.
|
||||
# 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 the clang to use (default: /usr/lib/llvm-22/bin/clang, or clang-22)
|
||||
# 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
|
||||
@@ -26,10 +30,21 @@ 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"
|
||||
@@ -39,30 +54,29 @@ if [[ ${1:-} == --tools ]]; then
|
||||
build_tools
|
||||
exit 0
|
||||
fi
|
||||
case ${1:-} in -h | --help) sed -n '2,24p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
|
||||
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:-$(nproc)}
|
||||
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 compiler: LLVM 22, with the kit's builtins for the console's processor
|
||||
real_clang=${PS5_CLANG:-}
|
||||
if [[ -z $real_clang ]]; then
|
||||
for candidate in /usr/lib/llvm-22/bin/clang clang-22; do
|
||||
if command -v "$candidate" > /dev/null; then real_clang=$(command -v "$candidate"); break; fi
|
||||
done
|
||||
fi
|
||||
[[ -n $real_clang ]] || fail "clang 22 was not found (set PS5_CLANG, or use make-title.sh)"
|
||||
real_clang=$(readlink -f "$real_clang")
|
||||
# ---- 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")
|
||||
for tool in ld.lld llvm-ar llvm-nm llvm-config; do
|
||||
[[ -x $llvm_bin/$tool ]] || fail "$llvm_bin/$tool is missing (LLVM's lld and tools, beside clang)"
|
||||
done
|
||||
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
|
||||
@@ -82,7 +96,7 @@ 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 $(sha256sum "$kit/ps5/patches/sms-pc-port.patch" | cut -d' ' -f1)"
|
||||
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"
|
||||
@@ -107,7 +121,7 @@ if [[ ! -f $build/build.ninja ]]; then
|
||||
-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; fail "the configuration failed"; }
|
||||
{ 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
|
||||
@@ -123,14 +137,14 @@ while IFS= read -r line; do
|
||||
*) inputs+=("$kit/lib/$line") ;;
|
||||
esac
|
||||
done < "$kit/lib/link.txt"
|
||||
PS5_DIST_DIR="$out" PS5_OPENGL_PREFIX="$kit/opengl/sdk" \
|
||||
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 ]]; then
|
||||
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
|
||||
|
||||
@@ -1,23 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# Super Mario Sunshine for the PlayStation 5 - an HD texture pack for the console, made in the kit's container.
|
||||
# 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)
|
||||
# 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 Docker (or Podman:
|
||||
# CONTAINER=podman), an internet connection and about 5 GB of free disk space.
|
||||
# /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)
|
||||
container=${CONTAINER:-docker}
|
||||
command -v "$container" > /dev/null || { echo "make-texture-pack.sh: $container was not found: install Docker Desktop (or Podman)" >&2; exit 2; }
|
||||
tag=sunshine-ps5-kit:$(<"$kit/VERSION")
|
||||
# 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
|
||||
|
||||
"$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
|
||||
# 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"
|
||||
+14
-7
@@ -1,23 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# Super Mario Sunshine for the PlayStation 5 - the builder kit, run in a container.
|
||||
# Super Mario Sunshine for the PlayStation 5 - the builder kit: make the title.
|
||||
#
|
||||
# make-title.sh [OUT] OUT/PPSA99312/ (OUT defaults to ./out)
|
||||
# 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)
|
||||
#
|
||||
# Needs Docker (or Podman: CONTAINER=podman) and an internet connection: the
|
||||
# first run builds the container's image, which downloads a compiler, and each
|
||||
# title build fetches the game's source code from GitHub the first time (it is
|
||||
# kept in OUT/.work afterwards). See README.md.
|
||||
# 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)" >&2; exit 2; }
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
# 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
|
||||
}
|
||||
+305
-41
@@ -1,5 +1,5 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 2ad719a..8a74c1b 100644
|
||||
index 091476d..3fcb2a8 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -90,6 +90,18 @@ include(${CMAKE_BINARY_DIR}/decomp_sources.cmake)
|
||||
@@ -21,17 +21,17 @@ index 2ad719a..8a74c1b 100644
|
||||
set(SMS_GAME_FLAGS
|
||||
-w -fno-strict-aliasing -fwrapv -fno-omit-frame-pointer
|
||||
# Retail MSL has no _FORTIFY_SOURCE checks (Ubuntu g++ enables them at -O1+;
|
||||
@@ -118,7 +130,8 @@ check_cxx_compiler_flag("-fno-unreachable-traps" SMS_HAVE_NO_UNREACHABLE_TRAPS)
|
||||
if(SMS_HAVE_NO_UNREACHABLE_TRAPS)
|
||||
list(APPEND SMS_GAME_FLAGS -fno-unreachable-traps)
|
||||
endif()
|
||||
-if(SMS_ARCH STREQUAL "64")
|
||||
@@ -121,7 +133,8 @@ endif()
|
||||
# Non-PIE: the game's code and data sit at the same addresses every run, which
|
||||
# 64-bit builds need for pointers in u32 slots and save states need everywhere
|
||||
# (a state holds pointers to the game's vtables and globals).
|
||||
-if(SMS_ARCH STREQUAL "64" OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
+# (The PS5 only runs position-independent code; its titles load at 0x400000.)
|
||||
+if(SMS_ARCH STREQUAL "64" AND NOT PS5)
|
||||
+if((SMS_ARCH STREQUAL "64" OR CMAKE_SYSTEM_NAME STREQUAL "Linux") AND NOT PS5)
|
||||
list(APPEND SMS_GAME_FLAGS -fno-pie)
|
||||
endif()
|
||||
# Clang rejects the game's pointer-to-int truncations ((int)ptr, (u32)ptr) as
|
||||
@@ -128,6 +141,13 @@ endif()
|
||||
@@ -131,6 +144,13 @@ endif()
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND SMS_GAME_FLAGS -fms-extensions)
|
||||
endif()
|
||||
@@ -45,7 +45,7 @@ index 2ad719a..8a74c1b 100644
|
||||
|
||||
# macOS: native arm64 reserves the low 4 GiB as PAGEZERO, so port_low_alloc
|
||||
# cannot satisfy pointer-in-u32. Build x86_64 (Rosetta) with a 4 KiB pagezero.
|
||||
@@ -294,6 +314,10 @@ elseif(WIN32)
|
||||
@@ -300,6 +320,10 @@ elseif(WIN32)
|
||||
set(_object_prefix "")
|
||||
endif()
|
||||
set(SMS_OBJCOPY objcopy)
|
||||
@@ -56,7 +56,7 @@ index 2ad719a..8a74c1b 100644
|
||||
else()
|
||||
set(_object_prefix "")
|
||||
set(SMS_OBJCOPY objcopy)
|
||||
@@ -315,7 +339,7 @@ list(FILTER SMS_PLATFORM_SOURCES EXCLUDE REGEX "/platform/gx/")
|
||||
@@ -321,7 +345,7 @@ list(FILTER SMS_PLATFORM_SOURCES EXCLUDE REGEX "/platform/gx/")
|
||||
# platform/mods/eclipse is built by cmake/eclipse.cmake, and only with SMS_ECLIPSE.
|
||||
list(FILTER SMS_PLATFORM_SOURCES EXCLUDE REGEX "/platform/mods/eclipse/")
|
||||
list(FILTER SMS_PLATFORM_SOURCES EXCLUDE REGEX "/tests/")
|
||||
@@ -65,7 +65,7 @@ index 2ad719a..8a74c1b 100644
|
||||
list(FILTER SMS_PLATFORM_SOURCES EXCLUDE REGEX "/platform/trace/trace\\.cpp$")
|
||||
endif()
|
||||
add_executable(sms ${SMS_PORT_SRC}/port_main.cpp ${SMS_PLATFORM_SOURCES})
|
||||
@@ -366,13 +390,14 @@ if(WIN32)
|
||||
@@ -373,13 +397,14 @@ if(WIN32)
|
||||
# data. Keep the image below 4 GiB, as on the Linux non-PIE build.
|
||||
target_link_options(sms PRIVATE -Wl,--image-base,0x400000 -Wl,--disable-dynamicbase -Wl,--disable-high-entropy-va)
|
||||
endif()
|
||||
@@ -77,11 +77,39 @@ index 2ad719a..8a74c1b 100644
|
||||
else()
|
||||
target_link_options(sms PRIVATE -Wl,--defsym=lbl_80004600=0x80004600)
|
||||
endif()
|
||||
-if(SMS_ARCH STREQUAL "64")
|
||||
+if(SMS_ARCH STREQUAL "64" AND NOT PS5)
|
||||
-if(SMS_ARCH STREQUAL "64" OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
+if((SMS_ARCH STREQUAL "64" OR CMAKE_SYSTEM_NAME STREQUAL "Linux") AND NOT PS5)
|
||||
target_compile_options(sms PRIVATE -fno-pie)
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
target_link_options(sms PRIVATE -no-pie)
|
||||
@@ -398,16 +423,21 @@ if(APPLE)
|
||||
endif()
|
||||
# Save states copy the game's own global variables: the link map says where
|
||||
# they are, and tools/savestate_ranges.py writes that into the executable.
|
||||
-if(APPLE)
|
||||
+# (The PS5's link makes its title from the executable in the link itself, so
|
||||
+# the console's link script does this there.)
|
||||
+if(PS5)
|
||||
+elseif(APPLE)
|
||||
target_link_options(sms PRIVATE "LINKER:-map,${CMAKE_BINARY_DIR}/sms.map")
|
||||
else()
|
||||
target_link_options(sms PRIVATE "LINKER:-Map=${CMAKE_BINARY_DIR}/sms.map")
|
||||
endif()
|
||||
-add_custom_command(TARGET sms POST_BUILD
|
||||
- COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/savestate_ranges.py
|
||||
- --map ${CMAKE_BINARY_DIR}/sms.map --exe $<TARGET_FILE:sms>
|
||||
- --archive libsms_game.a --archive libsms_eclipse.a
|
||||
- COMMENT "Recording the game's data for save states")
|
||||
+if(NOT PS5)
|
||||
+ add_custom_command(TARGET sms POST_BUILD
|
||||
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/savestate_ranges.py
|
||||
+ --map ${CMAKE_BINARY_DIR}/sms.map --exe $<TARGET_FILE:sms>
|
||||
+ --archive libsms_game.a --archive libsms_eclipse.a
|
||||
+ COMMENT "Recording the game's data for save states")
|
||||
+endif()
|
||||
find_package(Threads REQUIRED)
|
||||
if(WIN32 AND SMS_ARCH STREQUAL "64")
|
||||
add_executable(sms_windows_stack_test EXCLUDE_FROM_ALL
|
||||
diff --git a/decomp-patches/modhook-zz-macos-data-exports.patch b/decomp-patches/modhook-zz-macos-data-exports.patch
|
||||
index 6bb5f59..bdd84c6 100644
|
||||
--- a/decomp-patches/modhook-zz-macos-data-exports.patch
|
||||
@@ -339,7 +367,7 @@ index c85815a..956cdf3 100644
|
||||
// a headless run, so the process would ignore them. (No effect if the GX
|
||||
// layer initialised SDL first; the user's own setting wins.)
|
||||
diff --git a/platform/gx/CMakeLists.txt b/platform/gx/CMakeLists.txt
|
||||
index 50a25d1..d97dde6 100644
|
||||
index 3a31075..bf0b8eb 100644
|
||||
--- a/platform/gx/CMakeLists.txt
|
||||
+++ b/platform/gx/CMakeLists.txt
|
||||
@@ -55,8 +55,10 @@ target_include_directories(sms_gx
|
||||
@@ -790,23 +818,24 @@ index bcd4174..3887f46 100644
|
||||
|
||||
// Once a display frame: over the memory budget (SMS_TEXTURE_PACK_MB, 1536
|
||||
diff --git a/platform/gx/src/gx_internal.h b/platform/gx/src/gx_internal.h
|
||||
index 88b4c57..d204cf6 100644
|
||||
index 1b0c961..77410da 100644
|
||||
--- a/platform/gx/src/gx_internal.h
|
||||
+++ b/platform/gx/src/gx_internal.h
|
||||
@@ -284,7 +284,12 @@ void shaderPrepareStage(int stage); // while a stage loads: its programs (the w
|
||||
// ---------------------------------------------------------------- overlay panels
|
||||
// Each slot keeps its own texture, so panels drawn in the same frame keep their
|
||||
// cached bitmaps. GXPC_DrawOverlay draws the debug overlay's slot.
|
||||
-enum OverlaySlot { OVERLAY_DEBUG, OVERLAY_CHEAT_MENU, OVERLAY_WARP_MENU, OVERLAY_SLOTS };
|
||||
+enum OverlaySlot { OVERLAY_DEBUG, OVERLAY_CHEAT_MENU, OVERLAY_WARP_MENU, OVERLAY_LOADING, OVERLAY_SLOTS };
|
||||
@@ -290,8 +290,14 @@ enum OverlaySlot {
|
||||
OVERLAY_WARP_MENU,
|
||||
OVERLAY_STATE_MENU,
|
||||
OVERLAY_STATE_TOAST,
|
||||
+ OVERLAY_LOADING,
|
||||
OVERLAY_SLOTS
|
||||
};
|
||||
+// A black frame saying LOADING, shown from inside a long load (gx_platform.cpp);
|
||||
+// `step` animates its dots.
|
||||
+void loadingScreen(int step);
|
||||
+size_t hiresResidentBytes(); // the texture pack's replacements held, for the statistics
|
||||
+void drawLoadingFrame(int step, int winW, int winH); // gx_render.cpp, on the GL thread
|
||||
void drawOverlayPanel(OverlaySlot slot, const uint8_t* rgba, int w, int h, int x, int y, int scale, int winW, int winH);
|
||||
// Panel bitmaps (gx_overlay.cpp): RGBA8, w x h, row 0 at the top. Text shares
|
||||
// one scratch buffer, so panels are rasterised on the GL thread only.
|
||||
// SMS_WINDOW_SHOTS: the window as shown, menus included, at GX frame `frame` (GL thread, before the swap).
|
||||
void captureWindow(uint32_t frame, int w, int h);
|
||||
diff --git a/platform/gx/src/gx_overlay.cpp b/platform/gx/src/gx_overlay.cpp
|
||||
index d4cb8da..add238b 100644
|
||||
--- a/platform/gx/src/gx_overlay.cpp
|
||||
@@ -896,10 +925,10 @@ index d4cb8da..add238b 100644
|
||||
GXPC_DrawOverlay(upload, w, h, 8, 8, scale, winW, winH);
|
||||
}
|
||||
diff --git a/platform/gx/src/gx_platform.cpp b/platform/gx/src/gx_platform.cpp
|
||||
index 23778b5..e39c1f3 100644
|
||||
index f2c1fc4..baa3ab8 100644
|
||||
--- a/platform/gx/src/gx_platform.cpp
|
||||
+++ b/platform/gx/src/gx_platform.cpp
|
||||
@@ -37,6 +37,15 @@
|
||||
@@ -38,6 +38,15 @@
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#endif
|
||||
@@ -915,7 +944,7 @@ index 23778b5..e39c1f3 100644
|
||||
|
||||
namespace gx {
|
||||
extern void (*g_displayCopyHook)(const void* xfb);
|
||||
@@ -598,10 +607,86 @@ void dumpFrame(const void* xfb) {
|
||||
@@ -599,10 +608,86 @@ void dumpFrame(const void* xfb) {
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
@@ -1002,7 +1031,7 @@ index 23778b5..e39c1f3 100644
|
||||
const bool skip = s_skipPresent && s_presentCost >= kSkipPresentCost;
|
||||
s_skipPresent = false;
|
||||
s_lastPresent = 0;
|
||||
@@ -737,6 +822,27 @@ int GXPC_InitAuto(float efbScale) {
|
||||
@@ -738,6 +823,27 @@ int GXPC_InitAuto(float efbScale) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1030,7 +1059,7 @@ index 23778b5..e39c1f3 100644
|
||||
void GXPC_Present(const void* xfb) {
|
||||
#ifdef SMS_GX_HAVE_SDL2
|
||||
if (s_mode == MODE_WINDOW && s_window) {
|
||||
@@ -864,6 +970,25 @@ void sms_gx_pump_events(void) {
|
||||
@@ -867,6 +973,25 @@ void sms_gx_pump_events(void) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -1057,7 +1086,7 @@ index 23778b5..e39c1f3 100644
|
||||
// the debug overlay is open, and closes it; a press that does either
|
||||
// is kept from the pad layer, release included. Otherwise they reach
|
||||
diff --git a/platform/gx/src/gx_render.cpp b/platform/gx/src/gx_render.cpp
|
||||
index dae542e..b985d17 100644
|
||||
index bd96a35..4e7c559 100644
|
||||
--- a/platform/gx/src/gx_render.cpp
|
||||
+++ b/platform/gx/src/gx_render.cpp
|
||||
@@ -14,9 +14,25 @@
|
||||
@@ -1390,9 +1419,9 @@ index dae542e..b985d17 100644
|
||||
const GLsizeiptr bytes = GLsizeiptr(s_efbW) * EFB_H * 4;
|
||||
int cur = s_frameNo & 1, t = depth ? 1 : 0;
|
||||
if (!(s_peekIssued & (1u << t))) {
|
||||
@@ -2999,6 +3213,27 @@ void GXPC_DrawOverlay(const uint8_t* rgba, int w, int h, int x, int y, int scale
|
||||
|
||||
} // extern "C"
|
||||
@@ -3030,6 +3244,27 @@ void gx::captureWindow(uint32_t frame, int w, int h) {
|
||||
}
|
||||
}
|
||||
|
||||
+// The window black, with LOADING and its dots at the bottom right.
|
||||
+void gx::drawLoadingFrame(int step, int winW, int winH) {
|
||||
@@ -1418,7 +1447,7 @@ index dae542e..b985d17 100644
|
||||
void gx::drawOverlayPanel(OverlaySlot slot, const uint8_t* rgba, int w, int h, int x, int y, int scale, int winW, int winH) {
|
||||
if (!s_ready || w <= 0 || h <= 0) return;
|
||||
if (!rgba && (!s_overlayTex[slot] || w != s_overlayW[slot] || h != s_overlayH[slot])) return;
|
||||
@@ -3093,7 +3328,7 @@ void main() { o_color = texture(u_color, v_uv); }
|
||||
@@ -3124,7 +3359,7 @@ void main() { o_color = texture(u_color, v_uv); }
|
||||
float window[2] = {float(winW), float(winH)};
|
||||
glUniform4fv(s_overlayRect, 1, rect);
|
||||
glUniform2fv(s_overlayWindow, 1, window);
|
||||
@@ -1460,7 +1489,7 @@ index 56ff7cc..7d3826b 100644
|
||||
// The kernel checks the source and fails with EFAULT instead of faulting.
|
||||
struct iovec local = {to, size}, remote = {(void*)from, size};
|
||||
diff --git a/platform/os/os_thread.cpp b/platform/os/os_thread.cpp
|
||||
index e676983..335167f 100644
|
||||
index 193c3a7..a1f253d 100644
|
||||
--- a/platform/os/os_thread.cpp
|
||||
+++ b/platform/os/os_thread.cpp
|
||||
@@ -328,6 +328,19 @@ double mono_seconds()
|
||||
@@ -1533,7 +1562,7 @@ index e676983..335167f 100644
|
||||
Host* h = new Host();
|
||||
pthread_cond_init(&h->cv, NULL);
|
||||
h->irq_enabled = true;
|
||||
@@ -854,3 +901,37 @@ extern "C" void OSWaitCond(OSCond* c, OSMutex* m)
|
||||
@@ -871,3 +918,37 @@ extern "C" void OSWaitCond(OSCond* c, OSMutex* m)
|
||||
extern "C" void OSSignalCond(OSCond* c) { OSWakeupThread(&c->queue); }
|
||||
|
||||
extern "C" double port_idle_seconds(void) { return g_idle_seconds; }
|
||||
@@ -1572,10 +1601,10 @@ index e676983..335167f 100644
|
||||
+ OSRestoreInterrupts(lvl);
|
||||
+}
|
||||
diff --git a/platform/pad/pad.cpp b/platform/pad/pad.cpp
|
||||
index ecc299e..2a64638 100644
|
||||
index c05f07f..930473b 100644
|
||||
--- a/platform/pad/pad.cpp
|
||||
+++ b/platform/pad/pad.cpp
|
||||
@@ -68,7 +68,13 @@ __attribute__((weak)) int GXPC_GameInputBlocked(void);
|
||||
@@ -69,7 +69,13 @@ __attribute__((weak)) int GXPC_GameInputBlocked(void);
|
||||
// SMS_CAMERA_SPEED=percent scales manual camera rotation (100 = retail)
|
||||
// SMS_FREE_CAMERA=1 the camera stays where it is put (no auto swing-back)
|
||||
// SMS_MOUSE_SENSITIVITY=% mouse look speed, when the GX layer captures the mouse
|
||||
@@ -1589,7 +1618,7 @@ index ecc299e..2a64638 100644
|
||||
int port_free_camera = 0;
|
||||
f32 port_camera_speed_x = 1.0f;
|
||||
f32 port_camera_speed_y = 1.0f;
|
||||
@@ -664,6 +670,12 @@ void init()
|
||||
@@ -739,6 +745,12 @@ void init()
|
||||
parse_bindings(kDefaultBindings, "defaults");
|
||||
g_invert_cx = env_on("SMS_CAMERA_INVERT_X");
|
||||
g_invert_cy = env_on("SMS_CAMERA_INVERT_Y");
|
||||
@@ -1602,7 +1631,7 @@ index ecc299e..2a64638 100644
|
||||
if (g_invert_cx || g_invert_cy)
|
||||
port_log("[pad] camera inverted:%s%s\n", g_invert_cx ? " X" : "", g_invert_cy ? " Y" : "");
|
||||
const char* path = getenv("SMS_BINDINGS");
|
||||
@@ -736,8 +748,10 @@ void autopress_init()
|
||||
@@ -811,8 +823,10 @@ void autopress_init()
|
||||
g_auto.push_back(a);
|
||||
}
|
||||
// Headless runs have no SDL event loop: set the key state directly.
|
||||
@@ -1613,7 +1642,7 @@ index ecc299e..2a64638 100644
|
||||
port_log("[pad] SMS_AUTOPRESS: %u scripted presses via %s\n", (unsigned)g_auto.size(),
|
||||
g_push ? "SDL_PushEvent (keyboard path)" : "direct key state");
|
||||
}
|
||||
@@ -909,6 +923,20 @@ extern "C" u32 PADRead(PADStatus* status)
|
||||
@@ -1045,6 +1059,20 @@ extern "C" u32 PADRead(PADStatus* status)
|
||||
return PAD_CHAN0_BIT;
|
||||
}
|
||||
|
||||
@@ -1647,7 +1676,7 @@ index e3838ba..57faea6 100644
|
||||
int port_vi_deterministic(void);
|
||||
s64 port_vi_virtual_ticks(void);
|
||||
diff --git a/platform/port_runtime.cpp b/platform/port_runtime.cpp
|
||||
index e2ccfa4..30ad8c1 100644
|
||||
index cecfc98..1951eca 100644
|
||||
--- a/platform/port_runtime.cpp
|
||||
+++ b/platform/port_runtime.cpp
|
||||
@@ -73,6 +73,7 @@ int port_heat_haze = 1;
|
||||
@@ -1750,6 +1779,80 @@ index e2ccfa4..30ad8c1 100644
|
||||
if (p != MAP_FAILED)
|
||||
return p;
|
||||
#endif
|
||||
diff --git a/platform/savestate/savestate.cpp b/platform/savestate/savestate.cpp
|
||||
index 33a62af..fa5c45c 100644
|
||||
--- a/platform/savestate/savestate.cpp
|
||||
+++ b/platform/savestate/savestate.cpp
|
||||
@@ -6,7 +6,9 @@
|
||||
// after linking, so tools/savestate_ranges.py reads the link map and writes
|
||||
// them, with a fingerprint of the build, into kRanges below (it finds the
|
||||
// placeholder in the executable by its magic). A build without that table
|
||||
-// saves and loads the portable part only.
|
||||
+// saves and loads the portable part only. A position-independent build (the
|
||||
+// PS5's) has the ranges at link addresses, and the table's own link address
|
||||
+// beside them, so they slide by where the table was loaded.
|
||||
//
|
||||
// A state is saved and loaded between two frames (patch zzz-savestate-01), on
|
||||
// the game's thread while it owns the CPU, with no disc read or interrupt
|
||||
@@ -60,10 +62,12 @@ extern "C" __attribute__((weak)) const void* port_button_glyph(int chr);
|
||||
// Filled in after linking (tools/savestate_ranges.py; keep the layout in
|
||||
// step): the build's fingerprint and the game's writable data as [start, end)
|
||||
// address ranges. count 0: this build cannot save or load exactly.
|
||||
+// linkAddress: where the linker put this table, for a position-independent
|
||||
+// build (savestate_ranges.py --pie); 0, the ranges are where they are.
|
||||
struct PortSaveStateRanges {
|
||||
char magic[16];
|
||||
uint8_t fingerprint[32];
|
||||
- uint32_t count, reserved;
|
||||
+ uint32_t count, linkAddress;
|
||||
uint64_t ranges[2048][2];
|
||||
};
|
||||
__attribute__((used)) static const volatile PortSaveStateRanges kRanges = { "SMSSTATE-RANGES", {}, 0, 0, {} };
|
||||
@@ -120,11 +124,19 @@ uint32_t range_count()
|
||||
return n <= sizeof kRanges.ranges / sizeof kRanges.ranges[0] ? n : 0;
|
||||
}
|
||||
|
||||
+// Where range i is in this run.
|
||||
+u8* range_start(uint32_t i)
|
||||
+{
|
||||
+ const uintptr_t slide = kRanges.linkAddress ? (uintptr_t)&kRanges - kRanges.linkAddress : 0;
|
||||
+ return (u8*)(uintptr_t)(kRanges.ranges[i][0] + slide);
|
||||
+}
|
||||
+size_t range_size(uint32_t i) { return (size_t)(kRanges.ranges[i][1] - kRanges.ranges[i][0]); }
|
||||
+
|
||||
size_t globals_size()
|
||||
{
|
||||
size_t n = 0;
|
||||
for (uint32_t i = 0; i < range_count(); i++)
|
||||
- n += (size_t)(kRanges.ranges[i][1] - kRanges.ranges[i][0]);
|
||||
+ n += range_size(i);
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -575,9 +587,8 @@ int port_savestate_save(const PortSaveStatePortable* portable)
|
||||
job->aram.assign(aram, aram + aramSize);
|
||||
job->globals.reserve(info.globalsSize);
|
||||
for (uint32_t i = 0; i < range_count(); i++) {
|
||||
- const u8* a = (const u8*)(uintptr_t)kRanges.ranges[i][0];
|
||||
- const u8* b = (const u8*)(uintptr_t)kRanges.ranges[i][1];
|
||||
- job->globals.insert(job->globals.end(), a, b);
|
||||
+ const u8* a = range_start(i);
|
||||
+ job->globals.insert(job->globals.end(), a, a + range_size(i));
|
||||
}
|
||||
}
|
||||
port_log("[savestate] saving %s (%s)\n", job->path.c_str(), exact ? "exact" : "position only");
|
||||
@@ -661,9 +672,8 @@ int port_savestate_load_exact(const char* path, PortSaveStatePortable* portable,
|
||||
}
|
||||
size_t at = 0;
|
||||
for (uint32_t i = 0; i < range_count(); i++) {
|
||||
- u8* start = (u8*)(uintptr_t)kRanges.ranges[i][0];
|
||||
- const size_t len = (size_t)(kRanges.ranges[i][1] - kRanges.ranges[i][0]);
|
||||
- memcpy(start, globals.data() + at, len);
|
||||
+ const size_t len = range_size(i);
|
||||
+ memcpy(range_start(i), globals.data() + at, len);
|
||||
at += len;
|
||||
}
|
||||
for (int i = 0; i < n; i++)
|
||||
diff --git a/platform/trace/trace_windows.cpp b/platform/trace/trace_windows.cpp
|
||||
index 4067508..19ad775 100644
|
||||
--- a/platform/trace/trace_windows.cpp
|
||||
@@ -1788,3 +1891,164 @@ index 3a13700..239f209 100644
|
||||
const s32 now = (s32)VIGetRetraceCount();
|
||||
if (now - due > max_behind)
|
||||
due = now;
|
||||
diff --git a/tools/savestate_ranges.py b/tools/savestate_ranges.py
|
||||
index 73aa40a..db2f7df 100644
|
||||
--- a/tools/savestate_ranges.py
|
||||
+++ b/tools/savestate_ranges.py
|
||||
@@ -5,12 +5,16 @@
|
||||
|
||||
A save state copies the game's own global variables (platform/savestate), and
|
||||
only the link map says where the linker put them. This reads the map (GNU ld
|
||||
-on Linux and Windows, ld64 on macOS), keeps the writable sections that came
|
||||
+on Linux and Windows, ld64 on macOS, lld on the PS5), keeps the writable sections that came
|
||||
from the game's archives, merges the neighbours, and writes them with a
|
||||
fingerprint of the build into the placeholder table in the executable (found by
|
||||
its magic; kRanges in platform/savestate/savestate.cpp). The fingerprint is a
|
||||
SHA-256 of the executable with the table blank, so two identical builds share
|
||||
it and any other build differs. Running it again gives the same result.
|
||||
+
|
||||
+A position-independent executable (the PS5's) has its ranges at link
|
||||
+addresses: --pie also records where the linker put the table, and the game
|
||||
+slides the ranges by where it finds the table at run time.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -118,6 +122,42 @@ def ld64_ranges(lines, archives):
|
||||
return out
|
||||
|
||||
|
||||
+LLD_LINE = re.compile(r"^\s*([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+(\d+)( +)(\S.*)$")
|
||||
+
|
||||
+
|
||||
+def lld_ranges(lines, archives):
|
||||
+ """lld -Map: "VMA LMA Size Align Out In Symbol", the three last columns told
|
||||
+ apart by their indent (an output section, then 8 more spaces for an input
|
||||
+ section, 16 for a symbol)."""
|
||||
+ out = []
|
||||
+ section = None
|
||||
+ for line in lines:
|
||||
+ m = LLD_LINE.match(line.rstrip("\n"))
|
||||
+ if not m:
|
||||
+ continue
|
||||
+ indent, text = len(m.group(5)), m.group(6)
|
||||
+ if indent == 1:
|
||||
+ section = text.split()[0]
|
||||
+ elif indent == 9 and section and GNU_WRITABLE.match(section):
|
||||
+ start, size = int(m.group(1), 16), int(m.group(3), 16)
|
||||
+ if size and from_game(text, archives):
|
||||
+ out.append([start, start + size])
|
||||
+ return out
|
||||
+
|
||||
+
|
||||
+def link_address(image, offset):
|
||||
+ """The address an ELF64 file offset is loaded at (its PT_LOAD segment)."""
|
||||
+ if image[:4] != b"\x7fELF" or image[4] != 2:
|
||||
+ sys.exit("savestate_ranges: --pie needs a 64-bit ELF executable")
|
||||
+ phoff, = struct.unpack_from("<Q", image, 0x20)
|
||||
+ phentsize, phnum = struct.unpack_from("<HH", image, 0x36)
|
||||
+ for i in range(phnum):
|
||||
+ kind, _, p_offset, p_vaddr, _, p_filesz = struct.unpack_from("<IIQQQQ", image, phoff + i * phentsize)
|
||||
+ if kind == 1 and p_offset <= offset < p_offset + p_filesz:
|
||||
+ return p_vaddr + offset - p_offset
|
||||
+ sys.exit("savestate_ranges: the range table is in no loaded segment")
|
||||
+
|
||||
+
|
||||
def merge(ranges):
|
||||
ranges = sorted(ranges)
|
||||
merged = []
|
||||
@@ -134,13 +174,16 @@ def main():
|
||||
parser.add_argument("--map", required=True)
|
||||
parser.add_argument("--exe", required=True)
|
||||
parser.add_argument("--archive", action="append", default=[])
|
||||
+ parser.add_argument("--pie", action="store_true", help="record the table's link address, to slide by at run time")
|
||||
args = parser.parse_args()
|
||||
archives = args.archive or ["libsms_game.a"]
|
||||
|
||||
with open(args.map, encoding="utf-8", errors="replace") as f:
|
||||
lines = f.readlines()
|
||||
ld64 = any(line.startswith("# Object files:") for line in lines[:50])
|
||||
- ranges = merge(ld64_ranges(lines, archives) if ld64 else gnu_ranges(lines, archives))
|
||||
+ lld = any(re.match(r"\s+VMA\s+LMA\s+Size\s+Align\s+Out\s+In\s+Symbol", line) for line in lines[:5])
|
||||
+ ranges = merge(ld64_ranges(lines, archives) if ld64 else
|
||||
+ lld_ranges(lines, archives) if lld else gnu_ranges(lines, archives))
|
||||
if len(ranges) > MAX_RANGES:
|
||||
sys.exit(f"savestate_ranges: {len(ranges)} ranges, more than the table's {MAX_RANGES}")
|
||||
|
||||
@@ -152,7 +195,10 @@ def main():
|
||||
blank = bytes(TABLE_SIZE - len(MAGIC))
|
||||
image[at + len(MAGIC):at + TABLE_SIZE] = blank
|
||||
fingerprint = hashlib.sha256(image).digest()
|
||||
- table = fingerprint + struct.pack("<II", len(ranges), 0)
|
||||
+ table_address = link_address(image, at) if args.pie else 0
|
||||
+ if table_address >= 1 << 32:
|
||||
+ sys.exit("savestate_ranges: the range table's link address does not fit its field")
|
||||
+ table = fingerprint + struct.pack("<II", len(ranges), table_address)
|
||||
table += b"".join(struct.pack("<QQ", start, end) for start, end in ranges)
|
||||
image[at + len(MAGIC):at + len(MAGIC) + len(table)] = table
|
||||
|
||||
diff --git a/tools/tests/test_savestate_ranges.py b/tools/tests/test_savestate_ranges.py
|
||||
index 046a189..c1f72cb 100644
|
||||
--- a/tools/tests/test_savestate_ranges.py
|
||||
+++ b/tools/tests/test_savestate_ranges.py
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
-"""tools/savestate_ranges.py: the game's data found in GNU ld and ld64 link maps."""
|
||||
+"""tools/savestate_ranges.py: the game's data found in GNU ld, ld64 and lld link maps."""
|
||||
import importlib.util
|
||||
import pathlib
|
||||
+import struct
|
||||
import unittest
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
@@ -52,6 +53,36 @@ LD64 = """\
|
||||
<<dead>> \t0x00000008\t[ 2] _unused
|
||||
"""
|
||||
|
||||
+# lld (the PS5's link): an output section, input sections 8 spaces further in,
|
||||
+# symbols 16; sizes in hex without 0x
|
||||
+LLD = """\
|
||||
+ VMA LMA Size Align Out In Symbol
|
||||
+ 0 0 ed7b30 16384 .text
|
||||
+ 0 0 40 16 libsms_game.a(Main.cpp.o):(.text.main)
|
||||
+ 0 0 0 1 main
|
||||
+ 1adc000 1adc000 100 16 .data.rel.ro
|
||||
+ 1adc000 1adc000 100 16 libsms_game.a(Vtables.cpp.o):(.data.rel.ro._ZTV1T)
|
||||
+ 1da4240 1da4240 400 32 .data
|
||||
+ 1da4240 1da4240 4 4 /b/libps5_platform.a(platform.c.o):(.data.mouse_handle)
|
||||
+ 1da4240 1da4240 4 1 mouse_handle
|
||||
+ 1da4244 1da4244 3c 4 libsms_game.a(Application.cpp.o):(.data.gpApplication)
|
||||
+ 1da4244 1da4244 4 1 gpApplication
|
||||
+ 1da4280 1da4280 20 8 libsms_game.a(Mario.cpp.o):(.data._ZN5TMario5sDataE)
|
||||
+ 1df6658 1df6658 100 8 .bss
|
||||
+ 1df6658 1df6658 30 8 libsms_game.a(J2DPrint.cpp.o):(.bss._ZN8J2DPrint6mMutexE)
|
||||
+ 1df6688 1df6688 8 8 libsms_gx.a(gx_api.cpp.o):(.bss.s_state)
|
||||
+"""
|
||||
+
|
||||
+
|
||||
+def elf_with_segment(vaddr, p_offset):
|
||||
+ """A 64-bit ELF header with one PT_LOAD segment, for link_address."""
|
||||
+ image = bytearray(0x200)
|
||||
+ image[:5] = b"\x7fELF\x02"
|
||||
+ struct.pack_into("<Q", image, 0x20, 0x40) # e_phoff
|
||||
+ struct.pack_into("<HH", image, 0x36, 56, 1) # e_phentsize, e_phnum
|
||||
+ struct.pack_into("<IIQQQQ", image, 0x40, 1, 4, p_offset, vaddr, vaddr, 0x100)
|
||||
+ return image
|
||||
+
|
||||
|
||||
class RangesTest(unittest.TestCase):
|
||||
archives = ['libsms_game.a', 'libsms_eclipse.a']
|
||||
@@ -64,6 +95,14 @@ class RangesTest(unittest.TestCase):
|
||||
found = ranges.merge(ranges.ld64_ranges(LD64.splitlines(True), self.archives))
|
||||
self.assertEqual(found, [[0x200040, 0x200070], [0x300000, 0x300100]])
|
||||
|
||||
+ def test_lld_keeps_game_input_sections_in_writable_data(self):
|
||||
+ found = ranges.merge(ranges.lld_ranges(LLD.splitlines(True), self.archives))
|
||||
+ self.assertEqual(found, [[0x1da4244, 0x1da42a0], [0x1df6658, 0x1df6688]])
|
||||
+
|
||||
+ def test_link_address_of_a_file_offset(self):
|
||||
+ image = elf_with_segment(0x1ec0000, 0x100)
|
||||
+ self.assertEqual(ranges.link_address(image, 0x150), 0x1ec0050)
|
||||
+
|
||||
def test_archive_members_in_either_linker_spelling(self):
|
||||
self.assertTrue(ranges.from_game('/b/libsms_game.a(X.o)', self.archives))
|
||||
self.assertTrue(ranges.from_game('/b/libsms_game.a[3](X.o)', self.archives))
|
||||
@@ -15,7 +15,7 @@
|
||||
"conceptId": "99312",
|
||||
"contentBadgeType": 1,
|
||||
"contentId": "UP9000-PPSA99312_00-SUNSHINE00000000",
|
||||
"contentVersion": "00.008.000",
|
||||
"contentVersion": "00.009.000",
|
||||
"downloadDataSize": 256,
|
||||
"gameIntent": {
|
||||
"permittedIntents": [
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/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()
|
||||
+19
-4
@@ -123,7 +123,7 @@ done
|
||||
radv_link_flags+=(--version-script "$work/link/game-local.map")
|
||||
"$sdk/bin/prospero-lld" "${radv_linker_script[@]}" --eh-frame-hdr "${radv_link_flags[@]}" \
|
||||
--version-script "$native/app-symbols.map" --exclude-libs=ALL \
|
||||
-e _start -o "$work/link/llvm-pie.elf" \
|
||||
-e _start -o "$work/link/llvm-pie.elf" -Map="$work/link/sms.map" \
|
||||
-u ps5_agc_gate2_run \
|
||||
"$work/link/obj/app_crt.o" --start-group "${objects[@]}" "${experimental[@]}" \
|
||||
-L "$opengl/lib" -lPS5OpenGL --end-group \
|
||||
@@ -148,7 +148,8 @@ null_imports=$(comm -23 \
|
||||
awk '$1 == "U" { sub(/@.*/, "", $2); print $2 }' | sort -u) \
|
||||
<(for library in "$sdk"/target/lib/*.so "$work/link/stubs/libSceAgc.so" "$work/link/stubs/libSceAgcDriver.so" \
|
||||
"$work/link/stubs/libSceMouse.so"; do
|
||||
case ${library##*/} in libkernel_sys.so | libScePosixForWebKit.so) continue ;; esac
|
||||
# (an if, not a case: a Mac's bash 3.2 misreads a case pattern's ")" inside $( ))
|
||||
if [[ ${library##*/} == libkernel_sys.so || ${library##*/} == libScePosixForWebKit.so ]]; then continue; fi
|
||||
"$sdk/bin/llvm-nm" -D --defined-only "$library" 2>/dev/null | awk '{ print $NF }'
|
||||
done | sort -u))
|
||||
if [[ -n $null_imports ]]; then
|
||||
@@ -156,6 +157,14 @@ if [[ -n $null_imports ]]; then
|
||||
echo "link-title.sh: bind them to the platform layer (PS5_Vulkan's tools/radv-link.sh)" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Save states copy the game's own global variables: the port's tool reads where
|
||||
# the link put them from the map and writes them into the ELF, at link addresses
|
||||
# the game slides by where it was loaded (--pie). Without it, states keep only
|
||||
# the position.
|
||||
if [[ -n ${SMS_SAVESTATE_RANGES:-} ]]; then
|
||||
python3 "$SMS_SAVESTATE_RANGES" --pie --map "$work/link/sms.map" --exe "$work/link/llvm-pie.elf" \
|
||||
--archive libsms_game.a --archive libsms_eclipse.a
|
||||
fi
|
||||
"$tool" link --in "$work/link/llvm-pie.elf" --out "$work/eboot.elf.new" \
|
||||
--stub-dir "$sdk/target/lib" --stub "$work/link/stubs/libSceAgc.so" \
|
||||
--stub "$work/link/stubs/libSceAgcDriver.so" --stub "$work/link/stubs/libSceMouse.so" --module-sdk 0x02000009 \
|
||||
@@ -171,14 +180,20 @@ 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
|
||||
(cd "$vulkan/runtime" && sha256sum --check --strict --quiet libc.prx.sha256)
|
||||
# (Python, not sha256sum: the builder kit runs this on a Mac too)
|
||||
python3 - "$vulkan/runtime/libc.prx" "$vulkan/runtime/libc.prx.sha256" <<'CHECK'
|
||||
import hashlib, sys
|
||||
want = open(sys.argv[2]).read().split()[0]
|
||||
if hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest() != want:
|
||||
sys.exit("link-title.sh: libc.prx does not match libc.prx.sha256")
|
||||
CHECK
|
||||
cp "$vulkan/runtime/libc.prx" "$app/sce_module/libc.prx"
|
||||
mv "$work/eboot.elf.new" "$work/eboot.elf"
|
||||
# The linked ELF of the title last packaged, for tools/run.sh to symbolise a crash with
|
||||
# (the game's build tree may be on the VM's own disk)
|
||||
mkdir -p "$root/build"
|
||||
cp "$work/link/llvm-pie.elf" "$root/build/last-title.elf"
|
||||
printf '==> %s: %s (eboot.bin %s bytes)\n' "$title_id" "$app" "$(stat -c %s "$app/eboot.bin")"
|
||||
printf '==> %s: %s (eboot.bin %s bytes)\n' "$title_id" "$app" "$(wc -c < "$app/eboot.bin" | tr -d ' ')"
|
||||
# The licences beside eboot.bin
|
||||
mkdir -p "$app/licenses"
|
||||
|
||||
|
||||
@@ -72,11 +72,11 @@ 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/Dockerfile" "$kit/"
|
||||
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" "$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
|
||||
@@ -106,6 +106,37 @@ cp "$vulkan"/tooling/native/*.cpp "$vulkan"/tooling/native/*.hpp "$vulkan"/tooli
|
||||
"$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/"
|
||||
@@ -139,6 +170,7 @@ own_revision=$(git -C "$root" rev-parse HEAD 2>/dev/null || echo "(this kit's ow
|
||||
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"
|
||||
@@ -149,7 +181,9 @@ leaks=$(find "$kit" \( -name 'libsms_game*' -o -path '*/sms_game.dir/*' -o -path
|
||||
-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; }
|
||||
(cd "$root/dist/kit" && rm -f "$name.zip" && zip -qry "$name.zip" "$name")
|
||||
# (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"
|
||||
Reference in new issue
Block a user