mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2024-11-25 03:56:54 +01:00
Merge branch 'main' into ed64-basic
This commit is contained in:
commit
1566fe9c48
20
.clang-format
Normal file
20
.clang-format
Normal file
@ -0,0 +1,20 @@
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBraces: Attach
|
||||
ColumnLimit: 120
|
||||
IndentWidth: 4
|
||||
SpaceBeforeParens: Custom
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterFunctionDeclarationName: true
|
||||
AfterFunctionDefinitionName: true
|
||||
UseTab: Never
|
@ -6,7 +6,7 @@
|
||||
"mounts": [
|
||||
"source=n64flashcartmenu-bashhistory,target=/commandhistory,type=volume"
|
||||
],
|
||||
"postCreateCommand": "git submodule update --init && cd ./libdragon && ./build.sh",
|
||||
"postCreateCommand": "git submodule update --init && cd ./libdragon && make clobber -j && make libdragon tools -j && make install tools-install -j",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
@ -24,4 +24,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -110,3 +110,4 @@ jobs:
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./output/docs
|
||||
cname: menu.summercart64.dev
|
||||
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -1,9 +1,17 @@
|
||||
# Ignore editor specific config folders/files
|
||||
/.vscode
|
||||
|
||||
# Ignore compilation result directories
|
||||
/build
|
||||
/filesystem
|
||||
/output
|
||||
|
||||
# Ignore generated files in the libdragon FS
|
||||
/filesystem/FiraMonoBold.font64
|
||||
|
||||
# Ignore external development tools
|
||||
/tools/*
|
||||
|
||||
# There should never be ROMs uploaded, but just incase, make sure they are excluded.
|
||||
# Ignore any N64 ROM
|
||||
**/*.n64
|
||||
**/*.v64
|
||||
**/*.z64
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,7 +1,7 @@
|
||||
[submodule "libdragon"]
|
||||
path = libdragon
|
||||
url = https://github.com/DragonMinded/libdragon
|
||||
branch = unstable
|
||||
branch = preview
|
||||
ignore = dirty
|
||||
[submodule "src/libs/libspng"]
|
||||
path = src/libs/libspng
|
||||
|
28
Makefile
28
Makefile
@ -8,10 +8,16 @@ FILESYSTEM_DIR = filesystem
|
||||
BUILD_DIR = build
|
||||
OUTPUT_DIR = output
|
||||
|
||||
FLAGS += -DMENU_VERSION=\"0.0.1.$(shell date +%Y-%m-%dT%H:%M:%SZ).ALPHA\"
|
||||
MENU_VERSION ?= "Rolling release"
|
||||
BUILD_TIMESTAMP = "$(shell TZ='UTC' date "+%Y-%m-%d %H:%M:%S %:z")"
|
||||
|
||||
include $(N64_INST)/include/n64.mk
|
||||
|
||||
N64_ROM_SAVETYPE = none
|
||||
N64_ROM_RTC = 1
|
||||
N64_ROM_REGIONFREE = 1
|
||||
N64_ROM_REGION = E
|
||||
|
||||
N64_CFLAGS += -iquote $(SOURCE_DIR) -iquote $(ASSETS_DIR) -I $(SOURCE_DIR)/libs -flto=auto $(FLAGS)
|
||||
|
||||
SRCS = \
|
||||
@ -58,6 +64,7 @@ SRCS = \
|
||||
menu/views/fault.c \
|
||||
menu/views/file_info.c \
|
||||
menu/views/image_viewer.c \
|
||||
menu/views/text_viewer.c \
|
||||
menu/views/load_disk.c \
|
||||
menu/views/load_emulator.c \
|
||||
menu/views/load_rom.c \
|
||||
@ -66,6 +73,7 @@ SRCS = \
|
||||
menu/views/system_info.c \
|
||||
menu/views/settings_editor.c \
|
||||
menu/views/rtc.c \
|
||||
menu/views/flashcart_info.c \
|
||||
utils/fs.c
|
||||
|
||||
FONTS = \
|
||||
@ -81,7 +89,7 @@ FILESYSTEM = \
|
||||
|
||||
$(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second
|
||||
$(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second
|
||||
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 2026-2026 --ellipsis 2026,1
|
||||
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-1FF -r 2026-2026 --ellipsis 2026,1
|
||||
|
||||
$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))
|
||||
|
||||
@ -91,6 +99,9 @@ $(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf
|
||||
|
||||
$(BUILD_DIR)/$(PROJECT_NAME).dfs: $(FILESYSTEM)
|
||||
|
||||
$(BUILD_DIR)/menu/views/credits.o: .FORCE
|
||||
$(BUILD_DIR)/menu/views/credits.o: FLAGS+=-DMENU_VERSION=\"$(MENU_VERSION)\" -DBUILD_TIMESTAMP=\"$(BUILD_TIMESTAMP)\"
|
||||
|
||||
$(BUILD_DIR)/$(PROJECT_NAME).elf: $(OBJS)
|
||||
|
||||
disassembly: $(BUILD_DIR)/$(PROJECT_NAME).elf
|
||||
@ -125,9 +136,18 @@ all: $(OUTPUT_DIR)/$(PROJECT_NAME).n64 64drive ed64 ed64-clone sc64
|
||||
.PHONY: all
|
||||
|
||||
clean:
|
||||
@rm -rf ./$(BUILD_DIR) ./$(FILESYSTEM_DIR) ./$(OUTPUT_DIR)
|
||||
@rm -f ./$(FILESYSTEM)
|
||||
@find ./$(FILESYSTEM_DIR) -type d -empty -delete
|
||||
@rm -rf ./$(BUILD_DIR) ./$(OUTPUT_DIR)
|
||||
.PHONY: clean
|
||||
|
||||
format:
|
||||
@find ./$(SOURCE_DIR) \
|
||||
-path \./$(SOURCE_DIR)/libs -prune \
|
||||
-o -iname *.c -print \
|
||||
-o -iname *.h -print \
|
||||
| xargs clang-format -i
|
||||
|
||||
run: $(OUTPUT_DIR)/$(PROJECT_NAME).n64
|
||||
ifeq ($(OS),Windows_NT)
|
||||
./localdeploy.bat
|
||||
@ -147,4 +167,6 @@ endif
|
||||
# test:
|
||||
# TODO: run tests
|
||||
|
||||
.FORCE:
|
||||
|
||||
-include $(DEPS)
|
||||
|
21
README.md
21
README.md
@ -3,6 +3,16 @@
|
||||
# N64 Flashcart Menu
|
||||
An open source menu for N64 flashcarts.
|
||||
|
||||
## Supported Flashcarts
|
||||
|
||||
### Fully supported
|
||||
* SummerCart64
|
||||
* 64Drive
|
||||
|
||||
### Work in Progress
|
||||
* ED64
|
||||
* ED64P
|
||||
|
||||
|
||||
## Current (notable) menu features
|
||||
* Fully Open Source.
|
||||
@ -33,6 +43,17 @@ An open source menu for N64 flashcarts.
|
||||
## Getting started
|
||||
Using your PC, insert the SD card and ensure it is formatted for compatibility (We recommend FAT32 in most instances).
|
||||
|
||||
### Save files
|
||||
By default, all save files (whether `FlashRam`, `SRAM` or `EEPROM`) use the `.sav` extension and match the filename of the ROM.
|
||||
|
||||
Each save file can be found in the `/saves` folder located in the same directory as the ROM and shares the same file name, apart from the extension.
|
||||
|
||||
If transfering a file from a different flashcart such as the ED64, it will be necessary to change the extension of the file to `sav`.
|
||||
|
||||
i.e. for `Glover (USA).eep` you would need to change the extension to `Glover (USA).sav`
|
||||
|
||||
**NOTE:** certain emulator saves or saves created for a different ROM version or region may be incompatible.
|
||||
|
||||
|
||||
### ROM Boxart
|
||||
To use boxart, you need to place png files of size 158x112 in the folder `/menu/boxart` on the SD card.
|
||||
|
0
filesystem/.gitkeep
Normal file
0
filesystem/.gitkeep
Normal file
@ -1 +1 @@
|
||||
Subproject commit 6323128d72fdf32dfaa134f40191ba72e5527076
|
||||
Subproject commit 8dd362b8d858ae60befe9d27bc55cf770b9b50af
|
@ -102,7 +102,7 @@ cic_type_t cic_detect (uint8_t *ipl3) {
|
||||
switch (cic_calculate_ipl3_checksum(ipl3, 0x3F)) {
|
||||
case 0x45CC73EE317AULL: return CIC_6101; // 6101
|
||||
case 0x44160EC5D9AFULL: return CIC_7102; // 7102
|
||||
case 0xA536C0F1D859ULL: return CIC_6102_7101; // 6102 / 7101
|
||||
case 0xA536C0F1D859ULL: return CIC_x102; // 6102 / 7101
|
||||
}
|
||||
switch (cic_calculate_ipl3_checksum(ipl3, 0x78)) {
|
||||
case 0x586FD4709867ULL: return CIC_x103; // 6103 / 7103
|
||||
@ -133,7 +133,7 @@ uint8_t cic_get_seed (cic_type_t cic_type) {
|
||||
case CIC_5167: return 0xDD;
|
||||
case CIC_6101: return 0x3F;
|
||||
case CIC_7102: return 0x3F;
|
||||
case CIC_6102_7101: return 0x3F;
|
||||
case CIC_x102: return 0x3F;
|
||||
case CIC_x103: return 0x78;
|
||||
case CIC_x105: return 0x91;
|
||||
case CIC_x106: return 0x85;
|
||||
|
@ -13,7 +13,7 @@ typedef enum {
|
||||
CIC_5167,
|
||||
CIC_6101,
|
||||
CIC_7102,
|
||||
CIC_6102_7101,
|
||||
CIC_x102,
|
||||
CIC_x103,
|
||||
CIC_x105,
|
||||
CIC_x106,
|
||||
|
@ -73,6 +73,8 @@ static flashcart_err_t d64_deinit (void) {
|
||||
static bool d64_has_feature (flashcart_features_t feature) {
|
||||
switch (feature) {
|
||||
case FLASHCART_FEATURE_64DD: return false;
|
||||
case FLASHCART_FEATURE_RTC: return true;
|
||||
case FLASHCART_FEATURE_USB: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@ -81,11 +83,11 @@ static flashcart_err_t d64_load_rom (char *rom_path, flashcart_progress_callback
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(rom_path), FA_READ) != FR_OK) {
|
||||
if (f_open(&fil, strip_fs_prefix(rom_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
fatfs_fix_file_size(&fil);
|
||||
|
||||
size_t rom_size = f_size(&fil);
|
||||
|
||||
@ -96,7 +98,7 @@ static flashcart_err_t d64_load_rom (char *rom_path, flashcart_progress_callback
|
||||
|
||||
size_t sdram_size = MiB(64);
|
||||
|
||||
size_t chunk_size = MiB(1);
|
||||
size_t chunk_size = KiB(128);
|
||||
for (int offset = 0; offset < sdram_size; offset += chunk_size) {
|
||||
size_t block_size = MIN(sdram_size - offset, chunk_size);
|
||||
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
|
||||
@ -123,11 +125,11 @@ static flashcart_err_t d64_load_file (char *file_path, uint32_t rom_offset, uint
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(file_path), FA_READ) != FR_OK) {
|
||||
if (f_open(&fil, strip_fs_prefix(file_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
fatfs_fix_file_size(&fil);
|
||||
|
||||
size_t file_size = f_size(&fil) - file_offset;
|
||||
|
||||
@ -162,7 +164,7 @@ static flashcart_err_t d64_load_save (char *save_path) {
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) {
|
||||
if (f_open(&fil, strip_fs_prefix(save_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
@ -249,7 +251,13 @@ static flashcart_err_t d64_set_save_type (flashcart_save_type_t save_type) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_err_t d64_set_save_writeback (uint32_t *sectors) {
|
||||
static flashcart_err_t d64_set_save_writeback (char *save_path) {
|
||||
uint32_t sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8)));
|
||||
|
||||
if (fatfs_get_file_sectors(save_path, sectors, ADDRESS_TYPE_MEM, SAVE_WRITEBACK_MAX_SECTORS)) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
if (d64_ll_write_save_writeback_lba_list(sectors)) {
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
@ -8,15 +8,13 @@
|
||||
#include "utils/utils.h"
|
||||
|
||||
#include "flashcart.h"
|
||||
#include "flashcart_utils.h"
|
||||
|
||||
#include "64drive/64drive.h"
|
||||
#include "sc64/sc64.h"
|
||||
#include "ed64/ed64.h"
|
||||
|
||||
|
||||
#define SAVE_WRITEBACK_MAX_SECTORS (256)
|
||||
|
||||
|
||||
static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = {
|
||||
0,
|
||||
512,
|
||||
@ -28,34 +26,44 @@ static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = {
|
||||
KiB(128),
|
||||
};
|
||||
|
||||
static uint32_t save_writeback_sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8)));
|
||||
|
||||
static flashcart_err_t dummy_init (void) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static void save_writeback_sectors_callback (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size) {
|
||||
for (uint32_t i = 0; i < cluster_size; i++) {
|
||||
uint32_t offset = file_sector + i;
|
||||
uint32_t sector = cluster_sector + i;
|
||||
|
||||
if ((offset > SAVE_WRITEBACK_MAX_SECTORS) || (offset > sector_count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
save_writeback_sectors[offset] = sector;
|
||||
static bool dummy_has_feature (flashcart_features_t feature) {
|
||||
switch (feature) {
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static flashcart_err_t dummy_load_rom (char *rom_path, flashcart_progress_callback_t *progress) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_err_t dummy_init (void) {
|
||||
static flashcart_err_t dummy_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_err_t dummy_load_save (char *save_path) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_err_t dummy_set_save_type (flashcart_save_type_t save_type) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_t *flashcart = &((flashcart_t) {
|
||||
.init = dummy_init,
|
||||
.deinit = NULL,
|
||||
.load_rom = NULL,
|
||||
.load_file = NULL,
|
||||
.load_save = NULL,
|
||||
.set_save_type = NULL,
|
||||
.has_feature = dummy_has_feature,
|
||||
.load_rom = dummy_load_rom,
|
||||
.load_file = dummy_load_file,
|
||||
.load_save = dummy_load_save,
|
||||
.load_64dd_ipl = NULL,
|
||||
.load_64dd_disk = NULL,
|
||||
.set_save_type = dummy_set_save_type,
|
||||
.set_save_writeback = NULL,
|
||||
});
|
||||
|
||||
@ -70,9 +78,9 @@ static flashcart_t *flashcart = &((flashcart_t) {
|
||||
char *flashcart_convert_error_message (flashcart_err_t err) {
|
||||
switch (err) {
|
||||
case FLASHCART_OK: return "No error";
|
||||
case FLASHCART_ERR_NOT_DETECTED: return "No flashcart hardware was detected";
|
||||
case FLASHCART_ERR_OUTDATED: return "Outdated flashcart firmware";
|
||||
case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization";
|
||||
case FLASHCART_ERR_BBFS: return "Error during iQue NAND initialization";
|
||||
case FLASHCART_ERR_ARGS: return "Invalid argument passed to flashcart function";
|
||||
case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart";
|
||||
case FLASHCART_ERR_INT: return "Internal flashcart error";
|
||||
@ -81,15 +89,20 @@ char *flashcart_convert_error_message (flashcart_err_t err) {
|
||||
}
|
||||
}
|
||||
|
||||
flashcart_err_t flashcart_init (void) {
|
||||
flashcart_err_t flashcart_init (const char **storage_prefix) {
|
||||
flashcart_err_t err;
|
||||
|
||||
bool sd_card_initialized = debug_init_sdfs("sd:/", -1);
|
||||
if (sys_bbplayer()) {
|
||||
// TODO: Add iQue callbacks
|
||||
*storage_prefix = "bbfs:/";
|
||||
if (bbfs_init()) {
|
||||
return FLASHCART_ERR_BBFS;
|
||||
}
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// NOTE: Some flashcarts doesn't have USB port, can't throw error here
|
||||
debug_init_usblog();
|
||||
#endif
|
||||
*storage_prefix = "sd:/";
|
||||
bool sd_card_initialized = debug_init_sdfs(*storage_prefix, -1);
|
||||
|
||||
switch (cart_type) {
|
||||
case CART_CI: // 64drive
|
||||
@ -97,7 +110,8 @@ flashcart_err_t flashcart_init (void) {
|
||||
break;
|
||||
|
||||
case CART_EDX: // Series X EverDrive-64
|
||||
// break; // FIXME: Commented out as required to fall through due to need of F/W 3.06 and UNFLoader (at minimum).
|
||||
break;
|
||||
|
||||
case CART_ED: // Original EverDrive-64
|
||||
flashcart = ed64_get_flashcart();
|
||||
break;
|
||||
@ -106,15 +120,22 @@ flashcart_err_t flashcart_init (void) {
|
||||
flashcart = sc64_get_flashcart();
|
||||
break;
|
||||
|
||||
default:
|
||||
return FLASHCART_ERR_NOT_DETECTED;
|
||||
default: // Probably emulator
|
||||
*storage_prefix = "rom:/";
|
||||
debug_init_isviewer();
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// NOTE: Some flashcarts doesn't have USB port, can't throw error here
|
||||
debug_init_usblog();
|
||||
#endif
|
||||
|
||||
if ((err = flashcart->init()) != FLASHCART_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!sd_card_initialized) {
|
||||
if ((cart_type != CART_NULL) && (!sd_card_initialized)) {
|
||||
return FLASHCART_ERR_SD_CARD;
|
||||
}
|
||||
|
||||
@ -187,19 +208,11 @@ flashcart_err_t flashcart_load_save (char *save_path, flashcart_save_type_t save
|
||||
return err;
|
||||
}
|
||||
|
||||
if (flashcart->set_save_writeback) {
|
||||
for (int i = 0; i < SAVE_WRITEBACK_MAX_SECTORS; i++) {
|
||||
save_writeback_sectors[i] = 0;
|
||||
}
|
||||
if (file_get_sectors(save_path, save_writeback_sectors_callback)) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
if ((err = flashcart->set_save_writeback(save_writeback_sectors)) != FLASHCART_OK) {
|
||||
return err;
|
||||
}
|
||||
if (!flashcart->set_save_writeback) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
return FLASHCART_OK;
|
||||
return flashcart->set_save_writeback(save_path);
|
||||
}
|
||||
|
||||
flashcart_err_t flashcart_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress) {
|
||||
|
@ -15,9 +15,9 @@
|
||||
/** @brief Flashcart error enumeration */
|
||||
typedef enum {
|
||||
FLASHCART_OK,
|
||||
FLASHCART_ERR_NOT_DETECTED,
|
||||
FLASHCART_ERR_OUTDATED,
|
||||
FLASHCART_ERR_SD_CARD,
|
||||
FLASHCART_ERR_BBFS,
|
||||
FLASHCART_ERR_ARGS,
|
||||
FLASHCART_ERR_LOAD,
|
||||
FLASHCART_ERR_INT,
|
||||
@ -27,6 +27,8 @@ typedef enum {
|
||||
/** @brief List of optional supported flashcart features */
|
||||
typedef enum {
|
||||
FLASHCART_FEATURE_64DD,
|
||||
FLASHCART_FEATURE_RTC,
|
||||
FLASHCART_FEATURE_USB,
|
||||
} flashcart_features_t;
|
||||
|
||||
/** @brief Flashcart save type enumeration */
|
||||
@ -73,12 +75,12 @@ typedef struct {
|
||||
/** @brief The flashcart set save type function */
|
||||
flashcart_err_t (*set_save_type) (flashcart_save_type_t save_type);
|
||||
/** @brief The flashcart set save writeback function */
|
||||
flashcart_err_t (*set_save_writeback) (uint32_t *sectors);
|
||||
flashcart_err_t (*set_save_writeback) (char *save_path);
|
||||
} flashcart_t;
|
||||
|
||||
|
||||
char *flashcart_convert_error_message (flashcart_err_t err);
|
||||
flashcart_err_t flashcart_init (void);
|
||||
flashcart_err_t flashcart_init (const char **storage_prefix);
|
||||
flashcart_err_t flashcart_deinit (void);
|
||||
bool flashcart_has_feature (flashcart_features_t feature);
|
||||
flashcart_err_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress);
|
||||
|
@ -5,13 +5,6 @@
|
||||
#include "utils/utils.h"
|
||||
|
||||
|
||||
void fix_file_size (FIL *fil) {
|
||||
// HACK: Align file size to the SD sector size to prevent FatFs from doing partial sector load.
|
||||
// We are relying on direct transfer from SD to SDRAM without CPU intervention.
|
||||
// Sending some extra bytes isn't an issue here.
|
||||
fil->obj.objsize = ALIGN(f_size(fil), FS_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
void pi_dma_read_data (void *src, void *dst, size_t length) {
|
||||
data_cache_hit_writeback_invalidate(dst, length);
|
||||
dma_read_async(dst, (uint32_t) (src), length);
|
||||
@ -27,3 +20,68 @@ void pi_dma_write_data (void *src, void *dst, size_t length) {
|
||||
dma_write_raw_async(src, (uint32_t) (dst), length);
|
||||
dma_wait();
|
||||
}
|
||||
|
||||
void fatfs_fix_file_size (FIL *fil) {
|
||||
// HACK: Align file size to the SD sector size to prevent FatFs from doing partial sector load.
|
||||
// We are relying on direct transfer from SD to SDRAM without CPU intervention.
|
||||
// Sending some extra bytes isn't an issue here.
|
||||
fil->obj.objsize = ALIGN(f_size(fil), FS_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
bool fatfs_get_file_sectors (char *path, uint32_t *address, address_type_t type, uint32_t max_sectors) {
|
||||
FATFS *fs;
|
||||
FIL fil;
|
||||
bool error = false;
|
||||
|
||||
if (f_open(&fil, strip_fs_prefix(path), FA_READ) != FR_OK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fatfs_fix_file_size(&fil);
|
||||
|
||||
fs = fil.obj.fs;
|
||||
|
||||
uint32_t sector_count = MIN(f_size(&fil) / FS_SECTOR_SIZE, max_sectors);
|
||||
|
||||
for (uint32_t file_sector = 0; file_sector < sector_count; file_sector += fs->csize) {
|
||||
if ((f_lseek(&fil, (file_sector * FS_SECTOR_SIZE) + (FS_SECTOR_SIZE / 2))) != FR_OK) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t cluster = fil.clust;
|
||||
|
||||
if (cluster >= fs->n_fatent) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t cluster_sector = (fs->database + ((LBA_t) (fs->csize) * (cluster - 2)));
|
||||
|
||||
for (uint32_t i = 0; i < fs->csize; i++) {
|
||||
uint32_t sector = (cluster_sector + i);
|
||||
|
||||
if ((file_sector + i) >= sector_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case ADDRESS_TYPE_MEM:
|
||||
*address = sector;
|
||||
break;
|
||||
|
||||
case ADDRESS_TYPE_PI:
|
||||
io_write((uint32_t) (address), sector);
|
||||
break;
|
||||
}
|
||||
|
||||
address++;
|
||||
}
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
@ -8,12 +8,26 @@
|
||||
#define FLASHCART_UTILS_H__
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <fatfs/ff.h>
|
||||
|
||||
|
||||
void fix_file_size (FIL *fil);
|
||||
#define SAVE_WRITEBACK_MAX_SECTORS (256)
|
||||
|
||||
|
||||
typedef enum {
|
||||
ADDRESS_TYPE_MEM,
|
||||
ADDRESS_TYPE_PI,
|
||||
} address_type_t;
|
||||
|
||||
|
||||
void pi_dma_read_data (void *src, void *dst, size_t length);
|
||||
void pi_dma_write_data (void *src, void *dst, size_t length);
|
||||
void fatfs_fix_file_size (FIL *fil);
|
||||
bool fatfs_get_file_sectors (char *path, uint32_t *address, address_type_t address_type, uint32_t max_sectors);
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -75,9 +75,6 @@ static const uint8_t vzone_to_pzone[DISK_TYPES][DISK_ZONES] = {
|
||||
static const uint8_t rom_zones[DISK_TYPES] = { 5, 7, 9, 11, 13, 15, 16 };
|
||||
|
||||
|
||||
static uint32_t disk_sectors_start_offset;
|
||||
|
||||
|
||||
static flashcart_err_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;
|
||||
@ -110,23 +107,6 @@ static flashcart_err_t load_to_flash (FIL *fil, void *address, size_t size, UINT
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static uint32_t disk_sectors_start (uint32_t offset) {
|
||||
disk_sectors_start_offset = offset;
|
||||
return (offset + (DISK_MAX_SECTORS * sizeof(uint32_t)));
|
||||
}
|
||||
|
||||
static void disk_sectors_callback (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size) {
|
||||
for (uint32_t i = 0; i < cluster_size; i++) {
|
||||
uint32_t offset = file_sector + i;
|
||||
uint32_t sector = cluster_sector + i;
|
||||
|
||||
if ((offset > DISK_MAX_SECTORS) || (offset > sector_count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
io_write(ROM_ADDRESS + disk_sectors_start_offset + (offset * sizeof(uint32_t)), sector);
|
||||
}
|
||||
}
|
||||
|
||||
static bool disk_zone_track_is_bad (uint8_t zone, uint8_t track, flashcart_disk_parameters_t *disk_parameters) {
|
||||
for (int i = 0; i < DISK_BAD_TRACKS_PER_ZONE; i++) {
|
||||
@ -153,7 +133,7 @@ static void disk_set_thb_mapping (uint32_t offset, uint16_t track, uint8_t head,
|
||||
io_write(ROM_ADDRESS + offset + (index * sizeof(uint32_t)), mapping);
|
||||
}
|
||||
|
||||
static uint32_t disk_load_thb_table (uint32_t offset, flashcart_disk_parameters_t *disk_parameters) {
|
||||
static void disk_load_thb_table (flashcart_disk_parameters_t *disk_parameters, uint32_t *thb_table_offset, uint32_t *current_offset) {
|
||||
int file_offset = 0;
|
||||
|
||||
uint16_t lba = 0;
|
||||
@ -175,15 +155,15 @@ static uint32_t disk_load_thb_table (uint32_t offset, flashcart_disk_parameters_
|
||||
uint16_t track = track_offset + zone_track;
|
||||
|
||||
if (disk_zone_track_is_bad(pzone, zone_track, disk_parameters)) {
|
||||
disk_set_thb_mapping(offset, track, head, 0, false, false, 0);
|
||||
disk_set_thb_mapping(offset, track, head, 1, false, false, 0);
|
||||
disk_set_thb_mapping(*current_offset, track, head, 0, false, false, 0);
|
||||
disk_set_thb_mapping(*current_offset, track, head, 1, false, false, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint8_t block = 0; block < DISK_BLOCKS; block += 1) {
|
||||
bool valid = !(disk_system_lba_is_bad(lba, disk_parameters));
|
||||
bool writable = (vzone >= rom_zones[disk_parameters->disk_type]);
|
||||
disk_set_thb_mapping(offset, track, head, (starting_block ^ block), valid, writable, file_offset);
|
||||
disk_set_thb_mapping(*current_offset, track, head, (starting_block ^ block), valid, writable, file_offset);
|
||||
file_offset += (sector_length * DISK_SECTORS_PER_BLOCK);
|
||||
lba += 1;
|
||||
}
|
||||
@ -192,7 +172,19 @@ static uint32_t disk_load_thb_table (uint32_t offset, flashcart_disk_parameters_
|
||||
}
|
||||
}
|
||||
|
||||
return (offset + (DISK_TRACKS * DISK_HEADS * DISK_BLOCKS * sizeof(uint32_t)));
|
||||
*thb_table_offset = *current_offset;
|
||||
*current_offset += (DISK_TRACKS * DISK_HEADS * DISK_BLOCKS * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
static bool disk_load_sector_table (char *path, uint32_t *sector_table_offset, uint32_t *current_offset) {
|
||||
if (fatfs_get_file_sectors(path, (uint32_t *) (ROM_ADDRESS + *current_offset), ADDRESS_TYPE_PI, DISK_MAX_SECTORS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
*sector_table_offset = *current_offset;
|
||||
*current_offset += (DISK_MAX_SECTORS * sizeof(uint32_t));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -260,6 +252,8 @@ static flashcart_err_t sc64_deinit (void) {
|
||||
static bool sc64_has_feature (flashcart_features_t feature) {
|
||||
switch (feature) {
|
||||
case FLASHCART_FEATURE_64DD: return true;
|
||||
case FLASHCART_FEATURE_RTC: return true;
|
||||
case FLASHCART_FEATURE_USB: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@ -268,11 +262,11 @@ static flashcart_err_t sc64_load_rom (char *rom_path, flashcart_progress_callbac
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(rom_path), FA_READ) != FR_OK) {
|
||||
if (f_open(&fil, strip_fs_prefix(rom_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
fatfs_fix_file_size(&fil);
|
||||
|
||||
size_t rom_size = f_size(&fil);
|
||||
|
||||
@ -288,7 +282,7 @@ static flashcart_err_t sc64_load_rom (char *rom_path, flashcart_progress_callbac
|
||||
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;
|
||||
|
||||
size_t chunk_size = MiB(1);
|
||||
size_t chunk_size = KiB(128);
|
||||
for (int offset = 0; offset < sdram_size; offset += chunk_size) {
|
||||
size_t block_size = MIN(sdram_size - offset, chunk_size);
|
||||
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
|
||||
@ -349,11 +343,11 @@ static flashcart_err_t sc64_load_file (char *file_path, uint32_t rom_offset, uin
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(file_path), FA_READ) != FR_OK) {
|
||||
if (f_open(&fil, strip_fs_prefix(file_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
fatfs_fix_file_size(&fil);
|
||||
|
||||
size_t file_size = f_size(&fil) - file_offset;
|
||||
|
||||
@ -411,7 +405,7 @@ static flashcart_err_t sc64_load_save (char *save_path) {
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) {
|
||||
if (f_open(&fil, strip_fs_prefix(save_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
@ -437,11 +431,11 @@ static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_ca
|
||||
FIL fil;
|
||||
UINT br;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(ipl_path), FA_READ) != FR_OK) {
|
||||
if (f_open(&fil, strip_fs_prefix(ipl_path), FA_READ) != FR_OK) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
fix_file_size(&fil);
|
||||
fatfs_fix_file_size(&fil);
|
||||
|
||||
size_t ipl_size = f_size(&fil);
|
||||
|
||||
@ -450,7 +444,7 @@ static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_ca
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
size_t chunk_size = KiB(256);
|
||||
size_t chunk_size = KiB(128);
|
||||
for (int offset = 0; offset < ipl_size; offset += chunk_size) {
|
||||
size_t block_size = MIN(ipl_size - offset, chunk_size);
|
||||
if (f_read(&fil, (void *) (IPL_ADDRESS + offset), block_size, &br) != FR_OK) {
|
||||
@ -474,19 +468,17 @@ static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_ca
|
||||
}
|
||||
|
||||
static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters) {
|
||||
sc64_disk_mapping_t mapping = { .count = 0 };
|
||||
uint32_t next_mapping_offset = DISK_MAPPING_ROM_OFFSET;
|
||||
sc64_disk_mapping_t mapping;
|
||||
uint32_t mapping_offset = DISK_MAPPING_ROM_OFFSET;
|
||||
sc64_drive_type_t drive_type = (disk_parameters->development_drive ? DRIVE_TYPE_DEVELOPMENT : DRIVE_TYPE_RETAIL);
|
||||
|
||||
// TODO: Support loading multiple disks
|
||||
// LOOP START
|
||||
mapping.disks[mapping.count].thb_table = next_mapping_offset;
|
||||
mapping.disks[mapping.count].sector_table = disk_load_thb_table(mapping.disks[mapping.count].thb_table, disk_parameters);
|
||||
next_mapping_offset = disk_sectors_start(mapping.disks[mapping.count].sector_table);
|
||||
if (file_get_sectors(disk_path, disk_sectors_callback)) {
|
||||
for (mapping.count = 0; mapping.count < 1; mapping.count++) {
|
||||
disk_load_thb_table(disk_parameters++, &mapping.disks[mapping.count].thb_table, &mapping_offset);
|
||||
if (disk_load_sector_table(disk_path++, &mapping.disks[mapping.count].sector_table, &mapping_offset)) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
mapping.count += 1;
|
||||
// LOOP END
|
||||
}
|
||||
|
||||
if (mapping.count == 0) {
|
||||
return FLASHCART_ERR_ARGS;
|
||||
@ -496,8 +488,6 @@ static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_para
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
||||
sc64_drive_type_t drive_type = disk_parameters->development_drive ? DRIVE_TYPE_DEVELOPMENT : DRIVE_TYPE_RETAIL;
|
||||
|
||||
const struct {
|
||||
sc64_cfg_id_t id;
|
||||
uint32_t value;
|
||||
@ -557,7 +547,13 @@ static flashcart_err_t sc64_set_save_type (flashcart_save_type_t save_type) {
|
||||
return FLASHCART_OK;
|
||||
}
|
||||
|
||||
static flashcart_err_t sc64_set_save_writeback (uint32_t *sectors) {
|
||||
static flashcart_err_t sc64_set_save_writeback (char *save_path) {
|
||||
uint32_t sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8)));
|
||||
|
||||
if (fatfs_get_file_sectors(save_path, sectors, ADDRESS_TYPE_MEM, SAVE_WRITEBACK_MAX_SECTORS)) {
|
||||
return FLASHCART_ERR_LOAD;
|
||||
}
|
||||
|
||||
pi_dma_write_data(sectors, SC64_BUFFERS->BUFFER, 1024);
|
||||
|
||||
if (sc64_ll_writeback_enable(SC64_BUFFERS->BUFFER) != SC64_OK) {
|
||||
|
2
src/libs/libspng
vendored
2
src/libs/libspng
vendored
@ -1 +1 @@
|
||||
Subproject commit e5c1fc470fceaca08b8c30dc40768c28b82b9e12
|
||||
Subproject commit adc94393dbeddf9e027d1b2dfff7c1bab975224e
|
2
src/libs/miniz
vendored
2
src/libs/miniz
vendored
@ -1 +1 @@
|
||||
Subproject commit 18795fa61e590521381ba9e1fa4a4ab362b095f6
|
||||
Subproject commit 16413c213de38e703d883006193734e8b1178d5d
|
@ -19,10 +19,9 @@
|
||||
|
||||
|
||||
static bool is_64dd_connected (void) {
|
||||
return (
|
||||
((io_read(0x05000540) & 0x0000FFFF) == 0x0000) ||
|
||||
(io_read(0x06001010) == 0x2129FFF8)
|
||||
);
|
||||
bool is_64dd_io_present = ((io_read(0x05000540) & 0x0000FFFF) == 0x0000);
|
||||
bool is_64dd_ipl_present = (io_read(0x06001010) == 0x2129FFF8);
|
||||
return (is_64dd_io_present || is_64dd_ipl_present);
|
||||
}
|
||||
|
||||
static bool create_saves_subdirectory (path_t *path) {
|
||||
@ -112,7 +111,7 @@ cart_load_err_t cart_load_64dd_ipl_and_disk (menu_t *menu, flashcart_progress_ca
|
||||
return CART_LOAD_ERR_EXP_PAK_NOT_FOUND;
|
||||
}
|
||||
|
||||
path_t *path = path_init("sd:/", DDIPL_LOCATION);
|
||||
path_t *path = path_init(menu->storage_prefix, DDIPL_LOCATION);
|
||||
flashcart_disk_parameters_t disk_parameters;
|
||||
|
||||
disk_parameters.development_drive = (menu->load.disk_info.region == DISK_REGION_DEVELOPMENT);
|
||||
@ -154,7 +153,7 @@ cart_load_err_t cart_load_64dd_ipl_and_disk (menu_t *menu, flashcart_progress_ca
|
||||
}
|
||||
|
||||
cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type, flashcart_progress_callback_t progress) {
|
||||
path_t *path = path_init("sd:/", EMU_LOCATION);
|
||||
path_t *path = path_init(menu->storage_prefix, EMU_LOCATION);
|
||||
|
||||
flashcart_save_type_t save_type = FLASHCART_SAVE_TYPE_NONE;
|
||||
uint32_t emulated_rom_offset = 0x200000;
|
||||
|
@ -24,7 +24,7 @@ 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_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, ...);
|
||||
@ -45,7 +45,8 @@ typedef struct component_context_menu {
|
||||
struct component_context_menu *submenu;
|
||||
struct {
|
||||
const char *text;
|
||||
void (*action) (menu_t *menu);
|
||||
void (*action) (menu_t *menu, void *arg);
|
||||
void *arg;
|
||||
struct component_context_menu *submenu;
|
||||
} list[];
|
||||
} component_context_menu_t;
|
||||
@ -63,7 +64,7 @@ typedef struct {
|
||||
surface_t *image;
|
||||
} component_boxart_t;
|
||||
|
||||
component_boxart_t *component_boxart_init (char *game_code);
|
||||
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code);
|
||||
void component_boxart_free (component_boxart_t *b);
|
||||
void component_boxart_draw (component_boxart_t *b);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include <fatfs/ff.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../components.h"
|
||||
@ -31,22 +31,21 @@ static void load_from_cache (component_background_t *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
FIL fil;
|
||||
UINT bytes_read;
|
||||
FILE *f;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(c->cache_location), FA_READ) != FR_OK) {
|
||||
if ((f = fopen(c->cache_location, "rb")) == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache_metadata_t cache_metadata;
|
||||
|
||||
if ((f_read(&fil, &cache_metadata, sizeof(cache_metadata), &bytes_read) != FR_OK) || (bytes_read != sizeof(cache_metadata))) {
|
||||
f_close(&fil);
|
||||
if (fread(&cache_metadata, sizeof(cache_metadata), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cache_metadata.magic != CACHE_METADATA_MAGIC || cache_metadata.width > DISPLAY_WIDTH || cache_metadata.height > DISPLAY_HEIGHT) {
|
||||
f_close(&fil);
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -57,17 +56,17 @@ static void load_from_cache (component_background_t *c) {
|
||||
surface_free(c->image);
|
||||
free(c->image);
|
||||
c->image = NULL;
|
||||
f_close(&fil);
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((f_read(&fil, c->image->buffer, cache_metadata.size, &bytes_read) != FR_OK) || (bytes_read != cache_metadata.size)) {
|
||||
if (fread(c->image->buffer, cache_metadata.size, 1, f) != 1) {
|
||||
surface_free(c->image);
|
||||
free(c->image);
|
||||
c->image = NULL;
|
||||
}
|
||||
|
||||
f_close(&fil);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static void save_to_cache (component_background_t *c) {
|
||||
@ -75,9 +74,9 @@ static void save_to_cache (component_background_t *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
FIL fil;
|
||||
UINT bytes_written;
|
||||
if (f_open(&fil, strip_sd_prefix(c->cache_location), FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) {
|
||||
FILE *f;
|
||||
|
||||
if ((f = fopen(c->cache_location, "wb")) == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -88,10 +87,10 @@ static void save_to_cache (component_background_t *c) {
|
||||
.size = (c->image->height * c->image->stride),
|
||||
};
|
||||
|
||||
f_write(&fil, &cache_metadata, sizeof(cache_metadata), &bytes_written);
|
||||
f_write(&fil, c->image->buffer, cache_metadata.size, &bytes_written);
|
||||
fwrite(&cache_metadata, sizeof(cache_metadata), 1, f);
|
||||
fwrite(c->image->buffer, cache_metadata.size, 1, f);
|
||||
|
||||
f_close(&fil);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static void prepare_background (component_background_t *c) {
|
||||
@ -166,7 +165,7 @@ static void display_list_free (void *arg) {
|
||||
void component_background_init (char *cache_location) {
|
||||
if (!background) {
|
||||
background = calloc(1, sizeof(component_background_t));
|
||||
background->cache_location = cache_location;
|
||||
background->cache_location = strdup(cache_location);
|
||||
load_from_cache(background);
|
||||
prepare_background(background);
|
||||
}
|
||||
@ -183,6 +182,9 @@ void component_background_free (void) {
|
||||
rdpq_call_deferred(display_list_free, background->image_display_list);
|
||||
background->image_display_list = NULL;
|
||||
}
|
||||
if (background->cache_location) {
|
||||
free(background->cache_location);
|
||||
}
|
||||
free(background);
|
||||
background = NULL;
|
||||
}
|
||||
|
@ -6,9 +6,8 @@
|
||||
#include "constants.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
#ifndef BOXART_DIRECTORY
|
||||
#define BOXART_DIRECTORY "sd:/menu/boxart"
|
||||
#endif
|
||||
|
||||
#define BOXART_DIRECTORY "menu/boxart"
|
||||
|
||||
|
||||
static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
|
||||
@ -18,25 +17,38 @@ static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void
|
||||
}
|
||||
|
||||
|
||||
component_boxart_t *component_boxart_init (char *game_code) {
|
||||
component_boxart_t *b = calloc(1, sizeof(component_boxart_t));
|
||||
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code) {
|
||||
component_boxart_t *b;
|
||||
char file_name[8];
|
||||
|
||||
if (b) {
|
||||
b->loading = true;
|
||||
char *path = alloca(strlen(BOXART_DIRECTORY) + 1 + 7 + 1);
|
||||
|
||||
// TODO: This is bad, we should only check for 3 letter codes
|
||||
sprintf(path, "%s/%.3s.png", BOXART_DIRECTORY, game_code);
|
||||
if (png_decoder_start(path, BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) != PNG_OK) {
|
||||
sprintf(path, "%s/%.2s.png", BOXART_DIRECTORY, &game_code[1]);
|
||||
if (png_decoder_start(path, BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) != PNG_OK) {
|
||||
free(b);
|
||||
b = NULL;
|
||||
}
|
||||
}
|
||||
if ((b = calloc(1, sizeof(component_boxart_t))) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return b;
|
||||
b->loading = true;
|
||||
|
||||
path_t *path = path_init(storage_prefix, BOXART_DIRECTORY);
|
||||
|
||||
sprintf(file_name, "%.3s.png", game_code);
|
||||
path_push(path, file_name);
|
||||
if (png_decoder_start(path_get(path), BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) == PNG_OK) {
|
||||
path_free(path);
|
||||
return b;
|
||||
}
|
||||
path_pop(path);
|
||||
|
||||
// TODO: This is bad, we should only check for 3 letter codes
|
||||
sprintf(file_name, "%.2s.png", game_code + 1);
|
||||
path_push(path, file_name);
|
||||
if (png_decoder_start(path_get(path), BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) == PNG_OK) {
|
||||
path_free(path);
|
||||
return b;
|
||||
}
|
||||
|
||||
path_free(path);
|
||||
free(b);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void component_boxart_free (component_boxart_t *b) {
|
||||
|
@ -80,12 +80,12 @@ void component_scrollbar_draw (int x, int y, int width, int height, int position
|
||||
}
|
||||
}
|
||||
|
||||
void component_file_list_scrollbar_draw (int position, int items, int visible_items) {
|
||||
void component_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,
|
||||
LIST_SCROLLBAR_X,
|
||||
LIST_SCROLLBAR_Y,
|
||||
LIST_SCROLLBAR_WIDTH,
|
||||
LIST_SCROLLBAR_HEIGHT,
|
||||
position,
|
||||
items,
|
||||
visible_items
|
||||
@ -121,6 +121,10 @@ void component_messagebox_draw (char *fmt, ...) {
|
||||
.wrap = WRAP_WORD
|
||||
}, FNT_DEFAULT, formatted, ¶graph_nbytes);
|
||||
|
||||
if (formatted != buffer) {
|
||||
free(formatted);
|
||||
}
|
||||
|
||||
component_dialog_draw(
|
||||
paragraph->bbox.x1 - paragraph->bbox.x0 + MESSAGEBOX_MARGIN,
|
||||
paragraph->bbox.y1 - paragraph->bbox.y0 + MESSAGEBOX_MARGIN
|
||||
@ -154,6 +158,10 @@ void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *f
|
||||
formatted,
|
||||
nbytes
|
||||
);
|
||||
|
||||
if (formatted != buffer) {
|
||||
free(formatted);
|
||||
}
|
||||
}
|
||||
|
||||
void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) {
|
||||
@ -179,4 +187,8 @@ void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign,
|
||||
formatted,
|
||||
nbytes
|
||||
);
|
||||
|
||||
if (formatted != buffer) {
|
||||
free(formatted);
|
||||
}
|
||||
}
|
||||
|
@ -77,19 +77,19 @@
|
||||
#define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24)
|
||||
|
||||
/** @brief The scroll bar width. */
|
||||
#define FILE_LIST_SCROLLBAR_WIDTH (12)
|
||||
#define LIST_SCROLLBAR_WIDTH (12)
|
||||
/** @brief The scroll bar height. */
|
||||
#define FILE_LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT)
|
||||
#define LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT)
|
||||
/** @brief The scroll bar position on the X axis. */
|
||||
#define FILE_LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - FILE_LIST_SCROLLBAR_WIDTH)
|
||||
#define LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - LIST_SCROLLBAR_WIDTH)
|
||||
/** @brief The scroll bar position on the Y axis. */
|
||||
#define FILE_LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0)
|
||||
#define LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0)
|
||||
|
||||
/** @brief The maximum amount of file list entries. */
|
||||
#define FILE_LIST_ENTRIES (20)
|
||||
#define LIST_ENTRIES (20)
|
||||
/** @brief The maximum width available for a file list entry. */
|
||||
#define FILE_LIST_MAX_WIDTH (480)
|
||||
#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - FILE_LIST_SCROLLBAR_WIDTH)
|
||||
#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - LIST_SCROLLBAR_WIDTH)
|
||||
#define FILE_LIST_HIGHLIGHT_X (VISIBLE_AREA_X0)
|
||||
|
||||
/** @brief The default background colour. */
|
||||
|
@ -48,7 +48,7 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm)
|
||||
cm->submenu->selected = 0;
|
||||
cm->submenu->parent = cm;
|
||||
} else if (cm->list[cm->selected].action) {
|
||||
cm->list[cm->selected].action(menu);
|
||||
cm->list[cm->selected].action(menu, cm->list[cm->selected].arg);
|
||||
top->hide_pending = true;
|
||||
}
|
||||
} else if (menu->actions.go_up) {
|
||||
|
@ -8,15 +8,19 @@
|
||||
static const char *dir_prefix = "/";
|
||||
|
||||
|
||||
static int format_file_size (char *buffer, int size) {
|
||||
if (size < 8 * 1024) {
|
||||
return sprintf(buffer, "%d B", size);
|
||||
static int format_file_size (char *buffer, int64_t size) {
|
||||
if (size < 0) {
|
||||
return sprintf(buffer, "unknown");
|
||||
} else if (size == 0) {
|
||||
return sprintf(buffer, "empty");
|
||||
} else if (size < 8 * 1024) {
|
||||
return sprintf(buffer, "%lld B", size);
|
||||
} else if (size < 8 * 1024 * 1024) {
|
||||
return sprintf(buffer, "%d kB", size / 1024);
|
||||
return sprintf(buffer, "%lld kB", size / 1024);
|
||||
} else if (size < 1 * 1024 * 1024 * 1024) {
|
||||
return sprintf(buffer, "%d MB", size / 1024 / 1024);
|
||||
return sprintf(buffer, "%lld MB", size / 1024 / 1024);
|
||||
} else {
|
||||
return sprintf(buffer, "%d GB", size / 1024 / 1024 / 1024);
|
||||
return sprintf(buffer, "%lld GB", size / 1024 / 1024 / 1024);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,29 +28,29 @@ static int format_file_size (char *buffer, int size) {
|
||||
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;
|
||||
if (entries > LIST_ENTRIES && selected >= (LIST_ENTRIES / 2)) {
|
||||
starting_position = selected - (LIST_ENTRIES / 2);
|
||||
if (starting_position >= entries - LIST_ENTRIES) {
|
||||
starting_position = entries - LIST_ENTRIES;
|
||||
}
|
||||
}
|
||||
|
||||
component_file_list_scrollbar_draw(selected, entries, FILE_LIST_ENTRIES);
|
||||
component_list_scrollbar_draw(selected, entries, LIST_ENTRIES);
|
||||
|
||||
if (entries == 0) {
|
||||
component_main_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"^%02X** empty directory **",
|
||||
STL_UNKNOWN
|
||||
STL_GRAY
|
||||
);
|
||||
} else {
|
||||
rdpq_paragraph_t *file_list_layout;
|
||||
rdpq_paragraph_t *layout;
|
||||
|
||||
size_t name_lengths[FILE_LIST_ENTRIES];
|
||||
size_t name_lengths[LIST_ENTRIES];
|
||||
size_t total_length = 1;
|
||||
|
||||
for (int i = 0; i < FILE_LIST_ENTRIES; i++) {
|
||||
for (int i = 0; i < LIST_ENTRIES; i++) {
|
||||
int entry_index = starting_position + i;
|
||||
|
||||
if (entry_index >= entries) {
|
||||
@ -72,7 +76,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
||||
file_list_layout
|
||||
);
|
||||
|
||||
for (int i = 0; i < FILE_LIST_ENTRIES; i++) {
|
||||
for (int i = 0; i < LIST_ENTRIES; i++) {
|
||||
int entry_index = starting_position + i;
|
||||
|
||||
entry_t *entry = &list[entry_index];
|
||||
@ -80,12 +84,16 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
||||
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;
|
||||
case ENTRY_TYPE_DIR: style = STL_YELLOW; break;
|
||||
case ENTRY_TYPE_ROM: style = STL_DEFAULT; break;
|
||||
case ENTRY_TYPE_DISK: style = STL_DEFAULT; break;
|
||||
case ENTRY_TYPE_EMULATOR: style = STL_DEFAULT; break;
|
||||
case ENTRY_TYPE_SAVE: style = STL_GREEN; break;
|
||||
case ENTRY_TYPE_IMAGE: style = STL_BLUE; break;
|
||||
case ENTRY_TYPE_MUSIC: style = STL_BLUE; break;
|
||||
case ENTRY_TYPE_TEXT: style = STL_ORANGE; break;
|
||||
case ENTRY_TYPE_OTHER: style = STL_GRAY; break;
|
||||
default: style = STL_GRAY; break;
|
||||
}
|
||||
|
||||
rdpq_paragraph_builder_style(style);
|
||||
@ -126,7 +134,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
||||
|
||||
rdpq_paragraph_builder_begin(
|
||||
&(rdpq_textparms_t) {
|
||||
.width = VISIBLE_AREA_WIDTH - FILE_LIST_SCROLLBAR_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
|
||||
.width = VISIBLE_AREA_WIDTH - 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,
|
||||
@ -135,7 +143,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
||||
NULL
|
||||
);
|
||||
|
||||
char file_size[8];
|
||||
char file_size[16];
|
||||
|
||||
for (int i = starting_position; i < entries; i++) {
|
||||
entry_t *entry = &list[i];
|
||||
@ -144,7 +152,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
||||
rdpq_paragraph_builder_span(file_size, format_file_size(file_size, entry->size));
|
||||
}
|
||||
|
||||
if ((i + 1) == (starting_position + FILE_LIST_ENTRIES)) {
|
||||
if ((i + 1) == (starting_position + LIST_ENTRIES)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include <fatfs/ff.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "disk_info.h"
|
||||
@ -39,18 +39,14 @@ static const int disk_id_lbas[DISK_ID_LBA_COUNT] = {
|
||||
};
|
||||
|
||||
|
||||
static bool load_system_area_lba (FIL *fil, int lba, uint8_t *buffer) {
|
||||
UINT bytes_read;
|
||||
static bool load_system_area_lba (FILE *f, int lba, uint8_t *buffer) {
|
||||
if (lba >= SYSTEM_AREA_LBA_COUNT) {
|
||||
return true;
|
||||
}
|
||||
if (f_lseek(fil, SYSTEM_AREA_LBA_LENGTH * lba) != FR_OK) {
|
||||
if (fseek(f, SYSTEM_AREA_LBA_LENGTH * lba, SEEK_SET)) {
|
||||
return true;
|
||||
}
|
||||
if (f_read(fil, buffer, SYSTEM_AREA_LBA_LENGTH, &bytes_read) != FR_OK) {
|
||||
return true;
|
||||
}
|
||||
if (bytes_read != SYSTEM_AREA_LBA_LENGTH) {
|
||||
if (fread(buffer, SYSTEM_AREA_LBA_LENGTH, 1, f) != 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -113,7 +109,7 @@ static void update_bad_system_area_lbas (disk_info_t *disk_info) {
|
||||
}
|
||||
}
|
||||
|
||||
static disk_err_t load_and_verify_system_data_lba (FIL *fil, disk_info_t *disk_info) {
|
||||
static disk_err_t load_and_verify_system_data_lba (FILE *f, disk_info_t *disk_info) {
|
||||
uint8_t buffer[SYSTEM_AREA_LBA_LENGTH];
|
||||
int sector_length;
|
||||
|
||||
@ -122,7 +118,7 @@ static disk_err_t load_and_verify_system_data_lba (FIL *fil, disk_info_t *disk_i
|
||||
for (int i = 0; i < SYSTEM_DATA_LBA_COUNT; i++) {
|
||||
int lba = system_data_lbas[i];
|
||||
|
||||
if (load_system_area_lba(fil, lba, buffer)) {
|
||||
if (load_system_area_lba(f, lba, buffer)) {
|
||||
return DISK_ERR_IO;
|
||||
}
|
||||
|
||||
@ -155,7 +151,7 @@ static disk_err_t load_and_verify_system_data_lba (FIL *fil, disk_info_t *disk_i
|
||||
return valid_system_data_lba_found ? DISK_OK : DISK_ERR_INVALID;
|
||||
}
|
||||
|
||||
static disk_err_t load_and_verify_disk_id_lba (FIL *fil, disk_info_t *disk_info) {
|
||||
static disk_err_t load_and_verify_disk_id_lba (FILE *f, disk_info_t *disk_info) {
|
||||
uint8_t buffer[SYSTEM_AREA_LBA_LENGTH];
|
||||
|
||||
bool valid_disk_id_lba_found = false;
|
||||
@ -163,7 +159,7 @@ static disk_err_t load_and_verify_disk_id_lba (FIL *fil, disk_info_t *disk_info)
|
||||
for (int i = 0; i < DISK_ID_LBA_COUNT; i++) {
|
||||
int lba = disk_id_lbas[i];
|
||||
|
||||
if (load_system_area_lba(fil, lba, buffer)) {
|
||||
if (load_system_area_lba(f, lba, buffer)) {
|
||||
return DISK_ERR_IO;
|
||||
}
|
||||
|
||||
@ -180,29 +176,27 @@ static disk_err_t load_and_verify_disk_id_lba (FIL *fil, disk_info_t *disk_info)
|
||||
}
|
||||
|
||||
|
||||
disk_err_t disk_info_load (char *path, disk_info_t *disk_info) {
|
||||
FIL fil;
|
||||
disk_err_t disk_info_load (path_t *path, disk_info_t *disk_info) {
|
||||
FILE *f;
|
||||
disk_err_t err;
|
||||
|
||||
for (int i = 0; i < SYSTEM_AREA_LBA_COUNT; i++) {
|
||||
disk_info->bad_system_area_lbas[i] = false;
|
||||
}
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) {
|
||||
if ((f = fopen(path_get(path), "rb")) == NULL) {
|
||||
return DISK_ERR_NO_FILE;
|
||||
}
|
||||
|
||||
if ((err = load_and_verify_system_data_lba(&fil, disk_info)) != DISK_OK) {
|
||||
f_close(&fil);
|
||||
setbuf(f, NULL);
|
||||
if ((err = load_and_verify_system_data_lba(f, disk_info)) != DISK_OK) {
|
||||
fclose(f);
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = load_and_verify_disk_id_lba(&fil, disk_info)) != DISK_OK) {
|
||||
f_close(&fil);
|
||||
if ((err = load_and_verify_disk_id_lba(f, disk_info)) != DISK_OK) {
|
||||
fclose(f);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
if (fclose(f)) {
|
||||
return DISK_ERR_IO;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,9 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "path.h"
|
||||
|
||||
|
||||
/** @brief Disk state enumeration. */
|
||||
typedef enum {
|
||||
DISK_OK,
|
||||
@ -49,7 +52,7 @@ typedef struct {
|
||||
} disk_info_t;
|
||||
|
||||
|
||||
disk_err_t disk_info_load (char *path, disk_info_t *disk_info);
|
||||
disk_err_t disk_info_load (path_t *path, disk_info_t *disk_info);
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -1,21 +1,29 @@
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "fonts.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
|
||||
static void load_default_font (void) {
|
||||
rdpq_font_t *default_font = rdpq_font_load("rom:/FiraMonoBold.font64");
|
||||
static void load_default_font (char *custom_font_path) {
|
||||
char *font_path = "rom:/FiraMonoBold.font64";
|
||||
|
||||
if (custom_font_path && file_exists(custom_font_path)) {
|
||||
font_path = custom_font_path;
|
||||
}
|
||||
|
||||
rdpq_font_t *default_font = rdpq_font_load(font_path);
|
||||
|
||||
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_font_style(default_font, STL_GREEN, &((rdpq_fontstyle_t) { .color = RGBA32(0x70, 0xFF, 0x70, 0xFF) }));
|
||||
rdpq_font_style(default_font, STL_BLUE, &((rdpq_fontstyle_t) { .color = RGBA32(0x70, 0xBC, 0xFF, 0xFF) }));
|
||||
rdpq_font_style(default_font, STL_YELLOW, &((rdpq_fontstyle_t) { .color = RGBA32(0xFF, 0xFF, 0x70, 0xFF) }));
|
||||
rdpq_font_style(default_font, STL_ORANGE, &((rdpq_fontstyle_t) { .color = RGBA32(0xFF, 0x99, 0x00, 0xFF) }));
|
||||
rdpq_font_style(default_font, STL_GRAY, &((rdpq_fontstyle_t) { .color = RGBA32(0xA0, 0xA0, 0xA0, 0xFF) }));
|
||||
|
||||
rdpq_text_register_font(FNT_DEFAULT, default_font);
|
||||
}
|
||||
|
||||
|
||||
void fonts_init (void) {
|
||||
load_default_font();
|
||||
void fonts_init (char *custom_font_path) {
|
||||
load_default_font(custom_font_path);
|
||||
}
|
||||
|
@ -15,14 +15,15 @@ typedef enum {
|
||||
/** @brief Font style enumeration. */
|
||||
typedef enum {
|
||||
STL_DEFAULT = 0,
|
||||
STL_DIRECTORY,
|
||||
STL_SAVE,
|
||||
STL_MEDIA,
|
||||
STL_UNKNOWN,
|
||||
STL_GREEN,
|
||||
STL_BLUE,
|
||||
STL_YELLOW,
|
||||
STL_ORANGE,
|
||||
STL_GRAY,
|
||||
} menu_font_style_t;
|
||||
|
||||
|
||||
void fonts_init (void);
|
||||
void fonts_init (char *custom_font_path);
|
||||
|
||||
|
||||
#endif
|
||||
|
120
src/menu/menu.c
120
src/menu/menu.c
@ -20,19 +20,23 @@
|
||||
#include "views/views.h"
|
||||
|
||||
|
||||
#define TV_TYPE_RAM *((uint32_t *) (0x80000300))
|
||||
#define MENU_DIRECTORY "/menu"
|
||||
#define MENU_SETTINGS_FILE "config.ini"
|
||||
#define MENU_CUSTOM_FONT_FILE "custom.font64"
|
||||
|
||||
#define CACHE_DIRECTORY "sd:/menu/cache"
|
||||
#define BACKGROUND_CACHE "sd:/menu/cache/background.data"
|
||||
#define MENU_CACHE_DIRECTORY "cache"
|
||||
#define BACKGROUND_CACHE_FILE "background.data"
|
||||
|
||||
#define FRAMERATE_DIVIDER (2)
|
||||
#define LAG_REPORT (false)
|
||||
#define FRAMERATE_DIVIDER (2)
|
||||
#define LAG_REPORT (false)
|
||||
|
||||
|
||||
static menu_t *menu;
|
||||
static tv_type_t tv_type;
|
||||
static volatile int frame_counter = 0;
|
||||
|
||||
extern tv_type_t __boot_tvtype;
|
||||
|
||||
|
||||
static void frame_counter_handler (void) {
|
||||
frame_counter += 1;
|
||||
@ -61,7 +65,6 @@ static void menu_init (boot_params_t *boot_params) {
|
||||
rdpq_init();
|
||||
dfs_init(DFS_DEFAULT_LOCATION);
|
||||
|
||||
fonts_init();
|
||||
sound_init_default();
|
||||
|
||||
menu = calloc(1, sizeof(menu_t));
|
||||
@ -70,36 +73,46 @@ static void menu_init (boot_params_t *boot_params) {
|
||||
menu->mode = MENU_MODE_NONE;
|
||||
menu->next_mode = MENU_MODE_STARTUP;
|
||||
|
||||
menu->flashcart_err = flashcart_init();
|
||||
menu->flashcart_err = flashcart_init(&menu->storage_prefix);
|
||||
if (menu->flashcart_err != FLASHCART_OK) {
|
||||
menu->next_mode = MENU_MODE_FAULT;
|
||||
}
|
||||
|
||||
menu->error_message = NULL;
|
||||
path_t *path = path_init(menu->storage_prefix, MENU_DIRECTORY);
|
||||
|
||||
directory_create(path_get(path));
|
||||
|
||||
path_push(path, MENU_SETTINGS_FILE);
|
||||
settings_init(path_get(path));
|
||||
settings_load(&menu->settings);
|
||||
path_pop(path);
|
||||
|
||||
directory_create(CACHE_DIRECTORY);
|
||||
path_push(path, MENU_CUSTOM_FONT_FILE);
|
||||
fonts_init(path_get(path));
|
||||
path_pop(path);
|
||||
|
||||
component_background_init(BACKGROUND_CACHE);
|
||||
path_push(path, MENU_CACHE_DIRECTORY);
|
||||
directory_create(path_get(path));
|
||||
|
||||
path_push(path, BACKGROUND_CACHE_FILE);
|
||||
component_background_init(path_get(path));
|
||||
|
||||
path_free(path);
|
||||
|
||||
menu->boot_params = boot_params;
|
||||
|
||||
bool default_directory_exists = directory_exists(menu->settings.default_directory);
|
||||
char *init_directory = default_directory_exists ? menu->settings.default_directory : "";
|
||||
|
||||
menu->browser.valid = false;
|
||||
menu->browser.reload = false;
|
||||
menu->browser.directory = path_init("sd:/", init_directory);
|
||||
|
||||
menu->load.rom_path = NULL;
|
||||
menu->browser.directory = path_init(menu->storage_prefix, menu->settings.default_directory);
|
||||
if (!directory_exists(path_get(menu->browser.directory))) {
|
||||
path_free(menu->browser.directory);
|
||||
menu->browser.directory = path_init(menu->storage_prefix, "/");
|
||||
}
|
||||
|
||||
hdmi_clear_game_id();
|
||||
|
||||
tv_type = get_tv_type();
|
||||
if ((tv_type == TV_PAL) && menu->settings.pal60_enabled) {
|
||||
// HACK: Set TV type to NTSC, so PAL console would output 60 Hz signal instead.
|
||||
TV_TYPE_RAM = TV_NTSC;
|
||||
__boot_tvtype = TV_NTSC;
|
||||
}
|
||||
|
||||
display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_DISABLED);
|
||||
@ -111,10 +124,16 @@ static void menu_deinit (menu_t *menu) {
|
||||
unregister_VI_handler(frame_counter_handler);
|
||||
|
||||
// NOTE: Restore previous TV type so boot procedure wouldn't passthrough wrong value.
|
||||
TV_TYPE_RAM = tv_type;
|
||||
__boot_tvtype = tv_type;
|
||||
|
||||
hdmi_send_game_id(menu->boot_params);
|
||||
|
||||
path_free(menu->load.disk_path);
|
||||
path_free(menu->load.rom_path);
|
||||
for (int i = 0; i < menu->browser.entries; i++) {
|
||||
free(menu->browser.list[i].name);
|
||||
}
|
||||
free(menu->browser.list);
|
||||
path_free(menu->browser.directory);
|
||||
free(menu);
|
||||
|
||||
@ -133,30 +152,41 @@ static void menu_deinit (menu_t *menu) {
|
||||
display_close();
|
||||
}
|
||||
|
||||
|
||||
// NOTE: Keep this array in sync with menu_mode_t
|
||||
static struct views_s {
|
||||
typedef const struct {
|
||||
menu_mode_t id;
|
||||
void (*init) (menu_t *menu);
|
||||
void (*show) (menu_t *menu, surface_t *display);
|
||||
} views[__MENU_MODE_COUNT] = {
|
||||
{ NULL, NULL }, // MENU_MODE_NONE
|
||||
{ view_startup_init, view_startup_display }, // MENU_MODE_STARTUP
|
||||
{ view_browser_init, view_browser_display }, // MENU_MODE_BROWSER
|
||||
{ view_file_info_init, view_file_info_display }, // MENU_MODE_FILE_INFO
|
||||
{ view_system_info_init, view_system_info_display }, // MENU_MODE_SYSTEM_INFO
|
||||
{ view_image_viewer_init, view_image_viewer_display }, // MENU_MODE_IMAGE_VIEWER
|
||||
{ view_music_player_init, view_music_player_display }, // MENU_MODE_MUSIC_PLAYER
|
||||
{ view_credits_init, view_credits_display }, // MENU_MODE_CREDITS
|
||||
{ view_settings_init, view_settings_display }, // MENU_MODE_SETTINGS_EDITOR
|
||||
{ view_rtc_init, view_rtc_display }, // MENU_MODE_RTC
|
||||
{ view_load_rom_init, view_load_rom_display }, // MENU_MODE_LOAD_ROM
|
||||
{ view_load_disk_init, view_load_disk_display }, // MENU_MODE_LOAD_DISK
|
||||
{ view_load_emulator_init, view_load_emulator_display }, // MENU_MODE_LOAD_EMULATOR
|
||||
{ view_error_init, view_error_display }, // MENU_MODE_ERROR
|
||||
{ view_fault_init, view_fault_display }, // MENU_MODE_FAULT
|
||||
{ NULL, NULL }, // MENU_MODE_BOOT
|
||||
} view_t;
|
||||
|
||||
static view_t menu_views[] = {
|
||||
{ MENU_MODE_STARTUP, view_startup_init, view_startup_display },
|
||||
{ MENU_MODE_BROWSER, view_browser_init, view_browser_display },
|
||||
{ MENU_MODE_FILE_INFO, view_file_info_init, view_file_info_display },
|
||||
{ MENU_MODE_SYSTEM_INFO, view_system_info_init, view_system_info_display },
|
||||
{ MENU_MODE_IMAGE_VIEWER, view_image_viewer_init, view_image_viewer_display },
|
||||
{ MENU_MODE_TEXT_VIEWER, view_text_viewer_init, view_text_viewer_display },
|
||||
{ MENU_MODE_MUSIC_PLAYER, view_music_player_init, view_music_player_display },
|
||||
{ MENU_MODE_CREDITS, view_credits_init, view_credits_display },
|
||||
{ MENU_MODE_SETTINGS_EDITOR, view_settings_init, view_settings_display },
|
||||
{ MENU_MODE_RTC, view_rtc_init, view_rtc_display },
|
||||
{ MENU_MODE_FLASHCART, view_flashcart_info_init, view_flashcart_info_display },
|
||||
{ MENU_MODE_LOAD_ROM, view_load_rom_init, view_load_rom_display },
|
||||
{ MENU_MODE_LOAD_DISK, view_load_disk_init, view_load_disk_display },
|
||||
{ MENU_MODE_LOAD_EMULATOR, view_load_emulator_init, view_load_emulator_display },
|
||||
{ MENU_MODE_ERROR, view_error_init, view_error_display },
|
||||
{ MENU_MODE_FAULT, view_fault_init, view_fault_display },
|
||||
};
|
||||
|
||||
static view_t *menu_get_view (menu_mode_t id) {
|
||||
for (int i = 0; i < sizeof(menu_views) / sizeof(view_t); i++) {
|
||||
if (menu_views[i].id == id) {
|
||||
return &menu_views[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void menu_run (boot_params_t *boot_params) {
|
||||
menu_init(boot_params);
|
||||
|
||||
@ -168,8 +198,9 @@ void menu_run (boot_params_t *boot_params) {
|
||||
|
||||
actions_update(menu);
|
||||
|
||||
if (views[menu->mode].show) {
|
||||
views[menu->mode].show(menu, display);
|
||||
view_t *view = menu_get_view(menu->mode);
|
||||
if (view && view->show) {
|
||||
view->show(menu, display);
|
||||
} else {
|
||||
rdpq_attach_clear(display, NULL);
|
||||
rdpq_detach_wait();
|
||||
@ -183,8 +214,9 @@ void menu_run (boot_params_t *boot_params) {
|
||||
while (menu->mode != menu->next_mode) {
|
||||
menu->mode = menu->next_mode;
|
||||
|
||||
if (views[menu->mode].init) {
|
||||
views[menu->mode].init(menu);
|
||||
view_t *next_view = menu_get_view(menu->next_mode);
|
||||
if (next_view && next_view->init) {
|
||||
next_view->init(menu);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,6 @@
|
||||
#include "settings.h"
|
||||
|
||||
|
||||
#define BROWSER_LIST_SIZE 2048
|
||||
|
||||
|
||||
/** @brief Menu mode enumeration */
|
||||
typedef enum {
|
||||
MENU_MODE_NONE,
|
||||
@ -29,17 +26,18 @@ typedef enum {
|
||||
MENU_MODE_FILE_INFO,
|
||||
MENU_MODE_SYSTEM_INFO,
|
||||
MENU_MODE_IMAGE_VIEWER,
|
||||
MENU_MODE_TEXT_VIEWER,
|
||||
MENU_MODE_MUSIC_PLAYER,
|
||||
MENU_MODE_CREDITS,
|
||||
MENU_MODE_SETTINGS_EDITOR,
|
||||
MENU_MODE_RTC,
|
||||
MENU_MODE_FLASHCART,
|
||||
MENU_MODE_LOAD_ROM,
|
||||
MENU_MODE_LOAD_DISK,
|
||||
MENU_MODE_LOAD_EMULATOR,
|
||||
MENU_MODE_ERROR,
|
||||
MENU_MODE_FAULT,
|
||||
MENU_MODE_BOOT,
|
||||
__MENU_MODE_COUNT,
|
||||
} menu_mode_t;
|
||||
|
||||
/** @brief File entry type enumeration */
|
||||
@ -51,6 +49,7 @@ typedef enum {
|
||||
ENTRY_TYPE_SAVE,
|
||||
ENTRY_TYPE_IMAGE,
|
||||
ENTRY_TYPE_MUSIC,
|
||||
ENTRY_TYPE_TEXT,
|
||||
ENTRY_TYPE_OTHER,
|
||||
} entry_type_t;
|
||||
|
||||
@ -58,7 +57,7 @@ typedef enum {
|
||||
typedef struct {
|
||||
char *name;
|
||||
entry_type_t type;
|
||||
int size;
|
||||
int64_t size;
|
||||
} entry_t;
|
||||
|
||||
/** @brief Menu Structure */
|
||||
@ -66,6 +65,7 @@ typedef struct {
|
||||
menu_mode_t mode;
|
||||
menu_mode_t next_mode;
|
||||
|
||||
const char *storage_prefix;
|
||||
settings_t settings;
|
||||
boot_params_t *boot_params;
|
||||
|
||||
@ -91,7 +91,7 @@ typedef struct {
|
||||
bool valid;
|
||||
bool reload;
|
||||
path_t *directory;
|
||||
entry_t list[BROWSER_LIST_SIZE];
|
||||
entry_t *list;
|
||||
int entries;
|
||||
entry_t *entry;
|
||||
int selected;
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include <fatfs/ff.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "mp3_player.h"
|
||||
@ -18,19 +20,18 @@
|
||||
/** @brief MP3 File Information Structure. */
|
||||
typedef struct {
|
||||
bool loaded;
|
||||
bool io_error;
|
||||
|
||||
FIL fil;
|
||||
FSIZE_t data_start;
|
||||
int seek_predecode_frames;
|
||||
FILE *f;
|
||||
size_t file_size;
|
||||
size_t data_start;
|
||||
uint8_t buffer[16 * 1024];
|
||||
uint8_t *buffer_ptr;
|
||||
size_t buffer_left;
|
||||
|
||||
mp3dec_t dec;
|
||||
mp3dec_frame_info_t info;
|
||||
|
||||
uint8_t buffer[MAX_FREE_FORMAT_FRAME_SIZE];
|
||||
uint8_t *buffer_ptr;
|
||||
size_t buffer_left;
|
||||
|
||||
int seek_predecode_frames;
|
||||
float duration;
|
||||
float bitrate;
|
||||
|
||||
@ -48,9 +49,11 @@ static void mp3player_reset_decoder (void) {
|
||||
}
|
||||
|
||||
static void mp3player_fill_buffer (void) {
|
||||
UINT bytes_read;
|
||||
if (feof(p->f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (f_eof(&p->fil)) {
|
||||
if (p->buffer_left >= ALIGN(MAX_FREE_FORMAT_FRAME_SIZE, FS_SECTOR_SIZE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -59,11 +62,7 @@ static void mp3player_fill_buffer (void) {
|
||||
p->buffer_ptr = p->buffer;
|
||||
}
|
||||
|
||||
if (f_read(&p->fil, p->buffer + p->buffer_left, sizeof(p->buffer) - p->buffer_left, &bytes_read) == FR_OK) {
|
||||
p->buffer_left += bytes_read;
|
||||
} else {
|
||||
p->io_error = true;
|
||||
}
|
||||
p->buffer_left += fread(p->buffer + p->buffer_left, 1, sizeof(p->buffer) - p->buffer_left, p->f);
|
||||
}
|
||||
|
||||
static void mp3player_wave_read (void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) {
|
||||
@ -105,7 +104,7 @@ static void mp3player_calculate_duration (int samples) {
|
||||
uint32_t frames;
|
||||
int delay, padding;
|
||||
|
||||
long data_size = (f_size(&p->fil) - p->data_start);
|
||||
long data_size = (p->file_size - p->data_start);
|
||||
if (mp3dec_check_vbrtag((const uint8_t *) (p->buffer_ptr), p->info.frame_bytes, &frames, &delay, &padding) > 0) {
|
||||
p->duration = (frames * samples) / (float) (p->info.hz);
|
||||
p->bitrate = (data_size * 8) / p->duration;
|
||||
@ -133,7 +132,6 @@ mp3player_err_t mp3player_init (void) {
|
||||
mp3player_reset_decoder();
|
||||
|
||||
p->loaded = false;
|
||||
p->io_error = false;
|
||||
|
||||
p->wave = (waveform_t) {
|
||||
.name = "mp3player",
|
||||
@ -160,22 +158,32 @@ mp3player_err_t mp3player_load (char *path) {
|
||||
mp3player_unload();
|
||||
}
|
||||
|
||||
if (f_open(&p->fil, strip_sd_prefix(path), FA_READ) != FR_OK) {
|
||||
if ((p->f = fopen(path, "rb")) == NULL) {
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
setbuf(p->f, NULL);
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fileno(p->f), &st)) {
|
||||
fclose(p->f);
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
p->file_size = st.st_size;
|
||||
|
||||
mp3player_reset_decoder();
|
||||
|
||||
while (!(f_eof(&p->fil) && p->buffer_left == 0)) {
|
||||
while (!(feof(p->f) && p->buffer_left == 0)) {
|
||||
mp3player_fill_buffer();
|
||||
|
||||
if (p->io_error) {
|
||||
if (ferror(p->f)) {
|
||||
fclose(p->f);
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
|
||||
size_t id3v2_skip = mp3dec_skip_id3v2((const uint8_t *) (p->buffer_ptr), p->buffer_left);
|
||||
if (id3v2_skip > 0) {
|
||||
if (f_lseek(&p->fil, f_tell(&p->fil) - p->buffer_left + id3v2_skip) != FR_OK) {
|
||||
if (fseek(p->f, (-p->buffer_left) + id3v2_skip, SEEK_CUR)) {
|
||||
fclose(p->f);
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
mp3player_reset_decoder();
|
||||
@ -185,7 +193,7 @@ mp3player_err_t mp3player_load (char *path) {
|
||||
int samples = mp3dec_decode_frame(&p->dec, p->buffer_ptr, p->buffer_left, NULL, &p->info);
|
||||
if (samples > 0) {
|
||||
p->loaded = true;
|
||||
p->data_start = f_tell(&p->fil) - p->buffer_left + p->info.frame_offset;
|
||||
p->data_start = ftell(p->f) - p->buffer_left + p->info.frame_offset;
|
||||
|
||||
p->buffer_ptr += p->info.frame_offset;
|
||||
p->buffer_left -= p->info.frame_offset;
|
||||
@ -202,7 +210,7 @@ mp3player_err_t mp3player_load (char *path) {
|
||||
p->buffer_left -= p->info.frame_bytes;
|
||||
}
|
||||
|
||||
if (f_close(&p->fil) != FR_OK) {
|
||||
if (fclose(p->f)) {
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
|
||||
@ -213,12 +221,12 @@ void mp3player_unload (void) {
|
||||
mp3player_stop();
|
||||
if (p->loaded) {
|
||||
p->loaded = false;
|
||||
f_close(&p->fil);
|
||||
fclose(p->f);
|
||||
}
|
||||
}
|
||||
|
||||
mp3player_err_t mp3player_process (void) {
|
||||
if (p->io_error) {
|
||||
if (ferror(p->f)) {
|
||||
mp3player_unload();
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
@ -235,7 +243,7 @@ bool mp3player_is_playing (void) {
|
||||
}
|
||||
|
||||
bool mp3player_is_finished (void) {
|
||||
return p->loaded && f_eof(&p->fil) && (p->buffer_left == 0);
|
||||
return p->loaded && feof(p->f) && (p->buffer_left == 0);
|
||||
}
|
||||
|
||||
mp3player_err_t mp3player_play (void) {
|
||||
@ -244,8 +252,7 @@ mp3player_err_t mp3player_play (void) {
|
||||
}
|
||||
if (!mp3player_is_playing()) {
|
||||
if (mp3player_is_finished()) {
|
||||
if (f_lseek(&p->fil, p->data_start) != FR_OK) {
|
||||
p->io_error = true;
|
||||
if (fseek(p->f, p->data_start, SEEK_SET)) {
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
mp3player_reset_decoder();
|
||||
@ -288,25 +295,24 @@ mp3player_err_t mp3player_seek (int seconds) {
|
||||
return MP3PLAYER_OK;
|
||||
}
|
||||
|
||||
long position = ((long) (f_tell(&p->fil)) - p->buffer_left + bytes_to_move);
|
||||
long position = (ftell(p->f) - p->buffer_left + bytes_to_move);
|
||||
if (position < (long) (p->data_start)) {
|
||||
position = p->data_start;
|
||||
}
|
||||
|
||||
if (f_lseek(&p->fil, position) != FR_OK) {
|
||||
p->io_error = true;
|
||||
if (fseek(p->f, position, SEEK_SET)) {
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
|
||||
mp3player_reset_decoder();
|
||||
mp3player_fill_buffer();
|
||||
|
||||
p->seek_predecode_frames = (position == p->data_start) ? 0 : SEEK_PREDECODE_FRAMES;
|
||||
|
||||
if (p->io_error) {
|
||||
if (ferror(p->f)) {
|
||||
return MP3PLAYER_ERR_IO;
|
||||
}
|
||||
|
||||
p->seek_predecode_frames = (position == p->data_start) ? 0 : SEEK_PREDECODE_FRAMES;
|
||||
|
||||
return MP3PLAYER_OK;
|
||||
}
|
||||
|
||||
@ -342,9 +348,9 @@ float mp3player_get_progress (void) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
FSIZE_t data_size = f_size(&p->fil) - p->data_start;
|
||||
FSIZE_t data_consumed = f_tell(&p->fil) - p->buffer_left;
|
||||
FSIZE_t data_position = (data_consumed > p->data_start) ? (data_consumed - p->data_start) : 0;
|
||||
long data_size = p->file_size - p->data_start;
|
||||
long data_consumed = ftell(p->f) - p->buffer_left;
|
||||
long data_position = (data_consumed > p->data_start) ? (data_consumed - p->data_start) : 0;
|
||||
|
||||
return data_position / (float) (data_size);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ static void path_resize (path_t *path, size_t min_length) {
|
||||
assert(path->buffer != NULL);
|
||||
}
|
||||
|
||||
static path_t *path_create (char *string) {
|
||||
static path_t *path_create (const char *string) {
|
||||
if (string == NULL) {
|
||||
string = "";
|
||||
}
|
||||
@ -43,7 +43,7 @@ static void path_append (path_t *path, char *string) {
|
||||
}
|
||||
|
||||
|
||||
path_t *path_init (char *prefix, char *string) {
|
||||
path_t *path_init (const char *prefix, char *string) {
|
||||
path_t *path = path_create(prefix);
|
||||
size_t prefix_length = strlen(prefix);
|
||||
if ((prefix_length > 0) && (prefix[prefix_length - 1] == '/')) {
|
||||
|
@ -20,7 +20,7 @@ typedef struct {
|
||||
} path_t;
|
||||
|
||||
|
||||
path_t *path_init (char *prefix, char *string);
|
||||
path_t *path_init (const char *prefix, char *string);
|
||||
void path_free (path_t *path);
|
||||
path_t *path_clone (path_t *string);
|
||||
path_t *path_clone_push (path_t *path, char *string);
|
||||
|
@ -1,12 +1,14 @@
|
||||
#include <fatfs/ff.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <libspng/spng/spng.h>
|
||||
|
||||
#include "png_decoder.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
|
||||
/** @brief PNG File Information Structure. */
|
||||
typedef struct {
|
||||
FIL fil;
|
||||
FILE *f;
|
||||
|
||||
spng_ctx *ctx;
|
||||
struct spng_ihdr ihdr;
|
||||
@ -24,7 +26,7 @@ static png_decoder_t *decoder;
|
||||
|
||||
static void png_decoder_deinit (bool free_image) {
|
||||
if (decoder != NULL) {
|
||||
f_close(&decoder->fil);
|
||||
fclose(decoder->f);
|
||||
if (decoder->ctx != NULL) {
|
||||
spng_ctx_free(decoder->ctx);
|
||||
}
|
||||
@ -40,21 +42,6 @@ static void png_decoder_deinit (bool free_image) {
|
||||
}
|
||||
}
|
||||
|
||||
static int png_file_read (spng_ctx *ctx, void *user, void *dst_src, size_t length) {
|
||||
UINT bytes_read = 0;
|
||||
png_decoder_t *d = (png_decoder_t *) (user);
|
||||
|
||||
if (f_read(&d->fil, dst_src, length, &bytes_read) != FR_OK) {
|
||||
return SPNG_IO_ERROR;
|
||||
}
|
||||
|
||||
if (bytes_read != length) {
|
||||
return SPNG_EOF;
|
||||
}
|
||||
|
||||
return SPNG_OK;
|
||||
}
|
||||
|
||||
|
||||
png_err_t png_decoder_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data) {
|
||||
if (decoder != NULL) {
|
||||
@ -66,11 +53,13 @@ png_err_t png_decoder_start (char *path, int max_width, int max_height, png_call
|
||||
return PNG_ERR_OUT_OF_MEM;
|
||||
}
|
||||
|
||||
if (f_open(&decoder->fil, strip_sd_prefix(path), FA_READ) != FR_OK) {
|
||||
if ((decoder->f = fopen(path, "rb")) == NULL) {
|
||||
png_decoder_deinit(false);
|
||||
return PNG_ERR_NO_FILE;
|
||||
}
|
||||
|
||||
setbuf(decoder->f, NULL);
|
||||
|
||||
if ((decoder->ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32)) == NULL) {
|
||||
png_decoder_deinit(false);
|
||||
return PNG_ERR_OUT_OF_MEM;
|
||||
@ -86,7 +75,7 @@ png_err_t png_decoder_start (char *path, int max_width, int max_height, png_call
|
||||
return PNG_ERR_INT;
|
||||
}
|
||||
|
||||
if (spng_set_png_stream(decoder->ctx, png_file_read, decoder) != SPNG_OK) {
|
||||
if (spng_set_png_file(decoder->ctx, decoder->f) != SPNG_OK) {
|
||||
png_decoder_deinit(false);
|
||||
return PNG_ERR_INT;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fatfs/ff.h>
|
||||
#include <mini.c/src/mini.h>
|
||||
|
||||
#include "boot/cic.h"
|
||||
#include "rom_info.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
@ -16,6 +17,7 @@
|
||||
|
||||
#define CLOCK_RATE_DEFAULT (0x0000000F)
|
||||
|
||||
|
||||
/** @brief ROM File Information Structure. */
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t pi_dom1_config;
|
||||
@ -139,6 +141,8 @@ typedef struct {
|
||||
|
||||
|
||||
// List shamelessly stolen from https://github.com/ares-emulator/ares/blob/master/mia/medium/nintendo-64.cpp
|
||||
|
||||
// clang-format off
|
||||
static const match_t database[] = {
|
||||
MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED)
|
||||
|
||||
@ -574,6 +578,7 @@ static const match_t database[] = {
|
||||
|
||||
MATCH_END,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
static void fix_rom_header_endianness (rom_header_t *rom_header, rom_info_t *rom_info) {
|
||||
@ -652,15 +657,34 @@ static match_t find_rom_in_database (rom_header_t *rom_header) {
|
||||
return *match;
|
||||
}
|
||||
|
||||
static uint32_t fix_boot_address (cic_type_t cic_type, uint32_t boot_address) {
|
||||
static rom_cic_type_t detect_cic_type (uint8_t *ipl3) {
|
||||
switch (cic_detect(ipl3)) {
|
||||
case CIC_5101: return ROM_CIC_TYPE_5101;
|
||||
case CIC_5167: return ROM_CIC_TYPE_5167;
|
||||
case CIC_6101: return ROM_CIC_TYPE_6101;
|
||||
case CIC_7102: return ROM_CIC_TYPE_7102;
|
||||
case CIC_x102: return ROM_CIC_TYPE_x102;
|
||||
case CIC_x103: return ROM_CIC_TYPE_x103;
|
||||
case CIC_x105: return ROM_CIC_TYPE_x105;
|
||||
case CIC_x106: return ROM_CIC_TYPE_x106;
|
||||
case CIC_8301: return ROM_CIC_TYPE_8301;
|
||||
case CIC_8302: return ROM_CIC_TYPE_8302;
|
||||
case CIC_8303: return ROM_CIC_TYPE_8303;
|
||||
case CIC_8401: return ROM_CIC_TYPE_8401;
|
||||
case CIC_8501: return ROM_CIC_TYPE_8501;
|
||||
default: return ROM_CIC_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t fix_boot_address (rom_cic_type_t cic_type, uint32_t boot_address) {
|
||||
switch (cic_type) {
|
||||
case CIC_x103: return (boot_address - 0x100000);
|
||||
case CIC_x106: return (boot_address - 0x200000);
|
||||
case ROM_CIC_TYPE_x103: return (boot_address - 0x100000);
|
||||
case ROM_CIC_TYPE_x106: return (boot_address - 0x200000);
|
||||
default: return boot_address;
|
||||
}
|
||||
}
|
||||
|
||||
static rom_tv_type_t determine_tv_type (destination_type_t rom_destination_code) {
|
||||
static rom_tv_type_t determine_tv_type (rom_destination_type_t rom_destination_code) {
|
||||
// check the market type from the ROM destination_code and return best guess!
|
||||
switch (rom_destination_code) {
|
||||
case MARKET_NORTH_AMERICA:
|
||||
@ -695,7 +719,7 @@ static rom_tv_type_t determine_tv_type (destination_type_t rom_destination_code)
|
||||
}
|
||||
|
||||
static void extract_rom_info (match_t *match, rom_header_t *rom_header, rom_info_t *rom_info) {
|
||||
rom_info->cic_type = cic_detect(rom_header->ipl3);
|
||||
rom_info->cic_type = detect_cic_type(rom_header->ipl3);
|
||||
|
||||
if (match->type == MATCH_TYPE_HOMEBREW_HEADER) {
|
||||
if (rom_header->version & (1 << 0)) {
|
||||
@ -746,63 +770,72 @@ static void extract_rom_info (match_t *match, rom_header_t *rom_header, rom_info
|
||||
}
|
||||
|
||||
static void load_overrides (path_t *path, rom_info_t *rom_info) {
|
||||
path_t *overrides_path = path_clone(path);
|
||||
|
||||
path_ext_replace(overrides_path, "ini");
|
||||
|
||||
mini_t *ini = mini_load(path_get(overrides_path));
|
||||
|
||||
rom_info->override.cic = false;
|
||||
rom_info->override.save = false;
|
||||
rom_info->override.tv = false;
|
||||
|
||||
path_t *overrides_path = path_clone(path);
|
||||
if (ini) {
|
||||
rom_info->override.cic_type = mini_get_int(ini, NULL, "cic_type", ROM_CIC_TYPE_AUTOMATIC);
|
||||
if (rom_info->override.cic_type != ROM_CIC_TYPE_AUTOMATIC) {
|
||||
rom_info->override.cic = true;
|
||||
}
|
||||
|
||||
path_ext_replace(overrides_path, "ini");
|
||||
rom_info->override.save_type = mini_get_int(ini, NULL, "save_type", SAVE_TYPE_AUTOMATIC);
|
||||
if (rom_info->override.save_type != SAVE_TYPE_AUTOMATIC) {
|
||||
rom_info->override.save = true;
|
||||
}
|
||||
|
||||
if (!file_exists(path_get(overrides_path))) {
|
||||
path_free(overrides_path);
|
||||
return;
|
||||
rom_info->override.tv_type = mini_get_int(ini, NULL, "tv_type", ROM_TV_TYPE_AUTOMATIC);
|
||||
if (rom_info->override.tv_type != ROM_TV_TYPE_AUTOMATIC) {
|
||||
rom_info->override.tv = true;
|
||||
}
|
||||
|
||||
mini_free(ini);
|
||||
}
|
||||
|
||||
mini_t *ini = mini_try_load(path_get(overrides_path));
|
||||
|
||||
rom_info->override.save_type = mini_get_int(ini, NULL, "save_type", SAVE_TYPE_AUTOMATIC);
|
||||
if (rom_info->override.save_type != SAVE_TYPE_AUTOMATIC) {
|
||||
rom_info->override.save = true;
|
||||
}
|
||||
|
||||
rom_info->override.tv_type = mini_get_int(ini, NULL, "tv_type", ROM_TV_TYPE_AUTOMATIC);
|
||||
if (rom_info->override.tv_type != ROM_TV_TYPE_AUTOMATIC) {
|
||||
rom_info->override.tv = true;
|
||||
}
|
||||
|
||||
mini_free(ini);
|
||||
|
||||
path_free(overrides_path);
|
||||
}
|
||||
|
||||
|
||||
rom_err_t rom_info_override_save_type (path_t *path, rom_info_t *rom_info, rom_save_type_t save_type) {
|
||||
static rom_err_t save_override (path_t *path, const char *id, int value, int default_value) {
|
||||
path_t *overrides_path = path_clone(path);
|
||||
|
||||
path_ext_replace(overrides_path, "ini");
|
||||
|
||||
mini_t *ini = mini_try_load(path_get(overrides_path));
|
||||
|
||||
rom_info->override.save_type = save_type;
|
||||
|
||||
if (rom_info->override.save_type == SAVE_TYPE_AUTOMATIC) {
|
||||
rom_info->override.save = false;
|
||||
mini_delete_value(ini, NULL, "save_type");
|
||||
} else {
|
||||
rom_info->override.save = true;
|
||||
mini_set_int(ini, NULL, "save_type", rom_info->override.save_type);
|
||||
if (!ini) {
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
|
||||
bool empty_override_file = mini_empty(ini);
|
||||
if (value == default_value) {
|
||||
mini_delete_value(ini, NULL, id);
|
||||
} else {
|
||||
mini_set_int(ini, NULL, id, value);
|
||||
}
|
||||
|
||||
if (!empty_override_file) {
|
||||
mini_save(ini, MINI_FLAGS_NONE);
|
||||
bool empty = mini_empty(ini);
|
||||
|
||||
if (!empty) {
|
||||
if (mini_save(ini, MINI_FLAGS_NONE) != MINI_OK) {
|
||||
path_free(overrides_path);
|
||||
mini_free(ini);
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
mini_free(ini);
|
||||
|
||||
if (empty_override_file) {
|
||||
file_delete(path_get(overrides_path));
|
||||
if (empty) {
|
||||
if (remove(path_get(overrides_path))) {
|
||||
path_free(overrides_path);
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
path_free(overrides_path);
|
||||
@ -810,38 +843,45 @@ rom_err_t rom_info_override_save_type (path_t *path, rom_info_t *rom_info, rom_s
|
||||
return ROM_OK;
|
||||
}
|
||||
|
||||
rom_err_t rom_info_override_tv_type (path_t *path, rom_info_t *rom_info, rom_tv_type_t tv_type) {
|
||||
path_t *overrides_path = path_clone(path);
|
||||
|
||||
path_ext_replace(overrides_path, "ini");
|
||||
|
||||
mini_t *ini = mini_try_load(path_get(overrides_path));
|
||||
|
||||
rom_info->override.tv_type = tv_type;
|
||||
|
||||
if (rom_info->override.tv_type == ROM_TV_TYPE_AUTOMATIC) {
|
||||
rom_info->override.tv = false;
|
||||
mini_delete_value(ini, NULL, "tv_type");
|
||||
rom_cic_type_t rom_info_get_cic_type (rom_info_t *rom_info) {
|
||||
if (rom_info->override.cic) {
|
||||
return rom_info->override.cic_type;
|
||||
} else {
|
||||
rom_info->override.tv = true;
|
||||
mini_set_int(ini, NULL, "tv_type", rom_info->override.tv_type);
|
||||
return rom_info->cic_type;
|
||||
}
|
||||
}
|
||||
|
||||
bool rom_info_get_cic_seed (rom_info_t *rom_info, uint8_t *seed) {
|
||||
cic_type_t cic_type;
|
||||
|
||||
switch (rom_info_get_cic_type(rom_info)) {
|
||||
case ROM_CIC_TYPE_5101: cic_type = CIC_5101; break;
|
||||
case ROM_CIC_TYPE_5167: cic_type = CIC_5167; break;
|
||||
case ROM_CIC_TYPE_6101: cic_type = CIC_6101; break;
|
||||
case ROM_CIC_TYPE_7102: cic_type = CIC_7102; break;
|
||||
case ROM_CIC_TYPE_x102: cic_type = CIC_x102; break;
|
||||
case ROM_CIC_TYPE_x103: cic_type = CIC_x103; break;
|
||||
case ROM_CIC_TYPE_x105: cic_type = CIC_x105; break;
|
||||
case ROM_CIC_TYPE_x106: cic_type = CIC_x106; break;
|
||||
case ROM_CIC_TYPE_8301: cic_type = CIC_8301; break;
|
||||
case ROM_CIC_TYPE_8302: cic_type = CIC_8302; break;
|
||||
case ROM_CIC_TYPE_8303: cic_type = CIC_8303; break;
|
||||
case ROM_CIC_TYPE_8401: cic_type = CIC_8401; break;
|
||||
case ROM_CIC_TYPE_8501: cic_type = CIC_8501; break;
|
||||
default: cic_type = CIC_UNKNOWN; break;
|
||||
}
|
||||
|
||||
bool empty_override_file = mini_empty(ini);
|
||||
*seed = cic_get_seed(cic_type);
|
||||
|
||||
if (!empty_override_file) {
|
||||
mini_save(ini, MINI_FLAGS_NONE);
|
||||
}
|
||||
return (!rom_info->override.cic);
|
||||
}
|
||||
|
||||
mini_free(ini);
|
||||
rom_err_t rom_info_override_cic_type (path_t *path, rom_info_t *rom_info, rom_cic_type_t cic_type) {
|
||||
rom_info->override.cic = (cic_type != ROM_CIC_TYPE_AUTOMATIC);
|
||||
rom_info->override.cic_type = cic_type;
|
||||
|
||||
if (empty_override_file) {
|
||||
file_delete(path_get(overrides_path));
|
||||
}
|
||||
|
||||
path_free(overrides_path);
|
||||
|
||||
return ROM_OK;
|
||||
return save_override(path, "cic_type", rom_info->override.cic_type, ROM_CIC_TYPE_AUTOMATIC);
|
||||
}
|
||||
|
||||
rom_save_type_t rom_info_get_save_type (rom_info_t *rom_info) {
|
||||
@ -852,6 +892,13 @@ rom_save_type_t rom_info_get_save_type (rom_info_t *rom_info) {
|
||||
}
|
||||
}
|
||||
|
||||
rom_err_t rom_info_override_save_type (path_t *path, rom_info_t *rom_info, rom_save_type_t save_type) {
|
||||
rom_info->override.save = (save_type != SAVE_TYPE_AUTOMATIC);
|
||||
rom_info->override.save_type = save_type;
|
||||
|
||||
return save_override(path, "save_type", rom_info->override.save_type, SAVE_TYPE_AUTOMATIC);
|
||||
}
|
||||
|
||||
rom_tv_type_t rom_info_get_tv_type (rom_info_t *rom_info) {
|
||||
if (rom_info->override.tv) {
|
||||
return rom_info->override.tv_type;
|
||||
@ -860,22 +907,26 @@ rom_tv_type_t rom_info_get_tv_type (rom_info_t *rom_info) {
|
||||
}
|
||||
}
|
||||
|
||||
rom_err_t rom_info_override_tv_type (path_t *path, rom_info_t *rom_info, rom_tv_type_t tv_type) {
|
||||
rom_info->override.tv = (tv_type != ROM_TV_TYPE_AUTOMATIC);
|
||||
rom_info->override.tv_type = tv_type;
|
||||
|
||||
return save_override(path, "tv_type", rom_info->override.tv_type, ROM_TV_TYPE_AUTOMATIC);
|
||||
}
|
||||
|
||||
rom_err_t rom_info_load (path_t *path, rom_info_t *rom_info) {
|
||||
FIL fil;
|
||||
UINT br;
|
||||
FILE *f;
|
||||
rom_header_t rom_header;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(path_get(path)), FA_READ) != FR_OK) {
|
||||
if ((f = fopen(path_get(path), "rb")) == NULL) {
|
||||
return ROM_ERR_NO_FILE;
|
||||
}
|
||||
if (f_read(&fil, &rom_header, sizeof(rom_header), &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
setbuf(f, NULL);
|
||||
if (fread(&rom_header, sizeof(rom_header), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
if (br != sizeof(rom_header)) {
|
||||
if (fclose(f)) {
|
||||
return ROM_ERR_IO;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file rom_info.h
|
||||
* @brief N64 ROM Database.
|
||||
* @note Only works with N64 ROM's by checking the first 1024 bytes of the file.
|
||||
* @note Only works with N64 ROM's by checking the first 4096 bytes of the file.
|
||||
* @ingroup menu
|
||||
*/
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "boot/cic.h"
|
||||
#include "path.h"
|
||||
|
||||
|
||||
@ -31,7 +30,7 @@ typedef enum {
|
||||
ENDIANNESS_LITTLE,
|
||||
/** @brief Is Byte Swapped Endian. */
|
||||
ENDIANNESS_BYTE_SWAP,
|
||||
} endianness_t;
|
||||
} rom_endianness_t;
|
||||
|
||||
/** @brief ROM media type enumeration. */
|
||||
typedef enum {
|
||||
@ -45,7 +44,7 @@ typedef enum {
|
||||
N64_DISK_EXPANDABLE = 'E',
|
||||
/** @brief Is an Aleck64 program. */
|
||||
N64_ALECK64 = 'Z'
|
||||
} category_type_t;
|
||||
} rom_category_type_t;
|
||||
|
||||
/** @brief ROM market region & language type enumeration. */
|
||||
typedef enum {
|
||||
@ -89,27 +88,46 @@ typedef enum {
|
||||
MARKET_OTHER_Y = 'Y', // many EU ROM's uses this.
|
||||
/** @brief The ROM is designed for an undefined region and TBD language(s). */
|
||||
MARKET_OTHER_Z = 'Z' // no known ROM's use this.
|
||||
} destination_type_t;
|
||||
} rom_destination_type_t;
|
||||
|
||||
/** @brief ROM CIC type enumeration. */
|
||||
typedef enum {
|
||||
ROM_CIC_TYPE_UNKNOWN = 0, // No known CIC type detected
|
||||
ROM_CIC_TYPE_5101 = 5101, // Aleck64 CIC-5101
|
||||
ROM_CIC_TYPE_5167 = 5167, // 64DD ROM conversion CIC-5167
|
||||
ROM_CIC_TYPE_6101 = 6101, // NTSC CIC-6101
|
||||
ROM_CIC_TYPE_7102 = 7102, // PAL CIC-7102
|
||||
ROM_CIC_TYPE_x102 = 6102, // NTSC CIC-6102 / PAL CIC-7101
|
||||
ROM_CIC_TYPE_x103 = 6103, // NTSC CIC-6103 / PAL CIC-7103
|
||||
ROM_CIC_TYPE_x105 = 6105, // NTSC CIC-6105 / PAL CIC-7105
|
||||
ROM_CIC_TYPE_x106 = 6106, // NTSC CIC-6106 / PAL CIC-7106
|
||||
ROM_CIC_TYPE_8301 = 8301, // NDDJ0 64DD IPL
|
||||
ROM_CIC_TYPE_8302 = 8302, // NDDJ1 64DD IPL
|
||||
ROM_CIC_TYPE_8303 = 8303, // NDDJ2 64DD IPL
|
||||
ROM_CIC_TYPE_8401 = 8401, // NDXJ0 64DD IPL
|
||||
ROM_CIC_TYPE_8501 = 8501, // NDDE0 64DD IPL
|
||||
ROM_CIC_TYPE_AUTOMATIC = -1, // Guess CIC from IPL3
|
||||
} rom_cic_type_t;
|
||||
|
||||
/** @brief ROM save type enumeration. */
|
||||
typedef enum {
|
||||
/** @brief There is no expected save type. */
|
||||
SAVE_TYPE_NONE,
|
||||
SAVE_TYPE_EEPROM_4K,
|
||||
SAVE_TYPE_EEPROM_16K,
|
||||
SAVE_TYPE_SRAM,
|
||||
SAVE_TYPE_SRAM_BANKED,
|
||||
SAVE_TYPE_SRAM_128K,
|
||||
SAVE_TYPE_FLASHRAM,
|
||||
SAVE_TYPE_FLASHRAM_PKST2,
|
||||
SAVE_TYPE_NONE = 0,
|
||||
SAVE_TYPE_EEPROM_4K = 1,
|
||||
SAVE_TYPE_EEPROM_16K = 2,
|
||||
SAVE_TYPE_SRAM = 3,
|
||||
SAVE_TYPE_SRAM_BANKED = 4,
|
||||
SAVE_TYPE_SRAM_128K = 5,
|
||||
SAVE_TYPE_FLASHRAM = 6,
|
||||
SAVE_TYPE_FLASHRAM_PKST2 = 7,
|
||||
SAVE_TYPE_AUTOMATIC = -1,
|
||||
} rom_save_type_t;
|
||||
|
||||
typedef enum {
|
||||
ROM_TV_TYPE_PAL,
|
||||
ROM_TV_TYPE_NTSC,
|
||||
ROM_TV_TYPE_MPAL,
|
||||
ROM_TV_TYPE_UNKNOWN,
|
||||
ROM_TV_TYPE_PAL = 0,
|
||||
ROM_TV_TYPE_NTSC = 1,
|
||||
ROM_TV_TYPE_MPAL = 2,
|
||||
ROM_TV_TYPE_UNKNOWN = 3,
|
||||
ROM_TV_TYPE_AUTOMATIC = -1,
|
||||
} rom_tv_type_t;
|
||||
|
||||
@ -129,49 +147,62 @@ typedef enum {
|
||||
|
||||
/** @brief The ROM is faulty when using 8MB of memory. */
|
||||
EXPANSION_PAK_FAULTY,
|
||||
} expansion_pak_t;
|
||||
} rom_expansion_pak_t;
|
||||
|
||||
/** @brief ROM Information Structure. */
|
||||
typedef struct {
|
||||
/** @brief The file endian. */
|
||||
endianness_t endianness;
|
||||
rom_endianness_t endianness;
|
||||
|
||||
/** @brief The clock rate defined in the ROM's header. */
|
||||
float clock_rate;
|
||||
|
||||
/** @brief The boot address defined in the ROM's header. */
|
||||
uint32_t boot_address;
|
||||
|
||||
struct {
|
||||
/** @brief The SDK version defined in the ROM's header. */
|
||||
uint8_t version;
|
||||
/** @brief The SDK revision defined in the ROM's header. */
|
||||
char revision;
|
||||
} libultra;
|
||||
|
||||
/** @brief The check code defined in the ROM's header. */
|
||||
uint64_t check_code;
|
||||
|
||||
/** @brief The title defined in the ROM's header. */
|
||||
char title[20];
|
||||
|
||||
union {
|
||||
/** @brief The game code defined in the ROM's header. */
|
||||
char game_code[4];
|
||||
struct {
|
||||
/** @brief The game media type. */
|
||||
category_type_t category_code : 8;
|
||||
rom_category_type_t category_code : 8;
|
||||
/** @brief The game unique identifier. */
|
||||
char unique_code[2];
|
||||
/** @brief The game region and or market. */
|
||||
destination_type_t destination_code : 8;
|
||||
rom_destination_type_t destination_code : 8;
|
||||
};
|
||||
};
|
||||
|
||||
/** @brief The ROM version defined in the ROM's header. */
|
||||
uint8_t version;
|
||||
|
||||
cic_type_t cic_type;
|
||||
/** @brief The CIC type required by the ROM. */
|
||||
rom_cic_type_t cic_type;
|
||||
|
||||
/** @brief The save type required by the ROM. */
|
||||
rom_save_type_t save_type;
|
||||
|
||||
/** @brief The TV type required by the ROM. */
|
||||
rom_tv_type_t tv_type;
|
||||
|
||||
/** @brief Overrides of auto-detected CIC/save/TV types. */
|
||||
struct {
|
||||
bool cic;
|
||||
rom_cic_type_t cic_type;
|
||||
|
||||
bool save;
|
||||
rom_save_type_t save_type;
|
||||
|
||||
@ -188,15 +219,21 @@ typedef struct {
|
||||
bool real_time_clock;
|
||||
bool disk_conversion;
|
||||
bool combo_rom_disk_game;
|
||||
expansion_pak_t expansion_pak;
|
||||
rom_expansion_pak_t expansion_pak;
|
||||
} features;
|
||||
} rom_info_t;
|
||||
|
||||
|
||||
rom_err_t rom_info_override_save_type (path_t *path, rom_info_t *rom_info, rom_save_type_t save_type);
|
||||
rom_err_t rom_info_override_tv_type (path_t *path, rom_info_t *rom_info, rom_tv_type_t tv_type);
|
||||
rom_cic_type_t rom_info_get_cic_type (rom_info_t *rom_info);
|
||||
bool rom_info_get_cic_seed (rom_info_t *rom_info, uint8_t *seed);
|
||||
rom_err_t rom_info_override_cic_type (path_t *path, rom_info_t *rom_info, rom_cic_type_t cic_type);
|
||||
|
||||
rom_save_type_t rom_info_get_save_type (rom_info_t *rom_info);
|
||||
rom_err_t rom_info_override_save_type (path_t *path, rom_info_t *rom_info, rom_save_type_t save_type);
|
||||
|
||||
rom_tv_type_t rom_info_get_tv_type (rom_info_t *rom_info);
|
||||
rom_err_t rom_info_override_tv_type (path_t *path, rom_info_t *rom_info, rom_tv_type_t tv_type);
|
||||
|
||||
rom_err_t rom_info_load (path_t *path, rom_info_t *rom_info);
|
||||
|
||||
|
||||
|
@ -5,14 +5,12 @@
|
||||
#include "utils/fs.h"
|
||||
|
||||
|
||||
#ifndef SETTINGS_FILE_PATH
|
||||
#define SETTINGS_FILE_PATH "sd:/menu/config.ini"
|
||||
#endif
|
||||
static char *settings_path = NULL;
|
||||
|
||||
|
||||
static settings_t init = {
|
||||
.pal60_enabled = false,
|
||||
.hidden_files_enabled = false,
|
||||
.show_protected_entries = false,
|
||||
.default_directory = "/",
|
||||
.use_saves_folder = true,
|
||||
|
||||
@ -23,15 +21,22 @@ static settings_t init = {
|
||||
};
|
||||
|
||||
|
||||
void settings_init (char *path) {
|
||||
if (settings_path) {
|
||||
free(settings_path);
|
||||
}
|
||||
settings_path = strdup(path);
|
||||
}
|
||||
|
||||
void settings_load (settings_t *settings) {
|
||||
if (!file_exists(SETTINGS_FILE_PATH)) {
|
||||
if (!file_exists(settings_path)) {
|
||||
settings_save(&init);
|
||||
}
|
||||
|
||||
mini_t *ini = mini_try_load(SETTINGS_FILE_PATH);
|
||||
mini_t *ini = mini_try_load(settings_path);
|
||||
|
||||
settings->pal60_enabled = mini_get_bool(ini, "menu", "pal60", init.pal60_enabled); // TODO: consider changing file setting name
|
||||
settings->hidden_files_enabled = mini_get_bool(ini, "menu", "show_hidden_files", init.hidden_files_enabled);
|
||||
settings->show_protected_entries = mini_get_bool(ini, "menu", "show_protected_entries", init.show_protected_entries);
|
||||
settings->default_directory = strdup(mini_get_string(ini, "menu", "default_directory", init.default_directory));
|
||||
settings->use_saves_folder = mini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder);
|
||||
|
||||
@ -44,10 +49,10 @@ void settings_load (settings_t *settings) {
|
||||
}
|
||||
|
||||
void settings_save (settings_t *settings) {
|
||||
mini_t *ini = mini_create(SETTINGS_FILE_PATH);
|
||||
mini_t *ini = mini_create(settings_path);
|
||||
|
||||
mini_set_bool(ini, "menu", "pal60", settings->pal60_enabled);
|
||||
mini_set_bool(ini, "menu", "show_hidden_files", settings->hidden_files_enabled);
|
||||
mini_set_bool(ini, "menu", "show_protected_entries", settings->show_protected_entries);
|
||||
mini_set_string(ini, "menu", "default_directory", settings->default_directory);
|
||||
mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder);
|
||||
|
||||
|
@ -13,8 +13,8 @@ typedef struct {
|
||||
/** @brief Use 60 Hz refresh rate on a PAL console */
|
||||
bool pal60_enabled;
|
||||
|
||||
/** @brief Show files marked as hidden in the browser */
|
||||
bool hidden_files_enabled;
|
||||
/** @brief Show files/directories that are filtered in the browser */
|
||||
bool show_protected_entries;
|
||||
|
||||
/** @brief Default directory to navigate to when menu loads */
|
||||
char *default_directory;
|
||||
@ -33,6 +33,8 @@ typedef struct {
|
||||
} settings_t;
|
||||
|
||||
|
||||
/** @brief Init settings path */
|
||||
void settings_init (char *path);
|
||||
/** @brief The settings to load */
|
||||
void settings_load (settings_t *settings);
|
||||
/** @brief The settings to save */
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
#define DEFAULT_FREQUENCY (44100)
|
||||
#define NUM_BUFFERS (2)
|
||||
#define NUM_BUFFERS (4)
|
||||
#define NUM_CHANNELS (2)
|
||||
|
||||
|
||||
|
@ -2,8 +2,9 @@
|
||||
// Main use of these functions is to aid menu development
|
||||
// (for example replace files on the SD card or reboot menu).
|
||||
|
||||
#include <fatfs/ff.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <usb.h>
|
||||
|
||||
#include "usb_comm.h"
|
||||
@ -12,10 +13,12 @@
|
||||
|
||||
#define MAX_FILE_SIZE MiB(4)
|
||||
|
||||
|
||||
/** @brief The supported USB commands structure. */
|
||||
typedef struct {
|
||||
/** @brief The command identifier. */
|
||||
const char *id;
|
||||
|
||||
/** @brief The command operation. */
|
||||
void (*op) (menu_t *menu);
|
||||
} usb_comm_command_t;
|
||||
@ -71,15 +74,12 @@ static void command_reboot (menu_t *menu) {
|
||||
};
|
||||
|
||||
static void command_send_file (menu_t *menu) {
|
||||
char path[256];
|
||||
FILE *f;
|
||||
char buffer[256];
|
||||
uint8_t data[8192];
|
||||
char length[8];
|
||||
|
||||
FIL f;
|
||||
int remaining;
|
||||
uint8_t data[8192];
|
||||
UINT bytes_written;
|
||||
|
||||
if (usb_comm_read_string(path, sizeof(path), ' ')) {
|
||||
if (usb_comm_read_string(buffer, sizeof(buffer), ' ')) {
|
||||
return usb_comm_send_error("Invalid path argument\n");
|
||||
}
|
||||
|
||||
@ -91,11 +91,16 @@ static void command_send_file (menu_t *menu) {
|
||||
return usb_comm_send_error("Invalid file length argument\n");
|
||||
}
|
||||
|
||||
if (f_open(&f, path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) {
|
||||
path_t *path = path_init(menu->storage_prefix, buffer);
|
||||
|
||||
if ((f = fopen(path_get(path), "wb")) == NULL) {
|
||||
path_free(path);
|
||||
return usb_comm_send_error("Couldn't create file\n");
|
||||
}
|
||||
setbuf(f, NULL);
|
||||
path_free(path);
|
||||
|
||||
remaining = atoi(length);
|
||||
int remaining = atoi(length);
|
||||
|
||||
if (remaining > MAX_FILE_SIZE) {
|
||||
return usb_comm_send_error("File size too big\n");
|
||||
@ -104,18 +109,14 @@ static void command_send_file (menu_t *menu) {
|
||||
while (remaining > 0) {
|
||||
int block_size = MIN(remaining, sizeof(data));
|
||||
usb_read(data, block_size);
|
||||
if (f_write(&f, data, block_size, &bytes_written) != FR_OK) {
|
||||
f_close(&f);
|
||||
return usb_comm_send_error("Couldn't write data to the file\n");
|
||||
}
|
||||
if (bytes_written != block_size) {
|
||||
f_close(&f);
|
||||
if (fwrite(data, 1, block_size, f) != block_size) {
|
||||
fclose(f);
|
||||
return usb_comm_send_error("Couldn't write all required data to the file\n");
|
||||
}
|
||||
remaining -= block_size;
|
||||
}
|
||||
|
||||
if (f_close(&f) != FR_OK) {
|
||||
if (fclose(f)) {
|
||||
return usb_comm_send_error("Couldn't flush data to the file\n");
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <fatfs/ff.h>
|
||||
|
||||
#include "../fonts.h"
|
||||
#include "utils/fs.h"
|
||||
#include "views.h"
|
||||
@ -12,10 +11,23 @@
|
||||
static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
|
||||
static const char *disk_extensions[] = { "ndd", NULL };
|
||||
static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", NULL };
|
||||
static const char *save_extensions[] = { "sav", NULL }; // TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts.
|
||||
// TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts.
|
||||
static const char *save_extensions[] = { "sav", NULL };
|
||||
static const char *image_extensions[] = { "png", NULL };
|
||||
static const char *text_extensions[] = { "txt", "ini", "yml", "yaml", NULL };
|
||||
static const char *music_extensions[] = { "mp3", NULL };
|
||||
|
||||
static const char *hidden_paths[] = {
|
||||
"/menu.bin",
|
||||
"/menu",
|
||||
"/N64FlashcartMenu.n64",
|
||||
"/OS64.v64",
|
||||
"/OS64P.v64",
|
||||
"/sc64menu.n64",
|
||||
"/System Volume Information",
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
||||
static int compare_entry (const void *pa, const void *pb) {
|
||||
entry_t *a = (entry_t *) (pa);
|
||||
@ -50,78 +62,97 @@ static int compare_entry (const void *pa, const void *pb) {
|
||||
return -1;
|
||||
} else if (b->type == ENTRY_TYPE_MUSIC) {
|
||||
return 1;
|
||||
} else if (a->type == ENTRY_TYPE_TEXT) {
|
||||
return -1;
|
||||
} else if (b->type == ENTRY_TYPE_TEXT) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return strcasecmp((const char *) (a->name), (const char *) (b->name));
|
||||
}
|
||||
|
||||
static bool load_directory (menu_t *menu) {
|
||||
DIR dir;
|
||||
FILINFO info;
|
||||
|
||||
static void browser_list_free (menu_t *menu) {
|
||||
for (int i = menu->browser.entries - 1; i >= 0; i--) {
|
||||
free(menu->browser.list[i].name);
|
||||
}
|
||||
|
||||
free(menu->browser.list);
|
||||
|
||||
menu->browser.list = NULL;
|
||||
menu->browser.entries = 0;
|
||||
menu->browser.selected = -1;
|
||||
menu->browser.entry = NULL;
|
||||
menu->browser.selected = -1;
|
||||
}
|
||||
|
||||
if (f_opendir(&dir, strip_sd_prefix(path_get(menu->browser.directory))) != FR_OK) {
|
||||
return true;
|
||||
static bool load_directory (menu_t *menu) {
|
||||
int result;
|
||||
dir_t info;
|
||||
|
||||
browser_list_free(menu);
|
||||
|
||||
path_t *path = path_clone(menu->browser.directory);
|
||||
|
||||
result = dir_findfirst(path_get(path), &info);
|
||||
|
||||
while (result == 0) {
|
||||
bool hide = false;
|
||||
|
||||
if (!menu->settings.show_protected_entries) {
|
||||
path_push(path, info.d_name);
|
||||
|
||||
for (int i = 0; hidden_paths[i] != NULL; i++) {
|
||||
if (strcmp(strip_fs_prefix(path_get(path)), hidden_paths[i]) == 0) {
|
||||
hide = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
path_pop(path);
|
||||
}
|
||||
|
||||
if (!hide) {
|
||||
menu->browser.list = realloc(menu->browser.list, (menu->browser.entries + 1) * sizeof(entry_t));
|
||||
|
||||
entry_t *entry = &menu->browser.list[menu->browser.entries++];
|
||||
|
||||
entry->name = strdup(info.d_name);
|
||||
if (!entry->name) {
|
||||
path_free(path);
|
||||
browser_list_free(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (info.d_type == DT_DIR) {
|
||||
entry->type = ENTRY_TYPE_DIR;
|
||||
} else if (file_has_extensions(entry->name, rom_extensions)) {
|
||||
entry->type = ENTRY_TYPE_ROM;
|
||||
} else if (file_has_extensions(entry->name, disk_extensions)) {
|
||||
entry->type = ENTRY_TYPE_DISK;
|
||||
}else if (file_has_extensions(entry->name, emulator_extensions)) {
|
||||
entry->type = ENTRY_TYPE_EMULATOR;
|
||||
} else if (file_has_extensions(entry->name, save_extensions)) {
|
||||
entry->type = ENTRY_TYPE_SAVE;
|
||||
} else if (file_has_extensions(entry->name, image_extensions)) {
|
||||
entry->type = ENTRY_TYPE_IMAGE;
|
||||
} else if (file_has_extensions(entry->name, text_extensions)) {
|
||||
entry->type = ENTRY_TYPE_TEXT;
|
||||
} else if (file_has_extensions(entry->name, music_extensions)) {
|
||||
entry->type = ENTRY_TYPE_MUSIC;
|
||||
} else {
|
||||
entry->type = ENTRY_TYPE_OTHER;
|
||||
}
|
||||
|
||||
entry->size = info.d_size;
|
||||
}
|
||||
|
||||
result = dir_findnext(path_get(path), &info);
|
||||
}
|
||||
|
||||
while (menu->browser.entries < BROWSER_LIST_SIZE) {
|
||||
if (f_readdir(&dir, &info) != FR_OK) {
|
||||
return true;
|
||||
}
|
||||
path_free(path);
|
||||
|
||||
size_t length = strlen(info.fname);
|
||||
|
||||
if (length == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (info.fattrib & AM_SYS) {
|
||||
continue;
|
||||
}
|
||||
if ((info.fattrib & AM_HID) && !menu->settings.hidden_files_enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry_t *entry = &menu->browser.list[menu->browser.entries];
|
||||
|
||||
entry->name = strdup(info.fname);
|
||||
if (!entry->name) {
|
||||
f_closedir(&dir);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (info.fattrib & AM_DIR) {
|
||||
entry->type = ENTRY_TYPE_DIR;
|
||||
} else if (file_has_extensions(info.fname, rom_extensions)) {
|
||||
entry->type = ENTRY_TYPE_ROM;
|
||||
} else if (file_has_extensions(info.fname, disk_extensions)) {
|
||||
entry->type = ENTRY_TYPE_DISK;
|
||||
}else if (file_has_extensions(info.fname, emulator_extensions)) {
|
||||
entry->type = ENTRY_TYPE_EMULATOR;
|
||||
} else if (file_has_extensions(info.fname, save_extensions)) {
|
||||
entry->type = ENTRY_TYPE_SAVE;
|
||||
} else if (file_has_extensions(info.fname, image_extensions)) {
|
||||
entry->type = ENTRY_TYPE_IMAGE;
|
||||
} else if (file_has_extensions(info.fname, music_extensions)) {
|
||||
entry->type = ENTRY_TYPE_MUSIC;
|
||||
} else {
|
||||
entry->type = ENTRY_TYPE_OTHER;
|
||||
}
|
||||
|
||||
entry->size = info.fsize;
|
||||
|
||||
menu->browser.entries += 1;
|
||||
}
|
||||
|
||||
if (f_closedir(&dir) != FR_OK) {
|
||||
if (result < -1) {
|
||||
browser_list_free(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -135,6 +166,22 @@ static bool load_directory (menu_t *menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool reload_directory (menu_t *menu) {
|
||||
int selected = menu->browser.selected;
|
||||
|
||||
if (load_directory(menu)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
menu->browser.selected = selected;
|
||||
if (menu->browser.selected >= menu->browser.entries) {
|
||||
menu->browser.selected = menu->browser.entries - 1;
|
||||
}
|
||||
menu->browser.entry = menu->browser.selected >= 0 ? &menu->browser.list[menu->browser.selected] : NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool push_directory (menu_t *menu, char *directory) {
|
||||
path_t *previous_directory = path_clone(menu->browser.directory);
|
||||
|
||||
@ -175,47 +222,35 @@ static bool pop_directory (menu_t *menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void show_properties (menu_t *menu) {
|
||||
static void show_properties (menu_t *menu, void *arg) {
|
||||
menu->next_mode = MENU_MODE_FILE_INFO;
|
||||
}
|
||||
|
||||
static void delete_entry (menu_t *menu) {
|
||||
int selected = menu->browser.selected;
|
||||
|
||||
static void delete_entry (menu_t *menu, void *arg) {
|
||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
|
||||
if (menu->browser.entry->type == ENTRY_TYPE_DIR) {
|
||||
if (directory_delete(path_get(path))) {
|
||||
if (remove(path_get(path))) {
|
||||
menu->browser.valid = false;
|
||||
if (menu->browser.entry->type == ENTRY_TYPE_DIR) {
|
||||
menu_show_error(menu, "Couldn't delete directory\nDirectory might not be empty");
|
||||
path_free(path);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (file_delete(path_get(path))) {
|
||||
} else {
|
||||
menu_show_error(menu, "Couldn't delete file");
|
||||
path_free(path);
|
||||
return;
|
||||
}
|
||||
path_free(path);
|
||||
return;
|
||||
}
|
||||
|
||||
path_free(path);
|
||||
|
||||
if (load_directory(menu)) {
|
||||
if (reload_directory(menu)) {
|
||||
menu->browser.valid = false;
|
||||
menu_show_error(menu, "Couldn't refresh directory contents after delete operation");
|
||||
return;
|
||||
}
|
||||
|
||||
menu->browser.selected = selected;
|
||||
if (menu->browser.selected >= menu->browser.entries) {
|
||||
menu->browser.selected = menu->browser.entries - 1;
|
||||
}
|
||||
menu->browser.entry = menu->browser.selected >= 0 ? &menu->browser.list[menu->browser.selected] : NULL;
|
||||
}
|
||||
|
||||
static void set_default_directory (menu_t *menu) {
|
||||
static void set_default_directory (menu_t *menu, void *arg) {
|
||||
free(menu->settings.default_directory);
|
||||
menu->settings.default_directory = strdup(strip_sd_prefix(path_get(menu->browser.directory)));
|
||||
menu->settings.default_directory = strdup(strip_fs_prefix(path_get(menu->browser.directory)));
|
||||
settings_save(&menu->settings);
|
||||
}
|
||||
|
||||
@ -228,28 +263,18 @@ static component_context_menu_t entry_context_menu = {
|
||||
}
|
||||
};
|
||||
|
||||
static void edit_settings (menu_t *menu) {
|
||||
menu->next_mode = MENU_MODE_SETTINGS_EDITOR;
|
||||
}
|
||||
|
||||
static void show_system_info (menu_t *menu) {
|
||||
menu->next_mode = MENU_MODE_SYSTEM_INFO;
|
||||
}
|
||||
|
||||
static void show_credits (menu_t *menu) {
|
||||
menu->next_mode = MENU_MODE_CREDITS;
|
||||
}
|
||||
|
||||
static void edit_rtc (menu_t *menu) {
|
||||
menu->next_mode = MENU_MODE_RTC;
|
||||
static void set_menu_next_mode (menu_t *menu, void *arg) {
|
||||
menu_mode_t next_mode = (menu_mode_t) (arg);
|
||||
menu->next_mode = next_mode;
|
||||
}
|
||||
|
||||
static component_context_menu_t settings_context_menu = {
|
||||
.list = {
|
||||
{ .text = "Edit settings", .action = edit_settings },
|
||||
{ .text = "Show system info", .action = show_system_info },
|
||||
{ .text = "Show credits", .action = show_credits },
|
||||
{ .text = "Adjust RTC", .action = edit_rtc },
|
||||
{ .text = "Edit settings", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_SETTINGS_EDITOR) },
|
||||
{ .text = "Show system info", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_SYSTEM_INFO) },
|
||||
{ .text = "Show credits", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_CREDITS) },
|
||||
{ .text = "Adjust RTC", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_RTC) },
|
||||
{ .text = "Show cart info", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_FLASHCART) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}
|
||||
};
|
||||
@ -300,6 +325,9 @@ static void process (menu_t *menu) {
|
||||
case ENTRY_TYPE_IMAGE:
|
||||
menu->next_mode = MENU_MODE_IMAGE_VIEWER;
|
||||
break;
|
||||
case ENTRY_TYPE_TEXT:
|
||||
menu->next_mode = MENU_MODE_TEXT_VIEWER;
|
||||
break;
|
||||
case ENTRY_TYPE_MUSIC:
|
||||
menu->next_mode = MENU_MODE_MUSIC_PLAYER;
|
||||
break;
|
||||
@ -337,6 +365,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
case ENTRY_TYPE_ROM: action = "A: Load"; break;
|
||||
case ENTRY_TYPE_DISK: action = "A: Load"; break;
|
||||
case ENTRY_TYPE_IMAGE: action = "A: Show"; break;
|
||||
case ENTRY_TYPE_TEXT: action = "A: View"; break;
|
||||
case ENTRY_TYPE_MUSIC: action = "A: Play"; break;
|
||||
default: action = "A: Info"; break;
|
||||
}
|
||||
@ -347,14 +376,14 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"%s\n"
|
||||
"^%02XB: Back^00",
|
||||
menu->browser.entries == 0 ? "" : action,
|
||||
path_is_root(menu->browser.directory) ? STL_UNKNOWN : STL_DEFAULT
|
||||
path_is_root(menu->browser.directory) ? STL_GRAY : STL_DEFAULT
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_RIGHT, VALIGN_TOP,
|
||||
"Start: Settings\n"
|
||||
"^%02XR: Options^00",
|
||||
menu->browser.entries == 0 ? STL_UNKNOWN : STL_DEFAULT
|
||||
menu->browser.entries == 0 ? STL_GRAY : STL_DEFAULT
|
||||
);
|
||||
|
||||
if (menu->current_time >= 0) {
|
||||
@ -380,24 +409,18 @@ void view_browser_init (menu_t *menu) {
|
||||
component_context_menu_init(&settings_context_menu);
|
||||
if (load_directory(menu)) {
|
||||
path_free(menu->browser.directory);
|
||||
menu->browser.directory = path_init("sd:/", "");
|
||||
menu->browser.directory = path_init(menu->storage_prefix, "");
|
||||
menu_show_error(menu, "Error while opening initial directory");
|
||||
} else {
|
||||
menu->browser.valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (menu->browser.reload) {
|
||||
menu->browser.reload = false;
|
||||
int selected = menu->browser.selected;
|
||||
if (load_directory(menu)) {
|
||||
if (reload_directory(menu)) {
|
||||
menu_show_error(menu, "Error while reloading current directory");
|
||||
menu->browser.valid = false;
|
||||
} else {
|
||||
menu->browser.selected = selected;
|
||||
if (menu->browser.selected >= menu->browser.entries) {
|
||||
menu->browser.selected = menu->browser.entries - 1;
|
||||
}
|
||||
menu->browser.entry = menu->browser.selected >= 0 ? &menu->browser.list[menu->browser.selected] : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,11 @@
|
||||
|
||||
|
||||
#ifndef MENU_VERSION
|
||||
#define MENU_VERSION "0.0.0.6.ALPHA"
|
||||
#define MENU_VERSION "Unknown"
|
||||
#endif
|
||||
|
||||
#ifndef BUILD_TIMESTAMP
|
||||
#define BUILD_TIMESTAMP "Unknown"
|
||||
#endif
|
||||
|
||||
|
||||
@ -28,7 +32,8 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
"\n"
|
||||
"Menu Revision: V%s\n"
|
||||
"Menu version: %s\n"
|
||||
"Build timestamp: %s\n"
|
||||
"\n"
|
||||
"Github:\n"
|
||||
" https://github.com/Polprzewodnikowy/N64FlashcartMenu\n"
|
||||
@ -44,7 +49,8 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
" mini.c (BSD 2-Clause License)\n"
|
||||
" minimp3 (CC0 1.0 Universal)\n"
|
||||
" miniz (MIT License)",
|
||||
MENU_VERSION
|
||||
MENU_VERSION,
|
||||
BUILD_TIMESTAMP
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
|
@ -50,5 +50,4 @@ void view_error_display (menu_t *menu, surface_t *display) {
|
||||
void menu_show_error (menu_t *menu, char *error_message) {
|
||||
menu->next_mode = MENU_MODE_ERROR;
|
||||
menu->error_message = error_message;
|
||||
menu->browser.valid = false;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include <fatfs/ff.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "utils/fs.h"
|
||||
#include "views.h"
|
||||
@ -16,7 +16,7 @@ static const char *dexdrive_extensions[] = { "mpk", NULL };
|
||||
static const char *emulator_extensions[] = { "emu", NULL };
|
||||
|
||||
|
||||
static FILINFO info;
|
||||
static struct stat st;
|
||||
|
||||
|
||||
static char *format_file_type (char *name, bool is_directory) {
|
||||
@ -75,17 +75,14 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"\n"
|
||||
"\n"
|
||||
" Size: %d bytes\n"
|
||||
" Attributes: %s%s%s%s%s\n"
|
||||
" Attributes: %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
|
||||
" Modified: %s",
|
||||
st.st_size,
|
||||
S_ISDIR(st.st_mode) ? "Directory" : "File",
|
||||
st.st_mode & S_IWUSR ? "" : "(Read only)",
|
||||
format_file_type(menu->browser.entry->name, S_ISDIR(st.st_mode)),
|
||||
ctime(&st.st_mtim.tv_sec)
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
@ -101,7 +98,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
void view_file_info_init (menu_t *menu) {
|
||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
|
||||
if (f_stat(strip_sd_prefix(path_get(path)), &info) != FR_OK) {
|
||||
if (stat(path_get(path), &st)) {
|
||||
menu_show_error(menu, "Couldn't obtain file information");
|
||||
}
|
||||
|
||||
|
46
src/menu/views/flashcart_info.c
Normal file
46
src/menu/views/flashcart_info.c
Normal file
@ -0,0 +1,46 @@
|
||||
#include "views.h"
|
||||
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
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();
|
||||
|
||||
component_layout_draw();
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_CENTER, VALIGN_TOP,
|
||||
"FLASHCART INFORMATION\n"
|
||||
);
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
"B: Back"
|
||||
);
|
||||
|
||||
rdpq_detach_show();
|
||||
}
|
||||
|
||||
|
||||
void view_flashcart_info_init (menu_t *menu) {
|
||||
// Nothing to initialize (yet)
|
||||
}
|
||||
|
||||
void view_flashcart_info_display (menu_t *menu, surface_t *display) {
|
||||
process(menu);
|
||||
draw(menu, display);
|
||||
}
|
@ -126,9 +126,21 @@ static void load (menu_t *menu) {
|
||||
}
|
||||
|
||||
menu->next_mode = MENU_MODE_BOOT;
|
||||
menu->boot_params->device_type = load_rom ? BOOT_DEVICE_TYPE_ROM : BOOT_DEVICE_TYPE_64DD;
|
||||
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
||||
menu->boot_params->detect_cic_seed = true;
|
||||
|
||||
if (load_rom) {
|
||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
||||
menu->boot_params->detect_cic_seed = rom_info_get_cic_seed(&menu->load.rom_info, &menu->boot_params->cic_seed);
|
||||
switch (rom_info_get_tv_type(&menu->load.rom_info)) {
|
||||
case ROM_TV_TYPE_PAL: menu->boot_params->tv_type = BOOT_TV_TYPE_PAL; break;
|
||||
case ROM_TV_TYPE_NTSC: menu->boot_params->tv_type = BOOT_TV_TYPE_NTSC; break;
|
||||
case ROM_TV_TYPE_MPAL: menu->boot_params->tv_type = BOOT_TV_TYPE_MPAL; break;
|
||||
default: menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; break;
|
||||
}
|
||||
} else {
|
||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_64DD;
|
||||
menu->boot_params->tv_type = BOOT_TV_TYPE_NTSC;
|
||||
menu->boot_params->detect_cic_seed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -142,7 +154,7 @@ void view_load_disk_init (menu_t *menu) {
|
||||
|
||||
menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
|
||||
disk_err_t err = disk_info_load(path_get(menu->load.disk_path), &menu->load.disk_info);
|
||||
disk_err_t err = disk_info_load(menu->load.disk_path, &menu->load.disk_info);
|
||||
if (err != DISK_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
}
|
||||
|
@ -8,15 +8,15 @@ static bool load_pending;
|
||||
static component_boxart_t *boxart;
|
||||
|
||||
|
||||
static char *convert_error_message (disk_err_t err) {
|
||||
static char *convert_error_message (rom_err_t err) {
|
||||
switch (err) {
|
||||
case ROM_ERR_IO: return "I/O error during loading ROM information";
|
||||
case ROM_ERR_IO: return "I/O error during loading/storing ROM information";
|
||||
case ROM_ERR_NO_FILE: return "Couldn't open ROM file";
|
||||
default: return "Unknown ROM info load error";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *format_rom_endianness (endianness_t endianness) {
|
||||
static const char *format_rom_endianness (rom_endianness_t endianness) {
|
||||
switch (endianness) {
|
||||
case ENDIANNESS_BIG: return "Big (default)";
|
||||
case ENDIANNESS_LITTLE: return "Little (unsupported)";
|
||||
@ -25,7 +25,7 @@ static const char *format_rom_endianness (endianness_t endianness) {
|
||||
}
|
||||
}
|
||||
|
||||
static const char *format_rom_media_type (category_type_t media_type) {
|
||||
static const char *format_rom_media_type (rom_category_type_t media_type) {
|
||||
switch (media_type) {
|
||||
case N64_CART: return "Cartridge";
|
||||
case N64_DISK: return "Disk";
|
||||
@ -36,7 +36,7 @@ static const char *format_rom_media_type (category_type_t media_type) {
|
||||
}
|
||||
}
|
||||
|
||||
static const char *format_rom_destination_market (destination_type_t market_type) {
|
||||
static const char *format_rom_destination_market (rom_destination_type_t market_type) {
|
||||
// TODO: These are all assumptions and should be corrected if required.
|
||||
// From http://n64devkit.square7.ch/info/submission/pal/01-01.html
|
||||
switch (market_type) {
|
||||
@ -87,7 +87,7 @@ static const char *format_rom_tv_type (rom_tv_type_t tv_type) {
|
||||
}
|
||||
}
|
||||
|
||||
static char *format_rom_expansion_pak_info (expansion_pak_t expansion_pak_info) {
|
||||
static const char *format_rom_expansion_pak_info (rom_expansion_pak_t expansion_pak_info) {
|
||||
switch (expansion_pak_info) {
|
||||
case EXPANSION_PAK_REQUIRED: return "Required";
|
||||
case EXPANSION_PAK_RECOMMENDED: return "Recommended";
|
||||
@ -97,72 +97,94 @@ static char *format_rom_expansion_pak_info (expansion_pak_t expansion_pak_info)
|
||||
}
|
||||
}
|
||||
|
||||
static const char *format_cic_type (cic_type_t cic_type) {
|
||||
static const char *format_cic_type (rom_cic_type_t cic_type) {
|
||||
switch (cic_type) {
|
||||
case CIC_5101: return "5101";
|
||||
case CIC_5167: return "5167";
|
||||
case CIC_6101: return "6101";
|
||||
case CIC_7102: return "7102";
|
||||
case CIC_6102_7101: return "6102 / 7101";
|
||||
case CIC_x103: return "6103 / 7103";
|
||||
case CIC_x105: return "6105 / 7105";
|
||||
case CIC_x106: return "6106 / 7106";
|
||||
case CIC_8301: return "8301";
|
||||
case CIC_8302: return "8302";
|
||||
case CIC_8303: return "8303";
|
||||
case CIC_8401: return "8401";
|
||||
case CIC_8501: return "8501";
|
||||
case ROM_CIC_TYPE_5101: return "5101";
|
||||
case ROM_CIC_TYPE_5167: return "5167";
|
||||
case ROM_CIC_TYPE_6101: return "6101";
|
||||
case ROM_CIC_TYPE_7102: return "7102";
|
||||
case ROM_CIC_TYPE_x102: return "6102 / 7101";
|
||||
case ROM_CIC_TYPE_x103: return "6103 / 7103";
|
||||
case ROM_CIC_TYPE_x105: return "6105 / 7105";
|
||||
case ROM_CIC_TYPE_x106: return "6106 / 7106";
|
||||
case ROM_CIC_TYPE_8301: return "8301";
|
||||
case ROM_CIC_TYPE_8302: return "8302";
|
||||
case ROM_CIC_TYPE_8303: return "8303";
|
||||
case ROM_CIC_TYPE_8401: return "8401";
|
||||
case ROM_CIC_TYPE_8501: return "8501";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void set_save_type (menu_t *menu, rom_save_type_t save_type) {
|
||||
rom_info_override_save_type(menu->load.rom_path, &menu->load.rom_info, save_type);
|
||||
static void set_cic_type (menu_t *menu, void *arg) {
|
||||
rom_cic_type_t cic_type = (rom_cic_type_t) (arg);
|
||||
rom_err_t err = rom_info_override_cic_type(menu->load.rom_path, &menu->load.rom_info, cic_type);
|
||||
if (err != ROM_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
}
|
||||
menu->browser.reload = true;
|
||||
}
|
||||
|
||||
static void set_save_type_automatic (menu_t *menu) { set_save_type(menu, SAVE_TYPE_AUTOMATIC); }
|
||||
static void set_save_type_none (menu_t *menu) { set_save_type(menu, SAVE_TYPE_NONE); }
|
||||
static void set_save_type_eeprom_4kbit (menu_t *menu) { set_save_type(menu, SAVE_TYPE_EEPROM_4K); }
|
||||
static void set_save_type_eeprom_16kbit (menu_t *menu) { set_save_type(menu, SAVE_TYPE_EEPROM_16K); }
|
||||
static void set_save_type_sram_256kbit (menu_t *menu) { set_save_type(menu, SAVE_TYPE_SRAM); }
|
||||
static void set_save_type_sram_768kbit (menu_t *menu) { set_save_type(menu, SAVE_TYPE_SRAM_BANKED); }
|
||||
static void set_save_type_sram_1mbit (menu_t *menu) { set_save_type(menu, SAVE_TYPE_SRAM_128K); }
|
||||
static void set_save_type_flash_ram_1mbit (menu_t *menu) { set_save_type(menu, SAVE_TYPE_FLASHRAM); }
|
||||
static void set_save_type (menu_t *menu, void *arg) {
|
||||
rom_save_type_t save_type = (rom_save_type_t) (arg);
|
||||
rom_err_t err = rom_info_override_save_type(menu->load.rom_path, &menu->load.rom_info, save_type);
|
||||
if (err != ROM_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
}
|
||||
menu->browser.reload = true;
|
||||
}
|
||||
|
||||
static component_context_menu_t set_save_type_context_menu = { .list = {
|
||||
{ .text = "Automatic", .action = set_save_type_automatic },
|
||||
{ .text = "None", .action = set_save_type_none },
|
||||
{ .text = "EEPROM 4kbit", .action = set_save_type_eeprom_4kbit },
|
||||
{ .text = "EEPROM 16kbit", .action = set_save_type_eeprom_16kbit },
|
||||
{ .text = "SRAM 256kbit", .action = set_save_type_sram_256kbit },
|
||||
{ .text = "SRAM 768kbit", .action = set_save_type_sram_768kbit },
|
||||
{ .text = "SRAM 1Mbit", .action = set_save_type_sram_1mbit },
|
||||
{ .text = "FlashRAM 1Mbit", .action = set_save_type_flash_ram_1mbit },
|
||||
static void set_tv_type (menu_t *menu, void *arg) {
|
||||
rom_tv_type_t tv_type = (rom_tv_type_t) (arg);
|
||||
rom_err_t err = rom_info_override_tv_type(menu->load.rom_path, &menu->load.rom_info, tv_type);
|
||||
if (err != ROM_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
}
|
||||
menu->browser.reload = true;
|
||||
}
|
||||
|
||||
static component_context_menu_t set_cic_type_context_menu = { .list = {
|
||||
{.text = "Automatic", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_AUTOMATIC) },
|
||||
{.text = "CIC-6101", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_6101) },
|
||||
{.text = "CIC-7102", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_7102) },
|
||||
{.text = "CIC-6102 / CIC-7101", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_x102) },
|
||||
{.text = "CIC-6103 / CIC-7103", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_x103) },
|
||||
{.text = "CIC-6105 / CIC-7105", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_x105) },
|
||||
{.text = "CIC-6106 / CIC-7106", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_x106) },
|
||||
{.text = "Aleck64 CIC-5101", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_5101) },
|
||||
{.text = "64DD ROM conversion CIC-5167", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_5167) },
|
||||
{.text = "NDDJ0 64DD IPL", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_8301) },
|
||||
{.text = "NDDJ1 64DD IPL", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_8302) },
|
||||
{.text = "NDDJ2 64DD IPL", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_8303) },
|
||||
{.text = "NDXJ0 64DD IPL", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_8401) },
|
||||
{.text = "NDDE0 64DD IPL", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_8501) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
static void set_tv_type (menu_t *menu, rom_tv_type_t tv_type) {
|
||||
rom_info_override_tv_type(menu->load.rom_path, &menu->load.rom_info, tv_type);
|
||||
menu->browser.reload = true;
|
||||
}
|
||||
|
||||
static void set_tv_type_automatic (menu_t *menu) { set_tv_type(menu, ROM_TV_TYPE_AUTOMATIC); }
|
||||
static void set_tv_type_pal (menu_t *menu) { set_tv_type(menu, ROM_TV_TYPE_PAL); }
|
||||
static void set_tv_type_ntsc (menu_t *menu) { set_tv_type(menu, ROM_TV_TYPE_NTSC); }
|
||||
static void set_tv_type_mpal (menu_t *menu) { set_tv_type(menu, ROM_TV_TYPE_MPAL); }
|
||||
static component_context_menu_t set_save_type_context_menu = { .list = {
|
||||
{ .text = "Automatic", .action = set_save_type, .arg = (void *) (SAVE_TYPE_AUTOMATIC) },
|
||||
{ .text = "None", .action = set_save_type, .arg = (void *) (SAVE_TYPE_NONE) },
|
||||
{ .text = "EEPROM 4kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_EEPROM_4K) },
|
||||
{ .text = "EEPROM 16kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_EEPROM_16K) },
|
||||
{ .text = "SRAM 256kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM) },
|
||||
{ .text = "SRAM 768kbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM_BANKED) },
|
||||
{ .text = "SRAM 1Mbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_SRAM_128K) },
|
||||
{ .text = "FlashRAM 1Mbit", .action = set_save_type, .arg = (void *) (SAVE_TYPE_FLASHRAM) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
static component_context_menu_t set_tv_type_context_menu = { .list = {
|
||||
{ .text = "Automatic", .action = set_tv_type_automatic },
|
||||
{ .text = "PAL", .action = set_tv_type_pal },
|
||||
{ .text = "NTSC", .action = set_tv_type_ntsc },
|
||||
{ .text = "MPAL", .action = set_tv_type_mpal },
|
||||
{ .text = "Automatic", .action = set_tv_type, .arg = (void *) (ROM_TV_TYPE_AUTOMATIC) },
|
||||
{ .text = "PAL", .action = set_tv_type, .arg = (void *) (ROM_TV_TYPE_PAL) },
|
||||
{ .text = "NTSC", .action = set_tv_type, .arg = (void *) (ROM_TV_TYPE_NTSC) },
|
||||
{ .text = "MPAL", .action = set_tv_type, .arg = (void *) (ROM_TV_TYPE_MPAL) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
static component_context_menu_t options_context_menu = { .list = {
|
||||
{ .text = "Set save type", .submenu = &set_save_type_context_menu },
|
||||
{ .text = "Set TV type", .submenu = &set_tv_type_context_menu },
|
||||
{ .text = "Set CIC Type", .submenu = &set_cic_type_context_menu },
|
||||
{ .text = "Set Save Type", .submenu = &set_save_type_context_menu },
|
||||
{ .text = "Set TV Type", .submenu = &set_tv_type_context_menu },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
@ -230,7 +252,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
format_rom_save_type(rom_info_get_save_type(&menu->load.rom_info)),
|
||||
format_rom_tv_type(rom_info_get_tv_type(&menu->load.rom_info)),
|
||||
format_rom_expansion_pak_info(menu->load.rom_info.features.expansion_pak),
|
||||
format_cic_type(menu->load.rom_info.cic_type),
|
||||
format_cic_type(rom_info_get_cic_type(&menu->load.rom_info)),
|
||||
menu->load.rom_info.boot_address,
|
||||
(menu->load.rom_info.libultra.version / 10.0f), menu->load.rom_info.libultra.revision,
|
||||
menu->load.rom_info.clock_rate
|
||||
@ -281,8 +303,8 @@ static void load (menu_t *menu) {
|
||||
menu->next_mode = MENU_MODE_BOOT;
|
||||
|
||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
||||
menu->boot_params->detect_cic_seed = true;
|
||||
switch(rom_info_get_tv_type(&menu->load.rom_info)) {
|
||||
menu->boot_params->detect_cic_seed = rom_info_get_cic_seed(&menu->load.rom_info, &menu->boot_params->cic_seed);
|
||||
switch (rom_info_get_tv_type(&menu->load.rom_info)) {
|
||||
case ROM_TV_TYPE_PAL: menu->boot_params->tv_type = BOOT_TV_TYPE_PAL; break;
|
||||
case ROM_TV_TYPE_NTSC: menu->boot_params->tv_type = BOOT_TV_TYPE_NTSC; break;
|
||||
case ROM_TV_TYPE_MPAL: menu->boot_params->tv_type = BOOT_TV_TYPE_MPAL; break;
|
||||
@ -296,21 +318,23 @@ static void deinit (void) {
|
||||
|
||||
|
||||
void view_load_rom_init (menu_t *menu) {
|
||||
load_pending = false;
|
||||
|
||||
if (menu->load.rom_path) {
|
||||
path_free(menu->load.rom_path);
|
||||
menu->load.rom_path = NULL;
|
||||
}
|
||||
|
||||
load_pending = false;
|
||||
|
||||
menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
|
||||
rom_err_t err = rom_info_load(menu->load.rom_path, &menu->load.rom_info);
|
||||
if (err != ROM_OK) {
|
||||
path_free(menu->load.rom_path);
|
||||
menu->load.rom_path = NULL;
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
return;
|
||||
}
|
||||
|
||||
boxart = component_boxart_init(menu->load.rom_info.game_code);
|
||||
boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code);
|
||||
|
||||
component_context_menu_init(&options_context_menu);
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
#include "views.h"
|
||||
|
||||
static char *format_boolean_type (int state) {
|
||||
|
||||
static const char *format_switch (bool state) {
|
||||
switch (state) {
|
||||
case 0: return "Off";
|
||||
case 1: return "On";
|
||||
default: return "Unknown";
|
||||
case true: return "On";
|
||||
case false: return "Off";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
@ -33,23 +34,22 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"\n"
|
||||
"To change the settings, please adjust them\n"
|
||||
"directly in the 'menu/config.ini' file.\n\n"
|
||||
"pal60_enabled: %s\n"
|
||||
"hidden_files_enabled: %s\n"
|
||||
"default_directory: %s\n"
|
||||
"use_saves_folder: %s\n"
|
||||
"bgm_enabled: %s\n"
|
||||
"sound_enabled: %s\n"
|
||||
"rumble_enabled: %s\n",
|
||||
format_boolean_type(menu->settings.pal60_enabled),
|
||||
format_boolean_type(menu->settings.hidden_files_enabled),
|
||||
"pal60_enabled: %s\n"
|
||||
"show_protected_entries: %s\n"
|
||||
"default_directory: %s\n"
|
||||
"use_saves_folder: %s\n"
|
||||
"bgm_enabled: %s\n"
|
||||
"sound_enabled: %s\n"
|
||||
"rumble_enabled: %s\n",
|
||||
format_switch(menu->settings.pal60_enabled),
|
||||
format_switch(menu->settings.show_protected_entries),
|
||||
menu->settings.default_directory,
|
||||
format_boolean_type(menu->settings.use_saves_folder),
|
||||
format_boolean_type(menu->settings.bgm_enabled),
|
||||
format_boolean_type(menu->settings.sound_enabled),
|
||||
format_boolean_type(menu->settings.rumble_enabled)
|
||||
format_switch(menu->settings.use_saves_folder),
|
||||
format_switch(menu->settings.bgm_enabled),
|
||||
format_switch(menu->settings.sound_enabled),
|
||||
format_switch(menu->settings.rumble_enabled)
|
||||
);
|
||||
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
@ -66,5 +66,6 @@ void view_settings_init (menu_t *menu) {
|
||||
|
||||
void view_settings_display (menu_t *menu, surface_t *display) {
|
||||
process(menu);
|
||||
|
||||
draw(menu, display);
|
||||
}
|
||||
|
@ -7,27 +7,25 @@ static int joypad[4];
|
||||
static int accessory[4];
|
||||
|
||||
|
||||
static char *format_accessory (int joypad) {
|
||||
static const char *format_accessory (int joypad) {
|
||||
switch (accessory[joypad]) {
|
||||
case JOYPAD_ACCESSORY_TYPE_RUMBLE_PAK:
|
||||
return "[Rumble Pak is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_CONTROLLER_PAK:
|
||||
return "[Controller Pak is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_TRANSFER_PAK:
|
||||
return "[Transfer Pak is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_BIO_SENSOR:
|
||||
return "[BIO Sensor is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_SNAP_STATION:
|
||||
return "[Snap Station is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_NONE:
|
||||
return "";
|
||||
default:
|
||||
return "[unknown accessory inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_RUMBLE_PAK: return "[Rumble Pak is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_CONTROLLER_PAK: return "[Controller Pak is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_TRANSFER_PAK: return "[Transfer Pak is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_BIO_SENSOR: return "[BIO Sensor is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_SNAP_STATION: return "[Snap Station is inserted]";
|
||||
case JOYPAD_ACCESSORY_TYPE_NONE: return "";
|
||||
default: return "[unknown accessory inserted]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
JOYPAD_PORT_FOREACH (port) {
|
||||
joypad[port] = (joypad_get_style(port) != JOYPAD_STYLE_NONE);
|
||||
accessory[port] = joypad_get_accessory_type(port);
|
||||
}
|
||||
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
@ -53,10 +51,10 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"\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",
|
||||
"Joypad 1 is %sconnected %s\n"
|
||||
"Joypad 2 is %sconnected %s\n"
|
||||
"Joypad 3 is %sconnected %s\n"
|
||||
"Joypad 4 is %sconnected %s\n",
|
||||
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n",
|
||||
is_memory_expanded() ? "" : "not ",
|
||||
(joypad[0]) ? "" : "not ", format_accessory(0),
|
||||
@ -76,10 +74,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
|
||||
void view_system_info_init (menu_t *menu) {
|
||||
JOYPAD_PORT_FOREACH (port) {
|
||||
joypad[port] = (joypad_get_style(port) != JOYPAD_STYLE_NONE);
|
||||
accessory[port] = joypad_get_accessory_type(port);
|
||||
}
|
||||
// Nothing to initialize (yet)
|
||||
}
|
||||
|
||||
void view_system_info_display (menu_t *menu, surface_t *display) {
|
||||
|
172
src/menu/views/text_viewer.c
Normal file
172
src/menu/views/text_viewer.c
Normal file
@ -0,0 +1,172 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "../components/constants.h"
|
||||
#include "../fonts.h"
|
||||
#include "utils/utils.h"
|
||||
#include "views.h"
|
||||
|
||||
|
||||
#define MAX_FILE_SIZE KiB(128)
|
||||
|
||||
|
||||
typedef struct {
|
||||
FILE *f;
|
||||
char *contents;
|
||||
size_t length;
|
||||
int lines;
|
||||
int current_line;
|
||||
int offset;
|
||||
bool vertical_scroll_possible;
|
||||
} text_file_t;
|
||||
|
||||
static text_file_t *text;
|
||||
|
||||
|
||||
static void perform_vertical_scroll (int lines) {
|
||||
if (!text->vertical_scroll_possible) {
|
||||
return;
|
||||
}
|
||||
|
||||
int direction = (lines < 0) ? -1 : 1;
|
||||
int next_offset = text->offset;
|
||||
|
||||
for (int i = 0; i < abs(lines); i++) {
|
||||
while (true) {
|
||||
next_offset += direction;
|
||||
if (next_offset <= 0) {
|
||||
text->current_line = 0;
|
||||
text->offset = 0;
|
||||
return;
|
||||
}
|
||||
if (next_offset > text->length) {
|
||||
return;
|
||||
}
|
||||
if (text->contents[next_offset - 1] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
text->current_line += direction;
|
||||
text->offset = next_offset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
} else if (text) {
|
||||
if (menu->actions.go_up) {
|
||||
perform_vertical_scroll(menu->actions.go_fast ? -10 : -1);
|
||||
} else if (menu->actions.go_down) {
|
||||
perform_vertical_scroll(menu->actions.go_fast ? 10 : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw (menu_t *menu, surface_t *d) {
|
||||
rdpq_attach(d, NULL);
|
||||
|
||||
component_background_draw();
|
||||
|
||||
component_layout_draw();
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"%s\n",
|
||||
text->contents + text->offset
|
||||
);
|
||||
|
||||
component_list_scrollbar_draw(text->current_line, text->lines, LIST_ENTRIES);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"^%02XUp / Down: Scroll^00\n"
|
||||
"B: Back",
|
||||
text->vertical_scroll_possible ? STL_DEFAULT : STL_GRAY
|
||||
);
|
||||
|
||||
rdpq_detach_show();
|
||||
}
|
||||
|
||||
static void deinit (void) {
|
||||
if (text) {
|
||||
if (text->f) {
|
||||
fclose(text->f);
|
||||
}
|
||||
if (text->contents) {
|
||||
free(text->contents);
|
||||
}
|
||||
free(text);
|
||||
text = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void view_text_viewer_init (menu_t *menu) {
|
||||
if ((text = calloc(1, sizeof(text_file_t))) == NULL) {
|
||||
return menu_show_error(menu, "Couldn't allocate memory for the text file");
|
||||
}
|
||||
|
||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
text->f = fopen(path_get(path), "r");
|
||||
path_free(path);
|
||||
|
||||
if (text->f == NULL) {
|
||||
deinit();
|
||||
return menu_show_error(menu, "Couldn't open text file");
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fileno(text->f), &st)) {
|
||||
deinit();
|
||||
return menu_show_error(menu, "Couldn't get text file size");
|
||||
}
|
||||
text->length = st.st_size;
|
||||
|
||||
if (text->length <= 0) {
|
||||
deinit();
|
||||
return menu_show_error(menu, "Text file is empty");
|
||||
}
|
||||
|
||||
if (text->length > MAX_FILE_SIZE) {
|
||||
deinit();
|
||||
return menu_show_error(menu, "Text file is too big to be displayed");
|
||||
}
|
||||
|
||||
if ((text->contents = malloc((text->length + 1) * sizeof(char))) == NULL) {
|
||||
deinit();
|
||||
return menu_show_error(menu, "Couldn't allocate memory for the text file contents");
|
||||
}
|
||||
|
||||
if (fread(text->contents, text->length, 1, text->f) != 1) {
|
||||
deinit();
|
||||
return menu_show_error(menu, "Couldn't read text file contents");
|
||||
}
|
||||
text->contents[text->length] = '\0';
|
||||
|
||||
if (fclose(text->f)) {
|
||||
deinit();
|
||||
return menu_show_error(menu, "Couldn't close text file");
|
||||
}
|
||||
text->f = NULL;
|
||||
|
||||
text->lines = 1;
|
||||
for (size_t i = 0; i < text->length; i++) {
|
||||
if (text->contents[i] == '\n') {
|
||||
text->lines += 1;
|
||||
}
|
||||
}
|
||||
|
||||
text->vertical_scroll_possible = (text->lines > LIST_ENTRIES);
|
||||
}
|
||||
|
||||
void view_text_viewer_display (menu_t *menu, surface_t *display) {
|
||||
process(menu);
|
||||
|
||||
draw(menu, display);
|
||||
|
||||
if (menu->next_mode != MENU_MODE_TEXT_VIEWER) {
|
||||
deinit();
|
||||
}
|
||||
}
|
@ -23,33 +23,39 @@ void view_startup_display (menu_t *menu, surface_t *display);
|
||||
void view_browser_init (menu_t *menu);
|
||||
void view_browser_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_system_info_init (menu_t *menu);
|
||||
void view_system_info_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_file_info_init (menu_t *menu);
|
||||
void view_file_info_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_system_info_init (menu_t *menu);
|
||||
void view_system_info_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_image_viewer_init (menu_t *menu);
|
||||
void view_image_viewer_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_text_viewer_init (menu_t *menu);
|
||||
void view_text_viewer_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_music_player_init (menu_t *menu);
|
||||
void view_music_player_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_credits_init (menu_t *menu);
|
||||
void view_credits_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_load_rom_init (menu_t *menu);
|
||||
void view_load_rom_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_load_disk_init (menu_t *menu);
|
||||
void view_load_disk_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_settings_init (menu_t *menu);
|
||||
void view_settings_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_rtc_init (menu_t *menu);
|
||||
void view_rtc_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_flashcart_info_init (menu_t *menu);
|
||||
void view_flashcart_info_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_load_rom_init (menu_t *menu);
|
||||
void view_load_rom_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_load_disk_init (menu_t *menu);
|
||||
void view_load_disk_display (menu_t *menu, surface_t *display);
|
||||
|
||||
void view_load_emulator_init (menu_t *menu);
|
||||
void view_load_emulator_display (menu_t *menu, surface_t *display);
|
||||
|
||||
|
172
src/utils/fs.c
172
src/utils/fs.c
@ -1,145 +1,85 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <fatfs/ff.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "fs.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
char *strip_sd_prefix (char *path) {
|
||||
const char *prefix = "sd:/";
|
||||
|
||||
char *strip_fs_prefix (char *path) {
|
||||
const char *prefix = ":/";
|
||||
char *found = strstr(path, prefix);
|
||||
if (found) {
|
||||
return found + strlen(prefix) - 1;
|
||||
return (found + strlen(prefix) - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
bool file_exists (char *path) {
|
||||
FRESULT fr;
|
||||
FILINFO fno;
|
||||
|
||||
fr = f_stat(strip_sd_prefix(path), &fno);
|
||||
|
||||
return ((fr == FR_OK) && (!(fno.fattrib & AM_DIR)));
|
||||
struct stat st;
|
||||
int error = stat(path, &st);
|
||||
return ((error == 0) && S_ISREG(st.st_mode));
|
||||
}
|
||||
|
||||
|
||||
size_t file_get_size (char *path) {
|
||||
FILINFO fno;
|
||||
|
||||
if (f_stat(strip_sd_prefix(path), &fno) != FR_OK) {
|
||||
return 0;
|
||||
int64_t file_get_size (char *path) {
|
||||
struct stat st;
|
||||
if (stat(path, &st)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (size_t) (fno.fsize);
|
||||
return (int64_t) (st.st_size);
|
||||
}
|
||||
|
||||
bool file_delete (char *path) {
|
||||
if (file_exists(path)) {
|
||||
return (f_unlink(strip_sd_prefix(path)) != FR_OK);
|
||||
bool file_allocate (char *path, size_t size) {
|
||||
FILE *f;
|
||||
if ((f = fopen(path, "wb")) == NULL) {
|
||||
return true;
|
||||
}
|
||||
if (fseek(f, size, SEEK_SET)) {
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
if (ftell(f) != size) {
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
if (fclose(f)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool file_allocate (char *path, size_t size) {
|
||||
FIL fil;
|
||||
bool error = false;
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(path), FA_WRITE | FA_CREATE_NEW) != FR_OK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (f_lseek(&fil, size) != FR_OK) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (f_tell(&fil) != size) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
bool file_fill (char *path, uint8_t value) {
|
||||
FIL fil;
|
||||
FILE *f;
|
||||
bool error = false;
|
||||
uint8_t buffer[FS_SECTOR_SIZE * 8];
|
||||
FRESULT res;
|
||||
UINT bytes_to_write;
|
||||
UINT bytes_written;
|
||||
size_t bytes_to_write;
|
||||
|
||||
for (int i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = value;
|
||||
}
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(path), FA_WRITE) != FR_OK) {
|
||||
if ((f = fopen(path, "rb+")) == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < f_size(&fil); i += sizeof(buffer)) {
|
||||
bytes_to_write = MIN(f_size(&fil) - f_tell(&fil), sizeof(buffer));
|
||||
res = f_write(&fil, buffer, bytes_to_write, &bytes_written);
|
||||
if ((res != FR_OK) || (bytes_to_write != bytes_written)) {
|
||||
setbuf(f, NULL);
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
size_t size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
for (size_t i = 0; i < size; i += sizeof(buffer)) {
|
||||
bytes_to_write = MIN(size - ftell(f), sizeof(buffer));
|
||||
if (fwrite(buffer, 1, bytes_to_write, f) != bytes_to_write) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (f_tell(&fil) != f_size(&fil)) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
bool file_get_sectors (char *path, void (*callback) (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size)) {
|
||||
FATFS *fs;
|
||||
FIL fil;
|
||||
bool error = false;
|
||||
|
||||
if (!callback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fs = fil.obj.fs;
|
||||
|
||||
uint32_t sector_count = (ALIGN(f_size(&fil), FS_SECTOR_SIZE) / FS_SECTOR_SIZE);
|
||||
|
||||
uint32_t cluster_sector = 0;
|
||||
|
||||
for (int file_sector = 0; file_sector < sector_count; file_sector += fs->csize) {
|
||||
if ((f_lseek(&fil, (file_sector * FS_SECTOR_SIZE) + (FS_SECTOR_SIZE / 2))) != FR_OK) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t cluster = fil.clust;
|
||||
if (cluster >= fs->n_fatent) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
cluster_sector = (fs->database + ((LBA_t) (fs->csize) * (cluster - 2)));
|
||||
callback(sector_count, file_sector, cluster_sector, fs->csize);
|
||||
}
|
||||
|
||||
if (f_close(&fil) != FR_OK) {
|
||||
if (fclose(f)) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
@ -165,20 +105,9 @@ bool file_has_extensions (char *path, const char *extensions[]) {
|
||||
|
||||
|
||||
bool directory_exists (char *path) {
|
||||
FRESULT fr;
|
||||
FILINFO fno;
|
||||
|
||||
fr = f_stat(strip_sd_prefix(path), &fno);
|
||||
|
||||
return ((fr == FR_OK) && (fno.fattrib & AM_DIR));
|
||||
}
|
||||
|
||||
bool directory_delete (char *path) {
|
||||
if (directory_exists(path)) {
|
||||
return (f_unlink(strip_sd_prefix(path)) != FR_OK);
|
||||
}
|
||||
|
||||
return false;
|
||||
struct stat st;
|
||||
int error = stat(path, &st);
|
||||
return ((error == 0) && S_ISDIR(st.st_mode));
|
||||
}
|
||||
|
||||
bool directory_create (char *path) {
|
||||
@ -188,8 +117,12 @@ bool directory_create (char *path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char *directory = strdup(strip_sd_prefix(path));
|
||||
char *separator = directory;
|
||||
char *directory = strdup(path);
|
||||
char *separator = strip_fs_prefix(directory);
|
||||
|
||||
if (separator != directory) {
|
||||
separator++;
|
||||
}
|
||||
|
||||
do {
|
||||
separator = strchr(separator, '/');
|
||||
@ -199,8 +132,7 @@ bool directory_create (char *path) {
|
||||
}
|
||||
|
||||
if (directory[0] != '\0') {
|
||||
FRESULT res = f_mkdir(directory);
|
||||
if ((res != FR_OK) && (res != FR_EXIST)) {
|
||||
if (mkdir(directory, 0777) && (errno != EEXIST)) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -10,18 +10,15 @@
|
||||
#define FS_SECTOR_SIZE (512)
|
||||
|
||||
|
||||
char *strip_sd_prefix (char *path);
|
||||
char *strip_fs_prefix (char *path);
|
||||
|
||||
bool file_exists (char *path);
|
||||
size_t file_get_size (char *path);
|
||||
bool file_delete (char *path);
|
||||
int64_t file_get_size (char *path);
|
||||
bool file_allocate (char *path, size_t size);
|
||||
bool file_fill (char *path, uint8_t value);
|
||||
bool file_get_sectors (char *path, void (*callback) (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size));
|
||||
bool file_has_extensions (char *path, const char *extensions[]);
|
||||
|
||||
bool directory_exists (char *path);
|
||||
bool directory_delete (char *path);
|
||||
bool directory_create (char *path);
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user