mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2025-01-12 18:09:16 +01:00
Merge branch 'main' into cpak-management
This commit is contained in:
commit
7e9bd189cc
20
.clang-format
Normal file
20
.clang-format
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
|
AlignAfterOpenBracket: BlockIndent
|
||||||
|
AlignEscapedNewlines: DontAlign
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
AllowShortEnumsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||||
|
AlwaysBreakBeforeMultilineStrings: true
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
ColumnLimit: 120
|
||||||
|
IndentWidth: 4
|
||||||
|
SpaceBeforeParens: Custom
|
||||||
|
SpaceBeforeParensOptions:
|
||||||
|
AfterControlStatements: true
|
||||||
|
AfterFunctionDeclarationName: true
|
||||||
|
AfterFunctionDefinitionName: true
|
||||||
|
UseTab: Never
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -77,7 +77,7 @@ jobs:
|
|||||||
# continue-on-error: true
|
# continue-on-error: true
|
||||||
|
|
||||||
- name: Upload rolling release
|
- name: Upload rolling release
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
uses: softprops/action-gh-release@v2
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
name: Rolling release
|
name: Rolling release
|
||||||
|
9
Makefile
9
Makefile
@ -88,7 +88,7 @@ FILESYSTEM = \
|
|||||||
|
|
||||||
$(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second
|
$(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second
|
||||||
$(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second
|
$(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second
|
||||||
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 2026-2026 --ellipsis 2026,1
|
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-1FF -r 2026-2026 --ellipsis 2026,1
|
||||||
|
|
||||||
$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))
|
$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))
|
||||||
|
|
||||||
@ -140,6 +140,13 @@ clean:
|
|||||||
@rm -rf ./$(BUILD_DIR) ./$(OUTPUT_DIR)
|
@rm -rf ./$(BUILD_DIR) ./$(OUTPUT_DIR)
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
|
|
||||||
|
format:
|
||||||
|
@find ./$(SOURCE_DIR) \
|
||||||
|
-path \./$(SOURCE_DIR)/libs -prune \
|
||||||
|
-o -iname *.c -print \
|
||||||
|
-o -iname *.h -print \
|
||||||
|
| xargs clang-format -i
|
||||||
|
|
||||||
run: $(OUTPUT_DIR)/$(PROJECT_NAME).n64
|
run: $(OUTPUT_DIR)/$(PROJECT_NAME).n64
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
./localdeploy.bat
|
./localdeploy.bat
|
||||||
|
@ -82,11 +82,8 @@ If required, you can manually adjust the file on the SD card using your computer
|
|||||||
- Download the latest `sc64menu.n64` file from the releases page, then put it in the root directory of your SD card.
|
- Download the latest `sc64menu.n64` file from the releases page, then put it in the root directory of your SD card.
|
||||||
|
|
||||||
##### 64DD disk support
|
##### 64DD disk support
|
||||||
For the ability to load and run 64DD disk images, you need to add the folder `/menu/64ddipl` on the SD card.
|
For the ability to load and run 64DD disk images, you need to place required 64DD IPL dumps in the `/menu/64ddipl` folder on the SD card.
|
||||||
Download and add the relevant ipl files and rename them before adding them to the folder:
|
For more details follow [this guide on the 64dd.org website](https://64dd.org/tutorial_sc64.html).
|
||||||
- `NDDE0.n64` the US Prototype IPL can be downloaded from [here](https://64dd.org/dumps/64DD_IPL_US_MJR.n64)
|
|
||||||
- `NDXJ0.n64` the JPN Development IPL can be downloaded from [here](https://64dd.org/dumps/64DD_IPL_DEV_H4G.n64)
|
|
||||||
- `NDDJ2.n64` the JPN Retail IPL can be downloaded from [here](https://64dd.org/dumps/N64DD_IPLROM_(J).zip)
|
|
||||||
|
|
||||||
Note: to load an expansion disk (e.g. F-Zero X) browse to the N64 ROM and load it (but not start it) and then browse to the DD expansion file and press the `R` button.
|
Note: to load an expansion disk (e.g. F-Zero X) browse to the N64 ROM and load it (but not start it) and then browse to the DD expansion file and press the `R` button.
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ char *flashcart_convert_error_message (flashcart_err_t err) {
|
|||||||
case FLASHCART_OK: return "No error";
|
case FLASHCART_OK: return "No error";
|
||||||
case FLASHCART_ERR_OUTDATED: return "Outdated flashcart firmware";
|
case FLASHCART_ERR_OUTDATED: return "Outdated flashcart firmware";
|
||||||
case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization";
|
case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization";
|
||||||
|
case FLASHCART_ERR_BBFS: return "Error during iQue NAND initialization";
|
||||||
case FLASHCART_ERR_ARGS: return "Invalid argument passed to flashcart function";
|
case FLASHCART_ERR_ARGS: return "Invalid argument passed to flashcart function";
|
||||||
case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart";
|
case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart";
|
||||||
case FLASHCART_ERR_INT: return "Internal flashcart error";
|
case FLASHCART_ERR_INT: return "Internal flashcart error";
|
||||||
@ -93,6 +94,9 @@ flashcart_err_t flashcart_init (const char **storage_prefix) {
|
|||||||
if (sys_bbplayer()) {
|
if (sys_bbplayer()) {
|
||||||
// TODO: Add iQue callbacks
|
// TODO: Add iQue callbacks
|
||||||
*storage_prefix = "bbfs:/";
|
*storage_prefix = "bbfs:/";
|
||||||
|
if (bbfs_init()) {
|
||||||
|
return FLASHCART_ERR_BBFS;
|
||||||
|
}
|
||||||
return FLASHCART_OK;
|
return FLASHCART_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ typedef enum {
|
|||||||
FLASHCART_OK,
|
FLASHCART_OK,
|
||||||
FLASHCART_ERR_OUTDATED,
|
FLASHCART_ERR_OUTDATED,
|
||||||
FLASHCART_ERR_SD_CARD,
|
FLASHCART_ERR_SD_CARD,
|
||||||
|
FLASHCART_ERR_BBFS,
|
||||||
FLASHCART_ERR_ARGS,
|
FLASHCART_ERR_ARGS,
|
||||||
FLASHCART_ERR_LOAD,
|
FLASHCART_ERR_LOAD,
|
||||||
FLASHCART_ERR_INT,
|
FLASHCART_ERR_INT,
|
||||||
|
@ -19,10 +19,9 @@
|
|||||||
|
|
||||||
|
|
||||||
static bool is_64dd_connected (void) {
|
static bool is_64dd_connected (void) {
|
||||||
return (
|
bool is_64dd_io_present = ((io_read(0x05000540) & 0x0000FFFF) == 0x0000);
|
||||||
((io_read(0x05000540) & 0x0000FFFF) == 0x0000) ||
|
bool is_64dd_ipl_present = (io_read(0x06001010) == 0x2129FFF8);
|
||||||
(io_read(0x06001010) == 0x2129FFF8)
|
return (is_64dd_io_present || is_64dd_ipl_present);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool create_saves_subdirectory (path_t *path) {
|
static bool create_saves_subdirectory (path_t *path) {
|
||||||
|
@ -24,7 +24,7 @@ void component_progressbar_draw (int x0, int y0, int x1, int y1, float progress)
|
|||||||
void component_seekbar_draw (float progress);
|
void component_seekbar_draw (float progress);
|
||||||
void component_loader_draw (float position);
|
void component_loader_draw (float position);
|
||||||
void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items);
|
void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items);
|
||||||
void component_file_list_scrollbar_draw (int position, int items, int visible_items);
|
void component_list_scrollbar_draw (int position, int items, int visible_items);
|
||||||
void component_dialog_draw (int width, int height);
|
void component_dialog_draw (int width, int height);
|
||||||
void component_messagebox_draw (char *fmt, ...);
|
void component_messagebox_draw (char *fmt, ...);
|
||||||
void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
|
void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);
|
||||||
|
@ -80,12 +80,12 @@ void component_scrollbar_draw (int x, int y, int width, int height, int position
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void component_file_list_scrollbar_draw (int position, int items, int visible_items) {
|
void component_list_scrollbar_draw (int position, int items, int visible_items) {
|
||||||
component_scrollbar_draw(
|
component_scrollbar_draw(
|
||||||
FILE_LIST_SCROLLBAR_X,
|
LIST_SCROLLBAR_X,
|
||||||
FILE_LIST_SCROLLBAR_Y,
|
LIST_SCROLLBAR_Y,
|
||||||
FILE_LIST_SCROLLBAR_WIDTH,
|
LIST_SCROLLBAR_WIDTH,
|
||||||
FILE_LIST_SCROLLBAR_HEIGHT,
|
LIST_SCROLLBAR_HEIGHT,
|
||||||
position,
|
position,
|
||||||
items,
|
items,
|
||||||
visible_items
|
visible_items
|
||||||
|
@ -77,19 +77,19 @@
|
|||||||
#define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24)
|
#define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24)
|
||||||
|
|
||||||
/** @brief The scroll bar width. */
|
/** @brief The scroll bar width. */
|
||||||
#define FILE_LIST_SCROLLBAR_WIDTH (12)
|
#define LIST_SCROLLBAR_WIDTH (12)
|
||||||
/** @brief The scroll bar height. */
|
/** @brief The scroll bar height. */
|
||||||
#define FILE_LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT)
|
#define LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT)
|
||||||
/** @brief The scroll bar position on the X axis. */
|
/** @brief The scroll bar position on the X axis. */
|
||||||
#define FILE_LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - FILE_LIST_SCROLLBAR_WIDTH)
|
#define LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - LIST_SCROLLBAR_WIDTH)
|
||||||
/** @brief The scroll bar position on the Y axis. */
|
/** @brief The scroll bar position on the Y axis. */
|
||||||
#define FILE_LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0)
|
#define LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0)
|
||||||
|
|
||||||
/** @brief The maximum amount of file list entries. */
|
/** @brief The maximum amount of file list entries. */
|
||||||
#define FILE_LIST_ENTRIES (20)
|
#define LIST_ENTRIES (20)
|
||||||
/** @brief The maximum width available for a file list entry. */
|
/** @brief The maximum width available for a file list entry. */
|
||||||
#define FILE_LIST_MAX_WIDTH (480)
|
#define FILE_LIST_MAX_WIDTH (480)
|
||||||
#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - FILE_LIST_SCROLLBAR_WIDTH)
|
#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - LIST_SCROLLBAR_WIDTH)
|
||||||
#define FILE_LIST_HIGHLIGHT_X (VISIBLE_AREA_X0)
|
#define FILE_LIST_HIGHLIGHT_X (VISIBLE_AREA_X0)
|
||||||
|
|
||||||
/** @brief The default background colour. */
|
/** @brief The default background colour. */
|
||||||
|
@ -28,14 +28,14 @@ static int format_file_size (char *buffer, int64_t size) {
|
|||||||
void component_file_list_draw (entry_t *list, int entries, int selected) {
|
void component_file_list_draw (entry_t *list, int entries, int selected) {
|
||||||
int starting_position = 0;
|
int starting_position = 0;
|
||||||
|
|
||||||
if (entries > FILE_LIST_ENTRIES && selected >= (FILE_LIST_ENTRIES / 2)) {
|
if (entries > LIST_ENTRIES && selected >= (LIST_ENTRIES / 2)) {
|
||||||
starting_position = selected - (FILE_LIST_ENTRIES / 2);
|
starting_position = selected - (LIST_ENTRIES / 2);
|
||||||
if (starting_position >= entries - FILE_LIST_ENTRIES) {
|
if (starting_position >= entries - LIST_ENTRIES) {
|
||||||
starting_position = entries - FILE_LIST_ENTRIES;
|
starting_position = entries - LIST_ENTRIES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component_file_list_scrollbar_draw(selected, entries, FILE_LIST_ENTRIES);
|
component_list_scrollbar_draw(selected, entries, LIST_ENTRIES);
|
||||||
|
|
||||||
if (entries == 0) {
|
if (entries == 0) {
|
||||||
component_main_text_draw(
|
component_main_text_draw(
|
||||||
@ -47,10 +47,10 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
|||||||
rdpq_paragraph_t *file_list_layout;
|
rdpq_paragraph_t *file_list_layout;
|
||||||
rdpq_paragraph_t *layout;
|
rdpq_paragraph_t *layout;
|
||||||
|
|
||||||
size_t name_lengths[FILE_LIST_ENTRIES];
|
size_t name_lengths[LIST_ENTRIES];
|
||||||
size_t total_length = 1;
|
size_t total_length = 1;
|
||||||
|
|
||||||
for (int i = 0; i < FILE_LIST_ENTRIES; i++) {
|
for (int i = 0; i < LIST_ENTRIES; i++) {
|
||||||
int entry_index = starting_position + i;
|
int entry_index = starting_position + i;
|
||||||
|
|
||||||
if (entry_index >= entries) {
|
if (entry_index >= entries) {
|
||||||
@ -76,7 +76,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
|||||||
file_list_layout
|
file_list_layout
|
||||||
);
|
);
|
||||||
|
|
||||||
for (int i = 0; i < FILE_LIST_ENTRIES; i++) {
|
for (int i = 0; i < LIST_ENTRIES; i++) {
|
||||||
int entry_index = starting_position + i;
|
int entry_index = starting_position + i;
|
||||||
|
|
||||||
entry_t *entry = &list[entry_index];
|
entry_t *entry = &list[entry_index];
|
||||||
@ -134,7 +134,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
|||||||
|
|
||||||
rdpq_paragraph_builder_begin(
|
rdpq_paragraph_builder_begin(
|
||||||
&(rdpq_textparms_t) {
|
&(rdpq_textparms_t) {
|
||||||
.width = VISIBLE_AREA_WIDTH - FILE_LIST_SCROLLBAR_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
|
.width = VISIBLE_AREA_WIDTH - LIST_SCROLLBAR_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
|
||||||
.height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2),
|
.height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2),
|
||||||
.align = ALIGN_RIGHT,
|
.align = ALIGN_RIGHT,
|
||||||
.wrap = WRAP_ELLIPSES,
|
.wrap = WRAP_ELLIPSES,
|
||||||
@ -152,7 +152,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
|
|||||||
rdpq_paragraph_builder_span(file_size, format_file_size(file_size, entry->size));
|
rdpq_paragraph_builder_span(file_size, format_file_size(file_size, entry->size));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((i + 1) == (starting_position + FILE_LIST_ENTRIES)) {
|
if ((i + 1) == (starting_position + LIST_ENTRIES)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,16 +141,32 @@ typedef struct {
|
|||||||
|
|
||||||
|
|
||||||
// List shamelessly stolen from https://github.com/ares-emulator/ares/blob/master/mia/medium/nintendo-64.cpp
|
// List shamelessly stolen from https://github.com/ares-emulator/ares/blob/master/mia/medium/nintendo-64.cpp
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
static const match_t database[] = {
|
static const match_t database[] = {
|
||||||
MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED)
|
MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED)
|
||||||
|
|
||||||
MATCH_CHECK_CODE(0x000000004CBC3B56, SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // DMTJ 64DD cartridge conversion
|
MATCH_CHECK_CODE(0x000000004CBC3B56, SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // DMTJ 64DD cartridge conversion
|
||||||
|
|
||||||
MATCH_CHECK_CODE(0x0DD4ABABB5A2A91E, SAVE_TYPE_EEPROM_16K, FEAT_EXP_PAK_REQUIRED), // DK Retail kiosk demo
|
MATCH_CHECK_CODE(0x0DD4ABABB5A2A91E, SAVE_TYPE_EEPROM_16K, FEAT_EXP_PAK_REQUIRED), // DK Retail kiosk demo
|
||||||
MATCH_CHECK_CODE(0xEB85EBC9596682AF, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Doubutsu Banchou
|
MATCH_CHECK_CODE(0xEB85EBC9596682AF, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Doubutsu Banchou
|
||||||
|
MATCH_CHECK_CODE(0x9A746EBF2802EA99, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Toon panic
|
||||||
|
MATCH_CHECK_CODE(0x21548CA921548CA9, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Mini racers
|
||||||
|
MATCH_CHECK_CODE(0xBC9B2CC34ED04DA5, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Starcraft 64 [Prototype 2000]
|
||||||
|
MATCH_CHECK_CODE(0x5D40ED2C10D6ABCF, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064
|
||||||
|
MATCH_CHECK_CODE(0x7280E03F497689BA, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064 [ENG patch]
|
||||||
|
|
||||||
|
MATCH_CHECK_CODE(0xCDB8B4D08832352D, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [USA CRACK]
|
||||||
|
MATCH_CHECK_CODE(0xB66E0F7C2709C22F, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [PAL CRACK]
|
||||||
|
|
||||||
|
MATCH_CHECK_CODE(0xCE84793D27ECC1AD, SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [USA CRACK]
|
||||||
|
MATCH_CHECK_CODE(0x1F95CAAA047FC22A, SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [PAL CRACK]
|
||||||
|
|
||||||
|
MATCH_CHECK_CODE(0xE3FF09DFCAE4B0ED, SAVE_TYPE_SRAM, FEAT_RPAK), // Banjo tooie [USA CRACK]
|
||||||
|
|
||||||
MATCH_ID_REGION_VERSION("NK4J", 0, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
MATCH_ID_REGION_VERSION("NK4J", 0, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||||
MATCH_ID_REGION_VERSION("NK4J", 1, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
MATCH_ID_REGION_VERSION("NK4J", 1, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||||
MATCH_ID("NK4", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
MATCH_ID("NK4", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)]
|
||||||
|
|
||||||
MATCH_ID_REGION_VERSION("NSMJ", 3, SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Super Mario 64 Shindou Edition
|
MATCH_ID_REGION_VERSION("NSMJ", 3, SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Super Mario 64 Shindou Edition
|
||||||
MATCH_ID("NSM", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Super Mario 64
|
MATCH_ID("NSM", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Super Mario 64
|
||||||
@ -175,6 +191,7 @@ static const match_t database[] = {
|
|||||||
MATCH_ID_REGION("NWTJ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Wetrix
|
MATCH_ID_REGION("NWTJ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Wetrix
|
||||||
MATCH_ID("NWT", SAVE_TYPE_NONE, FEAT_CPAK), // Wetrix
|
MATCH_ID("NWT", SAVE_TYPE_NONE, FEAT_CPAK), // Wetrix
|
||||||
|
|
||||||
|
// EEPROM 4K
|
||||||
MATCH_ID("CLB", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_64DD_ENHANCED), // Mario Party (NTSC)
|
MATCH_ID("CLB", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_64DD_ENHANCED), // Mario Party (NTSC)
|
||||||
MATCH_ID("NAB", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Air Boarder 64
|
MATCH_ID("NAB", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Air Boarder 64
|
||||||
MATCH_ID("NAD", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Worms Armageddon (U)
|
MATCH_ID("NAD", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Worms Armageddon (U)
|
||||||
@ -269,6 +286,7 @@ static const match_t database[] = {
|
|||||||
MATCH_ID("NXO", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Cruis'n Exotica
|
MATCH_ID("NXO", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Cruis'n Exotica
|
||||||
MATCH_ID("NYK", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Yakouchuu II: Satsujin Kouro
|
MATCH_ID("NYK", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Yakouchuu II: Satsujin Kouro
|
||||||
|
|
||||||
|
// EEPROM 16K
|
||||||
MATCH_ID("N3D", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Doraemon 3: Nobita no Machi SOS!
|
MATCH_ID("N3D", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Doraemon 3: Nobita no Machi SOS!
|
||||||
MATCH_ID("NB7", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
|
MATCH_ID("NB7", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
|
||||||
MATCH_ID("NCW", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Cruis'n World
|
MATCH_ID("NCW", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Cruis'n World
|
||||||
@ -293,12 +311,14 @@ static const match_t database[] = {
|
|||||||
MATCH_ID("NUB", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_TPAK), // PD Ultraman Battle Collection 64
|
MATCH_ID("NUB", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_TPAK), // PD Ultraman Battle Collection 64
|
||||||
MATCH_ID("NYS", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Yoshi's Story
|
MATCH_ID("NYS", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Yoshi's Story
|
||||||
|
|
||||||
|
// SRAM 256K
|
||||||
MATCH_ID("CFZ", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // F-Zero X (J)
|
MATCH_ID("CFZ", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // F-Zero X (J)
|
||||||
MATCH_ID("CPS", SAVE_TYPE_SRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium (J)
|
MATCH_ID("CPS", SAVE_TYPE_SRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium (J)
|
||||||
MATCH_ID("CZL", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // Legend of Zelda: Ocarina of Time [Zelda no Densetsu - Toki no Ocarina (J)]
|
MATCH_ID("CZL", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // Legend of Zelda: Ocarina of Time [Zelda no Densetsu - Toki no Ocarina (J)]
|
||||||
MATCH_ID("NA2", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 2
|
MATCH_ID("NA2", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 2
|
||||||
MATCH_ID("NAL", SAVE_TYPE_SRAM, FEAT_RPAK), // Super Smash Bros. [Nintendo All-Star! Dairantou Smash Brothers (J)]
|
MATCH_ID("NAL", SAVE_TYPE_SRAM, FEAT_RPAK), // Super Smash Bros. [Nintendo All-Star! Dairantou Smash Brothers (J)]
|
||||||
MATCH_ID("NB5", SAVE_TYPE_SRAM, FEAT_RPAK), // Biohazard 2 (J)
|
MATCH_ID("NB5", SAVE_TYPE_SRAM, FEAT_RPAK), // Biohazard 2 (J)
|
||||||
|
MATCH_ID("NDD", SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // 64DD Conversion Rom
|
||||||
MATCH_ID("NFZ", SAVE_TYPE_SRAM, FEAT_RPAK), // F-Zero X (U + E)
|
MATCH_ID("NFZ", SAVE_TYPE_SRAM, FEAT_RPAK), // F-Zero X (U + E)
|
||||||
MATCH_ID("NG6", SAVE_TYPE_SRAM, FEAT_RPAK), // Ganmare Goemon: Dero Dero Douchuu Obake Tenkomori
|
MATCH_ID("NG6", SAVE_TYPE_SRAM, FEAT_RPAK), // Ganmare Goemon: Dero Dero Douchuu Obake Tenkomori
|
||||||
MATCH_ID("NGP", SAVE_TYPE_SRAM, FEAT_CPAK), // Goemon: Mononoke Sugoroku
|
MATCH_ID("NGP", SAVE_TYPE_SRAM, FEAT_CPAK), // Goemon: Mononoke Sugoroku
|
||||||
@ -331,11 +351,14 @@ static const match_t database[] = {
|
|||||||
MATCH_ID("NYW", SAVE_TYPE_SRAM, FEAT_NONE), // Harvest Moon 64
|
MATCH_ID("NYW", SAVE_TYPE_SRAM, FEAT_NONE), // Harvest Moon 64
|
||||||
MATCH_ID("NZL", SAVE_TYPE_SRAM, FEAT_RPAK), // Legend of Zelda: Ocarina of Time (E)
|
MATCH_ID("NZL", SAVE_TYPE_SRAM, FEAT_RPAK), // Legend of Zelda: Ocarina of Time (E)
|
||||||
|
|
||||||
|
// SRAM 768K
|
||||||
MATCH_ID("CDZ", SAVE_TYPE_SRAM_BANKED, FEAT_RPAK | FEAT_64DD_ENHANCED), // Dezaemon 3D
|
MATCH_ID("CDZ", SAVE_TYPE_SRAM_BANKED, FEAT_RPAK | FEAT_64DD_ENHANCED), // Dezaemon 3D
|
||||||
|
|
||||||
|
// FLASHRAM
|
||||||
MATCH_ID("CP2", SAVE_TYPE_FLASHRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium 2 (J)
|
MATCH_ID("CP2", SAVE_TYPE_FLASHRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium 2 (J)
|
||||||
MATCH_ID("NAF", SAVE_TYPE_FLASHRAM, FEAT_CPAK | FEAT_RTC), // Doubutsu no Mori
|
MATCH_ID("NAF", SAVE_TYPE_FLASHRAM, FEAT_CPAK | FEAT_RTC), // Doubutsu no Mori
|
||||||
MATCH_ID("NCC", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Command & Conquer
|
MATCH_ID("NCC", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Command & Conquer
|
||||||
|
MATCH_ID("NCV", SAVE_TYPE_FLASHRAM, FEAT_NONE), // Cubivore (Translation)
|
||||||
MATCH_ID("NCK", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // NBA Courtside 2 featuring Kobe Bryant
|
MATCH_ID("NCK", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // NBA Courtside 2 featuring Kobe Bryant
|
||||||
MATCH_ID("NDA", SAVE_TYPE_FLASHRAM, FEAT_CPAK), // Derby Stallion 64
|
MATCH_ID("NDA", SAVE_TYPE_FLASHRAM, FEAT_CPAK), // Derby Stallion 64
|
||||||
MATCH_ID("NDP", SAVE_TYPE_FLASHRAM, FEAT_EXP_PAK_REQUIRED), // Dinosaur Planet (Unlicensed)
|
MATCH_ID("NDP", SAVE_TYPE_FLASHRAM, FEAT_EXP_PAK_REQUIRED), // Dinosaur Planet (Unlicensed)
|
||||||
@ -354,6 +377,7 @@ static const match_t database[] = {
|
|||||||
|
|
||||||
MATCH_ID("NP3", SAVE_TYPE_FLASHRAM_PKST2, FEAT_TPAK), // Pokemon Stadium 2 [Pocket Monsters Stadium - Kin Gin (J)]
|
MATCH_ID("NP3", SAVE_TYPE_FLASHRAM_PKST2, FEAT_TPAK), // Pokemon Stadium 2 [Pocket Monsters Stadium - Kin Gin (J)]
|
||||||
|
|
||||||
|
// CONTROLLER PAK / NONE
|
||||||
MATCH_ID("N22", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - Round 2
|
MATCH_ID("N22", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - Round 2
|
||||||
MATCH_ID("N2M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2002
|
MATCH_ID("N2M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2002
|
||||||
MATCH_ID("N32", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes 2
|
MATCH_ID("N32", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes 2
|
||||||
@ -576,6 +600,7 @@ static const match_t database[] = {
|
|||||||
|
|
||||||
MATCH_END,
|
MATCH_END,
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
|
||||||
static void fix_rom_header_endianness (rom_header_t *rom_header, rom_info_t *rom_info) {
|
static void fix_rom_header_endianness (rom_header_t *rom_header, rom_info_t *rom_info) {
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
|
static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
|
||||||
static const char *disk_extensions[] = { "ndd", NULL };
|
static const char *disk_extensions[] = { "ndd", NULL };
|
||||||
static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", NULL };
|
static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", NULL };
|
||||||
static const char *save_extensions[] = { "sav", NULL }; // TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts.
|
// TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts.
|
||||||
|
static const char *save_extensions[] = { "sav", NULL };
|
||||||
//static const char *joypad_accessory_save_extensions[] = { "cpak", "mpk", NULL };
|
//static const char *joypad_accessory_save_extensions[] = { "cpak", "mpk", NULL };
|
||||||
static const char *image_extensions[] = { "png", NULL };
|
static const char *image_extensions[] = { "png", NULL };
|
||||||
static const char *text_extensions[] = { "txt", "ini", "yml", "yaml", NULL };
|
static const char *text_extensions[] = { "txt", "ini", "yml", "yaml", NULL };
|
||||||
@ -25,7 +26,7 @@ static const char *hidden_paths[] = {
|
|||||||
"/OS64P.v64",
|
"/OS64P.v64",
|
||||||
"/sc64menu.n64",
|
"/sc64menu.n64",
|
||||||
"/System Volume Information",
|
"/System Volume Information",
|
||||||
NULL
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -230,6 +231,7 @@ static void delete_entry (menu_t *menu, void *arg) {
|
|||||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||||
|
|
||||||
if (remove(path_get(path))) {
|
if (remove(path_get(path))) {
|
||||||
|
menu->browser.valid = false;
|
||||||
if (menu->browser.entry->type == ENTRY_TYPE_DIR) {
|
if (menu->browser.entry->type == ENTRY_TYPE_DIR) {
|
||||||
menu_show_error(menu, "Couldn't delete directory\nDirectory might not be empty");
|
menu_show_error(menu, "Couldn't delete directory\nDirectory might not be empty");
|
||||||
} else {
|
} else {
|
||||||
|
@ -50,5 +50,4 @@ void view_error_display (menu_t *menu, surface_t *display) {
|
|||||||
void menu_show_error (menu_t *menu, char *error_message) {
|
void menu_show_error (menu_t *menu, char *error_message) {
|
||||||
menu->next_mode = MENU_MODE_ERROR;
|
menu->next_mode = MENU_MODE_ERROR;
|
||||||
menu->error_message = error_message;
|
menu->error_message = error_message;
|
||||||
menu->browser.valid = false;
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,66 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "utils/fs.h"
|
#include "../components/constants.h"
|
||||||
|
#include "../fonts.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
#include "views.h"
|
#include "views.h"
|
||||||
|
|
||||||
|
|
||||||
static char *text;
|
#define MAX_FILE_SIZE KiB(128)
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FILE *f;
|
||||||
|
char *contents;
|
||||||
|
size_t length;
|
||||||
|
int lines;
|
||||||
|
int current_line;
|
||||||
|
int offset;
|
||||||
|
bool vertical_scroll_possible;
|
||||||
|
} text_file_t;
|
||||||
|
|
||||||
|
static text_file_t *text;
|
||||||
|
|
||||||
|
|
||||||
|
static void perform_vertical_scroll (int lines) {
|
||||||
|
if (!text->vertical_scroll_possible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int direction = (lines < 0) ? -1 : 1;
|
||||||
|
int next_offset = text->offset;
|
||||||
|
|
||||||
|
for (int i = 0; i < abs(lines); i++) {
|
||||||
|
while (true) {
|
||||||
|
next_offset += direction;
|
||||||
|
if (next_offset <= 0) {
|
||||||
|
text->current_line = 0;
|
||||||
|
text->offset = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (next_offset > text->length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (text->contents[next_offset - 1] == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text->current_line += direction;
|
||||||
|
text->offset = next_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void process (menu_t *menu) {
|
static void process (menu_t *menu) {
|
||||||
if (menu->actions.back) {
|
if (menu->actions.back) {
|
||||||
menu->next_mode = MENU_MODE_BROWSER;
|
menu->next_mode = MENU_MODE_BROWSER;
|
||||||
|
} else if (text) {
|
||||||
|
if (menu->actions.go_up) {
|
||||||
|
perform_vertical_scroll(menu->actions.go_fast ? -10 : -1);
|
||||||
|
} else if (menu->actions.go_down) {
|
||||||
|
perform_vertical_scroll(menu->actions.go_fast ? 10 : 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,20 +71,19 @@ static void draw (menu_t *menu, surface_t *d) {
|
|||||||
|
|
||||||
component_layout_draw();
|
component_layout_draw();
|
||||||
|
|
||||||
if (text) {
|
component_main_text_draw(
|
||||||
component_main_text_draw(
|
ALIGN_LEFT, VALIGN_TOP,
|
||||||
ALIGN_LEFT, VALIGN_TOP,
|
"%s\n",
|
||||||
"%s\n",
|
text->contents + text->offset
|
||||||
text
|
);
|
||||||
);
|
|
||||||
} else {
|
component_list_scrollbar_draw(text->current_line, text->lines, LIST_ENTRIES);
|
||||||
component_messagebox_draw("Text file is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
component_actions_bar_text_draw(
|
component_actions_bar_text_draw(
|
||||||
ALIGN_LEFT, VALIGN_TOP,
|
ALIGN_LEFT, VALIGN_TOP,
|
||||||
"\n"
|
"^%02XUp / Down: Scroll^00\n"
|
||||||
"B: Back"
|
"B: Back",
|
||||||
|
text->vertical_scroll_possible ? STL_DEFAULT : STL_GRAY
|
||||||
);
|
);
|
||||||
|
|
||||||
rdpq_detach_show();
|
rdpq_detach_show();
|
||||||
@ -43,6 +91,12 @@ static void draw (menu_t *menu, surface_t *d) {
|
|||||||
|
|
||||||
static void deinit (void) {
|
static void deinit (void) {
|
||||||
if (text) {
|
if (text) {
|
||||||
|
if (text->f) {
|
||||||
|
fclose(text->f);
|
||||||
|
}
|
||||||
|
if (text->contents) {
|
||||||
|
free(text->contents);
|
||||||
|
}
|
||||||
free(text);
|
free(text);
|
||||||
text = NULL;
|
text = NULL;
|
||||||
}
|
}
|
||||||
@ -50,45 +104,61 @@ static void deinit (void) {
|
|||||||
|
|
||||||
|
|
||||||
void view_text_viewer_init (menu_t *menu) {
|
void view_text_viewer_init (menu_t *menu) {
|
||||||
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
if ((text = calloc(1, sizeof(text_file_t))) == NULL) {
|
||||||
|
return menu_show_error(menu, "Couldn't allocate memory for the text file");
|
||||||
FILE *f;
|
|
||||||
|
|
||||||
if ((f = fopen(path_get(path), "r")) == NULL) {
|
|
||||||
path_free(path);
|
|
||||||
menu_show_error(menu, "Couldn't open text file");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
|
||||||
|
text->f = fopen(path_get(path), "r");
|
||||||
path_free(path);
|
path_free(path);
|
||||||
|
|
||||||
|
if (text->f == NULL) {
|
||||||
|
deinit();
|
||||||
|
return menu_show_error(menu, "Couldn't open text file");
|
||||||
|
}
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstat(fileno(f), &st)) {
|
if (fstat(fileno(text->f), &st)) {
|
||||||
fclose(f);
|
deinit();
|
||||||
menu_show_error(menu, "Couldn't get text file size");
|
return menu_show_error(menu, "Couldn't get text file size");
|
||||||
return;
|
}
|
||||||
|
text->length = st.st_size;
|
||||||
|
|
||||||
|
if (text->length <= 0) {
|
||||||
|
deinit();
|
||||||
|
return menu_show_error(menu, "Text file is empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement proper text file viewer with both vertical and horizontal scrolling
|
if (text->length > MAX_FILE_SIZE) {
|
||||||
size_t size = MIN(st.st_size, 1024);
|
deinit();
|
||||||
|
return menu_show_error(menu, "Text file is too big to be displayed");
|
||||||
|
}
|
||||||
|
|
||||||
if (size) {
|
if ((text->contents = malloc((text->length + 1) * sizeof(char))) == NULL) {
|
||||||
if ((text = calloc(sizeof(char), size)) == NULL) {
|
deinit();
|
||||||
fclose(f);
|
return menu_show_error(menu, "Couldn't allocate memory for the text file contents");
|
||||||
menu_show_error(menu, "Couldn't allocate memory for the text file contents");
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fread(text, size, 1, f) != 1) {
|
if (fread(text->contents, text->length, 1, text->f) != 1) {
|
||||||
fclose(f);
|
deinit();
|
||||||
menu_show_error(menu, "Couldn't read text file contents");
|
return menu_show_error(menu, "Couldn't read text file contents");
|
||||||
return;
|
}
|
||||||
|
text->contents[text->length] = '\0';
|
||||||
|
|
||||||
|
if (fclose(text->f)) {
|
||||||
|
deinit();
|
||||||
|
return menu_show_error(menu, "Couldn't close text file");
|
||||||
|
}
|
||||||
|
text->f = NULL;
|
||||||
|
|
||||||
|
text->lines = 1;
|
||||||
|
for (size_t i = 0; i < text->length; i++) {
|
||||||
|
if (text->contents[i] == '\n') {
|
||||||
|
text->lines += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fclose(f)) {
|
text->vertical_scroll_possible = (text->lines > LIST_ENTRIES);
|
||||||
menu_show_error(menu, "Couldn't close text file");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_text_viewer_display (menu_t *menu, surface_t *display) {
|
void view_text_viewer_display (menu_t *menu, surface_t *display) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user