mirror of
https://github.com/ApacheThunder/GodMode9Nrio.git
synced 2025-06-19 03:05:34 -04:00

* Screenshot feature removed to free up more ram. * SCFG register changes on startup moved to dedicated function run from ITCM memory. This should hopefully reduce instances where program hangs at startup. Bootstarp is still provided incase some people still have that problem.
916 lines
29 KiB
C++
916 lines
29 KiB
C++
/*-----------------------------------------------------------------
|
|
Copyright (C) 2005 - 2013
|
|
Michael "Chishm" Chisholm
|
|
Dave "WinterMute" Murphy
|
|
Claudio "sverx"
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
------------------------------------------------------------------*/
|
|
|
|
#include "file_browse.h"
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <dirent.h>
|
|
|
|
#include <nds.h>
|
|
#include <nds/arm9/dldi.h>
|
|
#include <fat.h>
|
|
|
|
#include "main.h"
|
|
#include "config.h"
|
|
#include "date.h"
|
|
#include "fileOperations.h"
|
|
#include "driveMenu.h"
|
|
#include "driveOperations.h"
|
|
#include "font.h"
|
|
#include "hexEditor.h"
|
|
#include "my_sd.h"
|
|
#include "keyboard.h"
|
|
#include "ndsInfo.h"
|
|
#include "startMenu.h"
|
|
#include "nitrofs.h"
|
|
#include "inifile.h"
|
|
#include "nds_loader_arm9.h"
|
|
#include "language.h"
|
|
|
|
#define ENTRIES_START_ROW 1
|
|
#define OPTIONS_ENTRIES_START_ROW 2
|
|
#define ENTRY_PAGE_LENGTH 10
|
|
|
|
bool extension(const std::string_view filename, const std::vector<std::string_view> &extensions) {
|
|
for(const std::string_view &ext : extensions) {
|
|
if(filename.length() >= ext.length() && strcasecmp(filename.substr(filename.length() - ext.length()).data(), ext.data()) == 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool dirEntryPredicate (const DirEntry& lhs, const DirEntry& rhs) {
|
|
|
|
if (!lhs.isDirectory && rhs.isDirectory) {
|
|
return false;
|
|
}
|
|
if (lhs.isDirectory && !rhs.isDirectory) {
|
|
return true;
|
|
}
|
|
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
|
|
}
|
|
|
|
bool getDirectoryContents(std::vector<DirEntry>& dirContents) {
|
|
dirContents.clear();
|
|
|
|
DIR *pdir = opendir (".");
|
|
|
|
if (pdir == nullptr) {
|
|
font->print(firstCol, 0, true, STR_UNABLE_TO_OPEN_DIRECTORY, alignStart);
|
|
font->update(true);
|
|
return false;
|
|
} else {
|
|
while (true) {
|
|
dirent *pent = readdir(pdir);
|
|
if (pent == nullptr)
|
|
break;
|
|
|
|
if (strcmp(pent->d_name, ".") == 0 || strcmp(pent->d_name, "..") == 0)
|
|
continue;
|
|
|
|
bool isApp = false;
|
|
if (extension(pent->d_name, {"nds", "argv", "dsi", "ids", "app", "srl"})) {
|
|
isApp = (currentDrive == Drive::sdCard && sdMounted) || (currentDrive == Drive::flashcard && flashcardMounted);
|
|
} else if (extension(pent->d_name, {"firm"})) {
|
|
isApp = (is3DS && sdMounted);
|
|
}
|
|
|
|
dirContents.emplace_back(pent->d_name, pent->d_type == DT_DIR ? 0 : -1, pent->d_type == DT_DIR, isApp);
|
|
}
|
|
closedir(pdir);
|
|
}
|
|
|
|
std::sort(dirContents.begin(), dirContents.end(), dirEntryPredicate);
|
|
|
|
// Add ".." to top of list
|
|
dirContents.insert(dirContents.begin(), {"..", 0, true, false});
|
|
|
|
return true;
|
|
}
|
|
|
|
void showDirectoryContents(std::vector<DirEntry> &dirContents, int fileOffset, int startRow, const char *curdir) {
|
|
font->clear(true);
|
|
|
|
// Top bar
|
|
font->printf(firstCol, 0, true, alignStart, Palette::blackGreen, "%*c", 256 / font->width(), ' ');
|
|
|
|
std::string time = RetTime();
|
|
|
|
// Print the path
|
|
if(font->calcWidth(curdir) > SCREEN_COLS - 6)
|
|
font->print(rtl ? -1 : (-1 - time.size()), 0, true, curdir, Alignment::right, Palette::blackGreen, true);
|
|
else
|
|
font->print(firstCol, 0, true, curdir, alignStart, Palette::blackGreen);
|
|
|
|
// Print time
|
|
font->print(lastCol, 0, true, time, alignEnd, Palette::blackGreen);
|
|
|
|
// Print directory listing
|
|
for (int i = 0; i < ((int)dirContents.size() - startRow) && i < ENTRIES_PER_SCREEN; i++) {
|
|
DirEntry *entry = &dirContents[i + startRow];
|
|
|
|
Palette pal;
|
|
if ((fileOffset - startRow) == i) {
|
|
pal = Palette::white;
|
|
} else if (entry->selected) {
|
|
pal = Palette::yellow;
|
|
} else if (entry->isDirectory) {
|
|
pal = Palette::blue;
|
|
} else {
|
|
pal = Palette::gray;
|
|
}
|
|
|
|
// Load size if not loaded yet
|
|
if(entry->size == -1)
|
|
entry->size = getFileSize(entry->name.c_str());
|
|
|
|
int nameSize = 0;
|
|
for(int i = 0; i < SCREEN_COLS; nameSize++) {
|
|
if((entry->name[nameSize] & 0xC0) != 0x80)
|
|
i++;
|
|
}
|
|
|
|
font->print(firstCol, i + 1, true, entry->name.substr(0, nameSize), alignStart, pal);
|
|
if (entry->name == "..") {
|
|
font->print(lastCol, i + 1, true, "(..)", alignEnd, pal);
|
|
} else if (entry->isDirectory) {
|
|
font->print(lastCol, i + 1, true, " " + STR_DIR, alignEnd, pal);
|
|
} else {
|
|
font->printf(lastCol, i + 1, true, alignEnd, pal, " (%s)", getBytes(entry->size).c_str());
|
|
}
|
|
}
|
|
|
|
font->update(true);
|
|
}
|
|
|
|
FileOperation fileBrowse_A(DirEntry* entry, const char *curdir) {
|
|
if(config->screenSwap())
|
|
lcdMainOnTop();
|
|
|
|
int pressed = 0;
|
|
std::vector<FileOperation> operations;
|
|
int optionOffset = 0;
|
|
std::string fullPath = curdir + entry->name;
|
|
int y = font->calcHeight(fullPath) + 1;
|
|
|
|
if (!entry->isDirectory) {
|
|
if (entry->isApp)operations.push_back(FileOperation::bootFile);
|
|
|
|
if(extension(entry->name, {"nds", "dsi", "ids", "app", "srl"})) {
|
|
if(currentDrive != Drive::nitroFS)
|
|
operations.push_back(FileOperation::mountNitroFS);
|
|
operations.push_back(FileOperation::ndsInfo);
|
|
operations.push_back(FileOperation::trimNds);
|
|
}
|
|
if(currentDrive != Drive::fatImg && extension(entry->name, {"img", "sd", "sav", "pub", "pu1", "pu2", "pu3", "pu4", "pu5", "pu6", "pu7", "pu8", "pu9", "prv", "pr1", "pr2", "pr3", "pr4", "pr5", "pr6", "pr7", "pr8", "pr9", "0000"})) {
|
|
operations.push_back(FileOperation::mountImg);
|
|
}
|
|
if(extension(entry->name, {"frf"})) {
|
|
operations.push_back(FileOperation::loadFont);
|
|
}
|
|
|
|
operations.push_back(FileOperation::hexEdit);
|
|
|
|
// The bios SHA1 functions are only available on the DSi
|
|
// https://problemkaputt.de/gbatek.htm#biossha1functionsdsionly
|
|
if (bios9iEnabled)operations.push_back(FileOperation::calculateSHA1);
|
|
}
|
|
|
|
operations.push_back(FileOperation::showInfo);
|
|
|
|
if (sdMounted && (strcmp(curdir, "sd:/gm9i/out/") != 0))operations.push_back(FileOperation::copySdOut);
|
|
|
|
if (flashcardMounted && (strcmp(curdir, "fat:/gm9i/out/") != 0))operations.push_back(FileOperation::copyFatOut);
|
|
|
|
while (true) {
|
|
font->clear(false);
|
|
|
|
font->print(firstCol, 0, false, fullPath, alignStart);
|
|
|
|
int optionsCol = rtl ? -4 : 3;
|
|
int row = y;
|
|
for(FileOperation operation : operations) {
|
|
switch(operation) {
|
|
case FileOperation::bootFile:
|
|
font->print(optionsCol, row++, false, extension(entry->name, {"firm"}) ? STR_BOOT_FILE : STR_BOOT_FILE_DIRECT, alignStart);
|
|
break;
|
|
case FileOperation::mountNitroFS:
|
|
font->print(optionsCol, row++, false, STR_MOUNT_NITROFS, alignStart);
|
|
break;
|
|
case FileOperation::ndsInfo:
|
|
font->print(optionsCol, row++, false, STR_SHOW_NDS_INFO, alignStart);
|
|
break;
|
|
case FileOperation::trimNds:
|
|
font->print(optionsCol, row++, false, STR_TRIM_NDS, alignStart);
|
|
break;
|
|
case FileOperation::mountImg:
|
|
font->print(optionsCol, row++, false, STR_MOUNT_FAT_IMG, alignStart);
|
|
break;
|
|
case FileOperation::hexEdit:
|
|
font->print(optionsCol, row++, false, STR_OPEN_HEX, alignStart);
|
|
break;
|
|
case FileOperation::showInfo:
|
|
font->print(optionsCol, row++, false, entry->isDirectory ? STR_SHOW_DIRECTORY_INFO : STR_SHOW_FILE_INFO, alignStart);
|
|
break;
|
|
case FileOperation::copySdOut:
|
|
font->print(optionsCol, row++, false, STR_COPY_SD_OUT, alignStart);
|
|
break;
|
|
case FileOperation::copyFatOut:
|
|
font->print(optionsCol, row++, false, STR_COPY_FAT_OUT, alignStart);
|
|
break;
|
|
case FileOperation::calculateSHA1:
|
|
font->print(optionsCol, row++, false, STR_CALC_SHA1, alignStart);
|
|
break;
|
|
case FileOperation::loadFont:
|
|
font->print(optionsCol, row++, false, STR_LOAD_FONT, alignStart);
|
|
break;
|
|
case FileOperation::none:
|
|
row++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
font->print(optionsCol, ++row, false, STR_A_SELECT_B_CANCEL, alignStart);
|
|
|
|
// Show cursor
|
|
font->print(firstCol, y + optionOffset, false, rtl ? "<-" : "->", alignStart);
|
|
|
|
font->update(false);
|
|
|
|
// Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do
|
|
do {
|
|
scanKeys();
|
|
pressed = keysDownRepeat();
|
|
swiWaitForVBlank();
|
|
|
|
if(driveRemoved(currentDrive)) {
|
|
if(config->screenSwap())
|
|
screenSwapped ? lcdMainOnBottom() : lcdMainOnTop();
|
|
|
|
return FileOperation::none;
|
|
}
|
|
} while (!(pressed & (KEY_UP| KEY_DOWN | KEY_A | KEY_B | KEY_L)));
|
|
|
|
if (pressed & KEY_UP) optionOffset -= 1;
|
|
if (pressed & KEY_DOWN) optionOffset += 1;
|
|
|
|
if (optionOffset < 0) // Wrap around to bottom of list
|
|
optionOffset = operations.size() - 1;
|
|
|
|
if (optionOffset >= (int)operations.size()) // Wrap around to top of list
|
|
optionOffset = 0;
|
|
|
|
if (pressed & KEY_A) {
|
|
switch(operations[optionOffset]) {
|
|
case FileOperation::bootFile: {
|
|
font->clear(false);
|
|
applaunch = true;
|
|
font->print(optionsCol, optionOffset + y, false, STR_LOADING, alignStart);
|
|
font->update(false);
|
|
break;
|
|
} case FileOperation::copySdOut: {
|
|
if (access("sd:/gm9i", F_OK) != 0) {
|
|
font->print(optionsCol, optionOffset + y, false, STR_CREATING_DIRECTORY, alignStart);
|
|
font->update(false);
|
|
mkdir("sd:/gm9i", 0777);
|
|
}
|
|
if (access("sd:/gm9i/out", F_OK) != 0) {
|
|
font->print(optionsCol, optionOffset + y, false, STR_CREATING_DIRECTORY, alignStart);
|
|
font->update(false);
|
|
mkdir("sd:/gm9i/out", 0777);
|
|
}
|
|
char destPath[256];
|
|
snprintf(destPath, sizeof(destPath), "sd:/gm9i/out/%s", entry->name.c_str());
|
|
font->print(optionsCol, optionOffset + y, false, STR_COPYING, alignStart);
|
|
font->update(false);
|
|
remove(destPath);
|
|
char sourcePath[PATH_MAX];
|
|
snprintf(sourcePath, sizeof(sourcePath), "%s%s", curdir, entry->name.c_str());
|
|
fcopy(sourcePath, destPath);
|
|
chdir(curdir); // For after copying a folder
|
|
break;
|
|
} case FileOperation::copyFatOut: {
|
|
if (access("fat:/gm9i", F_OK) != 0) {
|
|
font->print(optionsCol, optionOffset + y, false, STR_CREATING_DIRECTORY, alignStart);
|
|
font->update(false);
|
|
mkdir("fat:/gm9i", 0777);
|
|
}
|
|
if (access("fat:/gm9i/out", F_OK) != 0) {
|
|
font->print(optionsCol, optionOffset + y, false, STR_CREATING_DIRECTORY, alignStart);
|
|
font->update(false);
|
|
mkdir("fat:/gm9i/out", 0777);
|
|
}
|
|
char destPath[256];
|
|
snprintf(destPath, sizeof(destPath), "fat:/gm9i/out/%s", entry->name.c_str());
|
|
font->print(optionsCol, (optionOffset + y), false, STR_COPYING, alignStart);
|
|
font->update(false);
|
|
remove(destPath);
|
|
char sourcePath[PATH_MAX];
|
|
snprintf(sourcePath, sizeof(sourcePath), "%s%s", curdir, entry->name.c_str());
|
|
fcopy(sourcePath, destPath);
|
|
chdir(curdir); // For after copying a folder
|
|
break;
|
|
} case FileOperation::mountNitroFS: {
|
|
if(nitroMounted)
|
|
nitroUnmount();
|
|
|
|
ownNitroFSMounted = 2;
|
|
nitroMounted = nitroFSInit(entry->name.c_str());
|
|
if (nitroMounted) {
|
|
chdir("nitro:/");
|
|
nitroCurrentDrive = currentDrive;
|
|
currentDrive = Drive::nitroFS;
|
|
}
|
|
break;
|
|
} case FileOperation::ndsInfo: {
|
|
ndsInfo(entry->name.c_str());
|
|
break;
|
|
} case FileOperation::trimNds: {
|
|
entry->size = trimNds(entry->name.c_str());
|
|
break;
|
|
} case FileOperation::showInfo: {
|
|
changeFileAttribs(entry);
|
|
break;
|
|
} case FileOperation::mountImg: {
|
|
if(imgMounted)
|
|
imgUnmount();
|
|
|
|
imgMounted = imgMount(entry->name.c_str(), !extension(entry->name, {"img", "sd"}));
|
|
if (imgMounted) {
|
|
chdir("img:/");
|
|
imgCurrentDrive = currentDrive;
|
|
currentDrive = Drive::fatImg;
|
|
}
|
|
break;
|
|
} case FileOperation::hexEdit: {
|
|
hexEditor(entry->name.c_str(), currentDrive);
|
|
break;
|
|
} case FileOperation::loadFont: {
|
|
delete font;
|
|
font = new Font(entry->name.c_str());
|
|
|
|
// Reload language to update button characters
|
|
langInit(true);
|
|
break;
|
|
} case FileOperation::calculateSHA1: {
|
|
u8 sha1[20] = {0};
|
|
char filePath[PATH_MAX];
|
|
snprintf(filePath, sizeof(filePath), "%s%s", curdir, entry->name.c_str());
|
|
bool ret = calculateSHA1(filePath, sha1);
|
|
if (!ret)
|
|
break;
|
|
|
|
font->clear(false);
|
|
font->print(firstCol, 0, false, STR_SHA1_HASH_IS, alignStart);
|
|
char sha1Str[41];
|
|
for (int i = 0; i < 20; ++i)
|
|
sniprintf(sha1Str + i * 2, 3, "%02X", sha1[i]);
|
|
font->print(firstCol, 1, false, sha1Str, alignStart);
|
|
font->print(firstCol, font->calcHeight(sha1Str) + 2, false, STR_A_CONTINUE, alignStart);
|
|
font->update(false);
|
|
|
|
// Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do
|
|
int pressed;
|
|
do {
|
|
scanKeys();
|
|
pressed = keysDownRepeat();
|
|
swiWaitForVBlank();
|
|
} while (!(pressed & (KEY_A | KEY_Y | KEY_B | KEY_X)));
|
|
break;
|
|
} case FileOperation::none: {
|
|
break;
|
|
}
|
|
}
|
|
keysDownRepeat(); // prevent unwanted key repeat
|
|
|
|
if(config->screenSwap())
|
|
screenSwapped ? lcdMainOnBottom() : lcdMainOnTop();
|
|
|
|
return operations[optionOffset];
|
|
} else if (pressed & KEY_B) {
|
|
if(config->screenSwap())
|
|
screenSwapped ? lcdMainOnBottom() : lcdMainOnTop();
|
|
|
|
return FileOperation::none;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool fileBrowse_paste(char dest[256]) {
|
|
if(config->screenSwap())
|
|
lcdMainOnTop();
|
|
|
|
int pressed = 0;
|
|
int optionOffset = 0;
|
|
|
|
while (true) {
|
|
font->clear(false);
|
|
|
|
font->print(firstCol, 0, false, STR_PASTE_CLIPBOARD_HERE, alignStart);
|
|
|
|
int optionsCol = rtl ? -4 : 3;
|
|
int row = OPTIONS_ENTRIES_START_ROW, maxCursors = 0;
|
|
font->print(optionsCol, row++, false, STR_COPY_FILES, alignStart);
|
|
for (auto &file : clipboard) {
|
|
if (!driveWritable(file.drive))
|
|
continue;
|
|
maxCursors++;
|
|
font->print(optionsCol, row++, false, STR_MOVE_FILES, alignStart);
|
|
break;
|
|
}
|
|
font->print(optionsCol, ++row, false, STR_A_SELECT_B_CANCEL, alignStart);
|
|
|
|
// Show cursor
|
|
font->print(firstCol, optionOffset + OPTIONS_ENTRIES_START_ROW, false, rtl ? "<-" : "->", alignStart);
|
|
|
|
font->update(false);
|
|
|
|
// Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do
|
|
do {
|
|
scanKeys();
|
|
pressed = keysDownRepeat();
|
|
swiWaitForVBlank();
|
|
} while (!(pressed & (KEY_UP | KEY_DOWN | KEY_A | KEY_B)));
|
|
|
|
if (pressed & KEY_UP) optionOffset -= 1;
|
|
if (pressed & KEY_DOWN) optionOffset += 1;
|
|
|
|
if (optionOffset < 0) optionOffset = maxCursors; // Wrap around to bottom of list
|
|
if (optionOffset > maxCursors) optionOffset = 0; // Wrap around to top of list
|
|
|
|
if (pressed & KEY_A) {
|
|
font->print(optionsCol, optionOffset + OPTIONS_ENTRIES_START_ROW, false, optionOffset ? STR_MOVING : STR_COPYING, alignStart);
|
|
for (auto &file : clipboard) {
|
|
std::string destPath = dest + file.name;
|
|
if (file.path == destPath)
|
|
continue; // If the source and destination for the clipped file is the same skip it
|
|
|
|
if (optionOffset && driveWritable(file.drive)) { // Don't remove if from read-only drive
|
|
if (currentDrive == file.drive) {
|
|
rename(file.path.c_str(), destPath.c_str());
|
|
} else {
|
|
fcopy(file.path.c_str(), destPath.c_str()); // Copy file to destination, since renaming won't work
|
|
remove(file.path.c_str()); // Delete source file after copying
|
|
}
|
|
} else {
|
|
remove(destPath.c_str());
|
|
fcopy(file.path.c_str(), destPath.c_str());
|
|
}
|
|
}
|
|
clipboardUsed = true; // Disable clipboard restore
|
|
clipboardOn = false; // Clear clipboard after copying or moving
|
|
|
|
if(config->screenSwap())
|
|
screenSwapped ? lcdMainOnBottom() : lcdMainOnTop();
|
|
|
|
return true;
|
|
}
|
|
if (pressed & KEY_B) {
|
|
if(config->screenSwap())
|
|
screenSwapped ? lcdMainOnBottom() : lcdMainOnTop();
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void recRemove(const char *path, std::vector<DirEntry> dirContents) {
|
|
if(chdir(path) == 0 && getDirectoryContents(dirContents)) {
|
|
for (int i = 1; i < ((int)dirContents.size()); i++) {
|
|
DirEntry &entry = dirContents[i];
|
|
if (entry.isDirectory)
|
|
recRemove(entry.name.c_str(), dirContents);
|
|
if (!(FAT_getAttr(entry.name.c_str()) & ATTR_READONLY)) {
|
|
remove(entry.name.c_str());
|
|
}
|
|
}
|
|
chdir("..");
|
|
remove(path);
|
|
}
|
|
}
|
|
|
|
void fileBrowse_drawBottomScreen(DirEntry* entry) {
|
|
font->clear(false);
|
|
|
|
int row = -1;
|
|
|
|
if (!isDSiMode() && isRegularDS) {
|
|
font->print(firstCol, row--, false, STR_POWERTEXT_DS, alignStart);
|
|
} else if (is3DS) {
|
|
font->print(firstCol, row--, false, STR_HOMETEXT, alignStart);
|
|
font->print(firstCol, row--, false, STR_POWERTEXT_3DS, alignStart);
|
|
} else {
|
|
font->print(firstCol, row--, false, STR_POWERTEXT, alignStart);
|
|
}
|
|
font->print(firstCol, row--, false, STR_START_START_MENU, alignStart);
|
|
font->print(firstCol, row--, false, clipboardOn ? STR_CLEAR_CLIPBOARD : STR_RESTORE_CLIPBOARD, alignStart);
|
|
font->print(firstCol, row--, false, STR_DIRECTORY_OPTIONS, alignStart);
|
|
if(driveWritable(currentDrive))
|
|
font->print(firstCol, row--, false, clipboardOn ? STR_PASTE_FILES_CREATE_ENTRY : STR_COPY_FILES_CREATE_ENTRY, alignStart);
|
|
else if(!clipboardOn)
|
|
font->print(firstCol, row--, false, STR_COPY_FILE, alignStart);
|
|
font->print(firstCol, row--, false, entry->selected ? STR_DESELECT_FILES : STR_SELECT_FILES, alignStart);
|
|
if(driveWritable(currentDrive))
|
|
font->print(firstCol, row--, false, STR_DELETE_RENAME_FILE, alignStart);
|
|
font->print(firstCol, row--, false, titleName, alignStart);
|
|
|
|
// Load size if not loaded yet
|
|
if(entry->size == -1)
|
|
entry->size = getFileSize(entry->name.c_str());
|
|
|
|
Palette pal = entry->selected ? Palette::yellow : (entry->isDirectory ? Palette::blue : Palette::gray);
|
|
font->print(firstCol, 0, false, entry->name, alignStart, pal);
|
|
if (entry->name != "..") {
|
|
if (entry->isDirectory) {
|
|
font->print(firstCol, font->calcHeight(entry->name), false, STR_DIR, alignStart, pal);
|
|
} else if (entry->size == 1) {
|
|
font->print(firstCol, font->calcHeight(entry->name), false, STR_1_BYTE, alignStart, pal);
|
|
} else {
|
|
font->printf(firstCol, font->calcHeight(entry->name), false, alignStart, pal, STR_N_BYTES.c_str(), entry->size);
|
|
}
|
|
}
|
|
if (clipboardOn) {
|
|
font->print(firstCol, 4, false, STR_CLIPBOARD, alignStart);
|
|
for (size_t i = 0; i < clipboard.size(); ++i) {
|
|
if (i < 4) {
|
|
font->print(firstCol, 5 + i, false, clipboard[i].name, alignStart, clipboard[i].folder ? Palette::blue : Palette::gray);
|
|
} else {
|
|
font->printf(firstCol, 5 + i, false, alignStart, Palette::gray, clipboard.size() - 4 == 1 ? STR_1_MORE_FILE.c_str() : STR_N_MORE_FILES.c_str(), clipboard.size() - 4);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
font->update(false);
|
|
}
|
|
|
|
std::string browseForFile (void) {
|
|
int pressed = 0;
|
|
int held = 0;
|
|
int screenOffset = 0;
|
|
int fileOffset = 0;
|
|
std::vector<DirEntry> dirContents;
|
|
char curdir[PATH_MAX];
|
|
|
|
getDirectoryContents(dirContents);
|
|
|
|
while (true) {
|
|
getcwd(curdir, PATH_MAX);
|
|
|
|
// Ensure the path ends in a slash
|
|
int pathLen = strlen(curdir);
|
|
if(pathLen < PATH_MAX && curdir[pathLen - 1] != '/') {
|
|
curdir[pathLen] = '/';
|
|
curdir[pathLen + 1] = '\0';
|
|
pathLen++;
|
|
}
|
|
|
|
DirEntry* entry = &dirContents[fileOffset];
|
|
|
|
fileBrowse_drawBottomScreen(entry);
|
|
showDirectoryContents(dirContents, fileOffset, screenOffset, curdir);
|
|
|
|
// Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do
|
|
do {
|
|
scanKeys();
|
|
pressed = keysDownRepeat();
|
|
held = keysHeld();
|
|
swiWaitForVBlank();
|
|
|
|
if(driveRemoved(currentDrive)) {
|
|
screenMode = 0;
|
|
return "null";
|
|
}
|
|
} while (!(pressed & ~(KEY_R | KEY_LID)));
|
|
|
|
if (pressed & KEY_UP) {
|
|
fileOffset--;
|
|
if(fileOffset < 0)
|
|
fileOffset = dirContents.size() - 1;
|
|
} else if (pressed & KEY_DOWN) {
|
|
fileOffset++;
|
|
if(fileOffset > (int)dirContents.size() - 1)
|
|
fileOffset = 0;
|
|
} else if (pressed & KEY_LEFT) {
|
|
fileOffset -= ENTRY_PAGE_LENGTH;
|
|
if(fileOffset < 0)
|
|
fileOffset = 0;
|
|
} else if (pressed & KEY_RIGHT) {
|
|
fileOffset += ENTRY_PAGE_LENGTH;
|
|
if(fileOffset > (int)dirContents.size() - 1)
|
|
fileOffset = dirContents.size() - 1;
|
|
}
|
|
|
|
|
|
// Scroll screen if needed
|
|
if (fileOffset < screenOffset) {
|
|
screenOffset = fileOffset;
|
|
}
|
|
if (fileOffset > screenOffset + ENTRIES_PER_SCREEN - 1) {
|
|
screenOffset = fileOffset - ENTRIES_PER_SCREEN + 1;
|
|
}
|
|
|
|
if ((!(held & KEY_R) && (pressed & KEY_A))
|
|
|| (!entry->isDirectory && (held & KEY_R) && (pressed & KEY_A))) {
|
|
if (entry->name == ".." && strcmp(curdir, getDrivePath()) == 0) {
|
|
screenMode = 0;
|
|
return "null";
|
|
} else if (entry->isDirectory) {
|
|
font->printf(firstCol, fileOffset - screenOffset + ENTRIES_START_ROW, true, alignStart, Palette::white, "%-*s", SCREEN_COLS - 5, STR_ENTERING_DIRECTORY.c_str(), alignStart);
|
|
font->update(true);
|
|
// Enter selected directory
|
|
chdir(entry->name.c_str());
|
|
getDirectoryContents(dirContents);
|
|
screenOffset = 0;
|
|
fileOffset = 0;
|
|
} else {
|
|
FileOperation getOp = fileBrowse_A(entry, curdir);
|
|
if(getOp == FileOperation::bootFile) {
|
|
// Return the chosen file
|
|
return entry->name;
|
|
} else if (getOp == FileOperation::copySdOut
|
|
|| getOp == FileOperation::copyFatOut
|
|
|| (getOp == FileOperation::mountNitroFS && nitroMounted)
|
|
|| (getOp == FileOperation::mountImg && imgMounted)) {
|
|
getDirectoryContents(dirContents); // Refresh directory listing
|
|
if ((getOp == FileOperation::mountNitroFS && nitroMounted)
|
|
|| (getOp == FileOperation::mountImg && imgMounted)) {
|
|
screenOffset = 0;
|
|
fileOffset = 0;
|
|
}
|
|
}
|
|
}
|
|
} else if (entry->isDirectory && (held & KEY_R) && (pressed & KEY_A)) { // Directory options
|
|
if (entry->name == "..") {
|
|
screenMode = 0;
|
|
return "null";
|
|
} else {
|
|
FileOperation getOp = fileBrowse_A(entry, curdir);
|
|
if (getOp == FileOperation::copySdOut || getOp == FileOperation::copyFatOut) {
|
|
getDirectoryContents (dirContents); // Refresh directory listing
|
|
} else if (getOp == FileOperation::showInfo) {
|
|
for (int i = 0; i < 15; i++) swiWaitForVBlank();
|
|
}
|
|
}
|
|
} else if (pressed & KEY_B) {
|
|
if (strcmp(curdir, getDrivePath()) == 0) {
|
|
screenMode = 0;
|
|
return "null";
|
|
}
|
|
// Go up a directory
|
|
chdir("..");
|
|
getDirectoryContents(dirContents);
|
|
screenOffset = 0;
|
|
fileOffset = 0;
|
|
|
|
// Return selection to where it was
|
|
char *trailingSlash = strrchr(curdir, '/');
|
|
*trailingSlash = '\0';
|
|
std::string dirName = strrchr(curdir, '/') + 1;
|
|
*trailingSlash = '/';
|
|
for(size_t i = 0; i < dirContents.size(); i++) {
|
|
if(dirContents[i].name == dirName) {
|
|
fileOffset = i;
|
|
if (fileOffset > screenOffset + ENTRIES_PER_SCREEN - 1)
|
|
screenOffset = fileOffset - ENTRIES_PER_SCREEN + 1;
|
|
break;
|
|
}
|
|
}
|
|
} else if ((held & KEY_R) && (pressed & KEY_X) && (entry->name != ".." && driveWritable(currentDrive))) { // Rename file/folder
|
|
pressed = 0;
|
|
|
|
std::string newName = kbdGetString(STR_RENAME_TO, -1, entry->name);
|
|
|
|
if (newName.length() > 0) {
|
|
// Check for unsupported characters
|
|
for (uint i = 0; i < newName.length(); i++) {
|
|
switch(newName[i]) {
|
|
case '>':
|
|
case '<':
|
|
case ':':
|
|
case '"':
|
|
case '/':
|
|
case '\\':
|
|
case '|':
|
|
case '?':
|
|
case '*':
|
|
newName[i] = '_'; // Remove unsupported character
|
|
}
|
|
}
|
|
if (rename(entry->name.c_str(), newName.c_str()) == 0) {
|
|
getDirectoryContents(dirContents);
|
|
}
|
|
}
|
|
} else if ((pressed & KEY_X) && (entry->name != ".." && driveWritable(currentDrive))) { // Delete action
|
|
font->clear(false);
|
|
int selections = std::count_if(dirContents.begin(), dirContents.end(), [](const DirEntry &x){ return x.selected; });
|
|
if (entry->selected && selections > 1) {
|
|
font->printf(firstCol, 0, false, alignStart, Palette::white, STR_DELETE_N_PATHS.c_str(), selections);
|
|
for (uint i = 0, printed = 0; i < dirContents.size() && printed < 5; i++) {
|
|
if (dirContents[i].selected) {
|
|
font->printf(firstCol, printed + 2, false, alignStart, Palette::red, "- %s", dirContents[i].name.c_str());
|
|
printed++;
|
|
}
|
|
}
|
|
if(selections > 5)
|
|
font->printf(firstCol, 7, false, alignStart, Palette::red, selections - 5 == 1 ? STR_AND_1_MORE.c_str() : STR_AND_N_MORE.c_str(), selections - 5);
|
|
} else {
|
|
font->printf(firstCol, 0, false, alignStart, Palette::white, STR_DELETE_X.c_str(), entry->name.c_str());
|
|
}
|
|
font->print(firstCol, (!entry->selected || selections == 1) ? 2 : (selections > 5 ? 9 : selections + 3), false, STR_A_YES_B_NO, alignStart);
|
|
font->update(false);
|
|
|
|
while (true) {
|
|
scanKeys();
|
|
pressed = keysDownRepeat();
|
|
swiWaitForVBlank();
|
|
if (pressed & KEY_A) {
|
|
if (entry->selected) {
|
|
font->clear(false);
|
|
font->print(firstCol, 0, false, STR_DELETING_FILES, alignStart);
|
|
font->update(false);
|
|
struct stat st;
|
|
for (auto &item : dirContents) {
|
|
if(item.selected) {
|
|
if (FAT_getAttr(item.name.c_str()) & ATTR_READONLY)
|
|
continue;
|
|
stat(item.name.c_str(), &st);
|
|
if (st.st_mode & S_IFDIR)
|
|
recRemove(item.name.c_str(), dirContents);
|
|
else
|
|
remove(item.name.c_str());
|
|
}
|
|
}
|
|
fileOffset = 0;
|
|
} else if (FAT_getAttr(entry->name.c_str()) & ATTR_READONLY) {
|
|
font->clear(false);
|
|
font->printf(firstCol, 0, false, alignStart, Palette::white, STR_FAILED_DELETING.c_str(), entry->name.c_str());
|
|
font->print(firstCol, 3, false, STR_A_CONTINUE, alignStart);
|
|
pressed = 0;
|
|
|
|
while (!(pressed & KEY_A)) {
|
|
scanKeys();
|
|
pressed = keysDown();
|
|
swiWaitForVBlank();
|
|
}
|
|
for (int i = 0; i < 15; i++) swiWaitForVBlank();
|
|
} else {
|
|
if (entry->isDirectory) {
|
|
font->clear(false);
|
|
font->print(firstCol, 0, false, STR_DELETING_FOLDER, alignStart);
|
|
font->update(false);
|
|
recRemove(entry->name.c_str(), dirContents);
|
|
} else {
|
|
font->clear(false);
|
|
font->print(firstCol, 0, false, STR_DELETING_FILES, alignStart);
|
|
font->update(false);
|
|
remove(entry->name.c_str());
|
|
}
|
|
fileOffset--;
|
|
}
|
|
getDirectoryContents (dirContents);
|
|
pressed = 0;
|
|
break;
|
|
}
|
|
if (pressed & KEY_B) {
|
|
pressed = 0;
|
|
break;
|
|
}
|
|
}
|
|
} else if ((held & KEY_R) && (pressed & KEY_Y) && driveWritable(currentDrive)) { // Create new folder
|
|
pressed = 0;
|
|
|
|
std::string newName = kbdGetString(STR_NAME_FOR_NEW_FOLDER);
|
|
|
|
if (newName.length() > 0) {
|
|
// Check for unsupported characters
|
|
for (uint i = 0; i < newName.length(); i++) {
|
|
switch(newName[i]) {
|
|
case '>':
|
|
case '<':
|
|
case ':':
|
|
case '"':
|
|
case '/':
|
|
case '\\':
|
|
case '|':
|
|
case '?':
|
|
case '*':
|
|
newName[i] = '_'; // Remove unsupported character
|
|
}
|
|
}
|
|
if (mkdir(newName.c_str(), 0777) == 0) {
|
|
getDirectoryContents (dirContents);
|
|
}
|
|
}
|
|
} else if ((pressed & KEY_L && !(held & KEY_R)) && entry->name != "..") { // Add to selection
|
|
bool select = !entry->selected;
|
|
entry->selected = select;
|
|
while(held & KEY_L) {
|
|
do {
|
|
scanKeys();
|
|
pressed = keysDownRepeat();
|
|
held = keysHeld();
|
|
swiWaitForVBlank();
|
|
} while ((held & KEY_L) && !(pressed & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT)));
|
|
|
|
if(pressed & (KEY_UP | KEY_DOWN)) {
|
|
if (pressed & KEY_UP) {
|
|
fileOffset--;
|
|
if(fileOffset < 0) {
|
|
fileOffset = dirContents.size() - 1;
|
|
} else {
|
|
entry = &dirContents[fileOffset];
|
|
if(entry->name != "..")
|
|
entry->selected = select;
|
|
}
|
|
} else if (pressed & KEY_DOWN) {
|
|
fileOffset++;
|
|
if(fileOffset > (int)dirContents.size() - 1) {
|
|
fileOffset = 0;
|
|
} else {
|
|
entry = &dirContents[fileOffset];
|
|
if(entry->name != "..")
|
|
entry->selected = select;
|
|
}
|
|
}
|
|
|
|
// Scroll screen if needed
|
|
if (fileOffset < screenOffset) {
|
|
screenOffset = fileOffset;
|
|
} else if (fileOffset > screenOffset + ENTRIES_PER_SCREEN - 1) {
|
|
screenOffset = fileOffset - ENTRIES_PER_SCREEN + 1;
|
|
}
|
|
}
|
|
|
|
if(pressed & KEY_LEFT) {
|
|
for(auto &item : dirContents) {
|
|
if(item.name != "..")
|
|
item.selected = false;
|
|
}
|
|
} else if(pressed & KEY_RIGHT) {
|
|
for(auto &item : dirContents) {
|
|
if(item.name != "..")
|
|
item.selected = true;
|
|
}
|
|
}
|
|
|
|
fileBrowse_drawBottomScreen(entry);
|
|
showDirectoryContents(dirContents, fileOffset, screenOffset, curdir);
|
|
}
|
|
} else if (pressed & KEY_Y) {
|
|
// Copy
|
|
if (!clipboardOn) {
|
|
if (entry->name != "..") {
|
|
clipboardOn = true;
|
|
clipboardUsed = false;
|
|
clipboard.clear();
|
|
if (entry->selected) {
|
|
for (auto &item : dirContents) {
|
|
if(item.selected) {
|
|
clipboard.emplace_back(curdir + item.name, item.name, item.isDirectory, currentDrive);
|
|
item.selected = false;
|
|
}
|
|
}
|
|
} else {
|
|
clipboard.emplace_back(curdir + entry->name, entry->name, entry->isDirectory, currentDrive);
|
|
}
|
|
}
|
|
// Paste
|
|
} else if (driveWritable(currentDrive) && fileBrowse_paste(curdir)) {
|
|
getDirectoryContents (dirContents);
|
|
}
|
|
} else if ((pressed & KEY_SELECT) && !clipboardUsed) {
|
|
clipboardOn = !clipboardOn;
|
|
} if (pressed & KEY_START) { // START menu
|
|
startMenu();
|
|
} else if (pressed & config->screenSwapKey()) { // Swap screens
|
|
screenSwapped = !screenSwapped;
|
|
screenSwapped ? lcdMainOnBottom() : lcdMainOnTop();
|
|
}
|
|
}
|
|
}
|
|
|