{"record":{"id":"b3cfae44aa01ab91","repo":"can1357/oh-my-pi","slug":"invalid-arj-method-4-compressed-data-non-zero-tra","errorCode":null,"errorMessage":"Invalid ARJ method-4 compressed data: non-zero trailing bits","messagePattern":"Invalid ARJ method-4 compressed data: non-zero trailing bits","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/arj.ts","lineNumber":116,"sourceCode":"\tconstructor(bytes: Uint8Array) {\n\t\tthis.#bytes = bytes;\n\t}\n\n\tread(count: number): number {\n\t\tif (!Number.isInteger(count) || count < 0 || count > 24 || this.#position + count > this.#bytes.byteLength * 8) {\n\t\t\tthrow new ArchiveError(\"Invalid ARJ method-4 compressed data: truncated bitstream\");\n\t\t}\n\t\tlet value = 0;\n\t\tfor (let index = 0; index < count; index++) {\n\t\t\tconst position = this.#position++;\n\t\t\tvalue = value * 2 + ((this.#bytes[position >>> 3]! >>> (7 - (position & 7))) & 1);\n\t\t}\n\t\treturn value;\n\t}\n\n\tassertZeroPadding(): void {\n\t\twhile (this.#position < this.#bytes.byteLength * 8) {\n\t\t\tif (this.read(1) !== 0) throw new ArchiveError(\"Invalid ARJ method-4 compressed data: non-zero trailing bits\");\n\t\t}\n\t}\n}\n\nfunction decompressArjMethod4(packed: Uint8Array, outSize: number): Uint8Array {\n\tconst reader = new ArjBitReader(packed);\n\tconst output = new Uint8Array(outSize);\n\tlet outputPosition = 0;\n\twhile (outputPosition < outSize) {\n\t\tlet lengthCode = 0;\n\t\tlet lengthWidth = 0;\n\t\tfor (; lengthWidth < 7; lengthWidth++) {\n\t\t\tif (reader.read(1) === 0) break;\n\t\t\tlengthCode += 2 ** lengthWidth;\n\t\t}\n\t\tif (lengthWidth !== 0) lengthCode += reader.read(lengthWidth);\n\t\tif (lengthCode === 0) {\n\t\t\toutput[outputPosition++] = reader.read(8);","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/arj.ts#L98-L134","documentation":"After decompression completes, method-4 streams are byte-aligned by convention: every remaining bit in the final partial byte should be 0 padding. assertZeroPadding walks those trailing bits and throws if any is 1, signaling the stream ended in an unexpected state — typically corruption or a decoder/encoder mismatch.","triggerScenarios":"decompressArjMethod4() finishes emitting outSize bytes and calls assertZeroPadding(); a remaining bit is 1. Happens with corrupted packed tails, wrong compressed-size slices (padding bits of the next member included), or non-standard encoders that do not zero pad.","commonSituations":"An off-by-one compressedSize when extracting the packed payload so neighboring bytes leak in, bit-rot flipping a padding bit, or an old/buggy ARJ compressor whose output violates the zero-padding convention this reader enforces.","solutions":["Verify you sliced exactly compressedSize bytes of packed data — a one-byte overshoot imports the next member's bits and trips this check.","Run `arj t` / 7-Zip on the archive; if it passes but this library fails, the file was made by a non-conformant encoder — re-pack with a standard tool.","If the file is corrupt, restore from a trusted copy; padding-bit corruption usually accompanies wider damage.","Do not modify the library to ignore trailing bits unless you accept silently tolerating corrupted tails; prefer strict re-verification."],"exampleFix":"// before: rounding the slice length up to a byte boundary with padding included\nconst packed = bytes.subarray(dataStart, dataStart + Math.ceil(rawLen / 8) + 1);\n// after: slice exactly the declared compressed size\nconst packed = bytes.subarray(dataStart, dataStart + entry.compressedSize);","handlingStrategy":"validation","validationCode":"// Slice exactly the declared compressed size — overshooting imports neighboring padding bits\nif (dataOffset + entry.compressedSize > bytes.byteLength) {\n  throw new Error(`Member ${entry.name}: packed data exceeds buffer`);\n}\nconst packed = bytes.subarray(dataOffset, dataOffset + entry.compressedSize);","typeGuard":null,"tryCatchPattern":"try {\n  return decompressArjMethod4(packed, originalSize);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"non-zero trailing bits\")) {\n    throw new Error(`Member ${name}: stream tail invalid — check packed slice length or re-obtain file`);\n  }\n  throw err;\n}","preventionTips":["Use exact compressedSize slices; avoid byte-rounded or padded regions.","If external tools accept the file but this library rejects it, suspect a non-conformant encoder — re-pack with a standard archiver.","Keep strict padding checks enabled to catch silent corruption."],"tags":["archive","decompression","padding","corrupt-file"],"backgroundTag":"truncated-compressed-stream","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}