mirror of
https://github.com/hax4dazy/TinWoo.git
synced 2025-02-09 19:25:05 +01:00
Add files via upload
This commit is contained in:
parent
db1a30f4a0
commit
0db990d4f3
181
source/HDInstall.cpp
Normal file
181
source/HDInstall.cpp
Normal 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 <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
|
||||
{
|
||||
int togo = ourTitleList.size();
|
||||
for (titleItr = 0; titleItr < ourTitleList.size(); titleItr++) {
|
||||
auto s = std::to_string(togo);
|
||||
inst::ui::instPage::filecount("inst.info_page.queue"_lang + s);
|
||||
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();
|
||||
togo = (togo - 1);
|
||||
}
|
||||
|
||||
inst::ui::instPage::filecount("inst.info_page.queue"_lang + "0");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
212
source/data/buffered_placeholder_writer.cpp
Normal file
212
source/data/buffered_placeholder_writer.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
55
source/data/byte_buffer.cpp
Normal file
55
source/data/byte_buffer.cpp
Normal 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);
|
||||
}
|
||||
}
|
41
source/data/byte_stream.cpp
Normal file
41
source/data/byte_stream.cpp
Normal 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
154
source/install/http_nsp.cpp
Normal 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
162
source/install/http_xci.cpp
Normal 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
192
source/install/install.cpp
Normal 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);
|
||||
}
|
||||
}
|
250
source/install/install_nsp.cpp
Normal file
250
source/install/install_nsp.cpp
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
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;
|
||||
ncmU64ToContentInfoSize(cnmtNcaSize & 0xFFFFFFFFFFFF, &cnmtContentInfo);
|
||||
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);
|
||||
//https://gbatemp.net/threads/nszip-nsp-compressor-decompressor-to-reduce-storage.530313/
|
||||
|
||||
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()
|
||||
{
|
||||
u16 ECDSA = 0;
|
||||
u16 RSA_2048 = 0;
|
||||
u16 RSA_4096 = 0;
|
||||
// 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");
|
||||
|
||||
// https://switchbrew.org/wiki/Ticket#Certificate_chain
|
||||
ECDSA = (0x4 + 0x3C + 0x40 + 0x146);
|
||||
RSA_2048 = (0x4 + 0x100 + 0x3C + 0x146);
|
||||
RSA_4096 = (0x4 + 0x200 + 0x3C + 0x146);
|
||||
|
||||
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);
|
||||
|
||||
// try to fix a temp ticket and change it t a permanent one
|
||||
if (inst::config::fixticket) {
|
||||
//ECDSA SHA256
|
||||
if (tikBuf.get()[0x0] == 0x5 && (tikBuf.get()[ECDSA] == 0x10 || tikBuf.get()[ECDSA] == 0x30))
|
||||
{
|
||||
tikBuf.get()[ECDSA] = 0x0;
|
||||
tikBuf.get()[ECDSA - 1] = 0x10; //fix broken Master key revision
|
||||
}
|
||||
|
||||
//RSA_2048 SHA256
|
||||
else if (tikBuf.get()[0x0] == 0x4 && (tikBuf.get()[RSA_2048] == 0x10 || tikBuf.get()[RSA_2048] == 0x30))
|
||||
{
|
||||
tikBuf.get()[RSA_2048] = 0x0;
|
||||
tikBuf.get()[RSA_2048 - 1] = 0x10;
|
||||
}
|
||||
|
||||
//RSA_4096 SHA256
|
||||
else if (tikBuf.get()[0x0] == 0x3 && (tikBuf.get()[RSA_4096] == 0x10 || tikBuf.get()[RSA_4096] == 0x30))
|
||||
{
|
||||
tikBuf.get()[RSA_4096] = 0x0;
|
||||
tikBuf.get()[RSA_4096 - 1] = 0x10;
|
||||
}
|
||||
|
||||
//ECDSA SHA1
|
||||
else if (tikBuf.get()[0x0] == 0x2 && (tikBuf.get()[ECDSA] == 0x10 || tikBuf.get()[ECDSA] == 0x30))
|
||||
{
|
||||
tikBuf.get()[ECDSA] = 0x0;
|
||||
tikBuf.get()[ECDSA - 1] = 0x10;
|
||||
}
|
||||
|
||||
//RSA_2048 SHA1
|
||||
else if (tikBuf.get()[0x0] == 0x1 && (tikBuf.get()[RSA_2048] == 0x10 || tikBuf.get()[RSA_2048] == 0x30))
|
||||
{
|
||||
tikBuf.get()[RSA_2048] = 0x0;
|
||||
tikBuf.get()[RSA_2048 - 1] = 0x10;
|
||||
}
|
||||
|
||||
//RSA_4096 SHA1
|
||||
else if (tikBuf.get()[0x0] == 0x0 && (tikBuf.get()[RSA_4096] == 0x10 || tikBuf.get()[RSA_4096] == 0x30))
|
||||
{
|
||||
tikBuf.get()[RSA_4096] = 0x0;
|
||||
tikBuf.get()[RSA_4096 - 1] = 0x10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//printout the cert and ticket to a file in the tinwoo directory for testing.
|
||||
/*
|
||||
FILE * pFile;
|
||||
pFile = fopen ("cert.hxd", "wb");
|
||||
fwrite (certBuf.get(), sizeof(char), certSize, pFile);
|
||||
fclose (pFile);
|
||||
|
||||
pFile = fopen ("tik.hxd", "wb");
|
||||
fwrite (tikBuf.get(), sizeof(char), tikSize, pFile);
|
||||
fclose (pFile);
|
||||
*/
|
||||
|
||||
// Finally, let's actually import the ticket
|
||||
ASSERT_OK(esImportTicket(tikBuf.get(), tikSize, certBuf.get(), certSize), "Failed to import ticket");
|
||||
}
|
||||
}
|
||||
}
|
237
source/install/install_xci.cpp
Normal file
237
source/install/install_xci.cpp
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
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;
|
||||
ncmU64ToContentInfoSize(cnmtNcaSize & 0xFFFFFFFFFFFF, &cnmtContentInfo);
|
||||
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()
|
||||
{
|
||||
u16 ECDSA = 0;
|
||||
u16 RSA_2048 = 0;
|
||||
u16 RSA_4096 = 0;
|
||||
|
||||
// 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");
|
||||
|
||||
// https://switchbrew.org/wiki/Ticket#Certificate_chain
|
||||
ECDSA = (0x4 + 0x3C + 0x40 + 0x146);
|
||||
RSA_2048 = (0x4 + 0x100 + 0x3C + 0x146);
|
||||
RSA_4096 = (0x4 + 0x200 + 0x3C + 0x146);
|
||||
|
||||
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);
|
||||
|
||||
// try to fix a temp ticket and change it t a permanent one
|
||||
if (inst::config::fixticket) {
|
||||
//ECDSA SHA256
|
||||
if (tikBuf.get()[0x0] == 0x5 && (tikBuf.get()[ECDSA] == 0x10 || tikBuf.get()[ECDSA] == 0x30))
|
||||
{
|
||||
tikBuf.get()[ECDSA] = 0x0;
|
||||
tikBuf.get()[ECDSA - 1] = 0x10; //fix broken Master key revision
|
||||
}
|
||||
|
||||
//RSA_2048 SHA256
|
||||
else if (tikBuf.get()[0x0] == 0x4 && (tikBuf.get()[RSA_2048] == 0x10 || tikBuf.get()[RSA_2048] == 0x30))
|
||||
{
|
||||
tikBuf.get()[RSA_2048] = 0x0;
|
||||
tikBuf.get()[RSA_2048 - 1] = 0x10;
|
||||
}
|
||||
|
||||
//RSA_4096 SHA256
|
||||
else if (tikBuf.get()[0x0] == 0x3 && (tikBuf.get()[RSA_4096] == 0x10 || tikBuf.get()[RSA_4096] == 0x30))
|
||||
{
|
||||
tikBuf.get()[RSA_4096] = 0x0;
|
||||
tikBuf.get()[RSA_4096 - 1] = 0x10;
|
||||
}
|
||||
|
||||
//ECDSA SHA1
|
||||
else if (tikBuf.get()[0x0] == 0x2 && (tikBuf.get()[ECDSA] == 0x10 || tikBuf.get()[ECDSA] == 0x30))
|
||||
{
|
||||
tikBuf.get()[ECDSA] = 0x0;
|
||||
tikBuf.get()[ECDSA - 1] = 0x10;
|
||||
}
|
||||
|
||||
//RSA_2048 SHA1
|
||||
else if (tikBuf.get()[0x0] == 0x1 && (tikBuf.get()[RSA_2048] == 0x10 || tikBuf.get()[RSA_2048] == 0x30))
|
||||
{
|
||||
tikBuf.get()[RSA_2048] = 0x0;
|
||||
tikBuf.get()[RSA_2048 - 1] = 0x10;
|
||||
}
|
||||
|
||||
//RSA_4096 SHA1
|
||||
else if (tikBuf.get()[0x0] == 0x0 && (tikBuf.get()[RSA_4096] == 0x10 || tikBuf.get()[RSA_4096] == 0x30))
|
||||
{
|
||||
tikBuf.get()[RSA_4096] = 0x0;
|
||||
tikBuf.get()[RSA_4096 - 1] = 0x10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Finally, let's actually import the ticket
|
||||
ASSERT_OK(esImportTicket(tikBuf.get(), tikSize, certBuf.get(), certSize), "Failed to import ticket");
|
||||
}
|
||||
}
|
||||
}
|
170
source/install/nsp.cpp
Normal file
170
source/install/nsp.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
//https://switchbrew.org/wiki/NCA
|
||||
|
||||
#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);
|
||||
|
||||
/* debug print header
|
||||
FILE * pFile;
|
||||
pFile = fopen ("header.hxd", "wb");
|
||||
fwrite (m_headerBytes.data(), sizeof(char), m_headerBytes.size(), pFile);
|
||||
fclose (pFile);
|
||||
*/
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
//print fileEntryOffset
|
||||
FILE * fp;
|
||||
fp = fopen ("offset.txt", "a+");
|
||||
size_t debug = fileEntryOffset;
|
||||
fprintf(fp, "%zu\n", debug);
|
||||
fclose(fp);
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
/*
|
||||
//print size of header
|
||||
FILE * fp;
|
||||
fp = fopen ("file.txt", "a+");
|
||||
u64 debug = m_headerBytes.size();
|
||||
fprintf(fp, "%lu\n", debug);
|
||||
fclose(fp);
|
||||
*/
|
||||
|
||||
return m_headerBytes.size();
|
||||
}
|
||||
}
|
75
source/install/sdmc_nsp.cpp
Normal file
75
source/install/sdmc_nsp.cpp
Normal 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);
|
||||
}
|
||||
}
|
75
source/install/sdmc_xci.cpp
Normal file
75
source/install/sdmc_xci.cpp
Normal 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);
|
||||
}
|
||||
}
|
89
source/install/simple_filesystem.cpp
Normal file
89
source/install/simple_filesystem.cpp
Normal 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
193
source/install/usb_nsp.cpp
Normal 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
192
source/install/usb_xci.cpp
Normal 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
174
source/install/xci.cpp
Normal 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);
|
||||
}
|
||||
}
|
35
source/main.cpp
Normal file
35
source/main.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#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;
|
||||
}
|
571
source/netInstall.cpp
Normal file
571
source/netInstall.cpp
Normal file
@ -0,0 +1,571 @@
|
||||
/*
|
||||
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 <list>
|
||||
#include <algorithm>
|
||||
#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 = 2048;
|
||||
const int REMOTE_INSTALL_PORT = 2000;
|
||||
static int m_serverSocket = 0;
|
||||
static int m_clientSocket = 0;
|
||||
bool netConnected = false;
|
||||
|
||||
namespace inst::ui {
|
||||
extern MainApplication* mainApp;
|
||||
}
|
||||
|
||||
namespace netInstStuff {
|
||||
|
||||
//Strip the filename from the url we are trying to download - ie list.txt, index.html etc...
|
||||
std::string stripfilename(const std::string& s) {
|
||||
|
||||
int pos = 0;
|
||||
std::string mystr = s;
|
||||
pos = mystr.find_last_of('/');
|
||||
|
||||
//if the url doesn't contain "/" after "http://" don't bother stripping as we won't need to.
|
||||
if (pos > 7) {
|
||||
mystr = mystr.substr(0, pos);
|
||||
}
|
||||
|
||||
/*
|
||||
//Debug code
|
||||
FILE * fp;
|
||||
fp = fopen ("link log.txt", "a+");
|
||||
const char *info = mystr.c_str();
|
||||
fprintf(fp, "%s\n", info);
|
||||
fclose(fp);
|
||||
*/
|
||||
|
||||
return mystr;
|
||||
}
|
||||
|
||||
//Find Case Insensitive Sub String in a given substring
|
||||
size_t findCaseInsensitive(std::string data, std::string toSearch, size_t pos = 0)
|
||||
{
|
||||
// Convert complete given String to lower case
|
||||
std::transform(data.begin(), data.end(), data.begin(), ::tolower);
|
||||
// Convert complete given Sub String to lower case
|
||||
std::transform(toSearch.begin(), toSearch.end(), toSearch.begin(), ::tolower);
|
||||
// Find sub string in given string
|
||||
return data.find(toSearch, pos);
|
||||
}
|
||||
|
||||
std::string urlencode(std::string str)
|
||||
{
|
||||
std::string new_str = "";
|
||||
char c;
|
||||
int ic;
|
||||
const char* chars = str.c_str();
|
||||
char bufHex[10];
|
||||
int len = strlen(chars);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
c = chars[i];
|
||||
ic = c;
|
||||
// bodge code needed to prevernt encoded url changing forward slash to %2F
|
||||
if (c == '/') new_str += '/';
|
||||
else if (c == ':') new_str += ':';
|
||||
else if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') new_str += c;
|
||||
else {
|
||||
sprintf(bufHex, "%X", c);
|
||||
if (ic < 16)
|
||||
new_str += "%0";
|
||||
else
|
||||
new_str += "%";
|
||||
new_str += bufHex;
|
||||
}
|
||||
}
|
||||
return new_str;
|
||||
}
|
||||
|
||||
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 {
|
||||
int togo = ourUrlList.size();
|
||||
for (urlItr = 0; urlItr < ourUrlList.size(); urlItr++) {
|
||||
auto s = std::to_string(togo);
|
||||
inst::ui::instPage::filecount("inst.info_page.queue"_lang + s);
|
||||
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();
|
||||
togo = (togo - 1);
|
||||
}
|
||||
|
||||
inst::ui::instPage::filecount("inst.info_page.queue"_lang + "0");
|
||||
}
|
||||
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()
|
||||
{
|
||||
/*
|
||||
https://switchbrew.github.io/libnx/hid_8h.html#aa163470a1a7b811662e5c38905cc86fba4d9ae7fa7e27704abaf86c8a8a5398bd
|
||||
*/
|
||||
padConfigureInput(8, HidNpadStyleSet_NpadStandard);
|
||||
PadState pad;
|
||||
padInitializeAny(&pad);
|
||||
|
||||
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");
|
||||
close(m_serverSocket); //close if already open.
|
||||
m_serverSocket = 0; //reset so we can try again.
|
||||
}
|
||||
}
|
||||
|
||||
std::string ourIPAddress = inst::util::getIPAddress();
|
||||
inst::ui::mainApp->netinstPage->pageInfoText->SetText("inst.net.top_info1"_lang + ourIPAddress);
|
||||
inst::ui::mainApp->CallForRender();
|
||||
netConnected = false;
|
||||
LOG_DEBUG("%s %s\n", "Switch IP is ", ourIPAddress.c_str());
|
||||
LOG_DEBUG("%s\n", "Waiting for network");
|
||||
LOG_DEBUG("%s\n", "B to cancel");
|
||||
|
||||
std::vector<std::string> urls;
|
||||
|
||||
while (true)
|
||||
{
|
||||
padUpdate(&pad);
|
||||
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
if (kDown & HidNpadButton_Minus) {
|
||||
std::string url;
|
||||
|
||||
if (inst::config::httpkeyboard) {
|
||||
url = inst::util::softwareKeyboard("inst.net.url.hint"_lang, inst::config::httpIndexUrl, 500);
|
||||
inst::config::httpIndexUrl = url;
|
||||
inst::config::setConfig();
|
||||
//refresh options page
|
||||
inst::ui::mainApp->optionspage->setMenuText();
|
||||
}
|
||||
else {
|
||||
url = inst::config::httpIndexUrl;
|
||||
}
|
||||
|
||||
if (url == "") {
|
||||
url = ("http://127.0.0.1");
|
||||
inst::ui::mainApp->CreateShowDialog("inst.net.help.title"_lang, "inst.net.help.blank"_lang, { "common.ok"_lang }, true);
|
||||
inst::config::httpIndexUrl = url;
|
||||
inst::config::setConfig();
|
||||
//refresh options page
|
||||
inst::ui::mainApp->optionspage->setMenuText();
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
std::string response;
|
||||
if (inst::util::formatUrlString(url) == "" || url == "https://" || url == "http://" || url == "HTTP://" || url == "HTTPS://")
|
||||
inst::ui::mainApp->CreateShowDialog("inst.net.url.invalid"_lang, "", { "common.ok"_lang }, false);
|
||||
else {
|
||||
if (url[url.size() - 1] != '/')
|
||||
|
||||
//First try and stream the links
|
||||
response = inst::curl::downloadToBuffer(url);
|
||||
|
||||
//If the above fails we probably have an html page - try to download it instead.
|
||||
if (response.empty()) {
|
||||
response = inst::curl::html_to_buffer(url);
|
||||
}
|
||||
|
||||
// debug - write the webpage to check for the game urls
|
||||
/*
|
||||
FILE * fp;
|
||||
fp = fopen ("index.html", "a+");
|
||||
auto *info = response.c_str();
|
||||
fprintf(fp, "%s", info);
|
||||
fclose(fp);
|
||||
*/
|
||||
//end of debug
|
||||
}
|
||||
|
||||
if (!response.empty()) {
|
||||
if (response[0] == '{')
|
||||
try {
|
||||
nlohmann::json j = nlohmann::json::parse(response);
|
||||
for (const auto& file : j["files"]) {
|
||||
/* info on dealing with c++ vector
|
||||
https://www.cplusplus.com/reference/vector/vector/
|
||||
https://www.cplusplus.com/reference/vector/vector/push_back/
|
||||
*/
|
||||
urls.push_back(file["url"]);
|
||||
}
|
||||
|
||||
/*
|
||||
//debug scan http links for size of file
|
||||
FILE * fp;
|
||||
fp = fopen ("http link log.txt", "a+");
|
||||
|
||||
for (const auto &file : j["files"]) {
|
||||
std::string url = file["url"];
|
||||
std::string size = file["size"];
|
||||
auto *info = url.c_str();
|
||||
auto *info2 = size.c_str();
|
||||
fprintf(fp, "%s-%sMB\n", info, info2);
|
||||
}
|
||||
fclose(fp);
|
||||
//end of debug
|
||||
//*/
|
||||
|
||||
return urls;
|
||||
}
|
||||
catch (const nlohmann::detail::exception& ex) {
|
||||
LOG_DEBUG("Failed to parse JSON\n");
|
||||
}
|
||||
|
||||
else if (response[0] == '<') {
|
||||
std::size_t index = 0;
|
||||
while (index < response.size()) {
|
||||
std::string link;
|
||||
auto found = findCaseInsensitive(response, "href=\"", index);
|
||||
if (found == std::string::npos)
|
||||
break;
|
||||
|
||||
index = found + 6;
|
||||
while (index < response.size()) {
|
||||
if (response[index] == '"') {
|
||||
if (link.find("../") == std::string::npos)
|
||||
if (findCaseInsensitive(link, ".nsp") != std::string::npos || findCaseInsensitive(link, ".nsz") != std::string::npos || findCaseInsensitive(link, ".xci") != std::string::npos || findCaseInsensitive(link, ".xcz") != std::string::npos) {
|
||||
if (inst::config::useoldphp) {
|
||||
link = urlencode(link);
|
||||
if (inst::config::streamhtmls) {
|
||||
std::string before_strip = stripfilename(url);
|
||||
urls.push_back(before_strip + "/" + link);
|
||||
}
|
||||
else {
|
||||
urls.push_back(link);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (inst::config::streamhtmls) {
|
||||
std::string before_strip = stripfilename(url);
|
||||
urls.push_back(before_strip + "/" + link);
|
||||
}
|
||||
else {
|
||||
urls.push_back(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
link += response[index++];
|
||||
}
|
||||
|
||||
}
|
||||
if (urls.size() > 0) {
|
||||
|
||||
//debug scan http links
|
||||
/*
|
||||
FILE * fp;
|
||||
fp = fopen ("http link log.txt", "a+");
|
||||
|
||||
for (unsigned long int i = 0; i < urls.size(); i++) {
|
||||
std::string debug = urls[i];
|
||||
const char *info = debug.c_str();
|
||||
fprintf(fp, "%s\n", info);
|
||||
}
|
||||
fclose(fp);
|
||||
*/
|
||||
//end of debug
|
||||
std::sort(urls.begin(), urls.end(), inst::util::ignoreCaseCompare);
|
||||
return urls;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Failed to parse games from HTML\n");
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
LOG_DEBUG("Failed to fetch game list\n");
|
||||
inst::ui::mainApp->CreateShowDialog("inst.net.index_error"_lang, "inst.net.index_error_info"_lang, { "common.ok"_lang }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct sockaddr_in client;
|
||||
socklen_t clientLen = sizeof(client);
|
||||
|
||||
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)
|
||||
{
|
||||
close(m_serverSocket);
|
||||
m_serverSocket = 0;
|
||||
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
131
source/nx/content_meta.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
220
source/nx/fs.cpp
Normal file
220
source/nx/fs.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
/*
|
||||
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 <switch.h>
|
||||
|
||||
#include <cmath>
|
||||
#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(), FsContentAttributes_All);
|
||||
|
||||
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.";
|
||||
else if (rc == 0x234c02)
|
||||
errorMsg = "Failed to open filesystem. Make sure your signature patches are up to date and set up properly!";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
std::string GetFreeStorageSpace() {
|
||||
s64 size = 0;
|
||||
std::string sizeStr = "";
|
||||
Result ret = 0;
|
||||
if (R_FAILED(ret = fsFsGetFreeSpace(fsdevGetDeviceFileSystem("sdmc:"), "/", &size))) {
|
||||
return sizeStr;
|
||||
}
|
||||
sizeStr = convertSize(size);
|
||||
return sizeStr;
|
||||
}
|
||||
|
||||
std::string convertSize(s64 size)
|
||||
{
|
||||
std::string sizeStr = "";
|
||||
double bytes = (double)size;
|
||||
if (bytes < 1000.0) {
|
||||
// Bytes
|
||||
sizeStr = std::to_string(bytes) + " bytes";
|
||||
}
|
||||
else {
|
||||
bytes = bytes / 1024;
|
||||
if (bytes < 1000.0) {
|
||||
// KiB
|
||||
bytes = round(bytes * 100.0) / 100.0;
|
||||
sizeStr = std::to_string(bytes);
|
||||
sizeStr.erase(sizeStr.find_last_not_of('0') + 1, std::string::npos);
|
||||
sizeStr = sizeStr + " KiB";
|
||||
}
|
||||
else {
|
||||
bytes = bytes / 1024;
|
||||
if (bytes < 1000.0) {
|
||||
// MiB
|
||||
bytes = round(bytes * 100.0) / 100.0;
|
||||
sizeStr = std::to_string(bytes);
|
||||
sizeStr.erase(sizeStr.find_last_not_of('0') + 1, std::string::npos);
|
||||
sizeStr = sizeStr + " MiB";
|
||||
}
|
||||
else {
|
||||
bytes = bytes / 1024;
|
||||
if (bytes < 1000.0) {
|
||||
// GiB
|
||||
bytes = round(bytes * 100.0) / 100.0;
|
||||
sizeStr = std::to_string(bytes);
|
||||
sizeStr.erase(sizeStr.find_last_not_of('0') + 1, std::string::npos);
|
||||
sizeStr = sizeStr + " GiB";
|
||||
}
|
||||
else {
|
||||
bytes = bytes / 1024;
|
||||
// TiB
|
||||
bytes = round(bytes * 100.0) / 100.0;
|
||||
sizeStr = std::to_string(bytes);
|
||||
sizeStr.erase(sizeStr.find_last_not_of('0') + 1, std::string::npos);
|
||||
sizeStr = sizeStr + " TiB";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sizeStr;
|
||||
}
|
||||
}
|
145
source/nx/ipc/es.c
Normal file
145
source/nx/ipc/es.c
Normal 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
197
source/nx/ipc/ns_ext.c
Normal 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);
|
||||
}
|
74
source/nx/ipc/service_guard.h
Normal file
74
source/nx/ipc/service_guard.h
Normal 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), ())
|
493
source/nx/nca_writer.cpp
Normal file
493
source/nx/nca_writer.cpp
Normal file
@ -0,0 +1,493 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
//code - https://github.com/nicoboss/nsz
|
||||
//https://github.com/nicoboss/nsz/blob/master/nsz/NszDecompressor.py
|
||||
//https://www.w3schools.com/cpp/cpp_classes.asp
|
||||
//https://github.com/minetest/minetestmapper/blob/master/ZstdDecompressor.cpp
|
||||
//https://github.com/nicoboss/nsz/blob/master/nsz/BlockDecompressorReader.py
|
||||
|
||||
class NczHeader
|
||||
{
|
||||
public:
|
||||
static const u64 MAGIC = 0x4E544345535A434E;
|
||||
static const u64 BLOCK = 0x4E435A424C4F434B; //at 0xD0 from magic address
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
encrypt(m_deflateBuffer.data(), m_deflateBuffer.size(), m_offset);
|
||||
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)
|
||||
{
|
||||
while (sz > 0)
|
||||
{
|
||||
const size_t readChunkSz = std::min(sz, buffInSize);
|
||||
ZSTD_inBuffer input = { ptr, readChunkSz, 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 0;
|
||||
}
|
||||
|
||||
size_t len = output.pos;
|
||||
u8* p = (u8*)buffOut;
|
||||
|
||||
while (len)
|
||||
{
|
||||
const size_t writeChunkSz = std::min(0x1000000 - m_deflateBuffer.size(), len);
|
||||
|
||||
append(m_deflateBuffer, p, writeChunkSz);
|
||||
|
||||
if (m_deflateBuffer.size() >= 0x1000000)
|
||||
{
|
||||
encrypt(m_deflateBuffer.data(), m_deflateBuffer.size(), m_offset);
|
||||
flush();
|
||||
}
|
||||
|
||||
p += writeChunkSz;
|
||||
len -= writeChunkSz;
|
||||
}
|
||||
}
|
||||
|
||||
sz -= readChunkSz;
|
||||
ptr += readChunkSz;
|
||||
}
|
||||
|
||||
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
76
source/nx/ncm.cpp
Normal 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, ®isteredId, size), "Failed to create placeholder");
|
||||
}
|
||||
|
||||
void ContentStorage::DeletePlaceholder(const NcmPlaceHolderId& placeholderId)
|
||||
{
|
||||
ASSERT_OK(ncmContentStorageDeletePlaceHolder(&m_contentStorage, &placeholderId), "Failed to delete placeholder");
|
||||
}
|
||||
|
||||
void ContentStorage::WritePlaceholder(const NcmPlaceHolderId& placeholderId, u64 offset, void* buffer, size_t bufSize)
|
||||
{
|
||||
ASSERT_OK(ncmContentStorageWritePlaceHolder(&m_contentStorage, &placeholderId, offset, buffer, bufSize), "Failed to write to placeholder");
|
||||
}
|
||||
|
||||
void ContentStorage::Register(const NcmPlaceHolderId& placeholderId, const NcmContentId& registeredId)
|
||||
{
|
||||
ASSERT_OK(ncmContentStorageRegister(&m_contentStorage, ®isteredId, &placeholderId), "Failed to register placeholder NCA");
|
||||
}
|
||||
|
||||
void ContentStorage::Delete(const NcmContentId& registeredId)
|
||||
{
|
||||
ASSERT_OK(ncmContentStorageDelete(&m_contentStorage, ®isteredId), "Failed to delete registered NCA");
|
||||
}
|
||||
|
||||
bool ContentStorage::Has(const NcmContentId& registeredId)
|
||||
{
|
||||
bool hasNCA = false;
|
||||
ASSERT_OK(ncmContentStorageHas(&m_contentStorage, &hasNCA, ®isteredId), "Failed to check if NCA is present");
|
||||
return hasNCA;
|
||||
}
|
||||
|
||||
std::string ContentStorage::GetPath(const NcmContentId& registeredId)
|
||||
{
|
||||
char pathBuf[FS_MAX_PATH] = { 0 };
|
||||
ASSERT_OK(ncmContentStorageGetPath(&m_contentStorage, pathBuf, FS_MAX_PATH, ®isteredId), "Failed to get installed NCA path");
|
||||
return std::string(pathBuf);
|
||||
}
|
||||
}
|
191
source/nx/usbhdd.cpp
Normal file
191
source/nx/usbhdd.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
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>
|
||||
#include <algorithm>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
182
source/sdInstall.cpp
Normal file
182
source/sdInstall.cpp
Normal 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 <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
|
||||
{
|
||||
int togo = ourTitleList.size();
|
||||
for (titleItr = 0; titleItr < ourTitleList.size(); titleItr++) {
|
||||
auto s = std::to_string(togo);
|
||||
inst::ui::instPage::filecount("inst.info_page.queue"_lang + s);
|
||||
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();
|
||||
togo = (togo - 1);
|
||||
}
|
||||
|
||||
inst::ui::instPage::filecount("inst.info_page.queue"_lang + "0");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
76
source/sigInstall.cpp
Normal file
76
source/sigInstall.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#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();
|
||||
}
|
||||
}
|
237
source/ui/HDInstPage.cpp
Normal file
237
source/ui/HDInstPage.cpp
Normal file
@ -0,0 +1,237 @@
|
||||
#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;
|
||||
s32 zzz = 0; //touchscreen variable
|
||||
bool show_file_ext;
|
||||
|
||||
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);
|
||||
this->pageInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->pageInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
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->SetItemsFocusColor(COLOR("#4f4f4dAA"));
|
||||
this->menu->SetScrollbarColor(COLOR("#1A1919FF"));
|
||||
this->Add(this->topRect);
|
||||
this->Add(this->infoRect);
|
||||
this->Add(this->botRect);
|
||||
this->Add(this->titleImage);
|
||||
this->Add(this->butText);
|
||||
this->Add(this->pageInfoText);
|
||||
this->Add(this->menu);
|
||||
}
|
||||
|
||||
void HDInstPage::drawMenuItems(bool clearItems, std::filesystem::path ourPath) {
|
||||
int myindex = this->menu->GetSelectedIndex(); //store index so when page redraws we can get the last item we checked.
|
||||
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;
|
||||
if (show_file_ext == false) {
|
||||
itm = inst::util::SplitFilename(file);
|
||||
}
|
||||
else {
|
||||
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);
|
||||
this->menu->SetSelectedIndex(myindex); //jump to the index we saved from above
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
HidTouchScreenState state = { 0 };
|
||||
|
||||
if (hidGetTouchScreenStates(&state, 1)) {
|
||||
|
||||
if ((Down & HidNpadButton_A) || (state.count != zzz))
|
||||
{
|
||||
zzz = state.count;
|
||||
|
||||
if (zzz != 1) {
|
||||
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();
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_ZL)
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6));
|
||||
|
||||
if (Down & HidNpadButton_ZR)
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6));
|
||||
|
||||
//goto top of list
|
||||
if (Down & HidNpadButton_L) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - x));
|
||||
}
|
||||
|
||||
//goto bottom of list
|
||||
if (Down & HidNpadButton_R) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + x));
|
||||
}
|
||||
|
||||
//don't show file extensions - refresh page
|
||||
if (Down & HidNpadButton_Left) {
|
||||
show_file_ext = false;
|
||||
this->drawMenuItems(true, currentDir);
|
||||
//this->drawMenuItems(true);
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_Right) {
|
||||
show_file_ext = true;
|
||||
this->drawMenuItems(true, currentDir);
|
||||
}
|
||||
}
|
||||
}
|
28
source/ui/MainApplication.cpp
Normal file
28
source/ui/MainApplication.cpp
Normal 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);
|
||||
}
|
||||
}
|
153
source/ui/instPage.cpp
Normal file
153
source/ui/instPage.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
#include <filesystem>
|
||||
#include "ui/MainApplication.hpp"
|
||||
#include "ui/instPage.hpp"
|
||||
#include "util/config.hpp"
|
||||
#include "util/lang.hpp"
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
FsFileSystem* fs;
|
||||
FsFileSystem devices[4];
|
||||
int statvfs(const char* path, struct statvfs* buf);
|
||||
|
||||
double GetSpace(const char* path)
|
||||
{
|
||||
struct statvfs stat;
|
||||
if (statvfs(path, &stat) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return stat.f_bsize * stat.f_bavail;
|
||||
}
|
||||
|
||||
#define COLOR(hex) pu::ui::Color::FromHex(hex)
|
||||
|
||||
//using namespace std;
|
||||
|
||||
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, "");
|
||||
}
|
||||
else {
|
||||
this->SetBackgroundImage("romfs:/images/Background.png");
|
||||
this->titleImage = Image::New(0, 0, "romfs:/images/Install.png");
|
||||
this->appVersionText = TextBlock::New(0, 0, "");
|
||||
}
|
||||
this->appVersionText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->pageInfoText = TextBlock::New(10, 109, "");
|
||||
this->pageInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->pageInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->installInfoText = TextBlock::New(10, 640, "");
|
||||
this->installInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->installInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->sdInfoText = TextBlock::New(10, 600, "");
|
||||
this->sdInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->sdInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->nandInfoText = TextBlock::New(10, 560, "");
|
||||
this->nandInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->nandInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->countText = TextBlock::New(10, 520, "");
|
||||
this->countText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->countText->SetColor(COLOR("#FFFFFFFF"));
|
||||
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->sdInfoText);
|
||||
this->Add(this->nandInfoText);
|
||||
this->Add(this->countText);
|
||||
this->Add(this->installBar);
|
||||
}
|
||||
|
||||
Result sdfreespace() {
|
||||
devices[0] = *fsdevGetDeviceFileSystem("sdmc");
|
||||
fs = &devices[0];
|
||||
Result rc = fsOpenSdCardFileSystem(fs);
|
||||
double mb = 0;
|
||||
if (R_FAILED(rc)) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
mb = (GetSpace("sdmc:/") / 1024) / 1024; //megabytes
|
||||
}
|
||||
return mb;
|
||||
}
|
||||
|
||||
Result sysfreespace() {
|
||||
FsFileSystem nandFS;
|
||||
Result rc = fsOpenBisFileSystem(&nandFS, FsBisPartitionId_User, "");
|
||||
fsdevMountDevice("user", nandFS);
|
||||
double mb = 0;
|
||||
if (R_FAILED(rc)) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
mb = (GetSpace("user:/") / 1024) / 1024; //megabytes
|
||||
}
|
||||
fsdevUnmountDevice("user");
|
||||
return mb;
|
||||
}
|
||||
|
||||
void instPage::setTopInstInfoText(std::string ourText) {
|
||||
mainApp->instpage->pageInfoText->SetText(ourText);
|
||||
mainApp->CallForRender();
|
||||
}
|
||||
|
||||
void instPage::filecount(std::string ourText) {
|
||||
mainApp->instpage->countText->SetText(ourText);
|
||||
mainApp->CallForRender();
|
||||
}
|
||||
|
||||
void instPage::setInstInfoText(std::string ourText) {
|
||||
mainApp->instpage->installInfoText->SetText(ourText);
|
||||
//
|
||||
std::string info = std::to_string(sdfreespace());
|
||||
std::string message = ("inst.net.sd"_lang + info + " MB");
|
||||
mainApp->instpage->sdInfoText->SetText(message);
|
||||
//
|
||||
info = std::to_string(sysfreespace());
|
||||
message = ("inst.net.nand"_lang + info + " MB");
|
||||
mainApp->instpage->nandInfoText->SetText(message);
|
||||
//
|
||||
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->sdInfoText->SetText("");
|
||||
mainApp->instpage->nandInfoText->SetText("");
|
||||
mainApp->instpage->countText->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) {
|
||||
}
|
||||
}
|
336
source/ui/mainPage.cpp
Normal file
336
source/ui/mainPage.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
#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 "usbhsfs.h"
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#define COLOR(hex) pu::ui::Color::FromHex(hex)
|
||||
|
||||
int statvfs(const char* path, struct statvfs* buf);
|
||||
s32 prev_touchcount = 0;
|
||||
|
||||
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 + "%");
|
||||
//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);
|
||||
this->butText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->optionMenu = pu::ui::elm::Menu::New(0, 95, 1280, COLOR("#FFFFFF00"), COLOR("#4f4f4d33"), 94, 6);
|
||||
this->optionMenu->SetItemsFocusColor(COLOR("#4f4f4dAA"));
|
||||
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);
|
||||
if (nx::hdd::count() && nx::hdd::rootPath()) {
|
||||
this->hdd = Image::New(1156, 669, "romfs:/images/icons/usb-hd-connected.png");
|
||||
this->Add(this->hdd);
|
||||
}
|
||||
this->Add(this->awooImage);
|
||||
this->Add(this->eggImage);
|
||||
this->awooImage->SetVisible(!inst::config::gayMode);
|
||||
this->Add(this->optionMenu);
|
||||
this->AddRenderCallback(mainMenuThread);
|
||||
}
|
||||
|
||||
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) || ((Held & HidNpadButton_L) && (Down & HidNpadButton_R)) || ((Down & HidNpadButton_L) && (Held & HidNpadButton_R))) && mainApp->IsShown()) {
|
||||
mainApp->FadeOut();
|
||||
mainApp->Close();
|
||||
}
|
||||
|
||||
HidTouchScreenState state = { 0 };
|
||||
|
||||
if (hidGetTouchScreenStates(&state, 1)) {
|
||||
|
||||
if ((Down & HidNpadButton_A) || (state.count != prev_touchcount))
|
||||
{
|
||||
prev_touchcount = state.count;
|
||||
|
||||
if (prev_touchcount != 1) {
|
||||
int menuindex = this->optionMenu->GetSelectedIndex();
|
||||
switch (menuindex) {
|
||||
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();
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_ZL) {
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_ZR) {
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_L) {
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_R) {
|
||||
}
|
||||
}
|
||||
}
|
346
source/ui/netInstPage.cpp
Normal file
346
source/ui/netInstPage.cpp
Normal file
@ -0,0 +1,346 @@
|
||||
#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"
|
||||
#include <sstream>
|
||||
|
||||
#define COLOR(hex) pu::ui::Color::FromHex(hex)
|
||||
|
||||
namespace inst::ui {
|
||||
extern MainApplication* mainApp;
|
||||
s32 xxx = 0;
|
||||
|
||||
std::string httplastUrl = "http://";
|
||||
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(1210, 680, "");
|
||||
}
|
||||
else {
|
||||
this->SetBackgroundImage("romfs:/images/Background.png");
|
||||
this->titleImage = Image::New(0, 0, "romfs:/images/Net.png");
|
||||
this->appVersionText = TextBlock::New(1210, 680, "");
|
||||
}
|
||||
this->appVersionText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->pageInfoText = TextBlock::New(10, 109, "");
|
||||
this->pageInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->pageInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
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("#4f4f4dAA"));
|
||||
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_withext(bool clearItems) {
|
||||
int myindex = this->menu->GetSelectedIndex(); //store index so when page redraws we can get the last item we checked.
|
||||
if (clearItems) this->selectedUrls = {};
|
||||
if (clearItems) this->alternativeNames = {};
|
||||
std::string itm;
|
||||
|
||||
this->menu->ClearItems();
|
||||
for (auto& urls : this->ourUrls) {
|
||||
itm = inst::util::shortenString(inst::util::formatUrlString(urls), 56, true);
|
||||
auto ourEntry = pu::ui::elm::MenuItem::New(itm);
|
||||
ourEntry->SetColor(COLOR("#FFFFFFFF"));
|
||||
ourEntry->SetIcon("romfs:/images/icons/checkbox-blank-outline.png");
|
||||
long unsigned int i;
|
||||
for (i = 0; i < this->selectedUrls.size(); i++) {
|
||||
if (this->selectedUrls[i] == urls) {
|
||||
ourEntry->SetIcon("romfs:/images/icons/check-box-outline.png");
|
||||
}
|
||||
}
|
||||
this->menu->AddItem(ourEntry);
|
||||
this->menu->SetSelectedIndex(myindex); //jump to the index we saved from above
|
||||
}
|
||||
}
|
||||
|
||||
void netInstPage::drawMenuItems(bool clearItems) {
|
||||
int myindex = this->menu->GetSelectedIndex(); //store index so when page redraws we can get the last item we checked.
|
||||
if (clearItems) this->selectedUrls = {};
|
||||
if (clearItems) this->alternativeNames = {};
|
||||
std::string itm;
|
||||
|
||||
//Degug code to print out and sort a list of the files on the http server.
|
||||
/*
|
||||
for (auto& xy: this->ourUrls) {
|
||||
std::string base_filename = xy.substr(xy.find_last_of("/") + 1); //just get the filename
|
||||
std::string::size_type const p(base_filename.find_last_of('.'));
|
||||
std::string file_without_extension = base_filename.substr(0, p); //strip of file extension
|
||||
itm = inst::util::shortenString(inst::util::formatUrlString(file_without_extension), 56, true);
|
||||
modded.push_back(itm);
|
||||
std::sort(modded.begin(), modded.end());
|
||||
}
|
||||
|
||||
for (auto& yz: this->modded) {
|
||||
FILE * fp;
|
||||
fp = fopen ("gamelist.txt", "a+");
|
||||
auto *info = yz.c_str();
|
||||
fprintf(fp, "%s\n", info);
|
||||
fclose(fp);
|
||||
}
|
||||
*/
|
||||
|
||||
this->menu->ClearItems();
|
||||
for (auto& urls : this->ourUrls) {
|
||||
//Alt code to remove file extension from the item shown on the screen
|
||||
std::string base_filename = urls.substr(urls.find_last_of("/") + 1); //just get the filename
|
||||
std::string::size_type const p(base_filename.find_last_of('.'));
|
||||
std::string file_without_extension = base_filename.substr(0, p); //strip of file extension
|
||||
itm = inst::util::shortenString(inst::util::formatUrlString(file_without_extension), 56, true);
|
||||
//itm = inst::util::shortenString(inst::util::formatUrlString(urls), 56, true);
|
||||
auto ourEntry = pu::ui::elm::MenuItem::New(itm);
|
||||
ourEntry->SetColor(COLOR("#FFFFFFFF"));
|
||||
ourEntry->SetIcon("romfs:/images/icons/checkbox-blank-outline.png");
|
||||
long unsigned int i;
|
||||
for (i = 0; i < this->selectedUrls.size(); i++) {
|
||||
if (this->selectedUrls[i] == urls) {
|
||||
ourEntry->SetIcon("romfs:/images/icons/check-box-outline.png");
|
||||
}
|
||||
}
|
||||
this->menu->AddItem(ourEntry);
|
||||
this->menu->SetSelectedIndex(myindex); //jump to the index we saved from above
|
||||
}
|
||||
}
|
||||
|
||||
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->butText->SetText("inst.net.buttons"_lang + " \ue0f0 Install From HTTP Directory");
|
||||
this->menu->SetVisible(false);
|
||||
this->menu->ClearItems();
|
||||
this->infoImage->SetVisible(true);
|
||||
mainApp->LoadLayout(mainApp->netinstPage);
|
||||
this->ourUrls = netInstStuff::OnSelected();
|
||||
|
||||
if (!this->ourUrls.size()) {
|
||||
mainApp->LoadLayout(mainApp->mainPage);
|
||||
return;
|
||||
}
|
||||
|
||||
else if (this->ourUrls[0] == "supplyUrl") {
|
||||
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, inst::config::httplastUrl, 500);
|
||||
if (keyboardResult.size() > 0) {
|
||||
httplastUrl = keyboardResult;
|
||||
|
||||
if (keyboardResult == "") {
|
||||
keyboardResult = "http://127.0.0.1";
|
||||
}
|
||||
else {
|
||||
inst::config::httplastUrl = keyboardResult;
|
||||
inst::config::setConfig();
|
||||
}
|
||||
|
||||
if (inst::util::formatUrlString(keyboardResult) == "" || keyboardResult == "https://" || keyboardResult == "http://") {
|
||||
mainApp->CreateShowDialog("inst.net.url.invalid"_lang, "", { "common.ok"_lang }, false);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
netConnected = true;
|
||||
this->pageInfoText->SetText("inst.net.top_info"_lang);
|
||||
this->butText->SetText("inst.net.buttons1"_lang);
|
||||
this->drawMenuItems(true);
|
||||
mainApp->CallForRender();
|
||||
this->infoImage->SetVisible(false); //
|
||||
this->menu->SetVisible(true);
|
||||
this->menu->SetSelectedIndex(0); //when page first loads jump to start of the menu
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
HidTouchScreenState state = { 0 };
|
||||
|
||||
if (hidGetTouchScreenStates(&state, 1)) {
|
||||
|
||||
if (netConnected) {
|
||||
if ((Down & HidNpadButton_A) || (state.count != xxx))
|
||||
{
|
||||
xxx = state.count;
|
||||
|
||||
if (xxx != 1) {
|
||||
int var = this->menu->GetItems().size();
|
||||
auto s = std::to_string(var);
|
||||
if (s == "0") {
|
||||
//do nothing here because there's no items in the list, that way the app won't freeze
|
||||
}
|
||||
else {
|
||||
this->selectTitle(this->menu->GetSelectedIndex());
|
||||
if (this->menu->GetItems().size() == 1 && this->selectedUrls.size() == 1) {
|
||||
this->startInstall(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((Down & HidNpadButton_Minus) || (state.count != xxx))
|
||||
{
|
||||
xxx = state.count;
|
||||
|
||||
if (xxx != 1) {
|
||||
int var = this->menu->GetItems().size();
|
||||
auto s = std::to_string(var);
|
||||
//std::string s = ourUrlString; //debug stuff
|
||||
//this->appVersionText->SetText(s); //debug stuff
|
||||
|
||||
if (s == "0") {
|
||||
//do nothing here because there's no items in the list, that way the app won't freeze
|
||||
}
|
||||
else {
|
||||
this->selectTitle(this->menu->GetSelectedIndex());
|
||||
if (this->menu->GetItems().size() == 1 && this->selectedUrls.size() == 1) {
|
||||
this->startInstall(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((Down & HidNpadButton_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) {
|
||||
int var = this->menu->GetItems().size();
|
||||
auto s = std::to_string(var);
|
||||
|
||||
if (s == "0") {
|
||||
//do nothing here because there's no items in the list, that way the app won't freeze
|
||||
}
|
||||
|
||||
else {
|
||||
if (this->selectedUrls.size() == 0) {
|
||||
this->selectTitle(this->menu->GetSelectedIndex());
|
||||
this->startInstall(false);
|
||||
return;
|
||||
}
|
||||
this->startInstall(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_ZL)
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6));
|
||||
|
||||
if (Down & HidNpadButton_ZR)
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6));
|
||||
|
||||
//goto top of list
|
||||
if (Down & HidNpadButton_L) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - x));
|
||||
}
|
||||
|
||||
//goto bottom of list
|
||||
if (Down & HidNpadButton_R) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + x));
|
||||
}
|
||||
|
||||
//don't show file extensions
|
||||
if (Down & HidNpadButton_Left) {
|
||||
this->drawMenuItems(true);
|
||||
}
|
||||
|
||||
//show file extensions
|
||||
if (Down & HidNpadButton_Right) {
|
||||
this->drawMenuItems_withext(true);
|
||||
}
|
||||
}
|
||||
}
|
413
source/ui/optionsPage.cpp
Normal file
413
source/ui/optionsPage.cpp
Normal file
@ -0,0 +1,413 @@
|
||||
#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;
|
||||
s32 prev_touchcount = 0;
|
||||
|
||||
std::vector<std::string> languageStrings = { "En", "Jpn", "Fr", "De", "It", "Ru", "Es", "Tw" };
|
||||
|
||||
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(1200, 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(1200, 680, "v" + inst::config::appVersion);
|
||||
}
|
||||
this->appVersionText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->appVersionText->SetFont(pu::ui::MakeDefaultFontName(20));
|
||||
this->pageInfoText = TextBlock::New(10, 109, "options.title"_lang);
|
||||
this->pageInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->pageInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
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("#4f4f4dAA"));
|
||||
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) {
|
||||
if (ourLangCode >= 0) return languageStrings[ourLangCode];
|
||||
else {
|
||||
return "options.language.system_language"_lang;
|
||||
}
|
||||
}
|
||||
|
||||
void sigPatchesMenuItem_Click() {
|
||||
sig::installSigPatches();
|
||||
}
|
||||
|
||||
void thememessage() {
|
||||
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 useoldphp = pu::ui::elm::MenuItem::New("options.menu_items.useoldphp"_lang);
|
||||
useoldphp->SetColor(COLOR("#FFFFFFFF"));
|
||||
useoldphp->SetIcon(this->getMenuOptionIcon(inst::config::useoldphp));
|
||||
this->menu->AddItem(useoldphp);
|
||||
auto streamhtmls = pu::ui::elm::MenuItem::New("options.menu_items.streamhtmls"_lang);
|
||||
streamhtmls->SetColor(COLOR("#FFFFFFFF"));
|
||||
streamhtmls->SetIcon(this->getMenuOptionIcon(inst::config::streamhtmls));
|
||||
this->menu->AddItem(streamhtmls);
|
||||
auto fixticket = pu::ui::elm::MenuItem::New("options.menu_items.fixticket"_lang);
|
||||
fixticket->SetColor(COLOR("#FFFFFFFF"));
|
||||
fixticket->SetIcon(this->getMenuOptionIcon(inst::config::fixticket));
|
||||
this->menu->AddItem(fixticket);
|
||||
auto httpkeyboard = pu::ui::elm::MenuItem::New("options.menu_items.usehttpkeyboard"_lang);
|
||||
httpkeyboard->SetColor(COLOR("#FFFFFFFF"));
|
||||
httpkeyboard->SetIcon(this->getMenuOptionIcon(inst::config::httpkeyboard));
|
||||
this->menu->AddItem(httpkeyboard);
|
||||
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 httpServerUrlOption = pu::ui::elm::MenuItem::New("options.menu_items.http_url"_lang + inst::util::shortenString(inst::config::httpIndexUrl, 42, false));
|
||||
httpServerUrlOption->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->menu->AddItem(httpServerUrlOption);
|
||||
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_ZL)
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6));
|
||||
|
||||
if (Down & HidNpadButton_ZR)
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6));
|
||||
|
||||
//goto top of list
|
||||
if (Down & HidNpadButton_L) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - x));
|
||||
}
|
||||
|
||||
//goto bottom of list
|
||||
if (Down & HidNpadButton_R) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + x));
|
||||
}
|
||||
|
||||
HidTouchScreenState state = { 0 };
|
||||
|
||||
if (hidGetTouchScreenStates(&state, 1)) {
|
||||
|
||||
if ((Down & HidNpadButton_A) || (state.count != prev_touchcount))
|
||||
{
|
||||
prev_touchcount = state.count;
|
||||
|
||||
if (prev_touchcount != 1) {
|
||||
|
||||
std::string keyboardResult;
|
||||
int rc;
|
||||
std::vector<std::string> downloadUrl;
|
||||
std::vector<std::string> languageList;
|
||||
int index = this->menu->GetSelectedIndex();
|
||||
switch (index) {
|
||||
case 0:
|
||||
inst::config::ignoreReqVers = !inst::config::ignoreReqVers;
|
||||
inst::config::setConfig();
|
||||
this->setMenuText();
|
||||
//makes sure to jump back to the selected item once the menu is reloaded
|
||||
this->menu->SetSelectedIndex(index);
|
||||
//
|
||||
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();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
break;
|
||||
case 2:
|
||||
inst::config::overClock = !inst::config::overClock;
|
||||
inst::config::setConfig();
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
break;
|
||||
case 3:
|
||||
inst::config::deletePrompt = !inst::config::deletePrompt;
|
||||
inst::config::setConfig();
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
break;
|
||||
case 4:
|
||||
inst::config::autoUpdate = !inst::config::autoUpdate;
|
||||
inst::config::setConfig();
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
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();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
thememessage();
|
||||
inst::config::setConfig();
|
||||
break;
|
||||
|
||||
case 6:
|
||||
if (inst::config::useSound) {
|
||||
inst::config::useSound = false;
|
||||
}
|
||||
else {
|
||||
inst::config::useSound = true;
|
||||
}
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
inst::config::setConfig();
|
||||
break;
|
||||
|
||||
case 7:
|
||||
if (inst::config::useoldphp) {
|
||||
inst::config::useoldphp = false;
|
||||
}
|
||||
else {
|
||||
inst::config::useoldphp = true;
|
||||
}
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
inst::config::setConfig();
|
||||
break;
|
||||
|
||||
case 8:
|
||||
if (inst::config::streamhtmls) {
|
||||
inst::config::streamhtmls = false;
|
||||
}
|
||||
else {
|
||||
inst::config::streamhtmls = true;
|
||||
}
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
inst::config::setConfig();
|
||||
break;
|
||||
|
||||
case 9:
|
||||
if (inst::config::fixticket) {
|
||||
inst::config::fixticket = false;
|
||||
}
|
||||
else {
|
||||
inst::config::fixticket = true;
|
||||
}
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
inst::config::setConfig();
|
||||
break;
|
||||
|
||||
case 10:
|
||||
if (inst::config::httpkeyboard) {
|
||||
inst::config::httpkeyboard = false;
|
||||
}
|
||||
else {
|
||||
inst::config::httpkeyboard = true;
|
||||
}
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
inst::config::setConfig();
|
||||
break;
|
||||
|
||||
case 11:
|
||||
sigPatchesMenuItem_Click();
|
||||
break;
|
||||
|
||||
case 12:
|
||||
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();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
}
|
||||
break;
|
||||
|
||||
case 13:
|
||||
keyboardResult = inst::util::softwareKeyboard("inst.net.url.hint"_lang, inst::config::httpIndexUrl.c_str(), 500);
|
||||
if (keyboardResult.size() > 0) {
|
||||
inst::config::httpIndexUrl = keyboardResult;
|
||||
inst::config::setConfig();
|
||||
this->setMenuText();
|
||||
this->menu->SetSelectedIndex(index);
|
||||
}
|
||||
break;
|
||||
|
||||
case 14:
|
||||
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, "romfs:/images/lang.png");
|
||||
if (rc == -1) break;
|
||||
switch (rc) {
|
||||
case 0:
|
||||
inst::config::languageSetting = 0;
|
||||
break;
|
||||
case 1:
|
||||
inst::config::languageSetting = 1;
|
||||
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 = 5;
|
||||
break;
|
||||
case 6:
|
||||
inst::config::languageSetting = 6;
|
||||
break;
|
||||
case 7:
|
||||
inst::config::languageSetting = 7;
|
||||
break;
|
||||
default:
|
||||
inst::config::languageSetting = 0;
|
||||
}
|
||||
inst::config::setConfig();
|
||||
mainApp->FadeOut();
|
||||
mainApp->Close();
|
||||
break;
|
||||
case 15:
|
||||
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 16:
|
||||
inst::ui::mainApp->CreateShowDialog("options.credits.title"_lang, "options.credits.desc"_lang, { "common.close"_lang }, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
261
source/ui/sdInstPage.cpp
Normal file
261
source/ui/sdInstPage.cpp
Normal file
@ -0,0 +1,261 @@
|
||||
//#include <switch.h>
|
||||
//#include <string>
|
||||
#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;
|
||||
s32 yyy = 0;
|
||||
bool show_ext;
|
||||
|
||||
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, "");
|
||||
}
|
||||
else {
|
||||
this->SetBackgroundImage("romfs:/images/Background.png");
|
||||
this->titleImage = Image::New(0, 0, "romfs:/images/Sd.png");
|
||||
this->appVersionText = TextBlock::New(0, 0, "");
|
||||
}
|
||||
this->appVersionText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->pageInfoText = TextBlock::New(10, 109, "inst.sd.top_info"_lang);
|
||||
this->pageInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
this->pageInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
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("#4f4f4dAA"));
|
||||
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) {
|
||||
int myindex = this->menu->GetSelectedIndex(); //store index so when page redraws we can get the last item we checked.
|
||||
|
||||
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;
|
||||
if (show_ext == false) {
|
||||
itm = inst::util::SplitFilename(file);
|
||||
}
|
||||
else {
|
||||
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);
|
||||
this->menu->SetSelectedIndex(myindex); //jump to the index we saved from above
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
HidTouchScreenState state = { 0 };
|
||||
|
||||
if (hidGetTouchScreenStates(&state, 1)) {
|
||||
|
||||
if ((Down & HidNpadButton_A) || (state.count != yyy))
|
||||
{
|
||||
yyy = state.count;
|
||||
|
||||
if (yyy != 1) {
|
||||
int var = this->menu->GetItems().size();
|
||||
auto s = std::to_string(var);
|
||||
|
||||
if (s == "0") {
|
||||
//do nothing here because there's no items in the list, that way the app won't freeze
|
||||
}
|
||||
|
||||
else {
|
||||
this->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) {
|
||||
int var = this->menu->GetItems().size();
|
||||
auto s = std::to_string(var);
|
||||
|
||||
if (s == "0") {
|
||||
//do nothing here because there's no items in the list, that way the app won't freeze
|
||||
}
|
||||
|
||||
else {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_ZL)
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6));
|
||||
|
||||
if (Down & HidNpadButton_ZR)
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6));
|
||||
|
||||
//goto top of list
|
||||
if (Down & HidNpadButton_L) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - x));
|
||||
}
|
||||
|
||||
//goto bottom of list
|
||||
if (Down & HidNpadButton_R) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + x));
|
||||
}
|
||||
|
||||
//don't show file extensions - refresh page
|
||||
if (Down & HidNpadButton_Left) {
|
||||
show_ext = false;
|
||||
this->drawMenuItems(true, currentDir);
|
||||
//this->drawMenuItems(true);
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_Right) {
|
||||
show_ext = true;
|
||||
this->drawMenuItems(true, currentDir);
|
||||
}
|
||||
}
|
||||
}
|
229
source/ui/usbInstPage.cpp
Normal file
229
source/ui/usbInstPage.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
#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;
|
||||
s32 www = 0; //touchscreen variable
|
||||
|
||||
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, "");
|
||||
}
|
||||
else {
|
||||
this->SetBackgroundImage("romfs:/images/Background.png");
|
||||
this->titleImage = Image::New(0, 0, "romfs:/images/Usb.png");
|
||||
this->appVersionText = TextBlock::New(0, 0, "");
|
||||
}
|
||||
this->appVersionText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->pageInfoText = TextBlock::New(10, 109, "");
|
||||
this->pageInfoText->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->pageInfoText->SetFont(pu::ui::MakeDefaultFontName(30));
|
||||
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("#4f4f4dAA"));
|
||||
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_withext(bool clearItems) {
|
||||
int myindex = this->menu->GetSelectedIndex(); //store index so when page redraws we can get the last item we checked.
|
||||
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);
|
||||
this->menu->SetSelectedIndex(myindex); //jump to the index we saved from above
|
||||
}
|
||||
}
|
||||
|
||||
void usbInstPage::drawMenuItems(bool clearItems) {
|
||||
int myindex = this->menu->GetSelectedIndex(); //store index so when page redraws we can get the last item we checked.
|
||||
if (clearItems) this->selectedTitles = {};
|
||||
this->menu->ClearItems();
|
||||
for (auto& url : this->ourTitles) {
|
||||
|
||||
std::string base_filename = url.substr(url.find_last_of("/") + 1); //just get the filename
|
||||
std::string::size_type const p(base_filename.find_last_of('.'));
|
||||
std::string file_without_extension = base_filename.substr(0, p); //strip of file extension
|
||||
std::string itm = inst::util::shortenString(inst::util::formatUrlString(file_without_extension), 56, true);
|
||||
|
||||
//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);
|
||||
this->menu->SetSelectedIndex(myindex); //jump to the index we saved from above
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
HidTouchScreenState state = { 0 };
|
||||
|
||||
if (hidGetTouchScreenStates(&state, 1)) {
|
||||
|
||||
if ((Down & HidNpadButton_A) || (state.count != www))
|
||||
{
|
||||
www = state.count;
|
||||
|
||||
if (www != 1) {
|
||||
int var = this->menu->GetItems().size();
|
||||
auto s = std::to_string(var);
|
||||
if (s == "0") {
|
||||
//do nothing here because there's no items in the list, that way the app won't freeze
|
||||
}
|
||||
else {
|
||||
this->selectTitle(this->menu->GetSelectedIndex());
|
||||
if (this->menu->GetItems().size() == 1 && this->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) {
|
||||
int var = this->menu->GetItems().size();
|
||||
auto s = std::to_string(var);
|
||||
|
||||
if (s == "0") {
|
||||
//do nothing here because there's no items in the list, that way the app won't freeze
|
||||
}
|
||||
|
||||
else {
|
||||
if (this->selectedTitles.size() == 0) {
|
||||
this->selectTitle(this->menu->GetSelectedIndex());
|
||||
this->startInstall();
|
||||
return;
|
||||
}
|
||||
this->startInstall();
|
||||
}
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_ZL)
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - 6));
|
||||
|
||||
if (Down & HidNpadButton_ZR)
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + 6));
|
||||
|
||||
//goto top of list
|
||||
if (Down & HidNpadButton_L) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::max(0, this->menu->GetSelectedIndex() - x));
|
||||
}
|
||||
|
||||
//goto bottom of list
|
||||
if (Down & HidNpadButton_R) {
|
||||
int x = this->menu->GetItems().size() - 1;
|
||||
this->menu->SetSelectedIndex(std::min((s32)this->menu->GetItems().size() - 1, this->menu->GetSelectedIndex() + x));
|
||||
}
|
||||
|
||||
//don't show file extensions
|
||||
if (Down & HidNpadButton_Left) {
|
||||
this->drawMenuItems(true);
|
||||
}
|
||||
|
||||
//show file extensions
|
||||
if (Down & HidNpadButton_Right) {
|
||||
this->drawMenuItems_withext(true);
|
||||
}
|
||||
}
|
||||
}
|
201
source/usbInstall.cpp
Normal file
201
source/usbInstall.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
padConfigureInput(8, HidNpadStyleSet_NpadStandard);
|
||||
PadState pad;
|
||||
padInitializeAny(&pad);
|
||||
|
||||
while (true) {
|
||||
if (bufferData(&header, sizeof(TUSHeader), 500000000) != 0) break;
|
||||
|
||||
padUpdate(&pad);
|
||||
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 {
|
||||
int togo = ourTitleList.size();
|
||||
for (fileItr = 0; fileItr < ourTitleList.size(); fileItr++) {
|
||||
auto s = std::to_string(togo);
|
||||
inst::ui::instPage::filecount("inst.info_page.queue"_lang + s);
|
||||
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();
|
||||
togo = (togo - 1);
|
||||
}
|
||||
|
||||
inst::ui::instPage::filecount("inst.info_page.queue"_lang + "0");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
95
source/util/config.cpp
Normal file
95
source/util/config.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include "util/config.hpp"
|
||||
#include "util/json.hpp"
|
||||
|
||||
namespace inst::config {
|
||||
std::string gAuthKey;
|
||||
std::string sigPatchesUrl;
|
||||
std::string httpIndexUrl;
|
||||
std::string httplastUrl;
|
||||
std::vector<std::string> updateInfo;
|
||||
int languageSetting;
|
||||
bool autoUpdate;
|
||||
bool deletePrompt;
|
||||
bool ignoreReqVers;
|
||||
bool overClock;
|
||||
bool gayMode;
|
||||
bool useSound;
|
||||
bool usbAck;
|
||||
bool validateNCAs;
|
||||
bool useoldphp;
|
||||
bool streamhtmls;
|
||||
bool fixticket;
|
||||
bool httpkeyboard;
|
||||
|
||||
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},
|
||||
{"httpIndexUrl", httpIndexUrl},
|
||||
{"httplastUrl", httplastUrl},
|
||||
{"httpoldphp", useoldphp},
|
||||
{"streamhtmls", streamhtmls},
|
||||
{"fixticket", fixticket},
|
||||
{"httpkeyboard", httpkeyboard}
|
||||
};
|
||||
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>();
|
||||
useoldphp = j["httpoldphp"].get<bool>();
|
||||
streamhtmls = j["streamhtmls"].get<bool>();
|
||||
fixticket = j["fixticket"].get<bool>();
|
||||
httpkeyboard = j["httpkeyboard"].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>();
|
||||
httpIndexUrl = j["httpIndexUrl"].get<std::string>();
|
||||
httplastUrl = j["httplastUrl"].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 = 0;
|
||||
httpIndexUrl = "http://";
|
||||
httplastUrl = "http://";
|
||||
autoUpdate = true;
|
||||
deletePrompt = true;
|
||||
gayMode = false;
|
||||
useSound = true;
|
||||
useoldphp = false;
|
||||
streamhtmls = true;
|
||||
fixticket = true;
|
||||
httpkeyboard = false;
|
||||
ignoreReqVers = true;
|
||||
overClock = true;
|
||||
usbAck = false;
|
||||
validateNCAs = true;
|
||||
setConfig();
|
||||
}
|
||||
}
|
||||
}
|
90
source/util/crypto.cpp
Normal file
90
source/util/crypto.cpp
Normal 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;
|
||||
}
|
181
source/util/curl.cpp
Normal file
181
source/util/curl.cpp
Normal file
@ -0,0 +1,181 @@
|
||||
#include <curl/curl.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#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;
|
||||
}
|
||||
|
||||
char* unconstchar(const char* s) {
|
||||
if (!s)
|
||||
return NULL;
|
||||
int i;
|
||||
char* res = NULL;
|
||||
res = (char*)malloc(strlen(s) + 1);
|
||||
if (!res) {
|
||||
fprintf(stderr, "Memory Allocation Failed! Exiting...\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
else {
|
||||
for (i = 0; s[i] != '\0'; i++) {
|
||||
res[i] = s[i];
|
||||
}
|
||||
res[i] = '\0';
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
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, "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36");
|
||||
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;
|
||||
char* url = unconstchar(ourUrl.c_str());
|
||||
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
curl_handle = curl_easy_init();
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_REFERER, url);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADER, 0L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36");
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string html_to_buffer(const std::string ourUrl) {
|
||||
CURL* curl;
|
||||
FILE* fp;
|
||||
CURLcode res{};
|
||||
char* url = unconstchar(ourUrl.c_str());
|
||||
char outfilename[FILENAME_MAX] = "temp.html";
|
||||
long time_delay = 5000;
|
||||
curl = curl_easy_init();
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
|
||||
if (curl)
|
||||
{
|
||||
fp = fopen(outfilename, "wb");
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
//
|
||||
curl_easy_setopt(curl, CURLOPT_REFERER, url);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36");
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, time_delay);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, time_delay);
|
||||
//
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
||||
res = curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
fclose(fp);
|
||||
}
|
||||
// put file into a buffer:
|
||||
std::uintmax_t filesize = std::filesystem::file_size("temp.html");
|
||||
// Allocate buffer to hold file
|
||||
char* buf = new char[filesize];
|
||||
// Read file
|
||||
std::ifstream fin("temp.html", std::ios::binary);
|
||||
fin.read(buf, filesize);
|
||||
// Close file
|
||||
fin.close();
|
||||
std::string x = buf;
|
||||
|
||||
if (res == CURLE_OK) {
|
||||
std::remove("temp.html"); // delete file
|
||||
curl_global_cleanup();
|
||||
return x;
|
||||
}
|
||||
else {
|
||||
LOG_DEBUG(curl_easy_strerror(result));
|
||||
curl_global_cleanup();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
53
source/util/debug.c
Normal file
53
source/util/debug.c
Normal 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
53
source/util/file_util.cpp
Normal 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());
|
||||
}
|
||||
}
|
79
source/util/lang.cpp
Normal file
79
source/util/lang.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#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 == 0) {
|
||||
SetLanguage ourLang;
|
||||
u64 lcode = 0;
|
||||
setInitialize();
|
||||
setGetSystemLanguage(&lcode);
|
||||
setMakeLanguage(lcode, &ourLang);
|
||||
setExit();
|
||||
langInt = (int)ourLang;
|
||||
}
|
||||
switch (langInt) {
|
||||
case 0:
|
||||
languagePath = "romfs:/lang/en.json";
|
||||
break;
|
||||
case 1:
|
||||
languagePath = "romfs:/lang/jp.json";
|
||||
break;
|
||||
case 2:
|
||||
languagePath = "romfs:/lang/fr.json";
|
||||
break;
|
||||
case 3:
|
||||
languagePath = "romfs:/lang/de.json";
|
||||
break;
|
||||
case 4:
|
||||
languagePath = "romfs:/lang/it.json";
|
||||
break;
|
||||
case 5:
|
||||
languagePath = "romfs:/lang/ru.json";
|
||||
break;
|
||||
case 6:
|
||||
languagePath = "romfs:/lang/es.json";
|
||||
break;
|
||||
case 7:
|
||||
languagePath = "romfs:/lang/tw.json";
|
||||
break;
|
||||
default:
|
||||
if (std::filesystem::exists(inst::config::appDir + "/lang/custom.json")) {
|
||||
languagePath = (inst::config::appDir + "/lang/custom.json");
|
||||
}
|
||||
else {
|
||||
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()]);
|
||||
}
|
||||
}
|
267
source/util/network_util.cpp
Normal file
267
source/util/network_util.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
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;
|
||||
padUpdate(&pad); //test
|
||||
}
|
||||
|
||||
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;
|
||||
padUpdate(&pad); //test
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
}
|
134
source/util/title_util.cpp
Normal file
134
source/util/title_util.cpp
Normal 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;
|
||||
}
|
||||
}
|
163
source/util/unzip.cpp
Normal file
163
source/util/unzip.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
#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;
|
||||
}
|
||||
}
|
598
source/util/usb_comms_tinleaf.c
Normal file
598
source/util/usb_comms_tinleaf.c
Normal file
@ -0,0 +1,598 @@
|
||||
#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
105
source/util/usb_util.cpp
Normal 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;
|
||||
}
|
||||
}
|
348
source/util/util.cpp
Normal file
348
source/util/util.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
#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() {
|
||||
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();
|
||||
}
|
||||
|
||||
auto caseInsensitiveLess = [](auto& x, auto& y)->bool {
|
||||
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);
|
||||
//debug
|
||||
/*
|
||||
FILE * fp;
|
||||
fp = fopen ("log.txt", "a+");
|
||||
for (unsigned long int i = 0; i < files.size(); i++) {
|
||||
std::string debug = files[i];
|
||||
const char *info = debug.c_str();
|
||||
fprintf(fp, "%s\n", info);
|
||||
}
|
||||
fclose(fp);
|
||||
*/
|
||||
//files.erase(std::remove(files.begin(), files.end(), "Some file.x"), files.end());
|
||||
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);
|
||||
|
||||
//debug
|
||||
/*
|
||||
FILE * fp;
|
||||
fp = fopen ("log2.txt", "a+");
|
||||
for (unsigned long int i = 0; i < files.size(); i++) {
|
||||
std::string debug = files[i];
|
||||
const char *info = debug.c_str();
|
||||
fprintf(fp, "%s\n", info);
|
||||
}
|
||||
fclose(fp);
|
||||
*/
|
||||
files.erase(std::remove(files.begin(), files.end(), "ums0:/System Volume Information"), files.end());
|
||||
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 {};
|
||||
}
|
||||
|
||||
std::string SplitFilename(const std::string& str) {
|
||||
std::string base_filename = str.substr(str.find_last_of("/") + 1); //just get the filename
|
||||
std::string::size_type const p(base_filename.find_last_of('.'));
|
||||
std::string file_without_extension = base_filename.substr(0, p); //strip of file extension
|
||||
return file_without_extension;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user