{"record":{"id":"ab62d966ee8a76ed","repo":"can1357/oh-my-pi","slug":"invalid-arj-archive-truncated-what","errorCode":null,"errorMessage":"Invalid ARJ archive: truncated ${what}","messagePattern":"Invalid ARJ archive: truncated (.+?)","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/arj.ts","lineNumber":30,"sourceCode":"const LEGACY_DECODER = new TextDecoder(\"windows-1252\");\n\nfunction u16(bytes: Uint8Array, offset: number): number {\n\treturn bytes[offset]! | (bytes[offset + 1]! << 8);\n}\n\nfunction u32(bytes: Uint8Array, offset: number): number {\n\treturn (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;\n}\n\nfunction assertRange(bytes: Uint8Array, start: number, end: number, what: string): void {\n\tif (\n\t\t!Number.isSafeInteger(start) ||\n\t\t!Number.isSafeInteger(end) ||\n\t\tstart < 0 ||\n\t\tend < start ||\n\t\tend > bytes.byteLength\n\t) {\n\t\tthrow new ArchiveError(`Invalid ARJ archive: truncated ${what}`);\n\t}\n}\n\nfunction dosTimeToMs(value: number): number | undefined {\n\tif (value === 0) return undefined;\n\tconst year = 1980 + ((value >>> 25) & 0x7f);\n\tconst month = (value >>> 21) & 0x0f;\n\tconst day = (value >>> 16) & 0x1f;\n\tconst hour = (value >>> 11) & 0x1f;\n\tconst minute = (value >>> 5) & 0x3f;\n\tconst second = (value & 0x1f) * 2;\n\tif (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) return undefined;\n\treturn Date.UTC(year, month - 1, day, hour, minute, second);\n}\n\nfunction readCString(bytes: Uint8Array, start: number, end: number, field: string): { value: string; next: number } {\n\tlet terminator = start;\n\twhile (terminator < end && bytes[terminator] !== 0) terminator++;","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/arj.ts#L12-L48","documentation":"assertRange in the ARJ archive reader verifies that a [start, end) byte window lies entirely within the loaded archive bytes. If the offsets are non-integers, negative, inverted, or extend past the buffer, it throws ArchiveError('Invalid ARJ archive: truncated <what>'), where <what> names the structure being read (header signature, basic header, extended header size, local file header, etc.).","triggerScenarios":"Calling readArj/parseArjBlock on a byte buffer that ends mid-structure: a partially downloaded or copied .arj file, an archive truncated by a failed transfer, a header whose declared size exceeds remaining bytes, or reading from a short buffer where offset arithmetic overruns byteLength.","commonSituations":"Incomplete FTP/HTTP download of an .arj archive; a file truncated at the source (bad floppy/disk recovery of a DOS-era archive); passing a Uint8Array subarray/slice that cut off the tail; corrupted archive headers with bogus size fields.","solutions":["Re-acquire the archive and verify its size/checksum against the original source.","Check the full file was passed to the reader (not a truncated subarray) and that the download/copy completed.","Validate the archive with an external tool to confirm it is corrupt before deeper debugging.","If the source is a stream, ensure the reader gets all bytes (readAllBytes) rather than a first-chunk buffer."],"exampleFix":"// before\nconst bytes = (await Bun.file(\"data.arj\").arrayBuffer()).slice(0, 1024); // truncated\nreadArj(new Uint8Array(bytes));\n// after\nconst bytes = new Uint8Array(await Bun.file(\"data.arj\").arrayBuffer());\ntry { readArj(bytes); } catch (e) { /* ArchiveError: truncated ... */ }","handlingStrategy":"try-catch","validationCode":"if (bytes.byteLength < 4 || bytes[0] !== 0x60 || bytes[1] !== 0xea) {\n  throw new Error(\"Not an ARJ archive (missing 0x60 0xEA signature)\");\n}","typeGuard":"function looksLikeArj(bytes: Uint8Array): boolean {\n  return bytes.byteLength >= 4 && bytes[0] === 0x60 && bytes[1] === 0xea;\n}","tryCatchPattern":"import { ArchiveError } from \"@oh-my-pi/pi-utils/ar/ar/error\";\ntry {\n  return readArj(bytes);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"truncated\")) {\n    throw new Error(`ARJ file incomplete (${bytes.byteLength} bytes read); re-download the archive`);\n  }\n  throw err;\n}","preventionTips":["Verify file size and checksum against the source before extracting.","Never feed the reader a subarray/slice that may cut the archive short.","Ensure downloads/transfers complete (Content-Length check) before parsing.","Treat DOS-era .arj files as suspect: detect truncation early via the trailing end-of-archive marker."],"tags":["archive","arj","corrupt-file"],"backgroundTag":"truncated-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}