├── .gitignore ├── .gitmodules ├── Makefile ├── filesystem ├── loading_dot.sprite └── shadow.sprite ├── sc64menu.n64 └── src ├── boot ├── boot.c ├── boot.h ├── boot_io.h ├── cic.c ├── cic.h └── reboot.S ├── flashcart ├── 64drive │ ├── 64drive.c │ ├── 64drive.h │ ├── 64drive_ll.c │ ├── 64drive_ll.h │ └── README.md ├── flashcart.c ├── flashcart.h ├── flashcart_utils.c ├── flashcart_utils.h └── sc64 │ ├── README.md │ ├── sc64.c │ ├── sc64.h │ ├── sc64_ll.c │ └── sc64_ll.h ├── main.c └── utils ├── fs.c ├── fs.h └── utils.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Build files 2 | *.a 3 | build/ 4 | 5 | .libdragon 6 | 7 | ## OSX junk 8 | .DS_Store 9 | .Trashes 10 | ._* 11 | 12 | ## Temporary files 13 | *.tmp 14 | *.swp 15 | *~ 16 | 17 | *.v64 18 | *.z64 19 | *.elf 20 | *.bin 21 | *.o 22 | *.dfs 23 | *.d 24 | 25 | node_modules 26 | libdragon 27 | 28 | ## Editors 29 | .vscode 30 | 31 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libdragon"] 2 | path = libdragon 3 | url = https://github.com/DragonMinded/libdragon 4 | branch = trunk 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | V=1 2 | SOURCE_DIR=src 3 | BUILD_DIR=build 4 | include $(N64_INST)/include/n64.mk 5 | 6 | sc64: mockup_menu.z64 7 | @cp $< sc64menu.n64 8 | .PHONY: sc64 9 | 10 | all: mockup_menu.z64 sc64 11 | .PHONY: all 12 | 13 | OBJS = $(BUILD_DIR)/main.o \ 14 | $(BUILD_DIR)/boot/cic.o \ 15 | $(BUILD_DIR)/boot/boot.o \ 16 | $(BUILD_DIR)/boot/reboot.o \ 17 | $(BUILD_DIR)/flashcart/64drive/64drive_ll.o \ 18 | $(BUILD_DIR)/flashcart/64drive/64drive.o \ 19 | $(BUILD_DIR)/flashcart/flashcart_utils.o \ 20 | $(BUILD_DIR)/flashcart/flashcart.o \ 21 | $(BUILD_DIR)/flashcart/sc64/sc64_ll.o \ 22 | $(BUILD_DIR)/flashcart/sc64/sc64.o \ 23 | $(BUILD_DIR)/utils/fs.o 24 | 25 | mockup_menu.z64: N64_ROM_TITLE="Mockup Menu" 26 | mockup_menu.z64: $(BUILD_DIR)/spritemap.dfs 27 | 28 | $(BUILD_DIR)/spritemap.dfs: $(wildcard filesystem/*) 29 | $(BUILD_DIR)/mockup_menu.elf: $(OBJS) 30 | 31 | clean: 32 | rm -f $(BUILD_DIR)/* *.z64 33 | .PHONY: clean 34 | 35 | -include $(wildcard $(BUILD_DIR)/*.d) -------------------------------------------------------------------------------- /filesystem/loading_dot.sprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthCarvalho/n64menu/63810ab7e3150c7ca323d0e559aeec5100e22997/filesystem/loading_dot.sprite -------------------------------------------------------------------------------- /filesystem/shadow.sprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthCarvalho/n64menu/63810ab7e3150c7ca323d0e559aeec5100e22997/filesystem/shadow.sprite -------------------------------------------------------------------------------- /sc64menu.n64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArthCarvalho/n64menu/63810ab7e3150c7ca323d0e559aeec5100e22997/sc64menu.n64 -------------------------------------------------------------------------------- /src/boot/boot.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "boot_io.h" 4 | #include "boot.h" 5 | #include "cic.h" 6 | 7 | 8 | #define C0_STATUS_FR (1 << 26) 9 | #define C0_STATUS_CU0 (1 << 28) 10 | #define C0_STATUS_CU1 (1 << 29) 11 | 12 | 13 | extern uint32_t reboot_start __attribute__((section(".text"))); 14 | extern size_t reboot_size __attribute__((section(".text"))); 15 | 16 | 17 | static io32_t *boot_get_device_base (boot_params_t *params) { 18 | io32_t *device_base_address = ROM_CART; 19 | if (params->device_type == BOOT_DEVICE_TYPE_64DD) { 20 | device_base_address = ROM_DDIPL; 21 | } 22 | return device_base_address; 23 | } 24 | 25 | static void boot_detect_cic_seed (boot_params_t *params) { 26 | io32_t *base = boot_get_device_base(params); 27 | 28 | uint8_t ipl3[IPL3_LENGTH] __attribute__((aligned(8))); 29 | 30 | data_cache_hit_writeback_invalidate(ipl3, sizeof(ipl3)); 31 | dma_read_raw_async(ipl3, (uint32_t) (&base[16]), sizeof(ipl3)); 32 | dma_wait(); 33 | 34 | params->cic_seed = cic_get_seed(cic_detect(ipl3)); 35 | } 36 | 37 | 38 | void boot (boot_params_t *params) { 39 | if (params->tv_type == BOOT_TV_TYPE_PASSTHROUGH) { 40 | switch (get_tv_type()) { 41 | case TV_PAL: 42 | params->tv_type = BOOT_TV_TYPE_PAL; 43 | break; 44 | case TV_NTSC: 45 | params->tv_type = BOOT_TV_TYPE_NTSC; 46 | break; 47 | case TV_MPAL: 48 | params->tv_type = BOOT_TV_TYPE_MPAL; 49 | break; 50 | default: 51 | params->tv_type = BOOT_TV_TYPE_NTSC; 52 | break; 53 | } 54 | } 55 | 56 | if (params->detect_cic_seed) { 57 | boot_detect_cic_seed(params); 58 | } 59 | 60 | C0_WRITE_STATUS(C0_STATUS_CU1 | C0_STATUS_CU0 | C0_STATUS_FR); 61 | 62 | while (!(cpu_io_read(&SP->SR) & SP_SR_HALT)); 63 | 64 | cpu_io_write(&SP->SR, 65 | SP_SR_CLR_SIG7 | 66 | SP_SR_CLR_SIG6 | 67 | SP_SR_CLR_SIG5 | 68 | SP_SR_CLR_SIG4 | 69 | SP_SR_CLR_SIG3 | 70 | SP_SR_CLR_SIG2 | 71 | SP_SR_CLR_SIG1 | 72 | SP_SR_CLR_SIG0 | 73 | SP_SR_CLR_INTR_BREAK | 74 | SP_SR_CLR_SSTEP | 75 | SP_SR_CLR_INTR | 76 | SP_SR_CLR_BROKE | 77 | SP_SR_SET_HALT 78 | ); 79 | cpu_io_write(&SP->SEMAPHORE, 0); 80 | cpu_io_write(&SP->PC, 0); 81 | 82 | while (cpu_io_read(&SP->DMA_BUSY)); 83 | 84 | cpu_io_write(&PI->SR, PI_SR_CLR_INTR | PI_SR_RESET); 85 | while ((cpu_io_read(&VI->CURR_LINE) & ~(VI_CURR_LINE_FIELD)) != 0); 86 | cpu_io_write(&VI->V_INTR, 0x3FF); 87 | cpu_io_write(&VI->H_LIMITS, 0); 88 | cpu_io_write(&VI->CURR_LINE, 0); 89 | cpu_io_write(&AI->MADDR, 0); 90 | cpu_io_write(&AI->LEN, 0); 91 | 92 | while (cpu_io_read(&SP->SR) & SP_SR_DMA_BUSY); 93 | 94 | uint32_t *reboot_src = &reboot_start; 95 | io32_t *reboot_dst = SP_MEM->IMEM; 96 | size_t reboot_instructions = (size_t) (&reboot_size) / sizeof(uint32_t); 97 | 98 | for (int i = 0; i < reboot_instructions; i++) { 99 | cpu_io_write(&reboot_dst[i], reboot_src[i]); 100 | } 101 | 102 | cpu_io_write(&PI->DOM[0].LAT, 0xFF); 103 | cpu_io_write(&PI->DOM[0].PWD, 0xFF); 104 | cpu_io_write(&PI->DOM[0].PGS, 0x0F); 105 | cpu_io_write(&PI->DOM[0].RLS, 0x03); 106 | 107 | io32_t *base = boot_get_device_base(params); 108 | uint32_t pi_config = io_read((uint32_t) (base)); 109 | 110 | cpu_io_write(&PI->DOM[0].LAT, pi_config & 0xFF); 111 | cpu_io_write(&PI->DOM[0].PWD, pi_config >> 8); 112 | cpu_io_write(&PI->DOM[0].PGS, pi_config >> 16); 113 | cpu_io_write(&PI->DOM[0].RLS, pi_config >> 20); 114 | 115 | if (cpu_io_read(&DPC->SR) & DPC_SR_XBUS_DMEM_DMA) { 116 | while (cpu_io_read(&DPC->SR) & DPC_SR_PIPE_BUSY); 117 | } 118 | 119 | io32_t *ipl3_src = base; 120 | io32_t *ipl3_dst = SP_MEM->DMEM; 121 | 122 | for (int i = 16; i < 1024; i++) { 123 | cpu_io_write(&ipl3_dst[i], io_read((uint32_t) (&ipl3_src[i]))); 124 | } 125 | 126 | register uint32_t boot_device asm ("s3"); 127 | register uint32_t tv_type asm ("s4"); 128 | register uint32_t reset_type asm ("s5"); 129 | register uint32_t cic_seed asm ("s6"); 130 | register uint32_t version asm ("s7"); 131 | 132 | boot_device = (params->device_type & 0x01); 133 | tv_type = (params->tv_type & 0x03); 134 | reset_type = BOOT_RESET_TYPE_COLD; 135 | cic_seed = (params->cic_seed & 0xFF); 136 | version = (params->tv_type == BOOT_TV_TYPE_PAL) ? 6 137 | : (params->tv_type == BOOT_TV_TYPE_NTSC) ? 1 138 | : (params->tv_type == BOOT_TV_TYPE_MPAL) ? 4 139 | : 0; 140 | 141 | asm volatile ( 142 | "la $t3, reboot \n" 143 | "jr $t3 \n" :: 144 | [boot_device] "r" (boot_device), 145 | [tv_type] "r" (tv_type), 146 | [reset_type] "r" (reset_type), 147 | [cic_seed] "r" (cic_seed), 148 | [version] "r" (version) : 149 | "t3" 150 | ); 151 | 152 | while (1); 153 | } 154 | -------------------------------------------------------------------------------- /src/boot/boot.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file boot.h 3 | * @brief Flashcart Boot Subsystem 4 | * @ingroup boot 5 | */ 6 | 7 | #ifndef BOOT_H__ 8 | #define BOOT_H__ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | /** @brief Boot device type enumeration */ 16 | typedef enum { 17 | BOOT_DEVICE_TYPE_ROM = 0, 18 | BOOT_DEVICE_TYPE_64DD = 1, 19 | } boot_device_type_t; 20 | 21 | /** @brief Reset type enumeration */ 22 | typedef enum { 23 | BOOT_RESET_TYPE_COLD = 0, 24 | BOOT_RESET_TYPE_NMI = 1, 25 | } boot_reset_type_t; 26 | 27 | /** @brief TV type enumeration */ 28 | typedef enum { 29 | BOOT_TV_TYPE_PAL = 0, 30 | BOOT_TV_TYPE_NTSC = 1, 31 | BOOT_TV_TYPE_MPAL = 2, 32 | BOOT_TV_TYPE_PASSTHROUGH = 3, 33 | } boot_tv_type_t; 34 | 35 | /** @brief Boot Parameters Structure */ 36 | typedef struct { 37 | boot_device_type_t device_type; 38 | boot_tv_type_t tv_type; 39 | uint8_t cic_seed; 40 | bool detect_cic_seed; 41 | } boot_params_t; 42 | 43 | 44 | void boot (boot_params_t *params); 45 | 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/boot/boot_io.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file boot_io.h 3 | * @brief Flashcart Boot IO 4 | * @ingroup boot 5 | */ 6 | 7 | #ifndef BOOT_IO_H__ 8 | #define BOOT_IO_H__ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | typedef volatile uint8_t io8_t; 16 | typedef volatile uint32_t io32_t; 17 | 18 | 19 | #define UNCACHED(address) ((typeof(address)) (((io32_t) (address)) | (0xA0000000UL))) 20 | 21 | /** @brief Memory Structure. */ 22 | typedef struct { 23 | io32_t DMEM[1024]; 24 | io32_t IMEM[1024]; 25 | } sp_mem_t; 26 | 27 | #define SP_MEM_BASE (0x04000000UL) 28 | #define SP_MEM ((sp_mem_t *) SP_MEM_BASE) 29 | 30 | /** @brief SP Registers Structure. */ 31 | typedef struct { 32 | io32_t PADDR; 33 | io32_t MADDR; 34 | io32_t RD_LEN; 35 | io32_t WR_LEN; 36 | io32_t SR; 37 | io32_t DMA_FULL; 38 | io32_t DMA_BUSY; 39 | io32_t SEMAPHORE; 40 | io32_t __reserved[0xFFF8]; 41 | io32_t PC; 42 | } sp_regs_t; 43 | 44 | #define SP_BASE (0x04040000UL) 45 | #define SP ((sp_regs_t *) SP_BASE) 46 | 47 | #define SP_SR_HALT (1 << 0) 48 | #define SP_SR_BROKE (1 << 1) 49 | #define SP_SR_DMA_BUSY (1 << 2) 50 | #define SP_SR_DMA_FULL (1 << 3) 51 | #define SP_SR_IO_FULL (1 << 4) 52 | #define SP_SR_SSTEP (1 << 5) 53 | #define SP_SR_INTR_BREAK (1 << 6) 54 | #define SP_SR_SIG0 (1 << 7) 55 | #define SP_SR_SIG1 (1 << 8) 56 | #define SP_SR_SIG2 (1 << 9) 57 | #define SP_SR_SIG3 (1 << 10) 58 | #define SP_SR_SIG4 (1 << 11) 59 | #define SP_SR_SIG5 (1 << 12) 60 | #define SP_SR_SIG6 (1 << 13) 61 | #define SP_SR_SIG7 (1 << 14) 62 | #define SP_SR_CLR_HALT (1 << 0) 63 | #define SP_SR_SET_HALT (1 << 1) 64 | #define SP_SR_CLR_BROKE (1 << 2) 65 | #define SP_SR_CLR_INTR (1 << 3) 66 | #define SP_SR_SET_INTR (1 << 4) 67 | #define SP_SR_CLR_SSTEP (1 << 5) 68 | #define SP_SR_SET_SSTEP (1 << 6) 69 | #define SP_SR_CLR_INTR_BREAK (1 << 7) 70 | #define SP_SR_SET_INTR_BREAK (1 << 8) 71 | #define SP_SR_CLR_SIG0 (1 << 9) 72 | #define SP_SR_SET_SIG0 (1 << 10) 73 | #define SP_SR_CLR_SIG1 (1 << 11) 74 | #define SP_SR_SET_SIG1 (1 << 12) 75 | #define SP_SR_CLR_SIG2 (1 << 13) 76 | #define SP_SR_SET_SIG2 (1 << 14) 77 | #define SP_SR_CLR_SIG3 (1 << 15) 78 | #define SP_SR_SET_SIG3 (1 << 16) 79 | #define SP_SR_CLR_SIG4 (1 << 17) 80 | #define SP_SR_SET_SIG4 (1 << 18) 81 | #define SP_SR_CLR_SIG5 (1 << 19) 82 | #define SP_SR_SET_SIG5 (1 << 20) 83 | #define SP_SR_CLR_SIG6 (1 << 21) 84 | #define SP_SR_SET_SIG6 (1 << 22) 85 | #define SP_SR_CLR_SIG7 (1 << 23) 86 | #define SP_SR_SET_SIG7 (1 << 24) 87 | 88 | 89 | /** @brief DPC Registers Structure. */ 90 | typedef struct { 91 | io32_t START; 92 | io32_t END; 93 | io32_t CURRENT; 94 | io32_t SR; 95 | io32_t CLOCK; 96 | io32_t BUF_BUSY; 97 | io32_t PIPE_BUSY; 98 | io32_t TMEM; 99 | } dpc_regs_t; 100 | 101 | #define DPC_BASE (0x04100000UL) 102 | #define DPC ((dpc_regs_t *) DPC_BASE) 103 | 104 | #define DPC_SR_XBUS_DMEM_DMA (1 << 0) 105 | #define DPC_SR_FREEZE (1 << 1) 106 | #define DPC_SR_FLUSH (1 << 2) 107 | #define DPC_SR_START_GCLK (1 << 3) 108 | #define DPC_SR_TMEM_BUSY (1 << 4) 109 | #define DPC_SR_PIPE_BUSY (1 << 5) 110 | #define DPC_SR_CMD_BUSY (1 << 6) 111 | #define DPC_SR_CBUF_READY (1 << 7) 112 | #define DPC_SR_DMA_BUSY (1 << 8) 113 | #define DPC_SR_END_VALID (1 << 9) 114 | #define DPC_SR_START_VALID (1 << 10) 115 | #define DPC_SR_CLR_XBUS_DMEM_DMA (1 << 0) 116 | #define DPC_SR_SET_XBUS_DMEM_DMA (1 << 1) 117 | #define DPC_SR_CLR_FREEZE (1 << 2) 118 | #define DPC_SR_SET_FREEZE (1 << 3) 119 | #define DPC_SR_CLR_FLUSH (1 << 4) 120 | #define DPC_SR_SET_FLUSH (1 << 5) 121 | #define DPC_SR_CLR_TMEM_CTR (1 << 6) 122 | #define DPC_SR_CLR_PIPE_CTR (1 << 7) 123 | #define DPC_SR_CLR_CMD_CTR (1 << 8) 124 | #define DPC_SR_CLR_CLOCK_CTR (1 << 9) 125 | 126 | 127 | /** @brief Video Interface Registers Structure. */ 128 | typedef struct { 129 | /** @brief The Control Register. */ 130 | io32_t CR; 131 | /** @brief The Memory Address. */ 132 | io32_t MADDR; 133 | /** @brief The Horizontal Width. */ 134 | io32_t H_WIDTH; 135 | /** @brief The Virtical Interupt. */ 136 | io32_t V_INTR; 137 | /** @brief The Current Line. */ 138 | io32_t CURR_LINE; 139 | /** @brief The Timings. */ 140 | io32_t TIMING; 141 | /** @brief The Virtical Sync. */ 142 | io32_t V_SYNC; 143 | /** @brief The Horizontal Sync. */ 144 | io32_t H_SYNC; 145 | /** @brief The Horizontal Sync Leap. */ 146 | io32_t H_SYNC_LEAP; 147 | /** @brief The Horizontal Limits. */ 148 | io32_t H_LIMITS; 149 | /** @brief The Virtical Limits. */ 150 | io32_t V_LIMITS; 151 | /** @brief The Colour Burst. */ 152 | io32_t COLOR_BURST; 153 | /** @brief The Horizontal Scale. */ 154 | io32_t H_SCALE; 155 | /** @brief The Virtical Scale. */ 156 | io32_t V_SCALE; 157 | } vi_regs_t; 158 | 159 | #define VI_BASE (0x04400000UL) 160 | #define VI ((vi_regs_t *) VI_BASE) 161 | 162 | #define VI_CR_TYPE_16 (2 << 0) 163 | #define VI_CR_TYPE_32 (3 << 0) 164 | #define VI_CR_GAMMA_DITHER_ON (1 << 2) 165 | #define VI_CR_GAMMA_ON (1 << 3) 166 | #define VI_CR_DIVOT_ON (1 << 4) 167 | #define VI_CR_SERRATE_ON (1 << 6) 168 | #define VI_CR_ANTIALIAS_0 (1 << 8) 169 | #define VI_CR_ANTIALIAS_1 (1 << 9) 170 | #define VI_CR_PIXEL_ADVANCE_0 (1 << 12) 171 | #define VI_CR_PIXEL_ADVANCE_1 (1 << 13) 172 | #define VI_CR_PIXEL_ADVANCE_2 (1 << 14) 173 | #define VI_CR_PIXEL_ADVANCE_3 (1 << 15) 174 | #define VI_CR_DITHER_FILTER_ON (1 << 16) 175 | 176 | #define VI_CURR_LINE_FIELD (1 << 0) 177 | 178 | /** @brief Audio Interface Registers Structure. */ 179 | typedef struct { 180 | /** @brief The Memory Address. */ 181 | io32_t MADDR; 182 | /** @brief The Length of bytes. */ 183 | io32_t LEN; 184 | /** @brief The Control Register. */ 185 | io32_t CR; 186 | /** @brief The Status Register. */ 187 | io32_t SR; 188 | /** @brief The DAC rate. */ 189 | io32_t DACRATE; 190 | /** @brief The bit rate. */ 191 | io32_t BITRATE; 192 | } ai_regs_t; 193 | 194 | #define AI_BASE (0x04500000UL) 195 | #define AI ((ai_regs_t *) AI_BASE) 196 | 197 | #define AI_SR_DMA_BUSY (1 << 30) 198 | #define AI_SR_FIFO_FULL (1 << 31) 199 | #define AI_CR_DMA_ON (1 << 0) 200 | 201 | 202 | /** @brief Peripheral Interface Register Structure. */ 203 | typedef struct { 204 | /** @brief The Memory Address. */ 205 | io32_t MADDR; 206 | /** @brief The Cart Address. */ 207 | io32_t PADDR; 208 | /** @brief The Read Length. */ 209 | io32_t RDMA; 210 | /** @brief The Write Length. */ 211 | io32_t WDMA; 212 | /** @brief The Status Register. */ 213 | io32_t SR; 214 | /** @brief The Domain 2 Registers. */ 215 | struct { 216 | /** @brief The Latch Value. */ 217 | io32_t LAT; 218 | /** @brief The Pulse Width Value. */ 219 | io32_t PWD; 220 | /** @brief The Page Size Value. */ 221 | io32_t PGS; 222 | /** @brief The Release Value. */ 223 | io32_t RLS; 224 | } DOM[2]; 225 | } pi_regs_t; 226 | 227 | #define PI_BASE (0x04600000UL) 228 | #define PI ((pi_regs_t *) PI_BASE) 229 | 230 | #define PI_SR_DMA_BUSY (1 << 0) 231 | #define PI_SR_IO_BUSY (1 << 1) 232 | #define PI_SR_DMA_ERROR (1 << 2) 233 | #define PI_SR_RESET (1 << 0) 234 | #define PI_SR_CLR_INTR (1 << 1) 235 | 236 | 237 | #define ROM_DDIPL_BASE (0x06000000UL) 238 | #define ROM_DDIPL ((io32_t *) ROM_DDIPL_BASE) 239 | 240 | 241 | #define ROM_CART_BASE (0x10000000UL) 242 | #define ROM_CART ((io32_t *) ROM_CART_BASE) 243 | 244 | 245 | static inline uint32_t cpu_io_read (io32_t *address) { 246 | io32_t *uncached = UNCACHED(address); 247 | uint32_t value = *uncached; 248 | return value; 249 | } 250 | 251 | static inline void cpu_io_write (io32_t *address, uint32_t value) { 252 | io32_t *uncached = UNCACHED(address); 253 | *uncached = value; 254 | } 255 | 256 | 257 | #endif 258 | -------------------------------------------------------------------------------- /src/boot/cic.c: -------------------------------------------------------------------------------- 1 | #include "cic.h" 2 | 3 | 4 | static inline uint32_t _get (uint8_t *p, int index) { 5 | int i = index * 4; 6 | return (p[i] << 24 | p[i + 1] << 16 | p[i + 2] << 8 | p[i + 3]); 7 | } 8 | 9 | static inline uint32_t _add (uint32_t a1, uint32_t a2) { 10 | return a1 + a2; 11 | } 12 | 13 | static inline uint32_t _sub (uint32_t a1, uint32_t a2) { 14 | return a1 - a2; 15 | } 16 | 17 | static inline uint32_t _mul (uint32_t a1, uint32_t a2) { 18 | return a1 * a2; 19 | } 20 | 21 | static inline uint32_t _rol (uint32_t a, uint32_t s) { 22 | return ((a) << (s)) | ((a) >> (-(s) & 31)); 23 | } 24 | 25 | static inline uint32_t _ror (uint32_t a, uint32_t s) { 26 | return ((a) >> (s)) | ((a) << (-(s) & 31)); 27 | } 28 | 29 | static uint32_t _sum (uint32_t a0, uint32_t a1, uint32_t a2) { 30 | uint64_t prod = ((uint64_t) (a0)) * (a1 == 0 ? a2 : a1); 31 | uint32_t hi = (prod >> 32) & 0xFFFFFFFF; 32 | uint32_t lo = prod & 0xFFFFFFFF; 33 | uint32_t diff = hi - lo; 34 | return (diff == 0) ? a0 : diff; 35 | }; 36 | 37 | static uint64_t cic_calculate_ipl3_checksum (uint8_t *ipl3, uint8_t seed) { 38 | const uint32_t MAGIC = 0x6C078965; 39 | 40 | uint32_t data, prev, next; 41 | data = prev = next = _get(ipl3, 0); 42 | 43 | uint32_t init = _add(_mul(MAGIC, seed), 1) ^ data; 44 | 45 | uint32_t buf[16]; 46 | for (int i = 0; i < 16; i++) { 47 | buf[i] = init; 48 | } 49 | 50 | for (int i = 1; i <= 1008; i++) { 51 | prev = data; 52 | data = next; 53 | 54 | buf[0] = _add(buf[0], _sum(_sub(1007, i), data, i)); 55 | buf[1] = _sum(buf[1], data, i); 56 | buf[2] = buf[2] ^ data; 57 | buf[3] = _add(buf[3], _sum(_add(data, 5), MAGIC, i)); 58 | buf[4] = _add(buf[4], _ror(data, prev & 0x1F)); 59 | buf[5] = _add(buf[5], _rol(data, prev >> 27)); 60 | buf[6] = (data < buf[6]) ? (_add(buf[3], buf[6]) ^ _add(data, i)) : (_add(buf[4], data) ^ buf[6]); 61 | buf[7] = _sum(buf[7], _rol(data, prev & 0x1F), i); 62 | buf[8] = _sum(buf[8], _ror(data, prev >> 27), i); 63 | buf[9] = (prev < data) ? _sum(buf[9], data, i) : _add(buf[9], data); 64 | 65 | if (i == 1008) { 66 | break; 67 | } 68 | 69 | next = _get(ipl3, i); 70 | 71 | buf[10] = _sum(_add(buf[10], data), next, i); 72 | buf[11] = _sum(buf[11] ^ data, next, i); 73 | buf[12] = _add(buf[12], buf[8] ^ data); 74 | buf[13] = _add(buf[13], _add(_ror(data, data & 0x1F), _ror(next, next & 0x1F))); 75 | buf[14] = _sum(_sum(buf[14], _ror(data, prev & 0x1F), i), _ror(next, data & 0x1F), i); 76 | buf[15] = _sum(_sum(buf[15], _rol(data, prev >> 27), i), _rol(next, data >> 27), i); 77 | } 78 | 79 | uint32_t final_buf[4]; 80 | for (int i = 0; i < 4; i++) { 81 | final_buf[i] = buf[0]; 82 | } 83 | 84 | for (int i = 0; i < 16; i++) { 85 | uint32_t data = buf[i]; 86 | final_buf[0] = _add(final_buf[0], _ror(data, data & 0x1F)); 87 | final_buf[1] = (data < final_buf[0]) ? _add(final_buf[1], data) : _sum(final_buf[1], data, i); 88 | final_buf[2] = (((data & 0x02) >> 1) == (data & 0x01)) ? _add(final_buf[2], data) : _sum(final_buf[2], data, i); 89 | final_buf[3] = ((data & 0x01) == 0x01) ? (final_buf[3] ^ data) : _sum(final_buf[3], data, i); 90 | } 91 | 92 | uint32_t final_sum = _sum(final_buf[0], final_buf[1], 16); 93 | uint32_t final_xor = final_buf[3] ^ final_buf[2]; 94 | 95 | uint64_t checksum = (((((uint64_t) (final_sum)) & 0xFFFF)) << 32) | (final_xor); 96 | 97 | return checksum; 98 | } 99 | 100 | 101 | cic_type_t cic_detect (uint8_t *ipl3) { 102 | switch (cic_calculate_ipl3_checksum(ipl3, 0x3F)) { 103 | case 0x45CC73EE317AULL: return CIC_6101; // 6101 104 | case 0x44160EC5D9AFULL: return CIC_7102; // 7102 105 | case 0xA536C0F1D859ULL: return CIC_x102; // 6102 / 7101 106 | } 107 | switch (cic_calculate_ipl3_checksum(ipl3, 0x78)) { 108 | case 0x586FD4709867ULL: return CIC_x103; // 6103 / 7103 109 | } 110 | switch (cic_calculate_ipl3_checksum(ipl3, 0x85)) { 111 | case 0x2BBAD4E6EB74ULL: return CIC_x106; // 6106 / 7106 112 | } 113 | switch (cic_calculate_ipl3_checksum(ipl3, 0x91)) { 114 | case 0x8618A45BC2D3ULL: return CIC_x105; // 6105 / 7105 115 | } 116 | switch (cic_calculate_ipl3_checksum(ipl3, 0xDD)) { 117 | case 0x6EE8D9E84970ULL: return CIC_8401; // NDXJ0 118 | case 0x6C216495C8B9ULL: return CIC_8301; // NDDJ0 119 | case 0xE27F43BA93ACULL: return CIC_8302; // NDDJ1 120 | case 0x32B294E2AB90ULL: return CIC_8303; // NDDJ2 121 | case 0x083C6C77E0B1ULL: return CIC_5167; // 64DD Cartridge conversion 122 | } 123 | switch (cic_calculate_ipl3_checksum(ipl3, 0xDE)) { 124 | case 0x05BA2EF0A5F1ULL: return CIC_8501; // NDDE0 125 | } 126 | 127 | return CIC_UNKNOWN; 128 | } 129 | 130 | uint8_t cic_get_seed (cic_type_t cic_type) { 131 | switch (cic_type) { 132 | case CIC_5101: return 0xAC; 133 | case CIC_5167: return 0xDD; 134 | case CIC_6101: return 0x3F; 135 | case CIC_7102: return 0x3F; 136 | case CIC_x102: return 0x3F; 137 | case CIC_x103: return 0x78; 138 | case CIC_x105: return 0x91; 139 | case CIC_x106: return 0x85; 140 | case CIC_8301: return 0xDD; 141 | case CIC_8302: return 0xDD; 142 | case CIC_8303: return 0xDD; 143 | case CIC_8401: return 0xDD; 144 | case CIC_8501: return 0xDE; 145 | default: return 0x3F; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/boot/cic.h: -------------------------------------------------------------------------------- 1 | #ifndef CIC_H__ 2 | #define CIC_H__ 3 | 4 | 5 | #include 6 | 7 | 8 | #define IPL3_LENGTH (4032) 9 | 10 | 11 | typedef enum { 12 | CIC_5101, 13 | CIC_5167, 14 | CIC_6101, 15 | CIC_7102, 16 | CIC_x102, 17 | CIC_x103, 18 | CIC_x105, 19 | CIC_x106, 20 | CIC_8301, 21 | CIC_8302, 22 | CIC_8303, 23 | CIC_8401, 24 | CIC_8501, 25 | CIC_UNKNOWN, 26 | } cic_type_t; 27 | 28 | 29 | cic_type_t cic_detect (uint8_t *ipl3); 30 | uint8_t cic_get_seed (cic_type_t cic_type); 31 | 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/boot/reboot.S: -------------------------------------------------------------------------------- 1 | #define IPL3_ENTRY 0xA4000040 2 | #define REBOOT_ADDRESS 0xA4001000 3 | #define STACK_ADDRESS 0xA4001FF0 4 | 5 | #define RI_ADDRESS 0xA4700000 6 | 7 | #define RI_SELECT 0x0C 8 | #define RI_REFRESH 0x10 9 | 10 | 11 | .set noat 12 | .section .text.reboot, "ax", %progbits 13 | 14 | reboot_start: 15 | .global reboot_start 16 | 17 | # NOTE: CIC x105 requirement 18 | ipl2: 19 | .set noreorder 20 | lui $t5, 0xBFC0 21 | 1: 22 | lw $t0, 0x7FC($t5) 23 | addiu $t5, $t5, 0x7C0 24 | andi $t0, $t0, 0x80 25 | bnel $t0, $zero, 1b 26 | lui $t5, 0xBFC0 27 | lw $t0, 0x24($t5) 28 | lui $t3, 0xB000 29 | .set reorder 30 | 31 | reboot_entry: 32 | .set reboot, REBOOT_ADDRESS + (. - reboot_start) 33 | .global reboot 34 | 35 | li $sp, STACK_ADDRESS 36 | 37 | reset_rdram: 38 | bnez $s5, reset_rdram_skip 39 | 40 | li $t0, RI_ADDRESS 41 | 42 | sw $zero, RI_REFRESH($t0) 43 | sw $zero, RI_SELECT($t0) 44 | reset_rdram_skip: 45 | 46 | detect_console_region: 47 | li $t0, 1 48 | beq $s4, $zero, pal_console 49 | beq $s4, $t0, ntsc_console 50 | b mpal_console 51 | 52 | pal_console: 53 | li $ra, 0xA4001554 54 | b prepare_registers 55 | 56 | ntsc_console: 57 | li $ra, 0xA4001550 58 | b prepare_registers 59 | 60 | mpal_console: 61 | li $ra, 0xA4001554 62 | 63 | prepare_registers: 64 | move $at, $zero 65 | move $v0, $zero 66 | move $v1, $zero 67 | move $a0, $zero 68 | move $a1, $zero 69 | move $a2, $zero 70 | move $a3, $zero 71 | move $t0, $zero 72 | move $t1, $zero 73 | li $t2, 0x40 74 | move $t4, $zero 75 | move $t5, $zero 76 | move $t6, $zero 77 | move $t7, $zero 78 | move $s0, $zero 79 | move $s1, $zero 80 | move $s2, $zero 81 | move $t8, $zero 82 | move $t9, $zero 83 | move $k0, $zero 84 | move $k1, $zero 85 | move $gp, $zero 86 | move $fp, $zero 87 | 88 | run_ipl3: 89 | li $t3, IPL3_ENTRY 90 | jr $t3 91 | 92 | .set reboot_size, (. - reboot_start) 93 | .global reboot_size 94 | -------------------------------------------------------------------------------- /src/flashcart/64drive/64drive.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "../../utils/fs.h" 9 | #include "../../utils/utils.h" 10 | 11 | #include "../flashcart_utils.h" 12 | #include "64drive_ll.h" 13 | #include "64drive.h" 14 | 15 | 16 | #define ROM_ADDRESS (0x10000000) 17 | #define SAVE_ADDRESS_DEV_A (0x13FE0000) 18 | #define SAVE_ADDRESS_DEV_A_PKST2 (0x11606560) 19 | #define SAVE_ADDRESS_DEV_B (0x1FFC0000) 20 | 21 | #define SUPPORTED_FPGA_REVISION (205) 22 | 23 | 24 | static d64_device_variant_t device_variant = DEVICE_VARIANT_UNKNOWN; 25 | static d64_save_type_t current_save_type = SAVE_TYPE_NONE; 26 | 27 | 28 | static flashcart_err_t d64_init (void) { 29 | uint16_t fpga_revision; 30 | uint32_t bootloader_version; 31 | 32 | if (d64_ll_enable_extended_mode(false)) { 33 | return FLASHCART_ERR_INT; 34 | } 35 | 36 | if (d64_ll_get_version(&device_variant, &fpga_revision, &bootloader_version)) { 37 | return FLASHCART_ERR_INT; 38 | } 39 | 40 | if (fpga_revision < SUPPORTED_FPGA_REVISION) { 41 | return FLASHCART_ERR_OUTDATED; 42 | } 43 | 44 | if (d64_ll_enable_save_writeback(false)) { 45 | return FLASHCART_ERR_INT; 46 | } 47 | 48 | if (d64_ll_set_save_type(SAVE_TYPE_NONE)) { 49 | return FLASHCART_ERR_INT; 50 | } 51 | 52 | current_save_type = SAVE_TYPE_NONE; 53 | 54 | if (d64_ll_enable_cartrom_writes(true)) { 55 | return FLASHCART_ERR_INT; 56 | } 57 | 58 | if (d64_ll_set_persistent_variable_storage(false, 0, 0)) { 59 | return FLASHCART_ERR_INT; 60 | } 61 | 62 | return FLASHCART_OK; 63 | } 64 | 65 | static flashcart_err_t d64_deinit (void) { 66 | if (d64_ll_enable_cartrom_writes(false)) { 67 | return FLASHCART_ERR_INT; 68 | } 69 | 70 | return FLASHCART_OK; 71 | } 72 | 73 | static bool d64_has_feature (flashcart_features_t feature) { 74 | switch (feature) { 75 | case FLASHCART_FEATURE_64DD: return false; 76 | case FLASHCART_FEATURE_RTC: return true; 77 | case FLASHCART_FEATURE_USB: return true; 78 | default: return false; 79 | } 80 | } 81 | 82 | static flashcart_err_t d64_load_rom (char *rom_path, flashcart_progress_callback_t *progress) { 83 | FIL fil; 84 | UINT br; 85 | 86 | if (f_open(&fil, strip_sd_prefix(rom_path), FA_READ) != FR_OK) { 87 | return FLASHCART_ERR_LOAD; 88 | } 89 | 90 | fix_file_size(&fil); 91 | 92 | size_t rom_size = f_size(&fil); 93 | 94 | if (rom_size > MiB(64)) { 95 | f_close(&fil); 96 | return FLASHCART_ERR_LOAD; 97 | } 98 | 99 | size_t sdram_size = MiB(64); 100 | 101 | size_t chunk_size = KiB(128); 102 | for (int offset = 0; offset < sdram_size; offset += chunk_size) { 103 | size_t block_size = MIN(sdram_size - offset, chunk_size); 104 | if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) { 105 | f_close(&fil); 106 | return FLASHCART_ERR_LOAD; 107 | } 108 | if (progress) { 109 | progress(f_tell(&fil) / (float) (f_size(&fil))); 110 | } 111 | } 112 | if (f_tell(&fil) != rom_size) { 113 | f_close(&fil); 114 | return FLASHCART_ERR_LOAD; 115 | } 116 | 117 | if (f_close(&fil) != FR_OK) { 118 | return FLASHCART_ERR_LOAD; 119 | } 120 | 121 | return FLASHCART_OK; 122 | } 123 | 124 | static flashcart_err_t d64_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) { 125 | FIL fil; 126 | UINT br; 127 | 128 | if (f_open(&fil, strip_sd_prefix(file_path), FA_READ) != FR_OK) { 129 | return FLASHCART_ERR_LOAD; 130 | } 131 | 132 | fix_file_size(&fil); 133 | 134 | size_t file_size = f_size(&fil) - file_offset; 135 | 136 | if (file_size > (MiB(64) - rom_offset)) { 137 | f_close(&fil); 138 | return FLASHCART_ERR_ARGS; 139 | } 140 | 141 | if (f_lseek(&fil, file_offset) != FR_OK) { 142 | f_close(&fil); 143 | return FLASHCART_ERR_LOAD; 144 | } 145 | 146 | if (f_read(&fil, (void *) (ROM_ADDRESS + rom_offset), file_size, &br) != FR_OK) { 147 | f_close(&fil); 148 | return FLASHCART_ERR_LOAD; 149 | } 150 | if (br != file_size) { 151 | f_close(&fil); 152 | return FLASHCART_ERR_LOAD; 153 | } 154 | 155 | if (f_close(&fil) != FR_OK) { 156 | return FLASHCART_ERR_LOAD; 157 | } 158 | 159 | return FLASHCART_OK; 160 | } 161 | 162 | static flashcart_err_t d64_load_save (char *save_path) { 163 | uint8_t eeprom_contents[2048] __attribute__((aligned(8))); 164 | FIL fil; 165 | UINT br; 166 | 167 | if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) { 168 | return FLASHCART_ERR_LOAD; 169 | } 170 | 171 | size_t save_size = f_size(&fil); 172 | 173 | bool is_eeprom_save = (current_save_type == SAVE_TYPE_EEPROM_4K || current_save_type == SAVE_TYPE_EEPROM_16K); 174 | 175 | void *address = (void *) (SAVE_ADDRESS_DEV_B); 176 | if (is_eeprom_save) { 177 | address = eeprom_contents; 178 | } else if (device_variant == DEVICE_VARIANT_A) { 179 | address = (void *) (SAVE_ADDRESS_DEV_A); 180 | if (current_save_type == SAVE_TYPE_FLASHRAM_PKST2) { 181 | address = (void *) (SAVE_ADDRESS_DEV_A_PKST2); 182 | } 183 | } 184 | 185 | if (f_read(&fil, address, save_size, &br) != FR_OK) { 186 | f_close(&fil); 187 | return FLASHCART_ERR_LOAD; 188 | } 189 | 190 | if (is_eeprom_save) { 191 | if (d64_ll_write_eeprom_contents(eeprom_contents)) { 192 | f_close(&fil); 193 | return FLASHCART_ERR_LOAD; 194 | } 195 | } 196 | 197 | if (f_close(&fil) != FR_OK) { 198 | return FLASHCART_ERR_LOAD; 199 | } 200 | 201 | if (br != save_size) { 202 | return FLASHCART_ERR_LOAD; 203 | } 204 | 205 | return FLASHCART_OK; 206 | } 207 | 208 | static flashcart_err_t d64_set_save_type (flashcart_save_type_t save_type) { 209 | d64_save_type_t type; 210 | 211 | switch (save_type) { 212 | case FLASHCART_SAVE_TYPE_NONE: 213 | type = SAVE_TYPE_NONE; 214 | break; 215 | case FLASHCART_SAVE_TYPE_EEPROM_4K: 216 | type = SAVE_TYPE_EEPROM_4K; 217 | break; 218 | case FLASHCART_SAVE_TYPE_EEPROM_16K: 219 | type = SAVE_TYPE_EEPROM_16K; 220 | break; 221 | case FLASHCART_SAVE_TYPE_SRAM: 222 | type = SAVE_TYPE_SRAM; 223 | break; 224 | case FLASHCART_SAVE_TYPE_SRAM_BANKED: 225 | type = SAVE_TYPE_SRAM_BANKED; 226 | break; 227 | case FLASHCART_SAVE_TYPE_SRAM_128K: 228 | // NOTE: 64drive doesn't support 128 kiB SRAM save type, fallback to 32 kiB SRAM save type 229 | type = SAVE_TYPE_SRAM; 230 | break; 231 | case FLASHCART_SAVE_TYPE_FLASHRAM: 232 | type = SAVE_TYPE_FLASHRAM; 233 | break; 234 | case FLASHCART_SAVE_TYPE_FLASHRAM_PKST2: 235 | type = (device_variant == DEVICE_VARIANT_A) ? SAVE_TYPE_FLASHRAM_PKST2 : SAVE_TYPE_FLASHRAM; 236 | break; 237 | default: 238 | return FLASHCART_ERR_ARGS; 239 | } 240 | 241 | if (d64_ll_enable_save_writeback(false)) { 242 | return FLASHCART_ERR_INT; 243 | } 244 | 245 | if (d64_ll_set_save_type(type)) { 246 | return FLASHCART_ERR_INT; 247 | } 248 | 249 | current_save_type = type; 250 | 251 | return FLASHCART_OK; 252 | } 253 | 254 | static flashcart_err_t d64_set_save_writeback (uint32_t *sectors) { 255 | if (d64_ll_write_save_writeback_lba_list(sectors)) { 256 | return FLASHCART_ERR_INT; 257 | } 258 | 259 | if (d64_ll_enable_save_writeback(true)) { 260 | return FLASHCART_ERR_INT; 261 | } 262 | 263 | return FLASHCART_OK; 264 | } 265 | 266 | 267 | static flashcart_t flashcart_d64 = { 268 | .init = d64_init, 269 | .deinit = d64_deinit, 270 | .has_feature = d64_has_feature, 271 | .load_rom = d64_load_rom, 272 | .load_file = d64_load_file, 273 | .load_save = d64_load_save, 274 | .load_64dd_ipl = NULL, 275 | .load_64dd_disk = NULL, 276 | .set_save_type = d64_set_save_type, 277 | .set_save_writeback = d64_set_save_writeback, 278 | }; 279 | 280 | 281 | flashcart_t *d64_get_flashcart (void) { 282 | return &flashcart_d64; 283 | } 284 | -------------------------------------------------------------------------------- /src/flashcart/64drive/64drive.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 64drive.h 3 | * @brief 64drive flashcart support 4 | * @ingroup flashcart 5 | */ 6 | 7 | #ifndef FLASHCART_64DRIVE_H__ 8 | #define FLASHCART_64DRIVE_H__ 9 | 10 | 11 | #include "../flashcart.h" 12 | 13 | 14 | /** 15 | * @addtogroup 64drive 16 | * @{ 17 | */ 18 | 19 | flashcart_t *d64_get_flashcart (void); 20 | 21 | /** @} */ /* 64drive */ 22 | 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/flashcart/64drive/64drive_ll.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../flashcart_utils.h" 4 | #include "64drive_ll.h" 5 | 6 | 7 | #define CI_STATUS_BUSY (1 << 12) 8 | 9 | #define D64_DEVICE_VARIANT_MASK (0xFFFF) 10 | #define D64_FPGA_REVISION_MASK (0xFFFF) 11 | 12 | 13 | typedef enum { 14 | CMD_ID_SET_SAVE_TYPE = 0xD0, 15 | CMD_ID_DISABLE_SAVE_WRITEBACK = 0xD1, 16 | CMD_ID_ENABLE_SAVE_WRITEBACK = 0xD2, 17 | CMD_ID_DISABLE_BYTESWAP_ON_LOAD = 0xE0, 18 | CMD_ID_ENABLE_BYTESWAP_ON_LOAD = 0xE1, 19 | CMD_ID_ENABLE_CARTROM_WRITES = 0xF0, 20 | CMD_ID_DISABLE_CARTROM_WRITES = 0xF1, 21 | CMD_ID_ENABLE_EXTENDED_MODE = 0xF8, 22 | CMD_ID_DISABLE_EXTENDED_MODE = 0xF9, 23 | } d64_ci_cmd_id_t; 24 | 25 | 26 | static d64_regs_t *d64_regs = D64_REGS; 27 | 28 | 29 | static bool d64_ll_ci_wait (void) { 30 | int timeout = 0; 31 | do { 32 | if (timeout++ >= 0x10000) { 33 | return true; 34 | } 35 | } while (io_read((uint32_t) (&d64_regs->STATUS)) & CI_STATUS_BUSY); 36 | return false; 37 | } 38 | 39 | static bool d64_ll_ci_cmd (d64_ci_cmd_id_t id) { 40 | io_write((uint32_t) (&d64_regs->COMMAND), id); 41 | return d64_ll_ci_wait(); 42 | } 43 | 44 | 45 | bool d64_ll_get_version (d64_device_variant_t *device_variant, uint16_t *fpga_revision, uint32_t *bootloader_version) { 46 | if (d64_ll_ci_wait()) { 47 | return true; 48 | } 49 | *device_variant = (d64_device_variant_t) (io_read((uint32_t) (&d64_regs->VARIANT)) & D64_DEVICE_VARIANT_MASK); 50 | *fpga_revision = (io_read((uint32_t) (&d64_regs->REVISION)) & D64_FPGA_REVISION_MASK); 51 | *bootloader_version = io_read((uint32_t) (&d64_regs->PERSISTENT)); 52 | return d64_ll_ci_wait(); 53 | } 54 | 55 | bool d64_ll_set_persistent_variable_storage (bool quick_reboot, d64_tv_type_t force_tv_type, uint8_t cic_seed) { 56 | if (d64_ll_ci_wait()) { 57 | return true; 58 | } 59 | io_write((uint32_t) (&d64_regs->PERSISTENT), (quick_reboot << 16) | ((force_tv_type & 0x03) << 8) | (cic_seed & 0xFF)); 60 | return d64_ll_ci_wait(); 61 | } 62 | 63 | bool d64_ll_set_save_type (d64_save_type_t save_type) { 64 | if (d64_ll_ci_wait()) { 65 | return true; 66 | } 67 | io_write((uint32_t) (&d64_regs->BUFFER), save_type); 68 | return d64_ll_ci_cmd(CMD_ID_SET_SAVE_TYPE); 69 | } 70 | 71 | bool d64_ll_enable_save_writeback (bool enabled) { 72 | if (d64_ll_ci_wait()) { 73 | return true; 74 | } 75 | return d64_ll_ci_cmd(enabled ? CMD_ID_ENABLE_SAVE_WRITEBACK : CMD_ID_DISABLE_SAVE_WRITEBACK); 76 | } 77 | 78 | bool d64_ll_enable_cartrom_writes (bool enabled) { 79 | if (d64_ll_ci_wait()) { 80 | return true; 81 | } 82 | return d64_ll_ci_cmd(enabled ? CMD_ID_ENABLE_CARTROM_WRITES : CMD_ID_DISABLE_CARTROM_WRITES); 83 | } 84 | 85 | bool d64_ll_enable_extended_mode (bool enabled) { 86 | d64_ll_ci_wait(); 87 | if (enabled) { 88 | io_write((uint32_t) (&D64_REGS->COMMAND), CMD_ID_ENABLE_EXTENDED_MODE); 89 | } else { 90 | io_write((uint32_t) (&D64_REGS_EXT->COMMAND), CMD_ID_DISABLE_EXTENDED_MODE); 91 | } 92 | d64_regs = enabled ? D64_REGS_EXT : D64_REGS; 93 | return d64_ll_ci_wait(); 94 | } 95 | 96 | bool d64_ll_write_eeprom_contents (void *contents) { 97 | if (d64_ll_ci_wait()) { 98 | return true; 99 | } 100 | pi_dma_write_data(contents, d64_regs->EEPROM, 2048); 101 | return d64_ll_ci_wait(); 102 | } 103 | 104 | bool d64_ll_write_save_writeback_lba_list (void *list) { 105 | if (d64_ll_ci_wait()) { 106 | return true; 107 | } 108 | pi_dma_write_data(list, d64_regs->WRITEBACK, 1024); 109 | return d64_ll_ci_wait(); 110 | } 111 | -------------------------------------------------------------------------------- /src/flashcart/64drive/64drive_ll.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 64drive_ll.h 3 | * @brief 64drive flashcart low level access 4 | * @ingroup flashcart 5 | */ 6 | 7 | #ifndef FLASHCART_64DRIVE_LL_H__ 8 | #define FLASHCART_64DRIVE_LL_H__ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | /** 16 | * @addtogroup 64drive 17 | * @{ 18 | */ 19 | 20 | /** @brief Registers Structure. */ 21 | typedef struct { 22 | uint8_t BUFFER[512]; 23 | uint32_t STATUS; 24 | uint32_t __unused_1; 25 | uint32_t COMMAND; 26 | uint32_t __unused_2; 27 | uint32_t LBA; 28 | uint32_t __unused_3; 29 | uint32_t LENGTH; 30 | uint32_t __unused_4; 31 | uint32_t RESULT; 32 | 33 | uint32_t __unused_5[49]; 34 | 35 | uint32_t SDRAM_SIZE; 36 | uint32_t MAGIC; 37 | uint32_t VARIANT; 38 | uint32_t PERSISTENT; 39 | uint32_t BUTTON_UPGRADE; 40 | uint32_t REVISION; 41 | 42 | uint32_t __unused_6[64]; 43 | 44 | uint32_t USB_COMMAND_STATUS; 45 | uint32_t USB_PARAM_RESULT[2]; 46 | 47 | uint32_t __unused_7[5]; 48 | 49 | uint32_t WIFI_COMMAND_STATUS; 50 | uint32_t WIFI_PARAM_RESULT[2]; 51 | 52 | uint32_t __unused_8[757]; 53 | 54 | uint8_t EEPROM[2048]; 55 | uint32_t WRITEBACK[256]; 56 | } d64_regs_t; 57 | 58 | /** @brief Registers Base Address. */ 59 | #define D64_REGS_BASE (0x18000000UL) 60 | #define D64_REGS_BASE_EXT (0x1F800000UL) 61 | #define D64_REGS ((d64_regs_t *) D64_REGS_BASE) 62 | #define D64_REGS_EXT ((d64_regs_t *) D64_REGS_BASE_EXT) 63 | 64 | /** @brief Device Variant Enumeration. */ 65 | typedef enum { 66 | DEVICE_VARIANT_UNKNOWN = 0x0000, 67 | DEVICE_VARIANT_A = 0x4100, 68 | DEVICE_VARIANT_B = 0x4200, 69 | } d64_device_variant_t; 70 | 71 | /** @brief TV Type Enumeration. */ 72 | typedef enum { 73 | TV_TYPE_PAL = 0, 74 | TV_TYPE_NTSC = 1, 75 | TV_TYPE_MPAL = 2, 76 | TV_TYPE_UNKNOWN = 3, 77 | } d64_tv_type_t; 78 | 79 | /** @brief Save Type Enumeration. */ 80 | typedef enum { 81 | SAVE_TYPE_NONE, 82 | SAVE_TYPE_EEPROM_4K, 83 | SAVE_TYPE_EEPROM_16K, 84 | SAVE_TYPE_SRAM, 85 | SAVE_TYPE_FLASHRAM, 86 | SAVE_TYPE_SRAM_BANKED, 87 | SAVE_TYPE_FLASHRAM_PKST2, 88 | } d64_save_type_t; 89 | 90 | 91 | bool d64_ll_get_version (d64_device_variant_t *device_variant, uint16_t *fpga_revision, uint32_t *bootloader_version); 92 | bool d64_ll_set_persistent_variable_storage (bool quick_reboot, d64_tv_type_t force_tv_type, uint8_t cic_seed); 93 | bool d64_ll_set_save_type (d64_save_type_t save_type); 94 | bool d64_ll_enable_save_writeback (bool enabled); 95 | bool d64_ll_enable_cartrom_writes (bool enabled); 96 | bool d64_ll_enable_extended_mode (bool enabled); 97 | bool d64_ll_write_eeprom_contents (void *contents); 98 | bool d64_ll_write_save_writeback_lba_list (void *list); 99 | 100 | /** @} */ /* 64drive */ 101 | 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /src/flashcart/64drive/README.md: -------------------------------------------------------------------------------- 1 | ## 64drive developer notes 2 | 3 | ### Official documentation 4 | 5 | http://64drive.retroactive.be/64drive_hardware_spec.pdf 6 | 7 | 8 | ### Save location offset in SDRAM 9 | 10 | | Type | HW1 | HW2 | 11 | | ---------------------------- | ------------ | ------------ | 12 | | SRAM | `0x03FE0000` | `0x0FFC0000` | 13 | | FlashRAM | `0x03FE0000` | `0x0FFC0000` | 14 | | SRAM banked | `0x03FE0000` | `0x0FFC0000` | 15 | | FlashRAM (Pokemon Stadium 2) | `0x01606560` | `0x0FFC0000` | 16 | 17 | EEPROM save types are stored in separate memory inside FPGA, rest of the save types are stored inside SDRAM memory. 18 | EEPROM save types need manual load as this memory space can't be written with "Read multiple sectors to SDRAM" command. 19 | 20 | 21 | ### "Persistent variable storage" register 22 | 23 | | Bits | Meaning | 24 | | --------- | ---------------------------------------------------------- | 25 | | `[31:17]` | _Unused_ | 26 | | `[16]` | Reset behavior: `0` - boot to menu / `1` - quick boot game | 27 | | `[15:10]` | _Unused_ | 28 | | `[9:8]` | Force TV type | 29 | | `[7:0]` | CIC seed | 30 | 31 | It's used by the bootloader to quickly load game without running menu again. 32 | Should contain bootloader version but it's zeroed if ROM wasn't ran through bootloader (e.g. ROM was loaded via USB). 33 | 34 | 35 | ### "Enable/disable save writeback" command 36 | 37 | Does not work when USB cable is connected - writeback is forced to be in disabled state. 38 | Curiously, official 64drive menu never calls this command, save writeback might be enabled by default. 39 | 40 | 41 | ### "Enable/disable byteswap on load" command 42 | 43 | Annoyingly, this command affects both loading single sector into the buffer and loading multiple sectors to the SDRAM. 44 | 45 | 46 | ### "Enable/disable extended address mode" command 47 | 48 | As of latest available firmware version 2.05 this command is not implemented. 49 | Documentation specifies it's supported on firmwares 2.06+ but this version (or anything newer) was never published. 50 | -------------------------------------------------------------------------------- /src/flashcart/flashcart.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../utils/fs.h" 8 | #include "../utils/utils.h" 9 | 10 | #include "flashcart.h" 11 | 12 | #include "64drive/64drive.h" 13 | #include "sc64/sc64.h" 14 | 15 | 16 | #define SAVE_WRITEBACK_MAX_SECTORS (256) 17 | 18 | 19 | static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = { 20 | 0, 21 | 512, 22 | KiB(2), 23 | KiB(32), 24 | KiB(96), 25 | KiB(128), 26 | KiB(128), 27 | KiB(128), 28 | }; 29 | 30 | static uint32_t save_writeback_sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8))); 31 | 32 | 33 | static void save_writeback_sectors_callback (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size) { 34 | for (uint32_t i = 0; i < cluster_size; i++) { 35 | uint32_t offset = file_sector + i; 36 | uint32_t sector = cluster_sector + i; 37 | 38 | if ((offset > SAVE_WRITEBACK_MAX_SECTORS) || (offset > sector_count)) { 39 | return; 40 | } 41 | 42 | save_writeback_sectors[offset] = sector; 43 | } 44 | } 45 | 46 | 47 | static flashcart_err_t dummy_init (void) { 48 | return FLASHCART_OK; 49 | } 50 | 51 | static flashcart_t *flashcart = &((flashcart_t) { 52 | .init = dummy_init, 53 | .deinit = NULL, 54 | .load_rom = NULL, 55 | .load_file = NULL, 56 | .load_save = NULL, 57 | .set_save_type = NULL, 58 | .set_save_writeback = NULL, 59 | }); 60 | 61 | #ifdef NDEBUG 62 | // HACK: libdragon mocks every debug function if NDEBUG flag is enabled. 63 | // Code below reverts that and point to real function instead. 64 | #undef debug_init_sdfs 65 | bool debug_init_sdfs (const char *prefix, int npart); 66 | #endif 67 | 68 | 69 | char *flashcart_convert_error_message (flashcart_err_t err) { 70 | switch (err) { 71 | case FLASHCART_OK: return "No error"; 72 | case FLASHCART_ERR_NOT_DETECTED: return "No flashcart hardware was detected"; 73 | case FLASHCART_ERR_OUTDATED: return "Outdated flashcart firmware"; 74 | case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization"; 75 | case FLASHCART_ERR_ARGS: return "Invalid argument passed to flashcart function"; 76 | case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart"; 77 | case FLASHCART_ERR_INT: return "Internal flashcart error"; 78 | case FLASHCART_ERR_FUNCTION_NOT_SUPPORTED: return "Flashcart doesn't support this function"; 79 | default: return "Unknown flashcart error"; 80 | } 81 | } 82 | 83 | flashcart_err_t flashcart_init (void) { 84 | flashcart_err_t err; 85 | 86 | bool sd_card_initialized = debug_init_sdfs("sd:/", -1); 87 | 88 | #ifndef NDEBUG 89 | // NOTE: Some flashcarts doesn't have USB port, can't throw error here 90 | debug_init_usblog(); 91 | #endif 92 | 93 | switch (cart_type) { 94 | case CART_CI: // 64drive 95 | flashcart = d64_get_flashcart(); 96 | break; 97 | 98 | case CART_EDX: // Series X EverDrive-64 99 | case CART_ED: // Original EverDrive-64 100 | break; 101 | 102 | case CART_SC: // SummerCart64 103 | flashcart = sc64_get_flashcart(); 104 | break; 105 | 106 | default: 107 | return FLASHCART_ERR_NOT_DETECTED; 108 | } 109 | 110 | if ((err = flashcart->init()) != FLASHCART_OK) { 111 | return err; 112 | } 113 | 114 | if (!sd_card_initialized) { 115 | return FLASHCART_ERR_SD_CARD; 116 | } 117 | 118 | return FLASHCART_OK; 119 | } 120 | 121 | flashcart_err_t flashcart_deinit (void) { 122 | if (flashcart->deinit) { 123 | return flashcart->deinit(); 124 | } 125 | 126 | return FLASHCART_OK; 127 | } 128 | 129 | bool flashcart_has_feature (flashcart_features_t feature) { 130 | return flashcart->has_feature(feature); 131 | } 132 | 133 | flashcart_err_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress) { 134 | flashcart_err_t err; 135 | 136 | if (rom_path == NULL) { 137 | return FLASHCART_ERR_ARGS; 138 | } 139 | 140 | cart_card_byteswap = byte_swap; 141 | err = flashcart->load_rom(rom_path, progress); 142 | cart_card_byteswap = false; 143 | 144 | return err; 145 | } 146 | 147 | flashcart_err_t flashcart_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) { 148 | if ((file_path == NULL) || ((file_offset % FS_SECTOR_SIZE) != 0)) { 149 | return FLASHCART_ERR_ARGS; 150 | } 151 | 152 | return flashcart->load_file(file_path, rom_offset, file_offset); 153 | } 154 | 155 | flashcart_err_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type) { 156 | flashcart_err_t err; 157 | 158 | if (save_type >= __FLASHCART_SAVE_TYPE_END) { 159 | return FLASHCART_ERR_ARGS; 160 | } 161 | 162 | if ((err = flashcart->set_save_type(save_type)) != FLASHCART_OK) { 163 | return err; 164 | } 165 | 166 | if ((save_path == NULL) || (save_type == FLASHCART_SAVE_TYPE_NONE)) { 167 | return FLASHCART_OK; 168 | } 169 | 170 | if (!file_exists(save_path)) { 171 | if (file_allocate(save_path, SAVE_SIZE[save_type])) { 172 | return FLASHCART_ERR_LOAD; 173 | } 174 | if (file_fill(save_path, 0xFF)) { 175 | return FLASHCART_ERR_LOAD; 176 | } 177 | } 178 | 179 | if (file_get_size(save_path) != SAVE_SIZE[save_type]) { 180 | return FLASHCART_ERR_LOAD; 181 | } 182 | 183 | if ((err = flashcart->load_save(save_path)) != FLASHCART_OK) { 184 | return err; 185 | } 186 | 187 | if (flashcart->set_save_writeback) { 188 | for (int i = 0; i < SAVE_WRITEBACK_MAX_SECTORS; i++) { 189 | save_writeback_sectors[i] = 0; 190 | } 191 | if (file_get_sectors(save_path, save_writeback_sectors_callback)) { 192 | return FLASHCART_ERR_LOAD; 193 | } 194 | if ((err = flashcart->set_save_writeback(save_writeback_sectors)) != FLASHCART_OK) { 195 | return err; 196 | } 197 | } 198 | 199 | return FLASHCART_OK; 200 | } 201 | 202 | flashcart_err_t flashcart_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress) { 203 | if (!flashcart->load_64dd_ipl) { 204 | return FLASHCART_ERR_FUNCTION_NOT_SUPPORTED; 205 | } 206 | 207 | if (ipl_path == NULL) { 208 | return FLASHCART_ERR_ARGS; 209 | } 210 | 211 | return flashcart->load_64dd_ipl(ipl_path, progress); 212 | } 213 | 214 | flashcart_err_t flashcart_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters) { 215 | if (!flashcart->load_64dd_disk) { 216 | return FLASHCART_ERR_FUNCTION_NOT_SUPPORTED; 217 | } 218 | 219 | if ((disk_path == NULL) || (disk_parameters == NULL)) { 220 | return FLASHCART_ERR_ARGS; 221 | } 222 | 223 | return flashcart->load_64dd_disk(disk_path, disk_parameters); 224 | } 225 | -------------------------------------------------------------------------------- /src/flashcart/flashcart.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file flashcart.h 3 | * @brief Flashcart Subsystem 4 | * @ingroup flashcart 5 | */ 6 | 7 | #ifndef FLASHCART_H__ 8 | #define FLASHCART_H__ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | /** @brief Flashcart error enumeration */ 16 | typedef enum { 17 | FLASHCART_OK, 18 | FLASHCART_ERR_NOT_DETECTED, 19 | FLASHCART_ERR_OUTDATED, 20 | FLASHCART_ERR_SD_CARD, 21 | FLASHCART_ERR_ARGS, 22 | FLASHCART_ERR_LOAD, 23 | FLASHCART_ERR_INT, 24 | FLASHCART_ERR_FUNCTION_NOT_SUPPORTED, 25 | } flashcart_err_t; 26 | 27 | /** @brief List of optional supported flashcart features */ 28 | typedef enum { 29 | FLASHCART_FEATURE_64DD, 30 | FLASHCART_FEATURE_RTC, 31 | FLASHCART_FEATURE_USB, 32 | } flashcart_features_t; 33 | 34 | /** @brief Flashcart save type enumeration */ 35 | typedef enum { 36 | FLASHCART_SAVE_TYPE_NONE, 37 | FLASHCART_SAVE_TYPE_EEPROM_4K, 38 | FLASHCART_SAVE_TYPE_EEPROM_16K, 39 | FLASHCART_SAVE_TYPE_SRAM, 40 | FLASHCART_SAVE_TYPE_SRAM_BANKED, 41 | FLASHCART_SAVE_TYPE_SRAM_128K, 42 | FLASHCART_SAVE_TYPE_FLASHRAM, 43 | FLASHCART_SAVE_TYPE_FLASHRAM_PKST2, 44 | __FLASHCART_SAVE_TYPE_END 45 | } flashcart_save_type_t; 46 | 47 | /** @brief Flashcart Disk Parameter Structure. */ 48 | typedef struct { 49 | bool development_drive; 50 | uint8_t disk_type; 51 | bool bad_system_area_lbas[24]; 52 | uint8_t defect_tracks[16][12]; 53 | } flashcart_disk_parameters_t; 54 | 55 | typedef void flashcart_progress_callback_t (float progress); 56 | 57 | /** @brief Flashcart Structure */ 58 | typedef struct { 59 | /** @brief The flashcart initialization function */ 60 | flashcart_err_t (*init) (void); 61 | /** @brief The flashcart de-initialization function */ 62 | flashcart_err_t (*deinit) (void); 63 | /** @brief The flashcart feature function */ 64 | bool (*has_feature) (flashcart_features_t feature); 65 | /** @brief The flashcart ROM load function */ 66 | flashcart_err_t (*load_rom) (char *rom_path, flashcart_progress_callback_t *progress); 67 | /** @brief The flashcart file load function */ 68 | flashcart_err_t (*load_file) (char *file_path, uint32_t rom_offset, uint32_t file_offset); 69 | /** @brief The flashcart save file load function */ 70 | flashcart_err_t (*load_save) (char *save_path); 71 | /** @brief The flashcart disk bios load function */ 72 | flashcart_err_t (*load_64dd_ipl) (char *ipl_path, flashcart_progress_callback_t *progress); 73 | /** @brief The flashcart disk load function */ 74 | flashcart_err_t (*load_64dd_disk) (char *disk_path, flashcart_disk_parameters_t *disk_parameters); 75 | /** @brief The flashcart set save type function */ 76 | flashcart_err_t (*set_save_type) (flashcart_save_type_t save_type); 77 | /** @brief The flashcart set save writeback function */ 78 | flashcart_err_t (*set_save_writeback) (uint32_t *sectors); 79 | } flashcart_t; 80 | 81 | 82 | char *flashcart_convert_error_message (flashcart_err_t err); 83 | flashcart_err_t flashcart_init (void); 84 | flashcart_err_t flashcart_deinit (void); 85 | bool flashcart_has_feature (flashcart_features_t feature); 86 | flashcart_err_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress); 87 | flashcart_err_t flashcart_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset); 88 | flashcart_err_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type); 89 | flashcart_err_t flashcart_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress); 90 | flashcart_err_t flashcart_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters); 91 | 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /src/flashcart/flashcart_utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "flashcart_utils.h" 4 | #include "../utils/fs.h" 5 | #include "../utils/utils.h" 6 | 7 | 8 | void fix_file_size (FIL *fil) { 9 | // HACK: Align file size to the SD sector size to prevent FatFs from doing partial sector load. 10 | // We are relying on direct transfer from SD to SDRAM without CPU intervention. 11 | // Sending some extra bytes isn't an issue here. 12 | fil->obj.objsize = ALIGN(f_size(fil), FS_SECTOR_SIZE); 13 | } 14 | 15 | void pi_dma_read_data (void *src, void *dst, size_t length) { 16 | data_cache_hit_writeback_invalidate(dst, length); 17 | dma_read_async(dst, (uint32_t) (src), length); 18 | dma_wait(); 19 | } 20 | 21 | void pi_dma_write_data (void *src, void *dst, size_t length) { 22 | assert((((uint32_t) (src)) & 0x07) == 0); 23 | assert((((uint32_t) (dst)) & 0x01) == 0); 24 | assert((length & 1) == 0); 25 | 26 | data_cache_hit_writeback(src, length); 27 | dma_write_raw_async(src, (uint32_t) (dst), length); 28 | dma_wait(); 29 | } 30 | -------------------------------------------------------------------------------- /src/flashcart/flashcart_utils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file flashcart_utils.h 3 | * @brief Flashcart utilities 4 | * @ingroup flashcart 5 | */ 6 | 7 | #ifndef FLASHCART_UTILS_H__ 8 | #define FLASHCART_UTILS_H__ 9 | 10 | 11 | #include 12 | 13 | 14 | void fix_file_size (FIL *fil); 15 | void pi_dma_read_data (void *src, void *dst, size_t length); 16 | void pi_dma_write_data (void *src, void *dst, size_t length); 17 | 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/flashcart/sc64/README.md: -------------------------------------------------------------------------------- 1 | ## SummerCart64 developer notes 2 | 3 | ### Official documentation 4 | 5 | https://github.com/Polprzewodnikowy/SummerCart64/tree/main/docs 6 | -------------------------------------------------------------------------------- /src/flashcart/sc64/sc64.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "../../utils/fs.h" 9 | #include "../../utils/utils.h" 10 | 11 | #include "../flashcart_utils.h" 12 | #include "sc64_ll.h" 13 | #include "sc64.h" 14 | 15 | 16 | #define SRAM_FLASHRAM_ADDRESS (0x08000000) 17 | #define ROM_ADDRESS (0x10000000) 18 | #define IPL_ADDRESS (0x13BC0000) 19 | #define EXTENDED_ADDRESS (0x14000000) 20 | #define SHADOW_ADDRESS (0x1FFC0000) 21 | #define EEPROM_ADDRESS (0x1FFE2000) 22 | 23 | #define SUPPORTED_MAJOR_VERSION (2) 24 | #define SUPPORTED_MINOR_VERSION (17) 25 | #define SUPPORTED_REVISION (0) 26 | 27 | #define DISK_MAPPING_ROM_OFFSET (0x02000000) 28 | #define DISK_MAX_SECTORS (126820) 29 | 30 | #define DISK_TRACKS (1175) 31 | #define DISK_HEADS (2) 32 | #define DISK_BLOCKS (2) 33 | #define DISK_SECTORS_PER_BLOCK (85) 34 | #define DISK_ZONES (16) 35 | #define DISK_BAD_TRACKS_PER_ZONE (12) 36 | #define DISK_TYPES (7) 37 | #define DISK_SYSTEM_LBA_COUNT (24) 38 | 39 | #define THB_UNMAPPED (0xFFFFFFFF) 40 | #define THB_WRITABLE_FLAG (1 << 31) 41 | 42 | 43 | static const struct { 44 | uint8_t head; 45 | uint8_t sector_length; 46 | uint8_t tracks; 47 | uint16_t track_offset; 48 | } zone_mapping[DISK_ZONES] = { 49 | { 0, 232, 158, 0 }, 50 | { 0, 216, 158, 158 }, 51 | { 0, 208, 149, 316 }, 52 | { 0, 192, 149, 465 }, 53 | { 0, 176, 149, 614 }, 54 | { 0, 160, 149, 763 }, 55 | { 0, 144, 149, 912 }, 56 | { 0, 128, 114, 1061 }, 57 | { 1, 216, 158, 0 }, 58 | { 1, 208, 158, 158 }, 59 | { 1, 192, 149, 316 }, 60 | { 1, 176, 149, 465 }, 61 | { 1, 160, 149, 614 }, 62 | { 1, 144, 149, 763 }, 63 | { 1, 128, 149, 912 }, 64 | { 1, 112, 114, 1061 }, 65 | }; 66 | static const uint8_t vzone_to_pzone[DISK_TYPES][DISK_ZONES] = { 67 | {0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10}, 68 | {0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11}, 69 | {0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12}, 70 | {0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13}, 71 | {0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14}, 72 | {0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15}, 73 | {0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8}, 74 | }; 75 | static const uint8_t rom_zones[DISK_TYPES] = { 5, 7, 9, 11, 13, 15, 16 }; 76 | 77 | 78 | static uint32_t disk_sectors_start_offset; 79 | 80 | 81 | static flashcart_err_t load_to_flash (FIL *fil, void *address, size_t size, UINT *br, flashcart_progress_callback_t *progress) { 82 | size_t erase_block_size; 83 | UINT bp; 84 | 85 | *br = 0; 86 | 87 | if (sc64_ll_flash_get_erase_block_size(&erase_block_size) != SC64_OK) { 88 | return FLASHCART_ERR_INT; 89 | } 90 | 91 | while (size > 0) { 92 | size_t program_size = MIN(size, erase_block_size); 93 | if (sc64_ll_flash_erase_block(address) != SC64_OK) { 94 | return FLASHCART_ERR_INT; 95 | } 96 | if (f_read(fil, address, program_size, &bp) != FR_OK) { 97 | return FLASHCART_ERR_LOAD; 98 | } 99 | if (sc64_ll_flash_wait_busy() != SC64_OK) { 100 | return FLASHCART_ERR_INT; 101 | } 102 | if (progress) { 103 | progress(f_tell(fil) / (float) (f_size(fil))); 104 | } 105 | address += program_size; 106 | size -= program_size; 107 | *br += bp; 108 | } 109 | 110 | return FLASHCART_OK; 111 | } 112 | 113 | static uint32_t disk_sectors_start (uint32_t offset) { 114 | disk_sectors_start_offset = offset; 115 | return (offset + (DISK_MAX_SECTORS * sizeof(uint32_t))); 116 | } 117 | 118 | static void disk_sectors_callback (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size) { 119 | for (uint32_t i = 0; i < cluster_size; i++) { 120 | uint32_t offset = file_sector + i; 121 | uint32_t sector = cluster_sector + i; 122 | 123 | if ((offset > DISK_MAX_SECTORS) || (offset > sector_count)) { 124 | return; 125 | } 126 | 127 | io_write(ROM_ADDRESS + disk_sectors_start_offset + (offset * sizeof(uint32_t)), sector); 128 | } 129 | } 130 | 131 | static bool disk_zone_track_is_bad (uint8_t zone, uint8_t track, flashcart_disk_parameters_t *disk_parameters) { 132 | for (int i = 0; i < DISK_BAD_TRACKS_PER_ZONE; i++) { 133 | if (disk_parameters->defect_tracks[zone][i] == track) { 134 | return true; 135 | } 136 | } 137 | 138 | return false; 139 | } 140 | 141 | static bool disk_system_lba_is_bad (uint16_t lba, flashcart_disk_parameters_t *disk_parameters) { 142 | if (lba < DISK_SYSTEM_LBA_COUNT) { 143 | return disk_parameters->bad_system_area_lbas[lba]; 144 | } 145 | 146 | return false; 147 | } 148 | 149 | static void disk_set_thb_mapping (uint32_t offset, uint16_t track, uint8_t head, uint8_t block, bool valid, bool writable, int file_offset) { 150 | uint32_t index = (track << 2) | (head << 1) | (block); 151 | uint32_t mapping = valid ? ((writable ? THB_WRITABLE_FLAG : 0) | (file_offset & ~(THB_WRITABLE_FLAG))) : THB_UNMAPPED; 152 | 153 | io_write(ROM_ADDRESS + offset + (index * sizeof(uint32_t)), mapping); 154 | } 155 | 156 | static uint32_t disk_load_thb_table (uint32_t offset, flashcart_disk_parameters_t *disk_parameters) { 157 | int file_offset = 0; 158 | 159 | uint16_t lba = 0; 160 | uint8_t starting_block = 0; 161 | 162 | for (uint8_t vzone = 0; vzone < DISK_ZONES; vzone++) { 163 | uint8_t pzone = vzone_to_pzone[disk_parameters->disk_type][vzone]; 164 | 165 | uint8_t head = zone_mapping[pzone].head; 166 | uint8_t sector_length = zone_mapping[pzone].sector_length; 167 | uint8_t tracks = zone_mapping[pzone].tracks; 168 | uint16_t track_offset = zone_mapping[pzone].track_offset; 169 | 170 | bool reverse = (head != 0); 171 | int zone_track_start = (reverse ? (tracks - 1) : 0); 172 | int zone_track_end = (reverse ? (-1) : tracks); 173 | 174 | for (int zone_track = zone_track_start; zone_track != zone_track_end; zone_track += (reverse ? (-1) : 1)) { 175 | uint16_t track = track_offset + zone_track; 176 | 177 | if (disk_zone_track_is_bad(pzone, zone_track, disk_parameters)) { 178 | disk_set_thb_mapping(offset, track, head, 0, false, false, 0); 179 | disk_set_thb_mapping(offset, track, head, 1, false, false, 0); 180 | continue; 181 | } 182 | 183 | for (uint8_t block = 0; block < DISK_BLOCKS; block += 1) { 184 | bool valid = !(disk_system_lba_is_bad(lba, disk_parameters)); 185 | bool writable = (vzone >= rom_zones[disk_parameters->disk_type]); 186 | disk_set_thb_mapping(offset, track, head, (starting_block ^ block), valid, writable, file_offset); 187 | file_offset += (sector_length * DISK_SECTORS_PER_BLOCK); 188 | lba += 1; 189 | } 190 | 191 | starting_block ^= 1; 192 | } 193 | } 194 | 195 | return (offset + (DISK_TRACKS * DISK_HEADS * DISK_BLOCKS * sizeof(uint32_t))); 196 | } 197 | 198 | 199 | static flashcart_err_t sc64_init (void) { 200 | uint16_t major; 201 | uint16_t minor; 202 | uint32_t revision; 203 | 204 | if (sc64_ll_get_version(&major, &minor, &revision) != SC64_OK) { 205 | return FLASHCART_ERR_OUTDATED; 206 | } 207 | if (major != SUPPORTED_MAJOR_VERSION) { 208 | return FLASHCART_ERR_OUTDATED; 209 | } 210 | if (minor < SUPPORTED_MINOR_VERSION) { 211 | return FLASHCART_ERR_OUTDATED; 212 | } else if (minor == SUPPORTED_MINOR_VERSION && revision < SUPPORTED_REVISION) { 213 | return FLASHCART_ERR_OUTDATED; 214 | } 215 | 216 | bool writeback_pending; 217 | do { 218 | if (sc64_ll_writeback_pending(&writeback_pending) != SC64_OK) { 219 | return FLASHCART_ERR_INT; 220 | } 221 | } while (writeback_pending); 222 | 223 | const struct { 224 | sc64_cfg_id_t id; 225 | uint32_t value; 226 | } default_config[] = { 227 | { CFG_ID_BOOTLOADER_SWITCH, false }, 228 | { CFG_ID_ROM_WRITE_ENABLE, true }, 229 | { CFG_ID_ROM_SHADOW_ENABLE, false }, 230 | { CFG_ID_DD_MODE, DD_MODE_DISABLED }, 231 | { CFG_ID_ISV_ADDRESS, 0x00000000 }, 232 | { CFG_ID_BOOT_MODE, BOOT_MODE_MENU }, 233 | { CFG_ID_SAVE_TYPE, SAVE_TYPE_NONE }, 234 | { CFG_ID_CIC_SEED, CIC_SEED_AUTO }, 235 | { CFG_ID_TV_TYPE, TV_TYPE_PASSTHROUGH }, 236 | { CFG_ID_DD_SD_ENABLE, false }, 237 | { CFG_ID_DD_DRIVE_TYPE, DRIVE_TYPE_RETAIL }, 238 | { CFG_ID_DD_DISK_STATE, DISK_STATE_EJECTED }, 239 | { CFG_ID_BUTTON_MODE, BUTTON_MODE_NONE }, 240 | { CFG_ID_ROM_EXTENDED_ENABLE, false }, 241 | }; 242 | 243 | for (int i = 0; i < sizeof(default_config) / sizeof(default_config[0]); i++) { 244 | if (sc64_ll_set_config(default_config[i].id, default_config[i].value) != SC64_OK) { 245 | return FLASHCART_ERR_INT; 246 | } 247 | } 248 | 249 | return FLASHCART_OK; 250 | } 251 | 252 | static flashcart_err_t sc64_deinit (void) { 253 | sc64_ll_set_config(CFG_ID_ROM_WRITE_ENABLE, false); 254 | 255 | sc64_ll_lock(); 256 | 257 | return FLASHCART_OK; 258 | } 259 | 260 | static bool sc64_has_feature (flashcart_features_t feature) { 261 | switch (feature) { 262 | case FLASHCART_FEATURE_64DD: return true; 263 | case FLASHCART_FEATURE_RTC: return true; 264 | case FLASHCART_FEATURE_USB: return true; 265 | default: return false; 266 | } 267 | } 268 | 269 | static flashcart_err_t sc64_load_rom (char *rom_path, flashcart_progress_callback_t *progress) { 270 | FIL fil; 271 | UINT br; 272 | 273 | if (f_open(&fil, strip_sd_prefix(rom_path), FA_READ) != FR_OK) { 274 | return FLASHCART_ERR_LOAD; 275 | } 276 | 277 | fix_file_size(&fil); 278 | 279 | size_t rom_size = f_size(&fil); 280 | 281 | if (rom_size > MiB(78)) { 282 | f_close(&fil); 283 | return FLASHCART_ERR_LOAD; 284 | } 285 | 286 | bool shadow_enabled = (rom_size > (MiB(64) - KiB(128))); 287 | bool extended_enabled = (rom_size > MiB(64)); 288 | 289 | size_t sdram_size = shadow_enabled ? (MiB(64) - KiB(128)) : rom_size; 290 | size_t shadow_size = shadow_enabled ? MIN(rom_size - sdram_size, KiB(128)) : 0; 291 | size_t extended_size = extended_enabled ? rom_size - MiB(64) : 0; 292 | 293 | size_t chunk_size = KiB(128); 294 | for (int offset = 0; offset < sdram_size; offset += chunk_size) { 295 | size_t block_size = MIN(sdram_size - offset, chunk_size); 296 | if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) { 297 | f_close(&fil); 298 | return FLASHCART_ERR_LOAD; 299 | } 300 | if (progress) { 301 | progress(f_tell(&fil) / (float) (f_size(&fil))); 302 | } 303 | } 304 | if (f_tell(&fil) != sdram_size) { 305 | f_close(&fil); 306 | return FLASHCART_ERR_LOAD; 307 | } 308 | 309 | if (sc64_ll_set_config(CFG_ID_ROM_SHADOW_ENABLE, shadow_enabled) != SC64_OK) { 310 | f_close(&fil); 311 | return FLASHCART_ERR_INT; 312 | } 313 | 314 | if (shadow_enabled) { 315 | flashcart_err_t err = load_to_flash(&fil, (void *) (SHADOW_ADDRESS), shadow_size, &br, progress); 316 | if (err != FLASHCART_OK) { 317 | f_close(&fil); 318 | return err; 319 | } 320 | if (br != shadow_size) { 321 | f_close(&fil); 322 | return FLASHCART_ERR_LOAD; 323 | } 324 | } 325 | 326 | if (sc64_ll_set_config(CFG_ID_ROM_EXTENDED_ENABLE, extended_enabled) != SC64_OK) { 327 | f_close(&fil); 328 | return FLASHCART_ERR_INT; 329 | } 330 | 331 | if (extended_enabled) { 332 | flashcart_err_t err = load_to_flash(&fil, (void *) (EXTENDED_ADDRESS), extended_size, &br, progress); 333 | if (err != FLASHCART_OK) { 334 | f_close(&fil); 335 | return err; 336 | } 337 | if (br != extended_size) { 338 | f_close(&fil); 339 | return FLASHCART_ERR_LOAD; 340 | } 341 | } 342 | 343 | if (f_close(&fil) != FR_OK) { 344 | return FLASHCART_ERR_LOAD; 345 | } 346 | 347 | return FLASHCART_OK; 348 | } 349 | 350 | static flashcart_err_t sc64_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) { 351 | FIL fil; 352 | UINT br; 353 | 354 | if (f_open(&fil, strip_sd_prefix(file_path), FA_READ) != FR_OK) { 355 | return FLASHCART_ERR_LOAD; 356 | } 357 | 358 | fix_file_size(&fil); 359 | 360 | size_t file_size = f_size(&fil) - file_offset; 361 | 362 | if (file_size > (MiB(64) - rom_offset)) { 363 | f_close(&fil); 364 | return FLASHCART_ERR_ARGS; 365 | } 366 | 367 | if (f_lseek(&fil, file_offset) != FR_OK) { 368 | f_close(&fil); 369 | return FLASHCART_ERR_LOAD; 370 | } 371 | 372 | if (f_read(&fil, (void *) (ROM_ADDRESS + rom_offset), file_size, &br) != FR_OK) { 373 | f_close(&fil); 374 | return FLASHCART_ERR_LOAD; 375 | } 376 | if (br != file_size) { 377 | f_close(&fil); 378 | return FLASHCART_ERR_LOAD; 379 | } 380 | 381 | if (f_close(&fil) != FR_OK) { 382 | return FLASHCART_ERR_LOAD; 383 | } 384 | 385 | return FLASHCART_OK; 386 | } 387 | 388 | static flashcart_err_t sc64_load_save (char *save_path) { 389 | void *address = NULL; 390 | uint32_t value; 391 | 392 | if (sc64_ll_get_config(CFG_ID_SAVE_TYPE, &value) != SC64_OK) { 393 | return FLASHCART_ERR_INT; 394 | } 395 | 396 | sc64_save_type_t type = (sc64_save_type_t) (value); 397 | 398 | switch (type) { 399 | case SAVE_TYPE_EEPROM_4K: 400 | case SAVE_TYPE_EEPROM_16K: 401 | address = (void *) (EEPROM_ADDRESS); 402 | break; 403 | case SAVE_TYPE_SRAM: 404 | case SAVE_TYPE_FLASHRAM: 405 | case SAVE_TYPE_SRAM_BANKED: 406 | address = (void *) (SRAM_FLASHRAM_ADDRESS); 407 | break; 408 | case SAVE_TYPE_NONE: 409 | default: 410 | return FLASHCART_ERR_ARGS; 411 | } 412 | 413 | FIL fil; 414 | UINT br; 415 | 416 | if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) { 417 | return FLASHCART_ERR_LOAD; 418 | } 419 | 420 | size_t save_size = f_size(&fil); 421 | 422 | if (f_read(&fil, address, save_size, &br) != FR_OK) { 423 | f_close(&fil); 424 | return FLASHCART_ERR_LOAD; 425 | } 426 | 427 | if (f_close(&fil) != FR_OK) { 428 | return FLASHCART_ERR_LOAD; 429 | } 430 | 431 | if (br != save_size) { 432 | return FLASHCART_ERR_LOAD; 433 | } 434 | 435 | return FLASHCART_OK; 436 | } 437 | 438 | static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress) { 439 | FIL fil; 440 | UINT br; 441 | 442 | if (f_open(&fil, strip_sd_prefix(ipl_path), FA_READ) != FR_OK) { 443 | return FLASHCART_ERR_LOAD; 444 | } 445 | 446 | fix_file_size(&fil); 447 | 448 | size_t ipl_size = f_size(&fil); 449 | 450 | if (ipl_size > MiB(4)) { 451 | f_close(&fil); 452 | return FLASHCART_ERR_LOAD; 453 | } 454 | 455 | size_t chunk_size = KiB(128); 456 | for (int offset = 0; offset < ipl_size; offset += chunk_size) { 457 | size_t block_size = MIN(ipl_size - offset, chunk_size); 458 | if (f_read(&fil, (void *) (IPL_ADDRESS + offset), block_size, &br) != FR_OK) { 459 | f_close(&fil); 460 | return FLASHCART_ERR_LOAD; 461 | } 462 | if (progress) { 463 | progress(f_tell(&fil) / (float) (f_size(&fil))); 464 | } 465 | } 466 | if (f_tell(&fil) != ipl_size) { 467 | f_close(&fil); 468 | return FLASHCART_ERR_LOAD; 469 | } 470 | 471 | if (f_close(&fil) != FR_OK) { 472 | return FLASHCART_ERR_LOAD; 473 | } 474 | 475 | return FLASHCART_OK; 476 | } 477 | 478 | static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters) { 479 | sc64_disk_mapping_t mapping = { .count = 0 }; 480 | uint32_t next_mapping_offset = DISK_MAPPING_ROM_OFFSET; 481 | 482 | // TODO: Support loading multiple disks 483 | // LOOP START 484 | mapping.disks[mapping.count].thb_table = next_mapping_offset; 485 | mapping.disks[mapping.count].sector_table = disk_load_thb_table(mapping.disks[mapping.count].thb_table, disk_parameters); 486 | next_mapping_offset = disk_sectors_start(mapping.disks[mapping.count].sector_table); 487 | if (file_get_sectors(disk_path, disk_sectors_callback)) { 488 | return FLASHCART_ERR_LOAD; 489 | } 490 | mapping.count += 1; 491 | // LOOP END 492 | 493 | if (mapping.count == 0) { 494 | return FLASHCART_ERR_ARGS; 495 | } 496 | 497 | if (sc64_ll_set_disk_mapping(&mapping) != SC64_OK) { 498 | return FLASHCART_ERR_INT; 499 | } 500 | 501 | sc64_drive_type_t drive_type = disk_parameters->development_drive ? DRIVE_TYPE_DEVELOPMENT : DRIVE_TYPE_RETAIL; 502 | 503 | const struct { 504 | sc64_cfg_id_t id; 505 | uint32_t value; 506 | } config[] = { 507 | { CFG_ID_DD_MODE, DD_MODE_FULL }, 508 | { CFG_ID_DD_SD_ENABLE, true }, 509 | { CFG_ID_DD_DRIVE_TYPE, drive_type }, 510 | { CFG_ID_DD_DISK_STATE, DISK_STATE_INSERTED }, 511 | { CFG_ID_BUTTON_MODE, BUTTON_MODE_DD_DISK_SWAP }, 512 | }; 513 | 514 | for (int i = 0; i < sizeof(config) / sizeof(config[0]); i++) { 515 | if (sc64_ll_set_config(config[i].id, config[i].value) != SC64_OK) { 516 | return FLASHCART_ERR_INT; 517 | } 518 | } 519 | 520 | return FLASHCART_OK; 521 | } 522 | 523 | static flashcart_err_t sc64_set_save_type (flashcart_save_type_t save_type) { 524 | sc64_save_type_t type; 525 | 526 | switch (save_type) { 527 | case FLASHCART_SAVE_TYPE_NONE: 528 | type = SAVE_TYPE_NONE; 529 | break; 530 | case FLASHCART_SAVE_TYPE_EEPROM_4K: 531 | type = SAVE_TYPE_EEPROM_4K; 532 | break; 533 | case FLASHCART_SAVE_TYPE_EEPROM_16K: 534 | type = SAVE_TYPE_EEPROM_16K; 535 | break; 536 | case FLASHCART_SAVE_TYPE_SRAM: 537 | type = SAVE_TYPE_SRAM; 538 | break; 539 | case FLASHCART_SAVE_TYPE_SRAM_BANKED: 540 | type = SAVE_TYPE_SRAM_BANKED; 541 | break; 542 | case FLASHCART_SAVE_TYPE_SRAM_128K: 543 | type = SAVE_TYPE_SRAM_128K; 544 | break; 545 | case FLASHCART_SAVE_TYPE_FLASHRAM: 546 | type = SAVE_TYPE_FLASHRAM; 547 | break; 548 | case FLASHCART_SAVE_TYPE_FLASHRAM_PKST2: 549 | type = SAVE_TYPE_FLASHRAM; 550 | break; 551 | default: 552 | return FLASHCART_ERR_ARGS; 553 | } 554 | 555 | if (sc64_ll_set_config(CFG_ID_SAVE_TYPE, type) != SC64_OK) { 556 | return FLASHCART_ERR_INT; 557 | } 558 | 559 | return FLASHCART_OK; 560 | } 561 | 562 | static flashcart_err_t sc64_set_save_writeback (uint32_t *sectors) { 563 | pi_dma_write_data(sectors, SC64_BUFFERS->BUFFER, 1024); 564 | 565 | if (sc64_ll_writeback_enable(SC64_BUFFERS->BUFFER) != SC64_OK) { 566 | return FLASHCART_ERR_INT; 567 | } 568 | 569 | return FLASHCART_OK; 570 | } 571 | 572 | 573 | static flashcart_t flashcart_sc64 = { 574 | .init = sc64_init, 575 | .deinit = sc64_deinit, 576 | .has_feature = sc64_has_feature, 577 | .load_rom = sc64_load_rom, 578 | .load_file = sc64_load_file, 579 | .load_save = sc64_load_save, 580 | .load_64dd_ipl = sc64_load_64dd_ipl, 581 | .load_64dd_disk = sc64_load_64dd_disk, 582 | .set_save_type = sc64_set_save_type, 583 | .set_save_writeback = sc64_set_save_writeback, 584 | }; 585 | 586 | 587 | flashcart_t *sc64_get_flashcart (void) { 588 | return &flashcart_sc64; 589 | } 590 | -------------------------------------------------------------------------------- /src/flashcart/sc64/sc64.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file sc64.h 3 | * @brief SC64 flashcart support 4 | * @ingroup flashcart 5 | */ 6 | 7 | #ifndef FLASHCART_SC64_H__ 8 | #define FLASHCART_SC64_H__ 9 | 10 | 11 | #include "../flashcart.h" 12 | 13 | 14 | /** 15 | * @addtogroup sc64 16 | * @{ 17 | */ 18 | 19 | flashcart_t *sc64_get_flashcart (void); 20 | 21 | /** @} */ /* sc64 */ 22 | 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/flashcart/sc64/sc64_ll.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../flashcart_utils.h" 4 | #include "sc64_ll.h" 5 | 6 | 7 | /** @brief SummerCart64 Registers Structure. */ 8 | typedef struct { 9 | uint32_t SR_CMD; 10 | uint32_t DATA[2]; 11 | uint32_t IDENTIFIER; 12 | uint32_t KEY; 13 | } sc64_regs_t; 14 | 15 | #define SC64_REGS_BASE (0x1FFF0000UL) 16 | #define SC64_REGS ((sc64_regs_t *) SC64_REGS_BASE) 17 | 18 | #define SC64_SR_CMD_ERROR (1 << 30) 19 | #define SC64_SR_CPU_BUSY (1 << 31) 20 | 21 | #define SC64_KEY_LOCK (0xFFFFFFFFUL) 22 | 23 | 24 | typedef enum { 25 | CMD_ID_VERSION_GET = 'V', 26 | CMD_ID_CONFIG_GET = 'c', 27 | CMD_ID_CONFIG_SET = 'C', 28 | CMD_ID_DISK_MAPPING_SET = 'D', 29 | CMD_ID_WRITEBACK_PENDING = 'w', 30 | CMD_ID_WRITEBACK_SD_INFO = 'W', 31 | CMD_ID_FLASH_WAIT_BUSY = 'p', 32 | CMD_ID_FLASH_ERASE_BLOCK = 'P', 33 | } sc64_cmd_id_t; 34 | 35 | /** @brief SummerCart64 Commands Structure. */ 36 | typedef struct { 37 | sc64_cmd_id_t id; 38 | uint32_t arg[2]; 39 | uint32_t rsp[2]; 40 | } sc64_cmd_t; 41 | 42 | 43 | static sc64_error_t sc64_ll_execute_cmd (sc64_cmd_t *cmd) { 44 | io_write((uint32_t) (&SC64_REGS->DATA[0]), cmd->arg[0]); 45 | io_write((uint32_t) (&SC64_REGS->DATA[1]), cmd->arg[1]); 46 | 47 | io_write((uint32_t) (&SC64_REGS->SR_CMD), (cmd->id & 0xFF)); 48 | 49 | uint32_t sr; 50 | do { 51 | sr = io_read((uint32_t) (&SC64_REGS->SR_CMD)); 52 | } while (sr & SC64_SR_CPU_BUSY); 53 | 54 | if (sr & SC64_SR_CMD_ERROR) { 55 | return (sc64_error_t) (io_read((uint32_t) (&SC64_REGS->DATA[0]))); 56 | } 57 | 58 | cmd->rsp[0] = io_read((uint32_t) (&SC64_REGS->DATA[0])); 59 | cmd->rsp[1] = io_read((uint32_t) (&SC64_REGS->DATA[1])); 60 | 61 | return SC64_OK; 62 | } 63 | 64 | 65 | void sc64_ll_lock (void) { 66 | io_write((uint32_t) (&SC64_REGS->KEY), SC64_KEY_LOCK); 67 | } 68 | 69 | sc64_error_t sc64_ll_get_version (uint16_t *major, uint16_t *minor, uint32_t *revision) { 70 | sc64_cmd_t cmd = { 71 | .id = CMD_ID_VERSION_GET 72 | }; 73 | sc64_error_t error = sc64_ll_execute_cmd(&cmd); 74 | *major = ((cmd.rsp[0] >> 16) & 0xFFFF); 75 | *minor = (cmd.rsp[0] & 0xFFFF); 76 | *revision = cmd.rsp[1]; 77 | return error; 78 | } 79 | 80 | sc64_error_t sc64_ll_get_config (sc64_cfg_id_t id, uint32_t *value) { 81 | sc64_cmd_t cmd = { 82 | .id = CMD_ID_CONFIG_GET, 83 | .arg = { id } 84 | }; 85 | sc64_error_t error = sc64_ll_execute_cmd(&cmd); 86 | *value = cmd.rsp[1]; 87 | return error; 88 | } 89 | 90 | sc64_error_t sc64_ll_set_config (sc64_cfg_id_t id, uint32_t value) { 91 | sc64_cmd_t cmd = { 92 | .id = CMD_ID_CONFIG_SET, 93 | .arg = { id, value } 94 | }; 95 | return sc64_ll_execute_cmd(&cmd); 96 | } 97 | 98 | sc64_error_t sc64_ll_set_disk_mapping (sc64_disk_mapping_t *disk_mapping) { 99 | int disk_count = disk_mapping->count; 100 | 101 | if (disk_count <= 0 || disk_count > 4) { 102 | return SC64_ERROR_BAD_ARGUMENT; 103 | } 104 | 105 | uint32_t info[8] __attribute__((aligned(8))); 106 | 107 | for (int i = 0; i < disk_count; i++) { 108 | info[i * 2] = disk_mapping->disks[i].thb_table; 109 | info[(i * 2) + 1] = disk_mapping->disks[i].sector_table; 110 | } 111 | 112 | uint32_t length = (disk_mapping->count * 2 * sizeof(uint32_t)); 113 | 114 | pi_dma_write_data(info, SC64_BUFFERS->BUFFER, length); 115 | 116 | sc64_cmd_t cmd = { 117 | .id = CMD_ID_DISK_MAPPING_SET, 118 | .arg = { (uint32_t) (SC64_BUFFERS->BUFFER), length } 119 | }; 120 | 121 | return sc64_ll_execute_cmd(&cmd); 122 | } 123 | 124 | sc64_error_t sc64_ll_writeback_pending (bool *pending) { 125 | sc64_cmd_t cmd = { 126 | .id = CMD_ID_WRITEBACK_PENDING 127 | }; 128 | sc64_error_t error = sc64_ll_execute_cmd(&cmd); 129 | *pending = (cmd.rsp[0] != 0); 130 | return error; 131 | } 132 | 133 | sc64_error_t sc64_ll_writeback_enable (void *address) { 134 | sc64_cmd_t cmd = { 135 | .id = CMD_ID_WRITEBACK_SD_INFO, 136 | .arg = { (uint32_t) (address) } 137 | }; 138 | return sc64_ll_execute_cmd(&cmd); 139 | } 140 | 141 | sc64_error_t sc64_ll_flash_wait_busy (void) { 142 | sc64_cmd_t cmd = { 143 | .id = CMD_ID_FLASH_WAIT_BUSY, 144 | .arg = { true } 145 | }; 146 | return sc64_ll_execute_cmd(&cmd); 147 | } 148 | 149 | sc64_error_t sc64_ll_flash_get_erase_block_size (size_t *erase_block_size) { 150 | sc64_cmd_t cmd = { 151 | .id = CMD_ID_FLASH_WAIT_BUSY, 152 | .arg = { false } 153 | }; 154 | sc64_error_t error = sc64_ll_execute_cmd(&cmd); 155 | *erase_block_size = (size_t) (cmd.rsp[0]); 156 | return error; 157 | } 158 | 159 | sc64_error_t sc64_ll_flash_erase_block (void *address) { 160 | sc64_cmd_t cmd = { 161 | .id = CMD_ID_FLASH_ERASE_BLOCK, 162 | .arg = { (uint32_t) (address) } 163 | }; 164 | return sc64_ll_execute_cmd(&cmd); 165 | } 166 | -------------------------------------------------------------------------------- /src/flashcart/sc64/sc64_ll.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file sc64_ll.h 3 | * @brief SC64 flashcart low level access 4 | * @ingroup flashcart 5 | */ 6 | 7 | #ifndef FLASHCART_SC64_LL_H__ 8 | #define FLASHCART_SC64_LL_H__ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | /** 16 | * @addtogroup sc64 17 | * @{ 18 | */ 19 | 20 | /** @brief The SC64 buffers structure. */ 21 | typedef struct { 22 | uint8_t BUFFER[8192]; 23 | uint8_t EEPROM[2048]; 24 | uint8_t DD_SECTOR[256]; 25 | uint8_t FLASHRAM[128]; 26 | } sc64_buffers_t; 27 | 28 | #define SC64_BUFFERS_BASE (0x1FFE0000UL) 29 | #define SC64_BUFFERS ((sc64_buffers_t *) SC64_BUFFERS_BASE) 30 | 31 | /** @brief The SC64 State Enumeration. */ 32 | typedef enum { 33 | SC64_OK, 34 | SC64_ERROR_BAD_ARGUMENT, 35 | SC64_ERROR_BAD_ADDRESS, 36 | SC64_ERROR_BAD_CONFIG_ID, 37 | SC64_ERROR_TIMEOUT, 38 | SC64_ERROR_SD_CARD, 39 | SC64_ERROR_UNKNOWN_CMD = -1 40 | } sc64_error_t; 41 | 42 | typedef enum { 43 | CFG_ID_BOOTLOADER_SWITCH, 44 | CFG_ID_ROM_WRITE_ENABLE, 45 | CFG_ID_ROM_SHADOW_ENABLE, 46 | CFG_ID_DD_MODE, 47 | CFG_ID_ISV_ADDRESS, 48 | CFG_ID_BOOT_MODE, 49 | CFG_ID_SAVE_TYPE, 50 | CFG_ID_CIC_SEED, 51 | CFG_ID_TV_TYPE, 52 | CFG_ID_DD_SD_ENABLE, 53 | CFG_ID_DD_DRIVE_TYPE, 54 | CFG_ID_DD_DISK_STATE, 55 | CFG_ID_BUTTON_STATE, 56 | CFG_ID_BUTTON_MODE, 57 | CFG_ID_ROM_EXTENDED_ENABLE, 58 | } sc64_cfg_id_t; 59 | 60 | typedef enum { 61 | DD_MODE_DISABLED = 0, 62 | DD_MODE_REGS = 1, 63 | DD_MODE_IPL = 2, 64 | DD_MODE_FULL = 3 65 | } sc64_dd_mode_t; 66 | 67 | /** @brief The SC64 Boot Mode Enumeration. */ 68 | typedef enum { 69 | BOOT_MODE_MENU = 0, 70 | BOOT_MODE_ROM = 1, 71 | BOOT_MODE_DDIPL = 2, 72 | BOOT_MODE_DIRECT_ROM = 3, 73 | BOOT_MODE_DIRECT_DDIPL = 4, 74 | } sc64_boot_mode_t; 75 | 76 | /** @brief The SC64 Save Type Enumeration. */ 77 | typedef enum { 78 | SAVE_TYPE_NONE, 79 | SAVE_TYPE_EEPROM_4K, 80 | SAVE_TYPE_EEPROM_16K, 81 | SAVE_TYPE_SRAM, 82 | SAVE_TYPE_FLASHRAM, 83 | SAVE_TYPE_SRAM_BANKED, 84 | SAVE_TYPE_SRAM_128K, 85 | } sc64_save_type_t; 86 | 87 | typedef enum { 88 | CIC_SEED_AUTO = 0xFFFF 89 | } sc64_cic_seed_t; 90 | 91 | typedef enum { 92 | TV_TYPE_PAL = 0, 93 | TV_TYPE_NTSC = 1, 94 | TV_TYPE_MPAL = 2, 95 | TV_TYPE_PASSTHROUGH = 3 96 | } sc64_tv_type_t; 97 | 98 | typedef enum { 99 | DRIVE_TYPE_RETAIL, 100 | DRIVE_TYPE_DEVELOPMENT, 101 | } sc64_drive_type_t; 102 | 103 | typedef enum { 104 | DISK_STATE_EJECTED, 105 | DISK_STATE_INSERTED, 106 | DISK_STATE_CHANGED, 107 | } sc64_disk_state_t; 108 | 109 | typedef enum { 110 | BUTTON_MODE_NONE, 111 | BUTTON_MODE_N64_IRQ, 112 | BUTTON_MODE_USB_PACKET, 113 | BUTTON_MODE_DD_DISK_SWAP, 114 | } sc64_button_mode_t; 115 | 116 | typedef struct { 117 | int count; 118 | struct { 119 | uint32_t thb_table; 120 | uint32_t sector_table; 121 | } disks[4]; 122 | } sc64_disk_mapping_t; 123 | 124 | 125 | void sc64_ll_lock (void); 126 | sc64_error_t sc64_ll_get_version (uint16_t *major, uint16_t *minor, uint32_t *revision); 127 | sc64_error_t sc64_ll_get_config (sc64_cfg_id_t cfg, uint32_t *value); 128 | sc64_error_t sc64_ll_set_config (sc64_cfg_id_t cfg, uint32_t value); 129 | sc64_error_t sc64_ll_set_disk_mapping (sc64_disk_mapping_t *disk_mapping); 130 | sc64_error_t sc64_ll_writeback_pending (bool *pending); 131 | sc64_error_t sc64_ll_writeback_enable (void *address); 132 | sc64_error_t sc64_ll_flash_wait_busy (void); 133 | sc64_error_t sc64_ll_flash_get_erase_block_size (size_t *erase_block_size); 134 | sc64_error_t sc64_ll_flash_erase_block (void *address); 135 | 136 | /** @} */ /* sc64 */ 137 | 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "boot/boot.h" 6 | #include "flashcart/flashcart.h" 7 | 8 | #define PI 3.141592653f 9 | #define PI2 (PI*2.0f) 10 | 11 | #define RDPQ_COMBINER_TEX_ALPHA RDPQ_COMBINER1((0,0,0,PRIM), (TEX0,0,PRIM,0)) 12 | 13 | int menu_active; 14 | boot_params_t boot_params; 15 | char rom_path[1024]; 16 | 17 | float lerp(float t, float a, float b) { 18 | return a + (b - a) * t; 19 | } 20 | 21 | float bezier_interp(float t, float x1, float y1, float x2, float y2) { 22 | float f0 = 1.0f - 3.0f * x2 + 3 * x1; 23 | float f1 = 3.0f * x2 - 6.0f * x1; 24 | float f2 = 3.0f * x1; 25 | float refinedT = t; 26 | 27 | for (int i = 0; i < 5; i++) { 28 | float refinedT2 = refinedT * refinedT; 29 | float refinedT3 = refinedT2 * refinedT; 30 | 31 | float x = f0 * refinedT3 + f1 * refinedT2 + f2 * refinedT; 32 | float slope = 1.0f / (3.0f * f0 * refinedT2 + 2.0f * f1 * refinedT + f2); 33 | refinedT -= (x - t) * slope; 34 | if(refinedT < 0.0f) refinedT = 0.0f; 35 | if(refinedT > 1.0f) refinedT = 1.0f; 36 | } 37 | 38 | // Resolve cubic bezier for the given x 39 | return 3.0f * pow(1.0f - refinedT, 2.0f) * refinedT * y1 + 3.0f * (1.0f - refinedT) * pow(refinedT, 2.0f) * y2 + pow(refinedT, 3.0f); 40 | } 41 | 42 | sprite_t *logo; 43 | sprite_t *shadow; 44 | sprite_t *loading_dot; 45 | 46 | int spinner_fade_counter = 0; 47 | float spinner_fade = 0.0f; 48 | float spinner_counter = 0.0f; 49 | 50 | void spinner_draw(float x, float y, float size, float alpha) { 51 | float dot_size = (11.0f * 0.5f) * 0.4f; 52 | rdpq_set_prim_color(RGBA32(255, 255, 255, alpha * 255.0f)); 53 | rdpq_mode_combiner(RDPQ_COMBINER_TEX_ALPHA); 54 | rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY); 55 | for(int a = 11; a >= 0 ; a--) { 56 | float thisAnimSize = (12.0f - fmod((spinner_counter + a),12.0f))- 9.0f; 57 | if(thisAnimSize < 0.0f) thisAnimSize = 0.0f; 58 | float animParam = thisAnimSize / 3.0f; 59 | float scaled = 0.4f + (animParam * 0.6f); 60 | float dot_ang = (a / 12.0f) * PI2; 61 | float dot_x = (x + sin(dot_ang) * size) - dot_size * scaled; 62 | float dot_y = (y + cos(dot_ang) * size) - dot_size * scaled; 63 | rdpq_sprite_blit(loading_dot, dot_x, dot_y, &(rdpq_blitparms_t){ 64 | .scale_x = scaled, .scale_y = scaled, 65 | }); 66 | } 67 | spinner_counter += 0.25f; 68 | } 69 | 70 | static void cart_load_progress(float progress) { 71 | surface_t *d = (progress >= 1.0f) ? display_get() : display_try_get(); 72 | if (d) { 73 | rdpq_attach(d, NULL); 74 | 75 | rdpq_set_mode_fill(RGBA32(0,0,0,0)); 76 | rdpq_fill_rectangle(0, 0, 640, 480); 77 | 78 | rdpq_set_mode_standard(); 79 | spinner_fade_counter++; 80 | if(spinner_fade_counter >= 10) { 81 | spinner_fade = (spinner_fade_counter - 10) / 30.0f; 82 | if(spinner_fade > 1.0f) spinner_fade = 1.0f; 83 | spinner_draw(640 - 55, 480 - 48, 20, spinner_fade); 84 | } 85 | rdpq_detach_show(); 86 | } 87 | 88 | } 89 | 90 | float fade_v1[] = { 0, 0 }; 91 | float fade_v2[] = { 640, 480 }; 92 | float fade_v3[] = { 0, 480 }; 93 | float fade_v4[] = { 640, 0 }; 94 | 95 | joypad_buttons_t p1_buttons; 96 | joypad_buttons_t p1_buttons_press; 97 | joypad_inputs_t p1_inputs; 98 | 99 | rdpq_font_t * font; 100 | 101 | static wav64_t se_titlelogo; 102 | static wav64_t se_cursor_ng; 103 | static wav64_t se_gametitle_cursor; 104 | static wav64_t se_filemenu_close; 105 | static wav64_t se_filemenu_open; 106 | static wav64_t se_gametitle_click; 107 | 108 | #define SCREEN_WIDTH 640 109 | #define SCREEN_HEIGHT 480 110 | 111 | #define CURSOR_INTERVAL 25 112 | #define CURSOR_INTERVAL_SECOND (CURSOR_INTERVAL - 6) 113 | 114 | #define FADE_DURATION 60.0f 115 | #define OPENING_WAIT 60.0f * 2.0f 116 | #define OPENING_SOUND_PLAY (OPENING_WAIT - 10.0f) 117 | #define OPENING_OUT 20.0f 118 | 119 | #define SCR_REGION_X_MIN 16.0f 120 | #define BOX_REGION_X_MIN 64.0f 121 | #define BOX_REGION_X_MAX 624.0f 122 | #define BOX_REGION_Y_MIN 16.0f 123 | #define BOX_REGION_Y_MAX 464.0f 124 | 125 | #define TITLE_BOX_WIDTH_E 256.0f 126 | #define TITLE_BOX_HEIGHT_E 179.0f 127 | 128 | #define TITLE_BOX_WIDTH_BORDER_E 257.0f 129 | #define TITLE_BOX_HEIGHT_BORDER_W 180.0f 130 | 131 | #define TITLE_SELECT_SCALE 1.2f 132 | 133 | #define CHANNEL_SFX1 0 134 | #define CHANNEL_SFX2 2 135 | #define CHANNEL_SFX3 4 136 | #define CHANNEL_MUSIC 6 137 | 138 | float box_region_min_x = BOX_REGION_X_MIN; 139 | float box_region_max_x = BOX_REGION_X_MAX; 140 | float box_region_min_y = BOX_REGION_Y_MIN; 141 | float box_region_max_y = BOX_REGION_Y_MAX; 142 | 143 | float viewport_y = BOX_REGION_Y_MIN; 144 | float viewport_y_bottom = BOX_REGION_Y_MAX; 145 | float viewport_y_target = BOX_REGION_Y_MIN; 146 | 147 | float box_region_scissor_left = 64.0f; 148 | 149 | int fade_state = 0; 150 | int fade_counter = FADE_DURATION; 151 | float fade_lvl = 1.0f; 152 | 153 | int main_state = 0; 154 | int opening_counter = OPENING_WAIT; 155 | float opening_card_offset_x = 0.0f; 156 | 157 | float shadow_vertex[8]; 158 | 159 | void shadow_draw(float x, float y, float width, float height, float alpha) { 160 | // Load shadow sprite surface 161 | surface_t surf = sprite_get_pixels(shadow); 162 | rdpq_set_prim_color(RGBA32(0, 0, 0, alpha * 255.0f)); 163 | rdpq_mode_combiner(RDPQ_COMBINER_TEX_ALPHA); 164 | rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY); 165 | rdpq_mode_dithering(DITHER_BAYER_BAYER); 166 | rdpq_mode_tlut(TLUT_NONE); 167 | rdpq_tex_upload(TILE0, &surf, &(rdpq_texparms_t){.s.repeats = 1, .t.repeats = 1}); 168 | 169 | float xw = x + width; 170 | float yh = y + height; 171 | 172 | rdpq_triangle(&TRIFMT_TEX, 173 | (float[]){ x, y, 0.0f, 0.0f, 1.0f }, 174 | (float[]){xw, y, 64.0f, 0.0f, 1.0f }, 175 | (float[]){xw, yh, 64.0f,64.0f, 1.0f } 176 | ); 177 | 178 | rdpq_triangle(&TRIFMT_TEX, 179 | (float[]){ x, y, 0.0f, 0.0f, 1.0f }, 180 | (float[]){xw, yh, 64.0f,64.0f, 1.0f }, 181 | (float[]){ x, yh, 64.0f, 0.0f, 1.0f } 182 | ); 183 | 184 | // Revert combiner 185 | rdpq_mode_combiner(RDPQ_COMBINER_TEX_FLAT); 186 | } 187 | 188 | void outline_draw(float x, float y, float width, float height, float brightness) { 189 | #define OUTLINE_WIDTH 1 190 | int brightv = brightness * 255.0f; 191 | x = round(x); 192 | y = round(y); 193 | width = ceil(width); 194 | height = ceil(height); 195 | rdpq_set_mode_fill(RGBA32(brightv,brightv,brightv,0)); 196 | rdpq_fill_rectangle(x, y, x + width, y + OUTLINE_WIDTH); 197 | rdpq_fill_rectangle(x, y+height-OUTLINE_WIDTH, x + width, y + height); 198 | rdpq_fill_rectangle(x, y, x+OUTLINE_WIDTH, y+height); 199 | rdpq_fill_rectangle(x+width-OUTLINE_WIDTH, y, x+width, y+height); 200 | } 201 | 202 | typedef struct SquareSprite_s { 203 | float x, y; 204 | float width; 205 | float height; 206 | } SquareSprite; 207 | 208 | typedef struct TitleBox_s { 209 | char id[8]; 210 | sprite_t * image; 211 | SquareSprite sprite; 212 | float scale; 213 | float scaleGrow; 214 | float offset_x, offset_y; 215 | int isSelected; 216 | float selectedOutline; 217 | float outlineCounter; 218 | float screenX; 219 | float screenY; 220 | } TitleBox; 221 | 222 | float title_brightness = 1.0f; 223 | 224 | #define MAX_TITLE_COUNT 40 225 | 226 | int title_count = 0; 227 | 228 | TitleBox title_list[MAX_TITLE_COUNT]; 229 | 230 | #define TITLE_COLUMNS 4 231 | #define TITLE_ROWS 50 232 | #define TITLE_TOTAL_SLOTS (TITLE_ROWS * TITLE_COLUMNS) 233 | 234 | TitleBox * title_row[TITLE_ROWS][TITLE_COLUMNS] = { 235 | {NULL, NULL, NULL, NULL}, 236 | {NULL, NULL, NULL, NULL}, 237 | {NULL, NULL, NULL, NULL}, 238 | {NULL, NULL, NULL, NULL}, 239 | {NULL, NULL, NULL, NULL}, 240 | {NULL, NULL, NULL, NULL}, 241 | {NULL, NULL, NULL, NULL}, 242 | {NULL, NULL, NULL, NULL}, 243 | {NULL, NULL, NULL, NULL}, 244 | {NULL, NULL, NULL, NULL} 245 | }; 246 | int title_row_count[TITLE_ROWS] = {4,4,2,0,0,0,0,0,0,0}; 247 | 248 | TitleBox * selectedTitle = NULL; 249 | 250 | int cursor_x = 0; 251 | int cursor_x_timer = 0; 252 | int cursor_y = 0; 253 | int cursor_y_timer = 0; 254 | 255 | void TitleBox_create(TitleBox * title, sprite_t * image, float x, float y) { 256 | title->image = image; 257 | title->sprite.x = x; 258 | title->sprite.y = y; 259 | title->sprite.width = TITLE_BOX_WIDTH_E; 260 | title->sprite.height = TITLE_BOX_HEIGHT_E; 261 | title->scale = 1.0f; 262 | title->offset_x = title->offset_y = 0.0f; 263 | title->isSelected = 0; 264 | title->scaleGrow = 0.0f; 265 | title->selectedOutline = 0.0f; 266 | title->outlineCounter = 0.0f; 267 | } 268 | 269 | void TitleBox_create2(TitleBox * title, sprite_t * image, char * id) { 270 | memcpy(title->id, id, 8); 271 | title->image = image; 272 | title->sprite.x = 0.0f; 273 | title->sprite.y = 0.0f; 274 | title->sprite.width = TITLE_BOX_WIDTH_E; 275 | title->sprite.height = TITLE_BOX_HEIGHT_E; 276 | title->scale = 1.0f; 277 | title->offset_x = title->offset_y = 0.0f; 278 | title->isSelected = 0; 279 | title->scaleGrow = 0.0f; 280 | title->selectedOutline = 0.0f; 281 | title->outlineCounter = 0.0f; 282 | } 283 | 284 | void TitleBox_update(TitleBox * title) { 285 | if(title->isSelected) { 286 | title->scaleGrow += 1.0f / 10.0f; 287 | if(title->scaleGrow > 1.0f) title->scaleGrow = 1.0f; 288 | //title->selectedOutline += 1.0f/10.0f; 289 | 290 | title->selectedOutline = 1.0f - (cos(title->outlineCounter) * 0.5f + 0.5f); 291 | //if(title->selectedOutline > 1.0f) title->selectedOutline = 1.0f; 292 | 293 | title->outlineCounter += 0.15f; 294 | } else { 295 | title->scaleGrow = 0.0f; 296 | title->selectedOutline = 0.0f; 297 | title->outlineCounter = 0.0f; 298 | } 299 | } 300 | 301 | void TitleBox_draw(TitleBox * title) { 302 | float titleScale = title->scale; 303 | float originalWidth = title->sprite.width * title->scale; 304 | float originalHeight = title->sprite.height * title->scale; 305 | if(title->isSelected) { 306 | float scaleHeight = (originalHeight + 16) / originalHeight; 307 | float scaleFactor = titleScale * scaleHeight; 308 | titleScale = titleScale + title->scaleGrow * (scaleFactor - titleScale); 309 | } 310 | 311 | // Center scale 312 | 313 | float scaledWidth = title->sprite.width * titleScale; 314 | float scaledHeight = title->sprite.height * titleScale; 315 | float scaledOffsetX = scaledWidth - originalWidth; 316 | float scaledOffsetY = scaledHeight - originalHeight; 317 | 318 | float offsetX = title->sprite.x - scaledOffsetX * 0.5f; 319 | float offsetY = title->sprite.y - scaledOffsetY * 0.5f; 320 | if(offsetX < box_region_min_x) offsetX = box_region_min_x; 321 | if((offsetX + scaledWidth) >= box_region_max_x) offsetX = box_region_max_x - scaledWidth; 322 | 323 | if(offsetY < box_region_min_y) offsetY = box_region_min_y; 324 | if((offsetY + scaledHeight) >= box_region_max_y) offsetY = box_region_max_y - scaledHeight; 325 | 326 | if((offsetY < viewport_y && (offsetY + scaledHeight) <= viewport_y) || 327 | (offsetY >= viewport_y_bottom && (offsetY + scaledHeight) > viewport_y_bottom)) { 328 | return; 329 | } 330 | 331 | if(title->isSelected) { 332 | float shadow_scaleX = titleScale * 1.1f; 333 | float shadow_scaleY = titleScale * 1.05f; 334 | float shadowSizeX = scaledWidth * shadow_scaleX; 335 | float shadowSizeY = scaledHeight * shadow_scaleY; 336 | float shadowOffsetX = offsetX - (shadowSizeX - scaledWidth) * 0.5f; 337 | 338 | shadow_draw(shadowOffsetX, offsetY - (viewport_y - BOX_REGION_Y_MIN), shadowSizeX, shadowSizeY, 0.23f); 339 | } 340 | int b = title_brightness * 255.0f; 341 | rdpq_set_prim_color(RGBA32(b, b, b, 255)); 342 | rdpq_sprite_blit(title->image, offsetX, offsetY - (viewport_y - BOX_REGION_Y_MIN), &(rdpq_blitparms_t){ 343 | .scale_x = titleScale, .scale_y = titleScale, 344 | }); 345 | 346 | if(title->isSelected) { 347 | outline_draw( 348 | offsetX, offsetY - (viewport_y - BOX_REGION_Y_MIN), 349 | title->sprite.width * titleScale, title->sprite.height * titleScale, 350 | title->selectedOutline 351 | ); 352 | } 353 | } 354 | 355 | typedef struct MenuSidebar_s { 356 | SquareSprite sprite; 357 | float vertex[8]; 358 | float width; 359 | int isOpen; 360 | int animCounter; 361 | int prevIsOpen; 362 | } MenuSidebar; 363 | MenuSidebar menu_sidebar; 364 | 365 | void vertex_set_from_xywh(SquareSprite * sprite, float * out) { 366 | out[0] = sprite->x; 367 | out[1] = sprite->y; 368 | 369 | out[2] = sprite->x + sprite->width; 370 | out[3] = sprite->y + sprite->height; 371 | 372 | out[4] = out[0]; 373 | out[5] = out[3]; 374 | 375 | out[6] = out[2]; 376 | out[7] = out[1]; 377 | } 378 | 379 | int menu_sidebar_anim[] = {0, 10, 20, 30, 30, 20, 10, 0}; 380 | 381 | void menu_sidebar_update(MenuSidebar * obj) { 382 | vertex_set_from_xywh(&obj->sprite, obj->vertex); 383 | obj->prevIsOpen = obj->isOpen; 384 | if(cursor_x < 0) { 385 | obj->isOpen = 1; 386 | } else { 387 | obj->isOpen = 0; 388 | } 389 | 390 | if(obj->prevIsOpen != obj->isOpen) { 391 | if(obj->isOpen) { 392 | wav64_play(&se_filemenu_open, CHANNEL_SFX1); 393 | } else { 394 | wav64_play(&se_filemenu_close, CHANNEL_SFX1); 395 | } 396 | } 397 | 398 | if(obj->isOpen) { 399 | obj->animCounter++; 400 | } else { 401 | obj->animCounter--; 402 | } 403 | #define SIDE_MENU_OPN_FRM 10.0f 404 | 405 | if(obj->animCounter > SIDE_MENU_OPN_FRM) obj->animCounter = SIDE_MENU_OPN_FRM; 406 | if(obj->animCounter < 0) obj->animCounter = 0; 407 | 408 | float anmfact = obj->animCounter / SIDE_MENU_OPN_FRM; 409 | float interpl; 410 | 411 | if(obj->isOpen) { 412 | interpl = bezier_interp(anmfact, .44f,.88f,.57f,1.26f); 413 | } else { 414 | interpl = 1.0f - bezier_interp(1.0f - anmfact, .44f,.88f,.57f,1.15f); 415 | } 416 | obj->sprite.width = 64.0f + abs(interpl * 256.0f); 417 | title_brightness = 0.5f + (1.0f - anmfact) * 0.5f; 418 | 419 | 420 | 421 | 422 | 423 | } 424 | void menu_sidebar_draw(MenuSidebar * obj) { 425 | rdpq_set_prim_color(RGBA32(255, 0, 0, 0)); 426 | rdpq_triangle(&TRIFMT_FILL, &obj->vertex[0], &obj->vertex[2], &obj->vertex[4]); 427 | rdpq_triangle(&TRIFMT_FILL, &obj->vertex[0], &obj->vertex[6], &obj->vertex[2]); 428 | } 429 | 430 | void setRomPath(char * code) { 431 | memset(rom_path, 0, sizeof(rom_path)); 432 | strcpy(rom_path, "sd:/menu/title/"); 433 | strcat(rom_path, code); 434 | strcat(rom_path, "/"); 435 | strcat(rom_path, code); 436 | strcat(rom_path, "_e.z64"); 437 | } 438 | void setupRomLoad(TitleBox * title) { 439 | setRomPath(title->id); 440 | 441 | flashcart_load_rom(rom_path, false, cart_load_progress); 442 | 443 | //boot_params.reset_type = BOOT_RESET_TYPE_NMI; 444 | boot_params.device_type = BOOT_DEVICE_TYPE_ROM; 445 | boot_params.tv_type = BOOT_TV_TYPE_PASSTHROUGH; 446 | boot_params.detect_cic_seed = true; 447 | 448 | menu_active = false; 449 | } 450 | 451 | void load_titles() { 452 | int row_max = TITLE_COLUMNS; 453 | 454 | //row_max = 4; // Quick hack to reduce the number of titles per row 455 | 456 | char pathstr[256]; 457 | strcpy(pathstr, "sd:/menu/title/"); 458 | 459 | FILE * f = fopen("sd://menu/title.csv", "r"); 460 | if(f != NULL){ 461 | setvbuf(f, NULL, _IONBF, 0); 462 | fseek(f, 0, SEEK_END); 463 | int csvsize = ftell(f); 464 | fseek(f, 0, SEEK_SET); 465 | fread(pathstr, 1, csvsize, f); 466 | fclose(f); 467 | char * pathstritr = pathstr; 468 | char flbuf[8]; 469 | int flbfpos = 0; 470 | title_count = 0; 471 | do { 472 | if(*pathstritr == ',' || *pathstritr == ' ' || *pathstritr == 0x0) { 473 | if(flbfpos != 0) { 474 | char filename[64]; 475 | flbuf[flbfpos] = 0; 476 | strcpy(filename, "sd:/menu/title/"); 477 | strcat(filename, flbuf); 478 | strcat(filename, "/"); 479 | strcat(filename, flbuf); 480 | strcat(filename, "_e.sprite"); 481 | TitleBox_create2(&title_list[title_count], sprite_load(filename), flbuf); 482 | title_row[title_count/row_max][title_count%row_max] = &title_list[title_count]; 483 | title_count++; 484 | } 485 | flbfpos = 0; 486 | } else { 487 | flbuf[flbfpos] = *pathstritr; 488 | flbfpos++; 489 | } 490 | pathstritr++; 491 | } while(*pathstritr != 0x00); 492 | } 493 | 494 | for(int y = 0; y < TITLE_ROWS; y++) { 495 | int row_count = 0; 496 | for(int x = 0; x < row_max; x++) { 497 | if(title_row[y][x] != NULL) row_count++; 498 | } 499 | title_row_count[y] = row_count; 500 | } 501 | 502 | float currentRowY = box_region_min_y; 503 | for(int y = 0; y < TITLE_ROWS; y++) { 504 | int currentRowCount = title_row_count[y]; 505 | if(currentRowCount == 0) continue; 506 | float rowScale = 1.0f; 507 | if(currentRowCount > 2) { 508 | rowScale = (BOX_REGION_X_MAX - BOX_REGION_X_MIN) / (currentRowCount * TITLE_BOX_WIDTH_E); 509 | } 510 | float scaledTitleX = TITLE_BOX_WIDTH_E * rowScale; 511 | 512 | for(int x = 0; x < row_max; x++) { 513 | TitleBox * current = title_row[y][x]; 514 | if(current == NULL) continue; 515 | current->scale = rowScale; 516 | 517 | current->sprite.x = box_region_min_x + scaledTitleX * x; 518 | current->sprite.y = currentRowY; 519 | 520 | } 521 | currentRowY += TITLE_BOX_HEIGHT_E * rowScale; 522 | } 523 | 524 | if(currentRowY > SCREEN_HEIGHT) { 525 | box_region_max_y = currentRowY; 526 | } 527 | } 528 | 529 | float gametitle_fade = 0.0f; 530 | SquareSprite gametitle_fade_sprite; 531 | float gametitle_fade_vertex[8]; 532 | 533 | void menu_update() { 534 | 535 | menu_sidebar_update(&menu_sidebar); 536 | 537 | int prev_cursor_x = cursor_x; 538 | int prev_cursor_y = cursor_y; 539 | 540 | int limit_reached = 0; 541 | int cursor_move = 0; 542 | 543 | int currentRowCount = title_row_count[cursor_y]; 544 | 545 | if(main_state == 3) { 546 | int input_x = 0; 547 | int input_y = 0; 548 | 549 | if(p1_buttons.d_left) { 550 | input_x = -1; 551 | } 552 | if(p1_buttons.d_right) { 553 | input_x = 1; 554 | } 555 | if(p1_inputs.stick_x < -30 || p1_inputs.stick_x > 30) { 556 | input_x = p1_inputs.stick_x; 557 | } 558 | 559 | if(p1_buttons.d_up) { 560 | input_y = -1; 561 | } 562 | if(p1_buttons.d_down) { 563 | input_y = 1; 564 | } 565 | if(p1_inputs.stick_y < -30 || p1_inputs.stick_y > 30) { 566 | input_y = -p1_inputs.stick_y; 567 | } 568 | 569 | if(input_x < 0) { 570 | if(cursor_x_timer == 0){ 571 | cursor_x--; 572 | } 573 | cursor_x_timer++; 574 | if(cursor_x_timer > CURSOR_INTERVAL) { 575 | cursor_x--; 576 | cursor_x_timer = CURSOR_INTERVAL_SECOND; 577 | } 578 | } else if(input_x > 0) { 579 | if(cursor_x_timer == 0){ 580 | cursor_x++; 581 | } 582 | cursor_x_timer++; 583 | if(cursor_x_timer > CURSOR_INTERVAL) { 584 | cursor_x++; 585 | cursor_x_timer = CURSOR_INTERVAL_SECOND; 586 | } 587 | } else { 588 | cursor_x_timer = 0; 589 | } 590 | if(cursor_x >= 0) { 591 | if(input_y < 0) { 592 | if(cursor_y_timer == 0){ 593 | cursor_y--; 594 | } 595 | cursor_y_timer++; 596 | if(cursor_y_timer > CURSOR_INTERVAL) { 597 | cursor_y--; 598 | cursor_y_timer = CURSOR_INTERVAL_SECOND; 599 | } 600 | } else if(input_y > 0) { 601 | if(cursor_y_timer == 0){ 602 | cursor_y++; 603 | } 604 | cursor_y_timer++; 605 | if(cursor_y_timer > CURSOR_INTERVAL) { 606 | cursor_y++; 607 | cursor_y_timer = CURSOR_INTERVAL_SECOND; 608 | } 609 | } else { 610 | cursor_y_timer = 0; 611 | } 612 | } 613 | } 614 | 615 | if(cursor_x >= 0) { 616 | if(cursor_y < 0) { 617 | if(prev_cursor_y != cursor_y) limit_reached = 1; 618 | cursor_y = 0; 619 | } else if (cursor_y >= TITLE_ROWS) { 620 | if(prev_cursor_y != cursor_y) limit_reached = 1; 621 | cursor_y = TITLE_ROWS-1; 622 | } 623 | 624 | // Next row? 625 | int rowCount = title_row_count[cursor_y]; 626 | if(rowCount == 0) { 627 | cursor_y--; 628 | limit_reached = 1; 629 | rowCount = title_row_count[cursor_y]; 630 | } else { 631 | if(prev_cursor_y != cursor_y) cursor_move = 1; 632 | } 633 | 634 | 635 | // Select most fitting x based on title sizes 636 | if(rowCount > 1) { 637 | float nextFitting = ((float)cursor_x + 0.5f) / (float)currentRowCount; 638 | cursor_x = nextFitting * rowCount; 639 | } 640 | 641 | if(cursor_x < 0) { 642 | if(prev_cursor_x != cursor_x) { 643 | limit_reached = 1; 644 | } 645 | cursor_x = 0; 646 | 647 | } else if(cursor_x >= rowCount) { 648 | if(prev_cursor_x != cursor_x) { 649 | limit_reached = 1; 650 | } 651 | cursor_x = rowCount - 1; 652 | } else { 653 | if(prev_cursor_x != cursor_x) { 654 | cursor_move = 1; 655 | } 656 | 657 | } 658 | } else { 659 | // Lateral menu activated 660 | if(cursor_x < -1) { 661 | cursor_x = -1; 662 | } 663 | } 664 | 665 | 666 | if(limit_reached) { 667 | wav64_play(&se_cursor_ng, CHANNEL_SFX1); 668 | } 669 | if(cursor_move) { 670 | wav64_play(&se_gametitle_cursor, CHANNEL_SFX1); 671 | } 672 | 673 | if(cursor_x >= 0) { 674 | TitleBox * cur = title_row[cursor_y][cursor_x]; 675 | if(cur != NULL) { 676 | if(selectedTitle != NULL) { 677 | selectedTitle->isSelected = 0; 678 | } 679 | cur->isSelected = 1; 680 | selectedTitle = cur; 681 | } 682 | } else { 683 | if(selectedTitle != NULL) { 684 | selectedTitle->isSelected = 0; 685 | } 686 | selectedTitle = NULL; 687 | } 688 | 689 | if(cursor_x >= 0 && selectedTitle && main_state == 3) { 690 | if(p1_buttons_press.a) { 691 | main_state = 4; 692 | selectedTitle->scaleGrow = 0.0f; 693 | wav64_play(&se_gametitle_click, CHANNEL_SFX1); 694 | } 695 | } 696 | 697 | 698 | 699 | for(int y = 0; y < TITLE_ROWS; y++) { 700 | for(int x = 0; x < TITLE_COLUMNS; x++) { 701 | TitleBox * current = title_row[y][x]; 702 | if(current == NULL) continue; 703 | TitleBox_update(current); 704 | } 705 | } 706 | 707 | if(selectedTitle) { 708 | float viewMidpoint = 240.0f; 709 | float midpointY = selectedTitle->sprite.y + (selectedTitle->sprite.height * 0.5); 710 | viewport_y_target = midpointY - viewMidpoint; 711 | 712 | if(viewport_y_target < box_region_min_y) viewport_y_target = box_region_min_y; 713 | if((viewport_y_target + (BOX_REGION_Y_MAX - BOX_REGION_Y_MIN)) > box_region_max_y) viewport_y_target = box_region_max_y - (BOX_REGION_Y_MAX - BOX_REGION_Y_MIN); 714 | viewport_y_target = round(viewport_y_target); 715 | } 716 | 717 | float vydiff = viewport_y_target - viewport_y; 718 | if(abs(vydiff) > 0.1f) { 719 | viewport_y += (viewport_y_target - viewport_y) * 0.4f; 720 | } else { 721 | viewport_y = viewport_y_target; 722 | } 723 | 724 | viewport_y_bottom = viewport_y + (BOX_REGION_Y_MAX - BOX_REGION_Y_MIN); 725 | 726 | // fade out 727 | if(main_state > 3) { 728 | switch(main_state) { 729 | case 4: 730 | gametitle_fade += 1.0f / 15.0f; 731 | if(gametitle_fade >= 1.0f) { 732 | gametitle_fade = 1.0f; 733 | main_state = 5; 734 | } 735 | break; 736 | case 5: 737 | setupRomLoad(selectedTitle); 738 | if(p1_buttons_press.b) { 739 | main_state = 6; 740 | spinner_fade = 0.0f; 741 | spinner_fade_counter = 0; 742 | spinner_counter = 0.0f; 743 | } 744 | break; 745 | case 6: 746 | gametitle_fade -= 1.0f / 20.0f; 747 | if(gametitle_fade <= 0.0f) { 748 | gametitle_fade = 0.0f; 749 | main_state = 3; 750 | } 751 | break; 752 | } 753 | float midpointX = selectedTitle->sprite.x + (selectedTitle->sprite.width * selectedTitle->scale) * 0.5; 754 | float midpointY = selectedTitle->sprite.y + (selectedTitle->sprite.height * selectedTitle->scale) * 0.5; 755 | midpointY -= (viewport_y - BOX_REGION_Y_MIN); 756 | gametitle_fade_sprite.x = lerp(gametitle_fade, midpointX - 32.0f, 0.0f); 757 | gametitle_fade_sprite.y = lerp(gametitle_fade, midpointY - 32.0f, 0.0f); 758 | gametitle_fade_sprite.width = lerp(gametitle_fade, 64.0f, 640.0f); 759 | gametitle_fade_sprite.height = lerp(gametitle_fade, 64.0f, 480.0f); 760 | vertex_set_from_xywh(&gametitle_fade_sprite, gametitle_fade_vertex); 761 | } 762 | 763 | 764 | } 765 | void menu_draw() { 766 | rdpq_set_mode_standard(); 767 | rdpq_mode_combiner(RDPQ_COMBINER_FLAT); 768 | 769 | if(main_state != 5) { 770 | int minSciss = opening_card_offset_x < menu_sidebar.sprite.width ? opening_card_offset_x : menu_sidebar.sprite.width; 771 | rdpq_set_scissor(minSciss,BOX_REGION_Y_MIN,BOX_REGION_X_MAX,BOX_REGION_Y_MAX); 772 | 773 | rdpq_mode_combiner(RDPQ_COMBINER_TEX_FLAT); 774 | 775 | rdpq_mode_filter(FILTER_BILINEAR); 776 | 777 | for(int y = 0; y < TITLE_ROWS; y++) { 778 | for(int x = 0; x < TITLE_COLUMNS; x++) { 779 | TitleBox * current = title_row[y][x]; 780 | if(current == NULL || current == selectedTitle) continue; 781 | TitleBox_draw(current); 782 | } 783 | } 784 | if(selectedTitle != NULL) { 785 | // Draw last 786 | TitleBox_draw(selectedTitle); 787 | } 788 | 789 | rdpq_set_scissor(BOX_REGION_Y_MIN,BOX_REGION_Y_MIN,BOX_REGION_X_MAX,BOX_REGION_Y_MAX); 790 | 791 | rdpq_set_mode_standard(); 792 | rdpq_mode_combiner(RDPQ_COMBINER_FLAT); 793 | 794 | menu_sidebar_draw(&menu_sidebar); 795 | 796 | } 797 | 798 | if(main_state > 3) { 799 | float gametitle_fade_c = gametitle_fade * 255.0f; 800 | rdpq_set_mode_standard(); 801 | rdpq_mode_combiner(RDPQ_COMBINER_FLAT); 802 | rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY); 803 | rdpq_set_prim_color(RGBA32(0, 0, 0, gametitle_fade_c)); 804 | rdpq_triangle(&TRIFMT_FILL, &gametitle_fade_vertex[0], &gametitle_fade_vertex[2], &gametitle_fade_vertex[4]); 805 | rdpq_triangle(&TRIFMT_FILL, &gametitle_fade_vertex[0], &gametitle_fade_vertex[6], &gametitle_fade_vertex[2]); 806 | if(main_state == 5) { 807 | spinner_fade_counter++; 808 | if(spinner_fade_counter >= 10) { 809 | spinner_fade = (spinner_fade_counter - 10) / 30.0f; 810 | if(spinner_fade > 1.0f) spinner_fade = 1.0f; 811 | 812 | spinner_draw(640 - 55, 480 - 48, 20, spinner_fade); 813 | } 814 | } 815 | } 816 | } 817 | 818 | void update(int ovfl) { 819 | switch(fade_state) { 820 | case 0: 821 | fade_lvl = fade_counter / FADE_DURATION; 822 | fade_counter--; 823 | if(fade_counter == 0) fade_state = 1; 824 | break; 825 | default: 826 | break; 827 | } 828 | 829 | if(fade_state == 1 && main_state == 0) { 830 | main_state = 1; 831 | } 832 | switch(main_state) { 833 | default: case 0: break; // Wait Fade 834 | case 1: // Wait logo 835 | opening_counter--; 836 | if(opening_counter == OPENING_SOUND_PLAY) { 837 | 838 | wav64_play(&se_titlelogo, CHANNEL_SFX1); 839 | } 840 | if(opening_counter == 0) { 841 | main_state = 2; 842 | load_titles(); 843 | opening_counter = OPENING_OUT; 844 | } 845 | break; 846 | case 2: // exit logo out of the screen 847 | opening_card_offset_x = ((1.0f - opening_counter / OPENING_OUT) * 640.0f); 848 | opening_counter--; 849 | if(opening_counter == 0) { 850 | main_state = 3; 851 | sprite_free(logo); 852 | } 853 | break; 854 | } 855 | 856 | if(main_state >= 2) menu_update(); 857 | } 858 | 859 | void draw(int cur_frame) { 860 | surface_t *disp = display_get(); 861 | rdpq_attach(disp, NULL); 862 | 863 | rdpq_set_scissor(SCR_REGION_X_MIN,BOX_REGION_Y_MIN,BOX_REGION_X_MAX,BOX_REGION_Y_MAX); 864 | 865 | if(main_state >= 2) { 866 | rdpq_set_mode_fill(RGBA32(48,48,48,0)); 867 | rdpq_fill_rectangle(64, BOX_REGION_Y_MIN, BOX_REGION_X_MAX, BOX_REGION_Y_MAX); 868 | menu_draw(); 869 | } 870 | 871 | if(main_state < 3) { 872 | rdpq_set_mode_fill(RGBA32(255,0,0,0)); 873 | rdpq_fill_rectangle(SCR_REGION_X_MIN, BOX_REGION_Y_MIN, 640 - opening_card_offset_x, BOX_REGION_Y_MAX); 874 | 875 | rdpq_set_mode_copy(true); 876 | 877 | rdpq_sprite_blit(logo, 194 - opening_card_offset_x, 112, &(rdpq_blitparms_t){ 878 | .scale_x = 1, .scale_y = 1, 879 | }); 880 | } 881 | 882 | // Draw Fade 883 | if(fade_state != 1) { 884 | rdpq_set_mode_standard(); 885 | rdpq_mode_dithering(DITHER_NOISE_NOISE); 886 | rdpq_mode_combiner(RDPQ_COMBINER_FLAT); 887 | rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY); 888 | rdpq_set_prim_color(RGBA32(0, 0, 0, fade_lvl * 255)); 889 | rdpq_triangle(&TRIFMT_FILL, fade_v1, fade_v2, fade_v3); 890 | rdpq_triangle(&TRIFMT_FILL, fade_v1, fade_v4, fade_v2); 891 | } 892 | 893 | // For measuring performance 894 | /*float fps = display_get_fps(); 895 | rdpq_text_printf(&(rdpq_textparms_t){ 896 | .align = ALIGN_LEFT, 897 | .width = 400, 898 | }, 1, 32, 55, "FPS: %.4f", fps);*/ 899 | 900 | rdpq_detach_show(); 901 | } 902 | 903 | int main(void) 904 | { 905 | /* Initialize peripherals */ 906 | display_init(RESOLUTION_640x480, DEPTH_16_BPP, 3, GAMMA_NONE, FILTERS_DEDITHER); 907 | dfs_init( DFS_DEFAULT_LOCATION ); 908 | debug_init_sdfs("sd:/", -1); 909 | rdpq_init(); 910 | joypad_init(); 911 | timer_init(); 912 | 913 | audio_init(44100, 4); 914 | mixer_init(20); 915 | 916 | wav64_open(&se_titlelogo, "rom:/se/titlelogo.wav64"); 917 | wav64_open(&se_cursor_ng, "rom:/se/cursor_ng.wav64"); 918 | wav64_open(&se_gametitle_cursor, "rom:/se/gametitle_cursor.wav64"); 919 | wav64_open(&se_filemenu_close, "rom:/se/filemenu_close.wav64"); 920 | wav64_open(&se_filemenu_open, "rom:/se/filemenu_open.wav64"); 921 | wav64_open(&se_gametitle_click, "rom:/se/gametitle_click.wav64"); 922 | 923 | logo = sprite_load("rom:/logo.sprite"); 924 | shadow = sprite_load("rom:/shadow.sprite"); 925 | loading_dot = sprite_load("rom:/loading_dot.sprite"); 926 | font = rdpq_font_load("rom:/default.font64"); 927 | rdpq_text_register_font(1, font); 928 | 929 | 930 | menu_sidebar.sprite.x = 0; 931 | menu_sidebar.sprite.y = 0; 932 | menu_sidebar.sprite.width = 64; 933 | menu_sidebar.sprite.height = 480; 934 | menu_sidebar.width = 64.0f; 935 | menu_sidebar.isOpen = 0; 936 | menu_sidebar.animCounter = 0; 937 | 938 | flashcart_init(); 939 | 940 | int cur_frame = 0; 941 | menu_active = 1; 942 | while(menu_active) { 943 | update(0); 944 | draw(cur_frame); 945 | joypad_poll(); 946 | p1_buttons = joypad_get_buttons(JOYPAD_PORT_1); 947 | p1_buttons_press = joypad_get_buttons_pressed(JOYPAD_PORT_1); 948 | p1_inputs = joypad_get_inputs(JOYPAD_PORT_1); 949 | mixer_try_play(); 950 | cur_frame++; 951 | } 952 | 953 | flashcart_deinit(); 954 | 955 | 956 | mixer_close(); 957 | audio_close(); 958 | 959 | rdpq_close(); 960 | rspq_close(); 961 | timer_close(); 962 | joypad_close(); 963 | 964 | display_close(); 965 | 966 | disable_interrupts(); 967 | 968 | boot(&boot_params); 969 | 970 | assertf(false, "Unexpected return from 'boot' function"); 971 | 972 | while (true) { 973 | 974 | } 975 | } -------------------------------------------------------------------------------- /src/utils/fs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "fs.h" 8 | #include "utils.h" 9 | 10 | 11 | char *strip_sd_prefix (char *path) { 12 | const char *prefix = "sd:/"; 13 | 14 | char *found = strstr(path, prefix); 15 | if (found) { 16 | return found + strlen(prefix) - 1; 17 | } 18 | 19 | return path; 20 | } 21 | 22 | 23 | bool file_exists (char *path) { 24 | FRESULT fr; 25 | FILINFO fno; 26 | 27 | fr = f_stat(strip_sd_prefix(path), &fno); 28 | 29 | return ((fr == FR_OK) && (!(fno.fattrib & AM_DIR))); 30 | } 31 | 32 | 33 | size_t file_get_size (char *path) { 34 | FILINFO fno; 35 | 36 | if (f_stat(strip_sd_prefix(path), &fno) != FR_OK) { 37 | return 0; 38 | } 39 | 40 | return (size_t) (fno.fsize); 41 | } 42 | 43 | bool file_delete (char *path) { 44 | if (file_exists(path)) { 45 | return (f_unlink(strip_sd_prefix(path)) != FR_OK); 46 | } 47 | return false; 48 | } 49 | 50 | bool file_allocate (char *path, size_t size) { 51 | FIL fil; 52 | bool error = false; 53 | 54 | if (f_open(&fil, strip_sd_prefix(path), FA_WRITE | FA_CREATE_NEW) != FR_OK) { 55 | return true; 56 | } 57 | 58 | if (f_lseek(&fil, size) != FR_OK) { 59 | error = true; 60 | } 61 | 62 | if (f_tell(&fil) != size) { 63 | error = true; 64 | } 65 | 66 | if (f_close(&fil) != FR_OK) { 67 | error = true; 68 | } 69 | 70 | return error; 71 | } 72 | 73 | bool file_fill (char *path, uint8_t value) { 74 | FIL fil; 75 | bool error = false; 76 | uint8_t buffer[FS_SECTOR_SIZE * 8]; 77 | FRESULT res; 78 | UINT bytes_to_write; 79 | UINT bytes_written; 80 | 81 | for (int i = 0; i < sizeof(buffer); i++) { 82 | buffer[i] = value; 83 | } 84 | 85 | if (f_open(&fil, strip_sd_prefix(path), FA_WRITE) != FR_OK) { 86 | return true; 87 | } 88 | 89 | for (int i = 0; i < f_size(&fil); i += sizeof(buffer)) { 90 | bytes_to_write = MIN(f_size(&fil) - f_tell(&fil), sizeof(buffer)); 91 | res = f_write(&fil, buffer, bytes_to_write, &bytes_written); 92 | if ((res != FR_OK) || (bytes_to_write != bytes_written)) { 93 | error = true; 94 | break; 95 | } 96 | } 97 | 98 | if (f_tell(&fil) != f_size(&fil)) { 99 | error = true; 100 | } 101 | 102 | if (f_close(&fil) != FR_OK) { 103 | error = true; 104 | } 105 | 106 | return error; 107 | } 108 | 109 | bool file_get_sectors (char *path, void (*callback) (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size)) { 110 | FATFS *fs; 111 | FIL fil; 112 | bool error = false; 113 | 114 | if (!callback) { 115 | return true; 116 | } 117 | 118 | if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) { 119 | return true; 120 | } 121 | 122 | fs = fil.obj.fs; 123 | 124 | uint32_t sector_count = (ALIGN(f_size(&fil), FS_SECTOR_SIZE) / FS_SECTOR_SIZE); 125 | 126 | uint32_t cluster_sector = 0; 127 | 128 | for (int file_sector = 0; file_sector < sector_count; file_sector += fs->csize) { 129 | if ((f_lseek(&fil, (file_sector * FS_SECTOR_SIZE) + (FS_SECTOR_SIZE / 2))) != FR_OK) { 130 | error = true; 131 | break; 132 | } 133 | uint32_t cluster = fil.clust; 134 | if (cluster >= fs->n_fatent) { 135 | error = true; 136 | break; 137 | } 138 | cluster_sector = (fs->database + ((LBA_t) (fs->csize) * (cluster - 2))); 139 | callback(sector_count, file_sector, cluster_sector, fs->csize); 140 | } 141 | 142 | if (f_close(&fil) != FR_OK) { 143 | error = true; 144 | } 145 | 146 | return error; 147 | } 148 | 149 | bool file_has_extensions (char *path, const char *extensions[]) { 150 | char *ext = strrchr(path, '.'); 151 | 152 | if (ext == NULL) { 153 | return false; 154 | } 155 | 156 | while (*extensions != NULL) { 157 | if (strcasecmp(ext + 1, *extensions) == 0) { 158 | return true; 159 | } 160 | extensions++; 161 | } 162 | 163 | return false; 164 | } 165 | 166 | 167 | bool directory_exists (char *path) { 168 | FRESULT fr; 169 | FILINFO fno; 170 | 171 | fr = f_stat(strip_sd_prefix(path), &fno); 172 | 173 | return ((fr == FR_OK) && (fno.fattrib & AM_DIR)); 174 | } 175 | 176 | bool directory_delete (char *path) { 177 | if (directory_exists(path)) { 178 | return (f_unlink(strip_sd_prefix(path)) != FR_OK); 179 | } 180 | 181 | return false; 182 | } 183 | 184 | bool directory_create (char *path) { 185 | bool error = false; 186 | 187 | if (directory_exists(path)) { 188 | return false; 189 | } 190 | 191 | char *directory = strdup(strip_sd_prefix(path)); 192 | char *separator = directory; 193 | 194 | do { 195 | separator = strchr(separator, '/'); 196 | 197 | if (separator != NULL) { 198 | *separator++ = '\0'; 199 | } 200 | 201 | if (directory[0] != '\0') { 202 | FRESULT res = f_mkdir(directory); 203 | if ((res != FR_OK) && (res != FR_EXIST)) { 204 | error = true; 205 | break; 206 | } 207 | } 208 | 209 | if (separator != NULL) { 210 | *(separator - 1) = '/'; 211 | } 212 | } while (separator != NULL); 213 | 214 | free(directory); 215 | 216 | return error; 217 | } 218 | -------------------------------------------------------------------------------- /src/utils/fs.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_FS_H__ 2 | #define UTILS_FS_H__ 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | #define FS_SECTOR_SIZE (512) 11 | 12 | 13 | char *strip_sd_prefix (char *path); 14 | 15 | bool file_exists (char *path); 16 | size_t file_get_size (char *path); 17 | bool file_delete (char *path); 18 | bool file_allocate (char *path, size_t size); 19 | bool file_fill (char *path, uint8_t value); 20 | bool file_get_sectors (char *path, void (*callback) (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size)); 21 | bool file_has_extensions (char *path, const char *extensions[]); 22 | 23 | bool directory_exists (char *path); 24 | bool directory_delete (char *path); 25 | bool directory_create (char *path); 26 | 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/utils/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H__ 2 | #define UTILS_H__ 3 | 4 | 5 | #define ALIGN(x, a) (((x) + ((typeof(x))(a) - 1)) & ~((typeof(x))(a) - 1)) 6 | 7 | #define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; }) 8 | #define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; }) 9 | 10 | #define KiB(x) ((x) * 1024) 11 | #define MiB(x) ((x) * 1024 * 1024) 12 | 13 | 14 | #endif 15 | --------------------------------------------------------------------------------