nitro-patcher 0.2.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.ts +25 -2
- package/dist/browser.js +10507 -78
- package/dist/browser.js.LEGAL.txt +460 -0
- package/dist/browser.js.map +4 -4
- package/dist/{chunk-IGURDH5L.js → chunk-B7NTPBDX.js} +70 -8
- package/dist/chunk-B7NTPBDX.js.map +1 -0
- package/dist/chunk-LK3JXA24.js +2067 -0
- package/dist/chunk-LK3JXA24.js.map +1 -0
- package/dist/cli.js +109 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -2
- package/dist/nitro-helper.d.ts +43 -134
- package/dist/nitro-helper.js +3 -1
- package/dist/nitro-patch-helper.d.ts +4 -0
- package/dist/nitro-patch-helper.js +2 -2
- package/dist/twl-complete-B2is30H0.d.ts +201 -0
- package/package.json +8 -2
- package/src/browser/buffer.ts +1 -0
- package/src/browser.ts +23 -2
- package/src/cli.ts +118 -18
- package/src/nitro/blz.ts +13 -4
- package/src/nitro/crypto.ts +29 -11
- package/src/nitro/filesystem.ts +88 -5
- package/src/nitro/header.ts +4 -0
- package/src/nitro/nds-file.ts +378 -30
- package/src/nitro/twl-complete.ts +665 -0
- package/src/nitro/twl.ts +53 -19
- package/src/nitro-helper.ts +2 -1
- package/src/nitro-patch-helper.ts +64 -3
- package/src/patch/zip.ts +14 -4
- package/src/runtime/browser.ts +2 -2
- package/src/runtime/node.ts +2 -15
- package/src/runtime/rsa.ts +14 -0
- package/dist/chunk-GLK3K2HY.js +0 -1120
- package/dist/chunk-GLK3K2HY.js.map +0 -1
- package/dist/chunk-IGURDH5L.js.map +0 -1
package/src/nitro/filesystem.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// Based on NitroHelper FNT/FAT/OverlayTable, GPL-3.0.
|
|
2
|
-
import
|
|
2
|
+
import iconv from "iconv-lite";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import { BinaryWriter, bytes, range } from "./binary";
|
|
5
|
+
|
|
6
|
+
/** Original IDs/offsets describe the input ROM; newly added files use -1 until serialized and reopened. */
|
|
5
7
|
export interface NitroFile {
|
|
6
8
|
name: string;
|
|
9
|
+
nameBytes?: Buffer;
|
|
7
10
|
id: number;
|
|
8
11
|
offset: number;
|
|
9
12
|
size: number;
|
|
@@ -12,11 +15,27 @@ export interface NitroFile {
|
|
|
12
15
|
|
|
13
16
|
export interface NitroFolder {
|
|
14
17
|
name: string;
|
|
18
|
+
nameBytes?: Buffer;
|
|
15
19
|
id: number;
|
|
16
20
|
files: NitroFile[];
|
|
17
21
|
folders: NitroFolder[];
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
export const encodeName = (name: string): Buffer => {
|
|
25
|
+
const encoded = iconv.encode(name, "shift_jis");
|
|
26
|
+
if (new TextDecoder("shift_jis").decode(encoded) !== name)
|
|
27
|
+
throw new Error(`FNT name cannot be represented in Shift_JIS: ${name}`);
|
|
28
|
+
if (!encoded.length || encoded.length > 127) throw new Error("FNT name must contain 1 to 127 encoded bytes");
|
|
29
|
+
return encoded;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface FileSystemLayout {
|
|
33
|
+
fnt: Buffer;
|
|
34
|
+
files: NitroFile[];
|
|
35
|
+
ids: Map<NitroFile, number>;
|
|
36
|
+
sourceIds: Map<number, number>;
|
|
37
|
+
}
|
|
38
|
+
|
|
20
39
|
export interface FatEntry {
|
|
21
40
|
id: number;
|
|
22
41
|
offset: number;
|
|
@@ -72,7 +91,8 @@ export class FileNameTable {
|
|
|
72
91
|
if (!tag) break;
|
|
73
92
|
const length = tag & 0x7f;
|
|
74
93
|
if (!length) throw new Error("Empty FNT directory name");
|
|
75
|
-
const
|
|
94
|
+
const nameBytes = Buffer.from(range(table, cursor, length, "FNT filename"));
|
|
95
|
+
const filename = decoder.decode(nameBytes);
|
|
76
96
|
cursor += length;
|
|
77
97
|
if (
|
|
78
98
|
filename.includes("/") ||
|
|
@@ -89,13 +109,15 @@ export class FileNameTable {
|
|
|
89
109
|
const id = range(table, cursor, 2, "FNT directory id").readUInt16LE();
|
|
90
110
|
cursor += 2;
|
|
91
111
|
if (id < 0xf000) throw new Error("Invalid FNT directory id");
|
|
92
|
-
|
|
112
|
+
const child = parse(id & 0xfff, filename, depth + 1);
|
|
113
|
+
child.nameBytes = nameBytes;
|
|
114
|
+
folder.folders.push(child);
|
|
93
115
|
} else {
|
|
94
116
|
const entry = fat.entries[fileId++];
|
|
95
117
|
if (!entry) throw new Error("FNT file id is outside FAT");
|
|
96
118
|
if (fileIds.has(entry.id)) throw new Error(`Duplicate FNT file id: ${entry.id}`);
|
|
97
119
|
fileIds.add(entry.id);
|
|
98
|
-
folder.files.push({ name: filename, ...entry });
|
|
120
|
+
folder.files.push({ name: filename, nameBytes, ...entry });
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
123
|
return folder;
|
|
@@ -103,6 +125,67 @@ export class FileNameTable {
|
|
|
103
125
|
this.root = { name: "root", id: 0xffff, files: [], folders: [parse(0, "data", 0)] };
|
|
104
126
|
}
|
|
105
127
|
|
|
128
|
+
serialize(fat: FileAllocationTable, overlays: NitroFile[]): FileSystemLayout {
|
|
129
|
+
const directories: { folder: NitroFolder; parent: number; firstId: number }[] = [];
|
|
130
|
+
const named: NitroFile[] = [];
|
|
131
|
+
const sort = <T extends NitroFile | NitroFolder>(items: T[]): T[] =>
|
|
132
|
+
[...items].sort((a, b) => Buffer.compare(a.nameBytes ?? encodeName(a.name), b.nameBytes ?? encodeName(b.name)));
|
|
133
|
+
const visit = (folder: NitroFolder, parent: number): void => {
|
|
134
|
+
const index = directories.length;
|
|
135
|
+
directories.push({ folder, parent, firstId: named.length });
|
|
136
|
+
if (directories.length > 4096) throw new Error("FNT directory count exceeds 4096");
|
|
137
|
+
named.push(...sort(folder.files));
|
|
138
|
+
for (const child of sort(folder.folders)) visit(child, 0xf000 + index);
|
|
139
|
+
};
|
|
140
|
+
visit(this.root.folders[0], 0xf000);
|
|
141
|
+
const namedIds = new Set(named.filter((file) => file.id >= 0).map((file) => file.id));
|
|
142
|
+
const overlayIds = new Set(overlays.map((file) => file.id));
|
|
143
|
+
const unnamed = fat.entries
|
|
144
|
+
.filter((entry) => !namedIds.has(entry.id))
|
|
145
|
+
.map((entry) => ({ ...entry, name: `FAT entry ${entry.id}` }));
|
|
146
|
+
const files: NitroFile[] = [
|
|
147
|
+
...unnamed.filter((entry) => overlayIds.has(entry.id)),
|
|
148
|
+
...overlays
|
|
149
|
+
.filter((file) => file.id < 0)
|
|
150
|
+
.sort(
|
|
151
|
+
(a, b) =>
|
|
152
|
+
Number(a.name.startsWith("overlay7_")) - Number(b.name.startsWith("overlay7_")) ||
|
|
153
|
+
Number(a.name.slice(a.name.indexOf("_") + 1, -4)) - Number(b.name.slice(b.name.indexOf("_") + 1, -4)),
|
|
154
|
+
),
|
|
155
|
+
...unnamed.filter((entry) => !overlayIds.has(entry.id)),
|
|
156
|
+
];
|
|
157
|
+
const firstNamed = files.length;
|
|
158
|
+
files.push(...named);
|
|
159
|
+
if (files.length > 0xf000) throw new Error("Filesystem file IDs exceed 0xEFFF");
|
|
160
|
+
const ids = new Map(files.map((file, id) => [file, id]));
|
|
161
|
+
const sourceIds = new Map(files.filter((file) => file.id >= 0).map((file) => [file.id, ids.get(file)!]));
|
|
162
|
+
const directoryIds = new Map(directories.map(({ folder }, index) => [folder, 0xf000 + index]));
|
|
163
|
+
const writer = new BinaryWriter();
|
|
164
|
+
writer.write(Buffer.alloc(directories.length * 8));
|
|
165
|
+
for (const [index, { folder, parent, firstId }] of directories.entries()) {
|
|
166
|
+
const record = Buffer.alloc(8);
|
|
167
|
+
record.writeUInt32LE(writer.position);
|
|
168
|
+
record.writeUInt16LE(firstNamed + firstId, 4);
|
|
169
|
+
record.writeUInt16LE(index === 0 ? directories.length : parent, 6);
|
|
170
|
+
record.copy(writer.buffer, index * 8);
|
|
171
|
+
for (const file of sort(folder.files)) {
|
|
172
|
+
const name = file.nameBytes ?? encodeName(file.name);
|
|
173
|
+
writer.write(Buffer.from([name.length]));
|
|
174
|
+
writer.write(name);
|
|
175
|
+
}
|
|
176
|
+
for (const child of sort(folder.folders)) {
|
|
177
|
+
const name = child.nameBytes ?? encodeName(child.name);
|
|
178
|
+
writer.write(Buffer.from([0x80 | name.length]));
|
|
179
|
+
writer.write(name);
|
|
180
|
+
const id = Buffer.alloc(2);
|
|
181
|
+
id.writeUInt16LE(directoryIds.get(child)!);
|
|
182
|
+
writer.write(id);
|
|
183
|
+
}
|
|
184
|
+
writer.write(Buffer.from([0]));
|
|
185
|
+
}
|
|
186
|
+
return { fnt: writer.toBuffer(), files, ids, sourceIds };
|
|
187
|
+
}
|
|
188
|
+
|
|
106
189
|
static findFile(id: number, folder: NitroFolder): NitroFile | undefined {
|
|
107
190
|
for (const file of folder.files) if (file.id === id) return file;
|
|
108
191
|
for (const subfolder of folder.folders) {
|
package/src/nitro/header.ts
CHANGED
|
@@ -77,6 +77,8 @@ export class Header {
|
|
|
77
77
|
declare ARM9autoload: number;
|
|
78
78
|
declare ARM7autoload: number;
|
|
79
79
|
declare ROMsize: number;
|
|
80
|
+
declare ntr_region_end: number;
|
|
81
|
+
declare twl_region_start: number;
|
|
80
82
|
declare headerSize: number;
|
|
81
83
|
declare logoCRC16: number;
|
|
82
84
|
declare headerCRC16: number;
|
|
@@ -219,6 +221,8 @@ field("secureDisable", 0x78, 8, "number");
|
|
|
219
221
|
field("ROMsize", 0x80, 4, "number");
|
|
220
222
|
field("headerSize", 0x84, 4, "number");
|
|
221
223
|
field("reserved2", 0x88, 56);
|
|
224
|
+
field("ntr_region_end", 0x90, 2, "number");
|
|
225
|
+
field("twl_region_start", 0x92, 2, "number");
|
|
222
226
|
field("logo", 0xc0, 156);
|
|
223
227
|
field("logoCRC16", 0x15c, 2, "number");
|
|
224
228
|
field("headerCRC16", 0x15e, 2, "number");
|