├── README.md ├── Makefile ├── .github └── workflows │ └── build-release.yml └── source ├── sha256.c └── main.c /README.md: -------------------------------------------------------------------------------- 1 | 2 | SuperFW flashing/dumping tool for NDS 3 | ===================================== 4 | 5 | This is a small NDS utility to dump and flash supercard firmware. 6 | 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # SPDX-FileContributor: Antonio Niño Díaz, 2024 4 | 5 | BLOCKSDS ?= /opt/blocksds/core 6 | 7 | # User config 8 | 9 | NAME := superfw-flasher 10 | GAME_TITLE := SuperFW flashing tool 11 | GAME_SUBTITLE := davidgf 12 | GAME_AUTHOR ?= davidgf.net 13 | 14 | include $(BLOCKSDS)/sys/default_makefiles/rom_arm9/Makefile 15 | 16 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: superfw flasher tool release 2 | run-name: Builds the NDS superfw flasher tool 3 | 4 | on: 5 | push: 6 | branches: 7 | - 'master' 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | build-nds-homebrew-tool: 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - name: Install dependencies 16 | run: | 17 | sudo apt-get install software-properties-common make 18 | sudo add-apt-repository --yes ppa:david-davidgf/blocksds-sdk 19 | sudo apt update 20 | sudo apt-get install blocksds-sdk 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | - name: Get short SHA 24 | id: slug 25 | run: echo "sha8=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_OUTPUT 26 | - name: Build tool .nds 27 | run: | 28 | export BLOCKSDS=/opt/blocksds-toolchain/blocksds/ 29 | export ARM_NONE_EABI_PATH=/opt/blocksds-toolchain/bin/ 30 | make clean && make 31 | - name: Upload artifacts 32 | if: ${{ success() }} 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: superfw-flasher-${{ steps.slug.outputs.sha8 }} 36 | path: superfw-flasher.nds 37 | - name: Create release 38 | id: create_release 39 | if: startsWith(github.ref, 'refs/tags/') 40 | uses: actions/create-release@v1 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | with: 44 | tag_name: ${{ github.ref }} 45 | release_name: Release ${{ github.ref }} 46 | draft: false 47 | prerelease: false 48 | - name: Upload Release Asset 49 | uses: actions/upload-release-asset@v1 50 | if: startsWith(github.ref, 'refs/tags/') 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | with: 54 | upload_url: ${{ steps.create_release.outputs.upload_url }} 55 | asset_path: ./superfw-flasher.nds 56 | asset_name: superfw-flasher.nds 57 | asset_content_type: application/octet-stream 58 | 59 | -------------------------------------------------------------------------------- /source/sha256.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024 David Guillen Fandos 3 | * 4 | * This program is free software: you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see 16 | * . 17 | */ 18 | 19 | // Minimal SHA256 implementation 20 | 21 | #include 22 | #include 23 | 24 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 25 | #define read32be(x) __builtin_bswap32(x) 26 | #define read64be(x) __builtin_bswap64(x) 27 | #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 28 | #define read32be(x) (x) 29 | #define read64be(x) (x) 30 | #else 31 | #error Could not detect platform endianess 32 | #endif 33 | 34 | #define rotr(x, a) (((x) >> (a)) | ((x) << (32-(a)))) 35 | 36 | static const uint32_t sha256_kinit[] = { 37 | 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 38 | 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, 39 | }; 40 | 41 | const uint32_t sha256k[] = { 42 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 43 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 44 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 45 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 46 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 47 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 48 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 49 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, 50 | }; 51 | 52 | void sha256_transform(uint32_t *state, const void *data) { 53 | uint32_t w[16]; 54 | const uint32_t * dui = (uint32_t*)data; 55 | for (unsigned i = 0; i < 16; i++) 56 | w[i] = read32be(dui[i]); 57 | 58 | uint32_t ls[8]; 59 | memcpy(ls, state, sizeof(ls)); 60 | 61 | for (unsigned i = 0; i < 64; i++) { 62 | unsigned widx = i & 15; 63 | 64 | uint32_t s1 = rotr(ls[4], 6) ^ rotr(ls[4], 11) ^ rotr(ls[4], 25); 65 | uint32_t ch = (ls[4] & ls[5]) ^ ((~ls[4]) & ls[6]); 66 | uint32_t t1 = ls[7] + s1 + ch + sha256k[i] + w[widx]; 67 | uint32_t s0 = rotr(ls[0], 2) ^ rotr(ls[0], 13) ^ rotr(ls[0], 22); 68 | uint32_t mj = (ls[0] & ls[1]) ^ (ls[0] & ls[2]) ^ (ls[1] & ls[2]); 69 | uint32_t t2 = s0 + mj; 70 | 71 | uint32_t w1 = w[(i+ 1)&15]; 72 | uint32_t w9 = w[(i+ 9)&15]; 73 | uint32_t w14 = w[(i+14)&15]; 74 | 75 | w[widx] += (w9 + 76 | (rotr(w1, 7) ^ rotr(w1, 18) ^ (w1 >> 3)) + 77 | (rotr(w14, 17) ^ rotr(w14, 19) ^ (w14 >> 10))); 78 | 79 | ls[7] = ls[6]; 80 | ls[6] = ls[5]; 81 | ls[5] = ls[4]; 82 | ls[4] = ls[3] + t1; 83 | ls[3] = ls[2]; 84 | ls[2] = ls[1]; 85 | ls[1] = ls[0]; 86 | ls[0] = t1 + t2; 87 | } 88 | 89 | for (unsigned i = 0; i < 8; i++) 90 | state[i] += ls[i]; 91 | } 92 | 93 | void sha256_internal(const uint8_t *inbuffer, unsigned length, void *output) { 94 | uint32_t *state = (uint32_t*)output; 95 | uint64_t bitlen = length << 3; 96 | 97 | // Init state 98 | memcpy(state, sha256_kinit, sizeof(sha256_kinit)); 99 | 100 | while (length >= 64) { 101 | sha256_transform(state, inbuffer); 102 | inbuffer += 64; 103 | length -= 64; 104 | } 105 | 106 | // Last bits 107 | union { 108 | uint8_t chars[64]; 109 | uint32_t u32[16]; 110 | uint64_t u64[8]; 111 | } tmp = {0}; 112 | memcpy(tmp.chars, inbuffer, length); 113 | tmp.chars[length] = 0x80; 114 | 115 | if (length >= 56) { 116 | // Need an extra block 117 | sha256_transform(state, tmp.u32); 118 | memset(tmp.chars, 0, 64); 119 | } 120 | 121 | tmp.u64[7] = read64be(bitlen); 122 | sha256_transform(state, tmp.u32); 123 | } 124 | 125 | 126 | // Get the sha1sum for a buffer 127 | void sha256sum(const uint8_t *inbuffer, unsigned length, void *output) { 128 | uint32_t *state = (uint32_t*)output; 129 | sha256_internal(inbuffer, length, output); 130 | 131 | // Output conversion 132 | for (unsigned i = 0; i < 8; i++) 133 | state[i] = read32be(state[i]); 134 | } 135 | 136 | 137 | -------------------------------------------------------------------------------- /source/main.c: -------------------------------------------------------------------------------- 1 | 2 | // Copyright 2024 David Guillen Fandos 3 | 4 | // SuperCard firmware flashing tool. 5 | // 6 | // This NDS tool handles certain operations (like read/flash) on the Supercard 7 | // firmware flash memory. 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define MAPPED_FIRMWARE 0 20 | #define MAPPED_SDRAM 1 21 | 22 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 23 | #define MIN(a, b) ((a) > (b) ? (b) : (a)) 24 | 25 | typedef struct { 26 | uint32_t size; // Size in bytes 27 | uint32_t blkwrite; // Buffer writing capabilities (zero means disabled) 28 | uint32_t regioncnt; // Erase region count (ideally 1, or perhaps 0) 29 | struct { 30 | uint32_t blksize; // Block size in bytes 31 | uint32_t blkcount; // Number of blocks 32 | } regions[256]; 33 | } t_flash_info; 34 | extern t_flash_info flashinfo; 35 | 36 | enum { 37 | SupercardSD, 38 | SupercardLite, 39 | Superchis, 40 | MAXDEVICES 41 | }; 42 | unsigned devtype = SupercardSD; 43 | const char *devnames[] = { "Supercard SD", "Supercard Lite", "SuperChis" }; 44 | const unsigned flash_fwsizes[] = { 512*1024, 496*1024, 2*1024*1024 }; 45 | const unsigned flash_erase_timeout[] = { 60, 60, 300 * 8 }; 46 | 47 | void sha256sum(const uint8_t *inbuffer, unsigned length, void *output); 48 | 49 | void sleep_1ms() { 50 | for (unsigned i = 0; i < (1<<14); i++) 51 | asm volatile ("nop"); 52 | } 53 | 54 | // The flash device address bus is connected with some permutated wires. 55 | // The permutation seems to only apply to the 9 LSB. 56 | // In general we do not care unless we need to send a specifc address or play 57 | // with sector/page erase. 58 | // The supercard lite does not do any of this crazy mapping :) 59 | static uint32_t addr_perm(uint32_t addr) { 60 | if (devtype != SupercardSD) 61 | return addr; 62 | else 63 | return (addr & 0xFFFFFE02) | 64 | ((addr & 0x001) << 7) | 65 | ((addr & 0x004) << 4) | 66 | ((addr & 0x008) << 2) | 67 | ((addr & 0x010) >> 4) | 68 | ((addr & 0x020) >> 3) | 69 | ((addr & 0x040) << 2) | 70 | ((addr & 0x080) >> 3) | 71 | ((addr & 0x100) >> 5); 72 | } 73 | 74 | 75 | #define SUPERCARD_LITE_FLASHWR 0x1510 76 | 77 | void write_supercard_modereg(uint16_t value) { 78 | // Write magic value and then the mode value (twice) to trigger the mode change. 79 | // Using asm to ensure we place a proper memory barrier. 80 | const uint16_t MODESWITCH_MAGIC = 0xA55A; 81 | uint32_t REG_SC_MODE_REG_ADDR = 0x09FFFFFE; 82 | 83 | asm volatile ( 84 | "strh %1, [%0]\n" 85 | "strh %1, [%0]\n" 86 | "strh %2, [%0]\n" 87 | "strh %2, [%0]\n" 88 | :: "l"(REG_SC_MODE_REG_ADDR), 89 | "l"(MODESWITCH_MAGIC), 90 | "l"(value) 91 | : "memory"); 92 | } 93 | 94 | void reset_superchis_flashmap() { 95 | const uint16_t MODESWITCH_MAGIC = 0xA55A; 96 | uint32_t REG_SC_MODE_REG_ADDR = 0x09FFFFFE; 97 | 98 | for (unsigned i = 0; i < 8; i++) { 99 | asm volatile ( 100 | "strh %1, [%0]\n" 101 | "strh %1, [%0]\n" 102 | "strh %2, [%0]\n" 103 | "nop; nop;" 104 | :: "l"(REG_SC_MODE_REG_ADDR), 105 | "l"(MODESWITCH_MAGIC), 106 | "l"(0x100) 107 | : "memory"); 108 | } 109 | } 110 | 111 | void set_superchis_bankreg(unsigned bankn) { 112 | const uint16_t MODESWITCH_MAGIC = 0xA55A; 113 | uint32_t REG_SC_MODE_REG_ADDR = 0x09FFFFFE; 114 | 115 | asm volatile ( 116 | "strh %1, [%0]\n" 117 | "strh %1, [%0]\n" 118 | "strh %2, [%0]\n" 119 | "nop; nop;" 120 | :: "l"(REG_SC_MODE_REG_ADDR), 121 | "l"(MODESWITCH_MAGIC), 122 | "l"(0x70 | ((bankn & 1) << 3)) 123 | : "memory"); 124 | } 125 | 126 | void set_supercard_mode(unsigned mapped_area, bool write_access, bool sdcard_interface) { 127 | // Bit0: Controls SDRAM vs internal Flash mapping 128 | // Bit1: Controls whether the SD card interface is mapped into the ROM addresspace. 129 | // Bit2: Controls read-only/write access. Doubles as SRAM bank selector! 130 | uint16_t value = mapped_area | (sdcard_interface ? 0x2 : 0x0) | (write_access ? 0x4 : 0x0); 131 | 132 | write_supercard_modereg(value); 133 | } 134 | 135 | 136 | void enable_sc_flash() { 137 | if (devtype == SupercardLite) 138 | write_supercard_modereg(SUPERCARD_LITE_FLASHWR); 139 | else 140 | set_supercard_mode(MAPPED_FIRMWARE, true, false); 141 | } 142 | void disable_sc_flash() { 143 | if (devtype == SupercardLite) 144 | write_supercard_modereg(0); 145 | else 146 | set_supercard_mode(MAPPED_FIRMWARE, false, false); 147 | } 148 | void sram_map_bank(unsigned bankn) { 149 | if (devtype == Superchis) 150 | set_superchis_bankreg(bankn); 151 | else 152 | set_supercard_mode(MAPPED_FIRMWARE, bankn != 0, false); 153 | } 154 | 155 | #define SLOT2_BASE_U16 ((volatile uint16_t*)(0x08000000)) 156 | #define SLOT2_SRAM_U8 ((volatile uint8_t*)( 0x0A000000)) 157 | 158 | static unsigned test_sram() { 159 | bool pmode = sysGetCartOwner(); 160 | sysSetCartOwner(BUS_OWNER_ARM9); 161 | 162 | // Just write the SRAM with some well-known data, and read it back 163 | REG_EXMEMCNT |= 0x3; // Use the slowest possible access time. 164 | 165 | for (unsigned bank = 0; bank < 2; bank++) { 166 | sram_map_bank(bank); 167 | for (unsigned i = 0; i < 64*1024; i++) 168 | SLOT2_SRAM_U8[i] = i ^ (i * i) ^ 0x5A ^ bank; 169 | } 170 | 171 | unsigned numerrs = 0; 172 | for (unsigned bank = 0; bank < 2; bank++) { 173 | sram_map_bank(bank); 174 | for (unsigned i = 0; i < 64*1024; i++) 175 | if (SLOT2_SRAM_U8[i] != ((i ^ (i * i) ^ 0x5A ^ bank) & 0xFF)) 176 | numerrs++; 177 | } 178 | 179 | set_supercard_mode(MAPPED_FIRMWARE, false, false); 180 | sysSetCartOwner(pmode); 181 | return numerrs; 182 | } 183 | 184 | static uint32_t flash_ident() { 185 | // Map the GBA cart into the ARM9, enter flash mode with write enable. 186 | bool pmode = sysGetCartOwner(); 187 | sysSetCartOwner(BUS_OWNER_ARM9); 188 | enable_sc_flash(); 189 | 190 | REG_EXMEMCNT |= 0xF; // use slow mode 191 | for (unsigned i = 0; i < 32; i++) 192 | SLOT2_BASE_U16[0] = 0x00F0; // Reset for a few cycles 193 | 194 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x00AA; 195 | SLOT2_BASE_U16[addr_perm(0x2AA)] = 0x0055; 196 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x0090; 197 | 198 | uint32_t ret = SLOT2_BASE_U16[addr_perm(0x000)] << 16; 199 | ret |= SLOT2_BASE_U16[addr_perm(0x001)]; 200 | 201 | for (unsigned i = 0; i < 32; i++) 202 | SLOT2_BASE_U16[0] = 0x00F0; // Reset for a few cycles 203 | 204 | disable_sc_flash(); 205 | sysSetCartOwner(pmode); 206 | 207 | return ret; 208 | } 209 | 210 | static bool flash_cfi(t_flash_info *info) { 211 | memset(info, 0, sizeof(*info)); 212 | 213 | // Map the GBA cart into the ARM9, enter flash mode with write enable. 214 | bool pmode = sysGetCartOwner(); 215 | sysSetCartOwner(BUS_OWNER_ARM9); 216 | enable_sc_flash(); 217 | 218 | REG_EXMEMCNT |= 0xF; // use slow mode 219 | for (unsigned i = 0; i < 32; i++) 220 | SLOT2_BASE_U16[0] = 0x00F0; // Reset for a few cycles 221 | 222 | // Enter CFI mode and extract flash information 223 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x0098; 224 | uint8_t qs[3] = { 225 | SLOT2_BASE_U16[addr_perm(0x010)], 226 | SLOT2_BASE_U16[addr_perm(0x011)], 227 | SLOT2_BASE_U16[addr_perm(0x012)], 228 | }; 229 | bool ret = (qs[0] == 'Q' && qs[1] == 'R' && qs[2] == 'Y'); 230 | 231 | if (ret) { 232 | info->size = 1 << SLOT2_BASE_U16[addr_perm(0x027)]; 233 | info->blkwrite = (SLOT2_BASE_U16[addr_perm(0x02A)] & 0xFF); 234 | info->blkwrite = (info->blkwrite ? (1 << info->blkwrite) : 0); 235 | 236 | info->regioncnt = SLOT2_BASE_U16[addr_perm(0x02C)] & 0xFF; 237 | 238 | for (unsigned i = 0; i < info->regioncnt; i++) { 239 | unsigned baddr = 0x2D + i*4; 240 | info->regions[i].blkcount = ((SLOT2_BASE_U16[addr_perm(baddr)] & 0xFF) | 241 | ((SLOT2_BASE_U16[addr_perm(baddr + 1)] & 0xFF) << 8)) + 1; 242 | 243 | unsigned bs = ((SLOT2_BASE_U16[addr_perm(baddr + 2)] & 0xFF) | 244 | ((SLOT2_BASE_U16[addr_perm(baddr + 3)] & 0xFF) << 8)) << 8; 245 | info->regions[i].blksize = (bs ?: 128); 246 | } 247 | } 248 | 249 | for (unsigned i = 0; i < 32; i++) 250 | SLOT2_BASE_U16[0] = 0x00F0; // Reset for a few cycles 251 | 252 | disable_sc_flash(); 253 | sysSetCartOwner(pmode); 254 | 255 | return ret; 256 | } 257 | 258 | static void flash_prot_dump(bool prot[128]) { 259 | bool pmode = sysGetCartOwner(); 260 | sysSetCartOwner(BUS_OWNER_ARM9); 261 | enable_sc_flash(); 262 | 263 | for (unsigned sa = 0; sa < 128; sa++) { 264 | 265 | REG_EXMEMCNT |= 0xF; // use slow mode 266 | for (unsigned i = 0; i < 32; i++) 267 | SLOT2_BASE_U16[0] = 0x00F0; // Reset for a few cycles 268 | 269 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x00AA; 270 | SLOT2_BASE_U16[addr_perm(0x2AA)] = 0x0055; 271 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x0090; 272 | 273 | unsigned protv = SLOT2_BASE_U16[addr_perm(0x002 | (sa << 11))]; 274 | 275 | for (unsigned i = 0; i < 32; i++) 276 | SLOT2_BASE_U16[0] = 0x00F0; // Reset for a few cycles 277 | 278 | prot[sa] = protv & 1; 279 | } 280 | 281 | disable_sc_flash(); 282 | sysSetCartOwner(pmode); 283 | } 284 | 285 | // Performs a flash full-chip erase. 286 | static bool flash_erase() { 287 | // Map the GBA cart into the ARM9, enter flash mode with write enable. 288 | bool pmode = sysGetCartOwner(); 289 | sysSetCartOwner(BUS_OWNER_ARM9); 290 | enable_sc_flash(); 291 | 292 | REG_EXMEMCNT |= 0xF; // use slow mode 293 | for (unsigned i = 0; i < 32; i++) 294 | SLOT2_BASE_U16[0] = 0x00F0; // Reset for a few cycles 295 | 296 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x00AA; 297 | SLOT2_BASE_U16[addr_perm(0x2AA)] = 0x0055; 298 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x0080; // Erase command 299 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x00AA; 300 | SLOT2_BASE_U16[addr_perm(0x2AA)] = 0x0055; 301 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x0010; // Full chip erase! 302 | 303 | // Wait for the erase operation to finish. We rely on Q6 toggling: 304 | for (unsigned i = 0; i < flash_erase_timeout[devtype]*1000; i++) { 305 | sleep_1ms(); 306 | if (SLOT2_BASE_U16[0] == SLOT2_BASE_U16[0]) 307 | break; 308 | } 309 | bool retok = (SLOT2_BASE_U16[0] == SLOT2_BASE_U16[0]); 310 | 311 | for (unsigned i = 0; i < 32; i++) 312 | SLOT2_BASE_U16[0] = 0x00F0; // Reset for a few cycles 313 | 314 | disable_sc_flash(); 315 | sysSetCartOwner(pmode); 316 | 317 | return retok; 318 | } 319 | 320 | // Checks that the erase operation actually erased the memory. 321 | static bool flash_erase_check(unsigned size) { 322 | // Map the GBA cart into the ARM9, enter flash mode with write enable. 323 | bool pmode = sysGetCartOwner(); 324 | sysSetCartOwner(BUS_OWNER_ARM9); 325 | enable_sc_flash(); 326 | REG_EXMEMCNT |= 0xF; // use slow mode 327 | 328 | bool errf = false; 329 | for (unsigned i = 0; i < size; i+= 2) { 330 | errf = (SLOT2_BASE_U16[i / 2] != 0xFFFF); 331 | if (errf) 332 | break; 333 | } 334 | 335 | disable_sc_flash(); 336 | sysSetCartOwner(pmode); 337 | 338 | return errf; 339 | } 340 | 341 | 342 | static bool flash_write(const uint8_t *buf, unsigned size) { 343 | bool ok = true; 344 | // Map the GBA cart into the ARM9, enter flash mode with write enable. 345 | bool pmode = sysGetCartOwner(); 346 | sysSetCartOwner(BUS_OWNER_ARM9); 347 | enable_sc_flash(); 348 | REG_EXMEMCNT |= 0xF; // use slow mode 349 | 350 | SLOT2_BASE_U16[0] = 0x00F0; // Force IDLE 351 | 352 | for (unsigned i = 0; i < size; i+= 2) { 353 | uint16_t value = buf[i] | (buf[i+1] << 8); 354 | 355 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x00AA; 356 | SLOT2_BASE_U16[addr_perm(0x2AA)] = 0x0055; 357 | SLOT2_BASE_U16[addr_perm(0x555)] = 0x00A0; // Program command 358 | 359 | // Perform the actual write operation 360 | SLOT2_BASE_U16[i / 2] = value; 361 | 362 | // It should take less than 1ms usually (in the order of us). 363 | for (unsigned j = 0; j < 32*1024; j++) { 364 | if (SLOT2_BASE_U16[0] == SLOT2_BASE_U16[0]) 365 | break; 366 | } 367 | bool notfinished = (SLOT2_BASE_U16[0] != SLOT2_BASE_U16[0]); 368 | 369 | SLOT2_BASE_U16[0] = 0x00F0; // Finish operation or abort. 370 | 371 | // Timed out or the write was incorrect 372 | if (notfinished || SLOT2_BASE_U16[i / 2] != value) { 373 | ok = false; 374 | break; 375 | } 376 | } 377 | 378 | disable_sc_flash(); 379 | sysSetCartOwner(pmode); 380 | 381 | return ok; 382 | } 383 | 384 | static bool flash_validate(const uint8_t *fwimg, unsigned fwsize) { 385 | // Map the GBA cart into the ARM9, enter flash mode with write enable. 386 | bool pmode = sysGetCartOwner(); 387 | sysSetCartOwner(BUS_OWNER_ARM9); 388 | enable_sc_flash(); 389 | 390 | return (!memcmp(fwimg, (uint8_t*)0x08000000, fwsize)); 391 | 392 | disable_sc_flash(); 393 | sysSetCartOwner(pmode); 394 | } 395 | 396 | static bool flash_dump(const char *filename, unsigned size) { 397 | // Map the GBA cart into the ARM9, enter flash mode with write enable. 398 | bool pmode = sysGetCartOwner(); 399 | sysSetCartOwner(BUS_OWNER_ARM9); 400 | enable_sc_flash(); 401 | 402 | char *data = (char*)malloc(size); 403 | memcpy(data, (void*)0x08000000, size); 404 | 405 | disable_sc_flash(); 406 | sysSetCartOwner(pmode); 407 | 408 | FILE *fd = fopen(filename, "wb"); 409 | if (!fd) { 410 | free(data); 411 | return false; 412 | } 413 | 414 | fwrite(data, 1, size, fd); 415 | 416 | fclose(fd); 417 | free(data); 418 | return true; 419 | } 420 | 421 | static bool rom_dump(const char *filename) { 422 | // Map the GBA cart into the ARM9, enter flash mode with write enable. 423 | FILE *fd = fopen(filename, "wb"); 424 | if (!fd) 425 | return false; 426 | 427 | char *data = (char*)malloc(512*1024); 428 | bool pmode = sysGetCartOwner(); 429 | sysSetCartOwner(BUS_OWNER_ARM9); 430 | set_supercard_mode(MAPPED_SDRAM, true, false); 431 | 432 | for (unsigned i = 0; i < 64; i++) { 433 | memcpy(data, (void*)(0x08000000 + i*512*1024), 512*1024); 434 | fwrite(data, 1, 512*1024, fd); 435 | } 436 | 437 | set_supercard_mode(MAPPED_FIRMWARE, false, false); 438 | sysSetCartOwner(pmode); 439 | 440 | fclose(fd); 441 | free(data); 442 | return true; 443 | } 444 | 445 | char superfw_str[128]; 446 | 447 | const char * firmware_ident() { 448 | // Identify a valid SuperFW firmware. 449 | if (!memcmp((uint8_t*)0x080000F0, "SUPERFW~DAVIDGF", 16)) { 450 | unsigned version = *(uint32_t*)0x080000C4; 451 | unsigned commtid = *(uint32_t*)0x080000C8; 452 | snprintf(superfw_str, sizeof(superfw_str), "SuperFW version %u.%u (%08x)", 453 | (version >> 16), (version & 0xFFFF), commtid); 454 | return superfw_str; 455 | } 456 | 457 | return NULL; 458 | } 459 | 460 | bool valid_header(const uint8_t *fw) { 461 | const uint8_t logo_hash[] = {0x08,0xa0,0x15,0x3c,0xfd,0x6b,0x0e,0xa5,0x4b,0x93,0x8f,0x7d,0x20,0x99,0x33,0xfa}; 462 | 463 | // Check the logo 464 | uint8_t hash[32]; 465 | sha256sum(&fw[0x4], 156, hash); 466 | bool logo_ok = !memcmp(hash, logo_hash, sizeof(logo_hash)); 467 | 468 | // Check that the checksum is also valid 469 | uint8_t checksum = 0x19; 470 | for (unsigned i = 0xA0; i < 0xBD; i++) 471 | checksum += fw[i]; 472 | checksum = -checksum; 473 | bool checksum_ok = checksum == fw[0xBD]; 474 | 475 | return logo_ok && checksum_ok; 476 | } 477 | 478 | typedef struct { 479 | char fn[PATH_MAX]; 480 | bool isdir; 481 | } t_fs_entry; 482 | 483 | int fncomp(const void* a, const void* b) { 484 | const t_fs_entry* ea = (t_fs_entry*)a; 485 | const t_fs_entry* eb = (t_fs_entry*)b; 486 | // Sort directories first 487 | if (ea->isdir && !eb->isdir) 488 | return -1; 489 | if (eb->isdir && !ea->isdir) 490 | return 1; 491 | return strcmp(ea->fn, eb->fn); 492 | } 493 | 494 | t_fs_entry *listdir(const char *path, int *nume) { 495 | unsigned cap = 8, nument = 0; 496 | t_fs_entry *ret = (t_fs_entry*)malloc(cap * sizeof(t_fs_entry)); 497 | ret[0].fn[0] = 0; 498 | 499 | DIR *dirp = opendir(path); 500 | while (1) { 501 | struct dirent *cur = readdir(dirp); 502 | if (!cur || !cur->d_name[0]) 503 | break; 504 | if (cur->d_name[0] == '.' && !cur->d_name[1]) 505 | continue; 506 | 507 | strcpy(ret[nument].fn, cur->d_name); 508 | ret[nument].isdir = cur->d_type == DT_DIR; 509 | if (cur->d_type == DT_DIR) 510 | strcat(ret[nument].fn, "/"); 511 | nument++; 512 | 513 | if (nument >= cap) { 514 | cap += 8; 515 | ret = (t_fs_entry*)realloc(ret, cap * sizeof(t_fs_entry)); 516 | } 517 | ret[nument].fn[0] = 0; 518 | } 519 | 520 | qsort(ret, nument, sizeof(t_fs_entry), fncomp); 521 | 522 | if (nume) *nume = nument; 523 | return ret; 524 | } 525 | 526 | void select_image(const char *path, PrintConsole *tops, PrintConsole *bots) { 527 | consoleSelect(bots); 528 | 529 | printf("Assuming that the flash is %d KiBs in size\n", flash_fwsizes[devtype] >> 10); 530 | 531 | struct stat st; 532 | if (stat(path, &st)) { 533 | printf("Could not stat() the selected file (%s)\n", path); 534 | return; 535 | } 536 | if (st.st_size > flash_fwsizes[devtype]) { 537 | printf("The file is bigger than the assumed flash size!\n"); 538 | return; 539 | } 540 | 541 | FILE *fd = fopen(path, "rb"); 542 | if (!fd) { 543 | printf("Could not open the selected file!\n"); 544 | return; 545 | } 546 | 547 | printf("Reading file ...\n"); 548 | uint8_t *fwimg = (uint8_t*)malloc(st.st_size); 549 | size_t ret = fread(fwimg, 1, st.st_size, fd); 550 | fclose(fd); 551 | if (ret != st.st_size) { 552 | free(fwimg); 553 | printf("Could not read the file correctly!\n"); 554 | return; 555 | } 556 | 557 | uint8_t hash[32]; 558 | sha256sum(fwimg, st.st_size, hash); 559 | printf("File loaded (size %d) with hash: %02x%02x%02x%02x%02x%02x%02x%02x!\n", (int)st.st_size, 560 | hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7]); 561 | 562 | if (!valid_header(fwimg)) { 563 | free(fwimg); 564 | printf("Invalid firmware file detected (invalid header)\n"); 565 | return; 566 | } else { 567 | printf("Looks like a valid GBA rom/firmware\n"); 568 | } 569 | 570 | // TODO: Parse SuperFW firmware images for more info. 571 | 572 | consoleSelect(tops); 573 | consoleClear(); 574 | printf("\x1b[1;5HSuperFW flashing tool"); 575 | 576 | printf("\x1b[4;2HFile: %s", path); 577 | printf("\x1b[5;2HSize: %ld bytes", st.st_size); 578 | 579 | printf("\x1b[9;9HReady to flash"); 580 | printf("\x1b[12;2HPress L + R + A to begin"); 581 | 582 | printf("\x1b[14;2HPress B to cancel"); 583 | 584 | while (1) { 585 | swiWaitForVBlank(); 586 | scanKeys(); 587 | 588 | if (keysDown() & KEY_B) 589 | break; 590 | 591 | if ((keysHeld() & (KEY_L|KEY_R|KEY_A)) == (KEY_L|KEY_R|KEY_A)) { 592 | consoleSelect(bots); 593 | printf("Erasing flash chip ...\n"); 594 | if (!flash_erase()) { 595 | printf("\x1b[31;1mErase failed!\x1b[37;1m\n"); 596 | break; 597 | } 598 | printf("\x1b[32;1mErase operation complete\x1b[37;1m\n"); 599 | 600 | printf("Verifying erase operation (%d KiB) ...\n", flash_fwsizes[devtype] >> 10); 601 | 602 | if (flash_erase_check(flash_fwsizes[devtype])) { 603 | printf("\x1b[31;1mErase validation failed!\x1b[37;1m\n"); 604 | break; 605 | } 606 | printf("Writing flash chip ...\n"); 607 | 608 | if (flash_write(fwimg, st.st_size)) 609 | printf("\x1b[32;1mFirmware flashed successfully!\x1b[37;1m\n"); 610 | else 611 | printf("\x1b[31;1mFlashing operation failed!\x1b[37;1m\n"); 612 | 613 | printf("Verifying written data ...\n"); 614 | if (flash_validate(fwimg, st.st_size)) 615 | printf("\x1b[32;1mValidation passed!\x1b[37;1m\n"); 616 | else 617 | printf("\x1b[31;1mValidation error!\x1b[37;1m\n"); 618 | 619 | break; 620 | } 621 | } 622 | 623 | free(fwimg); 624 | } 625 | 626 | void key_handler() { 627 | lcdSwap(); 628 | } 629 | 630 | int main(int argc, char **argv) { 631 | PrintConsole tops, bots; 632 | 633 | videoSetMode(MODE_0_2D); 634 | videoSetModeSub(MODE_0_2D); 635 | vramSetBankA(VRAM_A_MAIN_BG); 636 | vramSetBankC(VRAM_C_SUB_BG); 637 | 638 | consoleInit(&tops, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true); 639 | consoleInit(&bots, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true); 640 | 641 | irqSet(IRQ_KEYS, key_handler); 642 | irqEnable(IRQ_KEYS); 643 | REG_KEYCNT = KEY_SELECT | (1 << 14); 644 | 645 | // Init FAT filesystem and slot2 ... 646 | consoleSelect(&bots); 647 | consoleClear(); 648 | printf("Debug console:\n\n"); 649 | if (!fatInitDefault()) { 650 | perror("fatInitDefault()"); 651 | } 652 | printf("DLDI name:\n%s\n\n", io_dldi_data->friendlyName); 653 | printf("DSi mode: %d\n\n", isDSiMode()); 654 | 655 | while (1) { 656 | 657 | while (1) { 658 | // Render simple dev sel menu 659 | consoleSelect(&tops); 660 | consoleClear(); 661 | printf("\x1b[36;1m"); 662 | printf("\x1b[1;5HSuperFW flashing tool"); 663 | printf("\x1b[37;1m"); 664 | printf("\x1b[5;1H Select device type"); 665 | printf("\x1b[9;3H < %s >", devnames[devtype]); 666 | printf("\x1b[13;0H Select the right device type"); 667 | 668 | unsigned keys = keysDown(); 669 | 670 | if (keys & KEY_LEFT) 671 | devtype = (devtype + MAXDEVICES - 1) % MAXDEVICES; 672 | if (keys & KEY_RIGHT) 673 | devtype = (devtype + 1) % MAXDEVICES; 674 | 675 | if (keys & KEY_A) 676 | break; 677 | if (keys & KEY_START) 678 | return 0; 679 | 680 | swiWaitForVBlank(); 681 | scanKeys(); 682 | } 683 | 684 | if (devtype == Superchis) 685 | reset_superchis_flashmap(); 686 | 687 | unsigned menu_sel = 0; 688 | while (1) { 689 | // Render menu 690 | consoleSelect(&tops); 691 | consoleClear(); 692 | printf("\x1b[36;1m"); 693 | printf("\x1b[1;5HSuperFW flashing tool"); 694 | printf("\x1b[37;1m"); 695 | 696 | printf("\x1b[5;1H %s Identify cart", menu_sel == 0 ? ">" : " "); 697 | printf("\x1b[7;1H %s Dump flash", menu_sel == 1 ? ">" : " "); 698 | printf("\x1b[9;1H %s Write flash", menu_sel == 2 ? ">" : " "); 699 | printf("\x1b[11;1H %s Dump ROM", menu_sel == 3 ? ">" : " "); 700 | printf("\x1b[13;1H %s Test SRAM", menu_sel == 4 ? ">" : " "); 701 | if (devtype == SupercardLite) 702 | printf("\x1b[15;1H %s Protbits dump", menu_sel == 5 ? ">" : " "); 703 | 704 | printf("\x1b[20;8H Version 0.7"); 705 | printf("\x1b[22;6H %s", devnames[devtype]); 706 | 707 | swiWaitForVBlank(); 708 | scanKeys(); 709 | unsigned keys = keysDown(); 710 | 711 | if (keys & KEY_A) { 712 | switch (menu_sel) { 713 | case 0: 714 | consoleSelect(&bots); 715 | printf("Identified flash device ID as %08lx\n", flash_ident()); 716 | { 717 | const char *fwname = firmware_ident(); 718 | if (fwname) 719 | printf("Identified the firmware as %s\n", fwname); 720 | else { 721 | if (!valid_header((uint8_t*)0x08000000)) 722 | printf("Invalid firmware header detected!\n"); 723 | else 724 | printf("Unknown firmware detected!\n"); 725 | } 726 | 727 | t_flash_info info; 728 | if (!flash_cfi(&info)) 729 | printf("Flash does not support CFI\n"); 730 | else { 731 | printf("Size: %lu bytes\n", info.size); 732 | printf("Write buffer size: %lu bytes\n", info.blkwrite); 733 | printf("Has %lu erase sections\n", info.regioncnt); 734 | for (unsigned i = 0; i < info.regioncnt; i++) 735 | printf("S%u cnt: %lu size: %lu\n", i, info.regions[i].blkcount, info.regions[i].blksize); 736 | } 737 | } 738 | break; 739 | case 5: 740 | { 741 | bool prot[128]; 742 | consoleSelect(&bots); 743 | flash_prot_dump(prot); 744 | 745 | for (unsigned i = 0; i < 128; i++) 746 | printf("%d ", prot[i] ? 1 : 0); 747 | printf("\n"); 748 | } 749 | break; 750 | case 4: 751 | { 752 | unsigned numerrs = test_sram(); 753 | consoleSelect(&bots); 754 | if (numerrs) 755 | printf("\x1b[31;1mSRAM check failed with %d diffs!\x1b[37;1m\n", numerrs); 756 | else 757 | printf("\x1b[32;1mSRAM integrity check passed!\x1b[37;1m\n"); 758 | } 759 | break; 760 | case 1: 761 | consoleSelect(&bots); 762 | printf("Starting dump (%d KiB)...\n", flash_fwsizes[devtype] >> 10); 763 | if (!flash_dump("fat:/sc_flash_dump.bin", flash_fwsizes[devtype])) 764 | printf("Failed!\n"); 765 | else 766 | printf("Dump complete! File written: sc_flash_dump.bin\n"); 767 | break; 768 | case 3: 769 | consoleSelect(&bots); 770 | printf("Starting dump ...\n"); 771 | if (!rom_dump("fat:/sc_rom_dump.bin")) 772 | printf("Failed!\n"); 773 | else 774 | printf("Dump complete! File written: sc_rom_dump.bin\n"); 775 | break; 776 | case 2: 777 | // Present a small file browser or something. 778 | char curpath[PATH_MAX] = "fat:/"; 779 | int cur_entry = 0, top_entry = 0; 780 | int num_entries; 781 | t_fs_entry * l = listdir(curpath, &num_entries); 782 | 783 | while (1) { 784 | swiWaitForVBlank(); 785 | scanKeys(); 786 | unsigned keys = keysDown(); 787 | 788 | if (keys & KEY_B) 789 | break; 790 | if (keys & KEY_A) { 791 | if (l[cur_entry].fn[0]) { 792 | char tmp[PATH_MAX]; 793 | strcpy(tmp, curpath); 794 | strcat(tmp, "/"); 795 | strcat(tmp, l[cur_entry].fn); 796 | 797 | if (l[cur_entry].fn[strlen(l[cur_entry].fn)-1] == '/') { 798 | // Is a directory, go down the rabbit hole 799 | realpath(tmp, curpath); // Simplify the path (like "//" or "/../") 800 | 801 | top_entry = cur_entry = 0; 802 | free(l); 803 | l = listdir(curpath, &num_entries); 804 | } 805 | else { 806 | select_image(tmp, &tops, &bots); 807 | break; // Go back 808 | } 809 | } 810 | } 811 | 812 | if (keys & KEY_DOWN) 813 | cur_entry = MIN(num_entries - 1, cur_entry + 1); 814 | if (keys & KEY_UP) 815 | cur_entry = MAX(0, cur_entry - 1); 816 | if (keys & KEY_RIGHT) 817 | cur_entry = MIN(cur_entry + 8, num_entries - 1); 818 | if (keys & KEY_LEFT) 819 | cur_entry = MAX(0, cur_entry - 8); 820 | 821 | if (cur_entry - top_entry >= 8) 822 | top_entry = cur_entry - 7; 823 | if (cur_entry < top_entry) 824 | top_entry = cur_entry; 825 | 826 | // Render path list 827 | consoleSelect(&tops); 828 | consoleClear(); 829 | printf("\x1b[1;5HSuperFW flashing tool"); 830 | 831 | for (unsigned i = 0; i < 8; i++) { 832 | if (!l[top_entry + i].fn[0]) 833 | break; 834 | printf("\x1b[%d;1H %s %.28s", 5 + i*2, i + top_entry == cur_entry ? ">" : " ", l[top_entry + i].fn); 835 | } 836 | } 837 | free(l); 838 | break; 839 | }; 840 | } 841 | 842 | if (keys & KEY_B) 843 | break; 844 | 845 | const uint8_t maxopt[3] = { 5, 6, 5 }; 846 | 847 | if (keys & KEY_DOWN) 848 | menu_sel = (menu_sel + 1) % maxopt[devtype]; 849 | if (keys & KEY_UP) 850 | menu_sel = (menu_sel + maxopt[devtype] - 1) % maxopt[devtype]; 851 | } 852 | } 853 | 854 | return 0; 855 | } 856 | 857 | --------------------------------------------------------------------------------