diff --git a/Makefile b/Makefile index 201d4d0..6907ddf 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ DATA := data INCLUDES := include include/ui include/data include/install include/nx include/nx/ipc include/util $(CURDIR)/include/Plutonium/Plutonium/include APP_TITLE := TinWoo Installer APP_AUTHOR := MrDude -APP_VERSION := 1.0.5 +APP_VERSION := 1.0.7 ROMFS := romfs APP_ICON := icon.jpg diff --git a/README.md b/README.md index 41edaaa..a0d2df2 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,3 @@ -# TinWoo -A No-Bullshit-No-Bullshit NSP, NSZ, XCI, and XCZ Installer for Nintendo Switch. +# TinWoo DEV Branch -![Main Page](https://i.imgur.com/QOi0Yvv.jpg) - -## Features -- Installs NSP/NSZ/XCI/XCZ files and split NSP/XCI files from your SD card. -- Installs NSP/NSZ/XCI/XCZ files over LAN or USB from tools such as [NS-USBloader](https://github.com/developersu/ns-usbloader). -- Installs Split NSP/XCI/NSZ/XCZ over Lan or USB using [NS-USBloader(Mod)](https://mega.nz/file/I4p2gCCK#32GwAGtIcL3FVH-V-8Goae_hpnK8FQ0eS2PwLDOW6X4). -- Installs NSP/NSZ/XCI/XCZ files over the internet by URL or Google Drive. -- Installs NSP/NSZ/XCI/XCZ files from a Hard Drive (NTFS/Fat32/ExFat/EXT3/EXT4). -- Verifies NCAs by header signature before they're installed. -- Installs and manages the latest signature patches quickly and easily. -- Works on SX OS and Atmosphere. -- Able to theme, change install sounds. - -## Thanks to -- Blawar, Hunterweb, DarkMatterCore, XorTroll - -## Modified Code -This code was prominently modified by MrDude on 25/04/2022 to be able to build with new plutonium and up to date libnx. - -## Building All componenets of TinWoo at once -cd into the tinwoo folder then "make". - -## Build TinWoo components individually -First, build and install usb libs - "make libusb".\ -Second, built Plutonium - "make plutonium".\ -Third, Make Tinwoo - "make tinwwoo". - -## Cleanup TinWoo once built -First, "make libusbclean".\ -Second, "make cleanplutonium".\ -Third, "make clean". - -## Note -This is a work in progress and lets you build with new libnx, plutonium packages. Some stuff still needs fixed to work with the new plutonium and libnx changes. - -## Stuff still to fix -~~All known bugs fixed~~ \ - -## Build Issues -Make sure you are using Libnx build at least 9865dbf9 version. - -git clone --recursive https://github.com/switchbrew/libnx.git \ -cd libnx \ -git checkout 9865dbf9 \ -make install - -## Check libnx version -pacman -Q --info libnx +Don't use this branch - it's only for experimental purposes. \ No newline at end of file diff --git a/include/netInstall.hpp b/include/netInstall.hpp index c96f509..47c5406 100644 --- a/include/netInstall.hpp +++ b/include/netInstall.hpp @@ -20,6 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include +extern bool netConnected; namespace netInstStuff { void installTitleNet(std::vector ourUrlList, int ourStorage, std::vector urlListAltNames, std::string ourSource); diff --git a/include/util/config.hpp b/include/util/config.hpp index 05b2fee..c6074fb 100644 --- a/include/util/config.hpp +++ b/include/util/config.hpp @@ -5,10 +5,12 @@ namespace inst::config { static const std::string appDir = "sdmc:/switch/tinwoo"; static const std::string configPath = appDir + "/config.json"; - static const std::string appVersion = "1.0.5"; + static const std::string appVersion = "1.0.7"; extern std::string gAuthKey; extern std::string sigPatchesUrl; + extern std::string httpIndexUrl; + extern std::string httplastUrl; extern std::vector updateInfo; extern int languageSetting; extern bool ignoreReqVers; diff --git a/romfs/lang/de.json b/romfs/lang/de.json index bf6e74e..9058008 100644 --- a/romfs/lang/de.json +++ b/romfs/lang/de.json @@ -59,6 +59,8 @@ "alt_name": "Google Drive Datei", "source_string": " von Google Drive" }, + "index_error": "No games found!", + "index_error_info": "Did you type the correct URL?", "top_info": "Wähle Dateien zur Installation vom Server aus und drücke dann \ue0b3 !", "top_info1": "Warte auf eine Verbindung... Die IP Adresse deiner Switch ist: ", "failed": "Ferninstallation fehlgeschlagen!", diff --git a/romfs/lang/en.json b/romfs/lang/en.json index 44b3cb1..a2750d7 100644 --- a/romfs/lang/en.json +++ b/romfs/lang/en.json @@ -59,12 +59,14 @@ "alt_name": "Google Drive File", "source_string": " from Google Drive" }, + "index_error": "No games found!", + "index_error_info": "Did you type the correct URL?", "top_info": "Select what files you want to install from the server, then press the Plus button!", "top_info1": "Waiting for a connection... Your Switch's IP Address is: ", "failed": "Failed to perform remote install!", "transfer_interput": "An error occured during data transfer. Check your network connection.", "source_string": " over local network", - "buttons": "\ue0e3 Install Over Internet \ue0e2 Help \ue0e1 Cancel", + "buttons": "\ue0e3 Install Over Internet \ue0f0 Install From HTTP Directory \ue0e2 Help \ue0e1 Cancel", "buttons1": "\ue0e0 Select File \ue0e3 Select All \ue0ef Install File(s) \ue0e1 Cancel" }, "sd": { diff --git a/romfs/lang/fr.json b/romfs/lang/fr.json index 41a3c1e..671dc9f 100644 --- a/romfs/lang/fr.json +++ b/romfs/lang/fr.json @@ -59,6 +59,8 @@ "alt_name": "Fichier Google Drive", "source_string": " à partir de Google Drive" }, + "index_error": "No games found!", + "index_error_info": "Did you type the correct URL?", "top_info": "Sélectionnez les fichiers que vous voulez installer à partir du serveur, puis appuyez sur le bouton Plus !", "top_info1": "En attente d'une connexion... L'adresse IP de votre switch est : ", "failed": "Echec de l'installation à distance !", diff --git a/romfs/lang/it.json b/romfs/lang/it.json index 62bfb27..617d76a 100644 --- a/romfs/lang/it.json +++ b/romfs/lang/it.json @@ -59,6 +59,8 @@ "alt_name": "File Google Drive", "source_string": " da Google Drive" }, + "index_error": "No games found!", + "index_error_info": "Did you type the correct URL?", "top_info": "Seleziona quali file vuoi installare dal server, poi premi il tasto Più!", "top_info1": "Aspettando una connnessione... L'indirizzo IP è: ", "failed": "Impossibile eseguire l'installazione remota!", diff --git a/romfs/lang/jp.json b/romfs/lang/jp.json index 3941c39..35cccf3 100644 --- a/romfs/lang/jp.json +++ b/romfs/lang/jp.json @@ -59,6 +59,8 @@ "alt_name": "Googleドライブファイル", "source_string": " Googleドライブから" }, + "index_error": "No games found!", + "index_error_info": "Did you type the correct URL?", "top_info": "サーバーからインストールするファイルを選択し、+ボタンを押してください!", "top_info1": "接続を待機しています...スイッチのIPアドレスは次のとおりです。: ", "failed": "リモートインストールを実行できませんでした!", diff --git a/romfs/lang/ru.json b/romfs/lang/ru.json index 17ca6ee..5a2609e 100644 --- a/romfs/lang/ru.json +++ b/romfs/lang/ru.json @@ -59,6 +59,8 @@ "alt_name": "Файл Google Drive", "source_string": " из Google Drive" }, + "index_error": "No games found!", + "index_error_info": "Did you type the correct URL?", "top_info": "Выберите какие файлы вы хотите установить из сервера и нажмите \"+\"!", "top_info1": "Ожидание подключения... IP-адрес вашего Switch: ", "failed": "Не удалось выполнить установку из удалённого источника!", diff --git a/romfs/lang/zh-rTW.json b/romfs/lang/zh-rTW.json index aab1043..6f9f469 100644 --- a/romfs/lang/zh-rTW.json +++ b/romfs/lang/zh-rTW.json @@ -59,6 +59,8 @@ "alt_name": "Google網路硬碟檔案", "source_string": "來源為Google網路硬碟" }, + "index_error": "No games found!", + "index_error_info": "Did you type the correct URL?", "top_info": "選定要從伺服器安裝的檔案後,請按+鈕", "top_info1": "正在等待連線中... 你的Switch主機IP是: ", "failed": "遠端安裝失敗!", diff --git a/source/netInstall.cpp b/source/netInstall.cpp index d77202c..6be4ee2 100644 --- a/source/netInstall.cpp +++ b/source/netInstall.cpp @@ -49,6 +49,7 @@ const unsigned int MAX_URLS = 256; const int REMOTE_INSTALL_PORT = 2000; static int m_serverSocket = 0; static int m_clientSocket = 0; +bool netConnected = false; namespace inst::ui { extern MainApplication *mainApp; @@ -245,18 +246,21 @@ namespace netInstStuff{ if (m_serverSocket <= 0) { THROW_FORMAT("Server socket failed to initialize.\n"); + close(m_serverSocket); //close if already open. + m_serverSocket = 0; //reset so we can try again. } } std::string ourIPAddress = inst::util::getIPAddress(); inst::ui::mainApp->netinstPage->pageInfoText->SetText("inst.net.top_info1"_lang + ourIPAddress); inst::ui::mainApp->CallForRender(); + netConnected = false; LOG_DEBUG("%s %s\n", "Switch IP is ", ourIPAddress.c_str()); LOG_DEBUG("%s\n", "Waiting for network"); LOG_DEBUG("%s\n", "B to cancel"); std::vector urls; - + while (true) { padUpdate(&pad); @@ -283,6 +287,84 @@ namespace netInstStuff{ { inst::ui::mainApp->CreateShowDialog("inst.net.help.title"_lang, "inst.net.help.desc"_lang, {"common.ok"_lang}, true); } + + if (kDown & HidNpadButton_Minus) { + std::string url = inst::util::softwareKeyboard("inst.net.url.hint"_lang, inst::config::httpIndexUrl, 500); + if(url == "") { + url = "http://127.0.0.1"; + } + + else { + + inst::config::httpIndexUrl = url; + inst::config::setConfig(); + + + std::string response; + if (inst::util::formatUrlString(url) == "" || url == "https://" || url == "http://") + inst::ui::mainApp->CreateShowDialog("inst.net.url.invalid"_lang, "", {"common.ok"_lang}, false); + else { + if (url[url.size() - 1] != '/') + url += '/'; + response = inst::curl::downloadToBuffer(url); + } + + if (!response.empty()) { + if (response[0] == '{') + try { + nlohmann::json j = nlohmann::json::parse(response); + for (const auto &file : j["files"]) { + urls.push_back(file["url"]); + } + return urls; + } + catch (const nlohmann::detail::exception& ex) { + LOG_DEBUG("Failed to parse JSON\n"); + } + + else if (response[0] == '<') { + std::size_t index = 0; + while (index < response.size()) { + std::string link; + auto found = response.find("href=\"", index); + if (found == std::string::npos) + break; + + index = found + 6; + while (index < response.size()) { + if (response[index] == '"') { + if (link.find("../") == std::string::npos) + if (link.find(".nsp") != std::string::npos || link.find(".nsz") != std::string::npos || link.find(".xci") != std::string::npos || link.find(".xcz") != std::string::npos) + urls.push_back(link); + break; + } + link += response[index++]; + } + + } + if (urls.size() > 0){ + /* + //debug + FILE * fp; + std::string debug = urls[0]; + const char *info = debug.c_str(); + fp = fopen ("debug.txt", "w+"); + fprintf(fp, "%s", info); + fclose(fp); + */ + return urls; + } + + LOG_DEBUG("Failed to parse games from HTML\n"); + } + } + + else { + LOG_DEBUG("Failed to fetch game list\n"); + inst::ui::mainApp->CreateShowDialog("inst.net.index_error"_lang, "inst.net.index_error_info"_lang, {"common.ok"_lang}, true); + } + } + } struct sockaddr_in client; socklen_t clientLen = sizeof(client); @@ -329,6 +411,8 @@ namespace netInstStuff{ } catch (std::runtime_error& e) { + close(m_serverSocket); + m_serverSocket = 0; LOG_DEBUG("Failed to perform remote install!\n"); LOG_DEBUG("%s", e.what()); fprintf(stdout, "%s", e.what()); diff --git a/source/nx/nca_writer.cpp b/source/nx/nca_writer.cpp index c74f628..d6767d5 100644 --- a/source/nx/nca_writer.cpp +++ b/source/nx/nca_writer.cpp @@ -238,12 +238,13 @@ public: u64 processChunk(const u8* ptr, u64 sz) { ZSTD_inBuffer input = { ptr, sz, 0 }; + ZSTD_outBuffer output = { buffOut, buffOutSize, 0 }; m_deflateBuffer.resize(sz); m_deflateBuffer.resize(0); - while (input.pos < input.size) + while (input.pos < input.size || output.pos > 0) { - ZSTD_outBuffer output = { buffOut, buffOutSize, 0 }; + output = { buffOut, buffOutSize, 0 }; size_t const ret = ZSTD_decompressStream(dctx, &output, &input); if (ZSTD_isError(ret)) diff --git a/source/ui/HDInstPage.cpp b/source/ui/HDInstPage.cpp index 1714428..63cc0ac 100644 --- a/source/ui/HDInstPage.cpp +++ b/source/ui/HDInstPage.cpp @@ -192,5 +192,11 @@ namespace inst::ui { } if (this->selectedTitles.size() > 0) this->startInstall(); } + + if (Down & HidNpadButton_ZL) + this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6)); + + if (Down & HidNpadButton_ZR) + this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6)); } } diff --git a/source/ui/netInstPage.cpp b/source/ui/netInstPage.cpp index 28d0d89..d785449 100644 --- a/source/ui/netInstPage.cpp +++ b/source/ui/netInstPage.cpp @@ -16,7 +16,7 @@ namespace inst::ui { extern MainApplication *mainApp; s32 xxx=0; - std::string lastUrl = "https://"; + std::string httplastUrl = "http://"; std::string lastFileID = ""; std::string sourceString = ""; @@ -39,11 +39,9 @@ namespace inst::ui { this->appVersionText = TextBlock::New(1210, 680, ""); } this->appVersionText->SetColor(COLOR("#FFFFFFFF")); - //this->pageInfoText = TextBlock::New(10, 109, "", 30); this->pageInfoText = TextBlock::New(10, 109, ""); this->pageInfoText->SetFont(pu::ui::MakeDefaultFontName(30)); this->pageInfoText->SetColor(COLOR("#FFFFFFFF")); - //this->butText = TextBlock::New(10, 678, "", 24); this->butText = TextBlock::New(10, 678, ""); this->butText->SetColor(COLOR("#FFFFFFFF")); this->menu = pu::ui::elm::Menu::New(0, 156, 1280, COLOR("#FFFFFF00"), COLOR("#4f4f4d33"), 84, (506 / 84)); @@ -95,21 +93,34 @@ namespace inst::ui { void netInstPage::startNetwork() { this->butText->SetText("inst.net.buttons"_lang); + //this->butText->SetText("inst.net.buttons"_lang + " \ue0f0 Install From HTTP Directory"); this->menu->SetVisible(false); this->menu->ClearItems(); this->infoImage->SetVisible(true); mainApp->LoadLayout(mainApp->netinstPage); this->ourUrls = netInstStuff::OnSelected(); + if (!this->ourUrls.size()) { mainApp->LoadLayout(mainApp->mainPage); return; - } else if (this->ourUrls[0] == "supplyUrl") { + } + + else if (this->ourUrls[0] == "supplyUrl") { std::string keyboardResult; switch (mainApp->CreateShowDialog("inst.net.src.title"_lang, "common.cancel_desc"_lang, {"inst.net.src.opt0"_lang, "inst.net.src.opt1"_lang}, false)) { case 0: - keyboardResult = inst::util::softwareKeyboard("inst.net.url.hint"_lang, lastUrl, 500); + keyboardResult = inst::util::softwareKeyboard("inst.net.url.hint"_lang, inst::config::httplastUrl, 500); if (keyboardResult.size() > 0) { - lastUrl = keyboardResult; + httplastUrl = keyboardResult; + + if(keyboardResult == "") { + keyboardResult = "http://127.0.0.1"; + } + else { + inst::config::httplastUrl = keyboardResult; + inst::config::setConfig(); + } + if (inst::util::formatUrlString(keyboardResult) == "" || keyboardResult == "https://" || keyboardResult == "http://") { mainApp->CreateShowDialog("inst.net.url.invalid"_lang, "", {"common.ok"_lang}, false); break; @@ -139,6 +150,7 @@ namespace inst::ui { } else { mainApp->CallForRender(); // If we re-render a few times during this process the main screen won't flicker sourceString = "inst.net.source_string"_lang; + netConnected = true; this->pageInfoText->SetText("inst.net.top_info"_lang); this->butText->SetText("inst.net.buttons1"_lang); this->drawMenuItems(true); @@ -177,7 +189,28 @@ namespace inst::ui { if (hidGetTouchScreenStates(&state, 1)) { - if ((Down & HidNpadButton_A) || (state.count != xxx)) + if (netConnected) { + if ((Down & HidNpadButton_A) || (state.count != xxx)) + { + xxx = state.count; + + if (xxx != 1) { + int var = this->menu->GetItems().size(); + auto s = std::to_string(var); + if (s == "0") { + //do nothing here because there's no items in the list, that way the app won't freeze + } + else { + this->selectTitle(this->menu->GetSelectedIndex()); + if (this->menu->GetItems().size() == 1 && this->selectedUrls.size() == 1) { + this->startInstall(false); + } + } + } + } + } + + if ((Down & HidNpadButton_Minus) || (state.count != xxx)) { xxx = state.count; @@ -226,8 +259,14 @@ namespace inst::ui { this->startInstall(false); return; } - this->startInstall(false); + this->startInstall(false); } } + + if (Down & HidNpadButton_ZL) + this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6)); + + if (Down & HidNpadButton_ZR) + this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6)); } } \ No newline at end of file diff --git a/source/ui/optionsPage.cpp b/source/ui/optionsPage.cpp index 8c78897..761b15a 100644 --- a/source/ui/optionsPage.cpp +++ b/source/ui/optionsPage.cpp @@ -167,6 +167,12 @@ namespace inst::ui { mainApp->LoadLayout(mainApp->mainPage); } + if (Down & HidNpadButton_ZL) + this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6)); + + if (Down & HidNpadButton_ZR) + this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6)); + HidTouchScreenState state={0}; if (hidGetTouchScreenStates(&state, 1)) { diff --git a/source/ui/sdInstPage.cpp b/source/ui/sdInstPage.cpp index 04d8cb9..515b992 100644 --- a/source/ui/sdInstPage.cpp +++ b/source/ui/sdInstPage.cpp @@ -215,5 +215,11 @@ namespace inst::ui { if (this->selectedTitles.size() > 0) this->startInstall(); } } + + if (Down & HidNpadButton_ZL) + this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6)); + + if (Down & HidNpadButton_ZR) + this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6)); } } \ No newline at end of file diff --git a/source/ui/usbInstPage.cpp b/source/ui/usbInstPage.cpp index 5b9672e..9c5ee7b 100644 --- a/source/ui/usbInstPage.cpp +++ b/source/ui/usbInstPage.cpp @@ -170,5 +170,11 @@ namespace inst::ui { this->startInstall(); } } + + if (Down & HidNpadButton_ZL) + this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6)); + + if (Down & HidNpadButton_ZR) + this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6)); } } \ No newline at end of file diff --git a/source/util/config.cpp b/source/util/config.cpp index 644e88b..d273c23 100644 --- a/source/util/config.cpp +++ b/source/util/config.cpp @@ -6,6 +6,8 @@ namespace inst::config { std::string gAuthKey; std::string sigPatchesUrl; + std::string httpIndexUrl; + std::string httplastUrl; std::vector updateInfo; int languageSetting; bool autoUpdate; @@ -29,7 +31,9 @@ namespace inst::config { {"overClock", overClock}, {"sigPatchesUrl", sigPatchesUrl}, {"usbAck", usbAck}, - {"validateNCAs", validateNCAs} + {"validateNCAs", validateNCAs}, + {"httpIndexUrl", httpIndexUrl}, + {"httplastUrl", httplastUrl} }; std::ofstream file(inst::config::configPath); file << std::setw(4) << j << std::endl; @@ -49,6 +53,8 @@ namespace inst::config { languageSetting = j["languageSetting"].get(); overClock = j["overClock"].get(); sigPatchesUrl = j["sigPatchesUrl"].get(); + httpIndexUrl = j["httpIndexUrl"].get(); + httplastUrl = j["httplastUrl"].get(); usbAck = j["usbAck"].get(); validateNCAs = j["validateNCAs"].get(); } @@ -56,7 +62,9 @@ namespace inst::config { // If loading values from the config fails, we just load the defaults and overwrite the old config gAuthKey = {0x41,0x49,0x7a,0x61,0x53,0x79,0x42,0x4d,0x71,0x76,0x34,0x64,0x58,0x6e,0x54,0x4a,0x4f,0x47,0x51,0x74,0x5a,0x5a,0x53,0x33,0x43,0x42,0x6a,0x76,0x66,0x37,0x34,0x38,0x51,0x76,0x78,0x53,0x7a,0x46,0x30}; sigPatchesUrl = "https://github.com/mrdude2478/patches/releases/download/1/patches.zip"; - languageSetting = 99; + languageSetting = 0; + httpIndexUrl = "http://"; + httplastUrl = "http://"; autoUpdate = true; deletePrompt = true; gayMode = false; diff --git a/source/util/lang.cpp b/source/util/lang.cpp index b78bade..4d3f965 100644 --- a/source/util/lang.cpp +++ b/source/util/lang.cpp @@ -42,6 +42,9 @@ namespace Language { case 6: languagePath = "romfs:/lang/zh-rTW.json"; break; + case 99: + languagePath = "romfs:/lang/en.json"; + break; default: if (std::filesystem::exists(inst::config::appDir + "/lang/custom.json")) { languagePath = (inst::config::appDir + "/lang/custom.json"); diff --git a/source/util/unzip.cpp b/source/util/unzip.cpp index 9ff23bf..e91dcf3 100644 --- a/source/util/unzip.cpp +++ b/source/util/unzip.cpp @@ -48,7 +48,7 @@ bool _makeDirectoryParents(std::string path) //Done! bSuccess = true; break; - //std::string getHost(""); + //std::string getHost(); default: bSuccess = false; break; diff --git a/source/util/util.cpp b/source/util/util.cpp index a18582a..d42bb95 100644 --- a/source/util/util.cpp +++ b/source/util/util.cpp @@ -300,7 +300,7 @@ namespace inst::util { return; } - std::vector checkForAppUpdate () { + std::vector checkForAppUpdate() { try { std::string giturl = "https://api.github.com/repos/mrdude2478/TinWoo/releases/latest"; std::string jsonData = inst::curl::downloadToBuffer(giturl, 0, 0, 1000L);