This is an unofficial fan-made port of id Software's Quake to the PlayStation 2 console. It derives from QuakeSpasm 0.97.0, the modern, faithful Quake engine based on FitzQuake and id's GPL source release: the engine, client, server and QuakeC virtual machine here are QuakeSpasm's. id's original release notes for the Quake source code are in src/README.md.
Like the Quake II port it builds on, it relies solely on the free PS2DEV SDK, with no official Sony SDK and no proprietary libraries. The goal is a fully functional and playable single-player Quake on the PS2, built entirely with freely available tools.
QuakeSpasm's C code under src/quake/ is kept as close to untouched as possible; every change
is tagged // [PS2_QUAKE]: <why>. Everything specific to the console lives in src/ps2/ and is
written in modern C++ (C++20, no exceptions, no RTTI, warnings-as-errors). That backend comes from the
Quake II port: its renderer, input, audio, memory and debugging code carry over, ported to
QuakeSpasm's interfaces.
Playable from start to finish, on a real PS2 and in PCSX2. The whole single-player game runs, the shareware episode or all four episodes of the registered game, with everything working:
- Loading the game data from a USB drive or the internal HDD on a console, and from PCSX2's
host filesystem (
host:) in the emulator, from id's paks or from compressed ones that halve what a level load reads. What is read while a level runs (demos, a sound or model asked for mid-level, the music) goes through a background loader, so a frame never waits on the drive. - Saving games and settings to the memory card, or to the USB drive or hard disk the game runs from.
- The DualShock 2, with both analog sticks and rumble. A USB keyboard works too, for the in-game console.
- Sound effects and the CD soundtrack, the music streamed from ripped CD tracks.
- Everything on screen: textured, lightmapped worlds with their fullbright texels, warping water and scrolling skies, monsters, items and the view weapon, sprites, particles, and the in-game console, menus and status bar.
- NTSC and PAL, at 60 or 50 Hz.
What is left is refinement, mostly performance: a few of the heaviest views still drop below 60 fps. Not supported: network play (single player only), GS fog (no id map uses it), and skyboxes or other external textures.
-
The ps2dev toolchain and PS2SDK. Build/install from ps2dev (
ps2toolchain+ps2sdk). The build expects these onPATH:Tool Comes from Typical location mips64r5900el-ps2-elf-gcc/-g++ps2toolchain $PS2DEV/ee/binopenvclopenvcl $PS2DEV/bindvp-asps2toolchain (dvp) $PS2DEV/dvp/binbin2cps2sdk $PS2SDK/binA
~/.zshenv(or equivalent) along these lines is what the Makefile and the VSCode tasks expect:export PS2DEV=$HOME/ps2dev export PS2SDK=$PS2DEV/ps2sdk export PATH=$PS2DEV/bin:$PS2DEV/ee/bin:$PS2DEV/dvp/bin:$PS2SDK/bin:$PATH
makealso builds the host tools with the machine's own C++20 compiler, through CMake 3.24 or newer, socmakehas to be onPATHas well. -
PCSX2, to run it on the emulator, plus a PS2 BIOS image for it. The Makefile's
runtarget defaults to/Applications/PCSX2.app/Contents/MacOS/PCSX2; overridePCSX2=elsewhere. -
The Quake game data. It is not included here. Place it in an
id1/directory at the root of the repository (it is.gitignored): either the sharewarepak0.pak(episode 1), or the registered game'spak0.pakandpak1.pak.quake-ps2/ id1/ pak0.pak pak1.pak (registered game only) pak0.pkz (optional, with pak1.pkz: compressed packs, see below) music/ (optional: the soundtrack, see below)The shareware data is freely available: the Quake Official Archive has id's shareware releases, and
pak0.pakis in the v1.06 one. This port has been tested with that v1.06 shareware data and with the full game's data from a CD rip.The soundtrack is optional. Rip the Quake CD's audio tracks 2-11 to 16-bit WAVs named
id1/music/trackNN.wav(NN = the CD track number), at 44.1 or 22.05 kHz, then runmake musicto encode them into thetrackNN.adpfiles the game streams (the PS2's native ADPCM, about 1.5 MB a minute). Only the.adpfiles need to go onto the console's drive. A track with no.adpplays from its.wav, at a higher cost.Compressed packs are optional as well, and worth making for a console.
make pkzconverts eachid1/pakN.pakinto apakN.pkzbeside it, a zip of the same files, most of them deflated: pak0 from 17.8 MB to 8.2 MB, pak1 from 32.7 MB to 14.9 MB. The game loads apakN.pkzin place of thepakN.pakwith its number, so a console's drive needs only the.pkzfiles, and a level load reads half the bytes off it (Compressed packs). In PCSX2, where reading costs next to nothing and inflating doesn't, the paks load faster (a release build went from e1m1 to e1m2 in 1.23 s from them, 1.50 s from the.pkz): keep the.pkzfiles out of the repository'sid1/for emulator work, unless they are what you are testing.
Three dependencies are git submodules under src/tools/:
vclpp (the VU microprogram preprocessor),
vu-checker (checks on the VU toolchain's output) and
miniz (deflate, for the save games and the compressed packs).
Clone with --recursive, or run git submodule update --init --recursive in an existing clone.
make # debug build -> build/debug/quake.elf (+ host tools)
make release # optimized -> build/release/quake.elf
make run # build, then launch it in PCSX2make (debug) |
make release |
|
|---|---|---|
| Optimization | -O2 |
-O3 |
| Debug info | -gdwarf-2 -gz |
none |
| Assertions | on | compiled out |
| Tests and other debug-only code | in | compiled out |
Both configs strip the ELF that runs and keep the symbols next to it in quake_unstripped.elf.
make compiledb regenerates compile_commands.json for clangd, and make clean removes all
build output.
CI runs make and make release on Linux for every push and pull
request that changes more than the documentation, inside the ps2dev project's
ps2sdk container image, pinned to
one build of it. It checks that both configs compile and link, and keeps no ELF: releases are
packaged from local builds, as described below.
The host tools in src/tools/host/ (unpak, imgdump, musenc, pakzip, into
build/tools/) are a CMake project, CMakeLists.txt. make
configures it once into build/tools/cmake/, then runs its build every time, which does nothing
while the tools are up to date. They are built optimized and held to the game's strict warning set
as errors (/W4 under MSVC). They build on Windows too, with MSVC, from the repository root:
cmake -S src/tools/host -B build/tools
cmake --build build/tools --config ReleaseThat leaves the .exe files in build/tools/Release/, with the C/C++ runtime linked statically, so
they run without the Visual C++ Redistributable. A second CI workflow
builds the tools alone on Linux (GCC), macOS (Apple clang) and Windows (MSVC) whenever they change,
and keeps the Windows executables as an artifact, host-tools-windows-x64, on each run's page under
the repository's Actions tab, for 90 days. Downloading it needs a GitHub sign-in.
make_github_release.sh [-f] [-u] [version] cuts a
release: a clean build of both configs, each config's quake.elf packaged with LICENSE,
RELEASE_NOTES.txt (its @VERSION@ and @COMMIT@ filled in, so it points
to the exact source) and an id1/ holding the default config.cfg and an empty music/ folder,
zipped into github_rel_<version>/. -u adds quake_unstripped.elf. The player adds the paks.
make run symlinks build/<config>/id1 to the repo's id1/ and launches
PCSX2 -batch -elf build/<config>/quake.elf. PCSX2 needs a few settings that are off by default.
Edit ~/Library/Application Support/PCSX2/inis/PCSX2.ini (macOS) while PCSX2 is closed, since it
rewrites the file on exit:
[EmuCore]
HostFs = true ; host: maps to the ELF's directory; without it there is no game data
[Logging]
EnableIOPConsole = true ; the game's stdout goes through the IOP
EnableFileLogging = true ; ...and into logs/emulog.txt
[USB1]
Type = hidkbd ; optional: a USB keyboard that passes host keystrokes throughThe engine's console output is prefixed with [Q1] in the log:
tail -f ~/Library/Application\ Support/PCSX2/logs/emulog.txt | grep '\[Q1\]'- Keep the ELF's full path under 152 characters. PCSX2 passes it to the BIOS, and a longer one crashes the BIOS's loader before the game starts. A release unzipped deep in a folder tree can get there.
make run RUN_ARGS="-heapsize 20480"puts arguments on the game's command line. Like id's Quake, QuakeSpasm runs+commandsfrom there only for the registered game; anid1/autoexec.cfgruns at boot either way, and is the way to script a session.
Copy quake.elf (from a release, or build/release/) and id1/ side by side into a folder on a
FAT32/exFAT USB drive or on a PFS partition of the internal HDD, and launch the ELF from a loader
such as wLaunchELF:
mass:/QUAKE/
quake.elf
id1/
pak0.pkz (or pak0.pak)
pak1.pkz (or pak1.pak; registered game only)
music/ (optional: the trackNN.adp files)
- Finding the game data. The game looks in the folder it was launched from first, then at the drive roots: over USB only when launched from USB, and on the HDD and then USB otherwise. Every driver it needs is built into the ELF. If it finds nothing, the error screen lists where it looked.
- The IOP is reset at every console boot, and every driver loaded afresh, whatever the
launcher left running: the reset costs ~175 ms, and a USB drive then takes ~0.6 s to come back
up. From 2026-10-12 to 2026-11-01 the boot reused the launcher's drivers instead, which saved that
time but, on the Quake II port's console, froze the game after its first level load whenever it
went through an old launcher's USB driver (wLaunchELF 4.43a's usbhdfsd);
-DPS2_QUAKE_ALWAYS_RESET_IOP=0brings that boot back, for comparisons. TheIOP boot:lines in the console and inquake.logsay what the launcher left running, what the boot loaded, and when each step finished. - Load times. The HDD loads a level about twice as fast as the best USB setup. The PS2's USB is 1.1, so a
faster stick doesn't help, but fewer bytes and larger clusters do. Copy the
.pkzpacks rather than the.pakones: a load reads half the bytes (the Quake II port's loads over USB took 25-40% less time from its.pkz, and about as long from the HDD). And format the stick FAT32 with 32 KB clusters or larger (macOS picks 8 KB for a 16 GB stick;sudo newfs_msdos -F 32 -c 64 -v NAME /dev/rdiskNsMsets 32 KB, erasing the volume). - Saves and settings go to the memory card in slot 1, never to the USB drive or the HDD.
- NTSC or PAL. The game follows the console's own standard. Options → Video Options → Video mode forces one, after a restart, so a PAL console can run at 60 Hz on a TV that takes it.
- A log on the drive. A console has no stdout to read:
ps2_logfile 1(inid1/autoexec.cfg, or at the console) writesquake.lognext toid1/. - The debug ELF runs the same way, with assertions on and the debug overlays showing.
Trying the console's storage path in PCSX2. With HostFs = false the game boots as it would
on a console. make_usb_image.py (macOS) builds a FAT32 disk
image, build/quake-usb.img, holding both ELFs and a copy of id1/, to attach as a USB drive:
[USB2]
Type = Msd
Msd_subtype = 0
Msd_ImagePathMsd = /absolute/path/to/quake-usb.img[DEV9/Hdd] HddEnable and HddFile attach an HDD image the same way. PCSX2 doesn't model
either drive's speed, so its load times say nothing about a console's.
| Command | What it does |
|---|---|
ps2_memstats |
Prints memory use by subsystem, and the heap's totals. |
ps2_vramstats |
Prints the GS texture heap block by block, with its totals and its peak this run. |
hunk_print |
QuakeSpasm's own: prints the hunk by block, with what remains. |
ps2_dump_iop_mods |
Prints the boot report, the IOP modules loaded and the IOP memory free. |
screenshot |
Writes the last frame to id1/spasmNNNN.tga. |
ps2_saveinfo |
Says where saves are going, and lists the memory card's saves. |
menu_debug |
Opens the debug page. |
The debug page (Options → Debug Options) puts the debugging aids on the gamepad, for a console
with no keyboard: the Quake console itself (no pad button opens it; Start closes it in a game), the
log file, the IOP report, the save list, a dump of the console's text, a screenshot, god mode and
all the weapons (impulse 9) in a single player game, and in the builds that have them, the tests
below. While the console is down, L2 and R2 scroll it.
The log file. ps2_logfile 1 copies everything the console prints to quake.log, with a
timestamp per line, starting from the boot report. It also catches the reports printed on the way
to a fatal error (crash reports, stack traces, the render pipeline dump), so a crash on a console
still leaves a record; those are written even with the cvar off, behind the 16 KB of output the
log keeps while it is off, so a crash leaves a quake.log either way. Each line is a write to the drive, slow over USB, so leave it off when
timing anything.
Render pipeline hangs. In debug builds a wait on the GS that runs past a second dumps the state of the whole graphics pipeline (DMA, VIF1, VU1, GIF, GS, the last DMA packets and VU1's memory) to stdout and the log, then stops with an error screen.
Tests and traces, armed by cvars and build flags (see CVARS.md):
ps2_testmaps 1(debug builds) loads every level in turn and reports what each costs in RAM and in the GS texture heap.ps2_testaio 1(debug builds) reads a list of files through the background loader and through the engine's own loader and compares them, as the drive is and throttled to a USB stick's rate.ps2_perftest 1(profile builds) plays the three demos with a per-frame timing log, andps2_testmaps 2turns a full circle at a few points on every level. The scripts in src/tools/scripts/frame_log/ summarize and compare the captures.PS2_QUAKE_LOAD_TRACE(load_trace.h) traces each step of a level load into the log, and-DPS2_QUAKE_IO_LOG=1(io_log.h) records what each load read and how long it took, and from a.pkzwhat came off the drive and how long it spent inflating: the way to compare a.pakload with a.pkzone on a console.
QuakeSpasm draws through the public functions of its OpenGL renderer; the backend implements those directly on the PS2's hardware, with no GL underneath.
- The frame is one DMA chain, built by the EE while the GS draws the previous one. 3D goes
through VU1 microprograms; 2D goes straight to the GS. The wait for vsync that paces the frames
sleeps on the vblank interrupt (a semaphore signalled from an
INTC_VBLANK_Shandler), so the 8-11 ms a frame leaves before its field ends go to the other threads, the background loader above all;ps2_vsync_spin 1brings the old status-register spin back. - Textures stay 8-bit in VRAM, as in the game's data, and read Quake's palette through a CLUT.
- The world (view.cpp) keeps QuakeSpasm's visibility code and draws
in its passes: textures, then the lightmaps, then the fullbright texels. Water, slime and lava
warp on VU1, and the sky (sky.cpp) scrolls its two layers.
Dynamic lights (muzzle flashes, rockets, explosions) rebuild the lightmaps they reach, as in
QuakeSpasm, or with
ps2_dynamic_lights vertexVU1 adds the four nearest the camera at each vertex of the lightmap pass, which costs the EE nothing but washes the walls a flat grey rather than brightening their texture.gl_flashblenddraws QuakeSpasm's glows in their place. - Models (alias.cpp) are interpolated between poses on VU1 and lit
as QuakeSpasm lights them, with their shadows (
r_shadows) and the players' colours. QuakeSpasm keeps them in its cache, where room for something else can evict one, and reads it back the moment it is drawn; here that read goes to the background loader (model_load.h) and the entity sits out the few frames until the model is back, instead of the frame waiting on the drive. Sprites face the camera in all of QuakeSpasm's orientations, and particles are QuakeSpasm's round dots, or withr_particles 2its squares. - 2D (draw.cpp): the console, menus and status bar. Quake laid
them out for 320x200, and at QuakeSpasm's size of 1 they come out small on the 640x448 screen,
so here they are drawn at 1.5 times their size by default:
scr_menuscale,scr_conscaleandscr_sbarscaleare 1.5 (QuakeSpasm's 1), which Options' Scale slider sets together. The status bar is half see-through (scr_sbaralpha0.5, QuakeSpasm's 0.75). The main menu leaves out Multiplayer, which the PS2 doesn't play, and has an About page after Help/Ordering (about_menu.cpp): what the port is, its version, its author and its GitHub page. No game data has an "About" in the menu's lettering, so the ELF carries one, made from the menu pictures' own letters by make_menu_about.py. - Video Options (in the Options menu): NTSC or PAL, a 16- or 32-bit framebuffer (16-bit leaves more VRAM for textures), the picture's place on the TV (Screen X and Y, for a set that cuts an edge off: a white line marks the picture's edge while either row is under the cursor), the texture filtering, dithering, the dynamic lights (lightmaps, vertex or glows), the models' shadows, round or square particles, the GS latency, and the FPS counter and debug overlays. The first two apply after a restart. The Options page itself has the save device: the memory card, or the drive the game booted from (see Save games).
- Brightness and Contrast (the Options page's sliders, QuakeSpasm's
gammaandcontrast): QuakeSpasm applies them to the finished frame, which the GS has no stage for, so here they go into the palette as software Quake's gamma did (gamma.cpp): into the CLUTs every texture samples through, and into the lightmaps' ramp, so a lit wall comes out as QuakeSpasm's shader would make it. The models' light takes the curve per model, their shading doesn't; the vertex dynamic lights, the glows and the view tint are left as they are. A 16-bit framebuffer bands more in darks a brightening lifts, whichps2_fb_dithersoftens. - The boot splash shows the QUAKE plaque from the menus, built into the ELF, from the first moment of the boot until the game draws its first frame. The loading screen shows the loading plaque with a status line naming the file being read.
input.cpp drives the DualShock 2 (pad.cpp, held in analog mode) and an optional USB keyboard (keyboard.cpp).
The pad works as QuakeSpasm's game controller: each button is a key the menus understand (Cross
is Enter, Circle is Back) and binds like any other (bind RTRIGGER +attack). The defaults:
| Button | Key | Default bind |
|---|---|---|
| Cross / Circle | ABUTTON / BBUTTON |
+jump / +movedown (swim down) |
| Square / Triangle | XBUTTON / YBUTTON |
|
| L1 / R1 | LSHOULDER / RSHOULDER |
impulse 12 / impulse 10 (previous / next weapon) |
| L2 / R2 | LTRIGGER / RTRIGGER |
+jump / +attack |
| L3 / R3 | LTHUMB / RTHUMB |
|
| D-pad | the arrow keys | +forward, +back, +left, +right |
| Start / Select | ESCAPE / TAB |
the menu / +showscores |
The left stick moves and strafes and the right one turns and looks, through QuakeSpasm's joy_*
cvars. The dead zones are wider than QuakeSpasm's, since a worn DualShock 2's stick can rest far
enough off centre to turn the camera on its own. Outside a game the left stick navigates the menus.
The menus speak the pad's language. Options' Controls page lists the binds by the pad's buttons
(Cross, Circle, Square, Triangle, L1 to R3, the D-pad's Up, Down, Left and Right, Select): Cross
changes one, Square clears it. A keyboard's binds stay in config.cfg, but the page leaves them out.
The Options rows QuakeSpasm gives the mouse work the look stick instead:
- Look Speed scales
joy_sensitivity_yawandjoy_sensitivity_pitchtogether, 0.5 to 2; at 1 they are QuakeSpasm's 240 and 130 degrees a second. - Invert Look is
joy_invert. - Stick Look is
+mlook, mouse look for the stick. Off, the look stick only turns, as Quake's mouse does without mouse look, and the view levels itself as in classic Quake. It is on by default, andconfig.cfgkeeps a-mlookwhen it is turned off. - Lookspring recentres the view as the player runs after looking up or down.
Lookstrafe is gone: it had the mouse strafe instead of turn, and a look stick that strafed would leave nothing to turn with. The yes/no questions ("start a new game?") take Cross for yes and Circle for no.
Rumble (rumble.cpp) gives short bursts for the player's shots, damage
taken (stronger the harder the hit), pickups and powerups. It is off in menus, demos and while
paused; the Options page's Rumble row (in_rumble) turns it off.
A USB keyboard is a development tool here, so it is off by default in release builds
(in_keyboard 1 turns it on). It types at the console, with the keys mapped by position as on a US
layout.
QuakeSpasm's own mixer runs unchanged, at 22050 Hz, and the backend (audio/)
streams what it mixes to the SPU2 through the audsrv IOP driver. A feeder thread keeps that stream
topped up, so the sound carries on cleanly through stalls such as a level load or a save. audsrv
is started with thpri=39, its RPC thread one priority above fileXio's: at its default it answered
at fileXio's priority, and the IOP has no time slicing, so on the HDD the feeder's calls waited out
any file read in progress, up to 65 ms of audsrv's 106 ms ring on the Quake II port's console,
which is the audible stutter at a pickup there. ps2sdk's audsrv has taken the argument since
d41ea62.
Sounds first asked for mid-level load in the background (sound_load.h):
S_LoadSound outside a level's precache (and with the loading plaque down) hands the WAV to the
background loader (see Compressed packs), and the pump that runs before each
frame resamples it into the cache once it lands, so the frame that first hears a menu click, the
talk beep or a sound the cache had evicted no longer reads the file (58 ms from the HDD, 147 ms from
a USB stick on the Quake II port's console). A sound asked for while still loading starts when it
lands, if that is within half a second, and is dropped otherwise. Level loads stay synchronous.
CD music. The PS2 can't play the soundtrack off the Quake CD, so it plays it from files instead:
id1/music/trackNN.adp, made from a CD rip by make music (see Prerequisites),
or the .wav files directly. It behaves as QuakeSpasm's external music does: "Background Music"
(QuakeSpasm's "External Music", bgm_extmusic) and "Music Volume" in the options menu, music_loop,
and the music, music_stop, music_pause and music_resume commands. The format is the Quake II port's, so its tracks play here too.
MusicStream keeps two read buffers in flight, 32 KB each for
ADPCM (~1.3 s), up to 64 KB each for a WAV, and decodes on demand; the buffers come from the heap
when a track starts and go back when it stops. The file reads go to the background loader (see
Compressed packs) at its stream priority, since a blocking read off a USB stick
could otherwise cost a frame; one it declines is read on the main thread when it is needed.
QuakeSpasm's file system is kept as it is: its search path, its pakN.pak archives and its
lookups (common.c). For each pakN.pak it now looks for a pakN.pkz first,
and loads that instead. A pkz is a standard zip (pkz_format.h), written
by pakzip (make pkz) with the pak's names in the pak's order, so any zip tool reads it
(unzip -t id1/pak0.pkz), and the game loads one made by another tool as well. Each file is
deflated, or stored if that saves less than 10% (a few sounds), since inflating costs the console
time: the maps shrink to 45% of their size, the models to 39-47%, the 2D art to a quarter, the
sounds to 71-87%. The format and the tool are the Quake II port's.
pkz_pack.cpp inflates on the EE, with the miniz already linked for the
save games. A file read whole, as every map, model, sound and texture is, inflates straight into
the memory it is loaded to. A stream of a deflated file inflates through a 32 KB window as it is
read, and can seek: pakzip leaves a restart point every 32 KB, for 0.1% of the pack's size. Both
packs' tables and the whole-file workspace take 58 KB (tag Pack), and an open stream of a
deflated file 72 KB more. Debug builds also check the CRC of every file they read whole. The demos
are stored, not deflated (pakzip --store-ext .dem, what make pkz passes), so they can stream
through the background loader below; a pkz made before 2026-11-01 has them deflated, and they then
read on the main thread as before.
Background loading (async_io.h, ps2::aio). Every file read sleeps
its caller until the IOP's file server answers, 41 ms for a 32 KB chunk on a USB stick, so a read
made mid-level by the main thread is a dropped frame. COM_LocateFile resolves a file to a
descriptor and a byte range without reading it (a pak's second descriptor, a pkz entry's
compressed range, a loose file's own stream), and a thread one priority below the main one reads
it in 32 KB chunks aligned to the file, or inflates a deflated entry straight into the caller's
buffer, re-picking the best (priority, age) request after every chunk. It runs only while the
main thread sleeps, which since the vsync wait sleeps on the vblank interrupt is most of every
frame. Destinations are 64-byte aligned SIF DMA targets; the loader never allocates, prints or
touches the engine, and the places that free or close behind a read drain it first. The design
and its console measurements are the Quake II port's (misc/docs/plans/background-loading.md
there); ps2_testaio 1 is its self-test, and ps2_aio 0 turns it off. Tag Aio.
What goes through it: demos, the sounds first asked for while a level runs (see Sound), and the alias models the cache evicted and a draw wants back (see Rendering). Level loads stay synchronous under the plaque, where a stall is expected, and so do the menu pictures, which load as a menu opens. An asset that is still loading is skipped where it would be drawn or played, never shown as a placeholder; a missing one behaves as before, since the existence check stays synchronous. QuakeSpasm has no other mid-level file read: its textures come out of the level and model files, so none is loaded from a file of its own.
Demos stream through it (readahead_stream.h). COM_FOpenFile
on a .dem whose source can be read by range (a pak, a stored pkz entry, a loose file) returns an
unbuffered fopencookie stream over a ring of two 32 KB slots, each a loader request from the
open, so cl_demo.c's fscanf, fread and fclose work unchanged and a frame's reads copy from
memory. Before, the stream refilled on the main thread: a deflated pkz entry inflated through its
window as it was read, and on the Quake II port's console each 64 KB refill of a demo froze a
frame for 80 ms from the HDD. ps2_readahead_kb 0 gives every demo the old stream.
Games are saved to the memory card in slot 1 by default (save/), in a Q1PS2 folder
the PS2's browser shows with a 3D quad damage icon. The Options page's Save Device row
(ps2_savedevice) can keep them on the drive the game booted from instead: the USB drive (usb),
the hard disk (hdd), or under PCSX2 the host filesystem (host), in id1/save/ there, as the
Quake II port keeps them in baseq2/save/. Every device holds the same files: each of the menus'
20 slots is one compressed file (an e1m1 save takes 11 KB), and a save is written in full before
the one it replaces is deleted, so an interrupted save never loses the previous one. Loading reads
the selected device only.
Whenever the game reads or writes saved data (a save, a load, the save menus listing their slots, the settings), it shows the notice PS2 games show (save_notice.h): "Data is being saved to the Memory Card in slot 1. Do not remove the Memory Card or reset/switch off the console." ("loaded from" for a read), naming the device in use, in Quake's own message box. On a console the game stands still for a second or more while the card is busy, so the notice goes up before the device is touched, the music fades out under it rather than stopping mid-note, and once the device is done the music fades back in from where it stopped.
When the save device can't be used (no memory card in the slot, say), the load and save menus say so over their slots, with how to switch to the other device, and a save or a load that fails says why in a message box, dismissed with Cross.
config.cfg, the binds and settings, lives with the saves. It is written on quit and when leaving
the options menu:
Game data on host: (PCSX2) |
Game data on the HDD or USB (console) | |
|---|---|---|
| Saving | id1/config.cfg always, plus the card's copy when saves go to the card |
the save device's: the card's, or id1/config.cfg on the drive |
| Loading | id1/config.cfg first, the card's if there is none |
the drive's id1/config.cfg if it says saves go to the drive; otherwise the card's first, then the drive's |
So a player who keeps everything on the USB drive or the hard disk never has the card touched at
boot. Switching the save device from the drive back to the card rewrites the drive's
id1/config.cfg once more, so that it no longer names the drive and the next boot reads the card.
The card's copy is read at boot under the notice, drawn over the QUAKE plaque since the renderer
hasn't started yet. An unchanged config isn't written again, so leaving the options menu without
changing anything touches no device.
The repository tracks id1/config.cfg as the default settings. The game writes it deterministically,
so a run that changes no setting writes it back byte for byte, and git status shows when one did.
src/
quake/ QuakeSpasm's C: engine, client, server, QuakeC VM
ps2/ the PS2 backend - all new C++ code
system/ main() entry point, Sys_* seam, IOP boot, dlmalloc heap
renderer/ GS front-end, VRAM heap, textures, models, VU1 path
vu1progs/ VU1 microprograms (.vcl)
audio/ SNDDMA_* seam, audsrv device, mix ring, CD music streaming
input/ IN_* seam, DualShock pad and rumble, USB keyboard
math/ vector/matrix math for the renderer
net/ the network driver table (loopback only)
save/ save games on the memory card or the boot drive
fs/ compressed game packs (pakN.pkz) for QuakeSpasm's file system
debug/ on-screen error printing, the log file, stack traces, EE exception handling
menu/ the main menu's About page
tests/ standalone bring-up scenes and tests
builtin/ images compiled into the ELF (the QUAKE plaque, the main menu's About item, the palette), from imgdump
tools/
host/ host-side command line tools (unpak, imgdump, musenc, pakzip)
scripts/ Python helpers (symbolize, compile_commands.json, frame logs, the USB image, the About item) and make_github_release.sh
vclpp/ VCL preprocessor for the VU microprograms (git submodule)
vu-checker/ openvcl/dvp-as output checks run by every VU build (git submodule)
miniz/ deflate codec for the save games, the compressed packs and pakzip (git submodule)
vscode_extensions/ VCL/VU assembly syntax highlighting for VSCode
- QuakeSpasm, which this port derives from: everything under src/quake/
is QuakeSpasm 0.97.0's code, by the QuakeSpasm developers, changed only where tagged
[PS2_QUAKE]. QuakeSpasm is itself based on John Fitzgibbons' FitzQuake. - id Software's Quake, released under the GPL in 1999 (source; the original release notes are in src/README.md).
- The PS2 backend under src/ps2/ comes from the Quake II PS2 port.
This project also includes or utilizes other Open Source libraries:
Quake was released by id Software under the GNU General Public License, and QuakeSpasm is distributed under the same terms. New code written for the PS2 port and any changes made to the original source code are also released under the GNU General Public License version 2. See the accompanying LICENSE file for details.
