Building from Source
SMS is an EE (Emotion Engine) application that builds with the PS2 homebrew toolchain and links a set
of IOP driver modules directly into the ELF. This page is for developers: it walks through the one prerequisite
(Docker), the single make command that produces bin/SMS.elf, the driver modules embedded
at build time, the patched MX4SIO driver, and the CI workflow that rebuilds on every push.
Prerequisites
The build uses the pinned ps2dev toolchain image, so the only thing you need on your machine is
Docker and a checkout of the source tree. Everything else β the EE compiler, the IOP compiler, the PS2SDK
headers and libraries, bin2c, and so on β comes from inside the container.
- Docker installed and running (Docker Desktop on Windows/macOS, or the Docker engine on Linux).
- A clone of the SMS source tree (this repository), including its committed
irx/andimages/assets. - Enough disk to pull the toolchain image the first time you run it.
The pinned toolchain image
SMS builds against a single, version-pinned toolchain image: ps2dev/ps2dev:v1.0. This is the
public PS2 homebrew toolchain (its EE compiler is ee-gcc 3.2.3). Pinning to a specific tag matters β it is
what lets the build be reproducible: the project's CI produces a byte-identical ELF from the same source on
this exact image. Do not substitute a "latest" tag; use v1.0.
ps2dev/ps2dev:v1.0 image is built on an older Alpine (musl) base. That base is fine for compiling,
but modern GitHub Actions steps (checkout, artifact upload) run JavaScript on a Node runtime that this old musl
can't load. So on CI the checkout happens on the host and only the make step runs inside the
container. Locally you can do the whole thing in one docker run, as shown below.Building the EE app
From the root of the source tree, run the toolchain container with the repo mounted and invoke make.
The image is minimal, so the command also installs the small build-base package set it needs before building:
docker run --rm -v "$PWD":/src -w /src ps2dev/ps2dev:v1.0 \ sh -c 'apk add --no-cache build-base && make all'
That single command compiles every EE source file, embeds the IOP driver modules and the artwork, links the program, and strips it. The output is:
bin/SMS.elf
Mount the repo into the container
-v "$PWD":/src -w /src maps your current directory (the repo root) to /src inside the
container and makes it the working directory, so the build writes bin/SMS.elf straight back onto your
host filesystem. --rm discards the container when it exits.
Install the build-base and run make
apk add --no-cache build-base pulls in the host-side build tools the make run needs; then
make all does the actual compile and link. On CI the same step also adds git and
zip for packaging, but a plain local build only needs build-base.
Collect the ELF
When make finishes, the finished program is at bin/SMS.elf. That is the file you copy
to a device and boot β see Install & First Boot.
The build defaults to the full-device configuration (BDM ?= 1 in the Makefile), which enables the
BDM storage stack and the extra devices. That is the configuration this documentation describes, and the one you
want.
What if I'm on Windows and $PWD isn't expanded?
$PWD is a shell variable that expands to the current directory in a POSIX shell (bash, Git Bash,
WSL, macOS/Linux terminals). In PowerShell use ${PWD}, and in cmd.exe use %cd%. The rest
of the command is identical. The important part is that the repo root is bind-mounted to /src and
make all runs there.
Embedded IOP driver modules
SMS runs its storage and I/O drivers on the PS2's IOP (I/O Processor) as .irx modules. Rather than
load these off a device at runtime, SMS embeds them into the ELF at build time and loads them from memory as
it boots. That is what makes SMS self-contained β the single bin/SMS.elf carries every driver it
needs.
The Makefile lists the module set that gets linked in (the IOP_OBJS group, gated behind
BDM=1). In this fork that set is:
| Module | Role |
|---|---|
iomanx | Extended I/O manager β the modern file-I/O core the device layers sit on |
filexio | EEβIOP file-I/O bridge (the fileXio API SMS uses for BDM devices) |
bdm | Block Device Manager β the common layer all massN: devices share |
bdmfs_fatfs | FAT / exFAT filesystem layer on top of BDM |
sio2man | SIO2 controller manager (the PS2SDK build, used so the SIO2 devices can hook it) |
mcman / mcserv | Memory-card manager and server |
padman | Controller (pad) manager |
usbd | USB host driver |
usbmass_bd | USB mass-storage block device β massN: |
mx4sio_bd | MX4SIO microSD (SIO2) block device β massN: (the hardened build β see below) |
ata_bd | Internal ATA (exFAT HDD) block device β massN: |
iLinkman / IEEE1394_bd | i.LINK (FireWire) manager and block device β massN: |
mmceman | MMCE device manager (SD2PSX / MemCard PRO2) β mmce0: / mmce1: |
smbman | SMB network share client module |
On the EE side the Makefile links the libraries these need β -lmc (memory card),
-lpadx (pad), and -lfileXio β and defines -DBDM so the device code is
compiled in.
irx/ and are stored XZ-compressed (as
irx/*.irx.xz). At build time the Makefile runs each through bin2c to turn it into a C
array that links into the ELF, and SMS decompresses and loads them at boot with its own bundled XZ decoder. Because
the compressed modules (and the artwork) are committed, a fresh checkout already has every input the build needs β
make all just works, no separate module-build step required.The patched MX4SIO driver
The headline feature of this fork β reliable MX4SIO playback β depends on a
hardened version of the mx4sio_bd block-device driver. That driver is not the stock one: it is
rebuilt from the PS2SDK source with reliability hardening so playback stays robust under the demands a
continuously-streaming media player puts on the console.
So that the embedded binary is reproducible and reviewable, the patch is kept in the repo alongside the built module:
patches/mx4sio_bd-sms.patchβ the source patch, applied to PS2SDK'siop/sio/mx4sio_bd/.irx/mx4sio_bd.irxβ the resulting hardened module (committed compressed, like the others), which the EE app embeds.
Because the built module is already committed, you do not need to rebuild the driver to build SMS β the
standard make all above uses the committed irx/. You only rebuild the driver if you want
to change or re-verify it. To do that, in a PS2SDK source checkout:
patch -p1 < <repo>/patches/mx4sio_bd-sms.patch cd iop/sio/mx4sio_bd && PS2SDKSRC=<ps2sdk-root> make # -> mx4sio_bd.irx
then copy the resulting mx4sio_bd.irx back into the repo's irx/ (re-compressing it into
the committed irx/*.irx.xz form) and rebuild the EE app.
MX4SIO_SOLUTION.md) and
the patch itself for anyone who needs them.Continuous integration
A GitHub Actions workflow (.github/workflows/compilation.yml) builds SMS on every push (and
on pull requests and version tags). It checks the tree out on the host, then runs the same
docker run β¦ ps2dev/ps2dev:v1.0 β¦ make all build inside the pinned container β the identical command
you run locally β and packages the resulting bin/SMS.elf.
| Trigger | What CI does |
|---|---|
| Any push / PR / tag | Build in ps2dev/ps2dev:v1.0 and upload the built ELF as a per-commit artifact. |
| Push to the working branch | Refresh a single rolling pre-release that always holds the latest development ELF, so testers can grab the newest build from one stable URL. |
Push of a v* tag | Cut a stable release for that version tag with the built ELF attached. |
The workflow needs contents: write permission so it can publish the rolling and stable releases.
Because CI uses the same pinned toolchain as a local build, the ELF it produces matches a local
make all β a green CI run is a real signal that the tree still compiles cleanly end to end. See the
Releases & Changelog page for where the built ELFs land.
docker run β¦ make all, get bin/SMS.elf.Recap
- Install Docker; no local toolchain needed.
- Build with
docker run --rm -v "$PWD":/src -w /src ps2dev/ps2dev:v1.0 sh -c 'apk add --no-cache build-base && make all'. - Output is
bin/SMS.elf(default full-device /BDM=1build). - All IOP driver modules are embedded into the ELF from the committed, XZ-compressed
irx/. - The hardened MX4SIO driver ships prebuilt; rebuild it from
patches/mx4sio_bd-sms.patchonly if you're changing it. - CI rebuilds on every push with the same pinned toolchain and publishes the ELF.