Build, check, and repair PS4/PS5 PFS game images (.ffpfs, .ffpfsc, .exfat) from the command line
or a desktop app.
MkPFS.C# is a .NET 10 port of the Python MkPFS by PSBrew. It ships as a native executable (no Python needed), writes the same images as the original, and adds an offline PFSC block repair ported from PS5 Game Compressor.
- Pack: a game folder, an exFAT image, or any single file into a compressed
.ffpfsc(PFSC, zlib), or a folder into a plain PFS image (--raw, with signed, encrypted, 64-bit inode, and PS4 options). - exFAT: build exFAT images from game folders (deterministic, 64 KiB clusters by default).
- Batch: pack every game folder and image in a folder in one run.
- Check:
verify,inspect,tree, andunpackfor PFS, PFSC, and exFAT images, including encrypted ones (--ekpfs-key). - Repair: find and fix compressed blocks the PS5 may decode wrongly (images made with ISA-L).
- APR Emu:
ampr_emu.indexis created or refreshed automatically for games that use it. - GUI:
mkpfs-guiwith a page per command, cover and metadata preview, batch queue, and a PFSC block map; English, Português (BR), and Español.
Get the archive for your system from the releases page and unpack it anywhere:
| System | Command line | Desktop app |
|---|---|---|
| Windows x64 | mkpfs-<version>-win-x64.zip |
mkpfs-gui-<version>-win-x64.zip |
| Linux x64 | mkpfs-<version>-linux-x64.tar.gz |
mkpfs-gui-<version>-linux-x64.tar.gz |
| macOS Apple silicon | mkpfs-<version>-osx-arm64.tar.gz |
mkpfs-gui-<version>-osx-arm64.tar.gz |
- Keep each program in its folder with the libraries next to it (
mkpfs_zlib, plus Skia and HarfBuzz for the desktop app). - macOS: the desktop app is
MkPFS.C#.app. It is not notarized, so open it the first time with right-click > Open. - Linux: the desktop app needs X11 and fontconfig, which desktop distributions include.
SHA256SUMS.txton the release page lists the archive checksums.- Check the command line with
mkpfs selftest.
All paths may be absolute or relative to the current directory. Replace values in angle brackets
with your own paths; square brackets indicate an optional argument. Run mkpfs <command> --help
for the parser's built-in help.
| Command | Arguments | Default result | Purpose |
|---|---|---|---|
pack folder <source_dir> <image_file> |
game/homebrew folder, output path | exFAT wrapped in a compressed .ffpfsc |
Package a game folder. |
pack file <source_file> <image_file> |
input file, output path | compressed .ffpfsc |
Package one file in a PFS container. |
pack exfat <source_dir> [output] |
game/homebrew folder, optional output path | <titleId>.exfat beside the source |
Build an uncompressed exFAT image. |
batch <source_dir> <output_dir> |
directory of folders/images, destination directory | one .ffpfsc per discovered item |
Package many inputs; existing outputs are skipped. |
verify <image_file> |
image path | — | Validate an image, optionally against its source. |
inspect <image_file> |
image path | text report | Show image metadata and integrity information. |
tree <image_file> |
folder or image path | outer tree | List files and directories. |
unpack <image_file> <output_dir> |
image path, destination directory | — | Extract an image. |
repair <image_file> |
single-file .ffpfsc path |
repairs risky blocks | Repair PFSC blocks that a PS5 may decode incorrectly. |
Pack a game folder into a .ffpfsc (wrapped in exFAT and compressed in one pass):
mkpfs pack folder PPSA12345-app PPSA12345.ffpfscCompress an existing exFAT image:
mkpfs pack file PPSA12345.exfat PPSA12345.ffpfscPack a folder directly as PFS (--signed, --encrypted, --inode-bits 64, and --version PS4 apply
here):
mkpfs pack folder PPSA12345-app PPSA12345.ffpfs --rawBuild an exFAT image from a game folder:
mkpfs pack exfat PPSA12345-app PPSA12345.exfatPack every game folder and image file in a folder (existing outputs are skipped):
mkpfs batch ./games ./outputVerify an image against its source:
mkpfs verify PPSA12345.ffpfsc --source-file PPSA12345.exfatShow image details (--format json for scripts):
mkpfs inspect PPSA12345.ffpfscList the files, including inside a wrapped exFAT:
mkpfs tree PPSA12345.ffpfsc --deepExtract the game files:
mkpfs unpack PPSA12345.ffpfsc out --deepRepair blocks the PS5 may decode wrongly in a single-file .ffpfsc:
mkpfs repair PPSA12345.ffpfscBoth commands require a source path and an output image path. By default, the output extension is
changed to .ffpfsc when necessary. The default build is PS5, 32-bit inodes, case-insensitive,
zlib level 7, 64 KiB blocks, and compression enabled. Direct-PFS folder builds and single-file
builds also run a quick structure verification by default. The default exFAT-wrapped folder flow
runs a post-pack check only when --verify is supplied.
For the default exFAT-wrapped pack folder flow, compression remains enabled and the PFS block
size remains 64 KiB. Use --raw to make options that control direct PFS layout or compression
(--no-compress, --block-size, --inode-bits, --max-compressed-ratio,
--min-compress-size, and --skip-executable-compression) take effect.
| Option | Default | Meaning |
|---|---|---|
--adjust-output-file-extension |
on | Change the requested output extension to match the selected pack mode. |
--no-adjust-output-file-extension |
off | Keep the output filename exactly as supplied. Cannot be combined with --adjust-output-file-extension. |
--compress / --no-compress |
compression on | Enable or disable PFSC block compression. The two flags are mutually exclusive. |
--threshold-gain <0-100> |
0 |
Keep a compressed block only when it saves at least this percentage. |
--block-size <bytes|auto|auto-fit> |
auto (65536) |
PFS block size; it must be a power of two from 4096 through 2097152. auto-fit is supported by folder packing and the spool path to reduce file-data padding. |
--temp-folder <dir> |
system temporary directory | Where staged pack artifacts are written. |
--version <PS4|PS5> |
PS5 |
PFS profile version. |
--inode-bits <32|64> |
32 |
PFS inode-width mode. |
--case-sensitive / --case-insensitive |
case-insensitive | Select the PFS name-comparison mode. The two flags are mutually exclusive. |
--cpu-count <n> |
0 (automatic) |
PFSC compression workers. Automatic mode uses up to 16 workers and leaves one logical CPU free; a nonzero value is clamped to at least one. |
--compression-level <0-9> |
7 |
zlib compression level. |
--compression-backend <auto|zlib-ng|zlib|isal> |
auto |
Compatibility option. This port always uses zlib 1.3.1; unsupported values produce a warning. |
--max-compressed-ratio <0-100> |
100 |
Do not use PFSC when its stored size exceeds this percentage of the raw file size. |
--min-compress-size <bytes> |
resolved block size | Store smaller files raw without attempting PFSC compression. |
--skip-executable-compression |
off | Do not compress important executable files. |
--signed |
off | Build a signed PFS using a zero EKPFS/seed. It is not supported by the default exFAT-wrapped folder mode. |
--encrypted |
off | Encrypt filesystem blocks with AES-XTS. |
--ekpfs-key <64-hex> |
all-zero key | EKPFS key for an encrypted image; it requires --encrypted. |
--verbose |
off | Print per-file decisions. |
--dry-run |
off | Scan and report the planned layout without writing an image. |
--verify |
off | Run full post-pack verification instead of the default structure-only check. |
--verify-structure / --no-verify-structure |
structure check on | Explicitly enable or disable the default quick post-pack check. The two flags are mutually exclusive. |
--skip-verification |
off | Skip all post-pack verification. It cannot be combined with --verify. |
pack folder also accepts the following options:
| Option | Default | Meaning |
|---|---|---|
--raw |
off | Package the source directly as a PFS .ffpfs, rather than making the default exFAT-wrapped .ffpfsc. Use this mode for --signed, --inode-bits 64, and other direct-PFS settings. |
--require-game-files |
off | Refuse to pack unless sce_sys/param.json and eboot.bin are present. |
--no-ampr-index |
off | Do not create ampr_emu.index when fakelib/libSceAmpr.sprx is present. |
--ampr-skip-regen-if-exists |
off | When AMPR generation applies, retain a valid existing index. |
--ampr-force-regen |
off | Regenerate an existing AMPR index. |
pack file also accepts:
| Option | Default | Meaning |
|---|---|---|
--use-spool |
off | Force the legacy staged/spool builder instead of direct-to-image streaming. |
--rename-inner-image / --no-rename-inner-image |
rename on | Normalize the filename stored inside the image (the first flag explicitly selects the default), or preserve the source filename. |
mkpfs pack exfat <source_dir> [output] creates an uncompressed exFAT image. If output is omitted,
the program derives <titleId>.exfat beside the source directory; an output directory is also
accepted and receives that derived filename.
| Option | Default | Meaning |
|---|---|---|
--cluster-size <bytes|auto> |
auto (65536) |
exFAT cluster size. The automatic 64 KiB value is optimized for SMP/LVD. |
--overwrite |
off | Replace an existing output image. |
--verbose |
off | Print detailed packing output. |
--no-progress |
off | Hide the progress bar written to standard error. |
mkpfs batch <source_dir> <output_dir> discovers packable folders and image files in source_dir
and writes .ffpfsc images into output_dir. It skips existing outputs by default. Its compression,
PFS-profile, naming, and encryption options have the same meanings and defaults as the corresponding
pack options: --compress/--no-compress, --threshold-gain, --block-size (auto = 65536;
auto-fit is not accepted), --version, --inode-bits, --case-sensitive/--case-insensitive,
--cpu-count, --compression-level, --compression-backend, --max-compressed-ratio,
--min-compress-size, --skip-executable-compression, --encrypted, --ekpfs-key, and --verbose.
| Option | Default | Meaning |
|---|---|---|
--overwrite |
off | Replace images that already exist in the output directory. |
--dry-run |
off | Report the conversions without writing images. |
--verify |
off | Run full verification for each successful image. |
--compress / --no-compress |
compression on | Enable or disable compression; these flags are mutually exclusive. |
Encrypted read commands accept --ekpfs-key <64-hex> (default: all-zero key) and --new-crypt
(default: off) to select the alternate EKPFS derivation. verify, tree, and unpack also take
--format <auto|pfs|exfat>; the default auto detects the format from the input.
| Command | Options | Default | Meaning |
|---|---|---|---|
inspect <image_file> |
--format <text|json> |
text |
Select a human-readable or JSON metadata report. |
tree <image_file> |
--deep |
off | For a PFS that wraps one exFAT image, list the files inside that exFAT. |
unpack <image_file> <output_dir> |
--overwrite |
off | Replace an existing output path. |
--deep |
off | Extract files from an inner exFAT image instead of only the outer PFS contents. | |
--only <inner-path> |
none; repeatable | With --deep, extract only the named inner exFAT file or directory. |
|
--no-progress |
off | Hide extraction progress on standard error. | |
verify <image_file> |
--source-dir <dir> |
none | Compare hierarchy and payloads against a source folder. Cannot be combined with --source-file. |
--source-file <file> |
none | Compare a single-file image to the source file; not supported for exFAT input. | |
--expect-crc32 <hex> |
none | Require this cumulative payload CRC32. | |
--expect-manifest-sha256 <64-hex> |
none | Require this manifest SHA256 digest. | |
--require-game-files |
off | Warn when sce_sys/param.json, eboot.bin, or pfs-version.dat is missing. |
repair operates on an unsigned, unencrypted, single-file .ffpfsc. By default it scans for risky
compressed blocks, stores replacements raw, verifies their decoded content, and cleans unused bytes
in the outer PFS wrapper.
| Option | Default | Meaning |
|---|---|---|
--scan |
off | Report only; do not modify the image. Returns exit code 3 if blocks need repair. |
--bad-blocks <file> |
none | Repair the block numbers in a PS5 Game Compressor bad_blocks.tsv instead of the scan's risky-block selection. |
--recompress |
off | Re-encode repaired blocks with zlib level 7 instead of storing them raw. |
--mode <auto|in-place|copy> |
auto |
auto copy-replaces when free space is at least 1.2× the image, otherwise rewrites in place. in-place can leave an interrupted image corrupt; copy always uses a replacement copy. |
--report-dir <dir> |
none | Write summary.json and bad_blocks.tsv to this directory. |
--no-slack-cleanup |
off | Leave unused bytes in the outer PFS wrapper unchanged. |
--cpu-count <n> |
0 (all cores) |
Worker count for scanning and repair. |
--no-progress |
off | Hide progress output. |
Exit code 0 means success, 1 means an operation failed, 2 means invalid command-line usage,
and repair --scan uses 3 when it finds blocks that need repair.
mkpfs-gui runs the same commands from a window and shows their output and progress. From a
source checkout:
dotnet run --project src/MkPFS.Gui -c Release- Pick a game folder or image to see its cover, title, IDs, version, region, and APR Emu marker.
- The Batch page lists every item it will pack before you run it.
- Pack File, Pack Folder, and Batch have compression presets (Fast, Balanced, Max, Low RAM) and settings for the zlib level, CPU cores, block size, and when to keep blocks uncompressed.
- The Repair page scans an image and draws a block map (zlib, raw, risky); click a cell for its offset, stored size, and largest back-reference distance.
- Same inputs give the same images as Python MkPFS 1.0.0 run with its zlib backend. Set
SOURCE_DATE_EPOCHfor reproducible timestamps. - Compression always uses zlib 1.3.1 at level 7.
--compression-backendis accepted but ignored: ISA-L output uses back-references the PS5 decodes wrongly. repairis new.- Bugs found in the Python version while porting are listed in tools/oracle/README.md; some are fixed here.
- Switching from Python MkPFS: see MIGRATION.md.
Requirements:
- .NET SDK 10.0.401 or a newer 10.0.4xx (see
global.json). - Windows: Visual Studio 2026 with "Desktop development with C++" (native zlib and Native AOT).
- Linux and macOS: CMake and a C compiler; Linux Native AOT also needs
clangandzlib1g-dev.
Build and test:
dotnet build MkPFS.slnxdotnet test --solution MkPFS.slnxThe first build compiles the bundled zlib in native/. Parity tests skip when the oracle corpus
(tests/fixtures/generated) is missing.
Publish native executables:
dotnet publish src/MkPFS.Cli -r win-x64 -c Releasedotnet publish src/MkPFS.Gui -r win-x64 -c ReleaseUse linux-x64 or osx-arm64 on those systems. On Windows, Native AOT linking needs the Visual
Studio Installer folder (C:\Program Files (x86)\Microsoft Visual Studio\Installer) on PATH.
| Path | Content |
|---|---|
src/MkPFS.Core |
Formats and codecs: PFS, PFSC, exFAT, AMPR, crypto, readers, validators |
src/MkPFS.Build |
Image builders, compression planner, batch |
src/MkPFS.Repair |
PFSC repair (Game Compressor port) |
src/MkPFS.Cli |
mkpfs command line |
src/MkPFS.Gui |
mkpfs-gui desktop app (Avalonia) |
native/ |
zlib 1.3.1 and the mkpfs_zlib shim |
tests/MkPFS.Tests |
Unit tests |
tests/MkPFS.Parity |
Byte-for-byte tests against the Python oracle corpus |
tests/MkPFS.Gui.Tests |
GUI view model and headless UI tests |
tools/oracle |
Python oracle scripts (README) |
The parity tests compare against images and logs made by Python MkPFS. With Python MkPFS checked
out at ../MkPFS and uv installed:
uv run --project ../MkPFS python tools/oracle/build_goldens.py --checkPush a version tag to publish a release. The workflow reruns CI, then uploads the archives,
SHA256SUMS.txt, and release notes, and marks the release as latest. With a VT_API_KEY repository
secret (a VirusTotal API key), it also scans every archive and links the reports in the notes.
git tag v2.0.0git push origin v2.0.0- MkPFS by PSBrew: the Python original this port follows.
- PS5 Game Compressor by Juma Sayeh: the PFSC repair logic.
- Drakmor's APR Emu: the
ampr_emu.indexformat.
Third-party components:
- zlib 1.3.1 (zlib license)
- Avalonia, CommunityToolkit.Mvvm, System.CommandLine, and Spectre.Console (MIT)
- Material Design Icons (Apache-2.0)
GPL-3.0-only, same as MkPFS. See LICENSE.md.
This project is not affiliated with Sony Interactive Entertainment.