nitro-patcher 0.1.0 → 0.2.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.
- package/README.md +2 -0
- package/dist/browser.d.ts +25 -0
- package/dist/browser.js +82 -3
- package/dist/browser.js.map +3 -3
- package/dist/{chunk-L7ZM4LLP.js → chunk-ZY3OSZCA.js} +76 -2
- package/dist/{chunk-L7ZM4LLP.js.map → chunk-ZY3OSZCA.js.map} +1 -1
- package/dist/cli.js +104 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -2
- package/dist/nitro-patch-helper.d.ts +35 -1
- package/dist/nitro-patch-helper.js +15 -3
- package/package.json +4 -1
- package/src/browser.ts +43 -2
- package/src/cli.ts +116 -20
- package/src/nitro-patch-helper.ts +120 -1
|
@@ -343,6 +343,65 @@ var PatchReturnValue = Object.freeze({
|
|
|
343
343
|
MD5_MISMATCH: "MD5_MISMATCH"
|
|
344
344
|
});
|
|
345
345
|
var md5 = (data) => hashDigest("md5", data).toString("hex");
|
|
346
|
+
var invalidMetadata = (message) => {
|
|
347
|
+
console.warn(`\u5FFD\u7565 metadata.json\uFF1A${message}`);
|
|
348
|
+
return null;
|
|
349
|
+
};
|
|
350
|
+
var invalidMetadataField = (field, message) => {
|
|
351
|
+
console.warn(`\u5FFD\u7565 metadata.json \u7684 ${field}\uFF1A${message}`);
|
|
352
|
+
};
|
|
353
|
+
var readMetadata = (files) => {
|
|
354
|
+
const file = files.get("metadata.json");
|
|
355
|
+
if (!file) return null;
|
|
356
|
+
let metadata;
|
|
357
|
+
try {
|
|
358
|
+
metadata = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(file));
|
|
359
|
+
} catch (error) {
|
|
360
|
+
return invalidMetadata(
|
|
361
|
+
`\u683C\u5F0F\u9519\u8BEF\uFF0C\u53EF\u80FD\u662F\u56E0\u4E3A\u8865\u4E01\u5305\u5DF2\u635F\u574F\uFF1A${error instanceof Error ? error.message : String(error)}`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
365
|
+
return invalidMetadata("\u6839\u8282\u70B9\u5FC5\u987B\u662F\u5BF9\u8C61\u3002");
|
|
366
|
+
const value = metadata;
|
|
367
|
+
const result = {};
|
|
368
|
+
for (const field of ["id", "author", "name", "homepage", "version"]) {
|
|
369
|
+
if (typeof value[field] === "string") result[field] = value[field];
|
|
370
|
+
else invalidMetadataField(field, "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002");
|
|
371
|
+
}
|
|
372
|
+
if (typeof value.isBeta === "boolean") result.isBeta = value.isBeta;
|
|
373
|
+
else invalidMetadataField("isBeta", "\u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002");
|
|
374
|
+
return result;
|
|
375
|
+
};
|
|
376
|
+
var readReadme = (files) => {
|
|
377
|
+
for (const [filename2, format] of [
|
|
378
|
+
["README.md", "markdown"],
|
|
379
|
+
["README.txt", "plaintext"]
|
|
380
|
+
]) {
|
|
381
|
+
const file = files.get(filename2.toLowerCase());
|
|
382
|
+
if (!file) continue;
|
|
383
|
+
try {
|
|
384
|
+
return {
|
|
385
|
+
format,
|
|
386
|
+
content: new TextDecoder("utf-8", { fatal: true }).decode(file)
|
|
387
|
+
};
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.warn(
|
|
390
|
+
`\u5FFD\u7565 ${filename2}\uFF1A\u4E0D\u662F\u6709\u6548\u7684 UTF-8 \u6587\u672C\uFF1A${error instanceof Error ? error.message : String(error)}`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return null;
|
|
395
|
+
};
|
|
396
|
+
var readPatchMetadata = (patch, options = {}) => readMetadata(readZip(patch, options));
|
|
397
|
+
var readPatchReadme = (patch, options = {}) => readReadme(readZip(patch, options));
|
|
398
|
+
var readPatchInfo = (patch, options = {}) => {
|
|
399
|
+
const files = readZip(patch, options);
|
|
400
|
+
return { metadata: readMetadata(files), readme: readReadme(files) };
|
|
401
|
+
};
|
|
402
|
+
var readPatchMetadataFile = async (patchPath, options = {}) => readPatchMetadata(await readFile(patchPath), options);
|
|
403
|
+
var readPatchReadmeFile = async (patchPath, options = {}) => readPatchReadme(await readFile(patchPath), options);
|
|
404
|
+
var readPatchInfoFile = async (patchPath, options = {}) => readPatchInfo(await readFile(patchPath), options);
|
|
346
405
|
var patchBuffer = (original, patch, options = {}) => {
|
|
347
406
|
const input = bytes(original, "original ROM");
|
|
348
407
|
const patches = readZip(patch, options);
|
|
@@ -384,13 +443,28 @@ var patchIt = async (originalPath, patchPath, options = {}) => {
|
|
|
384
443
|
const [original, patch] = await Promise.all([readFile(originalPath), readFile(patchPath)]);
|
|
385
444
|
return patchBuffer(original, patch, options);
|
|
386
445
|
};
|
|
387
|
-
var PatchHelper = Object.freeze({
|
|
446
|
+
var PatchHelper = Object.freeze({
|
|
447
|
+
patchBuffer,
|
|
448
|
+
patchIt,
|
|
449
|
+
readPatchInfo,
|
|
450
|
+
readPatchInfoFile,
|
|
451
|
+
readPatchMetadata,
|
|
452
|
+
readPatchMetadataFile,
|
|
453
|
+
readPatchReadme,
|
|
454
|
+
readPatchReadmeFile
|
|
455
|
+
});
|
|
388
456
|
|
|
389
457
|
export {
|
|
390
458
|
decodeXdelta,
|
|
391
459
|
PatchReturnValue,
|
|
460
|
+
readPatchMetadata,
|
|
461
|
+
readPatchReadme,
|
|
462
|
+
readPatchInfo,
|
|
463
|
+
readPatchMetadataFile,
|
|
464
|
+
readPatchReadmeFile,
|
|
465
|
+
readPatchInfoFile,
|
|
392
466
|
patchBuffer,
|
|
393
467
|
patchIt,
|
|
394
468
|
PatchHelper
|
|
395
469
|
};
|
|
396
|
-
//# sourceMappingURL=chunk-
|
|
470
|
+
//# sourceMappingURL=chunk-ZY3OSZCA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/patch/zip.ts","../src/patch/xdelta.ts","../src/nitro-patch-helper.ts"],"sourcesContent":["import { inflateRawSync } from '#nitro-runtime';\nimport { crc32 } from '../nitro/crc';\n\n/** Resource limits apply to every central-directory entry, including duplicates. */\nexport interface ZipOptions {\n maxUncompressedSize?: number;\n maxEntries?: number;\n}\n\nconst fail = (message: string): never => {\n throw new Error(`ZIP: ${message}`);\n};\nconst utf8 = new TextDecoder('utf-8', { fatal: true });\n// IBM code page 437, byte values 0x80..0xff. ASCII bytes retain their literal values.\nconst cp437 =\n 'ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ';\n\nfunction decodeUtf8(bytes: Uint8Array): string {\n try {\n return utf8.decode(bytes);\n } catch {\n return fail('invalid UTF-8 filename');\n }\n}\nfunction boundOption(value: number | undefined, fallback: number, label: string): number {\n value ??= fallback;\n if (!Number.isSafeInteger(value) || value < 0)\n throw new RangeError(`${label} must be a non-negative safe integer`);\n return value;\n}\nfunction checkRange(bytes: Buffer, offset: number, length: number, end = bytes.length): void {\n if (offset < 0 || offset > end || length > end - offset)\n fail(`truncated or out-of-bounds record at ${offset} (length ${length})`);\n}\nfunction extras(bytes: Buffer): Map<number, Buffer> {\n const fields = new Map<number, Buffer>();\n let offset = 0;\n while (offset < bytes.length) {\n checkRange(bytes, offset, 4);\n const id = bytes.readUInt16LE(offset),\n length = bytes.readUInt16LE(offset + 2);\n offset += 4;\n checkRange(bytes, offset, length);\n if (id === 1) fail('ZIP64 archives are unsupported');\n fields.set(id, bytes.subarray(offset, offset + length));\n offset += length;\n }\n return fields;\n}\nfunction filename(raw: Buffer, flags: number, fields: Map<number, Buffer>): string {\n if (flags & 0x800) return decodeUtf8(raw);\n const unicode = fields.get(0x7075);\n if (unicode && unicode.length >= 5 && unicode[0] === 1 && unicode.readUInt32LE(1) === crc32(raw))\n return decodeUtf8(unicode.subarray(5));\n let result = '';\n for (const byte of raw) result += byte < 128 ? String.fromCharCode(byte) : cp437[byte - 128]!;\n return result;\n}\n\n/**\n * Read stored/deflated, single-disk ZIPs entirely in memory. Central-directory\n * sizes support streamed entries with data descriptors. Paths match PatchHelper:\n * backslashes become slashes, names are lowercase, and the final duplicate wins.\n * ZIP64, encryption, split archives and other compression methods fail explicitly.\n * Format reference: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT\n */\nexport const readZip = (input: Uint8Array, options: ZipOptions = {}): Map<string, Buffer> => {\n if (!(input instanceof Uint8Array))\n throw new TypeError('ZIP input must be a Buffer or Uint8Array');\n const bytes = Buffer.from(input.buffer, input.byteOffset, input.byteLength);\n const maxSize = boundOption(\n options.maxUncompressedSize,\n 1024 * 1024 * 1024,\n 'maxUncompressedSize',\n );\n const maxEntries = boundOption(options.maxEntries, 100000, 'maxEntries');\n let eocd = -1;\n for (let offset = bytes.length - 22; offset >= Math.max(0, bytes.length - 65557); offset--) {\n if (\n bytes.readUInt32LE(offset) === 0x06054b50 &&\n offset + 22 + bytes.readUInt16LE(offset + 20) === bytes.length\n ) {\n eocd = offset;\n break;\n }\n }\n if (eocd < 0) fail('end of central directory not found or truncated');\n const disk = bytes.readUInt16LE(eocd + 4),\n centralDisk = bytes.readUInt16LE(eocd + 6);\n const diskEntries = bytes.readUInt16LE(eocd + 8),\n count = bytes.readUInt16LE(eocd + 10);\n const centralSize = bytes.readUInt32LE(eocd + 12),\n centralOffset = bytes.readUInt32LE(eocd + 16);\n if (\n count === 0xffff ||\n diskEntries === 0xffff ||\n centralSize === 0xffffffff ||\n centralOffset === 0xffffffff ||\n (eocd >= 20 && bytes.readUInt32LE(eocd - 20) === 0x07064b50)\n )\n fail('ZIP64 archives are unsupported');\n if (disk || centralDisk || diskEntries !== count)\n fail('split or multiple-disk archives are unsupported');\n if (count > maxEntries) fail(`entry count exceeds limit ${maxEntries}`);\n checkRange(bytes, centralOffset, centralSize, eocd);\n if (centralOffset + centralSize !== eocd) fail('central directory size/offset mismatch');\n if (count > Math.floor(centralSize / 46)) fail('entry count exceeds central directory size');\n const result = new Map<string, Buffer>();\n const ranges: Array<{ start: number; end: number }> = [];\n let offset = centralOffset,\n totalSize = 0;\n for (let index = 0; index < count; index++) {\n checkRange(bytes, offset, 46, eocd);\n if (bytes.readUInt32LE(offset) !== 0x02014b50) fail('invalid central directory signature');\n const flags = bytes.readUInt16LE(offset + 8),\n method = bytes.readUInt16LE(offset + 10);\n const checksum = bytes.readUInt32LE(offset + 16),\n compressedSize = bytes.readUInt32LE(offset + 20),\n size = bytes.readUInt32LE(offset + 24);\n const nameLength = bytes.readUInt16LE(offset + 28),\n extraLength = bytes.readUInt16LE(offset + 30),\n commentLength = bytes.readUInt16LE(offset + 32);\n const startDisk = bytes.readUInt16LE(offset + 34),\n localOffset = bytes.readUInt32LE(offset + 42);\n if (\n size === 0xffffffff ||\n compressedSize === 0xffffffff ||\n localOffset === 0xffffffff ||\n startDisk === 0xffff\n )\n fail('ZIP64 entry is unsupported');\n if (startDisk) fail('split or multiple-disk entry is unsupported');\n if (flags & 0x2041) fail('encrypted entries are unsupported');\n if (flags & ~0x80e) fail('unsupported general purpose flags');\n if (method !== 0 && method !== 8) fail(`unsupported compression method ${method}`);\n if (size > maxSize - totalSize) fail(`uncompressed size exceeds limit ${maxSize}`);\n totalSize += size;\n checkRange(bytes, offset + 46, nameLength + extraLength + commentLength, eocd);\n const rawName = bytes.subarray(offset + 46, offset + 46 + nameLength);\n const fields = extras(\n bytes.subarray(offset + 46 + nameLength, offset + 46 + nameLength + extraLength),\n );\n const name = filename(rawName, flags, fields).replaceAll('\\\\', '/').toLowerCase();\n offset += 46 + nameLength + extraLength + commentLength;\n\n checkRange(bytes, localOffset, 30, centralOffset);\n if (bytes.readUInt32LE(localOffset) !== 0x04034b50) fail('invalid local header signature');\n if (\n bytes.readUInt16LE(localOffset + 6) !== flags ||\n bytes.readUInt16LE(localOffset + 8) !== method\n )\n fail('local header flags/compression method mismatch');\n const localNameLength = bytes.readUInt16LE(localOffset + 26),\n localExtraLength = bytes.readUInt16LE(localOffset + 28);\n const dataOffset = localOffset + 30 + localNameLength + localExtraLength;\n checkRange(bytes, localOffset + 30, localNameLength + localExtraLength, centralOffset);\n if (!rawName.equals(bytes.subarray(localOffset + 30, localOffset + 30 + localNameLength)))\n fail('local filename differs from central directory');\n extras(bytes.subarray(localOffset + 30 + localNameLength, dataOffset));\n if (\n !(flags & 8) &&\n (bytes.readUInt32LE(localOffset + 14) !== checksum ||\n bytes.readUInt32LE(localOffset + 18) !== compressedSize ||\n bytes.readUInt32LE(localOffset + 22) !== size)\n )\n fail('local header CRC/size mismatch');\n checkRange(bytes, dataOffset, compressedSize, centralOffset);\n let dataEnd = dataOffset + compressedSize;\n if (flags & 8) {\n // A descriptor may omit its signature. Use the CRC and both sizes to\n // disambiguate the rare case of an unsigned CRC equal to the signature.\n checkRange(bytes, dataEnd, 12, centralOffset);\n const matches = (at: number): boolean =>\n at + 12 <= centralOffset &&\n bytes.readUInt32LE(at) === checksum &&\n bytes.readUInt32LE(at + 4) === compressedSize &&\n bytes.readUInt32LE(at + 8) === size;\n if (matches(dataEnd)) dataEnd += 12;\n else if (bytes.readUInt32LE(dataEnd) === 0x08074b50 && matches(dataEnd + 4)) dataEnd += 16;\n else fail('data descriptor CRC/size mismatch');\n }\n ranges.push({ start: localOffset, end: dataEnd });\n const compressed = bytes.subarray(dataOffset, dataOffset + compressedSize);\n let data: Buffer;\n if (method === 0) {\n if (compressedSize !== size) fail('stored entry compressed/uncompressed size mismatch');\n data = Buffer.from(compressed);\n } else {\n try {\n data = inflateRawSync(compressed, { maxOutputLength: Math.max(1, size) });\n } catch (error) {\n return fail(\n `cannot inflate entry ${JSON.stringify(name)} within declared size: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n if (data.length !== size) fail(`uncompressed size mismatch for ${JSON.stringify(name)}`);\n if (crc32(data) !== checksum) fail(`CRC32 mismatch for ${JSON.stringify(name)}`);\n if (name && !name.endsWith('/')) result.set(name, data);\n }\n if (offset !== eocd) fail('central directory entry count/length mismatch');\n ranges.sort((a, b) => a.start - b.start);\n for (let i = 1; i < ranges.length; i++)\n if (ranges[i]!.start < ranges[i - 1]!.end) fail('overlapping local entry ranges');\n return result;\n};\n","/** Native RFC 3284 VCDIFF decoder with the xdelta3 Adler32/app-header extensions.\n * Supports the same default-table, uncompressed format as PleOps.XdeltaSharp 1.3.0.\n * Secondary compression and custom code tables are explicitly unsupported there too.\n */\nconst UINT32_MAX = 0xffffffff;\nconst fail = (message: string): never => {\n throw new Error(`VCDIFF: ${message}`);\n};\n\nclass Reader {\n pos = 0;\n constructor(\n readonly bytes: Buffer,\n readonly label = 'patch',\n ) {}\n get remaining() {\n return this.bytes.length - this.pos;\n }\n byte() {\n if (!this.remaining) fail(`truncated ${this.label}`);\n return this.bytes[this.pos++]!;\n }\n integer(): number {\n let value = 0;\n for (let count = 0; count < 5; count++) {\n const byte = this.byte();\n value = value * 128 + (byte & 127);\n if (value > UINT32_MAX) fail(`integer exceeds 32-bit limit in ${this.label}`);\n if (!(byte & 128)) return value;\n }\n return fail(`invalid variable-length integer in ${this.label}`);\n }\n take(size: number): Buffer {\n if (size > this.remaining)\n fail(`truncated ${this.label}: requested ${size} bytes, ${this.remaining} remain`);\n const result = this.bytes.subarray(this.pos, this.pos + size);\n this.pos += size;\n return result;\n }\n}\n\n// RFC 3284 section 5.6: (operation, size, COPY address mode), up to two per opcode.\ntype Operation = { type: 'ADD' | 'RUN' | 'COPY'; size: number; mode: number };\nconst table: Operation[][] = [];\nconst op = (type: Operation['type'], size: number, mode = 0): Operation => ({ type, size, mode });\ntable.push([op('RUN', 0)]);\nfor (let size = 0; size <= 17; size++) table.push([op('ADD', size)]);\nfor (let mode = 0; mode <= 8; mode++) {\n table.push([op('COPY', 0, mode)]);\n for (let size = 4; size <= 18; size++) table.push([op('COPY', size, mode)]);\n}\nfor (let mode = 0; mode <= 5; mode++) {\n for (let add = 1; add <= 4; add++) {\n for (let copy = 4; copy <= 6; copy++) table.push([op('ADD', add), op('COPY', copy, mode)]);\n }\n}\nfor (let mode = 6; mode <= 8; mode++) {\n for (let add = 1; add <= 4; add++) table.push([op('ADD', add), op('COPY', 4, mode)]);\n}\nfor (let mode = 0; mode <= 8; mode++) table.push([op('COPY', 4, mode), op('ADD', 1)]);\n\nfunction adler32(bytes: Uint8Array): number {\n let a = 1,\n b = 0;\n for (let start = 0; start < bytes.length; start += 5552) {\n const end = Math.min(start + 5552, bytes.length);\n for (let i = start; i < end; i++) {\n a += bytes[i]!;\n b += a;\n }\n a %= 65521;\n b %= 65521;\n }\n return (b * 65536 + a) >>> 0;\n}\n\nfunction asBuffer(value: Uint8Array, name: string): Buffer {\n if (!(value instanceof Uint8Array)) throw new TypeError(`${name} must be a Buffer or Uint8Array`);\n return Buffer.from(value.buffer, value.byteOffset, value.byteLength);\n}\nfunction limit(value: number | undefined, fallback: number, name: string): number {\n value ??= fallback;\n if (!Number.isSafeInteger(value) || value < 0 || value > UINT32_MAX)\n throw new RangeError(`${name} must be an integer between 0 and ${UINT32_MAX}`);\n return value;\n}\n\n/**\n * @param {Buffer|Uint8Array} source Original bytes.\n * @param {Buffer|Uint8Array} patch VCDIFF bytes.\n * @param {{maxOutputSize?: number, maxWindowSize?: number}} [options]\n * @returns {Buffer} Decoded target, only returned after all windows/checksums validate.\n */\nexport interface XdeltaOptions {\n maxOutputSize?: number;\n maxWindowSize?: number;\n}\n\nexport const decodeXdelta = (\n source: Uint8Array,\n patch: Uint8Array,\n options: XdeltaOptions = {},\n): Buffer => {\n const sourceBytes = asBuffer(source, 'source');\n const reader = new Reader(asBuffer(patch, 'patch'));\n const maxOutput = limit(options.maxOutputSize, 1024 * 1024 * 1024, 'maxOutputSize');\n const maxWindow = limit(options.maxWindowSize, 16 * 1024 * 1024, 'maxWindowSize');\n if (!reader.take(3).equals(Buffer.from([0xd6, 0xc3, 0xc4]))) fail('invalid file signature');\n if (reader.byte() !== 0) fail('unsupported version (expected 0)');\n const header = reader.byte();\n if (header & ~7) fail('unrecognized header indicator bits');\n if (header & 1)\n fail('secondary compression is unsupported; recreate the patch using xdelta3 -S none');\n if (header & 2) fail('custom code table is unsupported');\n if (header & 4) reader.take(reader.integer());\n const windows: Buffer[] = [];\n let total = 0;\n\n while (reader.remaining) {\n const indicator = reader.byte();\n if (indicator & ~7 || (indicator & 3) === 3)\n fail('invalid window indicator: source and target are mutually exclusive');\n let dictionary: Buffer = Buffer.alloc(0);\n if (indicator & 3) {\n const length = reader.integer();\n const offset = reader.integer();\n const available = indicator & 1 ? sourceBytes.length : total;\n if (offset > available || length > available - offset)\n fail('source segment exceeds available source/target bounds');\n if (indicator & 1) dictionary = sourceBytes.subarray(offset, offset + length);\n else {\n dictionary = Buffer.allocUnsafe(length);\n let base = 0;\n for (const previous of windows) {\n const start = Math.max(offset, base),\n end = Math.min(offset + length, base + previous.length);\n if (end > start) previous.copy(dictionary, start - offset, start - base, end - base);\n base += previous.length;\n if (base >= offset + length) break;\n }\n }\n }\n const deltaLength = reader.integer();\n const delta = new Reader(reader.take(deltaLength), 'delta window');\n const targetLength = delta.integer();\n if (targetLength > maxWindow)\n fail(`window size ${targetLength} exceeds maximum limit ${maxWindow}`);\n if (targetLength > maxOutput - total) fail(`output size exceeds maximum limit ${maxOutput}`);\n if (dictionary.length + targetLength > UINT32_MAX)\n fail('window address space exceeds 32-bit limit');\n const compressed = delta.byte();\n if (compressed & ~7) fail('unrecognized delta indicator bits');\n if (compressed) fail('secondary compression in delta sections is unsupported');\n const dataLength = delta.integer(),\n instructionLength = delta.integer(),\n addressLength = delta.integer();\n const checksum = indicator & 4 ? delta.take(4).readUInt32BE() : null;\n const data = new Reader(delta.take(dataLength), 'data section');\n const instructions = new Reader(delta.take(instructionLength), 'instruction section');\n const addresses = new Reader(delta.take(addressLength), 'address section');\n if (delta.remaining) fail('delta encoding length does not match section lengths');\n const target = Buffer.allocUnsafe(targetLength);\n const near = new Uint32Array(4),\n same = new Uint32Array(768);\n let nearIndex = 0,\n position = 0;\n while (instructions.remaining) {\n const operations = table[instructions.byte()]!;\n for (const { type, size: fixedSize, mode } of operations) {\n const size = fixedSize || instructions.integer();\n if (size > targetLength - position) fail(`${type} size exceeds target window length`);\n if (type === 'ADD') {\n data.take(size).copy(target, position);\n } else if (type === 'RUN') {\n target.fill(data.byte(), position, position + size);\n } else {\n const here = dictionary.length + position;\n let address;\n if (mode === 0) address = addresses.integer();\n else if (mode === 1) address = here - addresses.integer();\n else if (mode < 6) address = near[mode - 2]! + addresses.integer();\n else address = same[(mode - 6) * 256 + addresses.byte()]!;\n if (address < 0 || address >= here)\n fail(`COPY address ${address} is outside decoded address space ${here}`);\n near[nearIndex] = address;\n nearIndex = (nearIndex + 1) % near.length;\n same[address % same.length] = address;\n let copied = 0;\n if (address < dictionary.length) {\n copied = Math.min(size, dictionary.length - address);\n dictionary.copy(target, position, address, address + copied);\n }\n // Copy only already available bytes per iteration. Doubling handles overlap\n // (including a copy crossing from dictionary to current target) efficiently.\n const from = Math.max(0, address - dictionary.length);\n while (copied < size) {\n const count = Math.min(size - copied, position + copied - from);\n if (count <= 0) fail('COPY references unavailable target bytes');\n target.copy(target, position + copied, from, from + count);\n copied += count;\n }\n }\n position += size;\n }\n }\n if (position !== targetLength)\n fail(`target window length mismatch: decoded ${position}, expected ${targetLength}`);\n if (data.remaining || addresses.remaining) fail('unused bytes in data/address section');\n if (checksum !== null && adler32(target) !== checksum) fail('Adler32 checksum mismatch');\n windows.push(target);\n total += targetLength;\n }\n return Buffer.concat(windows, total);\n};\n","// Native TypeScript port of NitroPatchHelper/PatchHelper.cs, GPL-3.0.\nimport { hashDigest, readFile } from '#nitro-runtime';\nimport { bytes } from './nitro/binary';\nimport { NDSFile } from './nitro/nds-file';\nimport { readZip } from './patch/zip';\nimport { decodeXdelta } from './patch/xdelta';\n\nexport const PatchReturnValue = Object.freeze({\n SUCCESS: 'SUCCESS',\n MD5_MISMATCH: 'MD5_MISMATCH',\n} as const);\nexport type PatchReturnValue = (typeof PatchReturnValue)[keyof typeof PatchReturnValue];\n\nexport interface PatchResult {\n /** Generated ROM bytes. Also returned when the MD5 does not match, as in the original helper. */\n buffer: Buffer;\n returnValue: PatchReturnValue;\n /** MD5 of the original input, before preprocessing. */\n inputMd5: string;\n outputMd5: string;\n}\n\nexport interface PatchOptions {\n /** Maximum generated ROM/Xdelta target size in bytes, default 1 GiB. */\n maxOutputSize?: number;\n /** Maximum target size of an individual VCDIFF window, default 16 MiB. */\n maxWindowSize?: number;\n /** Maximum total uncompressed ZIP bytes, default 1 GiB. */\n maxUncompressedSize?: number;\n /** Maximum ZIP entry count. */\n maxEntries?: number;\n}\n\nconst md5 = (data: Uint8Array): string => hashDigest('md5', data).toString('hex');\n\n/** Apply an existing NitroPatcher ZIP package entirely in memory. */\nexport const patchBuffer = (\n original: Uint8Array,\n patch: Uint8Array,\n options: PatchOptions = {},\n): PatchResult => {\n const input = bytes(original, 'original ROM');\n const patches = readZip(patch, options);\n const inputMd5 = md5(input);\n const preprocessing = patches.get(`preprocessing/${inputMd5}.xdelta`);\n const rom = new NDSFile(\n preprocessing ? decodeXdelta(input, preprocessing, options) : input,\n options,\n );\n let matched = true;\n const md5File = patches.get('md5.txt');\n if (md5File) {\n matched = false;\n for (const line of md5File.toString('utf8').split('\\n')) {\n const expected = line.trim();\n if (!expected || expected.startsWith('#')) continue;\n // Keep the original format contract: exactly 32 characters, case-insensitive comparison.\n if (expected.length !== 32) throw new Error('md5.txt 格式错误,可能是因为补丁包已损坏。');\n if (expected.toLowerCase() === inputMd5) {\n matched = true;\n break;\n }\n }\n }\n for (const { path } of rom.listFiles()) {\n const normalized = path.replaceAll('\\\\', '/').toLowerCase();\n const delta = patches.get(`xdelta/${normalized}`);\n const replacement = delta\n ? decodeXdelta(rom.getFile(path), delta, options)\n : patches.get(normalized);\n if (replacement) rom.replaceFile(path, replacement);\n }\n const buffer = rom.toBuffer();\n return {\n buffer,\n returnValue: matched ? PatchReturnValue.SUCCESS : PatchReturnValue.MD5_MISMATCH,\n inputMd5,\n outputMd5: md5(buffer),\n };\n};\n\n/** Read input files and apply the patch. Does not write an output file. */\nexport const patchIt = async (\n originalPath: string,\n patchPath: string,\n options: PatchOptions = {},\n): Promise<PatchResult> => {\n const [original, patch] = await Promise.all([readFile(originalPath), readFile(patchPath)]);\n return patchBuffer(original, patch, options);\n};\n\n/** Namespaced counterpart of the original C# helper, sharing the same typed functions. */\nexport const PatchHelper = Object.freeze({ patchBuffer, patchIt });\nexport { decodeXdelta } from './patch/xdelta';\nexport type { XdeltaOptions } from './patch/xdelta';\n"],"mappings":"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","names":["bytes","offset","fail","bytes"]}
|
|
1
|
+
{"version":3,"sources":["../src/patch/zip.ts","../src/patch/xdelta.ts","../src/nitro-patch-helper.ts"],"sourcesContent":["import { inflateRawSync } from '#nitro-runtime';\nimport { crc32 } from '../nitro/crc';\n\n/** Resource limits apply to every central-directory entry, including duplicates. */\nexport interface ZipOptions {\n maxUncompressedSize?: number;\n maxEntries?: number;\n}\n\nconst fail = (message: string): never => {\n throw new Error(`ZIP: ${message}`);\n};\nconst utf8 = new TextDecoder('utf-8', { fatal: true });\n// IBM code page 437, byte values 0x80..0xff. ASCII bytes retain their literal values.\nconst cp437 =\n 'ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ';\n\nfunction decodeUtf8(bytes: Uint8Array): string {\n try {\n return utf8.decode(bytes);\n } catch {\n return fail('invalid UTF-8 filename');\n }\n}\nfunction boundOption(value: number | undefined, fallback: number, label: string): number {\n value ??= fallback;\n if (!Number.isSafeInteger(value) || value < 0)\n throw new RangeError(`${label} must be a non-negative safe integer`);\n return value;\n}\nfunction checkRange(bytes: Buffer, offset: number, length: number, end = bytes.length): void {\n if (offset < 0 || offset > end || length > end - offset)\n fail(`truncated or out-of-bounds record at ${offset} (length ${length})`);\n}\nfunction extras(bytes: Buffer): Map<number, Buffer> {\n const fields = new Map<number, Buffer>();\n let offset = 0;\n while (offset < bytes.length) {\n checkRange(bytes, offset, 4);\n const id = bytes.readUInt16LE(offset),\n length = bytes.readUInt16LE(offset + 2);\n offset += 4;\n checkRange(bytes, offset, length);\n if (id === 1) fail('ZIP64 archives are unsupported');\n fields.set(id, bytes.subarray(offset, offset + length));\n offset += length;\n }\n return fields;\n}\nfunction filename(raw: Buffer, flags: number, fields: Map<number, Buffer>): string {\n if (flags & 0x800) return decodeUtf8(raw);\n const unicode = fields.get(0x7075);\n if (unicode && unicode.length >= 5 && unicode[0] === 1 && unicode.readUInt32LE(1) === crc32(raw))\n return decodeUtf8(unicode.subarray(5));\n let result = '';\n for (const byte of raw) result += byte < 128 ? String.fromCharCode(byte) : cp437[byte - 128]!;\n return result;\n}\n\n/**\n * Read stored/deflated, single-disk ZIPs entirely in memory. Central-directory\n * sizes support streamed entries with data descriptors. Paths match PatchHelper:\n * backslashes become slashes, names are lowercase, and the final duplicate wins.\n * ZIP64, encryption, split archives and other compression methods fail explicitly.\n * Format reference: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT\n */\nexport const readZip = (input: Uint8Array, options: ZipOptions = {}): Map<string, Buffer> => {\n if (!(input instanceof Uint8Array))\n throw new TypeError('ZIP input must be a Buffer or Uint8Array');\n const bytes = Buffer.from(input.buffer, input.byteOffset, input.byteLength);\n const maxSize = boundOption(\n options.maxUncompressedSize,\n 1024 * 1024 * 1024,\n 'maxUncompressedSize',\n );\n const maxEntries = boundOption(options.maxEntries, 100000, 'maxEntries');\n let eocd = -1;\n for (let offset = bytes.length - 22; offset >= Math.max(0, bytes.length - 65557); offset--) {\n if (\n bytes.readUInt32LE(offset) === 0x06054b50 &&\n offset + 22 + bytes.readUInt16LE(offset + 20) === bytes.length\n ) {\n eocd = offset;\n break;\n }\n }\n if (eocd < 0) fail('end of central directory not found or truncated');\n const disk = bytes.readUInt16LE(eocd + 4),\n centralDisk = bytes.readUInt16LE(eocd + 6);\n const diskEntries = bytes.readUInt16LE(eocd + 8),\n count = bytes.readUInt16LE(eocd + 10);\n const centralSize = bytes.readUInt32LE(eocd + 12),\n centralOffset = bytes.readUInt32LE(eocd + 16);\n if (\n count === 0xffff ||\n diskEntries === 0xffff ||\n centralSize === 0xffffffff ||\n centralOffset === 0xffffffff ||\n (eocd >= 20 && bytes.readUInt32LE(eocd - 20) === 0x07064b50)\n )\n fail('ZIP64 archives are unsupported');\n if (disk || centralDisk || diskEntries !== count)\n fail('split or multiple-disk archives are unsupported');\n if (count > maxEntries) fail(`entry count exceeds limit ${maxEntries}`);\n checkRange(bytes, centralOffset, centralSize, eocd);\n if (centralOffset + centralSize !== eocd) fail('central directory size/offset mismatch');\n if (count > Math.floor(centralSize / 46)) fail('entry count exceeds central directory size');\n const result = new Map<string, Buffer>();\n const ranges: Array<{ start: number; end: number }> = [];\n let offset = centralOffset,\n totalSize = 0;\n for (let index = 0; index < count; index++) {\n checkRange(bytes, offset, 46, eocd);\n if (bytes.readUInt32LE(offset) !== 0x02014b50) fail('invalid central directory signature');\n const flags = bytes.readUInt16LE(offset + 8),\n method = bytes.readUInt16LE(offset + 10);\n const checksum = bytes.readUInt32LE(offset + 16),\n compressedSize = bytes.readUInt32LE(offset + 20),\n size = bytes.readUInt32LE(offset + 24);\n const nameLength = bytes.readUInt16LE(offset + 28),\n extraLength = bytes.readUInt16LE(offset + 30),\n commentLength = bytes.readUInt16LE(offset + 32);\n const startDisk = bytes.readUInt16LE(offset + 34),\n localOffset = bytes.readUInt32LE(offset + 42);\n if (\n size === 0xffffffff ||\n compressedSize === 0xffffffff ||\n localOffset === 0xffffffff ||\n startDisk === 0xffff\n )\n fail('ZIP64 entry is unsupported');\n if (startDisk) fail('split or multiple-disk entry is unsupported');\n if (flags & 0x2041) fail('encrypted entries are unsupported');\n if (flags & ~0x80e) fail('unsupported general purpose flags');\n if (method !== 0 && method !== 8) fail(`unsupported compression method ${method}`);\n if (size > maxSize - totalSize) fail(`uncompressed size exceeds limit ${maxSize}`);\n totalSize += size;\n checkRange(bytes, offset + 46, nameLength + extraLength + commentLength, eocd);\n const rawName = bytes.subarray(offset + 46, offset + 46 + nameLength);\n const fields = extras(\n bytes.subarray(offset + 46 + nameLength, offset + 46 + nameLength + extraLength),\n );\n const name = filename(rawName, flags, fields).replaceAll('\\\\', '/').toLowerCase();\n offset += 46 + nameLength + extraLength + commentLength;\n\n checkRange(bytes, localOffset, 30, centralOffset);\n if (bytes.readUInt32LE(localOffset) !== 0x04034b50) fail('invalid local header signature');\n if (\n bytes.readUInt16LE(localOffset + 6) !== flags ||\n bytes.readUInt16LE(localOffset + 8) !== method\n )\n fail('local header flags/compression method mismatch');\n const localNameLength = bytes.readUInt16LE(localOffset + 26),\n localExtraLength = bytes.readUInt16LE(localOffset + 28);\n const dataOffset = localOffset + 30 + localNameLength + localExtraLength;\n checkRange(bytes, localOffset + 30, localNameLength + localExtraLength, centralOffset);\n if (!rawName.equals(bytes.subarray(localOffset + 30, localOffset + 30 + localNameLength)))\n fail('local filename differs from central directory');\n extras(bytes.subarray(localOffset + 30 + localNameLength, dataOffset));\n if (\n !(flags & 8) &&\n (bytes.readUInt32LE(localOffset + 14) !== checksum ||\n bytes.readUInt32LE(localOffset + 18) !== compressedSize ||\n bytes.readUInt32LE(localOffset + 22) !== size)\n )\n fail('local header CRC/size mismatch');\n checkRange(bytes, dataOffset, compressedSize, centralOffset);\n let dataEnd = dataOffset + compressedSize;\n if (flags & 8) {\n // A descriptor may omit its signature. Use the CRC and both sizes to\n // disambiguate the rare case of an unsigned CRC equal to the signature.\n checkRange(bytes, dataEnd, 12, centralOffset);\n const matches = (at: number): boolean =>\n at + 12 <= centralOffset &&\n bytes.readUInt32LE(at) === checksum &&\n bytes.readUInt32LE(at + 4) === compressedSize &&\n bytes.readUInt32LE(at + 8) === size;\n if (matches(dataEnd)) dataEnd += 12;\n else if (bytes.readUInt32LE(dataEnd) === 0x08074b50 && matches(dataEnd + 4)) dataEnd += 16;\n else fail('data descriptor CRC/size mismatch');\n }\n ranges.push({ start: localOffset, end: dataEnd });\n const compressed = bytes.subarray(dataOffset, dataOffset + compressedSize);\n let data: Buffer;\n if (method === 0) {\n if (compressedSize !== size) fail('stored entry compressed/uncompressed size mismatch');\n data = Buffer.from(compressed);\n } else {\n try {\n data = inflateRawSync(compressed, { maxOutputLength: Math.max(1, size) });\n } catch (error) {\n return fail(\n `cannot inflate entry ${JSON.stringify(name)} within declared size: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n if (data.length !== size) fail(`uncompressed size mismatch for ${JSON.stringify(name)}`);\n if (crc32(data) !== checksum) fail(`CRC32 mismatch for ${JSON.stringify(name)}`);\n if (name && !name.endsWith('/')) result.set(name, data);\n }\n if (offset !== eocd) fail('central directory entry count/length mismatch');\n ranges.sort((a, b) => a.start - b.start);\n for (let i = 1; i < ranges.length; i++)\n if (ranges[i]!.start < ranges[i - 1]!.end) fail('overlapping local entry ranges');\n return result;\n};\n","/** Native RFC 3284 VCDIFF decoder with the xdelta3 Adler32/app-header extensions.\n * Supports the same default-table, uncompressed format as PleOps.XdeltaSharp 1.3.0.\n * Secondary compression and custom code tables are explicitly unsupported there too.\n */\nconst UINT32_MAX = 0xffffffff;\nconst fail = (message: string): never => {\n throw new Error(`VCDIFF: ${message}`);\n};\n\nclass Reader {\n pos = 0;\n constructor(\n readonly bytes: Buffer,\n readonly label = 'patch',\n ) {}\n get remaining() {\n return this.bytes.length - this.pos;\n }\n byte() {\n if (!this.remaining) fail(`truncated ${this.label}`);\n return this.bytes[this.pos++]!;\n }\n integer(): number {\n let value = 0;\n for (let count = 0; count < 5; count++) {\n const byte = this.byte();\n value = value * 128 + (byte & 127);\n if (value > UINT32_MAX) fail(`integer exceeds 32-bit limit in ${this.label}`);\n if (!(byte & 128)) return value;\n }\n return fail(`invalid variable-length integer in ${this.label}`);\n }\n take(size: number): Buffer {\n if (size > this.remaining)\n fail(`truncated ${this.label}: requested ${size} bytes, ${this.remaining} remain`);\n const result = this.bytes.subarray(this.pos, this.pos + size);\n this.pos += size;\n return result;\n }\n}\n\n// RFC 3284 section 5.6: (operation, size, COPY address mode), up to two per opcode.\ntype Operation = { type: 'ADD' | 'RUN' | 'COPY'; size: number; mode: number };\nconst table: Operation[][] = [];\nconst op = (type: Operation['type'], size: number, mode = 0): Operation => ({ type, size, mode });\ntable.push([op('RUN', 0)]);\nfor (let size = 0; size <= 17; size++) table.push([op('ADD', size)]);\nfor (let mode = 0; mode <= 8; mode++) {\n table.push([op('COPY', 0, mode)]);\n for (let size = 4; size <= 18; size++) table.push([op('COPY', size, mode)]);\n}\nfor (let mode = 0; mode <= 5; mode++) {\n for (let add = 1; add <= 4; add++) {\n for (let copy = 4; copy <= 6; copy++) table.push([op('ADD', add), op('COPY', copy, mode)]);\n }\n}\nfor (let mode = 6; mode <= 8; mode++) {\n for (let add = 1; add <= 4; add++) table.push([op('ADD', add), op('COPY', 4, mode)]);\n}\nfor (let mode = 0; mode <= 8; mode++) table.push([op('COPY', 4, mode), op('ADD', 1)]);\n\nfunction adler32(bytes: Uint8Array): number {\n let a = 1,\n b = 0;\n for (let start = 0; start < bytes.length; start += 5552) {\n const end = Math.min(start + 5552, bytes.length);\n for (let i = start; i < end; i++) {\n a += bytes[i]!;\n b += a;\n }\n a %= 65521;\n b %= 65521;\n }\n return (b * 65536 + a) >>> 0;\n}\n\nfunction asBuffer(value: Uint8Array, name: string): Buffer {\n if (!(value instanceof Uint8Array)) throw new TypeError(`${name} must be a Buffer or Uint8Array`);\n return Buffer.from(value.buffer, value.byteOffset, value.byteLength);\n}\nfunction limit(value: number | undefined, fallback: number, name: string): number {\n value ??= fallback;\n if (!Number.isSafeInteger(value) || value < 0 || value > UINT32_MAX)\n throw new RangeError(`${name} must be an integer between 0 and ${UINT32_MAX}`);\n return value;\n}\n\n/**\n * @param {Buffer|Uint8Array} source Original bytes.\n * @param {Buffer|Uint8Array} patch VCDIFF bytes.\n * @param {{maxOutputSize?: number, maxWindowSize?: number}} [options]\n * @returns {Buffer} Decoded target, only returned after all windows/checksums validate.\n */\nexport interface XdeltaOptions {\n maxOutputSize?: number;\n maxWindowSize?: number;\n}\n\nexport const decodeXdelta = (\n source: Uint8Array,\n patch: Uint8Array,\n options: XdeltaOptions = {},\n): Buffer => {\n const sourceBytes = asBuffer(source, 'source');\n const reader = new Reader(asBuffer(patch, 'patch'));\n const maxOutput = limit(options.maxOutputSize, 1024 * 1024 * 1024, 'maxOutputSize');\n const maxWindow = limit(options.maxWindowSize, 16 * 1024 * 1024, 'maxWindowSize');\n if (!reader.take(3).equals(Buffer.from([0xd6, 0xc3, 0xc4]))) fail('invalid file signature');\n if (reader.byte() !== 0) fail('unsupported version (expected 0)');\n const header = reader.byte();\n if (header & ~7) fail('unrecognized header indicator bits');\n if (header & 1)\n fail('secondary compression is unsupported; recreate the patch using xdelta3 -S none');\n if (header & 2) fail('custom code table is unsupported');\n if (header & 4) reader.take(reader.integer());\n const windows: Buffer[] = [];\n let total = 0;\n\n while (reader.remaining) {\n const indicator = reader.byte();\n if (indicator & ~7 || (indicator & 3) === 3)\n fail('invalid window indicator: source and target are mutually exclusive');\n let dictionary: Buffer = Buffer.alloc(0);\n if (indicator & 3) {\n const length = reader.integer();\n const offset = reader.integer();\n const available = indicator & 1 ? sourceBytes.length : total;\n if (offset > available || length > available - offset)\n fail('source segment exceeds available source/target bounds');\n if (indicator & 1) dictionary = sourceBytes.subarray(offset, offset + length);\n else {\n dictionary = Buffer.allocUnsafe(length);\n let base = 0;\n for (const previous of windows) {\n const start = Math.max(offset, base),\n end = Math.min(offset + length, base + previous.length);\n if (end > start) previous.copy(dictionary, start - offset, start - base, end - base);\n base += previous.length;\n if (base >= offset + length) break;\n }\n }\n }\n const deltaLength = reader.integer();\n const delta = new Reader(reader.take(deltaLength), 'delta window');\n const targetLength = delta.integer();\n if (targetLength > maxWindow)\n fail(`window size ${targetLength} exceeds maximum limit ${maxWindow}`);\n if (targetLength > maxOutput - total) fail(`output size exceeds maximum limit ${maxOutput}`);\n if (dictionary.length + targetLength > UINT32_MAX)\n fail('window address space exceeds 32-bit limit');\n const compressed = delta.byte();\n if (compressed & ~7) fail('unrecognized delta indicator bits');\n if (compressed) fail('secondary compression in delta sections is unsupported');\n const dataLength = delta.integer(),\n instructionLength = delta.integer(),\n addressLength = delta.integer();\n const checksum = indicator & 4 ? delta.take(4).readUInt32BE() : null;\n const data = new Reader(delta.take(dataLength), 'data section');\n const instructions = new Reader(delta.take(instructionLength), 'instruction section');\n const addresses = new Reader(delta.take(addressLength), 'address section');\n if (delta.remaining) fail('delta encoding length does not match section lengths');\n const target = Buffer.allocUnsafe(targetLength);\n const near = new Uint32Array(4),\n same = new Uint32Array(768);\n let nearIndex = 0,\n position = 0;\n while (instructions.remaining) {\n const operations = table[instructions.byte()]!;\n for (const { type, size: fixedSize, mode } of operations) {\n const size = fixedSize || instructions.integer();\n if (size > targetLength - position) fail(`${type} size exceeds target window length`);\n if (type === 'ADD') {\n data.take(size).copy(target, position);\n } else if (type === 'RUN') {\n target.fill(data.byte(), position, position + size);\n } else {\n const here = dictionary.length + position;\n let address;\n if (mode === 0) address = addresses.integer();\n else if (mode === 1) address = here - addresses.integer();\n else if (mode < 6) address = near[mode - 2]! + addresses.integer();\n else address = same[(mode - 6) * 256 + addresses.byte()]!;\n if (address < 0 || address >= here)\n fail(`COPY address ${address} is outside decoded address space ${here}`);\n near[nearIndex] = address;\n nearIndex = (nearIndex + 1) % near.length;\n same[address % same.length] = address;\n let copied = 0;\n if (address < dictionary.length) {\n copied = Math.min(size, dictionary.length - address);\n dictionary.copy(target, position, address, address + copied);\n }\n // Copy only already available bytes per iteration. Doubling handles overlap\n // (including a copy crossing from dictionary to current target) efficiently.\n const from = Math.max(0, address - dictionary.length);\n while (copied < size) {\n const count = Math.min(size - copied, position + copied - from);\n if (count <= 0) fail('COPY references unavailable target bytes');\n target.copy(target, position + copied, from, from + count);\n copied += count;\n }\n }\n position += size;\n }\n }\n if (position !== targetLength)\n fail(`target window length mismatch: decoded ${position}, expected ${targetLength}`);\n if (data.remaining || addresses.remaining) fail('unused bytes in data/address section');\n if (checksum !== null && adler32(target) !== checksum) fail('Adler32 checksum mismatch');\n windows.push(target);\n total += targetLength;\n }\n return Buffer.concat(windows, total);\n};\n","// Native TypeScript port of NitroPatchHelper/PatchHelper.cs, GPL-3.0.\nimport { hashDigest, readFile } from '#nitro-runtime';\nimport { bytes } from './nitro/binary';\nimport { NDSFile } from './nitro/nds-file';\nimport { readZip } from './patch/zip';\nimport { decodeXdelta } from './patch/xdelta';\n\nexport const PatchReturnValue = Object.freeze({\n SUCCESS: 'SUCCESS',\n MD5_MISMATCH: 'MD5_MISMATCH',\n} as const);\nexport type PatchReturnValue = (typeof PatchReturnValue)[keyof typeof PatchReturnValue];\n\nexport interface PatchResult {\n /** Generated ROM bytes. Also returned when the MD5 does not match, as in the original helper. */\n buffer: Buffer;\n returnValue: PatchReturnValue;\n /** MD5 of the original input, before preprocessing. */\n inputMd5: string;\n outputMd5: string;\n}\n\nexport interface PatchOptions {\n /** Maximum generated ROM/Xdelta target size in bytes, default 1 GiB. */\n maxOutputSize?: number;\n /** Maximum target size of an individual VCDIFF window, default 16 MiB. */\n maxWindowSize?: number;\n /** Maximum total uncompressed ZIP bytes, default 1 GiB. */\n maxUncompressedSize?: number;\n /** Maximum ZIP entry count. */\n maxEntries?: number;\n}\n\nexport interface PatchMetadata {\n id?: string;\n author?: string;\n name?: string;\n homepage?: string;\n version?: string;\n isBeta?: boolean;\n}\n\nexport interface PatchReadme {\n format: 'markdown' | 'plaintext';\n content: string;\n}\n\nexport interface PatchInfo {\n metadata: PatchMetadata | null;\n readme: PatchReadme | null;\n}\n\nconst md5 = (data: Uint8Array): string => hashDigest('md5', data).toString('hex');\nconst invalidMetadata = (message: string): null => {\n console.warn(`忽略 metadata.json:${message}`);\n return null;\n};\nconst invalidMetadataField = (field: keyof PatchMetadata, message: string): void => {\n console.warn(`忽略 metadata.json 的 ${field}:${message}`);\n};\n\nconst readMetadata = (files: Map<string, Buffer>): PatchMetadata | null => {\n const file = files.get('metadata.json');\n if (!file) return null;\n\n let metadata: unknown;\n try {\n metadata = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(file));\n } catch (error) {\n return invalidMetadata(\n `格式错误,可能是因为补丁包已损坏:${error instanceof Error ? error.message : String(error)}`,\n );\n }\n if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata))\n return invalidMetadata('根节点必须是对象。');\n\n const value = metadata as Record<string, unknown>;\n const result: PatchMetadata = {};\n for (const field of ['id', 'author', 'name', 'homepage', 'version'] as const) {\n if (typeof value[field] === 'string') result[field] = value[field];\n else invalidMetadataField(field, '必须是字符串。');\n }\n if (typeof value.isBeta === 'boolean') result.isBeta = value.isBeta;\n else invalidMetadataField('isBeta', '必须是布尔值。');\n\n return result;\n};\n\nconst readReadme = (files: Map<string, Buffer>): PatchReadme | null => {\n for (const [filename, format] of [\n ['README.md', 'markdown'],\n ['README.txt', 'plaintext'],\n ] as const) {\n const file = files.get(filename.toLowerCase());\n if (!file) continue;\n try {\n return {\n format,\n content: new TextDecoder('utf-8', { fatal: true }).decode(file),\n };\n } catch (error) {\n console.warn(\n `忽略 ${filename}:不是有效的 UTF-8 文本:${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n return null;\n};\n\n/** Read and validate metadata.json from a NitroPatcher ZIP package. */\nexport const readPatchMetadata = (\n patch: Uint8Array,\n options: PatchOptions = {},\n): PatchMetadata | null => readMetadata(readZip(patch, options));\n\n/** Read a root README.md or README.txt from a NitroPatcher ZIP package. */\nexport const readPatchReadme = (\n patch: Uint8Array,\n options: PatchOptions = {},\n): PatchReadme | null => readReadme(readZip(patch, options));\n\n/** Read metadata and README content while parsing the patch package only once. */\nexport const readPatchInfo = (patch: Uint8Array, options: PatchOptions = {}): PatchInfo => {\n const files = readZip(patch, options);\n return { metadata: readMetadata(files), readme: readReadme(files) };\n};\n\n/** Read metadata.json from a NitroPatcher ZIP package on disk. */\nexport const readPatchMetadataFile = async (\n patchPath: string,\n options: PatchOptions = {},\n): Promise<PatchMetadata | null> => readPatchMetadata(await readFile(patchPath), options);\n\n/** Read a patch README from disk. */\nexport const readPatchReadmeFile = async (\n patchPath: string,\n options: PatchOptions = {},\n): Promise<PatchReadme | null> => readPatchReadme(await readFile(patchPath), options);\n\n/** Read patch metadata and README content from disk. */\nexport const readPatchInfoFile = async (\n patchPath: string,\n options: PatchOptions = {},\n): Promise<PatchInfo> => readPatchInfo(await readFile(patchPath), options);\n\n/** Apply an existing NitroPatcher ZIP package entirely in memory. */\nexport const patchBuffer = (\n original: Uint8Array,\n patch: Uint8Array,\n options: PatchOptions = {},\n): PatchResult => {\n const input = bytes(original, 'original ROM');\n const patches = readZip(patch, options);\n const inputMd5 = md5(input);\n const preprocessing = patches.get(`preprocessing/${inputMd5}.xdelta`);\n const rom = new NDSFile(\n preprocessing ? decodeXdelta(input, preprocessing, options) : input,\n options,\n );\n let matched = true;\n const md5File = patches.get('md5.txt');\n if (md5File) {\n matched = false;\n for (const line of md5File.toString('utf8').split('\\n')) {\n const expected = line.trim();\n if (!expected || expected.startsWith('#')) continue;\n // Keep the original format contract: exactly 32 characters, case-insensitive comparison.\n if (expected.length !== 32) throw new Error('md5.txt 格式错误,可能是因为补丁包已损坏。');\n if (expected.toLowerCase() === inputMd5) {\n matched = true;\n break;\n }\n }\n }\n for (const { path } of rom.listFiles()) {\n const normalized = path.replaceAll('\\\\', '/').toLowerCase();\n const delta = patches.get(`xdelta/${normalized}`);\n const replacement = delta\n ? decodeXdelta(rom.getFile(path), delta, options)\n : patches.get(normalized);\n if (replacement) rom.replaceFile(path, replacement);\n }\n const buffer = rom.toBuffer();\n return {\n buffer,\n returnValue: matched ? PatchReturnValue.SUCCESS : PatchReturnValue.MD5_MISMATCH,\n inputMd5,\n outputMd5: md5(buffer),\n };\n};\n\n/** Read input files and apply the patch. Does not write an output file. */\nexport const patchIt = async (\n originalPath: string,\n patchPath: string,\n options: PatchOptions = {},\n): Promise<PatchResult> => {\n const [original, patch] = await Promise.all([readFile(originalPath), readFile(patchPath)]);\n return patchBuffer(original, patch, options);\n};\n\n/** Namespaced counterpart of the original C# helper, sharing the same typed functions. */\nexport const PatchHelper = Object.freeze({\n patchBuffer,\n patchIt,\n readPatchInfo,\n readPatchInfoFile,\n readPatchMetadata,\n readPatchMetadataFile,\n readPatchReadme,\n readPatchReadmeFile,\n});\nexport { decodeXdelta } from './patch/xdelta';\nexport type { XdeltaOptions } from './patch/xdelta';\n"],"mappings":"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","names":["bytes","offset","fail","bytes","filename"]}
|
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
patchBuffer,
|
|
4
|
+
readPatchInfo
|
|
5
|
+
} from "./chunk-ZY3OSZCA.js";
|
|
5
6
|
import "./chunk-GFF67HX4.js";
|
|
6
7
|
|
|
7
8
|
// src/cli.ts
|
|
8
9
|
import { mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from "fs/promises";
|
|
9
10
|
import { basename, dirname, join, resolve } from "path";
|
|
10
11
|
import { parseArgs } from "util";
|
|
12
|
+
import { render } from "markdansi";
|
|
11
13
|
var help = `nitro-patcher \u2014 \u539F\u751F Node.js NDS ROM \u8865\u4E01\u5DE5\u5177
|
|
12
14
|
|
|
13
|
-
\u7528\u6CD5\
|
|
15
|
+
\u7528\u6CD5\uFF1A
|
|
16
|
+
nitro-patcher [--json] [--dry-run] <\u539F\u59CBROM> <\u8865\u4E01ZIP> [\u8F93\u51FAROM]
|
|
17
|
+
nitro-patcher [--json] [--dry-run] --input <\u539F\u59CBROM> --patch <\u8865\u4E01ZIP> [--output <\u8F93\u51FAROM>]
|
|
14
18
|
|
|
15
|
-
--json
|
|
16
|
-
-
|
|
17
|
-
-
|
|
19
|
+
--json \u8F93\u51FA JSON \u72B6\u6001\u548C MD5
|
|
20
|
+
--dry-run \u5B8C\u6574\u6267\u884C\u8865\u4E01\u6D41\u7A0B\uFF0C\u4F46\u4E0D\u5199\u5165\u8F93\u51FA ROM
|
|
21
|
+
-i, --input \u539F\u59CB ROM \u8DEF\u5F84
|
|
22
|
+
-p, --patch \u8865\u4E01 ZIP \u8DEF\u5F84
|
|
23
|
+
-o, --output \u8F93\u51FA ROM \u8DEF\u5F84\uFF1B\u8BD5\u8FD0\u884C\u65F6\u53EF\u7701\u7565
|
|
24
|
+
-h, --help \u663E\u793A\u5E2E\u52A9
|
|
25
|
+
-v, --version \u663E\u793A\u7248\u672C`;
|
|
18
26
|
var sameFile = async (input, output) => {
|
|
19
27
|
if (resolve(input) === resolve(output)) return true;
|
|
20
28
|
const original = await stat(input);
|
|
@@ -26,13 +34,43 @@ var sameFile = async (input, output) => {
|
|
|
26
34
|
return false;
|
|
27
35
|
}
|
|
28
36
|
};
|
|
37
|
+
var safeTerminalText = (value) => {
|
|
38
|
+
const characters = [];
|
|
39
|
+
for (const character of value.replaceAll("\r\n", "\n").replaceAll("\r", "\n")) {
|
|
40
|
+
const code = character.codePointAt(0);
|
|
41
|
+
if (code < 32 && code !== 9 && code !== 10 || code >= 127 && code <= 159 || code >= 8234 && code <= 8238 || code >= 8294 && code <= 8297)
|
|
42
|
+
continue;
|
|
43
|
+
characters.push(character);
|
|
44
|
+
}
|
|
45
|
+
return characters.join("");
|
|
46
|
+
};
|
|
47
|
+
var formatMetadata = (metadata) => {
|
|
48
|
+
const labels = {
|
|
49
|
+
author: "\u4F5C\u8005",
|
|
50
|
+
name: "\u540D\u79F0",
|
|
51
|
+
homepage: "\u4E3B\u9875",
|
|
52
|
+
version: "\u7248\u672C"
|
|
53
|
+
};
|
|
54
|
+
const lines = [];
|
|
55
|
+
for (const field of ["author", "name", "homepage", "version"]) {
|
|
56
|
+
const value = metadata[field];
|
|
57
|
+
if (value === void 0) continue;
|
|
58
|
+
const suffix = field === "version" && metadata.isBeta ? "\uFF08\u6D4B\u8BD5\u7248\uFF09" : "";
|
|
59
|
+
lines.push(`- ${labels[field]}\uFF1A${safeTerminalText(value).replaceAll("\n", " ")}${suffix}`);
|
|
60
|
+
}
|
|
61
|
+
return lines.join("\n");
|
|
62
|
+
};
|
|
29
63
|
var main = async () => {
|
|
30
64
|
const { values, positionals } = parseArgs({
|
|
31
65
|
allowPositionals: true,
|
|
32
66
|
options: {
|
|
33
67
|
help: { type: "boolean", short: "h" },
|
|
34
68
|
version: { type: "boolean", short: "v" },
|
|
35
|
-
json: { type: "boolean" }
|
|
69
|
+
json: { type: "boolean" },
|
|
70
|
+
"dry-run": { type: "boolean" },
|
|
71
|
+
input: { type: "string", short: "i" },
|
|
72
|
+
patch: { type: "string", short: "p" },
|
|
73
|
+
output: { type: "string", short: "o" }
|
|
36
74
|
}
|
|
37
75
|
});
|
|
38
76
|
if (values.help) {
|
|
@@ -48,26 +86,70 @@ var main = async () => {
|
|
|
48
86
|
console.log(metadata.version);
|
|
49
87
|
return;
|
|
50
88
|
}
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
89
|
+
const dryRun = values["dry-run"] ?? false;
|
|
90
|
+
const usesNamedPaths = values.input !== void 0 || values.patch !== void 0 || values.output !== void 0;
|
|
91
|
+
if (usesNamedPaths && positionals.length > 0) throw new Error(help);
|
|
92
|
+
if (usesNamedPaths && (!values.input || !values.patch || !dryRun && !values.output) || !usesNamedPaths && (!dryRun && positionals.length !== 3 || dryRun && (positionals.length < 2 || positionals.length > 3)))
|
|
93
|
+
throw new Error(help);
|
|
94
|
+
const [original, patch, output] = usesNamedPaths ? [values.input, values.patch, values.output] : positionals;
|
|
95
|
+
if (!original || !patch) throw new Error(help);
|
|
96
|
+
if (!dryRun) {
|
|
97
|
+
if (!output) throw new Error(help);
|
|
98
|
+
if (await sameFile(original, output) || await sameFile(patch, output))
|
|
99
|
+
throw new Error("\u8F93\u51FA\u8DEF\u5F84\u4E0D\u80FD\u8986\u76D6\u539F\u59CB ROM \u6216\u8865\u4E01\u5305\u3002");
|
|
100
|
+
}
|
|
101
|
+
const [originalBytes, patchBytes] = await Promise.all([readFile(original), readFile(patch)]);
|
|
102
|
+
const info = readPatchInfo(patchBytes);
|
|
103
|
+
const result = patchBuffer(originalBytes, patchBytes);
|
|
104
|
+
if (!dryRun) {
|
|
105
|
+
if (!output) throw new Error(help);
|
|
106
|
+
const destination = join(await realpath(dirname(resolve(output))), basename(output));
|
|
107
|
+
const temporary = await mkdtemp(join(dirname(destination), ".nitro-patcher-"));
|
|
108
|
+
try {
|
|
109
|
+
const file = join(temporary, "output.nds");
|
|
110
|
+
await writeFile(file, result.buffer);
|
|
111
|
+
await rename(file, destination);
|
|
112
|
+
} finally {
|
|
113
|
+
await rm(temporary, { recursive: true, force: true });
|
|
114
|
+
}
|
|
64
115
|
}
|
|
65
116
|
const { returnValue, inputMd5, outputMd5 } = result;
|
|
66
|
-
if (values.json)
|
|
67
|
-
|
|
117
|
+
if (values.json) {
|
|
118
|
+
console.log(
|
|
119
|
+
JSON.stringify({
|
|
120
|
+
returnValue,
|
|
121
|
+
inputMd5,
|
|
122
|
+
outputMd5,
|
|
123
|
+
...info,
|
|
124
|
+
...dryRun && { dryRun: true }
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const sections = [];
|
|
130
|
+
if (info.metadata) {
|
|
131
|
+
const metadata = formatMetadata(info.metadata);
|
|
132
|
+
if (metadata) sections.push(`\u8865\u4E01\u5143\u6570\u636E\uFF1A
|
|
133
|
+
|
|
134
|
+
${metadata}`);
|
|
135
|
+
}
|
|
136
|
+
if (info.readme) {
|
|
137
|
+
const content = safeTerminalText(info.readme.content);
|
|
138
|
+
const rendered = info.readme.format === "markdown" ? render(content, {
|
|
139
|
+
width: process.stdout.columns ?? 80,
|
|
140
|
+
color: Boolean(process.stdout.isTTY),
|
|
141
|
+
hyperlinks: false
|
|
142
|
+
}) : content;
|
|
143
|
+
sections.push(`\u8865\u4E01\u8BF4\u660E\uFF1A
|
|
144
|
+
|
|
145
|
+
${rendered.trimEnd()}`);
|
|
146
|
+
}
|
|
147
|
+
if (dryRun) sections.push("\u8BD5\u8FD0\u884C\u5B8C\u6210\uFF0C\u672A\u5199\u5165\u8F93\u51FA ROM\u3002");
|
|
148
|
+
sections.push(`${returnValue}
|
|
68
149
|
|
|
69
150
|
\u539F\u59CB ROM \u7684 MD5\uFF1A${inputMd5}
|
|
70
151
|
\u751F\u6210 ROM \u7684 MD5\uFF1A${outputMd5}`);
|
|
152
|
+
console.log(sections.join("\n\n"));
|
|
71
153
|
};
|
|
72
154
|
main().catch((error) => {
|
|
73
155
|
console.error(error instanceof Error ? error.message : String(error));
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport {
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport { render } from 'markdansi';\nimport { patchBuffer, readPatchInfo, type PatchMetadata } from './nitro-patch-helper';\n\nconst help = `nitro-patcher — 原生 Node.js NDS ROM 补丁工具\n\n用法:\n nitro-patcher [--json] [--dry-run] <原始ROM> <补丁ZIP> [输出ROM]\n nitro-patcher [--json] [--dry-run] --input <原始ROM> --patch <补丁ZIP> [--output <输出ROM>]\n\n --json 输出 JSON 状态和 MD5\n --dry-run 完整执行补丁流程,但不写入输出 ROM\n -i, --input 原始 ROM 路径\n -p, --patch 补丁 ZIP 路径\n -o, --output 输出 ROM 路径;试运行时可省略\n -h, --help 显示帮助\n -v, --version 显示版本`;\n\nconst sameFile = async (input: string, output: string): Promise<boolean> => {\n if (resolve(input) === resolve(output)) return true;\n const original = await stat(input);\n try {\n const destination = await stat(output);\n return original.dev === destination.dev && original.ino === destination.ino;\n } catch (error) {\n if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) throw error;\n return false;\n }\n};\n\nconst safeTerminalText = (value: string): string => {\n const characters: string[] = [];\n for (const character of value.replaceAll('\\r\\n', '\\n').replaceAll('\\r', '\\n')) {\n const code = character.codePointAt(0)!;\n if (\n (code < 0x20 && code !== 0x09 && code !== 0x0a) ||\n (code >= 0x7f && code <= 0x9f) ||\n (code >= 0x202a && code <= 0x202e) ||\n (code >= 0x2066 && code <= 0x2069)\n )\n continue;\n characters.push(character);\n }\n return characters.join('');\n};\n\nconst formatMetadata = (metadata: PatchMetadata): string => {\n const labels = {\n author: '作者',\n name: '名称',\n homepage: '主页',\n version: '版本',\n } as const;\n const lines: string[] = [];\n for (const field of ['author', 'name', 'homepage', 'version'] as const) {\n const value = metadata[field];\n if (value === undefined) continue;\n const suffix = field === 'version' && metadata.isBeta ? '(测试版)' : '';\n lines.push(`- ${labels[field]}:${safeTerminalText(value).replaceAll('\\n', ' ')}${suffix}`);\n }\n return lines.join('\\n');\n};\n\nconst main = async (): Promise<void> => {\n const { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n json: { type: 'boolean' },\n 'dry-run': { type: 'boolean' },\n input: { type: 'string', short: 'i' },\n patch: { type: 'string', short: 'p' },\n output: { type: 'string', short: 'o' },\n },\n });\n if (values.help) {\n process.stdout.write(help);\n return;\n }\n if (values.version) {\n const metadata: unknown = JSON.parse(\n await readFile(new URL('../package.json', import.meta.url), 'utf8'),\n );\n if (\n !metadata ||\n typeof metadata !== 'object' ||\n !('version' in metadata) ||\n typeof metadata.version !== 'string'\n )\n throw new Error('Invalid package version metadata');\n console.log(metadata.version);\n return;\n }\n const dryRun = values['dry-run'] ?? false;\n const usesNamedPaths =\n values.input !== undefined || values.patch !== undefined || values.output !== undefined;\n if (usesNamedPaths && positionals.length > 0) throw new Error(help);\n if (\n (usesNamedPaths && (!values.input || !values.patch || (!dryRun && !values.output))) ||\n (!usesNamedPaths &&\n ((!dryRun && positionals.length !== 3) ||\n (dryRun && (positionals.length < 2 || positionals.length > 3))))\n )\n throw new Error(help);\n const [original, patch, output] = usesNamedPaths\n ? [values.input, values.patch, values.output]\n : positionals;\n if (!original || !patch) throw new Error(help);\n if (!dryRun) {\n if (!output) throw new Error(help);\n if ((await sameFile(original, output)) || (await sameFile(patch, output)))\n throw new Error('输出路径不能覆盖原始 ROM 或补丁包。');\n }\n const [originalBytes, patchBytes] = await Promise.all([readFile(original), readFile(patch)]);\n const info = readPatchInfo(patchBytes);\n const result = patchBuffer(originalBytes, patchBytes);\n if (!dryRun) {\n if (!output) throw new Error(help);\n const destination = join(await realpath(dirname(resolve(output))), basename(output));\n const temporary = await mkdtemp(join(dirname(destination), '.nitro-patcher-'));\n try {\n const file = join(temporary, 'output.nds');\n await writeFile(file, result.buffer);\n await rename(file, destination);\n } finally {\n await rm(temporary, { recursive: true, force: true });\n }\n }\n const { returnValue, inputMd5, outputMd5 } = result;\n if (values.json) {\n console.log(\n JSON.stringify({\n returnValue,\n inputMd5,\n outputMd5,\n ...info,\n ...(dryRun && { dryRun: true }),\n }),\n );\n return;\n }\n\n const sections: string[] = [];\n if (info.metadata) {\n const metadata = formatMetadata(info.metadata);\n if (metadata) sections.push(`补丁元数据:\\n\\n${metadata}`);\n }\n if (info.readme) {\n const content = safeTerminalText(info.readme.content);\n const rendered =\n info.readme.format === 'markdown'\n ? render(content, {\n width: process.stdout.columns ?? 80,\n color: Boolean(process.stdout.isTTY),\n hyperlinks: false,\n })\n : content;\n sections.push(`补丁说明:\\n\\n${rendered.trimEnd()}`);\n }\n if (dryRun) sections.push('试运行完成,未写入输出 ROM。');\n sections.push(`${returnValue}\\n\\n原始 ROM 的 MD5:${inputMd5}\\n生成 ROM 的 MD5:${outputMd5}`);\n console.log(sections.join('\\n\\n'));\n};\n\nmain().catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n});\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Arm9Header, Banner, BinaryWriter, FatEntry, FileAllocationTable, FileNameTable, Header, ModcryptHeader, NDSFile, NitroFile, NitroFolder, OverlayItem, OverlayRange, OverlayTable, RomFileInfo, RomOptions, SignedTwlHeader, TWL, TwlHeader, UnitCode, aes128CtrCrypt, compressArm9, compressBLZ, crc16, crc32, decompressArm9, decompressBLZ, encryptSecureArea, modcryptKey, updateHeaderSignatures } from './nitro-helper.js';
|
|
2
|
-
export { PatchHelper, PatchOptions, PatchResult, PatchReturnValue, XdeltaOptions, decodeXdelta, patchBuffer, patchIt } from './nitro-patch-helper.js';
|
|
2
|
+
export { PatchHelper, PatchInfo, PatchMetadata, PatchOptions, PatchReadme, PatchResult, PatchReturnValue, XdeltaOptions, decodeXdelta, patchBuffer, patchIt, readPatchInfo, readPatchInfoFile, readPatchMetadata, readPatchMetadataFile, readPatchReadme, readPatchReadmeFile } from './nitro-patch-helper.js';
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,14 @@ import {
|
|
|
4
4
|
PatchReturnValue,
|
|
5
5
|
decodeXdelta,
|
|
6
6
|
patchBuffer,
|
|
7
|
-
patchIt
|
|
8
|
-
|
|
7
|
+
patchIt,
|
|
8
|
+
readPatchInfo,
|
|
9
|
+
readPatchInfoFile,
|
|
10
|
+
readPatchMetadata,
|
|
11
|
+
readPatchMetadataFile,
|
|
12
|
+
readPatchReadme,
|
|
13
|
+
readPatchReadmeFile
|
|
14
|
+
} from "./chunk-ZY3OSZCA.js";
|
|
9
15
|
import {
|
|
10
16
|
Banner,
|
|
11
17
|
BinaryWriter,
|
|
@@ -51,6 +57,12 @@ export {
|
|
|
51
57
|
modcryptKey,
|
|
52
58
|
patchBuffer,
|
|
53
59
|
patchIt,
|
|
60
|
+
readPatchInfo,
|
|
61
|
+
readPatchInfoFile,
|
|
62
|
+
readPatchMetadata,
|
|
63
|
+
readPatchMetadataFile,
|
|
64
|
+
readPatchReadme,
|
|
65
|
+
readPatchReadmeFile,
|
|
54
66
|
updateHeaderSignatures
|
|
55
67
|
};
|
|
56
68
|
//# sourceMappingURL=index.js.map
|
|
@@ -33,6 +33,34 @@ interface PatchOptions {
|
|
|
33
33
|
/** Maximum ZIP entry count. */
|
|
34
34
|
maxEntries?: number;
|
|
35
35
|
}
|
|
36
|
+
interface PatchMetadata {
|
|
37
|
+
id?: string;
|
|
38
|
+
author?: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
homepage?: string;
|
|
41
|
+
version?: string;
|
|
42
|
+
isBeta?: boolean;
|
|
43
|
+
}
|
|
44
|
+
interface PatchReadme {
|
|
45
|
+
format: 'markdown' | 'plaintext';
|
|
46
|
+
content: string;
|
|
47
|
+
}
|
|
48
|
+
interface PatchInfo {
|
|
49
|
+
metadata: PatchMetadata | null;
|
|
50
|
+
readme: PatchReadme | null;
|
|
51
|
+
}
|
|
52
|
+
/** Read and validate metadata.json from a NitroPatcher ZIP package. */
|
|
53
|
+
declare const readPatchMetadata: (patch: Uint8Array, options?: PatchOptions) => PatchMetadata | null;
|
|
54
|
+
/** Read a root README.md or README.txt from a NitroPatcher ZIP package. */
|
|
55
|
+
declare const readPatchReadme: (patch: Uint8Array, options?: PatchOptions) => PatchReadme | null;
|
|
56
|
+
/** Read metadata and README content while parsing the patch package only once. */
|
|
57
|
+
declare const readPatchInfo: (patch: Uint8Array, options?: PatchOptions) => PatchInfo;
|
|
58
|
+
/** Read metadata.json from a NitroPatcher ZIP package on disk. */
|
|
59
|
+
declare const readPatchMetadataFile: (patchPath: string, options?: PatchOptions) => Promise<PatchMetadata | null>;
|
|
60
|
+
/** Read a patch README from disk. */
|
|
61
|
+
declare const readPatchReadmeFile: (patchPath: string, options?: PatchOptions) => Promise<PatchReadme | null>;
|
|
62
|
+
/** Read patch metadata and README content from disk. */
|
|
63
|
+
declare const readPatchInfoFile: (patchPath: string, options?: PatchOptions) => Promise<PatchInfo>;
|
|
36
64
|
/** Apply an existing NitroPatcher ZIP package entirely in memory. */
|
|
37
65
|
declare const patchBuffer: (original: Uint8Array, patch: Uint8Array, options?: PatchOptions) => PatchResult;
|
|
38
66
|
/** Read input files and apply the patch. Does not write an output file. */
|
|
@@ -41,6 +69,12 @@ declare const patchIt: (originalPath: string, patchPath: string, options?: Patch
|
|
|
41
69
|
declare const PatchHelper: Readonly<{
|
|
42
70
|
patchBuffer: (original: Uint8Array, patch: Uint8Array, options?: PatchOptions) => PatchResult;
|
|
43
71
|
patchIt: (originalPath: string, patchPath: string, options?: PatchOptions) => Promise<PatchResult>;
|
|
72
|
+
readPatchInfo: (patch: Uint8Array, options?: PatchOptions) => PatchInfo;
|
|
73
|
+
readPatchInfoFile: (patchPath: string, options?: PatchOptions) => Promise<PatchInfo>;
|
|
74
|
+
readPatchMetadata: (patch: Uint8Array, options?: PatchOptions) => PatchMetadata | null;
|
|
75
|
+
readPatchMetadataFile: (patchPath: string, options?: PatchOptions) => Promise<PatchMetadata | null>;
|
|
76
|
+
readPatchReadme: (patch: Uint8Array, options?: PatchOptions) => PatchReadme | null;
|
|
77
|
+
readPatchReadmeFile: (patchPath: string, options?: PatchOptions) => Promise<PatchReadme | null>;
|
|
44
78
|
}>;
|
|
45
79
|
|
|
46
|
-
export { PatchHelper, type PatchOptions, type PatchResult, PatchReturnValue, type XdeltaOptions, decodeXdelta, patchBuffer, patchIt };
|
|
80
|
+
export { PatchHelper, type PatchInfo, type PatchMetadata, type PatchOptions, type PatchReadme, type PatchResult, PatchReturnValue, type XdeltaOptions, decodeXdelta, patchBuffer, patchIt, readPatchInfo, readPatchInfoFile, readPatchMetadata, readPatchMetadataFile, readPatchReadme, readPatchReadmeFile };
|
|
@@ -3,14 +3,26 @@ import {
|
|
|
3
3
|
PatchReturnValue,
|
|
4
4
|
decodeXdelta,
|
|
5
5
|
patchBuffer,
|
|
6
|
-
patchIt
|
|
7
|
-
|
|
6
|
+
patchIt,
|
|
7
|
+
readPatchInfo,
|
|
8
|
+
readPatchInfoFile,
|
|
9
|
+
readPatchMetadata,
|
|
10
|
+
readPatchMetadataFile,
|
|
11
|
+
readPatchReadme,
|
|
12
|
+
readPatchReadmeFile
|
|
13
|
+
} from "./chunk-ZY3OSZCA.js";
|
|
8
14
|
import "./chunk-GFF67HX4.js";
|
|
9
15
|
export {
|
|
10
16
|
PatchHelper,
|
|
11
17
|
PatchReturnValue,
|
|
12
18
|
decodeXdelta,
|
|
13
19
|
patchBuffer,
|
|
14
|
-
patchIt
|
|
20
|
+
patchIt,
|
|
21
|
+
readPatchInfo,
|
|
22
|
+
readPatchInfoFile,
|
|
23
|
+
readPatchMetadata,
|
|
24
|
+
readPatchMetadataFile,
|
|
25
|
+
readPatchReadme,
|
|
26
|
+
readPatchReadmeFile
|
|
15
27
|
};
|
|
16
28
|
//# sourceMappingURL=nitro-patch-helper.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-patcher",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Native Node.js Nintendo DS ROM reader, repacker and ZIP/Xdelta patcher",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -68,6 +68,9 @@
|
|
|
68
68
|
"#browser-hmac": "@noble/hashes/hmac.js",
|
|
69
69
|
"#browser-aes": "@noble/ciphers/aes.js"
|
|
70
70
|
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"markdansi": "0.3.3"
|
|
73
|
+
},
|
|
71
74
|
"scripts": {
|
|
72
75
|
"clean": "tsx scripts/clean.ts",
|
|
73
76
|
"build": "pnpm run build:node && pnpm run build:browser",
|