can1357/oh-my-pi · error · ArchiveError

Invalid XZ BCJ filter 0x${filter.id.toString(16)} start offs

Error message

Invalid XZ BCJ filter 0x${filter.id.toString(16)} start offset

What it means

Each BCJ XZ filter requires its start offset to be aligned to the instruction size it decodes (16 for ARM64, 2 for RISC-V/Thumb-style, 4 for most others, 1 for x86's special case here when id=4 alignment is 1). The library throws when the 4-byte start offset stored in a BCJ filter's properties is not a multiple of the filter's required alignment.

Source

Thrown at packages/utils/src/ar/codecs/xz.ts:380

		}
		write32LE(bytes, index, instruction);
		write32LE(bytes, index + 4, instruction2);
		index += 6;
	}
}

function applyFilter(bytes: Uint8Array, filter: XzFilter): void {
	if (filter.id === 3) {
		if (filter.properties.byteLength !== 1) throw new ArchiveError("Invalid XZ Delta filter properties");
		deltaDecode(bytes, filter.properties[0]! + 1);
		return;
	}
	if (filter.properties.byteLength !== 0 && filter.properties.byteLength !== 4)
		throw new ArchiveError(`Invalid XZ BCJ filter 0x${filter.id.toString(16)} properties`);
	const startOffset = filter.properties.byteLength === 4 ? read32LE(filter.properties, 0) : 0;
	const alignment = filter.id === 6 ? 16 : filter.id === 8 || filter.id === 11 ? 2 : filter.id === 4 ? 1 : 4;
	if ((startOffset & (alignment - 1)) !== 0)
		throw new ArchiveError(`Invalid XZ BCJ filter 0x${filter.id.toString(16)} start offset`);
	switch (filter.id) {
		case 4:
			x86Decode(bytes, startOffset);
			break;
		case 5:
			powerPcDecode(bytes, startOffset);
			break;
		case 6:
			ia64Decode(bytes, startOffset);
			break;
		case 7:
			armDecode(bytes, startOffset);
			break;
		case 8:
			armThumbDecode(bytes, startOffset);
			break;
		case 9:
			sparcDecode(bytes, startOffset);

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-compress the file with standard xz using default BCJ settings (no custom --delta start offset) to get an aligned start offset
  2. Check the producing encoder's start-offset argument and align it to the filter's instruction size
  3. Verify the file with `xz -t` and obtain a good copy if the bytes are corrupt
  4. If this file must be decoded, pre-process it externally (decode with the xz binary) and feed uncompressed data to the library

Example fix

// before: unaligned start offset for PowerPC BCJ (alignment 4)
const filter = { id: 5, properties: new Uint8Array([1, 0, 0, 0]) };
// after: start offset aligned to the filter's instruction size
const filter = { id: 5, properties: new Uint8Array([4, 0, 0, 0]) };
Defensive patterns

Strategy: validation

Validate before calling

function bcjStartOffsetAligned(f: { id: number; properties: Uint8Array }): boolean {
  if (f.properties.byteLength !== 4) return true;
  const off = f.properties[0] | f.properties[1] << 8 | f.properties[2] << 16 | f.properties[3] << 24;
  const align = f.id === 6 ? 16 : f.id === 8 || f.id === 11 ? 2 : f.id === 4 ? 1 : 4;
  return (off & (align - 1)) === 0;
}

Type guard

null

Try / catch

try {
  await decodeXz(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('start offset')) {
    throw new Error('Archive BCJ start offset is misaligned; re-compress with default xz settings');
  }
  throw err;
}

Prevention

When it happens

Trigger: Decompressing an XZ stream where a BCJ filter's properties contain a 4-byte start offset whose value fails the alignment check (e.g. start offset 1 with a 4-byte-aligned filter such as PowerPC or SPARC).

Common situations: Archives produced by encoders that wrote an unaligned start offset; manual or corrupted edits to filter properties; exotic BCJ start offsets from non-default encoder configurations that this decoder validates strictly.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c3150a70b258ab83. Report an issue: GitHub.