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 FROM debian:bookworm-slim
ARG SC64_DEPLOYER_VERSION=v2.18.0 ARG SC64_DEPLOYER_VERSION=v2.20.0
RUN apt-get update && \ RUN apt-get update && \
apt-get upgrade -y && \ apt-get upgrade -y && \
apt-get install build-essential doxygen git python3 wget -y && \ apt-get install build-essential doxygen git python3 wget -y && \

View File

@ -113,7 +113,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run Doxygen - name: Run Doxygen
uses: mattnotmitt/doxygen-action@1.9.5 uses: mattnotmitt/doxygen-action@v1
with: with:
doxyfile-path: './Doxyfile' 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)/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 $(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1
$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null)) $(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))
$(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf $(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf

View File

@ -26,6 +26,7 @@ An open source menu for N64 flashcarts.
* Real Time Clock support. * Real Time Clock support.
* Music playback (MP3). * Music playback (MP3).
* Menu sound effects. * Menu sound effects.
* N64 ROM autoload.
## Documentation ## Documentation
@ -49,22 +50,51 @@ An open source menu for N64 flashcarts.
## Experimental features ## Experimental features
These features are subject to change: These features are subject to change:
### ROM Boxart ### N64 ROM autoload
To use boxart, place PNG files in the `/menu/boxart` folder on the SD card with the following dimensions: 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.
* Standard covers: 158x112 NOTE: to return to the menu, hold joypad `start` button whilst powering on the console.
* 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`.
You can download these boxart packs:
[American Boxart](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w) ### GamePak sprites
To use N64 `GamePak` sprites, place `PNG` files within the `sd:/menu/boxart/` folder.
[European Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M)
[64DD Boxart](https://mega.nz/file/O3JzwD7B#BYl1aV-pbrJ-MxWUbM_K0yGVIRbmSoxJJZqQInRzZyM) #### 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
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 ### Menu Settings

View File

@ -1,11 +1,19 @@
## First time setup of SD card ## 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. - 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`. - 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`**). - 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 ### Emulator support
Emulators should be added to the `/menu/emulators` directory on the SD card. Emulators should be added to the `/menu/emulators` directory on the SD card.
@ -36,7 +44,7 @@ SD:\
│ │ ├── NDDJ2.n64 │ │ ├── NDDJ2.n64
│ │ └── NDXJ0.n64 │ │ └── NDXJ0.n64
│ │ │ │
│ └── emulators │ └── emulators\
│ ├── neon64bu.rom │ ├── neon64bu.rom
│ ├── sodium64.z64 │ ├── sodium64.z64
│ ├── gb.v64 │ ├── gb.v64

View File

@ -8,11 +8,11 @@ You can use a dev container in VSCode to ease development.
### To deploy: ### To deploy:
#### SC64 #### 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. * Extract and place `sc64deployer.exe` in the `tools/sc64` directory.
Make sure that your firmware is compatible (currently v2.18.0+) Make sure that your firmware is compatible (currently v2.20.0+)
See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.18.0/docs/00_quick_startup_guide.md#firmware-backupupdate) See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.20.0/docs/00_quick_startup_guide.md#firmware-backupupdate)
##### From the devcontainer ##### From the devcontainer
It is not currently possible to directly communicate with USB devices. 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. * Add the required file to the correct folder on your SD card.
## Update Libdragon submodule ## Update submodules
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. 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 ## Generate documentation
Run `doxygen` from the dev container terminal. Run `doxygen` from the dev container terminal.
Make sure you fix the warnings before creating a PR! 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/) Once merged, they can be viewed [here](https://polprzewodnikowy.github.io/N64FlashcartMenu/)
### Test generated docs in the dev-container ### 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: Install Prerequisites:
```bash ```bash
apt-get install ruby-full build-essential zlib1g-dev 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; io32_t *reboot_dst = SP_MEM->IMEM;
size_t reboot_instructions = (size_t) (&reboot_size) / sizeof(uint32_t); 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]); cpu_io_write(&reboot_dst[i], reboot_src[i]);
} }

View File

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

View File

@ -3,37 +3,47 @@
#include <stdint.h> #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 { typedef union {
uint32_t raw; uint32_t raw; /**< Raw 32-bit instruction */
struct { struct {
uint32_t op : 6; uint32_t op : 6; /**< Opcode field */
uint32_t rs : 5; uint32_t rs : 5; /**< Source register */
uint32_t rt : 5; uint32_t rt : 5; /**< Target register */
uint32_t imm : 16; uint32_t imm : 16; /**< Immediate value */
} i_type; } i_type; /**< I-type instruction format */
struct { struct {
uint32_t op : 6; uint32_t op : 6; /**< Opcode field */
uint32_t target : 26; uint32_t target : 26; /**< Target Address field */
} j_type; } j_type; /**< J-type instruction format */
struct { struct {
uint32_t op : 6; uint32_t op : 6; /**< Opcode field */
uint32_t rs : 5; uint32_t rs : 5; /**< Source register */
uint32_t rt : 5; uint32_t rt : 5; /**< Target register */
uint32_t rd : 5; uint32_t rd : 5; /**< Destination register */
uint32_t sa : 5; uint32_t sa : 5; /**< Shift amount */
uint32_t funct : 6; uint32_t funct : 6; /**< Function field */
} r_type; } r_type; /**< Alternate R-type instruction format */
struct { struct {
uint32_t op : 6; uint32_t op : 6; /**< Opcode field */
uint32_t co : 1; uint32_t co : 1; /**< Coprocessor operation bit */
uint32_t funct : 25; uint32_t funct : 25; /**< Function field */
} c_type; } c_type; /**< C-type instruction format */
} vr4300_instruction_t; } vr4300_instruction_t;
/**
* @brief VR4300 Opcode Enumeration
*
* Enumeration for different opcodes used in VR4300 instructions.
*/
typedef enum { typedef enum {
OP_SPECIAL, OP_SPECIAL,
OP_REGIMM, 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_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) #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_64DD: return false;
case FLASHCART_FEATURE_RTC: return true; case FLASHCART_FEATURE_RTC: return true;
case FLASHCART_FEATURE_USB: 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; 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 sdram_size = MiB(64);
size_t chunk_size = KiB(128); 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); size_t block_size = MIN(sdram_size - offset, chunk_size);
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) { if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
f_close(&fil); f_close(&fil);

View File

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

View File

@ -29,6 +29,11 @@ typedef enum {
FLASHCART_FEATURE_64DD, FLASHCART_FEATURE_64DD,
FLASHCART_FEATURE_RTC, FLASHCART_FEATURE_RTC,
FLASHCART_FEATURE_USB, 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; } flashcart_features_t;
/** @brief Flashcart save type enumeration */ /** @brief Flashcart save type enumeration */

View File

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

View File

@ -232,7 +232,7 @@ static flashcart_err_t sc64_init (void) {
{ CFG_ID_ROM_EXTENDED_ENABLE, false }, { 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) { if (sc64_ll_set_config(default_config[i].id, default_config[i].value) != SC64_OK) {
return FLASHCART_ERR_INT; 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_64DD: return true;
case FLASHCART_FEATURE_RTC: return true; case FLASHCART_FEATURE_RTC: return true;
case FLASHCART_FEATURE_USB: 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; 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 extended_size = extended_enabled ? rom_size - MiB(64) : 0;
size_t chunk_size = KiB(128); 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); size_t block_size = MIN(sdram_size - offset, chunk_size);
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) { if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
f_close(&fil); 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); 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); size_t block_size = MIN(ipl_size - offset, chunk_size);
if (f_read(&fil, (void *) (IPL_ADDRESS + offset), block_size, &br) != FR_OK) { if (f_read(&fil, (void *) (IPL_ADDRESS + offset), block_size, &br) != FR_OK) {
f_close(&fil); 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 }, { 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) { if (sc64_ll_set_config(config[i].id, config[i].value) != SC64_OK) {
return FLASHCART_ERR_INT; 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) { 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 held_dir = JOYPAD_8WAY_NONE;
joypad_8way_t fast_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_C); 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) { if (fast_dir != JOYPAD_8WAY_NONE) {
held_dir = fast_dir; held_dir = fast_dir;
@ -82,7 +90,14 @@ static void actions_update_direction (menu_t *menu) {
} }
static void actions_update_buttons (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) { if (pressed.a) {
menu->actions.enter = true; 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) { void actions_update (menu_t *menu) {
joypad_poll(); joypad_poll();

View File

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

View File

@ -1,74 +1,261 @@
/** /**
* @file components.h * @file components.h
* @brief Menu Components * @brief Menu Graphical User Interface Components
* @ingroup menu * @ingroup menu
*/ */
#ifndef COMPONENTS_H__ #ifndef COMPONENTS_H__
#define COMPONENTS_H__ #define COMPONENTS_H__
#include <libdragon.h> #include <libdragon.h>
#include "menu_state.h" #include "menu_state.h"
/** /**
* @addtogroup * @addtogroup menu_ui_components
* @{ menu_components * @{
*/ */
void component_box_draw (int x0, int y0, int x1, int y1, color_t color); /**
void component_border_draw (int x0, int y0, int x1, int y1); * @brief File image Enumeration.
void component_layout_draw (void); *
void component_progressbar_draw (int x0, int y0, int x1, int y1, float progress); * Enumeration for different types of file images used in the GUI.
void component_seekbar_draw (float progress); */
void component_loader_draw (float position); typedef enum {
void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items); IMAGE_BOXART_FRONT, /**< Boxart image from the front */
void component_list_scrollbar_draw (int position, int items, int visible_items); IMAGE_BOXART_BACK, /**< Boxart image from the back */
void component_dialog_draw (int width, int height); IMAGE_BOXART_TOP, /**< Boxart image from the top */
void component_messagebox_draw (char *fmt, ...); IMAGE_BOXART_BOTTOM, /**< Boxart image from the bottom */
void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); IMAGE_BOXART_LEFT, /**< Boxart image from the left side */
void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); 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;
void component_background_init (char *cache_location); /**
void component_background_free (void); * @brief Draw a box component.
void component_background_replace_image (surface_t *image); *
void component_background_draw (void); * @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);
void component_file_list_draw (entry_t *list, int entries, int selected); /**
* @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 { typedef struct component_context_menu {
int count; int row_count; /**< Number of rows in the context menu */
int selected; int row_selected; /**< Index of the selected row */
bool hide_pending; bool hide_pending; /**< Flag to indicate if hiding is pending */
struct component_context_menu *parent; struct component_context_menu *parent; /**< Pointer to the parent context menu */
struct component_context_menu *submenu; struct component_context_menu *submenu; /**< Pointer to the submenu */
struct { struct {
const char *text; const char *text; /**< Text of the menu item */
void (*action) (menu_t *menu, void *arg); void (*action)(menu_t *menu, void *arg); /**< Action function for the menu item */
void *arg; void *arg; /**< Argument for the action function */
struct component_context_menu *submenu; struct component_context_menu *submenu; /**< Pointer to the submenu */
} list[]; } list[]; /**< List of menu items */
} component_context_menu_t; } 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 */
void component_context_menu_init (component_context_menu_t *cm); /**
void component_context_menu_show (component_context_menu_t *cm); * @brief Initialize the context menu component.
bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm); *
void component_context_menu_draw (component_context_menu_t *cm); * @param cm Pointer to the context menu structure.
*/
void component_context_menu_init(component_context_menu_t *cm);
/** @brief Box Art Structure. */ /**
* @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.
*/
typedef struct { typedef struct {
bool loading; bool loading; /**< Flag to indicate if the box art is loading */
surface_t *image; surface_t *image; /**< Pointer to the box art image */
} component_boxart_t; } component_boxart_t;
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code); /**
void component_boxart_free (component_boxart_t *b); * @brief Initialize the box art component.
void component_boxart_draw (component_boxart_t *b); *
* @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);
/** @} */ /* menu_components */ /**
* @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);
#endif /** @} */ /* menu_ui_components */
#endif /* COMPONENTS_H__ */

View File

@ -98,9 +98,6 @@ static void prepare_background (component_background_t *c) {
return; return;
} }
uint16_t image_center_x = (c->image->width / 2);
uint16_t image_center_y = (c->image->height / 2);
// Darken the image // Darken the image
rdpq_attach(c->image, NULL); rdpq_attach(c->image, NULL);
rdpq_mode_push(); rdpq_mode_push();
@ -108,15 +105,13 @@ static void prepare_background (component_background_t *c) {
rdpq_set_prim_color(BACKGROUND_OVERLAY_COLOR); rdpq_set_prim_color(BACKGROUND_OVERLAY_COLOR);
rdpq_mode_combiner(RDPQ_COMBINER_FLAT); rdpq_mode_combiner(RDPQ_COMBINER_FLAT);
rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY); rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY);
rdpq_fill_rectangle( rdpq_fill_rectangle(0, 0, c->image->width, c->image->height);
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_mode_pop(); rdpq_mode_pop();
rdpq_detach(); rdpq_detach();
uint16_t image_center_x = (c->image->width / 2);
uint16_t image_center_y = (c->image->height / 2);
// Prepare display list // Prepare display list
rspq_block_begin(); rspq_block_begin();
rdpq_mode_push(); 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; component_boxart_t *b;
char file_name[8]; char boxart_id_path[8];
if ((b = calloc(1, sizeof(component_boxart_t))) == NULL) { if ((b = calloc(1, sizeof(component_boxart_t))) == NULL) {
return NULL; return NULL;
@ -29,22 +29,91 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam
path_t *path = path_init(storage_prefix, BOXART_DIRECTORY); path_t *path = path_init(storage_prefix, BOXART_DIRECTORY);
sprintf(file_name, "%.3s.png", game_code); sprintf(boxart_id_path, "%c/%c/%c/%c", game_code[0], game_code[1], game_code[2], game_code[3]);
path_push(path, file_name); path_push(path, boxart_id_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);
// TODO: This is bad, we should only check for 3 letter codes if (!directory_exists(path_get(path))) { // Allow boxart to not specify the region code.
sprintf(file_name, "%.2s.png", game_code + 1); path_pop(path);
path_push(path, file_name);
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
path_free(path);
return b;
} }
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);
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); path_free(path);
free(b); free(b);
@ -89,4 +158,4 @@ void component_boxart_draw (component_boxart_t *b) {
BOXART_LOADING_COLOR BOXART_LOADING_COLOR
); );
} }
} }

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), .height = LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT - (TEXT_MARGIN_VERTICAL * 2),
.align = align, .align = align,
.valign = valign, .valign = valign,
.wrap = WRAP_ELLIPSES, .wrap = WRAP_WORD,
.line_spacing = TEXT_LINE_SPACING_ADJUST, .line_spacing = TEXT_LINE_SPACING_ADJUST,
}, },
FNT_DEFAULT, 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) { void component_context_menu_init (component_context_menu_t *cm) {
cm->selected = -1; cm->row_selected = -1;
cm->count = 0; cm->row_count = 0;
cm->hide_pending = false; cm->hide_pending = false;
cm->parent = NULL; cm->parent = NULL;
for (int i = 0; (cm->list[i].text) != NULL; i++) { 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) { void component_context_menu_show (component_context_menu_t *cm) {
cm->selected = 0; cm->row_selected = 0;
cm->submenu = NULL; cm->submenu = NULL;
} }
bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) { 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; return false;
} }
@ -44,26 +44,26 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm)
} }
sound_play_effect(SFX_EXIT); sound_play_effect(SFX_EXIT);
} else if (menu->actions.enter) { } else if (menu->actions.enter) {
if (cm->list[cm->selected].submenu) { if (cm->list[cm->row_selected].submenu) {
cm->submenu = cm->list[cm->selected].submenu; cm->submenu = cm->list[cm->row_selected].submenu;
component_context_menu_init(cm->submenu); component_context_menu_init(cm->submenu);
cm->submenu->selected = 0; cm->submenu->row_selected = 0;
cm->submenu->parent = cm; cm->submenu->parent = cm;
} else if (cm->list[cm->selected].action) { } else if (cm->list[cm->row_selected].action) {
cm->list[cm->selected].action(menu, cm->list[cm->selected].arg); cm->list[cm->row_selected].action(menu, cm->list[cm->row_selected].arg);
top->hide_pending = true; top->hide_pending = true;
} }
sound_play_effect(SFX_ENTER); sound_play_effect(SFX_ENTER);
} else if (menu->actions.go_up) { } else if (menu->actions.go_up) {
cm->selected -= 1; cm->row_selected -= 1;
if (cm->selected < 0) { if (cm->row_selected < 0) {
cm->selected = 0; cm->row_selected = 0;
} }
sound_play_effect(SFX_CURSOR); sound_play_effect(SFX_CURSOR);
} else if (menu->actions.go_down) { } else if (menu->actions.go_down) {
cm->selected += 1; cm->row_selected += 1;
if (cm->selected >= cm->count) { if (cm->row_selected >= cm->row_count) {
cm->selected = (cm->count - 1); cm->row_selected = (cm->row_count - 1);
} }
sound_play_effect(SFX_CURSOR); 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) { void component_context_menu_draw (component_context_menu_t *cm) {
if (!cm || (cm->selected < 0)) { if (!cm || (cm->row_selected < 0)) {
return; return;
} }
@ -92,7 +92,7 @@ void component_context_menu_draw (component_context_menu_t *cm) {
NULL 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; const char *text = cm->list[i].text;
rdpq_paragraph_builder_span(text, strlen(text)); rdpq_paragraph_builder_span(text, strlen(text));
if (cm->list[i + 1].text != NULL) { 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_x0 = DISPLAY_CENTER_X - (width / 2);
int highlight_x1 = 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_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( component_box_draw(
highlight_x0, highlight_x0,
@ -126,6 +126,6 @@ void component_context_menu_draw (component_context_menu_t *cm) {
if (top->hide_pending) { if (top->hide_pending) {
top->hide_pending = false; top->hide_pending = false;
top->selected = -1; top->row_selected = -1;
} }
} }

View File

@ -52,7 +52,17 @@ typedef struct {
} disk_info_t; } 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); 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__ #ifndef FONTS_H__
#define 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 { typedef enum {
FNT_DEFAULT = 1, FNT_DEFAULT = 1, /**< Default font type */
} menu_font_type_t; } 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 { typedef enum {
STL_DEFAULT = 0, STL_DEFAULT = 0, /**< Default font style */
STL_GREEN, STL_GREEN, /**< Green font style */
STL_BLUE, STL_BLUE, /**< Blue font style */
STL_YELLOW, STL_YELLOW, /**< Yellow font style */
STL_ORANGE, STL_ORANGE, /**< Orange font style */
STL_GRAY, STL_GRAY, /**< Gray font style */
} menu_font_style_t; } 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);
void fonts_init (char *custom_font_path); #endif /* FONTS_H__ */
#endif

View File

@ -27,53 +27,19 @@
#define MENU_CACHE_DIRECTORY "cache" #define MENU_CACHE_DIRECTORY "cache"
#define BACKGROUND_CACHE_FILE "background.data" #define BACKGROUND_CACHE_FILE "background.data"
#define FRAMERATE_DIVIDER (2) #define INTERLACED (true)
#define LAG_REPORT (false) #define FPS_LIMIT (30.0f)
static menu_t *menu; 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) { static void menu_init (boot_params_t *boot_params) {
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)); menu = calloc(1, sizeof(menu_t));
assert(menu != NULL); assert(menu != NULL);
menu->boot_params = boot_params;
menu->mode = MENU_MODE_NONE; menu->mode = MENU_MODE_NONE;
menu->next_mode = MENU_MODE_STARTUP; 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; 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); path_t *path = path_init(menu->storage_prefix, MENU_DIRECTORY);
directory_create(path_get(path)); directory_create(path_get(path));
@ -91,6 +70,15 @@ static void menu_init (boot_params_t *boot_params) {
settings_load(&menu->settings); settings_load(&menu->settings);
path_pop(path); 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); path_push(path, MENU_CUSTOM_FONT_FILE);
fonts_init(path_get(path)); fonts_init(path_get(path));
path_pop(path); path_pop(path);
@ -103,40 +91,20 @@ static void menu_init (boot_params_t *boot_params) {
path_free(path); 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); menu->browser.directory = path_init(menu->storage_prefix, menu->settings.default_directory);
if (!directory_exists(path_get(menu->browser.directory))) { if (!directory_exists(path_get(menu->browser.directory))) {
path_free(menu->browser.directory); path_free(menu->browser.directory);
menu->browser.directory = path_init(menu->storage_prefix, "/"); 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) { 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); hdmi_send_game_id(menu->boot_params);
component_background_free();
path_free(menu->load.disk_path); path_free(menu->load.disk_path);
path_free(menu->load.rom_path); path_free(menu->load.rom_path);
for (int i = 0; i < menu->browser.entries; i++) { 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); path_free(menu->browser.directory);
free(menu); free(menu);
component_background_free(); display_close();
flashcart_deinit();
sound_deinit(); sound_deinit();
@ -158,7 +124,7 @@ static void menu_deinit (menu_t *menu) {
timer_close(); timer_close();
joypad_close(); joypad_close();
display_close(); flashcart_deinit();
} }
typedef const struct { typedef const struct {
@ -201,11 +167,9 @@ void menu_run (boot_params_t *boot_params) {
menu_init(boot_params); menu_init(boot_params);
while (true) { while (true) {
surface_t *display = (frame_counter >= FRAMERATE_DIVIDER) ? display_try_get() : NULL; surface_t *display = display_try_get();
if (display != NULL) { if (display != NULL) {
frame_counter_reset();
actions_update(menu); actions_update(menu);
view_t *view = menu_get_view(menu->mode); view_t *view = menu_get_view(menu->mode);

View File

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

View File

@ -7,37 +7,166 @@
#ifndef MP3_PLAYER_H__ #ifndef MP3_PLAYER_H__
#define MP3_PLAYER_H__ #define MP3_PLAYER_H__
#include <stdbool.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 { typedef enum {
MP3PLAYER_OK, MP3PLAYER_OK, /**< No error */
MP3PLAYER_ERR_OUT_OF_MEM, MP3PLAYER_ERR_OUT_OF_MEM, /**< Out of memory error */
MP3PLAYER_ERR_IO, MP3PLAYER_ERR_IO, /**< Input/Output error */
MP3PLAYER_ERR_NO_FILE, MP3PLAYER_ERR_NO_FILE, /**< No file found error */
MP3PLAYER_ERR_INVALID_FILE, MP3PLAYER_ERR_INVALID_FILE, /**< Invalid file error */
} mp3player_err_t; } mp3player_err_t;
/**
* @brief Initialize the MP3 player mixer.
*
* This function initializes the mixer for the MP3 player.
*/
void mp3player_mixer_init(void);
void mp3player_mixer_init (void); /**
mp3player_err_t mp3player_init (void); * @brief Initialize the MP3 player.
void mp3player_deinit (void); *
mp3player_err_t mp3player_load (char *path); * This function initializes the MP3 player and prepares it for playback.
void mp3player_unload (void); *
mp3player_err_t mp3player_process (void); * @return mp3player_err_t Error code indicating the result of the initialization.
bool mp3player_is_playing (void); */
bool mp3player_is_finished (void); mp3player_err_t mp3player_init(void);
mp3player_err_t mp3player_play (void);
void mp3player_stop (void);
mp3player_err_t mp3player_toggle (void);
void mp3player_mute (bool mute);
mp3player_err_t mp3player_seek (int seconds);
float mp3player_get_duration (void);
float mp3player_get_bitrate (void);
int mp3player_get_samplerate (void);
float mp3player_get_progress (void);
/**
* @brief Deinitialize the MP3 player.
*
* This function deinitializes the MP3 player and releases any resources.
*/
void mp3player_deinit(void);
#endif /**
* @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 /* MP3_PLAYER_H__ */

View File

@ -7,27 +7,68 @@
#ifndef PNG_DECODER_H__ #ifndef PNG_DECODER_H__
#define PNG_DECODER_H__ #define PNG_DECODER_H__
#include <surface.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 { typedef enum {
PNG_OK, PNG_OK, /**< No error */
PNG_ERR_INT, PNG_ERR_INT, /**< Internal error */
PNG_ERR_BUSY, PNG_ERR_BUSY, /**< Decoder is busy */
PNG_ERR_OUT_OF_MEM, PNG_ERR_OUT_OF_MEM, /**< Out of memory error */
PNG_ERR_NO_FILE, PNG_ERR_NO_FILE, /**< No file found error */
PNG_ERR_BAD_FILE, PNG_ERR_BAD_FILE, /**< Bad file error */
} png_err_t; } 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); 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); 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); 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); 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); void png_decoder_poll (void);
#endif /* PNG_DECODER_H__ */
#endif

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("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_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("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("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("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("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("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("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("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)); mini_t *ini = mini_try_load(path_get(overrides_path));
if (!ini) { if (!ini) {
path_free(overrides_path);
return ROM_ERR_SAVE_IO; return ROM_ERR_SAVE_IO;
} }

View File

@ -14,7 +14,10 @@ static settings_t init = {
.default_directory = "/", .default_directory = "/",
.use_saves_folder = true, .use_saves_folder = true,
.sound_enabled = true, .sound_enabled = true,
.rom_autoload_enabled = false,
.rom_autoload_path = "",
.rom_autoload_filename = "",
/* Beta feature flags (should always init to off) */ /* Beta feature flags (should always init to off) */
.bgm_enabled = false, .bgm_enabled = false,
.rumble_enabled = false, .rumble_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->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->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 */ /* 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->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); 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_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", "use_saves_folder", settings->use_saves_folder);
mini_set_bool(ini, "menu", "sound_enabled", settings->sound_enabled); 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! */ /* 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", "bgm_enabled", settings->bgm_enabled);

View File

@ -30,6 +30,16 @@ typedef struct {
/** @brief Enable rumble feedback */ /** @brief Enable rumble feedback */
bool rumble_enabled; 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; } settings_t;
@ -40,5 +50,4 @@ void settings_load (settings_t *settings);
/** @brief The settings to save */ /** @brief The settings to save */
void settings_save (settings_t *settings); void settings_save (settings_t *settings);
#endif #endif

View File

@ -86,6 +86,13 @@ void sound_play_effect(sound_effect_t sfx) {
void sound_deinit (void) { void sound_deinit (void) {
if (sound_initialized) { 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(); mixer_close();
audio_close(); audio_close();
sound_initialized = false; sound_initialized = false;
@ -93,9 +100,7 @@ void sound_deinit (void) {
} }
void sound_poll (void) { void sound_poll (void) {
if (sound_initialized && audio_can_write()) { if (sound_initialized) {
short *audio_buffer = audio_write_begin(); mixer_try_play();
mixer_poll(audio_buffer, audio_get_buffer_length());
audio_write_end();
} }
} }

View File

@ -9,24 +9,59 @@
#include <stdbool.h> #include <stdbool.h>
#define SOUND_MP3_PLAYER_CHANNEL (0) #define SOUND_MP3_PLAYER_CHANNEL (0) /**< Channel for MP3 player sound */
#define SOUND_SFX_CHANNEL (2) #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 { typedef enum {
SFX_CURSOR, SFX_CURSOR, /**< Sound effect for cursor movement */
SFX_ERROR, SFX_ERROR, /**< Sound effect for error */
SFX_ENTER, SFX_ENTER, /**< Sound effect for entering a menu */
SFX_EXIT, SFX_EXIT, /**< Sound effect for exiting a menu */
SFX_SETTING, SFX_SETTING, /**< Sound effect for changing a setting */
} sound_effect_t; } 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);
void sound_init_default (void); /**
void sound_init_mp3_playback (void); * @brief Initialize the MP3 playback system.
void sound_init_sfx (void); *
* 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); 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_play_effect(sound_effect_t sfx);
void sound_deinit (void); void sound_deinit (void);
void sound_poll (void); void sound_poll (void);
#endif #endif /* SOUND_H__ */

View File

@ -2,20 +2,36 @@
* @file usb_comm.h * @file usb_comm.h
* @brief USB communication subsystem * @brief USB communication subsystem
* @ingroup menu * @ingroup menu
*
* This file contains the declarations for the USB communication subsystem
* used in the menu system.
*/ */
#ifndef USB_COMM_H__ #ifndef USB_COMM_H__
#define USB_COMM_H__ #define USB_COMM_H__
#include "menu_state.h" #include "menu_state.h"
#ifndef NDEBUG #ifndef NDEBUG
void usb_comm_poll (menu_t *menu); /**
* @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 #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) #define usb_comm_poll(menu)
#endif #endif
#endif /* USB_COMM_H__ */
#endif

View File

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

View File

@ -4,8 +4,8 @@
static void process (menu_t *menu) { static void process (menu_t *menu) {
if (menu->actions.back) { if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); 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)); rdpq_clear(RGBA32(0x7F, 0x00, 0x00, 0xFF));
const char *firmware_message = ( const char *firmware_message = (
"Supported firmware versions:\n" "Minimum supported firmware versions:\n"
"64drive: 2.05+\n" "64drive: 2.05+\n"
"EverDrive-64: ???+\n" "EverDrive-64: ???+\n"
"SummerCart64: 2.17.0+" "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) { static void process (menu_t *menu) {
if (menu->actions.back) { if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); 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", S_ISDIR(st.st_mode) ? "Directory" : "File",
st.st_mode & S_IWUSR ? "" : "(Read only)", st.st_mode & S_IWUSR ? "" : "(Read only)",
format_file_type(menu->browser.entry->name, S_ISDIR(st.st_mode)), 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( component_actions_bar_text_draw(

View File

@ -1,11 +1,35 @@
#include "views.h" #include "views.h"
#include "../sound.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) { static void process (menu_t *menu) {
if (menu->actions.back) { if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); 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( component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP, ALIGN_CENTER, VALIGN_TOP,
"FLASHCART INFORMATION\n" "FLASHCART INFORMATION"
"\n" "\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( component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP, ALIGN_LEFT, VALIGN_TOP,
"\n" "\n"
"\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( component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP, ALIGN_LEFT, VALIGN_TOP,

View File

@ -5,8 +5,7 @@
#include "views.h" #include "views.h"
static bool load_pending; static bool load_disk_with_rom;
static bool load_rom;
static component_boxart_t *boxart; static component_boxart_t *boxart;
@ -31,15 +30,15 @@ static char *format_disk_region (disk_region_t region) {
static void process (menu_t *menu) { static void process (menu_t *menu) {
if (menu->actions.enter) { if (menu->actions.enter) {
load_pending = true; menu->boot_pending.disk_file = true;
load_rom = false; load_disk_with_rom = false;
} else if (menu->actions.options && menu->load.rom_path) { } else if (menu->actions.options && menu->load.rom_path) {
load_pending = true; menu->boot_pending.disk_file = true;
load_rom = true; load_disk_with_rom = true;
sound_play_effect(SFX_SETTING); sound_play_effect(SFX_SETTING);
} else if (menu->actions.back) { } else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); 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(); component_background_draw();
if (load_pending) { if (menu->boot_pending.disk_file) {
component_loader_draw(0.0f); component_loader_draw(0.0f);
} else { } else {
component_layout_draw(); component_layout_draw();
@ -94,7 +93,9 @@ static void draw (menu_t *menu, surface_t *d) {
); );
} }
component_boxart_draw(boxart); if (boxart != NULL) {
component_boxart_draw(boxart);
}
} }
rdpq_detach_show(); rdpq_detach_show();
@ -117,7 +118,7 @@ static void draw_progress (float progress) {
static void load (menu_t *menu) { static void load (menu_t *menu) {
cart_load_err_t err; 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); err = cart_load_n64_rom_and_save(menu, draw_progress);
if (err != CART_LOAD_OK) { if (err != CART_LOAD_OK) {
menu_show_error(menu, cart_load_convert_error_message(err)); 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; 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->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); 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)) { 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; 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); 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); disk_err_t err = disk_info_load(menu->load.disk_path, &menu->load.disk_info);
if (err != DISK_OK) { if (err != DISK_OK) {
menu_show_error(menu, convert_error_message(err)); 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) { 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); draw(menu, display);
if (load_pending) { if (menu->boot_pending.disk_file) {
load_pending = false; menu->boot_pending.disk_file = false;
load(menu); 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_gameboy_color_rom_extensions[] = { "gbc", NULL };
static const char *emu_sega_8bit_rom_extensions[] = { "sms", "gg", "sg", 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 cart_load_emu_type_t emu_type;
static char *format_emulator_name (cart_load_emu_type_t emulator_info) { 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) { static void process (menu_t *menu) {
if (menu->actions.enter) { if (menu->actions.enter) {
load_pending = true; menu->boot_pending.emulator_file = true;
} else if (menu->actions.back) { } else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); 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(); component_background_draw();
if (load_pending) { if (menu->boot_pending.emulator_file) {
component_loader_draw(0.0f); component_loader_draw(0.0f);
} else { } else {
component_layout_draw(); component_layout_draw();
@ -107,7 +106,7 @@ static void load (menu_t *menu) {
void view_load_emulator_init (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); 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); draw(menu, display);
if (load_pending) { if (menu->boot_pending.emulator_file) {
load_pending = false; menu->boot_pending.emulator_file = false;
load(menu); load(menu);
} }
} }

View File

@ -3,12 +3,12 @@
#include "boot/boot.h" #include "boot/boot.h"
#include "../sound.h" #include "../sound.h"
#include "views.h" #include "views.h"
#include <string.h>
#include "utils/fs.h"
static bool show_extra_info_message = false; static bool show_extra_info_message = false;
static bool load_pending;
static component_boxart_t *boxart; static component_boxart_t *boxart;
static char *convert_error_message (rom_err_t err) { static char *convert_error_message (rom_err_t err) {
switch (err) { switch (err) {
case ROM_ERR_LOAD_IO: return "I/O error during loading ROM information and/or options"; 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; 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 = { static component_context_menu_t set_cic_type_context_menu = { .list = {
{.text = "Automatic", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_AUTOMATIC) }, {.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) }, {.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 CIC Type", .submenu = &set_cic_type_context_menu },
{ .text = "Set Save Type", .submenu = &set_save_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 TV Type", .submenu = &set_tv_type_context_menu },
{ .text = "Set ROM to autoload", .action = set_autoload_type },
COMPONENT_CONTEXT_MENU_LIST_END, COMPONENT_CONTEXT_MENU_LIST_END,
}}; }};
@ -196,10 +208,10 @@ static void process (menu_t *menu) {
} }
if (menu->actions.enter) { if (menu->actions.enter) {
load_pending = true; menu->boot_pending.rom_file = true;
} else if (menu->actions.back) { } else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
} else if (menu->actions.options) { } else if (menu->actions.options) {
component_context_menu_show(&options_context_menu); component_context_menu_show(&options_context_menu);
sound_play_effect(SFX_SETTING); sound_play_effect(SFX_SETTING);
@ -218,7 +230,7 @@ static void draw (menu_t *menu, surface_t *d) {
component_background_draw(); component_background_draw();
if (load_pending) { if (menu->boot_pending.rom_file) {
component_loader_draw(0.0f); component_loader_draw(0.0f);
} else { } else {
component_layout_draw(); component_layout_draw();
@ -262,7 +274,9 @@ static void draw (menu_t *menu, surface_t *d) {
"R: Options" "R: Options"
); );
component_boxart_draw(boxart); if (boxart != NULL) {
component_boxart_draw(boxart);
}
if (show_extra_info_message) { if (show_extra_info_message) {
component_messagebox_draw( component_messagebox_draw(
@ -335,18 +349,19 @@ static void load (menu_t *menu) {
static void deinit (void) { static void deinit (void) {
component_boxart_free(boxart); component_boxart_free(boxart);
boxart = NULL;
} }
void view_load_rom_init (menu_t *menu) { 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);
}
if (menu->load.rom_path) { menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
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); rom_err_t err = rom_info_load(menu->load.rom_path, &menu->load.rom_info);
if (err != ROM_OK) { if (err != ROM_OK) {
path_free(menu->load.rom_path); path_free(menu->load.rom_path);
@ -355,9 +370,10 @@ void view_load_rom_init (menu_t *menu) {
return; 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); component_context_menu_init(&options_context_menu);
}
} }
void view_load_rom_display (menu_t *menu, surface_t *display) { void view_load_rom_display (menu_t *menu, surface_t *display) {
@ -365,8 +381,8 @@ void view_load_rom_display (menu_t *menu, surface_t *display) {
draw(menu, display); draw(menu, display);
if (load_pending) { if (menu->boot_pending.rom_file) {
load_pending = false; menu->boot_pending.rom_file = false;
load(menu); load(menu);
} }

View File

@ -41,8 +41,8 @@ static void process (menu_t *menu) {
if (err != MP3PLAYER_OK) { if (err != MP3PLAYER_OK) {
menu_show_error(menu, convert_error_message(err)); menu_show_error(menu, convert_error_message(err));
} else if (menu->actions.back) { } else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
} else if (menu->actions.enter) { } else if (menu->actions.enter) {
err = mp3player_toggle(); err = mp3player_toggle();
if (err != MP3PLAYER_OK) { 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 "../sound.h"
#include "views.h" #include "views.h"
// FIXME: add implementation! #define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; })
// struct { #define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; })
// uint16_t seconds; #define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max)))
// uint16_t minutes;
// uint16_t hours;
// uint16_t day;
// uint16_t month;
// uint16_t year;
// } adjusted_datetime;
// 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) { static void process (menu_t *menu) {
if (menu->actions.back) { if (menu->actions.back && !is_editing_mode) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); 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;
}
} }
} }
@ -30,36 +169,51 @@ static void draw (menu_t *menu, surface_t *d) {
component_layout_draw(); component_layout_draw();
component_main_text_draw( component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP, ALIGN_CENTER, VALIGN_TOP,
"ADJUST REAL TIME CLOCK\n" "ADJUST REAL TIME CLOCK\n"
"\n" "\n"
"\n" "\n"
"To set the date and time, please use the PC terminal\n" "To set the RTC date and time, Press A.\n"
"application and set via USB or a game that uses it.\n\n" "You can also use the PC terminal application via USB,\n"
"Current date & time: %s\n", "or even an N64 game with RTC support.\n"
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n"
);
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n" "\n"
"Current date & time: %s\n"
"\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown"
); );
if (!is_editing_mode) {
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"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"
);
}
component_actions_bar_text_draw( if (is_editing_mode) {
ALIGN_LEFT, VALIGN_TOP, component_editdatetime_draw(rtc_tm, editing_field_type);
"\n" // "A: Save\n" }
"B: Back"
);
rdpq_detach_show(); rdpq_detach_show();
} }
void view_rtc_init (menu_t *menu) { 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) { 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) { 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); settings_save(&menu->settings);
} }
static void set_protected_entries_type (menu_t *menu, void *arg) { 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); settings_save(&menu->settings);
menu->browser.reload = true; menu->browser.reload = true;
} }
static void set_use_saves_folder_type (menu_t *menu, void *arg) { 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); settings_save(&menu->settings);
} }
static void set_sound_enabled_type (menu_t *menu, void *arg) { 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); sound_use_sfx(menu->settings.sound_enabled);
settings_save(&menu->settings); settings_save(&menu->settings);
} }
#ifdef BETA_SETTINGS #ifdef BETA_SETTINGS
static void set_bgm_enabled_type (menu_t *menu, void *arg) { 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); settings_save(&menu->settings);
} }
static void set_rumble_enabled_type (menu_t *menu, void *arg) { 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); 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 = { 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) }, {.text = "Off", .action = set_pal60_type, .arg = (void *) (false) },
COMPONENT_CONTEXT_MENU_LIST_END, COMPONENT_CONTEXT_MENU_LIST_END,
}}; }};
static component_context_menu_t set_protected_entries_type_context_menu = { .list = { static component_context_menu_t set_protected_entries_type_context_menu = { .list = {
{.text = "On", .action = set_protected_entries_type, .arg = (void *) (true) }, {.text = "On", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_protected_entries_type, .arg = (void *) (false) }, {.text = "Off", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(false) },
COMPONENT_CONTEXT_MENU_LIST_END, COMPONENT_CONTEXT_MENU_LIST_END,
}}; }};
static component_context_menu_t set_sound_enabled_type_context_menu = { .list = { static component_context_menu_t set_sound_enabled_type_context_menu = { .list = {
{.text = "On", .action = set_sound_enabled_type, .arg = (void *) (true) }, {.text = "On", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_sound_enabled_type, .arg = (void *) (false) }, {.text = "Off", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(false) },
COMPONENT_CONTEXT_MENU_LIST_END, COMPONENT_CONTEXT_MENU_LIST_END,
}}; }};
static component_context_menu_t set_use_saves_folder_type_context_menu = { .list = { 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 = "On", .action = set_use_saves_folder_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_use_saves_folder_type, .arg = (void *) (false) }, {.text = "Off", .action = set_use_saves_folder_type, .arg = (void *)(uintptr_t)(false) },
COMPONENT_CONTEXT_MENU_LIST_END, COMPONENT_CONTEXT_MENU_LIST_END,
}}; }};
#ifdef BETA_SETTINGS #ifdef BETA_SETTINGS
static component_context_menu_t set_bgm_enabled_type_context_menu = { .list = { static component_context_menu_t set_bgm_enabled_type_context_menu = { .list = {
{.text = "On", .action = set_bgm_enabled_type, .arg = (void *) (true) }, {.text = "On", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_bgm_enabled_type, .arg = (void *) (false) }, {.text = "Off", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(false) },
COMPONENT_CONTEXT_MENU_LIST_END, COMPONENT_CONTEXT_MENU_LIST_END,
}}; }};
static component_context_menu_t set_rumble_enabled_type_context_menu = { .list = { static component_context_menu_t set_rumble_enabled_type_context_menu = { .list = {
{.text = "On", .action = set_rumble_enabled_type, .arg = (void *) (true) }, {.text = "On", .action = set_rumble_enabled_type, .arg = (void *)(uintptr_t)(true) },
{.text = "Off", .action = set_rumble_enabled_type, .arg = (void *) (false) }, {.text = "Off", .action = set_rumble_enabled_type, .arg = (void *)(uintptr_t)(false) },
COMPONENT_CONTEXT_MENU_LIST_END, COMPONENT_CONTEXT_MENU_LIST_END,
}}; }};
#endif #endif
@ -114,8 +114,8 @@ static void process (menu_t *menu) {
component_context_menu_show(&options_context_menu); component_context_menu_show(&options_context_menu);
sound_play_effect(SFX_SETTING); sound_play_effect(SFX_SETTING);
} else if (menu->actions.back) { } else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); 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, ALIGN_LEFT, VALIGN_TOP,
"\n\n" "\n\n"
" Default Directory : %s\n\n" " Default Directory : %s\n\n"
" Autoload ROM : %s\n"
"To change the following menu settings, press 'A':\n" "To change the following menu settings, press 'A':\n"
"* PAL60 Mode : %s\n" "* PAL60 Mode : %s\n"
" Show Hidden Files : %s\n" " Show Hidden Files : %s\n"
@ -145,9 +146,11 @@ static void draw (menu_t *menu, surface_t *d) {
" Background Music : %s\n" " Background Music : %s\n"
" Rumble Feedback : %s\n" " Rumble Feedback : %s\n"
#endif #endif
"Note: Certain settings have the following caveats:\n\n" "\n\n"
"* Requires a flashcart reboot.\n", "Note: Certain settings have the following caveats:\n"
"* Requires rebooting the N64 Console.\n",
menu->settings.default_directory, menu->settings.default_directory,
format_switch(menu->settings.rom_autoload_enabled),
format_switch(menu->settings.pal60_enabled), format_switch(menu->settings.pal60_enabled),
format_switch(menu->settings.show_protected_entries), format_switch(menu->settings.show_protected_entries),
format_switch(menu->settings.use_saves_folder), 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) { 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; menu->next_mode = MENU_MODE_BROWSER;
} }

View File

@ -28,8 +28,8 @@ static void process (menu_t *menu) {
} }
if (menu->actions.back) { if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); 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 2 is %sconnected %s\n"
"Joypad 3 is %sconnected %s\n" "Joypad 3 is %sconnected %s\n"
"Joypad 4 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 ", is_memory_expanded() ? "" : "not ",
(joypad[0]) ? "" : "not ", format_accessory(0), (joypad[0]) ? "" : "not ", format_accessory(0),
(joypad[1]) ? "" : "not ", format_accessory(1), (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) { static void process (menu_t *menu) {
if (menu->actions.back) { if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
sound_play_effect(SFX_EXIT); sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
} else if (text) { } else if (text) {
if (menu->actions.go_up) { if (menu->actions.go_up) {
perform_vertical_scroll(menu->actions.go_fast ? -10 : -1); perform_vertical_scroll(menu->actions.go_fast ? -10 : -1);