nitro-patcher 0.1.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/LICENSE +674 -0
- package/README.md +22 -0
- package/dist/browser.d.ts +34 -0
- package/dist/browser.js +4616 -0
- package/dist/browser.js.LEGAL.txt +147 -0
- package/dist/browser.js.map +7 -0
- package/dist/chunk-ANN47R34.js +1 -0
- package/dist/chunk-ANN47R34.js.map +1 -0
- package/dist/chunk-GFF67HX4.js +1181 -0
- package/dist/chunk-GFF67HX4.js.map +1 -0
- package/dist/chunk-L7ZM4LLP.js +396 -0
- package/dist/chunk-L7ZM4LLP.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +76 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/nitro-helper.d.ts +315 -0
- package/dist/nitro-helper.js +44 -0
- package/dist/nitro-helper.js.map +1 -0
- package/dist/nitro-patch-helper.d.ts +46 -0
- package/dist/nitro-patch-helper.js +16 -0
- package/dist/nitro-patch-helper.js.map +1 -0
- package/package.json +86 -0
- package/src/browser/buffer.ts +1 -0
- package/src/browser.ts +60 -0
- package/src/cli.ts +76 -0
- package/src/index.ts +2 -0
- package/src/nitro/banner.ts +77 -0
- package/src/nitro/binary.ts +80 -0
- package/src/nitro/blz.ts +163 -0
- package/src/nitro/crc.ts +23 -0
- package/src/nitro/crypto.ts +178 -0
- package/src/nitro/filesystem.ts +183 -0
- package/src/nitro/header.ts +254 -0
- package/src/nitro/nds-file.ts +285 -0
- package/src/nitro/twl.ts +338 -0
- package/src/nitro-helper.ts +14 -0
- package/src/nitro-patch-helper.ts +95 -0
- package/src/patch/xdelta.ts +214 -0
- package/src/patch/zip.ts +206 -0
- package/src/runtime/browser.ts +40 -0
- package/src/runtime/node.ts +28 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createAesBlockCipher } from '#nitro-runtime';
|
|
2
|
+
|
|
3
|
+
export interface ModcryptHeader {
|
|
4
|
+
gameCode: string;
|
|
5
|
+
gameTitle: string;
|
|
6
|
+
twlInternalFlags: number;
|
|
7
|
+
appflags?: Buffer;
|
|
8
|
+
hmac_arm9i?: Buffer;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// KEY1 table carried forward from NitroHelper/Arm9Encryptor.cs (Dirbaio/ndstool).
|
|
12
|
+
const KEY1 = Buffer.from(
|
|
13
|
+
'99D5205F5744F5B96E19A4D99E6A5A94D8AEF1EB4175E23A9382D03233EE31D5CC57619A3706A21B793972F555AEF6BE5F1B69FBE59DF1E9CE2CD9A15E3205E6' +
|
|
14
|
+
'FED3FECFD462040D8BF5ECB72B6079BB1295310D6E3FDA2B8884F0F13D127E254522F1BB24061A0611ADDF288B6481342BEB332999AAF2BD9C14959D9FF7F58C' +
|
|
15
|
+
'7297A1299DD15FCF664D071ADED34A4B85C9A7A31795053A3D490ABF0A898BA24A8249DD2790F10BE9EB1C6A83764505BA817061173F4BDEAECFAB3957F23A56' +
|
|
16
|
+
'4811AD8A40E1453FFA9B0254CAA693FBEF4DFE6FA3D8879C08BAD5486A8D2DFD6E15F874BDBE528B18228A9EFB7437071B366C4A19BA4262B97991107B676596' +
|
|
17
|
+
'FE0223E8EE998C773E5C86644D6D7886A54F65E21EB2DF5A0AD07E0814B071ACBDDB831CB9D7A162CDC6637C5269C3E6BF75CE12445D2104FAFBD33C381163D4' +
|
|
18
|
+
'958541494609F2084311DC1F76C0156D1F3C6370EA87806CC3BD638BC2372137DCEE09232E376A4D7390F75030AC1C92041023914FD207AA683E4F9AC964606A' +
|
|
19
|
+
'C81421F3D62241124424CFE68A56DD0D534DE1851E8C525A9C1984C20357F16FE300BE58F64CEDD521649C1FBE55033C4ADCFFAAC9DAE05D5EBFE6DEF5D8B1F8' +
|
|
20
|
+
'FF36B3B9626795DB315F37ED4C70679990B518316C3D9999E442DAD3254213A0AED7706CB155CFC7D746D54361173D4428E93385D5D0A293AA25121FFBC50B46' +
|
|
21
|
+
'F597765645A6BE87B1946BE8B1FE3399AE1F3E6C39711D09009037E4103E7574FF8C833BB0F1B0F90105474295F1D6AC7E38E69E9574263FB4685018D04330B4' +
|
|
22
|
+
'4C4BE368BFE54DB6958B0AA074253277CFA1F72CD871135AABEAC951E80DEEEFE9937E19A71E433881162CA148E373CC29216CD35DCEA0D9617143A01513B564' +
|
|
23
|
+
'92CF2A19DCADB7A59F8665F81A9FE7FBF7FDB8136C27DB6FDF351CF78D2C5B9B12AB386406CCDE31E84E751164E3FAEAEB3454C2AD3F34EB932C7D26369D56F3' +
|
|
24
|
+
'5AE1F6B398634A9E3283E49A84607D902E130EEE934B36A285EC1638E8880602BFF0A03AEDD76A9A73E157CFF844B8DC2E2359D1DF9552719961A04BD57F6E78' +
|
|
25
|
+
'BAA9C530D34086329D320C9C37B7022FBA5498A9C41304C98DBEC8E75D97502E93D622590C27BC2292E0A7200F936F7F4C9FD3B5A62A0B7467497D1026CBD1C5' +
|
|
26
|
+
'8671E78CA09CE95BB21AF601EE8C9E5E83F21ADBE6E5EA845976D27CF68DA5493648C21652BB83A374B9070C3BFF6128E161E9E4EF6E15AA4EBAE85D0596BB32' +
|
|
27
|
+
'56B0FB72520F0EC84225657689AFF2DE1027F0014B74A79707D5265454091F820A867D30390EB3269B0B57BB360631AFFD79FCD930102B0CB3E19BD77BDC5FEF' +
|
|
28
|
+
'D2F813454D4775BD46963C7E75F33EB567C59A3BB05B296BDE805BC81505B131B6CE49DDAD84B5AE60DC67313430FE4EBD802FA6BF63392186D9357F16682205' +
|
|
29
|
+
'54E990268C076C51A43155D70907A83E2E5366C1F8F27BC4F258CFF187C5A2E7278F308758A064622318B9887CFACEC498AEAD17CC4A5BF3E948D556D30DF2C8' +
|
|
30
|
+
'92738CDBD72F56AC81F992694DC632F6E6C08D21E276806111BCDC6C93AF19699BD0BFB9319F0267A351EE8306227B0CAB494240B8D5017DCE5EF7555339C599' +
|
|
31
|
+
'46D8879FBAF764B4E39AFAA16D90681030CA8A54A79F60C319F56B0D7A5198E6984351B4D635E94FC3DF0F7BD62F5CBD3A156119F14BCBAADC6D64C9D3C61E56' +
|
|
32
|
+
'EF384C50718675CC0D0D4EE928F6065D701BAAD345CFA839AC95A62EB4E422D474A8375F487A04CCA54C40D828B428080D1C725241F07D47193A534E5884626B' +
|
|
33
|
+
'93B58A81214E0DDCB43FA2C6FCC92B40DA3804E95E5A866B0C22258568118D7C921D95554DAB8EBBDAA6E6B751B6325A0541DD052A0A5650911747CCC9E67EB5' +
|
|
34
|
+
'614ADB736751C833F5DA6E742E54C3370D6DAF08E8158A5FE25921CDA8DE0C065A776B5FDB18653EC850DE78E0B882B35D4E7232074FC13423BA96B7674EA428' +
|
|
35
|
+
'1E3462EB2D6A70E92F42C4704E5A319CF95B4728AADA716F381FB378C4926B1C9EF6359AB74D0EBFCC1829410348355D55D02BC629AF5C6074698E5E9B7CD4BD' +
|
|
36
|
+
'7B44647D3F925D69B61F004BD48335CF7E644E17AE8DD52E9A28124E2E2B49085CAEC64685AE41611E6F82D25137161F0BF659A49ACA5AAF0DD4338B2063F184' +
|
|
37
|
+
'805CCBCF08B4B9D31605BD6283319B5651989FBAB25BAAB2226B2CB5D448FA632B5F58FA61FA6409BB38E0B89D9260A80D676F0E37F50D019FC277D4FEECF173' +
|
|
38
|
+
'3039E07DF56198E42C2855045655DB2F6BECE55806B664806A2A1A4E5B0FD8C40A2E5219D962F53048BE8C7B4F389BA2C3AFC9D3C7C1624186B96121576F994F' +
|
|
39
|
+
'C1BACE7BB53B4D5E8A8B44575F135F706D5B2947DC38E2EC045565122AE81743E18EDD2AB3E294F7096E5CE6EB8AF86D89495448F52FADBFEA944BCAFC398782' +
|
|
40
|
+
'5F8A01F275F2E671D6D842DEF12D1D28A6887EA3A0471D30D9A371DF491CCB01F836B1F2F022585D456BBDA0BBB28842C78C28CE93E8906308907C893CF57DB7' +
|
|
41
|
+
'042D4F555116FD7E79E8BEC1F212D4F8B4840523A0CCD22BFDE1ABAD0DD1556C2341944D77374F05280CBF17B312676C8CC35AF741842A6DD09412272CB4ED9C' +
|
|
42
|
+
'4DEC478297D567B91B9DC055077EE58EE2A8E73E12E40E3A2A455534A2F92D5A1BAB527C83105F55D2F15A432BC6A7A4891595E8B44B9DF875E39F60785BD6E6' +
|
|
43
|
+
'0D44E62106BD472253A400AD8D43138539F7AAFC38AF7BEDFCE42B5450984CFC8580F7DF3C8022E194DADE24C6B07A3938DC0FA1A7F4F96F6318578B84412A2E' +
|
|
44
|
+
'D453F2D9000FD0DD996E19A60AD0EC5B5824ABC0CB0665EC1A1338940A67032F3FF7E377447733C61439D0E3C0A20879BB409957410B0190CDE1CC4867DBB3AF' +
|
|
45
|
+
'8874F34C828F72B1B52329C4126C19FC8E46A49CC4256587D36DBE8A93110338ED832BF346A493EA3B53851DCED4F1088327EDFC9B1A18BCF98BAEDC24AB5038' +
|
|
46
|
+
'E9724B1022177B465DAB5964F340AEF8BBE5C8F926034E557DEBEBFEF739E6E00A11BE2E28FF98EDC0C9425642C3FD00F6AF87A25B013F329247959A72A5323D' +
|
|
47
|
+
'AE6BD09B07D24992E3784AFAA1067DF241CF77740414B20C86846416D5BB51A1E56FF1D1F2E2F75F58204DB857C7CFDDC5D8BE763DF65F7EE72A8B88241B383F' +
|
|
48
|
+
'0E412377F5F04BD40C1FFAA40B805FCF45F6E0DA2F345953FB203C52625E35B562FE8B6063E3865A151A6ED14745BC32B4EB6738ABE46E333AB5EDA3AD67E04E' +
|
|
49
|
+
'4195EE626271261D31EF6230AFD782ACC2DC0504F59707BF11592307C06402E897E53EAF18AC59A68B4A33901C6E7C9C207E4C3C3E6164BBC56B7C7E3E9FC54C' +
|
|
50
|
+
'9FEA73F5D789C04CF4FBF42DEC141B51D5C112C810DF0B4A8B9CBC93456A3E3E7DC1A9BACDC1B407E4E1688643B26D38F3FB0C5C663771DE56EF6EA0104065A7' +
|
|
51
|
+
'98F7D0BE0EC83736EC10CA7C9CAB841E051776021C4F52AA5FC1C6A056B9D80484444DA759D8DE60E6380E058F03E13B6D8104336F300BCE69052133FB26BB89' +
|
|
52
|
+
'7DB6AE877E5107E0ACF7960A6BF9C45C1DE44447B85EFAE3788455424B485EF77D4735861D2B430503EC8AB81E063C760C481A43A7B78AED1E13C643EE10EFDB' +
|
|
53
|
+
'ECFB3C83B29544EFD854514E2D11441DFB36591E7A34C1C3CA570061EA67A5169B55D055E17FD936D24076AEDC01CEB07A83D5CB2098EC6BC1729234F3825737' +
|
|
54
|
+
'628A32360C9043AEAE5C9B788E136502FD6871C1FEB031A02482B0C3B17969A7F5D2EBD082C032DC9EC7263C6D8D98C1BB22D4D00F33EC3EB9CCE1DC6A4C7736' +
|
|
55
|
+
'141CF9BF819F285F71853229907548C4B34ACED8448F142FFD4057EFAA0875D946D1D66E32551FC318FE841FFC84D5FF715E1B48C386950E280827D33883717B' +
|
|
56
|
+
'4C8063549A56B0ACCF80CA3109EFFEF3BEAF247EA6FE533FC28D4A3368D122A666AD7BEADEB643B0A1259500A33F7546141144ECD795BC92F04FA91653629760' +
|
|
57
|
+
'2A0F41F17124BEEE947F08CD6093B3855B07003FD80F28839AD1699FD1DA2EC39001A2B96B4E2A669DDAAEA6EA2AD3682F0C0C9CD28C4AEDE29E57659D0987A3' +
|
|
58
|
+
'B4C4325DC9D4322BB1E0711E644DE69071E31E40ED7DF3840EEDC87876AEC0712772BB05EA0264FBF3486BB542933FED9F1353D2F7FE2AEC1D4725DB3C9186C6' +
|
|
59
|
+
'8EF011FD237436F7A4F59E7A7E535044D447CAD3EB386DE6D971947F4AC6694B11F452EA22FE8AB036678B59E8E6802AEB650413EEECDC9E5FB1EC056A59E69F' +
|
|
60
|
+
'5E596B89BFF71ACA44F95B6A718503E42962E0706F41C4CFB2B1CCE37EA607A887E77F8493DB524B6CEC7EDDD4244810699F046074E64818F3E42CB94F2E507A' +
|
|
61
|
+
'DFD454692B8BA7F3CEFF1FF33E26013917958489B0F04C4B82919FC44BAC9DA574AF1725C9CA32D3BC898A8489CC0DAE7CA2DB9C6A7891EEEA765D4E8760F569' +
|
|
62
|
+
'1567D402CFAF483607EABF6F662D068FC49AFEF9F6908775B8F7AD0F76105A3D59B02EB3C7352CCC70562BCBE33796C52F461B8A2246C788A726329861DF8622' +
|
|
63
|
+
'8AF41C2F87A109AACCA9AED3BD00451C9A5487865287EFFF1E8FA18FC1895C351BDA2D3A2C16B2C2F156E278C16B6397C5568FC9327F2CAAAFA6A8AC20912288' +
|
|
64
|
+
'DEE4608BF94B42251AE37F9C2C19893A7E05D436CC6958C2C1328B2F9085EB7A3950A5A12792C566B0204F587E5583432B45E29CE4D812902C168356167903B3' +
|
|
65
|
+
'AD2D61181A131F37E2E19C737B80D5FD2D5187FC7BAAD71F2C7A8EAFF48DBBCD95117C720BEE6FE2B9AFDE3783DE8C8D620567B796C68D56B60DD762BAD64636' +
|
|
66
|
+
'BD8EC8E6EA2A6C1014FF6B5BFA823C46B1304346518A7D9B923E83795B555DB26C5ECE90628E5398C90D6DE52D57CDC58157BAE1E8B88F72E54F13DCEA9D7115' +
|
|
67
|
+
'10B21188D509D47F5B657F2C3B384C1168508DFB9EB059BF9480894AC51A18128953D14A1029E88C1CECB6EA46C7178B251531A8A26B43B19DE2DB0B879BB011' +
|
|
68
|
+
'040E71D2297789820A66417F1D0B48FF72BB24FDC248A19BFE7B7FCE88DB86D9853B1CB0DCA83307BF512EE30E9A00971E06C097439DD8B645C486675F00F888' +
|
|
69
|
+
'9AA4529EC7AA8A8375ECC518AECEC32F1A2BF918FFAE1AF5530BB53351A7FDE8A8E1A264B622174380CC0AD8AE3BBA40D7D9924A89DF0410EE9B182B6A77698A' +
|
|
70
|
+
'68F4F9B9A221156EE61E3B0362309B60417E259B9E8FC5521008F8C269A1211188375E793566FF1042186EED97B66B1C4E36E56D7DB4E4BF20B9E0053A69D5B8' +
|
|
71
|
+
'E3D5DCE0B9AC533E07A457AD77FF4818762AAC492A8E47756D9F676330358C390539D56F643A5BADCA0BBB82529945B193363699AF13204436D8024409399285' +
|
|
72
|
+
'FF4A4A9787A663D7C7B5B524ED0FB46F0C585214D9A67BD379BC3858A1BD3B8406D81A06FD6BA8EA4B69280437AD8299FB0E1B85BDA85D73CDDC58750ABE636C' +
|
|
73
|
+
'48E74CE4302B0460B915D8DA8681758F96D48D1C5D70857C1C677BD50867A6CE4B0A6670B7E563D45B8A82EA1067CAE2F4EF17852F2A5F8A9782F86AD63410EA' +
|
|
74
|
+
'EBC95C3CE149F846EBDEBDF6A992F1AAA6A018B03AD30F1FF36FFF31454344D3509AF7880996C1CE76CCF22C2CBAAD82778F1884C0D2079C3690834E0BA54F43' +
|
|
75
|
+
'3E04AB784FD6FB09012490DA6F3C3A610D7F694AEB2B3002B4DBE084A9ECD735BF377D8558CEA94EE480C7A8D3306748EB29AF2F746AB4A73F0F3F92AFF3CAAC' +
|
|
76
|
+
'AF4BD994C043CA810D2F48A1B027D5D2EF4B0585A3DE4D93303CF0BB4A8F30274CEBE33E64ED9A2F3BF182F0BAF4CF7F40CBB0E17FBCAA57D3C974F2FA430D22' +
|
|
77
|
+
'D0F4774E93D785701F99BFB6DE35F130A75E71F06B012D7B64F033530A3988F36B3AA66B35D22F43CD02FDB5E9BC5BAAD8A4197E0E5D94819E6F77ADD60E7493' +
|
|
78
|
+
'96E7C4185FADF519',
|
|
79
|
+
'hex',
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
export const aes128CtrCrypt = (key: Buffer, counter: Buffer, data: Buffer): Buffer => {
|
|
83
|
+
if (key.length !== 16 || counter.length !== 16)
|
|
84
|
+
throw new RangeError('AES key and counter must have 16 bytes');
|
|
85
|
+
const ctr = Buffer.from(counter).reverse();
|
|
86
|
+
const aes = createAesBlockCipher(Buffer.from(key).reverse());
|
|
87
|
+
const output = Buffer.alloc(data.length);
|
|
88
|
+
for (let p = 0; p < data.length; p += 16) {
|
|
89
|
+
const mask = aes(ctr);
|
|
90
|
+
for (let j = 0; j < 16 && p + j < data.length; j++) output[p + j] = data[p + j] ^ mask[15 - j];
|
|
91
|
+
for (let j = 15; j >= 0; j--) {
|
|
92
|
+
ctr[j] = (ctr[j] + 1) & 255;
|
|
93
|
+
if (ctr[j]) break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return output;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const modcryptKey = (header: ModcryptHeader): Buffer => {
|
|
100
|
+
if (typeof header.gameCode !== 'string' || header.gameCode.length !== 4)
|
|
101
|
+
throw new Error('Expected four-character game code');
|
|
102
|
+
if (header.twlInternalFlags & 4 || (header.appflags?.[3] ?? 0) & 0x80) {
|
|
103
|
+
const key = Buffer.alloc(16);
|
|
104
|
+
key.write(header.gameTitle, 0, 12, 'ascii');
|
|
105
|
+
key.write(header.gameCode, 12, 4, 'ascii');
|
|
106
|
+
return key;
|
|
107
|
+
}
|
|
108
|
+
if (!header.hmac_arm9i || header.hmac_arm9i.length < 16)
|
|
109
|
+
throw new Error('Missing ARM9i HMAC for modcrypt key');
|
|
110
|
+
const x = Buffer.from(
|
|
111
|
+
'Nintendo' + header.gameCode + [...header.gameCode].reverse().join(''),
|
|
112
|
+
'ascii',
|
|
113
|
+
);
|
|
114
|
+
for (let i = 0; i < 16; i++) x[i] ^= header.hmac_arm9i[i];
|
|
115
|
+
const mask = (1n << 128n) - 1n;
|
|
116
|
+
let v = x.readBigUInt64LE(0) | (x.readBigUInt64LE(8) << 64n);
|
|
117
|
+
v = (v + 0xfffefb4e295902582a680f5f1a4f3e79n) & mask;
|
|
118
|
+
v = ((v << 42n) | (v >> 86n)) & mask;
|
|
119
|
+
const out = Buffer.alloc(16);
|
|
120
|
+
out.writeBigUInt64LE(v & ((1n << 64n) - 1n));
|
|
121
|
+
out.writeBigUInt64LE(v >> 64n, 8);
|
|
122
|
+
return out;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const encryptSecureArea = (gameCode: string, input: Buffer): Buffer => {
|
|
126
|
+
if (typeof gameCode !== 'string' || gameCode.length !== 4)
|
|
127
|
+
throw new Error('Expected four-character game code');
|
|
128
|
+
if (input.length < 0x800) throw new RangeError('Secure area must contain at least 0x800 bytes');
|
|
129
|
+
const output = Buffer.from(input);
|
|
130
|
+
let magic = new Uint32Array(0x412);
|
|
131
|
+
let args: [number, number, number] = [0, 0, 0];
|
|
132
|
+
function encrypt(a: number, b: number): [number, number] {
|
|
133
|
+
for (let i = 0; i < 16; i++) {
|
|
134
|
+
const c = (magic[i] ^ a) >>> 0;
|
|
135
|
+
const sum = (magic[18 + (c >>> 24)] + magic[274 + ((c >>> 16) & 255)]) >>> 0;
|
|
136
|
+
const f = (magic[786 + (c & 255)] + (magic[530 + ((c >>> 8) & 255)] ^ sum)) >>> 0;
|
|
137
|
+
a = (b ^ f) >>> 0;
|
|
138
|
+
b = c;
|
|
139
|
+
}
|
|
140
|
+
return [(b ^ magic[17]) >>> 0, (a ^ magic[16]) >>> 0];
|
|
141
|
+
}
|
|
142
|
+
function init2() {
|
|
143
|
+
[args[2], args[1]] = encrypt(args[2], args[1]);
|
|
144
|
+
[args[1], args[0]] = encrypt(args[1], args[0]);
|
|
145
|
+
const key = Buffer.alloc(12);
|
|
146
|
+
args.forEach((v, i) => key.writeUInt32LE(v, i * 4));
|
|
147
|
+
for (let j = 0; j < 18; j++) magic[j] ^= key.readUInt32BE((j * 4) & 7);
|
|
148
|
+
let a = 0,
|
|
149
|
+
b = 0;
|
|
150
|
+
for (let i = 0; i < 0x412; i += 2) {
|
|
151
|
+
[a, b] = encrypt(a, b);
|
|
152
|
+
magic[i] = a;
|
|
153
|
+
magic[i + 1] = b;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function init1() {
|
|
157
|
+
magic = Uint32Array.from({ length: 0x412 }, (_, i) => KEY1.readUInt32LE(i * 4));
|
|
158
|
+
const code = Buffer.from(gameCode, 'ascii').readUInt32LE();
|
|
159
|
+
args = [code, code >>> 1, (code << 1) >>> 0];
|
|
160
|
+
init2();
|
|
161
|
+
init2();
|
|
162
|
+
}
|
|
163
|
+
init1();
|
|
164
|
+
args[1] = (args[1] << 1) >>> 0;
|
|
165
|
+
args[2] >>>= 1;
|
|
166
|
+
init2();
|
|
167
|
+
for (let p = 8; p < 0x800; p += 8) {
|
|
168
|
+
const [b, a] = encrypt(output.readUInt32LE(p + 4), output.readUInt32LE(p));
|
|
169
|
+
output.writeUInt32LE(a, p);
|
|
170
|
+
output.writeUInt32LE(b, p + 4);
|
|
171
|
+
}
|
|
172
|
+
let [b, a] = encrypt(0x6a624f79, 0x72636e65);
|
|
173
|
+
init1();
|
|
174
|
+
[b, a] = encrypt(b, a);
|
|
175
|
+
output.writeUInt32LE(a, 0);
|
|
176
|
+
output.writeUInt32LE(b, 4);
|
|
177
|
+
return output;
|
|
178
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Based on NitroHelper FNT/FAT/OverlayTable, GPL-3.0.
|
|
2
|
+
import { bytes, range } from './binary';
|
|
3
|
+
|
|
4
|
+
/** A file in the original ROM, optionally replaced with new bytes. */
|
|
5
|
+
export interface NitroFile {
|
|
6
|
+
name: string;
|
|
7
|
+
id: number;
|
|
8
|
+
offset: number;
|
|
9
|
+
size: number;
|
|
10
|
+
replacement?: Buffer;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NitroFolder {
|
|
14
|
+
name: string;
|
|
15
|
+
id: number;
|
|
16
|
+
files: NitroFile[];
|
|
17
|
+
folders: NitroFolder[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FatEntry {
|
|
21
|
+
id: number;
|
|
22
|
+
offset: number;
|
|
23
|
+
size: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class FileAllocationTable {
|
|
27
|
+
readonly entries: FatEntry[];
|
|
28
|
+
readonly sortedIDs: number[];
|
|
29
|
+
|
|
30
|
+
constructor(input: Uint8Array, offset: number, size: number) {
|
|
31
|
+
const data = bytes(input);
|
|
32
|
+
const table = range(data, offset, size, 'FAT');
|
|
33
|
+
if (size % 8 || size / 8 > 65535) throw new Error('Invalid FAT entry count');
|
|
34
|
+
this.entries = [];
|
|
35
|
+
for (let p = 0; p < size; p += 8) {
|
|
36
|
+
const start = table.readUInt32LE(p),
|
|
37
|
+
end = table.readUInt32LE(p + 4);
|
|
38
|
+
range(data, start, end - start, `FAT entry ${p / 8}`);
|
|
39
|
+
this.entries.push({ id: p / 8, offset: start, size: end - start });
|
|
40
|
+
}
|
|
41
|
+
this.sortedIDs = [...this.entries]
|
|
42
|
+
.sort((a, b) => a.offset - b.offset || a.id - b.id)
|
|
43
|
+
.map((entry) => entry.id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class FileNameTable {
|
|
48
|
+
readonly root: NitroFolder;
|
|
49
|
+
|
|
50
|
+
constructor(fat: FileAllocationTable, input: Uint8Array, offset: number, size: number) {
|
|
51
|
+
const table = range(bytes(input), offset, size, 'FNT');
|
|
52
|
+
range(table, 0, 8, 'FNT root directory');
|
|
53
|
+
const count = table.readUInt16LE(6);
|
|
54
|
+
if (count < 1 || count > 4096) throw new Error('Invalid FNT directory count');
|
|
55
|
+
range(table, 0, count * 8, 'FNT directory table');
|
|
56
|
+
const decoder = new TextDecoder('shift_jis');
|
|
57
|
+
const visited = new Set<number>();
|
|
58
|
+
const fileIds = new Set<number>();
|
|
59
|
+
const parse = (index: number, name: string, depth: number): NitroFolder => {
|
|
60
|
+
if (depth > 256 || index >= count || visited.has(index))
|
|
61
|
+
throw new Error('Invalid FNT directory tree or cycle');
|
|
62
|
+
visited.add(index);
|
|
63
|
+
let cursor = table.readUInt32LE(index * 8);
|
|
64
|
+
let fileId = table.readUInt16LE(index * 8 + 4);
|
|
65
|
+
if (cursor < count * 8) throw new Error('FNT directory names overlap directory table');
|
|
66
|
+
const folder: NitroFolder = {
|
|
67
|
+
name,
|
|
68
|
+
id: index === 0 ? 0xffff : 0xf000 + index,
|
|
69
|
+
files: [],
|
|
70
|
+
folders: [],
|
|
71
|
+
};
|
|
72
|
+
const names = new Set<string>();
|
|
73
|
+
while (true) {
|
|
74
|
+
const tag = range(table, cursor++, 1, 'FNT name')[0];
|
|
75
|
+
if (!tag) break;
|
|
76
|
+
const length = tag & 0x7f;
|
|
77
|
+
if (!length) throw new Error('Empty FNT directory name');
|
|
78
|
+
const filename = decoder.decode(range(table, cursor, length, 'FNT filename'));
|
|
79
|
+
cursor += length;
|
|
80
|
+
if (
|
|
81
|
+
filename.includes('/') ||
|
|
82
|
+
filename.includes('\\') ||
|
|
83
|
+
filename.includes('\0') ||
|
|
84
|
+
filename === '.' ||
|
|
85
|
+
filename === '..'
|
|
86
|
+
)
|
|
87
|
+
throw new Error('Invalid FNT path component');
|
|
88
|
+
const normalized = filename.toLowerCase();
|
|
89
|
+
if (names.has(normalized)) throw new Error(`Duplicate FNT path: ${filename}`);
|
|
90
|
+
names.add(normalized);
|
|
91
|
+
if (tag & 0x80) {
|
|
92
|
+
const id = range(table, cursor, 2, 'FNT directory id').readUInt16LE();
|
|
93
|
+
cursor += 2;
|
|
94
|
+
if (id < 0xf000) throw new Error('Invalid FNT directory id');
|
|
95
|
+
folder.folders.push(parse(id & 0xfff, filename, depth + 1));
|
|
96
|
+
} else {
|
|
97
|
+
const entry = fat.entries[fileId++];
|
|
98
|
+
if (!entry) throw new Error('FNT file id is outside FAT');
|
|
99
|
+
if (fileIds.has(entry.id)) throw new Error(`Duplicate FNT file id: ${entry.id}`);
|
|
100
|
+
fileIds.add(entry.id);
|
|
101
|
+
folder.files.push({ name: filename, ...entry });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return folder;
|
|
105
|
+
};
|
|
106
|
+
this.root = { name: 'root', id: 0xffff, files: [], folders: [parse(0, 'data', 0)] };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static findFile(id: number, folder: NitroFolder): NitroFile | undefined {
|
|
110
|
+
for (const file of folder.files) if (file.id === id) return file;
|
|
111
|
+
for (const subfolder of folder.folders) {
|
|
112
|
+
const file = FileNameTable.findFile(id, subfolder);
|
|
113
|
+
if (file) return file;
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface OverlayItem {
|
|
120
|
+
overlayId: number;
|
|
121
|
+
ramAddress: number;
|
|
122
|
+
ramSize: number;
|
|
123
|
+
bssSize: number;
|
|
124
|
+
staticInitialiserStartAddress: number;
|
|
125
|
+
staticInitialiserEndAddress: number;
|
|
126
|
+
fileId: number;
|
|
127
|
+
reserved: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const overlayFields = [
|
|
131
|
+
'overlayId',
|
|
132
|
+
'ramAddress',
|
|
133
|
+
'ramSize',
|
|
134
|
+
'bssSize',
|
|
135
|
+
'staticInitialiserStartAddress',
|
|
136
|
+
'staticInitialiserEndAddress',
|
|
137
|
+
'fileId',
|
|
138
|
+
'reserved',
|
|
139
|
+
] as const;
|
|
140
|
+
|
|
141
|
+
export class OverlayTable {
|
|
142
|
+
readonly entries: OverlayItem[] = [];
|
|
143
|
+
constructor(
|
|
144
|
+
input: Uint8Array,
|
|
145
|
+
offset: number,
|
|
146
|
+
size: number,
|
|
147
|
+
readonly isArm9: boolean,
|
|
148
|
+
) {
|
|
149
|
+
if (!size) return;
|
|
150
|
+
const table = range(bytes(input), offset, size, 'overlay table');
|
|
151
|
+
if (size % 32) throw new Error('Invalid overlay table size');
|
|
152
|
+
for (let p = 0; p < size; p += 32) {
|
|
153
|
+
if (table.readUInt32LE(p) === 0xffffffff) break;
|
|
154
|
+
this.entries.push({
|
|
155
|
+
overlayId: table.readUInt32LE(p),
|
|
156
|
+
ramAddress: table.readUInt32LE(p + 4),
|
|
157
|
+
ramSize: table.readUInt32LE(p + 8),
|
|
158
|
+
bssSize: table.readUInt32LE(p + 12),
|
|
159
|
+
staticInitialiserStartAddress: table.readUInt32LE(p + 16),
|
|
160
|
+
staticInitialiserEndAddress: table.readUInt32LE(p + 20),
|
|
161
|
+
fileId: table.readUInt32LE(p + 24),
|
|
162
|
+
reserved: table.readUInt32LE(p + 28),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
readBasicOverlays(fat: FileAllocationTable): NitroFile[] {
|
|
167
|
+
return this.entries.map((item) => {
|
|
168
|
+
const file = fat.entries[item.fileId];
|
|
169
|
+
if (!file) throw new Error('Overlay file id is outside FAT');
|
|
170
|
+
return {
|
|
171
|
+
...file,
|
|
172
|
+
name: `overlay${this.isArm9 ? '' : '7'}_${String(item.overlayId).padStart(4, '0')}.bin`,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
toBuffer(): Buffer {
|
|
177
|
+
const data = Buffer.alloc(this.entries.length * 32);
|
|
178
|
+
this.entries.forEach((entry, i) =>
|
|
179
|
+
overlayFields.forEach((field, j) => data.writeUInt32LE(entry[field], i * 32 + j * 4)),
|
|
180
|
+
);
|
|
181
|
+
return data;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// Port of NitroHelper/Header.cs. Copyright Xzonn and upstream contributors, GPL-3.0.
|
|
2
|
+
import { bytes, range } from './binary';
|
|
3
|
+
import { crc16 } from './crc';
|
|
4
|
+
import { encryptSecureArea } from './crypto';
|
|
5
|
+
|
|
6
|
+
export const UnitCode = Object.freeze({ NDS: 0, NDS_DSi: 2, DSi: 3 });
|
|
7
|
+
|
|
8
|
+
export class Header {
|
|
9
|
+
raw: Buffer;
|
|
10
|
+
trimmedRom: boolean;
|
|
11
|
+
doublePadding: boolean;
|
|
12
|
+
nitrocode: boolean;
|
|
13
|
+
decrypted: boolean;
|
|
14
|
+
headerCRC: boolean;
|
|
15
|
+
logoCRC: boolean;
|
|
16
|
+
secureCRC: boolean;
|
|
17
|
+
dlp_signature: Buffer;
|
|
18
|
+
declare ARM9romOffset: number;
|
|
19
|
+
declare ARM9entryAddress: number;
|
|
20
|
+
declare ARM9ramAddress: number;
|
|
21
|
+
declare ARM9size: number;
|
|
22
|
+
declare ARM7romOffset: number;
|
|
23
|
+
declare ARM7entryAddress: number;
|
|
24
|
+
declare ARM7ramAddress: number;
|
|
25
|
+
declare ARM7size: number;
|
|
26
|
+
declare FNToffset: number;
|
|
27
|
+
declare FNTsize: number;
|
|
28
|
+
declare FAToffset: number;
|
|
29
|
+
declare FATsize: number;
|
|
30
|
+
declare ARM9overlayOffset: number;
|
|
31
|
+
declare ARM9overlaySize: number;
|
|
32
|
+
declare ARM7overlayOffset: number;
|
|
33
|
+
declare ARM7overlaySize: number;
|
|
34
|
+
declare flagsRead: number;
|
|
35
|
+
declare flagsInit: number;
|
|
36
|
+
declare bannerOffset: number;
|
|
37
|
+
declare dsi9_rom_offset: number;
|
|
38
|
+
declare offset_0x1C4: number;
|
|
39
|
+
declare dsi9_ram_address: number;
|
|
40
|
+
declare dsi9_size: number;
|
|
41
|
+
declare dsi7_rom_offset: number;
|
|
42
|
+
declare offset_0x1D4: number;
|
|
43
|
+
declare dsi7_ram_address: number;
|
|
44
|
+
declare dsi7_size: number;
|
|
45
|
+
declare digest_ntr_start: number;
|
|
46
|
+
declare digest_ntr_size: number;
|
|
47
|
+
declare digest_twl_start: number;
|
|
48
|
+
declare digest_twl_size: number;
|
|
49
|
+
declare sector_hashtable_start: number;
|
|
50
|
+
declare sector_hashtable_size: number;
|
|
51
|
+
declare block_hashtable_start: number;
|
|
52
|
+
declare block_hashtable_size: number;
|
|
53
|
+
declare digest_sector_size: number;
|
|
54
|
+
declare digest_block_sectorcount: number;
|
|
55
|
+
declare banner_size: number;
|
|
56
|
+
declare offset_0x20C: number;
|
|
57
|
+
declare total_rom_size: number;
|
|
58
|
+
declare offset_0x214: number;
|
|
59
|
+
declare offset_0x218: number;
|
|
60
|
+
declare offset_0x21C: number;
|
|
61
|
+
declare modcrypt1_start: number;
|
|
62
|
+
declare modcrypt1_size: number;
|
|
63
|
+
declare modcrypt2_start: number;
|
|
64
|
+
declare modcrypt2_size: number;
|
|
65
|
+
declare tid_low: number;
|
|
66
|
+
declare tid_high: number;
|
|
67
|
+
declare public_sav_size: number;
|
|
68
|
+
declare private_sav_size: number;
|
|
69
|
+
declare unitCode: number;
|
|
70
|
+
declare encryptionSeed: number;
|
|
71
|
+
declare twlInternalFlags: number;
|
|
72
|
+
declare permitsFlags: number;
|
|
73
|
+
declare ROMversion: number;
|
|
74
|
+
declare internalFlags: number;
|
|
75
|
+
declare secureCRC16: number;
|
|
76
|
+
declare ROMtimeout: number;
|
|
77
|
+
declare ARM9autoload: number;
|
|
78
|
+
declare ARM7autoload: number;
|
|
79
|
+
declare ROMsize: number;
|
|
80
|
+
declare headerSize: number;
|
|
81
|
+
declare logoCRC16: number;
|
|
82
|
+
declare headerCRC16: number;
|
|
83
|
+
declare debug_romOffset: number;
|
|
84
|
+
declare debug_size: number;
|
|
85
|
+
declare debug_ramAddress: number;
|
|
86
|
+
declare reserved3: number;
|
|
87
|
+
declare mbk9_wramcnt_setting: number;
|
|
88
|
+
declare region_flags: number;
|
|
89
|
+
declare access_control: number;
|
|
90
|
+
declare scfg_ext_mask: number;
|
|
91
|
+
declare gameTitle: string;
|
|
92
|
+
declare gameCode: string;
|
|
93
|
+
declare makerCode: string;
|
|
94
|
+
declare reserved: Buffer;
|
|
95
|
+
declare reserved2: Buffer;
|
|
96
|
+
declare logo: Buffer;
|
|
97
|
+
declare appflags: Buffer;
|
|
98
|
+
declare hmac_arm9: Buffer;
|
|
99
|
+
declare hmac_arm7: Buffer;
|
|
100
|
+
declare hmac_digest_master: Buffer;
|
|
101
|
+
declare hmac_icon_title: Buffer;
|
|
102
|
+
declare hmac_arm9i: Buffer;
|
|
103
|
+
declare hmac_arm7i: Buffer;
|
|
104
|
+
declare hmac_arm9_no_secure: Buffer;
|
|
105
|
+
declare rsa_signature: Buffer;
|
|
106
|
+
declare secureDisable: bigint;
|
|
107
|
+
|
|
108
|
+
constructor(input: Uint8Array) {
|
|
109
|
+
const data = bytes(input, 'header');
|
|
110
|
+
range(data, 0, 0x170, 'header');
|
|
111
|
+
const size = data.readUInt32LE(0x84);
|
|
112
|
+
if (size < 0x170 || size > data.length) throw new RangeError(`Invalid header size: ${size}`);
|
|
113
|
+
this.raw = Buffer.from(data.subarray(0, size));
|
|
114
|
+
if (this.unitCode & 2 && size > 0x200) range(this.raw, 0, 0x1000, 'DSi header');
|
|
115
|
+
this.trimmedRom = this.total_rom_size
|
|
116
|
+
? this.total_rom_size >= data.length
|
|
117
|
+
: data.length !== this.size;
|
|
118
|
+
this.doublePadding =
|
|
119
|
+
(this.ARM9size % 0x400 < 0x200 &&
|
|
120
|
+
this.ARM7romOffset % 0x400 === 0 &&
|
|
121
|
+
this.ARM9overlayOffset % 0x400 === 0) ||
|
|
122
|
+
(this.ARM7size % 0x400 < 0x200 &&
|
|
123
|
+
this.FNToffset % 0x400 === 0 &&
|
|
124
|
+
this.ARM7overlayOffset % 0x400 === 0) ||
|
|
125
|
+
(this.FNTsize % 0x400 < 0x200 && this.FAToffset % 0x400 === 0) ||
|
|
126
|
+
(this.FATsize % 0x400 < 0x200 && this.bannerOffset % 0x400 === 0);
|
|
127
|
+
const nitroOffset = this.ARM9romOffset + this.ARM9size;
|
|
128
|
+
this.nitrocode =
|
|
129
|
+
nitroOffset >= size &&
|
|
130
|
+
nitroOffset + 4 <= data.length &&
|
|
131
|
+
data.readUInt32LE(nitroOffset) === 0xdec00621;
|
|
132
|
+
this.decrypted = data.length >= 0x4008 && data.readBigUInt64LE(0x4000) === 0xe7ffdeffe7ffdeffn;
|
|
133
|
+
this.headerCRC = crc16(this.raw.subarray(0, 0x15e)) === this.headerCRC16;
|
|
134
|
+
this.logoCRC = crc16(this.logo) === this.logoCRC16;
|
|
135
|
+
const secure = data.subarray(0x4000, 0x8000);
|
|
136
|
+
this.secureCRC =
|
|
137
|
+
secure.length > 0 &&
|
|
138
|
+
crc16(this.decrypted ? encryptSecureArea(this.gameCode, secure) : secure) ===
|
|
139
|
+
this.secureCRC16;
|
|
140
|
+
this.dlp_signature = Buffer.alloc(0);
|
|
141
|
+
if (
|
|
142
|
+
this.ROMsize >= size &&
|
|
143
|
+
this.ROMsize + 0x88 <= data.length &&
|
|
144
|
+
data.readUInt32LE(this.ROMsize) === 0x00016361
|
|
145
|
+
) {
|
|
146
|
+
this.dlp_signature = Buffer.from(data.subarray(this.ROMsize, this.ROMsize + 0x88));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get size() {
|
|
151
|
+
return 2 ** (17 + this.raw[0x14]);
|
|
152
|
+
}
|
|
153
|
+
set size(value: number) {
|
|
154
|
+
const power = Math.log2(value);
|
|
155
|
+
if (!Number.isInteger(power) || power < 17 || power > 32)
|
|
156
|
+
throw new RangeError('ROM capacity must be a power of two from 128 KiB to 4 GiB');
|
|
157
|
+
this.raw[0x14] = power - 17;
|
|
158
|
+
}
|
|
159
|
+
toBuffer() {
|
|
160
|
+
const data = Buffer.from(this.raw);
|
|
161
|
+
data.writeUInt16LE(crc16(data.subarray(0xc0, 0x15c)), 0x15c);
|
|
162
|
+
data.writeUInt16LE(crc16(data.subarray(0, 0x15e)), 0x15e);
|
|
163
|
+
return data;
|
|
164
|
+
}
|
|
165
|
+
toString() {
|
|
166
|
+
return `NDS header: ${this.gameTitle.replace(/\0+$/, '')} (${this.gameCode})`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function field(
|
|
171
|
+
name: string,
|
|
172
|
+
offset: number,
|
|
173
|
+
length: number,
|
|
174
|
+
type: 'buffer' | 'string' | 'number' = 'buffer',
|
|
175
|
+
dsi = false,
|
|
176
|
+
) {
|
|
177
|
+
Object.defineProperty(Header.prototype, name, {
|
|
178
|
+
enumerable: true,
|
|
179
|
+
get(this: Header) {
|
|
180
|
+
if (dsi && (!(this.unitCode & 2) || this.raw.length <= 0x200))
|
|
181
|
+
return type === 'buffer' ? Buffer.alloc(length) : 0;
|
|
182
|
+
const data = range(this.raw, offset, length, `header ${name}`);
|
|
183
|
+
if (type === 'string') return data.toString('latin1');
|
|
184
|
+
if (type === 'buffer') return data;
|
|
185
|
+
return length === 8 ? data.readBigUInt64LE() : data.readUIntLE(0, length);
|
|
186
|
+
},
|
|
187
|
+
set(this: Header, value: string | number | bigint | Uint8Array) {
|
|
188
|
+
const target = range(this.raw, offset, length, `header ${name}`);
|
|
189
|
+
if (type === 'string') {
|
|
190
|
+
if (typeof value !== 'string' || Buffer.byteLength(value, 'latin1') > length)
|
|
191
|
+
throw new TypeError(`Invalid ${name}`);
|
|
192
|
+
target.fill(0);
|
|
193
|
+
target.write(value, 'latin1');
|
|
194
|
+
} else if (type === 'buffer') {
|
|
195
|
+
if (!(value instanceof Uint8Array)) throw new TypeError(`Invalid ${name}`);
|
|
196
|
+
const source = bytes(value, name);
|
|
197
|
+
if (source.length !== length) throw new RangeError(`${name} must contain ${length} bytes`);
|
|
198
|
+
source.copy(target);
|
|
199
|
+
} else if (length === 8) {
|
|
200
|
+
if (typeof value !== 'bigint') throw new TypeError(`Invalid ${name}`);
|
|
201
|
+
target.writeBigUInt64LE(value);
|
|
202
|
+
} else {
|
|
203
|
+
if (typeof value !== 'number') throw new TypeError(`Invalid ${name}`);
|
|
204
|
+
target.writeUIntLE(value, 0, length);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
field('gameTitle', 0, 12, 'string');
|
|
210
|
+
field('gameCode', 12, 4, 'string');
|
|
211
|
+
field('makerCode', 16, 2, 'string');
|
|
212
|
+
for (const [name, offset] of Object.entries({
|
|
213
|
+
unitCode: 0x12,
|
|
214
|
+
encryptionSeed: 0x13,
|
|
215
|
+
twlInternalFlags: 0x1c,
|
|
216
|
+
permitsFlags: 0x1d,
|
|
217
|
+
ROMversion: 0x1e,
|
|
218
|
+
internalFlags: 0x1f,
|
|
219
|
+
}))
|
|
220
|
+
field(name, offset, 1, 'number');
|
|
221
|
+
field('reserved', 0x15, 7);
|
|
222
|
+
const ntr32 =
|
|
223
|
+
'ARM9romOffset ARM9entryAddress ARM9ramAddress ARM9size ARM7romOffset ARM7entryAddress ARM7ramAddress ARM7size FNToffset FNTsize FAToffset FATsize ARM9overlayOffset ARM9overlaySize ARM7overlayOffset ARM7overlaySize flagsRead flagsInit bannerOffset'.split(
|
|
224
|
+
' ',
|
|
225
|
+
);
|
|
226
|
+
ntr32.forEach((name, i) => field(name, 0x20 + i * 4, 4, 'number'));
|
|
227
|
+
field('secureCRC16', 0x6c, 2, 'number');
|
|
228
|
+
field('ROMtimeout', 0x6e, 2, 'number');
|
|
229
|
+
field('ARM9autoload', 0x70, 4, 'number');
|
|
230
|
+
field('ARM7autoload', 0x74, 4, 'number');
|
|
231
|
+
field('secureDisable', 0x78, 8, 'number');
|
|
232
|
+
field('ROMsize', 0x80, 4, 'number');
|
|
233
|
+
field('headerSize', 0x84, 4, 'number');
|
|
234
|
+
field('reserved2', 0x88, 56);
|
|
235
|
+
field('logo', 0xc0, 156);
|
|
236
|
+
field('logoCRC16', 0x15c, 2, 'number');
|
|
237
|
+
field('headerCRC16', 0x15e, 2, 'number');
|
|
238
|
+
'debug_romOffset debug_size debug_ramAddress reserved3'
|
|
239
|
+
.split(' ')
|
|
240
|
+
.forEach((name, i) => field(name, 0x160 + i * 4, 4, 'number'));
|
|
241
|
+
field('appflags', 0x1bc, 4, 'buffer', true);
|
|
242
|
+
'mbk9_wramcnt_setting region_flags access_control scfg_ext_mask'
|
|
243
|
+
.split(' ')
|
|
244
|
+
.forEach((name, i) => field(name, 0x1ac + i * 4, 4, 'number', true));
|
|
245
|
+
const twl32 =
|
|
246
|
+
'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(
|
|
247
|
+
' ',
|
|
248
|
+
);
|
|
249
|
+
twl32.forEach((name, i) => field(name, 0x1c0 + i * 4, 4, 'number', true));
|
|
250
|
+
'hmac_arm9 hmac_arm7 hmac_digest_master hmac_icon_title hmac_arm9i hmac_arm7i'
|
|
251
|
+
.split(' ')
|
|
252
|
+
.forEach((name, i) => field(name, 0x300 + i * 20, 20, 'buffer', true));
|
|
253
|
+
field('hmac_arm9_no_secure', 0x3a0, 20, 'buffer', true);
|
|
254
|
+
field('rsa_signature', 0xf80, 0x80, 'buffer', true);
|