├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── meta ├── audio.wav ├── banner.png └── icon.png ├── romfs └── .gitignore └── source └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | build/* 3 | output/* 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "buildtools"] 2 | path = buildtools 3 | url = git@github.com:Steveice10/buildtools.git 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 := AutoLoader 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 := citrus ctru m 29 | 30 | BUILD_FLAGS := 31 | RUN_FLAGS := 32 | 33 | # 3DS CONFIGURATION # 34 | 35 | TITLE := $(NAME) 36 | DESCRIPTION := http:C based CIA installer/updater 37 | AUTHOR := ksanislo 38 | PRODUCT_CODE := AutoLoader 39 | UNIQUE_ID := 0xB1982 40 | 41 | SYSTEM_MODE := 64MB 42 | SYSTEM_MODE_EXT := Legacy 43 | 44 | ICON_FLAGS := 45 | 46 | ROMFS_DIR := 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 | # AutoLoader 2 | Automated 3DS CIA installer/updater that operates from a web backend 3 | 4 | To use with your own app, write the URL to the sd card as /autoloader.url and APT_DoAppJump() over. 5 | 6 | This requires my fork of [citrus](https://github.com/ksanislo/citrus/) which provides an overloaded version of ctr::app::install() that can accept an httpcContext. 7 | -------------------------------------------------------------------------------- /meta/audio.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksanislo/AutoLoader/e2abfebc5ff058a3a1c97e2df4697a46806e5bf0/meta/audio.wav -------------------------------------------------------------------------------- /meta/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksanislo/AutoLoader/e2abfebc5ff058a3a1c97e2df4697a46806e5bf0/meta/banner.png -------------------------------------------------------------------------------- /meta/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksanislo/AutoLoader/e2abfebc5ff058a3a1c97e2df4697a46806e5bf0/meta/icon.png -------------------------------------------------------------------------------- /romfs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksanislo/AutoLoader/e2abfebc5ff058a3a1c97e2df4697a46806e5bf0/romfs/.gitignore -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define AUTO_UPDATE_FILE "autoloader.url" 13 | // If we don't have a file, we'll use this one. 14 | #define DEFAULT_URL "http://3ds.intherack.com/files/QRWebLoader.cia" 15 | 16 | #define SSLOPTION_NOVERIFY 1<<9 17 | 18 | // Max URL length used in qr variation 19 | #define QUIRC_MAX_PAYLOAD 8896 20 | 21 | 22 | using namespace ctr; 23 | 24 | bool onProgress(u64 pos, u64 size){ 25 | printf("pos: %" PRId64 "-%" PRId64 "\n", pos, size); 26 | gpu::flushBuffer(); 27 | hid::poll(); 28 | return !hid::pressed(hid::BUTTON_B); 29 | } 30 | 31 | Result http_getinfo(char *url, app::App *app){ 32 | Result ret=0; 33 | u8* buf; 34 | u32 statuscode=0; 35 | httpcContext context; 36 | 37 | app->mediaType = fs::SD; 38 | app->size = 0; 39 | app->titleId = 0x0000000000000000; 40 | 41 | ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 0); 42 | if(ret!=0){ 43 | goto stop; 44 | } 45 | 46 | // We should /probably/ make sure Range: is supported 47 | // before we try to do this, but these 8 bytes are the titleId 48 | ret = httpcAddRequestHeaderField(&context, (char*)"Range", (char*)"bytes=11292-11299"); 49 | if(ret!=0){ 50 | goto stop; 51 | } 52 | 53 | // This disables the SSL certificate checks. 54 | ret = httpcSetSSLOpt(&context, SSLOPTION_NOVERIFY); 55 | if(ret!=0){ 56 | goto stop; 57 | } 58 | 59 | ret = httpcBeginRequest(&context); 60 | if(ret!=0){ 61 | goto stop; 62 | } 63 | 64 | ret = httpcGetResponseStatusCode(&context, &statuscode, 0); 65 | if(ret!=0){ 66 | goto stop; 67 | } 68 | 69 | if(statuscode==301||statuscode==302){ 70 | ret = httpcGetResponseHeader(&context, (char*)"Location", (char*)url, QUIRC_MAX_PAYLOAD-1); 71 | if(ret!=0){ 72 | goto stop; 73 | } 74 | 75 | ret=http_getinfo(url, app); 76 | goto stop; 77 | } 78 | 79 | if(statuscode==206){ 80 | buf = (u8*)malloc(8); // Allocate u8*8 == u64 81 | if(buf==NULL){ 82 | goto stop; 83 | } 84 | memset(buf, 0, 8); // Zero out 85 | 86 | ret = httpcDownloadData(&context, buf, 8, NULL); 87 | 88 | // Safely convert our 8 byte string into a u64 89 | 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]); 90 | free(buf); 91 | 92 | buf = (u8*)malloc(64); 93 | if(buf==NULL){ 94 | goto stop; 95 | } 96 | 97 | ret = httpcGetResponseHeader(&context, (char*)"Content-Range", (char*)buf, 64); 98 | if(ret==0){ 99 | char *ptr = strchr((const char *)buf, 47); 100 | app->size = atoll(&ptr[1]); 101 | } 102 | 103 | free(buf); 104 | } else { 105 | printf("HTTP status: %lu\n", statuscode); 106 | } 107 | 108 | stop: 109 | ret = httpcCloseContext(&context); 110 | 111 | return ret; 112 | } 113 | 114 | Result http_download(char *url, app::App *app){ 115 | Result ret=0; 116 | httpcContext context; 117 | u32 statuscode=0; 118 | u32 contentsize=0, downloadsize=0; 119 | char *buf; 120 | 121 | ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 0); 122 | if(ret!=0){ 123 | goto stop; 124 | } 125 | 126 | ret = httpcAddRequestHeaderField(&context, (char*)"Accept-Encoding", (char*)"gzip, deflate"); 127 | if(ret!=0){ 128 | goto stop; 129 | } 130 | 131 | // This disables the SSL certificate checks. 132 | ret = httpcSetSSLOpt(&context, 1<<9); 133 | if(ret!=0){ 134 | goto stop; 135 | } 136 | 137 | ret = httpcBeginRequest(&context); 138 | if(ret!=0){ 139 | goto stop; 140 | } 141 | 142 | ret = httpcGetResponseStatusCode(&context, &statuscode, 0); 143 | if(ret!=0){ 144 | goto stop; 145 | } 146 | 147 | if(statuscode==200){ 148 | ret = httpcGetDownloadSizeState(&context, &downloadsize, &contentsize); 149 | if(ret!=0){ 150 | goto stop; 151 | } 152 | 153 | buf = (char*)malloc(16); 154 | if(buf==NULL){ 155 | goto stop; 156 | } 157 | memset(buf, 0, 16); 158 | 159 | ret = httpcGetResponseHeader(&context, (char*)"Content-Encoding", (char*)buf, 16); 160 | if(ret==0){ 161 | printf("Content-Encoding: %s\n", buf); 162 | } 163 | 164 | app::install(app->mediaType, &context, app->size, &onProgress); 165 | 166 | free(buf); 167 | } else { 168 | printf("HTTP status: %lu\n", statuscode); 169 | } 170 | 171 | stop: 172 | ret = httpcCloseContext(&context); 173 | 174 | return ret; 175 | } 176 | 177 | int main(int argc, char **argv){ 178 | Result ret=0; 179 | 180 | core::init(argc); 181 | httpcInit(0x1000); 182 | 183 | consoleInit(GFX_BOTTOM,NULL); 184 | 185 | app::App app; 186 | 187 | char *url = (char*)malloc(QUIRC_MAX_PAYLOAD); 188 | FILE *fd = fopen(AUTO_UPDATE_FILE, "r"); 189 | if(fd != NULL){ 190 | fread(url, sizeof(char), QUIRC_MAX_PAYLOAD - 1, fd); 191 | fclose(fd); 192 | } else 193 | strcpy(url, DEFAULT_URL); 194 | 195 | printf("Downloading %s\n", url); 196 | gpu::flushBuffer(); 197 | 198 | ret = http_getinfo(url, &app); 199 | if(ret!=0)return ret; 200 | 201 | if(app.titleId>>48 != 0x4){ // 3DS titleId 202 | printf("Not a 3DS .cia file.\n"); 203 | return -1; 204 | } 205 | 206 | if(app.titleId != 0 && app::installed(app)){ // Check if we have a titleId to remove 207 | printf("Uninstalling titleId: 0x%llx\n", app.titleId); 208 | gpu::flushBuffer(); 209 | app::uninstall(app); 210 | } 211 | 212 | ret = http_download(url, &app); 213 | if(ret!=0)return ret; 214 | free(url); 215 | 216 | // We're done with http:C 217 | httpcExit(); 218 | 219 | printf("Install finished.\nLaunching title 0x%llx.\n", app.titleId); 220 | gpu::flushBuffer(); 221 | 222 | ctr::app::launch(app); // Launch what we installed. 223 | 224 | // Main loop 225 | while (core::running()) 226 | { 227 | hid::poll(); 228 | 229 | // Your code goes here 230 | 231 | if (hid::pressed(hid::BUTTON_START)) 232 | break; // break in order to return to hbmenu 233 | 234 | // Flush and swap framebuffers 235 | gpu::flushBuffer(); 236 | gpu::swapBuffers(true); 237 | } 238 | 239 | // Exit services 240 | core::exit(); 241 | 242 | return 0; 243 | } 244 | --------------------------------------------------------------------------------