Documentation
¶
Overview ¶
Package pak1 provides read, extract, pack, and transactional edit operations for Arma Reforger PAC1 (".pak") archives.
The package parses FORM/PAC1 container layout and exposes file metadata from the FILE tree, including payload offsets, sizes, compression markers, and entry modification time.
Typical workflow:
- open archive with Open
- inspect entries via Reader.EntryCount, Reader.EntryAt, or Reader.ForEachEntry
- read data with Reader.OpenEntry, Reader.ReadEntry, or Reader.ReadRawEntry
- extract files with Reader.Extract
- pack new archive with Pack or PackFile
- update existing archive atomically via OpenEditor and Editor.Commit
Example (open and read):
r, err := pak1.Open("data.pak")
if err != nil {
return err
}
defer r.Close()
first, ok := r.EntryAt(0)
if !ok {
return nil
}
payload, err := r.ReadEntry(first.Path)
if err != nil {
return err
}
_ = payload
Example (extract):
err = r.Extract(ctx, "out", pak1.ExtractOptions{
MaxWorkers: 4,
})
if err != nil {
return err
}
Example (pack):
res, err := pak1.PackFile(ctx, "out.pak", inputs, pak1.PackOptions{})
if err != nil {
return err
}
_ = res
Example (transactional edit):
editor, err := pak1.OpenEditor("addon.pak", pak1.EditOptions{BackupKeep: 1})
if err != nil {
return err
}
if err := editor.DeleteDir("obsolete"); err != nil {
return err
}
_, err = editor.Commit(ctx)
if err != nil {
return err
}
Index ¶
- Constants
- Variables
- func NormalizePath(raw string) string
- func ReadHead(path string) ([]byte, error)
- func ReadHeadFromReaderAt(ra io.ReaderAt, size int64) ([]byte, error)
- func ResolveSeriesPaths(basePath string) ([]string, error)
- type ChunkInfo
- type CompressionType
- 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 EntrySourceInfo
- func ListEntriesSet(paths []string) ([]EntrySourceInfo, error)
- func ListEntriesSetWithOptions(paths []string, opts ReaderOptions) ([]EntrySourceInfo, error)
- func ListSeriesEntries(basePath string) ([]EntrySourceInfo, error)
- func ListSeriesEntriesWithOptions(basePath string, opts ReaderOptions) ([]EntrySourceInfo, error)
- type ExtractOptions
- type Input
- type PackEntryProgress
- type PackOptions
- type PackResult
- type Reader
- func (r *Reader) Chunks() []ChunkInfo
- func (r *Reader) Close() error
- func (r *Reader) Entries() []EntryInfo
- func (r *Reader) EntryAt(index int) (EntryInfo, bool)
- func (r *Reader) EntryCount() int
- func (r *Reader) Extract(ctx context.Context, dstDir string, opts ExtractOptions) error
- func (r *Reader) ForEachEntry(callback func(entry EntryInfo) bool)
- func (r *Reader) Head() []byte
- func (r *Reader) OpenEntry(name string) (io.ReadCloser, error)
- func (r *Reader) OpenEntryInfo(info EntryInfo) (io.ReadCloser, error)
- func (r *Reader) OpenRawEntry(name string) (io.ReadCloser, error)
- func (r *Reader) OpenRawEntryInfo(info EntryInfo) (io.ReadCloser, error)
- func (r *Reader) ReadEntry(name string) ([]byte, error)
- func (r *Reader) ReadRawEntry(name string) ([]byte, error)
- type ReaderOptions
Constants ¶
const ( DefaultWriteBuffer = 16 * 1024 * 1024 DefaultMinCompressSize = 512 DefaultMaxCompressSize = 16 * 1024 * 1024 )
Default packer tuning values.
const FileExtension = ".pak"
FileExtension is the PAK file extension.
Variables ¶
var ( // ErrInvalidHeader means the file is missing or has an invalid FORM/PAC1 header. ErrInvalidHeader = errors.New("invalid PAK file: missing or bad FORM/PAC1 header") // ErrInvalidChunk means one or more IFF chunks are malformed. ErrInvalidChunk = errors.New("invalid PAK chunk") // ErrMissingDataChunk means DATA chunk is required but not found. ErrMissingDataChunk = errors.New("missing DATA chunk") // ErrMissingFileChunk means FILE chunk is required but not found. ErrMissingFileChunk = errors.New("missing FILE chunk") // ErrNilReader means the reader is nil. ErrNilReader = errors.New("reader is nil") // ErrNilWriter means the writer is nil. ErrNilWriter = errors.New("writer is nil") // ErrReaderAtRequired means operation requires io.ReaderAt support. ErrReaderAtRequired = errors.New("readerAt is required") // ErrClosed means reader/resource is already closed. ErrClosed = errors.New("reader or resource already closed") // ErrEntryNotFound means archive entry path is not present. ErrEntryNotFound = errors.New("entry not found") // ErrInvalidEntryPath means one of input paths is empty or invalid after normalization. ErrInvalidEntryPath = errors.New("invalid entry path") // ErrDuplicateEntryPath means two entries resolve to the same logical path. ErrDuplicateEntryPath = errors.New("duplicate entry path") // ErrUnsupportedCompression means entry uses unknown or unsupported compression type. ErrUnsupportedCompression = errors.New("unsupported compression type") // ErrInvalidEntryOffset means entry payload offset/length is outside DATA chunk bounds. ErrInvalidEntryOffset = errors.New("invalid entry offset") // 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") // ErrEmptyInputs means pack operation got no inputs. ErrEmptyInputs = errors.New("no inputs provided for pack") // ErrSizeOverflow means value exceeds uint32 or supported file format limit. ErrSizeOverflow = errors.New("size exceeds PAK format limits") // ErrInvalidCompressPattern means one or more compression rules are invalid. ErrInvalidCompressPattern = errors.New("invalid compress rules") )
Sentinel errors for PAK operations. Use errors.Is in callers.
Functions ¶
func NormalizePath ¶
NormalizePath converts an archive path to canonical slash-separated form.
func ReadHeadFromReaderAt ¶
ReadHeadFromReaderAt parses archive header and returns raw HEAD chunk payload.
func ResolveSeriesPaths ¶
ResolveSeriesPaths expands "name.pak" to [name.pak, name001.pak, name002.pak, ...]. Existing siblings with exactly three decimal digits are appended in numeric order.
Types ¶
type ChunkInfo ¶
type ChunkInfo struct {
// Type is four-character chunk id, for example DATA.
Type string `json:"type" yaml:"type"`
// Length is chunk body size in bytes.
Length uint32 `json:"length" yaml:"length"`
// Offset is absolute file offset where chunk header starts.
Offset int64 `json:"offset" yaml:"offset"`
}
ChunkInfo describes one top-level IFF chunk.
type CompressionType ¶
type CompressionType uint32
CompressionType is entry compression marker from FILE metadata.
const ( // CompressionNone means raw, uncompressed payload. CompressionNone CompressionType = 0 // CompressionZlib means zlib/deflate payload. CompressionZlib CompressionType = 0x106 )
Supported compression type values.
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 staged operations transactionally and returns pack result.
type EntryInfo ¶
type EntryInfo struct {
// ModTime is per-entry modification time from FILE metadata.
// Stored in archive as uint32 little-endian Unix seconds.
ModTime time.Time `json:"mod_time,omitzero" yaml:"mod_time,omitzero"`
// Path is canonical archive path, slash-separated.
Path string `json:"path" yaml:"path"`
// Offset is absolute payload offset in .pak file.
Offset uint32 `json:"offset" yaml:"offset"`
// CompressedSize is stored payload length in bytes.
CompressedSize uint32 `json:"compressed_size" yaml:"compressed_size"`
// OriginalSize is uncompressed payload length for compressed entries.
OriginalSize uint32 `json:"original_size,omitempty" yaml:"original_size,omitempty"`
// CompressionType is entry compression marker.
CompressionType CompressionType `json:"compression_type,omitempty" yaml:"compression_type,omitempty"`
// Unknown1 is first 4-byte opaque metadata field from FILE node.
Unknown1 [4]byte `json:"unknown1,omitempty" yaml:"unknown1,omitempty"`
}
EntryInfo describes one file entry from FILE tree.
func ListEntries ¶
ListEntries opens archive, parses FILE tree, and returns entries.
func ListEntriesFromReaderAt ¶
ListEntriesFromReaderAt parses archive and returns entries.
func ListEntriesFromReaderAtWithOptions ¶
func ListEntriesFromReaderAtWithOptions(ra io.ReaderAt, size int64, opts ReaderOptions) ([]EntryInfo, error)
ListEntriesFromReaderAtWithOptions parses archive with explicit options and returns entries.
func ListEntriesWithOptions ¶
func ListEntriesWithOptions(path string, opts ReaderOptions) ([]EntryInfo, error)
ListEntriesWithOptions opens archive with explicit options and returns entries.
func (EntryInfo) IsCompressed ¶
IsCompressed reports whether entry payload must be decompressed.
func (EntryInfo) LogicalSize ¶
LogicalSize returns original size for compressed entries, otherwise stored size.
type EntrySourceInfo ¶
type EntrySourceInfo struct {
// ArchivePath is source archive path where Entry was resolved.
ArchivePath string `json:"archive_path" yaml:"archive_path"`
// Entry is resolved entry metadata.
Entry EntryInfo `json:"entry" yaml:"entry"`
}
EntrySourceInfo binds one entry to concrete archive path in a multi-pack set.
func ListEntriesSet ¶
func ListEntriesSet(paths []string) ([]EntrySourceInfo, error)
ListEntriesSet merges entries from several archives in given order. If the same normalized path appears multiple times, later archive wins.
func ListEntriesSetWithOptions ¶
func ListEntriesSetWithOptions(paths []string, opts ReaderOptions) ([]EntrySourceInfo, error)
ListEntriesSetWithOptions merges entries from archives with reader options. If the same normalized path appears multiple times, later archive wins.
func ListSeriesEntries ¶
func ListSeriesEntries(basePath string) ([]EntrySourceInfo, error)
ListSeriesEntries resolves archive series paths and returns merged entries.
func ListSeriesEntriesWithOptions ¶
func ListSeriesEntriesWithOptions(basePath string, opts ReaderOptions) ([]EntrySourceInfo, error)
ListSeriesEntriesWithOptions resolves archive series paths and returns merged entries.
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:"-"`
// Entries limits extraction to selected metadata list; nil means all 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 sanitization and uses raw normalized archive names.
RawNames bool `json:"raw_names,omitempty" yaml:"raw_names,omitempty"`
// RawPayload skips decompression and writes stored payload bytes.
RawPayload bool `json:"raw_payload,omitempty" yaml:"raw_payload,omitempty"`
}
ExtractOptions configures archive extraction behavior.
type Input ¶
type Input struct {
// ModTime is optional source timestamp written to FILE metadata.
ModTime time.Time `json:"mod_time" yaml:"mod_time"`
// Open returns raw source payload stream.
Open func() (io.ReadCloser, error) `json:"-" yaml:"-"`
// Path is destination path inside archive.
Path string `json:"path" yaml:"path"`
// SizeHint is expected source 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 archive.
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"`
// CompressedSize is stored payload size in bytes.
CompressedSize uint32 `json:"compressed_size" yaml:"compressed_size"`
// OriginalSize is original source size for compressed payload entries.
OriginalSize uint32 `json:"original_size,omitempty" yaml:"original_size,omitempty"`
// CompressionType is stored compression marker.
CompressionType CompressionType `json:"compression_type,omitempty" yaml:"compression_type,omitempty"`
// CompressionCandidate reports whether compression path was selected.
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.
OnEntryDone func(entry PackEntryProgress) `json:"-" yaml:"-"`
// Head stores opaque HEAD chunk payload. Empty means default header is used.
Head []byte `json:"head,omitempty" yaml:"head,omitempty"`
// Compress defines ordered path rules for compression candidate selection.
Compress []pathrules.Rule `json:"compress,omitempty" yaml:"compress,omitempty"`
// CompressMatcherOptions controls 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 smaller entries.
MinCompressSize uint32 `json:"min_compress_size,omitempty" yaml:"min_compress_size,omitempty"`
// MaxCompressSize disables compression for larger entries.
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 DATA chunk body length in bytes.
DataSize int64 `json:"data_size" yaml:"data_size"`
// FileIndexSize is FILE chunk body length in bytes.
FileIndexSize int64 `json:"file_index_size" yaml:"file_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 as compressed payload.
CompressedEntries int `json:"compressed_entries,omitempty" yaml:"compressed_entries,omitempty"`
// SkippedCompressionEntries is number of candidates stored raw.
SkippedCompressionEntries int `json:"skipped_compression_entries,omitempty" yaml:"skipped_compression_entries,omitempty"`
// Duration is end-to-end pack 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 PAC1 archive to output from provided inputs.
func PackFile ¶
func PackFile(ctx context.Context, outPath string, inputs []Input, opts PackOptions) (*PackResult, error)
PackFile writes PAC1 archive to output path.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader provides read-only access to parsed PAC1 archive.
func NewReaderFromReaderAt ¶
NewReaderFromReaderAt parses PAC1 from existing ReaderAt and known size.
func NewReaderFromReaderAtWithOptions ¶
func NewReaderFromReaderAtWithOptions(ra io.ReaderAt, size int64, opts ReaderOptions) (*Reader, error)
NewReaderFromReaderAtWithOptions parses PAC1 from existing ReaderAt and known size with options.
func OpenWithOptions ¶
func OpenWithOptions(path string, opts ReaderOptions) (*Reader, error)
OpenWithOptions opens PAC1 file by path and parses chunk/index structures with options.
func (*Reader) EntryCount ¶
EntryCount returns number of parsed entries without copying metadata slice.
func (*Reader) Extract ¶
Extract writes selected entries to dstDir. Extraction is parallelized by MaxWorkers.
func (*Reader) ForEachEntry ¶
ForEachEntry iterates parsed entries in source order until callback returns false.
func (*Reader) OpenEntry ¶
func (r *Reader) OpenEntry(name string) (io.ReadCloser, error)
OpenEntry opens named entry and returns logical payload stream.
func (*Reader) OpenEntryInfo ¶
func (r *Reader) OpenEntryInfo(info EntryInfo) (io.ReadCloser, error)
OpenEntryInfo opens entry payload stream by already resolved metadata.
func (*Reader) OpenRawEntry ¶
func (r *Reader) OpenRawEntry(name string) (io.ReadCloser, error)
OpenRawEntry opens named entry and returns stored payload stream without decompression.
func (*Reader) OpenRawEntryInfo ¶
func (r *Reader) OpenRawEntryInfo(info EntryInfo) (io.ReadCloser, error)
OpenRawEntryInfo opens raw entry payload stream by already resolved metadata.
type ReaderOptions ¶
type ReaderOptions struct {
// EntryPathPrefix keeps entries under prefix (or exact path match).
EntryPathPrefix string `json:"entry_path_prefix,omitempty" yaml:"entry_path_prefix,omitempty"`
// MinEntryOriginalSize keeps entries with logical size >= threshold.
MinEntryOriginalSize uint32 `json:"min_entry_original_size,omitempty" yaml:"min_entry_original_size,omitempty"`
// RawOnUnknownCompression returns raw entry payload for unsupported compression.
RawOnUnknownCompression bool `json:"raw_on_unknown_compression,omitempty" yaml:"raw_on_unknown_compression,omitempty"`
}
ReaderOptions configures parse and payload-open behavior.