Merge branch 'develop' into add-rom-patcher

This commit is contained in:
Robin Jones 2024-08-06 20:11:07 +01:00 committed by GitHub
commit f505135c97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 1157 additions and 705 deletions

View File

@ -21,6 +21,7 @@
- [ ] Improvement (non-breaking change that adds a new feature)
- [ ] Bug fix (fixes an issue)
- [ ] Breaking change (breaking change)
- [ ] Documentation Improvement
- [ ] Config and build (change in the configuration and build system, has no impact on code or features)
## Checklist:

View File

@ -11,6 +11,9 @@ on:
jobs:
build-menu:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
@ -68,21 +71,31 @@ jobs:
name: SC64
path: ./output/sc64menu.n64
# - name: Delete rolling-release tag and release
# uses: dev-drprasad/delete-tag-and-release@v1.0
# if: github.ref == 'refs/heads/main'
# with:
# github_token: ${{ secrets.GITHUB_TOKEN }}
# tag_name: rolling-release
# continue-on-error: true
- name: Upload rolling release
uses: softprops/action-gh-release@v2
if: github.ref == 'refs/heads/main'
with:
name: Rolling release
body: Rolling release built from latest commit on `main` branch.
tag_name: rolling-release
tag_name: 'rolling_release'
make_latest: true
files: |
./output/N64FlashcartMenu.n64
./output/menu.bin
# ./output/OS64.v64
# ./output/OS64P.v64
./output/sc64menu.n64
continue-on-error: true
- name: Upload dev rolling release
uses: softprops/action-gh-release@v2
if: github.ref == 'refs/heads/develop'
with:
name: 'Rolling pre-release'
body: Experimental pre-release built from latest commit on `develop` branch.
target_commitish: develop
tag_name: 'rolling_pre-release'
prerelease: true
files: |
./output/N64FlashcartMenu.n64
./output/menu.bin

1
.gitignore vendored
View File

@ -7,6 +7,7 @@
# Ignore generated files in the libdragon FS
/filesystem/FiraMonoBold.font64
/filesystem/*.wav64
# Ignore external development tools
/tools/*

View File

@ -905,7 +905,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = README.md ./src
INPUT = README.md ./src ./docs
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

View File

@ -79,17 +79,27 @@ SRCS = \
FONTS = \
FiraMonoBold.ttf
SOUNDS = \
cursorsound.wav \
back.wav \
enter.wav \
error.wav \
settings.wav
OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o,$(basename $(SRCS))))
MINIZ_OBJS = $(filter $(BUILD_DIR)/libs/miniz/%.o,$(OBJS))
SPNG_OBJS = $(filter $(BUILD_DIR)/libs/libspng/%.o,$(OBJS))
DEPS = $(OBJS:.o=.d)
FILESYSTEM = \
$(addprefix $(FILESYSTEM_DIR)/, $(notdir $(FONTS:%.ttf=%.font64)))
$(addprefix $(FILESYSTEM_DIR)/, $(notdir $(FONTS:%.ttf=%.font64))) \
$(addprefix $(FILESYSTEM_DIR)/, $(notdir $(SOUNDS:%.wav=%.wav64)))
$(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second
$(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-1FF -r 2026-2026 --ellipsis 2026,1
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 80-1FF -r 2026-2026 --ellipsis 2026,1
$(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1
$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))
@ -97,6 +107,10 @@ $(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf
@echo " [FONT] $@"
@$(N64_MKFONT) $(MKFONT_FLAGS) -o $(FILESYSTEM_DIR) "$<"
$(FILESYSTEM_DIR)/%.wav64: $(ASSETS_DIR)/%.wav
@echo " [AUDIO] $@"
@$(N64_AUDIOCONV) $(AUDIOCONV_FLAGS) -o $(FILESYSTEM_DIR) "$<"
$(BUILD_DIR)/$(PROJECT_NAME).dfs: $(FILESYSTEM)
$(BUILD_DIR)/menu/views/credits.o: .FORCE

135
README.md
View File

@ -25,9 +25,15 @@ An open source menu for N64 flashcarts.
* Comprehensive ROM information display.
* Real Time Clock support.
* Music playback (MP3).
* Menu sound effects.
### Video showcase (as of Oct 12 2023)
## Documentation
* [Getting started guide](./docs/00_getting_started_sd.md)
* [Menu controls](./docs/01_menu_controls.md)
* [Developer guide](./docs/99_developer_guide.md)
## Video showcase (as of Oct 12 2023)
[![N64FlashcartMenu Showcase](http://img.youtube.com/vi/6CKImHTifDA/0.jpg)](http://www.youtube.com/watch?v=6CKImHTifDA "N64FlashcartMenu Showcase (Oct 12 2023)")
@ -40,123 +46,60 @@ An open source menu for N64 flashcarts.
* Support as many common mods and features as possible.
## Getting started
Using your PC, insert the SD card and ensure it is formatted for compatibility (We recommend FAT32 in most instances).
### Save files
By default, all save files (whether `FlashRam`, `SRAM` or `EEPROM`) use the `.sav` extension and match the filename of the ROM.
Each save file can be found in the `/saves` folder located in the same directory as the ROM and shares the same file name, apart from the extension.
If transfering a file from a different flashcart such as the ED64, it will be necessary to change the extension of the file to `sav`.
i.e. for `Glover (USA).eep` you would need to change the extension to `Glover (USA).sav`
**NOTE:** certain emulator saves or saves created for a different ROM version or region may be incompatible.
## Experimental features
These features are subject to change:
### ROM Boxart
To use boxart, you need to place png files of size 158x112 in the folder `/menu/boxart` on the SD card.
To use boxart, place PNG files in the `/menu/boxart` folder on the SD card with the following dimensions:
* Standard covers: 158x112
* 64DD covers: 129x112
* Japanese covers: 112x158
Each file must be named according to the 2 letter ROM ID, or 3 letter ROM ID including media type.
i.e. for GoldenEye 2 letters, this would be `GE.png`.
i.e. for GoldenEye 3 letters, this would be `NGE.png`.
A known set of PNG files using 2 letter ID's can be downloaded [here](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w).
You can download these boxart packs:
[American Boxart](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w)
### Emulator support
Emulators should be added to the `/menu/emulators` directory on the SD card.
[European Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M)
[64DD Boxart](https://mega.nz/file/O3JzwD7B#BYl1aV-pbrJ-MxWUbM_K0yGVIRbmSoxJJZqQInRzZyM)
Menu currently supports the following emulators and associated ROM file names:
- **NES**: [neon64v2](https://github.com/hcs64/neon64v2) by *hcs64* - `neon64bu.rom`
- **SNES**: [sodium64](https://github.com/Hydr8gon/sodium64) by *Hydr8gon* - `sodium64.z64`
- **Game Boy** / **GB Color**: [gb64](https://lambertjamesd.github.io/gb64/romwrapper/romwrapper.html) by *lambertjamesd* - `gb.v64` / `gbc.v64`
- **Sega Master System** / **Sega Game Gear** / **Sg1000**: [TotalSMS](https://github.com/ITotalJustice/TotalSMS) - `TotalSMS.z64` (Currently broken)
### Menu Settings
The Menu creates a `config.ini` file in `sd:/menu/` which contains various settings that are used by the menu.
Currently these are read-only (can be viewed in the menu by pressing `L` on the Joypad).
If required, you can manually adjust the file on the SD card using your computer.
### SC64 Specific
- 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 place required 64DD IPL dumps in the `/menu/64ddipl` folder on the SD card.
For more details follow [this guide on the 64dd.org website](https://64dd.org/tutorial_sc64.html).
## Flashcart specific
Note: to load an expansion disk (e.g. F-Zero X) browse to the N64 ROM and load it (but not start it) and then browse to the DD expansion file and press the `R` button.
### SC64
* Ensure the cart has the latest [firmware](https://github.com/Polprzewodnikowy/SummerCart64/releases/latest) installed.
* Download the latest `sc64menu.n64` file from the [releases](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases/) page, then put it in the root directory of your SD card.
### 64drive Specific
- Ensure the cart has the latest [firmware](https://64drive.retroactive.be/support.php) installed.
- Download the latest `menu.bin` file from the releases page, then put it in the root directory of your SD card.
### 64drive
* Ensure the cart has the latest [firmware](https://64drive.retroactive.be/support.php) installed.
* Download the latest `menu.bin` file from the [releases](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases/) page, then put it in the root directory of your SD card.
### ED64 & ED64P Specific
### ED64 & ED64P
Currently not supported, but work is in progress (See [PR's](https://github.com/Polprzewodnikowy/N64FlashcartMenu/pulls)).
The aim is to replace [Altra64](https://github.com/networkfusion/altra64) and [ED64-UnofficialOS](https://github.com/n64-tools/ED64-UnofficialOS-binaries).
# Developer documentation
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.18.0/sc64-deployer-windows-v2.18.0.zip)
* Extract and place `sc64deployer.exe` in the `tools/sc64` directory.
Make sure that your firmware is compatible (currently v2.18.0+)
See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.18.0/docs/00_quick_startup_guide.md#firmware-backupupdate)
#### From the devcontainer
It is not currently possible to directly communicate with USB devices.
BUT, as a work around you can use a proxy TCP/IP connection
Set up a proxy: open a terminal window, `cd ./tools/sc64` and then `./sc64deployer.exe server`
Then in the dev container, use `make run` or `make run-debug`
#### From your host (Windows) OS
* 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).
TODO: it does not yet work with `F5`: see https://devblogs.microsoft.com/cppblog/now-announcing-makefile-support-in-visual-studio-code/
WORKAROUND: in the dev container terminal, use make directly, i.e.: `make`
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`.
### Ares Emulator
For ease of development and debugging, the menu ROM is able to run in the Ares emulator (without most flashcart features).
* Ensure you have the Ares emulator on you computer.
* Load the `N64FlashcartMenu.n64` ROM.
### Others
* Add the required file to the correct folder on your SD card.
# Update Libdragon submodule
This repo currently uses the `preview` branch as a submodule at a specific commit.
To update to the latest version, use `git submodule update --remote ` from the terminal.
# Generate documentation
Run `doxygen` from the dev container terminal.
Make sure you fix the warnings before creating a PR!
Generated documentation is located in `output/docs` folder and auto published to the `gh-pages` branch when merged with `main`.
Once merged, they can be viewed [here](https://polprzewodnikowy.github.io/N64FlashcartMenu/)
# Open source software and licenses used
- [libdragon](https://github.com/DragonMinded/libdragon) (UNLICENSE License)
- [libspng](https://github.com/randy408/libspng) (BSD 2-Clause License)
- [mini.c](https://github.com/univrsal/mini.c) (BSD 2-Clause License)
- [minimp3](https://github.com/lieff/minimp3) (CC0 1.0 Universal)
- [miniz](https://github.com/richgel999/miniz) (MIT License)
* [libdragon](https://github.com/DragonMinded/libdragon) (UNLICENSE License)
* [libspng](https://github.com/randy408/libspng) (BSD 2-Clause License)
* [mini.c](https://github.com/univrsal/mini.c) (BSD 2-Clause License)
* [minimp3](https://github.com/lieff/minimp3) (CC0 1.0 Universal)
* [miniz](https://github.com/richgel999/miniz) (MIT License)
## Sounds
See [License](https://pixabay.com/en/service/license-summary/) for the following sounds:
* [Cursor sound](https://pixabay.com/en/sound-effects/click-buttons-ui-menu-sounds-effects-button-7-203601/) by Skyscraper_seven (Free to use)
* [Actions (Enter, back) sound](https://pixabay.com/en/sound-effects/menu-button-user-interface-pack-190041/) by Liecio (Free to use)
* [Error sound](https://pixabay.com/en/sound-effects/error-call-to-attention-129258/) by Universfield (Free to use)

BIN
assets/back.wav Normal file

Binary file not shown.

BIN
assets/cursorsound.wav Normal file

Binary file not shown.

BIN
assets/enter.wav Normal file

Binary file not shown.

BIN
assets/error.wav Normal file

Binary file not shown.

BIN
assets/settings.wav Normal file

Binary file not shown.

View File

@ -0,0 +1,82 @@
## First time setup of SD card
Using your PC, insert the SD card and ensure it is formatted for compatibility with your flashcart (*FAT32 and EXFAT are fully supported on the SC64*).
- Download the latest `sc64menu.n64` (assuming you are using an *sc64*) file from the [releases](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases/) page, then put it in the root directory of your SD card.
- Create a folder in the root of your SD card called `menu`.
- Place your ROMs on the SD Card, in any folder (**except for `menu`**).
### Emulator support
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/releases) by *hcs64* - `neon64bu.rom`
- **SNES**: [sodium64](https://github.com/Hydr8gon/sodium64/releases) by *Hydr8gon* - `sodium64.z64`
- **Game Boy** / **GB Color**: [gb64](https://lambertjamesd.github.io/gb64/romwrapper/romwrapper.html) by *lambertjamesd* - `gb.v64` / `gbc.v64` ("Download Emulator" button)
### 64DD disk support
For the ability to load and run 64DD disk images, you need to place the required 64DD IPL dumps in the `/menu/64ddipl` folder on the SD card.
For more details, follow [this guide on the 64dd.org website](https://64dd.org/tutorial_sc64.html).
#### So what would the layout of the SD Card look like?
```plaintext
SD:\
├── sc64menu.n64
├── menu\
│ │
│ │
│ ├── 64ddipl\
│ │ ├── NDDE0.n64
│ │ ├── NDDJ2.n64
│ │ └── NDXJ0.n64
│ │
│ └── emulators
│ ├── neon64bu.rom
│ ├── sodium64.z64
│ ├── gb.v64
│ └── gbc.v64
├── (a rom).z64
├── (a rom).n64
├── (some folder with roms)\
│ └── (some folder with roms)\
| └── (Some supported ROM files)
├── (Some supported ROM files)
|
└── (Some folder with 64DD disk images)\
└── (Some 64DD disk images)
```
## Save Files
All save files (whether `FlashRam`, `SRAM` or `EEPROM`) use the `.sav` extension and match the filename of a ROM.
Each save file can be found in the `/saves` folder located in the **same directory** as the ROM and shares the same file name, apart from the extension.
These files are created and modified when a "game" saves.
```plaintext
├── (some folder with roms)\
├── a_rom.z64
├── b_rom.n64
└── saves\
├── a_rom.sav
└── b_rom.sav
```
### Transfering saves from an ED64
If transferring a file from a different flashcart, such as the ED64, it will be necessary to change the extension of the file to `sav`.
i.e. for `Glover (USA).eep` you would need to change the extension to `Glover (USA).sav`
You may also need to pad/trim the files to their original size:
- For EEPROM 4Kbit games, remove the padding.
- For others, use a tool such as Ninjiteu's N64 Save converter.
**NOTE:** certain emulator saves or saves created for a different ROM version or region may be incompatible.

14
docs/01_menu_controls.md Normal file
View File

@ -0,0 +1,14 @@
## Menu Controls
### Fast scroll
Use the C-Up and C-Down buttons
### DD ROMs
#### Expansion Disks
To load an expansion disk (e.g. F-Zero X) browse to the N64 ROM and load it (but not start it) and then browse to the DD expansion file and press the `R` button.
#### Disk swapping
This feature is not currently available in the menu.

View File

@ -0,0 +1,69 @@
## Developer documentation
You can use a dev container in VSCode to ease development.
### A quickstart video tutorial on how to set up your environment
[![Devcontainer quickstart guide](http://img.youtube.com/vi/h05ufOsRgZU/0.jpg)](http://www.youtube.com/watch?v=h05ufOsRgZU "Devcontainer quickstart guide").
### To deploy:
#### SC64
* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.18.0/sc64-deployer-windows-v2.18.0.zip)
* Extract and place `sc64deployer.exe` in the `tools/sc64` directory.
Make sure that your firmware is compatible (currently v2.18.0+)
See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.18.0/docs/00_quick_startup_guide.md#firmware-backupupdate)
##### From the devcontainer
It is not currently possible to directly communicate with USB devices.
BUT, as a workaround you can use a proxy TCP/IP connection
Set up a proxy: open a terminal window, `cd ./tools/sc64` and then `./sc64deployer.exe server`
Then in the dev container, use `make run` or `make run-debug`
##### From your host (Windows) OS
* 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).
NOTE: it does not yet work with `F5`: see [this blog post](https://devblogs.microsoft.com/cppblog/now-announcing-makefile-support-in-visual-studio-code/)
WORKAROUND: in the dev container terminal, use make directly, i.e.: `make`
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`.
#### Ares Emulator
For ease of development and debugging, the menu ROM can run in the [Ares emulator](https://ares-emu.net/) (without most flashcart features).
* Ensure you have the Ares emulator on your computer.
* Load the `N64FlashcartMenu.n64` ROM.
#### Others
* Add the required file to the correct folder on your SD card.
## Update Libdragon submodule
This repo currently uses the `preview` branch as a submodule at a specific commit.
To update to the latest version, use `git submodule update --remote` from the terminal.
## Generate documentation
Run `doxygen` from the dev container terminal.
Make sure you fix the warnings before creating a PR!
Generated documentation is located in the `output/docs` folder and auto-published to the `gh-pages` branch when merged with `main`.
Once merged, they can be viewed [here](https://polprzewodnikowy.github.io/N64FlashcartMenu/)
### Test generated docs in the dev-container
Install Prerequisites:
```bash
apt-get install ruby-full build-essential zlib1g-dev
gem install jekyll bundler
```
You can then serve the webpage:
```bash
cd output/docs && jekyll serve
```

@ -1 +1 @@
Subproject commit af650428e9615f4e08d8e7eae187929a90c15ccc
Subproject commit 9bae49994bf1c796f9939ea1aa7c133813b9053f

View File

@ -170,7 +170,7 @@ static flashcart_err_t d64_load_save (char *save_path) {
size_t save_size = f_size(&fil);
bool is_eeprom_save = (current_save_type == SAVE_TYPE_EEPROM_4K || current_save_type == SAVE_TYPE_EEPROM_16K);
bool is_eeprom_save = (current_save_type == SAVE_TYPE_EEPROM_4KBIT || current_save_type == SAVE_TYPE_EEPROM_16KBIT);
void *address = (void *) (SAVE_ADDRESS_DEV_B);
if (is_eeprom_save) {
@ -212,27 +212,27 @@ static flashcart_err_t d64_set_save_type (flashcart_save_type_t save_type) {
case FLASHCART_SAVE_TYPE_NONE:
type = SAVE_TYPE_NONE;
break;
case FLASHCART_SAVE_TYPE_EEPROM_4K:
type = SAVE_TYPE_EEPROM_4K;
case FLASHCART_SAVE_TYPE_EEPROM_4KBIT:
type = SAVE_TYPE_EEPROM_4KBIT;
break;
case FLASHCART_SAVE_TYPE_EEPROM_16K:
type = SAVE_TYPE_EEPROM_16K;
case FLASHCART_SAVE_TYPE_EEPROM_16KBIT:
type = SAVE_TYPE_EEPROM_16KBIT;
break;
case FLASHCART_SAVE_TYPE_SRAM:
type = SAVE_TYPE_SRAM;
case FLASHCART_SAVE_TYPE_SRAM_256KBIT:
type = SAVE_TYPE_SRAM_256KBIT;
break;
case FLASHCART_SAVE_TYPE_SRAM_BANKED:
type = SAVE_TYPE_SRAM_BANKED;
break;
case FLASHCART_SAVE_TYPE_SRAM_128K:
case FLASHCART_SAVE_TYPE_SRAM_1MBIT:
// NOTE: 64drive doesn't support 128 kiB SRAM save type, fallback to 32 kiB SRAM save type
type = SAVE_TYPE_SRAM;
type = SAVE_TYPE_SRAM_256KBIT;
break;
case FLASHCART_SAVE_TYPE_FLASHRAM:
type = SAVE_TYPE_FLASHRAM;
case FLASHCART_SAVE_TYPE_FLASHRAM_1MBIT:
type = SAVE_TYPE_FLASHRAM_1MBIT;
break;
case FLASHCART_SAVE_TYPE_FLASHRAM_PKST2:
type = (device_variant == DEVICE_VARIANT_A) ? SAVE_TYPE_FLASHRAM_PKST2 : SAVE_TYPE_FLASHRAM;
type = (device_variant == DEVICE_VARIANT_A) ? SAVE_TYPE_FLASHRAM_PKST2 : SAVE_TYPE_FLASHRAM_1MBIT;
break;
default:
return FLASHCART_ERR_ARGS;

View File

@ -79,10 +79,10 @@ typedef enum {
/** @brief Save Type Enumeration. */
typedef enum {
SAVE_TYPE_NONE,
SAVE_TYPE_EEPROM_4K,
SAVE_TYPE_EEPROM_16K,
SAVE_TYPE_SRAM,
SAVE_TYPE_FLASHRAM,
SAVE_TYPE_EEPROM_4KBIT,
SAVE_TYPE_EEPROM_16KBIT,
SAVE_TYPE_SRAM_256KBIT,
SAVE_TYPE_FLASHRAM_1MBIT,
SAVE_TYPE_SRAM_BANKED,
SAVE_TYPE_FLASHRAM_PKST2,
} d64_save_type_t;

View File

@ -34,12 +34,12 @@ typedef enum {
/** @brief Flashcart save type enumeration */
typedef enum {
FLASHCART_SAVE_TYPE_NONE,
FLASHCART_SAVE_TYPE_EEPROM_4K,
FLASHCART_SAVE_TYPE_EEPROM_16K,
FLASHCART_SAVE_TYPE_SRAM,
FLASHCART_SAVE_TYPE_EEPROM_4KBIT,
FLASHCART_SAVE_TYPE_EEPROM_16KBIT,
FLASHCART_SAVE_TYPE_SRAM_256KBIT,
FLASHCART_SAVE_TYPE_SRAM_BANKED,
FLASHCART_SAVE_TYPE_SRAM_128K,
FLASHCART_SAVE_TYPE_FLASHRAM,
FLASHCART_SAVE_TYPE_SRAM_1MBIT,
FLASHCART_SAVE_TYPE_FLASHRAM_1MBIT,
FLASHCART_SAVE_TYPE_FLASHRAM_PKST2,
__FLASHCART_SAVE_TYPE_END
} flashcart_save_type_t;

View File

@ -51,7 +51,7 @@ bool fatfs_get_file_sectors (char *path, uint32_t *address, address_type_t type,
uint32_t cluster = fil.clust;
if (cluster >= fs->n_fatent) {
if ((cluster < 2) || (cluster >= fs->n_fatent)) {
error = true;
break;
}

View File

@ -388,12 +388,12 @@ static flashcart_err_t sc64_load_save (char *save_path) {
sc64_save_type_t type = (sc64_save_type_t) (value);
switch (type) {
case SAVE_TYPE_EEPROM_4K:
case SAVE_TYPE_EEPROM_16K:
case SAVE_TYPE_EEPROM_4KBIT:
case SAVE_TYPE_EEPROM_16KBIT:
address = (void *) (EEPROM_ADDRESS);
break;
case SAVE_TYPE_SRAM:
case SAVE_TYPE_FLASHRAM:
case SAVE_TYPE_SRAM_256KBIT:
case SAVE_TYPE_FLASHRAM_1MBIT:
case SAVE_TYPE_SRAM_BANKED:
address = (void *) (SRAM_FLASHRAM_ADDRESS);
break;
@ -515,26 +515,26 @@ static flashcart_err_t sc64_set_save_type (flashcart_save_type_t save_type) {
case FLASHCART_SAVE_TYPE_NONE:
type = SAVE_TYPE_NONE;
break;
case FLASHCART_SAVE_TYPE_EEPROM_4K:
type = SAVE_TYPE_EEPROM_4K;
case FLASHCART_SAVE_TYPE_EEPROM_4KBIT:
type = SAVE_TYPE_EEPROM_4KBIT;
break;
case FLASHCART_SAVE_TYPE_EEPROM_16K:
type = SAVE_TYPE_EEPROM_16K;
case FLASHCART_SAVE_TYPE_EEPROM_16KBIT:
type = SAVE_TYPE_EEPROM_16KBIT;
break;
case FLASHCART_SAVE_TYPE_SRAM:
type = SAVE_TYPE_SRAM;
case FLASHCART_SAVE_TYPE_SRAM_256KBIT:
type = SAVE_TYPE_SRAM_256KBIT;
break;
case FLASHCART_SAVE_TYPE_SRAM_BANKED:
type = SAVE_TYPE_SRAM_BANKED;
break;
case FLASHCART_SAVE_TYPE_SRAM_128K:
type = SAVE_TYPE_SRAM_128K;
case FLASHCART_SAVE_TYPE_SRAM_1MBIT:
type = SAVE_TYPE_SRAM_1MBIT;
break;
case FLASHCART_SAVE_TYPE_FLASHRAM:
type = SAVE_TYPE_FLASHRAM;
case FLASHCART_SAVE_TYPE_FLASHRAM_1MBIT:
type = SAVE_TYPE_FLASHRAM_1MBIT;
break;
case FLASHCART_SAVE_TYPE_FLASHRAM_PKST2:
type = SAVE_TYPE_FLASHRAM;
type = SAVE_TYPE_FLASHRAM_1MBIT;
break;
default:
return FLASHCART_ERR_ARGS;

View File

@ -76,12 +76,12 @@ typedef enum {
/** @brief The SC64 Save Type Enumeration. */
typedef enum {
SAVE_TYPE_NONE,
SAVE_TYPE_EEPROM_4K,
SAVE_TYPE_EEPROM_16K,
SAVE_TYPE_SRAM,
SAVE_TYPE_FLASHRAM,
SAVE_TYPE_EEPROM_4KBIT,
SAVE_TYPE_EEPROM_16KBIT,
SAVE_TYPE_SRAM_256KBIT,
SAVE_TYPE_FLASHRAM_1MBIT,
SAVE_TYPE_SRAM_BANKED,
SAVE_TYPE_SRAM_128K,
SAVE_TYPE_SRAM_1MBIT,
} sc64_save_type_t;
typedef enum {

View File

@ -21,6 +21,7 @@ static void actions_clear (menu_t *menu) {
menu->actions.back = false;
menu->actions.options = false;
menu->actions.settings = false;
menu->actions.lz_context = false;
}
static void actions_update_direction (menu_t *menu) {
@ -91,6 +92,8 @@ static void actions_update_buttons (menu_t *menu) {
menu->actions.options = true;
} else if (pressed.start) {
menu->actions.settings = true;
} else if (pressed.l || pressed.z) {
menu->actions.lz_context = true;
}
}

View File

@ -35,12 +35,12 @@ static bool create_saves_subdirectory (path_t *path) {
static flashcart_save_type_t convert_save_type (rom_save_type_t save_type) {
switch (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_EEPROM_4KBIT: return FLASHCART_SAVE_TYPE_EEPROM_4KBIT;
case SAVE_TYPE_EEPROM_16KBIT: return FLASHCART_SAVE_TYPE_EEPROM_16KBIT;
case SAVE_TYPE_SRAM_256KBIT: return FLASHCART_SAVE_TYPE_SRAM_256KBIT;
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_SRAM_1MBIT: return FLASHCART_SAVE_TYPE_SRAM_1MBIT;
case SAVE_TYPE_FLASHRAM_1MBIT: return FLASHCART_SAVE_TYPE_FLASHRAM_1MBIT;
case SAVE_TYPE_FLASHRAM_PKST2: return FLASHCART_SAVE_TYPE_FLASHRAM_PKST2;
default: return FLASHCART_SAVE_TYPE_NONE;
}
@ -166,19 +166,19 @@ cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type,
break;
case CART_LOAD_EMU_TYPE_SNES:
path_push(path, "sodium64.z64");
save_type = FLASHCART_SAVE_TYPE_SRAM;
save_type = FLASHCART_SAVE_TYPE_SRAM_256KBIT;
break;
case CART_LOAD_EMU_TYPE_GAMEBOY:
path_push(path, "gb.v64");
save_type = FLASHCART_SAVE_TYPE_FLASHRAM;
save_type = FLASHCART_SAVE_TYPE_FLASHRAM_1MBIT;
break;
case CART_LOAD_EMU_TYPE_GAMEBOY_COLOR:
path_push(path, "gbc.v64");
save_type = FLASHCART_SAVE_TYPE_FLASHRAM;
save_type = FLASHCART_SAVE_TYPE_FLASHRAM_1MBIT;
break;
case CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT:
path_push(path, "TotalSMS.z64");
save_type = FLASHCART_SAVE_TYPE_SRAM;
save_type = FLASHCART_SAVE_TYPE_SRAM_256KBIT;
break;
}

View File

@ -31,7 +31,7 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam
sprintf(file_name, "%.3s.png", game_code);
path_push(path, file_name);
if (png_decoder_start(path_get(path), BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) == PNG_OK) {
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
path_free(path);
return b;
}
@ -40,7 +40,7 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam
// TODO: This is bad, we should only check for 3 letter codes
sprintf(file_name, "%.2s.png", game_code + 1);
path_push(path, file_name);
if (png_decoder_start(path_get(path), BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) == PNG_OK) {
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
path_free(path);
return b;
}
@ -65,15 +65,20 @@ void component_boxart_free (component_boxart_t *b) {
}
void component_boxart_draw (component_boxart_t *b) {
if (b && b->image && b->image->width == BOXART_WIDTH && b->image->height == BOXART_HEIGHT) {
int box_x = BOXART_X;
int box_y = BOXART_Y;
if (b && b->image && b->image->width <= BOXART_WIDTH_MAX && b->image->height <= BOXART_HEIGHT_MAX) {
rdpq_mode_push();
rdpq_set_mode_copy(false);
rdpq_tex_blit(
b->image,
BOXART_X,
BOXART_Y,
NULL
);
if (b->image->height == BOXART_HEIGHT_MAX) {
box_x = BOXART_X_JP;
box_y = BOXART_Y_JP;
} else if (b->image->width == BOXART_WIDTH_DD && b->image->height == BOXART_HEIGHT_DD) {
box_x = BOXART_X_DD;
box_y = BOXART_Y_DD;
}
rdpq_tex_blit(b->image, box_x, box_y, NULL);
rdpq_mode_pop();
} else {
component_box_draw(

View File

@ -118,7 +118,8 @@ void component_messagebox_draw (char *fmt, ...) {
.height = VISIBLE_AREA_HEIGHT,
.align = ALIGN_CENTER,
.valign = VALIGN_CENTER,
.wrap = WRAP_WORD
.wrap = WRAP_WORD,
.line_spacing = TEXT_LINE_SPACING_ADJUST,
}, FNT_DEFAULT, formatted, &paragraph_nbytes);
if (formatted != buffer) {
@ -151,10 +152,11 @@ void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *f
.align = align,
.valign = valign,
.wrap = WRAP_ELLIPSES,
.line_spacing = TEXT_LINE_SPACING_ADJUST,
},
FNT_DEFAULT,
VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL,
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL,
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL,
formatted,
nbytes
);
@ -180,10 +182,11 @@ void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign,
.align = align,
.valign = valign,
.wrap = WRAP_ELLIPSES,
.line_spacing = TEXT_LINE_SPACING_ADJUST,
},
FNT_DEFAULT,
VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL,
LAYOUT_ACTIONS_SEPARATOR_Y + BORDER_THICKNESS + TEXT_MARGIN_VERTICAL,
LAYOUT_ACTIONS_SEPARATOR_Y + BORDER_THICKNESS + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL,
formatted,
nbytes
);

View File

@ -65,16 +65,38 @@
#define MESSAGEBOX_MARGIN (32)
#define TEXT_MARGIN_HORIZONTAL (10)
#define TEXT_MARGIN_VERTICAL (7)
#define TEXT_MARGIN_VERTICAL (6)
#define TEXT_OFFSET_VERTICAL (1)
#define TEXT_LINE_SPACING_ADJUST (0)
/** @brief The boxart picture width. */
#define BOXART_WIDTH (158)
/** @brief The boxart picture height. */
#define BOXART_HEIGHT (112)
/** @brief The boxart picture width (64DD). */
#define BOXART_WIDTH_DD (129)
/** @brief The boxart picture height. */
#define BOXART_HEIGHT_DD (112)
/** @brief The boxart picture maximum width. */
#define BOXART_WIDTH_MAX (158)
/** @brief The boxart picture maximum height. */
#define BOXART_HEIGHT_MAX (158)
/** @brief The box art position on the X axis. */
#define BOXART_X (VISIBLE_AREA_X1 - BOXART_WIDTH - 24)
/** @brief The box art position on the Y axis. */
#define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24)
/** @brief The box art position on the X axis for japanese caratules.*/
#define BOXART_X_JP (VISIBLE_AREA_X1 - BOXART_WIDTH_MAX + 21)
/** @brief The box art position on the Y axis for japanese caratules. */
#define BOXART_Y_JP (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT_MAX - 24)
/** @brief The box art position on the X axis for 64DD caratules.*/
#define BOXART_X_DD (VISIBLE_AREA_X1 - BOXART_WIDTH_DD - 23)
/** @brief The box art position on the Y axis for 64DD caratules. */
#define BOXART_Y_DD (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT_DD - 24)
/** @brief The scroll bar width. */
#define LIST_SCROLLBAR_WIDTH (12)
@ -86,7 +108,7 @@
#define LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0)
/** @brief The maximum amount of file list entries. */
#define LIST_ENTRIES (20)
#define LIST_ENTRIES (19)
/** @brief The maximum width available for a file list entry. */
#define FILE_LIST_MAX_WIDTH (480)
#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - LIST_SCROLLBAR_WIDTH)

View File

@ -1,5 +1,6 @@
#include "../components.h"
#include "../fonts.h"
#include "../sound.h"
#include "constants.h"
@ -41,6 +42,7 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm)
} else {
cm->hide_pending = true;
}
sound_play_effect(SFX_EXIT);
} else if (menu->actions.enter) {
if (cm->list[cm->selected].submenu) {
cm->submenu = cm->list[cm->selected].submenu;
@ -51,16 +53,19 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm)
cm->list[cm->selected].action(menu, cm->list[cm->selected].arg);
top->hide_pending = true;
}
sound_play_effect(SFX_ENTER);
} else if (menu->actions.go_up) {
cm->selected -= 1;
if (cm->selected < 0) {
cm->selected = 0;
}
sound_play_effect(SFX_CURSOR);
} else if (menu->actions.go_down) {
cm->selected += 1;
if (cm->selected >= cm->count) {
cm->selected = (cm->count - 1);
}
sound_play_effect(SFX_CURSOR);
}
return true;
@ -81,6 +86,7 @@ void component_context_menu_draw (component_context_menu_t *cm) {
.height = VISIBLE_AREA_HEIGHT,
.align = ALIGN_CENTER,
.valign = VALIGN_CENTER,
.line_spacing = TEXT_LINE_SPACING_ADJUST,
},
FNT_DEFAULT,
NULL

View File

@ -71,6 +71,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
.width = FILE_LIST_MAX_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
.height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2),
.wrap = WRAP_ELLIPSES,
.line_spacing = TEXT_LINE_SPACING_ADJUST,
},
FNT_DEFAULT,
file_list_layout
@ -114,7 +115,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
layout = rdpq_paragraph_builder_end();
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);
int highlight_y = VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL + ((selected - starting_position) * highlight_height);
component_box_draw(
FILE_LIST_HIGHLIGHT_X,
@ -127,7 +128,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
rdpq_paragraph_render(
layout,
VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL,
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL
);
rdpq_paragraph_free(layout);
@ -138,6 +139,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
.height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2),
.align = ALIGN_RIGHT,
.wrap = WRAP_ELLIPSES,
.line_spacing = TEXT_LINE_SPACING_ADJUST,
},
FNT_DEFAULT,
NULL
@ -164,7 +166,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
rdpq_paragraph_render(
layout,
VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL,
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL
);
rdpq_paragraph_free(layout);

View File

@ -67,6 +67,10 @@ static void menu_init (boot_params_t *boot_params) {
sound_init_default();
JOYPAD_PORT_FOREACH (port) {
joypad_set_rumble_active(port, false);
}
menu = calloc(1, sizeof(menu_t));
assert(menu != NULL);
@ -115,6 +119,11 @@ static void menu_init (boot_params_t *boot_params) {
__boot_tvtype = TV_NTSC;
}
sound_init_sfx();
if (menu->settings.sound_enabled) {
sound_use_sfx(true);
}
display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_DISABLED);
register_VI_handler(frame_counter_handler);

View File

@ -88,6 +88,7 @@ typedef struct {
bool back;
bool options;
bool settings;
bool lz_context;
} actions;
struct {

View File

@ -1,3 +1,4 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
@ -146,234 +147,234 @@ typedef struct {
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(0x000000004CBC3B56, SAVE_TYPE_SRAM_256KBIT, 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_CHECK_CODE(0x9A746EBF2802EA99, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Toon panic
MATCH_CHECK_CODE(0x21548CA921548CA9, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Mini racers
MATCH_CHECK_CODE(0xBC9B2CC34ED04DA5, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Starcraft 64 [Prototype 2000]
MATCH_CHECK_CODE(0x5D40ED2C10D6ABCF, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064
MATCH_CHECK_CODE(0x7280E03F497689BA, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064 [ENG patch]
MATCH_CHECK_CODE(0x0DD4ABABB5A2A91E, SAVE_TYPE_EEPROM_16KBIT, FEAT_EXP_PAK_REQUIRED), // DK Retail kiosk demo
MATCH_CHECK_CODE(0xEB85EBC9596682AF, SAVE_TYPE_FLASHRAM_1MBIT, FEAT_NONE), // Doubutsu Banchou
MATCH_CHECK_CODE(0x9A746EBF2802EA99, SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Toon panic
MATCH_CHECK_CODE(0x21548CA921548CA9, SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Mini racers
MATCH_CHECK_CODE(0xBC9B2CC34ED04DA5, SAVE_TYPE_FLASHRAM_1MBIT, FEAT_NONE), // Starcraft 64 [Prototype 2000]
MATCH_CHECK_CODE(0x5D40ED2C10D6ABCF, SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Viewpoint 2064
MATCH_CHECK_CODE(0x7280E03F497689BA, SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Viewpoint 2064 [ENG patch]
MATCH_CHECK_CODE(0xCDB8B4D08832352D, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [USA CRACK]
MATCH_CHECK_CODE(0xB66E0F7C2709C22F, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [PAL CRACK]
MATCH_CHECK_CODE(0xCDB8B4D08832352D, SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Jet Force Gemini [USA CRACK]
MATCH_CHECK_CODE(0xB66E0F7C2709C22F, SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Jet Force Gemini [PAL CRACK]
MATCH_CHECK_CODE(0xCE84793D27ECC1AD, SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [USA CRACK]
MATCH_CHECK_CODE(0x1F95CAAA047FC22A, SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [PAL CRACK]
MATCH_CHECK_CODE(0xCE84793D27ECC1AD, SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [USA CRACK]
MATCH_CHECK_CODE(0x1F95CAAA047FC22A, SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [PAL CRACK]
MATCH_CHECK_CODE(0xE3FF09DFCAE4B0ED, SAVE_TYPE_SRAM, FEAT_RPAK), // Banjo tooie [USA CRACK]
MATCH_CHECK_CODE(0xE3FF09DFCAE4B0ED, SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Banjo tooie [USA CRACK]
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_4K, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
MATCH_ID_REGION_VERSION("NK4J", 0, SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
MATCH_ID_REGION_VERSION("NK4J", 1, SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
MATCH_ID("NK4", SAVE_TYPE_EEPROM_4KBIT, 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("NSMJ", 3, SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Super Mario 64 Shindou Edition
MATCH_ID("NSM", SAVE_TYPE_EEPROM_4KBIT, 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_VERSION("NWRJ", 2, SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Wave Race 64 Shindou Edition
MATCH_ID("NWR", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Wave Race 64
MATCH_ID_REGION("N3HJ", SAVE_TYPE_SRAM, FEAT_NONE), // Ganbare! Nippon! Olympics 2000
MATCH_ID_REGION("N3HJ", SAVE_TYPE_SRAM_256KBIT, 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_REGION("ND3J", SAVE_TYPE_EEPROM_16KBIT, 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_REGION("ND4J", SAVE_TYPE_EEPROM_16KBIT, 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("NDKJ", SAVE_TYPE_EEPROM_4KBIT, 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("NSVE", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Space Station Silicon Valley
MATCH_ID("NSV", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK | FEAT_EXP_PAK_BROKEN), // Space Station Silicon Valley
MATCH_ID_REGION("NWTJ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Wetrix
MATCH_ID_REGION("NWTJ", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Wetrix
MATCH_ID("NWT", SAVE_TYPE_NONE, FEAT_CPAK), // Wetrix
// EEPROM 4K
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("CLB", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK | FEAT_64DD_ENHANCED), // Mario Party (NTSC)
MATCH_ID("NAB", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Air Boarder 64
MATCH_ID("NAD", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Worms Armageddon (U)
MATCH_ID("NAG", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // AeroGauge
MATCH_ID("NB6", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_TPAK), // Super B-Daman: Battle Phoenix 64
MATCH_ID("NBC", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Blast Corps
MATCH_ID("NBD", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Bomberman Hero [Mirian Ojo o Sukue! (J)]
MATCH_ID("NBH", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Body Harvest
MATCH_ID("NBK", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Banjo-Kazooie [Banjo to Kazooie no Daiboken (J)]
MATCH_ID("NBM", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Bomberman 64 [Baku Bomberman (J)]
MATCH_ID("NBN", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Bakuretsu Muteki Bangaioh
MATCH_ID("NBV", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Bomberman 64: The Second Attack! [Baku Bomberman 2 (J)]
MATCH_ID("NCG", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK), // Choro Q 64 II - Hacha Mecha Grand Prix Race (J)
MATCH_ID("NCH", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Chopper Attack
MATCH_ID("NCR", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Penny Racers [Choro Q 64 (J)]
MATCH_ID("NCT", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Chameleon Twist
MATCH_ID("NCU", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Cruis'n USA
MATCH_ID("NCX", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Custom Robo
MATCH_ID("NDQ", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Disney's Donald Duck - Goin' Quackers [Quack Attack (E)]
MATCH_ID("NDR", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Doraemon: Nobita to 3tsu no Seireiseki
MATCH_ID("NDU", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Duck Dodgers starring Daffy Duck
MATCH_ID("NDY", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Diddy Kong Racing
MATCH_ID("NEA", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // PGA European Tour
MATCH_ID("NER", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Aero Fighters Assault [Sonic Wings Assault (J)]
MATCH_ID("NF2", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // F-1 World Grand Prix II
MATCH_ID("NFG", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Fighter Destiny 2
MATCH_ID("NFH", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // In-Fisherman Bass Hunter 64
MATCH_ID("NFW", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // F-1 World Grand Prix
MATCH_ID("NFX", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Star Fox 64 [Lylat Wars (E)]
MATCH_ID("NFY", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Kakutou Denshou: F-Cup Maniax
MATCH_ID("NGE", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // GoldenEye 007
MATCH_ID("NGL", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Getter Love!!
MATCH_ID("NGU", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Tsumi to Batsu: Hoshi no Keishousha (Sin and Punishment)
MATCH_ID("NGV", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Glover
MATCH_ID("NHA", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Bomberman 64: Arcade Edition (J)
MATCH_ID("NHF", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // 64 Hanafuda: Tenshi no Yakusoku
MATCH_ID("NHP", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Heiwa Pachinko World 64
MATCH_ID("NIC", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Indy Racing 2000
MATCH_ID("NIJ", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK | FEAT_EXP_PAK_RECOMMENDED), // Indiana Jones and the Infernal Machine
MATCH_ID("NIR", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Utchan Nanchan no Hono no Challenger: Denryuu Ira Ira Bou
MATCH_ID("NJM", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Earthworm Jim 3D
MATCH_ID("NK2", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Snowboard Kids 2 [Chou Snobow Kids (J)]
MATCH_ID("NKA", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Fighters Destiny [Fighting Cup (J)]
MATCH_ID("NKI", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Killer Instinct Gold
MATCH_ID("NKT", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Mario Kart 64
MATCH_ID("NLB", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Mario Party (PAL)
MATCH_ID("NLL", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Last Legion UX
MATCH_ID("NLR", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Lode Runner 3-D
MATCH_ID("NMG", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Monaco Grand Prix [Racing Simulation 2 (G)]
MATCH_ID("NMI", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Mission: Impossible
MATCH_ID("NML", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK | FEAT_TPAK), // Mickey's Speedway USA [Mickey no Racing Challenge USA (J)]
MATCH_ID("NMO", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Monopoly
MATCH_ID("NMR", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Multi-Racing Championship
MATCH_ID("NMS", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // Morita Shougi 64
MATCH_ID("NMU", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Big Mountain 2000
MATCH_ID("NMW", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Mario Party 2
MATCH_ID("NMZ", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Zool - Majou Tsukai Densetsu (J)
MATCH_ID("NN6", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Dr. Mario 64
MATCH_ID("NNA", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Star Wars Episode I: Battle for Naboo
MATCH_ID("NOS", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // 64 Oozumou
MATCH_ID("NP2", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Chou Kuukan Night Pro Yakyuu King 2 (J)
MATCH_ID("NPG", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK | FEAT_VRU), // Hey You, Pikachu! [Pikachu Genki Dechu (J)]
MATCH_ID("NPT", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK | FEAT_TPAK), // Puyo Puyon Party
MATCH_ID("NPW", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Pilotwings 64
MATCH_ID("NPY", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Puyo Puyo Sun 64
MATCH_ID("NRA", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Rally '99 (J)
MATCH_ID("NRC", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Top Gear Overdrive
MATCH_ID("NRS", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Star Wars: Rogue Squadron [Shutsugeki! Rogue Chuutai (J)]
MATCH_ID("NS3", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // AI Shougi 3
MATCH_ID("NS6", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Star Soldier: Vanishing Earth
MATCH_ID("NSA", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Sonic Wings Assault (J)
MATCH_ID("NSC", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Starshot: Space Circus Fever
MATCH_ID("NSN", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Snow Speeder (J)
MATCH_ID("NSS", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Super Robot Spirits
MATCH_ID("NSU", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Rocket: Robot on Wheels
MATCH_ID("NSW", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Star Wars: Shadows of the Empire [Teikoku no Kage (J)]
MATCH_ID("NT6", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Tetris 64
MATCH_ID("NTB", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Transformers: Beast Wars Metals 64 (J)
MATCH_ID("NTC", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // 64 Trump Collection
MATCH_ID("NTJ", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Tom & Jerry in Fists of Fury
MATCH_ID("NTM", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Mischief Makers [Yuke Yuke!! Trouble Makers (J)]
MATCH_ID("NTN", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // All Star Tennis '99
MATCH_ID("NTP", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Tetrisphere
MATCH_ID("NTR", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Top Gear Rally (J + E)
MATCH_ID("NTW", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK), // 64 de Hakken!! Tamagotchi
MATCH_ID("NTX", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Taz Express
MATCH_ID("NVL", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // V-Rally Edition '99
MATCH_ID("NVY", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // V-Rally Edition '99 (J)
MATCH_ID("NWC", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Wild Choppers
MATCH_ID("NWQ", SAVE_TYPE_EEPROM_4KBIT, FEAT_CPAK | FEAT_RPAK), // Rally Challenge 2000
MATCH_ID("NWU", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Worms Armageddon (E)
MATCH_ID("NXO", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Cruis'n Exotica
MATCH_ID("NYK", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Yakouchuu II: Satsujin Kouro
// EEPROM 16K
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("N3D", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Doraemon 3: Nobita no Machi SOS!
MATCH_ID("NB7", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
MATCH_ID("NCW", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Cruis'n World
MATCH_ID("NCZ", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Custom Robo V2
MATCH_ID("ND2", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Doraemon 2: Nobita to Hikari no Shinden
MATCH_ID("ND6", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK | FEAT_VRU), // Densha de Go! 64
MATCH_ID("NDO", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey Kong 64
MATCH_ID("NEP", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Star Wars Episode I: Racer
MATCH_ID("NEV", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Neon Genesis Evangelion
MATCH_ID("NFU", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Conker's Bad Fur Day
MATCH_ID("NGC", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK), // GT 64: Championship Edition
MATCH_ID("NGT", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK), // City Tour GrandPrix - Zen Nihon GT Senshuken
MATCH_ID("NIM", SAVE_TYPE_EEPROM_16KBIT, FEAT_NONE), // Ide Yosuke no Mahjong Juku
MATCH_ID("NM8", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK | FEAT_TPAK), // Mario Tennis
MATCH_ID("NMV", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Mario Party 3
MATCH_ID("NMX", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK), // Excitebike 64
MATCH_ID("NNB", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK), // Kobe Bryant in NBA Courtside
MATCH_ID("NPD", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK | FEAT_EXP_PAK_RECOMMENDED), // Perfect Dark
MATCH_ID("NPP", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK), // Parlor! Pro 64: Pachinko Jikki Simulation Game
MATCH_ID("NR7", SAVE_TYPE_EEPROM_16KBIT, FEAT_TPAK), // Robot Poncots 64: 7tsu no Umi no Caramel
MATCH_ID("NRZ", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Ridge Racer 64
MATCH_ID("NUB", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_TPAK), // PD Ultraman Battle Collection 64
MATCH_ID("NYS", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Yoshi's Story
// SRAM 256K
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("NDD", SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // 64DD Conversion Rom
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("CFZ", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK | FEAT_64DD_ENHANCED), // F-Zero X (J)
MATCH_ID("CPS", SAVE_TYPE_SRAM_256KBIT, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium (J)
MATCH_ID("CZL", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK | FEAT_64DD_ENHANCED), // Legend of Zelda: Ocarina of Time [Zelda no Densetsu - Toki no Ocarina (J)]
MATCH_ID("NA2", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 2
MATCH_ID("NAL", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Super Smash Bros. [Nintendo All-Star! Dairantou Smash Brothers (J)]
MATCH_ID("NB5", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Biohazard 2 (J)
MATCH_ID("NDD", SAVE_TYPE_SRAM_256KBIT, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // 64DD Conversion Rom
MATCH_ID("NFZ", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // F-Zero X (U + E)
MATCH_ID("NG6", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Ganmare Goemon: Dero Dero Douchuu Obake Tenkomori
MATCH_ID("NGP", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK), // Goemon: Mononoke Sugoroku
MATCH_ID("NHY", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_RPAK), // Hybrid Heaven (J)
MATCH_ID("NIB", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Itoi Shigesato no Bass Tsuri No. 1 Kettei Ban!
MATCH_ID("NJ5", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK), // Jikkyou Powerful Pro Yakyuu 5
MATCH_ID("NJG", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Jinsei Game 64
MATCH_ID("NKG", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_RPAK), // Major League Baseball featuring Ken Griffey Jr.
MATCH_ID("NMF", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK | FEAT_TPAK), // Mario Golf 64
MATCH_ID("NOB", SAVE_TYPE_SRAM_256KBIT, FEAT_NONE), // Ogre Battle 64: Person of Lordly Caliber
MATCH_ID("NP4", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK), // Jikkyou Powerful Pro Yakyuu 4
MATCH_ID("NP6", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_TPAK), // Jikkyou Powerful Pro Yakyuu 6
MATCH_ID("NPA", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_TPAK), // Jikkyou Powerful Pro Yakyuu 2000
MATCH_ID("NPE", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK), // Jikkyou Powerful Pro Yakyuu Basic Ban 2001
MATCH_ID("NPM", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK), // Premier Manager 64
MATCH_ID("NPS", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_RPAK), // Jikkyou J.League 1999: Perfect Striker 2
MATCH_ID("NRE", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Resident Evil 2
MATCH_ID("NRI", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK), // New Tetris, The
MATCH_ID("NS4", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_TPAK), // Super Robot Taisen 64
MATCH_ID("NSI", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK), // Fushigi no Dungeon: Fuurai no Shiren 2
MATCH_ID("NT3", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK), // Shin Nihon Pro Wrestling - Toukon Road 2 - The Next Generation (J)
MATCH_ID("NTE", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // 1080 Snowboarding
MATCH_ID("NUM", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK | FEAT_TPAK), // Nushi Zuri 64: Shiokaze ni Notte
MATCH_ID("NUT", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK), // Nushi Zuri 64
MATCH_ID("NVB", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Bass Rush - ECOGEAR PowerWorm Championship (J)
MATCH_ID("NVP", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 64
MATCH_ID("NW2", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // WCW-nWo Revenge
MATCH_ID("NWL", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Waialae Country Club: True Golf Classics
MATCH_ID("NWX", SAVE_TYPE_SRAM_256KBIT, FEAT_CPAK | FEAT_RPAK), // WWF WrestleMania 2000
MATCH_ID("NYW", SAVE_TYPE_SRAM_256KBIT, FEAT_NONE), // Harvest Moon 64
MATCH_ID("NZL", SAVE_TYPE_SRAM_256KBIT, FEAT_RPAK), // Legend of Zelda: Ocarina of Time (E)
// SRAM 768K
MATCH_ID("CDZ", SAVE_TYPE_SRAM_BANKED, FEAT_RPAK | FEAT_64DD_ENHANCED), // Dezaemon 3D
// FLASHRAM
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("NCV", SAVE_TYPE_FLASHRAM, FEAT_NONE), // Cubivore (Translation)
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("CP2", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium 2 (J)
MATCH_ID("NAF", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_CPAK | FEAT_RTC), // Doubutsu no Mori
MATCH_ID("NCC", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_RPAK), // Command & Conquer
MATCH_ID("NCV", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_NONE), // Cubivore (Translation)
MATCH_ID("NCK", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_RPAK), // NBA Courtside 2 featuring Kobe Bryant
MATCH_ID("NDA", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_CPAK), // Derby Stallion 64
MATCH_ID("NDP", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_EXP_PAK_REQUIRED), // Dinosaur Planet (Unlicensed)
MATCH_ID("NJF", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_RPAK), // Jet Force Gemini [Star Twins (J)]
MATCH_ID("NKJ", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_RPAK), // Ken Griffey Jr.'s Slugfest
MATCH_ID("NM6", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_RPAK), // Mega Man 64
MATCH_ID("NMQ", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_RPAK), // Paper Mario
MATCH_ID("NPF", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_NONE), // Pokemon Snap [Pocket Monsters Snap (J)]
MATCH_ID("NPN", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_NONE), // Pokemon Puzzle League
MATCH_ID("NPO", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_TPAK), // Pokemon Stadium
MATCH_ID("NRH", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_RPAK), // Rockman Dash - Hagane no Boukenshin (J)
MATCH_ID("NSQ", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_RPAK | FEAT_EXP_PAK_RECOMMENDED), // StarCraft 64
MATCH_ID("NT9", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_NONE), // Tigger's Honey Hunt
MATCH_ID("NW4", SAVE_TYPE_FLASHRAM_1MBIT, FEAT_CPAK | FEAT_RPAK), // WWF No Mercy
MATCH_ID("NZS", SAVE_TYPE_FLASHRAM_1MBIT, 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)]
@ -749,12 +750,12 @@ static void extract_rom_info (match_t *match, rom_header_t *rom_header, rom_info
}
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 1: match->data.save = SAVE_TYPE_EEPROM_4KBIT; break;
case 2: match->data.save = SAVE_TYPE_EEPROM_16KBIT; break;
case 3: match->data.save = SAVE_TYPE_SRAM_256KBIT; 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;
case 5: match->data.save = SAVE_TYPE_FLASHRAM_1MBIT; break;
case 6: match->data.save = SAVE_TYPE_SRAM_1MBIT; break;
default: match->data.save = SAVE_TYPE_NONE; break;
}
}
@ -832,13 +833,21 @@ static rom_err_t save_override (path_t *path, const char *id, int value, int def
mini_t *ini = mini_try_load(path_get(overrides_path));
if (!ini) {
return ROM_ERR_IO;
return ROM_ERR_SAVE_IO;
}
int mini_err;
if (value == default_value) {
mini_delete_value(ini, NULL, id);
mini_err = mini_delete_value(ini, NULL, id);
} else {
mini_set_int(ini, NULL, id, value);
mini_err = mini_set_int(ini, NULL, id, value);
}
if ((mini_err != MINI_OK) && (mini_err != MINI_VALUE_NOT_FOUND)) {
path_free(overrides_path);
mini_free(ini);
return ROM_ERR_SAVE_IO;
}
bool empty = mini_empty(ini);
@ -847,16 +856,16 @@ static rom_err_t save_override (path_t *path, const char *id, int value, int def
if (mini_save(ini, MINI_FLAGS_NONE) != MINI_OK) {
path_free(overrides_path);
mini_free(ini);
return ROM_ERR_IO;
return ROM_ERR_SAVE_IO;
}
}
mini_free(ini);
if (empty) {
if (remove(path_get(overrides_path))) {
if (remove(path_get(overrides_path)) && (errno != ENOENT)) {
path_free(overrides_path);
return ROM_ERR_IO;
return ROM_ERR_SAVE_IO;
}
}
@ -946,10 +955,10 @@ rom_err_t rom_info_load (path_t *path, rom_info_t *rom_info) {
setbuf(f, NULL);
if (fread(&rom_header, sizeof(rom_header), 1, f) != 1) {
fclose(f);
return ROM_ERR_IO;
return ROM_ERR_LOAD_IO;
}
if (fclose(f)) {
return ROM_ERR_IO;
return ROM_ERR_LOAD_IO;
}
fix_rom_header_endianness(&rom_header, rom_info);

View File

@ -18,7 +18,8 @@
/** @brief ROM error enumeration. */
typedef enum {
ROM_OK,
ROM_ERR_IO,
ROM_ERR_LOAD_IO,
ROM_ERR_SAVE_IO,
ROM_ERR_NO_FILE,
} rom_err_t;
@ -113,12 +114,12 @@ typedef enum {
typedef enum {
/** @brief There is no expected save type. */
SAVE_TYPE_NONE = 0,
SAVE_TYPE_EEPROM_4K = 1,
SAVE_TYPE_EEPROM_16K = 2,
SAVE_TYPE_SRAM = 3,
SAVE_TYPE_EEPROM_4KBIT = 1,
SAVE_TYPE_EEPROM_16KBIT = 2,
SAVE_TYPE_SRAM_256KBIT = 3,
SAVE_TYPE_SRAM_BANKED = 4,
SAVE_TYPE_SRAM_128K = 5,
SAVE_TYPE_FLASHRAM = 6,
SAVE_TYPE_SRAM_1MBIT = 5,
SAVE_TYPE_FLASHRAM_1MBIT = 6,
SAVE_TYPE_FLASHRAM_PKST2 = 7,
SAVE_TYPE_AUTOMATIC = -1,
} rom_save_type_t;

View File

@ -13,10 +13,10 @@ static settings_t init = {
.show_protected_entries = false,
.default_directory = "/",
.use_saves_folder = true,
.sound_enabled = true,
/* Beta feature flags (should always init to off) */
.bgm_enabled = false,
.sound_enabled = false,
.rumble_enabled = false,
};
@ -39,10 +39,10 @@ void settings_load (settings_t *settings) {
settings->show_protected_entries = mini_get_bool(ini, "menu", "show_protected_entries", init.show_protected_entries);
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);
settings->sound_enabled = mini_get_bool(ini, "menu", "sound_enabled", init.sound_enabled);
/* 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);
@ -55,10 +55,10 @@ void settings_save (settings_t *settings) {
mini_set_bool(ini, "menu", "show_protected_entries", settings->show_protected_entries);
mini_set_string(ini, "menu", "default_directory", settings->default_directory);
mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder);
mini_set_bool(ini, "menu", "sound_enabled", settings->sound_enabled);
/* Beta feature flags, they should not save until production ready! */
// mini_set_bool(ini, "menu_beta_flag", "bgm_enabled", settings->bgm_enabled);
// mini_set_bool(ini, "menu_beta_flag", "sound_enabled", settings->sound_enabled);
// mini_set_bool(ini, "menu_beta_flag", "rumble_enabled", settings->rumble_enabled);
mini_save(ini, MINI_FLAGS_SKIP_EMPTY_GROUPS);

View File

@ -3,14 +3,18 @@
#include <libdragon.h>
#include "mp3_player.h"
#include "sound.h"
#define DEFAULT_FREQUENCY (44100)
#define NUM_BUFFERS (4)
#define NUM_CHANNELS (2)
#define NUM_CHANNELS (3)
static wav64_t sfx_cursor, sfx_error, sfx_enter, sfx_exit, sfx_setting;
static bool sound_initialized = false;
static bool sfx_enabled = false;
static void sound_reconfigure (int frequency) {
@ -35,6 +39,51 @@ void sound_init_mp3_playback (void) {
sound_reconfigure(mp3player_get_samplerate());
}
void sound_init_sfx (void) {
mixer_ch_set_vol(SOUND_SFX_CHANNEL, 0.5f, 0.5f);
wav64_open(&sfx_cursor, "rom:/cursorsound.wav64");
wav64_open(&sfx_exit, "rom:/back.wav64");
wav64_open(&sfx_setting, "rom:/settings.wav64");
wav64_open(&sfx_enter, "rom:/enter.wav64");
wav64_open(&sfx_error, "rom:/error.wav64");
sfx_enabled = true;
}
void sound_use_sfx(bool state) {
if (state) {
sfx_enabled = true;
}
else {
sfx_enabled = false;
}
}
void sound_play_effect(sound_effect_t sfx) {
if(sfx_enabled) {
switch (sfx) {
case SFX_CURSOR:
wav64_play(&sfx_cursor, SOUND_SFX_CHANNEL);
break;
case SFX_EXIT:
wav64_play(&sfx_exit, SOUND_SFX_CHANNEL);
break;
case SFX_SETTING:
wav64_play(&sfx_setting, SOUND_SFX_CHANNEL);
break;
case SFX_ENTER:
wav64_play(&sfx_enter, SOUND_SFX_CHANNEL);
break;
case SFX_ERROR:
wav64_play(&sfx_error, SOUND_SFX_CHANNEL);
break;
default:
break;
}
}
}
void sound_deinit (void) {
if (sound_initialized) {
mixer_close();

View File

@ -7,14 +7,26 @@
#ifndef SOUND_H__
#define SOUND_H__
#include <stdbool.h>
#define SOUND_MP3_PLAYER_CHANNEL (0)
#define SOUND_SFX_CHANNEL (2)
typedef enum {
SFX_CURSOR,
SFX_ERROR,
SFX_ENTER,
SFX_EXIT,
SFX_SETTING,
} sound_effect_t;
void sound_init_default (void);
void sound_init_mp3_playback (void);
void sound_init_sfx (void);
void sound_use_sfx(bool);
void sound_play_effect(sound_effect_t sfx);
void sound_deinit (void);
void sound_poll (void);
#endif

View File

@ -1,11 +1,12 @@
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <time.h>
#include "../fonts.h"
#include "utils/fs.h"
#include "views.h"
#include "../sound.h"
static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
@ -22,10 +23,17 @@ static const char *hidden_paths[] = {
"/menu.bin",
"/menu",
"/N64FlashcartMenu.n64",
"/OS64.v64",
"/OS64P.v64",
"/ED64",
"/ED64P",
"/sc64menu.n64",
// Windows garbage
"/System Volume Information",
// macOS garbage
"/.fseventsd",
"/.Spotlight-V100",
"/.Trashes",
"/.VolumeIcon.icns",
"/.metadata_never_index",
NULL,
};
@ -303,16 +311,19 @@ static void process (menu_t *menu) {
if (menu->browser.selected < 0) {
menu->browser.selected = 0;
}
sound_play_effect(SFX_CURSOR);
} else if (menu->actions.go_down) {
menu->browser.selected += scroll_speed;
if (menu->browser.selected >= menu->browser.entries) {
menu->browser.selected = menu->browser.entries - 1;
}
sound_play_effect(SFX_CURSOR);
}
menu->browser.entry = &menu->browser.list[menu->browser.selected];
}
if (menu->actions.enter && menu->browser.entry) {
sound_play_effect(SFX_ENTER);
switch (menu->browser.entry->type) {
case ENTRY_TYPE_DIR:
if (push_directory(menu, menu->browser.entry->name)) {
@ -350,10 +361,13 @@ static void process (menu_t *menu) {
menu->browser.valid = false;
menu_show_error(menu, "Couldn't open last directory");
}
sound_play_effect(SFX_EXIT);
} else if (menu->actions.options && menu->browser.entry) {
component_context_menu_show(&entry_context_menu);
sound_play_effect(SFX_SETTING);
} else if (menu->actions.settings) {
component_context_menu_show(&settings_context_menu);
sound_play_effect(SFX_SETTING);
}
}

View File

@ -1,5 +1,5 @@
#include "views.h"
#include "../sound.h"
#ifndef MENU_VERSION
#define MENU_VERSION "Unknown"
@ -13,6 +13,7 @@
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}

View File

@ -1,9 +1,11 @@
#include "views.h"
#include "../sound.h"
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}
@ -48,6 +50,7 @@ void view_error_display (menu_t *menu, surface_t *display) {
}
void menu_show_error (menu_t *menu, char *error_message) {
sound_play_effect(SFX_ERROR);
menu->next_mode = MENU_MODE_ERROR;
menu->error_message = error_message;
}

View File

@ -1,4 +1,5 @@
#include <sys/stat.h>
#include "../sound.h"
#include "utils/fs.h"
#include "views.h"
@ -12,8 +13,8 @@ static const char *patch_extensions[] = { "aps", "bps", "ips", "pps", "ups", "xd
static const char *archive_extensions[] = { "zip", "rar", "7z", "tar", "gz", NULL };
static const char *image_extensions[] = { "png", "jpg", "gif", NULL };
static const char *music_extensions[] = { "mp3", "wav", "ogg", "wma", "flac", NULL };
static const char *dexdrive_extensions[] = { "mpk", NULL };
static const char *emulator_extensions[] = { "emu", NULL };
static const char *controller_pak_extensions[] = { "mpk", "pak", NULL };
static const char *emulator_extensions[] = { "nes", "smc", "gb", "gbc", "sms", "gg", NULL };
static struct stat st;
@ -38,10 +39,10 @@ static char *format_file_type (char *name, bool is_directory) {
return " Type: Image file\n";
} else if (file_has_extensions(name, music_extensions)) {
return " Type: Music file\n";
} else if (file_has_extensions(name, dexdrive_extensions)) {
return " Type: DexDrive CPak backup file\n";
} else if (file_has_extensions(name, controller_pak_extensions)) {
return " Type: Controller Pak file\n";
} else if (file_has_extensions(name, emulator_extensions)) {
return " Type: Emulator file\n";
return " Type: Emulator ROM file\n";
}
return " Type: Unknown file\n";
}
@ -50,6 +51,7 @@ static char *format_file_type (char *name, bool is_directory) {
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}

View File

@ -1,9 +1,11 @@
#include "views.h"
#include "../sound.h"
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}
@ -17,8 +19,17 @@ static void draw (menu_t *menu, surface_t *d) {
component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"FLASHCART INFORMATION\n"
"\n"
"\n"
"This feature is not yet supported.\n\n"
);
// FIXME: Display:
// * cart_type
// * Firmware version
// * supported features (flashcart_features_t)
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"

View File

@ -1,4 +1,5 @@
#include <stdlib.h>
#include "../sound.h"
#include "../png_decoder.h"
#include "views.h"
@ -40,6 +41,7 @@ static void process (menu_t *menu) {
} else {
menu->next_mode = MENU_MODE_BROWSER;
}
sound_play_effect(SFX_EXIT);
} else if (menu->actions.enter && image) {
if (show_message) {
show_message = false;
@ -48,6 +50,7 @@ static void process (menu_t *menu) {
} else {
show_message = true;
}
sound_play_effect(SFX_ENTER);
}
}

View File

@ -1,11 +1,13 @@
#include "../cart_load.h"
#include "../disk_info.h"
#include "boot/boot.h"
#include "../sound.h"
#include "views.h"
static bool load_pending;
static bool load_rom;
static component_boxart_t *boxart;
static char *convert_error_message (disk_err_t err) {
@ -34,8 +36,10 @@ static void process (menu_t *menu) {
} else if (menu->actions.options && menu->load.rom_path) {
load_pending = true;
load_rom = true;
sound_play_effect(SFX_SETTING);
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}
@ -89,6 +93,8 @@ static void draw (menu_t *menu, surface_t *d) {
"R: Load with ROM"
);
}
component_boxart_draw(boxart);
}
rdpq_detach_show();
@ -145,6 +151,9 @@ static void load (menu_t *menu) {
}
}
static void deinit (void) {
component_boxart_free(boxart);
}
void view_load_disk_init (menu_t *menu) {
if (menu->load.disk_path) {
@ -160,6 +169,8 @@ void view_load_disk_init (menu_t *menu) {
if (err != DISK_OK) {
menu_show_error(menu, convert_error_message(err));
}
boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id);
}
void view_load_disk_display (menu_t *menu, surface_t *display) {
@ -171,4 +182,8 @@ void view_load_disk_display (menu_t *menu, surface_t *display) {
load_pending = false;
load(menu);
}
if (menu->next_mode != MENU_MODE_LOAD_DISK) {
deinit();
}
}

View File

@ -1,6 +1,7 @@
#include "../cart_load.h"
#include "boot/boot.h"
#include "utils/fs.h"
#include "../sound.h"
#include "views.h"
@ -36,6 +37,7 @@ static void process (menu_t *menu) {
load_pending = true;
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}

View File

@ -1,16 +1,18 @@
#include "../cart_load.h"
#include "../rom_info.h"
#include "boot/boot.h"
#include "../sound.h"
#include "views.h"
static bool show_extra_info_message = false;
static bool load_pending;
static component_boxart_t *boxart;
static char *convert_error_message (rom_err_t err) {
switch (err) {
case ROM_ERR_IO: return "I/O error during loading/storing ROM information";
case ROM_ERR_LOAD_IO: return "I/O error during loading ROM information and/or options";
case ROM_ERR_SAVE_IO: return "I/O error during storing ROM options";
case ROM_ERR_NO_FILE: return "Couldn't open ROM file";
default: return "Unknown ROM info load error";
}
@ -64,16 +66,16 @@ static const char *format_rom_destination_market (rom_destination_type_t market_
}
}
static const char *format_rom_save_type (rom_save_type_t save_type) {
static const char *format_rom_save_type (rom_save_type_t save_type, bool supports_cpak) {
switch (save_type) {
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)";
case SAVE_TYPE_NONE: return supports_cpak ? "Controller PAK" : "None";
case SAVE_TYPE_EEPROM_4KBIT: return supports_cpak ? "EEPROM 4kbit | Controller PAK" : "EEPROM 4kbit";
case SAVE_TYPE_EEPROM_16KBIT: return supports_cpak ? "EEPROM 16kbit | Controller PAK" : "EEPROM 16kbit";
case SAVE_TYPE_SRAM_256KBIT: return supports_cpak ? "SRAM 256kbit | Controller PAK" : "SRAM 256kbit";
case SAVE_TYPE_SRAM_BANKED: return supports_cpak ? "SRAM 768kbit / 3 banks | Controller PAK" : "SRAM 768kbit / 3 banks";
case SAVE_TYPE_SRAM_1MBIT: return supports_cpak ? "SRAM 1Mbit | Controller PAK" : "SRAM 1Mbit";
case SAVE_TYPE_FLASHRAM_1MBIT: return supports_cpak ? "FlashRAM 1Mbit | Controller PAK" : "FlashRAM 1Mbit";
case SAVE_TYPE_FLASHRAM_PKST2: return supports_cpak ? "FlashRAM (Pokemon Stadium 2) | Controller PAK" : "FlashRAM (Pokemon Stadium 2)";
default: return "Unknown";
}
}
@ -164,12 +166,12 @@ static component_context_menu_t set_cic_type_context_menu = { .list = {
static component_context_menu_t set_save_type_context_menu = { .list = {
{ .text = "Automatic", .action = set_save_type, .arg = (void *) (SAVE_TYPE_AUTOMATIC) },
{ .text = "None", .action = set_save_type, .arg = (void *) (SAVE_TYPE_NONE) },
{ .text = "EEPROM 4kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_EEPROM_4K) },
{ .text = "EEPROM 16kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_EEPROM_16K) },
{ .text = "SRAM 256kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM) },
{ .text = "SRAM 768kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM_BANKED) },
{ .text = "SRAM 1Mbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM_128K) },
{ .text = "FlashRAM 1Mbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_FLASHRAM) },
{ .text = "EEPROM 4kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_EEPROM_4KBIT) },
{ .text = "EEPROM 16kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_EEPROM_16KBIT) },
{ .text = "SRAM 256kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM_256KBIT) },
{ .text = "SRAM 768kbit / 3 banks", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM_BANKED) },
{ .text = "SRAM 1Mbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM_1MBIT) },
{ .text = "FlashRAM 1Mbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_FLASHRAM_1MBIT) },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
@ -197,8 +199,17 @@ static void process (menu_t *menu) {
load_pending = true;
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
} else if (menu->actions.options) {
component_context_menu_show(&options_context_menu);
sound_play_effect(SFX_SETTING);
} else if (menu->actions.lz_context) {
if (show_extra_info_message) {
show_extra_info_message = false;
} else {
show_extra_info_message = true;
}
sound_play_effect(SFX_SETTING);
}
}
@ -226,22 +237,48 @@ static void draw (menu_t *menu, surface_t *d) {
"\n"
"\n"
"\n"
"Description:\n None.\n\n\n\n\n\n\n\n"
"Expansion PAK: %s\n"
"TV type: %s\n"
"CIC: %s\n"
"GS/AR Cheats: Off\n"
"Patches: Off\n"
"Save type: %s\n",
format_rom_expansion_pak_info(menu->load.rom_info.features.expansion_pak),
format_rom_tv_type(rom_info_get_tv_type(&menu->load.rom_info)),
format_cic_type(rom_info_get_cic_type(&menu->load.rom_info)),
format_rom_save_type(rom_info_get_save_type(&menu->load.rom_info), menu->load.rom_info.features.controller_pak)
);
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"A: Load and run ROM\n"
"B: Back"
);
component_actions_bar_text_draw(
ALIGN_RIGHT, VALIGN_TOP,
"L|Z: Extra Info\n"
"R: Options"
);
component_boxart_draw(boxart);
if (show_extra_info_message) {
component_messagebox_draw(
"EXTRA ROM INFO\n"
"\n"
"Endianness: %s\n"
"Title: %.20s\n"
"Game code: %c%c%c%c\n"
"Media type: %s\n"
" Destination market: %s\n"
"Variant: %s\n"
"Version: %hhu\n"
"Check code: 0x%016llX\n"
" Save type: %s\n"
" TV 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",
"Clock Rate: %.2fMHz\n\n\n"
"Press L|Z to return.\n",
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],
@ -249,28 +286,11 @@ static void draw (menu_t *menu, surface_t *d) {
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(rom_info_get_save_type(&menu->load.rom_info)),
format_rom_tv_type(rom_info_get_tv_type(&menu->load.rom_info)),
format_rom_expansion_pak_info(menu->load.rom_info.features.expansion_pak),
format_cic_type(rom_info_get_cic_type(&menu->load.rom_info)),
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(
ALIGN_LEFT, VALIGN_TOP,
"A: Load and run ROM\n"
"B: Exit"
);
component_actions_bar_text_draw(
ALIGN_RIGHT, VALIGN_TOP,
"\n"
"R: Options"
);
component_boxart_draw(boxart);
}
component_context_menu_draw(&options_context_menu);
}

View File

@ -42,11 +42,13 @@ static void process (menu_t *menu) {
menu_show_error(menu, convert_error_message(err));
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
} else if (menu->actions.enter) {
err = mp3player_toggle();
if (err != MP3PLAYER_OK) {
menu_show_error(menu, convert_error_message(err));
}
sound_play_effect(SFX_ENTER);
} else if (menu->actions.go_left || menu->actions.go_right) {
int seconds = menu->actions.go_fast ? SEEK_SECONDS_FAST : SEEK_SECONDS;
err = mp3player_seek(menu->actions.go_left ? (-seconds) : seconds);

View File

@ -1,4 +1,5 @@
#include <time.h>
#include "../sound.h"
#include "views.h"
// FIXME: add implementation!
@ -18,6 +19,7 @@
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}
@ -34,7 +36,7 @@ static void draw (menu_t *menu, surface_t *d) {
"\n"
"\n"
"To set the date and time, please use the PC terminal\n"
"application and set via USB.\n\n"
"application and set via USB or a game that uses it.\n\n"
"Current date & time: %s\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n"
);

View File

@ -1,3 +1,6 @@
#include <stdbool.h>
#include "../sound.h"
#include "../settings.h"
#include "views.h"
@ -8,10 +11,111 @@ static const char *format_switch (bool state) {
}
}
static void set_pal60_type (menu_t *menu, void *arg) {
menu->settings.pal60_enabled = (bool) (arg);
settings_save(&menu->settings);
}
static void set_protected_entries_type (menu_t *menu, void *arg) {
menu->settings.show_protected_entries = (bool) (arg);
settings_save(&menu->settings);
menu->browser.reload = true;
}
static void set_use_saves_folder_type (menu_t *menu, void *arg) {
menu->settings.use_saves_folder = (bool) (arg);
settings_save(&menu->settings);
}
static void set_sound_enabled_type (menu_t *menu, void *arg) {
menu->settings.sound_enabled = (bool) (arg);
sound_use_sfx(menu->settings.sound_enabled);
settings_save(&menu->settings);
}
#ifdef BETA_SETTINGS
static void set_bgm_enabled_type (menu_t *menu, void *arg) {
menu->settings.bgm_enabled = (bool) (arg);
settings_save(&menu->settings);
}
static void set_rumble_enabled_type (menu_t *menu, void *arg) {
menu->settings.rumble_enabled = (bool) (arg);
settings_save(&menu->settings);
}
// static void set_use_default_settings (menu_t *menu, void *arg) {
// // FIXME: add implementation
// menu->browser.reload = true;
// }
#endif
static component_context_menu_t set_pal60_type_context_menu = { .list = {
{.text = "On", .action = set_pal60_type, .arg = (void *) (true) },
{.text = "Off", .action = set_pal60_type, .arg = (void *) (false) },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
static component_context_menu_t set_protected_entries_type_context_menu = { .list = {
{.text = "On", .action = set_protected_entries_type, .arg = (void *) (true) },
{.text = "Off", .action = set_protected_entries_type, .arg = (void *) (false) },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
static component_context_menu_t set_sound_enabled_type_context_menu = { .list = {
{.text = "On", .action = set_sound_enabled_type, .arg = (void *) (true) },
{.text = "Off", .action = set_sound_enabled_type, .arg = (void *) (false) },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
static component_context_menu_t set_use_saves_folder_type_context_menu = { .list = {
{.text = "On", .action = set_use_saves_folder_type, .arg = (void *) (true) },
{.text = "Off", .action = set_use_saves_folder_type, .arg = (void *) (false) },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
#ifdef BETA_SETTINGS
static component_context_menu_t set_bgm_enabled_type_context_menu = { .list = {
{.text = "On", .action = set_bgm_enabled_type, .arg = (void *) (true) },
{.text = "Off", .action = set_bgm_enabled_type, .arg = (void *) (false) },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
static component_context_menu_t set_rumble_enabled_type_context_menu = { .list = {
{.text = "On", .action = set_rumble_enabled_type, .arg = (void *) (true) },
{.text = "Off", .action = set_rumble_enabled_type, .arg = (void *) (false) },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
#endif
static component_context_menu_t options_context_menu = { .list = {
{ .text = "PAL60 Mode", .submenu = &set_pal60_type_context_menu },
{ .text = "Show Hidden Files", .submenu = &set_protected_entries_type_context_menu },
{ .text = "Sound Effects", .submenu = &set_sound_enabled_type_context_menu },
{ .text = "Use Saves Folder", .submenu = &set_use_saves_folder_type_context_menu },
#ifdef BETA_SETTINGS
{ .text = "Background Music", .submenu = &set_bgm_enabled_type_context_menu },
{ .text = "Rumble Feedback", .submenu = &set_rumble_enabled_type_context_menu },
// { .text = "Restore Defaults", .action = set_use_default_settings },
#endif
COMPONENT_CONTEXT_MENU_LIST_END,
}};
static void process (menu_t *menu) {
if (menu->actions.back) {
if (component_context_menu_process(menu, &options_context_menu)) {
return;
}
if (menu->actions.enter) {
component_context_menu_show(&options_context_menu);
sound_play_effect(SFX_SETTING);
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}
@ -24,44 +128,54 @@ static void draw (menu_t *menu, surface_t *d) {
component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"SETTINGS EDITOR\n"
"MENU SETTINGS EDITOR\n"
"\n"
);
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n"
"To change the settings, please adjust them\n"
"directly in the 'menu/config.ini' file.\n\n"
"pal60_enabled: %s\n"
"show_protected_entries: %s\n"
"default_directory: %s\n"
"use_saves_folder: %s\n"
"bgm_enabled: %s\n"
"sound_enabled: %s\n"
"rumble_enabled: %s\n",
"\n\n"
" Default Directory : %s\n\n"
"To change the following menu settings, press 'A':\n"
"* PAL60 Mode : %s\n"
" Show Hidden Files : %s\n"
" Use Saves folder : %s\n"
" Sound Effects : %s\n"
#ifdef BETA_SETTINGS
" Background Music : %s\n"
" Rumble Feedback : %s\n"
#endif
"Note: Certain settings have the following caveats:\n\n"
"* Requires a flashcart reboot.\n",
menu->settings.default_directory,
format_switch(menu->settings.pal60_enabled),
format_switch(menu->settings.show_protected_entries),
menu->settings.default_directory,
format_switch(menu->settings.use_saves_folder),
format_switch(menu->settings.sound_enabled)
#ifdef BETA_SETTINGS
,
format_switch(menu->settings.bgm_enabled),
format_switch(menu->settings.sound_enabled),
format_switch(menu->settings.rumble_enabled)
#endif
);
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"A: Change\n"
"B: Back"
);
component_context_menu_draw(&options_context_menu);
rdpq_detach_show();
}
void view_settings_init (menu_t *menu) {
// Nothing to initialize (yet)
component_context_menu_init(&options_context_menu);
}
void view_settings_display (menu_t *menu, surface_t *display) {

View File

@ -1,5 +1,6 @@
#include <time.h>
#include "../sound.h"
#include "views.h"
@ -28,6 +29,7 @@ static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
}
}

View File

@ -3,6 +3,7 @@
#include "../components/constants.h"
#include "../fonts.h"
#include "../sound.h"
#include "utils/utils.h"
#include "views.h"
@ -55,6 +56,7 @@ static void perform_vertical_scroll (int lines) {
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
} else if (text) {
if (menu->actions.go_up) {
perform_vertical_scroll(menu->actions.go_fast ? -10 : -1);

View File

@ -1,7 +1,7 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include "fs.h"