├── source ├── program.shlist ├── ui │ ├── reboot.h │ ├── titleselect.h │ ├── netloader.h │ ├── netsender.h │ ├── error.h │ ├── menu.h │ ├── background.h │ ├── error.c │ ├── reboot.c │ ├── titleselect.c │ ├── background.c │ ├── netloader.c │ ├── menu.c │ └── netsender.c ├── worker.h ├── parsing │ ├── cppsupport.cpp │ ├── shortcut.h │ ├── scanner.h │ ├── 3dsx.h │ ├── smdh.h │ ├── descriptor.h │ ├── memmap.h │ ├── shortcut.cpp │ ├── memmap.cpp │ ├── descriptor.cpp │ └── scanner.c ├── network.h ├── titles.h ├── math-stubs.c ├── text.h ├── common.h ├── language.h ├── shaders │ ├── drawing.pica │ └── wave.pica ├── ui.h ├── launch.h ├── worker.c ├── drawing.h ├── ui.c ├── network.c ├── main.c ├── lzss.s ├── menu.h ├── loaders │ ├── rosalina.c │ └── hax2.c ├── titles.c ├── launch.c ├── text.c ├── menu-list.c ├── drawing.c ├── menu-entry.c └── language.c ├── icon.png ├── .gitignore ├── preview.png ├── gfx ├── logo.png ├── logo2.png ├── star.png ├── wifi0.png ├── wifi1.png ├── wifi2.png ├── wifi3.png ├── battery0.png ├── battery1.png ├── battery2.png ├── battery3.png ├── battery4.png ├── bubble.png ├── loading.png ├── settings.png ├── wifiNull.png ├── appbubble.png ├── folderIcon.png ├── batteryCharge.png ├── defaultIcon.png ├── scrollbarKnob.png ├── scrollbarTrack.png └── images.t3s ├── .github └── workflows │ ├── release.yaml │ └── build.yaml ├── README.md └── Makefile /source/program.shlist: -------------------------------------------------------------------------------- 1 | shaders/drawing.pica 2 | shaders/wave.pica 3 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*/ 2 | build/ 3 | *.t3x 4 | *.3dsx 5 | *.smdh 6 | *.exe 7 | *.elf 8 | *~ 9 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/preview.png -------------------------------------------------------------------------------- /gfx/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/logo.png -------------------------------------------------------------------------------- /gfx/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/logo2.png -------------------------------------------------------------------------------- /gfx/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/star.png -------------------------------------------------------------------------------- /gfx/wifi0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/wifi0.png -------------------------------------------------------------------------------- /gfx/wifi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/wifi1.png -------------------------------------------------------------------------------- /gfx/wifi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/wifi2.png -------------------------------------------------------------------------------- /gfx/wifi3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/wifi3.png -------------------------------------------------------------------------------- /gfx/battery0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/battery0.png -------------------------------------------------------------------------------- /gfx/battery1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/battery1.png -------------------------------------------------------------------------------- /gfx/battery2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/battery2.png -------------------------------------------------------------------------------- /gfx/battery3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/battery3.png -------------------------------------------------------------------------------- /gfx/battery4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/battery4.png -------------------------------------------------------------------------------- /gfx/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/bubble.png -------------------------------------------------------------------------------- /gfx/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/loading.png -------------------------------------------------------------------------------- /gfx/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/settings.png -------------------------------------------------------------------------------- /gfx/wifiNull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/wifiNull.png -------------------------------------------------------------------------------- /gfx/appbubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/appbubble.png -------------------------------------------------------------------------------- /gfx/folderIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/folderIcon.png -------------------------------------------------------------------------------- /gfx/batteryCharge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/batteryCharge.png -------------------------------------------------------------------------------- /gfx/defaultIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/defaultIcon.png -------------------------------------------------------------------------------- /gfx/scrollbarKnob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/scrollbarKnob.png -------------------------------------------------------------------------------- /gfx/scrollbarTrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooolgamer/the-pirate-launcher/HEAD/gfx/scrollbarTrack.png -------------------------------------------------------------------------------- /source/ui/reboot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../common.h" 3 | 4 | void rebootUpdate(void); 5 | void rebootDrawBot(void); 6 | -------------------------------------------------------------------------------- /source/ui/titleselect.h: -------------------------------------------------------------------------------- 1 | #include "../common.h" 2 | 3 | void titleSelectInit(menuEntry_s* me); 4 | void titleSelectUpdate(void); 5 | void titleSelectDrawBot(void); 6 | -------------------------------------------------------------------------------- /source/worker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | void workerInit(void); 5 | void workerExit(void); 6 | void workerSchedule(ThreadFunc func, void* data); 7 | -------------------------------------------------------------------------------- /source/ui/netloader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../network.h" 3 | 4 | void netloaderTask(void* arg); 5 | void netloaderUpdate(void); 6 | void netloaderDrawBot(void); 7 | void netloaderExit(void); 8 | -------------------------------------------------------------------------------- /source/ui/netsender.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../network.h" 3 | 4 | void netsenderTask(void* arg); 5 | void netsenderUpdate(void); 6 | void netsenderDrawBot(void); 7 | void netsenderExit(void); 8 | -------------------------------------------------------------------------------- /source/ui/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../common.h" 3 | 4 | void errorScreen(const char* title, const char* fmt, ...) __attribute__((format (printf, 2, 3))); 5 | void errorUpdate(void); 6 | void errorDrawBot(void); 7 | -------------------------------------------------------------------------------- /source/ui/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../common.h" 3 | 4 | #define SCROLLING_SPEED 16 5 | 6 | void menuUpdate(void); 7 | void menuDrawTop(float iod); 8 | void menuDrawBot(void); 9 | void menuLoadFileAssoc(void); 10 | 11 | float menuDrawEntry(menuEntry_s* me, float x, float y, bool selected); 12 | -------------------------------------------------------------------------------- /source/parsing/cppsupport.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" void __cxa_pure_virtual() 4 | { 5 | } 6 | 7 | void* operator new(size_t size) { return malloc(size); } 8 | void operator delete(void *p) { free(p); } 9 | void* operator new[](size_t size) { return malloc(size); } 10 | void operator delete[](void *p) { free(p); } 11 | -------------------------------------------------------------------------------- /source/parsing/shortcut.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include <3ds.h> 3 | 4 | typedef struct 5 | { 6 | char* executable; 7 | char* descriptor; 8 | char* icon; 9 | char* arg; 10 | char* name; 11 | char* description; 12 | char* author; 13 | } shortcut_s; 14 | 15 | Result shortcutCreate(shortcut_s* d, const char* path); 16 | void shortcutFree(shortcut_s* d); 17 | -------------------------------------------------------------------------------- /source/network.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | #define NETWORK_PORT 17491 5 | 6 | bool networkInit(void); 7 | void networkDeactivate(void); 8 | void networkError(void (* update)(void), StrId titleStrId, const char* func, int err); 9 | void networkDrawBot(StrId titleStrId, const char* other, bool transferring, size_t filelen, size_t filetotal); 10 | -------------------------------------------------------------------------------- /source/titles.h: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | typedef bool (*TitleFilterFn)(u64 tid); 4 | 5 | void titlesClear(void); 6 | int titlesCount(void); 7 | void titlesGetEntry(u64* outTid, u8* outMediatype, int index); 8 | bool titlesExists(u64 tid, u8 mediatype); 9 | bool titlesCheckUpdate(bool async, UIState newState); 10 | bool titlesLoadSmdh(smdh_s* smdh, u8 mediatype, u64 tid); 11 | -------------------------------------------------------------------------------- /source/ui/background.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../common.h" 3 | 4 | #define BUBBLE_COUNT 64 5 | 6 | typedef struct 7 | { 8 | s32 x, y; 9 | float z; 10 | float angle, angleSin; 11 | float angv; 12 | u8 fade; 13 | } bubble_t; 14 | 15 | void backgroundInit(void); 16 | void backgroundUpdate(void); 17 | void backgroundDrawTop(float iod); 18 | void backgroundDrawBot(void); 19 | -------------------------------------------------------------------------------- /source/parsing/scanner.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include <3ds.h> 3 | #include "3dsx.h" 4 | #define NUM_SERVICESTHATMATTER 5 5 | 6 | typedef struct 7 | { 8 | bool scanned; 9 | u32 sectionSizes[3]; 10 | bool servicesThatMatter[NUM_SERVICESTHATMATTER]; 11 | } executableMetadata_s; 12 | 13 | void scannerInit(executableMetadata_s* em); 14 | void scannerScan(executableMetadata_s* em, const char* path, bool autodetectServices); 15 | -------------------------------------------------------------------------------- /gfx/images.t3s: -------------------------------------------------------------------------------- 1 | --atlas -f rgba8888 -z auto 2 | 3 | appbubble.png 4 | battery0.png 5 | battery1.png 6 | battery2.png 7 | battery3.png 8 | battery4.png 9 | batteryCharge.png 10 | bubble.png 11 | defaultIcon.png 12 | folderIcon.png 13 | loading.png 14 | logo.png 15 | logo2.png 16 | scrollbarKnob.png 17 | scrollbarTrack.png 18 | settings.png 19 | wifi0.png 20 | wifi1.png 21 | wifi2.png 22 | wifi3.png 23 | wifiNull.png 24 | star.png 25 | -------------------------------------------------------------------------------- /source/math-stubs.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static float fast_cos(float x) 4 | { 5 | // Adapted from here: https://stackoverflow.com/a/28050328 6 | x -= 0.25f + floorf(x + 0.25f); 7 | x *= 16.0f * (fabs(x) - 0.5f); 8 | x += 0.225f * x * (fabs(x) - 1.0f); 9 | return x; 10 | } 11 | 12 | float sinf(float x) 13 | { 14 | return fast_cos(x / (2*M_PI) - 0.25f); 15 | } 16 | 17 | float cosf(float x) 18 | { 19 | return fast_cos(x / (2*M_PI)); 20 | } 21 | -------------------------------------------------------------------------------- /source/text.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | #include "language.h" 4 | 5 | void textInit(void); 6 | void textExit(void); 7 | int textGetLang(void); 8 | const char* textGetString(StrId id); 9 | void textSetColor(u32 color); 10 | float textCalcWidth(const char* text); 11 | void textDraw(float x, float y, float scaleX, float scaleY, bool baseline, const char* text); 12 | void textDrawInBox(const char* text, int orientation, float scaleX, float scaleY, float baseline, float left, float right); 13 | -------------------------------------------------------------------------------- /source/parsing/3dsx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include <3ds.h> 3 | 4 | #define _3DSX_MAGIC 0x58534433 // '3DSX' 5 | #define _3DSX_HEADER_SIZE offsetof(_3DSX_Header, smdhOffset) 6 | 7 | // File header 8 | typedef struct 9 | { 10 | u32 magic; 11 | u16 headerSize, relocHdrSize; 12 | u32 formatVer; 13 | u32 flags; 14 | 15 | // Sizes of the code, rodata and data segments + 16 | // size of the BSS section (uninitialized latter half of the data segment) 17 | u32 codeSegSize, rodataSegSize, dataSegSize, bssSize; 18 | 19 | // Extended header below: 20 | 21 | // offset and size of smdh 22 | u32 smdhOffset, smdhSize; 23 | // offset to filesystem 24 | u32 fsOffset; 25 | } _3DSX_Header; 26 | -------------------------------------------------------------------------------- /source/parsing/smdh.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include <3ds.h> 3 | 4 | typedef struct 5 | { 6 | u32 magic; 7 | u16 version; 8 | u16 reserved; 9 | } smdhHeader_s; 10 | 11 | typedef struct 12 | { 13 | u16 shortDescription[0x40]; 14 | u16 longDescription[0x80]; 15 | u16 publisher[0x40]; 16 | } smdhTitle_s; 17 | 18 | typedef struct 19 | { 20 | u8 gameRatings[0x10]; 21 | u32 regionLock; 22 | u8 matchMakerId[0xC]; 23 | u32 flags; 24 | u16 eulaVersion; 25 | u16 reserved; 26 | u32 defaultFrame; 27 | u32 cecId; 28 | } smdhSettings_s; 29 | 30 | typedef struct 31 | { 32 | smdhHeader_s header; 33 | smdhTitle_s applicationTitles[16]; 34 | smdhSettings_s settings; 35 | u8 reserved[0x8]; 36 | u8 smallIconData[0x480]; 37 | u16 bigIconData[0x900]; 38 | } smdh_s; 39 | -------------------------------------------------------------------------------- /source/parsing/descriptor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "scanner.h" 3 | 4 | typedef struct 5 | { 6 | u64 tid; 7 | u8 mediatype; 8 | } targetTitle_s; 9 | 10 | typedef struct 11 | { 12 | char name[9]; 13 | int priority; 14 | } serviceRequest_s; 15 | 16 | typedef struct 17 | { 18 | targetTitle_s* targetTitles; 19 | u32 numTargetTitles; 20 | 21 | serviceRequest_s *requestedServices; 22 | u32 numRequestedServices; 23 | 24 | bool selectTargetProcess; 25 | bool autodetectServices; 26 | 27 | executableMetadata_s executableMetadata; 28 | } descriptor_s; 29 | 30 | void descriptorInit(descriptor_s* d); 31 | void descriptorFree(descriptor_s* d); 32 | void descriptorLoad(descriptor_s* d, const char* path); 33 | void descriptorScanFile(descriptor_s* d, const char* path); 34 | -------------------------------------------------------------------------------- /source/parsing/memmap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include <3ds.h> 3 | 4 | typedef struct 5 | { 6 | u32 num; 7 | u32 text_end; 8 | u32 data_address; 9 | u32 data_size; 10 | u32 processLinearOffset; 11 | u32 processHookAddress; 12 | u32 processAppCodeAddress; 13 | u32 processHookTidLow, processHookTidHigh; 14 | u32 mediatype; 15 | bool capabilities[0x10]; // {socuAccess, csndAccess, qtmAccess, nfcAccess, httpcAccess, reserved...} 16 | } memmap_header_t; 17 | 18 | typedef struct 19 | { 20 | u32 src, dst, size; 21 | } memmap_entry_t; 22 | 23 | typedef struct 24 | { 25 | memmap_header_t header; 26 | memmap_entry_t map[]; 27 | } memmap_t; 28 | 29 | #define memmapSize(m) (sizeof(memmap_header_t) + sizeof(memmap_entry_t)*(m)->header.num) 30 | memmap_t* memmapLoad(const char* path); 31 | -------------------------------------------------------------------------------- /source/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // C stdlib includes 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // 3DS includes 21 | #include <3ds.h> 22 | #include 23 | #include 24 | 25 | // hbmenu includes 26 | #include "ui.h" 27 | #include "drawing.h" 28 | #include "text.h" 29 | #include "worker.h" 30 | #include "menu.h" 31 | #include "launch.h" 32 | #include "titles.h" 33 | #include "ui/error.h" 34 | 35 | #define DIRECTORY_SEPARATOR_CHAR '/' 36 | static const char DIRECTORY_SEPARATOR[] = "/"; 37 | 38 | bool isDebugMode(); 39 | -------------------------------------------------------------------------------- /source/ui/error.c: -------------------------------------------------------------------------------- 1 | #include "error.h" 2 | #include 3 | 4 | static const char* errorTitle; 5 | static char errorStr[256]; 6 | 7 | void errorScreen(const char* title, const char* fmt, ...) 8 | { 9 | va_list va; 10 | va_start(va, fmt); 11 | vsnprintf(errorStr, sizeof(errorStr), fmt, va); 12 | va_end(va); 13 | errorTitle = title; 14 | uiEnterState(UI_STATE_ERROR); 15 | } 16 | 17 | void errorUpdate(void) 18 | { 19 | if (hidKeysDown() & KEY_B) 20 | uiExitState(); 21 | } 22 | 23 | void errorDrawBot(void) 24 | { 25 | drawingSetMode(DRAW_MODE_DRAWING); 26 | drawingSetZ(0.4f); 27 | 28 | drawingWithColor(0x80FFFFFF); 29 | drawingDrawQuad(0.0f, 60.0f, 320.0f, 120.0f); 30 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 31 | 32 | textSetColor(0xFF545454); 33 | textDrawInBox(errorTitle, 0, 0.75f, 0.75f, 60.0f+25.0f, 8.0f, 320-8.0f); 34 | textDraw(8.0f, 60.0f+25.0f+8.0f, 0.5f, 0.5f, false, errorStr); 35 | } 36 | -------------------------------------------------------------------------------- /source/language.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include <3ds.h> 3 | 4 | typedef enum 5 | { 6 | StrId_Loading = 0, 7 | StrId_Directory, 8 | StrId_DefaultLongTitle, 9 | StrId_DefaultPublisher, 10 | StrId_IOError, 11 | StrId_CouldNotOpenFile, 12 | 13 | StrId_NoAppsFound_Title, 14 | StrId_NoAppsFound_Msg, 15 | 16 | StrId_Reboot, 17 | StrId_ReturnToHome, 18 | 19 | StrId_TitleSelector, 20 | StrId_ErrorReadingTitleMetadata, 21 | StrId_NoTitlesFound, 22 | StrId_SelectTitle, 23 | 24 | StrId_NoTargetTitleSupport, 25 | StrId_MissingTargetTitle, 26 | 27 | StrId_NetworkOffline, 28 | StrId_NetworkError, 29 | StrId_NetworkTransferring, 30 | 31 | StrId_NetLoader, 32 | StrId_NetLoaderUnavailable, 33 | StrId_NetLoaderActive, 34 | 35 | StrId_NetSender, 36 | StrId_NetSenderUnavailable, 37 | StrId_NetSenderInvalidIp, 38 | StrId_NetSenderActive, 39 | 40 | StrId_Max, 41 | } StrId; 42 | 43 | extern const char* const g_strings[StrId_Max][16]; 44 | -------------------------------------------------------------------------------- /source/shaders/drawing.pica: -------------------------------------------------------------------------------- 1 | ; Example PICA200 vertex shader 2 | 3 | ; Uniforms 4 | .fvec projection[4] 5 | 6 | ; Constants 7 | .constf myconst(0.0, 1.0, -1.0, 0.1) 8 | .constf myconst2(0.3, 0.0, 0.0, 0.0) 9 | .alias zeros myconst.xxxx ; Vector full of zeros 10 | .alias ones myconst.yyyy ; Vector full of ones 11 | 12 | ; Outputs 13 | .out outpos position 14 | .out outclr color 15 | .out outtc0 texcoord0 16 | 17 | ; Inputs (defined as aliases for convenience) 18 | .alias inpos v0 19 | .alias intex v1 20 | 21 | .entry draw_main 22 | .proc draw_main 23 | ; Force the w component of inpos to be 1.0 24 | mov r0.xyz, inpos 25 | mov r0.w, ones 26 | 27 | ; outpos = projectionMatrix * inpos 28 | dp4 outpos.x, projection[0], r0 29 | dp4 outpos.y, projection[1], r0 30 | dp4 outpos.z, projection[2], r0 31 | dp4 outpos.w, projection[3], r0 32 | 33 | ; outclr = white 34 | mov outclr, ones 35 | 36 | ; outtc0 = intex 37 | mov outtc0, intex 38 | 39 | ; We're finished 40 | end 41 | .end 42 | -------------------------------------------------------------------------------- /source/shaders/wave.pica: -------------------------------------------------------------------------------- 1 | ; Example PICA200 vertex shader 2 | 3 | ; Uniforms 4 | .fvec projection[4] 5 | .fvec gradient[2] 6 | 7 | ; Constants 8 | .constf myconst(0.0, 1.0, -1.0, 0.1) 9 | .constf myconst2(0.3, 0.0, 0.0, 0.0) 10 | .alias zeros myconst.xxxx ; Vector full of zeros 11 | .alias ones myconst.yyyy ; Vector full of ones 12 | 13 | ; Outputs 14 | .out outpos position 15 | .out outclr color 16 | 17 | ; Inputs (defined as aliases for convenience) 18 | .alias inpos v0 19 | .alias inprm v1 20 | 21 | .entry wave_main 22 | .proc wave_main 23 | ; Force the w component of inpos to be 1.0 24 | mov r0.xyz, inpos 25 | mov r0.w, ones 26 | 27 | ; outpos = projectionMatrix * inpos 28 | dp4 outpos.x, projection[0], r0 29 | dp4 outpos.y, projection[1], r0 30 | dp4 outpos.z, projection[2], r0 31 | dp4 outpos.w, projection[3], r0 32 | 33 | add r7, ones, -inprm.x 34 | mul r7, gradient[0], r7 35 | mad outclr, inprm.x, gradient[1], r7 36 | 37 | ; We're finished 38 | end 39 | .end 40 | -------------------------------------------------------------------------------- /source/ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | #define UI_STATE_STACK_DEPTH 4 5 | 6 | typedef enum 7 | { 8 | UI_STATE_NULL = 0, 9 | UI_STATE_MENU, 10 | UI_STATE_ERROR, 11 | 12 | UI_STATE_REBOOT, 13 | UI_STATE_TITLESELECT, 14 | UI_STATE_NETLOADER, 15 | UI_STATE_NETSENDER, 16 | 17 | UI_STATE_BACKGROUND, 18 | UI_STATE_MAX, 19 | } UIState; 20 | 21 | typedef struct 22 | { 23 | void (* update)(void); 24 | void (* drawTop)(float iod); 25 | void (* drawBot)(void); 26 | } uiStateInfo_s; 27 | 28 | extern touchPosition g_touchPos; 29 | extern circlePosition g_cstickPos; 30 | 31 | extern u8 g_systemModel; 32 | 33 | extern const uiStateInfo_s g_uiStateTable[UI_STATE_MAX]; 34 | #define g_uiStateBg (&g_uiStateTable[UI_STATE_BACKGROUND]) 35 | 36 | void uiInit(void); 37 | void uiEnterBusy(void); 38 | void uiEnterState(UIState newState); 39 | void uiExitState(void); 40 | void uiExitLoop(void); 41 | const uiStateInfo_s* uiGetStateInfo(void); 42 | bool uiIsBusy(void); 43 | 44 | bool uiUpdate(void); 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release CI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | name: Build boot.3dsx 10 | uses: ./.github/workflows/build.yaml 11 | publish_release: 12 | name: Publish release archive 13 | needs: build 14 | runs-on: ubuntu-latest 15 | env: 16 | VERSTRING: ${{ needs.build.outputs.VERSTRING }} 17 | steps: 18 | - name: Download cacert.pem 19 | run: mkdir -p config/ssl && curl -sSfL "https://curl.se/ca/cacert.pem" -o config/ssl/cacert.pem 20 | - name: Download build artifact from previous job 21 | uses: actions/download-artifact@v4 22 | with: 23 | name: 3ds-hbmenu-${{ env.VERSTRING }} 24 | - name: Bundle release archive 25 | run: zip "3ds-hbmenu-$VERSTRING.zip" -r config boot.3dsx 26 | - name: Publish release archive 27 | uses: softprops/action-gh-release@v1 28 | if: startsWith(github.ref, 'refs/tags/') 29 | with: 30 | files: "./3ds-hbmenu-${{ env.VERSTRING }}.zip" 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /source/launch.h: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | extern void (*__system_retAddr)(void); 4 | 5 | enum 6 | { 7 | LOADER_SHOW_REBOOT = BIT(0), 8 | LOADER_NEED_SCAN = BIT(1), 9 | }; 10 | 11 | typedef struct 12 | { 13 | // Mandatory fields 14 | const char* name; 15 | u32 flags; 16 | bool (* init)(void); 17 | void (* deinit)(void); 18 | void (* launchFile)(const char* path, argData_s* args, executableMetadata_s* em); 19 | 20 | // Optional fields 21 | void (* useTitle)(u64 tid, u8 mediatype); 22 | } loaderFuncs_s; 23 | 24 | void launchInit(void); 25 | void launchExit(void); 26 | const loaderFuncs_s* launchGetLoader(void); 27 | size_t launchAddArg(argData_s* ad, const char* arg); 28 | void launchAddArgsFromString(argData_s* ad, char* arg); 29 | void launchMenuEntry(menuEntry_s* me); 30 | Handle launchOpenFile(const char* path); 31 | bool launchHomeMenuEnabled(void); 32 | void launchHomeMenu(void); 33 | 34 | static inline bool loaderHasFlag(u32 flag) 35 | { 36 | return (launchGetLoader()->flags & flag) != 0; 37 | } 38 | 39 | static inline bool loaderCanUseTitles(void) 40 | { 41 | return launchGetLoader()->useTitle != NULL; 42 | } 43 | -------------------------------------------------------------------------------- /source/worker.c: -------------------------------------------------------------------------------- 1 | #include "worker.h" 2 | 3 | #define WORKER_STACK_SIZE (16*1024) 4 | 5 | static Thread s_workerThread; 6 | static Handle s_workerEvent; 7 | 8 | static volatile struct 9 | { 10 | ThreadFunc func; 11 | void* data; 12 | 13 | bool exit; 14 | } s_workerParam; 15 | 16 | static void workerThreadProc(void* unused) 17 | { 18 | for (;;) 19 | { 20 | svcWaitSynchronization(s_workerEvent, U64_MAX); 21 | svcClearEvent(s_workerEvent); 22 | 23 | if (s_workerParam.exit) 24 | break; 25 | 26 | s_workerParam.func(s_workerParam.data); 27 | } 28 | } 29 | 30 | void workerInit(void) 31 | { 32 | Result res; 33 | 34 | res = svcCreateEvent(&s_workerEvent, 0); 35 | if (R_FAILED(res)) svcBreak(USERBREAK_PANIC); 36 | 37 | s_workerThread = threadCreate(workerThreadProc, NULL, WORKER_STACK_SIZE, 0x30, -2, true); 38 | } 39 | 40 | void workerExit(void) 41 | { 42 | s_workerParam.exit = true; 43 | svcSignalEvent(s_workerEvent); 44 | threadJoin(s_workerThread, U64_MAX); 45 | } 46 | 47 | void workerSchedule(ThreadFunc func, void* data) 48 | { 49 | uiEnterBusy(); 50 | s_workerParam.func = func; 51 | s_workerParam.data = data; 52 | svcSignalEvent(s_workerEvent); 53 | } 54 | -------------------------------------------------------------------------------- /source/ui/reboot.c: -------------------------------------------------------------------------------- 1 | #include "reboot.h" 2 | 3 | static bool rebooting = false; 4 | 5 | void rebootUpdate(void) 6 | { 7 | if (rebooting) return; 8 | 9 | u32 down = hidKeysDown(); 10 | if ((down & KEY_A) || (down & KEY_X)) 11 | { 12 | rebooting = true; 13 | drawingSetFade(-1.0/60); 14 | 15 | if (!(down & KEY_X) && launchHomeMenuEnabled()) 16 | launchHomeMenu(); 17 | else 18 | APT_HardwareResetAsync(); 19 | return; 20 | } 21 | 22 | if (down & KEY_Y) 23 | { 24 | // Check a gamecard is inserted. 25 | if (titlesLoadSmdh(NULL, 2, 0)) 26 | { 27 | // Check we have access to ns:s. 28 | Result rc = nsInit(); 29 | if (R_SUCCEEDED(rc)) 30 | { 31 | // Reboot the console. 32 | rebooting = true; 33 | drawingSetFade(-1.0/60); 34 | NS_RebootToTitle(2, 0); 35 | nsExit(); 36 | return; 37 | } 38 | } 39 | } 40 | 41 | if (down & KEY_B) 42 | { 43 | uiExitState(); 44 | return; 45 | } 46 | } 47 | 48 | void rebootDrawBot(void) 49 | { 50 | drawingSetMode(DRAW_MODE_DRAWING); 51 | drawingSetZ(0.4f); 52 | 53 | drawingWithColor(0x80FFFFFF); 54 | drawingDrawQuad(0.0f, 60.0f, 320.0f, 120.0f); 55 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 56 | 57 | //textSetColor(0xFF767676); 58 | textSetColor(0xFF545454); 59 | textDraw(8.0f, 60.0f+8.0f, 0.6f, 0.6f, false, textGetString(launchHomeMenuEnabled() ? StrId_ReturnToHome : StrId_Reboot)); 60 | } 61 | -------------------------------------------------------------------------------- /source/drawing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | #include "images.h" 4 | 5 | typedef int ImageId; 6 | 7 | typedef struct 8 | { 9 | float width, height; 10 | } imageInfo_s; 11 | 12 | extern imageInfo_s* g_imageData; 13 | 14 | #define DRAWING_MAX_VERTICES 8192 15 | 16 | typedef enum 17 | { 18 | DRAW_MODE_INVALID = 0, 19 | DRAW_MODE_DRAWING, 20 | DRAW_MODE_WAVE, 21 | } DrawingMode; 22 | 23 | typedef struct 24 | { 25 | float position[3]; 26 | float texcoord[2]; 27 | } drawVertex_s; 28 | 29 | void drawingInit(void); 30 | void drawingExit(void); 31 | void drawingFrame(void); 32 | void drawingSetFade(float fade); 33 | 34 | static inline u32 drawingGetFrames(void) 35 | { 36 | extern u32 g_drawFrames; 37 | return g_drawFrames; 38 | } 39 | 40 | void drawingEnableDepth(bool enable); 41 | void drawingSetMode(DrawingMode mode); 42 | void drawingSetZ(float z); 43 | void drawingSetTex(C3D_Tex* tex); 44 | void drawingSetGradient(unsigned which, float r, float g, float b, float a); 45 | 46 | void drawingWithVertexColor(void); 47 | void drawingWithColor(u32 color); 48 | void drawingWithTex(C3D_Tex* tex, u32 color); 49 | 50 | void drawingAddVertex(float vx, float vy, float tx, float ty); 51 | void drawingSubmitPrim(GPU_Primitive_t prim, int vertices); 52 | 53 | void drawingDrawQuad(float x, float y, float w, float h); 54 | void drawingDrawImage(ImageId id, u32 color, float x, float y); 55 | void drawingDrawWave(float* points, u32 num_points, float x, float width, float dy_top, float dy_bot); 56 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: # Note: will run on tags push too 5 | pull_request: 6 | workflow_dispatch: # manual run 7 | workflow_call: 8 | outputs: 9 | VERSTRING: 10 | description: Version string 11 | value: ${{ jobs.build.outputs.VERSTRING }} 12 | 13 | jobs: 14 | build: 15 | name: Build boot.3dsx 16 | runs-on: ubuntu-latest 17 | outputs: 18 | VERSTRING: ${{ steps.verstring.outputs.VERSTRING }} 19 | container: 20 | image: devkitpro/devkitarm 21 | options: --user 1001 # runner user, for git safedir 22 | steps: 23 | - name: Checkout repo 24 | uses: actions/checkout@v4 25 | with: 26 | submodules: recursive # we currently don't use submodules, but just in case... 27 | fetch-depth: 0 # deep clone (for git describe) 28 | fetch-tags: true 29 | - name: Set version string 30 | id: verstring 31 | run: | 32 | VERSTRING=$(git describe --tags --match "v[0-9]*" --abbrev=7 | sed 's/-[0-9]*-g/-/') 33 | echo "VERSTRING=$VERSTRING" | tee -a $GITHUB_OUTPUT 34 | - name: Build 35 | run: make -j$(nproc --all) 36 | - name: Publish boot.3dsx (only on manual run or release) 37 | if: github.event_name == 'workflow_dispatch' || github.event_name == 'release' 38 | uses: actions/upload-artifact@v4 39 | with: 40 | # This produces a zip with boot.3dsx inside for security reasons 41 | # For publish_release this is presented as boot.3dsx however 42 | name: 3ds-hbmenu-${{ steps.verstring.outputs.VERSTRING }} 43 | path: boot.3dsx 44 | -------------------------------------------------------------------------------- /source/ui.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | // Global variables 4 | touchPosition g_touchPos; 5 | circlePosition g_cstickPos; 6 | u8 g_systemModel; 7 | 8 | // Static variables 9 | static UIState s_stateStack[UI_STATE_STACK_DEPTH]; 10 | static int s_stateStackPos = 1; 11 | static bool s_stateBusy; 12 | static bool s_shouldExit; 13 | 14 | void uiInit(void) 15 | { 16 | Result res = cfguInit(); 17 | if (R_SUCCEEDED(res)) 18 | { 19 | res = CFGU_GetSystemModel(&g_systemModel); 20 | cfguExit(); 21 | } 22 | if (R_FAILED(res)) g_systemModel = 0; 23 | } 24 | 25 | void uiEnterBusy(void) 26 | { 27 | s_stateBusy = true; 28 | } 29 | 30 | void uiEnterState(UIState newState) 31 | { 32 | s_stateBusy = false; 33 | if (s_stateStack[s_stateStackPos-1] == newState) return; 34 | if (s_stateStackPos>=UI_STATE_STACK_DEPTH) return; 35 | s_stateStack[s_stateStackPos++] = newState; 36 | } 37 | 38 | void uiExitState(void) 39 | { 40 | if (s_stateStackPos<=1) return; 41 | s_stateStackPos--; 42 | } 43 | 44 | void uiExitLoop(void) 45 | { 46 | s_shouldExit = true; 47 | } 48 | 49 | const uiStateInfo_s* uiGetStateInfo(void) 50 | { 51 | UIState cur = s_stateStack[s_stateStackPos-1]; 52 | return &g_uiStateTable[cur]; 53 | } 54 | 55 | bool uiIsBusy(void) 56 | { 57 | return s_stateBusy; 58 | } 59 | 60 | static int iabs(int x) 61 | { 62 | return x < 0 ? (-x) : x; 63 | } 64 | 65 | bool uiUpdate(void) 66 | { 67 | // Read input state 68 | hidScanInput(); 69 | hidTouchRead(&g_touchPos); 70 | hidCstickRead(&g_cstickPos); 71 | g_cstickPos.dx = iabs(g_cstickPos.dx)<5 ? 0 : g_cstickPos.dx; 72 | g_cstickPos.dy = iabs(g_cstickPos.dy)<5 ? 0 : g_cstickPos.dy; 73 | 74 | const uiStateInfo_s* ui; 75 | 76 | // Update background 77 | ui = g_uiStateBg; 78 | if (ui->update) ui->update(); 79 | 80 | if (!s_stateBusy) 81 | { 82 | // Update current state 83 | ui = uiGetStateInfo(); 84 | if (ui->update) ui->update(); 85 | } 86 | 87 | return !s_shouldExit; 88 | } 89 | -------------------------------------------------------------------------------- /source/parsing/shortcut.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "shortcut.h" 4 | } 5 | #include 6 | 7 | using namespace tinyxml2; 8 | 9 | static void loadXmlString(char** out, XMLElement* in, const char* key) 10 | { 11 | if (!out || !in || !key)return; 12 | 13 | XMLElement* node = in->FirstChildElement(key); 14 | if (node) 15 | { 16 | const char* str = node->GetText(); 17 | if (str) 18 | *out = strdup(str); 19 | } 20 | } 21 | 22 | // TODO : error checking 23 | static Result shortcutLoad(shortcut_s* s, const char* path) 24 | { 25 | XMLDocument doc; 26 | if(doc.LoadFile(path))return -2; 27 | 28 | XMLElement* shortcut = doc.FirstChildElement("shortcut"); 29 | if(shortcut) 30 | { 31 | XMLElement* executable = shortcut->FirstChildElement("executable"); 32 | if(executable) 33 | { 34 | const char* str = executable->GetText(); 35 | if(str) 36 | s->executable = strdup(str); 37 | } 38 | if(!s->executable) return -3; 39 | 40 | XMLElement* descriptor = shortcut->FirstChildElement("descriptor"); 41 | const char* descriptor_path = path; 42 | if(descriptor) descriptor_path = descriptor->GetText(); 43 | if(descriptor_path) 44 | s->descriptor = strdup(descriptor_path); 45 | 46 | loadXmlString(&s->icon, shortcut, "icon"); 47 | loadXmlString(&s->arg, shortcut, "arg"); 48 | loadXmlString(&s->name, shortcut, "name"); 49 | loadXmlString(&s->description, shortcut, "description"); 50 | loadXmlString(&s->author, shortcut, "author"); 51 | }else return -4; 52 | 53 | return 0; 54 | } 55 | 56 | Result shortcutCreate(shortcut_s* s, const char* path) 57 | { 58 | memset(s, 0, sizeof(*s)); 59 | Result res = shortcutLoad(s, path); 60 | if (R_FAILED(res)) 61 | shortcutFree(s); 62 | return res; 63 | } 64 | 65 | void shortcutFree(shortcut_s* s) 66 | { 67 | if (s->executable) free(s->executable); 68 | if (s->descriptor) free(s->descriptor); 69 | if (s->icon) free(s->icon); 70 | if (s->arg) free(s->arg); 71 | if (s->name) free(s->name); 72 | if (s->description) free(s->description); 73 | if (s->author) free(s->author); 74 | } 75 | -------------------------------------------------------------------------------- /source/network.c: -------------------------------------------------------------------------------- 1 | #include "network.h" 2 | 3 | #include 4 | 5 | static void* SOC_buffer; 6 | 7 | bool networkInit(void) 8 | { 9 | if (!SOC_buffer) 10 | { 11 | SOC_buffer = memalign(0x1000, 0x100000); 12 | if (!SOC_buffer) 13 | return false; 14 | } 15 | 16 | Result ret = socInit(SOC_buffer, 0x100000); 17 | if (R_FAILED(ret)) 18 | { 19 | socExit(); 20 | return false; 21 | } 22 | return true; 23 | } 24 | 25 | void networkDeactivate(void) 26 | { 27 | socExit(); 28 | if (SOC_buffer) 29 | { 30 | free(SOC_buffer); 31 | SOC_buffer = NULL; 32 | } 33 | } 34 | 35 | // xError in their own files call xDeactivate beforehand 36 | void networkError(void (* update)(void), StrId titleStrId, const char* func, int err) 37 | { 38 | if (uiGetStateInfo()->update == update) 39 | uiExitState(); 40 | errorScreen(textGetString(titleStrId), textGetString(StrId_NetworkError), func, err); 41 | } 42 | 43 | void networkDrawBot(StrId titleStrId, const char* other, bool transferring, size_t filelen, size_t filetotal) 44 | { 45 | drawingSetMode(DRAW_MODE_DRAWING); 46 | drawingSetZ(0.4f); 47 | 48 | drawingWithColor(0x80FFFFFF); 49 | drawingDrawQuad(0.0f, 60.0f, 320.0f, 120.0f); 50 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 51 | 52 | textSetColor(0xFF545454); 53 | textDrawInBox(textGetString(titleStrId), 0, 0.75f, 0.75f, 60.0f+25.0f, 8.0f, 320-8.0f); 54 | 55 | char buf[256]; 56 | const char* text = other; 57 | if (text == NULL) 58 | { 59 | text = buf; 60 | u32 ip = gethostid(); 61 | 62 | if (ip == 0) 63 | snprintf(buf, sizeof(buf), textGetString(StrId_NetworkOffline)); 64 | else 65 | snprintf(buf, sizeof(buf), textGetString(StrId_NetworkTransferring), filetotal/1024, filelen/1024); 66 | } 67 | 68 | textDraw(8.0f, 60.0f+25.0f+8.0f, 0.5f, 0.5f, false, text); 69 | 70 | if (transferring) 71 | { 72 | float progress = (float)filetotal / filelen; 73 | float width = progress*320; 74 | 75 | drawingWithColor(0xC000E000); 76 | drawingDrawQuad(0.0f, 60.0f+120.0f-16.0f, width, 16.0f); 77 | drawingWithColor(0xC0C0C0C0); 78 | drawingDrawQuad(width, 60.0f+120.0f-16.0f, 320.0f-width, 16.0f); 79 | 80 | snprintf(buf, sizeof(buf), "%.02f%%", progress*100); 81 | textSetColor(0xFF000000); 82 | textDrawInBox(buf, 0, 0.5f, 0.5f, 60.0f+120.0f-3.0f, 0.0f, 320.0f); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /source/main.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "ui/menu.h" 3 | #include "ui/error.h" 4 | #include "ui/reboot.h" 5 | #include "ui/titleselect.h" 6 | #include "ui/netloader.h" 7 | #include "ui/netsender.h" 8 | #include "ui/background.h" 9 | 10 | bool DEBUG_MODE = false; 11 | 12 | const uiStateInfo_s g_uiStateTable[UI_STATE_MAX] = 13 | { 14 | [UI_STATE_MENU] = { 15 | .update = menuUpdate, 16 | .drawTop = menuDrawTop, 17 | .drawBot = menuDrawBot, 18 | }, 19 | [UI_STATE_ERROR] = { 20 | .update = errorUpdate, 21 | .drawBot = errorDrawBot, 22 | }, 23 | [UI_STATE_REBOOT] = { 24 | .update = rebootUpdate, 25 | .drawBot = rebootDrawBot, 26 | }, 27 | [UI_STATE_TITLESELECT] = { 28 | .update = titleSelectUpdate, 29 | .drawBot = titleSelectDrawBot, 30 | }, 31 | [UI_STATE_NETLOADER] = { 32 | .update = netloaderUpdate, 33 | .drawBot = netloaderDrawBot, 34 | }, 35 | [UI_STATE_NETSENDER] = { 36 | .update = netsenderUpdate, 37 | .drawBot = netsenderDrawBot, 38 | }, 39 | [UI_STATE_BACKGROUND] = { 40 | .update = backgroundUpdate, 41 | .drawTop = backgroundDrawTop, 42 | .drawBot = backgroundDrawBot, 43 | }, 44 | }; 45 | 46 | bool isDebugMode() 47 | { 48 | return DEBUG_MODE; 49 | } 50 | 51 | static void startup(void *unused) 52 | { 53 | menuLoadFileAssoc(); 54 | menuScan("sdmc:/3ds"); 55 | uiEnterState(UI_STATE_MENU); 56 | } 57 | 58 | const char *__romfs_path = "sdmc:/boot.3dsx"; 59 | 60 | int main() 61 | { 62 | Result rc; 63 | FILE *file = fopen("sdmc:/debug_hbl.txt", "r"); 64 | if (file) 65 | { 66 | DEBUG_MODE = true; 67 | fclose(file); 68 | } 69 | osSetSpeedupEnable(true); 70 | rc = romfsInit(); 71 | if (R_FAILED(rc)) 72 | svcBreak(USERBREAK_PANIC); 73 | 74 | menuStartupPath(); 75 | hidSetRepeatParameters(20, 10); 76 | ptmuInit(); 77 | uiInit(); 78 | drawingInit(); 79 | textInit(); 80 | launchInit(); 81 | workerInit(); 82 | 83 | backgroundInit(); 84 | 85 | workerSchedule(startup, NULL); 86 | 87 | // Main loop 88 | while (aptMainLoop()) 89 | { 90 | if (!uiUpdate()) 91 | break; 92 | drawingFrame(); 93 | } 94 | 95 | netloaderExit(); 96 | netsenderExit(); 97 | launchExit(); 98 | workerExit(); 99 | textExit(); 100 | drawingExit(); 101 | romfsExit(); 102 | ptmuExit(); 103 | return 0; 104 | } 105 | -------------------------------------------------------------------------------- /source/lzss.s: -------------------------------------------------------------------------------- 1 | @ Code by mtheall 2 | 3 | .arch armv5te 4 | 5 | .arm 6 | .align 2 7 | .global lzssDecompress 8 | .hidden lzssDecompress 9 | .type lzssDecompress, %function 10 | lzssDecompress: 11 | push {r4-r6} 12 | ldr r3, =(0x01010101) 13 | 14 | .Lloop: 15 | cmp r2, #0 @ if(size <= 0) 16 | pople {r4-r6} @ pop stack 17 | bxle lr @ return 18 | 19 | rors r3, r3, #1 @ r3 = (r3<<31) | (r3>>1) 20 | ldrcsb r12, [r0], #1 @ if(r3 & (1<<31)) flags = *in++ 21 | tst r12, r3 @ if(flags & r3 == 0) 22 | beq .Lcopy_uncompressed @ goto copy_uncompressed 23 | 24 | ldrb r4, [r0], #1 @ r4 = *in++ 25 | lsr r6, r4, #4 @ len = r4>>4 26 | add r6, r6, #3 @ len += 3 27 | and r5, r4, #0x0F @ disp = r4 & 0x0F note: disp is in r5 28 | ldrb r4, [r0], #1 @ r4 = *in++ 29 | orr r4, r4, r5, lsl #8 @ disp = r4 | (disp<<8) note: disp changes to r4 30 | add r4, r4, #1 @ disp++ 31 | sub r2, r2, r6 @ size -= len 32 | tst r4, #1 @ if(r4 & 1 == 0) // aligned displacement 33 | beq .Lcopy_aligned @ goto copy_aligned 34 | 35 | .Lcopy_compressed: 36 | ldrb r5, [r1, -r4] @ r5 = *(out - disp) 37 | subs r6, r6, #1 @ --len 38 | strb r5, [r1], #1 @ *out++ = r5 39 | bne .Lcopy_compressed @ goto copy_compressed 40 | b .Lloop @ if(len == 0) goto loop 41 | 42 | .Lcopy_aligned: 43 | tst r1, #0x1 @ if(r1 & 0x1 == 0) // src/dst is aligned 44 | beq .Lcopy_hwords @ goto copy_hwords 45 | ldrb r5, [r1, -r4] @ r5 = *(out - disp) // read a byte to align 46 | sub r6, r6, #1 @ len-- 47 | strb r5, [r1], #1 @ *out++ = r5 48 | .Lcopy_hwords: 49 | subs r6, r6, #2 @ len -= 2 50 | ldrgeh r5, [r1, -r4] @ if(len >= 0) r5 = *(short*)(out - disp) 51 | strgeh r5, [r1], #2 @ if(len >= 0) *(short*)out++ = r5 52 | bgt .Lcopy_hwords @ if(len > 0) goto copy_hwords 53 | beq .Lloop @ if(len == 0) goto loop 54 | @ extra byte 55 | ldrb r5, [r1, -r4] @ r5 = *(out - disp) 56 | strb r5, [r1], #1 @ *out++ = r5 57 | b .Lloop @ goto loop 58 | 59 | .Lcopy_uncompressed: 60 | ldrb r4, [r0], #1 @ r4 = *in++ 61 | sub r2, r2, #1 @ size-- 62 | strb r4, [r1], #1 @ *out++ = r4 63 | b .Lloop @ goto loop 64 | -------------------------------------------------------------------------------- /source/parsing/memmap.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "memmap.h" 4 | } 5 | #include 6 | 7 | using namespace tinyxml2; 8 | 9 | static u32 getXmlUnsignedInt(XMLElement* el) 10 | { 11 | if(!el) return 0; 12 | 13 | const char* str = el->GetText(); 14 | if(!str) return 0; 15 | 16 | return strtoul(str, NULL, 0); 17 | } 18 | 19 | static u32 getXmlInt(XMLElement* el) 20 | { 21 | if(!el) return 0; 22 | 23 | const char* str = el->GetText(); 24 | if(!str) return 0; 25 | 26 | return strtol(str, NULL, 0); 27 | } 28 | 29 | // TODO : error checking 30 | memmap_t* memmapLoad(const char* path) 31 | { 32 | if(!path)return NULL; 33 | 34 | XMLDocument doc; 35 | if(doc.LoadFile(path))return NULL; 36 | 37 | memmap_header_t header; 38 | XMLElement* header_element = doc.FirstChildElement("header"); 39 | if(header_element) 40 | { 41 | header.num = getXmlUnsignedInt(header_element->FirstChildElement("num")); 42 | header.text_end = getXmlUnsignedInt(header_element->FirstChildElement("text_end")); 43 | header.data_address = getXmlUnsignedInt(header_element->FirstChildElement("data_address")); 44 | header.data_size = getXmlUnsignedInt(header_element->FirstChildElement("data_size")); 45 | header.processLinearOffset = getXmlUnsignedInt(header_element->FirstChildElement("processLinearOffset")); 46 | header.processHookAddress = getXmlUnsignedInt(header_element->FirstChildElement("processHookAddress")); 47 | header.processAppCodeAddress = getXmlUnsignedInt(header_element->FirstChildElement("processAppCodeAddress")); 48 | header.processHookTidLow = getXmlUnsignedInt(header_element->FirstChildElement("processHookTidLow")); 49 | header.processHookTidHigh = getXmlUnsignedInt(header_element->FirstChildElement("processHookTidHigh")); 50 | header.mediatype = getXmlUnsignedInt(header_element->FirstChildElement("mediatype")); 51 | }else return NULL; 52 | 53 | memmap_t* ret = (memmap_t*) malloc(sizeof(memmap_header_t) + header.num * sizeof(memmap_entry_t)); 54 | if(!ret) return NULL; 55 | 56 | ret->header = header; 57 | 58 | XMLElement* map = doc.FirstChildElement("map"); 59 | if(map) 60 | { 61 | u32 i = 0; 62 | 63 | for (tinyxml2::XMLElement* child = map->FirstChildElement(); child != NULL && i < header.num; child = child->NextSiblingElement()) 64 | { 65 | if(!strcmp(child->Name(), "entry")) 66 | { 67 | ret->map[i].src = getXmlInt(child->FirstChildElement("src")); 68 | ret->map[i].dst = getXmlInt(child->FirstChildElement("dst")); 69 | ret->map[i].size = getXmlInt(child->FirstChildElement("size")); 70 | 71 | i++; 72 | } 73 | } 74 | 75 | if(i == header.num) return ret; 76 | } 77 | 78 | free(ret); 79 | 80 | return NULL; 81 | } 82 | -------------------------------------------------------------------------------- /source/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | #include "parsing/smdh.h" 4 | #include "parsing/descriptor.h" 5 | 6 | #define ENTRY_NAMELENGTH (0x40*3) 7 | #define ENTRY_DESCLENGTH (0x80*3) 8 | #define ENTRY_AUTHORLENGTH (0x40*3) 9 | #define ENTRY_ARGBUFSIZE 0x400 10 | 11 | typedef enum 12 | { 13 | ENTRY_TYPE_FILE, 14 | ENTRY_TYPE_FOLDER, 15 | ENTRY_TYPE_FILEASSOC, 16 | ENTRY_TYPE_FILE_OTHER 17 | } MenuEntryType; 18 | 19 | typedef struct menuEntry_s_tag menuEntry_s; 20 | typedef struct menu_s_tag menu_s; 21 | 22 | typedef struct 23 | { 24 | char* dst; 25 | u32 buf[ENTRY_ARGBUFSIZE/sizeof(u32)]; 26 | } argData_s; 27 | 28 | struct menuEntry_s_tag 29 | { 30 | menu_s* menu; 31 | menuEntry_s* next; 32 | MenuEntryType type; 33 | 34 | char path[PATH_MAX+1]; 35 | char starpath[PATH_MAX+1]; 36 | argData_s args; 37 | 38 | char name[ENTRY_NAMELENGTH+1]; 39 | char description[ENTRY_DESCLENGTH+1]; 40 | char author[ENTRY_AUTHORLENGTH+1]; 41 | 42 | bool fileAssocType; //< 0 file_extension, 1 = filename 43 | char fileAssocStr[PATH_MAX + 1]; //< file_extension/filename 44 | 45 | smdh_s smdh; 46 | descriptor_s descriptor; 47 | 48 | C3D_Tex* icon; 49 | C3D_Tex texture; 50 | 51 | u64 titleId; 52 | u8 titleMediatype; 53 | bool titleSelected; 54 | 55 | bool isStarred; 56 | }; 57 | 58 | menuEntry_s* menuCreateEntry(MenuEntryType type); 59 | void menuDeleteEntry(menuEntry_s* me); 60 | 61 | void menuEntryInit(menuEntry_s* me, MenuEntryType type); 62 | void menuEntryFree(menuEntry_s* me); 63 | bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut); 64 | void menuEntryParseSmdh(menuEntry_s* me); 65 | void menuEntryFileAssocLoad(const char* filepath); 66 | bool menuEntryLoadExternalIcon(menuEntry_s* me, const char* filepath); 67 | bool menuEntryImportIcon(menuEntry_s* me, C3D_Tex* texture); 68 | 69 | struct menu_s_tag 70 | { 71 | menuEntry_s *firstEntry, *lastEntry; 72 | int nEntries; 73 | int curEntry; 74 | 75 | char dirname[PATH_MAX+1]; 76 | 77 | float scrollTarget; 78 | float scrollLocation; 79 | float scrollVelocity; 80 | 81 | touchPosition previousTouch, firstTouch; 82 | int touchTimer; 83 | bool perturbed; 84 | }; 85 | 86 | menu_s* menuGetCurrent(void); 87 | int menuScan(const char* target); 88 | char *menuGetRootBasePath(void); 89 | void menuStartupPath(void); 90 | void menuToggleStar(menuEntry_s* me); 91 | 92 | menu_s* menuFileAssocGetCurrent(void); 93 | void menuFileAssocClear(void); 94 | int menuFileAssocScan(const char* target); 95 | void menuFileAssocAddEntry(menuEntry_s* me); 96 | 97 | static inline char* getExtension(const char* str) 98 | { 99 | const char* p; 100 | for (p = str+strlen(str); p >= str && *p != '.'; p--); 101 | return (char*)p; 102 | } 103 | 104 | static inline char* getSlash(const char* str) 105 | { 106 | const char* p; 107 | for (p = str+strlen(str); p >= str && *p != '/'; p--); 108 | return (char*)p; 109 | } 110 | -------------------------------------------------------------------------------- /source/loaders/rosalina.c: -------------------------------------------------------------------------------- 1 | #include "../common.h" 2 | #include <3ds/services/ptmsysm.h> 3 | #include <3ds/synchronization.h> 4 | 5 | static Handle hbldrHandle; 6 | static Handle rosalinaHandle; 7 | static int rosalinaRefcount; 8 | 9 | static Result ROSALINA_EnableDebugger(bool enabled) 10 | { 11 | u32 *cmdbuf = getThreadCommandBuffer(); 12 | cmdbuf[0] = IPC_MakeHeader(1, 1, 0); 13 | cmdbuf[1] = enabled; 14 | Result rc = svcSendSyncRequest(rosalinaHandle); 15 | rc = cmdbuf[1]; 16 | return rc; 17 | } 18 | 19 | static bool init(void) 20 | { 21 | bool hbldr = R_SUCCEEDED(svcConnectToPort(&hbldrHandle, "hb:ldr")); 22 | if (!isDebugMode()) 23 | { 24 | return hbldr; 25 | } 26 | 27 | bool rosalina = false; 28 | Result res; 29 | if (AtomicPostIncrement(&rosalinaRefcount)) 30 | res = 0; 31 | else 32 | { 33 | res = srvGetServiceHandle(&rosalinaHandle, "rosalina:dbg"); 34 | if (R_FAILED(res)) 35 | AtomicDecrement(&rosalinaRefcount); 36 | Result r = srvGetServiceHandle(&rosalinaHandle, "rosalina:dbg"); 37 | rosalina = R_SUCCEEDED(r); 38 | if (!rosalina) 39 | { 40 | errorScreen("Failed getting handle", "The attempt to connect to the rosalina debugger control service failed."); 41 | return false; 42 | } 43 | ROSALINA_EnableDebugger(false); 44 | } 45 | 46 | return hbldr && rosalina; 47 | } 48 | 49 | static Result ROSALINA_DebugNextApp() 50 | { 51 | u32 *cmdbuf = getThreadCommandBuffer(); 52 | cmdbuf[0] = IPC_MakeHeader(2, 0, 0); 53 | Result rc = svcSendSyncRequest(rosalinaHandle); 54 | rc = cmdbuf[1]; 55 | return rc; 56 | } 57 | 58 | static Result HBLDR_SetTarget(const char *path) 59 | { 60 | u32 pathLen = strlen(path) + 1; 61 | u32 *cmdbuf = getThreadCommandBuffer(); 62 | 63 | cmdbuf[0] = IPC_MakeHeader(2, 0, 2); // 0x20002 64 | cmdbuf[1] = IPC_Desc_StaticBuffer(pathLen, 0); 65 | cmdbuf[2] = (u32)path; 66 | Result rc = svcSendSyncRequest(hbldrHandle); 67 | if (R_SUCCEEDED(rc)) 68 | rc = cmdbuf[1]; 69 | return rc; 70 | } 71 | 72 | static Result HBLDR_SetArgv(const void *buffer, u32 size) 73 | { 74 | u32 *cmdbuf = getThreadCommandBuffer(); 75 | 76 | cmdbuf[0] = IPC_MakeHeader(3, 0, 2); // 0x30002 77 | cmdbuf[1] = IPC_Desc_StaticBuffer(size, 1); 78 | cmdbuf[2] = (u32)buffer; 79 | 80 | Result rc = svcSendSyncRequest(hbldrHandle); 81 | if (R_SUCCEEDED(rc)) 82 | rc = cmdbuf[1]; 83 | return rc; 84 | } 85 | 86 | static void deinit(void) 87 | { 88 | svcCloseHandle(hbldrHandle); 89 | if (AtomicDecrement(&rosalinaHandle)) 90 | return; 91 | svcCloseHandle(rosalinaHandle); 92 | } 93 | 94 | static void launchFile(const char *path, argData_s *args, executableMetadata_s *em) 95 | { 96 | 97 | if (strncmp(path, "sdmc:/", 6) == 0) 98 | path += 5; 99 | 100 | u32 *command_buffer = getThreadCommandBuffer(); 101 | command_buffer[0] = IPC_MakeHeader(0x101, 1, 0); 102 | command_buffer[1] = true; 103 | if (isDebugMode()) 104 | { 105 | Result r = ROSALINA_EnableDebugger(true); 106 | if (R_FAILED(r)) 107 | { 108 | errorScreen("Failed to enable debugger", "Rosalina failed to enable the debugger with error 0x%08lx", r); 109 | return; 110 | } 111 | r = ROSALINA_DebugNextApp(); 112 | if (R_FAILED(r)) 113 | { 114 | errorScreen("Failed to debug next app", "Rosalina failed to start debugging session for the next app. Error code : 0x%08lx", r); 115 | return; 116 | } 117 | } 118 | HBLDR_SetTarget(path); 119 | HBLDR_SetArgv(args->buf, sizeof(args->buf)); 120 | uiExitLoop(); 121 | } 122 | 123 | const loaderFuncs_s loader_Rosalina = 124 | { 125 | .name = "Rosalina", 126 | .init = init, 127 | .deinit = deinit, 128 | .launchFile = launchFile, 129 | }; 130 | -------------------------------------------------------------------------------- /source/parsing/descriptor.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "descriptor.h" 4 | } 5 | #include 6 | 7 | using namespace tinyxml2; 8 | 9 | void descriptorInit(descriptor_s* d) 10 | { 11 | if(!d)return; 12 | 13 | d->targetTitles = NULL; 14 | d->numTargetTitles = 0; 15 | 16 | d->requestedServices = NULL; 17 | d->numRequestedServices = 0; 18 | 19 | d->selectTargetProcess = false; 20 | d->autodetectServices = true; 21 | 22 | scannerInit(&d->executableMetadata); 23 | } 24 | 25 | // TODO : error checking 26 | void descriptorLoad(descriptor_s* d, const char* path) 27 | { 28 | if(!d || !path)return; 29 | 30 | XMLDocument doc; 31 | if(doc.LoadFile(path))return; 32 | 33 | XMLElement* targets = doc.FirstChildElement("targets"); 34 | if(targets) 35 | { 36 | // grab selectable target flag (default to false) 37 | { 38 | if(targets->QueryBoolAttribute("selectable", &d->selectTargetProcess)) d->selectTargetProcess = false; 39 | } 40 | 41 | // grab preferred target titles 42 | { 43 | d->numTargetTitles = 0; 44 | for (tinyxml2::XMLElement* child = targets->FirstChildElement(); child != NULL; child = child->NextSiblingElement()) 45 | { 46 | if(!strcmp(child->Name(), "title")) 47 | { 48 | d->numTargetTitles++; 49 | } 50 | } 51 | 52 | d->targetTitles = (targetTitle_s*)malloc(sizeof(targetTitle_s) * d->numTargetTitles); 53 | d->numTargetTitles = 0; 54 | 55 | for (tinyxml2::XMLElement* child = targets->FirstChildElement(); child != NULL; child = child->NextSiblingElement()) 56 | { 57 | if(!strcmp(child->Name(), "title")) 58 | { 59 | // SD is default mediatype 60 | int mediatype; 61 | if(child->QueryIntAttribute("mediatype", &mediatype))mediatype = 1; 62 | 63 | d->targetTitles[d->numTargetTitles].tid = strtoull(child->GetText(), NULL, 16); 64 | d->targetTitles[d->numTargetTitles].mediatype = mediatype; 65 | 66 | d->numTargetTitles++; 67 | } 68 | } 69 | } 70 | } 71 | 72 | XMLElement* services = doc.FirstChildElement("services"); 73 | if(services) 74 | { 75 | // grab "autodetect services" flag (default to true) 76 | { 77 | if(services->QueryBoolAttribute("autodetect", &d->autodetectServices)) d->autodetectServices = true; 78 | } 79 | 80 | // grab requested services 81 | { 82 | d->numRequestedServices = 0; 83 | for (tinyxml2::XMLElement* child = services->FirstChildElement(); child != NULL; child = child->NextSiblingElement()) 84 | { 85 | if(!strcmp(child->Name(), "request")) 86 | { 87 | d->numRequestedServices++; 88 | } 89 | } 90 | 91 | d->requestedServices = (serviceRequest_s*)malloc(sizeof(serviceRequest_s) * d->numRequestedServices); 92 | d->numRequestedServices = 0; 93 | 94 | for (tinyxml2::XMLElement* child = services->FirstChildElement(); child != NULL; child = child->NextSiblingElement()) 95 | { 96 | if(!strcmp(child->Name(), "request")) 97 | { 98 | serviceRequest_s* req = &d->requestedServices[d->numRequestedServices]; 99 | 100 | // 1 (highest) is default priority 101 | if(child->QueryIntAttribute("priority", &req->priority)) req->priority = 1; 102 | 103 | strncpy(req->name, child->GetText(), 8); 104 | req->name[8] = 0; 105 | 106 | d->numRequestedServices++; 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | void descriptorFree(descriptor_s* d) 114 | { 115 | if(!d)return; 116 | 117 | if(d->targetTitles) 118 | { 119 | free(d->targetTitles); 120 | d->targetTitles = NULL; 121 | } 122 | 123 | if(d->requestedServices) 124 | { 125 | free(d->requestedServices); 126 | d->requestedServices = NULL; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /source/ui/titleselect.c: -------------------------------------------------------------------------------- 1 | #include "titleselect.h" 2 | #include "menu.h" 3 | 4 | #define UPDATE_FREQUENCY 250 5 | 6 | static menuEntry_s* s_launchEntry; 7 | static menuEntry_s s_iconEntry; 8 | static int s_curTitle, s_move; 9 | static volatile bool s_iconReady; 10 | static u64 s_curTitleID; 11 | static u8 s_curMediatype; 12 | static u64 s_lastUpdate; 13 | 14 | static void loadTitleData(void* unused) 15 | { 16 | int count = titlesCount(), oldPos = s_curTitle; 17 | do 18 | { 19 | titlesGetEntry(&s_curTitleID, &s_curMediatype, s_curTitle); 20 | if (titlesLoadSmdh(&s_iconEntry.smdh, s_curMediatype, s_curTitleID)) 21 | { 22 | // Reading this title's SMDH succeeded - display it. 23 | s_iconReady = false; 24 | menuEntryParseSmdh(&s_iconEntry); 25 | s_iconReady = true; 26 | uiEnterState(UI_STATE_TITLESELECT); 27 | return; 28 | } 29 | s_curTitle += s_move; 30 | if (s_curTitle < 0) s_curTitle += count; 31 | if (s_curTitle >= count) s_curTitle -= count; 32 | } while (s_curTitle != oldPos); 33 | 34 | // If we got here we failed to find a title with a readable SMDH. 35 | s_curTitle = -1; 36 | uiExitState(); 37 | errorScreen(textGetString(StrId_TitleSelector), textGetString(StrId_ErrorReadingTitleMetadata), 38 | (u32)(s_curTitleID>>32), (u32)s_curTitleID, s_curMediatype); 39 | } 40 | 41 | void titleSelectInit(menuEntry_s* me) 42 | { 43 | s_launchEntry = me; 44 | titlesCheckUpdate(false, UI_STATE_NULL); 45 | s_lastUpdate = osGetTime(); 46 | if (!titlesCount()) 47 | { 48 | errorScreen(textGetString(StrId_TitleSelector), textGetString(StrId_NoTitlesFound)); 49 | return; 50 | } 51 | s_curTitle = -1; 52 | menuEntryInit(&s_iconEntry, ENTRY_TYPE_FILE); 53 | uiEnterState(UI_STATE_TITLESELECT); 54 | } 55 | 56 | static void launchEntry(void* unused) 57 | { 58 | launchMenuEntry(s_launchEntry); 59 | } 60 | 61 | void titleSelectUpdate(void) 62 | { 63 | u32 kDown = hidKeysDown(); 64 | 65 | if (s_curTitle < 0) 66 | { 67 | s_curTitle = 0; 68 | s_move = 1; 69 | workerSchedule(loadTitleData, NULL); 70 | return; 71 | } 72 | 73 | u64 curTime = osGetTime(); 74 | if ((curTime-s_lastUpdate) > UPDATE_FREQUENCY) 75 | { 76 | s_lastUpdate = curTime; 77 | if (titlesCheckUpdate(true, UI_STATE_TITLESELECT)) 78 | { 79 | s_curTitle = -1; 80 | return; 81 | } 82 | } 83 | 84 | if ((kDown & KEY_A) || (kDown & KEY_B)) 85 | { 86 | // Free entry. 87 | menuEntryFree(&s_iconEntry); 88 | uiExitState(); 89 | if (kDown & KEY_A) 90 | { 91 | s_launchEntry->titleSelected = true; 92 | s_launchEntry->titleId = s_curTitleID; 93 | s_launchEntry->titleMediatype = s_curMediatype; 94 | workerSchedule(launchEntry, NULL); 95 | } 96 | return; 97 | } 98 | 99 | s_move = 0; 100 | if (kDown & KEY_RIGHT) s_move++; 101 | if (kDown & KEY_LEFT) s_move--; 102 | 103 | int tgt = s_curTitle+s_move; 104 | int cnt = titlesCount(); 105 | if (tgt < 0) tgt += cnt; 106 | if (tgt >= cnt) tgt -= cnt; 107 | 108 | if (tgt != s_curTitle) 109 | { 110 | s_curTitle = tgt; 111 | workerSchedule(loadTitleData, NULL); 112 | return; 113 | } 114 | } 115 | 116 | void titleSelectDrawBot(void) 117 | { 118 | drawingSetMode(DRAW_MODE_DRAWING); 119 | drawingSetZ(0.4f); 120 | 121 | drawingWithColor(0x80FFFFFF); 122 | drawingDrawQuad(0.0f, 10.0f, 320.0f, 120.0f); 123 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 124 | 125 | textSetColor(0xFF545454); 126 | textDrawInBox(textGetString(StrId_TitleSelector), 0, 0.75f, 0.75f, 10.0f+25.0f, 8.0f, 320-8.0f); 127 | textDraw(8.0f, 10.0f+25.0f+8.0f, 0.5f, 0.5f, false, textGetString(StrId_SelectTitle)); 128 | 129 | if (s_iconReady) 130 | { 131 | float x = (320.0f-g_imageData[images_appbubble_idx].width)/2+4; 132 | menuDrawEntry(&s_iconEntry, x, 150.0f-4, true); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /source/titles.c: -------------------------------------------------------------------------------- 1 | #include "titles.h" 2 | 3 | typedef struct 4 | { 5 | int totalNum; 6 | int filteredNum; 7 | u64* ids; 8 | } titleList_s; 9 | 10 | static titleList_s s_titles[3]; 11 | 12 | static void titleListClear(titleList_s* tl) 13 | { 14 | if (tl->ids) free(tl->ids); 15 | memset(tl, 0, sizeof(*tl)); 16 | } 17 | 18 | void titlesClear(void) 19 | { 20 | int i; 21 | for (i = 0; i < 3; i ++) 22 | titleListClear(&s_titles[i]); 23 | } 24 | 25 | int titlesCount(void) 26 | { 27 | return s_titles[2].filteredNum + s_titles[1].filteredNum + s_titles[0].filteredNum; 28 | } 29 | 30 | void titlesGetEntry(u64* outTid, u8* outMediatype, int index) 31 | { 32 | if (index < 0) 33 | return; 34 | if (index < s_titles[2].filteredNum) 35 | { 36 | *outTid = s_titles[2].ids[index]; 37 | *outMediatype = 2; 38 | return; 39 | } 40 | index -= s_titles[2].filteredNum; 41 | if (index < s_titles[1].filteredNum) 42 | { 43 | *outTid = s_titles[1].ids[index]; 44 | *outMediatype = 1; 45 | return; 46 | } 47 | index -= s_titles[1].filteredNum; 48 | if (index >= s_titles[0].filteredNum) 49 | return; 50 | *outTid = s_titles[0].ids[index]; 51 | *outMediatype = 0; 52 | } 53 | 54 | bool titlesExists(u64 tid, u8 mediatype) 55 | { 56 | titleList_s* tl = &s_titles[mediatype]; 57 | int i; 58 | for (i = 0; i < tl->filteredNum; i ++) 59 | if (tl->ids[i] == tid) 60 | return true; 61 | return false; 62 | } 63 | 64 | static void titlesUpdate(void* pnewState) 65 | { 66 | int i, j; 67 | UIState newState = (UIState)pnewState; 68 | for (i = 0; i < 3; i ++) 69 | { 70 | titleList_s* tl = &s_titles[i]; 71 | if (tl->totalNum == 0) goto _fail; 72 | u64* new_list = (u64*)realloc(tl->ids, tl->totalNum*sizeof(u64)); 73 | if (!new_list) goto _fail; 74 | tl->ids = new_list; 75 | u32 temp; 76 | Result ret = AM_GetTitleList(&temp, i, tl->totalNum, tl->ids); 77 | if (R_FAILED(ret)) goto _fail; 78 | tl->filteredNum = 0; 79 | for (j = 0; j < tl->totalNum; j ++) 80 | { 81 | u64 tid = tl->ids[j]; 82 | tl->ids[tl->filteredNum] = tid; 83 | u32 tid_high = tid >> 32; 84 | if (tid_high == 0x00040010 || tid_high == 0x00040000 || tid_high == 0x00040002) 85 | tl->filteredNum ++; 86 | } 87 | continue; 88 | 89 | _fail: 90 | titleListClear(tl); 91 | } 92 | 93 | if (newState > UI_STATE_NULL) 94 | uiEnterState(newState); 95 | } 96 | 97 | bool titlesCheckUpdate(bool async, UIState newState) 98 | { 99 | int i; 100 | bool needUpdate = false; 101 | for (i = 0; i < 3; i ++) 102 | { 103 | int num; 104 | Result res = AM_GetTitleCount(i, (u32*)&num); 105 | if (R_FAILED(res) && s_titles[i].totalNum != 0) 106 | { 107 | needUpdate = true; 108 | s_titles[i].totalNum = 0; 109 | } else if (R_SUCCEEDED(res) && s_titles[i].totalNum != num) 110 | { 111 | needUpdate = true; 112 | s_titles[i].totalNum = num; 113 | } 114 | } 115 | 116 | if (!needUpdate) return false; 117 | 118 | if (async) 119 | workerSchedule(titlesUpdate, (void*)newState); 120 | else 121 | titlesUpdate((void*)newState); 122 | return true; 123 | } 124 | 125 | bool titlesLoadSmdh(smdh_s* smdh, u8 mediatype, u64 tid) 126 | { 127 | static const u32 filePath[] = {0, 0, 2, 0x6E6F6369, 0}; 128 | u32 archivePath[] = {tid & 0xFFFFFFFF, tid >> 32, mediatype, 0}; 129 | 130 | FS_Path apath = { PATH_BINARY, sizeof(archivePath), archivePath }; 131 | FS_Path fpath = { PATH_BINARY, sizeof(filePath), filePath }; 132 | Handle file = 0; 133 | Result res = FSUSER_OpenFileDirectly(&file, ARCHIVE_SAVEDATA_AND_CONTENT, apath, fpath, FS_OPEN_READ, 0); 134 | if (R_FAILED(res)) return false; 135 | 136 | if (!smdh) 137 | { 138 | FSFILE_Close(file); 139 | return true; 140 | } 141 | 142 | u32 bytesRead; 143 | res = FSFILE_Read(file, &bytesRead, 0, smdh, sizeof(*smdh)); 144 | FSFILE_Close(file); 145 | 146 | return R_SUCCEEDED(res) && bytesRead==sizeof(*smdh); 147 | } 148 | -------------------------------------------------------------------------------- /source/parsing/scanner.c: -------------------------------------------------------------------------------- 1 | #include <3ds.h> 2 | #include 3 | #include 4 | #include "scanner.h" 5 | #include "descriptor.h" 6 | 7 | static const char* const servicesThatMatter[] = 8 | { 9 | "soc:U", 10 | "csnd:SND", 11 | "qtm:s", 12 | "nfc:u", 13 | "http:C" 14 | }; 15 | 16 | void scannerInit(executableMetadata_s* em) 17 | { 18 | em->scanned = false; 19 | em->sectionSizes[0] = 0; 20 | em->sectionSizes[1] = 0; 21 | em->sectionSizes[2] = 0; 22 | 23 | memset(em->servicesThatMatter, 0, sizeof(em->servicesThatMatter)); 24 | } 25 | 26 | static Result scan3dsx(const char* path, const char* const* patterns, int num_patterns, u32* sectionSizes, bool* patternsFound) 27 | { 28 | if(!path)return -1; 29 | 30 | FILE* f = fopen(path, "rb"); 31 | if(!f)return -2; 32 | 33 | Result ret = 0; 34 | 35 | _3DSX_Header hdr; 36 | fread(&hdr, _3DSX_HEADER_SIZE, 1, f); 37 | 38 | if(hdr.magic != _3DSX_MAGIC) 39 | { 40 | ret = -3; 41 | goto end; 42 | } 43 | 44 | if(sectionSizes) 45 | { 46 | sectionSizes[0] = hdr.codeSegSize; 47 | sectionSizes[1] = hdr.rodataSegSize; 48 | sectionSizes[2] = hdr.dataSegSize + hdr.bssSize; 49 | } 50 | 51 | if(patterns && num_patterns && patternsFound) 52 | { 53 | const int buffer_size = 0x1000; 54 | const int max_pattern_size = 0x10; 55 | 56 | static __thread u8 buffer[0x1000 + 0x10]; 57 | 58 | int j; 59 | for(j=0; jscanned)return; 111 | 112 | Result ret; 113 | if (autodetectServices) 114 | ret = scan3dsx(path, servicesThatMatter, NUM_SERVICESTHATMATTER, em->sectionSizes, em->servicesThatMatter); 115 | else 116 | ret = scan3dsx(path, NULL, 0, em->sectionSizes, NULL); 117 | 118 | em->scanned = R_SUCCEEDED(ret); 119 | } 120 | 121 | void descriptorScanFile(descriptor_s* d, const char* path) 122 | { 123 | executableMetadata_s* em = &d->executableMetadata; 124 | 125 | // Retrieve section sizes from the executable and, if autodetection is enabled, 126 | // scan it for service names (default, not ideal but whatchagonnado) 127 | scannerScan(em, path, d->autodetectServices); 128 | 129 | if (!d->autodetectServices) 130 | { 131 | // Populate the metadata structure with section sizes and requested services from descriptor 132 | int i, j; 133 | for(i=0; inumRequestedServices; i++) 134 | { 135 | for(j=0; jrequestedServices[i].name, servicesThatMatter[j])) 138 | { 139 | em->servicesThatMatter[j] = d->requestedServices[i].priority; 140 | break; 141 | } 142 | } 143 | } 144 | em->scanned = true; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /source/launch.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "ui/titleselect.h" 3 | 4 | static const loaderFuncs_s *s_loader; 5 | static Handle s_hbKill; 6 | 7 | void launchInit(void) 8 | { 9 | #define ADD_LOADER(_name) \ 10 | do \ 11 | { \ 12 | extern const loaderFuncs_s _name; \ 13 | if (_name.init()) \ 14 | { \ 15 | s_loader = &_name; \ 16 | return; \ 17 | } \ 18 | } while (0) 19 | 20 | s_hbKill = envGetHandle("hb:kill"); 21 | 22 | ADD_LOADER(loader_Rosalina); 23 | ADD_LOADER(loader_Ninjhax2); 24 | 25 | // Shouldn't happen 26 | svcBreak(USERBREAK_PANIC); 27 | } 28 | 29 | void launchExit(void) 30 | { 31 | s_loader->deinit(); 32 | } 33 | 34 | const loaderFuncs_s *launchGetLoader(void) 35 | { 36 | return s_loader; 37 | } 38 | 39 | size_t launchAddArg(argData_s *ad, const char *arg) 40 | { 41 | size_t len = strlen(arg) + 1; 42 | if ((ad->dst + len) >= (char *)(ad + 1)) 43 | return len; // Overflow 44 | ad->buf[0]++; 45 | strcpy(ad->dst, arg); 46 | ad->dst += len; 47 | return len; 48 | } 49 | 50 | void launchAddArgsFromString(argData_s *ad, char *arg) 51 | { 52 | char c, *pstr, *str = arg, *endarg = arg + strlen(arg); 53 | 54 | do 55 | { 56 | do 57 | { 58 | c = *str++; 59 | } while ((c == ' ' || c == '\t') && str < endarg); 60 | 61 | pstr = str - 1; 62 | 63 | if (c == '\"') 64 | { 65 | pstr++; 66 | while (*str++ != '\"' && str < endarg) 67 | ; 68 | } 69 | else if (c == '\'') 70 | { 71 | pstr++; 72 | while (*str++ != '\'' && str < endarg) 73 | ; 74 | } 75 | else 76 | { 77 | do 78 | { 79 | c = *str++; 80 | } while (c != ' ' && c != '\t' && str < endarg); 81 | } 82 | 83 | str--; 84 | 85 | if (str == (endarg - 1)) 86 | { 87 | if (*str == '\"' || *str == '\'') 88 | *(str++) = 0; 89 | else 90 | str++; 91 | } 92 | else 93 | { 94 | *(str++) = '\0'; 95 | } 96 | 97 | launchAddArg(ad, pstr); 98 | 99 | } while (str < endarg); 100 | } 101 | 102 | void launchMenuEntry(menuEntry_s *me) 103 | { 104 | bool canUseTitles = loaderCanUseTitles(); 105 | if (me->descriptor.numTargetTitles && canUseTitles) 106 | { 107 | // Update the list of available titles 108 | titlesCheckUpdate(false, UI_STATE_NULL); 109 | 110 | int i; 111 | for (i = 0; i < me->descriptor.numTargetTitles; i++) 112 | if (titlesExists(me->descriptor.targetTitles[i].tid, me->descriptor.targetTitles[i].mediatype)) 113 | break; 114 | 115 | if (i == me->descriptor.numTargetTitles) 116 | { 117 | errorScreen(s_loader->name, textGetString(StrId_MissingTargetTitle)); 118 | return; 119 | } 120 | 121 | // Use the title 122 | s_loader->useTitle(me->descriptor.targetTitles[i].tid, me->descriptor.targetTitles[i].mediatype); 123 | } 124 | else if (me->descriptor.selectTargetProcess) 125 | { 126 | if (!canUseTitles) 127 | { 128 | errorScreen(s_loader->name, textGetString(StrId_NoTargetTitleSupport)); 129 | return; 130 | } 131 | 132 | // Launch the title selector 133 | if (!me->titleSelected) 134 | { 135 | titleSelectInit(me); 136 | return; 137 | } 138 | // Use the title 139 | s_loader->useTitle(me->titleId, me->titleMediatype); 140 | } 141 | 142 | // Scan the executable if needed 143 | if (loaderHasFlag(LOADER_NEED_SCAN)) 144 | descriptorScanFile(&me->descriptor, me->path); 145 | // Launch it 146 | s_loader->launchFile(me->path, &me->args, &me->descriptor.executableMetadata); 147 | } 148 | 149 | Handle launchOpenFile(const char *path) 150 | { 151 | if (strncmp(path, "sdmc:/", 6) == 0) 152 | path += 5; 153 | else if (*path != '/') 154 | return 0; 155 | 156 | // Convert the executable path to UTF-16 157 | static uint16_t __utf16path[PATH_MAX + 1]; 158 | ssize_t units = utf8_to_utf16(__utf16path, (const uint8_t *)path, PATH_MAX); 159 | if (units < 0 || units >= PATH_MAX) 160 | return 0; 161 | __utf16path[units] = 0; 162 | 163 | // Open the file directly 164 | FS_Path apath = {PATH_EMPTY, 1, (u8 *)""}; 165 | FS_Path fpath = {PATH_UTF16, (units + 1) * 2, (u8 *)__utf16path}; 166 | Handle file; 167 | Result res = FSUSER_OpenFileDirectly(&file, ARCHIVE_SDMC, apath, fpath, FS_OPEN_READ, 0); 168 | return R_SUCCEEDED(res) ? file : 0; 169 | } 170 | 171 | bool launchHomeMenuEnabled(void) 172 | { 173 | return s_hbKill != 0; 174 | } 175 | 176 | void launchHomeMenu(void) 177 | { 178 | if (!launchHomeMenuEnabled()) 179 | return; 180 | svcSignalEvent(s_hbKill); 181 | __system_retAddr = NULL; 182 | uiExitLoop(); 183 | } 184 | -------------------------------------------------------------------------------- /source/text.c: -------------------------------------------------------------------------------- 1 | #include "text.h" 2 | 3 | static C3D_Tex* s_glyphSheets; 4 | static float s_textScale; 5 | static int s_textLang = CFG_LANGUAGE_EN; 6 | 7 | void textInit(void) 8 | { 9 | // Ensure the shared system font is mapped 10 | fontEnsureMapped(); 11 | 12 | // Load the glyph texture sheets 13 | int i; 14 | TGLP_s* glyphInfo = fontGetGlyphInfo(NULL); 15 | s_glyphSheets = malloc(sizeof(C3D_Tex)*glyphInfo->nSheets); 16 | s_textScale = 30.0f / glyphInfo->cellHeight; 17 | for (i = 0; i < glyphInfo->nSheets; i ++) 18 | { 19 | C3D_Tex* tex = &s_glyphSheets[i]; 20 | tex->data = fontGetGlyphSheetTex(NULL, i); 21 | tex->fmt = glyphInfo->sheetFmt; 22 | tex->size = glyphInfo->sheetSize; 23 | tex->width = glyphInfo->sheetWidth; 24 | tex->height = glyphInfo->sheetHeight; 25 | tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) 26 | | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE); 27 | tex->border = 0; 28 | tex->lodParam = 0; 29 | } 30 | 31 | Result res = cfguInit(); 32 | if (R_SUCCEEDED(res)) 33 | { 34 | u8 lang; 35 | res = CFGU_GetSystemLanguage(&lang); 36 | if (R_SUCCEEDED(res)) 37 | s_textLang = lang; 38 | cfguExit(); 39 | } 40 | } 41 | 42 | void textExit(void) 43 | { 44 | free(s_glyphSheets); 45 | } 46 | 47 | int textGetLang(void) 48 | { 49 | return s_textLang; 50 | } 51 | 52 | const char* textGetString(StrId id) 53 | { 54 | const char* str = g_strings[id][s_textLang]; 55 | if (!str) str = g_strings[id][CFG_LANGUAGE_EN]; 56 | return str; 57 | } 58 | 59 | void textSetColor(u32 color) 60 | { 61 | C3D_TexEnv* env = C3D_GetTexEnv(0); 62 | C3D_TexEnvSrc(env, C3D_RGB, GPU_CONSTANT, 0, 0); 63 | C3D_TexEnvSrc(env, C3D_Alpha, GPU_TEXTURE0, GPU_CONSTANT, 0); 64 | C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); 65 | C3D_TexEnvFunc(env, C3D_Alpha, GPU_MODULATE); 66 | C3D_TexEnvColor(env, color); 67 | } 68 | 69 | static inline float maxf(float a, float b) 70 | { 71 | return a > b ? a : b; 72 | } 73 | 74 | float textCalcWidth(const char* text) 75 | { 76 | float width = 0.0f; 77 | float maxWidth = 0.0f; 78 | ssize_t units; 79 | uint32_t code; 80 | const uint8_t* p = (const uint8_t*)text; 81 | do 82 | { 83 | if (!*p) break; 84 | units = decode_utf8(&code, p); 85 | if (units == -1) 86 | break; 87 | p += units; 88 | 89 | if (code == '\n') 90 | { 91 | maxWidth = maxf(width, maxWidth); 92 | width = 0.0f; 93 | continue; 94 | } 95 | 96 | if (code > 0) 97 | { 98 | int glyphIdx = fontGlyphIndexFromCodePoint(NULL, code); 99 | charWidthInfo_s* cwi = fontGetCharWidthInfo(NULL, glyphIdx); 100 | width += cwi->charWidth; 101 | } 102 | } while (code > 0); 103 | return s_textScale*maxf(width, maxWidth); 104 | } 105 | 106 | void textDraw(float x, float y, float scaleX, float scaleY, bool baseline, const char* text) 107 | { 108 | ssize_t units; 109 | uint32_t code; 110 | 111 | const uint8_t* p = (const uint8_t*)text; 112 | float firstX = x; 113 | u32 flags = GLYPH_POS_CALC_VTXCOORD | (baseline ? GLYPH_POS_AT_BASELINE : 0); 114 | scaleX *= s_textScale; 115 | scaleY *= s_textScale; 116 | do 117 | { 118 | if (!*p) break; 119 | units = decode_utf8(&code, p); 120 | if (units == -1) 121 | break; 122 | p += units; 123 | if (code == '\n') 124 | { 125 | x = firstX; 126 | y += ceilf(scaleY*fontGetInfo(NULL)->lineFeed); 127 | } 128 | else if (code > 0) 129 | { 130 | int glyphIdx = fontGlyphIndexFromCodePoint(NULL, code); 131 | fontGlyphPos_s data; 132 | fontCalcGlyphPos(&data, NULL, glyphIdx, flags, scaleX, scaleY); 133 | 134 | // Draw the glyph 135 | drawingSetTex(&s_glyphSheets[data.sheetIndex]); 136 | drawingAddVertex(x+data.vtxcoord.left, y+data.vtxcoord.bottom, data.texcoord.left, data.texcoord.bottom); 137 | drawingAddVertex(x+data.vtxcoord.right, y+data.vtxcoord.bottom, data.texcoord.right, data.texcoord.bottom); 138 | drawingAddVertex(x+data.vtxcoord.left, y+data.vtxcoord.top, data.texcoord.left, data.texcoord.top); 139 | drawingAddVertex(x+data.vtxcoord.right, y+data.vtxcoord.top, data.texcoord.right, data.texcoord.top); 140 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 141 | 142 | x += data.xAdvance; 143 | 144 | } 145 | } while (code > 0); 146 | } 147 | 148 | void textDrawInBox(const char* text, int orientation, float scaleX, float scaleY, float baseline, float left, float right) 149 | { 150 | float bwidth = right-left; 151 | float twidth = scaleX*textCalcWidth(text); 152 | if (twidth > bwidth) 153 | { 154 | scaleX *= bwidth / twidth; 155 | twidth = bwidth; 156 | } 157 | float x; 158 | if (orientation < 0) 159 | x = left; 160 | else if (orientation > 0) 161 | x = floorf(right-twidth); 162 | else 163 | x = left + floorf((bwidth-twidth)/2); 164 | textDraw(x, baseline, scaleX, scaleY, true, text); 165 | } 166 | -------------------------------------------------------------------------------- /source/loaders/hax2.c: -------------------------------------------------------------------------------- 1 | #include "../common.h" 2 | #include "../parsing/memmap.h" 3 | 4 | typedef struct 5 | { 6 | s32 processId; 7 | bool capabilities[0x10]; 8 | } processEntry_s; 9 | 10 | typedef void (*callBootloader_2x_fn)(Handle file, u32* argbuf, u32 arglength); 11 | typedef void (*callBootloaderNewProcess_2x_fn)(s32 processId, u32* argbuf, u32 arglength); 12 | typedef void (*callBootloaderRunTitle_2x_fn)(u8 mediatype, u32* argbuf, u32 argbuflength, u32 tid_low, u32 tid_high); 13 | typedef void (*callBootloaderRunTitleCustom_2x_fn)(u8 mediatype, u32* argbuf, u32 argbuflength, u32 tid_low, u32 tid_high, memmap_t* mmap); 14 | typedef void (*getBestProcess_2x_fn)(u32 sectionSizes[3], bool* requirements, int num_requirements, processEntry_s* out, int out_size, int* out_len); 15 | 16 | #define callBootloader_2x ((callBootloader_2x_fn)0x00100000) 17 | #define callBootloaderNewProcess_2x ((callBootloaderNewProcess_2x_fn)0x00100008) 18 | #define callBootloaderRunTitle_2x ((callBootloaderRunTitle_2x_fn)0x00100010) 19 | #define callBootloaderRunTitleCustom_2x ((callBootloaderRunTitleCustom_2x_fn)0x00100014) 20 | #define getBestProcess_2x ((getBestProcess_2x_fn)0x0010000C) 21 | 22 | static s32 targetProcess = -1; 23 | static u64 targetTid; 24 | static u8 targetMediatype; 25 | static Handle fileHandle; 26 | static u32 argBuf[ENTRY_ARGBUFSIZE/sizeof(u32)]; 27 | static u32 argBufLen; 28 | static u32 memMapBuf[0x40]; 29 | static bool useMemMap; 30 | 31 | static bool init(void) 32 | { 33 | return R_SUCCEEDED(amInit()); 34 | } 35 | 36 | static void deinit(void) 37 | { 38 | amExit(); 39 | } 40 | 41 | static void bootloaderJump(void) 42 | { 43 | if (targetProcess == -1) 44 | callBootloader_2x(fileHandle, argBuf, argBufLen); 45 | else if (targetProcess == -2) 46 | { 47 | if (useMemMap) 48 | callBootloaderRunTitleCustom_2x(targetMediatype, argBuf, argBufLen, (u32)targetTid, (u32)(targetTid>>32), (memmap_t*)memMapBuf); 49 | else 50 | callBootloaderRunTitle_2x(targetMediatype, argBuf, argBufLen, (u32)targetTid, (u32)(targetTid>>32)); 51 | } 52 | else 53 | callBootloaderNewProcess_2x(targetProcess, argBuf, argBufLen); 54 | } 55 | 56 | static void launchFile(const char* path, argData_s* args, executableMetadata_s* em) 57 | { 58 | if (em && em->scanned && targetProcess == -1) 59 | { 60 | 61 | // this is a really shitty implementation of what we should be doing 62 | // i'm really too lazy to do any better right now, but a good solution will come 63 | // (some day) 64 | processEntry_s out[4]; 65 | int out_len = 0; 66 | getBestProcess_2x(em->sectionSizes, (bool*)em->servicesThatMatter, NUM_SERVICESTHATMATTER, out, 4, &out_len); 67 | 68 | // temp : check if we got all the services we want 69 | if ( 70 | em->servicesThatMatter[0] <= out[0].capabilities[0] 71 | && em->servicesThatMatter[1] <= out[0].capabilities[1] 72 | && em->servicesThatMatter[2] <= out[0].capabilities[2] 73 | && em->servicesThatMatter[3] <= out[0].capabilities[3] 74 | && em->servicesThatMatter[4] <= out[0].capabilities[4]) 75 | targetProcess = out[0].processId; 76 | else 77 | { 78 | // temp : if we didn't get everything we wanted, we search for the candidate that has as many highest-priority services as possible 79 | int i, j; 80 | int best_id = 0; 81 | int best_sum = 0; 82 | for (i=0; iservicesThatMatter[j] == 1) && out[i].capabilities[j]; 87 | 88 | if(sum > best_sum) 89 | { 90 | best_id = i; 91 | best_sum = sum; 92 | } 93 | } 94 | targetProcess = out[best_id].processId; 95 | } 96 | } else if (targetProcess != -1) 97 | targetProcess = -2; 98 | 99 | if (targetProcess == -1) 100 | { 101 | fileHandle = launchOpenFile(path); 102 | if (fileHandle==0) 103 | { 104 | errorScreen(textGetString(StrId_IOError), textGetString(StrId_CouldNotOpenFile), path); 105 | return; 106 | } 107 | } 108 | 109 | argBufLen = args->dst - (char*)args->buf; 110 | memcpy(argBuf, args->buf, argBufLen); 111 | __system_retAddr = bootloaderJump; 112 | uiExitLoop(); 113 | } 114 | 115 | static void useTitle(u64 tid, u8 mediatype) 116 | { 117 | targetProcess = -2; 118 | targetTid = tid; 119 | targetMediatype = mediatype; 120 | 121 | char buf[32]; 122 | sprintf(buf, "/mmap/%08lX%08lX.xml", (u32)(tid>>32), (u32)tid); 123 | memmap_t* map = memmapLoad(buf); 124 | if (map) 125 | { 126 | u32 size = memmapSize(map); 127 | if (size <= sizeof(memMapBuf)) 128 | { 129 | useMemMap = true; 130 | memcpy(memMapBuf, map, size); 131 | } 132 | free(map); 133 | } 134 | } 135 | 136 | const loaderFuncs_s loader_Ninjhax2 = 137 | { 138 | .name = "hax 2.x", 139 | .flags = LOADER_SHOW_REBOOT | LOADER_NEED_SCAN, 140 | .init = init, 141 | .deinit = deinit, 142 | .launchFile = launchFile, 143 | .useTitle = useTitle, 144 | }; 145 | -------------------------------------------------------------------------------- /source/menu-list.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | static menu_s s_menu[2]; 4 | static menu_s s_menuFileAssoc[2]; 5 | 6 | static bool s_curMenu, s_curMenuFileAssoc; 7 | 8 | menu_s* menuGetCurrent(void) 9 | { 10 | return &s_menu[s_curMenu]; 11 | } 12 | 13 | menu_s* menuFileAssocGetCurrent(void) { 14 | return &s_menuFileAssoc[s_curMenuFileAssoc]; 15 | } 16 | 17 | menuEntry_s* menuCreateEntry(MenuEntryType type) 18 | { 19 | menuEntry_s* me = (menuEntry_s*)malloc(sizeof(menuEntry_s)); 20 | menuEntryInit(me, type); 21 | return me; 22 | } 23 | 24 | void menuDeleteEntry(menuEntry_s* me) 25 | { 26 | menuEntryFree(me); 27 | free(me); 28 | } 29 | 30 | static void _menuAddEntry(menu_s* m, menuEntry_s* me) 31 | { 32 | me->menu = m; 33 | if (m->lastEntry) 34 | { 35 | m->lastEntry->next = me; 36 | m->lastEntry = me; 37 | } else 38 | { 39 | m->firstEntry = me; 40 | m->lastEntry = me; 41 | } 42 | m->nEntries++; 43 | } 44 | 45 | static void _menuClear(menu_s* menu) 46 | { 47 | menuEntry_s* cur, *next; 48 | for (cur = menu->firstEntry; cur; cur = next) { 49 | next = cur->next; 50 | menuDeleteEntry(cur); 51 | } 52 | memset(menu, 0, sizeof(*menu)); 53 | } 54 | 55 | static void menuClear(void) 56 | { 57 | _menuClear(&s_menu[!s_curMenu]); 58 | } 59 | 60 | void menuFileAssocClear(void) 61 | { 62 | _menuClear(&s_menuFileAssoc[!s_curMenuFileAssoc]); 63 | } 64 | 65 | void menuFileAssocAddEntry(menuEntry_s* me) 66 | { 67 | _menuAddEntry(&s_menuFileAssoc[!s_curMenuFileAssoc], me); 68 | } 69 | 70 | static void menuAddEntry(menuEntry_s* me) 71 | { 72 | _menuAddEntry(&s_menu[!s_curMenu], me); 73 | } 74 | 75 | static int menuEntryCmp(const void *p1, const void *p2) 76 | { 77 | const menuEntry_s* lhs = *(menuEntry_s**)p1; 78 | const menuEntry_s* rhs = *(menuEntry_s**)p2; 79 | 80 | if(lhs->isStarred != rhs->isStarred) 81 | return lhs->isStarred ? -1 : 1; 82 | if(lhs->type == rhs->type) 83 | return strcasecmp(lhs->name, rhs->name); 84 | if(lhs->type == ENTRY_TYPE_FOLDER) 85 | return -1; 86 | return 1; 87 | } 88 | 89 | static void menuSort(void) 90 | { 91 | int i; 92 | menu_s* m = &s_menu[!s_curMenu]; 93 | int nEntries = m->nEntries; 94 | if (nEntries==0) return; 95 | 96 | menuEntry_s** list = (menuEntry_s**)calloc(nEntries, sizeof(menuEntry_s*)); 97 | if(list == NULL) return; 98 | 99 | menuEntry_s* p = m->firstEntry; 100 | for(i = 0; i < nEntries; ++i) { 101 | list[i] = p; 102 | p = p->next; 103 | } 104 | 105 | qsort(list, nEntries, sizeof(menuEntry_s*), menuEntryCmp); 106 | 107 | menuEntry_s** pp = &m->firstEntry; 108 | for(i = 0; i < nEntries; ++i) { 109 | *pp = list[i]; 110 | pp = &(*pp)->next; 111 | } 112 | m->lastEntry = list[nEntries-1]; 113 | *pp = NULL; 114 | 115 | free(list); 116 | } 117 | 118 | int menuFileAssocScan(const char* target) 119 | { 120 | menuFileAssocClear(); 121 | 122 | if (chdir(target) < 0) 123 | return 1; 124 | 125 | if (getcwd(s_menuFileAssoc[!s_curMenuFileAssoc].dirname, PATH_MAX + 1) == NULL) 126 | return 1; 127 | 128 | DIR* dir; 129 | struct dirent* dp; 130 | char temp[PATH_MAX + 1]; 131 | 132 | dir = opendir(s_menuFileAssoc[!s_curMenuFileAssoc].dirname); 133 | if (!dir) 134 | return 2; 135 | 136 | while ((dp = readdir(dir))) { 137 | if (dp->d_name[0] == '.') 138 | continue; 139 | 140 | memset(temp, 0, sizeof(temp)); 141 | snprintf(temp, sizeof(temp) - 1, "%s/%s", s_menuFileAssoc[!s_curMenuFileAssoc].dirname, dp->d_name); 142 | 143 | const char* ext = getExtension(dp->d_name); 144 | if (strcasecmp(ext, ".cfg") != 0) 145 | continue; 146 | 147 | menuEntryFileAssocLoad(temp); 148 | } 149 | 150 | closedir(dir); 151 | s_curMenuFileAssoc = !s_curMenuFileAssoc; 152 | menuFileAssocClear(); 153 | 154 | return 0; 155 | } 156 | 157 | int menuScan(const char* target) 158 | { 159 | if (chdir(target) < 0) return 1; 160 | getcwd(s_menu[!s_curMenu].dirname, PATH_MAX+1); 161 | 162 | DIR* dir; 163 | struct dirent* dp; 164 | dir = opendir(s_menu[!s_curMenu].dirname); 165 | if (!dir) return 2; 166 | 167 | while ((dp = readdir(dir))) 168 | { 169 | archive_dir_t* dirSt = (archive_dir_t*)dir->dirData->dirStruct; 170 | FS_DirectoryEntry* entry = &dirSt->entry_data[dirSt->index]; 171 | menuEntry_s* me = NULL; 172 | bool shortcut = false; 173 | if (entry->attributes & FS_ATTRIBUTE_HIDDEN || dp->d_name[0] == '.') 174 | continue; 175 | 176 | if (entry->attributes & FS_ATTRIBUTE_DIRECTORY) 177 | me = menuCreateEntry(ENTRY_TYPE_FOLDER); 178 | else 179 | { 180 | const char* ext = getExtension(dp->d_name); 181 | if (strcasecmp(ext, ".3dsx")==0 || (shortcut = strcasecmp(ext, ".xml")==0)) 182 | me = menuCreateEntry(ENTRY_TYPE_FILE); 183 | 184 | if (!me) 185 | me = menuCreateEntry(ENTRY_TYPE_FILE_OTHER); 186 | } 187 | 188 | if (!me) 189 | continue; 190 | 191 | snprintf(me->path, sizeof(me->path), "%s/%s", s_menu[!s_curMenu].dirname, dp->d_name); 192 | if (menuEntryLoad(me, dp->d_name, shortcut)) 193 | menuAddEntry(me); 194 | else 195 | menuDeleteEntry(me); 196 | } 197 | 198 | closedir(dir); 199 | menuSort(); 200 | 201 | // Swap the menu and clear the previous menu 202 | s_curMenu = !s_curMenu; 203 | menuClear(); 204 | return 0; 205 | } 206 | 207 | void menuToggleStar(menuEntry_s* me) 208 | { 209 | me->isStarred = !me->isStarred; 210 | if (me->isStarred) 211 | { 212 | FILE* f = fopen(me->starpath, "w"); 213 | if (f) fclose(f); 214 | } else 215 | remove(me->starpath); 216 | 217 | // Sort the menu again 218 | s_curMenu = !s_curMenu; 219 | menuSort(); 220 | s_curMenu = !s_curMenu; 221 | menuClear(); 222 | 223 | menu_s* menu = menuGetCurrent(); 224 | int pos = -1; 225 | for (menuEntry_s* cur = menu->firstEntry; cur; cur = cur->next) 226 | { 227 | ++pos; 228 | if (cur == me) 229 | break; 230 | } 231 | menu->curEntry = pos; 232 | menu->perturbed = true; 233 | } 234 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Pirate Launcher 2 | 3 | The Pirate Launcher is a joke made on the homebrew channel on the Wii. This is the same joke, but on 3DS, except this is the entire menu and not the banner. 4 | 5 | In short, this is just a reskin of The Homebrew Launcher which is forked on. 6 | it has a hidden feature read it for understanding. 7 | 8 | ### Preview: 9 | 10 | 11 | ## Original readme 12 | 13 | #### Presentation 14 | 15 | The Homebrew Launcher (hbmenu for short) is the main menu used to list and launch homebrew applications. It is essentially a graphical shell around an existing homebrew loading mechanism. The following entrypoints are supported: 16 | 17 | - [Luma3DS Rosalina](https://github.com/LumaTeam/Luma3DS) **(recommended)**: Works on all system versions from 4.0 onwards; it provides unrestricted access to 3DS system resources as well as nice extra features such as remote debugging (GDB). For more information read the [Rosalina documentation](https://github.com/LumaTeam/Luma3DS/wiki/Rosalina). 18 | - [Legacy \*hax 2.x](https://smealum.github.io/3ds/): This is a now-obsolete homebrew loading system that only provides limited access to 3DS system resources, as it only attacks a low privilege level. Support for this entrypoint is deprecated and may be removed in a future release. 19 | 20 | 3DS homebrew is built and distributed as executables with the `.3dsx` extension. Note that you may encounter files with the `.cia` extension - these are **not** homebrew executables that can be loaded using hbmenu. 21 | 22 | #### Usage 23 | 24 | To install hbmenu, simply copy `boot.3dsx` to the root of your SD card. If you are using a recent version of [Luma3DS](https://github.com/LumaTeam/Luma3DS) you probably already have a copy of hbmenu installed, as it comes bundled with it. 25 | 26 | Use the D-Pad, Circle Pad or the touchscreen to select an application, and press A or touch it again to start it. Use the C-Stick alternatively on New 3DS to scroll the list of applications. 27 | 28 | hbmenu supports starring applications, so that they are shown at the beginning of the list. The SELECT button stars/unstars the currently selected homebrew application. 29 | 30 | On \*hax 2.x, it is not possible to go back to the 3DS HOME menu using the HOME button. As an alternative, you can press the START button where you can reboot your console or relaunch HOME menu. 31 | 32 | hbmenu starts in the sdmc:/3ds/ directory for applications and it will look for 3dsx files inside it. You can navigate the directory tree and open/browse folders as you would expect. Additionally, folders containing a 3dsx file with the same name as the folder (or alternatively `boot.3dsx`) will be detected as an application bundle folder, and it will be presented as a single icon that can directly launch the application. 33 | 34 | Here is an example directory structure that hbmenu will have no trouble recognizing: 35 | 36 | - sdmc:/ 37 | - 3ds/ 38 | - games/ 39 | - Hermes.3dsx 40 | - cubemadness.3dsx 41 | - Checkpoint/ *(this folder will be detected as an application bundle)* 42 | - Checkpoint.3dsx 43 | - ... 44 | - ftpd.3dsx 45 | - mgba.3dsx 46 | - 3dscraft.3dsx 47 | - blargSNES.3dsx 48 | - gameyob.3dsx 49 | - 3dnes.3dsx 50 | 51 | If hbmenu does not find an icon file (either embedded in the executable or provided separately) to associate with a given 3dsx, it will display a default icon and the path to the executable as a fallback. 52 | 53 | hbmenu also allows you to create "shortcuts" which are xml files containing a path to a 3dsx file and optional arguments to pass to the .3dsx. This file can also include a path to icon data as well as name, description and author text using tags as follows: 54 | 55 | 56 | The path to the 3dsx file goes here. 57 | path to smdh icon data 58 | Place arguments to be passed to 3dsx here. 59 | Name to display 60 | Description of homebrew app 61 | Name of the author 62 | 63 | 64 | Arguments are space or tab separated but can use single or double quotes to contain whitespace. 65 | 66 | Name, description and author will be read from the .3dsx if it has embedded SMDH data or from the supplied icon path. The fields in the xml file will then override their respective entries. 67 | 68 | You should not hotswap the SD card while hbmenu is running since it compromises the 3DS OS's stability amongst other things. It is recommended that you instead use a file transfer homebrew application such as ftpd to transfer files without rebooting. 69 | 70 | #### Technical notes 71 | 72 | hbmenu does all its rendering in hardware thanks to the [citro3d](https://github.com/fincs/citro3d) library. The 3DS system font is also used to render all text. 73 | 74 | hbmenu uses some funky mechanisms to launch 3dsx files. If you're interested in launching 3dsx files from your own application, you should look here; although these mechanisms may change in the future. 75 | 76 | #### Netloader 77 | 78 | hbmenu contains support for the 3dslink protocol, which allows you to remotely load applications. 79 | Press Y to activate as usual then run `3dslink <3dsxfile>` if your network can cope with UDP broadcast messages. 80 | If 3dslink says 3DS not found then you can use `-a ` to tell it where to send the file. 81 | 82 | All the other arguments you give 3dslink will be passed as arguments to the launched 3dsx file. You can also specify argv[0] with `-0 ` which is useful for 83 | setting the current working directory if you already have data files in a particular place, i.e. `3dslink myfile.3dsx -0 sdmc:/3ds/mydata/` 84 | 85 | 3dslink is provided with devkitARM or you can download binaries from [WinterMute's website](http://davejmurphy.com/3dslink/). 86 | 87 | #### Building 88 | 89 | hbmenu uses zlib for compression and tinyxml2 for XML parsing. These libraries are provided by devkitPro through the portlibs mechanism. In order to install them, use the following command: 90 | 91 | ```shell 92 | pacman -S 3ds-zlib 3ds-tinyxml2 3ds-libconfig 93 | ``` 94 | 95 | (Note that `dkp-pacman` is used instead on systems that do not distribute pacman, such as macOS or Debian-based Linux distros) 96 | 97 | Binaries of hbmenu can be downloaded from the [Releases](https://github.com/fincs/new-hbmenu/releases) page. 98 | 99 | #### File Associations 100 | 101 | This is a feature backported from [nx-hbmenu](https://switchbrew.org/wiki/Homebrew_Menu#File_Associations). However, there is one notable difference: icons must be a 48x48 t3x-generated file with GPU_RGB565 as its color format. 102 | 103 | # The Homebrew Launcher - FOR DEVELOPERS 104 | 105 | ## WARNING 1 : This is not the standard homebrew launcher, this is a fork for homebrew developers. 106 | ## WARNING 2 : At the time of writing, you will need to install a fork of luma3ds in order to be able to use it. 107 | 108 | 109 | 110 | 111 | This repository contains a fork of homebrew launcher specifically made for helping homebrew developers debug their homebrews. 112 | 113 | You will however need a specific fork of luma to be able to use it. The reason being that at the time of writing, rosalina did not expose a way to communicate with it from third party homebrews. 114 | 115 | I did submit a pull request here for the feature to end up in mainstream luma3ds but it is not yet the case https://github.com/LumaTeam/Luma3DS/pull/1836 116 | 117 | You can find my fork here https://github.com/Alexyo21/Polari3DS 118 | or cooolgamer https://github.com/cooolgamorg/Starlight3DS 119 | or if you want a more vanilla one, 120 | https://github.com/SeleDreams/Luma3DS 121 | thanks again 122 | 123 | also you need an debug_help.txt with r written in it to enable all of the hidden feature, though I yet have to implement the laucher command to make I work... 124 | This version of the homebrew launcher communicates with luma to make it start the debugger when starting a homebrew software and if a 3dslink.txt file is present on the root of the sd card, it will automatically start 3dslink when it starts, making it way faster to get started with debugging homebrews. Even more if you replace the boot.3dsx at the root of your sd card and then in the luma config (sd:/luma/config.ini), set the autoboot_mode value to 1. Doing this will make homebrew launcher start on boot of the 3ds, so that if your homebrew freezes the whole console you can very quickly go back to debugging after rebooting 125 | 126 | Original repository of homebrew launcher : https://github.com/devkitPro/3ds-hbmenu 127 | #### Contributing 128 | 129 | hbmenu is looking for contributors! We're making this repository public so that you, the community, can make hbmenu into the menu of your dreams. Or show you how to make your own, better menu! Of course we'd rather you improved hbmenu rather than went off and started fragmenting the userbase, but any contributions to the homebrew scene are welcome. Feel free to use code from hbmenu for your own projects, so long as you give credit to its original authors. 130 | 131 | #### Credits 132 | 133 | - Selesdreams: for code and tips 134 | - smea: code & original hbmenu version 135 | - fincs: code & rewrite 136 | - GEMISIS: code 137 | - mtheall: code 138 | - WinterMute: netloader code 139 | - Fluto: graphics 140 | - Arkhandar: graphics 141 | - dotjasp: graphics (regionfree icon) 142 | - gruetzkopf, TuxSH, AuroraWright, Soph1a7, SentientTurtle, Yami-chan, d3m3vilurr, daedreth, JixunMoe, yy-codes, MCPE-PC: translations 143 | -------------------------------------------------------------------------------- /source/ui/background.c: -------------------------------------------------------------------------------- 1 | #include "background.h" 2 | 3 | static u32 wifiStatus; 4 | static u8 batteryLevel = 5; 5 | static u8 charging; 6 | static char timeString[9]; 7 | static char versionString[64]; 8 | #ifdef DBGSTRING 9 | static char dbgString[64]; 10 | #endif 11 | 12 | #define SECONDS_IN_DAY 86400 13 | #define SECONDS_IN_HOUR 3600 14 | #define SECONDS_IN_MINUTE 60 15 | 16 | #define WAVE_HEIGHT 48.0f 17 | #define WAVE_NUMPOINTS 32 18 | #define WAVE_NUMCURVES 5 19 | 20 | static bubble_t bubbles[BUBBLE_COUNT]; 21 | static float logoPosX, logoPosY; 22 | static ImageId logoImg = images_logo_idx; 23 | 24 | static inline float floatFract(float x) 25 | { 26 | return x - floorf(x); 27 | } 28 | 29 | static float randomFloat(void) 30 | { 31 | // Wichmann-Hill 32 | static u16 s1 = 100, s2 = 100, s3 = 100; 33 | 34 | s1 = (171 * s1) % 30269; 35 | s2 = (172 * s2) % 30307; 36 | s3 = (170 * s3) % 30323; 37 | 38 | return floatFract(s1/30269.0f + s2/30307.0f + s3/30323.0f); 39 | } 40 | 41 | static float randomFloatInterval(float vmin, float vmax) 42 | { 43 | return randomFloat()*(vmax-vmin) + vmin; 44 | } 45 | 46 | static struct 47 | { 48 | float phase; 49 | float amplitude; 50 | float velocity; 51 | } waveCurves[WAVE_NUMCURVES]; 52 | 53 | //static float wavePos; 54 | static float wavePoints[WAVE_NUMPOINTS]; 55 | static float waveDisturbance[WAVE_NUMPOINTS]; 56 | static u32 waveDisturbancePos; 57 | static float waveDisturbanceCur; 58 | 59 | static const ImageId batteryLevels[] = 60 | { 61 | images_battery0_idx, 62 | images_battery0_idx, 63 | images_battery1_idx, 64 | images_battery2_idx, 65 | images_battery3_idx, 66 | images_battery4_idx, 67 | }; 68 | 69 | static const ImageId wifiLevels[] = 70 | { 71 | images_wifiNull_idx, 72 | images_wifi1_idx, 73 | images_wifi2_idx, 74 | images_wifi3_idx, 75 | }; 76 | 77 | static float randf() 78 | { 79 | return (float)rand()/(float)RAND_MAX; 80 | } 81 | 82 | void backgroundInit(void) 83 | { 84 | int i = 0; 85 | for (i = 0; i < BUBBLE_COUNT; i ++) 86 | { 87 | bubbles[i].x = rand() % 400; 88 | bubbles[i].y = 172 + (rand() % 308); 89 | bubbles[i].z = randf(); 90 | bubbles[i].angle = randf(); 91 | bubbles[i].angv = 0.02f*randf(); 92 | bubbles[i].fade = 15; 93 | } 94 | float height = WAVE_HEIGHT*0.8f; 95 | for (i = 0; i < WAVE_NUMCURVES; i ++) 96 | { 97 | waveCurves[i].amplitude = randomFloatInterval(0, height); 98 | waveCurves[i].phase = randomFloat(); 99 | waveCurves[i].velocity = randomFloatInterval(1.0f, 4.0f); 100 | height -= waveCurves[i].amplitude; 101 | } 102 | waveCurves[WAVE_NUMCURVES-1].velocity = M_TAU; 103 | sprintf(versionString, "%s \xEE\x80\x9D %s", launchGetLoader()->name, VERSION); 104 | } 105 | 106 | void waveDisturb(float amount) 107 | { 108 | waveDisturbanceCur += amount; 109 | if (waveDisturbanceCur > WAVE_HEIGHT) 110 | waveDisturbanceCur = WAVE_HEIGHT; 111 | } 112 | 113 | void waveUpdate(void) 114 | { 115 | waveDisturbance[waveDisturbancePos] = waveDisturbanceCur; 116 | 117 | for (u32 i = 0; i < WAVE_NUMPOINTS; i ++) 118 | { 119 | float x = (float)i / (WAVE_NUMPOINTS-1); 120 | float y = 120.0f; 121 | float dist = waveDisturbance[(waveDisturbancePos + i + 1) % WAVE_NUMPOINTS]; 122 | for (u32 j = 0; j < WAVE_NUMCURVES; j ++) 123 | { 124 | float ampl = waveCurves[j].amplitude; 125 | if (j == (WAVE_NUMCURVES-1)) 126 | ampl = dist; 127 | y += 0.5f*ampl * sinf(M_TAU*(waveCurves[j].velocity*x + waveCurves[j].phase)); 128 | } 129 | wavePoints[i] = y; 130 | } 131 | 132 | for (u32 j = 0; j < WAVE_NUMCURVES; j ++) 133 | { 134 | float variance = randomFloatInterval(0.95f, 1.05f); 135 | if (j == (WAVE_NUMCURVES-1)) 136 | variance *= waveCurves[j].velocity; 137 | waveCurves[j].phase += variance/60.0f; 138 | } 139 | 140 | waveDisturbancePos = (waveDisturbancePos + 1) % WAVE_NUMPOINTS; 141 | waveDisturbanceCur -= randomFloatInterval(0.95f, 1.05f); 142 | if (waveDisturbanceCur < 0.0f) 143 | waveDisturbanceCur = 0.0f; 144 | } 145 | 146 | static void bubbleUpdate(bubble_t* bubble) 147 | { 148 | // Float up the screen. 149 | bubble->y -= 2; 150 | 151 | // Check if faded away, then reset if gone. 152 | if (bubble->fade < 10) 153 | { 154 | bubble->x = rand() % 400; 155 | bubble->y = 470 + (rand() % 10); 156 | bubble->z = randf(); 157 | bubble->angle = randf(); 158 | bubble->angv = 0.02f*randf(); 159 | bubble->fade = 15; 160 | } 161 | // Check if too far up screen and start fizzling away. 162 | else if (bubble->y < 172) 163 | bubble->fade -= 10; 164 | // Otherwise make sure the bubble is visible. 165 | else if (bubble->fade < 255) 166 | bubble->fade += 10; 167 | 168 | bubble->angle += bubble->angv; 169 | bubble->angleSin = sinf(M_TAU*bubble->angle); 170 | } 171 | 172 | static bool checkLogoAdv(u32 down) 173 | { 174 | static const u32 params[] = { KEY_UP, KEY_UP, KEY_DOWN, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_LEFT, KEY_RIGHT, KEY_L, KEY_R }; 175 | static u32 state, timeout; 176 | 177 | if (down & params[state]) 178 | { 179 | state++; 180 | timeout = 30; 181 | if (state == sizeof(params)/sizeof(params[0])) 182 | { 183 | state = 0; 184 | return true; 185 | } 186 | } 187 | 188 | if (timeout && !--timeout) 189 | state = 0; 190 | 191 | return false; 192 | } 193 | 194 | void backgroundUpdate(void) 195 | { 196 | u32 i, j; 197 | u32 frames = drawingGetFrames(); 198 | u32 kDown = hidKeysDown(); 199 | 200 | wifiStatus = osGetWifiStrength(); 201 | PTMU_GetBatteryLevel(&batteryLevel); 202 | PTMU_GetBatteryChargeState(&charging); 203 | 204 | u64 timeInSeconds = osGetTime() / 1000; 205 | u64 dayTime = timeInSeconds % SECONDS_IN_DAY; 206 | u8 hour = dayTime / SECONDS_IN_HOUR; 207 | u8 min = (dayTime % SECONDS_IN_HOUR) / SECONDS_IN_MINUTE; 208 | u8 seconds = dayTime % SECONDS_IN_MINUTE; 209 | sprintf(timeString, "%02d:%02d:%02d", hour, min, seconds); 210 | #ifdef DBGSTRING 211 | sprintf(dbgString, "fs:%lu gpu: %.2f%% cpu: %.2f%%", frames, C3D_GetDrawingTime()*6, C3D_GetProcessingTime()*6); 212 | #endif 213 | 214 | if (kDown & (KEY_LEFT|KEY_RIGHT)) 215 | waveDisturb(WAVE_HEIGHT); 216 | else if (kDown & (KEY_UP|KEY_DOWN)) 217 | waveDisturb(WAVE_HEIGHT*0.5f); 218 | 219 | // Update graphical effects 220 | for (j = frames; j; j --) 221 | { 222 | waveUpdate(); 223 | for (i = 0; i < BUBBLE_COUNT; i ++) 224 | bubbleUpdate(&bubbles[i]); 225 | } 226 | 227 | // Update logo 228 | if (logoImg == images_logo2_idx) 229 | logoPosX += frames/64.0f; 230 | logoPosY -= frames/192.0f; 231 | if (checkLogoAdv(kDown)) 232 | logoImg = images_logo2_idx; 233 | } 234 | 235 | void bubbleDraw(bubble_t* bubble, float top, float iod) 236 | { 237 | if ((bubble->y+32) <= top) 238 | return; // Nothing to do 239 | u32 color = ((u32)bubble->fade << 24) | 0xFFFFFF; 240 | float x = bubble->x + iod*(10+10*bubble->z) + 16*bubble->angleSin; 241 | float y = bubble->y - top; 242 | if (top > 0.0f) 243 | x -= (400-320)/2; 244 | drawingDrawImage(images_bubble_idx, color, x, y); 245 | } 246 | 247 | void backgroundDrawTop(float iod) 248 | { 249 | int i; 250 | 251 | drawingSetMode(DRAW_MODE_DRAWING); 252 | 253 | // Clear screen 254 | drawingSetMode(DRAW_MODE_DRAWING); 255 | drawingSetZ(1.0f); 256 | drawingEnableDepth(false); 257 | drawingWithColor(0xFF000000); 258 | drawingDrawQuad(0.0f, 0.0f, 400.0f, 240.0f); 259 | 260 | // Draw the wave 261 | drawingSetMode(DRAW_MODE_WAVE); 262 | drawingWithVertexColor(); 263 | drawingSetGradient(0, 1.0f, 1.0f, 1.0f, 0.0f); 264 | drawingSetGradient(1, 1.0f, 1.0f, 1.0f, 1.0f); 265 | drawingDrawWave(wavePoints, WAVE_NUMPOINTS, 0.0f, 400.0f, -4.0f, +0.0f); 266 | drawingSetGradient(0, 1.0f, 1.0f, 1.0f, 1.0f); 267 | drawingSetGradient(1, 0.0f, 0.0f, 0.0f, 1.0f); 268 | drawingDrawWave(wavePoints, WAVE_NUMPOINTS, 0.0f, 400.0f, +0.0f, +8.0f); 269 | drawingSetGradient(0, 0.0f, 0.0f, 0.0f, 1.0f); 270 | drawingDrawWave(wavePoints, WAVE_NUMPOINTS, 0.0f, 400.0f, +8.0f, +240.0f); 271 | drawingSetMode(DRAW_MODE_DRAWING); 272 | drawingEnableDepth(true); 273 | 274 | // Draw bubbles! 275 | drawingSetZ(0.6f); 276 | for (i = 0; i < BUBBLE_COUNT; i ++) 277 | bubbleDraw(&bubbles[i], 0.0f, iod); 278 | 279 | // Draw HUD 280 | drawingSetZ(0.5f); 281 | 282 | float logo_width = g_imageData[logoImg].width; 283 | float logo_left = floorf(0.5f*(400.0f - logo_width)); 284 | float logo_right = 400.0f - logo_left; 285 | 286 | textSetColor(0xFFFFFFFF); 287 | textDrawInBox(timeString, 0, 0.5f, 0.5f, 15.0f, 0.0f, 400.0f); 288 | textDrawInBox(versionString, 1, 0.5f, 0.5f, 200.0f, 80.0f, logo_right); 289 | #ifdef DBGSTRING 290 | textDrawInBox(dbgString, -1, 0.5f, 0.5f, 214.0f, 20.0f, logo_right); 291 | #endif 292 | 293 | float posX = 20.0f*sinf(C3D_Angle(logoPosX)); 294 | float posY = 6.0f*sinf(C3D_Angle(logoPosY)); 295 | drawingDrawImage(logoImg, 0xFFFFFFFF, logo_left+posX-iod*6.0f, 63.0f+posY); 296 | drawingDrawImage(wifiLevels[wifiStatus], 0xFFFFFFFF, 0.0f, 0.0f); 297 | drawingDrawImage(charging ? images_batteryCharge_idx : batteryLevels[batteryLevel], 0xFFFFFFFF, 400.0f-27, 0.0f); 298 | } 299 | 300 | void backgroundDrawBot(void) 301 | { 302 | int i; 303 | 304 | drawingSetMode(DRAW_MODE_DRAWING); 305 | 306 | // Clear screen 307 | drawingSetZ(0.8f); 308 | drawingEnableDepth(false); 309 | drawingWithColor(0xFF000000); 310 | drawingDrawQuad(0.0f, 0.0f, 320.0f, 240.0f); 311 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 312 | drawingEnableDepth(true); 313 | 314 | // Draw bubbles! 315 | drawingSetZ(0.6f); 316 | for (i = 0; i < BUBBLE_COUNT; i ++) 317 | bubbleDraw(&bubbles[i], 240.0f, 0.0f); 318 | } 319 | -------------------------------------------------------------------------------- /source/ui/netloader.c: -------------------------------------------------------------------------------- 1 | #include "netloader.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #define ZLIB_CHUNK (16 * 1024) 13 | 14 | static int listenfd = -1; 15 | static int datafd = -1; 16 | static int udpfd = -1; 17 | static volatile size_t filelen, filetotal; 18 | static volatile bool wantExit = false; 19 | 20 | static void netloaderError(const char *func, int err); 21 | 22 | static bool set_socket_nonblocking(int sock) 23 | { 24 | int flags = fcntl(sock, F_GETFL); 25 | if (flags == -1) 26 | return false; 27 | return fcntl(sock, F_SETFL, flags | O_NONBLOCK) == 0; 28 | } 29 | 30 | static int recvall(int sock, void *buffer, int size, int flags) 31 | { 32 | int len, sizeleft = size; 33 | 34 | while (sizeleft) 35 | { 36 | len = recv(sock, buffer, sizeleft, flags); 37 | if (!len) 38 | { 39 | size = 0; 40 | break; 41 | } 42 | else if (len < 0) 43 | { 44 | if (errno != EAGAIN && errno != EWOULDBLOCK) 45 | { 46 | netloaderError("recv", errno); 47 | break; 48 | } 49 | } 50 | else 51 | { 52 | sizeleft -= len; 53 | buffer += len; 54 | } 55 | } 56 | return size; 57 | } 58 | 59 | static bool netloaderInit(void) 60 | { 61 | return networkInit(); 62 | } 63 | 64 | static bool netloaderActivate(void) 65 | { 66 | struct sockaddr_in serv_addr; 67 | // create udp socket for broadcast ping 68 | for (;;) 69 | { 70 | udpfd = socket(AF_INET, SOCK_DGRAM, 0); 71 | if (!(udpfd < 0 && errno == -ENETDOWN)) 72 | break; 73 | svcSleepThread(16666666ULL); 74 | if (wantExit) 75 | return false; 76 | } 77 | if (udpfd < 0) 78 | { 79 | netloaderError("udp socket", errno); 80 | return false; 81 | } 82 | 83 | memset(&serv_addr, '0', sizeof(serv_addr)); 84 | serv_addr.sin_family = AF_INET; 85 | serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 86 | serv_addr.sin_port = htons(NETWORK_PORT); 87 | 88 | if (bind(udpfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) 89 | { 90 | netloaderError("bind udp socket", errno); 91 | return false; 92 | } 93 | 94 | if (!set_socket_nonblocking(udpfd)) 95 | { 96 | netloaderError("listen fcntl", errno); 97 | return false; 98 | } 99 | 100 | // create listening socket on all addresses on NETWORK_PORT 101 | 102 | listenfd = socket(AF_INET, SOCK_STREAM, 0); 103 | if (listenfd < 0) 104 | { 105 | netloaderError("socket", errno); 106 | return false; 107 | } 108 | 109 | int rc = bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); 110 | if (rc != 0) 111 | { 112 | netloaderError("bind", errno); 113 | return false; 114 | } 115 | 116 | if (!set_socket_nonblocking(listenfd)) 117 | { 118 | netloaderError("listen fcntl", errno); 119 | return false; 120 | } 121 | 122 | rc = listen(listenfd, 10); 123 | if (rc != 0) 124 | { 125 | netloaderError("listen", errno); 126 | return false; 127 | } 128 | 129 | return true; 130 | } 131 | 132 | static void netloaderDeactivate(void) 133 | { 134 | if (listenfd >= 0) 135 | { 136 | close(listenfd); 137 | listenfd = -1; 138 | } 139 | 140 | if (datafd >= 0) 141 | { 142 | close(datafd); 143 | datafd = -1; 144 | } 145 | 146 | if (udpfd >= 0) 147 | { 148 | close(udpfd); 149 | udpfd = -1; 150 | } 151 | 152 | networkDeactivate(); 153 | } 154 | 155 | void netloaderError(const char *func, int err) 156 | { 157 | netloaderDeactivate(); 158 | networkError(netloaderUpdate, StrId_NetLoader, func, err); 159 | } 160 | 161 | static int receiveAndDecompress(int sock, FILE *fh, size_t filesize) 162 | { 163 | static unsigned char in[ZLIB_CHUNK]; 164 | static unsigned char out[ZLIB_CHUNK]; 165 | 166 | int ret; 167 | unsigned have; 168 | z_stream strm; 169 | size_t chunksize; 170 | 171 | /* allocate inflate state */ 172 | strm.zalloc = Z_NULL; 173 | strm.zfree = Z_NULL; 174 | strm.opaque = Z_NULL; 175 | strm.avail_in = 0; 176 | strm.next_in = Z_NULL; 177 | ret = inflateInit(&strm); 178 | if (ret != Z_OK) 179 | { 180 | netloaderError("inflateInit", ret); 181 | return ret; 182 | } 183 | 184 | size_t total = 0; 185 | // decompress until deflate stream ends or end of file 186 | do 187 | { 188 | int len = recvall(sock, &chunksize, 4, 0); 189 | 190 | if (len != 4) 191 | { 192 | inflateEnd(&strm); 193 | netloaderError("chunksize", len); 194 | return Z_DATA_ERROR; 195 | } 196 | 197 | strm.avail_in = recvall(sock, in, chunksize, 0); 198 | 199 | if (strm.avail_in == 0) 200 | { 201 | inflateEnd(&strm); 202 | netloaderError("closed", 0); 203 | return Z_DATA_ERROR; 204 | } 205 | 206 | strm.next_in = in; 207 | 208 | // run inflate() on input until output buffer not full 209 | do 210 | { 211 | strm.avail_out = ZLIB_CHUNK; 212 | strm.next_out = out; 213 | ret = inflate(&strm, Z_NO_FLUSH); 214 | 215 | switch (ret) 216 | { 217 | case Z_NEED_DICT: 218 | ret = Z_DATA_ERROR; // and fall through 219 | case Z_DATA_ERROR: 220 | case Z_MEM_ERROR: 221 | case Z_STREAM_ERROR: 222 | inflateEnd(&strm); 223 | netloaderError("inflate", ret); 224 | return ret; 225 | } 226 | 227 | have = ZLIB_CHUNK - strm.avail_out; 228 | 229 | if (fwrite(out, 1, have, fh) != have || ferror(fh)) 230 | { 231 | inflateEnd(&strm); 232 | netloaderError("fwrite", 0); 233 | return Z_ERRNO; 234 | } 235 | 236 | total += have; 237 | filetotal = total; 238 | // sprintf(progress,"%zu (%d%%)",total, (100 * total) / filesize); 239 | // netloader_draw_progress(); 240 | } while (strm.avail_out == 0); 241 | 242 | // done when inflate() says it's done 243 | } while (ret != Z_STREAM_END); 244 | 245 | // clean up and return 246 | inflateEnd(&strm); 247 | return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; 248 | } 249 | 250 | void netloaderTask(void *arg) 251 | { 252 | struct sockaddr_in sa_udp_remote; 253 | char recvbuf[256]; 254 | wantExit = false; 255 | filelen = 0; 256 | filetotal = 0; 257 | 258 | if (!netloaderInit()) 259 | { 260 | errorScreen(textGetString(StrId_NetLoader), textGetString(StrId_NetLoaderUnavailable)); 261 | return; 262 | } 263 | 264 | uiEnterState(UI_STATE_NETLOADER); 265 | 266 | if (!netloaderActivate()) 267 | { 268 | netloaderDeactivate(); 269 | uiExitState(); 270 | return; 271 | } 272 | 273 | while (datafd < 0) 274 | { 275 | if (wantExit) 276 | { 277 | netloaderDeactivate(); 278 | uiExitState(); 279 | return; 280 | } 281 | 282 | socklen_t fromlen = sizeof(sa_udp_remote); 283 | 284 | int len = recvfrom(udpfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&sa_udp_remote, &fromlen); 285 | if (len != -1 && strncmp(recvbuf, "3dsboot", 7) == 0) 286 | { 287 | sa_udp_remote.sin_family = AF_INET; 288 | sa_udp_remote.sin_port = htons(NETWORK_PORT); 289 | sendto(udpfd, "boot3ds", 7, 0, (struct sockaddr *)&sa_udp_remote, sizeof(sa_udp_remote)); 290 | } 291 | 292 | socklen_t addrlen = sizeof(struct sockaddr_in); 293 | datafd = accept(listenfd, (struct sockaddr *)&sa_udp_remote, &addrlen); 294 | if (datafd < 0) 295 | { 296 | if (errno != -EWOULDBLOCK && errno != EWOULDBLOCK) 297 | { 298 | netloaderError("accept", errno); 299 | return; 300 | } 301 | } 302 | else 303 | { 304 | close(listenfd); 305 | listenfd = -1; 306 | } 307 | 308 | svcSleepThread(16666666ULL); 309 | } 310 | 311 | int namelen; 312 | int len = recvall(datafd, &namelen, 4, 0); 313 | if (len != 4 || namelen >= (sizeof(recvbuf) + 1)) 314 | { 315 | netloaderError("namelen", errno); 316 | return; 317 | } 318 | 319 | len = recvall(datafd, recvbuf, namelen, 0); 320 | if (len != namelen) 321 | { 322 | netloaderError("name", errno); 323 | return; 324 | } 325 | 326 | recvbuf[namelen] = 0; 327 | len = recvall(datafd, (int *)&filelen, 4, 0); 328 | if (len != 4) 329 | { 330 | netloaderError("filelen", errno); 331 | return; 332 | } 333 | 334 | int response = 0; 335 | 336 | static menuEntry_s me; 337 | menuEntryInit(&me, ENTRY_TYPE_FILE); 338 | strncpy(me.path, "sdmc:/3ds/", sizeof(me.path) - 1); 339 | strncat(me.path, recvbuf, sizeof(me.path) - 1); 340 | me.path[sizeof(me.path) - 1] = 0; 341 | 342 | FILE *outf = fopen(me.path, "wb"); 343 | if (!outf) 344 | response = -1; 345 | send(datafd, &response, sizeof(response), 0); 346 | 347 | if (response < 0) 348 | { 349 | netloaderError("fopen", errno); 350 | return; 351 | } 352 | 353 | static char fbuf[64 * 1024]; 354 | setvbuf(outf, fbuf, _IOFBF, sizeof(fbuf)); 355 | len = receiveAndDecompress(datafd, outf, filelen); 356 | fclose(outf); 357 | 358 | if (len != Z_OK) 359 | return; 360 | 361 | send(datafd, &response, sizeof(response), 0); 362 | 363 | int cmdlen; 364 | len = recvall(datafd, &cmdlen, 4, 0); 365 | if (len == 4 && cmdlen <= sizeof(fbuf)) 366 | { 367 | len = recvall(datafd, fbuf, cmdlen, 0); 368 | if (len == cmdlen) 369 | { 370 | argData_s *ad = &me.args; 371 | ad->buf[0] = 0; 372 | ad->dst = (char *)&ad->buf[1]; 373 | 374 | char *ptr = fbuf; 375 | char *ptrend = fbuf + cmdlen; 376 | while (ptr < ptrend) 377 | ptr += launchAddArg(ad, ptr); 378 | } 379 | } 380 | 381 | uint32_t remote = sa_udp_remote.sin_addr.s_addr; 382 | if (remote) 383 | { 384 | char netlinked[18]; 385 | sprintf(netlinked, "%08" PRIx32 "_3DSLINK_", remote); 386 | launchAddArg(&me.args, netlinked); 387 | } 388 | 389 | netloaderDeactivate(); 390 | uiExitState(); 391 | launchMenuEntry(&me); 392 | } 393 | 394 | void netloaderUpdate(void) 395 | { 396 | if (wantExit || datafd >= 0) 397 | return; 398 | 399 | if (hidKeysDown() & KEY_B) 400 | wantExit = true; 401 | } 402 | 403 | void netloaderExit(void) 404 | { 405 | wantExit = true; 406 | } 407 | 408 | void netloaderDrawBot(void) 409 | { 410 | char buf[256]; 411 | const char *text = NULL; 412 | if (datafd < 0) 413 | { 414 | u32 ip = gethostid(); 415 | snprintf(buf, sizeof(buf), textGetString(StrId_NetLoaderActive), ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF, NETWORK_PORT); 416 | text = buf; 417 | } 418 | 419 | networkDrawBot(StrId_NetLoader, text, (datafd >= 0 && filelen), filelen, filetotal); 420 | } 421 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITARM)),) 6 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITARM)/3ds_rules 11 | 12 | #enable debug features 13 | export DEBUG ?= 0 14 | 15 | export VERSTRING := $(shell git describe --tags --match "v[0-9]*" --abbrev=7 | sed 's/-[0-9]*-g/-/') 16 | 17 | #--------------------------------------------------------------------------------- 18 | # TARGET is the name of the output 19 | # BUILD is the directory where object files & intermediate files will be placed 20 | # SOURCES is a list of directories containing source code 21 | # DATA is a list of directories containing data files 22 | # INCLUDES is a list of directories containing header files 23 | # GRAPHICS is a list of directories containing graphics files 24 | # GFXBUILD is the directory where converted graphics files will be placed 25 | # If set to $(BUILD), it will statically link in the converted 26 | # files as if they were data files. 27 | # 28 | # NO_SMDH: if set to anything, no SMDH file is generated. 29 | # ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) 30 | # APP_TITLE is the name of the app stored in the SMDH file (Optional) 31 | # APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) 32 | # APP_AUTHOR is the author of the app stored in the SMDH file (Optional) 33 | # ICON is the filename of the icon (.png), relative to the project folder. 34 | # If not set, it attempts to use one of the following (in this order): 35 | # - .png 36 | # - icon.png 37 | # - /default_icon.png 38 | #--------------------------------------------------------------------------------- 39 | TARGET := boot 40 | BUILD := build 41 | SOURCES := source source/ui source/parsing source/loaders 42 | DATA := data 43 | INCLUDES := include 44 | GRAPHICS := gfx 45 | ROMFS := romfs 46 | GFXBUILD := $(ROMFS)/gfx 47 | 48 | APP_TITLE := Homebrew Menu $(VERSTRING) 49 | APP_DESCRIPTION := Nintendo 3DS Homebrew Launcher 50 | APP_AUTHOR := Alexyo21, Cooolgamer, Devkitpro 51 | 52 | #--------------------------------------------------------------------------------- 53 | # options for code generation 54 | #--------------------------------------------------------------------------------- 55 | ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mfpu=vfpv2 -mtp=soft -marm -mthumb-interwork 56 | 57 | ifeq ($(DEBUG),1) 58 | OPTFLAGS := -Og -fno-fast-math 59 | LIBS := -lconfig -lcitro3d -lctrud -lm -lz -ltinyxml2 60 | UFLAGS := 61 | else 62 | OPTFLAGS := -O2 -fomit-frame-pointer -ffast-math 63 | LIBS := -lconfig -lcitro3d -lctru -lm -lz -ltinyxml2 64 | UFLAGS := -fno-rtti -fno-exceptions 65 | endif 66 | 67 | 68 | CFLAGS := -g -Wall -flto -mword-relocations \ 69 | -ftrivial-auto-var-init=zero -fzero-init-padding-bits=all -ffunction-sections \ 70 | $(OPTFLAGS) $(ARCH) 71 | 72 | CFLAGS += $(INCLUDE) -D__3DS__ -DVERSION=\"$(VERSTRING)\" 73 | 74 | CXXFLAGS := $(CFLAGS) -std=gnu++11 -flto 75 | 76 | CXXFLAGS += $(UFLAGS) 77 | 78 | ASFLAGS := -g -flto $(ARCH) 79 | LDFLAGS = -specs=3dsx.specs -g -flto $(ARCH) -Wl,-Map,$(notdir $*.map) 80 | 81 | # LIBS := -lconfig -lcitro3d $(LiBCTR) -lm -lz -ltinyxml2 82 | 83 | #--------------------------------------------------------------------------------- 84 | # list of directories containing libraries, this must be the top level containing 85 | # include and lib 86 | #--------------------------------------------------------------------------------- 87 | LIBDIRS := $(PORTLIBS) $(CTRULIB) 88 | 89 | 90 | #--------------------------------------------------------------------------------- 91 | # no real need to edit anything past this point unless you need to add additional 92 | # rules for different file extensions 93 | #--------------------------------------------------------------------------------- 94 | ifneq ($(BUILD),$(notdir $(CURDIR))) 95 | #--------------------------------------------------------------------------------- 96 | 97 | export OUTPUT := $(CURDIR)/$(TARGET) 98 | export TOPDIR := $(CURDIR) 99 | 100 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 101 | $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) \ 102 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 103 | 104 | export DEPSDIR := $(CURDIR)/$(BUILD) 105 | 106 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 107 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 108 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 109 | PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) 110 | SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) 111 | GFXFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.t3s))) 112 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 113 | 114 | #--------------------------------------------------------------------------------- 115 | # use CXX for linking C++ projects, CC for standard C 116 | #--------------------------------------------------------------------------------- 117 | ifeq ($(strip $(CPPFILES)),) 118 | #--------------------------------------------------------------------------------- 119 | export LD := $(CC) 120 | #--------------------------------------------------------------------------------- 121 | else 122 | #--------------------------------------------------------------------------------- 123 | export LD := $(CXX) 124 | #--------------------------------------------------------------------------------- 125 | endif 126 | #--------------------------------------------------------------------------------- 127 | 128 | #--------------------------------------------------------------------------------- 129 | ifeq ($(GFXBUILD),$(BUILD)) 130 | #--------------------------------------------------------------------------------- 131 | export T3XFILES := $(GFXFILES:.t3s=.t3x) 132 | #--------------------------------------------------------------------------------- 133 | else 134 | #--------------------------------------------------------------------------------- 135 | export ROMFS_T3XFILES := $(patsubst %.t3s, $(GFXBUILD)/%.t3x, $(GFXFILES)) 136 | export T3XHFILES := $(patsubst %.t3s, $(BUILD)/%.h, $(GFXFILES)) 137 | #--------------------------------------------------------------------------------- 138 | endif 139 | #--------------------------------------------------------------------------------- 140 | 141 | export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 142 | 143 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \ 144 | $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ 145 | $(addsuffix .o,$(T3XFILES)) 146 | 147 | export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) 148 | 149 | export HFILES := $(PICAFILES:.v.pica=_shbin.h) $(SHLISTFILES:.shlist=_shbin.h) \ 150 | $(addsuffix .h,$(subst .,_,$(BINFILES))) \ 151 | $(GFXFILES:.t3s=.h) 152 | 153 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 154 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 155 | -I$(CURDIR)/$(BUILD) 156 | 157 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 158 | 159 | export _3DSXDEPS := $(if $(NO_SMDH),,$(OUTPUT).smdh) 160 | 161 | ifeq ($(strip $(ICON)),) 162 | icons := $(wildcard *.png) 163 | ifneq (,$(findstring $(TARGET).png,$(icons))) 164 | export APP_ICON := $(TOPDIR)/$(TARGET).png 165 | else 166 | ifneq (,$(findstring icon.png,$(icons))) 167 | export APP_ICON := $(TOPDIR)/icon.png 168 | endif 169 | endif 170 | else 171 | export APP_ICON := $(TOPDIR)/$(ICON) 172 | endif 173 | 174 | ifeq ($(strip $(NO_SMDH)),) 175 | export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh 176 | endif 177 | 178 | ifneq ($(ROMFS),) 179 | export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) 180 | endif 181 | 182 | .PHONY: all clean 183 | 184 | #--------------------------------------------------------------------------------- 185 | all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) 186 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 187 | 188 | $(BUILD): 189 | @mkdir -p $@ 190 | 191 | ifneq ($(GFXBUILD),$(BUILD)) 192 | $(GFXBUILD): 193 | @mkdir -p $@ 194 | endif 195 | 196 | ifneq ($(DEPSDIR),$(BUILD)) 197 | $(DEPSDIR): 198 | @mkdir -p $@ 199 | endif 200 | 201 | #--------------------------------------------------------------------------------- 202 | clean: 203 | @echo clean ... 204 | @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf $(GFXBUILD) 205 | 206 | #--------------------------------------------------------------------------------- 207 | $(GFXBUILD)/%.t3x $(BUILD)/%.h : %.t3s 208 | #--------------------------------------------------------------------------------- 209 | @echo $(notdir $<) 210 | @tex3ds -i $< -H $(BUILD)/$*.h -d $(DEPSDIR)/$*.d -o $(GFXBUILD)/$*.t3x 211 | 212 | #--------------------------------------------------------------------------------- 213 | else 214 | 215 | #--------------------------------------------------------------------------------- 216 | # main targets 217 | #--------------------------------------------------------------------------------- 218 | $(OUTPUT).3dsx : $(OUTPUT).elf $(_3DSXDEPS) 219 | 220 | $(OFILES_SOURCES) : $(HFILES) 221 | 222 | $(OUTPUT).elf : $(OFILES) 223 | 224 | #--------------------------------------------------------------------------------- 225 | # you need a rule like this for each extension you use as binary data 226 | #--------------------------------------------------------------------------------- 227 | %.bin.o %_bin.h : %.bin 228 | #--------------------------------------------------------------------------------- 229 | @echo $(notdir $<) 230 | @$(bin2o) 231 | 232 | #--------------------------------------------------------------------------------- 233 | .PRECIOUS : %.t3x %.shbin 234 | #--------------------------------------------------------------------------------- 235 | %.t3x.o %_t3x.h : %.t3x 236 | #--------------------------------------------------------------------------------- 237 | @echo $(notdir $<) 238 | @$(bin2o) 239 | 240 | #--------------------------------------------------------------------------------- 241 | %.shbin.o %_shbin.h : %.shbin 242 | #--------------------------------------------------------------------------------- 243 | $(SILENTMSG) $(notdir $<) 244 | $(bin2o) 245 | 246 | #--------------------------------------------------------------------------------- 247 | %.t3x %.h : %.t3s 248 | #--------------------------------------------------------------------------------- 249 | @echo $(notdir $<) 250 | @tex3ds -i $< -H $*.h -d $*.d -o $*.t3x 251 | 252 | -include $(DEPSDIR)/*.d 253 | 254 | #--------------------------------------------------------------------------------------- 255 | endif 256 | #--------------------------------------------------------------------------------------- 257 | -------------------------------------------------------------------------------- /source/drawing.c: -------------------------------------------------------------------------------- 1 | #include "drawing.h" 2 | #include "program_shbin.h" 3 | 4 | // Global variables 5 | imageInfo_s* g_imageData; 6 | shaderProgram_s g_drawProg, g_waveProg; 7 | u8 uLoc_projection; 8 | u8 uLoc_gradient; 9 | C3D_AttrInfo g_drawAttrInfo; 10 | C3D_BufInfo g_drawBufInfo; 11 | u32 g_drawFrames = 1; 12 | 13 | // Static variables 14 | static DVLB_s* s_programBin; 15 | static C3D_RenderTarget* s_targets[3]; 16 | static C3D_Mtx s_projectionTop, s_projectionBot; 17 | static DrawingMode s_drawingMode; 18 | static drawVertex_s* s_drawBuffer; 19 | static int s_drawBufferPos; 20 | static float s_drawBufferZ = 0.5; 21 | static C3D_Tex* s_curTex; 22 | static C3D_Tex s_imagesTex; 23 | static Tex3DS_Texture s_imagesTexInfo; 24 | static float s_screenWidth; 25 | static float s_brightnessLevel; 26 | static float s_brightnessFade = 2.5f / 60; 27 | static bool s_drew; 28 | static u32 s_lastFrame; 29 | 30 | #define CLEAR_COLOR 0x68B0D8FF 31 | 32 | #define DISPLAY_TRANSFER_FLAGS \ 33 | (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | \ 34 | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | \ 35 | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) 36 | 37 | void lzssDecompress(const void *in, void *out, u32 size); 38 | 39 | static void loadImages(void) 40 | { 41 | FILE* f = fopen("romfs:/gfx/images.t3x", "rb"); 42 | if (!f) svcBreak(USERBREAK_PANIC); 43 | s_imagesTexInfo = Tex3DS_TextureImportStdio(f, &s_imagesTex, NULL, false); 44 | fclose(f); 45 | if (!s_imagesTexInfo) svcBreak(USERBREAK_PANIC); 46 | 47 | size_t numSubTex = Tex3DS_GetNumSubTextures(s_imagesTexInfo); 48 | g_imageData = (imageInfo_s*)malloc(numSubTex*sizeof(imageInfo_s)); 49 | if (!g_imageData) svcBreak(USERBREAK_PANIC); 50 | 51 | for (size_t i = 0; i < numSubTex; i ++) 52 | { 53 | const Tex3DS_SubTexture* subtex = Tex3DS_GetSubTexture(s_imagesTexInfo, i); 54 | g_imageData[i].width = subtex->width; 55 | g_imageData[i].height = subtex->height; 56 | } 57 | } 58 | 59 | void drawingInit(void) 60 | { 61 | gfxInitDefault(); 62 | gfxSet3D(true); 63 | C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); 64 | 65 | // Load programs 66 | s_programBin = DVLB_ParseFile((u32*)program_shbin, program_shbin_size); 67 | shaderProgramInit(&g_drawProg); 68 | shaderProgramSetVsh(&g_drawProg, &s_programBin->DVLE[0]); 69 | shaderProgramInit(&g_waveProg); 70 | shaderProgramSetVsh(&g_waveProg, &s_programBin->DVLE[1]); 71 | 72 | // Load uniform positions 73 | uLoc_projection = shaderInstanceGetUniformLocation(g_drawProg.vertexShader, "projection"); 74 | uLoc_gradient = shaderInstanceGetUniformLocation(g_waveProg.vertexShader, "gradient"); 75 | 76 | // Create vertex buffer 77 | s_drawBuffer = (drawVertex_s*)linearAlloc(sizeof(drawVertex_s)*DRAWING_MAX_VERTICES); 78 | 79 | // Configure attribute informations 80 | AttrInfo_Init(&g_drawAttrInfo); 81 | AttrInfo_AddLoader(&g_drawAttrInfo, 0, GPU_FLOAT, 3); // v0=position 82 | AttrInfo_AddLoader(&g_drawAttrInfo, 1, GPU_FLOAT, 2); // v1=texcoord 83 | 84 | // Configure buffer informations 85 | BufInfo_Init(&g_drawBufInfo); 86 | BufInfo_Add(&g_drawBufInfo, s_drawBuffer, sizeof(drawVertex_s), 2, 0x10); 87 | 88 | // Create rendering targets 89 | s_targets[0] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH16); 90 | s_targets[1] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH16); 91 | s_targets[2] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH16); 92 | C3D_RenderTargetSetOutput(s_targets[0], GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); 93 | C3D_RenderTargetSetOutput(s_targets[1], GFX_TOP, GFX_RIGHT, DISPLAY_TRANSFER_FLAGS); 94 | C3D_RenderTargetSetOutput(s_targets[2], GFX_BOTTOM, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); 95 | 96 | // Precalc stuff 97 | Mtx_OrthoTilt(&s_projectionTop, 0.0, 400.0, 240.0, 0.0, 0.0, 1.0, true); 98 | Mtx_OrthoTilt(&s_projectionBot, 0.0, 320.0, 240.0, 0.0, 0.0, 1.0, true); 99 | loadImages(); 100 | } 101 | 102 | void drawingExit(void) 103 | { 104 | // Free the images 105 | free(g_imageData); 106 | C3D_TexDelete(&s_imagesTex); 107 | Tex3DS_TextureFree(s_imagesTexInfo); 108 | 109 | // Free the shader programs 110 | shaderProgramFree(&g_waveProg); 111 | shaderProgramFree(&g_drawProg); 112 | DVLB_Free(s_programBin); 113 | 114 | // Deinitialize graphics 115 | C3D_Fini(); 116 | gfxExit(); 117 | } 118 | 119 | void drawingSetFade(float fade) 120 | { 121 | s_brightnessFade = fade; 122 | s_brightnessLevel = fade > 0.0f ? 0.0f : 1.0f; 123 | } 124 | 125 | void drawingEnableDepth(bool enable) 126 | { 127 | // Configure depth test to overwrite pixels with the same depth (needed to draw overlapping graphics) 128 | C3D_DepthTest(enable, enable ? GPU_GEQUAL : GPU_ALWAYS, GPU_WRITE_ALL); 129 | } 130 | 131 | void drawingSetMode(DrawingMode mode) 132 | { 133 | if (mode == s_drawingMode) return; 134 | s_drawingMode = mode; 135 | switch (mode) 136 | { 137 | case DRAW_MODE_DRAWING: 138 | C3D_BindProgram(&g_drawProg); 139 | C3D_SetAttrInfo(&g_drawAttrInfo); 140 | C3D_SetBufInfo(&g_drawBufInfo); 141 | break; 142 | case DRAW_MODE_WAVE: 143 | C3D_BindProgram(&g_waveProg); 144 | C3D_SetAttrInfo(&g_drawAttrInfo); 145 | C3D_SetBufInfo(&g_drawBufInfo); 146 | default: 147 | break; 148 | } 149 | } 150 | 151 | void drawingSetZ(float z) 152 | { 153 | s_drawBufferZ = z; 154 | } 155 | 156 | void drawingSetTex(C3D_Tex* tex) 157 | { 158 | if (tex == s_curTex) return; 159 | s_curTex = tex; 160 | C3D_TexBind(0, tex); 161 | } 162 | 163 | void drawingSetGradient(unsigned which, float r, float g, float b, float a) 164 | { 165 | C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_gradient+which, r, g, b, a); 166 | } 167 | 168 | void drawingWithVertexColor(void) 169 | { 170 | C3D_TexEnv* env = C3D_GetTexEnv(0); 171 | C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, 0, 0); 172 | C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); 173 | } 174 | 175 | void drawingWithColor(u32 color) 176 | { 177 | C3D_TexEnv* env = C3D_GetTexEnv(0); 178 | C3D_TexEnvSrc(env, C3D_Both, GPU_CONSTANT, 0, 0); 179 | C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); 180 | C3D_TexEnvColor(env, color); 181 | } 182 | 183 | void drawingWithTex(C3D_Tex* tex, u32 color) 184 | { 185 | drawingSetTex(tex); 186 | C3D_TexEnv* env = C3D_GetTexEnv(0); 187 | C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_CONSTANT, 0); 188 | C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); 189 | C3D_TexEnvColor(env, color); 190 | } 191 | 192 | void drawingAddVertex(float vx, float vy, float tx, float ty) 193 | { 194 | drawVertex_s* vtx = &s_drawBuffer[s_drawBufferPos++]; 195 | vtx->position[0] = vx; 196 | vtx->position[1] = vy; 197 | vtx->position[2] = s_drawBufferZ; 198 | vtx->texcoord[0] = tx; 199 | vtx->texcoord[1] = ty; 200 | } 201 | 202 | void drawingSubmitPrim(GPU_Primitive_t prim, int vertices) 203 | { 204 | C3D_DrawArrays(prim, s_drawBufferPos-vertices, vertices); 205 | } 206 | 207 | static void drawingFade(float width, float height) 208 | { 209 | if (s_brightnessLevel >= 1.0f) return; 210 | drawingWithColor(((u32)((1-s_brightnessLevel)*255)) << 24); 211 | drawingDrawQuad(0, 0, width, height); 212 | } 213 | 214 | static void drawingTopScreen(float iod) 215 | { 216 | const uiStateInfo_s* ui; 217 | 218 | // Set projection matrix 219 | if (iod <= 0.0f) 220 | C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &s_projectionTop); 221 | 222 | // Draw background 223 | ui = g_uiStateBg; 224 | if (ui->drawTop) ui->drawTop(iod); 225 | 226 | // Draw current state 227 | ui = uiGetStateInfo(); 228 | if (ui->drawTop) ui->drawTop(iod); 229 | 230 | /* 231 | // Draw debug text 232 | static char debugText[128]; 233 | snprintf(debugText, sizeof(debugText), "Cmdbuf usage: %d%%", (int)(C3D_GetCmdBufUsage()*100)); 234 | */ 235 | 236 | drawingSetMode(DRAW_MODE_DRAWING); 237 | drawingSetZ(0.0f); 238 | textSetColor(0xFFFFFFFF); 239 | //textDraw(8.0f, 32.0f, 0.5f, 0.5f, true, debugText); 240 | 241 | // Draw fade 242 | drawingFade(400, 240); 243 | } 244 | 245 | static void drawingBottomScreen(void) 246 | { 247 | const uiStateInfo_s* ui; 248 | 249 | // Set projection matrix 250 | C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &s_projectionBot); 251 | 252 | // Draw background 253 | ui = g_uiStateBg; 254 | if (ui->drawBot) ui->drawBot(); 255 | 256 | // Draw current state 257 | ui = uiGetStateInfo(); 258 | if (ui->drawBot) ui->drawBot(); 259 | 260 | if (uiIsBusy()) 261 | { 262 | // Draw 'busy' icon 263 | int i; 264 | static float counter = 0; 265 | 266 | drawingSetMode(DRAW_MODE_DRAWING); 267 | drawingSetZ(0.0f); 268 | 269 | drawingWithColor(0x80FFFFFF); 270 | drawingDrawQuad(0.0f, 80.0f, 320.0f, 100.0f); 271 | 272 | for (i = 0; i < 8; i ++) 273 | { 274 | float angle = ((float)i/8.0f + counter)*M_TAU; 275 | float x = 320.0f/2 + 24*cosf(angle); 276 | float y = 240.0f/2 + 24*sinf(angle); 277 | drawingDrawImage(images_loading_idx, 0xFFFFFFFF, x-4, y-4); 278 | } 279 | counter += g_drawFrames*0.5f/60; 280 | 281 | textSetColor(0xFF000000); // black 282 | textDrawInBox(textGetString(StrId_Loading), 0, 0.5f, 0.5f, 170.f, 0.0f, 320.0f); 283 | } 284 | 285 | // Draw fade 286 | drawingFade(320, 240); 287 | } 288 | 289 | void drawingFrame(void) 290 | { 291 | float slider = osGet3DSliderState(); 292 | float iod = slider/3; 293 | 294 | C3D_FrameBegin(C3D_FRAME_SYNCDRAW); 295 | u32 frame = C3D_FrameCounter(0); 296 | if (s_drew) 297 | g_drawFrames = frame-s_lastFrame; 298 | else 299 | s_drew = true; 300 | s_lastFrame = frame; 301 | s_drawBufferPos = 0; 302 | 303 | // Update brightness level 304 | s_brightnessLevel += s_brightnessFade*g_drawFrames; 305 | if (s_brightnessLevel < 0.0f) 306 | { 307 | s_brightnessLevel = 0.0f; 308 | s_brightnessFade = 0.0f; 309 | } else if (s_brightnessLevel >= 1.0f) 310 | { 311 | s_brightnessLevel = 1.0f; 312 | s_brightnessFade = 0.0f; 313 | } 314 | 315 | // Top screen 316 | s_screenWidth = 400.0f; 317 | C3D_FrameDrawOn(s_targets[0]); 318 | drawingTopScreen(-iod); 319 | if (iod > 0.0f) 320 | { 321 | C3D_FrameDrawOn(s_targets[1]); 322 | drawingTopScreen(iod); 323 | } 324 | 325 | // Bottom screen 326 | s_screenWidth = 320.0f; 327 | C3D_FrameDrawOn(s_targets[2]); 328 | drawingBottomScreen(); 329 | 330 | C3D_FrameEnd(0); 331 | } 332 | 333 | void drawingDrawQuad(float x, float y, float w, float h) 334 | { 335 | drawingAddVertex(x, y+h, 0.0f, 0.0f); 336 | drawingAddVertex(x+w, y+h, 0.75f, 0.0f); 337 | drawingAddVertex(x, y, 0.0f, 0.75f); 338 | drawingAddVertex(x+w, y, 0.75f, 0.75f); 339 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 340 | } 341 | 342 | void drawingDrawImage(ImageId id, u32 color, float x, float y) 343 | { 344 | const imageInfo_s* p = &g_imageData[id]; 345 | drawingWithTex(&s_imagesTex, color); 346 | 347 | // Calculate texcoords 348 | float tcTopLeft[2], tcTopRight[2], tcBotLeft[2], tcBotRight[2]; 349 | const Tex3DS_SubTexture* subtex = Tex3DS_GetSubTexture(s_imagesTexInfo, id); 350 | Tex3DS_SubTextureBottomLeft (subtex, &tcBotLeft[0], &tcBotLeft[1]); 351 | Tex3DS_SubTextureBottomRight(subtex, &tcBotRight[0], &tcBotRight[1]); 352 | Tex3DS_SubTextureTopLeft (subtex, &tcTopLeft[0], &tcTopLeft[1]); 353 | Tex3DS_SubTextureTopRight (subtex, &tcTopRight[0], &tcTopRight[1]); 354 | 355 | drawingAddVertex(x, y+p->height, tcBotLeft[0], tcBotLeft[1]); 356 | drawingAddVertex(x+p->width, y+p->height, tcBotRight[0], tcBotRight[1]); 357 | drawingAddVertex(x, y, tcTopLeft[0], tcTopLeft[1]); 358 | drawingAddVertex(x+p->width, y, tcTopRight[0], tcTopRight[1]); 359 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 360 | } 361 | 362 | void drawingDrawWave(float* points, u32 num_points, float x, float width, float dy_top, float dy_bot) 363 | { 364 | for (u32 i = 0; i < num_points; i ++) 365 | { 366 | float px = x + width * i / (num_points-1); 367 | drawingAddVertex(px, points[i] + dy_top, 0.0f, 0.0f); 368 | drawingAddVertex(px, points[i] + dy_bot, 1.0f, 0.0f); 369 | } 370 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 2*num_points); 371 | } 372 | -------------------------------------------------------------------------------- /source/ui/menu.c: -------------------------------------------------------------------------------- 1 | #include "menu.h" 2 | 3 | #include "netloader.h" 4 | #include "netsender.h" 5 | 6 | static bool showingHomeIcon; 7 | static float homeIconStatus; 8 | static u32 storedButtons; 9 | 10 | char rootPathBase[PATH_MAX]; 11 | char rootPath[PATH_MAX + 8]; 12 | 13 | static void changeDirTask(void *arg) 14 | { 15 | menuScan((const char *)arg); 16 | uiEnterState(UI_STATE_MENU); 17 | } 18 | 19 | static void launchMenuEntryTask(void *arg) 20 | { 21 | menuEntry_s *me = (menuEntry_s *)arg; 22 | if (me->type == ENTRY_TYPE_FOLDER) 23 | changeDirTask(me->path); 24 | else 25 | launchMenuEntry(me); 26 | } 27 | 28 | static void toggleStarTask(void *arg) 29 | { 30 | menuEntry_s *me = (menuEntry_s *)arg; 31 | menuToggleStar(me); 32 | uiEnterState(UI_STATE_MENU); 33 | } 34 | 35 | char *menuGetRootBasePath(void) 36 | { 37 | return rootPathBase; 38 | } 39 | 40 | void menuStartupPath(void) 41 | { 42 | char temp[PATH_MAX + 29]; 43 | 44 | #if defined(__3DS__) 45 | strncpy(rootPathBase, "sdmc:", sizeof(rootPathBase) - 1); 46 | #else 47 | getcwd(rootPathBase, sizeof(rootPathBase)); 48 | #endif 49 | 50 | snprintf(rootPath, sizeof(rootPath) - 1, "%s%s%s", rootPathBase, DIRECTORY_SEPARATOR, "3ds"); 51 | 52 | struct stat fileStat = {0}; 53 | 54 | /* create the root directory */ 55 | if (stat(rootPath, &fileStat) == -1) 56 | mkdir(rootPath, 0755); 57 | 58 | /* create the /config directory */ 59 | snprintf(temp, sizeof(temp) - 1, "%s/config", rootPathBase); 60 | mkdir(temp, 0755); 61 | 62 | /* create /config/3ds-hbmenu directory */ 63 | snprintf(temp, sizeof(temp) - 1, "%s/config/3ds-hbmenu", rootPathBase); 64 | mkdir(temp, 0755); 65 | 66 | snprintf(temp, sizeof(temp) - 1, "%s/config/3ds-hbmenu/fileassoc", rootPathBase); 67 | 68 | /* create /config/3ds-hbmenu/fileassoc */ 69 | if (stat(temp, &fileStat) == -1) 70 | mkdir(temp, 0755); 71 | } 72 | 73 | void menuLoadFileAssoc(void) 74 | { 75 | char temp[PATH_MAX + 29]; 76 | 77 | memset(temp, 0, sizeof(temp) - 1); 78 | snprintf(temp, sizeof(temp) - 1, "%s/config/3ds-hbmenu/fileassoc", rootPathBase); 79 | 80 | menuFileAssocScan(temp); 81 | } 82 | 83 | static float menuGetScrollHeight(menu_s *menu) 84 | { 85 | float ret = 4.0f + menu->nEntries * (4.0f + g_imageData[images_appbubble_idx].height) - 240.0f; 86 | if (ret < 0.0f) 87 | ret = 0.0f; 88 | return ret; 89 | } 90 | 91 | static int iabs(int x) 92 | { 93 | return x < 0 ? (-x) : x; 94 | } 95 | 96 | static float menuGetEntryPos(menu_s *menu, int order) 97 | { 98 | float ret = order * (4.0f + g_imageData[images_appbubble_idx].height); 99 | if (order > menu->curEntry) 100 | ret += 4.0f; 101 | return ret; 102 | } 103 | 104 | static void menuUpdateAnimation(menu_s *menu, float maxScroll, float val) 105 | { 106 | if (menu->perturbed) 107 | { 108 | menu->scrollTarget = menuGetEntryPos(menu, menu->curEntry) - menu->scrollLocation; 109 | float borderBot = 240.0f - g_imageData[images_appbubble_idx].height - 4; 110 | if (menu->scrollTarget > borderBot || (maxScroll > 0.0f && menu->curEntry == (menu->nEntries - 1))) 111 | menu->scrollVelocity += (menu->scrollTarget - borderBot) / SCROLLING_SPEED; 112 | if (menu->scrollTarget < 0.0f || (maxScroll > 0.0f && menu->curEntry == 0)) 113 | menu->scrollVelocity += menu->scrollTarget / SCROLLING_SPEED; 114 | } 115 | else if (maxScroll > 0.0f) 116 | { 117 | if (menu->scrollLocation >= maxScroll) 118 | { 119 | menu->scrollVelocity += (maxScroll - menu->scrollLocation) / SCROLLING_SPEED; 120 | if (val > 0.0f) 121 | menu->scrollVelocity -= val; 122 | } 123 | else if (menu->scrollLocation < 0.0f) 124 | { 125 | menu->scrollVelocity -= menu->scrollLocation / SCROLLING_SPEED; 126 | if (val < 0.0f) 127 | menu->scrollVelocity -= val; 128 | } 129 | else 130 | menu->scrollVelocity -= val; 131 | } 132 | 133 | menu->scrollLocation += menu->scrollVelocity; 134 | menu->scrollVelocity *= 0.75f; 135 | if (fabs(menu->scrollVelocity) < (1.0f / 1024)) 136 | { 137 | menu->scrollVelocity = 0.0f; 138 | menu->perturbed = false; 139 | } 140 | } 141 | 142 | void menuUpdate(void) 143 | { 144 | static bool firstStart = true; 145 | 146 | menu_s *menu = menuGetCurrent(); 147 | u32 down = hidKeysDown() | (hidKeysDownRepeat() & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT)); 148 | u32 held = hidKeysHeld(); 149 | u32 up = hidKeysUp(); 150 | 151 | if (firstStart && isDebugMode()) 152 | { 153 | workerSchedule(netloaderTask, NULL); 154 | } 155 | 156 | // Avoid registering a SELECT press with the Rosalina menu combo 157 | // See https://github.com/mtheall/ftpd/commit/0db916db66ced76b7e4d05a0407189224c070ad0 158 | bool selectPress = false; 159 | if (down == KEY_SELECT && held == KEY_SELECT) 160 | storedButtons = KEY_SELECT; 161 | else if (up & KEY_SELECT) 162 | { 163 | if (storedButtons == KEY_SELECT) 164 | selectPress = true; 165 | } 166 | else 167 | storedButtons |= held; 168 | 169 | bool pressedSettings = (down & KEY_TOUCH) && g_touchPos.px >= (320 - g_imageData[images_settings_idx].width) && g_touchPos.py >= (240 - g_imageData[images_settings_idx].height); 170 | if (down & KEY_A) 171 | { 172 | if (menu->nEntries > 0) 173 | { 174 | int i; 175 | menuEntry_s *me; 176 | for (i = 0, me = menu->firstEntry; i != menu->curEntry; i++, me = me->next) 177 | ; 178 | workerSchedule(launchMenuEntryTask, me); 179 | } 180 | } 181 | else if (down & KEY_B) 182 | { 183 | workerSchedule(changeDirTask, ".."); 184 | } 185 | else if ((down & KEY_START) || pressedSettings) 186 | { 187 | if (loaderHasFlag(LOADER_SHOW_REBOOT)) 188 | uiEnterState(UI_STATE_REBOOT); 189 | else 190 | showingHomeIcon = true; 191 | } 192 | else if (down & KEY_Y) 193 | { 194 | workerSchedule(netloaderTask, NULL); 195 | } 196 | else if (down & KEY_X) 197 | { 198 | int i; 199 | menuEntry_s *me; 200 | for (i = 0, me = menu->firstEntry; i != menu->curEntry; i++, me = me->next) 201 | ; 202 | if (me->type == ENTRY_TYPE_FILE) 203 | workerSchedule(netsenderTask, me); 204 | } 205 | else if (selectPress && menu->nEntries > 0) 206 | { 207 | int i; 208 | menuEntry_s *me; 209 | for (i = 0, me = menu->firstEntry; i != menu->curEntry; i++, me = me->next) 210 | ; 211 | if (me->type == ENTRY_TYPE_FILE) 212 | workerSchedule(toggleStarTask, me); 213 | } 214 | else if (menu->nEntries > 0) 215 | { 216 | u32 i; 217 | int move = 0; 218 | 219 | float val = g_cstickPos.dy * 1.0f / 64; 220 | 221 | if (down & KEY_UP) 222 | move--; 223 | if (down & KEY_DOWN) 224 | move++; 225 | if (down & KEY_LEFT) 226 | move -= 4; 227 | if (down & KEY_RIGHT) 228 | move += 4; 229 | 230 | int oldEntry = menu->curEntry; 231 | 232 | if (down & KEY_TOUCH) 233 | { 234 | menu->touchTimer = 0; 235 | menu->firstTouch = g_touchPos; 236 | } 237 | else if (held & KEY_TOUCH) 238 | { 239 | val += (g_touchPos.py - menu->previousTouch.py) * 16.0f / 64; 240 | menu->touchTimer += drawingGetFrames(); 241 | } 242 | else if ((up & KEY_TOUCH) && menu->touchTimer < 30 && (iabs(menu->firstTouch.px - menu->previousTouch.px) + iabs(menu->firstTouch.py - menu->previousTouch.py)) < 12) 243 | { 244 | menuEntry_s *me, *me_sel = NULL; 245 | float location = menu->scrollLocation + menu->previousTouch.py - 4; 246 | 247 | for (i = 0, me = menu->firstEntry; me; i++, me = me->next) 248 | { 249 | if (menuGetEntryPos(menu, i) > location) 250 | break; 251 | me_sel = me; 252 | } 253 | 254 | if (!me_sel) 255 | { 256 | me_sel = menu->firstEntry; 257 | i = 1; 258 | } 259 | 260 | if (menu->curEntry == (i - 1)) 261 | { 262 | workerSchedule(launchMenuEntryTask, me_sel); 263 | return; 264 | } 265 | 266 | menu->curEntry = i - 1; 267 | } 268 | 269 | int newEntry = menu->curEntry + move; 270 | if (newEntry < 0) 271 | newEntry = 0; 272 | if (newEntry >= menu->nEntries) 273 | newEntry = menu->nEntries - 1; 274 | menu->curEntry = newEntry; 275 | 276 | if (oldEntry != newEntry) 277 | menu->perturbed = true; 278 | 279 | menu->previousTouch = g_touchPos; 280 | for (i = drawingGetFrames(); i; i--) 281 | menuUpdateAnimation(menu, menuGetScrollHeight(menu), val); 282 | } 283 | firstStart = false; 284 | } 285 | 286 | void menuDrawTop(float iod) 287 | { 288 | menu_s *menu = menuGetCurrent(); 289 | 290 | drawingSetMode(DRAW_MODE_DRAWING); 291 | drawingSetZ(0.4f); 292 | 293 | drawingWithColor(0x60FFFFFF); 294 | drawingDrawQuad(0.0f, 240 - 24.0f, 400.0f, 24.0f); 295 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 296 | 297 | textSetColor(0xFF545454); 298 | textDrawInBox(menu->dirname, 0, 0.5f, 0.5f, 240.0f - 8, 8.0f, 400 - 8.0f); 299 | } 300 | 301 | float menuDrawEntry(menuEntry_s *me, float x, float y, bool selected) 302 | { 303 | float bubbleWidth = g_imageData[images_appbubble_idx].width; 304 | float bubbleHeight = g_imageData[images_appbubble_idx].height; 305 | 306 | float height = bubbleHeight + 4.0f; 307 | if (selected) 308 | { 309 | height += 4.0f; 310 | x -= 4.0f; 311 | } 312 | 313 | if ((y + height) <= 0.0f || y >= 240.f) 314 | return height; 315 | 316 | if (selected) 317 | drawingDrawImage(images_appbubble_idx, 0x80808080, x, y + 4); 318 | drawingDrawImage(images_appbubble_idx, !me->isStarred ? 0xFFFFFFFF : 0xFFD0FFFF, x, y); 319 | 320 | if (me->icon) 321 | { 322 | drawingWithTex(me->icon, 0xFFFFFFFF); 323 | drawingDrawQuad(x + 8, y + 8, 48, 48); 324 | } 325 | else if (me->type == ENTRY_TYPE_FOLDER) 326 | drawingDrawImage(images_folderIcon_idx, 0xFFFFFFFF, x + 8, y + 8); 327 | else 328 | drawingDrawImage(images_defaultIcon_idx, 0xFFFFFFFF, x + 8, y + 8); 329 | 330 | if (me->isStarred) 331 | { 332 | float starWidth = g_imageData[images_star_idx].width; 333 | float starHeight = g_imageData[images_star_idx].height; 334 | drawingDrawImage(images_star_idx, 0xFFFFFFFF, x - 0.25f * starWidth, y + bubbleHeight - 0.75 * starHeight); 335 | } 336 | 337 | textSetColor(0xFF545454); 338 | textDrawInBox(me->name, -1, 0.5f, 0.5f, y + 20, x + 66, x + bubbleWidth - 8); 339 | textDrawInBox(me->description, -1, 0.4f, 0.4f, y + 35, x + 66 + 4, x + bubbleWidth - 8); 340 | textDrawInBox(me->author, 1, 0.4f, 0.4f, y + bubbleHeight - 6, x + 66, x + bubbleWidth - 8); 341 | 342 | return height; 343 | } 344 | 345 | static float calcScrollbarKnobPos(menu_s *menu, float totalHeight) 346 | { 347 | totalHeight -= 240.0f; 348 | if (totalHeight < 0.0f) 349 | return 5.0f; 350 | float trackHeight = g_imageData[images_scrollbarTrack_idx].height - g_imageData[images_scrollbarKnob_idx].height; 351 | float curPos = menu->scrollLocation; 352 | if (curPos < 0.0f) 353 | curPos = 0.0f; 354 | else if (curPos >= totalHeight) 355 | curPos = totalHeight; 356 | return 5.0f + curPos * trackHeight / totalHeight; 357 | } 358 | 359 | void menuDrawBot(void) 360 | { 361 | int i; 362 | menuEntry_s *me; 363 | menu_s *menu = menuGetCurrent(); 364 | 365 | drawingSetMode(DRAW_MODE_DRAWING); 366 | drawingSetZ(0.4f); 367 | 368 | if (menu->nEntries == 0) 369 | { 370 | drawingWithColor(0x80FFFFFF); 371 | drawingDrawQuad(0.0f, 60.0f, 320.0f, 120.0f); 372 | drawingSubmitPrim(GPU_TRIANGLE_STRIP, 4); 373 | 374 | textSetColor(0xFF545454); 375 | textDrawInBox(textGetString(StrId_NoAppsFound_Title), 0, 0.75f, 0.75f, 60.0f + 25.0f, 8.0f, 320 - 8.0f); 376 | textDraw(8.0f, 60.0f + 25.0f + 8.0f, 0.5f, 0.5f, false, textGetString(StrId_NoAppsFound_Msg)); 377 | } 378 | else 379 | { 380 | // Draw menu entries 381 | float y = 0.0; 382 | float loc = floorf(menu->scrollLocation); 383 | for (me = menu->firstEntry, i = 0; me; me = me->next, i++) 384 | y += menuDrawEntry(me, 9.0f, 4 + y - loc, i == menu->curEntry); 385 | 386 | // Draw scrollbar 387 | drawingDrawImage(images_scrollbarTrack_idx, 0xFFFFFFFF, 308.0f, 5.0f); 388 | drawingDrawImage(images_scrollbarKnob_idx, 0xFFFFFFFF, 308.0f, calcScrollbarKnobPos(menu, y)); 389 | } 390 | 391 | drawingDrawImage(images_settings_idx, 0xFFFFFFFF, 320.0f - g_imageData[images_settings_idx].width, 240.0f - g_imageData[images_settings_idx].height); 392 | 393 | if (showingHomeIcon) 394 | { 395 | const float maxOpac = 0.75f; 396 | homeIconStatus += 1.0 / 32; 397 | float opac = 0.0; 398 | if (homeIconStatus < 1.0f) 399 | opac = maxOpac * sqrtf(homeIconStatus); 400 | else if (homeIconStatus < 2.0f) 401 | opac = maxOpac; 402 | else if (homeIconStatus < 3.0f) 403 | opac = maxOpac * sqrtf(3.0f - homeIconStatus); 404 | if (opac > 0.0f) 405 | { 406 | textSetColor((u32)(opac * 0x100) << 24); 407 | // Check for New 2DS XL 408 | if (g_systemModel == 5) 409 | textDraw(0.0f, 200.0f, 1.0f, 1.0f, true, "\n\xEE\x80\x9A \xEE\x81\xB3"); 410 | else 411 | textDrawInBox("\xEE\x81\xB3\n▼", 0, 1.0f, 1.0f, 200.0f, 0.0f, 320.0f); 412 | } 413 | else 414 | { 415 | showingHomeIcon = false; 416 | homeIconStatus = 0.0f; 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /source/ui/netsender.c: -------------------------------------------------------------------------------- 1 | #include "netsender.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #define ZLIB_CHUNK (16 * 1024) 13 | 14 | typedef u32 in_addr_t; 15 | 16 | static int datafd = -1; 17 | static struct in_addr dsaddr; 18 | static LightEvent event; 19 | static volatile size_t filelen, filetotal; 20 | static volatile bool wantExit = false; 21 | static volatile bool doneSearching = false; 22 | static volatile bool errored = false; 23 | #define RECEIVER_IP_LENGTH 15 24 | static char receiverIp[RECEIVER_IP_LENGTH+1]; 25 | 26 | static void netsenderError(const char* func, int err); 27 | 28 | static bool netsenderInit(void) 29 | { 30 | return networkInit(); 31 | } 32 | 33 | static void netsenderDeactivate(void) 34 | { 35 | networkDeactivate(); 36 | } 37 | 38 | void netsenderError(const char* func, int err) 39 | { 40 | errored = true; 41 | netsenderDeactivate(); 42 | networkError(netsenderUpdate, StrId_NetSender, func, err); 43 | } 44 | 45 | /*--------------------------------------------------------------------------------- 46 | Subtract the `struct timeval' values Y from X, 47 | storing the result in RESULT. 48 | Return 1 if the difference is negative, otherwise 0. 49 | 50 | From http://www.gnu.org/software/libtool/manual/libc/Elapsed-Time.html 51 | ---------------------------------------------------------------------------------*/ 52 | static int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y) 53 | { 54 | struct timeval tmp; 55 | tmp.tv_sec = y->tv_sec; 56 | tmp.tv_usec = y->tv_usec; 57 | 58 | // Perform the carry for the later subtraction by updating y. 59 | if (x->tv_usec < tmp.tv_usec) 60 | { 61 | int nsec = (tmp.tv_usec - x->tv_usec) / 1000000 + 1; 62 | tmp.tv_usec -= 1000000 * nsec; 63 | tmp.tv_sec += nsec; 64 | } 65 | 66 | if (x->tv_usec - tmp.tv_usec > 1000000) 67 | { 68 | int nsec = (x->tv_usec - tmp.tv_usec) / 1000000; 69 | tmp.tv_usec += 1000000 * nsec; 70 | tmp.tv_sec -= nsec; 71 | } 72 | 73 | // Compute the time remaining to wait. tv_usec is certainly positive. 74 | result->tv_sec = x->tv_sec - tmp.tv_sec; 75 | result->tv_usec = x->tv_usec - tmp.tv_usec; 76 | 77 | // Return 1 if result is negative. 78 | return x->tv_sec < tmp.tv_sec; 79 | } 80 | 81 | static void timeval_add (struct timeval *result, struct timeval *x, struct timeval *y) 82 | { 83 | result->tv_sec = x->tv_sec + y->tv_sec; 84 | result->tv_usec = x->tv_usec + y->tv_usec; 85 | 86 | if (result->tv_usec > 1000000) 87 | { 88 | result->tv_sec += result->tv_usec / 1000000; 89 | result->tv_usec = result->tv_usec % 1000000; 90 | } 91 | } 92 | 93 | static struct in_addr find3DS(int retries) 94 | { 95 | struct sockaddr_in s, remote, rs; 96 | char recvbuf[256]; 97 | char mess[] = "3dsboot"; 98 | 99 | int broadcastSock = socket(PF_INET, SOCK_DGRAM, 0); 100 | if (broadcastSock < 0) 101 | { 102 | netsenderError("broadcast socket", errno); 103 | remote.sin_addr.s_addr = INADDR_NONE; 104 | return remote.sin_addr; 105 | } 106 | 107 | memset(&s, 0, sizeof(struct sockaddr_in)); 108 | s.sin_family = AF_INET; 109 | s.sin_port = htons(NETWORK_PORT); 110 | s.sin_addr.s_addr = INADDR_BROADCAST; 111 | 112 | memset(&rs, 0, sizeof(struct sockaddr_in)); 113 | rs.sin_family = AF_INET; 114 | rs.sin_port = htons(NETWORK_PORT); 115 | rs.sin_addr.s_addr = INADDR_ANY; 116 | 117 | int recvSock = socket(PF_INET, SOCK_DGRAM, 0); 118 | 119 | if (recvSock < 0) 120 | { 121 | netsenderError("receive socket", errno); 122 | close(broadcastSock); 123 | remote.sin_addr.s_addr = INADDR_NONE; 124 | return remote.sin_addr; 125 | } 126 | 127 | if (bind(recvSock, (struct sockaddr*) &rs, sizeof(rs)) < 0) 128 | { 129 | netsenderError("bind receive socket", errno); 130 | close(broadcastSock); 131 | close(recvSock); 132 | remote.sin_addr.s_addr = INADDR_NONE; 133 | return remote.sin_addr; 134 | } 135 | 136 | fcntl(recvSock, F_SETFL, O_NONBLOCK); 137 | struct timeval wanted, now, result; 138 | 139 | gettimeofday(&wanted, NULL); 140 | 141 | int timeout = retries, len; 142 | while (timeout) 143 | { 144 | if (wantExit) 145 | break; 146 | 147 | gettimeofday(&now, NULL); 148 | if (timeval_subtract(&result,&wanted,&now)) 149 | { 150 | if (sendto(broadcastSock, mess, strlen(mess), 0, (struct sockaddr *)&s, sizeof(s)) < 0) 151 | { 152 | netsenderError("sendto", errno); 153 | close(broadcastSock); 154 | close(recvSock); 155 | remote.sin_addr.s_addr = INADDR_NONE; 156 | return remote.sin_addr; 157 | } 158 | 159 | result.tv_sec=0; 160 | result.tv_usec=150000; 161 | timeval_add(&wanted,&now,&result); 162 | timeout--; 163 | } 164 | socklen_t socklen = sizeof(remote); 165 | len = recvfrom(recvSock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&remote, &socklen); 166 | if (len != -1) 167 | { 168 | if (strncmp("boot3ds",recvbuf,strlen("boot3ds")) == 0) 169 | { 170 | break; 171 | } 172 | } 173 | 174 | svcSleepThread(1e6); 175 | } 176 | 177 | if (timeout == 0) 178 | remote.sin_addr.s_addr = INADDR_NONE; 179 | 180 | close(broadcastSock); 181 | close(recvSock); 182 | return remote.sin_addr; 183 | } 184 | 185 | int sendData(int sock, int sendsize, char *buffer) 186 | { 187 | while (sendsize) 188 | { 189 | int len = send(sock, buffer, sendsize, 0); 190 | if (len == 0) break; 191 | if (len != -1) 192 | { 193 | sendsize -= len; 194 | buffer += len; 195 | } 196 | else 197 | { 198 | if (errno != EWOULDBLOCK && errno != EAGAIN) 199 | { 200 | netsenderError("send", errno); 201 | break; 202 | } 203 | } 204 | } 205 | return sendsize != 0; 206 | } 207 | 208 | int recvData(int sock, char *buffer, int size, int flags) 209 | { 210 | int len, sizeleft = size; 211 | 212 | while (sizeleft) 213 | { 214 | len = recv(sock, buffer, sizeleft, flags); 215 | if (len == 0) 216 | { 217 | size = 0; 218 | break; 219 | } 220 | if (len != -1) 221 | { 222 | sizeleft -=len; 223 | buffer +=len; 224 | } 225 | else 226 | { 227 | if (errno != EWOULDBLOCK && errno != EAGAIN) 228 | { 229 | netsenderError("recv", errno); 230 | break; 231 | } 232 | } 233 | } 234 | return size; 235 | } 236 | 237 | static int sendInt32LE(int socket, u32 size) 238 | { 239 | char lenbuf[4]; 240 | lenbuf[0] = size & 0xff; 241 | lenbuf[1] = (size >> 8) & 0xff; 242 | lenbuf[2] = (size >> 16) & 0xff; 243 | lenbuf[3] = (size >> 24) & 0xff; 244 | 245 | return sendData(socket, 4, lenbuf); 246 | } 247 | 248 | static int recvInt32LE(int socket, s32 *data) 249 | { 250 | char intbuf[4]; 251 | int len = recvData(socket,intbuf,4,0); 252 | 253 | if (len == 4) 254 | { 255 | *data = (intbuf[0] & 0xff) | (intbuf[1] << 8) | (intbuf[2] << 16) | (intbuf[3] << 24); 256 | return 0; 257 | } 258 | 259 | return -1; 260 | } 261 | 262 | static void send3DSXFile(in_addr_t inaddr, char *name, FILE *fh) 263 | { 264 | static unsigned char in[ZLIB_CHUNK]; 265 | static unsigned char out[ZLIB_CHUNK]; 266 | 267 | int ret, flush; 268 | unsigned have; 269 | z_stream strm; 270 | 271 | // allocate deflate state 272 | strm.zalloc = Z_NULL; 273 | strm.zfree = Z_NULL; 274 | strm.opaque = Z_NULL; 275 | ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION); 276 | if (ret != Z_OK) return; 277 | 278 | datafd = socket(AF_INET,SOCK_STREAM,0); 279 | if (datafd < 0) 280 | { 281 | netsenderError("datafd", errno); 282 | return; 283 | } 284 | 285 | struct sockaddr_in s; 286 | memset(&s, 0, sizeof(struct sockaddr_in)); 287 | s.sin_family = AF_INET; 288 | s.sin_port = htons(NETWORK_PORT); 289 | s.sin_addr.s_addr = inaddr; 290 | 291 | if (connect(datafd, (struct sockaddr *)&s, sizeof(s)) < 0 ) 292 | { 293 | netsenderError("connect", errno); 294 | goto _cleanup; 295 | } 296 | 297 | int namelen = strlen(name); 298 | 299 | if (sendInt32LE(datafd, namelen)) 300 | { 301 | netsenderError("filenameLen", errno); 302 | goto _cleanup; 303 | } 304 | 305 | if (sendData(datafd, namelen, name)) 306 | { 307 | netsenderError("filename", errno); 308 | goto _cleanup; 309 | } 310 | 311 | if (sendInt32LE(datafd, filelen)) 312 | { 313 | netsenderError("filelen", errno); 314 | goto _cleanup; 315 | } 316 | 317 | s32 response; 318 | if (recvInt32LE(datafd, &response) != 0) 319 | { 320 | netsenderError("response", 0); 321 | goto _cleanup; 322 | } 323 | 324 | if (response != 0) 325 | { 326 | switch (response) 327 | { 328 | case -1: 329 | netsenderError("create file", 0); 330 | break; 331 | case -2: 332 | netsenderError("no space", 0); 333 | break; 334 | case -3: 335 | netsenderError("no memory", 0); 336 | break; 337 | default: 338 | netsenderError("response:", response); 339 | break; 340 | } 341 | goto _cleanup; 342 | } 343 | 344 | size_t totalsent = 0, blocks = 0; 345 | 346 | do 347 | { 348 | strm.avail_in = fread(in, 1, ZLIB_CHUNK, fh); 349 | filetotal += strm.avail_in; 350 | if (ferror(fh)) 351 | { 352 | netsenderError("ferror", 0); 353 | deflateEnd(&strm); 354 | goto _cleanup; 355 | } 356 | flush = feof(fh) ? Z_FINISH : Z_NO_FLUSH; 357 | strm.next_in = in; 358 | // run deflate() on input until output buffer not full, finish compression if all of source has been read in 359 | do 360 | { 361 | strm.avail_out = ZLIB_CHUNK; 362 | strm.next_out = out; 363 | ret = deflate(&strm, flush); // no bad return value 364 | have = ZLIB_CHUNK - strm.avail_out; 365 | 366 | if (have != 0) 367 | { 368 | if (sendInt32LE(datafd,have)) 369 | { 370 | netsenderError("chunk size", errno); 371 | goto _cleanup; 372 | } 373 | 374 | if (sendData(datafd, have, (char*)out)) 375 | { 376 | netsenderError("have", errno); 377 | deflateEnd(&strm); 378 | goto _cleanup; 379 | } 380 | 381 | totalsent += have; 382 | blocks++; 383 | } 384 | } while (strm.avail_out == 0); 385 | // done when last data in file processed 386 | } while (flush != Z_FINISH); 387 | deflateEnd(&strm); 388 | 389 | if (recvInt32LE(datafd,&response)!=0) 390 | goto _cleanup; 391 | 392 | static char cmdbuf[3072]; 393 | memset(cmdbuf, 0, 3072); 394 | snprintf(&cmdbuf[4], 3072-4, "3dslink:/%s", name); 395 | u32 cmdlen = strlen(&cmdbuf[4])+1; 396 | 397 | cmdbuf[0] = cmdlen & 0xff; 398 | cmdbuf[1] = (cmdlen>>8) & 0xff; 399 | cmdbuf[2] = (cmdlen>>16) & 0xff; 400 | cmdbuf[3] = (cmdlen>>24) & 0xff; 401 | 402 | if (sendData(datafd,cmdlen+4,cmdbuf)) 403 | netsenderError("cmdbuf", errno); 404 | 405 | _cleanup: 406 | close(datafd); 407 | datafd = -1; 408 | } 409 | 410 | void netsenderTask(void* arg) 411 | { 412 | wantExit = false; 413 | filelen = 0; 414 | filetotal = 0; 415 | dsaddr.s_addr = INADDR_NONE; 416 | doneSearching = false; 417 | errored = false; 418 | 419 | if (!netsenderInit()) 420 | { 421 | errorScreen(textGetString(StrId_NetSender), textGetString(StrId_NetSenderUnavailable)); 422 | return; 423 | } 424 | 425 | uiEnterState(UI_STATE_NETSENDER); 426 | 427 | dsaddr = find3DS(10); 428 | if (errored || wantExit) 429 | goto _cleanup; 430 | 431 | doneSearching = true; 432 | if (dsaddr.s_addr == INADDR_NONE) 433 | { 434 | LightEvent_Init(&event, RESET_ONESHOT); 435 | LightEvent_Wait(&event); 436 | LightEvent_Clear(&event); 437 | } 438 | 439 | if (wantExit || dsaddr.s_addr == INADDR_NONE) 440 | goto _cleanup; 441 | 442 | menuEntry_s* me = (menuEntry_s*)arg; 443 | char* filename = strdup(me->path); 444 | char* basename = strrchr(filename,'/') + 1; // assume it doesnt fail 445 | 446 | FILE* inf = fopen(me->path, "rb"); 447 | fseek(inf, 0, SEEK_END); 448 | filelen = ftell(inf); 449 | fseek(inf, 0, SEEK_SET); 450 | 451 | send3DSXFile(dsaddr.s_addr, basename, inf); 452 | 453 | fclose(inf); 454 | free(filename); 455 | 456 | _cleanup: 457 | netsenderDeactivate(); 458 | if (!errored) 459 | uiExitState(); 460 | } 461 | 462 | static bool validateIp(const char* ip, size_t len) 463 | { 464 | if (len < 7) // if it's not long enough to hold 4 digits and 3 separators, it's too short. 465 | return false; 466 | 467 | struct addrinfo *info; 468 | if (getaddrinfo(ip, NULL, NULL, &info) == 0) // check it's a valid address first 469 | { 470 | dsaddr = ((struct sockaddr_in*)info->ai_addr)->sin_addr; 471 | freeaddrinfo(info); 472 | // this won't error, so 0.0.0.0 (the default ip) will return false and force the user to put something else 473 | return inet_addr(ip) != 0; 474 | } 475 | 476 | return false; 477 | } 478 | 479 | static SwkbdCallbackResult callback(void *user, const char **ppMessage, const char *text, size_t textlen) 480 | { 481 | bool validIp = validateIp(text, textlen); 482 | if (!validIp) 483 | { 484 | *ppMessage = textGetString(StrId_NetSenderInvalidIp); 485 | return SWKBD_CALLBACK_CONTINUE; 486 | } 487 | 488 | return SWKBD_CALLBACK_OK; 489 | } 490 | 491 | void netsenderUpdate(void) 492 | { 493 | if (wantExit || datafd >= 0) return; 494 | 495 | if (hidKeysDown() & KEY_B) 496 | wantExit = true; 497 | 498 | if (doneSearching && dsaddr.s_addr == INADDR_NONE) 499 | { 500 | SwkbdState swkbd; 501 | swkbdInit(&swkbd, SWKBD_TYPE_NUMPAD, 2, RECEIVER_IP_LENGTH); 502 | swkbdSetNumpadKeys(&swkbd, L'.', 0); 503 | swkbdSetFeatures(&swkbd, SWKBD_DARKEN_TOP_SCREEN | SWKBD_FIXED_WIDTH); 504 | swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY, 0, 0); 505 | swkbdSetInitialText(&swkbd, "000.000.000.000"); 506 | swkbdSetFilterCallback(&swkbd, callback, NULL); 507 | SwkbdButton button = swkbdInputText(&swkbd, receiverIp, RECEIVER_IP_LENGTH+1); 508 | 509 | if (button == SWKBD_BUTTON_LEFT) // Cancel 510 | wantExit = true; 511 | 512 | LightEvent_Signal(&event); 513 | } 514 | } 515 | 516 | void netsenderExit(void) 517 | { 518 | wantExit = true; 519 | } 520 | 521 | void netsenderDrawBot(void) 522 | { 523 | char buf[256]; 524 | const char* text = NULL; 525 | if (!doneSearching) 526 | { 527 | snprintf(buf, sizeof(buf), textGetString(StrId_NetSenderActive)); 528 | text = buf; 529 | } 530 | 531 | networkDrawBot(StrId_NetSender, text, (datafd >= 0 && filelen), filelen, filetotal); 532 | } 533 | -------------------------------------------------------------------------------- /source/menu-entry.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "parsing/shortcut.h" 3 | 4 | #include 5 | 6 | void menuEntryInit(menuEntry_s *me, MenuEntryType type) 7 | { 8 | memset(me, 0, sizeof(*me)); 9 | me->type = type; 10 | descriptorInit(&me->descriptor); 11 | } 12 | 13 | void menuEntryFree(menuEntry_s *me) 14 | { 15 | descriptorFree(&me->descriptor); 16 | C3D_TexDelete(&me->texture); 17 | } 18 | 19 | bool fileExists(const char *path) 20 | { 21 | struct stat st; 22 | return stat(path, &st) == 0 && S_ISREG(st.st_mode); 23 | } 24 | 25 | bool menuEntryLoadExternalIcon(menuEntry_s *me, const char *filepath) 26 | { 27 | FILE *iconFile = fopen(filepath, "rb"); 28 | 29 | if (!iconFile) 30 | return false; 31 | 32 | if (!me->icon) 33 | me->icon = &me->texture; 34 | 35 | Tex3DS_Texture texture = Tex3DS_TextureImportStdio(iconFile, me->icon, NULL, false); 36 | fclose(iconFile); 37 | 38 | if (!texture) 39 | return false; 40 | 41 | const Tex3DS_SubTexture *subTexture = Tex3DS_GetSubTexture(texture, 0); 42 | 43 | if (subTexture->width != 64 && subTexture->height != 64) 44 | { 45 | C3D_TexDelete(me->icon); 46 | me->icon = NULL; 47 | Tex3DS_TextureFree(texture); 48 | 49 | return false; 50 | } 51 | 52 | // Delete the t3x object since we don't need it 53 | Tex3DS_TextureFree(texture); 54 | 55 | return true; 56 | } 57 | 58 | bool menuEntryImportIcon(menuEntry_s *me, C3D_Tex *texture) 59 | { 60 | if (!me->icon) 61 | { 62 | me->icon = &me->texture; 63 | C3D_TexInit(me->icon, 64, 64, GPU_RGB565); 64 | } 65 | 66 | if (!texture) 67 | return false; 68 | 69 | if (texture->fmt != GPU_RGB565) 70 | return false; 71 | 72 | if (texture->width != 64 && texture->height != 64) 73 | return false; 74 | 75 | /* move texture from top of space to bottom */ 76 | 77 | u16 *dest = (u16 *)me->icon->data + (64 - 48) * 64; 78 | u16 *src = (u16 *)texture->data; 79 | 80 | int j; 81 | for (j = 0; j < 48; j += 8) 82 | { 83 | memcpy(dest, src, 48 * 8 * sizeof(u16)); 84 | 85 | src += 64 * 8; 86 | dest += 64 * 8; 87 | } 88 | 89 | return true; 90 | } 91 | 92 | static bool menuEntryLoadEmbeddedSmdh(menuEntry_s *me) 93 | { 94 | _3DSX_Header header; 95 | 96 | FILE *f = fopen(me->path, "rb"); 97 | if (!f) 98 | return false; 99 | 100 | if (fread(&header, sizeof(header), 1, f) != 1 || header.headerSize < sizeof(header) || header.smdhSize < sizeof(smdh_s)) 101 | { 102 | fclose(f); 103 | return false; 104 | } 105 | 106 | fseek(f, header.smdhOffset, SEEK_SET); 107 | bool ok = fread(&me->smdh, sizeof(smdh_s), 1, f) == 1; 108 | fclose(f); 109 | return ok; 110 | } 111 | 112 | static bool menuEntryLoadExternalSmdh(menuEntry_s *me, const char *file) 113 | { 114 | FILE *f = fopen(file, "rb"); 115 | if (!f) 116 | return false; 117 | bool ok = fread(&me->smdh, sizeof(smdh_s), 1, f) == 1; 118 | fclose(f); 119 | return ok; 120 | } 121 | 122 | static void fixSpaceNewLine(char *buf) 123 | { 124 | char *outp = buf, *inp = buf; 125 | char lastc = 0; 126 | do 127 | { 128 | char c = *inp++; 129 | if (c == ' ' && lastc == ' ') 130 | outp[-1] = '\n'; 131 | else 132 | *outp++ = c; 133 | lastc = c; 134 | } while (lastc); 135 | } 136 | 137 | void menuEntryFileAssocLoad(const char *filepath) 138 | { 139 | bool success = false; 140 | bool iconLoaded = false; 141 | 142 | menuEntry_s *entry = NULL; 143 | 144 | config_setting_t *fileAssoc = NULL; 145 | config_setting_t *targets = NULL; 146 | config_setting_t *target = NULL; 147 | config_setting_t *appArguments = NULL; 148 | config_setting_t *targetArguments = NULL; 149 | 150 | config_t config = {0}; 151 | 152 | int targetsLength = 0; 153 | int argsLength = 0; 154 | int index = 0; 155 | 156 | char appPath[PATH_MAX + 8]; 157 | char mainIconPath[PATH_MAX + 1]; 158 | char targetIconPath[PATH_MAX + 1]; 159 | char targetFileExtension[PATH_MAX + 1]; 160 | char targetFilename[PATH_MAX + 1]; 161 | 162 | char appAuthor[ENTRY_AUTHORLENGTH + 2]; 163 | char appDescription[ENTRY_DESCLENGTH + 2]; 164 | 165 | const char *stringValue = NULL; 166 | 167 | config_init(&config); 168 | 169 | memset(appPath, 0, sizeof(appPath)); 170 | memset(mainIconPath, 0, sizeof(mainIconPath)); 171 | memset(appAuthor, 0, sizeof(appAuthor)); 172 | memset(appDescription, 0, sizeof(appDescription)); 173 | 174 | if (!fileExists(filepath)) 175 | return; 176 | 177 | if (config_read_file(&config, filepath)) 178 | { 179 | 180 | fileAssoc = config_lookup(&config, "fileassoc"); 181 | 182 | if (fileAssoc != NULL) 183 | { 184 | if (config_setting_lookup_string(fileAssoc, "app_path", &stringValue)) 185 | snprintf(appPath, sizeof(appPath) - 1, "%s%s", menuGetRootBasePath(), stringValue); 186 | 187 | if (config_setting_lookup_string(fileAssoc, "icon_path", &stringValue)) 188 | snprintf(mainIconPath, sizeof(mainIconPath) - 1, "%s%s", menuGetRootBasePath(), stringValue); 189 | 190 | appArguments = config_setting_lookup(fileAssoc, "app_args"); 191 | targets = config_setting_lookup(fileAssoc, "targets"); 192 | 193 | if (appPath[0] && targets) 194 | { 195 | targetsLength = config_setting_length(targets); 196 | 197 | if (targetsLength > 0) 198 | { 199 | entry = menuCreateEntry(ENTRY_TYPE_FILE); 200 | success = false; 201 | 202 | if (entry) 203 | { 204 | strncpy(entry->path, appPath, sizeof(entry->path) - 1); 205 | entry->path[sizeof(entry->path) - 1] = 0; 206 | 207 | stringValue = getSlash(appPath); 208 | 209 | if (stringValue[0] == '/') 210 | stringValue++; 211 | 212 | if (menuEntryLoad(entry, stringValue, false)) 213 | { 214 | strncpy(appAuthor, entry->author, sizeof(appAuthor)); 215 | appAuthor[sizeof(appAuthor) - 1] = 0; 216 | 217 | strncpy(appDescription, entry->description, sizeof(appDescription)); 218 | appDescription[sizeof(appDescription) - 1] = 0; 219 | 220 | success = true; 221 | } 222 | 223 | menuDeleteEntry(entry); 224 | entry = NULL; 225 | } 226 | 227 | if (success) 228 | { 229 | for (index = 0; index < targetsLength; index++) 230 | { 231 | target = config_setting_get_elem(targets, index); 232 | 233 | if (target == NULL) 234 | continue; 235 | 236 | memset(targetIconPath, 0, sizeof(targetIconPath)); 237 | memset(targetFileExtension, 0, sizeof(targetFileExtension)); 238 | memset(targetFilename, 0, sizeof(targetFilename)); 239 | 240 | if (config_setting_lookup_string(target, "icon_path", &stringValue)) 241 | snprintf(targetIconPath, sizeof(targetIconPath) - 1, "%s%s", menuGetRootBasePath(), stringValue); 242 | 243 | if (config_setting_lookup_string(target, "file_extension", &stringValue)) 244 | strncpy(targetFileExtension, stringValue, sizeof(targetFileExtension) - 1); 245 | 246 | if (config_setting_lookup_string(target, "filename", &stringValue)) 247 | strncpy(targetFilename, stringValue, sizeof(targetFilename) - 1); 248 | 249 | targetArguments = config_setting_lookup(target, "app_args"); 250 | 251 | if ((targetFileExtension[0] != 0) == (targetFilename[0] != 0)) 252 | continue; 253 | 254 | entry = menuCreateEntry(ENTRY_TYPE_FILEASSOC); 255 | iconLoaded = false; 256 | 257 | if (entry) 258 | { 259 | strncpy(entry->path, appPath, sizeof(entry->path)); 260 | entry->path[sizeof(entry->path) - 1] = 0; 261 | 262 | strncpy(entry->author, appAuthor, sizeof(entry->author)); 263 | entry->author[sizeof(entry->author) - 1] = 0; 264 | 265 | strncpy(entry->description, appDescription, sizeof(entry->description)); 266 | entry->description[sizeof(entry->description) - 1] = 0; 267 | 268 | if (targetFileExtension[0]) 269 | { 270 | entry->fileAssocType = 0; 271 | strncpy(entry->fileAssocStr, targetFileExtension, sizeof(entry->fileAssocStr)); 272 | } 273 | else if (targetFilename[0]) 274 | { 275 | entry->fileAssocType = 1; 276 | strncpy(entry->fileAssocStr, targetFilename, sizeof(entry->fileAssocStr)); 277 | } 278 | 279 | entry->fileAssocStr[sizeof(entry->fileAssocStr) - 1] = 0; 280 | 281 | if (targetIconPath[0]) 282 | iconLoaded = menuEntryLoadExternalIcon(entry, targetIconPath); 283 | 284 | if (!iconLoaded && mainIconPath[0]) 285 | iconLoaded = menuEntryLoadExternalIcon(entry, mainIconPath); 286 | 287 | argData_s *argData = &entry->args; 288 | argData->dst = (char *)&argData->buf[1]; 289 | 290 | launchAddArg(argData, entry->path); 291 | 292 | config_setting_t *configArgs = targetArguments ? targetArguments : appArguments; 293 | if (configArgs) 294 | { 295 | argsLength = config_setting_length(configArgs); 296 | for (int argument = 0; argument < argsLength; argument++) 297 | { 298 | stringValue = config_setting_get_string_elem(configArgs, argument); 299 | 300 | if (stringValue == NULL) 301 | continue; 302 | 303 | launchAddArg(argData, stringValue); 304 | } 305 | } 306 | } 307 | 308 | if (entry) 309 | menuFileAssocAddEntry(entry); 310 | } 311 | } 312 | } 313 | } 314 | } 315 | } 316 | 317 | config_destroy(&config); 318 | } 319 | 320 | bool menuEntryLoad(menuEntry_s *me, const char *name, bool shortcut) 321 | { 322 | static char tempbuf[PATH_MAX + 1]; 323 | bool isAppBundleFolder = false; 324 | 325 | menu_s *menuFileAssoc = menuFileAssocGetCurrent(); 326 | menuEntry_s *fileAssocEntry = NULL; 327 | 328 | tempbuf[PATH_MAX] = 0; 329 | strcpy(me->name, name); 330 | snprintf(me->starpath, sizeof(me->starpath) - 1, "%.*s.star", sizeof(me->starpath) - 12, me->path); 331 | 332 | if (me->type == ENTRY_TYPE_FOLDER) 333 | do 334 | { 335 | // Check if this folder is an application bundle (except if it's the starting directory) 336 | if (strcmp(me->path, "sdmc:/3ds") == 0) 337 | break; 338 | 339 | snprintf(tempbuf, sizeof(tempbuf) - 1, "%.*s/boot.3dsx", sizeof(tempbuf) - 12, me->path); 340 | bool found = fileExists(tempbuf); 341 | bool fileAssocFlag = false; 342 | 343 | if (!found) 344 | { 345 | snprintf(tempbuf, sizeof(tempbuf) - 1, "%.*s/%.*s.3dsx", sizeof(tempbuf) / 2, me->path, sizeof(tempbuf) / 2 - 7, name); 346 | found = fileExists(tempbuf); 347 | } 348 | 349 | if (!found && menuFileAssoc->nEntries > 0) 350 | { 351 | fileAssocFlag = true; 352 | 353 | DIR *dir = opendir(fileAssocEntry->path); 354 | struct dirent *dp; 355 | 356 | if (dir) 357 | { 358 | int index = 0; 359 | while ((dp = readdir(dir))) 360 | { 361 | if (dp->d_name[0] == '.') 362 | continue; 363 | 364 | for (fileAssocEntry = menuFileAssoc->firstEntry, index = 0; fileAssocEntry; fileAssocEntry = fileAssocEntry->next, index++) 365 | { 366 | if (!fileAssocEntry->fileAssocType) 367 | continue; 368 | 369 | if (strcmp(dp->d_name, fileAssocEntry->fileAssocStr)) 370 | continue; 371 | 372 | snprintf(tempbuf, sizeof(tempbuf) - 1, "%.*s/%.*s", (int)sizeof(tempbuf) / 2, me->path, (int)sizeof(tempbuf) / 2 - 7, dp->d_name); 373 | found = fileExists(tempbuf); 374 | 375 | if (found) 376 | break; 377 | } 378 | } 379 | closedir(dir); 380 | } 381 | } 382 | 383 | if (found) 384 | { 385 | isAppBundleFolder = true; 386 | shortcut = false; 387 | me->type = fileAssocFlag ? ENTRY_TYPE_FILE_OTHER : ENTRY_TYPE_FILE; 388 | strcpy(me->path, tempbuf); 389 | } 390 | } while (0); 391 | 392 | if (me->type == ENTRY_TYPE_FOLDER) 393 | strcpy(me->description, textGetString(StrId_Directory)); 394 | 395 | if (me->type == ENTRY_TYPE_FILE) 396 | { 397 | strcpy(me->name, name); 398 | strcpy(me->description, textGetString(StrId_DefaultLongTitle)); 399 | strcpy(me->author, textGetString(StrId_DefaultPublisher)); 400 | 401 | shortcut_s sc; 402 | 403 | if (shortcut) 404 | { 405 | if (R_FAILED(shortcutCreate(&sc, me->path))) 406 | { 407 | return false; 408 | } 409 | 410 | if (!fileExists(sc.executable)) 411 | { 412 | shortcutFree(&sc); 413 | return false; 414 | } 415 | 416 | strcpy(me->path, "sdmc:"); 417 | strcat(me->path, sc.executable); 418 | } 419 | 420 | bool smdhLoaded = false; 421 | 422 | // Load the SMDH 423 | if (shortcut) 424 | { 425 | FILE *f = sc.icon ? fopen(sc.icon, "rb") : NULL; 426 | if (f) 427 | { 428 | smdhLoaded = fread(&me->smdh, sizeof(smdh_s), 1, f) == 1; 429 | fclose(f); 430 | } 431 | } 432 | 433 | if (!smdhLoaded) 434 | // Attempt loading the embedded SMDH 435 | smdhLoaded = menuEntryLoadEmbeddedSmdh(me); 436 | 437 | if (!smdhLoaded && isAppBundleFolder) 438 | do 439 | { 440 | // Attempt loading external SMDH from app bundle folder 441 | strcpy(tempbuf, me->path); 442 | char *ext = getExtension(tempbuf); 443 | strcpy(ext, ".smdh"); 444 | smdhLoaded = menuEntryLoadExternalSmdh(me, tempbuf); 445 | if (smdhLoaded) 446 | break; 447 | 448 | char *slash = getSlash(tempbuf); 449 | strcpy(slash, "/icon.smdh"); 450 | smdhLoaded = menuEntryLoadExternalSmdh(me, tempbuf); 451 | if (smdhLoaded) 452 | break; 453 | } while (0); 454 | 455 | if (smdhLoaded) 456 | { 457 | menuEntryParseSmdh(me); 458 | 459 | // Detect HANS, and only show it if the loader supports target titles 460 | if (strcmp(me->name, "HANS") == 0 && !loaderCanUseTitles()) 461 | return false; 462 | 463 | // Fix description for some applications using multiple spaces to indicate newline 464 | fixSpaceNewLine(me->description); 465 | } 466 | 467 | // Metadata overrides for shortcuts 468 | if (shortcut) 469 | { 470 | if (sc.name) 471 | strncpy(me->name, sc.name, ENTRY_NAMELENGTH); 472 | if (sc.description) 473 | strncpy(me->description, sc.description, ENTRY_DESCLENGTH); 474 | if (sc.author) 475 | strncpy(me->author, sc.author, ENTRY_AUTHORLENGTH); 476 | } 477 | 478 | // Load the descriptor 479 | if (shortcut && sc.descriptor && fileExists(sc.descriptor)) 480 | descriptorLoad(&me->descriptor, sc.descriptor); 481 | else 482 | { 483 | strcpy(tempbuf, me->path); 484 | strcpy(getExtension(tempbuf), ".xml"); 485 | bool found = fileExists(tempbuf); 486 | if (!found && isAppBundleFolder) 487 | { 488 | strcpy(tempbuf, me->path); 489 | strcpy(getSlash(tempbuf), "/descriptor.xml"); 490 | found = fileExists(tempbuf); 491 | } 492 | if (found) 493 | descriptorLoad(&me->descriptor, tempbuf); 494 | } 495 | 496 | // Initialize the argument data 497 | argData_s *ad = &me->args; 498 | ad->dst = (char *)&ad->buf[1]; 499 | launchAddArg(ad, me->path); 500 | 501 | // Load the argument(s) from the shortcut 502 | if (shortcut && sc.arg && *sc.arg) 503 | launchAddArgsFromString(ad, sc.arg); 504 | 505 | if (shortcut) 506 | shortcutFree(&sc); 507 | } 508 | 509 | if (me->type == ENTRY_TYPE_FILE_OTHER) 510 | { 511 | if (menuFileAssoc->nEntries == 0) 512 | return false; 513 | 514 | int index = 0; 515 | char *strptr; 516 | 517 | for (fileAssocEntry = menuFileAssoc->firstEntry, index = 0; fileAssocEntry; fileAssocEntry = fileAssocEntry->next, index++) 518 | { 519 | if (!fileAssocEntry->fileAssocType) 520 | strptr = getExtension(me->path); 521 | 522 | if (fileAssocEntry->fileAssocType) 523 | { 524 | strptr = getSlash(me->path); 525 | if (strptr[0] == '/') 526 | strptr++; 527 | } 528 | 529 | if (strcmp(strptr, fileAssocEntry->fileAssocStr)) 530 | continue; 531 | 532 | me->type = ENTRY_TYPE_FILE; 533 | 534 | /* attempt to load icon from entry->path (with extension t3x) */ 535 | /* on failure, should use icon data from the entry */ 536 | 537 | memset(tempbuf, 0, sizeof(tempbuf)); 538 | strncpy(tempbuf, me->path, sizeof(tempbuf)); 539 | tempbuf[sizeof(tempbuf) - 1] = 0; 540 | 541 | strptr = getExtension(tempbuf); 542 | strncpy(strptr, ".t3x", sizeof(tempbuf) - 1 - ((ptrdiff_t)strptr - (ptrdiff_t)tempbuf)); 543 | 544 | bool iconLoaded = menuEntryLoadExternalIcon(me, tempbuf); 545 | if (!iconLoaded && fileAssocEntry->icon) 546 | iconLoaded = menuEntryImportIcon(me, fileAssocEntry->icon); 547 | 548 | /* attempt to load the smdh from entry->path with extension .smdh */ 549 | /* on failure, use the config from the entry */ 550 | memset(tempbuf, 0, sizeof(tempbuf)); 551 | strncpy(tempbuf, me->path, sizeof(tempbuf)); 552 | tempbuf[sizeof(tempbuf) - 1] = 0; 553 | 554 | strptr = getExtension(tempbuf); 555 | strncpy(strptr, ".smdh", sizeof(tempbuf) - 1 - ((ptrdiff_t)strptr - (ptrdiff_t)tempbuf)); 556 | 557 | bool smdhLoaded = menuEntryLoadExternalSmdh(me, tempbuf); 558 | if (smdhLoaded) 559 | { 560 | menuEntryParseSmdh(me); 561 | } 562 | else 563 | { 564 | strncpy(me->author, fileAssocEntry->author, sizeof(me->author)); 565 | me->author[sizeof(me->author) - 1] = 0; 566 | 567 | strncpy(me->description, fileAssocEntry->description, sizeof(me->description)); 568 | me->description[sizeof(me->description) - 1] = 0; 569 | } 570 | 571 | /* initialize argument data */ 572 | argData_s *argData = &me->args; 573 | argData_s *argDataAssoc = &fileAssocEntry->args; 574 | 575 | char *argSource = (char *)&argDataAssoc->buf[1]; 576 | bool fTokenFound = false; 577 | 578 | argData->dst = (char *)&argData->buf[1]; 579 | 580 | for (u32 argIndex = 0; argIndex < argDataAssoc->buf[0]; argIndex++, argSource += strlen(argSource) + 1) 581 | { 582 | if (argIndex) 583 | { 584 | strptr = strchr(argSource, '%'); 585 | if (strptr && strptr[0] && strptr[1] && (strptr == argSource || strptr[-1] != '\\')) 586 | { 587 | if (strptr[1] == 'f') 588 | { 589 | memset(tempbuf, 0, sizeof(tempbuf)); 590 | snprintf(tempbuf, sizeof(tempbuf) - 1, "%.*s%s%s", (int)((uintptr_t)strptr - (uintptr_t)argSource), argSource, me->path, &strptr[2]); 591 | 592 | launchAddArg(argData, tempbuf); 593 | fTokenFound = true; 594 | continue; 595 | } 596 | } 597 | } 598 | 599 | launchAddArg(argData, argSource); 600 | } 601 | 602 | if (!fTokenFound) 603 | launchAddArg(argData, me->path); 604 | 605 | strncpy(me->path, fileAssocEntry->path, sizeof(me->path)); 606 | me->path[sizeof(me->path) - 1] = 0; 607 | 608 | return true; 609 | } 610 | 611 | return false; 612 | } 613 | 614 | me->isStarred = me->type == ENTRY_TYPE_FILE && fileExists(me->starpath); 615 | 616 | return true; 617 | } 618 | 619 | static void safe_utf8_convert(char *buf, const u16 *input, size_t bufsize) 620 | { 621 | ssize_t units = utf16_to_utf8((uint8_t *)buf, input, bufsize); 622 | if (units < 0) 623 | units = 0; 624 | buf[units] = 0; 625 | } 626 | 627 | void menuEntryParseSmdh(menuEntry_s *me) 628 | { 629 | if (!me->icon) 630 | { 631 | me->icon = &me->texture; 632 | C3D_TexInit(me->icon, 64, 64, GPU_RGB565); 633 | } 634 | 635 | // Copy 48x48 -> 64x64 636 | u16 *dest = (u16 *)me->icon->data + (64 - 48) * 64; 637 | u16 *src = (u16 *)me->smdh.bigIconData; 638 | int j; 639 | for (j = 0; j < 48; j += 8) 640 | { 641 | memcpy(dest, src, 48 * 8 * sizeof(u16)); 642 | src += 48 * 8; 643 | dest += 64 * 8; 644 | } 645 | 646 | int lang = textGetLang(); 647 | safe_utf8_convert(me->name, me->smdh.applicationTitles[lang].shortDescription, ENTRY_NAMELENGTH); 648 | safe_utf8_convert(me->description, me->smdh.applicationTitles[lang].longDescription, ENTRY_NAMELENGTH); 649 | safe_utf8_convert(me->author, me->smdh.applicationTitles[lang].publisher, ENTRY_NAMELENGTH); 650 | } 651 | -------------------------------------------------------------------------------- /source/language.c: -------------------------------------------------------------------------------- 1 | #include "language.h" 2 | 3 | #define STR_JP(_str) [CFG_LANGUAGE_JP] = _str 4 | #define STR_EN(_str) [CFG_LANGUAGE_EN] = _str 5 | #define STR_FR(_str) [CFG_LANGUAGE_FR] = _str 6 | #define STR_DE(_str) [CFG_LANGUAGE_DE] = _str 7 | #define STR_IT(_str) [CFG_LANGUAGE_IT] = _str 8 | #define STR_ES(_str) [CFG_LANGUAGE_ES] = _str 9 | #define STR_ZH(_str) [CFG_LANGUAGE_ZH] = _str 10 | #define STR_KO(_str) [CFG_LANGUAGE_KO] = _str 11 | #define STR_NL(_str) [CFG_LANGUAGE_NL] = _str 12 | #define STR_PT(_str) [CFG_LANGUAGE_PT] = _str 13 | #define STR_RU(_str) [CFG_LANGUAGE_RU] = _str 14 | #define STR_TW(_str) [CFG_LANGUAGE_TW] = _str 15 | 16 | const char* const g_strings[StrId_Max][16] = 17 | { 18 | [StrId_Loading] = 19 | { 20 | STR_EN("Loading…"), 21 | STR_ES("Cargando…"), 22 | STR_DE("Lade…"), 23 | STR_FR("Chargement…"), 24 | STR_IT("Caricamento…"), 25 | STR_JP("ロード中…"), 26 | STR_PT("Carregando…"), 27 | STR_NL("Laden…"), 28 | STR_KO("로딩 중…"), 29 | STR_RU("загрузка…"), 30 | STR_ZH("加载中…"), 31 | STR_TW("加載中…"), 32 | }, 33 | 34 | [StrId_Directory] = 35 | { 36 | STR_EN("Directory"), 37 | STR_ES("Carpeta"), 38 | STR_DE("Verzeichnis"), 39 | STR_FR("Dossier"), 40 | STR_IT("Cartella"), 41 | STR_JP("フォルダ"), 42 | STR_PT("Directório"), 43 | STR_NL("Map"), 44 | STR_KO("디렉토리"), 45 | STR_RU("каталог"), 46 | STR_ZH("目录"), 47 | STR_TW("資料夾"), 48 | }, 49 | 50 | [StrId_DefaultLongTitle] = 51 | { 52 | STR_EN("Homebrew application"), 53 | STR_ES("Aplicación homebrew"), 54 | STR_DE("Homebrew-Anwendung"), 55 | STR_FR("Application homebrew"), 56 | STR_IT("Applicazione homebrew"), 57 | STR_JP("自作アプリ"), 58 | STR_PT("Aplicação Homebrew"), 59 | STR_NL("Homebrew toepassing"), 60 | STR_KO("Homebrew 애플리케이션"), 61 | STR_RU("приложение хомебреw"), 62 | STR_ZH("自制应用程序"), 63 | STR_TW("自製程式"), 64 | }, 65 | 66 | [StrId_DefaultPublisher] = 67 | { 68 | STR_EN("Unknown author"), 69 | STR_ES("Autor desconocido"), 70 | STR_DE("Unbekannter Autor"), 71 | STR_FR("Auteur inconnu"), 72 | STR_IT("Autore sconosciuto"), 73 | STR_JP("作者不明"), 74 | STR_PT("Autor Desconhecido"), 75 | STR_NL("Auteur onbekend"), 76 | STR_KO("알 수 없는 개발자"), 77 | STR_RU("неизвестный автор"), 78 | STR_ZH("未知作者"), 79 | STR_TW("未知作者"), 80 | }, 81 | 82 | [StrId_IOError] = 83 | { 84 | STR_EN("I/O Error"), 85 | STR_ES("Error de E/S"), 86 | STR_DE("E/A-Fehler"), 87 | STR_FR("Erreur d'E/S"), 88 | STR_IT("Errore di I/O"), 89 | STR_JP("入出力エラー"), 90 | STR_PT("Erro de E/S"), 91 | STR_NL("I/O Fout"), 92 | STR_KO("읽기/쓰기 오류"), 93 | STR_RU("I/O-ошибка"), 94 | STR_ZH("读写出错"), 95 | STR_TW("讀寫錯誤"), 96 | }, 97 | 98 | [StrId_CouldNotOpenFile] = 99 | { 100 | STR_EN("Could not open file:\n%s"), 101 | STR_ES("No se pudo abrir el archivo:\n%s"), 102 | STR_DE("Konnte Datei nicht öffnen:\n%s"), 103 | STR_FR("Impossible d'ouvrir le fichier :\n%s"), 104 | STR_IT("Impossibile aprire il file:\n%s"), 105 | STR_JP("ファイルを開くことができませんでした:\n%s"), 106 | STR_PT("Não foi possível abrir o ficheiro:\n%s"), 107 | STR_NL("Kan bestand niet openen:\n%s"), 108 | STR_KO("다음 파일을 열 수 없습니다:\n%s"), 109 | STR_RU("Не могу открыть файл:\n%s"), 110 | STR_ZH("无法打开文件:\n%s"), 111 | STR_TW("開啓檔案失敗:\n%s"), 112 | }, 113 | 114 | [StrId_NoAppsFound_Title] = 115 | { 116 | STR_EN("No applications found"), 117 | STR_ES("No hay aplicaciones"), 118 | STR_DE("Keine Anwendungen gefunden"), 119 | STR_FR("Aucune application trouvée"), 120 | STR_IT("Nessun'applicazione trovata"), 121 | STR_JP("アプリが見つかりませんでした"), 122 | STR_PT("Não foram encontradas aplicações"), 123 | STR_NL("Geen toepassingen gevonden"), 124 | STR_KO("애플리케이션을 찾을 수 없습니다"), 125 | STR_RU("приложение не найдено"), 126 | STR_ZH("找不到可执行的自制程序"), 127 | STR_TW("未能找到可執行的自製程式"), 128 | }, 129 | 130 | [StrId_NoAppsFound_Msg] = 131 | { 132 | STR_EN( 133 | "No applications could be found on the SD card.\n" 134 | "Make sure a folder named /3ds exists in the\n" 135 | "root of the SD card and it contains applications.\n" 136 | ), 137 | STR_ES( 138 | "No se han podido encontrar aplicaciones en la\n" 139 | "tarjeta SD. Compruebe que haya una carpeta\n" 140 | "llamada /3ds y que contenga aplicaciones.\n" 141 | ), 142 | STR_DE( 143 | "Auf der SD-Karte wurden keine Anwendungen\n" 144 | "gefunden. Stelle sicher, dass ein Verzeichnis\n" 145 | "namens /3ds im Wurzelverzeichnis der SD-Karte\n" 146 | "existiert und Anwendungen enthält!" 147 | ), 148 | STR_FR( 149 | "Aucune application n'a été trouvée sur la carte\n" 150 | "SD. Veillez à ce qu'un dossier intitulé /3ds\n" 151 | "existe à la racine de la carte SD et à ce qu'il\n" 152 | "contienne des applications." 153 | ), 154 | STR_IT( 155 | "Nessun'applicazione è stata trovata sulla scheda\n" 156 | "SD. Assicurati che esista una cartella chiamata\n" 157 | "/3ds nella root della scheda SD e che contenga\n" 158 | "delle applicazioni." 159 | ), 160 | STR_JP( 161 | "SDカードにアプリケーションが見つかりませんでした。\n" 162 | "SDカードのルートに「3ds」という名前のフォルダを\n" 163 | "作成してください。" 164 | ), 165 | STR_PT( 166 | "Nenhuma aplicação foi encontrada no cartão SD.\n" 167 | "Certifique-se que uma pasta com o nome /3ds\n" 168 | "existe na raiz do cartão SD e que contêm\n" 169 | "aplicações." 170 | ), 171 | STR_NL( 172 | "Geen toepassingen gevonden op de SD kaart.\n" 173 | "Zorg ervoor dat een map genaamd /3ds in de\n" 174 | "rootdirectory van de SD kaart aangemaakt is\n" 175 | "en de toepassingen bevat." 176 | ), 177 | STR_KO( 178 | "SD 카드에 애플리케이션이 없습니다.\n" 179 | "SD 카드의 루트에 /3ds 폴더가 있고\n" 180 | "애플리케이션을 포함하는지 확인해 주십시오." 181 | ), 182 | STR_RU( 183 | "На SD-карте нет приложений.\n" 184 | "Убедитесь, что на карте SD есть каталог с\n" 185 | "названием 3ds и она содержит приложения." 186 | ), 187 | STR_ZH( 188 | "内存卡找不到任何可执行的应用程序。\n" 189 | "请在内存卡的根目录建立「3ds」子目录,\n" 190 | "并存放自制应用软件至该目录。" 191 | ), 192 | STR_TW( 193 | "記憶體找不到任何可執行的應用程式。\n" 194 | "請在記憶體建立「3ds」資料夾,\n" 195 | "然後儲存自製軟體到此處。" 196 | ), 197 | }, 198 | 199 | [StrId_Reboot] = 200 | { 201 | STR_EN( 202 | "Returning to \xEE\x81\xB3HOME is not available.\n" 203 | "You're about to reboot your console.\n\n" 204 | " \xEE\x80\x80 Reboot\n" 205 | " \xEE\x80\x81 Cancel" 206 | ), 207 | STR_ES( 208 | "Volver a \xEE\x81\xB3HOME no está disponible.\n" 209 | "Está a punto de reiniciar su consola.\n\n" 210 | " \xEE\x80\x80 Reiniciar\n" 211 | " \xEE\x80\x81 Cancelar" 212 | ), 213 | STR_DE( 214 | "Rückkehr zu \xEE\x81\xB3HOME nicht verfügbar.\n" 215 | "Deine Konsole wird neu gestartet.\n\n" 216 | " \xEE\x80\x80 Neu starten\n" 217 | " \xEE\x80\x81 Abbrechen" 218 | ), 219 | STR_FR( 220 | "Retour au menu \xEE\x81\xB3HOME indisponible.\n" 221 | "Vous êtes sur le point de redémarrer\n" 222 | "votre console.\n\n" 223 | " \xEE\x80\x80 Redémarrer\n" 224 | " \xEE\x80\x81 Annuler" 225 | ), 226 | STR_IT( 227 | "Ritorno al menu \xEE\x81\xB3HOME non disponibile.\n" 228 | "Stai per riavviare la tua console.\n\n" 229 | " \xEE\x80\x80 Riavvia\n" 230 | " \xEE\x80\x81 Annulla" 231 | ), 232 | STR_JP( 233 | "\xEE\x81\xB3HOME に戻ることができませんでした。\n" 234 | "今すぐ本体を再起動してください。\n\n" 235 | " \xEE\x80\x80 再起動\n" 236 | " \xEE\x80\x81 キャンセル" 237 | ), 238 | STR_PT( 239 | "Regressar para \xEE\x81\xB3HOME não está\n" 240 | "disponível. Está a reiniciar a sua consola.\n\n" 241 | " \xEE\x80\x80 Reiniciar\n" 242 | " \xEE\x80\x81 Cancelar" 243 | ), 244 | STR_NL( 245 | "Terugkeren naar \xEE\x81\xB3HOME is niet\n" 246 | "beschikbaar.Wil je de console herstarten?\n\n" 247 | " \xEE\x80\x80 Herstarten\n" 248 | " \xEE\x80\x81 Annuleren" 249 | ), 250 | STR_KO( 251 | "\xEE\x81\xB3HOME 으로 돌아갈 수 없습니다.\n" 252 | "콘솔을 재부팅합니다.\n\n" 253 | " \xEE\x80\x80 재부팅\n" 254 | " \xEE\x80\x81 취소" 255 | ), 256 | STR_RU( 257 | "Возврат к \xEE\x81\xB3HOME недоступен.\n" 258 | "Вы собираетесь перезагрузить консоль.\n\n" 259 | " \xEE\x80\x80 Перезагрузите\n" 260 | " \xEE\x80\x81 Отмена" 261 | ), 262 | STR_ZH( 263 | "无法返回至主机的 \xEE\x81\xB3HOME 菜单。\n" 264 | "您需要重新启动您的 3DS 设备。\n\n" 265 | " \xEE\x80\x80 重启设备\n" 266 | " \xEE\x80\x81 取消操作" 267 | ), 268 | STR_TW( 269 | "無法返回至主機的 \xEE\x81\xB3HOME 選單。\n" 270 | "您需要重新啓動您的 3DS 設備。\n\n" 271 | " \xEE\x80\x80 重啓設備\n" 272 | " \xEE\x80\x81 取消操作" 273 | ), 274 | }, 275 | 276 | [StrId_ReturnToHome] = 277 | { 278 | STR_EN( 279 | "You're about to return to \xEE\x81\xB3HOME.\n\n" 280 | " \xEE\x80\x80 Return\n" 281 | " \xEE\x80\x81 Cancel\n" 282 | " \xEE\x80\x82 Reboot" 283 | ), 284 | STR_ES( 285 | "Está a punto de volver a \xEE\x81\xB3HOME.\n\n" 286 | " \xEE\x80\x80 Volver\n" 287 | " \xEE\x80\x81 Cancelar\n" 288 | " \xEE\x80\x82 Reiniciar" 289 | ), 290 | STR_DE( 291 | "Rückkehr zum \xEE\x81\xB3HOME-Menü.\n\n" 292 | " \xEE\x80\x80 Fortfahren\n" 293 | " \xEE\x80\x81 Abbrechen\n" 294 | " \xEE\x80\x82 Konsole neustarten" 295 | ), 296 | STR_FR( 297 | "Retour au menu \xEE\x81\xB3HOME.\n\n" 298 | " \xEE\x80\x80 Continuer\n" 299 | " \xEE\x80\x81 Annuler\n" 300 | " \xEE\x80\x82 Redémarrer" 301 | ), 302 | STR_IT( 303 | "Ritorno al menu \xEE\x81\xB3HOME.\n\n" 304 | " \xEE\x80\x80 Continua\n" 305 | " \xEE\x80\x81 Annulla\n" 306 | " \xEE\x80\x82 Riavvia" 307 | ), 308 | STR_JP( 309 | "\xEE\x81\xB3HOME に戻ろうとしています。\n\n" 310 | " \xEE\x80\x80 戻る\n" 311 | " \xEE\x80\x81 キャンセル\n" 312 | " \xEE\x80\x82 再起動" 313 | ), 314 | STR_PT( 315 | "Regressar ao menu \xEE\x81\xB3HOME.\n\n" 316 | " \xEE\x80\x80 Regressar\n" 317 | " \xEE\x80\x81 Cancelar\n" 318 | " \xEE\x80\x82 Reiniciar" 319 | ), 320 | STR_NL( 321 | "Je keert zo terug naar \xEE\x81\xB3HOME.\n\n" 322 | " \xEE\x80\x80 Doorgaan\n" 323 | " \xEE\x80\x81 Annuleren\n" 324 | " \xEE\x80\x82 Herstarten" 325 | ), 326 | STR_KO( 327 | "\xEE\x81\xB3HOME 으로 돌아갈 것 입니다.\n\n" 328 | " \xEE\x80\x80 돌아가기\n" 329 | " \xEE\x80\x81 취소\n" 330 | " \xEE\x80\x82 재부팅" 331 | ), 332 | STR_RU( 333 | "Вы возвращаетесь в \xEE\x81\xB3HOME.\n\n" 334 | " \xEE\x80\x80 Вернуть\n" 335 | " \xEE\x80\x81 Отмена\n" 336 | " \xEE\x80\x82 Перезагрузите" 337 | ), 338 | STR_ZH( 339 | "您即将返回到主機的 \xEE\x81\xB3HOME 菜单。\n\n" 340 | " \xEE\x80\x80 确认返回\n" 341 | " \xEE\x80\x81 取消操作\n" 342 | " \xEE\x80\x82 重启设备" 343 | ), 344 | STR_TW( 345 | "您即將返回到主機的 \xEE\x81\xB3HOME 選單。\n\n" 346 | " \xEE\x80\x80 確認返回\n" 347 | " \xEE\x80\x81 取消操作\n" 348 | " \xEE\x80\x82 重啓設備" 349 | ), 350 | }, 351 | 352 | [StrId_TitleSelector] = 353 | { 354 | STR_EN("Title selector"), 355 | STR_ES("Selector de título"), 356 | STR_DE("Titel-Selektor"), 357 | STR_FR("Sélecteur de titre"), 358 | STR_IT("Selettore del titolo"), 359 | STR_JP("タイトルセレクタ"), 360 | STR_PT("Selector de Títulos"), 361 | STR_NL("Titel selector"), 362 | STR_KO("타이틀 선택기"), 363 | STR_RU("Селектор заголовков"), 364 | STR_ZH("应用启动器"), 365 | STR_TW("自製程式啓動器"), 366 | }, 367 | 368 | [StrId_ErrorReadingTitleMetadata] = 369 | { 370 | STR_EN("Error reading title metadata.\n%08lX%08lX@%d"), 371 | STR_ES("Error leyendo los metadatos de los títulos.\n%08lX%08lX@%d"), 372 | STR_DE("Fehler beim lesen der Titel-Metadaten.\n%08lX%08lX@%d"), 373 | STR_FR( 374 | "Erreur lors de la lecture des métadonnées\n" 375 | "de titre.\n%08lX%08lX@%d" 376 | ), 377 | STR_IT("Errore nella lettura dei metadata dei titoli.\n%08lX%08lX@%d"), 378 | STR_JP("タイトルメタデータを読み取ることができませんでした。\n%08lX%08lX@%d"), 379 | STR_PT("Erro a ler os metadados do título.\n%08lX%08lX@%d"), 380 | STR_NL("Fout bij het lezen van titel metadata.\n%08lX%08lX@%d"), 381 | STR_KO("타이틀 메타데이터를 읽을 수 없습니다.\n%08lX%08lX@%d"), 382 | STR_RU("Ошибка чтения метаданных заголовка\n.%08lX%08lX@%d"), 383 | STR_ZH("读取软件相关信息时发生错误:\n%08lX%08lX@%d"), 384 | STR_TW("讀取軟體相關數據時發生錯誤:\n%08lX%08lX@%d"), 385 | }, 386 | 387 | [StrId_NoTitlesFound] = 388 | { 389 | STR_EN("No titles could be detected."), 390 | STR_ES("No se han podido detectar títulos."), 391 | STR_DE("Keine Titel gefunden."), 392 | STR_FR("Aucun titre trouvé."), 393 | STR_IT("Nessun titolo trovato."), 394 | STR_JP("タイトルが見つかりませんでした。"), 395 | STR_PT("Nenhum título foi encontrado."), 396 | STR_NL("Geen titels gevonden."), 397 | STR_KO("타이틀을 감지하지 못하였습니다."), 398 | STR_RU("Заголовки не обнаружены"), 399 | STR_ZH("主机内找不到任何软件。"), 400 | STR_TW("主機内找不到任何軟體。"), 401 | }, 402 | 403 | [StrId_SelectTitle] = 404 | { 405 | STR_EN( 406 | "Please select a target title.\n\n" 407 | " \xEE\x80\x80 Select\n" 408 | " \xEE\x80\x81 Cancel" 409 | ), 410 | STR_ES( 411 | "Elija el título de destino.\n\n" 412 | " \xEE\x80\x80 Seleccionar\n" 413 | " \xEE\x80\x81 Cancelar" 414 | ), 415 | STR_DE( 416 | "Bitte wähle den Ziel-Titel aus.\n\n" 417 | " \xEE\x80\x80 Auswählen\n" 418 | " \xEE\x80\x81 Abbrechen" 419 | ), 420 | STR_FR( 421 | "Veuillez sélectionner un titre de destination.\n\n" 422 | " \xEE\x80\x80 Sélectionner\n" 423 | " \xEE\x80\x81 Annuler" 424 | ), 425 | STR_IT( 426 | "Seleziona il titolo di destinazione.\n\n" 427 | " \xEE\x80\x80 Seleziona\n" 428 | " \xEE\x80\x81 Annulla" 429 | ), 430 | STR_JP( 431 | "ターゲットタイトルを選択してください。\n\n" 432 | " \xEE\x80\x80 選択\n" 433 | " \xEE\x80\x81 キャンセル" 434 | ), 435 | STR_PT( 436 | "Por favor escolha um título alvo.\n\n" 437 | " \xEE\x80\x80 Escolher\n" 438 | " \xEE\x80\x81 Cancelar" 439 | ), 440 | STR_NL( 441 | "Selecteer een titel.\n\n" 442 | " \xEE\x80\x80 Selecteer\n" 443 | " \xEE\x80\x81 Annuleren" 444 | ), 445 | STR_KO( 446 | "대상 타이틀을 선택해 주십시오.\n\n" 447 | " \xEE\x80\x80 선택\n" 448 | " \xEE\x80\x81 취소" 449 | ), 450 | STR_RU( 451 | "Выберите целевой заголовок.\n\n" 452 | " \xEE\x80\x80 Выберите\n" 453 | " \xEE\x80\x81 Отмена" 454 | ), 455 | STR_ZH( 456 | "请选择一个目标软件。\n\n" 457 | " \xEE\x80\x80 确认\n" 458 | " \xEE\x80\x81 取消" 459 | ), 460 | STR_TW( 461 | "請選擇一個目標軟體。\n\n" 462 | " \xEE\x80\x80 確認\n" 463 | " \xEE\x80\x81 取消" 464 | ), 465 | }, 466 | 467 | [StrId_NoTargetTitleSupport] = 468 | { 469 | STR_EN( 470 | "This homebrew exploit does not have support\n" 471 | "for launching applications under target titles.\n" 472 | "Please use a different exploit." 473 | ), 474 | STR_ES( 475 | "Este exploit de homebrew no tiene soporte para\n" 476 | "ejecutar aplicaciones bajo títulos de destino.\n" 477 | "Use otro exploit diferente." 478 | ), 479 | STR_DE( 480 | "Dieser Homebrew-Exploit unterstützt das Starten\n" 481 | "von Anwendungen unter Ziel-Titeln nicht.\n" 482 | "Bitte verwende einen anderen Exploit." 483 | ), 484 | STR_FR( 485 | "Cet exploit homebrew ne permet pas de lancer\n" 486 | "des applications sous des titres précis.\n" 487 | "Veuillez utiliser un exploit différent." 488 | ), 489 | STR_IT( 490 | "Questo exploit homebrew non permette di avviare\n" 491 | "applicazioni in titoli specifici.\n" 492 | "Utilizza un exploit diverso." 493 | ), 494 | STR_JP( 495 | "この自作エクスプロイトでは、ターゲットタイトルの\n" 496 | "下でアプリを起動することができません。\n" 497 | "別のエクスプロイトを使用してください。" 498 | ), 499 | STR_PT( 500 | "Este exploit homebrew não têm suporte\n" 501 | "para executar aplicações no título alvo.\n" 502 | "Por favor use um exploit diferente." 503 | ), 504 | STR_NL( 505 | "Deze homebrew exploit heeft geen ondersteuning\n" 506 | "voor het starten van toepassingen met de gekozen titlel.\n" 507 | "Gebruik een andere exploit." 508 | ), 509 | STR_KO( 510 | "이 Homebrew 익스플로잇은 해당 제목에서 애플리케이션을\n" 511 | "실행하는 것을 지원하지 않습니다.\n" 512 | "다른 익스플로잇을 사용해 주십시오." 513 | ), 514 | STR_RU( 515 | "Этот эксплойт homebrew не поддерживает запуск\n" 516 | "приложений под целевыми заголовками.\n" 517 | "Пожалуйста, используйте другой эксплойт." 518 | ), 519 | STR_ZH( 520 | "您所利用漏洞启动的「自制软件启动器」,\n" 521 | "无法在当前选中的软件中启动自制软件。\n" 522 | "请使用其它的漏洞来启动「自制软件启动器」。" 523 | ), 524 | STR_TW( 525 | "您所利用漏洞開啓的「自製軟體啓動器」\n" 526 | "無法在當前選中的軟體啓動自製軟件。\n" 527 | "請利用其它漏洞來啓動「自製軟體啓動器」。" 528 | ), 529 | }, 530 | 531 | [StrId_MissingTargetTitle] = 532 | { 533 | STR_EN( 534 | "The application you attempted to run requires\n" 535 | "a title that is not installed in the system." 536 | ), 537 | STR_ES( 538 | "La aplicación seleccionada necesita un título\n" 539 | "que no está instalado en el sistema." 540 | ), 541 | STR_DE( 542 | "Die ausgewählte Anwendung benötigt einen\n" 543 | "Titel der nicht installiert ist" 544 | ), 545 | STR_FR( 546 | "L'application sélectionnée requiert un titre\n" 547 | "qui n'a pas été installé sur le système." 548 | ), 549 | STR_IT( 550 | "L'applicazione selezionata richiede un titolo\n" 551 | "che non è installato nel sistema." 552 | ), 553 | STR_JP( 554 | "このアプリを実行するために\n" 555 | "必要なタイトルがインストールされていません。" 556 | ), 557 | STR_PT( 558 | "A aplicação que acabou de tentar executar requer\n" 559 | "um título que não está instalado neste sistema." 560 | ), 561 | STR_NL( 562 | "De toepassing die je probeert te starten\n" 563 | "vereist een titel die niet geinstalleerd is." 564 | ), 565 | STR_KO( 566 | "해당 애플리케이션은 시스템에 설치되지 않은\n" 567 | "타이틀을 요구합니다." 568 | ), 569 | STR_RU( 570 | "Для приложения требуется зависимость,\n" 571 | "которая не установлена." 572 | ), 573 | STR_ZH( 574 | "主机找不到该应用程序\n" 575 | "所需求的软件。" 576 | ), 577 | STR_TW( 578 | "主機找不到該應用程式\n" 579 | "所需求的軟體。" 580 | ), 581 | }, 582 | 583 | [StrId_NetworkError] = 584 | { 585 | STR_EN("An error occurred.\nTechnical details: [%s:%d]"), 586 | STR_ES("Ha ocurrido un error.\nDatos técnicos: [%s:%d]"), 587 | STR_DE("Ein Fehler ist aufgetreten\nTechnische Details: [%s:%d]"), 588 | STR_FR("Une erreur s'est produite.\nDétails techniques : [%s:%d]"), 589 | STR_IT("Si è verificato un errore.\nDettagli tecnici : [%s:%d]"), 590 | STR_JP("エラーが発生しました。\n技術的な詳細:[%s:%d]"), 591 | STR_PT("Ocorreu um erro.\nDetalhes técnicos: [%s:%d]"), 592 | STR_NL("Er is een fout opgetreden\nTechnische details: [%s:%d]"), 593 | STR_KO("오류가 발생했습니다.\n기술적인 세부사항: [%s:%d]"), 594 | STR_RU("Произошла ошибка.\nТехнические подробности: [%s:%d]"), 595 | STR_ZH("发生错误。\n详细错误信息:[%s:%d]"), 596 | STR_TW("發生錯誤。\n詳細錯誤資訊:[%s:%d]"), 597 | }, 598 | 599 | [StrId_NetworkOffline] = 600 | { 601 | STR_EN( 602 | "Offline, waiting for network…\n\n" 603 | " \xEE\x80\x81 Cancel" 604 | ), 605 | STR_ES( 606 | "Sin conexión, esperando a la red…\n\n" 607 | " \xEE\x80\x81 Cancelar" 608 | ), 609 | STR_DE( 610 | "Offline, warte auf Netzwerkverbindung…\n\n" 611 | " \xEE\x80\x81 Abbrechen" 612 | ), 613 | STR_FR( 614 | "Hors-ligne, en attente de connexion réseau…\n\n" 615 | " \xEE\x80\x81 Annuler" 616 | ), 617 | STR_IT( 618 | "Disconnesso, in attesa della connessione…\n\n" 619 | " \xEE\x80\x81 Annulla" 620 | ), 621 | STR_JP( 622 | "オフライン、ネット接続を待っています…\n\n" 623 | " \xEE\x80\x81 キャンセル" 624 | ), 625 | STR_PT( 626 | "[[Offline, waiting for network]]…\n\n" 627 | " \xEE\x80\x81 Cancelar" 628 | ), 629 | STR_NL( 630 | "[[Offline, waiting for network]]…\n\n" 631 | " \xEE\x80\x81 Annuleren" 632 | ), 633 | STR_KO( 634 | "오프라인, 네트워크 대기 중……\n\n" 635 | " \xEE\x80\x81 취소" 636 | ), 637 | STR_RU( 638 | "[[Offline, waiting for network]]…\n\n" 639 | " \xEE\x80\x81 Отмена" 640 | ), 641 | STR_ZH( 642 | "无法连接网络,等待网络连接…\n\n" 643 | " \xEE\x80\x81 取消等待" 644 | ), 645 | STR_TW( 646 | "當前離線,等待網路連線…\n\n" 647 | " \xEE\x80\x81 取消等待" 648 | ), 649 | }, 650 | 651 | [StrId_NetworkTransferring] = 652 | { 653 | STR_EN( 654 | "Transferring…\n" 655 | "%zu out of %zu KiB written" 656 | ), 657 | STR_ES( 658 | "Transfiriendo…\n" 659 | "%zu de %zu KiB escritos" 660 | ), 661 | STR_DE( 662 | "Übertragen…\n" 663 | "%zu von %zu KiB geschrieben" 664 | ), 665 | STR_FR( 666 | "Transfert…\n" 667 | "%zu sur %zu Kio écrits" 668 | ), 669 | STR_IT( 670 | "Trasferimento…\n" 671 | "%zu di %zu KiB scritti" 672 | ), 673 | STR_JP( 674 | "データを転送しています…\n" 675 | "%zu / %zu KiB 転送済み" 676 | ), 677 | STR_PT( 678 | "A transferir…\n" 679 | "%zu de %zu KiB escritos" 680 | ), 681 | STR_NL( 682 | "Overbrengen…\n" 683 | "%zu van %zu KiB geschreven" 684 | ), 685 | STR_KO( 686 | "전송 중…\n" 687 | "%zu / %zu KiB 쓰여짐" 688 | ), 689 | STR_RU( 690 | "Передача…\n" 691 | "%zu из %zu КиБ написано" 692 | ), 693 | STR_ZH( 694 | "正在传输…\n" 695 | "已完成 %zu / %zu KiB" 696 | ), 697 | STR_TW( 698 | "正在傳輸…\n" 699 | "已完成 %zu / %zu KiB" 700 | ), 701 | }, 702 | 703 | [StrId_NetLoader] = 704 | { 705 | STR_EN("3dslink NetLoader"), 706 | STR_ES("Cargador de programas 3dslink"), 707 | STR_DE("3dslink Netzwerk-Loader"), 708 | STR_FR("Chargeur de programme 3dslink"), 709 | STR_IT("Caricamento programmi 3dslink"), 710 | STR_JP("3dslinkネットローダ"), 711 | STR_PT("Carregador de programas 3dslink"), 712 | STR_NL("3dslink netwerk lader"), 713 | STR_KO("3dslink 네트워크 로더"), 714 | STR_RU("Загрузчик 3dslink"), 715 | STR_ZH("3dslink 网络执行模块"), 716 | STR_TW("3dslink 網路執行模組"), 717 | }, 718 | 719 | [StrId_NetLoaderUnavailable] = 720 | { 721 | STR_EN("The NetLoader is currently unavailable."), 722 | STR_ES("El cargador de programas no está disponible."), 723 | STR_DE("Der Netzwerk-Loader ist zur Zeit nicht verfügbar."), 724 | STR_FR("Le chargeur de programme 3dslink est indisponible."), 725 | STR_IT("Il caricamento programmi 3dslink non è disponibile."), 726 | STR_JP("ネットローダは現在利用できません。"), 727 | STR_PT("O carregador de programas está de momento indisponível."), 728 | STR_NL("De netwerk lader is niet beschikbaar."), 729 | STR_KO("현재 네트워크 로더는 사용할 수 없습니다."), 730 | STR_RU("Загрузчик в настоящее время недоступен."), 731 | STR_ZH("无法启动 3dslink 网络执行模块。"), 732 | STR_TW("無法啓動 3dslink 網路執行模組。"), 733 | }, 734 | 735 | [StrId_NetLoaderActive] = 736 | { 737 | STR_EN( 738 | "Waiting for 3dslink to connect…\n" 739 | "IP Addr: %lu.%lu.%lu.%lu, Port: %d\n\n" 740 | " \xEE\x80\x81 Cancel" 741 | ), 742 | STR_ES( 743 | "Esperando a que se conecte 3dslink…\n" 744 | "Dir.IP: %lu.%lu.%lu.%lu, Puerto: %d\n\n" 745 | " \xEE\x80\x81 Cancelar" 746 | ), 747 | STR_DE( 748 | "Warte auf Verbindung von 3dslink…\n" 749 | "IP Addr: %lu.%lu.%lu.%lu, Port: %d\n\n" 750 | " \xEE\x80\x81 Abbrechen" 751 | ), 752 | STR_FR( 753 | "En attente de la connexion de 3dslink…\n" 754 | "Adr. IP : %lu.%lu.%lu.%lu, Port : %d\n\n" 755 | " \xEE\x80\x81 Annuler" 756 | ), 757 | STR_IT( 758 | "In attesa della connessione di 3dslink…\n" 759 | "Ind. IP : %lu.%lu.%lu.%lu, Porta : %d\n\n" 760 | " \xEE\x80\x81 Annulla" 761 | ), 762 | STR_JP( 763 | "3dslinkが接続されるのを待っています…\n" 764 | "IPアドレス:%lu.%lu.%lu.%lu, ポート番号:%d\n\n" 765 | " \xEE\x80\x81 キャンセル" 766 | ), 767 | STR_PT( 768 | "A aguardar pela conexão do 3dslink…\n" 769 | "End. IP: %lu.%lu.%lu.%lu, Porta: %d\n\n" 770 | " \xEE\x80\x81 Cancelar" 771 | ), 772 | STR_NL( 773 | "Wachten op 3dslink verbinding…\n" 774 | "IP Addr: %lu.%lu.%lu.%lu, Poort: %d\n\n" 775 | " \xEE\x80\x81 Annuleren" 776 | ), 777 | STR_KO( 778 | "3dslink 연결 대기 중…\n" 779 | "IP 주소: %lu.%lu.%lu.%lu, 포트: %d\n\n" 780 | " \xEE\x80\x81 취소" 781 | ), 782 | STR_RU( 783 | "Ожидание подключения 3dslink…\n" 784 | "айпи адрес: %lu.%lu.%lu.%lu, Порт: %d\n\n" 785 | " \xEE\x80\x81 Отмена" 786 | ), 787 | STR_ZH( 788 | "等待 3dslink 连接…\n" 789 | "IP 地址:%lu.%lu.%lu.%lu,端口:%d\n\n" 790 | " \xEE\x80\x81 取消等待" 791 | ), 792 | STR_TW( 793 | "等待 3dslink 連接…\n" 794 | "IP 位址:%lu.%lu.%lu.%lu,連接埠:%d\n\n" 795 | " \xEE\x80\x81 取消等待" 796 | ), 797 | }, 798 | 799 | [StrId_NetSender] = 800 | { 801 | STR_EN("3dslink Server"), 802 | STR_ES("Servidor 3dslink"), 803 | STR_FR("Serveur 3dslink"), 804 | }, 805 | 806 | [StrId_NetSenderUnavailable] = 807 | { 808 | STR_EN("The 3dslink server is currently unavailable."), 809 | STR_ES("El servidor 3dslink no está disponible actualmente."), 810 | STR_FR("Le serveur 3dslink est indisponible."), 811 | }, 812 | 813 | [StrId_NetSenderInvalidIp] = 814 | { 815 | STR_EN("Invalid IP address."), 816 | STR_ES("Dirección IP inválida."), 817 | STR_FR("Adresse IP invalide."), 818 | }, 819 | 820 | [StrId_NetSenderActive] = 821 | { 822 | STR_EN( 823 | "Waiting for 3dslink to connect…\n\n\n" 824 | " \xEE\x80\x81 Cancel" 825 | ), 826 | STR_ES( 827 | "Esperando a que se conecte 3dslink…\n\n\n" 828 | " \xEE\x80\x81 Cancelar" 829 | ), 830 | STR_DE( 831 | "Warte auf Verbindung von 3dslink…\n\n\n" 832 | " \xEE\x80\x81 Abbrechen" 833 | ), 834 | STR_FR( 835 | "En attente de la connexion de 3dslink…\n\n\n" 836 | " \xEE\x80\x81 Annuler" 837 | ), 838 | STR_IT( 839 | "In attesa della connessione di 3dslink…\n\n\n" 840 | " \xEE\x80\x81 Annulla" 841 | ), 842 | STR_JP( 843 | "3dslinkが接続されるのを待っています…\n\n\n" 844 | " \xEE\x80\x81 キャンセル" 845 | ), 846 | STR_PT( 847 | "A aguardar pela conexão do 3dslink…\n\n\n" 848 | " \xEE\x80\x81 Cancelar" 849 | ), 850 | STR_NL( 851 | "Wachten op 3dslink verbinding…\n\n\n" 852 | " \xEE\x80\x81 Annuleren" 853 | ), 854 | STR_KO( 855 | "3dslink 연결 대기 중…\n\n\n" 856 | " \xEE\x80\x81 취소" 857 | ), 858 | STR_RU( 859 | "Ожидание подключения 3dslink…\n\n\n" 860 | " \xEE\x80\x81 Отмена" 861 | ), 862 | STR_ZH( 863 | "等待 3dslink 连接…\n\n\n" 864 | " \xEE\x80\x81 取消等待" 865 | ), 866 | STR_TW( 867 | "等待 3dslink 連接…\n\n\n" 868 | " \xEE\x80\x81 取消等待" 869 | ), 870 | }, 871 | }; 872 | --------------------------------------------------------------------------------