pbo

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 1, 2026 License: MIT Imports: 24 Imported by: 0

README

pbo

Go package for reading, packing, and editing PBO archives.

Main points:

  • streaming input API via Input.Open
  • deterministic pack order by normalized path
  • optional LZSS compression by path rules
  • pack and hash set in one flow (PackAndHash*)
  • transactional edit API with backup rotation

Usage examples

Pack

PackFile writes a deterministic PBO from []Input. Compression is optional and controlled by path rules. Examples below use github.com/woozymasta/pathrules.

inputs := []pbo.Input{
  {
    Path: "config.cpp",
    Open: func() (io.ReadCloser, error) {
      return os.Open("src/config.cpp")
    },
  },
}

opts := pbo.PackOptions{
  Headers: []pbo.HeaderPair{
    {Key: "prefix", Value: "myaddon"},
  },
  Compress: []pathrules.Rule{
    {Action: pathrules.ActionInclude, Pattern: "*.rvmat"},
    {Action: pathrules.ActionInclude, Pattern: "textures/**"},
  },
  CompressMatcherOptions: pathrules.MatcherOptions{
    CaseInsensitive: true,
    DefaultAction:   pathrules.ActionExclude,
  },
  OnEntryDone: func(e pbo.PackEntryProgress) {
    // optional per-entry progress callback
  },
}

res, err := pbo.PackFile(ctx, "addon.pbo", inputs, opts)
if err != nil {
  return err
}

_ = res.WrittenEntries
_ = res.RawBytes
_ = res.CompressedBytes
_ = res.CompressedEntries
_ = res.SkippedCompressionEntries
_ = res.Duration
Compress by extensions

If you only have extension lists, convert them to include rules.

compressExts := []string{"rvmat", "ogg", "paa"}
opts := pbo.PackOptions{
  Compress: pathrules.ParseExtensions(compressExts),
  CompressMatcherOptions: pathrules.MatcherOptions{
    CaseInsensitive: true,
    DefaultAction:   pathrules.ActionExclude,
  },
}

You can also load compression rules directly from file:

// compress.rules:
// !*.rvmat
// !textures/**

rules, err := pathrules.LoadRulesFile("compress.rules")
if err != nil {
  return err
}

opts.Compress = rules
Pack and hash

Use PackAndHashFile when you need archive creation and hash set in one pass. This avoids a second read for hash computation.

res, hs, err := pbo.PackAndHashFile(
  ctx,
  "addon.pbo",
  inputs,
  opts,
  pbo.SignVersionV3,
  pbo.GameTypeDayZ,
)
if err != nil {
  return err
}

_ = res
_ = hs
Read and extract

Open archive, read entries by path, and extract to directory in one flow. ExtractOptions.MaxWorkers controls parallel extraction workers. Path sanitization is enabled by default for Extract.

r, err := pbo.Open("addon.pbo")
if err != nil {
  return err
}
defer r.Close()

entries := r.Entries()
data, err := r.ReadEntry(entries[0].Path)
if err != nil {
  return err
}
_ = data

err = r.Extract(ctx, "out", pbo.ExtractOptions{MaxWorkers: 4})
if err != nil {
  return err
}

// Disable default sanitization only when raw names are required.
err = r.Extract(ctx, "out-raw", pbo.ExtractOptions{
  MaxWorkers: 4,
  RawNames:   true,
})
if err != nil {
  return err
}
Reader filters

OpenWithOptions and ListEntriesWithOptions support entry filters for noisy/obfuscated archives.

r, err := pbo.OpenWithOptions("addon.pbo", pbo.ReaderOptions{
  MinEntryOriginalSize: 12,   // logical original size (fallback to DataSize)
  MinEntryDataSize:     0,    // packed size threshold
  EntryPathPrefix:      "scripts/4_world",
  FilterASCIIOnly:      false,
  SanitizeControlChars: true, // safe textual output
  SanitizeNames:        true, // filesystem-safe normalized paths
})
if err != nil {
  return err
}
defer r.Close()

// Extract uses parsed entries by default.
if err := r.Extract(ctx, "out", pbo.ExtractOptions{MaxWorkers: 4}); err != nil {
  return err
}
Edit existing PBO

Use OpenEditor for transactional changes to an existing archive. Queue add/replace/delete operations, then apply once with Commit.

editor, err := pbo.OpenEditor("addon.pbo", pbo.EditOptions{
  PackOptions: pbo.PackOptions{
    Compress: []pathrules.Rule{
      {Action: pathrules.ActionInclude, Pattern: "*.txt"},
      {Action: pathrules.ActionInclude, Pattern: "*.c"},
    },
    CompressMatcherOptions: pathrules.MatcherOptions{
      CaseInsensitive: true,
      DefaultAction:   pathrules.ActionExclude,
    },
  },
  BackupKeep: 1,
})
if err != nil {
  return err
}

if err := editor.Replace(pbo.Input{
  Path: "scripts/main.c",
  Open: func() (io.ReadCloser, error) {
    return os.Open("scripts/main.c")
  },
}); err != nil {
  return err
}

if err := editor.DeleteDir("obsolete"); err != nil {
  return err
}

_, err = editor.Commit(ctx)
if err != nil {
  return err
}

Compression behavior

[!IMPORTANT]
In many modern mod packs, compression gives small size reduction. Textures and models are usually already compressed by source formats. Most gain comes from scripts and text, but they are often a tiny part of total archive size. Compressing everything can spend CPU time for marginal output difference. Use it selectively when it matches your build goals.

Compression still works correctly in this package.

Compression is considered only when:

  • final PackOptions.Compress rule decision includes entry path
  • size is in [MinCompressSize, MaxCompressSize]

Behavior details:

  • known-size candidates use in-memory compression path
  • unknown-size candidates are written raw
  • compressed payload is used only if it is smaller than raw payload

[!NOTE]
Unknown-size inputs are never compressed in the main pack flow.

Limits and notes

  • classic PBO payload addressing is limited to 4 GiB
  • pack writes payload sequentially and patches index fields after payload write
  • this package does not run source transforms by itself
  • caller should provide transformed streams via Input.Open

Documentation

Overview

Package pbo provides read, extract, pack, hash, and edit operations for PBO (Packed Bank of files) archives. It is designed for streaming workflows: packing accepts caller-provided streams (Input.Open), and reading/extracting works without loading full archive payload into memory.

Compression rules (summary):

  • path decision must include entry via PackOptions.Compress rules;
  • final entry size must be within [MinCompressSize, MaxCompressSize];
  • known-size inputs use in-memory compression path (bounded by MaxCompressSize);
  • unknown-size inputs are streamed raw (no temp-file fallback);
  • compression is written only when result is smaller than source.

Reading

Open a PBO and list or read entries:

r, err := pbo.Open("addon.pbo")
if err != nil {
    return err
}
defer r.Close()
for _, e := range r.Entries() {
    data, _ := r.ReadEntry(e.Path)
    // use data
}

For metadata-only scans, use fast helpers without creating a full reader:

headers, err := pbo.ReadHeaders("addon.pbo")
if err != nil {
    return err
}
entries, err := pbo.ListEntries("addon.pbo")
if err != nil {
    return err
}
_, _ = headers, entries

For filesystem-safe listing names:

entries, err := pbo.ListEntriesWithOptions("addon.pbo", pbo.ReaderOptions{
    SanitizeNames: true,
})
if err != nil {
    return err
}
_ = entries

For noisy or obfuscated archives, combine entry filters:

r, err := pbo.OpenWithOptions("addon.pbo", pbo.ReaderOptions{
    MinEntryOriginalSize: 12,
    MinEntryDataSize:     0,
    EntryPathPrefix:      "scripts/4_world",
    FilterASCIIOnly:      false,
    SanitizeControlChars: true,
    SanitizeNames:        true,
})
if err != nil {
    return err
}
defer r.Close()

For archives with meaningful non-zero index offsets, use compatibility mode:

r, err := pbo.OpenWithOptions("addon.pbo", pbo.ReaderOptions{
    OffsetMode: pbo.OffsetModeStoredCompat,
})
if err != nil {
    return err
}
defer r.Close()

Extracting

Extract all entries to a directory (parallel workers):

if err := r.Extract(ctx, "out/", pbo.ExtractOptions{MaxWorkers: 4}); err != nil {
    return err
}

Path sanitization is enabled by default during extraction. Disable it explicitly when raw names are required:

if err := r.Extract(ctx, "out/", pbo.ExtractOptions{
    MaxWorkers: 4,
    RawNames:   true,
}); err != nil {
    return err
}

Packing

Pack from stream-oriented inputs (order is deterministic by path): examples below use github.com/woozymasta/pathrules for compression filters:

inputs := []pbo.Input{
    {Path: "config.cpp", Open: func() (io.ReadCloser, error) { return os.Open("src/config.cpp") }},
}
res, err := pbo.Pack(ctx, outFile, inputs, pbo.PackOptions{
    Headers: []pbo.HeaderPair{{Key: "prefix", Value: "myaddon"}},
    // Empty rule set means no compression.
    Compress: []pathrules.Rule{
        {Action: pathrules.ActionInclude, Pattern: "*.rvmat"},
        {Action: pathrules.ActionInclude, Pattern: "textures/**"},
    },
    CompressMatcherOptions: pathrules.MatcherOptions{
        CaseInsensitive: true,
        DefaultAction:   pathrules.ActionExclude,
    },
    OnEntryDone: func(entry pbo.PackEntryProgress) {
        // progress callback per written entry
    },
})
_ = res.CompressedEntries
_ = res.SkippedCompressionEntries

Use default mode for restricted environments where only output path is writable (unknown-size candidates remain raw):

res, err := pbo.Pack(ctx, outFile, inputs, pbo.PackOptions{
    Compress: []pathrules.Rule{
        {Action: pathrules.ActionInclude, Pattern: "*"},
    },
})

To write to a path and append the SHA1 trailer:

res, err := pbo.PackFile(ctx, "addon.pbo", inputs, opts)

To pack and calculate signature hash set in one flow:

res, hs, err := pbo.PackAndHashFile(
    ctx,
    "addon.pbo",
    inputs,
    opts,
    pbo.SignVersionV3,
    pbo.GameTypeDayZ,
)
_, _ = res, hs

To edit existing archive in one transaction:

editor, err := pbo.OpenEditor("addon.pbo", pbo.EditOptions{
    PackOptions: pbo.PackOptions{
        Compress: []pathrules.Rule{
            {Action: pathrules.ActionInclude, Pattern: "*.txt"},
        },
        CompressMatcherOptions: pathrules.MatcherOptions{
            CaseInsensitive: true,
            DefaultAction:   pathrules.ActionExclude,
        },
    },
    BackupKeep:  1,
})
if err != nil {
    return err
}
if err := editor.Replace(pbo.Input{
    Path: "scripts/main.c",
    Open: func() (io.ReadCloser, error) { return os.Open("scripts/main.c") },
}); err != nil {
    return err
}
if _, err := editor.Commit(ctx); err != nil {
    return err
}

Index

Constants

View Source
const (
	DefaultWriteBuffer     = 4 * 1024 * 1024
	LargeWriteBuffer       = 16 * 1024 * 1024
	DefaultMinCompressSize = 512
	DefaultMaxCompressSize = 16 * 1024 * 1024
)

Default packer tuning values.

View Source
const FileExtension = ".pbo"

FileExtension is the PBO file extension.

Variables

View Source
var (
	// ErrInvalidHeader means the PBO file is missing or has a bad header.
	ErrInvalidHeader = errors.New("invalid PBO file: missing or bad header")
	// ErrFileNameTooLong means the entry filename exceeds the maximum length.
	ErrFileNameTooLong = errors.New("entry filename exceeds maximum length")
	// ErrNilReader means the reader is nil.
	ErrNilReader = errors.New("reader is nil")
	// ErrReaderAtRequired means operation requires io.ReaderAt support.
	ErrReaderAtRequired = errors.New("readerAt is required")
	// ErrWriterAtRequired means operation requires io.WriterAt support.
	ErrWriterAtRequired = errors.New("writerAt is required")
	// ErrNilWriter means the writer is nil.
	ErrNilWriter = errors.New("writer is nil")
	// ErrEntryNotFound means the entry is not found.
	ErrEntryNotFound = errors.New("entry not found")
	// ErrClosed means the reader or resource is already closed.
	ErrClosed = errors.New("reader or resource already closed")
	// ErrSizeOverflow means the size exceeds the uint32 or 4 GiB PBO limit.
	ErrSizeOverflow = errors.New("size exceeds uint32 or 4 GiB PBO limit")
	// ErrEmptyInputs means no inputs provided for pack.
	ErrEmptyInputs = errors.New("no inputs provided for pack")
	// ErrInvalidCompressPattern means one or more compression rules are invalid.
	ErrInvalidCompressPattern = errors.New("invalid compress rules")
	// ErrUnsupportedSignVersion means the signature version is not supported.
	ErrUnsupportedSignVersion = errors.New("unsupported signature version")
	// ErrUnsupportedGameTypeV3 means the game type is not supported for v3.
	ErrUnsupportedGameTypeV3 = errors.New("unsupported game type for v3")
	// ErrTrailerTooShort means the file is too short for the trailer.
	ErrTrailerTooShort = errors.New("file too short for trailer")
	// ErrInvalidTrailerPrefix means the trailer does not start with 0x00.
	ErrInvalidTrailerPrefix = errors.New("trailer does not start with 0x00")
	// ErrTrailerHashMismatch means the trailer hash mismatch.
	ErrTrailerHashMismatch = errors.New("trailer hash mismatch")
	// ErrInvalidSHA1DigestLength means the SHA1 digest length is invalid.
	ErrInvalidSHA1DigestLength = errors.New("invalid SHA1 digest length")
	// ErrInvalidEntryPath means one of input entry paths is empty or invalid after normalization.
	ErrInvalidEntryPath = errors.New("invalid entry path")
	// ErrDuplicateEntryPath means two inputs resolve to the same path (case-insensitive).
	ErrDuplicateEntryPath = errors.New("duplicate entry path")
	// ErrInvalidExtractPath means archive entry path is invalid for extraction destination.
	ErrInvalidExtractPath = errors.New("invalid extract path")
	// ErrExtractPathOutsideRoot means resolved extraction path escapes destination root.
	ErrExtractPathOutsideRoot = errors.New("extract path escapes destination root")
	// ErrInvalidEntryOffset means one or more entry offsets are malformed for selected reader policy.
	ErrInvalidEntryOffset = errors.New("invalid entry offset")
)

Sentinel errors for PBO operations. Use errors.Is in callers.

Functions

func NormalizePath

func NormalizePath(raw string) string

NormalizePath converts an archive/internal path to normalized slash-separated form. It trims spaces, accepts both "/" and "\", removes leading "./" and "/", and cleans "." segments.

func NormalizePrefixHeader

func NormalizePrefixHeader(raw string) string

NormalizePrefixHeader normalizes PBO "prefix" header value to "\" separators.

func PackAndHash

func PackAndHash(
	ctx context.Context,
	out io.WriteSeeker,
	inputs []Input,
	opts PackOptions,
	signVersion SignVersion,
	gameType GameType,
) (*PackResult, HashSet, error)

PackAndHash writes a PBO to out and calculates hash set over written bytes. The output writer must also implement io.ReaderAt for hash calculation.

func PackAndHashFile

func PackAndHashFile(
	ctx context.Context,
	outPath string,
	inputs []Input,
	opts PackOptions,
	signVersion SignVersion,
	gameType GameType,
) (*PackResult, HashSet, error)

PackAndHashFile writes a PBO to outPath, returns hash set, and appends SHA1 trailer.

func SanitizePath added in v0.1.1

func SanitizePath(pathValue string) (string, error)

SanitizePath rewrites one path to deterministic filesystem-safe slash-separated form.

func ToggleSealedInPlace added in v0.2.0

func ToggleSealedInPlace(out io.ReadWriteSeeker, sealedKeyValue *SealedKey) error

ToggleSealedInPlace toggles sealed transform for an existing archive stream.

This operation is symmetric: applying it twice with the same key restores the original bytes.

Types

type EditOptions

type EditOptions struct {
	// PackOptions are applied for added/replaced entries during commit.
	PackOptions PackOptions `json:"pack_options,omitzero" yaml:"pack_options,omitzero"`
	// BackupKeep controls how many backup generations are kept after successful commit.
	// 0 means remove backup, 1 keeps only `<archive>.bak`, N keeps `.bak` + `.bak.1..N-1`.
	BackupKeep int `json:"backup_keep,omitempty" yaml:"backup_keep,omitempty"`
}

EditOptions configures file-based archive edit flow.

type Editor

type Editor struct {
	// contains filtered or unexported fields
}

Editor accumulates archive edit operations and applies them on Commit.

func OpenEditor

func OpenEditor(path string, opts EditOptions) (*Editor, error)

OpenEditor creates staged editor for file-based archive rewrite workflow.

func (*Editor) Add

func (e *Editor) Add(inputs ...Input) error

Add schedules adding new entries and fails on path collision during commit.

func (*Editor) Commit

func (e *Editor) Commit(ctx context.Context) (*PackResult, error)

Commit applies all staged operations in one rewrite transaction.

func (*Editor) Delete

func (e *Editor) Delete(paths ...string) error

Delete schedules exact-path removal.

func (*Editor) DeleteDir

func (e *Editor) DeleteDir(prefixes ...string) error

DeleteDir schedules directory-prefix removal.

func (*Editor) Replace

func (e *Editor) Replace(inputs ...Input) error

Replace schedules replacing existing entries.

type EntryInfo

type EntryInfo struct {
	// Path is the entry path as stored in archive index.
	Path string `json:"path" yaml:"path"`
	// Offset is byte offset of entry payload.
	Offset uint32 `json:"offset" yaml:"offset"`
	// DataSize is stored payload size in bytes.
	DataSize uint32 `json:"data_size" yaml:"data_size"`
	// OriginalSize is uncompressed size for compressed entries; zero otherwise.
	OriginalSize uint32 `json:"original_size,omitempty" yaml:"original_size,omitempty"`
	// TimeStamp is Unix timestamp from entry record.
	TimeStamp uint32 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
	// MimeType stores entry mime marker.
	MimeType MimeType `json:"mime_type,omitempty" yaml:"mime_type,omitempty"`
}

EntryInfo describes a single parsed PBO entry.

func ListEntries

func ListEntries(path string) ([]EntryInfo, error)

ListEntries opens a PBO and returns entry metadata without payload reads.

func ListEntriesFromReaderAt

func ListEntriesFromReaderAt(ra io.ReaderAt, size int64) ([]EntryInfo, error)

ListEntriesFromReaderAt parses entry metadata from a random-access source.

func ListEntriesFromReaderAtWithOptions

func ListEntriesFromReaderAtWithOptions(ra io.ReaderAt, size int64, opts ReaderOptions) ([]EntryInfo, error)

ListEntriesFromReaderAtWithOptions parses entry metadata from a random-access source using reader options.

func ListEntriesWithOptions

func ListEntriesWithOptions(path string, opts ReaderOptions) ([]EntryInfo, error)

ListEntriesWithOptions opens a PBO and returns entry metadata without payload reads using reader options.

func (*EntryInfo) IsCompressed

func (e *EntryInfo) IsCompressed() bool

IsCompressed reports whether this entry is stored with LZSS compression.

type ExtractFileMode

type ExtractFileMode string

ExtractFileMode controls output file open behavior during extraction.

const (
	// ExtractFileModeAuto first tries create-only, then falls back to truncate for existing files.
	ExtractFileModeAuto ExtractFileMode = "auto"
	// ExtractFileModeOverwriteSmart rewrites files in place and truncates only when existing file is larger.
	ExtractFileModeOverwriteSmart ExtractFileMode = "overwrite_smart"
	// ExtractFileModeTruncate opens existing files with truncate and creates missing files.
	ExtractFileModeTruncate ExtractFileMode = "truncate"
	// ExtractFileModeCreateOnly creates files only when absent and fails on existing files.
	ExtractFileModeCreateOnly ExtractFileMode = "create_only"
)

Output file creation policies for extraction.

type ExtractOptions

type ExtractOptions struct {
	// OnEntryDone is called after one entry is fully written to disk.
	OnEntryDone func(entry EntryInfo, written int64, outputPath string) `json:"-" yaml:"-"`
	// FileMode controls output file creation policy.
	FileMode ExtractFileMode `json:"file_mode,omitempty" yaml:"file_mode,omitempty"`
	// Entries limits extraction to selected metadata list; nil means all parsed entries.
	Entries []EntryInfo `json:"-" yaml:"-"`
	// MaxWorkers is number of extraction workers (zero means GOMAXPROCS).
	MaxWorkers int `json:"max_workers,omitempty" yaml:"max_workers,omitempty"`
	// ContinueOnError keeps extraction running when one or more entries fail.
	// Default false is fail-fast mode.
	ContinueOnError bool `json:"continue_on_error,omitempty" yaml:"continue_on_error,omitempty"`
	// RawNames disables default path sanitization during extract.
	// When false (default), extract rewrites names to filesystem-safe output paths.
	RawNames bool `json:"raw_names,omitempty" yaml:"raw_names,omitempty"`
}

ExtractOptions configures Extract behavior.

type GameType

type GameType string

GameType is game-specific hash policy discriminator for v3 signatures.

const (
	// GameTypeAny is the default game type for v3 signature hash policy.
	GameTypeAny GameType = ""
	// GameTypeArma is the game type for Arma 3.
	GameTypeArma GameType = "arma"
	// GameTypeDayZ is the game type for DayZ.
	GameTypeDayZ GameType = "dayz"
)

Supported game types for v3 signature hash policy.

type HashSet

type HashSet struct {
	// Hash1 is SHA1 of the full PBO data (without trailer).
	Hash1 [20]byte `json:"hash1" yaml:"hash1"`
	// Hash2 is composed from hash1, name hash, and prefix rules.
	Hash2 [20]byte `json:"hash2" yaml:"hash2"`
	// Hash3 is composed from file hash, name hash, and prefix rules.
	Hash3 [20]byte `json:"hash3" yaml:"hash3"`
}

HashSet contains signature hashes for one PBO.

func ComputeHashSet

func ComputeHashSet(path string, version SignVersion, gameType GameType) (HashSet, error)

ComputeHashSet calculates hash1/hash2/hash3 for a PBO.

type HeaderPair

type HeaderPair struct {
	Key   string `json:"key" yaml:"key"`
	Value string `json:"value" yaml:"value"`
}

HeaderPair is a PBO header key-value pair written in provided order.

func ReadHeaders

func ReadHeaders(path string) ([]HeaderPair, error)

ReadHeaders opens a PBO and returns only header key-value pairs without parsing entry table.

func ReadHeadersFromReaderAt

func ReadHeadersFromReaderAt(ra io.ReaderAt, size int64) ([]HeaderPair, error)

ReadHeadersFromReaderAt reads only PBO header key-value pairs from a random-access source.

func ReadHeadersFromReaderAtWithOptions added in v0.2.0

func ReadHeadersFromReaderAtWithOptions(ra io.ReaderAt, size int64, opts ReaderOptions) ([]HeaderPair, error)

ReadHeadersFromReaderAtWithOptions reads only PBO header key-value pairs from a random-access source.

func ReadHeadersWithOptions added in v0.2.0

func ReadHeadersWithOptions(path string, opts ReaderOptions) ([]HeaderPair, error)

ReadHeadersWithOptions opens a PBO and returns only header key-value pairs using reader options.

type Input

type Input struct {
	// ModTime is optional entry timestamp.
	ModTime time.Time `json:"mod_time" yaml:"mod_time"`
	// Open returns raw source stream for this entry.
	Open func() (io.ReadCloser, error) `json:"-" yaml:"-"`
	// Path is destination path inside PBO.
	Path string `json:"path" yaml:"path"`
	// SizeHint is expected size in bytes (zero when unknown).
	SizeHint int64 `json:"size_hint,omitempty" yaml:"size_hint,omitempty"`
}

Input describes one source stream to be packed into a PBO entry.

type MimeType

type MimeType uint32

MimeType is the 4-byte PBO entry type (stored little-endian).

const (
	// MimeHeader marks the first header record ("Vers").
	MimeHeader MimeType = 0x56657273
	// MimeCompress marks LZSS-compressed data ("Cprs").
	MimeCompress MimeType = 0x43707273
	// MimeEncoded marks VBS-encrypted data ("Enco").
	MimeEncoded MimeType = 0x456e6372
	// MimeNil marks uncompressed or terminator entry.
	MimeNil MimeType = 0x00000000
)

PBO entry mime constants.

type OffsetMode

type OffsetMode string

OffsetMode controls how reader resolves payload offsets from index table.

const (
	// OffsetModeSequential ignores stored index offsets and derives payload offsets sequentially.
	OffsetModeSequential OffsetMode = "sequential"
	// OffsetModeStoredCompat tries to use non-zero stored offsets and falls back to sequential on malformed data.
	OffsetModeStoredCompat OffsetMode = "stored_compat"
	// OffsetModeStoredStrict requires stored non-zero offsets to be valid and fails otherwise.
	OffsetModeStoredStrict OffsetMode = "stored_strict"
)

Reader offset resolution modes.

type PackEntryProgress

type PackEntryProgress struct {
	// Path is entry path written to archive.
	Path string `json:"path" yaml:"path"`
	// Offset is payload offset in resulting archive.
	Offset uint32 `json:"offset" yaml:"offset"`
	// DataSize is stored payload size in bytes.
	DataSize uint32 `json:"data_size" yaml:"data_size"`
	// OriginalSize is original size for compressed entries; zero for raw entries.
	OriginalSize uint32 `json:"original_size,omitempty" yaml:"original_size,omitempty"`
	// MimeType is stored entry mime marker.
	MimeType MimeType `json:"mime_type,omitempty" yaml:"mime_type,omitempty"`
	// CompressionCandidate reports whether compression path was selected for this input entry.
	CompressionCandidate bool `json:"compression_candidate,omitempty" yaml:"compression_candidate,omitempty"`
	// Compressed reports whether compressed payload was actually written.
	Compressed bool `json:"compressed,omitempty" yaml:"compressed,omitempty"`
}

PackEntryProgress contains one completed entry write event from pack flow.

type PackOptions

type PackOptions struct {
	// OnEntryDone is called after one entry is fully written to archive payload.
	OnEntryDone func(entry PackEntryProgress) `json:"-" yaml:"-"`
	// SealedKey enables sealed archive transform when set.
	// Nil keeps standard plain PBO read/write behavior.
	SealedKey *SealedKey `json:"sealed_key,omitempty" yaml:"sealed_key,omitempty"`

	// Headers are written in deterministic order.
	Headers []HeaderPair `json:"headers,omitempty" yaml:"headers,omitempty"`
	// Compress defines ordered path rules for compression candidate selection.
	Compress []pathrules.Rule `json:"compress,omitempty" yaml:"compress,omitempty"`
	// WriterBufferSize is buffered writer size in bytes.
	WriterBufferSize int `json:"writer_buffer_size,omitempty" yaml:"writer_buffer_size,omitempty"`
	// MinCompressSize disables compression for entries smaller than this size.
	// Default is 512 bytes.
	MinCompressSize uint32 `json:"min_compress_size,omitempty" yaml:"min_compress_size,omitempty"`
	// MaxCompressSize disables compression for entries larger than this size.
	// Default is 16 MiB and also bounds known-size in-memory compression path.
	MaxCompressSize uint32 `json:"max_compress_size,omitempty" yaml:"max_compress_size,omitempty"`

	// CompressMatcherOptions control compression path rule matching.
	CompressMatcherOptions pathrules.MatcherOptions `json:"compress_matcher_options,omitzero" yaml:"compress_matcher_options,omitzero"`
	// contains filtered or unexported fields
}

PackOptions configures pack behavior.

type PackResult

type PackResult struct {
	// WrittenEntries is number of entries written to archive.
	WrittenEntries int `json:"written_entries" yaml:"written_entries"`
	// DataSize is total payload bytes written.
	DataSize int64 `json:"data_size" yaml:"data_size"`
	// IndexSize is total index bytes written.
	IndexSize int64 `json:"index_size" yaml:"index_size"`
	// RawBytes is total bytes written for uncompressed payload entries.
	RawBytes int64 `json:"raw_bytes,omitempty" yaml:"raw_bytes,omitempty"`
	// CompressedBytes is total bytes written for compressed payload entries.
	CompressedBytes int64 `json:"compressed_bytes,omitempty" yaml:"compressed_bytes,omitempty"`
	// CompressedEntries is number of entries written with compressed payload.
	CompressedEntries int `json:"compressed_entries,omitempty" yaml:"compressed_entries,omitempty"`
	// SkippedCompressionEntries is number of compression candidates stored as raw payload.
	SkippedCompressionEntries int `json:"skipped_compression_entries,omitempty" yaml:"skipped_compression_entries,omitempty"`
	// Duration is end-to-end pack core duration.
	Duration time.Duration `json:"duration,omitempty" yaml:"duration,omitempty"`
}

PackResult contains pack output statistics.

func Pack

func Pack(ctx context.Context, out io.WriteSeeker, inputs []Input, opts PackOptions) (*PackResult, error)

Pack writes a PBO to out from the given inputs. Inputs are sorted by path for deterministic output.

func PackFile

func PackFile(ctx context.Context, outPath string, inputs []Input, opts PackOptions) (*PackResult, error)

PackFile writes a PBO to outPath and appends a SHA1 trailer.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader provides read-only access to a parsed PBO file.

func NewReaderFromReaderAt

func NewReaderFromReaderAt(ra io.ReaderAt, size int64) (*Reader, error)

NewReaderFromReaderAt parses PBO from existing ReaderAt and known size.

func NewReaderFromReaderAtWithOptions

func NewReaderFromReaderAtWithOptions(ra io.ReaderAt, size int64, opts ReaderOptions) (*Reader, error)

NewReaderFromReaderAtWithOptions parses PBO from existing ReaderAt and known size using explicit reader options.

func Open

func Open(path string) (*Reader, error)

Open opens PBO file by path and parses index/header structures.

func OpenWithOptions

func OpenWithOptions(path string, opts ReaderOptions) (*Reader, error)

OpenWithOptions opens PBO file by path and parses index/header structures using explicit reader options.

func (*Reader) Close

func (r *Reader) Close() error

Close closes the underlying file if reader owns one.

func (*Reader) CopyEntryInfoTo added in v0.3.0

func (r *Reader) CopyEntryInfoTo(info EntryInfo, dst io.Writer, buf []byte) (int64, error)

CopyEntryInfoTo decompresses and copies entry described by info into dst using the provided buf. Unlike OpenEntryInfo, it does not spawn a goroutine or pipe. buf may be nil; a default buffer is used for the uncompressed path.

func (*Reader) CopyEntryTo added in v0.3.0

func (r *Reader) CopyEntryTo(name string, dst io.Writer, buf []byte) (int64, error)

CopyEntryTo decompresses and copies the named entry into dst using the provided buf. Unlike OpenEntry, it does not spawn a goroutine or pipe, making it more efficient for callers that only need to push entry content into an io.Writer (os.File, http.ResponseWriter). buf may be nil; a default buffer is used for the uncompressed path.

func (*Reader) CopyPackedEntryInfoTo added in v0.3.0

func (r *Reader) CopyPackedEntryInfoTo(info EntryInfo, dst io.Writer) (int64, error)

CopyPackedEntryInfoTo copies the raw stored bytes of an entry into dst. Like OpenPackedEntryInfo, it yields compressed bytes for compressed entries.

func (*Reader) Entries

func (r *Reader) Entries() []EntryInfo

Entries returns a copy of parsed entries.

func (*Reader) EntriesView added in v0.3.0

func (r *Reader) EntriesView() []EntryInfo

EntriesView returns the internal entry slice without copying. Callers must not modify the returned slice or its elements. Suitable for read-only listing and UI polling where avoiding the copy matters.

func (*Reader) Extract

func (r *Reader) Extract(ctx context.Context, dstDir string, opts ExtractOptions) error

Extract writes selected entries from the PBO to dstDir. Extraction is parallelized by MaxWorkers. By default extraction is fail-fast; set ContinueOnError to keep processing and return the first encountered error at the end.

func (*Reader) Headers

func (r *Reader) Headers() []HeaderPair

Headers returns parsed headers in original order.

func (*Reader) OpenEntry

func (r *Reader) OpenEntry(name string) (io.ReadCloser, error)

OpenEntry opens named entry for reading. Returned stream yields decompressed content for LZSS-compressed entries.

func (*Reader) OpenEntryInfo

func (r *Reader) OpenEntryInfo(info EntryInfo) (io.ReadCloser, error)

OpenEntryInfo opens entry stream by already resolved metadata. Returned stream yields decompressed content for LZSS-compressed entries.

func (*Reader) OpenPackedEntryInfo added in v0.3.0

func (r *Reader) OpenPackedEntryInfo(info EntryInfo) (io.ReadCloser, error)

OpenPackedEntryInfo opens a read stream over the raw stored bytes of an entry. The returned reader yields compressed bytes for LZSS-compressed entries no decompression is applied. Use this for selective replace in Editor, hash tooling, or any consumer that needs the packed payload verbatim.

func (*Reader) RangeEntries added in v0.3.0

func (r *Reader) RangeEntries(fn func(EntryInfo) bool)

RangeEntries calls fn for each entry in index order. Iteration stops early when fn returns false.

func (*Reader) ReadEntry

func (r *Reader) ReadEntry(name string) ([]byte, error)

ReadEntry reads full (decompressed) content of the named entry.

func (*Reader) SHA1Trailer

func (r *Reader) SHA1Trailer() ([20]byte, bool)

SHA1Trailer returns parsed 20-byte trailer hash when present.

type ReaderOptions

type ReaderOptions struct {
	// SealedKey enables sealed archive decode when set.
	// Nil keeps standard plain PBO read behavior.
	SealedKey *SealedKey `json:"sealed_key,omitempty" yaml:"sealed_key,omitempty"`
	// OffsetMode controls whether stored index offsets are used.
	OffsetMode OffsetMode `json:"offset_mode,omitempty" yaml:"offset_mode,omitempty"`
	// EntryPathPrefix keeps entries whose normalized path is equal to prefix or starts with "prefix/".
	EntryPathPrefix string `json:"entry_path_prefix,omitempty" yaml:"entry_path_prefix,omitempty"`
	// MinEntryOriginalSize keeps entries with original size >= this value.
	// For uncompressed entries OriginalSize is treated as DataSize.
	MinEntryOriginalSize uint32 `json:"min_entry_original_size,omitempty" yaml:"min_entry_original_size,omitempty"`
	// MinEntryDataSize keeps entries with packed payload size >= this value.
	MinEntryDataSize uint32 `json:"min_entry_data_size,omitempty" yaml:"min_entry_data_size,omitempty"`
	// EnableJunkFilter drops malformed/mangled entries from visible entry list.
	EnableJunkFilter bool `json:"enable_junk_filter,omitempty" yaml:"enable_junk_filter,omitempty"`
	// FilterASCIIOnly keeps only entries with ASCII-only path bytes.
	FilterASCIIOnly bool `json:"filter_ascii_only,omitempty" yaml:"filter_ascii_only,omitempty"`
	// SanitizeControlChars rewrites control/format runes in entry paths for safe textual output.
	SanitizeControlChars bool `json:"sanitize_control_chars,omitempty" yaml:"sanitize_control_chars,omitempty"`
	// SanitizeNames rewrites entry paths to filesystem-safe names for listing workflows.
	SanitizeNames bool `json:"sanitize_names,omitempty" yaml:"sanitize_names,omitempty"`
}

ReaderOptions configures reader parse compatibility behavior.

type SealedKey added in v0.2.0

type SealedKey [16]byte

SealedKey is a fixed 16-byte key used by optional sealed transform mode.

type SignVersion

type SignVersion uint32

SignVersion is PBO signature hash policy version.

const (
	// SignVersionV2 is the legacy version of PBO signature hash policy.
	SignVersionV2 SignVersion = 2
	// SignVersionV3 is the current version of PBO signature hash policy.
	SignVersionV3 SignVersion = 3
)

Supported signature hash policy versions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL