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 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
- Variables
- func NormalizePath(raw string) string
- func NormalizePrefixHeader(raw string) string
- func PackAndHash(ctx context.Context, out io.WriteSeeker, inputs []Input, opts PackOptions, ...) (*PackResult, HashSet, error)
- func PackAndHashFile(ctx context.Context, outPath string, inputs []Input, opts PackOptions, ...) (*PackResult, HashSet, error)
- type EditOptions
- type Editor
- type EntryInfo
- func ListEntries(path string) ([]EntryInfo, error)
- func ListEntriesFromReaderAt(ra io.ReaderAt, size int64) ([]EntryInfo, error)
- func ListEntriesFromReaderAtWithOptions(ra io.ReaderAt, size int64, opts ReaderOptions) ([]EntryInfo, error)
- func ListEntriesWithOptions(path string, opts ReaderOptions) ([]EntryInfo, error)
- type ExtractFileMode
- type ExtractOptions
- type GameType
- type HashSet
- type HeaderPair
- type Input
- type MimeType
- type OffsetMode
- type PackEntryProgress
- type PackOptions
- type PackResult
- type Reader
- func (r *Reader) Close() error
- func (r *Reader) Entries() []EntryInfo
- func (r *Reader) Extract(ctx context.Context, dstDir string, opts ExtractOptions) error
- func (r *Reader) Headers() []HeaderPair
- func (r *Reader) OpenEntry(name string) (io.ReadCloser, error)
- func (r *Reader) OpenEntryInfo(info EntryInfo) (io.ReadCloser, error)
- func (r *Reader) ReadEntry(name string) ([]byte, error)
- func (r *Reader) SHA1Trailer() ([20]byte, bool)
- type ReaderOptions
- type SignVersion
Constants ¶
const ( DefaultWriteBuffer = 16 * 1024 * 1024 DefaultMinCompressSize = 512 DefaultMaxCompressSize = 16 * 1024 * 1024 )
Default packer tuning values.
Variables ¶
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") // 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 ¶
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 ¶
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.
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) Commit ¶
func (e *Editor) Commit(ctx context.Context) (*PackResult, error)
Commit applies all staged operations in one rewrite transaction.
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 ¶
ListEntries opens a PBO and returns entry metadata without payload reads.
func ListEntriesFromReaderAt ¶
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 ¶
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"`
// 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.
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:"-"`
// 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"`
// CompressMatcherOptions control compression path rule matching.
CompressMatcherOptions pathrules.MatcherOptions `json:"compress_matcher_options,omitzero" yaml:"compress_matcher_options,omitzero"`
// 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"`
}
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 ¶
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 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) Extract ¶
Extract writes selected entries from the PBO to dstDir. Extraction is parallelized by MaxWorkers; on failure it returns the first encountered error.
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) SHA1Trailer ¶
SHA1Trailer returns parsed 20-byte trailer hash when present.
type ReaderOptions ¶
type ReaderOptions struct {
// OffsetMode controls whether stored index offsets are used.
OffsetMode OffsetMode `json:"offset_mode,omitempty" yaml:"offset_mode,omitempty"`
// EnableJunkFilter drops malformed/mangled entries from visible entry list.
EnableJunkFilter bool `json:"enable_junk_filter,omitempty" yaml:"enable_junk_filter,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 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.