BabylonJS/Babylon.js · error

deflate: expected byte alignment

Error message

deflate: expected byte alignment

What it means

ensureByteAligned fires when a byte-oriented read (readByte or readUint16LE, used only for stored deflate blocks) is attempted while the bit reader still holds unconsumed bits in its buffer. Stored blocks in DEFLATE must start on a byte boundary; hitting this means the bit-level alignment logic and the byte-level reads got out of sync, i.e. the stream is malformed or was mis-decoded up to this point.

Source

Thrown at packages/dev/loaders/src/FBX/parsers/zlibInflate.ts:128

        if (this.byteOffset + 2 > this.endOffset) {
            throw new Error("zlib: unexpected end of input");
        }
        const value = this.input[this.byteOffset] | (this.input[this.byteOffset + 1] << 8);
        this.byteOffset += 2;
        return value;
    }

    public readByte(): number {
        this.ensureByteAligned();
        if (this.byteOffset >= this.endOffset) {
            throw new Error("zlib: unexpected end of input");
        }
        return this.input[this.byteOffset++];
    }

    private ensureByteAligned(): void {
        if (this.bitCount !== 0) {
            throw new Error("deflate: expected byte alignment");
        }
    }

    private ensureBits(count: number): void {
        while (this.bitCount < count) {
            if (this.byteOffset >= this.endOffset) {
                throw new Error("zlib: unexpected end of input");
            }
            this.bitBuffer |= this.input[this.byteOffset++] << this.bitCount;
            this.bitCount += 8;
        }
    }
}

class OutputWriter {
    private offset = 0;
    private adlerA = 1;
    private adlerB = 0;

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Confirm the payload really is a zlib stream (header check passes and it came from a standard deflate compressor).
  2. Check any custom modifications to the inflate loop — every stored block must call alignToByte() before readUint16LE/readByte.
  3. Ensure the BitReader is used by a single synchronous inflate call, not shared across concurrent decodes.
  4. If the file is suspect, try decompressing it with an independent zlib implementation (e.g. pako) to confirm where the corruption starts.

Example fix

// before
reader.readBits(3); // leftover bits consumed ad hoc
const len = reader.readUint16LE();
// after
reader.alignToByte();
const len = reader.readUint16LE();
Defensive patterns

Strategy: try-catch

Validate before calling

function isZlibStream(buf: Uint8Array): boolean {
  return buf.byteLength >= 6 && (buf[0] & 0x0f) === 8 && ((buf[0] << 8) + buf[1]) % 31 === 0;
}

Try / catch

try {
  return inflateZlib(payload, count);
} catch (e) {
  if (e instanceof Error && e.message === "deflate: expected byte alignment") {
    throw new Error("payload is not a valid deflate stream (alignment error) — wrong offset or corrupt file");
  }
  throw e;
}

Prevention

When it happens

Trigger: readUint16LE or readByte is called when bitCount !== 0. In practice this happens only if inflateStoredBlock's alignToByte() contract is violated — i.e. corrupt preceding data caused the decoder to misinterpret block boundaries, or the internal reader state was corrupted by concurrent/shared use.

Common situations: Parsing a corrupted or non-deflate buffer that happened to pass the zlib header check; a modified/patched inflate pipeline that reads bits without aligning before stored blocks; sharing a BitReader across async tasks so state is interleaved.

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/f3e98c5ebcc3a507. Report an issue: GitHub.