void action_toggle_install() { consoleClear(); CConfig::Mode nextMode = CConfig::Mode::INSTALL_CIA; switch (config.GetMode()) { case CConfig::Mode::DOWNLOAD_CIA: nextMode = CConfig::Mode::INSTALL_CIA; break; case CConfig::Mode::INSTALL_CIA: nextMode = CConfig::Mode::INSTALL_TICKET; break; case CConfig::Mode::INSTALL_TICKET: nextMode = CConfig::Mode::DOWNLOAD_CIA; break; } if (nextMode == CConfig::Mode::INSTALL_TICKET || nextMode == CConfig::Mode::INSTALL_CIA) { if (!bSvcHaxAvailable) { nextMode = CConfig::Mode::DOWNLOAD_CIA; printf(CONSOLE_RED "Kernel access not available.\nCan't enable Install modes.\nYou can only make a CIA.\n" CONSOLE_RESET); wait_key_specific("\nPress A to continue.", KEY_A); } } config.SetMode(nextMode); }
void ProcessGameQueue() { // Create the tickets folder if we're in ticket mode char empty_titleVersion[2] = {0x00, 0x00}; std::vector<game_item>::iterator game = game_queue.begin(); while(aptMainLoop() && game != game_queue.end()) { std::string selected_titleid = (*game).titleid; std::string selected_enckey = (*game).titlekey; std::string selected_name = (*game).name; std::string selected_region = (*game).region; if (config.GetMode() == CConfig::Mode::INSTALL_TICKET) { CreateTicket(selected_titleid, selected_enckey, empty_titleVersion, "/CIAngel/tmp/ticket"); InstallTicket("/CIAngel/tmp/ticket", selected_titleid); } else { Result res = DownloadTitle(selected_titleid, selected_enckey, selected_name, selected_region); if (R_FAILED(res)) { printf("Error processing queue. Returning to menu\n"); break; } } game = game_queue.erase(game); } wait_key_specific("Press A to continue.\n", KEY_A); }
Result ProcessCIA(std::string dir, std::string titleName) { FILE *tik = fopen((dir + "/ticket").c_str(), "rb"); if (!tik) { return -1; } TIK_CONTEXT tik_context = process_tik(tik); FILE *tmd = fopen((dir + "/tmd").c_str(),"rb"); if (!tmd) { fclose(tik); return -1; } TMD_CONTEXT tmd_context = process_tmd(tmd); if(tik_context.result != 0 || tmd_context.result != 0){ printf("[!] Input files could not be processed successfully\n"); free(tmd_context.content_struct); fclose(tik); fclose(tmd); return -1; } int result; if (config.GetMode() == CConfig::Mode::INSTALL_CIA) { result = install_cia(tmd_context, tik_context); } else { FILE *output = fopen((dir + "/" + titleName + ".cia").c_str(),"wb"); if (!output) { result = -2; } else { result = generate_cia(tmd_context, tik_context, output); if(result != 0) { remove((dir + "/" + titleName + ".cia").c_str()); } } } // free allocated memory/handles free(tmd_context.content_struct); fclose(tik); fclose(tmd); // Clean up temp files remove((dir + "/ticket").c_str()); remove((dir + "/tmd").c_str()); return result; }
void action_prompt_queue() { consoleClear(); std::string mode_text; switch (config.GetMode()) { case CConfig::Mode::DOWNLOAD_CIA: mode_text = "download"; break; case CConfig::Mode::INSTALL_CIA: mode_text = "install"; break; case CConfig::Mode::INSTALL_TICKET: mode_text = "create tickets for"; break; } printf("Queue contains %d items.\n", game_queue.size()); printf("Press A to %s queue.\n", mode_text.c_str()); printf("Press B to return to menu.\n"); printf("Press X to clear queue.\n"); while (aptMainLoop()) { u32 key = wait_key(); if (key & KEY_B) { break; } if (key & KEY_X) { game_queue.clear(); break; } if (key & KEY_A) { ProcessGameQueue(); break; } } }
// Draw the main menu void menu_main() { const char *options[] = { "Search for a title by name", "Search for a title by serial", "Process download queue", "Enter a title key/ID pair", "Fetch title key/ID from input.txt", "Download wings.json", "About CIAngel", "Exit", }; char footer[50]; while (!bExit && aptMainLoop()) { std::string mode_text; switch (config.GetMode()) { case CConfig::Mode::DOWNLOAD_CIA: mode_text = "Create CIA"; break; case CConfig::Mode::INSTALL_CIA: mode_text = "Install CIA"; break; case CConfig::Mode::INSTALL_TICKET: mode_text = "Create Ticket"; break; } // We have to update the footer every draw, incase the user switches install mode or region sprintf(footer, "Mode (L):%s Region (R):%s Queue: %d", mode_text.c_str(), config.GetRegionFilter().c_str(), game_queue.size()); menu_multkey_draw("CIAngel by cearp and Drakia", footer, 0, sizeof(options) / sizeof(char*), options, NULL, menu_main_keypress); clear_screen(GFX_BOTTOM); } }
/* Menu Action Functions */ void action_search(bool (*match)(std::string &searchString, Json::Value &gameData, int &outScore)) { HB_Keyboard sHBKB; bool bKBCancelled = false; consoleClear(); printf("Please enter text to search for:\n"); std::string searchString = getInput(&sHBKB, bKBCancelled); if (bKBCancelled) { return; } // User has entered their input, so let's scrap the keyboard clear_screen(GFX_BOTTOM); std::vector<game_item> display_output; int outScore; for (unsigned int i = 0; i < sourceData.size(); i++) { // Check the region filter std::string regionFilter = config.GetRegionFilter(); if(regionFilter != "off" && sourceData[i]["region"].asString() != regionFilter) { continue; } // Check that the encTitleKey isn't null if (sourceData[i]["encTitleKey"].isNull()) { continue; } // Create an ASCII version of the name if one doesn't exist yet if (sourceData[i]["ascii_name"].isNull()) { // Normalize the name down to ASCII utf8proc_option_t options = (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_DECOMPOSE | UTF8PROC_COMPAT | UTF8PROC_STRIPMARK | UTF8PROC_STRIPCC); utf8proc_uint8_t* szName; utf8proc_uint8_t *str = (utf8proc_uint8_t*)sourceData[i]["name"].asCString(); utf8proc_map(str, 0, &szName, options); sourceData[i]["ascii_name"] = (const char*)szName; free(szName); } if (match(searchString, sourceData[i], outScore)) { game_item item; item.score = outScore; item.index = i; switch(sourceDataType) { case JSON_TYPE_WINGS: item.titleid = sourceData[i]["titleid"].asString(); item.titlekey = sourceData[i]["enckey"].asString(); item.name = sourceData[i]["ascii_name"].asString(); item.region = sourceData[i]["region"].asString(); item.code = sourceData[i]["code"].asString(); break; case JSON_TYPE_ONLINE: item.titleid = sourceData[i]["titleID"].asString(); item.titlekey = sourceData[i]["encTitleKey"].asString(); item.name = sourceData[i]["ascii_name"].asString(); item.region = sourceData[i]["region"].asString(); item.code = sourceData[i]["serial"].asString(); break; } std::string typeCheck = item.titleid.substr(4,4); //if title id belongs to gameapp/dlc/update/dsiware, use it. if not, ignore. case sensitve of course if(typeCheck == "0000" || typeCheck == "008c" || typeCheck == "000e" || typeCheck == "8004"){ display_output.push_back(item); } } } unsigned int display_amount = display_output.size(); // We technically have 30 rows to work with, minus 2 for header/footer. But stick with 20 entries for now if (display_amount == 0) { printf("No matching titles found.\n"); wait_key_specific("\nPress A to return.\n", KEY_A); return; } // sort similar names by fuzzy score if(display_amount>1) { std::sort(display_output.begin(), display_output.end(), compareByScore); } std::string mode_text; switch (config.GetMode()) { case CConfig::Mode::DOWNLOAD_CIA: mode_text = "Create CIA"; break; case CConfig::Mode::INSTALL_CIA: mode_text = "Install CIA"; break; case CConfig::Mode::INSTALL_TICKET: mode_text = "Create Ticket"; break; } char footer[51]; char header[51]; sprintf(header, "Select a Title (found %i results)", display_amount); sprintf(footer, "Press A to %s. Press X to queue.", mode_text.c_str()); titles_multkey_draw(header, footer, 1, &display_output, &display_output, menu_search_keypress); }
// Search menu keypress callback bool menu_search_keypress(int selected, u32 key, void* data) { std::vector<game_item>* cb_data = (std::vector<game_item>*)data; // If key is 0, it means aptMainLoop() returned false, so we're exiting // Go back to the previous menu which will handle quitting if (!key) { return true; } // B goes back a screen if (key & KEY_B) { return true; } // A triggers the default action on the selected title if (key & KEY_A) { // Clean up the console since we'll be using it consoleClear(); // Fetch the title data and start downloading std::string selected_titleid = (*cb_data)[selected].titleid; std::string selected_enckey = (*cb_data)[selected].titlekey; std::string selected_name = (*cb_data)[selected].name; std::string selected_region = (*cb_data)[selected].region; printf("OK - %s\n", selected_name.c_str()); //removes any problem chars, not sure if whitespace is a problem too...? removeForbiddenChar(&selected_name); if(config.GetMode() == CConfig::Mode::INSTALL_TICKET) { char empty_titleVersion[2] = {0x00, 0x00}; CreateTicket(selected_titleid, selected_enckey, empty_titleVersion, "/CIAngel/tmp/ticket"); InstallTicket("/CIAngel/tmp/ticket", selected_titleid); } else { DownloadTitle(selected_titleid, selected_enckey, selected_name, selected_region); } wait_key_specific("\nPress A to continue.\n", KEY_A); return true; } // X triggers adding items to the download queue if (key & KEY_X) { consoleClear(); std::string titleid = (*cb_data)[selected].titleid; if (std::find_if(game_queue.begin(), game_queue.end(), find_game_item(titleid)) == game_queue.end()) { game_queue.push_back((*cb_data)[selected]); printf("Game added to queue.\n"); } else { printf("Game already in queue.\n"); } printf("Queue size: %d\n", game_queue.size()); wait_key_specific("\nPress A to continue.\n", KEY_A); return true; } return false; }
Result DownloadTitle(std::string titleId, std::string encTitleKey, std::string titleName, std::string region) { // Convert the titleid to a u64 for later use char* nTitleId = parse_string(titleId); u64 uTitleId = u8_to_u64((u8*)nTitleId, BIG_ENDIAN); free (nTitleId); // Wait for wifi to be available u32 wifi = 0; Result ret = 0; Result res = 0; while(R_SUCCEEDED(ret = ACU_GetWifiStatus(&wifi)) && wifi == 0) { hidScanInput(); if (hidKeysDown() & KEY_B) { ret = -1; break; } } if (R_FAILED(ret)) { printf("Unable to access internet.\n"); return ret; } std::string outputDir = "/CIAngel"; if (titleName.length() == 0) { titleName = titleId; } // Include region in filename if (region.length() > 0) { titleName = titleName + " (" + region + ")"; } std::string mode_text; if(config.GetMode() == CConfig::Mode::DOWNLOAD_CIA) { mode_text = "create"; } else if(config.GetMode() == CConfig::Mode::INSTALL_CIA) { mode_text = "install"; } printf("Starting - %s\n", titleName.c_str()); // If in install mode, download/install the SEED entry if (config.GetMode() == CConfig::Mode::INSTALL_CIA) { // Download and install the SEEDDB entry if install mode // Code based on code from FBI: https://github.com/Steveice10/FBI/blob/master/source/core/util.c#L254 // Copyright (C) 2015 Steveice10 u8 seed[16]; static const char* regionStrings[] = {"JP", "US", "GB", "GB", "HK", "KR", "TW"}; u8 region = CFG_REGION_USA; CFGU_GetSystemLanguage(®ion); if(region <= CFG_REGION_TWN) { char url[128]; snprintf(url, 128, SEED_URL "0x%016llX/ext_key?country=%s", uTitleId, regionStrings[region]); httpcContext context; if(R_SUCCEEDED(res = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 1))) { httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); u32 responseCode = 0; if(R_SUCCEEDED(res = httpcBeginRequest(&context)) && R_SUCCEEDED(res = httpcGetResponseStatusCode(&context, &responseCode, 0))) { if(responseCode == 200) { u32 pos = 0; u32 bytesRead = 0; while(pos < sizeof(seed) && (R_SUCCEEDED(res = httpcDownloadData(&context, &seed[pos], sizeof(seed) - pos, &bytesRead)) || (u32)res == HTTPC_RESULTCODE_DOWNLOADPENDING)) { pos += bytesRead; } } else { res = -1; } } httpcCloseContext(&context); } if (R_SUCCEEDED(res)) { res = InstallSeed(uTitleId, seed); if (R_FAILED(res)) { printf("Error installing SEEDDB entry: 0x%lx\n", res); } } } } // Make sure the CIA doesn't already exist std::string cp = outputDir + "/" + titleName + ".cia"; if (config.GetMode() == CConfig::Mode::DOWNLOAD_CIA && FileExists(cp.c_str())) { printf("%s already exists.\n", cp.c_str()); return 0; } std::ofstream ofs; FILE *oh = fopen((outputDir + "/tmp/tmd").c_str(), "wb"); if (!oh) { printf("Error opening %s/tmp/tmd\n", outputDir.c_str()); return -1; } res = DownloadFile((NUS_URL + titleId + "/tmd").c_str(), oh, false); fclose(oh); if (res != 0) { printf("Could not download TMD. Internet/Title ID is OK?\n"); return res; } // Read version std::ifstream tmdfs; tmdfs.open(outputDir + "/tmp/tmd", std::ofstream::out | std::ofstream::in | std::ofstream::binary); char titleVersion[2]; tmdfs.seekg(top+0x9C, std::ios::beg); tmdfs.read(titleVersion, 0x2); tmdfs.close(); CreateTicket(titleId, encTitleKey, titleVersion, outputDir + "/tmp/ticket"); printf("Now %s the CIA...\n", mode_text.c_str()); res = ProcessCIA(outputDir + "/tmp", titleName); if (res != 0) { printf("Could not %s the CIA.\n", mode_text.c_str()); return res; } if (config.GetMode() == CConfig::Mode::DOWNLOAD_CIA) { rename((outputDir + "/tmp/" + titleName + ".cia").c_str(), (outputDir + "/" + titleName + ".cia").c_str()); } printf(" DONE!\n"); return res; }