mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2024-11-24 19:46:54 +01:00
Merge remote-tracking branch 'upstream/main' into ed64-basic
This commit is contained in:
commit
7abb7e6e8b
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@ -68,12 +68,13 @@ jobs:
|
||||
name: SC64
|
||||
path: ./output/sc64menu.n64
|
||||
|
||||
- name: Delete rolling-release tag and release
|
||||
uses: dev-drprasad/delete-tag-and-release@v1.0
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag_name: rolling-release
|
||||
# - name: Delete rolling-release tag and release
|
||||
# uses: dev-drprasad/delete-tag-and-release@v1.0
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
# with:
|
||||
# github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# tag_name: rolling-release
|
||||
# continue-on-error: true
|
||||
|
||||
- name: Upload rolling release
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
@ -88,6 +89,7 @@ jobs:
|
||||
./output/OS64.v64
|
||||
./output/OS64P.v64
|
||||
./output/sc64menu.n64
|
||||
continue-on-error: true
|
||||
|
||||
generate-docs:
|
||||
runs-on: ubuntu-latest
|
||||
|
3
Makefile
3
Makefile
@ -16,6 +16,8 @@ SRCS = \
|
||||
boot/boot.c \
|
||||
boot/crc32.c \
|
||||
boot/reboot.S \
|
||||
flashcart/64drive/64drive_ll.c \
|
||||
flashcart/64drive/64drive.c \
|
||||
flashcart/flashcart_utils.c \
|
||||
flashcart/flashcart.c \
|
||||
flashcart/sc64/sc64_ll.c \
|
||||
@ -44,6 +46,7 @@ SRCS = \
|
||||
menu/rom_database.c \
|
||||
menu/settings.c \
|
||||
menu/sound.c \
|
||||
menu/usb_comm.c \
|
||||
menu/views/browser.c \
|
||||
menu/views/credits.c \
|
||||
menu/views/error.c \
|
||||
|
16
README.md
16
README.md
@ -23,10 +23,13 @@ An open source menu for N64 flashcarts.
|
||||
|
||||
## Getting started
|
||||
|
||||
### 64drive
|
||||
Ensure the cart has the latest [firmware](https://64drive.retroactive.be/support.php) installed.
|
||||
Download the latest `menu.bin` file from the releases page, then put it in the root directory of your SD card.
|
||||
|
||||
### SC64
|
||||
Ensure the cart is running the latest [firmware](https://github.com/Polprzewodnikowy/SummerCart64/releases/latest).
|
||||
Download the `sc64menu.n64` ROM from the latest action run assets.
|
||||
Add it to the root folder on your SD card.
|
||||
Ensure the cart has the latest [firmware](https://github.com/Polprzewodnikowy/SummerCart64/releases/latest) installed.
|
||||
Download the latest `sc64menu.n64` file from the releases page, then put it in the root directory of your SD card.
|
||||
|
||||
### ED64
|
||||
Download the `ED64.v64` ROM from the latest action run assets and place it in the `/ED64` folder.
|
||||
@ -39,8 +42,10 @@ Download the `ED64P.v64` ROM from the latest action run assets and place it in t
|
||||
|
||||
#### ROM Boxart
|
||||
To use boxart, you need to place png files of size 158x112 in the folder `/menu/boxart` on the SD card.
|
||||
Each file must be named according to the 2 letter ROM ID. e.g. for goldeneye, this would be `GE.png`.
|
||||
A known set of PNG files can be downloaded [here](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w).
|
||||
Each file must be named according to the 2 letter ROM ID, or 3 letter ROM ID including media type.
|
||||
i.e. for GoldenEye 2 letters, this would be `GE.png`.
|
||||
i.e. for GoldenEye 3 letters, this would be `NGE.png`.
|
||||
A known set of PNG files using 2 letter ID's can be downloaded [here](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w).
|
||||
|
||||
#### Emulator support
|
||||
Emulators should be added to the `/emulators` directory on the SD card.
|
||||
@ -49,6 +54,7 @@ Menu currently supports the following emulators and associated ROM file names:
|
||||
- **NES**: [neon64v2](https://github.com/hcs64/neon64v2) by *hcs64* - `neon64bu.rom`
|
||||
- **SNES**: [sodium64](https://github.com/Hydr8gon/sodium64) by *Hydr8gon* - `sodium64.z64`
|
||||
- **Game Boy** / **GB Color**: [gb64](https://lambertjamesd.github.io/gb64/romwrapper/romwrapper.html) by *lambertjamesd* - `gb.v64` / `gbc.v64`
|
||||
- **Sega Master System** / **Sega Game Gear** / **Sg1000**: [TotalSMS](https://github.com/ITotalJustice/TotalSMS) - `TotalSMS.z64` (Currently broken)
|
||||
|
||||
|
||||
# Developer documentation
|
||||
|
@ -21,8 +21,14 @@ echo !!! Now toggle power to the N64 !!!
|
||||
echo:
|
||||
echo:
|
||||
|
||||
if not "%1" == "/d" goto :exit
|
||||
if not "%1" == "/d" goto :not_d
|
||||
|
||||
%~dp0tools\sc64\sc64deployer debug --no-writeback
|
||||
|
||||
:exit
|
||||
:not_d
|
||||
|
||||
if not "%1" == "/du" goto :not_du
|
||||
|
||||
%~dp0tools\sc64\sc64deployer debug --no-writeback --init "send-file /sc64menu.n64 @output/sc64menu.n64@;reboot"
|
||||
|
||||
:not_du
|
||||
|
@ -4,28 +4,12 @@ set -e
|
||||
|
||||
REMOTE="--remote ${REMOTE:-host.docker.internal:9064}"
|
||||
|
||||
## FIXME: this does not work!
|
||||
# Make sure we are connected
|
||||
#echo Detecting SC64...
|
||||
#sc64deployer $REMOTE list
|
||||
|
||||
# Get the information
|
||||
echo SC64 info...:
|
||||
sc64deployer $REMOTE info
|
||||
echo
|
||||
echo
|
||||
|
||||
# Load the ROM
|
||||
echo Loading ROM...:
|
||||
sc64deployer $REMOTE upload ./output/N64FlashcartMenu.n64
|
||||
|
||||
echo
|
||||
echo
|
||||
# Toggle the power of the N64 to boot the ROM.
|
||||
echo !!! Now toggle power to the N64 !!!
|
||||
echo
|
||||
echo
|
||||
|
||||
if [ "$1" = "-d" ]; then
|
||||
sc64deployer $REMOTE debug --no-writeback
|
||||
fi
|
||||
|
||||
if [ "$1" = "-du" ]; then
|
||||
sc64deployer $REMOTE debug --no-writeback --init "send-file /sc64menu.n64 @output/sc64menu.n64@;reboot"
|
||||
fi
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
extern uint32_t reboot_start __attribute__((section(".text")));
|
||||
extern size_t reboot_size __attribute__((section(".text")));
|
||||
extern int reboot_entry_offset __attribute__((section(".text")));
|
||||
|
||||
|
||||
typedef struct {
|
||||
@ -88,7 +87,23 @@ void boot (boot_params_t *params) {
|
||||
|
||||
while (!(cpu_io_read(&SP->SR) & SP_SR_HALT));
|
||||
|
||||
cpu_io_write(&SP->SR, SP_SR_CLR_INTR | SP_SR_SET_HALT);
|
||||
cpu_io_write(&SP->SR,
|
||||
SP_SR_CLR_SIG7 |
|
||||
SP_SR_CLR_SIG6 |
|
||||
SP_SR_CLR_SIG5 |
|
||||
SP_SR_CLR_SIG4 |
|
||||
SP_SR_CLR_SIG3 |
|
||||
SP_SR_CLR_SIG2 |
|
||||
SP_SR_CLR_SIG1 |
|
||||
SP_SR_CLR_SIG0 |
|
||||
SP_SR_CLR_INTR_BREAK |
|
||||
SP_SR_CLR_SSTEP |
|
||||
SP_SR_CLR_INTR |
|
||||
SP_SR_CLR_BROKE |
|
||||
SP_SR_SET_HALT
|
||||
);
|
||||
cpu_io_write(&SP->SEMAPHORE, 0);
|
||||
cpu_io_write(&SP->PC, 0);
|
||||
|
||||
while (cpu_io_read(&SP->DMA_BUSY));
|
||||
|
||||
@ -134,15 +149,12 @@ void boot (boot_params_t *params) {
|
||||
cpu_io_write(&ipl3_dst[i], io_read((uint32_t) (&ipl3_src[i])));
|
||||
}
|
||||
|
||||
register void (*entry_point)(void) asm ("t3");
|
||||
register uint32_t boot_device asm ("s3");
|
||||
register uint32_t tv_type asm ("s4");
|
||||
register uint32_t reset_type asm ("s5");
|
||||
register uint32_t cic_seed asm ("s6");
|
||||
register uint32_t version asm ("s7");
|
||||
void *stack_pointer;
|
||||
|
||||
entry_point = (void (*)(void)) UNCACHED(&SP_MEM->IMEM[(int) (&reboot_entry_offset)]);
|
||||
boot_device = (params->device_type & 0x01);
|
||||
tv_type = (params->tv_type & 0x03);
|
||||
reset_type = BOOT_RESET_TYPE_COLD;
|
||||
@ -151,18 +163,16 @@ void boot (boot_params_t *params) {
|
||||
: (params->tv_type == BOOT_TV_TYPE_NTSC) ? 1
|
||||
: (params->tv_type == BOOT_TV_TYPE_MPAL) ? 4
|
||||
: 0;
|
||||
stack_pointer = (void *) UNCACHED(&SP_MEM->IMEM[1020]);
|
||||
|
||||
asm volatile (
|
||||
"move $sp, %[stack_pointer] \n"
|
||||
"jr %[entry_point] \n" ::
|
||||
[entry_point] "r" (entry_point),
|
||||
"la $t3, reboot \n"
|
||||
"jr $t3 \n" ::
|
||||
[boot_device] "r" (boot_device),
|
||||
[tv_type] "r" (tv_type),
|
||||
[reset_type] "r" (reset_type),
|
||||
[cic_seed] "r" (cic_seed),
|
||||
[version] "r" (version),
|
||||
[stack_pointer] "r" (stack_pointer)
|
||||
[version] "r" (version) :
|
||||
"t3"
|
||||
);
|
||||
|
||||
while (1);
|
||||
|
@ -37,6 +37,8 @@ typedef struct {
|
||||
io32_t DMA_FULL;
|
||||
io32_t DMA_BUSY;
|
||||
io32_t SEMAPHORE;
|
||||
io32_t __reserved[0xFFF8];
|
||||
io32_t PC;
|
||||
} sp_regs_t;
|
||||
|
||||
#define SP_BASE (0x04040000UL)
|
||||
|
@ -1,3 +1,7 @@
|
||||
#define IPL3_ENTRY 0xA4000040
|
||||
#define REBOOT_ADDRESS 0xA4001000
|
||||
#define STACK_ADDRESS 0xA4001FF0
|
||||
|
||||
#define RI_ADDRESS 0xA4700000
|
||||
|
||||
#define RI_MODE 0x00
|
||||
@ -13,8 +17,6 @@
|
||||
#define RDRAM_RESET_DELAY 1024
|
||||
#define RDRAM_STANDBY_DELAY 512
|
||||
|
||||
#define IPL3_ENTRY 0xA4000040
|
||||
|
||||
.set noat
|
||||
.section .text.reboot, "ax", %progbits
|
||||
.type reboot, %object
|
||||
@ -36,8 +38,10 @@ ipl2:
|
||||
.set reorder
|
||||
|
||||
reboot_entry:
|
||||
.equ reboot_entry_offset, ((. - reboot_start) / 4)
|
||||
.global reboot_entry_offset
|
||||
.set reboot, REBOOT_ADDRESS + (. - reboot_start)
|
||||
.global reboot
|
||||
|
||||
li $sp, STACK_ADDRESS
|
||||
|
||||
bnez $s5, reset_rdram_skip
|
||||
|
||||
@ -66,6 +70,12 @@ reset_rdram:
|
||||
bnez $t2, 1b
|
||||
reset_rdram_skip:
|
||||
|
||||
prepare_registers:
|
||||
la $t0, ra_table
|
||||
sll $t1, $s4, 2
|
||||
add $t0, $t1
|
||||
lw $ra, ($t0)
|
||||
|
||||
move $at, $zero
|
||||
move $v0, $zero
|
||||
move $v1, $zero
|
||||
@ -76,7 +86,6 @@ reset_rdram_skip:
|
||||
move $t0, $zero
|
||||
move $t1, $zero
|
||||
move $t2, $zero
|
||||
move $t3, $zero
|
||||
move $t4, $zero
|
||||
move $t5, $zero
|
||||
move $t6, $zero
|
||||
@ -90,13 +99,16 @@ reset_rdram_skip:
|
||||
move $k1, $zero
|
||||
move $gp, $zero
|
||||
move $s8, $zero
|
||||
move $ra, $zero
|
||||
|
||||
mtc0 $zero, $9
|
||||
|
||||
run_ipl3:
|
||||
li $t3, IPL3_ENTRY
|
||||
jr $t3
|
||||
|
||||
.equ reboot_size, (. - reboot_start)
|
||||
ra_values:
|
||||
.set ra_table, REBOOT_ADDRESS + (. - reboot_start)
|
||||
.word 0xA4001554
|
||||
.word 0xA4001550
|
||||
.word 0xA4001554
|
||||
|
||||
.set reboot_size, (. - reboot_start)
|
||||
.global reboot_size
|
||||
|
271
src/flashcart/64drive/64drive.c
Normal file
271
src/flashcart/64drive/64drive.c
Normal file
@ -0,0 +1,271 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <fatfs/ff.h>
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "utils/fs.h"
|
||||
#include "utils/utils.h"
|
||||
|
||||
#include "../flashcart_utils.h"
|
||||
#include "64drive_ll.h"
|
||||
#include "64drive.h"
|
||||
|
||||
|
||||
#define ROM_ADDRESS (0x10000000)
|
||||
#define SAVE_ADDRESS_DEV_A (0x13FE0000)
|
||||
#define SAVE_ADDRESS_DEV_A_PKST2 (0x11606560)
|
||||
#define SAVE_ADDRESS_DEV_B (0x1FFC0000)
|
||||
|
||||
#define SUPPORTED_FPGA_REVISION (205)
|
||||
|
||||
|
||||
static d64_device_variant_t device_variant = DEVICE_VARIANT_UNKNOWN;
|
||||
static d64_save_type_t current_save_type = SAVE_TYPE_NONE;
|
||||
|
||||
|
||||
static flashcart_error_t d64_init (void) {
|
||||
uint16_t fpga_revision;
|
||||
uint32_t bootloader_version;
|
||||
|
||||
if (d64_ll_enable_extended_mode(false)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_get_version(&device_variant, &fpga_revision, &bootloader_version)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
if (fpga_revision < SUPPORTED_FPGA_REVISION) {
|
||||
return FLASHCART_ERROR_OUTDATED;
|
||||
}
|
||||
|
||||
if (d64_ll_enable_save_writeback(false)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_set_save_type(SAVE_TYPE_NONE)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
current_save_type = SAVE_TYPE_NONE;
|
||||
|
||||
if (d64_ll_enable_cartrom_writes(true)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_set_persistent_variable_storage(false, 0, 0)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_deinit (void) {
|
||||
if (d64_ll_enable_cartrom_writes(false)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_load_rom (char *rom_path, flashcart_progress_callback_t *progress) {
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(rom_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
|
||||
size_t rom_size = f_size(&fil);
|
||||
|
||||
if (rom_size > MiB(64)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
size_t sdram_size = MiB(64);
|
||||
|
||||
size_t chunk_size = MiB(1);
|
||||
for (int offset = 0; offset < sdram_size; offset += chunk_size) {
|
||||
size_t block_size = MIN(sdram_size - offset, chunk_size);
|
||||
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
if (progress) {
|
||||
progress(f_tell(&fil) / (float) (f_size(&fil)));
|
||||
}
|
||||
}
|
||||
if (f_tell(&fil) != rom_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) {
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(file_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
|
||||
size_t file_size = f_size(&fil) - file_offset;
|
||||
|
||||
if (file_size > (MiB(64) - rom_offset)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
}
|
||||
|
||||
if (f_lseek(&fil, file_offset) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
if (f_read(&fil, (void *) (ROM_ADDRESS + rom_offset), file_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
if (br != file_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_load_save (char *save_path) {
|
||||
uint8_t eeprom_contents[2048] __attribute__((aligned(8)));
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
size_t save_size = f_size(&fil);
|
||||
|
||||
bool is_eeprom_save = (current_save_type == SAVE_TYPE_EEPROM_4K || current_save_type == SAVE_TYPE_EEPROM_16K);
|
||||
|
||||
void *address = (void *) (SAVE_ADDRESS_DEV_B);
|
||||
if (is_eeprom_save) {
|
||||
address = eeprom_contents;
|
||||
} else if (device_variant == DEVICE_VARIANT_A) {
|
||||
address = (void *) (SAVE_ADDRESS_DEV_A);
|
||||
if (current_save_type == SAVE_TYPE_FLASHRAM_PKST2) {
|
||||
address = (void *) (SAVE_ADDRESS_DEV_A_PKST2);
|
||||
}
|
||||
}
|
||||
|
||||
if (f_read(&fil, address, save_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
if (is_eeprom_save) {
|
||||
if (d64_ll_write_eeprom_contents(eeprom_contents)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
if (br != save_size) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_set_save_type (flashcart_save_type_t save_type) {
|
||||
d64_save_type_t type;
|
||||
|
||||
switch (save_type) {
|
||||
case FLASHCART_SAVE_TYPE_NONE:
|
||||
type = SAVE_TYPE_NONE;
|
||||
break;
|
||||
case FLASHCART_SAVE_TYPE_EEPROM_4K:
|
||||
type = SAVE_TYPE_EEPROM_4K;
|
||||
break;
|
||||
case FLASHCART_SAVE_TYPE_EEPROM_16K:
|
||||
type = SAVE_TYPE_EEPROM_16K;
|
||||
break;
|
||||
case FLASHCART_SAVE_TYPE_SRAM:
|
||||
type = SAVE_TYPE_SRAM;
|
||||
break;
|
||||
case FLASHCART_SAVE_TYPE_SRAM_BANKED:
|
||||
type = SAVE_TYPE_SRAM_BANKED;
|
||||
break;
|
||||
case FLASHCART_SAVE_TYPE_SRAM_128K:
|
||||
// NOTE: 64drive doesn't support 128 kiB SRAM save type, fallback to 32 kiB SRAM save type
|
||||
type = SAVE_TYPE_SRAM;
|
||||
break;
|
||||
case FLASHCART_SAVE_TYPE_FLASHRAM:
|
||||
type = SAVE_TYPE_FLASHRAM;
|
||||
break;
|
||||
case FLASHCART_SAVE_TYPE_FLASHRAM_PKST2:
|
||||
type = (device_variant == DEVICE_VARIANT_A) ? SAVE_TYPE_FLASHRAM_PKST2 : SAVE_TYPE_FLASHRAM;
|
||||
break;
|
||||
default:
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
}
|
||||
|
||||
if (d64_ll_enable_save_writeback(false)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_set_save_type(type)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
current_save_type = type;
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_set_save_writeback (uint32_t *sectors) {
|
||||
if (d64_ll_write_save_writeback_lba_list(sectors)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_enable_save_writeback(true)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
|
||||
static flashcart_t flashcart_d64 = {
|
||||
.init = d64_init,
|
||||
.deinit = d64_deinit,
|
||||
.load_rom = d64_load_rom,
|
||||
.load_file = d64_load_file,
|
||||
.load_save = d64_load_save,
|
||||
.set_save_type = d64_set_save_type,
|
||||
.set_save_writeback = d64_set_save_writeback,
|
||||
};
|
||||
|
||||
|
||||
flashcart_t *d64_get_flashcart (void) {
|
||||
return &flashcart_d64;
|
||||
}
|
24
src/flashcart/64drive/64drive.h
Normal file
24
src/flashcart/64drive/64drive.h
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @file 64drive.h
|
||||
* @brief 64drive flashcart support
|
||||
* @ingroup flashcart
|
||||
*/
|
||||
|
||||
#ifndef FLASHCART_64DRIVE_H__
|
||||
#define FLASHCART_64DRIVE_H__
|
||||
|
||||
|
||||
#include "../flashcart.h"
|
||||
|
||||
|
||||
/**
|
||||
* @addtogroup 64drive
|
||||
* @{
|
||||
*/
|
||||
|
||||
flashcart_t *d64_get_flashcart (void);
|
||||
|
||||
/** @} */ /* 64drive */
|
||||
|
||||
|
||||
#endif
|
110
src/flashcart/64drive/64drive_ll.c
Normal file
110
src/flashcart/64drive/64drive_ll.c
Normal file
@ -0,0 +1,110 @@
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "../flashcart_utils.h"
|
||||
#include "64drive_ll.h"
|
||||
|
||||
|
||||
#define CI_STATUS_BUSY (1 << 12)
|
||||
|
||||
#define DEVICE_VARIANT_MASK (0xFFFF)
|
||||
#define FPGA_REVISION_MASK (0xFFFF)
|
||||
|
||||
|
||||
typedef enum {
|
||||
CMD_ID_SET_SAVE_TYPE = 0xD0,
|
||||
CMD_ID_DISABLE_SAVE_WRITEBACK = 0xD1,
|
||||
CMD_ID_ENABLE_SAVE_WRITEBACK = 0xD2,
|
||||
CMD_ID_DISABLE_BYTESWAP_ON_LOAD = 0xE0,
|
||||
CMD_ID_ENABLE_BYTESWAP_ON_LOAD = 0xE1,
|
||||
CMD_ID_ENABLE_CARTROM_WRITES = 0xF0,
|
||||
CMD_ID_DISABLE_CARTROM_WRITES = 0xF1,
|
||||
CMD_ID_ENABLE_EXTENDED_MODE = 0xF8,
|
||||
CMD_ID_DISABLE_EXTENDED_MODE = 0xF9,
|
||||
} d64_ci_cmd_id_t;
|
||||
|
||||
|
||||
static d64_regs_t *d64_regs = D64_REGS;
|
||||
|
||||
|
||||
static bool d64_ll_ci_wait (void) {
|
||||
int timeout = 0;
|
||||
do {
|
||||
if (timeout++ >= 0x10000) {
|
||||
return true;
|
||||
}
|
||||
} while (io_read((uint32_t) (&d64_regs->STATUS)) & CI_STATUS_BUSY);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool d64_ll_ci_cmd (d64_ci_cmd_id_t id) {
|
||||
io_write((uint32_t) (&d64_regs->COMMAND), id);
|
||||
return d64_ll_ci_wait();
|
||||
}
|
||||
|
||||
|
||||
bool d64_ll_get_version (d64_device_variant_t *device_variant, uint16_t *fpga_revision, uint32_t *bootloader_version) {
|
||||
if (d64_ll_ci_wait()) {
|
||||
return true;
|
||||
}
|
||||
*device_variant = (d64_device_variant_t) (io_read((uint32_t) (&d64_regs->VARIANT)) & DEVICE_VARIANT_MASK);
|
||||
*fpga_revision = (io_read((uint32_t) (&d64_regs->REVISION)) & FPGA_REVISION_MASK);
|
||||
*bootloader_version = io_read((uint32_t) (&d64_regs->PERSISTENT));
|
||||
return d64_ll_ci_wait();
|
||||
}
|
||||
|
||||
bool d64_ll_set_persistent_variable_storage (bool quick_reboot, d64_tv_type_t force_tv_type, uint8_t cic_seed) {
|
||||
if (d64_ll_ci_wait()) {
|
||||
return true;
|
||||
}
|
||||
io_write((uint32_t) (&d64_regs->PERSISTENT), (quick_reboot << 16) | ((force_tv_type & 0x03) << 8) | (cic_seed & 0xFF));
|
||||
return d64_ll_ci_wait();
|
||||
}
|
||||
|
||||
bool d64_ll_set_save_type (d64_save_type_t save_type) {
|
||||
if (d64_ll_ci_wait()) {
|
||||
return true;
|
||||
}
|
||||
io_write((uint32_t) (&d64_regs->BUFFER), save_type);
|
||||
return d64_ll_ci_cmd(CMD_ID_SET_SAVE_TYPE);
|
||||
}
|
||||
|
||||
bool d64_ll_enable_save_writeback (bool enabled) {
|
||||
if (d64_ll_ci_wait()) {
|
||||
return true;
|
||||
}
|
||||
return d64_ll_ci_cmd(enabled ? CMD_ID_ENABLE_SAVE_WRITEBACK : CMD_ID_DISABLE_SAVE_WRITEBACK);
|
||||
}
|
||||
|
||||
bool d64_ll_enable_cartrom_writes (bool enabled) {
|
||||
if (d64_ll_ci_wait()) {
|
||||
return true;
|
||||
}
|
||||
return d64_ll_ci_cmd(enabled ? CMD_ID_ENABLE_CARTROM_WRITES : CMD_ID_DISABLE_CARTROM_WRITES);
|
||||
}
|
||||
|
||||
bool d64_ll_enable_extended_mode (bool enabled) {
|
||||
d64_ll_ci_wait();
|
||||
if (enabled) {
|
||||
io_write((uint32_t) (&D64_REGS->COMMAND), CMD_ID_ENABLE_EXTENDED_MODE);
|
||||
} else {
|
||||
io_write((uint32_t) (&D64_REGS_EXT->COMMAND), CMD_ID_DISABLE_EXTENDED_MODE);
|
||||
}
|
||||
d64_regs = enabled ? D64_REGS_EXT : D64_REGS;
|
||||
return d64_ll_ci_wait();
|
||||
}
|
||||
|
||||
bool d64_ll_write_eeprom_contents (void *contents) {
|
||||
if (d64_ll_ci_wait()) {
|
||||
return true;
|
||||
}
|
||||
pi_dma_write_data(contents, d64_regs->EEPROM, 2048);
|
||||
return d64_ll_ci_wait();
|
||||
}
|
||||
|
||||
bool d64_ll_write_save_writeback_lba_list (void *list) {
|
||||
if (d64_ll_ci_wait()) {
|
||||
return true;
|
||||
}
|
||||
pi_dma_write_data(list, d64_regs->WRITEBACK, 1024);
|
||||
return d64_ll_ci_wait();
|
||||
}
|
99
src/flashcart/64drive/64drive_ll.h
Normal file
99
src/flashcart/64drive/64drive_ll.h
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @file 64drive_ll.h
|
||||
* @brief 64drive flashcart low level access
|
||||
* @ingroup flashcart
|
||||
*/
|
||||
|
||||
#ifndef FLASHCART_64DRIVE_LL_H__
|
||||
#define FLASHCART_64DRIVE_LL_H__
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/**
|
||||
* @addtogroup 64drive
|
||||
* @{
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint8_t BUFFER[512];
|
||||
uint32_t STATUS;
|
||||
uint32_t __unused_1;
|
||||
uint32_t COMMAND;
|
||||
uint32_t __unused_2;
|
||||
uint32_t LBA;
|
||||
uint32_t __unused_3;
|
||||
uint32_t LENGTH;
|
||||
uint32_t __unused_4;
|
||||
uint32_t RESULT;
|
||||
|
||||
uint32_t __unused_5[49];
|
||||
|
||||
uint32_t SDRAM_SIZE;
|
||||
uint32_t MAGIC;
|
||||
uint32_t VARIANT;
|
||||
uint32_t PERSISTENT;
|
||||
uint32_t BUTTON_UPGRADE;
|
||||
uint32_t REVISION;
|
||||
|
||||
uint32_t __unused_6[64];
|
||||
|
||||
uint32_t USB_COMMAND_STATUS;
|
||||
uint32_t USB_PARAM_RESULT[2];
|
||||
|
||||
uint32_t __unused_7[5];
|
||||
|
||||
uint32_t WIFI_COMMAND_STATUS;
|
||||
uint32_t WIFI_PARAM_RESULT[2];
|
||||
|
||||
uint32_t __unused_8[757];
|
||||
|
||||
uint8_t EEPROM[2048];
|
||||
uint32_t WRITEBACK[256];
|
||||
} d64_regs_t;
|
||||
|
||||
#define D64_REGS_BASE (0x18000000UL)
|
||||
#define D64_REGS_BASE_EXT (0x1F800000UL)
|
||||
#define D64_REGS ((d64_regs_t *) D64_REGS_BASE)
|
||||
#define D64_REGS_EXT ((d64_regs_t *) D64_REGS_BASE_EXT)
|
||||
|
||||
|
||||
typedef enum {
|
||||
DEVICE_VARIANT_UNKNOWN = 0x0000,
|
||||
DEVICE_VARIANT_A = 0x4100,
|
||||
DEVICE_VARIANT_B = 0x4200,
|
||||
} d64_device_variant_t;
|
||||
|
||||
typedef enum {
|
||||
TV_TYPE_PAL = 0,
|
||||
TV_TYPE_NTSC = 1,
|
||||
TV_TYPE_MPAL = 2,
|
||||
TV_TYPE_UNKNOWN = 3,
|
||||
} d64_tv_type_t;
|
||||
|
||||
typedef enum {
|
||||
SAVE_TYPE_NONE,
|
||||
SAVE_TYPE_EEPROM_4K,
|
||||
SAVE_TYPE_EEPROM_16K,
|
||||
SAVE_TYPE_SRAM,
|
||||
SAVE_TYPE_FLASHRAM,
|
||||
SAVE_TYPE_SRAM_BANKED,
|
||||
SAVE_TYPE_FLASHRAM_PKST2,
|
||||
} d64_save_type_t;
|
||||
|
||||
|
||||
bool d64_ll_get_version (d64_device_variant_t *device_variant, uint16_t *fpga_revision, uint32_t *bootloader_version);
|
||||
bool d64_ll_set_persistent_variable_storage (bool quick_reboot, d64_tv_type_t force_tv_type, uint8_t cic_seed);
|
||||
bool d64_ll_set_save_type (d64_save_type_t save_type);
|
||||
bool d64_ll_enable_save_writeback (bool enabled);
|
||||
bool d64_ll_enable_cartrom_writes (bool enabled);
|
||||
bool d64_ll_enable_extended_mode (bool enabled);
|
||||
bool d64_ll_write_eeprom_contents (void *contents);
|
||||
bool d64_ll_write_save_writeback_lba_list (void *list);
|
||||
|
||||
/** @} */ /* 64drive */
|
||||
|
||||
|
||||
#endif
|
49
src/flashcart/64drive/README.md
Normal file
49
src/flashcart/64drive/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
## 64drive developer notes
|
||||
|
||||
### Official documentation
|
||||
|
||||
http://64drive.retroactive.be/64drive_hardware_spec.pdf
|
||||
|
||||
|
||||
### Save location offset in SDRAM
|
||||
|
||||
| Type | HW1 | HW2 |
|
||||
| ---------------------------- | ------------ | ------------ |
|
||||
| SRAM | `0x03FE0000` | `0x0FFC0000` |
|
||||
| FlashRAM | `0x03FE0000` | `0x0FFC0000` |
|
||||
| SRAM banked | `0x03FE0000` | `0x0FFC0000` |
|
||||
| FlashRAM (Pokemon Stadium 2) | `0x01606560` | `0x0FFC0000` |
|
||||
|
||||
EEPROM save types are stored in separate memory inside FPGA, rest of the save types are stored inside SDRAM memory.
|
||||
EEPROM save types need manual load as this memory space can't be written with "Read multiple sectors to SDRAM" command.
|
||||
|
||||
|
||||
### "Persistent variable storage" register
|
||||
|
||||
| Bits | Meaning |
|
||||
| --------- | ---------------------------------------------------------- |
|
||||
| `[31:17]` | _Unused_ |
|
||||
| `[16]` | Reset behavior: `0` - boot to menu / `1` - quick boot game |
|
||||
| `[15:10]` | _Unused_ |
|
||||
| `[9:8]` | Force TV type |
|
||||
| `[7:0]` | CIC seed |
|
||||
|
||||
It's used by the bootloader to quickly load game without running menu again.
|
||||
Should contain bootloader version but it's zeroed if ROM wasn't ran through bootloader (e.g. ROM was loaded via USB).
|
||||
|
||||
|
||||
### "Enable/disable save writeback" command
|
||||
|
||||
Does not work when USB cable is connected - writeback is forced to be in disabled state.
|
||||
Curiously, official 64drive menu never calls this command, save writeback might be enabled by default.
|
||||
|
||||
|
||||
### "Enable/disable byteswap on load" command
|
||||
|
||||
Annoyingly, this command affects both loading single sector into the buffer and loading multiple sectors to the SDRAM.
|
||||
|
||||
|
||||
### "Enable/disable extended address mode" command
|
||||
|
||||
As of latest available firmware version 2.05 this command is not implemented.
|
||||
Documentation specifies it's supported on firmwares 2.06+ but this version (or anything newer) was never published.
|
@ -8,11 +8,13 @@
|
||||
#include "utils/utils.h"
|
||||
|
||||
#include "flashcart.h"
|
||||
|
||||
#include "64drive/64drive.h"
|
||||
#include "sc64/sc64.h"
|
||||
#include "ed64/ed64.h"
|
||||
|
||||
|
||||
#define WRITEBACK_MAX_SECTORS (256)
|
||||
#define SAVE_WRITEBACK_MAX_SECTORS (256)
|
||||
|
||||
|
||||
static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = {
|
||||
@ -23,8 +25,12 @@ static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = {
|
||||
KiB(96),
|
||||
KiB(128),
|
||||
KiB(128),
|
||||
KiB(128),
|
||||
};
|
||||
|
||||
static uint32_t save_writeback_sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8)));
|
||||
|
||||
|
||||
static flashcart_error_t dummy_init (void) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
@ -56,7 +62,7 @@ flashcart_error_t flashcart_init (void) {
|
||||
int (* libcart_init) (void);
|
||||
flashcart_t *(* get) (void);
|
||||
} flashcarts[CART_MAX] = {
|
||||
{ CART_CI, ci_init, NULL }, // 64drive
|
||||
{ CART_CI, ci_init, d64_get_flashcart }, // 64drive
|
||||
{ CART_SC, sc_init, sc64_get_flashcart }, // SC64
|
||||
{ CART_EDX, edx_init, NULL }, // Series X EverDrive-64
|
||||
{ CART_ED, ed_init, ed64_get_flashcart }, // Original EverDrive-64
|
||||
@ -96,6 +102,7 @@ flashcart_error_t flashcart_deinit (void) {
|
||||
if (flashcart->deinit) {
|
||||
return flashcart->deinit();
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
@ -125,9 +132,21 @@ flashcart_error_t flashcart_load_file (char *file_path, uint32_t rom_offset, uin
|
||||
return flashcart->load_file(file_path, rom_offset, file_offset);
|
||||
}
|
||||
|
||||
static void save_writeback_sectors_callback (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size) {
|
||||
for (uint32_t i = 0; i < cluster_size; i++) {
|
||||
uint32_t offset = file_sector + i;
|
||||
uint32_t sector = cluster_sector + i;
|
||||
|
||||
if ((offset > SAVE_WRITEBACK_MAX_SECTORS) || (offset > sector_count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
save_writeback_sectors[offset] = sector;
|
||||
}
|
||||
}
|
||||
|
||||
flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type) {
|
||||
flashcart_error_t error;
|
||||
uint32_t sectors[WRITEBACK_MAX_SECTORS] __attribute__((aligned(8)));
|
||||
|
||||
if (save_type >= __FLASHCART_SAVE_TYPE_END) {
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
@ -159,10 +178,13 @@ flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t sa
|
||||
}
|
||||
|
||||
if (flashcart->set_save_writeback) {
|
||||
if (file_get_sectors(save_path, sectors, WRITEBACK_MAX_SECTORS)) {
|
||||
for (int i = 0; i < SAVE_WRITEBACK_MAX_SECTORS; i++) {
|
||||
save_writeback_sectors[i] = 0;
|
||||
}
|
||||
if (file_get_sectors(save_path, save_writeback_sectors_callback)) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
}
|
||||
if ((error = flashcart->set_save_writeback(sectors)) != FLASHCART_OK) {
|
||||
if ((error = flashcart->set_save_writeback(save_writeback_sectors)) != FLASHCART_OK) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ typedef enum {
|
||||
FLASHCART_SAVE_TYPE_SRAM_BANKED,
|
||||
FLASHCART_SAVE_TYPE_SRAM_128K,
|
||||
FLASHCART_SAVE_TYPE_FLASHRAM,
|
||||
FLASHCART_SAVE_TYPE_FLASHRAM_PKST2,
|
||||
__FLASHCART_SAVE_TYPE_END
|
||||
} flashcart_save_type_t;
|
||||
|
||||
|
@ -85,7 +85,7 @@ static flashcart_error_t sc64_init (void) {
|
||||
uint32_t value;
|
||||
} default_config[] = {
|
||||
{ CFG_ID_BOOTLOADER_SWITCH, false },
|
||||
{ CFG_ID_ROM_WRITE_ENABLE, false },
|
||||
{ CFG_ID_ROM_WRITE_ENABLE, true },
|
||||
{ CFG_ID_ROM_SHADOW_ENABLE, false },
|
||||
{ CFG_ID_DD_MODE, DD_MODE_DISABLED },
|
||||
{ CFG_ID_ISV_ADDRESS, 0x00000000 },
|
||||
@ -110,6 +110,8 @@ static flashcart_error_t sc64_init (void) {
|
||||
}
|
||||
|
||||
static flashcart_error_t sc64_deinit (void) {
|
||||
sc64_ll_set_config(CFG_ID_ROM_WRITE_ENABLE, false);
|
||||
|
||||
sc64_ll_lock();
|
||||
|
||||
return FLASHCART_OK;
|
||||
@ -309,6 +311,9 @@ static flashcart_error_t sc64_set_save_type (flashcart_save_type_t save_type) {
|
||||
case FLASHCART_SAVE_TYPE_FLASHRAM:
|
||||
type = SAVE_TYPE_FLASHRAM;
|
||||
break;
|
||||
case FLASHCART_SAVE_TYPE_FLASHRAM_PKST2:
|
||||
type = SAVE_TYPE_FLASHRAM;
|
||||
break;
|
||||
default:
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
}
|
||||
|
@ -91,7 +91,6 @@ cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type,
|
||||
case CART_LOAD_EMU_TYPE_SNES:
|
||||
path_push(path, "sodium64.z64");
|
||||
save_type = FLASHCART_SAVE_TYPE_SRAM;
|
||||
emulated_rom_offset = 0x104000;
|
||||
break;
|
||||
case CART_LOAD_EMU_TYPE_GAMEBOY:
|
||||
path_push(path, "gb.v64");
|
||||
@ -101,6 +100,10 @@ cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type,
|
||||
path_push(path, "gbc.v64");
|
||||
save_type = FLASHCART_SAVE_TYPE_FLASHRAM;
|
||||
break;
|
||||
case CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT:
|
||||
path_push(path, "TotalSMS.z64");
|
||||
save_type = FLASHCART_SAVE_TYPE_SRAM;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!file_exists(path_get(path))) {
|
||||
@ -120,6 +123,7 @@ cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type,
|
||||
|
||||
switch (emu_type) {
|
||||
case CART_LOAD_EMU_TYPE_SNES:
|
||||
// The emulator expects the header to be removed from the ROM being uploaded.
|
||||
emulated_file_offset = ((file_get_size(path_get(path)) & 0x3FF) == 0x200) ? 0x200 : 0;
|
||||
break;
|
||||
default:
|
||||
|
@ -28,6 +28,7 @@ typedef enum {
|
||||
CART_LOAD_EMU_TYPE_SNES,
|
||||
CART_LOAD_EMU_TYPE_GAMEBOY,
|
||||
CART_LOAD_EMU_TYPE_GAMEBOY_COLOR,
|
||||
CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT,
|
||||
} cart_load_emu_type_t;
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ typedef struct {
|
||||
surface_t *image;
|
||||
} component_boxart_t;
|
||||
|
||||
component_boxart_t *component_boxart_init (uint16_t id);
|
||||
component_boxart_t *component_boxart_init (uint8_t media_type, uint16_t id);
|
||||
void component_boxart_free (component_boxart_t *b);
|
||||
void component_boxart_draw (component_boxart_t *b);
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "../path.h"
|
||||
#include "../png_decoder.h"
|
||||
#include "constants.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
|
||||
#define BOXART_DIRECTORY "sd:/menu/boxart"
|
||||
@ -16,14 +17,18 @@ static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void
|
||||
}
|
||||
|
||||
|
||||
component_boxart_t *component_boxart_init (uint16_t id) {
|
||||
component_boxart_t *component_boxart_init (uint8_t media_type, uint16_t id) {
|
||||
component_boxart_t *b = calloc(1, sizeof(component_boxart_t));
|
||||
|
||||
if (b) {
|
||||
b->loading = true;
|
||||
char *path = alloca(strlen(BOXART_DIRECTORY) + 1 + 7 + 1); // allocate for the largest path.
|
||||
sprintf(path, "%s/%c%c%c.png", BOXART_DIRECTORY, (media_type & 0xFF), ((id >> 8) & 0xFF), (id & 0xFF));
|
||||
|
||||
char *path = alloca(strlen(BOXART_DIRECTORY) + 1 + 6 + 1);
|
||||
sprintf(path, "%s/%c%c.png", BOXART_DIRECTORY, ((id >> 8) & 0xFF), (id & 0xFF));
|
||||
// if the file does not exist, also check for just the id.
|
||||
if (!file_exists(path)) {
|
||||
sprintf(path, "%s/%c%c.png", BOXART_DIRECTORY, ((id >> 8) & 0xFF), (id & 0xFF));
|
||||
}
|
||||
|
||||
if (png_decoder_start(path, BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) != PNG_OK) {
|
||||
free(b);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "png_decoder.h"
|
||||
#include "settings.h"
|
||||
#include "sound.h"
|
||||
#include "usb_comm.h"
|
||||
#include "utils/fs.h"
|
||||
#include "views/views.h"
|
||||
|
||||
@ -28,7 +29,6 @@
|
||||
|
||||
|
||||
static menu_t *menu;
|
||||
static bool boot_pending;
|
||||
static tv_type_t tv_type;
|
||||
static volatile int frame_counter = 0;
|
||||
|
||||
@ -61,8 +61,6 @@ static void menu_init (boot_params_t *boot_params) {
|
||||
fonts_init();
|
||||
sound_init_default();
|
||||
|
||||
boot_pending = false;
|
||||
|
||||
menu = calloc(1, sizeof(menu_t));
|
||||
assert(menu != NULL);
|
||||
|
||||
@ -147,7 +145,7 @@ static struct views_s {
|
||||
void menu_run (boot_params_t *boot_params) {
|
||||
menu_init(boot_params);
|
||||
|
||||
while (!boot_pending && (exception_reset_time() < RESET_TIME_LENGTH)) {
|
||||
while (exception_reset_time() < RESET_TIME_LENGTH) {
|
||||
surface_t *display = (frame_counter >= FRAMERATE_DIVIDER) ? display_try_get() : NULL;
|
||||
|
||||
if (display != NULL) {
|
||||
@ -159,7 +157,12 @@ void menu_run (boot_params_t *boot_params) {
|
||||
views[menu->mode].show(menu, display);
|
||||
} else {
|
||||
rdpq_attach_clear(display, NULL);
|
||||
rdpq_detach_show();
|
||||
rdpq_detach_wait();
|
||||
display_show(display);
|
||||
}
|
||||
|
||||
if (menu->mode == MENU_MODE_BOOT) {
|
||||
break;
|
||||
}
|
||||
|
||||
while (menu->mode != menu->next_mode) {
|
||||
@ -168,10 +171,6 @@ void menu_run (boot_params_t *boot_params) {
|
||||
if (views[menu->mode].init) {
|
||||
views[menu->mode].init(menu);
|
||||
}
|
||||
|
||||
if (menu->mode == MENU_MODE_BOOT) {
|
||||
boot_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
time(&menu->current_time);
|
||||
@ -180,6 +179,8 @@ void menu_run (boot_params_t *boot_params) {
|
||||
sound_poll();
|
||||
|
||||
png_decoder_poll();
|
||||
|
||||
usb_comm_poll(menu);
|
||||
}
|
||||
|
||||
menu_deinit(menu);
|
||||
|
163
src/menu/usb_comm.c
Normal file
163
src/menu/usb_comm.c
Normal file
@ -0,0 +1,163 @@
|
||||
// NOTE: This code doesn't implement EverDrive-64 USB protocol.
|
||||
// Main use of these functions is to aid menu development
|
||||
// (for example replace files on the SD card or reboot menu).
|
||||
|
||||
#include <fatfs/ff.h>
|
||||
#include <string.h>
|
||||
#include <usb.h>
|
||||
|
||||
#include "usb_comm.h"
|
||||
#include "utils/utils.h"
|
||||
|
||||
|
||||
#define MAX_FILE_SIZE MiB(4)
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *id;
|
||||
void (*op) (menu_t *menu);
|
||||
} usb_comm_command_t;
|
||||
|
||||
|
||||
static int usb_comm_get_char (void) {
|
||||
char c;
|
||||
|
||||
if (USBHEADER_GETSIZE(usb_poll()) <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
usb_read(&c, sizeof(c));
|
||||
|
||||
return (int) (c);
|
||||
}
|
||||
|
||||
static bool usb_comm_read_string (char *string, int length, char end) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
int c = usb_comm_get_char();
|
||||
|
||||
if (c < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
string[i] = (char) (c);
|
||||
|
||||
if (c == '\0' || c == end) {
|
||||
string[i] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == (length - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void usb_comm_send_error (const char *message) {
|
||||
usb_purge();
|
||||
usb_write(DATATYPE_TEXT, message, strlen(message));
|
||||
}
|
||||
|
||||
|
||||
static void command_reboot (menu_t *menu) {
|
||||
menu->next_mode = MENU_MODE_BOOT;
|
||||
|
||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
||||
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
||||
menu->boot_params->detect_cic_seed = true;
|
||||
};
|
||||
|
||||
static void command_send_file (menu_t *menu) {
|
||||
char path[256];
|
||||
char length[8];
|
||||
|
||||
FIL f;
|
||||
int remaining;
|
||||
uint8_t data[8192];
|
||||
UINT bytes_written;
|
||||
|
||||
if (usb_comm_read_string(path, sizeof(path), ' ')) {
|
||||
return usb_comm_send_error("Invalid path argument\n");
|
||||
}
|
||||
|
||||
if (usb_comm_get_char() != '@') {
|
||||
return usb_comm_send_error("Invalid argument\n");
|
||||
}
|
||||
|
||||
if (usb_comm_read_string(length, sizeof(length), '@')) {
|
||||
return usb_comm_send_error("Invalid file length argument\n");
|
||||
}
|
||||
|
||||
if (f_open(&f, path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) {
|
||||
return usb_comm_send_error("Couldn't create file\n");
|
||||
}
|
||||
|
||||
remaining = atoi(length);
|
||||
|
||||
if (remaining > MAX_FILE_SIZE) {
|
||||
return usb_comm_send_error("File size too big\n");
|
||||
}
|
||||
|
||||
while (remaining > 0) {
|
||||
int block_size = MIN(remaining, sizeof(data));
|
||||
usb_read(data, block_size);
|
||||
if (f_write(&f, data, block_size, &bytes_written) != FR_OK) {
|
||||
f_close(&f);
|
||||
return usb_comm_send_error("Couldn't write data to the file\n");
|
||||
}
|
||||
if (bytes_written != block_size) {
|
||||
f_close(&f);
|
||||
return usb_comm_send_error("Couldn't write all required data to the file\n");
|
||||
}
|
||||
remaining -= block_size;
|
||||
}
|
||||
|
||||
if (f_close(&f) != FR_OK) {
|
||||
return usb_comm_send_error("Couldn't flush data to the file\n");
|
||||
}
|
||||
|
||||
if (usb_comm_get_char() != '\0') {
|
||||
return usb_comm_send_error("Invalid token at the end of data stream\n");
|
||||
}
|
||||
}
|
||||
|
||||
static usb_comm_command_t commands[] = {
|
||||
{ .id = "reboot", .op = command_reboot },
|
||||
{ .id = "send-file", .op = command_send_file },
|
||||
{ .id = NULL },
|
||||
};
|
||||
|
||||
|
||||
void usb_comm_poll (menu_t *menu) {
|
||||
uint32_t header = usb_poll();
|
||||
|
||||
if (USBHEADER_GETTYPE(header) != DATATYPE_TEXT) {
|
||||
usb_purge();
|
||||
return;
|
||||
}
|
||||
|
||||
if (USBHEADER_GETSIZE(header) > 0) {
|
||||
char cmd_id[32];
|
||||
|
||||
if (usb_comm_read_string(cmd_id, sizeof(cmd_id), ' ')) {
|
||||
usb_comm_send_error("Command id too long\n");
|
||||
} else {
|
||||
usb_comm_command_t *cmd = commands;
|
||||
|
||||
while (cmd->id != NULL) {
|
||||
if (strcmp(cmd->id, cmd_id) == 0) {
|
||||
cmd->op(menu);
|
||||
break;
|
||||
}
|
||||
cmd++;
|
||||
}
|
||||
|
||||
usb_purge();
|
||||
|
||||
if (cmd->id == NULL) {
|
||||
usb_comm_send_error("Unknown command\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
src/menu/usb_comm.h
Normal file
21
src/menu/usb_comm.h
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @file usb_comm.h
|
||||
* @brief USB communication subsystem
|
||||
* @ingroup menu
|
||||
*/
|
||||
|
||||
#ifndef USB_COMM_H__
|
||||
#define USB_COMM_H__
|
||||
|
||||
|
||||
#include "menu_state.h"
|
||||
|
||||
|
||||
#ifndef NDEBUG
|
||||
void usb_comm_poll (menu_t *menu);
|
||||
#else
|
||||
#define usb_comm_poll(menu)
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
@ -10,7 +10,7 @@
|
||||
|
||||
|
||||
static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
|
||||
static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", NULL };
|
||||
static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", NULL };
|
||||
static const char *save_extensions[] = { "sav", NULL }; // TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts.
|
||||
static const char *image_extensions[] = { "png", NULL };
|
||||
static const char *music_extensions[] = { "mp3", NULL };
|
||||
|
@ -31,7 +31,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
const char *firmware_message = (
|
||||
"Minimum supported versions:\n"
|
||||
"EverDrive-64: ?\n"
|
||||
"64drive: ?\n"
|
||||
"64drive: 2.05\n"
|
||||
"SC64: 2.16.0"
|
||||
);
|
||||
|
||||
|
@ -8,10 +8,28 @@ static const char *emu_nes_rom_extensions[] = { "nes", NULL };
|
||||
static const char *emu_snes_rom_extensions[] = { "sfc", "smc", NULL };
|
||||
static const char *emu_gameboy_rom_extensions[] = { "gb", NULL };
|
||||
static const char *emu_gameboy_color_rom_extensions[] = { "gbc", NULL };
|
||||
static const char *emu_sega_8bit_rom_extensions[] = { "sms", "gg", "sg", NULL };
|
||||
|
||||
static bool load_pending;
|
||||
static cart_load_emu_type_t emu_type;
|
||||
|
||||
static char *format_emulator_name (cart_load_emu_type_t emulator_info) {
|
||||
switch (emulator_info) {
|
||||
case CART_LOAD_EMU_TYPE_NES:
|
||||
return "Nintendo Famicom (NES)";
|
||||
case CART_LOAD_EMU_TYPE_SNES:
|
||||
return "Nintendo Super Famicom (SNES)";
|
||||
case CART_LOAD_EMU_TYPE_GAMEBOY:
|
||||
return "Nintendo GAMEBOY";
|
||||
case CART_LOAD_EMU_TYPE_GAMEBOY_COLOR:
|
||||
return "Nintendo GAMEBOY Color";
|
||||
case CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT:
|
||||
return "SEGA 8bit system";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.enter) {
|
||||
@ -33,17 +51,22 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_CENTER, VALIGN_TOP,
|
||||
"Emulator information\n"
|
||||
"THE EMULATOR\n"
|
||||
"Rom Name\n"
|
||||
"Load Emulated ROM\n"
|
||||
);
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
"%s",
|
||||
"\n"
|
||||
"Emulated System: %s\n"
|
||||
"Rom Name: %s",
|
||||
format_emulator_name(emu_type),
|
||||
menu->browser.entry->name
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"A: Load and run Emulator ROM\n"
|
||||
"A: Load and run Emulated ROM\n"
|
||||
"B: Exit"
|
||||
);
|
||||
}
|
||||
@ -93,6 +116,8 @@ void view_load_emulator_init (menu_t *menu) {
|
||||
emu_type = CART_LOAD_EMU_TYPE_GAMEBOY;
|
||||
} else if (file_has_extensions(path_get(path), emu_gameboy_color_rom_extensions)) {
|
||||
emu_type = CART_LOAD_EMU_TYPE_GAMEBOY_COLOR;
|
||||
} else if (file_has_extensions(path_get(path), emu_sega_8bit_rom_extensions)) {
|
||||
emu_type = CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT;
|
||||
} else {
|
||||
menu_show_error(menu, "Unsupported ROM");
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ void view_load_rom_init (menu_t *menu) {
|
||||
|
||||
rom_header = file_read_rom_header(path_get(path));
|
||||
|
||||
boxart = component_boxart_init(rom_header.metadata.unique_identifier);
|
||||
boxart = component_boxart_init(rom_header.metadata.media_type, rom_header.metadata.unique_identifier);
|
||||
|
||||
path_free(path);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ char *strip_sd_prefix (char *path) {
|
||||
|
||||
char *found = strstr(path, prefix);
|
||||
if (found) {
|
||||
return found + strlen(prefix);
|
||||
return found + strlen(prefix) - 1;
|
||||
}
|
||||
|
||||
return path;
|
||||
@ -106,13 +106,13 @@ bool file_fill (char *path, uint8_t value) {
|
||||
return error;
|
||||
}
|
||||
|
||||
bool file_get_sectors (char *path, uint32_t *sectors, size_t entries) {
|
||||
bool file_get_sectors (char *path, void (*callback) (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size)) {
|
||||
FATFS *fs;
|
||||
FIL fil;
|
||||
bool error = false;
|
||||
|
||||
for (int i = 0; i < entries; i++) {
|
||||
sectors[i] = 0;
|
||||
if (!callback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) {
|
||||
@ -121,25 +121,22 @@ bool file_get_sectors (char *path, uint32_t *sectors, size_t entries) {
|
||||
|
||||
fs = fil.obj.fs;
|
||||
|
||||
uint32_t file_sector_entries = (ALIGN(f_size(&fil), FS_SECTOR_SIZE) / FS_SECTOR_SIZE);
|
||||
file_sector_entries = (file_sector_entries > entries) ? entries : file_sector_entries;
|
||||
uint32_t sector_count = (ALIGN(f_size(&fil), FS_SECTOR_SIZE) / FS_SECTOR_SIZE);
|
||||
|
||||
uint32_t cluster_sector = 0;
|
||||
|
||||
for (int file_sector = 0; file_sector < file_sector_entries; file_sector++) {
|
||||
if ((file_sector % fs->csize) == 0) {
|
||||
if ((f_lseek(&fil, (file_sector * FS_SECTOR_SIZE) + (FS_SECTOR_SIZE / 2))) != FR_OK) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t cluster = fil.clust;
|
||||
if (cluster >= fs->n_fatent) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
cluster_sector = (fs->database + ((LBA_t) (fs->csize) * (cluster - 2)));
|
||||
for (int file_sector = 0; file_sector < sector_count; file_sector += fs->csize) {
|
||||
if ((f_lseek(&fil, (file_sector * FS_SECTOR_SIZE) + (FS_SECTOR_SIZE / 2))) != FR_OK) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
*sectors++ = (cluster_sector + (file_sector % fs->csize));
|
||||
uint32_t cluster = fil.clust;
|
||||
if (cluster >= fs->n_fatent) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
cluster_sector = (fs->database + ((LBA_t) (fs->csize) * (cluster - 2)));
|
||||
callback(sector_count, file_sector, cluster_sector, fs->csize);
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
|
@ -17,7 +17,7 @@ size_t file_get_size (char *path);
|
||||
bool file_delete (char *path);
|
||||
bool file_allocate (char *path, size_t size);
|
||||
bool file_fill (char *path, uint8_t value);
|
||||
bool file_get_sectors (char *path, uint32_t *sectors, size_t entries);
|
||||
bool file_get_sectors (char *path, void (*callback) (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size));
|
||||
bool file_has_extensions (char *path, const char *extensions[]);
|
||||
|
||||
bool directory_exists (char *path);
|
||||
|
Loading…
Reference in New Issue
Block a user