├── .gitignore ├── Makefile ├── README.md ├── meta ├── audio.wav ├── banner.png └── icon.png ├── romfs └── .gitignore └── source ├── autoloader.cpp ├── autoloader.h ├── camera.cpp ├── camera.h ├── common.h ├── graphics.cpp ├── graphics.h ├── http.cpp ├── http.h ├── main.cpp ├── mega.cpp └── mega.h /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | build/* 3 | output/* 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # TARGET # 2 | 3 | TARGET := 3DS 4 | LIBRARY := 0 5 | 6 | ifeq ($(TARGET),3DS) 7 | ifeq ($(strip $(DEVKITPRO)),) 8 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=devkitPro") 9 | endif 10 | 11 | ifeq ($(strip $(DEVKITARM)),) 12 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 13 | endif 14 | endif 15 | 16 | # COMMON CONFIGURATION # 17 | 18 | NAME := QRWebLoader 19 | 20 | BUILD_DIR := build 21 | OUTPUT_DIR := output 22 | INCLUDE_DIRS := include 23 | SOURCE_DIRS := source 24 | 25 | EXTRA_OUTPUT_FILES := 26 | 27 | LIBRARY_DIRS := $(DEVKITPRO)/citrus $(DEVKITPRO)/libctru $(DEVKITPRO)/portlibs/armv6k 28 | LIBRARIES := quirc mbedtls mbedcrypto jsmn sf2d citrus ctru m 29 | 30 | BUILD_FLAGS := 31 | RUN_FLAGS := 32 | 33 | # 3DS CONFIGURATION # 34 | 35 | TITLE := $(NAME) 36 | DESCRIPTION := QR Web Loader 37 | AUTHOR := ksanislo 38 | PRODUCT_CODE := QRWebLoader 39 | UNIQUE_ID := 0xB1989 40 | 41 | SYSTEM_MODE := 64MB 42 | SYSTEM_MODE_EXT := Legacy 43 | 44 | ICON_FLAGS := 45 | 46 | ROMFS_DIR := romfs/ 47 | BANNER_AUDIO := meta/audio.wav 48 | BANNER_IMAGE := meta/banner.png 49 | ICON := meta/icon.png 50 | 51 | # INTERNAL # 52 | 53 | include buildtools/make_base 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [FBI](https://github.com/Steveice10/FBI) now has preleminary QR install support, as such, further development efforts will be focused toward extending the MEGA file (and eventually folder support) to that application instead. 2 | 3 | # QR Web Loader 4 | 5 | Usage: 6 | Point CAM1 (rear facing, right side) at a QR code containing the URL of the .cia you would like to install. Usually about 9" away works well for a QR of normal size, but your device may differ. 7 | 8 | You can use both regular http/https links, as well as direct .cia file links on Mega.nz. 9 | 10 | Compiling this requires my fork of [citrus](https://github.com/ksanislo/citrus/) which provides an overloaded version of ctr::app::install() that implements callbacks, [quirc](https://github.com/dlbeer/quirc) for QR decoding, [jsmn](http://zserge.com/jsmn.html) for JSON, and [mbedTLS](https://tls.mbed.org) for AES/base64. 11 | -------------------------------------------------------------------------------- /meta/audio.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksanislo/QRWebLoader/16585b7faefce3665032dc837591a8aafdc5c90b/meta/audio.wav -------------------------------------------------------------------------------- /meta/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksanislo/QRWebLoader/16585b7faefce3665032dc837591a8aafdc5c90b/meta/banner.png -------------------------------------------------------------------------------- /meta/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksanislo/QRWebLoader/16585b7faefce3665032dc837591a8aafdc5c90b/meta/icon.png -------------------------------------------------------------------------------- /romfs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksanislo/QRWebLoader/16585b7faefce3665032dc837591a8aafdc5c90b/romfs/.gitignore -------------------------------------------------------------------------------- /source/autoloader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "http.h" 6 | #include "autoloader.h" 7 | #include "common.h" 8 | 9 | using namespace ctr; 10 | 11 | int useAutoloader(char *url, app::App app){ 12 | Result ret=0; 13 | FILE *fd = fopen(AUTOLOADER_FILE, "wb"); 14 | if(fd == NULL) return -1; 15 | fwrite(url,sizeof(char),strlen(url),fd); 16 | fclose(fd); 17 | 18 | app.titleId = AUTOLOADER_TITLEID; 19 | 20 | if(app::installed(app)){ 21 | app::launch(app); 22 | } else { 23 | strcpy(url,(char*)AUTOLOADER_URL); 24 | ret = http_getinfo(url, &app); 25 | if(ret!=0)return ret; 26 | 27 | ret = http_download(url, &app); 28 | if(ret!=0)return ret; 29 | 30 | app::launch(app); 31 | } 32 | 33 | while (core::running()){ 34 | hid::poll(); 35 | if (hid::pressed(hid::BUTTON_START)) 36 | break; 37 | } 38 | 39 | return 0; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /source/autoloader.h: -------------------------------------------------------------------------------- 1 | using namespace ctr; 2 | 3 | int useAutoloader(char *url, app::App app); 4 | -------------------------------------------------------------------------------- /source/camera.cpp: -------------------------------------------------------------------------------- 1 | #include <3ds.h> 2 | 3 | #include "camera.h" 4 | #include "common.h" 5 | 6 | void configCamera(){ 7 | CAMU_SetSize(SELECT_OUT1, SIZE_CTR_TOP_LCD, CONTEXT_A); 8 | CAMU_SetOutputFormat(SELECT_OUT1, OUTPUT_RGB_565, CONTEXT_A); 9 | CAMU_SetNoiseFilter(SELECT_OUT1, true); 10 | CAMU_SetWhiteBalance(SELECT_OUT1, WHITE_BALANCE_AUTO); 11 | CAMU_SetContrast(SELECT_OUT1, CONTRAST_NORMAL); 12 | CAMU_SetAutoExposure(SELECT_OUT1, true); 13 | CAMU_SetAutoWhiteBalance(SELECT_OUT1, true); 14 | // CAMU_SetAutoExposureWindow(SELECT_OUT1, 100, 20, 200, 200); 15 | // CAMU_SetAutoWhiteBalanceWindow(SELECT_OUT1, 100, 20, 400, 200); 16 | CAMU_SetAutoExposureWindow(SELECT_OUT1, 0, 0, 400, 240); 17 | CAMU_SetAutoWhiteBalanceWindow(SELECT_OUT1, 0, 0, 400, 240); 18 | CAMU_SetTrimming(PORT_CAM1, false); 19 | CAMU_Activate(SELECT_OUT1); 20 | } 21 | 22 | void takePicture(u16 *buf){ 23 | u32 bufSize; 24 | Handle camReceiveEvent = 0; 25 | 26 | CAMU_GetMaxBytes(&bufSize, WIDTH, HEIGHT); 27 | CAMU_SetTransferBytes(PORT_CAM1, bufSize, WIDTH, HEIGHT); 28 | CAMU_ClearBuffer(PORT_CAM1); 29 | CAMU_StartCapture(PORT_CAM1); 30 | CAMU_SetReceiving(&camReceiveEvent, (u8*)buf, PORT_CAM1, WIDTH * HEIGHT * 2, (s16) bufSize); 31 | svcWaitSynchronization(camReceiveEvent, WAIT_TIMEOUT); 32 | CAMU_StopCapture(PORT_CAM1); 33 | svcCloseHandle(camReceiveEvent); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /source/camera.h: -------------------------------------------------------------------------------- 1 | void configCamera(); 2 | void takePicture(u16 *buf); 3 | -------------------------------------------------------------------------------- /source/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define u8 uint8_t 12 | #define u16 uint16_t 13 | #define u32 uint32_t 14 | #define u64 uint64_t 15 | 16 | #define vu8 volatile u8 17 | #define vu16 volatile u16 18 | #define vu32 volatile u32 19 | #define vu64 volatile u64 20 | 21 | #define max(a,b) \ 22 | ({ __typeof__ (a) _a = (a); \ 23 | __typeof__ (b) _b = (b); \ 24 | _a > _b ? _a : _b; }) 25 | #define min(a,b) \ 26 | ({ __typeof__ (a) _a = (a); \ 27 | __typeof__ (b) _b = (b); \ 28 | _a < _b ? _a : _b; }) 29 | #define getbe16(d) \ 30 | (((d)[0]<<8) | (d)[1]) 31 | #define getbe32(d) \ 32 | ((((u32) getbe16(d))<<16) | ((u32) getbe16(d+2))) 33 | #define getbe64(d) \ 34 | ((((u64) getbe32(d))<<32) | ((u64) getbe32(d+4))) 35 | #define getle16(d) (*((u16*) (d))) 36 | #define getle32(d) (*((u32*) (d))) 37 | #define getle64(d) (*((u64*) (d))) 38 | #define align(v,a) \ 39 | (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) 40 | 41 | #define SSLOPTION_NOVERIFY 1<<9 42 | #define WIDTH 400 43 | #define HEIGHT 240 44 | #define WAIT_TIMEOUT 300000000ULL 45 | #define AUTOLOADER_FILE "autoloader.url" 46 | #define AUTOLOADER_URL "http://3ds.intherack.com/files/AutoLoader.cia" 47 | #define AUTOLOADER_TITLEID 0x000400000b198200 48 | #define TITLEID 0x000400000b198900 49 | 50 | 51 | /* 52 | // work files / directories 53 | #define GAME_DIR "/D9Game" 54 | #define WORK_DIR "/Decrypt9" 55 | 56 | inline char* strupper(const char* str) { 57 | char* buffer = (char*)malloc(strlen(str) + 1); 58 | for (int i = 0; i < strlen(str); ++i) 59 | buffer[i] = toupper((unsigned)str[i]); 60 | return buffer; 61 | } 62 | 63 | inline char* strlower(const char* str) { 64 | char* buffer = (char*)malloc(strlen(str) + 1); 65 | for (int i = 0; i < strlen(str); ++i) 66 | buffer[i] = tolower((unsigned)str[i]); 67 | return buffer; 68 | } 69 | */ 70 | -------------------------------------------------------------------------------- /source/graphics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | #include "graphics.h" 5 | 6 | void writePictureToIntensityMap(void *fb, void *img, u16 width, u16 height){ 7 | u8 *fb_8 = (u8*) fb; 8 | u16 *img_16 = (u16*) img; 9 | for(u32 i = 0; i < width * height; i++){ 10 | u16 data = img_16[i]; 11 | uint8_t b = ((data >> 11) & 0x1F) << 3; 12 | uint8_t g = ((data >> 5) & 0x3F) << 2; 13 | uint8_t r = (data & 0x1F) << 3; 14 | fb_8[i] = (r + g + b) / 3; 15 | } 16 | } 17 | 18 | void writePictureToFramebufferRGB565(void *fb, void *img, u16 x, u16 y, u16 width, u16 height) { 19 | u8 *fb_8 = (u8*) fb; 20 | u16 *img_16 = (u16*) img; 21 | int i, j, draw_x, draw_y; 22 | for(j = 0; j < height; j++) { 23 | for(i = 0; i < width; i++) { 24 | draw_y = y + height-1 - j; 25 | draw_x = x + i; 26 | u32 v = (draw_y + draw_x * height) * 3; 27 | u16 data = img_16[j * width + i]; 28 | uint8_t b = ((data >> 11) & 0x1F) << 3; 29 | uint8_t g = ((data >> 5) & 0x3F) << 2; 30 | uint8_t r = (data & 0x1F) << 3; 31 | fb_8[ v ] = r; 32 | fb_8[v+1] = g; 33 | fb_8[v+2] = b; 34 | } 35 | } 36 | } 37 | 38 | void putpixel(void *fb, int x, int y, u32 c) { 39 | u8 *fb_8 = (u8*) fb; 40 | u32 v = ((HEIGHT - y - 1) + (x * HEIGHT)) * 3; 41 | fb_8[ v ] = (((c) >> 0) & 0xFF); 42 | fb_8[v+1] = (((c) >> 8) & 0xFF); 43 | fb_8[v+2] = (((c) >> 16) & 0xFF); 44 | } 45 | 46 | void bhm_line(void *fb,int x1,int y1,int x2,int y2,u32 c) 47 | { 48 | int x,y,dx,dy,dx1,dy1,px,py,xe,ye,i; 49 | dx=x2-x1; 50 | dy=y2-y1; 51 | dx1=fabs(dx); 52 | dy1=fabs(dy); 53 | px=2*dy1-dx1; 54 | py=2*dx1-dy1; 55 | if(dy1<=dx1){ 56 | if(dx>=0){ 57 | x=x1; 58 | y=y1; 59 | xe=x2; 60 | } else { 61 | x=x2; 62 | y=y2; 63 | xe=x1; 64 | } 65 | putpixel(fb,x,y,c); 66 | for(i=0;x0 && dy>0)){ 72 | y=y+1; 73 | } else { 74 | y=y-1; 75 | } 76 | px=px+2*(dy1-dx1); 77 | } 78 | putpixel(fb,x,y,c); 79 | } 80 | } else { 81 | if(dy>=0){ 82 | x=x1; 83 | y=y1; 84 | ye=y2; 85 | } else { 86 | x=x2; 87 | y=y2; 88 | ye=y1; 89 | } 90 | putpixel(fb,x,y,c); 91 | for(i=0;y0 && dy>0)){ 97 | x=x+1; 98 | } else { 99 | x=x-1; 100 | } 101 | py=py+2*(dx1-dy1); 102 | } 103 | putpixel(fb,x,y,c); 104 | } 105 | } 106 | } 107 | 108 | -------------------------------------------------------------------------------- /source/graphics.h: -------------------------------------------------------------------------------- 1 | void writePictureToIntensityMap(void *fb, void *img, u16 width, u16 height); 2 | void writePictureToFramebufferRGB565(void *fb, void *img, u16 x, u16 y, u16 width, u16 height); 3 | void putpixel(void *fb, int x, int y, u32 c); 4 | void bhm_line(void *fb,int x1,int y1,int x2,int y2,u32 c); 5 | -------------------------------------------------------------------------------- /source/http.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "http.h" 10 | 11 | extern "C" { 12 | #include 13 | } 14 | 15 | #include "autoloader.h" 16 | #include "common.h" 17 | 18 | using namespace ctr; 19 | 20 | bool onProgress(u64 pos, u64 size); 21 | 22 | static httpcContext context; 23 | 24 | int fetchHttpData(void *buf, u32 bufSize, u32 *bufFill){ 25 | return httpcDownloadData(&context, (u8*)buf, bufSize, bufFill); 26 | } 27 | 28 | Result http_getinfo(char *url, app::App *app){ 29 | Result ret=0; 30 | u8* buf; 31 | u32 statuscode=0; 32 | 33 | app->mediaType = fs::SD; 34 | app->size = 0; 35 | app->titleId = 0x0000000000000000; 36 | 37 | ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 0); 38 | if(ret!=0){ 39 | goto stop; 40 | } 41 | 42 | // We should /probably/ make sure Range: is supported 43 | // before we try to do this, but these 8 bytes are the titleId 44 | ret = httpcAddRequestHeaderField(&context, (char*)"Range", (char*)"bytes=11292-11299"); 45 | if(ret!=0){ 46 | goto stop; 47 | } 48 | 49 | // Disable the SSL certificate checks. 50 | ret = httpcSetSSLOpt(&context, SSLOPTION_NOVERIFY); 51 | if(ret!=0){ 52 | goto stop; 53 | } 54 | 55 | ret = httpcBeginRequest(&context); 56 | if(ret!=0){ 57 | goto stop; 58 | } 59 | 60 | ret = httpcGetResponseStatusCode(&context, &statuscode, 0); 61 | if(ret!=0){ 62 | goto stop; 63 | } 64 | 65 | if(statuscode==301||statuscode==302){ 66 | ret = httpcGetResponseHeader(&context, (char*)"Location", (char*)url, QUIRC_MAX_PAYLOAD-1); 67 | if(ret!=0){ 68 | goto stop; 69 | } 70 | 71 | ret=http_getinfo(url, app); 72 | goto stop; 73 | } 74 | 75 | if(statuscode==206){ 76 | buf = (u8*)malloc(8); // Allocate u8*8 == u64 77 | if(buf==NULL){ 78 | goto stop; 79 | } 80 | memset(buf, 0, 8); // Zero out 81 | 82 | ret = httpcDownloadData(&context, buf, 8, NULL); 83 | 84 | // Safely convert our 8 byte string into a u64 85 | app->titleId = ((u64)buf[0]<<56|(u64)buf[1]<<48|(u64)buf[2]<<40|(u64)buf[3]<<32|(u64)buf[4]<<24|(u64)buf[5]<<16|(u64)buf[6]<<8|(u64)buf[7]); 86 | free(buf); 87 | 88 | buf = (u8*)malloc(64); 89 | if(buf==NULL){ 90 | goto stop; 91 | } 92 | 93 | ret = httpcGetResponseHeader(&context, (char*)"Content-Range", (char*)buf, 64); 94 | if(ret==0){ 95 | char *ptr = strchr((const char *)buf, 47); 96 | app->size = atoll(&ptr[1]); 97 | } 98 | 99 | free(buf); 100 | } else { 101 | printf("HTTP status: %lu\n", statuscode); 102 | } 103 | 104 | stop: 105 | ret = httpcCloseContext(&context); 106 | 107 | return ret; 108 | } 109 | 110 | Result http_download(char *url, app::App *app){ 111 | Result ret=0; 112 | u32 statuscode=0; 113 | char *buf; 114 | 115 | ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 0); 116 | if(ret!=0){ 117 | goto stop; 118 | } 119 | 120 | /* No gzip until libarchive is implemented. 121 | ret = httpcAddRequestHeaderField(&context, (char*)"Accept-Encoding", (char*)"gzip, deflate"); 122 | if(ret!=0){ 123 | goto stop; 124 | } 125 | */ 126 | 127 | // Disable the SSL certificate checks. 128 | ret = httpcSetSSLOpt(&context, 1<<9); 129 | if(ret!=0){ 130 | goto stop; 131 | } 132 | 133 | ret = httpcBeginRequest(&context); 134 | if(ret!=0){ 135 | goto stop; 136 | } 137 | 138 | ret = httpcGetResponseStatusCode(&context, &statuscode, 0); 139 | if(ret!=0){ 140 | goto stop; 141 | } 142 | if(statuscode==200){ 143 | buf = (char*)malloc(16); 144 | if(buf==NULL){ 145 | goto stop; 146 | } 147 | memset(buf, 0, 16); 148 | 149 | ret = httpcGetResponseHeader(&context, (char*)"Content-Encoding", (char*)buf, 16); 150 | if(ret==0){ 151 | printf("Content-Encoding: %s\n", buf); 152 | } 153 | 154 | app::install(app->mediaType, &fetchHttpData, app->size, &onProgress); 155 | 156 | free(buf); 157 | } else { 158 | printf("HTTP status: %lu\n", statuscode); 159 | } 160 | 161 | stop: 162 | ret = httpcCloseContext(&context); 163 | 164 | return ret; 165 | } 166 | 167 | int doWebInstall (char *url){ 168 | app::App app; 169 | Result ret=0; 170 | 171 | printf("Processing %s\n",url); 172 | 173 | ret = http_getinfo(url, &app); 174 | if(ret!=0)return ret; 175 | 176 | if(app.titleId>>48 != 0x4){ // 3DS titleId 177 | printf("Not a 3DS .cia file.\n"); 178 | return -1; 179 | } 180 | 181 | printf("titleId: 0x%016llx\n", app.titleId); 182 | if (app.titleId == TITLEID) printf("This .cia matches our titleId, direct\ninstall and uninstall disabled.\n"); 183 | printf("Press B to cancel\n"); 184 | if (app.titleId != AUTOLOADER_TITLEID) printf(" Y to use Autoloader\n"); 185 | if (app.titleId != TITLEID && app::installed(app)) printf(" X to uninstall\n"); 186 | if (app.titleId != TITLEID) printf(" A to install\n"); 187 | 188 | while (core::running()){ 189 | hid::poll(); 190 | 191 | if (hid::pressed(hid::BUTTON_X) && app.titleId != TITLEID && app::installed(app)){ 192 | printf("Uninstalling..."); 193 | app::uninstall(app); 194 | printf("done.\n"); 195 | return 0; 196 | } 197 | 198 | if (hid::pressed(hid::BUTTON_Y)){ 199 | useAutoloader(url, app); 200 | return 0; 201 | } 202 | 203 | if (hid::pressed(hid::BUTTON_A) && app.titleId != TITLEID && ! app::installed(app)){ 204 | ret = http_download(url, &app); 205 | if(ret!=0)return ret; 206 | 207 | printf("titleId: 0x%016llx\nInstall finished.\n", app.titleId); 208 | return ret; 209 | } 210 | 211 | if (hid::pressed(hid::BUTTON_B)) 212 | break; 213 | } 214 | return 0; 215 | } 216 | 217 | -------------------------------------------------------------------------------- /source/http.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace ctr; 4 | 5 | Result http_getinfo(char *url, app::App *app); 6 | Result http_download(char *url, app::App *app); 7 | int doWebInstall (char *url); 8 | 9 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | extern "C" { 11 | #include 12 | } 13 | 14 | #include "http.h" 15 | #include "mega.h" 16 | #include "graphics.h" 17 | #include "camera.h" 18 | #include "common.h" 19 | 20 | extern "C" { 21 | u32 __stacksize__ = 0x40000; 22 | } 23 | 24 | using namespace ctr; 25 | 26 | bool onProgress(u64 pos, u64 size){ 27 | printf("pos: %" PRId64 "-%" PRId64 "\n", pos, size); 28 | hid::poll(); 29 | return !hid::pressed(hid::BUTTON_B); 30 | } 31 | 32 | int main(int argc, char **argv){ 33 | core::init(argc); 34 | httpcInit(0x1000); 35 | camInit(); 36 | 37 | gfxSetDoubleBuffering(GFX_TOP, true); 38 | gfxSetDoubleBuffering(GFX_BOTTOM, false); 39 | 40 | consoleInit(GFX_BOTTOM,NULL); 41 | gfxSet3D(false); 42 | 43 | printf("Initializing camera..."); 44 | gpu::swapBuffers(true); 45 | configCamera(); 46 | printf("done.\n"); 47 | gpu::swapBuffers(true); 48 | 49 | u16 *camBuf = (u16*)malloc(WIDTH * HEIGHT * 2); 50 | if(!camBuf){ 51 | printf("Failed to allocate memory!"); 52 | return 0; 53 | } 54 | 55 | struct quirc *qr; 56 | 57 | qr = quirc_new(); 58 | if (!qr){ 59 | printf("Failed to allocate memory"); 60 | return 0; 61 | } 62 | 63 | if (quirc_resize(qr, WIDTH, HEIGHT) < 0){ 64 | printf("Failed to allocate video memory"); 65 | return 0; 66 | } 67 | 68 | printf("Watching for QR codes...\nPress START to exit.\n"); 69 | 70 | // Main loop 71 | while (core::running()) 72 | { 73 | hid::poll(); 74 | 75 | if (hid::pressed(hid::BUTTON_START)) 76 | break; // break in order to return to hbmenu 77 | 78 | takePicture(camBuf); 79 | writePictureToFramebufferRGB565(gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), camBuf, 0, 0, WIDTH, HEIGHT); 80 | gpu::swapBuffers(true); 81 | 82 | int w=WIDTH, h=HEIGHT; 83 | u8 *image = (u8*)quirc_begin(qr, &w, &h); 84 | writePictureToIntensityMap(image, camBuf, WIDTH, HEIGHT); 85 | quirc_end(qr); 86 | 87 | int num_codes = quirc_count(qr); 88 | 89 | for (int i = 0; i < num_codes; i++){ 90 | struct quirc_code code; 91 | struct quirc_data data; 92 | quirc_decode_error_t err; 93 | 94 | quirc_extract(qr, i, &code); 95 | 96 | writePictureToFramebufferRGB565(gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), camBuf, 0, 0, WIDTH, HEIGHT); 97 | for (int j = 0; j < 4; j++) { 98 | struct quirc_point *a = &code.corners[j]; 99 | struct quirc_point *b = &code.corners[(j + 1) % 4]; 100 | bhm_line(gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), a->x, a->y, b->x, b->y, 0x0077FF77); 101 | } 102 | gpu::swapBuffers(true); 103 | 104 | err = quirc_decode(&code, &data); 105 | if (!err){ 106 | CAMU_Activate(SELECT_NONE); 107 | if (strncmp((const char *)data.payload, "https://mega.nz/", 16)==0 || strncmp((const char *)data.payload, "https://mega.co.nz/", 19)==0) 108 | doMegaInstall((char*)data.payload); 109 | else 110 | doWebInstall((char*)data.payload); 111 | CAMU_Activate(SELECT_OUT1); 112 | printf("Watching for QR codes...\nPress START to exit.\n"); 113 | } 114 | } 115 | } 116 | 117 | CAMU_Activate(SELECT_NONE); 118 | 119 | printf("Cleaning up..."); 120 | gpu::swapBuffers(true); 121 | quirc_destroy(qr); 122 | 123 | free(camBuf); 124 | 125 | // Exit services 126 | camExit(); 127 | httpcExit(); 128 | printf("done.\n"); 129 | 130 | core::exit(); 131 | 132 | return 0; 133 | } 134 | 135 | -------------------------------------------------------------------------------- /source/mega.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | extern "C" { 9 | #include 10 | } 11 | 12 | #include "mega.h" 13 | #include "common.h" 14 | 15 | using namespace ctr; 16 | 17 | static u8 *aeskey, *aesiv; 18 | static httpcContext context; 19 | 20 | 21 | bool onProgress(u64 pos, u64 size); 22 | 23 | uint64_t swap_uint64( uint64_t val ) { 24 | val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL ); 25 | val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL ); 26 | return (val << 32) | (val >> 32); 27 | } 28 | 29 | int jsoneq(const char *json, jsmntok_t *tok, const char *s) { 30 | if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start && 31 | strncmp(json + tok->start, s, tok->end - tok->start) == 0) { 32 | return 0; 33 | } 34 | return -1; 35 | } 36 | 37 | int decodeMegaFileName(char *filename, char *buf) { 38 | int i, r, c; 39 | int len = strlen(buf); 40 | size_t olen; 41 | unsigned char zeroiv[16]={0}; 42 | char *jsonbuf; 43 | jsmn_parser p; 44 | jsmntok_t t[128]; // We expect no more than 128 tokens 45 | 46 | for(c = 0; c < len + ((len * 3) & 0x03); c++){ 47 | if(buf[c] == '-') 48 | buf[c] = '+'; 49 | else if(buf[c] == '_') 50 | buf[c] = '/'; 51 | if (c >= len) 52 | buf[c] = '='; 53 | } 54 | 55 | strcpy(filename, buf); // store in our return filename for temp space 56 | 57 | mbedtls_base64_decode(NULL, 0, &olen, (const unsigned char*)filename, strlen(filename)); 58 | buf = (char*)realloc(buf, olen); 59 | mbedtls_base64_decode((unsigned char*)buf, olen, &olen, (const unsigned char*)filename, strlen(filename)); 60 | 61 | mbedtls_aes_context aes; 62 | mbedtls_aes_setkey_dec( &aes, aeskey, 128 ); 63 | jsonbuf = (char*)malloc(olen); 64 | mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, olen, &zeroiv[0], (unsigned char*) buf, (unsigned char*) jsonbuf); 65 | mbedtls_aes_free( &aes ); 66 | if(strncmp("MEGA", jsonbuf, 4)!=0){ 67 | printf("MEGA magic string not found.\nDecryption key bad/missing?\n"); 68 | return 1; 69 | } 70 | jsonbuf+=4; // Bypass header. 71 | 72 | r = jsmn_parse(&p, jsonbuf, strlen(jsonbuf), t, sizeof(t)/sizeof(t[0])); 73 | if (r < 0) { 74 | printf("Failed to parse JSON: %d\n", r); 75 | return 1; 76 | } 77 | 78 | if (r < 1 || t[0].type != JSMN_OBJECT) { 79 | printf("Object expected\n"); 80 | return 1; 81 | } 82 | 83 | for (i = 1; i < r; i++) { 84 | if (jsoneq(jsonbuf, &t[i], "n") == 0) { 85 | strncpy(filename,jsonbuf + t[i+1].start,t[i+1].end-t[i+1].start); 86 | filename[t[i+1].end-t[i+1].start] = (char)NULL; 87 | i++; 88 | } 89 | } 90 | 91 | free(jsonbuf-4); // Free the original jsonbuf 92 | return 0; 93 | } 94 | 95 | int parseMegaFileResponse(char *jsonString, char *filename, u32 *size, char* url) { 96 | int i, r; 97 | char *buf; 98 | jsmn_parser p; 99 | jsmntok_t t[128]; /* We expect no more than 128 tokens */ 100 | 101 | jsmn_init(&p); 102 | r = jsmn_parse(&p, jsonString, strlen(jsonString), t, sizeof(t)/sizeof(t[0])); 103 | if (r < 0) { 104 | printf("Failed to parse JSON: %d\n", r); 105 | return 1; 106 | } 107 | 108 | /* Assume the top-level element is an object */ 109 | if (r < 1 || t[0].type != JSMN_ARRAY) { 110 | printf("Array expected\n"); 111 | return 1; 112 | } 113 | 114 | /* Loop over all keys of the root object */ 115 | for (i = 2; i < r; i++) { 116 | if (jsoneq(jsonString, &t[i], "s") == 0) { // size 117 | *size = strtoul(jsonString + t[i+1].start, NULL, 10); 118 | i++; 119 | } else if (jsoneq(jsonString, &t[i], "at") == 0) { // filename 120 | // This will be base64 encoded, allocate with padding. 121 | buf=(char*)malloc(t[i+1].end-t[i+1].start + 1 + ((t[i+1].end-t[i+1].start * 3) & 0x03) ); 122 | memset(buf,0,t[i+1].end-t[i+1].start + 1 + ((t[i+1].end-t[i+1].start * 3) & 0x03)); 123 | strncpy(buf,jsonString + t[i+1].start,t[i+1].end-t[i+1].start); 124 | buf[t[i+1].end-t[i+1].start + ((t[i+1].end-t[i+1].start * 3) & 0x03)] = (char)NULL; 125 | decodeMegaFileName(filename, buf); 126 | free(buf); 127 | i++; 128 | } else if (jsoneq(jsonString, &t[i], "g") == 0) { // url 129 | strncpy(url,jsonString + t[i+1].start,t[i+1].end-t[i+1].start); 130 | url[t[i+1].end-t[i+1].start] = (char)NULL; 131 | i++; 132 | } 133 | } 134 | return 0; 135 | } 136 | 137 | int fetchMegaData(void *buf, u32 bufSize, u32 *bufFill){ 138 | Result ret = 0; 139 | u32 downloadpos = 0; 140 | u64 *aesiv_64 = (u64*) aesiv; 141 | 142 | char *startptr, *endptr; 143 | u32 startpos = 0; 144 | u32 decodepos = 0; 145 | size_t chunksize = 0; 146 | 147 | size_t offset=0; 148 | 149 | unsigned char stream_block[16]; 150 | u8 *dlbuf; 151 | 152 | u8 *contentrange = (u8*)malloc(256); 153 | if(httpcGetResponseHeader(&context, (char*)"Content-Range", (char*)contentrange, 256)==0){ 154 | startptr = strchr((char *)contentrange, ' '); 155 | startptr++[0] = (char)NULL; // end string 156 | endptr = strchr((char *)startptr, '-'); 157 | endptr[0] = (char)NULL; // end string 158 | startpos = atol(startptr); 159 | } 160 | free(contentrange); 161 | 162 | ret = httpcGetDownloadSizeState(&context, &downloadpos, NULL); 163 | if(ret!=0){ 164 | goto stop; 165 | } 166 | startpos += downloadpos; 167 | 168 | dlbuf = (u8*)malloc(bufSize); 169 | memset(dlbuf, 0, bufSize); 170 | 171 | ret = httpcDownloadData(&context, dlbuf, bufSize, bufFill); 172 | if(ret!=0 && ret != (s32)HTTPC_RESULTCODE_DOWNLOADPENDING){ 173 | goto stop; 174 | } 175 | 176 | mbedtls_aes_context aes; 177 | mbedtls_aes_setkey_enc( &aes, aeskey, 128 ); 178 | 179 | aesiv_64[1] = swap_uint64((u64)startpos/16); // Set our IV block location. 180 | offset = startpos % 16; // Set our starting block offset 181 | 182 | if(offset != 0){ // If we have an offset, we need to pre-fill stream_block 183 | mbedtls_aes_crypt_ecb( &aes, MBEDTLS_AES_ENCRYPT, aesiv, stream_block ); 184 | aesiv_64[1] = swap_uint64(((u64)startpos/16) + 1); // Bump counter 185 | } 186 | 187 | for (decodepos = 0; decodepos < *bufFill ; decodepos+=0x1000) { // AES decrypt in 4K blocks 188 | chunksize = (((*bufFill - decodepos) < 0x1000) ? (*bufFill - decodepos) : 0x1000 ); 189 | mbedtls_aes_crypt_ctr( &aes, chunksize, &offset, aesiv, stream_block, dlbuf+decodepos, (unsigned char*)buf+decodepos ); 190 | if (decodepos + chunksize == *bufFill) break; 191 | } 192 | 193 | mbedtls_aes_free( &aes ); 194 | free(dlbuf); 195 | 196 | stop: 197 | return ret; 198 | } 199 | 200 | int decodeMegaFileKey(char* str) 201 | { 202 | u64 *aeskey_64 = (u64*) aeskey; 203 | u64 *aesiv_64 = (u64*) aesiv; 204 | int len = strlen(str); 205 | int newlen = len + ((len * 3) & 0x03); 206 | int i; 207 | size_t olen; 208 | u8 *buf; 209 | u64 *buf_64; 210 | 211 | //Remove URL base64 encoding, and pad with = 212 | for(i = 0; i < newlen; i++){ 213 | if(str[i] == '-') 214 | str[i] = '+'; 215 | else if(str[i] == '_') 216 | str[i] = '/'; 217 | 218 | if (i >= len) 219 | str[i] = '='; 220 | } 221 | 222 | buf = (u8*)malloc(256/8); 223 | int ret = mbedtls_base64_decode((unsigned char*)buf, (256/8), &olen, (const unsigned char*)str, newlen); 224 | buf_64 = (u64*) buf; 225 | aeskey_64[0] = buf_64[0] ^ buf_64[2]; 226 | aeskey_64[1] = buf_64[1] ^ buf_64[3]; 227 | aesiv_64[0] = buf_64[2]; 228 | aesiv_64[1] = 0; 229 | free(buf); 230 | return ret; 231 | } 232 | 233 | int prepareMegaInstall (char *url, app::App *app){ 234 | Result ret=0; 235 | 236 | char *ptr, *locptr, *keyptr; 237 | u8 *req, *buf; 238 | u32 bufLen; 239 | int reqlen; 240 | u32 filesize; 241 | char *filename; 242 | char megafileid[128]; 243 | 244 | // Allocate space for 128 bit AES key and 128 bit AES IV 245 | aeskey = (u8*)malloc(128 / 8); 246 | aesiv = (u8*)malloc(128 / 8); 247 | 248 | // Allocate URL length+4 bytes since we may need to pad with = 249 | buf = (u8*)malloc(strlen(url)+4); 250 | strcpy((char*)buf, url); 251 | ptr = strchr((const char *)buf, '#'); 252 | if (ptr[1] != '!'){ 253 | printf("URL not supported\n"); 254 | goto stop; 255 | } 256 | locptr = strchr((const char *)ptr, '!'); 257 | locptr++[0] = (char)NULL; // end prev string 258 | keyptr = strchr((const char *)locptr, '!'); 259 | keyptr++[0] = (char)NULL; // end prev string 260 | 261 | strncpy(megafileid, locptr, sizeof(megafileid)); 262 | 263 | // Decode the URL for our AES key 264 | decodeMegaFileKey(keyptr); 265 | 266 | req = (u8*)malloc(256); 267 | reqlen = sprintf((char*)req, "[{\"a\":\"g\",\"g\":1,\"p\":\"%s\"}]", megafileid); 268 | 269 | httpcOpenContext(&context, HTTPC_METHOD_POST, (char*)"https://g.api.mega.co.nz/cs", 0); 270 | httpcSetSSLOpt(&context, 1<<9); 271 | httpcAddPostDataRaw(&context, (u32*)req, reqlen); 272 | 273 | httpcBeginRequest(&context); 274 | 275 | buf = (u8*)realloc(buf, 0x1000); 276 | ret = httpcDownloadData(&context, buf, 0x1000, &bufLen); 277 | free(req); 278 | 279 | filename = (char*)malloc(0x1000); 280 | parseMegaFileResponse((char*)buf, filename, &filesize, url); 281 | httpcCloseContext(&context); 282 | printf("file: %s\nsize: %lu\n", filename, filesize); 283 | 284 | app->size = filesize; 285 | app->mediaType = fs::SD; 286 | 287 | free(filename); 288 | stop: 289 | free(buf); 290 | return ret; 291 | } 292 | 293 | int getMegaInfo (char *url, app::App *app){ 294 | Result ret=0; 295 | 296 | u8 *buf; 297 | u32 bufFill; 298 | u32 statuscode; 299 | 300 | ret=httpcOpenContext(&context, HTTPC_METHOD_GET, url, 0); 301 | ret=httpcSetSSLOpt(&context, 1<<9); 302 | ret=httpcAddRequestHeaderField(&context, (char*)"Range", (char*)"bytes=11292-11299"); 303 | ret=httpcBeginRequest(&context); 304 | httpcGetResponseStatusCode(&context, &statuscode, 0); 305 | 306 | buf = (u8*)malloc(8); 307 | fetchMegaData(buf, 8, &bufFill); 308 | app->titleId = ((u64)buf[0]<<56|(u64)buf[1]<<48|(u64)buf[2]<<40|(u64)buf[3]<<32|(u64)buf[4]<<24|(u64)buf[5]<<16|(u64)buf[6]<<8|(u64)buf[7]); 309 | httpcCloseContext(&context); 310 | free(buf); 311 | return ret; 312 | } 313 | 314 | int getMegaDownload (char *url, app::App *app){ 315 | Result ret=0; 316 | u32 statuscode; 317 | 318 | ret=httpcOpenContext(&context, HTTPC_METHOD_GET, url, 0); 319 | ret=httpcSetSSLOpt(&context, 1<<9); 320 | ret=httpcBeginRequest(&context); 321 | httpcGetResponseStatusCode(&context, &statuscode, 0); 322 | app::install(app->mediaType, &fetchMegaData, app->size, &onProgress); 323 | httpcCloseContext(&context); 324 | return ret; 325 | } 326 | 327 | void cleanupMegaInstall() { 328 | free(aeskey); 329 | free(aesiv); 330 | } 331 | 332 | int doMegaInstall (char *url){ 333 | app::App app; 334 | Result ret=0; 335 | 336 | printf("Processing %s\n",url); 337 | 338 | prepareMegaInstall(url, &app); 339 | 340 | ret = getMegaInfo(url, &app); 341 | if(ret!=0)return ret; 342 | 343 | if(app.titleId>>48 != 0x4){ // 3DS titleId 344 | printf("Not a 3DS .cia file.\n"); 345 | return -1; 346 | } 347 | 348 | printf("titleId: 0x%016llx\n", app.titleId); 349 | if (app.titleId == TITLEID) printf("This .cia matches our titleId, direct\ninstall and uninstall disabled.\n"); 350 | printf("Press B to cancel\n"); 351 | if (app.titleId != TITLEID && app::installed(app)) printf(" X to uninstall\n"); 352 | if (app.titleId != TITLEID) printf(" A to install\n"); 353 | 354 | while (core::running()){ 355 | hid::poll(); 356 | 357 | if (hid::pressed(hid::BUTTON_X) && app.titleId != TITLEID && app::installed(app)){ 358 | printf("Uninstalling..."); 359 | app::uninstall(app); 360 | printf("done.\n"); 361 | return 0; 362 | } 363 | 364 | if (hid::pressed(hid::BUTTON_A) && app.titleId != TITLEID && ! app::installed(app)){ 365 | ret = getMegaDownload(url, &app); 366 | if(ret!=0)return ret; 367 | 368 | printf("titleId: 0x%016llx\nInstall finished.\n", app.titleId); 369 | return ret; 370 | } 371 | 372 | if (hid::pressed(hid::BUTTON_B)) 373 | break; 374 | } 375 | return 0; 376 | } 377 | 378 | -------------------------------------------------------------------------------- /source/mega.h: -------------------------------------------------------------------------------- 1 | int decodeMegaFileKey(void *aeskey, void *aesiv, char* str); 2 | int doMegaInstall (char *url); 3 | --------------------------------------------------------------------------------