nitro-patcher 1.1.1 → 1.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.
@@ -573,9 +573,15 @@ var createPatch = async (original, modified, options = {}) => {
573
573
  if (sourceFiles.has(path) && before.equals(after)) continue;
574
574
  if (!sourceFiles.has(path) && !/^(data|overlay)\//.test(path) && !/^overarm[97]\.bin$/.test(path))
575
575
  warnings.push(`\u65B0\u589E\u7CFB\u7EDF\u6587\u4EF6\uFF1A${file.path}`);
576
+ changedFiles++;
577
+ if (!sourceFiles.has(path) || after.length < 128 * 1024) {
578
+ entries[file.path] = after;
579
+ continue;
580
+ }
576
581
  try {
577
582
  const delta = await createDelta(before, after);
578
- if (decodeXdelta(before, delta, options).equals(after)) entries[`xdelta/${file.path}`] = delta;
583
+ if (delta.length > after.length * 0.8) entries[file.path] = after;
584
+ else if (decodeXdelta(before, delta, options).equals(after)) entries[`xdelta/${file.path}`] = delta;
579
585
  else {
580
586
  warnings.push(`\u6587\u4EF6\u5DEE\u5206\u89E3\u7801\u5185\u5BB9\u4E0D\u540C\uFF0C\u5DF2\u4FDD\u5B58\u5B8C\u6574\u66FF\u6362\u5185\u5BB9\uFF1A${file.path}`);
581
587
  entries[file.path] = after;
@@ -584,7 +590,6 @@ var createPatch = async (original, modified, options = {}) => {
584
590
  warnings.push(`\u6587\u4EF6\u5DEE\u5206\u68C0\u67E5\u672A\u5B8C\u6210\uFF0C\u5DF2\u4FDD\u5B58\u5B8C\u6574\u66FF\u6362\u5185\u5BB9\uFF1A${file.path}\uFF1A${errorMessage(reason)}`);
585
591
  entries[file.path] = after;
586
592
  }
587
- changedFiles++;
588
593
  }
589
594
  const metadata = { ...options.metadata, formatVersion: 2 };
590
595
  const inputMd5 = hashDigest("md5", input).toString("hex");
@@ -807,4 +812,4 @@ export {
807
812
  patchIt,
808
813
  PatchHelper
809
814
  };
810
- //# sourceMappingURL=chunk-2F24YQ5P.js.map
815
+ //# sourceMappingURL=chunk-PALHUY7B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/patch/xdelta.ts","../src/patch/zip.ts","../src/patch/create.ts","../src/nitro-patch-helper.ts"],"sourcesContent":["/** 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) 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.\ninterface Operation {\n type: \"ADD\" | \"RUN\" | \"COPY\";\n size: number;\n mode: number;\n}\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 = (source: Uint8Array, patch: Uint8Array, options: XdeltaOptions = {}): 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) 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) 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) 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) 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) 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","import { inflateRawSync } from \"#nitro-runtime\";\n\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\nexport interface ZipEntry {\n path: string;\n data: Buffer;\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) 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 readZipEntries = (input: Uint8Array, options: ZipOptions = {}): Map<string, ZipEntry> => {\n if (!(input instanceof Uint8Array)) 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(options.maxUncompressedSize, 1024 * 1024 * 1024, \"maxUncompressedSize\");\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 (bytes.readUInt32LE(offset) === 0x06054b50 && offset + 22 + bytes.readUInt16LE(offset + 20) === bytes.length) {\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) 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, ZipEntry>();\n const ranges: { 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 (size === 0xffffffff || compressedSize === 0xffffffff || localOffset === 0xffffffff || startDisk === 0xffff)\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(bytes.subarray(offset + 46 + nameLength, offset + 46 + nameLength + extraLength));\n const path = filename(rawName, flags, fields).replaceAll(\"\\\\\", \"/\");\n const name = path.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 (bytes.readUInt16LE(localOffset + 6) !== flags || bytes.readUInt16LE(localOffset + 8) !== method)\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, { path, 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\n/** Read file contents using normalized paths, with the final duplicate winning. */\nexport const readZip = (input: Uint8Array, options: ZipOptions = {}): Map<string, Buffer> =>\n new Map([...readZipEntries(input, options)].map(([name, entry]) => [name, entry.data]));\n\n/** Extract one file using the same normalized, case-insensitive paths as readZip. */\nexport const extractZipEntry = (input: Uint8Array, entryName: string, options: ZipOptions = {}): Buffer => {\n if (typeof entryName !== \"string\" || !entryName) throw new TypeError(\"ZIP entry name must be a non-empty string\");\n const normalized = entryName.replaceAll(\"\\\\\", \"/\").toLowerCase();\n if (normalized.endsWith(\"/\")) fail(`entry ${JSON.stringify(entryName)} is a directory`);\n const entry = readZip(input, options).get(normalized);\n if (entry === undefined) return fail(`entry ${JSON.stringify(entryName)} not found`);\n return entry;\n};\n","import { init, xd3_encode_memory, xd3_smatch_cfg } from \"@xzonn/xdelta3-wasm\";\nimport { zipSync } from \"fflate\";\n\nimport { hashDigest } from \"#nitro-runtime\";\n\nimport { decodeXdelta } from \"./xdelta\";\nimport { bytes } from \"../nitro/binary\";\nimport { crc16 } from \"../nitro/crc\";\nimport { decryptSecureArea, encryptSecureArea } from \"../nitro/crypto\";\nimport type { NitroFolder } from \"../nitro/filesystem\";\nimport { Header } from \"../nitro/header\";\nimport { NDSFile } from \"../nitro/nds-file\";\nimport { patchBuffer } from \"../nitro-patch-helper\";\nimport type { PatchMetadata, PatchOptions } from \"../nitro-patch-helper\";\n\nexport interface CreatePatchOptions extends PatchOptions {\n metadata?: Omit<PatchMetadata, \"formatVersion\">;\n readme?: string;\n}\n\nexport interface CreatePatchResult {\n buffer: Uint8Array<ArrayBuffer>;\n changedFiles: number;\n warnings: string[];\n}\n\nconst normalizePath = (path: string): string => path.replaceAll(\"\\\\\", \"/\").toLowerCase();\nconst listFiles = (rom: NDSFile) => new Map(rom.listFiles().map((file) => [normalizePath(file.path), file]));\nconst listDirectories = (folder: NitroFolder, prefix = \"data\"): string[] =>\n [prefix, ...folder.folders.flatMap((child) => listDirectories(child, `${prefix}/${child.name}`))].sort();\nconst getUnnamedFiles = (rom: NDSFile): Buffer[] => {\n const namedIds = new Set(\n rom\n .listFiles()\n .filter((file) => /^(data|overlay)\\//i.test(file.path))\n .map((file) => file.id),\n );\n return rom.fatTable.entries\n .filter((entry) => !namedIds.has(entry.id))\n .map((entry) => rom.original.subarray(entry.offset, entry.offset + entry.size));\n};\n\n// Repacking assigns physical locations, capacities and derived checksums.\nconst normalizeHeader = (rom: NDSFile): Buffer => {\n const header = new Header(rom.getFile(\"header.bin\"));\n header.size = 0x20000;\n for (const field of [\n \"ARM9romOffset\",\n \"ARM9size\",\n \"ARM7romOffset\",\n \"ARM7size\",\n \"FNToffset\",\n \"FNTsize\",\n \"FAToffset\",\n \"FATsize\",\n \"ARM9overlayOffset\",\n \"ARM9overlaySize\",\n \"ARM7overlayOffset\",\n \"ARM7overlaySize\",\n \"bannerOffset\",\n \"secureCRC16\",\n \"ROMsize\",\n \"logoCRC16\",\n \"headerCRC16\",\n ] as const)\n header[field] = 0;\n if (header.unitCode === 2) {\n for (const field of [\n \"ntr_region_end\",\n \"twl_region_start\",\n \"dsi9_rom_offset\",\n \"dsi7_rom_offset\",\n \"digest_ntr_start\",\n \"digest_ntr_size\",\n \"digest_twl_start\",\n \"digest_twl_size\",\n \"sector_hashtable_start\",\n \"sector_hashtable_size\",\n \"block_hashtable_start\",\n \"block_hashtable_size\",\n \"total_rom_size\",\n \"modcrypt1_start\",\n \"modcrypt1_size\",\n \"modcrypt2_start\",\n \"modcrypt2_size\",\n ] as const)\n header[field] = 0;\n for (const field of [\n \"hmac_arm9\",\n \"hmac_arm7\",\n \"hmac_digest_master\",\n \"hmac_icon_title\",\n \"hmac_arm9i\",\n \"hmac_arm7i\",\n \"hmac_arm9_no_secure\",\n \"rsa_signature\",\n ] as const)\n header[field] = Buffer.alloc(header[field].length);\n }\n return header.raw;\n};\n\nconst compareContent = (actual: Buffer, expected: Buffer, path: string, warnings: string[]): void => {\n if (!actual.equals(expected)) warnings.push(`重建内容与修改后的 ROM 不同:${path}`);\n};\n\nconst errorMessage = (reason: unknown): string => (reason instanceof Error ? reason.message : String(reason));\n\nconst parseRom = (input: Buffer, label: string, warnings: string[]): NDSFile => {\n try {\n const rom = new NDSFile(input, { dsiMode: \"complete\" });\n rom.listFiles();\n return rom;\n } catch (reason) {\n try {\n const rom = new NDSFile(input);\n rom.listFiles();\n warnings.push(`${label} 使用常规模式解析:${errorMessage(reason)}`);\n return rom;\n } catch (error) {\n throw new Error(`无法解析${label}:${errorMessage(error)}`, { cause: error });\n }\n }\n};\n\nconst createDelta = async (before: Buffer, after: Buffer): Promise<Buffer> => {\n await init();\n const windowSize = 64 * 1024;\n const maxSize = after.length * 2 + Math.ceil(after.length / windowSize) * 64 + 1024;\n const result = xd3_encode_memory(after, before, maxSize, xd3_smatch_cfg.DEFAULT, windowSize);\n if (result.ret !== 0) throw new Error(`Cannot encode VCDIFF: ${result.str}`);\n return Buffer.from(result.output);\n};\n\nconst verifyContent = (actual: NDSFile, expected: NDSFile, arm9: Buffer, warnings: string[]): void => {\n const actualFiles = listFiles(actual);\n const expectedFiles = listFiles(expected);\n if ([...actualFiles.keys()].sort().join(\"\\n\") !== [...expectedFiles.keys()].sort().join(\"\\n\"))\n warnings.push(\"重建 ROM 的文件路径集合与修改后的 ROM 不同。\");\n const actualDirectories = new Set(listDirectories(actual.data));\n const expectedDirectories = new Set(listDirectories(expected.data));\n for (const path of expectedDirectories)\n if (!actualDirectories.has(path)) {\n if ([...expectedFiles.keys()].some((name) => name.startsWith(normalizePath(`${path}/`))))\n warnings.push(`重建 ROM 缺少目录:${path}`);\n else warnings.push(`新增空目录已忽略:${path}`);\n }\n for (const path of actualDirectories)\n if (!expectedDirectories.has(path)) {\n if ([...actualFiles.keys()].some((name) => name.startsWith(normalizePath(`${path}/`))))\n warnings.push(`重建 ROM 保留了原始目录:${path}`);\n else warnings.push(`已保留原始 ROM 的空目录:${path}`);\n }\n for (const [path, file] of expectedFiles) {\n if ([\"header.bin\", \"fnt.bin\", \"fat.bin\", \"banner.bin\", \"overarm9.bin\", \"overarm7.bin\"].includes(path)) continue;\n if (!actualFiles.has(path)) continue;\n let content = path === \"arm9.bin\" ? Buffer.from(arm9) : expected.getFile(file.path);\n if (path === \"arm9.bin\" && expected.header.unitCode === 2) {\n const plain = expected.header.decrypted ? content : decryptSecureArea(expected.header.gameCode, content);\n plain.writeUInt16LE(crc16(plain.subarray(0x10, 0x800)), 0xe);\n content = expected.header.decrypted ? plain : encryptSecureArea(expected.header.gameCode, plain);\n }\n compareContent(actual.getFile(file.path), content, file.path, warnings);\n }\n compareContent(actual.banner.toBuffer(), expected.banner.toBuffer(), \"banner.bin\", warnings);\n compareContent(normalizeHeader(actual), normalizeHeader(expected), \"header.bin\", warnings);\n compareContent(actual.header.dlp_signature, expected.header.dlp_signature, \"Download Play 签名\", warnings);\n for (const [left, right] of [\n [actual.overlay9Table, expected.overlay9Table],\n [actual.overlay7Table, expected.overlay7Table],\n ]) {\n if (left.entries.length !== right.entries.length) warnings.push(\"重建 ROM 的覆盖模块数量与修改后的 ROM 不同。\");\n for (const entry of right.entries) {\n const other = left.entries.find((item) => item.overlayId === entry.overlayId);\n if (!other) {\n warnings.push(`重建 ROM 缺少覆盖模块:${entry.overlayId}`);\n continue;\n }\n for (const field of [\n \"ramAddress\",\n \"ramSize\",\n \"bssSize\",\n \"staticInitialiserStartAddress\",\n \"staticInitialiserEndAddress\",\n \"reserved\",\n ] as const)\n if (other[field] !== entry[field])\n warnings.push(`重建覆盖模块 ${entry.overlayId} 的 ${field} 与修改后的 ROM 不同。`);\n }\n }\n const actualUnnamed = getUnnamedFiles(actual);\n const expectedUnnamed = getUnnamedFiles(expected);\n if (actualUnnamed.length !== expectedUnnamed.length)\n warnings.push(\"重建 ROM 的未命名 FAT 项数量与修改后的 ROM 不同。\");\n actualUnnamed.forEach((content, index) => {\n if (expectedUnnamed[index]) compareContent(content, expectedUnnamed[index], `未命名 FAT 项 ${index}`, warnings);\n });\n if (actual.header.unitCode === 2)\n for (const [name, check] of Object.entries(actual.verifyDsi()))\n if (check.status === \"invalid\") warnings.push(`重建 ROM 的 DSi 校验结果为 invalid:${name}`);\n};\n\nconst verifyPatch = (\n input: Buffer,\n archive: Uint8Array,\n target: NDSFile,\n arm9: Buffer,\n options: PatchOptions,\n warnings: string[],\n): boolean => {\n let output: Buffer;\n try {\n output = patchBuffer(input, archive, options).buffer;\n } catch (reason) {\n warnings.push(`补丁应用检查未完成:${errorMessage(reason)}`);\n return false;\n }\n try {\n verifyContent(parseRom(output, \"重建 ROM\", warnings), target, arm9, warnings);\n } catch (reason) {\n warnings.push(`补丁数据核对未完成:${errorMessage(reason)}`);\n }\n return true;\n};\n\n/** Create an XZP; differences and application checks are returned as warnings. */\nexport const createPatch = async (\n original: Uint8Array,\n modified: Uint8Array,\n options: CreatePatchOptions = {},\n): Promise<CreatePatchResult> => {\n const input = bytes(original, \"original ROM\");\n const targetBytes = bytes(modified, \"modified ROM\");\n const warnings: string[] = [];\n const source = parseRom(input, \"原始 ROM\", warnings);\n const target = parseRom(targetBytes, \"修改后的 ROM\", warnings);\n let preprocessing = false;\n if (source.header.gameCode !== target.header.gameCode)\n warnings.push(`两个 ROM 的游戏代码不同:${source.header.gameCode} / ${target.header.gameCode}`);\n if (source.header.unitCode !== target.header.unitCode)\n warnings.push(`两个 ROM 的 unitCode 不同:${source.header.unitCode} / ${target.header.unitCode}`);\n if (source.header.ROMversion !== target.header.ROMversion)\n warnings.push(`两个 ROM 的修订版本不同:${source.header.ROMversion} / ${target.header.ROMversion}`);\n if (source.header.gameCode !== target.header.gameCode || source.header.unitCode !== target.header.unitCode)\n preprocessing = true;\n const sourceFiles = listFiles(source);\n const targetFiles = listFiles(target);\n const headerChanged = !normalizeHeader(source).equals(normalizeHeader(target));\n const sourceArm9 = source.getFile(\"arm9.bin\");\n const targetArm9 = target.getFile(\"arm9.bin\");\n const sourcePostData = sourceArm9.subarray(source.header.ARM9size);\n const targetPostData = targetArm9.subarray(target.header.ARM9size);\n if (!sourcePostData.equals(targetPostData)) warnings.push(\"两个 ROM 的 ARM9 尾部记录不同。\");\n for (const [label, rom, entries] of [\n [\"原始 ROM\", source, sourceFiles],\n [\"修改后的 ROM\", target, targetFiles],\n ] as const) {\n const file = entries.get(\"arm9.bin\")!;\n const end = file.offset + file.size;\n if (rom.header.nitrocode && rom.original.subarray(end, end + 4).equals(Buffer.from(\"2106c0de\", \"hex\")))\n warnings.push(`${label} 的 ARM9 含有额外尾部记录。`);\n }\n const targetProgram = targetArm9.subarray(0, target.header.ARM9size);\n const arm9 =\n sourcePostData.length && targetProgram.subarray(-sourcePostData.length).equals(sourcePostData)\n ? targetProgram\n : Buffer.concat([targetProgram, sourcePostData]);\n const removed = [...sourceFiles.keys()].filter((path) => !targetFiles.has(path));\n if (removed.length) {\n warnings.push(`修改后的 ROM 缺少原始 ROM 中的 ${removed.length} 个文件。`);\n preprocessing = true;\n }\n const sourceUnnamed = getUnnamedFiles(source);\n const targetUnnamed = getUnnamedFiles(target);\n if (\n sourceUnnamed.length !== targetUnnamed.length ||\n sourceUnnamed.some((content, index) => !content.equals(targetUnnamed[index] ?? Buffer.alloc(0)))\n ) {\n warnings.push(\"两个 ROM 的未命名 FAT 数据不同。\");\n preprocessing = true;\n }\n const entries: Record<string, Uint8Array> = {};\n let changedFiles = 0;\n for (const [path, file] of targetFiles) {\n if (path === \"fnt.bin\" || path === \"fat.bin\") continue;\n if (path === \"header.bin\" && !headerChanged) continue;\n const before = sourceFiles.has(path) ? source.getFile(file.path) : Buffer.alloc(0);\n const after = path === \"arm9.bin\" ? arm9 : target.getFile(file.path);\n if (sourceFiles.has(path) && before.equals(after)) continue;\n if (!sourceFiles.has(path) && !/^(data|overlay)\\//.test(path) && !/^overarm[97]\\.bin$/.test(path))\n warnings.push(`新增系统文件:${file.path}`);\n changedFiles++;\n if (!sourceFiles.has(path) || after.length < 128 * 1024) {\n entries[file.path] = after;\n continue;\n }\n try {\n const delta = await createDelta(before, after);\n if (delta.length > after.length * 0.8) entries[file.path] = after;\n else if (decodeXdelta(before, delta, options).equals(after)) entries[`xdelta/${file.path}`] = delta;\n else {\n warnings.push(`文件差分解码内容不同,已保存完整替换内容:${file.path}`);\n entries[file.path] = after;\n }\n } catch (reason) {\n warnings.push(`文件差分检查未完成,已保存完整替换内容:${file.path}:${errorMessage(reason)}`);\n entries[file.path] = after;\n }\n }\n const metadata: PatchMetadata = { ...options.metadata, formatVersion: 2 };\n const inputMd5 = hashDigest(\"md5\", input).toString(\"hex\");\n entries[\"metadata.json\"] = Buffer.from(JSON.stringify(metadata, null, 2));\n entries[\"md5.txt\"] = Buffer.from(inputMd5);\n entries[\"readme.md\"] = Buffer.from(options.readme ?? \"\");\n let archive = zipSync(entries, { level: 9 });\n if (!preprocessing) preprocessing = !verifyPatch(input, archive, target, arm9, options, warnings);\n if (preprocessing) {\n try {\n const delta = await createDelta(input, targetBytes);\n const formatVersion = target.header.unitCode & 2 && !target.twl?.complete ? 1 : 2;\n archive = zipSync(\n {\n [`preprocessing/${inputMd5}.xdelta`]: delta,\n \"metadata.json\": Buffer.from(JSON.stringify({ ...metadata, formatVersion }, null, 2)),\n \"md5.txt\": entries[\"md5.txt\"],\n \"readme.md\": entries[\"readme.md\"],\n },\n { level: 9 },\n );\n changedFiles = 1;\n warnings.push(\"补丁使用整份 ROM 的预处理差分保存修改。\");\n verifyPatch(input, archive, target, targetArm9, options, warnings);\n } catch (reason) {\n warnings.push(`预处理差分生成未完成,已保留按文件补丁包:${errorMessage(reason)}`);\n }\n }\n return { buffer: new Uint8Array(archive), changedFiles, warnings };\n};\n","// Native TypeScript port of NitroPatchHelper/PatchHelper.cs, GPL-3.0.\nimport { hashDigest, readFile } from \"#nitro-runtime\";\n\nimport { bytes } from \"./nitro/binary\";\nimport { OverlayTable } from \"./nitro/filesystem\";\nimport { NDSFile } from \"./nitro/nds-file\";\nimport type { DsiVerification } from \"./nitro/twl-complete\";\nimport { decodeXdelta } from \"./patch/xdelta\";\nimport { extractZipEntry as extractEntry, readZip, readZipEntries } from \"./patch/zip\";\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 dsi?: DsiVerification;\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 formatVersion?: number;\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 if (value.formatVersion !== undefined) {\n const version = value.formatVersion;\n if (typeof version !== \"number\" || !Number.isSafeInteger(version) || version < 1)\n throw new Error(\"metadata.json formatVersion must be a positive safe integer\");\n if (version !== 1 && version !== 2) throw new Error(`Unsupported XZP format version: ${version}`);\n result.formatVersion = version;\n }\n for (const field of [\"id\", \"author\", \"name\", \"homepage\", \"version\"] as const) {\n const fieldValue = value[field];\n if (fieldValue === undefined) continue;\n if (typeof fieldValue === \"string\") result[field] = fieldValue;\n else invalidMetadataField(field, \"必须是字符串。\");\n }\n if (value.isBeta === undefined) return result;\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 = (patch: Uint8Array, options: PatchOptions = {}): PatchMetadata | null =>\n readMetadata(readZip(patch, options));\n\n/** Read a root README.md or README.txt from a NitroPatcher ZIP package. */\nexport const readPatchReadme = (patch: Uint8Array, options: PatchOptions = {}): PatchReadme | null =>\n 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/** Extract one file from a ZIP archive entirely in memory. */\nexport const extractZipEntry = (archive: Uint8Array, entryName: string, options: PatchOptions = {}): Buffer =>\n extractEntry(archive, entryName, options);\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 (patchPath: string, options: PatchOptions = {}): Promise<PatchReadme | null> =>\n readPatchReadme(await readFile(patchPath), options);\n\n/** Read patch metadata and README content from disk. */\nexport const readPatchInfoFile = async (patchPath: string, options: PatchOptions = {}): Promise<PatchInfo> =>\n readPatchInfo(await readFile(patchPath), options);\n\n/** Apply an existing NitroPatcher ZIP package entirely in memory. */\nexport const patchBuffer = (original: Uint8Array, patch: Uint8Array, options: PatchOptions = {}): PatchResult => {\n const input = bytes(original, \"original ROM\");\n const entries = readZipEntries(patch, options);\n const patches = new Map([...entries].map(([name, entry]) => [name, entry.data]));\n const formatVersion = readMetadata(patches)?.formatVersion ?? 1;\n const inputMd5 = md5(input);\n const preprocessing = patches.get(`preprocessing/${inputMd5}.xdelta`);\n const romOptions = { ...options, dsiMode: formatVersion === 2 ? (\"complete\" as const) : (\"legacy\" as const) };\n const rom = new NDSFile(preprocessing ? decodeXdelta(input, preprocessing, options) : input, romOptions);\n if (formatVersion === 2)\n for (const name of [\"arm9i.bin\", \"arm7i.bin\"]) {\n if ((patches.has(name) || patches.has(`xdelta/${name}`)) && !rom.hasFile(name))\n throw new Error(`${name} requires a complete DSi-enhanced cartridge`);\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 ? decodeXdelta(rom.getFile(path), delta, options) : patches.get(normalized);\n if (replacement) rom.replaceFile(path, replacement);\n }\n if (formatVersion === 2) {\n for (const name of [\"overarm9.bin\", \"overarm7.bin\"])\n if (!rom.hasFile(name)) {\n const delta = patches.get(`xdelta/${name}`);\n const replacement = delta ? decodeXdelta(Buffer.alloc(0), delta, options) : patches.get(name);\n if (replacement) rom.replaceFile(name, replacement);\n }\n const candidates = new Map<string, { path: string; data: Buffer; delta: boolean }>();\n for (const [name, entry] of entries) {\n const delta = name.startsWith(\"xdelta/\");\n const target = delta ? name.slice(7) : name;\n if ((!target.startsWith(\"data/\") && !target.startsWith(\"overlay/\")) || rom.hasFile(target)) continue;\n if (!delta && entries.has(`xdelta/${target}`)) continue;\n candidates.set(target, { path: delta ? entry.path.slice(7) : entry.path, data: entry.data, delta });\n }\n if (candidates.size && (patches.has(\"fnt.bin\") || patches.has(\"xdelta/fnt.bin\")))\n throw new Error(\"Adding files conflicts with an fnt.bin replacement\");\n const overlayTables = new Map<string, OverlayTable>();\n for (const name of [\"overarm9.bin\", \"overarm7.bin\"])\n if (rom.hasFile(name)) {\n const raw = rom.getFile(name);\n overlayTables.set(name, new OverlayTable(raw, 0, raw.length, name === \"overarm9.bin\"));\n }\n const additions = [...candidates.values()].sort((a, b) => Buffer.compare(Buffer.from(a.path), Buffer.from(b.path)));\n for (const entry of additions) {\n const content = entry.delta ? decodeXdelta(Buffer.alloc(0), entry.data, options) : entry.data;\n if (entry.path.toLowerCase().startsWith(\"overlay/\")) {\n const match = /^overlay\\/overlay(7)?_(\\d{4,})\\.bin$/i.exec(entry.path);\n const overlayId = match?.[2];\n const arm9 = !match?.[1];\n if (!overlayId || String(Number(overlayId)).padStart(4, \"0\") !== overlayId)\n throw new Error(`Invalid overlay path: ${entry.path}`);\n const name = arm9 ? \"overarm9.bin\" : \"overarm7.bin\";\n const table = overlayTables.get(name);\n if (!table) throw new Error(`New overlay requires ${name}: ${entry.path}`);\n const metadata = table.entries.find((item) => item.overlayId === Number(overlayId));\n if (!metadata) throw new Error(`New overlay is missing its table entry: ${entry.path}`);\n rom.addOverlay(arm9, metadata, content);\n } else rom.addFile(entry.path, content);\n }\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 ...(formatVersion === 2 && rom.header.unitCode === 2 && { dsi: new NDSFile(buffer, romOptions).verifyDsi() }),\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 extractZipEntry,\n patchBuffer,\n patchIt,\n readPatchInfo,\n readPatchInfoFile,\n readPatchMetadata,\n readPatchMetadataFile,\n readPatchReadme,\n readPatchReadmeFile,\n});\nexport { createPatch } from \"./patch/create\";\nexport type { CreatePatchOptions, CreatePatchResult } from \"./patch/create\";\nexport { decodeXdelta } from \"./patch/xdelta\";\nexport type { XdeltaOptions } from \"./patch/xdelta\";\n"],"mappings":"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","names":["bytes","fail","bytes","offset","entries","filename","extractZipEntry","table"]}
package/dist/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ createPatch,
3
4
  patchBuffer,
4
5
  readPatchInfo
5
- } from "./chunk-2F24YQ5P.js";
6
+ } from "./chunk-PALHUY7B.js";
6
7
  import {
7
8
  NDSFile
8
9
  } from "./chunk-LK3JXA24.js";
@@ -17,15 +18,17 @@ var help = `nitro-patcher \u2014 NDS ROM \u8865\u4E01\u5DE5\u5177
17
18
  \u7528\u6CD5\uFF1A
18
19
  nitro-patcher [--json] [--dry-run] <\u539F\u59CBROM> <\u8865\u4E01ZIP> [\u8F93\u51FAROM]
19
20
  nitro-patcher patch [--json] [--dry-run] --input <\u539F\u59CBROM> --patch <\u8865\u4E01ZIP> [--output <\u8F93\u51FAROM>]
21
+ nitro-patcher create [--json] [--dry-run] --input <\u539F\u59CBROM> --modified <\u4FEE\u6539\u540EROM> [--output <\u8F93\u51FAXZP>]
20
22
  nitro-patcher extract [--json] --input <ROM> --output <\u76EE\u5F55>
21
23
  nitro-patcher inspect [--json] --input <ROM>
22
24
 
23
25
  --json \u7ED3\u679C\u8F93\u51FA\u4E3A JSON \u683C\u5F0F
24
- --dry-run \u5B8C\u6574\u6267\u884C\u8865\u4E01\u6D41\u7A0B\uFF0C\u4F46\u4E0D\u5199\u5165\u8F93\u51FA ROM
26
+ --dry-run \u5B8C\u6574\u6267\u884C\u8865\u4E01\u6D41\u7A0B\uFF0C\u4F46\u4E0D\u5199\u5165\u8F93\u51FA\u6587\u4EF6
25
27
  --dsi-mode extract/inspect \u7684 DSi \u5904\u7406\u6A21\u5F0F\uFF1Alegacy \u6216 complete
26
28
  -i, --input \u8F93\u5165 ROM \u8DEF\u5F84
27
29
  -p, --patch \u8865\u4E01 ZIP \u8DEF\u5F84
28
- -o, --output \u8F93\u51FA ROM \u6216\u63D0\u53D6\u76EE\u5F55\uFF1B\u8BD5\u8FD0\u884C\u65F6\u53EF\u7701\u7565
30
+ -m, --modified \u4FEE\u6539\u540E\u7684 ROM \u8DEF\u5F84
31
+ -o, --output \u8F93\u51FA ROM\u3001\u8865\u4E01 XZP \u6216\u63D0\u53D6\u76EE\u5F55\uFF1B\u8BD5\u8FD0\u884C\u65F6\u53EF\u7701\u7565
29
32
  -h, --help \u663E\u793A\u5E2E\u52A9
30
33
  -v, --version \u663E\u793A\u7248\u672C`;
31
34
  var sameFile = async (input, output) => {
@@ -137,6 +140,7 @@ var main = async () => {
137
140
  "dsi-mode": { type: "string" },
138
141
  input: { type: "string", short: "i" },
139
142
  patch: { type: "string", short: "p" },
143
+ modified: { type: "string", short: "m" },
140
144
  output: { type: "string", short: "o" }
141
145
  }
142
146
  });
@@ -159,13 +163,16 @@ var main = async () => {
159
163
  dryRun: values["dry-run"] ?? false,
160
164
  input: values.input,
161
165
  patch: values.patch,
166
+ modified: values.modified,
162
167
  output: values.output,
163
168
  dsiMode
164
169
  };
165
170
  const command = positionals.length === 1 ? positionals[0] : void 0;
166
171
  if (positionals.length !== 0 && positionals.length !== 1 && positionals.length !== 3) throw new Error(help);
167
- if (command !== void 0 && command !== "patch" && command !== "extract" && command !== "inspect")
172
+ if (command !== void 0 && command !== "patch" && command !== "create" && command !== "extract" && command !== "inspect")
168
173
  throw new Error(help);
174
+ const creating = command === "create";
175
+ if (!creating && options.modified !== void 0) throw new Error(help);
169
176
  if (command === "extract") {
170
177
  await extractRom(options);
171
178
  return;
@@ -175,33 +182,49 @@ var main = async () => {
175
182
  return;
176
183
  }
177
184
  const dryRun = options.dryRun;
178
- if (options.dsiMode !== void 0) throw new Error("patch selects DSi processing from metadata.json formatVersion");
185
+ if (options.dsiMode !== void 0)
186
+ throw new Error(
187
+ creating ? "create selects DSi processing from the input ROMs" : "patch selects DSi processing from metadata.json formatVersion"
188
+ );
189
+ if (creating && (!options.modified || options.patch !== void 0)) throw new Error(help);
179
190
  const usesLegacyPaths = positionals.length === 3;
180
191
  if (usesLegacyPaths && (values.input !== void 0 || values.patch !== void 0 || values.output !== void 0))
181
192
  throw new Error(help);
182
- if (!usesLegacyPaths && (!values.input || !values.patch || !dryRun && !values.output)) throw new Error(help);
183
- const [original, patch, output] = usesLegacyPaths ? positionals : [values.input, values.patch, values.output];
184
- if (!original || !patch) throw new Error(help);
193
+ if (!usesLegacyPaths && (!values.input || !(creating ? values.modified : values.patch) || !dryRun && !values.output))
194
+ throw new Error(help);
195
+ const [original, secondInput, output] = usesLegacyPaths ? positionals : [values.input, creating ? values.modified : values.patch, values.output];
196
+ if (!original || !secondInput) throw new Error(help);
185
197
  if (!dryRun) {
186
198
  if (!output) throw new Error(help);
187
- if (await sameFile(original, output) || await sameFile(patch, output))
188
- throw new Error("\u8F93\u51FA\u8DEF\u5F84\u4E0D\u80FD\u8986\u76D6\u539F\u59CB ROM \u6216\u8865\u4E01\u5305\u3002");
199
+ if (await sameFile(original, output) || await sameFile(secondInput, output))
200
+ throw new Error(creating ? "\u8F93\u51FA\u8DEF\u5F84\u4E0D\u80FD\u8986\u76D6\u539F\u59CB ROM \u6216\u4FEE\u6539\u540E\u7684 ROM\u3002" : "\u8F93\u51FA\u8DEF\u5F84\u4E0D\u80FD\u8986\u76D6\u539F\u59CB ROM \u6216\u8865\u4E01\u5305\u3002");
189
201
  }
190
- const [originalBytes, patchBytes] = await Promise.all([readFile(original), readFile(patch)]);
191
- const info = readPatchInfo(patchBytes);
192
- const result = patchBuffer(originalBytes, patchBytes);
202
+ const [originalBytes, secondInputBytes] = await Promise.all([readFile(original), readFile(secondInput)]);
203
+ const info = creating ? void 0 : readPatchInfo(secondInputBytes);
204
+ const result = creating ? await createPatch(originalBytes, secondInputBytes) : patchBuffer(originalBytes, secondInputBytes);
193
205
  if (!dryRun) {
194
206
  if (!output) throw new Error(help);
195
207
  const destination = join(await realpath(dirname(resolve(output))), basename(output));
196
208
  const temporary = await mkdtemp(join(dirname(destination), ".nitro-patcher-"));
197
209
  try {
198
- const file = join(temporary, "output.nds");
210
+ const file = join(temporary, creating ? "patch.xzp" : "output.nds");
199
211
  await writeFile(file, result.buffer);
200
212
  await rename(file, destination);
201
213
  } finally {
202
214
  await rm(temporary, { recursive: true, force: true });
203
215
  }
204
216
  }
217
+ if ("changedFiles" in result) {
218
+ const { changedFiles, warnings } = result;
219
+ if (options.json) {
220
+ console.log(JSON.stringify({ changedFiles, warnings, ...dryRun && { dryRun: true } }));
221
+ } else {
222
+ if (dryRun) console.log("\u8BD5\u8FD0\u884C\u5B8C\u6210\uFF0C\u672A\u5199\u5165\u8F93\u51FA\u8865\u4E01\u3002");
223
+ console.log(`\u5DF2\u521B\u5EFA\u8865\u4E01\uFF0C\u5305\u542B ${changedFiles} \u9879\u6587\u4EF6\u4FEE\u6539\u3002`);
224
+ for (const warning of warnings) console.warn(`\u8B66\u544A\uFF1A${safeTerminalText(warning)}`);
225
+ }
226
+ return;
227
+ }
205
228
  const { returnValue, inputMd5, outputMd5 } = result;
206
229
  if (options.json) {
207
230
  console.log(
@@ -217,13 +240,13 @@ var main = async () => {
217
240
  return;
218
241
  }
219
242
  const sections = [];
220
- if (info.metadata) {
243
+ if (info?.metadata) {
221
244
  const metadata = formatMetadata(info.metadata);
222
245
  if (metadata) sections.push(`\u8865\u4E01\u4FE1\u606F\uFF1A
223
246
 
224
247
  ${metadata}`);
225
248
  }
226
- if (info.readme) {
249
+ if (info?.readme) {
227
250
  const content = safeTerminalText(info.readme.content);
228
251
  const rendered = info.readme.format === "markdown" ? render(content, {
229
252
  width: process.stdout.columns ?? 80,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { render } from \"markdansi\";\nimport { mkdir, mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve, sep } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { NDSFile } from \"./nitro/nds-file\";\nimport { type PatchMetadata, patchBuffer, readPatchInfo } from \"./nitro-patch-helper\";\n\nconst help = `nitro-patcher — NDS ROM 补丁工具\n\n用法:\n nitro-patcher [--json] [--dry-run] <原始ROM> <补丁ZIP> [输出ROM]\n nitro-patcher patch [--json] [--dry-run] --input <原始ROM> --patch <补丁ZIP> [--output <输出ROM>]\n nitro-patcher extract [--json] --input <ROM> --output <目录>\n nitro-patcher inspect [--json] --input <ROM>\n\n --json 结果输出为 JSON 格式\n --dry-run 完整执行补丁流程,但不写入输出 ROM\n --dsi-mode extract/inspect 的 DSi 处理模式:legacy 或 complete\n -i, --input 输入 ROM 路径\n -p, --patch 补丁 ZIP 路径\n -o, --output 输出 ROM 或提取目录;试运行时可省略\n -h, --help 显示帮助\n -v, --version 显示版本`;\n\ninterface CommandOptions {\n json: boolean;\n dryRun: boolean;\n input: string | undefined;\n patch: string | undefined;\n output: string | undefined;\n dsiMode: \"legacy\" | \"complete\" | undefined;\n}\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 extractRom = async (options: CommandOptions): Promise<void> => {\n if (!options.input || !options.output || options.patch !== undefined || options.dryRun) throw new Error(help);\n const destination = join(await realpath(dirname(resolve(options.output))), basename(resolve(options.output)));\n const rom = new NDSFile(await readFile(options.input), { dsiMode: options.dsiMode ?? \"legacy\" });\n const files = rom.listFiles();\n await mkdir(destination, { recursive: true });\n for (const { path } of files) {\n const output = resolve(destination, path);\n if (!output.startsWith(`${destination}${sep}`)) throw new Error(`ROM 文件路径超出输出目录:${path}`);\n await mkdir(dirname(output), { recursive: true });\n await writeFile(output, rom.getFile(path));\n }\n\n if (options.json) {\n console.log(\n JSON.stringify({\n output: destination,\n fileCount: files.length,\n files: files.map(({ path }) => path),\n }),\n );\n } else {\n console.log(`已提取 ${files.length} 个文件到:${destination}`);\n }\n};\n\nconst inspectRom = async (options: CommandOptions): Promise<void> => {\n if (!options.input || options.patch !== undefined || options.output !== undefined || options.dryRun)\n throw new Error(help);\n const input = await readFile(options.input);\n const rom = new NDSFile(input, { dsiMode: options.dsiMode ?? \"legacy\" });\n const info = {\n gameTitle: rom.header.gameTitle.replace(/\\0+$/, \"\"),\n gameCode: rom.header.gameCode,\n size: input.length,\n fileCount: rom.listFiles().length,\n checksums: {\n header: rom.header.headerCRC,\n logo: rom.header.logoCRC,\n secureArea: rom.header.secureCRC,\n banner: rom.banner.bannerCRC,\n },\n ...(options.dsiMode === \"complete\" && { dsi: rom.verifyDsi() }),\n };\n if (options.json) {\n console.log(JSON.stringify(info));\n return;\n }\n console.log(`ROM 信息:\n\n标题:${safeTerminalText(info.gameTitle).replaceAll(\"\\n\", \" \")}\n游戏代码:${safeTerminalText(info.gameCode).replaceAll(\"\\n\", \" \")}\n大小:${info.size} 字节\n文件数量:${info.fileCount}\n校验:\n- Header:${info.checksums.header ? \"通过\" : \"失败\"}\n- Logo:${info.checksums.logo ? \"通过\" : \"失败\"}\n- Secure Area:${info.checksums.secureArea ? \"通过\" : \"失败\"}\n- Banner:${info.checksums.banner ? \"通过\" : \"失败\"}`);\n if (info.dsi)\n for (const [name, result] of Object.entries(info.dsi))\n console.log(`- DSi ${name}:${result.status}${result.reason ? ` (${result.reason})` : \"\"}`);\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 \"dsi-mode\": { type: \"string\" },\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(await readFile(new URL(\"../package.json\", import.meta.url), \"utf8\"));\n if (!metadata || typeof metadata !== \"object\" || !(\"version\" in metadata) || typeof metadata.version !== \"string\")\n throw new Error(\"Invalid package version metadata\");\n console.log(metadata.version);\n return;\n }\n const dsiMode = values[\"dsi-mode\"];\n if (dsiMode !== undefined && dsiMode !== \"legacy\" && dsiMode !== \"complete\")\n throw new Error(\"Invalid --dsi-mode; expected legacy or complete\");\n const options: CommandOptions = {\n json: values.json ?? false,\n dryRun: values[\"dry-run\"] ?? false,\n input: values.input,\n patch: values.patch,\n output: values.output,\n dsiMode,\n };\n const command = positionals.length === 1 ? positionals[0] : undefined;\n if (positionals.length !== 0 && positionals.length !== 1 && positionals.length !== 3) throw new Error(help);\n if (command !== undefined && command !== \"patch\" && command !== \"extract\" && command !== \"inspect\")\n throw new Error(help);\n if (command === \"extract\") {\n await extractRom(options);\n return;\n }\n if (command === \"inspect\") {\n await inspectRom(options);\n return;\n }\n\n const dryRun = options.dryRun;\n if (options.dsiMode !== undefined) throw new Error(\"patch selects DSi processing from metadata.json formatVersion\");\n const usesLegacyPaths = positionals.length === 3;\n if (usesLegacyPaths && (values.input !== undefined || values.patch !== undefined || values.output !== undefined))\n throw new Error(help);\n if (!usesLegacyPaths && (!values.input || !values.patch || (!dryRun && !values.output))) throw new Error(help);\n const [original, patch, output] = usesLegacyPaths ? positionals : [values.input, values.patch, values.output];\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 (options.json) {\n console.log(\n JSON.stringify({\n returnValue,\n inputMd5,\n outputMd5,\n ...info,\n ...(dryRun && { dryRun: true }),\n ...(result.dsi && { dsi: result.dsi }),\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.trim()}`);\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":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { render } from \"markdansi\";\nimport { mkdir, mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve, sep } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { NDSFile } from \"./nitro/nds-file\";\nimport { type PatchMetadata, createPatch, patchBuffer, readPatchInfo } from \"./nitro-patch-helper\";\n\nconst help = `nitro-patcher — NDS ROM 补丁工具\n\n用法:\n nitro-patcher [--json] [--dry-run] <原始ROM> <补丁ZIP> [输出ROM]\n nitro-patcher patch [--json] [--dry-run] --input <原始ROM> --patch <补丁ZIP> [--output <输出ROM>]\n nitro-patcher create [--json] [--dry-run] --input <原始ROM> --modified <修改后ROM> [--output <输出XZP>]\n nitro-patcher extract [--json] --input <ROM> --output <目录>\n nitro-patcher inspect [--json] --input <ROM>\n\n --json 结果输出为 JSON 格式\n --dry-run 完整执行补丁流程,但不写入输出文件\n --dsi-mode extract/inspect 的 DSi 处理模式:legacy 或 complete\n -i, --input 输入 ROM 路径\n -p, --patch 补丁 ZIP 路径\n -m, --modified 修改后的 ROM 路径\n -o, --output 输出 ROM、补丁 XZP 或提取目录;试运行时可省略\n -h, --help 显示帮助\n -v, --version 显示版本`;\n\ninterface CommandOptions {\n json: boolean;\n dryRun: boolean;\n input: string | undefined;\n patch: string | undefined;\n modified: string | undefined;\n output: string | undefined;\n dsiMode: \"legacy\" | \"complete\" | undefined;\n}\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 extractRom = async (options: CommandOptions): Promise<void> => {\n if (!options.input || !options.output || options.patch !== undefined || options.dryRun) throw new Error(help);\n const destination = join(await realpath(dirname(resolve(options.output))), basename(resolve(options.output)));\n const rom = new NDSFile(await readFile(options.input), { dsiMode: options.dsiMode ?? \"legacy\" });\n const files = rom.listFiles();\n await mkdir(destination, { recursive: true });\n for (const { path } of files) {\n const output = resolve(destination, path);\n if (!output.startsWith(`${destination}${sep}`)) throw new Error(`ROM 文件路径超出输出目录:${path}`);\n await mkdir(dirname(output), { recursive: true });\n await writeFile(output, rom.getFile(path));\n }\n\n if (options.json) {\n console.log(\n JSON.stringify({\n output: destination,\n fileCount: files.length,\n files: files.map(({ path }) => path),\n }),\n );\n } else {\n console.log(`已提取 ${files.length} 个文件到:${destination}`);\n }\n};\n\nconst inspectRom = async (options: CommandOptions): Promise<void> => {\n if (!options.input || options.patch !== undefined || options.output !== undefined || options.dryRun)\n throw new Error(help);\n const input = await readFile(options.input);\n const rom = new NDSFile(input, { dsiMode: options.dsiMode ?? \"legacy\" });\n const info = {\n gameTitle: rom.header.gameTitle.replace(/\\0+$/, \"\"),\n gameCode: rom.header.gameCode,\n size: input.length,\n fileCount: rom.listFiles().length,\n checksums: {\n header: rom.header.headerCRC,\n logo: rom.header.logoCRC,\n secureArea: rom.header.secureCRC,\n banner: rom.banner.bannerCRC,\n },\n ...(options.dsiMode === \"complete\" && { dsi: rom.verifyDsi() }),\n };\n if (options.json) {\n console.log(JSON.stringify(info));\n return;\n }\n console.log(`ROM 信息:\n\n标题:${safeTerminalText(info.gameTitle).replaceAll(\"\\n\", \" \")}\n游戏代码:${safeTerminalText(info.gameCode).replaceAll(\"\\n\", \" \")}\n大小:${info.size} 字节\n文件数量:${info.fileCount}\n校验:\n- Header:${info.checksums.header ? \"通过\" : \"失败\"}\n- Logo:${info.checksums.logo ? \"通过\" : \"失败\"}\n- Secure Area:${info.checksums.secureArea ? \"通过\" : \"失败\"}\n- Banner:${info.checksums.banner ? \"通过\" : \"失败\"}`);\n if (info.dsi)\n for (const [name, result] of Object.entries(info.dsi))\n console.log(`- DSi ${name}:${result.status}${result.reason ? ` (${result.reason})` : \"\"}`);\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 \"dsi-mode\": { type: \"string\" },\n input: { type: \"string\", short: \"i\" },\n patch: { type: \"string\", short: \"p\" },\n modified: { type: \"string\", short: \"m\" },\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(await readFile(new URL(\"../package.json\", import.meta.url), \"utf8\"));\n if (!metadata || typeof metadata !== \"object\" || !(\"version\" in metadata) || typeof metadata.version !== \"string\")\n throw new Error(\"Invalid package version metadata\");\n console.log(metadata.version);\n return;\n }\n const dsiMode = values[\"dsi-mode\"];\n if (dsiMode !== undefined && dsiMode !== \"legacy\" && dsiMode !== \"complete\")\n throw new Error(\"Invalid --dsi-mode; expected legacy or complete\");\n const options: CommandOptions = {\n json: values.json ?? false,\n dryRun: values[\"dry-run\"] ?? false,\n input: values.input,\n patch: values.patch,\n modified: values.modified,\n output: values.output,\n dsiMode,\n };\n const command = positionals.length === 1 ? positionals[0] : undefined;\n if (positionals.length !== 0 && positionals.length !== 1 && positionals.length !== 3) throw new Error(help);\n if (\n command !== undefined &&\n command !== \"patch\" &&\n command !== \"create\" &&\n command !== \"extract\" &&\n command !== \"inspect\"\n )\n throw new Error(help);\n const creating = command === \"create\";\n if (!creating && options.modified !== undefined) throw new Error(help);\n if (command === \"extract\") {\n await extractRom(options);\n return;\n }\n if (command === \"inspect\") {\n await inspectRom(options);\n return;\n }\n\n const dryRun = options.dryRun;\n if (options.dsiMode !== undefined)\n throw new Error(\n creating\n ? \"create selects DSi processing from the input ROMs\"\n : \"patch selects DSi processing from metadata.json formatVersion\",\n );\n if (creating && (!options.modified || options.patch !== undefined)) throw new Error(help);\n const usesLegacyPaths = positionals.length === 3;\n if (usesLegacyPaths && (values.input !== undefined || values.patch !== undefined || values.output !== undefined))\n throw new Error(help);\n if (\n !usesLegacyPaths &&\n (!values.input || !(creating ? values.modified : values.patch) || (!dryRun && !values.output))\n )\n throw new Error(help);\n const [original, secondInput, output] = usesLegacyPaths\n ? positionals\n : [values.input, creating ? values.modified : values.patch, values.output];\n if (!original || !secondInput) throw new Error(help);\n if (!dryRun) {\n if (!output) throw new Error(help);\n if ((await sameFile(original, output)) || (await sameFile(secondInput, output)))\n throw new Error(creating ? \"输出路径不能覆盖原始 ROM 或修改后的 ROM。\" : \"输出路径不能覆盖原始 ROM 或补丁包。\");\n }\n const [originalBytes, secondInputBytes] = await Promise.all([readFile(original), readFile(secondInput)]);\n const info = creating ? undefined : readPatchInfo(secondInputBytes);\n const result = creating\n ? await createPatch(originalBytes, secondInputBytes)\n : patchBuffer(originalBytes, secondInputBytes);\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, creating ? \"patch.xzp\" : \"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 if (\"changedFiles\" in result) {\n const { changedFiles, warnings } = result;\n if (options.json) {\n console.log(JSON.stringify({ changedFiles, warnings, ...(dryRun && { dryRun: true }) }));\n } else {\n if (dryRun) console.log(\"试运行完成,未写入输出补丁。\");\n console.log(`已创建补丁,包含 ${changedFiles} 项文件修改。`);\n for (const warning of warnings) console.warn(`警告:${safeTerminalText(warning)}`);\n }\n return;\n }\n const { returnValue, inputMd5, outputMd5 } = result;\n if (options.json) {\n console.log(\n JSON.stringify({\n returnValue,\n inputMd5,\n outputMd5,\n ...info,\n ...(dryRun && { dryRun: true }),\n ...(result.dsi && { dsi: result.dsi }),\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.trim()}`);\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.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  readPatchMetadataFile,
14
14
  readPatchReadme,
15
15
  readPatchReadmeFile
16
- } from "./chunk-2F24YQ5P.js";
16
+ } from "./chunk-PALHUY7B.js";
17
17
  import {
18
18
  Banner,
19
19
  BinaryWriter,
@@ -16,7 +16,7 @@ declare class Banner {
16
16
  chineseTitle: string;
17
17
  koreanTitle: string;
18
18
  constructor(input: Uint8Array, offset?: number, hardSize?: number);
19
- get titleCount(): 8 | 7 | 6;
19
+ get titleCount(): 7 | 8 | 6;
20
20
  getDefSize(hardSize?: number): number;
21
21
  toBuffer(): Buffer<ArrayBuffer>;
22
22
  }
@@ -12,7 +12,7 @@ import {
12
12
  readPatchMetadataFile,
13
13
  readPatchReadme,
14
14
  readPatchReadmeFile
15
- } from "./chunk-2F24YQ5P.js";
15
+ } from "./chunk-PALHUY7B.js";
16
16
  import "./chunk-LK3JXA24.js";
17
17
  export {
18
18
  PatchHelper,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-patcher",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Native Node.js Nintendo DS ROM reader, repacker and ZIP/Xdelta patcher",
5
5
  "type": "module",
6
6
  "engines": {
package/src/cli.ts CHANGED
@@ -5,22 +5,24 @@ import { basename, dirname, join, resolve, sep } from "node:path";
5
5
  import { parseArgs } from "node:util";
6
6
 
7
7
  import { NDSFile } from "./nitro/nds-file";
8
- import { type PatchMetadata, patchBuffer, readPatchInfo } from "./nitro-patch-helper";
8
+ import { type PatchMetadata, createPatch, patchBuffer, readPatchInfo } from "./nitro-patch-helper";
9
9
 
10
10
  const help = `nitro-patcher — NDS ROM 补丁工具
11
11
 
12
12
  用法:
13
13
  nitro-patcher [--json] [--dry-run] <原始ROM> <补丁ZIP> [输出ROM]
14
14
  nitro-patcher patch [--json] [--dry-run] --input <原始ROM> --patch <补丁ZIP> [--output <输出ROM>]
15
+ nitro-patcher create [--json] [--dry-run] --input <原始ROM> --modified <修改后ROM> [--output <输出XZP>]
15
16
  nitro-patcher extract [--json] --input <ROM> --output <目录>
16
17
  nitro-patcher inspect [--json] --input <ROM>
17
18
 
18
19
  --json 结果输出为 JSON 格式
19
- --dry-run 完整执行补丁流程,但不写入输出 ROM
20
+ --dry-run 完整执行补丁流程,但不写入输出文件
20
21
  --dsi-mode extract/inspect 的 DSi 处理模式:legacy 或 complete
21
22
  -i, --input 输入 ROM 路径
22
23
  -p, --patch 补丁 ZIP 路径
23
- -o, --output 输出 ROM 或提取目录;试运行时可省略
24
+ -m, --modified 修改后的 ROM 路径
25
+ -o, --output 输出 ROM、补丁 XZP 或提取目录;试运行时可省略
24
26
  -h, --help 显示帮助
25
27
  -v, --version 显示版本`;
26
28
 
@@ -29,6 +31,7 @@ interface CommandOptions {
29
31
  dryRun: boolean;
30
32
  input: string | undefined;
31
33
  patch: string | undefined;
34
+ modified: string | undefined;
32
35
  output: string | undefined;
33
36
  dsiMode: "legacy" | "complete" | undefined;
34
37
  }
@@ -153,6 +156,7 @@ const main = async (): Promise<void> => {
153
156
  "dsi-mode": { type: "string" },
154
157
  input: { type: "string", short: "i" },
155
158
  patch: { type: "string", short: "p" },
159
+ modified: { type: "string", short: "m" },
156
160
  output: { type: "string", short: "o" },
157
161
  },
158
162
  });
@@ -175,13 +179,22 @@ const main = async (): Promise<void> => {
175
179
  dryRun: values["dry-run"] ?? false,
176
180
  input: values.input,
177
181
  patch: values.patch,
182
+ modified: values.modified,
178
183
  output: values.output,
179
184
  dsiMode,
180
185
  };
181
186
  const command = positionals.length === 1 ? positionals[0] : undefined;
182
187
  if (positionals.length !== 0 && positionals.length !== 1 && positionals.length !== 3) throw new Error(help);
183
- if (command !== undefined && command !== "patch" && command !== "extract" && command !== "inspect")
188
+ if (
189
+ command !== undefined &&
190
+ command !== "patch" &&
191
+ command !== "create" &&
192
+ command !== "extract" &&
193
+ command !== "inspect"
194
+ )
184
195
  throw new Error(help);
196
+ const creating = command === "create";
197
+ if (!creating && options.modified !== undefined) throw new Error(help);
185
198
  if (command === "extract") {
186
199
  await extractRom(options);
187
200
  return;
@@ -192,33 +205,58 @@ const main = async (): Promise<void> => {
192
205
  }
193
206
 
194
207
  const dryRun = options.dryRun;
195
- if (options.dsiMode !== undefined) throw new Error("patch selects DSi processing from metadata.json formatVersion");
208
+ if (options.dsiMode !== undefined)
209
+ throw new Error(
210
+ creating
211
+ ? "create selects DSi processing from the input ROMs"
212
+ : "patch selects DSi processing from metadata.json formatVersion",
213
+ );
214
+ if (creating && (!options.modified || options.patch !== undefined)) throw new Error(help);
196
215
  const usesLegacyPaths = positionals.length === 3;
197
216
  if (usesLegacyPaths && (values.input !== undefined || values.patch !== undefined || values.output !== undefined))
198
217
  throw new Error(help);
199
- if (!usesLegacyPaths && (!values.input || !values.patch || (!dryRun && !values.output))) throw new Error(help);
200
- const [original, patch, output] = usesLegacyPaths ? positionals : [values.input, values.patch, values.output];
201
- if (!original || !patch) throw new Error(help);
218
+ if (
219
+ !usesLegacyPaths &&
220
+ (!values.input || !(creating ? values.modified : values.patch) || (!dryRun && !values.output))
221
+ )
222
+ throw new Error(help);
223
+ const [original, secondInput, output] = usesLegacyPaths
224
+ ? positionals
225
+ : [values.input, creating ? values.modified : values.patch, values.output];
226
+ if (!original || !secondInput) throw new Error(help);
202
227
  if (!dryRun) {
203
228
  if (!output) throw new Error(help);
204
- if ((await sameFile(original, output)) || (await sameFile(patch, output)))
205
- throw new Error("输出路径不能覆盖原始 ROM 或补丁包。");
229
+ if ((await sameFile(original, output)) || (await sameFile(secondInput, output)))
230
+ throw new Error(creating ? "输出路径不能覆盖原始 ROM 或修改后的 ROM。" : "输出路径不能覆盖原始 ROM 或补丁包。");
206
231
  }
207
- const [originalBytes, patchBytes] = await Promise.all([readFile(original), readFile(patch)]);
208
- const info = readPatchInfo(patchBytes);
209
- const result = patchBuffer(originalBytes, patchBytes);
232
+ const [originalBytes, secondInputBytes] = await Promise.all([readFile(original), readFile(secondInput)]);
233
+ const info = creating ? undefined : readPatchInfo(secondInputBytes);
234
+ const result = creating
235
+ ? await createPatch(originalBytes, secondInputBytes)
236
+ : patchBuffer(originalBytes, secondInputBytes);
210
237
  if (!dryRun) {
211
238
  if (!output) throw new Error(help);
212
239
  const destination = join(await realpath(dirname(resolve(output))), basename(output));
213
240
  const temporary = await mkdtemp(join(dirname(destination), ".nitro-patcher-"));
214
241
  try {
215
- const file = join(temporary, "output.nds");
242
+ const file = join(temporary, creating ? "patch.xzp" : "output.nds");
216
243
  await writeFile(file, result.buffer);
217
244
  await rename(file, destination);
218
245
  } finally {
219
246
  await rm(temporary, { recursive: true, force: true });
220
247
  }
221
248
  }
249
+ if ("changedFiles" in result) {
250
+ const { changedFiles, warnings } = result;
251
+ if (options.json) {
252
+ console.log(JSON.stringify({ changedFiles, warnings, ...(dryRun && { dryRun: true }) }));
253
+ } else {
254
+ if (dryRun) console.log("试运行完成,未写入输出补丁。");
255
+ console.log(`已创建补丁,包含 ${changedFiles} 项文件修改。`);
256
+ for (const warning of warnings) console.warn(`警告:${safeTerminalText(warning)}`);
257
+ }
258
+ return;
259
+ }
222
260
  const { returnValue, inputMd5, outputMd5 } = result;
223
261
  if (options.json) {
224
262
  console.log(
@@ -235,11 +273,11 @@ const main = async (): Promise<void> => {
235
273
  }
236
274
 
237
275
  const sections: string[] = [];
238
- if (info.metadata) {
276
+ if (info?.metadata) {
239
277
  const metadata = formatMetadata(info.metadata);
240
278
  if (metadata) sections.push(`补丁信息:\n\n${metadata}`);
241
279
  }
242
- if (info.readme) {
280
+ if (info?.readme) {
243
281
  const content = safeTerminalText(info.readme.content);
244
282
  const rendered =
245
283
  info.readme.format === "markdown"