commit b6816abe56f8856297cd8ab25240f5e95f9cca2d Author: yellows8 Date: Sun Feb 11 19:07:18 2018 -0500 Initial public commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d09ee29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*~ +*.exe +*.o +test +tahoma12.c +tahoma24.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..08ecf11 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +all: + make -f Makefile.nx + make -f Makefile.pc + +clean: + make -f Makefile.pc clean + make -f Makefile.nx clean diff --git a/Makefile.nx b/Makefile.nx new file mode 100644 index 0000000..5ba762c --- /dev/null +++ b/Makefile.nx @@ -0,0 +1,189 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# 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 +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := common/ nx_main/ nx_main/loaders/ +DATA := data +INCLUDES := include +EXEFS_SRC := exefs_src + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 \ + -ffast-math \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -DSWITCH + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# 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 OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +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 +#--------------------------------------------------------------------------------- +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),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.nx + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).pfs0 $(OUTPUT).nro + +$(OUTPUT).pfs0 : $(OUTPUT).nso + +$(OUTPUT).nso : $(OUTPUT).elf + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +%.nxfnt.o : %.nxfnt +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Makefile.pc b/Makefile.pc new file mode 100644 index 0000000..24556a7 --- /dev/null +++ b/Makefile.pc @@ -0,0 +1,38 @@ +# canned command sequence for binary data +#--------------------------------------------------------------------------------- +define bin2o + bin2s $< | $(AS) -o $(@) + echo "extern const u8" `(echo $( `(echo $(> `(echo $(> `(echo $( +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SWITCH +#include +#endif + +#include +typedef uint8_t u8; +typedef uint32_t u32; +typedef uint64_t u64; + +#define M_TAU (2*M_PI) + +typedef union { + uint32_t abgr; + struct { + uint8_t r,g,b,a; + }; +} color_t; + +#include "font.h" +#include "nacp.h" +#include "menu.h" +#include "text.h" +#include "ui.h" +#include "launch.h" +#include "nro.h" +#include "nanojpeg.h" + +void menuStartup(); +void menuLoop(); + +static inline uint8_t BlendColor(uint32_t src, uint32_t dst, uint8_t alpha) +{ + uint8_t one_minus_alpha = (uint8_t)255 - alpha; + return (dst*alpha + src*one_minus_alpha)/(uint8_t)255; +} + +static inline color_t MakeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + color_t clr; + clr.r = r; + clr.g = g; + clr.b = b; + clr.a = a; + return clr; +} + +#ifdef SWITCH +extern uint8_t* g_framebuf; +static inline void DrawPixel(uint32_t x, uint32_t y, color_t clr) +{ + if (x >= 1280 || y >= 720) + return; + u32 off = 4*gfxGetFramebufferDisplayOffset(x, y); + g_framebuf[off] = BlendColor(g_framebuf[off], clr.r, clr.a); off++; + g_framebuf[off] = BlendColor(g_framebuf[off], clr.g, clr.a); off++; + g_framebuf[off] = BlendColor(g_framebuf[off], clr.b, clr.a); off++; + g_framebuf[off] = 0xff; +} +static inline void DrawPixelRaw(uint32_t x, uint32_t y, color_t clr) +{ + if (x >= 1280 || y >= 720) + return; + u32 off = 4*gfxGetFramebufferDisplayOffset(x, y); + g_framebuf[off] = clr.r; off++; + g_framebuf[off] = clr.g; off++; + g_framebuf[off] = clr.b; off++; + g_framebuf[off] = 0xff; +} +#else +extern color_t pixels[720][1280]; +static inline void DrawPixel(uint32_t x, uint32_t y, color_t clr) +{ + if (x >= 1280 || y >= 720) + return; + pixels[y][x].r = BlendColor(pixels[y][x].r, clr.r, clr.a); + pixels[y][x].g = BlendColor(pixels[y][x].g, clr.g, clr.a); + pixels[y][x].b = BlendColor(pixels[y][x].b, clr.b, clr.a); + pixels[y][x].a = 0xff; +} +static inline void DrawPixelRaw(uint32_t x, uint32_t y, color_t clr) +{ + if (x >= 1280 || y >= 720) + return; + pixels[y][x].r = clr.r; + pixels[y][x].g = clr.g; + pixels[y][x].b = clr.b; + pixels[y][x].a = 0xff; +} +#endif + +void DrawPixel(uint32_t x, uint32_t y, color_t clr); +void DrawText(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text); +void DrawTextTruncate(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text); diff --git a/common/font.c b/common/font.c new file mode 100644 index 0000000..9123d90 --- /dev/null +++ b/common/font.c @@ -0,0 +1,168 @@ +#include "common.h" + +static inline const ffnt_page_t* FontGetPage(const ffnt_header_t* font, uint32_t page_id) +{ + //__builtin_printf("GetPage %u\n", (unsigned int)page_id); + if (page_id >= font->npages) + return NULL; + ffnt_pageentry_t* ent = &((ffnt_pageentry_t*)(font+1))[page_id]; + if (ent->size == 0) + return NULL; + return (const ffnt_page_t*)((const uint8_t*)font + ent->offset); +} + +static inline bool FontLoadGlyph(glyph_t* glyph, const ffnt_header_t* font, uint32_t codepoint) +{ + //__builtin_printf("LoadGlyph %u\n", (unsigned int)codepoint); + const ffnt_page_t* page = FontGetPage(font, codepoint >> 8); + if (!page) + return false; + + codepoint &= 0xFF; + uint32_t off = page->hdr.pos[codepoint]; + if (off == ~(uint32_t)0) + return false; + + //__builtin_printf("%c %u\n", (char)codepoint, (unsigned int)off); + glyph->width = page->hdr.widths[codepoint]; + glyph->height = page->hdr.heights[codepoint]; + glyph->advance = page->hdr.advances[codepoint]; + glyph->posX = page->hdr.posX[codepoint]; + glyph->posY = page->hdr.posY[codepoint]; + glyph->data = &page->data[off]; + return true; +} + +static void DrawGlyph(uint32_t x, uint32_t y, color_t clr, const glyph_t* glyph) +{ + uint32_t i, j; + const uint8_t* data = glyph->data; + x += glyph->posX; + y += glyph->posY; + //__builtin_printf("DrawGlyph %u %u %08X\n", (unsigned int)x, (unsigned int)y, (unsigned int)clr.abgr); + for (j = 0; j < glyph->height; j ++) + { + for (i = 0; i < glyph->width; i ++) + { + clr.a = *data++; + if (!clr.a) continue; + DrawPixel(x+i, y+j, clr); + } + } +} + +static inline uint8_t DecodeByte(const char** ptr) +{ + uint8_t c = (uint8_t)**ptr; + *ptr += 1; + return c; +} + +// UTF-8 code adapted from http://www.json.org/JSON_checker/utf8_decode.c + +static inline int8_t DecodeUTF8Cont(const char** ptr) +{ + int c = DecodeByte(ptr); + return ((c & 0xC0) == 0x80) ? (c & 0x3F) : -1; +} + +static inline uint32_t DecodeUTF8(const char** ptr) +{ + uint32_t r; + uint8_t c; + int8_t c1, c2, c3; + + c = DecodeByte(ptr); + if ((c & 0x80) == 0) + return c; + if ((c & 0xE0) == 0xC0) + { + c1 = DecodeUTF8Cont(ptr); + if (c1 >= 0) + { + r = ((c & 0x1F) << 6) | c1; + if (r >= 0x80) + return r; + } + } else if ((c & 0xF0) == 0xE0) + { + c1 = DecodeUTF8Cont(ptr); + if (c1 >= 0) + { + c2 = DecodeUTF8Cont(ptr); + if (c2 >= 0) + { + r = ((c & 0x0F) << 12) | (c1 << 6) | c2; + if (r >= 0x800 && (r < 0xD800 || r >= 0xE000)) + return r; + } + } + } else if ((c & 0xF8) == 0xF0) + { + c1 = DecodeUTF8Cont(ptr); + if (c1 >= 0) + { + c2 = DecodeUTF8Cont(ptr); + if (c2 >= 0) + { + c3 = DecodeUTF8Cont(ptr); + if (c3 >= 0) + { + r = ((c & 0x07) << 18) | (c1 << 12) | (c2 << 6) | c3; + if (r >= 0x10000 && r < 0x110000) + return r; + } + } + } + } + return 0xFFFD; +} + +static void DrawText_(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text) +{ + //__builtin_printf("DrawText %u %u %08X %s\n", (unsigned int)x, (unsigned int)y, (unsigned int)clr.abgr, text); + y += font->baseline; + uint32_t origX = x; + while (*text) + { + if (max_width && x-origX >= max_width) { + text = end_text; + max_width = 0; + } + + glyph_t glyph; + uint32_t codepoint = DecodeUTF8(&text); + + if (codepoint == '\n') + { + if (max_width) { + text = end_text; + max_width = 0; + continue; + } + + x = origX; + y += font->height; + continue; + } + + if (!FontLoadGlyph(&glyph, font, codepoint)) + { + if (!FontLoadGlyph(&glyph, font, '?')) + continue; + } + + DrawGlyph(x, y, clr, &glyph); + x += glyph.advance; + } +} + +void DrawText(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text) +{ + DrawText_(font, x, y, clr, text, 0, NULL); +} + +void DrawTextTruncate(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text) +{ + DrawText_(font, x, y, clr, text, max_width, end_text); +} diff --git a/common/font.h b/common/font.h new file mode 100644 index 0000000..b937356 --- /dev/null +++ b/common/font.h @@ -0,0 +1,36 @@ +typedef struct { + uint8_t magic[4]; // 'fFNT' + int version; // 1 + uint16_t npages; + uint8_t height; + uint8_t baseline; +} ffnt_header_t; + +typedef struct { + uint32_t size, offset; +} ffnt_pageentry_t; + +typedef struct { + uint32_t pos[0x100]; + uint8_t widths[0x100]; + uint8_t heights[0x100]; + int8_t advances[0x100]; + int8_t posX[0x100]; + int8_t posY[0x100]; +} ffnt_pagehdr_t; + +typedef struct { + ffnt_pagehdr_t hdr; + uint8_t data[]; +} ffnt_page_t; + +typedef struct { + uint8_t width, height; + int8_t posX, posY, advance; + const uint8_t* data; +} glyph_t; + +extern const ffnt_header_t tahoma24_nxfnt; +extern const ffnt_header_t tahoma12_nxfnt; +#define tahoma24 &tahoma24_nxfnt +#define tahoma12 &tahoma12_nxfnt diff --git a/common/language.c b/common/language.c new file mode 100644 index 0000000..8423809 --- /dev/null +++ b/common/language.c @@ -0,0 +1,773 @@ +#include "language.h" + +//TODO: Update this once libnx supports settings get-language. + +#define STR_JP(_str) [/*CFG_LANGUAGE_JP*/0] = _str +#define STR_EN(_str) [/*CFG_LANGUAGE_EN*/1] = _str +#define STR_FR(_str) [/*CFG_LANGUAGE_FR*/2] = _str +#define STR_DE(_str) [/*CFG_LANGUAGE_DE*/3] = _str +#define STR_IT(_str) [/*CFG_LANGUAGE_IT*/4] = _str +#define STR_ES(_str) [/*CFG_LANGUAGE_ES*/5] = _str +#define STR_ZH(_str) [/*CFG_LANGUAGE_ZH*/6] = _str +#define STR_KO(_str) [/*CFG_LANGUAGE_KO*/7] = _str +#define STR_NL(_str) [/*CFG_LANGUAGE_NL*/8] = _str +#define STR_PT(_str) [/*CFG_LANGUAGE_PT*/9] = _str +#define STR_RU(_str) [/*CFG_LANGUAGE_RU*/10] = _str +#define STR_TW(_str) [/*CFG_LANGUAGE_TW*/11] = _str + +const char* const g_strings[StrId_Max][16] = +{ + [StrId_Loading] = + { + STR_EN("Loading…"), + STR_ES("Cargando…"), + STR_DE("Lade…"), + STR_FR("Chargement…"), + STR_IT("Caricamento…"), + STR_JP("ロード中…"), + STR_PT("Carregando…"), + STR_NL("Laden…"), + STR_KO("로딩중…"), + STR_RU("загрузка…"), + STR_ZH("加载中…"), + STR_TW("加載中…"), + }, + + [StrId_Directory] = + { + STR_EN("Directory"), + STR_ES("Carpeta"), + STR_DE("Verzeichnis"), + STR_FR("Dossier"), + STR_IT("Cartella"), + STR_JP("フォルダ"), + STR_PT("Directório"), + STR_NL("Map"), + STR_KO("디렉토리"), + STR_RU("каталог"), + STR_ZH("目录"), + STR_TW("資料夾"), + }, + + [StrId_DefaultVersion] = + { + STR_EN("1.0.0"), + STR_ES("1.0.0"), + STR_DE("1.0.0"), + STR_FR("1.0.0"), + STR_IT("1.0.0"), + STR_JP("1.0.0"), + STR_PT("1.0.0"), + STR_NL("1.0.0"), + STR_KO("1.0.0"), + STR_RU("1.0.0"), + STR_ZH("1.0.0"), + STR_TW("1.0.0"), + }, + + /*[StrId_DefaultLongTitle] = + { + STR_EN("Homebrew application"), + STR_ES("Aplicación homebrew"), + STR_DE("Homebrew-Anwendung"), + STR_FR("Application homebrew"), + STR_IT("Applicazione homebrew"), + STR_JP("自作アプリ"), + STR_PT("Aplicação Homebrew"), + STR_NL("Homebrew toepassing"), + STR_KO("홈브류 애플리케이션"), + STR_RU("приложение хомебреw"), + STR_ZH("自制应用程序"), + STR_TW("自製程式"), + },*/ + + [StrId_DefaultPublisher] = + { + STR_EN("Unknown author"), + STR_ES("Autor desconocido"), + STR_DE("Unbekannter Autor"), + STR_FR("Auteur inconnu"), + STR_IT("Autore sconosciuto"), + STR_JP("未知の作者"), + STR_PT("Autor Desconhecido"), + STR_NL("Auteur onbekend"), + STR_KO("작자미상"), + STR_RU("неизвестный автор"), + STR_ZH("未知作者"), + STR_TW("未知作者"), + }, + + [StrId_IOError] = + { + STR_EN("I/O Error"), + STR_ES("Error de E/S"), + STR_DE("E/A-Fehler"), + STR_FR("Erreur d'E/S"), + STR_IT("Errore di I/O"), + STR_JP("入出力エラー"), + STR_PT("Erro de E/S"), + STR_NL("I/O Fout"), + STR_KO("I/O 에러"), + STR_RU("I/O-ошибка"), + STR_ZH("读写出错"), + STR_TW("讀寫錯誤"), + }, + + [StrId_CouldNotOpenFile] = + { + STR_EN("Could not open file:\n%s"), + STR_ES("No se pudo abrir el archivo:\n%s"), + STR_DE("Konnte Datei nicht öffnen:\n%s"), + STR_FR("Impossible d'ouvrir le fichier :\n%s"), + STR_IT("Impossibile aprire il file:\n%s"), + STR_JP("ファイルを開くことができませんでした:\n%s"), + STR_PT("Não foi possível abrir o ficheiro:\n%s"), + STR_NL("Kan bestand niet openen:\n%s"), + STR_KO("파일을 열 수 없습니다:\n%s"), + STR_RU("Не могу открыть файл:\n%s"), + STR_ZH("无法打开文件:\n%s"), + STR_TW("開啓檔案失敗:\n%s"), + }, + + [StrId_NoAppsFound_Title] = + { + STR_EN("No applications found"), + STR_ES("No hay aplicaciones"), + STR_DE("Keine Anwendungen gefunden"), + STR_FR("Aucune application trouvée"), + STR_IT("Nessun'applicazione trovata"), + STR_JP("アプリが見つかりませんでした"), + STR_PT("Não foram encontradas aplicações"), + STR_NL("Geen toepassingen gevonden"), + STR_KO("애플리케이션을 찾을 수 없습니다"), + STR_RU("приложение не найдено"), + STR_ZH("找不到可执行的自制程序"), + STR_TW("未能找到可執行的自製程式"), + }, + + [StrId_NoAppsFound_Msg] = + { + STR_EN( + "No applications could be found on the SD card.\n" + "Make sure a folder named /switch exists in the\n" + "root of the SD card and it contains applications.\n" + ), + STR_ES( + "No se han podido encontrar aplicaciones en la\n" + "tarjeta SD. Compruebe que haya una carpeta\n" + "llamada /switch y que contenga aplicaciones.\n" + ), + STR_DE( + "Auf der SD-Karte wurden keine Anwendungen\n" + "gefunden. Stelle sicher, dass ein Verzeichnis\n" + "namens /switch im Wurzelverzeichnis der SD-Karte\n" + "existiert und Anwendungen enthält!" + ), + STR_FR( + "Aucune application n'a été trouvée sur la carte\n" + "SD. Veillez à ce qu'un dossier intitulé /switch\n" + "existe à la racine de la carte SD et à ce qu'il\n" + "contienne des applications." + ), + STR_IT( + "Nessun'applicazione è stata trovata sulla scheda\n" + "SD. Assicurati che esista una cartella chiamata\n" + "/switch nella root della scheda SD e che contenga\n" + "delle applicazioni." + ), + STR_JP( + "SDカードにアプリケーションが見つかりませんでした。\n" + "SDカードのルートに「/switch」という名前のフォルダを\n" + "作成してください。" + ), + STR_PT( + "Nenhuma aplicação foi encontrada no cartão SD.\n" + "Certifique-se que uma pasta com o nome /switch\n" + "existe na raiz do cartão SD e que contêm\n" + "aplicações." + ), + STR_NL( + "Geen toepassingen gevonden op de SD kaart.\n" + "Zorg ervoor dat een map genaamd /switch in de\n" + "rootdirectory van de SD kaart aangemaakt is\n" + "en de toepassingen bevat." + ), + STR_KO( + "애플리케이션을 SD 카드에서 찾을 수 없습니다.\n" + "SD 카드 최상단에 /switch 라는 이름의 폴더가 있는지,\n" + "애플리케이션을 포함하고 있는지 확인해 주십시오." + ), + STR_RU( + "На SD-карте нет приложений.\n" + "Убедитесь, что на карте SD есть каталог с\n" + "названием switch и она содержит приложения." + ), + STR_ZH( + "内存卡找不到任何可执行的应用程序。\n" + "请在内存卡的根目录建立「switch」子目录,\n" + "并存放自制应用软件至该目录。" + ), + STR_TW( + "記憶體找不到任何可執行的應用程式。\n" + "請在記憶體建立「switch」資料夾,\n" + "然後儲存自製軟體到此處。" + ), + }, + + /*[StrId_Reboot] = + { + STR_EN( + "Returning to \xEE\x81\xB3HOME is not available.\n" + "You're about to reboot your console.\n\n" + " \xEE\x80\x80 Reboot\n" + " \xEE\x80\x81 Cancel" + ), + STR_ES( + "Volver a \xEE\x81\xB3HOME no está disponible.\n" + "Está a punto de reiniciar su consola.\n\n" + " \xEE\x80\x80 Reiniciar\n" + " \xEE\x80\x81 Cancelar" + ), + STR_DE( + "Rückkehr zu \xEE\x81\xB3HOME nicht verfügbar.\n" + "Deine Konsole wird neu gestartet.\n\n" + " \xEE\x80\x80 Neu starten\n" + " \xEE\x80\x81 Abbrechen" + ), + STR_FR( + "Retour au menu \xEE\x81\xB3HOME indisponible.\n" + "Vous êtes sur le point de redémarrer\n" + "votre console.\n\n" + " \xEE\x80\x80 Redémarrer\n" + " \xEE\x80\x81 Annuler" + ), + STR_IT( + "Ritorno al menu \xEE\x81\xB3HOME non disponibile.\n" + "Stai per riavviare la tua console.\n\n" + " \xEE\x80\x80 Riavvia\n" + " \xEE\x80\x81 Annulla" + ), + STR_JP( + "\xEE\x81\xB3HOMEに戻ることはできません。\n" + "コンソールが今すぐ再起動する。\n\n" + " \xEE\x80\x80 再起動\n" + " \xEE\x80\x81 キャンセル" + ), + STR_PT( + "Regressar para \xEE\x81\xB3HOME não está\n" + "disponível. Está a reiniciar a sua consola.\n\n" + " \xEE\x80\x80 Reiniciar\n" + " \xEE\x80\x81 Cancelar" + ), + STR_NL( + "Terugkeren naar \xEE\x81\xB3HOME is niet\n" + "beschikbaar.Wil je de console herstarten?\n\n" + " \xEE\x80\x80 Herstarten\n" + " \xEE\x80\x81 Annuleren" + ), + STR_KO( + "\xEE\x81\xB3홈으로 돌아갈 수 없습니다.\n" + "당신의 기기를 리부팅 하려 합니다.\n\n" + " \xEE\x80\x80 리부팅\n" + " \xEE\x80\x81 취소" + ), + STR_RU( + "Возврат к \xEE\x81\xB3HOME недоступен.\n" + "Вы собираетесь перезагрузить консоль.\n\n" + " \xEE\x80\x80 Перезагрузите\n" + " \xEE\x80\x81 Отмена" + ), + STR_ZH( + "无法返回至主机的 \xEE\x81\xB3HOME 菜单。\n" + "您需要重新启动您的 3DS 设备。\n\n" + " \xEE\x80\x80 重启设备\n" + " \xEE\x80\x81 取消操作" + ), + STR_TW( + "無法返回至主機的 \xEE\x81\xB3HOME 選單。\n" + "您需要重新啓動您的 3DS 設備。\n\n" + " \xEE\x80\x80 重啓設備\n" + " \xEE\x80\x81 取消操作" + ), + },*/ + + /*[StrId_ReturnToHome] = + { + STR_EN( + "You're about to return to \xEE\x81\xB3HOME.\n\n" + " \xEE\x80\x80 Return\n" + " \xEE\x80\x81 Cancel\n" + " \xEE\x80\x82 Reboot" + ), + STR_ES( + "Está a punto de volver a \xEE\x81\xB3HOME.\n\n" + " \xEE\x80\x80 Volver\n" + " \xEE\x80\x81 Cancelar\n" + " \xEE\x80\x82 Reiniciar" + ), + STR_DE( + "Rückkehr zum \xEE\x81\xB3HOME-Menü.\n\n" + " \xEE\x80\x80 Fortfahren\n" + " \xEE\x80\x81 Abbrechen\n" + " \xEE\x80\x82 Konsole neustarten" + ), + STR_FR( + "Retour au menu \xEE\x81\xB3HOME.\n\n" + " \xEE\x80\x80 Continuer\n" + " \xEE\x80\x81 Annuler\n" + " \xEE\x80\x82 Redémarrer" + ), + STR_IT( + "Ritorno al menu \xEE\x81\xB3HOME.\n\n" + " \xEE\x80\x80 Continua\n" + " \xEE\x80\x81 Annulla\n" + " \xEE\x80\x82 Riavvia" + ), + STR_JP( + "あなたは今すぐ\xEE\x81\xB3HOMEに戻されます。\n\n" + " \xEE\x80\x80 戻る\n" + " \xEE\x80\x81 キャンセル\n" + " \xEE\x80\x82 再起動" + ), + STR_PT( + "Regressar ao menu \xEE\x81\xB3HOME.\n\n" + " \xEE\x80\x80 Regressar\n" + " \xEE\x80\x81 Cancelar\n" + " \xEE\x80\x82 Reiniciar" + ), + STR_NL( + "Je keert zo terug naar \xEE\x81\xB3HOME.\n\n" + " \xEE\x80\x80 Doorgaan\n" + " \xEE\x80\x81 Annuleren\n" + " \xEE\x80\x82 Herstarten" + ), + STR_KO( + "\xEE\x81\xB3홈으로 돌아가려 합니다.\n\n" + " \xEE\x80\x80 이동\n" + " \xEE\x80\x81 취소\n" + " \xEE\x80\x82 리부팅" + ), + STR_RU( + "Вы возвращаетесь в \xEE\x81\xB3HOME.\n\n" + " \xEE\x80\x80 Вернуть\n" + " \xEE\x80\x81 Отмена\n" + " \xEE\x80\x82 Перезагрузите" + ), + STR_ZH( + "您即将返回到主機的 \xEE\x81\xB3HOME 菜单。\n\n" + " \xEE\x80\x80 确认返回\n" + " \xEE\x80\x81 取消操作\n" + " \xEE\x80\x82 重启设备" + ), + STR_TW( + "您即將返回到主機的 \xEE\x81\xB3HOME 選單。\n\n" + " \xEE\x80\x80 確認返回\n" + " \xEE\x80\x81 取消操作\n" + " \xEE\x80\x82 重啓設備" + ), + },*/ + + /*[StrId_TitleSelector] = + { + STR_EN("Title selector"), + STR_ES("Selector de título"), + STR_DE("Titel-Selektor"), + STR_FR("Sélecteur de titre"), + STR_IT("Selettore del titolo"), + STR_JP("タイトルセレクタ"), + STR_PT("Selector de Títulos"), + STR_NL("Titel selector"), + STR_KO("타이틀 선택기"), + STR_RU("Селектор заголовков"), + STR_ZH("应用启动器"), + STR_TW("自製程式啓動器"), + }, + + [StrId_ErrorReadingTitleMetadata] = + { + STR_EN("Error reading title metadata.\n%08lX%08lX@%d"), + STR_ES("Error leyendo los metadatos de los títulos.\n%08lX%08lX@%d"), + STR_DE("Fehler beim lesen der Titel-Metadaten.\n%08lX%08lX@%d"), + STR_FR( + "Erreur lors de la lecture des métadonnées\n" + "de titre.\n%08lX%08lX@%d" + ), + STR_IT("Errore nella lettura dei metadata dei titoli.\n%08lX%08lX@%d"), + STR_JP("タイトルメタデータを読み取ることができませんでした。\n%08lX%08lX@%d"), + STR_PT("Erro a ler os metadados do título.\n%08lX%08lX@%d"), + STR_NL("Fout bij het lezen van titel metadata.\n%08lX%08lX@%d"), + STR_KO("타이틀 메타데이터를 읽는데 실패하였습니다.\n%08lX%08lX@%d"), + STR_RU("Ошибка чтения метаданных заголовка\n.%08lX%08lX@%d"), + STR_ZH("读取软件相关信息时发生错误:\n%08lX%08lX@%d"), + STR_TW("讀取軟體相關數據時發生錯誤:\n%08lX%08lX@%d"), + }, + + [StrId_NoTitlesFound] = + { + STR_EN("No titles could be detected."), + STR_ES("No se han podido detectar títulos."), + STR_DE("Keine Titel gefunden."), + STR_FR("Aucun titre trouvé."), + STR_IT("Nessun titolo trovato."), + STR_JP("タイトルが見つかりませんでした。"), + STR_PT("Nenhum título foi encontrado."), + STR_NL("Geen titels gevonden."), + STR_KO("타이틀을 찾지 못하였습니다."), + STR_RU("Заголовки не обнаружены"), + STR_ZH("主机内找不到任何软件。"), + STR_TW("主機内找不到任何軟體。"), + }, + + [StrId_SelectTitle] = + { + STR_EN( + "Please select a target title.\n\n" + " \xEE\x80\x80 Select\n" + " \xEE\x80\x81 Cancel" + ), + STR_ES( + "Elija el título de destino.\n\n" + " \xEE\x80\x80 Seleccionar\n" + " \xEE\x80\x81 Cancelar" + ), + STR_DE( + "Bitte wähle den Ziel-Titel aus.\n\n" + " \xEE\x80\x80 Auswählen\n" + " \xEE\x80\x81 Abbrechen" + ), + STR_FR( + "Veuillez sélectionner un titre de destination.\n\n" + " \xEE\x80\x80 Sélectionner\n" + " \xEE\x80\x81 Annuler" + ), + STR_IT( + "Seleziona il titolo di destinazione.\n\n" + " \xEE\x80\x80 Seleziona\n" + " \xEE\x80\x81 Annulla" + ), + STR_JP( + "ターゲットタイトルを選択してください。\n\n" + " \xEE\x80\x80 選択\n" + " \xEE\x80\x81 キャンセル" + ), + STR_PT( + "Por favor escolha um título alvo.\n\n" + " \xEE\x80\x80 Escolher\n" + " \xEE\x80\x81 Cancelar" + ), + STR_NL( + "Selecteer een titel.\n\n" + " \xEE\x80\x80 Selecteer\n" + " \xEE\x80\x81 Annuleren" + ), + STR_KO( + "대상 타이틀을 선택해 주십시오.\n\n" + " \xEE\x80\x80 선택\n" + " \xEE\x80\x81 취소" + ), + STR_RU( + "Выберите целевой заголовок.\n\n" + " \xEE\x80\x80 Выберите\n" + " \xEE\x80\x81 Отмена" + ), + STR_ZH( + "请选择一个目标软件。\n\n" + " \xEE\x80\x80 确认\n" + " \xEE\x80\x81 取消" + ), + STR_TW( + "請選擇一個目標軟體。\n\n" + " \xEE\x80\x80 確認\n" + " \xEE\x80\x81 取消" + ), + }, + + [StrId_NoTargetTitleSupport] = + { + STR_EN( + "This homebrew exploit does not have support\n" + "for launching applications under target titles.\n" + "Please use a different exploit." + ), + STR_ES( + "Este exploit de homebrew no tiene soporte para\n" + "ejecutar aplicaciones bajo títulos de destino.\n" + "Use otro exploit diferente." + ), + STR_DE( + "Dieser Homebrew-Exploit unterstützt das Starten\n" + "von Anwendungen unter Ziel-Titeln nicht.\n" + "Bitte verwende einen anderen Exploit." + ), + STR_FR( + "Cet exploit homebrew ne permet pas de lancer\n" + "des applications sous des titres précis.\n" + "Veuillez utiliser un exploit différent." + ), + STR_IT( + "Questo exploit homebrew non permette di avviare\n" + "applicazioni in titoli specifici.\n" + "Utilizza un exploit diverso." + ), + STR_JP( + "この自家製のエクスプロイトは、ターゲットタイトルの\n" + "下でアプリを起動することができません。\n" + "別のエクスプロイトを使用してください。" + ), + STR_PT( + "Este exploit homebrew não têm suporte\n" + "para executar aplicações no título alvo.\n" + "Por favor use um exploit diferente." + ), + STR_NL( + "Deze homebrew exploit heeft geen ondersteuning\n" + "voor het starten van toepassingen met de gekozen titlel.\n" + "Gebruik een andere exploit." + ), + STR_KO( + "이 홈브류 익스플로잇은 해당 타이틀에서 어플리케이션을\n" + "실행시키는 것을 지원하지 않습니다.\n" + "다른 익스플로잇을 사용해 주십시오." + ), + STR_RU( + "Этот эксплойт homebrew не поддерживает запуск\n" + "приложений под целевыми заголовками.\n" + "Пожалуйста, используйте другой эксплойт." + ), + STR_ZH( + "您所利用漏洞启动的「自制软件启动器」,\n" + "无法在当前选中的软件中启动自制软件。\n" + "请使用其它的漏洞来启动「自制软件启动器」。" + ), + STR_TW( + "您所利用漏洞開啓的「自製軟體啓動器」\n" + "無法在當前選中的軟體啓動自製軟件。\n" + "請利用其它漏洞來啓動「自製軟體啓動器」。" + ), + }, + + [StrId_MissingTargetTitle] = + { + STR_EN( + "The application you attempted to run requires\n" + "a title that is not installed in the system." + ), + STR_ES( + "La aplicación seleccionada necesita un título\n" + "que no está instalado en el sistema." + ), + STR_DE( + "Die ausgewählte Anwendung benötigt einen\n" + "Titel der nicht installiert ist" + ), + STR_FR( + "L'application sélectionnée requiert un titre\n" + "qui n'a pas été installé sur le système." + ), + STR_IT( + "L'applicazione selezionata richiede un titolo\n" + "che non è installato nel sistema." + ), + STR_JP( + "このアプリを実行するために\n" + "適切なタイトルがインストールされていません。" + ), + STR_PT( + "A aplicação que acabou de tentar executar requer\n" + "um título que não está instalado neste sistema." + ), + STR_NL( + "De toepassing die je probeert te starten\n" + "vereist een titel die niet geinstalleerd is." + ), + STR_KO( + "시도한 애플리케이션의 실행에 필요한 타이틀이\n" + "시스템에 설치되어 있지 않습니다." + ), + STR_RU( + "Для приложения требуется зависимость,\n" + "которая не установлена." + ), + STR_ZH( + "主机找不到该应用程序\n" + "所需求的软件。" + ), + STR_TW( + "主機找不到該應用程式\n" + "所需求的軟體。" + ), + },*/ + + /*[StrId_NetLoader] = + { + STR_EN("3dslink NetLoader"), + STR_ES("Cargador de programas 3dslink"), + STR_DE("3dslink Netzwerk-Loader"), + STR_FR("Chargeur de programme 3dslink"), + STR_IT("Caricamento programmi 3dslink"), + STR_JP("3dslinkネットローダ"), + STR_PT("Carregador de programas 3dslink"), + STR_NL("3dslink netwerk lader"), + STR_KO("3dslink 넷로더"), + STR_RU("Загрузчик 3dslink"), + STR_ZH("3dslink 网络执行模块"), + STR_TW("3dslink 網路執行模組"), + }, + + [StrId_NetLoaderUnavailable] = + { + STR_EN("The NetLoader is currently unavailable."), + STR_ES("El cargador de programas no está disponible."), + STR_DE("Der Netzwerk-Loader ist zur Zeit nicht verfügbar."), + STR_FR("Le chargeur de programme 3dslink est indisponible."), + STR_IT("Il caricamento programmi 3dslink non è disponibile."), + STR_JP("3dslinkネットローダを利用できません。"), + STR_PT("O carregador de programas está de momento indisponível."), + STR_NL("De netwerk lader is niet beschikbaar."), + STR_KO("넷로더는 현재 사용이 불가능 합니다."), + STR_RU("Загрузчик в настоящее время недоступен."), + STR_ZH("无法启动 3dslink 网络执行模块。"), + STR_TW("無法啓動 3dslink 網路執行模組。"), + }, + + [StrId_NetLoaderError] = + { + STR_EN("An error occurred.\nTechnical details: [%s:%d]"), + STR_ES("Ha ocurrido un error.\nDatos técnicos: [%s:%d]"), + STR_DE("Ein Fehler ist aufgetreten\nTechnische Details: [%s:%d]"), + STR_FR("Une erreur s'est produite.\nDétails techniques : [%s:%d]"), + STR_IT("Si è verificato un errore.\nDettagli tecnici : [%s:%d]"), + STR_JP("エラーが発生しました。\n技術的な詳細:[%s:%d]"), + STR_PT("Ocorreu um erro.\nDetalhes técnicos: [%s:%d]"), + STR_NL("Er is een fout opgetreden\nTechnische details: [%s:%d]"), + STR_KO("에러가 발생했습니다.\n상세: [%s:%d]"), + STR_RU("Произошла ошибка.\nТехнические подробности: [%s:%d]"), + STR_ZH("发生错误。\n详细错误信息:[%s:%d]"), + STR_TW("發生錯誤。\n詳細錯誤資訊:[%s:%d]"), + }, + + [StrId_NetLoaderOffline] = + { + STR_EN("Offline, waiting for network…\n\n\n \xEE\x80\x81 Cancel"), + STR_ZH("无法连接网络,等待网络连接…\n\n\n \xEE\x80\x81 取消"), + STR_TW("當前離線,等待網路連線…\n\n\n \xEE\x80\x81 取消"), + STR_IT("Disconnesso, in attesa della connessione…\n\n\n \xEE\x80\x81 Annullare"), + }, + + [StrId_NetLoaderActive] = + { + STR_EN( + "Waiting for 3dslink to connect…\n" + "IP Addr: %lu.%lu.%lu.%lu, Port: %d\n\n" + " \xEE\x80\x81 Cancel" + ), + STR_ES( + "Esperando a que se conecte 3dslink…\n" + "Dir.IP: %lu.%lu.%lu.%lu, Puerto: %d\n\n" + " \xEE\x80\x81 Cancelar" + ), + STR_DE( + "Warte auf Verbindung von 3dslink…\n" + "IP Addr: %lu.%lu.%lu.%lu, Port: %d\n\n" + " \xEE\x80\x81 Abbrechen" + ), + STR_FR( + "En attente de la connexion de 3dslink…\n" + "Adr. IP : %lu.%lu.%lu.%lu, Port : %d\n\n" + " \xEE\x80\x81 Annuler" + ), + STR_IT( + "In attesa della connessione di 3dslink…\n" + "Ind. IP : %lu.%lu.%lu.%lu, Porta : %d\n\n" + " \xEE\x80\x81 Annullare" + ), + STR_JP( + "3dslinkが接続するのを待っている…\n" + "IPアドレス:%lu.%lu.%lu.%lu, ポート番号:%d\n\n" + " \xEE\x80\x81 キャンセル" + ), + STR_PT( + "A aguardar pela conexão do 3dslink…\n" + "End. IP: %lu.%lu.%lu.%lu, Porta: %d\n\n" + " \xEE\x80\x81 Cancelar" + ), + STR_NL( + "Wachten op 3dslink verbinding…\n" + "IP Addr: %lu.%lu.%lu.%lu, Poort: %d\n\n" + " \xEE\x80\x81 Annuleren" + ), + STR_KO( + "3dslink 의 연결을 대기중…\n" + "IP 주소: %lu.%lu.%lu.%lu, 포트: %d\n\n" + " \xEE\x80\x81 취소" + ), + STR_RU( + "Ожидание подключения 3dslink…\n" + "айпи адрес: %lu.%lu.%lu.%lu, Порт: %d\n\n" + " \xEE\x80\x81 Отмена" + ), + STR_ZH( + "等待 3dslink 连接…\n" + "IP 地址:%lu.%lu.%lu.%lu,端口:%d\n\n" + " \xEE\x80\x81 取消等待" + ), + STR_TW( + "等待 3dslink 連接…\n" + "IP 位址:%lu.%lu.%lu.%lu,連接埠:%d\n\n" + " \xEE\x80\x81 取消等待" + ), + }, + + [StrId_NetLoaderTransferring] = + { + STR_EN( + "Transferring…\n" + "%zu out of %zu KiB written" + ), + STR_ES( + "Transfiriendo…\n" + "%zu de %zu KiB escritos" + ), + STR_DE( + "Übertragen…\n" + "%zu von %zu KiB geschrieben" + ), + STR_FR( + "Transfert…\n" + "%zu sur %zu Kio écrits" + ), + STR_IT( + "Trasferimento…\n" + "%zu di %zu KiB scritti" + ), + STR_JP( + "データが転送されます…\n" + "%zu / %zu KiB 書かれた" + ), + STR_PT( + "A transferir…\n" + "%zu de %zu KiB escritos" + ), + STR_NL( + "Overbrengen…\n" + "%zu van %zu KiB geschreven" + ), + STR_KO( + "전송중…\n" + "%zu / %zu KiB 전송" + ), + STR_RU( + "Передача…\n" + "%zu из %zu КИБ написано" + ), + STR_ZH( + "正在传输…\n" + "已完成 %zu / %zu KiB" + ), + STR_TW( + "正在傳輸…\n" + "已完成 %zu / %zu KiB" + ), + },*/ +}; + diff --git a/common/language.h b/common/language.h new file mode 100644 index 0000000..e446bb4 --- /dev/null +++ b/common/language.h @@ -0,0 +1,37 @@ +#pragma once + +typedef enum +{ + StrId_Loading = 0, + StrId_Directory, + StrId_DefaultVersion, + StrId_DefaultPublisher, + StrId_IOError, + StrId_CouldNotOpenFile, + + StrId_NoAppsFound_Title, + StrId_NoAppsFound_Msg, + + StrId_Reboot, + StrId_ReturnToHome, + + StrId_TitleSelector, + StrId_ErrorReadingTitleMetadata, + StrId_NoTitlesFound, + StrId_SelectTitle, + + StrId_NoTargetTitleSupport, + StrId_MissingTargetTitle, + + StrId_NetLoader, + StrId_NetLoaderUnavailable, + StrId_NetLoaderOffline, + StrId_NetLoaderError, + StrId_NetLoaderActive, + StrId_NetLoaderTransferring, + + StrId_Max, +} StrId; + +extern const char* const g_strings[StrId_Max][16]; + diff --git a/common/launch.c b/common/launch.c new file mode 100644 index 0000000..16e660c --- /dev/null +++ b/common/launch.c @@ -0,0 +1,59 @@ +#include "common.h" + +size_t launchAddArg(argData_s* ad, const char* arg) { + size_t len = strlen(arg)+1; + if ((ad->dst+len) >= (char*)(ad+1)) return len; // Overflow + ad->buf[0]++; + strcpy(ad->dst, arg); + ad->dst += len; + return len; +} + +void launchAddArgsFromString(argData_s* ad, char* arg) { + char c, *pstr, *str=arg, *endarg = arg+strlen(arg); + + do + { + do + { + c = *str++; + } while ((c == ' ' || c == '\t') && str < endarg); + + pstr = str-1; + + if (c == '\"') + { + pstr++; + while(*str++ != '\"' && str < endarg); + } + else if (c == '\'') + { + pstr++; + while(*str++ != '\'' && str < endarg); + } + else + { + do + { + c = *str++; + } while (c != ' ' && c != '\t' && str < endarg); + } + + str--; + + if (str == (endarg - 1)) + { + if(*str == '\"' || *str == '\'') + *(str++) = 0; + else + str++; + } + else + { + *(str++) = '\0'; + } + + launchAddArg(ad, pstr); + + } while(strtype = type; +} + +void menuEntryFree(menuEntry_s* me) { + me->icon_size = 0; + if (me->icon) { + free(me->icon); + me->icon = NULL; + } + + if (me->icon_gfx) { + free(me->icon_gfx); + me->icon_gfx = NULL; + } + + if (me->nacp) { + free(me->nacp); + me->nacp = NULL; + } +} + +bool fileExists(const char* path) { + struct stat st; + return stat(path, &st)==0 && S_ISREG(st.st_mode); +} + +static bool menuEntryLoadEmbeddedIcon(menuEntry_s* me) { + NroHeader header; + AssetHeader asset_header; + + FILE* f = fopen(me->path, "rb"); + if (!f) return false; + + fseek(f, sizeof(NroStart), SEEK_SET); + if (fread(&header, sizeof(header), 1, f) != 1) + { + fclose(f); + return false; + } + + fseek(f, header.size, SEEK_SET); + + if (fread(&asset_header, sizeof(asset_header), 1, f) != 1 + || asset_header.magic != ASSETHEADER_MAGICNUM + || asset_header.version > ASSETHEADER_VERSION + || asset_header.icon.offset == 0 + || asset_header.icon.size == 0) + { + fclose(f); + return false; + } + + me->icon_size = asset_header.icon.size; + me->icon = (uint8_t*)malloc(me->icon_size); + if (me->icon == NULL) { + fclose(f); + return false; + } + memset(me->icon, 0, me->icon_size); + + fseek(f, header.size + asset_header.icon.offset, SEEK_SET); + bool ok = fread(me->icon, me->icon_size, 1, f) == 1; + fclose(f); + return ok; +} + +static bool menuEntryLoadEmbeddedNacp(menuEntry_s* me) { + NroHeader header; + AssetHeader asset_header; + + FILE* f = fopen(me->path, "rb"); + if (!f) return false; + + fseek(f, sizeof(NroStart), SEEK_SET); + if (fread(&header, sizeof(header), 1, f) != 1) + { + fclose(f); + return false; + } + + fseek(f, header.size, SEEK_SET); + + if (fread(&asset_header, sizeof(asset_header), 1, f) != 1 + || asset_header.magic != ASSETHEADER_MAGICNUM + || asset_header.version > ASSETHEADER_VERSION + || asset_header.nacp.offset == 0 + || asset_header.nacp.size == 0) + { + fclose(f); + return false; + } + + if (asset_header.nacp.size < sizeof(NacpStruct)) + { + fclose(f); + return false; + } + + me->nacp = (NacpStruct*)malloc(sizeof(NacpStruct)); + if (me->nacp == NULL) { + fclose(f); + return false; + } + + fseek(f, header.size + asset_header.nacp.offset, SEEK_SET); + bool ok = fread(me->nacp, sizeof(NacpStruct), 1, f) == 1; + fclose(f); + return ok; +} + +/*static void fixSpaceNewLine(char* buf) { + char *outp = buf, *inp = buf; + char lastc = 0; + do + { + char c = *inp++; + if (c == ' ' && lastc == ' ') + outp[-1] = '\n'; + else + *outp++ = c; + lastc = c; + } while (lastc); +}*/ + +bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) { + static char tempbuf[PATH_MAX+1]; + //bool isOldAppFolder = false; + + tempbuf[PATH_MAX] = 0; + strcpy(me->name, name); + if (me->type == ENTRY_TYPE_FOLDER) + { + snprintf(tempbuf, sizeof(tempbuf)-1, "%.*s/%.*s.nro", (int)sizeof(tempbuf)/2, me->path, (int)sizeof(tempbuf)/2-7, name); + bool found = fileExists(tempbuf); + + if (found) + { + //isOldAppFolder = true; + shortcut = false; + me->type = ENTRY_TYPE_FILE; + strcpy(me->path, tempbuf); + } /*else + strcpy(me->name, textGetString(StrId_Directory));*/ + } + + if (me->type == ENTRY_TYPE_FILE) + { + strcpy(me->name, name); + strcpy(me->author, textGetString(StrId_DefaultPublisher)); + strcpy(me->version, textGetString(StrId_DefaultVersion)); + + //shortcut_s sc; + + /*if (shortcut) + { + if (R_FAILED(shortcutCreate(&sc, me->path))) + return false; + if (!fileExists(sc.executable)) + { + shortcutFree(&sc); + return false; + } + strcpy(me->path, "sdmc:"); + strcat(me->path, sc.executable); + }*/ + + bool iconLoaded = false; + + // Load the icon + /*if (shortcut) + { + FILE* f = sc.icon ? fopen(sc.icon, "rb") : NULL; + if (f) + { + iconLoaded = fread(&me->smdh, sizeof(smdh_s), 1, f) == 1; + fclose(f); + } + }*/ + + if (!iconLoaded) do + { + // Attempt loading external icon + /*strcpy(tempbuf, me->path); + char* ext = getExtension(tempbuf); + + strcpy(ext, ".jpg"); + iconLoaded = menuEntryLoadExternalIcon(me, tempbuf); + if (iconLoaded) break; + + if (isOldAppFolder) + { + char* slash = getSlash(tempbuf); + + strcpy(slash, "/icon.jpg"); + iconLoaded = menuEntryLoadExternalIcon(me, tempbuf); + if (iconLoaded) break; + }*/ + + // Attempt loading the embedded icon + if (!shortcut) + iconLoaded = menuEntryLoadEmbeddedIcon(me); + } while (0); + + if (iconLoaded) + { + menuEntryParseIcon(me); + } + + bool nacpLoaded = false; + + nacpLoaded = menuEntryLoadEmbeddedNacp(me); + + if (nacpLoaded) + { + menuEntryParseNacp(me); + + // Fix description for some applications using multiple spaces to indicate newline + //fixSpaceNewLine(me->description); + } + + // Metadata overrides for shortcuts + /*if (shortcut) + { + if (sc.name) strncpy(me->name, sc.name, ENTRY_NAMELENGTH); + if (sc.description) strncpy(me->description, sc.description, ENTRY_DESCLENGTH); + if (sc.author) strncpy(me->author, sc.author, ENTRY_AUTHORLENGTH); + }*/ + + // Load the descriptor + /*if (shortcut && sc.descriptor && fileExists(sc.descriptor)) + descriptorLoad(&me->descriptor, sc.descriptor); + else + { + strcpy(tempbuf, me->path); + strcpy(getExtension(tempbuf), ".xml"); + bool found = fileExists(tempbuf); + if (!found && isOldAppFolder) + { + strcpy(tempbuf, me->path); + strcpy(getSlash(tempbuf), "/descriptor.xml"); + found = fileExists(tempbuf); + } + if (found) + descriptorLoad(&me->descriptor, tempbuf); + }*/ + + // Initialize the argument data + argData_s* ad = &me->args; + ad->dst = (char*)&ad->buf[1]; + launchAddArg(ad, me->path); + + // Load the argument(s) from the shortcut + /*if (shortcut && sc.arg && *sc.arg) + launchAddArgsFromString(ad, sc.arg);*/ + + /*if (shortcut) + shortcutFree(&sc);*/ + } + + return true; +} + +void menuEntryParseIcon(menuEntry_s* me) { + uint8_t *imageptr = NULL; + size_t imagesize = 256*256*3; + + if (me->icon_size==0 || me->icon==NULL) return; + + njInit(); + + if (njDecode(me->icon, me->icon_size) != NJ_OK) { + njDone(); + return; + } + + me->icon_size = 0; + free(me->icon); + me->icon = NULL; + + if ((njGetWidth() != 256 || njGetHeight() != 256 || (size_t)njGetImageSize() != imagesize) || njIsColor() != 1) {//The decoded image must be RGB and 256x256. + njDone(); + return; + } + + imageptr = njGetImage(); + if (imageptr == NULL) { + njDone(); + return; + } + + me->icon_gfx = (uint8_t*)malloc(imagesize); + if (me->icon_gfx == NULL) { + njDone(); + return; + } + + memcpy(me->icon_gfx, imageptr, imagesize); + + njDone(); +} + +void menuEntryParseNacp(menuEntry_s* me) { + int lang = 0;//TODO: Update this once libnx supports settings get-language. + + if (me->nacp==NULL) return; + + strncpy(me->name, me->nacp->lang[lang].name, sizeof(me->name)-1); + strncpy(me->author, me->nacp->lang[lang].author, sizeof(me->author)-1); + strncpy(me->version, me->nacp->version, sizeof(me->version)-1); + + free(me->nacp); + me->nacp = NULL; +} + diff --git a/common/menu-list.c b/common/menu-list.c new file mode 100644 index 0000000..67db454 --- /dev/null +++ b/common/menu-list.c @@ -0,0 +1,150 @@ +#include "common.h" + +static menu_s s_menu[2]; +static bool s_curMenu; + +menu_s* menuGetCurrent(void) { + return &s_menu[s_curMenu]; +} + +static menuEntry_s* menuCreateEntry(MenuEntryType type) { + menuEntry_s* me = (menuEntry_s*)malloc(sizeof(menuEntry_s)); + menuEntryInit(me, type); + return me; +} + +static void menuDeleteEntry(menuEntry_s* me) { + menuEntryFree(me); + free(me); +} + +static void menuAddEntry(menuEntry_s* me) { + menu_s* m = &s_menu[!s_curMenu]; + me->menu = m; + if (m->lastEntry) + { + m->lastEntry->next = me; + m->lastEntry = me; + } else + { + m->firstEntry = me; + m->lastEntry = me; + } + m->nEntries ++; +} + +static void menuClear(void) { + menu_s* m = &s_menu[!s_curMenu]; + menuEntry_s *cur, *next; + for (cur = m->firstEntry; cur; cur = next) + { + next = cur->next; + menuDeleteEntry(cur); + } + memset(m, 0, sizeof(*m)); +} + +static int menuEntryCmp(const void *p1, const void *p2) { + const menuEntry_s* lhs = *(menuEntry_s**)p1; + const menuEntry_s* rhs = *(menuEntry_s**)p2; + + if(lhs->type == rhs->type) + return strcasecmp(lhs->name, rhs->name); + if(lhs->type == ENTRY_TYPE_FOLDER) + return -1; + return 1; +} + +static void menuSort(void) { + int i; + menu_s* m = &s_menu[!s_curMenu]; + int nEntries = m->nEntries; + if (nEntries==0) return; + + menuEntry_s** list = (menuEntry_s**)calloc(nEntries, sizeof(menuEntry_s*)); + if(list == NULL) return; + + menuEntry_s* p = m->firstEntry; + for(i = 0; i < nEntries; ++i) { + list[i] = p; + p = p->next; + } + + qsort(list, nEntries, sizeof(menuEntry_s*), menuEntryCmp); + + menuEntry_s** pp = &m->firstEntry; + for(i = 0; i < nEntries; ++i) { + *pp = list[i]; + pp = &(*pp)->next; + } + m->lastEntry = list[nEntries-1]; + *pp = NULL; + + free(list); +} + +int menuScan(const char* target) { + if (chdir(target) < 0) return 1; + if (getcwd(s_menu[!s_curMenu].dirname, PATH_MAX+1) == NULL) + return 1; + + DIR* dir; + struct dirent* dp; + char tmp_path[PATH_MAX+1]; + dir = opendir(s_menu[!s_curMenu].dirname); + if (!dir) return 2; + + while ((dp = readdir(dir))) + { + menuEntry_s* me = NULL; + bool shortcut = false; + /*if (entry->attributes & FS_ATTRIBUTE_HIDDEN) + continue;*/ + + bool entrytype=0; + + memset(tmp_path, 0, sizeof(tmp_path)); + snprintf(tmp_path, sizeof(tmp_path)-1, "%s%s", s_menu[!s_curMenu].dirname, dp->d_name); + + #ifdef SWITCH + fsdev_dir_t* dirSt = (fsdev_dir_t*)dir->dirData->dirStruct; + FsDirectoryEntry* entry = &dirSt->entry_data[dirSt->index]; + + entrytype = entry->type == ENTRYTYPE_DIR; + #else + struct stat tmpstat; + + if(stat(tmp_path, &tmpstat)==-1) + continue; + + entrytype = (tmpstat.st_mode & S_IFMT) != S_IFREG; + #endif + + if (entrytype) + me = menuCreateEntry(ENTRY_TYPE_FOLDER); + else + { + const char* ext = getExtension(dp->d_name); + if (strcasecmp(ext, ".nro")==0/* || (shortcut = strcasecmp(ext, ".xml")==0)*/) + me = menuCreateEntry(ENTRY_TYPE_FILE); + } + + if (!me) + continue; + + strncpy(me->path, tmp_path, sizeof(me->path)-1); + if (menuEntryLoad(me, dp->d_name, shortcut)) + menuAddEntry(me); + else + menuDeleteEntry(me); + } + + closedir(dir); + menuSort(); + + // Swap the menu and clear the previous menu + s_curMenu = !s_curMenu; + menuClear(); + return 0; +} + diff --git a/common/menu.c b/common/menu.c new file mode 100644 index 0000000..e828f89 --- /dev/null +++ b/common/menu.c @@ -0,0 +1,140 @@ +#include "common.h" + +#include "switchicon_questionmark_bin.h" +#include "folder_icon_bin.h" + +void launchMenuEntryTask(menuEntry_s* arg) +{ + menuEntry_s* me = arg; + if (me->type == ENTRY_TYPE_FOLDER) + menuScan(me->path); + //changeDirTask(me->path); + else + launchMenuEntry(me); +} + +//Draws a RGB888 image. +static void drawImage(int x, int y, int width, int height, const uint8_t *image) { + int tmpx, tmpy; + int pos; + + for (tmpx=0; tmpxicon_gfx) + imageptr = me->icon_gfx; + else if (me->type == ENTRY_TYPE_FOLDER) + imageptr = (uint8_t*)folder_icon_bin; + else + imageptr = (uint8_t*)switchicon_questionmark_bin; + + if (imageptr) drawImage(start_x, start_y+32, 256, 256, imageptr); + + DrawTextTruncate(tahoma12, start_x + 8, start_y + 8, MakeColor(64, 64, 64, 255), me->name, 256 - 32, "..."); + + if (is_active) { + start_x = 64; + start_y = 96 + 32; + + memset(tmpstr, 0, sizeof(tmpstr)); + snprintf(tmpstr, sizeof(tmpstr)-1, "Name: %s\nAuthor: %s\nVersion: %s", me->name, me->author, me->version); + DrawText(tahoma12, start_x, start_y, MakeColor(64, 64, 64, 255), tmpstr); + } +} + +void menuStartup() { + const char *path; + + #ifdef SWITCH + path = "sdmc:/switch"; + #else + path = "switch"; + #endif + + menuScan(path); +} + +void menuLoop() { + menuEntry_s* me; + menu_s* menu = menuGetCurrent(); + int i; + int cnt=0; + int x, y; + + for (x=0; x<1280; x++) { + for (y=0; y<122; y++) { + DrawPixelRaw(x, y, MakeColor(237+8, 237+8, 237+8, 237+8)); + } + } + + DrawText(tahoma24, 64, 64, MakeColor(64, 64, 64, 255), "The Homebrew Launcher"); + DrawText(tahoma12, 64 + 256 + 128 + 128, 64 + 16, MakeColor(64, 64, 64, 255), "v1.0.0"); + DrawText(tahoma12, 64 + 256 + 128 + 128, 64 + 16 + 16, MakeColor(64, 64, 64, 255), menu->dirname); + + if (menu->nEntries==0) + { + DrawText(tahoma12, 64, 96 + 32, MakeColor(64, 64, 64, 255), textGetString(StrId_NoAppsFound_Msg)); + } else + { + // Draw menu entries + for (me = menu->firstEntry, i = 0; me; me = me->next, i ++) { + if ((i < menu->curEntry && menu->curEntry-i < 4) || i>=menu->curEntry) { + drawEntry(me, cnt, i==menu->curEntry); + cnt++; + if (cnt==4) break; + } + } + } +} diff --git a/common/menu.h b/common/menu.h new file mode 100644 index 0000000..3083291 --- /dev/null +++ b/common/menu.h @@ -0,0 +1,75 @@ +#pragma once + +#define ENTRY_NAMELENGTH 0x200 +#define ENTRY_AUTHORLENGTH 0x100 +#define ENTRY_VERLENGTH 0x10 +#define ENTRY_ARGBUFSIZE 0x400 + +typedef enum +{ + ENTRY_TYPE_FILE, + ENTRY_TYPE_FOLDER, +} MenuEntryType; + +typedef struct menuEntry_s_tag menuEntry_s; +typedef struct menu_s_tag menu_s; + +struct menu_s_tag +{ + menuEntry_s *firstEntry, *lastEntry; + int nEntries; + int curEntry; + + char dirname[PATH_MAX+1]; +}; + +typedef struct +{ + char* dst; + uint32_t buf[ENTRY_ARGBUFSIZE/sizeof(uint32_t)]; +} argData_s; + +struct menuEntry_s_tag +{ + menu_s* menu; + menuEntry_s* next; + MenuEntryType type; + + char path[PATH_MAX+1]; + argData_s args; + + char name[ENTRY_NAMELENGTH+1]; + char author[ENTRY_AUTHORLENGTH+1]; + char version[ENTRY_VERLENGTH+1]; + + uint8_t *icon; + size_t icon_size; + uint8_t *icon_gfx; + + NacpStruct *nacp; +}; + +void menuEntryInit(menuEntry_s* me, MenuEntryType type); +void menuEntryFree(menuEntry_s* me); +bool fileExists(const char* path); +bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut); +void menuEntryParseIcon(menuEntry_s* me); +void menuEntryParseNacp(menuEntry_s* me); + +menu_s* menuGetCurrent(void); +int menuScan(const char* target); + +static inline char* getExtension(const char* str) +{ + const char* p; + for (p = str+strlen(str); p >= str && *p != '.'; p--); + return (char*)p; +} + +static inline char* getSlash(const char* str) +{ + const char* p; + for (p = str+strlen(str); p >= str && *p != '/'; p--); + return (char*)p; +} + diff --git a/common/nacp.h b/common/nacp.h new file mode 100644 index 0000000..82ad865 --- /dev/null +++ b/common/nacp.h @@ -0,0 +1,41 @@ +#pragma once + +typedef struct { + char name[0x200]; + char author[0x100]; +} NacpLanguageEntry; + +typedef struct { + NacpLanguageEntry lang[12]; + NacpLanguageEntry lang_unk[4];//? + + u8 x3000_unk[0x24];////Normally all-zero? + u32 x3024_unk; + u32 x3028_unk; + u32 x302C_unk; + u32 x3030_unk; + u32 x3034_unk; + u64 titleid0; + + u8 x3040_unk[0x20]; + char version[0x10]; + + u64 titleid_dlcbase; + u64 titleid1; + + u32 x3080_unk; + u32 x3084_unk; + u32 x3088_unk; + u8 x308C_unk[0x24];//zeros? + + u64 titleid2; + u64 titleids[7];//"Array of application titleIDs, normally the same as the above app-titleIDs. Only set for game-updates?" + + u32 x30F0_unk; + u32 x30F4_unk; + + u64 titleid3;//"Application titleID. Only set for game-updates?" + + char bcat_passphrase[0x40]; + u8 x3140_unk[0xEC0];//Normally all-zero? +} NacpStruct; diff --git a/common/nanojpeg.c b/common/nanojpeg.c new file mode 100644 index 0000000..abfac49 --- /dev/null +++ b/common/nanojpeg.c @@ -0,0 +1,916 @@ +// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder +// version 1.3.5 (2016-11-14) +// Copyright (c) 2009-2016 Martin J. Fiedler +// published under the terms of the MIT license +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + + +/////////////////////////////////////////////////////////////////////////////// +// DOCUMENTATION SECTION // +// read this if you want to know what this is all about // +/////////////////////////////////////////////////////////////////////////////// + +// INTRODUCTION +// ============ +// +// This is a minimal decoder for baseline JPEG images. It accepts memory dumps +// of JPEG files as input and generates either 8-bit grayscale or packed 24-bit +// RGB images as output. It does not parse JFIF or Exif headers; all JPEG files +// are assumed to be either grayscale or YCbCr. CMYK or other color spaces are +// not supported. All YCbCr subsampling schemes with power-of-two ratios are +// supported, as are restart intervals. Progressive or lossless JPEG is not +// supported. +// Summed up, NanoJPEG should be able to decode all images from digital cameras +// and most common forms of other non-progressive JPEG images. +// The decoder is not optimized for speed, it's optimized for simplicity and +// small code. Image quality should be at a reasonable level. A bicubic chroma +// upsampling filter ensures that subsampled YCbCr images are rendered in +// decent quality. The decoder is not meant to deal with broken JPEG files in +// a graceful manner; if anything is wrong with the bitstream, decoding will +// simply fail. +// The code should work with every modern C compiler without problems and +// should not emit any warnings. It uses only (at least) 32-bit integer +// arithmetic and is supposed to be endianness independent and 64-bit clean. +// However, it is not thread-safe. + + +// COMPILE-TIME CONFIGURATION +// ========================== +// +// The following aspects of NanoJPEG can be controlled with preprocessor +// defines: +// +// _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example +// program. +// _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header +// file for NanoJPEG. Example: +// #define _NJ_INCLUDE_HEADER_ONLY +// #include "nanojpeg.c" +// int main(void) { +// njInit(); +// // your code here +// njDone(); +// } +// NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy() +// functions from the standard C library (default). +// NJ_USE_LIBC=0 = Don't use the standard C library. In this mode, +// external functions njAlloc(), njFreeMem(), +// njFillMem() and njCopyMem() need to be defined +// and implemented somewhere. +// NJ_USE_WIN32=0 = Normal mode (default). +// NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and +// NJ_USE_LIBC=0, NanoJPEG will use its own +// implementations of the required C library +// functions (default if compiling with MSVC and +// NJ_USE_LIBC=0). +// NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter +// (default). +// NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling +// (bad quality, but faster and less code). + + +// API +// === +// +// For API documentation, read the "header section" below. + + +// EXAMPLE +// ======= +// +// A few pages below, you can find an example program that uses NanoJPEG to +// convert JPEG files into PGM or PPM. To compile it, use something like +// gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c +// You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :) +// The only thing you might need is -Wno-shift-negative-value, because this +// code relies on the target machine using two's complement arithmetic, but +// the C standard does not, even though *any* practically useful machine +// nowadays uses two's complement. + + +/////////////////////////////////////////////////////////////////////////////// +// HEADER SECTION // +// copy and pase this into nanojpeg.h if you want // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _NANOJPEG_H +#define _NANOJPEG_H + +// nj_result_t: Result codes for njDecode(). +typedef enum _nj_result { + NJ_OK = 0, // no error, decoding successful + NJ_NO_JPEG, // not a JPEG file + NJ_UNSUPPORTED, // unsupported format + NJ_OUT_OF_MEM, // out of memory + NJ_INTERNAL_ERR, // internal error + NJ_SYNTAX_ERROR, // syntax error + __NJ_FINISHED, // used internally, will never be reported +} nj_result_t; + +// njInit: Initialize NanoJPEG. +// For safety reasons, this should be called at least one time before using +// using any of the other NanoJPEG functions. +void njInit(void); + +// njDecode: Decode a JPEG image. +// Decodes a memory dump of a JPEG file into internal buffers. +// Parameters: +// jpeg = The pointer to the memory dump. +// size = The size of the JPEG file. +// Return value: The error code in case of failure, or NJ_OK (zero) on success. +nj_result_t njDecode(const void* jpeg, const int size); + +// njGetWidth: Return the width (in pixels) of the most recently decoded +// image. If njDecode() failed, the result of njGetWidth() is undefined. +int njGetWidth(void); + +// njGetHeight: Return the height (in pixels) of the most recently decoded +// image. If njDecode() failed, the result of njGetHeight() is undefined. +int njGetHeight(void); + +// njIsColor: Return 1 if the most recently decoded image is a color image +// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result +// of njGetWidth() is undefined. +int njIsColor(void); + +// njGetImage: Returns the decoded image data. +// Returns a pointer to the most recently image. The memory layout it byte- +// oriented, top-down, without any padding between lines. Pixels of color +// images will be stored as three consecutive bytes for the red, green and +// blue channels. This data format is thus compatible with the PGM or PPM +// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. +// If njDecode() failed, the result of njGetImage() is undefined. +unsigned char* njGetImage(void); + +// njGetImageSize: Returns the size (in bytes) of the image data returned +// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is +// undefined. +int njGetImageSize(void); + +// njDone: Uninitialize NanoJPEG. +// Resets NanoJPEG's internal state and frees all memory that has been +// allocated at run-time by NanoJPEG. It is still possible to decode another +// image after a njDone() call. +void njDone(void); + +#endif//_NANOJPEG_H + + +/////////////////////////////////////////////////////////////////////////////// +// CONFIGURATION SECTION // +// adjust the default settings for the NJ_ defines here // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef NJ_USE_LIBC + #define NJ_USE_LIBC 1 +#endif + +#ifndef NJ_USE_WIN32 + #ifdef _MSC_VER + #define NJ_USE_WIN32 (!NJ_USE_LIBC) + #else + #define NJ_USE_WIN32 0 + #endif +#endif + +#ifndef NJ_CHROMA_FILTER + #define NJ_CHROMA_FILTER 1 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EXAMPLE PROGRAM // +// just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) // +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _NJ_EXAMPLE_PROGRAM + +#include +#include +#include + +int main(int argc, char* argv[]) { + int size; + char *buf; + FILE *f; + + if (argc < 2) { + printf("Usage: %s []\n", argv[0]); + return 2; + } + f = fopen(argv[1], "rb"); + if (!f) { + printf("Error opening the input file.\n"); + return 1; + } + fseek(f, 0, SEEK_END); + size = (int) ftell(f); + buf = (char*) malloc(size); + fseek(f, 0, SEEK_SET); + size = (int) fread(buf, 1, size, f); + fclose(f); + + njInit(); + if (njDecode(buf, size)) { + free((void*)buf); + printf("Error decoding the input file.\n"); + return 1; + } + free((void*)buf); + + f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb"); + if (!f) { + printf("Error opening the output file.\n"); + return 1; + } + fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight()); + fwrite(njGetImage(), 1, njGetImageSize(), f); + fclose(f); + njDone(); + return 0; +} + +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// IMPLEMENTATION SECTION // +// you may stop reading here // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _NJ_INCLUDE_HEADER_ONLY + +#ifdef _MSC_VER + #define NJ_INLINE static __inline + #define NJ_FORCE_INLINE static __forceinline +#else + #define NJ_INLINE static inline + #define NJ_FORCE_INLINE static inline +#endif + +#if NJ_USE_LIBC + #include + #include + #define njAllocMem malloc + #define njFreeMem free + #define njFillMem memset + #define njCopyMem memcpy +#elif NJ_USE_WIN32 + #include + #define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size))) + #define njFreeMem(block) ((void) LocalFree((HLOCAL) block)) + NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm { + mov edi, block + mov al, value + mov ecx, count + rep stosb + } } + NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm { + mov edi, dest + mov esi, src + mov ecx, count + rep movsb + } } +#else + extern void* njAllocMem(int size); + extern void njFreeMem(void* block); + extern void njFillMem(void* block, unsigned char byte, int size); + extern void njCopyMem(void* dest, const void* src, int size); +#endif + +typedef struct _nj_code { + unsigned char bits, code; +} nj_vlc_code_t; + +typedef struct _nj_cmp { + int cid; + int ssx, ssy; + int width, height; + int stride; + int qtsel; + int actabsel, dctabsel; + int dcpred; + unsigned char *pixels; +} nj_component_t; + +typedef struct _nj_ctx { + nj_result_t error; + const unsigned char *pos; + int size; + int length; + int width, height; + int mbwidth, mbheight; + int mbsizex, mbsizey; + int ncomp; + nj_component_t comp[3]; + int qtused, qtavail; + unsigned char qtab[4][64]; + nj_vlc_code_t vlctab[4][65536]; + int buf, bufbits; + int block[64]; + int rstinterval; + unsigned char *rgb; +} nj_context_t; + +static nj_context_t nj; + +static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, +11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, +42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, +38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; + +NJ_FORCE_INLINE unsigned char njClip(const int x) { + return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x); +} + +#define W1 2841 +#define W2 2676 +#define W3 2408 +#define W5 1609 +#define W6 1108 +#define W7 565 + +NJ_INLINE void njRowIDCT(int* blk) { + int x0, x1, x2, x3, x4, x5, x6, x7, x8; + if (!((x1 = blk[4] << 11) + | (x2 = blk[6]) + | (x3 = blk[2]) + | (x4 = blk[1]) + | (x5 = blk[7]) + | (x6 = blk[5]) + | (x7 = blk[3]))) + { + blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = blk[0] << 3; + return; + } + x0 = (blk[0] << 11) + 128; + x8 = W7 * (x4 + x5); + x4 = x8 + (W1 - W7) * x4; + x5 = x8 - (W1 + W7) * x5; + x8 = W3 * (x6 + x7); + x6 = x8 - (W3 - W5) * x6; + x7 = x8 - (W3 + W5) * x7; + x8 = x0 + x1; + x0 -= x1; + x1 = W6 * (x3 + x2); + x2 = x1 - (W2 + W6) * x2; + x3 = x1 + (W2 - W6) * x3; + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = (181 * (x4 + x5) + 128) >> 8; + x4 = (181 * (x4 - x5) + 128) >> 8; + blk[0] = (x7 + x1) >> 8; + blk[1] = (x3 + x2) >> 8; + blk[2] = (x0 + x4) >> 8; + blk[3] = (x8 + x6) >> 8; + blk[4] = (x8 - x6) >> 8; + blk[5] = (x0 - x4) >> 8; + blk[6] = (x3 - x2) >> 8; + blk[7] = (x7 - x1) >> 8; +} + +NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) { + int x0, x1, x2, x3, x4, x5, x6, x7, x8; + if (!((x1 = blk[8*4] << 8) + | (x2 = blk[8*6]) + | (x3 = blk[8*2]) + | (x4 = blk[8*1]) + | (x5 = blk[8*7]) + | (x6 = blk[8*5]) + | (x7 = blk[8*3]))) + { + x1 = njClip(((blk[0] + 32) >> 6) + 128); + for (x0 = 8; x0; --x0) { + *out = (unsigned char) x1; + out += stride; + } + return; + } + x0 = (blk[0] << 8) + 8192; + x8 = W7 * (x4 + x5) + 4; + x4 = (x8 + (W1 - W7) * x4) >> 3; + x5 = (x8 - (W1 + W7) * x5) >> 3; + x8 = W3 * (x6 + x7) + 4; + x6 = (x8 - (W3 - W5) * x6) >> 3; + x7 = (x8 - (W3 + W5) * x7) >> 3; + x8 = x0 + x1; + x0 -= x1; + x1 = W6 * (x3 + x2) + 4; + x2 = (x1 - (W2 + W6) * x2) >> 3; + x3 = (x1 + (W2 - W6) * x3) >> 3; + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = (181 * (x4 + x5) + 128) >> 8; + x4 = (181 * (x4 - x5) + 128) >> 8; + *out = njClip(((x7 + x1) >> 14) + 128); out += stride; + *out = njClip(((x3 + x2) >> 14) + 128); out += stride; + *out = njClip(((x0 + x4) >> 14) + 128); out += stride; + *out = njClip(((x8 + x6) >> 14) + 128); out += stride; + *out = njClip(((x8 - x6) >> 14) + 128); out += stride; + *out = njClip(((x0 - x4) >> 14) + 128); out += stride; + *out = njClip(((x3 - x2) >> 14) + 128); out += stride; + *out = njClip(((x7 - x1) >> 14) + 128); +} + +#define njThrow(e) do { nj.error = e; return; } while (0) +#define njCheckError() do { if (nj.error) return; } while (0) + +static int njShowBits(int bits) { + unsigned char newbyte; + if (!bits) return 0; + while (nj.bufbits < bits) { + if (nj.size <= 0) { + nj.buf = (nj.buf << 8) | 0xFF; + nj.bufbits += 8; + continue; + } + newbyte = *nj.pos++; + nj.size--; + nj.bufbits += 8; + nj.buf = (nj.buf << 8) | newbyte; + if (newbyte == 0xFF) { + if (nj.size) { + unsigned char marker = *nj.pos++; + nj.size--; + switch (marker) { + case 0x00: + case 0xFF: + break; + case 0xD9: nj.size = 0; break; + default: + if ((marker & 0xF8) != 0xD0) + nj.error = NJ_SYNTAX_ERROR; + else { + nj.buf = (nj.buf << 8) | marker; + nj.bufbits += 8; + } + } + } else + nj.error = NJ_SYNTAX_ERROR; + } + } + return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1); +} + +NJ_INLINE void njSkipBits(int bits) { + if (nj.bufbits < bits) + (void) njShowBits(bits); + nj.bufbits -= bits; +} + +NJ_INLINE int njGetBits(int bits) { + int res = njShowBits(bits); + njSkipBits(bits); + return res; +} + +NJ_INLINE void njByteAlign(void) { + nj.bufbits &= 0xF8; +} + +static void njSkip(int count) { + nj.pos += count; + nj.size -= count; + nj.length -= count; + if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR; +} + +NJ_INLINE unsigned short njDecode16(const unsigned char *pos) { + return (pos[0] << 8) | pos[1]; +} + +static void njDecodeLength(void) { + if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR); + nj.length = njDecode16(nj.pos); + if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR); + njSkip(2); +} + +NJ_INLINE void njSkipMarker(void) { + njDecodeLength(); + njSkip(nj.length); +} + +NJ_INLINE void njDecodeSOF(void) { + int i, ssxmax = 0, ssymax = 0; + nj_component_t* c; + njDecodeLength(); + njCheckError(); + if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR); + if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED); + nj.height = njDecode16(nj.pos+1); + nj.width = njDecode16(nj.pos+3); + if (!nj.width || !nj.height) njThrow(NJ_SYNTAX_ERROR); + nj.ncomp = nj.pos[5]; + njSkip(6); + switch (nj.ncomp) { + case 1: + case 3: + break; + default: + njThrow(NJ_UNSUPPORTED); + } + if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR); + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { + c->cid = nj.pos[0]; + if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR); + if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two + if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR); + if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two + if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR); + njSkip(3); + nj.qtused |= 1 << c->qtsel; + if (c->ssx > ssxmax) ssxmax = c->ssx; + if (c->ssy > ssymax) ssymax = c->ssy; + } + if (nj.ncomp == 1) { + c = nj.comp; + c->ssx = c->ssy = ssxmax = ssymax = 1; + } + nj.mbsizex = ssxmax << 3; + nj.mbsizey = ssymax << 3; + nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex; + nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey; + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { + c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax; + c->height = (nj.height * c->ssy + ssymax - 1) / ssymax; + c->stride = nj.mbwidth * c->ssx << 3; + if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED); + if (!(c->pixels = (unsigned char*) njAllocMem(c->stride * nj.mbheight * c->ssy << 3))) njThrow(NJ_OUT_OF_MEM); + } + if (nj.ncomp == 3) { + nj.rgb = (unsigned char*) njAllocMem(nj.width * nj.height * nj.ncomp); + if (!nj.rgb) njThrow(NJ_OUT_OF_MEM); + } + njSkip(nj.length); +} + +NJ_INLINE void njDecodeDHT(void) { + int codelen, currcnt, remain, spread, i, j; + nj_vlc_code_t *vlc; + static unsigned char counts[16]; + njDecodeLength(); + njCheckError(); + while (nj.length >= 17) { + i = nj.pos[0]; + if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR); + if (i & 0x02) njThrow(NJ_UNSUPPORTED); + i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value + for (codelen = 1; codelen <= 16; ++codelen) + counts[codelen - 1] = nj.pos[codelen]; + njSkip(17); + vlc = &nj.vlctab[i][0]; + remain = spread = 65536; + for (codelen = 1; codelen <= 16; ++codelen) { + spread >>= 1; + currcnt = counts[codelen - 1]; + if (!currcnt) continue; + if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR); + remain -= currcnt << (16 - codelen); + if (remain < 0) njThrow(NJ_SYNTAX_ERROR); + for (i = 0; i < currcnt; ++i) { + register unsigned char code = nj.pos[i]; + for (j = spread; j; --j) { + vlc->bits = (unsigned char) codelen; + vlc->code = code; + ++vlc; + } + } + njSkip(currcnt); + } + while (remain--) { + vlc->bits = 0; + ++vlc; + } + } + if (nj.length) njThrow(NJ_SYNTAX_ERROR); +} + +NJ_INLINE void njDecodeDQT(void) { + int i; + unsigned char *t; + njDecodeLength(); + njCheckError(); + while (nj.length >= 65) { + i = nj.pos[0]; + if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR); + nj.qtavail |= 1 << i; + t = &nj.qtab[i][0]; + for (i = 0; i < 64; ++i) + t[i] = nj.pos[i + 1]; + njSkip(65); + } + if (nj.length) njThrow(NJ_SYNTAX_ERROR); +} + +NJ_INLINE void njDecodeDRI(void) { + njDecodeLength(); + njCheckError(); + if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR); + nj.rstinterval = njDecode16(nj.pos); + njSkip(nj.length); +} + +static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) { + int value = njShowBits(16); + int bits = vlc[value].bits; + if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; } + njSkipBits(bits); + value = vlc[value].code; + if (code) *code = (unsigned char) value; + bits = value & 15; + if (!bits) return 0; + value = njGetBits(bits); + if (value < (1 << (bits - 1))) + value += ((-1) << bits) + 1; + return value; +} + +NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) { + unsigned char code = 0; + int value, coef = 0; + njFillMem(nj.block, 0, sizeof(nj.block)); + c->dcpred += njGetVLC(&nj.vlctab[c->dctabsel][0], NULL); + nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0]; + do { + value = njGetVLC(&nj.vlctab[c->actabsel][0], &code); + if (!code) break; // EOB + if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR); + coef += (code >> 4) + 1; + if (coef > 63) njThrow(NJ_SYNTAX_ERROR); + nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef]; + } while (coef < 63); + for (coef = 0; coef < 64; coef += 8) + njRowIDCT(&nj.block[coef]); + for (coef = 0; coef < 8; ++coef) + njColIDCT(&nj.block[coef], &out[coef], c->stride); +} + +NJ_INLINE void njDecodeScan(void) { + int i, mbx, mby, sbx, sby; + int rstcount = nj.rstinterval, nextrst = 0; + nj_component_t* c; + njDecodeLength(); + njCheckError(); + if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR); + if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED); + njSkip(1); + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { + if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR); + if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR); + c->dctabsel = nj.pos[1] >> 4; + c->actabsel = (nj.pos[1] & 1) | 2; + njSkip(2); + } + if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED); + njSkip(nj.length); + for (mbx = mby = 0;;) { + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) + for (sby = 0; sby < c->ssy; ++sby) + for (sbx = 0; sbx < c->ssx; ++sbx) { + njDecodeBlock(c, &c->pixels[((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx) << 3]); + njCheckError(); + } + if (++mbx >= nj.mbwidth) { + mbx = 0; + if (++mby >= nj.mbheight) break; + } + if (nj.rstinterval && !(--rstcount)) { + njByteAlign(); + i = njGetBits(16); + if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR); + nextrst = (nextrst + 1) & 7; + rstcount = nj.rstinterval; + for (i = 0; i < 3; ++i) + nj.comp[i].dcpred = 0; + } + } + nj.error = __NJ_FINISHED; +} + +#if NJ_CHROMA_FILTER + +#define CF4A (-9) +#define CF4B (111) +#define CF4C (29) +#define CF4D (-3) +#define CF3A (28) +#define CF3B (109) +#define CF3C (-9) +#define CF3X (104) +#define CF3Y (27) +#define CF3Z (-3) +#define CF2A (139) +#define CF2B (-11) +#define CF(x) njClip(((x) + 64) >> 7) + +NJ_INLINE void njUpsampleH(nj_component_t* c) { + const int xmax = c->width - 3; + unsigned char *out, *lin, *lout; + int x, y; + out = (unsigned char*) njAllocMem((c->width * c->height) << 1); + if (!out) njThrow(NJ_OUT_OF_MEM); + lin = c->pixels; + lout = out; + for (y = c->height; y; --y) { + lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]); + lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]); + lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]); + for (x = 0; x < xmax; ++x) { + lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]); + lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]); + } + lin += c->stride; + lout += c->width << 1; + lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]); + lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]); + lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]); + } + c->width <<= 1; + c->stride = c->width; + njFreeMem((void*)c->pixels); + c->pixels = out; +} + +NJ_INLINE void njUpsampleV(nj_component_t* c) { + const int w = c->width, s1 = c->stride, s2 = s1 + s1; + unsigned char *out, *cin, *cout; + int x, y; + out = (unsigned char*) njAllocMem((c->width * c->height) << 1); + if (!out) njThrow(NJ_OUT_OF_MEM); + for (x = 0; x < w; ++x) { + cin = &c->pixels[x]; + cout = &out[x]; + *cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w; + *cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w; + *cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w; + cin += s1; + for (y = c->height - 3; y; --y) { + *cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w; + *cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w; + cin += s1; + } + cin += s1; + *cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w; + *cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w; + *cout = CF(CF2A * cin[0] + CF2B * cin[-s1]); + } + c->height <<= 1; + c->stride = c->width; + njFreeMem((void*) c->pixels); + c->pixels = out; +} + +#else + +NJ_INLINE void njUpsample(nj_component_t* c) { + int x, y, xshift = 0, yshift = 0; + unsigned char *out, *lin, *lout; + while (c->width < nj.width) { c->width <<= 1; ++xshift; } + while (c->height < nj.height) { c->height <<= 1; ++yshift; } + out = (unsigned char*) njAllocMem(c->width * c->height); + if (!out) njThrow(NJ_OUT_OF_MEM); + lin = c->pixels; + lout = out; + for (y = 0; y < c->height; ++y) { + lin = &c->pixels[(y >> yshift) * c->stride]; + for (x = 0; x < c->width; ++x) + lout[x] = lin[x >> xshift]; + lout += c->width; + } + c->stride = c->width; + njFreeMem((void*) c->pixels); + c->pixels = out; +} + +#endif + +NJ_INLINE void njConvert(void) { + int i; + nj_component_t* c; + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { + #if NJ_CHROMA_FILTER + while ((c->width < nj.width) || (c->height < nj.height)) { + if (c->width < nj.width) njUpsampleH(c); + njCheckError(); + if (c->height < nj.height) njUpsampleV(c); + njCheckError(); + } + #else + if ((c->width < nj.width) || (c->height < nj.height)) + njUpsample(c); + #endif + if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR); + } + if (nj.ncomp == 3) { + // convert to RGB + int x, yy; + unsigned char *prgb = nj.rgb; + const unsigned char *py = nj.comp[0].pixels; + const unsigned char *pcb = nj.comp[1].pixels; + const unsigned char *pcr = nj.comp[2].pixels; + for (yy = nj.height; yy; --yy) { + for (x = 0; x < nj.width; ++x) { + register int y = py[x] << 8; + register int cb = pcb[x] - 128; + register int cr = pcr[x] - 128; + *prgb++ = njClip((y + 359 * cr + 128) >> 8); + *prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8); + *prgb++ = njClip((y + 454 * cb + 128) >> 8); + } + py += nj.comp[0].stride; + pcb += nj.comp[1].stride; + pcr += nj.comp[2].stride; + } + } else if (nj.comp[0].width != nj.comp[0].stride) { + // grayscale -> only remove stride + unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride]; + unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width]; + int y; + for (y = nj.comp[0].height - 1; y; --y) { + njCopyMem(pout, pin, nj.comp[0].width); + pin += nj.comp[0].stride; + pout += nj.comp[0].width; + } + nj.comp[0].stride = nj.comp[0].width; + } +} + +void njInit(void) { + njFillMem(&nj, 0, sizeof(nj_context_t)); +} + +void njDone(void) { + int i; + for (i = 0; i < 3; ++i) + if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels); + if (nj.rgb) njFreeMem((void*) nj.rgb); + njInit(); +} + +nj_result_t njDecode(const void* jpeg, const int size) { + njDone(); + nj.pos = (const unsigned char*) jpeg; + nj.size = size & 0x7FFFFFFF; + if (nj.size < 2) return NJ_NO_JPEG; + if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG; + njSkip(2); + while (!nj.error) { + if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR; + njSkip(2); + switch (nj.pos[-1]) { + case 0xC0: njDecodeSOF(); break; + case 0xC4: njDecodeDHT(); break; + case 0xDB: njDecodeDQT(); break; + case 0xDD: njDecodeDRI(); break; + case 0xDA: njDecodeScan(); break; + case 0xFE: njSkipMarker(); break; + default: + if ((nj.pos[-1] & 0xF0) == 0xE0) + njSkipMarker(); + else + return NJ_UNSUPPORTED; + } + } + if (nj.error != __NJ_FINISHED) return nj.error; + nj.error = NJ_OK; + njConvert(); + return nj.error; +} + +int njGetWidth(void) { return nj.width; } +int njGetHeight(void) { return nj.height; } +int njIsColor(void) { return (nj.ncomp != 1); } +unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; } +int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; } + +#endif // _NJ_INCLUDE_HEADER_ONLY diff --git a/common/nanojpeg.h b/common/nanojpeg.h new file mode 100644 index 0000000..64ea7c2 --- /dev/null +++ b/common/nanojpeg.h @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////////////////////// +// HEADER SECTION // +// copy and pase this into nanojpeg.h if you want // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _NANOJPEG_H +#define _NANOJPEG_H + +// nj_result_t: Result codes for njDecode(). +typedef enum _nj_result { + NJ_OK = 0, // no error, decoding successful + NJ_NO_JPEG, // not a JPEG file + NJ_UNSUPPORTED, // unsupported format + NJ_OUT_OF_MEM, // out of memory + NJ_INTERNAL_ERR, // internal error + NJ_SYNTAX_ERROR, // syntax error + __NJ_FINISHED, // used internally, will never be reported +} nj_result_t; + +// njInit: Initialize NanoJPEG. +// For safety reasons, this should be called at least one time before using +// using any of the other NanoJPEG functions. +void njInit(void); + +// njDecode: Decode a JPEG image. +// Decodes a memory dump of a JPEG file into internal buffers. +// Parameters: +// jpeg = The pointer to the memory dump. +// size = The size of the JPEG file. +// Return value: The error code in case of failure, or NJ_OK (zero) on success. +nj_result_t njDecode(const void* jpeg, const int size); + +// njGetWidth: Return the width (in pixels) of the most recently decoded +// image. If njDecode() failed, the result of njGetWidth() is undefined. +int njGetWidth(void); + +// njGetHeight: Return the height (in pixels) of the most recently decoded +// image. If njDecode() failed, the result of njGetHeight() is undefined. +int njGetHeight(void); + +// njIsColor: Return 1 if the most recently decoded image is a color image +// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result +// of njGetWidth() is undefined. +int njIsColor(void); + +// njGetImage: Returns the decoded image data. +// Returns a pointer to the most recently image. The memory layout it byte- +// oriented, top-down, without any padding between lines. Pixels of color +// images will be stored as three consecutive bytes for the red, green and +// blue channels. This data format is thus compatible with the PGM or PPM +// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. +// If njDecode() failed, the result of njGetImage() is undefined. +unsigned char* njGetImage(void); + +// njGetImageSize: Returns the size (in bytes) of the image data returned +// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is +// undefined. +int njGetImageSize(void); + +// njDone: Uninitialize NanoJPEG. +// Resets NanoJPEG's internal state and frees all memory that has been +// allocated at run-time by NanoJPEG. It is still possible to decode another +// image after a njDone() call. +void njDone(void); + +#endif//_NANOJPEG_H diff --git a/common/nro.h b/common/nro.h new file mode 100644 index 0000000..6ad2cd7 --- /dev/null +++ b/common/nro.h @@ -0,0 +1,43 @@ +#pragma once + +#define NROHEADER_MAGICNUM 0x304f524e + +#define ASSETHEADER_MAGICNUM 0x54455341 +#define ASSETHEADER_VERSION 0 + +typedef struct { + u32 FileOff; + u32 Size; +} NsoSegment; + +typedef struct { + u32 unused; + u32 modOffset; + u8 Padding[8]; +} NroStart; + +typedef struct { + u32 Magic; + u32 Unk1; + u32 size; + u32 Unk2; + NsoSegment Segments[3]; + u32 bssSize; + u32 Unk3; + u8 BuildId[0x20]; + u8 Padding[0x20]; +} NroHeader; + +typedef struct { + u64 offset; + u64 size; +} AssetSection; + +typedef struct { + u32 magic; + u32 version; + AssetSection icon; + AssetSection nacp; + AssetSection romfs; +} AssetHeader; + diff --git a/common/text.c b/common/text.c new file mode 100644 index 0000000..6fa4b51 --- /dev/null +++ b/common/text.c @@ -0,0 +1,15 @@ +#include "text.h" + +//TODO: Update this once libnx supports settings get-language. + +static int s_textLang = /*CFG_LANGUAGE_EN*/1; + +int textGetLang(void) { + return s_textLang; +} + +const char* textGetString(StrId id) { + const char* str = g_strings[id][s_textLang]; + if (!str) str = g_strings[id][/*CFG_LANGUAGE_EN*/1]; + return str; +} diff --git a/common/text.h b/common/text.h new file mode 100644 index 0000000..6258e35 --- /dev/null +++ b/common/text.h @@ -0,0 +1,6 @@ +#pragma once +#include "common.h" +#include "language.h" + +int textGetLang(void); +const char* textGetString(StrId id); diff --git a/common/ui.c b/common/ui.c new file mode 100644 index 0000000..244c838 --- /dev/null +++ b/common/ui.c @@ -0,0 +1,18 @@ +#include "common.h" + +static bool s_shouldExit; + +void uiExitLoop(void) { + s_shouldExit = true; +} + +bool menuUpdate(void); + +bool uiUpdate(void) { + bool exitflag=0; + + exitflag = !menuUpdate(); + if (!exitflag) return exitflag; + + return !s_shouldExit; +} diff --git a/common/ui.h b/common/ui.h new file mode 100644 index 0000000..21df0d8 --- /dev/null +++ b/common/ui.h @@ -0,0 +1,6 @@ +#pragma once +#include "common.h" + +void uiExitLoop(void); + +bool uiUpdate(void); diff --git a/data/folder_icon.bin b/data/folder_icon.bin new file mode 100644 index 0000000..0ef96de --- /dev/null +++ b/data/folder_icon.bin @@ -0,0 +1 @@ +šׯثͤɦɪġ۝ƖƤרƠ·¤ӦƽͤڜѤۨͣןƻ̧իƻʨآʨӫ̥ȚͣŢͣśͥǝ̥Ȟ̥ȣͦǠͥʤˣǖͥƛ³ˤ̌żŞxzz{{xvxxzz{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{z{zzxvvtttvyz}}|{yxywɅŧäŞƛƛĠä§§äĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĤĢġàâäŝқƝĞ᥻ĞҙĞŠŠ¨ŠħĠâĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĠàĞĠŝĢŝĠĞĞĠĞäääåĢåĢåǥţƦţ£ݡə™Ϟˣʠ߫ſřƖŗŗŕĔŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŘÖ×ŗƚ› \ No newline at end of file diff --git a/data/switchicon_questionmark.bin b/data/switchicon_questionmark.bin new file mode 100644 index 0000000..6aba456 --- /dev/null +++ b/data/switchicon_questionmark.bin @@ -0,0 +1 @@ +ſϡbbbXXXMMMHHHHHHIIIIIIHHHIIIIIIIIIIIIIIIIIIIIIIIIGGG\\\BBB?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????;;;CCCAAAhhh@@@@@@@@@@@@@@@@@@@@@@@@===???BBBDDDDDDHHHUUUbbb֍OOODDD@@@BBBAAA@@@AAAAAA@@@??????????????????????????????DDD___===@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@>>>>>>XXX@@@@@@@@@@@@@@@@@@@@@@@@CCC???===???@@@AAAAAABBB???===IIIyyy쿿lll???BBBCCC???@@@@@@@@@@@@@@@???>>>@@@BBBAAAAAAAAAAAAAAAAAAAAAAAA@@@]]]FFF???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>BBBAAA===ZZZ@@@@@@@@@@@@@@@@@@@@@@@@>>>???AAABBBCCCAAA???>>>DDD>>>>>>@@@AAA]]]~~~CCC@@@AAA>>>@@@>>>AAA??????@@@BBBBBBAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@CCC\\\>>>@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@AAA???ZZZ@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA@@@>>>>>>???@@@@@@>>>AAA>>>@@@CCC<<>>@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@CCC񏏏BBBBBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAABBB===BBBAAAAAADDD<<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@DDD>>>>>>AAACCC:::EEE>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@CCClllEEE===>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>AAACCC>>>@@@BBB===AAA????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@@@BBB@@@??????@@@DDD@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>>>>fff采???@@@DDD@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???BBB;;;AAAAAA???@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<<>>eeeBBB???CCC@@@<<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@??????@@@EEEAAA<<>>AAAqqqCCC@@@???>>>CCCCCC@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAABBB???>>>>>>BBB===DDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA===???<<>>AAA@@@<<>>??????>>>CCC@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA:::GGG<<<@@@>>>DDDAAA???CCC<<>>CCC???>>>FFF???@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@AAA@@@BBB===DDD???@@@@@@@@@AAA@@@===BBB???>>>CCC@@@@@@@@@@@@@@@@@@@@@@@@CCCAAA???@@@AAA@@@@@@@@@@@@>>>CCC???===BBB???@@@???>>>AAA===BBB@@@>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@>>>AAA>>>AAA<<>>@@@@@@@@@@@@@@@@@@@@@@@@jjjZZZ??????@@@AAA@@@@@@@@@AAACCC???BBBAAAAAABBB>>>@@@;;;IIIAAAAAA===AAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@AAA@@@DDD@@@EEEᱱMMMCCC======FFF@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>AAA???@@@AAAAAA@@@??????@@@<<<@@@>>>??????>>>AAA@@@AAA>>>;;;@@@DDD===AAA???@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@>>>BBB<<>>CCC@@@;;;@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB쨨@@@@@@@@@@@@???????????????@@@AAAAAA>>>AAACCC@@@@@@BBB???DDDDDD;;;CCC???>>>@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB???QQQ>>>>>>EEE<<<@@@@@@@@@@@@@@@@@@@@@@@@???nnnzzzAAA@@@@@@???>>>???@@@AAA@@@BBB===DDD>>><<>>DDD@@@@@@CCC@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@>>>BBBAAArrr@@@???BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@===RRR___@@@???@@@@@@??????AAAAAA@@@<<>>???@@@@@@@@@@@@@@@@@@@@@@@@CCCBBBSSS??????AAAAAA@@@@@@@@@@@@???AAA???MMMWWWBBB@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@BBB<<>>AAA@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@??????BBB@@@???AAAAAAAAAAAA@@@CCC===<<>>CCC>>>CCCBBB===@@@???FFF<<<@@@BBBBBB>>>===BBB@@@AAABBBAAA===BBB===AAA@@@@@@BBB@@@@@@@@@@@@AAA@@@>>>BBBAAADDDGGG>>>@@@???BBBCCC===@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggҮ\\\DDD===@@@@@@CCC>>>AAACCC>>>???```@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???FFF===AAA>>>>>>AAACCC>>>;;;DDDAAA@@@{{{ИGGGAAA@@@AAABBB>>>AAA>>><<<@@@CCC>>>CCCAAA@@@@@@BBB@@@@@@???@@@AAA???@@@@@@???nnn񛛛AAA???CCC>>>???AAA@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggddd@@@======@@@BBBBBBAAAAAA???@@@???@@@???BBB@@@>>>???RRR@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYBBB<<>>===>>>YYYcccBBBAAA>>>@@@AAA===@@@@@@BBB@@@@@@???@@@??????DDD??????GGG@@@CCC???>>>BBB>>>BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg❝IIICCCCCCBBBCCCBBB?????????@@@CCC???BBBAAACCC===??????BBB???>>>===NNN@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAA>>>@@@DDDaaaAAAAAA???BBB?????????BBBCCC@@@谰@@@@@@DDD>>>BBB@@@@@@BBB@@@@@@>>>@@@@@@???BBB???SSSnnnBBB<<>>AAABBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggddd===BBBBBB<<<<<>>BBB???AAABBBCCC<<>>BBB;;;BBB}}}휜BBB<<>>???BBB>>>===AAAppp???>>>AAA@@@===BBBBBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggBBBCCC<<>>===BBB@@@:::@@@@@@===>>>@@@@@@XXX@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY===>>>CCCsss@@@AAAAAA<<>>AAA{{{잞:::DDDBBB@@@@@@>>>BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggfff===@@@EEE:::>>>@@@===@@@BBB>>>===@@@AAAAAA?????????@@@???AAACCC>>>CCC>>>DDD@@@???@@@TTT@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYDDD@@@>>>񀀀AAA===AAACCC===@@@BBBGGGPPP???@@@@@@@@@@@@BBB@@@>>>CCCAAA>>>AAABBB>>>vvv힞CCC>>>???@@@AAA@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggfffAAAAAA???EEE???>>>BBB^^^fffBBB<<>>BBB>>>>>>BBB@@@>>>ooo@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY>>>???AAA^^^>>>BBB@@@===???CCC???AAAHHH@@@???BBB@@@@@@BBB@@@AAA@@@@@@BBBAAA@@@FFFOOOyyy===@@@AAA???@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggooo???@@@BBB@@@RRRDŽ???AAA@@@AAA>>>@@@BBB>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???BBB@@@ddd𳳳>>>DDDAAA===AAAAAA@@@BBB???쩩BBBCCC===AAA@@@@@@BBB@@@AAA???@@@AAA@@@===???DDDOOO@@@AAA???BBB===AAABBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggBBB@@@OOOܻMMM>>>???>>>??????DDD???BBBNNN@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???AAA???>>>ooo괴AAACCC===<<>>???uuu===AAA@@@AAA@@@???@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggmmmPPP@@@FFF@@@>>>AAA>>>AAA999@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYBBB???===@@@CCCDDDssswwwYYY>>>DDD>>>BBBBBBVVVmmmBBB@@@@@@:::DDDfffyyyDDDBBB@@@@@@BBB???@@@@@@BBB@@@@@@@@@@@@@@@@@@AAABBBAAA???PPPBBB@@@@@@@@@???AAA@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg൵???<<>>@@@FFFVVV@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???@@@@@@??????CCC<<<>>>@@@CCC@@@???HHHccc???@@@DDD===@@@???BBB@@@<<>>AAA@@@@@@BBB@@@@@@AAAAAA??????@@@AAA@@@DDDTTTjjj>>>>>>@@@AAA<<>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???>>>CCC>>>AAAAAAAAADDD???@@@<<>>AAA@@@BBBAAA???===AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@???AAAAAA@@@======???@@@@@@XXX𣣣===???DDD<<>>BBBAAA@@@@@@BBB@@@??????@@@AAAAAA@@@@@@AAABBBAAA@@@GGG⬬III???@@@===EEE???>>>DDD===@@@>>>BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggggggBBBAAA;;;AAA???EEE???ppp@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???EEE???<<<@@@???CCCBBBBBB???@@@uuu@@@BBBAAAAAABBB>>>CCC@@@???BBB===BBB@@@@@@BBB@@@BBB@@@???@@@@@@??????@@@>>>EEE===>>>EEEAAAiii|||III@@@AAACCC??????AAA@@@@@@???AAA@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg㊊???BBB???@@@@@@>>>???UUU@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAA===BBBBBB???@@@@@@>>>===DDD???fffAAA>>>AAA>>>???BBB===AAAAAA<<>>>>>@@@AAAAAA@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg尰@@@>>>BBBAAA@@@AAA???KKK@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@AAA>>>AAABBBkkk>>>BBB???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA@@@>>>???@@@AAAAAA@@@>>>??????@@@BBBBBB@@@===@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@BBB@@@???AAA???BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@???BBB;;;BBBaaa@@@???>>>GGG;;;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>???AAAAAA@@@???>>>AAACCCBBB@@@??????@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@BBB@@@???@@@@@@===AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@AAA???AAADDD???NNNɭccc===BBBEEEAAA>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@??????AAAAAA??????AAA@@@===???AAAAAA@@@???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg@@@===BBB@@@AAA===AAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@BBBBBB===;;;AAA@@@???AAA@@@AAA@@@AAA<<<@@@:::FFF@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAA@@@>>>>>>@@@BBBAAACCCAAA???@@@AAAAAA@@@>>>@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggAAA===AAA@@@AAA???AAAHHH@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@>>>@@@DDD@@@AAA@@@===@@@>>>EEE???CCC???AAABBB<<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@???BBBAAA===<<<===>>>???@@@??????@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggBBBAAA???@@@>>>CCC<<>>AAA@@@AAA???AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>BBB???@@@DDD???===FFFBBBAAA@@@AAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ggg⡡AAABBB???@@@>>>@@@@@@ppp@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@>>>BBB@@@BBB@@@@@@AAA@@@BBBDDD@@@BBB???===DDD@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@???CCC???>>>AAA<<>>???BBBAAA???@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@gggႂ@@@===AAAAAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@???BBB@@@===AAACCC???@@@>>>BBB===???BBBAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>@@@BBB===AAASSSbbb@@@???AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA>>>BBB???dddIIIAAA@@@AAA??????@@@CCC@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@AAAAAA@@@>>>???CCC???@@@???@@@AAA??????CCCBBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA@@@@@@CCCOOOAAA???>>>@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???@@@???eee䨨<<>>>>>AAA@@@???BBB@@@DDD===BBB???<<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@AAABBBBBB;;;iii򚚚??????DDD@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???@@@AAAhhhRRRAAA???CCC>>>>>>>>>AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???>>>BBB===BBBBBB======@@@BBB<<>>???<<<@@@FFF@@@EEE<<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@AAABBB@@@AAA@@@EEEDDDDDDEEEEEEBBB>>>>>>AAA@@@<<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>DDDAAA???ooo򰰰@@@DDDAAA@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@>>>???ccc|||>>>CCCBBBBBBAAA======999yyy@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@===>>>BBB@@@ͧUUUDDDDDD???AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@???CCC;;;AAAJJJmmmAAA===???@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA@@@???@@@aaa䫫YYY@@@<<>>FFF===@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@===CCCBBB@@@hhhDDDCCC??????@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???@@@@@@???BBBbbbǗUUU@@@CCCAAAAAAAAAAAA>>>CCC??????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@HHHCCC@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB>>>BBB???<<>>BBBBBB<<>>??????CCC>>>BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@齽AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@AAA?????????EEEwwwNNNAAA===???AAABBBBBB?????????AAA???SSSsssAAACCC???===CCCBBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc⣣AAAAAA???AAAAAA@@@>>>DDDAAA?????????AAA>>>BBBAAA>>>BBBAAA@@@@@@>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAAAAA>>>BBBAAA@@@???XXX섄@@@===???AAA>>>BBB???@@@@@@@@@BBB@@@>>>@@@>>>EEEDDDXXXFFF?????????>>>???DDDBBB>>>ZZZꫫ???:::DDD<<>>BBB<<>>AAAAAA===AAA@@@@@@BBB@@@@@@CCCCCC;;;{{{>>>CCCBBB???@@@???@@@===FFF|||AAAEEE???BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccclll>>>@@@AAA???AAA@@@CCC???CCC@@@@@@===AAA\\\@@@???@@@AAA???@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@???DDD===@@@BBBHHHqqqBBB@@@BBBAAA>>>@@@@@@@@@@@@BBB@@@AAA======BBB___>>>??????AAA@@@BBB@@@\\\񼼼???>>>@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccclll@@@AAA???BBBAAA===>>>DDD>>>IIIeeeAAA@@@AAAAAA???@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@??????CCC???@@@\\\ꠠ>>>>>>DDD>>>>>>BBB@@@@@@@@@BBB@@@???AAADDD===```AAADDD>>>???@@@;;;FFF{{{빹CCC???BBBBBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccrrrAAA???@@@>>>???CCC@@@AAA@@@@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???@@@===EEE??????jjj@@@AAA???===CCC???AAA@@@@@@BBB@@@@@@AAA>>>===WWW@@@===@@@???EEE>>>>>>WWW񰰰AAA;;;CCCBBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccsssAAA===BBBDDD>>>@@@BBBAAA@@@???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???AAA???AAA???@@@fff믯???BBBAAA===CCC???@@@@@@@@@BBB@@@>>>AAA???DDDccc===CCC>>>AAA@@@===@@@??????ddd@@@BBB===BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccuuuAAA@@@AAA<<<@@@@@@CCC???AAA??????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@???AAATTT򙙙>>>???DDD???@@@@@@???@@@@@@BBB@@@CCC>>>@@@???AAA}}}쫫HHHBBB>>>CCC???@@@CCC>>>???BBBFFF~~~BBB>>>@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccxxx???DDD<<>>̼;;;BBB@@@@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAA@@@???CCC???AAAEEEhhhAAA@@@>>>CCC???>>>BBB@@@@@@BBB@@@@@@AAA??????AAA???LLLeeesssSSSBBB>>>@@@DDD\\\FFF@@@DDDAAA???UUUwwwdddLLL???===???@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc|||@@@AAA???@@@AAA@@@JJJ̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAA<<>>===FFF???:::AAABBB>>>]]]999AAACCC@@@DDDAAA???@@@AAAAAA@@@???BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc}}}@@@AAA???@@@AAA@@@MMM̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY>>>BBBCCC??????BBB:::IIIrrrAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@???BBB@@@>>>BBB@@@<<<<<>>@@@@@@???>>>@@@BBBBBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc@@@AAA???@@@@@@@@@RRR̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@CCC>>>???>>>AAACCC???dddEEE@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@???BBB@@@===???AAABBBCCC@@@???===@@@jjj<<<@@@@@@AAAAAA@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccĉ@@@@@@@@@@@@@@@@@@XXX̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@>>>DDDAAA===BBBAAA>>>rrr쭭DDD>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@???AAAAAA@@@???>>>???AAA???AAABBB???뀀EEE@@@AAA>>>>>>>>>@@@AAABBB@@@???BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc̅AAA@@@@@@@@@@@@AAA^^^̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???@@@@@@???BBBAAA>>>===BBBAAASSSہ@@@BBBBBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@AAA@@@@@@AAAAAA???????????????@@@CCCkkk===??????AAABBBBBB@@@??????@@@AAABBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc̈AAA@@@@@@@@@@@@AAAddd̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAAAAAAAA;;;BBBBBB???EEE>>>AAA@@@@@@[[[ótttFFF@@@CCC===>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@AAA??????@@@@@@BBBCCCAAAEEE<<>>@@@@@@CCC@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@>>>???AAAAAA???@@@AAA>>>===CCC>>>AAABBBjjjRRRCCC===AAA@@@@@@AAA@@@>>>>>>@@@AAA@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc̋AAA???@@@@@@@@@AAAkkk̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@AAAAAA===AAA@@@DDD???BBB@@@@@@<<>>EEE>>>@@@BBB??????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???@@@CCC<<>>AAA>>>AAABBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc˼hhhLLLFFFAAAHHHddd̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???AAA???@@@BBBBBBBBB>>>AAA@@@@@@AAA@@@???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>BBBCCC>>>AAAAAA<<>>AAA??????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB???AAA???>>>BBBBBB>>>@@@======AAABBB===???@@@???BBBAAA===<<>>CCC<<>>@@@>>>???CCC@@@>>>@@@DDD@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA<<<@@@CCC???>>>AAABBB>>>CCCAAA???AAA>>>AAAAAABBB>>>???BBBAAA???DDD===BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???AAA??????@@@DDD>>>CCC===JJJDDD<<>>AAA???>>>@@@AAA@@@<<<@@@CCC<<>>@@@AAA;;;AAAaaabbb???@@@DDD@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@DDD>>><<<@@@AAABBBBBBAAA:::DDDDDD>>>AAAAAACCC===BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@DDD<<>>???BBB??????BBBAAA===@@@===CCCBBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc²̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAA>>>BBB===CCCHHHFFFCCC@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>BBBAAA@@@DDD@@@<<>>@@@AAABBB===@@@EEEvvv̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY?????????CCCAAAppp???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAADDDCCC???@@@<<>>>>>CCC̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAA@@@@@@@@@@@@aaaSSSDDD@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@??????BBBBBB===??????CCC@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccɈAAADDD;;;CCCBBB===CCCAAA???̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY>>>AAA???<<>>AAAAAABBB>>>AAABBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccˁ@@@>>>BBB@@@>>>BBB??????@@@̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY???CCC>>>AAA<<>>DDD@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@DDD===@@@DDD@@@>>>BBB===>>>@@@???===EEE???BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???ccc̀@@@???BBB???===BBB@@@@@@@@@̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@BBBAAA>>>>>>AAAFFFAAA===BBB???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAAAAABBB???>>>DDD===???CCCCCC@@@>>>BBB<<<@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccʇ???EEE<<>>̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY>>>AAA>>>BBBBBBDDD@@@CCC>>>???BBBCCC>>>BBBEEE<<<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA===AAAAAA???AAA???<<>>>>>DDD???<<>>===AAA===??????BBB===BBB<<>>AAA>>>AAABBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccFFF===AAAAAAAAA???AAAAAAooo̻AAA@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYYAAA===CCCBBB;;;DDD@@@AAA???BBB>>>@@@AAA===@@@AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@SSS@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???cccɒFFF<<>>>>>CCC@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@===DDD>>>AAAAAAHHH@@@CCC>>>;;;>>>AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@```;AAA@@@AAAEEE===@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAABBBDDD===AAA===DDD압>>>BBBCCCCCC???AAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA@@@===@@@BBB[[[DDD???===BBB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@===@@@??????DDD???KKKeee@@@<<>>AAA>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@??????DDD<<>>AAAGGGCCC???AAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@??????@@@===CCCFFF>>>CCC@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA@@@>>>AAA@@@BBB??????@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@CCC@@@DDD>>>>>>@@@@@@??????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@???AAA======@@@>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????@@@BBBDDD?????????CCC>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA>>>>>>eee뤤FFF>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@===BBBDDD@@@???CCC@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@>>>===BBB@@@AAA===CCC@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@???AAA???YYY@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@CCC>>>bbbEEEAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA===CCC<<>>CCC>>>>>>AAA???AAAAAA@@@@@@@@@???@@@@@@AAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@___===EEE???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAA???>>>BBBWWW@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAA???===BBBAAABBB===@@@@@@@@@eeeqqq>>>AAACCC@@@???@@@???@@@BBBCCCAAA>>>===>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@BBB]]]BBB<<>>\\\@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@>>>???@@@AAA???===AAABBB>>>@@@???CCC@@@EEE櫫PPP===>>>???CCC@@@@@@>>>===???BBBCCCAAA???@@@@@@@@@@@@@@@@@@@@@@@@AAA```???EEE>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAAWWW@@@@@@@@@@@@@@@@@@@@@@@@===@@@CCCBBB@@@???@@@CCCAAA;;;@@@BBB>>><<<^^^KKKBBB???>>>AAAAAABBBAAA>>>===>>>AAA????????????????????????CCC]]]>>><<>>[[[@@@@@@@@@@@@@@@@@@@@@@@@BBB@@@======???AAA@@@??????AAAEEE;;;TTT뱱yyyQQQ???>>>???AAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@EEE^^^CCC???===AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB;;;BBBYYY@@@@@@@@@@@@@@@@@@@@@@@@>>>???@@@@@@@@@@@@AAAAAABBBCCClll뼼wwwkkkaaaWWWQQQTTTTTTTTTTTTTTTTTTTTTTTTYYY```GGGDDDIIIDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCDDDGGGCCCvvvRRRRRRRRRRRRRRRRRRRRRRRRPPPSSSTTTRRRRRR\\\ppp \ No newline at end of file diff --git a/data/tahoma12.nxfnt b/data/tahoma12.nxfnt new file mode 100644 index 0000000..f709e54 Binary files /dev/null and b/data/tahoma12.nxfnt differ diff --git a/data/tahoma24.nxfnt b/data/tahoma24.nxfnt new file mode 100644 index 0000000..9c06062 Binary files /dev/null and b/data/tahoma24.nxfnt differ diff --git a/nx_main/loaders/builtin.c b/nx_main/loaders/builtin.c new file mode 100644 index 0000000..3dd03d1 --- /dev/null +++ b/nx_main/loaders/builtin.c @@ -0,0 +1,32 @@ +#include "../common/common.h" + +static u32 argBuf[ENTRY_ARGBUFSIZE/sizeof(u32)]; + +static bool init(void) +{ + return envHasNextLoad(); +} + +static void deinit(void) +{ + +} + +static void launchFile(const char* path, argData_s* args) +{ + /*if (strncmp(path, "sdmc:/",6) == 0) + path += 5;*/ + memcpy(argBuf, args->buf, sizeof(args->buf)); + Result rc = envSetNextLoad(path, (char*)argBuf); + if(R_FAILED(rc)) fatalSimple(rc);//TODO: How should failing be handled? + uiExitLoop(); +} + +const loaderFuncs_s loader_builtin = +{ + .name = "builtin", + .init = init, + .deinit = deinit, + .launchFile = launchFile, +}; + diff --git a/nx_main/main.c b/nx_main/main.c new file mode 100644 index 0000000..682b455 --- /dev/null +++ b/nx_main/main.c @@ -0,0 +1,88 @@ +#include +#include +#include + +#include "../common/common.h" + +uint8_t* g_framebuf; + +int main(int argc, char **argv) +{ + gfxInitDefault(); + + appletSetScreenShotPermission(1); + + menuStartup(); + + launchInit(); + + while (appletMainLoop()) + { + //Scan all the inputs. This should be done once for each frame + hidScanInput(); + + g_framebuf = gfxGetFramebuffer(NULL, NULL); + memset(g_framebuf, 237, gfxGetFramebufferSize()); + if (!uiUpdate()) break; + menuLoop(); + + gfxFlushBuffers(); + gfxSwapBuffers(); + gfxWaitForVsync(); + } + + launchExit(); + + gfxExit(); + return 0; +} + +void launchMenuEntryTask(menuEntry_s* arg); + +//This is implemented here due to the hid code. +bool menuUpdate(void) { + bool exitflag = 0; + menu_s* menu = menuGetCurrent(); + u32 down = hidKeysDown(CONTROLLER_P1_AUTO); + + if (down & KEY_A) + { + if (menu->nEntries > 0) + { + int i; + menuEntry_s* me; + for (i = 0, me = menu->firstEntry; i != menu->curEntry; i ++, me = me->next); + launchMenuEntryTask(me); + //workerSchedule(launchMenuEntryTask, me); + } + } + else if (down & KEY_B) + { + //workerSchedule(changeDirTask, ".."); + menuScan(".."); + } + else if (down & KEY_PLUS) + { + exitflag = 1; + } + /*else if (down & KEY_Y) + { + workerSchedule(netloaderTask, NULL); + }*/ + else if (menu->nEntries > 0) + { + int move = 0; + + if (down & KEY_LEFT) move--; + if (down & KEY_RIGHT) move++; + if (down & KEY_DOWN) move-=4; + if (down & KEY_UP) move+=4; + + int newEntry = menu->curEntry + move; + if (newEntry < 0) newEntry = 0; + if (newEntry >= menu->nEntries) newEntry = menu->nEntries-1; + menu->curEntry = newEntry; + } + + return exitflag; +} diff --git a/nx_main/nx_launch.c b/nx_main/nx_launch.c new file mode 100644 index 0000000..d5420fa --- /dev/null +++ b/nx_main/nx_launch.c @@ -0,0 +1,75 @@ +#include "../common/common.h" + +static const loaderFuncs_s* s_loader; + +void launchInit(void) { +#define ADD_LOADER(_name) do \ + { \ + extern const loaderFuncs_s _name; \ + if (_name.init()) \ + { \ + s_loader = &_name; \ + return; \ + } \ + } while(0) + + ADD_LOADER(loader_builtin); + + // Shouldn't happen + fatalSimple(-1);//TODO: What value should be used for this? +} + +void launchExit(void) { + s_loader->deinit(); +} + +const loaderFuncs_s* launchGetLoader(void) { + return s_loader; +} + +void launchMenuEntry(menuEntry_s* me) { + /*bool canUseTitles = loaderCanUseTitles(); + if (me->descriptor.numTargetTitles && canUseTitles) + { + // Update the list of available titles + titlesCheckUpdate(false, UI_STATE_NULL); + + int i; + for (i = 0; i < me->descriptor.numTargetTitles; i ++) + if (titlesExists(me->descriptor.targetTitles[i].tid, me->descriptor.targetTitles[i].mediatype)) + break; + + if (i == me->descriptor.numTargetTitles) + { + errorScreen(s_loader->name, textGetString(StrId_MissingTargetTitle)); + return; + } + + // Use the title + s_loader->useTitle(me->descriptor.targetTitles[i].tid, me->descriptor.targetTitles[i].mediatype); + } else if (me->descriptor.selectTargetProcess) + { + if (!canUseTitles) + { + errorScreen(s_loader->name, textGetString(StrId_NoTargetTitleSupport)); + return; + } + + // Launch the title selector + if (!me->titleSelected) + { + titleSelectInit(me); + return; + } + + // Use the title + s_loader->useTitle(me->titleId, me->titleMediatype); + }*/ + + // Scan the executable if needed + /*if (loaderHasFlag(LOADER_NEED_SCAN)) + descriptorScanFile(&me->descriptor, me->path);*/ + + // Launch it + s_loader->launchFile(me->path, &me->args); +} diff --git a/pc_main/main.cpp b/pc_main/main.cpp new file mode 100644 index 0000000..5d38289 --- /dev/null +++ b/pc_main/main.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +extern "C" { + +#include "../common/common.h" + +} + +color_t pixels[720][1280]; + +int main() +{ + sf::RenderWindow window(sf::VideoMode(1280, 720), "Test"); + window.setFramerateLimit(60); + + menuStartup(); + + while (window.isOpen()) + { + sf::Event event; + while (window.pollEvent(event)) + { + if (event.type == sf::Event::Closed) + { + window.close(); + break; + } + } + + memset(pixels, 237, sizeof(pixels)); + + if (!uiUpdate()) break; + menuLoop(); + window.clear(); + + sf::Image image; + image.create(1280, 720, (const unsigned char*)pixels); + + sf::Texture texture; + texture.loadFromImage(image); + + sf::Sprite sprite; + sprite.setTexture(texture); + + window.draw(sprite); + window.display(); + } + + return 0; +} + +extern "C" bool menuUpdate(void) { + return 0; +} diff --git a/pc_main/pc_launch.c b/pc_main/pc_launch.c new file mode 100644 index 0000000..d9d76d7 --- /dev/null +++ b/pc_main/pc_launch.c @@ -0,0 +1,19 @@ +#include "../common/common.h" + +static const loaderFuncs_s* s_loader; + +void launchInit(void) { + +} + +void launchExit(void) { + //s_loader->deinit(); +} + +const loaderFuncs_s* launchGetLoader(void) { + return s_loader; +} + +void launchMenuEntry(menuEntry_s* me) { + +} diff --git a/resources/folder_icon_.jpg b/resources/folder_icon_.jpg new file mode 100644 index 0000000..938dfce Binary files /dev/null and b/resources/folder_icon_.jpg differ diff --git a/resources/switchicon-questionmark.jpg b/resources/switchicon-questionmark.jpg new file mode 100644 index 0000000..53307b4 Binary files /dev/null and b/resources/switchicon-questionmark.jpg differ