nitro-patcher 0.1.0 → 0.2.1

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.
@@ -5,158 +5,11 @@ import {
5
5
  hashDigest,
6
6
  inflateRawSync,
7
7
  readFile
8
- } from "./chunk-GFF67HX4.js";
9
-
10
- // src/patch/zip.ts
11
- var fail = (message) => {
12
- throw new Error(`ZIP: ${message}`);
13
- };
14
- var utf8 = new TextDecoder("utf-8", { fatal: true });
15
- var cp437 = "\xC7\xFC\xE9\xE2\xE4\xE0\xE5\xE7\xEA\xEB\xE8\xEF\xEE\xEC\xC4\xC5\xC9\xE6\xC6\xF4\xF6\xF2\xFB\xF9\xFF\xD6\xDC\xA2\xA3\xA5\u20A7\u0192\xE1\xED\xF3\xFA\xF1\xD1\xAA\xBA\xBF\u2310\xAC\xBD\xBC\xA1\xAB\xBB\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255D\u255C\u255B\u2510\u2514\u2534\u252C\u251C\u2500\u253C\u255E\u255F\u255A\u2554\u2569\u2566\u2560\u2550\u256C\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256B\u256A\u2518\u250C\u2588\u2584\u258C\u2590\u2580\u03B1\xDF\u0393\u03C0\u03A3\u03C3\xB5\u03C4\u03A6\u0398\u03A9\u03B4\u221E\u03C6\u03B5\u2229\u2261\xB1\u2265\u2264\u2320\u2321\xF7\u2248\xB0\u2219\xB7\u221A\u207F\xB2\u25A0\xA0";
16
- function decodeUtf8(bytes2) {
17
- try {
18
- return utf8.decode(bytes2);
19
- } catch {
20
- return fail("invalid UTF-8 filename");
21
- }
22
- }
23
- function boundOption(value, fallback, label) {
24
- value ??= fallback;
25
- if (!Number.isSafeInteger(value) || value < 0)
26
- throw new RangeError(`${label} must be a non-negative safe integer`);
27
- return value;
28
- }
29
- function checkRange(bytes2, offset, length, end = bytes2.length) {
30
- if (offset < 0 || offset > end || length > end - offset)
31
- fail(`truncated or out-of-bounds record at ${offset} (length ${length})`);
32
- }
33
- function extras(bytes2) {
34
- const fields = /* @__PURE__ */ new Map();
35
- let offset = 0;
36
- while (offset < bytes2.length) {
37
- checkRange(bytes2, offset, 4);
38
- const id = bytes2.readUInt16LE(offset), length = bytes2.readUInt16LE(offset + 2);
39
- offset += 4;
40
- checkRange(bytes2, offset, length);
41
- if (id === 1) fail("ZIP64 archives are unsupported");
42
- fields.set(id, bytes2.subarray(offset, offset + length));
43
- offset += length;
44
- }
45
- return fields;
46
- }
47
- function filename(raw, flags, fields) {
48
- if (flags & 2048) return decodeUtf8(raw);
49
- const unicode = fields.get(28789);
50
- if (unicode && unicode.length >= 5 && unicode[0] === 1 && unicode.readUInt32LE(1) === crc32(raw))
51
- return decodeUtf8(unicode.subarray(5));
52
- let result = "";
53
- for (const byte of raw) result += byte < 128 ? String.fromCharCode(byte) : cp437[byte - 128];
54
- return result;
55
- }
56
- var readZip = (input, options = {}) => {
57
- if (!(input instanceof Uint8Array))
58
- throw new TypeError("ZIP input must be a Buffer or Uint8Array");
59
- const bytes2 = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
60
- const maxSize = boundOption(
61
- options.maxUncompressedSize,
62
- 1024 * 1024 * 1024,
63
- "maxUncompressedSize"
64
- );
65
- const maxEntries = boundOption(options.maxEntries, 1e5, "maxEntries");
66
- let eocd = -1;
67
- for (let offset2 = bytes2.length - 22; offset2 >= Math.max(0, bytes2.length - 65557); offset2--) {
68
- if (bytes2.readUInt32LE(offset2) === 101010256 && offset2 + 22 + bytes2.readUInt16LE(offset2 + 20) === bytes2.length) {
69
- eocd = offset2;
70
- break;
71
- }
72
- }
73
- if (eocd < 0) fail("end of central directory not found or truncated");
74
- const disk = bytes2.readUInt16LE(eocd + 4), centralDisk = bytes2.readUInt16LE(eocd + 6);
75
- const diskEntries = bytes2.readUInt16LE(eocd + 8), count = bytes2.readUInt16LE(eocd + 10);
76
- const centralSize = bytes2.readUInt32LE(eocd + 12), centralOffset = bytes2.readUInt32LE(eocd + 16);
77
- if (count === 65535 || diskEntries === 65535 || centralSize === 4294967295 || centralOffset === 4294967295 || eocd >= 20 && bytes2.readUInt32LE(eocd - 20) === 117853008)
78
- fail("ZIP64 archives are unsupported");
79
- if (disk || centralDisk || diskEntries !== count)
80
- fail("split or multiple-disk archives are unsupported");
81
- if (count > maxEntries) fail(`entry count exceeds limit ${maxEntries}`);
82
- checkRange(bytes2, centralOffset, centralSize, eocd);
83
- if (centralOffset + centralSize !== eocd) fail("central directory size/offset mismatch");
84
- if (count > Math.floor(centralSize / 46)) fail("entry count exceeds central directory size");
85
- const result = /* @__PURE__ */ new Map();
86
- const ranges = [];
87
- let offset = centralOffset, totalSize = 0;
88
- for (let index = 0; index < count; index++) {
89
- checkRange(bytes2, offset, 46, eocd);
90
- if (bytes2.readUInt32LE(offset) !== 33639248) fail("invalid central directory signature");
91
- const flags = bytes2.readUInt16LE(offset + 8), method = bytes2.readUInt16LE(offset + 10);
92
- const checksum = bytes2.readUInt32LE(offset + 16), compressedSize = bytes2.readUInt32LE(offset + 20), size = bytes2.readUInt32LE(offset + 24);
93
- const nameLength = bytes2.readUInt16LE(offset + 28), extraLength = bytes2.readUInt16LE(offset + 30), commentLength = bytes2.readUInt16LE(offset + 32);
94
- const startDisk = bytes2.readUInt16LE(offset + 34), localOffset = bytes2.readUInt32LE(offset + 42);
95
- if (size === 4294967295 || compressedSize === 4294967295 || localOffset === 4294967295 || startDisk === 65535)
96
- fail("ZIP64 entry is unsupported");
97
- if (startDisk) fail("split or multiple-disk entry is unsupported");
98
- if (flags & 8257) fail("encrypted entries are unsupported");
99
- if (flags & ~2062) fail("unsupported general purpose flags");
100
- if (method !== 0 && method !== 8) fail(`unsupported compression method ${method}`);
101
- if (size > maxSize - totalSize) fail(`uncompressed size exceeds limit ${maxSize}`);
102
- totalSize += size;
103
- checkRange(bytes2, offset + 46, nameLength + extraLength + commentLength, eocd);
104
- const rawName = bytes2.subarray(offset + 46, offset + 46 + nameLength);
105
- const fields = extras(
106
- bytes2.subarray(offset + 46 + nameLength, offset + 46 + nameLength + extraLength)
107
- );
108
- const name = filename(rawName, flags, fields).replaceAll("\\", "/").toLowerCase();
109
- offset += 46 + nameLength + extraLength + commentLength;
110
- checkRange(bytes2, localOffset, 30, centralOffset);
111
- if (bytes2.readUInt32LE(localOffset) !== 67324752) fail("invalid local header signature");
112
- if (bytes2.readUInt16LE(localOffset + 6) !== flags || bytes2.readUInt16LE(localOffset + 8) !== method)
113
- fail("local header flags/compression method mismatch");
114
- const localNameLength = bytes2.readUInt16LE(localOffset + 26), localExtraLength = bytes2.readUInt16LE(localOffset + 28);
115
- const dataOffset = localOffset + 30 + localNameLength + localExtraLength;
116
- checkRange(bytes2, localOffset + 30, localNameLength + localExtraLength, centralOffset);
117
- if (!rawName.equals(bytes2.subarray(localOffset + 30, localOffset + 30 + localNameLength)))
118
- fail("local filename differs from central directory");
119
- extras(bytes2.subarray(localOffset + 30 + localNameLength, dataOffset));
120
- if (!(flags & 8) && (bytes2.readUInt32LE(localOffset + 14) !== checksum || bytes2.readUInt32LE(localOffset + 18) !== compressedSize || bytes2.readUInt32LE(localOffset + 22) !== size))
121
- fail("local header CRC/size mismatch");
122
- checkRange(bytes2, dataOffset, compressedSize, centralOffset);
123
- let dataEnd = dataOffset + compressedSize;
124
- if (flags & 8) {
125
- checkRange(bytes2, dataEnd, 12, centralOffset);
126
- const matches = (at) => at + 12 <= centralOffset && bytes2.readUInt32LE(at) === checksum && bytes2.readUInt32LE(at + 4) === compressedSize && bytes2.readUInt32LE(at + 8) === size;
127
- if (matches(dataEnd)) dataEnd += 12;
128
- else if (bytes2.readUInt32LE(dataEnd) === 134695760 && matches(dataEnd + 4)) dataEnd += 16;
129
- else fail("data descriptor CRC/size mismatch");
130
- }
131
- ranges.push({ start: localOffset, end: dataEnd });
132
- const compressed = bytes2.subarray(dataOffset, dataOffset + compressedSize);
133
- let data;
134
- if (method === 0) {
135
- if (compressedSize !== size) fail("stored entry compressed/uncompressed size mismatch");
136
- data = Buffer.from(compressed);
137
- } else {
138
- try {
139
- data = inflateRawSync(compressed, { maxOutputLength: Math.max(1, size) });
140
- } catch (error) {
141
- return fail(
142
- `cannot inflate entry ${JSON.stringify(name)} within declared size: ${error instanceof Error ? error.message : String(error)}`
143
- );
144
- }
145
- }
146
- if (data.length !== size) fail(`uncompressed size mismatch for ${JSON.stringify(name)}`);
147
- if (crc32(data) !== checksum) fail(`CRC32 mismatch for ${JSON.stringify(name)}`);
148
- if (name && !name.endsWith("/")) result.set(name, data);
149
- }
150
- if (offset !== eocd) fail("central directory entry count/length mismatch");
151
- ranges.sort((a, b) => a.start - b.start);
152
- for (let i = 1; i < ranges.length; i++)
153
- if (ranges[i].start < ranges[i - 1].end) fail("overlapping local entry ranges");
154
- return result;
155
- };
8
+ } from "./chunk-GLK3K2HY.js";
156
9
 
157
10
  // src/patch/xdelta.ts
158
11
  var UINT32_MAX = 4294967295;
159
- var fail2 = (message) => {
12
+ var fail = (message) => {
160
13
  throw new Error(`VCDIFF: ${message}`);
161
14
  };
162
15
  var Reader = class {
@@ -164,14 +17,12 @@ var Reader = class {
164
17
  this.bytes = bytes2;
165
18
  this.label = label;
166
19
  }
167
- bytes;
168
- label;
169
20
  pos = 0;
170
21
  get remaining() {
171
22
  return this.bytes.length - this.pos;
172
23
  }
173
24
  byte() {
174
- if (!this.remaining) fail2(`truncated ${this.label}`);
25
+ if (!this.remaining) fail(`truncated ${this.label}`);
175
26
  return this.bytes[this.pos++];
176
27
  }
177
28
  integer() {
@@ -179,14 +30,13 @@ var Reader = class {
179
30
  for (let count = 0; count < 5; count++) {
180
31
  const byte = this.byte();
181
32
  value = value * 128 + (byte & 127);
182
- if (value > UINT32_MAX) fail2(`integer exceeds 32-bit limit in ${this.label}`);
33
+ if (value > UINT32_MAX) fail(`integer exceeds 32-bit limit in ${this.label}`);
183
34
  if (!(byte & 128)) return value;
184
35
  }
185
- return fail2(`invalid variable-length integer in ${this.label}`);
36
+ return fail(`invalid variable-length integer in ${this.label}`);
186
37
  }
187
38
  take(size) {
188
- if (size > this.remaining)
189
- fail2(`truncated ${this.label}: requested ${size} bytes, ${this.remaining} remain`);
39
+ if (size > this.remaining) fail(`truncated ${this.label}: requested ${size} bytes, ${this.remaining} remain`);
190
40
  const result = this.bytes.subarray(this.pos, this.pos + size);
191
41
  this.pos += size;
192
42
  return result;
@@ -237,27 +87,26 @@ var decodeXdelta = (source, patch, options = {}) => {
237
87
  const reader = new Reader(asBuffer(patch, "patch"));
238
88
  const maxOutput = limit(options.maxOutputSize, 1024 * 1024 * 1024, "maxOutputSize");
239
89
  const maxWindow = limit(options.maxWindowSize, 16 * 1024 * 1024, "maxWindowSize");
240
- if (!reader.take(3).equals(Buffer.from([214, 195, 196]))) fail2("invalid file signature");
241
- if (reader.byte() !== 0) fail2("unsupported version (expected 0)");
90
+ if (!reader.take(3).equals(Buffer.from([214, 195, 196]))) fail("invalid file signature");
91
+ if (reader.byte() !== 0) fail("unsupported version (expected 0)");
242
92
  const header = reader.byte();
243
- if (header & ~7) fail2("unrecognized header indicator bits");
244
- if (header & 1)
245
- fail2("secondary compression is unsupported; recreate the patch using xdelta3 -S none");
246
- if (header & 2) fail2("custom code table is unsupported");
93
+ if (header & ~7) fail("unrecognized header indicator bits");
94
+ if (header & 1) fail("secondary compression is unsupported; recreate the patch using xdelta3 -S none");
95
+ if (header & 2) fail("custom code table is unsupported");
247
96
  if (header & 4) reader.take(reader.integer());
248
97
  const windows = [];
249
98
  let total = 0;
250
99
  while (reader.remaining) {
251
100
  const indicator = reader.byte();
252
101
  if (indicator & ~7 || (indicator & 3) === 3)
253
- fail2("invalid window indicator: source and target are mutually exclusive");
102
+ fail("invalid window indicator: source and target are mutually exclusive");
254
103
  let dictionary = Buffer.alloc(0);
255
104
  if (indicator & 3) {
256
105
  const length = reader.integer();
257
106
  const offset = reader.integer();
258
107
  const available = indicator & 1 ? sourceBytes.length : total;
259
108
  if (offset > available || length > available - offset)
260
- fail2("source segment exceeds available source/target bounds");
109
+ fail("source segment exceeds available source/target bounds");
261
110
  if (indicator & 1) dictionary = sourceBytes.subarray(offset, offset + length);
262
111
  else {
263
112
  dictionary = Buffer.allocUnsafe(length);
@@ -273,20 +122,18 @@ var decodeXdelta = (source, patch, options = {}) => {
273
122
  const deltaLength = reader.integer();
274
123
  const delta = new Reader(reader.take(deltaLength), "delta window");
275
124
  const targetLength = delta.integer();
276
- if (targetLength > maxWindow)
277
- fail2(`window size ${targetLength} exceeds maximum limit ${maxWindow}`);
278
- if (targetLength > maxOutput - total) fail2(`output size exceeds maximum limit ${maxOutput}`);
279
- if (dictionary.length + targetLength > UINT32_MAX)
280
- fail2("window address space exceeds 32-bit limit");
125
+ if (targetLength > maxWindow) fail(`window size ${targetLength} exceeds maximum limit ${maxWindow}`);
126
+ if (targetLength > maxOutput - total) fail(`output size exceeds maximum limit ${maxOutput}`);
127
+ if (dictionary.length + targetLength > UINT32_MAX) fail("window address space exceeds 32-bit limit");
281
128
  const compressed = delta.byte();
282
- if (compressed & ~7) fail2("unrecognized delta indicator bits");
283
- if (compressed) fail2("secondary compression in delta sections is unsupported");
129
+ if (compressed & ~7) fail("unrecognized delta indicator bits");
130
+ if (compressed) fail("secondary compression in delta sections is unsupported");
284
131
  const dataLength = delta.integer(), instructionLength = delta.integer(), addressLength = delta.integer();
285
132
  const checksum = indicator & 4 ? delta.take(4).readUInt32BE() : null;
286
133
  const data = new Reader(delta.take(dataLength), "data section");
287
134
  const instructions = new Reader(delta.take(instructionLength), "instruction section");
288
135
  const addresses = new Reader(delta.take(addressLength), "address section");
289
- if (delta.remaining) fail2("delta encoding length does not match section lengths");
136
+ if (delta.remaining) fail("delta encoding length does not match section lengths");
290
137
  const target = Buffer.allocUnsafe(targetLength);
291
138
  const near = new Uint32Array(4), same = new Uint32Array(768);
292
139
  let nearIndex = 0, position = 0;
@@ -294,7 +141,7 @@ var decodeXdelta = (source, patch, options = {}) => {
294
141
  const operations = table[instructions.byte()];
295
142
  for (const { type, size: fixedSize, mode } of operations) {
296
143
  const size = fixedSize || instructions.integer();
297
- if (size > targetLength - position) fail2(`${type} size exceeds target window length`);
144
+ if (size > targetLength - position) fail(`${type} size exceeds target window length`);
298
145
  if (type === "ADD") {
299
146
  data.take(size).copy(target, position);
300
147
  } else if (type === "RUN") {
@@ -306,8 +153,7 @@ var decodeXdelta = (source, patch, options = {}) => {
306
153
  else if (mode === 1) address = here - addresses.integer();
307
154
  else if (mode < 6) address = near[mode - 2] + addresses.integer();
308
155
  else address = same[(mode - 6) * 256 + addresses.byte()];
309
- if (address < 0 || address >= here)
310
- fail2(`COPY address ${address} is outside decoded address space ${here}`);
156
+ if (address < 0 || address >= here) fail(`COPY address ${address} is outside decoded address space ${here}`);
311
157
  near[nearIndex] = address;
312
158
  nearIndex = (nearIndex + 1) % near.length;
313
159
  same[address % same.length] = address;
@@ -319,7 +165,7 @@ var decodeXdelta = (source, patch, options = {}) => {
319
165
  const from = Math.max(0, address - dictionary.length);
320
166
  while (copied < size) {
321
167
  const count = Math.min(size - copied, position + copied - from);
322
- if (count <= 0) fail2("COPY references unavailable target bytes");
168
+ if (count <= 0) fail("COPY references unavailable target bytes");
323
169
  target.copy(target, position + copied, from, from + count);
324
170
  copied += count;
325
171
  }
@@ -327,31 +173,236 @@ var decodeXdelta = (source, patch, options = {}) => {
327
173
  position += size;
328
174
  }
329
175
  }
330
- if (position !== targetLength)
331
- fail2(`target window length mismatch: decoded ${position}, expected ${targetLength}`);
332
- if (data.remaining || addresses.remaining) fail2("unused bytes in data/address section");
333
- if (checksum !== null && adler32(target) !== checksum) fail2("Adler32 checksum mismatch");
176
+ if (position !== targetLength) fail(`target window length mismatch: decoded ${position}, expected ${targetLength}`);
177
+ if (data.remaining || addresses.remaining) fail("unused bytes in data/address section");
178
+ if (checksum !== null && adler32(target) !== checksum) fail("Adler32 checksum mismatch");
334
179
  windows.push(target);
335
180
  total += targetLength;
336
181
  }
337
182
  return Buffer.concat(windows, total);
338
183
  };
339
184
 
185
+ // src/patch/zip.ts
186
+ var fail2 = (message) => {
187
+ throw new Error(`ZIP: ${message}`);
188
+ };
189
+ var utf8 = new TextDecoder("utf-8", { fatal: true });
190
+ var cp437 = "\xC7\xFC\xE9\xE2\xE4\xE0\xE5\xE7\xEA\xEB\xE8\xEF\xEE\xEC\xC4\xC5\xC9\xE6\xC6\xF4\xF6\xF2\xFB\xF9\xFF\xD6\xDC\xA2\xA3\xA5\u20A7\u0192\xE1\xED\xF3\xFA\xF1\xD1\xAA\xBA\xBF\u2310\xAC\xBD\xBC\xA1\xAB\xBB\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255D\u255C\u255B\u2510\u2514\u2534\u252C\u251C\u2500\u253C\u255E\u255F\u255A\u2554\u2569\u2566\u2560\u2550\u256C\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256B\u256A\u2518\u250C\u2588\u2584\u258C\u2590\u2580\u03B1\xDF\u0393\u03C0\u03A3\u03C3\xB5\u03C4\u03A6\u0398\u03A9\u03B4\u221E\u03C6\u03B5\u2229\u2261\xB1\u2265\u2264\u2320\u2321\xF7\u2248\xB0\u2219\xB7\u221A\u207F\xB2\u25A0\xA0";
191
+ function decodeUtf8(bytes2) {
192
+ try {
193
+ return utf8.decode(bytes2);
194
+ } catch {
195
+ return fail2("invalid UTF-8 filename");
196
+ }
197
+ }
198
+ function boundOption(value, fallback, label) {
199
+ value ??= fallback;
200
+ if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${label} must be a non-negative safe integer`);
201
+ return value;
202
+ }
203
+ function checkRange(bytes2, offset, length, end = bytes2.length) {
204
+ if (offset < 0 || offset > end || length > end - offset)
205
+ fail2(`truncated or out-of-bounds record at ${offset} (length ${length})`);
206
+ }
207
+ function extras(bytes2) {
208
+ const fields = /* @__PURE__ */ new Map();
209
+ let offset = 0;
210
+ while (offset < bytes2.length) {
211
+ checkRange(bytes2, offset, 4);
212
+ const id = bytes2.readUInt16LE(offset), length = bytes2.readUInt16LE(offset + 2);
213
+ offset += 4;
214
+ checkRange(bytes2, offset, length);
215
+ if (id === 1) fail2("ZIP64 archives are unsupported");
216
+ fields.set(id, bytes2.subarray(offset, offset + length));
217
+ offset += length;
218
+ }
219
+ return fields;
220
+ }
221
+ function filename(raw, flags, fields) {
222
+ if (flags & 2048) return decodeUtf8(raw);
223
+ const unicode = fields.get(28789);
224
+ if (unicode && unicode.length >= 5 && unicode[0] === 1 && unicode.readUInt32LE(1) === crc32(raw))
225
+ return decodeUtf8(unicode.subarray(5));
226
+ let result = "";
227
+ for (const byte of raw) result += byte < 128 ? String.fromCharCode(byte) : cp437[byte - 128];
228
+ return result;
229
+ }
230
+ var readZip = (input, options = {}) => {
231
+ if (!(input instanceof Uint8Array)) throw new TypeError("ZIP input must be a Buffer or Uint8Array");
232
+ const bytes2 = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
233
+ const maxSize = boundOption(options.maxUncompressedSize, 1024 * 1024 * 1024, "maxUncompressedSize");
234
+ const maxEntries = boundOption(options.maxEntries, 1e5, "maxEntries");
235
+ let eocd = -1;
236
+ for (let offset2 = bytes2.length - 22; offset2 >= Math.max(0, bytes2.length - 65557); offset2--) {
237
+ if (bytes2.readUInt32LE(offset2) === 101010256 && offset2 + 22 + bytes2.readUInt16LE(offset2 + 20) === bytes2.length) {
238
+ eocd = offset2;
239
+ break;
240
+ }
241
+ }
242
+ if (eocd < 0) fail2("end of central directory not found or truncated");
243
+ const disk = bytes2.readUInt16LE(eocd + 4), centralDisk = bytes2.readUInt16LE(eocd + 6);
244
+ const diskEntries = bytes2.readUInt16LE(eocd + 8), count = bytes2.readUInt16LE(eocd + 10);
245
+ const centralSize = bytes2.readUInt32LE(eocd + 12), centralOffset = bytes2.readUInt32LE(eocd + 16);
246
+ if (count === 65535 || diskEntries === 65535 || centralSize === 4294967295 || centralOffset === 4294967295 || eocd >= 20 && bytes2.readUInt32LE(eocd - 20) === 117853008)
247
+ fail2("ZIP64 archives are unsupported");
248
+ if (disk || centralDisk || diskEntries !== count) fail2("split or multiple-disk archives are unsupported");
249
+ if (count > maxEntries) fail2(`entry count exceeds limit ${maxEntries}`);
250
+ checkRange(bytes2, centralOffset, centralSize, eocd);
251
+ if (centralOffset + centralSize !== eocd) fail2("central directory size/offset mismatch");
252
+ if (count > Math.floor(centralSize / 46)) fail2("entry count exceeds central directory size");
253
+ const result = /* @__PURE__ */ new Map();
254
+ const ranges = [];
255
+ let offset = centralOffset, totalSize = 0;
256
+ for (let index = 0; index < count; index++) {
257
+ checkRange(bytes2, offset, 46, eocd);
258
+ if (bytes2.readUInt32LE(offset) !== 33639248) fail2("invalid central directory signature");
259
+ const flags = bytes2.readUInt16LE(offset + 8), method = bytes2.readUInt16LE(offset + 10);
260
+ const checksum = bytes2.readUInt32LE(offset + 16), compressedSize = bytes2.readUInt32LE(offset + 20), size = bytes2.readUInt32LE(offset + 24);
261
+ const nameLength = bytes2.readUInt16LE(offset + 28), extraLength = bytes2.readUInt16LE(offset + 30), commentLength = bytes2.readUInt16LE(offset + 32);
262
+ const startDisk = bytes2.readUInt16LE(offset + 34), localOffset = bytes2.readUInt32LE(offset + 42);
263
+ if (size === 4294967295 || compressedSize === 4294967295 || localOffset === 4294967295 || startDisk === 65535)
264
+ fail2("ZIP64 entry is unsupported");
265
+ if (startDisk) fail2("split or multiple-disk entry is unsupported");
266
+ if (flags & 8257) fail2("encrypted entries are unsupported");
267
+ if (flags & ~2062) fail2("unsupported general purpose flags");
268
+ if (method !== 0 && method !== 8) fail2(`unsupported compression method ${method}`);
269
+ if (size > maxSize - totalSize) fail2(`uncompressed size exceeds limit ${maxSize}`);
270
+ totalSize += size;
271
+ checkRange(bytes2, offset + 46, nameLength + extraLength + commentLength, eocd);
272
+ const rawName = bytes2.subarray(offset + 46, offset + 46 + nameLength);
273
+ const fields = extras(bytes2.subarray(offset + 46 + nameLength, offset + 46 + nameLength + extraLength));
274
+ const name = filename(rawName, flags, fields).replaceAll("\\", "/").toLowerCase();
275
+ offset += 46 + nameLength + extraLength + commentLength;
276
+ checkRange(bytes2, localOffset, 30, centralOffset);
277
+ if (bytes2.readUInt32LE(localOffset) !== 67324752) fail2("invalid local header signature");
278
+ if (bytes2.readUInt16LE(localOffset + 6) !== flags || bytes2.readUInt16LE(localOffset + 8) !== method)
279
+ fail2("local header flags/compression method mismatch");
280
+ const localNameLength = bytes2.readUInt16LE(localOffset + 26), localExtraLength = bytes2.readUInt16LE(localOffset + 28);
281
+ const dataOffset = localOffset + 30 + localNameLength + localExtraLength;
282
+ checkRange(bytes2, localOffset + 30, localNameLength + localExtraLength, centralOffset);
283
+ if (!rawName.equals(bytes2.subarray(localOffset + 30, localOffset + 30 + localNameLength)))
284
+ fail2("local filename differs from central directory");
285
+ extras(bytes2.subarray(localOffset + 30 + localNameLength, dataOffset));
286
+ if (!(flags & 8) && (bytes2.readUInt32LE(localOffset + 14) !== checksum || bytes2.readUInt32LE(localOffset + 18) !== compressedSize || bytes2.readUInt32LE(localOffset + 22) !== size))
287
+ fail2("local header CRC/size mismatch");
288
+ checkRange(bytes2, dataOffset, compressedSize, centralOffset);
289
+ let dataEnd = dataOffset + compressedSize;
290
+ if (flags & 8) {
291
+ checkRange(bytes2, dataEnd, 12, centralOffset);
292
+ const matches = (at) => at + 12 <= centralOffset && bytes2.readUInt32LE(at) === checksum && bytes2.readUInt32LE(at + 4) === compressedSize && bytes2.readUInt32LE(at + 8) === size;
293
+ if (matches(dataEnd)) dataEnd += 12;
294
+ else if (bytes2.readUInt32LE(dataEnd) === 134695760 && matches(dataEnd + 4)) dataEnd += 16;
295
+ else fail2("data descriptor CRC/size mismatch");
296
+ }
297
+ ranges.push({ start: localOffset, end: dataEnd });
298
+ const compressed = bytes2.subarray(dataOffset, dataOffset + compressedSize);
299
+ let data;
300
+ if (method === 0) {
301
+ if (compressedSize !== size) fail2("stored entry compressed/uncompressed size mismatch");
302
+ data = Buffer.from(compressed);
303
+ } else {
304
+ try {
305
+ data = inflateRawSync(compressed, { maxOutputLength: Math.max(1, size) });
306
+ } catch (error) {
307
+ return fail2(
308
+ `cannot inflate entry ${JSON.stringify(name)} within declared size: ${error instanceof Error ? error.message : String(error)}`
309
+ );
310
+ }
311
+ }
312
+ if (data.length !== size) fail2(`uncompressed size mismatch for ${JSON.stringify(name)}`);
313
+ if (crc32(data) !== checksum) fail2(`CRC32 mismatch for ${JSON.stringify(name)}`);
314
+ if (name && !name.endsWith("/")) result.set(name, data);
315
+ }
316
+ if (offset !== eocd) fail2("central directory entry count/length mismatch");
317
+ ranges.sort((a, b) => a.start - b.start);
318
+ for (let i = 1; i < ranges.length; i++)
319
+ if (ranges[i].start < ranges[i - 1].end) fail2("overlapping local entry ranges");
320
+ return result;
321
+ };
322
+ var extractZipEntry = (input, entryName, options = {}) => {
323
+ if (typeof entryName !== "string" || !entryName) throw new TypeError("ZIP entry name must be a non-empty string");
324
+ const normalized = entryName.replaceAll("\\", "/").toLowerCase();
325
+ if (normalized.endsWith("/")) fail2(`entry ${JSON.stringify(entryName)} is a directory`);
326
+ const entry = readZip(input, options).get(normalized);
327
+ if (entry === void 0) return fail2(`entry ${JSON.stringify(entryName)} not found`);
328
+ return entry;
329
+ };
330
+
340
331
  // src/nitro-patch-helper.ts
341
332
  var PatchReturnValue = Object.freeze({
342
333
  SUCCESS: "SUCCESS",
343
334
  MD5_MISMATCH: "MD5_MISMATCH"
344
335
  });
345
336
  var md5 = (data) => hashDigest("md5", data).toString("hex");
337
+ var invalidMetadata = (message) => {
338
+ console.warn(`\u5FFD\u7565 metadata.json\uFF1A${message}`);
339
+ return null;
340
+ };
341
+ var invalidMetadataField = (field, message) => {
342
+ console.warn(`\u5FFD\u7565 metadata.json \u7684 ${field}\uFF1A${message}`);
343
+ };
344
+ var readMetadata = (files) => {
345
+ const file = files.get("metadata.json");
346
+ if (!file) return null;
347
+ let metadata;
348
+ try {
349
+ metadata = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(file));
350
+ } catch (error) {
351
+ return invalidMetadata(
352
+ `\u683C\u5F0F\u9519\u8BEF\uFF0C\u53EF\u80FD\u662F\u56E0\u4E3A\u8865\u4E01\u5305\u5DF2\u635F\u574F\uFF1A${error instanceof Error ? error.message : String(error)}`
353
+ );
354
+ }
355
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
356
+ return invalidMetadata("\u6839\u8282\u70B9\u5FC5\u987B\u662F\u5BF9\u8C61\u3002");
357
+ const value = metadata;
358
+ const result = {};
359
+ for (const field of ["id", "author", "name", "homepage", "version"]) {
360
+ const fieldValue = value[field];
361
+ if (fieldValue === void 0) continue;
362
+ if (typeof fieldValue === "string") result[field] = fieldValue;
363
+ else invalidMetadataField(field, "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002");
364
+ }
365
+ if (value.isBeta === void 0) return result;
366
+ if (typeof value.isBeta === "boolean") result.isBeta = value.isBeta;
367
+ else invalidMetadataField("isBeta", "\u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002");
368
+ return result;
369
+ };
370
+ var readReadme = (files) => {
371
+ for (const [filename2, format] of [
372
+ ["README.md", "markdown"],
373
+ ["README.txt", "plaintext"]
374
+ ]) {
375
+ const file = files.get(filename2.toLowerCase());
376
+ if (!file) continue;
377
+ try {
378
+ return {
379
+ format,
380
+ content: new TextDecoder("utf-8", { fatal: true }).decode(file)
381
+ };
382
+ } catch (error) {
383
+ console.warn(
384
+ `\u5FFD\u7565 ${filename2}\uFF1A\u4E0D\u662F\u6709\u6548\u7684 UTF-8 \u6587\u672C\uFF1A${error instanceof Error ? error.message : String(error)}`
385
+ );
386
+ }
387
+ }
388
+ return null;
389
+ };
390
+ var readPatchMetadata = (patch, options = {}) => readMetadata(readZip(patch, options));
391
+ var readPatchReadme = (patch, options = {}) => readReadme(readZip(patch, options));
392
+ var readPatchInfo = (patch, options = {}) => {
393
+ const files = readZip(patch, options);
394
+ return { metadata: readMetadata(files), readme: readReadme(files) };
395
+ };
396
+ var extractZipEntry2 = (archive, entryName, options = {}) => extractZipEntry(archive, entryName, options);
397
+ var readPatchMetadataFile = async (patchPath, options = {}) => readPatchMetadata(await readFile(patchPath), options);
398
+ var readPatchReadmeFile = async (patchPath, options = {}) => readPatchReadme(await readFile(patchPath), options);
399
+ var readPatchInfoFile = async (patchPath, options = {}) => readPatchInfo(await readFile(patchPath), options);
346
400
  var patchBuffer = (original, patch, options = {}) => {
347
401
  const input = bytes(original, "original ROM");
348
402
  const patches = readZip(patch, options);
349
403
  const inputMd5 = md5(input);
350
404
  const preprocessing = patches.get(`preprocessing/${inputMd5}.xdelta`);
351
- const rom = new NDSFile(
352
- preprocessing ? decodeXdelta(input, preprocessing, options) : input,
353
- options
354
- );
405
+ const rom = new NDSFile(preprocessing ? decodeXdelta(input, preprocessing, options) : input, options);
355
406
  let matched = true;
356
407
  const md5File = patches.get("md5.txt");
357
408
  if (md5File) {
@@ -384,13 +435,30 @@ var patchIt = async (originalPath, patchPath, options = {}) => {
384
435
  const [original, patch] = await Promise.all([readFile(originalPath), readFile(patchPath)]);
385
436
  return patchBuffer(original, patch, options);
386
437
  };
387
- var PatchHelper = Object.freeze({ patchBuffer, patchIt });
438
+ var PatchHelper = Object.freeze({
439
+ extractZipEntry: extractZipEntry2,
440
+ patchBuffer,
441
+ patchIt,
442
+ readPatchInfo,
443
+ readPatchInfoFile,
444
+ readPatchMetadata,
445
+ readPatchMetadataFile,
446
+ readPatchReadme,
447
+ readPatchReadmeFile
448
+ });
388
449
 
389
450
  export {
390
451
  decodeXdelta,
391
452
  PatchReturnValue,
453
+ readPatchMetadata,
454
+ readPatchReadme,
455
+ readPatchInfo,
456
+ extractZipEntry2 as extractZipEntry,
457
+ readPatchMetadataFile,
458
+ readPatchReadmeFile,
459
+ readPatchInfoFile,
392
460
  patchBuffer,
393
461
  patchIt,
394
462
  PatchHelper
395
463
  };
396
- //# sourceMappingURL=chunk-L7ZM4LLP.js.map
464
+ //# sourceMappingURL=chunk-IGURDH5L.js.map