297 Commits

Author SHA1 Message Date
Matt Nadareski
7ec0517866 Add editorconfig, fix issues 2026-01-25 17:20:06 -05:00
Matt Nadareski
d94b83aca6 Add default config path (fixes #25) 2025-12-15 15:52:58 -05:00
Matt Nadareski
e63792070a Update Serialization to 2.2.1 2025-11-25 07:53:34 -05:00
Matt Nadareski
2bc04c9e15 Bump version 2025-11-24 10:16:24 -05:00
Matt Nadareski
632b8d8c53 Add support for .NET 10 2025-11-24 10:07:12 -05:00
Matt Nadareski
eada29c89b Update packages 2025-11-06 08:19:40 -05:00
Matt Nadareski
818ca1ea03 Update rolling tag 2025-10-26 20:23:36 -04:00
Matt Nadareski
eceb4ba22e Update packages 2025-10-07 12:56:23 -04:00
Matt Nadareski
8627455ed7 Maintain consistent comments 2025-10-06 09:01:09 -04:00
Matt Nadareski
a7cc4ba4ed Clean up after the previous commit 2025-10-06 08:59:20 -04:00
Matt Nadareski
07676f4dcc Use CommandLine library for executable 2025-10-06 08:57:26 -04:00
Matt Nadareski
1a69113af7 Update Serialization to 2.0.1 2025-10-05 17:16:12 -04:00
Matt Nadareski
92adfa17df Add another note 2025-09-30 19:34:42 -04:00
Matt Nadareski
c48ac6e4cc Clean up the usings 2025-09-30 18:20:01 -04:00
Matt Nadareski
4e778bc837 Sync back fixes from IO 2025-09-30 18:09:17 -04:00
Matt Nadareski
ccd04cb15e Remove an unneeded BouncyCastle use 2025-09-30 17:05:50 -04:00
Matt Nadareski
97c17bb9dc Require exact versions for build 2025-09-30 11:23:44 -04:00
Matt Nadareski
2447007900 Update Serialization to 2.0.0 2025-09-29 07:47:32 -04:00
Matt Nadareski
f79a5fa246 Update packages 2025-09-24 11:21:54 -04:00
Matt Nadareski
16aeb6cafe Update Serialization to 1.9.5 2025-09-21 12:51:30 -04:00
Matt Nadareski
c4d812c426 Update Serialization to 1.9.4 2025-09-21 09:45:41 -04:00
Matt Nadareski
383572d5b5 Update packages 2025-09-20 22:45:45 -04:00
Matt Nadareski
415b3f17c1 Bump version 2025-09-19 11:03:00 -04:00
Matt Nadareski
06d52e04c5 Fix hashing output 2025-09-19 11:02:34 -04:00
Matt Nadareski
a26e8b260c Bump version 2025-09-17 22:51:28 -04:00
Matt Nadareski
6c2edd225d Fix the error statement too 2025-09-17 22:44:54 -04:00
Matt Nadareski
1fa9345f06 InputPaths are not args 2025-09-17 22:43:47 -04:00
Matt Nadareski
e342448599 Bump version 2025-09-07 08:39:18 -04:00
Matt Nadareski
27431e05de Stop reinventing the wheel again 2025-09-06 19:41:39 -04:00
Matt Nadareski
56f8869d1d Be consistent, for once 2025-09-06 19:38:52 -04:00
Matt Nadareski
9e9efaf491 Always overwrite 2025-09-06 19:35:51 -04:00
Matt Nadareski
e8a0d706de Simplest solution to start 2025-09-06 19:29:26 -04:00
Matt Nadareski
9eb49d170f Add basically unused option 2025-09-06 18:19:19 -04:00
Matt Nadareski
c31f95b85d Just pass the options 2025-09-06 18:16:40 -04:00
Matt Nadareski
dc1952d6f9 Be less vague, kinda 2025-09-06 18:15:34 -04:00
Matt Nadareski
6ae797a6eb Copy inputs to outputs for processing, optionally 2025-09-06 18:14:39 -04:00
Matt Nadareski
ed8e97aa0c More prep for a better future 2025-09-06 18:09:54 -04:00
Matt Nadareski
cc16770126 Start prepping for things 2025-09-06 18:04:24 -04:00
Matt Nadareski
8c186f969c Clear up them there wording 2025-09-06 17:56:05 -04:00
Matt Nadareski
ac542076aa Forgot to remove another helper 2025-09-06 17:48:01 -04:00
Matt Nadareski
94bebddda2 Goodbye old formats, I won't miss you 2025-09-06 17:45:17 -04:00
Matt Nadareski
8c66c40b48 Use separate Options class like nearly every other reference implementation 2025-09-06 17:34:23 -04:00
Matt Nadareski
1fbacaffb0 Take advantage of Wrapper functions for NDS 2025-09-06 16:07:37 -04:00
Matt Nadareski
0448682934 Add .NET Standard 2.x support 2025-09-06 15:51:44 -04:00
Matt Nadareski
9e563785c9 Update Nuget packages 2025-09-06 15:51:11 -04:00
Matt Nadareski
7469c97c6b Use existing IO extension 2025-07-28 09:36:52 -04:00
Matt Nadareski
7c909af154 Update IO to 1.6.3 2025-05-12 08:25:57 -04:00
Matt Nadareski
31b40e6f10 Bump version 2025-04-04 12:43:54 -04:00
Matt Nadareski
69a07cde85 Change table columns to match sections 2025-04-04 12:04:02 -04:00
Matt Nadareski
b9b4d6876c Include native libraries in executable for publish 2025-04-03 12:16:17 -04:00
Matt Nadareski
bed965c6dd Add script link to README, thanks Dimensional 2025-04-02 17:01:01 -04:00
Matt Nadareski
f01c04d796 Be consistent with language for once 2025-04-02 13:35:42 -04:00
Matt Nadareski
5de970965e README overhaul and minor flag update 2025-04-02 13:34:19 -04:00
Matt Nadareski
f3e4aa0b7e Slight cleanup of notes in README 2025-04-02 12:55:29 -04:00
Matt Nadareski
e2110e80c0 Add keys.bin order for now 2025-04-02 11:52:18 -04:00
Matt Nadareski
b0f2c6658a Add keys.conf mappings to README 2025-04-02 11:50:24 -04:00
Matt Nadareski
baf0b9045e Remove old note, add new note 2025-04-02 11:15:37 -04:00
Matt Nadareski
b82eb54aab Do something with the results 2025-04-02 11:10:48 -04:00
Matt Nadareski
7d541b6c4b Document unlicensed DS values 2025-04-02 10:50:38 -04:00
Matt Nadareski
9d72a25cc2 Relax validation on NDS 2025-04-01 20:25:07 -04:00
Matt Nadareski
79e8c1b6bf Add debug prefix for all key validation 2025-04-01 20:12:29 -04:00
Matt Nadareski
8f6df04e2c Remove weirdly-phrased time limitation 2025-04-01 16:54:24 -04:00
Matt Nadareski
466144f8c9 Move default config to root level 2025-04-01 16:50:52 -04:00
Matt Nadareski
629f2cc11e Fix old .NET build 2025-04-01 16:47:03 -04:00
Matt Nadareski
1ffd1a3bff Look for files in places with things (fixes #19) 2025-04-01 16:43:21 -04:00
Matt Nadareski
d4bcd62941 Add more config stuff to README 2025-04-01 14:12:25 -04:00
Matt Nadareski
702c31413b Add minimal documentation of new input 2025-04-01 14:06:31 -04:00
Matt Nadareski
d4a6e902cf Add NED validation 2025-04-01 13:58:55 -04:00
Matt Nadareski
c00e72506e Prefer config file, if exists (nodoc) 2025-04-01 13:46:34 -04:00
Matt Nadareski
7ccc160e90 Prepare configuration for future use (unused) 2025-04-01 13:30:18 -04:00
Matt Nadareski
068b83da8e Add default configuration file (unused) 2025-04-01 13:11:26 -04:00
Matt Nadareski
d429ea3e64 Remove now-obsolete TODO 2025-04-01 13:05:05 -04:00
Matt Nadareski
5bf1d85e05 Add key validation (unused) 2025-04-01 13:04:52 -04:00
Matt Nadareski
32b655b1f9 Reread decrypted ExeFS headers (fixes #20) 2025-04-01 12:33:05 -04:00
Matt Nadareski
e3a64ece96 Fix stray reference to old AES keys param 2025-04-01 11:59:19 -04:00
Matt Nadareski
74986afc38 Start wiring through configuration file 2025-04-01 11:58:23 -04:00
Matt Nadareski
3cc2bcf022 Remove support for old "citra" flag name 2025-04-01 11:45:13 -04:00
Matt Nadareski
ebd13e3728 Update README 2025-04-01 11:44:29 -04:00
Matt Nadareski
2d7a54ddb7 Create unused Configuration class 2025-04-01 11:42:25 -04:00
Matt Nadareski
60d989d899 Move more encryption vars to common location 2025-04-01 11:23:27 -04:00
Matt Nadareski
4de3cfe6fa Fix how conditions are used for references 2025-02-25 21:26:08 -05:00
Matt Nadareski
50034975c3 Add newline for DSTool info output 2025-02-19 21:15:58 -05:00
Matt Nadareski
ab4f076846 Use tabs before info lines for readability in multi-file scenarios 2025-02-19 21:10:52 -05:00
Matt Nadareski
08840055a4 The British are coming! 2025-02-19 20:55:55 -05:00
Matt Nadareski
02eafc688a Let info writing also write hashes 2025-02-19 15:51:46 -05:00
Matt Nadareski
db7c7ee273 Add simple info check (fixes #18) 2025-02-19 15:47:05 -05:00
Matt Nadareski
383e6bdfce Add info to interface; add start of implementations 2025-02-19 15:36:36 -05:00
Matt Nadareski
169bd48762 Add info skeleton 2025-02-19 15:32:28 -05:00
Matt Nadareski
a2ab3c25c9 Convert encrypt/decrypt bool to an enum 2025-02-19 15:30:59 -05:00
Matt Nadareski
abf0843d22 Update copyright 2024-12-30 21:32:22 -05:00
Matt Nadareski
ca64a2575e Remove unnecessary action step 2024-12-30 21:32:01 -05:00
Matt Nadareski
035760b5f5 Ensure .NET versions are installed for testing 2024-12-30 21:31:43 -05:00
Matt Nadareski
66e6a8cd4a Allow symbols to be packed 2024-12-30 21:31:01 -05:00
Matt Nadareski
d85e7656c4 Update packages 2024-12-30 21:29:34 -05:00
Matt Nadareski
942151b6d7 Add .cci as a recognized extension for 3DS 2024-12-13 09:51:39 -05:00
Matt Nadareski
d7a51c7798 Ensure publish script is executable 2024-12-06 12:26:44 -05:00
Matt Nadareski
bd86b95494 Use publish script and update README 2024-12-06 12:24:31 -05:00
Matt Nadareski
5562b403a4 Use wrappers to be more safe 2024-11-28 22:06:52 -05:00
Matt Nadareski
ead70df624 Support back to .NET Framework 2.0 2024-11-18 16:20:09 -05:00
Matt Nadareski
33e62e046b Make Core an unpublished package 2024-11-14 21:59:04 -05:00
Matt Nadareski
bb0b5a3d05 Remove all CIA references
The implementation was woefully incomplete to begin with, and if working with 3DS cart images has taught anything, making sure that it can deserialize properly needs to be done first. Once CIA support can be started again, it should be built from the ground up.
2024-11-14 21:53:31 -05:00
Matt Nadareski
6a094b2dd8 Bump version 2024-11-14 21:40:01 -05:00
Matt Nadareski
0e3d22020b Fix remaining offset and size issues 2024-11-14 21:39:20 -05:00
Matt Nadareski
695f0f44b6 Ensure ExeFS table doesn't finalize 2024-11-14 21:25:38 -05:00
Matt Nadareski
dc4bd15e04 Fix incorrect size being used 2024-11-14 21:17:35 -05:00
Matt Nadareski
ce54eb24e2 Fix more issues 2024-11-14 13:43:07 -05:00
Matt Nadareski
15961e7047 Wording change 2024-11-14 11:56:18 -05:00
Matt Nadareski
edd8ebc048 Update Serialization to 1.7.3 2024-11-14 11:44:41 -05:00
Matt Nadareski
23e9edbf69 Remove null assurances 2024-11-14 02:16:50 -05:00
Matt Nadareski
00ac5e1ca2 Split some steps for readability 2024-11-14 01:56:06 -05:00
Matt Nadareski
c79781a6d7 Keep fixing byte array math 2024-11-14 01:48:14 -05:00
Matt Nadareski
5aa50ee252 Fix IV and addition 2024-11-14 00:26:58 -05:00
Matt Nadareski
3fbaedd7d5 Reduce unnecessary methods 2024-11-13 21:15:44 -05:00
Matt Nadareski
bc31cb0f6a Start fixing issues, remove BigInteger 2024-11-13 21:11:26 -05:00
Matt Nadareski
3ef32748e9 Remove temp code 2024-11-13 14:51:16 -05:00
Matt Nadareski
41293ab7c5 Add byte array helpers 2024-11-13 14:41:32 -05:00
Matt Nadareski
7a5593dd72 Bump version 2024-11-13 12:27:54 -05:00
Matt Nadareski
326b747204 Add .NET 9 to target frameworks 2024-11-13 12:24:36 -05:00
Matt Nadareski
ac9fc92e06 Should be debug only 2024-11-05 14:09:00 -05:00
Matt Nadareski
8a2d91aace Attempt to reduce nesting in GHA builds 2024-11-05 13:57:50 -05:00
Matt Nadareski
add3d28488 Remove unnecessary code region 2024-10-14 00:24:39 -04:00
Matt Nadareski
bd078e4585 Reduce unnecessary namespace nesting 2024-10-14 00:22:44 -04:00
Matt Nadareski
9f5ffaf035 Move various extensions to Serialization 2024-10-14 00:15:26 -04:00
Matt Nadareski
3785426789 Not sure how those slipped through 2024-10-14 00:06:52 -04:00
Matt Nadareski
ee48155c77 Make usings make more sense 2024-10-14 00:05:41 -04:00
Matt Nadareski
c67277caa6 Fix formatting issue in DSTool 2024-10-14 00:03:33 -04:00
Matt Nadareski
cced3b5b3d Even further consolidation 2024-10-14 00:02:59 -04:00
Matt Nadareski
4c56377d7e Reduce unnessary code found after move 2024-10-13 23:59:44 -04:00
Matt Nadareski
201098e32c Re-consolidate internal libraries
These were originally split out from each other when the models for each of the different cart and digital types were included in this project. After it got split out, and after a lot of recent changes, it was apparent that this split was no longer necessary.
2024-10-13 23:50:22 -04:00
Matt Nadareski
8ec0307e1c Move DecryptArgs to N3DS namespace 2024-10-13 23:45:53 -04:00
Matt Nadareski
864cb57d30 Slight cleanup of PartitionKeys 2024-10-13 23:42:00 -04:00
Matt Nadareski
7576608111 Slight formatting tweak 2024-10-13 23:31:47 -04:00
Matt Nadareski
e3f740e115 Ensure better accessors 2024-10-13 23:31:13 -04:00
Matt Nadareski
4eaa56212f Missed one comment 2024-10-13 23:09:43 -04:00
Matt Nadareski
6ef543ee1a That was tiring 2024-10-13 23:08:48 -04:00
Matt Nadareski
5a4e747329 Almost there... 2024-10-13 23:05:34 -04:00
Matt Nadareski
4a35d490d5 Stay on target... 2024-10-13 23:01:36 -04:00
Matt Nadareski
2983e21330 One more step 2024-10-13 22:56:32 -04:00
Matt Nadareski
ead4f52f3c Bring CIA up to par with 3DS 2024-10-13 22:49:31 -04:00
Matt Nadareski
72afaeb010 Migrate more to split code 2024-10-13 22:10:59 -04:00
Matt Nadareski
5148ba5d83 Create and use CommonOperations for 3DS 2024-10-13 22:02:14 -04:00
Matt Nadareski
ccf746ca7c Better split encrypt/decrypt code 2024-10-13 20:52:04 -04:00
Matt Nadareski
656b8158e9 Slight formatting change 2024-10-13 20:41:02 -04:00
Matt Nadareski
263ca9b6bf Replace partitionIndex with index 2024-10-13 20:37:54 -04:00
Matt Nadareski
158fcfff36 Make more helper functions 2024-10-13 20:36:42 -04:00
Matt Nadareski
6e13e9e4c4 Make CIATool consistent with ThreeDSTool 2024-10-13 11:56:03 -04:00
Matt Nadareski
6d5bc3dbcf Be more consistent with naming 2024-10-13 11:50:52 -04:00
Matt Nadareski
c73e971704 Use new PartitionKeys class 2024-10-13 11:48:30 -04:00
Matt Nadareski
6c575d0157 Create new PartitionKeys class 2024-10-13 11:40:29 -04:00
Matt Nadareski
5be07157e8 Move FileType to separate file 2024-10-13 11:26:16 -04:00
Matt Nadareski
7d069d6bf7 Slight tweak for better readability 2024-10-13 11:24:13 -04:00
Matt Nadareski
46b60559a2 Rename private fields 2024-10-13 11:21:24 -04:00
Matt Nadareski
849315076c Rename private fields 2024-10-13 11:20:58 -04:00
Matt Nadareski
f6f3cd062e Make decrypt args slightly nicer 2024-10-13 11:19:56 -04:00
Matt Nadareski
3dac146451 It's okay, it's complex again 2024-10-12 02:44:44 -04:00
Matt Nadareski
af226e347c Support .dec extension 2024-10-12 02:34:31 -04:00
Matt Nadareski
37f27e41cf Slight redo of comments 2024-10-12 02:30:03 -04:00
Matt Nadareski
3742a5c29e Simplify decrypt args creation 2024-10-12 02:27:45 -04:00
Matt Nadareski
2e7bc64e26 Yeah, sure 2024-10-12 02:25:32 -04:00
Matt Nadareski
af14f829b0 Development is a flag, not food 2024-10-12 02:20:17 -04:00
Matt Nadareski
93d34c3b9c Change warning text 2024-10-12 02:09:44 -04:00
Matt Nadareski
51b690bb6d Rename flag but keep old one in code for now 2024-10-12 02:08:23 -04:00
Matt Nadareski
bd1d6935d8 Reduce passed around values 2024-10-12 02:06:51 -04:00
Matt Nadareski
08a1dc5e42 Derive key file in a safer way 2024-10-12 01:58:07 -04:00
Matt Nadareski
77407f54fb Only initialize for 3DS 2024-10-12 01:46:45 -04:00
Matt Nadareski
2eb969b6ac Slightly rename _decryptArgs 2024-10-12 01:43:04 -04:00
Matt Nadareski
747625c2b8 Path is part of the args now 2024-10-12 01:42:02 -04:00
Matt Nadareski
993ce492b7 Remove now-unused field from DSTool 2024-10-12 01:33:16 -04:00
Matt Nadareski
3b33e3d370 Force is not a decryption argument 2024-10-12 01:32:32 -04:00
Matt Nadareski
36ae14a997 Reduce reliance in decrypt args in 3DSTool 2024-10-12 01:26:45 -04:00
Matt Nadareski
4e0a582269 Reduce reliance in decrypt args in DSTool 2024-10-12 01:24:30 -04:00
Matt Nadareski
9f018ca73b Split encrypt and decrypt in DS 2024-10-12 01:07:00 -04:00
Matt Nadareski
3f07271e7f Move encrypt flag out of args 2024-10-12 00:59:07 -04:00
Matt Nadareski
84b62e2c52 Remove unused compatiblity packages 2024-10-12 00:32:24 -04:00
Matt Nadareski
041d5b633e Remove reliance on BinaryReader/Writer 2024-10-12 00:30:02 -04:00
Matt Nadareski
54080b3ef1 Simplify ExeFS reading 2024-10-12 00:18:00 -04:00
Matt Nadareski
32b430fafa Update libraries 2024-10-12 00:15:00 -04:00
Matt Nadareski
450f6fb267 Fix issue in Windows build script 2024-08-08 19:55:48 -04:00
Matt Nadareski
96f9715dff Missed a couple 2024-08-08 16:15:19 -04:00
Matt Nadareski
96ed279db5 Standardize comments in 3DS 2024-08-08 16:06:40 -04:00
Matt Nadareski
344d69ca52 Make 3DS code more readable 2024-08-08 15:49:36 -04:00
Matt Nadareski
0aa946378a Update SabreTools.Models 2024-08-08 14:55:07 -04:00
Matt Nadareski
6ec5c69e27 Use var because I can 2024-08-08 14:06:52 -04:00
Matt Nadareski
45de58d2f1 Simplify using statements 2024-08-08 14:01:42 -04:00
Matt Nadareski
10f1532bb1 Split NDS serialization methods 2024-08-08 13:40:45 -04:00
Matt Nadareski
45f5e1ffff Clean up serialization methods 2024-08-08 13:32:30 -04:00
Matt Nadareski
9fad439ab8 Use proper model name 2024-08-08 13:17:50 -04:00
Matt Nadareski
179b53f462 Migrate 3DS to using standard models 2024-08-08 11:53:47 -04:00
Matt Nadareski
c52d3de426 Reorganize NDS 2024-08-08 09:38:02 -04:00
Matt Nadareski
36393f7950 Migrate NDS to using standard models 2024-08-08 09:15:50 -04:00
Matt Nadareski
af8a8eb72d Bump to net8.0 for launch 2024-07-20 22:23:13 -04:00
Matt Nadareski
5040de04e5 Make aes_keys keys more obvious 2024-07-20 22:21:23 -04:00
Matt Nadareski
78b168e4ce Fix that funky formatting 2024-07-20 22:15:08 -04:00
Matt Nadareski
2df30d095b Modernize NDecrypt 2024-07-20 22:00:54 -04:00
spiritfader
e2c2c804a5 add support for .nds.enc extension (#16)
* add support for.nds.enc extension

* Update Program.cs
2024-03-31 16:36:39 -07:00
Matt Nadareski
ce13c34b0a Support and skip #-prefixed comments (fixes #13) 2024-02-18 23:03:46 -05:00
soxhi8
71199ee94a Fix opeation typo (#10) 2023-02-21 09:37:33 -08:00
Matt Nadareski
1159b677bc Bump version to 0.2.5 2023-01-06 10:14:31 -08:00
Matt Nadareski
6527d85f29 Commit hash only 2023-01-06 10:01:23 -08:00
Matt Nadareski
ff815299fb Try using a different variable 2023-01-06 09:56:43 -08:00
Matt Nadareski
c04de5700a Use different formatting for version string 2023-01-06 09:52:38 -08:00
Matt Nadareski
ab9d32f56a Attempt to use commit hash in naming 2023-01-06 09:51:40 -08:00
Matt Nadareski
8151f306ec Experiment adding version numbers 2023-01-06 09:46:37 -08:00
Matt Nadareski
fd9b94d428 Fix artifact naming 2023-01-06 09:41:43 -08:00
Matt Nadareski
9cc2b6befe Replace assembly location 2023-01-06 09:40:52 -08:00
Matt Nadareski
8c2d4d701a Move AppVeyor script, oops 2023-01-06 09:36:31 -08:00
Matt Nadareski
593e8b2b4d Add build status to README 2023-01-06 09:33:45 -08:00
Matt Nadareski
71f3295a2b Add AppVeyor config 2023-01-06 09:32:55 -08:00
Matt Nadareski
a29071e697 Modernize NDecrypt 2023-01-06 09:31:42 -08:00
Matt Nadareski
96c3c3732c Set license properly to MIT
MIT most closely matches the statement that was included previously in the README about an extremely permissive license.
2023-01-06 09:27:41 -08:00
Matt Nadareski
c8f8a7d99c Bump version to 0.2.4 2022-10-31 10:30:52 -07:00
Matt Nadareski
3ee8c2e79b Update csproj files with full info 2022-10-31 10:27:56 -07:00
Matt Nadareski
7d263886da Force reader buffer refresh to omit stale data 2022-10-27 16:41:22 -07:00
Matt Nadareski
6d1b23ffec Be less clever in code to make debugging easier 2022-10-27 15:28:34 -07:00
Matt Nadareski
92d1c3c5e5 Split NDecrypt.Library; add build targets 2022-10-27 11:27:13 -07:00
Matt Nadareski
855a353e90 Reverse library and exe naming 2022-10-27 11:06:06 -07:00
Matt Nadareski
6dac45f973 Update BouncyCastle to 1.9.0 2022-09-27 13:35:07 -07:00
Matt Nadareski
a6337e0864 Drop .NET 5 support 2022-09-27 12:27:16 -07:00
Matt Nadareski
778d5d4ffc Fix hashing because of .NET 6 2022-09-27 12:19:53 -07:00
Matt Nadareski
16a84f0dc6 .NET 6.0, .NET Standard 2.0, Cleanup 2022-04-17 13:10:55 -07:00
Matt Nadareski
7fbe044ab8 Read ContentIndex data, just in case 2021-09-28 22:23:04 -07:00
Matt Nadareski
7c9a035d7c Cleanup and add note 2021-09-28 22:19:23 -07:00
Matt Nadareski
964e6f243c Fill in all methods... with a lot of TODOs 2021-09-28 22:14:40 -07:00
Matt Nadareski
b454e42f8e Start filling in more methods for CIA 2021-09-28 21:54:31 -07:00
Matt Nadareski
9f70eae8be Fix reading NCCH data in CIA header 2021-09-28 21:48:58 -07:00
Matt Nadareski
20987573f4 Fix build 2021-09-28 21:37:12 -07:00
Matt Nadareski
2fb05b577e Fix reading CIA header info 2021-09-28 21:32:30 -07:00
Matt Nadareski
f72b8c3506 Rename NDS -> Nitro 2021-09-27 10:56:49 -07:00
Matt Nadareski
8b32b21475 Better reading of Ticket limits 2021-09-27 10:51:44 -07:00
Matt Nadareski
2779f5ef72 Better certificate chain reading 2021-09-27 10:42:54 -07:00
Matt Nadareski
bbbf71a603 Fill in more CIA-specific objects 2021-09-27 10:26:22 -07:00
Matt Nadareski
bea979dca5 Add CIA notes 2021-09-26 23:31:39 -07:00
Matt Nadareski
5a0169310b Better wording 2021-09-26 23:20:06 -07:00
Matt Nadareski
67c83bd126 Make note a little more apparent 2021-09-26 22:52:12 -07:00
Matt Nadareski
6fdcc2effe Add Citra names to README 2021-09-26 22:38:14 -07:00
Matt Nadareski
9ff0205e15 Add keyfile path input 2021-09-26 22:35:10 -07:00
Matt Nadareski
c91d7f2708 Separate out CMD portion
This also introduces a new helper class for holding all of the decryption args. This makes it so that the method signatures don't have to change so much.
2021-09-26 22:22:17 -07:00
Matt Nadareski
d9b333e3ba Start adding Citra keyfile support 2021-09-26 21:36:55 -07:00
Matt Nadareski
ef5a01edf7 Add CIA header (nw) 2021-07-22 22:36:05 -07:00
Matt Nadareski
012787a5b1 Hook up hashing, fix string output 2021-02-08 22:13:30 -08:00
Matt Nadareski
df5664fa1f Add hashing code (not hooked up) 2021-02-08 21:54:23 -08:00
Matt Nadareski
bef252f57f Merge pull request #4 from Icyelut/fix-README
Fix typos in README that suggests the wrong keyslots
2021-01-26 21:04:01 -08:00
Icyelut
b78db22464 Fix typo in README that suggests the wrong dev keyslot
Signed-off-by: Icyelut <PanetGhoul@protonmail.com>
2021-01-11 20:46:29 +09:00
Icyelut
6d980c94fd Fix typo in README that suggests a keyslot that doesn't exist
Signed-off-by: Icyelut <PanetGhoul@protonmail.com>
2021-01-11 19:27:38 +09:00
Matt Nadareski
8452cb4fc0 Update compatibilty numers (100%!!!) 2020-12-19 21:43:56 -08:00
Matt Nadareski
0deaa0aed3 Tools to proper namespaces 2020-12-17 22:06:30 -08:00
Matt Nadareski
9b5f90a750 A bit of code cleanup, better communication 2020-12-17 22:02:04 -08:00
Matt Nadareski
7415a21d5c Try/catch blocks around file processing 2020-12-17 11:30:34 -08:00
Matt Nadareski
f349389994 keys.bin is only needed for 3DS, better docs 2020-12-17 11:08:53 -08:00
Matt Nadareski
a046bd4152 Remove forced pause 2020-12-17 10:39:30 -08:00
Matt Nadareski
51a8f0c9df Report new compatibility information 2020-12-17 10:37:34 -08:00
Matt Nadareski
0449c16a01 Yet another empty one 2020-12-16 19:39:33 -08:00
Matt Nadareski
7638d7dbf8 One more decrypted empty secure area 2020-12-16 16:27:39 -08:00
Matt Nadareski
387bf46e5a Fix incorrect matching criteria 2020-12-16 16:07:45 -08:00
Matt Nadareski
6d257dc1e3 Add iQue extension for NDS 2020-12-16 13:15:49 -08:00
Matt Nadareski
91d816e359 Handle PoP cases, add compatibility to README 2020-12-16 11:57:55 -08:00
Matt Nadareski
e90f7a76af One more comment 2020-12-15 14:33:30 -08:00
Matt Nadareski
9750ae5a1e Add DQ5 DS secure area values (fixes #1) 2020-12-15 14:27:14 -08:00
Matt Nadareski
fa518ed5a5 Make method private 2020-12-15 13:51:34 -08:00
Matt Nadareski
350acd7be4 Each system in own namespace 2020-12-15 13:45:19 -08:00
Matt Nadareski
dde0c96e6c Re-merge projects 2020-12-15 13:30:28 -08:00
Matt Nadareski
36e6e803cc Fix build, add modern builds 2020-12-15 12:01:21 -08:00
Matt Nadareski
e669936e08 Add new versions with only Read 2020-12-14 23:29:08 -08:00
Matt Nadareski
dc55511a84 Add a couple proto checks, better logs for weird cases 2020-10-11 23:31:37 -07:00
Matt Nadareski
b9b8c76e84 Add force flag for both NDS and N3DS 2020-10-11 23:13:23 -07:00
Matt Nadareski
10fcd51e10 This annoyed me 2019-09-16 22:48:02 -07:00
Matt Nadareski
a0878d0bf4 Better handle 2GiB+ games
Thanks to Aringon for reporting the issue
2019-09-16 22:36:20 -07:00
Matt Nadareski
ed55f76d1e Better define keys.bin 2019-04-11 23:50:00 -07:00
Matt Nadareski
a726fc26d9 Code rearrange 2019-04-11 22:06:07 -07:00
Matt Nadareski
414df7808e Fix readme 2019-04-11 21:49:17 -07:00
Matt Nadareski
d61e03687b Add readme 2019-04-11 21:25:15 -07:00
Matt Nadareski
cccb4e6261 Remove constants from code 2019-04-11 16:58:02 -07:00
Matt Nadareski
df7ff2b95a Make main program a little better 2019-04-11 15:42:52 -07:00
Matt Nadareski
52a928638b Add other known blank values 2019-04-11 15:28:10 -07:00
Matt Nadareski
5962fc6072 Better comments 2019-04-11 15:02:01 -07:00
Matt Nadareski
5807d4ddc2 Handle empty secure area, better 2019-04-11 14:59:05 -07:00
Matt Nadareski
6fa8867f93 Empty secure area 2019-04-11 14:43:39 -07:00
Matt Nadareski
ed45d2d14e Add edge case decrypt values 2019-04-11 14:15:11 -07:00
Matt Nadareski
69ecb0d379 Fix name 2019-04-11 12:55:41 -07:00
Matt Nadareski
5974e2371e Rename stuff, add SRL extension 2019-04-11 11:17:21 -07:00
Matt Nadareski
1e5e3badbc Start of cleanup 2019-04-11 10:02:38 -07:00
Matt Nadareski
ef02b1c33a Rename to NDecrypt, Add NDS encrypt/decrypt 2019-04-11 03:01:13 -07:00
Matt Nadareski
32e8bab2a6 Math is hard 2019-04-08 21:08:36 -07:00
Matt Nadareski
bd1c6f8b51 Processing in right files 2019-04-08 20:28:14 -07:00
Matt Nadareski
30da008cec Processing shunting 2019-04-08 02:09:33 -07:00
Matt Nadareski
aa7566c312 Separate out helper methods 2019-04-08 01:13:51 -07:00
Matt Nadareski
56a5f4951b Set proper keys 2019-04-08 01:07:08 -07:00
Matt Nadareski
4b71cd621f Code shared too much 2019-04-08 00:55:57 -07:00
Matt Nadareski
4042b1c216 Rename IO, move common case 2019-04-08 00:17:37 -07:00
Matt Nadareski
26c7a34e98 Read and use full headers 2019-04-08 00:09:18 -07:00
Matt Nadareski
dc68aa5046 Summary overhaul 2019-04-07 22:45:27 -07:00
Matt Nadareski
ff486d8613 More modularization of shared code 2019-04-07 21:21:34 -07:00
Matt Nadareski
941f7d5191 Modularization, cleanup 2019-04-07 13:54:25 -07:00
Matt Nadareski
b83293e8c9 Fix encrypt 2019-04-07 03:04:05 -07:00
Matt Nadareski
0190f3d316 Encrypt, comments 2019-04-07 02:10:16 -07:00
Matt Nadareski
5c6b303d41 DoFinal 2019-04-07 02:03:44 -07:00
Matt Nadareski
0800d93d22 Notes and minor 2019-04-07 01:32:04 -07:00
50 changed files with 3063 additions and 1616 deletions

167
.editorconfig Normal file
View File

@@ -0,0 +1,167 @@
# top-most EditorConfig file
root = true
# C# files
[*.cs]
# Indentation and spacing
charset = utf-8
indent_size = 4
indent_style = space
tab_width = 4
trim_trailing_whitespace = true
# New line preferences
end_of_line = lf
insert_final_newline = true
max_line_length = unset
# using directive preferences
csharp_using_directive_placement = outside_namespace
dotnet_diagnostic.IDE0005.severity = error
# Code-block preferences
csharp_style_namespace_declarations = block_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_top_level_statements = false
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_inlined_variable_declaration = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
dotnet_diagnostic.IDE0001.severity = warning
dotnet_diagnostic.IDE0002.severity = warning
dotnet_diagnostic.IDE0004.severity = warning
dotnet_diagnostic.IDE0010.severity = error
dotnet_diagnostic.IDE0051.severity = warning
dotnet_diagnostic.IDE0052.severity = warning
dotnet_diagnostic.IDE0072.severity = warning
dotnet_diagnostic.IDE0080.severity = warning
dotnet_diagnostic.IDE0100.severity = error
dotnet_diagnostic.IDE0110.severity = error
dotnet_diagnostic.IDE0120.severity = warning
dotnet_diagnostic.IDE0121.severity = warning
dotnet_diagnostic.IDE0240.severity = error
dotnet_diagnostic.IDE0241.severity = error
dotnet_style_coalesce_expression = true
dotnet_style_namespace_match_folder = false
dotnet_style_null_propagation = true
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_collection_expression = when_types_loosely_match
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_compound_assignment = true
csharp_style_prefer_simple_property_accessors = true
dotnet_style_prefer_simplified_interpolation = true
dotnet_style_prefer_simplified_boolean_expressions = true
csharp_style_prefer_unbound_generic_type_in_nameof = true
# Field preferences
dotnet_diagnostic.IDE0044.severity = warning
dotnet_style_readonly_field = true
# Language keyword vs. framework types preferences
dotnet_diagnostic.IDE0049.severity = error
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_style_prefer_readonly_struct = true
dotnet_diagnostic.IDE0036.severity = warning
dotnet_diagnostic.IDE0040.severity = error
dotnet_diagnostic.IDE0380.severity = error
dotnet_style_require_accessibility_modifiers = always
# New-line preferences
dotnet_diagnostic.IDE2000.severity = warning
dotnet_diagnostic.IDE2002.severity = warning
dotnet_diagnostic.IDE2003.severity = warning
dotnet_diagnostic.IDE2004.severity = warning
dotnet_diagnostic.IDE2005.severity = warning
dotnet_diagnostic.IDE2006.severity = warning
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
dotnet_style_allow_multiple_blank_lines_experimental = false
dotnet_style_allow_statement_immediately_after_block_experimental = false
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
dotnet_diagnostic.IDE0280.severity = error
# Parentheses preferences
dotnet_diagnostic.IDE0047.severity = warning
dotnet_diagnostic.IDE0048.severity = warning
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = always_for_clarity
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Pattern-matching preferences
dotnet_diagnostic.IDE0019.severity = warning
dotnet_diagnostic.IDE0020.severity = warning
dotnet_diagnostic.IDE0038.severity = warning
dotnet_diagnostic.IDE0066.severity = none
dotnet_diagnostic.IDE0083.severity = warning
dotnet_diagnostic.IDE0260.severity = warning
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# var preferences
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = true
# .NET formatting options
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
# C# formatting options
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

48
.github/workflows/build_and_test.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Build and Test
on:
push:
branches: ["master"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Run tests
run: dotnet test
- name: Run publish script
run: ./publish-nix.sh -d
- name: Update rolling tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -f rolling
git push origin :refs/tags/rolling || true
git push origin rolling --force
- name: Upload to rolling
uses: ncipollo/release-action@v1.20.0
with:
allowUpdates: True
artifacts: "*.nupkg,*.snupkg,*.zip"
body: "Last built commit: ${{ github.sha }}"
name: "Rolling Release"
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True

20
.github/workflows/check_pr.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Build PR
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Build
run: dotnet build

5
.gitignore vendored
View File

@@ -258,4 +258,7 @@ paket-files/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
*.pyc
# VSCode
/NDecrypt/Properties/launchSettings.json

27
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,27 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/NDecrypt/bin/Debug/net10.0/NDecrypt.dll",
"args": [],
"cwd": "${workspaceFolder}/NDecrypt",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

24
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "shell",
"args": [
"build",
// Ask dotnet build to generate full paths for file names.
"/property:GenerateFullPaths=true",
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:NoSummary"
],
"group": "build",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2006
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "3DSDecrypt", "3DSDecrypt\3DSDecrypt.csproj", "{2E30006A-3C60-4576-A262-937B21C83C06}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2E30006A-3C60-4576-A262-937B21C83C06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E30006A-3C60-4576-A262-937B21C83C06}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E30006A-3C60-4576-A262-937B21C83C06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E30006A-3C60-4576-A262-937B21C83C06}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5AB50D9B-BA18-4F96-804B-52E7E0845B37}
EndGlobalSection
EndGlobal

View File

@@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2E30006A-3C60-4576-A262-937B21C83C06}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>_3DSDecrypt</RootNamespace>
<AssemblyName>3DSDecrypt</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="BouncyCastle.Crypto, Version=1.8.4.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
<HintPath>..\packages\BouncyCastle.1.8.4\lib\BouncyCastle.Crypto.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Data\Constants.cs" />
<Compile Include="Headers\NCCHHeaderFlags.cs" />
<Compile Include="ThreeDSTool.cs" />
<Compile Include="Data\Enums.cs" />
<Compile Include="Headers\AccessControlInfo.cs" />
<Compile Include="Headers\ARM11KernelCapabilities.cs" />
<Compile Include="Headers\ARM11LocalSystemCapabilities.cs" />
<Compile Include="Headers\ARM9AccessControl.cs" />
<Compile Include="Headers\CodeSetInfo.cs" />
<Compile Include="Headers\CXIExtendedHeader.cs" />
<Compile Include="Headers\ExeFSFileHeader.cs" />
<Compile Include="Headers\ExeFSHeader.cs" />
<Compile Include="Headers\NCCHHeader.cs" />
<Compile Include="Headers\NCSDHeader.cs" />
<Compile Include="Headers\PartitionTableEntry.cs" />
<Compile Include="Headers\StorageInfo.cs" />
<Compile Include="Headers\SystemControlInfo.cs" />
<Compile Include="Headers\SystemInfo.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -1,66 +0,0 @@
using System.Numerics;
namespace ThreeDS.Data
{
public static class Constants
{
// Setup Keys and IVs
public static byte[] PlainCounter = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static byte[] ExefsCounter = new byte[] { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static byte[] RomfsCounter = new byte[] { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
// Big Endian - 0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, 0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A
// Little Endian - 0x8A, 0x76, 0x52, 0x5D, 0xDC, 0x91, 0x45, 0x02, 0x08, 0x04, 0xFE, 0xC5, 0xAA, 0xE9, 0xF9, 0x1F
public static BigInteger AESHardwareConstant = new BigInteger(new byte[] { 0x8A, 0x76, 0x52, 0x5D, 0xDC, 0x91, 0x45, 0x02, 0x08, 0x04, 0xFE, 0xC5, 0xAA, 0xE9, 0xF9, 0x1F });
#region Retail keys
// KeyX 0x18 (New 3DS 9.3)
// Big Endian - 0x82, 0xE9, 0xC9, 0xBE, 0xBF, 0xB8, 0xBD, 0xB8, 0x75, 0xEC, 0xC0, 0xA0, 0x7D, 0x47, 0x43, 0x74
// Little Endian - 0x74, 0x43, 0x47, 0x7D, 0xA0, 0xC0, 0xEC, 0x75, 0xB8, 0xBD, 0xB8, 0xBF, 0xBE, 0xC9, 0xE9, 0x82
public static BigInteger KeyX0x18 = new BigInteger(new byte[] { 0x74, 0x43, 0x47, 0x7D, 0xA0, 0xC0, 0xEC, 0x75, 0xB8, 0xBD, 0xB8, 0xBF, 0xBE, 0xC9, 0xE9, 0x82, 0x00 });
// KeyX 0x1B (New 3DS 9.6)
// Big Endian - 0x45, 0xAD, 0x04, 0x95, 0x39, 0x92, 0xC7, 0xC8, 0x93, 0x72, 0x4A, 0x9A, 0x7B, 0xCE, 0x61, 0x82
// Little Endian - 0x82, 0x61, 0xCE, 0x7B, 0x9A, 0x4A, 0x72, 0x93, 0xC8, 0xC7, 0x92, 0x39, 0x95, 0x04, 0xAD, 0x45
public static BigInteger KeyX0x1B = new BigInteger(new byte[] { 0x82, 0x61, 0xCE, 0x7B, 0x9A, 0x4A, 0x72, 0x93, 0xC8, 0xC7, 0x92, 0x39, 0x95, 0x04, 0xAD, 0x45, 0x00 });
// KeyX 0x25 (> 7.x)
// Big Endian - 0xCE, 0xE7, 0xD8, 0xAB, 0x30, 0xC0, 0x0D, 0xAE, 0x85, 0x0E, 0xF5, 0xE3, 0x82, 0xAC, 0x5A, 0xF3
// Little Endian - 0xF3, 0x5A, 0xAC, 0x82, 0xE3, 0xF5, 0x0E, 0x85, 0xAE, 0x0D, 0xC0, 0x30, 0xAB, 0xD8, 0xE7, 0xCE
public static BigInteger KeyX0x25 = new BigInteger(new byte[] { 0xF3, 0x5A, 0xAC, 0x82, 0xE3, 0xF5, 0x0E, 0x85, 0xAE, 0x0D, 0xC0, 0x30, 0xAB, 0xD8, 0xE7, 0xCE, 0x00 });
// Dev KeyX 0x2C (< 6.x)
// Big Endian - 0xB9, 0x8E, 0x95, 0xCE, 0xCA, 0x3E, 0x4D, 0x17, 0x1F, 0x76, 0xA9, 0x4D, 0xE9, 0x34, 0xC0, 0x53
// Little Endian - 0x53, 0xC0, 0x34, 0xE9, 0x4D, 0xA9, 0x76, 0x1F, 0x17, 0x4D, 0x3E, 0xCA, 0xCE, 0x95, 0x8E, 0xB9
public static BigInteger KeyX0x2C = new BigInteger(new byte[] { 0x53, 0xC0, 0x34, 0xE9, 0x4D, 0xA9, 0x76, 0x1F, 0x17, 0x4D, 0x3E, 0xCA, 0xCE, 0x95, 0x8E, 0xB9, 0x00 });
#endregion
#region Dev Keys (Must be converted to LE)
// Dev KeyX 0x18 (New 3DS 9.3)
// Big Endian - 0x30, 0x4B, 0xF1, 0x46, 0x83, 0x72, 0xEE, 0x64, 0x11, 0x5E, 0xBD, 0x40, 0x93, 0xD8, 0x42, 0x76
// Little Endian - 0x76, 0x42, 0xD8, 0x93, 0x40, 0xBD, 0x5E, 0x11, 0x64, 0xEE, 0x72, 0x83, 0x46, 0xF1, 0x4B, 0x30
public static BigInteger DevKeyX0x18 = new BigInteger(new byte[] { 0x76, 0x42, 0xD8, 0x93, 0x40, 0xBD, 0x5E, 0x11, 0x64, 0xEE, 0x72, 0x83, 0x46, 0xF1, 0x4B, 0x30, 0x00 });
// Dev KeyX 0x1B New 3DS 9.6)
// Big Endian - 0x6C, 0x8B, 0x29, 0x44, 0xA0, 0x72, 0x60, 0x35, 0xF9, 0x41, 0xDF, 0xC0, 0x18, 0x52, 0x4F, 0xB6
// Little Endian - 0xB6, 0x4F, 0x52, 0x18, 0xC0, 0xDF, 0x41, 0xF9, 0x35, 0x60, 0x72, 0xA0, 0x44, 0x29, 0x8B, 0x6C
public static BigInteger DevKeyX0x1B = new BigInteger(new byte[] { 0xB6, 0x4F, 0x52, 0x18, 0xC0, 0xDF, 0x41, 0xF9, 0x35, 0x60, 0x72, 0xA0, 0x44, 0x29, 0x8B, 0x6C, 0x00 });
// Dev KeyX 0x25 (> 7.x)
// Big Endian - 0x81, 0x90, 0x7A, 0x4B, 0x6F, 0x1B, 0x47, 0x32, 0x3A, 0x67, 0x79, 0x74, 0xCE, 0x4A, 0xD7, 0x1B
// Little Endian - 0x1B, 0xD7, 0x4A, 0xCE, 0x74, 0x79, 0x67, 0x3A, 0x32, 0x47, 0x1B, 0x6F, 0x4B, 0x7A, 0x90, 0x81
public static BigInteger DevKeyX0x25 = new BigInteger(new byte[] { 0x1B, 0xD7, 0x4A, 0xCE, 0x74, 0x79, 0x67, 0x3A, 0x32, 0x47, 0x1B, 0x6F, 0x4B, 0x7A, 0x90, 0x81, 0x00 });
// Dev KeyX 0x2C (< 6.x)
// Big Endian - 0x51, 0x02, 0x07, 0x51, 0x55, 0x07, 0xCB, 0xB1, 0x8E, 0x24, 0x3D, 0xCB, 0x85, 0xE2, 0x3A, 0x1D
// Little Endian - 0x1D, 0x3A, 0xE2, 0x85, 0xCB, 0x3D, 0x24, 0x8E, 0xB1, 0xCB, 0x07, 0x55, 0x51, 0x07, 0x02, 0x51
public static BigInteger DevKeyX0x2C = new BigInteger(new byte[] { 0x1D, 0x3A, 0xE2, 0x85, 0xCB, 0x3D, 0x24, 0x8E, 0xB1, 0xCB, 0x07, 0x55, 0x51, 0x07, 0x02, 0x51, 0x00 });
#endregion
public const int CXTExtendedDataHeaderLength = 0x800;
}
}

View File

@@ -1,85 +0,0 @@
using System;
namespace ThreeDS.Data
{
[Flags]
public enum BitMasks : byte
{
FixedCryptoKey = 0x01,
NoMountRomFs = 0x02,
NoCrypto = 0x04,
NewKeyYGenerator = 0x20,
}
public enum ContentPlatform : byte
{
CTR = 0x01,
Snake = 0x02, // New3DS
}
[Flags]
public enum ContentType : byte
{
Data = 0x01,
Executable = 0x02,
SystemUpdate = 0x04,
Manual = 0x08,
Child = 0x04 | 0x08,
Trial = 0x10,
}
public enum CryptoMethod : byte
{
Original = 0x00,
Seven = 0x01,
NineThree = 0x0A,
NineSix = 0x0B,
}
public enum FilesystemType : ulong
{
None = 0,
Normal = 1,
FIRM = 3,
AGB_FIRMSave = 4,
}
public enum MediaCardDeviceType : byte
{
NORFlash = 0x01,
None = 0x02,
BT = 0x03,
}
public enum MediaPlatformIndex : byte
{
CTR = 0x01,
}
public enum MediaTypeIndex : byte
{
InnerDevice = 0x00,
Card1 = 0x01,
Card2 = 0x02,
ExtendedDevice = 0x03,
}
public enum NCCHFlags
{
CryptoMethod = 0x03,
ContentPlatform = 0x04,
ContentTypeBitMask = 0x05,
ContentUnitSize = 0x06,
BitMasks = 0x07,
}
public enum NCSDFlags
{
BackupWriteWaitTime = 0x00,
MediaCardDevice3X = 0x03,
MediaPlatformIndex = 0x04,
MediaTypeIndex = 0x05,
MediaUnitSize = 0x06,
MediaCardDevice2X = 0x07,
}
}

View File

@@ -1,28 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class ARM11KernelCapabilities
{
public byte[][] Descriptors = new byte[28][];
public byte[] Reserved = new byte[0x10];
public static ARM11KernelCapabilities Read(BinaryReader reader)
{
ARM11KernelCapabilities kc = new ARM11KernelCapabilities();
try
{
for (int i = 0; i < 28; i++)
kc.Descriptors[i] = reader.ReadBytes(4);
kc.Reserved = reader.ReadBytes(0x10);
return kc;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,54 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class ARM11LocalSystemCapabilities
{
public byte[] ProgramID = new byte[8];
public uint CoreVersion;
public byte Flag1;
public byte Flag2;
public byte Flag0;
public byte Priority;
public byte[][] ResourceLimitDescriptors = new byte[16][];
public StorageInfo StorageInfo;
public byte[][] ServiceAccessControl = new byte[32][];
public byte[][] ExtendedServiceAccessControl = new byte[2][];
public byte[] Reserved = new byte[0xF];
public byte ResourceLimitCategory;
public static ARM11LocalSystemCapabilities Read(BinaryReader reader)
{
ARM11LocalSystemCapabilities lsc = new ARM11LocalSystemCapabilities();
try
{
lsc.ProgramID = reader.ReadBytes(8);
lsc.CoreVersion = reader.ReadUInt32();
lsc.Flag1 = reader.ReadByte();
lsc.Flag2 = reader.ReadByte();
lsc.Flag0 = reader.ReadByte();
lsc.Priority = reader.ReadByte();
for (int i = 0; i < 16; i++)
lsc.ResourceLimitDescriptors[i] = reader.ReadBytes(2);
lsc.StorageInfo = StorageInfo.Read(reader);
for (int i = 0; i < 32; i++)
lsc.ServiceAccessControl[i] = reader.ReadBytes(8);
for (int i = 0; i < 2; i++)
lsc.ExtendedServiceAccessControl[i] = reader.ReadBytes(8);
lsc.Reserved = reader.ReadBytes(0xF);
lsc.ResourceLimitCategory = reader.ReadByte();
return lsc;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,26 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class ARM9AccessControl
{
public byte[] Descriptors = new byte[15];
public byte DescriptorVersion;
public static ARM9AccessControl Read(BinaryReader reader)
{
ARM9AccessControl ac = new ARM9AccessControl();
try
{
ac.Descriptors = reader.ReadBytes(15);
ac.DescriptorVersion = reader.ReadByte();
return ac;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,28 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class AccessControlInfo
{
public ARM11LocalSystemCapabilities ARM11LocalSystemCapabilities;
public ARM11KernelCapabilities ARM11KernelCapabilities;
public ARM9AccessControl ARM9AccessControl;
public static AccessControlInfo Read(BinaryReader reader)
{
AccessControlInfo aci = new AccessControlInfo();
try
{
aci.ARM11LocalSystemCapabilities = ARM11LocalSystemCapabilities.Read(reader);
aci.ARM11KernelCapabilities = ARM11KernelCapabilities.Read(reader);
aci.ARM9AccessControl = ARM9AccessControl.Read(reader);
return aci;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,32 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class CXIExtendedHeader
{
public SystemControlInfo SCI;
public AccessControlInfo ACI;
public byte[] AccessDescSignature = new byte[0x100];
public byte[] NCCHHDRPublicKey = new byte[0x100];
public AccessControlInfo ACIForLimitations;
public static CXIExtendedHeader Read(BinaryReader reader)
{
CXIExtendedHeader header = new CXIExtendedHeader();
try
{
header.SCI = SystemControlInfo.Read(reader);
header.ACI = AccessControlInfo.Read(reader);
header.AccessDescSignature = reader.ReadBytes(0x100);
header.NCCHHDRPublicKey = reader.ReadBytes(0x100);
header.ACIForLimitations = AccessControlInfo.Read(reader);
return header;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,28 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class CodeSetInfo
{
public byte[] Address = new byte[0x04];
public uint PhysicalRegionSizeInPages;
public uint SizeInBytes;
public static CodeSetInfo Read(BinaryReader reader)
{
CodeSetInfo csi = new CodeSetInfo();
try
{
csi.Address = reader.ReadBytes(4);
csi.PhysicalRegionSizeInPages = reader.ReadUInt32();
csi.SizeInBytes = reader.ReadUInt32();
return csi;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,32 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class ExeFSFileHeader
{
private const string codeSegment = ".code\0\0\0";
public string FileName;
public bool IsCodeBinary { get { return FileName == codeSegment; } }
public uint FileOffset;
public uint FileSize;
public byte[] FileHash = new byte[0x20];
public static ExeFSFileHeader Read(BinaryReader reader)
{
ExeFSFileHeader header = new ExeFSFileHeader();
try
{
header.FileName = new string(reader.ReadChars(8));
header.FileOffset = reader.ReadUInt32();
header.FileSize = reader.ReadUInt32();
return header;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,35 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class ExeFSHeader
{
public ExeFSFileHeader[] FileHeaders = new ExeFSFileHeader[10];
public byte[] Reserved = new byte[0x20];
public static ExeFSHeader Read(BinaryReader reader)
{
ExeFSHeader header = new ExeFSHeader();
try
{
for (int i = 0; i < 10; i++)
header.FileHeaders[i] = ExeFSFileHeader.Read(reader);
header.Reserved = reader.ReadBytes(0x20);
for (int i = 0; i < 10; i++)
{
byte[] fileHash = reader.ReadBytes(0x20);
header.FileHeaders[9 - i].FileHash = fileHash;
}
return header;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,101 +0,0 @@
using System;
using System.IO;
using System.Linq;
using ThreeDS.Data;
namespace ThreeDS.Headers
{
public class NCCHHeader
{
private const string NCCHMagicNumber = "NCCH";
public byte[] RSA2048Signature = new byte[0x100];
public uint ContentSizeInMediaUnits;
public uint ContentSizeInBytes { get { return ContentSizeInMediaUnits * 0x200; } }
public byte[] PartitionId = new byte[8];
public byte[] MakerCode = new byte[2];
public byte[] Version = new byte[2];
public byte[] VerificationHash = new byte[4];
public byte[] ProgramId = new byte[4];
public byte[] Reserved1 = new byte[0x10];
public byte[] LogoRegionHash = new byte[0x20];
public byte[] ProductCode = new byte[0x10];
public byte[] ExtendedHeaderHash = new byte[0x20];
public uint ExtendedHeaderSizeInBytes;
public byte[] Reserved2 = new byte[4];
public NCCHHeaderFlags Flags;
public uint PlainRegionOffsetInMediaUnits;
public uint PlainRegionOffsetInBytes { get { return PlainRegionOffsetInMediaUnits * 0x200; } }
public uint PlainRegionSizeInMediaUnits;
public uint PlainRegionSizeInBytes { get { return PlainRegionSizeInMediaUnits * 0x200; } }
public uint LogoRegionOffsetInMediaUnits;
public uint LogoRegionOffsetInBytes { get { return LogoRegionOffsetInMediaUnits * 0x200; } }
public uint LogoRegionSizeInMediaUnits;
public uint LogoRegionSizeInBytes { get { return LogoRegionSizeInMediaUnits * 0x200; } }
public uint ExeFSOffsetInMediaUnits;
public uint ExeFSOffsetInBytes { get { return ExeFSOffsetInMediaUnits * 0x200; } }
public uint ExeFSSizeInMediaUnits;
public uint ExeFSSizeInBytes { get { return ExeFSSizeInMediaUnits * 0x200; } }
public uint ExeFSHashRegionOffsetInMediaUnits;
public uint ExeFSHashRegionOffsetInBytes { get { return ExeFSHashRegionOffsetInMediaUnits * 0x200; } }
public uint ExeFSHashRegionSizeInMediaUnits;
public uint ExeFSHashRegionSizeInBytes { get { return ExeFSHashRegionSizeInMediaUnits * 0x200; } }
public uint RomFSOffsetInMediaUnits;
public uint RomFSOffsetInBytes { get { return RomFSOffsetInMediaUnits * 0x200; } }
public uint RomFSSizeInMediaUnits;
public uint RomFSSizeInBytes { get { return RomFSSizeInMediaUnits * 0x200; } }
public uint RomFSHashRegionOffsetInMediaUnits;
public uint RomFSHashRegionOffsetInBytes { get { return RomFSHashRegionOffsetInMediaUnits * 0x200; } }
public uint RomFSHashRegionSizeInMediaUnits;
public uint RomFSHashRegionSizeInBytes { get { return RomFSHashRegionSizeInMediaUnits * 0x200; } }
public byte[] ExeFSSuperblockHash = new byte[0x20];
public byte[] RomFSSuperblockHash = new byte[0x20];
public static NCCHHeader Read(BinaryReader reader)
{
NCCHHeader header = new NCCHHeader();
try
{
header.RSA2048Signature = reader.ReadBytes(0x100);
if (new string(reader.ReadChars(4)) != NCCHMagicNumber)
return null;
header.ContentSizeInMediaUnits = reader.ReadUInt32();
header.PartitionId = reader.ReadBytes(8).Reverse().ToArray();
header.MakerCode = reader.ReadBytes(2);
header.Version = reader.ReadBytes(2);
header.VerificationHash = reader.ReadBytes(4);
header.ProgramId = reader.ReadBytes(8);
header.Reserved1 = reader.ReadBytes(0x10);
header.LogoRegionHash = reader.ReadBytes(0x20);
header.ProductCode = reader.ReadBytes(0x10);
header.ExtendedHeaderHash = reader.ReadBytes(0x20);
header.ExtendedHeaderSizeInBytes = reader.ReadUInt32();
header.Reserved2 = reader.ReadBytes(4);
header.Flags = NCCHHeaderFlags.Read(reader);
header.PlainRegionOffsetInMediaUnits = reader.ReadUInt32();
header.PlainRegionSizeInMediaUnits = reader.ReadUInt32();
header.LogoRegionOffsetInMediaUnits = reader.ReadUInt32();
header.LogoRegionSizeInMediaUnits = reader.ReadUInt32();
header.ExeFSOffsetInMediaUnits = reader.ReadUInt32();
header.ExeFSSizeInMediaUnits = reader.ReadUInt32();
header.ExeFSHashRegionOffsetInMediaUnits = reader.ReadUInt32();
header.ExeFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
header.RomFSOffsetInMediaUnits = reader.ReadUInt32();
header.RomFSSizeInMediaUnits = reader.ReadUInt32();
header.RomFSHashRegionOffsetInMediaUnits = reader.ReadUInt32();
header.RomFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
header.ExeFSSuperblockHash = reader.ReadBytes(0x20);
header.RomFSSuperblockHash = reader.ReadBytes(0x20);
return header;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,58 +0,0 @@
using System;
using System.IO;
using ThreeDS.Data;
namespace ThreeDS.Headers
{
public class NCCHHeaderFlags
{
public byte Reserved0;
public byte Reserved1;
public byte Reserved2;
public CryptoMethod CryptoMethod;
public ContentPlatform ContentPlatform;
public ContentType MediaPlatformIndex;
public byte ContentUnitSize;
public uint ContentUnitSizeInBytes { get { return (uint)(0x200 * Math.Pow(2, this.ContentUnitSize)); } }
public BitMasks BitMasks;
public static NCCHHeaderFlags Read(BinaryReader reader)
{
NCCHHeaderFlags flags = new NCCHHeaderFlags();
try
{
flags.Reserved0 = reader.ReadByte();
flags.Reserved1 = reader.ReadByte();
flags.Reserved2 = reader.ReadByte();
flags.CryptoMethod = (CryptoMethod)reader.ReadByte();
flags.ContentPlatform = (ContentPlatform)reader.ReadByte();
flags.MediaPlatformIndex = (ContentType)reader.ReadByte();
flags.ContentUnitSize = reader.ReadByte();
flags.BitMasks = (BitMasks)reader.ReadByte();
return flags;
}
catch
{
return null;
}
}
public void Write(BinaryWriter writer)
{
try
{
writer.Write(this.Reserved0);
writer.Write(this.Reserved1);
writer.Write(this.Reserved2);
writer.Write((byte)this.CryptoMethod);
writer.Write((byte)this.ContentPlatform);
writer.Write((byte)this.MediaPlatformIndex);
writer.Write((byte)this.ContentUnitSize);
writer.Write((byte)this.BitMasks);
}
catch { }
}
}
}

View File

@@ -1,90 +0,0 @@
using System;
using System.IO;
using ThreeDS.Data;
namespace ThreeDS.Headers
{
public class NCSDHeader
{
private const string NCSDMagicNumber = "NCSD";
// Common to all NCSD files
public byte[] RSA2048Signature = new byte[0x100];
public uint ImageSizeInMediaUnits;
public uint ImageSizeInBytes { get { return ImageSizeInMediaUnits * 0x200; } }
public byte[] MediaId = new byte[8];
public FilesystemType PartitionsFSType;
public byte[] PartitionsCryptType = new byte[8];
public PartitionTableEntry[] PartitionsTable = new PartitionTableEntry[8];
// For carts
public byte[] ExheaderHash = new byte[0x20];
public uint AdditionalHeaderSize;
public uint SectorZeroOffset;
private byte[] partitionFlags = new byte[8];
public byte BackupWriteWaitTime { get { return partitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } }
public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)partitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } }
public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)partitionFlags[(int)NCSDFlags.MediaPlatformIndex]; } }
public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)partitionFlags[(int)NCSDFlags.MediaTypeIndex]; } }
public uint SectorSize { get { return (uint)(0x200 * Math.Pow(2, partitionFlags[(int)NCSDFlags.MediaUnitSize])); } }
public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)partitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } }
public byte[][] PartitionIdTable = new byte[8][];
public byte[] ReservedBlock1 = new byte[0x20];
public byte[] ReservedBlock2 = new byte[0xE];
public byte FirmUpdateByte1;
public byte FIrmUpdateByte2;
// For NAND
public byte[] Unknown = new byte[0x5E];
public byte[] EncryptedMBR = new byte[0x42];
public static NCSDHeader Read(BinaryReader reader)
{
NCSDHeader header = new NCSDHeader();
try
{
header.RSA2048Signature = reader.ReadBytes(0x100);
if (new string(reader.ReadChars(4)) != NCSDMagicNumber)
return null;
header.ImageSizeInMediaUnits = reader.ReadUInt32();
header.MediaId = reader.ReadBytes(8);
header.PartitionsFSType = (FilesystemType)reader.ReadUInt64();
header.PartitionsCryptType = reader.ReadBytes(8);
for (int i = 0; i < 8; i++)
header.PartitionsTable[i] = PartitionTableEntry.Read(reader);
if (header.PartitionsFSType == FilesystemType.Normal
|| header.PartitionsFSType == FilesystemType.None)
{
header.ExheaderHash = reader.ReadBytes(0x20);
header.AdditionalHeaderSize = reader.ReadUInt32();
header.SectorZeroOffset = reader.ReadUInt32();
header.partitionFlags = reader.ReadBytes(8);
for (int i = 0; i < 8; i++)
header.PartitionIdTable[i] = reader.ReadBytes(8);
header.ReservedBlock1 = reader.ReadBytes(0x20);
header.ReservedBlock2 = reader.ReadBytes(0xE);
header.FirmUpdateByte1 = reader.ReadByte();
header.FIrmUpdateByte2 = reader.ReadByte();
}
else if (header.PartitionsFSType == FilesystemType.FIRM)
{
header.Unknown = reader.ReadBytes(0x5E);
header.EncryptedMBR = reader.ReadBytes(0x42);
}
return header;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,31 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class PartitionTableEntry
{
public uint Offset { get; set; }
public uint Length { get; set; }
public static PartitionTableEntry Read(BinaryReader reader)
{
PartitionTableEntry entry = new PartitionTableEntry();
try
{
entry.Offset = reader.ReadUInt32();
entry.Length = reader.ReadUInt32();
return entry;
}
catch
{
return null;
}
}
public bool IsValid()
{
return Offset != 0 && Length != 0;
}
}
}

View File

@@ -1,32 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class StorageInfo
{
public byte[] ExtdataID = new byte[8];
public byte[] SystemSavedataIDs = new byte[8];
public byte[] StorageAccessibleUniqueIDs = new byte[8];
public byte[] FilesystemAccessInfo = new byte[7];
public byte OtherAttributes;
public static StorageInfo Read(BinaryReader reader)
{
StorageInfo si = new StorageInfo();
try
{
si.ExtdataID = reader.ReadBytes(8);
si.SystemSavedataIDs = reader.ReadBytes(8);
si.StorageAccessibleUniqueIDs = reader.ReadBytes(8);
si.FilesystemAccessInfo = reader.ReadBytes(7);
si.OtherAttributes = reader.ReadByte();
return si;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,49 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class SystemControlInfo
{
public char[] ApplicationTitle = new char[8];
public byte[] Reserved1 = new byte[5];
public byte Flag;
public byte[] RemasterVersion = new byte[2];
public CodeSetInfo TextCodesetInfo;
public uint StackSize;
public CodeSetInfo ReadOnlyCodeSetInfo;
public byte[] Reserved2 = new byte[4];
public CodeSetInfo DataCodeSetInfo;
public uint BSSSize;
public byte[][] DependencyModuleList = new byte[48][];
public SystemInfo SystemInfo;
public static SystemControlInfo Read(BinaryReader reader)
{
SystemControlInfo sci = new SystemControlInfo();
try
{
sci.ApplicationTitle = reader.ReadChars(8);
sci.Reserved1 = reader.ReadBytes(5);
sci.Flag = reader.ReadByte();
sci.RemasterVersion = reader.ReadBytes(2);
sci.TextCodesetInfo = CodeSetInfo.Read(reader);
sci.StackSize = reader.ReadUInt32();
sci.ReadOnlyCodeSetInfo = CodeSetInfo.Read(reader);
sci.Reserved2 = reader.ReadBytes(4);
sci.DataCodeSetInfo = CodeSetInfo.Read(reader);
sci.BSSSize = reader.ReadUInt32();
for (int i = 0; i < 48; i++)
sci.DependencyModuleList[i] = reader.ReadBytes(8);
sci.SystemInfo = SystemInfo.Read(reader);
return sci;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,28 +0,0 @@
using System.IO;
namespace ThreeDS.Headers
{
public class SystemInfo
{
public ulong SaveDataSize;
public byte[] JumpID = new byte[8];
public byte[] Reserved = new byte[0x30];
public static SystemInfo Read(BinaryReader reader)
{
SystemInfo si = new SystemInfo();
try
{
si.SaveDataSize = reader.ReadUInt64();
si.JumpID = reader.ReadBytes(8);
si.Reserved = reader.ReadBytes(0x30);
return si;
}
catch
{
return null;
}
}
}
}

View File

@@ -1,49 +0,0 @@
using System;
using System.IO;
namespace ThreeDS
{
class Program
{
public static void Main(string[] args)
{
if (args.Length < 2 || (args[0] != "encrypt" && args[0] != "decrypt"))
{
Console.WriteLine("Usage: 3dsdecrypt.exe (decrypt|encrypt) [-dev] <file|dir> ...");
return;
}
bool development = false;
int start = 1;
if (args[1] == "-dev")
{
development = true;
start = 2;
}
for (int i = start; i < args.Length; i++)
{
if (File.Exists(args[i]))
{
ThreeDSTool tool = new ThreeDSTool(args[i], development);
if (args[0] == "decrypt")
tool.Decrypt();
else if (args[0] == "encrypt")
tool.Encrypt();
}
else if (Directory.Exists(args[i]))
{
foreach (string file in Directory.EnumerateFiles(args[i], "*", SearchOption.AllDirectories))
{
ThreeDSTool tool = new ThreeDSTool(file, development);
if (args[0] == "decrypt")
tool.Decrypt();
else if (args[0] == "encrypt")
tool.Encrypt();
}
}
}
}
}
}

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("3DSDecrypt")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("3DSDecrypt")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("2e30006a-3c60-4576-a262-937b21c83c06")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,623 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Numerics;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using ThreeDS.Data;
using ThreeDS.Headers;
namespace ThreeDS
{
public class ThreeDSTool
{
private readonly string filename;
private readonly bool development;
public ThreeDSTool(string filename, bool development)
{
this.filename = filename;
this.development = development;
}
public void Decrypt()
{
if (!File.Exists(filename))
return;
Console.WriteLine(filename);
using (BinaryReader f = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
using (BinaryWriter g = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
{
NCSDHeader header = NCSDHeader.Read(f);
if (header == null)
{
Console.WriteLine("Error: Not a 3DS Rom!");
return;
}
// Iterate over all 8 NCCH partitions
for (int p = 0; p < 8; p++)
{
if (!header.PartitionsTable[p].IsValid())
{
Console.WriteLine("Partition {0} Not found... Skipping...", p);
continue;
}
// Seek to the beginning of the NCCH partition
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize), SeekOrigin.Begin);
NCCHHeader partitionHeader = NCCHHeader.Read(f);
if (partitionHeader == null)
{
Console.WriteLine("Partition {0} Unable to read NCCH header", p);
continue;
}
// Check if the 'NoCrypto' bit is set
if ((partitionHeader.Flags.BitMasks & BitMasks.NoCrypto) != 0)
{
Console.WriteLine("Partition {0:d}: Already Decrypted?...", p);
continue;
}
// PartitionID is used as IV joined with the content type.
byte[] plainIV = partitionHeader.PartitionId.Concat(Constants.PlainCounter).ToArray(); // Get the IV for plain sector (TitleID + Plain Counter)
byte[] exefsIV = partitionHeader.PartitionId.Concat(Constants.ExefsCounter).ToArray(); // Get the IV for ExeFS (TitleID + ExeFS Counter)
byte[] romfsIV = partitionHeader.PartitionId.Concat(Constants.RomfsCounter).ToArray(); // Get the IV for RomFS (TitleID + RomFS Counter)
BigInteger KeyX = 0;
BigInteger KeyX2C = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
BigInteger KeyY = new BigInteger(partitionHeader.RSA2048Signature.Take(16).Reverse().ToArray()); // KeyY is the first 16 bytes of the partition RSA-2048 SHA-256 signature
BigInteger NormalKey = 0;
BigInteger NormalKey2C = RotateLeft((RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
// Determine the Keys to be used
if ((partitionHeader.Flags.BitMasks & BitMasks.FixedCryptoKey) != 0)
{
NormalKey = 0x00;
NormalKey2C = 0x00;
if (p == 0)
Console.WriteLine("Encryption Method: Zero Key");
}
else
{
if (partitionHeader.Flags.CryptoMethod == CryptoMethod.Original)
{
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
if (p == 0)
Console.WriteLine("Encryption Method: Key 0x2C");
}
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.Seven)
{
KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25);
if (p == 0)
Console.WriteLine("Encryption Method: Key 0x25");
}
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.NineThree)
{
KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18);
if (p == 0)
Console.WriteLine("Encryption Method: Key 0x18");
}
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.NineSix)
{
KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B);
if (p == 0)
Console.WriteLine("Encryption Method: Key 0x1B");
}
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
}
// Decrypted extended header, if it exists
if (partitionHeader.ExtendedHeaderSizeInBytes > 0)
{
// Seek to the partition start and skip first part of the header
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
var str = BitConverter.ToString(plainIV).Replace("-", "");
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), plainIV));
Console.WriteLine("Partition {0} ExeFS: Decrypting: ExHeader", p);
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
g.Flush();
}
// Decrypt the ExeFS, if it exists
if (partitionHeader.ExeFSSizeInBytes > 0)
{
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIV));
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes((int)header.SectorSize)));
g.Flush();
Console.WriteLine("Partition {0} ExeFS: Decrypting: ExeFS Filename Table", p);
if (partitionHeader.Flags.CryptoMethod != CryptoMethod.Original)
{
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
ExeFSHeader exefsHeader = ExeFSHeader.Read(f);
if (exefsHeader != null)
{
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
{
if (!fileHeader.IsCodeBinary)
continue;
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10);
byte[] exefsIVWithOffsetForHeader = AddToByteArray(exefsIV, (int)ctroffset);
var exefsctrmode = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), exefsIVWithOffsetForHeader));
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffsetForHeader));
f.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
g.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
if (datalenM > 0)
{
for (int i = 0; i < datalenM; i++)
{
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024))));
g.Flush();
Console.Write("\rPartition {0} ExeFS: Decrypting: {1}... {2} / {3} mb...", p, fileHeader.FileName, i, datalenM + 1);
}
}
if (datalenB > 0)
{
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes((int)datalenB))));
g.Flush();
}
Console.Write("\rPartition {0} ExeFS: Decrypting: {1}... {2} / {3} mb... Done!\r\n", p, fileHeader.FileName, datalenM + 1, datalenM + 1);
}
}
}
// decrypt exefs
int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) / (1024 * 1024);
int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024);
int ctroffsetE = (int)(header.SectorSize / 0x10);
byte[] exefsIVWithOffset = AddToByteArray(exefsIV, ctroffsetE);
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffset));
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
if (exefsSizeM > 0)
{
for (int i = 0; i < exefsSizeM; i++)
{
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(1024 * 1024)));
g.Flush();
Console.Write("\rPartition {0} ExeFS: Decrypting: {1} / {2} mb", p, i, exefsSizeM + 1);
}
}
if (exefsSizeB > 0)
{
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(exefsSizeB)));
g.Flush();
}
Console.Write("\rPartition {0} ExeFS: Decrypting: {1} / {2} mb... Done!\r\n", p, exefsSizeM + 1, exefsSizeM + 1);
}
else
{
Console.WriteLine("Partition {0} ExeFS: No Data... Skipping...", p);
}
if (partitionHeader.RomFSOffsetInMediaUnits != 0)
{
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024);
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (1024 * 1024);
var romfsctrmode = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
romfsctrmode.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), romfsIV));
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
if (romfsSizeM > 0)
{
for (int i = 0; i < romfsSizeM; i++)
{
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024)));
g.Flush();
Console.Write("\rPartition {0} RomFS: Decrypting: {1} / {2} mb", p, i, romfsSizeM + 1);
}
}
if (romfsSizeB > 0)
{
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(romfsSizeB)));
g.Flush();
}
Console.Write("\rPartition {0} RomFS: Decrypting: {1} / {2} mb... Done!\r\n", p, romfsSizeM + 1, romfsSizeM + 1);
}
else
{
Console.WriteLine("Partition {0} RomFS: No Data... Skipping...", p);
}
// Write the new CryptoMethod
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin);
g.Write((byte)CryptoMethod.Original);
g.Flush();
// Write the new BitMasks flag
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18F, SeekOrigin.Begin);
BitMasks flag = partitionHeader.Flags.BitMasks;
flag = flag & (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
flag = (flag | BitMasks.NoCrypto);
g.Write((byte)flag);
g.Flush();
}
Console.WriteLine("Press Enter to Exit...");
Console.Read();
}
}
/// <summary>
/// Writes wrong header values
/// </summary>
public void Encrypt()
{
if (!File.Exists(filename))
return;
Console.WriteLine(filename);
using (BinaryReader f = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
using (BinaryWriter g = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
{
NCSDHeader header = NCSDHeader.Read(f);
if (header == null)
{
Console.WriteLine("Error: Not a 3DS Rom!");
return;
}
// Iterate over all 8 NCCH partitions
for (int p = 0; p < 8; p++)
{
if (!header.PartitionsTable[p].IsValid())
{
Console.WriteLine("Partition {0} Not found... Skipping...", p);
continue;
}
// Seek to the beginning of the NCCH partition
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize), SeekOrigin.Begin);
NCCHHeader partitionHeader = NCCHHeader.Read(f);
if (partitionHeader == null)
{
Console.WriteLine("Partition {0} Unable to read NCCH header", p);
continue;
}
// Get the backup flags
f.BaseStream.Seek(0x1188, SeekOrigin.Begin);
NCCHHeaderFlags backupFlags = NCCHHeaderFlags.Read(f);
// Check if the 'NoCrypto' bit is not set
if ((partitionHeader.Flags.BitMasks & BitMasks.NoCrypto) == 0)
{
Console.WriteLine("Partition {0:d}: Already Encrypted?...", p);
continue;
}
// PartitionID is used as IV joined with the content type.
byte[] plainIV = partitionHeader.PartitionId.Concat(Constants.PlainCounter).ToArray(); // Get the IV for plain sector (TitleID + Plain Counter)
byte[] exefsIV = partitionHeader.PartitionId.Concat(Constants.ExefsCounter).ToArray(); // Get the IV for ExeFS (TitleID + ExeFS Counter)
byte[] romfsIV = partitionHeader.PartitionId.Concat(Constants.RomfsCounter).ToArray(); // Get the IV for RomFS (TitleID + RomFS Counter)
BigInteger KeyX = 0;
BigInteger KeyX2C = Constants.KeyX0x2C;
BigInteger KeyY = new BigInteger(partitionHeader.RSA2048Signature.Take(16).Reverse().ToArray()); // KeyY is the first 16 bytes of the partition RSA-2048 SHA-256 signature
BigInteger NormalKey = 0;
BigInteger NormalKey2C = RotateLeft((RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
// Determine the Keys to be used
if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0)
{
NormalKey = 0x00;
NormalKey2C = 0x00;
if (p == 0)
Console.WriteLine("Encryption Method: Zero Key");
}
else
{
if (backupFlags.CryptoMethod == CryptoMethod.Original)
{
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
if (p == 0)
Console.WriteLine("Encryption Method: Key 0x2C");
}
else if (backupFlags.CryptoMethod == CryptoMethod.Seven)
{
KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25);
if (p == 0)
Console.WriteLine("Encryption Method: Key 0x25");
}
else if (backupFlags.CryptoMethod == CryptoMethod.NineThree)
{
KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18);
if (p == 0)
Console.WriteLine("Encryption Method: Key 0x18");
}
else if (backupFlags.CryptoMethod == CryptoMethod.NineSix)
{
KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B);
if (p == 0)
Console.WriteLine("Encryption Method: Key 0x1B");
}
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
}
// Encrypt extended header, if it exists
if (partitionHeader.ExtendedHeaderSizeInBytes > 0)
{
// Seek to the partition start and skip first part of the header
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
var str = BitConverter.ToString(plainIV).Replace("-", "");
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), plainIV));
Console.WriteLine("Partition {0} ExeFS: Encrypting: ExHeader", p);
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
g.Flush();
}
// Encrypt the ExeFS, if it exists
if (partitionHeader.ExeFSSizeInBytes > 0)
{
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIV));
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes((int)header.SectorSize)));
g.Flush();
Console.WriteLine("Partition {0} ExeFS: Encrypting: ExeFS Filename Table", p);
if (backupFlags.CryptoMethod != CryptoMethod.Original)
{
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
ExeFSHeader exefsHeader = ExeFSHeader.Read(f);
if (exefsHeader != null)
{
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
{
if (!fileHeader.IsCodeBinary)
continue;
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10);
byte[] exefsIVWithOffsetForHeader = AddToByteArray(exefsIV, (int)ctroffset);
var exefsctrmode = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), exefsIVWithOffsetForHeader));
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffsetForHeader));
f.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
g.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
if (datalenM > 0)
{
for (int i = 0; i < datalenM; i++)
{
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024))));
g.Flush();
Console.Write("\rPartition {0} ExeFS: Encrypting: {1}... {2} / {3} mb...", p, fileHeader.FileName, i, datalenM + 1);
}
}
if (datalenB > 0)
{
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes((int)datalenB))));
g.Flush();
}
Console.Write("\rPartition {0} ExeFS: Encrypting: {1}... {2} / {3} mb... Done!\r\n", p, fileHeader.FileName, datalenM + 1, datalenM + 1);
}
}
}
// decrypt exefs
int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) / (1024 * 1024);
int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024);
int ctroffsetE = (int)(header.SectorSize / 0x10);
byte[] exefsIVWithOffset = AddToByteArray(exefsIV, ctroffsetE);
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffset));
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
if (exefsSizeM > 0)
{
for (int i = 0; i < exefsSizeM; i++)
{
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(1024 * 1024)));
g.Flush();
Console.Write("\rPartition {0} ExeFS: Encrypting: {1} / {2} mb", p, i, exefsSizeM + 1);
}
}
if (exefsSizeB > 0)
{
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(exefsSizeB)));
g.Flush();
}
Console.Write("\rPartition {0} ExeFS: Encrypting: {1} / {2} mb... Done!\r\n", p, exefsSizeM + 1, exefsSizeM + 1);
}
else
{
Console.WriteLine("Partition {0} ExeFS: No Data... Skipping...", p);
}
if (partitionHeader.RomFSOffsetInMediaUnits != 0)
{
int romfsBlockSize = 16; // block size in mb
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (romfsBlockSize * (1024 * 1024));
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (romfsBlockSize * (1024 * 1024));
int romfsSizeTotalMb = (int)((partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024) + 1);
if (p > 0) // RomFS for partitions 1 and up always use Key0x2C
{
if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0) // except if using zero-key
{
NormalKey = 0x00;
}
else
{
KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
}
}
var romfsctrmode = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
romfsctrmode.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), romfsIV));
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
if (romfsSizeM > 0)
{
for (int i = 0; i < romfsSizeM; i++)
{
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(romfsBlockSize * 1024 * 1024)));
g.Flush();
Console.Write("\rPartition {0} RomFS: Encrypting: {1} / {2} mb", p, i * romfsBlockSize, romfsSizeTotalMb);
}
}
if (romfsSizeB > 0)
{
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(romfsSizeB)));
g.Flush();
}
Console.Write("\rPartition {0} RomFS: Encrypting: {1} / {2} mb... Done!\r\n", p, romfsSizeTotalMb, romfsSizeTotalMb);
}
else
{
Console.WriteLine("Partition {0} RomFS: No Data... Skipping...", p);
}
// Write the new CryptoMethod
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin);
if (p > 0)
{
g.Write((byte)CryptoMethod.Original); // For partitions 1 and up, set crypto-method to 0x00
g.Flush();
}
else
{
g.Write((byte)backupFlags.CryptoMethod); // If partition 0, restore crypto-method from backup flags
g.Flush();
}
// Write the new BitMasks flag
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18F, SeekOrigin.Begin);
BitMasks flag = partitionHeader.Flags.BitMasks;
flag = (flag & ((BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF));
flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupFlags.BitMasks);
g.Write((byte)flag);
g.Flush();
}
Console.WriteLine("Press Enter to Exit...");
Console.Read();
}
}
private static BigInteger RotateLeft(BigInteger val, int r_bits, int max_bits)
{
return (val << r_bits % max_bits) & (BigInteger.Pow(2, max_bits) - 1) | ((val & (BigInteger.Pow(2, max_bits) - 1)) >> (max_bits - (r_bits % max_bits)));
}
private static string ToBytes(int num)
{
string numstr = "";
int tmp = num;
while (numstr.Length < 16)
{
numstr += (char)(tmp & 0xFF);
tmp >>= 8;
}
return numstr;
}
private static byte[] AddToByteArray(byte[] input, int add)
{
int len = input.Length;
var bigint = new BigInteger(input.Reverse().ToArray());
bigint += add;
var arr = bigint.ToByteArray().Reverse().ToArray();
if (arr.Length < len)
{
byte[] temp = new byte[len];
for (int i = 0; i < (len - arr.Length); i++)
temp[i] = 0x00;
Array.Copy(arr, 0, temp, len - arr.Length, arr.Length);
arr = temp;
}
return arr;
}
private static byte[] TakeSixteen(BigInteger input)
{
var arr = input.ToByteArray().Take(16).Reverse().ToArray();
if (arr.Length < 16)
{
byte[] temp = new byte[16];
for (int i = 0; i < (16 - arr.Length); i++)
temp[i] = 0x00;
Array.Copy(arr, 0, temp, 16 - arr.Length, arr.Length);
arr = temp;
}
return arr;
}
}
}

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BouncyCastle" version="1.8.4" targetFramework="net461" />
</packages>

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright (c) 2018-2025 Matt Nadareski
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,89 @@
using System.IO;
using Newtonsoft.Json;
namespace NDecrypt.Core
{
internal class Configuration
{
#region DS-Specific Fields
/// <summary>
/// Encryption data taken from woodsec
/// </summary>
public string? NitroEncryptionData { get; set; }
#endregion
#region 3DS-Specific Fields
/// <summary>
/// AES Hardware Constant
/// </summary>
/// <remarks>generator</remarks>
public string? AESHardwareConstant { get; set; }
/// <summary>
/// KeyX 0x18 (New 3DS 9.3)
/// </summary>
/// <remarks>slot0x18KeyX</remarks>
public string? KeyX0x18 { get; set; }
/// <summary>
/// Dev KeyX 0x18 (New 3DS 9.3)
/// </summary>
public string? DevKeyX0x18 { get; set; }
/// <summary>
/// KeyX 0x1B (New 3DS 9.6)
/// </summary>
/// <remarks>slot0x1BKeyX</remarks>
public string? KeyX0x1B { get; set; }
/// <summary>
/// Dev KeyX 0x1B New 3DS 9.6)
/// </summary>
public string? DevKeyX0x1B { get; set; }
/// <summary>
/// KeyX 0x25 (> 7.x)
/// </summary>
/// <remarks>slot0x25KeyX</remarks>
public string? KeyX0x25 { get; set; }
/// <summary>
/// Dev KeyX 0x25 (> 7.x)
/// </summary>
public string? DevKeyX0x25 { get; set; }
/// <summary>
/// KeyX 0x2C (< 6.x)
/// </summary>
/// <remarks>slot0x2CKeyX</remarks>
public string? KeyX0x2C { get; set; }
/// <summary>
/// Dev KeyX 0x2C (< 6.x)
/// </summary>
public string? DevKeyX0x2C { get; set; }
#endregion
public static Configuration? Create(string path)
{
// Ensure the file exists
if (!File.Exists(path))
return null;
// Parse the configuration directly
try
{
string contents = File.ReadAllText(path);
return JsonConvert.DeserializeObject<Configuration?>(contents);
}
catch
{
return null;
}
}
}
}

168
NDecrypt.Core/DSTool.cs Normal file
View File

@@ -0,0 +1,168 @@
using System;
using System.IO;
using System.Text;
#if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER
using SabreTools.IO.Extensions;
#endif
using SabreTools.Serialization.Wrappers;
namespace NDecrypt.Core
{
public class DSTool : ITool
{
/// <summary>
/// Decryption args to use while processing
/// </summary>
private readonly DecryptArgs _decryptArgs;
public DSTool(DecryptArgs decryptArgs)
{
_decryptArgs = decryptArgs;
}
#region Encrypt
/// <inheritdoc/>
public bool EncryptFile(string input, string? output, bool force)
{
try
{
// If the output is provided, copy the input file
if (output is not null)
File.Copy(input, output, overwrite: true);
else
output = input;
// Open the output file for processing
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// Deserialize the cart information
var nitro = Nitro.Create(reader);
if (nitro is null)
{
Console.WriteLine("Error: Not a DS or DSi Rom!");
return false;
}
// Ensure the secure area was read
if (nitro.SecureArea is null)
{
Console.WriteLine("Error: Invalid secure area!");
return false;
}
// Encrypt the secure area
nitro.EncryptSecureArea(_decryptArgs.NitroEncryptionData, force);
// Write the encrypted secure area
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
writer.Seek(0x4000, SeekOrigin.Begin);
writer.Write(nitro.SecureArea);
writer.Flush();
return true;
}
catch
{
Console.WriteLine($"An error has occurred. {output} may be corrupted if it was partially processed.");
Console.WriteLine("Please check that the file was a valid DS or DSi file and try again.");
return false;
}
}
#endregion
#region Decrypt
/// <inheritdoc/>
public bool DecryptFile(string input, string? output, bool force)
{
try
{
// If the output is provided, copy the input file
if (output is not null)
File.Copy(input, output, overwrite: true);
else
output = input;
// Open the read and write on the same file for inplace processing
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// Deserialize the cart information
var nitro = Nitro.Create(reader);
if (nitro is null)
{
Console.WriteLine("Error: Not a DS or DSi Rom!");
return false;
}
// Ensure the secure area was read
if (nitro.SecureArea is null)
{
Console.WriteLine("Error: Invalid secure area!");
return false;
}
// Decrypt the secure area
nitro.DecryptSecureArea(_decryptArgs.NitroEncryptionData, force);
// Write the decrypted secure area
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
writer.Seek(0x4000, SeekOrigin.Begin);
writer.Write(nitro.SecureArea);
writer.Flush();
return true;
}
catch
{
Console.WriteLine($"An error has occurred. {output} may be corrupted if it was partially processed.");
Console.WriteLine("Please check that the file was a valid DS or DSi file and try again.");
return false;
}
}
#endregion
#region Info
/// <inheritdoc/>
public string? GetInformation(string filename)
{
try
{
// Open the file for reading
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// Deserialize the cart information
var cart = Nitro.Create(input);
if (cart?.Model is null)
return "Error: Not a DS/DSi cart image!";
// Get a string builder for the status
var sb = new StringBuilder();
sb.Append("\tSecure Area: ");
// Get the encryption status
bool? decrypted = cart.CheckIfDecrypted(out _);
if (decrypted is null)
sb.Append("Empty");
else if (decrypted == true)
sb.Append("Decrypted");
else
sb.Append("Encrypted");
// Return the status for the secure area
sb.Append(Environment.NewLine);
return sb.ToString();
}
catch (Exception ex)
{
Console.WriteLine(ex);
return null;
}
}
#endregion
}
}

View File

@@ -0,0 +1,391 @@
using System;
using System.IO;
using SabreTools.Hashing;
using SabreTools.IO.Encryption;
using SabreTools.IO.Extensions;
namespace NDecrypt.Core
{
public class DecryptArgs
{
#region Common Fields
/// <summary>
/// Represents if all of the keys have been initialized properly
/// </summary>
public bool? IsReady { get; private set; }
#endregion
#region DS-Specific Fields
/// <summary>
/// Blowfish Table
/// </summary>
public byte[] NitroEncryptionData { get; private set; } = [];
#endregion
#region 3DS-Specific Fields
/// <summary>
/// AES Hardware Constant
/// </summary>
public byte[] AESHardwareConstant { get; private set; } = [];
/// <summary>
/// KeyX 0x18 (New 3DS 9.3)
/// </summary>
public byte[] KeyX0x18 { get; private set; } = [];
/// <summary>
/// Dev KeyX 0x18 (New 3DS 9.3)
/// </summary>
public byte[] DevKeyX0x18 { get; private set; } = [];
/// <summary>
/// KeyX 0x1B (New 3DS 9.6)
/// </summary>
public byte[] KeyX0x1B { get; private set; } = [];
/// <summary>
/// Dev KeyX 0x1B New 3DS 9.6)
/// </summary>
public byte[] DevKeyX0x1B { get; private set; } = [];
/// <summary>
/// KeyX 0x25 (> 7.x)
/// </summary>
public byte[] KeyX0x25 { get; private set; } = [];
/// <summary>
/// Dev KeyX 0x25 (> 7.x)
/// </summary>
public byte[] DevKeyX0x25 { get; private set; } = [];
/// <summary>
/// KeyX 0x2C (< 6.x)
/// </summary>
public byte[] KeyX0x2C { get; private set; } = [];
/// <summary>
/// Dev KeyX 0x2C (< 6.x)
/// </summary>
public byte[] DevKeyX0x2C { get; private set; } = [];
#endregion
#region Internal Test Values
/// <summary>
/// Expected hash for NitroEncryptionData
/// </summary>
private static readonly byte[] ExpectedNitroSha512Hash =
[
0x1A, 0xD6, 0x40, 0x21, 0xFC, 0x3D, 0x1A, 0x9A,
0x9B, 0xC0, 0x88, 0x8E, 0x2E, 0x68, 0xDE, 0x4E,
0x8A, 0x60, 0x6B, 0x86, 0x63, 0x22, 0xD2, 0xC7,
0xC6, 0xD7, 0xD6, 0xCE, 0x65, 0xF5, 0xBA, 0xA7,
0xEA, 0x69, 0x63, 0x7E, 0xC9, 0xE4, 0x57, 0x7B,
0x01, 0xFD, 0xCE, 0xC2, 0x26, 0x3B, 0xD9, 0x0D,
0x84, 0x57, 0xC2, 0x00, 0xB8, 0x56, 0x9F, 0xE5,
0x56, 0xDA, 0x8D, 0xDE, 0x84, 0xB8, 0x8E, 0xE4,
];
/// <summary>
/// Initial value for key validation tests
/// </summary>
private static readonly byte[] TestIV =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
/// <summary>
/// Pattern to use for key validation tests
/// </summary>
private static readonly byte[] TestPattern =
[
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
];
/// <summary>
/// Expected output value for KeyX0x18
/// </summary>
private static readonly byte[] ExpectedKeyX0x18 =
[
0x06, 0xF1, 0xB2, 0x3B, 0x12, 0xAD, 0x80, 0xC1,
0x13, 0xC6, 0x18, 0x3D, 0x27, 0xB8, 0xB9, 0x95,
0x49, 0x73, 0x59, 0x82, 0xEF, 0xFE, 0x16, 0x48,
0x91, 0x2A, 0x89, 0x55, 0x9A, 0xDC, 0x3C, 0xA0,
0x84, 0x46, 0x14, 0xE0, 0x16, 0x59, 0x8E, 0x4F,
0xC2, 0x6C, 0x52, 0xA4, 0x7D, 0xAD, 0x4F, 0x23,
0xF1, 0xC6, 0x99, 0x44, 0x39, 0xB7, 0x42, 0xF0,
0x1F, 0xBB, 0x02, 0xF6, 0x0A, 0x8A, 0xC2, 0x9A,
];
/// <summary>
/// Expected output value for DevKeyX0x18
/// </summary>
private static readonly byte[] ExpectedDevKeyX0x18 =
[
0x99, 0x6E, 0x3C, 0x54, 0x97, 0x3C, 0xEA, 0xE8,
0xBA, 0xAE, 0x18, 0x5C, 0x93, 0x27, 0x65, 0x50,
0xF6, 0x6D, 0x67, 0xD7, 0xEF, 0xBD, 0x7C, 0xCB,
0x8A, 0xC1, 0x1A, 0x54, 0xFC, 0x3B, 0x8B, 0x3A,
0x0E, 0xE5, 0xEF, 0x27, 0x4A, 0x73, 0x7E, 0x0A,
0x2E, 0x2E, 0x9D, 0xAF, 0x6C, 0x03, 0xF2, 0x91,
0xC4, 0xFA, 0x73, 0xFD, 0x6B, 0xA0, 0x07, 0xD4,
0x75, 0x5B, 0x6F, 0x2E, 0x8B, 0x68, 0x4C, 0xD1,
];
/// <summary>
/// Expected output value for KeyX0x1B
/// </summary>
private static readonly byte[] ExpectedKeyX0x1B =
[
0x0A, 0xE4, 0x79, 0x02, 0x1B, 0xFA, 0x25, 0x4B,
0x2D, 0x92, 0x4F, 0xA8, 0x41, 0x59, 0xCE, 0x10,
0x09, 0xE6, 0x08, 0x61, 0x23, 0xC7, 0xD2, 0x30,
0x84, 0x37, 0xD5, 0x49, 0x42, 0x94, 0xB2, 0x70,
0x6A, 0xF3, 0x75, 0xB0, 0x1F, 0x4F, 0xA1, 0xCE,
0x03, 0xA2, 0x6A, 0x19, 0x5D, 0x32, 0x0D, 0xB5,
0x79, 0xCD, 0xFD, 0xF0, 0xDE, 0x49, 0x26, 0x2D,
0x29, 0x36, 0x30, 0x69, 0x8B, 0x45, 0xE1, 0xFC,
];
/// <summary>
/// Expected output value for DevKeyX0x1B
/// </summary>
private static readonly byte[] ExpectedDevKeyX0x1B =
[
0x16, 0x4F, 0xD9, 0x58, 0xC9, 0x20, 0xB3, 0xED,
0xC4, 0xEB, 0x57, 0x39, 0x10, 0xEF, 0xA8, 0xCC,
0xE5, 0x49, 0xBF, 0x52, 0x10, 0xA9, 0xCC, 0xE1,
0x65, 0x3B, 0x2D, 0x51, 0x45, 0xFB, 0x60, 0x52,
0x3E, 0x29, 0xEB, 0xEB, 0x3F, 0xF2, 0x76, 0x08,
0x00, 0x05, 0x7F, 0x64, 0x29, 0x4A, 0x17, 0x22,
0x56, 0x7F, 0x49, 0x94, 0x1A, 0x8C, 0x56, 0x35,
0x38, 0xBE, 0xA4, 0x2E, 0x58, 0xD3, 0x81, 0x8C,
];
/// <summary>
/// Expected output value for KeyX0x25
/// </summary>
private static readonly byte[] ExpectedKeyX0x25 =
[
0x37, 0xBC, 0x73, 0xD6, 0xEE, 0x73, 0xE0, 0x94,
0x42, 0x84, 0x74, 0xE5, 0xD8, 0xFB, 0x5F, 0x65,
0xF4, 0xCF, 0x2E, 0xC1, 0x43, 0x48, 0x6C, 0xAA,
0xC8, 0xF9, 0x96, 0xE6, 0x33, 0xDD, 0xE7, 0xBF,
0xD2, 0x21, 0x89, 0x39, 0x13, 0xD1, 0xEC, 0xCA,
0x1D, 0x5D, 0x1F, 0x77, 0x95, 0xD2, 0x8B, 0x27,
0x92, 0x79, 0xC5, 0x1D, 0x72, 0xA7, 0x28, 0x57,
0x41, 0x0E, 0x46, 0xB8, 0x80, 0x7B, 0x7C, 0x0D,
];
/// <summary>
/// Expected output value for DevKeyX0x25
/// </summary>
private static readonly byte[] ExpectedDevKeyX0x25 =
[
0x71, 0x65, 0x30, 0xF2, 0x68, 0xEC, 0x65, 0x0A,
0x8C, 0x9E, 0xC5, 0x5A, 0xFA, 0x37, 0x8E, 0xDA,
0x7B, 0x58, 0x3B, 0x66, 0x7C, 0x9D, 0x16, 0xD9,
0x2D, 0x8F, 0xCF, 0x04, 0x66, 0x7F, 0x27, 0x41,
0xBF, 0x5F, 0x1E, 0x11, 0x4C, 0xD6, 0xB9, 0x0A,
0xC5, 0x42, 0xCF, 0x2B, 0x87, 0x6B, 0xD4, 0x72,
0x4D, 0x9C, 0x29, 0x2E, 0xF8, 0xB0, 0x6F, 0x22,
0x35, 0x5B, 0x96, 0x83, 0xD1, 0xE4, 0x5E, 0xDB,
];
/// <summary>
/// Expected output value for KeyX0x2C
/// </summary>
private static readonly byte[] ExpectedKeyX0x2C =
[
0xAE, 0x44, 0x20, 0xDB, 0xA5, 0x96, 0xDC, 0xF3,
0xD8, 0x23, 0x9E, 0x3C, 0x44, 0x73, 0x3D, 0xCD,
0x07, 0xD5, 0xF8, 0xD0, 0xC6, 0xB3, 0x5A, 0x80,
0xB5, 0x5A, 0x55, 0x30, 0x5D, 0x4A, 0xBE, 0x61,
0xBF, 0xEF, 0x64, 0x17, 0x28, 0xD6, 0x26, 0x52,
0x42, 0x4D, 0x8F, 0x1C, 0xBC, 0x63, 0xD3, 0x91,
0x7D, 0xA6, 0x4F, 0xAF, 0x26, 0x38, 0x60, 0xEE,
0x79, 0x92, 0x2F, 0xD8, 0xCA, 0x4E, 0xE7, 0xEC,
];
/// <summary>
/// Expected output value for DevKeyX0x2C
/// </summary>
private static readonly byte[] ExpectedDevKeyX0x2C =
[
0x5F, 0x73, 0xD5, 0x9A, 0x67, 0xFF, 0x8C, 0x12,
0x31, 0x58, 0x0B, 0x58, 0x46, 0xFE, 0x05, 0x16,
0x92, 0xE4, 0x84, 0x06, 0x18, 0x9B, 0x58, 0x91,
0xE7, 0xF8, 0xCD, 0xA9, 0x95, 0xAC, 0x07, 0xCD,
0x43, 0x20, 0x7A, 0x8C, 0xCC, 0xAB, 0x48, 0x50,
0x29, 0x2F, 0x96, 0x73, 0xB0, 0xD9, 0xE5, 0xCB,
0xE6, 0x9A, 0x0D, 0xF7, 0xD0, 0x1E, 0xC2, 0xEC,
0xC1, 0xE2, 0x8E, 0xEE, 0x89, 0xB9, 0xB1, 0x97,
];
#endregion
/// <summary>
/// Setup all of the necessary constants
/// </summary>
/// <param name="keyfile">Path to the keyfile</param>
public DecryptArgs(string? config)
{
if (config is null || !File.Exists(config))
{
IsReady = false;
return;
}
// Try to read the configuration file
var configObj = Configuration.Create(config);
if (configObj is null)
{
IsReady = false;
return;
}
// Set the fields from the configuration
NitroEncryptionData = configObj.NitroEncryptionData.FromHexString() ?? [];
AESHardwareConstant = configObj.AESHardwareConstant.FromHexString() ?? [];
KeyX0x18 = configObj.KeyX0x18.FromHexString() ?? [];
KeyX0x1B = configObj.KeyX0x1B.FromHexString() ?? [];
KeyX0x25 = configObj.KeyX0x25.FromHexString() ?? [];
KeyX0x2C = configObj.KeyX0x2C.FromHexString() ?? [];
DevKeyX0x18 = configObj.DevKeyX0x18.FromHexString() ?? [];
DevKeyX0x1B = configObj.DevKeyX0x1B.FromHexString() ?? [];
DevKeyX0x25 = configObj.DevKeyX0x25.FromHexString() ?? [];
DevKeyX0x2C = configObj.DevKeyX0x2C.FromHexString() ?? [];
IsReady = true;
ValidateKeys();
}
/// <summary>
/// Validate that all keys provided are going to be valid
/// </summary>
/// <remarks>Does not know what the keys are, just the result</remarks>
private void ValidateKeys()
{
// NitroEncryptionData
if (NitroEncryptionData.Length > 0)
{
byte[]? actual = HashTool.GetByteArrayHashArray(NitroEncryptionData, HashType.SHA512);
if (actual is null || !actual.EqualsExactly(ExpectedNitroSha512Hash))
{
Console.WriteLine($"NitroEncryptionData invalid value, disabling...");
NitroEncryptionData = [];
}
}
// KeyX0x18
if (KeyX0x18.Length > 0)
{
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x18, TestIV);
byte[] actual = cipher.ProcessBytes(TestPattern);
if (!actual.EqualsExactly(ExpectedKeyX0x18))
{
Console.WriteLine($"KeyX0x18 invalid value, disabling...");
KeyX0x18 = [];
}
}
// DevKeyX0x18
if (DevKeyX0x18.Length > 0)
{
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x18, TestIV);
byte[] actual = cipher.ProcessBytes(TestPattern);
if (!actual.EqualsExactly(ExpectedDevKeyX0x18))
{
Console.WriteLine($"DevKeyX0x18 invalid value, disabling...");
DevKeyX0x18 = [];
}
}
// KeyX0x1B
if (KeyX0x1B.Length > 0)
{
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x1B, TestIV);
byte[] actual = cipher.ProcessBytes(TestPattern);
if (!actual.EqualsExactly(ExpectedKeyX0x1B))
{
Console.WriteLine($"KeyX0x1B invalid value, disabling...");
KeyX0x1B = [];
}
}
// DevKeyX0x1B
if (DevKeyX0x1B.Length > 0)
{
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x1B, TestIV);
byte[] actual = cipher.ProcessBytes(TestPattern);
if (!actual.EqualsExactly(ExpectedDevKeyX0x1B))
{
Console.WriteLine($"DevKeyX0x1B invalid value, disabling...");
DevKeyX0x1B = [];
}
}
// KeyX0x25
if (KeyX0x25.Length > 0)
{
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x25, TestIV);
byte[] actual = cipher.ProcessBytes(TestPattern);
if (!actual.EqualsExactly(ExpectedKeyX0x25))
{
Console.WriteLine($"KeyX0x25 invalid value, disabling...");
KeyX0x25 = [];
}
}
// DevKeyX0x25
if (DevKeyX0x25.Length > 0)
{
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x25, TestIV);
byte[] actual = cipher.ProcessBytes(TestPattern);
if (!actual.EqualsExactly(ExpectedDevKeyX0x25))
{
Console.WriteLine($"DevKeyX0x25 invalid value, disabling...");
DevKeyX0x25 = [];
}
}
// KeyX0x2C
if (KeyX0x2C.Length > 0)
{
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x2C, TestIV);
byte[] actual = cipher.ProcessBytes(TestPattern);
if (!actual.EqualsExactly(ExpectedKeyX0x2C))
{
Console.WriteLine($"KeyX0x2C invalid value, disabling...");
KeyX0x2C = [];
}
}
// DevKeyX0x2C
if (DevKeyX0x2C.Length > 0)
{
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x2C, TestIV);
byte[] actual = cipher.ProcessBytes(TestPattern);
if (!actual.EqualsExactly(ExpectedDevKeyX0x2C))
{
Console.WriteLine($"DevKeyX0x2C invalid value, disabling...");
DevKeyX0x2C = [];
}
}
}
}
}

32
NDecrypt.Core/ITool.cs Normal file
View File

@@ -0,0 +1,32 @@
namespace NDecrypt.Core
{
public interface ITool
{
/// <summary>
/// Attempts to encrypt an input file
/// </summary>
/// <param name="input">Name of the file to encrypt</param>
/// <param name="output">Optional name of the file to write to</param>
/// <param name="force">Indicates if the operation should be forced</param>
/// <returns>True if the file could be encrypted, false otherwise</returns>
/// <remarks>If an output filename is not provided, the input file will be overwritten</remarks>
public bool EncryptFile(string input, string? output, bool force);
/// <summary>
/// Attempts to decrypt an input file
/// </summary>
/// <param name="input">Name of the file to decrypt</param>
/// <param name="output">Optional name of the file to write to</param>
/// <param name="force">Indicates if the operation should be forced</param>
/// <returns>True if the file could be decrypted, false otherwise</returns>
/// <remarks>If an output filename is not provided, the input file will be overwritten</remarks>
public bool DecryptFile(string input, string? output, bool force);
/// <summary>
/// Attempts to get information on an input file
/// </summary>
/// <param name="filename">Name of the file get information on</param>
/// <returns>String representing the info, null on error</returns>
public string? GetInformation(string filename);
}
}

View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<IncludeSymbols>true</IncludeSymbols>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>0.5.0</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Common code for all NDecrypt processors</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2025</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SabreTools.Hashing" Version="[1.6.0]" />
<PackageReference Include="SabreTools.IO" Version="[1.9.0]" />
<PackageReference Include="SabreTools.Serialization" Version="[2.2.1]" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,138 @@
using System;
using SabreTools.Data.Models.N3DS;
using SabreTools.IO.Extensions;
namespace NDecrypt.Core
{
/// <summary>
/// Set of all keys associated with a partition
/// </summary>
public class PartitionKeys
{
public byte[] KeyX { get; private set; }
public byte[] KeyX2C { get; }
public byte[] KeyY { get; }
public byte[] NormalKey { get; private set; }
public byte[] NormalKey2C { get; }
/// <summary>
/// Decryption args to use while processing
/// </summary>
private readonly DecryptArgs _decryptArgs;
/// <summary>
/// Indicates if development images are expected
/// </summary>
private readonly bool _development;
/// <summary>
/// Create a new set of keys for a given partition
/// </summary>
/// <param name="args">Decryption args representing available keys</param>
/// <param name="signature">RSA-2048 signature from the partition</param>
/// <param name="masks">BitMasks from the partition or backup header</param>
/// <param name="method">CryptoMethod from the partition or backup header</param>
/// <param name="development">Determine if development keys are used</param>
public PartitionKeys(DecryptArgs args, byte[]? signature, BitMasks masks, CryptoMethod method, bool development)
{
// Validate inputs
if (args.IsReady != true)
throw new InvalidOperationException($"{nameof(args)} must be initialized before use");
if (signature is not null && signature.Length < 16)
throw new ArgumentOutOfRangeException(nameof(signature), $"{nameof(signature)} must be at least 16 bytes");
// Set fields for future use
_decryptArgs = args;
_development = development;
// Set the standard KeyX values
KeyX = new byte[16];
KeyX2C = development ? args.DevKeyX0x2C : args.KeyX0x2C;
// Backup headers can't have a KeyY value set
KeyY = new byte[16];
if (signature is not null)
Array.Copy(signature, KeyY, 16);
// Set the standard normal key values
NormalKey = new byte[16];
NormalKey2C = KeyX2C.RotateLeft(2);
NormalKey2C = NormalKey2C.Xor(KeyY);
NormalKey2C = NormalKey2C.Add(add: args.AESHardwareConstant);
NormalKey2C = NormalKey2C.RotateLeft(87);
// Special case for zero-key
#if NET20 || NET35
if ((masks & BitMasks.FixedCryptoKey) > 0)
#else
if (masks.HasFlag(BitMasks.FixedCryptoKey))
#endif
{
Console.WriteLine("Encryption Method: Zero Key");
NormalKey = new byte[16];
NormalKey2C = new byte[16];
return;
}
// Set KeyX values based on crypto method
switch (method)
{
case CryptoMethod.Original:
Console.WriteLine("Encryption Method: Key 0x2C");
KeyX = development ? args.DevKeyX0x2C : args.KeyX0x2C;
break;
case CryptoMethod.Seven:
Console.WriteLine("Encryption Method: Key 0x25");
KeyX = development ? args.DevKeyX0x25 : args.KeyX0x25;
break;
case CryptoMethod.NineThree:
Console.WriteLine("Encryption Method: Key 0x18");
KeyX = development ? args.DevKeyX0x18 : args.KeyX0x18;
break;
case CryptoMethod.NineSix:
Console.WriteLine("Encryption Method: Key 0x1B");
KeyX = development ? args.DevKeyX0x1B : args.KeyX0x1B;
break;
}
// Set the normal key based on the new KeyX value
NormalKey = KeyX.RotateLeft(2);
NormalKey = NormalKey.Xor(KeyY);
NormalKey = NormalKey.Add(args.AESHardwareConstant);
NormalKey = NormalKey.RotateLeft(87);
}
/// <summary>
/// Set RomFS values based on the bit masks
/// </summary>
public void SetRomFSValues(BitMasks masks)
{
// NormalKey has a constant value for zero-key
#if NET20 || NET35
if ((masks & BitMasks.FixedCryptoKey) > 0)
#else
if (masks.HasFlag(BitMasks.FixedCryptoKey))
#endif
{
NormalKey = new byte[16];
return;
}
// Encrypting RomFS for partitions 1 and up always use Key0x2C
KeyX = _development ? _decryptArgs.DevKeyX0x2C : _decryptArgs.KeyX0x2C;
NormalKey = KeyX.RotateLeft(2);
NormalKey = NormalKey.Xor(KeyY);
NormalKey = NormalKey.Add(_decryptArgs.AESHardwareConstant);
NormalKey = NormalKey.RotateLeft(87);
}
}
}

View File

@@ -0,0 +1,929 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Data.Models.N3DS;
using SabreTools.IO.Encryption;
using SabreTools.IO.Extensions;
using SabreTools.Serialization.Wrappers;
using static SabreTools.Data.Models.N3DS.Constants;
namespace NDecrypt.Core
{
public class ThreeDSTool : ITool
{
/// <summary>
/// Decryption args to use while processing
/// </summary>
private readonly DecryptArgs _decryptArgs;
/// <summary>
/// Indicates if development images are expected
/// </summary>
private readonly bool _development;
/// <summary>
/// Set of all partition keys
/// </summary>
private readonly PartitionKeys[] _keysMap = new PartitionKeys[8];
public ThreeDSTool(bool development, DecryptArgs decryptArgs)
{
_development = development;
_decryptArgs = decryptArgs;
}
#region Decrypt
/// <inheritdoc/>
public bool DecryptFile(string input, string? output, bool force)
{
// Ensure the constants are all set
if (_decryptArgs.IsReady != true)
{
Console.WriteLine("Could not read keys. Please make sure the file exists and try again.");
return false;
}
try
{
// If the output is provided, copy the input file
if (output is not null)
File.Copy(input, output, overwrite: true);
else
output = input;
// Open the output file for processing
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
// Deserialize the cart information
var cart = N3DS.Create(reader);
if (cart?.Model is null)
{
Console.WriteLine("Error: Not a 3DS cart image!");
return false;
}
// Decrypt all 8 NCCH partitions
DecryptAllPartitions(cart, force, reader, writer);
return true;
}
catch
{
Console.WriteLine($"An error has occurred. {output} may be corrupted if it was partially processed.");
Console.WriteLine("Please check that the file was a valid 3DS or New 3DS cart image and try again.");
return false;
}
}
/// <summary>
/// Decrypt all partitions in the partition table of an NCSD header
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="force">Indicates if the operation should be forced</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private void DecryptAllPartitions(N3DS cart, bool force, Stream reader, Stream writer)
{
// Check the partitions table
if (cart.PartitionsTable is null || cart.Partitions is null)
{
Console.WriteLine("Invalid partitions table!");
return;
}
// Iterate over all 8 NCCH partitions
for (int p = 0; p < 8; p++)
{
var partition = cart.Partitions[p];
if (partition is null || partition.MagicID != NCCHMagicNumber)
{
Console.WriteLine($"Partition {p} Not found... Skipping...");
continue;
}
// Check the partition has data
var partitionEntry = cart.PartitionsTable[p];
if (partitionEntry is null || partitionEntry.Length == 0)
{
Console.WriteLine($"Partition {p} No data... Skipping...");
continue;
}
// Decrypt the partition, if possible
if (ShouldDecryptPartition(cart, p, force))
DecryptPartition(cart, p, reader, writer);
}
}
/// <summary>
/// Determine if the current partition should be decrypted
/// </summary>s
private static bool ShouldDecryptPartition(N3DS cart, int index, bool force)
{
// If we're forcing the operation, tell the user
if (force)
{
Console.WriteLine($"Partition {index} is not verified due to force flag being set.");
return true;
}
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
else if (cart.PossiblyDecrypted(index))
{
Console.WriteLine($"Partition {index}: Already Decrypted?...");
return false;
}
// By default, it passes
return true;
}
/// <summary>
/// Decrypt a single partition
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private void DecryptPartition(N3DS cart, int index, Stream reader, Stream writer)
{
// Determine the keys needed for this partition
SetDecryptionKeys(cart, index);
// Decrypt the parts of the partition
DecryptExtendedHeader(cart, index, reader, writer);
DecryptExeFS(cart, index, reader, writer);
DecryptRomFS(cart, index, reader, writer);
// Update the flags
UpdateDecryptCryptoAndMasks(cart, index, writer);
}
/// <summary>
/// Determine the set of keys to be used for decryption
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
private void SetDecryptionKeys(N3DS cart, int index)
{
// Get the partition
var partition = cart.Partitions?[index];
if (partition?.Flags is null)
return;
// Get partition-specific values
byte[]? rsaSignature = partition.RSA2048Signature;
BitMasks masks = cart.GetBitMasks(index);
CryptoMethod method = cart.GetCryptoMethod(index);
// Get the partition keys
_keysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
}
/// <summary>
/// Decrypt the extended header, if it exists
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private bool DecryptExtendedHeader(N3DS cart, int index, Stream reader, Stream writer)
{
// Get required offsets
uint partitionOffset = cart.GetPartitionOffset(index);
if (partitionOffset == 0 || partitionOffset > reader.Length)
{
Console.WriteLine($"Partition {index} No Data... Skipping...");
return false;
}
uint extHeaderSize = cart.GetExtendedHeaderSize(index);
if (extHeaderSize == 0)
{
Console.WriteLine($"Partition {index} No Extended Header... Skipping...");
return false;
}
// Seek to the extended header
reader.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
writer.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
Console.WriteLine($"Partition {index}: Decrypting - ExHeader");
// Create the Plain AES cipher for this partition
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
// Process the extended header
AESCTR.PerformOperation(Constants.CXTExtendedDataHeaderLength, cipher, reader, writer, null);
#if NET6_0_OR_GREATER
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
reader.Seek(0, SeekOrigin.Begin);
#endif
writer.Flush();
return true;
}
/// <summary>
/// Decrypt the ExeFS, if it exists
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private bool DecryptExeFS(N3DS cart, int index, Stream reader, Stream writer)
{
// Validate the ExeFS
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > reader.Length)
{
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
return false;
}
uint exeFsSize = cart.GetExeFSSize(index);
if (exeFsSize == 0)
{
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
return false;
}
// Decrypt the filename table
DecryptExeFSFilenameTable(cart, index, reader, writer);
// For all but the original crypto method, process each of the files in the table
if (cart.GetCryptoMethod(index) != CryptoMethod.Original)
DecryptExeFSFileEntries(cart, index, reader, writer);
// Get the ExeFS files offset
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
// Seek to the ExeFS
reader.Seek(exeFsFilesOffset, SeekOrigin.Begin);
writer.Seek(exeFsFilesOffset, SeekOrigin.Begin);
// Create the ExeFS AES cipher for this partition
uint ctroffsetE = cart.MediaUnitSize / 0x10;
byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE);
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
// Setup and perform the decryption
exeFsSize -= cart.MediaUnitSize;
AESCTR.PerformOperation(exeFsSize,
cipher,
reader,
writer,
s => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {s}"));
return true;
}
/// <summary>
/// Decrypt the ExeFS Filename Table
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private void DecryptExeFSFilenameTable(N3DS cart, int index, Stream reader, Stream writer)
{
// Get ExeFS offset
uint exeFsOffset = cart.GetExeFSOffset(index);
if (exeFsOffset == 0 || exeFsOffset > reader.Length)
{
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
return;
}
// Seek to the ExeFS header
reader.Seek(exeFsOffset, SeekOrigin.Begin);
writer.Seek(exeFsOffset, SeekOrigin.Begin);
Console.WriteLine($"Partition {index} ExeFS: Decrypting - ExeFS Filename Table");
// Create the ExeFS AES cipher for this partition
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
// Process the filename table
byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize);
byte[] processedBytes = cipher.ProcessBytes(readBytes);
writer.Write(processedBytes);
#if NET6_0_OR_GREATER
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
reader.Seek(0, SeekOrigin.Begin);
#endif
writer.Flush();
}
/// <summary>
/// Decrypt the ExeFS file entries
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private void DecryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer)
{
if (cart.ExeFSHeaders is null || index < 0 || index > cart.ExeFSHeaders.Length)
{
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
return;
}
// Reread the decrypted ExeFS header
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
reader.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
cart.ExeFSHeaders[index] = SabreTools.Serialization.Readers.N3DS.ParseExeFSHeader(reader);
// Get the ExeFS header
var exeFsHeader = cart.ExeFSHeaders[index];
if (exeFsHeader?.FileHeaders is null)
{
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
return;
}
// Get the ExeFS files offset
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
// Loop through and process all headers
for (int i = 0; i < exeFsHeader.FileHeaders.Length; i++)
{
// Only attempt to process code binary files
if (!cart.IsCodeBinary(index, i))
continue;
// Get the file header
var fileHeader = exeFsHeader.FileHeaders[i];
if (fileHeader is null)
continue;
// Create the ExeFS AES ciphers for this partition
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
byte[] exefsIVWithOffsetForHeader = cart.ExeFSIV(index).Add(ctroffset);
var firstCipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader);
var secondCipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
// Seek to the file entry
reader.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
writer.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
// Setup and perform the encryption
AESCTR.PerformOperation(fileHeader.FileSize,
firstCipher,
secondCipher,
reader,
writer,
s => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {fileHeader.FileName}...{s}"));
}
}
/// <summary>
/// Decrypt the RomFS, if it exists
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private bool DecryptRomFS(N3DS cart, int index, Stream reader, Stream writer)
{
// Validate the RomFS
uint romFsOffset = cart.GetRomFSOffset(index);
if (romFsOffset == 0 || romFsOffset > reader.Length)
{
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
return false;
}
uint romFsSize = cart.GetRomFSSize(index);
if (romFsSize == 0)
{
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
return false;
}
// Seek to the RomFS
reader.Seek(romFsOffset, SeekOrigin.Begin);
writer.Seek(romFsOffset, SeekOrigin.Begin);
// Create the RomFS AES cipher for this partition
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
// Setup and perform the decryption
AESCTR.PerformOperation(romFsSize,
cipher,
reader,
writer,
s => Console.WriteLine($"\rPartition {index} RomFS: Decrypting - {s}"));
return true;
}
/// <summary>
/// Update the CryptoMethod and BitMasks for the decrypted partition
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="writer">Stream representing the output</param>
private static void UpdateDecryptCryptoAndMasks(N3DS cart, int index, Stream writer)
{
// Get required offsets
uint partitionOffset = cart.GetPartitionOffset(index);
// Seek to the CryptoMethod location
writer.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
// Write the new CryptoMethod
writer.Write((byte)CryptoMethod.Original);
writer.Flush();
// Seek to the BitMasks location
writer.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
// Write the new BitMasks flag
BitMasks flag = cart.GetBitMasks(index);
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
flag |= BitMasks.NoCrypto;
writer.Write((byte)flag);
writer.Flush();
}
#endregion
#region Encrypt
/// <inheritdoc/>
public bool EncryptFile(string input, string? output, bool force)
{
// Ensure the constants are all set
if (_decryptArgs.IsReady != true)
{
Console.WriteLine("Could not read keys. Please make sure the file exists and try again.");
return false;
}
try
{
// If the output is provided, copy the input file
if (output is not null)
File.Copy(input, output, overwrite: true);
else
output = input;
// Open the output file for processing
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
// Deserialize the cart information
var cart = N3DS.Create(reader);
if (cart?.Model is null)
{
Console.WriteLine("Error: Not a 3DS cart image!");
return false;
}
// Encrypt all 8 NCCH partitions
EncryptAllPartitions(cart, force, reader, writer);
return true;
}
catch
{
Console.WriteLine($"An error has occurred. {output} may be corrupted if it was partially processed.");
Console.WriteLine("Please check that the file was a valid 3DS or New 3DS cart image and try again.");
return false;
}
}
/// <summary>
/// Encrypt all partitions in the partition table of an NCSD header
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="force">Indicates if the operation should be forced</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private void EncryptAllPartitions(N3DS cart, bool force, Stream reader, Stream writer)
{
// Check the partitions table
if (cart.PartitionsTable is null || cart.Partitions is null)
{
Console.WriteLine("Invalid partitions table!");
return;
}
// Iterate over all 8 NCCH partitions
for (int p = 0; p < 8; p++)
{
// Check the partition exists
var partition = cart.Partitions[p];
if (partition is null || partition.MagicID != NCCHMagicNumber)
{
Console.WriteLine($"Partition {p} Not found... Skipping...");
continue;
}
// Check the partition has data
var partitionEntry = cart.PartitionsTable[p];
if (partitionEntry is null || partitionEntry.Length == 0)
{
Console.WriteLine($"Partition {p} No data... Skipping...");
continue;
}
// Encrypt the partition, if possible
if (ShouldEncryptPartition(cart, p, force))
EncryptPartition(cart, p, reader, writer);
}
}
/// <summary>
/// Determine if the current partition should be encrypted
/// </summary>
private static bool ShouldEncryptPartition(N3DS cart, int index, bool force)
{
// If we're forcing the operation, tell the user
if (force)
{
Console.WriteLine($"Partition {index} is not verified due to force flag being set.");
return true;
}
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
else if (!cart.PossiblyDecrypted(index))
{
Console.WriteLine($"Partition {index}: Already Encrypted?...");
return false;
}
// By default, it passes
return true;
}
/// <summary>
/// Encrypt a single partition
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private void EncryptPartition(N3DS cart, int index, Stream reader, Stream writer)
{
// Determine the keys needed for this partition
SetEncryptionKeys(cart, index);
// Encrypt the parts of the partition
EncryptExtendedHeader(cart, index, reader, writer);
EncryptExeFS(cart, index, reader, writer);
EncryptRomFS(cart, index, reader, writer);
// Update the flags
UpdateEncryptCryptoAndMasks(cart, index, writer);
}
/// <summary>
/// Determine the set of keys to be used for encryption
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
private void SetEncryptionKeys(N3DS cart, int index)
{
// Get the partition
var partition = cart.Partitions?[index];
if (partition is null)
return;
// Get the backup header
var backupHeader = cart.BackupHeader;
if (backupHeader?.Flags is null)
return;
// Get partition-specific values
byte[]? rsaSignature = partition.RSA2048Signature;
BitMasks masks = backupHeader.Flags.BitMasks;
CryptoMethod method = backupHeader.Flags.CryptoMethod;
// Get the partition keys
_keysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
}
/// <summary>
/// Encrypt the extended header, if it exists
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private bool EncryptExtendedHeader(N3DS cart, int index, Stream reader, Stream writer)
{
// Get required offsets
uint partitionOffset = cart.GetPartitionOffset(index);
if (partitionOffset == 0 || partitionOffset > reader.Length)
{
Console.WriteLine($"Partition {index} No Data... Skipping...");
return false;
}
uint extHeaderSize = cart.GetExtendedHeaderSize(index);
if (extHeaderSize == 0)
{
Console.WriteLine($"Partition {index} No Extended Header... Skipping...");
return false;
}
// Seek to the extended header
reader.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
writer.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
Console.WriteLine($"Partition {index}: Encrypting - ExHeader");
// Create the Plain AES cipher for this partition
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
// Process the extended header
AESCTR.PerformOperation(Constants.CXTExtendedDataHeaderLength, cipher, reader, writer, null);
#if NET6_0_OR_GREATER
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
reader.Seek(0, SeekOrigin.Begin);
#endif
writer.Flush();
return true;
}
/// <summary>
/// Encrypt the ExeFS, if it exists
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private bool EncryptExeFS(N3DS cart, int index, Stream reader, Stream writer)
{
if (cart.ExeFSHeaders is null || index < 0 || index > cart.ExeFSHeaders.Length)
{
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
return false;
}
// Get the ExeFS header
var exefsHeader = cart.ExeFSHeaders[index];
if (exefsHeader is null)
{
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
return false;
}
// For all but the original crypto method, process each of the files in the table
var backupHeader = cart.BackupHeader;
if (backupHeader!.Flags!.CryptoMethod != CryptoMethod.Original)
EncryptExeFSFileEntries(cart, index, reader, writer);
// Encrypt the filename table
EncryptExeFSFilenameTable(cart, index, reader, writer);
// Get the ExeFS files offset
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
// Seek to the ExeFS
reader.Seek(exeFsFilesOffset, SeekOrigin.Begin);
writer.Seek(exeFsFilesOffset, SeekOrigin.Begin);
// Create the ExeFS AES cipher for this partition
uint ctroffsetE = cart.MediaUnitSize / 0x10;
byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE);
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
// Setup and perform the encryption
uint exeFsSize = cart.GetExeFSSize(index) - cart.MediaUnitSize;
AESCTR.PerformOperation(exeFsSize,
cipher,
reader,
writer,
s => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {s}"));
return true;
}
/// <summary>
/// Encrypt the ExeFS Filename Table
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private void EncryptExeFSFilenameTable(N3DS cart, int index, Stream reader, Stream writer)
{
// Get ExeFS offset
uint exeFsOffset = cart.GetExeFSOffset(index);
if (exeFsOffset == 0 || exeFsOffset > reader.Length)
{
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
return;
}
// Seek to the ExeFS header
reader.Seek(exeFsOffset, SeekOrigin.Begin);
writer.Seek(exeFsOffset, SeekOrigin.Begin);
Console.WriteLine($"Partition {index} ExeFS: Encrypting - ExeFS Filename Table");
// Create the ExeFS AES cipher for this partition
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
// Process the filename table
byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize);
byte[] processedBytes = cipher.ProcessBytes(readBytes);
writer.Write(processedBytes);
#if NET6_0_OR_GREATER
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
reader.Seek(0, SeekOrigin.Begin);
#endif
writer.Flush();
}
/// <summary>
/// Encrypt the ExeFS file entries
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private void EncryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer)
{
// Get ExeFS offset
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > reader.Length)
{
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
return;
}
// Get to the start of the files
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
// If the header failed to read, log and return
var exeFsHeader = cart.ExeFSHeaders?[index];
if (exeFsHeader?.FileHeaders is null)
{
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
return;
}
// Loop through and process all headers
for (int i = 0; i < exeFsHeader.FileHeaders.Length; i++)
{
// Only attempt to process code binary files
if (!cart.IsCodeBinary(index, i))
continue;
// Get the file header
var fileHeader = exeFsHeader.FileHeaders[i];
if (fileHeader is null)
continue;
// Create the ExeFS AES ciphers for this partition
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
byte[] exefsIVWithOffsetForHeader = cart.ExeFSIV(index).Add(ctroffset);
var firstCipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader);
var secondCipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
// Seek to the file entry
reader.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
writer.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
// Setup and perform the encryption
AESCTR.PerformOperation(fileHeader.FileSize,
firstCipher,
secondCipher,
reader,
writer,
s => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {fileHeader.FileName}...{s}"));
}
}
/// <summary>
/// Encrypt the RomFS, if it exists
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="reader">Stream representing the input</param>
/// <param name="writer">Stream representing the output</param>
private bool EncryptRomFS(N3DS cart, int index, Stream reader, Stream writer)
{
// Validate the RomFS
uint romFsOffset = cart.GetRomFSOffset(index);
if (romFsOffset == 0 || romFsOffset > reader.Length)
{
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
return false;
}
uint romFsSize = cart.GetRomFSSize(index);
if (romFsSize == 0)
{
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
return false;
}
// Seek to the RomFS
reader.Seek(romFsOffset, SeekOrigin.Begin);
writer.Seek(romFsOffset, SeekOrigin.Begin);
// Force setting encryption keys for partitions 1 and above
if (index > 0)
{
var backupHeader = cart.BackupHeader;
_keysMap[index].SetRomFSValues(backupHeader!.Flags!.BitMasks);
}
// Create the RomFS AES cipher for this partition
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
// Setup and perform the decryption
AESCTR.PerformOperation(romFsSize,
cipher,
reader,
writer,
s => Console.WriteLine($"\rPartition {index} RomFS: Encrypting - {s}"));
return true;
}
/// <summary>
/// Update the CryptoMethod and BitMasks for the encrypted partition
/// </summary>
/// <param name="cart">Cart representing the 3DS file</param>
/// <param name="index">Index of the partition</param>
/// <param name="writer">Stream representing the output</param>
private static void UpdateEncryptCryptoAndMasks(N3DS cart, int index, Stream writer)
{
// Get required offsets
uint partitionOffset = cart.GetPartitionOffset(index);
// Get the backup header
var backupHeader = cart.BackupHeader;
if (backupHeader?.Flags is null)
return;
// Seek to the CryptoMethod location
writer.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
// Write the new CryptoMethod
// - For partitions 1 and up, set crypto-method to 0x00
// - If partition 0, restore crypto-method from backup flags
byte cryptoMethod = index > 0 ? (byte)CryptoMethod.Original : (byte)backupHeader.Flags.CryptoMethod;
writer.Write(cryptoMethod);
writer.Flush();
// Seek to the BitMasks location
writer.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
// Write the new BitMasks flag
BitMasks flag = cart.GetBitMasks(index);
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupHeader.Flags.BitMasks;
writer.Write((byte)flag);
writer.Flush();
}
#endregion
#region Info
/// <inheritdoc/>
public string? GetInformation(string filename)
{
try
{
// Open the file for reading
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// Deserialize the cart information
var cart = N3DS.Create(input);
if (cart?.Model is null)
return "Error: Not a 3DS cart image!";
// Get a string builder for the status
var sb = new StringBuilder();
// Iterate over all 8 NCCH partitions
for (int p = 0; p < 8; p++)
{
bool decrypted = cart.PossiblyDecrypted(p);
sb.AppendLine($"\tPartition {p}: {(decrypted ? "Decrypted" : "Encrypted")}");
}
// Return the status for all partitions
return sb.ToString();
}
catch (Exception ex)
{
Console.WriteLine(ex);
return null;
}
}
#endregion
}
}

36
NDecrypt.sln Normal file
View File

@@ -0,0 +1,36 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32922.545
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{31CE0445-F693-4C9A-B6CD-499C38CFF7FE}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NDecrypt", "NDecrypt\NDecrypt.csproj", "{05566793-831F-4AE1-A6D2-F9214F36618E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NDecrypt.Core", "NDecrypt.Core\NDecrypt.Core.csproj", "{91C54370-5741-4742-B2E9-EC498551AD1C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{05566793-831F-4AE1-A6D2-F9214F36618E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05566793-831F-4AE1-A6D2-F9214F36618E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05566793-831F-4AE1-A6D2-F9214F36618E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05566793-831F-4AE1-A6D2-F9214F36618E}.Release|Any CPU.Build.0 = Release|Any CPU
{91C54370-5741-4742-B2E9-EC498551AD1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91C54370-5741-4742-B2E9-EC498551AD1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91C54370-5741-4742-B2E9-EC498551AD1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91C54370-5741-4742-B2E9-EC498551AD1C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5AB50D9B-BA18-4F96-804B-52E7E0845B37}
EndGlobalSection
EndGlobal

15
NDecrypt/Enumerations.cs Normal file
View File

@@ -0,0 +1,15 @@
namespace NDecrypt
{
/// <summary>
/// Type of the detected file
/// </summary>
internal enum FileType
{
NULL,
NDS,
NDSi,
iQueDS,
N3DS,
iQue3DS,
}
}

View File

@@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.IO;
using NDecrypt.Core;
using SabreTools.CommandLine;
using SabreTools.CommandLine.Inputs;
namespace NDecrypt.Features
{
internal abstract class BaseFeature : Feature
{
#region Common Inputs
protected const string ConfigName = "config";
protected readonly StringInput ConfigString = new(ConfigName, ["-c", "--config"], "Path to config.json");
protected const string DevelopmentName = "development";
protected readonly FlagInput DevelopmentFlag = new(DevelopmentName, ["-d", "--development"], "Enable using development keys, if available");
protected const string ForceName = "force";
protected readonly FlagInput ForceFlag = new(ForceName, ["-f", "--force"], "Force operation by avoiding sanity checks");
protected const string HashName = "hash";
protected readonly FlagInput HashFlag = new(HashName, "--hash", "Output size and hashes to a companion file");
protected const string OverwriteName = "overwrite";
protected readonly FlagInput OverwriteFlag = new(OverwriteName, ["-o", "--overwrite"], "Overwrite input files instead of creating new ones");
#endregion
/// <summary>
/// Mapping of reusable tools
/// </summary>
private readonly Dictionary<FileType, ITool> _tools = [];
protected BaseFeature(string name, string[] flags, string description, string? detailed = null)
: base(name, flags, description, detailed)
{
}
/// <inheritdoc/>
public override bool Execute()
{
// Initialize required pieces
InitializeTools();
for (int i = 0; i < Inputs.Count; i++)
{
if (File.Exists(Inputs[i]))
{
ProcessFile(Inputs[i]);
}
else if (Directory.Exists(Inputs[i]))
{
foreach (string file in Directory.GetFiles(Inputs[i], "*", SearchOption.AllDirectories))
{
ProcessFile(file);
}
}
else
{
Console.WriteLine($"{Inputs[i]} is not a file or folder. Please check your spelling and formatting and try again.");
}
}
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => Inputs.Count > 0;
/// <summary>
/// Process a single file path
/// </summary>
/// <param name="input">File path to process</param>
protected abstract void ProcessFile(string input);
/// <summary>
/// Initialize the tools to be used by the feature
/// </summary>
private void InitializeTools()
{
var decryptArgs = new DecryptArgs(GetString(ConfigName, "config.json"));
_tools[FileType.NDS] = new DSTool(decryptArgs);
_tools[FileType.N3DS] = new ThreeDSTool(GetBoolean(DevelopmentName), decryptArgs);
}
/// <summary>
/// Derive the encryption tool to be used for the given file
/// </summary>
/// <param name="filename">Filename to derive the tool from</param>
protected ITool? DeriveTool(string filename)
{
if (!File.Exists(filename))
{
Console.WriteLine($"{filename} does not exist! Skipping...");
return null;
}
FileType type = DetermineFileType(filename);
return type switch
{
FileType.NDS => _tools[FileType.NDS],
FileType.NDSi => _tools[FileType.NDS],
FileType.iQueDS => _tools[FileType.NDS],
FileType.N3DS => _tools[FileType.N3DS],
_ => null,
};
}
/// <summary>
/// Derive an output filename from the input, if possible
/// </summary>
/// <param name="filename">Name of the input file to derive from</param>
/// <param name="extension">Preferred extension set by the feature implementation</param>
/// <returns>Output filename based on the input</returns>
protected static string GetOutputFile(string filename, string extension)
{
// Empty filenames are passed back
if (filename.Length == 0)
return filename;
// TODO: Replace the suffix instead of just appending
// TODO: Ensure that the input and output aren't the same
// If the extension does not include a leading period
#if NETCOREAPP || NETSTANDARD2_0_OR_GREATER
if (!extension.StartsWith('.'))
#else
if (!extension.StartsWith("."))
#endif
extension = $".{extension}";
// Append the extension and return
return $"{filename}{extension}";
}
/// <summary>
/// Write out the hashes of a file to a named file
/// </summary>
/// <param name="filename">Filename to get hashes for/param>
protected static void WriteHashes(string filename)
{
// If the file doesn't exist, don't try anything
if (!File.Exists(filename))
return;
// Get the hash string from the file
string? hashString = HashingHelper.GetInfo(filename);
if (hashString is null)
return;
// Open the output file and write the hashes
using var fs = File.Open(Path.GetFullPath(filename) + ".hash", FileMode.Create, FileAccess.Write, FileShare.None);
using var sw = new StreamWriter(fs);
sw.Write(hashString);
}
/// <summary>
/// Determine the file type from the filename extension
/// </summary>
/// <param name="filename">Filename to derive the type from</param>
/// <returns>FileType value, if possible</returns>
private static FileType DetermineFileType(string filename)
{
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase) // Standard carts
|| filename.EndsWith(".nds.dec", StringComparison.OrdinalIgnoreCase) // Carts/images with secure area decrypted
|| filename.EndsWith(".nds.enc", StringComparison.OrdinalIgnoreCase) // Carts/images with secure area encrypted
|| filename.EndsWith(".srl", StringComparison.OrdinalIgnoreCase)) // Development carts/images
{
Console.WriteLine("File recognized as Nintendo DS");
return FileType.NDS;
}
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("File recognized as Nintendo DSi");
return FileType.NDSi;
}
else if (filename.EndsWith(".ids", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("File recognized as iQue DS");
return FileType.iQueDS;
}
else if (filename.EndsWith(".3ds", StringComparison.OrdinalIgnoreCase) // Standard carts
|| filename.EndsWith(".3ds.dec", StringComparison.OrdinalIgnoreCase) // Decrypted carts/images
|| filename.EndsWith(".3ds.enc", StringComparison.OrdinalIgnoreCase) // Encrypted carts/images
|| filename.EndsWith(".cci", StringComparison.OrdinalIgnoreCase)) // Development carts/images
{
Console.WriteLine("File recognized as Nintendo 3DS");
return FileType.N3DS;
}
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.srl, *.dsi, *.3ds, *.cci");
return FileType.NULL;
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
namespace NDecrypt.Features
{
internal sealed class DecryptFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "decrypt";
private static readonly string[] _flags = ["d", "decrypt"];
private const string _description = "Decrypt the input files";
#endregion
public DecryptFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = true;
Add(ConfigString);
Add(DevelopmentFlag);
Add(ForceFlag);
Add(HashFlag);
// TODO: Include this when enabled
// Add(OverwriteFlag);
}
/// <inheritdoc/>
protected override void ProcessFile(string input)
{
// Attempt to derive the tool for the path
var tool = DeriveTool(input);
if (tool is null)
return;
// Derive the output filename, if required
string? output = null;
if (!GetBoolean(OverwriteName))
output = GetOutputFile(input, ".dec");
Console.WriteLine($"Processing {input}");
if (!tool.DecryptFile(input, output, GetBoolean(ForceName)))
{
Console.WriteLine("Decryption failed!");
return;
}
// Output the file hashes, if expected
if (GetBoolean(HashName))
WriteHashes(input);
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
namespace NDecrypt.Features
{
internal sealed class EncryptFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "encrypt";
private static readonly string[] _flags = ["e", "encrypt"];
private const string _description = "Encrypt the input files";
#endregion
public EncryptFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = true;
Add(ConfigString);
Add(DevelopmentFlag);
Add(ForceFlag);
Add(HashFlag);
// TODO: Include this when enabled
// Add(OverwriteFlag);
}
/// <inheritdoc/>
protected override void ProcessFile(string input)
{
// Attempt to derive the tool for the path
var tool = DeriveTool(input);
if (tool is null)
return;
// Derive the output filename, if required
string? output = null;
if (!GetBoolean(OverwriteName))
output = GetOutputFile(input, ".enc");
Console.WriteLine($"Processing {input}");
if (!tool.EncryptFile(input, output, GetBoolean(ForceName)))
{
Console.WriteLine("Encryption failed!");
return;
}
// Output the file hashes, if expected
if (GetBoolean(HashName))
WriteHashes(input);
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
namespace NDecrypt.Features
{
internal sealed class InfoFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "info";
private static readonly string[] _flags = ["i", "info"];
private const string _description = "Output file information";
#endregion
public InfoFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = true;
Add(HashFlag);
}
/// <inheritdoc/>
protected override void ProcessFile(string input)
{
// Attempt to derive the tool for the path
var tool = DeriveTool(input);
if (tool is null)
return;
Console.WriteLine($"Processing {input}");
string? infoString = tool.GetInformation(input);
infoString ??= "There was a problem getting file information!";
Console.WriteLine(infoString);
// Output the file hashes, if expected
if (GetBoolean(HashName))
WriteHashes(input);
}
}
}

33
NDecrypt/HashingHelper.cs Normal file
View File

@@ -0,0 +1,33 @@
using System.IO;
using SabreTools.Hashing;
namespace NDecrypt
{
internal static class HashingHelper
{
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <returns>Formatted string representing the hashes, null on error</returns>
public static string? GetInfo(string input)
{
// If the file doesn't exist, return null
if (!File.Exists(input))
return null;
// Get the file information, if possible
HashType[] hashTypes = [HashType.CRC32, HashType.MD5, HashType.SHA1, HashType.SHA256];
var hashDict = HashTool.GetFileHashesAndSize(input, hashTypes, out long size);
if (hashDict is null)
return null;
// Get the results
return $"Size: {size}\n"
+ $"CRC-32: {(hashDict.TryGetValue(HashType.CRC32, out string? value) ? value : string.Empty)}\n"
+ $"MD5: {(hashDict.TryGetValue(HashType.MD5, out string? value1) ? value1 : string.Empty)}\n"
+ $"SHA-1: {(hashDict.TryGetValue(HashType.SHA1, out string? value2) ? value2 : string.Empty)}\n"
+ $"SHA-256: {(hashDict.TryGetValue(HashType.SHA256, out string? value3) ? value3 : string.Empty)}\n";
}
}
}

50
NDecrypt/NDecrypt.csproj Normal file
View File

@@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>0.5.0</VersionPrefix>
<!-- Package Properties -->
<Title>NDecrypt</Title>
<Authors>Matt Nadareski</Authors>
<Description>DS/3DS Encryption Tool</Description>
<Copyright>Copyright (c) Matt Nadareski 2018-2025</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`)) OR $(TargetFramework.StartsWith(`net10`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NDecrypt.Core\NDecrypt.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.CommandLine" Version="[1.4.0]" />
</ItemGroup>
</Project>

89
NDecrypt/Program.cs Normal file
View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using NDecrypt.Features;
using SabreTools.CommandLine;
using SabreTools.CommandLine.Features;
namespace NDecrypt
{
class Program
{
public static void Main(string[] args)
{
// Create the command set
var commandSet = CreateCommands();
// If we have no args, show the help and quit
if (args is null || args.Length == 0)
{
commandSet.OutputAllHelp();
return;
}
// Get the first argument as a feature flag
string featureName = args[0];
// Get the associated feature
var topLevel = commandSet.GetTopLevel(featureName);
if (topLevel is null || topLevel is not Feature feature)
{
Console.WriteLine($"'{featureName}' is not valid feature flag");
commandSet.OutputFeatureHelp(featureName);
return;
}
// Handle default help functionality
if (topLevel is Help helpFeature)
{
helpFeature.ProcessArgs(args, 0, commandSet);
return;
}
// Now verify that all other flags are valid
if (!feature.ProcessArgs(args, 1))
return;
// If inputs are required
if (feature.RequiresInputs && !feature.VerifyInputs())
{
commandSet.OutputFeatureHelp(topLevel.Name);
Environment.Exit(0);
}
// Now execute the current feature
if (!feature.Execute())
{
Console.Error.WriteLine("An error occurred during processing!");
commandSet.OutputFeatureHelp(topLevel.Name);
}
}
/// <summary>
/// Create the command set for the program
/// </summary>
private static CommandSet CreateCommands()
{
List<string> header = [
"Cart Image Encrypt/Decrypt Tool",
string.Empty,
"NDecrypt <operation> [options] <path> ...",
string.Empty,
];
List<string> footer = [
string.Empty,
"<path> can be any file or folder that contains uncompressed items.",
"More than one path can be specified at a time.",
];
var commandSet = new CommandSet(header, footer);
commandSet.Add(new Help());
commandSet.Add(new EncryptFeature());
commandSet.Add(new DecryptFeature());
commandSet.Add(new InfoFeature());
return commandSet;
}
}
}

108
README.md Normal file
View File

@@ -0,0 +1,108 @@
# NDecrypt
[![Build and Test](https://github.com/SabreTools/NDecrypt/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/SabreTools/NDecrypt/actions/workflows/build_and_test.yml)
A simple tool for simple people.
## What is this?
This is a code port of 3 different programs:
- `3ds_encrypt.py`
- `3ds_decrypt.py`
- `woodsec` (part of [wooddumper](https://github.com/TuxSH/wooddumper))
## No really, what is this?
This tool allows you to encrypt and decrypt your personally dumped Nintendo DS, 3DS, and New 3DS cart images with minimal hassle.
## Where do I find it?
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/NDecrypt/releases)
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/NDecrypt/releases/tag/rolling)
## So how do I use this?
Usage: NDecrypt <operation> [flags] <path> ...
Possible values for <operation>:
e, encrypt - Encrypt the input files
d, decrypt - Decrypt the input files
i, info - Output file information
Possible values for [flags] (one or more can be used):
-?, -h, --help Display this help text and quit
-c, --config <path> Path to config.json
-d, --development Enable using development keys, if available
-f, --force Force operation by avoiding sanity checks
--hash Output size and hashes to a companion file
<path> can be any file or folder that contains uncompressed items.
More than one path can be specified at a time.
### Additional Notes
- Input files are overwritten, even if they are only partially processed. You should make backups of the files you're working on if you're worried about this.
- Mixed folders or inputs are also accepted, you can decrypt or encrypt multiple files, regardless of their type. This being said, you can only do encrypt _OR_ decrypt at one time.
- Required files will automatically be searched for in the application runtime directory as well as `%HOME%/.config/ndecrypt`, also known as `%USERPROFILE%\.config\ndecrypt` on Windows.
## I feel like something is missing
There is a major file that you can use to give NDecrypt that extra _oomph_ of functionality that it really needs. That is, you can't do any encryption or decryption without it present. I can't give you the files and I can't generate them for you on the fly with the correct values. Keys are a thorny thing and I just do not want to deal with them. Values are validated, at least, but you'll only get yelled at on run if one of them is wrong. Don't worry, they're just disabled, not removed.
This convenient table gives an overview of mappings between the current `config.json` type along with the 2 formerly-supported types and a completely unsupported but common type.
| `config.json` | `keys.bin` order | `aes_keys.txt` | rom-properties `keys.conf` |
| --- | --- | --- | --- |
| `NitroEncryptionData` | **N/A** | **UNMAPPED** | **UNMAPPED** |
| `AESHardwareConstant` | 1 | `generator` | `ctr-scrambler` |
| `KeyX0x18` | 2 | `slot0x18KeyX` | `ctr-Slot0x18KeyX` |
| `KeyX0x1B` | 3 | `slot0x1BKeyX` | `ctr-Slot0x1BKeyX` |
| `KeyX0x25` | 4 | `slot0x25KeyX` | `ctr-Slot0x25KeyX` |
| `KeyX0x2C` | 5 | `slot0x2CKeyX` | `ctr-Slot0x2CKeyX` |
| `DevKeyX0x18` | 6 | **UNMAPPED** | `ctr-dev-Slot0x18KeyX` |
| `DevKeyX0x1B` | 7 | **UNMAPPED** | `ctr-dev-Slot0x1BKeyX` |
| `DevKeyX0x25` | 8 | **UNMAPPED** | `ctr-dev-Slot0x25KeyX` |
| `DevKeyX0x2C` | 9 | **UNMAPPED** | `ctr-dev-Slot0x2CKeyX` |
**Note:** `Dev*` keys are not required for the vast majority of normal operations. They're only used if the `-d` option is included. Working with your own retail carts will pretty much never require these, so don't drive yourself silly dealing with them.
**Note:** The `NitroEncryptionData` field is also known as the "Blowfish table" for Nintendo DS carts. It's stored in the same hex string format as the other keys. There's some complicated stuff about how it's used and where it's stored, but all you need to know is that it is required.
**Community Note:** If you have used previous versions of NDecrypt and already have either `keys.bin` or `aes_keys.txt`, consider using [this helpful community-made script](https://gist.github.com/Dimensional/82f212a0b35bcf9caaa2bc9a70b3a92a) to make your life a bit easier. It will convert them into the new `config.json` format that will be supported from here on out.
### `config.json`
The up-and-coming, shiny, new, exciting, JSON-based format for storing the encryption keys that you need for Nintendo DS, 3DS, and New 3DS. This JSON file is not generated by anything, but maps pretty much one-to-one with the code inside of NDecrypt, making it super convenient to use. Keys provided need to be hex strings (e.g. `"AABBCCDD"`). Any keys that are left with `null` or `""` as the value will be ignored. See [the sample config](https://github.com/SabreTools/NDecrypt/blob/master/config-default.json) that I've nicely generated for you. You're welcome.
In the future, this file will be automatically generated on first run along with some cutesy little message telling you to fill it out when you get a chance. It's not doing it right now because I don't want to confuse users. Including those reading this. How meta.
### `keys.bin` (Deprecated)
This is the OG of NDecrypt key file formats. It's a weird, binary blob of a format that is composed of little-endian values (most common extraction methods produce big endian, so keep that in mind). It's only compatible wtih Nintendo 3DS and New 3DS keys and is incredibly inflexible in its layout. The little-endianness of it is a relic of how keys were handled in-code previously and I really can't fix it now. If you don't have a key, it needs to be filled with `0x00` bytes so it doesn't mess up the read. Yeah.
Oddly, this gets confused with some similar format that GodMode9 works with, but it has nothing to do with it. If you try to use one of those files in place of this one, something will probably break. It wasn't intentional, I just didn't look ahead of time. See the table in the main part of this section for the order the keys need to be stored in.
### `aes_keys.txt` (Deprecated)
This is an INI-based format that was super popular among 3DS emulators and probably still is. Weird thing, I know, but just roll with it please.
## But does it work?
As much as I'd like to think that this program is entirely without flaws, numbers need to speak for themselves sometimes. Here's a list of the supported sets and their current compatibility percentages with woodsec and the Python scripts (as of 2020-12-19):
- **Nintendo DS** - >99% compatible (Both encryption and decryption)
- **Nintendo DSi** - 100% compatible (Both encryption and decryption)
- **Nintendo 3DS** - 100% compatible (Both encryption and decryption)
- **Nintendo New 3DS** - 100% compatible (Both encryption and decryption)
Please note the above numbers are based on the current, documented values. The notable exceptions to this tend to be unlicensed carts which may be dumped incorrectly or have odd information stored in their secure area.
## Anything else?
I'd like to thank the developers of the original programs for doing the actual hard work to figure things out. I'd also like to thank everyone who helped to test this against the original programs and made code suggestions.
## Disclaimer
This program is **ONLY** for use with personally dumped files and keys and is not meant for enabling illegal activity. I do not condone using this program for anything other than personal use and research. If this program is used for anything other than that, I cannot be held liable for anything that happens.

12
config-default.json Normal file
View File

@@ -0,0 +1,12 @@
{
"NitroEncryptionData": null,
"AESHardwareConstant": null,
"KeyX0x18": null,
"KeyX0x1B": null,
"KeyX0x25": null,
"KeyX0x2C": null,
"DevKeyX0x18": null,
"DevKeyX0x1B": null,
"DevKeyX0x25": null,
"DevKeyX0x2C": null
}

151
publish-nix.sh Executable file
View File

@@ -0,0 +1,151 @@
#!/bin/bash
# This batch file assumes the following:
# - .NET 9.0 (or newer) SDK is installed and in PATH
# - zip is installed and in PATH
# - Git is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
USE_ALL=false
INCLUDE_DEBUG=false
NO_BUILD=false
NO_ARCHIVE=false
while getopts "udba" OPTION; do
case $OPTION in
u)
USE_ALL=true
;;
d)
INCLUDE_DEBUG=true
;;
b)
NO_BUILD=true
;;
a)
NO_ARCHIVE=true
;;
*)
echo "Invalid option provided"
exit 1
;;
esac
done
# Set the current directory as a variable
BUILD_FOLDER=$PWD
# Set the current commit hash
COMMIT=$(git log --pretty=%H -1)
# Output the selected options
echo "Selected Options:"
echo " Use all frameworks (-u) $USE_ALL"
echo " Include debug builds (-d) $INCLUDE_DEBUG"
echo " No build (-b) $NO_BUILD"
echo " No archive (-a) $NO_ARCHIVE"
echo " "
# Create the build matrix arrays
FRAMEWORKS=("net10.0")
RUNTIMES=("win-x86" "win-x64" "win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
# Use expanded lists, if requested
if [ $USE_ALL = true ]; then
FRAMEWORKS=("net20" "net35" "net40" "net452" "net462" "net472" "net48" "netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
fi
# Create the filter arrays
SINGLE_FILE_CAPABLE=("net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
VALID_APPLE_FRAMEWORKS=("net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
VALID_CROSS_PLATFORM_FRAMEWORKS=("netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
VALID_CROSS_PLATFORM_RUNTIMES=("win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
# Only build if requested
if [ $NO_BUILD = false ]; then
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# Create Nuget Packages
dotnet pack NDecrypt.Core/NDecrypt.Core.csproj --output $BUILD_FOLDER
# Build Program
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
for RUNTIME in "${RUNTIMES[@]}"; do
# Output the current build
echo "===== Build Program - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
echo "Skipped due to invalid combination"
continue
fi
fi
# If we have Apple silicon but an unsupported framework
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [ $RUNTIME = "osx-arm64" ]; then
echo "Skipped due to no Apple Silicon support"
continue
fi
fi
# Only .NET 5 and above can publish to a single file
if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]; then
# Only include Debug if set
if [ $INCLUDE_DEBUG = true ]; then
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
fi
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
else
# Only include Debug if set
if [ $INCLUDE_DEBUG = true ]; then
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
fi
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
fi
done
done
fi
# Only create archives if requested
if [ $NO_ARCHIVE = false ]; then
# Create Test archives
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
for RUNTIME in "${RUNTIMES[@]}"; do
# Output the current build
echo "===== Archive Program - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
echo "Skipped due to invalid combination"
continue
fi
fi
# If we have Apple silicon but an unsupported framework
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [ $RUNTIME = "osx-arm64" ]; then
echo "Skipped due to no Apple Silicon support"
continue
fi
fi
# Only include Debug if set
if [ $INCLUDE_DEBUG = true ]; then
cd $BUILD_FOLDER/NDecrypt/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/NDecrypt_${FRAMEWORK}_${RUNTIME}_debug.zip .
fi
cd $BUILD_FOLDER/NDecrypt/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/NDecrypt_${FRAMEWORK}_${RUNTIME}_release.zip .
done
done
# Reset the directory
cd $BUILD_FOLDER
fi

136
publish-win.ps1 Normal file
View File

@@ -0,0 +1,136 @@
# This batch file assumes the following:
# - .NET 9.0 (or newer) SDK is installed and in PATH
# - 7-zip commandline (7z.exe) is installed and in PATH
# - Git for Windows is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
param(
[Parameter(Mandatory = $false)]
[Alias("UseAll")]
[switch]$USE_ALL,
[Parameter(Mandatory = $false)]
[Alias("IncludeDebug")]
[switch]$INCLUDE_DEBUG,
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD,
[Parameter(Mandatory = $false)]
[Alias("NoArchive")]
[switch]$NO_ARCHIVE
)
# Set the current directory as a variable
$BUILD_FOLDER = $PSScriptRoot
# Set the current commit hash
$COMMIT = git log --pretty=format:"%H" -1
# Output the selected options
Write-Host "Selected Options:"
Write-Host " Use all frameworks (-UseAll) $USE_ALL"
Write-Host " Include debug builds (-IncludeDebug) $INCLUDE_DEBUG"
Write-Host " No build (-NoBuild) $NO_BUILD"
Write-Host " No archive (-NoArchive) $NO_ARCHIVE"
Write-Host " "
# Create the build matrix arrays
$FRAMEWORKS = @('net10.0')
$RUNTIMES = @('win-x86', 'win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
# Use expanded lists, if requested
if ($USE_ALL.IsPresent) {
$FRAMEWORKS = @('net20', 'net35', 'net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
}
# Create the filter arrays
$SINGLE_FILE_CAPABLE = @('net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
$VALID_APPLE_FRAMEWORKS = @('net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
$VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
$VALID_CROSS_PLATFORM_RUNTIMES = @('win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
# Only build if requested
if (!$NO_BUILD.IsPresent) {
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack NDecrypt.Core\NDecrypt.Core.csproj --output $BUILD_FOLDER
# Build Program
foreach ($FRAMEWORK in $FRAMEWORKS) {
foreach ($RUNTIME in $RUNTIMES) {
# Output the current build
Write-Host "===== Build Program - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
Write-Host "Skipped due to invalid combination"
continue
}
# If we have Apple silicon but an unsupported framework
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
Write-Host "Skipped due to no Apple Silicon support"
continue
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) {
# Only include Debug if set
if ($INCLUDE_DEBUG.IsPresent) {
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
}
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else {
# Only include Debug if set
if ($INCLUDE_DEBUG.IsPresent) {
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
}
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
}
# Only create archives if requested
if (!$NO_ARCHIVE.IsPresent) {
# Create Program archives
foreach ($FRAMEWORK in $FRAMEWORKS) {
foreach ($RUNTIME in $RUNTIMES) {
# Output the current build
Write-Host "===== Archive Program - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
Write-Host "Skipped due to invalid combination"
continue
}
# If we have Apple silicon but an unsupported framework
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
Write-Host "Skipped due to no Apple Silicon support"
continue
}
# Only include Debug if set
if ($INCLUDE_DEBUG.IsPresent) {
Set-Location -Path $BUILD_FOLDER\NDecrypt\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\NDecrypt_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
Set-Location -Path $BUILD_FOLDER\NDecrypt\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\NDecrypt_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
# Reset the directory
Set-Location -Path $PSScriptRoot
}