From 2ca30a9bd252d4b1e6feeac4b90ed4137f5f7170 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sun, 8 Aug 2021 07:31:00 -0500 Subject: [PATCH 01/21] WIP: Use bitmap mode and clean up some things --- .gitignore | 2 +- Makefile | 163 +-------- arm9/Makefile | 36 +- arm9/include/bmp.h | 14 +- arm9/include/screenshot.h | 8 +- arm9/source/driveMenu.cpp | 483 ++++++++++---------------- arm9/source/driveOperations.cpp | 16 +- arm9/source/driveOperations.h | 4 +- arm9/source/dumpOperations.cpp | 270 +++++++-------- arm9/source/fileOperations.cpp | 239 +++++-------- arm9/source/fileOperations.h | 5 +- arm9/source/file_browse.cpp | 596 ++++++++++++++------------------ arm9/source/font.cpp | 369 ++++++++++++++++++++ arm9/source/font.h | 94 +++++ arm9/source/hexEditor.cpp | 204 +++++------ arm9/source/main.cpp | 155 +++------ arm9/source/ndsInfo.cpp | 41 ++- arm9/source/screenshot.cpp | 192 +++++----- data/font_default.frf | Bin 0 -> 5796 bytes 19 files changed, 1443 insertions(+), 1448 deletions(-) create mode 100644 arm9/source/font.cpp create mode 100644 arm9/source/font.h create mode 100644 data/font_default.frf diff --git a/.gitignore b/.gitignore index 58b245c..c67ec00 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ *.nds *.cia *.elf -data/* +data/*.bin .vscode *.DS_Store diff --git a/Makefile b/Makefile index b6f4390..a3e25ba 100644 --- a/Makefile +++ b/Makefile @@ -9,149 +9,32 @@ endif include $(DEVKITARM)/ds_rules -export VERSION_MAJOR := 1 -export VERSION_MINOR := 1 -export VERSION_PATCH := 0 +export TARGET := GodMode9i - -VERSION := $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH) -#--------------------------------------------------------------------------------- -# TARGET is the name of the output -# BUILD is the directory where object files & intermediate files will be placed -# SOURCES is a list of directories containing source code -# INCLUDES is a list of directories containing extra header files -# DATA is a list of directories containing binary files embedded using bin2o -# GRAPHICS is a list of directories containing image files to be converted with grit -#--------------------------------------------------------------------------------- -TARGET := GodMode9i -BUILD := build -SOURCES := source -INCLUDES := include source -DATA := data -GRAPHICS := gfx - -#--------------------------------------------------------------------------------- -# options for code generation -#--------------------------------------------------------------------------------- -ARCH := -mthumb -mthumb-interwork - -CFLAGS := -g -Wall -O2 \ - -ffunction-sections -fdata-sections \ - -march=armv5te -mtune=arm946e-s -fomit-frame-pointer\ - -ffast-math \ - $(ARCH) - -CFLAGS += $(INCLUDE) -DARM9 -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++11 - -ASFLAGS := -g $(ARCH) -LDFLAGS = -specs=ds_arm9.specs -g -Wl,--gc-sections $(ARCH) -Wl,-Map,$(notdir $*.map) - -#--------------------------------------------------------------------------------- -# any extra libraries we wish to link with the project (order is important) -#--------------------------------------------------------------------------------- -LIBS := -lfat -lnds9 - - -#--------------------------------------------------------------------------------- -# list of directories containing libraries, this must be the top level containing -# include and lib -#--------------------------------------------------------------------------------- -LIBDIRS := $(LIBNDS) - -#--------------------------------------------------------------------------------- -# no real need to edit anything past this point unless you need to add additional -# rules for different file extensions -#--------------------------------------------------------------------------------- -ifneq ($(BUILD),$(notdir $(CURDIR))) -#--------------------------------------------------------------------------------- -export TOPDIR := $(CURDIR) - -export OUTPUT := $(CURDIR)/$(TARGET) - -export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ - $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \ - $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) - -export DEPSDIR := $(CURDIR)/$(BUILD) - -CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) -CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) -BMPFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.bmp))) -PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) -SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) -BINFILES := load.bin bootstub.bin - -#--------------------------------------------------------------------------------- -# use CXX for linking C++ projects, CC for standard C -#--------------------------------------------------------------------------------- -ifeq ($(strip $(CPPFILES)),) -#--------------------------------------------------------------------------------- - export LD := $(CC) -#--------------------------------------------------------------------------------- -else -#--------------------------------------------------------------------------------- - export LD := $(CXX) -#--------------------------------------------------------------------------------- -endif -#--------------------------------------------------------------------------------- - -export OFILES := $(addsuffix .o,$(BINFILES)) \ - $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) - -export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \ - $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ - -I$(CURDIR)/$(BUILD) - -export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) - -icons := $(wildcard *.bmp) - -ifneq (,$(findstring $(TARGET).bmp,$(icons))) - export GAME_ICON := $(CURDIR)/$(TARGET).bmp -else - ifneq (,$(findstring icon.bmp,$(icons))) - export GAME_ICON := $(CURDIR)/icon.bmp - endif -endif - export GAME_TITLE := $(TARGET) -.PHONY: bootloader bootstub clean arm7/$(TARGET).elf arm9/$(TARGET).elf +.PHONY: all bootloader bootstub clean dsi arm7/$(TARGET).elf arm9/$(TARGET).elf all: bootloader bootstub $(TARGET).nds dsi: $(TARGET).dsi - -dist: all - @rm -fr hbmenu - @mkdir hbmenu - @cp $(TARGET).nds hbmenu/BOOT.NDS - @cp BootStrap/_BOOT_MP.NDS BootStrap/TTMENU.DAT BootStrap/_DS_MENU.DAT BootStrap/ez5sys.bin BootStrap/akmenu4.nds hbmenu - @tar -cvjf $(TARGET)-$(VERSION).tar.bz2 hbmenu testfiles README.html COPYING hbmenu -X exclude.lst - -$(TARGET).nds: $(TARGET).arm7 $(TARGET).arm9 - ndstool -c $(TARGET).nds -7 $(TARGET).arm7.elf -9 $(TARGET).arm9.elf \ + +$(TARGET).nds: arm7/$(TARGET).elf arm9/$(TARGET).elf + ndstool -c $(TARGET).nds -7 arm7/$(TARGET).elf -9 arm9/$(TARGET).elf \ -b icon.bmp "GodMode9i;RocketRobz" \ -z 80040000 -u 00030004 python fix_ndsheader.py $(CURDIR)/$(TARGET).nds - -$(TARGET).dsi: $(TARGET).arm7 $(TARGET).arm9 - ndstool -c $(TARGET).dsi -7 $(TARGET).arm7.elf -9 $(TARGET).arm9.elf \ + +$(TARGET).dsi: arm7/$(TARGET).elf arm9/$(TARGET).elf + ndstool -c $(TARGET).dsi -7 arm7/$(TARGET).elf -9 arm9/$(TARGET).elf \ -b icon.bmp "GodMode9i;RocketRobz" \ -g HGMA 00 "GODMODE9I" -z 80040000 -u 00030004 python fix_ndsheader.py $(CURDIR)/$(TARGET).dsi -$(TARGET).arm7: arm7/$(TARGET).elf - cp arm7/$(TARGET).elf $(TARGET).arm7.elf - -$(TARGET).arm9: arm9/$(TARGET).elf - cp arm9/$(TARGET).elf $(TARGET).arm9.elf - #--------------------------------------------------------------------------------- arm7/$(TARGET).elf: @$(MAKE) -C arm7 - + #--------------------------------------------------------------------------------- arm9/$(TARGET).elf: @$(MAKE) -C arm9 @@ -163,7 +46,7 @@ arm9/$(TARGET).elf: #--------------------------------------------------------------------------------- clean: @echo clean ... - @rm -fr data + @rm -fr data/*.bin @rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds @rm -fr $(TARGET).arm7.elf @rm -fr $(TARGET).arm9.elf @@ -172,32 +55,8 @@ clean: @$(MAKE) -C arm9 clean @$(MAKE) -C arm7 clean -data: - @mkdir -p data - bootloader: data @$(MAKE) -C bootloader LOADBIN=$(CURDIR)/data/load.bin - + bootstub: data @$(MAKE) -C bootstub - -#--------------------------------------------------------------------------------- -else - -#--------------------------------------------------------------------------------- -# main targets -#--------------------------------------------------------------------------------- -#$(OUTPUT).nds : $(OUTPUT).elf -#$(OUTPUT).elf : $(OFILES) - -#--------------------------------------------------------------------------------- -%.bin.o : %.bin -#--------------------------------------------------------------------------------- - @echo $(notdir $<) - $(bin2o) - --include $(DEPSDIR)/*.d - -#--------------------------------------------------------------------------------------- -endif -#--------------------------------------------------------------------------------------- diff --git a/arm9/Makefile b/arm9/Makefile index fe7f79e..61aa9f4 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -37,8 +37,8 @@ endif #--------------------------------------------------------------------------------- TARGET := GodMode9i BUILD := build -SOURCES := source dldi-include ramdrive-include mbedtls -INCLUDES := include dldi-include ramdrive-include source +SOURCES := source source/graphics dldi-include ramdrive-include mbedtls +INCLUDES := include dldi-include ramdrive-include source source/graphics DATA := ../data GRAPHICS := ../gfx @@ -53,7 +53,7 @@ CFLAGS := -g -Wall -O2\ $(ARCH) CFLAGS += $(INCLUDE) -DARM9 -D_NO_BOOTSTUB_ -CXXFLAGS := $(CFLAGS) -fno-exceptions -std=gnu++11 +CXXFLAGS := $(CFLAGS) -fno-exceptions -std=gnu++17 ASFLAGS := -g $(ARCH) LDFLAGS = -specs=../ds_arm9_hi.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) @@ -62,14 +62,13 @@ LDFLAGS = -specs=../ds_arm9_hi.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) # any extra libraries we wish to link with the project (order is important) #--------------------------------------------------------------------------------- LIBS := -lfat -lnds9 - - + #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing # include and lib #--------------------------------------------------------------------------------- LIBDIRS := $(CURDIR) ../ $(LIBNDS) - + #--------------------------------------------------------------------------------- # no real need to edit anything past this point unless you need to add additional # rules for different file extensions @@ -91,7 +90,7 @@ BMPFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.bmp))) PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) - + #--------------------------------------------------------------------------------- # use CXX for linking C++ projects, CC for standard C #--------------------------------------------------------------------------------- @@ -110,20 +109,20 @@ export OFILES := $(addsuffix .o,$(BINFILES)) \ $(BMPFILES:.bmp=.o) \ $(PNGFILES:.png=.o) \ $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) - + export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ -I$(CURDIR)/$(BUILD) - + export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) export OUTPUT := $(CURDIR)/$(TARGET) - + .PHONY: $(BUILD) clean all : $(BUILD) - + #--------------------------------------------------------------------------------- $(BUILD): @[ -d $@ ] || mkdir -p $@ @@ -135,38 +134,43 @@ clean: #--------------------------------------------------------------------------------- else - + DEPENDS := $(OFILES:.o=.d) #--------------------------------------------------------------------------------- # main targets #--------------------------------------------------------------------------------- $(OUTPUT).elf : $(OFILES) - + #--------------------------------------------------------------------------------- %.bin.o : %.bin #--------------------------------------------------------------------------------- @echo $(notdir $<) $(bin2o) +#--------------------------------------------------------------------------------- +%.frf.o : %.frf +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + $(bin2o) + #--------------------------------------------------------------------------------- # This rule creates assembly source files using grit # grit takes an image file and a .grit describing how the file is to be processed # add additional rules like this for each image extension -# you use in the graphics folders +# you use in the graphics folders #--------------------------------------------------------------------------------- %.s %.h : %.bmp %.grit #--------------------------------------------------------------------------------- grit $< -fts -o$* - #--------------------------------------------------------------------------------- %.s %.h : %.png %.grit #--------------------------------------------------------------------------------- grit $< -fts -o$* -include $(DEPSDIR)/*.d - + #--------------------------------------------------------------------------------------- endif #--------------------------------------------------------------------------------------- diff --git a/arm9/include/bmp.h b/arm9/include/bmp.h index 0c7ea60..4d7b0cc 100644 --- a/arm9/include/bmp.h +++ b/arm9/include/bmp.h @@ -1,23 +1,29 @@ #ifndef _bmp_h_ #define _bmp_h_ +#include + typedef struct { u16 type; /* Magic identifier */ - u32 size; /* File size in bytes */ + u32 size; /* File size in bytes */ u16 reserved1, reserved2; - u32 offset; /* Offset to image data, bytes */ + u32 offset; /* Offset to image data, bytes */ } PACKED HEADER; typedef struct { u32 size; /* Header size in bytes */ - u32 width,height; /* Width and height of image */ + u32 width, height; /* Width and height of image */ u16 planes; /* Number of colour planes */ u16 bits; /* Bits per pixel */ u32 compression; /* Compression type */ u32 imagesize; /* Image size in bytes */ - u32 xresolution,yresolution; /* Pixels per meter */ + u32 xresolution, yresolution; /* Pixels per meter */ u32 ncolours; /* Number of colours */ u32 importantcolours; /* Important colours */ + u32 redBitmask; /* Red bitmask */ + u32 greenBitmask; /* Green bitmask */ + u32 blueBitmask; /* Blue bitmask */ + u32 reserved; } PACKED INFOHEADER; #endif //_bmp_h_ diff --git a/arm9/include/screenshot.h b/arm9/include/screenshot.h index 454bec4..12d23a6 100644 --- a/arm9/include/screenshot.h +++ b/arm9/include/screenshot.h @@ -1,2 +1,6 @@ -void screenshot(const char* filename); -void screenshotbmp(const char* filename); +#ifndef SCREENSHOT_H +#define SCREENSHOT_H + +bool screenshot(void); + +#endif // SCREENSHOT_H diff --git a/arm9/source/driveMenu.cpp b/arm9/source/driveMenu.cpp index c2b9683..67e41a0 100644 --- a/arm9/source/driveMenu.cpp +++ b/arm9/source/driveMenu.cpp @@ -33,214 +33,178 @@ #include "dumpOperations.h" #include "driveOperations.h" #include "fileOperations.h" +#include "font.h" -#define SCREEN_COLS 32 -#define ENTRIES_PER_SCREEN 22 #define ENTRIES_START_ROW 1 #define ENTRY_PAGE_LENGTH 10 -#define sizeOfdmAssignedOp 8 +enum class DriveMenuOperation { + none, + sdCard, + flashcard, + ramDrive1, + ramDrive2, + sysNand, + nitroFs, + fatImage, + gbaCart, + ndsCard, +}; //static bool ramDumped = false; bool flashcardMountSkipped = true; static bool flashcardMountRan = true; -static bool dmTextPrinted = false; static int dmCursorPosition = 0; -static int dmAssignedOp[sizeOfdmAssignedOp] = {-1}; -static int dmMaxCursors = -1; +static std::vector dmOperations; static u8 gbaFixedValue = 0; extern bool arm7SCFGLocked; extern bool expansionPakFound; -extern PrintConsole topConsole, bottomConsole; - -extern void printBorderTop(void); -extern void printBorderBottom(void); -extern void clearBorderTop(void); -extern void clearBorderBottom(void); - void dm_drawTopScreen(void) { - /*if (!ramDumped) { - printf ("Dumping RAM..."); - FILE* destinationFile = fopen("sd:/ramdump.bin", "wb"); - fwrite((void*)0x02000000, 1, 0x400000, destinationFile); - fclose(destinationFile); - consoleClear(); - ramDumped = true; - }*/ + font->clear(true); - consoleClear(); + // Top bar + font->printf(0, 0, true, Alignment::left, Palette::blackGreen, "%*c", 256 / font->width(), ' '); + font->print(0, 0, true, "[root]", Alignment::left, Palette::blackGreen); - printf ("\x1B[30m"); // Print background black color // Print time - printf ("\x1b[0;27H"); - printf (RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); - printf ("\x1b[0;0H"); - printf ("[root]"); - printf ("\x1B[47m"); // Print foreground white color - - // Move to 2nd row - printf ("\x1b[1;0H"); - - if (dmMaxCursors == -1) { - printf ("No drives found!"); + if (dmOperations.size() == 0) { + font->print(0, 1, true, "No drives found!", Alignment::left, Palette::blackGreen); } else - for (int i = 0; i <= dmMaxCursors; i++) { - iprintf ("\x1b[%d;0H", i + ENTRIES_START_ROW); - if (dmCursorPosition == i) { - printf ("\x1B[47m"); // Print foreground white color - } else { - printf ("\x1B[40m"); // Print foreground black color - } - if (dmAssignedOp[i] == 0) { - printf ("[sd:] SDCARD"); - if (sdLabel[0] != '\0') { - iprintf (" (%s)", sdLabel); - } - } else if (dmAssignedOp[i] == 1) { - printf ("[fat:] FLASHCART"); - if (fatLabel[0] != '\0') { - iprintf (" (%s)", fatLabel); - } - } else if (dmAssignedOp[i] == 2) { - printf ("GBA GAMECART"); - if (gbaFixedValue != 0x96) { - iprintf ("\x1b[%d;29H", i + ENTRIES_START_ROW); - printf ("[x]"); - } - } else if (dmAssignedOp[i] == 3) { - printf ("[nitro:] NDS GAME IMAGE"); - if ((sdMounted && nitroCurrentDrive==0) - || (flashcardMounted && nitroCurrentDrive==1) - || (ramdrive1Mounted && nitroCurrentDrive==2) - || (ramdrive2Mounted && nitroCurrentDrive==3) - || (nandMounted && nitroCurrentDrive==4) - || (imgMounted && nitroCurrentDrive==6)) - { - // Do nothing - } - else - { - iprintf ("\x1b[%d;29H", i + ENTRIES_START_ROW); - printf ("[x]"); - } - } else if (dmAssignedOp[i] == 4) { - printf ("NDS GAMECARD"); - } else if (dmAssignedOp[i] == 5) { - printf ("[ram1:] RAMDRIVE"); - } else if (dmAssignedOp[i] == 6) { - printf ("[ram2:] RAMDRIVE"); - } else if (dmAssignedOp[i] == 7) { - printf ("[nand:] SYSNAND"); - } else if (dmAssignedOp[i] == 8) { - printf ("[img:] FAT IMAGE"); - if ((sdMounted && imgCurrentDrive==0) - || (flashcardMounted && imgCurrentDrive==1) - || (ramdrive1Mounted && imgCurrentDrive==2) - || (ramdrive2Mounted && imgCurrentDrive==3) - || (nandMounted && imgCurrentDrive==4)) - { - if (imgLabel[0] != '\0') { - iprintf (" (%s)", imgLabel); + for (int i = 0; i < (int)dmOperations.size(); i++) { + Palette pal = dmCursorPosition == i ? Palette::white : Palette::gray; + switch(dmOperations[i]) { + case DriveMenuOperation::sdCard: + font->printf(0, i + 1, true, Alignment::left, pal, "[sd:] SDCARD (%s)", sdLabel[0] == 0 ? "UNTITLED" : sdLabel); + break; + case DriveMenuOperation::flashcard: + font->printf(0, i + 1, true, Alignment::left, pal, "[fat:] FLASHCARD (%s)", fatLabel[0] == 0 ? "UNTITLED" : fatLabel); + break; + case DriveMenuOperation::ramDrive1: + font->print(0, i + 1, true, "[ram1:] RAMDRIVE", Alignment::left, pal); + break; + case DriveMenuOperation::ramDrive2: + font->print(0, i + 1, true, "[ram2:] RAMDRIVE", Alignment::left, pal); + break; + case DriveMenuOperation::sysNand: + font->print(0, i + 1, true, "[nand:] SYSNAND", Alignment::left, pal); + break; + case DriveMenuOperation::nitroFs: + font->print(0, i + 1, true, "[nitro:] NDS GAME IMAGE", Alignment::left, pal); + if (!((sdMounted && nitroCurrentDrive==0) + || (flashcardMounted && nitroCurrentDrive==1) + || (ramdrive1Mounted && nitroCurrentDrive==2) + || (ramdrive2Mounted && nitroCurrentDrive==3) + || (nandMounted && nitroCurrentDrive==4) + || (imgMounted && nitroCurrentDrive==6))) + font->print(256 - font->width(), i + 1, true, "[x]", Alignment::right, pal); + break; + case DriveMenuOperation::fatImage: + if ((sdMounted && imgCurrentDrive==0) + || (flashcardMounted && imgCurrentDrive==1) + || (ramdrive1Mounted && imgCurrentDrive==2) + || (ramdrive2Mounted && imgCurrentDrive==3) + || (nandMounted && imgCurrentDrive==4)) { + font->printf(0, i + 1, true, Alignment::left, pal, "[nitro:] FAT IMAGE (%s)", imgLabel[0] == 0 ? "UNTITLED" : imgLabel); + } else { + font->print(0, i + 1, true, "[nitro:] FAT IMAGE", Alignment::left, pal); + font->print(256 - font->width(), i + 1, true, "[x]", Alignment::right, pal); } - } - else - { - iprintf ("\x1b[%d;29H", i + ENTRIES_START_ROW); - printf ("[x]"); - } + break; + case DriveMenuOperation::gbaCart: + font->print(0, i + 1, true, "GBA GAMECART", Alignment::left, pal); + if (gbaFixedValue != 0x96) + font->print(256 - font->width(), i + 1, true, "[x]", Alignment::right, pal); + break; + case DriveMenuOperation::ndsCard: + font->print(0, i + 1, true, "NDS GAMECARD", Alignment::left, pal); + break; + case DriveMenuOperation::none: + break; } } + + font->update(true); } void dm_drawBottomScreen(void) { - consoleClear(); + font->clear(false); - printf ("\x1B[47m"); // Print foreground white color - printf ("\x1b[23;0H"); - printf (titleName); - if (nitroMounted || imgMounted) { - printf ("\n"); - printf (IMAGETEXT); + int row = -1; + + if (!isDSiMode() && isRegularDS) { + font->print(0, row--, false, POWERTEXT_DS); + } else if (is3DS) { + font->print(0, row--, false, HOMETEXT); + font->print(0, row--, false, POWERTEXT_3DS); + } else { + font->print(0, row--, false, POWERTEXT); } + if (sdMountedDone) { if (isRegularDS || sdMounted) { - printf ("\n"); - printf (sdMounted ? "R+B - Unmount SD card" : "R+B - Remount SD card"); + font->print(0, row--, false, sdMounted ? "R+B - Unmount SD card" : "R+B - Remount SD card"); } } else { - printf ("\n"); - printf (flashcardMounted ? "R+B - Unmount Flashcard" : "R+B - Remount Flashcard"); + font->print(0, row--, false, flashcardMounted ? "R+B - Unmount Flashcard" : "R+B - Remount Flashcard"); } if (sdMounted || flashcardMounted) { - printf ("\n"); - printf (SCREENSHOTTEXT); - } - printf ("\n"); - if (!isDSiMode() && isRegularDS) { - printf (POWERTEXT_DS); - } else if (is3DS) { - printf (POWERTEXT_3DS); - printf ("\n"); - printf (HOMETEXT); - } else { - printf (POWERTEXT); + font->print(0, row--, false, SCREENSHOTTEXT); } - printf ("\x1B[40m"); // Print foreground black color - printf ("\x1b[0;0H"); - if (dmAssignedOp[dmCursorPosition] == 0) { - printf ("[sd:] SDCARD"); - if (sdLabel[0] != '\0') { - iprintf (" (%s)", sdLabel); - } - printf ("\n(SD FAT, "); - printDriveBytes(sdSize); - printf(")\n"); - printDriveBytes(getBytesFree("sd:/")); - printf(" space free"); - } else if (dmAssignedOp[dmCursorPosition] == 1) { - printf ("[fat:] FLASHCART"); - if (fatLabel[0] != '\0') { - iprintf (" (%s)", fatLabel); - } - printf ("\n(Slot-1 SD FAT, "); - printDriveBytes(fatSize); - printf(")\n"); - printDriveBytes(getBytesFree("fat:/")); - printf(" space free"); - } else if (dmAssignedOp[dmCursorPosition] == 2) { - printf ("GBA GAMECART\n"); - printf ("(GBA Game)"); - } else if (dmAssignedOp[dmCursorPosition] == 3) { - printf ("[nitro:] NDS GAME IMAGE\n"); - printf ("(Game Virtual)"); - } else if (dmAssignedOp[dmCursorPosition] == 4) { - printf ("NDS GAMECARD\n"); - printf ("(NDS Game)"); - } else if (dmAssignedOp[dmCursorPosition] == 5) { - printf ("[ram1:] RAMDRIVE\n"); - printf ("(RAMdrive FAT, 9 MB)"); - } else if (dmAssignedOp[dmCursorPosition] == 6) { - printf ("[ram2:] RAMDRIVE\n"); - printf ("(RAMdrive FAT, 16 MB)"); - } else if (dmAssignedOp[dmCursorPosition] == 7) { - printf ("[nand:] SYSNAND"); - printf ("\n(SysNAND FAT, "); - printDriveBytes(nandSize); - printf(")\n"); - printDriveBytes(getBytesFree("nand:/")); - printf(" space free"); - } else if (dmAssignedOp[dmCursorPosition] == 8) { - printf ("[img:] FAT IMAGE"); - printf ("\n(Image FAT, "); - printDriveBytes(imgSize); - printf(")"); + font->print(0, row--, false, IMAGETEXT); + font->print(0, row--, false, titleName); + + switch(dmOperations[dmCursorPosition]) { + case DriveMenuOperation::sdCard: + font->printf(0, 0, false, Alignment::left, Palette::white, "[sd:] SDCARD (%s)", sdLabel[0] == 0 ? "UNTITLED" : sdLabel); + font->printf(0, 1, false, Alignment::left, Palette::white, "(SD FAT, %s)", getDriveBytes(sdSize).c_str()); + font->printf(0, 2, false, Alignment::left, Palette::white, "%s free", getDriveBytes(getBytesFree("sd:/")).c_str()); + break; + case DriveMenuOperation::flashcard: + font->printf(0, 0, false, Alignment::left, Palette::white, "[fat:] FLASHCARD (%s)", fatLabel[0] == 0 ? "UNTITLED" : fatLabel); + font->printf(0, 1, false, Alignment::left, Palette::white, "(Slot-1 SD FAT, %s)", getDriveBytes(fatSize).c_str()); + font->printf(0, 2, false, Alignment::left, Palette::white, "%s free", getDriveBytes(getBytesFree("fat:/")).c_str()); + break; + case DriveMenuOperation::gbaCart: + font->print(0, 0, false, "GBA GAMECART"); + font->print(0, 1, false, "(GBA Game)"); + break; + case DriveMenuOperation::nitroFs: + font->print(0, 0, false, "[nitro:] NDS GAME IMAGE\n"); + font->print(0, 1, false, "(Game Virtual)"); + break; + case DriveMenuOperation::ndsCard: + font->print(0, 0, false, "NDS GAMECARD\n"); + font->print(0, 1, false, "(NDS Game)"); + break; + case DriveMenuOperation::ramDrive1: + font->print(0, 0, false, "[ram1:] RAMDRIVE\n"); + font->print(0, 1, false, "(RAMdrive FAT, 9 MB)"); + break; + case DriveMenuOperation::ramDrive2: + font->print(0, 0, false, "[ram2:] RAMDRIVE\n"); + font->print(0, 1, false, "(RAMdrive FAT, 16 MB)"); + break; + case DriveMenuOperation::sysNand: + font->print(0, 0, false, "[nand:] SYSNAND"); + font->printf(0, 1, false, Alignment::left, Palette::white, "(SysNAND FAT, %s)", getDriveBytes(fatSize).c_str()); + font->printf(0, 2, false, Alignment::left, Palette::white, "%s free", getDriveBytes(getBytesFree("nand:/")).c_str()); + break; + case DriveMenuOperation::fatImage: + font->print(0, 0, false, "[img:] FAT IMAGE"); + font->printf(0, 1, false, Alignment::left, Palette::white, "(Image FAT, %s)", getDriveBytes(imgSize).c_str()); + break; + case DriveMenuOperation::none: + break; } + + font->update(false); } void driveMenu (void) { @@ -252,71 +216,38 @@ void driveMenu (void) { gbaFixedValue = *(u8*)(0x080000B2); } - for (int i = 0; i < sizeOfdmAssignedOp; i++) { - dmAssignedOp[i] = -1; - } - dmMaxCursors = -1; - if (sdMounted){ - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 0; - } - if (nandMounted) { - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 7; - } - if (flashcardMounted) { - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 1; - } - if (ramdrive1Mounted) { - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 5; - } - if (ramdrive2Mounted) { - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 6; - } - if (imgMounted) { - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 8; - } + dmOperations.clear(); + if (sdMounted) + dmOperations.push_back(DriveMenuOperation::sdCard); + if (nandMounted) + dmOperations.push_back(DriveMenuOperation::sysNand); + if (flashcardMounted) + dmOperations.push_back(DriveMenuOperation::flashcard); + if (ramdrive1Mounted) + dmOperations.push_back(DriveMenuOperation::ramDrive1); + if (ramdrive2Mounted) + dmOperations.push_back(DriveMenuOperation::ramDrive2); + if (imgMounted) + dmOperations.push_back(DriveMenuOperation::fatImage); if (expansionPakFound || (io_dldi_data->ioInterface.features & FEATURE_SLOT_GBA) - || (isDSiMode() && !arm7SCFGLocked && !(REG_SCFG_MC & BIT(0)))) { - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 4; - } - if (!isDSiMode() && isRegularDS) { - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 2; - } - if (nitroMounted) { - dmMaxCursors++; - dmAssignedOp[dmMaxCursors] = 3; - } + || (isDSiMode() && !arm7SCFGLocked && !(REG_SCFG_MC & BIT(0)))) + dmOperations.push_back(DriveMenuOperation::ndsCard); + if (!isDSiMode() && isRegularDS) + dmOperations.push_back(DriveMenuOperation::gbaCart); + if (nitroMounted) + dmOperations.push_back(DriveMenuOperation::nitroFs); - if (dmCursorPosition < 0) dmCursorPosition = dmMaxCursors; // Wrap around to bottom of list - if (dmCursorPosition > dmMaxCursors) dmCursorPosition = 0; // Wrap around to top of list - - if (!dmTextPrinted) { - consoleSelect(&bottomConsole); - dm_drawBottomScreen(); - consoleSelect(&topConsole); - dm_drawTopScreen(); - - dmTextPrinted = true; - } + dm_drawBottomScreen(); + dm_drawTopScreen(); stored_SCFG_MC = REG_SCFG_MC; - printf ("\x1B[30m"); // Print black color for time text - // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - printf ("\x1b[0;27H"); // Print time - printf (RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -325,52 +256,53 @@ void driveMenu (void) { if (!isDSiMode() && isRegularDS) { if (*(u8*)(0x080000B2) != gbaFixedValue) { - dmTextPrinted = false; break; } } else if (isDSiMode()) { if (REG_SCFG_MC != stored_SCFG_MC) { - dmTextPrinted = false; break; } } - } while (!(pressed & KEY_UP) && !(pressed & KEY_DOWN) && !(pressed & KEY_A) && !(held & KEY_R) + } while (!(pressed & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT | KEY_A | KEY_R #ifdef SCREENSWAP - && !(pressed & KEY_TOUCH) + | KEY_TOUCH #endif - ); - - printf ("\x1B[47m"); // Print foreground white color + ))); - if ((pressed & KEY_UP) && dmMaxCursors != -1) { - dmCursorPosition -= 1; - dmTextPrinted = false; + if(dmOperations.size() != 0) { + if (pressed & KEY_UP) { + dmCursorPosition -= 1; + if(dmCursorPosition < 0) + dmCursorPosition = dmOperations.size() - 1; + } else if (pressed & KEY_DOWN) { + dmCursorPosition += 1; + if(dmCursorPosition >= (int)dmOperations.size()) + dmCursorPosition = 0; + } else if(pressed & KEY_LEFT) { + dmCursorPosition -= ENTRY_PAGE_LENGTH; + if(dmCursorPosition < 0) + dmCursorPosition = 0; + } else if(pressed & KEY_RIGHT) { + dmCursorPosition += ENTRY_PAGE_LENGTH; + if(dmCursorPosition >= (int)dmOperations.size()) + dmCursorPosition = dmOperations.size() - 1; + } } - if ((pressed & KEY_DOWN) && dmMaxCursors != -1) { - dmCursorPosition += 1; - dmTextPrinted = false; - } - - if (dmCursorPosition < 0) dmCursorPosition = dmMaxCursors; // Wrap around to bottom of list - if (dmCursorPosition > dmMaxCursors) dmCursorPosition = 0; // Wrap around to top of list if (pressed & KEY_A) { - if (dmAssignedOp[dmCursorPosition] == 0 && sdMounted) { - dmTextPrinted = false; + if (dmOperations[dmCursorPosition] == DriveMenuOperation::sdCard && sdMounted) { currentDrive = 0; chdir("sd:/"); screenMode = 1; break; - } else if (dmAssignedOp[dmCursorPosition] == 1 && flashcardMounted) { - dmTextPrinted = false; + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::flashcard && flashcardMounted) { currentDrive = 1; chdir("fat:/"); screenMode = 1; break; - } else if (dmAssignedOp[dmCursorPosition] == 2 && isRegularDS && flashcardMounted && gbaFixedValue == 0x96) { - dmTextPrinted = false; + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::gbaCart && isRegularDS && flashcardMounted && gbaFixedValue == 0x96) { gbaCartDump(); - } else if (dmAssignedOp[dmCursorPosition] == 3 && nitroMounted) { + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::nitroFs && nitroMounted) { if ((sdMounted && nitroCurrentDrive==0) || (flashcardMounted && nitroCurrentDrive==1) || (ramdrive1Mounted && nitroCurrentDrive==2) @@ -378,41 +310,35 @@ void driveMenu (void) { || (nandMounted && nitroCurrentDrive==4) || (imgMounted && nitroCurrentDrive==6)) { - dmTextPrinted = false; currentDrive = 5; chdir("nitro:/"); screenMode = 1; break; } - } else if (dmAssignedOp[dmCursorPosition] == 4 && (sdMounted || flashcardMounted)) { - dmTextPrinted = false; + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::ndsCard && (sdMounted || flashcardMounted)) { ndsCardDump(); - } else if (dmAssignedOp[dmCursorPosition] == 5 && isDSiMode() && ramdrive1Mounted) { - dmTextPrinted = false; + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::ramDrive1 && isDSiMode() && ramdrive1Mounted) { currentDrive = 2; chdir("ram1:/"); screenMode = 1; break; - } else if (dmAssignedOp[dmCursorPosition] == 6 && isDSiMode() && ramdrive2Mounted) { - dmTextPrinted = false; + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::ramDrive2 && isDSiMode() && ramdrive2Mounted) { currentDrive = 3; chdir("ram2:/"); screenMode = 1; break; - } else if (dmAssignedOp[dmCursorPosition] == 7 && isDSiMode() && nandMounted) { - dmTextPrinted = false; + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::sysNand && isDSiMode() && nandMounted) { currentDrive = 4; chdir("nand:/"); screenMode = 1; break; - } else if (dmAssignedOp[dmCursorPosition] == 8 && imgMounted) { + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::fatImage && imgMounted) { if ((sdMounted && imgCurrentDrive==0) || (flashcardMounted && imgCurrentDrive==1) || (ramdrive1Mounted && imgCurrentDrive==2) || (ramdrive2Mounted && imgCurrentDrive==3) || (nandMounted && imgCurrentDrive==4)) { - dmTextPrinted = false; currentDrive = 6; chdir("img:/"); screenMode = 1; @@ -423,7 +349,6 @@ void driveMenu (void) { // Unmount/Remount FAT image if ((held & KEY_R) && (pressed & KEY_X)) { - dmTextPrinted = false; if (nitroMounted) { currentDrive = 5; chdir("nitro:/"); @@ -437,7 +362,6 @@ void driveMenu (void) { // Unmount/Remount SD card if ((held & KEY_R) && (pressed & KEY_B)) { - dmTextPrinted = false; if (isDSiMode() && sdMountedDone) { if (sdMounted) { currentDrive = 0; @@ -467,40 +391,7 @@ void driveMenu (void) { // Make a screenshot if ((held & KEY_R) && (pressed & KEY_L)) { - if (sdMounted || flashcardMounted) { - if (access((sdMounted ? "sd:/gm9i" : "fat:/gm9i"), F_OK) != 0) { - mkdir((sdMounted ? "sd:/gm9i" : "fat:/gm9i"), 0777); - } - if (access((sdMounted ? "sd:/gm9i/out" : "fat:/gm9i/out"), F_OK) != 0) { - mkdir((sdMounted ? "sd:/gm9i/out" : "fat:/gm9i/out"), 0777); - } - char timeText[8]; - snprintf(timeText, sizeof(timeText), "%s", RetTime().c_str()); - char fileTimeText[8]; - snprintf(fileTimeText, sizeof(fileTimeText), "%s", RetTimeForFilename().c_str()); - char snapPath[40]; - // Take top screenshot - snprintf(snapPath, sizeof(snapPath), "%s:/gm9i/out/snap_%s_top.bmp", (sdMounted ? "sd" : "fat"), fileTimeText); - screenshotbmp(snapPath); - // Seamlessly swap top and bottom screens - lcdMainOnBottom(); - printBorderBottom(); - consoleSelect(&bottomConsole); - dm_drawTopScreen(); - printf("\x1B[30m"); // Print black color for time text - printf("\x1b[0;27H"); - printf(timeText); - clearBorderTop(); - consoleSelect(&topConsole); - dm_drawBottomScreen(); - // Take bottom screenshot - snprintf(snapPath, sizeof(snapPath), "%s:/gm9i/out/snap_%s_bot.bmp", (sdMounted ? "sd" : "fat"), fileTimeText); - screenshotbmp(snapPath); - dmTextPrinted = false; - lcdMainOnTop(); - printBorderTop(); - clearBorderBottom(); - } + screenshot(); } if (isDSiMode() && !flashcardMountSkipped && !pressed && !held) { diff --git a/arm9/source/driveOperations.cpp b/arm9/source/driveOperations.cpp index fc4a1e2..617c281 100644 --- a/arm9/source/driveOperations.cpp +++ b/arm9/source/driveOperations.cpp @@ -1,9 +1,10 @@ #include #include #include +#include +#include #include #include -#include #include "main.h" #include "dldi-include.h" @@ -58,19 +59,22 @@ static float getTbNumber(u64 bytes) { return tbNumber; } -void printDriveBytes(u64 bytes) +std::string getDriveBytes(u64 bytes) { + char buffer[12]; if (bytes < (1024 * 1024)) - printf("%d KB", (int)bytes / 1024); + sniprintf(buffer, sizeof(buffer), "%d KB", (int)bytes / 1024); else if (bytes >= (1024 * 1024) && bytes < (1024 * 1024 * 1024)) - printf("%d MB", (int)bytes / 1024 / 1024); + sniprintf(buffer, sizeof(buffer), "%d MB", (int)bytes / 1024 / 1024); else if (bytes >= 0x40000000 && bytes < 0x10000000000) - printf("%.1f GB", getGbNumber(bytes)); + snprintf(buffer, sizeof(buffer), "%.1f GB", getGbNumber(bytes)); else - printf("%.1f TB", getTbNumber(bytes)); + snprintf(buffer, sizeof(buffer), "%.1f TB", getTbNumber(bytes)); + + return buffer; } const char* getDrivePath(void) { diff --git a/arm9/source/driveOperations.h b/arm9/source/driveOperations.h index c2546d3..6f23371 100644 --- a/arm9/source/driveOperations.h +++ b/arm9/source/driveOperations.h @@ -1,6 +1,8 @@ #ifndef FLASHCARD_H #define FLASHCARD_H +#include + extern u8 stored_SCFG_MC; extern bool nandMounted; @@ -24,7 +26,7 @@ extern u32 nandSize; extern u64 sdSize; extern u64 fatSize; extern u64 imgSize; -extern void printDriveBytes(u64 bytes); +extern std::string getDriveBytes(u64 bytes); extern const char* getDrivePath(void); diff --git a/arm9/source/dumpOperations.cpp b/arm9/source/dumpOperations.cpp index d9069d7..70692ea 100644 --- a/arm9/source/dumpOperations.cpp +++ b/arm9/source/dumpOperations.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include -#include +#include "dumpOperations.h" #include "auxspi.h" #include "date.h" #include "driveOperations.h" +#include "font.h" #include "ndsheaderbanner.h" #include "read_card.h" #include "tonccpy.h" +#include +#include +#include +#include +#include + extern u8 copyBuf[]; extern bool expansionPakFound; -extern PrintConsole topConsole, bottomConsole; - static sNDSHeaderExt ndsCardHeader; //--------------------------------------------------------------------------------- @@ -138,9 +138,11 @@ uint32 cardEepromGetSizeFixed() { void ndsCardSaveDump(const char* filename) { FILE *out = fopen(filename, "wb"); if(out) { - consoleClear(); - iprintf("Dumping save...\n"); - iprintf("Do not remove the NDS card.\n"); + font->clear(false); + font->print(0, 0, false, "Dumping save..."); + font->print(0, 1, false, "Do not remove the NDS card."); + font->update(false); + unsigned char *buffer; auxspi_extra card_type = auxspi_has_extra(); if(card_type == AUXSPI_INFRARED) { @@ -168,22 +170,17 @@ void ndsCardSaveDump(const char* filename) { } void ndsCardSaveRestore(const char *filename) { - consoleSelect(&bottomConsole); - consoleClear(); - iprintf("\x1B[47m"); // Print foreground white color - iprintf("Restore the selected save to the"); // Line is 32 chars - iprintf("inserted game card?\n"); // Line is 32 chars - iprintf("( yes, no)\n"); + font->clear(false); + font->print(0, 0, false, "Restore the selected save to the inserted game card?"); + font->print(0, 2, false, "( yes, no)\n"); + font->update(false); - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do u16 pressed; do { - // Move to right side of screen - iprintf ("\x1b[0;26H"); // Print time - iprintf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -191,9 +188,6 @@ void ndsCardSaveRestore(const char *filename) { } while (!(pressed & (KEY_A | KEY_B))); if(pressed & KEY_A) { - consoleSelect(&bottomConsole); - consoleClear(); - auxspi_extra card_type = auxspi_has_extra(); bool auxspi = card_type == AUXSPI_INFRARED; FILE *in = fopen(filename, "rb"); @@ -228,23 +222,20 @@ void ndsCardSaveRestore(const char *filename) { fseek(in, 0, SEEK_END); length = ftell(in); fseek(in, 0, SEEK_SET); - if(length != (auxspi ? (int)(LEN*num_blocks) : size)) { + if(length != (auxspi ? (int)(LEN * num_blocks) : size)) { fclose(in); - iprintf("\x1B[41m"); // Print foreground red color - iprintf("The size of this save doesn't\n"); - iprintf("match the size of the size of\n"); - iprintf("the inserted game card.\n\n"); - iprintf("Write cancelled!\n"); - iprintf("\x1B[47m"); // Print foreground white color - iprintf("( OK)\n"); - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color + const std::string_view sizeError = "The size of this save doesn't match the size of the inserted game card.\n\nWrite cancelled!"; + + font->clear(false); + font->print(0, 0, false, sizeError, Alignment::left, Palette::red); + font->print(0, font->calcHeight(sizeError) + 1, false, "( OK)"); + font->update(false); + do { - // Move to right side of screen - iprintf ("\x1b[0;26H"); // Print time - iprintf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -252,7 +243,13 @@ void ndsCardSaveRestore(const char *filename) { } while (!(pressed & KEY_A)); return; } - iprintf("Restoring save...\nDo not remove the NDS card.\n\n\n\n\n\n\nProgress:"); + + font->clear(false); + font->print(0, 0, false, "Restoring save..."); + font->print(0, 1, false, "Do not remove the NDS card."); + font->print(0, 4, false, "Progress:"); + font->update(false); + if(type == 3) { if(auxspi) auxspi_erase(card_type); @@ -261,9 +258,12 @@ void ndsCardSaveRestore(const char *filename) { } if(auxspi){ buffer = new unsigned char[LEN]; + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); for(unsigned int i = 0; i < num_blocks; i++) { - iprintf ("\x1b[9;0H"); - iprintf ("%d/%d Bytes", i * LEN, length); + font->print((i * (SCREEN_COLS - 2) / num_blocks) + 1, 5, false, "="); + font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", i * LEN, length); + font->update(false); fread(buffer, 1, LEN, in); auxspi_write_data(i << shift, buffer, LEN, type, card_type); @@ -272,9 +272,13 @@ void ndsCardSaveRestore(const char *filename) { int blocks = size / 32; int written = 0; buffer = new unsigned char[blocks]; + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); for(unsigned int i = 0; i < 32; i++) { - iprintf ("\x1b[9;0H"); - iprintf ("%d/%d Bytes", i * blocks, length); + font->print((i * (SCREEN_COLS - 2) / 32) + 1, 5, false, "="); + font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", i * LEN, length); + font->update(false); + fread(buffer, 1, blocks, in); cardWriteEeprom(written, buffer, blocks, type); written += blocks; @@ -287,8 +291,10 @@ void ndsCardSaveRestore(const char *filename) { } void dumpFailMsg(void) { - consoleClear(); - iprintf("Failed to dump the ROM.\n"); + font->clear(false); + font->print(0, 0, false, "Failed to dump the ROM."); + font->update(false); + for (int i = 0; i < 60*2; i++) { swiWaitForVBlank(); } @@ -298,49 +304,44 @@ void ndsCardDump(void) { int pressed = 0; //bool showGameCardMsgAgain = false; - consoleSelect(&bottomConsole); - consoleClear(); - iprintf("\x1B[47m"); // Print foreground white color - iprintf("Dump NDS card ROM to\n"); - iprintf("\"%s:/gm9i/out\"?\n", (sdMounted ? "sd" : "fat")); - iprintf("( yes, trim, no,\n"); - iprintf(" save only)"); + font->clear(false); + font->printf(0, 0, false, Alignment::left, Palette::white, "Dump NDS card ROM to\n\"%s:/gm9i/out\"?", sdMounted ? "sd:" : "fat:"); + font->print(0, 2, false, "( yes, trim, no, save only)"); + font->update(false); - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - iprintf ("\x1b[0;26H"); // Print time - iprintf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); swiWaitForVBlank(); } while (!(pressed & (KEY_A | KEY_Y | KEY_B | KEY_X))); - consoleSelect(&bottomConsole); - iprintf ("\x1B[47m"); // Print foreground white color - if (pressed & KEY_X) { - consoleClear(); char folderPath[2][256]; sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat")); sprintf(folderPath[1], "%s:/gm9i/out", (sdMounted ? "sd" : "fat")); if (access(folderPath[0], F_OK) != 0) { - iprintf("Creating directory..."); + font->clear(false); + font->print(0, 0, false, "Creating directory..."); + font->update(false); mkdir(folderPath[0], 0777); } if (access(folderPath[1], F_OK) != 0) { - iprintf("\x1b[0;0H"); - iprintf("Creating directory..."); + font->clear(false); + font->print(0, 0, false, "Creating directory..."); + font->update(false); mkdir(folderPath[1], 0777); } - consoleClear(); + if (cardInit(&ndsCardHeader) != 0) { - iprintf("Unable to dump the save.\n"); - for (int i = 0; i < 60*2; i++) { + font->clear(false); + font->print(0, 0, false, "Unable to dump the save."); + font->update(false); + for (int i = 0; i < 60 * 2; i++) { swiWaitForVBlank(); } return; @@ -354,44 +355,41 @@ void ndsCardDump(void) { ndsCardSaveDump(destSavPath); } else if ((pressed & KEY_A) || (pressed & KEY_Y)) { - consoleClear(); bool trimRom = (pressed & KEY_Y); char folderPath[2][256]; sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat")); sprintf(folderPath[1], "%s:/gm9i/out", (sdMounted ? "sd" : "fat")); if (access(folderPath[0], F_OK) != 0) { - iprintf("Creating directory..."); + font->clear(false); + font->print(0, 0, false, "Creating directory..."); + font->update(false); mkdir(folderPath[0], 0777); } if (access(folderPath[1], F_OK) != 0) { - iprintf("\x1b[0;0H"); - iprintf("Creating directory..."); + font->clear(false); + font->print(0, 0, false, "Creating directory..."); + font->update(false); mkdir(folderPath[1], 0777); } /*if (expansionPakFound) { - consoleClear(); - printf("Please switch to the\ngame card, then press A.\n"); + font->clear(false) + font->print(0, 0, false, "Please switch to the game card, then press A."); + font->update(false); //flashcardUnmount(); io_dldi_data->ioInterface.shutdown(); - - consoleSelect(&topConsole); - printf ("\x1B[30m"); // Print black color + // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - printf ("\x1b[0;26H"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); swiWaitForVBlank(); } while (!(pressed & KEY_A)); - - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color }*/ - consoleClear(); + int cardInited = cardInit(&ndsCardHeader); char gameTitle[13] = {0}; char gameCode[7] = {0}; @@ -426,10 +424,14 @@ void ndsCardDump(void) { sprintf(destSavPath, "%s:/gm9i/out/%s.sav", (sdMounted ? "sd" : "fat"), fileName); if (cardInited == 0) { - iprintf("%s.nds\nis dumping...\n", fileName); - iprintf("Do not remove the NDS card.\n"); + font->clear(false); + font->printf(0, 0, false, Alignment::left, Palette::white, "%s.nds\nis dumping...", fileName); + font->print(0, 2, false, "Do not remove the NDS card."); + font->update(false); } else { - iprintf("Unable to dump the ROM.\n"); + font->clear(false); + font->print(0, 0, false, "Unable to dump the ROM."); + font->update(false); for (int i = 0; i < 60*2; i++) { swiWaitForVBlank(); } @@ -498,14 +500,11 @@ void ndsCardDump(void) { //flashcardUnmount(); io_dldi_data->ioInterface.shutdown(); - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - iprintf ("\x1b[0;26H"); // Print time - iprintf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -521,12 +520,9 @@ void ndsCardDump(void) { // Read from game card for (src = src; src < currentSize; src += 0x200) { - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color - // Move to right side of screen - iprintf ("\x1b[0;26H"); // Print time - iprintf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); consoleSelect(&bottomConsole); iprintf ("\x1B[47m"); // Print foreground white color @@ -537,14 +533,11 @@ void ndsCardDump(void) { } iprintf("\x1b[15;0H"); iprintf("Please switch to the\nflashcard, then press A.\n"); - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - printf ("\x1b[0;26H"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -568,12 +561,9 @@ void ndsCardDump(void) { destinationFileOpened = true; } for (writeSrc = writeSrc; writeSrc < currentSize; writeSrc += 0x200) { - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color - // Move to right side of screen - iprintf ("\x1b[0;26H"); // Print time - iprintf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); consoleSelect(&bottomConsole); printf ("\x1B[47m"); // Print foreground white color @@ -590,19 +580,19 @@ void ndsCardDump(void) { u32 currentSize = romSize; FILE* destinationFile = fopen(destPath, "wb"); if (destinationFile) { - for (u32 src = 0; src < romSize; src += 0x8000) { - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color - // Move to right side of screen - iprintf ("\x1b[0;26H"); - // Print time - iprintf (" %s" ,RetTime().c_str()); - consoleSelect(&bottomConsole); - iprintf ("\x1B[47m"); // Print foreground white color - iprintf ("\x1b[8;0H"); - iprintf ("Progress:\n"); - iprintf ("%i/%i Bytes", (int)src, (int)romSize); + font->print(0, 4, false, "Progress:"); + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); + for (u32 src = 0; src < romSize; src += 0x8000) { + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + + font->print((src / (romSize / (SCREEN_COLS - 2))) + 1, 5, false, "="); + font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", src, romSize); + font->update(false); + for (u32 i = 0; i < 0x8000; i += 0x200) { cardRead (src+i, copyBuf+i); } @@ -639,21 +629,16 @@ void readChange(void) { void gbaCartDump(void) { int pressed = 0; - consoleSelect(&bottomConsole); - consoleClear(); - iprintf("\x1B[47m"); // Print foreground white color - iprintf("Dump GBA cart ROM to\n"); - iprintf("\"fat:/gm9i/out\"?\n"); - iprintf("( yes, no)"); + font->clear(false); + font->printf(0, 0, false, Alignment::left, Palette::white, "Dump GBA cart ROM to\n\"%s:/gm9i/out\"?", sdMounted ? "sd:" : "fat:"); + font->print(0, 2, false, "( yes, no)"); + font->update(false); - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - iprintf ("\x1b[0;26H"); // Print time - iprintf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -661,22 +646,23 @@ void gbaCartDump(void) { } while (!(pressed & KEY_A) && !(pressed & KEY_B)); if (pressed & KEY_A) { - iprintf ("\x1b[0;27H"); - iprintf (" "); // Clear time + // Clear time + font->print(-1, 0, true, " ", Alignment::right, Palette::blackGreen); + font->update(true); } - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color - if (pressed & KEY_A) { consoleClear(); if (access("fat:/gm9i", F_OK) != 0) { - iprintf("Creating directory..."); + font->clear(false); + font->print(0, 0, false, "Creating directory..."); + font->update(false); mkdir("fat:/gm9i", 0777); } if (access("fat:/gm9i/out", F_OK) != 0) { - iprintf ("\x1b[0;0H"); - iprintf("Creating directory..."); + font->clear(false); + font->print(0, 0, false, "Creating directory..."); + font->update(false); mkdir("fat:/gm9i/out", 0777); } char gbaHeaderGameTitle[13] = "\0"; @@ -716,9 +702,12 @@ void gbaCartDump(void) { char destSavPath[256] = {0}; sprintf(destPath, "fat:/gm9i/out/%s.gba", fileName); sprintf(destSavPath, "fat:/gm9i/out/%s.sav", fileName); - consoleClear(); - iprintf("%s.gba\nis dumping...\n", fileName); - iprintf("Do not remove the GBA cart.\n"); + + font->clear(false); + font->printf(0, 0, false, Alignment::left, Palette::white, "%s.gba\nis dumping...", fileName); + font->print(0, 2, false, "Do not remove the GBA cart."); + font->update(false); + // Determine ROM size u32 romSize = 0x02000000; for (u32 i = 0x09FE0000; i > 0x08000000; i -= 0x20000) { @@ -772,6 +761,7 @@ void gbaCartDump(void) { } else { dumpFailMsg(); } + // Save file remove(destSavPath); destinationFile = fopen(destSavPath, "wb"); diff --git a/arm9/source/fileOperations.cpp b/arm9/source/fileOperations.cpp index c8777bb..428c236 100644 --- a/arm9/source/fileOperations.cpp +++ b/arm9/source/fileOperations.cpp @@ -7,56 +7,38 @@ #include "date.h" #include "file_browse.h" +#include "font.h" #define copyBufSize 0x8000 #define shaChunkSize 0x10000 u32 copyBuf[copyBufSize]; -extern PrintConsole topConsole, bottomConsole; - std::vector clipboard; bool clipboardOn = false; bool clipboardUsed = false; -void printBytes(int bytes) -{ +std::string getBytes(int bytes) { + char buffer[11]; if (bytes == 1) - iprintf("%d Byte", bytes); + sniprintf(buffer, sizeof(buffer), "%d Byte", bytes); else if (bytes < 1024) - iprintf("%d Bytes", bytes); + sniprintf(buffer, sizeof(buffer), "%d Bytes", bytes); else if (bytes < (1024 * 1024)) - printf("%d KB", bytes / 1024); + sniprintf(buffer, sizeof(buffer), "%d KB", bytes / 1024); else if (bytes < (1024 * 1024 * 1024)) - printf("%d MB", bytes / 1024 / 1024); + sniprintf(buffer, sizeof(buffer), "%d MB", bytes / 1024 / 1024); else - printf("%d GB", bytes / 1024 / 1024 / 1024); + sniprintf(buffer, sizeof(buffer), "%d GB", bytes / 1024 / 1024 / 1024); + + return buffer; } -void printBytesAlign(int bytes) -{ - if (bytes == 1) - iprintf("%4d Byte", bytes); - - else if (bytes < 1024) - iprintf("%3d Bytes", bytes); - - else if (bytes < (1024 * 1024)) - printf("%6d KB", bytes / 1024); - - else if (bytes < (1024 * 1024 * 1024)) - printf("%6d MB", bytes / 1024 / 1024); - - else - printf("%6d GB", bytes / 1024 / 1024 / 1024); -} - -off_t getFileSize(const char *fileName) -{ +off_t getFileSize(const char *fileName) { FILE* fp = fopen(fileName, "rb"); off_t fsize = 0; if (fp) { @@ -69,17 +51,24 @@ off_t getFileSize(const char *fileName) return fsize; } -bool calculateSHA1(const char *fileName, u8 *sha1) -{ +bool calculateSHA1(const char *fileName, u8 *sha1) { off_t fsize = getFileSize(fileName); u8 *buf = (u8*) malloc(shaChunkSize); if (!buf) { - iprintf("Could not allocate buffer\n"); + font->clear(false); + font->print(0, 0, false, "Could not allocate buffer"); + font->update(false); + for(int i = 0; i < 60 * 2; i++) + swiWaitForVBlank(); return false; } FILE* fp = fopen(fileName, "rb"); if (!fp) { - iprintf("Could not open file for reading\n"); + font->clear(false); + font->print(0, 0, false, "Could not open file for reading"); + font->update(false); + for(int i = 0; i < 60 * 2; i++) + swiWaitForVBlank(); free(buf); return false; } @@ -87,6 +76,18 @@ bool calculateSHA1(const char *fileName, u8 *sha1) swiSHA1context_t ctx; ctx.sha_block=0; //this is weird but it has to be done swiSHA1Init(&ctx); + + font->clear(false); + font->print(0, 0, false, "Calculating SHA1 hash of:"); + font->print(0, 1, false, fileName); + + int nameHeight = font->calcHeight(fileName); + font->print(0, nameHeight + 2, false, "( to cancel)"); + + font->print(0, nameHeight + 4, false, "Progress:"); + font->print(0, nameHeight + 5, false, "["); + font->print(-1, nameHeight + 5, false, "]"); + while (true) { size_t ret = fread(buf, 1, shaChunkSize, fp); if (!ret) break; @@ -94,26 +95,27 @@ bool calculateSHA1(const char *fileName, u8 *sha1) scanKeys(); int keys = keysHeld(); if (keys & KEY_START) return false; - iprintf("\x1b[1;A"); - iprintf("%ld/%lld bytes processed\n", ftell(fp), fsize); + + font->print((ftell(fp) / (fsize / (SCREEN_COLS - 2))) + 1, nameHeight + 5, false, "="); + font->printf(0, nameHeight + 6, false, Alignment::left, Palette::white, "%d/%d bytes processed", ftell(fp), fsize); + font->update(false); } swiSHA1Final(sha1, &ctx); free(buf); return true; } -void dirCopy(DirEntry* entry, int i, const char *destinationPath, const char *sourcePath) { +void dirCopy(const DirEntry &entry, int i, const char *destinationPath, const char *sourcePath) { std::vector dirContents; dirContents.clear(); - if (entry->isDirectory) chdir((sourcePath + ("/" + entry->name)).c_str()); + if (entry.isDirectory) chdir((sourcePath + ("/" + entry.name)).c_str()); getDirectoryContents(dirContents); - if (((int)dirContents.size()) == 1) mkdir((destinationPath + ("/" + entry->name)).c_str(), 0777); - if (((int)dirContents.size()) != 1) fcopy((sourcePath + ("/" + entry->name)).c_str(), (destinationPath + ("/" + entry->name)).c_str()); + if (((int)dirContents.size()) == 1) mkdir((destinationPath + ("/" + entry.name)).c_str(), 0777); + if (((int)dirContents.size()) != 1) fcopy((sourcePath + ("/" + entry.name)).c_str(), (destinationPath + ("/" + entry.name)).c_str()); } -int fcopy(const char *sourcePath, const char *destinationPath) -{ - DIR *isDir = opendir (sourcePath); +int fcopy(const char *sourcePath, const char *destinationPath) { + DIR *isDir = opendir(sourcePath); if (isDir != NULL) { closedir(isDir); @@ -122,17 +124,15 @@ int fcopy(const char *sourcePath, const char *destinationPath) chdir(sourcePath); std::vector dirContents; getDirectoryContents(dirContents); - DirEntry* entry = NULL; mkdir(destinationPath, 0777); for (int i = 1; i < ((int)dirContents.size()); i++) { chdir(sourcePath); - entry = &dirContents.at(i); - dirCopy(entry, i, destinationPath, sourcePath); + dirCopy(dirContents[i], i, destinationPath, sourcePath); } - chdir (destinationPath); - chdir (".."); + chdir(destinationPath); + chdir(".."); return 1; } else { closedir(isDir); @@ -142,26 +142,26 @@ int fcopy(const char *sourcePath, const char *destinationPath) off_t fsize = 0; if (sourceFile) { fseek(sourceFile, 0, SEEK_END); - fsize = ftell(sourceFile); // Get source file's size + fsize = ftell(sourceFile); // Get source file's size fseek(sourceFile, 0, SEEK_SET); } else { - fclose(sourceFile); return -1; } FILE* destinationFile = fopen(destinationPath, "wb"); - //if (destinationFile) { - fseek(destinationFile, 0, SEEK_SET); - /*} else { + if (!destinationFile) { fclose(sourceFile); - fclose(destinationFile); return -1; - }*/ + } + + font->clear(false); + font->print(0, 0, false, "Progress:"); + font->print(0, 1, false, "["); + font->print(-1, 1, false, "]"); off_t offset = 0; int numr; - while (1) - { + while (1) { scanKeys(); if (keysHeld() & KEY_B) { // Cancel copying @@ -170,18 +170,14 @@ int fcopy(const char *sourcePath, const char *destinationPath) return -1; break; } - consoleSelect(&topConsole); - printf ("\x1B[30m"); // Print black color - // Move to right side of screen - printf ("\x1b[0;26H"); - // Print time - printf (" %s" ,RetTime().c_str()); - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color - printf ("\x1b[16;0H"); - printf ("Progress:\n"); - printf ("%i/%i Bytes ", (int)offset, (int)fsize); + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + + font->print((offset / (fsize / (SCREEN_COLS - 2))) + 1, 1, false, "="); + font->printf(0, 2, false, Alignment::left, Palette::white, "%lld/%lld Bytes", offset, fsize); + font->update(false); // Copy file to destination path numr = fread(copyBuf, 1, copyBufSize, sourceFile); @@ -192,10 +188,6 @@ int fcopy(const char *sourcePath, const char *destinationPath) fclose(sourceFile); fclose(destinationFile); - printf ("\x1b[17;0H"); - printf ("%i/%i Bytes ", (int)fsize, (int)fsize); - for (int i = 0; i < 30; i++) swiWaitForVBlank(); - return 1; break; } @@ -205,62 +197,29 @@ int fcopy(const char *sourcePath, const char *destinationPath) } } -void changeFileAttribs(DirEntry* entry) { - consoleClear(); +void changeFileAttribs(const DirEntry *entry) { int pressed = 0; - int cursorScreenPos = 0; + int cursorScreenPos = font->calcHeight(entry->name); uint8_t currentAttribs = FAT_getAttr(entry->name.c_str()); uint8_t newAttribs = currentAttribs; - // Position cursor, depending on how long the file name is - for (int i = 0; i < 256; i++) { - if (i == 33 || i == 65 || i == 97 || i == 129 || i == 161 || i == 193 || i == 225) { - cursorScreenPos++; - } - if (entry->name.c_str()[i] == '\0') { - break; - } - } - - printf ("\x1b[0;0H"); - printf (entry->name.c_str()); - if (!entry->isDirectory) { - printf ("\x1b[%i;0H", 3+cursorScreenPos); - printf ("filesize: "); - printBytes(entry->size); - } - printf ("\x1b[%i;0H", 5+cursorScreenPos); - printf ("[ ] U read-only [ ] D hidden"); - printf ("\x1b[%i;0H", 6+cursorScreenPos); - printf ("[ ] R system [ ] L archive"); - printf ("\x1b[%i;0H", 7+cursorScreenPos); - printf ("[ ] virtual"); - printf ("\x1b[%i;1H", 7+cursorScreenPos); - printf ((newAttribs & ATTR_VOLUME) ? "X" : " "); - printf ("\x1b[%i;0H", 9+cursorScreenPos); - printf ("(UDRL to change attributes)"); while (1) { - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color - printf ("\x1b[%i;1H", 5+cursorScreenPos); - printf ((newAttribs & ATTR_READONLY) ? "X" : " "); - printf ("\x1b[%i;18H", 5+cursorScreenPos); - printf ((newAttribs & ATTR_HIDDEN) ? "X" : " "); - printf ("\x1b[%i;1H", 6+cursorScreenPos); - printf ((newAttribs & ATTR_SYSTEM) ? "X" : " "); - printf ("\x1b[%i;18H", 6+cursorScreenPos); - printf ((newAttribs & ATTR_ARCHIVE) ? "X" : " "); - printf ("\x1b[%i;0H", 11+cursorScreenPos); - printf ((currentAttribs==newAttribs) ? "( to continue) " : "( to apply, to cancel)"); + font->clear(false); + font->print(0, 0, false, entry->name); + if (!entry->isDirectory) + font->printf(0, cursorScreenPos + 1, false, Alignment::left, Palette::white, "filesize: %s", getBytes(entry->size).c_str()); + font->printf(0, cursorScreenPos + 3, false, Alignment::left, Palette::white, "[%c] ↑ read-only [%c] ↓ hidden", (newAttribs & ATTR_READONLY) ? 'X' : ' ', (newAttribs & ATTR_HIDDEN) ? 'X' : ' '); + font->printf(0, cursorScreenPos + 4, false, Alignment::left, Palette::white, "[%c] → system [%c] ← archive", (newAttribs & ATTR_SYSTEM) ? 'X' : ' ', (newAttribs & ATTR_ARCHIVE) ? 'X' : ' '); + font->printf(0, cursorScreenPos + 5, false, Alignment::left, Palette::white, "[%c] virtual", (newAttribs & ATTR_VOLUME) ? 'X' : ' '); + font->printf(0, cursorScreenPos + 6, false, Alignment::left, Palette::white, "(↑↓→← to change attributes)"); + font->print(0, cursorScreenPos + 8, false, (currentAttribs == newAttribs) ? "( to continue)" : "( to apply, to cancel)"); + font->update(false); - consoleSelect(&topConsole); - printf ("\x1B[30m"); // Print black color // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - printf ("\x1b[0;26H"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDown(); @@ -269,43 +228,17 @@ void changeFileAttribs(DirEntry* entry) { && !(pressed & KEY_A) && !(pressed & KEY_B)); if (pressed & KEY_UP) { - if (newAttribs & ATTR_READONLY) { - newAttribs -= ATTR_READONLY; - } else { - newAttribs += ATTR_READONLY; - } - } - - if (pressed & KEY_DOWN) { - if (newAttribs & ATTR_HIDDEN) { - newAttribs -= ATTR_HIDDEN; - } else { - newAttribs += ATTR_HIDDEN; - } - } - - if (pressed & KEY_RIGHT) { - if (newAttribs & ATTR_SYSTEM) { - newAttribs -= ATTR_SYSTEM; - } else { - newAttribs += ATTR_SYSTEM; - } - } - - if (pressed & KEY_LEFT) { - if (newAttribs & ATTR_ARCHIVE) { - newAttribs -= ATTR_ARCHIVE; - } else { - newAttribs += ATTR_ARCHIVE; - } - } - - if ((pressed & KEY_A) && (currentAttribs!=newAttribs)) { + newAttribs ^= ATTR_READONLY; + } else if (pressed & KEY_DOWN) { + newAttribs ^= ATTR_HIDDEN; + } else if (pressed & KEY_RIGHT) { + newAttribs ^= ATTR_SYSTEM; + } else if (pressed & KEY_LEFT) { + newAttribs ^= ATTR_ARCHIVE; + } else if ((pressed & KEY_A) && (currentAttribs != newAttribs)) { FAT_setAttr(entry->name.c_str(), newAttribs); break; - } - - if ((pressed & KEY_A) || (pressed & KEY_B)) { + } else if (pressed & (KEY_A | KEY_B)) { break; } } diff --git a/arm9/source/fileOperations.h b/arm9/source/fileOperations.h index a7e0931..c07647f 100644 --- a/arm9/source/fileOperations.h +++ b/arm9/source/fileOperations.h @@ -19,12 +19,11 @@ extern std::vector clipboard; extern bool clipboardOn; extern bool clipboardUsed; -extern void printBytes(int bytes); -extern void printBytesAlign(int bytes); +extern std::string getBytes(int bytes); extern off_t getFileSize(const char *fileName); extern bool calculateSHA1(const char *fileName, u8 *sha1); extern int fcopy(const char *sourcePath, const char *destinationPath); -void changeFileAttribs(DirEntry* entry); +void changeFileAttribs(const DirEntry *entry); #endif // FILE_COPY diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index 9a2171b..7333d3f 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -38,24 +38,16 @@ #include "driveMenu.h" #include "driveOperations.h" #include "dumpOperations.h" +#include "font.h" #include "hexEditor.h" #include "ndsInfo.h" #include "nitrofs.h" #include "inifile.h" #include "nds_loader_arm9.h" -#define SCREEN_COLS 22 -#define ENTRIES_PER_SCREEN 23 #define ENTRIES_START_ROW 1 #define OPTIONS_ENTRIES_START_ROW 2 #define ENTRY_PAGE_LENGTH 10 -extern PrintConsole topConsole, bottomConsole; - -extern void printBorderTop(void); -extern void printBorderBottom(void); -extern void clearBorderTop(void); -extern void clearBorderBottom(void); -extern void reinitConsoles(void); static char path[PATH_MAX]; @@ -84,7 +76,7 @@ bool dirEntryPredicate (const DirEntry& lhs, const DirEntry& rhs) { return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; } -void getDirectoryContents (std::vector& dirContents) { +void getDirectoryContents(std::vector& dirContents) { struct stat st; dirContents.clear(); @@ -92,7 +84,8 @@ void getDirectoryContents (std::vector& dirContents) { DIR *pdir = opendir ("."); if (pdir == NULL) { - iprintf ("Unable to open the directory.\n"); + font->print(0, 0, true, "Unable to open the directory."); + font->update(true); } else { while(true) { @@ -138,145 +131,150 @@ void getDirectoryContents (std::vector& dirContents) { void showDirectoryContents (const std::vector& dirContents, int fileOffset, int startRow) { getcwd(path, PATH_MAX); - consoleClear(); + font->clear(true); + + // Top bar + font->printf(0, 0, true, Alignment::left, Palette::blackGreen, "%*c", 256 / font->width(), ' '); + + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); // Print the path - printf ("\x1B[30m"); // Print black color - // Print time - printf ("\x1b[0;27H"); - printf (RetTime().c_str()); - - printf ("\x1b[0;0H"); - if (strlen(path) < SCREEN_COLS) { - iprintf ("%s", path); - } else { - iprintf ("%s", path + strlen(path) - SCREEN_COLS); - } - - // Move to 2nd row - iprintf ("\x1b[1;0H"); + if(font->calcWidth(path) > SCREEN_COLS - 6) + font->print(-6 - 1, 0, true, path, Alignment::right, Palette::blackGreen); + else + font->print(0, 0, true, path, Alignment::left, Palette::blackGreen); // Print directory listing for (int i = 0; i < ((int)dirContents.size() - startRow) && i < ENTRIES_PER_SCREEN; i++) { - const DirEntry* entry = &dirContents.at(i + startRow); + const DirEntry *entry = &dirContents[i + startRow]; - // Set row - iprintf ("\x1b[%d;0H", i + ENTRIES_START_ROW); + Palette pal; if ((fileOffset - startRow) == i) { - printf ("\x1B[47m"); // Print foreground white color + pal = Palette::white; } else if (entry->selected) { - printf ("\x1B[33m"); // Print custom yellow color + pal = Palette::yellow; } else if (entry->isDirectory) { - printf ("\x1B[37m"); // Print custom blue color + pal = Palette::blue; } else { - printf ("\x1B[40m"); // Print foreground black color + pal = Palette::gray; } - printf ("%.*s", SCREEN_COLS, entry->name.c_str()); + font->print(0, i + 1, true, entry->name, Alignment::left, pal); if (entry->name == "..") { - printf ("\x1b[%d;28H", i + ENTRIES_START_ROW); - printf ("(..)"); + font->print(-1, i + 1, true, "(..)", Alignment::right, pal); } else if (entry->isDirectory) { - printf ("\x1b[%d;27H", i + ENTRIES_START_ROW); - printf ("(dir)"); + font->print(-1, i + 1, true, "(dir)", Alignment::right, pal); } else { - printf ("\x1b[%d;23H", i + ENTRIES_START_ROW); - printBytesAlign((int)entry->size); + font->printf(-1, i + 1, true, Alignment::right, pal, "(%s)", getBytes(entry->size).c_str()); } } - printf ("\x1B[47m"); // Print foreground white color + font->update(true); } FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { int pressed = 0; - FileOperation assignedOp[4] = {FileOperation::none}; + std::vector operations; int optionOffset = 0; - int cursorScreenPos = 0; - int maxCursors = -1; + std::string fullPath = path + entry->name; + int y = font->calcHeight(fullPath) + 1; - consoleSelect(&bottomConsole); - consoleClear(); - printf ("\x1B[47m"); // Print foreground white color - char fullPath[256]; - snprintf(fullPath, sizeof(fullPath), "%s%s", path, entry->name.c_str()); - printf(fullPath); - // Position cursor, depending on how long the full file path is - for (int i = 0; i < 256; i++) { - if (i == 33 || i == 65 || i == 97 || i == 129 || i == 161 || i == 193 || i == 225) { - cursorScreenPos++; - } - if (fullPath[i] == '\0') { - break; - } - } - iprintf ("\x1b[%d;0H", cursorScreenPos + OPTIONS_ENTRIES_START_ROW); if (!entry->isDirectory) { if (entry->isApp) { - assignedOp[++maxCursors] = FileOperation::bootFile; - if (extension(entry->name, {"firm"})) { - printf(" Boot file\n"); - } else { - printf(" Boot file (Direct)\n"); - assignedOp[++maxCursors] = FileOperation::bootstrapFile; - printf(" Bootstrap file\n"); + operations.push_back(FileOperation::bootFile); + if (!extension(entry->name, {"firm"})) { + operations.push_back(FileOperation::bootstrapFile); } } - if(extension(entry->name, {"nds", "dsi", "ids", "app"})) - { - assignedOp[++maxCursors] = FileOperation::mountNitroFS; - printf(" Mount NitroFS\n"); - assignedOp[++maxCursors] = FileOperation::ndsInfo; - printf(" Show NDS file info\n"); - } - else if(extension(entry->name, {"sav", "sav1", "sav2", "sav3", "sav4", "sav5", "sav6", "sav7", "sav8", "sav9"})) - { - assignedOp[++maxCursors] = FileOperation::restoreSave; - printf(" Restore save\n"); - } - else if(extension(entry->name, {"img", "sd"})) - { - assignedOp[++maxCursors] = FileOperation::mountImg; - printf(" Mount as FAT image\n"); - } - assignedOp[++maxCursors] = FileOperation::hexEdit; - printf(" Open in hex editor\n"); - } - assignedOp[++maxCursors] = FileOperation::showInfo; - printf(entry->isDirectory ? " Show directory info\n" : " Show file info\n"); - if (sdMounted && (strcmp(path, "sd:/gm9i/out/") != 0)) { - assignedOp[++maxCursors] = FileOperation::copySdOut; - printf(" Copy to sd:/gm9i/out\n"); - } - if (flashcardMounted && (strcmp(path, "fat:/gm9i/out/") != 0)) { - assignedOp[++maxCursors] = FileOperation::copyFatOut; - printf(" Copy to fat:/gm9i/out\n"); - } - // The bios SHA1 functions are only available on the DSi - // https://problemkaputt.de/gbatek.htm#biossha1functionsdsionly - if (isDSiMode()) { - assignedOp[++maxCursors] = FileOperation::calculateSHA1; - printf(" Calculate SHA1 hash\n"); - } - printf("\n( select, cancel)"); - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color - while (true) { - // Clear old cursors - for (int i = OPTIONS_ENTRIES_START_ROW+cursorScreenPos; i < (maxCursors+1) + OPTIONS_ENTRIES_START_ROW+cursorScreenPos; i++) { - iprintf ("\x1b[%d;0H ", i); - } - // Show cursor - iprintf ("\x1b[%d;0H->", optionOffset + OPTIONS_ENTRIES_START_ROW+cursorScreenPos); - consoleSelect(&topConsole); - printf ("\x1B[30m"); // Print black color for time text + if(extension(entry->name, {"nds", "dsi", "ids", "app"})) { + operations.push_back(FileOperation::mountNitroFS); + operations.push_back(FileOperation::ndsInfo); + } else if(extension(entry->name, {"sav", "sav1", "sav2", "sav3", "sav4", "sav5", "sav6", "sav7", "sav8", "sav9"})) { + operations.push_back(FileOperation::restoreSave); + } else if(extension(entry->name, {"img", "sd"})) { + operations.push_back(FileOperation::mountImg); + } + + operations.push_back(FileOperation::hexEdit); + + // The bios SHA1 functions are only available on the DSi + // https://problemkaputt.de/gbatek.htm#biossha1functionsdsionly + if (isDSiMode()) { + operations.push_back(FileOperation::calculateSHA1); + } + } + + operations.push_back(FileOperation::showInfo); + + if (sdMounted && (strcmp(path, "sd:/gm9i/out/") != 0)) { + operations.push_back(FileOperation::copySdOut); + } + + if (flashcardMounted && (strcmp(path, "fat:/gm9i/out/") != 0)) { + operations.push_back(FileOperation::copyFatOut); + } + + while (true) { + font->clear(false); + + font->print(0, 0, false, fullPath); + + int row = y; + for(FileOperation operation : operations) { + switch(operation) { + case FileOperation::bootFile: + font->print(3, row++, false, extension(entry->name, {"firm"}) ? "Boot file" : "Boot file (Direct)"); + break; + case FileOperation::bootstrapFile: + font->print(3, row++, false, "Bootstrap file"); + break; + case FileOperation::mountNitroFS: + font->print(3, row++, false, "Mount NitroFS"); + break; + case FileOperation::ndsInfo: + font->print(3, row++, false, "Show NDS file info"); + break; + case FileOperation::restoreSave: + font->print(3, row++, false, "Restore save"); + break; + case FileOperation::mountImg: + font->print(3, row++, false, "Mount as FAT image"); + break; + case FileOperation::hexEdit: + font->print(3, row++, false, "Open in hex editor"); + break; + case FileOperation::showInfo: + font->print(3, row++, false, entry->isDirectory ? "Show directory info" : "Show file info"); + break; + case FileOperation::copySdOut: + font->print(3, row++, false, "Copy to sd:/gm9i/out"); + break; + case FileOperation::copyFatOut: + font->print(3, row++, false, "Copy to fat:/gm9i/out"); + break; + case FileOperation::calculateSHA1: + font->print(3, row++, false, "Calculate SHA1 hash"); + break; + case FileOperation::none: + row++; + break; + } + } + + font->print(3, ++row, false, "( select, cancel)"); + + // Show cursor + font->print(0, y + optionOffset, false, "->"); + + 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 { - // Move to right side of screen - printf ("\x1b[0;26H"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -288,26 +286,26 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { #endif ); - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color - 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 (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(assignedOp[optionOffset]) { + switch(operations[optionOffset]) { case FileOperation::bootFile: { applaunch = true; - iprintf ("\x1b[%d;3H", optionOffset + OPTIONS_ENTRIES_START_ROW+cursorScreenPos); - printf("Now loading..."); + font->print(3, optionOffset + y, false, "Now loading..."); + font->update(false); break; } case FileOperation::bootstrapFile: { char baseFile[256], savePath[PATH_MAX]; //, bootstrapConfigPath[32]; //snprintf(bootstrapConfigPath, 32, "%s:/_nds/nds-bootstrap.ini", isDSiMode() ? "sd" : "fat"); - strncpy(baseFile, entry->name.c_str(), 256); + strncpy(baseFile, entry->name.c_str(), 255); *strrchr(baseFile, '.') = 0; snprintf(savePath, PATH_MAX, "%s%s%s.sav", path, !access("saves", F_OK) ? "saves/" : "", baseFile); CIniFile bootstrapConfig("/_nds/nds-bootstrap.ini"); @@ -327,19 +325,19 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { break; } case FileOperation::copySdOut: { if (access("sd:/gm9i", F_OK) != 0) { - iprintf ("\x1b[%d;3H", optionOffset + OPTIONS_ENTRIES_START_ROW+cursorScreenPos); - printf("Creating directory..."); + font->print(3, optionOffset + y, false, "Creating directory..."); + font->update(false); mkdir("sd:/gm9i", 0777); } if (access("sd:/gm9i/out", F_OK) != 0) { - iprintf ("\x1b[%d;3H", optionOffset + OPTIONS_ENTRIES_START_ROW+cursorScreenPos); - printf("Creating directory..."); + font->print(3, optionOffset + y, false, "Creating directory..."); + font->update(false); mkdir("sd:/gm9i/out", 0777); } char destPath[256]; snprintf(destPath, sizeof(destPath), "sd:/gm9i/out/%s", entry->name.c_str()); - iprintf ("\x1b[%d;3H", optionOffset + OPTIONS_ENTRIES_START_ROW+cursorScreenPos); - printf("Copying... "); + font->print(3, optionOffset + y, false, "Copying..."); + font->update(false); remove(destPath); char sourceFolder[PATH_MAX]; getcwd(sourceFolder, PATH_MAX); @@ -350,19 +348,19 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { break; } case FileOperation::copyFatOut: { if (access("fat:/gm9i", F_OK) != 0) { - iprintf ("\x1b[%d;3H", optionOffset + OPTIONS_ENTRIES_START_ROW+cursorScreenPos); - printf("Creating directory..."); + font->print(3, optionOffset + y, false, "Creating directory..."); + font->update(false); mkdir("fat:/gm9i", 0777); } if (access("fat:/gm9i/out", F_OK) != 0) { - iprintf ("\x1b[%d;3H", optionOffset + OPTIONS_ENTRIES_START_ROW+cursorScreenPos); - printf("Creating directory..."); + font->print(3, optionOffset + y, false, "Creating directory..."); + font->update(false); mkdir("fat:/gm9i/out", 0777); } char destPath[256]; snprintf(destPath, sizeof(destPath), "fat:/gm9i/out/%s", entry->name.c_str()); - iprintf ("\x1b[%d;3H", optionOffset + OPTIONS_ENTRIES_START_ROW+cursorScreenPos); - printf("Copying... "); + font->print(3, (optionOffset + y), false, "Copying..."); + font->update(false); remove(destPath); char sourceFolder[PATH_MAX]; getcwd(sourceFolder, PATH_MAX); @@ -397,23 +395,27 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { hexEditor(entry->name.c_str(), currentDrive); break; } case FileOperation::calculateSHA1: { - iprintf("\x1b[2J"); - iprintf("Calculating SHA1 hash of:\n%s\n", entry->name.c_str()); - iprintf("Press to cancel\n\n"); u8 sha1[20] = {0}; bool ret = calculateSHA1(strcat(getcwd(path, PATH_MAX), entry->name.c_str()), sha1); - if (!ret) break; - iprintf("SHA1 hash is: \n"); - for (int i = 0; i < 20; ++i) iprintf("%02X", sha1[i]); - consoleSelect(&topConsole); - iprintf ("\x1B[30m"); // Print black color + if (!ret) + break; + + font->clear(false); + font->print(0, 0, false, "SHA1 hash is:"); + char sha1Str[41]; + for (int i = 0; i < 20; ++i) + sniprintf(sha1Str + i * 2, 3, "%02X", sha1[i]); + font->print(0, 1, false, sha1Str); + font->print(0, font->calcHeight(sha1Str) + 2, false, "( to continue)"); + 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 { - // Move to right side of screen - iprintf ("\x1b[0;26H"); // Print time - iprintf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + scanKeys(); pressed = keysDownRepeat(); swiWaitForVBlank(); @@ -423,7 +425,7 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { break; } } - return assignedOp[optionOffset]; + return operations[optionOffset]; } if (pressed & KEY_B) { return FileOperation::none; @@ -441,43 +443,33 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { bool fileBrowse_paste(char dest[256]) { int pressed = 0; int optionOffset = 0; - int maxCursors = -1; - consoleSelect(&bottomConsole); - consoleClear(); - printf ("\x1B[47m"); // Print foreground white color - printf("Paste clipboard here?"); - printf("\n\n"); - iprintf ("\x1b[%d;0H", OPTIONS_ENTRIES_START_ROW); - maxCursors++; - printf(" Copy files\n"); - for (auto &file : clipboard) { - if (file.nitro) - continue; - maxCursors++; - printf(" Move files\n"); - break; - } - printf("\n"); - printf("( select, cancel)"); - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color while (true) { - // Clear old cursors - for (int i = OPTIONS_ENTRIES_START_ROW; i < (maxCursors+1) + OPTIONS_ENTRIES_START_ROW; i++) { - iprintf ("\x1b[%d;0H ", i); - } - // Show cursor - iprintf ("\x1b[%d;0H->", optionOffset + OPTIONS_ENTRIES_START_ROW); + font->clear(false); + + font->print(0, 0, false, "Paste clipboard here?"); + + int row = OPTIONS_ENTRIES_START_ROW, maxCursors = 0; + font->print(3, row++, false, "Copy files"); + for (auto &file : clipboard) { + if (file.nitro) + continue; + maxCursors++; + font->print(3, row++, false, "Move files"); + break; + } + font->print(3, ++row, false, "( select, cancel)"); + + // Show cursor + font->print(0, optionOffset + OPTIONS_ENTRIES_START_ROW, false, "->"); + + font->update(false); - consoleSelect(&topConsole); - printf ("\x1B[30m"); // Print black color for time text // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - printf ("\x1b[0;26H"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -489,10 +481,6 @@ bool fileBrowse_paste(char dest[256]) { #endif ); - - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color - if (pressed & KEY_UP) optionOffset -= 1; if (pressed & KEY_DOWN) optionOffset += 1; @@ -500,8 +488,7 @@ bool fileBrowse_paste(char dest[256]) { if (optionOffset > maxCursors) optionOffset = 0; // Wrap around to top of list if (pressed & KEY_A) { - iprintf ("\x1b[%d;3H", optionOffset + OPTIONS_ENTRIES_START_ROW); - printf(optionOffset ? "Moving... " : "Copying..."); + font->print(3, optionOffset + OPTIONS_ENTRIES_START_ROW, false, optionOffset ? "Moving... " : "Copying..."); for (auto &file : clipboard) { std::string destPath = dest + file.name; if (file.path == destPath) @@ -537,14 +524,14 @@ bool fileBrowse_paste(char dest[256]) { } void recRemove(const char *path, std::vector dirContents) { - DirEntry *entry = NULL; chdir (path); getDirectoryContents(dirContents); for (int i = 1; i < ((int)dirContents.size()); i++) { - entry = &dirContents.at(i); - if (entry->isDirectory) recRemove(entry->name.c_str(), dirContents); - if (!(FAT_getAttr(entry->name.c_str()) & ATTR_READONLY)) { - remove(entry->name.c_str()); + 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 (".."); @@ -552,51 +539,52 @@ void recRemove(const char *path, std::vector dirContents) { } void fileBrowse_drawBottomScreen(DirEntry* entry) { - consoleClear(); - printf ("\x1B[47m"); // Print foreground white color - printf ("\x1b[22;0H"); - printf ("%s\n", titleName); - printf ("X - DELETE/[+R] RENAME file\n"); - printf ("L - %s files (with \x18\x19\x1A\x1B)\n", entry->selected ? "DESELECT" : "SELECT"); - printf ("Y - %s file/[+R] CREATE entry%s", clipboardOn ? "PASTE" : "COPY", clipboardOn ? "" : "\n"); - printf ("R+A - Directory options\n"); - if (sdMounted || flashcardMounted) { - printf ("%s\n", SCREENSHOTTEXT); - } - printf ("%s\n", clipboardOn ? "SELECT - Clear Clipboard" : "SELECT - Restore Clipboard"); + font->clear(false); + + int row = -1; + if (!isDSiMode() && isRegularDS) { - printf (POWERTEXT_DS); + font->print(0, row--, false, POWERTEXT_DS); } else if (is3DS) { - printf ("%s\n%s", POWERTEXT_3DS, HOMETEXT); + font->print(0, row--, false, HOMETEXT); + font->print(0, row--, false, POWERTEXT_3DS); } else { - printf (POWERTEXT); + font->print(0, row--, false, POWERTEXT); } - printf (entry->selected ? "\x1B[33m" : (entry->isDirectory ? "\x1B[37m" : "\x1B[40m")); // Print custom blue color or foreground black color - printf ("\x1b[0;0H"); - printf ("%s\n", entry->name.c_str()); + font->print(0, row--, false, clipboardOn ? "SELECT - Clear Clipboard" : "SELECT - Restore Clipboard"); + if (sdMounted || flashcardMounted) { + font->print(0, row--, false, SCREENSHOTTEXT); + } + font->print(0, row--, false, "R+A - Directory options\n"); + font->printf(0, row--, false, Alignment::left, Palette::white, "Y - %s file/[+R] CREATE entry", clipboardOn ? "PASTE" : "COPY"); + font->printf(0, row--, false, Alignment::left, Palette::white, "L - %s files (with ↑↓→←)\n", entry->selected ? "DESELECT" : "SELECT"); + font->print(0, row--, false, "X - DELETE/[+R] RENAME file\n"); + font->print(0, row--, false, titleName); + + Palette pal = entry->selected ? Palette::yellow : (entry->isDirectory ? Palette::blue : Palette::gray); + font->print(0, 0, false, entry->name, Alignment::left, pal); if (entry->name != "..") { if (entry->isDirectory) { - printf ("(dir)"); + font->print(0, font->calcHeight(entry->name), false, "(dir)", Alignment::left, pal); } else if (entry->size == 1) { - printf ("%i Byte", (int)entry->size); + font->printf(0, font->calcHeight(entry->name), false, Alignment::left, pal, "%i Byte", entry->size); } else { - printf ("%i Bytes", (int)entry->size); + font->printf(0, font->calcHeight(entry->name), false, Alignment::left, pal, "%i Bytes", entry->size); } } if (clipboardOn) { - printf ("\x1b[9;0H"); - printf ("\x1B[47m"); // Print foreground white color - printf ("[CLIPBOARD]\n"); + font->print(0, 6, false, "[CLIPBOARD]"); for (size_t i = 0; i < clipboard.size(); ++i) { - printf (clipboard[i].folder ? "\x1B[37m" : "\x1B[40m"); // Print custom blue color or foreground black color if (i < 4) { - printf ("%s\n", clipboard[i].name.c_str()); + font->print(0, 7 + i, false, clipboard[i].name, Alignment::left, clipboard[i].folder ? Palette::blue : Palette::gray); } else { - printf ("%d more files...\n", clipboard.size() - 4); + font->printf(0, 7 + i, false, Alignment::left, Palette::gray, "%d more files...", clipboard.size() - 4); break; } } } + + font->update(false); } std::string browseForFile (void) { @@ -609,23 +597,18 @@ std::string browseForFile (void) { getDirectoryContents (dirContents); while (true) { - DirEntry* entry = &dirContents.at(fileOffset); + DirEntry* entry = &dirContents[fileOffset]; - consoleSelect(&bottomConsole); fileBrowse_drawBottomScreen(entry); - consoleSelect(&topConsole); - showDirectoryContents (dirContents, fileOffset, screenOffset); + showDirectoryContents(dirContents, fileOffset, screenOffset); stored_SCFG_MC = REG_SCFG_MC; - printf ("\x1B[30m"); // Print black color for time text - // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do do { - // Move to right side of screen - printf ("\x1b[0;26H"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -639,16 +622,7 @@ std::string browseForFile (void) { if ((held & KEY_R) && (pressed & KEY_L)) { break; } - } while (!(pressed & KEY_UP) && !(pressed & KEY_DOWN) && !(pressed & KEY_LEFT) && !(pressed & KEY_RIGHT) - && !(pressed & KEY_A) && !(pressed & KEY_B) && !(pressed & KEY_X) && !(pressed & KEY_Y) - && !(pressed & KEY_L) && !(pressed & KEY_SELECT) -#ifdef SCREENSWAP - && !(pressed & KEY_TOUCH) -#endif - ); - - printf ("\x1B[47m"); // Print foreground white color - iprintf ("\x1b[%d;0H", fileOffset - screenOffset + ENTRIES_START_ROW); + } while (!pressed); if (isDSiMode() && !pressed && currentDrive == 1 && REG_SCFG_MC == 0x11 && flashcardMounted) { flashcardUnmount(); @@ -692,10 +666,11 @@ std::string browseForFile (void) { screenMode = 0; return "null"; } else if (entry->isDirectory) { - iprintf("Entering directory "); + font->printf(0, fileOffset - screenOffset + ENTRIES_START_ROW, true, Alignment::left, Palette::white, "%-*s", SCREEN_COLS - 5, "Entering directory"); + font->update(true); // Enter selected directory chdir (entry->name.c_str()); - getDirectoryContents (dirContents); + getDirectoryContents(dirContents); screenOffset = 0; fileOffset = 0; } else { @@ -748,8 +723,10 @@ std::string browseForFile (void) { // Rename file/folder if ((held & KEY_R) && (pressed & KEY_X) && (entry->name != ".." && strncmp(path, "nitro:/", 7) != 0)) { - printf ("\x1b[0;27H"); - printf (" "); // Clear time + // Clear time + font->print(-1, 0, true, " ", Alignment::right, Palette::blackGreen); + font->update(true); + pressed = 0; consoleDemoInit(); Keyboard *kbd = keyboardDemoInit(); @@ -757,13 +734,13 @@ std::string browseForFile (void) { kbd->OnKeyPressed = OnKeyPressed; keyboardShow(); - printf("Rename to: \n"); + iprintf("Rename to:\n"); fgets(newName, 256, stdin); newName[strlen(newName)-1] = 0; keyboardHide(); - consoleClear(); - reinitConsoles(); + videoSetModeSub(MODE_5_2D); + bgShow(bgInitSub(2, BgType_Bmp8, BgSize_B8_256x256, 3, 0)); if (newName[0] != '\0') { // Check for unsupported characters @@ -782,49 +759,44 @@ std::string browseForFile (void) { } } if (rename(entry->name.c_str(), newName) == 0) { - getDirectoryContents (dirContents); + getDirectoryContents(dirContents); } } } // Delete action if ((pressed & KEY_X) && (entry->name != ".." && strncmp(path, "nitro:/", 7) != 0)) { - consoleSelect(&bottomConsole); - consoleClear(); - printf ("\x1B[47m"); // Print foreground white color + font->clear(false); int selections = std::count_if(dirContents.begin(), dirContents.end(), [](const DirEntry &x){ return x.selected; }); if (entry->selected && selections > 1) { - iprintf("Delete %d paths?\n", selections); + font->printf(0, 0, false, Alignment::left, Palette::white, "Delete %d paths?", selections); for (uint i = 0, printed = 0; i < dirContents.size() && printed < 5; i++) { if (dirContents[i].selected) { - iprintf("\x1B[41m- %s\n", dirContents[i].name.c_str()); + font->printf(0, printed + 2, false, Alignment::left, Palette::red, "- %s", dirContents[i].name.c_str()); printed++; } } if(selections > 5) - iprintf("\x1B[41m- and %d more...\n", selections - 5); - iprintf("\x1B[47m"); + font->printf(0, 7, false, Alignment::left, Palette::red, "- and %d more...", selections - 5); } else { - iprintf("Delete \"%s\"?\n", entry->name.c_str()); + font->printf(0, 0, false, Alignment::left, Palette::white, "Delete \"%s\"?", entry->name.c_str()); } - printf ("( yes, no)"); - consoleSelect(&topConsole); - printf ("\x1B[30m"); // Print black color for time text + font->print(0, (!entry->selected || selections == 1) ? 2 : (selections > 5 ? 9 : selections + 3), false, "( yes, no)"); + font->update(false); + while (true) { - // Move to right side of screen - printf ("\x1b[0;26H"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); swiWaitForVBlank(); if (pressed & KEY_A) { - consoleSelect(&bottomConsole); - consoleClear(); - printf ("\x1B[47m"); // Print foreground white color if (entry->selected) { - printf ("Deleting files, please wait..."); + font->clear(false); + font->print(0, 0, false, "Deleting files, please wait..."); + font->update(false); struct stat st; for (auto &item : dirContents) { if(item.selected) { @@ -839,19 +811,16 @@ std::string browseForFile (void) { } fileOffset = 0; } else if (FAT_getAttr(entry->name.c_str()) & ATTR_READONLY) { - printf ("Failed deleting:\n"); - printf (entry->name.c_str()); - printf ("\n"); - printf ("\n"); - printf ("( to continue)"); + font->clear(false); + font->print(0, 0, false, "Failed deleting:"); + font->print(0, 1, false, entry->name); + font->print(0, 3, false, "( to continue)"); pressed = 0; - consoleSelect(&topConsole); - printf ("\x1B[30m"); // Print black color for time text + while (!(pressed & KEY_A)) { - // Move to right side of screen - printf ("\x1b[0;26H"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDown(); @@ -860,10 +829,14 @@ std::string browseForFile (void) { for (int i = 0; i < 15; i++) swiWaitForVBlank(); } else { if (entry->isDirectory) { - printf ("Deleting folder, please wait..."); + font->clear(false); + font->print(0, 0, false, "Deleting folder, please wait..."); + font->update(false); recRemove(entry->name.c_str(), dirContents); } else { - printf ("Deleting file, please wait..."); + font->clear(false); + font->print(0, 0, false, "Deleting folder, please wait..."); + font->update(false); remove(entry->name.c_str()); } fileOffset--; @@ -881,8 +854,10 @@ std::string browseForFile (void) { // Create new folder if ((held & KEY_R) && (pressed & KEY_Y) && (strncmp(path, "nitro:/", 7) != 0)) { - printf ("\x1b[0;27H"); - printf (" "); // Clear time + // Clear time + font->print(-1, 0, true, " ", Alignment::right, Palette::blackGreen); + font->update(true); + pressed = 0; consoleDemoInit(); Keyboard *kbd = keyboardDemoInit(); @@ -890,13 +865,13 @@ std::string browseForFile (void) { kbd->OnKeyPressed = OnKeyPressed; keyboardShow(); - printf("Name for new folder: \n"); + iprintf("Name for new folder:\n"); fgets(newName, 256, stdin); newName[strlen(newName)-1] = 0; keyboardHide(); - consoleClear(); - reinitConsoles(); + videoSetModeSub(MODE_5_2D); + bgShow(bgInitSub(2, BgType_Bmp8, BgSize_B8_256x256, 3, 0)); if (newName[0] != '\0') { // Check for unsupported characters @@ -921,17 +896,14 @@ std::string browseForFile (void) { } // Add to selection - if (pressed & KEY_L && entry->name != "..") { + if ((pressed & KEY_L && !(held & KEY_R)) && entry->name != "..") { bool select = !entry->selected; entry->selected = select; while(held & KEY_L) { do { - // Move to right side of screen - printf ("\x1b[0;26H"); - // Print black color for time text - printf ("\x1B[30m"); // Print time - printf (" %s" ,RetTime().c_str()); + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); scanKeys(); pressed = keysDownRepeat(); @@ -980,10 +952,8 @@ std::string browseForFile (void) { } } - consoleSelect(&bottomConsole); fileBrowse_drawBottomScreen(entry); - consoleSelect(&topConsole); - showDirectoryContents (dirContents, fileOffset, screenOffset); + showDirectoryContents(dirContents, fileOffset, screenOffset); } } @@ -1025,48 +995,8 @@ std::string browseForFile (void) { // Make a screenshot if ((held & KEY_R) && (pressed & KEY_L)) { - if (sdMounted || flashcardMounted) { - if (access((sdMounted ? "sd:/gm9i" : "fat:/gm9i"), F_OK) != 0) { - mkdir((sdMounted ? "sd:/gm9i" : "fat:/gm9i"), 0777); - if (strcmp(path, (sdMounted ? "sd:/" : "fat:/")) == 0) { - getDirectoryContents (dirContents); - } - } - if (access((sdMounted ? "sd:/gm9i/out" : "fat:/gm9i/out"), F_OK) != 0) { - mkdir((sdMounted ? "sd:/gm9i/out" : "fat:/gm9i/out"), 0777); - if (strcmp(path, (sdMounted ? "sd:/gm9i/" : "fat:/gm9i/")) == 0) { - getDirectoryContents (dirContents); - } - } - char timeText[8]; - snprintf(timeText, sizeof(timeText), "%s", RetTime().c_str()); - char fileTimeText[8]; - snprintf(fileTimeText, sizeof(fileTimeText), "%s", RetTimeForFilename().c_str()); - char snapPath[40]; - // Take top screenshot - snprintf(snapPath, sizeof(snapPath), "%s:/gm9i/out/snap_%s_top.bmp", (sdMounted ? "sd" : "fat"), fileTimeText); - screenshotbmp(snapPath); - // Seamlessly swap top and bottom screens - lcdMainOnBottom(); - printBorderBottom(); - consoleSelect(&bottomConsole); - showDirectoryContents (dirContents, fileOffset, screenOffset); - printf("\x1B[30m"); // Print black color for time text - printf ("\x1b[0;26H"); - printf (" %s" ,timeText); - clearBorderTop(); - consoleSelect(&topConsole); - fileBrowse_drawBottomScreen(entry); - // Take bottom screenshot - snprintf(snapPath, sizeof(snapPath), "%s:/gm9i/out/snap_%s_bot.bmp", (sdMounted ? "sd" : "fat"), fileTimeText); - screenshotbmp(snapPath); - if (strcmp(path, (sdMounted ? "sd:/gm9i/out/" : "fat:/gm9i/out/")) == 0) { - getDirectoryContents (dirContents); - } - lcdMainOnTop(); - printBorderTop(); - clearBorderBottom(); - } + if(screenshot()) + getDirectoryContents(dirContents); } } } diff --git a/arm9/source/font.cpp b/arm9/source/font.cpp new file mode 100644 index 0000000..a44e790 --- /dev/null +++ b/arm9/source/font.cpp @@ -0,0 +1,369 @@ +#include "font.h" + +#include "font_default_frf.h" +#include "tonccpy.h" + +#include +#include +#include +#include +u8 Font::textBuf[2][256 * 192]; +bool Font::mainScreen = false; + +Font *font = nullptr; + +bool Font::isStrongRTL(char16_t c) { + return (c >= 0x0590 && c <= 0x05FF) || c == 0x200F; +} + +bool Font::isWeak(char16_t c) { + return c < 'A' || (c > 'Z' && c < 'a') || (c > 'z' && c < 127); +} + +bool Font::isNumber(char16_t c) { + return c >= '0' && c <= '9'; +} + +Font::Font(const char *path) { + FILE *file = fopen(path, "rb"); + + const u8 *fileBuffer = font_default_frf; + if(file) { + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + + fileBuffer = new u8[size]; + if(!fileBuffer) { + fclose(file); + return; + } + + fseek(file, 0, SEEK_SET); + fread((void *)fileBuffer, 1, size, file); + } + const u8 *ptr = fileBuffer; + + // Check header magic, then skip over + if(memcmp(ptr, "RIFF", 4) != 0) + goto cleanup; + + ptr += 8; + + // check for and load META section + if(memcmp(ptr, "META", 4) == 0) { + tileWidth = ptr[8]; + tileHeight = ptr[9]; + tonccpy(&tileCount, ptr + 10, sizeof(u16)); + + if(tileWidth > TILE_MAX_WIDTH || tileHeight > TILE_MAX_HEIGHT) + goto cleanup; + + u32 section_size; + tonccpy(§ion_size, ptr + 4, sizeof(u32)); + ptr += 8 + section_size; + } else { + goto cleanup; + } + + // Character data + if(memcmp(ptr, "CDAT", 4) == 0) { + fontTiles = new u8[tileHeight * tileCount]; + if(!fontTiles) + goto cleanup; + + tonccpy(fontTiles, ptr + 8, tileHeight * tileCount); + + u32 section_size; + tonccpy(§ion_size, ptr + 4, sizeof(u32)); + ptr += 8 + section_size; + } else { + goto cleanup; + } + + // character map + if(memcmp(ptr, "CMAP", 4) == 0) { + fontMap = new u16[tileCount]; + if(!fontMap) + goto cleanup; + + tonccpy(fontMap, ptr + 8, sizeof(u16) * tileCount); + + u32 section_size; + tonccpy(§ion_size, ptr + 4, sizeof(u32)); + ptr += 8 + section_size; + } else { + goto cleanup; + } + + questionMark = getCharIndex('?'); + + // Copy palette to VRAM + for(uint i = 0; i < sizeof(palette) / sizeof(palette[0]); i++) { + tonccpy(BG_PALETTE + i * 0x10, palette[i], 4); + tonccpy(BG_PALETTE_SUB + i * 0x10, palette[i], 4); + } + +cleanup: + if(fileBuffer != font_default_frf) + delete[] fileBuffer; +} + +Font::~Font(void) { + if(fontTiles) + delete[] fontTiles; + + if(fontMap) + delete[] fontMap; +} + +u16 Font::getCharIndex(char16_t c) { + // Try a binary search + int left = 0; + int right = tileCount; + + while(left <= right) { + int mid = left + ((right - left) / 2); + if(fontMap[mid] == c) { + return mid; + } + + if(fontMap[mid] < c) { + left = mid + 1; + } else { + right = mid - 1; + } + } + + return questionMark; +} + +std::u16string Font::utf8to16(std::string_view text) { + std::u16string out; + for(uint i = 0; i < text.size();) { + char16_t c; + if(!(text[i] & 0x80)) { + c = text[i++]; + } else if((text[i] & 0xE0) == 0xC0) { + c = (text[i++] & 0x1F) << 6; + c |= text[i++] & 0x3F; + } else if((text[i] & 0xF0) == 0xE0) { + c = (text[i++] & 0x0F) << 12; + c |= (text[i++] & 0x3F) << 6; + c |= text[i++] & 0x3F; + } else { + i++; // out of range or something (This only does up to U+FFFF since it goes to a U16 anyways) + } + out += c; + } + return out; +} + +int Font::calcHeight(std::u16string_view text, int xPos) { + int lines = 1, chars = xPos + 1; + for(char16_t c : text) { + if(c == '\n' || chars > 256 / tileWidth) { + nocashMessage(c == '\n' ? "line" : "cha"); + lines++; + chars = xPos + 1; + } else { + chars++; + } + } + return lines; +} + +void Font::printf(int xPos, int yPos, bool top, Alignment align, Palette palette, const char *format, ...) { + char str[0x100]; + va_list va; + va_start(va, format); + vsniprintf(str, 0x100, format, va); + va_end(va); + + print(xPos, yPos, top, str, align, palette); +} + +ITCM_CODE void Font::print(int xPos, int yPos, bool top, std::u16string_view text, Alignment align, Palette palette, bool rtl) { + int x = xPos * tileWidth, y = yPos * tileHeight; + if(x < 0 && align != Alignment::center) + x += 255; + if(y < 0) + y += 191; + + // If RTL isn't forced, check for RTL text + if(!rtl) { + for(const auto c : text) { + if(isStrongRTL(c)) { + rtl = true; + break; + } + } + } + auto ltrBegin = text.end(), ltrEnd = text.end(); + + // Adjust x for alignment + switch(align) { + case Alignment::left: { + break; + } case Alignment::center: { + size_t newline = text.find('\n'); + while(newline != text.npos) { + print(xPos, yPos, top, text.substr(0, newline), align, palette, rtl); + text = text.substr(newline + 1); + newline = text.find('\n'); + yPos++; + y += tileHeight; + } + + x = ((256 - (text.length() * tileWidth)) / 2) + x; + break; + } case Alignment::right: { + size_t newline = text.find('\n'); + while(newline != text.npos) { + print(xPos, yPos, top, text.substr(0, newline), Alignment::left, palette, rtl); + text = text.substr(newline + 1); + newline = text.find('\n'); + yPos++; + y += tileHeight; + } + break; + } + } + + if(align == Alignment::right) + x -= (text.length() - 1) * tileWidth; + + // Align to grid + x -= x % tileWidth; + y -= y % tileHeight; + x += (256 % tileWidth) / 2; + y += (192 % tileHeight) / 2; + + const int xStart = x; + + // Loop through string and print it + for(auto it = (rtl ? text.end() - 1 : text.begin()); true; it += (rtl ? -1 : 1)) { + // If we hit the end of the string in an LTR section of an RTL + // string, it may not be done, if so jump back to printing RTL + if(it == (rtl ? text.begin() - 1 : text.end())) { + if(ltrBegin == text.end() || (ltrBegin == text.begin() && ltrEnd == text.end())) { + break; + } else { + it = ltrBegin; + ltrBegin = text.end(); + rtl = true; + } + } + + // If at the end of an LTR section within RTL, jump back to the RTL + if(it == ltrEnd && ltrBegin != text.end()) { + if(ltrBegin == text.begin() && (!isWeak(*ltrBegin) || isNumber(*ltrBegin))) + break; + + it = ltrBegin; + ltrBegin = text.end(); + rtl = true; + // If in RTL and hit a non-RTL character that's not punctuation, switch to LTR + } else if(rtl && !isStrongRTL(*it) && (!isWeak(*it) || isNumber(*it))) { + // Save where we are as the end of the LTR section + ltrEnd = it + 1; + + // Go back until an RTL character or the start of the string + bool allNumbers = true; + while(!isStrongRTL(*it) && it != text.begin()) { + // Check for if the LTR section is only numbers, + // if so they won't be removed from the end + if(allNumbers && !isNumber(*it) && !isWeak(*it)) + allNumbers = false; + it--; + } + + // Save where we are to return to after printing the LTR section + ltrBegin = it; + + // If on an RTL char right now, add one + if(isStrongRTL(*it)) { + it++; + } + + // Remove all punctuation and, if the section isn't only numbers, + // numbers from the end of the LTR section + if(allNumbers) { + while(isWeak(*it) && !isNumber(*it)) { + if(it != text.begin()) + ltrBegin++; + it++; + } + } else { + while(isWeak(*it)) { + if(it != text.begin()) + ltrBegin++; + it++; + } + } + + // But then allow all numbers directly touching the strong LTR or with 1 weak between + while((it - 1 >= text.begin() && isNumber(*(it - 1))) || (it - 2 >= text.begin() && isWeak(*(it - 1)) && isNumber(*(it - 2)))) { + if(it - 1 != text.begin()) + ltrBegin--; + it--; + } + + rtl = false; + } + + if(*it == '\n') { + x = xStart; + y += tileHeight; + continue; + } + + // Brackets are flipped in RTL + u16 index; + if(rtl) { + switch(*it) { + case '(': + index = getCharIndex(')'); + break; + case ')': + index = getCharIndex('('); + break; + case '[': + index = getCharIndex(']'); + break; + case ']': + index = getCharIndex('['); + break; + case '<': + index = getCharIndex('>'); + break; + case '>': + index = getCharIndex('<'); + break; + default: + index = getCharIndex(*it); + break; + } + } else { + index = getCharIndex(*it); + } + + // Wrap at right edge if not center aligning + if(x + tileWidth > 256 && align != Alignment::center) { + x = xStart; + y += tileHeight; + } + + // Don't draw off screen chars + if(x >= 0 && y >= 0 && y + tileHeight <= 192) { + u8 *dst = textBuf[top] + x; + for(int i = 0; i < tileHeight; i++) { + u8 px = fontTiles[(index * tileHeight) + i]; + for(int j = 0; j < tileWidth; j++) { + dst[(y + i) * 256 + j] = u8(palette) * 0x10 + ((px >> (7 - j)) & 1); + } + } + } + + x += tileWidth; + } +} diff --git a/arm9/source/font.h b/arm9/source/font.h new file mode 100644 index 0000000..a9962e6 --- /dev/null +++ b/arm9/source/font.h @@ -0,0 +1,94 @@ +#ifndef FONT_H +#define FONT_H + +#include "tonccpy.h" + +#include +#include +#include +#include + +#define TILE_MAX_WIDTH 8 +#define TILE_MAX_HEIGHT 10 + +#define SCREEN_COLS (256 / font->width()) +#define ENTRIES_PER_SCREEN ((192 - font->height()) / font->height()) + +enum class Alignment { + left, + center, + right, +}; + +enum class Palette : u8 { + white = 0, + gray, + red, + green, + greenAlt, + blue, + yellow, + blackRed, + blackGreen, + blackBlue, +}; + +class Font { + static bool isStrongRTL(char16_t c); + static bool isWeak(char16_t c); + static bool isNumber(char16_t c); + + static constexpr u16 palette[16][2] = { + {0x0000, 0x7FFF}, // White + {0x0000, 0x3DEF}, // Gray + {0x0000, 0x001F}, // Red + {0x0000, 0x03E0}, // Green + {0x0000, 0x02E0}, // Green (alt) + {0x0000, 0x656A}, // Blue + {0x0000, 0x3339}, // Yellow + {0x001F, 0x0000}, // Black on red + {0x03E0, 0x0000}, // Black on green + {0x656A, 0x0000}, // Black on blue + }; + + static u8 textBuf[2][256 * 192]; + static bool mainScreen; + + u8 tileWidth = 0, tileHeight = 0; + u16 tileCount = 0; + u16 questionMark = 0; + u8 *fontTiles = nullptr; + u16 *fontMap = nullptr; + + u16 getCharIndex(char16_t c); +public: + static std::u16string utf8to16(std::string_view text); + + static void update(bool top) { tonccpy(bgGetGfxPtr(top ? 2 : 6), Font::textBuf[top ^ mainScreen], 256 * 192); } + static void clear(bool top) { dmaFillWords(0, Font::textBuf[top ^ mainScreen], 256 * 192); } + + static void mainOnTop(bool top) { mainScreen = !top; } + + Font(const char *path); + + ~Font(void); + + u8 width(void) { return tileWidth; } + u8 height(void) { return tileHeight; } + + int calcWidth(std::string_view text) { return utf8to16(text).length(); } + int calcWidth(std::u16string_view text) { return text.length(); }; + + int calcHeight(std::string_view text, int xPos = 0) { return calcHeight(utf8to16(text)); } + int calcHeight(std::u16string_view text, int xPos = 0); + + void printf(int xPos, int yPos, bool top, Alignment align, Palette palette, const char *format, ...); + + void print(int xPos, int yPos, bool top, int value, Alignment align = Alignment::left, Palette palette = Palette::white) { print(xPos, yPos, top, std::to_string(value), align, palette); } + void print(int xPos, int yPos, bool top, std::string_view text, Alignment align = Alignment::left, Palette palette = Palette::white) { print(xPos, yPos, top, utf8to16(text), align, palette); } + void print(int xPos, int yPos, bool top, std::u16string_view text, Alignment align = Alignment::left, Palette palette = Palette::white, bool rtl = false); +}; + +extern Font *font; + +#endif // FONT_H \ No newline at end of file diff --git a/arm9/source/hexEditor.cpp b/arm9/source/hexEditor.cpp index 2a1c823..82813ee 100644 --- a/arm9/source/hexEditor.cpp +++ b/arm9/source/hexEditor.cpp @@ -1,41 +1,37 @@ #include "hexEditor.h" #include "date.h" -#include "tonccpy.h" #include "file_browse.h" +#include "font.h" +#include "tonccpy.h" #include #include #include -extern PrintConsole bottomConsole, bottomConsoleBG, topConsole; - -extern void reinitConsoles(void); - u32 jumpToOffset(u32 offset) { - consoleSelect(&bottomConsoleBG); - consoleClear(); - consoleSelect(&bottomConsole); - consoleClear(); - u8 cursorPosition = 0; u16 pressed = 0, held = 0; while(1) { - printf("\x1B[9;6H\x1B[47m-------------------"); - printf("\x1B[10;8H\x1B[47mJump to Offset"); - printf("\x1B[12;11H\x1B[37m%08lX", offset); - printf("\x1B[12;%dH\x1B[41m%lX", 17 - cursorPosition, (offset >> ((cursorPosition + 1) * 4)) & 0xF); - printf("\x1B[13;6H\x1B[47m-------------------"); + int y = (ENTRIES_PER_SCREEN - 4) / 2; + font->clear(false); + font->print(0, y, false, "--------------------", Alignment::center); + font->print(0, y + 1, false, "Jump to Offset", Alignment::center); + font->printf(0, y + 3, false, Alignment::center, Palette::blue, "%08lX", offset); + font->printf(3 - cursorPosition, y + 3, false, Alignment::center, Palette::red, "%lX", (offset >> ((cursorPosition + 1) * 4)) & 0xF); + font->print(0, y + 4, false, "--------------------", Alignment::center); + font->update(false); - consoleSelect(&topConsole); do { swiWaitForVBlank(); scanKeys(); pressed = keysDown(); held = keysDownRepeat(); - printf("\x1B[30m\x1B[0;26H %s", RetTime().c_str()); // Print time + + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); } while(!held); - consoleSelect(&bottomConsole); if(held & KEY_UP) { offset = (offset & ~(0xF0 << cursorPosition * 4)) | ((offset + (0x10 << (cursorPosition * 4))) & (0xF0 << cursorPosition * 4)); @@ -54,28 +50,27 @@ u32 jumpToOffset(u32 offset) { } u32 search(u32 offset, FILE *file) { - consoleSelect(&bottomConsoleBG); - consoleClear(); - consoleSelect(&bottomConsole); - consoleClear(); - u8 cursorPosition = 0; u16 pressed = 0, held = 0; while(1) { - printf("\x1B[9;4H\x1B[47m-----------------------"); - printf("\x1B[10;5H%c Search for String %c", cursorPosition == 0 ? '>' : ' ', cursorPosition == 0 ? '<' : ' '); - printf("\x1B[11;5H%c Search for Data %c", cursorPosition == 1 ? '>' : ' ', cursorPosition == 1 ? '<' : ' '); - printf("\x1B[12;4H-----------------------"); + int y = (ENTRIES_PER_SCREEN - 3) / 2; + font->clear(false); + font->print(0, y, false, "--------------------", Alignment::center); + font->printf(0, y + 1, false, Alignment::center, Palette::white, "%c Search for String %c", cursorPosition == 0 ? '>' : ' ', cursorPosition == 0 ? '<' : ' '); + font->printf(0, y + 2, false, Alignment::center, Palette::white, "%c Search for Data %c", cursorPosition == 1 ? '>' : ' ', cursorPosition == 1 ? '<' : ' '); + font->print(0, y + 3, false, "--------------------", Alignment::center); + font->update(false); - consoleSelect(&topConsole); do { swiWaitForVBlank(); scanKeys(); pressed = keysDown(); held = keysDownRepeat(); - printf("\x1B[30m\x1B[0;26H %s", RetTime().c_str()); // Print time + + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); } while(!held); - consoleSelect(&bottomConsole); if(held & (KEY_UP | KEY_DOWN)) { cursorPosition ^= 1; @@ -98,10 +93,9 @@ u32 search(u32 offset, FILE *file) { printf("Search for:\n"); fgets(str, sizeof(str), stdin); keyboardHide(); - consoleClear(); - reinitConsoles(); - consoleSelect(&bottomConsole); + videoSetModeSub(MODE_5_2D); + bgShow(bgInitSub(2, BgType_Bmp8, BgSize_B8_256x256, 3, 0)); BG_PALETTE_SUB[0x1F] = 0x9CF7; BG_PALETTE_SUB[0x2F] = 0xB710; @@ -114,27 +108,27 @@ u32 search(u32 offset, FILE *file) { str[strLen] = 0; // Remove ending \n that fgets has } else { - consoleClear(); - cursorPosition = 0; while(1) { - printf("\x1B[9;6H\x1B[47m------------------"); - printf("\x1B[10;9HEnter value:"); - u8 pos = 15 - strLen; - for(size_t i = 0; i < strLen * 2; i++) { - printf("\x1B[12;%dH\x1B[%dm%X", pos + i, (i == cursorPosition ? 31 : ((i / 2 % 2) ? 33 : 32)), str[i / 2] >> (!(i % 2) * 4) & 0xF); - } - printf("\x1B[13;6H\x1B[47m------------------"); + int y = (ENTRIES_PER_SCREEN - 4) / 2; + font->clear(false); + font->print(0, y, false, "--------------------", Alignment::center); + font->print(0, y + 1, false, "Enter value:", Alignment::center); + for(size_t i = 0; i < strLen * 2; i++) + font->printf(-strLen + i + 1, y + 3, false, Alignment::center, i == cursorPosition ? Palette::red : ((i / 2 % 2) ? Palette::greenAlt : Palette::green), "%X", str[i / 2] >> (!(i % 2) * 4) & 0xF); + font->print(0, y + 4, false, "--------------------", Alignment::center); + font->update(false); - consoleSelect(&topConsole); do { swiWaitForVBlank(); scanKeys(); pressed = keysDown(); held = keysDownRepeat(); - printf("\x1B[30m\x1B[0;26H %s", RetTime().c_str()); // Print time + + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); } while(!held); - consoleSelect(&bottomConsole); if(held & KEY_UP) { char val = str[cursorPosition / 2]; @@ -164,17 +158,20 @@ u32 search(u32 offset, FILE *file) { strLen--; if(cursorPosition > strLen * 2 - 1) cursorPosition -= 2; - consoleClear(); } } } } - consoleClear(); - printf("\x1B[9;6H\x1B[47m---------------------"); - printf("\x1B[10;12HSearching"); - printf("\x1B[14;8HPress B to cancel"); - printf("\x1B[15;6H---------------------"); + int y = (ENTRIES_PER_SCREEN - 7) / 2; + font->clear(false); + font->print(0, y, false, "--------------------", Alignment::center); + font->print(0, y + 1, false, "Searching", Alignment::center); + font->print(0, y + 6, false, "Press B to cancel", Alignment::center); + font->print(0, y + 7, false, "--------------------", Alignment::center); + font->update(false); + + char progressBar[21] = "[ ]"; size_t len = 32 << 10, pos = offset; fseek(file, 0, SEEK_END); @@ -187,7 +184,10 @@ u32 search(u32 offset, FILE *file) { return offset; } - printf("\x1B[12;6H%10d/%d", pos, fileLen); + progressBar[pos / (fileLen / 18) + 1] = '='; + font->print(0, y + 3, false, progressBar, Alignment::center); + font->printf(0, y + 4, false, Alignment::center, Palette::white, "%d/%d", pos, fileLen); + font->update(false); if(fseek(file, pos, SEEK_SET) != 0) break; @@ -204,64 +204,53 @@ u32 search(u32 offset, FILE *file) { } while(len == 32 << 10); delete[] buf; - consoleClear(); - printf("\x1B[9;5H\x1B[47m---------------------"); - printf("\x1B[10;6HReached end of file"); - printf("\x1B[11;8Hwith no results"); - printf("\x1B[12;5H---------------------"); + y = (ENTRIES_PER_SCREEN - 3) / 2; + font->clear(false); + font->print(0, y, false, "--------------------", Alignment::center); + font->print(0, y + 1, false, "Reached end of file", Alignment::center); + font->print(0, y + 2, false, "with no results", Alignment::center); + font->print(0, y + 3, false, "--------------------", Alignment::center); + font->update(false); do { swiWaitForVBlank(); scanKeys(); - printf("\x1B[30m\x1B[0;26H %s", RetTime().c_str()); // Print time + + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); } while(!keysDown()); return offset; } void hexEditor(const char *path, int drive) { - // Custom palettes - BG_PALETTE_SUB[0x1F] = 0x9CF7; - BG_PALETTE_SUB[0x2F] = 0xB710; - BG_PALETTE_SUB[0x3F] = 0xAE8D; - BG_PALETTE_SUB[0x7F] = 0xEA2D; - FILE *file = fopen(path, drive < 4 ? "rb+" : "rb"); if(!file) return; - consoleClear(); - fseek(file, 0, SEEK_END); u32 fileSize = ftell(file); fseek(file, 0, SEEK_SET); - u8 maxLines = std::min(23lu, fileSize / 8 + (fileSize % 8 != 0)); + u8 maxLines = std::min((u32)ENTRIES_PER_SCREEN, fileSize / 8 + (fileSize % 8 != 0)); u32 maxSize = ((fileSize - 8 * maxLines) & ~7) + (fileSize & 7 ? 8 : 0); - u8 cursorPosition = 0, mode = 0; u16 pressed = 0, held = 0; - u32 offset = 0; + u32 offset = 0, cursorPosition = 0, mode = 0; char data[8 * maxLines]; fseek(file, offset, SEEK_SET); fread(data, 1, sizeof(data), file); while(1) { - consoleSelect(&bottomConsoleBG); - printf ("\x1B[0;0H\x1B[46m"); // Blue - for(int i = 0; i < 4; i++) - printf ("\2"); - printf ("\x1B[42m"); // Green - for(int i = 0; i < 32 - 4; i++) - printf ("\2"); + font->clear(false); - consoleSelect(&bottomConsole); + font->printf(4, 0, false, Alignment::left, Palette::blackGreen, "%*c", SCREEN_COLS - 4, ' '); + font->print(0, 0, false, "Hex Editor", Alignment::center, Palette::blackGreen); - printf("\x1B[0;11H\x1B[30mHex Editor"); - - printf("\x1B[0;0H\x1B[30m%04lX", offset >> 0x10); + font->printf(0, 0, false, Alignment::left, Palette::blackBlue, "%04lX", offset >> 0x10); if(mode < 2) { fseek(file, offset, SEEK_SET); @@ -269,36 +258,38 @@ void hexEditor(const char *path, int drive) { fread(data, 1, std::min((u32)sizeof(data), fileSize - offset), file); } - for(int i = 0; i < maxLines; i++) { - printf("\x1B[%d;0H\x1B[37m%04lX", i + 1, (offset + i * 8) & 0xFFFF); + for(u32 i = 0; i < maxLines; i++) { + font->printf(0, i + 1, false, Alignment::left, Palette::blue, "%04lX", (offset + i * 8) & 0xFFFF); for(int j = 0; j < 4; j++) - printf("\x1B[%d;%dH\x1B[%dm%02X", i + 1, 5 + (j * 2), (mode > 0 && i * 8 + j == cursorPosition) ? (mode > 1 ? 30 : 31) : (offset + i * 8 + j >= fileSize ? 38 : 32 + (j % 2)), data[i * 8 + j]); + font->printf(5 + (j * 2), i + 1, false, Alignment::left, (mode > 0 && i * 8 + j == cursorPosition) ? (mode > 1 ? Palette::blackRed : Palette::red) : (offset + i * 8 + j >= fileSize ? Palette::gray : (j % 2 ? Palette::greenAlt : Palette::green)), "%02X", data[i * 8 + j]); for(int j = 0; j < 4; j++) - printf("\x1B[%d;%dH\x1B[%dm%02X", i + 1, 14 + (j * 2), (mode > 0 && i * 8 + 4 + j == cursorPosition) ? (mode > 1 ? 30 : 31) : (offset + i * 8 + 4 + j >= fileSize ? 38 : 32 + (j % 2)), data[i * 8 + 4 + j]); + font->printf(14 + (j * 2), i + 1, false, Alignment::left, (mode > 0 && i * 8 + 4 + j == cursorPosition) ? (mode > 1 ? Palette::blackRed : Palette::red) : (offset + i * 8 + 4 + j >= fileSize ? Palette::gray : (j % 2 ? Palette::greenAlt : Palette::green)), "%02X", data[i * 8 + 4 + j]); char line[9] = {0}; for(int j = 0; j < 8; j++) { char c = data[i * 8 + j]; if(c < ' ' || c > 127) - line[j] = ' '; + line[j] = '.'; else line[j] = c; } - printf("\x1B[%d;23H\x1B[47m%.8s", i + 1, line); + font->print(23, i + 1, false, line); if(mode > 0 && cursorPosition / 8 == i) { - printf("\x1B[%d;%dH\x1B[%dm%c", i + 1, 23 + cursorPosition % 8, mode > 1 ? 30 : 31, line[cursorPosition % 8]); + font->printf(23 + cursorPosition % 8, i + 1, false, Alignment::left, mode > 1 ? Palette::blackRed : Palette::red, "%c", line[cursorPosition % 8]); } } + font->update(false); - consoleSelect(&topConsole); do { swiWaitForVBlank(); scanKeys(); pressed = keysDown(); held = keysDownRepeat(); - printf("\x1B[30m\x1B[0;26H %s", RetTime().c_str()); // Print time + + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); } while(!held); - consoleSelect(&bottomConsole); if(mode == 0) { if(keysHeld() & KEY_R && held & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT)) { @@ -323,49 +314,42 @@ void hexEditor(const char *path, int drive) { offset = std::min(offset + 8 * maxLines, maxSize); } else if(pressed & KEY_A) { mode = 1; + cursorPosition = std::min(cursorPosition, fileSize - offset - 1); } else if(pressed & KEY_B) { break; } else if(pressed & KEY_X) { offset = std::min(search(offset, file), maxSize); - consoleClear(); } else if(pressed & KEY_Y) { offset = std::min(jumpToOffset(offset), maxSize); - consoleClear(); } } else if(mode == 1) { if(held & KEY_UP) { if(cursorPosition >= 8) cursorPosition -= 8; - else if(offset > 8) + else if(offset >= 8) offset -= 8; } else if(held & KEY_DOWN) { - if(cursorPosition < 8 * 22) + if(cursorPosition < 8u * (maxLines - 1)) cursorPosition += 8; else if(offset < fileSize - 8 * maxLines && fileSize > 8 * maxLines) offset += 8; - cursorPosition = std::min(cursorPosition, (u8)(fileSize - offset - 1)); + cursorPosition = std::min(cursorPosition, fileSize - offset - 1); } else if(held & KEY_LEFT) { if(cursorPosition > 0) cursorPosition--; } else if(held & KEY_RIGHT) { - if(cursorPosition < 8 * maxLines - 1) - cursorPosition = std::min((u8)(cursorPosition + 1), (u8)(fileSize - offset - 1)); + if(cursorPosition < 8u * maxLines - 1) + cursorPosition = std::min(cursorPosition + 1, fileSize - offset - 1); } else if(pressed & KEY_A) { if(drive < 4) { mode = 2; - consoleSelect(&bottomConsoleBG); - printf("\x1B[%d;%dH\x1B[%dm\2\2", 1 + cursorPosition / 8, 5 + (cursorPosition % 8 * 2) + (cursorPosition % 8 / 4), 31); - printf("\x1B[%d;%dH\x1B[%dm\2", 1 + cursorPosition / 8, 23 + cursorPosition % 8, 31); - consoleSelect(&bottomConsole); } } else if(pressed & KEY_B) { mode = 0; } else if(pressed & KEY_X) { offset = std::min(search(offset, file), maxSize); - consoleClear(); } else if(pressed & KEY_Y) { offset = std::min(jumpToOffset(offset), maxSize); - consoleClear(); } } else if(mode == 2) { if(held & KEY_UP) { @@ -380,23 +364,9 @@ void hexEditor(const char *path, int drive) { mode = 1; fseek(file, offset + cursorPosition, SEEK_SET); fwrite(data + cursorPosition, 1, 1, file); - - consoleSelect(&bottomConsoleBG); - printf("\x1B[%d;%dH\x1B[%dm\2\2", 1 + cursorPosition / 8, 5 + (cursorPosition % 8 * 2) + (cursorPosition % 8 / 4), 30); - printf("\x1B[%d;%dH\x1B[%dm\2", 1 + cursorPosition / 8, 23 + cursorPosition % 8, 30); - consoleSelect(&bottomConsole); } } } fclose(file); - - // Restore color palette - BG_PALETTE_SUB[0x1F] = 0x000F; - BG_PALETTE_SUB[0x2F] = 0x01E0; - BG_PALETTE_SUB[0x3F] = 0x3339; - BG_PALETTE_SUB[0x7F] = 0x656A; - - consoleSelect(&bottomConsoleBG); - consoleClear(); } diff --git a/arm9/source/main.cpp b/arm9/source/main.cpp index bbe0384..2af687b 100644 --- a/arm9/source/main.cpp +++ b/arm9/source/main.cpp @@ -34,6 +34,7 @@ #include "driveOperations.h" #include "file_browse.h" #include "fileOperations.h" +#include "font.h" #include "tonccpy.h" #include "version.h" @@ -57,8 +58,6 @@ bool applaunch = false; static int bg3; -PrintConsole topConsoleBG, topConsole, bottomConsoleBG, bottomConsole; - //--------------------------------------------------------------------------------- void stop (void) { //--------------------------------------------------------------------------------- @@ -69,60 +68,6 @@ void stop (void) { char filePath[PATH_MAX]; -void printBorderTop(void) { - consoleSelect(&topConsoleBG); - printf ("\x1B[42m"); // Print green color - for (int i = 0; i < 32; i++) { - printf ("\x02"); // Print top border - } -} - -void printBorderBottom(void) { - consoleSelect(&bottomConsoleBG); - printf ("\x1B[42m"); // Print green color - for (int i = 0; i < 32; i++) { - printf ("\x02"); // Print top border - } -} - -void clearBorderTop(void) { - consoleSelect(&topConsoleBG); - consoleClear(); -} - -void clearBorderBottom(void) { - consoleSelect(&bottomConsoleBG); - consoleClear(); -} - -void reinitConsoles(void) { - // Subscreen as a console - videoSetModeSub(MODE_0_2D); - vramSetBankH(VRAM_H_SUB_BG); - consoleInit(&bottomConsoleBG, 1, BgType_Text4bpp, BgSize_T_256x256, 7, 0, false, true); - consoleInit(&bottomConsole, 0, BgType_Text4bpp, BgSize_T_256x256, 15, 0, false, true); - - // Top screen as a console - videoSetMode(MODE_0_2D); - vramSetBankG(VRAM_G_MAIN_BG); - consoleInit(&topConsoleBG, 1, BgType_Text4bpp, BgSize_T_256x256, 7, 0, true, true); - consoleInit(&topConsole, 0, BgType_Text4bpp, BgSize_T_256x256, 15, 0, true, true); - - // Overwrite background white color - BG_PALETTE[15+(7*16)] = 0x656A; - BG_PALETTE_SUB[15+(7*16)] = 0x656A; - - // Custom yellow color - BG_PALETTE[15+(3*16)] = 0x3339; - BG_PALETTE_SUB[15+(3*16)] = 0x3339; - - // Overwrite 2nd smiley face with filled tile - dmaFillWords(0xFFFFFFFF, (void*)0x6000040, 8*8); // Top screen - dmaFillWords(0xFFFFFFFF, (void*)0x6200040, 8*8); // Bottom screen - - printBorderTop(); -} - //--------------------------------------------------------------------------------- int main(int argc, char **argv) { //--------------------------------------------------------------------------------- @@ -141,34 +86,29 @@ int main(int argc, char **argv) { sprintf(titleName, "GodMode9i %s", VER_NUMBER); // initialize video mode - videoSetMode(MODE_4_2D); + videoSetMode(MODE_5_2D); + videoSetModeSub(MODE_5_2D); // initialize VRAM banks vramSetPrimaryBanks(VRAM_A_MAIN_BG, VRAM_B_MAIN_SPRITE, - VRAM_C_LCD, + VRAM_C_SUB_BG, VRAM_D_LCD); - - // Subscreen as a console - videoSetModeSub(MODE_0_2D); - vramSetBankH(VRAM_H_SUB_BG); vramSetBankI(VRAM_I_SUB_SPRITE); - consoleInit(&bottomConsoleBG, 1, BgType_Text4bpp, BgSize_T_256x256, 7, 0, false, true); - consoleInit(&bottomConsole, 0, BgType_Text4bpp, BgSize_T_256x256, 15, 0, false, true); + + // Init built-in font + font = new Font("/font.frf"); // Display GM9i logo - bg3 = bgInit(3, BgType_Bmp8, BgSize_B8_256x256, 1, 0); + bg3 = bgInit(3, BgType_Bmp8, BgSize_B8_256x256, 0, 0); + bgInit(2, BgType_Bmp8, BgSize_B8_256x256, 3, 0); + bgInitSub(2, BgType_Bmp8, BgSize_B8_256x256, 3, 0); decompress(gm9i_logoBitmap, bgGetGfxPtr(bg3), LZ77Vram); tonccpy(BG_PALETTE, gm9i_logoPal, gm9i_logoPalLen); - printf ("\x1b[1;1H"); - printf(titleName); - printf ("\x1b[2;1H"); - printf ("------------------------------"); - printf ("\x1b[3;1H"); - printf ("https:/github.com/"); - printf ("\x1b[4;10H"); - printf ("DS-Homebrew/GodMode9i"); + font->print(1, 1, false, titleName); + font->print(1, 2, false, "---------------------------------------"); + font->print(1, 3, false, "https:/github.com/DS-Homebrew/GodMode9i"); fifoWaitValue32(FIFO_USER_06); if (fifoGetValue32(FIFO_USER_03) == 0) arm7SCFGLocked = true; @@ -178,36 +118,27 @@ int main(int argc, char **argv) { if (isDSiMode()) { if (!arm7SCFGLocked) { - printf ("\x1b[20;1H"); - printf ("X Held - Disable NAND access"); - printf ("\x1b[21;1H"); - printf ("Y Held - Disable cart access"); - printf ("\x1b[22;4H"); - printf ("Do these if it crashes here"); + font->print(-2, -4, false, "X Held - Disable NAND access", Alignment::right); + font->print(-2, -3, false, "Y Held - Disable cart access", Alignment::right); + font->print(-2, -2, false, "Do these if it crashes here", Alignment::right); } else { - printf ("\x1b[21;1H"); - printf ("X Held - Disable NAND access"); - printf ("\x1b[22;5H"); - printf ("Do this if it crashes here"); + font->print(-2, -3, false, "X Held - Disable NAND access", Alignment::right); + font->print(-2, -2, false, "Do this if it crashes here", Alignment::right); } } // Display for 2 seconds + font->update(false); for (int i = 0; i < 60*2; i++) { swiWaitForVBlank(); } - if (isDSiMode()) { - printf ("\x1b[20;1H"); - printf (" "); - printf ("\x1b[21;1H"); - printf (" "); - printf ("\x1b[22;4H"); - printf (" "); // Clear "Y Held" text - } - printf ("\x1b[22;11H"); - printf ("mounting drive(s)..."); - //printf ("%X %X", *(u32*)0x2FFFD00, *(u32*)0x2FFFD04); + font->clear(false); + font->print(1, 1, false, titleName); + font->print(1, 2, false, "---------------------------------------"); + font->print(1, 3, false, "https:/github.com/DS-Homebrew/GodMode9i"); + font->print(-2, -2, false, "Mounting drive(s)...", Alignment::right); + font->update(false); sysSetCartOwner (BUS_OWNER_ARM9); // Allow arm9 to access GBA ROM @@ -243,11 +174,11 @@ int main(int argc, char **argv) { flashcardMountSkipped = false; } - // Top screen as a console - videoSetMode(MODE_0_2D); - vramSetBankG(VRAM_G_MAIN_BG); - consoleInit(&topConsoleBG, 1, BgType_Text4bpp, BgSize_T_256x256, 7, 0, true, true); - consoleInit(&topConsole, 0, BgType_Text4bpp, BgSize_T_256x256, 15, 0, true, true); + bgHide(bg3); + + // TODO: better + delete font; + font = new Font("/font.frf"); // Overwrite background white color BG_PALETTE[15+(7*16)] = 0x656A; @@ -261,8 +192,6 @@ int main(int argc, char **argv) { dmaFillWords(0xFFFFFFFF, (void*)0x6000040, 8*8); // Top screen dmaFillWords(0xFFFFFFFF, (void*)0x6200040, 8*8); // Bottom screen - printBorderTop(); - keysSetRepeat(25,5); appInited = true; @@ -302,34 +231,34 @@ int main(int argc, char **argv) { } } fclose(argfile); - filename = argarray.at(0); + filename = argarray[0]; } else { argarray.push_back(strdup(filename.c_str())); } if (extension(filename, {"nds", "dsi", "ids", "app", "srl"})) { - char *name = argarray.at(0); + char *name = argarray[0]; strcpy (filePath + pathLen, name); - free(argarray.at(0)); - argarray.at(0) = filePath; - consoleClear(); - iprintf ("Running %s with %d parameters\n", argarray[0], argarray.size()); - int err = runNdsFile (argarray[0], argarray.size(), (const char **)&argarray[0]); - iprintf ("\x1b[31mStart failed. Error %i\n", err); + free(argarray[0]); + argarray[0] = filePath; + font->clear(false); + font->printf(0, 0, false, Alignment::left, Palette::white, "Running %s with %d parameters\n", argarray[0], argarray.size()); + int err = runNdsFile(argarray[0], argarray.size(), (const char **)&argarray[0]); + font->printf(0, 1, false, Alignment::left, Palette::white, "Start failed. Error %i\n", err); } if (extension(filename, {"firm"})) { - char *name = argarray.at(0); + char *name = argarray[0]; strcpy (filePath + pathLen, name); - free(argarray.at(0)); - argarray.at(0) = filePath; + free(argarray[0]); + argarray[0] = filePath; fcopy(argarray[0], "sd:/bootonce.firm"); fifoSendValue32(FIFO_USER_02, 1); // Reboot into selected .firm payload swiWaitForVBlank(); } while(argarray.size() !=0 ) { - free(argarray.at(0)); + free(argarray[0]); argarray.erase(argarray.begin()); } diff --git a/arm9/source/ndsInfo.cpp b/arm9/source/ndsInfo.cpp index 35357dd..f5bd502 100644 --- a/arm9/source/ndsInfo.cpp +++ b/arm9/source/ndsInfo.cpp @@ -1,13 +1,12 @@ #include "ndsInfo.h" #include "date.h" +#include "font.h" #include "tonccpy.h" #include #include -extern PrintConsole bottomConsole, bottomConsoleBG, topConsole; - constexpr const char *langNames[8] { "Japanese", "English", @@ -19,8 +18,6 @@ constexpr const char *langNames[8] { "Korean" }; -extern void reinitConsoles(void); - void ndsInfo(const char *path) { FILE *file = fopen(path, "rb"); if(!file) @@ -35,7 +32,10 @@ void ndsInfo(const char *path) { u32 ofs; fseek(file, 0x68, SEEK_SET); fread(&ofs, sizeof(u32), 1, file); - fseek(file, ofs, SEEK_SET); + if(ofs < 0x8000 || fseek(file, ofs, SEEK_SET) != 0) { + fclose(file); + return; + } u16 version; fread(&version, sizeof(u16), 1, file); @@ -51,10 +51,13 @@ void ndsInfo(const char *path) { fread(iconAnimation, 2, 0x40, file); fseek(file, ofs + 0x240, SEEK_SET); - } else { // DS + } else if((version & ~3) == 0) { // DS fseek(file, 0x20 - 2, SEEK_CUR); fread(iconBitmap, 1, 0x200, file); fread(iconPalette, 2, 0x10, file); + } else { + fclose(file); + return; } int languages = 5 + (version & 0x3); @@ -76,26 +79,23 @@ void ndsInfo(const char *path) { u16 pressed = 0, held = 0; int animationFrame = 0, frameDelay = 0, lang = 1; while(1) { - consoleClear(); + font->clear(false); + font->printf(0, 0, false, Alignment::left, Palette::white, "Header Title: %s", headerTitle); + font->printf(0, 1, false, Alignment::left, Palette::white, "Title ID: %s", tid); + font->printf(0, 2, false, Alignment::left, Palette::white, "Title: (%s)", langNames[lang]); + font->print(2, 3, false, titles + lang * 0x80); + font->update(false); - iprintf("Header Title: %s\n", headerTitle); - iprintf("Title ID: %s\n", tid); - iprintf("Title: (%s)\n ", langNames[lang]); - for(int j = 0; j < 0x80 && titles[lang * 0x80 + j]; j++) { - if(titles[lang * 0x80 + j] == '\n') - iprintf("\n "); - else - iprintf("%c", titles[lang * 0x80 + j]); - } - iprintf("\n"); - - consoleSelect(&topConsole); do { swiWaitForVBlank(); scanKeys(); pressed = keysDown(); held = keysDownRepeat(); - iprintf("\x1B[30m\x1B[0;26H %s", RetTime().c_str()); // Print time + + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + if(iconAnimation[animationFrame] && animationFrame < 0x40) { if(frameDelay < (iconAnimation[animationFrame] & 0xFF) - 1) { frameDelay++; @@ -111,7 +111,6 @@ void ndsInfo(const char *path) { } } } while(!held); - consoleSelect(&bottomConsole); if(held & KEY_UP) { if(lang > 0) diff --git a/arm9/source/screenshot.cpp b/arm9/source/screenshot.cpp index 2e83c1b..48fdd01 100644 --- a/arm9/source/screenshot.cpp +++ b/arm9/source/screenshot.cpp @@ -1,117 +1,129 @@ -#include -#include -#include - -#include - #include "screenshot.h" + #include "bmp.h" +#include "driveOperations.h" +#include "file_browse.h" +#include "font.h" +#include "date.h" + +#include +#include +#include +#include void wait(); -void screenshot(u8* buffer) { +void write16(void *address, u16 value) { - u8 vram_cr_temp=VRAM_A_CR; - VRAM_A_CR=VRAM_A_LCD; + u8* first = (u8*)address; + u8* second = first + 1; - u8* vram_temp=(u8*)malloc(128*1024); - dmaCopy(VRAM_A, vram_temp, 128*1024); + *first = value & 0xff; + *second = value >> 8; +} - REG_DISPCAPCNT=DCAP_BANK(0)|DCAP_ENABLE|DCAP_SIZE(3); +void write32(void *address, u32 value) { + + u8* first = (u8*)address; + u8* second = first + 1; + u8* third = first + 2; + u8* fourth = first + 3; + + *first = value & 0xff; + *second = (value >> 8) & 0xff; + *third = (value >> 16) & 0xff; + *fourth = (value >> 24) & 0xff; +} + +bool screenshotbmp(const char* filename) { + FILE *file = fopen(filename, "wb"); + + if(!file) + return false; + + REG_DISPCAPCNT = DCAP_BANK(DCAP_BANK_VRAM_D) | DCAP_SIZE(DCAP_SIZE_256x192) | DCAP_ENABLE; while(REG_DISPCAPCNT & DCAP_ENABLE); - dmaCopy(VRAM_A, buffer, 256*192*2); - dmaCopy(vram_temp, VRAM_A, 128*1024); - - VRAM_A_CR=vram_cr_temp; - - free(vram_temp); + u8* temp = new u8[256 * 192 * 2 + sizeof(INFOHEADER) + sizeof(HEADER)]; -} + if(!temp) { + fclose(file); + return false; + } -void screenshot(char* filename) { - - //fatInitDefault(); - FILE* file=fopen(filename, "w"); - - u8* temp=(u8*)malloc(256*192*2); - dmaCopy(VRAM_B, temp, 256*192*2); - fwrite(temp, 1, 256*192*2, file); - fclose(file); - free(temp); -} - -void write16(u16* address, u16 value) { - - u8* first=(u8*)address; - u8* second=first+1; - - *first=value&0xff; - *second=value>>8; -} - -void write32(u32* address, u32 value) { - - u8* first=(u8*)address; - u8* second=first+1; - u8* third=first+2; - u8* fourth=first+3; - - *first=value&0xff; - *second=(value>>8)&0xff; - *third=(value>>16)&0xff; - *fourth=(value>>24)&0xff; -} - -void screenshotbmp(const char* filename) { - - //fatInitDefault(); - FILE* file=fopen(filename, "wb"); - - REG_DISPCAPCNT=DCAP_BANK(3)|DCAP_ENABLE|DCAP_SIZE(3); - while(REG_DISPCAPCNT & DCAP_ENABLE); - - u8* temp=(u8*)malloc(256*192*3+sizeof(INFOHEADER)+sizeof(HEADER)); - - HEADER* header=(HEADER*)temp; - INFOHEADER* infoheader=(INFOHEADER*)(temp+sizeof(HEADER)); + HEADER *header= (HEADER*)temp; + INFOHEADER *infoheader = (INFOHEADER*)(temp + sizeof(HEADER)); write16(&header->type, 0x4D42); - write32(&header->size, 256*192*3+sizeof(INFOHEADER)+sizeof(HEADER)); - write32(&header->offset, sizeof(INFOHEADER)+sizeof(HEADER)); - write16(&header->reserved1, 0); - write16(&header->reserved2, 0); + write32(&header->size, 256 * 192 * 2 + sizeof(INFOHEADER) + sizeof(HEADER)); + write32(&header->reserved1, 0); + write32(&header->reserved2, 0); + write32(&header->offset, sizeof(INFOHEADER) + sizeof(HEADER)); - write16(&infoheader->bits, 24); write32(&infoheader->size, sizeof(INFOHEADER)); - write32(&infoheader->compression, 0); write32(&infoheader->width, 256); write32(&infoheader->height, 192); write16(&infoheader->planes, 1); - write32(&infoheader->imagesize, 256*192*3); - write32(&infoheader->xresolution, 0); - write32(&infoheader->yresolution, 0); - write32(&infoheader->importantcolours, 0); + write16(&infoheader->bits, 16); + write32(&infoheader->compression, 3); + write32(&infoheader->imagesize, 256 * 192 * 2); + write32(&infoheader->xresolution, 2835); + write32(&infoheader->yresolution, 2835); write32(&infoheader->ncolours, 0); + write32(&infoheader->importantcolours, 0); + write32(&infoheader->redBitmask, 0xF800); + write32(&infoheader->greenBitmask, 0x07E0); + write32(&infoheader->blueBitmask, 0x001F); + write32(&infoheader->reserved, 0); - for(int y=0;y<192;y++) - { - for(int x=0;x<256;x++) - { - u16 color=VRAM_D[256*191-y*256+x]; - - u8 b=(color&31)<<3; - u8 g=((color>>5)&31)<<3; - u8 r=((color>>10)&31)<<3; - - temp[((y*256)+x)*3+sizeof(INFOHEADER)+sizeof(HEADER)]=r; - temp[((y*256)+x)*3+1+sizeof(INFOHEADER)+sizeof(HEADER)]=g; - temp[((y*256)+x)*3+2+sizeof(INFOHEADER)+sizeof(HEADER)]=b; + u16 *ptr = (u16*)(temp + sizeof(HEADER) + sizeof(INFOHEADER)); + for(int y = 0; y < 192; y++) { + for(int x = 0; x < 256; x++) { + u16 color = VRAM_D[256 * 191 - y * 256 + x]; + *(ptr++) = ((color >> 10) & 0x1F) | (color & (0x1F << 5)) << 1 | ((color & 0x1F) << 11); } } DC_FlushAll(); - fwrite(temp, 1, 256*192*3+sizeof(INFOHEADER)+sizeof(HEADER), file); + fwrite(temp, 1, 256 * 192 * 2 + sizeof(INFOHEADER) + sizeof(HEADER), file); fclose(file); - free(temp); + delete[] temp; + return true; } + +bool screenshot(void) { + if (!(sdMounted || flashcardMounted)) + return false; + + if (access((sdMounted ? "sd:/gm9i" : "fat:/gm9i"), F_OK) != 0) { + mkdir((sdMounted ? "sd:/gm9i" : "fat:/gm9i"), 0777); + } + if (access((sdMounted ? "sd:/gm9i/out" : "fat:/gm9i/out"), F_OK) != 0) { + mkdir((sdMounted ? "sd:/gm9i/out" : "fat:/gm9i/out"), 0777); + } + + std::string fileTimeText = RetTimeForFilename(); + char snapPath[40]; + // Take top screenshot + snprintf(snapPath, sizeof(snapPath), "%s:/gm9i/out/snap_%s_top.bmp", (sdMounted ? "sd" : "fat"), fileTimeText.c_str()); + if(!screenshotbmp(snapPath)) + return false; + + // Seamlessly swap top and bottom screens + font->mainOnTop(false); + font->update(false); + font->update(true); + lcdMainOnBottom(); + + // Take bottom screenshot + snprintf(snapPath, sizeof(snapPath), "%s:/gm9i/out/snap_%s_bot.bmp", (sdMounted ? "sd" : "fat"), fileTimeText.c_str()); + if(!screenshotbmp(snapPath)) + return false; + + font->mainOnTop(true); + font->update(true); + font->update(false); + lcdMainOnTop(); + + return true; +} \ No newline at end of file diff --git a/data/font_default.frf b/data/font_default.frf new file mode 100644 index 0000000000000000000000000000000000000000..93446533c7b92ebe602445a4e4526a9049468afb GIT binary patch literal 5796 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 VH)XT@Q~o9YmO1&4yd`hTe*rqV4toFq literal 0 HcmV?d00001 From 6662d644d5d98122b6b21a7a6106400603f1fda3 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sun, 8 Aug 2021 08:26:33 -0500 Subject: [PATCH 02/21] Fix right alignment on fonts divisible by 256 --- arm9/source/font.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arm9/source/font.cpp b/arm9/source/font.cpp index a44e790..5c7233a 100644 --- a/arm9/source/font.cpp +++ b/arm9/source/font.cpp @@ -185,9 +185,9 @@ void Font::printf(int xPos, int yPos, bool top, Alignment align, Palette palette ITCM_CODE void Font::print(int xPos, int yPos, bool top, std::u16string_view text, Alignment align, Palette palette, bool rtl) { int x = xPos * tileWidth, y = yPos * tileHeight; if(x < 0 && align != Alignment::center) - x += 255; + x += 256; if(y < 0) - y += 191; + y += 192; // If RTL isn't forced, check for RTL text if(!rtl) { From ce49d1fdf9a616c6de8098cfe120111905726b43 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sun, 8 Aug 2021 08:27:11 -0500 Subject: [PATCH 03/21] Add padding space before file size --- arm9/source/file_browse.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index 7e4ed2c..f908afa 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -164,9 +164,9 @@ void showDirectoryContents (const std::vector& dirContents, int fileOf if (entry->name == "..") { font->print(-1, i + 1, true, "(..)", Alignment::right, pal); } else if (entry->isDirectory) { - font->print(-1, i + 1, true, "(dir)", Alignment::right, pal); + font->print(-1, i + 1, true, " (dir)", Alignment::right, pal); } else { - font->printf(-1, i + 1, true, Alignment::right, pal, "(%s)", getBytes(entry->size).c_str()); + font->printf(-1, i + 1, true, Alignment::right, pal, " (%s)", getBytes(entry->size).c_str()); } } From bba0daa23a993fa45b8290785e46d20193f39036 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Thu, 12 Aug 2021 16:56:52 -0500 Subject: [PATCH 04/21] Add loading font from an FRF file --- arm9/source/file_browse.cpp | 9 ++++++ arm9/source/file_browse.h | 1 + arm9/source/font.cpp | 64 +++++++++++++++++++++++++++++-------- arm9/source/font.h | 2 ++ arm9/source/main.cpp | 18 ++--------- 5 files changed, 65 insertions(+), 29 deletions(-) diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index f908afa..82c1353 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -196,6 +196,8 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { operations.push_back(FileOperation::restoreSave); } else if(extension(entry->name, {"img", "sd"})) { operations.push_back(FileOperation::mountImg); + } else if(extension(entry->name, {"frf"})) { + operations.push_back(FileOperation::loadFont); } operations.push_back(FileOperation::hexEdit); @@ -261,6 +263,9 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { case FileOperation::calculateSHA1: font->print(3, row++, false, "Calculate SHA1 hash"); break; + case FileOperation::loadFont: + font->print(3, row++, false, "Load font"); + break; case FileOperation::none: row++; break; @@ -401,6 +406,10 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { } case FileOperation::hexEdit: { hexEditor(entry->name.c_str(), currentDrive); break; + } case FileOperation::loadFont: { + delete font; + font = new Font(entry->name.c_str()); + break; } case FileOperation::calculateSHA1: { u8 sha1[20] = {0}; bool ret = calculateSHA1(strcat(getcwd(path, PATH_MAX), entry->name.c_str()), sha1); diff --git a/arm9/source/file_browse.h b/arm9/source/file_browse.h index a4c83cd..33a1a97 100644 --- a/arm9/source/file_browse.h +++ b/arm9/source/file_browse.h @@ -47,6 +47,7 @@ enum class FileOperation { copyFatOut, calculateSHA1, hexEdit, + loadFont, }; bool extension(const std::string &filename, const std::vector &extensions); diff --git a/arm9/source/font.cpp b/arm9/source/font.cpp index 5c7233a..e4ab568 100644 --- a/arm9/source/font.cpp +++ b/arm9/source/font.cpp @@ -24,7 +24,7 @@ bool Font::isNumber(char16_t c) { return c >= '0' && c <= '9'; } -Font::Font(const char *path) { +bool Font::load(const char *path) { FILE *file = fopen(path, "rb"); const u8 *fileBuffer = font_default_frf; @@ -35,7 +35,7 @@ Font::Font(const char *path) { fileBuffer = new u8[size]; if(!fileBuffer) { fclose(file); - return; + return false; } fseek(file, 0, SEEK_SET); @@ -44,8 +44,12 @@ Font::Font(const char *path) { const u8 *ptr = fileBuffer; // Check header magic, then skip over - if(memcmp(ptr, "RIFF", 4) != 0) - goto cleanup; + if(memcmp(ptr, "RIFF", 4) != 0) { + if(fileBuffer != font_default_frf) + delete[] fileBuffer; + + return false; + } ptr += 8; @@ -55,21 +59,32 @@ Font::Font(const char *path) { tileHeight = ptr[9]; tonccpy(&tileCount, ptr + 10, sizeof(u16)); - if(tileWidth > TILE_MAX_WIDTH || tileHeight > TILE_MAX_HEIGHT) - goto cleanup; + if(tileWidth > TILE_MAX_WIDTH || tileHeight > TILE_MAX_HEIGHT) { + if(fileBuffer != font_default_frf) + delete[] fileBuffer; + + return false; + } u32 section_size; tonccpy(§ion_size, ptr + 4, sizeof(u32)); ptr += 8 + section_size; } else { - goto cleanup; + if(fileBuffer != font_default_frf) + delete[] fileBuffer; + + return false; } // Character data if(memcmp(ptr, "CDAT", 4) == 0) { fontTiles = new u8[tileHeight * tileCount]; - if(!fontTiles) - goto cleanup; + if(!fontTiles) { + if(fileBuffer != font_default_frf) + delete[] fileBuffer; + + return false; + } tonccpy(fontTiles, ptr + 8, tileHeight * tileCount); @@ -77,14 +92,23 @@ Font::Font(const char *path) { tonccpy(§ion_size, ptr + 4, sizeof(u32)); ptr += 8 + section_size; } else { - goto cleanup; + if(fileBuffer != font_default_frf) + delete[] fileBuffer; + + return false; } // character map if(memcmp(ptr, "CMAP", 4) == 0) { fontMap = new u16[tileCount]; - if(!fontMap) - goto cleanup; + if(!fontMap) { + if(fileBuffer != font_default_frf) + delete[] fileBuffer; + + delete[] fontTiles; + + return false; + } tonccpy(fontMap, ptr + 8, sizeof(u16) * tileCount); @@ -92,7 +116,12 @@ Font::Font(const char *path) { tonccpy(§ion_size, ptr + 4, sizeof(u32)); ptr += 8 + section_size; } else { - goto cleanup; + if(fileBuffer != font_default_frf) + delete[] fileBuffer; + + delete[] fontTiles; + + return false; } questionMark = getCharIndex('?'); @@ -103,9 +132,16 @@ Font::Font(const char *path) { tonccpy(BG_PALETTE_SUB + i * 0x10, palette[i], 4); } -cleanup: if(fileBuffer != font_default_frf) delete[] fileBuffer; + + return true; +} + +Font::Font(const char *path) { + if(!load(path)) { + load(nullptr); + } } Font::~Font(void) { diff --git a/arm9/source/font.h b/arm9/source/font.h index a9962e6..d2224f3 100644 --- a/arm9/source/font.h +++ b/arm9/source/font.h @@ -60,6 +60,8 @@ class Font { u8 *fontTiles = nullptr; u16 *fontMap = nullptr; + bool load(const char *path); + u16 getCharIndex(char16_t c); public: static std::u16string utf8to16(std::string_view text); diff --git a/arm9/source/main.cpp b/arm9/source/main.cpp index 2af687b..d479634 100644 --- a/arm9/source/main.cpp +++ b/arm9/source/main.cpp @@ -97,7 +97,7 @@ int main(int argc, char **argv) { vramSetBankI(VRAM_I_SUB_SPRITE); // Init built-in font - font = new Font("/font.frf"); + font = new Font(nullptr); // Display GM9i logo bg3 = bgInit(3, BgType_Bmp8, BgSize_B8_256x256, 0, 0); @@ -176,21 +176,9 @@ int main(int argc, char **argv) { bgHide(bg3); - // TODO: better + // Reinit font, try to load default from SD this time delete font; - font = new Font("/font.frf"); - - // Overwrite background white color - BG_PALETTE[15+(7*16)] = 0x656A; - BG_PALETTE_SUB[15+(7*16)] = 0x656A; - - // Custom yellow color - BG_PALETTE[15+(3*16)] = 0x3339; - BG_PALETTE_SUB[15+(3*16)] = 0x3339; - - // Overwrite 2nd smiley face with filled tile - dmaFillWords(0xFFFFFFFF, (void*)0x6000040, 8*8); // Top screen - dmaFillWords(0xFFFFFFFF, (void*)0x6200040, 8*8); // Bottom screen + font = new Font(sdFound() ? "sd:/gm9i/font.frf" : "fat:/gm9i/font.frf"); keysSetRepeat(25,5); From 363ee012e297f67c88851117f546e6547e07650d Mon Sep 17 00:00:00 2001 From: Pk11 Date: Thu, 12 Aug 2021 17:44:27 -0500 Subject: [PATCH 05/21] Add some simple wrapping on spaces Wraps on the last space within 10 chars of the end of the line --- arm9/source/font.cpp | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/arm9/source/font.cpp b/arm9/source/font.cpp index e4ab568..ef5631e 100644 --- a/arm9/source/font.cpp +++ b/arm9/source/font.cpp @@ -3,6 +3,7 @@ #include "font_default_frf.h" #include "tonccpy.h" +#include #include #include #include @@ -196,11 +197,17 @@ std::u16string Font::utf8to16(std::string_view text) { int Font::calcHeight(std::u16string_view text, int xPos) { int lines = 1, chars = xPos + 1; - for(char16_t c : text) { - if(c == '\n' || chars > 256 / tileWidth) { - nocashMessage(c == '\n' ? "line" : "cha"); + for(auto it = text.begin(); it != text.end(); it++) { + if(*it == '\n' || (*it == ' ' && 256 / tileWidth - chars < 10 && text.end() - it > (256 / tileWidth - chars) && *std::find(it + 1, std::min(it + (256 / tileWidth - chars), text.end()), ' ') != ' ')) { lines++; chars = xPos + 1; + } else if(chars > 256 / tileWidth) { + lines++; + chars = xPos + 1; + + // Skip to next char if a space + if(*it == ' ') + it++; } else { chars++; } @@ -347,12 +354,24 @@ ITCM_CODE void Font::print(int xPos, int yPos, bool top, std::u16string_view tex rtl = false; } - if(*it == '\n') { + // Line break on newline or last space within 10 chars of edge in left align + if(*it == '\n' || (*it == ' ' && align == Alignment::left && 256 - x < tileWidth * 10 && text.end() - it > (256 - x) / tileWidth && *std::find(it + 1, std::min(it + (256 - x) / tileWidth, text.end()), ' ') != ' ')) { x = xStart; y += tileHeight; + continue; } + // Wrap at edge if left aligning + if(x + tileWidth > 256 && align == Alignment::left) { + x = xStart; + y += tileHeight; + + // Skip to next char if a space + if(*it == ' ') + it++; + } + // Brackets are flipped in RTL u16 index; if(rtl) { @@ -383,12 +402,6 @@ ITCM_CODE void Font::print(int xPos, int yPos, bool top, std::u16string_view tex index = getCharIndex(*it); } - // Wrap at right edge if not center aligning - if(x + tileWidth > 256 && align != Alignment::center) { - x = xStart; - y += tileHeight; - } - // Don't draw off screen chars if(x >= 0 && y >= 0 && y + tileHeight <= 192) { u8 *dst = textBuf[top] + x; From 19be8dd77a116ed6230dd97231726d37b0f22745 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Thu, 12 Aug 2021 18:05:15 -0500 Subject: [PATCH 06/21] Use 16 column hex editor when font small enough --- arm9/source/hexEditor.cpp | 74 +++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/arm9/source/hexEditor.cpp b/arm9/source/hexEditor.cpp index 82813ee..ac13cde 100644 --- a/arm9/source/hexEditor.cpp +++ b/arm9/source/hexEditor.cpp @@ -3,6 +3,7 @@ #include "date.h" #include "file_browse.h" #include "font.h" +#include "screenshot.h" #include "tonccpy.h" #include @@ -234,23 +235,26 @@ void hexEditor(const char *path, int drive) { u32 fileSize = ftell(file); fseek(file, 0, SEEK_SET); - u8 maxLines = std::min((u32)ENTRIES_PER_SCREEN, fileSize / 8 + (fileSize % 8 != 0)); - u32 maxSize = ((fileSize - 8 * maxLines) & ~7) + (fileSize & 7 ? 8 : 0); + u8 bytesPerLine = font->width() < 5 ? 16 : 8; + + u8 maxLines = std::min((u32)ENTRIES_PER_SCREEN, fileSize / bytesPerLine + (fileSize % bytesPerLine != 0)); + u32 maxSize = ((fileSize - bytesPerLine * maxLines) & ~(bytesPerLine - 1)) + (fileSize & (bytesPerLine - 1) ? bytesPerLine : 0); u16 pressed = 0, held = 0; u32 offset = 0, cursorPosition = 0, mode = 0; - char data[8 * maxLines]; + char data[bytesPerLine * maxLines]; fseek(file, offset, SEEK_SET); fread(data, 1, sizeof(data), file); while(1) { font->clear(false); - font->printf(4, 0, false, Alignment::left, Palette::blackGreen, "%*c", SCREEN_COLS - 4, ' '); + font->printf(0, 0, false, Alignment::left, Palette::blackGreen, "%*c", SCREEN_COLS, ' '); font->print(0, 0, false, "Hex Editor", Alignment::center, Palette::blackGreen); - font->printf(0, 0, false, Alignment::left, Palette::blackBlue, "%04lX", offset >> 0x10); + if(bytesPerLine < 16) + font->printf(0, 0, false, Alignment::left, Palette::blackBlue, "%04lX", offset >> 0x10); if(mode < 2) { fseek(file, offset, SEEK_SET); @@ -259,22 +263,26 @@ void hexEditor(const char *path, int drive) { } for(u32 i = 0; i < maxLines; i++) { - font->printf(0, i + 1, false, Alignment::left, Palette::blue, "%04lX", (offset + i * 8) & 0xFFFF); - for(int j = 0; j < 4; j++) - font->printf(5 + (j * 2), i + 1, false, Alignment::left, (mode > 0 && i * 8 + j == cursorPosition) ? (mode > 1 ? Palette::blackRed : Palette::red) : (offset + i * 8 + j >= fileSize ? Palette::gray : (j % 2 ? Palette::greenAlt : Palette::green)), "%02X", data[i * 8 + j]); - for(int j = 0; j < 4; j++) - font->printf(14 + (j * 2), i + 1, false, Alignment::left, (mode > 0 && i * 8 + 4 + j == cursorPosition) ? (mode > 1 ? Palette::blackRed : Palette::red) : (offset + i * 8 + 4 + j >= fileSize ? Palette::gray : (j % 2 ? Palette::greenAlt : Palette::green)), "%02X", data[i * 8 + 4 + j]); - char line[9] = {0}; - for(int j = 0; j < 8; j++) { - char c = data[i * 8 + j]; + if(bytesPerLine < 16) + font->printf(0, i + 1, false, Alignment::left, Palette::blue, "%04lX", (offset + i * bytesPerLine) & 0xFFFF); + else + font->printf(0, i + 1, false, Alignment::left, Palette::blue, "%08lX", offset + i * bytesPerLine); + + for(int group = 0; group < bytesPerLine / 4; group++) { + for(int j = 0; j < 4; j++) + font->printf(4 * (bytesPerLine / 8) + 1 + (group * 9) + (j * 2), i + 1, false, Alignment::left, (mode > 0 && i * bytesPerLine + (group * 4) + j == cursorPosition) ? (mode > 1 ? Palette::blackRed : Palette::red) : (offset + i * bytesPerLine + (group * 4) + j >= fileSize ? Palette::gray : (j % 2 ? Palette::greenAlt : Palette::green)), "%02X", data[i * bytesPerLine + group * 4 + j]); + } + char line[bytesPerLine + 1] = {0}; + for(int j = 0; j < bytesPerLine; j++) { + char c = data[i * bytesPerLine + j]; if(c < ' ' || c > 127) line[j] = '.'; else line[j] = c; } - font->print(23, i + 1, false, line); - if(mode > 0 && cursorPosition / 8 == i) { - font->printf(23 + cursorPosition % 8, i + 1, false, Alignment::left, mode > 1 ? Palette::blackRed : Palette::red, "%c", line[cursorPosition % 8]); + font->print(4 * (bytesPerLine / 8) + 1 + bytesPerLine / 4 * 9, i + 1, false, line); + if(mode > 0 && cursorPosition / bytesPerLine == i) { + font->printf(4 * (bytesPerLine / 8) + 1 + bytesPerLine / 4 * 9 + cursorPosition % bytesPerLine, i + 1, false, Alignment::left, mode > 1 ? Palette::blackRed : Palette::red, "%c", line[cursorPosition % bytesPerLine]); } } @@ -303,15 +311,15 @@ void hexEditor(const char *path, int drive) { offset = std::min(offset + 0x10000, maxSize); } } else if(held & KEY_UP) { - if(offset >= 8) - offset -= 8; + if(offset >= bytesPerLine) + offset -= bytesPerLine; } else if(held & KEY_DOWN) { - if(offset < fileSize - 8 * maxLines && fileSize > 8 * maxLines) - offset += 8; + if(offset < fileSize - bytesPerLine * maxLines && fileSize > bytesPerLine * maxLines) + offset += bytesPerLine; } else if(held & KEY_LEFT) { - offset = std::max((s64)offset - 8 * maxLines, 0ll); + offset = std::max((s64)offset - bytesPerLine * maxLines, 0ll); } else if(held & KEY_RIGHT) { - offset = std::min(offset + 8 * maxLines, maxSize); + offset = std::min(offset + bytesPerLine * maxLines, maxSize); } else if(pressed & KEY_A) { mode = 1; cursorPosition = std::min(cursorPosition, fileSize - offset - 1); @@ -324,21 +332,21 @@ void hexEditor(const char *path, int drive) { } } else if(mode == 1) { if(held & KEY_UP) { - if(cursorPosition >= 8) - cursorPosition -= 8; - else if(offset >= 8) - offset -= 8; + if(cursorPosition >= bytesPerLine) + cursorPosition -= bytesPerLine; + else if(offset >= bytesPerLine) + offset -= bytesPerLine; } else if(held & KEY_DOWN) { - if(cursorPosition < 8u * (maxLines - 1)) - cursorPosition += 8; - else if(offset < fileSize - 8 * maxLines && fileSize > 8 * maxLines) - offset += 8; + if((int)cursorPosition < bytesPerLine * (maxLines - 1)) + cursorPosition += bytesPerLine; + else if(offset < fileSize - bytesPerLine * maxLines && fileSize > bytesPerLine * maxLines) + offset += bytesPerLine; cursorPosition = std::min(cursorPosition, fileSize - offset - 1); } else if(held & KEY_LEFT) { if(cursorPosition > 0) cursorPosition--; } else if(held & KEY_RIGHT) { - if(cursorPosition < 8u * maxLines - 1) + if((int)cursorPosition < bytesPerLine * maxLines - 1) cursorPosition = std::min(cursorPosition + 1, fileSize - offset - 1); } else if(pressed & KEY_A) { if(drive < 4) { @@ -366,6 +374,10 @@ void hexEditor(const char *path, int drive) { fwrite(data + cursorPosition, 1, 1, file); } } + + if(keysHeld() & KEY_R && pressed & KEY_L) { + screenshot(); + } } fclose(file); From e2e892ead05328934f23c0a63f015e33c216f9cc Mon Sep 17 00:00:00 2001 From: Pk11 Date: Thu, 12 Aug 2021 18:11:36 -0500 Subject: [PATCH 07/21] Add default, original, and tiny fonts to resources --- resources/fonts/default-6x10.frf | Bin 0 -> 5796 bytes resources/fonts/original-8x8.frf | Bin 0 -> 2596 bytes resources/fonts/tiny-4x6.frf | Bin 0 -> 2084 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/fonts/default-6x10.frf create mode 100755 resources/fonts/original-8x8.frf create mode 100755 resources/fonts/tiny-4x6.frf diff --git a/resources/fonts/default-6x10.frf b/resources/fonts/default-6x10.frf new file mode 100644 index 0000000000000000000000000000000000000000..93446533c7b92ebe602445a4e4526a9049468afb GIT binary patch literal 5796 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 VH)XT@Q~o9YmO1&4yd`hTe*rqV4toFq literal 0 HcmV?d00001 diff --git a/resources/fonts/original-8x8.frf b/resources/fonts/original-8x8.frf new file mode 100755 index 0000000000000000000000000000000000000000..388af74c266a5079e08d1677fd5a3d91303a6888 GIT binary patch literal 2596 zcmZ8hU2Ggz75?f)ixsUlBUDvGZ6&*F-=!@r3?*!acF2#Dh7_8TrcQs{w4FG~G))FD zrMN>oojMi|dB{VXfE2$(g{m!-JkYA8Qro~v4vP^Z%e<@>*)mH74nM(|fSd(ZvwY{y z8Y<50>^JA!bIU?Jg&a z=*i}&UPU;&xVp3y!n6Y#h!EDY{bRL6(8<<9^gDi>LIi6-4*xB0>{{l_@KXJ@7!sh9J6!ICK(}A{DxoA2HH7}mF?S_;q1~=gcYkQ!ILE3 z*2Hu3^YhR2^=XM@Vf)-5Y14||0ID-{mk<0TCz$LywSg09xl$LI5Dpe>JH=>bCYhO; zm9DTY=2+fH+`vuXoz(*cIR)vy6oa@C*J=yfnnv2!d>6w>TlYCeDrYY>&w76(nv(>> zYL)hzM%wRu7sJVv*Ku(Unnv31d>3g~_uuQzfp<3Tv)GZi#EziYS<6q5gA?(kJ*n3? z^->HcQ;fTP*U0|n`%KPxV=Nh!`PS!%Kq|v+D2HfhX$+0Ik{n(`8*lVER4e` znH;b{=htHV=!q`-{%-Qo+|10FV8b*GARC^$5?0CUv5<4cY4zAHUWTMr+`1Sq##y#0 zaV^o3^T`CvT#^$Yjc}eYIMf;9RVwSkcTox~+E~wf;HmOk@O%`eLfIHYuc6h+dnFkwOy%~sNCP5?;~@)}1^eoL|? z-$Qk8XvlNW>0FwgzC>@+Qx`5wO?RRbFQ2$Dbs~y7^Bu|~W7uA{hYhOBg&&#gKfL}~ zo|Hh$FB>+!nw?9$hy5gVAaZfkn_IW8H&}sr?e*6^@qWI4$DZwbLAO8wMU*grK`4~* zG2DO~aT7j{n{f+1flu;A`V?-(r?Cm2K?R#(2`*1IYu^;#0emsB&F@lG101x94JPH>FF^WStj3Yc}kKrgD z$1xm-k1yjZ_$tQmH9Uc@<4Jr2<9G@IzKN&t49~!`ygFXQckw;EgqQIuUc(P@9zVkC zcmr?Y$9Nm>;9b0j3-~E6;%B^5e~w?^SEyqFzd;kf#ryajevc(w!XNNQ{0V=?W&8!p z_$&T~EBHJ9fmK|^Kk+a88*R?Z>1)np&SqZ9yps7r=Jm{*nYS|4%yi~rrj{ucoWgU3 zR}0S(qxboG6`j$GWPO0bB3+l8wqh3_!)f?&^^`5$@eyJMj*Xp+N?PaIDtvpnA%ZJKG L%3k?c`FQz1=xjqT literal 0 HcmV?d00001 diff --git a/resources/fonts/tiny-4x6.frf b/resources/fonts/tiny-4x6.frf new file mode 100755 index 0000000000000000000000000000000000000000..a0c1c7a5232da9039833bf23b5dc0da67020faa6 GIT binary patch literal 2084 zcmchXN32y<6ox;zgg9`(z=0Z~=ibM-F4Ub_rL!2uf5NCb7oANHu@Nt zIdyPg1TbPG!zND|7-S@rlu{AqR|tVXN}i{p4>89asH;#{6p}-s z1ph&ZRQ2^MP^q>is!Jwh6_AA#k5yK$QdN{XE048WQX0NDSFY9UBB7CzvKG}>@&$F* zo%5ZLN*z7Qd`|jEtrn7-zNOYjU5YzVm*{=PEV5JNuu4Hl7NR0CQ}I>BlBAimDn->Q zFRoc~l8*a#1+PtwW@>V;QjIGJ2+c&^-Id)mLaM4rjowuRH))PZd6pM^fJhZxS=*PD zYllS>MRSWIZ@7M?2~wz2&YE37Q3Llt!*SE9Ql4wordTywBOM*BA6kfznl747Odne@ zxIL$9fS3z0)MBcg{&J{7b_GPC;oy0ZPB}vCRmq=nhDQ?93AW)T`~!&n&>{y1m&(|r>)bqUlZ zmAaWij&(1c5arfi`>e%=|3AjbGY4id+znK;>Cnd*j4r2eDq}f~(;3GZoXJ_7%{iRQ zd5q_LF5p5g;$kK+k$x`WQZ6Gfz$7L!g{e&Aa;7tbE4Y%Yn90>#!z^Yqhq(+gkNGTM zA&Xed5|*-z<*XpGl2xo`4MVJD9qZY^MmDjTYq^f=xq%zGiJQ5FTe*$fxq~~oi@Ujp zd$~_%@)(cv1W&SqXL*jDJkKt6vxgUXiM_ncK3?Hf_VYRic!M{2n|FAh4|D<_@(~~N z37_&ApYsJ@@)ck64d3z|-}3`M@)JMv3%~Lkzw-xwvTfM*)}yT_T2Hl}Y3*vg(Av|= zt=C%nTL)U5s=vCw+EHz3Z*6aDZ*M=`ex&_$`-IMkos&8zcgA+6cUE@RbcQ;^`$qLu zeeFJMxE*Vw>^M8#M%x%0XXCBkX4!0;V{>iL=GlB(U<+-LEw&}L)Rx(DTVc^w+A3RZ zYi!8Y+B#cr8*HO(vdvc81NNXjWLs>jZL{r`Y^UwEy|&Nx+gtXoy=N1;6TAK0(4Ewc P-PPTp?%M9g?xyZvx)l%{ literal 0 HcmV?d00001 From 01daede48d8fff369c015017aae9b1653ffb7026 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Thu, 12 Aug 2021 18:25:00 -0500 Subject: [PATCH 08/21] Add custom font info to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0486670..8d6b4bd 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,11 @@ In order to compile this application on your own, you will need [devkitPro](http Once everything is downloaded and installed, `git clone` this repository, navigate to the folder in which it was cloned, and run `make` to compile the application. If there is an error, let us know. +## Custom Fonts +GodMode9i uses the same FRF font files as [GodMode9](https://github.com/d0k3/GodMode9). To create an FRF font use [GodMode9's Python script](https://github.com/d0k3/GodMode9/blob/master/utils/fontriff.py) or you can find some in the `resources/fonts` folder in this repository. + +When loading GodMode9i will try to load `/gm9i/font.frf` on your SD card and if that fails will load the default font. To change the default font when building GodMode9i, replace `data/font_default.frf` with your font. + ## Credits * [RocketRobz](https://github.com/RocketRobz): Main Developer. * [Evie/Pk11](https://github.com/Epicpkmn11): Contributor. From fe50b15537b077841fa77beb61efdb34dbb90d1f Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sat, 14 Aug 2021 13:34:14 -0500 Subject: [PATCH 09/21] Add Cyrillic to default font Was informed that the 3DS actually was translated to Russian and this it's worthwhile to put Cyrillic in the font for GM9, so updating this to match the update to GM9's font --- data/font_default.frf | Bin 5796 -> 6756 bytes resources/fonts/default-6x10.frf | Bin 5796 -> 6756 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/font_default.frf b/data/font_default.frf index 93446533c7b92ebe602445a4e4526a9049468afb..59b0c3c4a536093e6ca3e94ff362605571b8ea3b 100644 GIT binary patch delta 1086 zcmY+D&2LO$6vl6xryZ#-ZmYu>gTcIYeUw4*;^J1{)-5f?4I7I|%(9C{Ebihav+2@8 z(!^e3X~T3SBo=~1v1M1{FR-44=bU>hagv!lIp@6R<2PrXPyC*&-kzJCyoAYcUoTHpJAolq7j$eitKH zN~dMSLF0?mIktYHm z%bMqV&HPw-t*W{kr9b@xR?cws%TQ&I;|76S{W(;5z)j^0SP{duur@5ya+!<=uPShP z)N95oag#2OYHd@cy;OX?UT=M6o!=tX(lwF1s?6FYg6P(_zAN>&(ET;~q_5M*QyN4G zK1MK#Z5Trt+p&Xxf}N;h7tduk_FymS*oXZ%fCdc4aS#Cx;V_QiC?;?W$I-+IoWvFbGU}F?48yOyuAq)l{EgqBQ_#`(w^6h75I>5Y{ kNo)e+WF?7d0&7$^sqRwUqk2U3wCWAjo2r{{Nc1oQ0Lp?UAOHXW diff --git a/resources/fonts/default-6x10.frf b/resources/fonts/default-6x10.frf index 93446533c7b92ebe602445a4e4526a9049468afb..59b0c3c4a536093e6ca3e94ff362605571b8ea3b 100644 GIT binary patch delta 1086 zcmY+D&2LO$6vl6xryZ#-ZmYu>gTcIYeUw4*;^J1{)-5f?4I7I|%(9C{Ebihav+2@8 z(!^e3X~T3SBo=~1v1M1{FR-44=bU>hagv!lIp@6R<2PrXPyC*&-kzJCyoAYcUoTHpJAolq7j$eitKH zN~dMSLF0?mIktYHm z%bMqV&HPw-t*W{kr9b@xR?cws%TQ&I;|76S{W(;5z)j^0SP{duur@5ya+!<=uPShP z)N95oag#2OYHd@cy;OX?UT=M6o!=tX(lwF1s?6FYg6P(_zAN>&(ET;~q_5M*QyN4G zK1MK#Z5Trt+p&Xxf}N;h7tduk_FymS*oXZ%fCdc4aS#Cx;V_QiC?;?W$I-+IoWvFbGU}F?48yOyuAq)l{EgqBQ_#`(w^6h75I>5Y{ kNo)e+WF?7d0&7$^sqRwUqk2U3wCWAjo2r{{Nc1oQ0Lp?UAOHXW From 9df88c6b7fd5203268f3f284b6f27a20d2f577dc Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sat, 14 Aug 2021 13:54:37 -0500 Subject: [PATCH 10/21] Add taking screenshots to a few more places --- arm9/source/fileOperations.cpp | 6 +++++- arm9/source/file_browse.cpp | 15 ++++++++++++--- arm9/source/hexEditor.cpp | 6 ++++++ arm9/source/ndsInfo.cpp | 3 +++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/arm9/source/fileOperations.cpp b/arm9/source/fileOperations.cpp index 7ebe039..9a18662 100644 --- a/arm9/source/fileOperations.cpp +++ b/arm9/source/fileOperations.cpp @@ -9,6 +9,7 @@ #include "file_browse.h" #include "font.h" #include "ndsheaderbanner.h" +#include "screenshot.h" #define copyBufSize 0x8000 #define shaChunkSize 0x10000 @@ -218,7 +219,7 @@ int fcopy(const char *sourcePath, const char *destinationPath) { } void changeFileAttribs(const DirEntry *entry) { - int pressed = 0; + int pressed = 0, held = 0; int cursorScreenPos = font->calcHeight(entry->name); uint8_t currentAttribs = FAT_getAttr(entry->name.c_str()); uint8_t newAttribs = currentAttribs; @@ -242,6 +243,7 @@ void changeFileAttribs(const DirEntry *entry) { font->update(true); scanKeys(); + held = keysHeld(); pressed = keysDown(); swiWaitForVBlank(); } while (!(pressed & KEY_UP) && !(pressed & KEY_DOWN) && !(pressed & KEY_RIGHT) && !(pressed & KEY_LEFT) @@ -260,6 +262,8 @@ void changeFileAttribs(const DirEntry *entry) { break; } else if (pressed & (KEY_A | KEY_B)) { break; + } else if (held & KEY_R && pressed & KEY_L) { + screenshot(); } } } diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index 82c1353..4d66546 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -174,7 +174,7 @@ void showDirectoryContents (const std::vector& dirContents, int fileOf } FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { - int pressed = 0; + int pressed = 0, held = 0; std::vector operations; int optionOffset = 0; std::string fullPath = path + entry->name; @@ -287,9 +287,9 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { scanKeys(); pressed = keysDownRepeat(); + held = keysHeld(); swiWaitForVBlank(); - } while (!(pressed & KEY_UP) && !(pressed & KEY_DOWN) - && !(pressed & KEY_A) && !(pressed & KEY_B) + } while (!(pressed & (KEY_UP| KEY_DOWN | KEY_A | KEY_B | KEY_L)) #ifdef SCREENSWAP && !(pressed & KEY_TOUCH) #endif @@ -435,6 +435,10 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { scanKeys(); pressed = keysDownRepeat(); swiWaitForVBlank(); + + if(keysHeld() & KEY_R && pressed & KEY_L) { + screenshot(); + } } while (!(pressed & (KEY_A | KEY_Y | KEY_B | KEY_X))); break; } case FileOperation::none: { @@ -453,6 +457,11 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { screenSwapped ? lcdMainOnBottom() : lcdMainOnTop(); } #endif + + // Make a screenshot + if ((held & KEY_R) && (pressed & KEY_L)) { + screenshot(); + } } } diff --git a/arm9/source/hexEditor.cpp b/arm9/source/hexEditor.cpp index ac13cde..2e5df35 100644 --- a/arm9/source/hexEditor.cpp +++ b/arm9/source/hexEditor.cpp @@ -46,6 +46,8 @@ u32 jumpToOffset(u32 offset) { cursorPosition--; } else if(pressed & (KEY_A | KEY_B)) { return offset; + } else if(keysHeld() & KEY_R && pressed & KEY_L) { + screenshot(); } } } @@ -79,6 +81,8 @@ u32 search(u32 offset, FILE *file) { break; } else if(pressed & KEY_B) { return offset; + } else if(keysHeld() & KEY_R && pressed & KEY_L) { + screenshot(); } } @@ -160,6 +164,8 @@ u32 search(u32 offset, FILE *file) { if(cursorPosition > strLen * 2 - 1) cursorPosition -= 2; } + } else if(keysHeld() & KEY_R && pressed & KEY_L) { + screenshot(); } } } diff --git a/arm9/source/ndsInfo.cpp b/arm9/source/ndsInfo.cpp index f5bd502..dd0968f 100644 --- a/arm9/source/ndsInfo.cpp +++ b/arm9/source/ndsInfo.cpp @@ -2,6 +2,7 @@ #include "date.h" #include "font.h" +#include "screenshot.h" #include "tonccpy.h" #include @@ -120,6 +121,8 @@ void ndsInfo(const char *path) { lang++; } else if(pressed & KEY_B) { break; + } else if(keysHeld() & KEY_R && pressed & KEY_L) { + screenshot(); } } From 283f85b9855f6f52f1bb7e111bdd18ec34814915 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sun, 15 Aug 2021 15:38:24 -0500 Subject: [PATCH 11/21] Show nitro: above carts in drive list --- arm9/source/driveMenu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arm9/source/driveMenu.cpp b/arm9/source/driveMenu.cpp index 67e41a0..2b31a4e 100644 --- a/arm9/source/driveMenu.cpp +++ b/arm9/source/driveMenu.cpp @@ -229,14 +229,14 @@ void driveMenu (void) { dmOperations.push_back(DriveMenuOperation::ramDrive2); if (imgMounted) dmOperations.push_back(DriveMenuOperation::fatImage); + if (nitroMounted) + dmOperations.push_back(DriveMenuOperation::nitroFs); if (expansionPakFound || (io_dldi_data->ioInterface.features & FEATURE_SLOT_GBA) || (isDSiMode() && !arm7SCFGLocked && !(REG_SCFG_MC & BIT(0)))) dmOperations.push_back(DriveMenuOperation::ndsCard); if (!isDSiMode() && isRegularDS) dmOperations.push_back(DriveMenuOperation::gbaCart); - if (nitroMounted) - dmOperations.push_back(DriveMenuOperation::nitroFs); dm_drawBottomScreen(); dm_drawTopScreen(); From c0e347f2b4f965cc4998aacc9ba24c8d47e80147 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sun, 15 Aug 2021 15:40:13 -0500 Subject: [PATCH 12/21] Fix double : in dumping prompts --- arm9/source/dumpOperations.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arm9/source/dumpOperations.cpp b/arm9/source/dumpOperations.cpp index 70692ea..aa8c12b 100644 --- a/arm9/source/dumpOperations.cpp +++ b/arm9/source/dumpOperations.cpp @@ -305,7 +305,7 @@ void ndsCardDump(void) { //bool showGameCardMsgAgain = false; font->clear(false); - font->printf(0, 0, false, Alignment::left, Palette::white, "Dump NDS card ROM to\n\"%s:/gm9i/out\"?", sdMounted ? "sd:" : "fat:"); + font->printf(0, 0, false, Alignment::left, Palette::white, "Dump NDS card ROM to\n\"%s:/gm9i/out\"?", sdMounted ? "sd" : "fat"); font->print(0, 2, false, "( yes, trim, no, save only)"); font->update(false); @@ -630,7 +630,7 @@ void gbaCartDump(void) { int pressed = 0; font->clear(false); - font->printf(0, 0, false, Alignment::left, Palette::white, "Dump GBA cart ROM to\n\"%s:/gm9i/out\"?", sdMounted ? "sd:" : "fat:"); + font->printf(0, 0, false, Alignment::left, Palette::white, "Dump GBA cart ROM to\n\"%s:/gm9i/out\"?", sdMounted ? "sd" : "fat"); font->print(0, 2, false, "( yes, no)"); font->update(false); From 4fd905f894ec324a89a39a6f00e3ee3be805c582 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sun, 15 Aug 2021 17:26:12 -0500 Subject: [PATCH 13/21] Fix file name being able to wrap --- arm9/source/file_browse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index 4d66546..d3a2f8f 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -160,7 +160,7 @@ void showDirectoryContents (const std::vector& dirContents, int fileOf pal = Palette::gray; } - font->print(0, i + 1, true, entry->name, Alignment::left, pal); + font->print(0, i + 1, true, entry->name.substr(0, SCREEN_COLS), Alignment::left, pal); if (entry->name == "..") { font->print(-1, i + 1, true, "(..)", Alignment::right, pal); } else if (entry->isDirectory) { From 9ec144f4eab9599d83fd75b5b3e3e2634975f096 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Wed, 18 Aug 2021 00:38:19 -0500 Subject: [PATCH 14/21] Fix resource leaks in calculateSHA1() --- arm9/source/fileOperations.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arm9/source/fileOperations.cpp b/arm9/source/fileOperations.cpp index 9a18662..15861b7 100644 --- a/arm9/source/fileOperations.cpp +++ b/arm9/source/fileOperations.cpp @@ -96,7 +96,11 @@ bool calculateSHA1(const char *fileName, u8 *sha1) { swiSHA1Update(&ctx, buf, ret); scanKeys(); int keys = keysHeld(); - if (keys & KEY_START) return false; + if (keys & KEY_START) { + free(buf); + fclose(fp); + return false; + } font->print((ftell(fp) / (fsize / (SCREEN_COLS - 2))) + 1, nameHeight + 5, false, "="); font->printf(0, nameHeight + 6, false, Alignment::left, Palette::white, "%d/%d bytes processed", ftell(fp), fsize); @@ -104,6 +108,7 @@ bool calculateSHA1(const char *fileName, u8 *sha1) { } swiSHA1Final(sha1, &ctx); free(buf); + fclose(fp); return true; } From 162700115d03702095b51c059df18e177bb19df3 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Fri, 20 Aug 2021 10:41:41 -0500 Subject: [PATCH 15/21] Speed up directory loading Doesn't load the file size until displaying it which makes it *way* faster --- arm9/source/file_browse.cpp | 65 ++++++++++++++++--------------------- arm9/source/file_browse.h | 7 ++-- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index d3a2f8f..bd1131e 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -51,8 +51,8 @@ static char path[PATH_MAX]; -bool extension(const std::string &filename, const std::vector &extensions) { - for(const std::string &ext : extensions) { +bool extension(const std::string_view filename, const std::vector &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; } @@ -77,58 +77,41 @@ bool dirEntryPredicate (const DirEntry& lhs, const DirEntry& rhs) { } void getDirectoryContents(std::vector& dirContents) { - struct stat st; - dirContents.clear(); DIR *pdir = opendir ("."); - if (pdir == NULL) { + if (pdir == nullptr) { font->print(0, 0, true, "Unable to open the directory."); font->update(true); } else { + while (true) { + dirent *pent = readdir(pdir); + if (pent == nullptr) + break; - while(true) { - DirEntry dirEntry; + if (strcmp(pent->d_name, ".") == 0 || strcmp(pent->d_name, "..") == 0) + continue; - struct dirent* pent = readdir(pdir); - if(pent == NULL) break; - - stat(pent->d_name, &st); - if (strcmp(pent->d_name, "..") != 0) { - dirEntry.name = pent->d_name; - dirEntry.isDirectory = st.st_mode & S_IFDIR; - if (!dirEntry.isDirectory) { - dirEntry.size = getFileSize(dirEntry.name.c_str()); - } - if (extension(dirEntry.name, {"nds", "argv", "dsi", "ids", "app", "srl"})) { - dirEntry.isApp = ((currentDrive == 0 && sdMounted) || (currentDrive == 1 && flashcardMounted)); - } else if (extension(dirEntry.name, {"firm"})) { - dirEntry.isApp = (isDSiMode() && is3DS && sdMounted); - } else { - dirEntry.isApp = false; - } - - if (dirEntry.name.compare(".") != 0) { - dirContents.push_back (dirEntry); - } + bool isApp = false; + if (extension(pent->d_name, {"nds", "argv", "dsi", "ids", "app", "srl"})) { + isApp = (currentDrive == 0 && sdMounted) || (currentDrive == 1 && flashcardMounted); + } else if (extension(pent->d_name, {"firm"})) { + isApp = (isDSiMode() && is3DS && sdMounted); } + dirContents.emplace_back(pent->d_name, pent->d_type == DT_DIR ? 0 : -1, pent->d_type == DT_DIR, isApp); } - closedir(pdir); } - sort(dirContents.begin(), dirContents.end(), dirEntryPredicate); + std::sort(dirContents.begin(), dirContents.end(), dirEntryPredicate); - DirEntry dirEntry; - dirEntry.name = ".."; // ".." entry - dirEntry.isDirectory = true; - dirEntry.isApp = false; - dirContents.insert (dirContents.begin(), dirEntry); // Add ".." to top of list + // Add ".." to top of list + dirContents.insert(dirContents.begin(), {"..", 0, true, false}); } -void showDirectoryContents (const std::vector& dirContents, int fileOffset, int startRow) { +void showDirectoryContents(std::vector &dirContents, int fileOffset, int startRow) { getcwd(path, PATH_MAX); font->clear(true); @@ -147,7 +130,7 @@ void showDirectoryContents (const std::vector& dirContents, int fileOf // Print directory listing for (int i = 0; i < ((int)dirContents.size() - startRow) && i < ENTRIES_PER_SCREEN; i++) { - const DirEntry *entry = &dirContents[i + startRow]; + DirEntry *entry = &dirContents[i + startRow]; Palette pal; if ((fileOffset - startRow) == i) { @@ -160,6 +143,10 @@ void showDirectoryContents (const std::vector& dirContents, int fileOf pal = Palette::gray; } + // Load size if not loaded yet + if(entry->size == -1) + entry->size = getFileSize(entry->name.c_str()); + font->print(0, i + 1, true, entry->name.substr(0, SCREEN_COLS), Alignment::left, pal); if (entry->name == "..") { font->print(-1, i + 1, true, "(..)", Alignment::right, pal); @@ -586,6 +573,10 @@ void fileBrowse_drawBottomScreen(DirEntry* entry) { font->print(0, row--, false, "X - DELETE/[+R] RENAME file\n"); font->print(0, row--, false, titleName); + // 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(0, 0, false, entry->name, Alignment::left, pal); if (entry->name != "..") { diff --git a/arm9/source/file_browse.h b/arm9/source/file_browse.h index 33a1a97..e05a959 100644 --- a/arm9/source/file_browse.h +++ b/arm9/source/file_browse.h @@ -26,8 +26,11 @@ #include struct DirEntry { + DirEntry(std::string name, size_t size, bool isDirectory, bool isApp, bool selected = false) : name(name), size(size), isDirectory(isDirectory), isApp(isApp), selected(selected) {} + DirEntry() {} + std::string name; - size_t size; + int size; bool isDirectory; bool isApp; bool selected = false; @@ -50,7 +53,7 @@ enum class FileOperation { loadFont, }; -bool extension(const std::string &filename, const std::vector &extensions); +bool extension(const std::string_view filename, const std::vector &extensions); void OnKeyPressed(int key); std::string browseForFile (void); From 41849eeaf1cb2eac58463f463cc3c6783b2a2273 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Fri, 20 Aug 2021 10:43:51 -0500 Subject: [PATCH 16/21] Update font I cleaned up the Kana a bit, just keeping this up to date with GM9's --- data/font_default.frf | Bin 6756 -> 6756 bytes resources/fonts/default-6x10.frf | Bin 6756 -> 6756 bytes 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 resources/fonts/default-6x10.frf diff --git a/data/font_default.frf b/data/font_default.frf index 59b0c3c4a536093e6ca3e94ff362605571b8ea3b..65f86e6d99f6086e9797b36898e935038d3c048a 100644 GIT binary patch delta 540 zcmZXRy-EW?5XZ-`;DHC3OEz+d=oX8t7(>7dtb%yO6)7xItQg^n6e&`q5J_>xR#vt; zK7+MjZ)4{ZSc~sq>+HqYxXt`__CKHdm3?K;gXV_GEr3)NdP8~hKB-W7RNL(PCHidk z_NPLm0Fbh+LH-f=2$jN^UwtGE9pkPt$hW-A^UcobZ9kOh_nT}*H7!=7guc%fw^SqB)J zcVUOT?PwWc_!LDEzKV9_Xlu2p|J$QGd6o#F(`7P?$hBhnkb6ABLubDETTOTo^|?l_ RxCl<@wsXRQm)Fjd_yL7;TeJWG delta 540 zcmZXQF>4z!6vq`Kh#&^PbL3z`X>K@zN)vO?Gbo`Y8g97p29-K+!wnfccsPQG8*b{< zwSD~zoeSBsWa}sB9Q+-!_AlGlB|@MdPrs+9_cqxk`#m2WS!adNaZ8|K&egv1o545a^)fELFz+$F?(MQ8D{gzGBjTv>q%yYl`5hH$Pa7ORq5TE0(H zQ{)n9qNm}oCs7qu5OM2(d=$+Nx346MU35NdLjhW>{dz%sAI-kV#G?%n*Qqhf3lM%~ zrEEcnw#l6dXKj-mHDvccYN2J;hubEuQuh%=Xvq5l#rY+w1}5gu<4*(X_~&?jknx}K zZIr57OVvQx6KBWO2Q4v1WaAAXyx$5q0aS-pdJNjY5; z2*cjx3WJsBIWi2t)#YhFsn^rvZ~7)ug&{J~Lm})p{8yh3RQF@DI-y-P*-4c`s3{a_ O`0L~vJw7~6e#w8dJYR4C diff --git a/resources/fonts/default-6x10.frf b/resources/fonts/default-6x10.frf old mode 100644 new mode 100755 index 59b0c3c4a536093e6ca3e94ff362605571b8ea3b..65f86e6d99f6086e9797b36898e935038d3c048a GIT binary patch delta 540 zcmZXRy-EW?5XZ-`;DHC3OEz+d=oX8t7(>7dtb%yO6)7xItQg^n6e&`q5J_>xR#vt; zK7+MjZ)4{ZSc~sq>+HqYxXt`__CKHdm3?K;gXV_GEr3)NdP8~hKB-W7RNL(PCHidk z_NPLm0Fbh+LH-f=2$jN^UwtGE9pkPt$hW-A^UcobZ9kOh_nT}*H7!=7guc%fw^SqB)J zcVUOT?PwWc_!LDEzKV9_Xlu2p|J$QGd6o#F(`7P?$hBhnkb6ABLubDETTOTo^|?l_ RxCl<@wsXRQm)Fjd_yL7;TeJWG delta 540 zcmZXQF>4z!6vq`Kh#&^PbL3z`X>K@zN)vO?Gbo`Y8g97p29-K+!wnfccsPQG8*b{< zwSD~zoeSBsWa}sB9Q+-!_AlGlB|@MdPrs+9_cqxk`#m2WS!adNaZ8|K&egv1o545a^)fELFz+$F?(MQ8D{gzGBjTv>q%yYl`5hH$Pa7ORq5TE0(H zQ{)n9qNm}oCs7qu5OM2(d=$+Nx346MU35NdLjhW>{dz%sAI-kV#G?%n*Qqhf3lM%~ zrEEcnw#l6dXKj-mHDvccYN2J;hubEuQuh%=Xvq5l#rY+w1}5gu<4*(X_~&?jknx}K zZIr57OVvQx6KBWO2Q4v1WaAAXyx$5q0aS-pdJNjY5; z2*cjx3WJsBIWi2t)#YhFsn^rvZ~7)ug&{J~Lm})p{8yh3RQF@DI-y-P*-4c`s3{a_ O`0L~vJw7~6e#w8dJYR4C From 2bc47b299d491ad10978bdc26b8fc23c4e98f85b Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sat, 21 Aug 2021 17:39:39 -0500 Subject: [PATCH 17/21] Propely substr file names --- arm9/source/file_browse.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index bd1131e..5688616 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -147,7 +147,13 @@ void showDirectoryContents(std::vector &dirContents, int fileOffset, i if(entry->size == -1) entry->size = getFileSize(entry->name.c_str()); - font->print(0, i + 1, true, entry->name.substr(0, SCREEN_COLS), Alignment::left, pal); + int nameSize = 0; + for(int i = 0; i < SCREEN_COLS; nameSize++) { + if((entry->name[nameSize] & 0xC0) != 0x80) + i++; + } + + font->print(0, i + 1, true, entry->name.substr(0, nameSize), Alignment::left, pal); if (entry->name == "..") { font->print(-1, i + 1, true, "(..)", Alignment::right, pal); } else if (entry->isDirectory) { From d0122e0c122cd5f2a6201b6e9cac74d4eb7b3422 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Thu, 2 Sep 2021 10:25:09 -0500 Subject: [PATCH 18/21] =?UTF-8?q?Add=20=E7=BE=8E=E5=92=B2=E3=82=B4?= =?UTF-8?q?=E3=82=B7=E3=83=83=E3=82=AF=20(Misaki=20Gothic)=20font?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/fonts/misaki-gothic-8x8.frf | Bin 0 -> 70408 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/fonts/misaki-gothic-8x8.frf diff --git a/resources/fonts/misaki-gothic-8x8.frf b/resources/fonts/misaki-gothic-8x8.frf new file mode 100644 index 0000000000000000000000000000000000000000..2ec473ced5a099d23aabf0734abbc03d7c5b0afc GIT binary patch literal 70408 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

xUet%&QcGy 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 jEkjdi8qJ_tG>7KVa Date: Thu, 2 Sep 2021 11:01:49 -0500 Subject: [PATCH 19/21] Copy over Arabic support from TWiLight Currently not really needed, but if we make GM9i translatable (which I'll prolly do after this PR is merged) it'll be needed so might as well just do it now --- arm9/source/font.cpp | 33 ++++++++++++++++++++++++++---- arm9/source/font.h | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/arm9/source/font.cpp b/arm9/source/font.cpp index ef5631e..a4aaeba 100644 --- a/arm9/source/font.cpp +++ b/arm9/source/font.cpp @@ -7,14 +7,20 @@ #include #include #include -#include + u8 Font::textBuf[2][256 * 192]; bool Font::mainScreen = false; Font *font = nullptr; +// Specifically the Arabic letters that have supported presentation forms +bool Font::isArabic(char16_t c) { + return c >= 0x0622 && c <= 0x064A; +} + bool Font::isStrongRTL(char16_t c) { - return (c >= 0x0590 && c <= 0x05FF) || c == 0x200F; + // Hebrew, Arabic, or RLM + return (c >= 0x0590 && c <= 0x05FF) || (c >= 0x0600 && c <= 0x06FF) || c == 0x200F; } bool Font::isWeak(char16_t c) { @@ -25,6 +31,25 @@ bool Font::isNumber(char16_t c) { return c >= '0' && c <= '9'; } +char16_t Font::arabicForm(char16_t current, char16_t prev, char16_t next) { + if(isArabic(current)) { + // If previous should be connected to + if((prev >= 0x626 && prev <= 0x62E && prev != 0x627 && prev != 0x629) || (prev >= 0x633 && prev <= 0x64A && prev != 0x647)) { + if(isArabic(next)) // If next is arabic, medial + return arabicPresentationForms[current - 0x622][1]; + else // If not, final + return arabicPresentationForms[current - 0x622][2]; + } else { + if(isArabic(next)) // If next is arabic, initial + return arabicPresentationForms[current - 0x622][0]; + else // If not, isolated + return current; + } + } + + return current; +} + bool Font::load(const char *path) { FILE *file = fopen(path, "rb"); @@ -395,7 +420,7 @@ ITCM_CODE void Font::print(int xPos, int yPos, bool top, std::u16string_view tex index = getCharIndex('<'); break; default: - index = getCharIndex(*it); + index = getCharIndex(arabicForm(*it, *(it - 1), *(it + 1))); break; } } else { @@ -403,7 +428,7 @@ ITCM_CODE void Font::print(int xPos, int yPos, bool top, std::u16string_view tex } // Don't draw off screen chars - if(x >= 0 && y >= 0 && y + tileHeight <= 192) { + if(x >= 0 && x + tileWidth <= 256 && y >= 0 && y + tileHeight <= 192) { u8 *dst = textBuf[top] + x; for(int i = 0; i < tileHeight; i++) { u8 px = fontTiles[(index * tileHeight) + i]; diff --git a/arm9/source/font.h b/arm9/source/font.h index d2224f3..599de89 100644 --- a/arm9/source/font.h +++ b/arm9/source/font.h @@ -34,10 +34,58 @@ enum class Palette : u8 { }; class Font { + constexpr static char16_t arabicPresentationForms[][3] = { + // Initial, Medial, Final + {u'آ', u'ﺂ', u'ﺂ'}, // Alef with madda above + {u'أ', u'ﺄ', u'ﺄ'}, // Alef with hamza above + {u'ؤ', u'ﺆ', u'ﺆ'}, // Waw with hamza above + {u'إ', u'ﺈ', u'ﺈ'}, // Alef with hamza below + {u'ﺋ', u'ﺌ', u'ﺊ'}, // Yeh with hamza above + {u'ا', u'ﺎ', u'ﺎ'}, // Alef + {u'ﺑ', u'ﺒ', u'ﺐ'}, // Beh + {u'ة', u'ﺔ', u'ﺔ'}, // Teh marbuta + {u'ﺗ', u'ﺘ', u'ﺖ'}, // Teh + {u'ﺛ', u'ﺜ', u'ﺚ'}, // Theh + {u'ﺟ', u'ﺠ', u'ﺞ'}, // Jeem + {u'ﺣ', u'ﺤ', u'ﺢ'}, // Hah + {u'ﺧ', u'ﺨ', u'ﺦ'}, // Khah + {u'د', u'ﺪ', u'ﺪ'}, // Dal + {u'ذ', u'ﺬ', u'ﺬ'}, // Thal + {u'ر', u'ﺮ', u'ﺮ'}, // Reh + {u'ز', u'ﺰ', u'ﺰ'}, // Zain + {u'ﺳ', u'ﺴ', u'ﺲ'}, // Seen + {u'ﺷ', u'ﺸ', u'ﺶ'}, // Sheen + {u'ﺻ', u'ﺼ', u'ﺺ'}, // Sad + {u'ﺿ', u'ﻀ', u'ﺾ'}, // Dad + {u'ﻃ', u'ﻄ', u'ﻂ'}, // Tah + {u'ﻇ', u'ﻈ', u'ﻆ'}, // Zah + {u'ﻋ', u'ﻌ', u'ﻊ'}, // Ain + {u'ﻏ', u'ﻐ', u'ﻎ'}, // Ghain + {u'ػ', u'ػ', u'ػ'}, // Keheh with two dots above + {u'ؼ', u'ؼ', u'ؼ'}, // Keheh with three dots below + {u'ؽ', u'ؽ', u'ؽ'}, // Farsi yeh with inverted v + {u'ؾ', u'ؾ', u'ؾ'}, // Farsi yeh with two dots above + {u'ؿ', u'ؿ', u'ؿ'}, // Farsi yeh with three docs above + {u'ـ', u'ـ', u'ـ'}, // Tatweel + {u'ﻓ', u'ﻔ', u'ﻒ'}, // Feh + {u'ﻗ', u'ﻘ', u'ﻖ'}, // Qaf + {u'ﻛ', u'ﻜ', u'ﻚ'}, // Kaf + {u'ﻟ', u'ﻠ', u'ﻞ'}, // Lam + {u'ﻣ', u'ﻤ', u'ﻢ'}, // Meem + {u'ﻧ', u'ﻨ', u'ﻦ'}, // Noon + {u'ﻫ', u'ﻬ', u'ﻪ'}, // Heh + {u'و', u'ﻮ', u'ﻮ'}, // Waw + {u'ﯨ', u'ﯩ', u'ﻰ'}, // Alef maksura + {u'ﻳ', u'ﻴ', u'ﻲ'}, // Yeh + }; + + static bool isArabic(char16_t c); static bool isStrongRTL(char16_t c); static bool isWeak(char16_t c); static bool isNumber(char16_t c); + static char16_t arabicForm(char16_t current, char16_t prev, char16_t next); + static constexpr u16 palette[16][2] = { {0x0000, 0x7FFF}, // White {0x0000, 0x3DEF}, // Gray From 0f70de49d0a9ead2f66cd1b81229571d7a72472e Mon Sep 17 00:00:00 2001 From: Pk11 Date: Thu, 2 Sep 2021 20:19:00 -0500 Subject: [PATCH 20/21] Derp fix --- arm9/source/font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/font.cpp b/arm9/source/font.cpp index a4aaeba..a4e0139 100644 --- a/arm9/source/font.cpp +++ b/arm9/source/font.cpp @@ -34,7 +34,7 @@ bool Font::isNumber(char16_t c) { char16_t Font::arabicForm(char16_t current, char16_t prev, char16_t next) { if(isArabic(current)) { // If previous should be connected to - if((prev >= 0x626 && prev <= 0x62E && prev != 0x627 && prev != 0x629) || (prev >= 0x633 && prev <= 0x64A && prev != 0x647)) { + if((prev >= 0x626 && prev <= 0x62E && prev != 0x627 && prev != 0x629) || (prev >= 0x633 && prev <= 0x64A && prev != 0x648)) { if(isArabic(next)) // If next is arabic, medial return arabicPresentationForms[current - 0x622][1]; else // If not, final From 9de1d80ca1917471a78cdb74adbbf7c55dc8cd50 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Wed, 15 Sep 2021 17:35:50 -0500 Subject: [PATCH 21/21] Fix non-auxspi bytes progress --- arm9/source/dumpOperations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/dumpOperations.cpp b/arm9/source/dumpOperations.cpp index be6b2d7..1cdafc5 100644 --- a/arm9/source/dumpOperations.cpp +++ b/arm9/source/dumpOperations.cpp @@ -298,7 +298,7 @@ void ndsCardSaveRestore(const char *filename) { font->print(-1, 5, false, "]"); for(unsigned int i = 0; i < 32; i++) { font->print((i * (SCREEN_COLS - 2) / 32) + 1, 5, false, "="); - font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", i * LEN, length); + font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", written, size); font->update(false); fread(buffer, 1, blocks, in);