diff --git a/source/HDInstall.cpp b/source/HDInstall.cpp new file mode 100644 index 0000000..47a40c7 --- /dev/null +++ b/source/HDInstall.cpp @@ -0,0 +1,168 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "HDInstall.hpp" +#include "install/install_nsp.hpp" +#include "install/install_xci.hpp" +#include "install/sdmc_xci.hpp" +#include "install/sdmc_nsp.hpp" +#include "nx/fs.hpp" +#include "util/file_util.hpp" +#include "util/title_util.hpp" +#include "util/error.hpp" +#include "util/config.hpp" +#include "util/util.hpp" +#include "util/lang.hpp" +#include "ui/MainApplication.hpp" +#include "ui/instPage.hpp" + +namespace inst::ui { + extern MainApplication *mainApp; +} + +namespace nspInstStuff_B { + + void installNspFromFile(std::vector ourTitleList, int whereToInstall) + { + inst::util::initInstallServices(); + inst::ui::instPage::loadInstallScreen(); + bool nspInstalled = true; + NcmStorageId m_destStorageId = NcmStorageId_SdCard; + + if (whereToInstall) m_destStorageId = NcmStorageId_BuiltInUser; + unsigned int titleItr; + + std::vector previousClockValues; + if (inst::config::overClock) { + previousClockValues.push_back(inst::util::setClockSpeed(0, 1785000000)[0]); + previousClockValues.push_back(inst::util::setClockSpeed(1, 76800000)[0]); + previousClockValues.push_back(inst::util::setClockSpeed(2, 1600000000)[0]); + } + + try + { + for (titleItr = 0; titleItr < ourTitleList.size(); titleItr++) { + inst::ui::instPage::setTopInstInfoText("inst.info_page.top_info0"_lang + inst::util::shortenString(ourTitleList[titleItr].filename().string(), 40, true) + "inst.hd.source_string"_lang); + std::unique_ptr installTask; + + if (ourTitleList[titleItr].extension() == ".xci" || ourTitleList[titleItr].extension() == ".xcz") { + auto sdmcXCI = std::make_shared(ourTitleList[titleItr]); + installTask = std::make_unique(m_destStorageId, inst::config::ignoreReqVers, sdmcXCI); + } else { + auto sdmcNSP = std::make_shared(ourTitleList[titleItr]); + installTask = std::make_unique(m_destStorageId, inst::config::ignoreReqVers, sdmcNSP); + } + + LOG_DEBUG("%s\n", "Preparing installation"); + inst::ui::instPage::setInstInfoText("inst.info_page.preparing"_lang); + inst::ui::instPage::setInstBarPerc(0); + installTask->Prepare(); + installTask->Begin(); + } + } + catch (std::exception& e) + { + LOG_DEBUG("Failed to install"); + LOG_DEBUG("%s", e.what()); + fprintf(stdout, "%s", e.what()); + inst::ui::instPage::setInstInfoText("inst.info_page.failed"_lang + inst::util::shortenString(ourTitleList[titleItr].filename().string(), 42, true)); + inst::ui::instPage::setInstBarPerc(0); + std::string audioPath = ""; + if (std::filesystem::exists(inst::config::appDir + "/sounds/OHNO.WAV")) { + audioPath = (inst::config::appDir + "/sounds/OHNO.WAV"); + } + else { + audioPath = "romfs:/audio/bark.wav"; + } + std::thread audioThread(inst::util::playAudio,audioPath); + inst::ui::mainApp->CreateShowDialog("inst.info_page.failed"_lang + inst::util::shortenString(ourTitleList[titleItr].filename().string(), 42, true) + "!", "inst.info_page.failed_desc"_lang + "\n\n" + (std::string)e.what(), {"common.ok"_lang}, true); + audioThread.join(); + nspInstalled = false; + } + + if (previousClockValues.size() > 0) { + inst::util::setClockSpeed(0, previousClockValues[0]); + inst::util::setClockSpeed(1, previousClockValues[1]); + inst::util::setClockSpeed(2, previousClockValues[2]); + } + + if(nspInstalled) { + inst::ui::instPage::setInstInfoText("inst.info_page.complete"_lang); + inst::ui::instPage::setInstBarPerc(100); + std::string audioPath = ""; + + if (inst::config::useSound) { + if (std::filesystem::exists(inst::config::appDir + "/sounds/YIPPEE.WAV")) { + audioPath = (inst::config::appDir + "/sounds/YIPPEE.WAV"); + } + else { + audioPath = "romfs:/audio/ameizing.mp3"; + } + std::thread audioThread(inst::util::playAudio,audioPath); + + if (ourTitleList.size() > 1) { + if (inst::config::deletePrompt) { + if(inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.hd.delete_info_multi"_lang, "inst.hd.delete_desc"_lang, {"common.no"_lang,"common.yes"_lang}, false) == 1) { + for (long unsigned int i = 0; i < ourTitleList.size(); i++) { + if (std::filesystem::exists(ourTitleList[i])) std::filesystem::remove(ourTitleList[i]); + } + } + } else inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.info_page.desc0"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } else { + if (inst::config::deletePrompt) { + if(inst::ui::mainApp->CreateShowDialog(inst::util::shortenString(ourTitleList[0].filename().string(), 32, true) + "inst.hd.delete_info"_lang, "inst.hd.delete_desc"_lang, {"common.no"_lang,"common.yes"_lang}, false) == 1) if (std::filesystem::exists(ourTitleList[0])) std::filesystem::remove(ourTitleList[0]); + } else inst::ui::mainApp->CreateShowDialog(inst::util::shortenString(ourTitleList[0].filename().string(), 42, true) + "inst.info_page.desc1"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } + + audioThread.join(); + } + + else{ + if (ourTitleList.size() > 1) { + if (inst::config::deletePrompt) { + if(inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.hd.delete_info_multi"_lang, "inst.hd.delete_desc"_lang, {"common.no"_lang,"common.yes"_lang}, false) == 1) { + for (long unsigned int i = 0; i < ourTitleList.size(); i++) { + if (std::filesystem::exists(ourTitleList[i])) std::filesystem::remove(ourTitleList[i]); + } + } + } else inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.info_page.desc0"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } else { + if (inst::config::deletePrompt) { + if(inst::ui::mainApp->CreateShowDialog(inst::util::shortenString(ourTitleList[0].filename().string(), 32, true) + "inst.hd.delete_info"_lang, "inst.hd.delete_desc"_lang, {"common.no"_lang,"common.yes"_lang}, false) == 1) if (std::filesystem::exists(ourTitleList[0])) std::filesystem::remove(ourTitleList[0]); + } else inst::ui::mainApp->CreateShowDialog(inst::util::shortenString(ourTitleList[0].filename().string(), 42, true) + "inst.info_page.desc1"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } + } + } + + LOG_DEBUG("Done"); + inst::ui::instPage::loadMainMenu(); + inst::util::deinitInstallServices(); + return; + } +} diff --git a/source/data/buffered_placeholder_writer.cpp b/source/data/buffered_placeholder_writer.cpp new file mode 100644 index 0000000..5e6d071 --- /dev/null +++ b/source/data/buffered_placeholder_writer.cpp @@ -0,0 +1,212 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "data/buffered_placeholder_writer.hpp" + +#include +#include +#include +#include +#include "util/error.hpp" +#include "util/debug.h" + +namespace tin::data +{ + int NUM_BUFFER_SEGMENTS; + + BufferedPlaceholderWriter::BufferedPlaceholderWriter(std::shared_ptr& contentStorage, NcmContentId ncaId, size_t totalDataSize) : + m_totalDataSize(totalDataSize), m_contentStorage(contentStorage), m_ncaId(ncaId), m_writer(ncaId, contentStorage) + { + // Though currently the number of segments is fixed, we want them allocated on the heap, not the stack + m_bufferSegments = std::make_unique(NUM_BUFFER_SEGMENTS); + + if (m_bufferSegments == nullptr) + THROW_FORMAT("Failed to allocated buffer segments!\n"); + + m_currentFreeSegmentPtr = &m_bufferSegments[m_currentFreeSegment]; + m_currentSegmentToWritePtr = &m_bufferSegments[m_currentSegmentToWrite]; + } + + void BufferedPlaceholderWriter::AppendData(void* source, size_t length) + { + if (m_sizeBuffered + length > m_totalDataSize) + THROW_FORMAT("Cannot append data as it would exceed the expected total.\n"); + + size_t dataSizeRemaining = length; + u64 sourceOffset = 0; + + while (dataSizeRemaining > 0) + { + size_t bufferSegmentSizeRemaining = BUFFER_SEGMENT_DATA_SIZE - m_currentFreeSegmentPtr->writeOffset; + + if (m_currentFreeSegmentPtr->isFinalized) + THROW_FORMAT("Current buffer segment is already finalized!\n"); + + if (dataSizeRemaining < bufferSegmentSizeRemaining) + { + memcpy(m_currentFreeSegmentPtr->data + m_currentFreeSegmentPtr->writeOffset, (u8*)source + sourceOffset, dataSizeRemaining); + sourceOffset += dataSizeRemaining; + m_currentFreeSegmentPtr->writeOffset += dataSizeRemaining; + dataSizeRemaining = 0; + } + else + { + memcpy(m_currentFreeSegmentPtr->data + m_currentFreeSegmentPtr->writeOffset, (u8*)source + sourceOffset, bufferSegmentSizeRemaining); + dataSizeRemaining -= bufferSegmentSizeRemaining; + sourceOffset += bufferSegmentSizeRemaining; + m_currentFreeSegmentPtr->writeOffset += bufferSegmentSizeRemaining; + m_currentFreeSegmentPtr->isFinalized = true; + + m_currentFreeSegment = (m_currentFreeSegment + 1) % NUM_BUFFER_SEGMENTS; + m_currentFreeSegmentPtr = &m_bufferSegments[m_currentFreeSegment]; + } + } + + m_sizeBuffered += length; + + if (m_sizeBuffered == m_totalDataSize) + { + m_currentFreeSegmentPtr->isFinalized = true; + } + } + + bool BufferedPlaceholderWriter::CanAppendData(size_t length) + { + if (m_sizeBuffered + length > m_totalDataSize) + return false; + + if (!this->IsSizeAvailable(length)) + return false; + + return true; + } + + void BufferedPlaceholderWriter::WriteSegmentToPlaceholder() + { + if (m_sizeWrittenToPlaceholder >= m_totalDataSize) + THROW_FORMAT("Cannot write segment as end of data has already been reached!\n"); + + if (!m_currentSegmentToWritePtr->isFinalized) + THROW_FORMAT("Cannot write segment as it hasn't been finalized!\n"); + + // NOTE: The final segment will have leftover data from previous writes, however + // this will be accounted for by this size + size_t sizeToWriteToPlaceholder = std::min(m_totalDataSize - m_sizeWrittenToPlaceholder, BUFFER_SEGMENT_DATA_SIZE); + m_writer.write(m_currentSegmentToWritePtr->data, sizeToWriteToPlaceholder); + + m_currentSegmentToWritePtr->isFinalized = false; + m_currentSegmentToWritePtr->writeOffset = 0; + m_currentSegmentToWrite = (m_currentSegmentToWrite + 1) % NUM_BUFFER_SEGMENTS; + m_currentSegmentToWritePtr = &m_bufferSegments[m_currentSegmentToWrite]; + m_sizeWrittenToPlaceholder += sizeToWriteToPlaceholder; + } + + bool BufferedPlaceholderWriter::CanWriteSegmentToPlaceholder() + { + if (m_sizeWrittenToPlaceholder >= m_totalDataSize) + return false; + + if (!m_currentSegmentToWritePtr->isFinalized) + return false; + + return true; + } + + u32 BufferedPlaceholderWriter::CalcNumSegmentsRequired(size_t size) + { + if (m_currentFreeSegmentPtr->isFinalized) + return INT_MAX; + + size_t bufferSegmentSizeRemaining = BUFFER_SEGMENT_DATA_SIZE - m_currentFreeSegmentPtr->writeOffset; + + if (size <= bufferSegmentSizeRemaining) return 1; + else + { + double numSegmentsReq = 1 + (double)(size - bufferSegmentSizeRemaining) / (double)BUFFER_SEGMENT_DATA_SIZE; + return ceil(numSegmentsReq); + } + } + + bool BufferedPlaceholderWriter::IsSizeAvailable(size_t size) + { + u32 numSegmentsRequired = this->CalcNumSegmentsRequired(size); + + if ((int)numSegmentsRequired > NUM_BUFFER_SEGMENTS) + return false; + + for (unsigned int i = 0; i < numSegmentsRequired; i++) + { + unsigned int segmentIndex = m_currentFreeSegment + i; + BufferSegment* bufferSegment = &m_bufferSegments[segmentIndex % NUM_BUFFER_SEGMENTS]; + + if (bufferSegment->isFinalized) + return false; + + if (i != 0 && bufferSegment->writeOffset != 0) + THROW_FORMAT("Unexpected non-zero write offset at segment %u (%lu)\n", segmentIndex, bufferSegment->writeOffset); + } + + return true; + } + + bool BufferedPlaceholderWriter::IsBufferDataComplete() + { + if (m_sizeBuffered > m_totalDataSize) + THROW_FORMAT("Size buffered cannot exceed total data size!\n"); + + return m_sizeBuffered == m_totalDataSize; + } + + bool BufferedPlaceholderWriter::IsPlaceholderComplete() + { + if (m_sizeWrittenToPlaceholder > m_totalDataSize) + THROW_FORMAT("Size written to placeholder cannot exceed total data size!\n"); + + return m_sizeWrittenToPlaceholder == m_totalDataSize; + } + + size_t BufferedPlaceholderWriter::GetTotalDataSize() + { + return m_totalDataSize; + } + + size_t BufferedPlaceholderWriter::GetSizeBuffered() + { + return m_sizeBuffered; + } + + size_t BufferedPlaceholderWriter::GetSizeWrittenToPlaceholder() + { + return m_sizeWrittenToPlaceholder; + } + + void BufferedPlaceholderWriter::DebugPrintBuffers() + { + LOG_DEBUG("BufferedPlaceholderWriter Buffers: \n"); + + for (int i = 0; i < NUM_BUFFER_SEGMENTS; i++) + { + LOG_DEBUG("Buffer %u:\n", i); + printBytes(m_bufferSegments[i].data, BUFFER_SEGMENT_DATA_SIZE, true); + } + } +} \ No newline at end of file diff --git a/source/data/byte_buffer.cpp b/source/data/byte_buffer.cpp new file mode 100644 index 0000000..033da8f --- /dev/null +++ b/source/data/byte_buffer.cpp @@ -0,0 +1,55 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "data/byte_buffer.hpp" + +#include "util/error.hpp" +#include "util/debug.h" + +namespace tin::data +{ + ByteBuffer::ByteBuffer(size_t reserveSize) + { + m_buffer.resize(reserveSize); + } + + size_t ByteBuffer::GetSize() + { + return m_buffer.size(); + } + + u8* ByteBuffer::GetData() + { + return m_buffer.data(); + } + + void ByteBuffer::Resize(size_t size) + { + m_buffer.resize(size, 0); + } + + void ByteBuffer::DebugPrintContents() + { + LOG_DEBUG("Buffer Size: 0x%lx\n", this->GetSize()); + printBytes(this->GetData(), this->GetSize(), true); + } +} \ No newline at end of file diff --git a/source/data/byte_stream.cpp b/source/data/byte_stream.cpp new file mode 100644 index 0000000..fb76391 --- /dev/null +++ b/source/data/byte_stream.cpp @@ -0,0 +1,41 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "data/byte_stream.hpp" + +namespace tin::data +{ + BufferedByteStream::BufferedByteStream(ByteBuffer buffer) : + m_byteBuffer(buffer) + { + + } + + void BufferedByteStream::ReadBytes(void* dest, size_t length) + { + if (m_offset + length > m_byteBuffer.GetSize()) + return; + + memcpy(dest, m_byteBuffer.GetData() + m_offset, length); + m_offset += length; + } +} \ No newline at end of file diff --git a/source/install/http_nsp.cpp b/source/install/http_nsp.cpp new file mode 100644 index 0000000..95480b6 --- /dev/null +++ b/source/install/http_nsp.cpp @@ -0,0 +1,154 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/http_nsp.hpp" + +#include +#include +#include "data/buffered_placeholder_writer.hpp" +#include "util/title_util.hpp" +#include "util/error.hpp" +#include "util/debug.h" +#include "util/util.hpp" +#include "util/lang.hpp" +#include "ui/instPage.hpp" + +namespace tin::install::nsp +{ + bool stopThreadsHttpNsp; + + HTTPNSP::HTTPNSP(std::string url) : + m_download(url) + { + + } + + struct StreamFuncArgs + { + tin::network::HTTPDownload* download; + tin::data::BufferedPlaceholderWriter* bufferedPlaceholderWriter; + u64 pfs0Offset; + u64 ncaSize; + }; + + int CurlStreamFunc(void* in) + { + StreamFuncArgs* args = reinterpret_cast(in); + + auto streamFunc = [&](u8* streamBuf, size_t streamBufSize) -> size_t + { + while (true) + { + if (args->bufferedPlaceholderWriter->CanAppendData(streamBufSize)) + break; + } + + args->bufferedPlaceholderWriter->AppendData(streamBuf, streamBufSize); + return streamBufSize; + }; + + if (args->download->StreamDataRange(args->pfs0Offset, args->ncaSize, streamFunc) == 1) stopThreadsHttpNsp = true; + return 0; + } + + int PlaceholderWriteFunc(void* in) + { + StreamFuncArgs* args = reinterpret_cast(in); + + while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete() && !stopThreadsHttpNsp) + { + if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder()) + args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder(); + } + + return 0; + } + + void HTTPNSP::StreamToPlaceholder(std::shared_ptr& contentStorage, NcmContentId placeholderId) + { + const PFS0FileEntry* fileEntry = this->GetFileEntryByNcaId(placeholderId); + std::string ncaFileName = this->GetFileEntryName(fileEntry); + + LOG_DEBUG("Retrieving %s\n", ncaFileName.c_str()); + size_t ncaSize = fileEntry->fileSize; + + tin::data::BufferedPlaceholderWriter bufferedPlaceholderWriter(contentStorage, placeholderId, ncaSize); + StreamFuncArgs args; + args.download = &m_download; + args.bufferedPlaceholderWriter = &bufferedPlaceholderWriter; + args.pfs0Offset = this->GetDataOffset() + fileEntry->dataOffset; + args.ncaSize = ncaSize; + thrd_t curlThread; + thrd_t writeThread; + + stopThreadsHttpNsp = false; + thrd_create(&curlThread, CurlStreamFunc, &args); + thrd_create(&writeThread, PlaceholderWriteFunc, &args); + + u64 freq = armGetSystemTickFreq(); + u64 startTime = armGetSystemTick(); + size_t startSizeBuffered = 0; + double speed = 0.0; + + inst::ui::instPage::setInstBarPerc(0); + while (!bufferedPlaceholderWriter.IsBufferDataComplete() && !stopThreadsHttpNsp) + { + u64 newTime = armGetSystemTick(); + + if (newTime - startTime >= freq * 0.5) + { + size_t newSizeBuffered = bufferedPlaceholderWriter.GetSizeBuffered(); + double mbBuffered = (newSizeBuffered / 1000000.0) - (startSizeBuffered / 1000000.0); + double duration = ((double)(newTime - startTime) / (double)freq); + speed = mbBuffered / duration; + + startTime = newTime; + startSizeBuffered = newSizeBuffered; + + int downloadProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeBuffered() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0); + + inst::ui::instPage::setInstInfoText("inst.info_page.downloading"_lang + inst::util::formatUrlString(ncaFileName) + "inst.info_page.at"_lang + std::to_string(speed).substr(0, std::to_string(speed).size()-4) + "MB/s"); + inst::ui::instPage::setInstBarPerc((double)downloadProgress); + } + } + inst::ui::instPage::setInstBarPerc(100); + + inst::ui::instPage::setInstInfoText("inst.info_page.top_info0"_lang + ncaFileName + "..."); + inst::ui::instPage::setInstBarPerc(0); + while (!bufferedPlaceholderWriter.IsPlaceholderComplete() && !stopThreadsHttpNsp) + { + int installProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0); + + inst::ui::instPage::setInstBarPerc((double)installProgress); + } + inst::ui::instPage::setInstBarPerc(100); + + thrd_join(curlThread, NULL); + thrd_join(writeThread, NULL); + if (stopThreadsHttpNsp) THROW_FORMAT(("inst.net.transfer_interput"_lang).c_str()); + } + + void HTTPNSP::BufferData(void* buf, off_t offset, size_t size) + { + m_download.BufferDataRange(buf, offset, size, nullptr); + } +} \ No newline at end of file diff --git a/source/install/http_xci.cpp b/source/install/http_xci.cpp new file mode 100644 index 0000000..499416f --- /dev/null +++ b/source/install/http_xci.cpp @@ -0,0 +1,162 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/http_xci.hpp" + +#include +#include "data/buffered_placeholder_writer.hpp" +#include "util/error.hpp" +#include "util/util.hpp" +#include "util/lang.hpp" +#include "ui/instPage.hpp" + +namespace tin::install::xci +{ + bool stopThreadsHttpXci; + + HTTPXCI::HTTPXCI(std::string url) : + m_download(url) + { + + } + + struct StreamFuncArgs + { + tin::network::HTTPDownload* download; + tin::data::BufferedPlaceholderWriter* bufferedPlaceholderWriter; + u64 pfs0Offset; + u64 ncaSize; + }; + + int CurlStreamFunc(void* in) + { + StreamFuncArgs* args = reinterpret_cast(in); + + auto streamFunc = [&](u8* streamBuf, size_t streamBufSize) -> size_t + { + while (true) + { + if (args->bufferedPlaceholderWriter->CanAppendData(streamBufSize)) + break; + } + + args->bufferedPlaceholderWriter->AppendData(streamBuf, streamBufSize); + return streamBufSize; + }; + + if (args->download->StreamDataRange(args->pfs0Offset, args->ncaSize, streamFunc) == 1) stopThreadsHttpXci = true; + return 0; + } + + int PlaceholderWriteFunc(void* in) + { + StreamFuncArgs* args = reinterpret_cast(in); + + while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete() && !stopThreadsHttpXci) + { + if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder()) + args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder(); + } + + return 0; + } + + void HTTPXCI::StreamToPlaceholder(std::shared_ptr& contentStorage, NcmContentId ncaId) + { + const HFS0FileEntry* fileEntry = this->GetFileEntryByNcaId(ncaId); + std::string ncaFileName = this->GetFileEntryName(fileEntry); + + LOG_DEBUG("Retrieving %s\n", ncaFileName.c_str()); + size_t ncaSize = fileEntry->fileSize; + + tin::data::BufferedPlaceholderWriter bufferedPlaceholderWriter(contentStorage, ncaId, ncaSize); + StreamFuncArgs args; + args.download = &m_download; + args.bufferedPlaceholderWriter = &bufferedPlaceholderWriter; + args.pfs0Offset = this->GetDataOffset() + fileEntry->dataOffset; + args.ncaSize = ncaSize; + thrd_t curlThread; + thrd_t writeThread; + + stopThreadsHttpXci = false; + thrd_create(&curlThread, CurlStreamFunc, &args); + thrd_create(&writeThread, PlaceholderWriteFunc, &args); + + u64 freq = armGetSystemTickFreq(); + u64 startTime = armGetSystemTick(); + size_t startSizeBuffered = 0; + double speed = 0.0; + + inst::ui::instPage::setInstBarPerc(0); + while (!bufferedPlaceholderWriter.IsBufferDataComplete() && !stopThreadsHttpXci) + { + u64 newTime = armGetSystemTick(); + + if (newTime - startTime >= freq * 0.5) + { + size_t newSizeBuffered = bufferedPlaceholderWriter.GetSizeBuffered(); + double mbBuffered = (newSizeBuffered / 1000000.0) - (startSizeBuffered / 1000000.0); + double duration = ((double)(newTime - startTime) / (double)freq); + speed = mbBuffered / duration; + + startTime = newTime; + startSizeBuffered = newSizeBuffered; + int downloadProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeBuffered() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0); + #ifdef NXLINK_DEBUG + u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000; + u64 downloadSizeMB = bufferedPlaceholderWriter.GetSizeBuffered() / 1000000; + LOG_DEBUG("> Download Progress: %lu/%lu MB (%i%s) (%.2f MB/s)\r", downloadSizeMB, totalSizeMB, downloadProgress, "%", speed); + #endif + + inst::ui::instPage::setInstInfoText("inst.info_page.downloading"_lang + inst::util::formatUrlString(ncaFileName) + "inst.info_page.at"_lang + std::to_string(speed).substr(0, std::to_string(speed).size()-4) + "MB/s"); + inst::ui::instPage::setInstBarPerc((double)downloadProgress); + } + } + inst::ui::instPage::setInstBarPerc(100); + + #ifdef NXLINK_DEBUG + u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000; + #endif + + inst::ui::instPage::setInstInfoText("inst.info_page.top_info0"_lang + ncaFileName + "..."); + inst::ui::instPage::setInstBarPerc(0); + while (!bufferedPlaceholderWriter.IsPlaceholderComplete() && !stopThreadsHttpXci) + { + int installProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0); + #ifdef NXLINK_DEBUG + u64 installSizeMB = bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / 1000000; + LOG_DEBUG("> Install Progress: %lu/%lu MB (%i%s)\r", installSizeMB, totalSizeMB, installProgress, "%"); + #endif + inst::ui::instPage::setInstBarPerc((double)installProgress); + } + inst::ui::instPage::setInstBarPerc(100); + + thrd_join(curlThread, NULL); + thrd_join(writeThread, NULL); + if (stopThreadsHttpXci) THROW_FORMAT(("inst.net.transfer_interput"_lang).c_str()); + } + + void HTTPXCI::BufferData(void* buf, off_t offset, size_t size) + { + m_download.BufferDataRange(buf, offset, size, nullptr); + } +} \ No newline at end of file diff --git a/source/install/install.cpp b/source/install/install.cpp new file mode 100644 index 0000000..4fca448 --- /dev/null +++ b/source/install/install.cpp @@ -0,0 +1,192 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/install.hpp" + +#include +#include +#include +#include "util/error.hpp" + +#include "nx/ncm.hpp" +#include "util/title_util.hpp" + + +// TODO: Check NCA files are present +// TODO: Check tik/cert is present +namespace tin::install +{ + Install::Install(NcmStorageId destStorageId, bool ignoreReqFirmVersion) : + m_destStorageId(destStorageId), m_ignoreReqFirmVersion(ignoreReqFirmVersion), m_contentMeta() + { + appletSetMediaPlaybackState(true); + } + + Install::~Install() + { + appletSetMediaPlaybackState(false); + } + + // TODO: Implement RAII on NcmContentMetaDatabase + void Install::InstallContentMetaRecords(tin::data::ByteBuffer& installContentMetaBuf, int i) + { + NcmContentMetaDatabase contentMetaDatabase; + NcmContentMetaKey contentMetaKey = m_contentMeta[i].GetContentMetaKey(); + + try + { + ASSERT_OK(ncmOpenContentMetaDatabase(&contentMetaDatabase, m_destStorageId), "Failed to open content meta database"); + ASSERT_OK(ncmContentMetaDatabaseSet(&contentMetaDatabase, &contentMetaKey, (NcmContentMetaHeader*)installContentMetaBuf.GetData(), installContentMetaBuf.GetSize()), "Failed to set content records"); + ASSERT_OK(ncmContentMetaDatabaseCommit(&contentMetaDatabase), "Failed to commit content records"); + } + catch (std::runtime_error& e) + { + serviceClose(&contentMetaDatabase.s); + THROW_FORMAT(e.what()); + } + + serviceClose(&contentMetaDatabase.s); + } + + void Install::InstallApplicationRecord(int i) + { + Result rc = 0; + std::vector storageRecords; + u64 baseTitleId = tin::util::GetBaseTitleId(this->GetTitleId(i), this->GetContentMetaType(i)); + s32 contentMetaCount = 0; + + LOG_DEBUG("Base title Id: 0x%lx", baseTitleId); + + // TODO: Make custom error with result code field + // 0x410: The record doesn't already exist + if (R_FAILED(rc = nsCountApplicationContentMeta(baseTitleId, &contentMetaCount)) && rc != 0x410) + { + THROW_FORMAT("Failed to count application content meta"); + } + rc = 0; + + LOG_DEBUG("Content meta count: %u\n", contentMetaCount); + + // Obtain any existing app record content meta and append it to our vector + if (contentMetaCount > 0) + { + storageRecords.resize(contentMetaCount); + size_t contentStorageBufSize = contentMetaCount * sizeof(ContentStorageRecord); + auto contentStorageBuf = std::make_unique(contentMetaCount); + u32 entriesRead; + + ASSERT_OK(nsListApplicationRecordContentMeta(0, baseTitleId, contentStorageBuf.get(), contentStorageBufSize, &entriesRead), "Failed to list application record content meta"); + + if ((s32)entriesRead != contentMetaCount) + { + THROW_FORMAT("Mismatch between entries read and content meta count"); + } + + memcpy(storageRecords.data(), contentStorageBuf.get(), contentStorageBufSize); + } + + // Add our new content meta + ContentStorageRecord storageRecord; + storageRecord.metaRecord = m_contentMeta[i].GetContentMetaKey(); + storageRecord.storageId = m_destStorageId; + storageRecords.push_back(storageRecord); + + // Replace the existing application records with our own + try + { + nsDeleteApplicationRecord(baseTitleId); + } + catch (...) {} + + LOG_DEBUG("Pushing application record...\n"); + ASSERT_OK(nsPushApplicationRecord(baseTitleId, 0x3, storageRecords.data(), storageRecords.size() * sizeof(ContentStorageRecord)), "Failed to push application record"); + } + + // Validate and obtain all data needed for install + void Install::Prepare() + { + tin::data::ByteBuffer cnmtBuf; + + std::vector> tupelList = this->ReadCNMT(); + + for (size_t i = 0; i < tupelList.size(); i++) { + std::tuple cnmtTuple = tupelList[i]; + + m_contentMeta.push_back(std::get<0>(cnmtTuple)); + NcmContentInfo cnmtContentRecord = std::get<1>(cnmtTuple); + + nx::ncm::ContentStorage contentStorage(m_destStorageId); + + if (!contentStorage.Has(cnmtContentRecord.content_id)) + { + LOG_DEBUG("Installing CNMT NCA...\n"); + this->InstallNCA(cnmtContentRecord.content_id); + } + else + { + LOG_DEBUG("CNMT NCA already installed. Proceeding...\n"); + } + + // Parse data and create install content meta + if (m_ignoreReqFirmVersion) + LOG_DEBUG("WARNING: Required system firmware version is being IGNORED!\n"); + + tin::data::ByteBuffer installContentMetaBuf; + m_contentMeta[i].GetInstallContentMeta(installContentMetaBuf, cnmtContentRecord, m_ignoreReqFirmVersion); + + this->InstallContentMetaRecords(installContentMetaBuf, i); + this->InstallApplicationRecord(i); + } + } + + void Install::Begin() + { + LOG_DEBUG("Installing ticket and cert...\n"); + try + { + this->InstallTicketCert(); + } + catch (std::runtime_error& e) + { + LOG_DEBUG("WARNING: Ticket installation failed! This may not be an issue, depending on your use case.\nProceed with caution!\n"); + } + + for (nx::ncm::ContentMeta contentMeta: m_contentMeta) { + LOG_DEBUG("Installing NCAs...\n"); + for (auto& record : contentMeta.GetContentInfos()) + { + LOG_DEBUG("Installing from %s\n", tin::util::GetNcaIdString(record.content_id).c_str()); + this->InstallNCA(record.content_id); + } + } + } + + u64 Install::GetTitleId(int i) + { + return m_contentMeta[i].GetContentMetaKey().id; + } + + NcmContentMetaType Install::GetContentMetaType(int i) + { + return static_cast(m_contentMeta[i].GetContentMetaKey().type); + } +} diff --git a/source/install/install_nsp.cpp b/source/install/install_nsp.cpp new file mode 100644 index 0000000..630b882 --- /dev/null +++ b/source/install/install_nsp.cpp @@ -0,0 +1,182 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/install_nsp.hpp" + +#include +#include + +#include "install/nca.hpp" +#include "nx/fs.hpp" +#include "nx/ncm.hpp" +#include "util/config.hpp" +#include "util/crypto.hpp" +#include "util/file_util.hpp" +#include "util/title_util.hpp" +#include "util/debug.h" +#include "util/error.hpp" +#include "util/util.hpp" +#include "util/lang.hpp" +#include "ui/MainApplication.hpp" + +namespace inst::ui { + extern MainApplication *mainApp; +} + +namespace tin::install::nsp +{ + NSPInstall::NSPInstall(NcmStorageId destStorageId, bool ignoreReqFirmVersion, const std::shared_ptr& remoteNSP) : + Install(destStorageId, ignoreReqFirmVersion), m_NSP(remoteNSP) + { + m_NSP->RetrieveHeader(); + } + + std::vector> NSPInstall::ReadCNMT() + { + std::vector> CNMTList; + + for (const PFS0FileEntry* fileEntry : m_NSP->GetFileEntriesByExtension("cnmt.nca")) { + std::string cnmtNcaName(m_NSP->GetFileEntryName(fileEntry)); + NcmContentId cnmtContentId = tin::util::GetNcaIdFromString(cnmtNcaName); + size_t cnmtNcaSize = fileEntry->fileSize; + + nx::ncm::ContentStorage contentStorage(m_destStorageId); + + LOG_DEBUG("CNMT Name: %s\n", cnmtNcaName.c_str()); + + // We install the cnmt nca early to read from it later + this->InstallNCA(cnmtContentId); + std::string cnmtNCAFullPath = contentStorage.GetPath(cnmtContentId); + + NcmContentInfo cnmtContentInfo; + cnmtContentInfo.content_id = cnmtContentId; + *(u64*)&cnmtContentInfo.size = cnmtNcaSize & 0xFFFFFFFFFFFF; + cnmtContentInfo.content_type = NcmContentType_Meta; + + CNMTList.push_back( { tin::util::GetContentMetaFromNCA(cnmtNCAFullPath), cnmtContentInfo } ); + } + + return CNMTList; + } + + void NSPInstall::InstallNCA(const NcmContentId& ncaId) + { + const PFS0FileEntry* fileEntry = m_NSP->GetFileEntryByNcaId(ncaId); + std::string ncaFileName = m_NSP->GetFileEntryName(fileEntry); + + #ifdef NXLINK_DEBUG + size_t ncaSize = fileEntry->fileSize; + LOG_DEBUG("Installing %s to storage Id %u\n", ncaFileName.c_str(), m_destStorageId); + #endif + + std::shared_ptr contentStorage(new nx::ncm::ContentStorage(m_destStorageId)); + + // Attempt to delete any leftover placeholders + try { + contentStorage->DeletePlaceholder(*(NcmPlaceHolderId*)&ncaId); + } + catch (...) {} + // Attempt to delete leftover ncas + try { + contentStorage->Delete(ncaId); + } + catch (...) {} + + LOG_DEBUG("Size: 0x%lx\n", ncaSize); + + if (inst::config::validateNCAs && !m_declinedValidation) + { + tin::install::NcaHeader* header = new NcaHeader; + m_NSP->BufferData(header, m_NSP->GetDataOffset() + fileEntry->dataOffset, sizeof(tin::install::NcaHeader)); + + Crypto::AesXtr crypto(Crypto::Keys().headerKey, false); + crypto.decrypt(header, header, sizeof(tin::install::NcaHeader), 0, 0x200); + + if (header->magic != MAGIC_NCA3) + THROW_FORMAT("Invalid NCA magic"); + + if (!Crypto::rsa2048PssVerify(&header->magic, 0x200, header->fixed_key_sig, Crypto::NCAHeaderSignature)) + { + std::thread audioThread(inst::util::playAudio,"romfs:/audio/bark.wav"); + int rc = inst::ui::mainApp->CreateShowDialog("inst.nca_verify.title"_lang, "inst.nca_verify.desc"_lang, {"common.cancel"_lang, "inst.nca_verify.opt1"_lang}, false); + audioThread.join(); + if (rc != 1) + THROW_FORMAT(("inst.nca_verify.error"_lang + tin::util::GetNcaIdString(ncaId)).c_str()); + m_declinedValidation = true; + } + delete header; + } + + m_NSP->StreamToPlaceholder(contentStorage, ncaId); + + LOG_DEBUG("Registering placeholder...\n"); + + try + { + contentStorage->Register(*(NcmPlaceHolderId*)&ncaId, ncaId); + } + catch (...) + { + LOG_DEBUG(("Failed to register " + ncaFileName + ". It may already exist.\n").c_str()); + } + + try + { + contentStorage->DeletePlaceholder(*(NcmPlaceHolderId*)&ncaId); + } + catch (...) {} + } + + void NSPInstall::InstallTicketCert() + { + // Read the tik files and put it into a buffer + std::vector tikFileEntries = m_NSP->GetFileEntriesByExtension("tik"); + std::vector certFileEntries = m_NSP->GetFileEntriesByExtension("cert"); + + for (size_t i = 0; i < tikFileEntries.size(); i++) + { + if (tikFileEntries[i] == nullptr) { + LOG_DEBUG("Remote tik file is missing.\n"); + THROW_FORMAT("Remote tik file is not present!"); + } + + u64 tikSize = tikFileEntries[i]->fileSize; + auto tikBuf = std::make_unique(tikSize); + LOG_DEBUG("> Reading tik\n"); + m_NSP->BufferData(tikBuf.get(), m_NSP->GetDataOffset() + tikFileEntries[i]->dataOffset, tikSize); + + if (certFileEntries[i] == nullptr) + { + LOG_DEBUG("Remote cert file is missing.\n"); + THROW_FORMAT("Remote cert file is not present!"); + } + + u64 certSize = certFileEntries[i]->fileSize; + auto certBuf = std::make_unique(certSize); + LOG_DEBUG("> Reading cert\n"); + m_NSP->BufferData(certBuf.get(), m_NSP->GetDataOffset() + certFileEntries[i]->dataOffset, certSize); + + // Finally, let's actually import the ticket + ASSERT_OK(esImportTicket(tikBuf.get(), tikSize, certBuf.get(), certSize), "Failed to import ticket"); + } + } +} \ No newline at end of file diff --git a/source/install/install_xci.cpp b/source/install/install_xci.cpp new file mode 100644 index 0000000..2ce6469 --- /dev/null +++ b/source/install/install_xci.cpp @@ -0,0 +1,181 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include + +#include "install/install_xci.hpp" +#include "util/file_util.hpp" +#include "util/title_util.hpp" +#include "util/debug.h" +#include "util/error.hpp" +#include "util/config.hpp" +#include "util/crypto.hpp" +#include "util/util.hpp" +#include "util/lang.hpp" +#include "install/nca.hpp" +#include "ui/MainApplication.hpp" + +namespace inst::ui { + extern MainApplication *mainApp; +} + +namespace tin::install::xci +{ + XCIInstallTask::XCIInstallTask(NcmStorageId destStorageId, bool ignoreReqFirmVersion, const std::shared_ptr& xci) : + Install(destStorageId, ignoreReqFirmVersion), m_xci(xci) + { + m_xci->RetrieveHeader(); + } + + std::vector> XCIInstallTask::ReadCNMT() + { + std::vector> CNMTList; + + for (const HFS0FileEntry* fileEntry : m_xci->GetFileEntriesByExtension("cnmt.nca")) { + std::string cnmtNcaName(m_xci->GetFileEntryName(fileEntry)); + NcmContentId cnmtContentId = tin::util::GetNcaIdFromString(cnmtNcaName); + size_t cnmtNcaSize = fileEntry->fileSize; + + nx::ncm::ContentStorage contentStorage(m_destStorageId); + + LOG_DEBUG("CNMT Name: %s\n", cnmtNcaName.c_str()); + + // We install the cnmt nca early to read from it later + this->InstallNCA(cnmtContentId); + std::string cnmtNCAFullPath = contentStorage.GetPath(cnmtContentId); + + NcmContentInfo cnmtContentInfo; + cnmtContentInfo.content_id = cnmtContentId; + *(u64*)&cnmtContentInfo.size = cnmtNcaSize & 0xFFFFFFFFFFFF; + cnmtContentInfo.content_type = NcmContentType_Meta; + + CNMTList.push_back( { tin::util::GetContentMetaFromNCA(cnmtNCAFullPath), cnmtContentInfo } ); + } + + return CNMTList; + } + + void XCIInstallTask::InstallNCA(const NcmContentId& ncaId) + { + const HFS0FileEntry* fileEntry = m_xci->GetFileEntryByNcaId(ncaId); + std::string ncaFileName = m_xci->GetFileEntryName(fileEntry); + + #ifdef NXLINK_DEBUG + size_t ncaSize = fileEntry->fileSize; + LOG_DEBUG("Installing %s to storage Id %u\n", ncaFileName.c_str(), m_destStorageId); + #endif + + std::shared_ptr contentStorage(new nx::ncm::ContentStorage(m_destStorageId)); + + // Attempt to delete any leftover placeholders + try { + contentStorage->DeletePlaceholder(*(NcmPlaceHolderId*)&ncaId); + } + catch (...) {} + // Attempt to delete leftover ncas + try { + contentStorage->Delete(ncaId); + } + catch (...) {} + + LOG_DEBUG("Size: 0x%lx\n", ncaSize); + + if (inst::config::validateNCAs && !m_declinedValidation) + { + tin::install::NcaHeader* header = new NcaHeader; + m_xci->BufferData(header, m_xci->GetDataOffset() + fileEntry->dataOffset, sizeof(tin::install::NcaHeader)); + + Crypto::AesXtr crypto(Crypto::Keys().headerKey, false); + crypto.decrypt(header, header, sizeof(tin::install::NcaHeader), 0, 0x200); + + if (header->magic != MAGIC_NCA3) + THROW_FORMAT("Invalid NCA magic"); + + if (!Crypto::rsa2048PssVerify(&header->magic, 0x200, header->fixed_key_sig, Crypto::NCAHeaderSignature)) + { + std::thread audioThread(inst::util::playAudio,"romfs:/audio/bark.wav"); + int rc = inst::ui::mainApp->CreateShowDialog("inst.nca_verify.title"_lang, "inst.nca_verify.desc"_lang, {"common.cancel"_lang, "inst.nca_verify.opt1"_lang}, false); + audioThread.join(); + if (rc != 1) + THROW_FORMAT(("inst.nca_verify.error"_lang + tin::util::GetNcaIdString(ncaId)).c_str()); + m_declinedValidation = true; + } + delete header; + } + + m_xci->StreamToPlaceholder(contentStorage, ncaId); + + // Clean up the line for whatever comes next + LOG_DEBUG(" \r"); + LOG_DEBUG("Registering placeholder...\n"); + + try + { + contentStorage->Register(*(NcmPlaceHolderId*)&ncaId, ncaId); + } + catch (...) + { + LOG_DEBUG(("Failed to register " + ncaFileName + ". It may already exist.\n").c_str()); + } + + try + { + contentStorage->DeletePlaceholder(*(NcmPlaceHolderId*)&ncaId); + } + catch (...) {} + } + + void XCIInstallTask::InstallTicketCert() + { + // Read the tik files and put it into a buffer + std::vector tikFileEntries = m_xci->GetFileEntriesByExtension("tik"); + std::vector certFileEntries = m_xci->GetFileEntriesByExtension("cert"); + + for (size_t i = 0; i < tikFileEntries.size(); i++) + { + if (tikFileEntries[i] == nullptr) + { + LOG_DEBUG("Remote tik file is missing.\n"); + THROW_FORMAT("Remote tik file is not present!"); + } + + u64 tikSize = tikFileEntries[i]->fileSize; + auto tikBuf = std::make_unique(tikSize); + LOG_DEBUG("> Reading tik\n"); + m_xci->BufferData(tikBuf.get(), m_xci->GetDataOffset() + tikFileEntries[i]->dataOffset, tikSize); + + if (certFileEntries[i] == nullptr) + { + LOG_DEBUG("Remote cert file is missing.\n"); + THROW_FORMAT("Remote cert file is not present!"); + } + + u64 certSize = certFileEntries[i]->fileSize; + auto certBuf = std::make_unique(certSize); + LOG_DEBUG("> Reading cert\n"); + m_xci->BufferData(certBuf.get(), m_xci->GetDataOffset() + certFileEntries[i]->dataOffset, certSize); + + // Finally, let's actually import the ticket + ASSERT_OK(esImportTicket(tikBuf.get(), tikSize, certBuf.get(), certSize), "Failed to import ticket"); + } + } +} \ No newline at end of file diff --git a/source/install/nsp.cpp b/source/install/nsp.cpp new file mode 100644 index 0000000..c2f4de0 --- /dev/null +++ b/source/install/nsp.cpp @@ -0,0 +1,143 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/nsp.hpp" + +#include +#include "data/buffered_placeholder_writer.hpp" +#include "util/title_util.hpp" +#include "util/error.hpp" +#include "util/debug.h" + +namespace tin::install::nsp +{ + NSP::NSP() {} + + // TODO: Do verification: PFS0 magic, sizes not zero + void NSP::RetrieveHeader() + { + LOG_DEBUG("Retrieving remote NSP header...\n"); + + // Retrieve the base header + m_headerBytes.resize(sizeof(PFS0BaseHeader), 0); + this->BufferData(m_headerBytes.data(), 0x0, sizeof(PFS0BaseHeader)); + + LOG_DEBUG("Base header: \n"); + printBytes(m_headerBytes.data(), sizeof(PFS0BaseHeader), true); + + // Retrieve the full header + size_t remainingHeaderSize = this->GetBaseHeader()->numFiles * sizeof(PFS0FileEntry) + this->GetBaseHeader()->stringTableSize; + m_headerBytes.resize(sizeof(PFS0BaseHeader) + remainingHeaderSize, 0); + this->BufferData(m_headerBytes.data() + sizeof(PFS0BaseHeader), sizeof(PFS0BaseHeader), remainingHeaderSize); + + LOG_DEBUG("Full header: \n"); + printBytes(m_headerBytes.data(), m_headerBytes.size(), true); + } + + const PFS0FileEntry* NSP::GetFileEntry(unsigned int index) + { + if (index >= this->GetBaseHeader()->numFiles) + THROW_FORMAT("File entry index is out of bounds\n") + + size_t fileEntryOffset = sizeof(PFS0BaseHeader) + index * sizeof(PFS0FileEntry); + + if (m_headerBytes.size() < fileEntryOffset + sizeof(PFS0FileEntry)) + THROW_FORMAT("Header bytes is too small to get file entry!"); + + return reinterpret_cast(m_headerBytes.data() + fileEntryOffset); + } + + std::vector NSP::GetFileEntriesByExtension(std::string extension) + { + std::vector entryList; + + for (unsigned int i = 0; i < this->GetBaseHeader()->numFiles; i++) + { + const PFS0FileEntry* fileEntry = this->GetFileEntry(i); + std::string name(this->GetFileEntryName(fileEntry)); + auto foundExtension = name.substr(name.find(".") + 1); + + if (foundExtension == extension) + entryList.push_back(fileEntry); + } + + return entryList; + } + + const PFS0FileEntry* NSP::GetFileEntryByName(std::string name) + { + for (unsigned int i = 0; i < this->GetBaseHeader()->numFiles; i++) + { + const PFS0FileEntry* fileEntry = this->GetFileEntry(i); + std::string foundName(this->GetFileEntryName(fileEntry)); + + if (foundName == name) + return fileEntry; + } + + return nullptr; + } + + const PFS0FileEntry* NSP::GetFileEntryByNcaId(const NcmContentId& ncaId) + { + const PFS0FileEntry* fileEntry = nullptr; + std::string ncaIdStr = tin::util::GetNcaIdString(ncaId); + + if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".nca")) == nullptr) + { + if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".cnmt.nca")) == nullptr) + { + if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".ncz")) == nullptr) + { + if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".cnmt.ncz")) == nullptr) + { + return nullptr; + } + } + } + } + + return fileEntry; + } + + const char* NSP::GetFileEntryName(const PFS0FileEntry* fileEntry) + { + u64 stringTableStart = sizeof(PFS0BaseHeader) + this->GetBaseHeader()->numFiles * sizeof(PFS0FileEntry); + return reinterpret_cast(m_headerBytes.data() + stringTableStart + fileEntry->stringTableOffset); + } + + const PFS0BaseHeader* NSP::GetBaseHeader() + { + if (m_headerBytes.empty()) + THROW_FORMAT("Cannot retrieve header as header bytes are empty. Have you retrieved it yet?\n"); + + return reinterpret_cast(m_headerBytes.data()); + } + + u64 NSP::GetDataOffset() + { + if (m_headerBytes.empty()) + THROW_FORMAT("Cannot get data offset as header is empty. Have you retrieved it yet?\n"); + + return m_headerBytes.size(); + } +} \ No newline at end of file diff --git a/source/install/sdmc_nsp.cpp b/source/install/sdmc_nsp.cpp new file mode 100644 index 0000000..e8d7b66 --- /dev/null +++ b/source/install/sdmc_nsp.cpp @@ -0,0 +1,75 @@ +#include "install/sdmc_nsp.hpp" +#include "error.hpp" +#include "debug.h" +#include "nx/nca_writer.h" +#include "ui/instPage.hpp" +#include "util/lang.hpp" + +namespace tin::install::nsp +{ + SDMCNSP::SDMCNSP(std::string path) + { + m_nspFile = fopen((path).c_str(), "rb"); + if (!m_nspFile) + THROW_FORMAT("can't open file at %s\n", path.c_str()); + } + + SDMCNSP::~SDMCNSP() + { + fclose(m_nspFile); + } + + void SDMCNSP::StreamToPlaceholder(std::shared_ptr& contentStorage, NcmContentId ncaId) + { + const PFS0FileEntry* fileEntry = this->GetFileEntryByNcaId(ncaId); + std::string ncaFileName = this->GetFileEntryName(fileEntry); + + LOG_DEBUG("Retrieving %s\n", ncaFileName.c_str()); + size_t ncaSize = fileEntry->fileSize; + + NcaWriter writer(ncaId, contentStorage); + + float progress; + + u64 fileStart = GetDataOffset() + fileEntry->dataOffset; + u64 fileOff = 0; + size_t readSize = 0x400000; // 4MB buff + auto readBuffer = std::make_unique(readSize); + + try + { + inst::ui::instPage::setInstInfoText("inst.info_page.top_info0"_lang + ncaFileName + "..."); + inst::ui::instPage::setInstBarPerc(0); + while (fileOff < ncaSize) + { + progress = (float) fileOff / (float) ncaSize; + + if (fileOff % (0x400000 * 3) == 0) { + LOG_DEBUG("> Progress: %lu/%lu MB (%d%s)\r", (fileOff / 1000000), (ncaSize / 1000000), (int)(progress * 100.0), "%"); + inst::ui::instPage::setInstBarPerc((double)(progress * 100.0)); + } + + if (fileOff + readSize >= ncaSize) readSize = ncaSize - fileOff; + + this->BufferData(readBuffer.get(), fileOff + fileStart, readSize); + writer.write(readBuffer.get(), readSize); + + fileOff += readSize; + } + inst::ui::instPage::setInstBarPerc(100); + } + catch (std::exception& e) + { + LOG_DEBUG("something went wrong: %s\n", e.what()); + } + + writer.close(); + } + + void SDMCNSP::BufferData(void* buf, off_t offset, size_t size) + { + fseeko(m_nspFile, offset, SEEK_SET); + //fseek(m_nspFile, offset, SEEK_SET); + fread(buf, 1, size, m_nspFile); + } +} diff --git a/source/install/sdmc_xci.cpp b/source/install/sdmc_xci.cpp new file mode 100644 index 0000000..ae7e530 --- /dev/null +++ b/source/install/sdmc_xci.cpp @@ -0,0 +1,75 @@ +#include "install/sdmc_xci.hpp" +#include "error.hpp" +#include "debug.h" +#include "nx/nca_writer.h" +#include "ui/instPage.hpp" +#include "util/lang.hpp" + +namespace tin::install::xci +{ + SDMCXCI::SDMCXCI(std::string path) + { + m_xciFile = fopen((path).c_str(), "rb"); + if (!m_xciFile) + THROW_FORMAT("can't open file at %s\n", path.c_str()); + } + + SDMCXCI::~SDMCXCI() + { + fclose(m_xciFile); + } + + void SDMCXCI::StreamToPlaceholder(std::shared_ptr& contentStorage, NcmContentId ncaId) + { + const HFS0FileEntry* fileEntry = this->GetFileEntryByNcaId(ncaId); + std::string ncaFileName = this->GetFileEntryName(fileEntry); + + LOG_DEBUG("Retrieving %s\n", ncaFileName.c_str()); + size_t ncaSize = fileEntry->fileSize; + + NcaWriter writer(ncaId, contentStorage); + + float progress; + + u64 fileStart = GetDataOffset() + fileEntry->dataOffset; + u64 fileOff = 0; + size_t readSize = 0x400000; // 4MB buff + auto readBuffer = std::make_unique(readSize); + + try + { + inst::ui::instPage::setInstInfoText("inst.info_page.top_info0"_lang + ncaFileName + "..."); + inst::ui::instPage::setInstBarPerc(0); + while (fileOff < ncaSize) + { + progress = (float) fileOff / (float) ncaSize; + + if (fileOff % (0x400000 * 3) == 0) { + LOG_DEBUG("> Progress: %lu/%lu MB (%d%s)\r", (fileOff / 1000000), (ncaSize / 1000000), (int)(progress * 100.0), "%"); + inst::ui::instPage::setInstBarPerc((double)(progress * 100.0)); + } + + if (fileOff + readSize >= ncaSize) readSize = ncaSize - fileOff; + + this->BufferData(readBuffer.get(), fileOff + fileStart, readSize); + writer.write(readBuffer.get(), readSize); + + fileOff += readSize; + } + inst::ui::instPage::setInstBarPerc(100); + } + catch (std::exception& e) + { + LOG_DEBUG("something went wrong: %s\n", e.what()); + } + + writer.close(); + } + + void SDMCXCI::BufferData(void* buf, off_t offset, size_t size) + { + fseeko(m_xciFile, offset, SEEK_SET); + //fseek(m_xciFile, offset, SEEK_SET); + fread(buf, 1, size, m_xciFile); + } +} \ No newline at end of file diff --git a/source/install/simple_filesystem.cpp b/source/install/simple_filesystem.cpp new file mode 100644 index 0000000..dce06df --- /dev/null +++ b/source/install/simple_filesystem.cpp @@ -0,0 +1,89 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/simple_filesystem.hpp" + +#include +#include +#include "nx/fs.hpp" +#include "util/error.hpp" + +namespace tin::install::nsp +{ + SimpleFileSystem::SimpleFileSystem(nx::fs::IFileSystem& fileSystem, std::string rootPath, std::string absoluteRootPath) : + m_fileSystem(&fileSystem) , m_rootPath(rootPath), m_absoluteRootPath(absoluteRootPath) + {} + + SimpleFileSystem::~SimpleFileSystem() {} + + nx::fs::IFile SimpleFileSystem::OpenFile(std::string path) + { + return m_fileSystem->OpenFile(m_rootPath + path); + } + + bool SimpleFileSystem::HasFile(std::string path) + { + try + { + LOG_DEBUG(("Attempting to find file at " + m_rootPath + path + "\n").c_str()); + m_fileSystem->OpenFile(m_rootPath + path); + return true; + } + catch (std::exception& e) {} + return false; + } + + std::string SimpleFileSystem::GetFileNameFromExtension(std::string path, std::string extension) + { + nx::fs::IDirectory dir = m_fileSystem->OpenDirectory(m_rootPath + path, FsDirOpenMode_ReadFiles | FsDirOpenMode_ReadDirs); + + u64 entryCount = dir.GetEntryCount(); + auto dirEntries = std::make_unique(entryCount); + + dir.Read(0, dirEntries.get(), entryCount); + + for (unsigned int i = 0; i < entryCount; i++) + { + FsDirectoryEntry dirEntry = dirEntries[i]; + std::string dirEntryName = dirEntry.name; + + if (dirEntry.type == FsDirEntryType_Dir) + { + auto subdirPath = path + dirEntryName + "/"; + auto subdirFound = this->GetFileNameFromExtension(subdirPath, extension); + + if (subdirFound != "") + return subdirFound; + continue; + } + else if (dirEntry.type == FsDirEntryType_File) + { + auto foundExtension = dirEntryName.substr(dirEntryName.find(".") + 1); + + if (foundExtension == extension) + return dirEntryName; + } + } + + return ""; + } +} \ No newline at end of file diff --git a/source/install/usb_nsp.cpp b/source/install/usb_nsp.cpp new file mode 100644 index 0000000..384b358 --- /dev/null +++ b/source/install/usb_nsp.cpp @@ -0,0 +1,193 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/usb_nsp.hpp" + +#include +#include +#include +#include +#include "data/byte_buffer.hpp" +#include "data/buffered_placeholder_writer.hpp" +#include "util/usb_util.hpp" +#include "util/error.hpp" +#include "util/debug.h" +#include "util/util.hpp" +#include "util/usb_comms_tinleaf.h" +#include "util/lang.hpp" +#include "ui/instPage.hpp" + + +namespace tin::install::nsp +{ + bool stopThreadsUsbNsp; + std::string errorMessageUsbNsp; + + USBNSP::USBNSP(std::string nspName) : + m_nspName(nspName) + { + + } + + struct USBFuncArgs + { + std::string nspName; + tin::data::BufferedPlaceholderWriter* bufferedPlaceholderWriter; + u64 pfs0Offset; + u64 ncaSize; + }; + + int USBThreadFunc(void* in) + { + USBFuncArgs* args = reinterpret_cast(in); + tin::util::USBCmdHeader header = tin::util::USBCmdManager::SendFileRangeCmd(args->nspName, args->pfs0Offset, args->ncaSize); + + u8* buf = (u8*)memalign(0x1000, 0x800000); + u64 sizeRemaining = header.dataSize; + size_t tmpSizeRead = 0; + + try + { + while (sizeRemaining && !stopThreadsUsbNsp) + { + tmpSizeRead = tinleaf_usbCommsRead(buf, std::min(sizeRemaining, (u64)0x800000), 5000000000); + if (tmpSizeRead == 0) THROW_FORMAT(("inst.usb.error"_lang).c_str()); + sizeRemaining -= tmpSizeRead; + + while (true) + { + if (args->bufferedPlaceholderWriter->CanAppendData(tmpSizeRead)) + break; + } + + args->bufferedPlaceholderWriter->AppendData(buf, tmpSizeRead); + } + } + catch (std::exception& e) + { + stopThreadsUsbNsp = true; + errorMessageUsbNsp = e.what(); + } + + free(buf); + + return 0; + } + + int USBPlaceholderWriteFunc(void* in) + { + USBFuncArgs* args = reinterpret_cast(in); + + while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete() && !stopThreadsUsbNsp) + { + if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder()) + args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder(); + } + + return 0; + } + + void USBNSP::StreamToPlaceholder(std::shared_ptr& contentStorage, NcmContentId placeholderId) + { + const PFS0FileEntry* fileEntry = this->GetFileEntryByNcaId(placeholderId); + std::string ncaFileName = this->GetFileEntryName(fileEntry); + + LOG_DEBUG("Retrieving %s\n", ncaFileName.c_str()); + size_t ncaSize = fileEntry->fileSize; + + tin::data::BufferedPlaceholderWriter bufferedPlaceholderWriter(contentStorage, placeholderId, ncaSize); + USBFuncArgs args; + args.nspName = m_nspName; + args.bufferedPlaceholderWriter = &bufferedPlaceholderWriter; + args.pfs0Offset = this->GetDataOffset() + fileEntry->dataOffset; + args.ncaSize = ncaSize; + thrd_t usbThread; + thrd_t writeThread; + + stopThreadsUsbNsp = false; + thrd_create(&usbThread, USBThreadFunc, &args); + thrd_create(&writeThread, USBPlaceholderWriteFunc, &args); + + u64 freq = armGetSystemTickFreq(); + u64 startTime = armGetSystemTick(); + size_t startSizeBuffered = 0; + double speed = 0.0; + + inst::ui::instPage::setInstBarPerc(0); + while (!bufferedPlaceholderWriter.IsBufferDataComplete() && !stopThreadsUsbNsp) + { + u64 newTime = armGetSystemTick(); + + if (newTime - startTime >= freq) + { + size_t newSizeBuffered = bufferedPlaceholderWriter.GetSizeBuffered(); + double mbBuffered = (newSizeBuffered / 1000000.0) - (startSizeBuffered / 1000000.0); + double duration = ((double)(newTime - startTime) / (double)freq); + speed = mbBuffered / duration; + + startTime = newTime; + startSizeBuffered = newSizeBuffered; + int downloadProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeBuffered() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0); + #ifdef NXLINK_DEBUG + u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000; + u64 downloadSizeMB = bufferedPlaceholderWriter.GetSizeBuffered() / 1000000; + LOG_DEBUG("> Download Progress: %lu/%lu MB (%i%s) (%.2f MB/s)\r", downloadSizeMB, totalSizeMB, downloadProgress, "%", speed); + #endif + + inst::ui::instPage::setInstInfoText("inst.info_page.downloading"_lang + inst::util::formatUrlString(ncaFileName) + "inst.info_page.at"_lang + std::to_string(speed).substr(0, std::to_string(speed).size()-4) + "MB/s"); + inst::ui::instPage::setInstBarPerc((double)downloadProgress); + } + } + inst::ui::instPage::setInstBarPerc(100); + + #ifdef NXLINK_DEBUG + u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000; + #endif + + inst::ui::instPage::setInstInfoText("inst.info_page.top_info0"_lang + ncaFileName + "..."); + inst::ui::instPage::setInstBarPerc(0); + while (!bufferedPlaceholderWriter.IsPlaceholderComplete() && !stopThreadsUsbNsp) + { + int installProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0); + #ifdef NXLINK_DEBUG + u64 installSizeMB = bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / 1000000; + LOG_DEBUG("> Install Progress: %lu/%lu MB (%i%s)\r", installSizeMB, totalSizeMB, installProgress, "%"); + #endif + inst::ui::instPage::setInstBarPerc((double)installProgress); + } + inst::ui::instPage::setInstBarPerc(100); + + thrd_join(usbThread, NULL); + thrd_join(writeThread, NULL); + if (stopThreadsUsbNsp) throw std::runtime_error(errorMessageUsbNsp.c_str()); + } + + void USBNSP::BufferData(void* buf, off_t offset, size_t size) + { + LOG_DEBUG("buffering 0x%lx-0x%lx\n", offset, offset + size); + tin::util::USBCmdHeader header = tin::util::USBCmdManager::SendFileRangeCmd(m_nspName, offset, size); + u8* tempBuffer = (u8*)memalign(0x1000, header.dataSize); + if (tin::util::USBRead(tempBuffer, header.dataSize) == 0) THROW_FORMAT(("inst.usb.error"_lang).c_str()); + memcpy(buf, tempBuffer, header.dataSize); + free(tempBuffer); + } +} \ No newline at end of file diff --git a/source/install/usb_xci.cpp b/source/install/usb_xci.cpp new file mode 100644 index 0000000..c1e0767 --- /dev/null +++ b/source/install/usb_xci.cpp @@ -0,0 +1,192 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/usb_xci.hpp" + +#include +#include +#include +#include +#include "data/byte_buffer.hpp" +#include "data/buffered_placeholder_writer.hpp" +#include "util/usb_util.hpp" +#include "util/error.hpp" +#include "util/debug.h" +#include "util/util.hpp" +#include "util/usb_comms_tinleaf.h" +#include "util/lang.hpp" +#include "ui/instPage.hpp" + +namespace tin::install::xci +{ + bool stopThreadsUsbXci; + std::string errorMessageUsbXci; + + USBXCI::USBXCI(std::string xciName) : + m_xciName(xciName) + { + + } + + struct USBFuncArgs + { + std::string xciName; + tin::data::BufferedPlaceholderWriter* bufferedPlaceholderWriter; + u64 hfs0Offset; + u64 ncaSize; + }; + + int USBThreadFunc(void* in) + { + USBFuncArgs* args = reinterpret_cast(in); + tin::util::USBCmdHeader header = tin::util::USBCmdManager::SendFileRangeCmd(args->xciName, args->hfs0Offset, args->ncaSize); + + u8* buf = (u8*)memalign(0x1000, 0x800000); + u64 sizeRemaining = header.dataSize; + size_t tmpSizeRead = 0; + + try + { + while (sizeRemaining && !stopThreadsUsbXci) + { + tmpSizeRead = tinleaf_usbCommsRead(buf, std::min(sizeRemaining, (u64)0x800000), 5000000000); + if (tmpSizeRead == 0) THROW_FORMAT(("inst.usb.error"_lang).c_str()); + sizeRemaining -= tmpSizeRead; + + while (true) + { + if (args->bufferedPlaceholderWriter->CanAppendData(tmpSizeRead)) + break; + } + + args->bufferedPlaceholderWriter->AppendData(buf, tmpSizeRead); + } + } + catch (std::exception& e) + { + stopThreadsUsbXci = true; + errorMessageUsbXci = e.what(); + } + + free(buf); + + return 0; + } + + int USBPlaceholderWriteFunc(void* in) + { + USBFuncArgs* args = reinterpret_cast(in); + + while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete() && !stopThreadsUsbXci) + { + if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder()) + args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder(); + } + + return 0; + } + + void USBXCI::StreamToPlaceholder(std::shared_ptr& contentStorage, NcmContentId placeholderId) + { + const HFS0FileEntry* fileEntry = this->GetFileEntryByNcaId(placeholderId); + std::string ncaFileName = this->GetFileEntryName(fileEntry); + + LOG_DEBUG("Retrieving %s\n", ncaFileName.c_str()); + size_t ncaSize = fileEntry->fileSize; + + tin::data::BufferedPlaceholderWriter bufferedPlaceholderWriter(contentStorage, placeholderId, ncaSize); + USBFuncArgs args; + args.xciName = m_xciName; + args.bufferedPlaceholderWriter = &bufferedPlaceholderWriter; + args.hfs0Offset = this->GetDataOffset() + fileEntry->dataOffset; + args.ncaSize = ncaSize; + thrd_t usbThread; + thrd_t writeThread; + + stopThreadsUsbXci = false; + thrd_create(&usbThread, USBThreadFunc, &args); + thrd_create(&writeThread, USBPlaceholderWriteFunc, &args); + + u64 freq = armGetSystemTickFreq(); + u64 startTime = armGetSystemTick(); + size_t startSizeBuffered = 0; + double speed = 0.0; + + inst::ui::instPage::setInstBarPerc(0); + while (!bufferedPlaceholderWriter.IsBufferDataComplete() && !stopThreadsUsbXci) + { + u64 newTime = armGetSystemTick(); + + if (newTime - startTime >= freq) + { + size_t newSizeBuffered = bufferedPlaceholderWriter.GetSizeBuffered(); + double mbBuffered = (newSizeBuffered / 1000000.0) - (startSizeBuffered / 1000000.0); + double duration = ((double)(newTime - startTime) / (double)freq); + speed = mbBuffered / duration; + + startTime = newTime; + startSizeBuffered = newSizeBuffered; + int downloadProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeBuffered() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0); + #ifdef NXLINK_DEBUG + u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000; + u64 downloadSizeMB = bufferedPlaceholderWriter.GetSizeBuffered() / 1000000; + LOG_DEBUG("> Download Progress: %lu/%lu MB (%i%s) (%.2f MB/s)\r", downloadSizeMB, totalSizeMB, downloadProgress, "%", speed); + #endif + + inst::ui::instPage::setInstInfoText("inst.info_page.downloading"_lang + inst::util::formatUrlString(ncaFileName) + "inst.info_page.at"_lang + std::to_string(speed).substr(0, std::to_string(speed).size()-4) + "MB/s"); + inst::ui::instPage::setInstBarPerc((double)downloadProgress); + } + } + inst::ui::instPage::setInstBarPerc(100); + + #ifdef NXLINK_DEBUG + u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000; + #endif + + inst::ui::instPage::setInstInfoText("inst.info_page.top_info0"_lang + ncaFileName + "..."); + inst::ui::instPage::setInstBarPerc(0); + while (!bufferedPlaceholderWriter.IsPlaceholderComplete() && !stopThreadsUsbXci) + { + int installProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0); + #ifdef NXLINK_DEBUG + u64 installSizeMB = bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / 1000000; + LOG_DEBUG("> Install Progress: %lu/%lu MB (%i%s)\r", installSizeMB, totalSizeMB, installProgress, "%"); + #endif + inst::ui::instPage::setInstBarPerc((double)installProgress); + } + inst::ui::instPage::setInstBarPerc(100); + + thrd_join(usbThread, NULL); + thrd_join(writeThread, NULL); + if (stopThreadsUsbXci) throw std::runtime_error(errorMessageUsbXci.c_str()); + } + + void USBXCI::BufferData(void* buf, off_t offset, size_t size) + { + LOG_DEBUG("buffering 0x%lx-0x%lx\n", offset, offset + size); + tin::util::USBCmdHeader header = tin::util::USBCmdManager::SendFileRangeCmd(m_xciName, offset, size); + u8* tempBuffer = (u8*)memalign(0x1000, header.dataSize); + if (tin::util::USBRead(tempBuffer, header.dataSize) == 0) THROW_FORMAT(("inst.usb.error"_lang).c_str()); + memcpy(buf, tempBuffer, header.dataSize); + free(tempBuffer); + } +} \ No newline at end of file diff --git a/source/install/xci.cpp b/source/install/xci.cpp new file mode 100644 index 0000000..e95f8f6 --- /dev/null +++ b/source/install/xci.cpp @@ -0,0 +1,174 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "install/xci.hpp" +#include "util/title_util.hpp" +#include "error.hpp" +#include "debug.h" + +namespace tin::install::xci +{ + XCI::XCI() + { + } + + void XCI::RetrieveHeader() + { + LOG_DEBUG("Retrieving HFS0 header...\n"); + + // Retrieve hfs0 offset + u64 hfs0Offset = 0xf000; + + // Retrieve main hfs0 header + std::vector m_headerBytes; + m_headerBytes.resize(sizeof(HFS0BaseHeader), 0); + this->BufferData(m_headerBytes.data(), hfs0Offset, sizeof(HFS0BaseHeader)); + + LOG_DEBUG("Base header: \n"); + printBytes(m_headerBytes.data(), sizeof(HFS0BaseHeader), true); + + // Retrieve full header + HFS0BaseHeader *header = reinterpret_cast(m_headerBytes.data()); + if (header->magic != MAGIC_HFS0) + THROW_FORMAT("hfs0 magic doesn't match at 0x%lx\n", hfs0Offset); + + size_t remainingHeaderSize = header->numFiles * sizeof(HFS0FileEntry) + header->stringTableSize; + m_headerBytes.resize(sizeof(HFS0BaseHeader) + remainingHeaderSize, 0); + this->BufferData(m_headerBytes.data() + sizeof(HFS0BaseHeader), hfs0Offset + sizeof(HFS0BaseHeader), remainingHeaderSize); + + LOG_DEBUG("Base header: \n"); + printBytes(m_headerBytes.data(), sizeof(HFS0BaseHeader) + remainingHeaderSize, true); + + // Find Secure partition + header = reinterpret_cast(m_headerBytes.data()); + for (unsigned int i = 0; i < header->numFiles; i++) + { + const HFS0FileEntry *entry = hfs0GetFileEntry(header, i); + std::string entryName(hfs0GetFileName(header, entry)); + + if (entryName != "secure") + continue; + + m_secureHeaderOffset = hfs0Offset + remainingHeaderSize + 0x10 + entry->dataOffset; + m_secureHeaderBytes.resize(sizeof(HFS0BaseHeader), 0); + this->BufferData(m_secureHeaderBytes.data(), m_secureHeaderOffset, sizeof(HFS0BaseHeader)); + + LOG_DEBUG("Secure header: \n"); + printBytes(m_secureHeaderBytes.data(), sizeof(HFS0BaseHeader), true); + + if (this->GetSecureHeader()->magic != MAGIC_HFS0) + THROW_FORMAT("hfs0 magic doesn't match at 0x%lx\n", m_secureHeaderOffset); + + // Retrieve full header + remainingHeaderSize = this->GetSecureHeader()->numFiles * sizeof(HFS0FileEntry) + this->GetSecureHeader()->stringTableSize; + m_secureHeaderBytes.resize(sizeof(HFS0BaseHeader) + remainingHeaderSize, 0); + this->BufferData(m_secureHeaderBytes.data() + sizeof(HFS0BaseHeader), m_secureHeaderOffset + sizeof(HFS0BaseHeader), remainingHeaderSize); + + LOG_DEBUG("Base header: \n"); + printBytes(m_secureHeaderBytes.data(), sizeof(HFS0BaseHeader) + remainingHeaderSize, true); + return; + } + THROW_FORMAT("couldn't optain secure hfs0 header\n"); + } + + const HFS0BaseHeader* XCI::GetSecureHeader() + { + if (m_secureHeaderBytes.empty()) + THROW_FORMAT("Cannot retrieve header as header bytes are empty. Have you retrieved it yet?\n"); + + return reinterpret_cast(m_secureHeaderBytes.data()); + } + + u64 XCI::GetDataOffset() + { + if (m_secureHeaderBytes.empty()) + THROW_FORMAT("Cannot get data offset as header is empty. Have you retrieved it yet?\n"); + + return m_secureHeaderOffset + m_secureHeaderBytes.size(); + } + + const HFS0FileEntry* XCI::GetFileEntry(unsigned int index) + { + if (index >= this->GetSecureHeader()->numFiles) + THROW_FORMAT("File entry index is out of bounds\n") + + return hfs0GetFileEntry(this->GetSecureHeader(), index); + } + + const HFS0FileEntry* XCI::GetFileEntryByName(std::string name) + { + for (unsigned int i = 0; i < this->GetSecureHeader()->numFiles; i++) + { + const HFS0FileEntry* fileEntry = this->GetFileEntry(i); + std::string foundName(this->GetFileEntryName(fileEntry)); + + if (foundName == name) + return fileEntry; + } + + return nullptr; + } + + const HFS0FileEntry* XCI::GetFileEntryByNcaId(const NcmContentId& ncaId) + { + const HFS0FileEntry* fileEntry = nullptr; + std::string ncaIdStr = tin::util::GetNcaIdString(ncaId); + + if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".nca")) == nullptr) + { + if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".cnmt.nca")) == nullptr) + { + if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".ncz")) == nullptr) + { + if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".cnmt.ncz")) == nullptr) + { + return nullptr; + } + } + } + } + + return fileEntry; + } + + std::vector XCI::GetFileEntriesByExtension(std::string extension) + { + std::vector entryList; + + for (unsigned int i = 0; i < this->GetSecureHeader()->numFiles; i++) + { + const HFS0FileEntry* fileEntry = this->GetFileEntry(i); + std::string name(this->GetFileEntryName(fileEntry)); + auto foundExtension = name.substr(name.find(".") + 1); + + if (foundExtension == extension) + entryList.push_back(fileEntry); + } + + return entryList; + } + + const char* XCI::GetFileEntryName(const HFS0FileEntry* fileEntry) + { + return hfs0GetFileName(this->GetSecureHeader(), fileEntry); + } +} \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..91f31e6 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,34 @@ +#include +#include "switch.h" +#include "util/error.hpp" +#include "ui/MainApplication.hpp" +#include "util/util.hpp" +#include "util/config.hpp" + +using namespace pu::ui::render; +int main(int argc, char* argv[]) +{ + inst::util::initApp(); + try { + //const auto default_font_path = ("romfs:/fonts/Roboto-Regular.ttf"); + auto renderer_opts = pu::ui::render::RendererInitOptions(SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags); + renderer_opts.UseImage(pu::ui::render::IMGAllFlags); + renderer_opts.UseAudio(pu::ui::render::MixerAllFlags); + //renderer_opts.UseTTF(default_font_path); + renderer_opts.UseTTF(); + //renderer_opts.SetExtraDefaultFontSize(35); + renderer_opts.UseRomfs(); + auto renderer = pu::ui::render::Renderer::New(renderer_opts); + + auto main = inst::ui::MainApplication::New(renderer); + std::thread updateThread; + if (inst::config::autoUpdate && inst::util::getIPAddress() != "1.0.0.127") updateThread = std::thread(inst::util::checkForAppUpdate); + main->Prepare(); + main->ShowWithFadeIn(); + updateThread.join(); + } catch (std::exception& e) { + LOG_DEBUG("An error occurred:\n%s", e.what()); + } + inst::util::deinitApp(); + return 0; + } diff --git a/source/netInstall.cpp b/source/netInstall.cpp new file mode 100644 index 0000000..46c48ac --- /dev/null +++ b/source/netInstall.cpp @@ -0,0 +1,336 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "netInstall.hpp" +#include "install/install_nsp.hpp" +#include "install/http_nsp.hpp" +#include "install/install_xci.hpp" +#include "install/http_xci.hpp" +#include "install/install.hpp" +#include "util/error.hpp" +#include "util/network_util.hpp" +#include "util/config.hpp" +#include "util/util.hpp" +#include "util/curl.hpp" +#include "util/lang.hpp" +#include "ui/MainApplication.hpp" +#include "ui/instPage.hpp" + +const unsigned int MAX_URL_SIZE = 1024; +const unsigned int MAX_URLS = 256; +const int REMOTE_INSTALL_PORT = 2000; +static int m_serverSocket = 0; +static int m_clientSocket = 0; + +namespace inst::ui { + extern MainApplication *mainApp; +} + +namespace netInstStuff{ + + void InitializeServerSocket() try + { + // Create a socket + m_serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + + if (m_serverSocket < -1) + { + THROW_FORMAT("Failed to create a server socket. Error code: %u\n", errno); + } + + struct sockaddr_in server; + server.sin_family = AF_INET; + server.sin_port = htons(REMOTE_INSTALL_PORT); + server.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(m_serverSocket, (struct sockaddr*) &server, sizeof(server)) < 0) + { + THROW_FORMAT("Failed to bind server socket. Error code: %u\n", errno); + } + + // Set as non-blocking + fcntl(m_serverSocket, F_SETFL, fcntl(m_serverSocket, F_GETFL, 0) | O_NONBLOCK); + + if (listen(m_serverSocket, 5) < 0) + { + THROW_FORMAT("Failed to listen on server socket. Error code: %u\n", errno); + } + } + catch (std::exception& e) + { + LOG_DEBUG("Failed to initialize server socket!\n"); + fprintf(stdout, "%s", e.what()); + + if (m_serverSocket != 0) + { + close(m_serverSocket); + m_serverSocket = 0; + } + inst::ui::mainApp->CreateShowDialog("Failed to initialize server socket!", (std::string)e.what(), {"OK"}, true); + } + + void OnUnwound() + { + LOG_DEBUG("unwinding view\n"); + if (m_clientSocket != 0) + { + close(m_clientSocket); + m_clientSocket = 0; + } + + curl_global_cleanup(); + } + + void installTitleNet(std::vector ourUrlList, int ourStorage, std::vector urlListAltNames, std::string ourSource) + { + inst::util::initInstallServices(); + inst::ui::instPage::loadInstallScreen(); + bool nspInstalled = true; + NcmStorageId m_destStorageId = NcmStorageId_SdCard; + + if (ourStorage) m_destStorageId = NcmStorageId_BuiltInUser; + unsigned int urlItr; + + std::vector urlNames; + if (urlListAltNames.size() > 0) { + for (long unsigned int i = 0; i < urlListAltNames.size(); i++) { + urlNames.push_back(inst::util::shortenString(urlListAltNames[i], 38, true)); + } + } else { + for (long unsigned int i = 0; i < ourUrlList.size(); i++) { + urlNames.push_back(inst::util::shortenString(inst::util::formatUrlString(ourUrlList[i]), 38, true)); + } + } + + std::vector previousClockValues; + if (inst::config::overClock) { + previousClockValues.push_back(inst::util::setClockSpeed(0, 1785000000)[0]); + previousClockValues.push_back(inst::util::setClockSpeed(1, 76800000)[0]); + previousClockValues.push_back(inst::util::setClockSpeed(2, 1600000000)[0]); + } + + try { + for (urlItr = 0; urlItr < ourUrlList.size(); urlItr++) { + LOG_DEBUG("%s %s\n", "Install request from", ourUrlList[urlItr].c_str()); + inst::ui::instPage::setTopInstInfoText("inst.info_page.top_info0"_lang + urlNames[urlItr] + ourSource); + std::unique_ptr installTask; + + if (inst::curl::downloadToBuffer(ourUrlList[urlItr], 0x100, 0x103) == "HEAD") { + auto httpXCI = std::make_shared(ourUrlList[urlItr]); + installTask = std::make_unique(m_destStorageId, inst::config::ignoreReqVers, httpXCI); + } else { + auto httpNSP = std::make_shared(ourUrlList[urlItr]); + installTask = std::make_unique(m_destStorageId, inst::config::ignoreReqVers, httpNSP); + } + + LOG_DEBUG("%s\n", "Preparing installation"); + inst::ui::instPage::setInstInfoText("inst.info_page.preparing"_lang); + inst::ui::instPage::setInstBarPerc(0); + installTask->Prepare(); + installTask->Begin(); + } + } + catch (std::exception& e) { + LOG_DEBUG("Failed to install"); + LOG_DEBUG("%s", e.what()); + fprintf(stdout, "%s", e.what()); + inst::ui::instPage::setInstInfoText("inst.info_page.failed"_lang + urlNames[urlItr]); + inst::ui::instPage::setInstBarPerc(0); + std::string audioPath = ""; + if (std::filesystem::exists(inst::config::appDir + "/sounds/OHNO.WAV")) { + audioPath = (inst::config::appDir + "/sounds/OHNO.WAV"); + } + else { + audioPath = "romfs:/audio/bark.wav"; + } + std::thread audioThread(inst::util::playAudio,audioPath); + inst::ui::mainApp->CreateShowDialog("inst.info_page.failed"_lang + urlNames[urlItr] + "!", "inst.info_page.failed_desc"_lang + "\n\n" + (std::string)e.what(), {"common.ok"_lang}, true); + audioThread.join(); + nspInstalled = false; + } + + if (previousClockValues.size() > 0) { + inst::util::setClockSpeed(0, previousClockValues[0]); + inst::util::setClockSpeed(1, previousClockValues[1]); + inst::util::setClockSpeed(2, previousClockValues[2]); + } + + LOG_DEBUG("Telling the server we're done installing\n"); + // Send 1 byte ack to close the server + u8 ack = 0; + tin::network::WaitSendNetworkData(m_clientSocket, &ack, sizeof(u8)); + + if(nspInstalled) { + inst::ui::instPage::setInstInfoText("inst.info_page.complete"_lang); + inst::ui::instPage::setInstBarPerc(100); + std::string audioPath = ""; + + if (inst::config::useSound) { + if (std::filesystem::exists(inst::config::appDir + "/sounds/YIPPEE.WAV")) { + audioPath = (inst::config::appDir + "/sounds/YIPPEE.WAV"); + } + else { + audioPath = "romfs:/audio/ameizing.mp3"; + } + std::thread audioThread(inst::util::playAudio,audioPath); + + if (ourUrlList.size() > 1) inst::ui::mainApp->CreateShowDialog(std::to_string(ourUrlList.size()) + "inst.info_page.desc0"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + else inst::ui::mainApp->CreateShowDialog(urlNames[0] + "inst.info_page.desc1"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + audioThread.join(); + } + + else{ + if (ourUrlList.size() > 1) inst::ui::mainApp->CreateShowDialog(std::to_string(ourUrlList.size()) + "inst.info_page.desc0"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + else inst::ui::mainApp->CreateShowDialog(urlNames[0] + "inst.info_page.desc1"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } + } + + LOG_DEBUG("Done"); + inst::ui::instPage::loadMainMenu(); + inst::util::deinitInstallServices(); + return; + } + + std::vector OnSelected() + { + u64 freq = armGetSystemTickFreq(); + u64 startTime = armGetSystemTick(); + + OnUnwound(); + + try + { + ASSERT_OK(curl_global_init(CURL_GLOBAL_ALL), "Curl failed to initialized"); + + // Initialize the server socket if it hasn't already been + if (m_serverSocket == 0) + { + InitializeServerSocket(); + + if (m_serverSocket <= 0) + { + THROW_FORMAT("Server socket failed to initialize.\n"); + } + } + + std::string ourIPAddress = inst::util::getIPAddress(); + inst::ui::mainApp->netinstPage->pageInfoText->SetText("inst.net.top_info1"_lang + ourIPAddress); + inst::ui::mainApp->CallForRender(); + 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) + { + // If we don't update the UI occasionally the Switch basically crashes on this screen if you press the home button + u64 newTime = armGetSystemTick(); + if (newTime - startTime >= freq * 0.25) { + startTime = newTime; + inst::ui::mainApp->CallForRender(); + } + + // Break on input pressed + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + PadState pad; + padInitializeDefault(&pad); + hidInitializeTouchScreen(); + + u64 kDown = padGetButtonsDown(&pad); + + + if (kDown & HidNpadButton_B) + { + break; + } + if (kDown & HidNpadButton_Y) + { + return {"supplyUrl"}; + } + if (kDown & HidNpadButton_X) + { + inst::ui::mainApp->CreateShowDialog("inst.net.help.title"_lang, "inst.net.help.desc"_lang, {"common.ok"_lang}, true); + } + + struct sockaddr_in client; + socklen_t clientLen = sizeof(client); + + m_clientSocket = accept(m_serverSocket, (struct sockaddr*)&client, &clientLen); + + if (m_clientSocket >= 0) + { + LOG_DEBUG("%s\n", "Server accepted"); + u32 size = 0; + tin::network::WaitReceiveNetworkData(m_clientSocket, &size, sizeof(u32)); + size = ntohl(size); + + LOG_DEBUG("Received url buf size: 0x%x\n", size); + + if (size > MAX_URL_SIZE * MAX_URLS) + { + THROW_FORMAT("URL size %x is too large!\n", size); + } + + // Make sure the last string is null terminated + auto urlBuf = std::make_unique(size+1); + memset(urlBuf.get(), 0, size+1); + + tin::network::WaitReceiveNetworkData(m_clientSocket, urlBuf.get(), size); + + // Split the string up into individual URLs + std::stringstream urlStream(urlBuf.get()); + std::string segment; + + while (std::getline(urlStream, segment, '\n')) urls.push_back(segment); + std::sort(urls.begin(), urls.end(), inst::util::ignoreCaseCompare); + + break; + } + else if (errno != EAGAIN) + { + THROW_FORMAT("Failed to open client socket with code %u\n", errno); + } + } + + return urls; + + } + catch (std::runtime_error& e) + { + LOG_DEBUG("Failed to perform remote install!\n"); + LOG_DEBUG("%s", e.what()); + fprintf(stdout, "%s", e.what()); + inst::ui::mainApp->CreateShowDialog("inst.net.failed"_lang, (std::string)e.what(), {"common.ok"_lang}, true); + return {}; + } + } +} diff --git a/source/nx/content_meta.cpp b/source/nx/content_meta.cpp new file mode 100644 index 0000000..37deef6 --- /dev/null +++ b/source/nx/content_meta.cpp @@ -0,0 +1,131 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "nx/content_meta.hpp" + +#include +#include "util/title_util.hpp" +#include "util/debug.h" +#include "util/error.hpp" + +namespace nx::ncm +{ + ContentMeta::ContentMeta() + { + m_bytes.Resize(sizeof(PackagedContentMetaHeader)); + } + + ContentMeta::ContentMeta(u8* data, size_t size) : + m_bytes(size) + { + if (size < sizeof(PackagedContentMetaHeader)) + THROW_FORMAT("Content meta data size is too small!"); + + m_bytes.Resize(size); + memcpy(m_bytes.GetData(), data, size); + } + + PackagedContentMetaHeader ContentMeta::GetPackagedContentMetaHeader() + { + return m_bytes.Read(0); + } + + NcmContentMetaKey ContentMeta::GetContentMetaKey() + { + NcmContentMetaKey metaRecord; + PackagedContentMetaHeader contentMetaHeader = this->GetPackagedContentMetaHeader(); + + memset(&metaRecord, 0, sizeof(NcmContentMetaKey)); + metaRecord.id = contentMetaHeader.title_id; + metaRecord.version = contentMetaHeader.version; + metaRecord.type = static_cast(contentMetaHeader.type); + + return metaRecord; + } + + // TODO: Cache this + std::vector ContentMeta::GetContentInfos() + { + PackagedContentMetaHeader contentMetaHeader = this->GetPackagedContentMetaHeader(); + + std::vector contentInfos; + PackagedContentInfo* packagedContentInfos = (PackagedContentInfo*)(m_bytes.GetData() + sizeof(PackagedContentMetaHeader) + contentMetaHeader.extended_header_size); + + for (unsigned int i = 0; i < contentMetaHeader.content_count; i++) + { + PackagedContentInfo packagedContentInfo = packagedContentInfos[i]; + + // Don't install delta fragments. Even patches don't seem to install them. + if (static_cast(packagedContentInfo.content_info.content_type) <= 5) + { + contentInfos.push_back(packagedContentInfo.content_info); + } + } + + return contentInfos; + } + + void ContentMeta::GetInstallContentMeta(tin::data::ByteBuffer& installContentMetaBuffer, NcmContentInfo& cnmtNcmContentInfo, bool ignoreReqFirmVersion) + { + PackagedContentMetaHeader packagedContentMetaHeader = this->GetPackagedContentMetaHeader(); + std::vector contentInfos = this->GetContentInfos(); + + // Setup the content meta header + NcmContentMetaHeader contentMetaHeader; + contentMetaHeader.extended_header_size = packagedContentMetaHeader.extended_header_size; + contentMetaHeader.content_count = contentInfos.size() + 1; // Add one for the cnmt content record + contentMetaHeader.content_meta_count = packagedContentMetaHeader.content_meta_count; + contentMetaHeader.attributes = packagedContentMetaHeader.attributes; + + installContentMetaBuffer.Append(contentMetaHeader); + + // Setup the meta extended header + LOG_DEBUG("Install content meta pre size: 0x%lx\n", installContentMetaBuffer.GetSize()); + installContentMetaBuffer.Resize(installContentMetaBuffer.GetSize() + contentMetaHeader.extended_header_size); + LOG_DEBUG("Install content meta post size: 0x%lx\n", installContentMetaBuffer.GetSize()); + auto* extendedHeaderSourceBytes = m_bytes.GetData() + sizeof(PackagedContentMetaHeader); + u8* installExtendedHeaderStart = installContentMetaBuffer.GetData() + sizeof(NcmContentMetaHeader); + memcpy(installExtendedHeaderStart, extendedHeaderSourceBytes, contentMetaHeader.extended_header_size); + + // Optionally disable the required system version field + if (ignoreReqFirmVersion && (packagedContentMetaHeader.type == NcmContentMetaType_Application || packagedContentMetaHeader.type == NcmContentMetaType_Patch)) + { + installContentMetaBuffer.Write(0, sizeof(NcmContentMetaHeader) + 8); + } + + // Setup cnmt content record + installContentMetaBuffer.Append(cnmtNcmContentInfo); + + // Setup the content records + for (auto& contentInfo : contentInfos) + { + installContentMetaBuffer.Append(contentInfo); + } + + if (packagedContentMetaHeader.type == NcmContentMetaType_Patch) + { + NcmPatchMetaExtendedHeader* patchMetaExtendedHeader = (NcmPatchMetaExtendedHeader*)extendedHeaderSourceBytes; + installContentMetaBuffer.Resize(installContentMetaBuffer.GetSize() + patchMetaExtendedHeader->extended_data_size); + } + } +} + diff --git a/source/nx/fs.cpp b/source/nx/fs.cpp new file mode 100644 index 0000000..64f352d --- /dev/null +++ b/source/nx/fs.cpp @@ -0,0 +1,156 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "nx/fs.hpp" + +#include +#include "util/error.hpp" + +namespace nx::fs +{ + // IFile + + IFile::IFile(FsFile& file) + { + m_file = file; + } + + IFile::~IFile() + { + fsFileClose(&m_file); + } + + void IFile::Read(u64 offset, void* buf, size_t size) + { + u64 sizeRead; + ASSERT_OK(fsFileRead(&m_file, offset, buf, size, FsReadOption_None, &sizeRead), "Failed to read file"); + + if (sizeRead != size) + { + std::string msg = "Size read " + std::string("" + sizeRead) + " doesn't match expected size " + std::string("" + size); + THROW_FORMAT(msg.c_str()); + } + } + + s64 IFile::GetSize() + { + s64 sizeOut; + ASSERT_OK(fsFileGetSize(&m_file, &sizeOut), "Failed to get file size"); + return sizeOut; + } + + // End IFile + + // IDirectory + IDirectory::IDirectory(FsDir& dir) + { + m_dir = dir; + } + + IDirectory::~IDirectory() + { + fsDirClose(&m_dir); + } + + void IDirectory::Read(s64 inval, FsDirectoryEntry* buf, size_t numEntries) + { + ASSERT_OK(fsDirRead(&m_dir, &inval, numEntries, buf), "Failed to read directory"); + + /*if (entriesRead != numEntries) + { + std::string msg = "Entries read " + std::string("" + entriesRead) + " doesn't match expected number " + std::string("" + numEntries); + THROW_FORMAT(msg); + }*/ + } + + u64 IDirectory::GetEntryCount() + { + s64 entryCount = 0; + ASSERT_OK(fsDirGetEntryCount(&m_dir, &entryCount), "Failed to get entry count"); + return entryCount; + } + + // End IDirectory + + IFileSystem::IFileSystem() {} + + IFileSystem::~IFileSystem() + { + this->CloseFileSystem(); + } + + Result IFileSystem::OpenSdFileSystem() + { + ASSERT_OK(fsOpenSdCardFileSystem(&m_fileSystem), "Failed to mount sd card"); + return 0; + } + + void IFileSystem::OpenFileSystemWithId(std::string path, FsFileSystemType fileSystemType, u64 titleId) + { + Result rc = 0; + if (path.length() >= FS_MAX_PATH) + THROW_FORMAT("Directory path is too long!"); + + // libnx expects a FS_MAX_PATH-sized buffer + path.reserve(FS_MAX_PATH); + + std::string errorMsg = "Failed to open file system with id: " + path; + rc = fsOpenFileSystemWithId(&m_fileSystem, titleId, fileSystemType, path.c_str()); + + if (rc == 0x236e02) + errorMsg = "File " + path + " is unreadable! You may have a bad dump, fs_mitm may need to be removed, or your firmware version may be too low to decrypt it."; + + ASSERT_OK(rc, errorMsg.c_str()); + } + + void IFileSystem::CloseFileSystem() + { + fsFsClose(&m_fileSystem); + } + + IFile IFileSystem::OpenFile(std::string path) + { + if (path.length() >= FS_MAX_PATH) + THROW_FORMAT("Directory path is too long!"); + + // libnx expects a FS_MAX_PATH-sized buffer + path.reserve(FS_MAX_PATH); + + FsFile file; + ASSERT_OK(fsFsOpenFile(&m_fileSystem, path.c_str(), FsOpenMode_Read, &file), ("Failed to open file " + path).c_str()); + return IFile(file); + } + + IDirectory IFileSystem::OpenDirectory(std::string path, int flags) + { + // Account for null at the end of c strings + if (path.length() >= FS_MAX_PATH) + THROW_FORMAT("Directory path is too long!"); + + // libnx expects a FS_MAX_PATH-sized buffer + path.reserve(FS_MAX_PATH); + + FsDir dir; + ASSERT_OK(fsFsOpenDirectory(&m_fileSystem, path.c_str(), flags, &dir), ("Failed to open directory " + path).c_str()); + return IDirectory(dir); + } +} diff --git a/source/nx/ipc/es.c b/source/nx/ipc/es.c new file mode 100644 index 0000000..b94440f --- /dev/null +++ b/source/nx/ipc/es.c @@ -0,0 +1,145 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "nx/ipc/es.h" + +#include + +#include +#include "service_guard.h" + +static Service g_esSrv; + +NX_GENERATE_SERVICE_GUARD(es); + +Result _esInitialize() { + return smGetService(&g_esSrv, "es"); +} + +void _esCleanup() { + serviceClose(&g_esSrv); +} + +Service* esGetServiceSession() { + return &g_esSrv; +} + +Result esImportTicket(void const *tikBuf, size_t tikSize, void const *certBuf, size_t certSize) { + return serviceDispatch(&g_esSrv, 1, + .buffer_attrs = { + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + }, + .buffers = { + { tikBuf, tikSize }, + { certBuf, certSize }, + }, + ); +} + +Result esDeleteTicket(const RightsId *rightsIdBuf, size_t bufSize) { + return serviceDispatch(&g_esSrv, 3, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { rightsIdBuf, bufSize }, }, + ); +} + +Result esGetTitleKey(const RightsId *rightsId, u8 *outBuf, size_t bufSize) { + struct { + RightsId rights_Id; + u32 key_generation; + } in; + memcpy(&in.rights_Id, rightsId, sizeof(RightsId)); + in.key_generation = 0; + + return serviceDispatchIn(&g_esSrv, 8, in, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { outBuf, bufSize } }, + ); +} + +Result esCountCommonTicket(u32 *numTickets) { + struct { + u32 num_tickets; + } out; + + Result rc = serviceDispatchOut(&g_esSrv, 9, out); + if (R_SUCCEEDED(rc) && numTickets) *numTickets = out.num_tickets; + + return rc; +} + +Result esCountPersonalizedTicket(u32 *numTickets) { + struct { + u32 num_tickets; + } out; + + Result rc = serviceDispatchOut(&g_esSrv, 10, out); + if (R_SUCCEEDED(rc) && numTickets) *numTickets = out.num_tickets; + + return rc; +} + +Result esListCommonTicket(u32 *numRightsIdsWritten, RightsId *outBuf, size_t bufSize) { + struct { + u32 num_rights_ids_written; + } out; + + Result rc = serviceDispatchInOut(&g_esSrv, 11, *numRightsIdsWritten, out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { outBuf, bufSize } }, + ); + if (R_SUCCEEDED(rc) && numRightsIdsWritten) *numRightsIdsWritten = out.num_rights_ids_written; + + return rc; +} + +Result esListPersonalizedTicket(u32 *numRightsIdsWritten, RightsId *outBuf, size_t bufSize) { + struct { + u32 num_rights_ids_written; + } out; + + Result rc = serviceDispatchInOut(&g_esSrv, 12, *numRightsIdsWritten, out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { outBuf, bufSize } }, + ); + if (R_SUCCEEDED(rc) && numRightsIdsWritten) *numRightsIdsWritten = out.num_rights_ids_written; + + return rc; +} + +Result esGetCommonTicketData(u64 *unkOut, void *outBuf1, size_t bufSize1, const RightsId* rightsId) { + struct { + RightsId rights_id; + } in; + memcpy(&in.rights_id, rightsId, sizeof(RightsId)); + + struct { + u64 unk; + } out; + + Result rc = serviceDispatchInOut(&g_esSrv, 16, in, out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { outBuf1, bufSize1 } }, + ); + return rc; +} \ No newline at end of file diff --git a/source/nx/ipc/ns_ext.c b/source/nx/ipc/ns_ext.c new file mode 100644 index 0000000..f62213e --- /dev/null +++ b/source/nx/ipc/ns_ext.c @@ -0,0 +1,197 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "nx/ipc/ns_ext.h" + +#include +#include +#include +#include "service_guard.h" + +static Service g_nsAppManSrv, g_nsGetterSrv; + +static Result _nsextGetSession(Service* srv, Service* srv_out, u32 cmd_id); + +NX_GENERATE_SERVICE_GUARD(nsext); + +Result _nsextInitialize(void) { + Result rc=0; + + if(hosversionBefore(3,0,0)) + return smGetService(&g_nsAppManSrv, "ns:am"); + + rc = smGetService(&g_nsGetterSrv, "ns:am2");//TODO: Support the other services?(Only useful when ns:am2 isn't accessible) + if (R_FAILED(rc)) return rc; + + rc = _nsextGetSession(&g_nsGetterSrv, &g_nsAppManSrv, 7996); + + if (R_FAILED(rc)) serviceClose(&g_nsGetterSrv); + + return rc; +} + +void _nsextCleanup(void) { + serviceClose(&g_nsAppManSrv); + if(hosversionBefore(3,0,0)) return; + + serviceClose(&g_nsGetterSrv); +} + +static Result _nsextGetSession(Service* srv, Service* srv_out, u32 cmd_id) { + return serviceDispatch(srv, cmd_id, + .out_num_objects = 1, + .out_objects = srv_out, + ); +} + +Result nsPushApplicationRecord(u64 title_id, u8 last_modified_event, ContentStorageRecord *content_records_buf, size_t buf_size) { + + struct { + u8 last_modified_event; + u8 padding[0x7]; + u64 title_id; + } in = { last_modified_event, {0}, title_id }; + + return serviceDispatchIn(&g_nsAppManSrv, 16, in, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { content_records_buf, buf_size } }); +} + +Result nsListApplicationRecordContentMeta(u64 offset, u64 titleID, void *out_buf, size_t out_buf_size, u32 *entries_read_out) { + + struct { + u64 offset; + u64 titleID; + } in = { offset, titleID }; + + struct { + u32 entries_read; + } out; + + Result rc = serviceDispatchInOut(&g_nsAppManSrv, 17, in, out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { out_buf, out_buf_size } }); + + if (R_SUCCEEDED(rc) && entries_read_out) *entries_read_out = out.entries_read; + + return rc; +} + +Result nsDeleteApplicationRecord(u64 titleID) { + struct { + u64 titleID; + } in = { titleID }; + + return serviceDispatchIn(&g_nsAppManSrv, 27, in); +} + +Result nsLaunchApplication(u64 titleID) { + struct { + u64 titleID; + } in = { titleID }; + + return serviceDispatchIn(&g_nsAppManSrv, 19, in); +} + +Result nsPushLaunchVersion(u64 titleID, u32 version) { + struct { + u64 titleID; + u32 version; + u32 padding; + } in = { titleID, version, 0 }; + + return serviceDispatchIn(&g_nsAppManSrv, 36, in); +} + +Result nsGetContentMetaStorage(const NcmContentMetaKey *record, u8 *storageOut) { + + struct { + NcmContentMetaKey metaRecord; + } in; + memcpy(&in.metaRecord, record, sizeof(NcmContentMetaKey)); + + struct { + u8 out; + } out; + + Result rc = serviceDispatchInOut(&g_nsAppManSrv, 606, in, out); + + if (R_SUCCEEDED(rc) && storageOut) *storageOut = out.out; + + return rc; +} + +Result nsBeginInstallApplication(u64 tid, u32 unk, u8 storageId) { + + struct { + u32 storageId; + u32 unk; + u64 tid; + } in = { storageId, unk, tid }; + + return serviceDispatchIn(&g_nsAppManSrv, 26, in); +} + +Result nsInvalidateAllApplicationControlCache(void) { + return serviceDispatch(&g_nsAppManSrv, 401); +} + +Result nsInvalidateApplicationControlCache(u64 tid) { + + struct { + u64 tid; + } in = { tid }; + + return serviceDispatchIn(&g_nsAppManSrv, 404, in); +} + +Result nsCheckApplicationLaunchRights(u64 tid) { + + struct { + u64 tid; + } in = { tid }; + + return serviceDispatchIn(&g_nsAppManSrv, 39, in); +} + +Result nsGetApplicationContentPath(u64 tid, u8 type, char *out, size_t buf_size) { + + struct { + u8 padding[0x7]; + u8 type; + u64 tid; + } in = { {0}, type, tid }; + + return serviceDispatchIn(&g_nsAppManSrv, 21, in, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { out, buf_size } } + ); +} + +Result nsDisableApplicationAutoUpdate(u64 titleID) { + + struct { + u64 title_id; + } in = { titleID }; + + return serviceDispatchIn(&g_nsAppManSrv, 903, in); +} \ No newline at end of file diff --git a/source/nx/ipc/service_guard.h b/source/nx/ipc/service_guard.h new file mode 100644 index 0000000..2d627b4 --- /dev/null +++ b/source/nx/ipc/service_guard.h @@ -0,0 +1,74 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once +#include + +typedef struct ServiceGuard { + Mutex mutex; + u32 refCount; +} ServiceGuard; + +NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g) +{ + mutexLock(&g->mutex); + return (g->refCount++) == 0; +} + +NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void)) +{ + if (R_FAILED(rc)) { + cleanupFunc(); + --g->refCount; + } + mutexUnlock(&g->mutex); + return rc; +} + +NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void)) +{ + mutexLock(&g->mutex); + if (g->refCount && (--g->refCount) == 0) + cleanupFunc(); + mutexUnlock(&g->mutex); +} + +#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \ +\ +static ServiceGuard g_##name##Guard; \ +NX_INLINE Result _##name##Initialize _paramdecl; \ +static void _##name##Cleanup(void); \ +\ +Result name##Initialize _paramdecl \ +{ \ + Result rc = 0; \ + if (serviceGuardBeginInit(&g_##name##Guard)) \ + rc = _##name##Initialize _parampass; \ + return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \ +} \ +\ +void name##Exit(void) \ +{ \ + serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \ +} + +#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ()) diff --git a/source/nx/nca_writer.cpp b/source/nx/nca_writer.cpp new file mode 100644 index 0000000..c74f628 --- /dev/null +++ b/source/nx/nca_writer.cpp @@ -0,0 +1,478 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "nx/nca_writer.h" +#include "util/error.hpp" +#include +#include +#include "util/crypto.hpp" +#include "util/config.hpp" +#include "util/title_util.hpp" +#include "install/nca.hpp" + +void append(std::vector& buffer, const u8* ptr, u64 sz) +{ + u64 offset = buffer.size(); + buffer.resize(offset + sz); + memcpy(buffer.data() + offset, ptr, sz); +} + +NcaBodyWriter::NcaBodyWriter(const NcmContentId& ncaId, u64 offset, std::shared_ptr& contentStorage) : m_contentStorage(contentStorage), m_ncaId(ncaId), m_offset(offset) +{ +} + +NcaBodyWriter::~NcaBodyWriter() +{ +} + +u64 NcaBodyWriter::write(const u8* ptr, u64 sz) +{ + if(isOpen()) + { + m_contentStorage->WritePlaceholder(*(NcmPlaceHolderId*)&m_ncaId, m_offset, (void*)ptr, sz); + m_offset += sz; + return sz; + } + + return 0; +} + +bool NcaBodyWriter::isOpen() const +{ + return m_contentStorage != NULL; +} + + +class NczHeader +{ +public: + static const u64 MAGIC = 0x4E544345535A434E; + + class Section + { + public: + u64 offset; + u64 size; + u8 cryptoType; + u8 padding1[7]; + u64 padding2; + u8 cryptoKey[0x10]; + u8 cryptoCounter[0x10]; + } PACKED; + + class SectionContext : public Section + { + public: + SectionContext(const Section& s) : Section(s), crypto(s.cryptoKey, Crypto::AesCtr(Crypto::swapEndian(((u64*)&s.cryptoCounter)[0]))) + { + } + + virtual ~SectionContext() + { + } + + void decrypt(void* p, u64 sz, u64 offset) + { + if (this->cryptoType != 3) + { + return; + } + + crypto.seek(offset); + crypto.decrypt(p, p, sz); + } + + void encrypt(void* p, u64 sz, u64 offset) + { + if (this->cryptoType != 3) + { + return; + } + + crypto.seek(offset); + crypto.encrypt(p, p, sz); + } + + Crypto::Aes128Ctr crypto; + }; + + const bool isValid() + { + return m_magic == MAGIC && m_sectionCount < 0xFFFF; + } + + const u64 size() const + { + return sizeof(m_magic) + sizeof(m_sectionCount) + sizeof(Section) * m_sectionCount; + } + + const Section& section(u64 i) const + { + return m_sections[i]; + } + + const u64 sectionCount() const + { + return m_sectionCount; + } + +protected: + u64 m_magic; + u64 m_sectionCount; + Section m_sections[1]; +} PACKED; + +class NczBodyWriter : public NcaBodyWriter +{ +public: + NczBodyWriter(const NcmContentId& ncaId, u64 offset, std::shared_ptr& contentStorage) : NcaBodyWriter(ncaId, offset, contentStorage) + { + buffIn = malloc(buffInSize); + buffOut = malloc(buffOutSize); + + dctx = ZSTD_createDCtx(); + } + + virtual ~NczBodyWriter() + { + close(); + + for (auto& i : sections) + { + if (i) + { + delete i; + i = NULL; + } + } + + if (dctx) + { + ZSTD_freeDCtx(dctx); + dctx = NULL; + } + } + + bool close() + { + if (this->m_buffer.size()) + { + processChunk(m_buffer.data(), m_buffer.size()); + } + + flush(); + + return true; + } + + bool flush() + { + if(!isOpen()) + { + return false; + } + + if (m_deflateBuffer.size()) + { + m_contentStorage->WritePlaceholder(*(NcmPlaceHolderId*)&m_ncaId, m_offset, m_deflateBuffer.data(), m_deflateBuffer.size()); + m_offset += m_deflateBuffer.size(); + m_deflateBuffer.resize(0); + } + return true; + } + + NczHeader::SectionContext& section(u64 offset) + { + for (u64 i = 0; i < sections.size(); i++) + { + if (offset >= sections[i]->offset && offset < sections[i]->offset + sections[i]->size) + { + return *sections[i]; + } + } + return *sections[0]; + } + + bool encrypt(const void* ptr, u64 sz, u64 offset) + { + const u8* start = (u8*)ptr; + const u8* end = start + sz; + + while (start < end) + { + auto& s = section(offset); + + u64 sectionEnd = s.offset + s.size; + + u64 chunk = offset + sz > sectionEnd ? sectionEnd - offset : sz; + + s.encrypt((void*)start, chunk, offset); + + offset += chunk; + start += chunk; + sz -= chunk; + } + + return true; + } + + u64 processChunk(const u8* ptr, u64 sz) + { + ZSTD_inBuffer input = { ptr, sz, 0 }; + m_deflateBuffer.resize(sz); + m_deflateBuffer.resize(0); + + while (input.pos < input.size) + { + ZSTD_outBuffer output = { buffOut, buffOutSize, 0 }; + size_t const ret = ZSTD_decompressStream(dctx, &output, &input); + + if (ZSTD_isError(ret)) + { + LOG_DEBUG("%s\n", ZSTD_getErrorName(ret)); + return false; + } + + append(m_deflateBuffer, (const u8*)buffOut, output.pos); + + if (m_deflateBuffer.size() >= 0x1000000) // 16 MB + { + encrypt(m_deflateBuffer.data(), m_deflateBuffer.size(), m_offset); + + flush(); + } + + } + + if (m_deflateBuffer.size()) + { + encrypt(m_deflateBuffer.data(), m_deflateBuffer.size(), m_offset); + + flush(); + } + return 1; + } + + u64 write(const u8* ptr, u64 sz) override + { + if (!m_sectionsInitialized) + { + if (!m_buffer.size()) + { + append(m_buffer, ptr, sizeof(u64)*2); + ptr += sizeof(u64) * 2; + sz -= sizeof(u64) * 2; + } + + auto header = (NczHeader*)m_buffer.data(); + + if (m_buffer.size() + sz > header->size()) + { + u64 remainder = header->size() - m_buffer.size(); + append(m_buffer, ptr, remainder); + ptr += remainder; + sz -= remainder; + } + else + { + append(m_buffer, ptr, sz); + ptr += sz; + sz = 0; + } + + header = (NczHeader*)m_buffer.data(); + + if (m_buffer.size() == header->size()) + { + for (u64 i = 0; i < header->sectionCount(); i++) + { + sections.push_back(new NczHeader::SectionContext(header->section(i))); + } + + m_sectionsInitialized = true; + m_buffer.resize(0); + } + } + + while (sz) + { + if (m_buffer.size() + sz >= 0x1000000) + { + u64 chunk = 0x1000000 - m_buffer.size(); + append(m_buffer, ptr, chunk); + + processChunk(m_buffer.data(), m_buffer.size()); + m_buffer.resize(0); + + sz -= chunk; + ptr += chunk; + } + else + { + append(m_buffer, ptr, sz); + sz = 0; + } + + } + + return sz; + } + + size_t const buffInSize = ZSTD_DStreamInSize(); + size_t const buffOutSize = ZSTD_DStreamOutSize(); + + void* buffIn = NULL; + void* buffOut = NULL; + + ZSTD_DCtx* dctx = NULL; + + std::vector m_buffer; + std::vector m_deflateBuffer; + + bool m_sectionsInitialized = false; + + std::vector sections; +}; + +NcaWriter::NcaWriter(const NcmContentId& ncaId, std::shared_ptr& contentStorage) : m_ncaId(ncaId), m_contentStorage(contentStorage), m_writer(NULL) +{ +} + +NcaWriter::~NcaWriter() +{ + close(); +} + +bool NcaWriter::close() +{ + if (m_writer) + { + m_writer = NULL; + } + else if(m_buffer.size()) + { + if(isOpen()) + { + flushHeader(); + } + + m_buffer.resize(0); + } + m_contentStorage = NULL; + return true; +} + +bool NcaWriter::isOpen() const +{ + return (bool)m_contentStorage; +} + +u64 NcaWriter::write(const u8* ptr, u64 sz) +{ + if (m_buffer.size() < NCA_HEADER_SIZE) + { + if (m_buffer.size() + sz > NCA_HEADER_SIZE) + { + u64 remainder = NCA_HEADER_SIZE - m_buffer.size(); + append(m_buffer, ptr, remainder); + + ptr += remainder; + sz -= remainder; + } + else + { + append(m_buffer, ptr, sz); + ptr += sz; + sz = 0; + } + + if (m_buffer.size() == NCA_HEADER_SIZE) + { + flushHeader(); + } + } + + if (sz) + { + if (!m_writer) + { + if (sz >= sizeof(NczHeader::MAGIC)) + { + if (*(u64*)ptr == NczHeader::MAGIC) + { + m_writer = std::shared_ptr(new NczBodyWriter(m_ncaId, m_buffer.size(), m_contentStorage)); + } + else + { + m_writer = std::shared_ptr(new NcaBodyWriter(m_ncaId, m_buffer.size(), m_contentStorage)); + } + } + else + { + THROW_FORMAT("not enough data to read ncz header"); + } + } + + if(m_writer) + { + m_writer->write(ptr, sz); + } + else + { + THROW_FORMAT("null writer"); + } + } + + return sz; +} + +void NcaWriter::flushHeader() +{ + tin::install::NcaHeader header; + memcpy(&header, m_buffer.data(), sizeof(header)); + Crypto::AesXtr decryptor(Crypto::Keys().headerKey, false); + Crypto::AesXtr encryptor(Crypto::Keys().headerKey, true); + decryptor.decrypt(&header, &header, sizeof(header), 0, 0x200); + + if (header.magic == MAGIC_NCA3) + { + if(isOpen()) + { + m_contentStorage->CreatePlaceholder(m_ncaId, *(NcmPlaceHolderId*)&m_ncaId, header.nca_size); + } + } + else + { + THROW_FORMAT("Invalid NCA magic"); + } + + if (header.distribution == 1) + { + header.distribution = 0; + } + encryptor.encrypt(m_buffer.data(), &header, sizeof(header), 0, 0x200); + + if(isOpen()) + { + m_contentStorage->WritePlaceholder(*(NcmPlaceHolderId*)&m_ncaId, 0, m_buffer.data(), m_buffer.size()); + } +} diff --git a/source/nx/ncm.cpp b/source/nx/ncm.cpp new file mode 100644 index 0000000..cac9f7b --- /dev/null +++ b/source/nx/ncm.cpp @@ -0,0 +1,76 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "nx/ncm.hpp" +#include "util/error.hpp" + +namespace nx::ncm +{ + ContentStorage::ContentStorage(NcmStorageId storageId) + { + ASSERT_OK(ncmOpenContentStorage(&m_contentStorage, storageId), "Failed to open NCM ContentStorage"); + } + + ContentStorage::~ContentStorage() + { + serviceClose(&m_contentStorage.s); + } + + void ContentStorage::CreatePlaceholder(const NcmContentId &placeholderId, const NcmPlaceHolderId ®isteredId, size_t size) + { + ASSERT_OK(ncmContentStorageCreatePlaceHolder(&m_contentStorage, &placeholderId, ®isteredId, size), "Failed to create placeholder"); + } + + void ContentStorage::DeletePlaceholder(const NcmPlaceHolderId &placeholderId) + { + ASSERT_OK(ncmContentStorageDeletePlaceHolder(&m_contentStorage, &placeholderId), "Failed to delete placeholder"); + } + + void ContentStorage::WritePlaceholder(const NcmPlaceHolderId &placeholderId, u64 offset, void *buffer, size_t bufSize) + { + ASSERT_OK(ncmContentStorageWritePlaceHolder(&m_contentStorage, &placeholderId, offset, buffer, bufSize), "Failed to write to placeholder"); + } + + void ContentStorage::Register(const NcmPlaceHolderId &placeholderId, const NcmContentId ®isteredId) + { + ASSERT_OK(ncmContentStorageRegister(&m_contentStorage, ®isteredId, &placeholderId), "Failed to register placeholder NCA"); + } + + void ContentStorage::Delete(const NcmContentId ®isteredId) + { + ASSERT_OK(ncmContentStorageDelete(&m_contentStorage, ®isteredId), "Failed to delete registered NCA"); + } + + bool ContentStorage::Has(const NcmContentId ®isteredId) + { + bool hasNCA = false; + ASSERT_OK(ncmContentStorageHas(&m_contentStorage, &hasNCA, ®isteredId), "Failed to check if NCA is present"); + return hasNCA; + } + + std::string ContentStorage::GetPath(const NcmContentId ®isteredId) + { + char pathBuf[FS_MAX_PATH] = {0}; + ASSERT_OK(ncmContentStorageGetPath(&m_contentStorage, pathBuf, FS_MAX_PATH, ®isteredId), "Failed to get installed NCA path"); + return std::string(pathBuf); + } +} \ No newline at end of file diff --git a/source/nx/usbhdd.cpp b/source/nx/usbhdd.cpp new file mode 100644 index 0000000..7c7ec60 --- /dev/null +++ b/source/nx/usbhdd.cpp @@ -0,0 +1,190 @@ +/* +Microsoft Public License (Ms-PL) + +This license governs use of the accompanying software. If you use the +software, you accept this license. If you do not accept the license, +do not use the software. + + 1. Definitions + + The terms "reproduce," "reproduction," "derivative works," and + "distribution" have the same meaning here as under U.S. copyright + law. + + A "contribution" is the original software, or any additions or + changes to the software. + + A "contributor" is any person that distributes its contribution + under this license. + + "Licensed patents" are a contributor's patent claims that read + directly on its contribution. + + 2. Grant of Rights + + (A) Copyright Grant- Subject to the terms of this license, + including the license conditions and limitations in section 3, + each contributor grants you a non-exclusive, worldwide, + royalty-free copyright license to reproduce its contribution, + prepare derivative works of its contribution, and distribute its + contribution or any derivative works that you create. + + (B) Patent Grant- Subject to the terms of this license, including + the license conditions and limitations in section 3, each + contributor grants you a non-exclusive, worldwide, royalty-free + license under its licensed patents to make, have made, use, sell, + offer for sale, import, and/or otherwise dispose of its + contribution in the software or derivative works of the + contribution in the software. + + 3. Conditions and Limitations + + (A) No Trademark License- This license does not grant you rights + to use any contributors' name, logo, or trademarks. + + (B) If you bring a patent claim against any contributor over + patents that you claim are infringed by the software, your patent + license from such contributor to the software ends automatically. + + (C) If you distribute any portion of the software, you must retain + all copyright, patent, trademark, and attribution notices that are + present in the software. + + (D) If you distribute any portion of the software in source code + form, you may do so only under this license by including a + complete copy of this license with your distribution. If you + distribute any portion of the software in compiled or object code + form, you may only do so under a license that complies with this + license. + + (E) You may not distribute, copy, use, or link any portion of this + code to any other code that requires distribution of source code. + + (F) The software is licensed "as-is." You bear the risk of using + it. The contributors give no express warranties, guarantees, or + conditions. You may have additional consumer rights under your + local laws which this license cannot change. To the extent + permitted under your local laws, the contributors exclude the + implied warranties of merchantability, fitness for a particular + purpose and non-infringement. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nx::hdd +{ + static const u32 MAX_DEVICES = 32; + static UEvent *g_statusChangeEvent = NULL, g_exitEvent = { 0 }; + + static u32 g_usbDeviceCount = 0; + UsbHsFsDevice g_usbDevices[MAX_DEVICES]; + + static thrd_t g_thread = { 0 }; + static std::mutex g_mutex; + + static int entry(void *arg) + { + (void)arg; + + Result rc = 0; + int idx = 0; + u32 listed_device_count = 0; + + Waiter status_change_event_waiter = waiterForUEvent(g_statusChangeEvent); + Waiter exit_event_waiter = waiterForUEvent(&g_exitEvent); + + while(true) + { + rc = waitMulti(&idx, -1, status_change_event_waiter, exit_event_waiter); + if(R_FAILED(rc)) continue; + + std::scoped_lock lock(g_mutex); + + if(idx == 1) + { + break; + } + + g_usbDeviceCount = usbHsFsGetMountedDeviceCount(); + + if(!g_usbDeviceCount) continue; + + if(!(listed_device_count = usbHsFsListMountedDevices(g_usbDevices, std::min(g_usbDeviceCount, MAX_DEVICES)))) + { + continue; + } + } + + g_usbDeviceCount = 0; + + return 0; + } + + u32 count() + { + return std::min(g_usbDeviceCount, MAX_DEVICES); + } + + const char* rootPath(u32 index) + { + if(index >= MAX_DEVICES) + { + return nullptr; + } + + std::scoped_lock lock(g_mutex); + + if(index < usbHsFsGetMountedDeviceCount()) + { + return g_usbDevices[index].name; + } + + return nullptr; + } + + bool init() + { + if(g_statusChangeEvent) + { + return true; + } + + if(usbHsFsInitialize(0)) + { + return false; + } + + g_statusChangeEvent = usbHsFsGetStatusChangeUserEvent(); + + ueventCreate(&g_exitEvent, true); + + thrd_create(&g_thread, entry, NULL); + + return true; + } + + bool exit() + { + if(!g_statusChangeEvent) + { + return false; + } + + ueventSignal(&g_exitEvent); + + thrd_join(g_thread, NULL); + + g_statusChangeEvent = NULL; + + usbHsFsExit(); + + return true; + } +} diff --git a/source/sdInstall.cpp b/source/sdInstall.cpp new file mode 100644 index 0000000..cf35ced --- /dev/null +++ b/source/sdInstall.cpp @@ -0,0 +1,169 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "sdInstall.hpp" +#include "install/install_nsp.hpp" +#include "install/install_xci.hpp" +#include "install/sdmc_xci.hpp" +#include "install/sdmc_nsp.hpp" +#include "nx/fs.hpp" +#include "util/file_util.hpp" +#include "util/title_util.hpp" +#include "util/error.hpp" +#include "util/config.hpp" +#include "util/util.hpp" +#include "util/lang.hpp" +#include "ui/MainApplication.hpp" +#include "ui/instPage.hpp" + +namespace inst::ui { + extern MainApplication *mainApp; +} + +namespace nspInstStuff { + + void installNspFromFile(std::vector ourTitleList, int whereToInstall) + { + inst::util::initInstallServices(); + inst::ui::instPage::loadInstallScreen(); + bool nspInstalled = true; + NcmStorageId m_destStorageId = NcmStorageId_SdCard; + + if (whereToInstall) m_destStorageId = NcmStorageId_BuiltInUser; + unsigned int titleItr; + + std::vector previousClockValues; + if (inst::config::overClock) { + previousClockValues.push_back(inst::util::setClockSpeed(0, 1785000000)[0]); + previousClockValues.push_back(inst::util::setClockSpeed(1, 76800000)[0]); + previousClockValues.push_back(inst::util::setClockSpeed(2, 1600000000)[0]); + } + + try + { + for (titleItr = 0; titleItr < ourTitleList.size(); titleItr++) { + inst::ui::instPage::setTopInstInfoText("inst.info_page.top_info0"_lang + inst::util::shortenString(ourTitleList[titleItr].filename().string(), 40, true) + "inst.sd.source_string"_lang); + std::unique_ptr installTask; + + if (ourTitleList[titleItr].extension() == ".xci" || ourTitleList[titleItr].extension() == ".xcz") { + auto sdmcXCI = std::make_shared(ourTitleList[titleItr]); + installTask = std::make_unique(m_destStorageId, inst::config::ignoreReqVers, sdmcXCI); + } else { + auto sdmcNSP = std::make_shared(ourTitleList[titleItr]); + installTask = std::make_unique(m_destStorageId, inst::config::ignoreReqVers, sdmcNSP); + } + + LOG_DEBUG("%s\n", "Preparing installation"); + inst::ui::instPage::setInstInfoText("inst.info_page.preparing"_lang); + inst::ui::instPage::setInstBarPerc(0); + installTask->Prepare(); + installTask->Begin(); + } + } + catch (std::exception& e) + { + LOG_DEBUG("Failed to install"); + LOG_DEBUG("%s", e.what()); + fprintf(stdout, "%s", e.what()); + inst::ui::instPage::setInstInfoText("inst.info_page.failed"_lang + inst::util::shortenString(ourTitleList[titleItr].filename().string(), 42, true)); + inst::ui::instPage::setInstBarPerc(0); + std::string audioPath = ""; + if (std::filesystem::exists(inst::config::appDir + "/sounds/OHNO.WAV")) { + audioPath = (inst::config::appDir + "/sounds/OHNO.WAV"); + } + else { + audioPath = "romfs:/audio/bark.wav"; + } + std::thread audioThread(inst::util::playAudio,audioPath); + inst::ui::mainApp->CreateShowDialog("inst.info_page.failed"_lang + inst::util::shortenString(ourTitleList[titleItr].filename().string(), 42, true) + "!", "inst.info_page.failed_desc"_lang + "\n\n" + (std::string)e.what(), {"common.ok"_lang}, true); + audioThread.join(); + nspInstalled = false; + } + + if (previousClockValues.size() > 0) { + inst::util::setClockSpeed(0, previousClockValues[0]); + inst::util::setClockSpeed(1, previousClockValues[1]); + inst::util::setClockSpeed(2, previousClockValues[2]); + } + + if(nspInstalled) { + inst::ui::instPage::setInstInfoText("inst.info_page.complete"_lang); + inst::ui::instPage::setInstBarPerc(100); + std::string audioPath = ""; + + if (inst::config::useSound) { + + if (std::filesystem::exists(inst::config::appDir + "/sounds/YIPPEE.WAV")) { + audioPath = (inst::config::appDir + "/sounds/YIPPEE.WAV"); + } + else { + audioPath = "romfs:/audio/ameizing.mp3"; + } + std::thread audioThread(inst::util::playAudio,audioPath); + + if (ourTitleList.size() > 1) { + if (inst::config::deletePrompt) { + if(inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.sd.delete_info_multi"_lang, "inst.sd.delete_desc"_lang, {"common.no"_lang,"common.yes"_lang}, false) == 1) { + for (long unsigned int i = 0; i < ourTitleList.size(); i++) { + if (std::filesystem::exists(ourTitleList[i])) std::filesystem::remove(ourTitleList[i]); + } + } + } else inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.info_page.desc0"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } else { + if (inst::config::deletePrompt) { + if(inst::ui::mainApp->CreateShowDialog(inst::util::shortenString(ourTitleList[0].filename().string(), 32, true) + "inst.sd.delete_info"_lang, "inst.sd.delete_desc"_lang, {"common.no"_lang,"common.yes"_lang}, false) == 1) if (std::filesystem::exists(ourTitleList[0])) std::filesystem::remove(ourTitleList[0]); + } else inst::ui::mainApp->CreateShowDialog(inst::util::shortenString(ourTitleList[0].filename().string(), 42, true) + "inst.info_page.desc1"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } + + audioThread.join(); + } + + else{ + if (ourTitleList.size() > 1) { + if (inst::config::deletePrompt) { + if(inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.sd.delete_info_multi"_lang, "inst.sd.delete_desc"_lang, {"common.no"_lang,"common.yes"_lang}, false) == 1) { + for (long unsigned int i = 0; i < ourTitleList.size(); i++) { + if (std::filesystem::exists(ourTitleList[i])) std::filesystem::remove(ourTitleList[i]); + } + } + } else inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.info_page.desc0"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } else { + if (inst::config::deletePrompt) { + if(inst::ui::mainApp->CreateShowDialog(inst::util::shortenString(ourTitleList[0].filename().string(), 32, true) + "inst.sd.delete_info"_lang, "inst.sd.delete_desc"_lang, {"common.no"_lang,"common.yes"_lang}, false) == 1) if (std::filesystem::exists(ourTitleList[0])) std::filesystem::remove(ourTitleList[0]); + } else inst::ui::mainApp->CreateShowDialog(inst::util::shortenString(ourTitleList[0].filename().string(), 42, true) + "inst.info_page.desc1"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } + } + } + + LOG_DEBUG("Done"); + inst::ui::instPage::loadMainMenu(); + inst::util::deinitInstallServices(); + return; + } +} diff --git a/source/sigInstall.cpp b/source/sigInstall.cpp new file mode 100644 index 0000000..457397b --- /dev/null +++ b/source/sigInstall.cpp @@ -0,0 +1,73 @@ +#include +#include "util/error.hpp" +#include "ui/MainApplication.hpp" +#include "util/curl.hpp" +#include "util/util.hpp" +#include "util/unzip.hpp" +#include "util/config.hpp" +#include "util/lang.hpp" + +namespace inst::ui { + extern MainApplication *mainApp; +} + +namespace sig { + void installSigPatches () { + bpcInitialize(); + try { + std::string patchesVersion = inst::util::readTextFromFile("sdmc:/atmosphere/exefs_patches/es_patches/patches.txt"); + std::string versionText = ""; + std::string installButtonText = "sig.install"_lang; + if (patchesVersion != "") { + versionText = "\n\n" + "sig.version_text"_lang + patchesVersion + "."; + installButtonText = "sig.update"_lang; + } + int ourResult = inst::ui::mainApp->CreateShowDialog("sig.title0"_lang, "sig.desc0"_lang + versionText, {installButtonText, "sig.uninstall"_lang, "common.cancel"_lang}, true); + if (ourResult == 0) { + if (inst::util::getIPAddress() == "1.0.0.127") { + inst::ui::mainApp->CreateShowDialog("main.net.title"_lang, "main.net.desc"_lang, {"common.ok"_lang}, true); + return; + } + if (!inst::util::copyFile("sdmc:/bootloader/patches.ini", inst::config::appDir + "/patches.ini.old")) { + if (inst::ui::mainApp->CreateShowDialog("sig.backup_failed"_lang, "sig.backup_failed_desc"_lang, {"common.yes"_lang, "common.no"_lang}, false)) return; + } + std::string ourPath = inst::config::appDir + "/patches.zip"; + bool didDownload = inst::curl::downloadFile(inst::config::sigPatchesUrl, ourPath.c_str()); + bool didExtract = false; + if (didDownload) didExtract = inst::zip::extractFile(ourPath, "sdmc:/"); + else { + inst::ui::mainApp->CreateShowDialog("sig.download_failed"_lang, "sig.download_failed_desc"_lang, {"common.ok"_lang}, true); + return; + } + std::filesystem::remove(ourPath); + if (didExtract) { + patchesVersion = inst::util::readTextFromFile("sdmc:/atmosphere/exefs_patches/es_patches/patches.txt"); + versionText = ""; + if (patchesVersion != "") versionText = "sig.version_text2"_lang + patchesVersion + "! "; + if (inst::ui::mainApp->CreateShowDialog("sig.install_complete"_lang, versionText + "\n\n" + "sig.complete_desc"_lang, {"sig.restart"_lang, "sig.later"_lang}, false) == 0) bpcRebootSystem(); + } + else { + inst::ui::mainApp->CreateShowDialog("sig.extract_failed"_lang, "", {"common.ok"_lang}, true); + return; + } + return; + } else if (ourResult == 1) { + if (!inst::util::copyFile( inst::config::appDir + "/patches.ini.old", "sdmc:/bootloader/patches.ini")) { + if (inst::ui::mainApp->CreateShowDialog("sig.restore_failed"_lang, "", {"common.yes"_lang, "common.no"_lang}, false)) return; + } else std::filesystem::remove(inst::config::appDir + "/patches.ini.old"); + if (inst::util::removeDirectory("sdmc:/atmosphere/exefs_patches/es_patches")) { + if (inst::ui::mainApp->CreateShowDialog("sig.uninstall_complete"_lang, "sig.complete_desc"_lang, {"sig.restart"_lang, "sig.later"_lang}, false) == 0) bpcRebootSystem(); + } + else inst::ui::mainApp->CreateShowDialog("sig.remove_failed"_lang, "sig.remove_failed_desc"_lang, {"common.ok"_lang}, true); + } else return; + } + catch (std::exception& e) + { + LOG_DEBUG("Failed to install Signature Patches"); + LOG_DEBUG("%s", e.what()); + fprintf(stdout, "%s", e.what()); + inst::ui::mainApp->CreateShowDialog("sig.generic_error"_lang, (std::string)e.what(), {"common.ok"_lang}, true); + } + bpcExit(); + } +} \ No newline at end of file diff --git a/source/ui/HDInstPage.cpp b/source/ui/HDInstPage.cpp new file mode 100644 index 0000000..326a8de --- /dev/null +++ b/source/ui/HDInstPage.cpp @@ -0,0 +1,188 @@ +#include +#include "ui/MainApplication.hpp" +#include "ui/mainPage.hpp" +#include "ui/HDInstPage.hpp" +#include "HDInstall.hpp" +#include "util/util.hpp" +#include "util/config.hpp" +#include "util/lang.hpp" + +#define COLOR(hex) pu::ui::Color::FromHex(hex) + + +namespace inst::ui { + extern MainApplication *mainApp; + + HDInstPage::HDInstPage() : Layout::Layout() { + this->infoRect = Rectangle::New(0, 95, 1280, 60, COLOR("#00000080")); + this->SetBackgroundColor(COLOR("#000000FF")); + this->topRect = Rectangle::New(0, 0, 1280, 94, COLOR("#000000FF")); + this->botRect = Rectangle::New(0, 659, 1280, 61, COLOR("#000000FF")); + + if (inst::config::gayMode) { + if + (std::filesystem::exists(inst::config::appDir + "/images/Hd.png")) this->titleImage = Image::New(0, 0, (inst::config::appDir + "/images/Hd.png")); + else + this->titleImage = Image::New(0, 0, "romfs:/images/Hd.png"); + + if + (std::filesystem::exists(inst::config::appDir + "/images/Background.png")) this->SetBackgroundImage(inst::config::appDir + "/images/Background.png"); + else + { + this->SetBackgroundImage("romfs:/images/Background.png"); + } + } + + else { + this->SetBackgroundImage("romfs:/images/Background.png"); + this->titleImage = Image::New(0, 0, "romfs:/images/Hd.png"); + } + //this->pageInfoText = TextBlock::New(10, 109, "inst.hd.top_info"_lang, 30); + this->pageInfoText = TextBlock::New(10, 109, "inst.hd.top_info"_lang); + this->pageInfoText->SetColor(COLOR("#FFFFFFFF")); + //this->butText = TextBlock::New(10, 678, "inst.hd.buttons"_lang, 24); + this->butText = TextBlock::New(10, 678, "inst.hd.buttons"_lang); + this->butText->SetColor(COLOR("#FFFFFFFF")); + this->menu = pu::ui::elm::Menu::New(0, 156, 1280, COLOR("#FFFFFF00"), COLOR("#4f4f4d33"), 84, 6); + this->menu->SetScrollbarColor(COLOR("#1A1919FF")); + this->Add(this->topRect); + this->Add(this->infoRect); + this->Add(this->botRect); + this->Add(this->titleImage); + this->Add(this->appVersionText); + this->Add(this->butText); + this->Add(this->pageInfoText); + this->Add(this->menu); + } + + void HDInstPage::drawMenuItems(bool clearItems, std::filesystem::path ourPath) { + if (clearItems) this->selectedTitles = {}; + this->currentDir = ourPath; + + auto pathStr = this->currentDir.string(); + if(pathStr.length()) + { + if(pathStr[pathStr.length() - 1] == ':') + { + this->currentDir = this->currentDir / ""; + } + } + + this->menu->ClearItems(); + try { + this->ourDirectories = util::getDirsAtPath(this->currentDir); + this->ourFiles = util::getDirectoryFiles(this->currentDir, {".nsp", ".nsz", ".xci", ".xcz"}); + } + + catch (std::exception& e) { + this->drawMenuItems(false, this->currentDir.parent_path()); + return; + } + + std::string itm = ".."; + auto ourEntry = pu::ui::elm::MenuItem::New(itm); + ourEntry->SetColor(COLOR("#FFFFFFFF")); + ourEntry->SetIcon("romfs:/images/icons/folder-upload.png"); + this->menu->AddItem(ourEntry); + + for (auto& file: this->ourDirectories) { + if (file == "..") break; + std::string itm = file.filename().string(); + auto ourEntry = pu::ui::elm::MenuItem::New(itm); + ourEntry->SetColor(COLOR("#FFFFFFFF")); + ourEntry->SetIcon("romfs:/images/icons/folder.png"); + this->menu->AddItem(ourEntry); + } + for (auto& file: this->ourFiles) { + std::string itm = file.filename().string(); + auto ourEntry = pu::ui::elm::MenuItem::New(itm); + ourEntry->SetColor(COLOR("#FFFFFFFF")); + ourEntry->SetIcon("romfs:/images/icons/checkbox-blank-outline.png"); + for (long unsigned int i = 0; i < this->selectedTitles.size(); i++) { + if (this->selectedTitles[i] == file) { + ourEntry->SetIcon("romfs:/images/icons/check-box-outline.png"); + } + } + this->menu->AddItem(ourEntry); + } + } + + + void HDInstPage::followDirectory() { + int selectedIndex = this->menu->GetSelectedIndex(); + int dirListSize = this->ourDirectories.size(); + + dirListSize++; + selectedIndex--; + + if (selectedIndex < dirListSize) { + if (this->menu->GetItems()[this->menu->GetSelectedIndex()]->GetName() == ".." && this->menu->GetSelectedIndex() == 0) { + this->drawMenuItems(true, this->currentDir.parent_path()); + } else { + this->drawMenuItems(true, this->ourDirectories[selectedIndex]); + } + this->menu->SetSelectedIndex(0); + } + } + + void HDInstPage::selectNsp(int selectedIndex) { + int dirListSize = this->ourDirectories.size(); + dirListSize++; + + if (this->menu->GetItems()[selectedIndex]->GetIconPath() == "romfs:/images/icons/check-box-outline.png") { + for (long unsigned int i = 0; i < this->selectedTitles.size(); i++) { + if (this->selectedTitles[i] == this->ourFiles[selectedIndex - dirListSize]) this->selectedTitles.erase(this->selectedTitles.begin() + i); + } + } else if (this->menu->GetItems()[selectedIndex]->GetIconPath() == "romfs:/images/icons/checkbox-blank-outline.png") this->selectedTitles.push_back(this->ourFiles[selectedIndex - dirListSize]); + else { + this->followDirectory(); + return; + } + this->drawMenuItems(false, currentDir); + } + + void HDInstPage::startInstall() { + int dialogResult = -1; + if (this->selectedTitles.size() == 1) { + dialogResult = mainApp->CreateShowDialog("inst.target.desc0"_lang + inst::util::shortenString(std::filesystem::path(this->selectedTitles[0]).filename().string(), 32, true) + "inst.target.desc1"_lang, "common.cancel_desc"_lang, {"inst.target.opt0"_lang, "inst.target.opt1"_lang}, false); + } else dialogResult = mainApp->CreateShowDialog("inst.target.desc00"_lang + std::to_string(this->selectedTitles.size()) + "inst.target.desc01"_lang, "common.cancel_desc"_lang, {"inst.target.opt0"_lang, "inst.target.opt1"_lang}, false); + if (dialogResult == -1) return; + nspInstStuff_B::installNspFromFile(this->selectedTitles, dialogResult); + } + + + + void HDInstPage::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint touch_pos) { + if (Down & HidNpadButton_B) { + mainApp->LoadLayout(mainApp->mainPage); + } + //if ((Down & HidNpadButton_A) || (Up & HidGestureType_Touch)) { + if (Down & HidNpadButton_A) { + this->selectNsp(this->menu->GetSelectedIndex()); + if (this->ourFiles.size() == 1 && this->selectedTitles.size() == 1) { + this->startInstall(); + } + } + if ((Down & HidNpadButton_Y)) { + if (this->selectedTitles.size() == this->ourFiles.size()) this->drawMenuItems(true, currentDir); + else { + int topDir = 0; + topDir++; + for (long unsigned int i = this->ourDirectories.size() + topDir; i < this->menu->GetItems().size(); i++) { + if (this->menu->GetItems()[i]->GetIconPath() == "romfs:/images/icons/check-box-outline.png") continue; + else this->selectNsp(i); + } + this->drawMenuItems(false, currentDir); + } + } + if ((Down & HidNpadButton_X)) { + inst::ui::mainApp->CreateShowDialog("inst.hd.help.title"_lang, "inst.hd.help.desc"_lang, {"common.ok"_lang}, true); + } + if (Down & HidNpadButton_Plus) { + if (this->selectedTitles.size() == 0 && this->menu->GetItems()[this->menu->GetSelectedIndex()]->GetIconPath() == "romfs:/images/icons/checkbox-blank-outline.png") { + this->selectNsp(this->menu->GetSelectedIndex()); + } + if (this->selectedTitles.size() > 0) this->startInstall(); + } + } +} \ No newline at end of file diff --git a/source/ui/MainApplication.cpp b/source/ui/MainApplication.cpp new file mode 100644 index 0000000..f1adc6b --- /dev/null +++ b/source/ui/MainApplication.cpp @@ -0,0 +1,28 @@ +#include "ui/MainApplication.hpp" +#include "util/lang.hpp" + +namespace inst::ui { + MainApplication *mainApp; + + void MainApplication::OnLoad() { + mainApp = this; + + Language::Load(); + + this->mainPage = MainPage::New(); + this->netinstPage = netInstPage::New(); + this->sdinstPage = sdInstPage::New(); + this->HDinstPage = HDInstPage::New(); + this->usbinstPage = usbInstPage::New(); + this->instpage = instPage::New(); + this->optionspage = optionsPage::New(); + this->mainPage->SetOnInput(std::bind(&MainPage::onInput, this->mainPage, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->netinstPage->SetOnInput(std::bind(&netInstPage::onInput, this->netinstPage, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->sdinstPage->SetOnInput(std::bind(&sdInstPage::onInput, this->sdinstPage, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->HDinstPage->SetOnInput(std::bind(&HDInstPage::onInput, this->HDinstPage, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->usbinstPage->SetOnInput(std::bind(&usbInstPage::onInput, this->usbinstPage, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->instpage->SetOnInput(std::bind(&instPage::onInput, this->instpage, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->optionspage->SetOnInput(std::bind(&optionsPage::onInput, this->optionspage, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->LoadLayout(this->mainPage); + } +} \ No newline at end of file diff --git a/source/ui/instPage.cpp b/source/ui/instPage.cpp new file mode 100644 index 0000000..3ce3d95 --- /dev/null +++ b/source/ui/instPage.cpp @@ -0,0 +1,81 @@ +#include +#include "ui/MainApplication.hpp" +#include "ui/instPage.hpp" +#include "util/config.hpp" + +#define COLOR(hex) pu::ui::Color::FromHex(hex) + +namespace inst::ui { + extern MainApplication *mainApp; + + instPage::instPage() : Layout::Layout() { + this->infoRect = Rectangle::New(0, 95, 1280, 60, COLOR("#00000080")); + this->SetBackgroundColor(COLOR("#000000FF")); + this->topRect = Rectangle::New(0, 0, 1280, 94, COLOR("#000000FF")); + + if (inst::config::gayMode) { + if (std::filesystem::exists(inst::config::appDir + "/images/Install.png")) this->titleImage = Image::New(0, 0, (inst::config::appDir + "/images/Install.png")); + else this->titleImage = Image::New(0, 0, "romfs:/images/Install.png"); + if (std::filesystem::exists(inst::config::appDir + "/images/Background.png")) this->SetBackgroundImage(inst::config::appDir + "/images/Background.png"); + else this->SetBackgroundImage("romfs:/images/Background.png"); + //this->appVersionText = TextBlock::New(0, 0, "", 0); + this->appVersionText = TextBlock::New(0, 0, ""); + } + else { + this->SetBackgroundImage("romfs:/images/Background.png"); + this->titleImage = Image::New(0, 0, "romfs:/images/Install.png"); + //this->appVersionText = TextBlock::New(0, 0, "", 0); + this->appVersionText = TextBlock::New(0, 0, ""); + } + this->appVersionText->SetColor(COLOR("#FFFFFFFF")); + //this->pageInfoText = TextBlock::New(10, 109, "", 30); + this->pageInfoText = TextBlock::New(10, 109, ""); + this->pageInfoText->SetColor(COLOR("#FFFFFFFF")); + //this->installInfoText = TextBlock::New(15, 648, "", 22); + this->installInfoText = TextBlock::New(15, 648, ""); + this->installInfoText->SetColor(COLOR("#FFFFFFFF")); + //this->installBar = pu::ui::elm::ProgressBar::New(10, 600, 850, 40, 100.0f); + this->installBar = pu::ui::elm::ProgressBar::New(10, 680, 1260, 30, 100.0f); + this->installBar->SetBackgroundColor(COLOR("#000000FF")); + this->installBar->SetProgressColor(COLOR("#565759FF")); + this->Add(this->topRect); + this->Add(this->infoRect); + this->Add(this->titleImage); + this->Add(this->appVersionText); + this->Add(this->pageInfoText); + this->Add(this->installInfoText); + this->Add(this->installBar); + } + + void instPage::setTopInstInfoText(std::string ourText){ + mainApp->instpage->pageInfoText->SetText(ourText); + mainApp->CallForRender(); + } + + void instPage::setInstInfoText(std::string ourText){ + mainApp->instpage->installInfoText->SetText(ourText); + mainApp->CallForRender(); + } + + void instPage::setInstBarPerc(double ourPercent){ + mainApp->instpage->installBar->SetVisible(true); + mainApp->instpage->installBar->SetProgress(ourPercent); + mainApp->CallForRender(); + } + + void instPage::loadMainMenu(){ + mainApp->LoadLayout(mainApp->mainPage); + } + + void instPage::loadInstallScreen(){ + mainApp->instpage->pageInfoText->SetText(""); + mainApp->instpage->installInfoText->SetText(""); + mainApp->instpage->installBar->SetProgress(0); + mainApp->instpage->installBar->SetVisible(false); + mainApp->LoadLayout(mainApp->instpage); + mainApp->CallForRender(); + } + + void instPage::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint touch_pos) { + } +} \ No newline at end of file diff --git a/source/ui/mainPage.cpp b/source/ui/mainPage.cpp new file mode 100644 index 0000000..f2ef522 --- /dev/null +++ b/source/ui/mainPage.cpp @@ -0,0 +1,305 @@ +#include +#include +#include +#include +#include "ui/MainApplication.hpp" +#include "ui/mainPage.hpp" +#include "util/util.hpp" +#include "util/config.hpp" +#include "util/lang.hpp" +#include "sigInstall.hpp" +#include "HDInstall.hpp" +#include "data/buffered_placeholder_writer.hpp" +#include "nx/usbhdd.h" +#include + +#define COLOR(hex) pu::ui::Color::FromHex(hex) + + + +int statvfs(const char *path, struct statvfs *buf); + +double GetAvailableSpace(const char* path) +{ + struct statvfs stat; + + if (statvfs(path, &stat) != 0) { + // error happens, just quits here + return -1; + } + + // the available size is f_bsize * f_bavail + return stat.f_bsize * stat.f_bavail; +} + +double amountOfDiskSpaceUsed(const char* path) +{ + struct statvfs stat; + + if (statvfs(path, &stat) != 0) { + // error happens, just quits here + return -1; + } + const auto total = static_cast(stat.f_blocks); + const auto available = static_cast(stat.f_bavail); + const auto availableToRoot = static_cast(stat.f_bfree); + const auto used = total - availableToRoot; + const auto nonRootTotal = used + available; + return 100.0 * static_cast(used) / static_cast(nonRootTotal); +} + +double totalsize(const char* path) +{ + struct statvfs stat; + + if (statvfs(path, &stat) != 0) { + // error happens, just quits here + return -1; + } + return stat.f_blocks * stat.f_frsize; +} + +namespace inst::ui { + extern MainApplication *mainApp; + bool appletFinished = false; + bool updateFinished = false; + + void mathstuff() { + double math = (GetAvailableSpace("./") / 1024) / 1024; //megabytes + float math2 = ((float)math / 1024); //gigabytes + + double used = (amountOfDiskSpaceUsed("./")); //same file path as sdmc + + double total = (totalsize("sdmc:/") / 1024) / 1024; //megabytes + float total2 = ((float)total / 1024); //gigabytes + // + float GB = math2; + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << GB; //only show 2 decimal places + std::string freespace = stream.str(); + + + float GB2 = total2; + std::stringstream stream2; + stream2 << std::fixed << std::setprecision(2) << GB2; //only show 2 decimal places + std::string sdsize = stream2.str(); + + //printf("\nSdCard Free Space in MB: %li", math); + //printf("\nSdCard Free Space in GB: %.2f", math2); + std::stringstream stream3; + stream3 << std::fixed << std::setprecision(2) << used; //only show 2 decimal places + std::string percent = stream3.str(); + + //unmount sd here and mount system.... + //fsdevUnmountDevice("sdmc"); + FsFileSystem nandFS; + fsOpenBisFileSystem(&nandFS, FsBisPartitionId_User, ""); + fsdevMountDevice("user", nandFS); + + double math3 = (GetAvailableSpace("user:/") / 1024) / 1024; //megabytes + float math4 = ((float)math3 / 1024); //gigabytes + + double used2 = (amountOfDiskSpaceUsed("user:/")); //same file path as sdmc + + double total3 = (totalsize("user:/") / 1024) / 1024; //megabytes + float total4 = ((float)total3 / 1024); //gigabytes + // + float GB3 = math4; + std::stringstream stream4; + stream4 << std::fixed << std::setprecision(2) << GB3; //only show 2 decimal places + std::string freespace2 = stream4.str(); + + + float GB4 = total4; + std::stringstream stream5; + stream5 << std::fixed << std::setprecision(2) << GB4; //only show 2 decimal places + std::string sdsize2 = stream5.str(); + + //printf("\nSdCard Free Space in MB: %li", math); + //printf("\nSdCard Free Space in GB: %.2f", math2); + std::stringstream stream6; + stream6 << std::fixed << std::setprecision(2) << used2; //only show 2 decimal places + std::string percent2 = stream6.str(); + + //unmount user now as we already know how much space we have + fsdevUnmountDevice("user"); + + + std::string Info = ("System total size: " + sdsize2 + " GB" + "\nSystem free space: " + freespace2 + " GB" + "\nSystem percent used: " + percent2 + "%" + "\n\n" + "SD card total size: " + sdsize + " GB" + "\nSD card free space: " + freespace + " GB" + "\nSD card percent used: " + percent + "%"); + inst::ui::mainApp->CreateShowDialog("Space Usage Information", Info, {"common.ok"_lang}, true); + } + + void mainMenuThread() { + bool menuLoaded = mainApp->IsShown(); + if (!appletFinished && appletGetAppletType() == AppletType_LibraryApplet) { + tin::data::NUM_BUFFER_SEGMENTS = 2; + if (menuLoaded) { + inst::ui::appletFinished = true; + mainApp->CreateShowDialog("main.applet.title"_lang, "main.applet.desc"_lang, {"common.ok"_lang}, true); + } + } else if (!appletFinished) { + inst::ui::appletFinished = true; + tin::data::NUM_BUFFER_SEGMENTS = 128; + } + if (!updateFinished && (!inst::config::autoUpdate || inst::util::getIPAddress() == "1.0.0.127")) updateFinished = true; + + if (!updateFinished && menuLoaded && inst::config::updateInfo.size()) { + updateFinished = true; + optionsPage::askToUpdate(inst::config::updateInfo); + } + } + + MainPage::MainPage() : Layout::Layout() { + this->SetBackgroundColor(COLOR("#000000FF")); + this->topRect = Rectangle::New(0, 0, 1280, 94, COLOR("#000000FF")); + this->botRect = Rectangle::New(0, 659, 1280, 61, COLOR("#000000FF")); + + if (inst::config::gayMode) { + if (std::filesystem::exists(inst::config::appDir + "/images/Main.png")) this->titleImage = Image::New(0, 0, (inst::config::appDir + "/images/Main.png")); + else + this->titleImage = Image::New(0, 0, "romfs:/images/Main.png"); + if (std::filesystem::exists(inst::config::appDir + "/images/Background.png")) this->SetBackgroundImage(inst::config::appDir + "/images/Background.png"); + else + this->SetBackgroundImage("romfs:/images/Background.png"); + } + else { + this->SetBackgroundImage("romfs:/images/Background.png"); + this->titleImage = Image::New(0, 0, "romfs:/images/Main.png"); + } + //this->butText = TextBlock::New(10, 678, "main.buttons"_lang, 24); + this->butText = TextBlock::New(10, 678, "main.buttons"_lang); + this->butText->SetColor(COLOR("#FFFFFFFF")); + this->optionMenu = pu::ui::elm::Menu::New(0, 95, 1280, COLOR("#343E8700"), COLOR("#4f4f4d33"), 94, 6); + //this->optionMenu->SetItemsFocusColor(COLOR("#4f4f4d33")); + this->optionMenu->SetScrollbarColor(COLOR("#1A1919FF")); + this->installMenuItem = pu::ui::elm::MenuItem::New("main.menu.sd"_lang); + this->installMenuItem->SetColor(COLOR("#FFFFFFFF")); + this->installMenuItem->SetIcon("romfs:/images/icons/micro-sd.png"); + this->netInstallMenuItem = pu::ui::elm::MenuItem::New("main.menu.net"_lang); + this->netInstallMenuItem->SetColor(COLOR("#FFFFFFFF")); + this->netInstallMenuItem->SetIcon("romfs:/images/icons/cloud-download.png"); + this->usbInstallMenuItem = pu::ui::elm::MenuItem::New("main.menu.usb"_lang); + this->usbInstallMenuItem->SetColor(COLOR("#FFFFFFFF")); + this->usbInstallMenuItem->SetIcon("romfs:/images/icons/usb-port.png"); + this->HdInstallMenuItem = pu::ui::elm::MenuItem::New("main.menu.hdd"_lang); + this->HdInstallMenuItem->SetColor(COLOR("#FFFFFFFF")); + this->HdInstallMenuItem->SetIcon("romfs:/images/icons/usb-hd.png"); + this->settingsMenuItem = pu::ui::elm::MenuItem::New("main.menu.set"_lang); + this->settingsMenuItem->SetColor(COLOR("#FFFFFFFF")); + this->settingsMenuItem->SetIcon("romfs:/images/icons/settings.png"); + this->exitMenuItem = pu::ui::elm::MenuItem::New("main.menu.exit"_lang); + this->exitMenuItem->SetColor(COLOR("#FFFFFFFF")); + this->exitMenuItem->SetIcon("romfs:/images/icons/exit-run.png"); + if (inst::config::gayMode) { + if (std::filesystem::exists(inst::config::appDir + "/images/Main.png")) this->awooImage = Image::New(0, 0, inst::config::appDir + "/images/Main.png"); + else this->awooImage = Image::New(0, 0, "romfs:/images/Main.png"); + } + else{ + this->awooImage = Image::New(0, 0, "romfs:/images/Main.png"); + } + this->eggImage = Image::New(0, 0, ""); + this->Add(this->topRect); + this->Add(this->botRect); + this->Add(this->titleImage); + this->Add(this->butText); + this->optionMenu->AddItem(this->installMenuItem); + this->optionMenu->AddItem(this->netInstallMenuItem); + this->optionMenu->AddItem(this->usbInstallMenuItem); + this->optionMenu->AddItem(this->HdInstallMenuItem); + this->optionMenu->AddItem(this->settingsMenuItem); + this->optionMenu->AddItem(this->exitMenuItem); + this->Add(this->awooImage); + this->Add(this->eggImage); + this->awooImage->SetVisible(!inst::config::gayMode); + this->Add(this->optionMenu); + //this->AddThread(mainMenuThread); //fix later to prevent UI from freezing.... + } + + void MainPage::installMenuItem_Click() { + mainApp->sdinstPage->drawMenuItems(true, "sdmc:/"); + mainApp->sdinstPage->menu->SetSelectedIndex(0); + mainApp->LoadLayout(mainApp->sdinstPage); + } + + void MainPage::netInstallMenuItem_Click() { + if (inst::util::getIPAddress() == "1.0.0.127") { + inst::ui::mainApp->CreateShowDialog("main.net.title"_lang, "main.net.desc"_lang, {"common.ok"_lang}, true); + return; + } + mainApp->netinstPage->startNetwork(); + } + + void MainPage::usbInstallMenuItem_Click() { + if (!inst::config::usbAck) { + if (mainApp->CreateShowDialog("main.usb.warn.title"_lang, "main.usb.warn.desc"_lang, {"common.ok"_lang, "main.usb.warn.opt1"_lang}, false) == 1) { + inst::config::usbAck = true; + inst::config::setConfig(); + } + } + if (inst::util::getUsbState() == 5) mainApp->usbinstPage->startUsb(); + else mainApp->CreateShowDialog("main.usb.error.title"_lang, "main.usb.error.desc"_lang, {"common.ok"_lang}, false); + } + + void MainPage::HdInstallMenuItem_Click() { + if(nx::hdd::count() && nx::hdd::rootPath()) { + mainApp->HDinstPage->drawMenuItems(true, nx::hdd::rootPath()); + mainApp->HDinstPage->menu->SetSelectedIndex(0); + mainApp->LoadLayout(mainApp->HDinstPage); + } else { + inst::ui::mainApp->CreateShowDialog("main.hdd.title"_lang, "main.hdd.notfound"_lang, {"common.ok"_lang}, true); + } + } + + void MainPage::exitMenuItem_Click() { + mainApp->FadeOut(); + mainApp->Close(); + } + + void MainPage::settingsMenuItem_Click() { + mainApp->LoadLayout(mainApp->optionspage); + } + + void MainPage::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint touch_pos) { + if (((Down & HidNpadButton_Plus) || (Down & HidNpadButton_Minus) || (Down & HidNpadButton_B)) && mainApp->IsShown()) { + mainApp->FadeOut(); + mainApp->Close(); + } + if ((Down & HidNpadButton_A) /*|| (Up & HidGestureType_Touch)*/) { + switch (this->optionMenu->GetSelectedIndex()) { + case 0: + this->installMenuItem_Click(); + break; + case 1: + this->netInstallMenuItem_Click(); + break; + case 2: + MainPage::usbInstallMenuItem_Click(); + break; + case 3: + MainPage::HdInstallMenuItem_Click(); + break; + case 4: + MainPage::settingsMenuItem_Click(); + break; + case 5: + MainPage::exitMenuItem_Click(); + break; + default: + break; + } + } + + if (Down & HidNpadButton_X) { + this->awooImage->SetVisible(false); + this->eggImage->SetVisible(true); + } + if (Up & HidNpadButton_A) { + this->eggImage->SetVisible(false); + if (!inst::config::gayMode) this->awooImage->SetVisible(true); + } + if (Down & HidNpadButton_Y) { + mathstuff(); + } + + } +} \ No newline at end of file diff --git a/source/ui/netInstPage.cpp b/source/ui/netInstPage.cpp new file mode 100644 index 0000000..a42ba85 --- /dev/null +++ b/source/ui/netInstPage.cpp @@ -0,0 +1,193 @@ +#include +#include +#include "ui/MainApplication.hpp" +#include "ui/mainPage.hpp" +#include "ui/netInstPage.hpp" +#include "util/util.hpp" +#include "util/config.hpp" +#include "util/curl.hpp" +#include "util/lang.hpp" +#include "netInstall.hpp" + +#define COLOR(hex) pu::ui::Color::FromHex(hex) + +namespace inst::ui { + extern MainApplication *mainApp; + + std::string lastUrl = "https://"; + std::string lastFileID = ""; + std::string sourceString = ""; + + netInstPage::netInstPage() : Layout::Layout() { + this->infoRect = Rectangle::New(0, 95, 1280, 60, COLOR("#00000080")); + this->SetBackgroundColor(COLOR("#000000FF")); + this->topRect = Rectangle::New(0, 0, 1280, 94, COLOR("#000000FF")); + this->botRect = Rectangle::New(0, 659, 1280, 61, COLOR("#000000FF")); + + if (inst::config::gayMode) { + if (std::filesystem::exists(inst::config::appDir + "/images/Net.png")) this->titleImage = Image::New(0, 0, (inst::config::appDir + "/images/Net.png")); + else this->titleImage = Image::New(0, 0, "romfs:/images/Net.png"); + if (std::filesystem::exists(inst::config::appDir + "/images/Background.png")) this->SetBackgroundImage(inst::config::appDir + "/images/Background.png"); + else this->SetBackgroundImage("romfs:/images/Background.png"); + //this->appVersionText = TextBlock::New(0, 0, "", 0); + this->appVersionText = TextBlock::New(0, 0, ""); + } + else { + this->SetBackgroundImage("romfs:/images/Background.png"); + this->titleImage = Image::New(0, 0, "romfs:/images/Net.png"); + //this->appVersionText = TextBlock::New(0, 0, "", 0); + this->appVersionText = TextBlock::New(0, 0, ""); + } + this->appVersionText->SetColor(COLOR("#FFFFFFFF")); + //this->pageInfoText = TextBlock::New(10, 109, "", 30); + this->pageInfoText = TextBlock::New(10, 109, ""); + 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)); + this->menu->SetItemsFocusColor(COLOR("#4f4f4d33")); + this->menu->SetScrollbarColor(COLOR("#1A1919FF")); + this->infoImage = Image::New(453, 292, "romfs:/images/icons/lan-connection-waiting.png"); + this->Add(this->topRect); + this->Add(this->infoRect); + this->Add(this->botRect); + this->Add(this->titleImage); + this->Add(this->appVersionText); + this->Add(this->butText); + this->Add(this->pageInfoText); + this->Add(this->menu); + this->Add(this->infoImage); + } + + void netInstPage::drawMenuItems(bool clearItems) { + if (clearItems) this->selectedUrls = {}; + if (clearItems) this->alternativeNames = {}; + this->menu->ClearItems(); + for (auto& url: this->ourUrls) { + std::string itm = inst::util::shortenString(inst::util::formatUrlString(url), 56, true); + auto ourEntry = pu::ui::elm::MenuItem::New(itm); + ourEntry->SetColor(COLOR("#FFFFFFFF")); + ourEntry->SetIcon("romfs:/images/icons/checkbox-blank-outline.png"); + for (long unsigned int i = 0; i < this->selectedUrls.size(); i++) { + if (this->selectedUrls[i] == url) { + ourEntry->SetIcon("romfs:/images/icons/check-box-outline.png"); + } + } + this->menu->AddItem(ourEntry); + } + } + + void netInstPage::selectTitle(int selectedIndex) { + if (this->menu->GetItems()[selectedIndex]->GetIconPath() == "romfs:/images/icons/check-box-outline.png") { + for (long unsigned int i = 0; i < this->selectedUrls.size(); i++) { + if (this->selectedUrls[i] == this->ourUrls[selectedIndex]) this->selectedUrls.erase(this->selectedUrls.begin() + i); + } + } else this->selectedUrls.push_back(this->ourUrls[selectedIndex]); + this->drawMenuItems(false); + } + + void netInstPage::startNetwork() { + this->butText->SetText("inst.net.buttons"_lang); + 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") { + 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); + if (keyboardResult.size() > 0) { + lastUrl = keyboardResult; + if (inst::util::formatUrlString(keyboardResult) == "" || keyboardResult == "https://" || keyboardResult == "http://") { + mainApp->CreateShowDialog("inst.net.url.invalid"_lang, "", {"common.ok"_lang}, false); + break; + } + sourceString = "inst.net.url.source_string"_lang; + this->selectedUrls = {keyboardResult}; + this->startInstall(true); + return; + } + break; + case 1: + keyboardResult = inst::util::softwareKeyboard("inst.net.gdrive.hint"_lang, lastFileID, 50); + if (keyboardResult.size() > 0) { + lastFileID = keyboardResult; + std::string fileName = inst::util::getDriveFileName(keyboardResult); + if (fileName.size() > 0) this->alternativeNames = {fileName}; + else this->alternativeNames = {"inst.net.gdrive.alt_name"_lang}; + sourceString = "inst.net.gdrive.source_string"_lang; + this->selectedUrls = {"https://www.googleapis.com/drive/v3/files/" + keyboardResult + "?key=" + inst::config::gAuthKey + "&alt=media"}; + this->startInstall(true); + return; + } + break; + } + this->startNetwork(); + return; + } 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; + this->pageInfoText->SetText("inst.net.top_info"_lang); + this->butText->SetText("inst.net.buttons1"_lang); + this->drawMenuItems(true); + this->menu->SetSelectedIndex(0); + mainApp->CallForRender(); + this->infoImage->SetVisible(false); + this->menu->SetVisible(true); + } + return; + } + + void netInstPage::startInstall(bool urlMode) { + int dialogResult = -1; + if (this->selectedUrls.size() == 1) { + std::string ourUrlString; + if (this->alternativeNames.size() > 0) ourUrlString = inst::util::shortenString(this->alternativeNames[0], 32, true); + else ourUrlString = inst::util::shortenString(inst::util::formatUrlString(this->selectedUrls[0]), 32, true); + dialogResult = mainApp->CreateShowDialog("inst.target.desc0"_lang + ourUrlString + "inst.target.desc1"_lang, "common.cancel_desc"_lang, {"inst.target.opt0"_lang, "inst.target.opt1"_lang}, false); + } else dialogResult = mainApp->CreateShowDialog("inst.target.desc00"_lang + std::to_string(this->selectedUrls.size()) + "inst.target.desc01"_lang, "common.cancel_desc"_lang, {"inst.target.opt0"_lang, "inst.target.opt1"_lang}, false); + if (dialogResult == -1 && !urlMode) return; + else if (dialogResult == -1 && urlMode) { + this->startNetwork(); + return; + } + netInstStuff::installTitleNet(this->selectedUrls, dialogResult, this->alternativeNames, sourceString); + return; + } + + void netInstPage::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint touch_pos) { + if (Down & HidNpadButton_B) { + mainApp->LoadLayout(mainApp->mainPage); + } + if ((Down & HidNpadButton_A) /*|| (Up & HidGestureType_Touch)*/) { + this->selectTitle(this->menu->GetSelectedIndex()); + if (this->menu->GetItems().size() == 1 && this->selectedUrls.size() == 1) { + this->startInstall(false); + } + } + if ((Down & HidNpadButton_Y)) { + if (this->selectedUrls.size() == this->menu->GetItems().size()) this->drawMenuItems(true); + else { + for (long unsigned int i = 0; i < this->menu->GetItems().size(); i++) { + if (this->menu->GetItems()[i]->GetIconPath() == "romfs:/images/icons/check-box-outline.png") continue; + else this->selectTitle(i); + } + this->drawMenuItems(false); + } + } + if (Down & HidNpadButton_Plus) { + if (this->selectedUrls.size() == 0) { + this->selectTitle(this->menu->GetSelectedIndex()); + this->startInstall(false); + return; + } + this->startInstall(false); + } + } +} \ No newline at end of file diff --git a/source/ui/optionsPage.cpp b/source/ui/optionsPage.cpp new file mode 100644 index 0000000..b0751b7 --- /dev/null +++ b/source/ui/optionsPage.cpp @@ -0,0 +1,305 @@ +#include +#include +#include "ui/MainApplication.hpp" +#include "ui/mainPage.hpp" +#include "ui/instPage.hpp" +#include "ui/optionsPage.hpp" +#include "util/util.hpp" +#include "util/config.hpp" +#include "util/curl.hpp" +#include "util/unzip.hpp" +#include "util/lang.hpp" +#include "ui/instPage.hpp" +#include "sigInstall.hpp" + +#define COLOR(hex) pu::ui::Color::FromHex(hex) + + +namespace inst::ui { + extern MainApplication *mainApp; + + std::vector languageStrings = {"English", "日本語", "Français", "Deutsch", "Italiano", "Русский"}; + + optionsPage::optionsPage() : Layout::Layout() { + this->infoRect = Rectangle::New(0, 95, 1280, 60, COLOR("#00000080")); + this->SetBackgroundColor(COLOR("#000000FF")); + this->topRect = Rectangle::New(0, 0, 1280, 94, COLOR("#000000FF")); + this->botRect = Rectangle::New(0, 659, 1280, 61, COLOR("#000000FF")); + + if (inst::config::gayMode) { + if (std::filesystem::exists(inst::config::appDir + "/images/Settings.png")) this->titleImage = Image::New(0, 0, (inst::config::appDir + "/images/Settings.png")); + else this->titleImage = Image::New(0, 0, "romfs:/images/Settings.png"); + if (std::filesystem::exists(inst::config::appDir + "/images/Background.png")) this->SetBackgroundImage(inst::config::appDir + "/images/Background.png"); + else this->SetBackgroundImage("romfs:/images/Background.png"); + //this->appVersionText = TextBlock::New(1210, 680, "v" + inst::config::appVersion, 20); + this->appVersionText = TextBlock::New(1210, 680, "v" + inst::config::appVersion); + } + else { + this->SetBackgroundImage("romfs:/images/Background.png"); + this->titleImage = Image::New(0, 0, "romfs:/images/Settings.png"); + //this->appVersionText = TextBlock::New(1210, 680, "v" + inst::config::appVersion, 20); + this->appVersionText = TextBlock::New(1210, 680, "v" + inst::config::appVersion); + } + this->appVersionText->SetColor(COLOR("#FFFFFFFF")); + //this->pageInfoText = TextBlock::New(10, 109, "options.title"_lang, 30); + this->pageInfoText = TextBlock::New(10, 109, "options.title"_lang); + this->pageInfoText->SetColor(COLOR("#FFFFFFFF")); + //this->butText = TextBlock::New(10, 678, "options.buttons"_lang, 24); + this->butText = TextBlock::New(10, 678, "options.buttons"_lang); + this->butText->SetColor(COLOR("#FFFFFFFF")); + this->menu = pu::ui::elm::Menu::New(0, 156, 1280, COLOR("#FFFFFF00"), COLOR("#4f4f4d33"), 84, (506 / 84)); + this->menu->SetItemsFocusColor(COLOR("#4f4f4d33")); + this->menu->SetScrollbarColor(COLOR("#1A1919FF")); + this->Add(this->topRect); + this->Add(this->infoRect); + this->Add(this->botRect); + this->Add(this->titleImage); + this->Add(this->appVersionText); + this->Add(this->butText); + this->Add(this->pageInfoText); + this->setMenuText(); + this->Add(this->menu); + } + + void optionsPage::askToUpdate(std::vector updateInfo) { + if (!mainApp->CreateShowDialog("options.update.title"_lang, "options.update.desc0"_lang + updateInfo[0] + "options.update.desc1"_lang, {"options.update.opt0"_lang, "common.cancel"_lang}, false)) { + inst::ui::instPage::loadInstallScreen(); + inst::ui::instPage::setTopInstInfoText("options.update.top_info"_lang + updateInfo[0]); + inst::ui::instPage::setInstBarPerc(0); + inst::ui::instPage::setInstInfoText("options.update.bot_info"_lang + updateInfo[0]); + try { + std::string downloadName = inst::config::appDir + "/temp_download.zip"; + inst::curl::downloadFile(updateInfo[1], downloadName.c_str(), 0, true); + romfsExit(); + inst::ui::instPage::setInstInfoText("options.update.bot_info2"_lang + updateInfo[0]); + inst::zip::extractFile(downloadName, "sdmc:/"); + std::filesystem::remove(downloadName); + mainApp->CreateShowDialog("options.update.complete"_lang, "options.update.end_desc"_lang, {"common.ok"_lang}, false); + } catch (...) { + mainApp->CreateShowDialog("options.update.failed"_lang, "options.update.end_desc"_lang, {"common.ok"_lang}, false); + } + mainApp->FadeOut(); + mainApp->Close(); + } + return; + } + + std::string optionsPage::getMenuOptionIcon(bool ourBool) { + if(ourBool) return "romfs:/images/icons/check-box-outline.png"; + else return "romfs:/images/icons/checkbox-blank-outline.png"; + } + + std::string optionsPage::getMenuLanguage(int ourLangCode) { + switch (ourLangCode) { + case 1: + case 12: + return languageStrings[0]; + case 0: + return languageStrings[1]; + case 2: + case 13: + return languageStrings[2]; + case 3: + return languageStrings[3]; + case 4: + return languageStrings[4]; + case 10: + return languageStrings[5]; + default: + return "options.language.system_language"_lang; + } + } + + void sigPatchesMenuItem_Click() { + sig::installSigPatches(); + } + + void thememessage() { + //inst::ui::mainApp->CreateShowDialog("main.theme.title"_lang, "main.theme.desc"_lang, {"common.ok"_lang}, true); + int ourResult = inst::ui::mainApp->CreateShowDialog("main.theme.title"_lang, "main.theme.desc"_lang, {"common.ok"_lang, "common.cancel"_lang}, true); + if (ourResult != 0) { + // + } + else{ + mainApp->FadeOut(); + mainApp->Close(); + } + } + + void optionsPage::setMenuText() { + this->menu->ClearItems(); + auto ignoreFirmOption = pu::ui::elm::MenuItem::New("options.menu_items.ignore_firm"_lang); + ignoreFirmOption->SetColor(COLOR("#FFFFFFFF")); + ignoreFirmOption->SetIcon(this->getMenuOptionIcon(inst::config::ignoreReqVers)); + this->menu->AddItem(ignoreFirmOption); + auto validateOption = pu::ui::elm::MenuItem::New("options.menu_items.nca_verify"_lang); + validateOption->SetColor(COLOR("#FFFFFFFF")); + validateOption->SetIcon(this->getMenuOptionIcon(inst::config::validateNCAs)); + this->menu->AddItem(validateOption); + auto overclockOption = pu::ui::elm::MenuItem::New("options.menu_items.boost_mode"_lang); + overclockOption->SetColor(COLOR("#FFFFFFFF")); + overclockOption->SetIcon(this->getMenuOptionIcon(inst::config::overClock)); + this->menu->AddItem(overclockOption); + auto deletePromptOption = pu::ui::elm::MenuItem::New("options.menu_items.ask_delete"_lang); + deletePromptOption->SetColor(COLOR("#FFFFFFFF")); + deletePromptOption->SetIcon(this->getMenuOptionIcon(inst::config::deletePrompt)); + this->menu->AddItem(deletePromptOption); + auto autoUpdateOption = pu::ui::elm::MenuItem::New("options.menu_items.auto_update"_lang); + autoUpdateOption->SetColor(COLOR("#FFFFFFFF")); + autoUpdateOption->SetIcon(this->getMenuOptionIcon(inst::config::autoUpdate)); + this->menu->AddItem(autoUpdateOption); + + auto gayModeOption = pu::ui::elm::MenuItem::New("options.menu_items.gay_option"_lang); + gayModeOption->SetColor(COLOR("#FFFFFFFF")); + gayModeOption->SetIcon(this->getMenuOptionIcon(inst::config::gayMode)); + this->menu->AddItem(gayModeOption); + + auto useSoundOption = pu::ui::elm::MenuItem::New("options.menu_items.useSound"_lang); + useSoundOption->SetColor(COLOR("#FFFFFFFF")); + useSoundOption->SetIcon(this->getMenuOptionIcon(inst::config::useSound)); + this->menu->AddItem(useSoundOption); + + auto SigPatch = pu::ui::elm::MenuItem::New("main.menu.sig"_lang); + SigPatch->SetColor(COLOR("#FFFFFFFF")); + this->menu->AddItem(SigPatch); + + auto sigPatchesUrlOption = pu::ui::elm::MenuItem::New("options.menu_items.sig_url"_lang + inst::util::shortenString(inst::config::sigPatchesUrl, 42, false)); + sigPatchesUrlOption->SetColor(COLOR("#FFFFFFFF")); + this->menu->AddItem(sigPatchesUrlOption); + auto languageOption = pu::ui::elm::MenuItem::New("options.menu_items.language"_lang + this->getMenuLanguage(inst::config::languageSetting)); + languageOption->SetColor(COLOR("#FFFFFFFF")); + this->menu->AddItem(languageOption); + auto updateOption = pu::ui::elm::MenuItem::New("options.menu_items.check_update"_lang); + updateOption->SetColor(COLOR("#FFFFFFFF")); + this->menu->AddItem(updateOption); + auto creditsOption = pu::ui::elm::MenuItem::New("options.menu_items.credits"_lang); + creditsOption->SetColor(COLOR("#FFFFFFFF")); + this->menu->AddItem(creditsOption); + } + + void optionsPage::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint touch_pos) { + if (Down & HidNpadButton_B) { + mainApp->LoadLayout(mainApp->mainPage); + } + if ((Down & HidNpadButton_A)) { + std::string keyboardResult; + int rc; + std::vector downloadUrl; + std::vector languageList; + switch (this->menu->GetSelectedIndex()) { + case 0: + inst::config::ignoreReqVers = !inst::config::ignoreReqVers; + inst::config::setConfig(); + this->setMenuText(); + break; + case 1: + if (inst::config::validateNCAs) { + if (inst::ui::mainApp->CreateShowDialog("options.nca_warn.title"_lang, "options.nca_warn.desc"_lang, {"common.cancel"_lang, "options.nca_warn.opt1"_lang}, false) == 1) inst::config::validateNCAs = false; + } else inst::config::validateNCAs = true; + inst::config::setConfig(); + this->setMenuText(); + break; + case 2: + inst::config::overClock = !inst::config::overClock; + inst::config::setConfig(); + this->setMenuText(); + break; + case 3: + inst::config::deletePrompt = !inst::config::deletePrompt; + inst::config::setConfig(); + this->setMenuText(); + break; + case 4: + inst::config::autoUpdate = !inst::config::autoUpdate; + inst::config::setConfig(); + this->setMenuText(); + break; + case 5: + if (inst::config::gayMode) { + inst::config::gayMode = false; + mainApp->mainPage->awooImage->SetVisible(false); + + } + else { + inst::config::gayMode = true; + mainApp->mainPage->awooImage->SetVisible(true); + } + this->setMenuText(); + thememessage(); + inst::config::setConfig(); + break; + + case 6: + if (inst::config::useSound) { + inst::config::useSound = false; + } + else { + inst::config::useSound = true; + } + this->setMenuText(); + inst::config::setConfig(); + break; + + case 7: + sigPatchesMenuItem_Click(); + break; + case 8: + keyboardResult = inst::util::softwareKeyboard("options.sig_hint"_lang, inst::config::sigPatchesUrl.c_str(), 500); + if (keyboardResult.size() > 0) { + inst::config::sigPatchesUrl = keyboardResult; + inst::config::setConfig(); + this->setMenuText(); + } + break; + case 9: + languageList = languageStrings; + languageList.push_back("options.language.system_language"_lang); + rc = inst::ui::mainApp->CreateShowDialog("options.language.title"_lang, "options.language.desc"_lang, languageList, false); + if (rc == -1) break; + switch(rc) { + case 0: + inst::config::languageSetting = 1; + break; + case 1: + inst::config::languageSetting = 0; + break; + case 2: + inst::config::languageSetting = 2; + break; + case 3: + inst::config::languageSetting = 3; + break; + case 4: + inst::config::languageSetting = 4; + break; + case 5: + inst::config::languageSetting = 10; + break; + default: + inst::config::languageSetting = 99; + } + inst::config::setConfig(); + mainApp->FadeOut(); + mainApp->Close(); + break; + case 10: + if (inst::util::getIPAddress() == "1.0.0.127") { + inst::ui::mainApp->CreateShowDialog("main.net.title"_lang, "main.net.desc"_lang, {"common.ok"_lang}, true); + break; + } + downloadUrl = inst::util::checkForAppUpdate(); + if (!downloadUrl.size()) { + mainApp->CreateShowDialog("options.update.title_check_fail"_lang, "options.update.desc_check_fail"_lang, {"common.ok"_lang}, false); + break; + } + this->askToUpdate(downloadUrl); + break; + case 11: + inst::ui::mainApp->CreateShowDialog("options.credits.title"_lang, "options.credits.desc"_lang, {"common.close"_lang}, true); + break; + default: + break; + } + } + } +} \ No newline at end of file diff --git a/source/ui/sdInstPage.cpp b/source/ui/sdInstPage.cpp new file mode 100644 index 0000000..15c105b --- /dev/null +++ b/source/ui/sdInstPage.cpp @@ -0,0 +1,179 @@ +#include +#include "ui/MainApplication.hpp" +#include "ui/mainPage.hpp" +#include "ui/sdInstPage.hpp" +#include "sdInstall.hpp" +#include "util/util.hpp" +#include "util/config.hpp" +#include "util/lang.hpp" + +#define COLOR(hex) pu::ui::Color::FromHex(hex) + +namespace inst::ui { + extern MainApplication *mainApp; + + sdInstPage::sdInstPage() : Layout::Layout() { + this->infoRect = Rectangle::New(0, 95, 1280, 60, COLOR("#00000080")); + this->SetBackgroundColor(COLOR("#000000FF")); + this->topRect = Rectangle::New(0, 0, 1280, 94, COLOR("#000000FF")); + this->botRect = Rectangle::New(0, 659, 1280, 61, COLOR("#000000FF")); + + if (inst::config::gayMode) { + if (std::filesystem::exists(inst::config::appDir + "/images/Sd.png")) this->titleImage = Image::New(0, 0, (inst::config::appDir + "/images/Sd.png")); + else this->titleImage = Image::New(0, 0, "romfs:/images/Sd.png"); + if (std::filesystem::exists(inst::config::appDir + "/images/Background.png")) this->SetBackgroundImage(inst::config::appDir + "/images/Background.png"); + else this->SetBackgroundImage("romfs:/images/Background.png"); + //this->appVersionText = TextBlock::New(0, 0, "", 0); + this->appVersionText = TextBlock::New(0, 0, ""); + } + else { + this->SetBackgroundImage("romfs:/images/Background.png"); + this->titleImage = Image::New(0, 0, "romfs:/images/Sd.png"); + //this->appVersionText = TextBlock::New(0, 0, "", 0); + this->appVersionText = TextBlock::New(0, 0, ""); + } + this->appVersionText->SetColor(COLOR("#FFFFFFFF")); + //this->pageInfoText = TextBlock::New(10, 109, "inst.sd.top_info"_lang, 30); + this->pageInfoText = TextBlock::New(10, 109, "inst.sd.top_info"_lang); + this->pageInfoText->SetColor(COLOR("#FFFFFFFF")); + //this->butText = TextBlock::New(10, 678, "inst.sd.buttons"_lang, 24); + this->butText = TextBlock::New(10, 678, "inst.sd.buttons"_lang); + this->butText->SetColor(COLOR("#FFFFFFFF")); + this->menu = pu::ui::elm::Menu::New(0, 156, 1280, COLOR("#FFFFFF00"), COLOR("#4f4f4d33"), 84, (506 / 84)); + this->menu->SetItemsFocusColor(COLOR("#4f4f4d33")); + this->menu->SetScrollbarColor(COLOR("#1A1919FF")); + this->Add(this->topRect); + this->Add(this->infoRect); + this->Add(this->botRect); + this->Add(this->titleImage); + this->Add(this->appVersionText); + this->Add(this->butText); + this->Add(this->pageInfoText); + this->Add(this->menu); + } + + void sdInstPage::drawMenuItems(bool clearItems, std::filesystem::path ourPath) { + if (clearItems) this->selectedTitles = {}; + this->currentDir = ourPath; + + auto pathStr = this->currentDir.string(); + if(pathStr.length()) + { + if(pathStr[pathStr.length() - 1] == ':') + { + this->currentDir = this->currentDir / ""; + } + } + + this->menu->ClearItems(); + try { + this->ourDirectories = util::getDirsAtPath(this->currentDir); + this->ourFiles = util::getDirectoryFiles(this->currentDir, {".nsp", ".nsz", ".xci", ".xcz"}); + } catch (std::exception& e) { + this->drawMenuItems(false, this->currentDir.parent_path()); + return; + } + + std::string itm = ".."; + auto ourEntry = pu::ui::elm::MenuItem::New(itm); + ourEntry->SetColor(COLOR("#FFFFFFFF")); + ourEntry->SetIcon("romfs:/images/icons/folder-upload.png"); + this->menu->AddItem(ourEntry); + + for (auto& file: this->ourDirectories) { + if (file == "..") break; + std::string itm = file.filename().string(); + auto ourEntry = pu::ui::elm::MenuItem::New(itm); + ourEntry->SetColor(COLOR("#FFFFFFFF")); + ourEntry->SetIcon("romfs:/images/icons/folder.png"); + this->menu->AddItem(ourEntry); + } + for (auto& file: this->ourFiles) { + std::string itm = file.filename().string(); + auto ourEntry = pu::ui::elm::MenuItem::New(itm); + ourEntry->SetColor(COLOR("#FFFFFFFF")); + ourEntry->SetIcon("romfs:/images/icons/checkbox-blank-outline.png"); + for (long unsigned int i = 0; i < this->selectedTitles.size(); i++) { + if (this->selectedTitles[i] == file) { + ourEntry->SetIcon("romfs:/images/icons/check-box-outline.png"); + } + } + this->menu->AddItem(ourEntry); + } + } + + void sdInstPage::followDirectory() { + int selectedIndex = this->menu->GetSelectedIndex(); + int dirListSize = this->ourDirectories.size(); + + dirListSize++; + selectedIndex--; + + if (selectedIndex < dirListSize) { + if (this->menu->GetItems()[this->menu->GetSelectedIndex()]->GetName() == ".." && this->menu->GetSelectedIndex() == 0) { + this->drawMenuItems(true, this->currentDir.parent_path()); + } else { + this->drawMenuItems(true, this->ourDirectories[selectedIndex]); + } + this->menu->SetSelectedIndex(0); + } + } + + void sdInstPage::selectNsp(int selectedIndex) { + int dirListSize = this->ourDirectories.size(); + dirListSize++; + + if (this->menu->GetItems()[selectedIndex]->GetIconPath() == "romfs:/images/icons/check-box-outline.png") { + for (long unsigned int i = 0; i < this->selectedTitles.size(); i++) { + if (this->selectedTitles[i] == this->ourFiles[selectedIndex - dirListSize]) this->selectedTitles.erase(this->selectedTitles.begin() + i); + } + } else if (this->menu->GetItems()[selectedIndex]->GetIconPath() == "romfs:/images/icons/checkbox-blank-outline.png") this->selectedTitles.push_back(this->ourFiles[selectedIndex - dirListSize]); + else { + this->followDirectory(); + return; + } + this->drawMenuItems(false, currentDir); + } + + void sdInstPage::startInstall() { + int dialogResult = -1; + if (this->selectedTitles.size() == 1) { + dialogResult = mainApp->CreateShowDialog("inst.target.desc0"_lang + inst::util::shortenString(std::filesystem::path(this->selectedTitles[0]).filename().string(), 32, true) + "inst.target.desc1"_lang, "common.cancel_desc"_lang, {"inst.target.opt0"_lang, "inst.target.opt1"_lang}, false); + } else dialogResult = mainApp->CreateShowDialog("inst.target.desc00"_lang + std::to_string(this->selectedTitles.size()) + "inst.target.desc01"_lang, "common.cancel_desc"_lang, {"inst.target.opt0"_lang, "inst.target.opt1"_lang}, false); + if (dialogResult == -1) return; + nspInstStuff::installNspFromFile(this->selectedTitles, dialogResult); + } + + void sdInstPage::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint touch_pos) { + if (Down & HidNpadButton_B) { + mainApp->LoadLayout(mainApp->mainPage); + } + if ((Down & HidNpadButton_A) /*|| (Up & HidGestureType_Touch)*/) { + this->selectNsp(this->menu->GetSelectedIndex()); + if (this->ourFiles.size() == 1 && this->selectedTitles.size() == 1) { + this->startInstall(); + } + } + if ((Down & HidNpadButton_Y)) { + if (this->selectedTitles.size() == this->ourFiles.size()) this->drawMenuItems(true, currentDir); + else { + int topDir = 0; + topDir++; + for (long unsigned int i = this->ourDirectories.size() + topDir; i < this->menu->GetItems().size(); i++) { + if (this->menu->GetItems()[i]->GetIconPath() == "romfs:/images/icons/check-box-outline.png") continue; + else this->selectNsp(i); + } + this->drawMenuItems(false, currentDir); + } + } + if ((Down & HidNpadButton_X)) { + inst::ui::mainApp->CreateShowDialog("inst.sd.help.title"_lang, "inst.sd.help.desc"_lang, {"common.ok"_lang}, true); + } + if (Down & HidNpadButton_Plus) { + if (this->selectedTitles.size() == 0 && this->menu->GetItems()[this->menu->GetSelectedIndex()]->GetIconPath() == "romfs:/images/icons/checkbox-blank-outline.png") { + this->selectNsp(this->menu->GetSelectedIndex()); + } + if (this->selectedTitles.size() > 0) this->startInstall(); + } + } +} \ No newline at end of file diff --git a/source/ui/usbInstPage.cpp b/source/ui/usbInstPage.cpp new file mode 100644 index 0000000..6497e49 --- /dev/null +++ b/source/ui/usbInstPage.cpp @@ -0,0 +1,145 @@ +#include "ui/usbInstPage.hpp" +#include "ui/MainApplication.hpp" +#include "util/util.hpp" +#include "util/config.hpp" +#include "util/lang.hpp" +#include "usbInstall.hpp" + + +#define COLOR(hex) pu::ui::Color::FromHex(hex) + +namespace inst::ui { + extern MainApplication *mainApp; + + usbInstPage::usbInstPage() : Layout::Layout() { + this->infoRect = Rectangle::New(0, 95, 1280, 60, COLOR("#00000080")); + this->SetBackgroundColor(COLOR("#000000FF")); + this->topRect = Rectangle::New(0, 0, 1280, 94, COLOR("#000000FF")); + this->botRect = Rectangle::New(0, 659, 1280, 61, COLOR("#000000FF")); + + if (inst::config::gayMode) { + if (std::filesystem::exists(inst::config::appDir + "/images/Usb.png")) this->titleImage = Image::New(0, 0, (inst::config::appDir + "/images/Usb.png")); + else this->titleImage = Image::New(0, 0, "romfs:/images/Usb.png"); + if (std::filesystem::exists(inst::config::appDir + "/images/Background.png")) this->SetBackgroundImage(inst::config::appDir + "/images/Background.png"); + else this->SetBackgroundImage("romfs:/images/Background.png"); + //this->appVersionText = TextBlock::New(0, 0, "", 0); + this->appVersionText = TextBlock::New(0, 0, ""); + } + else { + this->SetBackgroundImage("romfs:/images/Background.png"); + this->titleImage = Image::New(0, 0, "romfs:/images/Usb.png"); + //this->appVersionText = TextBlock::New(0, 0, "", 0); + this->appVersionText = TextBlock::New(0, 0, ""); + } + this->appVersionText->SetColor(COLOR("#FFFFFFFF")); + //this->pageInfoText = TextBlock::New(10, 109, "", 30); + this->pageInfoText = TextBlock::New(10, 109, ""); + 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)); + this->menu->SetItemsFocusColor(COLOR("#4f4f4d33")); + this->menu->SetScrollbarColor(COLOR("#1A1919FF")); + this->infoImage = Image::New(460, 332, "romfs:/images/icons/usb-connection-waiting.png"); + this->Add(this->topRect); + this->Add(this->infoRect); + this->Add(this->botRect); + this->Add(this->titleImage); + this->Add(this->appVersionText); + this->Add(this->butText); + this->Add(this->pageInfoText); + this->Add(this->menu); + this->Add(this->infoImage); + } + + void usbInstPage::drawMenuItems(bool clearItems) { + if (clearItems) this->selectedTitles = {}; + this->menu->ClearItems(); + for (auto& url: this->ourTitles) { + std::string itm = inst::util::shortenString(inst::util::formatUrlString(url), 56, true); + auto ourEntry = pu::ui::elm::MenuItem::New(itm); + ourEntry->SetColor(COLOR("#FFFFFFFF")); + ourEntry->SetIcon("romfs:/images/icons/checkbox-blank-outline.png"); + for (long unsigned int i = 0; i < this->selectedTitles.size(); i++) { + if (this->selectedTitles[i] == url) { + ourEntry->SetIcon("romfs:/images/icons/check-box-outline.png"); + } + } + this->menu->AddItem(ourEntry); + } + } + + void usbInstPage::selectTitle(int selectedIndex) { + if (this->menu->GetItems()[selectedIndex]->GetIconPath() == "romfs:/images/icons/check-box-outline.png") { + for (long unsigned int i = 0; i < this->selectedTitles.size(); i++) { + if (this->selectedTitles[i] == this->ourTitles[selectedIndex]) this->selectedTitles.erase(this->selectedTitles.begin() + i); + } + } else this->selectedTitles.push_back(this->ourTitles[selectedIndex]); + this->drawMenuItems(false); + } + + void usbInstPage::startUsb() { + this->pageInfoText->SetText("inst.usb.top_info"_lang); + this->butText->SetText("inst.usb.buttons"_lang); + this->menu->SetVisible(false); + this->menu->ClearItems(); + this->infoImage->SetVisible(true); + mainApp->LoadLayout(mainApp->usbinstPage); + mainApp->CallForRender(); + this->ourTitles = usbInstStuff::OnSelected(); + if (!this->ourTitles.size()) { + mainApp->LoadLayout(mainApp->mainPage); + return; + } else { + mainApp->CallForRender(); // If we re-render a few times during this process the main screen won't flicker + this->pageInfoText->SetText("inst.usb.top_info2"_lang); + this->butText->SetText("inst.usb.buttons2"_lang); + this->drawMenuItems(true); + this->menu->SetSelectedIndex(0); + mainApp->CallForRender(); + this->infoImage->SetVisible(false); + this->menu->SetVisible(true); + } + return; + } + + void usbInstPage::startInstall() { + int dialogResult = -1; + if (this->selectedTitles.size() == 1) dialogResult = mainApp->CreateShowDialog("inst.target.desc0"_lang + inst::util::shortenString(inst::util::formatUrlString(this->selectedTitles[0]), 32, true) + "inst.target.desc1"_lang, "common.cancel_desc"_lang, {"inst.target.opt0"_lang, "inst.target.opt1"_lang}, false); + else dialogResult = mainApp->CreateShowDialog("inst.target.desc00"_lang + std::to_string(this->selectedTitles.size()) + "inst.target.desc01"_lang, "common.cancel_desc"_lang, {"inst.target.opt0"_lang, "inst.target.opt1"_lang}, false); + if (dialogResult == -1) return; + usbInstStuff::installTitleUsb(this->selectedTitles, dialogResult); + return; + } + + void usbInstPage::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint touch_pos) { + if (Down & HidNpadButton_B) { + mainApp->LoadLayout(mainApp->mainPage); + } + if ((Down & HidNpadButton_A) /*|| (Up & HidGestureType_Touch)*/) { + this->selectTitle(this->menu->GetSelectedIndex()); + if (this->menu->GetItems().size() == 1 && this->selectedTitles.size() == 1) { + this->startInstall(); + } + } + if ((Down & HidNpadButton_Y)) { + if (this->selectedTitles.size() == this->menu->GetItems().size()) this->drawMenuItems(true); + else { + for (long unsigned int i = 0; i < this->menu->GetItems().size(); i++) { + if (this->menu->GetItems()[i]->GetIconPath() == "romfs:/images/icons/check-box-outline.png") continue; + else this->selectTitle(i); + } + this->drawMenuItems(false); + } + } + if (Down & HidNpadButton_Plus) { + if (this->selectedTitles.size() == 0) { + this->selectTitle(this->menu->GetSelectedIndex()); + this->startInstall(); + return; + } + this->startInstall(); + } + } +} \ No newline at end of file diff --git a/source/usbInstall.cpp b/source/usbInstall.cpp new file mode 100644 index 0000000..2d644fa --- /dev/null +++ b/source/usbInstall.cpp @@ -0,0 +1,193 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include "usbInstall.hpp" +#include "install/usb_nsp.hpp" +#include "install/install_nsp.hpp" +#include "install/usb_xci.hpp" +#include "install/install_xci.hpp" +#include "util/error.hpp" +#include "util/usb_util.hpp" +#include "util/util.hpp" +#include "util/config.hpp" +#include "util/lang.hpp" +#include "ui/MainApplication.hpp" +#include "ui/usbInstPage.hpp" +#include "ui/instPage.hpp" + +namespace inst::ui { + extern MainApplication *mainApp; +} + +namespace usbInstStuff { + struct TUSHeader + { + u32 magic; // TUL0 (Tinfoil Usb List 0) + u32 titleListSize; + u64 padding; + } PACKED; + + int bufferData(void* buf, size_t size, u64 timeout = 5000000000) + { + u8* tempBuffer = (u8*)memalign(0x1000, size); + if (tin::util::USBRead(tempBuffer, size, timeout) == 0) return 0; + memcpy(buf, tempBuffer, size); + free(tempBuffer); + return size; + } + + std::vector OnSelected() { + TUSHeader header; + while(true) { + if (bufferData(&header, sizeof(TUSHeader), 500000000) != 0) break; + + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + PadState pad; + padInitializeDefault(&pad); + hidInitializeTouchScreen(); + u64 kDown = padGetButtonsDown(&pad); + + if (kDown & HidNpadButton_B) return {}; + if (kDown & HidNpadButton_X) inst::ui::mainApp->CreateShowDialog("inst.usb.help.title"_lang, "inst.usb.help.desc"_lang, {"common.ok"_lang}, true); + if (inst::util::getUsbState() != 5) return {}; + } + + if (header.magic != 0x304C5554) return {}; + + std::vector titleNames; + char* titleNameBuffer = (char*)memalign(0x1000, header.titleListSize + 1); + memset(titleNameBuffer, 0, header.titleListSize + 1); + + tin::util::USBRead(titleNameBuffer, header.titleListSize, 10000000000); + + // Split the string up into individual title names + std::stringstream titleNamesStream(titleNameBuffer); + std::string segment; + while (std::getline(titleNamesStream, segment, '\n')) titleNames.push_back(segment); + free(titleNameBuffer); + std::sort(titleNames.begin(), titleNames.end(), inst::util::ignoreCaseCompare); + + return titleNames; + } + + void installTitleUsb(std::vector ourTitleList, int ourStorage) + { + inst::util::initInstallServices(); + inst::ui::instPage::loadInstallScreen(); + bool nspInstalled = true; + NcmStorageId m_destStorageId = NcmStorageId_SdCard; + + if (ourStorage) m_destStorageId = NcmStorageId_BuiltInUser; + unsigned int fileItr; + + std::vector fileNames; + for (long unsigned int i = 0; i < ourTitleList.size(); i++) { + fileNames.push_back(inst::util::shortenString(inst::util::formatUrlString(ourTitleList[i]), 40, true)); + } + + std::vector previousClockValues; + if (inst::config::overClock) { + previousClockValues.push_back(inst::util::setClockSpeed(0, 1785000000)[0]); + previousClockValues.push_back(inst::util::setClockSpeed(1, 76800000)[0]); + previousClockValues.push_back(inst::util::setClockSpeed(2, 1600000000)[0]); + } + + try { + for (fileItr = 0; fileItr < ourTitleList.size(); fileItr++) { + inst::ui::instPage::setTopInstInfoText("inst.info_page.top_info0"_lang + fileNames[fileItr] + "inst.usb.source_string"_lang); + std::unique_ptr installTask; + + if (ourTitleList[fileItr].compare(ourTitleList[fileItr].size() - 3, 2, "xc") == 0) { + auto usbXCI = std::make_shared(ourTitleList[fileItr]); + installTask = std::make_unique(m_destStorageId, inst::config::ignoreReqVers, usbXCI); + } else { + auto usbNSP = std::make_shared(ourTitleList[fileItr]); + installTask = std::make_unique(m_destStorageId, inst::config::ignoreReqVers, usbNSP); + } + + LOG_DEBUG("%s\n", "Preparing installation"); + inst::ui::instPage::setInstInfoText("inst.info_page.preparing"_lang); + inst::ui::instPage::setInstBarPerc(0); + installTask->Prepare(); + + installTask->Begin(); + } + } + catch (std::exception& e) { + LOG_DEBUG("Failed to install"); + LOG_DEBUG("%s", e.what()); + fprintf(stdout, "%s", e.what()); + inst::ui::instPage::setInstInfoText("inst.info_page.failed"_lang + fileNames[fileItr]); + inst::ui::instPage::setInstBarPerc(0); + std::string audioPath = ""; + if (std::filesystem::exists(inst::config::appDir + "/sounds/OHNO.WAV")) { + audioPath = (inst::config::appDir + "/sounds/OHNO.WAV"); + } + else { + audioPath = "romfs:/audio/bark.wav"; + } + std::thread audioThread(inst::util::playAudio,audioPath); + inst::ui::mainApp->CreateShowDialog("inst.info_page.failed"_lang + fileNames[fileItr] + "!", "inst.info_page.failed_desc"_lang + "\n\n" + (std::string)e.what(), {"common.ok"_lang}, true); + audioThread.join(); + nspInstalled = false; + } + + if (previousClockValues.size() > 0) { + inst::util::setClockSpeed(0, previousClockValues[0]); + inst::util::setClockSpeed(1, previousClockValues[1]); + inst::util::setClockSpeed(2, previousClockValues[2]); + } + + if(nspInstalled) { + tin::util::USBCmdManager::SendExitCmd(); + inst::ui::instPage::setInstInfoText("inst.info_page.complete"_lang); + inst::ui::instPage::setInstBarPerc(100); + std::string audioPath = ""; + + if (inst::config::useSound) { + if (std::filesystem::exists(inst::config::appDir + "/sounds/YIPPEE.WAV")) { + audioPath = (inst::config::appDir + "/sounds/YIPPEE.WAV"); + } + else { + audioPath = "romfs:/audio/ameizing.mp3"; + } + std::thread audioThread(inst::util::playAudio,audioPath); + + if (ourTitleList.size() > 1) inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.info_page.desc0"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + else inst::ui::mainApp->CreateShowDialog(fileNames[0] + "inst.info_page.desc1"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + audioThread.join(); + } + else{ + if (ourTitleList.size() > 1) inst::ui::mainApp->CreateShowDialog(std::to_string(ourTitleList.size()) + "inst.info_page.desc0"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + else inst::ui::mainApp->CreateShowDialog(fileNames[0] + "inst.info_page.desc1"_lang, Language::GetRandomMsg(), {"common.ok"_lang}, true); + } + } + + LOG_DEBUG("Done"); + inst::ui::instPage::loadMainMenu(); + inst::util::deinitInstallServices(); + return; + } +} \ No newline at end of file diff --git a/source/util/config.cpp b/source/util/config.cpp new file mode 100644 index 0000000..644e88b --- /dev/null +++ b/source/util/config.cpp @@ -0,0 +1,71 @@ +#include +#include +#include "util/config.hpp" +#include "util/json.hpp" + +namespace inst::config { + std::string gAuthKey; + std::string sigPatchesUrl; + std::vector updateInfo; + int languageSetting; + bool autoUpdate; + bool deletePrompt; + bool ignoreReqVers; + bool overClock; + bool gayMode; + bool useSound; + bool usbAck; + bool validateNCAs; + + void setConfig() { + nlohmann::json j = { + {"autoUpdate", autoUpdate}, + {"deletePrompt", deletePrompt}, + {"gAuthKey", gAuthKey}, + {"gayMode", gayMode}, + {"useSound", useSound}, + {"ignoreReqVers", ignoreReqVers}, + {"languageSetting", languageSetting}, + {"overClock", overClock}, + {"sigPatchesUrl", sigPatchesUrl}, + {"usbAck", usbAck}, + {"validateNCAs", validateNCAs} + }; + std::ofstream file(inst::config::configPath); + file << std::setw(4) << j << std::endl; + } + + void parseConfig() { + try { + std::ifstream file(inst::config::configPath); + nlohmann::json j; + file >> j; + autoUpdate = j["autoUpdate"].get(); + deletePrompt = j["deletePrompt"].get(); + gAuthKey = j["gAuthKey"].get(); + gayMode = j["gayMode"].get(); + useSound = j["useSound"].get(); + ignoreReqVers = j["ignoreReqVers"].get(); + languageSetting = j["languageSetting"].get(); + overClock = j["overClock"].get(); + sigPatchesUrl = j["sigPatchesUrl"].get(); + usbAck = j["usbAck"].get(); + validateNCAs = j["validateNCAs"].get(); + } + catch (...) { + // 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; + autoUpdate = true; + deletePrompt = true; + gayMode = false; + useSound = false; + ignoreReqVers = true; + overClock = false; + usbAck = false; + validateNCAs = true; + setConfig(); + } + } +} \ No newline at end of file diff --git a/source/util/crypto.cpp b/source/util/crypto.cpp new file mode 100644 index 0000000..58265d5 --- /dev/null +++ b/source/util/crypto.cpp @@ -0,0 +1,90 @@ +#include "util/crypto.hpp" +#include +#include +#include +#include "util/error.hpp" + +void Crypto::calculateMGF1andXOR(unsigned char* data, size_t data_size, const void* source, size_t source_size) { + unsigned char h_buf[RSA_2048_BYTES] = {0}; + memcpy(h_buf, source, source_size); + + unsigned char mgf1_buf[0x20]; + size_t ofs = 0; + unsigned int seed = 0; + while (ofs < data_size) { + for (unsigned int i = 0; i < sizeof(seed); i++) { + h_buf[source_size + 3 - i] = (seed >> (8 * i)) & 0xFF; + } + sha256CalculateHash(mgf1_buf, h_buf, source_size + 4); + for (unsigned int i = ofs; i < data_size && i < ofs + 0x20; i++) { + data[i] ^= mgf1_buf[i - ofs]; + } + seed++; + ofs += 0x20; + } +} + +bool Crypto::rsa2048PssVerify(const void *data, size_t len, const unsigned char *signature, const unsigned char *modulus) { + mbedtls_mpi signature_mpi; + mbedtls_mpi modulus_mpi; + mbedtls_mpi e_mpi; + mbedtls_mpi message_mpi; + + mbedtls_mpi_init(&signature_mpi); + mbedtls_mpi_init(&modulus_mpi); + mbedtls_mpi_init(&e_mpi); + mbedtls_mpi_init(&message_mpi); + mbedtls_mpi_lset(&message_mpi, RSA_2048_BITS); + + unsigned char m_buf[RSA_2048_BYTES]; + unsigned char h_buf[0x24]; + const unsigned char E[3] = {1, 0, 1}; + + mbedtls_mpi_read_binary(&e_mpi, E, 3); + mbedtls_mpi_read_binary(&signature_mpi, signature, RSA_2048_BYTES); + mbedtls_mpi_read_binary(&modulus_mpi, modulus, RSA_2048_BYTES); + mbedtls_mpi_exp_mod(&message_mpi, &signature_mpi, &e_mpi, &modulus_mpi, NULL); + + if (mbedtls_mpi_write_binary(&message_mpi, m_buf, RSA_2048_BYTES) != 0) { + THROW_FORMAT("Failed to export exponentiated RSA message!"); + } + + mbedtls_mpi_free(&signature_mpi); + mbedtls_mpi_free(&modulus_mpi); + mbedtls_mpi_free(&e_mpi); + mbedtls_mpi_free(&message_mpi); + + /* There's no automated PSS verification as far as I can tell. */ + if (m_buf[RSA_2048_BYTES-1] != 0xBC) { + return false; + } + + memset(h_buf, 0, 0x24); + memcpy(h_buf, m_buf + RSA_2048_BYTES - 0x20 - 0x1, 0x20); + + /* Decrypt maskedDB. */ + calculateMGF1andXOR(m_buf, RSA_2048_BYTES - 0x20 - 1, h_buf, 0x20); + + m_buf[0] &= 0x7F; /* Constant lmask for rsa-2048-pss. */ + + /* Validate DB. */ + for (unsigned int i = 0; i < RSA_2048_BYTES - 0x20 - 0x20 - 1 - 1; i++) { + if (m_buf[i] != 0) { + return false; + } + } + if (m_buf[RSA_2048_BYTES - 0x20 - 0x20 - 1 - 1] != 1) { + return false; + } + + /* Check hash correctness. */ + unsigned char validate_buf[8 + 0x20 + 0x20]; + unsigned char validate_hash[0x20]; + memset(validate_buf, 0, 0x48); + + sha256CalculateHash(&validate_buf[8], data, len); + memcpy(&validate_buf[0x28], &m_buf[RSA_2048_BYTES - 0x20 - 0x20 - 1], 0x20); + sha256CalculateHash(validate_hash, validate_buf, 0x48); + + return memcmp(h_buf, validate_hash, 0x20) == 0; +} \ No newline at end of file diff --git a/source/util/curl.cpp b/source/util/curl.cpp new file mode 100644 index 0000000..861b4cc --- /dev/null +++ b/source/util/curl.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include "util/curl.hpp" +#include "util/config.hpp" +#include "util/error.hpp" +#include "ui/instPage.hpp" + +static size_t writeDataFile(void *ptr, size_t size, size_t nmemb, void *stream) { + size_t written = fwrite(ptr, size, nmemb, (FILE *)stream); + return written; +} + +size_t writeDataBuffer(char *ptr, size_t size, size_t nmemb, void *userdata) { + std::ostringstream *stream = (std::ostringstream*)userdata; + size_t count = size * nmemb; + stream->write(ptr, count); + return count; +} + +int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + if (ultotal) { + int uploadProgress = (int)(((double)ulnow / (double)ultotal) * 100.0); + inst::ui::instPage::setInstBarPerc(uploadProgress); + } else if (dltotal) { + int downloadProgress = (int)(((double)dlnow / (double)dltotal) * 100.0); + inst::ui::instPage::setInstBarPerc(downloadProgress); + } + return 0; +} + +namespace inst::curl { + bool downloadFile (const std::string ourUrl, const char *pagefilename, long timeout, bool writeProgress) { + CURL *curl_handle; + CURLcode result; + FILE *pagefile; + + curl_global_init(CURL_GLOBAL_ALL); + curl_handle = curl_easy_init(); + + curl_easy_setopt(curl_handle, CURLOPT_URL, ourUrl.c_str()); + curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "TinWoo"); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT_MS, timeout); + curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT_MS, timeout); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeDataFile); + if (writeProgress) curl_easy_setopt(curl_handle, CURLOPT_XFERINFOFUNCTION, progress_callback); + + pagefile = fopen(pagefilename, "wb"); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, pagefile); + result = curl_easy_perform(curl_handle); + + curl_easy_cleanup(curl_handle); + curl_global_cleanup(); + fclose(pagefile); + + if (result == CURLE_OK) return true; + else { + LOG_DEBUG(curl_easy_strerror(result)); + return false; + } + } + + std::string downloadToBuffer (const std::string ourUrl, int firstRange, int secondRange, long timeout) { + CURL *curl_handle; + CURLcode result; + std::ostringstream stream; + + curl_global_init(CURL_GLOBAL_ALL); + curl_handle = curl_easy_init(); + + curl_easy_setopt(curl_handle, CURLOPT_URL, ourUrl.c_str()); + curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "TinWoo"); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT_MS, timeout); + curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT_MS, timeout); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeDataBuffer); + if (firstRange && secondRange) { + const char * ourRange = (std::to_string(firstRange) + "-" + std::to_string(secondRange)).c_str(); + curl_easy_setopt(curl_handle, CURLOPT_RANGE, ourRange); + } + + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &stream); + result = curl_easy_perform(curl_handle); + + curl_easy_cleanup(curl_handle); + curl_global_cleanup(); + + if (result == CURLE_OK) return stream.str(); + else { + LOG_DEBUG(curl_easy_strerror(result)); + return ""; + } + } +} \ No newline at end of file diff --git a/source/util/debug.c b/source/util/debug.c new file mode 100644 index 0000000..6446de0 --- /dev/null +++ b/source/util/debug.c @@ -0,0 +1,53 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "util/debug.h" + +#include + +#include +#include +#include +#include + +void printBytes(u8 *bytes, size_t size, bool includeHeader) +{ +#ifdef NXLINK_DEBUG + int count = 0; + + if (includeHeader) + { + printf("\n\n00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); + printf("-----------------------------------------------\n"); + } + + for (int i = 0; i < size; i++) + { + printf("%02x ", bytes[i]); + count++; + if ((count % 16) == 0) + printf("\n"); + } + + printf("\n"); +#endif +} \ No newline at end of file diff --git a/source/util/file_util.cpp b/source/util/file_util.cpp new file mode 100644 index 0000000..a099355 --- /dev/null +++ b/source/util/file_util.cpp @@ -0,0 +1,53 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "util/file_util.hpp" + +#include + +#include "install/simple_filesystem.hpp" +#include "nx/fs.hpp" +#include "data/byte_buffer.hpp" +#include "util/title_util.hpp" + +namespace tin::util +{ + // TODO: do this manually so we don't have to "install" the cnmt's + nx::ncm::ContentMeta GetContentMetaFromNCA(const std::string& ncaPath) + { + // Create the cnmt filesystem + nx::fs::IFileSystem cnmtNCAFileSystem; + cnmtNCAFileSystem.OpenFileSystemWithId(ncaPath, FsFileSystemType_ContentMeta, 0); + tin::install::nsp::SimpleFileSystem cnmtNCASimpleFileSystem(cnmtNCAFileSystem, "/", ncaPath + "/"); + + // Find and read the cnmt file + auto cnmtName = cnmtNCASimpleFileSystem.GetFileNameFromExtension("", "cnmt"); + auto cnmtFile = cnmtNCASimpleFileSystem.OpenFile(cnmtName); + u64 cnmtSize = cnmtFile.GetSize(); + + tin::data::ByteBuffer cnmtBuf; + cnmtBuf.Resize(cnmtSize); + cnmtFile.Read(0x0, cnmtBuf.GetData(), cnmtSize); + + return nx::ncm::ContentMeta(cnmtBuf.GetData(), cnmtBuf.GetSize()); + } +} \ No newline at end of file diff --git a/source/util/lang.cpp b/source/util/lang.cpp new file mode 100644 index 0000000..1867495 --- /dev/null +++ b/source/util/lang.cpp @@ -0,0 +1,85 @@ +#include +#include +#include +#include "util/lang.hpp" +#include "util/config.hpp" + +namespace Language { + json lang; + + void Load() { + std::ifstream ifs; + std::string languagePath; + int langInt = inst::config::languageSetting; + if (langInt == 99) { + SetLanguage ourLang; + u64 lcode = 0; + setInitialize(); + setGetSystemLanguage(&lcode); + setMakeLanguage(lcode, &ourLang); + setExit(); + langInt = (int)ourLang; + } + switch (langInt) { + case 0: + languagePath = "romfs:/lang/jp.json"; + break; + case 2: + case 13: + languagePath = "romfs:/lang/fr.json"; + break; + case 3: + languagePath = "romfs:/lang/de.json"; + break; + case 4: + languagePath = "romfs:/lang/it.json"; + break; + case 5: + case 14: + languagePath = "romfs:/lang/es.json"; + break; + case 6: + languagePath = "romfs:/lang/zh-CN.json"; + break; + case 7: + languagePath = "romfs:/lang/ko.json"; + break; + case 8: + languagePath = "romfs:/lang/nl.json"; + break; + case 9: + languagePath = "romfs:/lang/pt.json"; + break; + case 10: + languagePath = "romfs:/lang/ru.json"; + break; + case 11: + languagePath = "romfs:/lang/zh-TW.json"; + break; + default: + languagePath = "romfs:/lang/en.json"; + } + if (std::filesystem::exists(languagePath)) ifs = std::ifstream(languagePath); + else ifs = std::ifstream("romfs:/lang/en.json"); + if (!ifs.good()) { + std::cout << "[FAILED TO LOAD LANGUAGE FILE]" << std::endl; + return; + } + lang = json::parse(ifs); + ifs.close(); + } + + std::string LanguageEntry(std::string key) { + json j = GetRelativeJson(lang, key); + if (j == nullptr) { + return "didn't find: " + key; + } + return j.get(); + } + + std::string GetRandomMsg() { + json j = Language::GetRelativeJson(lang, "inst.finished"); + srand(time(NULL)); + return(j[rand() % j.size()]); + } +} \ No newline at end of file diff --git a/source/util/network_util.cpp b/source/util/network_util.cpp new file mode 100644 index 0000000..816c33f --- /dev/null +++ b/source/util/network_util.cpp @@ -0,0 +1,265 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "util/network_util.hpp" + +#include +#include +#include +#include +#include +#include "util/error.hpp" + +namespace tin::network +{ + // HTTPHeader + + HTTPHeader::HTTPHeader(std::string url) : + m_url(url) + { + } + + size_t HTTPHeader::ParseHTMLHeader(char* bytes, size_t size, size_t numItems, void* userData) + { + HTTPHeader* header = reinterpret_cast(userData); + size_t numBytes = size * numItems; + std::string line(bytes, numBytes); + + // Remove any newlines or carriage returns + line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + + // Split into key and value + if (!line.empty()) + { + auto keyEnd = line.find(": "); + + if (keyEnd != 0) + { + std::string key = line.substr(0, keyEnd); + std::string value = line.substr(keyEnd + 2); + + // Make key lowercase + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + header->m_values[key] = value; + } + } + + return numBytes; + } + + void HTTPHeader::PerformRequest() + { + // We don't want any existing values to get mixed up with this request + m_values.clear(); + + CURL* curl = curl_easy_init(); + CURLcode rc = (CURLcode)0; + + if (!curl) + { + THROW_FORMAT("Failed to initialize curl\n"); + } + + curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOBODY, true); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "tinfoil"); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, this); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &tin::network::HTTPHeader::ParseHTMLHeader); + + rc = curl_easy_perform(curl); + if (rc != CURLE_OK) + { + THROW_FORMAT("Failed to retrieve HTTP Header: %s\n", curl_easy_strerror(rc)); + } + + u64 httpCode = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); + curl_easy_cleanup(curl); + + if (httpCode != 200 && httpCode != 204) + { + THROW_FORMAT("Unexpected HTTP response code when retrieving header: %lu\n", httpCode); + } + } + + bool HTTPHeader::HasValue(std::string key) + { + return m_values.count(key); + } + + std::string HTTPHeader::GetValue(std::string key) + { + return m_values[key]; + } + + // End HTTPHeader + // HTTPDownload + + HTTPDownload::HTTPDownload(std::string url) : + m_url(url), m_header(url) + { + // The header won't be populated until we do this + m_header.PerformRequest(); + + if (m_header.HasValue("accept-ranges")) + { + m_rangesSupported = m_header.GetValue("accept-ranges") == "bytes"; + } + else + { + CURL* curl = curl_easy_init(); + CURLcode rc = (CURLcode)0; + + if (!curl) + { + THROW_FORMAT("Failed to initialize curl\n"); + } + + curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOBODY, true); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "tinfoil"); + curl_easy_setopt(curl, CURLOPT_RANGE, "0-0"); + + rc = curl_easy_perform(curl); + if (rc != CURLE_OK) + { + THROW_FORMAT("Failed to retrieve HTTP Header: %s\n", curl_easy_strerror(rc)); + } + + u64 httpCode = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); + curl_easy_cleanup(curl); + + m_rangesSupported = httpCode == 206; + } + } + + size_t HTTPDownload::ParseHTMLData(char* bytes, size_t size, size_t numItems, void* userData) + { + auto streamFunc = *reinterpret_cast*>(userData); + size_t numBytes = size * numItems; + + if (streamFunc != nullptr) + return streamFunc((u8*)bytes, numBytes); + + return numBytes; + } + + void HTTPDownload::BufferDataRange(void* buffer, size_t offset, size_t size, std::function progressFunc) + { + size_t sizeRead = 0; + + auto streamFunc = [&](u8* streamBuf, size_t streamBufSize) -> size_t + { + if (sizeRead + streamBufSize > size) + { + LOG_DEBUG("New read size 0x%lx would exceed total expected size 0x%lx\n", sizeRead + streamBufSize, size); + return 0; + } + + if (progressFunc != nullptr) + progressFunc(sizeRead); + + memcpy(reinterpret_cast(buffer) + sizeRead, streamBuf, streamBufSize); + sizeRead += streamBufSize; + return streamBufSize; + }; + + this->StreamDataRange(offset, size, streamFunc); + } + + int HTTPDownload::StreamDataRange(size_t offset, size_t size, std::function streamFunc) + { + if (!m_rangesSupported) + { + THROW_FORMAT("Attempted range request when ranges aren't supported!\n"); + } + + auto writeDataFunc = streamFunc; + + CURL* curl = curl_easy_init(); + CURLcode rc = (CURLcode)0; + + if (!curl) + { + THROW_FORMAT("Failed to initialize curl\n"); + } + + std::stringstream ss; + ss << offset << "-" << (offset + size - 1); + auto range = ss.str(); + + curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str()); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "tinfoil"); + curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeDataFunc); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &tin::network::HTTPDownload::ParseHTMLData); + + rc = curl_easy_perform(curl); + + u64 httpCode = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); + curl_easy_cleanup(curl); + + if (httpCode != 206 || rc != CURLE_OK) return 1; + return 0; + } + + // End HTTPDownload + + size_t WaitReceiveNetworkData(int sockfd, void* buf, size_t len) + { + int ret = 0; + size_t read = 0; + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + PadState pad; + padInitializeDefault(&pad); + + while ((((ret = recv(sockfd, (u8*)buf + read, len - read, 0)) > 0 && (read += ret) < len) || errno == EAGAIN) && !(padGetButtonsDown(&pad) & HidNpadButton_B)) + { + errno = 0; + } + + return read; + } + + size_t WaitSendNetworkData(int sockfd, void* buf, size_t len) + { + errno = 0; + int ret = 0; + size_t written = 0; + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + PadState pad; + padInitializeDefault(&pad); + + while ((((ret = send(sockfd, (u8*)buf + written, len - written, 0)) > 0 && (written += ret) < len) || errno == EAGAIN) && !(padGetButtonsDown(&pad) & HidNpadButton_B)) + { + errno = 0; + } + + return written; + } +} \ No newline at end of file diff --git a/source/util/title_util.cpp b/source/util/title_util.cpp new file mode 100644 index 0000000..7e05cef --- /dev/null +++ b/source/util/title_util.cpp @@ -0,0 +1,134 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "util/title_util.hpp" + +#include +#include "util/error.hpp" + +namespace tin::util +{ + u64 GetRightsIdTid(RightsId rightsId) + { + return __bswap64(*(u64 *)rightsId.c); + } + + u64 GetRightsIdKeyGen(RightsId rightsId) + { + return __bswap64(*(u64 *)(rightsId.c + 8)); + } + + std::string GetNcaIdString(const NcmContentId& ncaId) + { + char ncaIdStr[FS_MAX_PATH] = {0}; + u64 ncaIdLower = __bswap64(*(u64 *)ncaId.c); + u64 ncaIdUpper = __bswap64(*(u64 *)(ncaId.c + 0x8)); + snprintf(ncaIdStr, FS_MAX_PATH, "%016lx%016lx", ncaIdLower, ncaIdUpper); + return std::string(ncaIdStr); + } + + NcmContentId GetNcaIdFromString(std::string ncaIdStr) + { + NcmContentId ncaId = {0}; + char lowerU64[17] = {0}; + char upperU64[17] = {0}; + memcpy(lowerU64, ncaIdStr.c_str(), 16); + memcpy(upperU64, ncaIdStr.c_str() + 16, 16); + + *(u64 *)ncaId.c = __bswap64(strtoul(lowerU64, NULL, 16)); + *(u64 *)(ncaId.c + 8) = __bswap64(strtoul(upperU64, NULL, 16)); + + return ncaId; + } + + u64 GetBaseTitleId(u64 titleId, NcmContentMetaType contentMetaType) + { + switch (contentMetaType) + { + case NcmContentMetaType_Patch: + return titleId ^ 0x800; + + case NcmContentMetaType_AddOnContent: + return (titleId ^ 0x1000) & ~0xFFF; + + default: + return titleId; + } + } + + std::string GetBaseTitleName(u64 baseTitleId) + { + Result rc = 0; + NsApplicationControlData appControlData; + size_t sizeRead; + + if (R_FAILED(rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, baseTitleId, &appControlData, sizeof(NsApplicationControlData), &sizeRead))) + { + LOG_DEBUG("Failed to get application control data. Error code: 0x%08x\n", rc); + return "Unknown"; + } + + if (sizeRead < sizeof(appControlData.nacp)) + { + LOG_DEBUG("Incorrect size for nacp\n"); + return "Unknown"; + } + + NacpLanguageEntry *languageEntry; + + if (R_FAILED(rc = nacpGetLanguageEntry(&appControlData.nacp, &languageEntry))) + { + LOG_DEBUG("Failed to get language entry. Error code: 0x%08x\n", rc); + return "Unknown"; + } + + if (languageEntry == NULL) + { + LOG_DEBUG("Language entry is null! Error code: 0x%08x\n", rc); + return "Unknown"; + } + + return languageEntry->name; + } + + std::string GetTitleName(u64 titleId, NcmContentMetaType contentMetaType) + { + u64 baseTitleId = GetBaseTitleId(titleId, contentMetaType); + std::string titleName = GetBaseTitleName(baseTitleId); + + switch (contentMetaType) + { + case NcmContentMetaType_Patch: + titleName += " (Update)"; + break; + + case NcmContentMetaType_AddOnContent: + titleName += " (DLC)"; + break; + + default: + break; + } + + return titleName; + } +} \ No newline at end of file diff --git a/source/util/unzip.cpp b/source/util/unzip.cpp new file mode 100644 index 0000000..9ff23bf --- /dev/null +++ b/source/util/unzip.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// https://github.com/AtlasNX/Kosmos-Updater/blob/master/source/FileManager.cpp + +unz_file_info_s * _getFileInfo(unzFile unz) { + unz_file_info_s * fileInfo = (unz_file_info_s*) malloc(sizeof(unz_file_info_s)); + unzGetCurrentFileInfo(unz, fileInfo, NULL, 0, NULL, 0, NULL, 0); + return fileInfo; +} + +std::string _getFullFileName(unzFile unz, unz_file_info_s * fileInfo) { + char filePath[fileInfo->size_filename + 1]; + + unzGetCurrentFileInfo(unz, fileInfo, filePath, fileInfo->size_filename, NULL, 0, NULL, 0); + filePath[fileInfo->size_filename] = '\0'; + + std::string path(filePath); + path.resize(fileInfo->size_filename); + + return path; +} + +bool _makeDirectoryParents(std::string path) +{ + bool bSuccess = false; + int nRC = ::mkdir(path.c_str(), 0775); + if(nRC == -1) + { + switch(errno) + { + case ENOENT: + //parent didn't exist, try to create it + if( _makeDirectoryParents(path.substr(0, path.find_last_of('/')))) + //Now, try to create again. + bSuccess = 0 == ::mkdir(path.c_str(), 0775); + else + bSuccess = false; + break; + case EEXIST: + //Done! + bSuccess = true; + break; + //std::string getHost(""); + default: + bSuccess = false; + break; + } + } + else + bSuccess = true; + + return bSuccess; +} + +int _extractFile(const char * path, unzFile unz, unz_file_info_s * fileInfo) { + //check to make sure filepath or fileInfo isnt null + if (path == NULL || fileInfo == NULL) + return -1; + + if (unzOpenCurrentFile(unz) != UNZ_OK) + return -2; + + char folderPath[strlen(path) + 1]; + strcpy(folderPath, path); + char * pos = strrchr(folderPath, '/'); + if (pos != NULL) { + *pos = '\0'; + _makeDirectoryParents(std::string(folderPath)); + } + + u32 blocksize = 0x8000; + u8 * buffer = (u8*) malloc(blocksize); + if (buffer == NULL) + return -3; + u32 done = 0; + int writeBytes = 0; + FILE * fp = fopen(path, "w"); + if (fp == NULL) { + free(buffer); + return -4; + } + + while (done < fileInfo->uncompressed_size) { + if (done + blocksize > fileInfo->uncompressed_size) { + blocksize = fileInfo->uncompressed_size - done; + } + unzReadCurrentFile(unz, buffer, blocksize); + writeBytes = write(fileno(fp), buffer, blocksize); + if (writeBytes <= 0) { + break; + } + done += writeBytes; + } + + fflush(fp); + fsync(fileno(fp)); + fclose(fp); + + free(buffer); + if (done != fileInfo->uncompressed_size) + return -4; + + unzCloseCurrentFile(unz); + return 0; +} + +namespace inst::zip { + bool extractFile(const std::string filename, const std::string destination) { + unzFile unz = unzOpen(filename.c_str()); + + int i = 0; + for (;;) { + int code; + if (i == 0) { + code = unzGoToFirstFile(unz); + } else { + code = unzGoToNextFile(unz); + } + i++; + + if (code == UNZ_END_OF_LIST_OF_FILE) { + break; + } else { + unz_file_pos pos; + unzGetFilePos(unz, &pos); + } + + unz_file_info_s * fileInfo = _getFileInfo(unz); + + std::string fileName = destination; + fileName += _getFullFileName(unz, fileInfo); + + if (fileName.back() != '/') { + int result = _extractFile(fileName.c_str(), unz, fileInfo); + if (result < 0) { + free(fileInfo); + unzClose(unz); + return false; + } + } + + free(fileInfo); + } + + if (i <= 0) { + unzClose(unz); + return false; + } + + unzClose(unz); + return true; + } +} \ No newline at end of file diff --git a/source/util/usb_comms_tinleaf.c b/source/util/usb_comms_tinleaf.c new file mode 100644 index 0000000..92edfa7 --- /dev/null +++ b/source/util/usb_comms_tinleaf.c @@ -0,0 +1,595 @@ +#include +#include +#include "switch/types.h" +#include "switch/result.h" +#include "switch/kernel/rwlock.h" +#include "switch/services/fatal.h" +#include "switch/services/usbds.h" +#include "switch/runtime/hosversion.h" +#include "util/usb_comms_tinleaf.h" +#include + +#define TOTAL_INTERFACES 4 + +typedef struct { + RwLock lock, lock_in, lock_out; + bool initialized; + + UsbDsInterface* interface; + UsbDsEndpoint *endpoint_in, *endpoint_out; + + u8 *endpoint_in_buffer, *endpoint_out_buffer; +} usbCommsInterface; + +static bool g_usbCommsInitialized = false; + +static usbCommsInterface g_usbCommsInterfaces[TOTAL_INTERFACES]; + +static bool g_usbCommsErrorHandling = 0; + +static RwLock g_usbCommsLock; + +static Result _usbCommsInterfaceInit1x(u32 intf_ind, const tinleaf_UsbCommsInterfaceInfo *info); +static Result _usbCommsInterfaceInit5x(u32 intf_ind, const tinleaf_UsbCommsInterfaceInfo *info); +static Result _usbCommsInterfaceInit(u32 intf_ind, const tinleaf_UsbCommsInterfaceInfo *info); + +static Result _usbCommsWrite(usbCommsInterface *interface, const void* buffer, size_t size, size_t *transferredSize, u64 timeout); + +static void _usbCommsUpdateInterfaceDescriptor(struct usb_interface_descriptor *desc, const tinleaf_UsbCommsInterfaceInfo *info) { + if (info != NULL) { + desc->bInterfaceClass = info->bInterfaceClass; + desc->bInterfaceSubClass = info->bInterfaceSubClass; + desc->bInterfaceProtocol = info->bInterfaceProtocol; + } +} + +Result tinleaf_usbCommsInitializeEx(u32 num_interfaces, const tinleaf_UsbCommsInterfaceInfo *infos) +{ + Result rc = 0; + rwlockWriteLock(&g_usbCommsLock); + + if (g_usbCommsInitialized) { + rc = MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + } else if (num_interfaces > TOTAL_INTERFACES) { + rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } else { + rc = usbDsInitialize(); + + if (R_SUCCEEDED(rc)) { + if (hosversionAtLeast(5,0,0)) { + u8 iManufacturer, iProduct, iSerialNumber; + static const u16 supported_langs[1] = {0x0409}; + // Send language descriptor + rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs)/sizeof(u16)); + // Send manufacturer + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iManufacturer, "Nintendo"); + // Send product + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iProduct, "Nintendo Switch"); + // Send serial number + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iSerialNumber, "SerialNumber"); + + // Send device descriptors + struct usb_device_descriptor device_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0110, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x40, + .idVendor = 0x057e, + .idProduct = 0x3000, + .bcdDevice = 0x0100, + .iManufacturer = iManufacturer, + .iProduct = iProduct, + .iSerialNumber = iSerialNumber, + .bNumConfigurations = 0x01 + }; + // Full Speed is USB 1.1 + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor); + + // High Speed is USB 2.0 + device_descriptor.bcdUSB = 0x0200; + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor); + + // Super Speed is USB 3.0 + device_descriptor.bcdUSB = 0x0300; + // Upgrade packet size to 512 + device_descriptor.bMaxPacketSize0 = 0x09; + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor); + + // Define Binary Object Store + u8 bos[0x16] = { + 0x05, // .bLength + USB_DT_BOS, // .bDescriptorType + 0x16, 0x00, // .wTotalLength + 0x02, // .bNumDeviceCaps + + // USB 2.0 + 0x07, // .bLength + USB_DT_DEVICE_CAPABILITY, // .bDescriptorType + 0x02, // .bDevCapabilityType + 0x02, 0x00, 0x00, 0x00, // dev_capability_data + + // USB 3.0 + 0x0A, // .bLength + USB_DT_DEVICE_CAPABILITY, // .bDescriptorType + 0x03, // .bDevCapabilityType + 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00 + }; + if (R_SUCCEEDED(rc)) rc = usbDsSetBinaryObjectStore(bos, sizeof(bos)); + } + + if (R_SUCCEEDED(rc)) { + for (u32 i = 0; i < num_interfaces; i++) { + usbCommsInterface *intf = &g_usbCommsInterfaces[i]; + rwlockWriteLock(&intf->lock); + rwlockWriteLock(&intf->lock_in); + rwlockWriteLock(&intf->lock_out); + rc = _usbCommsInterfaceInit(i, infos == NULL ? NULL : infos + i); + rwlockWriteUnlock(&intf->lock_out); + rwlockWriteUnlock(&intf->lock_in); + rwlockWriteUnlock(&intf->lock); + if (R_FAILED(rc)) { + break; + } + } + } + } + + if (R_SUCCEEDED(rc) && hosversionAtLeast(5,0,0)) { + rc = usbDsEnable(); + } + } + + if (R_SUCCEEDED(rc)) { + g_usbCommsInitialized = true; + g_usbCommsErrorHandling = false; + } + + rwlockWriteUnlock(&g_usbCommsLock); + + if (R_FAILED(rc)) { + tinleaf_usbCommsExit(); + } + + return rc; +} + +Result tinleaf_usbCommsInitialize(void) +{ + return tinleaf_usbCommsInitializeEx(1, NULL); +} + +static void _usbCommsInterfaceFree(usbCommsInterface *interface) +{ + rwlockWriteLock(&interface->lock); + if (!interface->initialized) { + rwlockWriteUnlock(&interface->lock); + return; + } + + rwlockWriteLock(&interface->lock_in); + rwlockWriteLock(&interface->lock_out); + + interface->initialized = 0; + + interface->endpoint_in = NULL; + interface->endpoint_out = NULL; + interface->interface = NULL; + + free(interface->endpoint_in_buffer); + free(interface->endpoint_out_buffer); + interface->endpoint_in_buffer = NULL; + interface->endpoint_out_buffer = NULL; + + rwlockWriteUnlock(&interface->lock_out); + rwlockWriteUnlock(&interface->lock_in); + + rwlockWriteUnlock(&interface->lock); +} + +void tinleaf_usbCommsExit(void) +{ + u32 i; + + rwlockWriteLock(&g_usbCommsLock); + + usbDsExit(); + + g_usbCommsInitialized = false; + + rwlockWriteUnlock(&g_usbCommsLock); + + for (i=0; iinitialized = 1; + + //The buffer for PostBufferAsync commands must be 0x1000-byte aligned. + interface->endpoint_in_buffer = memalign(0x1000, 0x1000); + if (interface->endpoint_in_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + + if (R_SUCCEEDED(rc)) { + interface->endpoint_out_buffer = memalign(0x1000, 0x1000); + if (interface->endpoint_out_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } + + if (R_SUCCEEDED(rc)) { + memset(interface->endpoint_in_buffer, 0, 0x1000); + memset(interface->endpoint_out_buffer, 0, 0x1000); + } + + if (R_FAILED(rc)) return rc; + + rc = usbDsRegisterInterface(&interface->interface); + if (R_FAILED(rc)) return rc; + + interface_descriptor.bInterfaceNumber = interface->interface->interface_index; + endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1; + endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1; + + // Full Speed Config + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + + // High Speed Config + endpoint_descriptor_in.wMaxPacketSize = 0x200; + endpoint_descriptor_out.wMaxPacketSize = 0x200; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + + // Super Speed Config + endpoint_descriptor_in.wMaxPacketSize = 0x400; + endpoint_descriptor_out.wMaxPacketSize = 0x400; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); + if (R_FAILED(rc)) return rc; + + //Setup endpoints. + rc = usbDsInterface_RegisterEndpoint(interface->interface, &interface->endpoint_in, endpoint_descriptor_in.bEndpointAddress); + if (R_FAILED(rc)) return rc; + + rc = usbDsInterface_RegisterEndpoint(interface->interface, &interface->endpoint_out, endpoint_descriptor_out.bEndpointAddress); + if (R_FAILED(rc)) return rc; + + rc = usbDsInterface_EnableInterface(interface->interface); + if (R_FAILED(rc)) return rc; + + return rc; +} + + +static Result _usbCommsInterfaceInit1x(u32 intf_ind, const tinleaf_UsbCommsInterfaceInfo *info) +{ + Result rc = 0; + usbCommsInterface *interface = &g_usbCommsInterfaces[intf_ind]; + + struct usb_interface_descriptor interface_descriptor = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = intf_ind, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, + }; + _usbCommsUpdateInterfaceDescriptor(&interface_descriptor, info); + + struct usb_endpoint_descriptor endpoint_descriptor_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_IN, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = 0x200, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_OUT, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = 0x200, + }; + + interface->initialized = 1; + + //The buffer for PostBufferAsync commands must be 0x1000-byte aligned. + interface->endpoint_in_buffer = memalign(0x1000, 0x1000); + if (interface->endpoint_in_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + + if (R_SUCCEEDED(rc)) { + interface->endpoint_out_buffer = memalign(0x1000, 0x1000); + if (interface->endpoint_out_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } + + if (R_SUCCEEDED(rc)) { + memset(interface->endpoint_in_buffer, 0, 0x1000); + memset(interface->endpoint_out_buffer, 0, 0x1000); + } + + if (R_FAILED(rc)) return rc; + + //Setup interface. + rc = usbDsGetDsInterface(&interface->interface, &interface_descriptor, "usb"); + if (R_FAILED(rc)) return rc; + + //Setup endpoints. + rc = usbDsInterface_GetDsEndpoint(interface->interface, &interface->endpoint_in, &endpoint_descriptor_in);//device->host + if (R_FAILED(rc)) return rc; + + rc = usbDsInterface_GetDsEndpoint(interface->interface, &interface->endpoint_out, &endpoint_descriptor_out);//host->device + if (R_FAILED(rc)) return rc; + + rc = usbDsInterface_EnableInterface(interface->interface); + if (R_FAILED(rc)) return rc; + + return rc; +} + +void tinleaf_usbCommsSetErrorHandling(bool flag) { + g_usbCommsErrorHandling = flag; +} + +static Result _usbCommsRead(usbCommsInterface *interface, void* buffer, size_t size, size_t *transferredSize, u64 timeout) +{ + Result rc=0; + u32 urbId=0; + u8 *bufptr = (u8*)buffer; + u8 *transfer_buffer = NULL; + u8 transfer_type=0; + u32 chunksize=0; + u32 tmp_transferredSize = 0; + size_t total_transferredSize=0; + UsbDsReportData reportdata; + + //Makes sure endpoints are ready for data-transfer / wait for init if needed. + rc = usbDsWaitReady(timeout); + if (R_FAILED(rc)) return rc; + + while(size) + { + if(((u64)bufptr) & 0xfff)//When bufptr isn't page-aligned copy the data into g_usbComms_endpoint_in_buffer and transfer that, otherwise use the bufptr directly. + { + transfer_buffer = interface->endpoint_out_buffer; + memset(interface->endpoint_out_buffer, 0, 0x1000); + + chunksize = 0x1000; + chunksize-= ((u64)bufptr) & 0xfff;//After this transfer, bufptr will be page-aligned(if size is large enough for another transfer). + if (sizedevice transfer. + rc = usbDsEndpoint_PostBufferAsync(interface->endpoint_out, transfer_buffer, chunksize, &urbId); + if (R_FAILED(rc)) return rc; + //Wait for the transfer to finish. + rc = eventWait(&interface->endpoint_out->CompletionEvent, timeout); + if (R_FAILED(rc)) + { + usbDsEndpoint_Cancel(interface->endpoint_out); + eventWait(&interface->endpoint_out->CompletionEvent, UINT64_MAX); + eventClear(&interface->endpoint_out->CompletionEvent); + return rc; + } + eventClear(&interface->endpoint_out->CompletionEvent); + + rc = usbDsEndpoint_GetReportData(interface->endpoint_out, &reportdata); + if (R_FAILED(rc)) return rc; + + rc = usbDsParseReportData(&reportdata, urbId, NULL, &tmp_transferredSize); + if (R_FAILED(rc)) return rc; + + if (tmp_transferredSize > chunksize) tmp_transferredSize = chunksize; + total_transferredSize+= (size_t)tmp_transferredSize; + + if (transfer_type==0) memcpy(bufptr, transfer_buffer, tmp_transferredSize); + bufptr+= tmp_transferredSize; + size-= tmp_transferredSize; + + if(tmp_transferredSize < chunksize)break; + } + + if (transferredSize) *transferredSize = total_transferredSize; + + return rc; +} + +static Result _usbCommsWrite(usbCommsInterface *interface, const void* buffer, size_t size, size_t *transferredSize, u64 timeout) +{ + Result rc=0; + u32 urbId=0; + u32 chunksize=0; + u8 *bufptr = (u8*)buffer; + u8 *transfer_buffer = NULL; + u32 tmp_transferredSize = 0; + size_t total_transferredSize=0; + UsbDsReportData reportdata; + + //Makes sure endpoints are ready for data-transfer / wait for init if needed. + rc = usbDsWaitReady(timeout); + if (R_FAILED(rc)) return rc; + + while(size) + { + if(((u64)bufptr) & 0xfff)//When bufptr isn't page-aligned copy the data into g_usbComms_endpoint_in_buffer and transfer that, otherwise use the bufptr directly. + { + transfer_buffer = interface->endpoint_in_buffer; + memset(interface->endpoint_in_buffer, 0, 0x1000); + + chunksize = 0x1000; + chunksize-= ((u64)bufptr) & 0xfff;//After this transfer, bufptr will be page-aligned(if size is large enough for another transfer). + if (sizeendpoint_in_buffer, bufptr, chunksize); + } + else + { + transfer_buffer = bufptr; + chunksize = size; + } + + //Start a device->host transfer. + rc = usbDsEndpoint_PostBufferAsync(interface->endpoint_in, transfer_buffer, chunksize, &urbId); + if(R_FAILED(rc))return rc; + + //Wait for the transfer to finish. + rc = eventWait(&interface->endpoint_in->CompletionEvent, timeout); + if (R_FAILED(rc)) + { + usbDsEndpoint_Cancel(interface->endpoint_in); + eventWait(&interface->endpoint_in->CompletionEvent, UINT64_MAX); + eventClear(&interface->endpoint_in->CompletionEvent); + return rc; + } + eventClear(&interface->endpoint_in->CompletionEvent); + + rc = usbDsEndpoint_GetReportData(interface->endpoint_in, &reportdata); + if (R_FAILED(rc)) return rc; + + rc = usbDsParseReportData(&reportdata, urbId, NULL, &tmp_transferredSize); + if (R_FAILED(rc)) return rc; + + if (tmp_transferredSize > chunksize) tmp_transferredSize = chunksize; + + total_transferredSize+= (size_t)tmp_transferredSize; + + bufptr+= tmp_transferredSize; + size-= tmp_transferredSize; + + if (tmp_transferredSize < chunksize) break; + } + + if (transferredSize) *transferredSize = total_transferredSize; + + return rc; +} + +size_t tinleaf_usbCommsReadEx(void* buffer, size_t size, u32 interface, u64 timeout) +{ + size_t transferredSize=0; + Result rc; + usbCommsInterface *inter = &g_usbCommsInterfaces[interface]; + bool initialized; + + if (interface>=TOTAL_INTERFACES) return 0; + + rwlockReadLock(&inter->lock); + initialized = inter->initialized; + rwlockReadUnlock(&inter->lock); + if (!initialized) return 0; + + rwlockWriteLock(&inter->lock_in); + rc = _usbCommsRead(&g_usbCommsInterfaces[interface], buffer, size, &transferredSize, timeout); + rwlockWriteUnlock(&inter->lock_in); + if (R_SUCCEEDED(rc)) return transferredSize; + else if (R_FAILED(rc)) return 0; + return 0; +} + +size_t tinleaf_usbCommsRead(void* buffer, size_t size, u64 timeout) +{ + return tinleaf_usbCommsReadEx(buffer, size, 0, timeout); +} + +size_t tinleaf_usbCommsWriteEx(const void* buffer, size_t size, u32 interface, u64 timeout) +{ + size_t transferredSize=0; + Result rc; + usbCommsInterface *inter = &g_usbCommsInterfaces[interface]; + bool initialized; + + if (interface>=TOTAL_INTERFACES) return 0; + + rwlockReadLock(&inter->lock); + initialized = inter->initialized; + rwlockReadUnlock(&inter->lock); + if (!initialized) return 0; + + rwlockWriteLock(&inter->lock_in); + rc = _usbCommsWrite(&g_usbCommsInterfaces[interface], buffer, size, &transferredSize, timeout); + rwlockWriteUnlock(&inter->lock_in); + if (R_SUCCEEDED(rc)) return transferredSize; + else if (R_FAILED(rc)) return 0; + return 0; +} + +size_t tinleaf_usbCommsWrite(const void* buffer, size_t size, u64 timeout) +{ + return tinleaf_usbCommsWriteEx(buffer, size, 0, timeout); +} + diff --git a/source/util/usb_util.cpp b/source/util/usb_util.cpp new file mode 100644 index 0000000..a377fd2 --- /dev/null +++ b/source/util/usb_util.cpp @@ -0,0 +1,105 @@ +/* +Copyright (c) 2017-2018 Adubbz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "util/usb_util.hpp" +#include "util/usb_comms_tinleaf.h" + +#include "data/byte_buffer.hpp" +#include "debug.h" +#include "error.hpp" + +namespace tin::util +{ + void USBCmdManager::SendCmdHeader(u32 cmdId, size_t dataSize) + { + USBCmdHeader header; + header.magic = 0x30435554; // TUC0 (Tinfoil USB Command 0) + header.type = USBCmdType::REQUEST; + header.cmdId = cmdId; + header.dataSize = dataSize; + + USBWrite(&header, sizeof(USBCmdHeader)); + } + + void USBCmdManager::SendExitCmd() + { + USBCmdManager::SendCmdHeader(0, 0); + } + + USBCmdHeader USBCmdManager::SendFileRangeCmd(std::string nspName, u64 offset, u64 size) + { + struct FileRangeCmdHeader + { + u64 size; + u64 offset; + u64 nspNameLen; + u64 padding; + } fRangeHeader; + + fRangeHeader.size = size; + fRangeHeader.offset = offset; + fRangeHeader.nspNameLen = nspName.size(); + fRangeHeader.padding = 0; + + USBCmdManager::SendCmdHeader(1, sizeof(FileRangeCmdHeader) + fRangeHeader.nspNameLen); + USBWrite(&fRangeHeader, sizeof(FileRangeCmdHeader)); + USBWrite(nspName.c_str(), fRangeHeader.nspNameLen); + + USBCmdHeader responseHeader; + USBRead(&responseHeader, sizeof(USBCmdHeader)); + return responseHeader; + } + + size_t USBRead(void* out, size_t len, u64 timeout) + { + u8* tmpBuf = (u8*)out; + size_t sizeRemaining = len; + size_t tmpSizeRead = 0; + + while (sizeRemaining) + { + tmpSizeRead = tinleaf_usbCommsRead(tmpBuf, sizeRemaining, timeout); + if (tmpSizeRead == 0) return 0; + tmpBuf += tmpSizeRead; + sizeRemaining -= tmpSizeRead; + } + + return len; + } + + size_t USBWrite(const void* in, size_t len, u64 timeout) + { + const u8 *bufptr = (const u8 *)in; + size_t cursize = len; + size_t tmpsize = 0; + + while (cursize) + { + tmpsize = tinleaf_usbCommsWrite(bufptr, cursize, timeout); + if (tmpsize == 0) return 0; + bufptr += tmpsize; + cursize -= tmpsize; + } + + return len; + } +} \ No newline at end of file diff --git a/source/util/util.cpp b/source/util/util.cpp new file mode 100644 index 0000000..a18582a --- /dev/null +++ b/source/util/util.cpp @@ -0,0 +1,317 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "switch.h" +#include "util/util.hpp" +#include "nx/ipc/tin_ipc.h" +#include "util/config.hpp" +#include "util/curl.hpp" +#include "ui/MainApplication.hpp" +#include "util/usb_comms_tinleaf.h" +#include "util/json.hpp" +#include "nx/usbhdd.h" + +namespace inst::util { + void initApp () { + // Seethe + //if (!pu::IsReiNX()) pu::IsAtmosphere(); + if (!std::filesystem::exists("sdmc:/switch")) std::filesystem::create_directory("sdmc:/switch"); + if (!std::filesystem::exists(inst::config::appDir)) std::filesystem::create_directory(inst::config::appDir); + inst::config::parseConfig(); + + socketInitializeDefault(); + #ifdef __DEBUG__ + nxlinkStdio(); + #endif + tinleaf_usbCommsInitialize(); + + nx::hdd::init(); + } + + void deinitApp () { + nx::hdd::exit(); + socketExit(); + tinleaf_usbCommsExit(); + } + + void initInstallServices() { + ncmInitialize(); + nsInitialize(); + nsextInitialize(); + esInitialize(); + splCryptoInitialize(); + splInitialize(); + } + + void deinitInstallServices() { + ncmExit(); + nsExit(); + nsextExit(); + esExit(); + splCryptoExit(); + splExit(); + } + + struct caseInsensitiveLess : public std::binary_function< char,char,bool > { + bool operator () (char x, char y) const { + return toupper(static_cast< unsigned char >(x)) < toupper(static_cast< unsigned char >(y)); + } + }; + + bool ignoreCaseCompare(const std::string &a, const std::string &b) { + return std::lexicographical_compare(a.begin(), a.end() , b.begin() ,b.end() , caseInsensitiveLess()); + } + + std::vector getDirectoryFiles(const std::string & dir, const std::vector & extensions) { + std::vector files; + for(auto & p: std::filesystem::directory_iterator(dir)) + { + if (std::filesystem::is_regular_file(p)) + { + std::string ourExtension = p.path().extension().string(); + std::transform(ourExtension.begin(), ourExtension.end(), ourExtension.begin(), ::tolower); + if (extensions.empty() || std::find(extensions.begin(), extensions.end(), ourExtension) != extensions.end()) + { + files.push_back(p.path()); + } + } + } + std::sort(files.begin(), files.end(), ignoreCaseCompare); + return files; + } + + std::vector getDirsAtPath(const std::string & dir) { + std::vector files; + for(auto & p: std::filesystem::directory_iterator(dir)) + { + if (std::filesystem::is_directory(p)) + { + files.push_back(p.path()); + } + } + std::sort(files.begin(), files.end(), ignoreCaseCompare); + return files; + } + + bool removeDirectory(std::string dir) { + try { + for(auto & p: std::filesystem::recursive_directory_iterator(dir)) + { + if (std::filesystem::is_regular_file(p)) + { + std::filesystem::remove(p); + } + } + rmdir(dir.c_str()); + return true; + } + catch (std::filesystem::filesystem_error & e) { + return false; + } + } + + bool copyFile(std::string inFile, std::string outFile) { + char ch; + std::ifstream f1(inFile); + std::ofstream f2(outFile); + + if(!f1 || !f2) return false; + + while(f1 && f1.get(ch)) f2.put(ch); + return true; + } + + std::string formatUrlString(std::string ourString) { + std::stringstream ourStream(ourString); + std::string segment; + std::vector seglist; + + while(std::getline(ourStream, segment, '/')) { + seglist.push_back(segment); + } + + CURL *curl = curl_easy_init(); + int outlength; + std::string finalString = curl_easy_unescape(curl, seglist[seglist.size() - 1].c_str(), seglist[seglist.size() - 1].length(), &outlength); + curl_easy_cleanup(curl); + + return finalString; + } + + std::string shortenString(std::string ourString, int ourLength, bool isFile) { + std::filesystem::path ourStringAsAPath = ourString; + std::string ourExtension = ourStringAsAPath.extension().string(); + if (ourString.size() - ourExtension.size() > (unsigned long)ourLength) { + if(isFile) return (std::string)ourString.substr(0,ourLength) + "(...)" + ourExtension; + else return (std::string)ourString.substr(0,ourLength) + "..."; + } else return ourString; + } + + std::string readTextFromFile(std::string ourFile) { + if (std::filesystem::exists(ourFile)) { + FILE * file = fopen(ourFile.c_str(), "r"); + char line[1024]; + fgets(line, 1024, file); + std::string url = line; + fflush(file); + fclose(file); + return url; + } + return ""; + } + + std::string softwareKeyboard(std::string guideText, std::string initialText, int LenMax) { + Result rc=0; + SwkbdConfig kbd; + char tmpoutstr[LenMax + 1] = {0}; + rc = swkbdCreate(&kbd, 0); + if (R_SUCCEEDED(rc)) { + swkbdConfigMakePresetDefault(&kbd); + swkbdConfigSetGuideText(&kbd, guideText.c_str()); + swkbdConfigSetInitialText(&kbd, initialText.c_str()); + swkbdConfigSetStringLenMax(&kbd, LenMax); + rc = swkbdShow(&kbd, tmpoutstr, sizeof(tmpoutstr)); + swkbdClose(&kbd); + if (R_SUCCEEDED(rc) && tmpoutstr[0] != 0) return(((std::string)(tmpoutstr))); + } + return ""; + } + + std::string getDriveFileName(std::string fileId) { + std::string htmlData = inst::curl::downloadToBuffer("https://drive.google.com/file/d/" + fileId + "/view"); + if (htmlData.size() > 0) { + std::smatch ourMatches; + std::regex ourRegex("\\s*(.+?)\\s*"); + std::regex_search(htmlData, ourMatches, ourRegex); + if (ourMatches.size() > 1) { + if (ourMatches[1].str() == "Google Drive -- Page Not Found") return ""; + return ourMatches[1].str().substr(0, ourMatches[1].str().size() - 15); + } + } + return ""; + } + + std::vector setClockSpeed(int deviceToClock, uint32_t clockSpeed) { + uint32_t hz = 0; + uint32_t previousHz = 0; + + if (deviceToClock > 2 || deviceToClock < 0) return {0,0}; + + if(hosversionAtLeast(8,0,0)) { + ClkrstSession session = {0}; + PcvModuleId pcvModuleId; + pcvInitialize(); + clkrstInitialize(); + + switch (deviceToClock) { + case 0: + pcvGetModuleId(&pcvModuleId, PcvModule_CpuBus); + break; + case 1: + pcvGetModuleId(&pcvModuleId, PcvModule_GPU); + break; + case 2: + pcvGetModuleId(&pcvModuleId, PcvModule_EMC); + break; + } + + clkrstOpenSession(&session, pcvModuleId, 3); + clkrstGetClockRate(&session, &previousHz); + clkrstSetClockRate(&session, clockSpeed); + clkrstGetClockRate(&session, &hz); + + pcvExit(); + clkrstCloseSession(&session); + clkrstExit(); + + return {previousHz, hz}; + } else { + PcvModule pcvModule; + pcvInitialize(); + + switch (deviceToClock) { + case 0: + pcvModule = PcvModule_CpuBus; + break; + case 1: + pcvModule = PcvModule_GPU; + break; + case 2: + pcvModule = PcvModule_EMC; + break; + } + + pcvGetClockRate(pcvModule, &previousHz); + pcvSetClockRate(pcvModule, clockSpeed); + pcvGetClockRate(pcvModule, &hz); + + pcvExit(); + + return {previousHz, hz}; + } + } + + std::string getIPAddress() { + struct in_addr addr = {(in_addr_t) gethostid()}; + return inet_ntoa(addr); + } + + int getUsbState() { + UsbState usbState = UsbState_Detached; + usbDsGetState(&usbState); + return (u32)usbState; + } + + void playAudio(std::string audioPath) { + int audio_rate = 22050; + Uint16 audio_format = AUDIO_S16SYS; + int audio_channels = 2; + int audio_buffers = 4096; + + if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) != 0) return; + + Mix_Chunk *sound = NULL; + sound = Mix_LoadWAV(audioPath.c_str()); + if(sound == NULL) { + Mix_FreeChunk(sound); + Mix_CloseAudio(); + return; + } + + int channel = Mix_PlayChannel(-1, sound, 0); + if(channel == -1) { + Mix_FreeChunk(sound); + Mix_CloseAudio(); + return; + } + + while(Mix_Playing(channel) != 0); + + Mix_FreeChunk(sound); + Mix_CloseAudio(); + + return; + } + + 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); + if (jsonData.size() == 0) return {}; + nlohmann::json ourJson = nlohmann::json::parse(jsonData); + if (ourJson["tag_name"].get() != inst::config::appVersion) { + std::vector ourUpdateInfo = {ourJson["tag_name"].get(), ourJson["assets"][0]["browser_download_url"].get()}; + inst::config::updateInfo = ourUpdateInfo; + return ourUpdateInfo; + } + } catch (...) {} + return {}; + } +} \ No newline at end of file