diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0251badc..78caa941 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 170726a9..b7da2151 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/.gitignore b/.gitignore index bc2d5ddf..9b9f4205 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Ignore generated files in the libdragon FS /filesystem/FiraMonoBold.font64 +/filesystem/*.wav64 # Ignore external development tools /tools/* @@ -14,4 +15,4 @@ # Ignore any N64 ROM **/*.n64 **/*.v64 -**/*.z64 +**/*.z64 \ No newline at end of file diff --git a/Doxyfile b/Doxyfile index 7b99e3b5..4216e624 100644 --- a/Doxyfile +++ b/Doxyfile @@ -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 diff --git a/Makefile b/Makefile index 5b119c47..c52e6e97 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index b331bb24..703804cc 100644 --- a/README.md +++ b/README.md @@ -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) +[European Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M) -### Emulator support -Emulators should be added to the `/menu/emulators` directory on the SD card. +[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) diff --git a/assets/back.wav b/assets/back.wav new file mode 100644 index 00000000..f8d4655e Binary files /dev/null and b/assets/back.wav differ diff --git a/assets/cursorsound.wav b/assets/cursorsound.wav new file mode 100644 index 00000000..e6b2a624 Binary files /dev/null and b/assets/cursorsound.wav differ diff --git a/assets/enter.wav b/assets/enter.wav new file mode 100644 index 00000000..ad88ca23 Binary files /dev/null and b/assets/enter.wav differ diff --git a/assets/error.wav b/assets/error.wav new file mode 100644 index 00000000..5ced329b Binary files /dev/null and b/assets/error.wav differ diff --git a/assets/settings.wav b/assets/settings.wav new file mode 100644 index 00000000..3e9a80f0 Binary files /dev/null and b/assets/settings.wav differ diff --git a/docs/00_getting_started_sd.md b/docs/00_getting_started_sd.md new file mode 100644 index 00000000..4ee6e763 --- /dev/null +++ b/docs/00_getting_started_sd.md @@ -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. diff --git a/docs/01_menu_controls.md b/docs/01_menu_controls.md new file mode 100644 index 00000000..ac0dce8d --- /dev/null +++ b/docs/01_menu_controls.md @@ -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. + diff --git a/docs/99_developer_guide.md b/docs/99_developer_guide.md new file mode 100644 index 00000000..ff6ad67c --- /dev/null +++ b/docs/99_developer_guide.md @@ -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 +``` diff --git a/libdragon b/libdragon index af650428..9bae4999 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit af650428e9615f4e08d8e7eae187929a90c15ccc +Subproject commit 9bae49994bf1c796f9939ea1aa7c133813b9053f diff --git a/src/flashcart/64drive/64drive.c b/src/flashcart/64drive/64drive.c index 4a23d4d2..0fd47bef 100644 --- a/src/flashcart/64drive/64drive.c +++ b/src/flashcart/64drive/64drive.c @@ -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; diff --git a/src/flashcart/64drive/64drive_ll.h b/src/flashcart/64drive/64drive_ll.h index a16d78a1..696b028a 100644 --- a/src/flashcart/64drive/64drive_ll.h +++ b/src/flashcart/64drive/64drive_ll.h @@ -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; diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index a481ecf2..b4996f36 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -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; diff --git a/src/flashcart/flashcart_utils.c b/src/flashcart/flashcart_utils.c index 3eb13605..cfc714da 100644 --- a/src/flashcart/flashcart_utils.c +++ b/src/flashcart/flashcart_utils.c @@ -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; } diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index 1b105fb0..7119a271 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -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; diff --git a/src/flashcart/sc64/sc64_ll.h b/src/flashcart/sc64/sc64_ll.h index d385693f..ce3a5aae 100644 --- a/src/flashcart/sc64/sc64_ll.h +++ b/src/flashcart/sc64/sc64_ll.h @@ -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 { diff --git a/src/menu/actions.c b/src/menu/actions.c index 8bb58587..14c87fb6 100644 --- a/src/menu/actions.c +++ b/src/menu/actions.c @@ -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; } } diff --git a/src/menu/cart_load.c b/src/menu/cart_load.c index f7b341ab..b57afb6c 100644 --- a/src/menu/cart_load.c +++ b/src/menu/cart_load.c @@ -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; } diff --git a/src/menu/components/boxart.c b/src/menu/components/boxart.c index 54121b6e..f50294ee 100644 --- a/src/menu/components/boxart.c +++ b/src/menu/components/boxart.c @@ -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( @@ -84,4 +89,4 @@ void component_boxart_draw (component_boxart_t *b) { BOXART_LOADING_COLOR ); } -} +} \ No newline at end of file diff --git a/src/menu/components/common.c b/src/menu/components/common.c index 6bf708f8..d0fce75f 100644 --- a/src/menu/components/common.c +++ b/src/menu/components/common.c @@ -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, ¶graph_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 ); diff --git a/src/menu/components/constants.h b/src/menu/components/constants.h index f3eb3e39..c468a327 100644 --- a/src/menu/components/constants.h +++ b/src/menu/components/constants.h @@ -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) diff --git a/src/menu/components/context_menu.c b/src/menu/components/context_menu.c index f2e32c42..c4d0364a 100644 --- a/src/menu/components/context_menu.c +++ b/src/menu/components/context_menu.c @@ -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 diff --git a/src/menu/components/file_list.c b/src/menu/components/file_list.c index b00ef329..7a9835ce 100644 --- a/src/menu/components/file_list.c +++ b/src/menu/components/file_list.c @@ -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); diff --git a/src/menu/menu.c b/src/menu/menu.c index 250528b2..43edc187 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -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); diff --git a/src/menu/menu_state.h b/src/menu/menu_state.h index bbeaad41..1a1941e2 100644 --- a/src/menu/menu_state.h +++ b/src/menu/menu_state.h @@ -88,6 +88,7 @@ typedef struct { bool back; bool options; bool settings; + bool lz_context; } actions; struct { diff --git a/src/menu/rom_info.c b/src/menu/rom_info.c index 14026ee9..25f07832 100644 --- a/src/menu/rom_info.c +++ b/src/menu/rom_info.c @@ -1,3 +1,4 @@ +#include #include #include @@ -144,459 +145,459 @@ typedef struct { // clang-format off static const match_t database[] = { - MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED) + 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("N3H", SAVE_TYPE_NONE, FEAT_CPAK), // International Track & Field 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("ND3", SAVE_TYPE_NONE, FEAT_CPAK), // Castlevania + 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("ND4", SAVE_TYPE_NONE, FEAT_CPAK), // Castlevania - Legacy of Darkness + 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("NWT", SAVE_TYPE_NONE, FEAT_CPAK), // 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 + 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("NP3", SAVE_TYPE_FLASHRAM_PKST2, FEAT_TPAK), // Pokemon Stadium 2 [Pocket Monsters Stadium - Kin Gin (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)] // CONTROLLER PAK / NONE - MATCH_ID("N22", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - Round 2 - MATCH_ID("N2M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2002 - MATCH_ID("N32", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes 2 - MATCH_ID("N3P", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Triple Play 2000 - MATCH_ID("N64", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Kira to Kaiketsu! 64 Tanteidan - MATCH_ID("N7I", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA Soccer 64 [FIFA 64 (E)] - MATCH_ID("N8I", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA - Road to World Cup 98 [World Cup e no Michi (J)] - MATCH_ID("N8M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 64 - MATCH_ID("N8W", SAVE_TYPE_NONE, FEAT_CPAK), // World Cup '98 - MATCH_ID("N9B", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Live '99 - MATCH_ID("N9C", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nascar '99 - MATCH_ID("N9F", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA 99 - MATCH_ID("N9H", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL '99 - MATCH_ID("N9M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football '99 - MATCH_ID("NAC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Air Combat - MATCH_ID("NAH", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Asteroids Hyper 64 - MATCH_ID("NAI", SAVE_TYPE_NONE, FEAT_CPAK), // Midway's Greatest Arcade Hits Volume 1 - MATCH_ID("NAM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes - MATCH_ID("NAR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Armorines - Project S.W.A.R.M. - MATCH_ID("NAS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball 2001 - MATCH_ID("NAY", SAVE_TYPE_NONE, FEAT_CPAK), // Aidyn Chronicles - The First Mage - MATCH_ID("NB2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone '99 [NBA Pro '99 (E)] - MATCH_ID("NB3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bust-A-Move '99 [Bust-A-Move 3 DX (E)] - MATCH_ID("NB4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bass Masters 2000 - MATCH_ID("NB8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Beetle Adventure Racing (J) - MATCH_ID("NB9", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Jam '99 - MATCH_ID("NBA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone '98 [NBA Pro '98 (E)] - MATCH_ID("NBE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball 2000 - MATCH_ID("NBF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bio F.R.E.A.K.S. - MATCH_ID("NBI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz 2000 - MATCH_ID("NBJ", SAVE_TYPE_NONE, FEAT_CPAK), // Bakushou Jinsei 64 - Mezase! Resort Ou - MATCH_ID("NBL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Buck Bumble - MATCH_ID("NBO", SAVE_TYPE_NONE, FEAT_CPAK), // Bottom of the 9th - MATCH_ID("NBP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Blues Brothers 2000 - MATCH_ID("NBQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battletanx - Global Assault - MATCH_ID("NBR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Milo's Astro Lanes - MATCH_ID("NBS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball '99 - MATCH_ID("NBU", SAVE_TYPE_NONE, FEAT_CPAK), // Bust-A-Move 2 - Arcade Edition - MATCH_ID("NBW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Super Bowling - MATCH_ID("NBX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battletanx - MATCH_ID("NBY", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bug's Life, A - MATCH_ID("NBZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz - MATCH_ID("NCB", SAVE_TYPE_NONE, FEAT_RPAK), // Charlie Blast's Territory - MATCH_ID("NCD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Carmageddon 64 - MATCH_ID("NCE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nuclear Strike 64 - MATCH_ID("NCL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // California Speed - MATCH_ID("NCO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Jeremy McGrath Supercross 2000 - MATCH_ID("NCS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // S.C.A.R.S. - MATCH_ID("NDC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // SD Hiryuu no Ken Densetsu (J) - MATCH_ID("NDE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Destruction Derby 64 - MATCH_ID("NDF", SAVE_TYPE_NONE, FEAT_RPAK), // Dance Dance Revolution - Disney Dancing Museum - MATCH_ID("NDH", SAVE_TYPE_NONE, FEAT_CPAK), // Duel Heroes - MATCH_ID("NDM", SAVE_TYPE_NONE, FEAT_CPAK), // Doom 64 - MATCH_ID("NDN", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Duke Nukem 64 - MATCH_ID("NDQ", SAVE_TYPE_NONE, FEAT_CPAK), // Disney's Donald Duck - Goin' Quackers [Quack Attack (E)] - MATCH_ID("NDS", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Dynamite Soccer 64 - MATCH_ID("NDT", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // South Park - MATCH_ID("NDW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Daikatana, John Romero's - MATCH_ID("NDZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Duke Nukem - Zero Hour - MATCH_ID("NEG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Extreme-G - MATCH_ID("NET", SAVE_TYPE_NONE, FEAT_CPAK), // Quest 64 [Eltale Monsters (J) Holy Magic Century (E)] - MATCH_ID("NF9", SAVE_TYPE_NONE, FEAT_CPAK), // Fox Sports College Hoops '99 - MATCH_ID("NFB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz 2001 - MATCH_ID("NFD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Flying Dragon - MATCH_ID("NFF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Fighting Force 64 - MATCH_ID("NFL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2001 - MATCH_ID("NFO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Forsaken 64 - MATCH_ID("NFQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Razor Freestyle Scooter - MATCH_ID("NFR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // F-1 Racing Championship - MATCH_ID("NFS", SAVE_TYPE_NONE, FEAT_CPAK), // Famista 64 - MATCH_ID("NG2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Extreme-G XG2 - MATCH_ID("NG5", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ganbare Goemon - Neo Momoyama Bakufu no Odori [Mystical Ninja Starring Goemon] - MATCH_ID("NGA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Deadly Arts [G.A.S.P!! Fighter's NEXTream (E-J)] - MATCH_ID("NGB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Hyper Bike - MATCH_ID("NGD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gauntlet Legends (J) - MATCH_ID("NGM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Goemon's Great Adventure [Mystical Ninja 2 Starring Goemon] - MATCH_ID("NGN", SAVE_TYPE_NONE, FEAT_CPAK), // Golden Nugget 64 - MATCH_ID("NGR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Rally (U) - MATCH_ID("NGS", SAVE_TYPE_NONE, FEAT_CPAK), // Jikkyou G1 Stable - MATCH_ID("NGX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gauntlet Legends - MATCH_ID("NH5", SAVE_TYPE_NONE, FEAT_CPAK), // Nagano Winter Olympics '98 [Hyper Olympics in Nagano (J)] - MATCH_ID("NH9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Breakaway '99 - MATCH_ID("NHC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hercules - The Legendary Journeys - MATCH_ID("NHG", SAVE_TYPE_NONE, FEAT_CPAK), // F-1 Pole Position 64 - MATCH_ID("NHG", SAVE_TYPE_NONE, FEAT_CPAK), // Human Grand Prix - New Generation - MATCH_ID("NHK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hiryuu no Ken Twin - MATCH_ID("NHL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Breakaway '98 - MATCH_ID("NHM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mia Hamm Soccer 64 - MATCH_ID("NHN", SAVE_TYPE_NONE, FEAT_CPAK), // Olympic Hockey Nagano '98 - MATCH_ID("NHO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Blades of Steel '99 [NHL Pro '99 (E)] - MATCH_ID("NHS", SAVE_TYPE_NONE, FEAT_CPAK), // Hamster Monogatari 64 - MATCH_ID("NHT", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hydro Thunder - MATCH_ID("NHV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hybrid Heaven (U + E) - MATCH_ID("NHW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hot Wheels Turbo Racing - MATCH_ID("NHX", SAVE_TYPE_NONE, FEAT_CPAK), // Hexen - MATCH_ID("NIS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // International Superstar Soccer 2000 - MATCH_ID("NIV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Space Invaders - MATCH_ID("NJ2", SAVE_TYPE_NONE, FEAT_CPAK), // Wonder Project J2 - Koruro no Mori no Jozet (J) - MATCH_ID("NJ3", SAVE_TYPE_NONE, FEAT_CPAK), // Jikkyou World Soccer 3 - MATCH_ID("NJA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Jam 2000 - MATCH_ID("NJE", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Eleven Beat 1997 - MATCH_ID("NJL", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Live 64 - MATCH_ID("NJP", SAVE_TYPE_NONE, FEAT_CPAK), // International Superstar Soccer 64 [Jikkyo J-League Perfect Striker (J)] - MATCH_ID("NJQ", SAVE_TYPE_NONE, FEAT_RPAK), // Batman Beyond - Return of the Joker [Batman of the Future - Return of the Joker (E)] - MATCH_ID("NKE", SAVE_TYPE_NONE, FEAT_RPAK), // Knife Edge - Nose Gunner - MATCH_ID("NKK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Knockout Kings 2000 - MATCH_ID("NKM", SAVE_TYPE_NONE, FEAT_CPAK), // Pro Mahjong Kiwame 64 (J) - MATCH_ID("NKR", SAVE_TYPE_NONE, FEAT_CPAK), // Rakuga Kids (E) - MATCH_ID("NL2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Rally 2 - MATCH_ID("NLC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Automobili Lamborghini [Super Speed Race 64 (J)] - MATCH_ID("NLG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // LEGO Racers - MATCH_ID("NM3", SAVE_TYPE_NONE, FEAT_RPAK), // Monster Truck Madness 64 - MATCH_ID("NM4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mortal Kombat 4 - MATCH_ID("NM9", SAVE_TYPE_NONE, FEAT_CPAK), // Harukanaru Augusta Masters 98 - MATCH_ID("NMA", SAVE_TYPE_NONE, FEAT_CPAK), // Jangou Simulation Mahjong Do 64 - MATCH_ID("NMB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mike Piazza's Strike Zone - MATCH_ID("NMD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2000 - MATCH_ID("NMJ", SAVE_TYPE_NONE, FEAT_CPAK), // Mahjong 64 - MATCH_ID("NMM", SAVE_TYPE_NONE, FEAT_CPAK), // Mahjong Master - MATCH_ID("NMT", SAVE_TYPE_NONE, FEAT_RPAK), // Magical Tetris Challenge - MATCH_ID("NMY", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mortal Kombat Mythologies - Sub-Zero - MATCH_ID("NN2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nascar 2000 - MATCH_ID("NNC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nightmare Creatures - MATCH_ID("NNL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Live 2000 - MATCH_ID("NNM", SAVE_TYPE_NONE, FEAT_CPAK), // Namco Museum 64 - MATCH_ID("NNR", SAVE_TYPE_NONE, FEAT_CPAK), // Pro Mahjong Tsuwamono 64 - Jansou Battle ni Chousen (J) - MATCH_ID("NNS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Beetle Adventure Racing - MATCH_ID("NO7", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // The World Is Not Enough - MATCH_ID("NOF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Offroad Challenge - MATCH_ID("NOH", SAVE_TYPE_NONE, FEAT_RPAK | FEAT_TPAK), // Transformers Beast Wars - Transmetals - MATCH_ID("NOM", SAVE_TYPE_NONE, FEAT_CPAK), // Onegai Monsters - MATCH_ID("NOW", SAVE_TYPE_NONE, FEAT_CPAK), // Brunswick Circuit Pro Bowling - MATCH_ID("NP9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ms. Pac-Man - Maze Madness - MATCH_ID("NPB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Puzzle Bobble 64 (J) - MATCH_ID("NPC", SAVE_TYPE_NONE, FEAT_CPAK), // Pachinko 365 Nichi (J) - MATCH_ID("NPK", SAVE_TYPE_NONE, FEAT_CPAK), // Chou Kuukan Night Pro Yakyuu King (J) - MATCH_ID("NPL", SAVE_TYPE_NONE, FEAT_CPAK), // Power League 64 (J) - MATCH_ID("NPR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // South Park Rally - MATCH_ID("NPU", SAVE_TYPE_NONE, FEAT_CPAK), // Power Rangers - Lightspeed Rescue - MATCH_ID("NPX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Polaris SnoCross - MATCH_ID("NPZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Susume! Taisen Puzzle Dama Toukon! Marumata Chou (J) - MATCH_ID("NQ2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Quake 2 - MATCH_ID("NQ8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club '98 - MATCH_ID("NQ9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club '99 - MATCH_ID("NQB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club 2000 - MATCH_ID("NQC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club 2001 - MATCH_ID("NQK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Quake 64 - MATCH_ID("NR2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rush 2 - Extreme Racing USA - MATCH_ID("NR3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Stunt Racer 64 - MATCH_ID("NR6", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tom Clancy's Rainbow Six - MATCH_ID("NRD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - MATCH_ID("NRG", SAVE_TYPE_NONE, FEAT_RPAK), // Rugrats - Scavenger Hunt [Treasure Hunt (E)] - MATCH_ID("NRK", SAVE_TYPE_NONE, FEAT_CPAK), // Rugrats in Paris - The Movie - MATCH_ID("NRO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Road Rash 64 - MATCH_ID("NRP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rampage - World Tour - MATCH_ID("NRP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rampage 2 - Universal Tour - MATCH_ID("NRR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Roadster's Trophy - MATCH_ID("NRT", SAVE_TYPE_NONE, FEAT_CPAK), // Rat Attack - MATCH_ID("NRT", SAVE_TYPE_NONE, FEAT_CPAK), // Robotron 64 - MATCH_ID("NRU", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // San Francisco Rush 2049 - MATCH_ID("NRV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Re-Volt - MATCH_ID("NRW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok: Rage Wars - MATCH_ID("NS2", SAVE_TYPE_NONE, FEAT_CPAK), // Simcity 2000 - MATCH_ID("NSB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Twisted Edge - Extreme Snowboarding [King Hill 64 - Extreme Snowboarding (J)] - MATCH_ID("NSD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Shadow Man - MATCH_ID("NSF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // San Francisco Rush - Extreme Racing - MATCH_ID("NSG", SAVE_TYPE_NONE, FEAT_CPAK), // Shadowgate 64 - Trials Of The Four Towers - MATCH_ID("NSH", SAVE_TYPE_NONE, FEAT_CPAK), // Saikyou Habu Shougi (J) - MATCH_ID("NSK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Snowboard Kids [Snobow Kids (J)] - MATCH_ID("NSL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Spider-Man - MATCH_ID("NSO", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Showtime - NBA on NBC - MATCH_ID("NSP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Superman - MATCH_ID("NST", SAVE_TYPE_NONE, FEAT_CPAK), // Eikou no Saint Andrews - MATCH_ID("NSX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Supercross 2000 - MATCH_ID("NSY", SAVE_TYPE_NONE, FEAT_CPAK), // Scooby-Doo! - Classic Creep Capers - MATCH_ID("NSZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz - Special Edition - MATCH_ID("NT2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok 2 - Seeds of Evil [Violence Killer - Turok New Generation (J)] - MATCH_ID("NT3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater 3 - MATCH_ID("NT4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // CyberTiger - MATCH_ID("NTA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Disney's Tarzan - MATCH_ID("NTF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater - MATCH_ID("NTH", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Toy Story 2 - Buzz Lightyear to the Rescue! - MATCH_ID("NTI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WWF: Attitude - MATCH_ID("NTK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok 3 - Shadow of Oblivion - MATCH_ID("NTO", SAVE_TYPE_NONE, FEAT_CPAK), // Shin Nihon Pro Wrestling - Toukon Road - Brave Spirits (J) - MATCH_ID("NTQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater 2 - MATCH_ID("NTS", SAVE_TYPE_NONE, FEAT_CPAK), // Centre Court Tennis [Let's Smash (J)] - MATCH_ID("NTT", SAVE_TYPE_NONE, FEAT_CPAK), // Tonic Trouble - MATCH_ID("NTU", SAVE_TYPE_NONE, FEAT_CPAK), // Turok: Dinosaur Hunter [Turok: Jikuu Senshi (J)] - MATCH_ID("NV2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Chameleon Twist 2 - MATCH_ID("NV3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Micro Machines 64 Turbo - MATCH_ID("NV8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Vigilante 8 - MATCH_ID("NVC", SAVE_TYPE_NONE, FEAT_CPAK), // Virtual Chess 64 - MATCH_ID("NVG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Vigilante 8 - Second Offense - MATCH_ID("NVR", SAVE_TYPE_NONE, FEAT_CPAK), // Virtual Pool 64 - MATCH_ID("NW3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Nitro - MATCH_ID("NW8", SAVE_TYPE_NONE, FEAT_CPAK), // Wayne Gretzky's 3D Hockey '98 - MATCH_ID("NWB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Iggy's Reckin' Balls [Iggy-kun no Bura Bura Poyon (J)] - MATCH_ID("NWD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Winback - Covert Operations - MATCH_ID("NWF", SAVE_TYPE_NONE, FEAT_RPAK), // Wheel of Fortune - MATCH_ID("NWG", SAVE_TYPE_NONE, FEAT_CPAK), // Wayne Gretzky's 3D Hockey - MATCH_ID("NWI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // ECW Hardcore Revolution - MATCH_ID("NWK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Michael Owens WLS 2000 [World League Soccer 2000 (E) / Telefoot Soccer 2000 (F)] - MATCH_ID("NWM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Mayhem - MATCH_ID("NWN", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW vs. nWo - World Tour - MATCH_ID("NWO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // World Driver Championship - MATCH_ID("NWP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Wipeout 64 - MATCH_ID("NWS", SAVE_TYPE_NONE, FEAT_CPAK), // International Superstar Soccer '98 [Jikkyo World Soccer - World Cup France '98 (J)] - MATCH_ID("NWV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Backstage Assault - MATCH_ID("NWW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WWF: War Zone - MATCH_ID("NWZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone 2000 - MATCH_ID("NX2", SAVE_TYPE_NONE, FEAT_CPAK), // Gex 64 - Enter the Gecko - MATCH_ID("NX3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gex 3 - Deep Cover Gecko - MATCH_ID("NXF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Xena Warrior Princess - The Talisman of Fate - MATCH_ID("NXG", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Hangtime - MATCH_ID("NY2", SAVE_TYPE_NONE, FEAT_CPAK), // Rayman 2 - The Great Escape - MATCH_ID("NYP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Paperboy - MATCH_ID("NYW", SAVE_TYPE_NONE, FEAT_CPAK), // Bokujou Monogatari 2 - MATCH_ID("NZO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battlezone - Rise of the Black Dogs + MATCH_ID("N22", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - Round 2 + MATCH_ID("N2M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2002 + MATCH_ID("N32", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes 2 + MATCH_ID("N3P", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Triple Play 2000 + MATCH_ID("N64", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Kira to Kaiketsu! 64 Tanteidan + MATCH_ID("N7I", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA Soccer 64 [FIFA 64 (E)] + MATCH_ID("N8I", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA - Road to World Cup 98 [World Cup e no Michi (J)] + MATCH_ID("N8M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 64 + MATCH_ID("N8W", SAVE_TYPE_NONE, FEAT_CPAK), // World Cup '98 + MATCH_ID("N9B", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Live '99 + MATCH_ID("N9C", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nascar '99 + MATCH_ID("N9F", SAVE_TYPE_NONE, FEAT_CPAK), // FIFA 99 + MATCH_ID("N9H", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL '99 + MATCH_ID("N9M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football '99 + MATCH_ID("NAC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Air Combat + MATCH_ID("NAH", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Asteroids Hyper 64 + MATCH_ID("NAI", SAVE_TYPE_NONE, FEAT_CPAK), // Midway's Greatest Arcade Hits Volume 1 + MATCH_ID("NAM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes + MATCH_ID("NAR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Armorines - Project S.W.A.R.M. + MATCH_ID("NAS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball 2001 + MATCH_ID("NAY", SAVE_TYPE_NONE, FEAT_CPAK), // Aidyn Chronicles - The First Mage + MATCH_ID("NB2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone '99 [NBA Pro '99 (E)] + MATCH_ID("NB3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bust-A-Move '99 [Bust-A-Move 3 DX (E)] + MATCH_ID("NB4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bass Masters 2000 + MATCH_ID("NB8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Beetle Adventure Racing (J) + MATCH_ID("NB9", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Jam '99 + MATCH_ID("NBA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone '98 [NBA Pro '98 (E)] + MATCH_ID("NBE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball 2000 + MATCH_ID("NBF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bio F.R.E.A.K.S. + MATCH_ID("NBI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz 2000 + MATCH_ID("NBJ", SAVE_TYPE_NONE, FEAT_CPAK), // Bakushou Jinsei 64 - Mezase! Resort Ou + MATCH_ID("NBL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Buck Bumble + MATCH_ID("NBO", SAVE_TYPE_NONE, FEAT_CPAK), // Bottom of the 9th + MATCH_ID("NBP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Blues Brothers 2000 + MATCH_ID("NBQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battletanx - Global Assault + MATCH_ID("NBR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Milo's Astro Lanes + MATCH_ID("NBS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // All-Star Baseball '99 + MATCH_ID("NBU", SAVE_TYPE_NONE, FEAT_CPAK), // Bust-A-Move 2 - Arcade Edition + MATCH_ID("NBW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Super Bowling + MATCH_ID("NBX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battletanx + MATCH_ID("NBY", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Bug's Life, A + MATCH_ID("NBZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz + MATCH_ID("NCB", SAVE_TYPE_NONE, FEAT_RPAK), // Charlie Blast's Territory + MATCH_ID("NCD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Carmageddon 64 + MATCH_ID("NCE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nuclear Strike 64 + MATCH_ID("NCL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // California Speed + MATCH_ID("NCO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Jeremy McGrath Supercross 2000 + MATCH_ID("NCS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // S.C.A.R.S. + MATCH_ID("NDC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // SD Hiryuu no Ken Densetsu (J) + MATCH_ID("NDE", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Destruction Derby 64 + MATCH_ID("NDF", SAVE_TYPE_NONE, FEAT_RPAK), // Dance Dance Revolution - Disney Dancing Museum + MATCH_ID("NDH", SAVE_TYPE_NONE, FEAT_CPAK), // Duel Heroes + MATCH_ID("NDM", SAVE_TYPE_NONE, FEAT_CPAK), // Doom 64 + MATCH_ID("NDN", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Duke Nukem 64 + MATCH_ID("NDQ", SAVE_TYPE_NONE, FEAT_CPAK), // Disney's Donald Duck - Goin' Quackers [Quack Attack (E)] + MATCH_ID("NDS", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Dynamite Soccer 64 + MATCH_ID("NDT", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // South Park + MATCH_ID("NDW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Daikatana, John Romero's + MATCH_ID("NDZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Duke Nukem - Zero Hour + MATCH_ID("NEG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Extreme-G + MATCH_ID("NET", SAVE_TYPE_NONE, FEAT_CPAK), // Quest 64 [Eltale Monsters (J) Holy Magic Century (E)] + MATCH_ID("NF9", SAVE_TYPE_NONE, FEAT_CPAK), // Fox Sports College Hoops '99 + MATCH_ID("NFB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz 2001 + MATCH_ID("NFD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Flying Dragon + MATCH_ID("NFF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Fighting Force 64 + MATCH_ID("NFL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2001 + MATCH_ID("NFO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Forsaken 64 + MATCH_ID("NFQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Razor Freestyle Scooter + MATCH_ID("NFR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // F-1 Racing Championship + MATCH_ID("NFS", SAVE_TYPE_NONE, FEAT_CPAK), // Famista 64 + MATCH_ID("NG2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Extreme-G XG2 + MATCH_ID("NG5", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ganbare Goemon - Neo Momoyama Bakufu no Odori [Mystical Ninja Starring Goemon] + MATCH_ID("NGA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Deadly Arts [G.A.S.P!! Fighter's NEXTream (E-J)] + MATCH_ID("NGB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Hyper Bike + MATCH_ID("NGD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gauntlet Legends (J) + MATCH_ID("NGM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Goemon's Great Adventure [Mystical Ninja 2 Starring Goemon] + MATCH_ID("NGN", SAVE_TYPE_NONE, FEAT_CPAK), // Golden Nugget 64 + MATCH_ID("NGR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Rally (U) + MATCH_ID("NGS", SAVE_TYPE_NONE, FEAT_CPAK), // Jikkyou G1 Stable + MATCH_ID("NGX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gauntlet Legends + MATCH_ID("NH5", SAVE_TYPE_NONE, FEAT_CPAK), // Nagano Winter Olympics '98 [Hyper Olympics in Nagano (J)] + MATCH_ID("NH9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Breakaway '99 + MATCH_ID("NHC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hercules - The Legendary Journeys + MATCH_ID("NHG", SAVE_TYPE_NONE, FEAT_CPAK), // F-1 Pole Position 64 + MATCH_ID("NHG", SAVE_TYPE_NONE, FEAT_CPAK), // Human Grand Prix - New Generation + MATCH_ID("NHK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hiryuu no Ken Twin + MATCH_ID("NHL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Breakaway '98 + MATCH_ID("NHM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mia Hamm Soccer 64 + MATCH_ID("NHN", SAVE_TYPE_NONE, FEAT_CPAK), // Olympic Hockey Nagano '98 + MATCH_ID("NHO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NHL Blades of Steel '99 [NHL Pro '99 (E)] + MATCH_ID("NHS", SAVE_TYPE_NONE, FEAT_CPAK), // Hamster Monogatari 64 + MATCH_ID("NHT", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hydro Thunder + MATCH_ID("NHV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hybrid Heaven (U + E) + MATCH_ID("NHW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Hot Wheels Turbo Racing + MATCH_ID("NHX", SAVE_TYPE_NONE, FEAT_CPAK), // Hexen + MATCH_ID("NIS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // International Superstar Soccer 2000 + MATCH_ID("NIV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Space Invaders + MATCH_ID("NJ2", SAVE_TYPE_NONE, FEAT_CPAK), // Wonder Project J2 - Koruro no Mori no Jozet (J) + MATCH_ID("NJ3", SAVE_TYPE_NONE, FEAT_CPAK), // Jikkyou World Soccer 3 + MATCH_ID("NJA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Jam 2000 + MATCH_ID("NJE", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Eleven Beat 1997 + MATCH_ID("NJL", SAVE_TYPE_NONE, FEAT_CPAK), // J.League Live 64 + MATCH_ID("NJP", SAVE_TYPE_NONE, FEAT_CPAK), // International Superstar Soccer 64 [Jikkyo J-League Perfect Striker (J)] + MATCH_ID("NJQ", SAVE_TYPE_NONE, FEAT_RPAK), // Batman Beyond - Return of the Joker [Batman of the Future - Return of the Joker (E)] + MATCH_ID("NKE", SAVE_TYPE_NONE, FEAT_RPAK), // Knife Edge - Nose Gunner + MATCH_ID("NKK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Knockout Kings 2000 + MATCH_ID("NKM", SAVE_TYPE_NONE, FEAT_CPAK), // Pro Mahjong Kiwame 64 (J) + MATCH_ID("NKR", SAVE_TYPE_NONE, FEAT_CPAK), // Rakuga Kids (E) + MATCH_ID("NL2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Top Gear Rally 2 + MATCH_ID("NLC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Automobili Lamborghini [Super Speed Race 64 (J)] + MATCH_ID("NLG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // LEGO Racers + MATCH_ID("NM3", SAVE_TYPE_NONE, FEAT_RPAK), // Monster Truck Madness 64 + MATCH_ID("NM4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mortal Kombat 4 + MATCH_ID("NM9", SAVE_TYPE_NONE, FEAT_CPAK), // Harukanaru Augusta Masters 98 + MATCH_ID("NMA", SAVE_TYPE_NONE, FEAT_CPAK), // Jangou Simulation Mahjong Do 64 + MATCH_ID("NMB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mike Piazza's Strike Zone + MATCH_ID("NMD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2000 + MATCH_ID("NMJ", SAVE_TYPE_NONE, FEAT_CPAK), // Mahjong 64 + MATCH_ID("NMM", SAVE_TYPE_NONE, FEAT_CPAK), // Mahjong Master + MATCH_ID("NMT", SAVE_TYPE_NONE, FEAT_RPAK), // Magical Tetris Challenge + MATCH_ID("NMY", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Mortal Kombat Mythologies - Sub-Zero + MATCH_ID("NN2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nascar 2000 + MATCH_ID("NNC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Nightmare Creatures + MATCH_ID("NNL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA Live 2000 + MATCH_ID("NNM", SAVE_TYPE_NONE, FEAT_CPAK), // Namco Museum 64 + MATCH_ID("NNR", SAVE_TYPE_NONE, FEAT_CPAK), // Pro Mahjong Tsuwamono 64 - Jansou Battle ni Chousen (J) + MATCH_ID("NNS", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Beetle Adventure Racing + MATCH_ID("NO7", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // The World Is Not Enough + MATCH_ID("NOF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Offroad Challenge + MATCH_ID("NOH", SAVE_TYPE_NONE, FEAT_RPAK | FEAT_TPAK), // Transformers Beast Wars - Transmetals + MATCH_ID("NOM", SAVE_TYPE_NONE, FEAT_CPAK), // Onegai Monsters + MATCH_ID("NOW", SAVE_TYPE_NONE, FEAT_CPAK), // Brunswick Circuit Pro Bowling + MATCH_ID("NP9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ms. Pac-Man - Maze Madness + MATCH_ID("NPB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Puzzle Bobble 64 (J) + MATCH_ID("NPC", SAVE_TYPE_NONE, FEAT_CPAK), // Pachinko 365 Nichi (J) + MATCH_ID("NPK", SAVE_TYPE_NONE, FEAT_CPAK), // Chou Kuukan Night Pro Yakyuu King (J) + MATCH_ID("NPL", SAVE_TYPE_NONE, FEAT_CPAK), // Power League 64 (J) + MATCH_ID("NPR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // South Park Rally + MATCH_ID("NPU", SAVE_TYPE_NONE, FEAT_CPAK), // Power Rangers - Lightspeed Rescue + MATCH_ID("NPX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Polaris SnoCross + MATCH_ID("NPZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Susume! Taisen Puzzle Dama Toukon! Marumata Chou (J) + MATCH_ID("NQ2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Quake 2 + MATCH_ID("NQ8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club '98 + MATCH_ID("NQ9", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club '99 + MATCH_ID("NQB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club 2000 + MATCH_ID("NQC", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Quarterback Club 2001 + MATCH_ID("NQK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Quake 64 + MATCH_ID("NR2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rush 2 - Extreme Racing USA + MATCH_ID("NR3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Stunt Racer 64 + MATCH_ID("NR6", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tom Clancy's Rainbow Six + MATCH_ID("NRD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing + MATCH_ID("NRG", SAVE_TYPE_NONE, FEAT_RPAK), // Rugrats - Scavenger Hunt [Treasure Hunt (E)] + MATCH_ID("NRK", SAVE_TYPE_NONE, FEAT_CPAK), // Rugrats in Paris - The Movie + MATCH_ID("NRO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Road Rash 64 + MATCH_ID("NRP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rampage - World Tour + MATCH_ID("NRP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Rampage 2 - Universal Tour + MATCH_ID("NRR", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Roadster's Trophy + MATCH_ID("NRT", SAVE_TYPE_NONE, FEAT_CPAK), // Rat Attack + MATCH_ID("NRT", SAVE_TYPE_NONE, FEAT_CPAK), // Robotron 64 + MATCH_ID("NRU", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // San Francisco Rush 2049 + MATCH_ID("NRV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Re-Volt + MATCH_ID("NRW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok: Rage Wars + MATCH_ID("NS2", SAVE_TYPE_NONE, FEAT_CPAK), // Simcity 2000 + MATCH_ID("NSB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Twisted Edge - Extreme Snowboarding [King Hill 64 - Extreme Snowboarding (J)] + MATCH_ID("NSD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Shadow Man + MATCH_ID("NSF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // San Francisco Rush - Extreme Racing + MATCH_ID("NSG", SAVE_TYPE_NONE, FEAT_CPAK), // Shadowgate 64 - Trials Of The Four Towers + MATCH_ID("NSH", SAVE_TYPE_NONE, FEAT_CPAK), // Saikyou Habu Shougi (J) + MATCH_ID("NSK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Snowboard Kids [Snobow Kids (J)] + MATCH_ID("NSL", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Spider-Man + MATCH_ID("NSO", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Showtime - NBA on NBC + MATCH_ID("NSP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Superman + MATCH_ID("NST", SAVE_TYPE_NONE, FEAT_CPAK), // Eikou no Saint Andrews + MATCH_ID("NSX", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Supercross 2000 + MATCH_ID("NSY", SAVE_TYPE_NONE, FEAT_CPAK), // Scooby-Doo! - Classic Creep Capers + MATCH_ID("NSZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NFL Blitz - Special Edition + MATCH_ID("NT2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok 2 - Seeds of Evil [Violence Killer - Turok New Generation (J)] + MATCH_ID("NT3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater 3 + MATCH_ID("NT4", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // CyberTiger + MATCH_ID("NTA", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Disney's Tarzan + MATCH_ID("NTF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater + MATCH_ID("NTH", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Toy Story 2 - Buzz Lightyear to the Rescue! + MATCH_ID("NTI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WWF: Attitude + MATCH_ID("NTK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Turok 3 - Shadow of Oblivion + MATCH_ID("NTO", SAVE_TYPE_NONE, FEAT_CPAK), // Shin Nihon Pro Wrestling - Toukon Road - Brave Spirits (J) + MATCH_ID("NTQ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Tony Hawk's Pro Skater 2 + MATCH_ID("NTS", SAVE_TYPE_NONE, FEAT_CPAK), // Centre Court Tennis [Let's Smash (J)] + MATCH_ID("NTT", SAVE_TYPE_NONE, FEAT_CPAK), // Tonic Trouble + MATCH_ID("NTU", SAVE_TYPE_NONE, FEAT_CPAK), // Turok: Dinosaur Hunter [Turok: Jikuu Senshi (J)] + MATCH_ID("NV2", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Chameleon Twist 2 + MATCH_ID("NV3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Micro Machines 64 Turbo + MATCH_ID("NV8", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Vigilante 8 + MATCH_ID("NVC", SAVE_TYPE_NONE, FEAT_CPAK), // Virtual Chess 64 + MATCH_ID("NVG", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Vigilante 8 - Second Offense + MATCH_ID("NVR", SAVE_TYPE_NONE, FEAT_CPAK), // Virtual Pool 64 + MATCH_ID("NW3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Nitro + MATCH_ID("NW8", SAVE_TYPE_NONE, FEAT_CPAK), // Wayne Gretzky's 3D Hockey '98 + MATCH_ID("NWB", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Iggy's Reckin' Balls [Iggy-kun no Bura Bura Poyon (J)] + MATCH_ID("NWD", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Winback - Covert Operations + MATCH_ID("NWF", SAVE_TYPE_NONE, FEAT_RPAK), // Wheel of Fortune + MATCH_ID("NWG", SAVE_TYPE_NONE, FEAT_CPAK), // Wayne Gretzky's 3D Hockey + MATCH_ID("NWI", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // ECW Hardcore Revolution + MATCH_ID("NWK", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Michael Owens WLS 2000 [World League Soccer 2000 (E) / Telefoot Soccer 2000 (F)] + MATCH_ID("NWM", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Mayhem + MATCH_ID("NWN", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW vs. nWo - World Tour + MATCH_ID("NWO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // World Driver Championship + MATCH_ID("NWP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Wipeout 64 + MATCH_ID("NWS", SAVE_TYPE_NONE, FEAT_CPAK), // International Superstar Soccer '98 [Jikkyo World Soccer - World Cup France '98 (J)] + MATCH_ID("NWV", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WCW: Backstage Assault + MATCH_ID("NWW", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // WWF: War Zone + MATCH_ID("NWZ", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // NBA In the Zone 2000 + MATCH_ID("NX2", SAVE_TYPE_NONE, FEAT_CPAK), // Gex 64 - Enter the Gecko + MATCH_ID("NX3", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Gex 3 - Deep Cover Gecko + MATCH_ID("NXF", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Xena Warrior Princess - The Talisman of Fate + MATCH_ID("NXG", SAVE_TYPE_NONE, FEAT_CPAK), // NBA Hangtime + MATCH_ID("NY2", SAVE_TYPE_NONE, FEAT_CPAK), // Rayman 2 - The Great Escape + MATCH_ID("NYP", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Paperboy + MATCH_ID("NYW", SAVE_TYPE_NONE, FEAT_CPAK), // Bokujou Monogatari 2 + MATCH_ID("NZO", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Battlezone - Rise of the Black Dogs MATCH_END, }; @@ -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); diff --git a/src/menu/rom_info.h b/src/menu/rom_info.h index cb241a2e..731c6553 100644 --- a/src/menu/rom_info.h +++ b/src/menu/rom_info.h @@ -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; diff --git a/src/menu/settings.c b/src/menu/settings.c index 3e046ed0..0b372531 100644 --- a/src/menu/settings.c +++ b/src/menu/settings.c @@ -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); diff --git a/src/menu/sound.c b/src/menu/sound.c index 9e58d001..98d62748 100644 --- a/src/menu/sound.c +++ b/src/menu/sound.c @@ -3,14 +3,18 @@ #include #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(); diff --git a/src/menu/sound.h b/src/menu/sound.h index 44ecf6bc..8752a9b9 100644 --- a/src/menu/sound.h +++ b/src/menu/sound.h @@ -7,14 +7,26 @@ #ifndef SOUND_H__ #define SOUND_H__ +#include #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 diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index e7459661..3814e04f 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -1,11 +1,12 @@ +#include #include #include -#include #include #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); } } diff --git a/src/menu/views/credits.c b/src/menu/views/credits.c index dc1d0665..fb068e6b 100644 --- a/src/menu/views/credits.c +++ b/src/menu/views/credits.c @@ -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); } } diff --git a/src/menu/views/error.c b/src/menu/views/error.c index a5c0b84a..07eb70d4 100644 --- a/src/menu/views/error.c +++ b/src/menu/views/error.c @@ -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; } diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index 211f5791..496c07f4 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -1,4 +1,5 @@ #include +#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); } } diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index 084cc073..41c1f443 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -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" diff --git a/src/menu/views/image_viewer.c b/src/menu/views/image_viewer.c index 21c153b3..d58d3743 100644 --- a/src/menu/views/image_viewer.c +++ b/src/menu/views/image_viewer.c @@ -1,4 +1,5 @@ #include +#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); } } diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 7216e97d..0165bdc9 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -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(); + } } diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index 60f8d114..589a6a59 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -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); } } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 1d4f41be..c408b47c 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -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,52 +237,61 @@ static void draw (menu_t *menu, surface_t *d) { "\n" "\n" "\n" - " Endianness: %s\n" - " Title: %.20s\n" - " Game code: %c%c%c%c\n" - " Media type: %s\n" - " Destination market: %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", - format_rom_endianness(menu->load.rom_info.endianness), - menu->load.rom_info.title, - menu->load.rom_info.game_code[0], menu->load.rom_info.game_code[1], menu->load.rom_info.game_code[2], menu->load.rom_info.game_code[3], - format_rom_media_type(menu->load.rom_info.category_code), - format_rom_destination_market(menu->load.rom_info.destination_code), - menu->load.rom_info.version, - menu->load.rom_info.check_code, - format_rom_save_type(rom_info_get_save_type(&menu->load.rom_info)), - format_rom_tv_type(rom_info_get_tv_type(&menu->load.rom_info)), + "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)), - 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 + 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: Exit" + "B: Back" ); component_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, - "\n" - "R: Options" + "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" + "Variant: %s\n" + "Version: %hhu\n" + "Check code: 0x%016llX\n" + "Boot address: 0x%08lX\n" + "SDK version: %.1f%c\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], + format_rom_media_type(menu->load.rom_info.category_code), + format_rom_destination_market(menu->load.rom_info.destination_code), + menu->load.rom_info.version, + menu->load.rom_info.check_code, + 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_context_menu_draw(&options_context_menu); } diff --git a/src/menu/views/music_player.c b/src/menu/views/music_player.c index 7b3d9751..4de00756 100644 --- a/src/menu/views/music_player.c +++ b/src/menu/views/music_player.c @@ -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); diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index caeb9305..ae56fd8b 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -1,4 +1,5 @@ #include +#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" ); diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 5e9d1854..55feffea 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -1,3 +1,6 @@ +#include +#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) { diff --git a/src/menu/views/system_info.c b/src/menu/views/system_info.c index 02b78f72..74ed4cbd 100644 --- a/src/menu/views/system_info.c +++ b/src/menu/views/system_info.c @@ -1,5 +1,6 @@ #include +#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); } } diff --git a/src/menu/views/text_viewer.c b/src/menu/views/text_viewer.c index 6b688479..cf8b505f 100644 --- a/src/menu/views/text_viewer.c +++ b/src/menu/views/text_viewer.c @@ -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); diff --git a/src/utils/fs.c b/src/utils/fs.c index 41b69c48..b02b2e2c 100644 --- a/src/utils/fs.c +++ b/src/utils/fs.c @@ -1,7 +1,7 @@ +#include #include #include #include -#include #include #include "fs.h"