nitro-patcher 0.1.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.
@@ -1,5 +1,5 @@
1
1
  // src/runtime/node.ts
2
- import { createHash, createHmac, createCipheriv, createPublicKey, verify } from "crypto";
2
+ import { createCipheriv, createHash, createHmac, createPublicKey, verify } from "crypto";
3
3
  import { readFile, writeFile } from "fs/promises";
4
4
  import { readFileSync } from "fs";
5
5
  import { inflateRawSync } from "zlib";
@@ -33,15 +33,11 @@ var bytes = (value, name = "input") => {
33
33
  };
34
34
  var range = (buffer, offset, size, name = "binary data") => {
35
35
  if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(size) || offset < 0 || size < 0 || offset + size > buffer.length) {
36
- throw new RangeError(
37
- `Invalid ${name} range: offset=${offset}, size=${size}, available=${buffer.length}`
38
- );
36
+ throw new RangeError(`Invalid ${name} range: offset=${offset}, size=${size}, available=${buffer.length}`);
39
37
  }
40
38
  return buffer.subarray(offset, offset + size);
41
39
  };
42
- var align = (value, alignment = 512) => {
43
- return Math.ceil(value / alignment) * alignment;
44
- };
40
+ var align = (value, alignment = 512) => Math.ceil(value / alignment) * alignment;
45
41
  var BinaryWriter = class {
46
42
  maxSize;
47
43
  buffer;
@@ -65,8 +61,7 @@ var BinaryWriter = class {
65
61
  }
66
62
  write(data) {
67
63
  data = bytes(data);
68
- if (!Number.isSafeInteger(this.position) || this.position < 0)
69
- throw new RangeError("Invalid writer position");
64
+ if (!Number.isSafeInteger(this.position) || this.position < 0) throw new RangeError("Invalid writer position");
70
65
  this.ensure(this.position + data.length);
71
66
  bytes(data).copy(this.buffer, this.position);
72
67
  this.position += data.length;
@@ -110,14 +105,79 @@ var crc32 = (data) => {
110
105
  return (crc ^ 4294967295) >>> 0;
111
106
  };
112
107
 
108
+ // src/nitro/banner.ts
109
+ var titles = [
110
+ "japaneseTitle",
111
+ "englishTitle",
112
+ "frenchTitle",
113
+ "germanTitle",
114
+ "italianTitle",
115
+ "spanishTitle",
116
+ "chineseTitle",
117
+ "koreanTitle"
118
+ ];
119
+ var Banner = class {
120
+ version;
121
+ raw;
122
+ bannerCRC;
123
+ tileData;
124
+ palette;
125
+ japaneseTitle = "";
126
+ englishTitle = "";
127
+ frenchTitle = "";
128
+ germanTitle = "";
129
+ italianTitle = "";
130
+ spanishTitle = "";
131
+ chineseTitle = "";
132
+ koreanTitle = "";
133
+ constructor(input, offset = 0, hardSize = 0) {
134
+ const data = bytes(input, "banner");
135
+ this.version = range(data, offset, 2, "banner").readUInt16LE();
136
+ let size = this.getDefSize(hardSize);
137
+ if (this.version >>> 8 === 1) size = hardSize > 0 && hardSize !== 4294967295 ? Math.min(hardSize, 9152) : 9152;
138
+ this.raw = Buffer.from(range(data, offset, size, "banner"));
139
+ this.bannerCRC = crc16(this.raw.subarray(32, 2112)) === this.raw.readUInt16LE(2);
140
+ this.tileData = Buffer.from(this.raw.subarray(32, 544));
141
+ this.palette = Buffer.from(this.raw.subarray(544, 576));
142
+ for (const [i, title] of titles.entries())
143
+ this[title] = i < this.titleCount ? this.raw.subarray(576 + i * 256, 832 + i * 256).toString("utf16le").split("\0")[0] : "";
144
+ }
145
+ get titleCount() {
146
+ return this.version >= 3 ? 8 : this.version >= 2 ? 7 : 6;
147
+ }
148
+ getDefSize(hardSize = 0) {
149
+ if (this.version === 2) return 2368;
150
+ if (this.version === 3) return 2624;
151
+ if (this.version === 259) return hardSize > 0 && hardSize !== 4294967295 ? hardSize : 9152;
152
+ return 2112;
153
+ }
154
+ toBuffer() {
155
+ const output = Buffer.from(this.raw);
156
+ output.writeUInt16LE(this.version);
157
+ if (this.tileData.length !== 512 || this.palette.length !== 32) throw new RangeError("Invalid banner icon size");
158
+ this.tileData.copy(output, 32);
159
+ this.palette.copy(output, 544);
160
+ for (let i = 0; i < this.titleCount; i++) {
161
+ const text = Buffer.from(this[titles[i]], "utf16le");
162
+ if (text.length > 256) throw new RangeError(`Banner ${titles[i]} exceeds 128 UTF-16 code units`);
163
+ output.fill(0, 576 + i * 256, 832 + i * 256);
164
+ text.copy(output, 576 + i * 256);
165
+ }
166
+ output.writeUInt16LE(crc16(output.subarray(32, 2112)), 2);
167
+ if (this.version >= 2) output.writeUInt16LE(crc16(output.subarray(32, 2368)), 4);
168
+ if (this.version >= 3) output.writeUInt16LE(crc16(output.subarray(32, 2624)), 6);
169
+ if (this.version >>> 8 >= 1) output.writeUInt16LE(crc16(output.subarray(4672, 9152)), 8);
170
+ return output;
171
+ }
172
+ };
173
+
113
174
  // src/nitro/crypto.ts
114
175
  var KEY1 = Buffer.from(
115
176
  "99D5205F5744F5B96E19A4D99E6A5A94D8AEF1EB4175E23A9382D03233EE31D5CC57619A3706A21B793972F555AEF6BE5F1B69FBE59DF1E9CE2CD9A15E3205E6FED3FECFD462040D8BF5ECB72B6079BB1295310D6E3FDA2B8884F0F13D127E254522F1BB24061A0611ADDF288B6481342BEB332999AAF2BD9C14959D9FF7F58C7297A1299DD15FCF664D071ADED34A4B85C9A7A31795053A3D490ABF0A898BA24A8249DD2790F10BE9EB1C6A83764505BA817061173F4BDEAECFAB3957F23A564811AD8A40E1453FFA9B0254CAA693FBEF4DFE6FA3D8879C08BAD5486A8D2DFD6E15F874BDBE528B18228A9EFB7437071B366C4A19BA4262B97991107B676596FE0223E8EE998C773E5C86644D6D7886A54F65E21EB2DF5A0AD07E0814B071ACBDDB831CB9D7A162CDC6637C5269C3E6BF75CE12445D2104FAFBD33C381163D4958541494609F2084311DC1F76C0156D1F3C6370EA87806CC3BD638BC2372137DCEE09232E376A4D7390F75030AC1C92041023914FD207AA683E4F9AC964606AC81421F3D62241124424CFE68A56DD0D534DE1851E8C525A9C1984C20357F16FE300BE58F64CEDD521649C1FBE55033C4ADCFFAAC9DAE05D5EBFE6DEF5D8B1F8FF36B3B9626795DB315F37ED4C70679990B518316C3D9999E442DAD3254213A0AED7706CB155CFC7D746D54361173D4428E93385D5D0A293AA25121FFBC50B46F597765645A6BE87B1946BE8B1FE3399AE1F3E6C39711D09009037E4103E7574FF8C833BB0F1B0F90105474295F1D6AC7E38E69E9574263FB4685018D04330B44C4BE368BFE54DB6958B0AA074253277CFA1F72CD871135AABEAC951E80DEEEFE9937E19A71E433881162CA148E373CC29216CD35DCEA0D9617143A01513B56492CF2A19DCADB7A59F8665F81A9FE7FBF7FDB8136C27DB6FDF351CF78D2C5B9B12AB386406CCDE31E84E751164E3FAEAEB3454C2AD3F34EB932C7D26369D56F35AE1F6B398634A9E3283E49A84607D902E130EEE934B36A285EC1638E8880602BFF0A03AEDD76A9A73E157CFF844B8DC2E2359D1DF9552719961A04BD57F6E78BAA9C530D34086329D320C9C37B7022FBA5498A9C41304C98DBEC8E75D97502E93D622590C27BC2292E0A7200F936F7F4C9FD3B5A62A0B7467497D1026CBD1C58671E78CA09CE95BB21AF601EE8C9E5E83F21ADBE6E5EA845976D27CF68DA5493648C21652BB83A374B9070C3BFF6128E161E9E4EF6E15AA4EBAE85D0596BB3256B0FB72520F0EC84225657689AFF2DE1027F0014B74A79707D5265454091F820A867D30390EB3269B0B57BB360631AFFD79FCD930102B0CB3E19BD77BDC5FEFD2F813454D4775BD46963C7E75F33EB567C59A3BB05B296BDE805BC81505B131B6CE49DDAD84B5AE60DC67313430FE4EBD802FA6BF63392186D9357F1668220554E990268C076C51A43155D70907A83E2E5366C1F8F27BC4F258CFF187C5A2E7278F308758A064622318B9887CFACEC498AEAD17CC4A5BF3E948D556D30DF2C892738CDBD72F56AC81F992694DC632F6E6C08D21E276806111BCDC6C93AF19699BD0BFB9319F0267A351EE8306227B0CAB494240B8D5017DCE5EF7555339C59946D8879FBAF764B4E39AFAA16D90681030CA8A54A79F60C319F56B0D7A5198E6984351B4D635E94FC3DF0F7BD62F5CBD3A156119F14BCBAADC6D64C9D3C61E56EF384C50718675CC0D0D4EE928F6065D701BAAD345CFA839AC95A62EB4E422D474A8375F487A04CCA54C40D828B428080D1C725241F07D47193A534E5884626B93B58A81214E0DDCB43FA2C6FCC92B40DA3804E95E5A866B0C22258568118D7C921D95554DAB8EBBDAA6E6B751B6325A0541DD052A0A5650911747CCC9E67EB5614ADB736751C833F5DA6E742E54C3370D6DAF08E8158A5FE25921CDA8DE0C065A776B5FDB18653EC850DE78E0B882B35D4E7232074FC13423BA96B7674EA4281E3462EB2D6A70E92F42C4704E5A319CF95B4728AADA716F381FB378C4926B1C9EF6359AB74D0EBFCC1829410348355D55D02BC629AF5C6074698E5E9B7CD4BD7B44647D3F925D69B61F004BD48335CF7E644E17AE8DD52E9A28124E2E2B49085CAEC64685AE41611E6F82D25137161F0BF659A49ACA5AAF0DD4338B2063F184805CCBCF08B4B9D31605BD6283319B5651989FBAB25BAAB2226B2CB5D448FA632B5F58FA61FA6409BB38E0B89D9260A80D676F0E37F50D019FC277D4FEECF1733039E07DF56198E42C2855045655DB2F6BECE55806B664806A2A1A4E5B0FD8C40A2E5219D962F53048BE8C7B4F389BA2C3AFC9D3C7C1624186B96121576F994FC1BACE7BB53B4D5E8A8B44575F135F706D5B2947DC38E2EC045565122AE81743E18EDD2AB3E294F7096E5CE6EB8AF86D89495448F52FADBFEA944BCAFC3987825F8A01F275F2E671D6D842DEF12D1D28A6887EA3A0471D30D9A371DF491CCB01F836B1F2F022585D456BBDA0BBB28842C78C28CE93E8906308907C893CF57DB7042D4F555116FD7E79E8BEC1F212D4F8B4840523A0CCD22BFDE1ABAD0DD1556C2341944D77374F05280CBF17B312676C8CC35AF741842A6DD09412272CB4ED9C4DEC478297D567B91B9DC055077EE58EE2A8E73E12E40E3A2A455534A2F92D5A1BAB527C83105F55D2F15A432BC6A7A4891595E8B44B9DF875E39F60785BD6E60D44E62106BD472253A400AD8D43138539F7AAFC38AF7BEDFCE42B5450984CFC8580F7DF3C8022E194DADE24C6B07A3938DC0FA1A7F4F96F6318578B84412A2ED453F2D9000FD0DD996E19A60AD0EC5B5824ABC0CB0665EC1A1338940A67032F3FF7E377447733C61439D0E3C0A20879BB409957410B0190CDE1CC4867DBB3AF8874F34C828F72B1B52329C4126C19FC8E46A49CC4256587D36DBE8A93110338ED832BF346A493EA3B53851DCED4F1088327EDFC9B1A18BCF98BAEDC24AB5038E9724B1022177B465DAB5964F340AEF8BBE5C8F926034E557DEBEBFEF739E6E00A11BE2E28FF98EDC0C9425642C3FD00F6AF87A25B013F329247959A72A5323DAE6BD09B07D24992E3784AFAA1067DF241CF77740414B20C86846416D5BB51A1E56FF1D1F2E2F75F58204DB857C7CFDDC5D8BE763DF65F7EE72A8B88241B383F0E412377F5F04BD40C1FFAA40B805FCF45F6E0DA2F345953FB203C52625E35B562FE8B6063E3865A151A6ED14745BC32B4EB6738ABE46E333AB5EDA3AD67E04E4195EE626271261D31EF6230AFD782ACC2DC0504F59707BF11592307C06402E897E53EAF18AC59A68B4A33901C6E7C9C207E4C3C3E6164BBC56B7C7E3E9FC54C9FEA73F5D789C04CF4FBF42DEC141B51D5C112C810DF0B4A8B9CBC93456A3E3E7DC1A9BACDC1B407E4E1688643B26D38F3FB0C5C663771DE56EF6EA0104065A798F7D0BE0EC83736EC10CA7C9CAB841E051776021C4F52AA5FC1C6A056B9D80484444DA759D8DE60E6380E058F03E13B6D8104336F300BCE69052133FB26BB897DB6AE877E5107E0ACF7960A6BF9C45C1DE44447B85EFAE3788455424B485EF77D4735861D2B430503EC8AB81E063C760C481A43A7B78AED1E13C643EE10EFDBECFB3C83B29544EFD854514E2D11441DFB36591E7A34C1C3CA570061EA67A5169B55D055E17FD936D24076AEDC01CEB07A83D5CB2098EC6BC1729234F3825737628A32360C9043AEAE5C9B788E136502FD6871C1FEB031A02482B0C3B17969A7F5D2EBD082C032DC9EC7263C6D8D98C1BB22D4D00F33EC3EB9CCE1DC6A4C7736141CF9BF819F285F71853229907548C4B34ACED8448F142FFD4057EFAA0875D946D1D66E32551FC318FE841FFC84D5FF715E1B48C386950E280827D33883717B4C8063549A56B0ACCF80CA3109EFFEF3BEAF247EA6FE533FC28D4A3368D122A666AD7BEADEB643B0A1259500A33F7546141144ECD795BC92F04FA916536297602A0F41F17124BEEE947F08CD6093B3855B07003FD80F28839AD1699FD1DA2EC39001A2B96B4E2A669DDAAEA6EA2AD3682F0C0C9CD28C4AEDE29E57659D0987A3B4C4325DC9D4322BB1E0711E644DE69071E31E40ED7DF3840EEDC87876AEC0712772BB05EA0264FBF3486BB542933FED9F1353D2F7FE2AEC1D4725DB3C9186C68EF011FD237436F7A4F59E7A7E535044D447CAD3EB386DE6D971947F4AC6694B11F452EA22FE8AB036678B59E8E6802AEB650413EEECDC9E5FB1EC056A59E69F5E596B89BFF71ACA44F95B6A718503E42962E0706F41C4CFB2B1CCE37EA607A887E77F8493DB524B6CEC7EDDD4244810699F046074E64818F3E42CB94F2E507ADFD454692B8BA7F3CEFF1FF33E26013917958489B0F04C4B82919FC44BAC9DA574AF1725C9CA32D3BC898A8489CC0DAE7CA2DB9C6A7891EEEA765D4E8760F5691567D402CFAF483607EABF6F662D068FC49AFEF9F6908775B8F7AD0F76105A3D59B02EB3C7352CCC70562BCBE33796C52F461B8A2246C788A726329861DF86228AF41C2F87A109AACCA9AED3BD00451C9A5487865287EFFF1E8FA18FC1895C351BDA2D3A2C16B2C2F156E278C16B6397C5568FC9327F2CAAAFA6A8AC20912288DEE4608BF94B42251AE37F9C2C19893A7E05D436CC6958C2C1328B2F9085EB7A3950A5A12792C566B0204F587E5583432B45E29CE4D812902C168356167903B3AD2D61181A131F37E2E19C737B80D5FD2D5187FC7BAAD71F2C7A8EAFF48DBBCD95117C720BEE6FE2B9AFDE3783DE8C8D620567B796C68D56B60DD762BAD64636BD8EC8E6EA2A6C1014FF6B5BFA823C46B1304346518A7D9B923E83795B555DB26C5ECE90628E5398C90D6DE52D57CDC58157BAE1E8B88F72E54F13DCEA9D711510B21188D509D47F5B657F2C3B384C1168508DFB9EB059BF9480894AC51A18128953D14A1029E88C1CECB6EA46C7178B251531A8A26B43B19DE2DB0B879BB011040E71D2297789820A66417F1D0B48FF72BB24FDC248A19BFE7B7FCE88DB86D9853B1CB0DCA83307BF512EE30E9A00971E06C097439DD8B645C486675F00F8889AA4529EC7AA8A8375ECC518AECEC32F1A2BF918FFAE1AF5530BB53351A7FDE8A8E1A264B622174380CC0AD8AE3BBA40D7D9924A89DF0410EE9B182B6A77698A68F4F9B9A221156EE61E3B0362309B60417E259B9E8FC5521008F8C269A1211188375E793566FF1042186EED97B66B1C4E36E56D7DB4E4BF20B9E0053A69D5B8E3D5DCE0B9AC533E07A457AD77FF4818762AAC492A8E47756D9F676330358C390539D56F643A5BADCA0BBB82529945B193363699AF13204436D8024409399285FF4A4A9787A663D7C7B5B524ED0FB46F0C585214D9A67BD379BC3858A1BD3B8406D81A06FD6BA8EA4B69280437AD8299FB0E1B85BDA85D73CDDC58750ABE636C48E74CE4302B0460B915D8DA8681758F96D48D1C5D70857C1C677BD50867A6CE4B0A6670B7E563D45B8A82EA1067CAE2F4EF17852F2A5F8A9782F86AD63410EAEBC95C3CE149F846EBDEBDF6A992F1AAA6A018B03AD30F1FF36FFF31454344D3509AF7880996C1CE76CCF22C2CBAAD82778F1884C0D2079C3690834E0BA54F433E04AB784FD6FB09012490DA6F3C3A610D7F694AEB2B3002B4DBE084A9ECD735BF377D8558CEA94EE480C7A8D3306748EB29AF2F746AB4A73F0F3F92AFF3CAACAF4BD994C043CA810D2F48A1B027D5D2EF4B0585A3DE4D93303CF0BB4A8F30274CEBE33E64ED9A2F3BF182F0BAF4CF7F40CBB0E17FBCAA57D3C974F2FA430D22D0F4774E93D785701F99BFB6DE35F130A75E71F06B012D7B64F033530A3988F36B3AA66B35D22F43CD02FDB5E9BC5BAAD8A4197E0E5D94819E6F77ADD60E749396E7C4185FADF519",
116
177
  "hex"
117
178
  );
118
179
  var aes128CtrCrypt = (key2, counter, data) => {
119
- if (key2.length !== 16 || counter.length !== 16)
120
- throw new RangeError("AES key and counter must have 16 bytes");
180
+ if (key2.length !== 16 || counter.length !== 16) throw new RangeError("AES key and counter must have 16 bytes");
121
181
  const ctr = Buffer.from(counter).reverse();
122
182
  const aes = createAesBlockCipher(Buffer.from(key2).reverse());
123
183
  const output = Buffer.alloc(data.length);
@@ -140,12 +200,8 @@ var modcryptKey = (header) => {
140
200
  key2.write(header.gameCode, 12, 4, "ascii");
141
201
  return key2;
142
202
  }
143
- if (!header.hmac_arm9i || header.hmac_arm9i.length < 16)
144
- throw new Error("Missing ARM9i HMAC for modcrypt key");
145
- const x = Buffer.from(
146
- "Nintendo" + header.gameCode + [...header.gameCode].reverse().join(""),
147
- "ascii"
148
- );
203
+ if (!header.hmac_arm9i || header.hmac_arm9i.length < 16) throw new Error("Missing ARM9i HMAC for modcrypt key");
204
+ const x = Buffer.from(`Nintendo${header.gameCode}${[...header.gameCode].reverse().join("")}`, "ascii");
149
205
  for (let i = 0; i < 16; i++) x[i] ^= header.hmac_arm9i[i];
150
206
  const mask = (1n << 128n) - 1n;
151
207
  let v = x.readBigUInt64LE(0) | x.readBigUInt64LE(8) << 64n;
@@ -157,8 +213,7 @@ var modcryptKey = (header) => {
157
213
  return out;
158
214
  };
159
215
  var encryptSecureArea = (gameCode, input) => {
160
- if (typeof gameCode !== "string" || gameCode.length !== 4)
161
- throw new Error("Expected four-character game code");
216
+ if (typeof gameCode !== "string" || gameCode.length !== 4) throw new Error("Expected four-character game code");
162
217
  if (input.length < 2048) throw new RangeError("Secure area must contain at least 0x800 bytes");
163
218
  const output = Buffer.from(input);
164
219
  let magic = new Uint32Array(1042);
@@ -210,199 +265,6 @@ var encryptSecureArea = (gameCode, input) => {
210
265
  return output;
211
266
  };
212
267
 
213
- // src/nitro/header.ts
214
- var UnitCode = Object.freeze({ NDS: 0, NDS_DSi: 2, DSi: 3 });
215
- var Header = class {
216
- raw;
217
- trimmedRom;
218
- doublePadding;
219
- nitrocode;
220
- decrypted;
221
- headerCRC;
222
- logoCRC;
223
- secureCRC;
224
- dlp_signature;
225
- constructor(input) {
226
- const data = bytes(input, "header");
227
- range(data, 0, 368, "header");
228
- const size = data.readUInt32LE(132);
229
- if (size < 368 || size > data.length) throw new RangeError(`Invalid header size: ${size}`);
230
- this.raw = Buffer.from(data.subarray(0, size));
231
- if (this.unitCode & 2 && size > 512) range(this.raw, 0, 4096, "DSi header");
232
- this.trimmedRom = this.total_rom_size ? this.total_rom_size >= data.length : data.length !== this.size;
233
- this.doublePadding = this.ARM9size % 1024 < 512 && this.ARM7romOffset % 1024 === 0 && this.ARM9overlayOffset % 1024 === 0 || this.ARM7size % 1024 < 512 && this.FNToffset % 1024 === 0 && this.ARM7overlayOffset % 1024 === 0 || this.FNTsize % 1024 < 512 && this.FAToffset % 1024 === 0 || this.FATsize % 1024 < 512 && this.bannerOffset % 1024 === 0;
234
- const nitroOffset = this.ARM9romOffset + this.ARM9size;
235
- this.nitrocode = nitroOffset >= size && nitroOffset + 4 <= data.length && data.readUInt32LE(nitroOffset) === 3737126433;
236
- this.decrypted = data.length >= 16392 && data.readBigUInt64LE(16384) === 0xe7ffdeffe7ffdeffn;
237
- this.headerCRC = crc16(this.raw.subarray(0, 350)) === this.headerCRC16;
238
- this.logoCRC = crc16(this.logo) === this.logoCRC16;
239
- const secure = data.subarray(16384, 32768);
240
- this.secureCRC = secure.length > 0 && crc16(this.decrypted ? encryptSecureArea(this.gameCode, secure) : secure) === this.secureCRC16;
241
- this.dlp_signature = Buffer.alloc(0);
242
- if (this.ROMsize >= size && this.ROMsize + 136 <= data.length && data.readUInt32LE(this.ROMsize) === 90977) {
243
- this.dlp_signature = Buffer.from(data.subarray(this.ROMsize, this.ROMsize + 136));
244
- }
245
- }
246
- get size() {
247
- return 2 ** (17 + this.raw[20]);
248
- }
249
- set size(value) {
250
- const power = Math.log2(value);
251
- if (!Number.isInteger(power) || power < 17 || power > 32)
252
- throw new RangeError("ROM capacity must be a power of two from 128 KiB to 4 GiB");
253
- this.raw[20] = power - 17;
254
- }
255
- toBuffer() {
256
- const data = Buffer.from(this.raw);
257
- data.writeUInt16LE(crc16(data.subarray(192, 348)), 348);
258
- data.writeUInt16LE(crc16(data.subarray(0, 350)), 350);
259
- return data;
260
- }
261
- toString() {
262
- return `NDS header: ${this.gameTitle.replace(/\0+$/, "")} (${this.gameCode})`;
263
- }
264
- };
265
- function field(name, offset, length, type = "buffer", dsi = false) {
266
- Object.defineProperty(Header.prototype, name, {
267
- enumerable: true,
268
- get() {
269
- if (dsi && (!(this.unitCode & 2) || this.raw.length <= 512))
270
- return type === "buffer" ? Buffer.alloc(length) : 0;
271
- const data = range(this.raw, offset, length, `header ${name}`);
272
- if (type === "string") return data.toString("latin1");
273
- if (type === "buffer") return data;
274
- return length === 8 ? data.readBigUInt64LE() : data.readUIntLE(0, length);
275
- },
276
- set(value) {
277
- const target = range(this.raw, offset, length, `header ${name}`);
278
- if (type === "string") {
279
- if (typeof value !== "string" || Buffer.byteLength(value, "latin1") > length)
280
- throw new TypeError(`Invalid ${name}`);
281
- target.fill(0);
282
- target.write(value, "latin1");
283
- } else if (type === "buffer") {
284
- if (!(value instanceof Uint8Array)) throw new TypeError(`Invalid ${name}`);
285
- const source = bytes(value, name);
286
- if (source.length !== length) throw new RangeError(`${name} must contain ${length} bytes`);
287
- source.copy(target);
288
- } else if (length === 8) {
289
- if (typeof value !== "bigint") throw new TypeError(`Invalid ${name}`);
290
- target.writeBigUInt64LE(value);
291
- } else {
292
- if (typeof value !== "number") throw new TypeError(`Invalid ${name}`);
293
- target.writeUIntLE(value, 0, length);
294
- }
295
- }
296
- });
297
- }
298
- field("gameTitle", 0, 12, "string");
299
- field("gameCode", 12, 4, "string");
300
- field("makerCode", 16, 2, "string");
301
- for (const [name, offset] of Object.entries({
302
- unitCode: 18,
303
- encryptionSeed: 19,
304
- twlInternalFlags: 28,
305
- permitsFlags: 29,
306
- ROMversion: 30,
307
- internalFlags: 31
308
- }))
309
- field(name, offset, 1, "number");
310
- field("reserved", 21, 7);
311
- var ntr32 = "ARM9romOffset ARM9entryAddress ARM9ramAddress ARM9size ARM7romOffset ARM7entryAddress ARM7ramAddress ARM7size FNToffset FNTsize FAToffset FATsize ARM9overlayOffset ARM9overlaySize ARM7overlayOffset ARM7overlaySize flagsRead flagsInit bannerOffset".split(
312
- " "
313
- );
314
- ntr32.forEach((name, i) => field(name, 32 + i * 4, 4, "number"));
315
- field("secureCRC16", 108, 2, "number");
316
- field("ROMtimeout", 110, 2, "number");
317
- field("ARM9autoload", 112, 4, "number");
318
- field("ARM7autoload", 116, 4, "number");
319
- field("secureDisable", 120, 8, "number");
320
- field("ROMsize", 128, 4, "number");
321
- field("headerSize", 132, 4, "number");
322
- field("reserved2", 136, 56);
323
- field("logo", 192, 156);
324
- field("logoCRC16", 348, 2, "number");
325
- field("headerCRC16", 350, 2, "number");
326
- "debug_romOffset debug_size debug_ramAddress reserved3".split(" ").forEach((name, i) => field(name, 352 + i * 4, 4, "number"));
327
- field("appflags", 444, 4, "buffer", true);
328
- "mbk9_wramcnt_setting region_flags access_control scfg_ext_mask".split(" ").forEach((name, i) => field(name, 428 + i * 4, 4, "number", true));
329
- var twl32 = "dsi9_rom_offset offset_0x1C4 dsi9_ram_address dsi9_size dsi7_rom_offset offset_0x1D4 dsi7_ram_address dsi7_size digest_ntr_start digest_ntr_size digest_twl_start digest_twl_size sector_hashtable_start sector_hashtable_size block_hashtable_start block_hashtable_size digest_sector_size digest_block_sectorcount banner_size offset_0x20C total_rom_size offset_0x214 offset_0x218 offset_0x21C modcrypt1_start modcrypt1_size modcrypt2_start modcrypt2_size tid_low tid_high public_sav_size private_sav_size".split(
330
- " "
331
- );
332
- twl32.forEach((name, i) => field(name, 448 + i * 4, 4, "number", true));
333
- "hmac_arm9 hmac_arm7 hmac_digest_master hmac_icon_title hmac_arm9i hmac_arm7i".split(" ").forEach((name, i) => field(name, 768 + i * 20, 20, "buffer", true));
334
- field("hmac_arm9_no_secure", 928, 20, "buffer", true);
335
- field("rsa_signature", 3968, 128, "buffer", true);
336
-
337
- // src/nitro/banner.ts
338
- var titles = [
339
- "japaneseTitle",
340
- "englishTitle",
341
- "frenchTitle",
342
- "germanTitle",
343
- "italianTitle",
344
- "spanishTitle",
345
- "chineseTitle",
346
- "koreanTitle"
347
- ];
348
- var Banner = class {
349
- version;
350
- raw;
351
- bannerCRC;
352
- tileData;
353
- palette;
354
- japaneseTitle = "";
355
- englishTitle = "";
356
- frenchTitle = "";
357
- germanTitle = "";
358
- italianTitle = "";
359
- spanishTitle = "";
360
- chineseTitle = "";
361
- koreanTitle = "";
362
- constructor(input, offset = 0, hardSize = 0) {
363
- const data = bytes(input, "banner");
364
- this.version = range(data, offset, 2, "banner").readUInt16LE();
365
- let size = this.getDefSize(hardSize);
366
- if (this.version >>> 8 === 1)
367
- size = hardSize > 0 && hardSize !== 4294967295 ? Math.min(hardSize, 9152) : 9152;
368
- this.raw = Buffer.from(range(data, offset, size, "banner"));
369
- this.bannerCRC = crc16(this.raw.subarray(32, 2112)) === this.raw.readUInt16LE(2);
370
- this.tileData = Buffer.from(this.raw.subarray(32, 544));
371
- this.palette = Buffer.from(this.raw.subarray(544, 576));
372
- for (const [i, title] of titles.entries())
373
- this[title] = i < this.titleCount ? this.raw.subarray(576 + i * 256, 832 + i * 256).toString("utf16le").split("\0")[0] : "";
374
- }
375
- get titleCount() {
376
- return this.version >= 3 ? 8 : this.version >= 2 ? 7 : 6;
377
- }
378
- getDefSize(hardSize = 0) {
379
- if (this.version === 2) return 2368;
380
- if (this.version === 3) return 2624;
381
- if (this.version === 259) return hardSize > 0 && hardSize !== 4294967295 ? hardSize : 9152;
382
- return 2112;
383
- }
384
- toBuffer() {
385
- const output = Buffer.from(this.raw);
386
- output.writeUInt16LE(this.version);
387
- if (this.tileData.length !== 512 || this.palette.length !== 32)
388
- throw new RangeError("Invalid banner icon size");
389
- this.tileData.copy(output, 32);
390
- this.palette.copy(output, 544);
391
- for (let i = 0; i < this.titleCount; i++) {
392
- const text = Buffer.from(this[titles[i]], "utf16le");
393
- if (text.length > 256)
394
- throw new RangeError(`Banner ${titles[i]} exceeds 128 UTF-16 code units`);
395
- output.fill(0, 576 + i * 256, 832 + i * 256);
396
- text.copy(output, 576 + i * 256);
397
- }
398
- output.writeUInt16LE(crc16(output.subarray(32, 2112)), 2);
399
- if (this.version >= 2) output.writeUInt16LE(crc16(output.subarray(32, 2368)), 4);
400
- if (this.version >= 3) output.writeUInt16LE(crc16(output.subarray(32, 2624)), 6);
401
- if (this.version >>> 8 >= 1) output.writeUInt16LE(crc16(output.subarray(4672, 9152)), 8);
402
- return output;
403
- }
404
- };
405
-
406
268
  // src/nitro/filesystem.ts
407
269
  var FileAllocationTable = class {
408
270
  entries;
@@ -432,8 +294,7 @@ var FileNameTable = class _FileNameTable {
432
294
  const visited = /* @__PURE__ */ new Set();
433
295
  const fileIds = /* @__PURE__ */ new Set();
434
296
  const parse = (index, name, depth) => {
435
- if (depth > 256 || index >= count || visited.has(index))
436
- throw new Error("Invalid FNT directory tree or cycle");
297
+ if (depth > 256 || index >= count || visited.has(index)) throw new Error("Invalid FNT directory tree or cycle");
437
298
  visited.add(index);
438
299
  let cursor = table.readUInt32LE(index * 8);
439
300
  let fileId = table.readUInt16LE(index * 8 + 4);
@@ -513,7 +374,6 @@ var OverlayTable = class {
513
374
  });
514
375
  }
515
376
  }
516
- isArm9;
517
377
  entries = [];
518
378
  readBasicOverlays(fat) {
519
379
  return this.entries.map((item) => {
@@ -534,6 +394,130 @@ var OverlayTable = class {
534
394
  }
535
395
  };
536
396
 
397
+ // src/nitro/header.ts
398
+ var UnitCode = Object.freeze({ NDS: 0, NDS_DSi: 2, DSi: 3 });
399
+ var Header = class {
400
+ raw;
401
+ trimmedRom;
402
+ doublePadding;
403
+ nitrocode;
404
+ decrypted;
405
+ headerCRC;
406
+ logoCRC;
407
+ secureCRC;
408
+ dlp_signature;
409
+ constructor(input) {
410
+ const data = bytes(input, "header");
411
+ range(data, 0, 368, "header");
412
+ const size = data.readUInt32LE(132);
413
+ if (size < 368 || size > data.length) throw new RangeError(`Invalid header size: ${size}`);
414
+ this.raw = Buffer.from(data.subarray(0, size));
415
+ if (this.unitCode & 2 && size > 512) range(this.raw, 0, 4096, "DSi header");
416
+ this.trimmedRom = this.total_rom_size ? this.total_rom_size >= data.length : data.length !== this.size;
417
+ this.doublePadding = this.ARM9size % 1024 < 512 && this.ARM7romOffset % 1024 === 0 && this.ARM9overlayOffset % 1024 === 0 || this.ARM7size % 1024 < 512 && this.FNToffset % 1024 === 0 && this.ARM7overlayOffset % 1024 === 0 || this.FNTsize % 1024 < 512 && this.FAToffset % 1024 === 0 || this.FATsize % 1024 < 512 && this.bannerOffset % 1024 === 0;
418
+ const nitroOffset = this.ARM9romOffset + this.ARM9size;
419
+ this.nitrocode = nitroOffset >= size && nitroOffset + 4 <= data.length && data.readUInt32LE(nitroOffset) === 3737126433;
420
+ this.decrypted = data.length >= 16392 && data.readBigUInt64LE(16384) === 0xe7ffdeffe7ffdeffn;
421
+ this.headerCRC = crc16(this.raw.subarray(0, 350)) === this.headerCRC16;
422
+ this.logoCRC = crc16(this.logo) === this.logoCRC16;
423
+ const secure = data.subarray(16384, 32768);
424
+ this.secureCRC = secure.length > 0 && crc16(this.decrypted ? encryptSecureArea(this.gameCode, secure) : secure) === this.secureCRC16;
425
+ this.dlp_signature = Buffer.alloc(0);
426
+ if (this.ROMsize >= size && this.ROMsize + 136 <= data.length && data.readUInt32LE(this.ROMsize) === 90977) {
427
+ this.dlp_signature = Buffer.from(data.subarray(this.ROMsize, this.ROMsize + 136));
428
+ }
429
+ }
430
+ get size() {
431
+ return 2 ** (17 + this.raw[20]);
432
+ }
433
+ set size(value) {
434
+ const power = Math.log2(value);
435
+ if (!Number.isInteger(power) || power < 17 || power > 32)
436
+ throw new RangeError("ROM capacity must be a power of two from 128 KiB to 4 GiB");
437
+ this.raw[20] = power - 17;
438
+ }
439
+ toBuffer() {
440
+ const data = Buffer.from(this.raw);
441
+ data.writeUInt16LE(crc16(data.subarray(192, 348)), 348);
442
+ data.writeUInt16LE(crc16(data.subarray(0, 350)), 350);
443
+ return data;
444
+ }
445
+ toString() {
446
+ return `NDS header: ${this.gameTitle.replace(/\0+$/, "")} (${this.gameCode})`;
447
+ }
448
+ };
449
+ function field(name, offset, length, type = "buffer", dsi = false) {
450
+ Object.defineProperty(Header.prototype, name, {
451
+ enumerable: true,
452
+ get() {
453
+ if (dsi && (!(this.unitCode & 2) || this.raw.length <= 512))
454
+ return type === "buffer" ? Buffer.alloc(length) : 0;
455
+ const data = range(this.raw, offset, length, `header ${name}`);
456
+ if (type === "string") return data.toString("latin1");
457
+ if (type === "buffer") return data;
458
+ return length === 8 ? data.readBigUInt64LE() : data.readUIntLE(0, length);
459
+ },
460
+ set(value) {
461
+ const target = range(this.raw, offset, length, `header ${name}`);
462
+ if (type === "string") {
463
+ if (typeof value !== "string" || Buffer.byteLength(value, "latin1") > length)
464
+ throw new TypeError(`Invalid ${name}`);
465
+ target.fill(0);
466
+ target.write(value, "latin1");
467
+ } else if (type === "buffer") {
468
+ if (!(value instanceof Uint8Array)) throw new TypeError(`Invalid ${name}`);
469
+ const source = bytes(value, name);
470
+ if (source.length !== length) throw new RangeError(`${name} must contain ${length} bytes`);
471
+ source.copy(target);
472
+ } else if (length === 8) {
473
+ if (typeof value !== "bigint") throw new TypeError(`Invalid ${name}`);
474
+ target.writeBigUInt64LE(value);
475
+ } else {
476
+ if (typeof value !== "number") throw new TypeError(`Invalid ${name}`);
477
+ target.writeUIntLE(value, 0, length);
478
+ }
479
+ }
480
+ });
481
+ }
482
+ field("gameTitle", 0, 12, "string");
483
+ field("gameCode", 12, 4, "string");
484
+ field("makerCode", 16, 2, "string");
485
+ for (const [name, offset] of Object.entries({
486
+ unitCode: 18,
487
+ encryptionSeed: 19,
488
+ twlInternalFlags: 28,
489
+ permitsFlags: 29,
490
+ ROMversion: 30,
491
+ internalFlags: 31
492
+ }))
493
+ field(name, offset, 1, "number");
494
+ field("reserved", 21, 7);
495
+ var ntr32 = "ARM9romOffset ARM9entryAddress ARM9ramAddress ARM9size ARM7romOffset ARM7entryAddress ARM7ramAddress ARM7size FNToffset FNTsize FAToffset FATsize ARM9overlayOffset ARM9overlaySize ARM7overlayOffset ARM7overlaySize flagsRead flagsInit bannerOffset".split(
496
+ " "
497
+ );
498
+ ntr32.forEach((name, i) => field(name, 32 + i * 4, 4, "number"));
499
+ field("secureCRC16", 108, 2, "number");
500
+ field("ROMtimeout", 110, 2, "number");
501
+ field("ARM9autoload", 112, 4, "number");
502
+ field("ARM7autoload", 116, 4, "number");
503
+ field("secureDisable", 120, 8, "number");
504
+ field("ROMsize", 128, 4, "number");
505
+ field("headerSize", 132, 4, "number");
506
+ field("reserved2", 136, 56);
507
+ field("logo", 192, 156);
508
+ field("logoCRC16", 348, 2, "number");
509
+ field("headerCRC16", 350, 2, "number");
510
+ "debug_romOffset debug_size debug_ramAddress reserved3".split(" ").forEach((name, i) => field(name, 352 + i * 4, 4, "number"));
511
+ field("appflags", 444, 4, "buffer", true);
512
+ "mbk9_wramcnt_setting region_flags access_control scfg_ext_mask".split(" ").forEach((name, i) => field(name, 428 + i * 4, 4, "number", true));
513
+ var twl32 = "dsi9_rom_offset offset_0x1C4 dsi9_ram_address dsi9_size dsi7_rom_offset offset_0x1D4 dsi7_ram_address dsi7_size digest_ntr_start digest_ntr_size digest_twl_start digest_twl_size sector_hashtable_start sector_hashtable_size block_hashtable_start block_hashtable_size digest_sector_size digest_block_sectorcount banner_size offset_0x20C total_rom_size offset_0x214 offset_0x218 offset_0x21C modcrypt1_start modcrypt1_size modcrypt2_start modcrypt2_size tid_low tid_high public_sav_size private_sav_size".split(
514
+ " "
515
+ );
516
+ twl32.forEach((name, i) => field(name, 448 + i * 4, 4, "number", true));
517
+ "hmac_arm9 hmac_arm7 hmac_digest_master hmac_icon_title hmac_arm9i hmac_arm7i".split(" ").forEach((name, i) => field(name, 768 + i * 20, 20, "buffer", true));
518
+ field("hmac_arm9_no_secure", 928, 20, "buffer", true);
519
+ field("rsa_signature", 3968, 128, "buffer", true);
520
+
537
521
  // src/nitro/blz.ts
538
522
  var decompressBLZ = (input, maxSize = 268435456) => {
539
523
  if (input.length < 8) throw new Error("BLZ footer is truncated");
@@ -555,8 +539,7 @@ var decompressBLZ = (input, maxSize = 268435456) => {
555
539
  if (src - 2 < prefix) throw new Error("Truncated BLZ reference");
556
540
  const high = input[--src], low = input[--src];
557
541
  const count = (high >>> 4) + 3, offset = ((high & 15) << 8 | low) + 3;
558
- if (dst - count < prefix || dst - 1 + offset >= size)
559
- throw new Error("Invalid BLZ reference");
542
+ if (dst - count < prefix || dst - 1 + offset >= size) throw new Error("Invalid BLZ reference");
560
543
  for (let j = 0; j < count; j++) {
561
544
  --dst;
562
545
  out[dst] = out[dst + offset];
@@ -650,10 +633,7 @@ var decompressArm9 = (data, header) => {
650
633
  const compressed = ptr > header.ARM9ramAddress && ptr + nitro >= header.ARM9ramAddress + data.length;
651
634
  return {
652
635
  compressed,
653
- data: compressed ? Buffer.concat([
654
- decompressBLZ(data.subarray(0, data.length - nitro)),
655
- data.subarray(data.length - nitro)
656
- ]) : Buffer.from(data)
636
+ data: compressed ? Buffer.concat([decompressBLZ(data.subarray(0, data.length - nitro)), data.subarray(data.length - nitro)]) : Buffer.from(data)
657
637
  };
658
638
  };
659
639
  var compressArm9 = (data, header, postSize = 0) => {
@@ -703,8 +683,7 @@ function validate(h) {
703
683
  }
704
684
  }
705
685
  const s = h.digest_sector_size;
706
- if (!Number.isSafeInteger(s) || s <= 0 || s > 16384 || 16384 % s)
707
- throw new Error("Invalid TWL digest sector size");
686
+ if (!Number.isSafeInteger(s) || s <= 0 || s > 16384 || 16384 % s) throw new Error("Invalid TWL digest sector size");
708
687
  if (!Number.isSafeInteger(h.digest_block_sectorcount) || h.digest_block_sectorcount <= 0 || h.digest_block_sectorcount > 1048576)
709
688
  throw new Error("Invalid TWL digest block sector count");
710
689
  if (h.digest_ntr_size < 16384 || h.digest_ntr_size % s || h.digest_twl_size % s)
@@ -734,45 +713,25 @@ var TWL = class {
734
713
  this.Hashtable2Data = Buffer.from(
735
714
  slice(original, h.block_hashtable_start, h.block_hashtable_size, "Block hash table")
736
715
  );
737
- this.DSi9Data = Buffer.from(
738
- slice(original, h.dsi9_rom_offset, Math.max(h.modcrypt1_size, h.dsi9_size), "ARM9i")
739
- );
740
- this.DSi7Data = Buffer.from(
741
- slice(original, h.dsi7_rom_offset, Math.max(h.modcrypt2_size, h.dsi7_size), "ARM7i")
742
- );
716
+ this.DSi9Data = Buffer.from(slice(original, h.dsi9_rom_offset, Math.max(h.modcrypt1_size, h.dsi9_size), "ARM9i"));
717
+ this.DSi7Data = Buffer.from(slice(original, h.dsi7_rom_offset, Math.max(h.modcrypt2_size, h.dsi7_size), "ARM7i"));
743
718
  this.Header2Data = h.trimmedRom ? null : Array.from(
744
719
  { length: 3 },
745
- (_, i) => Buffer.from(
746
- slice(original, h.digest_twl_start - 12288 + 4096 * i, 4096, "Secondary header")
747
- )
748
- );
749
- this.Overlays9Sha1Hmac = overlays.map(
750
- (o) => hash(slice(original, o.offset, o.size, "ARM9 overlay"))
720
+ (_, i) => Buffer.from(slice(original, h.digest_twl_start - 12288 + 4096 * i, 4096, "Secondary header"))
751
721
  );
722
+ this.Overlays9Sha1Hmac = overlays.map((o) => hash(slice(original, o.offset, o.size, "ARM9 overlay")));
752
723
  let encrypted = false;
753
724
  const modcryptStart = [
754
725
  { start: h.modcrypt1_start, size: h.modcrypt1_size },
755
726
  { start: h.modcrypt2_start, size: h.modcrypt2_size }
756
- ].find(
757
- ({ start, size }) => size > 0 && start >= h.digest_twl_start && start < 4294967295
758
- )?.start;
727
+ ].find(({ start, size }) => size > 0 && start >= h.digest_twl_start && start < 4294967295)?.start;
759
728
  if (modcryptStart !== void 0) {
760
729
  const index = Math.floor((modcryptStart - h.digest_twl_start) / h.digest_sector_size);
761
730
  const actual = hash(
762
- slice(
763
- original,
764
- h.digest_twl_start + index * h.digest_sector_size,
765
- h.digest_sector_size,
766
- "Modcrypt sector"
767
- )
731
+ slice(original, h.digest_twl_start + index * h.digest_sector_size, h.digest_sector_size, "Modcrypt sector")
768
732
  );
769
733
  encrypted = !actual.equals(
770
- slice(
771
- this.Hashtable1Data,
772
- this.firstDSiHashOffset + index * 20,
773
- 20,
774
- "Modcrypt sector hash"
775
- )
734
+ slice(this.Hashtable1Data, this.firstDSiHashOffset + index * 20, 20, "Modcrypt sector hash")
776
735
  );
777
736
  }
778
737
  if (encrypted) this.cryptSections(h);
@@ -828,8 +787,7 @@ var TWL = class {
828
787
  break;
829
788
  }
830
789
  }
831
- if (found < 0)
832
- throw new Error("ARM9 overlay hashes changed but original hash table was not found");
790
+ if (found < 0) throw new Error("ARM9 overlay hashes changed but original hash table was not found");
833
791
  hashes.forEach((h, i) => h.copy(unpacked.data, found + i * 20));
834
792
  return unpacked.compressed ? compressArm9(unpacked.data, header, arm9Data.length - hdrptr) : unpacked.data;
835
793
  }
@@ -868,8 +826,7 @@ var TWL = class {
868
826
  writer.position = h.sector_hashtable_start + index++ * 20;
869
827
  writer.write(hash(b));
870
828
  };
871
- for (let p = 0; p < 16384; p += h.digest_sector_size)
872
- put(secure.subarray(p, p + h.digest_sector_size));
829
+ for (let p = 0; p < 16384; p += h.digest_sector_size) put(secure.subarray(p, p + h.digest_sector_size));
873
830
  for (let p = h.digest_ntr_start + 16384; p < h.digest_ntr_start + h.digest_ntr_size; p += h.digest_sector_size)
874
831
  put(read(p, h.digest_sector_size));
875
832
  for (let p = h.digest_twl_start; p < h.digest_twl_start + h.digest_twl_size; p += h.digest_sector_size)
@@ -936,18 +893,8 @@ var NDSFile = class _NDSFile {
936
893
  this.fatTable = new FileAllocationTable(this.original, h.FAToffset, h.FATsize);
937
894
  this.fntTable = new FileNameTable(this.fatTable, this.original, h.FNToffset, h.FNTsize);
938
895
  this.root = this.fntTable.root;
939
- this.overlay9Table = new OverlayTable(
940
- this.original,
941
- h.ARM9overlayOffset,
942
- h.ARM9overlaySize,
943
- true
944
- );
945
- this.overlay7Table = new OverlayTable(
946
- this.original,
947
- h.ARM7overlayOffset,
948
- h.ARM7overlaySize,
949
- false
950
- );
896
+ this.overlay9Table = new OverlayTable(this.original, h.ARM9overlayOffset, h.ARM9overlaySize, true);
897
+ this.overlay7Table = new OverlayTable(this.original, h.ARM7overlayOffset, h.ARM7overlaySize, false);
951
898
  const arm9Overlays = this.overlay9Table.readBasicOverlays(this.fatTable);
952
899
  this.root.folders.push({
953
900
  name: "overlay",
@@ -973,7 +920,7 @@ var NDSFile = class _NDSFile {
973
920
  if (this.paths.has(key(path))) throw new Error(`Duplicate ROM path: ${path}`);
974
921
  this.paths.set(key(path), { path, file });
975
922
  }
976
- for (const child of folder.folders) visit(child, prefix + child.name + "/");
923
+ for (const child of folder.folders) visit(child, `${prefix + child.name}/`);
977
924
  };
978
925
  visit(this.root, "");
979
926
  if (h.unitCode & 2 && h.twlInternalFlags & 1 && h.tid_high && h.tid_high !== 4294967295) {
@@ -1018,15 +965,8 @@ var NDSFile = class _NDSFile {
1018
965
  toBuffer() {
1019
966
  const fntReplacement = this.root.files.find((file) => file.name === "fnt.bin")?.replacement;
1020
967
  if (fntReplacement) {
1021
- const replacementTree = new FileNameTable(
1022
- this.fatTable,
1023
- fntReplacement,
1024
- 0,
1025
- fntReplacement.length
1026
- );
1027
- const collect = (folder) => [...folder.files.map((file) => file.id), ...folder.folders.flatMap(collect)].sort(
1028
- (a, b) => a - b
1029
- );
968
+ const replacementTree = new FileNameTable(this.fatTable, fntReplacement, 0, fntReplacement.length);
969
+ const collect = (folder) => [...folder.files.map((file) => file.id), ...folder.folders.flatMap(collect)].sort((a, b) => a - b);
1030
970
  if (collect(replacementTree.root).join(",") !== collect(this.data).join(","))
1031
971
  throw new Error("Replacement FNT must preserve existing file IDs");
1032
972
  }
@@ -1071,8 +1011,7 @@ var NDSFile = class _NDSFile {
1071
1011
  item.reserved = (item.reserved & 4278190080 | size & 16777215) >>> 0;
1072
1012
  overlayIds.add(file.id);
1073
1013
  }
1074
- if (table.entries.length !== files.length)
1075
- throw new Error("Changing overlay file count is unsupported");
1014
+ if (table.entries.length !== files.length) throw new Error("Changing overlay file count is unsupported");
1076
1015
  const encoded = table.toBuffer();
1077
1016
  if (arm92) {
1078
1017
  h.ARM9overlayOffset = writer.position;
@@ -1161,15 +1100,15 @@ export {
1161
1100
  BinaryWriter,
1162
1101
  crc16,
1163
1102
  crc32,
1103
+ Banner,
1164
1104
  aes128CtrCrypt,
1165
1105
  modcryptKey,
1166
1106
  encryptSecureArea,
1167
- UnitCode,
1168
- Header,
1169
- Banner,
1170
1107
  FileAllocationTable,
1171
1108
  FileNameTable,
1172
1109
  OverlayTable,
1110
+ UnitCode,
1111
+ Header,
1173
1112
  decompressBLZ,
1174
1113
  compressBLZ,
1175
1114
  decompressArm9,
@@ -1178,4 +1117,4 @@ export {
1178
1117
  updateHeaderSignatures,
1179
1118
  NDSFile
1180
1119
  };
1181
- //# sourceMappingURL=chunk-GFF67HX4.js.map
1120
+ //# sourceMappingURL=chunk-GLK3K2HY.js.map