Merge branch 'main' into cpak-management

This commit is contained in:
Robin Jones 2024-04-29 18:50:06 +01:00 committed by GitHub
commit 7e9bd189cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 201 additions and 77 deletions

20
.clang-format Normal file
View 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

View File

@ -77,7 +77,7 @@ jobs:
# continue-on-error: true
- name: Upload rolling release
uses: softprops/action-gh-release@v0.1.15
uses: softprops/action-gh-release@v2
if: github.ref == 'refs/heads/main'
with:
name: Rolling release

View File

@ -88,7 +88,7 @@ FILESYSTEM = \
$(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second
$(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 2026-2026 --ellipsis 2026,1
$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-1FF -r 2026-2026 --ellipsis 2026,1
$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))
@ -140,6 +140,13 @@ clean:
@rm -rf ./$(BUILD_DIR) ./$(OUTPUT_DIR)
.PHONY: clean
format:
@find ./$(SOURCE_DIR) \
-path \./$(SOURCE_DIR)/libs -prune \
-o -iname *.c -print \
-o -iname *.h -print \
| xargs clang-format -i
run: $(OUTPUT_DIR)/$(PROJECT_NAME).n64
ifeq ($(OS),Windows_NT)
./localdeploy.bat

View File

@ -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.
##### 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.
Download and add the relevant ipl files and rename them before adding them to the folder:
- `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)
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.
For more details follow [this guide on the 64dd.org website](https://64dd.org/tutorial_sc64.html).
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.

View File

@ -79,6 +79,7 @@ char *flashcart_convert_error_message (flashcart_err_t err) {
case FLASHCART_OK: return "No error";
case FLASHCART_ERR_OUTDATED: return "Outdated flashcart firmware";
case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization";
case FLASHCART_ERR_BBFS: return "Error during iQue NAND initialization";
case FLASHCART_ERR_ARGS: return "Invalid argument passed to flashcart function";
case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart";
case FLASHCART_ERR_INT: return "Internal flashcart error";
@ -93,6 +94,9 @@ flashcart_err_t flashcart_init (const char **storage_prefix) {
if (sys_bbplayer()) {
// TODO: Add iQue callbacks
*storage_prefix = "bbfs:/";
if (bbfs_init()) {
return FLASHCART_ERR_BBFS;
}
return FLASHCART_OK;
}

View File

@ -17,6 +17,7 @@ typedef enum {
FLASHCART_OK,
FLASHCART_ERR_OUTDATED,
FLASHCART_ERR_SD_CARD,
FLASHCART_ERR_BBFS,
FLASHCART_ERR_ARGS,
FLASHCART_ERR_LOAD,
FLASHCART_ERR_INT,

View File

@ -19,10 +19,9 @@
static bool is_64dd_connected (void) {
return (
((io_read(0x05000540) & 0x0000FFFF) == 0x0000) ||
(io_read(0x06001010) == 0x2129FFF8)
);
bool is_64dd_io_present = ((io_read(0x05000540) & 0x0000FFFF) == 0x0000);
bool is_64dd_ipl_present = (io_read(0x06001010) == 0x2129FFF8);
return (is_64dd_io_present || is_64dd_ipl_present);
}
static bool create_saves_subdirectory (path_t *path) {

View File

@ -24,7 +24,7 @@ void component_progressbar_draw (int x0, int y0, int x1, int y1, float progress)
void component_seekbar_draw (float progress);
void component_loader_draw (float position);
void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items);
void component_file_list_scrollbar_draw (int position, int items, int visible_items);
void component_list_scrollbar_draw (int position, int items, int visible_items);
void component_dialog_draw (int width, int height);
void component_messagebox_draw (char *fmt, ...);
void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...);

View File

@ -80,12 +80,12 @@ void component_scrollbar_draw (int x, int y, int width, int height, int position
}
}
void component_file_list_scrollbar_draw (int position, int items, int visible_items) {
void component_list_scrollbar_draw (int position, int items, int visible_items) {
component_scrollbar_draw(
FILE_LIST_SCROLLBAR_X,
FILE_LIST_SCROLLBAR_Y,
FILE_LIST_SCROLLBAR_WIDTH,
FILE_LIST_SCROLLBAR_HEIGHT,
LIST_SCROLLBAR_X,
LIST_SCROLLBAR_Y,
LIST_SCROLLBAR_WIDTH,
LIST_SCROLLBAR_HEIGHT,
position,
items,
visible_items

View File

@ -77,19 +77,19 @@
#define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24)
/** @brief The scroll bar width. */
#define FILE_LIST_SCROLLBAR_WIDTH (12)
#define LIST_SCROLLBAR_WIDTH (12)
/** @brief The scroll bar height. */
#define FILE_LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT)
#define LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT)
/** @brief The scroll bar position on the X axis. */
#define FILE_LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - FILE_LIST_SCROLLBAR_WIDTH)
#define LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - LIST_SCROLLBAR_WIDTH)
/** @brief The scroll bar position on the Y axis. */
#define FILE_LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0)
#define LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0)
/** @brief The maximum amount of file list entries. */
#define FILE_LIST_ENTRIES (20)
#define LIST_ENTRIES (20)
/** @brief The maximum width available for a file list entry. */
#define FILE_LIST_MAX_WIDTH (480)
#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - FILE_LIST_SCROLLBAR_WIDTH)
#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - LIST_SCROLLBAR_WIDTH)
#define FILE_LIST_HIGHLIGHT_X (VISIBLE_AREA_X0)
/** @brief The default background colour. */

View File

@ -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) {
int starting_position = 0;
if (entries > FILE_LIST_ENTRIES && selected >= (FILE_LIST_ENTRIES / 2)) {
starting_position = selected - (FILE_LIST_ENTRIES / 2);
if (starting_position >= entries - FILE_LIST_ENTRIES) {
starting_position = entries - FILE_LIST_ENTRIES;
if (entries > LIST_ENTRIES && selected >= (LIST_ENTRIES / 2)) {
starting_position = selected - (LIST_ENTRIES / 2);
if (starting_position >= entries - LIST_ENTRIES) {
starting_position = entries - LIST_ENTRIES;
}
}
component_file_list_scrollbar_draw(selected, entries, FILE_LIST_ENTRIES);
component_list_scrollbar_draw(selected, entries, LIST_ENTRIES);
if (entries == 0) {
component_main_text_draw(
@ -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 *layout;
size_t name_lengths[FILE_LIST_ENTRIES];
size_t name_lengths[LIST_ENTRIES];
size_t total_length = 1;
for (int i = 0; i < FILE_LIST_ENTRIES; i++) {
for (int i = 0; i < LIST_ENTRIES; i++) {
int entry_index = starting_position + i;
if (entry_index >= entries) {
@ -76,7 +76,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
file_list_layout
);
for (int i = 0; i < FILE_LIST_ENTRIES; i++) {
for (int i = 0; i < LIST_ENTRIES; i++) {
int entry_index = starting_position + i;
entry_t *entry = &list[entry_index];
@ -134,7 +134,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) {
rdpq_paragraph_builder_begin(
&(rdpq_textparms_t) {
.width = VISIBLE_AREA_WIDTH - FILE_LIST_SCROLLBAR_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
.width = VISIBLE_AREA_WIDTH - LIST_SCROLLBAR_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2),
.height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2),
.align = ALIGN_RIGHT,
.wrap = WRAP_ELLIPSES,
@ -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));
}
if ((i + 1) == (starting_position + FILE_LIST_ENTRIES)) {
if ((i + 1) == (starting_position + LIST_ENTRIES)) {
break;
}

View File

@ -141,16 +141,32 @@ typedef struct {
// List shamelessly stolen from https://github.com/ares-emulator/ares/blob/master/mia/medium/nintendo-64.cpp
// clang-format off
static const match_t database[] = {
MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED)
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(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", 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("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("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("NAB", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Air Boarder 64
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("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("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
@ -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("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("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("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("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("NG6", SAVE_TYPE_SRAM, FEAT_RPAK), // Ganmare Goemon: Dero Dero Douchuu Obake Tenkomori
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("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
// FLASHRAM
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("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("NDA", SAVE_TYPE_FLASHRAM, FEAT_CPAK), // Derby Stallion 64
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)]
// CONTROLLER PAK / NONE
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("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,
};
// clang-format on
static void fix_rom_header_endianness (rom_header_t *rom_header, rom_info_t *rom_info) {

View File

@ -11,7 +11,8 @@
static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL };
static const char *disk_extensions[] = { "ndd", NULL };
static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", NULL };
static const char *save_extensions[] = { "sav", NULL }; // TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts.
// TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts.
static const char *save_extensions[] = { "sav", NULL };
//static const char *joypad_accessory_save_extensions[] = { "cpak", "mpk", NULL };
static const char *image_extensions[] = { "png", NULL };
static const char *text_extensions[] = { "txt", "ini", "yml", "yaml", NULL };
@ -25,7 +26,7 @@ static const char *hidden_paths[] = {
"/OS64P.v64",
"/sc64menu.n64",
"/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);
if (remove(path_get(path))) {
menu->browser.valid = false;
if (menu->browser.entry->type == ENTRY_TYPE_DIR) {
menu_show_error(menu, "Couldn't delete directory\nDirectory might not be empty");
} else {

View File

@ -50,5 +50,4 @@ void view_error_display (menu_t *menu, surface_t *display) {
void menu_show_error (menu_t *menu, char *error_message) {
menu->next_mode = MENU_MODE_ERROR;
menu->error_message = error_message;
menu->browser.valid = false;
}

View File

@ -1,17 +1,66 @@
#include <stdio.h>
#include <sys/stat.h>
#include "utils/fs.h"
#include "../components/constants.h"
#include "../fonts.h"
#include "utils/utils.h"
#include "views.h"
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) {
if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
} else if (text) {
if (menu->actions.go_up) {
perform_vertical_scroll(menu->actions.go_fast ? -10 : -1);
} else if (menu->actions.go_down) {
perform_vertical_scroll(menu->actions.go_fast ? 10 : 1);
}
}
}
@ -22,20 +71,19 @@ static void draw (menu_t *menu, surface_t *d) {
component_layout_draw();
if (text) {
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"%s\n",
text
);
} else {
component_messagebox_draw("Text file is empty");
}
component_main_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"%s\n",
text->contents + text->offset
);
component_list_scrollbar_draw(text->current_line, text->lines, LIST_ENTRIES);
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"\n"
"B: Back"
"^%02XUp / Down: Scroll^00\n"
"B: Back",
text->vertical_scroll_possible ? STL_DEFAULT : STL_GRAY
);
rdpq_detach_show();
@ -43,6 +91,12 @@ static void draw (menu_t *menu, surface_t *d) {
static void deinit (void) {
if (text) {
if (text->f) {
fclose(text->f);
}
if (text->contents) {
free(text->contents);
}
free(text);
text = NULL;
}
@ -50,45 +104,61 @@ static void deinit (void) {
void view_text_viewer_init (menu_t *menu) {
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
FILE *f;
if ((f = fopen(path_get(path), "r")) == NULL) {
path_free(path);
menu_show_error(menu, "Couldn't open text file");
return;
if ((text = calloc(1, sizeof(text_file_t))) == NULL) {
return menu_show_error(menu, "Couldn't allocate memory for the text file");
}
path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name);
text->f = fopen(path_get(path), "r");
path_free(path);
if (text->f == NULL) {
deinit();
return menu_show_error(menu, "Couldn't open text file");
}
struct stat st;
if (fstat(fileno(f), &st)) {
fclose(f);
menu_show_error(menu, "Couldn't get text file size");
return;
if (fstat(fileno(text->f), &st)) {
deinit();
return menu_show_error(menu, "Couldn't get text file size");
}
text->length = st.st_size;
if (text->length <= 0) {
deinit();
return menu_show_error(menu, "Text file is empty");
}
// TODO: Implement proper text file viewer with both vertical and horizontal scrolling
size_t size = MIN(st.st_size, 1024);
if (text->length > MAX_FILE_SIZE) {
deinit();
return menu_show_error(menu, "Text file is too big to be displayed");
}
if (size) {
if ((text = calloc(sizeof(char), size)) == NULL) {
fclose(f);
menu_show_error(menu, "Couldn't allocate memory for the text file contents");
return;
}
if ((text->contents = malloc((text->length + 1) * sizeof(char))) == NULL) {
deinit();
return menu_show_error(menu, "Couldn't allocate memory for the text file contents");
}
if (fread(text, size, 1, f) != 1) {
fclose(f);
menu_show_error(menu, "Couldn't read text file contents");
return;
if (fread(text->contents, text->length, 1, text->f) != 1) {
deinit();
return menu_show_error(menu, "Couldn't read text file contents");
}
text->contents[text->length] = '\0';
if (fclose(text->f)) {
deinit();
return menu_show_error(menu, "Couldn't close text file");
}
text->f = NULL;
text->lines = 1;
for (size_t i = 0; i < text->length; i++) {
if (text->contents[i] == '\n') {
text->lines += 1;
}
}
if (fclose(f)) {
menu_show_error(menu, "Couldn't close text file");
}
text->vertical_scroll_possible = (text->lines > LIST_ENTRIES);
}
void view_text_viewer_display (menu_t *menu, surface_t *display) {