mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2024-11-23 11:09:19 +01:00
Merge remote-tracking branch 'upstream/main' into add-rom-patcher
This commit is contained in:
commit
30a27ca52e
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": [
|
"mounts": [
|
||||||
"source=n64flashcartmenu-bashhistory,target=/commandhistory,type=volume"
|
"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": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
@ -24,4 +24,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -77,7 +77,7 @@ jobs:
|
|||||||
# continue-on-error: true
|
# continue-on-error: true
|
||||||
|
|
||||||
- name: Upload rolling release
|
- name: Upload rolling release
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
uses: softprops/action-gh-release@v2
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
name: Rolling release
|
name: Rolling release
|
||||||
@ -105,7 +105,7 @@ jobs:
|
|||||||
doxyfile-path: './Doxyfile'
|
doxyfile-path: './Doxyfile'
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -1,9 +1,17 @@
|
|||||||
|
# Ignore editor specific config folders/files
|
||||||
/.vscode
|
/.vscode
|
||||||
|
|
||||||
|
# Ignore compilation result directories
|
||||||
/build
|
/build
|
||||||
/filesystem
|
|
||||||
/output
|
/output
|
||||||
|
|
||||||
|
# Ignore generated files in the libdragon FS
|
||||||
|
/filesystem/FiraMonoBold.font64
|
||||||
|
|
||||||
|
# Ignore external development tools
|
||||||
/tools/*
|
/tools/*
|
||||||
|
|
||||||
# There should never be ROMs uploaded, but just incase, make sure they are excluded.
|
# Ignore any N64 ROM
|
||||||
**/*.n64
|
**/*.n64
|
||||||
|
**/*.v64
|
||||||
**/*.z64
|
**/*.z64
|
||||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,7 +1,7 @@
|
|||||||
[submodule "libdragon"]
|
[submodule "libdragon"]
|
||||||
path = libdragon
|
path = libdragon
|
||||||
url = https://github.com/DragonMinded/libdragon
|
url = https://github.com/DragonMinded/libdragon
|
||||||
branch = unstable
|
branch = preview
|
||||||
ignore = dirty
|
ignore = dirty
|
||||||
[submodule "src/libs/libspng"]
|
[submodule "src/libs/libspng"]
|
||||||
path = src/libs/libspng
|
path = src/libs/libspng
|
||||||
|
19
Makefile
19
Makefile
@ -13,11 +13,17 @@ BUILD_TIMESTAMP = "$(shell TZ='UTC' date "+%Y-%m-%d %H:%M:%S %:z")"
|
|||||||
|
|
||||||
include $(N64_INST)/include/n64.mk
|
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)
|
N64_CFLAGS += -iquote $(SOURCE_DIR) -iquote $(ASSETS_DIR) -I $(SOURCE_DIR)/libs -flto=auto $(FLAGS)
|
||||||
|
|
||||||
SRCS = \
|
SRCS = \
|
||||||
main.c \
|
main.c \
|
||||||
boot/boot.c \
|
boot/boot.c \
|
||||||
|
boot/cheats.c \
|
||||||
boot/cic.c \
|
boot/cic.c \
|
||||||
boot/reboot.S \
|
boot/reboot.S \
|
||||||
flashcart/64drive/64drive_ll.c \
|
flashcart/64drive/64drive_ll.c \
|
||||||
@ -83,7 +89,7 @@ FILESYSTEM = \
|
|||||||
|
|
||||||
$(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second
|
$(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
|
$(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))
|
$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))
|
||||||
|
|
||||||
@ -130,9 +136,18 @@ all: $(OUTPUT_DIR)/$(PROJECT_NAME).n64 64drive ed64 ed64-clone sc64
|
|||||||
.PHONY: all
|
.PHONY: all
|
||||||
|
|
||||||
clean:
|
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
|
.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
|
run: $(OUTPUT_DIR)/$(PROJECT_NAME).n64
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
./localdeploy.bat
|
./localdeploy.bat
|
||||||
|
15
README.md
15
README.md
@ -82,11 +82,8 @@ If required, you can manually adjust the file on the SD card using your computer
|
|||||||
- Download the latest `sc64menu.n64` file from the releases page, then put it in the root directory of your SD card.
|
- Download the latest `sc64menu.n64` file from the releases page, then put it in the root directory of your SD card.
|
||||||
|
|
||||||
##### 64DD disk support
|
##### 64DD disk support
|
||||||
For the ability to load and run 64DD disk images, you need to add the folder `/menu/64ddipl` on the SD card.
|
For the ability to load and run 64DD disk images, you need to place required 64DD IPL dumps in the `/menu/64ddipl` folder on the SD card.
|
||||||
Download and add the relevant ipl files and rename them before adding them to the folder:
|
For more details follow [this guide on the 64dd.org website](https://64dd.org/tutorial_sc64.html).
|
||||||
- `NDDE0.n64` the US Prototype IPL can be downloaded from [here](https://64dd.org/dumps/64DD_IPL_US_MJR.n64)
|
|
||||||
- `NDXJ0.n64` the JPN Development IPL can be downloaded from [here](https://64dd.org/dumps/64DD_IPL_DEV_H4G.n64)
|
|
||||||
- `NDDJ2.n64` the JPN Retail IPL can be downloaded from [here](https://64dd.org/dumps/N64DD_IPLROM_(J).zip)
|
|
||||||
|
|
||||||
Note: to load an expansion disk (e.g. F-Zero X) browse to the N64 ROM and load it (but not start it) and then browse to the DD expansion file and press the `R` button.
|
Note: to load an expansion disk (e.g. F-Zero X) browse to the N64 ROM and load it (but not start it) and then browse to the DD expansion file and press the `R` button.
|
||||||
|
|
||||||
@ -115,7 +112,6 @@ You can use a dev container in VSCode to ease development.
|
|||||||
Make sure that your firmware is compatible (currently v2.18.0+)
|
Make sure that your firmware is compatible (currently v2.18.0+)
|
||||||
See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.18.0/docs/00_quick_startup_guide.md#firmware-backupupdate)
|
See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.18.0/docs/00_quick_startup_guide.md#firmware-backupupdate)
|
||||||
|
|
||||||
|
|
||||||
#### From the devcontainer
|
#### From the devcontainer
|
||||||
It is not currently possible to directly communicate with USB devices.
|
It is not currently possible to directly communicate with USB devices.
|
||||||
BUT, as a work around you can use a proxy TCP/IP connection
|
BUT, as a work around you can use a proxy TCP/IP connection
|
||||||
@ -137,13 +133,18 @@ The ROM can be found in the `output` directory.
|
|||||||
|
|
||||||
NOTE: a "release" version of the SC64 menu is called `sc64menu.n64` and can be created for when you want to add it directly to the SDCard. This is generated by running `make all` or running `make sc64`.
|
NOTE: a "release" version of the SC64 menu is called `sc64menu.n64` and can be created for when you want to add it directly to the SDCard. This is generated by running `make all` or running `make sc64`.
|
||||||
|
|
||||||
|
### Ares Emulator
|
||||||
|
For ease of development and debugging, the menu ROM is able to run in the Ares emulator (without most flashcart features).
|
||||||
|
|
||||||
|
* Ensure you have the Ares emulator on you computer.
|
||||||
|
* Load the `N64FlashcartMenu.n64` ROM.
|
||||||
|
|
||||||
### Others
|
### Others
|
||||||
* Add the required file to the correct folder on your SD card.
|
* Add the required file to the correct folder on your SD card.
|
||||||
|
|
||||||
|
|
||||||
# Update Libdragon submodule
|
# Update Libdragon submodule
|
||||||
This repo currently uses the `unstable` branch as a submodule at a specific commit.
|
This repo currently uses the `preview` branch as a submodule at a specific commit.
|
||||||
To update to the latest version, use `git submodule update --remote ` from the terminal.
|
To update to the latest version, use `git submodule update --remote ` from the terminal.
|
||||||
|
|
||||||
# Generate documentation
|
# Generate documentation
|
||||||
|
0
filesystem/.gitkeep
Normal file
0
filesystem/.gitkeep
Normal file
@ -1 +1 @@
|
|||||||
Subproject commit 596490a9b650d52463b79c9356774bf3c65623f3
|
Subproject commit af650428e9615f4e08d8e7eae187929a90c15ccc
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
#include "boot_io.h"
|
#include "boot_io.h"
|
||||||
#include "boot.h"
|
#include "boot.h"
|
||||||
|
#include "cheats.h"
|
||||||
#include "cic.h"
|
#include "cic.h"
|
||||||
|
#include "reboot.h"
|
||||||
|
|
||||||
|
|
||||||
#define C0_STATUS_FR (1 << 26)
|
#define C0_STATUS_FR (1 << 26)
|
||||||
@ -10,10 +12,6 @@
|
|||||||
#define C0_STATUS_CU1 (1 << 29)
|
#define C0_STATUS_CU1 (1 << 29)
|
||||||
|
|
||||||
|
|
||||||
extern uint32_t reboot_start __attribute__((section(".text")));
|
|
||||||
extern size_t reboot_size __attribute__((section(".text")));
|
|
||||||
|
|
||||||
|
|
||||||
static io32_t *boot_get_device_base (boot_params_t *params) {
|
static io32_t *boot_get_device_base (boot_params_t *params) {
|
||||||
io32_t *device_base_address = ROM_CART;
|
io32_t *device_base_address = ROM_CART;
|
||||||
if (params->device_type == BOOT_DEVICE_TYPE_64DD) {
|
if (params->device_type == BOOT_DEVICE_TYPE_64DD) {
|
||||||
@ -22,7 +20,7 @@ static io32_t *boot_get_device_base (boot_params_t *params) {
|
|||||||
return device_base_address;
|
return device_base_address;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void boot_detect_cic_seed (boot_params_t *params) {
|
static cic_type_t boot_detect_cic (boot_params_t *params) {
|
||||||
io32_t *base = boot_get_device_base(params);
|
io32_t *base = boot_get_device_base(params);
|
||||||
|
|
||||||
uint8_t ipl3[IPL3_LENGTH] __attribute__((aligned(8)));
|
uint8_t ipl3[IPL3_LENGTH] __attribute__((aligned(8)));
|
||||||
@ -31,11 +29,17 @@ static void boot_detect_cic_seed (boot_params_t *params) {
|
|||||||
dma_read_raw_async(ipl3, (uint32_t) (&base[16]), sizeof(ipl3));
|
dma_read_raw_async(ipl3, (uint32_t) (&base[16]), sizeof(ipl3));
|
||||||
dma_wait();
|
dma_wait();
|
||||||
|
|
||||||
params->cic_seed = cic_get_seed(cic_detect(ipl3));
|
return cic_detect(ipl3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void boot (boot_params_t *params) {
|
void boot (boot_params_t *params) {
|
||||||
|
cic_type_t cic_type = boot_detect_cic(params);
|
||||||
|
|
||||||
|
if (params->detect_cic_seed) {
|
||||||
|
params->cic_seed = cic_get_seed(cic_type);
|
||||||
|
}
|
||||||
|
|
||||||
if (params->tv_type == BOOT_TV_TYPE_PASSTHROUGH) {
|
if (params->tv_type == BOOT_TV_TYPE_PASSTHROUGH) {
|
||||||
switch (get_tv_type()) {
|
switch (get_tv_type()) {
|
||||||
case TV_PAL:
|
case TV_PAL:
|
||||||
@ -53,10 +57,6 @@ void boot (boot_params_t *params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params->detect_cic_seed) {
|
|
||||||
boot_detect_cic_seed(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
C0_WRITE_STATUS(C0_STATUS_CU1 | C0_STATUS_CU0 | C0_STATUS_FR);
|
C0_WRITE_STATUS(C0_STATUS_CU1 | C0_STATUS_CU0 | C0_STATUS_FR);
|
||||||
|
|
||||||
while (!(cpu_io_read(&SP->SR) & SP_SR_HALT));
|
while (!(cpu_io_read(&SP->SR) & SP_SR_HALT));
|
||||||
@ -123,12 +123,16 @@ void boot (boot_params_t *params) {
|
|||||||
cpu_io_write(&ipl3_dst[i], io_read((uint32_t) (&ipl3_src[i])));
|
cpu_io_write(&ipl3_dst[i], io_read((uint32_t) (&ipl3_src[i])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cheats_installed = cheats_install(cic_type, params->cheat_list);
|
||||||
|
|
||||||
|
register uint32_t skip_rdram_reset asm ("a0");
|
||||||
register uint32_t boot_device asm ("s3");
|
register uint32_t boot_device asm ("s3");
|
||||||
register uint32_t tv_type asm ("s4");
|
register uint32_t tv_type asm ("s4");
|
||||||
register uint32_t reset_type asm ("s5");
|
register uint32_t reset_type asm ("s5");
|
||||||
register uint32_t cic_seed asm ("s6");
|
register uint32_t cic_seed asm ("s6");
|
||||||
register uint32_t version asm ("s7");
|
register uint32_t version asm ("s7");
|
||||||
|
|
||||||
|
skip_rdram_reset = cheats_installed;
|
||||||
boot_device = (params->device_type & 0x01);
|
boot_device = (params->device_type & 0x01);
|
||||||
tv_type = (params->tv_type & 0x03);
|
tv_type = (params->tv_type & 0x03);
|
||||||
reset_type = BOOT_RESET_TYPE_COLD;
|
reset_type = BOOT_RESET_TYPE_COLD;
|
||||||
@ -141,6 +145,7 @@ void boot (boot_params_t *params) {
|
|||||||
asm volatile (
|
asm volatile (
|
||||||
"la $t3, reboot \n"
|
"la $t3, reboot \n"
|
||||||
"jr $t3 \n" ::
|
"jr $t3 \n" ::
|
||||||
|
[skip_rdram_reset] "r" (skip_rdram_reset),
|
||||||
[boot_device] "r" (boot_device),
|
[boot_device] "r" (boot_device),
|
||||||
[tv_type] "r" (tv_type),
|
[tv_type] "r" (tv_type),
|
||||||
[reset_type] "r" (reset_type),
|
[reset_type] "r" (reset_type),
|
||||||
|
@ -38,6 +38,7 @@ typedef struct {
|
|||||||
boot_tv_type_t tv_type;
|
boot_tv_type_t tv_type;
|
||||||
uint8_t cic_seed;
|
uint8_t cic_seed;
|
||||||
bool detect_cic_seed;
|
bool detect_cic_seed;
|
||||||
|
uint32_t *cheat_list;
|
||||||
} boot_params_t;
|
} boot_params_t;
|
||||||
|
|
||||||
|
|
||||||
|
335
src/boot/cheats.c
Normal file
335
src/boot/cheats.c
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
#include <libdragon.h>
|
||||||
|
|
||||||
|
#include "boot_io.h"
|
||||||
|
#include "cheats.h"
|
||||||
|
#include "vr4300_asm.h"
|
||||||
|
|
||||||
|
#define HIT_INVALIDATE_I ((4 << 2) | 0)
|
||||||
|
#define HIT_WRITE_BACK_D ((6 << 2) | 1)
|
||||||
|
|
||||||
|
#define D_CACHE_LINE_SIZE (16)
|
||||||
|
|
||||||
|
#define CAUSE_IRQ_PRE_NMI (1 << 12)
|
||||||
|
#define CAUSE_EXC_CODE_MASK (0x7C)
|
||||||
|
#define CAUSE_EXC_CODE_WATCH (0x5C)
|
||||||
|
|
||||||
|
#define WATCHLO_W (1 << 0)
|
||||||
|
|
||||||
|
#define RELOCATED_EXCEPTION_HANDLER_ADDRESS (0x80000120)
|
||||||
|
#define EXCEPTION_HANDLER_ADDRESS (0x80000180)
|
||||||
|
#define PATCHER_ADDRESS (0x80700000)
|
||||||
|
#define ENGINE_TEMPORARY_ADDRESS (PATCHER_ADDRESS + 0x10000)
|
||||||
|
#define DEFAULT_ENGINE_ADDRESS (0x807C5C00)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t type;
|
||||||
|
uint32_t address;
|
||||||
|
uint16_t value;
|
||||||
|
} cheat_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
cheat_t main;
|
||||||
|
cheat_t sub;
|
||||||
|
} cheat_entry_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SPECIAL_DISABLE_EXPANSION_PAK = 0xEE,
|
||||||
|
SPECIAL_WRITE_BYTE_ON_BOOT = 0xF0,
|
||||||
|
SPECIAL_WRITE_SHORT_ON_BOOT = 0xF1,
|
||||||
|
SPECIAL_SET_STORE_LOCATION = 0xFF,
|
||||||
|
} cheat_type_special_t;
|
||||||
|
|
||||||
|
#define IS_WIDTH_16(t) ((t) & (1 << 0))
|
||||||
|
#define IS_CONDITION_NOT_EQUAL(t) ((t) & (1 << 1))
|
||||||
|
#define IS_CONDITION_GS_BUTTON(t) ((t) & (1 << 3))
|
||||||
|
|
||||||
|
#define IS_TYPE_REPEATER(t) ((t) == 0x50)
|
||||||
|
#define IS_TYPE_WRITE(t) ((((t)&0xF0) == 0x80) || (((t)&0xF0) == 0xA0))
|
||||||
|
#define IS_TYPE_CONDITIONAL(t) (((t)&0xF0) == 0xD0)
|
||||||
|
|
||||||
|
#define IS_DOUBLE_ENTRY(t) (IS_TYPE_CONDITIONAL(t) || IS_TYPE_REPEATER(t))
|
||||||
|
|
||||||
|
static bool cheats_patch_ipl3 (cic_type_t cic_type, io32_t *target) {
|
||||||
|
uint32_t patch_offset = 0;
|
||||||
|
uint32_t j_instruction = I_J((uint32_t)(target));
|
||||||
|
|
||||||
|
io32_t *ipl3 = SP_MEM->DMEM;
|
||||||
|
|
||||||
|
switch (cic_type) {
|
||||||
|
case CIC_5101: patch_offset = 476; break;
|
||||||
|
case CIC_6101:
|
||||||
|
case CIC_7102: patch_offset = 476; break;
|
||||||
|
case CIC_x102: patch_offset = 475; break;
|
||||||
|
case CIC_x103: patch_offset = 472; break;
|
||||||
|
case CIC_x105: patch_offset = 499; break;
|
||||||
|
case CIC_x106: patch_offset = 488; break;
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Check for "jr $t1" instruction
|
||||||
|
// Libdragon IPL3 could be brute-force signed with any retail
|
||||||
|
// CIC seed and checksum, and we support only retail libultra IPL3
|
||||||
|
if (cpu_io_read(&ipl3[patch_offset]) != I_JR(REG_T1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cic_type) {
|
||||||
|
case CIC_x105:
|
||||||
|
// NOTE: This disables game code checksum verification
|
||||||
|
cpu_io_write(&ipl3[486], I_NOP());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CIC_x106:
|
||||||
|
// NOTE: CIC x106 IPL3 is partially scrambled
|
||||||
|
j_instruction ^= 0x8188764A;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu_io_write(&ipl3[patch_offset], j_instruction);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cheats_get_next (uint32_t **cheat_list, cheat_entry_t *cheat) {
|
||||||
|
cheat_t *c = &cheat->main;
|
||||||
|
cheat->sub.type = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
uint32_t raw[2] = {(*cheat_list)[0], (*cheat_list)[1]};
|
||||||
|
|
||||||
|
(*cheat_list) += 2;
|
||||||
|
|
||||||
|
if ((raw[0] == 0) && (raw[1] == 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->type = ((raw[0] >> 24) & 0xFF);
|
||||||
|
c->address = (raw[0] & 0xA07FFFFF);
|
||||||
|
c->value = (raw[1] & 0xFFFF);
|
||||||
|
|
||||||
|
if (!IS_DOUBLE_ENTRY(c->type)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &cheat->sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static io32_t *cheats_get_engine_address (uint32_t *cheat_list) {
|
||||||
|
cheat_entry_t cheat;
|
||||||
|
while (cheats_get_next(&cheat_list, &cheat)) {
|
||||||
|
if (cheat.main.type == SPECIAL_SET_STORE_LOCATION) {
|
||||||
|
return (io32_t *)(cheat.main.address & 0x807FFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (io32_t *)(DEFAULT_ENGINE_ADDRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cheats_update_cache (volatile void *start, volatile void *end) {
|
||||||
|
data_cache_hit_writeback(start, (end - start));
|
||||||
|
inst_cache_hit_invalidate(start, (end - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cheats_install (cic_type_t cic_type, uint32_t *cheat_list) {
|
||||||
|
if (!cheat_list) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
io32_t *engine_start = (io32_t *)(ENGINE_TEMPORARY_ADDRESS);
|
||||||
|
io32_t *engine_p = engine_start;
|
||||||
|
|
||||||
|
io32_t *patcher_start = (io32_t *)(PATCHER_ADDRESS);
|
||||||
|
io32_t *patcher_p = patcher_start;
|
||||||
|
|
||||||
|
if (cheats_patch_ipl3(cic_type, patcher_start)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
io32_t *final_engine_address = cheats_get_engine_address(cheat_list);
|
||||||
|
|
||||||
|
// Original watch exception handler code written by Jay Oster 'Parasyte'
|
||||||
|
// https://github.com/parasyte/alt64/blob/master/utils.c#L1024-L1054
|
||||||
|
|
||||||
|
uint32_t ori_placeholder_instruction = I_ORI(REG_ZERO, REG_K0, A_OFFSET(RELOCATED_EXCEPTION_HANDLER_ADDRESS));
|
||||||
|
uint32_t ori_placeholder_address = (uint32_t)(final_engine_address + 20);
|
||||||
|
|
||||||
|
// Load cause register
|
||||||
|
*engine_p++ = I_MFC0(REG_K0, C0_REG_CAUSE);
|
||||||
|
|
||||||
|
// Disable watch exception when reset button is pressed
|
||||||
|
*engine_p++ = I_ANDI(REG_K1, REG_K0, CAUSE_IRQ_PRE_NMI);
|
||||||
|
*engine_p++ = I_BNEL(REG_K1, REG_ZERO, 1);
|
||||||
|
*engine_p++ = I_MTC0(REG_ZERO, C0_REG_WATCH_LO);
|
||||||
|
|
||||||
|
// Check if watch exception ocurred, if yes then proceed to relocate the game exception handler
|
||||||
|
*engine_p++ = I_ANDI(REG_K0, REG_K0, CAUSE_EXC_CODE_MASK);
|
||||||
|
*engine_p++ = I_ORI(REG_K1, REG_ZERO, CAUSE_EXC_CODE_WATCH);
|
||||||
|
*engine_p++ = I_BNE(REG_K0, REG_K1, 15); // Skips to after the 'eret' instruction
|
||||||
|
|
||||||
|
// Extract base register number from the store instruction
|
||||||
|
*engine_p++ = I_MFC0(REG_K1, C0_REG_EPC);
|
||||||
|
*engine_p++ = I_LW(REG_K1, 0, REG_K1);
|
||||||
|
*engine_p++ = I_LUI(REG_K0, 0x03E0);
|
||||||
|
*engine_p++ = I_AND(REG_K1, REG_K0, REG_K1);
|
||||||
|
*engine_p++ = I_SRL(REG_K1, REG_K1, 5);
|
||||||
|
|
||||||
|
// Update create final instruction and update its target register number
|
||||||
|
*engine_p++ = I_LUI(REG_K0, ori_placeholder_instruction >> 16);
|
||||||
|
*engine_p++ = I_ORI(REG_K0, REG_K0, ori_placeholder_instruction);
|
||||||
|
*engine_p++ = I_OR(REG_K0, REG_K0, REG_K1);
|
||||||
|
|
||||||
|
// Write created instruction into placeholder
|
||||||
|
*engine_p++ = I_LUI(REG_K1, A_BASE(ori_placeholder_address));
|
||||||
|
*engine_p++ = I_SW(REG_K0, A_OFFSET(ori_placeholder_address), REG_K1);
|
||||||
|
|
||||||
|
// Force write and instruction cache invalidation
|
||||||
|
*engine_p++ = I_CACHE(HIT_WRITE_BACK_D, A_OFFSET(ori_placeholder_address), REG_K1);
|
||||||
|
*engine_p++ = I_CACHE(HIT_INVALIDATE_I, A_OFFSET(ori_placeholder_address), REG_K1);
|
||||||
|
|
||||||
|
// Load address base and execute created instruction
|
||||||
|
*engine_p++ = I_LUI(REG_K0, A_BASE(RELOCATED_EXCEPTION_HANDLER_ADDRESS));
|
||||||
|
*engine_p++ = I_NOP();
|
||||||
|
|
||||||
|
// Return from the exception
|
||||||
|
*engine_p++ = I_ERET();
|
||||||
|
|
||||||
|
cheat_entry_t cheat;
|
||||||
|
|
||||||
|
while (cheats_get_next(&cheat_list, &cheat)) {
|
||||||
|
cheat_t *c = &cheat.main;
|
||||||
|
|
||||||
|
if (IS_TYPE_REPEATER(c->type)) {
|
||||||
|
if ((!IS_TYPE_WRITE(cheat.sub.type)) || IS_CONDITION_GS_BUTTON(cheat.sub.type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = ((c->address >> 8) & 0xFF);
|
||||||
|
int step = (c->address & 0xFF);
|
||||||
|
int16_t increment = (int16_t)(c->value);
|
||||||
|
|
||||||
|
c = &cheat.sub;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
*engine_p++ = I_LUI(REG_K0, A_BASE(c->address));
|
||||||
|
*engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value);
|
||||||
|
*engine_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0)
|
||||||
|
: I_SB(REG_K1, A_OFFSET(c->address), REG_K0);
|
||||||
|
|
||||||
|
c->address += step;
|
||||||
|
c->value += increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_TYPE_CONDITIONAL(c->type)) {
|
||||||
|
if ((!IS_TYPE_WRITE(cheat.sub.type)) || IS_CONDITION_GS_BUTTON(cheat.sub.type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*engine_p++ = I_LUI(REG_K0, A_BASE(c->address));
|
||||||
|
*engine_p++ = IS_WIDTH_16(c->type) ? I_LHU(REG_K0, A_OFFSET(c->address), REG_K0)
|
||||||
|
: I_LBU(REG_K0, A_OFFSET(c->address), REG_K0);
|
||||||
|
*engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value & (IS_WIDTH_16(c->type) ? 0xFFFF : 0xFF));
|
||||||
|
*engine_p++ = IS_CONDITION_NOT_EQUAL(c->type) ? I_BEQ(REG_K0, REG_K1, 3) : I_BNE(REG_K0, REG_K1, 3);
|
||||||
|
|
||||||
|
c = &cheat.sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_TYPE_WRITE(c->type)) {
|
||||||
|
if (IS_CONDITION_GS_BUTTON(c->type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*engine_p++ = I_LUI(REG_K0, A_BASE(c->address));
|
||||||
|
*engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value);
|
||||||
|
*engine_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0)
|
||||||
|
: I_SB(REG_K1, A_OFFSET(c->address), REG_K0);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c->type) {
|
||||||
|
case SPECIAL_WRITE_BYTE_ON_BOOT:
|
||||||
|
case SPECIAL_WRITE_SHORT_ON_BOOT: {
|
||||||
|
*patcher_p++ = I_LUI(REG_K0, A_BASE(c->address));
|
||||||
|
*patcher_p++ = I_ORI(REG_K1, REG_ZERO, c->value);
|
||||||
|
*patcher_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0)
|
||||||
|
: I_SB(REG_K1, A_OFFSET(c->address), REG_K0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SPECIAL_DISABLE_EXPANSION_PAK: {
|
||||||
|
*patcher_p++ = I_LUI(REG_K0, 0xA000);
|
||||||
|
*patcher_p++ = I_LUI(REG_K1, 0x0040);
|
||||||
|
*patcher_p++ = I_SW(REG_K1, 0x318, REG_K0);
|
||||||
|
*patcher_p++ = I_SW(REG_K1, 0x3F0, REG_K0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*engine_p++ = I_J(RELOCATED_EXCEPTION_HANDLER_ADDRESS);
|
||||||
|
*engine_p++ = I_NOP();
|
||||||
|
|
||||||
|
uint32_t j_engine_from_handler = I_J((uint32_t)(final_engine_address));
|
||||||
|
|
||||||
|
// Copy engine to the final location
|
||||||
|
*patcher_p++ = I_LUI(REG_T3, A_BASE((uint32_t)(engine_start)));
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T3, REG_T3, A_OFFSET((uint32_t)(engine_start)));
|
||||||
|
|
||||||
|
*patcher_p++ = I_LUI(REG_T4, A_BASE((uint32_t)(engine_p)));
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T4, REG_T4, A_OFFSET((uint32_t)(engine_p)));
|
||||||
|
|
||||||
|
*patcher_p++ = I_LUI(REG_T5, A_BASE((uint32_t)(final_engine_address)));
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T5, REG_T5, A_OFFSET((uint32_t)(final_engine_address)));
|
||||||
|
|
||||||
|
*patcher_p++ = I_ORI(REG_T6, REG_ZERO, 0);
|
||||||
|
|
||||||
|
*patcher_p++ = I_LW(REG_K1, 0, REG_T3);
|
||||||
|
*patcher_p++ = I_SW(REG_K1, 0, REG_T5);
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T3, REG_T3, 4);
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T5, REG_T5, 4);
|
||||||
|
*patcher_p++ = I_BNE(REG_T3, REG_T4, -5);
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T6, REG_T6, 4);
|
||||||
|
|
||||||
|
// Force write and invalidate instruction cache
|
||||||
|
*patcher_p++ = I_LUI(REG_T5, A_BASE((uint32_t)(final_engine_address)));
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T5, REG_T5, A_OFFSET((uint32_t)(final_engine_address)));
|
||||||
|
|
||||||
|
*patcher_p++ = I_CACHE(HIT_WRITE_BACK_D, 0, REG_T5);
|
||||||
|
*patcher_p++ = I_CACHE(HIT_INVALIDATE_I, 0, REG_T5);
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T6, REG_T6, -D_CACHE_LINE_SIZE);
|
||||||
|
*patcher_p++ = I_BGTZ(REG_T6, -4);
|
||||||
|
*patcher_p++ = I_ADDIU(REG_T5, REG_T5, D_CACHE_LINE_SIZE);
|
||||||
|
|
||||||
|
// Write jump instruction to the exception handler
|
||||||
|
*patcher_p++ = I_LUI(REG_K0, A_BASE(EXCEPTION_HANDLER_ADDRESS));
|
||||||
|
*patcher_p++ = I_ADDIU(REG_K0, REG_K0, A_OFFSET(EXCEPTION_HANDLER_ADDRESS));
|
||||||
|
|
||||||
|
*patcher_p++ = I_LUI(REG_K1, j_engine_from_handler >> 16);
|
||||||
|
*patcher_p++ = I_ORI(REG_K1, REG_K1, j_engine_from_handler);
|
||||||
|
*patcher_p++ = I_SW(REG_K1, 0, REG_K0);
|
||||||
|
*patcher_p++ = I_SW(REG_ZERO, 4, REG_K0);
|
||||||
|
|
||||||
|
*patcher_p++ = I_CACHE(HIT_WRITE_BACK_D, 0, REG_K0);
|
||||||
|
*patcher_p++ = I_CACHE(HIT_INVALIDATE_I, 0, REG_K0);
|
||||||
|
|
||||||
|
// Set watch exception on address 0x80000180
|
||||||
|
*patcher_p++ = I_ORI(REG_K1, REG_ZERO, EXCEPTION_HANDLER_ADDRESS | WATCHLO_W);
|
||||||
|
*patcher_p++ = I_MTC0(REG_K1, C0_REG_WATCH_LO);
|
||||||
|
*patcher_p++ = I_MTC0(REG_ZERO, C0_REG_WATCH_HI);
|
||||||
|
|
||||||
|
// Jump back to the game code
|
||||||
|
*patcher_p++ = I_JR(REG_T1);
|
||||||
|
*patcher_p++ = I_NOP();
|
||||||
|
|
||||||
|
cheats_update_cache(engine_start, engine_p);
|
||||||
|
cheats_update_cache(patcher_start, patcher_p);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
10
src/boot/cheats.h
Normal file
10
src/boot/cheats.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef CHEATS_H__
|
||||||
|
#define CHEATS_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "cic.h"
|
||||||
|
|
||||||
|
bool cheats_install (cic_type_t cic_type, uint32_t *cheat_list);
|
||||||
|
|
||||||
|
#endif
|
@ -1,3 +1,6 @@
|
|||||||
|
#include "reboot.h"
|
||||||
|
|
||||||
|
|
||||||
#define IPL3_ENTRY 0xA4000040
|
#define IPL3_ENTRY 0xA4000040
|
||||||
#define REBOOT_ADDRESS 0xA4001000
|
#define REBOOT_ADDRESS 0xA4001000
|
||||||
#define STACK_ADDRESS 0xA4001FF0
|
#define STACK_ADDRESS 0xA4001FF0
|
||||||
@ -34,9 +37,10 @@ reboot_entry:
|
|||||||
|
|
||||||
li $sp, STACK_ADDRESS
|
li $sp, STACK_ADDRESS
|
||||||
|
|
||||||
reset_rdram:
|
bnez $a0, reset_rdram_skip # Skip when cheats are enabled
|
||||||
bnez $s5, reset_rdram_skip
|
bnez $s5, reset_rdram_skip # Skip when reset type is set to NMI
|
||||||
|
|
||||||
|
reset_rdram:
|
||||||
li $t0, RI_ADDRESS
|
li $t0, RI_ADDRESS
|
||||||
|
|
||||||
sw $zero, RI_REFRESH($t0)
|
sw $zero, RI_REFRESH($t0)
|
||||||
|
17
src/boot/reboot.h
Normal file
17
src/boot/reboot.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#ifndef REBOOT_H__
|
||||||
|
#define REBOOT_H__
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef __ASSEMBLER__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
extern uint32_t reboot_start __attribute__((section(".text")));
|
||||||
|
extern size_t reboot_size __attribute__((section(".text")));
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
397
src/boot/vr4300_asm.h
Normal file
397
src/boot/vr4300_asm.h
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
#ifndef VR4300_ASM_H__
|
||||||
|
#define VR4300_ASM_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
uint32_t raw;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t op : 6;
|
||||||
|
uint32_t rs : 5;
|
||||||
|
uint32_t rt : 5;
|
||||||
|
uint32_t imm : 16;
|
||||||
|
} i_type;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t op : 6;
|
||||||
|
uint32_t target : 26;
|
||||||
|
} j_type;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t op : 6;
|
||||||
|
uint32_t rs : 5;
|
||||||
|
uint32_t rt : 5;
|
||||||
|
uint32_t rd : 5;
|
||||||
|
uint32_t sa : 5;
|
||||||
|
uint32_t funct : 6;
|
||||||
|
} r_type;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t op : 6;
|
||||||
|
uint32_t co : 1;
|
||||||
|
uint32_t funct : 25;
|
||||||
|
} c_type;
|
||||||
|
} vr4300_instruction_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OP_SPECIAL,
|
||||||
|
OP_REGIMM,
|
||||||
|
OP_J,
|
||||||
|
OP_JAL,
|
||||||
|
OP_BEQ,
|
||||||
|
OP_BNE,
|
||||||
|
OP_BLEZ,
|
||||||
|
OP_BGTZ,
|
||||||
|
OP_ADDI,
|
||||||
|
OP_ADDIU,
|
||||||
|
OP_SLTI,
|
||||||
|
OP_SLTIU,
|
||||||
|
OP_ANDI,
|
||||||
|
OP_ORI,
|
||||||
|
OP_XORI,
|
||||||
|
OP_LUI,
|
||||||
|
OP_COP0,
|
||||||
|
OP_COP1,
|
||||||
|
OP_COP2,
|
||||||
|
__OP_RESERVED_19,
|
||||||
|
OP_BEQL,
|
||||||
|
OP_BNEL,
|
||||||
|
OP_BLEZL,
|
||||||
|
OP_BGTZL,
|
||||||
|
OP_DADDI,
|
||||||
|
OP_DADDIU,
|
||||||
|
OP_LDL,
|
||||||
|
OP_LDR,
|
||||||
|
__OP_RESERVED_28,
|
||||||
|
__OP_RESERVED_29,
|
||||||
|
__OP_RESERVED_30,
|
||||||
|
__OP_RESERVED_31,
|
||||||
|
OP_LB,
|
||||||
|
OP_LH,
|
||||||
|
OP_LWL,
|
||||||
|
OP_LW,
|
||||||
|
OP_LBU,
|
||||||
|
OP_LHU,
|
||||||
|
OP_LWR,
|
||||||
|
OP_LWU,
|
||||||
|
OP_SB,
|
||||||
|
OP_SH,
|
||||||
|
OP_SWL,
|
||||||
|
OP_SW,
|
||||||
|
OP_SDL,
|
||||||
|
OP_SDR,
|
||||||
|
OP_SWR,
|
||||||
|
OP_CACHE,
|
||||||
|
OP_LL,
|
||||||
|
OP_LWC1,
|
||||||
|
OP_LWC2,
|
||||||
|
__OP_RESERVED_51,
|
||||||
|
OP_LLD,
|
||||||
|
OP_LDC1,
|
||||||
|
OP_LDC2,
|
||||||
|
OP_LD,
|
||||||
|
OP_SC,
|
||||||
|
OP_SWC1,
|
||||||
|
OP_SWC2,
|
||||||
|
__OP_RESERVED_59,
|
||||||
|
OP_SCD,
|
||||||
|
OP_SDC1,
|
||||||
|
OP_SDC2,
|
||||||
|
OP_SD,
|
||||||
|
} vr4300_op_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FUNCT_SSL,
|
||||||
|
__FUNCT_RESERVED_1,
|
||||||
|
FUNCT_SRL,
|
||||||
|
FUNCT_SRA,
|
||||||
|
FUNCT_SLLV,
|
||||||
|
__FUNCT_RESERVED_5,
|
||||||
|
FUNCT_SRLV,
|
||||||
|
FUNCT_SRAV,
|
||||||
|
FUNCT_JR,
|
||||||
|
FUNCT_JALR,
|
||||||
|
__FUNCT_RESERVED_10,
|
||||||
|
__FUNCT_RESERVED_11,
|
||||||
|
FUNCT_SYSCALL,
|
||||||
|
FUNCT_BREAK,
|
||||||
|
__FUNCT_RESERVED_14,
|
||||||
|
FUNCT_SYNC,
|
||||||
|
FUNCT_MFHI,
|
||||||
|
FUNCT_MTHI,
|
||||||
|
FUNCT_MFLO,
|
||||||
|
FUNCT_MTLO,
|
||||||
|
FUNCT_DSLLV,
|
||||||
|
__FUNCT_RESERVED_21,
|
||||||
|
FUNCT_DSRLV,
|
||||||
|
FUNCT_DSRAV,
|
||||||
|
FUNCT_MULT,
|
||||||
|
FUNCT_MULTU,
|
||||||
|
FUNCT_DIV,
|
||||||
|
FUNCT_DIVU,
|
||||||
|
FUNCT_DMULT,
|
||||||
|
FUNCT_DMULTU,
|
||||||
|
FUNCT_DDIV,
|
||||||
|
FUNCT_DDIVU,
|
||||||
|
FUNCT_ADD,
|
||||||
|
FUNCT_ADDU,
|
||||||
|
FUNCT_SUB,
|
||||||
|
FUNCT_SUBU,
|
||||||
|
FUNCT_AND,
|
||||||
|
FUNCT_OR,
|
||||||
|
FUNCT_XOR,
|
||||||
|
FUNCT_NOR,
|
||||||
|
__FUNCT_RESERVED_40,
|
||||||
|
__FUNCT_RESERVED_41,
|
||||||
|
FUNCT_SLT,
|
||||||
|
FUNCT_SLTU,
|
||||||
|
FUNCT_DADD,
|
||||||
|
FUNCT_DADDU,
|
||||||
|
FUNCT_DSUB,
|
||||||
|
FUNCT_DSUBU,
|
||||||
|
FUNCT_TGE,
|
||||||
|
FUNCT_TGEU,
|
||||||
|
FUNCT_TLT,
|
||||||
|
FUNCT_TLTU,
|
||||||
|
FUNCT_TEQ,
|
||||||
|
__FUNCT_RESERVED_53,
|
||||||
|
FUNCT_TNE,
|
||||||
|
__FUNCT_RESERVED_55,
|
||||||
|
FUNCT_DSLL,
|
||||||
|
__FUNCT_RESERVED_57,
|
||||||
|
FUNCT_DSRL,
|
||||||
|
FUNCT_DSRA,
|
||||||
|
FUNCT_DSLL32,
|
||||||
|
__FUNCT_RESERVED_61,
|
||||||
|
FUNCT_DSRL32,
|
||||||
|
FUNCT_DSRA32,
|
||||||
|
} vr4300_funct_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
REGIMM_BLTZ,
|
||||||
|
REGIMM_BGEZ,
|
||||||
|
REGIMM_BLTZL,
|
||||||
|
REGIMM_BGEZL,
|
||||||
|
__REGIMM_RESERVED_4,
|
||||||
|
__REGIMM_RESERVED_5,
|
||||||
|
__REGIMM_RESERVED_6,
|
||||||
|
__REGIMM_RESERVED_7,
|
||||||
|
REGIMM_TGEI,
|
||||||
|
REGIMM_TGEIU,
|
||||||
|
REGIMM_TLTI,
|
||||||
|
REGIMM_TLTIU,
|
||||||
|
REGIMM_TEQI,
|
||||||
|
__REGIMM_RESERVED_13,
|
||||||
|
REGIMM_TNEI,
|
||||||
|
__REGIMM_RESERVED_15,
|
||||||
|
REGIMM_BLTZAL,
|
||||||
|
REGIMM_BGEZAL,
|
||||||
|
REGIMM_BLTZALL,
|
||||||
|
REGIMM_BGEZALL,
|
||||||
|
__REGIMM_RESERVED_20,
|
||||||
|
__REGIMM_RESERVED_21,
|
||||||
|
__REGIMM_RESERVED_22,
|
||||||
|
__REGIMM_RESERVED_23,
|
||||||
|
__REGIMM_RESERVED_24,
|
||||||
|
__REGIMM_RESERVED_25,
|
||||||
|
__REGIMM_RESERVED_26,
|
||||||
|
__REGIMM_RESERVED_27,
|
||||||
|
__REGIMM_RESERVED_28,
|
||||||
|
__REGIMM_RESERVED_29,
|
||||||
|
__REGIMM_RESERVED_30,
|
||||||
|
__REGIMM_RESERVED_31,
|
||||||
|
} vr4300_regimm_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
REG_ZERO,
|
||||||
|
REG_AT,
|
||||||
|
REG_V0,
|
||||||
|
REG_V1,
|
||||||
|
REG_A0,
|
||||||
|
REG_A1,
|
||||||
|
REG_A2,
|
||||||
|
REG_A3,
|
||||||
|
REG_T0,
|
||||||
|
REG_T1,
|
||||||
|
REG_T2,
|
||||||
|
REG_T3,
|
||||||
|
REG_T4,
|
||||||
|
REG_T5,
|
||||||
|
REG_T6,
|
||||||
|
REG_T7,
|
||||||
|
REG_S0,
|
||||||
|
REG_S1,
|
||||||
|
REG_S2,
|
||||||
|
REG_S3,
|
||||||
|
REG_S4,
|
||||||
|
REG_S5,
|
||||||
|
REG_S6,
|
||||||
|
REG_S7,
|
||||||
|
REG_T8,
|
||||||
|
REG_T9,
|
||||||
|
REG_K0,
|
||||||
|
REG_K1,
|
||||||
|
REG_GP,
|
||||||
|
REG_SP,
|
||||||
|
REG_FP,
|
||||||
|
REG_RA,
|
||||||
|
} vr4300_reg_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
C0_REG_INDEX,
|
||||||
|
C0_REG_RANDOM,
|
||||||
|
C0_REG_ENTRY_LO_0,
|
||||||
|
C0_REG_ENTRY_LO_1,
|
||||||
|
C0_REG_CONTEXT,
|
||||||
|
C0_REG_PAGE_MASK,
|
||||||
|
C0_REG_WIRED,
|
||||||
|
__C0_REG_RESERVED_7,
|
||||||
|
C0_REG_BAD_V_ADDR,
|
||||||
|
C0_REG_COUNT,
|
||||||
|
C0_REG_ENTRY_HI,
|
||||||
|
C0_REG_COMPARE,
|
||||||
|
C0_REG_STATUS,
|
||||||
|
C0_REG_CAUSE,
|
||||||
|
C0_REG_EPC,
|
||||||
|
C0_REG_PR_ID,
|
||||||
|
C0_REG_CONFIG,
|
||||||
|
C0_REG_LL_ADDR,
|
||||||
|
C0_REG_WATCH_LO,
|
||||||
|
C0_REG_WATCH_HI,
|
||||||
|
C0_REG_X_CONTEXT,
|
||||||
|
__C0_REG_RESERVED_21,
|
||||||
|
__C0_REG_RESERVED_22,
|
||||||
|
__C0_REG_RESERVED_23,
|
||||||
|
__C0_REG_RESERVED_24,
|
||||||
|
__C0_REG_RESERVED_25,
|
||||||
|
C0_REG_PARITY_ERROR,
|
||||||
|
C0_REG_CACHE_ERROR,
|
||||||
|
C0_REG_TAG_LO,
|
||||||
|
C0_REG_TAG_HI,
|
||||||
|
C0_REG_ERROR_EPC,
|
||||||
|
__C0_REG_RESERVED_31,
|
||||||
|
} vr4300_c0_reg_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
COPZ_RS_MF,
|
||||||
|
COPZ_RS_DMF,
|
||||||
|
COPZ_RS_CF,
|
||||||
|
__COPZ_RS_RESERVED_3,
|
||||||
|
COPZ_RS_MT,
|
||||||
|
COPZ_RS_DMT,
|
||||||
|
COPZ_RS_CT,
|
||||||
|
__COPZ_RS_RESERVED_7,
|
||||||
|
COPZ_RS_BC,
|
||||||
|
__COPZ_RS_RESERVED_9,
|
||||||
|
__COPZ_RS_RESERVED_10,
|
||||||
|
__COPZ_RS_RESERVED_11,
|
||||||
|
__COPZ_RS_RESERVED_12,
|
||||||
|
__COPZ_RS_RESERVED_13,
|
||||||
|
__COPZ_RS_RESERVED_14,
|
||||||
|
__COPZ_RS_RESERVED_15,
|
||||||
|
} vr4300_copz_rs_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
__C0_FUNCT_RESERVED_0,
|
||||||
|
C0_FUNCT_TLBR,
|
||||||
|
C0_FUNCT_TLBWI,
|
||||||
|
__C0_FUNCT_RESERVED_3,
|
||||||
|
__C0_FUNCT_RESERVED_4,
|
||||||
|
__C0_FUNCT_RESERVED_5,
|
||||||
|
C0_FUNCT_TLBWR,
|
||||||
|
__C0_FUNCT_RESERVED_7,
|
||||||
|
C0_FUNCT_TLBP,
|
||||||
|
__C0_FUNCT_RESERVED_9,
|
||||||
|
__C0_FUNCT_RESERVED_10,
|
||||||
|
__C0_FUNCT_RESERVED_11,
|
||||||
|
__C0_FUNCT_RESERVED_12,
|
||||||
|
__C0_FUNCT_RESERVED_13,
|
||||||
|
__C0_FUNCT_RESERVED_14,
|
||||||
|
__C0_FUNCT_RESERVED_15,
|
||||||
|
__C0_FUNCT_RESERVED_16,
|
||||||
|
__C0_FUNCT_RESERVED_17,
|
||||||
|
__C0_FUNCT_RESERVED_18,
|
||||||
|
__C0_FUNCT_RESERVED_19,
|
||||||
|
__C0_FUNCT_RESERVED_20,
|
||||||
|
__C0_FUNCT_RESERVED_21,
|
||||||
|
__C0_FUNCT_RESERVED_22,
|
||||||
|
__C0_FUNCT_RESERVED_23,
|
||||||
|
C0_FUNCT_ERET,
|
||||||
|
__C0_FUNCT_RESERVED_25,
|
||||||
|
__C0_FUNCT_RESERVED_26,
|
||||||
|
__C0_FUNCT_RESERVED_27,
|
||||||
|
__C0_FUNCT_RESERVED_28,
|
||||||
|
__C0_FUNCT_RESERVED_29,
|
||||||
|
__C0_FUNCT_RESERVED_30,
|
||||||
|
__C0_FUNCT_RESERVED_31,
|
||||||
|
__C0_FUNCT_RESERVED_32,
|
||||||
|
__C0_FUNCT_RESERVED_33,
|
||||||
|
__C0_FUNCT_RESERVED_34,
|
||||||
|
__C0_FUNCT_RESERVED_35,
|
||||||
|
__C0_FUNCT_RESERVED_36,
|
||||||
|
__C0_FUNCT_RESERVED_37,
|
||||||
|
__C0_FUNCT_RESERVED_38,
|
||||||
|
__C0_FUNCT_RESERVED_39,
|
||||||
|
__C0_FUNCT_RESERVED_40,
|
||||||
|
__C0_FUNCT_RESERVED_41,
|
||||||
|
__C0_FUNCT_RESERVED_42,
|
||||||
|
__C0_FUNCT_RESERVED_43,
|
||||||
|
__C0_FUNCT_RESERVED_44,
|
||||||
|
__C0_FUNCT_RESERVED_45,
|
||||||
|
__C0_FUNCT_RESERVED_46,
|
||||||
|
__C0_FUNCT_RESERVED_47,
|
||||||
|
__C0_FUNCT_RESERVED_48,
|
||||||
|
__C0_FUNCT_RESERVED_49,
|
||||||
|
__C0_FUNCT_RESERVED_50,
|
||||||
|
__C0_FUNCT_RESERVED_51,
|
||||||
|
__C0_FUNCT_RESERVED_52,
|
||||||
|
__C0_FUNCT_RESERVED_53,
|
||||||
|
__C0_FUNCT_RESERVED_54,
|
||||||
|
__C0_FUNCT_RESERVED_55,
|
||||||
|
__C0_FUNCT_RESERVED_56,
|
||||||
|
__C0_FUNCT_RESERVED_57,
|
||||||
|
__C0_FUNCT_RESERVED_58,
|
||||||
|
__C0_FUNCT_RESERVED_59,
|
||||||
|
__C0_FUNCT_RESERVED_60,
|
||||||
|
__C0_FUNCT_RESERVED_61,
|
||||||
|
__C0_FUNCT_RESERVED_62,
|
||||||
|
__C0_FUNCT_RESERVED_63,
|
||||||
|
} vr4300_c0_funct;
|
||||||
|
|
||||||
|
#define __ASM_I_INST(o, s, t, i) \
|
||||||
|
(((vr4300_instruction_t){.i_type = {.op = (o), .rs = (s), .rt = (t), .imm = (i)&0xFFFF}}).raw)
|
||||||
|
#define __ASM_J_INST(o, t) (((vr4300_instruction_t){.j_type = {.op = (o), .target = (t)&0x3FFFFFF}}).raw)
|
||||||
|
#define __ASM_R_INST(o, s, t, d, a, f) \
|
||||||
|
(((vr4300_instruction_t){.r_type = {.op = (o), .rs = (s), .rt = (t), .rd = (d), .sa = (a), .funct = (f)}}).raw)
|
||||||
|
#define __ASM_C_INST(o, c, f) (((vr4300_instruction_t){.c_type = {.op = (o), .co = (c), .funct = (f)}}).raw)
|
||||||
|
|
||||||
|
#define A_OFFSET(a) ((int16_t)((a)&0xFFFF))
|
||||||
|
#define A_BASE(a) ((uint16_t)((((a) >> 16) & 0xFFFF) + (A_OFFSET(a) < 0 ? 1 : 0)))
|
||||||
|
|
||||||
|
#define I_ADDIU(rt, rs, immediate) __ASM_I_INST(OP_ADDIU, rs, rt, immediate)
|
||||||
|
#define I_AND(rd, rs, rt) __ASM_R_INST(OP_SPECIAL, rs, rt, rd, 0, FUNCT_AND)
|
||||||
|
#define I_ANDI(rt, rs, immediate) __ASM_I_INST(OP_ANDI, rs, rt, immediate)
|
||||||
|
#define I_BEQ(rs, rt, offset) __ASM_I_INST(OP_BEQ, rs, rt, offset)
|
||||||
|
#define I_BGTZ(rs, offset) __ASM_I_INST(OP_BGTZ, rs, 0, offset)
|
||||||
|
#define I_BNE(rs, rt, offset) __ASM_I_INST(OP_BNE, rs, rt, offset)
|
||||||
|
#define I_BNEL(rs, rt, offset) __ASM_I_INST(OP_BNEL, rs, rt, offset)
|
||||||
|
#define I_CACHE(op, offset, base) __ASM_I_INST(OP_CACHE, base, op, offset)
|
||||||
|
#define I_ERET() __ASM_C_INST(OP_COP0, 1, C0_FUNCT_ERET)
|
||||||
|
#define I_J(target) __ASM_J_INST(OP_J, (target >> 2))
|
||||||
|
#define I_JR(rs) __ASM_R_INST(OP_SPECIAL, rs, REG_ZERO, REG_ZERO, 0, FUNCT_JR)
|
||||||
|
#define I_LBU(rt, offset, base) __ASM_I_INST(OP_LBU, base, rt, offset)
|
||||||
|
#define I_LHU(rt, offset, base) __ASM_I_INST(OP_LHU, base, rt, offset)
|
||||||
|
#define I_LUI(rt, immediate) __ASM_I_INST(OP_LUI, 0, rt, immediate)
|
||||||
|
#define I_LW(rt, offset, base) __ASM_I_INST(OP_LW, base, rt, offset)
|
||||||
|
#define I_MFC0(rt, rd) __ASM_R_INST(OP_COP0, COPZ_RS_MF, rt, rd, 0, 0)
|
||||||
|
#define I_MTC0(rt, rd) __ASM_R_INST(OP_COP0, COPZ_RS_MT, rt, rd, 0, 0)
|
||||||
|
#define I_NOP() __ASM_R_INST(OP_SPECIAL, REG_ZERO, REG_ZERO, REG_ZERO, 0, FUNCT_SSL)
|
||||||
|
#define I_OR(rd, rs, rt) __ASM_R_INST(OP_SPECIAL, rs, rt, rd, 0, FUNCT_OR)
|
||||||
|
#define I_ORI(rt, rs, immediate) __ASM_I_INST(OP_ORI, rs, rt, immediate)
|
||||||
|
#define I_SB(rt, offset, base) __ASM_I_INST(OP_SB, base, rt, offset)
|
||||||
|
#define I_SH(rt, offset, base) __ASM_I_INST(OP_SH, base, rt, offset)
|
||||||
|
#define I_SRL(rd, rt, sa) __ASM_R_INST(OP_SPECIAL, 0, rt, rd, sa, FUNCT_SRL)
|
||||||
|
#define I_SW(rt, offset, base) __ASM_I_INST(OP_SW, base, rt, offset)
|
||||||
|
|
||||||
|
#endif
|
@ -83,11 +83,11 @@ static flashcart_err_t d64_load_rom (char *rom_path, flashcart_progress_callback
|
|||||||
FIL fil;
|
FIL fil;
|
||||||
UINT br;
|
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;
|
return FLASHCART_ERR_LOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_file_size(&fil);
|
fatfs_fix_file_size(&fil);
|
||||||
|
|
||||||
size_t rom_size = f_size(&fil);
|
size_t rom_size = f_size(&fil);
|
||||||
|
|
||||||
@ -125,11 +125,11 @@ static flashcart_err_t d64_load_file (char *file_path, uint32_t rom_offset, uint
|
|||||||
FIL fil;
|
FIL fil;
|
||||||
UINT br;
|
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;
|
return FLASHCART_ERR_LOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_file_size(&fil);
|
fatfs_fix_file_size(&fil);
|
||||||
|
|
||||||
size_t file_size = f_size(&fil) - file_offset;
|
size_t file_size = f_size(&fil) - file_offset;
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ static flashcart_err_t d64_load_save (char *save_path) {
|
|||||||
FIL fil;
|
FIL fil;
|
||||||
UINT br;
|
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;
|
return FLASHCART_ERR_LOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +251,13 @@ static flashcart_err_t d64_set_save_type (flashcart_save_type_t save_type) {
|
|||||||
return FLASHCART_OK;
|
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)) {
|
if (d64_ll_write_save_writeback_lba_list(sectors)) {
|
||||||
return FLASHCART_ERR_INT;
|
return FLASHCART_ERR_INT;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
### Official documentation
|
### Official documentation
|
||||||
|
|
||||||
http://64drive.retroactive.be/64drive_hardware_spec.pdf
|
https://web.archive.org/web/20220611032320/http://64drive.retroactive.be/64drive_hardware_spec.pdf
|
||||||
|
|
||||||
|
|
||||||
### Save location offset in SDRAM
|
### Save location offset in SDRAM
|
||||||
|
@ -8,14 +8,12 @@
|
|||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
|
||||||
#include "flashcart.h"
|
#include "flashcart.h"
|
||||||
|
#include "flashcart_utils.h"
|
||||||
|
|
||||||
#include "64drive/64drive.h"
|
#include "64drive/64drive.h"
|
||||||
#include "sc64/sc64.h"
|
#include "sc64/sc64.h"
|
||||||
|
|
||||||
|
|
||||||
#define SAVE_WRITEBACK_MAX_SECTORS (256)
|
|
||||||
|
|
||||||
|
|
||||||
static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = {
|
static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = {
|
||||||
0,
|
0,
|
||||||
512,
|
512,
|
||||||
@ -27,34 +25,44 @@ static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = {
|
|||||||
KiB(128),
|
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) {
|
static bool dummy_has_feature (flashcart_features_t feature) {
|
||||||
for (uint32_t i = 0; i < cluster_size; i++) {
|
switch (feature) {
|
||||||
uint32_t offset = file_sector + i;
|
default:
|
||||||
uint32_t sector = cluster_sector + i;
|
return false;
|
||||||
|
|
||||||
if ((offset > SAVE_WRITEBACK_MAX_SECTORS) || (offset > sector_count)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
save_writeback_sectors[offset] = sector;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return FLASHCART_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static flashcart_t *flashcart = &((flashcart_t) {
|
static flashcart_t *flashcart = &((flashcart_t) {
|
||||||
.init = dummy_init,
|
.init = dummy_init,
|
||||||
.deinit = NULL,
|
.deinit = NULL,
|
||||||
.load_rom = NULL,
|
.has_feature = dummy_has_feature,
|
||||||
.load_file = NULL,
|
.load_rom = dummy_load_rom,
|
||||||
.load_save = NULL,
|
.load_file = dummy_load_file,
|
||||||
.set_save_type = NULL,
|
.load_save = dummy_load_save,
|
||||||
|
.load_64dd_ipl = NULL,
|
||||||
|
.load_64dd_disk = NULL,
|
||||||
|
.set_save_type = dummy_set_save_type,
|
||||||
.set_save_writeback = NULL,
|
.set_save_writeback = NULL,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,9 +77,9 @@ static flashcart_t *flashcart = &((flashcart_t) {
|
|||||||
char *flashcart_convert_error_message (flashcart_err_t err) {
|
char *flashcart_convert_error_message (flashcart_err_t err) {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case FLASHCART_OK: return "No error";
|
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_OUTDATED: return "Outdated flashcart firmware";
|
||||||
case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization";
|
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_ARGS: return "Invalid argument passed to flashcart function";
|
||||||
case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart";
|
case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart";
|
||||||
case FLASHCART_ERR_INT: return "Internal flashcart error";
|
case FLASHCART_ERR_INT: return "Internal flashcart error";
|
||||||
@ -80,15 +88,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;
|
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
|
*storage_prefix = "sd:/";
|
||||||
// NOTE: Some flashcarts doesn't have USB port, can't throw error here
|
bool sd_card_initialized = debug_init_sdfs(*storage_prefix, -1);
|
||||||
debug_init_usblog();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
switch (cart_type) {
|
switch (cart_type) {
|
||||||
case CART_CI: // 64drive
|
case CART_CI: // 64drive
|
||||||
@ -96,6 +109,8 @@ flashcart_err_t flashcart_init (void) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CART_EDX: // Series X EverDrive-64
|
case CART_EDX: // Series X EverDrive-64
|
||||||
|
break;
|
||||||
|
|
||||||
case CART_ED: // Original EverDrive-64
|
case CART_ED: // Original EverDrive-64
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -103,15 +118,22 @@ flashcart_err_t flashcart_init (void) {
|
|||||||
flashcart = sc64_get_flashcart();
|
flashcart = sc64_get_flashcart();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default: // Probably emulator
|
||||||
return FLASHCART_ERR_NOT_DETECTED;
|
*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) {
|
if ((err = flashcart->init()) != FLASHCART_OK) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sd_card_initialized) {
|
if ((cart_type != CART_NULL) && (!sd_card_initialized)) {
|
||||||
return FLASHCART_ERR_SD_CARD;
|
return FLASHCART_ERR_SD_CARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,19 +206,11 @@ flashcart_err_t flashcart_load_save (char *save_path, flashcart_save_type_t save
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flashcart->set_save_writeback) {
|
if (!flashcart->set_save_writeback) {
|
||||||
for (int i = 0; i < SAVE_WRITEBACK_MAX_SECTORS; i++) {
|
return FLASHCART_OK;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
flashcart_err_t flashcart_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress) {
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
/** @brief Flashcart error enumeration */
|
/** @brief Flashcart error enumeration */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FLASHCART_OK,
|
FLASHCART_OK,
|
||||||
FLASHCART_ERR_NOT_DETECTED,
|
|
||||||
FLASHCART_ERR_OUTDATED,
|
FLASHCART_ERR_OUTDATED,
|
||||||
FLASHCART_ERR_SD_CARD,
|
FLASHCART_ERR_SD_CARD,
|
||||||
|
FLASHCART_ERR_BBFS,
|
||||||
FLASHCART_ERR_ARGS,
|
FLASHCART_ERR_ARGS,
|
||||||
FLASHCART_ERR_LOAD,
|
FLASHCART_ERR_LOAD,
|
||||||
FLASHCART_ERR_INT,
|
FLASHCART_ERR_INT,
|
||||||
@ -75,12 +75,12 @@ typedef struct {
|
|||||||
/** @brief The flashcart set save type function */
|
/** @brief The flashcart set save type function */
|
||||||
flashcart_err_t (*set_save_type) (flashcart_save_type_t save_type);
|
flashcart_err_t (*set_save_type) (flashcart_save_type_t save_type);
|
||||||
/** @brief The flashcart set save writeback function */
|
/** @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;
|
} flashcart_t;
|
||||||
|
|
||||||
|
|
||||||
char *flashcart_convert_error_message (flashcart_err_t err);
|
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);
|
flashcart_err_t flashcart_deinit (void);
|
||||||
bool flashcart_has_feature (flashcart_features_t feature);
|
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);
|
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"
|
#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) {
|
void pi_dma_read_data (void *src, void *dst, size_t length) {
|
||||||
data_cache_hit_writeback_invalidate(dst, length);
|
data_cache_hit_writeback_invalidate(dst, length);
|
||||||
dma_read_async(dst, (uint32_t) (src), 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_write_raw_async(src, (uint32_t) (dst), length);
|
||||||
dma_wait();
|
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__
|
#define FLASHCART_UTILS_H__
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <fatfs/ff.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_read_data (void *src, void *dst, size_t length);
|
||||||
void pi_dma_write_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
|
#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 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) {
|
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;
|
size_t erase_block_size;
|
||||||
UINT bp;
|
UINT bp;
|
||||||
@ -110,23 +107,6 @@ static flashcart_err_t load_to_flash (FIL *fil, void *address, size_t size, UINT
|
|||||||
return FLASHCART_OK;
|
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) {
|
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++) {
|
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);
|
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;
|
int file_offset = 0;
|
||||||
|
|
||||||
uint16_t lba = 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;
|
uint16_t track = track_offset + zone_track;
|
||||||
|
|
||||||
if (disk_zone_track_is_bad(pzone, zone_track, disk_parameters)) {
|
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(*current_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, 1, false, false, 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint8_t block = 0; block < DISK_BLOCKS; block += 1) {
|
for (uint8_t block = 0; block < DISK_BLOCKS; block += 1) {
|
||||||
bool valid = !(disk_system_lba_is_bad(lba, disk_parameters));
|
bool valid = !(disk_system_lba_is_bad(lba, disk_parameters));
|
||||||
bool writable = (vzone >= rom_zones[disk_parameters->disk_type]);
|
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);
|
file_offset += (sector_length * DISK_SECTORS_PER_BLOCK);
|
||||||
lba += 1;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -270,11 +262,11 @@ static flashcart_err_t sc64_load_rom (char *rom_path, flashcart_progress_callbac
|
|||||||
FIL fil;
|
FIL fil;
|
||||||
UINT br;
|
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;
|
return FLASHCART_ERR_LOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_file_size(&fil);
|
fatfs_fix_file_size(&fil);
|
||||||
|
|
||||||
size_t rom_size = f_size(&fil);
|
size_t rom_size = f_size(&fil);
|
||||||
|
|
||||||
@ -351,11 +343,11 @@ static flashcart_err_t sc64_load_file (char *file_path, uint32_t rom_offset, uin
|
|||||||
FIL fil;
|
FIL fil;
|
||||||
UINT br;
|
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;
|
return FLASHCART_ERR_LOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_file_size(&fil);
|
fatfs_fix_file_size(&fil);
|
||||||
|
|
||||||
size_t file_size = f_size(&fil) - file_offset;
|
size_t file_size = f_size(&fil) - file_offset;
|
||||||
|
|
||||||
@ -413,7 +405,7 @@ static flashcart_err_t sc64_load_save (char *save_path) {
|
|||||||
FIL fil;
|
FIL fil;
|
||||||
UINT br;
|
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;
|
return FLASHCART_ERR_LOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,11 +431,11 @@ static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_ca
|
|||||||
FIL fil;
|
FIL fil;
|
||||||
UINT br;
|
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;
|
return FLASHCART_ERR_LOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_file_size(&fil);
|
fatfs_fix_file_size(&fil);
|
||||||
|
|
||||||
size_t ipl_size = f_size(&fil);
|
size_t ipl_size = f_size(&fil);
|
||||||
|
|
||||||
@ -476,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) {
|
static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters) {
|
||||||
sc64_disk_mapping_t mapping = { .count = 0 };
|
sc64_disk_mapping_t mapping;
|
||||||
uint32_t next_mapping_offset = DISK_MAPPING_ROM_OFFSET;
|
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
|
// TODO: Support loading multiple disks
|
||||||
// LOOP START
|
for (mapping.count = 0; mapping.count < 1; mapping.count++) {
|
||||||
mapping.disks[mapping.count].thb_table = next_mapping_offset;
|
disk_load_thb_table(disk_parameters++, &mapping.disks[mapping.count].thb_table, &mapping_offset);
|
||||||
mapping.disks[mapping.count].sector_table = disk_load_thb_table(mapping.disks[mapping.count].thb_table, disk_parameters);
|
if (disk_load_sector_table(disk_path++, &mapping.disks[mapping.count].sector_table, &mapping_offset)) {
|
||||||
next_mapping_offset = disk_sectors_start(mapping.disks[mapping.count].sector_table);
|
|
||||||
if (file_get_sectors(disk_path, disk_sectors_callback)) {
|
|
||||||
return FLASHCART_ERR_LOAD;
|
return FLASHCART_ERR_LOAD;
|
||||||
}
|
}
|
||||||
mapping.count += 1;
|
}
|
||||||
// LOOP END
|
|
||||||
|
|
||||||
if (mapping.count == 0) {
|
if (mapping.count == 0) {
|
||||||
return FLASHCART_ERR_ARGS;
|
return FLASHCART_ERR_ARGS;
|
||||||
@ -498,8 +488,6 @@ static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_para
|
|||||||
return FLASHCART_ERR_INT;
|
return FLASHCART_ERR_INT;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc64_drive_type_t drive_type = disk_parameters->development_drive ? DRIVE_TYPE_DEVELOPMENT : DRIVE_TYPE_RETAIL;
|
|
||||||
|
|
||||||
const struct {
|
const struct {
|
||||||
sc64_cfg_id_t id;
|
sc64_cfg_id_t id;
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
@ -559,7 +547,13 @@ static flashcart_err_t sc64_set_save_type (flashcart_save_type_t save_type) {
|
|||||||
return FLASHCART_OK;
|
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);
|
pi_dma_write_data(sectors, SC64_BUFFERS->BUFFER, 1024);
|
||||||
|
|
||||||
if (sc64_ll_writeback_enable(SC64_BUFFERS->BUFFER) != SC64_OK) {
|
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) {
|
static bool is_64dd_connected (void) {
|
||||||
return (
|
bool is_64dd_io_present = ((io_read(0x05000540) & 0x0000FFFF) == 0x0000);
|
||||||
((io_read(0x05000540) & 0x0000FFFF) == 0x0000) ||
|
bool is_64dd_ipl_present = (io_read(0x06001010) == 0x2129FFF8);
|
||||||
(io_read(0x06001010) == 0x2129FFF8)
|
return (is_64dd_io_present || is_64dd_ipl_present);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool create_saves_subdirectory (path_t *path) {
|
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;
|
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;
|
flashcart_disk_parameters_t disk_parameters;
|
||||||
|
|
||||||
disk_parameters.development_drive = (menu->load.disk_info.region == DISK_REGION_DEVELOPMENT);
|
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) {
|
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;
|
flashcart_save_type_t save_type = FLASHCART_SAVE_TYPE_NONE;
|
||||||
uint32_t emulated_rom_offset = 0x200000;
|
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_seekbar_draw (float progress);
|
||||||
void component_loader_draw (float position);
|
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_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_dialog_draw (int width, int height);
|
||||||
void component_messagebox_draw (char *fmt, ...);
|
void component_messagebox_draw (char *fmt, ...);
|
||||||
void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
|
void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
|
||||||
@ -64,7 +64,7 @@ typedef struct {
|
|||||||
surface_t *image;
|
surface_t *image;
|
||||||
} component_boxart_t;
|
} 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_free (component_boxart_t *b);
|
||||||
void component_boxart_draw (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 <stdlib.h>
|
||||||
|
|
||||||
#include "../components.h"
|
#include "../components.h"
|
||||||
@ -31,22 +31,21 @@ static void load_from_cache (component_background_t *c) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FIL fil;
|
FILE *f;
|
||||||
UINT bytes_read;
|
|
||||||
|
|
||||||
if (f_open(&fil, strip_sd_prefix(c->cache_location), FA_READ) != FR_OK) {
|
if ((f = fopen(c->cache_location, "rb")) == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_metadata_t cache_metadata;
|
cache_metadata_t cache_metadata;
|
||||||
|
|
||||||
if ((f_read(&fil, &cache_metadata, sizeof(cache_metadata), &bytes_read) != FR_OK) || (bytes_read != sizeof(cache_metadata))) {
|
if (fread(&cache_metadata, sizeof(cache_metadata), 1, f) != 1) {
|
||||||
f_close(&fil);
|
fclose(f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cache_metadata.magic != CACHE_METADATA_MAGIC || cache_metadata.width > DISPLAY_WIDTH || cache_metadata.height > DISPLAY_HEIGHT) {
|
if (cache_metadata.magic != CACHE_METADATA_MAGIC || cache_metadata.width > DISPLAY_WIDTH || cache_metadata.height > DISPLAY_HEIGHT) {
|
||||||
f_close(&fil);
|
fclose(f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,17 +56,17 @@ static void load_from_cache (component_background_t *c) {
|
|||||||
surface_free(c->image);
|
surface_free(c->image);
|
||||||
free(c->image);
|
free(c->image);
|
||||||
c->image = NULL;
|
c->image = NULL;
|
||||||
f_close(&fil);
|
fclose(f);
|
||||||
return;
|
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);
|
surface_free(c->image);
|
||||||
free(c->image);
|
free(c->image);
|
||||||
c->image = NULL;
|
c->image = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
f_close(&fil);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void save_to_cache (component_background_t *c) {
|
static void save_to_cache (component_background_t *c) {
|
||||||
@ -75,9 +74,9 @@ static void save_to_cache (component_background_t *c) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FIL fil;
|
FILE *f;
|
||||||
UINT bytes_written;
|
|
||||||
if (f_open(&fil, strip_sd_prefix(c->cache_location), FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) {
|
if ((f = fopen(c->cache_location, "wb")) == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,10 +87,10 @@ static void save_to_cache (component_background_t *c) {
|
|||||||
.size = (c->image->height * c->image->stride),
|
.size = (c->image->height * c->image->stride),
|
||||||
};
|
};
|
||||||
|
|
||||||
f_write(&fil, &cache_metadata, sizeof(cache_metadata), &bytes_written);
|
fwrite(&cache_metadata, sizeof(cache_metadata), 1, f);
|
||||||
f_write(&fil, c->image->buffer, cache_metadata.size, &bytes_written);
|
fwrite(c->image->buffer, cache_metadata.size, 1, f);
|
||||||
|
|
||||||
f_close(&fil);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prepare_background (component_background_t *c) {
|
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) {
|
void component_background_init (char *cache_location) {
|
||||||
if (!background) {
|
if (!background) {
|
||||||
background = calloc(1, sizeof(component_background_t));
|
background = calloc(1, sizeof(component_background_t));
|
||||||
background->cache_location = cache_location;
|
background->cache_location = strdup(cache_location);
|
||||||
load_from_cache(background);
|
load_from_cache(background);
|
||||||
prepare_background(background);
|
prepare_background(background);
|
||||||
}
|
}
|
||||||
@ -183,6 +182,9 @@ void component_background_free (void) {
|
|||||||
rdpq_call_deferred(display_list_free, background->image_display_list);
|
rdpq_call_deferred(display_list_free, background->image_display_list);
|
||||||
background->image_display_list = NULL;
|
background->image_display_list = NULL;
|
||||||
}
|
}
|
||||||
|
if (background->cache_location) {
|
||||||
|
free(background->cache_location);
|
||||||
|
}
|
||||||
free(background);
|
free(background);
|
||||||
background = NULL;
|
background = NULL;
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,8 @@
|
|||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
#include "utils/fs.h"
|
#include "utils/fs.h"
|
||||||
|
|
||||||
#ifndef BOXART_DIRECTORY
|
|
||||||
#define BOXART_DIRECTORY "sd:/menu/boxart"
|
#define BOXART_DIRECTORY "menu/boxart"
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
|
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 *component_boxart_init (const char *storage_prefix, char *game_code) {
|
||||||
component_boxart_t *b = calloc(1, sizeof(component_boxart_t));
|
component_boxart_t *b;
|
||||||
|
char file_name[8];
|
||||||
|
|
||||||
if (b) {
|
if ((b = calloc(1, sizeof(component_boxart_t))) == NULL) {
|
||||||
b->loading = true;
|
return NULL;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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(
|
component_scrollbar_draw(
|
||||||
FILE_LIST_SCROLLBAR_X,
|
LIST_SCROLLBAR_X,
|
||||||
FILE_LIST_SCROLLBAR_Y,
|
LIST_SCROLLBAR_Y,
|
||||||
FILE_LIST_SCROLLBAR_WIDTH,
|
LIST_SCROLLBAR_WIDTH,
|
||||||
FILE_LIST_SCROLLBAR_HEIGHT,
|
LIST_SCROLLBAR_HEIGHT,
|
||||||
position,
|
position,
|
||||||
items,
|
items,
|
||||||
visible_items
|
visible_items
|
||||||
@ -121,6 +121,10 @@ void component_messagebox_draw (char *fmt, ...) {
|
|||||||
.wrap = WRAP_WORD
|
.wrap = WRAP_WORD
|
||||||
}, FNT_DEFAULT, formatted, ¶graph_nbytes);
|
}, FNT_DEFAULT, formatted, ¶graph_nbytes);
|
||||||
|
|
||||||
|
if (formatted != buffer) {
|
||||||
|
free(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
component_dialog_draw(
|
component_dialog_draw(
|
||||||
paragraph->bbox.x1 - paragraph->bbox.x0 + MESSAGEBOX_MARGIN,
|
paragraph->bbox.x1 - paragraph->bbox.x0 + MESSAGEBOX_MARGIN,
|
||||||
paragraph->bbox.y1 - paragraph->bbox.y0 + 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,
|
formatted,
|
||||||
nbytes
|
nbytes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (formatted != buffer) {
|
||||||
|
free(formatted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) {
|
void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) {
|
||||||
@ -179,4 +187,8 @@ void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign,
|
|||||||
formatted,
|
formatted,
|
||||||
nbytes
|
nbytes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (formatted != buffer) {
|
||||||
|
free(formatted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,19 +77,19 @@
|
|||||||
#define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24)
|
#define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24)
|
||||||
|
|
||||||
/** @brief The scroll bar width. */
|
/** @brief The scroll bar width. */
|
||||||
#define FILE_LIST_SCROLLBAR_WIDTH (12)
|
#define LIST_SCROLLBAR_WIDTH (12)
|
||||||
/** @brief The scroll bar height. */
|
/** @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. */
|
/** @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. */
|
/** @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. */
|
/** @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. */
|
/** @brief The maximum width available for a file list entry. */
|
||||||
#define FILE_LIST_MAX_WIDTH (480)
|
#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)
|
#define FILE_LIST_HIGHLIGHT_X (VISIBLE_AREA_X0)
|
||||||
|
|
||||||
/** @brief The default background colour. */
|
/** @brief The default background colour. */
|
||||||
|
@ -8,15 +8,19 @@
|
|||||||
static const char *dir_prefix = "/";
|
static const char *dir_prefix = "/";
|
||||||
|
|
||||||
|
|
||||||
static int format_file_size (char *buffer, int size) {
|
static int format_file_size (char *buffer, int64_t size) {
|
||||||
if (size < 8 * 1024) {
|
if (size < 0) {
|
||||||
return sprintf(buffer, "%d B", size);
|
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) {
|
} 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) {
|
} else if (size < 1 * 1024 * 1024 * 1024) {
|
||||||
return sprintf(buffer, "%d MB", size / 1024 / 1024);
|
return sprintf(buffer, "%lld MB", size / 1024 / 1024);
|
||||||
} else {
|
} else {
|
||||||
return sprintf(buffer, "%d GB", size / 1024 / 1024 / 1024);
|
return sprintf(buffer, "%lld GB", size / 1024 / 1024 / 1024);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,14 +28,14 @@ static int format_file_size (char *buffer, int size) {
|
|||||||
void component_file_list_draw (entry_t *list, int entries, int selected) {
|
void component_file_list_draw (entry_t *list, int entries, int selected) {
|
||||||
int starting_position = 0;
|
int starting_position = 0;
|
||||||
|
|
||||||
if (entries > FILE_LIST_ENTRIES && selected >= (FILE_LIST_ENTRIES / 2)) {
|
if (entries > LIST_ENTRIES && selected >= (LIST_ENTRIES / 2)) {
|
||||||
starting_position = selected - (FILE_LIST_ENTRIES / 2);
|
starting_position = selected - (LIST_ENTRIES / 2);
|
||||||
if (starting_position >= entries - FILE_LIST_ENTRIES) {
|
if (starting_position >= entries - LIST_ENTRIES) {
|
||||||
starting_position = entries - FILE_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) {
|
if (entries == 0) {
|
||||||
component_main_text_draw(
|
component_main_text_draw(
|
||||||
@ -43,10 +47,10 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
|||||||
rdpq_paragraph_t *file_list_layout;
|
rdpq_paragraph_t *file_list_layout;
|
||||||
rdpq_paragraph_t *layout;
|
rdpq_paragraph_t *layout;
|
||||||
|
|
||||||
size_t name_lengths[FILE_LIST_ENTRIES];
|
size_t name_lengths[LIST_ENTRIES];
|
||||||
size_t total_length = 1;
|
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;
|
int entry_index = starting_position + i;
|
||||||
|
|
||||||
if (entry_index >= entries) {
|
if (entry_index >= entries) {
|
||||||
@ -72,7 +76,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
|||||||
file_list_layout
|
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;
|
int entry_index = starting_position + i;
|
||||||
|
|
||||||
entry_t *entry = &list[entry_index];
|
entry_t *entry = &list[entry_index];
|
||||||
@ -130,7 +134,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
|||||||
|
|
||||||
rdpq_paragraph_builder_begin(
|
rdpq_paragraph_builder_begin(
|
||||||
&(rdpq_textparms_t) {
|
&(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),
|
.height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2),
|
||||||
.align = ALIGN_RIGHT,
|
.align = ALIGN_RIGHT,
|
||||||
.wrap = WRAP_ELLIPSES,
|
.wrap = WRAP_ELLIPSES,
|
||||||
@ -139,7 +143,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
|||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
char file_size[8];
|
char file_size[16];
|
||||||
|
|
||||||
for (int i = starting_position; i < entries; i++) {
|
for (int i = starting_position; i < entries; i++) {
|
||||||
entry_t *entry = &list[i];
|
entry_t *entry = &list[i];
|
||||||
@ -148,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));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include <fatfs/ff.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "disk_info.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) {
|
static bool load_system_area_lba (FILE *f, int lba, uint8_t *buffer) {
|
||||||
UINT bytes_read;
|
|
||||||
if (lba >= SYSTEM_AREA_LBA_COUNT) {
|
if (lba >= SYSTEM_AREA_LBA_COUNT) {
|
||||||
return true;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
if (f_read(fil, buffer, SYSTEM_AREA_LBA_LENGTH, &bytes_read) != FR_OK) {
|
if (fread(buffer, SYSTEM_AREA_LBA_LENGTH, 1, f) != 1) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (bytes_read != SYSTEM_AREA_LBA_LENGTH) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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];
|
uint8_t buffer[SYSTEM_AREA_LBA_LENGTH];
|
||||||
int sector_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++) {
|
for (int i = 0; i < SYSTEM_DATA_LBA_COUNT; i++) {
|
||||||
int lba = system_data_lbas[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;
|
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;
|
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];
|
uint8_t buffer[SYSTEM_AREA_LBA_LENGTH];
|
||||||
|
|
||||||
bool valid_disk_id_lba_found = false;
|
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++) {
|
for (int i = 0; i < DISK_ID_LBA_COUNT; i++) {
|
||||||
int lba = disk_id_lbas[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;
|
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) {
|
disk_err_t disk_info_load (path_t *path, disk_info_t *disk_info) {
|
||||||
FIL fil;
|
FILE *f;
|
||||||
disk_err_t err;
|
disk_err_t err;
|
||||||
|
|
||||||
for (int i = 0; i < SYSTEM_AREA_LBA_COUNT; i++) {
|
for (int i = 0; i < SYSTEM_AREA_LBA_COUNT; i++) {
|
||||||
disk_info->bad_system_area_lbas[i] = false;
|
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;
|
return DISK_ERR_NO_FILE;
|
||||||
}
|
}
|
||||||
|
setbuf(f, NULL);
|
||||||
if ((err = load_and_verify_system_data_lba(&fil, disk_info)) != DISK_OK) {
|
if ((err = load_and_verify_system_data_lba(f, disk_info)) != DISK_OK) {
|
||||||
f_close(&fil);
|
fclose(f);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
if ((err = load_and_verify_disk_id_lba(f, disk_info)) != DISK_OK) {
|
||||||
if ((err = load_and_verify_disk_id_lba(&fil, disk_info)) != DISK_OK) {
|
fclose(f);
|
||||||
f_close(&fil);
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
if (fclose(f)) {
|
||||||
if (f_close(&fil) != FR_OK) {
|
|
||||||
return DISK_ERR_IO;
|
return DISK_ERR_IO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "path.h"
|
||||||
|
|
||||||
|
|
||||||
/** @brief Disk state enumeration. */
|
/** @brief Disk state enumeration. */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
DISK_OK,
|
DISK_OK,
|
||||||
@ -49,7 +52,7 @@ typedef struct {
|
|||||||
} disk_info_t;
|
} 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
|
#endif
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
#include <libdragon.h>
|
#include <libdragon.h>
|
||||||
|
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
|
#include "utils/fs.h"
|
||||||
|
|
||||||
|
|
||||||
static void load_default_font (void) {
|
static void load_default_font (char *custom_font_path) {
|
||||||
rdpq_font_t *default_font = rdpq_font_load("rom:/FiraMonoBold.font64");
|
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_DEFAULT, &((rdpq_fontstyle_t) { .color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF) }));
|
||||||
rdpq_font_style(default_font, STL_GREEN, &((rdpq_fontstyle_t) { .color = RGBA32(0x70, 0xFF, 0x70, 0xFF) }));
|
rdpq_font_style(default_font, STL_GREEN, &((rdpq_fontstyle_t) { .color = RGBA32(0x70, 0xFF, 0x70, 0xFF) }));
|
||||||
@ -17,6 +24,6 @@ static void load_default_font (void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void fonts_init (void) {
|
void fonts_init (char *custom_font_path) {
|
||||||
load_default_font();
|
load_default_font(custom_font_path);
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ typedef enum {
|
|||||||
} menu_font_style_t;
|
} menu_font_style_t;
|
||||||
|
|
||||||
|
|
||||||
void fonts_init (void);
|
void fonts_init (char *custom_font_path);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -20,12 +20,15 @@
|
|||||||
#include "views/views.h"
|
#include "views/views.h"
|
||||||
|
|
||||||
|
|
||||||
#define MENU_DIRECTORY "sd:/menu"
|
#define MENU_DIRECTORY "/menu"
|
||||||
#define CACHE_DIRECTORY "sd:/menu/cache"
|
#define MENU_SETTINGS_FILE "config.ini"
|
||||||
#define BACKGROUND_CACHE "sd:/menu/cache/background.data"
|
#define MENU_CUSTOM_FONT_FILE "custom.font64"
|
||||||
|
|
||||||
#define FRAMERATE_DIVIDER (2)
|
#define MENU_CACHE_DIRECTORY "cache"
|
||||||
#define LAG_REPORT (false)
|
#define BACKGROUND_CACHE_FILE "background.data"
|
||||||
|
|
||||||
|
#define FRAMERATE_DIVIDER (2)
|
||||||
|
#define LAG_REPORT (false)
|
||||||
|
|
||||||
|
|
||||||
static menu_t *menu;
|
static menu_t *menu;
|
||||||
@ -62,7 +65,6 @@ static void menu_init (boot_params_t *boot_params) {
|
|||||||
rdpq_init();
|
rdpq_init();
|
||||||
dfs_init(DFS_DEFAULT_LOCATION);
|
dfs_init(DFS_DEFAULT_LOCATION);
|
||||||
|
|
||||||
fonts_init();
|
|
||||||
sound_init_default();
|
sound_init_default();
|
||||||
|
|
||||||
menu = calloc(1, sizeof(menu_t));
|
menu = calloc(1, sizeof(menu_t));
|
||||||
@ -71,31 +73,39 @@ static void menu_init (boot_params_t *boot_params) {
|
|||||||
menu->mode = MENU_MODE_NONE;
|
menu->mode = MENU_MODE_NONE;
|
||||||
menu->next_mode = MENU_MODE_STARTUP;
|
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) {
|
if (menu->flashcart_err != FLASHCART_OK) {
|
||||||
menu->next_mode = MENU_MODE_FAULT;
|
menu->next_mode = MENU_MODE_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->error_message = NULL;
|
path_t *path = path_init(menu->storage_prefix, MENU_DIRECTORY);
|
||||||
|
|
||||||
directory_create(MENU_DIRECTORY);
|
directory_create(path_get(path));
|
||||||
|
|
||||||
|
path_push(path, MENU_SETTINGS_FILE);
|
||||||
|
settings_init(path_get(path));
|
||||||
settings_load(&menu->settings);
|
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;
|
menu->boot_params = boot_params;
|
||||||
|
|
||||||
bool default_directory_exists = directory_exists(menu->settings.default_directory);
|
menu->browser.directory = path_init(menu->storage_prefix, menu->settings.default_directory);
|
||||||
char *init_directory = default_directory_exists ? menu->settings.default_directory : "";
|
if (!directory_exists(path_get(menu->browser.directory))) {
|
||||||
|
path_free(menu->browser.directory);
|
||||||
menu->browser.valid = false;
|
menu->browser.directory = path_init(menu->storage_prefix, "/");
|
||||||
menu->browser.reload = false;
|
}
|
||||||
menu->browser.directory = path_init("sd:/", init_directory);
|
|
||||||
|
|
||||||
menu->load.rom_path = NULL;
|
|
||||||
|
|
||||||
hdmi_clear_game_id();
|
hdmi_clear_game_id();
|
||||||
|
|
||||||
@ -118,6 +128,12 @@ static void menu_deinit (menu_t *menu) {
|
|||||||
|
|
||||||
hdmi_send_game_id(menu->boot_params);
|
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);
|
path_free(menu->browser.directory);
|
||||||
free(menu);
|
free(menu);
|
||||||
|
|
||||||
|
@ -19,9 +19,6 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
|
||||||
#define BROWSER_LIST_SIZE 2048
|
|
||||||
|
|
||||||
|
|
||||||
/** @brief Menu mode enumeration */
|
/** @brief Menu mode enumeration */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
MENU_MODE_NONE,
|
MENU_MODE_NONE,
|
||||||
@ -63,7 +60,7 @@ typedef enum {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
char *name;
|
char *name;
|
||||||
entry_type_t type;
|
entry_type_t type;
|
||||||
int size;
|
int64_t size;
|
||||||
} entry_t;
|
} entry_t;
|
||||||
|
|
||||||
/** @brief Menu Structure */
|
/** @brief Menu Structure */
|
||||||
@ -71,6 +68,7 @@ typedef struct {
|
|||||||
menu_mode_t mode;
|
menu_mode_t mode;
|
||||||
menu_mode_t next_mode;
|
menu_mode_t next_mode;
|
||||||
|
|
||||||
|
const char *storage_prefix;
|
||||||
settings_t settings;
|
settings_t settings;
|
||||||
boot_params_t *boot_params;
|
boot_params_t *boot_params;
|
||||||
|
|
||||||
@ -96,7 +94,7 @@ typedef struct {
|
|||||||
bool valid;
|
bool valid;
|
||||||
bool reload;
|
bool reload;
|
||||||
path_t *directory;
|
path_t *directory;
|
||||||
entry_t list[BROWSER_LIST_SIZE];
|
entry_t *list;
|
||||||
int entries;
|
int entries;
|
||||||
entry_t *entry;
|
entry_t *entry;
|
||||||
int selected;
|
int selected;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#include <fatfs/ff.h>
|
#include <stdio.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <libdragon.h>
|
#include <libdragon.h>
|
||||||
|
|
||||||
#include "mp3_player.h"
|
#include "mp3_player.h"
|
||||||
@ -18,19 +20,18 @@
|
|||||||
/** @brief MP3 File Information Structure. */
|
/** @brief MP3 File Information Structure. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool loaded;
|
bool loaded;
|
||||||
bool io_error;
|
|
||||||
|
|
||||||
FIL fil;
|
|
||||||
FSIZE_t data_start;
|
|
||||||
int seek_predecode_frames;
|
|
||||||
|
|
||||||
mp3dec_t dec;
|
|
||||||
mp3dec_frame_info_t info;
|
|
||||||
|
|
||||||
|
FILE *f;
|
||||||
|
size_t file_size;
|
||||||
|
size_t data_start;
|
||||||
uint8_t buffer[16 * 1024];
|
uint8_t buffer[16 * 1024];
|
||||||
uint8_t *buffer_ptr;
|
uint8_t *buffer_ptr;
|
||||||
size_t buffer_left;
|
size_t buffer_left;
|
||||||
|
|
||||||
|
mp3dec_t dec;
|
||||||
|
mp3dec_frame_info_t info;
|
||||||
|
|
||||||
|
int seek_predecode_frames;
|
||||||
float duration;
|
float duration;
|
||||||
float bitrate;
|
float bitrate;
|
||||||
|
|
||||||
@ -48,9 +49,7 @@ static void mp3player_reset_decoder (void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void mp3player_fill_buffer (void) {
|
static void mp3player_fill_buffer (void) {
|
||||||
UINT bytes_read;
|
if (feof(p->f)) {
|
||||||
|
|
||||||
if (f_eof(&p->fil)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,11 +62,7 @@ static void mp3player_fill_buffer (void) {
|
|||||||
p->buffer_ptr = p->buffer;
|
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 += fread(p->buffer + p->buffer_left, 1, sizeof(p->buffer) - p->buffer_left, p->f);
|
||||||
p->buffer_left += bytes_read;
|
|
||||||
} else {
|
|
||||||
p->io_error = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mp3player_wave_read (void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) {
|
static void mp3player_wave_read (void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) {
|
||||||
@ -109,7 +104,7 @@ static void mp3player_calculate_duration (int samples) {
|
|||||||
uint32_t frames;
|
uint32_t frames;
|
||||||
int delay, padding;
|
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) {
|
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->duration = (frames * samples) / (float) (p->info.hz);
|
||||||
p->bitrate = (data_size * 8) / p->duration;
|
p->bitrate = (data_size * 8) / p->duration;
|
||||||
@ -137,7 +132,6 @@ mp3player_err_t mp3player_init (void) {
|
|||||||
mp3player_reset_decoder();
|
mp3player_reset_decoder();
|
||||||
|
|
||||||
p->loaded = false;
|
p->loaded = false;
|
||||||
p->io_error = false;
|
|
||||||
|
|
||||||
p->wave = (waveform_t) {
|
p->wave = (waveform_t) {
|
||||||
.name = "mp3player",
|
.name = "mp3player",
|
||||||
@ -164,22 +158,32 @@ mp3player_err_t mp3player_load (char *path) {
|
|||||||
mp3player_unload();
|
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;
|
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();
|
mp3player_reset_decoder();
|
||||||
|
|
||||||
while (!(f_eof(&p->fil) && p->buffer_left == 0)) {
|
while (!(feof(p->f) && p->buffer_left == 0)) {
|
||||||
mp3player_fill_buffer();
|
mp3player_fill_buffer();
|
||||||
|
|
||||||
if (p->io_error) {
|
if (ferror(p->f)) {
|
||||||
|
fclose(p->f);
|
||||||
return MP3PLAYER_ERR_IO;
|
return MP3PLAYER_ERR_IO;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t id3v2_skip = mp3dec_skip_id3v2((const uint8_t *) (p->buffer_ptr), p->buffer_left);
|
size_t id3v2_skip = mp3dec_skip_id3v2((const uint8_t *) (p->buffer_ptr), p->buffer_left);
|
||||||
if (id3v2_skip > 0) {
|
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;
|
return MP3PLAYER_ERR_IO;
|
||||||
}
|
}
|
||||||
mp3player_reset_decoder();
|
mp3player_reset_decoder();
|
||||||
@ -189,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);
|
int samples = mp3dec_decode_frame(&p->dec, p->buffer_ptr, p->buffer_left, NULL, &p->info);
|
||||||
if (samples > 0) {
|
if (samples > 0) {
|
||||||
p->loaded = true;
|
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_ptr += p->info.frame_offset;
|
||||||
p->buffer_left -= p->info.frame_offset;
|
p->buffer_left -= p->info.frame_offset;
|
||||||
@ -206,7 +210,7 @@ mp3player_err_t mp3player_load (char *path) {
|
|||||||
p->buffer_left -= p->info.frame_bytes;
|
p->buffer_left -= p->info.frame_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f_close(&p->fil) != FR_OK) {
|
if (fclose(p->f)) {
|
||||||
return MP3PLAYER_ERR_IO;
|
return MP3PLAYER_ERR_IO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,12 +221,12 @@ void mp3player_unload (void) {
|
|||||||
mp3player_stop();
|
mp3player_stop();
|
||||||
if (p->loaded) {
|
if (p->loaded) {
|
||||||
p->loaded = false;
|
p->loaded = false;
|
||||||
f_close(&p->fil);
|
fclose(p->f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mp3player_err_t mp3player_process (void) {
|
mp3player_err_t mp3player_process (void) {
|
||||||
if (p->io_error) {
|
if (ferror(p->f)) {
|
||||||
mp3player_unload();
|
mp3player_unload();
|
||||||
return MP3PLAYER_ERR_IO;
|
return MP3PLAYER_ERR_IO;
|
||||||
}
|
}
|
||||||
@ -239,7 +243,7 @@ bool mp3player_is_playing (void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool mp3player_is_finished (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) {
|
mp3player_err_t mp3player_play (void) {
|
||||||
@ -248,8 +252,7 @@ mp3player_err_t mp3player_play (void) {
|
|||||||
}
|
}
|
||||||
if (!mp3player_is_playing()) {
|
if (!mp3player_is_playing()) {
|
||||||
if (mp3player_is_finished()) {
|
if (mp3player_is_finished()) {
|
||||||
if (f_lseek(&p->fil, p->data_start) != FR_OK) {
|
if (fseek(p->f, p->data_start, SEEK_SET)) {
|
||||||
p->io_error = true;
|
|
||||||
return MP3PLAYER_ERR_IO;
|
return MP3PLAYER_ERR_IO;
|
||||||
}
|
}
|
||||||
mp3player_reset_decoder();
|
mp3player_reset_decoder();
|
||||||
@ -292,25 +295,24 @@ mp3player_err_t mp3player_seek (int seconds) {
|
|||||||
return MP3PLAYER_OK;
|
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)) {
|
if (position < (long) (p->data_start)) {
|
||||||
position = p->data_start;
|
position = p->data_start;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f_lseek(&p->fil, position) != FR_OK) {
|
if (fseek(p->f, position, SEEK_SET)) {
|
||||||
p->io_error = true;
|
|
||||||
return MP3PLAYER_ERR_IO;
|
return MP3PLAYER_ERR_IO;
|
||||||
}
|
}
|
||||||
|
|
||||||
mp3player_reset_decoder();
|
mp3player_reset_decoder();
|
||||||
mp3player_fill_buffer();
|
mp3player_fill_buffer();
|
||||||
|
|
||||||
p->seek_predecode_frames = (position == p->data_start) ? 0 : SEEK_PREDECODE_FRAMES;
|
if (ferror(p->f)) {
|
||||||
|
|
||||||
if (p->io_error) {
|
|
||||||
return MP3PLAYER_ERR_IO;
|
return MP3PLAYER_ERR_IO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p->seek_predecode_frames = (position == p->data_start) ? 0 : SEEK_PREDECODE_FRAMES;
|
||||||
|
|
||||||
return MP3PLAYER_OK;
|
return MP3PLAYER_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,9 +348,9 @@ float mp3player_get_progress (void) {
|
|||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
FSIZE_t data_size = f_size(&p->fil) - p->data_start;
|
long data_size = p->file_size - p->data_start;
|
||||||
FSIZE_t data_consumed = f_tell(&p->fil) - p->buffer_left;
|
long data_consumed = ftell(p->f) - p->buffer_left;
|
||||||
FSIZE_t data_position = (data_consumed > p->data_start) ? (data_consumed - p->data_start) : 0;
|
long data_position = (data_consumed > p->data_start) ? (data_consumed - p->data_start) : 0;
|
||||||
|
|
||||||
return data_position / (float) (data_size);
|
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);
|
assert(path->buffer != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static path_t *path_create (char *string) {
|
static path_t *path_create (const char *string) {
|
||||||
if (string == NULL) {
|
if (string == NULL) {
|
||||||
string = "";
|
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);
|
path_t *path = path_create(prefix);
|
||||||
size_t prefix_length = strlen(prefix);
|
size_t prefix_length = strlen(prefix);
|
||||||
if ((prefix_length > 0) && (prefix[prefix_length - 1] == '/')) {
|
if ((prefix_length > 0) && (prefix[prefix_length - 1] == '/')) {
|
||||||
|
@ -20,7 +20,7 @@ typedef struct {
|
|||||||
} path_t;
|
} 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);
|
void path_free (path_t *path);
|
||||||
path_t *path_clone (path_t *string);
|
path_t *path_clone (path_t *string);
|
||||||
path_t *path_clone_push (path_t *path, char *string);
|
path_t *path_clone_push (path_t *path, char *string);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <fatfs/ff.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <libspng/spng/spng.h>
|
#include <libspng/spng/spng.h>
|
||||||
|
|
||||||
#include "png_decoder.h"
|
#include "png_decoder.h"
|
||||||
@ -7,7 +8,7 @@
|
|||||||
|
|
||||||
/** @brief PNG File Information Structure. */
|
/** @brief PNG File Information Structure. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
FIL fil;
|
FILE *f;
|
||||||
|
|
||||||
spng_ctx *ctx;
|
spng_ctx *ctx;
|
||||||
struct spng_ihdr ihdr;
|
struct spng_ihdr ihdr;
|
||||||
@ -25,7 +26,7 @@ static png_decoder_t *decoder;
|
|||||||
|
|
||||||
static void png_decoder_deinit (bool free_image) {
|
static void png_decoder_deinit (bool free_image) {
|
||||||
if (decoder != NULL) {
|
if (decoder != NULL) {
|
||||||
f_close(&decoder->fil);
|
fclose(decoder->f);
|
||||||
if (decoder->ctx != NULL) {
|
if (decoder->ctx != NULL) {
|
||||||
spng_ctx_free(decoder->ctx);
|
spng_ctx_free(decoder->ctx);
|
||||||
}
|
}
|
||||||
@ -41,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) {
|
png_err_t png_decoder_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data) {
|
||||||
if (decoder != NULL) {
|
if (decoder != NULL) {
|
||||||
@ -67,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;
|
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);
|
png_decoder_deinit(false);
|
||||||
return PNG_ERR_NO_FILE;
|
return PNG_ERR_NO_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setbuf(decoder->f, NULL);
|
||||||
|
|
||||||
if ((decoder->ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32)) == NULL) {
|
if ((decoder->ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32)) == NULL) {
|
||||||
png_decoder_deinit(false);
|
png_decoder_deinit(false);
|
||||||
return PNG_ERR_OUT_OF_MEM;
|
return PNG_ERR_OUT_OF_MEM;
|
||||||
@ -87,7 +75,7 @@ png_err_t png_decoder_start (char *path, int max_width, int max_height, png_call
|
|||||||
return PNG_ERR_INT;
|
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);
|
png_decoder_deinit(false);
|
||||||
return PNG_ERR_INT;
|
return PNG_ERR_INT;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <fatfs/ff.h>
|
|
||||||
#include <mini.c/src/mini.h>
|
#include <mini.c/src/mini.h>
|
||||||
|
|
||||||
#include "boot/cic.h"
|
#include "boot/cic.h"
|
||||||
@ -141,16 +141,32 @@ typedef struct {
|
|||||||
|
|
||||||
|
|
||||||
// List shamelessly stolen from https://github.com/ares-emulator/ares/blob/master/mia/medium/nintendo-64.cpp
|
// 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[] = {
|
static const match_t database[] = {
|
||||||
MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED)
|
MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED)
|
||||||
|
|
||||||
MATCH_CHECK_CODE(0x000000004CBC3B56, SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // DMTJ 64DD cartridge conversion
|
MATCH_CHECK_CODE(0x000000004CBC3B56, SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // DMTJ 64DD cartridge conversion
|
||||||
|
|
||||||
MATCH_CHECK_CODE(0x0DD4ABABB5A2A91E, SAVE_TYPE_EEPROM_16K, FEAT_EXP_PAK_REQUIRED), // DK Retail kiosk demo
|
MATCH_CHECK_CODE(0x0DD4ABABB5A2A91E, SAVE_TYPE_EEPROM_16K, FEAT_EXP_PAK_REQUIRED), // DK Retail kiosk demo
|
||||||
MATCH_CHECK_CODE(0xEB85EBC9596682AF, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Doubutsu Banchou
|
MATCH_CHECK_CODE(0xEB85EBC9596682AF, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Doubutsu Banchou
|
||||||
|
MATCH_CHECK_CODE(0x9A746EBF2802EA99, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Toon panic
|
||||||
|
MATCH_CHECK_CODE(0x21548CA921548CA9, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Mini racers
|
||||||
|
MATCH_CHECK_CODE(0xBC9B2CC34ED04DA5, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Starcraft 64 [Prototype 2000]
|
||||||
|
MATCH_CHECK_CODE(0x5D40ED2C10D6ABCF, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064
|
||||||
|
MATCH_CHECK_CODE(0x7280E03F497689BA, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064 [ENG patch]
|
||||||
|
|
||||||
|
MATCH_CHECK_CODE(0xCDB8B4D08832352D, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [USA CRACK]
|
||||||
|
MATCH_CHECK_CODE(0xB66E0F7C2709C22F, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [PAL CRACK]
|
||||||
|
|
||||||
|
MATCH_CHECK_CODE(0xCE84793D27ECC1AD, SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [USA CRACK]
|
||||||
|
MATCH_CHECK_CODE(0x1F95CAAA047FC22A, SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [PAL CRACK]
|
||||||
|
|
||||||
|
MATCH_CHECK_CODE(0xE3FF09DFCAE4B0ED, SAVE_TYPE_SRAM, FEAT_RPAK), // Banjo tooie [USA CRACK]
|
||||||
|
|
||||||
MATCH_ID_REGION_VERSION("NK4J", 0, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
MATCH_ID_REGION_VERSION("NK4J", 0, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||||
MATCH_ID_REGION_VERSION("NK4J", 1, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
MATCH_ID_REGION_VERSION("NK4J", 1, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||||
MATCH_ID("NK4", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
MATCH_ID("NK4", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||||
|
|
||||||
MATCH_ID_REGION_VERSION("NSMJ", 3, SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Super Mario 64 Shindou Edition
|
MATCH_ID_REGION_VERSION("NSMJ", 3, SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Super Mario 64 Shindou Edition
|
||||||
MATCH_ID("NSM", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Super Mario 64
|
MATCH_ID("NSM", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Super Mario 64
|
||||||
@ -175,6 +191,7 @@ static const match_t database[] = {
|
|||||||
MATCH_ID_REGION("NWTJ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Wetrix
|
MATCH_ID_REGION("NWTJ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Wetrix
|
||||||
MATCH_ID("NWT", SAVE_TYPE_NONE, FEAT_CPAK), // Wetrix
|
MATCH_ID("NWT", SAVE_TYPE_NONE, FEAT_CPAK), // Wetrix
|
||||||
|
|
||||||
|
// EEPROM 4K
|
||||||
MATCH_ID("CLB", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_64DD_ENHANCED), // Mario Party (NTSC)
|
MATCH_ID("CLB", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_64DD_ENHANCED), // Mario Party (NTSC)
|
||||||
MATCH_ID("NAB", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Air Boarder 64
|
MATCH_ID("NAB", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Air Boarder 64
|
||||||
MATCH_ID("NAD", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Worms Armageddon (U)
|
MATCH_ID("NAD", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Worms Armageddon (U)
|
||||||
@ -269,6 +286,7 @@ static const match_t database[] = {
|
|||||||
MATCH_ID("NXO", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Cruis'n Exotica
|
MATCH_ID("NXO", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Cruis'n Exotica
|
||||||
MATCH_ID("NYK", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Yakouchuu II: Satsujin Kouro
|
MATCH_ID("NYK", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Yakouchuu II: Satsujin Kouro
|
||||||
|
|
||||||
|
// EEPROM 16K
|
||||||
MATCH_ID("N3D", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Doraemon 3: Nobita no Machi SOS!
|
MATCH_ID("N3D", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Doraemon 3: Nobita no Machi SOS!
|
||||||
MATCH_ID("NB7", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
|
MATCH_ID("NB7", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
|
||||||
MATCH_ID("NCW", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Cruis'n World
|
MATCH_ID("NCW", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Cruis'n World
|
||||||
@ -293,12 +311,14 @@ static const match_t database[] = {
|
|||||||
MATCH_ID("NUB", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_TPAK), // PD Ultraman Battle Collection 64
|
MATCH_ID("NUB", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_TPAK), // PD Ultraman Battle Collection 64
|
||||||
MATCH_ID("NYS", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Yoshi's Story
|
MATCH_ID("NYS", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Yoshi's Story
|
||||||
|
|
||||||
|
// SRAM 256K
|
||||||
MATCH_ID("CFZ", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // F-Zero X (J)
|
MATCH_ID("CFZ", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // F-Zero X (J)
|
||||||
MATCH_ID("CPS", SAVE_TYPE_SRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium (J)
|
MATCH_ID("CPS", SAVE_TYPE_SRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium (J)
|
||||||
MATCH_ID("CZL", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // Legend of Zelda: Ocarina of Time [Zelda no Densetsu - Toki no Ocarina (J)]
|
MATCH_ID("CZL", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // Legend of Zelda: Ocarina of Time [Zelda no Densetsu - Toki no Ocarina (J)]
|
||||||
MATCH_ID("NA2", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 2
|
MATCH_ID("NA2", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 2
|
||||||
MATCH_ID("NAL", SAVE_TYPE_SRAM, FEAT_RPAK), // Super Smash Bros. [Nintendo All-Star! Dairantou Smash Brothers (J)]
|
MATCH_ID("NAL", SAVE_TYPE_SRAM, FEAT_RPAK), // Super Smash Bros. [Nintendo All-Star! Dairantou Smash Brothers (J)]
|
||||||
MATCH_ID("NB5", SAVE_TYPE_SRAM, FEAT_RPAK), // Biohazard 2 (J)
|
MATCH_ID("NB5", SAVE_TYPE_SRAM, FEAT_RPAK), // Biohazard 2 (J)
|
||||||
|
MATCH_ID("NDD", SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // 64DD Conversion Rom
|
||||||
MATCH_ID("NFZ", SAVE_TYPE_SRAM, FEAT_RPAK), // F-Zero X (U + E)
|
MATCH_ID("NFZ", SAVE_TYPE_SRAM, FEAT_RPAK), // F-Zero X (U + E)
|
||||||
MATCH_ID("NG6", SAVE_TYPE_SRAM, FEAT_RPAK), // Ganmare Goemon: Dero Dero Douchuu Obake Tenkomori
|
MATCH_ID("NG6", SAVE_TYPE_SRAM, FEAT_RPAK), // Ganmare Goemon: Dero Dero Douchuu Obake Tenkomori
|
||||||
MATCH_ID("NGP", SAVE_TYPE_SRAM, FEAT_CPAK), // Goemon: Mononoke Sugoroku
|
MATCH_ID("NGP", SAVE_TYPE_SRAM, FEAT_CPAK), // Goemon: Mononoke Sugoroku
|
||||||
@ -331,11 +351,14 @@ static const match_t database[] = {
|
|||||||
MATCH_ID("NYW", SAVE_TYPE_SRAM, FEAT_NONE), // Harvest Moon 64
|
MATCH_ID("NYW", SAVE_TYPE_SRAM, FEAT_NONE), // Harvest Moon 64
|
||||||
MATCH_ID("NZL", SAVE_TYPE_SRAM, FEAT_RPAK), // Legend of Zelda: Ocarina of Time (E)
|
MATCH_ID("NZL", SAVE_TYPE_SRAM, FEAT_RPAK), // Legend of Zelda: Ocarina of Time (E)
|
||||||
|
|
||||||
|
// SRAM 768K
|
||||||
MATCH_ID("CDZ", SAVE_TYPE_SRAM_BANKED, FEAT_RPAK | FEAT_64DD_ENHANCED), // Dezaemon 3D
|
MATCH_ID("CDZ", SAVE_TYPE_SRAM_BANKED, FEAT_RPAK | FEAT_64DD_ENHANCED), // Dezaemon 3D
|
||||||
|
|
||||||
|
// FLASHRAM
|
||||||
MATCH_ID("CP2", SAVE_TYPE_FLASHRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium 2 (J)
|
MATCH_ID("CP2", SAVE_TYPE_FLASHRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium 2 (J)
|
||||||
MATCH_ID("NAF", SAVE_TYPE_FLASHRAM, FEAT_CPAK | FEAT_RTC), // Doubutsu no Mori
|
MATCH_ID("NAF", SAVE_TYPE_FLASHRAM, FEAT_CPAK | FEAT_RTC), // Doubutsu no Mori
|
||||||
MATCH_ID("NCC", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Command & Conquer
|
MATCH_ID("NCC", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Command & Conquer
|
||||||
|
MATCH_ID("NCV", SAVE_TYPE_FLASHRAM, FEAT_NONE), // Cubivore (Translation)
|
||||||
MATCH_ID("NCK", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // NBA Courtside 2 featuring Kobe Bryant
|
MATCH_ID("NCK", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // NBA Courtside 2 featuring Kobe Bryant
|
||||||
MATCH_ID("NDA", SAVE_TYPE_FLASHRAM, FEAT_CPAK), // Derby Stallion 64
|
MATCH_ID("NDA", SAVE_TYPE_FLASHRAM, FEAT_CPAK), // Derby Stallion 64
|
||||||
MATCH_ID("NDP", SAVE_TYPE_FLASHRAM, FEAT_EXP_PAK_REQUIRED), // Dinosaur Planet (Unlicensed)
|
MATCH_ID("NDP", SAVE_TYPE_FLASHRAM, FEAT_EXP_PAK_REQUIRED), // Dinosaur Planet (Unlicensed)
|
||||||
@ -354,6 +377,7 @@ static const match_t database[] = {
|
|||||||
|
|
||||||
MATCH_ID("NP3", SAVE_TYPE_FLASHRAM_PKST2, FEAT_TPAK), // Pokemon Stadium 2 [Pocket Monsters Stadium - Kin Gin (J)]
|
MATCH_ID("NP3", SAVE_TYPE_FLASHRAM_PKST2, FEAT_TPAK), // Pokemon Stadium 2 [Pocket Monsters Stadium - Kin Gin (J)]
|
||||||
|
|
||||||
|
// CONTROLLER PAK / NONE
|
||||||
MATCH_ID("N22", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - Round 2
|
MATCH_ID("N22", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - Round 2
|
||||||
MATCH_ID("N2M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2002
|
MATCH_ID("N2M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2002
|
||||||
MATCH_ID("N32", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes 2
|
MATCH_ID("N32", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes 2
|
||||||
@ -576,6 +600,7 @@ static const match_t database[] = {
|
|||||||
|
|
||||||
MATCH_END,
|
MATCH_END,
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
|
||||||
static void fix_rom_header_endianness (rom_header_t *rom_header, rom_info_t *rom_info) {
|
static void fix_rom_header_endianness (rom_header_t *rom_header, rom_info_t *rom_info) {
|
||||||
@ -829,7 +854,7 @@ static rom_err_t save_override (path_t *path, const char *id, int value, int def
|
|||||||
mini_free(ini);
|
mini_free(ini);
|
||||||
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
if (file_delete(path_get(overrides_path))) {
|
if (remove(path_get(overrides_path))) {
|
||||||
path_free(overrides_path);
|
path_free(overrides_path);
|
||||||
return ROM_ERR_IO;
|
return ROM_ERR_IO;
|
||||||
}
|
}
|
||||||
@ -912,21 +937,18 @@ rom_err_t rom_info_override_tv_type (path_t *path, rom_info_t *rom_info, rom_tv_
|
|||||||
}
|
}
|
||||||
|
|
||||||
rom_err_t rom_info_load (path_t *path, rom_info_t *rom_info) {
|
rom_err_t rom_info_load (path_t *path, rom_info_t *rom_info) {
|
||||||
FIL fil;
|
FILE *f;
|
||||||
UINT br;
|
|
||||||
rom_header_t rom_header;
|
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;
|
return ROM_ERR_NO_FILE;
|
||||||
}
|
}
|
||||||
if (f_read(&fil, &rom_header, sizeof(rom_header), &br) != FR_OK) {
|
setbuf(f, NULL);
|
||||||
f_close(&fil);
|
if (fread(&rom_header, sizeof(rom_header), 1, f) != 1) {
|
||||||
|
fclose(f);
|
||||||
return ROM_ERR_IO;
|
return ROM_ERR_IO;
|
||||||
}
|
}
|
||||||
if (f_close(&fil) != FR_OK) {
|
if (fclose(f)) {
|
||||||
return ROM_ERR_IO;
|
|
||||||
}
|
|
||||||
if (br != sizeof(rom_header)) {
|
|
||||||
return ROM_ERR_IO;
|
return ROM_ERR_IO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,14 +5,12 @@
|
|||||||
#include "utils/fs.h"
|
#include "utils/fs.h"
|
||||||
|
|
||||||
|
|
||||||
#ifndef SETTINGS_FILE_PATH
|
static char *settings_path = NULL;
|
||||||
#define SETTINGS_FILE_PATH "sd:/menu/config.ini"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static settings_t init = {
|
static settings_t init = {
|
||||||
.pal60_enabled = false,
|
.pal60_enabled = false,
|
||||||
.hidden_files_enabled = false,
|
.show_protected_entries = false,
|
||||||
.default_directory = "/",
|
.default_directory = "/",
|
||||||
.use_saves_folder = true,
|
.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) {
|
void settings_load (settings_t *settings) {
|
||||||
if (!file_exists(SETTINGS_FILE_PATH)) {
|
if (!file_exists(settings_path)) {
|
||||||
settings_save(&init);
|
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->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->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);
|
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) {
|
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", "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_string(ini, "menu", "default_directory", settings->default_directory);
|
||||||
mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder);
|
mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder);
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ typedef struct {
|
|||||||
/** @brief Use 60 Hz refresh rate on a PAL console */
|
/** @brief Use 60 Hz refresh rate on a PAL console */
|
||||||
bool pal60_enabled;
|
bool pal60_enabled;
|
||||||
|
|
||||||
/** @brief Show files marked as hidden in the browser */
|
/** @brief Show files/directories that are filtered in the browser */
|
||||||
bool hidden_files_enabled;
|
bool show_protected_entries;
|
||||||
|
|
||||||
/** @brief Default directory to navigate to when menu loads */
|
/** @brief Default directory to navigate to when menu loads */
|
||||||
char *default_directory;
|
char *default_directory;
|
||||||
@ -33,6 +33,8 @@ typedef struct {
|
|||||||
} settings_t;
|
} settings_t;
|
||||||
|
|
||||||
|
|
||||||
|
/** @brief Init settings path */
|
||||||
|
void settings_init (char *path);
|
||||||
/** @brief The settings to load */
|
/** @brief The settings to load */
|
||||||
void settings_load (settings_t *settings);
|
void settings_load (settings_t *settings);
|
||||||
/** @brief The settings to save */
|
/** @brief The settings to save */
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
// Main use of these functions is to aid menu development
|
// Main use of these functions is to aid menu development
|
||||||
// (for example replace files on the SD card or reboot menu).
|
// (for example replace files on the SD card or reboot menu).
|
||||||
|
|
||||||
#include <fatfs/ff.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <usb.h>
|
#include <usb.h>
|
||||||
|
|
||||||
#include "usb_comm.h"
|
#include "usb_comm.h"
|
||||||
@ -70,18 +71,16 @@ static void command_reboot (menu_t *menu) {
|
|||||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
||||||
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
||||||
menu->boot_params->detect_cic_seed = true;
|
menu->boot_params->detect_cic_seed = true;
|
||||||
|
menu->boot_params->cheat_list = NULL;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void command_send_file (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];
|
char length[8];
|
||||||
|
|
||||||
FIL f;
|
if (usb_comm_read_string(buffer, sizeof(buffer), ' ')) {
|
||||||
int remaining;
|
|
||||||
uint8_t data[8192];
|
|
||||||
UINT bytes_written;
|
|
||||||
|
|
||||||
if (usb_comm_read_string(path, sizeof(path), ' ')) {
|
|
||||||
return usb_comm_send_error("Invalid path argument\n");
|
return usb_comm_send_error("Invalid path argument\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,11 +92,16 @@ static void command_send_file (menu_t *menu) {
|
|||||||
return usb_comm_send_error("Invalid file length argument\n");
|
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");
|
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) {
|
if (remaining > MAX_FILE_SIZE) {
|
||||||
return usb_comm_send_error("File size too big\n");
|
return usb_comm_send_error("File size too big\n");
|
||||||
@ -106,18 +110,14 @@ static void command_send_file (menu_t *menu) {
|
|||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
int block_size = MIN(remaining, sizeof(data));
|
int block_size = MIN(remaining, sizeof(data));
|
||||||
usb_read(data, block_size);
|
usb_read(data, block_size);
|
||||||
if (f_write(&f, data, block_size, &bytes_written) != FR_OK) {
|
if (fwrite(data, 1, block_size, f) != block_size) {
|
||||||
f_close(&f);
|
fclose(f);
|
||||||
return usb_comm_send_error("Couldn't write data to the file\n");
|
|
||||||
}
|
|
||||||
if (bytes_written != block_size) {
|
|
||||||
f_close(&f);
|
|
||||||
return usb_comm_send_error("Couldn't write all required data to the file\n");
|
return usb_comm_send_error("Couldn't write all required data to the file\n");
|
||||||
}
|
}
|
||||||
remaining -= block_size;
|
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");
|
return usb_comm_send_error("Couldn't flush data to the file\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/errno.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include <fatfs/ff.h>
|
|
||||||
|
|
||||||
#include "../fonts.h"
|
#include "../fonts.h"
|
||||||
#include "utils/fs.h"
|
#include "utils/fs.h"
|
||||||
#include "views.h"
|
#include "views.h"
|
||||||
@ -13,11 +12,23 @@ static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
|
|||||||
static const char *disk_extensions[] = { "ndd", NULL };
|
static const char *disk_extensions[] = { "ndd", NULL };
|
||||||
static const char *patch_extensions[] = { "bps", "ips", "aps", "ups", "xdelta", NULL };
|
static const char *patch_extensions[] = { "bps", "ips", "aps", "ups", "xdelta", NULL };
|
||||||
static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", 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 *image_extensions[] = { "png", NULL };
|
||||||
static const char *text_extensions[] = { "txt", "ini", "yml", "yaml", NULL };
|
static const char *text_extensions[] = { "txt", "ini", "yml", "yaml", NULL };
|
||||||
static const char *music_extensions[] = { "mp3", 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) {
|
static int compare_entry (const void *pa, const void *pb) {
|
||||||
entry_t *a = (entry_t *) (pa);
|
entry_t *a = (entry_t *) (pa);
|
||||||
@ -66,76 +77,87 @@ static int compare_entry (const void *pa, const void *pb) {
|
|||||||
return strcasecmp((const char *) (a->name), (const char *) (b->name));
|
return strcasecmp((const char *) (a->name), (const char *) (b->name));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool load_directory (menu_t *menu) {
|
static void browser_list_free (menu_t *menu) {
|
||||||
DIR dir;
|
|
||||||
FILINFO info;
|
|
||||||
|
|
||||||
for (int i = menu->browser.entries - 1; i >= 0; i--) {
|
for (int i = menu->browser.entries - 1; i >= 0; i--) {
|
||||||
free(menu->browser.list[i].name);
|
free(menu->browser.list[i].name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(menu->browser.list);
|
||||||
|
|
||||||
|
menu->browser.list = NULL;
|
||||||
menu->browser.entries = 0;
|
menu->browser.entries = 0;
|
||||||
menu->browser.selected = -1;
|
|
||||||
menu->browser.entry = NULL;
|
menu->browser.entry = NULL;
|
||||||
|
menu->browser.selected = -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (f_opendir(&dir, strip_sd_prefix(path_get(menu->browser.directory))) != FR_OK) {
|
static bool load_directory (menu_t *menu) {
|
||||||
return true;
|
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) {
|
path_free(path);
|
||||||
if (f_readdir(&dir, &info) != FR_OK) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t length = strlen(info.fname);
|
if (result < -1) {
|
||||||
|
browser_list_free(menu);
|
||||||
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, patch_extensions)) {
|
|
||||||
entry->type = ENTRY_TYPE_ROM_PATCH;
|
|
||||||
}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, text_extensions)) {
|
|
||||||
entry->type = ENTRY_TYPE_TEXT;
|
|
||||||
} 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) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,18 +234,15 @@ static void show_properties (menu_t *menu, void *arg) {
|
|||||||
static void delete_entry (menu_t *menu, void *arg) {
|
static void delete_entry (menu_t *menu, void *arg) {
|
||||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||||
|
|
||||||
if (menu->browser.entry->type == ENTRY_TYPE_DIR) {
|
if (remove(path_get(path))) {
|
||||||
if (directory_delete(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");
|
menu_show_error(menu, "Couldn't delete directory\nDirectory might not be empty");
|
||||||
path_free(path);
|
} else {
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (file_delete(path_get(path))) {
|
|
||||||
menu_show_error(menu, "Couldn't delete file");
|
menu_show_error(menu, "Couldn't delete file");
|
||||||
path_free(path);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
path_free(path);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
path_free(path);
|
path_free(path);
|
||||||
@ -236,7 +255,7 @@ static void delete_entry (menu_t *menu, void *arg) {
|
|||||||
|
|
||||||
static void set_default_directory (menu_t *menu, void *arg) {
|
static void set_default_directory (menu_t *menu, void *arg) {
|
||||||
free(menu->settings.default_directory);
|
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);
|
settings_save(&menu->settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +418,7 @@ void view_browser_init (menu_t *menu) {
|
|||||||
component_context_menu_init(&settings_context_menu);
|
component_context_menu_init(&settings_context_menu);
|
||||||
if (load_directory(menu)) {
|
if (load_directory(menu)) {
|
||||||
path_free(menu->browser.directory);
|
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");
|
menu_show_error(menu, "Error while opening initial directory");
|
||||||
} else {
|
} else {
|
||||||
menu->browser.valid = true;
|
menu->browser.valid = true;
|
||||||
|
@ -50,5 +50,4 @@ void view_error_display (menu_t *menu, surface_t *display) {
|
|||||||
void menu_show_error (menu_t *menu, char *error_message) {
|
void menu_show_error (menu_t *menu, char *error_message) {
|
||||||
menu->next_mode = MENU_MODE_ERROR;
|
menu->next_mode = MENU_MODE_ERROR;
|
||||||
menu->error_message = error_message;
|
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 "utils/fs.h"
|
||||||
#include "views.h"
|
#include "views.h"
|
||||||
@ -8,7 +8,7 @@ static const char *n64_rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
|
|||||||
static const char *text_extensions[] = { "txt", NULL };
|
static const char *text_extensions[] = { "txt", NULL };
|
||||||
static const char *config_extensions[] = { "ini", "cfg", "yml", "yaml", "toml", NULL };
|
static const char *config_extensions[] = { "ini", "cfg", "yml", "yaml", "toml", NULL };
|
||||||
static const char *save_extensions[] = { "sav", "eep", "eeprom", "sra", "srm", "ram", "fla", "flashram", NULL };
|
static const char *save_extensions[] = { "sav", "eep", "eeprom", "sra", "srm", "ram", "fla", "flashram", NULL };
|
||||||
static const char *patch_extensions[] = { "ips", "aps", "pps", "xdelta", NULL };
|
static const char *patch_extensions[] = { "aps", "bps", "ips", "pps", "ups", "xdelta", NULL };
|
||||||
static const char *archive_extensions[] = { "zip", "rar", "7z", "tar", "gz", NULL };
|
static const char *archive_extensions[] = { "zip", "rar", "7z", "tar", "gz", NULL };
|
||||||
static const char *image_extensions[] = { "png", "jpg", "gif", NULL };
|
static const char *image_extensions[] = { "png", "jpg", "gif", NULL };
|
||||||
static const char *music_extensions[] = { "mp3", "wav", "ogg", "wma", "flac", NULL };
|
static const char *music_extensions[] = { "mp3", "wav", "ogg", "wma", "flac", NULL };
|
||||||
@ -16,7 +16,7 @@ static const char *dexdrive_extensions[] = { "mpk", NULL };
|
|||||||
static const char *emulator_extensions[] = { "emu", 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) {
|
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"
|
||||||
"\n"
|
"\n"
|
||||||
" Size: %d bytes\n"
|
" Size: %d bytes\n"
|
||||||
" Attributes: %s%s%s%s%s\n"
|
" Attributes: %s %s\n"
|
||||||
"%s"
|
"%s"
|
||||||
" Modified: %u-%02u-%02u %02u:%02u",
|
" Modified: %s",
|
||||||
info.fsize,
|
st.st_size,
|
||||||
(info.fattrib & AM_DIR) ? "Directory " : "File ",
|
S_ISDIR(st.st_mode) ? "Directory" : "File",
|
||||||
(info.fattrib & AM_RDO) ? "| Read only " : "",
|
st.st_mode & S_IWUSR ? "" : "(Read only)",
|
||||||
(info.fattrib & AM_SYS) ? "| System " : "",
|
format_file_type(menu->browser.entry->name, S_ISDIR(st.st_mode)),
|
||||||
(info.fattrib & AM_ARC) ? "| Archive " : "",
|
ctime(&st.st_mtim.tv_sec)
|
||||||
(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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
component_actions_bar_text_draw(
|
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) {
|
void view_file_info_init (menu_t *menu) {
|
||||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||||
|
|
||||||
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");
|
menu_show_error(menu, "Couldn't obtain file information");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,10 +136,12 @@ static void load (menu_t *menu) {
|
|||||||
case ROM_TV_TYPE_MPAL: menu->boot_params->tv_type = BOOT_TV_TYPE_MPAL; 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;
|
default: menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; break;
|
||||||
}
|
}
|
||||||
|
menu->boot_params->cheat_list = NULL;
|
||||||
} else {
|
} else {
|
||||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_64DD;
|
menu->boot_params->device_type = BOOT_DEVICE_TYPE_64DD;
|
||||||
menu->boot_params->tv_type = BOOT_TV_TYPE_NTSC;
|
menu->boot_params->tv_type = BOOT_TV_TYPE_NTSC;
|
||||||
menu->boot_params->detect_cic_seed = true;
|
menu->boot_params->detect_cic_seed = true;
|
||||||
|
menu->boot_params->cheat_list = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +156,7 @@ void view_load_disk_init (menu_t *menu) {
|
|||||||
|
|
||||||
menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||||
|
|
||||||
disk_err_t err = disk_info_load(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) {
|
if (err != DISK_OK) {
|
||||||
menu_show_error(menu, convert_error_message(err));
|
menu_show_error(menu, convert_error_message(err));
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ static void load (menu_t *menu) {
|
|||||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
||||||
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
||||||
menu->boot_params->detect_cic_seed = true;
|
menu->boot_params->detect_cic_seed = true;
|
||||||
|
menu->boot_params->cheat_list = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -310,6 +310,7 @@ static void load (menu_t *menu) {
|
|||||||
case ROM_TV_TYPE_MPAL: menu->boot_params->tv_type = BOOT_TV_TYPE_MPAL; 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;
|
default: menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; break;
|
||||||
}
|
}
|
||||||
|
menu->boot_params->cheat_list = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void deinit (void) {
|
static void deinit (void) {
|
||||||
@ -334,7 +335,7 @@ void view_load_rom_init (menu_t *menu) {
|
|||||||
return;
|
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);
|
component_context_menu_init(&options_context_menu);
|
||||||
}
|
}
|
||||||
|
@ -34,15 +34,15 @@ static void draw (menu_t *menu, surface_t *d) {
|
|||||||
"\n"
|
"\n"
|
||||||
"To change the settings, please adjust them\n"
|
"To change the settings, please adjust them\n"
|
||||||
"directly in the 'menu/config.ini' file.\n\n"
|
"directly in the 'menu/config.ini' file.\n\n"
|
||||||
"pal60_enabled: %s\n"
|
"pal60_enabled: %s\n"
|
||||||
"hidden_files_enabled: %s\n"
|
"show_protected_entries: %s\n"
|
||||||
"default_directory: %s\n"
|
"default_directory: %s\n"
|
||||||
"use_saves_folder: %s\n"
|
"use_saves_folder: %s\n"
|
||||||
"bgm_enabled: %s\n"
|
"bgm_enabled: %s\n"
|
||||||
"sound_enabled: %s\n"
|
"sound_enabled: %s\n"
|
||||||
"rumble_enabled: %s\n",
|
"rumble_enabled: %s\n",
|
||||||
format_switch(menu->settings.pal60_enabled),
|
format_switch(menu->settings.pal60_enabled),
|
||||||
format_switch(menu->settings.hidden_files_enabled),
|
format_switch(menu->settings.show_protected_entries),
|
||||||
menu->settings.default_directory,
|
menu->settings.default_directory,
|
||||||
format_switch(menu->settings.use_saves_folder),
|
format_switch(menu->settings.use_saves_folder),
|
||||||
format_switch(menu->settings.bgm_enabled),
|
format_switch(menu->settings.bgm_enabled),
|
||||||
|
@ -1,13 +1,66 @@
|
|||||||
#include <fatfs/ff.h>
|
#include <stdio.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "utils/fs.h"
|
#include "../components/constants.h"
|
||||||
|
#include "../fonts.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
#include "views.h"
|
#include "views.h"
|
||||||
|
|
||||||
static char *file_content;
|
|
||||||
|
#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) {
|
static void process (menu_t *menu) {
|
||||||
if (menu->actions.back) {
|
if (menu->actions.back) {
|
||||||
menu->next_mode = MENU_MODE_BROWSER;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,61 +71,102 @@ static void draw (menu_t *menu, surface_t *d) {
|
|||||||
|
|
||||||
component_layout_draw();
|
component_layout_draw();
|
||||||
|
|
||||||
component_main_text_draw(
|
|
||||||
ALIGN_CENTER, VALIGN_TOP,
|
|
||||||
"TEXT VIEWER\n"
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
component_main_text_draw(
|
component_main_text_draw(
|
||||||
ALIGN_LEFT, VALIGN_TOP,
|
ALIGN_LEFT, VALIGN_TOP,
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"%s\n",
|
"%s\n",
|
||||||
file_content
|
text->contents + text->offset
|
||||||
);
|
);
|
||||||
|
|
||||||
|
component_list_scrollbar_draw(text->current_line, text->lines, LIST_ENTRIES);
|
||||||
|
|
||||||
component_actions_bar_text_draw(
|
component_actions_bar_text_draw(
|
||||||
ALIGN_LEFT, VALIGN_TOP,
|
ALIGN_LEFT, VALIGN_TOP,
|
||||||
"\n"
|
"^%02XUp / Down: Scroll^00\n"
|
||||||
"B: Back"
|
"B: Back",
|
||||||
|
text->vertical_scroll_possible ? STL_DEFAULT : STL_GRAY
|
||||||
);
|
);
|
||||||
|
|
||||||
rdpq_detach_show();
|
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) {
|
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);
|
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||||
|
text->f = fopen(path_get(path), "r");
|
||||||
uint32_t file_size = file_get_size(path_get(path));
|
|
||||||
|
|
||||||
if (file_size > 1024) { // FIXME: this is just a placeholder until scrolling is implemented.
|
|
||||||
file_size = 1024; // For the moment, we just set it to that, since any more would be a waste.
|
|
||||||
}
|
|
||||||
|
|
||||||
file_content = calloc(file_size, 1);
|
|
||||||
|
|
||||||
// read file content
|
|
||||||
FIL fil;
|
|
||||||
UINT br;
|
|
||||||
|
|
||||||
if (f_open(&fil, strip_sd_prefix(path_get(path)), FA_READ) != FR_OK) {
|
|
||||||
debugf("Error loading file\n");
|
|
||||||
}
|
|
||||||
if (f_read(&fil, file_content, file_size, &br) != FR_OK) {
|
|
||||||
f_close(&fil);
|
|
||||||
debugf("Error loading file content\n");
|
|
||||||
}
|
|
||||||
if (f_close(&fil) != FR_OK) {
|
|
||||||
debugf("Error closing file\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
path_free(path);
|
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) {
|
void view_text_viewer_display (menu_t *menu, surface_t *display) {
|
||||||
process(menu);
|
process(menu);
|
||||||
|
|
||||||
draw(menu, display);
|
draw(menu, display);
|
||||||
|
|
||||||
|
if (menu->next_mode != MENU_MODE_TEXT_VIEWER) {
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
172
src/utils/fs.c
172
src/utils/fs.c
@ -1,145 +1,85 @@
|
|||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <strings.h>
|
#include <sys/errno.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <fatfs/ff.h>
|
|
||||||
|
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
char *strip_sd_prefix (char *path) {
|
char *strip_fs_prefix (char *path) {
|
||||||
const char *prefix = "sd:/";
|
const char *prefix = ":/";
|
||||||
|
|
||||||
char *found = strstr(path, prefix);
|
char *found = strstr(path, prefix);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found + strlen(prefix) - 1;
|
return (found + strlen(prefix) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool file_exists (char *path) {
|
bool file_exists (char *path) {
|
||||||
FRESULT fr;
|
struct stat st;
|
||||||
FILINFO fno;
|
int error = stat(path, &st);
|
||||||
|
return ((error == 0) && S_ISREG(st.st_mode));
|
||||||
fr = f_stat(strip_sd_prefix(path), &fno);
|
|
||||||
|
|
||||||
return ((fr == FR_OK) && (!(fno.fattrib & AM_DIR)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t file_get_size (char *path) {
|
||||||
size_t file_get_size (char *path) {
|
struct stat st;
|
||||||
FILINFO fno;
|
if (stat(path, &st)) {
|
||||||
|
return -1;
|
||||||
if (f_stat(strip_sd_prefix(path), &fno) != FR_OK) {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
return (int64_t) (st.st_size);
|
||||||
return (size_t) (fno.fsize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool file_delete (char *path) {
|
bool file_allocate (char *path, size_t size) {
|
||||||
if (file_exists(path)) {
|
FILE *f;
|
||||||
return (f_unlink(strip_sd_prefix(path)) != FR_OK);
|
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;
|
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) {
|
bool file_fill (char *path, uint8_t value) {
|
||||||
FIL fil;
|
FILE *f;
|
||||||
bool error = false;
|
bool error = false;
|
||||||
uint8_t buffer[FS_SECTOR_SIZE * 8];
|
uint8_t buffer[FS_SECTOR_SIZE * 8];
|
||||||
FRESULT res;
|
size_t bytes_to_write;
|
||||||
UINT bytes_to_write;
|
|
||||||
UINT bytes_written;
|
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(buffer); i++) {
|
for (int i = 0; i < sizeof(buffer); i++) {
|
||||||
buffer[i] = value;
|
buffer[i] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f_open(&fil, strip_sd_prefix(path), FA_WRITE) != FR_OK) {
|
if ((f = fopen(path, "rb+")) == NULL) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < f_size(&fil); i += sizeof(buffer)) {
|
setbuf(f, NULL);
|
||||||
bytes_to_write = MIN(f_size(&fil) - f_tell(&fil), sizeof(buffer));
|
|
||||||
res = f_write(&fil, buffer, bytes_to_write, &bytes_written);
|
fseek(f, 0, SEEK_END);
|
||||||
if ((res != FR_OK) || (bytes_to_write != bytes_written)) {
|
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;
|
error = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f_tell(&fil) != f_size(&fil)) {
|
if (fclose(f)) {
|
||||||
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) {
|
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,20 +105,9 @@ bool file_has_extensions (char *path, const char *extensions[]) {
|
|||||||
|
|
||||||
|
|
||||||
bool directory_exists (char *path) {
|
bool directory_exists (char *path) {
|
||||||
FRESULT fr;
|
struct stat st;
|
||||||
FILINFO fno;
|
int error = stat(path, &st);
|
||||||
|
return ((error == 0) && S_ISDIR(st.st_mode));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool directory_create (char *path) {
|
bool directory_create (char *path) {
|
||||||
@ -188,8 +117,12 @@ bool directory_create (char *path) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *directory = strdup(strip_sd_prefix(path));
|
char *directory = strdup(path);
|
||||||
char *separator = directory;
|
char *separator = strip_fs_prefix(directory);
|
||||||
|
|
||||||
|
if (separator != directory) {
|
||||||
|
separator++;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
separator = strchr(separator, '/');
|
separator = strchr(separator, '/');
|
||||||
@ -199,8 +132,7 @@ bool directory_create (char *path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (directory[0] != '\0') {
|
if (directory[0] != '\0') {
|
||||||
FRESULT res = f_mkdir(directory);
|
if (mkdir(directory, 0777) && (errno != EEXIST)) {
|
||||||
if ((res != FR_OK) && (res != FR_EXIST)) {
|
|
||||||
error = true;
|
error = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -10,18 +10,15 @@
|
|||||||
#define FS_SECTOR_SIZE (512)
|
#define FS_SECTOR_SIZE (512)
|
||||||
|
|
||||||
|
|
||||||
char *strip_sd_prefix (char *path);
|
char *strip_fs_prefix (char *path);
|
||||||
|
|
||||||
bool file_exists (char *path);
|
bool file_exists (char *path);
|
||||||
size_t file_get_size (char *path);
|
int64_t file_get_size (char *path);
|
||||||
bool file_delete (char *path);
|
|
||||||
bool file_allocate (char *path, size_t size);
|
bool file_allocate (char *path, size_t size);
|
||||||
bool file_fill (char *path, uint8_t value);
|
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 file_has_extensions (char *path, const char *extensions[]);
|
||||||
|
|
||||||
bool directory_exists (char *path);
|
bool directory_exists (char *path);
|
||||||
bool directory_delete (char *path);
|
|
||||||
bool directory_create (char *path);
|
bool directory_create (char *path);
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user