nitro-patcher 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.ts +5 -2
- package/dist/browser.js +1367 -1418
- package/dist/browser.js.map +4 -4
- package/dist/{chunk-GFF67HX4.js → chunk-GLK3K2HY.js} +221 -282
- package/dist/chunk-GLK3K2HY.js.map +1 -0
- package/dist/{chunk-ZY3OSZCA.js → chunk-IGURDH5L.js} +180 -186
- package/dist/chunk-IGURDH5L.js.map +1 -0
- package/dist/cli.js +5 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/nitro-helper.d.ts +78 -78
- package/dist/nitro-helper.js +1 -1
- package/dist/nitro-patch-helper.d.ts +5 -2
- package/dist/nitro-patch-helper.js +4 -2
- package/package.json +6 -2
- package/src/browser/buffer.ts +1 -1
- package/src/browser.ts +16 -12
- package/src/cli.ts +38 -48
- package/src/index.ts +2 -2
- package/src/nitro/banner.ts +28 -31
- package/src/nitro/binary.ts +6 -16
- package/src/nitro/blz.ts +13 -21
- package/src/nitro/crypto.ts +78 -84
- package/src/nitro/filesystem.ts +36 -39
- package/src/nitro/header.ts +58 -71
- package/src/nitro/nds-file.ts +44 -63
- package/src/nitro/twl.ts +59 -100
- package/src/nitro-helper.ts +14 -14
- package/src/nitro-patch-helper.ts +49 -57
- package/src/patch/xdelta.ts +43 -49
- package/src/patch/zip.ts +47 -56
- package/src/runtime/browser.ts +9 -11
- package/src/runtime/node.ts +14 -15
- package/dist/chunk-GFF67HX4.js.map +0 -1
- package/dist/chunk-ZY3OSZCA.js.map +0 -1
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 { 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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { render } from \"markdansi\";\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\";\n\nimport { type PatchMetadata, patchBuffer, readPatchInfo } 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(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 dryRun = values[\"dry-run\"] ?? false;\n const usesNamedPaths = 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) || (dryRun && (positionals.length < 2 || positionals.length > 3))))\n )\n throw new Error(help);\n const [original, patch, output] = usesNamedPaths ? [values.input, values.patch, values.output] : 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.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.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, PatchInfo, PatchMetadata, PatchOptions, PatchReadme, PatchResult, PatchReturnValue, XdeltaOptions, decodeXdelta, patchBuffer, patchIt, readPatchInfo, readPatchInfoFile, readPatchMetadata, readPatchMetadataFile, readPatchReadme, readPatchReadmeFile } from './nitro-patch-helper.js';
|
|
2
|
+
export { PatchHelper, PatchInfo, PatchMetadata, PatchOptions, PatchReadme, PatchResult, PatchReturnValue, XdeltaOptions, decodeXdelta, extractZipEntry, patchBuffer, patchIt, readPatchInfo, readPatchInfoFile, readPatchMetadata, readPatchMetadataFile, readPatchReadme, readPatchReadmeFile } from './nitro-patch-helper.js';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
PatchHelper,
|
|
4
4
|
PatchReturnValue,
|
|
5
5
|
decodeXdelta,
|
|
6
|
+
extractZipEntry,
|
|
6
7
|
patchBuffer,
|
|
7
8
|
patchIt,
|
|
8
9
|
readPatchInfo,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
readPatchMetadataFile,
|
|
12
13
|
readPatchReadme,
|
|
13
14
|
readPatchReadmeFile
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-IGURDH5L.js";
|
|
15
16
|
import {
|
|
16
17
|
Banner,
|
|
17
18
|
BinaryWriter,
|
|
@@ -32,7 +33,7 @@ import {
|
|
|
32
33
|
encryptSecureArea,
|
|
33
34
|
modcryptKey,
|
|
34
35
|
updateHeaderSignatures
|
|
35
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-GLK3K2HY.js";
|
|
36
37
|
export {
|
|
37
38
|
Banner,
|
|
38
39
|
BinaryWriter,
|
|
@@ -54,6 +55,7 @@ export {
|
|
|
54
55
|
decompressArm9,
|
|
55
56
|
decompressBLZ,
|
|
56
57
|
encryptSecureArea,
|
|
58
|
+
extractZipEntry,
|
|
57
59
|
modcryptKey,
|
|
58
60
|
patchBuffer,
|
|
59
61
|
patchIt,
|
package/dist/nitro-helper.d.ts
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
+
declare class Banner {
|
|
2
|
+
version: number;
|
|
3
|
+
raw: Buffer;
|
|
4
|
+
bannerCRC: boolean;
|
|
5
|
+
tileData: Buffer;
|
|
6
|
+
palette: Buffer;
|
|
7
|
+
japaneseTitle: string;
|
|
8
|
+
englishTitle: string;
|
|
9
|
+
frenchTitle: string;
|
|
10
|
+
germanTitle: string;
|
|
11
|
+
italianTitle: string;
|
|
12
|
+
spanishTitle: string;
|
|
13
|
+
chineseTitle: string;
|
|
14
|
+
koreanTitle: string;
|
|
15
|
+
constructor(input: Uint8Array, offset?: number, hardSize?: number);
|
|
16
|
+
get titleCount(): 8 | 7 | 6;
|
|
17
|
+
getDefSize(hardSize?: number): number;
|
|
18
|
+
toBuffer(): Buffer<ArrayBuffer>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A file in the original ROM, optionally replaced with new bytes. */
|
|
22
|
+
interface NitroFile {
|
|
23
|
+
name: string;
|
|
24
|
+
id: number;
|
|
25
|
+
offset: number;
|
|
26
|
+
size: number;
|
|
27
|
+
replacement?: Buffer;
|
|
28
|
+
}
|
|
29
|
+
interface NitroFolder {
|
|
30
|
+
name: string;
|
|
31
|
+
id: number;
|
|
32
|
+
files: NitroFile[];
|
|
33
|
+
folders: NitroFolder[];
|
|
34
|
+
}
|
|
35
|
+
interface FatEntry {
|
|
36
|
+
id: number;
|
|
37
|
+
offset: number;
|
|
38
|
+
size: number;
|
|
39
|
+
}
|
|
40
|
+
declare class FileAllocationTable {
|
|
41
|
+
readonly entries: FatEntry[];
|
|
42
|
+
readonly sortedIDs: number[];
|
|
43
|
+
constructor(input: Uint8Array, offset: number, size: number);
|
|
44
|
+
}
|
|
45
|
+
declare class FileNameTable {
|
|
46
|
+
readonly root: NitroFolder;
|
|
47
|
+
constructor(fat: FileAllocationTable, input: Uint8Array, offset: number, size: number);
|
|
48
|
+
static findFile(id: number, folder: NitroFolder): NitroFile | undefined;
|
|
49
|
+
}
|
|
50
|
+
interface OverlayItem {
|
|
51
|
+
overlayId: number;
|
|
52
|
+
ramAddress: number;
|
|
53
|
+
ramSize: number;
|
|
54
|
+
bssSize: number;
|
|
55
|
+
staticInitialiserStartAddress: number;
|
|
56
|
+
staticInitialiserEndAddress: number;
|
|
57
|
+
fileId: number;
|
|
58
|
+
reserved: number;
|
|
59
|
+
}
|
|
60
|
+
declare class OverlayTable {
|
|
61
|
+
readonly isArm9: boolean;
|
|
62
|
+
readonly entries: OverlayItem[];
|
|
63
|
+
constructor(input: Uint8Array, offset: number, size: number, isArm9: boolean);
|
|
64
|
+
readBasicOverlays(fat: FileAllocationTable): NitroFile[];
|
|
65
|
+
toBuffer(): Buffer;
|
|
66
|
+
}
|
|
67
|
+
|
|
1
68
|
declare const UnitCode: Readonly<{
|
|
2
69
|
NDS: 0;
|
|
3
70
|
NDS_DSi: 2;
|
|
@@ -109,73 +176,6 @@ declare class Header {
|
|
|
109
176
|
toString(): string;
|
|
110
177
|
}
|
|
111
178
|
|
|
112
|
-
declare class Banner {
|
|
113
|
-
version: number;
|
|
114
|
-
raw: Buffer;
|
|
115
|
-
bannerCRC: boolean;
|
|
116
|
-
tileData: Buffer;
|
|
117
|
-
palette: Buffer;
|
|
118
|
-
japaneseTitle: string;
|
|
119
|
-
englishTitle: string;
|
|
120
|
-
frenchTitle: string;
|
|
121
|
-
germanTitle: string;
|
|
122
|
-
italianTitle: string;
|
|
123
|
-
spanishTitle: string;
|
|
124
|
-
chineseTitle: string;
|
|
125
|
-
koreanTitle: string;
|
|
126
|
-
constructor(input: Uint8Array, offset?: number, hardSize?: number);
|
|
127
|
-
get titleCount(): 8 | 7 | 6;
|
|
128
|
-
getDefSize(hardSize?: number): number;
|
|
129
|
-
toBuffer(): Buffer<ArrayBuffer>;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** A file in the original ROM, optionally replaced with new bytes. */
|
|
133
|
-
interface NitroFile {
|
|
134
|
-
name: string;
|
|
135
|
-
id: number;
|
|
136
|
-
offset: number;
|
|
137
|
-
size: number;
|
|
138
|
-
replacement?: Buffer;
|
|
139
|
-
}
|
|
140
|
-
interface NitroFolder {
|
|
141
|
-
name: string;
|
|
142
|
-
id: number;
|
|
143
|
-
files: NitroFile[];
|
|
144
|
-
folders: NitroFolder[];
|
|
145
|
-
}
|
|
146
|
-
interface FatEntry {
|
|
147
|
-
id: number;
|
|
148
|
-
offset: number;
|
|
149
|
-
size: number;
|
|
150
|
-
}
|
|
151
|
-
declare class FileAllocationTable {
|
|
152
|
-
readonly entries: FatEntry[];
|
|
153
|
-
readonly sortedIDs: number[];
|
|
154
|
-
constructor(input: Uint8Array, offset: number, size: number);
|
|
155
|
-
}
|
|
156
|
-
declare class FileNameTable {
|
|
157
|
-
readonly root: NitroFolder;
|
|
158
|
-
constructor(fat: FileAllocationTable, input: Uint8Array, offset: number, size: number);
|
|
159
|
-
static findFile(id: number, folder: NitroFolder): NitroFile | undefined;
|
|
160
|
-
}
|
|
161
|
-
interface OverlayItem {
|
|
162
|
-
overlayId: number;
|
|
163
|
-
ramAddress: number;
|
|
164
|
-
ramSize: number;
|
|
165
|
-
bssSize: number;
|
|
166
|
-
staticInitialiserStartAddress: number;
|
|
167
|
-
staticInitialiserEndAddress: number;
|
|
168
|
-
fileId: number;
|
|
169
|
-
reserved: number;
|
|
170
|
-
}
|
|
171
|
-
declare class OverlayTable {
|
|
172
|
-
readonly isArm9: boolean;
|
|
173
|
-
readonly entries: OverlayItem[];
|
|
174
|
-
constructor(input: Uint8Array, offset: number, size: number, isArm9: boolean);
|
|
175
|
-
readBasicOverlays(fat: FileAllocationTable): NitroFile[];
|
|
176
|
-
toBuffer(): Buffer;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
179
|
/** Seekable, bounded in-memory writer; unwritten gaps have MemoryStream's zero fill. */
|
|
180
180
|
declare class BinaryWriter {
|
|
181
181
|
readonly maxSize: number;
|
|
@@ -190,17 +190,6 @@ declare class BinaryWriter {
|
|
|
190
190
|
toBuffer(): Buffer<ArrayBufferLike>;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
interface ModcryptHeader {
|
|
194
|
-
gameCode: string;
|
|
195
|
-
gameTitle: string;
|
|
196
|
-
twlInternalFlags: number;
|
|
197
|
-
appflags?: Buffer;
|
|
198
|
-
hmac_arm9i?: Buffer;
|
|
199
|
-
}
|
|
200
|
-
declare const aes128CtrCrypt: (key: Buffer, counter: Buffer, data: Buffer) => Buffer;
|
|
201
|
-
declare const modcryptKey: (header: ModcryptHeader) => Buffer;
|
|
202
|
-
declare const encryptSecureArea: (gameCode: string, input: Buffer) => Buffer;
|
|
203
|
-
|
|
204
193
|
interface Arm9Header {
|
|
205
194
|
reserved2: Buffer;
|
|
206
195
|
ARM9ramAddress: number;
|
|
@@ -214,6 +203,17 @@ declare const decompressArm9: (data: Buffer, header: Arm9Header) => {
|
|
|
214
203
|
};
|
|
215
204
|
declare const compressArm9: (data: Buffer, header: Arm9Header, postSize?: number) => Buffer;
|
|
216
205
|
|
|
206
|
+
interface ModcryptHeader {
|
|
207
|
+
gameCode: string;
|
|
208
|
+
gameTitle: string;
|
|
209
|
+
twlInternalFlags: number;
|
|
210
|
+
appflags?: Buffer;
|
|
211
|
+
hmac_arm9i?: Buffer;
|
|
212
|
+
}
|
|
213
|
+
declare const aes128CtrCrypt: (key: Buffer, counter: Buffer, data: Buffer) => Buffer;
|
|
214
|
+
declare const modcryptKey: (header: ModcryptHeader) => Buffer;
|
|
215
|
+
declare const encryptSecureArea: (gameCode: string, input: Buffer) => Buffer;
|
|
216
|
+
|
|
217
217
|
interface TwlHeader extends ModcryptHeader {
|
|
218
218
|
digest_sector_size: number;
|
|
219
219
|
digest_block_sectorcount: number;
|
package/dist/nitro-helper.js
CHANGED
|
@@ -42,7 +42,7 @@ interface PatchMetadata {
|
|
|
42
42
|
isBeta?: boolean;
|
|
43
43
|
}
|
|
44
44
|
interface PatchReadme {
|
|
45
|
-
format:
|
|
45
|
+
format: "markdown" | "plaintext";
|
|
46
46
|
content: string;
|
|
47
47
|
}
|
|
48
48
|
interface PatchInfo {
|
|
@@ -55,6 +55,8 @@ declare const readPatchMetadata: (patch: Uint8Array, options?: PatchOptions) =>
|
|
|
55
55
|
declare const readPatchReadme: (patch: Uint8Array, options?: PatchOptions) => PatchReadme | null;
|
|
56
56
|
/** Read metadata and README content while parsing the patch package only once. */
|
|
57
57
|
declare const readPatchInfo: (patch: Uint8Array, options?: PatchOptions) => PatchInfo;
|
|
58
|
+
/** Extract one file from a ZIP archive entirely in memory. */
|
|
59
|
+
declare const extractZipEntry: (archive: Uint8Array, entryName: string, options?: PatchOptions) => Buffer;
|
|
58
60
|
/** Read metadata.json from a NitroPatcher ZIP package on disk. */
|
|
59
61
|
declare const readPatchMetadataFile: (patchPath: string, options?: PatchOptions) => Promise<PatchMetadata | null>;
|
|
60
62
|
/** Read a patch README from disk. */
|
|
@@ -67,6 +69,7 @@ declare const patchBuffer: (original: Uint8Array, patch: Uint8Array, options?: P
|
|
|
67
69
|
declare const patchIt: (originalPath: string, patchPath: string, options?: PatchOptions) => Promise<PatchResult>;
|
|
68
70
|
/** Namespaced counterpart of the original C# helper, sharing the same typed functions. */
|
|
69
71
|
declare const PatchHelper: Readonly<{
|
|
72
|
+
extractZipEntry: (archive: Uint8Array, entryName: string, options?: PatchOptions) => Buffer;
|
|
70
73
|
patchBuffer: (original: Uint8Array, patch: Uint8Array, options?: PatchOptions) => PatchResult;
|
|
71
74
|
patchIt: (originalPath: string, patchPath: string, options?: PatchOptions) => Promise<PatchResult>;
|
|
72
75
|
readPatchInfo: (patch: Uint8Array, options?: PatchOptions) => PatchInfo;
|
|
@@ -77,4 +80,4 @@ declare const PatchHelper: Readonly<{
|
|
|
77
80
|
readPatchReadmeFile: (patchPath: string, options?: PatchOptions) => Promise<PatchReadme | null>;
|
|
78
81
|
}>;
|
|
79
82
|
|
|
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 };
|
|
83
|
+
export { PatchHelper, type PatchInfo, type PatchMetadata, type PatchOptions, type PatchReadme, type PatchResult, PatchReturnValue, type XdeltaOptions, decodeXdelta, extractZipEntry, patchBuffer, patchIt, readPatchInfo, readPatchInfoFile, readPatchMetadata, readPatchMetadataFile, readPatchReadme, readPatchReadmeFile };
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
PatchHelper,
|
|
3
3
|
PatchReturnValue,
|
|
4
4
|
decodeXdelta,
|
|
5
|
+
extractZipEntry,
|
|
5
6
|
patchBuffer,
|
|
6
7
|
patchIt,
|
|
7
8
|
readPatchInfo,
|
|
@@ -10,12 +11,13 @@ import {
|
|
|
10
11
|
readPatchMetadataFile,
|
|
11
12
|
readPatchReadme,
|
|
12
13
|
readPatchReadmeFile
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-IGURDH5L.js";
|
|
15
|
+
import "./chunk-GLK3K2HY.js";
|
|
15
16
|
export {
|
|
16
17
|
PatchHelper,
|
|
17
18
|
PatchReturnValue,
|
|
18
19
|
decodeXdelta,
|
|
20
|
+
extractZipEntry,
|
|
19
21
|
patchBuffer,
|
|
20
22
|
patchIt,
|
|
21
23
|
readPatchInfo,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-patcher",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Native Node.js Nintendo DS ROM reader, repacker and ZIP/Xdelta patcher",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -50,15 +50,19 @@
|
|
|
50
50
|
"@noble/hashes": "2.4.0",
|
|
51
51
|
"@types/node": "22.20.2",
|
|
52
52
|
"buffer": "6.0.3",
|
|
53
|
-
"esbuild": "0.
|
|
53
|
+
"esbuild": "0.28.1",
|
|
54
54
|
"eslint": "10.9.1",
|
|
55
55
|
"eslint-config-prettier": "10.1.8",
|
|
56
|
+
"eslint-plugin-import-x": "4.17.1",
|
|
57
|
+
"eslint-plugin-prettier": "5.5.5",
|
|
58
|
+
"eslint-plugin-unused-imports": "4.4.1",
|
|
56
59
|
"fflate": "0.8.3",
|
|
57
60
|
"globals": "16.5.0",
|
|
58
61
|
"jiti": "2.7.0",
|
|
59
62
|
"prettier": "3.9.6",
|
|
60
63
|
"tsup": "8.5.1",
|
|
61
64
|
"tsx": "4.23.13",
|
|
65
|
+
"type-fest": "4.41.0",
|
|
62
66
|
"typescript": "5.9.3",
|
|
63
67
|
"typescript-eslint": "8.70.0"
|
|
64
68
|
},
|
package/src/browser/buffer.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { Buffer } from
|
|
1
|
+
export { Buffer } from "buffer";
|
package/src/browser.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { NDSFile } from
|
|
1
|
+
import { NDSFile } from "./nitro/nds-file";
|
|
2
2
|
import {
|
|
3
|
+
extractZipEntry as extractEntry,
|
|
3
4
|
patchBuffer as patch,
|
|
4
5
|
readPatchInfo as readInfo,
|
|
5
6
|
readPatchMetadata as readMetadata,
|
|
6
7
|
readPatchReadme as readReadme,
|
|
7
|
-
} from
|
|
8
|
+
} from "./nitro-patch-helper";
|
|
8
9
|
|
|
9
10
|
export interface PatchMetadata {
|
|
10
11
|
id?: string;
|
|
@@ -15,7 +16,7 @@ export interface PatchMetadata {
|
|
|
15
16
|
isBeta?: boolean;
|
|
16
17
|
}
|
|
17
18
|
export interface PatchReadme {
|
|
18
|
-
format:
|
|
19
|
+
format: "markdown" | "plaintext";
|
|
19
20
|
content: string;
|
|
20
21
|
}
|
|
21
22
|
export interface PatchInfo {
|
|
@@ -31,7 +32,7 @@ export interface BrowserPatchOptions {
|
|
|
31
32
|
}
|
|
32
33
|
export interface BrowserPatchResult {
|
|
33
34
|
buffer: Uint8Array<ArrayBuffer>;
|
|
34
|
-
returnValue:
|
|
35
|
+
returnValue: "SUCCESS" | "MD5_MISMATCH";
|
|
35
36
|
inputMd5: string;
|
|
36
37
|
outputMd5: string;
|
|
37
38
|
}
|
|
@@ -79,20 +80,23 @@ export const patchBuffer = (
|
|
|
79
80
|
};
|
|
80
81
|
};
|
|
81
82
|
/** Read and validate metadata.json from a patch package entirely in memory. */
|
|
82
|
-
export const readPatchMetadata = (
|
|
83
|
-
archive
|
|
84
|
-
options: BrowserPatchOptions = {},
|
|
85
|
-
): PatchMetadata | null => readMetadata(archive, options);
|
|
83
|
+
export const readPatchMetadata = (archive: Uint8Array, options: BrowserPatchOptions = {}): PatchMetadata | null =>
|
|
84
|
+
readMetadata(archive, options);
|
|
86
85
|
/** Read a root README.md or README.txt from a patch package entirely in memory. */
|
|
87
|
-
export const readPatchReadme = (
|
|
88
|
-
archive
|
|
89
|
-
options: BrowserPatchOptions = {},
|
|
90
|
-
): PatchReadme | null => readReadme(archive, options);
|
|
86
|
+
export const readPatchReadme = (archive: Uint8Array, options: BrowserPatchOptions = {}): PatchReadme | null =>
|
|
87
|
+
readReadme(archive, options);
|
|
91
88
|
/** Read metadata and README content while parsing the patch package only once. */
|
|
92
89
|
export const readPatchInfo = (archive: Uint8Array, options: BrowserPatchOptions = {}): PatchInfo =>
|
|
93
90
|
readInfo(archive, options);
|
|
91
|
+
/** Extract one file from a ZIP archive entirely in memory. */
|
|
92
|
+
export const extractZipEntry = (
|
|
93
|
+
archive: Uint8Array,
|
|
94
|
+
entryName: string,
|
|
95
|
+
options: BrowserPatchOptions = {},
|
|
96
|
+
): Uint8Array<ArrayBuffer> => new Uint8Array(extractEntry(archive, entryName, options));
|
|
94
97
|
|
|
95
98
|
export const PatchHelper = Object.freeze({
|
|
99
|
+
extractZipEntry,
|
|
96
100
|
patchBuffer,
|
|
97
101
|
readPatchInfo,
|
|
98
102
|
readPatchMetadata,
|
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import { render } from "markdansi";
|
|
3
|
+
import { mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
|
|
7
|
+
import { type PatchMetadata, patchBuffer, readPatchInfo } from "./nitro-patch-helper";
|
|
7
8
|
|
|
8
9
|
const help = `nitro-patcher — 原生 Node.js NDS ROM 补丁工具
|
|
9
10
|
|
|
@@ -26,14 +27,14 @@ const sameFile = async (input: string, output: string): Promise<boolean> => {
|
|
|
26
27
|
const destination = await stat(output);
|
|
27
28
|
return original.dev === destination.dev && original.ino === destination.ino;
|
|
28
29
|
} catch (error) {
|
|
29
|
-
if (!(error instanceof Error &&
|
|
30
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
30
31
|
return false;
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
const safeTerminalText = (value: string): string => {
|
|
35
36
|
const characters: string[] = [];
|
|
36
|
-
for (const character of value.replaceAll(
|
|
37
|
+
for (const character of value.replaceAll("\r\n", "\n").replaceAll("\r", "\n")) {
|
|
37
38
|
const code = character.codePointAt(0)!;
|
|
38
39
|
if (
|
|
39
40
|
(code < 0x20 && code !== 0x09 && code !== 0x0a) ||
|
|
@@ -44,37 +45,37 @@ const safeTerminalText = (value: string): string => {
|
|
|
44
45
|
continue;
|
|
45
46
|
characters.push(character);
|
|
46
47
|
}
|
|
47
|
-
return characters.join(
|
|
48
|
+
return characters.join("");
|
|
48
49
|
};
|
|
49
50
|
|
|
50
51
|
const formatMetadata = (metadata: PatchMetadata): string => {
|
|
51
52
|
const labels = {
|
|
52
|
-
author:
|
|
53
|
-
name:
|
|
54
|
-
homepage:
|
|
55
|
-
version:
|
|
53
|
+
author: "作者",
|
|
54
|
+
name: "名称",
|
|
55
|
+
homepage: "主页",
|
|
56
|
+
version: "版本",
|
|
56
57
|
} as const;
|
|
57
58
|
const lines: string[] = [];
|
|
58
|
-
for (const field of [
|
|
59
|
+
for (const field of ["author", "name", "homepage", "version"] as const) {
|
|
59
60
|
const value = metadata[field];
|
|
60
61
|
if (value === undefined) continue;
|
|
61
|
-
const suffix = field ===
|
|
62
|
-
lines.push(`- ${labels[field]}:${safeTerminalText(value).replaceAll(
|
|
62
|
+
const suffix = field === "version" && metadata.isBeta ? "(测试版)" : "";
|
|
63
|
+
lines.push(`- ${labels[field]}:${safeTerminalText(value).replaceAll("\n", " ")}${suffix}`);
|
|
63
64
|
}
|
|
64
|
-
return lines.join(
|
|
65
|
+
return lines.join("\n");
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
const main = async (): Promise<void> => {
|
|
68
69
|
const { values, positionals } = parseArgs({
|
|
69
70
|
allowPositionals: true,
|
|
70
71
|
options: {
|
|
71
|
-
help: { type:
|
|
72
|
-
version: { type:
|
|
73
|
-
json: { type:
|
|
74
|
-
|
|
75
|
-
input: { type:
|
|
76
|
-
patch: { type:
|
|
77
|
-
output: { type:
|
|
72
|
+
help: { type: "boolean", short: "h" },
|
|
73
|
+
version: { type: "boolean", short: "v" },
|
|
74
|
+
json: { type: "boolean" },
|
|
75
|
+
"dry-run": { type: "boolean" },
|
|
76
|
+
input: { type: "string", short: "i" },
|
|
77
|
+
patch: { type: "string", short: "p" },
|
|
78
|
+
output: { type: "string", short: "o" },
|
|
78
79
|
},
|
|
79
80
|
});
|
|
80
81
|
if (values.help) {
|
|
@@ -82,38 +83,27 @@ const main = async (): Promise<void> => {
|
|
|
82
83
|
return;
|
|
83
84
|
}
|
|
84
85
|
if (values.version) {
|
|
85
|
-
const metadata: unknown = JSON.parse(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
!metadata ||
|
|
90
|
-
typeof metadata !== 'object' ||
|
|
91
|
-
!('version' in metadata) ||
|
|
92
|
-
typeof metadata.version !== 'string'
|
|
93
|
-
)
|
|
94
|
-
throw new Error('Invalid package version metadata');
|
|
86
|
+
const metadata: unknown = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
87
|
+
if (!metadata || typeof metadata !== "object" || !("version" in metadata) || typeof metadata.version !== "string")
|
|
88
|
+
throw new Error("Invalid package version metadata");
|
|
95
89
|
console.log(metadata.version);
|
|
96
90
|
return;
|
|
97
91
|
}
|
|
98
|
-
const dryRun = values[
|
|
99
|
-
const usesNamedPaths =
|
|
100
|
-
values.input !== undefined || values.patch !== undefined || values.output !== undefined;
|
|
92
|
+
const dryRun = values["dry-run"] ?? false;
|
|
93
|
+
const usesNamedPaths = values.input !== undefined || values.patch !== undefined || values.output !== undefined;
|
|
101
94
|
if (usesNamedPaths && positionals.length > 0) throw new Error(help);
|
|
102
95
|
if (
|
|
103
96
|
(usesNamedPaths && (!values.input || !values.patch || (!dryRun && !values.output))) ||
|
|
104
97
|
(!usesNamedPaths &&
|
|
105
|
-
((!dryRun && positionals.length !== 3) ||
|
|
106
|
-
(dryRun && (positionals.length < 2 || positionals.length > 3))))
|
|
98
|
+
((!dryRun && positionals.length !== 3) || (dryRun && (positionals.length < 2 || positionals.length > 3))))
|
|
107
99
|
)
|
|
108
100
|
throw new Error(help);
|
|
109
|
-
const [original, patch, output] = usesNamedPaths
|
|
110
|
-
? [values.input, values.patch, values.output]
|
|
111
|
-
: positionals;
|
|
101
|
+
const [original, patch, output] = usesNamedPaths ? [values.input, values.patch, values.output] : positionals;
|
|
112
102
|
if (!original || !patch) throw new Error(help);
|
|
113
103
|
if (!dryRun) {
|
|
114
104
|
if (!output) throw new Error(help);
|
|
115
105
|
if ((await sameFile(original, output)) || (await sameFile(patch, output)))
|
|
116
|
-
throw new Error(
|
|
106
|
+
throw new Error("输出路径不能覆盖原始 ROM 或补丁包。");
|
|
117
107
|
}
|
|
118
108
|
const [originalBytes, patchBytes] = await Promise.all([readFile(original), readFile(patch)]);
|
|
119
109
|
const info = readPatchInfo(patchBytes);
|
|
@@ -121,9 +111,9 @@ const main = async (): Promise<void> => {
|
|
|
121
111
|
if (!dryRun) {
|
|
122
112
|
if (!output) throw new Error(help);
|
|
123
113
|
const destination = join(await realpath(dirname(resolve(output))), basename(output));
|
|
124
|
-
const temporary = await mkdtemp(join(dirname(destination),
|
|
114
|
+
const temporary = await mkdtemp(join(dirname(destination), ".nitro-patcher-"));
|
|
125
115
|
try {
|
|
126
|
-
const file = join(temporary,
|
|
116
|
+
const file = join(temporary, "output.nds");
|
|
127
117
|
await writeFile(file, result.buffer);
|
|
128
118
|
await rename(file, destination);
|
|
129
119
|
} finally {
|
|
@@ -152,18 +142,18 @@ const main = async (): Promise<void> => {
|
|
|
152
142
|
if (info.readme) {
|
|
153
143
|
const content = safeTerminalText(info.readme.content);
|
|
154
144
|
const rendered =
|
|
155
|
-
info.readme.format ===
|
|
145
|
+
info.readme.format === "markdown"
|
|
156
146
|
? render(content, {
|
|
157
147
|
width: process.stdout.columns ?? 80,
|
|
158
148
|
color: Boolean(process.stdout.isTTY),
|
|
159
149
|
hyperlinks: false,
|
|
160
150
|
})
|
|
161
151
|
: content;
|
|
162
|
-
sections.push(`补丁说明:\n\n${rendered.
|
|
152
|
+
sections.push(`补丁说明:\n\n${rendered.trim()}`);
|
|
163
153
|
}
|
|
164
|
-
if (dryRun) sections.push(
|
|
154
|
+
if (dryRun) sections.push("试运行完成,未写入输出 ROM。");
|
|
165
155
|
sections.push(`${returnValue}\n\n原始 ROM 的 MD5:${inputMd5}\n生成 ROM 的 MD5:${outputMd5}`);
|
|
166
|
-
console.log(sections.join(
|
|
156
|
+
console.log(sections.join("\n\n"));
|
|
167
157
|
};
|
|
168
158
|
|
|
169
159
|
main().catch((error: unknown) => {
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./nitro-helper";
|
|
2
|
+
export * from "./nitro-patch-helper";
|