Initial public commit.

This commit is contained in:
yellows8 2018-02-11 19:07:18 -05:00
commit b6816abe56
36 changed files with 3518 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*~
*.exe
*.o
test
tahoma12.c
tahoma24.c

7
Makefile Normal file
View File

@ -0,0 +1,7 @@
all:
make -f Makefile.nx
make -f Makefile.pc
clean:
make -f Makefile.pc clean
make -f Makefile.nx clean

189
Makefile.nx Normal file
View File

@ -0,0 +1,189 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/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):
# - <Project name>.jpg
# - icon.jpg
# - <libnx folder>/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
#---------------------------------------------------------------------------------------

38
Makefile.pc Normal file
View File

@ -0,0 +1,38 @@
# canned command sequence for binary data
#---------------------------------------------------------------------------------
define bin2o
bin2s $< | $(AS) -o $(@)
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
endef
test : pc_main/main.cpp pc_main/pc_launch.c \
common/menu.c common/font.c common/language.c common/launch.c \
common/menu-entry.c common/menu-list.c common/text.c \
common/nanojpeg.c common/ui.c \
build_pc/tahoma24.o build_pc/tahoma12.o build_pc/switchicon_questionmark.bin.o build_pc/folder_icon.bin.o
gcc -Wall -O2 -g0 $^ -lsfml-graphics -lsfml-window -lsfml-system -lstdc++ -I. -Ibuild_pc -o $@
build_pc/tahoma12.o : data/tahoma12.nxfnt
mkdir -p $(dir $@)
@echo $(notdir $<)
@$(bin2o)
build_pc/tahoma24.o : data/tahoma24.nxfnt
mkdir -p $(dir $@)
@echo $(notdir $<)
@$(bin2o)
build_pc/switchicon_questionmark.bin.o : data/switchicon_questionmark.bin
mkdir -p $(dir $@)
@echo $(notdir $<)
@$(bin2o)
build_pc/folder_icon.bin.o : data/folder_icon.bin
mkdir -p $(dir $@)
@echo $(notdir $<)
@$(bin2o)
clean:
rm -rf build_pc/ test

9
README.md Normal file
View File

@ -0,0 +1,9 @@
#### Building
Build with Makefile.nx directly or just run ```make```.
#### Credits
* This uses code based on 3DS [new-hbmenu](https://github.com/fincs/new-hbmenu).
* The "switchicon-questionmark" icon is based on the icon by [Sweet Farm from the Noun Project](https://thenounproject.com/term/nintendo-switch/694750/).
* "folder_icon": [Original](https://www.iconfinder.com/icons/97888/docs_folder_google_icon) icon by Dakirby309, modified by @fincs.
* [nanojpeg](svn.emphy.de/nanojpeg/trunk/nanojpeg/nanojpeg.c) is used for handling JPEG icons.

106
common/common.h Normal file
View File

@ -0,0 +1,106 @@
#pragma once
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <limits.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <malloc.h>
#ifdef SWITCH
#include <switch.h>
#endif
#include <stdint.h>
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);

168
common/font.c Normal file
View File

@ -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);
}

36
common/font.h Normal file
View File

@ -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

773
common/language.c Normal file
View File

@ -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"
),
},*/
};

37
common/language.h Normal file
View File

@ -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];

59
common/launch.c Normal file
View File

@ -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(str<endarg);
}

24
common/launch.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include "common.h"
typedef struct
{
// Mandatory fields
const char* name;
//u32 flags;
bool (* init)(void);
void (* deinit)(void);
void (* launchFile)(const char* path, argData_s* args);
// Optional fields
//void (* useTitle)(u64 tid, u8 mediatype);
} loaderFuncs_s;
void launchInit(void);
void launchExit(void);
const loaderFuncs_s* launchGetLoader(void);
size_t launchAddArg(argData_s* ad, const char* arg);
void launchAddArgsFromString(argData_s* ad, char* arg);
void launchMenuEntry(menuEntry_s* me);

0
common/main.h Normal file
View File

318
common/menu-entry.c Normal file
View File

@ -0,0 +1,318 @@
#include "common.h"
void menuEntryInit(menuEntry_s* me, MenuEntryType type) {
memset(me, 0, sizeof(*me));
me->type = 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;
}

150
common/menu-list.c Normal file
View File

@ -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;
}

140
common/menu.c Normal file
View File

@ -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; tmpx<width; tmpx++) {
for (tmpy=0; tmpy<height; tmpy++) {
pos = ((tmpy*width) + tmpx) * 3;
DrawPixelRaw(x+tmpx, y+tmpy, MakeColor(image[pos+0], image[pos+1], image[pos+2], 255));
}
}
}
static void drawEntry(menuEntry_s* me, int n, int is_active) {
int x, y;
int start_y = 96 + 304 - 32;//*(n % 2);
int end_y = start_y + 288;
int start_x = 64 + (256+16)*n;//(n / 2);
int end_x = /*1280 - 64*/start_x + 256;
uint8_t *imageptr = NULL;
char tmpstr[1024];
color_t border_color0 = MakeColor(255, 255, 255, 255);
color_t border_color1 = MakeColor(255, 255, 255, 255);
if (is_active) {
border_color0 = MakeColor(171, 224, 245, 255);
border_color1 = MakeColor(189, 228, 242, 255);
}
//{
for (x=(start_x-3); x<(end_x+3); x++) {
//DrawPixelRaw(x, start_y , border_color0);
//DrawPixelRaw(x, end_y , border_color0);
DrawPixelRaw(x, start_y-1, border_color0);
DrawPixelRaw(x, end_y +1, border_color0);
DrawPixelRaw(x, start_y-2, border_color0);
DrawPixelRaw(x, end_y +2, border_color0);
DrawPixelRaw(x, start_y-3, border_color1);
DrawPixelRaw(x, end_y +3, border_color1);
}
for (y=(start_y-3); y<(end_y+3); y++) {
DrawPixelRaw(start_x , y, border_color0);
DrawPixelRaw(end_x , y, border_color0);
DrawPixelRaw(start_x-1, y, border_color0);
DrawPixelRaw(end_x +1, y, border_color0);
DrawPixelRaw(start_x-2, y, border_color0);
DrawPixelRaw(end_x +2, y, border_color0);
DrawPixelRaw(start_x-3, y, border_color1);
//DrawPixelRaw(end_x +3, y, border_color1);
}
//}
for (x=start_x; x<end_x; x++) {
for (y=start_y; y<end_y; y++) {
DrawPixelRaw(x, y, MakeColor(255, 255, 255, 255));
}
}
if (me->icon_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;
}
}
}
}

75
common/menu.h Normal file
View File

@ -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;
}

41
common/nacp.h Normal file
View File

@ -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;

916
common/nanojpeg.c Normal file
View File

@ -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 <martin.fiedler@gmx.net>
// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[]) {
int size;
char *buf;
FILE *f;
if (argc < 2) {
printf("Usage: %s <input.jpg> [<output.ppm>]\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 <stdlib.h>
#include <string.h>
#define njAllocMem malloc
#define njFreeMem free
#define njFillMem memset
#define njCopyMem memcpy
#elif NJ_USE_WIN32
#include <windows.h>
#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

66
common/nanojpeg.h Normal file
View File

@ -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

43
common/nro.h Normal file
View File

@ -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;

15
common/text.c Normal file
View File

@ -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;
}

6
common/text.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include "common.h"
#include "language.h"
int textGetLang(void);
const char* textGetString(StrId id);

18
common/ui.c Normal file
View File

@ -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;
}

6
common/ui.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include "common.h"
void uiExitLoop(void);
bool uiUpdate(void);

1
data/folder_icon.bin Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
data/tahoma12.nxfnt Normal file

Binary file not shown.

BIN
data/tahoma24.nxfnt Normal file

Binary file not shown.

32
nx_main/loaders/builtin.c Normal file
View File

@ -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,
};

88
nx_main/main.c Normal file
View File

@ -0,0 +1,88 @@
#include <switch.h>
#include <string.h>
#include <stdio.h>
#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;
}

75
nx_main/nx_launch.c Normal file
View File

@ -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);
}

56
pc_main/main.cpp Normal file
View File

@ -0,0 +1,56 @@
#include <SFML/Graphics.hpp>
#include <string.h>
#include <math.h>
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;
}

19
pc_main/pc_launch.c Normal file
View File

@ -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) {
}

BIN
resources/folder_icon_.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB