mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2024-11-22 18:49:20 +01:00
Merge branch 'develop' into add-rom-patcher
This commit is contained in:
commit
106b8413f0
@ -1,6 +1,6 @@
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG SC64_DEPLOYER_VERSION=v2.18.0
|
||||
ARG SC64_DEPLOYER_VERSION=v2.20.0
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
apt-get install build-essential doxygen git python3 wget -y && \
|
||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -113,7 +113,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Doxygen
|
||||
uses: mattnotmitt/doxygen-action@1.9.5
|
||||
uses: mattnotmitt/doxygen-action@v1
|
||||
with:
|
||||
doxyfile-path: './Doxyfile'
|
||||
|
||||
|
1
Makefile
1
Makefile
@ -100,7 +100,6 @@ $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fc
|
||||
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 80-1FF -r 2026-2026 --ellipsis 2026,1
|
||||
$(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1
|
||||
|
||||
|
||||
$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))
|
||||
|
||||
$(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf
|
||||
|
54
README.md
54
README.md
@ -26,6 +26,7 @@ An open source menu for N64 flashcarts.
|
||||
* Real Time Clock support.
|
||||
* Music playback (MP3).
|
||||
* Menu sound effects.
|
||||
* N64 ROM autoload.
|
||||
|
||||
|
||||
## Documentation
|
||||
@ -49,22 +50,51 @@ An open source menu for N64 flashcarts.
|
||||
## Experimental features
|
||||
These features are subject to change:
|
||||
|
||||
### ROM Boxart
|
||||
To use boxart, place PNG files in the `/menu/boxart` folder on the SD card with the following dimensions:
|
||||
* Standard covers: 158x112
|
||||
* 64DD covers: 129x112
|
||||
* Japanese covers: 112x158
|
||||
### N64 ROM autoload
|
||||
To use the autoload function, while on the `N64 ROM information` display, press the `R` button on your joypad and select the `Set ROM to autoload` option. When you restart the console, it will now only load the selected ROM rather than the menu.
|
||||
NOTE: to return to the menu, hold joypad `start` button whilst powering on the console.
|
||||
|
||||
Each file must be named according to the 2 letter ROM ID, or 3 letter ROM ID including media type.
|
||||
i.e. for GoldenEye 2 letters, this would be `GE.png`.
|
||||
i.e. for GoldenEye 3 letters, this would be `NGE.png`.
|
||||
You can download these boxart packs:
|
||||
### GamePak sprites
|
||||
To use N64 `GamePak` sprites, place `PNG` files within the `sd:/menu/boxart/` folder.
|
||||
|
||||
[American Boxart](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w)
|
||||
|
||||
[European Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M)
|
||||
#### Supported sprites
|
||||
These must be `PNG` files that use the following dimensions:
|
||||
* Standard N64 GamePak boxart sprites: 158x112
|
||||
* Japanese N64 GamePak boxart sprites: 112x158
|
||||
* 64DD boxart sprites: 129x112
|
||||
|
||||
[64DD Boxart](https://mega.nz/file/O3JzwD7B#BYl1aV-pbrJ-MxWUbM_K0yGVIRbmSoxJJZqQInRzZyM)
|
||||
They will be loaded by directories using each character (case-sensitive) of the full 4 character Game Code (as identified in the menu ROM information).
|
||||
i.e. for GoldenEye NTSC USA (NGEE), this would be `sd:/menu/boxart/N/G/E/E/boxart_front.png`.
|
||||
i.e. for GoldenEye PAL (NGEP), this would be `sd:/menu/boxart/N/G/E/P/boxart_front.png`.
|
||||
|
||||
To improve compatibility between regions (as a fallback), you may exclude the region ID (last matched directory) for GamePaks to match with 3 letter IDs instead:
|
||||
i.e. for GoldenEye, this would be `sd:/menu/boxart/N/G/E/boxart_front.png`.
|
||||
|
||||
**Note1:** Excluding the region ID may show the wrong boxart.
|
||||
**Note2:** For future support, boxart sprites should also include: `boxart_back.png`, `boxart_top.png`, `boxart_bottom.png`, `boxart_left.png`, `boxart_right.png`.
|
||||
|
||||
As a starting point, here is a link to a boxart pack following the new structure, including `boxart_front.png` and failback images:
|
||||
* [Link](https://drive.google.com/file/d/1IpCmFqmGgGwKKmlRBxYObfFR9XywaC6n/view?usp=drive_link)
|
||||
|
||||
|
||||
#### Compatibilty mode
|
||||
If you cannot yet satisfy the correct boxart layout, The menu still has **deprecated** support for filenames containing the Game ID.
|
||||
|
||||
**Note:** This will add a noticeable delay for displaying parts of the menu.
|
||||
|
||||
Each file must be named according to the 2,3 or 4 letter GamePak ID (matched in this order).
|
||||
i.e.
|
||||
* for GoldenEye 4 letters, this would be `sd:/menu/boxart/NGEE.png` and/or `sd:/menu/boxart/NGEP.png`.
|
||||
* for GoldenEye 3 letters, this would be `sd:/menu/boxart/NGE.png`.
|
||||
* for GoldenEye 2 letters, this would be `sd:/menu/boxart/GE.png`.
|
||||
|
||||
|
||||
As a starting point, here are some links to boxart packs:
|
||||
* [Japan Boxart](https://mega.nz/file/KyJR0B6B#ERabLautAVPaqJTIdBSv4ghbudNhK7hnEr2ZS1Q6ub0)
|
||||
* [American Boxart](https://mega.nz/file/rugAFYSQ#JHfgCU2amzNVpC4S6enP3vg--wtAAwsziKa7cej6QCc)
|
||||
* [European Boxart](https://mega.nz/file/OmIV3aAK#kOWdutK1_41ffN64R6thbU7HEPR_M9qO0YM2mNG6RbQ)
|
||||
* [64DD Boxart](https://mega.nz/file/ay5wQIxJ#k3PF-VMLrZJxJTr-BOaOKa2TBIK7c2t4zwbdshsQl40)
|
||||
|
||||
|
||||
### Menu Settings
|
||||
|
@ -1,11 +1,19 @@
|
||||
## First time setup of SD card
|
||||
|
||||
Using your PC, insert the SD card and ensure it is formatted for compatibility with your flashcart (*FAT32 and EXFAT are fully supported on the SC64*).
|
||||
Using your PC, insert the SD card and ensure it is formatted for compatibility with your flashcart
|
||||
#### SC64
|
||||
- FAT32 and EXFAT are fully supported.
|
||||
- An SD formatted with 128 kiB cluster size is recommended.
|
||||
|
||||
- Download the latest `sc64menu.n64` (assuming you are using an *sc64*) file from the [releases](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases/) page, then put it in the root directory of your SD card.
|
||||
- Create a folder in the root of your SD card called `menu`.
|
||||
- Place your ROMs on the SD Card, in any folder (**except for `menu`**).
|
||||
|
||||
#### Other supported flashcarts
|
||||
- FAT32 recommended.
|
||||
- An SD formatted with default cluster size is recommended.
|
||||
|
||||
|
||||
|
||||
### Emulator support
|
||||
Emulators should be added to the `/menu/emulators` directory on the SD card.
|
||||
@ -36,7 +44,7 @@ SD:\
|
||||
│ │ ├── NDDJ2.n64
|
||||
│ │ └── NDXJ0.n64
|
||||
│ │
|
||||
│ └── emulators
|
||||
│ └── emulators\
|
||||
│ ├── neon64bu.rom
|
||||
│ ├── sodium64.z64
|
||||
│ ├── gb.v64
|
||||
|
@ -8,11 +8,11 @@ You can use a dev container in VSCode to ease development.
|
||||
|
||||
### To deploy:
|
||||
#### SC64
|
||||
* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.18.0/sc64-deployer-windows-v2.18.0.zip)
|
||||
* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.20.0/sc64-deployer-windows-v2.20.0.zip)
|
||||
* Extract and place `sc64deployer.exe` in the `tools/sc64` directory.
|
||||
|
||||
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)
|
||||
Make sure that your firmware is compatible (currently v2.20.0+)
|
||||
See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.20.0/docs/00_quick_startup_guide.md#firmware-backupupdate)
|
||||
|
||||
##### From the devcontainer
|
||||
It is not currently possible to directly communicate with USB devices.
|
||||
@ -45,10 +45,13 @@ For ease of development and debugging, the menu ROM can run in the [Ares emulato
|
||||
* Add the required file to the correct folder on your SD card.
|
||||
|
||||
|
||||
## Update Libdragon submodule
|
||||
This repo currently uses the `preview` branch as a submodule at a specific commit.
|
||||
## Update submodules
|
||||
To update to the latest version, use `git submodule update --remote` from the terminal.
|
||||
|
||||
### libdragon
|
||||
This repo currently uses the `preview` branch as a submodule at a specific commit.
|
||||
* To ensure your local instance is building against it, use `cd ./libdragon && make clobber -j && make libdragon tools -j && make install tools-install -j && cd ..`
|
||||
|
||||
## Generate documentation
|
||||
Run `doxygen` from the dev container terminal.
|
||||
Make sure you fix the warnings before creating a PR!
|
||||
@ -57,6 +60,8 @@ Generated documentation is located in the `output/docs` folder and auto-publishe
|
||||
Once merged, they can be viewed [here](https://polprzewodnikowy.github.io/N64FlashcartMenu/)
|
||||
|
||||
### Test generated docs in the dev-container
|
||||
Testing the documentation locally allows you to preview changes and ensure everything renders correctly before submitting your changes.
|
||||
|
||||
Install Prerequisites:
|
||||
```bash
|
||||
apt-get install ruby-full build-essential zlib1g-dev
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 9bae49994bf1c796f9939ea1aa7c133813b9053f
|
||||
Subproject commit 5295016230d657cd6c7fce5b6ed4a342538e09f5
|
@ -95,7 +95,7 @@ void boot (boot_params_t *params) {
|
||||
io32_t *reboot_dst = SP_MEM->IMEM;
|
||||
size_t reboot_instructions = (size_t) (&reboot_size) / sizeof(uint32_t);
|
||||
|
||||
for (int i = 0; i < reboot_instructions; i++) {
|
||||
for (unsigned int i = 0; i < reboot_instructions; i++) {
|
||||
cpu_io_write(&reboot_dst[i], reboot_src[i]);
|
||||
}
|
||||
|
||||
|
@ -7,41 +7,79 @@
|
||||
#ifndef BOOT_IO_H__
|
||||
#define BOOT_IO_H__
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/**
|
||||
* @typedef io8_t
|
||||
* @brief 8-bit volatile IO type.
|
||||
*/
|
||||
typedef volatile uint8_t io8_t;
|
||||
|
||||
/**
|
||||
* @typedef io32_t
|
||||
* @brief 32-bit volatile IO type.
|
||||
*/
|
||||
typedef volatile uint32_t io32_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Convert an address to its uncached equivalent.
|
||||
*
|
||||
* This macro takes an address and converts it to its uncached equivalent
|
||||
* by setting the appropriate bits.
|
||||
*
|
||||
* @param address The address to convert.
|
||||
* @return The uncached equivalent of the address.
|
||||
*/
|
||||
#define UNCACHED(address) ((typeof(address)) (((io32_t) (address)) | (0xA0000000UL)))
|
||||
|
||||
/** @brief Memory Structure. */
|
||||
/**
|
||||
* @brief Memory Structure.
|
||||
*
|
||||
* This structure represents the memory layout for the SP (Signal Processor),
|
||||
* containing both Data Memory (DMEM) and Instruction Memory (IMEM).
|
||||
*/
|
||||
typedef struct {
|
||||
io32_t DMEM[1024];
|
||||
io32_t IMEM[1024];
|
||||
io32_t DMEM[1024]; /**< Data Memory (DMEM) array of 1024 32-bit words. */
|
||||
io32_t IMEM[1024]; /**< Instruction Memory (IMEM) array of 1024 32-bit words. */
|
||||
} sp_mem_t;
|
||||
|
||||
/**
|
||||
* @brief Base address for SP memory.
|
||||
*/
|
||||
#define SP_MEM_BASE (0x04000000UL)
|
||||
|
||||
/**
|
||||
* @brief Pointer to the SP memory structure.
|
||||
*/
|
||||
#define SP_MEM ((sp_mem_t *) SP_MEM_BASE)
|
||||
|
||||
/** @brief SP Registers Structure. */
|
||||
/**
|
||||
* @brief SP Registers Structure.
|
||||
*
|
||||
* This structure represents the registers for the SP (Signal Processor).
|
||||
*/
|
||||
typedef struct {
|
||||
io32_t PADDR;
|
||||
io32_t MADDR;
|
||||
io32_t RD_LEN;
|
||||
io32_t WR_LEN;
|
||||
io32_t SR;
|
||||
io32_t DMA_FULL;
|
||||
io32_t DMA_BUSY;
|
||||
io32_t SEMAPHORE;
|
||||
io32_t PADDR; /**< Physical Address Register. */
|
||||
io32_t MADDR; /**< Memory Address Register. */
|
||||
io32_t RD_LEN; /**< Read Length Register. */
|
||||
io32_t WR_LEN; /**< Write Length Register. */
|
||||
io32_t SR; /**< Status Register. */
|
||||
io32_t DMA_FULL; /**< DMA Full Register. */
|
||||
io32_t DMA_BUSY; /**< DMA Busy Register. */
|
||||
io32_t SEMAPHORE; /**< Semaphore Register. */
|
||||
io32_t __reserved[0xFFF8];
|
||||
io32_t PC;
|
||||
} sp_regs_t;
|
||||
|
||||
/**
|
||||
* @brief Base address for SP registers.
|
||||
*/
|
||||
#define SP_BASE (0x04040000UL)
|
||||
|
||||
/**
|
||||
* @brief Pointer to the SP registers structure.
|
||||
*/
|
||||
#define SP ((sp_regs_t *) SP_BASE)
|
||||
|
||||
#define SP_SR_HALT (1 << 0)
|
||||
@ -85,7 +123,6 @@ typedef struct {
|
||||
#define SP_SR_CLR_SIG7 (1 << 23)
|
||||
#define SP_SR_SET_SIG7 (1 << 24)
|
||||
|
||||
|
||||
/** @brief DPC Registers Structure. */
|
||||
typedef struct {
|
||||
io32_t START;
|
||||
@ -123,7 +160,6 @@ typedef struct {
|
||||
#define DPC_SR_CLR_CMD_CTR (1 << 8)
|
||||
#define DPC_SR_CLR_CLOCK_CTR (1 << 9)
|
||||
|
||||
|
||||
/** @brief Video Interface Registers Structure. */
|
||||
typedef struct {
|
||||
/** @brief The Control Register. */
|
||||
@ -198,7 +234,6 @@ typedef struct {
|
||||
#define AI_SR_FIFO_FULL (1 << 31)
|
||||
#define AI_CR_DMA_ON (1 << 0)
|
||||
|
||||
|
||||
/** @brief Peripheral Interface Register Structure. */
|
||||
typedef struct {
|
||||
/** @brief The Memory Address. */
|
||||
@ -233,15 +268,12 @@ typedef struct {
|
||||
#define PI_SR_RESET (1 << 0)
|
||||
#define PI_SR_CLR_INTR (1 << 1)
|
||||
|
||||
|
||||
#define ROM_DDIPL_BASE (0x06000000UL)
|
||||
#define ROM_DDIPL ((io32_t *) ROM_DDIPL_BASE)
|
||||
|
||||
|
||||
#define ROM_CART_BASE (0x10000000UL)
|
||||
#define ROM_CART ((io32_t *) ROM_CART_BASE)
|
||||
|
||||
|
||||
static inline uint32_t cpu_io_read (io32_t *address) {
|
||||
io32_t *uncached = UNCACHED(address);
|
||||
uint32_t value = *uncached;
|
||||
@ -253,5 +285,4 @@ static inline void cpu_io_write (io32_t *address, uint32_t value) {
|
||||
*uncached = value;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
#endif /* BOOT_IO_H__ */
|
||||
|
@ -3,37 +3,47 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief VR4300 Instruction Structure
|
||||
*
|
||||
* This structure represents a VR4300 instruction, which can be of different types (R-type, I-type, J-type, etc.).
|
||||
*/
|
||||
typedef union {
|
||||
uint32_t raw;
|
||||
uint32_t raw; /**< Raw 32-bit instruction */
|
||||
|
||||
struct {
|
||||
uint32_t op : 6;
|
||||
uint32_t rs : 5;
|
||||
uint32_t rt : 5;
|
||||
uint32_t imm : 16;
|
||||
} i_type;
|
||||
uint32_t op : 6; /**< Opcode field */
|
||||
uint32_t rs : 5; /**< Source register */
|
||||
uint32_t rt : 5; /**< Target register */
|
||||
uint32_t imm : 16; /**< Immediate value */
|
||||
} i_type; /**< I-type instruction format */
|
||||
|
||||
struct {
|
||||
uint32_t op : 6;
|
||||
uint32_t target : 26;
|
||||
} j_type;
|
||||
uint32_t op : 6; /**< Opcode field */
|
||||
uint32_t target : 26; /**< Target Address field */
|
||||
} j_type; /**< J-type instruction format */
|
||||
|
||||
struct {
|
||||
uint32_t op : 6;
|
||||
uint32_t rs : 5;
|
||||
uint32_t rt : 5;
|
||||
uint32_t rd : 5;
|
||||
uint32_t sa : 5;
|
||||
uint32_t funct : 6;
|
||||
} r_type;
|
||||
uint32_t op : 6; /**< Opcode field */
|
||||
uint32_t rs : 5; /**< Source register */
|
||||
uint32_t rt : 5; /**< Target register */
|
||||
uint32_t rd : 5; /**< Destination register */
|
||||
uint32_t sa : 5; /**< Shift amount */
|
||||
uint32_t funct : 6; /**< Function field */
|
||||
} r_type; /**< Alternate R-type instruction format */
|
||||
|
||||
struct {
|
||||
uint32_t op : 6;
|
||||
uint32_t co : 1;
|
||||
uint32_t funct : 25;
|
||||
} c_type;
|
||||
uint32_t op : 6; /**< Opcode field */
|
||||
uint32_t co : 1; /**< Coprocessor operation bit */
|
||||
uint32_t funct : 25; /**< Function field */
|
||||
} c_type; /**< C-type instruction format */
|
||||
} vr4300_instruction_t;
|
||||
|
||||
/**
|
||||
* @brief VR4300 Opcode Enumeration
|
||||
*
|
||||
* Enumeration for different opcodes used in VR4300 instructions.
|
||||
*/
|
||||
typedef enum {
|
||||
OP_SPECIAL,
|
||||
OP_REGIMM,
|
||||
@ -394,4 +404,4 @@ typedef enum {
|
||||
#define I_SRL(rd, rt, sa) __ASM_R_INST(OP_SPECIAL, 0, rt, rd, sa, FUNCT_SRL)
|
||||
#define I_SW(rt, offset, base) __ASM_I_INST(OP_SW, base, rt, offset)
|
||||
|
||||
#endif
|
||||
#endif /* VR4300_ASM_H__ */
|
||||
|
@ -75,6 +75,9 @@ static bool d64_has_feature (flashcart_features_t feature) {
|
||||
case FLASHCART_FEATURE_64DD: return false;
|
||||
case FLASHCART_FEATURE_RTC: return true;
|
||||
case FLASHCART_FEATURE_USB: return true;
|
||||
case FLASHCART_FEATURE_AUTO_CIC: return true;
|
||||
case FLASHCART_FEATURE_AUTO_REGION: return true;
|
||||
case FLASHCART_FEATURE_SAVE_WRITEBACK: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@ -99,7 +102,7 @@ static flashcart_err_t d64_load_rom (char *rom_path, flashcart_progress_callback
|
||||
size_t sdram_size = MiB(64);
|
||||
|
||||
size_t chunk_size = KiB(128);
|
||||
for (int offset = 0; offset < sdram_size; offset += chunk_size) {
|
||||
for (unsigned int offset = 0; offset < sdram_size; offset += chunk_size) {
|
||||
size_t block_size = MIN(sdram_size - offset, chunk_size);
|
||||
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
|
@ -1,4 +1,4 @@
|
||||
## 64drive developer notes
|
||||
# 64drive developer notes
|
||||
|
||||
### Official documentation
|
||||
|
||||
|
@ -29,6 +29,11 @@ typedef enum {
|
||||
FLASHCART_FEATURE_64DD,
|
||||
FLASHCART_FEATURE_RTC,
|
||||
FLASHCART_FEATURE_USB,
|
||||
FLASHCART_FEATURE_AUTO_CIC,
|
||||
FLASHCART_FEATURE_AUTO_REGION,
|
||||
FLASHCART_FEATURE_DIAGNOSTIC_DATA,
|
||||
FLASHCART_FEATURE_BIOS_UPDATE_FROM_MENU,
|
||||
FLASHCART_FEATURE_SAVE_WRITEBACK
|
||||
} flashcart_features_t;
|
||||
|
||||
/** @brief Flashcart save type enumeration */
|
||||
|
@ -1,4 +1,4 @@
|
||||
## SummerCart64 developer notes
|
||||
# SummerCart64 developer notes
|
||||
|
||||
### Official documentation
|
||||
|
||||
|
@ -232,7 +232,7 @@ static flashcart_err_t sc64_init (void) {
|
||||
{ CFG_ID_ROM_EXTENDED_ENABLE, false },
|
||||
};
|
||||
|
||||
for (int i = 0; i < sizeof(default_config) / sizeof(default_config[0]); i++) {
|
||||
for (unsigned int i = 0; i < sizeof(default_config) / sizeof(default_config[0]); i++) {
|
||||
if (sc64_ll_set_config(default_config[i].id, default_config[i].value) != SC64_OK) {
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
@ -254,6 +254,10 @@ static bool sc64_has_feature (flashcart_features_t feature) {
|
||||
case FLASHCART_FEATURE_64DD: return true;
|
||||
case FLASHCART_FEATURE_RTC: return true;
|
||||
case FLASHCART_FEATURE_USB: return true;
|
||||
case FLASHCART_FEATURE_AUTO_CIC: return true;
|
||||
case FLASHCART_FEATURE_AUTO_REGION: return true;
|
||||
case FLASHCART_FEATURE_DIAGNOSTIC_DATA: return true;
|
||||
case FLASHCART_FEATURE_SAVE_WRITEBACK: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@ -283,7 +287,7 @@ static flashcart_err_t sc64_load_rom (char *rom_path, flashcart_progress_callbac
|
||||
size_t extended_size = extended_enabled ? rom_size - MiB(64) : 0;
|
||||
|
||||
size_t chunk_size = KiB(128);
|
||||
for (int offset = 0; offset < sdram_size; offset += chunk_size) {
|
||||
for (unsigned int offset = 0; offset < sdram_size; offset += chunk_size) {
|
||||
size_t block_size = MIN(sdram_size - offset, chunk_size);
|
||||
if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
@ -445,7 +449,7 @@ static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_ca
|
||||
}
|
||||
|
||||
size_t chunk_size = KiB(128);
|
||||
for (int offset = 0; offset < ipl_size; offset += chunk_size) {
|
||||
for (unsigned int offset = 0; offset < ipl_size; offset += chunk_size) {
|
||||
size_t block_size = MIN(ipl_size - offset, chunk_size);
|
||||
if (f_read(&fil, (void *) (IPL_ADDRESS + offset), block_size, &br) != FR_OK) {
|
||||
f_close(&fil);
|
||||
@ -499,7 +503,7 @@ static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_para
|
||||
{ CFG_ID_BUTTON_MODE, BUTTON_MODE_DD_DISK_SWAP },
|
||||
};
|
||||
|
||||
for (int i = 0; i < sizeof(config) / sizeof(config[0]); i++) {
|
||||
for (unsigned int i = 0; i < sizeof(config) / sizeof(config[0]); i++) {
|
||||
if (sc64_ll_set_config(config[i].id, config[i].value) != SC64_OK) {
|
||||
return FLASHCART_ERR_INT;
|
||||
}
|
||||
|
2
src/libs/miniz
vendored
2
src/libs/miniz
vendored
@ -1 +1 @@
|
||||
Subproject commit 16413c213de38e703d883006193734e8b1178d5d
|
||||
Subproject commit 35528ad769143b9ed38a95a22d460b963e39f278
|
@ -25,8 +25,16 @@ static void actions_clear (menu_t *menu) {
|
||||
}
|
||||
|
||||
static void actions_update_direction (menu_t *menu) {
|
||||
joypad_8way_t held_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_DPAD | JOYPAD_2D_STICK);
|
||||
joypad_8way_t fast_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_C);
|
||||
joypad_8way_t held_dir = JOYPAD_8WAY_NONE;
|
||||
joypad_8way_t fast_dir = JOYPAD_8WAY_NONE;
|
||||
|
||||
JOYPAD_PORT_FOREACH (i) {
|
||||
held_dir = joypad_get_direction(i, JOYPAD_2D_DPAD | JOYPAD_2D_STICK);
|
||||
fast_dir = joypad_get_direction(i, JOYPAD_2D_C);
|
||||
if (held_dir != JOYPAD_8WAY_NONE || fast_dir != JOYPAD_8WAY_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fast_dir != JOYPAD_8WAY_NONE) {
|
||||
held_dir = fast_dir;
|
||||
@ -82,7 +90,14 @@ static void actions_update_direction (menu_t *menu) {
|
||||
}
|
||||
|
||||
static void actions_update_buttons (menu_t *menu) {
|
||||
joypad_buttons_t pressed = joypad_get_buttons_pressed(JOYPAD_PORT_1);
|
||||
joypad_buttons_t pressed = {0};
|
||||
|
||||
JOYPAD_PORT_FOREACH (i) {
|
||||
pressed = joypad_get_buttons_pressed(i);
|
||||
if (pressed.raw) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pressed.a) {
|
||||
menu->actions.enter = true;
|
||||
@ -98,6 +113,12 @@ static void actions_update_buttons (menu_t *menu) {
|
||||
}
|
||||
|
||||
|
||||
void actions_init (void) {
|
||||
JOYPAD_PORT_FOREACH (port) {
|
||||
joypad_set_rumble_active(port, false);
|
||||
}
|
||||
}
|
||||
|
||||
void actions_update (menu_t *menu) {
|
||||
joypad_poll();
|
||||
|
||||
|
@ -10,7 +10,10 @@
|
||||
|
||||
#include "menu_state.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize the actions module
|
||||
*/
|
||||
void actions_init (void);
|
||||
void actions_update (menu_t *menu);
|
||||
|
||||
|
||||
|
@ -1,74 +1,261 @@
|
||||
/**
|
||||
* @file components.h
|
||||
* @brief Menu Components
|
||||
* @brief Menu Graphical User Interface Components
|
||||
* @ingroup menu
|
||||
*/
|
||||
|
||||
#ifndef COMPONENTS_H__
|
||||
#define COMPONENTS_H__
|
||||
|
||||
|
||||
#include <libdragon.h>
|
||||
#include "menu_state.h"
|
||||
|
||||
|
||||
/**
|
||||
* @addtogroup
|
||||
* @{ menu_components
|
||||
* @addtogroup menu_ui_components
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief File image Enumeration.
|
||||
*
|
||||
* Enumeration for different types of file images used in the GUI.
|
||||
*/
|
||||
typedef enum {
|
||||
IMAGE_BOXART_FRONT, /**< Boxart image from the front */
|
||||
IMAGE_BOXART_BACK, /**< Boxart image from the back */
|
||||
IMAGE_BOXART_TOP, /**< Boxart image from the top */
|
||||
IMAGE_BOXART_BOTTOM, /**< Boxart image from the bottom */
|
||||
IMAGE_BOXART_LEFT, /**< Boxart image from the left side */
|
||||
IMAGE_BOXART_RIGHT, /**< Boxart image from the right side */
|
||||
IMAGE_GAMEPAK_FRONT, /**< GamePak image from the front */
|
||||
IMAGE_GAMEPAK_BACK, /**< GamePak image from the back */
|
||||
IMAGE_THUMBNAIL, /**< File image thumbnail */
|
||||
IMAGE_TYPE_END /**< List end marker */
|
||||
} file_image_type_t;
|
||||
|
||||
/**
|
||||
* @brief Draw a box component.
|
||||
*
|
||||
* @param x0 Starting x-coordinate.
|
||||
* @param y0 Starting y-coordinate.
|
||||
* @param x1 Ending x-coordinate.
|
||||
* @param y1 Ending y-coordinate.
|
||||
* @param color Color of the box.
|
||||
*/
|
||||
void component_box_draw(int x0, int y0, int x1, int y1, color_t color);
|
||||
|
||||
/**
|
||||
* @brief Draw a border component.
|
||||
*
|
||||
* @param x0 Starting x-coordinate.
|
||||
* @param y0 Starting y-coordinate.
|
||||
* @param x1 Ending x-coordinate.
|
||||
* @param y1 Ending y-coordinate.
|
||||
*/
|
||||
void component_border_draw(int x0, int y0, int x1, int y1);
|
||||
|
||||
/**
|
||||
* @brief Draw the layout component.
|
||||
*/
|
||||
void component_layout_draw(void);
|
||||
|
||||
/**
|
||||
* @brief Draw a progress bar component.
|
||||
*
|
||||
* @param x0 Starting x-coordinate.
|
||||
* @param y0 Starting y-coordinate.
|
||||
* @param x1 Ending x-coordinate.
|
||||
* @param y1 Ending y-coordinate.
|
||||
* @param progress Progress value (0.0 to 1.0).
|
||||
*/
|
||||
void component_progressbar_draw(int x0, int y0, int x1, int y1, float progress);
|
||||
|
||||
/**
|
||||
* @brief Draw a seek bar component.
|
||||
*
|
||||
* @param progress Progress value (0.0 to 1.0).
|
||||
*/
|
||||
void component_seekbar_draw(float progress);
|
||||
|
||||
/**
|
||||
* @brief Draw a loader component.
|
||||
*
|
||||
* @param position Position value (0.0 to 1.0).
|
||||
*/
|
||||
void component_loader_draw(float position);
|
||||
|
||||
/**
|
||||
* @brief Draw a scrollbar component.
|
||||
*
|
||||
* @param x Starting x-coordinate.
|
||||
* @param y Starting y-coordinate.
|
||||
* @param width Width of the scrollbar.
|
||||
* @param height Height of the scrollbar.
|
||||
* @param position Current position.
|
||||
* @param items Total number of items.
|
||||
* @param visible_items Number of visible items.
|
||||
*/
|
||||
void component_scrollbar_draw(int x, int y, int width, int height, int position, int items, int visible_items);
|
||||
|
||||
/**
|
||||
* @brief Draw a list scrollbar component.
|
||||
*
|
||||
* @param position Current position.
|
||||
* @param items Total number of items.
|
||||
* @param visible_items Number of visible items.
|
||||
*/
|
||||
void component_list_scrollbar_draw(int position, int items, int visible_items);
|
||||
|
||||
/**
|
||||
* @brief Draw a dialog component.
|
||||
*
|
||||
* @param width Width of the dialog.
|
||||
* @param height Height of the dialog.
|
||||
*/
|
||||
void component_dialog_draw(int width, int height);
|
||||
|
||||
/**
|
||||
* @brief Draw a message box component.
|
||||
*
|
||||
* @param fmt Format string for the message.
|
||||
* @param ... Additional arguments for the format string.
|
||||
*/
|
||||
void component_messagebox_draw(char *fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief Draw the main text component.
|
||||
*
|
||||
* @param align Horizontal alignment.
|
||||
* @param valign Vertical alignment.
|
||||
* @param fmt Format string for the text.
|
||||
* @param ... Additional arguments for the format string.
|
||||
*/
|
||||
void component_main_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief Draw the actions bar text component.
|
||||
*
|
||||
* @param align Horizontal alignment.
|
||||
* @param valign Vertical alignment.
|
||||
* @param fmt Format string for the text.
|
||||
* @param ... Additional arguments for the format string.
|
||||
*/
|
||||
void component_actions_bar_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief Initialize the background component.
|
||||
*
|
||||
* @param cache_location Location of the cache.
|
||||
*/
|
||||
void component_background_init(char *cache_location);
|
||||
|
||||
/**
|
||||
* @brief Free the background component resources.
|
||||
*/
|
||||
void component_background_free(void);
|
||||
|
||||
/**
|
||||
* @brief Replace the background image.
|
||||
*
|
||||
* @param image New background image.
|
||||
*/
|
||||
void component_background_replace_image(surface_t *image);
|
||||
|
||||
/**
|
||||
* @brief Draw the background component.
|
||||
*/
|
||||
void component_background_draw(void);
|
||||
|
||||
/**
|
||||
* @brief Draw the file list component.
|
||||
*
|
||||
* @param list List of entries.
|
||||
* @param entries Number of entries.
|
||||
* @param selected Index of the selected entry.
|
||||
*/
|
||||
void component_file_list_draw(entry_t *list, int entries, int selected);
|
||||
|
||||
/**
|
||||
* @brief Context menu structure.
|
||||
*/
|
||||
typedef struct component_context_menu {
|
||||
int count;
|
||||
int selected;
|
||||
bool hide_pending;
|
||||
struct component_context_menu *parent;
|
||||
struct component_context_menu *submenu;
|
||||
int row_count; /**< Number of rows in the context menu */
|
||||
int row_selected; /**< Index of the selected row */
|
||||
bool hide_pending; /**< Flag to indicate if hiding is pending */
|
||||
struct component_context_menu *parent; /**< Pointer to the parent context menu */
|
||||
struct component_context_menu *submenu; /**< Pointer to the submenu */
|
||||
struct {
|
||||
const char *text;
|
||||
void (*action) (menu_t *menu, void *arg);
|
||||
void *arg;
|
||||
struct component_context_menu *submenu;
|
||||
} list[];
|
||||
const char *text; /**< Text of the menu item */
|
||||
void (*action)(menu_t *menu, void *arg); /**< Action function for the menu item */
|
||||
void *arg; /**< Argument for the action function */
|
||||
struct component_context_menu *submenu; /**< Pointer to the submenu */
|
||||
} list[]; /**< List of menu items */
|
||||
} component_context_menu_t;
|
||||
|
||||
#define COMPONENT_CONTEXT_MENU_LIST_END { .text = NULL }
|
||||
#define COMPONENT_CONTEXT_MENU_LIST_END { .text = NULL } /**< End marker for the context menu list */
|
||||
|
||||
/**
|
||||
* @brief Initialize the context menu component.
|
||||
*
|
||||
* @param cm Pointer to the context menu structure.
|
||||
*/
|
||||
void component_context_menu_init(component_context_menu_t *cm);
|
||||
|
||||
/**
|
||||
* @brief Show the context menu component.
|
||||
*
|
||||
* @param cm Pointer to the context menu structure.
|
||||
*/
|
||||
void component_context_menu_show(component_context_menu_t *cm);
|
||||
|
||||
/**
|
||||
* @brief Process the context menu component.
|
||||
*
|
||||
* @param menu Pointer to the menu structure.
|
||||
* @param cm Pointer to the context menu structure.
|
||||
* @return True if the context menu was processed, false otherwise.
|
||||
*/
|
||||
bool component_context_menu_process(menu_t *menu, component_context_menu_t *cm);
|
||||
|
||||
/**
|
||||
* @brief Draw the context menu component.
|
||||
*
|
||||
* @param cm Pointer to the context menu structure.
|
||||
*/
|
||||
void component_context_menu_draw(component_context_menu_t *cm);
|
||||
|
||||
/** @brief Box Art Structure. */
|
||||
/**
|
||||
* @brief Box Art Structure.
|
||||
*/
|
||||
typedef struct {
|
||||
bool loading;
|
||||
surface_t *image;
|
||||
bool loading; /**< Flag to indicate if the box art is loading */
|
||||
surface_t *image; /**< Pointer to the box art image */
|
||||
} component_boxart_t;
|
||||
|
||||
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code);
|
||||
/**
|
||||
* @brief Initialize the box art component.
|
||||
*
|
||||
* @param storage_prefix Prefix for the storage location.
|
||||
* @param game_code Game code for the box art.
|
||||
* @param current_image_view Current image view type.
|
||||
* @return Pointer to the initialized box art component.
|
||||
*/
|
||||
component_boxart_t *component_boxart_init(const char *storage_prefix, char *game_code, file_image_type_t current_image_view);
|
||||
|
||||
/**
|
||||
* @brief Free the box art component resources.
|
||||
*
|
||||
* @param b Pointer to the box art component.
|
||||
*/
|
||||
void component_boxart_free(component_boxart_t *b);
|
||||
|
||||
/**
|
||||
* @brief Draw the box art component.
|
||||
*
|
||||
* @param b Pointer to the box art component.
|
||||
*/
|
||||
void component_boxart_draw(component_boxart_t *b);
|
||||
|
||||
/** @} */ /* menu_components */
|
||||
/** @} */ /* menu_ui_components */
|
||||
|
||||
|
||||
#endif
|
||||
#endif /* COMPONENTS_H__ */
|
||||
|
@ -98,9 +98,6 @@ static void prepare_background (component_background_t *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t image_center_x = (c->image->width / 2);
|
||||
uint16_t image_center_y = (c->image->height / 2);
|
||||
|
||||
// Darken the image
|
||||
rdpq_attach(c->image, NULL);
|
||||
rdpq_mode_push();
|
||||
@ -108,15 +105,13 @@ static void prepare_background (component_background_t *c) {
|
||||
rdpq_set_prim_color(BACKGROUND_OVERLAY_COLOR);
|
||||
rdpq_mode_combiner(RDPQ_COMBINER_FLAT);
|
||||
rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY);
|
||||
rdpq_fill_rectangle(
|
||||
0 - (DISPLAY_CENTER_X - image_center_x),
|
||||
0 - (DISPLAY_CENTER_Y - image_center_y),
|
||||
DISPLAY_WIDTH - (DISPLAY_CENTER_X - image_center_x),
|
||||
DISPLAY_HEIGHT - (DISPLAY_CENTER_Y - image_center_y)
|
||||
);
|
||||
rdpq_fill_rectangle(0, 0, c->image->width, c->image->height);
|
||||
rdpq_mode_pop();
|
||||
rdpq_detach();
|
||||
|
||||
uint16_t image_center_x = (c->image->width / 2);
|
||||
uint16_t image_center_y = (c->image->height / 2);
|
||||
|
||||
// Prepare display list
|
||||
rspq_block_begin();
|
||||
rdpq_mode_push();
|
||||
|
@ -17,9 +17,9 @@ static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void
|
||||
}
|
||||
|
||||
|
||||
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code) {
|
||||
component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code, file_image_type_t current_image_view) {
|
||||
component_boxart_t *b;
|
||||
char file_name[8];
|
||||
char boxart_id_path[8];
|
||||
|
||||
if ((b = calloc(1, sizeof(component_boxart_t))) == NULL) {
|
||||
return NULL;
|
||||
@ -29,21 +29,90 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam
|
||||
|
||||
path_t *path = path_init(storage_prefix, BOXART_DIRECTORY);
|
||||
|
||||
sprintf(file_name, "%.3s.png", game_code);
|
||||
path_push(path, file_name);
|
||||
sprintf(boxart_id_path, "%c/%c/%c/%c", game_code[0], game_code[1], game_code[2], game_code[3]);
|
||||
path_push(path, boxart_id_path);
|
||||
|
||||
if (!directory_exists(path_get(path))) { // Allow boxart to not specify the region code.
|
||||
path_pop(path);
|
||||
}
|
||||
|
||||
if (directory_exists(path_get(path))) {
|
||||
switch (current_image_view) {
|
||||
case IMAGE_GAMEPAK_FRONT:
|
||||
path_push(path, "gamepak_front.png");
|
||||
break;
|
||||
case IMAGE_GAMEPAK_BACK:
|
||||
path_push(path, "gamepak_back.png");
|
||||
break;
|
||||
case IMAGE_BOXART_BACK:
|
||||
path_push(path, "boxart_back.png");
|
||||
break;
|
||||
case IMAGE_BOXART_LEFT:
|
||||
path_push(path, "boxart_left.png");
|
||||
break;
|
||||
case IMAGE_BOXART_RIGHT:
|
||||
path_push(path, "boxart_right.png");
|
||||
break;
|
||||
case IMAGE_BOXART_BOTTOM:
|
||||
path_push(path, "boxart_bottom.png");
|
||||
break;
|
||||
case IMAGE_BOXART_TOP:
|
||||
path_push(path, "boxart_top.png");
|
||||
break;
|
||||
default:
|
||||
path_push(path, "boxart_front.png");
|
||||
break;
|
||||
}
|
||||
|
||||
if (file_exists(path_get(path))) {
|
||||
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
|
||||
path_free(path);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // compatibility mode
|
||||
|
||||
char file_name[9];
|
||||
|
||||
// reset the directory path used for boxart.
|
||||
path_free(path);
|
||||
path = path_init(storage_prefix, BOXART_DIRECTORY);
|
||||
|
||||
snprintf(file_name, sizeof(file_name), "%c%c%c%c.png", game_code[0], game_code[1], game_code[2], game_code[3]);
|
||||
path_push(path, file_name);
|
||||
|
||||
if (file_exists(path_get(path))) {
|
||||
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
|
||||
path_free(path);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
path_pop(path);
|
||||
snprintf(file_name, sizeof(file_name), "%c%c%c.png", game_code[0], game_code[1], game_code[2]);
|
||||
path_push(path, file_name);
|
||||
|
||||
if (file_exists(path_get(path))) {
|
||||
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
|
||||
path_free(path);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
else {
|
||||
path_pop(path);
|
||||
|
||||
// TODO: This is bad, we should only check for 3 letter codes
|
||||
sprintf(file_name, "%.2s.png", game_code + 1);
|
||||
snprintf(file_name, sizeof(file_name), "%c%c.png", game_code[1], game_code[2]);
|
||||
path_push(path, file_name);
|
||||
if (file_exists(path_get(path))) {
|
||||
if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) {
|
||||
path_free(path);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: return default image.
|
||||
|
||||
path_free(path);
|
||||
free(b);
|
||||
|
@ -151,7 +151,7 @@ void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *f
|
||||
.height = LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT - (TEXT_MARGIN_VERTICAL * 2),
|
||||
.align = align,
|
||||
.valign = valign,
|
||||
.wrap = WRAP_ELLIPSES,
|
||||
.wrap = WRAP_WORD,
|
||||
.line_spacing = TEXT_LINE_SPACING_ADJUST,
|
||||
},
|
||||
FNT_DEFAULT,
|
||||
|
@ -13,22 +13,22 @@ static component_context_menu_t *get_current_submenu (component_context_menu_t *
|
||||
|
||||
|
||||
void component_context_menu_init (component_context_menu_t *cm) {
|
||||
cm->selected = -1;
|
||||
cm->count = 0;
|
||||
cm->row_selected = -1;
|
||||
cm->row_count = 0;
|
||||
cm->hide_pending = false;
|
||||
cm->parent = NULL;
|
||||
for (int i = 0; (cm->list[i].text) != NULL; i++) {
|
||||
cm->count += 1;
|
||||
cm->row_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void component_context_menu_show (component_context_menu_t *cm) {
|
||||
cm->selected = 0;
|
||||
cm->row_selected = 0;
|
||||
cm->submenu = NULL;
|
||||
}
|
||||
|
||||
bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) {
|
||||
if (!cm || (cm->selected < 0)) {
|
||||
if (!cm || (cm->row_selected < 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -44,26 +44,26 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm)
|
||||
}
|
||||
sound_play_effect(SFX_EXIT);
|
||||
} else if (menu->actions.enter) {
|
||||
if (cm->list[cm->selected].submenu) {
|
||||
cm->submenu = cm->list[cm->selected].submenu;
|
||||
if (cm->list[cm->row_selected].submenu) {
|
||||
cm->submenu = cm->list[cm->row_selected].submenu;
|
||||
component_context_menu_init(cm->submenu);
|
||||
cm->submenu->selected = 0;
|
||||
cm->submenu->row_selected = 0;
|
||||
cm->submenu->parent = cm;
|
||||
} else if (cm->list[cm->selected].action) {
|
||||
cm->list[cm->selected].action(menu, cm->list[cm->selected].arg);
|
||||
} else if (cm->list[cm->row_selected].action) {
|
||||
cm->list[cm->row_selected].action(menu, cm->list[cm->row_selected].arg);
|
||||
top->hide_pending = true;
|
||||
}
|
||||
sound_play_effect(SFX_ENTER);
|
||||
} else if (menu->actions.go_up) {
|
||||
cm->selected -= 1;
|
||||
if (cm->selected < 0) {
|
||||
cm->selected = 0;
|
||||
cm->row_selected -= 1;
|
||||
if (cm->row_selected < 0) {
|
||||
cm->row_selected = 0;
|
||||
}
|
||||
sound_play_effect(SFX_CURSOR);
|
||||
} else if (menu->actions.go_down) {
|
||||
cm->selected += 1;
|
||||
if (cm->selected >= cm->count) {
|
||||
cm->selected = (cm->count - 1);
|
||||
cm->row_selected += 1;
|
||||
if (cm->row_selected >= cm->row_count) {
|
||||
cm->row_selected = (cm->row_count - 1);
|
||||
}
|
||||
sound_play_effect(SFX_CURSOR);
|
||||
}
|
||||
@ -72,7 +72,7 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm)
|
||||
}
|
||||
|
||||
void component_context_menu_draw (component_context_menu_t *cm) {
|
||||
if (!cm || (cm->selected < 0)) {
|
||||
if (!cm || (cm->row_selected < 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ void component_context_menu_draw (component_context_menu_t *cm) {
|
||||
NULL
|
||||
);
|
||||
|
||||
for (int i = 0; i < cm->count; i++) {
|
||||
for (int i = 0; i < cm->row_count; i++) {
|
||||
const char *text = cm->list[i].text;
|
||||
rdpq_paragraph_builder_span(text, strlen(text));
|
||||
if (cm->list[i + 1].text != NULL) {
|
||||
@ -110,7 +110,7 @@ void component_context_menu_draw (component_context_menu_t *cm) {
|
||||
int highlight_x0 = DISPLAY_CENTER_X - (width / 2);
|
||||
int highlight_x1 = DISPLAY_CENTER_X + (width / 2);
|
||||
int highlight_height = (layout->bbox.y1 - layout->bbox.y0) / layout->nlines;
|
||||
int highlight_y = VISIBLE_AREA_Y0 + layout->bbox.y0 + ((cm->selected) * highlight_height);
|
||||
int highlight_y = VISIBLE_AREA_Y0 + layout->bbox.y0 + ((cm->row_selected) * highlight_height);
|
||||
|
||||
component_box_draw(
|
||||
highlight_x0,
|
||||
@ -126,6 +126,6 @@ void component_context_menu_draw (component_context_menu_t *cm) {
|
||||
|
||||
if (top->hide_pending) {
|
||||
top->hide_pending = false;
|
||||
top->selected = -1;
|
||||
top->row_selected = -1;
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,17 @@ typedef struct {
|
||||
} disk_info_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Loads disk information from the specified path.
|
||||
*
|
||||
* This function reads the disk information from the given path and populates
|
||||
* the provided disk_info structure with the relevant data.
|
||||
*
|
||||
* @param path A pointer to a path_t structure that specifies the path to the disk.
|
||||
* @param disk_info A pointer to a disk_info_t structure where the disk information will be stored.
|
||||
* @return A disk_err_t value indicating the success or failure of the operation.
|
||||
*/
|
||||
disk_err_t disk_info_load (path_t *path, disk_info_t *disk_info);
|
||||
|
||||
|
||||
#endif
|
||||
#endif /* DISK_INFO_H__ */
|
||||
|
@ -7,23 +7,39 @@
|
||||
#ifndef FONTS_H__
|
||||
#define FONTS_H__
|
||||
|
||||
/** @brief Font type enumeration. */
|
||||
/**
|
||||
* @brief Font type enumeration.
|
||||
*
|
||||
* This enumeration defines the different types of fonts that can be used
|
||||
* in the menu system.
|
||||
*/
|
||||
typedef enum {
|
||||
FNT_DEFAULT = 1,
|
||||
FNT_DEFAULT = 1, /**< Default font type */
|
||||
} menu_font_type_t;
|
||||
|
||||
/** @brief Font style enumeration. */
|
||||
/**
|
||||
* @brief Font style enumeration.
|
||||
*
|
||||
* This enumeration defines the different styles of fonts that can be used
|
||||
* in the menu system.
|
||||
*/
|
||||
typedef enum {
|
||||
STL_DEFAULT = 0,
|
||||
STL_GREEN,
|
||||
STL_BLUE,
|
||||
STL_YELLOW,
|
||||
STL_ORANGE,
|
||||
STL_GRAY,
|
||||
STL_DEFAULT = 0, /**< Default font style */
|
||||
STL_GREEN, /**< Green font style */
|
||||
STL_BLUE, /**< Blue font style */
|
||||
STL_YELLOW, /**< Yellow font style */
|
||||
STL_ORANGE, /**< Orange font style */
|
||||
STL_GRAY, /**< Gray font style */
|
||||
} menu_font_style_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize fonts.
|
||||
*
|
||||
* This function initializes the fonts used in the menu system. It can load
|
||||
* custom fonts from the specified path.
|
||||
*
|
||||
* @param custom_font_path Path to the custom font file.
|
||||
*/
|
||||
void fonts_init(char *custom_font_path);
|
||||
|
||||
|
||||
#endif
|
||||
#endif /* FONTS_H__ */
|
||||
|
100
src/menu/menu.c
100
src/menu/menu.c
@ -27,53 +27,19 @@
|
||||
#define MENU_CACHE_DIRECTORY "cache"
|
||||
#define BACKGROUND_CACHE_FILE "background.data"
|
||||
|
||||
#define FRAMERATE_DIVIDER (2)
|
||||
#define LAG_REPORT (false)
|
||||
#define INTERLACED (true)
|
||||
#define FPS_LIMIT (30.0f)
|
||||
|
||||
|
||||
static menu_t *menu;
|
||||
static tv_type_t tv_type;
|
||||
static volatile int frame_counter = 0;
|
||||
|
||||
extern tv_type_t __boot_tvtype;
|
||||
|
||||
|
||||
static void frame_counter_handler (void) {
|
||||
frame_counter += 1;
|
||||
}
|
||||
|
||||
static void frame_counter_reset (void) {
|
||||
#if LAG_REPORT
|
||||
static int accumulated = 0;
|
||||
if (frame_counter > FRAMERATE_DIVIDER) {
|
||||
accumulated += frame_counter - FRAMERATE_DIVIDER;
|
||||
debugf(
|
||||
"LAG: %d additional frame(s) displayed since last draw (accumulated: %d)\n",
|
||||
frame_counter - FRAMERATE_DIVIDER,
|
||||
accumulated
|
||||
);
|
||||
}
|
||||
#endif
|
||||
frame_counter = 0;
|
||||
}
|
||||
|
||||
static void menu_init (boot_params_t *boot_params) {
|
||||
joypad_init();
|
||||
timer_init();
|
||||
rtc_init();
|
||||
rspq_init();
|
||||
rdpq_init();
|
||||
dfs_init(DFS_DEFAULT_LOCATION);
|
||||
|
||||
sound_init_default();
|
||||
|
||||
JOYPAD_PORT_FOREACH (port) {
|
||||
joypad_set_rumble_active(port, false);
|
||||
}
|
||||
|
||||
menu = calloc(1, sizeof(menu_t));
|
||||
assert(menu != NULL);
|
||||
|
||||
menu->boot_params = boot_params;
|
||||
|
||||
menu->mode = MENU_MODE_NONE;
|
||||
menu->next_mode = MENU_MODE_STARTUP;
|
||||
|
||||
@ -82,6 +48,19 @@ static void menu_init (boot_params_t *boot_params) {
|
||||
menu->next_mode = MENU_MODE_FAULT;
|
||||
}
|
||||
|
||||
joypad_init();
|
||||
timer_init();
|
||||
rtc_init();
|
||||
rspq_init();
|
||||
rdpq_init();
|
||||
dfs_init(DFS_DEFAULT_LOCATION);
|
||||
|
||||
actions_init();
|
||||
sound_init_default();
|
||||
sound_init_sfx();
|
||||
|
||||
hdmi_clear_game_id();
|
||||
|
||||
path_t *path = path_init(menu->storage_prefix, MENU_DIRECTORY);
|
||||
|
||||
directory_create(path_get(path));
|
||||
@ -91,6 +70,15 @@ static void menu_init (boot_params_t *boot_params) {
|
||||
settings_load(&menu->settings);
|
||||
path_pop(path);
|
||||
|
||||
resolution_t resolution = {
|
||||
.width = 640,
|
||||
.height = 480,
|
||||
.interlaced = INTERLACED ? INTERLACE_HALF : INTERLACE_OFF,
|
||||
.pal60 = menu->settings.pal60_enabled,
|
||||
};
|
||||
display_init(resolution, DEPTH_16_BPP, 2, GAMMA_NONE, INTERLACED ? FILTERS_DISABLED : FILTERS_RESAMPLE);
|
||||
display_set_fps_limit(FPS_LIMIT);
|
||||
|
||||
path_push(path, MENU_CUSTOM_FONT_FILE);
|
||||
fonts_init(path_get(path));
|
||||
path_pop(path);
|
||||
@ -103,40 +91,20 @@ static void menu_init (boot_params_t *boot_params) {
|
||||
|
||||
path_free(path);
|
||||
|
||||
menu->boot_params = boot_params;
|
||||
sound_use_sfx(menu->settings.sound_enabled);
|
||||
|
||||
menu->browser.directory = path_init(menu->storage_prefix, menu->settings.default_directory);
|
||||
if (!directory_exists(path_get(menu->browser.directory))) {
|
||||
path_free(menu->browser.directory);
|
||||
menu->browser.directory = path_init(menu->storage_prefix, "/");
|
||||
}
|
||||
|
||||
hdmi_clear_game_id();
|
||||
|
||||
tv_type = get_tv_type();
|
||||
if ((tv_type == TV_PAL) && menu->settings.pal60_enabled) {
|
||||
// HACK: Set TV type to NTSC, so PAL console would output 60 Hz signal instead.
|
||||
__boot_tvtype = TV_NTSC;
|
||||
}
|
||||
|
||||
sound_init_sfx();
|
||||
if (menu->settings.sound_enabled) {
|
||||
sound_use_sfx(true);
|
||||
}
|
||||
|
||||
display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_DISABLED);
|
||||
|
||||
register_VI_handler(frame_counter_handler);
|
||||
}
|
||||
|
||||
static void menu_deinit (menu_t *menu) {
|
||||
unregister_VI_handler(frame_counter_handler);
|
||||
|
||||
// NOTE: Restore previous TV type so boot procedure wouldn't passthrough wrong value.
|
||||
__boot_tvtype = tv_type;
|
||||
|
||||
hdmi_send_game_id(menu->boot_params);
|
||||
|
||||
component_background_free();
|
||||
|
||||
path_free(menu->load.disk_path);
|
||||
path_free(menu->load.rom_path);
|
||||
for (int i = 0; i < menu->browser.entries; i++) {
|
||||
@ -146,9 +114,7 @@ static void menu_deinit (menu_t *menu) {
|
||||
path_free(menu->browser.directory);
|
||||
free(menu);
|
||||
|
||||
component_background_free();
|
||||
|
||||
flashcart_deinit();
|
||||
display_close();
|
||||
|
||||
sound_deinit();
|
||||
|
||||
@ -158,7 +124,7 @@ static void menu_deinit (menu_t *menu) {
|
||||
timer_close();
|
||||
joypad_close();
|
||||
|
||||
display_close();
|
||||
flashcart_deinit();
|
||||
}
|
||||
|
||||
typedef const struct {
|
||||
@ -201,11 +167,9 @@ void menu_run (boot_params_t *boot_params) {
|
||||
menu_init(boot_params);
|
||||
|
||||
while (true) {
|
||||
surface_t *display = (frame_counter >= FRAMERATE_DIVIDER) ? display_try_get() : NULL;
|
||||
surface_t *display = display_try_get();
|
||||
|
||||
if (display != NULL) {
|
||||
frame_counter_reset();
|
||||
|
||||
actions_update(menu);
|
||||
|
||||
view_t *view = menu_get_view(menu->mode);
|
||||
|
@ -109,6 +109,12 @@ typedef struct {
|
||||
path_t *rom_patch_path;
|
||||
rom_patch_info_t rom_patch_info;
|
||||
} load;
|
||||
|
||||
struct {
|
||||
bool rom_file;
|
||||
bool disk_file;
|
||||
bool emulator_file;
|
||||
} boot_pending;
|
||||
} menu_t;
|
||||
|
||||
|
||||
|
@ -7,37 +7,166 @@
|
||||
#ifndef MP3_PLAYER_H__
|
||||
#define MP3_PLAYER_H__
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
/** @brief MP3 file error enumeration */
|
||||
/**
|
||||
* @brief MP3 file error enumeration.
|
||||
*
|
||||
* Enumeration for different types of errors that can occur in the MP3 player.
|
||||
*/
|
||||
typedef enum {
|
||||
MP3PLAYER_OK,
|
||||
MP3PLAYER_ERR_OUT_OF_MEM,
|
||||
MP3PLAYER_ERR_IO,
|
||||
MP3PLAYER_ERR_NO_FILE,
|
||||
MP3PLAYER_ERR_INVALID_FILE,
|
||||
MP3PLAYER_OK, /**< No error */
|
||||
MP3PLAYER_ERR_OUT_OF_MEM, /**< Out of memory error */
|
||||
MP3PLAYER_ERR_IO, /**< Input/Output error */
|
||||
MP3PLAYER_ERR_NO_FILE, /**< No file found error */
|
||||
MP3PLAYER_ERR_INVALID_FILE, /**< Invalid file error */
|
||||
} mp3player_err_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize the MP3 player mixer.
|
||||
*
|
||||
* This function initializes the mixer for the MP3 player.
|
||||
*/
|
||||
void mp3player_mixer_init(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize the MP3 player.
|
||||
*
|
||||
* This function initializes the MP3 player and prepares it for playback.
|
||||
*
|
||||
* @return mp3player_err_t Error code indicating the result of the initialization.
|
||||
*/
|
||||
mp3player_err_t mp3player_init(void);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the MP3 player.
|
||||
*
|
||||
* This function deinitializes the MP3 player and releases any resources.
|
||||
*/
|
||||
void mp3player_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Load an MP3 file.
|
||||
*
|
||||
* This function loads an MP3 file from the specified path.
|
||||
*
|
||||
* @param path Path to the MP3 file.
|
||||
* @return mp3player_err_t Error code indicating the result of the load operation.
|
||||
*/
|
||||
mp3player_err_t mp3player_load(char *path);
|
||||
|
||||
/**
|
||||
* @brief Unload the current MP3 file.
|
||||
*
|
||||
* This function unloads the currently loaded MP3 file.
|
||||
*/
|
||||
void mp3player_unload(void);
|
||||
|
||||
/**
|
||||
* @brief Process the MP3 player.
|
||||
*
|
||||
* This function processes the MP3 player, handling playback and other operations.
|
||||
*
|
||||
* @return mp3player_err_t Error code indicating the result of the process operation.
|
||||
*/
|
||||
mp3player_err_t mp3player_process(void);
|
||||
|
||||
/**
|
||||
* @brief Check if the MP3 player is playing.
|
||||
*
|
||||
* This function checks if the MP3 player is currently playing.
|
||||
*
|
||||
* @return true if the MP3 player is playing, false otherwise.
|
||||
*/
|
||||
bool mp3player_is_playing(void);
|
||||
|
||||
/**
|
||||
* @brief Check if the MP3 player has finished playing.
|
||||
*
|
||||
* This function checks if the MP3 player has finished playing the current file.
|
||||
*
|
||||
* @return true if the MP3 player has finished playing, false otherwise.
|
||||
*/
|
||||
bool mp3player_is_finished(void);
|
||||
|
||||
/**
|
||||
* @brief Start playback of the MP3 file.
|
||||
*
|
||||
* This function starts playback of the currently loaded MP3 file.
|
||||
*
|
||||
* @return mp3player_err_t Error code indicating the result of the play operation.
|
||||
*/
|
||||
mp3player_err_t mp3player_play(void);
|
||||
|
||||
/**
|
||||
* @brief Stop playback of the MP3 file.
|
||||
*
|
||||
* This function stops playback of the currently loaded MP3 file.
|
||||
*/
|
||||
void mp3player_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Toggle playback of the MP3 file.
|
||||
*
|
||||
* This function toggles playback of the currently loaded MP3 file.
|
||||
*
|
||||
* @return mp3player_err_t Error code indicating the result of the toggle operation.
|
||||
*/
|
||||
mp3player_err_t mp3player_toggle(void);
|
||||
|
||||
/**
|
||||
* @brief Mute or unmute the MP3 player.
|
||||
*
|
||||
* This function mutes or unmutes the MP3 player.
|
||||
*
|
||||
* @param mute true to mute, false to unmute.
|
||||
*/
|
||||
void mp3player_mute(bool mute);
|
||||
|
||||
/**
|
||||
* @brief Seek to a specific position in the MP3 file.
|
||||
*
|
||||
* This function seeks to a specific position in the currently loaded MP3 file.
|
||||
*
|
||||
* @param seconds Number of seconds to seek.
|
||||
* @return mp3player_err_t Error code indicating the result of the seek operation.
|
||||
*/
|
||||
mp3player_err_t mp3player_seek(int seconds);
|
||||
|
||||
/**
|
||||
* @brief Get the duration of the MP3 file.
|
||||
*
|
||||
* This function gets the duration of the currently loaded MP3 file.
|
||||
*
|
||||
* @return float Duration of the MP3 file in seconds.
|
||||
*/
|
||||
float mp3player_get_duration(void);
|
||||
|
||||
/**
|
||||
* @brief Get the bitrate of the MP3 file.
|
||||
*
|
||||
* This function gets the bitrate of the currently loaded MP3 file.
|
||||
*
|
||||
* @return float Bitrate of the MP3 file in kbps.
|
||||
*/
|
||||
float mp3player_get_bitrate(void);
|
||||
|
||||
/**
|
||||
* @brief Get the sample rate of the MP3 file.
|
||||
*
|
||||
* This function gets the sample rate of the currently loaded MP3 file.
|
||||
*
|
||||
* @return int Sample rate of the MP3 file in Hz.
|
||||
*/
|
||||
int mp3player_get_samplerate(void);
|
||||
|
||||
/**
|
||||
* @brief Get the current playback progress.
|
||||
*
|
||||
* This function gets the current playback progress of the MP3 file.
|
||||
*
|
||||
* @return float Current playback progress as a percentage (0.0 to 100.0).
|
||||
*/
|
||||
float mp3player_get_progress(void);
|
||||
|
||||
|
||||
#endif
|
||||
#endif /* MP3_PLAYER_H__ */
|
||||
|
@ -7,27 +7,68 @@
|
||||
#ifndef PNG_DECODER_H__
|
||||
#define PNG_DECODER_H__
|
||||
|
||||
|
||||
#include <surface.h>
|
||||
|
||||
|
||||
/** @brief PNG decoder errors */
|
||||
/**
|
||||
* @brief PNG decoder errors
|
||||
*
|
||||
* Enumeration for different types of errors that can occur in the PNG decoder.
|
||||
*/
|
||||
typedef enum {
|
||||
PNG_OK,
|
||||
PNG_ERR_INT,
|
||||
PNG_ERR_BUSY,
|
||||
PNG_ERR_OUT_OF_MEM,
|
||||
PNG_ERR_NO_FILE,
|
||||
PNG_ERR_BAD_FILE,
|
||||
PNG_OK, /**< No error */
|
||||
PNG_ERR_INT, /**< Internal error */
|
||||
PNG_ERR_BUSY, /**< Decoder is busy */
|
||||
PNG_ERR_OUT_OF_MEM, /**< Out of memory error */
|
||||
PNG_ERR_NO_FILE, /**< No file found error */
|
||||
PNG_ERR_BAD_FILE, /**< Bad file error */
|
||||
} png_err_t;
|
||||
|
||||
/**
|
||||
* @brief PNG decoder callback type.
|
||||
*
|
||||
* This typedef defines the callback function type used by the PNG decoder.
|
||||
*
|
||||
* @param err Error code indicating the result of the decoding process.
|
||||
* @param decoded_image Pointer to the decoded image surface.
|
||||
* @param callback_data User-defined data passed to the callback function.
|
||||
*/
|
||||
typedef void png_callback_t (png_err_t err, surface_t *decoded_image, void *callback_data);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Start the PNG decoding process.
|
||||
*
|
||||
* This function starts the PNG decoding process for the specified file.
|
||||
*
|
||||
* @param path Path to the PNG file.
|
||||
* @param max_width Maximum width of the decoded image.
|
||||
* @param max_height Maximum height of the decoded image.
|
||||
* @param callback Callback function to be called when decoding is complete.
|
||||
* @param callback_data User-defined data to be passed to the callback function.
|
||||
* @return png_err_t Error code indicating the result of the start operation.
|
||||
*/
|
||||
png_err_t png_decoder_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data);
|
||||
|
||||
/**
|
||||
* @brief Abort the PNG decoding process.
|
||||
*
|
||||
* This function aborts the ongoing PNG decoding process.
|
||||
*/
|
||||
void png_decoder_abort (void);
|
||||
|
||||
/**
|
||||
* @brief Get the progress of the PNG decoding process.
|
||||
*
|
||||
* This function returns the current progress of the PNG decoding process as a percentage.
|
||||
*
|
||||
* @return float Current progress of the decoding process (0.0 to 100.0).
|
||||
*/
|
||||
float png_decoder_get_progress (void);
|
||||
|
||||
/**
|
||||
* @brief Poll the PNG decoder.
|
||||
*
|
||||
* This function polls the PNG decoder to handle any ongoing decoding tasks.
|
||||
*/
|
||||
void png_decoder_poll (void);
|
||||
|
||||
|
||||
#endif
|
||||
#endif /* PNG_DECODER_H__ */
|
||||
|
@ -186,6 +186,9 @@ static const match_t database[] = {
|
||||
|
||||
MATCH_ID_REGION("NDKJ", SAVE_TYPE_EEPROM_4KBIT, FEAT_NONE), // Dark Rift [Space Dynamites (J)]
|
||||
|
||||
MATCH_ID_REGION("NPDJ", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK | FEAT_EXP_PAK_REQUIRED),// Perfect Dark (J)
|
||||
MATCH_ID("NPD", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK | FEAT_EXP_PAK_RECOMMENDED), // Perfect Dark
|
||||
|
||||
MATCH_ID_REGION("NSVE", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK), // Space Station Silicon Valley
|
||||
MATCH_ID("NSV", SAVE_TYPE_EEPROM_4KBIT, FEAT_RPAK | FEAT_EXP_PAK_BROKEN), // Space Station Silicon Valley
|
||||
|
||||
@ -305,7 +308,6 @@ static const match_t database[] = {
|
||||
MATCH_ID("NMV", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Mario Party 3
|
||||
MATCH_ID("NMX", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK), // Excitebike 64
|
||||
MATCH_ID("NNB", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK), // Kobe Bryant in NBA Courtside
|
||||
MATCH_ID("NPD", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK | FEAT_RPAK | FEAT_TPAK | FEAT_EXP_PAK_RECOMMENDED), // Perfect Dark
|
||||
MATCH_ID("NPP", SAVE_TYPE_EEPROM_16KBIT, FEAT_CPAK), // Parlor! Pro 64: Pachinko Jikki Simulation Game
|
||||
MATCH_ID("NR7", SAVE_TYPE_EEPROM_16KBIT, FEAT_TPAK), // Robot Poncots 64: 7tsu no Umi no Caramel
|
||||
MATCH_ID("NRZ", SAVE_TYPE_EEPROM_16KBIT, FEAT_RPAK), // Ridge Racer 64
|
||||
@ -833,6 +835,7 @@ static rom_err_t save_override (path_t *path, const char *id, int value, int def
|
||||
mini_t *ini = mini_try_load(path_get(overrides_path));
|
||||
|
||||
if (!ini) {
|
||||
path_free(overrides_path);
|
||||
return ROM_ERR_SAVE_IO;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,9 @@ static settings_t init = {
|
||||
.default_directory = "/",
|
||||
.use_saves_folder = true,
|
||||
.sound_enabled = true,
|
||||
.rom_autoload_enabled = false,
|
||||
.rom_autoload_path = "",
|
||||
.rom_autoload_filename = "",
|
||||
|
||||
/* Beta feature flags (should always init to off) */
|
||||
.bgm_enabled = false,
|
||||
@ -41,6 +44,10 @@ void settings_load (settings_t *settings) {
|
||||
settings->use_saves_folder = mini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder);
|
||||
settings->sound_enabled = mini_get_bool(ini, "menu", "sound_enabled", init.sound_enabled);
|
||||
|
||||
settings->rom_autoload_enabled = mini_get_bool(ini, "menu", "autoload_rom_enabled", init.rom_autoload_enabled);
|
||||
settings->rom_autoload_path = strdup(mini_get_string(ini, "autoload", "rom_path", init.rom_autoload_path));
|
||||
settings->rom_autoload_filename = strdup(mini_get_string(ini, "autoload", "rom_filename", init.rom_autoload_filename));
|
||||
|
||||
/* Beta feature flags, they might not be in the file */
|
||||
settings->bgm_enabled = mini_get_bool(ini, "menu_beta_flag", "bgm_enabled", init.bgm_enabled);
|
||||
settings->rumble_enabled = mini_get_bool(ini, "menu_beta_flag", "rumble_enabled", init.rumble_enabled);
|
||||
@ -56,6 +63,9 @@ void settings_save (settings_t *settings) {
|
||||
mini_set_string(ini, "menu", "default_directory", settings->default_directory);
|
||||
mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder);
|
||||
mini_set_bool(ini, "menu", "sound_enabled", settings->sound_enabled);
|
||||
mini_set_bool(ini, "menu", "autoload_rom_enabled", settings->rom_autoload_enabled);
|
||||
mini_set_string(ini, "autoload", "rom_path", settings->rom_autoload_path);
|
||||
mini_set_string(ini, "autoload", "rom_filename", settings->rom_autoload_filename);
|
||||
|
||||
/* Beta feature flags, they should not save until production ready! */
|
||||
// mini_set_bool(ini, "menu_beta_flag", "bgm_enabled", settings->bgm_enabled);
|
||||
|
@ -30,6 +30,16 @@ typedef struct {
|
||||
|
||||
/** @brief Enable rumble feedback */
|
||||
bool rumble_enabled;
|
||||
|
||||
/** @brief Enable the ability to bypass the menu and instantly load a ROM */
|
||||
bool rom_autoload_enabled;
|
||||
|
||||
/** @brief A path to the autoloaded ROM */
|
||||
char *rom_autoload_path;
|
||||
|
||||
/** @brief A filename of the autoloaded ROM */
|
||||
char *rom_autoload_filename;
|
||||
|
||||
} settings_t;
|
||||
|
||||
|
||||
@ -40,5 +50,4 @@ void settings_load (settings_t *settings);
|
||||
/** @brief The settings to save */
|
||||
void settings_save (settings_t *settings);
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -86,6 +86,13 @@ void sound_play_effect(sound_effect_t sfx) {
|
||||
|
||||
void sound_deinit (void) {
|
||||
if (sound_initialized) {
|
||||
if (sfx_enabled) {
|
||||
wav64_close(&sfx_cursor);
|
||||
wav64_close(&sfx_exit);
|
||||
wav64_close(&sfx_setting);
|
||||
wav64_close(&sfx_enter);
|
||||
wav64_close(&sfx_error);
|
||||
}
|
||||
mixer_close();
|
||||
audio_close();
|
||||
sound_initialized = false;
|
||||
@ -93,9 +100,7 @@ void sound_deinit (void) {
|
||||
}
|
||||
|
||||
void sound_poll (void) {
|
||||
if (sound_initialized && audio_can_write()) {
|
||||
short *audio_buffer = audio_write_begin();
|
||||
mixer_poll(audio_buffer, audio_get_buffer_length());
|
||||
audio_write_end();
|
||||
if (sound_initialized) {
|
||||
mixer_try_play();
|
||||
}
|
||||
}
|
||||
|
@ -9,24 +9,59 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define SOUND_MP3_PLAYER_CHANNEL (0)
|
||||
#define SOUND_SFX_CHANNEL (2)
|
||||
#define SOUND_MP3_PLAYER_CHANNEL (0) /**< Channel for MP3 player sound */
|
||||
#define SOUND_SFX_CHANNEL (2) /**< Channel for sound effects */
|
||||
|
||||
/**
|
||||
* @brief Enumeration of available sound effects for menu interactions.
|
||||
*
|
||||
* This enumeration defines the different sound effects that can be used
|
||||
* for menu interactions.
|
||||
*/
|
||||
typedef enum {
|
||||
SFX_CURSOR,
|
||||
SFX_ERROR,
|
||||
SFX_ENTER,
|
||||
SFX_EXIT,
|
||||
SFX_SETTING,
|
||||
SFX_CURSOR, /**< Sound effect for cursor movement */
|
||||
SFX_ERROR, /**< Sound effect for error */
|
||||
SFX_ENTER, /**< Sound effect for entering a menu */
|
||||
SFX_EXIT, /**< Sound effect for exiting a menu */
|
||||
SFX_SETTING, /**< Sound effect for changing a setting */
|
||||
} sound_effect_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize the default sound system.
|
||||
*
|
||||
* This function initializes the default sound system, setting up
|
||||
* necessary resources and configurations.
|
||||
*/
|
||||
void sound_init_default(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize the MP3 playback system.
|
||||
*
|
||||
* This function initializes the MP3 playback system, preparing it
|
||||
* for playing MP3 files.
|
||||
*/
|
||||
void sound_init_mp3_playback(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize the sound effects system.
|
||||
*
|
||||
* This function initializes the sound effects system, setting up
|
||||
* necessary resources and configurations for playing sound effects.
|
||||
*/
|
||||
void sound_init_sfx(void);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable sound effects.
|
||||
* @param enable True to enable sound effects, false to disable.
|
||||
*/
|
||||
void sound_use_sfx(bool);
|
||||
|
||||
/**
|
||||
* @brief Play a specified sound effect.
|
||||
* @param sfx The sound effect to play, as defined in sound_effect_t.
|
||||
*/
|
||||
void sound_play_effect(sound_effect_t sfx);
|
||||
void sound_deinit (void);
|
||||
void sound_poll (void);
|
||||
|
||||
#endif
|
||||
#endif /* SOUND_H__ */
|
||||
|
@ -2,20 +2,36 @@
|
||||
* @file usb_comm.h
|
||||
* @brief USB communication subsystem
|
||||
* @ingroup menu
|
||||
*
|
||||
* This file contains the declarations for the USB communication subsystem
|
||||
* used in the menu system.
|
||||
*/
|
||||
|
||||
#ifndef USB_COMM_H__
|
||||
#define USB_COMM_H__
|
||||
|
||||
|
||||
#include "menu_state.h"
|
||||
|
||||
|
||||
#ifndef NDEBUG
|
||||
/**
|
||||
* @brief Poll the USB communication subsystem.
|
||||
*
|
||||
* This function polls the USB communication subsystem to handle any
|
||||
* incoming or outgoing data. It is only available in debug builds.
|
||||
*
|
||||
* @param menu Pointer to the menu structure.
|
||||
*/
|
||||
void usb_comm_poll(menu_t *menu);
|
||||
#else
|
||||
/**
|
||||
* @brief Poll the USB communication subsystem (no-op in release builds).
|
||||
*
|
||||
* This macro is a no-op in release builds, where USB communication polling
|
||||
* is disabled.
|
||||
*
|
||||
* @param menu Pointer to the menu structure.
|
||||
*/
|
||||
#define usb_comm_poll(menu)
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
#endif /* USB_COMM_H__ */
|
||||
|
@ -12,8 +12,8 @@
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
rdpq_clear(RGBA32(0x7F, 0x00, 0x00, 0xFF));
|
||||
|
||||
const char *firmware_message = (
|
||||
"Supported firmware versions:\n"
|
||||
"Minimum supported firmware versions:\n"
|
||||
"64drive: 2.05+\n"
|
||||
"EverDrive-64: ???+\n"
|
||||
"SummerCart64: 2.17.0+"
|
||||
|
@ -50,8 +50,8 @@ static char *format_file_type (char *name, bool is_directory) {
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
S_ISDIR(st.st_mode) ? "Directory" : "File",
|
||||
st.st_mode & S_IWUSR ? "" : "(Read only)",
|
||||
format_file_type(menu->browser.entry->name, S_ISDIR(st.st_mode)),
|
||||
ctime(&st.st_mtim.tv_sec)
|
||||
ctime(&st.st_mtime)
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
|
@ -1,11 +1,35 @@
|
||||
#include "views.h"
|
||||
#include "../sound.h"
|
||||
#include <libcart/cart.h>
|
||||
|
||||
|
||||
static inline const char *format_boolean_type (bool bool_value) {
|
||||
return bool_value ? "Supported" : "Unsupported";
|
||||
}
|
||||
|
||||
static const char *format_cart_type () {
|
||||
switch (cart_type) {
|
||||
case CART_CI:
|
||||
return "64drive";
|
||||
|
||||
case CART_EDX:
|
||||
return "Series X EverDrive-64";
|
||||
|
||||
case CART_ED:
|
||||
return "Series V EverDrive-64";
|
||||
|
||||
case CART_SC:
|
||||
return "SummerCart64";
|
||||
|
||||
default: // Probably emulator
|
||||
return "Emulator?";
|
||||
}
|
||||
}
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,24 +42,41 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_CENTER, VALIGN_TOP,
|
||||
"FLASHCART INFORMATION\n"
|
||||
"FLASHCART INFORMATION"
|
||||
"\n"
|
||||
"\n"
|
||||
"This feature is not yet supported.\n\n"
|
||||
);
|
||||
|
||||
// FIXME: Display:
|
||||
// * cart_type
|
||||
// * Firmware version
|
||||
// * supported features (flashcart_features_t)
|
||||
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
"\n"
|
||||
);
|
||||
"Type:\n"
|
||||
" %s\n\n"
|
||||
"Firmware:\n"
|
||||
" %s\n\n"
|
||||
"Features:\n"
|
||||
" Virtual 64DD: %s.\n"
|
||||
" Real Time Clock: %s.\n"
|
||||
" USB Debugging: %s.\n"
|
||||
" Automatic CIC: %s.\n"
|
||||
" Region Detection: %s.\n"
|
||||
" Save Writeback: %s.\n"
|
||||
" Update from menu: %s.\n"
|
||||
"\n\n",
|
||||
format_cart_type(),
|
||||
"Not Available", // TODO get cart firmware version(s).
|
||||
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_64DD)),
|
||||
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_RTC)),
|
||||
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_USB)),
|
||||
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_CIC)),
|
||||
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_REGION)),
|
||||
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_SAVE_WRITEBACK)),
|
||||
format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_BIOS_UPDATE_FROM_MENU))
|
||||
|
||||
//TODO: display the battery and temperature information (if available).
|
||||
//format_diagnostic_data(flashcart_has_feature(FLASHCART_FEATURE_DIAGNOSTIC_DATA))
|
||||
);
|
||||
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
|
@ -5,8 +5,7 @@
|
||||
#include "views.h"
|
||||
|
||||
|
||||
static bool load_pending;
|
||||
static bool load_rom;
|
||||
static bool load_disk_with_rom;
|
||||
static component_boxart_t *boxart;
|
||||
|
||||
|
||||
@ -31,15 +30,15 @@ static char *format_disk_region (disk_region_t region) {
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.enter) {
|
||||
load_pending = true;
|
||||
load_rom = false;
|
||||
menu->boot_pending.disk_file = true;
|
||||
load_disk_with_rom = false;
|
||||
} else if (menu->actions.options && menu->load.rom_path) {
|
||||
load_pending = true;
|
||||
load_rom = true;
|
||||
menu->boot_pending.disk_file = true;
|
||||
load_disk_with_rom = true;
|
||||
sound_play_effect(SFX_SETTING);
|
||||
} else if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +47,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
component_background_draw();
|
||||
|
||||
if (load_pending) {
|
||||
if (menu->boot_pending.disk_file) {
|
||||
component_loader_draw(0.0f);
|
||||
} else {
|
||||
component_layout_draw();
|
||||
@ -94,8 +93,10 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
);
|
||||
}
|
||||
|
||||
if (boxart != NULL) {
|
||||
component_boxart_draw(boxart);
|
||||
}
|
||||
}
|
||||
|
||||
rdpq_detach_show();
|
||||
}
|
||||
@ -117,7 +118,7 @@ static void draw_progress (float progress) {
|
||||
static void load (menu_t *menu) {
|
||||
cart_load_err_t err;
|
||||
|
||||
if (menu->load.rom_path && load_rom) {
|
||||
if (menu->load.rom_path && load_disk_with_rom) {
|
||||
err = cart_load_n64_rom_and_save(menu, draw_progress);
|
||||
if (err != CART_LOAD_OK) {
|
||||
menu_show_error(menu, cart_load_convert_error_message(err));
|
||||
@ -133,7 +134,7 @@ static void load (menu_t *menu) {
|
||||
|
||||
menu->next_mode = MENU_MODE_BOOT;
|
||||
|
||||
if (load_rom) {
|
||||
if (load_disk_with_rom) {
|
||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
||||
menu->boot_params->detect_cic_seed = rom_info_get_cic_seed(&menu->load.rom_info, &menu->boot_params->cic_seed);
|
||||
switch (rom_info_get_tv_type(&menu->load.rom_info)) {
|
||||
@ -161,16 +162,17 @@ void view_load_disk_init (menu_t *menu) {
|
||||
menu->load.disk_path = NULL;
|
||||
}
|
||||
|
||||
load_pending = false;
|
||||
menu->boot_pending.disk_file = false;
|
||||
|
||||
menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
|
||||
disk_err_t err = disk_info_load(menu->load.disk_path, &menu->load.disk_info);
|
||||
if (err != DISK_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
return;
|
||||
}
|
||||
|
||||
boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id);
|
||||
boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id, IMAGE_BOXART_FRONT);
|
||||
}
|
||||
|
||||
void view_load_disk_display (menu_t *menu, surface_t *display) {
|
||||
@ -178,8 +180,8 @@ void view_load_disk_display (menu_t *menu, surface_t *display) {
|
||||
|
||||
draw(menu, display);
|
||||
|
||||
if (load_pending) {
|
||||
load_pending = false;
|
||||
if (menu->boot_pending.disk_file) {
|
||||
menu->boot_pending.disk_file = false;
|
||||
load(menu);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ static const char *emu_gameboy_rom_extensions[] = { "gb", NULL };
|
||||
static const char *emu_gameboy_color_rom_extensions[] = { "gbc", NULL };
|
||||
static const char *emu_sega_8bit_rom_extensions[] = { "sms", "gg", "sg", NULL };
|
||||
|
||||
static bool load_pending;
|
||||
static cart_load_emu_type_t emu_type;
|
||||
|
||||
static char *format_emulator_name (cart_load_emu_type_t emulator_info) {
|
||||
@ -34,10 +33,10 @@ static char *format_emulator_name (cart_load_emu_type_t emulator_info) {
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.enter) {
|
||||
load_pending = true;
|
||||
menu->boot_pending.emulator_file = true;
|
||||
} else if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +45,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
component_background_draw();
|
||||
|
||||
if (load_pending) {
|
||||
if (menu->boot_pending.emulator_file) {
|
||||
component_loader_draw(0.0f);
|
||||
} else {
|
||||
component_layout_draw();
|
||||
@ -107,7 +106,7 @@ static void load (menu_t *menu) {
|
||||
|
||||
|
||||
void view_load_emulator_init (menu_t *menu) {
|
||||
load_pending = false;
|
||||
menu->boot_pending.emulator_file = false;
|
||||
|
||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
|
||||
@ -133,8 +132,8 @@ void view_load_emulator_display (menu_t *menu, surface_t *display) {
|
||||
|
||||
draw(menu, display);
|
||||
|
||||
if (load_pending) {
|
||||
load_pending = false;
|
||||
if (menu->boot_pending.emulator_file) {
|
||||
menu->boot_pending.emulator_file = false;
|
||||
load(menu);
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,12 @@
|
||||
#include "boot/boot.h"
|
||||
#include "../sound.h"
|
||||
#include "views.h"
|
||||
#include <string.h>
|
||||
#include "utils/fs.h"
|
||||
|
||||
static bool show_extra_info_message = false;
|
||||
static bool load_pending;
|
||||
static component_boxart_t *boxart;
|
||||
|
||||
|
||||
static char *convert_error_message (rom_err_t err) {
|
||||
switch (err) {
|
||||
case ROM_ERR_LOAD_IO: return "I/O error during loading ROM information and/or options";
|
||||
@ -145,6 +145,17 @@ static void set_tv_type (menu_t *menu, void *arg) {
|
||||
menu->browser.reload = true;
|
||||
}
|
||||
|
||||
static void set_autoload_type (menu_t *menu, void *arg) {
|
||||
free(menu->settings.rom_autoload_path);
|
||||
menu->settings.rom_autoload_path = strdup(strip_fs_prefix(path_get(menu->browser.directory)));
|
||||
free(menu->settings.rom_autoload_filename);
|
||||
menu->settings.rom_autoload_filename = strdup(menu->browser.entry->name);
|
||||
// FIXME: add a confirmation box here! (press start on reboot)
|
||||
menu->settings.rom_autoload_enabled = true;
|
||||
settings_save(&menu->settings);
|
||||
menu->browser.reload = true;
|
||||
}
|
||||
|
||||
static component_context_menu_t set_cic_type_context_menu = { .list = {
|
||||
{.text = "Automatic", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_AUTOMATIC) },
|
||||
{.text = "CIC-6101", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_6101) },
|
||||
@ -187,6 +198,7 @@ static component_context_menu_t options_context_menu = { .list = {
|
||||
{ .text = "Set CIC Type", .submenu = &set_cic_type_context_menu },
|
||||
{ .text = "Set Save Type", .submenu = &set_save_type_context_menu },
|
||||
{ .text = "Set TV Type", .submenu = &set_tv_type_context_menu },
|
||||
{ .text = "Set ROM to autoload", .action = set_autoload_type },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
@ -196,10 +208,10 @@ static void process (menu_t *menu) {
|
||||
}
|
||||
|
||||
if (menu->actions.enter) {
|
||||
load_pending = true;
|
||||
menu->boot_pending.rom_file = true;
|
||||
} else if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
} else if (menu->actions.options) {
|
||||
component_context_menu_show(&options_context_menu);
|
||||
sound_play_effect(SFX_SETTING);
|
||||
@ -218,7 +230,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
component_background_draw();
|
||||
|
||||
if (load_pending) {
|
||||
if (menu->boot_pending.rom_file) {
|
||||
component_loader_draw(0.0f);
|
||||
} else {
|
||||
component_layout_draw();
|
||||
@ -262,7 +274,9 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"R: Options"
|
||||
);
|
||||
|
||||
if (boxart != NULL) {
|
||||
component_boxart_draw(boxart);
|
||||
}
|
||||
|
||||
if (show_extra_info_message) {
|
||||
component_messagebox_draw(
|
||||
@ -335,17 +349,18 @@ static void load (menu_t *menu) {
|
||||
|
||||
static void deinit (void) {
|
||||
component_boxart_free(boxart);
|
||||
boxart = NULL;
|
||||
}
|
||||
|
||||
|
||||
void view_load_rom_init (menu_t *menu) {
|
||||
load_pending = false;
|
||||
|
||||
if (!menu->settings.rom_autoload_enabled) {
|
||||
if (menu->load.rom_path) {
|
||||
path_free(menu->load.rom_path);
|
||||
}
|
||||
|
||||
menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||
}
|
||||
|
||||
rom_err_t err = rom_info_load(menu->load.rom_path, &menu->load.rom_info);
|
||||
if (err != ROM_OK) {
|
||||
@ -355,18 +370,19 @@ void view_load_rom_init (menu_t *menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code);
|
||||
|
||||
if (!menu->settings.rom_autoload_enabled) {
|
||||
boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code, IMAGE_BOXART_FRONT);
|
||||
component_context_menu_init(&options_context_menu);
|
||||
}
|
||||
}
|
||||
|
||||
void view_load_rom_display (menu_t *menu, surface_t *display) {
|
||||
process(menu);
|
||||
|
||||
draw(menu, display);
|
||||
|
||||
if (load_pending) {
|
||||
load_pending = false;
|
||||
if (menu->boot_pending.rom_file) {
|
||||
menu->boot_pending.rom_file = false;
|
||||
load(menu);
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,8 @@ static void process (menu_t *menu) {
|
||||
if (err != MP3PLAYER_OK) {
|
||||
menu_show_error(menu, convert_error_message(err));
|
||||
} else if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
} else if (menu->actions.enter) {
|
||||
err = mp3player_toggle();
|
||||
if (err != MP3PLAYER_OK) {
|
||||
|
@ -1,25 +1,164 @@
|
||||
#include <time.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <libdragon.h>
|
||||
#include <sys/time.h>
|
||||
#include "../sound.h"
|
||||
#include "views.h"
|
||||
|
||||
// FIXME: add implementation!
|
||||
// struct {
|
||||
// uint16_t seconds;
|
||||
// uint16_t minutes;
|
||||
// uint16_t hours;
|
||||
// uint16_t day;
|
||||
// uint16_t month;
|
||||
// uint16_t year;
|
||||
// } adjusted_datetime;
|
||||
#define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; })
|
||||
#define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; })
|
||||
#define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max)))
|
||||
|
||||
// static void save_adjusted_datetime () {
|
||||
#define YEAR_MIN 1996
|
||||
#define YEAR_MAX 2095
|
||||
|
||||
// }
|
||||
typedef enum {
|
||||
RTC_EDIT_YEAR,
|
||||
RTC_EDIT_MONTH,
|
||||
RTC_EDIT_DAY,
|
||||
RTC_EDIT_HOUR,
|
||||
RTC_EDIT_MIN,
|
||||
RTC_EDIT_SEC,
|
||||
} rtc_field_t;
|
||||
|
||||
static const char* const DAYS_OF_WEEK[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||
|
||||
static struct tm rtc_tm = {0};
|
||||
static bool is_editing_mode;
|
||||
static rtc_field_t editing_field_type;
|
||||
|
||||
int wrap( uint16_t val, uint16_t min, uint16_t max ) {
|
||||
if( val < min ) return max;
|
||||
if( val > max ) return min;
|
||||
return val;
|
||||
}
|
||||
|
||||
rtc_time_t rtc_time_from_tm( struct tm *time ) {
|
||||
return(rtc_time_t){
|
||||
.year = CLAMP(time->tm_year + 1900, YEAR_MIN, YEAR_MAX),
|
||||
.month = CLAMP(time->tm_mon, 1, 12),
|
||||
.day = CLAMP(time->tm_mday, 1, 31),
|
||||
.hour = CLAMP(time->tm_hour, 0, 23),
|
||||
.min = CLAMP(time->tm_min, 0, 59),
|
||||
.sec = CLAMP(time->tm_sec, 0, 59),
|
||||
.week_day = CLAMP(time->tm_wday, 0, 6),
|
||||
};
|
||||
}
|
||||
|
||||
void adjust_rtc_time( struct tm *t, int incr ) {
|
||||
switch(editing_field_type)
|
||||
{
|
||||
case RTC_EDIT_YEAR:
|
||||
t->tm_year = wrap( t->tm_year + incr, YEAR_MIN - 1900, YEAR_MAX - 1900 );
|
||||
break;
|
||||
case RTC_EDIT_MONTH:
|
||||
t->tm_mon = wrap( t->tm_mon + incr, 0, 11 );
|
||||
break;
|
||||
case RTC_EDIT_DAY:
|
||||
t->tm_mday = wrap( t->tm_mday + incr, 1, 31 );
|
||||
break;
|
||||
case RTC_EDIT_HOUR:
|
||||
t->tm_hour = wrap( t->tm_hour + incr, 0, 23 );
|
||||
break;
|
||||
case RTC_EDIT_MIN:
|
||||
t->tm_min = wrap( t->tm_min + incr, 0, 59 );
|
||||
break;
|
||||
case RTC_EDIT_SEC:
|
||||
t->tm_sec = wrap( t->tm_sec + incr, 0, 59 );
|
||||
break;
|
||||
}
|
||||
// Recalculate day-of-week and day-of-year
|
||||
time_t timestamp = mktime( t );
|
||||
*t = *gmtime( ×tamp );
|
||||
}
|
||||
|
||||
void component_editdatetime_draw ( struct tm t, rtc_field_t selected_field ) {
|
||||
// FIXME: move this to components.c once improved.
|
||||
/* Format RTC date/time as strings */
|
||||
char full_dt[30];
|
||||
char current_selection_chars[30];
|
||||
|
||||
snprintf( full_dt, sizeof(full_dt), ">%04d|%02d|%02d|%02d|%02d|%02d< %s",
|
||||
t.tm_year + 1900,
|
||||
t.tm_mon + 1,
|
||||
t.tm_mday,
|
||||
t.tm_hour,
|
||||
t.tm_min,
|
||||
t.tm_sec,
|
||||
DAYS_OF_WEEK[t.tm_wday]
|
||||
);
|
||||
|
||||
switch(selected_field)
|
||||
{
|
||||
// Note: for what ever reason, hat chars need to be duplicated to display correctly. This will be solved when there is a decent UI for it.
|
||||
case RTC_EDIT_YEAR:
|
||||
snprintf( current_selection_chars, sizeof(current_selection_chars), "*^^^^^^^^********************");
|
||||
break;
|
||||
case RTC_EDIT_MONTH:
|
||||
snprintf( current_selection_chars, sizeof(current_selection_chars), "******^^^^*****************");
|
||||
break;
|
||||
case RTC_EDIT_DAY:
|
||||
snprintf( current_selection_chars, sizeof(current_selection_chars), "*********^^^^**************");
|
||||
break;
|
||||
case RTC_EDIT_HOUR:
|
||||
snprintf( current_selection_chars, sizeof(current_selection_chars), "************^^^^***********");
|
||||
break;
|
||||
case RTC_EDIT_MIN:
|
||||
snprintf( current_selection_chars, sizeof(current_selection_chars), "***************^^^^********");
|
||||
break;
|
||||
case RTC_EDIT_SEC:
|
||||
snprintf( current_selection_chars, sizeof(current_selection_chars), "******************^^^^*****");
|
||||
break;
|
||||
}
|
||||
component_messagebox_draw(
|
||||
"|YYYY|MM|DD|HH|MM|SS| DOW\n%s\n%s\n", full_dt, current_selection_chars);
|
||||
}
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
if (menu->actions.back && !is_editing_mode) {
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
else if (menu->actions.enter && !is_editing_mode) {
|
||||
rtc_tm = *gmtime(&menu->current_time);
|
||||
is_editing_mode = true;
|
||||
}
|
||||
|
||||
if (is_editing_mode) {
|
||||
if (menu->actions.go_left) {
|
||||
if ( editing_field_type <= RTC_EDIT_YEAR ) { editing_field_type = RTC_EDIT_SEC; }
|
||||
else { editing_field_type = editing_field_type - 1; }
|
||||
}
|
||||
else if (menu->actions.go_right) {
|
||||
if ( editing_field_type >= RTC_EDIT_SEC ) { editing_field_type = RTC_EDIT_YEAR; }
|
||||
else { editing_field_type = editing_field_type + 1; }
|
||||
}
|
||||
else if (menu->actions.go_up) {
|
||||
adjust_rtc_time( &rtc_tm, +1 );
|
||||
}
|
||||
else if (menu->actions.go_down) {
|
||||
adjust_rtc_time( &rtc_tm, -1 );
|
||||
}
|
||||
else if (menu->actions.options) { // R button = save
|
||||
if(rtc_is_writable()) {
|
||||
// FIXME: settimeofday is not available in libdragon yet.
|
||||
// struct timeval new_time = { .tv_sec = mktime(&rtc_tm) };
|
||||
// int res = settimeofday(&new_time, NULL);
|
||||
|
||||
rtc_time_t rtc_time = rtc_time_from_tm(&rtc_tm);
|
||||
int res = rtc_set(&rtc_time);
|
||||
if (res != 1) {
|
||||
menu_show_error(menu, "Failed to set RTC time");
|
||||
}
|
||||
}
|
||||
else {
|
||||
menu_show_error(menu, "RTC is not writable");
|
||||
}
|
||||
is_editing_mode = false;
|
||||
}
|
||||
else if (menu->actions.back) { // cancel
|
||||
is_editing_mode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,31 +174,46 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"ADJUST REAL TIME CLOCK\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"To set the date and time, please use the PC terminal\n"
|
||||
"application and set via USB or a game that uses it.\n\n"
|
||||
"Current date & time: %s\n",
|
||||
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n"
|
||||
"To set the RTC date and time, Press A.\n"
|
||||
"You can also use the PC terminal application via USB,\n"
|
||||
"or even an N64 game with RTC support.\n"
|
||||
"\n"
|
||||
"Current date & time: %s\n"
|
||||
"\n",
|
||||
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown"
|
||||
);
|
||||
|
||||
component_main_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
|
||||
if (!is_editing_mode) {
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n" // "A: Save\n"
|
||||
"A: Change\n"
|
||||
"B: Back"
|
||||
);
|
||||
}
|
||||
else {
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_RIGHT, VALIGN_TOP,
|
||||
"Up/Down: Adjust Field\n"
|
||||
"Left/Right: Switch Field"
|
||||
);
|
||||
component_actions_bar_text_draw(
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"R: Save\n"
|
||||
"B: Back"
|
||||
);
|
||||
}
|
||||
|
||||
if (is_editing_mode) {
|
||||
component_editdatetime_draw(rtc_tm, editing_field_type);
|
||||
}
|
||||
|
||||
rdpq_detach_show();
|
||||
}
|
||||
|
||||
|
||||
void view_rtc_init (menu_t *menu) {
|
||||
// Nothing to initialize (yet)
|
||||
is_editing_mode = false;
|
||||
editing_field_type = RTC_EDIT_YEAR;
|
||||
}
|
||||
|
||||
void view_rtc_display (menu_t *menu, surface_t *display) {
|
||||
|
@ -12,36 +12,36 @@ static const char *format_switch (bool state) {
|
||||
}
|
||||
|
||||
static void set_pal60_type (menu_t *menu, void *arg) {
|
||||
menu->settings.pal60_enabled = (bool) (arg);
|
||||
menu->settings.pal60_enabled = (bool)(uintptr_t)(arg);
|
||||
settings_save(&menu->settings);
|
||||
}
|
||||
|
||||
static void set_protected_entries_type (menu_t *menu, void *arg) {
|
||||
menu->settings.show_protected_entries = (bool) (arg);
|
||||
menu->settings.show_protected_entries = (bool)(uintptr_t)(arg);
|
||||
settings_save(&menu->settings);
|
||||
|
||||
menu->browser.reload = true;
|
||||
}
|
||||
|
||||
static void set_use_saves_folder_type (menu_t *menu, void *arg) {
|
||||
menu->settings.use_saves_folder = (bool) (arg);
|
||||
menu->settings.use_saves_folder = (bool)(uintptr_t)(arg);
|
||||
settings_save(&menu->settings);
|
||||
}
|
||||
|
||||
static void set_sound_enabled_type (menu_t *menu, void *arg) {
|
||||
menu->settings.sound_enabled = (bool) (arg);
|
||||
menu->settings.sound_enabled = (bool)(uintptr_t)(arg);
|
||||
sound_use_sfx(menu->settings.sound_enabled);
|
||||
settings_save(&menu->settings);
|
||||
}
|
||||
|
||||
#ifdef BETA_SETTINGS
|
||||
static void set_bgm_enabled_type (menu_t *menu, void *arg) {
|
||||
menu->settings.bgm_enabled = (bool) (arg);
|
||||
menu->settings.bgm_enabled = (bool)(uintptr_t)(arg);
|
||||
settings_save(&menu->settings);
|
||||
}
|
||||
|
||||
static void set_rumble_enabled_type (menu_t *menu, void *arg) {
|
||||
menu->settings.rumble_enabled = (bool) (arg);
|
||||
menu->settings.rumble_enabled = (bool)(uintptr_t)(arg);
|
||||
settings_save(&menu->settings);
|
||||
}
|
||||
|
||||
@ -53,39 +53,39 @@ static void set_rumble_enabled_type (menu_t *menu, void *arg) {
|
||||
|
||||
|
||||
static component_context_menu_t set_pal60_type_context_menu = { .list = {
|
||||
{.text = "On", .action = set_pal60_type, .arg = (void *) (true) },
|
||||
{.text = "On", .action = set_pal60_type, .arg = (void *)(uintptr_t)(true) },
|
||||
{.text = "Off", .action = set_pal60_type, .arg = (void *) (false) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
static component_context_menu_t set_protected_entries_type_context_menu = { .list = {
|
||||
{.text = "On", .action = set_protected_entries_type, .arg = (void *) (true) },
|
||||
{.text = "Off", .action = set_protected_entries_type, .arg = (void *) (false) },
|
||||
{.text = "On", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(true) },
|
||||
{.text = "Off", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(false) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
static component_context_menu_t set_sound_enabled_type_context_menu = { .list = {
|
||||
{.text = "On", .action = set_sound_enabled_type, .arg = (void *) (true) },
|
||||
{.text = "Off", .action = set_sound_enabled_type, .arg = (void *) (false) },
|
||||
{.text = "On", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(true) },
|
||||
{.text = "Off", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(false) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
static component_context_menu_t set_use_saves_folder_type_context_menu = { .list = {
|
||||
{.text = "On", .action = set_use_saves_folder_type, .arg = (void *) (true) },
|
||||
{.text = "Off", .action = set_use_saves_folder_type, .arg = (void *) (false) },
|
||||
{.text = "On", .action = set_use_saves_folder_type, .arg = (void *)(uintptr_t)(true) },
|
||||
{.text = "Off", .action = set_use_saves_folder_type, .arg = (void *)(uintptr_t)(false) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
#ifdef BETA_SETTINGS
|
||||
static component_context_menu_t set_bgm_enabled_type_context_menu = { .list = {
|
||||
{.text = "On", .action = set_bgm_enabled_type, .arg = (void *) (true) },
|
||||
{.text = "Off", .action = set_bgm_enabled_type, .arg = (void *) (false) },
|
||||
{.text = "On", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(true) },
|
||||
{.text = "Off", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(false) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
|
||||
static component_context_menu_t set_rumble_enabled_type_context_menu = { .list = {
|
||||
{.text = "On", .action = set_rumble_enabled_type, .arg = (void *) (true) },
|
||||
{.text = "Off", .action = set_rumble_enabled_type, .arg = (void *) (false) },
|
||||
{.text = "On", .action = set_rumble_enabled_type, .arg = (void *)(uintptr_t)(true) },
|
||||
{.text = "Off", .action = set_rumble_enabled_type, .arg = (void *)(uintptr_t)(false) },
|
||||
COMPONENT_CONTEXT_MENU_LIST_END,
|
||||
}};
|
||||
#endif
|
||||
@ -114,8 +114,8 @@ static void process (menu_t *menu) {
|
||||
component_context_menu_show(&options_context_menu);
|
||||
sound_play_effect(SFX_SETTING);
|
||||
} else if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,6 +136,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
ALIGN_LEFT, VALIGN_TOP,
|
||||
"\n\n"
|
||||
" Default Directory : %s\n\n"
|
||||
" Autoload ROM : %s\n"
|
||||
"To change the following menu settings, press 'A':\n"
|
||||
"* PAL60 Mode : %s\n"
|
||||
" Show Hidden Files : %s\n"
|
||||
@ -145,9 +146,11 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
" Background Music : %s\n"
|
||||
" Rumble Feedback : %s\n"
|
||||
#endif
|
||||
"Note: Certain settings have the following caveats:\n\n"
|
||||
"* Requires a flashcart reboot.\n",
|
||||
"\n\n"
|
||||
"Note: Certain settings have the following caveats:\n"
|
||||
"* Requires rebooting the N64 Console.\n",
|
||||
menu->settings.default_directory,
|
||||
format_switch(menu->settings.rom_autoload_enabled),
|
||||
format_switch(menu->settings.pal60_enabled),
|
||||
format_switch(menu->settings.show_protected_entries),
|
||||
format_switch(menu->settings.use_saves_folder),
|
||||
|
@ -9,6 +9,27 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
|
||||
|
||||
void view_startup_init (menu_t *menu) {
|
||||
// FIXME: rather than use a controller button, would it be better to use the cart button?
|
||||
JOYPAD_PORT_FOREACH (port) {
|
||||
joypad_poll();
|
||||
joypad_buttons_t b_held = joypad_get_buttons_held(port);
|
||||
|
||||
if (menu->settings.rom_autoload_enabled && b_held.start) {
|
||||
menu->settings.rom_autoload_enabled = false;
|
||||
menu->settings.rom_autoload_path = "";
|
||||
menu->settings.rom_autoload_filename = "";
|
||||
settings_save(&menu->settings);
|
||||
}
|
||||
}
|
||||
if (menu->settings.rom_autoload_enabled) {
|
||||
menu->browser.directory = path_init(menu->storage_prefix, menu->settings.rom_autoload_path);
|
||||
menu->load.rom_path = path_clone_push(menu->browser.directory, menu->settings.rom_autoload_filename);
|
||||
menu->boot_pending.rom_file = true;
|
||||
menu->next_mode = MENU_MODE_LOAD_ROM;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
|
||||
|
@ -28,8 +28,8 @@ static void process (menu_t *menu) {
|
||||
}
|
||||
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ static void draw (menu_t *menu, surface_t *d) {
|
||||
"Joypad 2 is %sconnected %s\n"
|
||||
"Joypad 3 is %sconnected %s\n"
|
||||
"Joypad 4 is %sconnected %s\n",
|
||||
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n",
|
||||
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown",
|
||||
is_memory_expanded() ? "" : "not ",
|
||||
(joypad[0]) ? "" : "not ", format_accessory(0),
|
||||
(joypad[1]) ? "" : "not ", format_accessory(1),
|
||||
|
@ -55,8 +55,8 @@ static void perform_vertical_scroll (int lines) {
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
if (menu->actions.back) {
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
sound_play_effect(SFX_EXIT);
|
||||
menu->next_mode = MENU_MODE_BROWSER;
|
||||
} else if (text) {
|
||||
if (menu->actions.go_up) {
|
||||
perform_vertical_scroll(menu->actions.go_fast ? -10 : -1);
|
||||
|
Loading…
Reference in New Issue
Block a user