| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
parent directory.. | ||||
PBPSharp is a managed, read-only C# library for reading PlayStation Portable PBP (EBOOT.PBP) containers and extracting the PlayStation disc images they carry. It handles both single-disc and multi-disc PBPs, parses the embedded PARAM.SFO metadata, decodes the PSAR ISO block index, and writes the extracted disc as a BIN file with a matching CUE sheet.
The library is the PBP extraction engine used by Batch Convert to CHD, where it serves as an intermediate step before converting PBP discs to CHD with chdman or CHDSharp.
| Requirement | Value |
|---|---|
| Target frameworks | net8.0, net9.0, net10.0 |
| Runtime | .NET 8, .NET 9 or .NET 10 |
| Platforms | Windows, Linux, macOS (pure managed code) |
| Dependencies | SharpZipLib 1.4.2 |
The package contains a separate assembly for each target framework, so the correct build is selected automatically by NuGet.
Read-only: PBPSharp can open, inspect and extract PBP files. It cannot create, modify or repack them.
dotnet add package PBPSharpor with the Package Manager console:
Install-Package PBPSharpusing PBPSharp;
using PBPSharp.Models;
var error = PbpFile.Open(@"C:\Games\EBOOT.PBP", out var pbp);
if (error != PbpError.None || pbp is null)
{
Console.Error.WriteLine($"Could not open the PBP: {error}");
return;
}
using (pbp)
{
Console.WriteLine($"{pbp.Title} ({pbp.DiscId}) - {pbp.Discs.Count} disc(s)");
var disc = pbp.Discs[0];
var result = disc.ExtractToBinCue(@"C:\Games\Game.bin");
Console.WriteLine(result == PbpError.None ? "Extracted." : $"Failed: {result}");
}PbpFile.Open parses the container, the PARAM.SFO metadata and every disc up front, so the overview properties are available without touching the disc payload.
using PBPSharp;
using PBPSharp.Models;
var error = PbpFile.Open(pbpPath, out var pbp);
if (error != PbpError.None || pbp is null)
{
Console.Error.WriteLine($"Could not open '{pbpPath}': {error}");
return;
}
using (pbp)
{
Console.WriteLine($"Title: {pbp.Title ?? "(unknown)"}");
Console.WriteLine($"Disc ID: {pbp.DiscId ?? "(unknown)"}");
Console.WriteLine($"Category: {pbp.Category ?? "(unknown)"}");
Console.WriteLine($"Multi-disc:{pbp.IsMultiDisc}");
foreach (var disc in pbp.Discs)
{
Console.WriteLine($" Disc {disc.Index}: {disc.DiscId}, " +
$"{disc.BlockCount} blocks, {disc.IsoSize:N0} bytes, " +
$"{disc.Toc.Count} tracks");
}
}A missing or corrupt PARAM.SFO does not fail the open. Metadata-dependent properties simply return null while the disc images remain extractable.
PbpDiscInfo.ExtractToBinCue writes the BIN file and, when no cue path is given, a CUE file next to it with the .cue extension.
using PBPSharp;
using PBPSharp.Models;
var error = PbpFile.Open(pbpPath, out var pbp);
if (error != PbpError.None || pbp is null)
{
Console.Error.WriteLine($"Could not open '{pbpPath}': {error}");
return;
}
using (pbp)
{
var binPath = Path.ChangeExtension(pbpPath, ".bin"); // cue becomes .cue
var result = pbp.Discs[0].ExtractToBinCue(binPath);
if (result != PbpError.None)
{
Console.Error.WriteLine($"Extraction failed: {result}");
return;
}
Console.WriteLine($"Extracted {pbp.Discs[0].IsoSize:N0} bytes to {binPath}");
}Multi-disc PBPs expose one PbpDiscInfo per disc in PbpFile.Discs. The Index property is 1-based and can be used to build the usual - Disc N file names.
using PBPSharp;
using PBPSharp.Models;
var outputDir = @"D:\PS1 Rips";
Directory.CreateDirectory(outputDir);
var error = PbpFile.Open(pbpPath, out var pbp);
if (error != PbpError.None || pbp is null)
{
Console.Error.WriteLine($"Could not open '{pbpPath}': {error}");
return;
}
using (pbp)
{
var baseName = Path.GetFileNameWithoutExtension(pbpPath);
foreach (var disc in pbp.Discs)
{
var suffix = pbp.IsMultiDisc ? $" - Disc {disc.Index}" : string.Empty;
var binPath = Path.Combine(outputDir, $"{baseName}{suffix}.bin");
var cuePath = Path.ChangeExtension(binPath, ".cue");
var result = disc.ExtractToBinCue(
binPath,
cuePath,
bytesWritten =>
Console.WriteLine($"Disc {disc.Index}: {bytesWritten:N0} bytes written")
);
Console.WriteLine(
result == PbpError.None
? $"Disc {disc.Index} -> {binPath}"
: $"Disc {disc.Index} failed: {result}"
);
}
}PbpDiscInfo.ExtractTo writes the raw ISO sectors to any writable stream. The optional callback reports the total bytes written so far, and the cancellation token is checked before every block.
using PBPSharp;
using PBPSharp.Models;
using var source = File.OpenRead(pbpPath);
var error = PbpFile.Open(source, ownsStream: false, out var pbp);
if (error != PbpError.None || pbp is null)
{
Console.Error.WriteLine($"Could not open '{pbpPath}': {error}");
return;
}
using (pbp)
using (var cts = new CancellationTokenSource())
using (var output = File.Create(@"C:\Games\Game.iso"))
{
var disc = pbp.Discs[0];
disc.ExtractTo(
output,
bytesWritten =>
{
var percent = disc.IsoSize == 0 ? 100 : (bytesWritten * 100.0) / disc.IsoSize;
Console.Write($"\r{percent,6:0.0}%");
},
cts.Token
);
Console.WriteLine();
}ExtractTo can throw OperationCanceledException when the token is cancelled and InvalidDataException when a compressed block cannot be decoded. Use ExtractToBinCue when you prefer an error code over exceptions, or trim the partial output file yourself after a failure.
Both overloads require a readable, seekable stream. The ownsStream flag decides whether disposing the PbpFile also disposes the stream.
using PBPSharp;
using PBPSharp.Models;
using var file = File.OpenRead(pbpPath);
var error = PbpFile.Open(file, ownsStream: false, out var pbp);
if (error != PbpError.None || pbp is null)
{
Console.Error.WriteLine($"Could not open the PBP: {error}");
return;
}
using (pbp)
{
Console.WriteLine(pbp.Title);
}
// The stream is still usable here because ownsStream was false.You can also open a PBP that is already in memory:
var bytes = await File.ReadAllBytesAsync(pbpPath);
using var memory = new MemoryStream(bytes);
var error = PbpFile.Open(memory, ownsStream: false, out var pbp);CueSheetWriter.GenerateCueSheet produces the CUE text without extracting anything, which is useful when the BIN already exists or when you need to inspect the track layout.
using PBPSharp;
using PBPSharp.Models;
var error = PbpFile.Open(pbpPath, out var pbp);
if (error != PbpError.None || pbp is null)
return;
using (pbp)
{
var disc = pbp.Discs[0];
foreach (var track in disc.Toc)
{
Console.WriteLine(
$"Track {track.TrackNo:00}: {track.TrackType}, " +
$"{track.Minutes:00}:{track.Seconds:00}:{track.Frames:00}"
);
}
var cue = CueSheetWriter.GenerateCueSheet("Game.bin", disc.Toc);
File.WriteAllText(@"C:\Games\Game.cue", cue);
}A generated CUE looks like this:
FILE "Game.bin" BINARY
TRACK 01 MODE2/2352
INDEX 01 00:02:00
TRACK 02 AUDIO
INDEX 00 04:11:72
INDEX 01 04:12:72For advanced scenarios you can read and decompress individual 16-sector blocks. Each block is up to 16 * PbpDiscInfo.IsoBlockSize bytes. The last block may be shorter; always use the returned bytesRead.
using PBPSharp;
using PBPSharp.Models;
var error = PbpFile.Open(pbpPath, out var pbp);
if (error != PbpError.None || pbp is null)
return;
using (pbp)
{
var disc = pbp.Discs[0];
var buffer = new byte[16 * PbpDiscInfo.IsoBlockSize];
for (var i = 0; i < disc.BlockCount; i++)
{
disc.ReadBlock(i, buffer, out var bytesRead);
// Process buffer[0..bytesRead]
}
}SfoData.Entries contains every entry in file order, including keys that are not in the well-known list.
using PBPSharp;
using PBPSharp.Models;
var error = PbpFile.Open(pbpPath, out var pbp);
if (error != PbpError.None || pbp is null)
return;
using (pbp)
{
foreach (var entry in pbp.SfoData.Entries)
{
var type = entry.Format == 0x0404 ? "uint32" : "string";
Console.WriteLine($"{entry.Key,-16} {type,-6} ({entry.Length,4} bytes) = {entry.Value}");
}
// Well-known keys are available as constants.
var title = pbp.SfoData.GetString(SfoData.Keys.Title);
var bootable = pbp.SfoData.GetUInt32(SfoData.Keys.Bootable);
}PbpFile.Open and PbpDiscInfo.ExtractToBinCue return a PbpError value instead of throwing for data-related problems. Corrupt containers, unsupported PSAR headers, truncated downloads and decompression failures are all reported through the enum, which makes batch processing straightforward.
var error = PbpFile.Open(path, out var pbp);
switch (error)
{
case PbpError.None:
// pbp is ready to use
break;
case PbpError.FileNotFound:
Console.Error.WriteLine("The file does not exist.");
break;
case PbpError.TruncatedPsar:
Console.Error.WriteLine("The PBP looks truncated - re-download it.");
break;
case PbpError.InvalidPsarHeader:
Console.Error.WriteLine("Not a PlayStation disc image (PSP app or unsupported PBP).");
break;
default:
Console.Error.WriteLine($"Could not open the PBP: {error}");
break;
}| PbpError | Value | Meaning |
|---|---|---|
| None | 0 | Success. |
| InvalidHeader | 1 | Missing or invalid PBP magic/header. |
| FileNotFound | 2 | The file does not exist or could not be opened. |
| IoError | 3 | An I/O error occurred (also returned for non-readable/non-seekable streams). |
| CorruptFile | 4 | The container is corrupt or its internal structure is invalid. |
| InvalidPsarHeader | 5 | The PSAR is not a PlayStation disc image (for example a PSP application). |
| DiscOutOfRange | 6 | The requested disc index does not exist. |
| ResourceNotFound | 7 | The requested resource is not present. |
| DecompressionError | 8 | An ISO block could not be decompressed. |
| TruncatedPsar | 9 | The PSAR parses but has no ISO index entries - typically a truncated/incomplete download. |
| InvalidSfo | 10 | Retained for API compatibility; Open no longer returns it (SFO problems are tolerated). |
Notes:
All types live in the PBPSharp namespace; the model types live in PBPSharp.Models.
The entry point. Represents an opened PBP container and owns the underlying stream when it was opened from a path.
| Member | Description |
|---|---|
| static PbpError Open(string path, out PbpFile? pbp) | Opens a PBP from disk. The returned instance owns the file stream. |
| static PbpError Open(Stream stream, bool ownsStream, out PbpFile? pbp) | Opens a PBP from a readable, seekable stream. ownsStream controls whether the stream is disposed with the instance. |
| PbpHeader Header | Parsed PBP header with the offsets of the embedded resources (SFO, ICON0, ICON1, PIC0, PIC1, SND0, DATA.PSP, DATA.PSAR) and the version. |
| SfoData SfoData | Parsed PARAM.SFO metadata. Never null; empty when the SFO is missing or corrupt. |
| IReadOnlyList<PbpDiscInfo> Discs | All discs found in the PSAR, in disc order. |
| bool IsMultiDisc | true when more than one disc was found. |
| string? Title | TITLE from the SFO, or null. |
| string? DiscId | DISC_ID from the SFO (the first disc for multi-disc PBPs), or null. |
| string? Category | CATEGORY from the SFO (for example ME), or null. |
| void Dispose() | Releases the stream when the instance owns it. Safe to call multiple times. |
Represents one disc inside a PBP.
| Member | Description |
|---|---|
| const int IsoBlockSize | Size of one ISO sector in bytes (0x930 = 2352). One PSAR block holds 16 sectors. |
| int Index | 1-based disc index within the PBP. |
| string DiscId | Disc ID read from the PSAR image header (for example SCUS94163). |
| IReadOnlyList<TocEntry> Toc | Track entries of the disc, including data and audio tracks. Empty when the TOC is malformed. |
| uint IsoSize | Total uncompressed ISO size in bytes. |
| int BlockCount | Number of indexed ISO blocks. |
| void ReadBlock(int blockIndex, byte[] buffer, out int bytesRead) | Reads and decompresses one block. The buffer must be at least 16 * IsoBlockSize bytes. |
| void ExtractTo(Stream outputStream, Action<uint>? progress = null, CancellationToken cancellationToken = default) | Extracts the full ISO to a stream, reporting cumulative bytes written and honoring cancellation. |
| PbpError ExtractToBinCue(string binPath, string? cuePath = null, Action<uint>? progress = null, CancellationToken cancellationToken = default) | Extracts to a BIN file and generates a CUE. When cuePath is null, the BIN path with a .cue extension is used. |
| Member | Description |
|---|---|
| static string GenerateCueSheet(string binFileName, IReadOnlyList<TocEntry> tocEntries) | Generates the complete CUE sheet text for the given BIN name and TOC. Data tracks become MODE2/2352, audio tracks become AUDIO, and audio tracks get an INDEX 00 position 150 frames before INDEX 01. |
| Type | Description |
|---|---|
| PbpHeader | Read-only struct with the parsed PBP header fields: Version, SfoOffset, Icon0Offset, Icon1Offset, Pic0Offset, Pic1Offset, Snd0Offset, DataPspOffset, DataPsarOffset, IsValid, plus the MagicValue and HeaderSize constants. |
| PbpError | Result/error enum returned by Open and ExtractToBinCue (see the table above). |
| SfoData | Parsed PARAM.SFO: Magic, Version, KeyTableOffset, DataTableOffset, Entries, Size, GetString(key), GetUInt32(key) and the Keys constants (Bootable, Category, DiscId, DiscVersion, License, ParentalLevel, PspSystemVer, Region, Title). |
| SfoEntry | One SFO entry: Key, Format (0x0204 UTF-8 string, 0x0404 uint32), Length, MaxLength, Value. |
| TocEntry | One TOC track: TrackType, TrackNo, Minutes, Seconds, Frames. |
| TrackType | Data = 0x41, Audio = 0x01. |
| NoIsoIndexException | Thrown when a valid PSAR disc container has no ISO index entries; surfaces as PbpError.TruncatedPsar from Open. |
PBP files are produced by several authoring tools, and PBPSharp reads the layouts each of them writes:
A PBP file starts with a 40-byte header whose magic is 0x50425000 ("\0PBP") and whose fields are file offsets to PARAM.SFO, the icon/picture/sound resources, DATA.PSP and DATA.PSAR. PBPSharp reads that header, parses the SFO (best effort), and then looks at the PSAR section:
All offsets are computed with 64-bit math, so multi-gigabyte images cannot overflow.
The library lives in the Batch Convert to CHD repository under PBPSharp/.
git clone https://github.com/purelogiccode/BatchConvertToCHD.git
cd BatchConvertToCHD
dotnet build PBPSharp/PBPSharp.csproj -c Release
dotnet pack PBPSharp/PBPSharp.csproj -c Release -o artifactsThe test suite for the library lives in BatchConvertToCHD.Tests/:
dotnet test BatchConvertToCHD.Tests/BatchConvertToCHD.Tests.csproj -c Release --filter "FullyQualifiedName~Pbp"PBPSharp is released under the MIT license.
| Back | FazBrowse Home | New Git URL |