From 92f609bf882d1c0955de8d0a817fd34e300fbcc9 Mon Sep 17 00:00:00 2001 From: Garhoogin Date: Sat, 8 Feb 2025 21:02:41 -0600 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 9 + LICENSE | 24 + README.md | 2 + src/blowfish.c | 309 ++++++++++ src/blowfish.h | 24 + src/cmd_clean.c | 252 ++++++++ src/cmd_common.c | 149 +++++ src/cmd_common.h | 84 +++ src/cmd_compact.c | 121 ++++ src/cmd_eb.c | 112 ++++ src/cmd_export.c | 386 ++++++++++++ src/cmd_fix.c | 169 ++++++ src/cmd_help.c | 83 +++ src/cmd_info.c | 111 ++++ src/cmd_load.c | 46 ++ src/cmd_loc.c | 62 ++ src/cmd_map.c | 119 ++++ src/cmd_ms5.c | 204 +++++++ src/cmd_user.c | 266 ++++++++ src/cmd_verify.c | 106 ++++ src/compression.c | 1480 +++++++++++++++++++++++++++++++++++++++++++++ src/compression.h | 39 ++ src/firmware.c | 426 +++++++++++++ src/firmware.h | 219 +++++++ src/fwutil.c | 184 ++++++ 26 files changed, 4988 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/blowfish.c create mode 100644 src/blowfish.h create mode 100644 src/cmd_clean.c create mode 100644 src/cmd_common.c create mode 100644 src/cmd_common.h create mode 100644 src/cmd_compact.c create mode 100644 src/cmd_eb.c create mode 100644 src/cmd_export.c create mode 100644 src/cmd_fix.c create mode 100644 src/cmd_help.c create mode 100644 src/cmd_info.c create mode 100644 src/cmd_load.c create mode 100644 src/cmd_loc.c create mode 100644 src/cmd_map.c create mode 100644 src/cmd_ms5.c create mode 100644 src/cmd_user.c create mode 100644 src/cmd_verify.c create mode 100644 src/compression.c create mode 100644 src/compression.h create mode 100644 src/firmware.c create mode 100644 src/firmware.h create mode 100644 src/fwutil.c diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a347dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +build.bat +*.exe +*.bin +*.d +*.nds +*.elf +*.srl +*.map +*.o diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f2a02f4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2025, Garhoogin + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..12cd210 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# fwutil + Operate on DS firmware. diff --git a/src/blowfish.c b/src/blowfish.c new file mode 100644 index 0000000..77a5d9b --- /dev/null +++ b/src/blowfish.c @@ -0,0 +1,309 @@ +#include "blowfish.h" + +#include +#include +#include + + + +static const BfBlowfishContext sInitTable = { + { + 0x5f20d599, 0xb9f54457, 0xd9a4196e, 0x945a6a9e, 0xebf1aed8, 0x3ae27541, 0x32d08293, 0xd531ee33, + 0x9a6157cc, 0x1ba20637, 0xf5723979, 0xbef6ae55, 0xfb691b5f, 0xe9f19de5, 0xa1d92cce, 0xe605325e, + 0xcffed3fe, 0x0d0462d4 + }, + { + { + 0xb7ecf58b, 0xbb79602b, 0x0d319512, 0x2bda3f6e, 0xf1f08488, 0x257e123d, 0xbbf12245, 0x061a0624, + 0x28dfad11, 0x3481648b, 0x2933eb2b, 0xbdf2aa99, 0x9d95149c, 0x8cf5f79f, 0x29a19772, 0xcf5fd19d, + 0x1a074d66, 0x4b4ad3de, 0xa3a7c985, 0x3a059517, 0xbf0a493d, 0xa28b890a, 0xdd49824a, 0x0bf19027, + 0x6a1cebe9, 0x05457683, 0x617081ba, 0xde4b3f17, 0x39abcfae, 0x563af257, 0x8aad1148, 0x3f45e140, + 0x54029bfa, 0xfb93a6ca, 0x6ffe4def, 0x9c87d8a3, 0x48d5ba08, 0xfd2d8d6a, 0x74f8156e, 0x8b52bebd, + 0x9e8a2218, 0x073774fb, 0x4a6c361b, 0x6242ba19, 0x109179b9, 0x9665677b, 0xe82302fe, 0x778c99ee, + 0x64865c3e, 0x86786d4d, 0xe2654fa5, 0x5adfb21e, 0x087ed00a, 0xac71b014, 0x1c83dbbd, 0x62a1d7b9, + 0x7c63c6cd, 0xe6c36952, 0x12ce75bf, 0x04215d44, 0x3cd3fbfa, 0xd4631138, 0x49418595, 0x08f20946, + 0x1fdc1143, 0x6d15c076, 0x70633c1f, 0x6c8087ea, 0x8b63bdc3, 0x372137c2, 0x2309eedc, 0x4d6a372e, + 0x50f79073, 0x921cac30, 0x91231004, 0xaa07d24f, 0x9a4f3e68, 0x6a6064c9, 0xf32114c8, 0x124122d6, + 0xe6cf2444, 0x0ddd568a, 0x85e14d53, 0x5a528c1e, 0xc284199c, 0x6ff15703, 0x58be00e3, 0xd5ed4cf6, + 0x1f9c6421, 0x3c0355be, 0xaaffdc4a, 0x5de0dac9, 0xdee6bf5e, 0xf8b1d8f5, 0xb9b336ff, 0xdb956762, + 0xed375f31, 0x9967704c, 0x3118b590, 0x99993d6c, 0xd3da42e4, 0xa0134225, 0x6c70d7ae, 0xc7cf55b1, + 0x43d546d7, 0x443d1761, 0x8533e928, 0x93a2d0d5, 0x1f1225aa, 0x460bc5fb, 0x567697f5, 0x87bea645, + 0xe86b94b1, 0x9933feb1, 0x6c3e1fae, 0x091d7139, 0xe4379000, 0x74753e10, 0x3b838cff, 0xf9b0f1b0, + 0x42470501, 0xacd6f195, 0x9ee6387e, 0x3f267495, 0x185068b4, 0xb43043d0, 0x68e34b4c, 0xb64de5bf, + 0xa00a8b95, 0x77322574, 0x2cf7a1cf, 0x5a1371d8, 0x51c9eaab, 0xefee0de8, 0x197e93e9, 0x38431ea7, + 0xa12c1681, 0xcc73e348, 0xd36c2129, 0xd9a0ce5d, 0xa0437161, 0x64b51315, 0x192acf92, 0xa5b7addc, + 0xf865869f, 0xfbe79f1a, 0x13b8fdf7, 0x6fdb276c, 0xf71c35df, 0x9b5b2c8d, 0x6438ab12, 0x31decc06, + 0x11754ee8, 0xeafae364, 0xc25434eb, 0xeb343fad, 0x267d2c93, 0xf3569d36, 0xb3f6e15a, 0x9e4a6398, + 0x9ae48332, 0x907d6084, 0xee0e132e, 0xa2364b93, 0x3816ec85, 0x020688e8, 0x3aa0f0bf, 0x9a6ad7ed, + 0xcf57e173, 0xdcb844f8, 0xd159232e, 0x715295df, 0x4ba06199, 0x786e7fd5, 0x30c5a9ba, 0x328640d3, + 0x9c0c329d, 0x2f02b737, 0xa99854ba, 0xc90413c4, 0xe7c8be8d, 0x2e50975d, 0x5922d693, 0x22bc270c, + 0x20a7e092, 0x7f6f930f, 0xb5d39f4c, 0x740b2aa6, 0x107d4967, 0xc5d1cb26, 0x8ce77186, 0x5be99ca0, + 0x01f61ab2, 0x5e9e8cee, 0xdb1af283, 0x84eae5e6, 0x7cd27659, 0x49a58df6, 0x16c24836, 0xa383bb52, + 0x0c07b974, 0x2861ff3b, 0xe4e961e1, 0xaa156eef, 0x5de8ba4e, 0x32bb9605, 0x72fbb056, 0xc80e0f52, + 0x76652542, 0xdef2af89, 0x01f02710, 0x97a7744b, 0x5426d507, 0x821f0954, 0x307d860a, 0x26b30e39, + 0xbb570b9b, 0xaf310636, 0xd9fc79fd, 0x0c2b1030, 0xd79be1b3, 0xef5fdc7b, 0x4513f8d2, 0xbd75474d, + 0x7e3c9646, 0xb53ef375, 0x3b9ac567, 0x6b295bb0, 0xc85b80de, 0x31b10515, 0xdd49ceb6, 0xaeb584ad, + 0x3167dc60, 0x4efe3034, 0xa62f80bd, 0x213963bf, 0x7f35d986, 0x05226816, 0x2690e954, 0x516c078c, + 0xd75531a4, 0x3ea80709, 0xc166532e, 0xc47bf2f8, 0xf1cf58f2, 0xe7a2c587, 0x87308f27, 0x6264a058, + 0x88b91823, 0xc4cefa7c, 0x17adae98, 0xf35b4acc, 0x56d548e9, 0xc8f20dd3, 0xdb8c7392, 0xac562fd7 + }, { + 0x6992f981, 0xf632c64d, 0x218dc0e6, 0x618076e2, 0x6cdcbc11, 0x6919af93, 0xb9bfd09b, 0x67029f31, + 0x83ee51a3, 0x0c7b2206, 0x404249ab, 0x7d01d5b8, 0x55f75ece, 0x99c53953, 0x9f87d846, 0xb464f7ba, + 0xa1fa9ae3, 0x1068906d, 0x548aca30, 0xc3609fa7, 0x0d6bf519, 0xe698517a, 0xb4514398, 0x4fe935d6, + 0x7b0fdfc3, 0xbd5c2fd6, 0x1961153a, 0xaacb4bf1, 0xc9646ddc, 0x561ec6d3, 0x504c38ef, 0xcc758671, + 0xe94e0d0d, 0x5d06f628, 0xd3aa1b70, 0x39a8cf45, 0x2ea695ac, 0xd422e4b4, 0x5f37a874, 0xcc047a48, + 0xd8404ca5, 0x0828b428, 0x52721c0d, 0x477df041, 0x4e533a19, 0x6b628458, 0x818ab593, 0xdc0d4e21, + 0xc6a23fb4, 0x402bc9fc, 0xe90438da, 0x6b865a5e, 0x8525220c, 0x7c8d1168, 0x55951d92, 0xbb8eab4d, + 0xb7e6a6da, 0x5a32b651, 0x05dd4105, 0x50560a2a, 0xcc471791, 0xb57ee6c9, 0x73db4a61, 0x33c85167, + 0x746edaf5, 0x37c3542e, 0x08af6d0d, 0x5f8a15e8, 0xcd2159e2, 0x060cdea8, 0x5f6b775a, 0x3e6518db, + 0x78de50c8, 0xb382b8e0, 0x32724e5d, 0x34c14f07, 0xb796ba23, 0x28a44e67, 0xeb62341e, 0xe9706a2d, + 0x70c4422f, 0x9c315a4e, 0x28475bf9, 0x6f71daaa, 0x78b31f38, 0x1c6b92c4, 0x9a35f69e, 0xbf0e4db7, + 0x412918cc, 0x5d354803, 0xc62bd055, 0x605caf29, 0x5e8e6974, 0xbdd47c9b, 0x7d64447b, 0x695d923f, + 0x4b001fb6, 0xcf3583d4, 0x174e647e, 0x2ed58dae, 0x4e12289a, 0x08492b2e, 0x46c6ae5c, 0x6141ae85, + 0xd2826f1e, 0x1f163751, 0xa459f60b, 0xaf5aca9a, 0x8b33d40d, 0x84f16320, 0xcfcb5c80, 0xd3b9b408, + 0x62bd0516, 0x569b3183, 0xba9f9851, 0xb2aa5bb2, 0xb52c6b22, 0x63fa48d4, 0xfa585f2b, 0x0964fa61, + 0xb8e038bb, 0xa860929d, 0x0e6f670d, 0x010df537, 0xd477c29f, 0x73f1ecfe, 0x7de03930, 0xe49861f5, + 0x0455282c, 0x2fdb5556, 0x58e5ec6b, 0x8064b606, 0x4e1a2a6a, 0xc4d80f5b, 0x19522e0a, 0x30f562d9, + 0x7b8cbe48, 0xa29b384f, 0xd3c9afc3, 0x4162c1c7, 0x2161b986, 0x4f996f57, 0x7bcebac1, 0x5e4d3bb5, + 0x57448b8a, 0x705f135f, 0x47295b6d, 0xece238dc, 0x12655504, 0x4317e82a, 0x2add8ee1, 0xf794e2b3, + 0xe65c6e09, 0x6df88aeb, 0x48544989, 0xbfad2ff5, 0xca4b94ea, 0x828739fc, 0xf2018a5f, 0x71e6f275, + 0xde42d8d6, 0x281d2df1, 0xa37e88a6, 0x301d47a0, 0xdf71a3d9, 0x01cb1c49, 0xf2b136f8, 0x5d5822f0, + 0xa0bd6b45, 0x4288b2bb, 0xce288cc7, 0x6390e893, 0x897c9008, 0xb77df53c, 0x554f2d04, 0x7efd1651, + 0xc1bee879, 0xf8d412f2, 0x230584b4, 0x2bd2cca0, 0xadabe1fd, 0x6c55d10d, 0x4d944123, 0x054f3777, + 0x17bf0c28, 0x6c6712b3, 0xf75ac38c, 0x6d2a8441, 0x271294d0, 0x9cedb42c, 0x8247ec4d, 0xb967d597, + 0x55c09d1b, 0x8ee57e07, 0x3ee7a8e2, 0x3a0ee412, 0x3455452a, 0x5a2df9a2, 0x7c52ab1b, 0x555f1083, + 0x435af1d2, 0xa4a7c62b, 0xe8951589, 0xf89d4bb4, 0x609fe375, 0xe6d65b78, 0x21e6440d, 0x2247bd06, + 0xad00a453, 0x8513438d, 0xfcaaf739, 0xed7baf38, 0x542be4fc, 0xfc4c9850, 0xdff78085, 0xe122803c, + 0x24deda94, 0x397ab0c6, 0xa10fdc38, 0x6ff9f4a7, 0x8b571863, 0x2e2a4184, 0xd9f253d4, 0xddd00f00, + 0xa6196e99, 0x5becd00a, 0xc0ab2458, 0xec6506cb, 0x9438131a, 0x2f03670a, 0x77e3f73f, 0xc6337744, + 0xe3d03914, 0x7908a2c0, 0x579940bb, 0x90010b41, 0x48cce1cd, 0xafb3db67, 0x4cf37488, 0xb1728f82, + 0xc42923b5, 0xfc196c12, 0x9ca4468e, 0x876525c4, 0x8abe6dd3, 0x38031193, 0xf32b83ed, 0xea93a446, + 0x1d85533b, 0x08f1d4ce, 0xfced2783, 0xbc181a9b, 0xdcae8bf9, 0x3850ab24, 0x104b72e9, 0x467b1722 + }, { + 0x6459ab5d, 0xf8ae40f3, 0xf9c8e5bb, 0x554e0326, 0xfeebeb7d, 0xe0e639f7, 0x2ebe110a, 0xed98ff28, + 0x5642c9c0, 0x00fdc342, 0xa287aff6, 0x323f015b, 0x9a954792, 0x3d32a572, 0x9bd06bae, 0x9249d207, + 0xfa4a78e3, 0xf27d06a1, 0x7477cf41, 0x0cb21404, 0x16648486, 0xa151bbd5, 0xd1f16fe5, 0x5ff7e2f2, + 0xb84d2058, 0xddcfc757, 0x76bed8c5, 0x7e5ff63d, 0x888b2ae7, 0x3f381b24, 0x7723410e, 0xd44bf0f5, + 0xa4fa1f0c, 0xcf5f800b, 0xdae0f645, 0x5359342f, 0x523c20fb, 0xb5355e62, 0x608bfe62, 0x5a86e363, + 0xd16e1a15, 0x32bc4547, 0x3867ebb4, 0x336ee4ab, 0xa3edb53a, 0x4ee067ad, 0x62ee9541, 0x1d267162, + 0x3062ef31, 0xac82d7af, 0x0405dcc2, 0xbf0797f5, 0x07235911, 0xe80264c0, 0xaf3ee597, 0xa659ac18, + 0x90334a8b, 0x9c7c6e1c, 0x3c4c7e20, 0xbb64613e, 0x7e7c6bc5, 0x4cc59f3e, 0xf573ea9f, 0x4cc089d7, + 0x2df4fbf4, 0x511b14ec, 0xc812c1d5, 0x4a0bdf10, 0x93bc9c8b, 0x3e3e6a45, 0xbaa9c17d, 0x07b4c1cd, + 0x8668e1e4, 0x386db243, 0x5c0cfbf3, 0xde713766, 0xa06eef56, 0xa7654010, 0xbed0f798, 0x3637c80e, + 0x7cca10ec, 0x1e84ab9c, 0x02761705, 0xaa524f1c, 0xa0c6c15f, 0x04d8b956, 0xa74d4484, 0x60ded859, + 0x050e38e6, 0x3be1038f, 0x3304816d, 0xce0b306f, 0x33210569, 0x89bb26fb, 0x87aeb67d, 0xe007517e, + 0x0a96f7ac, 0x5cc4f96b, 0x4744e41d, 0xe3fa5eb8, 0x42558478, 0xf75e484b, 0x8635477d, 0x05432b1d, + 0xb88aec03, 0x763c061e, 0x431a480c, 0xed8ab7a7, 0x43c6131e, 0xdbef10ee, 0x833cfbec, 0xef4495b2, + 0x4e5154d8, 0x1d44112d, 0x1e5936fb, 0xc3c1347a, 0x610057ca, 0x16a567ea, 0x55d0559b, 0x36d97fe1, + 0xae7640d2, 0xb0ce01dc, 0xcbd5837a, 0x6bec9820, 0x349272c1, 0x375782f3, 0x36328a62, 0xae43900c, + 0x789b5cae, 0x0265138e, 0xc17168fd, 0xa031b0fe, 0xc3b08224, 0xa76979b1, 0xd0ebd2f5, 0xdc32c082, + 0x3c26c79e, 0xc1988d6d, 0xd0d422bb, 0x3eec330f, 0xdce1ccb9, 0x36774c6a, 0xbff91c14, 0x5f289f81, + 0x29328571, 0xc4487590, 0xd8ce4ab3, 0x2f148f44, 0xef5740fd, 0xd97508aa, 0x6ed6d146, 0xc31f5532, + 0x1f84fe18, 0xffd584fc, 0x481b5e71, 0x0e9586c3, 0xd3270828, 0x7b718338, 0x5463804c, 0xacb0569a, + 0x31ca80cf, 0xf3feef09, 0x7e24afbe, 0x3f53fea6, 0x334a8dc2, 0xa622d168, 0xea7bad66, 0xb043b6de, + 0x009525a1, 0x46753fa3, 0xec441114, 0x92bc95d7, 0x16a94ff0, 0x60976253, 0xf1410f2a, 0xeebe2471, + 0xcd087f94, 0x85b39360, 0x3f00075b, 0x83280fd8, 0x9f69d19a, 0xc32edad1, 0xb9a20190, 0x662a4e6b, + 0xa6aeda9d, 0x68d32aea, 0x9c0c0c2f, 0xed4a8cd2, 0x65579ee2, 0xa387099d, 0x5d32c4b4, 0x2b32d4c9, + 0x1e71e0b1, 0x90e64d64, 0x401ee371, 0x84f37ded, 0x78c8ed0e, 0x71c0ae76, 0x05bb7227, 0xfb6402ea, + 0xb56b48f3, 0xed3f9342, 0xd253139f, 0xec2afef7, 0xdb25471d, 0xc686913c, 0xfd11f08e, 0xf7367423, + 0x7a9ef5a4, 0x4450537e, 0xd3ca47d4, 0xe66d38eb, 0x7f9471d9, 0x4b69c64a, 0xea52f411, 0xb08afe22, + 0x598b6736, 0x2a80e6e8, 0x130465eb, 0x9edcecee, 0x05ecb15f, 0x9fe6596a, 0x896b595e, 0xca1af7bf, + 0x6a5bf944, 0xe4038571, 0x70e06229, 0xcfc4416f, 0xe3ccb1b2, 0xa807a67e, 0x847fe787, 0x4b52db93, + 0xdd7eec6c, 0x104824d4, 0x60049f69, 0x1848e674, 0xb92ce4f3, 0x7a502e4f, 0x6954d4df, 0xf3a78b2b, + 0xf31fffce, 0x3901263e, 0x89849517, 0x4b4cf0b0, 0xc49f9182, 0xa59dac4b, 0x2517af74, 0xd332cac9, + 0x848a89bc, 0xae0dcc89, 0x9cdba27c, 0xee91786a, 0x4e5d76ea, 0x69f56087, 0x02d46715, 0x3648afcf + }, { + 0x6fbfea07, 0x8f062d66, 0xf9fe9ac4, 0x758790f6, 0x0fadf7b8, 0x3d5a1076, 0xb32eb059, 0xcc2c35c7, + 0xcb2b5670, 0xc59637e3, 0x8a1b462f, 0x88c74622, 0x983226a7, 0x2286df61, 0x2f1cf48a, 0xaa09a187, + 0xd3aea9cc, 0x1c4500bd, 0x8687549a, 0xffef8752, 0x8fa18f1e, 0x355c89c1, 0x3a2dda1b, 0xc2b2162c, + 0x78e256f1, 0x97636bc1, 0xc98f56c5, 0xaa2c7f32, 0xaca8a6af, 0x88229120, 0x8b60e4de, 0x25424bf9, + 0x9c7fe31a, 0x3a89192c, 0x36d4057e, 0xc25869cc, 0x2f8b32c1, 0x7aeb8590, 0xa1a55039, 0x66c59227, + 0x584f20b0, 0x4383557e, 0x9ce2452b, 0x9012d8e4, 0x5683162c, 0xb3037916, 0x18612dad, 0x371f131a, + 0x739ce1e2, 0xfdd5807b, 0xfc87512d, 0x1fd7aa7b, 0xaf8e7a2c, 0xcdbb8df4, 0x727c1195, 0xe26fee0b, + 0x37deafb9, 0x8d8cde83, 0xb7670562, 0x568dc696, 0x62d70db6, 0x3646d6ba, 0xe6c88ebd, 0x106c2aea, + 0x5b6bff14, 0x463c82fa, 0x464330b1, 0x9b7d8a51, 0x79833e92, 0xb25d555b, 0x90ce5e6c, 0x98538e62, + 0xe56d0dc9, 0xc5cd572d, 0xe1ba5781, 0x728fb8e8, 0xdc134fe5, 0x15719dea, 0x8811b210, 0x7fd409d5, + 0x2c7f655b, 0x114c383b, 0xfb8d5068, 0xbf59b09e, 0x4a898094, 0x12181ac5, 0x4ad15389, 0x8ce82910, + 0xeab6ec1c, 0x8b17c746, 0xa8311525, 0xb1436ba2, 0x0bdbe29d, 0x11b09b87, 0xd2710e04, 0x82897729, + 0x7f41660a, 0xff480b1d, 0xfd24bb72, 0x9ba148c2, 0xce7f7bfe, 0xd986db88, 0xb01c3b85, 0x0733a8dc, + 0xe32e51bf, 0x97009a0e, 0x97c0061e, 0xb6d89d43, 0x6786c445, 0x88f8005f, 0x9e52a49a, 0x838aaac7, + 0x18c5ec75, 0x2fc3ceae, 0x18f92b1a, 0xf51aaeff, 0x33b50b53, 0xe8fda751, 0x64a2e1a8, 0x431722b6, + 0xd80acc80, 0x40ba3bae, 0x4a92d9d7, 0x1004df89, 0x2b189bee, 0x8a69776a, 0xb9f9f468, 0x6e1521a2, + 0x033b1ee6, 0x609b3062, 0x9b257e41, 0x52c58f9e, 0xc2f80810, 0x1121a169, 0x795e3788, 0x10ff6635, + 0xed6e1842, 0x1c6bb697, 0x6de5364e, 0xbfe4b47d, 0x05e0b920, 0xb8d5693a, 0xe0dcd5e3, 0x3e53acb9, + 0xad57a407, 0x1848ff77, 0x49ac2a76, 0x75478e2a, 0x63679f6d, 0x398c3530, 0x6fd53905, 0xad5b3a64, + 0x82bb0bca, 0xb1459952, 0x99363693, 0x442013af, 0x4402d836, 0x85923909, 0x974a4aff, 0xd763a687, + 0x24b5b5c7, 0x6fb40fed, 0x1452580c, 0xd37ba6d9, 0x5838bc79, 0x843bbda1, 0x061ad806, 0xeaa86bfd, + 0x0428694b, 0x9982ad37, 0x851b0efb, 0x735da8bd, 0x7558dccd, 0x6c63be0a, 0xe44ce748, 0x60042b30, + 0xdad815b9, 0x8f758186, 0x1c8dd496, 0x7c85705d, 0xd57b671c, 0xcea66708, 0x70660a4b, 0xd463e5b7, + 0xea828a5b, 0xe2ca6710, 0x8517eff4, 0x8a5f2a2f, 0x6af88297, 0xea1034d6, 0x3c5cc9eb, 0x46f849e1, + 0xf6bddeeb, 0xaaf192a9, 0xb018a0a6, 0x1f0fd33a, 0x31ff6ff3, 0xd3444345, 0x88f79a50, 0xcec19609, + 0x2cf2cc76, 0x82adba2c, 0x84188f77, 0x9c07d2c0, 0x4e839036, 0x434fa50b, 0x78ab043e, 0x09fbd64f, + 0xda902401, 0x613a3c6f, 0x4a697f0d, 0x02302beb, 0x84e0dbb4, 0x35d7eca9, 0x857d37bf, 0x4ea9ce58, + 0xa8c780e4, 0x486730d3, 0x2faf29eb, 0xa7b46a74, 0x923f0f3f, 0xaccaf3af, 0x94d94baf, 0x81ca43c0, + 0xa1482f0d, 0xd2d527b0, 0x85054bef, 0x934ddea3, 0xbbf03c30, 0x27308f4a, 0x3ee3eb4c, 0x2f9aed64, + 0xf082f13b, 0x7fcff4ba, 0xe1b0cb40, 0x57aabc7f, 0xf274c9d3, 0x220d43fa, 0x4e77f4d0, 0x7085d793, + 0xb6bf991f, 0x30f135de, 0xf0715ea7, 0x7b2d016b, 0x5333f064, 0xf388390a, 0x6ba63a6b, 0x432fd235, + 0xb5fd02cd, 0xaa5bbce9, 0x7e19a4d8, 0x81945d0e, 0xad776f9e, 0x93740ed6, 0x18c4e796, 0x19f5ad5f + } + } +}; + + + +static void SwapInts(uint32_t *p1, uint32_t *p2) { + uint32_t temp = *p1; + *p1 = *p2; + *p2 = temp; +} + +static uint32_t F(BfBlowfishContext *ctx, uint32_t x) { + uint32_t y = 0; + y ^= ctx->sbox[0][(x >> 24) & 0xFF]; + y += ctx->sbox[1][(x >> 16) & 0xFF]; + y ^= ctx->sbox[2][(x >> 8) & 0xFF]; + y += ctx->sbox[3][(x >> 0) & 0xFF]; + return y; +} + +static void BfEncryptBlock(BfBlowfishContext *ctx, uint32_t *xl, uint32_t *xr) { + uint32_t Xl = *xl; + uint32_t Xr = *xr; + for (int i = 0; i < 16; i++) { + Xl ^= ctx->parr[i]; + Xr ^= F(ctx, Xl); + + SwapInts(&Xl, &Xr); + } + + *xl = Xr ^ ctx->parr[17]; + *xr = Xl ^ ctx->parr[16]; +} + +static void BfDecryptBlock(BfBlowfishContext *ctx, uint32_t *xl, uint32_t *xr) { + uint32_t Xl = *xl; + uint32_t Xr = *xr; + for (int i = 17; i > 1; i--) { + Xl ^= ctx->parr[i]; + Xr ^= F(ctx, Xl); + + SwapInts(&Xl, &Xr); + } + + *xl = Xr ^ ctx->parr[0]; + *xr = Xl ^ ctx->parr[1]; +} + +void BfCtxInit(BfBlowfishContext *ctx, const unsigned char *key, unsigned int keylen) { + unsigned int k = 0; + for (int i = 0; i < 18; i++) { + ctx->parr[i] ^= key[(k++) % keylen] << 24; + ctx->parr[i] ^= key[(k++) % keylen] << 16; + ctx->parr[i] ^= key[(k++) % keylen] << 8; + ctx->parr[i] ^= key[(k++) % keylen] << 0; + } + + uint32_t l = 0, r = 0; + for (int i = 0; i < 9; i++) { + BfEncryptBlock(ctx, &l, &r); + ctx->parr[i * 2 + 0] = l; + ctx->parr[i * 2 + 1] = r; + } + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 128; j++) { + BfEncryptBlock(ctx, &l, &r); + ctx->sbox[i][j * 2 + 0] = l; + ctx->sbox[i][j * 2 + 1] = r; + } + } +} + +static void BfCtxMixKey(BfBlowfishContext *ctx, uint32_t *keyBufp, uint32_t keylen) { + BfEncryptBlock(ctx, keyBufp + 2, keyBufp + 1); + BfEncryptBlock(ctx, keyBufp + 1, keyBufp + 0); + BfCtxInit(ctx, (unsigned char *) keyBufp, keylen); +} + +static void BfCtxInitWithKey(BfBlowfishContext *ctx, uint32_t key, const unsigned char *fwHeader) { + memcpy(ctx, &sInitTable, sizeof(BfBlowfishContext)); + + uint32_t keyBufp[3]; + keyBufp[0] = key; + keyBufp[1] = key >> 1; + keyBufp[2] = key << 1; + BfCtxMixKey(ctx, keyBufp, 12); + + const uint32_t *blowfishedKeyp = (const uint32_t *) (fwHeader + 0x18); + uint32_t keycpy[2]; + memcpy(keycpy, blowfishedKeyp, sizeof(keycpy)); + + BfDecryptBlock(ctx, keycpy + 1, keycpy + 0); + BfCtxMixKey(ctx, keyBufp, 12); +} + +void BfDecrypt(unsigned char *buf, unsigned int len, const unsigned char *fwHeader) { + //init blowfish + BfBlowfishContext ctx; + BfCtxInitWithKey(&ctx, *(const uint32_t *) (fwHeader + 0x8), fwHeader); + + for (unsigned int i = 0; i < len / 8; i++) { + uint32_t *p = (uint32_t *) (buf + i * 8); + BfDecryptBlock(&ctx, p + 1, p + 0); + } +} + +void BfEncrypt(unsigned char *buf, unsigned int len, const unsigned char *fwHeader) { + //init blowfish + BfBlowfishContext ctx; + BfCtxInitWithKey(&ctx, *(const uint32_t *) (fwHeader + 0x8), fwHeader); + + for (unsigned int i = 0; i < len / 8; i++) { + uint32_t *p = (uint32_t *) (buf + i * 8); + BfEncryptBlock(&ctx, p + 1, p + 0); + } +} + + +BfStream *BfDecryptStreamInit(const unsigned char *buffer, unsigned int len, const unsigned char *fwHeader) { + BfStream *stream = (BfStream *) calloc(1, sizeof(BfStream)); + + stream->buffer = buffer; + stream->size = len; + stream->srcpos = 0; + stream->error = 0; + BfCtxInitWithKey(&stream->ctx, *(const uint32_t *) (fwHeader + 0x8), fwHeader); + + return stream; +} + +unsigned char BfDecryptStreamNext(BfStream *stream) { + unsigned int blockpos = stream->srcpos & 7; + if (blockpos == 0) { + //check bounds on next fetch + if ((stream->size - stream->srcpos) < 8) { + stream->error = 1; + return 0xFF; + } + + //blockpos==0: fetch next block + stream->block[0] = (stream->buffer[stream->srcpos + 0] << 0) + | (stream->buffer[stream->srcpos + 1] << 8) + | (stream->buffer[stream->srcpos + 2] << 16) + | (stream->buffer[stream->srcpos + 3] << 24); + stream->block[1] = (stream->buffer[stream->srcpos + 4] << 0) + | (stream->buffer[stream->srcpos + 5] << 8) + | (stream->buffer[stream->srcpos + 6] << 16) + | (stream->buffer[stream->srcpos + 7] << 24); + BfDecryptBlock(&stream->ctx, stream->block + 1, stream->block + 0); + } + + stream->srcpos++; + return (stream->block[blockpos / 4] >> ((blockpos & 3) * 8)) & 0xFF; +} + +void BfDecryptStreamEnd(BfStream *stream) { + //nothing + free(stream); +} + diff --git a/src/blowfish.h b/src/blowfish.h new file mode 100644 index 0000000..9de60aa --- /dev/null +++ b/src/blowfish.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +typedef struct BlowfishCtx_ { + unsigned int parr[18]; + unsigned int sbox[4][256]; +} BfBlowfishContext; + +typedef struct BfStream_ { + BfBlowfishContext ctx; + const unsigned char *buffer; + unsigned int size; + unsigned int srcpos; + uint32_t block[2]; + int error; +} BfStream; + +void BfDecrypt(unsigned char *buf, unsigned int len, const unsigned char *fwHeader); +void BfEncrypt(unsigned char *buf, unsigned int len, const unsigned char *fwHeader); + +BfStream *BfDecryptStreamInit(const unsigned char *buffer, unsigned int len, const unsigned char *fwHeader); +unsigned char BfDecryptStreamNext(BfStream *stream); +void BfDecryptStreamEnd(BfStream *stream); diff --git a/src/cmd_clean.c b/src/cmd_clean.c new file mode 100644 index 0000000..ac57ccd --- /dev/null +++ b/src/cmd_clean.c @@ -0,0 +1,252 @@ +#include "cmd_common.h" +#include "firmware.h" + +#include +#include + +//simple file format for backing up user config data from a firmware image +typedef struct FirmwareSettings_ { + uint16_t wlTableSize; // size of wireless table + unsigned char wlTable[512]; // wireless initialization table + + uint16_t connSettingSize; // size of connection settings + unsigned char connSetting[0x400]; // connection settings + + uint16_t connExSettingSize; // size of extra connection settings + unsigned char connExSetting[0x600]; // extended connection settings + + uint16_t userConfigSize; // size of user configg + unsigned char userConfig[0x200]; // user config +} FirmwareSettings; + +void CmdHelpClean(void) { + puts(""); + puts("Usage: clean"); + puts(""); + puts("Cleans the firmware image of the wireless initialization tables, user config,"); + puts("and wireless connection settings. The cleaned information can optionally be"); + puts("written to a file to restore later."); +} + + +void CmdProcClean(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + (void) argc; + (void) argv; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + FlashHeader *hdr = (FlashHeader *) buffer; + + char fname[1024]; + printf("Save config path (enter to skip): "); + fgets(fname, sizeof(fname), stdin); + + int len = strlen(fname); + for (int i = 0; i < len; i++) { + if (fname[i] == '\n' || fname[i] == '\r') { + fname[i] = '\0'; + break; + } + } + + //wireless table + unsigned char *wlTable = buffer + 0x2A; + unsigned int wlTableSize = 0x200 - 0x2A; + + //user config + unsigned int ncdOffset = (hdr->nvramUserConfigAddr * 4) * 2; + unsigned char *ncd = NULL; + unsigned int ncdSize = 0; + if (ncdOffset < size && (ncdOffset + 0x200) <= size) { + ncd = buffer + ncdOffset; + ncdSize = 0x200; + } + + //connection settings + unsigned char *connSettings = NULL; + unsigned int connSettingsSize = 0; + unsigned char *connExSettings = NULL; + unsigned int connExSettingsSize = 0; + if (ncdOffset >= 0x400 && ncd != NULL) { + connSettings = ncd - 0x400; + connSettingsSize = 0x400; + } + if (HasTwlSettings(hdr->ipl2Type) && ncdOffset >= 0xA00 && connSettings != NULL) { + connExSettings = connSettings - 0x600; + connExSettingsSize = 0x600; + } + + if (fname[0] != '\0') { + //write + FILE *fp = fopen(fname, "wb"); + if (fp != NULL) { + FirmwareSettings *settings = calloc(1, sizeof(FirmwareSettings)); + + settings->wlTableSize = wlTableSize; + settings->connSettingSize = connSettingsSize; + settings->connExSettingSize = connExSettingsSize; + settings->userConfigSize = ncdSize; + + if (wlTable != NULL) memcpy(settings->wlTable, wlTable, wlTableSize); + if (connSettings != NULL) memcpy(settings->connSetting, connSettings, connSettingsSize); + if (connExSettings != NULL) memcpy(settings->connExSetting, connExSettings, connExSettingsSize); + if (ncd != NULL) memcpy(settings->userConfig, ncd, ncdSize); + + fwrite(settings, sizeof(*settings), 1, fp); + fclose(fp); + free(settings); + } + } + + //erase + if (wlTable != NULL) { memset(wlTable, 0xFF, wlTableSize ); printf("Erased wireless init table.\n"); } + if (connSettings != NULL) { memset(connSettings, 0xFF, connSettingsSize ); printf("Erased connection settings.\n"); } + if (connExSettings != NULL) { memset(connExSettings, 0xFF, connExSettingsSize); printf("Erased extra connection settings.\n"); } + + if (ncd != NULL) { + for (int i = 0; i < 2; i++) { + FlashUserConfigData *ncdi = &((FlashUserConfigData *) ncd)[i]; + memset(ncdi, 0, FLASH_NCD_SIZE + FLASH_NCD_EX_SIZE); + + ncdi->saveCount = i & 0x7F; + ncdi->version = 5; + ncdi->crc = ComputeCrc(ncdi, FLASH_NCD_SIZE-4, 0xFFFF); + if (HasExConfig(hdr->ipl2Type)) { + int isKOR = (hdr->ipl2Type != IPL2_TYPE_NORMAL) && (hdr->ipl2Type & IPL2_TYPE_KOREAN); + int isCHN = (hdr->ipl2Type != IPL2_TYPE_NORMAL) && (hdr->ipl2Type & IPL2_TYPE_CHINESE); + int isUSG = (hdr->ipl2Type != IPL2_TYPE_NORMAL) && (hdr->ipl2Type & IPL2_TYPE_USG); + + //Korean or Chinese USG: fill by FF + if (isKOR || (isCHN && !isUSG)) memset(&ncdi->exVersion, 0xFF, FLASH_NCD_EX_SIZE); + ncdi->exVersion = 1; + ncdi->exLanguage = 1; // default to English + + //language mask: + //Korean: Korean, Spanish, German, French, English, Japanese + //Chinese: Chinese, Spanish, Italian, German, French, English + ncdi->languages = isKOR ? 0x00AF : 0x007E; + + ncdi->exCrc = ComputeCrc(&ncdi->exVersion, FLASH_NCD_EX_SIZE-2, 0xFFFF); + } else { + //no ex config: zero only main buffer + memset(&ncdi->exVersion, 0xFF, FLASH_NCD_EX_SIZE); + } + } + printf("Erased user configuration.\n"); + } +} + +void CmdHelpRestore(void) { + puts(""); + puts("Usage: restore "); + puts(""); + puts("Restores configuration information from a file written by the 'clean' command."); + puts("You wil be prompted whether to restore the wireless initialization, user"); + puts("configuration information, and wireless connection settings."); +} + +void CmdProcRestore(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + + if (argc < 2) { + CmdHelpRestore(); + return; + } + + const char *fname = argv[1]; + FILE *fp = fopen(fname, "rb"); + if (fp == NULL) { + printf("Could not open '%s' for read access.\n", fname); + return; + } + + unsigned int settingsSize; + fseek(fp, 0, SEEK_END); + settingsSize = ftell(fp); + fseek(fp, 0, SEEK_SET); + unsigned char *settingsbuf = malloc(settingsSize); + fread(settingsbuf, settingsSize, 1, fp); + fclose(fp); + + if (settingsSize != sizeof(FirmwareSettings)) { + printf("Invalid settings file.\n"); + free(settingsbuf); + return; + } + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + FlashHeader *hdr = (FlashHeader *) buffer; + + FirmwareSettings *settings = (FirmwareSettings *) settingsbuf; + + + //wireless table + unsigned char *wlTable = buffer + 0x2A; + unsigned int wlTableSize = 0x200 - 0x2A; + + //user config + unsigned int ncdOffset = (hdr->nvramUserConfigAddr * 4) * 2; + unsigned char *ncd = NULL; + unsigned int ncdSize = 0; + if (ncdOffset < size && (ncdOffset + 0x200) <= size) { + ncd = buffer + ncdOffset; + ncdSize = 0x200; + } + + //connection settings + unsigned char *connSettings = NULL; + unsigned int connSettingsSize = 0; + unsigned char *connExSettings = NULL; + unsigned int connExSettingsSize = 0; + if (ncdOffset >= 0x400 && ncd != NULL) { + connSettings = ncd - 0x400; + connSettingsSize = 0x400; + } + if (HasTwlSettings(hdr->ipl2Type) && ncdOffset >= 0xA00 && connSettings != NULL) { + connExSettings = connSettings - 0x600; + connExSettingsSize = 0x600; + } + + char textbuffer[1024]; + if (wlTable != NULL) { + printf("Restore wireless init table? (y/n) "); + fgets(textbuffer, sizeof(textbuffer), stdin); + + if (toupper(textbuffer[0]) == 'Y') { + if (settings->wlTableSize < wlTableSize) wlTableSize = settings->wlTableSize; + memcpy(wlTable, settings->wlTable, wlTableSize); + } + } + if (ncd != NULL) { + printf("Restore user config? (y/n) "); + fgets(textbuffer, sizeof(textbuffer), stdin); + + if (toupper(textbuffer[0]) == 'Y') { + if (settings->userConfigSize < ncdSize) ncdSize = settings->userConfigSize; + memcpy(ncd, settings->userConfig, settings->userConfigSize); + } + } + if (connSettings != NULL) { + printf("Restore connection settings? (y/n) "); + fgets(textbuffer, sizeof(textbuffer), stdin); + + if (toupper(textbuffer[0]) == 'Y') { + if (settings->connSettingSize < connSettingsSize) connSettingsSize = settings->connSettingSize; + memcpy(connSettings, settings->connSetting, settings->connSettingSize); + } + } + if (connExSettings != NULL) { + printf("Restore extended connection settings? (y/n) "); + fgets(textbuffer, sizeof(textbuffer), stdin); + + if (toupper(textbuffer[0]) == 'Y') { + if (settings->connExSettingSize < connExSettingsSize) connExSettingsSize = settings->connExSettingSize; + memcpy(connExSettings, settings->connExSetting, connExSettingsSize); + } + } + + free(settingsbuf); +} diff --git a/src/cmd_common.c b/src/cmd_common.c new file mode 100644 index 0000000..5321c63 --- /dev/null +++ b/src/cmd_common.c @@ -0,0 +1,149 @@ +#include "cmd_common.h" +#include "firmware.h" + +#include +#include +#include +#include + + +static char *gFirmwarePath = NULL; +static unsigned char *gFirmware = NULL; +static unsigned int gFirmwareSize = 0; +static int gQuit = 0; + + +const char *GetCurrentFilePath(void) { + return gFirmwarePath; +} + +unsigned char *GetFirmwareImage(unsigned int *pSize) { + *pSize = gFirmwareSize; + return gFirmware; +} + +int LoadFirmwareImage(const char *path) { + FILE *fp = fopen(path, "rb"); + if (fp == NULL) { + printf("Could not open file '%s' for read acces.\n", path); + return 0; + } + + fseek(fp, 0, SEEK_END); + unsigned int size = ftell(fp); + fseek(fp, 0, SEEK_SET); + unsigned char *buf = malloc(size); + fread(buf, size, 1, fp); + fclose(fp); + + if (size < (4 * 1024)) { + printf("Invalid firmware image size (%d bytes).\n", size); + free(buf); + return 0; + } + + if (gFirmwarePath != NULL) { + free(gFirmwarePath); + } + if (gFirmware != NULL) { + free(gFirmware); + } + + gFirmwarePath = strdup(path); + gFirmware = buf; + gFirmwareSize = size; + printf("Loaded %s.\n", gFirmwarePath); + return 1; +} + +int RequireFirmwareImage(void) { + if (gFirmware == NULL) { + puts("No valid firmware image loaded."); + puts("Load a firmware image using the 'load' command."); + return 0; + } + + return 1; +} + +int IsExiting(void) { + return gQuit; +} + +void DoExit(void) { + gQuit = 1; +} + +uint32_t ParseArgNumber(const char *arg) { + if (*arg == '\0') return 0; + + //parse number. Assume hexadecimal by default. + int radix = 16; + uint32_t val = 0; + + if (gFirmware != NULL && gFirmwareSize >= 0x200 && arg[0] == '$') { + //pseudo-variable expansion + arg++; + + FlashHeader *hdr = (FlashHeader *) gFirmware; + uint32_t arm9StaticRomAddr = (hdr->arm9StaticRomAddr * 4) << hdr->arm9RomAddrScale; + uint32_t arm7StaticRomAddr = (hdr->arm7StaticRomAddr * 4) << hdr->arm7RomAddrScale; + uint32_t arm9SecondaryRomAddr = (hdr->arm9SecondaryRomAddr * 4) * 2; + uint32_t arm7SecondaryRomAddr = (hdr->arm7SecondaryRomAddr * 4) * 2; + uint32_t rsrcRomAddr = (hdr->resourceRomAddr * 4) * 2; + + uint32_t ncdaddr = (hdr->nvramUserConfigAddr * 4) * 2; + uint32_t connaddr = ncdaddr - 0x400; + uint32_t connexaddr = connaddr - 0x600; + + if (stricmp(arg, "arm9") == 0) return arm9StaticRomAddr; + if (stricmp(arg, "arm7") == 0) return arm7StaticRomAddr; + if (stricmp(arg, "arm9s") == 0) return arm9SecondaryRomAddr; + if (stricmp(arg, "arm7s") == 0) return arm7SecondaryRomAddr; + if (stricmp(arg, "rsrc") == 0) return rsrcRomAddr; + if (stricmp(arg, "ncd") == 0) return ncdaddr; + if (stricmp(arg, "ncd0") == 0) return ncdaddr; + if (stricmp(arg, "ncd1") == 0) return ncdaddr + 0x100; + if (stricmp(arg, "conn") == 0) return connaddr; + if (stricmp(arg, "conn0") == 0) return connaddr + 0x000; + if (stricmp(arg, "conn1") == 0) return connaddr + 0x100; + if (stricmp(arg, "conn2") == 0) return connaddr + 0x200; + if (stricmp(arg, "connex") == 0) return connexaddr; + if (stricmp(arg, "connex0") == 0) return connexaddr + 0x000; + if (stricmp(arg, "connex1") == 0) return connexaddr + 0x200; + if (stricmp(arg, "connex2") == 0) return connexaddr + 0x400; + + return val; + } + + if (arg[0] == '0') { + if (arg[1] == 'x' || arg[1] == 'X') { + radix = 16; // hexadecimal + arg += 2; + } else if (arg[1] == 'n' || arg[1] == 'N') { + radix = 10; // decimal + arg += 2; + } else if (arg[1] == 'o' || arg[1] == 'O') { + radix = 8; // octal + arg += 2; + } + } + + char c; + while ((c = *(arg++)) != '\0') { + int digit = -1; + if (c >= '0' && c <= '9') digit = (c - '0') + 0x0; + if (c >= 'A' && c <= 'F') digit = (c - 'A') + 0xA; + if (c >= 'a' && c <= 'f') digit = (c - 'a') + 0xA; + + //chech valid digit + if (digit == -1 || digit >= radix) { + return val; + } + + val *= (unsigned int) radix; + val += (unsigned int) digit; + } + + return val; +} diff --git a/src/cmd_common.h b/src/cmd_common.h new file mode 100644 index 0000000..a9650b3 --- /dev/null +++ b/src/cmd_common.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +// +// Get the current open file path. +// +const char *GetCurrentFilePath(void); + +// +// Get the currently open firmware image buffer. +// +unsigned char *GetFirmwareImage(unsigned int *pSize); + +// +// Load a firmware image from a file path. +// +int LoadFirmwareImage(const char *path); + +// +// Returns 1 if a firmware image is open, 0 otherwise. +// +int RequireFirmwareImage(void); + +// +// Get the current command processor exiting status. +// +int IsExiting(void); + +// +// Start the command processor exiting process. +// +void DoExit(void); + +// +// Parse a number from an argument list. +// +uint32_t ParseArgNumber(const char *arg); + + +// ----- command procs + +void CmdProcHelp(int argc, const char **argv); +void CmdProcInfo(int argc, const char **argv); +void CmdProcVerify(int argc, const char **argv); +void CmdProcMap(int argc, const char **argv); +void CmdProcLoad(int argc, const char **argv); +void CmdProcSave(int argc, const char **argv); +void CmdProcClean(int argc, const char **argv); +void CmdProcRestore(int argc, const char **argv); +void CmdProcExport(int argc, const char **argv); +void CmdProcImport(int argc, const char **argv); +void CmdProcLoc(int argc, const char **argv); +void CmdProcEB(int argc, const char **argv); +void CmdProcDB(int argc, const char **argv); +void CmdProcMD5(int argc, const char **argv); +void CmdProcUser(int argc, const char **argv); +void CmdProxFix(int argc, const char **argv); +void CmdProcCompact(int argc, const char **argv); +void CmdProcQuit(int argc, const char **argv); + + +// ----- command help procs +void CmdHelpHelp(void); +void CmdHelpInfo(void); +void CmdHelpVerify(void); +void CmdHelpMap(void); +void CmdHelpLoad(void); +void CmdHelpSave(void); +void CmdHelpClean(void); +void CmdHelpRestore(void); +void CmdHelpExport(void); +void CmdHelpImport(void); +void CmdHelpLoc(void); +void CmdHelpEB(void); +void CmdHelpDB(void); +void CmdHelpMD5(void); +void CmdHelpUser(void); +void CmdHelpFix(void); +void CmdHelpCompact(void); +void CmdHelpQuit(void); + diff --git a/src/cmd_compact.c b/src/cmd_compact.c new file mode 100644 index 0000000..76b2d3f --- /dev/null +++ b/src/cmd_compact.c @@ -0,0 +1,121 @@ +#include "cmd_common.h" +#include "firmware.h" +#include "blowfish.h" + +#include + +void CmdHelpCompact(void) { + puts(""); + puts("Usage: compact"); + puts(""); + puts("Recompresses each of the firmware's modules. Recompressing the modules may free"); + puts("up space to be used for larger data."); +} + +void CmdProcCompact(int argc, const char **argv) { + //compact the firmware. We do this by recompressing the binaries and relocating + //them to save as much space as possible. We will use the lower granularity + //wherever appropriate to move all unused space to the end. + (void) argc; + (void) argv; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + if (arm9Static == NULL || arm7Static == NULL || arm9Secondary == NULL || arm7Secondary == NULL || rsrc == NULL) { + if (arm9Static == NULL) printf("The ARM9 static module could not be decompressed.\n"); + if (arm7Static == NULL) printf("The ARM7 static module could not be decompressed.\n"); + if (arm9Secondary == NULL) printf("The ARM9 secondary module could not be decompressed.\n"); + if (arm7Secondary == NULL) printf("The ARM7 secondary module could not be decompressed.\n"); + if (rsrc == NULL) printf("The resources pack could not be decompressed.\n"); + goto End; + } + puts(""); + + //recompress each module + + unsigned int arm9StaticRecompSize, arm7StaticRecompSize, arm9SecondaryRecompSize, arm7SecondaryRecompSize, rsrcRecompSize; + + unsigned char *arm9StaticRecomp = CxCompressLZ(arm9Static, arm9StaticUncompressed, &arm9StaticRecompSize); + arm9StaticRecomp = CxPadCompressed(arm9StaticRecomp, arm9StaticRecompSize, 8, &arm9StaticRecompSize); + printf("ARM9 static : %08X -> %08X\n", arm9StaticSize, arm9StaticRecompSize); + + unsigned char *arm7StaticRecomp = CxCompressLZ(arm7Static, arm7StaticUncompressed, &arm7StaticRecompSize); + arm7StaticRecomp = CxPadCompressed(arm7StaticRecomp, arm7StaticRecompSize, 8, &arm7StaticRecompSize); + printf("ARM7 static : %08X -> %08X\n", arm7StaticSize, arm7StaticRecompSize); + + unsigned char *arm9SecondaryRecomp = CxCompressAshFirmware(arm9Secondary, arm9SecondaryUncompressed, &arm9SecondaryRecompSize); + arm9SecondaryRecomp = CxPadCompressed(arm9SecondaryRecomp, arm9SecondaryRecompSize, 8, &arm9SecondaryRecompSize); + printf("ARM9 secondary: %08X -> %08X\n", arm9SecondarySize, arm9SecondaryRecompSize); + + unsigned char *arm7SecondaryRecomp = CxCompressAshFirmware(arm7Secondary, arm7SecondaryUncompressed, &arm7SecondaryRecompSize); + arm7SecondaryRecomp = CxPadCompressed(arm7SecondaryRecomp, arm7SecondaryRecompSize, 8, &arm7SecondaryRecompSize); + printf("ARM7 secondary: %08X -> %08X\n", arm7SecondarySize, arm7SecondaryRecompSize); + + unsigned char *rsrcRecomp = CxCompressAshFirmware(rsrc, rsrcUncompressed, &rsrcRecompSize); + rsrcRecomp = CxPadCompressed(rsrcRecomp, rsrcRecompSize, 8, &rsrcRecompSize); + printf("Resources : %08X -> %08X\n", rsrcSize, rsrcRecompSize); + + unsigned int size1 = arm9StaticSize + arm7StaticSize + arm9SecondarySize + arm7SecondarySize + rsrcSize; + unsigned int size2 = arm9StaticRecompSize + arm7StaticRecompSize + arm9SecondaryRecompSize + arm7SecondaryRecompSize + rsrcRecompSize; + puts(""); + printf("Total saved: %08X\n", size1 - size2); + + //encrypt modules + BfEncrypt(arm9StaticRecomp, arm9StaticRecompSize, buffer); + BfEncrypt(arm7StaticRecomp, arm7StaticRecompSize, buffer); + + //place updated module + unsigned int offs = 0x200; + memcpy(buffer + offs, arm9StaticRecomp, arm9StaticRecompSize); + hdr->arm9RomAddrScale = 1; // 8x + hdr->arm9StaticRomAddr = (offs / 8); + + offs += arm9StaticRecompSize; + memcpy(buffer + offs, arm7StaticRecomp, arm7StaticRecompSize); + hdr->arm7RomAddrScale = 1; // 8x + hdr->arm7StaticRomAddr = (offs / 8); + + offs += arm7StaticRecompSize; + memcpy(buffer + offs, arm9SecondaryRecomp, arm9SecondaryRecompSize); + hdr->arm9SecondaryRomAddr = (offs / 8); + + offs += arm9SecondaryRecompSize; + memcpy(buffer + offs, arm7SecondaryRecomp, arm7SecondaryRecompSize); + hdr->arm7SecondaryRomAddr = (offs / 8); + + offs += arm7SecondaryRecompSize; + memcpy(buffer + offs, rsrcRecomp, rsrcRecompSize); + hdr->resourceRomAddr = (offs / 8); + + + free(arm9StaticRecomp); + free(arm7StaticRecomp); + free(arm9SecondaryRecomp); + free(arm7SecondaryRecomp); + free(rsrcRecomp); + +End: + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); +} diff --git a/src/cmd_eb.c b/src/cmd_eb.c new file mode 100644 index 0000000..d8c3d3d --- /dev/null +++ b/src/cmd_eb.c @@ -0,0 +1,112 @@ +#include "cmd_common.h" +#include "firmware.h" + +void CmdHelpEB(void) { + puts(""); + puts("Usage: eb
"); + puts(""); + puts("Bytes are separated by spaces. The byte values and address are interpreted in"); + puts("hexadecimal by default. Prefix with '0n' to input decimal, or '0o' for octal."); + puts("Hexadecimal values may optionally be prefixed with '0x' or suffixed with 'h'."); +} + +void CmdProcEB(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + + if (argc < 2) { + CmdHelpEB(); + return; + } + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + uint32_t addr = ParseArgNumber(argv[1]); + if (addr >= size) { + printf("Address %08X is out of bounds.\n", addr); + return; + } + + int i; + for (i = 2; i < argc && addr < size; i++) { + uint8_t bval = ParseArgNumber(argv[i]); + buffer[addr++] = bval; + } + + if (i < argc) { + puts(""); + printf("Write truncated to %d byte(s).\n", i - 2); + } +} + +void CmdHelpDB(void) { + puts(""); + puts("Usage: db
[size]"); + puts(""); + puts("Dumps the bytes at the specified address. If size is not specified, then up to"); + puts("128 bytes are dumped. The address is interpreted in hexadecimal by default."); + puts("Prefix with '0n' to input decimal, or '0o' for octal. Hexadecimal values may"); + puts("optionally be prefixed with '0x' or suffixed with 'h'."); +} + +void CmdProcDB(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + + if (argc < 2) { + CmdHelpDB(); + return; + } + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + uint32_t addr = ParseArgNumber(argv[1]); + + if (addr >= size) { + printf("Address %08X is out of bounds.\n", addr); + return; + } + + uint32_t nBytes = 0x80; + if (nBytes > (size - addr)) nBytes = size - addr; + if (argc >= 3) { + nBytes = ParseArgNumber(argv[2]); + } + + if (nBytes > (size - addr)) { + nBytes = size - addr; + printf("Dump truncated to %d bytes.\n", nBytes); + } + + puts(""); + printf(" | 0 1 2 3 4 5 6 7 8 9 A B C D E F | 0123456789ABCDEF \n"); + printf("----------+-------------------------------------------------+------------------\n"); + + uint32_t rowAddr = (addr & ~0xF); + uint32_t rowLast = (addr + nBytes - 1) & ~0xF; + while (rowAddr <= rowLast) { + printf(" %08X | ", rowAddr); + + for (unsigned int i = 0; i < 16; i++) { + uint32_t baddr = rowAddr + i; + if (baddr >= addr && baddr < (addr + nBytes)) printf("%02X ", buffer[baddr]); + else printf(" "); + } + + printf("| "); + for (unsigned int i = 0; i < 16; i++) { + uint32_t baddr = rowAddr + i; + if (baddr >= addr && baddr < (addr + nBytes)) { + unsigned char b = buffer[baddr]; + if (b < 0x20 || b >= 0x7F) b = '.'; + printf("%c", b); + } + else printf(" "); + } + + printf("\n"); + + rowAddr += 0x10; + } +} + diff --git a/src/cmd_export.c b/src/cmd_export.c new file mode 100644 index 0000000..e447cea --- /dev/null +++ b/src/cmd_export.c @@ -0,0 +1,386 @@ +#include "cmd_common.h" +#include "firmware.h" +#include "blowfish.h" +#include "compression.h" + +#include + +void CmdHelpExport(void) { + puts(""); + puts("Usage: export [flags...]"); + puts(""); + puts("Use one of the following for the module parameter:"); + puts(" arm9 ARM9 Static module"); + puts(" arm7 ARM7 static module"); + puts(" arm9s ARM9 Secondary module"); + puts(" arm7s ARM7 Secondary module"); + puts(" rsrc Resources pack"); + puts(""); + puts("Flags:"); + puts(" -e Do not decrypt the module (ARM7 and ARM9 static)"); + puts(" -c Do not decompress the module"); +} + +void CmdProcExport(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + + if (argc < 3) { + CmdHelpExport(); + return; + } + + int decrypt = 1, decompress = 1; + const char *modname = argv[1]; + const char *filename = argv[2]; + + for (int i = 3; i < argc; i++) { + if (strcmp(argv[i], "-e") == 0) { + decrypt = 0; + decompress = 0; + } else if (strcmp(argv[i], "-c") == 0) { + decompress = 0; + } else { + printf("Unrecognized flag %s.\n", argv[i]); + } + } + + unsigned char *result = NULL; + unsigned int resultSize = 0; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + //decompress module + CxCompressionType type9, type7, typeRsrc; + if (strcmp(modname, "arm9") == 0) { + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + + if (decompress) { + result = arm9Static; + resultSize = arm9StaticUncompressed; + } else { + free(arm9Static); + resultSize = arm9StaticSize; + result = malloc(resultSize); + memcpy(result, buffer + arm9StaticRomAddr, resultSize); + + if (decrypt) { + //decrypt + BfDecrypt(result, resultSize, buffer); + } + } + } else if (strcmp(modname, "arm7") == 0) { + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + + if (decompress) { + result = arm7Static; + resultSize = arm7StaticUncompressed; + } else { + free(arm7Static); + resultSize = arm7StaticSize; + result = malloc(resultSize); + memcpy(result, buffer + arm7StaticRomAddr, resultSize); + + if (decrypt) { + //decrypt + BfDecrypt(result, resultSize, buffer); + } + } + } else if (strcmp(modname, "arm9s") == 0) { + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + + if (decompress) { + //write decompressed + result = arm9Secondary; + resultSize = arm9SecondaryUncompressed; + } else { + //copy compressed + free(arm9Secondary); + resultSize = arm9SecondarySize; + result = malloc(resultSize); + memcpy(result, buffer + arm9SecondaryRomAddr, resultSize); + } + } else if (strcmp(modname, "arm7s") == 0) { + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + + if (decompress) { + //write decompressed + result = arm7Secondary; + resultSize = arm7SecondaryUncompressed; + } else { + //copy compressed + free(arm7Secondary); + resultSize = arm7SecondarySize; + result = malloc(resultSize); + memcpy(result, buffer + arm7SecondaryRomAddr, resultSize); + } + } else if (strcmp(modname, "rsrc") == 0) { + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + if (decompress) { + //write decompressed + result = rsrc; + resultSize = rsrcUncompressed; + } else { + //copy compressed + free(rsrc); + resultSize = rsrcSize; + result = malloc(resultSize); + memcpy(result, buffer + rsrcRomAddr, resultSize); + } + } else { + printf("Unknown module name '%s'.\n", modname); + return; + } + + if (result == NULL) { + puts("Could not extract module."); + return; + } + + FILE *fp = fopen(filename, "wb"); + if (fp == NULL) { + printf("Could not open '%s' for write access.\n", filename); + free(result); + return; + } + + fwrite(result, resultSize, 1, fp); + fclose(fp); + + free(result); +} + +void CmdHelpImport(void) { + puts(""); + puts("Usage: import [flags...]"); + puts(""); + puts("Import a module into this firmware image. The source file may be uncompressed,"); + puts("compressed, or compressed and encrypted. Only the ARM9 and ARM7 static modules"); + puts("are encrypted."); + puts(""); + puts("Use one of the following for the module parameter:"); + puts(" arm9 ARM9 Static module"); + puts(" arm7 ARM7 static module"); + puts(" arm9s ARM9 Secondary module"); + puts(" arm7s ARM7 Secondary module"); + puts(" rsrc Resources pack"); + puts(""); + puts("Flags:"); + puts(" -c Imported module is compressed."); + puts(" -e Imported module is compressed and encrypted."); +} + +void CmdProcImport(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + + if (argc < 2) { + CmdHelpImport(); + return; + } + + int decrypt = 1, decompress = 1; + const char *modname = argv[1]; + const char *filename = argv[2]; + + for (int i = 3; i < argc; i++) { + if (strcmp(argv[i], "-e") == 0) { + decrypt = 0; + decompress = 0; + } else if (strcmp(argv[i], "-c") == 0) { + decompress = 0; + } else { + printf("Unrecognized flag %s.\n", argv[i]); + } + } + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + + FILE *fp = fopen(filename, "rb"); + if (fp == NULL) { + printf("Could not open '%s' for read access.\n", filename); + return; + } + + unsigned int inSize, padSize; + unsigned char *inbuf; + fseek(fp, 0, SEEK_END); + inSize = ftell(fp); + fseek(fp, 0, SEEK_SET); + + //calculate padded size + if (!decompress) { + //pad size: for compressed (regardless of encryption), pad to 8. + padSize = (inSize + 7) & ~7; + } else { + //else: no padding + padSize = inSize; + } + + inbuf = calloc(padSize, 1); + fread(inbuf, inSize, 1, fp); + fclose(fp); + inSize = padSize; + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + //locate the load addresses for secondary modules and resources pack + GetSecondaryResourceLoadAddresses(arm9Static, arm9StaticUncompressed, arm7Static, arm7StaticUncompressed, &arm9SecondaryRamAddr, &arm7SecondaryRamAddr, &rsrcRamAddr); + + //can discard uncompressed binaries + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); + + //pull out compressed+encrypted modules + arm9Static = malloc(arm9StaticSize); + arm7Static = malloc(arm7StaticSize); + arm9Secondary = malloc(arm9SecondarySize); + arm7Secondary = malloc(arm7SecondarySize); + rsrc = malloc(rsrcSize); + + memcpy(arm9Static, buffer + arm9StaticRomAddr, arm9StaticSize); + memcpy(arm7Static, buffer + arm7StaticRomAddr, arm7StaticSize); + memcpy(arm9Secondary, buffer + arm9SecondaryRomAddr, arm9SecondarySize); + memcpy(arm7Secondary, buffer + arm7SecondaryRomAddr, arm7SecondarySize); + memcpy(rsrc, buffer + rsrcRomAddr, rsrcSize); + + //get module name + const char *const modnames[] = { "arm9", "arm7", "arm9s", "arm7s", "rsrc" }; + int modno = -1; + for (int i = 0; i < 5; i++) { + if (strcmp(modname, modnames[i]) == 0) { + modno = i; + break; + } + } + + uint32_t modSizes[] = { arm9StaticSize, arm7StaticSize, arm9SecondarySize, arm7SecondarySize, rsrcSize }; + unsigned char *mods[] = { arm9Static, arm7Static, arm9Secondary, arm7Secondary, rsrc }; + CxCompressionType modComps[] = { CX_COMPRESSION_NONE, CX_COMPRESSION_NONE, type9, type7, typeRsrc }; + + //replace module + { + free(mods[modno]); + modSizes[modno] = 0; + + if (decompress) { + //source file is uncompressed, unencrypted. + if (modno == 0 || modno == 1) { + //ARM9 or ARM7 static: must be LZ compressed, then encrypted + unsigned int compSize; + unsigned char *comp = CxCompressLZ(inbuf, inSize, &compSize); + comp = CxPadCompressed(comp, compSize, 8, &compSize); + free(inbuf); + + //encrypt + BfEncrypt(comp, compSize, buffer); + + mods[modno] = comp; + modSizes[modno] = compSize; + } else { + //secondary/resource: must be either LZ or ASH compressed + unsigned char *comp = NULL; + unsigned int compSize = 0; + if (modComps[modno] == CX_COMPRESSION_ASH) { + //ASH compress + printf("Compressing...\n"); + comp = CxCompressAshFirmware(inbuf, inSize, &compSize); + printf("Done.\n"); + } else { + //LZ compress + comp = CxCompressLZ(inbuf, inSize, &compSize); + } + comp = CxPadCompressed(comp, compSize, 8, &compSize); + free(inbuf); + + mods[modno] = comp; + modSizes[modno] = compSize; + } + } else if (decrypt) { + //source file is compressed, unencrypted. + if (modno == 0 || modno == 1) { + //static modules: must be encrypted + BfEncrypt(inbuf, inSize, buffer); + } else { + //secondary/resource modules: not encrypted + } + mods[modno] = inbuf; + modSizes[modno] = inSize; + } else { + //source file is compressed, encrypted. + mods[modno] = inbuf; + modSizes[modno] = inSize; + } + } + + //check size + uint32_t totalSize = 0; + for (int i = 0; i < 5; i++) { + totalSize += modSizes[i]; + totalSize = (totalSize + 7) & ~7; + } + + //get max address + uint32_t maxAddr = hdr->nvramUserConfigAddr * 8 - 0x400; + if (HasTwlSettings(hdr->ipl2Type)) maxAddr -= 0x600; + if (maxAddr > size) maxAddr = size; + + if ((totalSize + 0x200) >= maxAddr) { + printf("The module is too large.\n"); + goto End; + } + + //write modules + uint32_t curOffs = 0x200; + for (int i = 0; i < 5; i++) { + //write offset + switch (i) { + case 0: hdr->arm9StaticRomAddr = curOffs / 8; hdr->arm9RomAddrScale = 1; break; + case 1: hdr->arm7StaticRomAddr = curOffs / 8; hdr->arm7RomAddrScale = 1; break; + case 2: hdr->arm9SecondaryRomAddr = curOffs / 8; break; + case 3: hdr->arm7SecondaryRomAddr = curOffs / 8; break; + case 4: hdr->resourceRomAddr = curOffs / 8; break; + } + + //get source + unsigned char *src = mods[i]; + unsigned int modSize = modSizes[i]; + memcpy(buffer + curOffs, src, modSize); + + curOffs += modSize; + curOffs = (curOffs + 7) & ~7; + } + + UpdateFirmwareModuleChecksums(buffer, size); + +End: + for (int i = 0; i < 5; i++) { + if (mods[i] != NULL) free(mods[i]); + } +} + diff --git a/src/cmd_fix.c b/src/cmd_fix.c new file mode 100644 index 0000000..664a1bb --- /dev/null +++ b/src/cmd_fix.c @@ -0,0 +1,169 @@ +#include "cmd_common.h" +#include "firmware.h" + +void CmdHelpFix(void) { + puts(""); + puts("Usage: fix"); + puts(""); + puts("Fixes incorrect fields in the firmware. The CRCs of the static, secondary, and"); + puts("resource modules are corrected. The CRCs of the wireless initialization tables,"); + puts("user configuration, and wireless connection settings are corrected."); +} + +static void UpgradeNcdVersion(FlashUserConfigData *ncd) { + if (ncd->version == 5) return; // correct + + printf("Unspported user configuration version %d.\n", ncd->version); +} + +void CmdProxFix(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + (void) argc; + (void) argv; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + FlashHeader *hdr = (FlashHeader *) buffer; + FlashRfBbInfo *wl = (FlashRfBbInfo *) (buffer + 0x2A); + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + + //1. correct static module CRC + if (arm9Static != NULL && arm7Static != NULL) { + uint16_t staticCrc = hdr->staticCrc; + uint16_t staticCrc2 = ComputeStaticCrc(arm9Static, arm9StaticUncompressed, arm7Static, arm7StaticUncompressed); + if (staticCrc != staticCrc2) { + hdr->staticCrc = staticCrc2; + printf("Corrected static module CRC (%04X -> %04X)\n", staticCrc, staticCrc2); + } + } else { + puts("Could not decompress static modules."); + } + + //2. correct secondary module CRC + if (arm9Secondary != NULL && arm7Secondary != NULL) { + uint16_t secondaryCrc = hdr->secondaryCrc; + uint16_t secondaryCrc2 = ComputeSecondaryCrc(arm9Secondary, arm9SecondaryUncompressed, arm7Secondary, arm7SecondaryUncompressed); + if (secondaryCrc != secondaryCrc2) { + hdr->secondaryCrc = secondaryCrc2; + printf("Corrected secondary module CRC (%04X -> %04X)\n", secondaryCrc, secondaryCrc2); + } + } else { + puts("Could not decompress secondary modules."); + } + + //3. correct resources CRC + if (rsrc != NULL) { + uint16_t rsrcCrc = hdr->resourceCrc; + uint16_t rsrcCrc2 = ComputeCrc(rsrc, rsrcUncompressed, 0xFFFF); + if (rsrcCrc != rsrcCrc2) { + hdr->resourceCrc = rsrcCrc2; + printf("Corrected resources pack CRC (%04X -> %04X)\n", rsrcCrc, rsrcCrc2); + } + } else { + puts("Could not decompress resources pack."); + } + + //4. correct wireless table CRC + { + uint16_t wlCrc = wl->crc; + uint16_t wlCrc2 = ComputeCrc(buffer + 0x2A + 2, wl->tableSize, 0); + if (wl->tableSize < (0x200 - 0x2E) && wlCrc != wlCrc2) { + wl->crc = wlCrc2; + printf("Corrected wireless init CRC (%04X -> %04X)\n", wlCrc, wlCrc2); + } + } + + int hasExConfig = HasExConfig(hdr->ipl2Type); + int hasTwlConfig = HasTwlSettings(hdr->ipl2Type); + unsigned int ncdAddr = hdr->nvramUserConfigAddr * 8; + + //5. correct connection settings CRCs + if (ncdAddr >= 0x400) { + unsigned int connAddr = ncdAddr - 0x400; + for (int i = 0; i < 3; i++) { + FlashConnSetting *conn = (FlashConnSetting *) (buffer + connAddr + i * 0x100); + if (conn->setType == 0xFF) continue; + + //check CRC + uint16_t crc = conn->crc; + uint16_t crc2 = ComputeCrc(conn, sizeof(FlashConnSetting)-2, 0); + if (crc != crc2) { + conn->crc = crc2; + printf("Corrected connection %d CRC (%04X -> %04X)\n", i, crc, crc2); + } + } + + if (hasTwlConfig && connAddr >= 0x600) { + unsigned int connExAddr = connAddr - 0x600; + for (int i = 0; i < 3; i++) { + FlashConnExSetting *conn = (FlashConnExSetting *) (buffer + connExAddr + i * 0x200); + if (conn->base.setType == 0xFF) continue; + + uint16_t crc = conn->base.crc; + uint16_t crc2 = ComputeCrc(&conn->base, sizeof(FlashConnSetting)-2, 0); + if (crc != crc2) { + conn->base.crc = crc2; + printf("Corrected connection %d CRC (%04X -> %04X)\n", i + 3, crc, crc2); + } + + uint16_t exCrc = conn->exCrc; + uint16_t exCrc2 = ComputeCrc(&conn->base + 1, sizeof(FlashConnExSetting)-sizeof(FlashConnSetting)-2, 0); + if (exCrc != exCrc2) { + printf("Corrected connection %d CRC (%04X -> %04X)\n", i + 3, exCrc, exCrc2); + } + } + } + } + + //6. correct user data CRCs + if (ncdAddr < size && (ncdAddr + 0x200) < size) { + for (int i = 0; i < 2; i++) { + FlashUserConfigData *ncd = (FlashUserConfigData *) (buffer + ncdAddr + i * 0x100); + + if (ncd->version != 5) { + UpgradeNcdVersion(ncd); + if (ncd->version != 5) continue; + } + + uint16_t crc = ncd->crc; + uint16_t crc2 = ComputeCrc(ncd, FLASH_NCD_SIZE-4, 0xFFFF); + if (crc != crc2) { + ncd->crc = crc2; + printf("Corrected user config %d CRC (%04X -> %04X)\n", i, crc, crc2); + } + + if (hasExConfig) { + if (ncd->exVersion != 1) { + ncd->exVersion = 1; + } + + uint16_t exCrc = ncd->exCrc; + uint16_t exCrc2 = ComputeCrc(&ncd->exVersion, FLASH_NCD_EX_SIZE-2, 0xFFFF); + if (exCrc != exCrc2) { + ncd->exCrc = exCrc2; + printf("Corrected user config %d CRC (%04X -> %04X)\n", i, exCrc, exCrc2); + } + } + } + } + + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); +} diff --git a/src/cmd_help.c b/src/cmd_help.c new file mode 100644 index 0000000..28dbf60 --- /dev/null +++ b/src/cmd_help.c @@ -0,0 +1,83 @@ +#include "cmd_common.h" +#include "firmware.h" + +#include + +typedef struct HelpEntry_ { + const char *cmd; + void (*helpProc) (void); +} HelpEntry; + +static const HelpEntry sHelpEntries[] = { + { "help", CmdHelpHelp }, + { "quit", CmdHelpQuit }, + { "load", CmdHelpLoad }, + { "save", CmdHelpSave }, + { "info", CmdHelpInfo }, + { "verify", CmdHelpVerify }, + { "map", CmdHelpMap }, + { "compact", CmdHelpCompact }, + { "md5", CmdHelpMD5 }, + { "clean", CmdHelpClean }, + { "restore", CmdHelpRestore }, + { "fix", CmdHelpFix }, + { "import", CmdHelpImport }, + { "export", CmdHelpExport }, + { "user", CmdHelpUser }, + { "loc", CmdHelpLoc }, + { "eb", CmdHelpEB }, + { "db", CmdHelpDB } +}; + +void CmdHelpHelp(void) { + puts(""); + puts("Usage: help [command]"); + puts(""); + puts("Prints help for a given command. If no command is specified, a list of commands"); + puts("is printed."); +} + +void CmdProcHelp(int argc, const char **argv) { + (void) argc; + (void) argv; + + if (argc >= 2) { + const char *cmd = argv[1]; + + for (unsigned int i = 0; i < sizeof(sHelpEntries) / sizeof(sHelpEntries[0]); i++) { + if (stricmp(cmd, sHelpEntries[i].cmd) == 0) { + if (sHelpEntries[i].helpProc != NULL) sHelpEntries[i].helpProc(); + else puts("Unimplemented."); + return; + } + } + + printf("Unrecognized command '%s'.\n", cmd); + return; + } + + puts(""); + puts("Type help for information about a command."); + puts(""); + puts("File commands:"); + puts(" load Load a firmware image."); + puts(" save Saves a firmware image to disk."); + puts(""); + puts("Reporting commands:"); + puts(" info Print basic information about a firmware image."); + puts(" loc Locate a module occupying an address."); + puts(" map Prints a map of the firmware address space."); + puts(" md5 Calculates the MD5 sum of the firmware image."); + puts(" user Prints the user configuration information."); + puts(" verify Verify a firmware image."); + puts(""); + puts("Manipulation commands:"); + puts(" clean Cleans the user configuration and wireless configuration."); + puts(" compact Compacts the firmware image."); + puts(" db Dump bytes from the firmware image."); + puts(" eb Enter bytes into the firmware image."); + puts(" export Exports a firmware component."); + puts(" fix Fixes problems in the firmware image."); + puts(" import Import a firmware component."); + puts(" restore Restore firmware configuration from a file."); +} diff --git a/src/cmd_info.c b/src/cmd_info.c new file mode 100644 index 0000000..152d5b7 --- /dev/null +++ b/src/cmd_info.c @@ -0,0 +1,111 @@ +#include "cmd_common.h" +#include "firmware.h" + +void CmdHelpInfo(void) { + puts(""); + puts("Usage: info"); + puts(""); + puts("Prints basic information about a firmware image."); + puts(""); + puts("The ROM offsets and sizes for the firmware's modules are listed, as well as their"); + puts("destination addresses and the size of the module once uncompressed. If the module"); + puts("cannot be decompressed, its size is listed as 0. Verify the state of the modules"); + puts("using the verify command."); +} + +static const char *GetIpl2TypeString(int type) { + if (type == IPL2_TYPE_NORMAL) type = 0; + + const char *typestr = "DS"; + if (type & IPL2_TYPE_USG) { + if (type & IPL2_TYPE_CPU_NTR) { + typestr = "DS Lite with CPU-NTR"; + } else { + typestr = "DS Lite"; + } + } else if (type & IPL2_TYPE_TWL) { + typestr = "DSi"; + type &= ~(IPL2_TYPE_EXT_LANGUAGE | IPL2_TYPE_CHINESE | IPL2_TYPE_KOREAN); + } + + const char *region = "World"; + if (type & IPL2_TYPE_EXT_LANGUAGE) { + if (type & IPL2_TYPE_CHINESE) { + region = "iQue"; + } else if (type & IPL2_TYPE_KOREAN) { + region = "Korea"; + } + } + + static char buffer[64]; + sprintf(buffer, "%s (%s)", typestr, region); + return buffer; +} + +void CmdProcInfo(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + (void) argc; + (void) argv; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + FlashRfBbInfo *wl = (FlashRfBbInfo *) (buffer + 0x2A); + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + //locate the load addresses for secondary modules and resources pack + GetSecondaryResourceLoadAddresses(arm9Static, arm9StaticUncompressed, arm7Static, arm7StaticUncompressed, &arm9SecondaryRamAddr, &arm7SecondaryRamAddr, &rsrcRamAddr); + + //print firmware info + printf("\n"); + + printf("Firmware Info:\n"); + printf(" Build date : 20%02X/%02X/%02X %02X:%02X\n", hdr->timestamp[4], hdr->timestamp[3], hdr->timestamp[2], hdr->timestamp[1], hdr->timestamp[0]); + printf(" IPL2 type : %s\n", GetIpl2TypeString(hdr->ipl2Type)); + printf(" Extended settings : %s\n", (hdr->ipl2Type != 0xFF && (hdr->ipl2Type & 0x40)) ? "yes" : "no"); + printf(" Flash capacity : %d KB\n", 128 << hdr->flashCapacity); + printf("\n"); + + + //Module info: ARM9,ARM7 static modules, ARM9,ARM7 secondary modules, resource blob + printf("Module Info: Offset Size Address Uncompressed\n"); + printf(" ARM9 Static Module : %08X %08X %08X %08X\n", arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed); + printf(" ARM7 Static Module : %08X %08X %08X %08X\n", arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed); + printf(" ARM9 Secondary Module : %08X %08X %08X %08X\n", arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed); + printf(" ARM7 Secondary Module : %08X %08X %08X %08X\n", arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed); + printf(" Resources Pack : %08X %08X %08X %08X\n", rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed); + printf("\n"); + + uint16_t channels = wl->allowedChannel; + + //Wireless info: RFU type, allowed channels, register values + printf("Wireless Info:\n"); + printf(" RF Type : %s\n", GetRfType(wl->rfType)); + printf(" Module Vendor : %02X %02X\n", wl->module, wl->vendor); + printf(" Serial : %02X%02X%02X%02X%02X\n", wl->serial[0], wl->serial[1], wl->serial[2], wl->serial[3], wl->serial[4]); + printf(" MAC Address : %02X-%02X-%02X-%02X-%02X-%02X\n", wl->macAddr[0], wl->macAddr[1], wl->macAddr[2], wl->macAddr[3], wl->macAddr[4], wl->macAddr[5]); + printf(" Allowed Channels : "); for (int i = 0; i < 16; i++) if (channels & (1 << i)) printf("%d ", i); printf("\n"); + printf("\n"); + + + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); +} diff --git a/src/cmd_load.c b/src/cmd_load.c new file mode 100644 index 0000000..9c34ed4 --- /dev/null +++ b/src/cmd_load.c @@ -0,0 +1,46 @@ +#include "cmd_common.h" +#include "firmware.h" + +void CmdHelpLoad(void) { + puts(""); + puts("Usage: load "); + puts(""); + puts("Loads a firmare image from the given file path."); +} + +void CmdProcLoad(int argc, const char **argv) { + if (argc < 2) { + CmdHelpLoad(); + return; + } + + const char *filename = argv[1]; + LoadFirmwareImage(filename); +} + +void CmdHelpSave(void) { + puts(""); + puts("Usage: save [file name]"); + puts(""); + puts("Saves the working firmware image. A file name may optionally be specified."); +} + +void CmdProcSave(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + + const char *outpath = GetCurrentFilePath(); + if (argc >= 2) { + outpath = argv[1]; + } + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + FILE *fp = fopen(outpath, "wb"); + if (fp == NULL) { + printf("Could not open '%s' for write access.\n", outpath); + } + + fwrite(buffer, size, 1, fp); + fclose(fp); +} diff --git a/src/cmd_loc.c b/src/cmd_loc.c new file mode 100644 index 0000000..808870e --- /dev/null +++ b/src/cmd_loc.c @@ -0,0 +1,62 @@ +#include "cmd_common.h" +#include "firmware.h" + +void CmdHelpLoc(void) { + puts(""); + puts("Usage: loc
"); + puts(""); + puts("Locates the module that will be loaded at the specified address in RAM, and at"); + puts("what offset it appears."); +} + +void CmdProcLoc(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + if (argc < 2) { + CmdHelpLoc(); + return; + } + + const char *straddr = argv[1]; + uint32_t addr = ParseArgNumber(straddr); + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + //locate the load addresses for secondary modules and resources pack + GetSecondaryResourceLoadAddresses(arm9Static, arm9StaticUncompressed, arm7Static, arm7StaticUncompressed, &arm9SecondaryRamAddr, &arm7SecondaryRamAddr, &rsrcRamAddr); + + if (arm9StaticRamAddr && addr >= arm9StaticRamAddr && addr < (arm9StaticRamAddr + arm9StaticUncompressed)) { + printf("%08X: ARM9 Static Module + 0x%X\n", addr, addr - arm9StaticRamAddr); + } else if (arm7StaticRamAddr && addr >= arm7StaticRamAddr && addr < (arm7StaticRamAddr + arm7StaticUncompressed)) { + printf("%08X: ARM7 Static Module + 0x%X\n", addr, addr - arm7StaticRamAddr); + } else if (arm9SecondaryRamAddr && addr >= arm9SecondaryRamAddr && addr < (arm9SecondaryRamAddr + arm9SecondaryUncompressed)) { + printf("%08X: ARM9 Secondary Module + 0x%X\n", addr, addr - arm9SecondaryRamAddr); + } else if (arm7SecondaryRamAddr && addr >= arm7SecondaryRamAddr && addr < (arm7SecondaryRamAddr + arm7SecondaryUncompressed)) { + printf("%08X: ARM7 Secondary Module + 0x%X\n", addr, addr - arm7SecondaryRamAddr); + } else if (rsrcRamAddr && addr >= rsrcRamAddr && addr < (rsrcRamAddr + rsrcUncompressed)) { + printf("%08X: Resources Pack + 0x%X\n", addr, addr - rsrcRamAddr); + } else { + printf("No matches for address %08X.\n", addr); + } + + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); +} diff --git a/src/cmd_map.c b/src/cmd_map.c new file mode 100644 index 0000000..26ad23b --- /dev/null +++ b/src/cmd_map.c @@ -0,0 +1,119 @@ +#include "cmd_common.h" +#include "firmware.h" + +void CmdHelpMap(void) { + puts(""); + puts("Usage: map"); + puts(""); + puts("Prints a map of the firmware image's address space."); +} + +typedef struct Region_ { + uint32_t start; + uint32_t size; + const char *name; +} Region; + +static int RegionComparator(const void *e1, const void *e2) { + const Region *r1 = (const Region *) e1; + const Region *r2 = (const Region *) e2; + + if (r1->start < r2->start) return -1; + if (r1->start > r2->start) return 1; + return 0; +} + +void CmdProcMap(int argc, const char **argv) { + //map out the firmware address regions. + if (!RequireFirmwareImage()) return; + (void) argc; + (void) argv; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + if (arm9Static == NULL || arm7Static == NULL || arm9Secondary == NULL || arm7Secondary == NULL || rsrc == NULL) { + if (arm9Static == NULL) printf("The ARM9 static module could not be decompressed.\n"); + if (arm7Static == NULL) printf("The ARM7 static module could not be decompressed.\n"); + if (arm9Secondary == NULL) printf("The ARM9 secondary module could not be decompressed.\n"); + if (arm7Secondary == NULL) printf("The ARM7 secondary module could not be decompressed.\n"); + if (rsrc == NULL) printf("The resources pack could not be decompressed.\n"); + goto End; + } + + unsigned int ncdRomAddr = hdr->nvramUserConfigAddr * 4 * 2; + unsigned int ncdSize = 0x200; + unsigned int connSize = HasTwlSettings(hdr->ipl2Type) ? 0xA00 : 0x400; + unsigned int connRomAddr = ncdRomAddr - connSize; + + Region regions[] = { + { 0, 0x200, "Header" }, + { arm9StaticRomAddr, arm9StaticSize, "ARM9 Static" }, + { arm7StaticRomAddr, arm7StaticSize, "ARM7 Static" }, + { arm9SecondaryRomAddr, arm9SecondarySize, "ARM9 Secondary" }, + { arm7SecondaryRomAddr, arm7SecondarySize, "ARM7 Secondary" }, + { rsrcRomAddr, rsrcSize, "Resources Pack" }, + { connRomAddr, connSize, "Connection Settings" }, + { ncdRomAddr, ncdSize, "User Configuration" } + }; + qsort(regions, sizeof(regions) / sizeof(Region), sizeof(Region), RegionComparator); + + const char *fmtJct = " +----------------------------------------+-- %08X\n"; + puts(""); + + unsigned int i = 0; + uint32_t curAddr = 0; + for (i = 0; i < sizeof(regions) / sizeof(regions[0]); i++) { + Region *rgn = ®ions[i]; + printf(fmtJct, curAddr); + + char linebuf[64] = { 0 }; + if (curAddr < rgn->start) { + //free space + int len = snprintf(linebuf, sizeof(linebuf), "Free Space (%d bytes)", rgn->start - curAddr); + printf(" |"); + for (int i = 0; i < (40 - len) / 2; i++) putchar(' '); + printf(linebuf); + for (int i = 0; i < (40 - (40 - len) / 2 - len); i++) putchar(' '); + printf("|\n"); + curAddr = rgn->start; + printf(fmtJct, curAddr); + } + + //free space + int len = snprintf(linebuf, sizeof(linebuf), "%s (%d bytes)", rgn->name, rgn->size); + printf(" |"); + for (int i = 0; i < (40 - len) / 2; i++) putchar(' '); + printf(linebuf); + for (int i = 0; i < (40 - (40 - len) / 2 - len); i++) putchar(' '); + printf("|\n"); + + curAddr = rgn->start + rgn->size; + } + printf(fmtJct, curAddr); + + +End: + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); +} diff --git a/src/cmd_ms5.c b/src/cmd_ms5.c new file mode 100644 index 0000000..9afdc6e --- /dev/null +++ b/src/cmd_ms5.c @@ -0,0 +1,204 @@ +#include "cmd_common.h" +#include "firmware.h" + +#include +#include + + +void CmdHelpMD5(void) { + puts(""); + puts("Usage: md5"); + puts(""); + puts("Prints out the MD5 digests of components of the firmware."); +} + + +//MD5 sine table +static const uint32_t k[] = { + 0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE, + 0xF57C0FAF, 0x4787C62A, 0xA8304613, 0xFD469501, + 0x698098D8, 0x8B44F7AF, 0xFFFF5BB1, 0x895CD7BE, + 0x6B901122, 0xFD987193, 0xA679438E, 0x49B40821, + 0xF61E2562, 0xC040B340, 0x265E5A51, 0xE9B6C7AA, + 0xD62F105D, 0x02441453, 0xD8A1E681, 0xE7D3FBC8, + 0x21E1CDE6, 0xC33707D6, 0xF4D50D87, 0x455A14ED, + 0xA9E3E905, 0xFCEFA3F8, 0x676F02D9, 0x8D2A4C8A, + 0xFFFA3942, 0x8771F681, 0x6D9D6122, 0xFDE5380C, + 0xA4BEEA44, 0x4BDECFA9, 0xF6BB4B60, 0xBEBFBC70, + 0x289B7EC6, 0xEAA127FA, 0xD4EF3085, 0x04881D05, + 0xD9D4D039, 0xE6DB99E5, 0x1FA27CF8, 0xC4AC5665, + 0xF4292244, 0x432AFF97, 0xAB9423A7, 0xFC93A039, + 0x655b59C3, 0x8F0CCC92, 0xFFEFF47D, 0x85845DD1, + 0x6FA87E4F, 0xFE2CE6E0, 0xA3014314, 0x4E0811A1, + 0xF7537E82, 0xBD3AF235, 0x2AD7D2BB, 0xEB86D391 +}; + +//MD5 rotate table +static const int s[] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 +}; + + +static uint32_t RotL(uint32_t v1, int amt){ + return (v1 << amt) | (v1 >> (32 - amt)); +} + +static void ComputeMd5(const unsigned char *buf, unsigned int len, unsigned char *pDigest) { + unsigned int processedLen = len + 1; // added 1-bit + + //compute padding size (to 56 bytes mod 64) + if ((processedLen % 64) < 56) { + processedLen += 56 - (processedLen % 64); + } else if ((processedLen % 64) > 56) { + processedLen += 64 + 56 - (processedLen % 64); + } + processedLen += 8; + unsigned char *processed = calloc(processedLen, 1); + memcpy(processed, buf, len); + processed[len] = 0x80; + + uint64_t nBitsSrc = len * 8; + for (int i = 0; i < 8; i++) { + processed[processedLen - 8 + i] = (nBitsSrc >> (8 * i)) & 0xFF; + } + + //initial MD5 state + unsigned int a0 = 0x67452301; + unsigned int b0 = 0xEFCDAB89; + unsigned int c0 = 0x98BADCFE; + unsigned int d0 = 0x10325476; + + for (unsigned int n = 0; n < processedLen; n += 64) { + uint32_t chunk[16]; + const unsigned char *chunksrc = processed + n; + for (int i = 0; i < 16; i++) { + chunk[i] = (chunksrc[i * 4 + 0] << 0) | (chunksrc[i * 4 + 1] << 8) + | (chunksrc[i * 4 + 2] << 16) | (chunksrc[i * 4 + 3] << 24); + } + + uint32_t a = a0; + uint32_t b = b0; + uint32_t c = c0; + uint32_t d = d0; + for (int i = 0; i < 64; i++) { + + uint32_t f = 0, g = 0; + switch ((i >> 4)) { + case 0: + f = (b & c) | ((~b) & d); + g = i; + break; + case 1: + f = (d & b) | ((~d) & c); + g = 5 * i + 1; + break; + case 2: + f = b ^ c ^ d; + g = 3 * i + 5; + break; + case 3: + f = c ^ (b | (~d)); + g = 7 * i; + break; + } + + uint32_t tmp = d; + d = c; + c = b; + b = b + RotL(a + f + k[i] + chunk[g % 16], s[i]); + a = tmp; + } + a0 += a; + b0 += b; + c0 += c; + d0 += d; + } + free(processed); + + //write digest + for (int i = 0; i < 4; i++) *(pDigest++) = (a0 >> (8 * i)) & 0xFF; + for (int i = 0; i < 4; i++) *(pDigest++) = (b0 >> (8 * i)) & 0xFF; + for (int i = 0; i < 4; i++) *(pDigest++) = (c0 >> (8 * i)) & 0xFF; + for (int i = 0; i < 4; i++) *(pDigest++) = (d0 >> (8 * i)) & 0xFF; +} + +static void PrintDigest(const unsigned char *digest) { + for (int i = 0; i < 16; i++) printf("%02X", digest[i]); +} + +void CmdProcMD5(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + (void) argc; + (void) argv; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + //locate the load addresses for secondary modules and resources pack + GetSecondaryResourceLoadAddresses(arm9Static, arm9StaticUncompressed, arm7Static, arm7StaticUncompressed, &arm9SecondaryRamAddr, &arm7SecondaryRamAddr, &rsrcRamAddr); + + //digest buffers + unsigned char dFw[16], dStatic9c[16], dStatic9u[16], dStatic7c[16], dStatic7u[16]; + unsigned char dSecondary9c[16], dSecondary9u[16], dSecondary7c[16], dSecondary7u[16]; + unsigned char dRsrcu[16], dRsrcc[16]; + + ComputeMd5(buffer, size, dFw); + ComputeMd5(buffer + arm9StaticRomAddr, arm9StaticSize, dStatic9c); + ComputeMd5(buffer + arm7StaticRomAddr, arm7StaticSize, dStatic7c); + ComputeMd5(buffer + arm9SecondaryRomAddr, arm9SecondarySize, dSecondary9c); + ComputeMd5(buffer + arm7SecondaryRomAddr, arm7SecondarySize, dSecondary7c); + ComputeMd5(buffer + rsrcRomAddr, rsrcSize, dRsrcc); + ComputeMd5(arm9Static, arm9StaticUncompressed, dStatic9u); + ComputeMd5(arm7Static, arm7StaticUncompressed, dStatic7u); + ComputeMd5(arm9Secondary, arm9SecondaryUncompressed, dSecondary9u); + ComputeMd5(arm7Secondary, arm7SecondaryUncompressed, dSecondary7u); + ComputeMd5(rsrc, rsrcUncompressed, dRsrcu); + + + puts(""); + printf("Raw Digest : "); PrintDigest(dFw); puts(""); + puts(""); + printf("Compressed Modules\n"); + if (arm9Static != NULL) { printf(" ARM9 Static Digest : "); PrintDigest(dStatic9c); puts(""); } + if (arm7Static != NULL) { printf(" ARM7 Static Digest : "); PrintDigest(dStatic7c); puts(""); } + if (arm9Secondary != NULL) { printf(" ARM9 Secondary Digest : "); PrintDigest(dSecondary9c); puts(""); } + if (arm7Secondary != NULL) { printf(" ARM7 Secondary Digest : "); PrintDigest(dSecondary7c); puts(""); } + if (rsrc != NULL) { printf(" Resources Digest : "); PrintDigest(dRsrcc); puts(""); } + puts(""); + printf("Uncompressed Modules\n"); + if (arm9Static != NULL) { printf(" ARM9 Static Digest : "); PrintDigest(dStatic9u); puts(""); } + if (arm7Static != NULL) { printf(" ARM7 Static Digest : "); PrintDigest(dStatic7u); puts(""); } + if (arm9Secondary != NULL) { printf(" ARM9 Secondary Digest : "); PrintDigest(dSecondary9u); puts(""); } + if (arm7Secondary != NULL) { printf(" ARM7 Secondary Digest : "); PrintDigest(dSecondary7u); puts(""); } + if (rsrc != NULL) { printf(" Resources Digest : "); PrintDigest(dRsrcu); puts(""); } + + if (arm9Static == NULL || arm7Static == NULL || arm9Secondary == NULL || arm7Secondary == NULL || rsrc == NULL) { + puts(""); + printf("One or more modules failed to decompress.\n"); + goto End; + } + +End: + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); +} diff --git a/src/cmd_user.c b/src/cmd_user.c new file mode 100644 index 0000000..567ca2a --- /dev/null +++ b/src/cmd_user.c @@ -0,0 +1,266 @@ +#include "cmd_common.h" +#include "firmware.h" + +void CmdHelpUser(void) { + puts(""); + puts("Usage: user"); + puts(""); + puts("Prints out the current user configuration settings. Only the current"); + puts("information is printed."); + puts("The wireless connection settings are printed. When the advanced connection"); + puts("settings are present, those are printed too."); +} + +static int GetEffectiveConfig(FlashUserConfigData *ncd) { + + int crc1OK = ComputeCrc(ncd, FLASH_NCD_SIZE-4, 0xFFFF) == ncd[0].crc; + int crc2OK = ComputeCrc(ncd, FLASH_NCD_SIZE-4, 0xFFFF) == ncd[1].crc; + if (!crc1OK && !crc2OK) return -1; // no good config data + + if (crc1OK && !crc2OK) return 0; // config 1 good + if (crc2OK && !crc1OK) return 1; // config 2 good + + if (((ncd[0].saveCount + 1) & 0x7F) == ncd[1].saveCount) return 1; // config 2 is newer + return 0; // config 1 +} + +static void PrintUcs2(const uint16_t *str, unsigned int len) { + for (unsigned int i = 0; i < len; i++) { + if (str[i] >= 0x20 && str[i] < 0x7F) putchar((char) str[i]); + else putchar('?'); // out of ASCII plane + } +} + +static void PrintDate(const FlashDate *date) { + const char *const monthNames[] = { + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec" + }; + + if (date->month == 0 || date->month > 12 || date->day == 0 || date->day > 31) { + printf("Invalid date (%02d/%02d)", date->month, date->day); + } else { + printf("%s %d", monthNames[date->month - 1], date->day); + } +} + +static void PrintColor(int col) { + const char *const colorNames[] = { + "Gray", "Brown", "Red", "Pink", + "Orange", "Yellow", "Lime", "Green", + "Dark Green", "Sea Green", "Turquoise", "Blue", + "Dark Blue", "Purple", "Violet", "Magenta" + }; + printf("%d (%s)", col, colorNames[col]); +} + +static void PrintLanguage(FlashUserConfigData *ncd, int hasExConfig) { + const char *const languages[] = { + "Japanese", "English", "French", "German", "Italian", "Spanish", + "Chinese", "Korean" + }; + + if (!hasExConfig) { + int lang = ncd->language; + if (lang > 5) printf("Invalid (%d)", lang); + else printf("%s", languages[lang]); + } else { + int lang = ncd->exLanguage; + if (lang > 7) printf("Invalid (%d)", lang); + else printf("%s", languages[lang]); + } +} + +static void PrintSSID(const unsigned char *ssid) { + //null-terminated SSIDs + for (unsigned int i = 0; i < 32; i++) { + if (ssid[i] == 0) break; + + if (ssid[i] >= 0x20 && ssid[i] < 0x7F) putchar((char) ssid[i]); + else putchar('?'); + } +} + +static void PrintWEP(FlashConnSetting *set) { + if (set->wepMode == 0) { + //no WEP mode + printf("None"); + return; + } + + unsigned int keylen = 0; + switch (set->wepMode) { + case 1: + keylen = 40/8; + break; + case 2: + keylen = 104/8; + break; + case 3: + keylen = 128/8; + break; + } + + printf("WEP %d (", keylen * 8); + for (unsigned int j = 0; j < keylen; j++) { + printf("%02X", set->wepKey[0][j]); + } + printf(")"); +} + +static void PrintWPA(FlashConnExSetting *set) { + if (set->wpaMode == 0 && set->base.wepMode == 0) { + //no WEP mode + printf("None"); + return; + } + + const char *const wpaTypes[] = { + "WPA-PSK (TKIP)", + "WPA2-PSK (TKIP)", + "WPA-PSK (AES)", + "WPA2-PSK (AES)" + }; + + printf("%s (", wpaTypes[set->wpaMode - 4]); + for (unsigned int i = 0; i < 64; i++) { + char c = set->passphrase[i]; + if (!c) break; + + putchar(c); + } + printf(")"); +} + +static void PrintIP(uint32_t addr) { + if (addr == 0) { + printf("Auto-obtain"); + } else { + printf("%3d.%3d.%3d.%3d", (addr >> 0) & 0xFF, (addr >> 8) & 0xFF, (addr >> 16) & 0xFF, (addr >> 24) & 0xFF); + } +} + +static void PrintNcd(FlashUserConfigData *ncd, int hasExConfig) { + printf("User configuration\n"); + printf(" Nickname : "); PrintUcs2(ncd->nickname, ncd->nicknameLength); puts(""); + printf(" Comment : "); PrintUcs2(ncd->comment, ncd->commentLength); puts(""); + printf(" Birthday : "); PrintDate(&ncd->birthday); puts(""); + printf(" Favorite Color : "); PrintColor(ncd->favoriteColor); puts(""); + printf(" Language : "); PrintLanguage(ncd, hasExConfig); puts(""); +} + +static void PrintConnSetting(FlashConnSetting *set, int id) { + printf("Connection %d\n", id + 1); + if (set->setType == 0xFF) { + printf(" Not configured.\n"); + puts(""); + return; + } + + printf(" SSID : "); PrintSSID(set->ssid); puts(""); + printf(" Security : "); PrintWEP(set); puts(""); + printf(" IP : "); PrintIP(set->ipAddr); puts(""); + printf(" Gateway : "); PrintIP(set->gateway); puts(""); + printf(" Subnet : "); PrintIP(((1 << set->subnetMask) - 1)); puts(""); + printf(" DNS 1 : "); PrintIP(set->dns[0]); puts(""); + printf(" DNS 2 : "); PrintIP(set->dns[1]); puts(""); + + + uint64_t wfcId = (uint64_t) set->dwcUserIdLo; + wfcId |= ((uint64_t) set->dwcUserIdHi) << 32; + wfcId *= 1000ull; + + uint64_t wfcId2 = (uint64_t) set->dwcUnattestedUserIdLo; + wfcId2 |= ((uint64_t) set->dwcUnattestedUserIdMid1) << 5; + wfcId2 |= ((uint64_t) set->dwcUnattestedUserIdMid2) << 21; + wfcId2 |= ((uint64_t) set->dwcUnattestedUserIdHi) << 37; + wfcId2 *= 1000ull; + + printf(" WFC ID : %016llu (%016llu)\n", wfcId, wfcId2); + printf(" WFC Pass : %03X\n", set->pass); + + puts(""); +} + +static void PrintConnExSetting(FlashConnExSetting *set, int id) { + printf("Connection %d\n", id + 1); + if (set->base.setType == 0xFF) { + printf(" Not configured.\n"); + puts(""); + return; + } + + printf(" SSID : "); PrintSSID(set->base.ssid); puts(""); + printf(" Security : "); if (set->wpaMode) PrintWPA(set); else PrintWEP(&set->base); puts(""); + printf(" IP : "); PrintIP(set->base.ipAddr); puts(""); + printf(" Gateway : "); PrintIP(set->base.gateway); puts(""); + printf(" Subnet : "); PrintIP(((1 << set->base.subnetMask) - 1)); puts(""); + printf(" DNS 1 : "); PrintIP(set->base.dns[0]); puts(""); + printf(" DNS 2 : "); PrintIP(set->base.dns[1]); puts(""); + printf(" MTU : %d\n", set->base.mtu); + + uint64_t wfcId = (uint64_t) set->base.dwcUserIdLo; + wfcId |= ((uint64_t) set->base.dwcUserIdHi) << 32; + wfcId *= 1000ull; + + uint64_t wfcId2 = (uint64_t) set->base.dwcUnattestedUserIdLo; + wfcId2 |= ((uint64_t) set->base.dwcUnattestedUserIdMid1) << 5; + wfcId2 |= ((uint64_t) set->base.dwcUnattestedUserIdMid2) << 21; + wfcId2 |= ((uint64_t) set->base.dwcUnattestedUserIdHi) << 37; + wfcId2 *= 1000ull; + + printf(" WFC ID : %016llu (%016llu)\n", wfcId, wfcId2); + printf(" WFC Pass : %03X\n", set->base.pass); + + puts(""); +} + +void CmdProcUser(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + (void) argc; + (void) argv; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + FlashHeader *hdr = (FlashHeader *) buffer; + + int hasExConfig = HasExConfig(hdr->ipl2Type); + int hasTwlConfig = HasTwlSettings(hdr->ipl2Type); + + //address of user config + unsigned int ncdAddr = hdr->nvramUserConfigAddr * 8; + if (ncdAddr >= size || (ncdAddr + 0x200) > size) { + puts("Flash header user config address is invalid."); + return; + } + + FlashUserConfigData *ncd = (FlashUserConfigData *) (buffer + ncdAddr); + + //determine active configuration + int effective = GetEffectiveConfig(ncd); + if (effective == -1) { + puts("User configuation is invalid."); + return; + } + + //print user config + puts(""); + PrintNcd(&ncd[effective], hasExConfig); + + //print access point settings + if (ncdAddr >= 0x400) { + unsigned int connAddr = ncdAddr - 0x400; + puts(""); + for (int i = 0; i < 3; i++) { + PrintConnSetting((FlashConnSetting *) (buffer + connAddr + i * 0x100), i); + } + + if (hasTwlConfig && connAddr >= 0x600) { + unsigned int connExAddr = connAddr - 0x600; + for (int i = 0; i < 3; i++) { + PrintConnExSetting((FlashConnExSetting *) (buffer + connExAddr + i * 0x200), i + 3); + } + } + } +} diff --git a/src/cmd_verify.c b/src/cmd_verify.c new file mode 100644 index 0000000..da7d466 --- /dev/null +++ b/src/cmd_verify.c @@ -0,0 +1,106 @@ +#include "cmd_common.h" +#include "firmware.h" + +void CmdHelpVerify(void) { + puts(""); + puts("Usage: verify"); + puts(""); + puts("Verifies the integrity of the firmware image. This command verifies the"); + puts("checksums and data validity of the firmware."); +} + +static int VerifyArm7Accessible(uint32_t addr, uint32_t size) { + //address must be in WRAM or main memory + if (addr < 0x02000000) return 0; + if (addr >= 0x04000000) return 0; + if ((addr + size) < addr) return 0; + if ((addr + size) >= 0x04000000) return 0; + + return 1; +} + +static int VerifyArm9StaticAddress(uint32_t addr, uint32_t size) { + //ARM9 static must be accessible from the ARM7. + if (!VerifyArm7Accessible(addr, size)) return 0; + + return 1; +} + +static int VerifyArm7StaticAddress(uint32_t addr, uint32_t size) { + //ARM7 static must be accessible from the ARM7. + if (!VerifyArm7Accessible(addr, size)) return 0; + + return 1; +} + +void CmdProcVerify(int argc, const char **argv) { + if (!RequireFirmwareImage()) return; + + (void) argc; + (void) argv; + + unsigned int size; + unsigned char *buffer = GetFirmwareImage(&size); + + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + FlashRfBbInfo *wl = (FlashRfBbInfo *) (buffer + 0x2A); + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + int nErrors = 0; + printf("\nError list:\n"); + + //validate module data validity + if (arm9Static == NULL) { printf(" The ARM9 static module could not be decompressed.\n"); nErrors++; } + if (arm7Static == NULL) { printf(" The ARM7 static module could not be decompressed.\n"); nErrors++; } + if (arm9Secondary == NULL) { printf(" The ARM9 secondary module could not be decompressed.\n"); nErrors++; } + if (arm7Secondary == NULL) { printf(" The ARM7 secondary module could not be decompressed.\n"); nErrors++; } + if (rsrc == NULL) { printf(" The resources pack could not be decompressed.\n"); nErrors++; } + + //validate load addresses + int arm9StaticLoadOK = VerifyArm9StaticAddress(arm9StaticRamAddr, arm9StaticUncompressed); + int arm7StaticLoadOK = VerifyArm7StaticAddress(arm7StaticRamAddr, arm7StaticUncompressed); + if (arm9Static != NULL && !arm9StaticLoadOK) { printf(" Invalid load address for ARM9 static module.\n"); nErrors++; } + if (arm7Static != NULL && !arm7StaticLoadOK) { printf(" Invalid load address for ARM7 static module.\n"); nErrors++; } + + //get checksums from header + uint16_t staticCrc = hdr->staticCrc, secondaryCrc = hdr->secondaryCrc, rsrcCrc = hdr->resourceCrc; + uint16_t staticCrc2 = ComputeStaticCrc(arm9Static, arm9StaticUncompressed, arm7Static, arm7StaticUncompressed); + uint16_t secondaryCrc2 = ComputeSecondaryCrc(arm9Secondary, arm9SecondaryUncompressed, arm7Secondary, arm7SecondaryUncompressed); + uint16_t rsrcCrc2 = ComputeCrc(rsrc, rsrcUncompressed, 0xFFFF); + if (arm9Static != NULL && arm7Static != NULL && staticCrc != staticCrc2) { printf(" Checksum mismatch for static module: %04X (expected %04X)\n", staticCrc2, staticCrc); nErrors++; } + if (arm9Secondary != NULL && arm7Secondary != NULL && secondaryCrc != secondaryCrc2) { printf(" Checksum mismatch for secondary module: %04X (expected %04X)\n", secondaryCrc2, secondaryCrc); nErrors++; } + if (rsrc != NULL && rsrcCrc != rsrcCrc2) { printf(" Checksum mismatch for resources pack: %04X (expected %04X)\n", rsrcCrc2, rsrcCrc); nErrors++; } + + //validate wireless info + int isValidChannels = ((wl->allowedChannel & 0x8001) == 0) && ((wl->allowedChannel & 0x7FFE) != 0); + uint16_t wlCrc = 0; + if ((wl->tableSize + 0x2C) <= 0x200 || wl->tableSize < sizeof(*wl)) { + wlCrc = ComputeCrc(&wl->tableSize, wl->tableSize, 0); + } else { printf(" Invalid wireless init table size.\n"); nErrors++; } + if (wlCrc!= wl->crc) { printf(" CRC mismatch for wireless initialization.\n"); nErrors++; }; + if (!IsValidRfType(wl->rfType)) { printf(" No valid wireless RF type specified.\n"); nErrors++; } + if (!isValidChannels) { printf(" Invalid wireless channel specification.\n"); nErrors++; } + + //error footer + printf("\n%d error(s) found.\n\n", nErrors); + + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); +} diff --git a/src/compression.c b/src/compression.c new file mode 100644 index 0000000..ef40853 --- /dev/null +++ b/src/compression.c @@ -0,0 +1,1480 @@ +#include "compression.h" + +#include +#include +#include + + + +static void *CxiShrink(void *block, unsigned int to) { + void *newblock = realloc(block, to); + if (newblock == NULL) { + //alloc fail, return old block + return block; + } + return newblock; +} + + + +// ----- LZ decompression routines + +unsigned char *CxDecompressLZ(const unsigned char *buffer, unsigned int size, unsigned int *uncompressedSize) { + if (size < 4) return NULL; + + //find the length of the decompressed buffer. + uint32_t length = (*(uint32_t *) buffer) >> 8; + + //create a buffer for the decompressed buffer + unsigned char *result = (unsigned char *) malloc(length); + if (result == NULL) return NULL; + *uncompressedSize = length; + + //initialize variables + uint32_t offset = 4; + uint32_t dstOffset = 0; + while (1) { + uint8_t head = buffer[offset]; + offset++; + //loop 8 times + for (int i = 0; i < 8; i++) { + int flag = head >> 7; + head <<= 1; + + if (!flag) { + result[dstOffset] = buffer[offset]; + dstOffset++, offset++; + if (dstOffset == length) return result; + } else { + uint8_t high = buffer[offset++]; + uint8_t low = buffer[offset++]; + + //length of uncompressed chunk and offset + uint32_t offs = (((high & 0xF) << 8) | low) + 1; + uint32_t len = (high >> 4) + 3; + for (uint32_t j = 0; j < len; j++) { + result[dstOffset] = result[dstOffset - offs]; + dstOffset++; + if (dstOffset == length) return result; + } + } + } + } + return result; +} + +unsigned char *CxDecompressLZStream(unsigned int *uncompressedSize, CxStreamReadCallback callback, void *arg) { + int b = callback(arg); + if (b != 0x10) return NULL; + + unsigned int length = 0; + for (int i = 0; i < 3; i++) { + b = callback(arg); + if (b == CX_STREAM_EOF) return NULL; + + length |= b << (i * 8); + } + + unsigned char *result = (unsigned char *) malloc(length); + *uncompressedSize = length; + + //initialize variables + uint32_t dstOffset = 0; + while (1) { + if ((b = callback(arg)) == CX_STREAM_EOF) goto Error; + uint8_t head = b; + //printf("Out pos: %08X\n", dstOffset); + + //loop 8 times + for (int i = 0; i < 8; i++) { + int flag = head >> 7; + head <<= 1; + + if (!flag) { + if ((b = callback(arg)) == CX_STREAM_EOF) goto Error; + + result[dstOffset] = b; + dstOffset++; + } else { + if ((b = callback(arg)) == CX_STREAM_EOF) goto Error; + uint8_t high = b; + if ((b = callback(arg)) == CX_STREAM_EOF) goto Error; + uint8_t low = b; + + //length of uncompressed chunk and offset + uint32_t offs = (((high & 0xF) << 8) | low) + 1; + uint32_t len = (high >> 4) + 3; + + if (offs > dstOffset) goto Error; // reference underflow + if ((dstOffset + len) > length) goto Error; // reference overflow + if (offs == 1) goto Error; // BIOS uses SVC UnCompLZShort + + for (uint32_t j = 0; j < len; j++) { + result[dstOffset] = result[dstOffset - offs]; + dstOffset++; + } + } + if (dstOffset == length) return result; + } + } + return result; + +Error: + if (result != NULL) free(result); + return NULL; +} + + + +// ----- ASH decompression routines + +typedef struct BIT_READER_8_ { + const unsigned char *start; + const unsigned char *end; + const unsigned char *pos; + unsigned char current; + uint8_t nBitsBuffered; + uint8_t error; + uint8_t beBits; + uint32_t nBitsRead; +} BIT_READER_8; + +#define TREE_RIGHT 0x80000000 +#define TREE_LEFT 0x40000000 +#define TREE_VAL_MASK 0x3FFFFFFF + +static uint32_t BigToLittle32(uint32_t x) { + return (x >> 24) | (x << 24) | ((x & 0x00FF0000) >> 8) | ((x & 0x0000FF00) << 8); +} + +unsigned char CxiReverseByte(unsigned char x) { + unsigned char out = 0; + for (int i = 0; i < 8; i++) out |= ((x >> i) & 1) << (7 - i); + return out; +} + +void CxiInitBitReader(BIT_READER_8 *reader, const unsigned char *pos, const unsigned char *end, int beBits) { + reader->pos = pos; + reader->end = end; + reader->start = pos; + reader->beBits = beBits; + reader->nBitsBuffered = 8; + reader->nBitsRead = 0; + reader->current = *pos; + reader->error = 0; + if (reader->beBits) reader->current = CxiReverseByte(reader->current); +} + +uint32_t CxiConsumeBit(BIT_READER_8 *reader) { + if (reader->pos >= reader->end) { + //error + reader->error = 1; + return 0; + } + + unsigned char byteVal = reader->current; + reader->nBitsBuffered--; + reader->nBitsRead++; + + if (reader->nBitsBuffered > 0) { + reader->current >>= 1; + } else { + reader->pos++; + if (reader->pos < reader->end) { + reader->nBitsBuffered = 8; + reader->current = *reader->pos; + if (reader->beBits) reader->current = CxiReverseByte(reader->current); + } + } + + return byteVal & 1; +} + +uint32_t CxiConsumeBits(BIT_READER_8 *bitReader, unsigned int nBits) { + uint32_t string = 0, i = 0; + for (i = 0; i < nBits; i++) { + if (bitReader->pos >= bitReader->end) { + //error + bitReader->error = 1; + return string; + } + + bitReader->nBitsBuffered--; + bitReader->nBitsRead++; + if (bitReader->beBits) { + string <<= 1; + string |= (bitReader->current & 1); + } else { + string |= (bitReader->current & 1) << i; + } + + if (bitReader->nBitsBuffered > 0) { + bitReader->current >>= 1; + } else { + bitReader->pos++; + if (bitReader->pos < bitReader->end) { + bitReader->nBitsBuffered = 8; + bitReader->current = *bitReader->pos; + if (bitReader->beBits) bitReader->current = CxiReverseByte(bitReader->current); + } + } + } + + return string; +} + +uint32_t CxAshReadTree(BIT_READER_8 *reader, int width, uint32_t *leftTree, uint32_t *rightTree) { + uint32_t *workmem = (uint32_t *) calloc(2 * (1 << width), sizeof(uint32_t)); + uint32_t *work = workmem; + + uint32_t r23 = (1 << width); + uint32_t symRoot = 0; + uint32_t nNodes = 0; + do { + int bit = CxiConsumeBit(reader); + if (reader->error) goto Error; + + if (bit) { + if (r23 >= (2 * (1u << width)) || nNodes >= (2 * (1u << width))) goto Error; + + *(work++) = r23 | TREE_RIGHT; + *(work++) = r23 | TREE_LEFT; + nNodes += 2; + r23++; + } else { + if (nNodes == 0) goto Error; + + symRoot = CxiConsumeBits(reader, width); + if (reader->error) goto Error; + do { + uint32_t nodeval = *--work; + uint32_t idx = nodeval & TREE_VAL_MASK; + nNodes--; + if (nodeval & TREE_RIGHT) { + rightTree[idx] = symRoot; + symRoot = idx; + } else { + leftTree[idx] = symRoot; + break; + } + } while (nNodes > 0); + } + } while (nNodes > 0); + + free(workmem); + return symRoot; + +Error: + free(workmem); + return UINT32_MAX; +} + +unsigned char *CxDecompressAsh(const unsigned char *buffer, unsigned int size, unsigned int *uncompressedSize) { + if (size < 0xC) { + *uncompressedSize = 0; + return NULL; + } + + int symBits = 9, distBits = 11; + uint32_t uncompSize = BigToLittle32(*(uint32_t *) (buffer + 4)) & 0x00FFFFFF; + uint32_t outSize = uncompSize; + + BIT_READER_8 reader, reader2; + const unsigned char *endp = buffer + size; + uint32_t offsDstStream = BigToLittle32(*(const uint32_t *) (buffer + 0x8)); + if (offsDstStream >= size) { + //must reserve at least some space to write a minimal tree there + *uncompressedSize = 0; + return NULL; + } + + CxiInitBitReader(&reader, buffer + offsDstStream, endp, 1); + CxiInitBitReader(&reader2, buffer + 0xC, endp, 1); + + uint8_t *outbuf = calloc(uncompSize, 1); + uint8_t *destp = outbuf; + + uint32_t symMax = (1 << symBits); + uint32_t distMax = (1 << distBits); + + uint32_t *symLeftTree = calloc(2 * symMax - 1, sizeof(uint32_t)); + uint32_t *symRightTree = calloc(2 * symMax - 1, sizeof(uint32_t)); + uint32_t *distLeftTree = calloc(2 * distMax - 1, sizeof(uint32_t)); + uint32_t *distRightTree = calloc(2 * distMax - 1, sizeof(uint32_t)); + + uint32_t symRoot, distRoot; + symRoot = CxAshReadTree(&reader2, symBits, symLeftTree, symRightTree); + distRoot = CxAshReadTree(&reader, distBits, distLeftTree, distRightTree); + if (symRoot == UINT32_MAX || distRoot == UINT32_MAX) goto Error; + + //main uncompress loop + do { + uint32_t sym = symRoot; + while (sym >= symMax) { + if (!CxiConsumeBit(&reader2)) { + sym = symLeftTree[sym]; + } else { + sym = symRightTree[sym]; + } + } + + if (sym < 0x100) { + *(destp++) = sym; + uncompSize--; + } else { + uint32_t distsym = distRoot; + while (distsym >= distMax) { + if (!CxiConsumeBit(&reader)) { + distsym = distLeftTree[distsym]; + } else { + distsym = distRightTree[distsym]; + } + } + + uint32_t copylen = (sym - 0x100) + 3; + const uint8_t *srcp = destp - distsym - 1; + + if (copylen > uncompSize) goto Error; + //if ((distsym + 1) >= (size_t) (destp - outbuf)) goto Error; + + uncompSize -= copylen; + while (copylen--) { + *(destp++) = *(srcp++); + } + } + } while (uncompSize > 0); + + if (0) { +Error: + //free output + if (outbuf != NULL) free(outbuf); + outbuf = NULL; + outSize = 0; + } + + if (symLeftTree != NULL) free(symLeftTree); + if (symLeftTree != NULL) free(symRightTree); + if (symLeftTree != NULL) free(distLeftTree); + if (symLeftTree != NULL) free(distRightTree); + + *uncompressedSize = outSize; + return outbuf; +} + + +unsigned char *CxPadCompressed(unsigned char *comp, unsigned int size, unsigned int boundary, unsigned int *pOutSize) { + //check if size is already aligned to boundary + if ((size % boundary) == 0) { + *pOutSize = size; + return comp; + } + + //realloc + unsigned outSize = (size + boundary - 1) / boundary * boundary; + unsigned char *out = realloc(comp, outSize); + if (out == NULL) { + //error + free(comp); + return NULL; + } + + *pOutSize = outSize; + return out; +} + + +// ----- LZ Compression Routines + + +#define LZ_MIN_DISTANCE 0x01 // minimum distance per LZ encoding +#define LZ_MIN_SAFE_DISTANCE 0x02 // minimum safe distance per BIOS LZ bug +#define LZ_MAX_DISTANCE 0x1000 // maximum distance per LZ encoding +#define LZ_MIN_LENGTH 0x03 // minimum length per LZ encoding +#define LZ_MAX_LENGTH 0x12 // maximum length per LZ encoding + + +//struct for mapping an LZ graph +typedef struct CxiLzNode_ { + uint16_t distance; // distance of node if reference + uint16_t length; // length of node + uint32_t weight; // weight of node +} CxiLzNode; + +//struct for representing tokenized LZ data +typedef struct CxiLzToken_ { + uint8_t isReference; + union { + uint8_t symbol; + struct { + int16_t length; + int16_t distance; + }; + }; +} CxiLzToken; + +//struct for keeping track of LZ sliding window +typedef struct CxiLzState_ { + const unsigned char *buffer; + unsigned int size; + unsigned int pos; + unsigned int minLength; + unsigned int maxLength; + unsigned int minDistance; + unsigned int maxDistance; + unsigned int symLookup[512]; + unsigned int *chain; +} CxiLzState; + +static unsigned int CxiLzHash3(const unsigned char *p) { + unsigned char c0 = p[0]; // A + unsigned char c1 = p[0] ^ p[1]; // A ^ B + unsigned char c2 = p[0] ^ p[2]; // (A ^ B) ^ (B ^ C) + return (c0 ^ (c1 << 1) ^ (c2 << 2) ^ (c2 >> 7)) & 0x1FF; +} + +static void CxiLzStateInit(CxiLzState *state, const unsigned char *buffer, unsigned int size, unsigned int minLength, unsigned int maxLength, unsigned int minDistance, unsigned int maxDistance) { + state->buffer = buffer; + state->size = size; + state->pos = 0; + state->minLength = minLength; + state->maxLength = maxLength; + state->minDistance = minDistance; + state->maxDistance = maxDistance; + + for (unsigned int i = 0; i < 512; i++) { + //init symbol lookup to empty + state->symLookup[i] = UINT_MAX; + } + + state->chain = (unsigned int *) calloc(state->maxDistance, sizeof(unsigned int)); + for (unsigned int i = 0; i < state->maxDistance; i++) { + state->chain[i] = UINT_MAX; + } +} + +static void CxiLzStateFree(CxiLzState *state) { + free(state->chain); +} + +static unsigned int CxiLzStateGetChainIndex(CxiLzState *state, unsigned int index) { + return (state->pos - index) % state->maxDistance; +} + +static unsigned int CxiLzStateGetChain(CxiLzState *state, int index) { + unsigned int chainIndex = CxiLzStateGetChainIndex(state, index); + + return state->chain[chainIndex]; +} + +static void CxiLzStatePutChain(CxiLzState *state, unsigned int index, unsigned int data) { + unsigned int chainIndex = CxiLzStateGetChainIndex(state, index); + + state->chain[chainIndex] = data; +} + +static void CxiLzStateSlideByte(CxiLzState *state) { + if (state->pos >= state->size) return; // cannot slide + + //only update search structures when we have enough space left to necessitate searching. + if ((state->size - state->pos) >= 3) { + //fetch next 3 bytes' hash + unsigned int next = CxiLzHash3(state->buffer + state->pos); + + //get the distance back to the next byte before sliding. If it exists in the window, + //we'll have nextDelta less than UINT_MAX. We'll take this first occurrence and it + //becomes the offset from the current byte. Bear in mind the chain is 0-indexed starting + //at a distance of 1. + unsigned int nextDelta = state->symLookup[next]; + if (nextDelta != UINT_MAX) { + nextDelta++; + if (nextDelta >= state->maxDistance) { + nextDelta = UINT_MAX; + } + } + CxiLzStatePutChain(state, 0, nextDelta); + + //increment symbol lookups + for (int i = 0; i < 512; i++) { + if (state->symLookup[i] != UINT_MAX) { + state->symLookup[i]++; + if (state->symLookup[i] > state->maxDistance) state->symLookup[i] = UINT_MAX; + } + } + state->symLookup[next] = 0; // update entry for the current byte to the start of the chain + } + + state->pos++; +} + +static void CxiLzStateSlide(CxiLzState *state, unsigned int nSlide) { + while (nSlide--) CxiLzStateSlideByte(state); +} + +static unsigned int CxiCompareMemory(const unsigned char *b1, const unsigned char *b2, unsigned int nMax) { + //compare nAbsoluteMax bytes, do not perform any looping. + unsigned int nSame = 0; + while (nMax > 0) { + if (*(b1++) != *(b2++)) break; + nMax--; + nSame++; + } + return nSame; +} + +static int CxiLzConfirmMatch(const unsigned char *buffer, unsigned int size, unsigned int pos, unsigned int distance, unsigned int length) { + (void) size; + + //compare string match + return memcmp(buffer + pos, buffer + pos - distance, length) == 0; +} + +static unsigned int CxiLzSearch(CxiLzState *state, unsigned int *pDistance) { + unsigned int nBytesLeft = state->size - state->pos; + if (nBytesLeft < 3 || nBytesLeft < state->minLength) { + *pDistance = 0; + return 1; + } + + unsigned int firstMatch = state->symLookup[CxiLzHash3(state->buffer + state->pos)]; + if (firstMatch == UINT_MAX) { + //return byte literal + *pDistance = 0; + return 1; + } + + unsigned int distance = firstMatch + 1; + unsigned int bestLength = 1, bestDistance = 0; + + unsigned int nMaxCompare = state->maxLength; + if (nMaxCompare > nBytesLeft) nMaxCompare = nBytesLeft; + + //search backwards + const unsigned char *curp = state->buffer + state->pos; + while (distance <= state->maxDistance) { + //check only if distance is at least minDistance + if (distance >= state->minDistance) { + unsigned int matchLen = CxiCompareMemory(curp - distance, curp, nMaxCompare); + + if (matchLen > bestLength) { + bestLength = matchLen; + bestDistance = distance; + if (bestLength == nMaxCompare) break; + } + } + + if (distance == state->maxDistance) break; + unsigned int next = CxiLzStateGetChain(state, distance); + if (next == UINT_MAX) break; + distance += next; + } + + if (bestLength < state->minLength) { + bestLength = 1; + distance = 0; + } + *pDistance = bestDistance; + return bestLength; +} + +static inline int CxiLzNodeIsReference(const CxiLzNode *node) { + return node->length >= LZ_MIN_LENGTH; +} + +//length of compressed data output by LZ token +static inline unsigned int CxiLzTokenCost(unsigned int length) { + unsigned int nBytesToken; + if (length >= LZ_MIN_LENGTH) { + nBytesToken = 2; + } else { + nBytesToken = 1; + } + return 1 + nBytesToken * 8; +} + + +unsigned char *CxCompressLZ(const unsigned char *buffer, unsigned int size, unsigned int *compressedSize) { + CxiLzState state; + CxiLzStateInit(&state, buffer, size, LZ_MIN_LENGTH, LZ_MAX_LENGTH, LZ_MIN_SAFE_DISTANCE, LZ_MAX_DISTANCE); + + //create node list and fill in the maximum string reference sizes + CxiLzNode *nodes = (CxiLzNode *) calloc(size, sizeof(CxiLzNode)); + unsigned int pos = 0; + while (pos < size) { + unsigned int dst; + unsigned int len = CxiLzSearch(&state, &dst); + + //store longest found match + nodes[pos].length = len; + nodes[pos].distance = dst; + + pos++; + CxiLzStateSlide(&state, 1); + } + CxiLzStateFree(&state); + + //work backwards from the end of file + pos = size; + while (pos--) { + //get node at pos + CxiLzNode *node = nodes + pos; + + //search for largest LZ string match + unsigned int len = nodes[pos].length; + unsigned int dist = nodes[pos].distance; + + //if node takes us to the end of file, set weight to cost of this node. + if ((pos + len) == size) { + //token takes us to the end of the file, its weight equals this token cost. + node->length = len; + node->distance = dist; + node->weight = CxiLzTokenCost(len); + } else { + //else, search LZ matches from here down. + unsigned int weightBest = UINT_MAX; + unsigned int lenBest = 1; + while (len) { + //measure cost + unsigned int weightNext = nodes[pos + len].weight; + unsigned int weight = CxiLzTokenCost(len) + weightNext; + if (weight < weightBest) { + lenBest = len; + weightBest = weight; + } + + //decrement length w.r.t. length discontinuity + len--; + if (len != 0 && len < LZ_MIN_LENGTH) len = 1; + } + + //put node + node->length = lenBest; + node->distance = dist; + node->weight = weightBest; + } + } + + //from here on, we have a direct path to the end of file. All we need to do is traverse it. + + //get max compressed size + unsigned int maxCompressed = 4 + size + (size + 7) / 8; + + //encode LZ data + unsigned char *buf = (unsigned char *) calloc(maxCompressed, 1); + unsigned char *bufpos = buf; + *(uint32_t *) (bufpos) = (size << 8) | 0x10; + bufpos += 4; + + CxiLzNode *curnode = &nodes[0]; + + unsigned int srcpos = 0; + while (srcpos < size) { + uint8_t head = 0; + unsigned char *headpos = bufpos++; + + for (unsigned int i = 0; i < 8 && srcpos < size; i++) { + unsigned int length = curnode->length; + unsigned int distance = curnode->distance; + + if (CxiLzNodeIsReference(curnode)) { + //node is reference + uint16_t enc = (distance - LZ_MIN_DISTANCE) | ((length - LZ_MIN_LENGTH) << 12); + *(bufpos++) = (enc >> 8) & 0xFF; + *(bufpos++) = (enc >> 0) & 0xFF; + head |= 1 << (7 - i); + } else { + //node is literal byte + *(bufpos++) = buffer[srcpos]; + } + + srcpos += length; //remember: nodes correspond to byte positions + curnode += length; + } + + //put head byte + *headpos = head; + } + + //nodes no longer needed + free(nodes); + + unsigned int outSize = bufpos - buf; + *compressedSize = outSize; + return CxiShrink(buf, outSize); //reduce buffer size +} + + +// ----- ASH compression routines + + +static uint32_t LittleToBig(uint32_t i) { + return ((i >> 24) | (i << 24) | ((i & 0x00FF0000) >> 8) | ((i & 0x0000FF00) << 8)); +} + +#define min(a,b) (((a)<(b))?(a):(b)) +#define max(a,b) (((a)>(b))?(a):(b)) + + +typedef struct CxiHuffNode_ { + uint16_t sym; + uint16_t symMin; //had space to spare, maybe make searches a little simpler + uint16_t symMax; + uint16_t nRepresent; + int freq; + struct CxiHuffNode_ *left; + struct CxiHuffNode_ *right; +} CxiHuffNode; + + + +typedef struct BITSTREAM_ { + uint32_t *bits; + int nWords; + int nBitsInLastWord; + int nWordsAlloc; + int length; +} BITSTREAM; + + + +// ----- bit stream + +static void CxiBitStreamCreate(BITSTREAM *stream) { + stream->nWords = 0; + stream->length = 0; + stream->nBitsInLastWord = 32; + stream->nWordsAlloc = 16; + stream->bits = (uint32_t *) calloc(stream->nWordsAlloc, 4); +} + +static void CxiBitStreamFree(BITSTREAM *stream) { + free(stream->bits); +} + +static void CxiBitStreamWrite(BITSTREAM *stream, int bit) { + if (stream->nBitsInLastWord == 32) { + stream->nBitsInLastWord = 0; + stream->nWords++; + if (stream->nWords > stream->nWordsAlloc) { + int newAllocSize = (stream->nWordsAlloc + 2) * 3 / 2; + stream->bits = realloc(stream->bits, newAllocSize * 4); + stream->nWordsAlloc = newAllocSize; + } + stream->bits[stream->nWords - 1] = 0; + } + + stream->bits[stream->nWords - 1] |= (bit << (31 - stream->nBitsInLastWord)); + stream->nBitsInLastWord++; + stream->length++; +} + +static void *CxiBitStreamGetBytes(BITSTREAM *stream, int wordAlign, int beBytes, int beBits, unsigned int *size) { + //allocate buffer + unsigned int outSize = stream->nWords * 4; + if (!wordAlign) { + //nBitsInLast word is 32 if last word is full, 0 if empty. + if (stream->nBitsInLastWord <= 24) outSize--; + if (stream->nBitsInLastWord <= 16) outSize--; + if (stream->nBitsInLastWord <= 8) outSize--; + if (stream->nBitsInLastWord <= 0) outSize--; + } + unsigned char *outbuf = (unsigned char *) calloc(outSize, 1); + if (outbuf == NULL) return NULL; + + //this function handles converting byte and bit orders from the internal + //representation. Internally, we store the bit sequence as an array of + //words, where the first bits are inserted at the most significant bit. + // + + for (unsigned int i = 0; i < outSize; i++) { + int byteShift = 8 * ((beBytes) ? (3 - (i % 4)) : (i % 4)); + uint32_t word = stream->bits[i / 4]; + uint8_t byte = (word >> byteShift) & 0xFF; + + //if little endian bit order, swap here + if (!beBits) { + uint8_t temp = byte; + byte = 0; + for (int j = 0; j < 8; j++) byte |= ((temp >> j) & 1) << (7 - j); + } + outbuf[i] = byte; + } + + *size = outSize; + return outbuf; +} + +static void CxiBitStreamWriteBitsBE(BITSTREAM *stream, uint32_t bits, int nBits) { + for (int i = 0; i < nBits; i++) CxiBitStreamWrite(stream, (bits >> (nBits - 1 - i)) & 1); +} + + +static unsigned int CxiSearchLZ(const unsigned char *buffer, unsigned int size, unsigned int curpos, unsigned int minDistance, unsigned int maxDistance, unsigned int maxLength, unsigned int *pDistance) { + //nProcessedBytes = curpos + unsigned int nBytesLeft = size - curpos; + + //the maximum distance we can search backwards is limited by how far into the buffer we are. It won't + //make sense to a decoder to copy bytes from before we've started. + if (maxDistance > curpos) maxDistance = curpos; + + //keep track of the biggest match and where it was + unsigned int biggestRun = 0, biggestRunIndex = 0; + + //the longest string we can match, including repetition by overwriting the source. + unsigned int nMaxCompare = maxLength; + if (nMaxCompare > nBytesLeft) nMaxCompare = nBytesLeft; + + //begin searching backwards. + for (unsigned int j = minDistance; j <= maxDistance; j++) { + //compare up to 0xF bytes + unsigned int nMatched = CxiCompareMemory(buffer - j, buffer, nMaxCompare); + if (nMatched > biggestRun) { + biggestRun = nMatched; + biggestRunIndex = j; + if (biggestRun == nMaxCompare) break; + } + } + + *pDistance = biggestRunIndex; + return biggestRun; +} + +static unsigned int CxiSearchLZRestricted(const unsigned char *buffer, unsigned int size, unsigned int curpos, const unsigned int *distances, int nDistances, unsigned int maxLength, unsigned int *pDistance) { + if (nDistances == 0) { + *pDistance = 0; + return 0; + } + + //nProcessedBytes = curpos + unsigned int nBytesLeft = size - curpos; + + //the maximum distance we can search backwards is limited by how far into the buffer we are. It won't + //make sense to a decoder to copy bytes from before we've started. + unsigned int maxDistance = distances[nDistances - 1]; + if (maxDistance > curpos) maxDistance = curpos; + + //keep track of the biggest match and where it was + unsigned int biggestRun = 0, biggestRunIndex = 0; + + //the longest string we can match, including repetition by overwriting the source. + unsigned int nMaxCompare = maxLength; + if (nMaxCompare > nBytesLeft) nMaxCompare = nBytesLeft; + + //begin searching backwards. + for (int i = 0; i < nDistances; i++) { + unsigned int j = distances[i]; + if (j > maxDistance) break; + + unsigned int nMatched = CxiCompareMemory(buffer - j, buffer, nMaxCompare); + if (nMatched > biggestRun) { + biggestRun = nMatched; + biggestRunIndex = j; + if (biggestRun == nMaxCompare) break; + } + } + + *pDistance = biggestRunIndex; + return biggestRun; +} + + + +// ----- Huffman tree code + +#define ISLEAF(n) ((n)->left==NULL&&(n)->right==NULL) + +static void CxiHuffmanInit(CxiHuffNode *nodes, unsigned int nNodes) { + memset(nodes, 0, nNodes * 2 * sizeof(CxiHuffNode)); + for (unsigned int i = 0; i < nNodes; i++) { + nodes[i].symMin = nodes[i].symMax = nodes[i].sym = i; + nodes[i].nRepresent = 1; + } +} + +static int CxiHuffmanNodeComparator(const void *p1, const void *p2) { + return ((CxiHuffNode *) p2)->freq - ((CxiHuffNode *) p1)->freq; +} + +static void CxiHuffmanMakeShallowFirst(CxiHuffNode *node) { + if (ISLEAF(node)) return; + if (node->left->nRepresent > node->right->nRepresent) { + CxiHuffNode *left = node->left; + node->left = node->right; + node->right = left; + } + CxiHuffmanMakeShallowFirst(node->left); + CxiHuffmanMakeShallowFirst(node->right); +} + +static int CxiHuffmanHasSymbol(CxiHuffNode *node, uint16_t sym) { + if (ISLEAF(node)) return node->sym == sym; + if (sym < node->symMin || sym > node->symMax) return 0; + CxiHuffNode *left = node->left; + CxiHuffNode *right = node->right; + return CxiHuffmanHasSymbol(left, sym) || CxiHuffmanHasSymbol(right, sym); +} + +static void CxiHuffmanWriteSymbol(BITSTREAM *bits, uint16_t sym, CxiHuffNode *tree) { + if (ISLEAF(tree)) return; + + CxiHuffNode *left = tree->left; + CxiHuffNode *right = tree->right; + if (CxiHuffmanHasSymbol(left, sym)) { + CxiBitStreamWrite(bits, 0); + CxiHuffmanWriteSymbol(bits, sym, left); + } else { + CxiBitStreamWrite(bits, 1); + CxiHuffmanWriteSymbol(bits, sym, right); + } +} + +static void CxiHuffmanConstructTree(CxiHuffNode *nodes, int nNodes) { + //sort by frequency, then cut off the remainder (freq=0). + qsort(nodes, nNodes, sizeof(CxiHuffNode), CxiHuffmanNodeComparator); + for (int i = 0; i < nNodes; i++) { + if (nodes[i].freq == 0) { + nNodes = i; + break; + } + } + + //unflatten the histogram into a huffman tree. + int nRoots = nNodes; + int nTotalNodes = nNodes; + while (nRoots > 1) { + //copy bottom two nodes to just outside the current range + CxiHuffNode *srcA = nodes + nRoots - 2; + CxiHuffNode *destA = nodes + nTotalNodes; + memcpy(destA, srcA, sizeof(CxiHuffNode)); + + CxiHuffNode *left = destA; + CxiHuffNode *right = nodes + nRoots - 1; + CxiHuffNode *branch = srcA; + + branch->freq = left->freq + right->freq; + branch->sym = 0; + branch->left = left; + branch->right = right; + branch->symMin = min(left->symMin, right->symMin); + branch->symMax = max(right->symMax, left->symMax); + branch->nRepresent = left->nRepresent + right->nRepresent; //may overflow for root, but the root doesn't really matter for this + + nRoots--; + nTotalNodes++; + qsort(nodes, nRoots, sizeof(CxiHuffNode), CxiHuffmanNodeComparator); + } + + //just to be sure, make sure the shallow node always comes first + CxiHuffmanMakeShallowFirst(nodes); +} + +static unsigned int CxiHuffmanGetNodeDepth(CxiHuffNode *tree, unsigned int sym) { + if (tree->left == NULL) { + //this is the node + return 0; + } + + if (CxiHuffmanHasSymbol(tree->left, sym)) { + return CxiHuffmanGetNodeDepth(tree->left, sym) + 1; + } else if (CxiHuffmanHasSymbol(tree->right, sym)) { + return CxiHuffmanGetNodeDepth(tree->right, sym) + 1; + } else { + //!!! + return 0; + } +} + +typedef struct CxiHuffSymbolInfo_ { + uint16_t sym; + uint16_t depth; +} CxiHuffSymbolInfo; + +static int CxiHuffmanCountSymbolsOver(CxiHuffNode *tree, unsigned int nMin) { + if (tree->left == NULL) { + return tree->sym >= nMin; + } + return CxiHuffmanCountSymbolsOver(tree->left, nMin) + CxiHuffmanCountSymbolsOver(tree->right, nMin); +} + +static CxiHuffSymbolInfo *CxiHuffmanEnumerateSymbolInfoInternal(CxiHuffNode *tree, CxiHuffSymbolInfo *buf, int depth, unsigned int nMin) { + if (tree->left == NULL) { + if (tree->sym >= nMin) { + buf->sym = tree->sym; + buf->depth = depth; + buf++; + } + } else { + //run recursion + buf = CxiHuffmanEnumerateSymbolInfoInternal(tree->left, buf, depth + 1, nMin); + buf = CxiHuffmanEnumerateSymbolInfoInternal(tree->right, buf, depth + 1, nMin); + } + return buf; +} + +static int CxiHuffSymbolInfoComparator(const void *e1, const void *e2) { + const CxiHuffSymbolInfo *d1 = (const CxiHuffSymbolInfo *) e1; + const CxiHuffSymbolInfo *d2 = (const CxiHuffSymbolInfo *) e2; + + if (d1->sym < d2->sym) return -1; + if (d1->sym > d2->sym) return +1; + return 0; +} + +static CxiHuffSymbolInfo *CxiHuffmanEnumerateSymbolInfo(CxiHuffNode *tree, int *pCount, unsigned int nMin) { + int nNode = CxiHuffmanCountSymbolsOver(tree, nMin); + CxiHuffSymbolInfo *buf = (CxiHuffSymbolInfo *) calloc(nNode, sizeof(CxiHuffSymbolInfo)); + if (buf == NULL) return NULL; + + CxiHuffmanEnumerateSymbolInfoInternal(tree, buf, 0, nMin); + + //sort + qsort(buf, nNode, sizeof(*buf), CxiHuffSymbolInfoComparator); + + *pCount = nNode; + return buf; +} + + +// ----- ASH code + +static void CxiAshEnsureTreeElements(CxiHuffNode *nodes, int nNodes, int nMinNodes) { + //count nodes + int nPresent = 0; + for (int i = 0; i < nNodes; i++) { + if (nodes[i].freq) nPresent++; + } + + //have sufficient nodes? + if (nPresent >= nMinNodes) return; + + //add dummy nodes + for (int i = 0; i < nNodes; i++) { + if (nodes[i].freq == 0) { + nodes[i].freq = 1; + nPresent++; + if (nPresent >= nMinNodes) return; + } + } +} + +static void CxiAshWriteTree(BITSTREAM *stream, CxiHuffNode *nodes, int nBits) { + if (nodes->left != NULL) { + // + CxiBitStreamWrite(stream, 1); + CxiAshWriteTree(stream, nodes->left, nBits); + CxiAshWriteTree(stream, nodes->right, nBits); + } else { + //write value + CxiBitStreamWrite(stream, 0); + CxiBitStreamWriteBitsBE(stream, nodes->sym, nBits); + } +} + +static CxiLzToken *CxiAshTokenize(const unsigned char *buffer, unsigned int size, int nSymBits, int nDstBits, unsigned int *pnTokens) { + unsigned int nTokens = 0, tokenBufferSize = 16; + CxiLzToken *tokenBuffer = (CxiLzToken *) calloc(tokenBufferSize, sizeof(CxiLzToken)); + if (tokenBuffer == NULL) return NULL; + + // + unsigned int curpos = 0; + while (curpos < size) { + //ensure buffer capacity + if (nTokens + 1 > tokenBufferSize) { + tokenBufferSize = (tokenBufferSize + 2) * 3 / 2; + tokenBuffer = (CxiLzToken *) realloc(tokenBuffer, tokenBufferSize * sizeof(CxiLzToken)); + } + + //search backwards + unsigned int length, distance; + length = CxiSearchLZ(buffer, size, curpos, 1, (1 << nDstBits), (1 << nSymBits) - 1 - 0x100 + 3, &distance); + + CxiLzToken *token = &tokenBuffer[nTokens++]; + if (length >= 3) { + token->isReference = 1; + token->length = length; + token->distance = distance; + + buffer += length; + curpos += length; + } else { + token->isReference = 0; + token->symbol = *(buffer++); + curpos++; + } + } + + *pnTokens = nTokens; + tokenBuffer = realloc(tokenBuffer, nTokens * sizeof(CxiLzToken)); + return tokenBuffer; +} + +static void CxiAshGenHuffman(const CxiLzToken *tokens, unsigned int nTokens, CxiHuffNode *symNodes, unsigned int nSymNodes, CxiHuffNode *dstNodes, unsigned int nDstNodes) { + CxiHuffmanInit(symNodes, nSymNodes); + CxiHuffmanInit(dstNodes, nDstNodes); + + //construct frequency distribution + for (unsigned int i = 0; i < nTokens; i++) { + const CxiLzToken *token = &tokens[i]; + if (token->isReference) { + symNodes[token->length - 3 + 0x100].freq++; + dstNodes[token->distance - 1].freq++; + } else { + symNodes[token->symbol].freq++; + } + } + + //pre-tree construction: ensure at least two nodes are used + CxiAshEnsureTreeElements(symNodes, nSymNodes, 2); + CxiAshEnsureTreeElements(dstNodes, nDstNodes, 2); + + //construct trees + CxiHuffmanConstructTree(symNodes, nSymNodes); + CxiHuffmanConstructTree(dstNodes, nDstNodes); +} + +static unsigned int CxiAshRoundDown(unsigned int sym, unsigned int *vals, unsigned int nVals, int *pIndex) { + //if sym = 0, return 0 (no element can be equal) + if (sym == 0) { + *pIndex = -1; + return 0; + } + + //check lowest value <= + unsigned int idxLo = 0, idxHi = nVals; + unsigned int lo = 0; + int loIndex = -1; + while ((idxHi - idxLo) > 0) { + unsigned int idxMed = (idxLo + idxHi) / 2; + unsigned int valMed = vals[idxMed]; + + //check low or high + if (sym < valMed) { + idxHi = idxMed; + } else if (sym > valMed) { + idxLo = idxMed + 1; + loIndex = idxMed; + lo = valMed; + } else { + //exact match + *pIndex = idxMed; + return sym; + } + } + + //if lo == 0, no match found, so return 1 with index of -1 (1 is implicitly in the list) + if (lo == 0) lo = 1; + *pIndex = loIndex; + return lo; +} + +static CxiLzToken *CxiAshRetokenize(const unsigned char *buffer, unsigned int size, int nSymBits, int nDstBits, CxiHuffNode *symNodes, CxiHuffNode *dstNodes, unsigned int *pnTokens) { + //allocate graph + CxiLzNode *nodes = (CxiLzNode *) calloc(size, sizeof(CxiLzNode)); + if (nodes == NULL) return NULL; + + //get a list of allowed distances + int nLenNodesAvailable, nDstNodesAvailable; + CxiHuffSymbolInfo *lenInfo = CxiHuffmanEnumerateSymbolInfo(symNodes, &nLenNodesAvailable, 0x100); + CxiHuffSymbolInfo *dstInfo = CxiHuffmanEnumerateSymbolInfo(dstNodes, &nDstNodesAvailable, 0); + + //create array of symbol lengths + unsigned int minDstCost = UINT_MAX; + unsigned int *symDepths = (unsigned int *) calloc(1 << nSymBits, sizeof(unsigned int)); + unsigned int *dstDepths = (unsigned int *) calloc(1 << nDstBits, sizeof(unsigned int)); + for (int i = 0; i < (1 << nSymBits); i++) { + if (CxiHuffmanHasSymbol(symNodes, i)) symDepths[i] = CxiHuffmanGetNodeDepth(symNodes, i); + } + for (int i = 0; i < (1 << nDstBits); i++) { + if (CxiHuffmanHasSymbol(dstNodes, i)) dstDepths[i] = CxiHuffmanGetNodeDepth(dstNodes, i); + } + for (int i = 0; i < (1 << nDstBits); i++) { + if (dstDepths[i] && dstDepths[i] < minDstCost) minDstCost = dstDepths[i]; + } + + //create array of allowed lengths + unsigned int *lens = (unsigned int *) calloc(nLenNodesAvailable, sizeof(unsigned int)); + for (int i = 0; i < nLenNodesAvailable; i++) lens[i] = lenInfo[i].sym - 0x100 + 3; + + //create array of allowed distances + unsigned int *dsts = (unsigned int *) calloc(nDstNodesAvailable, sizeof(unsigned int)); + for (int i = 0; i < nDstNodesAvailable; i++) dsts[i] = dstInfo[i].sym + 1; + + //scan backwards from end of file + unsigned int pos = size; + while (pos-- > 0) { + //search LZ + unsigned int length = 0, distance = 0; + if (nLenNodesAvailable > 0) { + unsigned int maxCompare = lens[nLenNodesAvailable - 1]; + if ((size - pos) < maxCompare) { + int lengthIndex; //dummy + maxCompare = size - pos; + maxCompare = CxiAshRoundDown(maxCompare, lens, nLenNodesAvailable, &lengthIndex); + } + length = CxiSearchLZRestricted(buffer + pos, size, pos, dsts, nDstNodesAvailable, maxCompare, &distance); + } + + //check: length must be in the allowed lengths list. + int lengthIndex = -1; + if (length >= 3) { + length = CxiAshRoundDown(length, lens, nLenNodesAvailable, &lengthIndex); + } else { + length = 1; + } + + //NOTE: all byte values that appear in the file will have a symbol associated since they must appear at least once. + //thus we do not need to check that any byte value exists. + + //check length (should store reference?) + unsigned int weight = 0; + if (length < 3) { + //byte literal (can't go lower) + length = 1; + + //compute cost of byte literal + weight = symDepths[buffer[pos]]; + if ((pos + 1) < size) { + //add next weight + weight += nodes[pos + 1].weight; + } + } else { + //get cost of selected distance + unsigned int dstCost = dstDepths[distance - 1]; + + //scan size down + unsigned int weightBest = UINT_MAX, lengthBest = length; + while (length) { + unsigned int thisWeight; + + //compute weight of this length value + unsigned int thisLengthWeight; + if (length > 1) { + //length > 1: symbol (use cost of length symbol) + thisLengthWeight = lenInfo[lengthIndex].depth; + } else { + //length == 1: byte literal (use cost of byte literal) + thisLengthWeight = symDepths[buffer[pos]]; + } + + //takes us to end of file? + if ((pos + length) == size) { + //cost is just this node's weight + thisWeight = thisLengthWeight; + } else { + //cost is this node's weight plus the weight of the next node + CxiLzNode *next = nodes + pos + length; + thisWeight = thisLengthWeight + next->weight; + } + + //check if this node has better total weight. We allow the match to be equal since we're scanning + //sizes down, we'll prefer shorter lengths. This pushes the distribution of lengths to the lower + //end allowing for better Huffman coding. This also emphasizes byte literals, which have no distance + //cost. We add the minimum distance cost here to offset this (lack of) cost. + if (thisWeight <= weightBest || (length == 1 && thisWeight <= (weightBest + minDstCost))) { + weightBest = thisWeight; + lengthBest = length; + } + + //decrement length (with respect to allowed length encodings) + lengthIndex--; + if (lengthIndex >= 0) { + //length in list + length = lens[lengthIndex]; + } else if (lengthIndex == -1) { + //decrement to 1 + length = 1; + } else { + //end + break; + } + } + + length = lengthBest; + if (length < 3) { + //byte literal (distance cost is thus now zero since we have no distance component) + length = 1; + dstCost = 0; + } else { + //we ended up selecting an LZ copy-able length. but did we select the most optimal distance + //encoding? + //search possible distances where we can match the string at. We'll take the lowest-cost one. + for (int i = 0; i < nDstNodesAvailable; i++) { + unsigned int dst = dsts[i]; + if (dst > pos) break; + + //matching distance, check the cost + if (dstInfo[i].depth < dstCost) { + //check matching LZ string... + if (CxiLzConfirmMatch(buffer, size, pos, dst, length)) { + dstCost = dstInfo[i].depth; + distance = dst; + } + } + } + } + weight = weightBest + dstCost; + } + + //write node + if (length >= 3) { + nodes[pos].distance = distance; + nodes[pos].length = length; + } else { + nodes[pos].length = 1; + } + nodes[pos].weight = weight; + } + + free(lens); + free(dsts); + free(lenInfo); + free(dstInfo); + free(symDepths); + free(dstDepths); + + //convert graph into node array + unsigned int nTokens = 0; + pos = 0; + while (pos < size) { + CxiLzNode *node = nodes + pos; + nTokens++; + + pos += node->length; + } + + CxiLzToken *tokens = (CxiLzToken *) calloc(nTokens, sizeof(CxiLzToken)); + if (tokens == NULL) { + free(nodes); + return NULL; + } + + { + pos = 0; + unsigned int i = 0; + while (pos < size) { + CxiLzNode *node = nodes + pos; + + CxiLzToken *pToken = &tokens[i++]; + if (node->length >= 3) { + pToken->isReference = 1; + pToken->length = node->length; + pToken->distance = node->distance; + } else { + pToken->isReference = 0; + pToken->symbol = buffer[pos]; + } + + pos += node->length; + } + free(nodes); + } + + *pnTokens = nTokens; + return tokens; +} + +unsigned char *CxCompressAsh(const unsigned char *buffer, unsigned int size, int nSymBits, int nDstBits, unsigned int nPasses, unsigned int *compressedSize) { + //allocate tree structures + int nSymNodes = (1 << nSymBits); + int nDstNodes = (1 << nDstBits); + CxiHuffNode *symNodes = (CxiHuffNode *) calloc(nSymNodes * 2, sizeof(CxiHuffNode)); + CxiHuffNode *dstNodes = (CxiHuffNode *) calloc(nDstNodes * 2, sizeof(CxiHuffNode)); + if (symNodes == NULL || dstNodes == NULL) { + if (symNodes != NULL) free(symNodes); + if (dstNodes != NULL) free(dstNodes); + return NULL; + } + + //tokenize + unsigned int nTokens = 0; + CxiLzToken *tokens = CxiAshTokenize(buffer, size, nSymBits, nDstBits, &nTokens); + if (tokens == NULL) { + free(symNodes); + free(dstNodes); + return NULL; + } + + CxiAshGenHuffman(tokens, nTokens, symNodes, nSymNodes, dstNodes, nDstNodes); + + // ---------------------------------------------------------------------------------------------- + // Herein lies the really expensive operations (both memory and time). + // ---------------------------------------------------------------------------------------------- + + //iterate on adjusting the frequency distribution and traversing the encoding space + for (unsigned int i = 0; i < nPasses; i++) { + //discard tokenized sequence. + free(tokens); + + //re-tokenize + tokens = CxiAshRetokenize(buffer, size, nSymBits, nDstBits, symNodes, dstNodes, &nTokens); + if (tokens == NULL) { + free(symNodes); + free(dstNodes); + return NULL; + } + + //regenerate huffman tree due to changes in frequency distribution + CxiAshGenHuffman(tokens, nTokens, symNodes, nSymNodes, dstNodes, nDstNodes); + } + + // ---------------------------------------------------------------------------------------------- + // End of super intense operations + // ---------------------------------------------------------------------------------------------- + + //init streams + BITSTREAM symStream, dstStream; + CxiBitStreamCreate(&symStream); + CxiBitStreamCreate(&dstStream); + + //first, write huffman trees. + CxiAshWriteTree(&symStream, symNodes, nSymBits); + CxiAshWriteTree(&dstStream, dstNodes, nDstBits); + + //write data stream + for (unsigned int i = 0; i < nTokens; i++) { + CxiLzToken *token = &tokens[i]; + + if (token->isReference) { + CxiHuffmanWriteSymbol(&symStream, token->length - 3 + 0x100, symNodes); + CxiHuffmanWriteSymbol(&dstStream, token->distance - 1, dstNodes); + } else { + CxiHuffmanWriteSymbol(&symStream, token->symbol, symNodes); + } + } + + //free node and tree structs + free(tokens); + free(symNodes); + free(dstNodes); + + //encode data output + unsigned int symStreamSize = 0; + unsigned int dstStreamSize = 0; + void *symBytes = CxiBitStreamGetBytes(&symStream, 1, 1, 1, &symStreamSize); + void *dstBytes = CxiBitStreamGetBytes(&dstStream, 1, 1, 1, &dstStreamSize); + + //write data out + unsigned char *out = (unsigned char *) calloc(0xC + symStreamSize + dstStreamSize, 1); + { + //write header + uint32_t header[3]; + header[0] = 0x30485341; // 'ASH0' + header[1] = LittleToBig(size); + header[2] = LittleToBig(0xC + symStreamSize); + memcpy(out, header, sizeof(header)); + + //write streams + memcpy(out + sizeof(header), symBytes, symStreamSize); + memcpy(out + sizeof(header) + symStreamSize, dstBytes, dstStreamSize); + } + free(symBytes); + free(dstBytes); + + //free stuff + CxiBitStreamFree(&symStream); + CxiBitStreamFree(&dstStream); + + *compressedSize = 0xC + symStreamSize + dstStreamSize; + return out; +} + + diff --git a/src/compression.h b/src/compression.h new file mode 100644 index 0000000..f09d023 --- /dev/null +++ b/src/compression.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#define CX_STREAM_EOF -1 // EOF return value for CxStreamReadCallback + +typedef enum CxCompressionType_ { + CX_COMPRESSION_NONE, + CX_COMPRESSION_LZ, + CX_COMPRESSION_ASH +} CxCompressionType; + +typedef int (*CxStreamReadCallback) (void *pArg); + +unsigned char *CxDecompressLZ(const unsigned char *buffer, unsigned int size, unsigned int *uncompressedSize); + +unsigned char *CxDecompressLZStream(unsigned int *uncompressedSize, CxStreamReadCallback callback, void *arg); + +unsigned char *CxDecompressAsh(const unsigned char *buffer, unsigned int size, unsigned int *uncompressedSize); + +unsigned char *CxCompressLZ(const unsigned char *buffer, unsigned int size, unsigned int *compressedSize); + +unsigned char *CxCompressAsh(const unsigned char *buffer, unsigned int size, int nSymBits, int nDstBits, unsigned int nPasses, unsigned int *compressedSize); + +static inline unsigned char *CxCompressAshFirmware(const unsigned char *buffer, unsigned int size, unsigned int *compressedSize) { + unsigned char *out = CxCompressAsh(buffer, size, 9, 11, 2, compressedSize); + if (out != NULL) { + uint32_t hdr = (*compressedSize << 2) | 0x80000000; + out[0] = (hdr >> 0) & 0xFF; + out[1] = (hdr >> 8) & 0xFF; + out[2] = (hdr >> 16) & 0xFF; + out[3] = (hdr >> 24) & 0xFF; + + out[4] |= 0x80; + } + return out; +} + +unsigned char *CxPadCompressed(unsigned char *comp, unsigned int size, unsigned int boundary, unsigned int *pOutSize); diff --git a/src/firmware.c b/src/firmware.c new file mode 100644 index 0000000..2692809 --- /dev/null +++ b/src/firmware.c @@ -0,0 +1,426 @@ +#include +#include +#include +#include + +#include "firmware.h" +#include "compression.h" +#include "blowfish.h" + +uint16_t ComputeCrc(const void *p, unsigned int length, uint16_t init) { + const uint16_t tbl[] = { + 0x0000, 0xCC01, 0xD801, 0x1400, + 0xF001, 0x3C00, 0x2800, 0xE401, + 0xA001, 0x6C00, 0x7800, 0xB401, + 0x5000, 0x9C01, 0x8801, 0x4400 + }; + + uint16_t r = init; + const unsigned char *pp = (const unsigned char *) p; + for (unsigned int i = 0; i < length; i++) { + uint16_t c = (tbl[*pp & 0x0F] ^ (r >> 4)) ^ tbl[r & 0x0F]; + r = (tbl[*pp >> 4] ^ (c >> 4)) ^ tbl[c & 0xF]; + pp++; + } + return r; +} + +uint16_t ComputeStaticCrc(const void *arm9Static, unsigned int arm9StaticSize, const void *arm7Static, unsigned int arm7StaticSize) { + uint16_t crc = ComputeCrc(arm9Static, arm9StaticSize, 0xFFFF); + return ComputeCrc(arm7Static, arm7StaticSize, crc); +} + +uint16_t ComputeSecondaryCrc(const void *arm9Secondary, unsigned int arm9SecondarySize, const void *arm7Secondary, unsigned int arm7SecondarySize) { + uint16_t crc = ComputeCrc(arm9Secondary, arm9SecondarySize, 0xFFFF); + return ComputeCrc(arm7Secondary, arm7SecondarySize, crc); +} + +void UpdateFirmwareModuleChecksums(unsigned char *buffer, unsigned int size) { + //header + FlashHeader *hdr = (FlashHeader *) buffer; + + uint32_t arm9SecondaryRomAddr, arm9SecondarySize, arm9SecondaryRamAddr, arm9SecondaryUncompressed; + uint32_t arm7SecondaryRomAddr, arm7SecondarySize, arm7SecondaryRamAddr, arm7SecondaryUncompressed; + uint32_t arm9StaticRomAddr, arm9StaticSize, arm9StaticRamAddr, arm9StaticUncompressed; + uint32_t arm7StaticRomAddr, arm7StaticSize, arm7StaticRamAddr, arm7StaticUncompressed; + uint32_t rsrcRomAddr, rsrcSize, rsrcRamAddr, rsrcUncompressed; + + //unpack firmware and data headers + CxCompressionType type9, type7, typeRsrc; + unsigned char *arm9Static = GetArm9StaticInfo(buffer, size, &arm9StaticRomAddr, &arm9StaticRamAddr, &arm9StaticSize, &arm9StaticUncompressed); + unsigned char *arm7Static = GetArm7StaticInfo(buffer, size, &arm7StaticRomAddr, &arm7StaticRamAddr, &arm7StaticSize, &arm7StaticUncompressed); + unsigned char *arm9Secondary = GetArm9SecondaryInfo(buffer, size, &arm9SecondaryRomAddr, &arm9SecondaryRamAddr, &arm9SecondarySize, &arm9SecondaryUncompressed, &type9); + unsigned char *arm7Secondary = GetArm7SecondaryInfo(buffer, size, &arm7SecondaryRomAddr, &arm7SecondaryRamAddr, &arm7SecondarySize, &arm7SecondaryUncompressed, &type7); + unsigned char *rsrc = GetResourcesPackInfo(buffer, size, &rsrcRomAddr, &rsrcRamAddr, &rsrcSize, &rsrcUncompressed, &typeRsrc); + + //static module checksum + if (arm9Static != NULL && arm7Static != NULL) { + uint16_t sum = ComputeStaticCrc(arm9Static, arm9StaticUncompressed, arm7Static, arm7StaticUncompressed); + hdr->staticCrc = sum; + } + + //secondary module checksum + if (arm9Secondary != NULL && arm7Secondary != NULL) { + uint16_t sum = ComputeSecondaryCrc(arm9Secondary, arm9SecondaryUncompressed, arm7Secondary, arm7SecondaryUncompressed); + hdr->secondaryCrc = sum; + } + + //rsources checksum + if (rsrc != NULL) { + uint16_t sum = ComputeCrc(rsrc, rsrcUncompressed, 0xFFFF); + hdr->resourceCrc = sum; + } + + if (arm9Static != NULL) free(arm9Static); + if (arm7Static != NULL) free(arm7Static); + if (arm9Secondary != NULL) free(arm9Secondary); + if (arm7Secondary != NULL) free(arm7Secondary); + if (rsrc != NULL) free(rsrc); +} + + +// ----- RF utilities + + +const char *GetRfType(int type) { + switch (type) { + case 1: + return "MAX2822"; + case 2: + return "RF2958"; + case 3: + return "MM3156"; + case 4: + return "TEST"; + case 5: + return "MTMBBP ES1"; + case 6: + return "MM3218"; + } + return "(unknown)"; +} + +int IsValidRfType(int type) { + if (type == 1 || type == 2 || type == 3 || type == 5 || type == 6) return 1; + return 0; +} + + + + + +// ----- unpack routines + + +/* + * Searches for a byte pattern in the firmware image. + */ +static unsigned char *SearchPattern(const unsigned char *buf, unsigned int size, const unsigned char *search, unsigned int searchlen) { + if (searchlen > size) return NULL; + + for (unsigned int i = 0; i < (size - searchlen); i++) { + if (memcmp(buf + i, search, searchlen) == 0) return (unsigned char *) (buf + i); + } + return NULL; +} + +/* + * Searches for the decode_ash routine in the firmware image. + */ +static unsigned char *SearchDecodeAsh(const unsigned char *buf, unsigned int size) { + //signature of decode_ash in newer firmware versions + const unsigned char signature[] = { + 0xF0, 0x5F, 0x2D, 0xE9, // PUSH { R4-R12, LR } + 0x04, 0xD0, 0x4D, 0xE2, // SUB SP, #4 + 0x04, 0x40, 0x91, 0xE5, // LDR R4, [R1, #4] + 0x64, 0x58, 0x24, 0xE0, // EOR R5, R4, R4, ROR #16 + 0xFF, 0x58, 0xC5, 0xE3, // BIC R5, R5, #0x00FF000000 + 0x64, 0x44, 0xA0, 0xE1, // MOV R4, R4, ROR #8 + 0x25, 0x44, 0x24, 0xE0, // EOR R4, R4, R5, LSR #8 + 0xFF, 0x44, 0xC4, 0xE3, // BIC R4, R4, #0xFF000000 + }; + unsigned char *p = SearchPattern(buf, size, signature, sizeof(signature)); + if (p != NULL) return p; + + //original decode_ash in older firmware versions + const unsigned char signature2[] = { + 0xF0, 0x4F, 0x2D, 0xE9, // PUSH { R4-R11, LR } + 0x0C, 0xD0, 0x4D, 0xE2, // SUB SP, #0xC + 0x01, 0x90, 0xA0, 0xE1, // MOV sb, R1 + 0x05, 0x10, 0xD9, 0xE5, // LDRB R1, [sb, #5] + 0x06, 0x20, 0xD9, 0xE5, // LDRB R2, [sb, #6] + 0x08, 0x30, 0xD9, 0xE5, // LDRB R3, [sb, #8] + 0x01, 0x48, 0xA0, 0xE1, // MOV R4, R1, LSL #16 + 0x02, 0x54, 0x84, 0xE1, // ORR R5, R4, R2, LSL #8 + }; + p = SearchPattern(buf, size, signature2, sizeof(signature2)); + if (p != NULL) return p; + + return NULL; +} + + +// ----- decompression routines + +typedef struct StreamState_ { + const unsigned char *buf; + unsigned int pos; + unsigned int size; +} StreamState; + +static int ReadBlowfishCallback(void *arg) { + BfStream *stream = (BfStream *) arg; + unsigned char b = BfDecryptStreamNext(stream); + if (stream->error) return CX_STREAM_EOF; + return b; +} + +static int ReadNormalCallback(void *arg) { + StreamState *stream = (StreamState *) arg; + if (stream->pos >= stream->size) return CX_STREAM_EOF; + + return stream->buf[stream->pos++]; +} + +unsigned char *UncompressLZBlowfish(const unsigned char *buffer, unsigned int size, unsigned int romAddr, uint32_t *pSize, uint32_t *pUncompressed) { + if (buffer == NULL || romAddr >= size) { + return NULL; + } + + unsigned int uncompSize; + BfStream *stream = BfDecryptStreamInit(buffer + romAddr, size - romAddr, buffer); + unsigned char *uncomp = CxDecompressLZStream(&uncompSize, ReadBlowfishCallback, stream); + + if (uncomp != NULL) { + *pUncompressed = uncompSize; + *pSize = (stream->srcpos + 7) & ~7; // source size (round up to multiple of blowfish block size) + } + BfDecryptStreamEnd(stream); + return uncomp; +} + +unsigned char *UncompressLZNormal(const unsigned char *buffer, unsigned int size, unsigned int romAddr, uint32_t *pSize, uint32_t *pUncompressed) { + if (buffer == NULL || romAddr >= size) { + return NULL; + } + + unsigned int uncompSize; + StreamState stream; + stream.buf = buffer; + stream.pos = romAddr; + stream.size = size; + + unsigned char *uncomp = CxDecompressLZStream(&uncompSize, ReadNormalCallback, &stream); + if (uncomp != NULL) { + *pUncompressed = uncompSize; + *pSize = (stream.pos - romAddr + 7) & ~7; // source size (round up to multiple of blowfish block size) + } + return uncomp; +} + +unsigned char *GetArm9StaticInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed) { + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + + *pRomAddr = (4 * hdr->arm9StaticRomAddr) << hdr->arm9RomAddrScale; + *pRamAddr = 0x02800000 - ((hdr->arm9StaticRamAddr * 4) << hdr->arm9RamAddrScale); + *pSize = 0; + *pUncompressed = 0; + return UncompressLZBlowfish(buffer, size, *pRomAddr, pSize, pUncompressed); +} + +unsigned char *GetArm7StaticInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed) { + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + + *pRomAddr = (4 * hdr->arm7StaticRomAddr) << hdr->arm7RomAddrScale; + *pRamAddr = (hdr->arm7RamLocation ? 0x02800000 : 0x03810000) - ((hdr->arm7StaticRamAddr * 4) << hdr->arm7RamAddrScale); + *pSize = 0; + *pUncompressed = 0; + return UncompressLZBlowfish(buffer, size, *pRomAddr, pSize, pUncompressed); +} + +static unsigned char *UncompressLZOrASH(const unsigned char *buffer, unsigned int size, unsigned int romAddr, uint32_t *pSize, uint32_t *pUncompressed, CxCompressionType *pType) { + //bounds check + if (romAddr > size) return NULL; + if ((size - romAddr) < 4) return NULL; + + const unsigned char *p = buffer + romAddr; + if (*p == 0x10) { + //try decoding LZ + unsigned char *decp = UncompressLZNormal(buffer, size, romAddr, pSize, pUncompressed); + if (decp != NULL) { + *pType = CX_COMPRESSION_LZ; + return decp; + } + } + + if ((size - romAddr) < 0xC) return NULL; + + uint32_t header = *(const uint32_t *) (buffer + romAddr); + unsigned int compSize = (header & 0x00FFFFFF) >> 2; + if ((romAddr + compSize) > size) return NULL; + if ((romAddr + compSize) < romAddr) return NULL; + + unsigned int uncompSize; + unsigned char *decp = CxDecompressAsh(buffer + romAddr, compSize, &uncompSize); + *pUncompressed = uncompSize; + *pSize = compSize; + *pType = CX_COMPRESSION_ASH; + return decp; +} + +unsigned char *GetArm9SecondaryInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed, CxCompressionType *pType) { + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + + *pRomAddr = (4 * hdr->arm9SecondaryRomAddr) * 2; + *pRamAddr = 0; + *pSize = 0; + *pUncompressed = 0; + + unsigned char *uncomp = NULL; + uncomp = UncompressLZOrASH(buffer, size, *pRomAddr, pSize, pUncompressed, pType); + return uncomp; +} + +unsigned char *GetArm7SecondaryInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed, CxCompressionType *pType) { + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + + *pRomAddr = (4 * hdr->arm7SecondaryRomAddr) * 2; + *pRamAddr = 0; + *pSize = 0; + *pUncompressed = 0; + + unsigned char *uncomp = NULL; + uncomp = UncompressLZOrASH(buffer, size, *pRomAddr, pSize, pUncompressed, pType); + return uncomp; +} + +unsigned char *GetResourcesPackInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed, CxCompressionType *pType) { + //flash header + FlashHeader *hdr = (FlashHeader *) buffer; + + *pRomAddr = (4 * hdr->resourceRomAddr) * 2; + *pRamAddr = 0; + *pSize = 0; + *pUncompressed = 0; + + unsigned char *uncomp = NULL; + uncomp = UncompressLZOrASH(buffer, size, *pRomAddr, pSize, pUncompressed, pType); + return uncomp; +} + +void GetSecondaryResourceLoadAddresses( + const unsigned char *arm9Static, + unsigned int arm9StaticUncompressed, + const unsigned char *arm7Static, + unsigned int arm7StaticUncompressed, + uint32_t *pArm9SecondaryLoadAddr, + uint32_t *pArm7SecondaryLoadAddr, + uint32_t *pRsrcLoadAddr +) { + //locate the load addresses of the ARM9 secondary module and resources pack by locating the + //decode_ash function in the ARM9 static module. We look for thumb calls to this function + //and use that those to identify the load addresses. + if (arm9Static != NULL) { + unsigned char *pDecodeAsh = SearchDecodeAsh(arm9Static, arm9StaticUncompressed); + if (pDecodeAsh != NULL) { + + //get offset to decode_ash + uint32_t ofsDecodeAsh = pDecodeAsh - arm9Static; + + uint32_t rsrcRamAddr = 0x00000000; + uint32_t arm9SecondaryRamAddr = 0x00000000; + + //search for thumb BL decode_ash + for (unsigned int i = 4; i < ((arm9StaticUncompressed & ~1) - 2); i += 2) { + uint32_t rel = ((((ofsDecodeAsh - (i + 4)) + 2) & ~3) >> 1) & 0x3FFFFF; + + uint16_t u1 = *(uint16_t *) (arm9Static + i + 0); + uint16_t u2 = *(uint16_t *) (arm9Static + i + 2); + if ((u1 & 0xF800) != 0xF000) continue; + if ((u2 & 0xF800) != 0xE800) continue; + + if ((u1 & 0x7FF) != ((rel >> 11) & 0x7FF)) continue; + if ((u2 & 0x7FF) != ((rel >> 0) & 0x7FF)) continue; + + // case 1: (resources pack) + // LDR R0, =rsrcTarget + // LDR R1, =0x02200000 + // BLX decode_ash + + // case 2: (ARM9 Secondary) + // LDR R0, =Arm9SecondaryBase + // LDR R1, =0x022C0000 + // BLX decode_ash + + // case 3: (ARM7 Secondary) + // LDR R0, [R1, #0x0C] + // LDR R1, [R1, #0x10] + // BLX decode_ash + + uint16_t instr1 = *(uint16_t *) (arm9Static + i - 4); + uint16_t instr2 = *(uint16_t *) (arm9Static + i - 2); + (void) instr2; + + if ((instr1 & 0xFF00) == 0x4800) { // LDR R0, [PC, #X] + unsigned int pool = (i + ((instr1 & 0x00FF) << 2)) & ~3; + uint32_t addr = *(uint32_t *) (arm9Static + pool); + //printf("Case 1 at %08X (load to %08X)\n", i, *(uint32_t *) (arm9Static + pool)); + + if (arm9SecondaryRamAddr == 0) arm9SecondaryRamAddr = addr; + else rsrcRamAddr = addr; + } else if ((instr1 & 0xFFC7) == 0x68C0) { // LDR R0, [R1, #0xC] + + } + } + + *pArm9SecondaryLoadAddr = arm9SecondaryRamAddr; + *pRsrcLoadAddr = rsrcRamAddr; + } else { + //puts("Could not locate decode_ash."); + } + } + + //the ARM7 secondary load address is determined by the ARM7. + if (arm7Static != NULL) { + //the ARM7 secondary laod addess is located at 027FF86C. + for (unsigned int i = 0; i < ((arm7StaticUncompressed & ~1) - 4); i += 2) { + uint16_t instr1 = *(uint16_t *) (arm7Static + i + 0); + uint16_t instr2 = *(uint16_t *) (arm7Static + i + 2); + uint16_t instr3 = *(uint16_t *) (arm7Static + i + 4); + (void) instr3; + + if ((instr1 & 0xF800) != 0x4800) continue; // LDR R2, =Arm7SecondaryLoadAddr + if ((instr2 & 0xF800) != 0x4800) continue; // LDR R1, =0x027FF86C + + unsigned int pool = ((i + 2) & ~3) + 4 + ((instr2 & 0xFF) << 2); + uint32_t val = *(uint32_t *) (arm7Static + pool); + if (val != 0x027FF86C) continue; + + unsigned int pool2 = ((i + 0) & ~3) + 4 + ((instr1 & 0xFF) << 2); + uint32_t val2 = *(uint32_t *) (arm7Static + pool2); + + *pArm7SecondaryLoadAddr = val2; + } + } +} + + +// ----- IPL2 type functions + + +int HasTwlSettings(int ipl2Type) { + if (ipl2Type == IPL2_TYPE_NORMAL) return 0; // original DS + if (ipl2Type & IPL2_TYPE_TWL) return 1; + return 0; +} + +int HasExConfig(int ipl2Type) { + if (ipl2Type == IPL2_TYPE_NORMAL) return 0; // original DS + if (ipl2Type & (IPL2_TYPE_EXTENDED | IPL2_TYPE_TWL)) return 1; + return 0; +} + diff --git a/src/firmware.h b/src/firmware.h new file mode 100644 index 0000000..eeda7a8 --- /dev/null +++ b/src/firmware.h @@ -0,0 +1,219 @@ +#pragma once + +#include + +#include "compression.h" + + + +// ----- FLASH structures + +typedef struct FlashHeader_ { + uint16_t arm9SecondaryRomAddr; // +0x000 ARM9 Secondary Module ROM Address + uint16_t arm7SecondaryRomAddr; // +0x002 ARM7 Secondary Module ROM Address + uint16_t secondaryCrc; // +0x004 Secondary Module (ARM9+ARM7) CRC + uint16_t staticCrc; // +0x006 Static Module (ARM9+ARM7) CRC + uint32_t blowfishKey; // +0x008 Static Module blowfish key + uint16_t arm9StaticRomAddr; // +0x00C ARM9 Static Module ROM Address + uint16_t arm9StaticRamAddr; // +0x00E ARM9 Static Module RAM Address + uint16_t arm7StaticRomAddr; // +0x010 ARM7 Static Module ROM Address + uint16_t arm7StaticRamAddr; // +0x012 ARM7 Static Module RAM Address + uint16_t arm9RomAddrScale : 3; // +0x014 ARM9 Static Module ROM Address Scale + uint16_t arm9RamAddrScale : 3; // +0x014 ARM9 Static Module RAM Address Scale + uint16_t arm7RomAddrScale : 3; // +0x014 ARM7 Static Module ROM Address Scale + uint16_t arm7RamAddrScale : 3; // +0x014 ARM7 Static Module RAM Address Scale + uint16_t arm7RamLocation : 1; // +0x014 ARM7 Static Module RAM Address Selection (0=WRAM, 1=Main RAM) + uint16_t flashCapacity : 3; // +0x014 Flash memory capacity (dubious) + uint16_t resourceRomAddr; // +0x016 Resource Pack ROM Address + union { + struct { + unsigned char timestamp[5]; // +0x018 Firmware build time + unsigned char ipl2Type; // +0x01D IPL2 type + uint16_t pad1E; // +0x1E + }; + uint32_t unscrambleKey[2]; // +0x018 Unscramble key + }; + uint16_t nvramUserConfigAddr; // +0x020 NVRAM User Config Address + uint16_t field22; + uint16_t field24; + uint16_t resourceCrc; // +0x026 Resource Pack CRC + uint16_t field28; +} FlashHeader; + +//some of these bits are conjecture +#define IPL2_TYPE_NORMAL 0xFF // Indicates Original DS +#define IPL2_TYPE_CPU_NTR 0x80 // Indicates use of CPU-NTR on USG firmware and display? +#define IPL2_TYPE_EXTENDED 0x40 // Indicates presence of extended settings +#define IPL2_TYPE_USG 0x20 // Indicates DS Lite type +#define IPL2_TYPE_TWL 0x10 // Indicates DSi type +#define IPL2_TYPE_KOREAN 0x04 // Indicates Korean language support +#define IPL2_TYPE_CHINESE 0x02 // Indicates Chinese language support +#define IPL2_TYPE_EXT_LANGUAGE 0x01 // Indicates extended language support + +typedef struct FlashRfBbInfo_ { + uint16_t crc; // +0x02A CRC of table + uint16_t tableSize; // +0x02C Size of table + uint8_t vendor; // +0x02E Vendor ID + uint8_t module; // +0x02F Module ID + uint8_t serial[6]; // +0x030 Serial Number + uint8_t macAddr[6]; // +0x036 MAC Address + uint16_t allowedChannel; // +0x03C Allowed channels + uint16_t macFlags; // +0x03E MAC operation flags + uint8_t rfType; // +0x040 RF type + uint8_t rfRegisterBits; // +0x041 Width of RF register + uint8_t rfInitRegisterCount; // +0x042 Number of RF registers to initialize + uint8_t rfChannelRegisterCount; // +0x043 Number of RF registers per channel config + uint16_t macInitRegs[16]; // +0x044 MAC init regs + uint8_t bbInitRegs[0x69]; // +0x064 BBP init regs + uint8_t pad_; +} FlashRfBbInfo; + +typedef struct FlashDate_ { + uint8_t month; + uint8_t day; +} FlashDate; + +typedef struct FlashUserConfigData_ { + uint8_t version; // +0x000 (5=original retail) + uint8_t pad1; // +0x001 + + //ownerInfo + uint8_t favoriteColor; // +0x002 + FlashDate birthday; // +0x003 + uint8_t pad5; // +0x005 + uint16_t nickname[10]; // +0x006 + uint8_t nicknameLength; // +0x01A + uint8_t pad1B; // +0x01B + uint16_t comment[26]; // +0x01C + uint8_t commentLength; // +0x050 + uint8_t pad51; // +0x051 + + //alarm + uint8_t alarmHour; // +0x052 + uint8_t alarmMinute; // +0x053 + uint8_t alarmSecond; // +0x054 + uint8_t pad55; // +0x055 + uint16_t alarmEnableWeek; // +0x056 + + //tp + uint16_t tp1; // +0x058 + uint16_t tp2; // +0x05A + uint8_t tp3; // +0x05C + uint8_t tp4; // +0x05D + uint16_t tp5; // +0x05E + uint16_t tp6; // +0x060 + uint8_t tp7; // +0x062 + uint8_t tp8; // +0x063 + + //option + uint16_t language : 3; // +0x064 + uint16_t gbaMode : 1; + uint16_t backlight : 2; + uint16_t autoboot : 1; + uint16_t pad64_4 : 4; + uint16_t hasFavoriteColor : 1; + uint16_t hasTpCalib : 1; + uint16_t hasLanguage : 1; + uint16_t hasRtc : 1; + uint16_t hasNickname : 1; + uint8_t timezone; // +0x066 + uint8_t rtcClockAdjust; // +0x067 + uint64_t rtcOffset; // +0x068 + + uint16_t saveCount; // +0x070 + uint16_t crc; // +0x072 + + //extended settings + uint8_t exVersion; // +0x074 + uint8_t exLanguage; // +0x075 + uint16_t languages; // +0x076 + unsigned char pad78[0x86]; // +0x078 + uint16_t exCrc; // +0x0FE +} FlashUserConfigData; + +#define FLASH_NCD_SIZE 0x74 // size of user config +#define FLASH_NCD_EX_SIZE 0x8C // size of extended portion of user config + + +typedef struct FlashConnection_ { + unsigned char ispID[32]; // +0x000 + unsigned char ispPass[32]; // +0x020 + unsigned char ssid[32]; // +0x040 + unsigned char ssidAoss[32]; // +0x060 + unsigned char wepKey[4][16]; // +0x080 + uint32_t ipAddr; // +0x0C0 + uint32_t gateway; // +0x0C4 + uint32_t dns[2]; // +0x0C8 + uint8_t subnetMask; // +0x0D0 + unsigned char wepKey2[4][5]; // +0x0D1 + uint8_t authType; // +0x0E5 + uint8_t wepMode : 2; // +0x0E6 (0=None, 1=WEP 40, 2=WEP 104, 3=WEP 128) + uint8_t wepKeyNotation : 6; // +0x0E6 (0=hex, 1=ASCII) + uint8_t setType; // +0x0E7 (0=Manual, 1=AOSS, 2=Rakuraku, FF=unset) + uint8_t fieldE8; // +0x0E8 (TWL) + uint8_t fieldE9; // +0x0E9 (TWL) + uint16_t mtu; // +0x0EA (TWL) + unsigned char fieldEC[3]; // +0x0EC + uint8_t state; // +0x0EF + uint32_t dwcUserIdLo; // +0x0F0 DWC user ID (32) | 43-bit + uint16_t dwcUserIdHi : 11; // +0x0F4 DWC user ID (11) / + uint16_t dwcUnattestedUserIdLo : 5; // +0x0F4 DWC unattested user ID (5) | 43-bit + uint16_t dwcUnattestedUserIdMid1; // +0x0F6 DWC unattested user ID (16) | + uint16_t dwcUnattestedUserIdMid2; // +0x0F8 DWC unattested user ID (16) | + uint16_t dwcUnattestedUserIdHi : 6; // +0x0FA DWC unattested user ID (6) / + uint16_t pass : 10; // +0x0FA + uint16_t rand; // +0x0FC + uint16_t crc; // +0x0FE +} FlashConnSetting; + +typedef struct FlashConnectionEx_ { + FlashConnSetting base; // +0x000 + unsigned char psk[32]; // +0x100 + unsigned char passphrase[64]; // +0x120 + unsigned char field160[0x21]; // +0x160 + uint8_t wpaMode; // +0x181 (0=No/WEP, 4=WPA TKIP, 5=WPA2 TKIP, 6=WPA AES, 7=WPA2 AES) + uint8_t proxyEnable; // +0x182 + uint8_t proxyAuthenticate; // +0x183 + char proxyName[48]; // +0x184 + unsigned char field1B4[52]; // +0x1B4 + uint16_t proxyPort; // +0x1E8 + unsigned char field1EA[20]; // +0x1EA + uint16_t exCrc; // +0x1FE +} FlashConnExSetting; + + +// ----- Common routines + +uint16_t ComputeCrc(const void *p, unsigned int length, uint16_t init); +uint16_t ComputeStaticCrc(const void *arm9Static, unsigned int arm9StaticSize, const void *arm7Static, unsigned int arm7StaticSize); +uint16_t ComputeSecondaryCrc(const void *arm9Secondary, unsigned int arm9SecondarySize, const void *arm7Secondary, unsigned int arm7SecondarySize); +void UpdateFirmwareModuleChecksums(unsigned char *buffer, unsigned int size); + + +// ----- RF routines + +const char *GetRfType(int type); +int IsValidRfType(int type); + + +// ----- unpack routines + +unsigned char *UncompressLZBlowfish(const unsigned char *buffer, unsigned int size, unsigned int romAddr, uint32_t *pSize, uint32_t *pUncompressed); +unsigned char *GetArm9StaticInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed); +unsigned char *GetArm7StaticInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed); +unsigned char *GetArm9SecondaryInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed, CxCompressionType *pType); +unsigned char *GetArm7SecondaryInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed, CxCompressionType *pType); +unsigned char *GetResourcesPackInfo(const unsigned char *buffer, unsigned int size, uint32_t *pRomAddr, uint32_t *pRamAddr, uint32_t *pSize, uint32_t *pUncompressed, CxCompressionType *pType); + +void GetSecondaryResourceLoadAddresses( + const unsigned char *arm9Static, + unsigned int arm9StaticUncompressed, + const unsigned char *arm7Static, + unsigned int arm7StaticUncompressed, + uint32_t *pArm9SecondaryLoadAddr, + uint32_t *pArm7SecondaryLoadAddr, + uint32_t *pRsrcLoadAddr +); + +int HasTwlSettings(int ipl2Type); +int HasExConfig(int ipl2Type); diff --git a/src/fwutil.c b/src/fwutil.c new file mode 100644 index 0000000..3cde69d --- /dev/null +++ b/src/fwutil.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include + +#include "compression.h" +#include "blowfish.h" +#include "firmware.h" +#include "cmd_common.h" + + +void CmdHelpQuit(void) { + puts(""); + puts("Usage: quit"); + puts(""); + puts("Quits the program."); +} + +void CmdProcQuit(int argc, const char **argv) { + (void) argc; + (void) argv; + + DoExit(); +} + + +typedef struct CmdProcEntry_ { + char *cmd; + void (*proc) (int argc, const char **argv); +} CmdProcEntry; + +static CmdProcEntry sProcTable[] = { + { "help", CmdProcHelp }, + { "h", CmdProcHelp }, + + { "quit", CmdProcQuit }, + { "q", CmdProcQuit }, + { "exit", CmdProcQuit }, + + { "load", CmdProcLoad }, + { "save", CmdProcSave }, + { "info", CmdProcInfo }, + { "verify", CmdProcVerify }, + { "map", CmdProcMap }, + { "compact", CmdProcCompact }, + + { "md5", CmdProcMD5 }, + { "clean", CmdProcClean }, + { "restore", CmdProcRestore }, + { "fix", CmdProxFix }, + { "import", CmdProcImport }, + { "export", CmdProcExport }, + { "user", CmdProcUser }, + { "loc", CmdProcLoc }, + { "eb", CmdProcEB }, + { "db", CmdProcDB } +}; + +static void CmdParse(const char *buffer, int *pArgc, char ***pArgv) { + //empty command line? + if (*buffer == '\0' || *buffer == '\n' || *buffer == '\r') { + *pArgc = 0; + *pArgv = NULL; + return; + } + + //count arguments + const char *p = buffer; + int quote = 0, nArg = 1, lastSpace = 0, nChars = 1; + while (1) { + char c = *(p++); + if (c == '\0' || c == '\n' || c == '\r') break; + + int isWhiteSpace = (c == ' ' || c == '\t'); + + if (c == '"') { + quote = !quote; + } else if (isWhiteSpace && !quote) { + if (!lastSpace) { + nArg++; + nChars++; // null separator + } + } else { + nChars++; // character in string + } + lastSpace = isWhiteSpace; + } + + //fill arguments + char **argv = (char **) calloc(nArg, sizeof(char *)); + char *argbuf = (char *) calloc(nChars, 1); + char *argp = argbuf; + int curArgv = 0; + p = buffer; + lastSpace = 0; + quote = 0; + argv[curArgv++] = argp; // first argument + + while (1) { + char c = *(p++); + if (c == '\0' || c == '\n' || c == '\r') { + if (!lastSpace) { + *(argp++) = '\0'; + } + break; + } + + int isWhiteSpace = (c == ' ' || c == '\t'); + + if (c == '"') { + quote = !quote; + } else if (isWhiteSpace && !quote) { + if (!lastSpace) { + //put terminator and append to argument list + *(argp++) = '\0'; + if (curArgv < nArg) argv[curArgv++] = argp; + } + } else { + *(argp++) = c; + } + lastSpace = isWhiteSpace; + } + + *pArgv = argv; + *pArgc = nArg; +} + +static void CmdFree(char **argv) { + if (argv != NULL) { + free(argv[0]); + free(argv); + } +} + +static void CmdDispatch(int argc, const char **argv) { + if (argc == 0) return; + + for (unsigned int i = 0; i < sizeof(sProcTable) / sizeof(sProcTable[0]); i++) { + if (stricmp(sProcTable[i].cmd, argv[0]) == 0) { + if (sProcTable[i].proc != NULL) sProcTable[i].proc(argc, argv); + else puts("Unimplemented."); + return; + } + } + + printf("Unrecognized command '%s'.\n", argv[0]); +} + +static void CmdMain(const char *defpath) { + puts("*******************************************************************************"); + puts("* *"); + puts("* Nintendo DS Firmware Utility *"); + puts("* *"); + puts("*******************************************************************************"); + puts(""); + if (defpath != NULL) { + const char *loadargs[] = { + "load", defpath + }; + CmdProcLoad(2, loadargs); + puts(""); + } + puts("Type 'help' for a list of commands."); + puts(""); + + char buffer[1024]; + while (!IsExiting()) { + printf("> "); + fgets(buffer, sizeof(buffer), stdin); + + char **argv; + int argc; + CmdParse(buffer, &argc, &argv); + CmdDispatch(argc, argv); + CmdFree(argv); + puts(""); + } +} + +int main(int argc, char **argv) { + CmdMain(argc >= 1 ? argv[1] : NULL); + + return 0; +}