@oh-my-pi/pi-utils 17.4.0 → 17.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +22 -0
  3. package/THIRD-PARTY-NOTICES.txt +22909 -0
  4. package/dist/types/ar/arj.d.ts +5 -0
  5. package/dist/types/ar/asar.d.ts +7 -0
  6. package/dist/types/ar/bytes.d.ts +20 -0
  7. package/dist/types/ar/cab.d.ts +5 -0
  8. package/dist/types/ar/checksums.d.ts +10 -0
  9. package/dist/types/ar/codecs/bzip2.d.ts +4 -0
  10. package/dist/types/ar/codecs/gzip.d.ts +6 -0
  11. package/dist/types/ar/codecs/lzma.d.ts +6 -0
  12. package/dist/types/ar/codecs/lzw.d.ts +4 -0
  13. package/dist/types/ar/codecs/lzx.d.ts +7 -0
  14. package/dist/types/ar/codecs/xz.d.ts +4 -0
  15. package/dist/types/ar/codecs/zstd.d.ts +6 -0
  16. package/dist/types/ar/cpio.d.ts +7 -0
  17. package/dist/types/ar/deb.d.ts +5 -0
  18. package/dist/types/ar/entries.d.ts +22 -0
  19. package/dist/types/ar/error.d.ts +8 -0
  20. package/dist/types/ar/index.d.ts +11 -0
  21. package/dist/types/ar/iso.d.ts +5 -0
  22. package/dist/types/ar/limits.d.ts +33 -0
  23. package/dist/types/ar/lzh.d.ts +7 -0
  24. package/dist/types/ar/open.d.ts +45 -0
  25. package/dist/types/ar/paths.d.ts +18 -0
  26. package/dist/types/ar/rar/rar4-decoder.d.ts +6 -0
  27. package/dist/types/ar/rar/rar5-decoder.d.ts +6 -0
  28. package/dist/types/ar/rar.d.ts +5 -0
  29. package/dist/types/ar/reader.d.ts +28 -0
  30. package/dist/types/ar/registry.d.ts +17 -0
  31. package/dist/types/ar/rpm.d.ts +5 -0
  32. package/dist/types/ar/sevenzip/decode.d.ts +33 -0
  33. package/dist/types/ar/sevenzip.d.ts +5 -0
  34. package/dist/types/ar/source.d.ts +54 -0
  35. package/dist/types/ar/tar.d.ts +9 -0
  36. package/dist/types/ar/types.d.ts +99 -0
  37. package/dist/types/ar/unix-ar.d.ts +7 -0
  38. package/dist/types/ar/write.d.ts +10 -0
  39. package/dist/types/ar/zip.d.ts +10 -0
  40. package/dist/types/postmortem.d.ts +33 -0
  41. package/package.json +9 -3
  42. package/src/ar/arj.ts +314 -0
  43. package/src/ar/asar.ts +480 -0
  44. package/src/ar/bytes.ts +78 -0
  45. package/src/ar/cab.ts +377 -0
  46. package/src/ar/checksums.ts +62 -0
  47. package/src/ar/codecs/bzip2.ts +489 -0
  48. package/src/ar/codecs/gzip.ts +25 -0
  49. package/src/ar/codecs/lzma.ts +437 -0
  50. package/src/ar/codecs/lzw.ts +191 -0
  51. package/src/ar/codecs/lzx.ts +366 -0
  52. package/src/ar/codecs/xz.ts +522 -0
  53. package/src/ar/codecs/zstd.ts +25 -0
  54. package/src/ar/cpio.ts +389 -0
  55. package/src/ar/deb.ts +160 -0
  56. package/src/ar/entries.ts +97 -0
  57. package/src/ar/error.ts +11 -0
  58. package/src/ar/index.ts +16 -0
  59. package/src/ar/iso.ts +712 -0
  60. package/src/ar/limits.ts +80 -0
  61. package/src/ar/lzh.ts +659 -0
  62. package/src/ar/open.ts +225 -0
  63. package/src/ar/paths.ts +70 -0
  64. package/src/ar/rar/rar4-decoder.ts +459 -0
  65. package/src/ar/rar/rar5-decoder.ts +400 -0
  66. package/src/ar/rar.ts +735 -0
  67. package/src/ar/reader.ts +162 -0
  68. package/src/ar/registry.ts +208 -0
  69. package/src/ar/rpm.ts +320 -0
  70. package/src/ar/sevenzip/decode.ts +239 -0
  71. package/src/ar/sevenzip.ts +634 -0
  72. package/src/ar/source.ts +190 -0
  73. package/src/ar/tar.ts +771 -0
  74. package/src/ar/types.ts +131 -0
  75. package/src/ar/unix-ar.ts +312 -0
  76. package/src/ar/write.ts +55 -0
  77. package/src/ar/zip.ts +719 -0
  78. package/src/browsers.ts +2 -149
  79. package/src/docx/converter.ts +9 -9
  80. package/src/postmortem.ts +74 -0
  81. package/dist/types/docx/zip.d.ts +0 -6
  82. package/src/docx/zip.ts +0 -87
@@ -44,6 +44,39 @@ export type BrokenPipeSource = "ipc-send" | "stdio-write";
44
44
  export declare function classifyBrokenPipe(err: Error): BrokenPipeSource | undefined;
45
45
  /** Whether an EPIPE came from an IPC `send()` to an optional worker. */
46
46
  export declare function isIpcSendEpipe(err: Error): boolean;
47
+ /**
48
+ * Detect Bun's advanced-serialization (structured-clone) IPC decode failure.
49
+ *
50
+ * When a worker subprocess spawned with `serialization: "advanced"` sends a
51
+ * malformed or truncated frame, Bun raises the decode failure as a
52
+ * process-level `uncaughtException` in the *parent* rather than routing it to
53
+ * the channel's `ipc()` callback (oven-sh/bun#37287). The error is a bare
54
+ * `TypeError: Unable to deserialize data.` whose only own property is `message`
55
+ * — it carries no `code`, no `syscall`, and no `stack`. Matching all four traits
56
+ * keeps unrelated application `TypeError`s (which always carry a populated
57
+ * multi-frame stack) on the fatal path, so a genuine bug is never silently
58
+ * swallowed.
59
+ *
60
+ * Every advanced-serialization channel in this process is an optional worker
61
+ * subsystem (TTS, STT, tiny-title, mnemopi embeddings, JS eval), so one
62
+ * worker's bad frame must fault only that worker — via its own `onExit`/error
63
+ * path — never tear down the whole session. Callers log-and-continue instead of
64
+ * taking the fatal path. Mirrors {@link classifyBrokenPipe} for the send side
65
+ * (#2997, #9158).
66
+ */
67
+ export declare function isWorkerIpcDeserializeError(err: unknown): boolean;
68
+ /**
69
+ * Register a fault/recycle callback for an active advanced-serialization worker
70
+ * IPC channel.
71
+ *
72
+ * Bun surfaces a malformed frame as a process-global `uncaughtException`
73
+ * ({@link isWorkerIpcDeserializeError}) with no way to attribute it to a
74
+ * specific channel, so when one fires every registered handler is invoked to
75
+ * conservatively fault its worker — reject in-flight requests and recycle the
76
+ * subprocess — instead of leaving pending work to await forever. Returns an
77
+ * unregister function; callers MUST unregister when the worker exits.
78
+ */
79
+ export declare function registerWorkerIpcFaultHandler(handler: (err: Error) => void): () => void;
47
80
  /**
48
81
  * Treat unhandled stdout EPIPE rejections as a graceful peer disconnect.
49
82
  *
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "17.4.0",
4
+ "version": "17.4.2",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://omp.sh",
7
- "author": "Can Boluk",
7
+ "author": "Stencil Labs, Inc.",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-natives": "17.4.0"
34
+ "@oh-my-pi/pi-natives": "17.4.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/bun": "^1.3.14"
@@ -43,6 +43,8 @@
43
43
  "src",
44
44
  "README.md",
45
45
  "CHANGELOG.md",
46
+ "LICENSE",
47
+ "THIRD-PARTY-NOTICES.txt",
46
48
  "dist/types"
47
49
  ],
48
50
  "exports": {
@@ -50,6 +52,10 @@
50
52
  "types": "./dist/types/index.d.ts",
51
53
  "import": "./src/index.ts"
52
54
  },
55
+ "./ar": {
56
+ "types": "./dist/types/ar/index.d.ts",
57
+ "import": "./src/ar/index.ts"
58
+ },
53
59
  "./*": {
54
60
  "types": "./dist/types/*.d.ts",
55
61
  "import": "./src/*.ts"
package/src/ar/arj.ts ADDED
@@ -0,0 +1,314 @@
1
+ import { crc32 } from "./checksums";
2
+ import { ArchiveError } from "./error";
3
+ import { assertArchiveMemberSize, assertEntryCount, assertIndexSize, assertInMemorySize } from "./limits";
4
+ import { decompressLhStatic } from "./lzh";
5
+ import { assertArchivePathBytes, assertArchivePathString, normalizeArchiveEntryPath } from "./paths";
6
+ import { type ByteSource, readAllBytes } from "./source";
7
+ import type { ArchiveIndexEntry, FormatReader, FormatReadOptions, MemberSource } from "./types";
8
+
9
+ const ARJ_SIGNATURE_0 = 0x60;
10
+ const ARJ_SIGNATURE_1 = 0xea;
11
+ const ARJ_MAX_BASIC_HEADER = 2600;
12
+ const LEGACY_DECODER = new TextDecoder("windows-1252");
13
+
14
+ function u16(bytes: Uint8Array, offset: number): number {
15
+ return bytes[offset]! | (bytes[offset + 1]! << 8);
16
+ }
17
+
18
+ function u32(bytes: Uint8Array, offset: number): number {
19
+ return (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;
20
+ }
21
+
22
+ function assertRange(bytes: Uint8Array, start: number, end: number, what: string): void {
23
+ if (
24
+ !Number.isSafeInteger(start) ||
25
+ !Number.isSafeInteger(end) ||
26
+ start < 0 ||
27
+ end < start ||
28
+ end > bytes.byteLength
29
+ ) {
30
+ throw new ArchiveError(`Invalid ARJ archive: truncated ${what}`);
31
+ }
32
+ }
33
+
34
+ function dosTimeToMs(value: number): number | undefined {
35
+ if (value === 0) return undefined;
36
+ const year = 1980 + ((value >>> 25) & 0x7f);
37
+ const month = (value >>> 21) & 0x0f;
38
+ const day = (value >>> 16) & 0x1f;
39
+ const hour = (value >>> 11) & 0x1f;
40
+ const minute = (value >>> 5) & 0x3f;
41
+ const second = (value & 0x1f) * 2;
42
+ if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) return undefined;
43
+ return Date.UTC(year, month - 1, day, hour, minute, second);
44
+ }
45
+
46
+ function readCString(bytes: Uint8Array, start: number, end: number, field: string): { value: string; next: number } {
47
+ let terminator = start;
48
+ while (terminator < end && bytes[terminator] !== 0) terminator++;
49
+ if (terminator === end) throw new ArchiveError(`Invalid ARJ ${field}: missing terminator`);
50
+ return { value: LEGACY_DECODER.decode(bytes.subarray(start, terminator)), next: terminator + 1 };
51
+ }
52
+
53
+ interface ArjBlock {
54
+ bodyStart: number;
55
+ bodySize: number;
56
+ nextOffset: number;
57
+ metadataSize: number;
58
+ isEnd: boolean;
59
+ }
60
+
61
+ function parseArjBlock(bytes: Uint8Array, offset: number, options: FormatReadOptions): ArjBlock {
62
+ assertRange(bytes, offset, offset + 4, "header signature");
63
+ if (bytes[offset] !== ARJ_SIGNATURE_0 || bytes[offset + 1] !== ARJ_SIGNATURE_1) {
64
+ throw new ArchiveError("Invalid ARJ header signature");
65
+ }
66
+ const bodySize = u16(bytes, offset + 2);
67
+ if (bodySize === 0)
68
+ return { bodyStart: offset + 4, bodySize: 0, nextOffset: offset + 4, metadataSize: 4, isEnd: true };
69
+ if (bodySize < 30 || bodySize > ARJ_MAX_BASIC_HEADER) throw new ArchiveError("Invalid ARJ basic header size");
70
+ const bodyStart = offset + 4;
71
+ const bodyEnd = bodyStart + bodySize;
72
+ assertRange(bytes, bodyStart, bodyEnd + 4, "basic header");
73
+ if (crc32(bytes.subarray(bodyStart, bodyEnd)) !== u32(bytes, bodyEnd)) {
74
+ throw new ArchiveError("Invalid ARJ basic header CRC32");
75
+ }
76
+ let cursor = bodyEnd + 4;
77
+ let extensionCount = 0;
78
+ for (;;) {
79
+ assertRange(bytes, cursor, cursor + 2, "extended header size");
80
+ const extensionSize = u16(bytes, cursor);
81
+ cursor += 2;
82
+ if (extensionSize === 0) break;
83
+ if (++extensionCount > 65_535) throw new ArchiveError("Invalid ARJ archive: too many extended headers");
84
+ assertRange(bytes, cursor, cursor + extensionSize + 4, "extended header");
85
+ if (crc32(bytes.subarray(cursor, cursor + extensionSize)) !== u32(bytes, cursor + extensionSize)) {
86
+ throw new ArchiveError("Invalid ARJ extended header CRC32");
87
+ }
88
+ cursor += extensionSize + 4;
89
+ assertIndexSize(cursor - offset, options.limits, "header metadata");
90
+ }
91
+ return { bodyStart, bodySize, nextOffset: cursor, metadataSize: cursor - offset, isEnd: false };
92
+ }
93
+
94
+ class ArjBitReader {
95
+ readonly #bytes: Uint8Array;
96
+ #position = 0;
97
+
98
+ constructor(bytes: Uint8Array) {
99
+ this.#bytes = bytes;
100
+ }
101
+
102
+ read(count: number): number {
103
+ if (!Number.isInteger(count) || count < 0 || count > 24 || this.#position + count > this.#bytes.byteLength * 8) {
104
+ throw new ArchiveError("Invalid ARJ method-4 compressed data: truncated bitstream");
105
+ }
106
+ let value = 0;
107
+ for (let index = 0; index < count; index++) {
108
+ const position = this.#position++;
109
+ value = value * 2 + ((this.#bytes[position >>> 3]! >>> (7 - (position & 7))) & 1);
110
+ }
111
+ return value;
112
+ }
113
+
114
+ assertZeroPadding(): void {
115
+ while (this.#position < this.#bytes.byteLength * 8) {
116
+ if (this.read(1) !== 0) throw new ArchiveError("Invalid ARJ method-4 compressed data: non-zero trailing bits");
117
+ }
118
+ }
119
+ }
120
+
121
+ function decompressArjMethod4(packed: Uint8Array, outSize: number): Uint8Array {
122
+ const reader = new ArjBitReader(packed);
123
+ const output = new Uint8Array(outSize);
124
+ let outputPosition = 0;
125
+ while (outputPosition < outSize) {
126
+ let lengthCode = 0;
127
+ let lengthWidth = 0;
128
+ for (; lengthWidth < 7; lengthWidth++) {
129
+ if (reader.read(1) === 0) break;
130
+ lengthCode += 2 ** lengthWidth;
131
+ }
132
+ if (lengthWidth !== 0) lengthCode += reader.read(lengthWidth);
133
+ if (lengthCode === 0) {
134
+ output[outputPosition++] = reader.read(8);
135
+ continue;
136
+ }
137
+ const length = lengthCode + 2;
138
+ if (length > outSize - outputPosition) {
139
+ throw new ArchiveError("Invalid ARJ method-4 compressed data: match exceeds declared size");
140
+ }
141
+ let positionCode = 0;
142
+ let positionWidth = 9;
143
+ for (; positionWidth < 13; positionWidth++) {
144
+ if (reader.read(1) === 0) break;
145
+ positionCode += 2 ** positionWidth;
146
+ }
147
+ positionCode += reader.read(positionWidth);
148
+ if (positionCode >= 26_624 || positionCode >= outputPosition) {
149
+ throw new ArchiveError("Invalid ARJ method-4 compressed data: history distance is out of range");
150
+ }
151
+ let sourcePosition = outputPosition - positionCode - 1;
152
+ for (let index = 0; index < length; index++) output[outputPosition++] = output[sourcePosition++]!;
153
+ }
154
+ reader.assertZeroPadding();
155
+ return output;
156
+ }
157
+
158
+ class ArjMemberSource implements MemberSource {
159
+ readonly #archive: Uint8Array;
160
+ readonly #start: number;
161
+ readonly #packedSize: number;
162
+ readonly #method: number;
163
+ readonly #crc: number;
164
+
165
+ constructor(archive: Uint8Array, start: number, packedSize: number, method: number, crc: number) {
166
+ this.#archive = archive;
167
+ this.#start = start;
168
+ this.#packedSize = packedSize;
169
+ this.#method = method;
170
+ this.#crc = crc;
171
+ }
172
+
173
+ async read(size: number, memberPath: string): Promise<Uint8Array> {
174
+ const packed = this.#archive.subarray(this.#start, this.#start + this.#packedSize);
175
+ let output: Uint8Array;
176
+ switch (this.#method) {
177
+ case 0:
178
+ if (packed.byteLength !== size)
179
+ throw new ArchiveError(`ARJ member '${memberPath}' has inconsistent stored size`);
180
+ output = packed.slice();
181
+ break;
182
+ case 1:
183
+ case 2:
184
+ case 3:
185
+ output = decompressLhStatic(packed, size, 26_624, 5, 17, `ARJ method ${this.#method}`);
186
+ break;
187
+ case 4:
188
+ output = decompressArjMethod4(packed, size);
189
+ break;
190
+ case 8:
191
+ case 9:
192
+ if (size !== 0 || packed.byteLength !== 0) {
193
+ throw new ArchiveError(`ARJ member '${memberPath}' has invalid no-data method sizes`);
194
+ }
195
+ output = new Uint8Array(0);
196
+ break;
197
+ default:
198
+ throw new ArchiveError(`ARJ member '${memberPath}' uses unsupported compression method ${this.#method}`);
199
+ }
200
+ if (output.byteLength !== size)
201
+ throw new ArchiveError(`ARJ member '${memberPath}' extracted to an unexpected size`);
202
+ if (this.#method !== 8 && crc32(output) !== this.#crc) {
203
+ throw new ArchiveError(`ARJ member '${memberPath}' failed CRC32 verification`);
204
+ }
205
+ return output;
206
+ }
207
+ }
208
+
209
+ function normalizeHostPath(rawPath: string, hostOs: number): string {
210
+ let path = rawPath.replaceAll("\\", "/");
211
+ if (hostOs === 1) path = path.replaceAll(">", "/");
212
+ else if (hostOs === 4) path = path.replaceAll(":", "/");
213
+ return path;
214
+ }
215
+
216
+ /** Probe whether bytes begin with a CRC-framed ARJ main header. */
217
+ export function sniffArj(bytes: Uint8Array): boolean {
218
+ if (bytes.byteLength < 34 || bytes[0] !== ARJ_SIGNATURE_0 || bytes[1] !== ARJ_SIGNATURE_1) return false;
219
+ const size = u16(bytes, 2);
220
+ if (size < 30 || size > ARJ_MAX_BASIC_HEADER || bytes.byteLength < size + 8) return false;
221
+ const body = bytes.subarray(4, 4 + size);
222
+ return body[0]! >= 30 && body[0]! <= size && body[6] === 2 && crc32(body) === u32(bytes, 4 + size);
223
+ }
224
+
225
+ /** Index an ARJ archive and lazily decode stored, static-Huffman, and fast-LZSS members. */
226
+ export const readArj: FormatReader = async (
227
+ source: ByteSource,
228
+ options: FormatReadOptions,
229
+ ): Promise<ArchiveIndexEntry[]> => {
230
+ assertInMemorySize(source.size, options.limits);
231
+ let bytes: Uint8Array;
232
+ try {
233
+ bytes = await readAllBytes(source);
234
+ } catch (error) {
235
+ if (error instanceof ArchiveError) throw error;
236
+ throw new ArchiveError(`Unable to read ARJ archive: ${error instanceof Error ? error.message : String(error)}`);
237
+ }
238
+ if (bytes.byteLength !== source.size) throw new ArchiveError("Invalid ARJ archive: truncated data");
239
+ if (!sniffArj(bytes)) throw new ArchiveError("Invalid ARJ archive header");
240
+
241
+ const main = parseArjBlock(bytes, 0, options);
242
+ if (main.isEnd) throw new ArchiveError("Invalid ARJ archive: missing main header");
243
+ const mainFirstHeaderSize = bytes[main.bodyStart]!;
244
+ if (mainFirstHeaderSize < 30 || mainFirstHeaderSize > main.bodySize || bytes[main.bodyStart + 6] !== 2) {
245
+ throw new ArchiveError("Invalid ARJ main header");
246
+ }
247
+ const mainFlags = bytes[main.bodyStart + 4]!;
248
+ if ((mainFlags & 0x01) !== 0) throw new ArchiveError("Encrypted ARJ archives are unsupported");
249
+ if ((mainFlags & 0x04) !== 0) throw new ArchiveError("Multi-volume ARJ archives are unsupported");
250
+
251
+ const entries: ArchiveIndexEntry[] = [];
252
+ let offset = main.nextOffset;
253
+ let metadataSize = main.metadataSize;
254
+ let parsedCount = 0;
255
+ for (;;) {
256
+ const block = parseArjBlock(bytes, offset, options);
257
+ metadataSize += block.metadataSize;
258
+ assertIndexSize(metadataSize, options.limits, "index");
259
+ if (block.isEnd) break;
260
+ assertEntryCount(++parsedCount, options.limits);
261
+ const firstHeaderSize = bytes[block.bodyStart]!;
262
+ if (firstHeaderSize < 30 || firstHeaderSize > block.bodySize) throw new ArchiveError("Invalid ARJ local header");
263
+ const hostOs = bytes[block.bodyStart + 3]!;
264
+ const flags = bytes[block.bodyStart + 4]!;
265
+ const method = bytes[block.bodyStart + 5]!;
266
+ const fileType = bytes[block.bodyStart + 6]!;
267
+ if ((flags & 0x01) !== 0) throw new ArchiveError("Encrypted ARJ members are unsupported");
268
+ if ((flags & 0x0c) !== 0) throw new ArchiveError("Multi-volume ARJ members are unsupported");
269
+ const packedSize = u32(bytes, block.bodyStart + 12);
270
+ const size = u32(bytes, block.bodyStart + 16);
271
+ const fileCrc = u32(bytes, block.bodyStart + 20);
272
+ const accessMode = u16(bytes, block.bodyStart + 26);
273
+ const filename = readCString(
274
+ bytes,
275
+ block.bodyStart + firstHeaderSize,
276
+ block.bodyStart + block.bodySize,
277
+ "filename",
278
+ );
279
+ readCString(bytes, filename.next, block.bodyStart + block.bodySize, "comment");
280
+ assertArchivePathBytes(
281
+ filename.next - (block.bodyStart + firstHeaderSize) - 1,
282
+ "member path",
283
+ options.limits.maxPathBytes,
284
+ );
285
+ const rawPath = normalizeHostPath(filename.value, hostOs);
286
+ assertArchivePathString(rawPath, "member path", options.limits.maxPathBytes);
287
+ assertArchiveMemberSize(size, rawPath || "<unnamed>", options.limits);
288
+ const dataStart = block.nextOffset;
289
+ assertRange(bytes, dataStart, dataStart + packedSize, "member data");
290
+ offset = dataStart + packedSize;
291
+ const path = normalizeArchiveEntryPath(rawPath);
292
+ if (!path) continue;
293
+ const isDirectory = fileType === 3;
294
+ let mode: number | undefined;
295
+ if ((hostOs === 2 || hostOs === 8) && accessMode !== 0) {
296
+ mode = (isDirectory ? 0x4000 : 0x8000) | (accessMode & 0x0fff);
297
+ }
298
+ const rawMtime = u32(bytes, block.bodyStart + 8);
299
+ const mtimeMs =
300
+ hostOs === 2 || hostOs === 8 ? (rawMtime === 0 ? undefined : rawMtime * 1000) : dosTimeToMs(rawMtime);
301
+ entries.push({
302
+ path,
303
+ isDirectory,
304
+ size: isDirectory ? 0 : size,
305
+ mtimeMs,
306
+ mode,
307
+ storage: isDirectory
308
+ ? undefined
309
+ : { type: "member", source: new ArjMemberSource(bytes, dataStart, packedSize, method, fileCrc) },
310
+ });
311
+ }
312
+ if (parsedCount === 0) throw new ArchiveError("Invalid ARJ archive: no members");
313
+ return entries;
314
+ };