mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2024-11-28 21:44:15 +01:00
Merge remote-tracking branch 'upstream/main' into ed64-basic
This commit is contained in:
commit
23b371acb1
@ -6,9 +6,9 @@ RUN apt-get update && \
|
||||
wget https://github.com/DragonMinded/libdragon/releases/download/toolchain-continuous-prerelease/gcc-toolchain-mips64-x86_64.deb && \
|
||||
dpkg -i gcc-toolchain-mips64-x86_64.deb && \
|
||||
rm gcc-toolchain-mips64-x86_64.deb && \
|
||||
wget https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.16.0/sc64-deployer-linux-v2.16.0.tar.gz && \
|
||||
tar -xf sc64-deployer-linux-v2.16.0.tar.gz -C /usr/local/bin && \
|
||||
rm sc64-deployer-linux-v2.16.0.tar.gz && \
|
||||
wget https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.17.0/sc64-deployer-linux-v2.17.0.tar.gz && \
|
||||
tar -xf sc64-deployer-linux-v2.17.0.tar.gz -C /usr/local/bin && \
|
||||
rm sc64-deployer-linux-v2.17.0.tar.gz && \
|
||||
git config --global --add safe.directory "*" && \
|
||||
SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" && \
|
||||
echo "$SNIPPET" >> "/root/.bashrc"
|
||||
|
7
Makefile
7
Makefile
@ -14,6 +14,7 @@ N64_CFLAGS += -iquote $(SOURCE_DIR) -iquote $(ASSETS_DIR) -I $(SOURCE_DIR)/libs
|
||||
SRCS = \
|
||||
main.c \
|
||||
boot/boot.c \
|
||||
boot/cic.c \
|
||||
boot/crc32.c \
|
||||
boot/reboot.S \
|
||||
flashcart/64drive/64drive_ll.c \
|
||||
@ -24,7 +25,6 @@ SRCS = \
|
||||
flashcart/sc64/sc64.c \
|
||||
flashcart/ed64/ed64_ll.c \
|
||||
flashcart/ed64/ed64.c \
|
||||
hdmi/hdmi.c \
|
||||
libs/libspng/spng/spng.c \
|
||||
libs/mini.c/src/mini.c \
|
||||
libs/miniz/miniz_tdef.c \
|
||||
@ -38,12 +38,14 @@ SRCS = \
|
||||
menu/components/common.c \
|
||||
menu/components/context_menu.c \
|
||||
menu/components/file_list.c \
|
||||
menu/disk_info.c \
|
||||
menu/fonts.c \
|
||||
menu/hdmi.c \
|
||||
menu/menu.c \
|
||||
menu/mp3_player.c \
|
||||
menu/path.c \
|
||||
menu/png_decoder.c \
|
||||
menu/rom_database.c \
|
||||
menu/rom_info.c \
|
||||
menu/settings.c \
|
||||
menu/sound.c \
|
||||
menu/usb_comm.c \
|
||||
@ -53,6 +55,7 @@ SRCS = \
|
||||
menu/views/fault.c \
|
||||
menu/views/file_info.c \
|
||||
menu/views/image_viewer.c \
|
||||
menu/views/load_disk.c \
|
||||
menu/views/load_emulator.c \
|
||||
menu/views/load_rom.c \
|
||||
menu/views/music_player.c \
|
||||
|
24
README.md
24
README.md
@ -12,6 +12,7 @@ An open source menu for N64 flashcarts.
|
||||
## Current (notable) menu features
|
||||
* Fully Open Source.
|
||||
* Loads all known N64 games (including iQue and Aleck64 ROMs (even if they are byteswapped)).
|
||||
* Fully emulates the 64DD and loads 64DD disks (SummerCart64 only).
|
||||
* Emulator support (NES, SNES, GB, GBC) ROMs.
|
||||
* N64 ROM box image support.
|
||||
* Background image (PNG) support.
|
||||
@ -31,6 +32,13 @@ Download the latest `menu.bin` file from the releases page, then put it in the r
|
||||
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.
|
||||
|
||||
#### 64DD disk support
|
||||
For the ability to load and run 64DD disk images, you need to add the folder `/menu/64ddipl` on the SD card.
|
||||
Download and add the relevant ipl files and rename them before adding them to the folder:
|
||||
- `NDDE0.n64` the US Prototype IPL can be downloaded from [here](https://64dd.org/dumps/64DD_IPL_US_MJR.n64)
|
||||
- `NDXJ0.n64` the JPN Development IPL can be downloaded from [here](https://64dd.org/dumps/64DD_IPL_DEV_H4G.n64)
|
||||
- `NDDJ2.n64` the JPN Retail IPL can be downloaded from [here](https://64dd.org/dumps/N64DD_IPLROM_(J).zip)
|
||||
|
||||
### ED64
|
||||
Download the `ED64.v64` ROM from the latest action run assets and place it in the `/ED64` folder.
|
||||
|
||||
@ -38,7 +46,7 @@ Download the `ED64.v64` ROM from the latest action run assets and place it in th
|
||||
Download the `ED64P.v64` ROM from the latest action run assets and place it in the `/ED64P` folder.
|
||||
|
||||
|
||||
### Common to all
|
||||
### Common to all flashcarts
|
||||
|
||||
#### ROM Boxart
|
||||
To use boxart, you need to place png files of size 158x112 in the folder `/menu/boxart` on the SD card.
|
||||
@ -48,7 +56,7 @@ 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.
|
||||
Emulators should be added to the `/menu/emulators` directory on the SD card.
|
||||
|
||||
Menu currently supports the following emulators and associated ROM file names:
|
||||
- **NES**: [neon64v2](https://github.com/hcs64/neon64v2) by *hcs64* - `neon64bu.rom`
|
||||
@ -65,11 +73,11 @@ You can use a dev container in VSCode to ease development.
|
||||
|
||||
## To deploy:
|
||||
### SC64
|
||||
* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.16.0/sc64-deployer-windows-v2.16.0.zip)
|
||||
* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.17.0/sc64-deployer-windows-v2.17.0.zip)
|
||||
* Extract and place `sc64deployer.exe` in the `tools/sc64` directory.
|
||||
|
||||
Make sure that your firmware is compatible (currently v2.16.0+)
|
||||
See: https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.16.0/docs/00_quick_startup_guide.md#firmware-backupupdate
|
||||
Make sure that your firmware is compatible (currently v2.17.0+)
|
||||
See: https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.17.0/docs/00_quick_startup_guide.md#firmware-backupupdate
|
||||
|
||||
|
||||
#### From the devcontainer
|
||||
@ -84,7 +92,6 @@ Then in the dev container, use `make run` or `make run-debug`
|
||||
|
||||
* Run `./localdeploy.bat` from the terminal
|
||||
|
||||
|
||||
Toggle the N64 power switch to load the ROM.
|
||||
|
||||
`ms-vscode.makefile-tools` will help (installed automatically in dev container).
|
||||
@ -94,6 +101,11 @@ The ROM can be found in the `output` directory.
|
||||
|
||||
NOTE: a "release" version of the SC64 menu is called `sc64menu.n64` and can be created for when you want to add it directly to the SDCard. This is generated by running `make all` or running `make sc64`.
|
||||
|
||||
|
||||
### Others
|
||||
* Add the required file to the correct folder on your SD card.
|
||||
|
||||
|
||||
# Update Libdragon submodule
|
||||
This repo currently uses the `unstable` branch as a submodule at a specific commit.
|
||||
To update to the latest version, use `git submodule update --remote ` from the terminal.
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 352679922bad5403f8ea4b9123dfe0403d030bc2
|
||||
Subproject commit dd2202a6082cd3d92f4c22e64b27f148612c4c3a
|
@ -1,8 +1,8 @@
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "boot.h"
|
||||
#include "boot_io.h"
|
||||
#include "crc32.h"
|
||||
#include "boot.h"
|
||||
#include "cic.h"
|
||||
|
||||
|
||||
#define C0_STATUS_FR (1 << 26)
|
||||
@ -14,71 +14,34 @@ extern uint32_t reboot_start __attribute__((section(".text")));
|
||||
extern size_t reboot_size __attribute__((section(".text")));
|
||||
|
||||
|
||||
typedef struct {
|
||||
const uint32_t crc32;
|
||||
const uint8_t seed;
|
||||
} ipl3_crc32_t;
|
||||
|
||||
static const ipl3_crc32_t ipl3_crc32[] = {
|
||||
{ .crc32 = 0x587BD543, .seed = 0xAC }, // 5101
|
||||
{ .crc32 = 0x6170A4A1, .seed = 0x3F }, // 6101
|
||||
{ .crc32 = 0x009E9EA3, .seed = 0x3F }, // 7102
|
||||
{ .crc32 = 0x90BB6CB5, .seed = 0x3F }, // 6102/7101
|
||||
{ .crc32 = 0x0B050EE0, .seed = 0x78 }, // x103
|
||||
{ .crc32 = 0x98BC2C86, .seed = 0x91 }, // x105
|
||||
{ .crc32 = 0xACC8580A, .seed = 0x85 }, // x106
|
||||
{ .crc32 = 0x0E018159, .seed = 0xDD }, // 5167
|
||||
{ .crc32 = 0x10C68B18, .seed = 0xDD }, // NDXJ0
|
||||
{ .crc32 = 0xBC605D0A, .seed = 0xDD }, // NDDJ0
|
||||
{ .crc32 = 0x502C4466, .seed = 0xDD }, // NDDJ1
|
||||
{ .crc32 = 0x0C965795, .seed = 0xDD }, // NDDJ2
|
||||
{ .crc32 = 0x8FEBA21E, .seed = 0xDE }, // NDDE0
|
||||
};
|
||||
|
||||
|
||||
static io32_t *boot_get_device_base (boot_params_t *params) {
|
||||
io32_t *device_base_address = ROM_CART;
|
||||
if (params->device_type == BOOT_DEVICE_TYPE_DD) {
|
||||
if (params->device_type == BOOT_DEVICE_TYPE_64DD) {
|
||||
device_base_address = ROM_DDIPL;
|
||||
}
|
||||
return device_base_address;
|
||||
}
|
||||
|
||||
static bool boot_detect_cic_seed (boot_params_t *params) {
|
||||
static void boot_detect_cic_seed (boot_params_t *params) {
|
||||
io32_t *base = boot_get_device_base(params);
|
||||
|
||||
uint32_t ipl3[1008] __attribute__((aligned(8)));
|
||||
uint8_t ipl3[IPL3_LENGTH] __attribute__((aligned(8)));
|
||||
|
||||
data_cache_hit_writeback_invalidate(ipl3, sizeof(ipl3));
|
||||
dma_read_raw_async(ipl3, (uint32_t) (&base[16]), sizeof(ipl3));
|
||||
dma_wait();
|
||||
|
||||
uint32_t crc32 = crc32_calculate(ipl3, sizeof(ipl3));
|
||||
|
||||
for (int i = 0; i < sizeof(ipl3_crc32) / sizeof(ipl3_crc32_t); i++) {
|
||||
if (ipl3_crc32[i].crc32 == crc32) {
|
||||
params->cic_seed = ipl3_crc32[i].seed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
params->cic_seed = cic_get_seed(cic_detect(ipl3));
|
||||
}
|
||||
|
||||
|
||||
bool boot_is_warm (void) {
|
||||
return (OS_INFO->reset_type == OS_INFO_RESET_TYPE_NMI);
|
||||
}
|
||||
|
||||
void boot (boot_params_t *params) {
|
||||
if (params->tv_type == BOOT_TV_TYPE_PASSTHROUGH) {
|
||||
params->tv_type = OS_INFO->tv_type;
|
||||
}
|
||||
|
||||
if (params->detect_cic_seed) {
|
||||
if (!boot_detect_cic_seed(params)) {
|
||||
params->cic_seed = 0x3F;
|
||||
}
|
||||
boot_detect_cic_seed(params);
|
||||
}
|
||||
|
||||
OS_INFO->mem_size_6105 = OS_INFO->mem_size;
|
||||
@ -157,7 +120,7 @@ void boot (boot_params_t *params) {
|
||||
|
||||
boot_device = (params->device_type & 0x01);
|
||||
tv_type = (params->tv_type & 0x03);
|
||||
reset_type = BOOT_RESET_TYPE_COLD;
|
||||
reset_type = BOOT_RESET_TYPE_NMI;
|
||||
cic_seed = (params->cic_seed & 0xFF);
|
||||
version = (params->tv_type == BOOT_TV_TYPE_PAL) ? 6
|
||||
: (params->tv_type == BOOT_TV_TYPE_NTSC) ? 1
|
||||
|
@ -15,7 +15,7 @@
|
||||
/** @brief Boot device type enumeration */
|
||||
typedef enum {
|
||||
BOOT_DEVICE_TYPE_ROM = 0,
|
||||
BOOT_DEVICE_TYPE_DD = 1,
|
||||
BOOT_DEVICE_TYPE_64DD = 1,
|
||||
} boot_device_type_t;
|
||||
|
||||
/** @brief Reset type enumeration */
|
||||
|
56
src/boot/cic.c
Normal file
56
src/boot/cic.c
Normal file
@ -0,0 +1,56 @@
|
||||
#include "cic.h"
|
||||
#include "crc32.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
const uint32_t crc32;
|
||||
const cic_type_t type;
|
||||
} ipl3_crc32_t;
|
||||
|
||||
static const ipl3_crc32_t ipl3_crc32[] = {
|
||||
{ .crc32 = 0x587BD543, .type = CIC_5101 },
|
||||
{ .crc32 = 0x0E018159, .type = CIC_5167 },
|
||||
{ .crc32 = 0x6170A4A1, .type = CIC_6101 },
|
||||
{ .crc32 = 0x009E9EA3, .type = CIC_7102 },
|
||||
{ .crc32 = 0x90BB6CB5, .type = CIC_6102_7101 },
|
||||
{ .crc32 = 0x0B050EE0, .type = CIC_x103 },
|
||||
{ .crc32 = 0x98BC2C86, .type = CIC_x105 },
|
||||
{ .crc32 = 0xACC8580A, .type = CIC_x106 },
|
||||
{ .crc32 = 0xBC605D0A, .type = CIC_8301 },
|
||||
{ .crc32 = 0x502C4466, .type = CIC_8302 },
|
||||
{ .crc32 = 0x0C965795, .type = CIC_8303 },
|
||||
{ .crc32 = 0x10C68B18, .type = CIC_8401 },
|
||||
{ .crc32 = 0x8FEBA21E, .type = CIC_8501 },
|
||||
};
|
||||
|
||||
|
||||
cic_type_t cic_detect (uint8_t *ipl3) {
|
||||
uint32_t crc32 = crc32_calculate(ipl3, IPL3_LENGTH);
|
||||
|
||||
for (int i = 0; i < sizeof(ipl3_crc32) / sizeof(ipl3_crc32_t); i++) {
|
||||
if (ipl3_crc32[i].crc32 == crc32) {
|
||||
return ipl3_crc32[i].type;
|
||||
}
|
||||
}
|
||||
|
||||
return CIC_UNKNOWN;
|
||||
}
|
||||
|
||||
uint8_t cic_get_seed (cic_type_t cic_type) {
|
||||
switch (cic_type) {
|
||||
case CIC_5101: return 0xAC;
|
||||
case CIC_5167: return 0xDD;
|
||||
case CIC_6101: return 0x3F;
|
||||
case CIC_7102: return 0x3F;
|
||||
case CIC_6102_7101: return 0x3F;
|
||||
case CIC_x103: return 0x78;
|
||||
case CIC_x105: return 0x91;
|
||||
case CIC_x106: return 0x85;
|
||||
case CIC_8301: return 0xDD;
|
||||
case CIC_8302: return 0xDD;
|
||||
case CIC_8303: return 0xDD;
|
||||
case CIC_8401: return 0xDD;
|
||||
case CIC_8501: return 0xDE;
|
||||
default: return 0x3F;
|
||||
}
|
||||
}
|
33
src/boot/cic.h
Normal file
33
src/boot/cic.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef CIC_H__
|
||||
#define CIC_H__
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#define IPL3_LENGTH (4032)
|
||||
|
||||
|
||||
typedef enum {
|
||||
CIC_5101,
|
||||
CIC_5167,
|
||||
CIC_6101,
|
||||
CIC_7102,
|
||||
CIC_6102_7101,
|
||||
CIC_x103,
|
||||
CIC_x105,
|
||||
CIC_x106,
|
||||
CIC_8301,
|
||||
CIC_8302,
|
||||
CIC_8303,
|
||||
CIC_8401,
|
||||
CIC_8501,
|
||||
CIC_UNKNOWN,
|
||||
} cic_type_t;
|
||||
|
||||
|
||||
cic_type_t cic_detect (uint8_t *ipl3);
|
||||
uint8_t cic_get_seed (cic_type_t cic_type);
|
||||
|
||||
|
||||
#endif
|
@ -2,20 +2,6 @@
|
||||
#define REBOOT_ADDRESS 0xA4001000
|
||||
#define STACK_ADDRESS 0xA4001FF0
|
||||
|
||||
#define RI_ADDRESS 0xA4700000
|
||||
|
||||
#define RI_MODE 0x00
|
||||
#define RI_CONFIG 0x04
|
||||
#define RI_CURRENT_LOAD 0x08
|
||||
#define RI_SELECT 0x0C
|
||||
#define RI_REFRESH 0x10
|
||||
#define RI_LATENCY 0x14
|
||||
|
||||
#define RI_MODE_RESET 0x00000000
|
||||
#define RI_MODE_STANDBY 0x0000000E
|
||||
|
||||
#define RDRAM_RESET_DELAY 1024
|
||||
#define RDRAM_STANDBY_DELAY 512
|
||||
|
||||
.set noat
|
||||
.section .text.reboot, "ax", %progbits
|
||||
@ -43,39 +29,24 @@ reboot_entry:
|
||||
|
||||
li $sp, STACK_ADDRESS
|
||||
|
||||
bnez $s5, reset_rdram_skip
|
||||
detect_console_region:
|
||||
li $t0, 1
|
||||
beq $s4, $zero, pal_console
|
||||
beq $s4, $t0, ntsc_console
|
||||
b mpal_console
|
||||
|
||||
reset_rdram:
|
||||
li $t0, RI_ADDRESS
|
||||
pal_console:
|
||||
li $ra, 0xA4001554
|
||||
b reset_registers
|
||||
|
||||
li $t1, RI_MODE_RESET
|
||||
sw $t1, RI_MODE($t0)
|
||||
ntsc_console:
|
||||
li $ra, 0xA4001550
|
||||
b reset_registers
|
||||
|
||||
li $t2, RDRAM_RESET_DELAY
|
||||
1:
|
||||
addiu $t2, (-1)
|
||||
bnez $t2, 1b
|
||||
|
||||
sw $zero, RI_CONFIG($t0)
|
||||
sw $zero, RI_CURRENT_LOAD($t0)
|
||||
sw $zero, RI_SELECT($t0)
|
||||
sw $zero, RI_REFRESH($t0)
|
||||
|
||||
li $t1, RI_MODE_STANDBY
|
||||
sw $t1, RI_MODE($t0)
|
||||
|
||||
li $t2, RDRAM_STANDBY_DELAY
|
||||
1:
|
||||
addiu $t2, (-1)
|
||||
bnez $t2, 1b
|
||||
reset_rdram_skip:
|
||||
|
||||
prepare_registers:
|
||||
la $t0, ra_table
|
||||
sll $t1, $s4, 2
|
||||
add $t0, $t1
|
||||
lw $ra, ($t0)
|
||||
mpal_console:
|
||||
li $ra, 0xA4001554
|
||||
|
||||
reset_registers:
|
||||
move $at, $zero
|
||||
move $v0, $zero
|
||||
move $v1, $zero
|
||||
@ -98,17 +69,11 @@ prepare_registers:
|
||||
move $k0, $zero
|
||||
move $k1, $zero
|
||||
move $gp, $zero
|
||||
move $s8, $zero
|
||||
move $fp, $zero
|
||||
|
||||
run_ipl3:
|
||||
li $t3, IPL3_ENTRY
|
||||
jr $t3
|
||||
|
||||
ra_values:
|
||||
.set ra_table, REBOOT_ADDRESS + (. - reboot_start)
|
||||
.word 0xA4001554
|
||||
.word 0xA4001550
|
||||
.word 0xA4001554
|
||||
|
||||
.set reboot_size, (. - reboot_start)
|
||||
.global reboot_size
|
||||
|
@ -25,57 +25,64 @@ 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) {
|
||||
static flashcart_err_t d64_init (void) {
|
||||
uint16_t fpga_revision;
|
||||
uint32_t bootloader_version;
|
||||
|
||||
if (d64_ll_enable_extended_mode(false)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_get_version(&device_variant, &fpga_revision, &bootloader_version)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
if (fpga_revision < SUPPORTED_FPGA_REVISION) {
|
||||
return FLASHCART_ERROR_OUTDATED;
|
||||
return FLASHCART_ERR_OUTDATED;
|
||||
}
|
||||
|
||||
if (d64_ll_enable_save_writeback(false)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_set_save_type(SAVE_TYPE_NONE)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
current_save_type = SAVE_TYPE_NONE;
|
||||
|
||||
if (d64_ll_enable_cartrom_writes(true)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_set_persistent_variable_storage(false, 0, 0)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_deinit (void) {
|
||||
static flashcart_err_t d64_deinit (void) {
|
||||
if (d64_ll_enable_cartrom_writes(false)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_load_rom (char *rom_path, flashcart_progress_callback_t *progress) {
|
||||
static bool d64_has_feature (flashcart_features_t feature) {
|
||||
switch (feature) {
|
||||
case FLASHCART_FEATURE_64DD: return false;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
static flashcart_err_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;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
@ -84,7 +91,7 @@ static flashcart_error_t d64_load_rom (char *rom_path, flashcart_progress_callba
|
||||
|
||||
if (rom_size > MiB(64)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
size_t sdram_size = MiB(64);
|
||||
@ -94,7 +101,7 @@ static flashcart_error_t d64_load_rom (char *rom_path, flashcart_progress_callba
|
||||
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;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if (progress) {
|
||||
progress(f_tell(&fil) / (float) (f_size(&fil)));
|
||||
@ -102,22 +109,22 @@ static flashcart_error_t d64_load_rom (char *rom_path, flashcart_progress_callba
|
||||
}
|
||||
if (f_tell(&fil) != rom_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) {
|
||||
static flashcart_err_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;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
@ -126,37 +133,37 @@ static flashcart_error_t d64_load_file (char *file_path, uint32_t rom_offset, ui
|
||||
|
||||
if (file_size > (MiB(64) - rom_offset)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
if (f_lseek(&fil, file_offset) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (f_read(&fil, (void *) (ROM_ADDRESS + rom_offset), file_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if (br != file_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_load_save (char *save_path) {
|
||||
static flashcart_err_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;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
size_t save_size = f_size(&fil);
|
||||
@ -175,28 +182,28 @@ static flashcart_error_t d64_load_save (char *save_path) {
|
||||
|
||||
if (f_read(&fil, address, save_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (is_eeprom_save) {
|
||||
if (d64_ll_write_eeprom_contents(eeprom_contents)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (br != save_size) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_set_save_type (flashcart_save_type_t save_type) {
|
||||
static flashcart_err_t d64_set_save_type (flashcart_save_type_t save_type) {
|
||||
d64_save_type_t type;
|
||||
|
||||
switch (save_type) {
|
||||
@ -226,15 +233,15 @@ static flashcart_error_t d64_set_save_type (flashcart_save_type_t save_type) {
|
||||
type = (device_variant == DEVICE_VARIANT_A) ? SAVE_TYPE_FLASHRAM_PKST2 : SAVE_TYPE_FLASHRAM;
|
||||
break;
|
||||
default:
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
if (d64_ll_enable_save_writeback(false)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_set_save_type(type)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
current_save_type = type;
|
||||
@ -242,13 +249,13 @@ static flashcart_error_t d64_set_save_type (flashcart_save_type_t save_type) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t d64_set_save_writeback (uint32_t *sectors) {
|
||||
static flashcart_err_t d64_set_save_writeback (uint32_t *sectors) {
|
||||
if (d64_ll_write_save_writeback_lba_list(sectors)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
if (d64_ll_enable_save_writeback(true)) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
@ -258,9 +265,12 @@ static flashcart_error_t d64_set_save_writeback (uint32_t *sectors) {
|
||||
static flashcart_t flashcart_d64 = {
|
||||
.init = d64_init,
|
||||
.deinit = d64_deinit,
|
||||
.has_feature = d64_has_feature,
|
||||
.load_rom = d64_load_rom,
|
||||
.load_file = d64_load_file,
|
||||
.load_save = d64_load_save,
|
||||
.load_64dd_ipl = NULL,
|
||||
.load_64dd_disk = NULL,
|
||||
.set_save_type = d64_set_save_type,
|
||||
.set_save_writeback = d64_set_save_writeback,
|
||||
};
|
||||
|
@ -31,7 +31,21 @@ static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = {
|
||||
static uint32_t save_writeback_sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8)));
|
||||
|
||||
|
||||
static flashcart_error_t dummy_init (void) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static flashcart_err_t dummy_init (void) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
@ -53,52 +67,59 @@ static flashcart_t *flashcart = &((flashcart_t) {
|
||||
#endif
|
||||
|
||||
|
||||
flashcart_error_t flashcart_init (void) {
|
||||
flashcart_error_t error;
|
||||
char *flashcart_convert_error_message (flashcart_err_t err) {
|
||||
switch (err) {
|
||||
case FLASHCART_OK: return "No error";
|
||||
case FLASHCART_ERR_NOT_DETECTED: return "No flashcart hardware was detected";
|
||||
case FLASHCART_ERR_OUTDATED: return "Outdated flashcart firmware";
|
||||
case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization";
|
||||
case FLASHCART_ERR_ARGS: return "Invalid argument passed to flashcart function";
|
||||
case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart";
|
||||
case FLASHCART_ERR_INT: return "Internal flashcart error";
|
||||
case FLASHCART_ERR_FUNCTION_NOT_SUPPORTED: return "Flashcart doesn't support this function";
|
||||
default: return "Unknown flashcart error";
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Explicitly support only these flashcarts in this specific initialization order.
|
||||
struct {
|
||||
int type;
|
||||
int (* libcart_init) (void);
|
||||
flashcart_t *(* get) (void);
|
||||
} flashcarts[CART_MAX] = {
|
||||
{ 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
|
||||
};
|
||||
flashcart_err_t flashcart_init (void) {
|
||||
flashcart_err_t err;
|
||||
|
||||
for (int i = 0; i < CART_MAX; i++) {
|
||||
if (flashcarts[i].libcart_init() >= 0) {
|
||||
cart_type = flashcarts[i].type;
|
||||
if (flashcarts[i].get) {
|
||||
flashcart = flashcarts[i].get();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cart_type == CART_NULL) {
|
||||
return FLASHCART_ERROR_NOT_DETECTED;
|
||||
}
|
||||
bool sd_card_initialized = debug_init_sdfs("sd:/", -1);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// NOTE: Some flashcarts doesn't have USB port, can't throw error here
|
||||
debug_init_usblog();
|
||||
#endif
|
||||
|
||||
if ((error = flashcart->init()) != FLASHCART_OK) {
|
||||
return error;
|
||||
switch (cart_type) {
|
||||
case CART_CI: // 64drive
|
||||
flashcart = d64_get_flashcart();
|
||||
break;
|
||||
|
||||
case CART_EDX: // Series X EverDrive-64
|
||||
case CART_ED: // Original EverDrive-64
|
||||
break;
|
||||
|
||||
case CART_SC: // SummerCart64
|
||||
flashcart = sc64_get_flashcart();
|
||||
break;
|
||||
|
||||
default:
|
||||
return FLASHCART_ERR_NOT_DETECTED;
|
||||
}
|
||||
|
||||
if (!debug_init_sdfs("sd:/", -1)) {
|
||||
return FLASHCART_ERROR_SD_CARD;
|
||||
if ((err = flashcart->init()) != FLASHCART_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!sd_card_initialized) {
|
||||
return FLASHCART_ERR_SD_CARD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
flashcart_error_t flashcart_deinit (void) {
|
||||
flashcart_err_t flashcart_deinit (void) {
|
||||
if (flashcart->deinit) {
|
||||
return flashcart->deinit();
|
||||
}
|
||||
@ -106,54 +127,41 @@ flashcart_error_t flashcart_deinit (void) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress) {
|
||||
flashcart_error_t error;
|
||||
bool flashcart_has_feature (flashcart_features_t feature) {
|
||||
return flashcart->has_feature(feature);
|
||||
}
|
||||
|
||||
if ((rom_path == NULL) || (!file_exists(rom_path)) || (file_get_size(rom_path) < KiB(4))) {
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
flashcart_err_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress) {
|
||||
flashcart_err_t err;
|
||||
|
||||
if (rom_path == NULL) {
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
cart_card_byteswap = byte_swap;
|
||||
error = flashcart->load_rom(rom_path, progress);
|
||||
err = flashcart->load_rom(rom_path, progress);
|
||||
cart_card_byteswap = false;
|
||||
|
||||
return error;
|
||||
return err;
|
||||
}
|
||||
|
||||
flashcart_error_t flashcart_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) {
|
||||
if ((file_path == NULL) || (!file_exists(file_path))) {
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
}
|
||||
|
||||
if ((file_offset % FS_SECTOR_SIZE) != 0) {
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
flashcart_err_t flashcart_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) {
|
||||
if ((file_path == NULL) || ((file_offset % FS_SECTOR_SIZE) != 0)) {
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
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;
|
||||
flashcart_err_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type) {
|
||||
flashcart_err_t err;
|
||||
|
||||
if (save_type >= __FLASHCART_SAVE_TYPE_END) {
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
if ((error = flashcart->set_save_type(save_type)) != FLASHCART_OK) {
|
||||
return error;
|
||||
if ((err = flashcart->set_save_type(save_type)) != FLASHCART_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((save_path == NULL) || (save_type == FLASHCART_SAVE_TYPE_NONE)) {
|
||||
@ -162,19 +170,19 @@ flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t sa
|
||||
|
||||
if (!file_exists(save_path)) {
|
||||
if (file_allocate(save_path, SAVE_SIZE[save_type])) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if (file_fill(save_path, 0xFF)) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (file_get_size(save_path) != SAVE_SIZE[save_type]) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if ((error = flashcart->load_save(save_path)) != FLASHCART_OK) {
|
||||
return error;
|
||||
if ((err = flashcart->load_save(save_path)) != FLASHCART_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (flashcart->set_save_writeback) {
|
||||
@ -182,12 +190,36 @@ flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t sa
|
||||
save_writeback_sectors[i] = 0;
|
||||
}
|
||||
if (file_get_sectors(save_path, save_writeback_sectors_callback)) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if ((error = flashcart->set_save_writeback(save_writeback_sectors)) != FLASHCART_OK) {
|
||||
return error;
|
||||
if ((err = flashcart->set_save_writeback(save_writeback_sectors)) != FLASHCART_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
flashcart_err_t flashcart_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress) {
|
||||
if (!flashcart->load_64dd_ipl) {
|
||||
return FLASHCART_ERR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (ipl_path == NULL) {
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
return flashcart->load_64dd_ipl(ipl_path, progress);
|
||||
}
|
||||
|
||||
flashcart_err_t flashcart_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters) {
|
||||
if (!flashcart->load_64dd_disk) {
|
||||
return FLASHCART_ERR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if ((disk_path == NULL) || (disk_parameters == NULL)) {
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
return flashcart->load_64dd_disk(disk_path, disk_parameters);
|
||||
}
|
||||
|
@ -15,13 +15,19 @@
|
||||
/** @brief Flashcart error enumeration */
|
||||
typedef enum {
|
||||
FLASHCART_OK,
|
||||
FLASHCART_ERROR_NOT_DETECTED,
|
||||
FLASHCART_ERROR_OUTDATED,
|
||||
FLASHCART_ERROR_SD_CARD,
|
||||
FLASHCART_ERROR_ARGS,
|
||||
FLASHCART_ERROR_LOAD,
|
||||
FLASHCART_ERROR_INT,
|
||||
} flashcart_error_t;
|
||||
FLASHCART_ERR_NOT_DETECTED,
|
||||
FLASHCART_ERR_OUTDATED,
|
||||
FLASHCART_ERR_SD_CARD,
|
||||
FLASHCART_ERR_ARGS,
|
||||
FLASHCART_ERR_LOAD,
|
||||
FLASHCART_ERR_INT,
|
||||
FLASHCART_ERR_FUNCTION_NOT_SUPPORTED,
|
||||
} flashcart_err_t;
|
||||
|
||||
/** @brief List of optional supported flashcart features */
|
||||
typedef enum {
|
||||
FLASHCART_FEATURE_64DD,
|
||||
} flashcart_features_t;
|
||||
|
||||
/** @brief Flashcart save type enumeration */
|
||||
typedef enum {
|
||||
@ -36,25 +42,39 @@ typedef enum {
|
||||
__FLASHCART_SAVE_TYPE_END
|
||||
} flashcart_save_type_t;
|
||||
|
||||
typedef struct {
|
||||
bool development_drive;
|
||||
uint8_t disk_type;
|
||||
bool bad_system_area_lbas[24];
|
||||
uint8_t defect_tracks[16][12];
|
||||
} flashcart_disk_parameters_t;
|
||||
|
||||
typedef void flashcart_progress_callback_t (float progress);
|
||||
|
||||
/** @brief Flashcart Structure */
|
||||
typedef struct {
|
||||
flashcart_error_t (*init) (void);
|
||||
flashcart_error_t (*deinit) (void);
|
||||
flashcart_error_t (*load_rom) (char *rom_path, flashcart_progress_callback_t *progress);
|
||||
flashcart_error_t (*load_file) (char *file_path, uint32_t rom_offset, uint32_t file_offset);
|
||||
flashcart_error_t (*load_save) (char *save_path);
|
||||
flashcart_error_t (*set_save_type) (flashcart_save_type_t save_type);
|
||||
flashcart_error_t (*set_save_writeback) (uint32_t *sectors);
|
||||
flashcart_err_t (*init) (void);
|
||||
flashcart_err_t (*deinit) (void);
|
||||
bool (*has_feature) (flashcart_features_t feature);
|
||||
flashcart_err_t (*load_rom) (char *rom_path, flashcart_progress_callback_t *progress);
|
||||
flashcart_err_t (*load_file) (char *file_path, uint32_t rom_offset, uint32_t file_offset);
|
||||
flashcart_err_t (*load_save) (char *save_path);
|
||||
flashcart_err_t (*load_64dd_ipl) (char *ipl_path, flashcart_progress_callback_t *progress);
|
||||
flashcart_err_t (*load_64dd_disk) (char *disk_path, flashcart_disk_parameters_t *disk_parameters);
|
||||
flashcart_err_t (*set_save_type) (flashcart_save_type_t save_type);
|
||||
flashcart_err_t (*set_save_writeback) (uint32_t *sectors);
|
||||
} flashcart_t;
|
||||
|
||||
|
||||
flashcart_error_t flashcart_init (void);
|
||||
flashcart_error_t flashcart_deinit (void);
|
||||
flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress);
|
||||
flashcart_error_t flashcart_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset);
|
||||
flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type);
|
||||
char *flashcart_convert_error_message (flashcart_err_t err);
|
||||
flashcart_err_t flashcart_init (void);
|
||||
flashcart_err_t flashcart_deinit (void);
|
||||
bool flashcart_has_feature (flashcart_features_t feature);
|
||||
flashcart_err_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress);
|
||||
flashcart_err_t flashcart_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset);
|
||||
flashcart_err_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type);
|
||||
flashcart_err_t flashcart_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress);
|
||||
flashcart_err_t flashcart_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters);
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -15,35 +15,89 @@
|
||||
|
||||
#define SRAM_FLASHRAM_ADDRESS (0x08000000)
|
||||
#define ROM_ADDRESS (0x10000000)
|
||||
#define IPL_ADDRESS (0x13BC0000)
|
||||
#define EXTENDED_ADDRESS (0x14000000)
|
||||
#define SHADOW_ADDRESS (0x1FFC0000)
|
||||
#define EEPROM_ADDRESS (0x1FFE2000)
|
||||
|
||||
#define SUPPORTED_MAJOR_VERSION (2)
|
||||
#define SUPPORTED_MINOR_VERSION (16)
|
||||
#define SUPPORTED_MINOR_VERSION (17)
|
||||
#define SUPPORTED_REVISION (0)
|
||||
|
||||
#define DISK_MAPPING_ROM_OFFSET (0x02000000)
|
||||
#define DISK_MAX_SECTORS (126820)
|
||||
|
||||
static flashcart_error_t load_to_flash (FIL *fil, void *address, size_t size, UINT *br, flashcart_progress_callback_t *progress) {
|
||||
#define DISK_TRACKS (1175)
|
||||
#define DISK_HEADS (2)
|
||||
#define DISK_BLOCKS (2)
|
||||
#define DISK_SECTORS_PER_BLOCK (85)
|
||||
#define DISK_ZONES (16)
|
||||
#define DISK_BAD_TRACKS_PER_ZONE (12)
|
||||
#define DISK_TYPES (7)
|
||||
#define DISK_SYSTEM_LBA_COUNT (24)
|
||||
|
||||
#define THB_UNMAPPED (0xFFFFFFFF)
|
||||
#define THB_WRITABLE_FLAG (1 << 31)
|
||||
|
||||
|
||||
static const struct {
|
||||
uint8_t head;
|
||||
uint8_t sector_length;
|
||||
uint8_t tracks;
|
||||
uint16_t track_offset;
|
||||
} zone_mapping[DISK_ZONES] = {
|
||||
{ 0, 232, 158, 0 },
|
||||
{ 0, 216, 158, 158 },
|
||||
{ 0, 208, 149, 316 },
|
||||
{ 0, 192, 149, 465 },
|
||||
{ 0, 176, 149, 614 },
|
||||
{ 0, 160, 149, 763 },
|
||||
{ 0, 144, 149, 912 },
|
||||
{ 0, 128, 114, 1061 },
|
||||
{ 1, 216, 158, 0 },
|
||||
{ 1, 208, 158, 158 },
|
||||
{ 1, 192, 149, 316 },
|
||||
{ 1, 176, 149, 465 },
|
||||
{ 1, 160, 149, 614 },
|
||||
{ 1, 144, 149, 763 },
|
||||
{ 1, 128, 149, 912 },
|
||||
{ 1, 112, 114, 1061 },
|
||||
};
|
||||
static const uint8_t vzone_to_pzone[DISK_TYPES][DISK_ZONES] = {
|
||||
{0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10},
|
||||
{0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11},
|
||||
{0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12},
|
||||
{0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13},
|
||||
{0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8},
|
||||
};
|
||||
static const uint8_t rom_zones[DISK_TYPES] = { 5, 7, 9, 11, 13, 15, 16 };
|
||||
|
||||
|
||||
static uint32_t disk_sectors_start_offset;
|
||||
|
||||
|
||||
static flashcart_err_t load_to_flash (FIL *fil, void *address, size_t size, UINT *br, flashcart_progress_callback_t *progress) {
|
||||
size_t erase_block_size;
|
||||
UINT bp;
|
||||
|
||||
*br = 0;
|
||||
|
||||
if (sc64_ll_flash_get_erase_block_size(&erase_block_size) != SC64_OK) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
while (size > 0) {
|
||||
size_t program_size = MIN(size, erase_block_size);
|
||||
if (sc64_ll_flash_erase_block(address) != SC64_OK) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
if (f_read(fil, address, program_size, &bp) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if (sc64_ll_flash_wait_busy() != SC64_OK) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
if (progress) {
|
||||
progress(f_tell(fil) / (float) (f_size(fil)));
|
||||
@ -56,27 +110,113 @@ static flashcart_error_t load_to_flash (FIL *fil, void *address, size_t size, UI
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t sc64_init (void) {
|
||||
static uint32_t disk_sectors_start (uint32_t offset) {
|
||||
disk_sectors_start_offset = offset;
|
||||
return (offset + (DISK_MAX_SECTORS * sizeof(uint32_t)));
|
||||
}
|
||||
|
||||
static void disk_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 > DISK_MAX_SECTORS) || (offset > sector_count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
io_write(ROM_ADDRESS + disk_sectors_start_offset + (offset * sizeof(uint32_t)), sector);
|
||||
}
|
||||
}
|
||||
|
||||
static bool disk_zone_track_is_bad (uint8_t zone, uint8_t track, flashcart_disk_parameters_t *disk_parameters) {
|
||||
for (int i = 0; i < DISK_BAD_TRACKS_PER_ZONE; i++) {
|
||||
if (disk_parameters->defect_tracks[zone][i] == track) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool disk_system_lba_is_bad (uint16_t lba, flashcart_disk_parameters_t *disk_parameters) {
|
||||
if (lba < DISK_SYSTEM_LBA_COUNT) {
|
||||
return disk_parameters->bad_system_area_lbas[lba];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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) {
|
||||
uint32_t index = (track << 2) | (head << 1) | (block);
|
||||
uint32_t mapping = valid ? ((writable ? THB_WRITABLE_FLAG : 0) | (file_offset & ~(THB_WRITABLE_FLAG))) : THB_UNMAPPED;
|
||||
|
||||
io_write(ROM_ADDRESS + offset + (index * sizeof(uint32_t)), mapping);
|
||||
}
|
||||
|
||||
static uint32_t disk_load_thb_table (uint32_t offset, flashcart_disk_parameters_t *disk_parameters) {
|
||||
int file_offset = 0;
|
||||
|
||||
uint16_t lba = 0;
|
||||
uint8_t starting_block = 0;
|
||||
|
||||
for (uint8_t vzone = 0; vzone < DISK_ZONES; vzone++) {
|
||||
uint8_t pzone = vzone_to_pzone[disk_parameters->disk_type][vzone];
|
||||
|
||||
uint8_t head = zone_mapping[pzone].head;
|
||||
uint8_t sector_length = zone_mapping[pzone].sector_length;
|
||||
uint8_t tracks = zone_mapping[pzone].tracks;
|
||||
uint16_t track_offset = zone_mapping[pzone].track_offset;
|
||||
|
||||
bool reverse = (head != 0);
|
||||
int zone_track_start = (reverse ? (tracks - 1) : 0);
|
||||
int zone_track_end = (reverse ? (-1) : tracks);
|
||||
|
||||
for (int zone_track = zone_track_start; zone_track != zone_track_end; zone_track += (reverse ? (-1) : 1)) {
|
||||
uint16_t track = track_offset + zone_track;
|
||||
|
||||
if (disk_zone_track_is_bad(pzone, zone_track, disk_parameters)) {
|
||||
disk_set_thb_mapping(offset, track, head, 0, false, false, 0);
|
||||
disk_set_thb_mapping(offset, track, head, 1, false, false, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint8_t block = 0; block < DISK_BLOCKS; block += 1) {
|
||||
bool valid = !(disk_system_lba_is_bad(lba, disk_parameters));
|
||||
bool writable = (vzone >= rom_zones[disk_parameters->disk_type]);
|
||||
disk_set_thb_mapping(offset, track, head, (starting_block ^ block), valid, writable, file_offset);
|
||||
file_offset += (sector_length * DISK_SECTORS_PER_BLOCK);
|
||||
lba += 1;
|
||||
}
|
||||
|
||||
starting_block ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (offset + (DISK_TRACKS * DISK_HEADS * DISK_BLOCKS * sizeof(uint32_t)));
|
||||
}
|
||||
|
||||
|
||||
static flashcart_err_t sc64_init (void) {
|
||||
uint16_t major;
|
||||
uint16_t minor;
|
||||
uint32_t revision;
|
||||
|
||||
if (sc64_ll_get_version(&major, &minor, &revision) != SC64_OK) {
|
||||
return FLASHCART_ERROR_OUTDATED;
|
||||
return FLASHCART_ERR_OUTDATED;
|
||||
}
|
||||
if (major != SUPPORTED_MAJOR_VERSION) {
|
||||
return FLASHCART_ERROR_OUTDATED;
|
||||
return FLASHCART_ERR_OUTDATED;
|
||||
}
|
||||
if (minor < SUPPORTED_MINOR_VERSION) {
|
||||
return FLASHCART_ERROR_OUTDATED;
|
||||
return FLASHCART_ERR_OUTDATED;
|
||||
} else if (minor == SUPPORTED_MINOR_VERSION && revision < SUPPORTED_REVISION) {
|
||||
return FLASHCART_ERROR_OUTDATED;
|
||||
return FLASHCART_ERR_OUTDATED;
|
||||
}
|
||||
|
||||
bool writeback_pending;
|
||||
do {
|
||||
if (sc64_ll_writeback_pending(&writeback_pending) != SC64_OK) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
} while (writeback_pending);
|
||||
|
||||
@ -102,14 +242,14 @@ static flashcart_error_t sc64_init (void) {
|
||||
|
||||
for (int i = 0; i < sizeof(default_config) / sizeof(default_config[0]); i++) {
|
||||
if (sc64_ll_set_config(default_config[i].id, default_config[i].value) != SC64_OK) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t sc64_deinit (void) {
|
||||
static flashcart_err_t sc64_deinit (void) {
|
||||
sc64_ll_set_config(CFG_ID_ROM_WRITE_ENABLE, false);
|
||||
|
||||
sc64_ll_lock();
|
||||
@ -117,12 +257,19 @@ static flashcart_error_t sc64_deinit (void) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t sc64_load_rom (char *rom_path, flashcart_progress_callback_t *progress) {
|
||||
static bool sc64_has_feature (flashcart_features_t feature) {
|
||||
switch (feature) {
|
||||
case FLASHCART_FEATURE_64DD: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
static flashcart_err_t sc64_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;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
@ -131,7 +278,7 @@ static flashcart_error_t sc64_load_rom (char *rom_path, flashcart_progress_callb
|
||||
|
||||
if (rom_size > MiB(78)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
bool shadow_enabled = (rom_size > (MiB(64) - KiB(128)));
|
||||
@ -146,7 +293,7 @@ static flashcart_error_t sc64_load_rom (char *rom_path, flashcart_progress_callb
|
||||
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;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if (progress) {
|
||||
progress(f_tell(&fil) / (float) (f_size(&fil)));
|
||||
@ -154,56 +301,56 @@ static flashcart_error_t sc64_load_rom (char *rom_path, flashcart_progress_callb
|
||||
}
|
||||
if (f_tell(&fil) != sdram_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (sc64_ll_set_config(CFG_ID_ROM_SHADOW_ENABLE, shadow_enabled) != SC64_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
if (shadow_enabled) {
|
||||
flashcart_error_t error = load_to_flash(&fil, (void *) (SHADOW_ADDRESS), shadow_size, &br, progress);
|
||||
if (error != FLASHCART_OK) {
|
||||
flashcart_err_t err = load_to_flash(&fil, (void *) (SHADOW_ADDRESS), shadow_size, &br, progress);
|
||||
if (err != FLASHCART_OK) {
|
||||
f_close(&fil);
|
||||
return error;
|
||||
return err;
|
||||
}
|
||||
if (br != shadow_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (sc64_ll_set_config(CFG_ID_ROM_EXTENDED_ENABLE, extended_enabled) != SC64_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
if (extended_enabled) {
|
||||
flashcart_error_t error = load_to_flash(&fil, (void *) (EXTENDED_ADDRESS), extended_size, &br, progress);
|
||||
if (error != FLASHCART_OK) {
|
||||
flashcart_err_t err = load_to_flash(&fil, (void *) (EXTENDED_ADDRESS), extended_size, &br, progress);
|
||||
if (err != FLASHCART_OK) {
|
||||
f_close(&fil);
|
||||
return error;
|
||||
return err;
|
||||
}
|
||||
if (br != extended_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t sc64_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) {
|
||||
static flashcart_err_t sc64_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;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
@ -212,36 +359,36 @@ static flashcart_error_t sc64_load_file (char *file_path, uint32_t rom_offset, u
|
||||
|
||||
if (file_size > (MiB(64) - rom_offset)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
if (f_lseek(&fil, file_offset) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (f_read(&fil, (void *) (ROM_ADDRESS + rom_offset), file_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if (br != file_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t sc64_load_save (char *save_path) {
|
||||
static flashcart_err_t sc64_load_save (char *save_path) {
|
||||
void *address = NULL;
|
||||
uint32_t value;
|
||||
|
||||
if (sc64_ll_get_config(CFG_ID_SAVE_TYPE, &value) != SC64_OK) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
sc64_save_type_t type = (sc64_save_type_t) (value);
|
||||
@ -258,35 +405,120 @@ static flashcart_error_t sc64_load_save (char *save_path) {
|
||||
break;
|
||||
case SAVE_TYPE_NONE:
|
||||
default:
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
size_t save_size = f_size(&fil);
|
||||
|
||||
if (f_read(&fil, address, save_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (br != save_size) {
|
||||
return FLASHCART_ERROR_LOAD;
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t sc64_set_save_type (flashcart_save_type_t save_type) {
|
||||
static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress) {
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(ipl_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
|
||||
size_t ipl_size = f_size(&fil);
|
||||
|
||||
if (ipl_size > MiB(4)) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
size_t chunk_size = KiB(256);
|
||||
for (int offset = 0; offset < ipl_size; offset += chunk_size) {
|
||||
size_t block_size = MIN(ipl_size - offset, chunk_size);
|
||||
if (f_read(&fil, (void *) (IPL_ADDRESS + offset), block_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if (progress) {
|
||||
progress(f_tell(&fil) / (float) (f_size(&fil)));
|
||||
}
|
||||
}
|
||||
if (f_tell(&fil) != ipl_size) {
|
||||
f_close(&fil);
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters) {
|
||||
sc64_disk_mapping_t mapping = { .count = 0 };
|
||||
uint32_t next_mapping_offset = DISK_MAPPING_ROM_OFFSET;
|
||||
|
||||
// TODO: Support loading multiple disks
|
||||
// LOOP START
|
||||
mapping.disks[mapping.count].thb_table = next_mapping_offset;
|
||||
mapping.disks[mapping.count].sector_table = disk_load_thb_table(mapping.disks[mapping.count].thb_table, disk_parameters);
|
||||
next_mapping_offset = disk_sectors_start(mapping.disks[mapping.count].sector_table);
|
||||
if (file_get_sectors(disk_path, disk_sectors_callback)) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
mapping.count += 1;
|
||||
// LOOP END
|
||||
|
||||
if (mapping.count == 0) {
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
if (sc64_ll_set_disk_mapping(&mapping) != SC64_OK) {
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
sc64_drive_type_t drive_type = disk_parameters->development_drive ? DRIVE_TYPE_DEVELOPMENT : DRIVE_TYPE_RETAIL;
|
||||
|
||||
const struct {
|
||||
sc64_cfg_id_t id;
|
||||
uint32_t value;
|
||||
} config[] = {
|
||||
{ CFG_ID_DD_MODE, DD_MODE_FULL },
|
||||
{ CFG_ID_DD_SD_ENABLE, true },
|
||||
{ CFG_ID_DD_DRIVE_TYPE, drive_type },
|
||||
{ CFG_ID_DD_DISK_STATE, DISK_STATE_INSERTED },
|
||||
{ CFG_ID_BUTTON_MODE, BUTTON_MODE_DD_DISK_SWAP },
|
||||
};
|
||||
|
||||
for (int i = 0; i < sizeof(config) / sizeof(config[0]); i++) {
|
||||
if (sc64_ll_set_config(config[i].id, config[i].value) != SC64_OK) {
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_err_t sc64_set_save_type (flashcart_save_type_t save_type) {
|
||||
sc64_save_type_t type;
|
||||
|
||||
switch (save_type) {
|
||||
@ -315,21 +547,21 @@ static flashcart_error_t sc64_set_save_type (flashcart_save_type_t save_type) {
|
||||
type = SAVE_TYPE_FLASHRAM;
|
||||
break;
|
||||
default:
|
||||
return FLASHCART_ERROR_ARGS;
|
||||
return FLASHCART_ERR_ARGS;
|
||||
}
|
||||
|
||||
if (sc64_ll_set_config(CFG_ID_SAVE_TYPE, type) != SC64_OK) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_error_t sc64_set_save_writeback (uint32_t *sectors) {
|
||||
static flashcart_err_t sc64_set_save_writeback (uint32_t *sectors) {
|
||||
pi_dma_write_data(sectors, SC64_BUFFERS->BUFFER, 1024);
|
||||
|
||||
if (sc64_ll_writeback_enable(SC64_BUFFERS->BUFFER) != SC64_OK) {
|
||||
return FLASHCART_ERROR_INT;
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
@ -339,9 +571,12 @@ static flashcart_error_t sc64_set_save_writeback (uint32_t *sectors) {
|
||||
static flashcart_t flashcart_sc64 = {
|
||||
.init = sc64_init,
|
||||
.deinit = sc64_deinit,
|
||||
.has_feature = sc64_has_feature,
|
||||
.load_rom = sc64_load_rom,
|
||||
.load_file = sc64_load_file,
|
||||
.load_save = sc64_load_save,
|
||||
.load_64dd_ipl = sc64_load_64dd_ipl,
|
||||
.load_64dd_disk = sc64_load_64dd_disk,
|
||||
.set_save_type = sc64_set_save_type,
|
||||
.set_save_writeback = sc64_set_save_writeback,
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "../flashcart_utils.h"
|
||||
#include "sc64_ll.h"
|
||||
|
||||
|
||||
@ -23,6 +24,7 @@ typedef enum {
|
||||
CMD_ID_VERSION_GET = 'V',
|
||||
CMD_ID_CONFIG_GET = 'c',
|
||||
CMD_ID_CONFIG_SET = 'C',
|
||||
CMD_ID_DISK_MAPPING_SET = 'D',
|
||||
CMD_ID_WRITEBACK_PENDING = 'w',
|
||||
CMD_ID_WRITEBACK_SD_INFO = 'W',
|
||||
CMD_ID_FLASH_WAIT_BUSY = 'p',
|
||||
@ -91,6 +93,32 @@ sc64_error_t sc64_ll_set_config (sc64_cfg_id_t id, uint32_t value) {
|
||||
return sc64_ll_execute_cmd(&cmd);
|
||||
}
|
||||
|
||||
sc64_error_t sc64_ll_set_disk_mapping (sc64_disk_mapping_t *disk_mapping) {
|
||||
int disk_count = disk_mapping->count;
|
||||
|
||||
if (disk_count <= 0 || disk_count > 4) {
|
||||
return SC64_ERROR_BAD_ARGUMENT;
|
||||
}
|
||||
|
||||
uint32_t info[8] __attribute__((aligned(8)));
|
||||
|
||||
for (int i = 0; i < disk_count; i++) {
|
||||
info[i * 2] = disk_mapping->disks[i].thb_table;
|
||||
info[(i * 2) + 1] = disk_mapping->disks[i].sector_table;
|
||||
}
|
||||
|
||||
uint32_t length = (disk_mapping->count * 2 * sizeof(uint32_t));
|
||||
|
||||
pi_dma_write_data(info, SC64_BUFFERS->BUFFER, length);
|
||||
|
||||
sc64_cmd_t cmd = {
|
||||
.id = CMD_ID_DISK_MAPPING_SET,
|
||||
.arg = { (uint32_t) (SC64_BUFFERS->BUFFER), length }
|
||||
};
|
||||
|
||||
return sc64_ll_execute_cmd(&cmd);
|
||||
}
|
||||
|
||||
sc64_error_t sc64_ll_writeback_pending (bool *pending) {
|
||||
sc64_cmd_t cmd = {
|
||||
.id = CMD_ID_WRITEBACK_PENDING
|
||||
|
@ -110,11 +110,20 @@ typedef enum {
|
||||
BUTTON_MODE_DD_DISK_SWAP,
|
||||
} sc64_button_mode_t;
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
struct {
|
||||
uint32_t thb_table;
|
||||
uint32_t sector_table;
|
||||
} disks[4];
|
||||
} sc64_disk_mapping_t;
|
||||
|
||||
|
||||
void sc64_ll_lock (void);
|
||||
sc64_error_t sc64_ll_get_version (uint16_t *major, uint16_t *minor, uint32_t *revision);
|
||||
sc64_error_t sc64_ll_get_config (sc64_cfg_id_t cfg, uint32_t *value);
|
||||
sc64_error_t sc64_ll_set_config (sc64_cfg_id_t cfg, uint32_t value);
|
||||
sc64_error_t sc64_ll_set_disk_mapping (sc64_disk_mapping_t *disk_mapping);
|
||||
sc64_error_t sc64_ll_writeback_pending (bool *pending);
|
||||
sc64_error_t sc64_ll_writeback_enable (void *address);
|
||||
sc64_error_t sc64_ll_flash_wait_busy (void);
|
||||
|
@ -1,61 +0,0 @@
|
||||
// Implementation based on https://gitlab.com/pixelfx-public/n64-game-id
|
||||
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "hdmi.h"
|
||||
|
||||
|
||||
#define ROM_ADDRESS (0x10000000)
|
||||
#define DDIPL_ADDRESS (0x06000000)
|
||||
#define CONTROLLER_PORT (0)
|
||||
#define GAME_ID_COMMAND (0x1D)
|
||||
|
||||
|
||||
void hdmi_reset_game_id (void) {
|
||||
uint8_t joybus_tx[10];
|
||||
uint8_t joybus_rx[1];
|
||||
|
||||
memset(joybus_tx, 0, sizeof(joybus_tx));
|
||||
|
||||
execute_raw_command(
|
||||
CONTROLLER_PORT,
|
||||
GAME_ID_COMMAND,
|
||||
sizeof(joybus_tx),
|
||||
sizeof(joybus_rx),
|
||||
joybus_tx,
|
||||
joybus_rx
|
||||
);
|
||||
}
|
||||
|
||||
void hdmi_broadcast_game_id (boot_params_t *boot_params) {
|
||||
uint8_t rom_header[0x40] __attribute__((aligned(8)));
|
||||
uint32_t pi_address = ROM_ADDRESS;
|
||||
|
||||
if (boot_params->device_type == BOOT_DEVICE_TYPE_DD) {
|
||||
pi_address = DDIPL_ADDRESS;
|
||||
}
|
||||
|
||||
dma_read_async(rom_header, pi_address, sizeof(rom_header));
|
||||
dma_wait();
|
||||
|
||||
uint8_t joybus_tx[10];
|
||||
uint8_t joybus_rx[1];
|
||||
|
||||
// Copy CRC
|
||||
memcpy(joybus_tx, rom_header + 0x10, 8);
|
||||
|
||||
// Copy media format
|
||||
joybus_tx[8] = rom_header[0x3B];
|
||||
|
||||
// Copy country code
|
||||
joybus_tx[9] = rom_header[0x3E];
|
||||
|
||||
execute_raw_command(
|
||||
CONTROLLER_PORT,
|
||||
GAME_ID_COMMAND,
|
||||
sizeof(joybus_tx),
|
||||
sizeof(joybus_rx),
|
||||
joybus_tx,
|
||||
joybus_rx
|
||||
);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
#ifndef HDMI_H__
|
||||
#define HDMI_H__
|
||||
|
||||
|
||||
#include "boot/boot.h"
|
||||
|
||||
|
||||
void hdmi_reset_game_id (void);
|
||||
void hdmi_broadcast_game_id (boot_params_t *boot_params);
|
||||
|
||||
|
||||
#endif
|
@ -1,19 +1,14 @@
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "boot/boot.h"
|
||||
#include "hdmi/hdmi.h"
|
||||
#include "menu/menu.h"
|
||||
|
||||
|
||||
int main (void) {
|
||||
boot_params_t boot_params;
|
||||
|
||||
hdmi_reset_game_id();
|
||||
|
||||
menu_run(&boot_params);
|
||||
|
||||
hdmi_broadcast_game_id(&boot_params);
|
||||
|
||||
disable_interrupts();
|
||||
|
||||
boot(&boot_params);
|
||||
|
@ -3,8 +3,11 @@
|
||||
#include "actions.h"
|
||||
|
||||
|
||||
#define ACTIONS_REPEAT_DELAY 10
|
||||
#define JOYSTICK_DEADZONE 32
|
||||
#define ACTIONS_REPEAT_DELAY (8)
|
||||
|
||||
|
||||
static int dir_repeat_delay;
|
||||
static joypad_8way_t last_dir = JOYPAD_8WAY_NONE;
|
||||
|
||||
|
||||
static void actions_clear (menu_t *menu) {
|
||||
@ -12,7 +15,8 @@ static void actions_clear (menu_t *menu) {
|
||||
menu->actions.go_down = false;
|
||||
menu->actions.go_left = false;
|
||||
menu->actions.go_right = false;
|
||||
menu->actions.fast = false;
|
||||
menu->actions.go_fast = false;
|
||||
|
||||
menu->actions.enter = false;
|
||||
menu->actions.back = false;
|
||||
menu->actions.options = false;
|
||||
@ -20,93 +24,84 @@ static void actions_clear (menu_t *menu) {
|
||||
menu->actions.settings = false;
|
||||
}
|
||||
|
||||
static void actions_update_direction (menu_t *menu) {
|
||||
joypad_8way_t held_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_DPAD | JOYPAD_2D_STICK);
|
||||
joypad_8way_t fast_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_C);
|
||||
|
||||
void actions_update (menu_t *menu) {
|
||||
controller_scan();
|
||||
|
||||
struct controller_data down = get_keys_down();
|
||||
struct controller_data held = get_keys_held();
|
||||
struct controller_data pressed = get_keys_pressed();
|
||||
|
||||
if (down.c[0].err != ERROR_NONE) {
|
||||
return;
|
||||
if (fast_dir != JOYPAD_8WAY_NONE) {
|
||||
held_dir = fast_dir;
|
||||
menu->actions.go_fast = true;
|
||||
}
|
||||
|
||||
actions_clear(menu);
|
||||
joypad_8way_t final_dir = held_dir;
|
||||
|
||||
if (down.c[0].up || down.c[0].C_up) {
|
||||
menu->actions.go_up = true;
|
||||
menu->actions.vertical_held_counter = 0;
|
||||
if (down.c[0].C_up) {
|
||||
menu->actions.fast = true;
|
||||
}
|
||||
} else if (down.c[0].down || down.c[0].C_down) {
|
||||
menu->actions.go_down = true;
|
||||
menu->actions.vertical_held_counter = 0;
|
||||
if (down.c[0].C_down) {
|
||||
menu->actions.fast = true;
|
||||
}
|
||||
} else if (held.c[0].up || held.c[0].C_up) {
|
||||
menu->actions.vertical_held_counter += 1;
|
||||
if (menu->actions.vertical_held_counter >= ACTIONS_REPEAT_DELAY) {
|
||||
menu->actions.go_up = true;
|
||||
if (held.c[0].C_up) {
|
||||
menu->actions.fast = true;
|
||||
}
|
||||
}
|
||||
} else if (held.c[0].down || held.c[0].C_down) {
|
||||
menu->actions.vertical_held_counter += 1;
|
||||
if (menu->actions.vertical_held_counter >= ACTIONS_REPEAT_DELAY) {
|
||||
menu->actions.go_down = true;
|
||||
if (held.c[0].C_down) {
|
||||
menu->actions.fast = true;
|
||||
}
|
||||
}
|
||||
} else if (pressed.c[0].y > +JOYSTICK_DEADZONE) { // TODO: requires improvement for responsiveness
|
||||
menu->actions.vertical_held_counter += 1;
|
||||
if (menu->actions.vertical_held_counter >= ACTIONS_REPEAT_DELAY / 2) {
|
||||
menu->actions.go_up = true;
|
||||
if (pressed.c[0].y < +75) {
|
||||
menu->actions.vertical_held_counter = 0;
|
||||
}
|
||||
}
|
||||
} else if (pressed.c[0].y < -JOYSTICK_DEADZONE) { // TODO: requires improvement for responsiveness
|
||||
menu->actions.vertical_held_counter += 1;
|
||||
if (menu->actions.vertical_held_counter >= ACTIONS_REPEAT_DELAY / 2) {
|
||||
menu->actions.go_down = true;
|
||||
if (pressed.c[0].y > -75) {
|
||||
menu->actions.vertical_held_counter = 0;
|
||||
}
|
||||
}
|
||||
if ((last_dir != held_dir) && (last_dir == JOYPAD_8WAY_NONE)) {
|
||||
dir_repeat_delay = ACTIONS_REPEAT_DELAY;
|
||||
} else if (dir_repeat_delay > 0) {
|
||||
final_dir = JOYPAD_8WAY_NONE;
|
||||
}
|
||||
|
||||
if (down.c[0].left) {
|
||||
menu->actions.go_left = true;
|
||||
menu->actions.horizontal_held_counter = 0;
|
||||
} else if (down.c[0].right) {
|
||||
switch (final_dir) {
|
||||
case JOYPAD_8WAY_NONE:
|
||||
break;
|
||||
case JOYPAD_8WAY_RIGHT:
|
||||
menu->actions.go_right = true;
|
||||
menu->actions.horizontal_held_counter = 0;
|
||||
} else if (held.c[0].left) {
|
||||
menu->actions.horizontal_held_counter += 1;
|
||||
if (menu->actions.horizontal_held_counter >= ACTIONS_REPEAT_DELAY) {
|
||||
menu->actions.go_left = true;
|
||||
}
|
||||
} else if (held.c[0].right) {
|
||||
menu->actions.horizontal_held_counter += 1;
|
||||
if (menu->actions.horizontal_held_counter >= ACTIONS_REPEAT_DELAY) {
|
||||
break;
|
||||
case JOYPAD_8WAY_UP_RIGHT:
|
||||
menu->actions.go_up = true;
|
||||
menu->actions.go_right = true;
|
||||
}
|
||||
break;
|
||||
case JOYPAD_8WAY_UP:
|
||||
menu->actions.go_up = true;
|
||||
break;
|
||||
case JOYPAD_8WAY_UP_LEFT:
|
||||
menu->actions.go_up = true;
|
||||
menu->actions.go_left = true;
|
||||
break;
|
||||
case JOYPAD_8WAY_LEFT:
|
||||
menu->actions.go_left = true;
|
||||
break;
|
||||
case JOYPAD_8WAY_DOWN_LEFT:
|
||||
menu->actions.go_down = true;
|
||||
menu->actions.go_left = true;
|
||||
break;
|
||||
case JOYPAD_8WAY_DOWN:
|
||||
menu->actions.go_down = true;
|
||||
break;
|
||||
case JOYPAD_8WAY_DOWN_RIGHT:
|
||||
menu->actions.go_down = true;
|
||||
menu->actions.go_right = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (down.c[0].A) {
|
||||
if (dir_repeat_delay > 0) {
|
||||
dir_repeat_delay -= 1;
|
||||
}
|
||||
|
||||
last_dir = held_dir;
|
||||
}
|
||||
|
||||
static void actions_update_buttons (menu_t *menu) {
|
||||
joypad_buttons_t pressed = joypad_get_buttons_pressed(JOYPAD_PORT_1);
|
||||
|
||||
if (pressed.a) {
|
||||
menu->actions.enter = true;
|
||||
} else if (down.c[0].B) {
|
||||
} else if (pressed.b) {
|
||||
menu->actions.back = true;
|
||||
} else if (down.c[0].R) {
|
||||
} else if (pressed.r) {
|
||||
menu->actions.options = true;
|
||||
} else if (down.c[0].L) {
|
||||
} else if (pressed.l) {
|
||||
menu->actions.system_info = true;
|
||||
} else if (down.c[0].start) {
|
||||
} else if (pressed.start) {
|
||||
menu->actions.settings = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void actions_update (menu_t *menu) {
|
||||
joypad_poll();
|
||||
|
||||
actions_clear(menu);
|
||||
actions_update_direction(menu);
|
||||
actions_update_buttons(menu);
|
||||
}
|
||||
|
@ -1,75 +1,97 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "cart_load.h"
|
||||
#include "path.h"
|
||||
#include "utils/fs.h"
|
||||
#include "utils/utils.h"
|
||||
|
||||
|
||||
#ifndef SAVES_SUBDIRECTORY
|
||||
#define SAVES_SUBDIRECTORY "saves"
|
||||
#define EMU_LOCATION "/emulators"
|
||||
#endif
|
||||
#ifndef DDIPL_LOCATION
|
||||
#define DDIPL_LOCATION "/menu/64ddipl"
|
||||
#endif
|
||||
#ifndef EMU_LOCATION
|
||||
#define EMU_LOCATION "/menu/emulators"
|
||||
#endif
|
||||
|
||||
|
||||
static bool create_saves_subdirectory (menu_t *menu) {
|
||||
path_t *save_folder_path = path_clone_push(menu->browser.directory, SAVES_SUBDIRECTORY);
|
||||
static bool is_64dd_connected (void) {
|
||||
return (
|
||||
((io_read(0x05000540) & 0x0000FFFF) == 0x0000) ||
|
||||
(io_read(0x06001010) == 0x2129FFF8)
|
||||
);
|
||||
}
|
||||
|
||||
static bool create_saves_subdirectory (path_t *path) {
|
||||
path_t *save_folder_path = path_clone(path);
|
||||
path_pop(save_folder_path);
|
||||
path_push(save_folder_path, SAVES_SUBDIRECTORY);
|
||||
bool error = directory_create(path_get(save_folder_path));
|
||||
path_free(save_folder_path);
|
||||
return error;
|
||||
}
|
||||
|
||||
static flashcart_save_type_t convert_save_type (rom_header_t *header) {
|
||||
switch (rom_db_match_save_type(*header)) {
|
||||
case DB_SAVE_TYPE_EEPROM_4K:
|
||||
return FLASHCART_SAVE_TYPE_EEPROM_4K;
|
||||
case DB_SAVE_TYPE_EEPROM_16K:
|
||||
return FLASHCART_SAVE_TYPE_EEPROM_16K;
|
||||
case DB_SAVE_TYPE_SRAM:
|
||||
return FLASHCART_SAVE_TYPE_SRAM;
|
||||
case DB_SAVE_TYPE_SRAM_BANKED:
|
||||
return FLASHCART_SAVE_TYPE_SRAM_BANKED;
|
||||
case DB_SAVE_TYPE_SRAM_128K:
|
||||
return FLASHCART_SAVE_TYPE_SRAM_128K;
|
||||
case DB_SAVE_TYPE_FLASHRAM:
|
||||
return FLASHCART_SAVE_TYPE_FLASHRAM;
|
||||
default:
|
||||
return FLASHCART_SAVE_TYPE_NONE;
|
||||
static flashcart_save_type_t convert_save_type (rom_info_t *info) {
|
||||
switch (info->save_type) {
|
||||
case SAVE_TYPE_EEPROM_4K: return FLASHCART_SAVE_TYPE_EEPROM_4K;
|
||||
case SAVE_TYPE_EEPROM_16K: return FLASHCART_SAVE_TYPE_EEPROM_16K;
|
||||
case SAVE_TYPE_SRAM: return FLASHCART_SAVE_TYPE_SRAM;
|
||||
case SAVE_TYPE_SRAM_BANKED: return FLASHCART_SAVE_TYPE_SRAM_BANKED;
|
||||
case SAVE_TYPE_SRAM_128K: return FLASHCART_SAVE_TYPE_SRAM_128K;
|
||||
case SAVE_TYPE_FLASHRAM: return FLASHCART_SAVE_TYPE_FLASHRAM;
|
||||
case SAVE_TYPE_FLASHRAM_PKST2: return FLASHCART_SAVE_TYPE_FLASHRAM_PKST2;
|
||||
default: return FLASHCART_SAVE_TYPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char *cart_load_convert_error_message (cart_load_err_t err) {
|
||||
switch (err) {
|
||||
case CART_LOAD_ERR_SAVES_SUBDIR: return "Could not create saves subdirectory";
|
||||
case CART_LOAD_ERR_ROM: return "Error during ROM loading";
|
||||
case CART_LOAD_ERR_SAVE: return "Error during save creation or loading";
|
||||
case CART_LOAD_ERR_EMU_NOT_FOUND: return "Emulator ROM not found";
|
||||
case CART_LOAD_ERR_EMU: return "Error during emulator ROM loading";
|
||||
case CART_LOAD_ERR_EMU_ROM: return "Error during emulated ROM loading";
|
||||
default: return "Unknown cart load error";
|
||||
case CART_LOAD_OK: return "Cart load OK";
|
||||
case CART_LOAD_ERR_ROM_LOAD_FAIL: return "Error occured during ROM loading";
|
||||
case CART_LOAD_ERR_SAVE_LOAD_FAIL: return "Error occured during save loading";
|
||||
case CART_LOAD_ERR_64DD_PRESENT: return "64DD accessory is connected to the N64";
|
||||
case CART_LOAD_ERR_64DD_IPL_NOT_FOUND: return "Required 64DD IPL file was not found";
|
||||
case CART_LOAD_ERR_64DD_IPL_LOAD_FAIL: return "Error occured during 64DD IPL loading";
|
||||
case CART_LOAD_ERR_64DD_DISK_LOAD_FAIL: return "Error occured during 64DD disk loading";
|
||||
case CART_LOAD_ERR_EMU_NOT_FOUND: return "Required emulator file was not found";
|
||||
case CART_LOAD_ERR_EMU_LOAD_FAIL: return "Error occured during emulator ROM loading";
|
||||
case CART_LOAD_ERR_EMU_ROM_LOAD_FAIL: return "Error occured during emulated ROM loading";
|
||||
case CART_LOAD_ERR_CREATE_SAVES_SUBDIR_FAIL: return "Couldn't create saves subdirectory";
|
||||
case CART_LOAD_ERR_EXP_PAK_NOT_FOUND: return "Mandatory Expansion Pak accessory was not found";
|
||||
case CART_LOAD_ERR_FUNCTION_NOT_SUPPORTED: return "Your flashcart doesn't support required functionality";
|
||||
default: return "Unknown error [CART_LOAD]";
|
||||
}
|
||||
}
|
||||
|
||||
cart_load_err_t cart_load_n64_rom_and_save (menu_t *menu, rom_header_t *header, flashcart_progress_callback_t progress) {
|
||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
bool byte_swap = (header->config_flags == ROM_MID_BIG_ENDIAN);
|
||||
flashcart_save_type_t save_type = convert_save_type(header);
|
||||
cart_load_err_t cart_load_n64_rom_and_save (menu_t *menu, flashcart_progress_callback_t progress) {
|
||||
path_t *path = path_clone(menu->load.rom_path);
|
||||
|
||||
menu->flashcart_error = flashcart_load_rom(path_get(path), byte_swap, progress);
|
||||
if (menu->flashcart_error != FLASHCART_OK) {
|
||||
bool byte_swap = (menu->load.rom_info.endianness == ENDIANNESS_BYTE_SWAP);
|
||||
flashcart_save_type_t save_type = convert_save_type(&menu->load.rom_info);
|
||||
|
||||
menu->flashcart_err = flashcart_load_rom(path_get(path), byte_swap, progress);
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_ROM;
|
||||
return CART_LOAD_ERR_ROM_LOAD_FAIL;
|
||||
}
|
||||
|
||||
path_ext_replace(path, "sav");
|
||||
if (menu->settings.use_saves_folder) {
|
||||
path_push_subdir(path, SAVES_SUBDIRECTORY);
|
||||
if ((save_type != FLASHCART_SAVE_TYPE_NONE) && create_saves_subdirectory(menu)) {
|
||||
if ((save_type != FLASHCART_SAVE_TYPE_NONE) && create_saves_subdirectory(path)) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_SAVES_SUBDIR;
|
||||
return CART_LOAD_ERR_CREATE_SAVES_SUBDIR_FAIL;
|
||||
}
|
||||
path_push_subdir(path, SAVES_SUBDIRECTORY);
|
||||
}
|
||||
|
||||
menu->flashcart_error = flashcart_load_save(path_get(path), save_type);
|
||||
if (menu->flashcart_error != FLASHCART_OK) {
|
||||
menu->flashcart_err = flashcart_load_save(path_get(path), save_type);
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_SAVE;
|
||||
return CART_LOAD_ERR_SAVE_LOAD_FAIL;
|
||||
}
|
||||
|
||||
path_free(path);
|
||||
@ -77,8 +99,63 @@ cart_load_err_t cart_load_n64_rom_and_save (menu_t *menu, rom_header_t *header,
|
||||
return CART_LOAD_OK;
|
||||
}
|
||||
|
||||
cart_load_err_t cart_load_64dd_ipl_and_disk (menu_t *menu, flashcart_progress_callback_t progress) {
|
||||
if (!flashcart_has_feature(FLASHCART_FEATURE_64DD)) {
|
||||
return CART_LOAD_ERR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (is_64dd_connected()) {
|
||||
return CART_LOAD_ERR_64DD_PRESENT;
|
||||
}
|
||||
|
||||
if (!is_memory_expanded()) {
|
||||
return CART_LOAD_ERR_EXP_PAK_NOT_FOUND;
|
||||
}
|
||||
|
||||
path_t *path = path_init("sd:/", DDIPL_LOCATION);
|
||||
flashcart_disk_parameters_t disk_parameters;
|
||||
|
||||
disk_parameters.development_drive = (menu->load.disk_info.region == DISK_REGION_DEVELOPMENT);
|
||||
disk_parameters.disk_type = menu->load.disk_info.disk_type;
|
||||
memcpy(disk_parameters.bad_system_area_lbas, menu->load.disk_info.bad_system_area_lbas, sizeof(disk_parameters.bad_system_area_lbas));
|
||||
memcpy(disk_parameters.defect_tracks, menu->load.disk_info.defect_tracks, sizeof(disk_parameters.defect_tracks));
|
||||
|
||||
switch (menu->load.disk_info.region) {
|
||||
case DISK_REGION_DEVELOPMENT:
|
||||
path_push(path, "NDXJ0.n64");
|
||||
break;
|
||||
case DISK_REGION_JAPANESE:
|
||||
path_push(path, "NDDJ2.n64");
|
||||
break;
|
||||
case DISK_REGION_USA:
|
||||
path_push(path, "NDDE0.n64");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!file_exists(path_get(path))) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_64DD_IPL_NOT_FOUND;
|
||||
}
|
||||
|
||||
menu->flashcart_err = flashcart_load_64dd_ipl(path_get(path), progress);
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_64DD_IPL_LOAD_FAIL;
|
||||
}
|
||||
|
||||
path_free(path);
|
||||
|
||||
menu->flashcart_err = flashcart_load_64dd_disk(path_get(menu->load.disk_path), &disk_parameters);
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
return CART_LOAD_ERR_64DD_DISK_LOAD_FAIL;
|
||||
}
|
||||
|
||||
return CART_LOAD_OK;
|
||||
}
|
||||
|
||||
cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type, flashcart_progress_callback_t progress) {
|
||||
path_t *path = path_init("sd:/", EMU_LOCATION);
|
||||
|
||||
flashcart_save_type_t save_type = FLASHCART_SAVE_TYPE_NONE;
|
||||
uint32_t emulated_rom_offset = 0x200000;
|
||||
uint32_t emulated_file_offset = 0;
|
||||
@ -111,10 +188,10 @@ cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type,
|
||||
return CART_LOAD_ERR_EMU_NOT_FOUND;
|
||||
}
|
||||
|
||||
menu->flashcart_error = flashcart_load_rom(path_get(path), false, progress);
|
||||
if (menu->flashcart_error != FLASHCART_OK) {
|
||||
menu->flashcart_err = flashcart_load_rom(path_get(path), false, progress);
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_EMU;
|
||||
return CART_LOAD_ERR_EMU_LOAD_FAIL;
|
||||
}
|
||||
|
||||
path_free(path);
|
||||
@ -123,32 +200,32 @@ 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.
|
||||
// NOTE: 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:
|
||||
break;
|
||||
}
|
||||
|
||||
menu->flashcart_error = flashcart_load_file(path_get(path), emulated_rom_offset, emulated_file_offset);
|
||||
if (menu->flashcart_error != FLASHCART_OK) {
|
||||
menu->flashcart_err = flashcart_load_file(path_get(path), emulated_rom_offset, emulated_file_offset);
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_EMU_ROM;
|
||||
return CART_LOAD_ERR_EMU_ROM_LOAD_FAIL;
|
||||
}
|
||||
|
||||
path_ext_replace(path, "sav");
|
||||
if (menu->settings.use_saves_folder) {
|
||||
path_push_subdir(path, SAVES_SUBDIRECTORY);
|
||||
if ((save_type != FLASHCART_SAVE_TYPE_NONE) && create_saves_subdirectory(menu)) {
|
||||
if ((save_type != FLASHCART_SAVE_TYPE_NONE) && create_saves_subdirectory(path)) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_SAVES_SUBDIR;
|
||||
return CART_LOAD_ERR_CREATE_SAVES_SUBDIR_FAIL;
|
||||
}
|
||||
path_push_subdir(path, SAVES_SUBDIRECTORY);
|
||||
}
|
||||
|
||||
menu->flashcart_error = flashcart_load_save(path_get(path), save_type);
|
||||
if (menu->flashcart_error != FLASHCART_OK) {
|
||||
menu->flashcart_err = flashcart_load_save(path_get(path), save_type);
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
path_free(path);
|
||||
return CART_LOAD_ERR_SAVE;
|
||||
return CART_LOAD_ERR_SAVE_LOAD_FAIL;
|
||||
}
|
||||
|
||||
path_free(path);
|
||||
|
@ -8,19 +8,26 @@
|
||||
#define CART_LOAD_H__
|
||||
|
||||
|
||||
#include "disk_info.h"
|
||||
#include "flashcart/flashcart.h"
|
||||
#include "menu_state.h"
|
||||
#include "rom_database.h"
|
||||
#include "rom_info.h"
|
||||
|
||||
|
||||
typedef enum {
|
||||
CART_LOAD_OK,
|
||||
CART_LOAD_ERR_SAVES_SUBDIR,
|
||||
CART_LOAD_ERR_ROM,
|
||||
CART_LOAD_ERR_SAVE,
|
||||
CART_LOAD_ERR_ROM_LOAD_FAIL,
|
||||
CART_LOAD_ERR_SAVE_LOAD_FAIL,
|
||||
CART_LOAD_ERR_64DD_PRESENT,
|
||||
CART_LOAD_ERR_64DD_IPL_NOT_FOUND,
|
||||
CART_LOAD_ERR_64DD_IPL_LOAD_FAIL,
|
||||
CART_LOAD_ERR_64DD_DISK_LOAD_FAIL,
|
||||
CART_LOAD_ERR_EMU_NOT_FOUND,
|
||||
CART_LOAD_ERR_EMU,
|
||||
CART_LOAD_ERR_EMU_ROM,
|
||||
CART_LOAD_ERR_EMU_LOAD_FAIL,
|
||||
CART_LOAD_ERR_EMU_ROM_LOAD_FAIL,
|
||||
CART_LOAD_ERR_CREATE_SAVES_SUBDIR_FAIL,
|
||||
CART_LOAD_ERR_EXP_PAK_NOT_FOUND,
|
||||
CART_LOAD_ERR_FUNCTION_NOT_SUPPORTED,
|
||||
} cart_load_err_t;
|
||||
|
||||
typedef enum {
|
||||
@ -33,7 +40,8 @@ typedef enum {
|
||||
|
||||
|
||||
char *cart_load_convert_error_message (cart_load_err_t err);
|
||||
cart_load_err_t cart_load_n64_rom_and_save (menu_t *menu, rom_header_t *header, flashcart_progress_callback_t progress);
|
||||
cart_load_err_t cart_load_n64_rom_and_save (menu_t *menu, flashcart_progress_callback_t progress);
|
||||
cart_load_err_t cart_load_64dd_ipl_and_disk (menu_t *menu, flashcart_progress_callback_t progress);
|
||||
cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type, flashcart_progress_callback_t progress);
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ typedef struct {
|
||||
surface_t *image;
|
||||
} component_boxart_t;
|
||||
|
||||
component_boxart_t *component_boxart_init (uint8_t media_type, uint16_t id);
|
||||
component_boxart_t *component_boxart_init (char *game_code);
|
||||
void component_boxart_free (component_boxart_t *b);
|
||||
void component_boxart_draw (component_boxart_t *b);
|
||||
|
||||
|
@ -17,24 +17,23 @@ static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void
|
||||
}
|
||||
|
||||
|
||||
component_boxart_t *component_boxart_init (uint8_t media_type, uint16_t id) {
|
||||
component_boxart_t *component_boxart_init (char *game_code) {
|
||||
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));
|
||||
|
||||
// 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));
|
||||
}
|
||||
char *path = alloca(strlen(BOXART_DIRECTORY) + 1 + 7 + 1);
|
||||
|
||||
// TODO: This is bad, we should only check for 3 letter codes
|
||||
sprintf(path, "%s/%.3s.png", BOXART_DIRECTORY, game_code);
|
||||
if (png_decoder_start(path, BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) != PNG_OK) {
|
||||
sprintf(path, "%s/%.2s.png", BOXART_DIRECTORY, &game_code[1]);
|
||||
if (png_decoder_start(path, BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) != PNG_OK) {
|
||||
free(b);
|
||||
b = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
@ -122,8 +122,8 @@ void component_messagebox_draw (char *fmt, ...) {
|
||||
}, FNT_DEFAULT, formatted, ¶graph_nbytes);
|
||||
|
||||
component_dialog_draw(
|
||||
paragraph->bbox[2] - paragraph->bbox[0] + MESSAGEBOX_MARGIN,
|
||||
paragraph->bbox[3] - paragraph->bbox[1] + MESSAGEBOX_MARGIN
|
||||
paragraph->bbox.x1 - paragraph->bbox.x0 + MESSAGEBOX_MARGIN,
|
||||
paragraph->bbox.y1 - paragraph->bbox.y0 + MESSAGEBOX_MARGIN
|
||||
);
|
||||
|
||||
rdpq_paragraph_render(paragraph, DISPLAY_CENTER_X - (MESSAGEBOX_MAX_WIDTH / 2), VISIBLE_AREA_Y0);
|
||||
|
@ -69,15 +69,15 @@ void component_context_menu_draw (component_context_menu_t *cm) {
|
||||
|
||||
rdpq_paragraph_t *layout = rdpq_paragraph_builder_end();
|
||||
|
||||
int width = layout->bbox[2] - layout->bbox[0] + MESSAGEBOX_MARGIN;
|
||||
int height = layout->bbox[3] - layout->bbox[1] + MESSAGEBOX_MARGIN;
|
||||
int width = layout->bbox.x1 - layout->bbox.x0 + MESSAGEBOX_MARGIN;
|
||||
int height = layout->bbox.y1 - layout->bbox.y0 + MESSAGEBOX_MARGIN;
|
||||
|
||||
component_dialog_draw(width, height);
|
||||
|
||||
int highlight_x0 = DISPLAY_CENTER_X - (width / 2);
|
||||
int highlight_x1 = DISPLAY_CENTER_X + (width / 2);
|
||||
int highlight_height = (layout->bbox[3] - layout->bbox[1]) / layout->nlines;
|
||||
int highlight_y = VISIBLE_AREA_Y0 + layout->bbox[1] + ((cm->selected) * highlight_height);
|
||||
int highlight_height = (layout->bbox.y1 - layout->bbox.y0) / layout->nlines;
|
||||
int highlight_y = VISIBLE_AREA_Y0 + layout->bbox.y0 + ((cm->selected) * highlight_height);
|
||||
|
||||
component_box_draw(
|
||||
highlight_x0,
|
||||
|
@ -105,7 +105,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
||||
|
||||
layout = rdpq_paragraph_builder_end();
|
||||
|
||||
int highlight_height = (layout->bbox[3] - layout->bbox[1]) / layout->nlines;
|
||||
int highlight_height = (layout->bbox.y1 - layout->bbox.y0) / layout->nlines;
|
||||
int highlight_y = VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + ((selected - starting_position) * highlight_height);
|
||||
|
||||
component_box_draw(
|
||||
|
212
src/menu/disk_info.c
Normal file
212
src/menu/disk_info.c
Normal file
@ -0,0 +1,212 @@
|
||||
#include <fatfs/ff.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "disk_info.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
|
||||
#define SECTORS_PER_BLOCK (85)
|
||||
#define DISK_ZONES (16)
|
||||
#define DISK_BAD_TRACKS_PER_ZONE (12)
|
||||
|
||||
#define SYSTEM_AREA_SECTOR_LENGTH (232)
|
||||
#define SYSTEM_AREA_LBA_LENGTH (SYSTEM_AREA_SECTOR_LENGTH * SECTORS_PER_BLOCK)
|
||||
#define SYSTEM_AREA_LBA_COUNT (24)
|
||||
|
||||
#define SYSTEM_DATA_LBA_COUNT (8)
|
||||
#define DISK_ID_LBA_COUNT (2)
|
||||
|
||||
#define DEV_SYSTEM_DATA_SECTOR_LENGTH (192)
|
||||
#define RETAIL_SYSTEM_DATA_SECTOR_LENGTH (232)
|
||||
|
||||
#define REGION_ID_DEVELOPMENT (0x00000000)
|
||||
#define REGION_ID_JAPANESE (0xE848D316)
|
||||
#define REGION_ID_USA (0x2263EE56)
|
||||
|
||||
#define GET_U32(b) (((b)[0] << 24) | ((b)[1] << 16) | ((b)[2] << 8) | (b)[3])
|
||||
|
||||
|
||||
static const int tracks_per_zone[DISK_ZONES] = {
|
||||
158, 158, 149, 149, 149, 149, 149, 114, 158, 158, 149, 149, 149, 149, 149, 114
|
||||
};
|
||||
static const int system_data_lbas[SYSTEM_DATA_LBA_COUNT] = {
|
||||
9, 8, 1, 0, 11, 10, 3, 2
|
||||
};
|
||||
static const int disk_id_lbas[DISK_ID_LBA_COUNT] = {
|
||||
15, 14
|
||||
};
|
||||
|
||||
|
||||
static bool load_system_area_lba (FIL *fil, int lba, uint8_t *buffer) {
|
||||
UINT bytes_read;
|
||||
if (lba >= SYSTEM_AREA_LBA_COUNT) {
|
||||
return true;
|
||||
}
|
||||
if (f_lseek(fil, SYSTEM_AREA_LBA_LENGTH * lba) != FR_OK) {
|
||||
return true;
|
||||
}
|
||||
if (f_read(fil, buffer, SYSTEM_AREA_LBA_LENGTH, &bytes_read) != FR_OK) {
|
||||
return true;
|
||||
}
|
||||
if (bytes_read != SYSTEM_AREA_LBA_LENGTH) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool verify_system_area_lba (uint8_t *buffer, int sector_length) {
|
||||
for (int sector = 1; sector < SECTORS_PER_BLOCK; sector++) {
|
||||
for (int i = 0; i < sector_length; i++) {
|
||||
if (buffer[i] != buffer[(sector * sector_length) + i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool verify_system_data_lba (uint8_t *buffer) {
|
||||
return (
|
||||
(buffer[4] == 0x10) &&
|
||||
((buffer[5] & 0xF0) == 0x10) &&
|
||||
((buffer[5] & 0x0F) <= DISK_TYPE_6) &&
|
||||
(GET_U32(&buffer[24]) == 0xFFFFFFFF)
|
||||
);
|
||||
}
|
||||
|
||||
static bool set_defect_tracks (uint8_t *buffer, disk_info_t *disk_info) {
|
||||
for (int head_zone = 0; head_zone < DISK_ZONES; head_zone++) {
|
||||
uint8_t start = ((head_zone == 0) ? 0 : buffer[7 + head_zone]);
|
||||
uint8_t end = buffer[7 + head_zone + 1];
|
||||
if ((end - start) > DISK_BAD_TRACKS_PER_ZONE) {
|
||||
return false;
|
||||
}
|
||||
int track = 0;
|
||||
for (int offset = start; offset < end; offset++) {
|
||||
disk_info->defect_tracks[head_zone][track++] = buffer[0x20 + offset];
|
||||
}
|
||||
for (int i = 0; track < DISK_BAD_TRACKS_PER_ZONE; i++) {
|
||||
disk_info->defect_tracks[head_zone][track++] = tracks_per_zone[head_zone] - i - 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void update_bad_system_area_lbas (disk_info_t *disk_info) {
|
||||
if (disk_info->region == DISK_REGION_DEVELOPMENT) {
|
||||
disk_info->bad_system_area_lbas[0] = true;
|
||||
disk_info->bad_system_area_lbas[1] = true;
|
||||
disk_info->bad_system_area_lbas[8] = true;
|
||||
disk_info->bad_system_area_lbas[9] = true;
|
||||
} else {
|
||||
disk_info->bad_system_area_lbas[2] = true;
|
||||
disk_info->bad_system_area_lbas[3] = true;
|
||||
disk_info->bad_system_area_lbas[10] = true;
|
||||
disk_info->bad_system_area_lbas[11] = true;
|
||||
disk_info->bad_system_area_lbas[12] = true;
|
||||
}
|
||||
|
||||
for (int lba = 16; lba < SYSTEM_AREA_LBA_COUNT; lba++) {
|
||||
disk_info->bad_system_area_lbas[lba] = true;
|
||||
}
|
||||
}
|
||||
|
||||
static disk_err_t load_and_verify_system_data_lba (FIL *fil, disk_info_t *disk_info) {
|
||||
uint8_t buffer[SYSTEM_AREA_LBA_LENGTH];
|
||||
int sector_length;
|
||||
|
||||
bool valid_system_data_lba_found = false;
|
||||
|
||||
for (int i = 0; i < SYSTEM_DATA_LBA_COUNT; i++) {
|
||||
int lba = system_data_lbas[i];
|
||||
|
||||
if (load_system_area_lba(fil, lba, buffer)) {
|
||||
return DISK_ERR_IO;
|
||||
}
|
||||
|
||||
switch (GET_U32(&buffer[0])) {
|
||||
case REGION_ID_DEVELOPMENT:
|
||||
disk_info->region = DISK_REGION_DEVELOPMENT;
|
||||
sector_length = DEV_SYSTEM_DATA_SECTOR_LENGTH;
|
||||
break;
|
||||
case REGION_ID_JAPANESE:
|
||||
disk_info->region = DISK_REGION_JAPANESE;
|
||||
sector_length = RETAIL_SYSTEM_DATA_SECTOR_LENGTH;
|
||||
break;
|
||||
case REGION_ID_USA:
|
||||
disk_info->region = DISK_REGION_USA;
|
||||
sector_length = RETAIL_SYSTEM_DATA_SECTOR_LENGTH;
|
||||
break;
|
||||
default:
|
||||
disk_info->bad_system_area_lbas[lba] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (verify_system_area_lba(buffer, sector_length) && verify_system_data_lba(buffer) && set_defect_tracks(buffer, disk_info)) {
|
||||
valid_system_data_lba_found = true;
|
||||
disk_info->disk_type = (buffer[5] & 0x0F);
|
||||
} else {
|
||||
disk_info->bad_system_area_lbas[lba] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return valid_system_data_lba_found ? DISK_OK : DISK_ERR_INVALID;
|
||||
}
|
||||
|
||||
static disk_err_t load_and_verify_disk_id_lba (FIL *fil, disk_info_t *disk_info) {
|
||||
uint8_t buffer[SYSTEM_AREA_LBA_LENGTH];
|
||||
|
||||
bool valid_disk_id_lba_found = false;
|
||||
|
||||
for (int i = 0; i < DISK_ID_LBA_COUNT; i++) {
|
||||
int lba = disk_id_lbas[i];
|
||||
|
||||
if (load_system_area_lba(fil, lba, buffer)) {
|
||||
return DISK_ERR_IO;
|
||||
}
|
||||
|
||||
if (verify_system_area_lba(buffer, SYSTEM_AREA_SECTOR_LENGTH)) {
|
||||
valid_disk_id_lba_found = true;
|
||||
memcpy(disk_info->id, &buffer[0], sizeof(disk_info->id));
|
||||
disk_info->version = buffer[4];
|
||||
} else {
|
||||
disk_info->bad_system_area_lbas[lba] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return valid_disk_id_lba_found ? DISK_OK : DISK_ERR_INVALID;
|
||||
}
|
||||
|
||||
|
||||
disk_err_t disk_info_load (char *path, disk_info_t *disk_info) {
|
||||
FIL fil;
|
||||
disk_err_t err;
|
||||
|
||||
for (int i = 0; i < SYSTEM_AREA_LBA_COUNT; i++) {
|
||||
disk_info->bad_system_area_lbas[i] = false;
|
||||
}
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) {
|
||||
return DISK_ERR_NO_FILE;
|
||||
}
|
||||
|
||||
if ((err = load_and_verify_system_data_lba(&fil, disk_info)) != DISK_OK) {
|
||||
f_close(&fil);
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = load_and_verify_disk_id_lba(&fil, disk_info)) != DISK_OK) {
|
||||
f_close(&fil);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return DISK_ERR_IO;
|
||||
}
|
||||
|
||||
update_bad_system_area_lbas(disk_info);
|
||||
|
||||
return DISK_OK;
|
||||
}
|
52
src/menu/disk_info.h
Normal file
52
src/menu/disk_info.h
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @file disk_info.h
|
||||
* @brief 64DD disk information
|
||||
* @ingroup menu
|
||||
*/
|
||||
|
||||
#ifndef DISK_INFO_H__
|
||||
#define DISK_INFO_H__
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
DISK_OK,
|
||||
DISK_ERR_IO,
|
||||
DISK_ERR_NO_FILE,
|
||||
DISK_ERR_INVALID,
|
||||
} disk_err_t;
|
||||
|
||||
typedef enum {
|
||||
DISK_REGION_DEVELOPMENT,
|
||||
DISK_REGION_JAPANESE,
|
||||
DISK_REGION_USA,
|
||||
} disk_region_t;
|
||||
|
||||
typedef enum {
|
||||
DISK_TYPE_0,
|
||||
DISK_TYPE_1,
|
||||
DISK_TYPE_2,
|
||||
DISK_TYPE_3,
|
||||
DISK_TYPE_4,
|
||||
DISK_TYPE_5,
|
||||
DISK_TYPE_6,
|
||||
} disk_type_t;
|
||||
|
||||
typedef struct {
|
||||
disk_region_t region;
|
||||
disk_type_t disk_type;
|
||||
char id[4];
|
||||
uint8_t version;
|
||||
|
||||
bool bad_system_area_lbas[24];
|
||||
uint8_t defect_tracks[16][12];
|
||||
} disk_info_t;
|
||||
|
||||
|
||||
disk_err_t disk_info_load (char *path, disk_info_t *disk_info);
|
||||
|
||||
|
||||
#endif
|
34
src/menu/hdmi.c
Normal file
34
src/menu/hdmi.c
Normal file
@ -0,0 +1,34 @@
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "hdmi.h"
|
||||
|
||||
|
||||
#define ROM_ADDRESS (0x10000000)
|
||||
#define DDIPL_ADDRESS (0x06000000)
|
||||
|
||||
|
||||
void hdmi_clear_game_id (void) {
|
||||
pixelfx_clear_game_id();
|
||||
}
|
||||
|
||||
void hdmi_send_game_id (boot_params_t *boot_params) {
|
||||
uint8_t rom_header[0x40] __attribute__((aligned(8)));
|
||||
uint32_t pi_address = ROM_ADDRESS;
|
||||
|
||||
if (boot_params->device_type == BOOT_DEVICE_TYPE_64DD) {
|
||||
pi_address = DDIPL_ADDRESS;
|
||||
}
|
||||
|
||||
dma_read_async(rom_header, pi_address, sizeof(rom_header));
|
||||
dma_wait();
|
||||
|
||||
uint64_t rom_check_code;
|
||||
uint8_t media_format;
|
||||
uint8_t region_code;
|
||||
|
||||
memcpy((uint8_t *) (&rom_check_code), rom_header + 0x10, sizeof(rom_check_code));
|
||||
media_format = rom_header[0x3B];
|
||||
region_code = rom_header[0x3E];
|
||||
|
||||
pixelfx_send_game_id(rom_check_code, media_format, region_code);
|
||||
}
|
12
src/menu/hdmi.h
Normal file
12
src/menu/hdmi.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef HDMI_H__
|
||||
#define HDMI_H__
|
||||
|
||||
|
||||
#include "boot/boot.h"
|
||||
|
||||
|
||||
void hdmi_clear_game_id (void);
|
||||
void hdmi_send_game_id (boot_params_t *boot_params);
|
||||
|
||||
|
||||
#endif
|
@ -8,6 +8,7 @@
|
||||
#include "boot/boot.h"
|
||||
#include "flashcart/flashcart.h"
|
||||
#include "fonts.h"
|
||||
#include "hdmi.h"
|
||||
#include "menu_state.h"
|
||||
#include "menu.h"
|
||||
#include "mp3_player.h"
|
||||
@ -53,12 +54,12 @@ static void frame_counter_reset (void) {
|
||||
}
|
||||
|
||||
static void menu_init (boot_params_t *boot_params) {
|
||||
controller_init();
|
||||
joypad_init();
|
||||
timer_init();
|
||||
rtc_init();
|
||||
rspq_init();
|
||||
rdpq_init();
|
||||
fonts_init();
|
||||
|
||||
sound_init_default();
|
||||
|
||||
menu = calloc(1, sizeof(menu_t));
|
||||
@ -67,8 +68,8 @@ static void menu_init (boot_params_t *boot_params) {
|
||||
menu->mode = MENU_MODE_NONE;
|
||||
menu->next_mode = MENU_MODE_STARTUP;
|
||||
|
||||
menu->flashcart_error = flashcart_init();
|
||||
if (menu->flashcart_error != FLASHCART_OK) {
|
||||
menu->flashcart_err = flashcart_init();
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
menu->next_mode = MENU_MODE_FAULT;
|
||||
}
|
||||
|
||||
@ -88,21 +89,31 @@ static void menu_init (boot_params_t *boot_params) {
|
||||
menu->browser.valid = false;
|
||||
menu->browser.directory = path_init("sd:/", init_directory);
|
||||
|
||||
menu->load.rom_path = NULL;
|
||||
|
||||
hdmi_clear_game_id();
|
||||
|
||||
tv_type = get_tv_type();
|
||||
if ((tv_type == TV_PAL) && menu->settings.pal60) {
|
||||
if ((tv_type == TV_PAL) && menu->settings.pal60_enabled) {
|
||||
// HACK: Set TV type to NTSC, so PAL console would output 60 Hz signal instead.
|
||||
TV_TYPE_RAM = TV_NTSC;
|
||||
}
|
||||
|
||||
display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, ANTIALIAS_OFF);
|
||||
display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_DISABLED);
|
||||
|
||||
register_VI_handler(frame_counter_handler);
|
||||
|
||||
fonts_init();
|
||||
}
|
||||
|
||||
static void menu_deinit (menu_t *menu) {
|
||||
unregister_VI_handler(frame_counter_handler);
|
||||
|
||||
// NOTE: Restore previous TV type so boot procedure wouldn't passthrough wrong value.
|
||||
TV_TYPE_RAM = tv_type;
|
||||
|
||||
hdmi_send_game_id(menu->boot_params);
|
||||
|
||||
path_free(menu->browser.directory);
|
||||
free(menu);
|
||||
|
||||
@ -110,13 +121,13 @@ static void menu_deinit (menu_t *menu) {
|
||||
|
||||
flashcart_deinit();
|
||||
|
||||
sound_close();
|
||||
sound_deinit();
|
||||
|
||||
rdpq_close();
|
||||
rspq_close();
|
||||
rtc_close();
|
||||
timer_close();
|
||||
|
||||
unregister_VI_handler(frame_counter_handler);
|
||||
joypad_close();
|
||||
|
||||
display_close();
|
||||
}
|
||||
@ -136,6 +147,7 @@ static struct views_s {
|
||||
{ view_music_player_init, view_music_player_display }, // MENU_MODE_MUSIC_PLAYER
|
||||
{ view_credits_init, view_credits_display }, // MENU_MODE_CREDITS
|
||||
{ view_load_rom_init, view_load_rom_display }, // MENU_MODE_LOAD_ROM
|
||||
{ view_load_disk_init, view_load_disk_display }, // MENU_MODE_LOAD_DISK
|
||||
{ view_load_emulator_init, view_load_emulator_display }, // MENU_MODE_LOAD_EMULATOR
|
||||
{ view_error_init, view_error_display }, // MENU_MODE_ERROR
|
||||
{ view_fault_init, view_fault_display }, // MENU_MODE_FAULT
|
||||
|
@ -9,9 +9,12 @@
|
||||
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "boot/boot.h"
|
||||
#include "disk_info.h"
|
||||
#include "flashcart/flashcart.h"
|
||||
#include "path.h"
|
||||
#include "rom_info.h"
|
||||
#include "settings.h"
|
||||
|
||||
|
||||
@ -29,6 +32,7 @@ typedef enum {
|
||||
MENU_MODE_MUSIC_PLAYER,
|
||||
MENU_MODE_CREDITS,
|
||||
MENU_MODE_LOAD_ROM,
|
||||
MENU_MODE_LOAD_DISK,
|
||||
MENU_MODE_LOAD_EMULATOR,
|
||||
MENU_MODE_ERROR,
|
||||
MENU_MODE_FAULT,
|
||||
@ -40,6 +44,7 @@ typedef enum {
|
||||
typedef enum {
|
||||
ENTRY_TYPE_DIR,
|
||||
ENTRY_TYPE_ROM,
|
||||
ENTRY_TYPE_DISK,
|
||||
ENTRY_TYPE_EMULATOR,
|
||||
ENTRY_TYPE_SAVE,
|
||||
ENTRY_TYPE_IMAGE,
|
||||
@ -61,9 +66,9 @@ typedef struct {
|
||||
|
||||
settings_t settings;
|
||||
boot_params_t *boot_params;
|
||||
flashcart_error_t flashcart_error;
|
||||
|
||||
char *error_message;
|
||||
flashcart_err_t flashcart_err;
|
||||
|
||||
time_t current_time;
|
||||
|
||||
@ -72,9 +77,7 @@ typedef struct {
|
||||
bool go_down;
|
||||
bool go_left;
|
||||
bool go_right;
|
||||
bool fast;
|
||||
int vertical_held_counter;
|
||||
int horizontal_held_counter;
|
||||
bool go_fast;
|
||||
|
||||
bool enter;
|
||||
bool back;
|
||||
@ -91,6 +94,13 @@ typedef struct {
|
||||
entry_t *entry;
|
||||
int selected;
|
||||
} browser;
|
||||
|
||||
struct {
|
||||
path_t *rom_path;
|
||||
rom_info_t rom_info;
|
||||
path_t *disk_path;
|
||||
disk_info_t disk_info;
|
||||
} load;
|
||||
} menu_t;
|
||||
|
||||
|
||||
|
@ -1,278 +0,0 @@
|
||||
#include <libdragon.h>
|
||||
#include "rom_database.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
uint8_t extract_homebrew_setting(uint8_t setting, uint8_t bit_position) {
|
||||
return (setting & (1 << bit_position)) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalize the Homebrew save type.
|
||||
*/
|
||||
uint8_t extract_homebrew_save_type(uint8_t save_type) {
|
||||
switch (save_type) {
|
||||
case HB_SAVE_TYPE_NONE:
|
||||
return DB_SAVE_TYPE_NONE;
|
||||
case HB_SAVE_TYPE_EEPROM_4K:
|
||||
return DB_SAVE_TYPE_EEPROM_4K;
|
||||
case HB_SAVE_TYPE_EEPROM_16K:
|
||||
return DB_SAVE_TYPE_EEPROM_16K;
|
||||
case HB_SAVE_TYPE_SRAM:
|
||||
return DB_SAVE_TYPE_SRAM;
|
||||
case HB_SAVE_TYPE_SRAM_BANKED:
|
||||
return DB_SAVE_TYPE_SRAM_BANKED;
|
||||
case HB_SAVE_TYPE_FLASHRAM:
|
||||
return DB_SAVE_TYPE_FLASHRAM;
|
||||
case HB_SAVE_TYPE_SRAM_128K:
|
||||
return DB_SAVE_TYPE_SRAM_128K;
|
||||
default:
|
||||
return DB_SAVE_TYPE_INVALID; // Invalid save type, handle accordingly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Reads the N64 ROM header from a file. @see https://n64brew.dev/wiki/ROM_Header
|
||||
*/
|
||||
rom_header_t file_read_rom_header(char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
debugf("loading path: %s\n", path);
|
||||
if (!fp) {
|
||||
debugf("Error loading rom file header\n");
|
||||
}
|
||||
|
||||
rom_header_t *rom_header = malloc(sizeof(rom_header_t));
|
||||
|
||||
fseek(fp, 0x00, SEEK_SET);
|
||||
fread(&(rom_header->config_flags), sizeof(rom_header->config_flags), 1, fp);
|
||||
|
||||
// FIXME: handle endian appropriately, perhaps: cart_card_byteswap
|
||||
|
||||
fseek(fp, 0x04, SEEK_SET);
|
||||
fread(&(rom_header->clock_rate), sizeof(rom_header->clock_rate), 1, fp);
|
||||
fseek(fp, 0x08, SEEK_SET);
|
||||
fread(&(rom_header->boot_address), sizeof(rom_header->boot_address), 1, fp);
|
||||
fseek(fp, 0x0C, SEEK_SET);
|
||||
fread(&(rom_header->sdk_version), sizeof(rom_header->sdk_version), 1, fp);
|
||||
fseek(fp, 0x10, SEEK_SET);
|
||||
fread(&(rom_header->checksum), sizeof(rom_header->checksum), 1, fp);
|
||||
fseek(fp, 0x18, SEEK_SET);
|
||||
fread(&(rom_header->unknown_reserved_1), sizeof(rom_header->unknown_reserved_1), 1, fp);
|
||||
fseek(fp, 0x20, SEEK_SET);
|
||||
fgets(rom_header->title, sizeof(rom_header->title), fp);
|
||||
fseek(fp, 0x34, SEEK_SET);
|
||||
fread(&(rom_header->unknown_reserved_2), sizeof(rom_header->unknown_reserved_2), 1, fp);
|
||||
fseek(fp, 0x3B, SEEK_SET);
|
||||
fread(&(rom_header->metadata.media_type), sizeof(rom_header->metadata.media_type), 1, fp);
|
||||
fseek(fp, 0x3C, SEEK_SET);
|
||||
fread(&(rom_header->metadata.unique_identifier), sizeof(rom_header->metadata.unique_identifier), 1, fp);
|
||||
fseek(fp, 0x3E, SEEK_SET);
|
||||
fread(&(rom_header->metadata.destination_market), sizeof(rom_header->metadata.destination_market), 1, fp);
|
||||
fseek(fp, 0x3F, SEEK_SET);
|
||||
fread(&(rom_header->metadata.version), sizeof(rom_header->metadata.version), 1, fp);
|
||||
fseek(fp, 0x40, SEEK_SET);
|
||||
fread(&(rom_header->ipl_boot_code), sizeof(rom_header->ipl_boot_code), 1, fp);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
// FIXME: memory leak, rom header is copied, pointer should be returned instead or data freed before returning
|
||||
return *rom_header;
|
||||
}
|
||||
|
||||
// TODO: make sure this can also handle an external input file.
|
||||
|
||||
uint8_t rom_db_match_save_type(rom_header_t rom_header) {
|
||||
|
||||
// These are ordered to ensure they are handled correctly... (presumes big endian)
|
||||
|
||||
// First: Match by the `ED` or `HB` Developer ID
|
||||
if (rom_header.metadata.unique_identifier == *(uint16_t *)"ED" || rom_header.metadata.unique_identifier == *(uint16_t *)"HB") {
|
||||
// #ifdef ED64_COMPATIBLE
|
||||
// uint8_t low_nibble = rom_header.metadata.version & 0x0F;
|
||||
// uint8_t rtc_enabled = extract_homebrew_setting(low_nibble, 0); // Bit 0
|
||||
// uint8_t region_free_enabled = extract_homebrew_setting(low_nibble, 1); // Bit 1
|
||||
// #endif
|
||||
|
||||
uint8_t high_nibble = (rom_header.metadata.version >> 4) & 0x0F;
|
||||
|
||||
return extract_homebrew_save_type(high_nibble);
|
||||
}
|
||||
|
||||
// Second: Match the default entries for crc.
|
||||
// DOUBUTSU BANCHOU (ANIMAL LEADER, Cubivore) - Contains no game ID
|
||||
if (rom_header.checksum == 0xEB85EBC9596682AF) return DB_SAVE_TYPE_FLASHRAM;
|
||||
// DK Retail kiosk demo (shares ID with Dinosaur planet, but hacks are unlikely)!
|
||||
if (rom_header.checksum == 0x0DD4ABABB5A2A91E) return DB_SAVE_TYPE_EEPROM_16K;
|
||||
// DMTJ 64DD game
|
||||
// if (rom_header.checksum == 0x4cbc3b56) return DB_SAVE_TYPE_SRAM;
|
||||
|
||||
|
||||
// FIXME: we need to take into account the Category (first char) and the Region code (last char) before a general match of the ID.
|
||||
|
||||
// Finally: Match the default entries for "unique" ROM ID.
|
||||
// It is useful to use this rather than CRC's so that ROM hacks continue to work.
|
||||
static char *cart_ids[] = {
|
||||
// EEP4K
|
||||
"AB", "AD", "AG", "BC", "BD", "BH", "BK", "BM", "BN", "BV", "B6", "CG", "CH", "CR", "CT", "CU",
|
||||
"CX", "DR", "DQ", "DU", "DY", "D3", "D4", "EA", "ER", "FG", "FH", "FW", "FX", "FY", "GC", "GE",
|
||||
"GF", "GU", "GV", "HA", "HF", "HP", "IC", "IJ", "IR", "JM", "K2", "KA", "KI", "KT", "LB", "LR",
|
||||
"MG", "MI", "ML", "MO", "MR", "MS", "MU", "MW", "N6", "NA", "OH", "PG", "PW", "PY", "RC", "RS",
|
||||
"S6", "SA", "SC", "SM", "SU", "SV", "SW", "TB", "TC", "TJ", "TM", "TN", "TP", "TR", "TX", "T6",
|
||||
"VL", "VY", "WC", "WL", "WQ", "WR", "WU", "XO", "4W", "GL", "O2", "OS", "PM", "PT", "SN", "SB",
|
||||
"SS",
|
||||
// EEP16K
|
||||
"B7", "CW", "CZ", "DO", "D2", "D6", "EP", "EV", "FU", "F2", "IM", "M8", "MV", "MX", "NB", "NX",
|
||||
"PD", "RZ", "UB", "X7", "YS", "3D", "R7",
|
||||
// SRAM
|
||||
"AL", "AY", "A2", "DA", "FZ", "GP", "G6", "K4", "KG", "MF", "OB", "RE", "RI", "TE", "VB", "VP",
|
||||
"WI", "W2", "WX", "WZ", "YW", "ZL", "B5", "IB", "JG", "UT", "UM", "T3",
|
||||
// SRAM_BANKED
|
||||
"DZ",
|
||||
// SRAM_128K
|
||||
|
||||
// FLASHRAM
|
||||
"AF", "CC", "CK", "DL", "DP", "JD", "JF", "KJ", "M6", "MQ", "P2", "P3", "PF", "PH", "PN", "PO",
|
||||
"PS", "RH", "SI", "SQ", "T9", "W4", "ZS", "DA",
|
||||
// Controller Pak required
|
||||
"2V", "32", "3P", "3T", "8W", "AC", "AM", "AR", "B4", "BF", "BL", "BO", "BP", "BQ", "BR", "BX",
|
||||
"BY", "CD", "CE", "CL", "CO", "CS", "DE", "DH", "DM", "DN", "DT", "EG", "ET", "F9", "FD", "FF",
|
||||
"FO", "FQ", "G2", "G5", "GA", "GM", "GN", "GR", "GX", "HC", "HT", "HX", "HW", "HV", "IV", "KK",
|
||||
"L2", "LG", "M4", "MB", "MY", "NC", "NS", "O7", "OF", "2P", "PR", "PU", "PX", "R2", "R3", "R6",
|
||||
"RK", "RO", "RP", "RR", "RT", "RU", "RV", "RX", "SD", "SF", "SG", "SK", "SL", "SP", "SX", "SY",
|
||||
"T4", "TA", "TF", "TH", "TQ", "WB", "X2", "X3", "Y2", "V3", "V8", "VC", "VG", "VR", "WD", "WO",
|
||||
"WP", "WT", "XF", "YP", "ZO",
|
||||
|
||||
// Definitely none
|
||||
// "JQ", // Batman Beyond: Return of the Joker
|
||||
// "CB", // Charlie Blast's Territory (Password)
|
||||
// "CF", // Clayfighter 63 1/3
|
||||
// "C2", // Clayfighter: Sculptor's Cut
|
||||
// "DK", // Dark Rift
|
||||
// "EL", // Elmo's Letter Adventure
|
||||
// Elmo's Number Journey
|
||||
// "JO", // Jeopardy!
|
||||
// "KE", // Knife Edge: Nosegunner
|
||||
// "ME", // Mace: The Dark Age
|
||||
// "MT", //Magical Tetris Challenge
|
||||
// "M3", //Monster Truck Madness (Password)
|
||||
// "MK", //Mortal Kombat Trilogy
|
||||
// Powerpuff Girls: Chemical X Traction (Password)
|
||||
// "RG", //Rugrats: Scavenger Hunt
|
||||
// "CY", //South Park: Chef's Luv Shack
|
||||
// "OH", //Transformers Beast Wars: Transmetals
|
||||
// "WA", //War Gods
|
||||
// "WF", //Wheel of Fortune
|
||||
|
||||
// To work out
|
||||
// "AH", "BW", "GB", "LC", "2V", "DW", "M3", "MK", "NC", "WA",
|
||||
|
||||
// Last entry
|
||||
"\0\0"
|
||||
|
||||
};
|
||||
static int save_types[] = {
|
||||
// EEP4K
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01,
|
||||
// EEP16K
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
// SRAM
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
// SRAM_BANKED
|
||||
0x04,
|
||||
// SRAM_128K
|
||||
|
||||
// FLASHRAM
|
||||
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
|
||||
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
|
||||
// Controller Pak required
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
|
||||
// Last entry.
|
||||
0xFF
|
||||
};
|
||||
|
||||
for (int i = 0; save_types[i] != 0xFF; i++) {
|
||||
|
||||
if (rom_header.metadata.unique_identifier == *(uint16_t *) cart_ids[i]) {
|
||||
return save_types[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return DB_SAVE_TYPE_NONE; //Nothing matched.
|
||||
}
|
||||
|
||||
uint8_t rom_db_match_expansion_pak(rom_header_t rom_header) {
|
||||
|
||||
// Space Station Silicon Valley has known issues on NTSC.
|
||||
// We only check for the known working market here.
|
||||
if ( (rom_header.metadata.unique_identifier == *(uint16_t *) "SV") && (rom_header.metadata.destination_market != MARKET_EUROPEAN_BASIC)) {
|
||||
return DB_MEMORY_EXPANSION_FAULTY;
|
||||
}
|
||||
|
||||
static char *cart_ids[] = {
|
||||
|
||||
// See: https://nintendo.fandom.com/wiki/Nintendo_64_Expansion_Pak
|
||||
|
||||
// Expansion Pak Required
|
||||
"DO", "DP", "ZS", // Donkey Kong, Dino Planet, Majoras Mask
|
||||
|
||||
// Expansion Pak Recommended (games are pretty broken without it)
|
||||
"IJ", "PD", "SQ", // Indiana Jones, Perfect Dark, Starcraft
|
||||
|
||||
// Expansion Pak Enhanced (box art suggest improvements in some form)
|
||||
"3T", "2M", "32", "4W", "9F", "AC", "AM", "AR", "AS", "AY", "BE", "CC", "CE", "CO", "D4", "DT",
|
||||
"DQ", "DZ", "F2", "FL", "GB", "GX", "HT", "HV", "IC", "IS", "JA", "L2", "MD", "MX", "NA", "O7",
|
||||
"Q2", "Q9", "QB", "RC", "RE", "RO", "RU", "RS", "RV", "RW", "SD", "SL", "P3", "T2", "T4", "TF",
|
||||
"TK", "Y2", "TQ", "V8", "VG", "XF", "ZO",
|
||||
|
||||
// Last entry
|
||||
"\0\0"
|
||||
|
||||
};
|
||||
static int exp_types[] = {
|
||||
|
||||
// Expansion Pak Required
|
||||
0x01, 0x01, 0x01,
|
||||
|
||||
// Expansion Pak Recommended
|
||||
0x02, 0x02, 0x02,
|
||||
|
||||
// Expansion Pak Enhanced
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
|
||||
// Last entry.
|
||||
0xFF
|
||||
};
|
||||
|
||||
for (int i = 0; exp_types[i] != 0xff; i++) {
|
||||
|
||||
if (rom_header.metadata.unique_identifier == *(uint16_t *) cart_ids[i]) {
|
||||
return exp_types[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return DB_MEMORY_EXPANSION_NONE;
|
||||
}
|
@ -1,244 +0,0 @@
|
||||
/**
|
||||
* @file rom_database.h
|
||||
* @brief N64 ROM Database.
|
||||
* @note Only works with N64 ROM's by checking the first 1024 bytes of the file.
|
||||
* @ingroup menu
|
||||
*/
|
||||
|
||||
#ifndef ROM_DATABASE_H__
|
||||
#define ROM_DATABASE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/**
|
||||
* @brief ROM database save type enumeration.
|
||||
* @note These values are independent of flashcart / OS
|
||||
* but by default align to SC64.
|
||||
*/
|
||||
typedef enum {
|
||||
/** @brief The ROM has no save type. */
|
||||
DB_SAVE_TYPE_NONE = 0x00,
|
||||
/** @brief The ROM uses EEPROM 4K saves. */
|
||||
DB_SAVE_TYPE_EEPROM_4K = 0x01,
|
||||
/** @brief The ROM uses EEPROM 16K saves. */
|
||||
DB_SAVE_TYPE_EEPROM_16K = 0x02,
|
||||
/** @brief The ROM uses SRAM saves. */
|
||||
DB_SAVE_TYPE_SRAM = 0x03,
|
||||
/** @brief The ROM uses SRAM Banked saves. */
|
||||
DB_SAVE_TYPE_SRAM_BANKED = 0x04,
|
||||
/** @brief The ROM uses SRAM 128K saves @note This is not supported by all flashcarts. */
|
||||
DB_SAVE_TYPE_SRAM_128K = 0x05,
|
||||
/** @brief The ROM uses FLASHRAM saves. */
|
||||
DB_SAVE_TYPE_FLASHRAM = 0x06,
|
||||
/** @brief The ROM uses CPAK saves @note This must be handled by user code. */
|
||||
DB_SAVE_TYPE_CPAK = 0x10,
|
||||
/** @brief The ROM uses Disk Drive saves @note This is not supported by all flashcarts. */
|
||||
DB_SAVE_TYPE_DD = 0x20,
|
||||
/** @brief The ROM uses Disk Drive conversion saves @note This must be handled by user code. */
|
||||
DB_SAVE_TYPE_DD_CONVERSION = 0x30,
|
||||
/** @brief The ROM uses a save type that was not recognised. */
|
||||
DB_SAVE_TYPE_INVALID = 0xFF,
|
||||
} db_savetype_t;
|
||||
|
||||
|
||||
/** @brief ROM system memory requirements enumeration. */
|
||||
typedef enum {
|
||||
/** @brief The ROM is happy with 4MB of memory. */
|
||||
DB_MEMORY_EXPANSION_NONE = 0x00,
|
||||
/** @brief The ROM requires 8MB of memory. */
|
||||
DB_MEMORY_EXPANSION_REQUIRED = 0x01,
|
||||
/** @brief The ROM recommends 8MB of memory. */
|
||||
DB_MEMORY_EXPANSION_RECOMMENDED = 0x02,
|
||||
/** @brief The ROM suggests 8MB of memory. */
|
||||
DB_MEMORY_EXPANSION_SUGGESTED = 0x03,
|
||||
/** @brief The ROM is faulty when using 8MB of memory. */
|
||||
DB_MEMORY_EXPANSION_FAULTY = 0x04,
|
||||
} rom_memorytype_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief ROM homebrew save type enumeration.
|
||||
* @note These align to the Krikzz ED64 save types and are generally accepted
|
||||
* by all emulators.
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
/** @brief The ROM has no save type. */
|
||||
HB_SAVE_TYPE_NONE = 0x00,
|
||||
/** @brief The ROM uses EEPROM 4K saves. */
|
||||
HB_SAVE_TYPE_EEPROM_4K = 0x01,
|
||||
/** @brief The ROM uses EEPROM 16K saves. */
|
||||
HB_SAVE_TYPE_EEPROM_16K = 0x02,
|
||||
/** @brief The ROM uses SRAM saves. */
|
||||
HB_SAVE_TYPE_SRAM = 0x03,
|
||||
/** @brief The ROM uses SRAM Banked saves. */
|
||||
HB_SAVE_TYPE_SRAM_BANKED = 0x04,
|
||||
/** @brief The ROM uses FLASHRAM saves. */
|
||||
HB_SAVE_TYPE_FLASHRAM = 0x05,
|
||||
/** @brief The ROM uses SRAM 128K saves @note This is not supported by all flashcarts. */
|
||||
HB_SAVE_TYPE_SRAM_128K = 0x06,
|
||||
} homebrew_savetype_t;
|
||||
|
||||
/**
|
||||
* @brief ROM file endian enumeration.
|
||||
*
|
||||
* @note this is a hack used for checking ROM's against expected Big Endian
|
||||
* when reading from the file system.
|
||||
*/
|
||||
typedef enum {
|
||||
/** @brief Big Endian ROM */
|
||||
ROM_BIG_ENDIAN = 0x80371240,
|
||||
/** @brief Little Endian ROM */
|
||||
ROM_LITTLE_ENDIAN = 0x40123780,
|
||||
/** @brief Mid Big Endian ROM */
|
||||
ROM_MID_BIG_ENDIAN = 0x37804012,
|
||||
/** @brief Mid Little Endian ROM */
|
||||
ROM_MID_LITTLE_ENDIAN = 0x12408037,
|
||||
/** @brief Big Endian IPL ROM */
|
||||
IPL_BIG_ENDIAN = 0x80270740,
|
||||
} rom_endian_type_t;
|
||||
|
||||
|
||||
/** @brief ROM media type enumeration. */
|
||||
typedef enum {
|
||||
/** @brief Is a stand alone Cartridge program. */
|
||||
N64_CART = 'N',
|
||||
/** @brief Is a stand alone Disk Drive program. */
|
||||
N64_DISK = 'D',
|
||||
/** @brief Is a Cartridge program that could use an extra Disk Drive program to expand its capabilities. */
|
||||
N64_CART_EXPANDABLE = 'C',
|
||||
/** @brief Is a Disk Drive program that could use an extra Cartridge program to expand its capabilities. */
|
||||
N64_DISK_EXPANDABLE = 'E',
|
||||
/** @brief Is an Aleck64 program. */
|
||||
N64_ALECK64 = 'Z'
|
||||
} rom_media_type_t;
|
||||
|
||||
/** @brief ROM market region & language type enumeration. */
|
||||
typedef enum {
|
||||
/** @brief The ROM is designed for Japanese and "English" languages. */
|
||||
MARKET_JAPANESE_MULTI = 'A', // 1080 Snowboarding JPN is the only ROM that uses this? possibily a mistake, or the fact it also includes American English!.
|
||||
/** @brief The ROM is designed for Brazil (Portuguese) language. */
|
||||
MARKET_BRAZILIAN = 'B',
|
||||
/** @brief The ROM is designed for Chinese language. */
|
||||
MARKET_CHINESE = 'C',
|
||||
/** @brief The ROM is designed for German language. */
|
||||
MARKET_GERMAN = 'D',
|
||||
/** @brief The ROM is designed for North American "English" language. */
|
||||
MARKET_NORTH_AMERICA = 'E',
|
||||
/** @brief The ROM is designed for French language. */
|
||||
MARKET_FRENCH = 'F',
|
||||
/** @brief The ROM is designed for a NTSC Gateway 64. */
|
||||
MARKET_GATEWAY64_NTSC = 'G',
|
||||
/** @brief The ROM is designed for Dutch language. */
|
||||
MARKET_DUTCH = 'H',
|
||||
/** @brief The ROM is designed for Italian language. */
|
||||
MARKET_ITALIAN = 'I',
|
||||
/** @brief The ROM is designed for Japanese language. */
|
||||
MARKET_JAPANESE = 'J',
|
||||
/** @brief The ROM is designed for Korean language. */
|
||||
MARKET_KOREAN = 'K',
|
||||
/** @brief The ROM is designed for a PAL Gateway 64. */
|
||||
MARKET_GATEWAY64_PAL = 'L',
|
||||
/** @brief The ROM is designed for Canada region (English and French) language. */
|
||||
MARKET_CANADIAN = 'N',
|
||||
/** @brief The ROM is designed for European market and languages (must at minimum include English). */
|
||||
MARKET_EUROPEAN_BASIC = 'P', // Sometimes used for Australian region ROMs as well.
|
||||
/** @brief The ROM is designed for Spanish language */
|
||||
MARKET_SPANISH = 'S',
|
||||
/** @brief The ROM is designed for Australia (English) language. */
|
||||
MARKET_AUSTRALIAN = 'U',
|
||||
/** @brief The ROM is designed for Scandinavian (Swedish, Norwegian, Finnish, etc.) languages. */
|
||||
MARKET_SCANDINAVIAN = 'W',
|
||||
/** @brief The ROM is designed for an undefined region and TBD language(s). */
|
||||
MARKET_OTHER_X = 'X', // many EU ROM's, Top Gear Rally (Asia) and HSV Racing (AUS) ROM uses this.
|
||||
/** @brief The ROM is designed for a European region and language(s). */
|
||||
MARKET_OTHER_Y = 'Y', // many EU ROM's uses this.
|
||||
/** @brief The ROM is designed for an undefined region and TBD language(s). */
|
||||
MARKET_OTHER_Z = 'Z' // no known ROM's use this.
|
||||
} rom_destination_market_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief ROM Config Flags Structure
|
||||
* @note This information is derived from the ROM header.
|
||||
* @see https://n64brew.dev/wiki/Peripheral_Interface#Domains
|
||||
* i.e.
|
||||
* 0x00 = PI BSD Domain 1 Release register
|
||||
* 0x01 = PI BSD Domain 1 Page Size register
|
||||
* 0x02 = PI BSD Domain 1 Pulse Width register
|
||||
* 0x03 = PI BSD Domain 1 Latch register
|
||||
*/
|
||||
typedef struct {
|
||||
/* PI BSD Domain 1 Release register value */
|
||||
uint8_t domain1_release;
|
||||
/* PI BSD Domain 1 Page Size register value */
|
||||
uint8_t domain1_page_size;
|
||||
/* PI BSD Domain 1 Pulse Width register value */
|
||||
uint8_t domain1_latency;
|
||||
/* PI BSD Domain 1 Latch register value */
|
||||
uint8_t domain1_pulse_width;
|
||||
} rom_config_flags_t;
|
||||
|
||||
/**
|
||||
* @brief ROM Metadata Structure
|
||||
* @note This information is derived from the ROM header. i.e.
|
||||
* 0x3B = Media Type
|
||||
* 0x3C and 0x3D = Unique Identifier
|
||||
* 0x3E = Destination Market
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t media_type; // rom_media_type_t
|
||||
uint16_t unique_identifier;
|
||||
uint8_t destination_market; // rom_destination_market_t
|
||||
uint8_t version;
|
||||
} rom_metadata_t;
|
||||
|
||||
/**
|
||||
* @brief ROM Header Structure
|
||||
* @note This information is derived from the ROM header. @see https://n64brew.dev/wiki/ROM_Header
|
||||
*/
|
||||
typedef struct {
|
||||
/** @brief The ROM configuration flags @note we currently use this to work out the endian @see rom_endian_type_t. */
|
||||
uint32_t config_flags; // TODO: use rom_config_flags_t
|
||||
|
||||
/** @brief The ROM file clock rate. */
|
||||
uint32_t clock_rate;
|
||||
/** @brief The ROM file boot address. */
|
||||
uint32_t boot_address;
|
||||
/** @brief The ROM file SDK version. */
|
||||
uint32_t sdk_version;
|
||||
|
||||
/** @brief The ROM file checksum. */
|
||||
uint64_t checksum;
|
||||
|
||||
/** @brief The ROM file unknown reserved region at 0x18. for 8 bytes */
|
||||
uint64_t unknown_reserved_1;
|
||||
|
||||
/** @brief The ROM file title */
|
||||
char title[21]; // 20 chars + null char
|
||||
|
||||
/** @brief The ROM file unknown reserved region at 0x34. for 7 bytes */
|
||||
char unknown_reserved_2[7];
|
||||
|
||||
/** @brief The ROM file metadata @see rom_metadata_t. */
|
||||
rom_metadata_t metadata;
|
||||
/** @brief The ROM file release version. */
|
||||
|
||||
char ipl_boot_code[0x0FC0];
|
||||
|
||||
} rom_header_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
rom_header_t file_read_rom_header(char *path);
|
||||
uint8_t rom_db_match_save_type(rom_header_t rom_header);
|
||||
uint8_t rom_db_match_expansion_pak(rom_header_t rom_header);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
738
src/menu/rom_info.c
Normal file
738
src/menu/rom_info.c
Normal file
@ -0,0 +1,738 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <fatfs/ff.h>
|
||||
|
||||
#include "rom_info.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
|
||||
#define SWAP_VARS(x0, x1) { typeof(x0) tmp = (x0); (x0) = (x1); (x1) = (tmp); }
|
||||
|
||||
#define PI_CONFIG_BIG_ENDIAN (0x80371240)
|
||||
#define PI_CONFIG_LITTLE_ENDIAN (0x40123780)
|
||||
#define PI_CONFIG_BYTE_SWAPPED (0x37804012)
|
||||
#define PI_CONFIG_64DD_IPL (0x80270740)
|
||||
|
||||
#define CLOCK_RATE_DEFAULT (0x0000000F)
|
||||
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t pi_dom1_config;
|
||||
uint32_t clock_rate;
|
||||
uint32_t boot_address;
|
||||
struct {
|
||||
uint8_t __unused_1[2];
|
||||
uint8_t version;
|
||||
char revision;
|
||||
} libultra;
|
||||
uint64_t check_code;
|
||||
uint8_t __unused_1[8];
|
||||
char title[20];
|
||||
uint8_t __unused_2[7];
|
||||
union {
|
||||
char game_code[4];
|
||||
struct {
|
||||
char category_code;
|
||||
char unique_code[2];
|
||||
char destination_code;
|
||||
};
|
||||
};
|
||||
uint8_t version;
|
||||
uint8_t ipl3[IPL3_LENGTH];
|
||||
} rom_header_t;
|
||||
|
||||
typedef enum {
|
||||
// Check only game code
|
||||
MATCH_TYPE_ID,
|
||||
|
||||
// Check game code and region
|
||||
MATCH_TYPE_ID_REGION,
|
||||
|
||||
// Check game code, region and version
|
||||
MATCH_TYPE_ID_REGION_VERSION,
|
||||
|
||||
// Check game check code
|
||||
MATCH_TYPE_CHECK_CODE,
|
||||
|
||||
// Check for homebrew header ID
|
||||
MATCH_TYPE_HOMEBREW_HEADER,
|
||||
|
||||
// List end marker
|
||||
MATCH_TYPE_END
|
||||
} match_type_t;
|
||||
|
||||
typedef enum {
|
||||
// No features supported
|
||||
FEAT_NONE = 0,
|
||||
|
||||
// Controller Pak
|
||||
FEAT_CPAK = (1 << 0),
|
||||
|
||||
// Rumble Pak
|
||||
FEAT_RPAK = (1 << 1),
|
||||
|
||||
// Transfer Pak
|
||||
FEAT_TPAK = (1 << 2),
|
||||
|
||||
// Voice Recognition Unit
|
||||
FEAT_VRU = (1 << 3),
|
||||
|
||||
// Real Time Clock
|
||||
FEAT_RTC = (1 << 4),
|
||||
|
||||
// Expansion Pak (for games that will not work without it inserted into the console)
|
||||
FEAT_EXP_PAK_REQUIRED = (1 << 5),
|
||||
|
||||
// Expansion Pak (for games with game play enhancements)
|
||||
FEAT_EXP_PAK_RECOMMENDED = (1 << 6),
|
||||
|
||||
// Expansion Pak (for games with visual (or other) enhancements)
|
||||
FEAT_EXP_PAK_ENHANCED = (1 << 7),
|
||||
|
||||
// No Expansion Pak (for games "broken" with it inserted into the console)
|
||||
FEAT_EXP_PAK_BROKEN = (1 << 8),
|
||||
|
||||
// 64DD disk to ROM conversion
|
||||
FEAT_64DD_CONVERSION = (1 << 9),
|
||||
|
||||
// Combo ROM + Disk games
|
||||
FEAT_64DD_ENHANCED = (1 << 10),
|
||||
} feat_t;
|
||||
|
||||
typedef struct {
|
||||
// Which fields to check
|
||||
match_type_t type;
|
||||
|
||||
// Fields to check for matching
|
||||
union {
|
||||
struct {
|
||||
// Game code (with media type and optional region) or unique ID
|
||||
const char *id;
|
||||
|
||||
// Game version
|
||||
uint8_t version;
|
||||
};
|
||||
|
||||
// Game check code
|
||||
uint64_t check_code;
|
||||
} fields;
|
||||
|
||||
// Matched game metadata
|
||||
struct {
|
||||
// Save type (only cartridge save types)
|
||||
save_type_t save;
|
||||
|
||||
// Supported features
|
||||
feat_t feat;
|
||||
} data;
|
||||
} match_t;
|
||||
|
||||
|
||||
#define MATCH_ID(i, s, f) { .type = MATCH_TYPE_ID, .fields = { .id = i }, .data = { .save = s, .feat = f } }
|
||||
#define MATCH_ID_REGION(i, s, f) { .type = MATCH_TYPE_ID_REGION, .fields = { .id = i }, .data = { .save = s, .feat = f } }
|
||||
#define MATCH_ID_REGION_VERSION(i, v, s, f) { .type = MATCH_TYPE_ID_REGION_VERSION, .fields = { .id = i, .version = v }, .data = { .save = s, .feat = f } }
|
||||
#define MATCH_CHECK_CODE(c, s, f) { .type = MATCH_TYPE_CHECK_CODE, .fields = { .check_code = c }, .data = { .save = s, .feat = f } }
|
||||
#define MATCH_HOMEBREW_HEADER(i) { .type = MATCH_TYPE_HOMEBREW_HEADER, .fields = { .id = i }, .data = { .feat = FEAT_NONE } }
|
||||
#define MATCH_END { .type = MATCH_TYPE_END, .data = { .save = SAVE_TYPE_NONE, .feat = FEAT_NONE } }
|
||||
|
||||
|
||||
// List shamelessly stolen from https://github.com/ares-emulator/ares/blob/master/mia/medium/nintendo-64.cpp
|
||||
static const match_t database[] = {
|
||||
MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED)
|
||||
|
||||
MATCH_CHECK_CODE(0x000000004CBC3B56, SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // DMTJ 64DD cartridge conversion
|
||||
MATCH_CHECK_CODE(0x0DD4ABABB5A2A91E, SAVE_TYPE_EEPROM_16K, FEAT_EXP_PAK_REQUIRED), // DK Retail kiosk demo
|
||||
MATCH_CHECK_CODE(0xEB85EBC9596682AF, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Doubutsu Banchou
|
||||
|
||||
MATCH_ID_REGION_VERSION("NK4J", 0, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||
MATCH_ID_REGION_VERSION("NK4J", 1, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||
MATCH_ID("NK4", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||
|
||||
MATCH_ID_REGION_VERSION("NSMJ", 3, SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Super Mario 64 Shindou Edition
|
||||
MATCH_ID("NSM", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Super Mario 64
|
||||
|
||||
MATCH_ID_REGION_VERSION("NWRJ", 2, SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Wave Race 64 Shindou Edition
|
||||
MATCH_ID("NWR", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Wave Race 64
|
||||
|
||||
MATCH_ID_REGION("N3HJ", SAVE_TYPE_SRAM, FEAT_NONE), // Ganbare! Nippon! Olympics 2000
|
||||
MATCH_ID("N3H", SAVE_TYPE_NONE, FEAT_CPAK), // International Track & Field 2000
|
||||
|
||||
MATCH_ID_REGION("ND3J", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Akumajou Dracula Mokushiroku (J)
|
||||
MATCH_ID("ND3", SAVE_TYPE_NONE, FEAT_CPAK), // Castlevania
|
||||
|
||||
MATCH_ID_REGION("ND4J", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Akumajou Dracula Mokushiroku Gaiden: Legend of Cornell (J)
|
||||
MATCH_ID("ND4", SAVE_TYPE_NONE, FEAT_CPAK), // Castlevania - Legacy of Darkness
|
||||
|
||||
MATCH_ID_REGION("NDKJ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Dark Rift [Space Dynamites (J)]
|
||||
|
||||
MATCH_ID_REGION("NSVE", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Space Station Silicon Valley
|
||||
MATCH_ID("NSV", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_EXP_PAK_BROKEN), // Space Station Silicon Valley
|
||||
|
||||
MATCH_ID_REGION("NWTJ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Wetrix
|
||||
MATCH_ID("NWT", SAVE_TYPE_NONE, FEAT_CPAK), // Wetrix
|
||||
|
||||
MATCH_ID("CLB", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_64DD_ENHANCED), // Mario Party (NTSC)
|
||||
MATCH_ID("NAB", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Air Boarder 64
|
||||
MATCH_ID("NAD", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Worms Armageddon (U)
|
||||
MATCH_ID("NAG", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // AeroGauge
|
||||
MATCH_ID("NB6", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_TPAK), // Super B-Daman: Battle Phoenix 64
|
||||
MATCH_ID("NBC", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Blast Corps
|
||||
MATCH_ID("NBD", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Bomberman Hero [Mirian Ojo o Sukue! (J)]
|
||||
MATCH_ID("NBH", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Body Harvest
|
||||
MATCH_ID("NBK", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Banjo-Kazooie [Banjo to Kazooie no Daiboken (J)]
|
||||
MATCH_ID("NBM", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Bomberman 64 [Baku Bomberman (J)]
|
||||
MATCH_ID("NBN", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Bakuretsu Muteki Bangaioh
|
||||
MATCH_ID("NBV", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Bomberman 64: The Second Attack! [Baku Bomberman 2 (J)]
|
||||
MATCH_ID("NCG", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK), // Choro Q 64 II - Hacha Mecha Grand Prix Race (J)
|
||||
MATCH_ID("NCH", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Chopper Attack
|
||||
MATCH_ID("NCR", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Penny Racers [Choro Q 64 (J)]
|
||||
MATCH_ID("NCT", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Chameleon Twist
|
||||
MATCH_ID("NCU", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Cruis'n USA
|
||||
MATCH_ID("NCX", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Custom Robo
|
||||
MATCH_ID("NDQ", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Disney's Donald Duck - Goin' Quackers [Quack Attack (E)]
|
||||
MATCH_ID("NDR", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Doraemon: Nobita to 3tsu no Seireiseki
|
||||
MATCH_ID("NDU", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Duck Dodgers starring Daffy Duck
|
||||
MATCH_ID("NDY", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Diddy Kong Racing
|
||||
MATCH_ID("NEA", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // PGA European Tour
|
||||
MATCH_ID("NER", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Aero Fighters Assault [Sonic Wings Assault (J)]
|
||||
MATCH_ID("NF2", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // F-1 World Grand Prix II
|
||||
MATCH_ID("NFG", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Fighter Destiny 2
|
||||
MATCH_ID("NFH", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // In-Fisherman Bass Hunter 64
|
||||
MATCH_ID("NFW", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // F-1 World Grand Prix
|
||||
MATCH_ID("NFX", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Star Fox 64 [Lylat Wars (E)]
|
||||
MATCH_ID("NFY", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Kakutou Denshou: F-Cup Maniax
|
||||
MATCH_ID("NGE", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // GoldenEye 007
|
||||
MATCH_ID("NGL", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Getter Love!!
|
||||
MATCH_ID("NGU", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Tsumi to Batsu: Hoshi no Keishousha (Sin and Punishment)
|
||||
MATCH_ID("NGV", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Glover
|
||||
MATCH_ID("NHA", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Bomberman 64: Arcade Edition (J)
|
||||
MATCH_ID("NHF", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // 64 Hanafuda: Tenshi no Yakusoku
|
||||
MATCH_ID("NHP", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Heiwa Pachinko World 64
|
||||
MATCH_ID("NIC", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Indy Racing 2000
|
||||
MATCH_ID("NIJ", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_EXP_PAK_RECOMMENDED), // Indiana Jones and the Infernal Machine
|
||||
MATCH_ID("NIR", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Utchan Nanchan no Hono no Challenger: Denryuu Ira Ira Bou
|
||||
MATCH_ID("NJM", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Earthworm Jim 3D
|
||||
MATCH_ID("NK2", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Snowboard Kids 2 [Chou Snobow Kids (J)]
|
||||
MATCH_ID("NKA", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Fighters Destiny [Fighting Cup (J)]
|
||||
MATCH_ID("NKI", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Killer Instinct Gold
|
||||
MATCH_ID("NKT", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Mario Kart 64
|
||||
MATCH_ID("NLB", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Mario Party (PAL)
|
||||
MATCH_ID("NLL", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Last Legion UX
|
||||
MATCH_ID("NLR", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Lode Runner 3-D
|
||||
MATCH_ID("NMG", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Monaco Grand Prix [Racing Simulation 2 (G)]
|
||||
MATCH_ID("NMI", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Mission: Impossible
|
||||
MATCH_ID("NML", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_TPAK), // Mickey's Speedway USA [Mickey no Racing Challenge USA (J)]
|
||||
MATCH_ID("NMO", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Monopoly
|
||||
MATCH_ID("NMR", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Multi-Racing Championship
|
||||
MATCH_ID("NMS", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // Morita Shougi 64
|
||||
MATCH_ID("NMU", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Big Mountain 2000
|
||||
MATCH_ID("NMW", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Mario Party 2
|
||||
MATCH_ID("NMZ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Zool - Majou Tsukai Densetsu (J)
|
||||
MATCH_ID("NN6", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Dr. Mario 64
|
||||
MATCH_ID("NNA", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Star Wars Episode I: Battle for Naboo
|
||||
MATCH_ID("NOS", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // 64 Oozumou
|
||||
MATCH_ID("NP2", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Chou Kuukan Night Pro Yakyuu King 2 (J)
|
||||
MATCH_ID("NPG", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_VRU), // Hey You, Pikachu! [Pikachu Genki Dechu (J)]
|
||||
MATCH_ID("NPT", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_TPAK), // Puyo Puyon Party
|
||||
MATCH_ID("NPW", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Pilotwings 64
|
||||
MATCH_ID("NPY", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Puyo Puyo Sun 64
|
||||
MATCH_ID("NRA", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Rally '99 (J)
|
||||
MATCH_ID("NRC", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Top Gear Overdrive
|
||||
MATCH_ID("NRS", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Star Wars: Rogue Squadron [Shutsugeki! Rogue Chuutai (J)]
|
||||
MATCH_ID("NS3", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // AI Shougi 3
|
||||
MATCH_ID("NS6", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Star Soldier: Vanishing Earth
|
||||
MATCH_ID("NSA", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Sonic Wings Assault (J)
|
||||
MATCH_ID("NSC", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Starshot: Space Circus Fever
|
||||
MATCH_ID("NSN", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Snow Speeder (J)
|
||||
MATCH_ID("NSS", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Super Robot Spirits
|
||||
MATCH_ID("NSU", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Rocket: Robot on Wheels
|
||||
MATCH_ID("NSW", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Star Wars: Shadows of the Empire [Teikoku no Kage (J)]
|
||||
MATCH_ID("NT6", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Tetris 64
|
||||
MATCH_ID("NTB", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Transformers: Beast Wars Metals 64 (J)
|
||||
MATCH_ID("NTC", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // 64 Trump Collection
|
||||
MATCH_ID("NTJ", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Tom & Jerry in Fists of Fury
|
||||
MATCH_ID("NTM", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Mischief Makers [Yuke Yuke!! Trouble Makers (J)]
|
||||
MATCH_ID("NTN", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // All Star Tennis '99
|
||||
MATCH_ID("NTP", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Tetrisphere
|
||||
MATCH_ID("NTR", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Top Gear Rally (J + E)
|
||||
MATCH_ID("NTW", SAVE_TYPE_EEPROM_4K, FEAT_CPAK), // 64 de Hakken!! Tamagotchi
|
||||
MATCH_ID("NTX", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Taz Express
|
||||
MATCH_ID("NVL", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // V-Rally Edition '99
|
||||
MATCH_ID("NVY", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // V-Rally Edition '99 (J)
|
||||
MATCH_ID("NWC", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Wild Choppers
|
||||
MATCH_ID("NWQ", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Rally Challenge 2000
|
||||
MATCH_ID("NWU", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Worms Armageddon (E)
|
||||
MATCH_ID("NXO", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Cruis'n Exotica
|
||||
MATCH_ID("NYK", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Yakouchuu II: Satsujin Kouro
|
||||
|
||||
MATCH_ID("N3D", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Doraemon 3: Nobita no Machi SOS!
|
||||
MATCH_ID("NB7", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
|
||||
MATCH_ID("NCW", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Cruis'n World
|
||||
MATCH_ID("NCZ", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Custom Robo V2
|
||||
MATCH_ID("ND2", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Doraemon 2: Nobita to Hikari no Shinden
|
||||
MATCH_ID("ND6", SAVE_TYPE_EEPROM_16K, FEAT_RPAK | FEAT_VRU), // Densha de Go! 64
|
||||
MATCH_ID("NDO", SAVE_TYPE_EEPROM_16K, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey Kong 64
|
||||
MATCH_ID("NEP", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Star Wars Episode I: Racer
|
||||
MATCH_ID("NEV", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Neon Genesis Evangelion
|
||||
MATCH_ID("NFU", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Conker's Bad Fur Day
|
||||
MATCH_ID("NGC", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_RPAK), // GT 64: Championship Edition
|
||||
MATCH_ID("NGT", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_RPAK), // City Tour GrandPrix - Zen Nihon GT Senshuken
|
||||
MATCH_ID("NIM", SAVE_TYPE_EEPROM_16K, FEAT_NONE), // Ide Yosuke no Mahjong Juku
|
||||
MATCH_ID("NM8", SAVE_TYPE_EEPROM_16K, FEAT_RPAK | FEAT_TPAK), // Mario Tennis
|
||||
MATCH_ID("NMV", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Mario Party 3
|
||||
MATCH_ID("NMX", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_RPAK), // Excitebike 64
|
||||
MATCH_ID("NNB", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_RPAK), // Kobe Bryant in NBA Courtside
|
||||
MATCH_ID("NPD", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK | FEAT_EXP_PAK_RECOMMENDED), // Perfect Dark
|
||||
MATCH_ID("NPP", SAVE_TYPE_EEPROM_16K, FEAT_CPAK), // Parlor! Pro 64: Pachinko Jikki Simulation Game
|
||||
MATCH_ID("NR7", SAVE_TYPE_EEPROM_16K, FEAT_TPAK), // Robot Poncots 64: 7tsu no Umi no Caramel
|
||||
MATCH_ID("NRZ", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Ridge Racer 64
|
||||
MATCH_ID("NUB", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_TPAK), // PD Ultraman Battle Collection 64
|
||||
MATCH_ID("NYS", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Yoshi's Story
|
||||
|
||||
MATCH_ID("CFZ", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // F-Zero X (J)
|
||||
MATCH_ID("CPS", SAVE_TYPE_SRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium (J)
|
||||
MATCH_ID("CZL", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // Legend of Zelda: Ocarina of Time [Zelda no Densetsu - Toki no Ocarina (J)]
|
||||
MATCH_ID("NA2", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 2
|
||||
MATCH_ID("NAL", SAVE_TYPE_SRAM, FEAT_RPAK), // Super Smash Bros. [Nintendo All-Star! Dairantou Smash Brothers (J)]
|
||||
MATCH_ID("NB5", SAVE_TYPE_SRAM, FEAT_RPAK), // Biohazard 2 (J)
|
||||
MATCH_ID("NFZ", SAVE_TYPE_SRAM, FEAT_RPAK), // F-Zero X (U + E)
|
||||
MATCH_ID("NG6", SAVE_TYPE_SRAM, FEAT_RPAK), // Ganmare Goemon: Dero Dero Douchuu Obake Tenkomori
|
||||
MATCH_ID("NGP", SAVE_TYPE_SRAM, FEAT_CPAK), // Goemon: Mononoke Sugoroku
|
||||
MATCH_ID("NHY", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Hybrid Heaven (J)
|
||||
MATCH_ID("NIB", SAVE_TYPE_SRAM, FEAT_RPAK), // Itoi Shigesato no Bass Tsuri No. 1 Kettei Ban!
|
||||
MATCH_ID("NJ5", SAVE_TYPE_SRAM, FEAT_CPAK), // Jikkyou Powerful Pro Yakyuu 5
|
||||
MATCH_ID("NJG", SAVE_TYPE_SRAM, FEAT_RPAK), // Jinsei Game 64
|
||||
MATCH_ID("NKG", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Major League Baseball featuring Ken Griffey Jr.
|
||||
MATCH_ID("NMF", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_TPAK), // Mario Golf 64
|
||||
MATCH_ID("NOB", SAVE_TYPE_SRAM, FEAT_NONE), // Ogre Battle 64: Person of Lordly Caliber
|
||||
MATCH_ID("NP4", SAVE_TYPE_SRAM, FEAT_CPAK), // Jikkyou Powerful Pro Yakyuu 4
|
||||
MATCH_ID("NP6", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_TPAK), // Jikkyou Powerful Pro Yakyuu 6
|
||||
MATCH_ID("NPA", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_TPAK), // Jikkyou Powerful Pro Yakyuu 2000
|
||||
MATCH_ID("NPE", SAVE_TYPE_SRAM, FEAT_CPAK), // Jikkyou Powerful Pro Yakyuu Basic Ban 2001
|
||||
MATCH_ID("NPM", SAVE_TYPE_SRAM, FEAT_CPAK), // Premier Manager 64
|
||||
MATCH_ID("NPS", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Jikkyou J.League 1999: Perfect Striker 2
|
||||
MATCH_ID("NRE", SAVE_TYPE_SRAM, FEAT_RPAK), // Resident Evil 2
|
||||
MATCH_ID("NRI", SAVE_TYPE_SRAM, FEAT_CPAK), // New Tetris, The
|
||||
MATCH_ID("NS4", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_TPAK), // Super Robot Taisen 64
|
||||
MATCH_ID("NSI", SAVE_TYPE_SRAM, FEAT_CPAK), // Fushigi no Dungeon: Fuurai no Shiren 2
|
||||
MATCH_ID("NT3", SAVE_TYPE_SRAM, FEAT_CPAK), // Shin Nihon Pro Wrestling - Toukon Road 2 - The Next Generation (J)
|
||||
MATCH_ID("NTE", SAVE_TYPE_SRAM, FEAT_RPAK), // 1080 Snowboarding
|
||||
MATCH_ID("NUM", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_TPAK), // Nushi Zuri 64: Shiokaze ni Notte
|
||||
MATCH_ID("NUT", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK), // Nushi Zuri 64
|
||||
MATCH_ID("NVB", SAVE_TYPE_SRAM, FEAT_RPAK), // Bass Rush - ECOGEAR PowerWorm Championship (J)
|
||||
MATCH_ID("NVP", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 64
|
||||
MATCH_ID("NW2", SAVE_TYPE_SRAM, FEAT_RPAK), // WCW-nWo Revenge
|
||||
MATCH_ID("NWL", SAVE_TYPE_SRAM, FEAT_RPAK), // Waialae Country Club: True Golf Classics
|
||||
MATCH_ID("NWX", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // WWF WrestleMania 2000
|
||||
MATCH_ID("NYW", SAVE_TYPE_SRAM, FEAT_NONE), // Harvest Moon 64
|
||||
MATCH_ID("NZL", SAVE_TYPE_SRAM, FEAT_RPAK), // Legend of Zelda: Ocarina of Time (E)
|
||||
|
||||
MATCH_ID("CDZ", SAVE_TYPE_SRAM_BANKED, FEAT_RPAK | FEAT_64DD_ENHANCED), // Dezaemon 3D
|
||||
|
||||
MATCH_ID("CP2", SAVE_TYPE_FLASHRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium 2 (J)
|
||||
MATCH_ID("NAF", SAVE_TYPE_FLASHRAM, FEAT_CPAK | FEAT_RTC), // Doubutsu no Mori
|
||||
MATCH_ID("NCC", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Command & Conquer
|
||||
MATCH_ID("NCK", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // NBA Courtside 2 featuring Kobe Bryant
|
||||
MATCH_ID("NDA", SAVE_TYPE_FLASHRAM, FEAT_CPAK), // Derby Stallion 64
|
||||
MATCH_ID("NDP", SAVE_TYPE_FLASHRAM, FEAT_EXP_PAK_REQUIRED), // Dinosaur Planet (Unlicensed)
|
||||
MATCH_ID("NJF", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Jet Force Gemini [Star Twins (J)]
|
||||
MATCH_ID("NKJ", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Ken Griffey Jr.'s Slugfest
|
||||
MATCH_ID("NM6", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Mega Man 64
|
||||
MATCH_ID("NMQ", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Paper Mario
|
||||
MATCH_ID("NPF", SAVE_TYPE_FLASHRAM, FEAT_NONE), // Pokemon Snap [Pocket Monsters Snap (J)]
|
||||
MATCH_ID("NPN", SAVE_TYPE_FLASHRAM, FEAT_NONE), // Pokemon Puzzle League
|
||||
MATCH_ID("NPO", SAVE_TYPE_FLASHRAM, FEAT_TPAK), // Pokemon Stadium
|
||||
MATCH_ID("NRH", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Rockman Dash - Hagane no Boukenshin (J)
|
||||
MATCH_ID("NSQ", SAVE_TYPE_FLASHRAM, FEAT_RPAK | FEAT_EXP_PAK_RECOMMENDED), // StarCraft 64
|
||||
MATCH_ID("NT9", SAVE_TYPE_FLASHRAM, FEAT_NONE), // Tigger's Honey Hunt
|
||||
MATCH_ID("NW4", SAVE_TYPE_FLASHRAM, FEAT_CPAK | FEAT_RPAK), // WWF No Mercy
|
||||
MATCH_ID("NZS", SAVE_TYPE_FLASHRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Legend of Zelda: Majora's Mask [Zelda no Densetsu - Mujura no Kamen (J)]
|
||||
|
||||
MATCH_ID("NP3", SAVE_TYPE_FLASHRAM_PKST2, FEAT_TPAK), // Pokemon Stadium 2 [Pocket Monsters Stadium - Kin Gin (J)]
|
||||
|
||||
MATCH_ID("N22", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - Round 2
|
||||
MATCH_ID("N2M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2002
|
||||
MATCH_ID("N32", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes 2
|
||||
MATCH_ID("N3P", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Triple Play 2000
|
||||
MATCH_ID("N64", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Kira to Kaiketsu! 64 Tanteidan
|
||||
MATCH_ID("N7I", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA Soccer 64 [FIFA 64 (E)]
|
||||
MATCH_ID("N8I", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA - Road to World Cup 98 [World Cup e no Michi (J)]
|
||||
MATCH_ID("N8M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 64
|
||||
MATCH_ID("N8W", SAVE_TYPE_NONE, FEAT_CPAK), // World Cup '98
|
||||
MATCH_ID("N9B", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Live '99
|
||||
MATCH_ID("N9C", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nascar '99
|
||||
MATCH_ID("N9F", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA 99
|
||||
MATCH_ID("N9H", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL '99
|
||||
MATCH_ID("N9M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football '99
|
||||
MATCH_ID("NAC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Air Combat
|
||||
MATCH_ID("NAH", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Asteroids Hyper 64
|
||||
MATCH_ID("NAI", SAVE_TYPE_NONE, FEAT_CPAK), // Midway's Greatest Arcade Hits Volume 1
|
||||
MATCH_ID("NAM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes
|
||||
MATCH_ID("NAR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Armorines - Project S.W.A.R.M.
|
||||
MATCH_ID("NAS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball 2001
|
||||
MATCH_ID("NAY", SAVE_TYPE_NONE, FEAT_CPAK), // Aidyn Chronicles - The First Mage
|
||||
MATCH_ID("NB2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone '99 [NBA Pro '99 (E)]
|
||||
MATCH_ID("NB3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bust-A-Move '99 [Bust-A-Move 3 DX (E)]
|
||||
MATCH_ID("NB4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bass Masters 2000
|
||||
MATCH_ID("NB8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Beetle Adventure Racing (J)
|
||||
MATCH_ID("NB9", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Jam '99
|
||||
MATCH_ID("NBA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone '98 [NBA Pro '98 (E)]
|
||||
MATCH_ID("NBE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball 2000
|
||||
MATCH_ID("NBF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bio F.R.E.A.K.S.
|
||||
MATCH_ID("NBI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz 2000
|
||||
MATCH_ID("NBJ", SAVE_TYPE_NONE, FEAT_CPAK), // Bakushou Jinsei 64 - Mezase! Resort Ou
|
||||
MATCH_ID("NBL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Buck Bumble
|
||||
MATCH_ID("NBO", SAVE_TYPE_NONE, FEAT_CPAK), // Bottom of the 9th
|
||||
MATCH_ID("NBP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Blues Brothers 2000
|
||||
MATCH_ID("NBQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battletanx - Global Assault
|
||||
MATCH_ID("NBR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Milo's Astro Lanes
|
||||
MATCH_ID("NBS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball '99
|
||||
MATCH_ID("NBU", SAVE_TYPE_NONE, FEAT_CPAK), // Bust-A-Move 2 - Arcade Edition
|
||||
MATCH_ID("NBW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Super Bowling
|
||||
MATCH_ID("NBX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battletanx
|
||||
MATCH_ID("NBY", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bug's Life, A
|
||||
MATCH_ID("NBZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz
|
||||
MATCH_ID("NCB", SAVE_TYPE_NONE, FEAT_RPAK), // Charlie Blast's Territory
|
||||
MATCH_ID("NCD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Carmageddon 64
|
||||
MATCH_ID("NCE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nuclear Strike 64
|
||||
MATCH_ID("NCL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // California Speed
|
||||
MATCH_ID("NCO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Jeremy McGrath Supercross 2000
|
||||
MATCH_ID("NCS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // S.C.A.R.S.
|
||||
MATCH_ID("NDC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // SD Hiryuu no Ken Densetsu (J)
|
||||
MATCH_ID("NDE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Destruction Derby 64
|
||||
MATCH_ID("NDF", SAVE_TYPE_NONE, FEAT_RPAK), // Dance Dance Revolution - Disney Dancing Museum
|
||||
MATCH_ID("NDH", SAVE_TYPE_NONE, FEAT_CPAK), // Duel Heroes
|
||||
MATCH_ID("NDM", SAVE_TYPE_NONE, FEAT_CPAK), // Doom 64
|
||||
MATCH_ID("NDN", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Duke Nukem 64
|
||||
MATCH_ID("NDQ", SAVE_TYPE_NONE, FEAT_CPAK), // Disney's Donald Duck - Goin' Quackers [Quack Attack (E)]
|
||||
MATCH_ID("NDS", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Dynamite Soccer 64
|
||||
MATCH_ID("NDT", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // South Park
|
||||
MATCH_ID("NDW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Daikatana, John Romero's
|
||||
MATCH_ID("NDZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Duke Nukem - Zero Hour
|
||||
MATCH_ID("NEG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Extreme-G
|
||||
MATCH_ID("NET", SAVE_TYPE_NONE, FEAT_CPAK), // Quest 64 [Eltale Monsters (J) Holy Magic Century (E)]
|
||||
MATCH_ID("NF9", SAVE_TYPE_NONE, FEAT_CPAK), // Fox Sports College Hoops '99
|
||||
MATCH_ID("NFB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz 2001
|
||||
MATCH_ID("NFD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Flying Dragon
|
||||
MATCH_ID("NFF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Fighting Force 64
|
||||
MATCH_ID("NFL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2001
|
||||
MATCH_ID("NFO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Forsaken 64
|
||||
MATCH_ID("NFQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Razor Freestyle Scooter
|
||||
MATCH_ID("NFR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // F-1 Racing Championship
|
||||
MATCH_ID("NFS", SAVE_TYPE_NONE, FEAT_CPAK), // Famista 64
|
||||
MATCH_ID("NG2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Extreme-G XG2
|
||||
MATCH_ID("NG5", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ganbare Goemon - Neo Momoyama Bakufu no Odori [Mystical Ninja Starring Goemon]
|
||||
MATCH_ID("NGA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Deadly Arts [G.A.S.P!! Fighter's NEXTream (E-J)]
|
||||
MATCH_ID("NGB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Hyper Bike
|
||||
MATCH_ID("NGD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gauntlet Legends (J)
|
||||
MATCH_ID("NGM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Goemon's Great Adventure [Mystical Ninja 2 Starring Goemon]
|
||||
MATCH_ID("NGN", SAVE_TYPE_NONE, FEAT_CPAK), // Golden Nugget 64
|
||||
MATCH_ID("NGR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Rally (U)
|
||||
MATCH_ID("NGS", SAVE_TYPE_NONE, FEAT_CPAK), // Jikkyou G1 Stable
|
||||
MATCH_ID("NGX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gauntlet Legends
|
||||
MATCH_ID("NH5", SAVE_TYPE_NONE, FEAT_CPAK), // Nagano Winter Olympics '98 [Hyper Olympics in Nagano (J)]
|
||||
MATCH_ID("NH9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Breakaway '99
|
||||
MATCH_ID("NHC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hercules - The Legendary Journeys
|
||||
MATCH_ID("NHG", SAVE_TYPE_NONE, FEAT_CPAK), // F-1 Pole Position 64
|
||||
MATCH_ID("NHG", SAVE_TYPE_NONE, FEAT_CPAK), // Human Grand Prix - New Generation
|
||||
MATCH_ID("NHK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hiryuu no Ken Twin
|
||||
MATCH_ID("NHL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Breakaway '98
|
||||
MATCH_ID("NHM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mia Hamm Soccer 64
|
||||
MATCH_ID("NHN", SAVE_TYPE_NONE, FEAT_CPAK), // Olympic Hockey Nagano '98
|
||||
MATCH_ID("NHO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Blades of Steel '99 [NHL Pro '99 (E)]
|
||||
MATCH_ID("NHS", SAVE_TYPE_NONE, FEAT_CPAK), // Hamster Monogatari 64
|
||||
MATCH_ID("NHT", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hydro Thunder
|
||||
MATCH_ID("NHV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hybrid Heaven (U + E)
|
||||
MATCH_ID("NHW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hot Wheels Turbo Racing
|
||||
MATCH_ID("NHX", SAVE_TYPE_NONE, FEAT_CPAK), // Hexen
|
||||
MATCH_ID("NIS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // International Superstar Soccer 2000
|
||||
MATCH_ID("NIV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Space Invaders
|
||||
MATCH_ID("NJ2", SAVE_TYPE_NONE, FEAT_CPAK), // Wonder Project J2 - Koruro no Mori no Jozet (J)
|
||||
MATCH_ID("NJ3", SAVE_TYPE_NONE, FEAT_CPAK), // Jikkyou World Soccer 3
|
||||
MATCH_ID("NJA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Jam 2000
|
||||
MATCH_ID("NJE", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Eleven Beat 1997
|
||||
MATCH_ID("NJL", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Live 64
|
||||
MATCH_ID("NJP", SAVE_TYPE_NONE, FEAT_CPAK), // International Superstar Soccer 64 [Jikkyo J-League Perfect Striker (J)]
|
||||
MATCH_ID("NJQ", SAVE_TYPE_NONE, FEAT_RPAK), // Batman Beyond - Return of the Joker [Batman of the Future - Return of the Joker (E)]
|
||||
MATCH_ID("NKE", SAVE_TYPE_NONE, FEAT_RPAK), // Knife Edge - Nose Gunner
|
||||
MATCH_ID("NKK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Knockout Kings 2000
|
||||
MATCH_ID("NKM", SAVE_TYPE_NONE, FEAT_CPAK), // Pro Mahjong Kiwame 64 (J)
|
||||
MATCH_ID("NKR", SAVE_TYPE_NONE, FEAT_CPAK), // Rakuga Kids (E)
|
||||
MATCH_ID("NL2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Rally 2
|
||||
MATCH_ID("NLC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Automobili Lamborghini [Super Speed Race 64 (J)]
|
||||
MATCH_ID("NLG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // LEGO Racers
|
||||
MATCH_ID("NM3", SAVE_TYPE_NONE, FEAT_RPAK), // Monster Truck Madness 64
|
||||
MATCH_ID("NM4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mortal Kombat 4
|
||||
MATCH_ID("NM9", SAVE_TYPE_NONE, FEAT_CPAK), // Harukanaru Augusta Masters 98
|
||||
MATCH_ID("NMA", SAVE_TYPE_NONE, FEAT_CPAK), // Jangou Simulation Mahjong Do 64
|
||||
MATCH_ID("NMB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mike Piazza's Strike Zone
|
||||
MATCH_ID("NMD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2000
|
||||
MATCH_ID("NMJ", SAVE_TYPE_NONE, FEAT_CPAK), // Mahjong 64
|
||||
MATCH_ID("NMM", SAVE_TYPE_NONE, FEAT_CPAK), // Mahjong Master
|
||||
MATCH_ID("NMT", SAVE_TYPE_NONE, FEAT_RPAK), // Magical Tetris Challenge
|
||||
MATCH_ID("NMY", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mortal Kombat Mythologies - Sub-Zero
|
||||
MATCH_ID("NN2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nascar 2000
|
||||
MATCH_ID("NNC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nightmare Creatures
|
||||
MATCH_ID("NNL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Live 2000
|
||||
MATCH_ID("NNM", SAVE_TYPE_NONE, FEAT_CPAK), // Namco Museum 64
|
||||
MATCH_ID("NNR", SAVE_TYPE_NONE, FEAT_CPAK), // Pro Mahjong Tsuwamono 64 - Jansou Battle ni Chousen (J)
|
||||
MATCH_ID("NNS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Beetle Adventure Racing
|
||||
MATCH_ID("NO7", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // The World Is Not Enough
|
||||
MATCH_ID("NOF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Offroad Challenge
|
||||
MATCH_ID("NOH", SAVE_TYPE_NONE, FEAT_RPAK | FEAT_TPAK), // Transformers Beast Wars - Transmetals
|
||||
MATCH_ID("NOM", SAVE_TYPE_NONE, FEAT_CPAK), // Onegai Monsters
|
||||
MATCH_ID("NOW", SAVE_TYPE_NONE, FEAT_CPAK), // Brunswick Circuit Pro Bowling
|
||||
MATCH_ID("NP9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ms. Pac-Man - Maze Madness
|
||||
MATCH_ID("NPB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Puzzle Bobble 64 (J)
|
||||
MATCH_ID("NPC", SAVE_TYPE_NONE, FEAT_CPAK), // Pachinko 365 Nichi (J)
|
||||
MATCH_ID("NPK", SAVE_TYPE_NONE, FEAT_CPAK), // Chou Kuukan Night Pro Yakyuu King (J)
|
||||
MATCH_ID("NPL", SAVE_TYPE_NONE, FEAT_CPAK), // Power League 64 (J)
|
||||
MATCH_ID("NPR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // South Park Rally
|
||||
MATCH_ID("NPU", SAVE_TYPE_NONE, FEAT_CPAK), // Power Rangers - Lightspeed Rescue
|
||||
MATCH_ID("NPX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Polaris SnoCross
|
||||
MATCH_ID("NPZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Susume! Taisen Puzzle Dama Toukon! Marumata Chou (J)
|
||||
MATCH_ID("NQ2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Quake 2
|
||||
MATCH_ID("NQ8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club '98
|
||||
MATCH_ID("NQ9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club '99
|
||||
MATCH_ID("NQB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club 2000
|
||||
MATCH_ID("NQC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club 2001
|
||||
MATCH_ID("NQK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Quake 64
|
||||
MATCH_ID("NR2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rush 2 - Extreme Racing USA
|
||||
MATCH_ID("NR3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Stunt Racer 64
|
||||
MATCH_ID("NR6", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tom Clancy's Rainbow Six
|
||||
MATCH_ID("NRD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing
|
||||
MATCH_ID("NRG", SAVE_TYPE_NONE, FEAT_RPAK), // Rugrats - Scavenger Hunt [Treasure Hunt (E)]
|
||||
MATCH_ID("NRK", SAVE_TYPE_NONE, FEAT_CPAK), // Rugrats in Paris - The Movie
|
||||
MATCH_ID("NRO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Road Rash 64
|
||||
MATCH_ID("NRP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rampage - World Tour
|
||||
MATCH_ID("NRP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rampage 2 - Universal Tour
|
||||
MATCH_ID("NRR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Roadster's Trophy
|
||||
MATCH_ID("NRT", SAVE_TYPE_NONE, FEAT_CPAK), // Rat Attack
|
||||
MATCH_ID("NRT", SAVE_TYPE_NONE, FEAT_CPAK), // Robotron 64
|
||||
MATCH_ID("NRU", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // San Francisco Rush 2049
|
||||
MATCH_ID("NRV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Re-Volt
|
||||
MATCH_ID("NRW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok: Rage Wars
|
||||
MATCH_ID("NS2", SAVE_TYPE_NONE, FEAT_CPAK), // Simcity 2000
|
||||
MATCH_ID("NSB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Twisted Edge - Extreme Snowboarding [King Hill 64 - Extreme Snowboarding (J)]
|
||||
MATCH_ID("NSD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Shadow Man
|
||||
MATCH_ID("NSF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // San Francisco Rush - Extreme Racing
|
||||
MATCH_ID("NSG", SAVE_TYPE_NONE, FEAT_CPAK), // Shadowgate 64 - Trials Of The Four Towers
|
||||
MATCH_ID("NSH", SAVE_TYPE_NONE, FEAT_CPAK), // Saikyou Habu Shougi (J)
|
||||
MATCH_ID("NSK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Snowboard Kids [Snobow Kids (J)]
|
||||
MATCH_ID("NSL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Spider-Man
|
||||
MATCH_ID("NSO", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Showtime - NBA on NBC
|
||||
MATCH_ID("NSP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Superman
|
||||
MATCH_ID("NST", SAVE_TYPE_NONE, FEAT_CPAK), // Eikou no Saint Andrews
|
||||
MATCH_ID("NSX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Supercross 2000
|
||||
MATCH_ID("NSY", SAVE_TYPE_NONE, FEAT_CPAK), // Scooby-Doo! - Classic Creep Capers
|
||||
MATCH_ID("NSZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz - Special Edition
|
||||
MATCH_ID("NT2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok 2 - Seeds of Evil [Violence Killer - Turok New Generation (J)]
|
||||
MATCH_ID("NT3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater 3
|
||||
MATCH_ID("NT4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // CyberTiger
|
||||
MATCH_ID("NTA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Disney's Tarzan
|
||||
MATCH_ID("NTF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater
|
||||
MATCH_ID("NTH", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Toy Story 2 - Buzz Lightyear to the Rescue!
|
||||
MATCH_ID("NTI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WWF: Attitude
|
||||
MATCH_ID("NTK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok 3 - Shadow of Oblivion
|
||||
MATCH_ID("NTO", SAVE_TYPE_NONE, FEAT_CPAK), // Shin Nihon Pro Wrestling - Toukon Road - Brave Spirits (J)
|
||||
MATCH_ID("NTQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater 2
|
||||
MATCH_ID("NTS", SAVE_TYPE_NONE, FEAT_CPAK), // Centre Court Tennis [Let's Smash (J)]
|
||||
MATCH_ID("NTT", SAVE_TYPE_NONE, FEAT_CPAK), // Tonic Trouble
|
||||
MATCH_ID("NTU", SAVE_TYPE_NONE, FEAT_CPAK), // Turok: Dinosaur Hunter [Turok: Jikuu Senshi (J)]
|
||||
MATCH_ID("NV2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Chameleon Twist 2
|
||||
MATCH_ID("NV3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Micro Machines 64 Turbo
|
||||
MATCH_ID("NV8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Vigilante 8
|
||||
MATCH_ID("NVC", SAVE_TYPE_NONE, FEAT_CPAK), // Virtual Chess 64
|
||||
MATCH_ID("NVG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Vigilante 8 - Second Offense
|
||||
MATCH_ID("NVR", SAVE_TYPE_NONE, FEAT_CPAK), // Virtual Pool 64
|
||||
MATCH_ID("NW3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Nitro
|
||||
MATCH_ID("NW8", SAVE_TYPE_NONE, FEAT_CPAK), // Wayne Gretzky's 3D Hockey '98
|
||||
MATCH_ID("NWB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Iggy's Reckin' Balls [Iggy-kun no Bura Bura Poyon (J)]
|
||||
MATCH_ID("NWD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Winback - Covert Operations
|
||||
MATCH_ID("NWF", SAVE_TYPE_NONE, FEAT_RPAK), // Wheel of Fortune
|
||||
MATCH_ID("NWG", SAVE_TYPE_NONE, FEAT_CPAK), // Wayne Gretzky's 3D Hockey
|
||||
MATCH_ID("NWI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // ECW Hardcore Revolution
|
||||
MATCH_ID("NWK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Michael Owens WLS 2000 [World League Soccer 2000 (E) / Telefoot Soccer 2000 (F)]
|
||||
MATCH_ID("NWM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Mayhem
|
||||
MATCH_ID("NWN", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW vs. nWo - World Tour
|
||||
MATCH_ID("NWO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // World Driver Championship
|
||||
MATCH_ID("NWP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Wipeout 64
|
||||
MATCH_ID("NWS", SAVE_TYPE_NONE, FEAT_CPAK), // International Superstar Soccer '98 [Jikkyo World Soccer - World Cup France '98 (J)]
|
||||
MATCH_ID("NWV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Backstage Assault
|
||||
MATCH_ID("NWW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WWF: War Zone
|
||||
MATCH_ID("NWZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone 2000
|
||||
MATCH_ID("NX2", SAVE_TYPE_NONE, FEAT_CPAK), // Gex 64 - Enter the Gecko
|
||||
MATCH_ID("NX3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gex 3 - Deep Cover Gecko
|
||||
MATCH_ID("NXF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Xena Warrior Princess - The Talisman of Fate
|
||||
MATCH_ID("NXG", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Hangtime
|
||||
MATCH_ID("NY2", SAVE_TYPE_NONE, FEAT_CPAK), // Rayman 2 - The Great Escape
|
||||
MATCH_ID("NYP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Paperboy
|
||||
MATCH_ID("NYW", SAVE_TYPE_NONE, FEAT_CPAK), // Bokujou Monogatari 2
|
||||
MATCH_ID("NZO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battlezone - Rise of the Black Dogs
|
||||
|
||||
MATCH_END,
|
||||
};
|
||||
|
||||
|
||||
static void fix_rom_header_endianness (rom_header_t *rom_header, rom_info_t *rom_info) {
|
||||
uint8_t *raw = (uint8_t *) (rom_header);
|
||||
|
||||
switch (rom_header->pi_dom1_config) {
|
||||
case PI_CONFIG_LITTLE_ENDIAN:
|
||||
rom_info->endianness = ENDIANNESS_LITTLE;
|
||||
for (int i = 0; i < sizeof(rom_header_t); i += 4) {
|
||||
SWAP_VARS(raw[i + 0], raw[i + 3]);
|
||||
SWAP_VARS(raw[i + 1], raw[i + 2]);
|
||||
}
|
||||
break;
|
||||
|
||||
case PI_CONFIG_BYTE_SWAPPED:
|
||||
rom_info->endianness = ENDIANNESS_BYTE_SWAP;
|
||||
for (int i = 0; i < sizeof(rom_header_t); i += 2) {
|
||||
SWAP_VARS(raw[i + 0], raw[i + 1]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
rom_info->endianness = ENDIANNESS_BIG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool compare_id (const match_t *match, rom_header_t *rom_header) {
|
||||
int characters_to_check = (match->type == MATCH_TYPE_ID) ? 3 : 4;
|
||||
|
||||
for (int i = (characters_to_check - 1); i >= 0; i--) {
|
||||
if (match->fields.id[i] != rom_header->game_code[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (match->type == MATCH_TYPE_ID_REGION_VERSION) {
|
||||
if (match->fields.version != rom_header->version) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static match_t find_rom_in_database (rom_header_t *rom_header) {
|
||||
const match_t *match;
|
||||
|
||||
for (match = database; match->type != MATCH_TYPE_END; match++) {
|
||||
switch (match->type) {
|
||||
case MATCH_TYPE_ID:
|
||||
case MATCH_TYPE_ID_REGION:
|
||||
case MATCH_TYPE_ID_REGION_VERSION:
|
||||
if (compare_id(match, rom_header)) {
|
||||
return *match;
|
||||
}
|
||||
break;
|
||||
|
||||
case MATCH_TYPE_CHECK_CODE:
|
||||
if (match->fields.check_code == rom_header->check_code) {
|
||||
return *match;
|
||||
}
|
||||
break;
|
||||
|
||||
case MATCH_TYPE_HOMEBREW_HEADER:
|
||||
if (strncmp(match->fields.id, rom_header->unique_code, sizeof(rom_header->unique_code)) == 0) {
|
||||
return *match;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return *match;
|
||||
}
|
||||
|
||||
static uint32_t fix_boot_address (cic_type_t cic_type, uint32_t boot_address) {
|
||||
switch (cic_type) {
|
||||
case CIC_x103: return (boot_address - 0x100000);
|
||||
case CIC_x106: return (boot_address - 0x200000);
|
||||
default: return boot_address;
|
||||
}
|
||||
}
|
||||
|
||||
static void extract_rom_info (match_t *match, rom_header_t *rom_header, rom_info_t *rom_info) {
|
||||
rom_info->cic_type = cic_detect(rom_header->ipl3);
|
||||
|
||||
if (match->type == MATCH_TYPE_HOMEBREW_HEADER) {
|
||||
if (rom_header->version & (1 << 0)) {
|
||||
match->data.feat |= FEAT_RTC;
|
||||
}
|
||||
switch ((rom_header->version & 0xF0) >> 4) {
|
||||
case 0: match->data.save = SAVE_TYPE_NONE; break;
|
||||
case 1: match->data.save = SAVE_TYPE_EEPROM_4K; break;
|
||||
case 2: match->data.save = SAVE_TYPE_EEPROM_16K; break;
|
||||
case 3: match->data.save = SAVE_TYPE_SRAM; break;
|
||||
case 4: match->data.save = SAVE_TYPE_SRAM_BANKED; break;
|
||||
case 5: match->data.save = SAVE_TYPE_FLASHRAM; break;
|
||||
case 6: match->data.save = SAVE_TYPE_SRAM_128K; break;
|
||||
default: match->data.save = SAVE_TYPE_NONE; break;
|
||||
}
|
||||
}
|
||||
|
||||
rom_info->clock_rate = (rom_header->clock_rate == CLOCK_RATE_DEFAULT) ? 62.5f : (rom_header->clock_rate & ~(CLOCK_RATE_DEFAULT)) / 1000000.0f;
|
||||
rom_info->boot_address = fix_boot_address(rom_info->cic_type, rom_header->boot_address);
|
||||
rom_info->libultra.version = rom_header->libultra.version;
|
||||
rom_info->libultra.revision = rom_header->libultra.revision;
|
||||
rom_info->check_code = rom_header->check_code;
|
||||
memcpy(rom_info->title, rom_header->title, sizeof(rom_info->title));
|
||||
memcpy(rom_info->game_code, rom_header->game_code, sizeof(rom_info->game_code));
|
||||
rom_info->version = rom_header->version;
|
||||
|
||||
rom_info->save_type = match->data.save;
|
||||
|
||||
rom_info->features.controller_pak = (match->data.feat & FEAT_CPAK);
|
||||
rom_info->features.rumble_pak = (match->data.feat & FEAT_RPAK);
|
||||
rom_info->features.transfer_pak = (match->data.feat & FEAT_TPAK);
|
||||
rom_info->features.voice_recognition_unit = (match->data.feat & FEAT_VRU);
|
||||
rom_info->features.real_time_clock = (match->data.feat & FEAT_RTC);
|
||||
rom_info->features.disk_conversion = (match->data.feat & FEAT_64DD_CONVERSION);
|
||||
rom_info->features.combo_rom_disk_game = (match->data.feat & FEAT_64DD_ENHANCED);
|
||||
if (match->data.feat & FEAT_EXP_PAK_REQUIRED) {
|
||||
rom_info->features.expansion_pak = EXPANSION_PAK_REQUIRED;
|
||||
} else if (match->data.feat & FEAT_EXP_PAK_RECOMMENDED) {
|
||||
rom_info->features.expansion_pak = EXPANSION_PAK_RECOMMENDED;
|
||||
} else if (match->data.feat & FEAT_EXP_PAK_ENHANCED) {
|
||||
rom_info->features.expansion_pak = EXPANSION_PAK_SUGGESTED;
|
||||
} else if (match->data.feat & FEAT_EXP_PAK_BROKEN) {
|
||||
rom_info->features.expansion_pak = EXPANSION_PAK_FAULTY;
|
||||
} else {
|
||||
rom_info->features.expansion_pak = EXPANSION_PAK_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rom_err_t rom_info_load (char *path, rom_info_t *rom_info) {
|
||||
FIL fil;
|
||||
UINT br;
|
||||
rom_header_t rom_header;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) {
|
||||
return ROM_ERR_NO_FILE;
|
||||
}
|
||||
if (f_read(&fil, &rom_header, sizeof(rom_header), &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
if (br != sizeof(rom_header)) {
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
|
||||
fix_rom_header_endianness(&rom_header, rom_info);
|
||||
|
||||
match_t match = find_rom_in_database(&rom_header);
|
||||
|
||||
extract_rom_info(&match, &rom_header, rom_info);
|
||||
|
||||
return ROM_OK;
|
||||
}
|
156
src/menu/rom_info.h
Normal file
156
src/menu/rom_info.h
Normal file
@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @file rom_info.h
|
||||
* @brief N64 ROM Database.
|
||||
* @note Only works with N64 ROM's by checking the first 1024 bytes of the file.
|
||||
* @ingroup menu
|
||||
*/
|
||||
|
||||
#ifndef ROM_INFO_H__
|
||||
#define ROM_INFO_H__
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "boot/cic.h"
|
||||
|
||||
|
||||
typedef enum {
|
||||
ROM_OK,
|
||||
ROM_ERR_IO,
|
||||
ROM_ERR_NO_FILE,
|
||||
} rom_err_t;
|
||||
|
||||
typedef enum {
|
||||
ENDIANNESS_BIG,
|
||||
ENDIANNESS_LITTLE,
|
||||
ENDIANNESS_BYTE_SWAP,
|
||||
} endianness_t;
|
||||
|
||||
/** @brief ROM media type enumeration. */
|
||||
typedef enum {
|
||||
/** @brief Is a stand alone Cartridge program. */
|
||||
N64_CART = 'N',
|
||||
/** @brief Is a stand alone Disk Drive program. */
|
||||
N64_DISK = 'D',
|
||||
/** @brief Is a Cartridge program that could use an extra Disk Drive program to expand its capabilities. */
|
||||
N64_CART_EXPANDABLE = 'C',
|
||||
/** @brief Is a Disk Drive program that could use an extra Cartridge program to expand its capabilities. */
|
||||
N64_DISK_EXPANDABLE = 'E',
|
||||
/** @brief Is an Aleck64 program. */
|
||||
N64_ALECK64 = 'Z'
|
||||
} category_type_t;
|
||||
|
||||
/** @brief ROM market region & language type enumeration. */
|
||||
typedef enum {
|
||||
/** @brief The ROM is designed for Japanese and "English" languages. */
|
||||
MARKET_JAPANESE_MULTI = 'A', // 1080 Snowboarding JPN is the only ROM that uses this? possibily a mistake, or the fact it also includes American English!.
|
||||
/** @brief The ROM is designed for Brazil (Portuguese) language. */
|
||||
MARKET_BRAZILIAN = 'B',
|
||||
/** @brief The ROM is designed for Chinese language. */
|
||||
MARKET_CHINESE = 'C',
|
||||
/** @brief The ROM is designed for German language. */
|
||||
MARKET_GERMAN = 'D',
|
||||
/** @brief The ROM is designed for North American "English" language. */
|
||||
MARKET_NORTH_AMERICA = 'E',
|
||||
/** @brief The ROM is designed for French language. */
|
||||
MARKET_FRENCH = 'F',
|
||||
/** @brief The ROM is designed for a NTSC Gateway 64. */
|
||||
MARKET_GATEWAY64_NTSC = 'G',
|
||||
/** @brief The ROM is designed for Dutch language. */
|
||||
MARKET_DUTCH = 'H',
|
||||
/** @brief The ROM is designed for Italian language. */
|
||||
MARKET_ITALIAN = 'I',
|
||||
/** @brief The ROM is designed for Japanese language. */
|
||||
MARKET_JAPANESE = 'J',
|
||||
/** @brief The ROM is designed for Korean language. */
|
||||
MARKET_KOREAN = 'K',
|
||||
/** @brief The ROM is designed for a PAL Gateway 64. */
|
||||
MARKET_GATEWAY64_PAL = 'L',
|
||||
/** @brief The ROM is designed for Canada region (English and French) language. */
|
||||
MARKET_CANADIAN = 'N',
|
||||
/** @brief The ROM is designed for European market and languages (must at minimum include English). */
|
||||
MARKET_EUROPEAN_BASIC = 'P', // Sometimes used for Australian region ROMs as well.
|
||||
/** @brief The ROM is designed for Spanish language */
|
||||
MARKET_SPANISH = 'S',
|
||||
/** @brief The ROM is designed for Australia (English) language. */
|
||||
MARKET_AUSTRALIAN = 'U',
|
||||
/** @brief The ROM is designed for Scandinavian (Swedish, Norwegian, Finnish, etc.) languages. */
|
||||
MARKET_SCANDINAVIAN = 'W',
|
||||
/** @brief The ROM is designed for an undefined region and TBD language(s). */
|
||||
MARKET_OTHER_X = 'X', // many EU ROM's, Top Gear Rally (Asia) and HSV Racing (AUS) ROM uses this.
|
||||
/** @brief The ROM is designed for a European region and language(s). */
|
||||
MARKET_OTHER_Y = 'Y', // many EU ROM's uses this.
|
||||
/** @brief The ROM is designed for an undefined region and TBD language(s). */
|
||||
MARKET_OTHER_Z = 'Z' // no known ROM's use this.
|
||||
} destination_type_t;
|
||||
|
||||
typedef enum {
|
||||
SAVE_TYPE_NONE,
|
||||
SAVE_TYPE_EEPROM_4K,
|
||||
SAVE_TYPE_EEPROM_16K,
|
||||
SAVE_TYPE_SRAM,
|
||||
SAVE_TYPE_SRAM_BANKED,
|
||||
SAVE_TYPE_SRAM_128K,
|
||||
SAVE_TYPE_FLASHRAM,
|
||||
SAVE_TYPE_FLASHRAM_PKST2,
|
||||
} save_type_t;
|
||||
|
||||
typedef enum {
|
||||
/** @brief The ROM is happy with 4MB of memory. */
|
||||
EXPANSION_PAK_NONE,
|
||||
|
||||
/** @brief The ROM requires 8MB of memory. */
|
||||
EXPANSION_PAK_REQUIRED,
|
||||
|
||||
/** @brief The ROM recommends 8MB of memory. */
|
||||
EXPANSION_PAK_RECOMMENDED,
|
||||
|
||||
/** @brief The ROM suggests 8MB of memory. */
|
||||
EXPANSION_PAK_SUGGESTED,
|
||||
|
||||
/** @brief The ROM is faulty when using 8MB of memory. */
|
||||
EXPANSION_PAK_FAULTY,
|
||||
} expansion_pak_t;
|
||||
|
||||
typedef struct {
|
||||
endianness_t endianness;
|
||||
float clock_rate;
|
||||
uint32_t boot_address;
|
||||
struct {
|
||||
uint8_t version;
|
||||
char revision;
|
||||
} libultra;
|
||||
uint64_t check_code;
|
||||
char title[20];
|
||||
union {
|
||||
char game_code[4];
|
||||
struct {
|
||||
category_type_t category_code : 8;
|
||||
char unique_code[2];
|
||||
destination_type_t destination_code : 8;
|
||||
};
|
||||
};
|
||||
uint8_t version;
|
||||
|
||||
cic_type_t cic_type;
|
||||
|
||||
save_type_t save_type;
|
||||
|
||||
struct {
|
||||
bool controller_pak;
|
||||
bool rumble_pak;
|
||||
bool transfer_pak;
|
||||
bool voice_recognition_unit;
|
||||
bool real_time_clock;
|
||||
bool disk_conversion;
|
||||
bool combo_rom_disk_game;
|
||||
expansion_pak_t expansion_pak;
|
||||
} features;
|
||||
} rom_info_t;
|
||||
|
||||
|
||||
rom_err_t rom_info_load (char *path, rom_info_t *rom_info);
|
||||
|
||||
|
||||
#endif
|
@ -4,15 +4,20 @@
|
||||
#include "settings.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
|
||||
#define SETTINGS_FILE_PATH "sd:/config.ini"
|
||||
|
||||
#ifndef SETTINGS_FILE_PATH
|
||||
#define SETTINGS_FILE_PATH "sd:/menu/config.ini"
|
||||
#endif
|
||||
|
||||
static settings_t init = {
|
||||
.pal60 = false,
|
||||
.show_hidden_files = false,
|
||||
.pal60_enabled = false,
|
||||
.hidden_files_enabled = false,
|
||||
.default_directory = "/",
|
||||
.use_saves_folder = false,
|
||||
.use_saves_folder = true,
|
||||
|
||||
/* Beta feature flags */
|
||||
.bgm_enabled = false,
|
||||
.sound_enabled = false,
|
||||
.rumble_enabled = true,
|
||||
};
|
||||
|
||||
|
||||
@ -23,22 +28,33 @@ void settings_load (settings_t *settings) {
|
||||
|
||||
mini_t *ini = mini_try_load(SETTINGS_FILE_PATH);
|
||||
|
||||
settings->pal60 = mini_get_bool(ini, "menu", "pal60", init.pal60);
|
||||
settings->show_hidden_files = mini_get_bool(ini, "menu", "show_hidden_files", init.show_hidden_files);
|
||||
settings->pal60_enabled = mini_get_bool(ini, "menu", "pal60", init.pal60_enabled); // TODO: consider changing file setting name
|
||||
settings->hidden_files_enabled = mini_get_bool(ini, "menu", "show_hidden_files", init.hidden_files_enabled);
|
||||
settings->default_directory = strdup(mini_get_string(ini, "menu", "default_directory", init.default_directory));
|
||||
settings->use_saves_folder = mini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder);
|
||||
|
||||
/* Beta feature flags, they might not be in the file */
|
||||
settings->bgm_enabled = mini_get_bool(ini, "menu_beta_flag", "bgm_enabled", init.bgm_enabled);
|
||||
settings->sound_enabled = mini_get_bool(ini, "menu_beta_flag", "sound_enabled", init.sound_enabled);
|
||||
settings->rumble_enabled = mini_get_bool(ini, "menu_beta_flag", "rumble_enabled", init.rumble_enabled);
|
||||
|
||||
|
||||
mini_free(ini);
|
||||
}
|
||||
|
||||
void settings_save (settings_t *settings) {
|
||||
mini_t *ini = mini_create(SETTINGS_FILE_PATH);
|
||||
|
||||
mini_set_bool(ini, "menu", "pal60", settings->pal60);
|
||||
mini_set_bool(ini, "menu", "show_hidden_files", settings->show_hidden_files);
|
||||
mini_set_bool(ini, "menu", "pal60", settings->pal60_enabled);
|
||||
mini_set_bool(ini, "menu", "show_hidden_files", settings->hidden_files_enabled);
|
||||
mini_set_string(ini, "menu", "default_directory", settings->default_directory);
|
||||
mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder);
|
||||
|
||||
/* Beta feature flags, they should not save until production ready! */
|
||||
// mini_set_bool(ini, "menu_beta_flag", "bgm_enabled", init.bgm_enabled);
|
||||
// mini_set_bool(ini, "menu_beta_flag", "sound_enabled", init.sound_enabled);
|
||||
// mini_set_bool(ini, "menu_beta_flag", "rumble_enabled", init.rumble_enabled);
|
||||
|
||||
mini_save(ini);
|
||||
|
||||
mini_free(ini);
|
||||
|
@ -10,17 +10,27 @@
|
||||
|
||||
/** @brief Settings Structure */
|
||||
typedef struct {
|
||||
/** @brief Select 60 Hz refresh rate if running on PAL console */
|
||||
bool pal60;
|
||||
/** @brief Use 60 Hz refresh rate on a PAL console */
|
||||
bool pal60_enabled;
|
||||
|
||||
/** @brief Show files marked as hidden in the browser */
|
||||
bool show_hidden_files;
|
||||
bool hidden_files_enabled;
|
||||
|
||||
/** @brief Default directory to navigate to when menu loads */
|
||||
char *default_directory;
|
||||
|
||||
/** @brief Put saves into separate directory */
|
||||
bool use_saves_folder;
|
||||
|
||||
/** @brief Enable Background music */
|
||||
bool bgm_enabled;
|
||||
|
||||
/** @brief Enable Sounds */
|
||||
bool sound_enabled;
|
||||
|
||||
/** @brief Enable rumble feedback */
|
||||
bool rumble_enabled;
|
||||
|
||||
} settings_t;
|
||||
|
||||
|
||||
|
@ -35,6 +35,14 @@ void sound_init_mp3_playback (void) {
|
||||
sound_reconfigure(mp3player_get_samplerate());
|
||||
}
|
||||
|
||||
void sound_deinit (void) {
|
||||
if (sound_initialized) {
|
||||
mixer_close();
|
||||
audio_close();
|
||||
sound_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void sound_poll (void) {
|
||||
if (sound_initialized && audio_can_write()) {
|
||||
short *audio_buffer = audio_write_begin();
|
||||
@ -42,11 +50,3 @@ void sound_poll (void) {
|
||||
audio_write_end();
|
||||
}
|
||||
}
|
||||
|
||||
void sound_close (void) {
|
||||
if (sound_initialized) {
|
||||
mixer_close();
|
||||
audio_close();
|
||||
sound_initialized = false;
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,8 @@
|
||||
|
||||
void sound_init_default (void);
|
||||
void sound_init_mp3_playback (void);
|
||||
void sound_deinit (void);
|
||||
void sound_poll (void);
|
||||
void sound_close (void);
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
|
||||
static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
|
||||
static const char *disk_extensions[] = { "ndd", 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 };
|
||||
@ -29,6 +30,10 @@ static int compare_entry (const void *pa, const void *pb) {
|
||||
return -1;
|
||||
} else if (b->type == ENTRY_TYPE_ROM) {
|
||||
return 1;
|
||||
} else if (a->type == ENTRY_TYPE_DISK) {
|
||||
return -1;
|
||||
} else if (b->type == ENTRY_TYPE_DISK) {
|
||||
return 1;
|
||||
} else if (a->type == ENTRY_TYPE_EMULATOR) {
|
||||
return -1;
|
||||
} else if (b->type == ENTRY_TYPE_EMULATOR) {
|
||||
@ -81,7 +86,7 @@ static bool load_directory (menu_t *menu) {
|
||||
if (info.fattrib & AM_SYS) {
|
||||
continue;
|
||||
}
|
||||
if ((info.fattrib & AM_HID) && !menu->settings.show_hidden_files) {
|
||||
if ((info.fattrib & AM_HID) && !menu->settings.hidden_files_enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -97,6 +102,8 @@ static bool load_directory (menu_t *menu) {
|
||||
entry->type = ENTRY_TYPE_DIR;
|
||||
} else if (file_has_extensions(info.fname, rom_extensions)) {
|
||||
entry->type = ENTRY_TYPE_ROM;
|
||||
} else if (file_has_extensions(info.fname, disk_extensions)) {
|
||||
entry->type = ENTRY_TYPE_DISK;
|
||||
}else if (file_has_extensions(info.fname, emulator_extensions)) {
|
||||
entry->type = ENTRY_TYPE_EMULATOR;
|
||||
} else if (file_has_extensions(info.fname, save_extensions)) {
|
||||
@ -226,7 +233,7 @@ static void process (menu_t *menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
int scroll_speed = menu->actions.fast ? 10 : 1;
|
||||
int scroll_speed = menu->actions.go_fast ? 10 : 1;
|
||||
|
||||
if (menu->browser.entries > 1) {
|
||||
if (menu->actions.go_up) {
|
||||
@ -254,6 +261,9 @@ static void process (menu_t *menu) {
|
||||
case ENTRY_TYPE_ROM:
|
||||
menu->next_mode = MENU_MODE_LOAD_ROM;
|
||||
break;
|
||||
case ENTRY_TYPE_DISK:
|
||||
menu->next_mode = MENU_MODE_LOAD_DISK;
|
||||
break;
|
||||
case ENTRY_TYPE_EMULATOR:
|
||||
menu->next_mode = MENU_MODE_LOAD_EMULATOR;
|
||||
break;
|
||||
@ -297,6 +307,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
switch (menu->browser.entry->type) {
|
||||
case ENTRY_TYPE_DIR: action = "A: Enter"; break;
|
||||
case ENTRY_TYPE_ROM: action = "A: Load"; break;
|
||||
case ENTRY_TYPE_DISK: action = "A: Load"; break;
|
||||
case ENTRY_TYPE_IMAGE: action = "A: Show"; break;
|
||||
case ENTRY_TYPE_MUSIC: action = "A: Play"; break;
|
||||
default: action = "A: Info"; break;
|
||||
|
@ -23,11 +23,18 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
static void deinit (menu_t *menu) {
|
||||
menu->error_message = NULL;
|
||||
menu->flashcart_err = FLASHCART_OK;
|
||||
}
|
||||
|
||||
|
||||
void view_error_init (menu_t *menu) {
|
||||
// Nothing to initialize (yet)
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
debugf(
|
||||
"Flashcart error [%d]: %s\n",
|
||||
menu->flashcart_err,
|
||||
flashcart_convert_error_message(menu->flashcart_err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void view_error_display (menu_t *menu, surface_t *display) {
|
||||
|
@ -1,38 +1,16 @@
|
||||
#include "views.h"
|
||||
|
||||
|
||||
static char *convert_error_message (flashcart_error_t error) {
|
||||
switch (error) {
|
||||
case FLASHCART_OK:
|
||||
return "No error";
|
||||
case FLASHCART_ERROR_NOT_DETECTED:
|
||||
return "No flashcart hardware was detected";
|
||||
case FLASHCART_ERROR_OUTDATED:
|
||||
return "Outdated flashcart firmware";
|
||||
case FLASHCART_ERROR_SD_CARD:
|
||||
return "Error during SD card initialization";
|
||||
case FLASHCART_ERROR_ARGS:
|
||||
return "Invalid argument passed to flashcart function";
|
||||
case FLASHCART_ERROR_LOAD:
|
||||
return "Error during loading data into flashcart";
|
||||
case FLASHCART_ERROR_INT:
|
||||
return "Internal flashcart error";
|
||||
default:
|
||||
return "Unknown flashcart error";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void draw (menu_t *menu, surface_t *d) {
|
||||
rdpq_attach(d, NULL);
|
||||
|
||||
rdpq_clear(RGBA32(0x7F, 0x00, 0x00, 0xFF));
|
||||
|
||||
const char *firmware_message = (
|
||||
"Minimum supported versions:\n"
|
||||
"EverDrive-64: ?\n"
|
||||
"64drive: 2.05\n"
|
||||
"SC64: 2.16.0"
|
||||
"Supported firmware versions:\n"
|
||||
"64drive: 2.05+\n"
|
||||
"EverDrive-64: ???+\n"
|
||||
"SummerCart64: 2.17.0+"
|
||||
);
|
||||
|
||||
component_messagebox_draw(
|
||||
@ -41,8 +19,8 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"%s\n"
|
||||
"\n"
|
||||
"%s",
|
||||
convert_error_message(menu->flashcart_error),
|
||||
menu->flashcart_error == FLASHCART_ERROR_OUTDATED ? firmware_message : ""
|
||||
flashcart_convert_error_message(menu->flashcart_err),
|
||||
(menu->flashcart_err == FLASHCART_ERR_OUTDATED) ? firmware_message : ""
|
||||
);
|
||||
|
||||
rdpq_detach_show();
|
||||
|
160
src/menu/views/load_disk.c
Normal file
160
src/menu/views/load_disk.c
Normal file
@ -0,0 +1,160 @@
|
||||
#include "../cart_load.h"
|
||||
#include "../disk_info.h"
|
||||
#include "boot/boot.h"
|
||||
#include "views.h"
|
||||
|
||||
|
||||
static bool load_pending;
|
||||
static bool load_rom;
|
||||
|
||||
|
||||
static char *convert_error_message (disk_err_t err) {
|
||||
switch (err) {
|
||||
case DISK_ERR_IO: return "I/O error during loading 64DD disk information";
|
||||
case DISK_ERR_NO_FILE: return "Couldn't open 64DD disk file";
|
||||
case DISK_ERR_INVALID: return "Invalid 64DD disk file";
|
||||
default: return "Unknown disk info load error";
|
||||
}
|
||||
}
|
||||
|
||||
static char *format_disk_region (disk_region_t region) {
|
||||
switch (region) {
|
||||
case DISK_REGION_DEVELOPMENT: return "Development";
|
||||
case DISK_REGION_JAPANESE: return "Japan";
|
||||
case DISK_REGION_USA: return "USA";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.enter) {
|
||||
load_pending = true;
|
||||
load_rom = false;
|
||||
} else if (menu->actions.options && menu->load.rom_path) {
|
||||
load_pending = true;
|
||||
load_rom = true;
|
||||
} else if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw (menu_t *menu, surface_t *d) {
|
||||
rdpq_attach(d, NULL);
|
||||
|
||||
component_background_draw();
|
||||
|
||||
if (load_pending) {
|
||||
component_loader_draw(0.0f);
|
||||
} else {
|
||||
component_layout_draw();
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_CENTER, VALIGN_TOP,
|
||||
"64DD disk information\n"
|
||||
"\n"
|
||||
"%s",
|
||||
menu->browser.entry->name
|
||||
);
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
" Region: %s\n"
|
||||
" Unique ID: %.4s\n"
|
||||
" Version: %hhu\n"
|
||||
" Disk type: %d\n"
|
||||
"\n"
|
||||
" %s%s",
|
||||
format_disk_region(menu->load.disk_info.region),
|
||||
menu->load.disk_info.id,
|
||||
menu->load.disk_info.version,
|
||||
menu->load.disk_info.disk_type,
|
||||
menu->load.rom_path ? "ROM: " : "",
|
||||
menu->load.rom_path ? path_last_get(menu->load.rom_path) : ""
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"A: Load and run 64DD disk\n"
|
||||
"B: Exit"
|
||||
);
|
||||
|
||||
if (menu->load.rom_path) {
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_RIGHT, VALIGN_TOP,
|
||||
"R: Load with ROM"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
rdpq_detach_show();
|
||||
}
|
||||
|
||||
static void draw_progress (float progress) {
|
||||
surface_t *d = (progress >= 1.0f) ? display_get() : display_try_get();
|
||||
|
||||
if (d) {
|
||||
rdpq_attach(d, NULL);
|
||||
|
||||
component_background_draw();
|
||||
|
||||
component_loader_draw(progress);
|
||||
|
||||
rdpq_detach_show();
|
||||
}
|
||||
}
|
||||
|
||||
static void load (menu_t *menu) {
|
||||
cart_load_err_t err;
|
||||
|
||||
if (menu->load.rom_path && load_rom) {
|
||||
err = cart_load_n64_rom_and_save(menu, draw_progress);
|
||||
if (err != CART_LOAD_OK) {
|
||||
menu_show_error(menu, cart_load_convert_error_message(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
err = cart_load_64dd_ipl_and_disk(menu, draw_progress);
|
||||
if (err != CART_LOAD_OK) {
|
||||
menu_show_error(menu, cart_load_convert_error_message(err));
|
||||
return;
|
||||
}
|
||||
|
||||
menu->next_mode = MENU_MODE_BOOT;
|
||||
menu->boot_params->device_type = load_rom ? BOOT_DEVICE_TYPE_ROM : BOOT_DEVICE_TYPE_64DD;
|
||||
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
||||
menu->boot_params->detect_cic_seed = true;
|
||||
}
|
||||
|
||||
|
||||
void view_load_disk_init (menu_t *menu) {
|
||||
if (menu->load.disk_path) {
|
||||
path_free(menu->load.disk_path);
|
||||
menu->load.disk_path = NULL;
|
||||
}
|
||||
|
||||
load_pending = false;
|
||||
|
||||
menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
|
||||
disk_err_t err = disk_info_load(path_get(menu->load.disk_path), &menu->load.disk_info);
|
||||
if (err != DISK_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
}
|
||||
}
|
||||
|
||||
void view_load_disk_display (menu_t *menu, surface_t *display) {
|
||||
process(menu);
|
||||
|
||||
draw(menu, display);
|
||||
|
||||
if (load_pending) {
|
||||
load_pending = false;
|
||||
load(menu);
|
||||
}
|
||||
}
|
@ -75,7 +75,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
}
|
||||
|
||||
static void draw_progress (float progress) {
|
||||
surface_t *d = display_try_get();
|
||||
surface_t *d = (progress >= 1.0f) ? display_get() : display_try_get();
|
||||
|
||||
if (d) {
|
||||
rdpq_attach(d, NULL);
|
||||
|
@ -1,140 +1,110 @@
|
||||
#include "../cart_load.h"
|
||||
#include "../rom_database.h"
|
||||
#include "../rom_info.h"
|
||||
#include "boot/boot.h"
|
||||
#include "views.h"
|
||||
|
||||
|
||||
static bool load_pending;
|
||||
static rom_header_t rom_header;
|
||||
static component_boxart_t *boxart;
|
||||
|
||||
|
||||
static char *format_rom_endian (rom_endian_type_t endian) {
|
||||
switch (endian) {
|
||||
case ROM_BIG_ENDIAN:
|
||||
case IPL_BIG_ENDIAN:
|
||||
return "Big (default)";
|
||||
case ROM_LITTLE_ENDIAN:
|
||||
return "Little (unsupported)";
|
||||
case ROM_MID_BIG_ENDIAN:
|
||||
return "Byte swapped";
|
||||
default:
|
||||
return "Unknown";
|
||||
static char *convert_error_message (disk_err_t err) {
|
||||
switch (err) {
|
||||
case ROM_ERR_IO: return "I/O error during loading ROM information";
|
||||
case ROM_ERR_NO_FILE: return "Couldn't open ROM file";
|
||||
default: return "Unknown ROM info load error";
|
||||
}
|
||||
}
|
||||
|
||||
static char *format_rom_media_type (rom_media_type_t media_type) {
|
||||
static const char *format_rom_endianness (endianness_t endianness) {
|
||||
switch (endianness) {
|
||||
case ENDIANNESS_BIG: return "Big (default)";
|
||||
case ENDIANNESS_LITTLE: return "Little (unsupported)";
|
||||
case ENDIANNESS_BYTE_SWAP: return "Byte swapped";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *format_rom_media_type (category_type_t media_type) {
|
||||
switch (media_type) {
|
||||
case N64_CART:
|
||||
return "Cartridge";
|
||||
case N64_DISK:
|
||||
return "Disk";
|
||||
case N64_CART_EXPANDABLE:
|
||||
return "Cartridge (Expandable)";
|
||||
case N64_DISK_EXPANDABLE:
|
||||
return "Disk (Expandable)";
|
||||
case N64_ALECK64:
|
||||
return "Aleck64";
|
||||
default:
|
||||
return "Unknown";
|
||||
case N64_CART: return "Cartridge";
|
||||
case N64_DISK: return "Disk";
|
||||
case N64_CART_EXPANDABLE: return "Cartridge (Expandable)";
|
||||
case N64_DISK_EXPANDABLE: return "Disk (Expandable)";
|
||||
case N64_ALECK64: return "Aleck64";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static char *format_rom_destination_market (rom_destination_market_t market_type) {
|
||||
static const char *format_rom_destination_market (destination_type_t market_type) {
|
||||
// TODO: These are all assumptions and should be corrected if required.
|
||||
// From http://n64devkit.square7.ch/info/submission/pal/01-01.html
|
||||
switch (market_type) {
|
||||
case MARKET_JAPANESE_MULTI:
|
||||
return "Japanese & English"; // 1080 Snowboarding JPN
|
||||
case MARKET_BRAZILIAN:
|
||||
return "Brazilian (Portuguese)";
|
||||
case MARKET_CHINESE:
|
||||
return "Chinese";
|
||||
case MARKET_GERMAN:
|
||||
return "German";
|
||||
case MARKET_NORTH_AMERICA:
|
||||
return "American English";
|
||||
case MARKET_FRENCH:
|
||||
return "French";
|
||||
case MARKET_DUTCH:
|
||||
return "Dutch";
|
||||
case MARKET_ITALIAN:
|
||||
return "Italian";
|
||||
case MARKET_JAPANESE:
|
||||
return "Japanese";
|
||||
case MARKET_KOREAN:
|
||||
return "Korean";
|
||||
case MARKET_CANADIAN:
|
||||
return "Canadaian (English & French)";
|
||||
case MARKET_SPANISH:
|
||||
return "Spanish";
|
||||
case MARKET_AUSTRALIAN:
|
||||
return "Australian (English)";
|
||||
case MARKET_SCANDINAVIAN:
|
||||
return "Scandinavian";
|
||||
case MARKET_GATEWAY64_NTSC:
|
||||
return "LodgeNet/Gateway (NTSC)";
|
||||
case MARKET_GATEWAY64_PAL:
|
||||
return "LodgeNet/Gateway (PAL)";
|
||||
case MARKET_EUROPEAN_BASIC:
|
||||
return "PAL (includes English)"; // Mostly EU but is used on some Australian ROMs
|
||||
case MARKET_OTHER_X: // FIXME: AUS HSV Racing ROM's and Asia Top Gear Rally use this so not only EUR
|
||||
return "Regional (non specific)";
|
||||
case MARKET_OTHER_Y:
|
||||
return "European (non specific)";
|
||||
case MARKET_OTHER_Z:
|
||||
return "Regional (unknown)";
|
||||
default:
|
||||
return "Unknown";
|
||||
case MARKET_JAPANESE_MULTI: return "Japanese & English"; // 1080 Snowboarding JPN
|
||||
case MARKET_BRAZILIAN: return "Brazilian (Portuguese)";
|
||||
case MARKET_CHINESE: return "Chinese";
|
||||
case MARKET_GERMAN: return "German";
|
||||
case MARKET_NORTH_AMERICA: return "American English";
|
||||
case MARKET_FRENCH: return "French";
|
||||
case MARKET_DUTCH: return "Dutch";
|
||||
case MARKET_ITALIAN: return "Italian";
|
||||
case MARKET_JAPANESE: return "Japanese";
|
||||
case MARKET_KOREAN: return "Korean";
|
||||
case MARKET_CANADIAN: return "Canadaian (English & French)";
|
||||
case MARKET_SPANISH: return "Spanish";
|
||||
case MARKET_AUSTRALIAN: return "Australian (English)";
|
||||
case MARKET_SCANDINAVIAN: return "Scandinavian";
|
||||
case MARKET_GATEWAY64_NTSC: return "LodgeNet/Gateway (NTSC)";
|
||||
case MARKET_GATEWAY64_PAL: return "LodgeNet/Gateway (PAL)";
|
||||
case MARKET_EUROPEAN_BASIC: return "PAL (includes English)"; // Mostly EU but is used on some Australian ROMs
|
||||
case MARKET_OTHER_X: return "Regional (non specific)"; // FIXME: AUS HSV Racing ROM's and Asia Top Gear Rally use this so not only EUR
|
||||
case MARKET_OTHER_Y: return "European (non specific)";
|
||||
case MARKET_OTHER_Z: return "Regional (unknown)";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static char *format_rom_save_type (db_savetype_t save_type) {
|
||||
static const char *format_rom_save_type (save_type_t save_type) {
|
||||
switch (save_type) {
|
||||
case DB_SAVE_TYPE_NONE:
|
||||
return "None";
|
||||
case DB_SAVE_TYPE_EEPROM_4K:
|
||||
return "EEPROM 4K";
|
||||
case DB_SAVE_TYPE_EEPROM_16K:
|
||||
return "EEPROM 16K";
|
||||
case DB_SAVE_TYPE_SRAM:
|
||||
return "SRAM";
|
||||
case DB_SAVE_TYPE_SRAM_BANKED:
|
||||
return "SRAM Banked";
|
||||
case DB_SAVE_TYPE_SRAM_128K:
|
||||
return "SRAM 128K";
|
||||
case DB_SAVE_TYPE_FLASHRAM:
|
||||
return "FlashRAM";
|
||||
case DB_SAVE_TYPE_CPAK:
|
||||
return "Controller Pak";
|
||||
default:
|
||||
return "Unknown";
|
||||
case SAVE_TYPE_NONE: return "None";
|
||||
case SAVE_TYPE_EEPROM_4K: return "EEPROM 4K";
|
||||
case SAVE_TYPE_EEPROM_16K: return "EEPROM 16K";
|
||||
case SAVE_TYPE_SRAM: return "SRAM";
|
||||
case SAVE_TYPE_SRAM_BANKED: return "SRAM Banked";
|
||||
case SAVE_TYPE_SRAM_128K: return "SRAM 128K";
|
||||
case SAVE_TYPE_FLASHRAM: return "FlashRAM";
|
||||
case SAVE_TYPE_FLASHRAM_PKST2: return "FlashRAM (Pokemon Stadium 2)";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static char *format_rom_expansion_pak_info (rom_memorytype_t expansion_pak_info) {
|
||||
static char *format_rom_expansion_pak_info (expansion_pak_t expansion_pak_info) {
|
||||
switch (expansion_pak_info) {
|
||||
case DB_MEMORY_EXPANSION_REQUIRED:
|
||||
return "Required";
|
||||
case DB_MEMORY_EXPANSION_RECOMMENDED:
|
||||
return "Recommended";
|
||||
case DB_MEMORY_EXPANSION_SUGGESTED:
|
||||
return "Suggested";
|
||||
case DB_MEMORY_EXPANSION_FAULTY:
|
||||
return "May require ROM patch";
|
||||
default:
|
||||
return "Not required";
|
||||
case EXPANSION_PAK_REQUIRED: return "Required";
|
||||
case EXPANSION_PAK_RECOMMENDED: return "Recommended";
|
||||
case EXPANSION_PAK_SUGGESTED: return "Suggested";
|
||||
case EXPANSION_PAK_FAULTY: return "May require ROM patch";
|
||||
default: return "Not required";
|
||||
}
|
||||
}
|
||||
|
||||
static float format_rom_clockrate (uint32_t clockrate) {
|
||||
/* Generally ROMs have a clock rate of 0x0000000F which signifies they used the default 62.5MHz clock. */
|
||||
if (clockrate == 0x0F) {
|
||||
return 62.5;
|
||||
static const char *format_cic_type (cic_type_t cic_type) {
|
||||
switch (cic_type) {
|
||||
case CIC_5101: return "5101";
|
||||
case CIC_5167: return "5167";
|
||||
case CIC_6101: return "6101";
|
||||
case CIC_7102: return "7102";
|
||||
case CIC_6102_7101: return "6102 / 7101";
|
||||
case CIC_x103: return "6103 / 7103";
|
||||
case CIC_x105: return "6105 / 7105";
|
||||
case CIC_x106: return "6106 / 7106";
|
||||
case CIC_8301: return "8301";
|
||||
case CIC_8302: return "8302";
|
||||
case CIC_8303: return "8303";
|
||||
case CIC_8401: return "8401";
|
||||
case CIC_8501: return "8501";
|
||||
default: return "Unknown";
|
||||
}
|
||||
|
||||
/* If it did not, we need to show the different value. */
|
||||
return (float) clockrate / 1000000;
|
||||
}
|
||||
|
||||
|
||||
@ -170,32 +140,34 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
" Endian: %s\n"
|
||||
" Endianness: %s\n"
|
||||
" Title: %.20s\n"
|
||||
" Media type: %c - %s\n"
|
||||
" Unique ID: %.2s\n"
|
||||
" Destination market: %c - %s\n"
|
||||
" Game code: %c%c%c%c\n"
|
||||
" Media type: %s\n"
|
||||
" Destination market: %s\n"
|
||||
" Version: %hhu\n"
|
||||
" Checksum: 0x%016llX\n"
|
||||
" Check code: 0x%016llX\n"
|
||||
" Save type: %s\n"
|
||||
" Expansion PAK: %s\n"
|
||||
"\n"
|
||||
" Extra information:\n"
|
||||
" CIC: %s\n"
|
||||
" Boot address: 0x%08lX\n"
|
||||
" SDK version: %.1f%c\n"
|
||||
" Clock Rate: %.2fMHz\n",
|
||||
format_rom_endian(rom_header.config_flags),
|
||||
rom_header.title,
|
||||
rom_header.metadata.media_type, format_rom_media_type(rom_header.metadata.media_type),
|
||||
(char *) (&rom_header.metadata.unique_identifier),
|
||||
rom_header.metadata.destination_market, format_rom_destination_market(rom_header.metadata.destination_market),
|
||||
rom_header.metadata.version,
|
||||
rom_header.checksum,
|
||||
format_rom_save_type(rom_db_match_save_type(rom_header)),
|
||||
format_rom_expansion_pak_info(rom_db_match_expansion_pak(rom_header)),
|
||||
rom_header.boot_address,
|
||||
(float) ((rom_header.sdk_version >> 8) & 0xFF) / 10.0f, (char) (rom_header.sdk_version & 0xFF),
|
||||
format_rom_clockrate(rom_header.clock_rate)
|
||||
format_rom_endianness(menu->load.rom_info.endianness),
|
||||
menu->load.rom_info.title,
|
||||
menu->load.rom_info.game_code[0], menu->load.rom_info.game_code[1], menu->load.rom_info.game_code[2], menu->load.rom_info.game_code[3],
|
||||
format_rom_media_type(menu->load.rom_info.category_code),
|
||||
format_rom_destination_market(menu->load.rom_info.destination_code),
|
||||
menu->load.rom_info.version,
|
||||
menu->load.rom_info.check_code,
|
||||
format_rom_save_type(menu->load.rom_info.save_type),
|
||||
format_rom_expansion_pak_info(menu->load.rom_info.features.expansion_pak),
|
||||
format_cic_type(menu->load.rom_info.cic_type),
|
||||
menu->load.rom_info.boot_address,
|
||||
(menu->load.rom_info.libultra.version / 10.0f), menu->load.rom_info.libultra.revision,
|
||||
menu->load.rom_info.clock_rate
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
@ -211,7 +183,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
}
|
||||
|
||||
static void draw_progress (float progress) {
|
||||
surface_t *d = display_try_get();
|
||||
surface_t *d = (progress >= 1.0f) ? display_get() : display_try_get();
|
||||
|
||||
if (d) {
|
||||
rdpq_attach(d, NULL);
|
||||
@ -225,7 +197,7 @@ static void draw_progress (float progress) {
|
||||
}
|
||||
|
||||
static void load (menu_t *menu) {
|
||||
cart_load_err_t err = cart_load_n64_rom_and_save(menu, &rom_header, draw_progress);
|
||||
cart_load_err_t err = cart_load_n64_rom_and_save(menu, draw_progress);
|
||||
|
||||
if (err != CART_LOAD_OK) {
|
||||
menu_show_error(menu, cart_load_convert_error_message(err));
|
||||
@ -244,15 +216,21 @@ static void deinit (void) {
|
||||
|
||||
|
||||
void view_load_rom_init (menu_t *menu) {
|
||||
if (menu->load.rom_path) {
|
||||
path_free(menu->load.rom_path);
|
||||
menu->load.rom_path = NULL;
|
||||
}
|
||||
|
||||
load_pending = false;
|
||||
|
||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
|
||||
rom_header = file_read_rom_header(path_get(path));
|
||||
rom_err_t err = rom_info_load(path_get(menu->load.rom_path), &menu->load.rom_info);
|
||||
if (err != ROM_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
}
|
||||
|
||||
boxart = component_boxart_init(rom_header.metadata.media_type, rom_header.metadata.unique_identifier);
|
||||
|
||||
path_free(path);
|
||||
boxart = component_boxart_init(menu->load.rom_info.game_code);
|
||||
}
|
||||
|
||||
void view_load_rom_display (menu_t *menu, surface_t *display) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
|
||||
#define SEEK_SECONDS (5)
|
||||
#define SEEK_SECONDS_FAST (60)
|
||||
|
||||
|
||||
static char *convert_error_message (mp3player_err_t err) {
|
||||
@ -47,8 +48,8 @@ static void process (menu_t *menu) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
}
|
||||
} else if (menu->actions.go_left || menu->actions.go_right) {
|
||||
int seconds = (menu->actions.go_left ? -SEEK_SECONDS : SEEK_SECONDS);
|
||||
err = mp3player_seek(seconds);
|
||||
int seconds = menu->actions.go_fast ? SEEK_SECONDS_FAST : SEEK_SECONDS;
|
||||
err = mp3player_seek(menu->actions.go_left ? (-seconds) : seconds);
|
||||
if (err != MP3PLAYER_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
}
|
||||
|
@ -2,10 +2,6 @@
|
||||
#include "views.h"
|
||||
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
|
||||
static void draw (menu_t *menu, surface_t *d) {
|
||||
rdpq_attach_clear(d, NULL);
|
||||
rdpq_detach_show();
|
||||
@ -13,11 +9,9 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
|
||||
void view_startup_init (menu_t *menu) {
|
||||
// Nothing to initialize (yet)
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
|
||||
void view_startup_display (menu_t *menu, surface_t *display) {
|
||||
process(menu);
|
||||
|
||||
draw(menu, display);
|
||||
}
|
||||
|
@ -3,21 +3,23 @@
|
||||
#include "views.h"
|
||||
|
||||
|
||||
static int controllers;
|
||||
static int joypad[4];
|
||||
static int accessory[4];
|
||||
|
||||
|
||||
static char *format_accessory (int controller) {
|
||||
switch (accessory[controller]) {
|
||||
case ACCESSORY_RUMBLEPAK:
|
||||
static char *format_accessory (int joypad) {
|
||||
switch (accessory[joypad]) {
|
||||
case JOYPAD_ACCESSORY_TYPE_RUMBLE_PAK:
|
||||
return "[Rumble Pak is inserted]";
|
||||
case ACCESSORY_MEMPAK:
|
||||
case JOYPAD_ACCESSORY_TYPE_CONTROLLER_PAK:
|
||||
return "[Controller Pak is inserted]";
|
||||
case ACCESSORY_VRU:
|
||||
return "[VRU is inserted]";
|
||||
case ACCESSORY_TRANSFERPAK:
|
||||
case JOYPAD_ACCESSORY_TYPE_TRANSFER_PAK:
|
||||
return "[Transfer Pak is inserted]";
|
||||
case ACCESSORY_NONE:
|
||||
case JOYPAD_ACCESSORY_TYPE_BIO_SENSOR:
|
||||
return "[BIO Sensor is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_SNAP_STATION:
|
||||
return "[Snap Station is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_NONE:
|
||||
return "";
|
||||
default:
|
||||
return "[unknown accessory inserted]";
|
||||
@ -43,8 +45,6 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"N64 SYSTEM INFORMATION"
|
||||
);
|
||||
|
||||
time_t current_time = time(NULL);
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
@ -57,12 +57,12 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"JoyPad 2 is %sconnected %s\n"
|
||||
"JoyPad 3 is %sconnected %s\n"
|
||||
"JoyPad 4 is %sconnected %s\n",
|
||||
current_time >= 0 ? ctime(¤t_time) : "Unknown\n",
|
||||
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n",
|
||||
is_memory_expanded() ? "" : "not ",
|
||||
(controllers & CONTROLLER_1_INSERTED) ? "" : "not ", format_accessory(0),
|
||||
(controllers & CONTROLLER_2_INSERTED) ? "" : "not ", format_accessory(1),
|
||||
(controllers & CONTROLLER_3_INSERTED) ? "" : "not ", format_accessory(2),
|
||||
(controllers & CONTROLLER_4_INSERTED) ? "" : "not ", format_accessory(3)
|
||||
(joypad[0]) ? "" : "not ", format_accessory(0),
|
||||
(joypad[1]) ? "" : "not ", format_accessory(1),
|
||||
(joypad[2]) ? "" : "not ", format_accessory(2),
|
||||
(joypad[3]) ? "" : "not ", format_accessory(3)
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
@ -76,10 +76,9 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
|
||||
void view_system_info_init (menu_t *menu) {
|
||||
controllers = get_controllers_present();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
accessory[i] = identify_accessory(i);
|
||||
JOYPAD_PORT_FOREACH (port) {
|
||||
joypad[port] = (joypad_get_style(port) != JOYPAD_STYLE_NONE);
|
||||
accessory[port] = joypad_get_accessory_type(port);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,9 @@ void view_credits_display (menu_t *menu, surface_t *display);
|
||||
void view_load_rom_init (menu_t *menu);
|
||||
void view_load_rom_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_load_disk_init (menu_t *menu);
|
||||
void view_load_disk_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_load_emulator_init (menu_t *menu);
|
||||
void view_load_emulator_display (menu_t *menu, surface_t *display);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user