neozipkit 1.0.2 → 1.5.0

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 (128) hide show
  1. package/NEOZIP_APPNOTE.md +625 -0
  2. package/README.md +44 -11
  3. package/WHATS_NEW.md +153 -10
  4. package/dist/browser/ZipkitBrowser.d.ts.map +1 -1
  5. package/dist/browser/ZipkitBrowser.js +4 -6
  6. package/dist/browser/ZipkitBrowser.js.map +1 -1
  7. package/dist/browser/zstd-browser-shim.d.ts +17 -9
  8. package/dist/browser/zstd-browser-shim.d.ts.map +1 -1
  9. package/dist/browser/zstd-browser-shim.js +99 -21
  10. package/dist/browser/zstd-browser-shim.js.map +1 -1
  11. package/dist/browser-esm/index.js +6534 -1275
  12. package/dist/browser-esm/index.js.map +4 -4
  13. package/dist/browser-umd/index.js +6534 -1275
  14. package/dist/browser-umd/index.js.map +4 -4
  15. package/dist/browser-umd/index.min.js +6 -3
  16. package/dist/core/ZipCompress.d.ts +6 -0
  17. package/dist/core/ZipCompress.d.ts.map +1 -1
  18. package/dist/core/ZipCompress.js +12 -11
  19. package/dist/core/ZipCompress.js.map +1 -1
  20. package/dist/core/ZipCopy.d.ts.map +1 -1
  21. package/dist/core/ZipCopy.js +33 -25
  22. package/dist/core/ZipCopy.js.map +1 -1
  23. package/dist/core/ZipDecompress.d.ts.map +1 -1
  24. package/dist/core/ZipDecompress.js +13 -8
  25. package/dist/core/ZipDecompress.js.map +1 -1
  26. package/dist/core/ZipEntry.d.ts +24 -0
  27. package/dist/core/ZipEntry.d.ts.map +1 -1
  28. package/dist/core/ZipEntry.js +146 -14
  29. package/dist/core/ZipEntry.js.map +1 -1
  30. package/dist/core/Zipkit.d.ts +43 -11
  31. package/dist/core/Zipkit.d.ts.map +1 -1
  32. package/dist/core/Zipkit.js +132 -82
  33. package/dist/core/Zipkit.js.map +1 -1
  34. package/dist/core/components/HashCalculator.d.ts +22 -1
  35. package/dist/core/components/HashCalculator.d.ts.map +1 -1
  36. package/dist/core/components/HashCalculator.js +49 -6
  37. package/dist/core/components/HashCalculator.js.map +1 -1
  38. package/dist/core/components/Util.d.ts +3 -4
  39. package/dist/core/components/Util.d.ts.map +1 -1
  40. package/dist/core/components/Util.js +10 -8
  41. package/dist/core/components/Util.js.map +1 -1
  42. package/dist/core/constants/Headers.d.ts +2 -3
  43. package/dist/core/constants/Headers.d.ts.map +1 -1
  44. package/dist/core/constants/Headers.js +15 -5
  45. package/dist/core/constants/Headers.js.map +1 -1
  46. package/dist/core/constants/MetaPaths.d.ts +38 -0
  47. package/dist/core/constants/MetaPaths.d.ts.map +1 -0
  48. package/dist/core/constants/MetaPaths.js +79 -0
  49. package/dist/core/constants/MetaPaths.js.map +1 -0
  50. package/dist/core/index.d.ts +5 -0
  51. package/dist/core/index.d.ts.map +1 -1
  52. package/dist/core/index.js +18 -1
  53. package/dist/core/index.js.map +1 -1
  54. package/dist/core/inflateZipPayload.d.ts +6 -0
  55. package/dist/core/inflateZipPayload.d.ts.map +1 -0
  56. package/dist/core/inflateZipPayload.js +28 -0
  57. package/dist/core/inflateZipPayload.js.map +1 -0
  58. package/dist/core/merkle/MerkleRoot.d.ts +109 -0
  59. package/dist/core/merkle/MerkleRoot.d.ts.map +1 -0
  60. package/dist/core/merkle/MerkleRoot.js +275 -0
  61. package/dist/core/merkle/MerkleRoot.js.map +1 -0
  62. package/dist/core/origin/OriginExtra.d.ts +43 -0
  63. package/dist/core/origin/OriginExtra.d.ts.map +1 -0
  64. package/dist/core/origin/OriginExtra.js +187 -0
  65. package/dist/core/origin/OriginExtra.js.map +1 -0
  66. package/dist/core/version.js +1 -1
  67. package/dist/core/zip64/Zip64.d.ts +138 -0
  68. package/dist/core/zip64/Zip64.d.ts.map +1 -0
  69. package/dist/core/zip64/Zip64.js +353 -0
  70. package/dist/core/zip64/Zip64.js.map +1 -0
  71. package/dist/node/ZipCompressNode.d.ts.map +1 -1
  72. package/dist/node/ZipCompressNode.js +40 -49
  73. package/dist/node/ZipCompressNode.js.map +1 -1
  74. package/dist/node/ZipCopyNode.d.ts.map +1 -1
  75. package/dist/node/ZipCopyNode.js +26 -26
  76. package/dist/node/ZipCopyNode.js.map +1 -1
  77. package/dist/node/ZipDecompressNode.d.ts.map +1 -1
  78. package/dist/node/ZipDecompressNode.js +21 -3
  79. package/dist/node/ZipDecompressNode.js.map +1 -1
  80. package/dist/node/ZipkitNode.d.ts +23 -1
  81. package/dist/node/ZipkitNode.d.ts.map +1 -1
  82. package/dist/node/ZipkitNode.js +158 -57
  83. package/dist/node/ZipkitNode.js.map +1 -1
  84. package/dist/node/ZstdNode.d.ts +14 -4
  85. package/dist/node/ZstdNode.d.ts.map +1 -1
  86. package/dist/node/ZstdNode.js +28 -4
  87. package/dist/node/ZstdNode.js.map +1 -1
  88. package/dist/node/buildZipBuffer.d.ts +49 -0
  89. package/dist/node/buildZipBuffer.d.ts.map +1 -0
  90. package/dist/node/buildZipBuffer.js +146 -0
  91. package/dist/node/buildZipBuffer.js.map +1 -0
  92. package/dist/node/index.d.ts +3 -0
  93. package/dist/node/index.d.ts.map +1 -1
  94. package/dist/node/index.js +6 -1
  95. package/dist/node/index.js.map +1 -1
  96. package/dist/node/inflateZipPayload.d.ts +5 -0
  97. package/dist/node/inflateZipPayload.d.ts.map +1 -0
  98. package/dist/node/inflateZipPayload.js +21 -0
  99. package/dist/node/inflateZipPayload.js.map +1 -0
  100. package/dist/types/index.js +2 -2
  101. package/node-esm.mjs +32 -0
  102. package/package.json +13 -6
  103. package/src/browser/ZipkitBrowser.ts +4 -6
  104. package/src/browser/zstd-browser-shim.ts +110 -28
  105. package/src/core/ZipCompress.ts +20 -13
  106. package/src/core/ZipCopy.ts +56 -27
  107. package/src/core/ZipDecompress.ts +14 -8
  108. package/src/core/ZipEntry.ts +200 -14
  109. package/src/core/Zipkit.ts +189 -98
  110. package/src/core/components/HashCalculator.ts +58 -6
  111. package/src/core/components/Util.ts +16 -7
  112. package/src/core/constants/Headers.ts +15 -4
  113. package/src/core/constants/MetaPaths.ts +86 -0
  114. package/src/core/index.ts +17 -0
  115. package/src/core/inflateZipPayload.ts +27 -0
  116. package/src/core/merkle/MerkleRoot.ts +312 -0
  117. package/src/core/origin/OriginExtra.ts +209 -0
  118. package/src/core/version.ts +1 -1
  119. package/src/core/zip64/Zip64.ts +473 -0
  120. package/src/node/ZipCompressNode.ts +40 -49
  121. package/src/node/ZipCopyNode.ts +31 -29
  122. package/src/node/ZipDecompressNode.ts +16 -3
  123. package/src/node/ZipkitNode.ts +203 -72
  124. package/src/node/ZstdNode.ts +33 -8
  125. package/src/node/buildZipBuffer.ts +221 -0
  126. package/src/node/index.ts +7 -0
  127. package/src/node/inflateZipPayload.ts +19 -0
  128. package/src/types/index.ts +2 -2
@@ -24,6 +24,17 @@ import {
24
24
  import { ZipFileEntry, FileData } from '../types';
25
25
  import { Logger } from './components/Logger';
26
26
  import { crc32 } from './encryption/ZipCrypto';
27
+ import {
28
+ ZIP64_U16,
29
+ ZIP64_U32,
30
+ ZIP64_VERSION_NEEDED,
31
+ buildZip64Extra,
32
+ exceedsU16,
33
+ exceedsU32,
34
+ needsZip64Entry,
35
+ parseZip64Extra,
36
+ stripZip64FromExtra,
37
+ } from './zip64/Zip64';
27
38
 
28
39
  const VER_ENCODING = 30;
29
40
  const VER_EXTRACT = 10; // Version needed to extract (1.0)
@@ -49,6 +60,15 @@ export default class ZipEntry implements ZipFileEntry {
49
60
  localHdrOffset: number = 0; // Relative offset to local header from File/Disk start
50
61
  filename: string = ''; // File name
51
62
  extraField: Buffer | null = null; // Extra field
63
+ /**
64
+ * Caller-supplied extra-field records (complete header id + size + payload).
65
+ * Written on the local header and the central-directory header beside 0x014E.
66
+ * Unknown ids parsed from an existing header (including ZipWiki 0x014F) are kept here
67
+ * so a later header rebuild does not drop them.
68
+ */
69
+ additionalExtra: Buffer | null = null;
70
+ /** When false, local/central headers omit the Info-ZIP Unicode Path extra. */
71
+ emitUnicodePath = true;
52
72
  comment: string | null = null; // Entry comment
53
73
 
54
74
  // File Data
@@ -82,7 +102,14 @@ export default class ZipEntry implements ZipFileEntry {
82
102
  universalTime: number | null = null; // Universal Time
83
103
  uid: number | null = null; // User ID
84
104
  gid: number | null = null; // Group ID
85
- sha256: string | null = null; // SHA-256 hash of the file
105
+ sha256: string | null = null; // SHA-256 hash of the file (Extra Field 0x014E, bare content digest)
106
+
107
+ /**
108
+ * APPNOTE §6.3 v1 Merkle leaf: SHA-256(0x00 ‖ uncompressed payload).
109
+ * In-memory only (not written as a ZIP Extra today). Filled in the same streaming hash
110
+ * pass as `sha256` during create/extract — never reconstruct by re-reading payload when set.
111
+ */
112
+ merkleLeafV1: string | null = null;
86
113
 
87
114
  // Symbolic link data
88
115
  isSymlink: boolean = false; // Entry is a symbolic link
@@ -93,6 +120,14 @@ export default class ZipEntry implements ZipFileEntry {
93
120
  originalEntry: string | null = null; // Original entry name for hard links
94
121
  inode: number | null = null; // Inode number for hard links
95
122
 
123
+ /** True when Zip64 extra (0x0001) was applied or will be emitted (8-byte data descriptors). */
124
+ usesZip64Extra: boolean = false;
125
+ /**
126
+ * When true, emit Zip64 Extra Field `0x0001` and version-needed 45 even if
127
+ * sizes/offsets fit classic u16/u32 fields (APPNOTE MAY; for testing).
128
+ */
129
+ forceZip64: boolean = false;
130
+
96
131
  fileData?: FileData;
97
132
 
98
133
  /**
@@ -299,6 +334,31 @@ export default class ZipEntry implements ZipFileEntry {
299
334
  this.neoCryptoFlags = _data.readUInt16LE(7);
300
335
  }
301
336
  }
337
+ } else if (_id === HDR_ID.ZIP64) {
338
+ const z64 = parseZip64Extra(_data, {
339
+ uncompressedSize: this.uncompressedSize,
340
+ compressedSize: this.compressedSize,
341
+ localHdrOffset: this.localHdrOffset,
342
+ diskStart: this.volNumber,
343
+ });
344
+ this.usesZip64Extra = true;
345
+ if (z64.uncompressedSize !== undefined) {
346
+ this.uncompressedSize = z64.uncompressedSize;
347
+ }
348
+ if (z64.compressedSize !== undefined) {
349
+ this.compressedSize = z64.compressedSize;
350
+ }
351
+ if (z64.localHdrOffset !== undefined) {
352
+ this.localHdrOffset = z64.localHdrOffset;
353
+ }
354
+ if (z64.diskStart !== undefined) {
355
+ this.volNumber = z64.diskStart;
356
+ }
357
+ } else if (_id !== HDR_ID.UNICODE_PATH) {
358
+ const rec = Buffer.from(this.extraField.subarray(i, i + 4 + _len));
359
+ this.additionalExtra = this.additionalExtra
360
+ ? Buffer.concat([this.additionalExtra, rec])
361
+ : rec;
302
362
  }
303
363
  // Skip Unicode Path here as we already processed it
304
364
  i += 4 + _len;
@@ -323,10 +383,30 @@ export default class ZipEntry implements ZipFileEntry {
323
383
  * @returns true if the filename contains non-ASCII characters or special characters
324
384
  */
325
385
  needsUnicodeHandling(): boolean {
386
+ if (!this.emitUnicodePath) return false;
326
387
  // Check if filename contains non-ASCII characters or special characters like apostrophes
327
388
  return /[^\x00-\x7E]|['"]/.test(this.filename);
328
389
  }
329
390
 
391
+ /** Version needed to extract. Method 93 (zstd) is 6.3; Zip64 is 4.5. */
392
+ private versionNeededToExtract(): number {
393
+ let ver = VER_EXTRACT;
394
+ if (this.cmpMethod === CMP_METHOD.AES_ENCRYPT) ver = VER_AES_EXTRACT;
395
+ else if (this.cmpMethod === CMP_METHOD.ZSTD) ver = 63;
396
+ if (
397
+ this.forceZip64 ||
398
+ needsZip64Entry(
399
+ this.uncompressedSize,
400
+ this.compressedSize,
401
+ this.localHdrOffset,
402
+ this.volNumber
403
+ )
404
+ ) {
405
+ ver = Math.max(ver, ZIP64_VERSION_NEEDED);
406
+ }
407
+ return ver;
408
+ }
409
+
330
410
  /**
331
411
  * Adds UTF-8 Unicode Path field to a buffer
332
412
  * @param buffer - The buffer to write to
@@ -382,13 +462,38 @@ export default class ZipEntry implements ZipFileEntry {
382
462
  if (isNeoEncrypt) {
383
463
  extraFieldLen += NEO_CRYPTO_EXTRA_FIELD_SIZE;
384
464
  }
465
+
466
+ const sha256Local = this.sha256 ? Buffer.from(this.sha256, 'hex') : null;
467
+ const sha256LocalLen = sha256Local && sha256Local.length > 0 ? 4 + sha256Local.length : 0;
468
+ const callerExtra = stripZip64FromExtra(this.additionalExtra);
469
+ const callerExtraLen = callerExtra?.length ?? 0;
470
+
471
+ const useZip64Sizes =
472
+ this.forceZip64 ||
473
+ exceedsU32(this.uncompressedSize) ||
474
+ exceedsU32(this.compressedSize);
475
+ if (useZip64Sizes) {
476
+ this.usesZip64Extra = true;
477
+ }
478
+ const zip64LocalExtra = useZip64Sizes
479
+ ? buildZip64Extra(
480
+ {
481
+ uncompressedSize: this.uncompressedSize,
482
+ compressedSize: this.compressedSize,
483
+ },
484
+ { forLocal: true }
485
+ )
486
+ : null;
487
+ const zip64LocalLen = zip64LocalExtra?.length ?? 0;
488
+
489
+ extraFieldLen += sha256LocalLen + callerExtraLen + zip64LocalLen;
385
490
 
386
491
  const data = Buffer.alloc(LOCAL_HDR.SIZE + this.filename.length + extraFieldLen);
387
492
 
388
493
  // "PK\003\004"
389
494
  data.writeUInt32LE(LOCAL_HDR.SIGNATURE, 0);
390
495
  // version needed to extract (NeoEncrypt keeps standard ZIP method — use normal extract version)
391
- const verExtract = this.cmpMethod === CMP_METHOD.AES_ENCRYPT ? VER_AES_EXTRACT : VER_EXTRACT;
496
+ const verExtract = this.versionNeededToExtract();
392
497
  data.writeUInt16LE(verExtract, LOCAL_HDR.VER_EXTRACT);
393
498
  // general purpose bit flag
394
499
  data.writeUInt16LE(this.bitFlags >>> 0, LOCAL_HDR.FLAGS);
@@ -398,10 +503,15 @@ export default class ZipEntry implements ZipFileEntry {
398
503
  data.writeUInt32LE(this.timeDateDOS >>> 0, LOCAL_HDR.TIMEDATE_DOS);
399
504
  // uncompressed file crc-32 value (AE-2 stores 0)
400
505
  data.writeUInt32LE(this.aesVersion === 2 ? 0 : this.crc, LOCAL_HDR.CRC);
401
- // compressed size
402
- data.writeUInt32LE(this.compressedSize, LOCAL_HDR.CMP_SIZE);
403
- // uncompressed size
404
- data.writeUInt32LE(this.uncompressedSize, LOCAL_HDR.UNCMP_SIZE);
506
+ // compressed / uncompressed sizes (Zip64 sentinels when needed)
507
+ data.writeUInt32LE(
508
+ useZip64Sizes ? ZIP64_U32 : this.compressedSize,
509
+ LOCAL_HDR.CMP_SIZE
510
+ );
511
+ data.writeUInt32LE(
512
+ useZip64Sizes ? ZIP64_U32 : this.uncompressedSize,
513
+ LOCAL_HDR.UNCMP_SIZE
514
+ );
405
515
  // filename length
406
516
  data.writeUInt16LE(this.filename.length, LOCAL_HDR.FNAME_LEN);
407
517
  // extra field length
@@ -420,6 +530,12 @@ export default class ZipEntry implements ZipFileEntry {
420
530
  extraOffset = this.addUnicodePathField(data, extraOffset);
421
531
  }
422
532
 
533
+ // Zip64 sizes first among optional extras so Node can patch them by scanning
534
+ if (zip64LocalExtra) {
535
+ zip64LocalExtra.copy(data, extraOffset);
536
+ extraOffset += zip64LocalLen;
537
+ }
538
+
423
539
  // Add AES extra field
424
540
  if (isAes) {
425
541
  data.writeUInt16LE(HDR_ID.AES, extraOffset);
@@ -445,6 +561,17 @@ export default class ZipEntry implements ZipFileEntry {
445
561
  extraOffset += NEO_CRYPTO_EXTRA_FIELD_SIZE;
446
562
  }
447
563
 
564
+ if (sha256Local && sha256LocalLen > 0) {
565
+ data.writeUInt16LE(HDR_ID.SHA256, extraOffset);
566
+ data.writeUInt16LE(sha256Local.length, extraOffset + 2);
567
+ sha256Local.copy(data, extraOffset + 4);
568
+ extraOffset += sha256LocalLen;
569
+ }
570
+
571
+ if (callerExtra && callerExtraLen > 0) {
572
+ callerExtra.copy(data, extraOffset);
573
+ }
574
+
448
575
  return data;
449
576
  }
450
577
 
@@ -485,7 +612,45 @@ export default class ZipEntry implements ZipFileEntry {
485
612
  const isNeoEncrypt = this.neoCryptoAlgorithm > 0;
486
613
  const neoLen = isNeoEncrypt ? NEO_CRYPTO_EXTRA_FIELD_SIZE : 0;
487
614
 
488
- const extraLen = utfLen + sha256Len + uidgidLen + symlinkLen + hardlinkLen + (needsUnicode ? unicodePathLen : 0) + aesLen + neoLen;
615
+ const callerExtra = stripZip64FromExtra(this.additionalExtra);
616
+ const callerExtraLen = callerExtra?.length ?? 0;
617
+
618
+ const zip64Fields: {
619
+ uncompressedSize?: number;
620
+ compressedSize?: number;
621
+ localHdrOffset?: number;
622
+ diskStart?: number;
623
+ } = {};
624
+ if (this.forceZip64 || exceedsU32(this.uncompressedSize)) {
625
+ zip64Fields.uncompressedSize = this.uncompressedSize;
626
+ }
627
+ if (this.forceZip64 || exceedsU32(this.compressedSize)) {
628
+ zip64Fields.compressedSize = this.compressedSize;
629
+ }
630
+ if (this.forceZip64 || exceedsU32(this.localHdrOffset)) {
631
+ zip64Fields.localHdrOffset = this.localHdrOffset;
632
+ }
633
+ if (exceedsU16(this.volNumber)) {
634
+ zip64Fields.diskStart = this.volNumber;
635
+ }
636
+ const zip64CentralExtra =
637
+ Object.keys(zip64Fields).length > 0 ? buildZip64Extra(zip64Fields) : null;
638
+ if (zip64CentralExtra) {
639
+ this.usesZip64Extra = true;
640
+ }
641
+ const zip64CentralLen = zip64CentralExtra?.length ?? 0;
642
+
643
+ const extraLen =
644
+ utfLen +
645
+ sha256Len +
646
+ uidgidLen +
647
+ symlinkLen +
648
+ hardlinkLen +
649
+ (needsUnicode ? unicodePathLen : 0) +
650
+ aesLen +
651
+ neoLen +
652
+ callerExtraLen +
653
+ zip64CentralLen;
489
654
 
490
655
  // Calculate actual filename length (ASCII conversion may change length)
491
656
  const asciiName = this.filename.replace(/[^\x00-\x7E]/g, '?');
@@ -499,7 +664,7 @@ export default class ZipEntry implements ZipFileEntry {
499
664
  // Version made by - Needs to be set for NeoZip
500
665
  data.writeUInt16LE(this.isUpdated ? this.VER_MADE_BY : this.verMadeBy, CENTRAL_DIR.VER_MADE);
501
666
  // Version needed to extract
502
- const centralVerExtract = this.cmpMethod === CMP_METHOD.AES_ENCRYPT ? VER_AES_EXTRACT : VER_EXTRACT;
667
+ const centralVerExtract = this.versionNeededToExtract();
503
668
  data.writeInt16LE(this.isUpdated ? centralVerExtract : this.verMadeBy, CENTRAL_DIR.VER_EXT);
504
669
  // Encrypt, Decrypt Flags
505
670
  data.writeInt16LE(this.bitFlags >>> 0, CENTRAL_DIR.FLAGS);
@@ -509,10 +674,15 @@ export default class ZipEntry implements ZipFileEntry {
509
674
  data.writeUInt32LE(this.timeDateDOS >>> 0, CENTRAL_DIR.TIMEDATE_DOS);
510
675
  // Uncompressed file CRC-32 value (AE-2 stores 0)
511
676
  data.writeUInt32LE(this.aesVersion === 2 ? 0 : this.crc, CENTRAL_DIR.CRC);
512
- // Compressed Size
513
- data.writeUInt32LE(this.compressedSize, CENTRAL_DIR.CMP_SIZE);
514
- // Uncompressed Size
515
- data.writeUInt32LE(this.uncompressedSize, CENTRAL_DIR.UNCMP_SIZE);
677
+ // Compressed / uncompressed sizes and local offset (Zip64 sentinels when needed)
678
+ data.writeUInt32LE(
679
+ zip64Fields.compressedSize !== undefined ? ZIP64_U32 : this.compressedSize,
680
+ CENTRAL_DIR.CMP_SIZE
681
+ );
682
+ data.writeUInt32LE(
683
+ zip64Fields.uncompressedSize !== undefined ? ZIP64_U32 : this.uncompressedSize,
684
+ CENTRAL_DIR.UNCMP_SIZE
685
+ );
516
686
  // Filename Length
517
687
  data.writeUInt16LE(this.filename.length, CENTRAL_DIR.FNAME_LEN);
518
688
  // Extra Field Length
@@ -520,13 +690,19 @@ export default class ZipEntry implements ZipFileEntry {
520
690
  // File Comment Length
521
691
  data.writeUInt16LE(commentLen, CENTRAL_DIR.COMMENT_LEN);
522
692
  // Volume Number Start
523
- data.writeUInt16LE(0, CENTRAL_DIR.DISK_NUM);
693
+ data.writeUInt16LE(
694
+ zip64Fields.diskStart !== undefined ? ZIP64_U16 : this.volNumber,
695
+ CENTRAL_DIR.DISK_NUM
696
+ );
524
697
  // Internal File Attributes
525
698
  data.writeUInt16LE(this.intFileAttr >>> 0, CENTRAL_DIR.INT_FILE_ATTR);
526
699
  // External File Attributes
527
700
  data.writeUInt32LE(this.extFileAttr >>> 0, CENTRAL_DIR.EXT_FILE_ATTR);
528
701
  // Local Header Offset
529
- data.writeUInt32LE(this.localHdrOffset, CENTRAL_DIR.LOCAL_HDR_OFFSET);
702
+ data.writeUInt32LE(
703
+ zip64Fields.localHdrOffset !== undefined ? ZIP64_U32 : this.localHdrOffset,
704
+ CENTRAL_DIR.LOCAL_HDR_OFFSET
705
+ );
530
706
 
531
707
  // Write filename - use ASCII filename (replacing non-ASCII with ?)
532
708
  // This ensures compatibility with older ZIP readers
@@ -544,6 +720,12 @@ export default class ZipEntry implements ZipFileEntry {
544
720
  // Add Extra Field data after file comment
545
721
  let extraOffset = currentOffset;
546
722
 
723
+ // Zip64 extended information (sizes / offset / disk)
724
+ if (zip64CentralExtra) {
725
+ zip64CentralExtra.copy(data, extraOffset);
726
+ extraOffset += zip64CentralLen;
727
+ }
728
+
547
729
  // Add Universal Time field
548
730
  if (this.universalTime) {
549
731
  data.writeUInt16LE(HDR_ID.UNV_TIME, extraOffset); // 0x5455
@@ -630,6 +812,10 @@ export default class ZipEntry implements ZipFileEntry {
630
812
  extraOffset += NEO_CRYPTO_EXTRA_FIELD_SIZE;
631
813
  }
632
814
 
815
+ if (callerExtra && callerExtraLen > 0) {
816
+ callerExtra.copy(data, extraOffset);
817
+ }
818
+
633
819
  return data;
634
820
  }
635
821