SMS DOCS

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.

β„Ή You do not need to build to run SMS
If you just want to play media, grab a prebuilt ELF from the Releases page and follow Install & First Boot. This page is only for people who want to compile SMS themselves or hack on it.

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.

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.

⚠ Why only the build runs in the container
The 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:

ModuleRole
iomanxExtended I/O manager β€” the modern file-I/O core the device layers sit on
filexioEE↔IOP file-I/O bridge (the fileXio API SMS uses for BDM devices)
bdmBlock Device Manager β€” the common layer all massN: devices share
bdmfs_fatfsFAT / exFAT filesystem layer on top of BDM
sio2manSIO2 controller manager (the PS2SDK build, used so the SIO2 devices can hook it)
mcman / mcservMemory-card manager and server
padmanController (pad) manager
usbdUSB host driver
usbmass_bdUSB mass-storage block device β†’ massN:
mx4sio_bdMX4SIO microSD (SIO2) block device β†’ massN: (the hardened build β€” see below)
ata_bdInternal ATA (exFAT HDD) block device β†’ massN:
iLinkman / IEEE1394_bdi.LINK (FireWire) manager and block device β†’ massN:
mmcemanMMCE device manager (SD2PSX / MemCard PRO2) β†’ mmce0: / mmce1:
smbmanSMB 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.

β„Ή The modules are committed and XZ-compressed
The embedded modules live in the repo under 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:

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.

⚠ Driver internals are out of scope here
This page documents where the patched driver lives and how to rebuild it, not the specifics of the fix. The rationale and internals are written up in the repo's developer notes (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.

TriggerWhat CI does
Any push / PR / tagBuild in ps2dev/ps2dev:v1.0 and upload the built ELF as a per-commit artifact.
Push to the working branchRefresh 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* tagCut 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.

β„Ή Reproducibility in one line
Local build and CI build run the same command against the same pinned image, and a fresh checkout carries every build input (compressed modules, artwork). That is the whole reproducibility story: clone, docker run … make all, get bin/SMS.elf.

Recap