Hello World on the PlayStation
How to compile and run Hello world on the original PlayStation hardware.
The original PlayStation, codenamed “PSX”, was launched around 19951 to a worldwide success. Sony’s opening move on the console market saw a proliferation of games due to its lax third party developer policy.
The software development kit (SDK) was available for licensed developers as part of Sony’s official developer tools 2. The PsyQ SDK had its origin within Psygnosis, a company Sony had acquired in the early 90’s.
Since the offical software development kit was expensive, Sony later launched a consumer grade development kit. The Net Yaroze3 was cheaper and targeted at hobbyists. It helped kick-start an indie game development movement.
More recently, open source SDK’s, such as PSXSDK, CKSDK and PSn00bSDK have also made the scene.
In this article, we’ll use the original PsyQ SDK and Nugget, a MIT licensed4 toolchain using modern compilers.
Tricking the PlayStation to run our code
Creating a PS1 hello world binary and running it in an emulator is hardly any fun. Therefore, we’ll need a proper PlayStation and some way to run our custom code on it.
To achieve that, we have to both physically ship our code to the PlayStation and get it executed. The naïve approach is to burn our compiled code on to a CD and and pop it in. Unfortunately that won’t work.
The PlayStation’s copy protection prevents us from booting custom CD’s5. To trick the PlayStation to run our Hello World, we’ll have to bypass that.
There are several options, ranging from a hardware modded PlayStation (modchip) to swapping a spinning CD6 to various software modifications. Softmods permit booting custom CD’s and executing payloads stored on memory cards, as well as payloads loaded via the serial port. The most popular softmods use either game enhancer cartridges, such as the Action replay and GameShark or the built-in memory card slot.
I chose to do the softmod using the memory card.
To do that, we need a way to package our our compiled code as a bootable “payload”, and write it to a PlayStation memory card. This one’s a bit of a chickend-and-egg problem, since the easiest way to write to a memory card is to use a soft modded PlayStation 2, which in turn requries you to have a memory card with a softmod on it.
Alternatively, you can get a memory card that has an SD card slot. There’s the MemcardPro and its Chinese counterpart PSxMemCard, which based on the open source SD2PSX. In addition there are various open source options7. I eventually settled on the PSxMemCard Gen2.
To trick the PlayStation to boot custom code, we’ll use FreePSXBoot. We’ll discuss this part later in the section on packaging our code.
Compiling a binary for the PSX
Lucky for me, there are great examples available online 8,9,10,11,12. For the groundwork, I used Nolibgs hello world examples.
The C source is listed below. Be sure to check your video mode is correct:
// Based on https://github.com/ABelliqueux/nolibgs_hello_worlds/blob/main/hello_world/hello_world.c
#include <sys/types.h>
#include <stdio.h>
#include <libgte.h>
#include <libetc.h>
#include <libgpu.h>
// Video Mode (0=NTSC, 1=PAL)
#define VMODE 0
#define SCREENXRES 320
#define SCREENYRES 240
// The PsyQ Debug font height
#define FONTHEIGHT 7
// Display buffers (double buffered)
DISPENV disp[2];
DRAWENV draw[2];
// Display buffer index
short db = 0;
void init(void)
{
ResetGraph(0);
// Display area setup
SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES);
SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES);
SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES);
SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES);
// Displau area setup (PAL)
if (VMODE)
{
SetVideoMode(MODE_PAL);
disp[0].screen.y += 8;
disp[1].screen.y += 8;
}
// Display masking setup
SetDispMask(1);
setRGB0(&draw[0], 50, 50, 50);
setRGB0(&draw[1], 50, 50, 50);
draw[0].isbg = 1;
draw[1].isbg = 1;
// Font setup
PutDispEnv(&disp[db]);
PutDrawEnv(&draw[db]);
FntLoad(960, 0);
// Print area setup (space for 12 characters at x=14, y=14)
FntOpen(14, 14, SCREENXRES, FONTHEIGHT, 0, 12);
}
void display(void)
{
// Wait for drawing and vertial blank
DrawSync(0);
VSync(0);
// Flip buffers
PutDispEnv(&disp[db]);
PutDrawEnv(&draw[db]);
db = !db;
}
int main(void)
{
init();
while (1)
{
FntPrint("Hello world !");
FntFlush(-1);
display();
}
return 0;
}
The PlayStation 1 used the 32-bit MIPS R3051 CPU which will be our compilation target. The nolibgs repository has compiler setup instructions for various operating systems.
On Ubuntu Linux, I did the following to compile the code:
# Install GNU C cross compiler for mips architecture
sudo apt-get install gcc-mipsel-linux-gnu g++-mipsel-linux-gnu binutils-mipsel-linux-gnu
# Get the nolibgs examples and Nugget toolchain
git clone https://github.com/ABelliqueux/nolibgs_hello_worlds.git --recursive
cd nolibgs_hello_worlds
# Get the PsyQ SDK
wget http://psx.arthus.net/sdk/Psy-Q/psyq-4.7-converted-full.7z
7z x psyq-4.7-converted-full.7z -o./psyq
# Save the example source listing from above
cp ~/Downloads/hello_world.c hello_world/hello_world.c
# Compile our ps-exe
make hello_world
# Check the compiled binary
cd hello_world
file hello_world.ps-exe
hello_world.ps-exe: Sony Playstation executable PC=0x80010000, .text=[0x80010000,0x7800], Stack=0x801fff00, ()
It’s worthwhile to check that the compiled executable conforms to the PlayStation’s requirements, like shown in the last step above.
The executable’s entry point (PC) should be at 0x8001000013.
Additionally, the executable should specify a stack pointer (Stack), since the PlayStation BIOS won’t set that up (I think?).
I was unable to boot executables that had No Stack!.
Packaging our payload on the memory card
To trick the PlayStation to boot custom code, we’ll use FreePSXBoot. It’s an exploit that uses a bug in the PSX BIOS to load arbitrary code from the memory card. The exploit is activated when the PlayStation tries to read the directory of the memory card14.
To package our Hello World .ps-exe, we’ll use the builder tool.
You’ll have to know what model your PlayStation is (or it’s BIOS version).
It’s usually written in the sticker on the bottom of the unit.
We’re using 9002 here.
# Get FreePSXBoot
git clone git@github.com:brad-lin/FreePSXBoot.git
cd FreePSXBoot/builder
# Compile the builder tool (if the build fails, check below)
make
# Package our ps-exe
./builder -model 9002 -in ../hello_world/hello_world.ps-exe -out payload.mcd
For me, compiling builder failed with error: ‘memcpy’ is not a member of ‘std’; did you mean ‘wmemcpy’. I had to apply the fix described here.
With that, we have our payload stored in payload.mcd memory card file15.
Next, we’ll transfer the file to the memory card. The following is specific to the PSxMemCard Gen2.
The SD card contains a directory called MemoryCards with the following contents:
MemoryCards/
├── COH
│ ⋮
│
├── PS1
│ ├── BOOT
│ │ ├── BootCard-1.mcd
│ │ └── BootCard.ini
│ └── Card1
│ ├── Card1-1.mcd
│ └── Card1-2.mcd
└── PS2
⋮
We’ll add our payload as a boot card under MemoryCards/PS1/BOOT.
Eeach virtual memory card (BOOT, Card1) can store 8 virtual card channels16;
these are essentially memory cards meant for the same game (or booting).
The COH and PS2 directories are irrelevant for us.
Copy the payload over to the SD card as BootCard-2.mcd, or whatever the next subsequent free channel is.
In this example it is 2.
cp payload.mcd /media/me/sdcard/MemoryCards/PS1/BOOT/BootCard-2.mcd
You can also edit the BootCard.ini file to add a display name for the payload.
We used the channel 2 so the contents of the file should be:
[ChannelName]
1=Unirom
2=HelloWorld
And with that, we’re ready to run.
Start the PlayStation console with the lid open and make sure the memory card is set to read the correct file. In the PSX shell, select the “MEMORY CARD” option in the menu; this will trigger the PSX exploit.
Below is a video of how that looks.
-
PlayStation europe launch dates (archived). ↩
-
Net yaroze on Wikipedia. ↩
-
PS1 copy protections description on consolemods.org. ↩
-
There’s the PicoMemcard. ↩
-
Lameguy’s PlayStation Programming examples (archived) ↩
-
Nugget and PsyQ examples on GitHub. ↩
-
Bare-metal C PlayStation programming examples on GitHub. ↩
-
PlayStation assmebly examples on GitHub. ↩
-
PSXdev example (archived). ↩
-
Main RAM starts at 0x80000000 (first 64K reserved for BIOS) ↩
-
FreePSXBoot exploit on GitHub. ↩
-
It seems various tools and emulators use the extensions
mcd,mcrandsrmexchangeably. These seem to be the PS1 raw memory card image format. ↩