Complete rewrite of menu drawing + code cleanup + ROM cold boot fix

Uses new `rdpq_text` API for font drawing
This commit is contained in:
Mateusz Faderewski 2023-08-03 17:18:55 +02:00
parent eb9668c721
commit 8e8f77e55b
41 changed files with 1331 additions and 1343 deletions

View File

@ -10,13 +10,12 @@ OUTPUT_DIR = output
include $(N64_INST)/include/n64.mk
N64_CFLAGS += -iquote $(SOURCE_DIR) -I $(SOURCE_DIR)/libs $(FLAGS)
N64_LDFLAGS += --wrap asset_load
SRCS = \
main.c \
boot/boot.c \
boot/crc32.c \
boot/ipl2.S \
boot/reboot.S \
flashcart/flashcart_utils.c \
flashcart/flashcart.c \
flashcart/sc64/sc64_internal.c \
@ -28,8 +27,11 @@ SRCS = \
libs/miniz/miniz_zip.c \
libs/miniz/miniz.c \
menu/actions.c \
menu/assets.c \
menu/components/background.c \
menu/components/boxart.c \
menu/components/common.c \
menu/components/file_list.c \
menu/fonts.c \
menu/menu.c \
menu/mp3_player.c \
menu/path.c \
@ -41,8 +43,6 @@ SRCS = \
menu/views/error.c \
menu/views/fault.c \
menu/views/file_info.c \
menu/views/fragments/fragments.c \
menu/views/fragments/widgets.c \
menu/views/image_viewer.c \
menu/views/load.c \
menu/views/music_player.c \
@ -59,7 +59,7 @@ SPNG_OBJS = $(filter $(BUILD_DIR)/libs/libspng/%.o,$(OBJS))
$(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second
$(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second
$(BUILD_DIR)/FiraMono-Bold.o: MKFONT_FLAGS+=-c 0 --size 16 -r 20-7F -r 2000-206F
$(BUILD_DIR)/FiraMono-Bold.o: MKFONT_FLAGS+=-c 0 --size 16 -r 20-7F -r 2026-2026 --ellipsis 2026,1
$(BUILD_DIR)/%.o: $(ASSETS_DIR)/%.ttf
@echo " [FONT] $@"

@ -1 +1 @@
Subproject commit 1c35500e787b3bd2634e1cd0870b246ddffddfe2
Subproject commit 4409fe77e208aaef77800b684602d4ea5a74da0b

View File

@ -10,7 +10,9 @@
#define C0_STATUS_CU1 (1 << 29)
extern uint32_t ipl2 __attribute__((section(".data")));
extern uint32_t reboot_start __attribute__((section(".text")));
extern size_t reboot_size __attribute__((section(".text")));
extern int reboot_entry_offset __attribute__((section(".text")));
typedef struct {
@ -99,11 +101,12 @@ void boot (boot_params_t *params) {
while (cpu_io_read(&SP->SR) & SP_SR_DMA_BUSY);
uint32_t *ipl2_src = &ipl2;
io32_t *ipl2_dst = SP_MEM->IMEM;
uint32_t *reboot_src = &reboot_start;
io32_t *reboot_dst = SP_MEM->IMEM;
size_t reboot_instructions = (size_t) (&reboot_size) / sizeof(uint32_t);
for (int i = 0; i < 8; i++) {
cpu_io_write(&ipl2_dst[i], ipl2_src[i]);
for (int i = 0; i < reboot_instructions; i++) {
cpu_io_write(&reboot_dst[i], reboot_src[i]);
}
cpu_io_write(&PI->DOM[0].LAT, 0xFF);
@ -138,10 +141,10 @@ void boot (boot_params_t *params) {
register uint32_t version asm ("s7");
void *stack_pointer;
entry_point = (void (*)(void)) UNCACHED(&SP_MEM->DMEM[16]);
entry_point = (void (*)(void)) UNCACHED(&SP_MEM->IMEM[(int) (&reboot_entry_offset)]);
boot_device = (params->device_type & 0x01);
tv_type = (params->tv_type & 0x03);
reset_type = (params->reset_type & 0x01);
reset_type = BOOT_RESET_TYPE_COLD;
cic_seed = (params->cic_seed & 0xFF);
version = (params->tv_type == BOOT_TV_TYPE_PAL) ? 6 : 1;
stack_pointer = (void *) UNCACHED(&SP_MEM->IMEM[1020]);

View File

@ -24,7 +24,6 @@ typedef enum {
BOOT_RESET_TYPE_NMI = 1,
} boot_reset_type_t;
/** @brief TV type enumeration */
typedef enum {
BOOT_TV_TYPE_PAL = 0,
@ -33,11 +32,9 @@ typedef enum {
BOOT_TV_TYPE_PASSTHROUGH = 3,
} boot_tv_type_t;
/** @brief Boot Parameters Structure */
typedef struct {
boot_device_type_t device_type;
boot_reset_type_t reset_type;
boot_tv_type_t tv_type;
uint8_t cic_seed;
bool detect_cic_seed;

View File

@ -1,17 +0,0 @@
.set noat
.set noreorder
.section .text.ipl2, "ax", %progbits
.type ipl2, %object
ipl2:
.global ipl2
lui $t5, 0xBFC0
1:
lw $t0, 0x7FC($t5)
addiu $t5, $t5, 0x7C0
andi $t0, $t0, 0x80
bnel $t0, $zero, 1b
lui $t5, 0xBFC0
lw $t0, 0x24($t5)
lui $t3, 0xB000

57
src/boot/reboot.S Normal file
View File

@ -0,0 +1,57 @@
#define RI_ADDRESS 0xA4700000
#define RI_MODE 0x00
#define RI_CONFIG 0x04
#define RI_CURRENT_LOAD 0x08
#define RI_SELECT 0x0C
#define RI_REFRESH 0x10
#define RI_LATENCY 0x14
#define RI_RERROR 0x18
#define RI_WERROR 0x1C
#define RI_MODE_RESET 0x0000000E
#define IPL3_ENTRY 0xA4000040
.section .text.reboot, "ax", %progbits
.type reboot, %object
reboot_start:
.global reboot_start
# NOTE: CIC x105 requirement
ipl2:
.set noat
.set noreorder
lui $t5, 0xBFC0
1:
lw $t0, 0x7FC($t5)
addiu $t5, $t5, 0x7C0
andi $t0, $t0, 0x80
bnel $t0, $zero, 1b
lui $t5, 0xBFC0
lw $t0, 0x24($t5)
lui $t3, 0xB000
.set reorder
.set at
reboot_entry:
.equ reboot_entry_offset, ((. - reboot_start) / 4)
.global reboot_entry_offset
reset_rdram:
li $t0, RI_ADDRESS
li $t1, RI_MODE_RESET
sw $t1, RI_MODE($t0)
sw $zero, RI_CONFIG($t0)
sw $zero, RI_CURRENT_LOAD($t0)
sw $zero, RI_SELECT($t0)
sw $zero, RI_REFRESH($t0)
run_ipl3:
li $t3, IPL3_ENTRY
jr $t3
.equ reboot_size, (. - reboot_start)
.global reboot_size

View File

@ -91,7 +91,7 @@ flashcart_error_t flashcart_deinit (void) {
return FLASHCART_OK;
}
flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap) {
flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress) {
flashcart_error_t error;
if ((rom_path == NULL) || (!file_exists(rom_path)) || (file_get_size(rom_path) < KiB(4))) {
@ -102,7 +102,7 @@ flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap) {
return FLASHCART_ERROR_INT;
}
error = flashcart->load_rom(rom_path);
error = flashcart->load_rom(rom_path, progress);
if (cart_card_byteswap(false)) {
return FLASHCART_ERROR_INT;

View File

@ -35,11 +35,13 @@ typedef enum {
__FLASHCART_SAVE_TYPE_END
} flashcart_save_type_t;
typedef void flashcart_progress_callback_t (float progress);
/** @brief Flashcart Structure */
typedef struct {
flashcart_error_t (*init) (void);
flashcart_error_t (*deinit) (void);
flashcart_error_t (*load_rom) (char *rom_path);
flashcart_error_t (*load_rom) (char *rom_path, flashcart_progress_callback_t *progress);
flashcart_error_t (*load_save) (char *save_path);
flashcart_error_t (*set_save_type) (flashcart_save_type_t save_type);
flashcart_error_t (*set_save_writeback) (uint32_t *sectors);
@ -48,7 +50,7 @@ typedef struct {
flashcart_error_t flashcart_init (void);
flashcart_error_t flashcart_deinit (void);
flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap);
flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress);
flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type);

View File

@ -23,7 +23,7 @@
#define SUPPORTED_MINOR_VERSION (16)
static flashcart_error_t load_to_flash (FIL *fil, void *address, size_t size, UINT *br) {
static flashcart_error_t load_to_flash (FIL *fil, void *address, size_t size, UINT *br, flashcart_progress_callback_t *progress) {
size_t erase_block_size;
UINT bp;
@ -44,6 +44,9 @@ static flashcart_error_t load_to_flash (FIL *fil, void *address, size_t size, UI
if (sc64_flash_wait_busy() != SC64_OK) {
return FLASHCART_ERROR_INT;
}
if (progress) {
progress(f_tell(fil) / (float) (f_size(fil)));
}
address += program_size;
size -= program_size;
*br += bp;
@ -109,7 +112,7 @@ static flashcart_error_t sc64_deinit (void) {
return FLASHCART_OK;
}
static flashcart_error_t sc64_load_rom (char *rom_path) {
static flashcart_error_t sc64_load_rom (char *rom_path, flashcart_progress_callback_t *progress) {
FIL fil;
UINT br;
@ -133,11 +136,18 @@ static flashcart_error_t sc64_load_rom (char *rom_path) {
size_t shadow_size = shadow_enabled ? MIN(rom_size - sdram_size, KiB(128)) : 0;
size_t extended_size = extended_enabled ? rom_size - MiB(64) : 0;
if (f_read(&fil, (void *) (ROM_ADDRESS), sdram_size, &br) != FR_OK) {
f_close(&fil);
return FLASHCART_ERROR_LOAD;
size_t chunk_size = MiB(1);
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_ERROR_LOAD;
}
if (progress) {
progress(f_tell(&fil) / (float) (f_size(&fil)));
}
}
if (br != sdram_size) {
if (f_tell(&fil) != sdram_size) {
f_close(&fil);
return FLASHCART_ERROR_LOAD;
}
@ -148,7 +158,7 @@ static flashcart_error_t sc64_load_rom (char *rom_path) {
}
if (shadow_enabled) {
flashcart_error_t error = load_to_flash(&fil, (void *) (SHADOW_ADDRESS), shadow_size, &br);
flashcart_error_t error = load_to_flash(&fil, (void *) (SHADOW_ADDRESS), shadow_size, &br, progress);
if (error != FLASHCART_OK) {
f_close(&fil);
return error;
@ -165,7 +175,7 @@ static flashcart_error_t sc64_load_rom (char *rom_path) {
}
if (extended_enabled) {
flashcart_error_t error = load_to_flash(&fil, (void *) (EXTENDED_ADDRESS), extended_size, &br);
flashcart_error_t error = load_to_flash(&fil, (void *) (EXTENDED_ADDRESS), extended_size, &br, progress);
if (error != FLASHCART_OK) {
f_close(&fil);
return error;

View File

@ -1,54 +0,0 @@
#include <stdint.h>
#include <string.h>
#include <libdragon.h>
#include "assets.h"
typedef struct {
char *name;
uint8_t *data;
int size;
} asset_t;
#define ASSET_IMPORT(a) \
extern uint8_t *_binary_assets_##a##_start __attribute__((section(".data"))); \
extern int _binary_assets_##a##_size __attribute__((section(".data")));
#define ASSET(n, a) { n, (uint8_t *) (&_binary_assets_##a##_start), (int) (&_binary_assets_##a##_size) }
ASSET_IMPORT(FiraMono_Bold_font64);
static asset_t assets_list[] = {
ASSET("assets:/font", FiraMono_Bold_font64),
};
extern void *__real_asset_load (char *fn, int *sz);
void *__wrap_asset_load (char *fn, int *sz) {
for (int i = 0; i < sizeof(assets_list) / sizeof(assets_list[0]); i++) {
asset_t *asset = &assets_list[i];
if (strcmp(asset->name, fn) == 0) {
*sz = asset->size;
return asset->data;
}
}
return __real_asset_load(fn, sz);
}
static assets_t assets;
void assets_init (void) {
assets.font = rdpq_font_load("assets:/font");
assets.font_height = 16;
}
assets_t *assets_get (void) {
return &assets;
}

View File

@ -1,27 +0,0 @@
/**
* @file assets.h
* @brief Menu Assets
* @ingroup menu
*/
#ifndef ASSETS_H__
#define ASSETS_H__
#include <rdpq_font.h>
/** @brief Assets Structure */
typedef struct {
/** @brief RDPQ Font */
rdpq_font_t *font;
/** @brief Font Height */
int font_height;
} assets_t;
void assets_init (void);
assets_t *assets_get (void);
#endif

52
src/menu/components.h Normal file
View File

@ -0,0 +1,52 @@
/**
* @file components.h
* @brief Menu Components
* @ingroup menu
*/
#ifndef COMPONENTS_H__
#define COMPONENTS_H__
#include <libdragon.h>
#include "menu_state.h"
/**
* @addtogroup
* @{ 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);
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_file_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, ...);
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);
void component_file_list_draw (entry_t *list, int entries, int selected);
typedef struct {
bool loading;
surface_t *image;
} component_boxart_t;
component_boxart_t *component_boxart_init (uint16_t id);
void component_boxart_free (component_boxart_t *b);
void component_boxart_draw (component_boxart_t *b);
/** @} */ /* menu_components */
#endif

View File

@ -1,13 +1,20 @@
#include <fatfs/ff.h>
#include <stdlib.h>
#include "components.h"
#include "../components.h"
#include "constants.h"
#include "utils/fs.h"
#define CACHE_METADATA_MAGIC (0x424B4731)
typedef struct {
char *cache_location;
surface_t *image;
rspq_block_t *image_display_list;
} component_background_t;
typedef struct {
uint32_t magic;
uint32_t width;
@ -16,6 +23,9 @@ typedef struct {
} cache_metadata_t;
static component_background_t *background = NULL;
static void load_from_cache (component_background_t *c) {
if (!c->cache_location) {
return;
@ -35,10 +45,7 @@ static void load_from_cache (component_background_t *c) {
return;
}
uint32_t display_width = display_get_width();
uint32_t display_height = display_get_height();
if (cache_metadata.magic != CACHE_METADATA_MAGIC || cache_metadata.width > display_width || cache_metadata.height > display_height) {
if (cache_metadata.magic != CACHE_METADATA_MAGIC || cache_metadata.width > DISPLAY_WIDTH || cache_metadata.height > DISPLAY_HEIGHT) {
f_close(&fil);
return;
}
@ -92,62 +99,61 @@ static void prepare_background (component_background_t *c) {
return;
}
uint32_t display_width = display_get_width();
uint32_t display_height = display_get_height();
int16_t display_center_x = (display_width / 2);
int16_t display_center_y = (display_height / 2);
int16_t image_center_x = (c->image->width / 2);
int16_t image_center_y = (c->image->height / 2);
uint16_t image_center_x = (c->image->width / 2);
uint16_t image_center_y = (c->image->height / 2);
// Darken the image
rdpq_attach(c->image, NULL);
rdpq_mode_push();
rdpq_set_mode_standard();
rdpq_set_prim_color(RGBA32(0x00, 0x00, 0x00, 0xA0));
rdpq_set_prim_color(BACKGROUND_OVERLAY_COLOR);
rdpq_mode_combiner(RDPQ_COMBINER_FLAT);
rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY);
rdpq_fill_rectangle(0, 0, c->image->width, c->image->height);
rdpq_fill_rectangle(
0 - (DISPLAY_CENTER_X - image_center_x),
0 - (DISPLAY_CENTER_Y - image_center_y),
DISPLAY_WIDTH - (DISPLAY_CENTER_X - image_center_x),
DISPLAY_HEIGHT - (DISPLAY_CENTER_Y - image_center_y)
);
rdpq_mode_pop();
rdpq_detach();
// Prepare display list
rspq_block_begin();
rdpq_mode_push();
if ((c->image->width != display_width) || (c->image->height != display_height)) {
rdpq_set_mode_fill(RGBA32(0x00, 0x00, 0x00, 0xFF));
if ((c->image->width != DISPLAY_WIDTH) || (c->image->height != DISPLAY_HEIGHT)) {
rdpq_set_mode_fill(BACKGROUND_EMPTY_COLOR);
}
if (c->image->width != display_width) {
if (c->image->width != DISPLAY_WIDTH) {
rdpq_fill_rectangle(
0,
display_center_y - image_center_y,
display_center_x - image_center_x,
display_center_y + image_center_y
DISPLAY_CENTER_Y - image_center_y,
DISPLAY_CENTER_X - image_center_x,
DISPLAY_CENTER_Y + image_center_y
);
rdpq_fill_rectangle(
display_center_x + image_center_x - (c->image->width % 2),
display_center_y - image_center_y,
display_width,
display_center_y + image_center_y
DISPLAY_CENTER_X + image_center_x - (c->image->width % 2),
DISPLAY_CENTER_Y - image_center_y,
DISPLAY_WIDTH,
DISPLAY_CENTER_Y + image_center_y
);
}
if (c->image->height != display_height) {
if (c->image->height != DISPLAY_HEIGHT) {
rdpq_fill_rectangle(
0,
0,
display_width,
display_center_y - image_center_y
DISPLAY_WIDTH,
DISPLAY_CENTER_Y - image_center_y
);
rdpq_fill_rectangle(
0,
display_center_y + image_center_y - (c->image->height % 2),
display_width,
display_height
DISPLAY_CENTER_Y + image_center_y - (c->image->height % 2),
DISPLAY_WIDTH,
DISPLAY_HEIGHT
);
}
rdpq_set_mode_copy(false);
rdpq_tex_blit(c->image, display_center_x - image_center_x, display_center_y - image_center_y, NULL);
rdpq_tex_blit(c->image, DISPLAY_CENTER_X - image_center_x, DISPLAY_CENTER_Y - image_center_y, NULL);
rdpq_mode_pop();
c->image_display_list = rspq_block_end();
}
@ -157,46 +163,56 @@ static void display_list_free (void *arg) {
}
component_background_t *component_background_create (char *cache_location) {
component_background_t *c = calloc(1, sizeof(component_background_t));
c->cache_location = cache_location;
load_from_cache(c);
prepare_background(c);
return c;
void component_background_init (char *cache_location) {
if (!background) {
background = calloc(1, sizeof(component_background_t));
background->cache_location = cache_location;
load_from_cache(background);
prepare_background(background);
}
}
void component_background_replace_image (component_background_t *c, surface_t *image) {
if (!c) {
void component_background_free (void) {
if (background) {
if (background->image) {
surface_free(background->image);
free(background->image);
background->image = NULL;
}
if (background->image_display_list) {
rdpq_call_deferred(display_list_free, background->image_display_list);
background->image_display_list = NULL;
}
free(background);
background = NULL;
}
}
void component_background_replace_image (surface_t *image) {
if (!background) {
return;
}
if (c->image) {
surface_free(c->image);
free(c->image);
c->image = NULL;
if (background->image) {
surface_free(background->image);
free(background->image);
background->image = NULL;
}
if (c->image_display_list) {
rdpq_call_deferred(display_list_free, c->image_display_list);
c->image_display_list = NULL;
if (background->image_display_list) {
rdpq_call_deferred(display_list_free, background->image_display_list);
background->image_display_list = NULL;
}
c->image = image;
save_to_cache(c);
prepare_background(c);
background->image = image;
save_to_cache(background);
prepare_background(background);
}
void component_background_draw (component_background_t *c) {
if (!c || !c->image_display_list) {
rdpq_clear(RGBA32(0x00, 0x00, 0x00, 0xFF));
return;
void component_background_draw (void) {
if (background && background->image_display_list) {
rspq_block_run(background->image_display_list);
} else {
rdpq_clear(BACKGROUND_EMPTY_COLOR);
}
rspq_block_run(c->image_display_list);
}

View File

@ -0,0 +1,70 @@
#include <stdlib.h>
#include "../components.h"
#include "../path.h"
#include "../png_decoder.h"
#include "constants.h"
#define BOXART_DIRECTORY "sd:/menu/boxart"
static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
component_boxart_t *b = (component_boxart_t *) (callback_data);
b->loading = false;
b->image = decoded_image;
}
component_boxart_t *component_boxart_init (uint16_t id) {
component_boxart_t *b = calloc(1, sizeof(component_boxart_t));
if (b) {
b->loading = true;
char *path = alloca(strlen(BOXART_DIRECTORY) + 1 + 6 + 1);
sprintf(path, "%s/%c%c.png", BOXART_DIRECTORY, ((id >> 8) & 0xFF), (id & 0xFF));
if (png_decoder_start(path, BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) != PNG_OK) {
free(b);
b = NULL;
}
}
return b;
}
void component_boxart_free (component_boxart_t *b) {
if (b) {
if (b->loading) {
png_decoder_abort();
}
if (b->image) {
surface_free(b->image);
free(b->image);
}
free(b);
}
}
void component_boxart_draw (component_boxart_t *b) {
if (b && b->image && b->image->width == BOXART_WIDTH && b->image->height == BOXART_HEIGHT) {
rdpq_mode_push();
rdpq_set_mode_copy(false);
rdpq_tex_blit(
b->image,
BOXART_X,
BOXART_Y,
NULL
);
rdpq_mode_pop();
} else {
component_box_draw(
BOXART_X,
BOXART_Y,
BOXART_X + BOXART_WIDTH,
BOXART_Y + BOXART_HEIGHT,
BOXART_LOADING_COLOR
);
}
}

View File

@ -0,0 +1,182 @@
#include <stdarg.h>
#include "../components.h"
#include "../fonts.h"
#include "constants.h"
void component_box_draw (int x0, int y0, int x1, int y1, color_t color) {
rdpq_mode_push();
rdpq_set_mode_fill(color);
rdpq_fill_rectangle(x0, y0, x1, y1);
rdpq_mode_pop();
}
void component_border_draw (int x0, int y0, int x1, int y1) {
rdpq_mode_push();
rdpq_set_mode_fill(BORDER_COLOR);
rdpq_fill_rectangle(x0 - BORDER_THICKNESS, y0 - BORDER_THICKNESS, x1 + BORDER_THICKNESS, y0);
rdpq_fill_rectangle(x0 - BORDER_THICKNESS, y1, x1 + BORDER_THICKNESS, y1 + BORDER_THICKNESS);
rdpq_fill_rectangle(x0 - BORDER_THICKNESS, y0, x0, y1);
rdpq_fill_rectangle(x1, y0, x1 + BORDER_THICKNESS, y1);
rdpq_mode_pop();
}
void component_layout_draw (void) {
component_border_draw(
VISIBLE_AREA_X0,
VISIBLE_AREA_Y0,
VISIBLE_AREA_X1,
VISIBLE_AREA_Y1
);
component_box_draw(
VISIBLE_AREA_X0,
LAYOUT_ACTIONS_SEPARATOR_Y,
VISIBLE_AREA_X1,
LAYOUT_ACTIONS_SEPARATOR_Y + BORDER_THICKNESS,
BORDER_COLOR
);
}
void component_progressbar_draw (int x0, int y0, int x1, int y1, float progress) {
float progress_width = progress * (x1 - x0);
component_box_draw(x0, y0, x0 + progress_width, y1, PROGRESSBAR_DONE_COLOR);
component_box_draw(x0 + progress_width, y0, x1, y1, PROGRESSBAR_BG_COLOR);
}
void component_seekbar_draw (float position) {
int x0 = SEEKBAR_X;
int y0 = SEEKBAR_Y;
int x1 = SEEKBAR_X + SEEKBAR_WIDTH;
int y1 = SEEKBAR_Y + SEEKBAR_HEIGHT;
component_border_draw(x0, y0, x1, y1);
component_progressbar_draw(x0, y0, x1, y1, position);
}
void component_loader_draw (float progress) {
int x0 = LOADER_X;
int y0 = LOADER_Y;
int x1 = LOADER_X + LOADER_WIDTH;
int y1 = LOADER_Y + LOADER_HEIGHT;
component_border_draw(x0, y0, x1, y1);
component_progressbar_draw(x0, y0, x1, y1, progress);
}
void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items) {
if (items <= 1 || items <= visible_items) {
component_box_draw(x, y, x + width, y + height, SCROLLBAR_INACTIVE_COLOR);
} else {
int scroll_height = (int) ((visible_items / (float) (items)) * height);
float scroll_position = ((position / (float) (items - 1)) * (height - scroll_height));
component_box_draw(x, y, x + width, y + height, SCROLLBAR_BG_COLOR);
component_box_draw(x, y + scroll_position, x + width, y + scroll_position + scroll_height, SCROLLBAR_POSITION_COLOR);
}
}
void component_file_list_scrollbar_draw (int position, int items, int visible_items) {
component_scrollbar_draw(
FILE_LIST_SCROLLBAR_X,
FILE_LIST_SCROLLBAR_Y,
FILE_LIST_SCROLLBAR_WIDTH,
FILE_LIST_SCROLLBAR_HEIGHT,
position,
items,
visible_items
);
}
void component_dialog_draw (int width, int height) {
int x0 = DISPLAY_CENTER_X - (width / 2);
int y0 = DISPLAY_CENTER_Y - (height / 2);
int x1 = DISPLAY_CENTER_X + (width / 2);
int y1 = DISPLAY_CENTER_Y + (height / 2);
component_border_draw(x0, y0, x1, y1);
component_box_draw(x0, y0, x1, y1, DIALOG_BG_COLOR);
}
void component_messagebox_draw (char *fmt, ...) {
char buffer[512];
size_t nbytes = sizeof(buffer);
va_list va;
va_start(va, fmt);
char *formatted = vasnprintf(buffer, &nbytes, fmt, va);
va_end(va);
int paragraph_nbytes = nbytes;
rdpq_paragraph_t *paragraph = rdpq_paragraph_build(&(rdpq_textparms_t) {
.width = MESSAGEBOX_MAX_WIDTH,
.height = VISIBLE_AREA_HEIGHT,
.align = ALIGN_CENTER,
.valign = VALIGN_CENTER,
.wrap = WRAP_WORD
}, FNT_DEFAULT, formatted, &paragraph_nbytes);
component_dialog_draw(
paragraph->bbox[2] - paragraph->bbox[0] + MESSAGEBOX_MARGIN,
paragraph->bbox[3] - paragraph->bbox[1] + MESSAGEBOX_MARGIN
);
rdpq_paragraph_render(paragraph, DISPLAY_CENTER_X - (MESSAGEBOX_MAX_WIDTH / 2), VISIBLE_AREA_Y0);
rdpq_paragraph_free(paragraph);
}
void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) {
char buffer[1024];
size_t nbytes = sizeof(buffer);
va_list va;
va_start(va, fmt);
char *formatted = vasnprintf(buffer, &nbytes, fmt, va);
va_end(va);
rdpq_text_printn(
&(rdpq_textparms_t) {
.width = VISIBLE_AREA_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
.height = LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT - (TEXT_MARGIN_VERTICAL * 2),
.align = align,
.valign = valign,
.wrap = WRAP_ELLIPSES,
},
FNT_DEFAULT,
VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL,
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL,
formatted,
nbytes
);
}
void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) {
char buffer[256];
size_t nbytes = sizeof(buffer);
va_list va;
va_start(va, fmt);
char *formatted = vasnprintf(buffer, &nbytes, fmt, va);
va_end(va);
rdpq_text_printn(
&(rdpq_textparms_t) {
.width = VISIBLE_AREA_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
.height = VISIBLE_AREA_Y1 - LAYOUT_ACTIONS_SEPARATOR_Y - BORDER_THICKNESS - (TEXT_MARGIN_VERTICAL * 2),
.align = align,
.valign = valign,
.wrap = WRAP_ELLIPSES,
},
FNT_DEFAULT,
VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL,
LAYOUT_ACTIONS_SEPARATOR_Y + BORDER_THICKNESS + TEXT_MARGIN_VERTICAL,
formatted,
nbytes
);
}

View File

@ -1,32 +0,0 @@
/**
* @file components.h
* @brief Menu Components
* @ingroup menu
*/
#ifndef COMPONENTS_H__
#define COMPONENTS_H__
#include <libdragon.h>
/**
* @addtogroup
* @{ menu_components
*/
typedef struct {
char *cache_location;
surface_t *image;
rspq_block_t *image_display_list;
} component_background_t;
component_background_t *component_background_create (char *cache_location);
void component_background_replace_image (component_background_t *c, surface_t *image);
void component_background_draw (component_background_t *c);
/** @} */ /* menu_components */
#endif

View File

@ -0,0 +1,83 @@
/**
* @file constants.h
* @brief Menu components constants
* @ingroup menu
*/
#ifndef COMPONENTS_CONSTANTS_H__
#define COMPONENTS_CONSTANTS_H__
#define DISPLAY_WIDTH (640)
#define DISPLAY_HEIGHT (480)
#define DISPLAY_CENTER_X (DISPLAY_WIDTH / 2)
#define DISPLAY_CENTER_Y (DISPLAY_HEIGHT / 2)
#define OVERSCAN_WIDTH (32)
#define OVERSCAN_HEIGHT (24)
#define VISIBLE_AREA_X0 (OVERSCAN_WIDTH)
#define VISIBLE_AREA_Y0 (OVERSCAN_HEIGHT)
#define VISIBLE_AREA_X1 (DISPLAY_WIDTH - OVERSCAN_WIDTH)
#define VISIBLE_AREA_Y1 (DISPLAY_HEIGHT - OVERSCAN_HEIGHT)
#define VISIBLE_AREA_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0)
#define VISIBLE_AREA_HEIGHT (VISIBLE_AREA_Y1 - VISIBLE_AREA_Y0)
#define BORDER_THICKNESS (4)
#define LAYOUT_ACTIONS_SEPARATOR_Y (400)
#define SEEKBAR_HEIGHT (24)
#define SEEKBAR_WIDTH (524)
#define SEEKBAR_X (DISPLAY_CENTER_X - (SEEKBAR_WIDTH / 2))
#define SEEKBAR_Y (VISIBLE_AREA_Y1 - SEEKBAR_HEIGHT - 80)
#define LOADER_WIDTH (320)
#define LOADER_HEIGHT (24)
#define LOADER_X (DISPLAY_CENTER_X - (LOADER_WIDTH / 2))
#define LOADER_Y (DISPLAY_CENTER_Y - (LOADER_HEIGHT / 2))
#define MESSAGEBOX_MAX_WIDTH (360)
#define MESSAGEBOX_MARGIN (32)
#define TEXT_MARGIN_HORIZONTAL (10)
#define TEXT_MARGIN_VERTICAL (7)
#define BOXART_WIDTH (158)
#define BOXART_HEIGHT (112)
#define BOXART_X (VISIBLE_AREA_X1 - BOXART_WIDTH - 24)
#define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24)
#define FILE_LIST_SCROLLBAR_WIDTH (12)
#define FILE_LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT)
#define FILE_LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - FILE_LIST_SCROLLBAR_WIDTH)
#define FILE_LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0)
#define FILE_LIST_ENTRIES (20)
#define FILE_LIST_MAX_WIDTH (480)
#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - FILE_LIST_SCROLLBAR_WIDTH)
#define FILE_LIST_HIGHLIGHT_X (VISIBLE_AREA_X0)
#define BACKGROUND_EMPTY_COLOR RGBA32(0x00, 0x00, 0x00, 0xFF)
#define BACKGROUND_OVERLAY_COLOR RGBA32(0x00, 0x00, 0x00, 0xA0)
#define BORDER_COLOR RGBA32(0xFF, 0xFF, 0xFF, 0xFF)
#define PROGRESSBAR_BG_COLOR RGBA32(0x00, 0x00, 0x00, 0xFF)
#define PROGRESSBAR_DONE_COLOR RGBA32(0x3B, 0x7C, 0xF5, 0xFF)
#define SCROLLBAR_BG_COLOR RGBA32(0x3F, 0x3F, 0x3F, 0xFF)
#define SCROLLBAR_INACTIVE_COLOR RGBA32(0x5F, 0x5F, 0x5F, 0xFF)
#define SCROLLBAR_POSITION_COLOR RGBA32(0x7F, 0x7F, 0x7F, 0xFF)
#define DIALOG_BG_COLOR RGBA32(0x00, 0x00, 0x00, 0xFF)
#define BOXART_LOADING_COLOR RGBA32(0x3F, 0x3F, 0x3F, 0xFF)
#define FILE_LIST_HIGHLIGHT_COLOR RGBA32(0x3F, 0x3F, 0x3F, 0xFF)
#endif

View File

@ -0,0 +1,145 @@
#include <stdlib.h>
#include "../components.h"
#include "../fonts.h"
#include "constants.h"
static const char *dir_prefix = "/";
static void format_file_size (char *buffer, int size) {
if (size < 8 * 1024) {
sprintf(buffer, "%d B", size);
} else if (size < 8 * 1024 * 1024) {
sprintf(buffer, "%d kB", size / 1024);
} else if (size < 1 * 1024 * 1024 * 1024) {
sprintf(buffer, "%d MB", size / 1024 / 1024);
} else {
sprintf(buffer, "%d GB", size / 1024 / 1024 / 1024);
}
}
void component_file_list_draw (entry_t *list, int entries, int selected) {
int starting_position = 0;
if (entries > FILE_LIST_ENTRIES && selected >= (FILE_LIST_ENTRIES / 2)) {
starting_position = selected - (FILE_LIST_ENTRIES / 2);
if (starting_position >= entries - FILE_LIST_ENTRIES) {
starting_position = entries - FILE_LIST_ENTRIES;
}
}
component_file_list_scrollbar_draw(selected, entries, FILE_LIST_ENTRIES);
if (entries == 0) {
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"^%02X** empty directory **",
STL_UNKNOWN
);
} else {
const int list_max_characters = (256 + strlen(dir_prefix)) * FILE_LIST_ENTRIES;
const int paragraph_size = sizeof(rdpq_paragraph_t) + sizeof(rdpq_paragraph_char_t) * list_max_characters;
rdpq_paragraph_t *paragraph = calloc(1, paragraph_size);
paragraph->capacity = list_max_characters;
rdpq_paragraph_builder_begin(
&(rdpq_textparms_t) {
.width = FILE_LIST_MAX_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
.height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2),
.wrap = WRAP_ELLIPSES,
},
FNT_DEFAULT,
paragraph
);
for (int i = starting_position; i < entries; i++) {
if (i == (starting_position + FILE_LIST_ENTRIES)) {
break;
}
entry_t *entry = &list[i];
menu_font_style_t style;
switch (entry->type) {
case ENTRY_TYPE_DIR: style = STL_DIRECTORY; break;
case ENTRY_TYPE_SAVE: style = STL_SAVE; break;
case ENTRY_TYPE_OTHER: style = STL_UNKNOWN; break;
case ENTRY_TYPE_IMAGE: style = STL_MEDIA; break;
case ENTRY_TYPE_MUSIC: style = STL_MEDIA; break;
default: style = STL_DEFAULT; break;
}
rdpq_paragraph_builder_style(style);
if (entry->type == ENTRY_TYPE_DIR) {
rdpq_paragraph_builder_span(dir_prefix, strlen(dir_prefix));
}
rdpq_paragraph_builder_span(entry->name, strlen(entry->name));
rdpq_paragraph_builder_newline();
}
rdpq_paragraph_builder_end();
int highlight_height = (paragraph->bbox[3] - paragraph->bbox[1]) / paragraph->nlines;
int highlight_y = VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + ((selected - starting_position) * highlight_height);
component_box_draw(
FILE_LIST_HIGHLIGHT_X,
highlight_y,
FILE_LIST_HIGHLIGHT_X + FILE_LIST_HIGHLIGHT_WIDTH,
highlight_y + highlight_height,
FILE_LIST_HIGHLIGHT_COLOR
);
rdpq_paragraph_render(
paragraph,
VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL,
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL
);
memset(paragraph, 0, paragraph_size);
rdpq_paragraph_builder_begin(
&(rdpq_textparms_t) {
.width = VISIBLE_AREA_WIDTH - FILE_LIST_SCROLLBAR_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
.height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2),
.align = ALIGN_RIGHT,
.wrap = WRAP_ELLIPSES,
},
FNT_DEFAULT,
paragraph
);
char file_size[16];
for (int i = starting_position; i < entries; i++) {
if (i == (starting_position + FILE_LIST_ENTRIES)) {
break;
}
entry_t *entry = &list[i];
if (entry->type != ENTRY_TYPE_DIR) {
format_file_size(file_size, entry->size);
rdpq_paragraph_builder_span(file_size, strlen(file_size));
}
rdpq_paragraph_builder_newline();
}
rdpq_paragraph_render(
paragraph,
VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL,
VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL
);
rdpq_paragraph_free(paragraph);
}
}

30
src/menu/fonts.c Normal file
View File

@ -0,0 +1,30 @@
#include <libdragon.h>
#include "fonts.h"
#define FONT_IMPORT(f) \
extern void *_binary_assets_##f##_start __attribute__((section(".data"))); \
extern int _binary_assets_##f##_size __attribute__((section(".data")));
#define FONT_LOAD(f) rdpq_font_load_buf((void *) (&_binary_assets_##f##_start), (size_t) (&_binary_assets_##f##_size))
FONT_IMPORT(FiraMono_Bold_font64);
static void load_default_font (void) {
rdpq_font_t *default_font = FONT_LOAD(FiraMono_Bold_font64);
rdpq_font_style(default_font, STL_DEFAULT, &((rdpq_fontstyle_t) { .color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF) }));
rdpq_font_style(default_font, STL_DIRECTORY, &((rdpq_fontstyle_t) { .color = RGBA32(0xFF, 0xFF, 0x70, 0xFF) }));
rdpq_font_style(default_font, STL_SAVE, &((rdpq_fontstyle_t) { .color = RGBA32(0x70, 0xFF, 0x70, 0xFF) }));
rdpq_font_style(default_font, STL_MEDIA, &((rdpq_fontstyle_t) { .color = RGBA32(0x70, 0xBC, 0xFF, 0xFF) }));
rdpq_font_style(default_font, STL_UNKNOWN, &((rdpq_fontstyle_t) { .color = RGBA32(0xA0, 0xA0, 0xA0, 0xFF) }));
rdpq_text_register_font(FNT_DEFAULT, default_font);
}
void fonts_init (void) {
load_default_font();
}

27
src/menu/fonts.h Normal file
View File

@ -0,0 +1,27 @@
/**
* @file fonts.h
* @brief Menu fonts
* @ingroup menu
*/
#ifndef FONTS_H__
#define FONTS_H__
typedef enum {
FNT_DEFAULT = 1,
} menu_font_type_t;
typedef enum {
STL_DEFAULT = 0,
STL_DIRECTORY,
STL_SAVE,
STL_MEDIA,
STL_UNKNOWN,
} menu_font_style_t;
void fonts_init (void);
#endif

View File

@ -3,9 +3,9 @@
#include <libdragon.h>
#include "actions.h"
#include "assets.h"
#include "boot/boot.h"
#include "flashcart/flashcart.h"
#include "fonts.h"
#include "menu_state.h"
#include "menu.h"
#include "mp3_player.h"
@ -17,6 +17,9 @@
#define TV_TYPE_RAM *((uint32_t *) (0x80000300))
#define CACHE_DIRECTORY "sd:/menu/cache"
#define BACKGROUND_CACHE "sd:/menu/cache/background.data"
static menu_t *menu;
static bool boot_pending;
@ -27,12 +30,12 @@ static void menu_init (boot_params_t *boot_params) {
controller_init();
timer_init();
rtc_init();
audio_init(44100, 2);
audio_init(44100, 3);
mixer_init(2);
rspq_init();
rdpq_init();
assets_init();
fonts_init();
mp3player_mixer_init();
boot_pending = false;
@ -48,8 +51,14 @@ static void menu_init (boot_params_t *boot_params) {
menu->next_mode = MENU_MODE_FAULT;
}
menu->error_message = NULL;
settings_load(&menu->settings);
directory_create(CACHE_DIRECTORY);
component_background_init(BACKGROUND_CACHE);
menu->boot_params = boot_params;
bool default_directory_exists = directory_exists(menu->settings.default_directory);
@ -67,21 +76,24 @@ static void menu_init (boot_params_t *boot_params) {
}
static void menu_deinit (menu_t *menu) {
display_close();
// NOTE: Restore previous TV type so boot procedure wouldn't passthrough wrong value.
TV_TYPE_RAM = tv_type;
flashcart_deinit();
path_free(menu->browser.directory);
free(menu);
component_background_free();
flashcart_deinit();
rdpq_close();
rspq_close();
mixer_close();
audio_close();
rtc_close();
timer_close();
display_close();
}
@ -203,7 +215,7 @@ void menu_run (boot_params_t *boot_params) {
audio_write_end();
}
png_poll();
png_decoder_poll();
}
menu_deinit(menu);

View File

@ -9,7 +9,6 @@
#include "boot/boot.h"
#include "components/components.h"
#include "flashcart/flashcart.h"
#include "path.h"
#include "settings.h"
@ -60,6 +59,8 @@ typedef struct {
boot_params_t *boot_params;
flashcart_error_t flashcart_error;
char *error_message;
struct {
bool go_up;
bool go_down;
@ -83,10 +84,6 @@ typedef struct {
int entries;
int selected;
} browser;
struct {
component_background_t *background;
} components;
} menu_t;

View File

@ -121,7 +121,7 @@ mp3player_err_t mp3player_init (void) {
p = calloc(1, sizeof(mp3player_t));
if (p == NULL) {
return MP3PLAYER_ERR_MALLOC;
return MP3PLAYER_ERR_OUT_OF_MEM;
}
mp3player_reset_decoder();

View File

@ -14,7 +14,7 @@
/** @brief MP3 file error enumeration */
typedef enum {
MP3PLAYER_OK,
MP3PLAYER_ERR_MALLOC,
MP3PLAYER_ERR_OUT_OF_MEM,
MP3PLAYER_ERR_IO,
MP3PLAYER_ERR_NO_FILE,
MP3PLAYER_ERR_INVALID_FILE,

View File

@ -13,6 +13,7 @@ typedef struct {
surface_t *image;
uint8_t *row_buffer;
int decoded_rows;
png_callback_t *callback;
void *callback_data;
@ -55,7 +56,7 @@ static int png_file_read (spng_ctx *ctx, void *user, void *dst_src, size_t lengt
}
png_err_t png_decode_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) {
if (decoder != NULL) {
return PNG_ERR_BUSY;
}
@ -124,45 +125,58 @@ png_err_t png_decode_start (char *path, int max_width, int max_height, png_callb
return PNG_ERR_OUT_OF_MEM;
}
decoder->decoded_rows = 0;
decoder->callback = callback;
decoder->callback_data = callback_data;
return PNG_OK;
}
void png_decode_abort (void) {
void png_decoder_abort (void) {
png_decoder_deinit(true);
}
void png_poll (void) {
if (decoder) {
enum spng_errno err;
struct spng_row_info row_info;
float png_decoder_get_progress (void) {
if (!decoder) {
return 0.0f;
}
if ((err = spng_get_row_info(decoder->ctx, &row_info)) != SPNG_OK) {
decoder->callback(PNG_ERR_BAD_FILE, NULL, decoder->callback_data);
png_decoder_deinit(true);
return;
}
return (float) (decoder->decoded_rows) / (decoder->ihdr.height);
}
err = spng_decode_row(decoder->ctx, decoder->row_buffer, decoder->ihdr.width * 3);
void png_decoder_poll (void) {
if (!decoder) {
return;
}
if (err == SPNG_OK || err == SPNG_EOI) {
uint16_t *image_buffer = decoder->image->buffer + (row_info.row_num * decoder->image->stride);
for (int i = 0; i < decoder->ihdr.width * 3; i += 3) {
uint8_t r = decoder->row_buffer[i + 0] >> 3;
uint8_t g = decoder->row_buffer[i + 1] >> 3;
uint8_t b = decoder->row_buffer[i + 2] >> 3;
*image_buffer++ = (r << 11) | (g << 6) | (b << 1) | 1;
}
}
enum spng_errno err;
struct spng_row_info row_info;
if (err == SPNG_EOI) {
decoder->callback(PNG_OK, decoder->image, decoder->callback_data);
png_decoder_deinit(false);
} else if (err != SPNG_OK) {
decoder->callback(PNG_ERR_BAD_FILE, NULL, decoder->callback_data);
png_decoder_deinit(true);
if ((err = spng_get_row_info(decoder->ctx, &row_info)) != SPNG_OK) {
decoder->callback(PNG_ERR_BAD_FILE, NULL, decoder->callback_data);
png_decoder_deinit(true);
return;
}
err = spng_decode_row(decoder->ctx, decoder->row_buffer, decoder->ihdr.width * 3);
if (err == SPNG_OK || err == SPNG_EOI) {
decoder->decoded_rows += 1;
uint16_t *image_buffer = decoder->image->buffer + (row_info.row_num * decoder->image->stride);
for (int i = 0; i < decoder->ihdr.width * 3; i += 3) {
uint8_t r = decoder->row_buffer[i + 0] >> 3;
uint8_t g = decoder->row_buffer[i + 1] >> 3;
uint8_t b = decoder->row_buffer[i + 2] >> 3;
*image_buffer++ = (r << 11) | (g << 6) | (b << 1) | 1;
}
}
if (err == SPNG_EOI) {
decoder->callback(PNG_OK, decoder->image, decoder->callback_data);
png_decoder_deinit(false);
} else if (err != SPNG_OK) {
decoder->callback(PNG_ERR_BAD_FILE, NULL, decoder->callback_data);
png_decoder_deinit(true);
}
}

View File

@ -24,9 +24,10 @@ typedef enum {
typedef void png_callback_t (png_err_t err, surface_t *decoded_image, void *callback_data);
png_err_t png_decode_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data);
void png_decode_abort (void);
void png_poll (void);
png_err_t png_decoder_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data);
void png_decoder_abort (void);
float png_decoder_get_progress (void);
void png_decoder_poll (void);
#endif

View File

@ -1,12 +1,10 @@
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fatfs/ff.h>
#include <libdragon.h>
#include "../components/components.h"
#include "fragments/fragments.h"
#include "../fonts.h"
#include "utils/fs.h"
#include "views.h"
@ -159,33 +157,6 @@ static bool pop_directory (menu_t *menu) {
return false;
}
static void format_size (char *buffer, int size) {
if (size < 8 * 1024) {
sprintf(buffer, "%4d B ", size);
} else if (size < 8 * 1024 * 1024) {
sprintf(buffer, "%4d kB", size / 1024);
} else if (size < 1 * 1024 * 1024 * 1024) {
sprintf(buffer, "%4d MB", size / 1024 / 1024);
} else {
sprintf(buffer, "%4d GB", size / 1024 / 1024 / 1024);
}
}
static void format_entry (char *buffer, entry_t *entry, bool selected) {
int cutoff_length = (entry->type == ENTRY_TYPE_DIR ? 57 : 49);
int name_length = strlen(entry->name);
strcpy(buffer, "");
if (entry->type == ENTRY_TYPE_DIR) {
strcat(buffer, "/");
}
if (name_length > cutoff_length) {
strncat(buffer, entry->name, cutoff_length - 1);
strcat(buffer, "");
} else {
strcat(buffer, entry->name);
}
}
static void process (menu_t *menu) {
int scroll_speed = menu->actions.fast ? 10 : 1;
@ -204,14 +175,14 @@ static void process (menu_t *menu) {
}
}
if (menu->actions.enter) {
if (menu->actions.enter && menu->browser.selected >= 0) {
entry_t *entry = &menu->browser.list[menu->browser.selected];
switch (entry->type) {
case ENTRY_TYPE_DIR:
if (push_directory(menu, entry->name)) {
menu->browser.valid = false;
menu->next_mode = MENU_MODE_ERROR;
menu_show_error(menu, "Couldn't open next directory");
}
break;
case ENTRY_TYPE_ROM:
@ -230,12 +201,10 @@ static void process (menu_t *menu) {
} else if (menu->actions.back && !path_is_root(menu->browser.directory)) {
if (pop_directory(menu)) {
menu->browser.valid = false;
menu->next_mode = MENU_MODE_ERROR;
}
} else if (menu->actions.file_info) {
if (menu->browser.selected >= 0) {
menu->next_mode = MENU_MODE_FILE_INFO;
menu_show_error(menu, "Couldn't open last directory");
}
} else if (menu->actions.file_info && menu->browser.selected >= 0) {
menu->next_mode = MENU_MODE_FILE_INFO;
} else if (menu->actions.system_info) {
menu->next_mode = MENU_MODE_SYSTEM_INFO;
} else if (menu->actions.settings) {
@ -243,125 +212,52 @@ static void process (menu_t *menu) {
}
}
static void draw (menu_t *menu, surface_t *d) {
char buffer[64];
layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const int text_file_size_x = text_x + 478;
const int text_other_actions_x = text_x + 450;
const int highlight_offset = 2;
const color_t highlight_color = RGBA32(0x3F, 0x3F, 0x3F, 0xFF);
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
const color_t directory_color = RGBA32(0xFF, 0xFF, 0x70, 0xFF);
const color_t save_color = RGBA32(0x70, 0xFF, 0x70, 0xFF);
const color_t music_color = RGBA32(0x70, 0xBC, 0xFF, 0xFF);
const color_t other_color = RGBA32(0xA0, 0xA0, 0xA0, 0xFF);
int starting_position = 0;
if (menu->browser.entries > layout->main_lines && menu->browser.selected >= (layout->main_lines / 2)) {
starting_position = menu->browser.selected - (layout->main_lines / 2);
if (starting_position >= menu->browser.entries - layout->main_lines) {
starting_position = menu->browser.entries - layout->main_lines;
}
}
rdpq_attach(d, NULL);
// Background
component_background_draw(menu->components.background);
component_background_draw();
// Layout
fragment_borders(d);
fragment_scrollbar(d, menu->browser.selected, menu->browser.entries);
component_layout_draw();
// Highlight
if (menu->browser.entries > 0) {
rdpq_set_mode_fill(highlight_color);
rdpq_fill_rectangle(
layout->offset_x,
text_y + highlight_offset + ((menu->browser.selected - starting_position) * layout->line_height),
d->width - layout->offset_x - layout->scrollbar_width,
text_y + layout->line_height + highlight_offset + ((menu->browser.selected - starting_position) * layout->line_height)
component_file_list_draw(menu->browser.list, menu->browser.entries, menu->browser.selected);
const char *action = NULL;
switch (menu->browser.list[menu->browser.selected].type) {
case ENTRY_TYPE_DIR: action = "A: Enter"; break;
case ENTRY_TYPE_ROM: action = "A: Load"; break;
case ENTRY_TYPE_IMAGE: action = "A: Show"; break;
case ENTRY_TYPE_MUSIC: action = "A: Play"; break;
default: action = "A: Info"; break;
}
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"%s\n"
"^%02XB: Back^00",
menu->browser.entries == 0 ? "" : action,
path_is_root(menu->browser.directory) ? STL_UNKNOWN : STL_DEFAULT
);
component_actions_bar_text_draw(
ALIGN_RIGHT, VALIGN_TOP,
"%s\n"
"L: Settings",
menu->browser.entries == 0 ? "" : "R: Info"
);
time_t current_time = time(NULL);
if (current_time >= 0) {
component_actions_bar_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"\n"
"%s",
ctime(&current_time)
);
}
// Text start
fragment_text_start(text_color);
// Main screen
for (int i = starting_position; i < menu->browser.entries; i++) {
if (i == (starting_position + layout->main_lines)) {
break;
}
entry_t *entry = &menu->browser.list[i];
switch (entry->type) {
case ENTRY_TYPE_DIR: fragment_text_set_color(directory_color); break;
case ENTRY_TYPE_SAVE: fragment_text_set_color(save_color); break;
case ENTRY_TYPE_OTHER: fragment_text_set_color(other_color); break;
case ENTRY_TYPE_MUSIC: fragment_text_set_color(music_color); break;
default: fragment_text_set_color(text_color); break;
}
format_entry(buffer, entry, i == menu->browser.selected);
fragment_textf(text_x, text_y, buffer);
if (entry->type != ENTRY_TYPE_DIR) {
format_size(buffer, entry->size);
fragment_text_set_color(text_color);
fragment_textf(text_file_size_x, text_y, buffer);
}
text_y += layout->line_height;
}
if (menu->browser.entries == 0) {
fragment_text_set_color(other_color);
fragment_textf(text_x, text_y, "** empty directory **");
}
// Actions bar
fragment_text_set_color(text_color);
text_y = layout->actions_y + layout->offset_text_y;
if (menu->browser.entries > 0) {
switch (menu->browser.list[menu->browser.selected].type) {
case ENTRY_TYPE_DIR:
fragment_textf(text_x, text_y, "A: Enter");
break;
case ENTRY_TYPE_ROM:
fragment_textf(text_x, text_y, "A: Load");
break;
case ENTRY_TYPE_IMAGE:
fragment_textf(text_x, text_y, "A: Show");
break;
case ENTRY_TYPE_MUSIC:
fragment_textf(text_x, text_y, "A: Play");
break;
default:
fragment_textf(text_x, text_y, "A: Info");
break;
}
fragment_textf(text_other_actions_x, text_y, "R: Info");
}
text_y += layout->line_height;
if (!path_is_root(menu->browser.directory)) {
fragment_textf(text_x, text_y, "B: Back");
}
fragment_textf(text_other_actions_x, text_y, "L: Settings");
time_t current_time = -1;
current_time = time( NULL );
if( current_time != -1 )
{
fragment_textf(text_other_actions_x - 288, text_y, ctime( &current_time ));
}
rdpq_detach_show();
}
@ -371,7 +267,7 @@ void view_browser_init (menu_t *menu) {
if (load_directory(menu)) {
path_free(menu->browser.directory);
menu->browser.directory = path_init(NULL);
menu->next_mode = MENU_MODE_ERROR;
menu_show_error(menu, "Error while opening initial directory");
} else {
menu->browser.valid = true;
}
@ -380,5 +276,6 @@ void view_browser_init (menu_t *menu) {
void view_browser_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
}

View File

@ -1,7 +1,3 @@
#include <libdragon.h>
#include "../components/components.h"
#include "fragments/fragments.h"
#include "views.h"
@ -17,51 +13,45 @@ static void process (menu_t *menu) {
}
static void draw (menu_t *menu, surface_t *d) {
layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_attach(d, NULL);
// Background
component_background_draw(menu->components.background);
component_background_draw();
// Layout
fragment_borders(d);
component_layout_draw();
// Text start
fragment_text_start(text_color);
component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"MENU INFORMATION"
);
text_y += fragment_textf((d->width / 2) - 76, text_y, "MENU INFORMATION");
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n"
"Menu Revision: V%s\n"
"\n"
"Github:\n"
" https://github.com/Polprzewodnikowy/N64FlashcartMenu\n"
"Authors:\n"
" JonesAlmighty / NetworkFusion\n"
" Mateusz Faderewski / Polprzewodnikowy\n"
"Credits:\n"
" N64Brew / libdragon contributors\n"
"\n"
"OSS software used:\n"
" libdragon (UNLICENSE License)\n"
" libspng (BSD 2-Clause License)\n"
" mini.c (BSD 2-Clause License)\n"
" minimp3 (CC0 1.0 Universal)\n"
" miniz (MIT License)",
MENU_VERSION
);
text_y += fragment_textf(text_x, text_y, "\n");
text_y += fragment_textf(text_x, text_y, "Menu Revision: V%s", MENU_VERSION);
text_y += fragment_textf(text_x, text_y, "\n");
text_y += fragment_textf(text_x, text_y, "Authors:");
text_y += fragment_textf(text_x, text_y, " JonesAlmighty / NetworkFusion");
text_y += fragment_textf(text_x, text_y, " korgeaux / Polprzewodnikowy");
text_y += fragment_textf(text_x, text_y, "\n");
text_y += fragment_textf(text_x, text_y, "Credits:");
text_y += fragment_textf(text_x, text_y, " N64Brew / libdragon contributors.");
text_y += fragment_textf(text_x, text_y, "\n");
text_y += fragment_textf(text_x, text_y, "Github:");
text_y += fragment_textf(text_x, text_y, " https://github.com/Polprzewodnikowy/N64FlashcartMenu");
text_y += fragment_textf(text_x, text_y, "\n");
text_y += fragment_textf(text_x, text_y, "OSS licenses used:");
text_y += fragment_textf(text_x, text_y, " UNLICENSE"); /* libdragon license */
text_y += fragment_textf(text_x, text_y, " MIT");
text_y += fragment_textf(text_x, text_y, " BSD 2-Clause"); /* libspng license */
text_y += fragment_textf(text_x, text_y, " CC0 1.0 Universal"); /* minimp3 license */
// text_y += fragment_textf(text_x, text_y, " Permissive, unspecific"); /* miniz license */
// Actions bar
text_y = layout->actions_y + layout->offset_text_y;
text_y += fragment_textf(text_x, text_y, "B: Exit");
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"B: Exit"
);
rdpq_detach_show();
}
@ -73,5 +63,6 @@ void view_credits_init (menu_t *menu) {
void view_credits_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
}

View File

@ -1,5 +1,3 @@
#include <libdragon.h>
#include "views.h"
@ -10,11 +8,21 @@ static void process (menu_t *menu) {
}
static void draw (menu_t *menu, surface_t *d) {
graphics_fill_screen(d, graphics_make_color(0, 0, 0, 255));
rdpq_attach(d, NULL);
graphics_draw_text(d, 40, 32, "ERROR!");
component_background_draw();
display_show(d);
if (menu->error_message) {
component_messagebox_draw(menu->error_message);
} else {
component_messagebox_draw("Unspecified error");
}
rdpq_detach_show();
}
static void deinit (menu_t *menu) {
menu->error_message = NULL;
}
@ -24,5 +32,15 @@ void view_error_init (menu_t *menu) {
void view_error_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
if (menu->next_mode != MENU_MODE_ERROR) {
deinit(menu);
}
}
void menu_show_error (menu_t *menu, char *error_message) {
menu->next_mode = MENU_MODE_ERROR;
menu->error_message = error_message;
}

View File

@ -1,11 +1,7 @@
#include <libdragon.h>
#include "flashcart/flashcart.h"
#include "fragments/fragments.h"
#include "views.h"
static char *format_flashcart_error (flashcart_error_t error) {
static char *convert_error_message (flashcart_error_t error) {
switch (error) {
case FLASHCART_OK:
return "No error";
@ -28,26 +24,26 @@ static char *format_flashcart_error (flashcart_error_t error) {
static void draw (menu_t *menu, surface_t *d) {
layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const color_t bg_color = RGBA32(0x7F, 0x00, 0x00, 0xFF);
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_attach(d, NULL);
rdpq_clear(bg_color);
fragment_text_start(text_color);
text_y += fragment_textf(text_x, text_y, "Unrecoverable error:");
text_y += fragment_textf(text_x, text_y, " %s", format_flashcart_error(menu->flashcart_error));
if (menu->flashcart_error == FLASHCART_ERROR_OUTDATED) {
text_y += fragment_textf(text_x, text_y, " Minimum supported versions:");
text_y += fragment_textf(text_x, text_y, " EverDrive-64: ?");
text_y += fragment_textf(text_x, text_y, " 64drive: ?");
text_y += fragment_textf(text_x, text_y, " SC64: 2.16.0");
}
rdpq_clear(RGBA32(0x7F, 0x00, 0x00, 0xFF));
const char *firmware_message = (
"Minimum supported versions:\n"
"EverDrive-64: ?\n"
"64drive: ?\n"
"SC64: 2.16.0"
);
component_messagebox_draw(
"UNRECOVERABLE ERROR\n"
"\n"
"%s\n"
"\n"
"%s",
convert_error_message(menu->flashcart_error),
menu->flashcart_error == FLASHCART_ERROR_OUTDATED ? firmware_message : ""
);
rdpq_detach_show();
}

View File

@ -1,215 +1,51 @@
#include <fatfs/ff.h>
#include <libdragon.h>
#include <stdlib.h>
#include "../components/components.h"
#include "../png_decoder.h"
#include "../rom_database.h"
#include "fragments/fragments.h"
#include "utils/fs.h"
#include "utils/str_utils.h"
#include "views.h"
#ifndef ROM_BOXART_PATH
#define ROM_BOXART_PATH "/menu/boxart"
#endif
static const char *n64_rom_extensions[] = { "z64", "n64", "v64", NULL };
static const char *text_extensions[] = { "txt", NULL };
static const char *config_extensions[] = { "ini", "cfg", "yml", "yaml", "toml", NULL };
static const char *save_extensions[] = { "sav", "eep", "eeprom", "sra", "srm", "ram", "fla", "flashram", NULL };
static const char *patch_extensions[] = { "ips", "aps", "pps", "xdelta", NULL };
static const char *archive_extensions[] = { "zip", "rar", "7z", "tar", "gz", NULL };
static const char *image_extensions[] = { "png", "jpg", "gif", NULL };
static const char *music_extensions[] = { "mp3", "wav", "ogg", "wma", "flac", NULL };
static const char *dexdrive_extensions[] = { "mpk", NULL };
static const char *emulator_extensions[] = { "emu", NULL };
static FILINFO info;
static const char *n64_rom_extensions[] = { "z64", "n64", "v64", NULL };
static rom_header_t rom_header;
static bool boxart_image_loading;
static surface_t *boxart_image;
static char *format_rom_endian (uint32_t endian) {
switch (endian)
{
case ROM_BIG_ENDIAN:
case IPL_BIG_ENDIAN:
return "Big (default)"; // expected
break;
case ROM_LITTLE_ENDIAN:
return "Little";
break;
case ROM_MID_LITTLE_ENDIAN:
return "Middle Little";
break;
case ROM_MID_BIG_ENDIAN:
return "Middle Big";
break;
default:
return "Unknown";
break;
static char *format_file_type (char *name, bool is_directory) {
if (is_directory) {
return "";
} if (file_has_extensions(name, n64_rom_extensions)) {
return " Type: N64 ROM\n";
} else if (file_has_extensions(name, text_extensions)) {
return " Type: Text file\n";
} else if (file_has_extensions(name, config_extensions)) {
return " Type: Config file\n";
} else if (file_has_extensions(name, save_extensions)) {
return " Type: N64 save\n";
} else if (file_has_extensions(name, patch_extensions)) {
return " Type: ROM patch\n";
} else if (file_has_extensions(name, archive_extensions)) {
return " Type: Archive\n";
} else if (file_has_extensions(name, image_extensions)) {
return " Type: Image file\n";
} else if (file_has_extensions(name, music_extensions)) {
return " Type: Music file\n";
} else if (file_has_extensions(name, dexdrive_extensions)) {
return " Type: DexDrive CPak backup file\n";
} else if (file_has_extensions(name, emulator_extensions)) {
return " Type: Emulator file\n";
}
return " Type: Unknown file\n";
}
static char *format_rom_media_type (uint8_t type) {
switch (type)
{
case N64_CART:
return "Cartridge";
break;
case N64_DISK:
return "Disk";
break;
case N64_CART_EXPANDABLE:
return "Cart Expandable";
break;
case N64_DISK_EXPANDABLE:
return "Disk Expandable";
break;
case N64_ALECK64:
return "Aleck64";
break;
default:
return "Unknown";
break;
}
}
static char *format_rom_destination_market (uint8_t market_type) {
// TODO: These are all assumptions and should be corrected if required.
switch (market_type) {
case MARKET_ALL:
return "All";
case MARKET_BRAZIL:
return "Brazil (MPAL)";
case MARKET_CHINA:
return "China";
case MARKET_GERMANY:
return "Germany (PAL)";
case MARKET_USA:
return "USA (NTSC)";
case MARKET_FRANCE:
return "France (PAL)";
case MARKET_NETHERLANDS:
return "Netherlands (PAL)";
case MARKET_ITALY:
return "Italy (PAL)";
case MARKET_JAPAN:
return "Japan (NTSC)";
case MARKET_KOREA:
return "Korea";
case MARKET_CANADA:
return "Canada";
case MARKET_SPAIN:
return "Spain (PAL)";
case MARKET_AUSTRAILA:
return "Austraila (PAL)";
case MARKET_SCANDINAVAIA:
return "Scandinavaia";
case MARKET_GATEWAY64_NTSC:
return "Gateway (NTSC)";
case MARKET_GATEWAY64_PAL:
return "Gateway (PAL)";
case MARKET_PAL_GENERIC:
return "Generic (PAL)";
case MARKET_PAL_X: //FIXME: some AUS ROM's use this so not only EUR
case MARKET_PAL_Y:
case MARKET_PAL_Z:
return "Unknown (PAL)";
default:
return "Unknown";
}
}
static char *format_rom_save_type (uint8_t type) {
switch (type)
{
case DB_SAVE_TYPE_EEPROM_4K:
return "EEPROM 4K";
break;
case DB_SAVE_TYPE_EEPROM_16K:
return "EEPROM 16K";
break;
case DB_SAVE_TYPE_SRAM:
return "SRAM";
break;
case DB_SAVE_TYPE_SRAM_BANKED:
return "SRAM Banked";
break;
case DB_SAVE_TYPE_SRAM_128K:
return "SRAM 128K [ED64]";
break;
case DB_SAVE_TYPE_FLASHRAM:
return "FLASH RAM";
break;
case DB_SAVE_TYPE_CPAK:
return "Controller PAK";
break;
default:
return "Unknown";
break;
}
}
static char *format_rom_memory_type (uint8_t type) {
switch (type)
{
case DB_MEMORY_EXPANSION_REQUIRED:
return "Required";
break;
case DB_MEMORY_EXPANSION_RECOMMENDED:
return "Recommended";
break;
case DB_MEMORY_EXPANSION_SUGGESTED:
return "Suggested";
break;
case DB_MEMORY_EXPANSION_FAULTY:
return "Ensure Patched";
break;
default:
return "Not Required";
break;
}
}
static char *format_file_type (void) {
// TODO: should be at least a switch statement!
if (str_endswith(info.fname, ".z64", false) ||
str_endswith(info.fname, ".n64", false) ||
str_endswith(info.fname, ".v64", false) ||
str_endswith(info.fname, ".rom", false)
) {
// TODO: check the necessary bytes in the header to ensure!
return "N64 ROM";
}
else if (str_endswith(info.fname, ".txt", false)) {
return "Text File";
}
else if (str_endswith(info.fname, ".ini", false)) {
return "INI File";
}
else if (str_endswith(info.fname, ".yml", false) || str_endswith(info.fname, ".yaml", false)) {
return "YAML File";
}
else if (str_endswith(info.fname, ".toml", false)) {
return "TOML File";
}
else if (str_endswith(info.fname, ".sav", false) || str_endswith(info.fname, ".eep", false) || str_endswith(info.fname, ".sra", false) || str_endswith(info.fname, ".srm", false)|| str_endswith(info.fname, ".fla", false)) {
return "ROM Save File";
}
else if (str_endswith(info.fname, ".ips", false) || str_endswith(info.fname, ".aps", false) || str_endswith(info.fname, ".pps", false) || str_endswith(info.fname, ".xdelta", false)) {
return "ROM Patch File";
}
else if (str_endswith(info.fname, ".zip", false)) {
return "ZIP Archive";
}
else if (str_endswith(info.fname, ".mpk", false)) {
return "DexDrive CPak Backup File";
}
else if (str_endswith(info.fname, ".emu", false)) {
return "Emulator File";
}
else {
return "Unknown File";
}
}
static void process (menu_t *menu) {
if (menu->actions.back) {
@ -217,152 +53,57 @@ static void process (menu_t *menu) {
}
}
static void menu_fileinfo_draw_unknown_info(surface_t *d, layout_t *layout) {
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
text_y += fragment_textf(text_x, text_y, "File Information:\n\n");
text_y += fragment_textf(text_x, text_y, " Name:\n\n %s\n", info.fname);
text_y += fragment_textf(text_x, text_y, " Size:\n\n %d Bytes\n", (int)info.fsize);
text_y += fragment_textf(text_x, text_y, " Attributes:\n\n %s%s%s%s%s\n",
((info.fattrib & AM_DIR) ? " Directory" : "File"),
((info.fattrib & AM_RDO) ? " | ReadOnly" : ""),
((info.fattrib & AM_SYS) ? " | System" : ""),
((info.fattrib & AM_ARC) ? " | Archive" : ""),
((info.fattrib & AM_HID) ? " | Hidden" : "")
);
text_y += fragment_textf(text_x, text_y, " Modified Timestamp:\n\n %u-%02u-%02u, %02u:%02u\n",
(info.fdate >> 9) + 1980,
info.fdate >> 5 & 15,
info.fdate & 31,
info.ftime >> 11,
info.ftime >> 5 & 63
);
text_y += fragment_textf(text_x, text_y, " Type:\n\n %s\n", format_file_type());
}
static void menu_fileinfo_draw_n64_rom_info(surface_t *d, layout_t *layout) {
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
text_y += fragment_textf(text_x, text_y, "File Information:\n\n");
text_y += fragment_textf(text_x, text_y, " Name:\n\n %s\n", info.fname);
text_y += fragment_textf(text_x, text_y, " Size:\n\n %d Bytes\n", (int)info.fsize);
text_y += fragment_textf(text_x, text_y, " Modified Timestamp:\n\n %u-%02u-%02u, %02u:%02u\n",
(info.fdate >> 9) + 1980,
info.fdate >> 5 & 15,
info.fdate & 31,
info.ftime >> 11,
info.ftime >> 5 & 63
);
text_y += fragment_textf(text_x, text_y, "\n");
text_y += fragment_textf(text_x, text_y, "N64 ROM Information:\n");
text_y += fragment_textf(text_x, text_y, " Endian: %s\n", format_rom_endian(rom_header.config_flags));
text_y += fragment_textf(text_x, text_y, " Title: %s\n", rom_header.title);
text_y += fragment_textf(text_x, text_y, " Media Type: %c - %s\n", rom_header.metadata.media_type, format_rom_media_type(rom_header.metadata.media_type));
text_y += fragment_textf(text_x, text_y, " Unique ID: %.2s\n", (char*)&(rom_header.metadata.unique_identifier));
text_y += fragment_textf(text_x, text_y, " Destination Market: %c - %s\n", rom_header.metadata.destination_market, format_rom_destination_market(rom_header.metadata.destination_market));
text_y += fragment_textf(text_x, text_y, " Version: %hhu\n", rom_header.version);
text_y += fragment_textf(text_x, text_y, " Checksum: 0x%016llX\n", rom_header.checksum);
text_y += fragment_textf(text_x, text_y, " Save Type: %s\n", format_rom_save_type(rom_db_match_save_type(rom_header)));
text_y += fragment_textf(text_x, text_y, " Expansion PAK: %s\n", format_rom_memory_type(rom_db_match_expansion_pak(rom_header)));
// TODO: Should Extra Info be optional.
text_y += fragment_textf(text_x, text_y, " Extra Info:");
if ((rom_header.clock_rate & 0xFFFFFFF0) != 0) {
text_y += fragment_textf(text_x, text_y, " Clock Rate?!: %d.%02dMHz\n", (rom_header.clock_rate & 0xFFFFFFF0) /1000000, (rom_header.clock_rate & 0xFFFFFFF0) % 1000000);
}
text_y += fragment_textf(text_x, text_y, " Boot address: 0x%08lX\n", rom_header.boot_address);
text_y += fragment_textf(text_x, text_y, " SDK Version: %d\n", rom_header.sdk_version);
if (boxart_image) {
uint16_t x = (640 - 150) - (boxart_image->width / 2);
uint16_t y = (480 - 150) - (boxart_image->height / 2);
rdpq_set_mode_copy(false);
rdpq_tex_blit(boxart_image, x, y, NULL);
}
}
static void draw (menu_t *menu, surface_t *d) {
layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_attach(d, NULL);
// Background
component_background_draw(menu->components.background);
component_background_draw();
// Layout
fragment_borders(d);
component_layout_draw();
// Text start
fragment_text_start(text_color);
component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"ENTRY INFORMATION\n"
"\n"
"%s",
menu->browser.list[menu->browser.selected].name
);
if (file_has_extensions(info.fname, n64_rom_extensions)) {
menu_fileinfo_draw_n64_rom_info(d, layout);
} else {
menu_fileinfo_draw_unknown_info(d, layout);
}
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n"
"\n"
"\n"
" Size: %d bytes\n"
" Attributes: %s%s%s%s%s\n"
"%s"
" Modified: %u-%02u-%02u %02u:%02u",
info.fsize,
(info.fattrib & AM_DIR) ? "Directory " : "File ",
(info.fattrib & AM_RDO) ? "| Read only " : "",
(info.fattrib & AM_SYS) ? "| System " : "",
(info.fattrib & AM_ARC) ? "| Archive " : "",
(info.fattrib & AM_HID) ? "| Hidden " : "",
format_file_type(info.fname, info.fattrib & AM_DIR),
(info.fdate >> 9) + 1980, info.fdate >> 5 & 0x0F, info.fdate & 0x1F, info.ftime >> 11, info.ftime >> 5 & 0x3F
);
// Ensure RDP mode and loaded texture dont mess up font drawing.
fragment_text_start(text_color);
// Actions bar
text_y = layout->actions_y + layout->offset_text_y;
text_y += fragment_textf(text_x, text_y, "B: Exit");
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"B: Exit"
);
rdpq_detach_show();
}
static void boxart_image_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
boxart_image_loading = false;
boxart_image = decoded_image;
}
/* loads a PNG image for a given ROM ID from ROM_BOXART_PATH. e.g. sd:/menu/boxart/<id>.png */
static void boxart_image_load (uint16_t id) {
char sd_boxart_path[32];
sprintf(sd_boxart_path, "%s/%.2s.png", ROM_BOXART_PATH, (char *) (&id));
if (png_decode_start(sd_boxart_path, 158, 112, boxart_image_callback, NULL) != PNG_OK) {
debugf("Error loading boxart image\n");
}
}
static void deinit (menu_t *menu) {
if (boxart_image_loading) {
png_decode_abort();
}
if (boxart_image) {
surface_free(boxart_image);
free(boxart_image);
}
}
void view_file_info_init (menu_t *menu) {
boxart_image_loading = true;
boxart_image = NULL;
char *file_name = menu->browser.list[menu->browser.selected].name;
path_t *file = path_clone(menu->browser.directory);
path_push(file, file_name);
path_push(file, menu->browser.list[menu->browser.selected].name);
if (f_stat(path_get(file), &info) != FR_OK) {
menu->next_mode = MENU_MODE_ERROR;
}
if (file_has_extensions(file_name, n64_rom_extensions)) {
rom_header = file_read_rom_header(path_get(file));
boxart_image_load(rom_header.metadata.unique_identifier);
if (f_stat(strip_sd_prefix(path_get(file)), &info) != FR_OK) {
menu_show_error(menu, "Couldn't obtain file information");
}
path_free(file);
@ -372,8 +113,4 @@ void view_file_info_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
if (menu->next_mode != MENU_MODE_FILE_INFO) {
deinit(menu);
}
}

View File

@ -1,108 +0,0 @@
#include <stdarg.h>
#include <libdragon.h>
#include "fragments.h"
#include "../../assets.h"
// TODO: Prepare layout for PAL display
static layout_t layout = {
.offset_x = 32,
.offset_y = 24,
.offset_text_x = 10,
.offset_text_y = 7,
.line_height = 18,
.scrollbar_width = 10,
.progressbar_height = 16,
.border_thickness = 5,
.main_lines = 20,
.actions_x = 20,
.actions_y = 405,
.actions_lines = 2,
};
layout_t *layout_get(void) {
return &layout;
}
void fragment_borders (surface_t *d) {
widget_border(
layout.offset_x,
layout.offset_y,
d->width - layout.offset_x,
d->height - layout.offset_y,
layout.border_thickness
);
widget_horizontal_line(
layout.offset_x,
d->width - layout.offset_x,
layout.offset_y + ((layout.main_lines + 1) * layout.line_height),
layout.border_thickness
);
}
void fragment_scrollbar (surface_t *d, int position, int items) {
widget_scrollbar(
d->width - layout.offset_x - layout.scrollbar_width,
layout.offset_y,
layout.scrollbar_width,
(layout.main_lines + 1) * layout.line_height,
position,
items,
layout.main_lines
);
}
void fragment_progressbar (surface_t *d, float progress) {
widget_progressbar (
layout.offset_x,
layout.offset_y + ((layout.main_lines + 1) * layout.line_height) - layout.progressbar_height,
d->width - (layout.offset_x * 2),
layout.progressbar_height,
progress
);
}
void fragment_text_start (color_t color) {
rdpq_font_begin(color);
}
void fragment_text_set_color (color_t color) {
rdpq_set_prim_color(color);
}
int fragment_textf (int x, int y, char *fmt, ...) {
char buffer[64];
assets_t *assets = assets_get();
rdpq_font_position(x, y + assets->font_height);
va_list va;
va_start(va, fmt);
int n = vsnprintf(buffer, sizeof(buffer), fmt, va);
va_end(va);
rdpq_font_printn(assets->font, buffer, n);
return layout.line_height;
}
void fragment_loader (surface_t *d) {
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
const int offset_x = 248;
const int offset_y = 212;
const int text_offset_x = -39;
widget_border(offset_x, offset_y, d->width - offset_x, d->height - offset_y, layout.border_thickness);
fragment_text_start(text_color);
fragment_textf((d->width / 2) + text_offset_x, (d->height / 2) - (layout.line_height / 2), "Loading…");
}

View File

@ -1,59 +0,0 @@
/**
* @file fragments.h
* @brief Menu View Fragments
* @ingroup menu
*/
#ifndef FRAGMENTS_H__
#define FRAGMENTS_H__
#include <surface.h>
/**
* @addtogroup view_fragments
* @{
*/
void widget_horizontal_line (int x1, int x2, int y, int thickness);
void widget_border (int x1, int y1, int x2, int y2, int thickness);
void widget_scrollbar (int x, int y, int width, int height, int position, int items, int visible_items);
void widget_progressbar (int x, int y, int width, int height, float progress);
/** @brief Layout Structure */
typedef struct {
int offset_x;
int offset_y;
int offset_text_x;
int offset_text_y;
int line_height;
int scrollbar_width;
int progressbar_height;
int border_thickness;
int main_lines;
int actions_x;
int actions_y;
int actions_lines;
} layout_t;
layout_t *layout_get(void);
void fragment_borders (surface_t *d);
void fragment_scrollbar (surface_t *d, int position, int items);
void fragment_progressbar (surface_t *d, float progress);
void fragment_text_start (color_t color);
void fragment_text_set_color (color_t color);
int fragment_textf (int x, int y, char *fmt, ...);
void fragment_loader (surface_t *d);
/** @} */ /* view_fragments */
#endif

View File

@ -1,55 +0,0 @@
#include <libdragon.h>
void widget_horizontal_line (int x1, int x2, int y, int thickness) {
color_t line_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_set_mode_fill(line_color);
rdpq_fill_rectangle(x1, y, x2, y + thickness);
}
void widget_border (int x1, int y1, int x2, int y2, int thickness) {
color_t border_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_set_mode_fill(border_color);
rdpq_fill_rectangle(x1 - thickness, y1 - thickness, x2 + thickness, y1);
rdpq_fill_rectangle(x1 - thickness, y2, x2 + thickness, y2 + thickness);
rdpq_fill_rectangle(x1 - thickness, y1, x1, y2);
rdpq_fill_rectangle(x2, y1, x2 + thickness, y2);
}
void widget_scrollbar (int x, int y, int width, int height, int position, int items, int visible_items) {
color_t bg_color = RGBA32(0x3F, 0x3F, 0x3F, 0xFF);
color_t inactive_color = RGBA32(0x5F, 0x5F, 0x5F, 0xFF);
color_t active_color = RGBA32(0x7F, 0x7F, 0x7F, 0xFF);
if (items < 2 || items <= visible_items) {
rdpq_set_mode_fill(inactive_color);
rdpq_fill_rectangle(x, y, x + width, y + height);
} else {
int scroll_height = (int) ((visible_items / (float) (items)) * height);
float scroll_position = ((position / (float) (items - 1)) * (height - scroll_height));
rdpq_set_mode_fill(bg_color);
rdpq_fill_rectangle(x, y, x + width, y + height);
rdpq_set_fill_color(active_color);
rdpq_fill_rectangle(x, y + scroll_position, x + width, y + scroll_position + scroll_height);
}
}
void widget_progressbar (int x, int y, int width, int height, float progress) {
color_t bg_color = RGBA32(0x3F, 0x3F, 0x3F, 0xFF);
color_t fg_color = RGBA32(0x7F, 0x7F, 0x7F, 0xFF);
float progress_width = progress * width;
rdpq_set_fill_color(fg_color);
rdpq_fill_rectangle(x, y, x + progress_width, y + height);
rdpq_set_mode_fill(bg_color);
rdpq_fill_rectangle(x + progress_width, y, x + width, y + height);
}

View File

@ -1,24 +1,52 @@
#include <libdragon.h>
#include <stdlib.h>
#include "../components/components.h"
#include "../png_decoder.h"
#include "fragments/fragments.h"
#include "views.h"
static bool show_message;
static bool image_loading;
static bool image_set_as_background;
static surface_t *image;
static char *convert_error_message (png_err_t err) {
switch (err) {
case PNG_ERR_INT: return "Internal PNG decoder error";
case PNG_ERR_BUSY: return "PNG decode already in process";
case PNG_ERR_OUT_OF_MEM: return "PNG decode failed due to insufficient memory";
case PNG_ERR_NO_FILE: return "PNG decoder couldn't open file";
case PNG_ERR_BAD_FILE: return "Invalid PNG file";
default: return "Unknown PNG decoder error";
}
}
static void image_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
menu_t *menu = (menu_t *) (callback_data);
image_loading = false;
image = decoded_image;
if (err != PNG_OK) {
menu_show_error(menu, convert_error_message(err));
}
}
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
} else if (menu->actions.enter) {
if (image) {
if (show_message) {
show_message = false;
} else {
menu->next_mode = MENU_MODE_BROWSER;
}
} else if (menu->actions.enter && image) {
if (show_message) {
show_message = false;
image_set_as_background = true;
menu->next_mode = MENU_MODE_BROWSER;
} else {
show_message = true;
}
}
}
@ -27,40 +55,42 @@ static void draw (menu_t *menu, surface_t *d) {
if (!image) {
rdpq_attach(d, NULL);
component_background_draw(menu->components.background);
component_background_draw();
fragment_loader(d);
component_loader_draw(png_decoder_get_progress());
} else {
rdpq_attach_clear(d, NULL);
uint16_t x = (d->width / 2) - (image->width / 2);
uint16_t y = (d->height / 2) - (image->height / 2);
rdpq_set_mode_copy(false);
rdpq_tex_blit(image, x, y, NULL);
rdpq_mode_push();
rdpq_set_mode_copy(false);
rdpq_tex_blit(image, x, y, NULL);
rdpq_mode_pop();
if (show_message) {
component_messagebox_draw(
"Set \"%s\" as background image?\n\n"
"A: Yes, B: Back",
menu->browser.list[menu->browser.selected].name
);
} else if (image_set_as_background) {
component_messagebox_draw("Preparing background…");
}
}
rdpq_detach_show();
}
static void image_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
image_loading = false;
image = decoded_image;
if (err != PNG_OK) {
menu_t *menu = (menu_t *) (callback_data);
menu->next_mode = MENU_MODE_ERROR;
}
}
static void deinit (menu_t *menu) {
if (image_loading) {
png_decode_abort();
png_decoder_abort();
}
if (image) {
if (image_set_as_background) {
component_background_replace_image(menu->components.background, image);
component_background_replace_image(image);
} else {
surface_free(image);
free(image);
@ -70,17 +100,17 @@ static void deinit (menu_t *menu) {
void view_image_viewer_init (menu_t *menu) {
image_loading = false;
show_message = false;
image_loading = true;
image_set_as_background = false;
image = NULL;
path_t *path = path_clone(menu->browser.directory);
path_push(path, menu->browser.list[menu->browser.selected].name);
if (png_decode_start(path_get(path), 640, 480, image_callback, menu) == PNG_OK) {
image_loading = true;
} else {
menu->next_mode = MENU_MODE_ERROR;
png_err_t err = png_decoder_start(path_get(path), 640, 480, image_callback, menu);
if (err != PNG_OK) {
menu_show_error(menu, convert_error_message(err));
}
path_free(path);

View File

@ -1,22 +1,19 @@
#include <libdragon.h>
#include "../components/components.h"
#include "../rom_database.h"
#include "boot/boot.h"
#include "flashcart/flashcart.h"
#include "fragments/fragments.h"
#include "views.h"
static bool load_pending;
static rom_header_t rom_header;
static component_boxart_t *boxart;
static char *format_rom_endian (uint32_t endian) {
static char *format_rom_endian (rom_endian_type_t endian) {
switch (endian) {
case ROM_BIG_ENDIAN:
case IPL_BIG_ENDIAN:
return "Big (native)";
return "Big (default)";
case ROM_LITTLE_ENDIAN:
return "Little (unsupported)";
case ROM_MID_BIG_ENDIAN:
@ -26,7 +23,7 @@ static char *format_rom_endian (uint32_t endian) {
}
}
static char *format_rom_media_type (uint8_t media_type) {
static char *format_rom_media_type (rom_media_type_t media_type) {
switch (media_type) {
case N64_CART:
return "Cartridge";
@ -43,7 +40,7 @@ static char *format_rom_media_type (uint8_t media_type) {
}
}
static char *format_rom_destination_market (uint8_t market_type) {
static char *format_rom_destination_market (rom_destination_market_t market_type) {
// TODO: These are all assumptions and should be corrected if required.
switch (market_type) {
case MARKET_ALL:
@ -80,17 +77,16 @@ static char *format_rom_destination_market (uint8_t market_type) {
return "Gateway (PAL)";
case MARKET_PAL_GENERIC:
return "Generic (PAL)";
case MARKET_PAL_X: //FIXME: some AUS ROM's use this so not only EUR
case MARKET_PAL_X: // FIXME: some AUS ROM's use this so not only EUR
case MARKET_PAL_Y:
case MARKET_PAL_Z:
return "Unknown (PAL)";
default:
return "Unknown";
}
}
static char *format_rom_save_type (uint8_t save_type) {
static char *format_rom_save_type (db_savetype_t save_type) {
switch (save_type) {
case DB_SAVE_TYPE_NONE:
return "None";
@ -107,34 +103,113 @@ static char *format_rom_save_type (uint8_t save_type) {
case DB_SAVE_TYPE_FLASHRAM:
return "FlashRAM";
case DB_SAVE_TYPE_CPAK:
return "Controller PAK";
return "Controller Pak";
default:
return "Unknown";
}
}
static char *format_rom_memory_type (uint8_t memory_type) {
switch (memory_type)
{
case DB_MEMORY_EXPANSION_REQUIRED:
return "Required";
break;
case DB_MEMORY_EXPANSION_RECOMMENDED:
return "Recommended";
break;
case DB_MEMORY_EXPANSION_SUGGESTED:
return "Suggested";
break;
case DB_MEMORY_EXPANSION_FAULTY:
return "May require ROM patch";
break;
default:
return "Not required";
break;
static char *format_rom_expansion_pak_info (rom_memorytype_t expansion_pak_info) {
switch (expansion_pak_info) {
case DB_MEMORY_EXPANSION_REQUIRED:
return "Required";
case DB_MEMORY_EXPANSION_RECOMMENDED:
return "Recommended";
case DB_MEMORY_EXPANSION_SUGGESTED:
return "Suggested";
case DB_MEMORY_EXPANSION_FAULTY:
return "May require ROM patch";
default:
return "Not required";
}
}
static flashcart_save_type_t convert_save_type (uint8_t save_type) {
static void process (menu_t *menu) {
if (menu->actions.enter) {
load_pending = true;
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
}
}
static void draw (menu_t *menu, surface_t *d) {
rdpq_attach(d, NULL);
component_background_draw();
if (load_pending) {
component_loader_draw(0.0f);
} else {
component_layout_draw();
component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"N64 ROM information\n"
"\n"
"%s",
menu->browser.list[menu->browser.selected].name
);
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n"
"\n"
"\n"
" Endian: %s\n"
" Title: %.20s\n"
" Media type: %c - %s\n"
" Unique ID: %.2s\n"
" Destination market: %c - %s\n"
" Version: %hhu\n"
" Checksum: 0x%016llX\n"
" Save type: %s\n"
" Expansion PAK: %s\n"
"\n"
" Extra info:\n"
" Boot address: 0x%08lX\n"
" SDK version: %.1f%c",
format_rom_endian(rom_header.config_flags),
rom_header.title,
rom_header.metadata.media_type, format_rom_media_type(rom_header.metadata.media_type),
(char *) (&rom_header.metadata.unique_identifier),
rom_header.metadata.destination_market, format_rom_destination_market(rom_header.metadata.destination_market),
rom_header.version,
rom_header.checksum,
format_rom_save_type(rom_db_match_save_type(rom_header)),
format_rom_expansion_pak_info(rom_db_match_expansion_pak(rom_header)),
rom_header.boot_address,
(float) ((rom_header.sdk_version >> 8) & 0xFF) / 10.0f, (char) (rom_header.sdk_version & 0xFF)
);
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"A: Load and run ROM\n"
"B: Exit"
);
component_boxart_draw(boxart);
}
rdpq_detach_show();
}
static void draw_progress (float progress) {
surface_t *d = display_try_get();
if (d) {
rdpq_attach(d, NULL);
component_background_draw();
component_loader_draw(progress);
rdpq_detach_show();
}
}
static flashcart_save_type_t convert_save_type (db_savetype_t save_type) {
switch (save_type) {
case DB_SAVE_TYPE_EEPROM_4K:
return FLASHCART_SAVE_TYPE_EEPROM_4K;
@ -153,7 +228,6 @@ static flashcart_save_type_t convert_save_type (uint8_t save_type) {
}
}
static void load (menu_t *menu) {
menu->next_mode = MENU_MODE_BOOT;
@ -161,7 +235,7 @@ static void load (menu_t *menu) {
path_push(path, menu->browser.list[menu->browser.selected].name);
bool byte_swap = (rom_header.config_flags == ROM_MID_BIG_ENDIAN);
menu->flashcart_error = flashcart_load_rom(path_get(path), byte_swap);
menu->flashcart_error = flashcart_load_rom(path_get(path), byte_swap, draw_progress);
if (menu->flashcart_error != FLASHCART_OK) {
menu->next_mode = MENU_MODE_FAULT;
path_free(path);
@ -181,60 +255,12 @@ static void load (menu_t *menu) {
path_free(path);
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
menu->boot_params->reset_type = BOOT_RESET_TYPE_NMI;
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
menu->boot_params->detect_cic_seed = true;
}
static void process (menu_t *menu) {
if (menu->actions.enter) {
load_pending = true;
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
}
}
static void draw (menu_t *menu, surface_t *d) {
layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_attach(d, NULL);
// Background
component_background_draw(menu->components.background);
if (load_pending) {
fragment_loader(d);
} else {
// Layout
fragment_borders(d);
// Text start
fragment_text_start(text_color);
// Main screen
text_y += fragment_textf(text_x, text_y, "Title: %.20s", rom_header.title);
text_y += fragment_textf(text_x, text_y, "Save type: %s", format_rom_save_type(rom_db_match_save_type(rom_header)));
text_y += fragment_textf(text_x, text_y, "Media type: %c - %s", rom_header.metadata.media_type, format_rom_media_type(rom_header.metadata.media_type));
text_y += fragment_textf(text_x, text_y, "Unique ID: %.2s", (char*)&(rom_header.metadata.unique_identifier));
text_y += fragment_textf(text_x, text_y, "Destination market: %c - %s", rom_header.metadata.destination_market, format_rom_destination_market(rom_header.metadata.destination_market));
text_y += fragment_textf(text_x, text_y, "Version: %hhu", rom_header.version);
text_y += fragment_textf(text_x, text_y, "Checksum: 0x%016llX", rom_header.checksum);
text_y += fragment_textf(text_x, text_y, "ROM endian: %s", format_rom_endian(rom_header.config_flags));
text_y += fragment_textf(text_x, text_y, "Expansion PAK: %s", format_rom_memory_type(rom_db_match_expansion_pak(rom_header)));
// Actions bar
text_y = layout->actions_y + layout->offset_text_y;
text_y += fragment_textf(text_x, text_y, "A: Load and run ROM");
text_y += fragment_textf(text_x, text_y, "B: Exit");
}
rdpq_detach_show();
static void deinit (void) {
component_boxart_free(boxart);
}
@ -246,14 +272,22 @@ void view_load_init (menu_t *menu) {
rom_header = file_read_rom_header(path_get(path));
boxart = component_boxart_init(rom_header.metadata.unique_identifier);
path_free(path);
}
void view_load_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
if (load_pending) {
load_pending = false;
load(menu);
}
if (menu->next_mode != MENU_MODE_LOAD) {
deinit();
}
}

View File

@ -1,8 +1,4 @@
#include <libdragon.h>
#include "../components/components.h"
#include "../mp3_player.h"
#include "fragments/fragments.h"
#include "views.h"
@ -13,20 +9,18 @@
static int unmute_counter;
static void format_name (char *buffer, char *name) {
int cutoff_length = 57;
int name_length = strlen(name);
strcpy(buffer, " ");
if (name_length > cutoff_length) {
strncat(buffer, name, cutoff_length - 1);
strcat(buffer, "");
} else {
strcat(buffer, name);
static char *convert_error_message (mp3player_err_t err) {
switch (err) {
case MP3PLAYER_ERR_OUT_OF_MEM: return "MP3 player failed due to insufficient memory";
case MP3PLAYER_ERR_IO: return "I/O error during MP3 playback";
case MP3PLAYER_ERR_NO_FILE: return "No MP3 file is loaded";
case MP3PLAYER_ERR_INVALID_FILE: return "Invalid MP3 file";
default: return "Unknown MP3 player error";
}
}
static void format_elapsed_duration (char *buffer, float elapsed, float duration) {
strcpy(buffer, " ");
strcpy(buffer, "");
if (duration >= 3600) {
sprintf(buffer + strlen(buffer), "%02d:", (int) (elapsed) / 3600);
@ -43,6 +37,8 @@ static void format_elapsed_duration (char *buffer, float elapsed, float duration
static void process (menu_t *menu) {
mp3player_err_t err;
if (unmute_counter > 0) {
unmute_counter -= 1;
if (unmute_counter == 0) {
@ -50,90 +46,94 @@ static void process (menu_t *menu) {
}
}
if (mp3player_process() != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
err = mp3player_process();
if (err != MP3PLAYER_OK) {
menu_show_error(menu, convert_error_message(err));
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
} else if (menu->actions.enter) {
if (mp3player_toggle() != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
err = mp3player_toggle();
if (err != MP3PLAYER_OK) {
menu_show_error(menu, convert_error_message(err));
}
} else if (menu->actions.go_left || menu->actions.go_right) {
mp3player_mute(true);
unmute_counter = SEEK_UNMUTE_TIMEOUT;
int seconds = (menu->actions.go_left ? -SEEK_SECONDS : SEEK_SECONDS);
if (mp3player_seek(seconds) != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
err = mp3player_seek(seconds);
if (err != MP3PLAYER_OK) {
menu_show_error(menu, convert_error_message(err));
}
}
}
static void draw (menu_t *menu, surface_t *d) {
char buffer[64];
layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_attach(d, NULL);
// Background
component_background_draw(menu->components.background);
component_background_draw();
// Layout
fragment_borders(d);
component_layout_draw();
// Progressbar
fragment_progressbar(d, mp3player_get_progress());
component_seekbar_draw(mp3player_get_progress());
// Text start
fragment_text_start(text_color);
component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"MUSIC PLAYER\n"
"\n"
"%s",
menu->browser.list[menu->browser.selected].name
);
// Main screen
text_y += fragment_textf(text_x, text_y, "Now playing:");
format_name(buffer, menu->browser.list[menu->browser.selected].name);
text_y += fragment_textf(text_x, text_y, buffer);
char formatted_track_elapsed_length[64];
text_y += layout->line_height;
text_y += fragment_textf(text_x, text_y, "Track elapsed / length:");
format_elapsed_duration(buffer, mp3player_get_duration() * mp3player_get_progress(), mp3player_get_duration());
text_y += fragment_textf(text_x, text_y, buffer);
format_elapsed_duration(
formatted_track_elapsed_length,
mp3player_get_duration() * mp3player_get_progress(),
mp3player_get_duration()
);
text_y += layout->line_height;
text_y += fragment_textf(text_x, text_y, "Average bitrate:");
text_y += fragment_textf(text_x, text_y, " %.0f kbps", mp3player_get_bitrate() / 1000);
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n"
"\n"
"\n"
" Track elapsed / length:\n"
" %s\n"
"\n"
" Average bitrate:\n"
" %.0f kbps\n"
"\n"
" Samplerate:\n"
" %d Hz",
formatted_track_elapsed_length,
mp3player_get_bitrate() / 1000,
mp3player_get_samplerate()
);
text_y += layout->line_height;
text_y += fragment_textf(text_x, text_y, "Samplerate:");
text_y += fragment_textf(text_x, text_y, " %d Hz", mp3player_get_samplerate());
// Actions bar
text_y = layout->actions_y + layout->offset_text_y;
if (mp3player_is_playing()) {
fragment_textf(text_x, text_y, "A: Pause");
} else if (mp3player_is_finished()) {
fragment_textf(text_x, text_y, "A: Play again");
} else {
fragment_textf(text_x, text_y, "A: Play");
}
text_y += layout->line_height;
fragment_textf(text_x, text_y, "B: Exit | Left / Right: Rewind / Fast forward");
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"A: %s\n"
"B: Exit | Left / Right: Rewind / Fast forward",
mp3player_is_playing() ? "Pause" : mp3player_is_finished() ? "Play again" : "Play"
);
rdpq_detach_show();
}
static void deinit (void) {
mp3player_deinit();
}
void view_music_player_init (menu_t *menu) {
mp3player_err_t error;
mp3player_err_t err;
unmute_counter = 0;
error = mp3player_init();
if (error != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
err = mp3player_init();
if (err != MP3PLAYER_OK) {
menu_show_error(menu, convert_error_message(err));
mp3player_deinit();
return;
}
@ -141,9 +141,9 @@ void view_music_player_init (menu_t *menu) {
path_t *path = path_clone(menu->browser.directory);
path_push(path, menu->browser.list[menu->browser.selected].name);
error = mp3player_load(path_get(path));
if (error != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
err = mp3player_load(path_get(path));
if (err != MP3PLAYER_OK) {
menu_show_error(menu, convert_error_message(err));
mp3player_deinit();
} else {
mp3player_mute(false);
@ -155,8 +155,10 @@ void view_music_player_init (menu_t *menu) {
void view_music_player_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
if (menu->next_mode != MENU_MODE_MUSIC_PLAYER) {
mp3player_deinit();
deinit();
}
}

View File

@ -1,15 +1,7 @@
#include <libdragon.h>
#include "../components/components.h"
#include "utils/fs.h"
#include "views.h"
#define CACHE_DIRECTORY "sd:/menu/cache"
#define BACKGROUND_CACHE "sd:/menu/cache/background.data"
static void process (menu_t *menu) {
menu->next_mode = MENU_MODE_BROWSER;
}
@ -19,19 +11,13 @@ static void draw (menu_t *menu, surface_t *d) {
rdpq_detach_show();
}
static void load (menu_t *menu) {
menu->components.background = component_background_create(BACKGROUND_CACHE);
}
void view_startup_init (menu_t *menu) {
directory_create(CACHE_DIRECTORY);
// Nothing to initialize (yet)
}
void view_startup_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
load(menu);
}

View File

@ -1,28 +1,30 @@
#include <time.h>
#include <libdragon.h>
#include "../components/components.h"
#include "fragments/fragments.h"
#include "views.h"
char *accessory_type_s( int accessory )
{
switch( accessory )
{
static int controllers;
static int accessory[4];
static char *format_accessory (int controller) {
switch (accessory[controller]) {
case ACCESSORY_RUMBLEPAK:
return "[RumblePak]";
return "[Rumble Pak is inserted]";
case ACCESSORY_MEMPAK:
return "[ControllerPak]";
return "[Controller Pak is inserted]";
case ACCESSORY_VRU:
return "[VRU]";
return "[VRU is inserted]";
case ACCESSORY_TRANSFERPAK:
return "[TransferPak]";
return "[Transfer Pak is inserted]";
case ACCESSORY_NONE:
return "";
default:
return "Unknown";
return "[unknown accessory inserted]";
}
}
static void process (menu_t *menu) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
@ -30,79 +32,58 @@ static void process (menu_t *menu) {
}
static void draw (menu_t *menu, surface_t *d) {
layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_attach(d, NULL);
// Background
component_background_draw(menu->components.background);
component_background_draw();
// Layout
fragment_borders(d);
component_layout_draw();
// Text start
fragment_text_start(text_color);
component_main_text_draw(
ALIGN_CENTER, VALIGN_TOP,
"N64 SYSTEM INFORMATION"
);
text_y += fragment_textf((d->width / 2) - 108, text_y, "N64 SYSTEM INFORMATION\n\n");
time_t current_time = time(NULL);
text_y += fragment_textf(text_x, text_y, "\n");
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n"
"Current date & time: %s"
"\n"
"Expansion PAK is %sinserted\n"
"\n"
"JoyPad 1 is %sconnected %s\n"
"JoyPad 2 is %sconnected %s\n"
"JoyPad 3 is %sconnected %s\n"
"JoyPad 4 is %sconnected %s\n",
current_time >= 0 ? ctime(&current_time) : "Unknown\n",
is_memory_expanded() ? "" : "not ",
(controllers & CONTROLLER_1_INSERTED) ? "" : "not ", format_accessory(0),
(controllers & CONTROLLER_2_INSERTED) ? "" : "not ", format_accessory(1),
(controllers & CONTROLLER_3_INSERTED) ? "" : "not ", format_accessory(2),
(controllers & CONTROLLER_4_INSERTED) ? "" : "not ", format_accessory(3)
);
time_t current_time = -1;
current_time = time( NULL );
if( current_time != -1 )
{
text_y += fragment_textf(text_x, text_y, "Current date & time: %s\n\n", ctime( &current_time ));
text_y += fragment_textf(text_x, text_y, "To change the time, use USB App...");
}
text_y += fragment_textf(text_x, text_y, "\n");
text_y += fragment_textf(text_x, text_y, "Expansion PAK is %sinserted\n", is_memory_expanded() ? "" : "not " );
text_y += fragment_textf(text_x, text_y, "\n");
int controllers = get_controllers_present();
text_y += fragment_textf(text_x, text_y, "JoyPad 1 is %sconnected\n", (controllers & CONTROLLER_1_INSERTED) ? "" : "not " );
text_y += fragment_textf(text_x, text_y, "JoyPad 2 is %sconnected\n", (controllers & CONTROLLER_2_INSERTED) ? "" : "not " );
text_y += fragment_textf(text_x, text_y, "JoyPad 3 is %sconnected\n", (controllers & CONTROLLER_3_INSERTED) ? "" : "not " );
text_y += fragment_textf(text_x, text_y, "JoyPad 4 is %sconnected\n", (controllers & CONTROLLER_4_INSERTED) ? "" : "not " );
text_y += fragment_textf(text_x, text_y, "\n");
struct controller_data output;
int accessories = get_accessories_present( &output );
text_y += fragment_textf(text_x, text_y, "JoyPad 1 Accessory Pak is %sinserted %s\n", (accessories & CONTROLLER_1_INSERTED) ? "" : "not ",
(accessories & CONTROLLER_1_INSERTED) ? accessory_type_s( identify_accessory( 0 ) ) : "" );
text_y += fragment_textf(text_x, text_y, "JoyPad 2 Accessory Pak is %sinserted %s\n", (accessories & CONTROLLER_2_INSERTED) ? "" : "not ",
(accessories & CONTROLLER_2_INSERTED) ? accessory_type_s( identify_accessory( 1 ) ) : "" );
text_y += fragment_textf(text_x, text_y, "JoyPad 3 Accessory Pak is %sinserted %s\n", (accessories & CONTROLLER_3_INSERTED) ? "" : "not ",
(accessories & CONTROLLER_3_INSERTED) ? accessory_type_s( identify_accessory( 2 ) ) : "" );
text_y += fragment_textf(text_x, text_y, "JoyPad 4 Accessory Pak is %sinserted %s\n", (accessories & CONTROLLER_4_INSERTED) ? "" : "not ",
(accessories & CONTROLLER_4_INSERTED) ? accessory_type_s( identify_accessory( 3 ) ) : "" );
// Actions bar
text_y = layout->actions_y + layout->offset_text_y;
text_y += fragment_textf(text_x, text_y, "B: Exit");
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"B: Exit"
);
rdpq_detach_show();
}
void view_system_info_init (menu_t *menu) {
// Nothing to initialize (yet)
void view_system_info_init (menu_t *menu) {
controllers = get_controllers_present();
for (int i = 0; i < 4; i++) {
accessory[i] = identify_accessory(i);
}
}
void view_system_info_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
}
}

View File

@ -8,10 +8,10 @@
#define VIEWS_H__
#include <surface.h>
#include "../components.h"
#include "../menu_state.h"
/**
* @addtogroup view
* @{
@ -47,6 +47,9 @@ void view_error_display (menu_t *menu, surface_t *display);
void view_fault_init (menu_t *menu);
void view_fault_display (menu_t *menu, surface_t *display);
void menu_show_error (menu_t *menu, char *error_message);
/** @} */ /* view */
#endif

View File

@ -1,33 +0,0 @@
#ifndef STR_UTILS_H__
#define STR_UTILS_H__
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
// e.g. if (str_endswith(cur_rom, ".z64", false) || str_endswith(cur_rom, ".n64", false))
static bool str_endswith(const char *str, const char *suffix, bool case_sensitive) {
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
if (str_len < suffix_len)
return false;
if (!case_sensitive) {
for (size_t i = 0; i < suffix_len; i++) {
if (tolower(str[str_len - suffix_len + i]) != tolower(suffix[i]))
return false;
}
} else {
for (size_t i = 0; i < suffix_len; i++) {
if (str[str_len - suffix_len + i] != suffix[i])
return false;
}
}
return true;
}
#endif