nitro-patcher 0.2.0 → 0.3.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.
@@ -1,13 +1,14 @@
1
1
  // Native TypeScript port of NitroHelper/NDSFile.cs, GPL-3.0.
2
- import { readFile, writeFile } from '#nitro-runtime';
3
- import { BinaryWriter, bytes, range, DEFAULT_MAX_SIZE } from './binary';
4
- import { Header } from './header';
5
- import { Banner } from './banner';
6
- import { FileAllocationTable, FileNameTable, OverlayTable } from './filesystem';
7
- import type { NitroFile, NitroFolder } from './filesystem';
8
- import { crc16 } from './crc';
9
- import { encryptSecureArea } from './crypto';
10
- import { TWL } from './twl';
2
+ import { readFile, writeFile } from "#nitro-runtime";
3
+
4
+ import { Banner } from "./banner";
5
+ import { BinaryWriter, DEFAULT_MAX_SIZE, bytes, range } from "./binary";
6
+ import { crc16 } from "./crc";
7
+ import { encryptSecureArea } from "./crypto";
8
+ import { FileAllocationTable, FileNameTable, OverlayTable } from "./filesystem";
9
+ import type { NitroFile, NitroFolder } from "./filesystem";
10
+ import { Header } from "./header";
11
+ import { TWL } from "./twl";
11
12
 
12
13
  export interface RomOptions {
13
14
  /** Maximum generated ROM size in bytes. Defaults to 1 GiB. */
@@ -22,7 +23,7 @@ export interface RomFileInfo {
22
23
  size: number;
23
24
  }
24
25
 
25
- const key = (path: string): string => path.replaceAll('\\', '/').toLowerCase();
26
+ const key = (path: string): string => path.replaceAll("\\", "/").toLowerCase();
26
27
 
27
28
  /** Parsed Nintendo DS ROM. Replacements do not change the input buffer. */
28
29
  export class NDSFile {
@@ -40,31 +41,21 @@ export class NDSFile {
40
41
  private readonly maxOutputSize: number;
41
42
 
42
43
  constructor(input: Uint8Array, options: RomOptions = {}) {
43
- this.original = bytes(input, 'ROM');
44
+ this.original = bytes(input, "ROM");
44
45
  this.maxOutputSize = options.maxOutputSize ?? DEFAULT_MAX_SIZE;
45
46
  this.header = new Header(this.original);
46
47
  const h = this.header;
47
- range(this.original, h.ARM9romOffset, h.ARM9size + (h.nitrocode ? 12 : 0), 'ARM9');
48
- range(this.original, h.ARM7romOffset, h.ARM7size, 'ARM7');
48
+ range(this.original, h.ARM9romOffset, h.ARM9size + (h.nitrocode ? 12 : 0), "ARM9");
49
+ range(this.original, h.ARM7romOffset, h.ARM7size, "ARM7");
49
50
  this.banner = new Banner(this.original, h.bannerOffset, h.banner_size);
50
51
  this.fatTable = new FileAllocationTable(this.original, h.FAToffset, h.FATsize);
51
52
  this.fntTable = new FileNameTable(this.fatTable, this.original, h.FNToffset, h.FNTsize);
52
53
  this.root = this.fntTable.root;
53
- this.overlay9Table = new OverlayTable(
54
- this.original,
55
- h.ARM9overlayOffset,
56
- h.ARM9overlaySize,
57
- true,
58
- );
59
- this.overlay7Table = new OverlayTable(
60
- this.original,
61
- h.ARM7overlayOffset,
62
- h.ARM7overlaySize,
63
- false,
64
- );
54
+ this.overlay9Table = new OverlayTable(this.original, h.ARM9overlayOffset, h.ARM9overlaySize, true);
55
+ this.overlay7Table = new OverlayTable(this.original, h.ARM7overlayOffset, h.ARM7overlaySize, false);
65
56
  const arm9Overlays = this.overlay9Table.readBasicOverlays(this.fatTable);
66
57
  this.root.folders.push({
67
- name: 'overlay',
58
+ name: "overlay",
68
59
  id: 0xffff,
69
60
  files: [...arm9Overlays, ...this.overlay7Table.readBasicOverlays(this.fatTable)],
70
61
  folders: [],
@@ -73,23 +64,23 @@ export class NDSFile {
73
64
  range(this.original, offset, size, name);
74
65
  this.root.files.push({ name, id: 0xffff, offset, size });
75
66
  };
76
- system('header.bin', 0, h.headerSize);
77
- system('banner.bin', h.bannerOffset, this.banner.raw.length);
78
- system('fnt.bin', h.FNToffset, h.FNTsize);
79
- system('fat.bin', h.FAToffset, h.FATsize);
80
- system('arm9.bin', h.ARM9romOffset, h.ARM9size + (h.nitrocode ? 12 : 0));
81
- system('arm7.bin', h.ARM7romOffset, h.ARM7size);
82
- if (h.ARM9overlaySize) system('overarm9.bin', h.ARM9overlayOffset, h.ARM9overlaySize);
83
- if (h.ARM7overlaySize) system('overarm7.bin', h.ARM7overlayOffset, h.ARM7overlaySize);
67
+ system("header.bin", 0, h.headerSize);
68
+ system("banner.bin", h.bannerOffset, this.banner.raw.length);
69
+ system("fnt.bin", h.FNToffset, h.FNTsize);
70
+ system("fat.bin", h.FAToffset, h.FATsize);
71
+ system("arm9.bin", h.ARM9romOffset, h.ARM9size + (h.nitrocode ? 12 : 0));
72
+ system("arm7.bin", h.ARM7romOffset, h.ARM7size);
73
+ if (h.ARM9overlaySize) system("overarm9.bin", h.ARM9overlayOffset, h.ARM9overlaySize);
74
+ if (h.ARM7overlaySize) system("overarm7.bin", h.ARM7overlayOffset, h.ARM7overlaySize);
84
75
  const visit = (folder: NitroFolder, prefix: string): void => {
85
76
  for (const file of folder.files) {
86
77
  const path = prefix + file.name;
87
78
  if (this.paths.has(key(path))) throw new Error(`Duplicate ROM path: ${path}`);
88
79
  this.paths.set(key(path), { path, file });
89
80
  }
90
- for (const child of folder.folders) visit(child, prefix + child.name + '/');
81
+ for (const child of folder.folders) visit(child, `${prefix + child.name}/`);
91
82
  };
92
- visit(this.root, '');
83
+ visit(this.root, "");
93
84
  if (h.unitCode & 2 && h.twlInternalFlags & 1 && h.tid_high && h.tid_high !== 0xffffffff) {
94
85
  // Upstream silently discarded malformed TWL regions. Preserve valid DSi data,
95
86
  // and reject malformed data instead of emitting a ROM with missing sections.
@@ -135,20 +126,13 @@ export class NDSFile {
135
126
 
136
127
  /** Rebuild offsets, allocation table, CRCs and ROM padding. Safe to call repeatedly. */
137
128
  toBuffer(): Buffer {
138
- const fntReplacement = this.root.files.find((file) => file.name === 'fnt.bin')?.replacement;
129
+ const fntReplacement = this.root.files.find((file) => file.name === "fnt.bin")?.replacement;
139
130
  if (fntReplacement) {
140
- const replacementTree = new FileNameTable(
141
- this.fatTable,
142
- fntReplacement,
143
- 0,
144
- fntReplacement.length,
145
- );
131
+ const replacementTree = new FileNameTable(this.fatTable, fntReplacement, 0, fntReplacement.length);
146
132
  const collect = (folder: NitroFolder): number[] =>
147
- [...folder.files.map((file) => file.id), ...folder.folders.flatMap(collect)].sort(
148
- (a, b) => a - b,
149
- );
150
- if (collect(replacementTree.root).join(',') !== collect(this.data).join(','))
151
- throw new Error('Replacement FNT must preserve existing file IDs');
133
+ [...folder.files.map((file) => file.id), ...folder.folders.flatMap(collect)].sort((a, b) => a - b);
134
+ if (collect(replacementTree.root).join(",") !== collect(this.data).join(","))
135
+ throw new Error("Replacement FNT must preserve existing file IDs");
152
136
  }
153
137
  const h = new Header(this.header.toBuffer());
154
138
  h.nitrocode = this.header.nitrocode;
@@ -161,7 +145,7 @@ export class NDSFile {
161
145
  if (!file) throw new Error(`Missing ROM system file: ${name}`);
162
146
  return file;
163
147
  };
164
- const replacedHeader = system('header.bin').replacement;
148
+ const replacedHeader = system("header.bin").replacement;
165
149
  if (replacedHeader) {
166
150
  const replacement = new Header(replacedHeader);
167
151
  h.gameTitle = replacement.gameTitle;
@@ -177,13 +161,13 @@ export class NDSFile {
177
161
  const overlayIds = new Set<number>();
178
162
  const writeOverlays = (arm9: boolean): void => {
179
163
  const originalTable = arm9 ? this.overlay9Table : this.overlay7Table;
180
- const name = arm9 ? 'overarm9.bin' : 'overarm7.bin';
164
+ const name = arm9 ? "overarm9.bin" : "overarm7.bin";
181
165
  const entry = this.root.files.find((file) => file.name === name);
182
166
  if (!entry) return;
183
167
  const raw = entry.replacement ?? originalTable.toBuffer();
184
168
  const table = new OverlayTable(raw, 0, raw.length, arm9);
185
169
  const files = this.overlay.files
186
- .filter((file) => file.name.startsWith(arm9 ? 'overlay_' : 'overlay7_'))
170
+ .filter((file) => file.name.startsWith(arm9 ? "overlay_" : "overlay7_"))
187
171
  .sort((a, b) => a.id - b.id);
188
172
  for (const file of files) {
189
173
  const item = table.entries.find((item) => item.fileId === file.id);
@@ -193,8 +177,7 @@ export class NDSFile {
193
177
  item.reserved = ((item.reserved & 0xff000000) | (size & 0xffffff)) >>> 0;
194
178
  overlayIds.add(file.id);
195
179
  }
196
- if (table.entries.length !== files.length)
197
- throw new Error('Changing overlay file count is unsupported');
180
+ if (table.entries.length !== files.length) throw new Error("Changing overlay file count is unsupported");
198
181
  const encoded = table.toBuffer();
199
182
  if (arm9) {
200
183
  h.ARM9overlayOffset = writer.position;
@@ -209,30 +192,29 @@ export class NDSFile {
209
192
  };
210
193
  writer.position = h.headerSize;
211
194
  h.ARM9romOffset = writer.position;
212
- const arm9 = system('arm9.bin');
195
+ const arm9 = system("arm9.bin");
213
196
  const arm9Bytes = this.fileBytes(arm9);
214
197
  if (arm9.replacement) {
215
- h.nitrocode =
216
- arm9Bytes.length >= 12 && arm9Bytes.readUInt32LE(arm9Bytes.length - 12) === 0xdec00621;
198
+ h.nitrocode = arm9Bytes.length >= 12 && arm9Bytes.readUInt32LE(arm9Bytes.length - 12) === 0xdec00621;
217
199
  }
218
200
  h.ARM9size = arm9Bytes.length - (h.nitrocode ? 12 : 0);
219
201
  write(arm9);
220
202
  h.ARM9overlayOffset = 0;
221
203
  writeOverlays(true);
222
204
  h.ARM7romOffset = writer.position;
223
- h.ARM7size = this.fileBytes(system('arm7.bin')).length;
224
- write(system('arm7.bin'));
205
+ h.ARM7size = this.fileBytes(system("arm7.bin")).length;
206
+ write(system("arm7.bin"));
225
207
  h.ARM7overlayOffset = 0;
226
208
  writeOverlays(false);
227
209
  h.FNToffset = writer.position;
228
- h.FNTsize = this.fileBytes(system('fnt.bin')).length;
229
- write(system('fnt.bin'));
210
+ h.FNTsize = this.fileBytes(system("fnt.bin")).length;
211
+ write(system("fnt.bin"));
230
212
  h.FAToffset = writer.position;
231
213
  h.FATsize = this.fatTable.entries.length * 8;
232
214
  writer.write(Buffer.alloc(h.FATsize));
233
215
  writer.pad(0x200);
234
216
  h.bannerOffset = writer.position;
235
- const bannerFile = system('banner.bin');
217
+ const bannerFile = system("banner.bin");
236
218
  const banner = bannerFile.replacement
237
219
  ? new Banner(bannerFile.replacement, 0, bannerFile.replacement.length)
238
220
  : this.banner;
@@ -265,8 +247,7 @@ export class NDSFile {
265
247
  writer.position = logicalEnd;
266
248
  // An ARM9 replacement can add or remove the decrypted secure-area marker.
267
249
  // Both TWL hashing and the header CRC must use the bytes being serialized.
268
- h.decrypted =
269
- writer.length >= 0x4008 && writer.buffer.readBigUInt64LE(0x4000) === 0xe7ffdeffe7ffdeffn;
250
+ h.decrypted = writer.length >= 0x4008 && writer.buffer.readBigUInt64LE(0x4000) === 0xe7ffdeffe7ffdeffn;
270
251
  if (this.twl) this.twl.writeTo(writer, h);
271
252
  h.ROMsize = writer.position;
272
253
  h.size = 2 ** Math.ceil(Math.log2(Math.max(h.ROMsize + h.dlp_signature.length, 0x20000)));
package/src/nitro/twl.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { hashDigest, hmacSha1, verifyRsaSha1, readFileSync } from '#nitro-runtime';
2
- import type { BinaryWriter } from './binary';
3
- import type { ModcryptHeader } from './crypto';
4
- import type { Arm9Header } from './blz';
5
- import { aes128CtrCrypt, encryptSecureArea, modcryptKey } from './crypto';
6
- import { compressArm9, decompressArm9 } from './blz';
1
+ import { hashDigest, hmacSha1, readFileSync, verifyRsaSha1 } from "#nitro-runtime";
2
+
3
+ import type { BinaryWriter } from "./binary";
4
+ import type { Arm9Header } from "./blz";
5
+ import { compressArm9, decompressArm9 } from "./blz";
6
+ import type { ModcryptHeader } from "./crypto";
7
+ import { aes128CtrCrypt, encryptSecureArea, modcryptKey } from "./crypto";
7
8
 
8
9
  export interface TwlHeader extends ModcryptHeader {
9
10
  digest_sector_size: number;
@@ -43,8 +44,8 @@ export interface OverlayRange {
43
44
  }
44
45
 
45
46
  export const HMAC_SHA1_KEY = Buffer.from(
46
- '2106c0deba98ce3fa692e39d46f2ed0176e3cc08562363facad4ecdf9a6278348f6d633cfe22ca9220889723d2cfaec232678dfeca836498acfd3e3787465824',
47
- 'hex',
47
+ "2106c0deba98ce3fa692e39d46f2ed0176e3cc08562363facad4ecdf9a6278348f6d633cfe22ca9220889723d2cfaec232678dfeca836498acfd3e3787465824",
48
+ "hex",
48
49
  );
49
50
  const hash = (data: Buffer): Buffer => hmacSha1(HMAC_SHA1_KEY, data);
50
51
  function slice(data: Buffer, start: number, size: number, label: string): Buffer {
@@ -60,46 +61,44 @@ function slice(data: Buffer, start: number, size: number, label: string): Buffer
60
61
  }
61
62
  function validate(h: TwlHeader): void {
62
63
  for (const field of [
63
- 'digest_ntr_start',
64
- 'digest_ntr_size',
65
- 'digest_twl_start',
66
- 'digest_twl_size',
67
- 'sector_hashtable_start',
68
- 'sector_hashtable_size',
69
- 'block_hashtable_start',
70
- 'block_hashtable_size',
71
- 'dsi9_rom_offset',
72
- 'dsi9_size',
73
- 'dsi7_rom_offset',
74
- 'dsi7_size',
75
- 'modcrypt1_start',
76
- 'modcrypt1_size',
77
- 'modcrypt2_start',
78
- 'modcrypt2_size',
79
- 'total_rom_size',
64
+ "digest_ntr_start",
65
+ "digest_ntr_size",
66
+ "digest_twl_start",
67
+ "digest_twl_size",
68
+ "sector_hashtable_start",
69
+ "sector_hashtable_size",
70
+ "block_hashtable_start",
71
+ "block_hashtable_size",
72
+ "dsi9_rom_offset",
73
+ "dsi9_size",
74
+ "dsi7_rom_offset",
75
+ "dsi7_size",
76
+ "modcrypt1_start",
77
+ "modcrypt1_size",
78
+ "modcrypt2_start",
79
+ "modcrypt2_size",
80
+ "total_rom_size",
80
81
  ] as const) {
81
82
  if (!Number.isSafeInteger(h[field]) || h[field] < 0 || h[field] > 0xffffffff) {
82
83
  throw new Error(`Invalid TWL ${field}`);
83
84
  }
84
85
  }
85
86
  const s = h.digest_sector_size;
86
- if (!Number.isSafeInteger(s) || s <= 0 || s > 0x4000 || 0x4000 % s)
87
- throw new Error('Invalid TWL digest sector size');
87
+ if (!Number.isSafeInteger(s) || s <= 0 || s > 0x4000 || 0x4000 % s) throw new Error("Invalid TWL digest sector size");
88
88
  if (
89
89
  !Number.isSafeInteger(h.digest_block_sectorcount) ||
90
90
  h.digest_block_sectorcount <= 0 ||
91
91
  h.digest_block_sectorcount > 0x100000
92
92
  )
93
- throw new Error('Invalid TWL digest block sector count');
93
+ throw new Error("Invalid TWL digest block sector count");
94
94
  if (h.digest_ntr_size < 0x4000 || h.digest_ntr_size % s || h.digest_twl_size % s)
95
- throw new Error('Invalid TWL digest region size');
95
+ throw new Error("Invalid TWL digest region size");
96
96
  const hashes = (h.digest_ntr_size + h.digest_twl_size) / s;
97
97
  if (
98
98
  hashes * 20 > h.sector_hashtable_size ||
99
- Math.ceil(h.sector_hashtable_size / (h.digest_block_sectorcount * 20)) * 20 >
100
- h.block_hashtable_size
99
+ Math.ceil(h.sector_hashtable_size / (h.digest_block_sectorcount * 20)) * 20 > h.block_hashtable_size
101
100
  )
102
- throw new Error('TWL hash tables are too small');
101
+ throw new Error("TWL hash tables are too small");
103
102
  }
104
103
  export class TWL {
105
104
  readonly firstDSiHashOffset: number;
@@ -117,51 +116,31 @@ export class TWL {
117
116
  this.firstDSiHashOffset = (h.digest_ntr_size / h.digest_sector_size) * 20;
118
117
  this.dsiHashSize = (h.digest_twl_size / h.digest_sector_size) * 20;
119
118
  this.Hashtable1Data = Buffer.from(
120
- slice(original, h.sector_hashtable_start, h.sector_hashtable_size, 'Sector hash table'),
119
+ slice(original, h.sector_hashtable_start, h.sector_hashtable_size, "Sector hash table"),
121
120
  );
122
121
  this.Hashtable2Data = Buffer.from(
123
- slice(original, h.block_hashtable_start, h.block_hashtable_size, 'Block hash table'),
124
- );
125
- this.DSi9Data = Buffer.from(
126
- slice(original, h.dsi9_rom_offset, Math.max(h.modcrypt1_size, h.dsi9_size), 'ARM9i'),
127
- );
128
- this.DSi7Data = Buffer.from(
129
- slice(original, h.dsi7_rom_offset, Math.max(h.modcrypt2_size, h.dsi7_size), 'ARM7i'),
122
+ slice(original, h.block_hashtable_start, h.block_hashtable_size, "Block hash table"),
130
123
  );
124
+ this.DSi9Data = Buffer.from(slice(original, h.dsi9_rom_offset, Math.max(h.modcrypt1_size, h.dsi9_size), "ARM9i"));
125
+ this.DSi7Data = Buffer.from(slice(original, h.dsi7_rom_offset, Math.max(h.modcrypt2_size, h.dsi7_size), "ARM7i"));
131
126
  this.Header2Data = h.trimmedRom
132
127
  ? null
133
128
  : Array.from({ length: 3 }, (_, i) =>
134
- Buffer.from(
135
- slice(original, h.digest_twl_start - 0x3000 + 0x1000 * i, 0x1000, 'Secondary header'),
136
- ),
129
+ Buffer.from(slice(original, h.digest_twl_start - 0x3000 + 0x1000 * i, 0x1000, "Secondary header")),
137
130
  );
138
- this.Overlays9Sha1Hmac = overlays.map((o) =>
139
- hash(slice(original, o.offset, o.size, 'ARM9 overlay')),
140
- );
131
+ this.Overlays9Sha1Hmac = overlays.map((o) => hash(slice(original, o.offset, o.size, "ARM9 overlay")));
141
132
  let encrypted = false;
142
133
  const modcryptStart = [
143
134
  { start: h.modcrypt1_start, size: h.modcrypt1_size },
144
135
  { start: h.modcrypt2_start, size: h.modcrypt2_size },
145
- ].find(
146
- ({ start, size }) => size > 0 && start >= h.digest_twl_start && start < 0xffffffff,
147
- )?.start;
136
+ ].find(({ start, size }) => size > 0 && start >= h.digest_twl_start && start < 0xffffffff)?.start;
148
137
  if (modcryptStart !== undefined) {
149
138
  const index = Math.floor((modcryptStart - h.digest_twl_start) / h.digest_sector_size);
150
139
  const actual = hash(
151
- slice(
152
- original,
153
- h.digest_twl_start + index * h.digest_sector_size,
154
- h.digest_sector_size,
155
- 'Modcrypt sector',
156
- ),
140
+ slice(original, h.digest_twl_start + index * h.digest_sector_size, h.digest_sector_size, "Modcrypt sector"),
157
141
  );
158
142
  encrypted = !actual.equals(
159
- slice(
160
- this.Hashtable1Data,
161
- this.firstDSiHashOffset + index * 20,
162
- 20,
163
- 'Modcrypt sector hash',
164
- ),
143
+ slice(this.Hashtable1Data, this.firstDSiHashOffset + index * 20, 20, "Modcrypt sector hash"),
165
144
  );
166
145
  }
167
146
  if (encrypted) this.cryptSections(h);
@@ -178,8 +157,8 @@ export class TWL {
178
157
  const off = h[`modcrypt${id}_start`] - start;
179
158
  const result = aes128CtrCrypt(
180
159
  key,
181
- slice(counter, 0, 16, 'Modcrypt counter'),
182
- slice(data, off, size, 'Modcrypt payload'),
160
+ slice(counter, 0, 16, "Modcrypt counter"),
161
+ slice(data, off, size, "Modcrypt payload"),
183
162
  );
184
163
  if (writer) {
185
164
  writer.position = start + off;
@@ -194,20 +173,16 @@ export class TWL {
194
173
  this.DSi7Data = this.importData(data, offset, size);
195
174
  }
196
175
  private importData(data: Buffer | string, offset: number, size?: number): Buffer {
197
- if (typeof data === 'string') data = readFileSync(data);
176
+ if (typeof data === "string") data = readFileSync(data);
198
177
  size ??= data.length - offset;
199
- const src = slice(data, offset, size, 'DSi import');
178
+ const src = slice(data, offset, size, "DSi import");
200
179
  const out = Buffer.alloc(Math.ceil(size / 16) * 16, 255);
201
180
  src.copy(out);
202
181
  return out;
203
182
  }
204
- updateOverlays9Sha1Hmac(
205
- arm9Data: Buffer,
206
- header: Arm9Header,
207
- overlays: readonly Buffer[],
208
- ): Buffer {
183
+ updateOverlays9Sha1Hmac(arm9Data: Buffer, header: Arm9Header, overlays: readonly Buffer[]): Buffer {
209
184
  if (overlays.length !== this.Overlays9Sha1Hmac.length)
210
- throw new Error('Changing ARM9 overlay count is unsupported');
185
+ throw new Error("Changing ARM9 overlay count is unsupported");
211
186
  const hashes = overlays.map(hash);
212
187
  if (hashes.every((h, i) => h.equals(this.Overlays9Sha1Hmac[i]))) return Buffer.from(arm9Data);
213
188
  const init = header.reserved2.readUInt32LE(0) & 0x3fff;
@@ -215,30 +190,23 @@ export class TWL {
215
190
  const unpacked = decompressArm9(arm9Data, header);
216
191
  const end = unpacked.data.readUInt32LE(init + 8) - header.ARM9ramAddress;
217
192
  let found = -1;
218
- for (
219
- let p = Math.min(end - hashes.length * 20, unpacked.data.length - hashes.length * 20);
220
- p >= 0;
221
- p--
222
- ) {
193
+ for (let p = Math.min(end - hashes.length * 20, unpacked.data.length - hashes.length * 20); p >= 0; p--) {
223
194
  if (unpacked.data.subarray(p, p + 20).equals(this.Overlays9Sha1Hmac[0])) {
224
195
  found = p;
225
196
  break;
226
197
  }
227
198
  }
228
- if (found < 0)
229
- throw new Error('ARM9 overlay hashes changed but original hash table was not found');
199
+ if (found < 0) throw new Error("ARM9 overlay hashes changed but original hash table was not found");
230
200
  hashes.forEach((h, i) => h.copy(unpacked.data, found + i * 20));
231
201
  // Original C# inverted this check and tried to compress uncompressed data.
232
202
  // Preserve the original storage format: recompress only a compressed ARM9.
233
- return unpacked.compressed
234
- ? compressArm9(unpacked.data, header, arm9Data.length - hdrptr)
235
- : unpacked.data;
203
+ return unpacked.compressed ? compressArm9(unpacked.data, header, arm9Data.length - hdrptr) : unpacked.data;
236
204
  }
237
205
  writeTo(writer: BinaryWriter, h: TwlHeader): Buffer {
238
206
  validate(h);
239
207
  // Fixed DSi regions cannot be shifted safely without rewriting the header.
240
208
  if (writer.position > h.sector_hashtable_start) {
241
- throw new Error('NTR output overlaps the fixed DSi sector hash table');
209
+ throw new Error("NTR output overlaps the fixed DSi sector hash table");
242
210
  }
243
211
  if (
244
212
  h.sector_hashtable_start + h.sector_hashtable_size > h.block_hashtable_start ||
@@ -246,7 +214,7 @@ export class TWL {
246
214
  h.dsi9_rom_offset + this.DSi9Data.length > h.dsi7_rom_offset ||
247
215
  h.dsi7_rom_offset + this.DSi7Data.length > h.total_rom_size
248
216
  ) {
249
- throw new Error('DSi sections overlap or exceed the ROM size');
217
+ throw new Error("DSi sections overlap or exceed the ROM size");
250
218
  }
251
219
  const fill = (end: number): void => {
252
220
  if (writer.position < end) writer.fillTo(end, 255);
@@ -268,7 +236,7 @@ export class TWL {
268
236
  fill(h.total_rom_size);
269
237
  const finalPosition = writer.position;
270
238
  const read = (p: number, n: number): Buffer =>
271
- slice(writer.buffer.subarray(0, writer.length), p, n, 'TWL output digest');
239
+ slice(writer.buffer.subarray(0, writer.length), p, n, "TWL output digest");
272
240
  let secure: Buffer = Buffer.from(read(h.digest_ntr_start, 0x4000));
273
241
  if (h.decrypted) secure = encryptSecureArea(h.gameCode, secure);
274
242
  let index = 0;
@@ -276,19 +244,10 @@ export class TWL {
276
244
  writer.position = h.sector_hashtable_start + index++ * 20;
277
245
  writer.write(hash(b));
278
246
  };
279
- for (let p = 0; p < 0x4000; p += h.digest_sector_size)
280
- put(secure.subarray(p, p + h.digest_sector_size));
281
- for (
282
- let p = h.digest_ntr_start + 0x4000;
283
- p < h.digest_ntr_start + h.digest_ntr_size;
284
- p += h.digest_sector_size
285
- )
247
+ for (let p = 0; p < 0x4000; p += h.digest_sector_size) put(secure.subarray(p, p + h.digest_sector_size));
248
+ for (let p = h.digest_ntr_start + 0x4000; p < h.digest_ntr_start + h.digest_ntr_size; p += h.digest_sector_size)
286
249
  put(read(p, h.digest_sector_size));
287
- for (
288
- let p = h.digest_twl_start;
289
- p < h.digest_twl_start + h.digest_twl_size;
290
- p += h.digest_sector_size
291
- )
250
+ for (let p = h.digest_twl_start; p < h.digest_twl_start + h.digest_twl_size; p += h.digest_sector_size)
292
251
  put(read(p, h.digest_sector_size));
293
252
  index = 0;
294
253
  for (
@@ -314,9 +273,9 @@ export const updateHeaderSignatures = (
314
273
  originalHeader: Buffer,
315
274
  keepOriginal: boolean,
316
275
  ): void => {
317
- const signedData = Buffer.from(slice(originalHeader, 0, 0xe00, 'Signed header'));
318
- const digest = slice(header.hmac_digest_master, 0, 20, 'Master digest');
319
- const signature = slice(header.rsa_signature, 0, 128, 'RSA signature');
276
+ const signedData = Buffer.from(slice(originalHeader, 0, 0xe00, "Signed header"));
277
+ const digest = slice(header.hmac_digest_master, 0, 20, "Master digest");
278
+ const signature = slice(header.rsa_signature, 0, 128, "RSA signature");
320
279
  digest.copy(signedData, 0x328);
321
280
  const valid = verifyRsaSha1(signedData, signature);
322
281
  const position = writer.position;
@@ -328,7 +287,7 @@ export const updateHeaderSignatures = (
328
287
  mask[0] = 0;
329
288
  mask[1] = 1;
330
289
  mask[107] = 0;
331
- hashDigest('sha1', signedData).copy(mask, 108);
290
+ hashDigest("sha1", signedData).copy(mask, 108);
332
291
  header.rsa_signature = mask;
333
292
  }
334
293
  writer.position = 0xf80;
@@ -1,14 +1,14 @@
1
- export { NDSFile } from './nitro/nds-file';
2
- export type { RomOptions, RomFileInfo } from './nitro/nds-file';
3
- export { Header, UnitCode } from './nitro/header';
4
- export { Banner } from './nitro/banner';
5
- export { FileAllocationTable, FileNameTable, OverlayTable } from './nitro/filesystem';
6
- export type { NitroFile, NitroFolder, FatEntry, OverlayItem } from './nitro/filesystem';
7
- export { BinaryWriter } from './nitro/binary';
8
- export { crc16, crc32 } from './nitro/crc';
9
- export { encryptSecureArea, aes128CtrCrypt, modcryptKey } from './nitro/crypto';
10
- export type { ModcryptHeader } from './nitro/crypto';
11
- export { compressBLZ, decompressBLZ, compressArm9, decompressArm9 } from './nitro/blz';
12
- export type { Arm9Header } from './nitro/blz';
13
- export { TWL, updateHeaderSignatures } from './nitro/twl';
14
- export type { TwlHeader, SignedTwlHeader, OverlayRange } from './nitro/twl';
1
+ export { NDSFile } from "./nitro/nds-file";
2
+ export type { RomFileInfo, RomOptions } from "./nitro/nds-file";
3
+ export { Header, UnitCode } from "./nitro/header";
4
+ export { Banner } from "./nitro/banner";
5
+ export { FileAllocationTable, FileNameTable, OverlayTable } from "./nitro/filesystem";
6
+ export type { FatEntry, NitroFile, NitroFolder, OverlayItem } from "./nitro/filesystem";
7
+ export { BinaryWriter } from "./nitro/binary";
8
+ export { crc16, crc32 } from "./nitro/crc";
9
+ export { aes128CtrCrypt, encryptSecureArea, modcryptKey } from "./nitro/crypto";
10
+ export type { ModcryptHeader } from "./nitro/crypto";
11
+ export { compressArm9, compressBLZ, decompressArm9, decompressBLZ } from "./nitro/blz";
12
+ export type { Arm9Header } from "./nitro/blz";
13
+ export { TWL, updateHeaderSignatures } from "./nitro/twl";
14
+ export type { OverlayRange, SignedTwlHeader, TwlHeader } from "./nitro/twl";