ZSO Format
ZSO is a compressed variant of the standard ISO disc image. OPL decompresses each sector on the fly using LZ4, so the PS2 sees a normal disc โ but your storage device holds a smaller file. Support was added in OPL 1.2.0 and works across all storage backends that use OPL's built-in core.
.zso file, OPL shows an on-screen warning. For most backends (USB, MX4SIO, iLink, MMCE,
SMB, and the internal HDD) it then falls back to its own built-in core and the game still launches โ
just via the OPL core instead of Neutrino. Exception: for games sourced from UDPBD or UDPFS
the launch is aborted entirely rather than falling back, because those network transports have no
built-in OPL core โ Neutrino is their only launch path. To use Neutrino for a game on any backend you
must first decompress the image back to .iso (see below).
Requirements
ZSO conversion is done on your PC before copying files to the PS2's storage device. You need:
- Python 3 โ any recent 3.x release.
- lz4 Python library โ install once with
pip install lz4. - ziso.py โ included in the
pc/folder of the OPL source repository.
Compressing an ISO to ZSO
Pass -c 2 to compress. The 2 selects high-compression mode (LZ4 HC), which
gives the best ratio. Values 1โ12 all compress; values above 1 use LZ4 high-compression. Compression
level 1 uses standard LZ4 (faster, slightly larger output).
python ziso.py -c 2 "input.iso" "output.zso"
The script prints a running percentage and a final summary showing the compressed size and compression ratio. Blocks that do not compress well (within 5% of the original size) are stored uncompressed inside the ZSO file automatically โ no manual tuning needed.
Optional flags
| Flag | Default | Effect |
|---|---|---|
-c <1โ12> | required | Compression level. 1 = standard LZ4, 2โ12 = LZ4 high-compression. |
-c 0 | โ | Decompress a ZSO back to ISO. |
-b <size> | 2048 | Block size in bytes. Must be a multiple of 2048. Default matches a CD/DVD sector. |
-m | off | Enable multiprocessing โ uses all CPU cores; significantly faster on large ISOs. |
-t <percent> | 95 | Compression threshold. If a compressed block is larger than this percentage of the original, it is stored uncompressed. |
-a <align> | 0 | Padding alignment. 0 = smallest output (slower seek). 6 = faster random access (slightly larger file). |
-h | โ | Print usage help. |
Decompressing a ZSO back to ISO
Pass -c 0 to decompress. This is the only way to use the image with Neutrino or any
other tool that does not understand the ZSO container.
python ziso.py -c 0 "input.zso" "output.iso"
Using ZSO files on your PS2
USB, MMCE, MX4SIO, iLink, and SMB
Place the .zso file in the same folder as your ISOs โ OPL scans for both
.iso and .zso extensions in the same directory sweep. No extra
configuration is required; OPL detects the ZSO magic header at launch time and initialises the
LZ4 decompressor automatically.
SLES_123.45.GameTitle.zso (OPL legacy GameID format) or a free-form name ending in
.zso are both recognised. The extension comparison is case-insensitive, so
.ZSO and .zso are treated identically by the file scanner.
Internal HDD (APA)
Use the latest version of HDL-Dump to install a ZSO image to the internal HDD in the same way you would install a plain ISO. The APA partition will contain the compressed data; OPL reads the ZSO magic from the partition header and initialises the decompressor before any sector reads.
Internal HDD (exFAT / BDM)
Copy the .zso file to the mass storage area of the exFAT drive exactly as you would
a plain ISO. OPL's BDM path scans for .iso and .zso together.
Neutrino and ZSO
The Neutrino external core does not implement ZSO decompression. When a game is set to use
Neutrino in per-game settings and the image is a .zso file, OPL detects this before
launch and:
- Displays a brief on-screen warning.
- For USB, MX4SIO, iLink, MMCE, SMB, and the internal HDD: overrides the core selection back to the built-in OPL core for that launch.
- Launches the game normally using OPL โ except for UDPBD/UDPFS games, where the launch is cancelled instead, since those transports have no built-in OPL core to fall back to.
The per-game Neutrino setting is not permanently changed โ it will still show as enabled next
time you open per-game settings. To use Neutrino for the game permanently, decompress the image
to ISO first (see above), then copy the .iso in place of
the .zso.
The same fallback applies on the internal HDD: a ZSO partition that has Neutrino selected will fall back to OPL with a warning.
Quick reference
| Task | Command |
|---|---|
| Install dependency | pip install lz4 |
| Compress ISO โ ZSO | python ziso.py -c 2 game.iso game.zso |
| Compress, multicore | python ziso.py -c 2 -m game.iso game.zso |
| Decompress ZSO โ ISO | python ziso.py -c 0 game.zso game.iso |
| HDD install | Use HDL-Dump (same as ISO) |
How does the on-the-fly decompression work?
A ZSO file starts with a 24-byte header (magic ZISO, total uncompressed size,
block size, version, and alignment). Following the header is an index table โ one 32-bit entry
per 2048-byte sector โ that stores the file offset of each compressed block. The MSB of each
index entry is a "plain" flag: when set, that block is stored uncompressed (it did not meet the
compression threshold).
OPL's IOP-side ZSO module reads the header and index table at mount time. On every sector read request from cdvdman, it looks up the block offset in the index, reads the compressed bytes, and decompresses them with LZ4 into a per-sector buffer before handing the data to the EE. Because LZ4 decompression is fast, the impact on load times is typically small โ and the smaller file size can reduce seek times on slower devices, partially offsetting the decompression cost.
For images larger than 2 GB, the script automatically sets the alignment field so that block offsets shifted by the alignment value still fit in the 31-bit non-flag portion of each index entry.