Add files via upload

This commit is contained in:
mrdude2478 2022-04-26 01:17:42 +01:00 committed by GitHub
parent aa4ac15957
commit c1fb59c71a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 7995 additions and 0 deletions

168
source/HDInstall.cpp Normal file
View File

@ -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 <cstring>
#include <string>
#include <sstream>
#include <filesystem>
#include <ctime>
#include <thread>
#include <memory>
#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<std::filesystem::path> 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<int> 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<tin::install::Install> installTask;
if (ourTitleList[titleItr].extension() == ".xci" || ourTitleList[titleItr].extension() == ".xcz") {
auto sdmcXCI = std::make_shared<tin::install::xci::SDMCXCI>(ourTitleList[titleItr]);
installTask = std::make_unique<tin::install::xci::XCIInstallTask>(m_destStorageId, inst::config::ignoreReqVers, sdmcXCI);
} else {
auto sdmcNSP = std::make_shared<tin::install::nsp::SDMCNSP>(ourTitleList[titleItr]);
installTask = std::make_unique<tin::install::nsp::NSPInstall>(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;
}
}

View File

@ -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 <climits>
#include <math.h>
#include <algorithm>
#include <exception>
#include "util/error.hpp"
#include "util/debug.h"
namespace tin::data
{
int NUM_BUFFER_SEGMENTS;
BufferedPlaceholderWriter::BufferedPlaceholderWriter(std::shared_ptr<nx::ncm::ContentStorage>& 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<BufferSegment[]>(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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

154
source/install/http_nsp.cpp Normal file
View File

@ -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 <switch.h>
#include <threads.h>
#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<StreamFuncArgs*>(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<StreamFuncArgs*>(in);
while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete() && !stopThreadsHttpNsp)
{
if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder())
args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder();
}
return 0;
}
void HTTPNSP::StreamToPlaceholder(std::shared_ptr<nx::ncm::ContentStorage>& 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);
}
}

162
source/install/http_xci.cpp Normal file
View File

@ -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 <threads.h>
#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<StreamFuncArgs*>(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<StreamFuncArgs*>(in);
while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete() && !stopThreadsHttpXci)
{
if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder())
args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder();
}
return 0;
}
void HTTPXCI::StreamToPlaceholder(std::shared_ptr<nx::ncm::ContentStorage>& 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);
}
}

192
source/install/install.cpp Normal file
View File

@ -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 <switch.h>
#include <cstring>
#include <memory>
#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<ContentStorageRecord> 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<ContentStorageRecord[]>(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<std::tuple<nx::ncm::ContentMeta, NcmContentInfo>> tupelList = this->ReadCNMT();
for (size_t i = 0; i < tupelList.size(); i++) {
std::tuple<nx::ncm::ContentMeta, NcmContentInfo> 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<NcmContentMetaType>(m_contentMeta[i].GetContentMetaKey().type);
}
}

View File

@ -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 <machine/endian.h>
#include <thread>
#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<NSP>& remoteNSP) :
Install(destStorageId, ignoreReqFirmVersion), m_NSP(remoteNSP)
{
m_NSP->RetrieveHeader();
}
std::vector<std::tuple<nx::ncm::ContentMeta, NcmContentInfo>> NSPInstall::ReadCNMT()
{
std::vector<std::tuple<nx::ncm::ContentMeta, NcmContentInfo>> 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<nx::ncm::ContentStorage> 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<const PFS0FileEntry*> tikFileEntries = m_NSP->GetFileEntriesByExtension("tik");
std::vector<const PFS0FileEntry*> 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<u8[]>(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<u8[]>(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");
}
}
}

View File

@ -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 <thread>
#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>& xci) :
Install(destStorageId, ignoreReqFirmVersion), m_xci(xci)
{
m_xci->RetrieveHeader();
}
std::vector<std::tuple<nx::ncm::ContentMeta, NcmContentInfo>> XCIInstallTask::ReadCNMT()
{
std::vector<std::tuple<nx::ncm::ContentMeta, NcmContentInfo>> 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<nx::ncm::ContentStorage> 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<const HFS0FileEntry*> tikFileEntries = m_xci->GetFileEntriesByExtension("tik");
std::vector<const HFS0FileEntry*> 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<u8[]>(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<u8[]>(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");
}
}
}

143
source/install/nsp.cpp Normal file
View File

@ -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 <threads.h>
#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<PFS0FileEntry*>(m_headerBytes.data() + fileEntryOffset);
}
std::vector<const PFS0FileEntry*> NSP::GetFileEntriesByExtension(std::string extension)
{
std::vector<const PFS0FileEntry*> 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<const char*>(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<PFS0BaseHeader*>(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();
}
}

View File

@ -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<nx::ncm::ContentStorage>& 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<u8[]>(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);
}
}

View File

@ -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<nx::ncm::ContentStorage>& 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<u8[]>(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);
}
}

View File

@ -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 <exception>
#include <memory>
#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<FsDirectoryEntry[]>(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 "";
}
}

193
source/install/usb_nsp.cpp Normal file
View File

@ -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 <switch.h>
#include <algorithm>
#include <malloc.h>
#include <threads.h>
#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<USBFuncArgs*>(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<USBFuncArgs*>(in);
while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete() && !stopThreadsUsbNsp)
{
if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder())
args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder();
}
return 0;
}
void USBNSP::StreamToPlaceholder(std::shared_ptr<nx::ncm::ContentStorage>& 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);
}
}

192
source/install/usb_xci.cpp Normal file
View File

@ -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 <switch.h>
#include <algorithm>
#include <malloc.h>
#include <threads.h>
#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<USBFuncArgs*>(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<USBFuncArgs*>(in);
while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete() && !stopThreadsUsbXci)
{
if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder())
args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder();
}
return 0;
}
void USBXCI::StreamToPlaceholder(std::shared_ptr<nx::ncm::ContentStorage>& 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);
}
}

174
source/install/xci.cpp Normal file
View File

@ -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<u8> 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<HFS0BaseHeader*>(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<HFS0BaseHeader*>(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<HFS0BaseHeader*>(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<const HFS0FileEntry*> XCI::GetFileEntriesByExtension(std::string extension)
{
std::vector<const HFS0FileEntry*> 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);
}
}

34
source/main.cpp Normal file
View File

@ -0,0 +1,34 @@
#include <thread>
#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;
}

336
source/netInstall.cpp Normal file
View File

@ -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 <cstring>
#include <string>
#include <sys/socket.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <sstream>
#include <curl/curl.h>
#include <thread>
#include <switch.h>
#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<std::string> ourUrlList, int ourStorage, std::vector<std::string> 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<std::string> 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<int> 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<tin::install::Install> installTask;
if (inst::curl::downloadToBuffer(ourUrlList[urlItr], 0x100, 0x103) == "HEAD") {
auto httpXCI = std::make_shared<tin::install::xci::HTTPXCI>(ourUrlList[urlItr]);
installTask = std::make_unique<tin::install::xci::XCIInstallTask>(m_destStorageId, inst::config::ignoreReqVers, httpXCI);
} else {
auto httpNSP = std::make_shared<tin::install::nsp::HTTPNSP>(ourUrlList[urlItr]);
installTask = std::make_unique<tin::install::nsp::NSPInstall>(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<std::string> 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<std::string> 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<char[]>(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 {};
}
}
}

131
source/nx/content_meta.cpp Normal file
View File

@ -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 <string.h>
#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<PackagedContentMetaHeader>(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<NcmContentMetaType>(contentMetaHeader.type);
return metaRecord;
}
// TODO: Cache this
std::vector<NcmContentInfo> ContentMeta::GetContentInfos()
{
PackagedContentMetaHeader contentMetaHeader = this->GetPackagedContentMetaHeader();
std::vector<NcmContentInfo> 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<u8>(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<NcmContentInfo> 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<NcmContentMetaHeader>(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<u32>(0, sizeof(NcmContentMetaHeader) + 8);
}
// Setup cnmt content record
installContentMetaBuffer.Append<NcmContentInfo>(cnmtNcmContentInfo);
// Setup the content records
for (auto& contentInfo : contentInfos)
{
installContentMetaBuffer.Append<NcmContentInfo>(contentInfo);
}
if (packagedContentMetaHeader.type == NcmContentMetaType_Patch)
{
NcmPatchMetaExtendedHeader* patchMetaExtendedHeader = (NcmPatchMetaExtendedHeader*)extendedHeaderSourceBytes;
installContentMetaBuffer.Resize(installContentMetaBuffer.GetSize() + patchMetaExtendedHeader->extended_data_size);
}
}
}

156
source/nx/fs.cpp Normal file
View File

@ -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 <cstring>
#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);
}
}

145
source/nx/ipc/es.c Normal file
View File

@ -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 <string.h>
#include <switch.h>
#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;
}

197
source/nx/ipc/ns_ext.c Normal file
View File

@ -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 <stdio.h>
#include <string.h>
#include <switch.h>
#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);
}

View File

@ -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 <switch.h>
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), ())

478
source/nx/nca_writer.cpp Normal file
View File

@ -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 <zstd.h>
#include <string.h>
#include "util/crypto.hpp"
#include "util/config.hpp"
#include "util/title_util.hpp"
#include "install/nca.hpp"
void append(std::vector<u8>& 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<nx::ncm::ContentStorage>& 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<nx::ncm::ContentStorage>& 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<u8> m_buffer;
std::vector<u8> m_deflateBuffer;
bool m_sectionsInitialized = false;
std::vector<NczHeader::SectionContext*> sections;
};
NcaWriter::NcaWriter(const NcmContentId& ncaId, std::shared_ptr<nx::ncm::ContentStorage>& 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<NcaBodyWriter>(new NczBodyWriter(m_ncaId, m_buffer.size(), m_contentStorage));
}
else
{
m_writer = std::shared_ptr<NcaBodyWriter>(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());
}
}

76
source/nx/ncm.cpp Normal file
View File

@ -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 &registeredId, size_t size)
{
ASSERT_OK(ncmContentStorageCreatePlaceHolder(&m_contentStorage, &placeholderId, &registeredId, 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 &registeredId)
{
ASSERT_OK(ncmContentStorageRegister(&m_contentStorage, &registeredId, &placeholderId), "Failed to register placeholder NCA");
}
void ContentStorage::Delete(const NcmContentId &registeredId)
{
ASSERT_OK(ncmContentStorageDelete(&m_contentStorage, &registeredId), "Failed to delete registered NCA");
}
bool ContentStorage::Has(const NcmContentId &registeredId)
{
bool hasNCA = false;
ASSERT_OK(ncmContentStorageHas(&m_contentStorage, &hasNCA, &registeredId), "Failed to check if NCA is present");
return hasNCA;
}
std::string ContentStorage::GetPath(const NcmContentId &registeredId)
{
char pathBuf[FS_MAX_PATH] = {0};
ASSERT_OK(ncmContentStorageGetPath(&m_contentStorage, pathBuf, FS_MAX_PATH, &registeredId), "Failed to get installed NCA path");
return std::string(pathBuf);
}
}

190
source/nx/usbhdd.cpp Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <dirent.h>
#include <threads.h>
#include <usbhsfs.h>
#include <mutex>
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;
}
}

169
source/sdInstall.cpp Normal file
View File

@ -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 <cstring>
#include <string>
#include <sstream>
#include <filesystem>
#include <ctime>
#include <thread>
#include <memory>
#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<std::filesystem::path> 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<int> 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<tin::install::Install> installTask;
if (ourTitleList[titleItr].extension() == ".xci" || ourTitleList[titleItr].extension() == ".xcz") {
auto sdmcXCI = std::make_shared<tin::install::xci::SDMCXCI>(ourTitleList[titleItr]);
installTask = std::make_unique<tin::install::xci::XCIInstallTask>(m_destStorageId, inst::config::ignoreReqVers, sdmcXCI);
} else {
auto sdmcNSP = std::make_shared<tin::install::nsp::SDMCNSP>(ourTitleList[titleItr]);
installTask = std::make_unique<tin::install::nsp::NSPInstall>(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;
}
}

73
source/sigInstall.cpp Normal file
View File

@ -0,0 +1,73 @@
#include <switch.h>
#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();
}
}

188
source/ui/HDInstPage.cpp Normal file
View File

@ -0,0 +1,188 @@
#include <filesystem>
#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();
}
}
}

View File

@ -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);
}
}

81
source/ui/instPage.cpp Normal file
View File

@ -0,0 +1,81 @@
#include <filesystem>
#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) {
}
}

305
source/ui/mainPage.cpp Normal file
View File

@ -0,0 +1,305 @@
#include <filesystem>
#include <iostream>
#include <sstream>
#include <switch.h>
#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 <sys/statvfs.h>
#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<unsigned long>(stat.f_blocks);
const auto available = static_cast<unsigned long>(stat.f_bavail);
const auto availableToRoot = static_cast<unsigned long>(stat.f_bfree);
const auto used = total - availableToRoot;
const auto nonRootTotal = used + available;
return 100.0 * static_cast<double>(used) / static_cast<double>(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();
}
}
}

193
source/ui/netInstPage.cpp Normal file
View File

@ -0,0 +1,193 @@
#include <filesystem>
#include <switch.h>
#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);
}
}
}

305
source/ui/optionsPage.cpp Normal file
View File

@ -0,0 +1,305 @@
#include <filesystem>
#include <switch.h>
#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<std::string> 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<std::string> 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<std::string> downloadUrl;
std::vector<std::string> 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;
}
}
}
}

179
source/ui/sdInstPage.cpp Normal file
View File

@ -0,0 +1,179 @@
#include <filesystem>
#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();
}
}
}

145
source/ui/usbInstPage.cpp Normal file
View File

@ -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();
}
}
}

193
source/usbInstall.cpp Normal file
View File

@ -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 <string>
#include <thread>
#include <malloc.h>
#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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<int> 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<tin::install::Install> installTask;
if (ourTitleList[fileItr].compare(ourTitleList[fileItr].size() - 3, 2, "xc") == 0) {
auto usbXCI = std::make_shared<tin::install::xci::USBXCI>(ourTitleList[fileItr]);
installTask = std::make_unique<tin::install::xci::XCIInstallTask>(m_destStorageId, inst::config::ignoreReqVers, usbXCI);
} else {
auto usbNSP = std::make_shared<tin::install::nsp::USBNSP>(ourTitleList[fileItr]);
installTask = std::make_unique<tin::install::nsp::NSPInstall>(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;
}
}

71
source/util/config.cpp Normal file
View File

@ -0,0 +1,71 @@
#include <fstream>
#include <iomanip>
#include "util/config.hpp"
#include "util/json.hpp"
namespace inst::config {
std::string gAuthKey;
std::string sigPatchesUrl;
std::vector<std::string> 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<bool>();
deletePrompt = j["deletePrompt"].get<bool>();
gAuthKey = j["gAuthKey"].get<std::string>();
gayMode = j["gayMode"].get<bool>();
useSound = j["useSound"].get<bool>();
ignoreReqVers = j["ignoreReqVers"].get<bool>();
languageSetting = j["languageSetting"].get<int>();
overClock = j["overClock"].get<bool>();
sigPatchesUrl = j["sigPatchesUrl"].get<std::string>();
usbAck = j["usbAck"].get<bool>();
validateNCAs = j["validateNCAs"].get<bool>();
}
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();
}
}
}

90
source/util/crypto.cpp Normal file
View File

@ -0,0 +1,90 @@
#include "util/crypto.hpp"
#include <string.h>
#include <mbedtls/bignum.h>
#include <stdexcept>
#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;
}

102
source/util/curl.cpp Normal file
View File

@ -0,0 +1,102 @@
#include <curl/curl.h>
#include <string>
#include <sstream>
#include <iostream>
#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 "";
}
}
}

53
source/util/debug.c Normal file
View File

@ -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 <string.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <arpa/inet.h>
#include <unistd.h>
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
}

53
source/util/file_util.cpp Normal file
View File

@ -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 <memory>
#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());
}
}

85
source/util/lang.cpp Normal file
View File

@ -0,0 +1,85 @@
#include <iostream>
#include <switch.h>
#include <filesystem>
#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>();
}
std::string GetRandomMsg() {
json j = Language::GetRelativeJson(lang, "inst.finished");
srand(time(NULL));
return(j[rand() % j.size()]);
}
}

View File

@ -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 <switch.h>
#include <curl/curl.h>
#include <algorithm>
#include <cstring>
#include <sstream>
#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<HTTPHeader*>(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<std::function<size_t (u8* bytes, size_t size)>*>(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<void (size_t sizeRead)> 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<u8*>(buffer) + sizeRead, streamBuf, streamBufSize);
sizeRead += streamBufSize;
return streamBufSize;
};
this->StreamDataRange(offset, size, streamFunc);
}
int HTTPDownload::StreamDataRange(size_t offset, size_t size, std::function<size_t (u8* bytes, size_t size)> 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;
}
}

134
source/util/title_util.cpp Normal file
View File

@ -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 <machine/endian.h>
#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;
}
}

161
source/util/unzip.cpp Normal file
View File

@ -0,0 +1,161 @@
#include <minizip/unzip.h>
#include <algorithm>
#include <dirent.h>
#include <string>
#include <cstring>
#include <switch.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
// 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;
}
}

View File

@ -0,0 +1,595 @@
#include <string.h>
#include <malloc.h>
#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 <stdio.h>
#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; i<TOTAL_INTERFACES; i++)
{
_usbCommsInterfaceFree(&g_usbCommsInterfaces[i]);
}
}
static Result _usbCommsInterfaceInit(u32 intf_ind, const tinleaf_UsbCommsInterfaceInfo *info)
{
if (hosversionAtLeast(5,0,0)) {
return _usbCommsInterfaceInit5x(intf_ind, info);
} else {
return _usbCommsInterfaceInit1x(intf_ind, info);
}
}
static Result _usbCommsInterfaceInit5x(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 = 4,
.bNumEndpoints = 2,
.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 = 0x40,
};
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 = 0x40,
};
struct usb_ss_endpoint_companion_descriptor endpoint_companion = {
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
.bMaxBurst = 0x0F,
.bmAttributes = 0x00,
.wBytesPerInterval = 0x00,
};
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;
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 (size<chunksize) chunksize = size;
transfer_type = 0;
}
else
{
transfer_buffer = bufptr;
chunksize = size;
transfer_type = 1;
}
//Start a host->device 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 (size<chunksize) chunksize = size;
memcpy(interface->endpoint_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);
}

105
source/util/usb_util.cpp Normal file
View File

@ -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;
}
}

317
source/util/util.cpp Normal file
View File

@ -0,0 +1,317 @@
#include <filesystem>
#include <vector>
#include <algorithm>
#include <fstream>
#include <unistd.h>
#include <curl/curl.h>
#include <regex>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#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<std::filesystem::path> getDirectoryFiles(const std::string & dir, const std::vector<std::string> & extensions) {
std::vector<std::filesystem::path> 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<std::filesystem::path> getDirsAtPath(const std::string & dir) {
std::vector<std::filesystem::path> 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<std::string> 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("<title>\\s*(.+?)\\s*</title>");
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<uint32_t> 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<std::string> 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<std::string>() != inst::config::appVersion) {
std::vector<std::string> ourUpdateInfo = {ourJson["tag_name"].get<std::string>(), ourJson["assets"][0]["browser_download_url"].get<std::string>()};
inst::config::updateInfo = ourUpdateInfo;
return ourUpdateInfo;
}
} catch (...) {}
return {};
}
}