Merge branch 'develop' into ed64-xseries

This commit is contained in:
Robin Jones 2024-11-08 01:05:48 +00:00 committed by GitHub
commit de2cb405f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1154 additions and 271 deletions

View File

@ -29,6 +29,7 @@ SRCS = \
flashcart/64drive/64drive_ll.c \
flashcart/64drive/64drive.c \
flashcart/flashcart_utils.c \
flashcart/ed64/ed64_vseries.c \
flashcart/flashcart.c \
flashcart/ed64/ed64x.c \
flashcart/sc64/sc64_ll.c \

View File

@ -26,6 +26,7 @@ An open source menu for N64 flashcarts.
* Real Time Clock support.
* Music playback (MP3).
* Menu sound effects.
* N64 ROM autoload.
## Documentation
@ -49,6 +50,9 @@ An open source menu for N64 flashcarts.
## Experimental features
These features are subject to change:
### N64 ROM autoload
To use the autoload function, while on the `N64 ROM information` display, press the `R` button on your joypad and select the `Set ROM to autoload` option. When you restart the console, it will now only load the selected ROM rather than the menu.
NOTE: to return to the menu, hold joypad `start` button whilst powering on the console.
### GamePak sprites
To use N64 `GamePak` sprites, place `PNG` files within the `sd:/menu/boxart/` folder.
@ -60,7 +64,7 @@ These must be `PNG` files that use the following dimensions:
* Japanese N64 GamePak boxart sprites: 112x158
* 64DD boxart sprites: 129x112
They will be loaded by directories using each character of the full 4 character Game Code (as identified in the menus ROM information).
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`.
@ -70,6 +74,9 @@ 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.
@ -107,10 +114,20 @@ If required, you can manually adjust the file on the SD card using your computer
* Download the latest `menu.bin` file from the [releases](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases/) page, then put it in the root directory of your SD card.
### ED64 & ED64P
### ED64 - WIP - UNTESTED AND UNSUPPORTED - USE AT OWN RISK
Currently not supported, but work is in progress (See [PR's](https://github.com/Polprzewodnikowy/N64FlashcartMenu/pulls)).
NOTE: The menu may be able to load ROM's but not perform saves and may break existing ones..
The aim is to replace [Altra64](https://github.com/networkfusion/altra64) and [ED64-UnofficialOS](https://github.com/n64-tools/ED64-UnofficialOS-binaries).
#### ED64 (Vseries)
The aim is to reach feature parity with [ED64-UnofficialOS](https://github.com/n64-tools/ED64-UnofficialOS-binaries) / [ED64-OfficialOS](https://krikzz.com/pub/support/everdrive-64/v2x-v3x/os-bin/).
Download the `OS64.v64` ROM from the latest [action run - assets] and place it in the `/ED64` folder.
#### ED64 (X series)
X Series support is currently awaiting fixes, in the meantime use the official [OS](https://krikzz.com/pub/support/everdrive-64/x-series/OS/) instead.
#### ED64 (P clone)
Download the `OS64P.v64` ROM from the latest [action run - assets] and place it in the `/ED64P` folder.
The aim is to reach feature parity with [Altra64](https://github.com/networkfusion/altra64)
# Open source software and licenses used

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,155 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <fatfs/ff.h>
#include <libdragon.h>
#include "utils/fs.h"
#include "utils/utils.h"
#include "../flashcart_utils.h"
#include "ed64_vseries.h"
typedef enum {
ED64_V1_0 = 110,
ED64_V2_0 = 320,
ED64_V2_5 = 325,
ED64_V3_0 = 330,
} ed64_vseries_device_variant_t;
/* ED64 save location base address */
#define SRAM_ADDRESS (0xA8000000)
/* ED64 ROM location base address */
#define ROM_ADDRESS (0xB0000000)
static flashcart_err_t ed64_vseries_init (void) {
return FLASHCART_OK;
}
static flashcart_err_t ed64_vseries_deinit (void) {
return FLASHCART_OK;
}
static ed64_vseries_device_variant_t get_cart_model() {
ed64_vseries_device_variant_t variant = ED64_V1_0; // FIXME: check cart model from ll for better feature handling.
return variant;
}
static bool ed64_vseries_has_feature (flashcart_features_t feature) {
bool is_model_v3 = (get_cart_model() == ED64_V3_0);
switch (feature) {
case FLASHCART_FEATURE_RTC: return is_model_v3 ? true : false;
case FLASHCART_FEATURE_USB: return is_model_v3 ? true : false;
case FLASHCART_FEATURE_AUTO_CIC: return is_model_v3 ? true : false;
default: return false;
}
}
static flashcart_err_t ed64_vseries_load_rom (char *rom_path, flashcart_progress_callback_t *progress) {
FIL fil;
UINT br;
if (f_open(&fil, strip_fs_prefix(rom_path), FA_READ) != FR_OK) {
return FLASHCART_ERR_LOAD;
}
fatfs_fix_file_size(&fil);
size_t rom_size = f_size(&fil);
if (rom_size > MiB(64)) {
f_close(&fil);
return FLASHCART_ERR_LOAD;
}
size_t sdram_size = MiB(64);
size_t chunk_size = KiB(128);
for (int offset = 0; offset < sdram_size; offset += chunk_size) {
size_t block_size = MIN(sdram_size - offset, chunk_size);
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
f_close(&fil);
return FLASHCART_ERR_LOAD;
}
if (progress) {
progress(f_tell(&fil) / (float) (f_size(&fil)));
}
}
if (f_tell(&fil) != rom_size) {
f_close(&fil);
return FLASHCART_ERR_LOAD;
}
if (f_close(&fil) != FR_OK) {
return FLASHCART_ERR_LOAD;
}
return FLASHCART_OK;
}
static flashcart_err_t ed64_vseries_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) {
FIL fil;
UINT br;
if (f_open(&fil, strip_fs_prefix(file_path), FA_READ) != FR_OK) {
return FLASHCART_ERR_LOAD;
}
fatfs_fix_file_size(&fil);
size_t file_size = f_size(&fil) - file_offset;
if (file_size > (MiB(64) - rom_offset)) {
f_close(&fil);
return FLASHCART_ERR_ARGS;
}
if (f_lseek(&fil, file_offset) != FR_OK) {
f_close(&fil);
return FLASHCART_ERR_LOAD;
}
if (f_read(&fil, (void *) (ROM_ADDRESS + rom_offset), file_size, &br) != FR_OK) {
f_close(&fil);
return FLASHCART_ERR_LOAD;
}
if (br != file_size) {
f_close(&fil);
return FLASHCART_ERR_LOAD;
}
if (f_close(&fil) != FR_OK) {
return FLASHCART_ERR_LOAD;
}
return FLASHCART_OK;
}
static flashcart_err_t ed64_vseries_load_save (char *save_path) {
// FIXME: the savetype will be none.
return FLASHCART_OK;
}
static flashcart_err_t ed64_vseries_set_save_type (flashcart_save_type_t save_type) {
// FIXME: the savetype will be none.
return FLASHCART_OK;
}
static flashcart_t flashcart_ed64_vseries = {
.init = ed64_vseries_init,
.deinit = ed64_vseries_deinit,
.has_feature = ed64_vseries_has_feature,
.load_rom = ed64_vseries_load_rom,
.load_file = ed64_vseries_load_file,
.load_save = ed64_vseries_load_save,
.load_64dd_ipl = NULL,
.load_64dd_disk = NULL,
.set_save_type = ed64_vseries_set_save_type,
.set_save_writeback = NULL,
};
flashcart_t *ed64_vseries_get_flashcart (void) {
return &flashcart_ed64_vseries;
}

View File

@ -0,0 +1,24 @@
/**
* @file ed64_vseries.h
* @brief ED64 Vseries flashcart support
* @ingroup flashcart
*/
#ifndef FLASHCART_ED64_VSERIES_H__
#define FLASHCART_ED64_VSERIES_H__
#include "../flashcart.h"
/**
* @addtogroup ED64_Vseries
* @{
*/
flashcart_t *ed64_vseries_get_flashcart (void);
/** @} */ /* ED64_Vseries */
#endif

View File

@ -0,0 +1,24 @@
/**
* @file ed64xseries.h
* @brief ED64 Xseries flashcart support
* @ingroup flashcart
*/
#ifndef FLASHCART_ED64XSERIES_H__
#define FLASHCART_ED64XSERIES_H__
#include "../flashcart.h"
/**
* @addtogroup ED64_Xseries
* @{
*/
flashcart_t *ed64xseries_get_flashcart (void);
/** @} */ /* ED64_Xseries */
#endif

View File

@ -10,6 +10,7 @@
#include "flashcart.h"
#include "flashcart_utils.h"
#include "ed64/ed64_vseries.h"
#include "64drive/64drive.h"
#include "ed64/ed64x.h"
#include "sc64/sc64.h"
@ -110,10 +111,11 @@ flashcart_err_t flashcart_init (const char **storage_prefix) {
break;
case CART_EDX: // Official EverDrive 64 Series X
flashcart = ed64x_get_flashcart();
flashcart = ed64_xseries_get_flashcart();
break;
case CART_ED: // Series V EverDrive-64 or clone
flashcart = ed64_vseries_get_flashcart();
break;
case CART_SC: // SummerCart64

View File

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

View File

@ -1,109 +1,261 @@
/**
* @file components.h
* @brief Menu Components
* @brief Menu Graphical User Interface Components
* @ingroup menu
*/
#ifndef COMPONENTS_H__
#define COMPONENTS_H__
#include <libdragon.h>
#include "menu_state.h"
/** @brief File image Enumeration. */
typedef enum {
/** @brief Boxart image from the front */
IMAGE_BOXART_FRONT,
/** @brief Boxart image from the back */
IMAGE_BOXART_BACK,
/** @brief Boxart image from the top */
IMAGE_BOXART_TOP,
/** @brief Boxart image from the bottom */
IMAGE_BOXART_BOTTOM,
/** @brief Boxart image from the left side */
IMAGE_BOXART_LEFT,
/** @brief Boxart image from the right side */
IMAGE_BOXART_RIGHT,
/** @brief GamePak image from the front */
IMAGE_GAMEPAK_FRONT,
/** @brief GamePak image from the back */
IMAGE_GAMEPAK_BACK,
/** @brief File image thumbnail */
IMAGE_THUMBNAIL,
/** @brief List end marker */
IMAGE_TYPE_END
} file_image_type_t;
/**
* @addtogroup
* @{ menu_components
* @addtogroup menu_ui_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);
void component_layout_draw (void);
void component_progressbar_draw (int x0, int y0, int x1, int y1, float progress);
void component_seekbar_draw (float progress);
void component_loader_draw (float position);
void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items);
void component_list_scrollbar_draw (int position, int items, int visible_items);
void component_dialog_draw (int width, int height);
void component_messagebox_draw (char *fmt, ...);
void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
/**
* @brief File image Enumeration.
*
* Enumeration for different types of file images used in the GUI.
*/
typedef enum {
IMAGE_BOXART_FRONT, /**< Boxart image from the front */
IMAGE_BOXART_BACK, /**< Boxart image from the back */
IMAGE_BOXART_TOP, /**< Boxart image from the top */
IMAGE_BOXART_BOTTOM, /**< Boxart image from the bottom */
IMAGE_BOXART_LEFT, /**< Boxart image from the left side */
IMAGE_BOXART_RIGHT, /**< Boxart image from the right side */
IMAGE_GAMEPAK_FRONT, /**< GamePak image from the front */
IMAGE_GAMEPAK_BACK, /**< GamePak image from the back */
IMAGE_THUMBNAIL, /**< File image thumbnail */
IMAGE_TYPE_END /**< List end marker */
} file_image_type_t;
void component_background_init (char *cache_location);
void component_background_free (void);
void component_background_replace_image (surface_t *image);
void component_background_draw (void);
/**
* @brief Draw a box component.
*
* @param x0 Starting x-coordinate.
* @param y0 Starting y-coordinate.
* @param x1 Ending x-coordinate.
* @param y1 Ending y-coordinate.
* @param color Color of the box.
*/
void component_box_draw(int x0, int y0, int x1, int y1, color_t color);
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 {
int row_count;
int row_selected;
bool hide_pending;
struct component_context_menu *parent;
struct component_context_menu *submenu;
int row_count; /**< Number of rows in the context menu */
int row_selected; /**< Index of the selected row */
bool hide_pending; /**< Flag to indicate if hiding is pending */
struct component_context_menu *parent; /**< Pointer to the parent context menu */
struct component_context_menu *submenu; /**< Pointer to the submenu */
struct {
const char *text;
void (*action) (menu_t *menu, void *arg);
void *arg;
struct component_context_menu *submenu;
} list[];
const char *text; /**< Text of the menu item */
void (*action)(menu_t *menu, void *arg); /**< Action function for the menu item */
void *arg; /**< Argument for the action function */
struct component_context_menu *submenu; /**< Pointer to the submenu */
} list[]; /**< List of menu items */
} component_context_menu_t;
#define COMPONENT_CONTEXT_MENU_LIST_END { .text = NULL }
#define COMPONENT_CONTEXT_MENU_LIST_END { .text = NULL } /**< End marker for the context menu list */
void component_context_menu_init (component_context_menu_t *cm);
void component_context_menu_show (component_context_menu_t *cm);
bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm);
void component_context_menu_draw (component_context_menu_t *cm);
/**
* @brief Initialize the context menu component.
*
* @param cm Pointer to the context menu structure.
*/
void component_context_menu_init(component_context_menu_t *cm);
/** @brief 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 {
bool loading;
surface_t *image;
bool loading; /**< Flag to indicate if the box art is loading */
surface_t *image; /**< Pointer to the box art image */
} component_boxart_t;
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code, file_image_type_t current_image_view);
void component_boxart_free (component_boxart_t *b);
void component_boxart_draw (component_boxart_t *b);
/**
* @brief Initialize the box art component.
*
* @param storage_prefix Prefix for the storage location.
* @param game_code Game code for the box art.
* @param current_image_view Current image view type.
* @return Pointer to the initialized box art component.
*/
component_boxart_t *component_boxart_init(const char *storage_prefix, char *game_code, file_image_type_t current_image_view);
/** @} */ /* 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

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

View File

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

View File

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

View File

@ -104,6 +104,12 @@ typedef struct {
path_t *disk_path;
disk_info_t disk_info;
} load;
struct {
bool rom_file;
bool disk_file;
bool emulator_file;
} boot_pending;
} menu_t;

View File

@ -7,37 +7,166 @@
#ifndef MP3_PLAYER_H__
#define MP3_PLAYER_H__
#include <stdbool.h>
/** @brief MP3 file error enumeration */
/**
* @brief MP3 file error enumeration.
*
* Enumeration for different types of errors that can occur in the MP3 player.
*/
typedef enum {
MP3PLAYER_OK,
MP3PLAYER_ERR_OUT_OF_MEM,
MP3PLAYER_ERR_IO,
MP3PLAYER_ERR_NO_FILE,
MP3PLAYER_ERR_INVALID_FILE,
MP3PLAYER_OK, /**< No error */
MP3PLAYER_ERR_OUT_OF_MEM, /**< Out of memory error */
MP3PLAYER_ERR_IO, /**< Input/Output error */
MP3PLAYER_ERR_NO_FILE, /**< No file found error */
MP3PLAYER_ERR_INVALID_FILE, /**< Invalid file error */
} mp3player_err_t;
/**
* @brief Initialize the MP3 player mixer.
*
* This function initializes the mixer for the MP3 player.
*/
void mp3player_mixer_init(void);
void mp3player_mixer_init (void);
mp3player_err_t mp3player_init (void);
void mp3player_deinit (void);
mp3player_err_t mp3player_load (char *path);
void mp3player_unload (void);
mp3player_err_t mp3player_process (void);
bool mp3player_is_playing (void);
bool mp3player_is_finished (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 Initialize the MP3 player.
*
* This function initializes the MP3 player and prepares it for playback.
*
* @return mp3player_err_t Error code indicating the result of the initialization.
*/
mp3player_err_t mp3player_init(void);
/**
* @brief Deinitialize the MP3 player.
*
* This function deinitializes the MP3 player and releases any resources.
*/
void mp3player_deinit(void);
#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__
#define PNG_DECODER_H__
#include <surface.h>
/** @brief PNG decoder errors */
/**
* @brief PNG decoder errors
*
* Enumeration for different types of errors that can occur in the PNG decoder.
*/
typedef enum {
PNG_OK,
PNG_ERR_INT,
PNG_ERR_BUSY,
PNG_ERR_OUT_OF_MEM,
PNG_ERR_NO_FILE,
PNG_ERR_BAD_FILE,
PNG_OK, /**< No error */
PNG_ERR_INT, /**< Internal error */
PNG_ERR_BUSY, /**< Decoder is busy */
PNG_ERR_OUT_OF_MEM, /**< Out of memory error */
PNG_ERR_NO_FILE, /**< No file found error */
PNG_ERR_BAD_FILE, /**< Bad file error */
} png_err_t;
/**
* @brief PNG decoder callback type.
*
* This typedef defines the callback function type used by the PNG decoder.
*
* @param err Error code indicating the result of the decoding process.
* @param decoded_image Pointer to the decoded image surface.
* @param callback_data User-defined data passed to the callback function.
*/
typedef void png_callback_t (png_err_t err, surface_t *decoded_image, void *callback_data);
/**
* @brief Start the PNG decoding process.
*
* This function starts the PNG decoding process for the specified file.
*
* @param path Path to the PNG file.
* @param max_width Maximum width of the decoded image.
* @param max_height Maximum height of the decoded image.
* @param callback Callback function to be called when decoding is complete.
* @param callback_data User-defined data to be passed to the callback function.
* @return png_err_t Error code indicating the result of the start operation.
*/
png_err_t png_decoder_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data);
/**
* @brief Abort the PNG decoding process.
*
* This function aborts the ongoing PNG decoding process.
*/
void png_decoder_abort (void);
/**
* @brief Get the progress of the PNG decoding process.
*
* This function returns the current progress of the PNG decoding process as a percentage.
*
* @return float Current progress of the decoding process (0.0 to 100.0).
*/
float png_decoder_get_progress (void);
/**
* @brief Poll the PNG decoder.
*
* This function polls the PNG decoder to handle any ongoing decoding tasks.
*/
void png_decoder_poll (void);
#endif
#endif /* PNG_DECODER_H__ */

View File

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

View File

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

View File

@ -9,28 +9,46 @@
#include <stdbool.h>
#define SOUND_MP3_PLAYER_CHANNEL (0)
#define SOUND_SFX_CHANNEL (2)
#define SOUND_MP3_PLAYER_CHANNEL (0) /**< Channel for MP3 player sound */
#define SOUND_SFX_CHANNEL (2) /**< Channel for sound effects */
/**
* @brief Enumeration of available sound effects for menu interactions.
*
* This enumeration defines the different sound effects that can be used
* for menu interactions.
*/
typedef enum {
SFX_CURSOR,
SFX_ERROR,
SFX_ENTER,
SFX_EXIT,
SFX_SETTING,
SFX_CURSOR, /**< Sound effect for cursor movement */
SFX_ERROR, /**< Sound effect for error */
SFX_ENTER, /**< Sound effect for entering a menu */
SFX_EXIT, /**< Sound effect for exiting a menu */
SFX_SETTING, /**< Sound effect for changing a setting */
} sound_effect_t;
void sound_init_default (void);
void sound_init_mp3_playback (void);
/**
* @brief Initialize the default sound system.
*
* This function initializes the default sound system, setting up
* necessary resources and configurations.
*/
void sound_init_default(void);
/**
* @brief Initialize sound effects system.
* @brief Initialize the MP3 playback system.
*
* This function initializes the MP3 playback system, preparing it
* for playing MP3 files.
*/
void sound_init_sfx (void);
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.
@ -46,4 +64,4 @@ void sound_play_effect(sound_effect_t sfx);
void sound_deinit (void);
void sound_poll (void);
#endif
#endif /* SOUND_H__ */

View File

@ -2,20 +2,36 @@
* @file usb_comm.h
* @brief USB communication subsystem
* @ingroup menu
*
* This file contains the declarations for the USB communication subsystem
* used in the menu system.
*/
#ifndef USB_COMM_H__
#define USB_COMM_H__
#include "menu_state.h"
#ifndef NDEBUG
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
/**
* @brief Poll the USB communication subsystem (no-op in release builds).
*
* This macro is a no-op in release builds, where USB communication polling
* is disabled.
*
* @param menu Pointer to the menu structure.
*/
#define usb_comm_poll(menu)
#endif
#endif
#endif /* USB_COMM_H__ */

View File

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

View File

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

View File

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

View File

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

View File

@ -136,6 +136,7 @@ static void draw (menu_t *menu, surface_t *d) {
ALIGN_LEFT, VALIGN_TOP,
"\n\n"
" Default Directory : %s\n\n"
" Autoload ROM : %s\n"
"To change the following menu settings, press 'A':\n"
"* PAL60 Mode : %s\n"
" Show Hidden Files : %s\n"
@ -149,6 +150,7 @@ static void draw (menu_t *menu, surface_t *d) {
"Note: Certain settings have the following caveats:\n"
"* Requires rebooting the N64 Console.\n",
menu->settings.default_directory,
format_switch(menu->settings.rom_autoload_enabled),
format_switch(menu->settings.pal60_enabled),
format_switch(menu->settings.show_protected_entries),
format_switch(menu->settings.use_saves_folder),

View File

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

View File

@ -57,7 +57,7 @@ static void draw (menu_t *menu, surface_t *d) {
"Joypad 2 is %sconnected %s\n"
"Joypad 3 is %sconnected %s\n"
"Joypad 4 is %sconnected %s\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown",
is_memory_expanded() ? "" : "not ",
(joypad[0]) ? "" : "not ", format_accessory(0),
(joypad[1]) ? "" : "not ", format_accessory(1),