Merge branch 'develop' into add-rom-patcher

This commit is contained in:
Robin Jones 2024-11-07 20:39:12 +00:00 committed by GitHub
commit 106b8413f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 1273 additions and 423 deletions

View File

@ -1,6 +1,6 @@
FROM debian:bookworm-slim
ARG SC64_DEPLOYER_VERSION=v2.18.0
ARG SC64_DEPLOYER_VERSION=v2.20.0
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install build-essential doxygen git python3 wget -y && \

View File

@ -113,7 +113,7 @@ jobs:
- uses: actions/checkout@v4
- name: Run Doxygen
uses: mattnotmitt/doxygen-action@1.9.5
uses: mattnotmitt/doxygen-action@v1
with:
doxyfile-path: './Doxyfile'

View File

@ -100,7 +100,6 @@ $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fc
$(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))
$(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf

View File

@ -26,6 +26,7 @@ An open source menu for N64 flashcarts.
* Real Time Clock support.
* Music playback (MP3).
* Menu sound effects.
* N64 ROM autoload.
## Documentation
@ -49,22 +50,51 @@ An open source menu for N64 flashcarts.
## Experimental features
These features are subject to change:
### ROM Boxart
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
### N64 ROM autoload
To use the autoload function, while on the `N64 ROM information` display, press the `R` button on your joypad and select the `Set ROM to autoload` option. When you restart the console, it will now only load the selected ROM rather than the menu.
NOTE: to return to the menu, hold joypad `start` button whilst powering on the console.
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`.
You can download these boxart packs:
### GamePak sprites
To use N64 `GamePak` sprites, place `PNG` files within the `sd:/menu/boxart/` folder.
[American Boxart](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w)
[European Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M)
#### Supported sprites
These must be `PNG` files that use the following dimensions:
* Standard N64 GamePak boxart sprites: 158x112
* Japanese N64 GamePak boxart sprites: 112x158
* 64DD boxart sprites: 129x112
[64DD Boxart](https://mega.nz/file/O3JzwD7B#BYl1aV-pbrJ-MxWUbM_K0yGVIRbmSoxJJZqQInRzZyM)
They will be loaded by directories using each character (case-sensitive) of the full 4 character Game Code (as identified in the menu ROM information).
i.e. for GoldenEye NTSC USA (NGEE), this would be `sd:/menu/boxart/N/G/E/E/boxart_front.png`.
i.e. for GoldenEye PAL (NGEP), this would be `sd:/menu/boxart/N/G/E/P/boxart_front.png`.
To improve compatibility between regions (as a fallback), you may exclude the region ID (last matched directory) for GamePaks to match with 3 letter IDs instead:
i.e. for GoldenEye, this would be `sd:/menu/boxart/N/G/E/boxart_front.png`.
**Note1:** Excluding the region ID may show the wrong boxart.
**Note2:** For future support, boxart sprites should also include: `boxart_back.png`, `boxart_top.png`, `boxart_bottom.png`, `boxart_left.png`, `boxart_right.png`.
As a starting point, here is a link to a boxart pack following the new structure, including `boxart_front.png` and failback images:
* [Link](https://drive.google.com/file/d/1IpCmFqmGgGwKKmlRBxYObfFR9XywaC6n/view?usp=drive_link)
#### Compatibilty mode
If you cannot yet satisfy the correct boxart layout, The menu still has **deprecated** support for filenames containing the Game ID.
**Note:** This will add a noticeable delay for displaying parts of the menu.
Each file must be named according to the 2,3 or 4 letter GamePak ID (matched in this order).
i.e.
* for GoldenEye 4 letters, this would be `sd:/menu/boxart/NGEE.png` and/or `sd:/menu/boxart/NGEP.png`.
* for GoldenEye 3 letters, this would be `sd:/menu/boxart/NGE.png`.
* for GoldenEye 2 letters, this would be `sd:/menu/boxart/GE.png`.
As a starting point, here are some links to boxart packs:
* [Japan Boxart](https://mega.nz/file/KyJR0B6B#ERabLautAVPaqJTIdBSv4ghbudNhK7hnEr2ZS1Q6ub0)
* [American Boxart](https://mega.nz/file/rugAFYSQ#JHfgCU2amzNVpC4S6enP3vg--wtAAwsziKa7cej6QCc)
* [European Boxart](https://mega.nz/file/OmIV3aAK#kOWdutK1_41ffN64R6thbU7HEPR_M9qO0YM2mNG6RbQ)
* [64DD Boxart](https://mega.nz/file/ay5wQIxJ#k3PF-VMLrZJxJTr-BOaOKa2TBIK7c2t4zwbdshsQl40)
### Menu Settings

View File

@ -1,11 +1,19 @@
## 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*).
Using your PC, insert the SD card and ensure it is formatted for compatibility with your flashcart
#### SC64
- FAT32 and EXFAT are fully supported.
- An SD formatted with 128 kiB cluster size is recommended.
- 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`**).
#### Other supported flashcarts
- FAT32 recommended.
- An SD formatted with default cluster size is recommended.
### Emulator support
Emulators should be added to the `/menu/emulators` directory on the SD card.
@ -36,7 +44,7 @@ SD:\
│ │ ├── NDDJ2.n64
│ │ └── NDXJ0.n64
│ │
│ └── emulators
│ └── emulators\
│ ├── neon64bu.rom
│ ├── sodium64.z64
│ ├── gb.v64

View File

@ -8,11 +8,11 @@ You can use a dev container in VSCode to ease development.
### To deploy:
#### SC64
* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.18.0/sc64-deployer-windows-v2.18.0.zip)
* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.20.0/sc64-deployer-windows-v2.20.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)
Make sure that your firmware is compatible (currently v2.20.0+)
See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.20.0/docs/00_quick_startup_guide.md#firmware-backupupdate)
##### From the devcontainer
It is not currently possible to directly communicate with USB devices.
@ -45,10 +45,13 @@ For ease of development and debugging, the menu ROM can run in the [Ares emulato
* 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.
## Update submodules
To update to the latest version, use `git submodule update --remote` from the terminal.
### libdragon
This repo currently uses the `preview` branch as a submodule at a specific commit.
* To ensure your local instance is building against it, use `cd ./libdragon && make clobber -j && make libdragon tools -j && make install tools-install -j && cd ..`
## Generate documentation
Run `doxygen` from the dev container terminal.
Make sure you fix the warnings before creating a PR!
@ -57,6 +60,8 @@ Generated documentation is located in the `output/docs` folder and auto-publishe
Once merged, they can be viewed [here](https://polprzewodnikowy.github.io/N64FlashcartMenu/)
### Test generated docs in the dev-container
Testing the documentation locally allows you to preview changes and ensure everything renders correctly before submitting your changes.
Install Prerequisites:
```bash
apt-get install ruby-full build-essential zlib1g-dev

@ -1 +1 @@
Subproject commit 9bae49994bf1c796f9939ea1aa7c133813b9053f
Subproject commit 5295016230d657cd6c7fce5b6ed4a342538e09f5

View File

@ -95,7 +95,7 @@ void boot (boot_params_t *params) {
io32_t *reboot_dst = SP_MEM->IMEM;
size_t reboot_instructions = (size_t) (&reboot_size) / sizeof(uint32_t);
for (int i = 0; i < reboot_instructions; i++) {
for (unsigned int i = 0; i < reboot_instructions; i++) {
cpu_io_write(&reboot_dst[i], reboot_src[i]);
}

View File

@ -7,41 +7,79 @@
#ifndef BOOT_IO_H__
#define BOOT_IO_H__
#include <stddef.h>
#include <stdint.h>
/**
* @typedef io8_t
* @brief 8-bit volatile IO type.
*/
typedef volatile uint8_t io8_t;
/**
* @typedef io32_t
* @brief 32-bit volatile IO type.
*/
typedef volatile uint32_t io32_t;
/**
* @brief Convert an address to its uncached equivalent.
*
* This macro takes an address and converts it to its uncached equivalent
* by setting the appropriate bits.
*
* @param address The address to convert.
* @return The uncached equivalent of the address.
*/
#define UNCACHED(address) ((typeof(address)) (((io32_t) (address)) | (0xA0000000UL)))
/** @brief Memory Structure. */
/**
* @brief Memory Structure.
*
* This structure represents the memory layout for the SP (Signal Processor),
* containing both Data Memory (DMEM) and Instruction Memory (IMEM).
*/
typedef struct {
io32_t DMEM[1024];
io32_t IMEM[1024];
io32_t DMEM[1024]; /**< Data Memory (DMEM) array of 1024 32-bit words. */
io32_t IMEM[1024]; /**< Instruction Memory (IMEM) array of 1024 32-bit words. */
} sp_mem_t;
/**
* @brief Base address for SP memory.
*/
#define SP_MEM_BASE (0x04000000UL)
/**
* @brief Pointer to the SP memory structure.
*/
#define SP_MEM ((sp_mem_t *) SP_MEM_BASE)
/** @brief SP Registers Structure. */
/**
* @brief SP Registers Structure.
*
* This structure represents the registers for the SP (Signal Processor).
*/
typedef struct {
io32_t PADDR;
io32_t MADDR;
io32_t RD_LEN;
io32_t WR_LEN;
io32_t SR;
io32_t DMA_FULL;
io32_t DMA_BUSY;
io32_t SEMAPHORE;
io32_t PADDR; /**< Physical Address Register. */
io32_t MADDR; /**< Memory Address Register. */
io32_t RD_LEN; /**< Read Length Register. */
io32_t WR_LEN; /**< Write Length Register. */
io32_t SR; /**< Status Register. */
io32_t DMA_FULL; /**< DMA Full Register. */
io32_t DMA_BUSY; /**< DMA Busy Register. */
io32_t SEMAPHORE; /**< Semaphore Register. */
io32_t __reserved[0xFFF8];
io32_t PC;
} sp_regs_t;
/**
* @brief Base address for SP registers.
*/
#define SP_BASE (0x04040000UL)
/**
* @brief Pointer to the SP registers structure.
*/
#define SP ((sp_regs_t *) SP_BASE)
#define SP_SR_HALT (1 << 0)
@ -85,7 +123,6 @@ typedef struct {
#define SP_SR_CLR_SIG7 (1 << 23)
#define SP_SR_SET_SIG7 (1 << 24)
/** @brief DPC Registers Structure. */
typedef struct {
io32_t START;
@ -123,7 +160,6 @@ typedef struct {
#define DPC_SR_CLR_CMD_CTR (1 << 8)
#define DPC_SR_CLR_CLOCK_CTR (1 << 9)
/** @brief Video Interface Registers Structure. */
typedef struct {
/** @brief The Control Register. */
@ -198,7 +234,6 @@ typedef struct {
#define AI_SR_FIFO_FULL (1 << 31)
#define AI_CR_DMA_ON (1 << 0)
/** @brief Peripheral Interface Register Structure. */
typedef struct {
/** @brief The Memory Address. */
@ -233,15 +268,12 @@ typedef struct {
#define PI_SR_RESET (1 << 0)
#define PI_SR_CLR_INTR (1 << 1)
#define ROM_DDIPL_BASE (0x06000000UL)
#define ROM_DDIPL ((io32_t *) ROM_DDIPL_BASE)
#define ROM_CART_BASE (0x10000000UL)
#define ROM_CART ((io32_t *) ROM_CART_BASE)
static inline uint32_t cpu_io_read (io32_t *address) {
io32_t *uncached = UNCACHED(address);
uint32_t value = *uncached;
@ -253,5 +285,4 @@ static inline void cpu_io_write (io32_t *address, uint32_t value) {
*uncached = value;
}
#endif
#endif /* BOOT_IO_H__ */

View File

@ -3,37 +3,47 @@
#include <stdint.h>
/**
* @brief VR4300 Instruction Structure
*
* This structure represents a VR4300 instruction, which can be of different types (R-type, I-type, J-type, etc.).
*/
typedef union {
uint32_t raw;
uint32_t raw; /**< Raw 32-bit instruction */
struct {
uint32_t op : 6;
uint32_t rs : 5;
uint32_t rt : 5;
uint32_t imm : 16;
} i_type;
uint32_t op : 6; /**< Opcode field */
uint32_t rs : 5; /**< Source register */
uint32_t rt : 5; /**< Target register */
uint32_t imm : 16; /**< Immediate value */
} i_type; /**< I-type instruction format */
struct {
uint32_t op : 6;
uint32_t target : 26;
} j_type;
uint32_t op : 6; /**< Opcode field */
uint32_t target : 26; /**< Target Address field */
} j_type; /**< J-type instruction format */
struct {
uint32_t op : 6;
uint32_t rs : 5;
uint32_t rt : 5;
uint32_t rd : 5;
uint32_t sa : 5;
uint32_t funct : 6;
} r_type;
uint32_t op : 6; /**< Opcode field */
uint32_t rs : 5; /**< Source register */
uint32_t rt : 5; /**< Target register */
uint32_t rd : 5; /**< Destination register */
uint32_t sa : 5; /**< Shift amount */
uint32_t funct : 6; /**< Function field */
} r_type; /**< Alternate R-type instruction format */
struct {
uint32_t op : 6;
uint32_t co : 1;
uint32_t funct : 25;
} c_type;
uint32_t op : 6; /**< Opcode field */
uint32_t co : 1; /**< Coprocessor operation bit */
uint32_t funct : 25; /**< Function field */
} c_type; /**< C-type instruction format */
} vr4300_instruction_t;
/**
* @brief VR4300 Opcode Enumeration
*
* Enumeration for different opcodes used in VR4300 instructions.
*/
typedef enum {
OP_SPECIAL,
OP_REGIMM,
@ -394,4 +404,4 @@ typedef enum {
#define I_SRL(rd, rt, sa) __ASM_R_INST(OP_SPECIAL, 0, rt, rd, sa, FUNCT_SRL)
#define I_SW(rt, offset, base) __ASM_I_INST(OP_SW, base, rt, offset)
#endif
#endif /* VR4300_ASM_H__ */

View File

@ -75,6 +75,9 @@ static bool d64_has_feature (flashcart_features_t feature) {
case FLASHCART_FEATURE_64DD: return false;
case FLASHCART_FEATURE_RTC: return true;
case FLASHCART_FEATURE_USB: return true;
case FLASHCART_FEATURE_AUTO_CIC: return true;
case FLASHCART_FEATURE_AUTO_REGION: return true;
case FLASHCART_FEATURE_SAVE_WRITEBACK: return true;
default: return false;
}
}
@ -99,7 +102,7 @@ static flashcart_err_t d64_load_rom (char *rom_path, flashcart_progress_callback
size_t sdram_size = MiB(64);
size_t chunk_size = KiB(128);
for (int offset = 0; offset < sdram_size; offset += chunk_size) {
for (unsigned int offset = 0; offset < sdram_size; offset += chunk_size) {
size_t block_size = MIN(sdram_size - offset, chunk_size);
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
f_close(&fil);

View File

@ -1,4 +1,4 @@
## 64drive developer notes
# 64drive developer notes
### Official documentation

View File

@ -29,6 +29,11 @@ typedef enum {
FLASHCART_FEATURE_64DD,
FLASHCART_FEATURE_RTC,
FLASHCART_FEATURE_USB,
FLASHCART_FEATURE_AUTO_CIC,
FLASHCART_FEATURE_AUTO_REGION,
FLASHCART_FEATURE_DIAGNOSTIC_DATA,
FLASHCART_FEATURE_BIOS_UPDATE_FROM_MENU,
FLASHCART_FEATURE_SAVE_WRITEBACK
} flashcart_features_t;
/** @brief Flashcart save type enumeration */

View File

@ -1,4 +1,4 @@
## SummerCart64 developer notes
# SummerCart64 developer notes
### Official documentation

View File

@ -232,7 +232,7 @@ static flashcart_err_t sc64_init (void) {
{ CFG_ID_ROM_EXTENDED_ENABLE, false },
};
for (int i = 0; i < sizeof(default_config) / sizeof(default_config[0]); i++) {
for (unsigned int i = 0; i < sizeof(default_config) / sizeof(default_config[0]); i++) {
if (sc64_ll_set_config(default_config[i].id, default_config[i].value) != SC64_OK) {
return FLASHCART_ERR_INT;
}
@ -254,6 +254,10 @@ static bool sc64_has_feature (flashcart_features_t feature) {
case FLASHCART_FEATURE_64DD: return true;
case FLASHCART_FEATURE_RTC: return true;
case FLASHCART_FEATURE_USB: return true;
case FLASHCART_FEATURE_AUTO_CIC: return true;
case FLASHCART_FEATURE_AUTO_REGION: return true;
case FLASHCART_FEATURE_DIAGNOSTIC_DATA: return true;
case FLASHCART_FEATURE_SAVE_WRITEBACK: return true;
default: return false;
}
}
@ -283,7 +287,7 @@ static flashcart_err_t sc64_load_rom (char *rom_path, flashcart_progress_callbac
size_t extended_size = extended_enabled ? rom_size - MiB(64) : 0;
size_t chunk_size = KiB(128);
for (int offset = 0; offset < sdram_size; offset += chunk_size) {
for (unsigned int offset = 0; offset < sdram_size; offset += chunk_size) {
size_t block_size = MIN(sdram_size - offset, chunk_size);
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
f_close(&fil);
@ -445,7 +449,7 @@ static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_ca
}
size_t chunk_size = KiB(128);
for (int offset = 0; offset < ipl_size; offset += chunk_size) {
for (unsigned int offset = 0; offset < ipl_size; offset += chunk_size) {
size_t block_size = MIN(ipl_size - offset, chunk_size);
if (f_read(&fil, (void *) (IPL_ADDRESS + offset), block_size, &br) != FR_OK) {
f_close(&fil);
@ -499,7 +503,7 @@ static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_para
{ CFG_ID_BUTTON_MODE, BUTTON_MODE_DD_DISK_SWAP },
};
for (int i = 0; i < sizeof(config) / sizeof(config[0]); i++) {
for (unsigned int i = 0; i < sizeof(config) / sizeof(config[0]); i++) {
if (sc64_ll_set_config(config[i].id, config[i].value) != SC64_OK) {
return FLASHCART_ERR_INT;
}

2
src/libs/miniz vendored

@ -1 +1 @@
Subproject commit 16413c213de38e703d883006193734e8b1178d5d
Subproject commit 35528ad769143b9ed38a95a22d460b963e39f278

View File

@ -25,8 +25,16 @@ static void actions_clear (menu_t *menu) {
}
static void actions_update_direction (menu_t *menu) {
joypad_8way_t held_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_DPAD | JOYPAD_2D_STICK);
joypad_8way_t fast_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_C);
joypad_8way_t held_dir = JOYPAD_8WAY_NONE;
joypad_8way_t fast_dir = JOYPAD_8WAY_NONE;
JOYPAD_PORT_FOREACH (i) {
held_dir = joypad_get_direction(i, JOYPAD_2D_DPAD | JOYPAD_2D_STICK);
fast_dir = joypad_get_direction(i, JOYPAD_2D_C);
if (held_dir != JOYPAD_8WAY_NONE || fast_dir != JOYPAD_8WAY_NONE) {
break;
}
}
if (fast_dir != JOYPAD_8WAY_NONE) {
held_dir = fast_dir;
@ -82,7 +90,14 @@ static void actions_update_direction (menu_t *menu) {
}
static void actions_update_buttons (menu_t *menu) {
joypad_buttons_t pressed = joypad_get_buttons_pressed(JOYPAD_PORT_1);
joypad_buttons_t pressed = {0};
JOYPAD_PORT_FOREACH (i) {
pressed = joypad_get_buttons_pressed(i);
if (pressed.raw) {
break;
}
}
if (pressed.a) {
menu->actions.enter = true;
@ -98,6 +113,12 @@ static void actions_update_buttons (menu_t *menu) {
}
void actions_init (void) {
JOYPAD_PORT_FOREACH (port) {
joypad_set_rumble_active(port, false);
}
}
void actions_update (menu_t *menu) {
joypad_poll();

View File

@ -10,7 +10,10 @@
#include "menu_state.h"
/**
* @brief Initialize the actions module
*/
void actions_init (void);
void actions_update (menu_t *menu);

View File

@ -1,74 +1,261 @@
/**
* @file components.h
* @brief Menu Components
* @brief Menu Graphical User Interface Components
* @ingroup menu
*/
#ifndef COMPONENTS_H__
#define COMPONENTS_H__
#include <libdragon.h>
#include "menu_state.h"
/**
* @addtogroup
* @{ menu_components
* @addtogroup menu_ui_components
* @{
*/
/**
* @brief File image Enumeration.
*
* Enumeration for different types of file images used in the GUI.
*/
typedef enum {
IMAGE_BOXART_FRONT, /**< Boxart image from the front */
IMAGE_BOXART_BACK, /**< Boxart image from the back */
IMAGE_BOXART_TOP, /**< Boxart image from the top */
IMAGE_BOXART_BOTTOM, /**< Boxart image from the bottom */
IMAGE_BOXART_LEFT, /**< Boxart image from the left side */
IMAGE_BOXART_RIGHT, /**< Boxart image from the right side */
IMAGE_GAMEPAK_FRONT, /**< GamePak image from the front */
IMAGE_GAMEPAK_BACK, /**< GamePak image from the back */
IMAGE_THUMBNAIL, /**< File image thumbnail */
IMAGE_TYPE_END /**< List end marker */
} file_image_type_t;
/**
* @brief Draw a box component.
*
* @param x0 Starting x-coordinate.
* @param y0 Starting y-coordinate.
* @param x1 Ending x-coordinate.
* @param y1 Ending y-coordinate.
* @param color Color of the box.
*/
void component_box_draw(int x0, int y0, int x1, int y1, color_t color);
/**
* @brief Draw a border component.
*
* @param x0 Starting x-coordinate.
* @param y0 Starting y-coordinate.
* @param x1 Ending x-coordinate.
* @param y1 Ending y-coordinate.
*/
void component_border_draw(int x0, int y0, int x1, int y1);
/**
* @brief Draw the layout component.
*/
void component_layout_draw(void);
/**
* @brief Draw a progress bar component.
*
* @param x0 Starting x-coordinate.
* @param y0 Starting y-coordinate.
* @param x1 Ending x-coordinate.
* @param y1 Ending y-coordinate.
* @param progress Progress value (0.0 to 1.0).
*/
void component_progressbar_draw(int x0, int y0, int x1, int y1, float progress);
/**
* @brief Draw a seek bar component.
*
* @param progress Progress value (0.0 to 1.0).
*/
void component_seekbar_draw(float progress);
/**
* @brief Draw a loader component.
*
* @param position Position value (0.0 to 1.0).
*/
void component_loader_draw(float position);
/**
* @brief Draw a scrollbar component.
*
* @param x Starting x-coordinate.
* @param y Starting y-coordinate.
* @param width Width of the scrollbar.
* @param height Height of the scrollbar.
* @param position Current position.
* @param items Total number of items.
* @param visible_items Number of visible items.
*/
void component_scrollbar_draw(int x, int y, int width, int height, int position, int items, int visible_items);
/**
* @brief Draw a list scrollbar component.
*
* @param position Current position.
* @param items Total number of items.
* @param visible_items Number of visible items.
*/
void component_list_scrollbar_draw(int position, int items, int visible_items);
/**
* @brief Draw a dialog component.
*
* @param width Width of the dialog.
* @param height Height of the dialog.
*/
void component_dialog_draw(int width, int height);
/**
* @brief Draw a message box component.
*
* @param fmt Format string for the message.
* @param ... Additional arguments for the format string.
*/
void component_messagebox_draw(char *fmt, ...);
/**
* @brief Draw the main text component.
*
* @param align Horizontal alignment.
* @param valign Vertical alignment.
* @param fmt Format string for the text.
* @param ... Additional arguments for the format string.
*/
void component_main_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
/**
* @brief Draw the actions bar text component.
*
* @param align Horizontal alignment.
* @param valign Vertical alignment.
* @param fmt Format string for the text.
* @param ... Additional arguments for the format string.
*/
void component_actions_bar_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
/**
* @brief Initialize the background component.
*
* @param cache_location Location of the cache.
*/
void component_background_init(char *cache_location);
/**
* @brief Free the background component resources.
*/
void component_background_free(void);
/**
* @brief Replace the background image.
*
* @param image New background image.
*/
void component_background_replace_image(surface_t *image);
/**
* @brief Draw the background component.
*/
void component_background_draw(void);
/**
* @brief Draw the file list component.
*
* @param list List of entries.
* @param entries Number of entries.
* @param selected Index of the selected entry.
*/
void component_file_list_draw(entry_t *list, int entries, int selected);
/**
* @brief Context menu structure.
*/
typedef struct component_context_menu {
int count;
int selected;
bool hide_pending;
struct component_context_menu *parent;
struct component_context_menu *submenu;
int row_count; /**< Number of rows in the context menu */
int row_selected; /**< Index of the selected row */
bool hide_pending; /**< Flag to indicate if hiding is pending */
struct component_context_menu *parent; /**< Pointer to the parent context menu */
struct component_context_menu *submenu; /**< Pointer to the submenu */
struct {
const char *text;
void (*action) (menu_t *menu, void *arg);
void *arg;
struct component_context_menu *submenu;
} list[];
const char *text; /**< Text of the menu item */
void (*action)(menu_t *menu, void *arg); /**< Action function for the menu item */
void *arg; /**< Argument for the action function */
struct component_context_menu *submenu; /**< Pointer to the submenu */
} list[]; /**< List of menu items */
} component_context_menu_t;
#define COMPONENT_CONTEXT_MENU_LIST_END { .text = NULL }
#define COMPONENT_CONTEXT_MENU_LIST_END { .text = NULL } /**< End marker for the context menu list */
/**
* @brief Initialize the context menu component.
*
* @param cm Pointer to the context menu structure.
*/
void component_context_menu_init(component_context_menu_t *cm);
/**
* @brief Show the context menu component.
*
* @param cm Pointer to the context menu structure.
*/
void component_context_menu_show(component_context_menu_t *cm);
/**
* @brief Process the context menu component.
*
* @param menu Pointer to the menu structure.
* @param cm Pointer to the context menu structure.
* @return True if the context menu was processed, false otherwise.
*/
bool component_context_menu_process(menu_t *menu, component_context_menu_t *cm);
/**
* @brief Draw the context menu component.
*
* @param cm Pointer to the context menu structure.
*/
void component_context_menu_draw(component_context_menu_t *cm);
/** @brief Box Art Structure. */
/**
* @brief Box Art Structure.
*/
typedef struct {
bool loading;
surface_t *image;
bool loading; /**< Flag to indicate if the box art is loading */
surface_t *image; /**< Pointer to the box art image */
} component_boxart_t;
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code);
/**
* @brief Initialize the box art component.
*
* @param storage_prefix Prefix for the storage location.
* @param game_code Game code for the box art.
* @param current_image_view Current image view type.
* @return Pointer to the initialized box art component.
*/
component_boxart_t *component_boxart_init(const char *storage_prefix, char *game_code, file_image_type_t current_image_view);
/**
* @brief Free the box art component resources.
*
* @param b Pointer to the box art component.
*/
void component_boxart_free(component_boxart_t *b);
/**
* @brief Draw the box art component.
*
* @param b Pointer to the box art component.
*/
void component_boxart_draw(component_boxart_t *b);
/** @} */ /* menu_components */
/** @} */ /* menu_ui_components */
#endif
#endif /* COMPONENTS_H__ */

View File

@ -98,9 +98,6 @@ static void prepare_background (component_background_t *c) {
return;
}
uint16_t image_center_x = (c->image->width / 2);
uint16_t image_center_y = (c->image->height / 2);
// Darken the image
rdpq_attach(c->image, NULL);
rdpq_mode_push();
@ -108,15 +105,13 @@ static void prepare_background (component_background_t *c) {
rdpq_set_prim_color(BACKGROUND_OVERLAY_COLOR);
rdpq_mode_combiner(RDPQ_COMBINER_FLAT);
rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY);
rdpq_fill_rectangle(
0 - (DISPLAY_CENTER_X - image_center_x),
0 - (DISPLAY_CENTER_Y - image_center_y),
DISPLAY_WIDTH - (DISPLAY_CENTER_X - image_center_x),
DISPLAY_HEIGHT - (DISPLAY_CENTER_Y - image_center_y)
);
rdpq_fill_rectangle(0, 0, c->image->width, c->image->height);
rdpq_mode_pop();
rdpq_detach();
uint16_t image_center_x = (c->image->width / 2);
uint16_t image_center_y = (c->image->height / 2);
// Prepare display list
rspq_block_begin();
rdpq_mode_push();

View File

@ -17,9 +17,9 @@ static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void
}
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code) {
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code, file_image_type_t current_image_view) {
component_boxart_t *b;
char file_name[8];
char boxart_id_path[8];
if ((b = calloc(1, sizeof(component_boxart_t))) == NULL) {
return NULL;
@ -29,21 +29,90 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam
path_t *path = path_init(storage_prefix, BOXART_DIRECTORY);
sprintf(file_name, "%.3s.png", game_code);
path_push(path, file_name);
sprintf(boxart_id_path, "%c/%c/%c/%c", game_code[0], game_code[1], game_code[2], game_code[3]);
path_push(path, boxart_id_path);
if (!directory_exists(path_get(path))) { // Allow boxart to not specify the region code.
path_pop(path);
}
if (directory_exists(path_get(path))) {
switch (current_image_view) {
case IMAGE_GAMEPAK_FRONT:
path_push(path, "gamepak_front.png");
break;
case IMAGE_GAMEPAK_BACK:
path_push(path, "gamepak_back.png");
break;
case IMAGE_BOXART_BACK:
path_push(path, "boxart_back.png");
break;
case IMAGE_BOXART_LEFT:
path_push(path, "boxart_left.png");
break;
case IMAGE_BOXART_RIGHT:
path_push(path, "boxart_right.png");
break;
case IMAGE_BOXART_BOTTOM:
path_push(path, "boxart_bottom.png");
break;
case IMAGE_BOXART_TOP:
path_push(path, "boxart_top.png");
break;
default:
path_push(path, "boxart_front.png");
break;
}
if (file_exists(path_get(path))) {
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
path_free(path);
return b;
}
}
}
else { // compatibility mode
char file_name[9];
// reset the directory path used for boxart.
path_free(path);
path = path_init(storage_prefix, BOXART_DIRECTORY);
snprintf(file_name, sizeof(file_name), "%c%c%c%c.png", game_code[0], game_code[1], game_code[2], game_code[3]);
path_push(path, file_name);
if (file_exists(path_get(path))) {
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
path_free(path);
return b;
}
}
path_pop(path);
snprintf(file_name, sizeof(file_name), "%c%c%c.png", game_code[0], game_code[1], game_code[2]);
path_push(path, file_name);
if (file_exists(path_get(path))) {
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
path_free(path);
return b;
}
}
else {
path_pop(path);
// TODO: This is bad, we should only check for 3 letter codes
sprintf(file_name, "%.2s.png", game_code + 1);
snprintf(file_name, sizeof(file_name), "%c%c.png", game_code[1], game_code[2]);
path_push(path, file_name);
if (file_exists(path_get(path))) {
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
path_free(path);
return b;
}
}
}
}
// TODO: return default image.
path_free(path);
free(b);

View File

@ -151,7 +151,7 @@ void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *f
.height = LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT - (TEXT_MARGIN_VERTICAL * 2),
.align = align,
.valign = valign,
.wrap = WRAP_ELLIPSES,
.wrap = WRAP_WORD,
.line_spacing = TEXT_LINE_SPACING_ADJUST,
},
FNT_DEFAULT,

View File

@ -13,22 +13,22 @@ static component_context_menu_t *get_current_submenu (component_context_menu_t *
void component_context_menu_init (component_context_menu_t *cm) {
cm->selected = -1;
cm->count = 0;
cm->row_selected = -1;
cm->row_count = 0;
cm->hide_pending = false;
cm->parent = NULL;
for (int i = 0; (cm->list[i].text) != NULL; i++) {
cm->count += 1;
cm->row_count += 1;
}
}
void component_context_menu_show (component_context_menu_t *cm) {
cm->selected = 0;
cm->row_selected = 0;
cm->submenu = NULL;
}
bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) {
if (!cm || (cm->selected < 0)) {
if (!cm || (cm->row_selected < 0)) {
return false;
}
@ -44,26 +44,26 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm)
}
sound_play_effect(SFX_EXIT);
} else if (menu->actions.enter) {
if (cm->list[cm->selected].submenu) {
cm->submenu = cm->list[cm->selected].submenu;
if (cm->list[cm->row_selected].submenu) {
cm->submenu = cm->list[cm->row_selected].submenu;
component_context_menu_init(cm->submenu);
cm->submenu->selected = 0;
cm->submenu->row_selected = 0;
cm->submenu->parent = cm;
} else if (cm->list[cm->selected].action) {
cm->list[cm->selected].action(menu, cm->list[cm->selected].arg);
} else if (cm->list[cm->row_selected].action) {
cm->list[cm->row_selected].action(menu, cm->list[cm->row_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;
cm->row_selected -= 1;
if (cm->row_selected < 0) {
cm->row_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);
cm->row_selected += 1;
if (cm->row_selected >= cm->row_count) {
cm->row_selected = (cm->row_count - 1);
}
sound_play_effect(SFX_CURSOR);
}
@ -72,7 +72,7 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm)
}
void component_context_menu_draw (component_context_menu_t *cm) {
if (!cm || (cm->selected < 0)) {
if (!cm || (cm->row_selected < 0)) {
return;
}
@ -92,7 +92,7 @@ void component_context_menu_draw (component_context_menu_t *cm) {
NULL
);
for (int i = 0; i < cm->count; i++) {
for (int i = 0; i < cm->row_count; i++) {
const char *text = cm->list[i].text;
rdpq_paragraph_builder_span(text, strlen(text));
if (cm->list[i + 1].text != NULL) {
@ -110,7 +110,7 @@ void component_context_menu_draw (component_context_menu_t *cm) {
int highlight_x0 = DISPLAY_CENTER_X - (width / 2);
int highlight_x1 = DISPLAY_CENTER_X + (width / 2);
int highlight_height = (layout->bbox.y1 - layout->bbox.y0) / layout->nlines;
int highlight_y = VISIBLE_AREA_Y0 + layout->bbox.y0 + ((cm->selected) * highlight_height);
int highlight_y = VISIBLE_AREA_Y0 + layout->bbox.y0 + ((cm->row_selected) * highlight_height);
component_box_draw(
highlight_x0,
@ -126,6 +126,6 @@ void component_context_menu_draw (component_context_menu_t *cm) {
if (top->hide_pending) {
top->hide_pending = false;
top->selected = -1;
top->row_selected = -1;
}
}

View File

@ -52,7 +52,17 @@ typedef struct {
} disk_info_t;
/**
* @brief Loads disk information from the specified path.
*
* This function reads the disk information from the given path and populates
* the provided disk_info structure with the relevant data.
*
* @param path A pointer to a path_t structure that specifies the path to the disk.
* @param disk_info A pointer to a disk_info_t structure where the disk information will be stored.
* @return A disk_err_t value indicating the success or failure of the operation.
*/
disk_err_t disk_info_load (path_t *path, disk_info_t *disk_info);
#endif
#endif /* DISK_INFO_H__ */

View File

@ -7,23 +7,39 @@
#ifndef FONTS_H__
#define FONTS_H__
/** @brief Font type enumeration. */
/**
* @brief Font type enumeration.
*
* This enumeration defines the different types of fonts that can be used
* in the menu system.
*/
typedef enum {
FNT_DEFAULT = 1,
FNT_DEFAULT = 1, /**< Default font type */
} menu_font_type_t;
/** @brief Font style enumeration. */
/**
* @brief Font style enumeration.
*
* This enumeration defines the different styles of fonts that can be used
* in the menu system.
*/
typedef enum {
STL_DEFAULT = 0,
STL_GREEN,
STL_BLUE,
STL_YELLOW,
STL_ORANGE,
STL_GRAY,
STL_DEFAULT = 0, /**< Default font style */
STL_GREEN, /**< Green font style */
STL_BLUE, /**< Blue font style */
STL_YELLOW, /**< Yellow font style */
STL_ORANGE, /**< Orange font style */
STL_GRAY, /**< Gray font style */
} menu_font_style_t;
/**
* @brief Initialize fonts.
*
* This function initializes the fonts used in the menu system. It can load
* custom fonts from the specified path.
*
* @param custom_font_path Path to the custom font file.
*/
void fonts_init(char *custom_font_path);
#endif
#endif /* FONTS_H__ */

View File

@ -27,53 +27,19 @@
#define MENU_CACHE_DIRECTORY "cache"
#define BACKGROUND_CACHE_FILE "background.data"
#define FRAMERATE_DIVIDER (2)
#define LAG_REPORT (false)
#define INTERLACED (true)
#define FPS_LIMIT (30.0f)
static menu_t *menu;
static tv_type_t tv_type;
static volatile int frame_counter = 0;
extern tv_type_t __boot_tvtype;
static void frame_counter_handler (void) {
frame_counter += 1;
}
static void frame_counter_reset (void) {
#if LAG_REPORT
static int accumulated = 0;
if (frame_counter > FRAMERATE_DIVIDER) {
accumulated += frame_counter - FRAMERATE_DIVIDER;
debugf(
"LAG: %d additional frame(s) displayed since last draw (accumulated: %d)\n",
frame_counter - FRAMERATE_DIVIDER,
accumulated
);
}
#endif
frame_counter = 0;
}
static void menu_init (boot_params_t *boot_params) {
joypad_init();
timer_init();
rtc_init();
rspq_init();
rdpq_init();
dfs_init(DFS_DEFAULT_LOCATION);
sound_init_default();
JOYPAD_PORT_FOREACH (port) {
joypad_set_rumble_active(port, false);
}
menu = calloc(1, sizeof(menu_t));
assert(menu != NULL);
menu->boot_params = boot_params;
menu->mode = MENU_MODE_NONE;
menu->next_mode = MENU_MODE_STARTUP;
@ -82,6 +48,19 @@ static void menu_init (boot_params_t *boot_params) {
menu->next_mode = MENU_MODE_FAULT;
}
joypad_init();
timer_init();
rtc_init();
rspq_init();
rdpq_init();
dfs_init(DFS_DEFAULT_LOCATION);
actions_init();
sound_init_default();
sound_init_sfx();
hdmi_clear_game_id();
path_t *path = path_init(menu->storage_prefix, MENU_DIRECTORY);
directory_create(path_get(path));
@ -91,6 +70,15 @@ static void menu_init (boot_params_t *boot_params) {
settings_load(&menu->settings);
path_pop(path);
resolution_t resolution = {
.width = 640,
.height = 480,
.interlaced = INTERLACED ? INTERLACE_HALF : INTERLACE_OFF,
.pal60 = menu->settings.pal60_enabled,
};
display_init(resolution, DEPTH_16_BPP, 2, GAMMA_NONE, INTERLACED ? FILTERS_DISABLED : FILTERS_RESAMPLE);
display_set_fps_limit(FPS_LIMIT);
path_push(path, MENU_CUSTOM_FONT_FILE);
fonts_init(path_get(path));
path_pop(path);
@ -103,40 +91,20 @@ static void menu_init (boot_params_t *boot_params) {
path_free(path);
menu->boot_params = boot_params;
sound_use_sfx(menu->settings.sound_enabled);
menu->browser.directory = path_init(menu->storage_prefix, menu->settings.default_directory);
if (!directory_exists(path_get(menu->browser.directory))) {
path_free(menu->browser.directory);
menu->browser.directory = path_init(menu->storage_prefix, "/");
}
hdmi_clear_game_id();
tv_type = get_tv_type();
if ((tv_type == TV_PAL) && menu->settings.pal60_enabled) {
// HACK: Set TV type to NTSC, so PAL console would output 60 Hz signal instead.
__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);
}
static void menu_deinit (menu_t *menu) {
unregister_VI_handler(frame_counter_handler);
// NOTE: Restore previous TV type so boot procedure wouldn't passthrough wrong value.
__boot_tvtype = tv_type;
hdmi_send_game_id(menu->boot_params);
component_background_free();
path_free(menu->load.disk_path);
path_free(menu->load.rom_path);
for (int i = 0; i < menu->browser.entries; i++) {
@ -146,9 +114,7 @@ static void menu_deinit (menu_t *menu) {
path_free(menu->browser.directory);
free(menu);
component_background_free();
flashcart_deinit();
display_close();
sound_deinit();
@ -158,7 +124,7 @@ static void menu_deinit (menu_t *menu) {
timer_close();
joypad_close();
display_close();
flashcart_deinit();
}
typedef const struct {
@ -201,11 +167,9 @@ void menu_run (boot_params_t *boot_params) {
menu_init(boot_params);
while (true) {
surface_t *display = (frame_counter >= FRAMERATE_DIVIDER) ? display_try_get() : NULL;
surface_t *display = display_try_get();
if (display != NULL) {
frame_counter_reset();
actions_update(menu);
view_t *view = menu_get_view(menu->mode);

View File

@ -109,6 +109,12 @@ typedef struct {
path_t *rom_patch_path;
rom_patch_info_t rom_patch_info;
} load;
struct {
bool rom_file;
bool disk_file;
bool emulator_file;
} boot_pending;
} menu_t;

View File

@ -7,37 +7,166 @@
#ifndef MP3_PLAYER_H__
#define MP3_PLAYER_H__
#include <stdbool.h>
/** @brief MP3 file error enumeration */
/**
* @brief MP3 file error enumeration.
*
* Enumeration for different types of errors that can occur in the MP3 player.
*/
typedef enum {
MP3PLAYER_OK,
MP3PLAYER_ERR_OUT_OF_MEM,
MP3PLAYER_ERR_IO,
MP3PLAYER_ERR_NO_FILE,
MP3PLAYER_ERR_INVALID_FILE,
MP3PLAYER_OK, /**< No error */
MP3PLAYER_ERR_OUT_OF_MEM, /**< Out of memory error */
MP3PLAYER_ERR_IO, /**< Input/Output error */
MP3PLAYER_ERR_NO_FILE, /**< No file found error */
MP3PLAYER_ERR_INVALID_FILE, /**< Invalid file error */
} mp3player_err_t;
/**
* @brief Initialize the MP3 player mixer.
*
* This function initializes the mixer for the MP3 player.
*/
void mp3player_mixer_init(void);
/**
* @brief Initialize the MP3 player.
*
* This function initializes the MP3 player and prepares it for playback.
*
* @return mp3player_err_t Error code indicating the result of the initialization.
*/
mp3player_err_t mp3player_init(void);
/**
* @brief Deinitialize the MP3 player.
*
* This function deinitializes the MP3 player and releases any resources.
*/
void mp3player_deinit(void);
/**
* @brief Load an MP3 file.
*
* This function loads an MP3 file from the specified path.
*
* @param path Path to the MP3 file.
* @return mp3player_err_t Error code indicating the result of the load operation.
*/
mp3player_err_t mp3player_load(char *path);
/**
* @brief Unload the current MP3 file.
*
* This function unloads the currently loaded MP3 file.
*/
void mp3player_unload(void);
/**
* @brief Process the MP3 player.
*
* This function processes the MP3 player, handling playback and other operations.
*
* @return mp3player_err_t Error code indicating the result of the process operation.
*/
mp3player_err_t mp3player_process(void);
/**
* @brief Check if the MP3 player is playing.
*
* This function checks if the MP3 player is currently playing.
*
* @return true if the MP3 player is playing, false otherwise.
*/
bool mp3player_is_playing(void);
/**
* @brief Check if the MP3 player has finished playing.
*
* This function checks if the MP3 player has finished playing the current file.
*
* @return true if the MP3 player has finished playing, false otherwise.
*/
bool mp3player_is_finished(void);
/**
* @brief Start playback of the MP3 file.
*
* This function starts playback of the currently loaded MP3 file.
*
* @return mp3player_err_t Error code indicating the result of the play operation.
*/
mp3player_err_t mp3player_play(void);
/**
* @brief Stop playback of the MP3 file.
*
* This function stops playback of the currently loaded MP3 file.
*/
void mp3player_stop(void);
/**
* @brief Toggle playback of the MP3 file.
*
* This function toggles playback of the currently loaded MP3 file.
*
* @return mp3player_err_t Error code indicating the result of the toggle operation.
*/
mp3player_err_t mp3player_toggle(void);
/**
* @brief Mute or unmute the MP3 player.
*
* This function mutes or unmutes the MP3 player.
*
* @param mute true to mute, false to unmute.
*/
void mp3player_mute(bool mute);
/**
* @brief Seek to a specific position in the MP3 file.
*
* This function seeks to a specific position in the currently loaded MP3 file.
*
* @param seconds Number of seconds to seek.
* @return mp3player_err_t Error code indicating the result of the seek operation.
*/
mp3player_err_t mp3player_seek(int seconds);
/**
* @brief Get the duration of the MP3 file.
*
* This function gets the duration of the currently loaded MP3 file.
*
* @return float Duration of the MP3 file in seconds.
*/
float mp3player_get_duration(void);
/**
* @brief Get the bitrate of the MP3 file.
*
* This function gets the bitrate of the currently loaded MP3 file.
*
* @return float Bitrate of the MP3 file in kbps.
*/
float mp3player_get_bitrate(void);
/**
* @brief Get the sample rate of the MP3 file.
*
* This function gets the sample rate of the currently loaded MP3 file.
*
* @return int Sample rate of the MP3 file in Hz.
*/
int mp3player_get_samplerate(void);
/**
* @brief Get the current playback progress.
*
* This function gets the current playback progress of the MP3 file.
*
* @return float Current playback progress as a percentage (0.0 to 100.0).
*/
float mp3player_get_progress(void);
#endif
#endif /* MP3_PLAYER_H__ */

View File

@ -7,27 +7,68 @@
#ifndef PNG_DECODER_H__
#define PNG_DECODER_H__
#include <surface.h>
/** @brief PNG decoder errors */
/**
* @brief PNG decoder errors
*
* Enumeration for different types of errors that can occur in the PNG decoder.
*/
typedef enum {
PNG_OK,
PNG_ERR_INT,
PNG_ERR_BUSY,
PNG_ERR_OUT_OF_MEM,
PNG_ERR_NO_FILE,
PNG_ERR_BAD_FILE,
PNG_OK, /**< No error */
PNG_ERR_INT, /**< Internal error */
PNG_ERR_BUSY, /**< Decoder is busy */
PNG_ERR_OUT_OF_MEM, /**< Out of memory error */
PNG_ERR_NO_FILE, /**< No file found error */
PNG_ERR_BAD_FILE, /**< Bad file error */
} png_err_t;
/**
* @brief PNG decoder callback type.
*
* This typedef defines the callback function type used by the PNG decoder.
*
* @param err Error code indicating the result of the decoding process.
* @param decoded_image Pointer to the decoded image surface.
* @param callback_data User-defined data passed to the callback function.
*/
typedef void png_callback_t (png_err_t err, surface_t *decoded_image, void *callback_data);
/**
* @brief Start the PNG decoding process.
*
* This function starts the PNG decoding process for the specified file.
*
* @param path Path to the PNG file.
* @param max_width Maximum width of the decoded image.
* @param max_height Maximum height of the decoded image.
* @param callback Callback function to be called when decoding is complete.
* @param callback_data User-defined data to be passed to the callback function.
* @return png_err_t Error code indicating the result of the start operation.
*/
png_err_t png_decoder_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data);
/**
* @brief Abort the PNG decoding process.
*
* This function aborts the ongoing PNG decoding process.
*/
void png_decoder_abort (void);
/**
* @brief Get the progress of the PNG decoding process.
*
* This function returns the current progress of the PNG decoding process as a percentage.
*
* @return float Current progress of the decoding process (0.0 to 100.0).
*/
float png_decoder_get_progress (void);
/**
* @brief Poll the PNG decoder.
*
* This function polls the PNG decoder to handle any ongoing decoding tasks.
*/
void png_decoder_poll (void);
#endif
#endif /* PNG_DECODER_H__ */

View File

@ -186,6 +186,9 @@ static const match_t database[] = {
MATCH_ID_REGION("NDKJ", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Dark Rift [Space Dynamites (J)]
MATCH_ID_REGION("NPDJ", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK | FEAT_EXP_PAK_REQUIRED),// Perfect Dark (J)
MATCH_ID("NPD", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK | FEAT_EXP_PAK_RECOMMENDED), // Perfect Dark
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
@ -305,7 +308,6 @@ static const match_t database[] = {
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
@ -833,6 +835,7 @@ 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) {
path_free(overrides_path);
return ROM_ERR_SAVE_IO;
}

View File

@ -14,6 +14,9 @@ static settings_t init = {
.default_directory = "/",
.use_saves_folder = true,
.sound_enabled = true,
.rom_autoload_enabled = false,
.rom_autoload_path = "",
.rom_autoload_filename = "",
/* Beta feature flags (should always init to off) */
.bgm_enabled = false,
@ -41,6 +44,10 @@ void settings_load (settings_t *settings) {
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);
settings->rom_autoload_enabled = mini_get_bool(ini, "menu", "autoload_rom_enabled", init.rom_autoload_enabled);
settings->rom_autoload_path = strdup(mini_get_string(ini, "autoload", "rom_path", init.rom_autoload_path));
settings->rom_autoload_filename = strdup(mini_get_string(ini, "autoload", "rom_filename", init.rom_autoload_filename));
/* 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->rumble_enabled = mini_get_bool(ini, "menu_beta_flag", "rumble_enabled", init.rumble_enabled);
@ -56,6 +63,9 @@ void settings_save (settings_t *settings) {
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);
mini_set_bool(ini, "menu", "autoload_rom_enabled", settings->rom_autoload_enabled);
mini_set_string(ini, "autoload", "rom_path", settings->rom_autoload_path);
mini_set_string(ini, "autoload", "rom_filename", settings->rom_autoload_filename);
/* Beta feature flags, they should not save until production ready! */
// mini_set_bool(ini, "menu_beta_flag", "bgm_enabled", settings->bgm_enabled);

View File

@ -30,6 +30,16 @@ typedef struct {
/** @brief Enable rumble feedback */
bool rumble_enabled;
/** @brief Enable the ability to bypass the menu and instantly load a ROM */
bool rom_autoload_enabled;
/** @brief A path to the autoloaded ROM */
char *rom_autoload_path;
/** @brief A filename of the autoloaded ROM */
char *rom_autoload_filename;
} settings_t;
@ -40,5 +50,4 @@ void settings_load (settings_t *settings);
/** @brief The settings to save */
void settings_save (settings_t *settings);
#endif

View File

@ -86,6 +86,13 @@ void sound_play_effect(sound_effect_t sfx) {
void sound_deinit (void) {
if (sound_initialized) {
if (sfx_enabled) {
wav64_close(&sfx_cursor);
wav64_close(&sfx_exit);
wav64_close(&sfx_setting);
wav64_close(&sfx_enter);
wav64_close(&sfx_error);
}
mixer_close();
audio_close();
sound_initialized = false;
@ -93,9 +100,7 @@ void sound_deinit (void) {
}
void sound_poll (void) {
if (sound_initialized && audio_can_write()) {
short *audio_buffer = audio_write_begin();
mixer_poll(audio_buffer, audio_get_buffer_length());
audio_write_end();
if (sound_initialized) {
mixer_try_play();
}
}

View File

@ -9,24 +9,59 @@
#include <stdbool.h>
#define SOUND_MP3_PLAYER_CHANNEL (0)
#define SOUND_SFX_CHANNEL (2)
#define SOUND_MP3_PLAYER_CHANNEL (0) /**< Channel for MP3 player sound */
#define SOUND_SFX_CHANNEL (2) /**< Channel for sound effects */
/**
* @brief Enumeration of available sound effects for menu interactions.
*
* This enumeration defines the different sound effects that can be used
* for menu interactions.
*/
typedef enum {
SFX_CURSOR,
SFX_ERROR,
SFX_ENTER,
SFX_EXIT,
SFX_SETTING,
SFX_CURSOR, /**< Sound effect for cursor movement */
SFX_ERROR, /**< Sound effect for error */
SFX_ENTER, /**< Sound effect for entering a menu */
SFX_EXIT, /**< Sound effect for exiting a menu */
SFX_SETTING, /**< Sound effect for changing a setting */
} sound_effect_t;
/**
* @brief Initialize the default sound system.
*
* This function initializes the default sound system, setting up
* necessary resources and configurations.
*/
void sound_init_default(void);
/**
* @brief Initialize the MP3 playback system.
*
* This function initializes the MP3 playback system, preparing it
* for playing MP3 files.
*/
void sound_init_mp3_playback(void);
/**
* @brief Initialize the sound effects system.
*
* This function initializes the sound effects system, setting up
* necessary resources and configurations for playing sound effects.
*/
void sound_init_sfx(void);
/**
* @brief Enable or disable sound effects.
* @param enable True to enable sound effects, false to disable.
*/
void sound_use_sfx(bool);
/**
* @brief Play a specified sound effect.
* @param sfx The sound effect to play, as defined in sound_effect_t.
*/
void sound_play_effect(sound_effect_t sfx);
void sound_deinit (void);
void sound_poll (void);
#endif
#endif /* SOUND_H__ */

View File

@ -2,20 +2,36 @@
* @file usb_comm.h
* @brief USB communication subsystem
* @ingroup menu
*
* This file contains the declarations for the USB communication subsystem
* used in the menu system.
*/
#ifndef USB_COMM_H__
#define USB_COMM_H__
#include "menu_state.h"
#ifndef NDEBUG
/**
* @brief Poll the USB communication subsystem.
*
* This function polls the USB communication subsystem to handle any
* incoming or outgoing data. It is only available in debug builds.
*
* @param menu Pointer to the menu structure.
*/
void usb_comm_poll(menu_t *menu);
#else
/**
* @brief Poll the USB communication subsystem (no-op in release builds).
*
* This macro is a no-op in release builds, where USB communication polling
* is disabled.
*
* @param menu Pointer to the menu structure.
*/
#define usb_comm_poll(menu)
#endif
#endif
#endif /* USB_COMM_H__ */

View File

@ -12,8 +12,8 @@
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
}
}

View File

@ -4,8 +4,8 @@
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
}
}

View File

@ -7,7 +7,7 @@ static void draw (menu_t *menu, surface_t *d) {
rdpq_clear(RGBA32(0x7F, 0x00, 0x00, 0xFF));
const char *firmware_message = (
"Supported firmware versions:\n"
"Minimum supported firmware versions:\n"
"64drive: 2.05+\n"
"EverDrive-64: ???+\n"
"SummerCart64: 2.17.0+"

View File

@ -50,8 +50,8 @@ 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);
menu->next_mode = MENU_MODE_BROWSER;
}
}
@ -84,7 +84,7 @@ static void draw (menu_t *menu, surface_t *d) {
S_ISDIR(st.st_mode) ? "Directory" : "File",
st.st_mode & S_IWUSR ? "" : "(Read only)",
format_file_type(menu->browser.entry->name, S_ISDIR(st.st_mode)),
ctime(&st.st_mtim.tv_sec)
ctime(&st.st_mtime)
);
component_actions_bar_text_draw(

View File

@ -1,11 +1,35 @@
#include "views.h"
#include "../sound.h"
#include <libcart/cart.h>
static inline const char *format_boolean_type (bool bool_value) {
return bool_value ? "Supported" : "Unsupported";
}
static const char *format_cart_type () {
switch (cart_type) {
case CART_CI:
return "64drive";
case CART_EDX:
return "Series X EverDrive-64";
case CART_ED:
return "Series V EverDrive-64";
case CART_SC:
return "SummerCart64";
default: // Probably emulator
return "Emulator?";
}
}
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
}
}
@ -18,24 +42,41 @@ static void draw (menu_t *menu, surface_t *d) {
component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"FLASHCART INFORMATION\n"
"FLASHCART INFORMATION"
"\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"
"\n"
);
"Type:\n"
" %s\n\n"
"Firmware:\n"
" %s\n\n"
"Features:\n"
" Virtual 64DD: %s.\n"
" Real Time Clock: %s.\n"
" USB Debugging: %s.\n"
" Automatic CIC: %s.\n"
" Region Detection: %s.\n"
" Save Writeback: %s.\n"
" Update from menu: %s.\n"
"\n\n",
format_cart_type(),
"Not Available", // TODO get cart firmware version(s).
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_64DD)),
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_RTC)),
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_USB)),
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_CIC)),
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_REGION)),
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_SAVE_WRITEBACK)),
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_BIOS_UPDATE_FROM_MENU))
//TODO: display the battery and temperature information (if available).
//format_diagnostic_data(flashcart_has_feature(FLASHCART_FEATURE_DIAGNOSTIC_DATA))
);
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,

View File

@ -5,8 +5,7 @@
#include "views.h"
static bool load_pending;
static bool load_rom;
static bool load_disk_with_rom;
static component_boxart_t *boxart;
@ -31,15 +30,15 @@ static char *format_disk_region (disk_region_t region) {
static void process (menu_t *menu) {
if (menu->actions.enter) {
load_pending = true;
load_rom = false;
menu->boot_pending.disk_file = true;
load_disk_with_rom = false;
} else if (menu->actions.options && menu->load.rom_path) {
load_pending = true;
load_rom = true;
menu->boot_pending.disk_file = true;
load_disk_with_rom = true;
sound_play_effect(SFX_SETTING);
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
}
}
@ -48,7 +47,7 @@ static void draw (menu_t *menu, surface_t *d) {
component_background_draw();
if (load_pending) {
if (menu->boot_pending.disk_file) {
component_loader_draw(0.0f);
} else {
component_layout_draw();
@ -94,8 +93,10 @@ static void draw (menu_t *menu, surface_t *d) {
);
}
if (boxart != NULL) {
component_boxart_draw(boxart);
}
}
rdpq_detach_show();
}
@ -117,7 +118,7 @@ static void draw_progress (float progress) {
static void load (menu_t *menu) {
cart_load_err_t err;
if (menu->load.rom_path && load_rom) {
if (menu->load.rom_path && load_disk_with_rom) {
err = cart_load_n64_rom_and_save(menu, draw_progress);
if (err != CART_LOAD_OK) {
menu_show_error(menu, cart_load_convert_error_message(err));
@ -133,7 +134,7 @@ static void load (menu_t *menu) {
menu->next_mode = MENU_MODE_BOOT;
if (load_rom) {
if (load_disk_with_rom) {
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
menu->boot_params->detect_cic_seed = rom_info_get_cic_seed(&menu->load.rom_info, &menu->boot_params->cic_seed);
switch (rom_info_get_tv_type(&menu->load.rom_info)) {
@ -161,16 +162,17 @@ void view_load_disk_init (menu_t *menu) {
menu->load.disk_path = NULL;
}
load_pending = false;
menu->boot_pending.disk_file = false;
menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
disk_err_t err = disk_info_load(menu->load.disk_path, &menu->load.disk_info);
if (err != DISK_OK) {
menu_show_error(menu, convert_error_message(err));
return;
}
boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id);
boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id, IMAGE_BOXART_FRONT);
}
void view_load_disk_display (menu_t *menu, surface_t *display) {
@ -178,8 +180,8 @@ void view_load_disk_display (menu_t *menu, surface_t *display) {
draw(menu, display);
if (load_pending) {
load_pending = false;
if (menu->boot_pending.disk_file) {
menu->boot_pending.disk_file = false;
load(menu);
}

View File

@ -11,7 +11,6 @@ static const char *emu_gameboy_rom_extensions[] = { "gb", NULL };
static const char *emu_gameboy_color_rom_extensions[] = { "gbc", NULL };
static const char *emu_sega_8bit_rom_extensions[] = { "sms", "gg", "sg", NULL };
static bool load_pending;
static cart_load_emu_type_t emu_type;
static char *format_emulator_name (cart_load_emu_type_t emulator_info) {
@ -34,10 +33,10 @@ static char *format_emulator_name (cart_load_emu_type_t emulator_info) {
static void process (menu_t *menu) {
if (menu->actions.enter) {
load_pending = true;
menu->boot_pending.emulator_file = true;
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
}
}
@ -46,7 +45,7 @@ static void draw (menu_t *menu, surface_t *d) {
component_background_draw();
if (load_pending) {
if (menu->boot_pending.emulator_file) {
component_loader_draw(0.0f);
} else {
component_layout_draw();
@ -107,7 +106,7 @@ static void load (menu_t *menu) {
void view_load_emulator_init (menu_t *menu) {
load_pending = false;
menu->boot_pending.emulator_file = false;
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
@ -133,8 +132,8 @@ void view_load_emulator_display (menu_t *menu, surface_t *display) {
draw(menu, display);
if (load_pending) {
load_pending = false;
if (menu->boot_pending.emulator_file) {
menu->boot_pending.emulator_file = false;
load(menu);
}
}

View File

@ -3,12 +3,12 @@
#include "boot/boot.h"
#include "../sound.h"
#include "views.h"
#include <string.h>
#include "utils/fs.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_LOAD_IO: return "I/O error during loading ROM information and/or options";
@ -145,6 +145,17 @@ static void set_tv_type (menu_t *menu, void *arg) {
menu->browser.reload = true;
}
static void set_autoload_type (menu_t *menu, void *arg) {
free(menu->settings.rom_autoload_path);
menu->settings.rom_autoload_path = strdup(strip_fs_prefix(path_get(menu->browser.directory)));
free(menu->settings.rom_autoload_filename);
menu->settings.rom_autoload_filename = strdup(menu->browser.entry->name);
// FIXME: add a confirmation box here! (press start on reboot)
menu->settings.rom_autoload_enabled = true;
settings_save(&menu->settings);
menu->browser.reload = true;
}
static component_context_menu_t set_cic_type_context_menu = { .list = {
{.text = "Automatic", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_AUTOMATIC) },
{.text = "CIC-6101", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_6101) },
@ -187,6 +198,7 @@ static component_context_menu_t options_context_menu = { .list = {
{ .text = "Set CIC Type", .submenu = &set_cic_type_context_menu },
{ .text = "Set Save Type", .submenu = &set_save_type_context_menu },
{ .text = "Set TV Type", .submenu = &set_tv_type_context_menu },
{ .text = "Set ROM to autoload", .action = set_autoload_type },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
@ -196,10 +208,10 @@ static void process (menu_t *menu) {
}
if (menu->actions.enter) {
load_pending = true;
menu->boot_pending.rom_file = true;
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
} else if (menu->actions.options) {
component_context_menu_show(&options_context_menu);
sound_play_effect(SFX_SETTING);
@ -218,7 +230,7 @@ static void draw (menu_t *menu, surface_t *d) {
component_background_draw();
if (load_pending) {
if (menu->boot_pending.rom_file) {
component_loader_draw(0.0f);
} else {
component_layout_draw();
@ -262,7 +274,9 @@ static void draw (menu_t *menu, surface_t *d) {
"R: Options"
);
if (boxart != NULL) {
component_boxart_draw(boxart);
}
if (show_extra_info_message) {
component_messagebox_draw(
@ -335,17 +349,18 @@ static void load (menu_t *menu) {
static void deinit (void) {
component_boxart_free(boxart);
boxart = NULL;
}
void view_load_rom_init (menu_t *menu) {
load_pending = false;
if (!menu->settings.rom_autoload_enabled) {
if (menu->load.rom_path) {
path_free(menu->load.rom_path);
}
menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
}
rom_err_t err = rom_info_load(menu->load.rom_path, &menu->load.rom_info);
if (err != ROM_OK) {
@ -355,18 +370,19 @@ void view_load_rom_init (menu_t *menu) {
return;
}
boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code);
if (!menu->settings.rom_autoload_enabled) {
boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code, IMAGE_BOXART_FRONT);
component_context_menu_init(&options_context_menu);
}
}
void view_load_rom_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
if (load_pending) {
load_pending = false;
if (menu->boot_pending.rom_file) {
menu->boot_pending.rom_file = false;
load(menu);
}

View File

@ -41,8 +41,8 @@ static void process (menu_t *menu) {
if (err != MP3PLAYER_OK) {
menu_show_error(menu, convert_error_message(err));
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
} else if (menu->actions.enter) {
err = mp3player_toggle();
if (err != MP3PLAYER_OK) {

View File

@ -1,25 +1,164 @@
#include <time.h>
#include <stdbool.h>
#include <stdio.h>
#include <libdragon.h>
#include <sys/time.h>
#include "../sound.h"
#include "views.h"
// FIXME: add implementation!
// struct {
// uint16_t seconds;
// uint16_t minutes;
// uint16_t hours;
// uint16_t day;
// uint16_t month;
// uint16_t year;
// } adjusted_datetime;
#define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; })
#define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; })
#define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max)))
// static void save_adjusted_datetime () {
#define YEAR_MIN 1996
#define YEAR_MAX 2095
// }
typedef enum {
RTC_EDIT_YEAR,
RTC_EDIT_MONTH,
RTC_EDIT_DAY,
RTC_EDIT_HOUR,
RTC_EDIT_MIN,
RTC_EDIT_SEC,
} rtc_field_t;
static const char* const DAYS_OF_WEEK[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static struct tm rtc_tm = {0};
static bool is_editing_mode;
static rtc_field_t editing_field_type;
int wrap( uint16_t val, uint16_t min, uint16_t max ) {
if( val < min ) return max;
if( val > max ) return min;
return val;
}
rtc_time_t rtc_time_from_tm( struct tm *time ) {
return(rtc_time_t){
.year = CLAMP(time->tm_year + 1900, YEAR_MIN, YEAR_MAX),
.month = CLAMP(time->tm_mon, 1, 12),
.day = CLAMP(time->tm_mday, 1, 31),
.hour = CLAMP(time->tm_hour, 0, 23),
.min = CLAMP(time->tm_min, 0, 59),
.sec = CLAMP(time->tm_sec, 0, 59),
.week_day = CLAMP(time->tm_wday, 0, 6),
};
}
void adjust_rtc_time( struct tm *t, int incr ) {
switch(editing_field_type)
{
case RTC_EDIT_YEAR:
t->tm_year = wrap( t->tm_year + incr, YEAR_MIN - 1900, YEAR_MAX - 1900 );
break;
case RTC_EDIT_MONTH:
t->tm_mon = wrap( t->tm_mon + incr, 0, 11 );
break;
case RTC_EDIT_DAY:
t->tm_mday = wrap( t->tm_mday + incr, 1, 31 );
break;
case RTC_EDIT_HOUR:
t->tm_hour = wrap( t->tm_hour + incr, 0, 23 );
break;
case RTC_EDIT_MIN:
t->tm_min = wrap( t->tm_min + incr, 0, 59 );
break;
case RTC_EDIT_SEC:
t->tm_sec = wrap( t->tm_sec + incr, 0, 59 );
break;
}
// Recalculate day-of-week and day-of-year
time_t timestamp = mktime( t );
*t = *gmtime( &timestamp );
}
void component_editdatetime_draw ( struct tm t, rtc_field_t selected_field ) {
// FIXME: move this to components.c once improved.
/* Format RTC date/time as strings */
char full_dt[30];
char current_selection_chars[30];
snprintf( full_dt, sizeof(full_dt), ">%04d|%02d|%02d|%02d|%02d|%02d< %s",
t.tm_year + 1900,
t.tm_mon + 1,
t.tm_mday,
t.tm_hour,
t.tm_min,
t.tm_sec,
DAYS_OF_WEEK[t.tm_wday]
);
switch(selected_field)
{
// Note: for what ever reason, hat chars need to be duplicated to display correctly. This will be solved when there is a decent UI for it.
case RTC_EDIT_YEAR:
snprintf( current_selection_chars, sizeof(current_selection_chars), "*^^^^^^^^********************");
break;
case RTC_EDIT_MONTH:
snprintf( current_selection_chars, sizeof(current_selection_chars), "******^^^^*****************");
break;
case RTC_EDIT_DAY:
snprintf( current_selection_chars, sizeof(current_selection_chars), "*********^^^^**************");
break;
case RTC_EDIT_HOUR:
snprintf( current_selection_chars, sizeof(current_selection_chars), "************^^^^***********");
break;
case RTC_EDIT_MIN:
snprintf( current_selection_chars, sizeof(current_selection_chars), "***************^^^^********");
break;
case RTC_EDIT_SEC:
snprintf( current_selection_chars, sizeof(current_selection_chars), "******************^^^^*****");
break;
}
component_messagebox_draw(
"|YYYY|MM|DD|HH|MM|SS| DOW\n%s\n%s\n", full_dt, current_selection_chars);
}
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
if (menu->actions.back && !is_editing_mode) {
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
}
else if (menu->actions.enter && !is_editing_mode) {
rtc_tm = *gmtime(&menu->current_time);
is_editing_mode = true;
}
if (is_editing_mode) {
if (menu->actions.go_left) {
if ( editing_field_type <= RTC_EDIT_YEAR ) { editing_field_type = RTC_EDIT_SEC; }
else { editing_field_type = editing_field_type - 1; }
}
else if (menu->actions.go_right) {
if ( editing_field_type >= RTC_EDIT_SEC ) { editing_field_type = RTC_EDIT_YEAR; }
else { editing_field_type = editing_field_type + 1; }
}
else if (menu->actions.go_up) {
adjust_rtc_time( &rtc_tm, +1 );
}
else if (menu->actions.go_down) {
adjust_rtc_time( &rtc_tm, -1 );
}
else if (menu->actions.options) { // R button = save
if(rtc_is_writable()) {
// FIXME: settimeofday is not available in libdragon yet.
// struct timeval new_time = { .tv_sec = mktime(&rtc_tm) };
// int res = settimeofday(&new_time, NULL);
rtc_time_t rtc_time = rtc_time_from_tm(&rtc_tm);
int res = rtc_set(&rtc_time);
if (res != 1) {
menu_show_error(menu, "Failed to set RTC time");
}
}
else {
menu_show_error(menu, "RTC is not writable");
}
is_editing_mode = false;
}
else if (menu->actions.back) { // cancel
is_editing_mode = false;
}
}
}
@ -35,31 +174,46 @@ static void draw (menu_t *menu, surface_t *d) {
"ADJUST REAL TIME CLOCK\n"
"\n"
"\n"
"To set the date and time, please use the PC terminal\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"
"To set the RTC date and time, Press A.\n"
"You can also use the PC terminal application via USB,\n"
"or even an N64 game with RTC support.\n"
"\n"
"Current date & time: %s\n"
"\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown"
);
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n"
);
if (!is_editing_mode) {
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n" // "A: Save\n"
"A: Change\n"
"B: Back"
);
}
else {
component_actions_bar_text_draw(
ALIGN_RIGHT, VALIGN_TOP,
"Up/Down: Adjust Field\n"
"Left/Right: Switch Field"
);
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"R: Save\n"
"B: Back"
);
}
if (is_editing_mode) {
component_editdatetime_draw(rtc_tm, editing_field_type);
}
rdpq_detach_show();
}
void view_rtc_init (menu_t *menu) {
// Nothing to initialize (yet)
is_editing_mode = false;
editing_field_type = RTC_EDIT_YEAR;
}
void view_rtc_display (menu_t *menu, surface_t *display) {

View File

@ -12,36 +12,36 @@ static const char *format_switch (bool state) {
}
static void set_pal60_type (menu_t *menu, void *arg) {
menu->settings.pal60_enabled = (bool) (arg);
menu->settings.pal60_enabled = (bool)(uintptr_t)(arg);
settings_save(&menu->settings);
}
static void set_protected_entries_type (menu_t *menu, void *arg) {
menu->settings.show_protected_entries = (bool) (arg);
menu->settings.show_protected_entries = (bool)(uintptr_t)(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);
menu->settings.use_saves_folder = (bool)(uintptr_t)(arg);
settings_save(&menu->settings);
}
static void set_sound_enabled_type (menu_t *menu, void *arg) {
menu->settings.sound_enabled = (bool) (arg);
menu->settings.sound_enabled = (bool)(uintptr_t)(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);
menu->settings.bgm_enabled = (bool)(uintptr_t)(arg);
settings_save(&menu->settings);
}
static void set_rumble_enabled_type (menu_t *menu, void *arg) {
menu->settings.rumble_enabled = (bool) (arg);
menu->settings.rumble_enabled = (bool)(uintptr_t)(arg);
settings_save(&menu->settings);
}
@ -53,39 +53,39 @@ static void set_rumble_enabled_type (menu_t *menu, void *arg) {
static component_context_menu_t set_pal60_type_context_menu = { .list = {
{.text = "On", .action = set_pal60_type, .arg = (void *) (true) },
{.text = "On", .action = set_pal60_type, .arg = (void *)(uintptr_t)(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) },
{.text = "On", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(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) },
{.text = "On", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(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) },
{.text = "On", .action = set_use_saves_folder_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_use_saves_folder_type, .arg = (void *)(uintptr_t)(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) },
{.text = "On", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(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) },
{.text = "On", .action = set_rumble_enabled_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_rumble_enabled_type, .arg = (void *)(uintptr_t)(false) },
COMPONENT_CONTEXT_MENU_LIST_END,
}};
#endif
@ -114,8 +114,8 @@ static void process (menu_t *menu) {
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);
menu->next_mode = MENU_MODE_BROWSER;
}
}
@ -136,6 +136,7 @@ static void draw (menu_t *menu, surface_t *d) {
ALIGN_LEFT, VALIGN_TOP,
"\n\n"
" Default Directory : %s\n\n"
" Autoload ROM : %s\n"
"To change the following menu settings, press 'A':\n"
"* PAL60 Mode : %s\n"
" Show Hidden Files : %s\n"
@ -145,9 +146,11 @@ static void draw (menu_t *menu, surface_t *d) {
" Background Music : %s\n"
" Rumble Feedback : %s\n"
#endif
"Note: Certain settings have the following caveats:\n\n"
"* Requires a flashcart reboot.\n",
"\n\n"
"Note: Certain settings have the following caveats:\n"
"* Requires rebooting the N64 Console.\n",
menu->settings.default_directory,
format_switch(menu->settings.rom_autoload_enabled),
format_switch(menu->settings.pal60_enabled),
format_switch(menu->settings.show_protected_entries),
format_switch(menu->settings.use_saves_folder),

View File

@ -9,6 +9,27 @@ static void draw (menu_t *menu, surface_t *d) {
void view_startup_init (menu_t *menu) {
// FIXME: rather than use a controller button, would it be better to use the cart button?
JOYPAD_PORT_FOREACH (port) {
joypad_poll();
joypad_buttons_t b_held = joypad_get_buttons_held(port);
if (menu->settings.rom_autoload_enabled && b_held.start) {
menu->settings.rom_autoload_enabled = false;
menu->settings.rom_autoload_path = "";
menu->settings.rom_autoload_filename = "";
settings_save(&menu->settings);
}
}
if (menu->settings.rom_autoload_enabled) {
menu->browser.directory = path_init(menu->storage_prefix, menu->settings.rom_autoload_path);
menu->load.rom_path = path_clone_push(menu->browser.directory, menu->settings.rom_autoload_filename);
menu->boot_pending.rom_file = true;
menu->next_mode = MENU_MODE_LOAD_ROM;
return;
}
menu->next_mode = MENU_MODE_BROWSER;
}

View File

@ -28,8 +28,8 @@ static void process (menu_t *menu) {
}
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
}
}
@ -57,7 +57,7 @@ static void draw (menu_t *menu, surface_t *d) {
"Joypad 2 is %sconnected %s\n"
"Joypad 3 is %sconnected %s\n"
"Joypad 4 is %sconnected %s\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown",
is_memory_expanded() ? "" : "not ",
(joypad[0]) ? "" : "not ", format_accessory(0),
(joypad[1]) ? "" : "not ", format_accessory(1),

View File

@ -55,8 +55,8 @@ 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);
menu->next_mode = MENU_MODE_BROWSER;
} else if (text) {
if (menu->actions.go_up) {
perform_vertical_scroll(menu->actions.go_fast ? -10 : -1);