Normalize line endings that got messed up for whatever reason

This commit is contained in:
Pengfei
2021-08-24 18:49:11 +08:00
parent adb4325d79
commit 8bcaa0b711
36 changed files with 5213 additions and 5212 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto
+395 -395
View File
@@ -1,395 +1,395 @@
// Copyright 2020 Pengfei Zhu // Copyright 2020 Pengfei Zhu
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <cryptopp/aes.h> #include <cryptopp/aes.h>
#include <cryptopp/modes.h> #include <cryptopp/modes.h>
#include <cryptopp/sha.h> #include <cryptopp/sha.h>
#include "common/alignment.h" #include "common/alignment.h"
#include "core/cia_builder.h" #include "core/cia_builder.h"
#include "core/db/title_db.h" #include "core/db/title_db.h"
#include "core/db/title_keys_bin.h" #include "core/db/title_keys_bin.h"
#include "core/file_sys/certificate.h" #include "core/file_sys/certificate.h"
#include "core/file_sys/cia_common.h" #include "core/file_sys/cia_common.h"
#include "core/file_sys/ticket.h" #include "core/file_sys/ticket.h"
#include "core/file_sys/title_metadata.h" #include "core/file_sys/title_metadata.h"
#include "core/importer.h" #include "core/importer.h"
namespace Core { namespace Core {
constexpr std::size_t CIA_ALIGNMENT = 0x40; constexpr std::size_t CIA_ALIGNMENT = 0x40;
class HashedFile : public FileUtil::IOFile { class HashedFile : public FileUtil::IOFile {
public: public:
explicit HashedFile(const std::string& filename, const char openmode[], int flags = 0) explicit HashedFile(const std::string& filename, const char openmode[], int flags = 0)
: FileUtil::IOFile(filename, openmode, flags) {} : FileUtil::IOFile(filename, openmode, flags) {}
~HashedFile() override = default; ~HashedFile() override = default;
void SetHashEnabled(bool enabled) { void SetHashEnabled(bool enabled) {
hash_enabled = enabled; hash_enabled = enabled;
if (enabled) { // Restart when hash is newly restarted if (enabled) { // Restart when hash is newly restarted
sha.Restart(); sha.Restart();
} }
} }
void GetHash(u8* out) { void GetHash(u8* out) {
sha.Final(out); sha.Final(out);
} }
bool VerifyHash(u8* out) { bool VerifyHash(u8* out) {
return sha.Verify(out); return sha.Verify(out);
} }
std::size_t Write(const char* data, std::size_t length) override { std::size_t Write(const char* data, std::size_t length) override {
const std::size_t length_written = FileUtil::IOFile::Write(data, length); const std::size_t length_written = FileUtil::IOFile::Write(data, length);
if (hash_enabled) { if (hash_enabled) {
sha.Update(reinterpret_cast<const CryptoPP::byte*>(data), length_written); sha.Update(reinterpret_cast<const CryptoPP::byte*>(data), length_written);
} }
return length_written; return length_written;
} }
private: private:
CryptoPP::SHA256 sha; CryptoPP::SHA256 sha;
bool hash_enabled{}; bool hash_enabled{};
}; };
CIABuilder::CIABuilder(const Config& config, std::shared_ptr<TicketDB> ticket_db_) CIABuilder::CIABuilder(const Config& config, std::shared_ptr<TicketDB> ticket_db_)
: ticket_db(std::move(ticket_db_)) { : ticket_db(std::move(ticket_db_)) {
if (!config.enc_title_keys_bin_path.empty()) { if (!config.enc_title_keys_bin_path.empty()) {
enc_title_keys_bin = std::make_unique<EncTitleKeysBin>(); enc_title_keys_bin = std::make_unique<EncTitleKeysBin>();
if (!LoadTitleKeysBin(*enc_title_keys_bin, config.enc_title_keys_bin_path)) { if (!LoadTitleKeysBin(*enc_title_keys_bin, config.enc_title_keys_bin_path)) {
LOG_WARNING(Core, "encTitleKeys.bin invalid"); LOG_WARNING(Core, "encTitleKeys.bin invalid");
enc_title_keys_bin.reset(); enc_title_keys_bin.reset();
} }
} }
} }
CIABuilder::~CIABuilder() = default; CIABuilder::~CIABuilder() = default;
bool CIABuilder::Init(CIABuildType type_, const std::string& destination, TitleMetadata tmd_, bool CIABuilder::Init(CIABuildType type_, const std::string& destination, TitleMetadata tmd_,
std::size_t total_size_, const Common::ProgressCallback& callback_) { std::size_t total_size_, const Common::ProgressCallback& callback_) {
type = type_; type = type_;
header = {}; header = {};
meta = {}; meta = {};
if (!FileUtil::CreateFullPath(destination)) { if (!FileUtil::CreateFullPath(destination)) {
LOG_ERROR(Core, "Could not create {}", destination); LOG_ERROR(Core, "Could not create {}", destination);
return false; return false;
} }
file = std::make_shared<HashedFile>(destination, "wb"); file = std::make_shared<HashedFile>(destination, "wb");
if (!*file) { if (!*file) {
LOG_ERROR(Core, "Could not open file {}", destination); LOG_ERROR(Core, "Could not open file {}", destination);
return false; return false;
} }
tmd = std::move(tmd_); tmd = std::move(tmd_);
if (type == CIABuildType::Standard) { if (type == CIABuildType::Standard) {
// Remove encrypted flag from TMD chunks // Remove encrypted flag from TMD chunks
for (auto& chunk : tmd.tmd_chunks) { for (auto& chunk : tmd.tmd_chunks) {
chunk.type &= ~0x01; chunk.type &= ~0x01;
} }
} }
if (type == CIABuildType::Legit || type == CIABuildType::PirateLegit) { if (type == CIABuildType::Legit || type == CIABuildType::PirateLegit) {
// Check for legit TMD // Check for legit TMD
if (!tmd.VerifyHashes() || !tmd.ValidateSignature()) { if (!tmd.VerifyHashes() || !tmd.ValidateSignature()) {
LOG_ERROR(Core, "TMD is not legit"); LOG_ERROR(Core, "TMD is not legit");
return false; return false;
} }
} }
header.header_size = sizeof(header); header.header_size = sizeof(header);
// Header will be written in Finalize // Header will be written in Finalize
// Cert // Cert
cert_offset = Common::AlignUp(header.header_size, CIA_ALIGNMENT); cert_offset = Common::AlignUp(header.header_size, CIA_ALIGNMENT);
header.cert_size = CIA_CERT_SIZE; header.cert_size = CIA_CERT_SIZE;
if (!WriteCert()) { if (!WriteCert()) {
LOG_ERROR(Core, "Could not write cert to file {}", destination); LOG_ERROR(Core, "Could not write cert to file {}", destination);
return false; return false;
} }
// Ticket // Ticket
ticket_offset = Common::AlignUp(cert_offset + header.cert_size, CIA_ALIGNMENT); ticket_offset = Common::AlignUp(cert_offset + header.cert_size, CIA_ALIGNMENT);
if (!WriteTicket()) { if (!WriteTicket()) {
return false; return false;
} }
// TMD will be written in Finalize (we need to set content hash, etc) // TMD will be written in Finalize (we need to set content hash, etc)
tmd_offset = Common::AlignUp(ticket_offset + header.tik_size, CIA_ALIGNMENT); tmd_offset = Common::AlignUp(ticket_offset + header.tik_size, CIA_ALIGNMENT);
header.tmd_size = tmd.GetSize(); header.tmd_size = tmd.GetSize();
content_offset = Common::AlignUp(tmd_offset + header.tmd_size, CIA_ALIGNMENT); content_offset = Common::AlignUp(tmd_offset + header.tmd_size, CIA_ALIGNMENT);
header.content_size = 0; header.content_size = 0;
// Meta will be written in Finalize // Meta will be written in Finalize
header.meta_size = 0; header.meta_size = 0;
// Initialize variables // Initialize variables
written = content_offset; written = content_offset;
total_size = total_size_; total_size = total_size_;
callback = callback_; callback = callback_;
wrapper.total_size = total_size; wrapper.total_size = total_size;
wrapper.SetCurrent(written); wrapper.SetCurrent(written);
callback(written, total_size); callback(written, total_size);
return true; return true;
} }
void CIABuilder::Cleanup() { void CIABuilder::Cleanup() {
file.reset(); file.reset();
} }
bool CIABuilder::WriteCert() { bool CIABuilder::WriteCert() {
if (!Certs::IsLoaded()) { if (!Certs::IsLoaded()) {
return false; return false;
} }
file->Seek(cert_offset, SEEK_SET); file->Seek(cert_offset, SEEK_SET);
for (const auto& cert : CIACertNames) { for (const auto& cert : CIACertNames) {
if (!Certs::Get(cert).Save(*file)) { if (!Certs::Get(cert).Save(*file)) {
LOG_ERROR(Core, "Failed to write cert {}", cert); LOG_ERROR(Core, "Failed to write cert {}", cert);
return false; return false;
} }
} }
return true; return true;
} }
bool CIABuilder::FindLegitTicket(Ticket& ticket, u64 title_id) const { bool CIABuilder::FindLegitTicket(Ticket& ticket, u64 title_id) const {
if (ticket_db && ticket_db->tickets.count(title_id)) { if (ticket_db && ticket_db->tickets.count(title_id)) {
ticket = ticket_db->tickets.at(title_id); ticket = ticket_db->tickets.at(title_id);
if (!ticket.ValidateSignature()) { if (!ticket.ValidateSignature()) {
LOG_ERROR(Core, "Ticket in ticket.db for {:016x} is not legit", title_id); LOG_ERROR(Core, "Ticket in ticket.db for {:016x} is not legit", title_id);
return false; return false;
} }
return true; return true;
} }
LOG_ERROR(Core, "Ticket for {:016x} does not exist in ticket.db", title_id); LOG_ERROR(Core, "Ticket for {:016x} does not exist in ticket.db", title_id);
return false; return false;
} }
Ticket CIABuilder::BuildStandardTicket(u64 title_id) const { Ticket CIABuilder::BuildStandardTicket(u64 title_id) const {
Ticket ticket = BuildFakeTicket(title_id); Ticket ticket = BuildFakeTicket(title_id);
// Fill in common_key_index and title_key from either ticket.db (installed tickets) // Fill in common_key_index and title_key from either ticket.db (installed tickets)
// or GM9 support files (encTitleKeys.bin) found on the SD card // or GM9 support files (encTitleKeys.bin) found on the SD card
if (ticket_db && ticket_db->tickets.count(title_id)) { // ticket.db if (ticket_db && ticket_db->tickets.count(title_id)) { // ticket.db
const auto& legit_ticket = ticket_db->tickets.at(title_id); const auto& legit_ticket = ticket_db->tickets.at(title_id);
ticket.body.common_key_index = legit_ticket.body.common_key_index; ticket.body.common_key_index = legit_ticket.body.common_key_index;
ticket.body.title_key = legit_ticket.body.title_key; ticket.body.title_key = legit_ticket.body.title_key;
} else if (enc_title_keys_bin && enc_title_keys_bin->count(title_id)) { // support files } else if (enc_title_keys_bin && enc_title_keys_bin->count(title_id)) { // support files
const auto& entry = enc_title_keys_bin->at(title_id); const auto& entry = enc_title_keys_bin->at(title_id);
ticket.body.common_key_index = entry.common_key_index; ticket.body.common_key_index = entry.common_key_index;
ticket.body.title_key = entry.title_key; ticket.body.title_key = entry.title_key;
} else { } else {
LOG_WARNING(Core, "Could not find title key for {:016x}", title_id); LOG_WARNING(Core, "Could not find title key for {:016x}", title_id);
} }
return ticket; return ticket;
} }
static Key::AESKey GetTitleKey(const Ticket& ticket) { static Key::AESKey GetTitleKey(const Ticket& ticket) {
Key::SelectCommonKeyIndex(ticket.body.common_key_index); Key::SelectCommonKeyIndex(ticket.body.common_key_index);
if (!Key::IsNormalKeyAvailable(Key::TicketCommonKey)) { if (!Key::IsNormalKeyAvailable(Key::TicketCommonKey)) {
LOG_ERROR(Core, "Ticket common key is not available"); LOG_ERROR(Core, "Ticket common key is not available");
return {}; return {};
} }
const auto ticket_key = Key::GetNormalKey(Key::TicketCommonKey); const auto ticket_key = Key::GetNormalKey(Key::TicketCommonKey);
Key::AESKey ctr{}; Key::AESKey ctr{};
std::memcpy(ctr.data(), &ticket.body.title_id, 8); std::memcpy(ctr.data(), &ticket.body.title_id, 8);
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption aes; CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption aes;
aes.SetKeyWithIV(ticket_key.data(), ticket_key.size(), ctr.data()); aes.SetKeyWithIV(ticket_key.data(), ticket_key.size(), ctr.data());
Key::AESKey title_key = ticket.body.title_key; Key::AESKey title_key = ticket.body.title_key;
aes.ProcessData(title_key.data(), title_key.data(), title_key.size()); aes.ProcessData(title_key.data(), title_key.data(), title_key.size());
return title_key; return title_key;
} }
bool CIABuilder::WriteTicket() { bool CIABuilder::WriteTicket() {
const auto title_id = tmd.GetTitleID(); const auto title_id = tmd.GetTitleID();
Ticket ticket; Ticket ticket;
if (type == CIABuildType::Legit) { if (type == CIABuildType::Legit) {
if (!FindLegitTicket(ticket, title_id)) { if (!FindLegitTicket(ticket, title_id)) {
return false; return false;
} }
} else { } else {
ticket = BuildStandardTicket(title_id); ticket = BuildStandardTicket(title_id);
} }
title_key = GetTitleKey(ticket); title_key = GetTitleKey(ticket);
header.tik_size = ticket.GetSize(); header.tik_size = ticket.GetSize();
file->Seek(ticket_offset, SEEK_SET); file->Seek(ticket_offset, SEEK_SET);
if (!ticket.Save(*file)) { if (!ticket.Save(*file)) {
LOG_ERROR(Core, "Could not write ticket"); LOG_ERROR(Core, "Could not write ticket");
return false; return false;
} }
return true; return true;
} }
class CIAEncryptAndHash final : public CryptoFunc { class CIAEncryptAndHash final : public CryptoFunc {
public: public:
explicit CIAEncryptAndHash(const Key::AESKey& key, const Key::AESKey& iv) { explicit CIAEncryptAndHash(const Key::AESKey& key, const Key::AESKey& iv) {
aes.SetKeyWithIV(key.data(), key.size(), iv.data()); aes.SetKeyWithIV(key.data(), key.size(), iv.data());
} }
~CIAEncryptAndHash() override = default; ~CIAEncryptAndHash() override = default;
void ProcessData(u8* data, std::size_t size) override { void ProcessData(u8* data, std::size_t size) override {
sha.Update(data, size); sha.Update(data, size);
aes.ProcessData(data, data, size); aes.ProcessData(data, data, size);
} }
bool VerifyHash(const u8* hash) { bool VerifyHash(const u8* hash) {
return sha.Verify(hash); return sha.Verify(hash);
} }
private: private:
CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption aes; CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption aes;
CryptoPP::SHA256 sha; CryptoPP::SHA256 sha;
}; };
bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) { bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) {
if (!ncch.Load()) { if (!ncch.Load()) {
return false; return false;
} }
file->Seek(written, SEEK_SET); // To enforce alignment file->Seek(written, SEEK_SET); // To enforce alignment
wrapper.SetCurrent(written); wrapper.SetCurrent(written);
auto& tmd_chunk = tmd.GetContentChunkByID(content_id); auto& tmd_chunk = tmd.GetContentChunkByID(content_id);
if (type == CIABuildType::Standard) { if (type == CIABuildType::Standard) {
// Decrypt the NCCH. We created a HashedFile to transparently calculate the hash as there // Decrypt the NCCH. We created a HashedFile to transparently calculate the hash as there
// is no easy way to get decrypted NCCH content otherwise. // is no easy way to get decrypted NCCH content otherwise.
file->SetHashEnabled(true); file->SetHashEnabled(true);
{ {
std::lock_guard lock{abort_ncch_mutex}; std::lock_guard lock{abort_ncch_mutex};
abort_ncch = &ncch; abort_ncch = &ncch;
} }
const auto ret = ncch.DecryptToFile(file, wrapper.Wrap(callback)); const auto ret = ncch.DecryptToFile(file, wrapper.Wrap(callback));
{ {
std::lock_guard lock{abort_ncch_mutex}; std::lock_guard lock{abort_ncch_mutex};
abort_ncch = nullptr; abort_ncch = nullptr;
} }
if (!ret) { if (!ret) {
return false; return false;
} }
file->GetHash(tmd_chunk.hash.data()); file->GetHash(tmd_chunk.hash.data());
file->SetHashEnabled(false); file->SetHashEnabled(false);
} else { } else {
ncch.file->Seek(0, SEEK_SET); ncch.file->Seek(0, SEEK_SET);
// Calculate IV // Calculate IV
Key::AESKey iv{}; Key::AESKey iv{};
std::memcpy(iv.data(), &tmd_chunk.index, sizeof(tmd_chunk.index)); std::memcpy(iv.data(), &tmd_chunk.index, sizeof(tmd_chunk.index));
const bool is_encrypted = static_cast<u16>(tmd_chunk.type) & 0x01; const bool is_encrypted = static_cast<u16>(tmd_chunk.type) & 0x01;
// For encrypted content, the hashes are calculated before CIA/CDN encryption. // For encrypted content, the hashes are calculated before CIA/CDN encryption.
// So we have to add hash calculation to the CryptoFunc of the FileDecryptor. // So we have to add hash calculation to the CryptoFunc of the FileDecryptor.
// For unencrypted content, we can just use HashedFile's hashing. // For unencrypted content, we can just use HashedFile's hashing.
std::shared_ptr<CIAEncryptAndHash> crypto; std::shared_ptr<CIAEncryptAndHash> crypto;
if (is_encrypted) { if (is_encrypted) {
crypto = std::make_shared<CIAEncryptAndHash>(title_key, iv); crypto = std::make_shared<CIAEncryptAndHash>(title_key, iv);
} else { // crypto left to be null } else { // crypto left to be null
file->SetHashEnabled(true); file->SetHashEnabled(true);
} }
decryptor.SetCrypto(crypto); decryptor.SetCrypto(crypto);
if (!decryptor.CryptAndWriteFile(ncch.file, ncch.file->GetSize(), file, if (!decryptor.CryptAndWriteFile(ncch.file, ncch.file->GetSize(), file,
wrapper.Wrap(callback))) { wrapper.Wrap(callback))) {
return false; return false;
} }
// Verify the hash // Verify the hash
bool verified{}; bool verified{};
if (is_encrypted) { if (is_encrypted) {
verified = crypto->VerifyHash(tmd_chunk.hash.data()); verified = crypto->VerifyHash(tmd_chunk.hash.data());
} else { } else {
verified = file->VerifyHash(tmd_chunk.hash.data()); verified = file->VerifyHash(tmd_chunk.hash.data());
file->SetHashEnabled(false); file->SetHashEnabled(false);
} }
if (!verified) { if (!verified) {
LOG_ERROR(Core, "Hash dismatch for content {}", content_id); LOG_ERROR(Core, "Hash dismatch for content {}", content_id);
return false; return false;
} }
} }
written = Common::AlignUp(file->Tell(), CIA_ALIGNMENT); written = Common::AlignUp(file->Tell(), CIA_ALIGNMENT);
header.content_size = written - content_offset; header.content_size = written - content_offset;
header.SetContentPresent(tmd_chunk.index); header.SetContentPresent(tmd_chunk.index);
// DLCs do not have a meta // DLCs do not have a meta
if (tmd_chunk.index != TMDContentIndex::Main || (tmd.GetTitleID() >> 32) == 0x0004008c) { if (tmd_chunk.index != TMDContentIndex::Main || (tmd.GetTitleID() >> 32) == 0x0004008c) {
return true; return true;
} }
// Load meta if the content is main // Load meta if the content is main
static_assert(sizeof(ncch.exheader_header.dependency_list) == sizeof(meta.dependencies), static_assert(sizeof(ncch.exheader_header.dependency_list) == sizeof(meta.dependencies),
"Dependency list should be of the same size in NCCH and CIA"); "Dependency list should be of the same size in NCCH and CIA");
std::memcpy(meta.dependencies.data(), &ncch.exheader_header.dependency_list, std::memcpy(meta.dependencies.data(), &ncch.exheader_header.dependency_list,
sizeof(meta.dependencies)); sizeof(meta.dependencies));
// Note: GodMode9 has this hardcoded to 2. // Note: GodMode9 has this hardcoded to 2.
meta.core_version = ncch.exheader_header.arm11_system_local_caps.core_version; meta.core_version = ncch.exheader_header.arm11_system_local_caps.core_version;
std::vector<u8> smdh_buffer; std::vector<u8> smdh_buffer;
if (!ncch.LoadSectionExeFS("icon", smdh_buffer)) { if (!ncch.LoadSectionExeFS("icon", smdh_buffer)) {
LOG_WARNING(Core, "Failed to load icon in ExeFS"); LOG_WARNING(Core, "Failed to load icon in ExeFS");
return true; return true;
} }
std::memcpy(meta.icon_data.data(), smdh_buffer.data(), std::memcpy(meta.icon_data.data(), smdh_buffer.data(),
std::min(meta.icon_data.size(), smdh_buffer.size())); std::min(meta.icon_data.size(), smdh_buffer.size()));
header.meta_size = sizeof(meta); header.meta_size = sizeof(meta);
return true; return true;
} }
bool CIABuilder::Finalize() { bool CIABuilder::Finalize() {
// Write header // Write header
file->Seek(0, SEEK_SET); file->Seek(0, SEEK_SET);
if (file->WriteBytes(&header, sizeof(header)) != sizeof(header)) { if (file->WriteBytes(&header, sizeof(header)) != sizeof(header)) {
LOG_ERROR(Core, "Failed to write header"); LOG_ERROR(Core, "Failed to write header");
return false; return false;
} }
// Write TMD // Write TMD
if (type == CIABuildType::Standard) { if (type == CIABuildType::Standard) {
tmd.FixHashes(); tmd.FixHashes();
} }
file->Seek(tmd_offset, SEEK_SET); file->Seek(tmd_offset, SEEK_SET);
if (!tmd.Save(*file)) { if (!tmd.Save(*file)) {
return false; return false;
} }
// Write meta // Write meta
if (header.meta_size) { if (header.meta_size) {
file->Seek(written, SEEK_SET); file->Seek(written, SEEK_SET);
if (file->WriteBytes(&meta, sizeof(meta)) != sizeof(meta)) { if (file->WriteBytes(&meta, sizeof(meta)) != sizeof(meta)) {
LOG_ERROR(Core, "Failed to write meta"); LOG_ERROR(Core, "Failed to write meta");
return false; return false;
} }
} }
callback(total_size, total_size); callback(total_size, total_size);
return true; return true;
} }
void CIABuilder::Abort() { void CIABuilder::Abort() {
if (type == CIABuildType::Standard) { // Abort NCCH decryption if (type == CIABuildType::Standard) { // Abort NCCH decryption
std::lock_guard lock{abort_ncch_mutex}; std::lock_guard lock{abort_ncch_mutex};
if (abort_ncch) { if (abort_ncch) {
abort_ncch->AbortDecryptToFile(); abort_ncch->AbortDecryptToFile();
} }
} else { // Abort the decryptor } else { // Abort the decryptor
decryptor.Abort(); decryptor.Abort();
} }
} }
} // namespace Core } // namespace Core
+137 -137
View File
@@ -1,137 +1,137 @@
// Copyright 2017 Citra Emulator Project / 2020 Pengfei Zhu // Copyright 2017 Citra Emulator Project / 2020 Pengfei Zhu
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include "common/file_util.h" #include "common/file_util.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "common/swap.h" #include "common/swap.h"
#include "core/file_decryptor.h" #include "core/file_decryptor.h"
#include "core/file_sys/cia_common.h" #include "core/file_sys/cia_common.h"
#include "core/file_sys/ncch_container.h" #include "core/file_sys/ncch_container.h"
#include "core/file_sys/title_metadata.h" #include "core/file_sys/title_metadata.h"
#include "core/key/key.h" #include "core/key/key.h"
namespace Core { namespace Core {
constexpr std::size_t CIA_CONTENT_MAX_COUNT = 0x10000; constexpr std::size_t CIA_CONTENT_MAX_COUNT = 0x10000;
constexpr std::size_t CIA_CONTENT_BITS_SIZE = (CIA_CONTENT_MAX_COUNT / 8); constexpr std::size_t CIA_CONTENT_BITS_SIZE = (CIA_CONTENT_MAX_COUNT / 8);
constexpr std::size_t CIA_HEADER_SIZE = 0x2020; constexpr std::size_t CIA_HEADER_SIZE = 0x2020;
constexpr std::size_t CIA_CERT_SIZE = 0xA00; constexpr std::size_t CIA_CERT_SIZE = 0xA00;
constexpr std::size_t CIA_METADATA_SIZE = 0x3AC0; constexpr std::size_t CIA_METADATA_SIZE = 0x3AC0;
struct Config; struct Config;
class EncTitleKeysBin; class EncTitleKeysBin;
class HashedFile; class HashedFile;
class Ticket; class Ticket;
class TicketDB; class TicketDB;
class CIABuilder { class CIABuilder {
public: public:
explicit CIABuilder(const Config& config, std::shared_ptr<TicketDB> ticket_db); explicit CIABuilder(const Config& config, std::shared_ptr<TicketDB> ticket_db);
~CIABuilder(); ~CIABuilder();
/** /**
* Initializes the building of the CIA. * Initializes the building of the CIA.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool Init(CIABuildType type, const std::string& destination, TitleMetadata tmd, bool Init(CIABuildType type, const std::string& destination, TitleMetadata tmd,
std::size_t total_size, const Common::ProgressCallback& callback); std::size_t total_size, const Common::ProgressCallback& callback);
void Cleanup(); void Cleanup();
/** /**
* Adds an NCCH content to the CIA. * Adds an NCCH content to the CIA.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool AddContent(u16 content_id, NCCHContainer& ncch); bool AddContent(u16 content_id, NCCHContainer& ncch);
/** /**
* Finalizes this CIA and write remaining data. * Finalizes this CIA and write remaining data.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool Finalize(); bool Finalize();
/** /**
* Aborts the current work. In fact, only usable during AddContent. * Aborts the current work. In fact, only usable during AddContent.
*/ */
void Abort(); void Abort();
private: private:
struct Header { struct Header {
u32_le header_size; u32_le header_size;
u16_le type; u16_le type;
u16_le version; u16_le version;
u32_le cert_size; u32_le cert_size;
u32_le tik_size; u32_le tik_size;
u32_le tmd_size; u32_le tmd_size;
u32_le meta_size; u32_le meta_size;
u64_le content_size; u64_le content_size;
std::array<u8, CIA_CONTENT_BITS_SIZE> content_present; std::array<u8, CIA_CONTENT_BITS_SIZE> content_present;
bool IsContentPresent(u16 index) const { bool IsContentPresent(u16 index) const {
// The content_present is a bit array which defines which content in the TMD // The content_present is a bit array which defines which content in the TMD
// is included in the CIA, so check the bit for this index and add if set. // is included in the CIA, so check the bit for this index and add if set.
// The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc. // The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc.
return (content_present[index >> 3] & (0x80 >> (index & 7))); return (content_present[index >> 3] & (0x80 >> (index & 7)));
} }
void SetContentPresent(u16 index) { void SetContentPresent(u16 index) {
content_present[index >> 3] |= (0x80 >> (index & 7)); content_present[index >> 3] |= (0x80 >> (index & 7));
} }
}; };
static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong"); static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong");
struct Metadata { struct Metadata {
std::array<u64_le, 0x30> dependencies; std::array<u64_le, 0x30> dependencies;
std::array<u8, 0x180> reserved; std::array<u8, 0x180> reserved;
u32_le core_version; u32_le core_version;
std::array<u8, 0xfc> reserved_2; std::array<u8, 0xfc> reserved_2;
std::array<u8, 0x36c0> icon_data; std::array<u8, 0x36c0> icon_data;
}; };
static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong"); static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong");
bool WriteCert(); bool WriteCert();
bool FindLegitTicket(Ticket& ticket, u64 title_id) const; bool FindLegitTicket(Ticket& ticket, u64 title_id) const;
Ticket BuildStandardTicket(u64 title_id) const; Ticket BuildStandardTicket(u64 title_id) const;
bool WriteTicket(); bool WriteTicket();
// Persistent state // Persistent state
const std::shared_ptr<TicketDB> ticket_db; const std::shared_ptr<TicketDB> ticket_db;
std::unique_ptr<EncTitleKeysBin> enc_title_keys_bin; std::unique_ptr<EncTitleKeysBin> enc_title_keys_bin;
// State of a single task // State of a single task
CIABuildType type; CIABuildType type;
Header header{}; Header header{};
Metadata meta{}; Metadata meta{};
TitleMetadata tmd; TitleMetadata tmd;
Key::AESKey title_key{}; Key::AESKey title_key{};
std::size_t cert_offset{}; std::size_t cert_offset{};
std::size_t ticket_offset{}; std::size_t ticket_offset{};
std::size_t tmd_offset{}; std::size_t tmd_offset{};
std::size_t content_offset{}; std::size_t content_offset{};
std::shared_ptr<HashedFile> file; std::shared_ptr<HashedFile> file;
std::size_t written{}; // size written (with alignment) std::size_t written{}; // size written (with alignment)
std::size_t total_size{}; std::size_t total_size{};
Common::ProgressCallback callback; Common::ProgressCallback callback;
Common::ProgressCallbackWrapper wrapper; Common::ProgressCallbackWrapper wrapper;
// The NCCH to abort on // The NCCH to abort on
std::mutex abort_ncch_mutex; std::mutex abort_ncch_mutex;
NCCHContainer* abort_ncch{}; NCCHContainer* abort_ncch{};
FileDecryptor decryptor; FileDecryptor decryptor;
}; };
} // namespace Core } // namespace Core
+93 -93
View File
@@ -1,93 +1,93 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <string> #include <string>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/key/key.h" #include "core/key/key.h"
namespace FileUtil { namespace FileUtil {
class IOFile; class IOFile;
} }
namespace Core { namespace Core {
class CryptoFunc; class CryptoFunc;
/** /**
* Generalized file decryptor. * Generalized file decryptor.
* Helper that reads, decrypts and writes data. This uses three threads to process the data * Helper that reads, decrypts and writes data. This uses three threads to process the data
* and call progress callbacks occasionally. * and call progress callbacks occasionally.
*/ */
class FileDecryptor { class FileDecryptor {
public: public:
explicit FileDecryptor(); explicit FileDecryptor();
~FileDecryptor(); ~FileDecryptor();
/** /**
* Set up the crypto to use. * Set up the crypto to use.
* Default / nullptr is plain copy. * Default / nullptr is plain copy.
*/ */
void SetCrypto(std::shared_ptr<CryptoFunc> crypto); void SetCrypto(std::shared_ptr<CryptoFunc> crypto);
/** /**
* Crypts and writes a file. * Crypts and writes a file.
* *
* @param source Source file * @param source Source file
* @param size Size to read, decrypt and write * @param size Size to read, decrypt and write
* @param destination Destination file * @param destination Destination file
* @param callback Progress callback. default for nothing. * @param callback Progress callback. default for nothing.
*/ */
bool CryptAndWriteFile( bool CryptAndWriteFile(
std::shared_ptr<FileUtil::IOFile> source, std::size_t size, std::shared_ptr<FileUtil::IOFile> source, std::size_t size,
std::shared_ptr<FileUtil::IOFile> destination, std::shared_ptr<FileUtil::IOFile> destination,
const Common::ProgressCallback& callback = [](u64, u64) {}); const Common::ProgressCallback& callback = [](u64, u64) {});
void DataReadLoop(); void DataReadLoop();
void DataDecryptLoop(); void DataDecryptLoop();
void DataWriteLoop(); void DataWriteLoop();
void Abort(); void Abort();
private: private:
static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB
std::shared_ptr<FileUtil::IOFile> source; std::shared_ptr<FileUtil::IOFile> source;
std::shared_ptr<FileUtil::IOFile> destination; std::shared_ptr<FileUtil::IOFile> destination;
std::shared_ptr<CryptoFunc> crypto; std::shared_ptr<CryptoFunc> crypto;
std::size_t total_size{}; std::size_t total_size{};
std::array<std::array<u8, BufferSize>, 3> buffers; std::array<std::array<u8, BufferSize>, 3> buffers;
std::array<Common::Event, 3> data_read_event; std::array<Common::Event, 3> data_read_event;
std::array<Common::Event, 3> data_decrypted_event; std::array<Common::Event, 3> data_decrypted_event;
std::array<Common::Event, 3> data_written_event; std::array<Common::Event, 3> data_written_event;
std::unique_ptr<std::thread> read_thread; std::unique_ptr<std::thread> read_thread;
std::unique_ptr<std::thread> decrypt_thread; std::unique_ptr<std::thread> decrypt_thread;
std::unique_ptr<std::thread> write_thread; std::unique_ptr<std::thread> write_thread;
Common::ProgressCallback callback; Common::ProgressCallback callback;
Common::Event completion_event; Common::Event completion_event;
bool is_good{true}; bool is_good{true};
std::atomic_bool is_running{false}; std::atomic_bool is_running{false};
}; };
class CryptoFunc { class CryptoFunc {
public: public:
virtual ~CryptoFunc(); virtual ~CryptoFunc();
virtual void ProcessData(u8* data, std::size_t size) = 0; virtual void ProcessData(u8* data, std::size_t size) = 0;
}; };
std::shared_ptr<CryptoFunc> CreateCTRCrypto(const Key::AESKey& key, const Key::AESKey& ctr, std::shared_ptr<CryptoFunc> CreateCTRCrypto(const Key::AESKey& key, const Key::AESKey& ctr,
std::size_t seek_pos = 0); std::size_t seek_pos = 0);
} // namespace Core } // namespace Core
File diff suppressed because it is too large Load Diff
+330 -330
View File
@@ -1,330 +1,330 @@
// Copyright 2017 Citra Emulator Project / 2019 threeSD Project // Copyright 2017 Citra Emulator Project / 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "common/swap.h" #include "common/swap.h"
#include "core/sdmc_decryptor.h" #include "core/sdmc_decryptor.h"
namespace Core { namespace Core {
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
/// NCCH (Nintendo Content Container Header) header /// NCCH (Nintendo Content Container Header) header
struct NCCH_Header { struct NCCH_Header {
u8 signature[0x100]; u8 signature[0x100];
u32_le magic; u32_le magic;
u32_le content_size; u32_le content_size;
u8 partition_id[8]; u8 partition_id[8];
u16_le maker_code; u16_le maker_code;
u16_le version; u16_le version;
u8 reserved_0[4]; u8 reserved_0[4];
u64_le program_id; u64_le program_id;
u8 reserved_1[0x10]; u8 reserved_1[0x10];
u8 logo_region_hash[0x20]; u8 logo_region_hash[0x20];
u8 product_code[0x10]; u8 product_code[0x10];
u8 extended_header_hash[0x20]; u8 extended_header_hash[0x20];
u32_le extended_header_size; u32_le extended_header_size;
u8 reserved_2[4]; u8 reserved_2[4];
u8 reserved_flag[3]; u8 reserved_flag[3];
u8 secondary_key_slot; u8 secondary_key_slot;
u8 platform; u8 platform;
enum class ContentType : u8 { enum class ContentType : u8 {
Application = 0, Application = 0,
SystemUpdate = 1, SystemUpdate = 1,
Manual = 2, Manual = 2,
Child = 3, Child = 3,
Trial = 4, Trial = 4,
}; };
union { union {
BitField<0, 1, u8> is_data; BitField<0, 1, u8> is_data;
BitField<1, 1, u8> is_executable; BitField<1, 1, u8> is_executable;
BitField<2, 3, ContentType> content_type; BitField<2, 3, ContentType> content_type;
}; };
u8 content_unit_size; u8 content_unit_size;
union { union {
BitField<0, 1, u8> fixed_key; BitField<0, 1, u8> fixed_key;
BitField<1, 1, u8> no_romfs; BitField<1, 1, u8> no_romfs;
BitField<2, 1, u8> no_crypto; BitField<2, 1, u8> no_crypto;
BitField<5, 1, u8> seed_crypto; BitField<5, 1, u8> seed_crypto;
u8 raw_crypto_flags; u8 raw_crypto_flags;
}; };
u32_le plain_region_offset; u32_le plain_region_offset;
u32_le plain_region_size; u32_le plain_region_size;
u32_le logo_region_offset; u32_le logo_region_offset;
u32_le logo_region_size; u32_le logo_region_size;
u32_le exefs_offset; u32_le exefs_offset;
u32_le exefs_size; u32_le exefs_size;
u32_le exefs_hash_region_size; u32_le exefs_hash_region_size;
u8 reserved_3[4]; u8 reserved_3[4];
u32_le romfs_offset; u32_le romfs_offset;
u32_le romfs_size; u32_le romfs_size;
u32_le romfs_hash_region_size; u32_le romfs_hash_region_size;
u8 reserved_4[4]; u8 reserved_4[4];
u8 exefs_super_block_hash[0x20]; u8 exefs_super_block_hash[0x20];
u8 romfs_super_block_hash[0x20]; u8 romfs_super_block_hash[0x20];
}; };
static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong"); static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong");
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
// ExeFS (executable file system) headers // ExeFS (executable file system) headers
struct ExeFs_SectionHeader { struct ExeFs_SectionHeader {
char name[8]; char name[8];
u32 offset; u32 offset;
u32 size; u32 size;
}; };
struct ExeFs_Header { struct ExeFs_Header {
ExeFs_SectionHeader section[8]; ExeFs_SectionHeader section[8];
u8 reserved[0x80]; u8 reserved[0x80];
u8 hashes[8][0x20]; u8 hashes[8][0x20];
}; };
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
// ExHeader (executable file system header) headers // ExHeader (executable file system header) headers
struct ExHeader_SystemInfoFlags { struct ExHeader_SystemInfoFlags {
u8 reserved[5]; u8 reserved[5];
u8 flag; u8 flag;
u8 remaster_version[2]; u8 remaster_version[2];
}; };
struct ExHeader_CodeSegmentInfo { struct ExHeader_CodeSegmentInfo {
u32 address; u32 address;
u32 num_max_pages; u32 num_max_pages;
u32 code_size; u32 code_size;
}; };
struct ExHeader_CodeSetInfo { struct ExHeader_CodeSetInfo {
u8 name[8]; u8 name[8];
ExHeader_SystemInfoFlags flags; ExHeader_SystemInfoFlags flags;
ExHeader_CodeSegmentInfo text; ExHeader_CodeSegmentInfo text;
u32 stack_size; u32 stack_size;
ExHeader_CodeSegmentInfo ro; ExHeader_CodeSegmentInfo ro;
u8 reserved[4]; u8 reserved[4];
ExHeader_CodeSegmentInfo data; ExHeader_CodeSegmentInfo data;
u32 bss_size; u32 bss_size;
}; };
struct ExHeader_DependencyList { struct ExHeader_DependencyList {
u8 program_id[0x30][8]; u8 program_id[0x30][8];
}; };
struct ExHeader_SystemInfo { struct ExHeader_SystemInfo {
u64 save_data_size; u64 save_data_size;
u64_le jump_id; u64_le jump_id;
u8 reserved_2[0x30]; u8 reserved_2[0x30];
}; };
struct ExHeader_StorageInfo { struct ExHeader_StorageInfo {
union { union {
u64_le ext_save_data_id; u64_le ext_save_data_id;
// When using extended savedata access // When using extended savedata access
// Prefer the ID specified in the most significant bits // Prefer the ID specified in the most significant bits
BitField<40, 20, u64> extdata_id3; BitField<40, 20, u64> extdata_id3;
BitField<20, 20, u64> extdata_id4; BitField<20, 20, u64> extdata_id4;
BitField<0, 20, u64> extdata_id5; BitField<0, 20, u64> extdata_id5;
}; };
u8 system_save_data_id[8]; u8 system_save_data_id[8];
union { union {
u64_le storage_accessible_unique_ids; u64_le storage_accessible_unique_ids;
// When using extended savedata access // When using extended savedata access
// Prefer the ID specified in the most significant bits // Prefer the ID specified in the most significant bits
BitField<40, 20, u64> extdata_id0; BitField<40, 20, u64> extdata_id0;
BitField<20, 20, u64> extdata_id1; BitField<20, 20, u64> extdata_id1;
BitField<0, 20, u64> extdata_id2; BitField<0, 20, u64> extdata_id2;
}; };
u8 access_info[7]; u8 access_info[7];
u8 other_attributes; u8 other_attributes;
}; };
struct ExHeader_ARM11_SystemLocalCaps { struct ExHeader_ARM11_SystemLocalCaps {
u64_le program_id; u64_le program_id;
u32_le core_version; u32_le core_version;
u8 reserved_flags[2]; u8 reserved_flags[2];
union { union {
u8 flags0; u8 flags0;
BitField<0, 2, u8> ideal_processor; BitField<0, 2, u8> ideal_processor;
BitField<2, 2, u8> affinity_mask; BitField<2, 2, u8> affinity_mask;
BitField<4, 4, u8> system_mode; BitField<4, 4, u8> system_mode;
}; };
u8 priority; u8 priority;
u8 resource_limit_descriptor[0x10][2]; u8 resource_limit_descriptor[0x10][2];
ExHeader_StorageInfo storage_info; ExHeader_StorageInfo storage_info;
u8 service_access_control[0x20][8]; u8 service_access_control[0x20][8];
u8 ex_service_access_control[0x2][8]; u8 ex_service_access_control[0x2][8];
u8 reserved[0xf]; u8 reserved[0xf];
u8 resource_limit_category; u8 resource_limit_category;
}; };
struct ExHeader_ARM11_KernelCaps { struct ExHeader_ARM11_KernelCaps {
u32_le descriptors[28]; u32_le descriptors[28];
u8 reserved[0x10]; u8 reserved[0x10];
}; };
struct ExHeader_ARM9_AccessControl { struct ExHeader_ARM9_AccessControl {
u8 descriptors[15]; u8 descriptors[15];
u8 descversion; u8 descversion;
}; };
struct ExHeader_Header { struct ExHeader_Header {
ExHeader_CodeSetInfo codeset_info; ExHeader_CodeSetInfo codeset_info;
ExHeader_DependencyList dependency_list; ExHeader_DependencyList dependency_list;
ExHeader_SystemInfo system_info; ExHeader_SystemInfo system_info;
ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps;
ExHeader_ARM11_KernelCaps arm11_kernel_caps; ExHeader_ARM11_KernelCaps arm11_kernel_caps;
ExHeader_ARM9_AccessControl arm9_access_control; ExHeader_ARM9_AccessControl arm9_access_control;
struct { struct {
u8 signature[0x100]; u8 signature[0x100];
u8 ncch_public_key_modulus[0x100]; u8 ncch_public_key_modulus[0x100];
ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps;
ExHeader_ARM11_KernelCaps arm11_kernel_caps; ExHeader_ARM11_KernelCaps arm11_kernel_caps;
ExHeader_ARM9_AccessControl arm9_access_control; ExHeader_ARM9_AccessControl arm9_access_control;
} access_desc; } access_desc;
}; };
static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong"); static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong");
/** /**
* Encryption type of an importable content. * Encryption type of an importable content.
*/ */
enum class EncryptionType { enum class EncryptionType {
None, None,
FixedKey, FixedKey,
NCCHSecure1, NCCHSecure1,
NCCHSecure2, NCCHSecure2,
NCCHSecure3, NCCHSecure3,
NCCHSecure4, NCCHSecure4,
}; };
/** /**
* Helper which implements an interface to deal with NCCH containers which can * Helper which implements an interface to deal with NCCH containers which can
* contain ExeFS archives or RomFS archives for games or other applications. * contain ExeFS archives or RomFS archives for games or other applications.
* *
* Note that this is heavily stripped down and can only read (primary-key * Note that this is heavily stripped down and can only read (primary-key
* encrypted non-code sections of) ExeFS and ExHeader by design. * encrypted non-code sections of) ExeFS and ExHeader by design.
*/ */
class NCCHContainer { class NCCHContainer {
public: public:
NCCHContainer(std::shared_ptr<FileUtil::IOFile> file); NCCHContainer(std::shared_ptr<FileUtil::IOFile> file);
NCCHContainer() {} NCCHContainer() {}
bool OpenFile(std::shared_ptr<FileUtil::IOFile> file); bool OpenFile(std::shared_ptr<FileUtil::IOFile> file);
/** /**
* Ensure ExeFS and exheader is loaded and ready for reading sections * Ensure ExeFS and exheader is loaded and ready for reading sections
*/ */
bool Load(); bool Load();
/** /**
* Reads an application ExeFS section of an NCCH file (non-compressed, primary key only) * Reads an application ExeFS section of an NCCH file (non-compressed, primary key only)
* @param name Name of section to read out of NCCH file * @param name Name of section to read out of NCCH file
* @param buffer Vector to read data into * @param buffer Vector to read data into
*/ */
bool LoadSectionExeFS(const char* name, std::vector<u8>& buffer); bool LoadSectionExeFS(const char* name, std::vector<u8>& buffer);
/** /**
* Get the Program ID of the NCCH container * Get the Program ID of the NCCH container
*/ */
bool ReadProgramId(u64_le& program_id); bool ReadProgramId(u64_le& program_id);
/** /**
* Get the Extdata ID of the NCCH container * Get the Extdata ID of the NCCH container
*/ */
bool ReadExtdataId(u64& extdata_id); bool ReadExtdataId(u64& extdata_id);
/** /**
* Checks whether the NCCH container contains an ExeFS * Checks whether the NCCH container contains an ExeFS
* @return bool check result * @return bool check result
*/ */
bool HasExeFS(); bool HasExeFS();
/** /**
* Checks whether the NCCH container contains an ExHeader * Checks whether the NCCH container contains an ExHeader
* @return bool check result * @return bool check result
*/ */
bool HasExHeader(); bool HasExHeader();
/** /**
* Reads the name of the codeset. * Reads the name of the codeset.
*/ */
bool ReadCodesetName(std::string& name); bool ReadCodesetName(std::string& name);
/** /**
* Reads the product code. * Reads the product code.
*/ */
bool ReadProductCode(std::string& name); bool ReadProductCode(std::string& name);
/** /**
* Gets encryption type (which key is used). * Gets encryption type (which key is used).
*/ */
bool ReadEncryptionType(EncryptionType& encryption); bool ReadEncryptionType(EncryptionType& encryption);
/** /**
* Gets whether seed crypto is used. * Gets whether seed crypto is used.
*/ */
bool ReadSeedCrypto(bool& used); bool ReadSeedCrypto(bool& used);
/** /**
* Decrypts this NCCH and write to the destination file. * Decrypts this NCCH and write to the destination file.
*/ */
bool DecryptToFile( bool DecryptToFile(
std::shared_ptr<FileUtil::IOFile> dest_file, std::shared_ptr<FileUtil::IOFile> dest_file,
const Common::ProgressCallback& callback = [](u64, u64) {}); const Common::ProgressCallback& callback = [](u64, u64) {});
/** /**
* Aborts DecryptToFile. Simply aborts the decryptor. * Aborts DecryptToFile. Simply aborts the decryptor.
*/ */
void AbortDecryptToFile(); void AbortDecryptToFile();
NCCH_Header ncch_header; NCCH_Header ncch_header;
ExHeader_Header exheader_header; ExHeader_Header exheader_header;
ExeFs_Header exefs_header; ExeFs_Header exefs_header;
private: private:
bool has_exheader = false; bool has_exheader = false;
bool has_exefs = false; bool has_exefs = false;
bool has_romfs = false; bool has_romfs = false;
bool is_loaded = false; bool is_loaded = false;
bool is_encrypted = false; bool is_encrypted = false;
// for decrypting exheader, exefs header and icon/banner section // for decrypting exheader, exefs header and icon/banner section
std::array<u8, 16> primary_key{}; std::array<u8, 16> primary_key{};
std::array<u8, 16> secondary_key{}; // for decrypting romfs and .code section std::array<u8, 16> secondary_key{}; // for decrypting romfs and .code section
std::array<u8, 16> exheader_ctr{}; std::array<u8, 16> exheader_ctr{};
std::array<u8, 16> exefs_ctr{}; std::array<u8, 16> exefs_ctr{};
std::array<u8, 16> romfs_ctr{}; std::array<u8, 16> romfs_ctr{};
u32 exefs_offset = 0; u32 exefs_offset = 0;
std::string root_folder; std::string root_folder;
std::string filepath; std::string filepath;
std::shared_ptr<FileUtil::IOFile> file; std::shared_ptr<FileUtil::IOFile> file;
std::shared_ptr<FileUtil::IOFile> exefs_file; std::shared_ptr<FileUtil::IOFile> exefs_file;
// Used for DecryptToFile // Used for DecryptToFile
FileDecryptor decryptor; FileDecryptor decryptor;
std::atomic_bool aborted{false}; std::atomic_bool aborted{false};
friend class CIABuilder; friend class CIABuilder;
}; };
/** /**
* Extracts the shared RomFS from a NCCH image. * Extracts the shared RomFS from a NCCH image.
* Used for handling system archives. * Used for handling system archives.
*/ */
std::vector<u8> LoadSharedRomFS(const std::vector<u8>& data); std::vector<u8> LoadSharedRomFS(const std::vector<u8>& data);
} // namespace Core } // namespace Core
+244 -244
View File
@@ -1,244 +1,244 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "core/file_decryptor.h" #include "core/file_decryptor.h"
#include "core/file_sys/cia_common.h" #include "core/file_sys/cia_common.h"
namespace Core { namespace Core {
class CIABuilder; class CIABuilder;
class SDMCDecryptor; class SDMCDecryptor;
class TicketDB; class TicketDB;
class TitleDB; class TitleDB;
class TitleMetadata; class TitleMetadata;
/** /**
* Type of an importable content. * Type of an importable content.
* Applications, updates and DLCs are all considered titles. * Applications, updates and DLCs are all considered titles.
*/ */
enum class ContentType { enum class ContentType {
Application, Application,
Update, Update,
DLC, DLC,
Savegame, Savegame,
Extdata, Extdata,
SystemArchive, SystemArchive,
Sysdata, Sysdata,
SystemTitle, SystemTitle,
SystemApplet, // This should belong to System Title, but they cause problems so a new category. SystemApplet, // This should belong to System Title, but they cause problems so a new category.
}; };
constexpr bool IsTitle(ContentType type) { constexpr bool IsTitle(ContentType type) {
return type == ContentType::Application || type == ContentType::Update || return type == ContentType::Application || type == ContentType::Update ||
type == ContentType::DLC || type == ContentType::SystemTitle || type == ContentType::DLC || type == ContentType::SystemTitle ||
type == ContentType::SystemApplet; type == ContentType::SystemApplet;
} }
constexpr bool IsNandTitle(ContentType type) { constexpr bool IsNandTitle(ContentType type) {
return type == ContentType::SystemTitle || type == ContentType::SystemApplet; return type == ContentType::SystemTitle || type == ContentType::SystemApplet;
} }
/** /**
* Struct that specifies an importable content. * Struct that specifies an importable content.
*/ */
struct ContentSpecifier { struct ContentSpecifier {
ContentType type; ContentType type;
u64 id; u64 id;
bool already_exists; ///< Tells whether a file already exists in target path. bool already_exists; ///< Tells whether a file already exists in target path.
u64 maximum_size; ///< The maximum size of the content. May be slightly bigger than real size. u64 maximum_size; ///< The maximum size of the content. May be slightly bigger than real size.
std::string name; ///< Optional. The content's preferred display name. std::string name; ///< Optional. The content's preferred display name.
u64 extdata_id; ///< Extdata ID for Applications. u64 extdata_id; ///< Extdata ID for Applications.
std::vector<u16> icon; ///< Optional. The content's icon. std::vector<u16> icon; ///< Optional. The content's icon.
}; };
/** /**
* A set of values that are used to initialize the importer. * A set of values that are used to initialize the importer.
* All paths to directories shall end with a '/' (will be automatically added when not present) * All paths to directories shall end with a '/' (will be automatically added when not present)
*/ */
struct Config { struct Config {
std::string sdmc_path; ///< SDMC root path ("Nintendo 3DS/<ID0>/<ID1>") std::string sdmc_path; ///< SDMC root path ("Nintendo 3DS/<ID0>/<ID1>")
std::string user_path; ///< Target user path of Citra std::string user_path; ///< Target user path of Citra
// Necessary system files keys are loaded from. // Necessary system files keys are loaded from.
std::string movable_sed_path; ///< Path to movable.sed std::string movable_sed_path; ///< Path to movable.sed
std::string bootrom_path; ///< Path to bootrom (boot9.bin) (Sysdata 0) std::string bootrom_path; ///< Path to bootrom (boot9.bin) (Sysdata 0)
std::string certs_db_path; ///< Path to certs.db. Used while building CIA. std::string certs_db_path; ///< Path to certs.db. Used while building CIA.
// Optional, used while building CIA, but usually missing these files won't hinder CIA building. // Optional, used while building CIA, but usually missing these files won't hinder CIA building.
std::string nand_title_db_path; ///< Path to NAND title.db. Entirely optional. std::string nand_title_db_path; ///< Path to NAND title.db. Entirely optional.
std::string ticket_db_path; ///< Path to ticket.db. Entirely optional. std::string ticket_db_path; ///< Path to ticket.db. Entirely optional.
std::string enc_title_keys_bin_path; ///< Path to encTitleKeys.bin. Entireley optional. std::string enc_title_keys_bin_path; ///< Path to encTitleKeys.bin. Entireley optional.
// The following system files are optional for importing and are only copied so that Citra // The following system files are optional for importing and are only copied so that Citra
// will be able to decrypt imported encrypted ROMs. // will be able to decrypt imported encrypted ROMs.
std::string safe_mode_firm_path; ///< Path to safe mode firm (A folder) (Sysdata 1) std::string safe_mode_firm_path; ///< Path to safe mode firm (A folder) (Sysdata 1)
std::string seed_db_path; ///< Path to seeddb.bin (Sysdata 2) std::string seed_db_path; ///< Path to seeddb.bin (Sysdata 2)
std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3) std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3)
// Note: Sysdata 4 is aes_keys.txt (slot0x25KeyX) // Note: Sysdata 4 is aes_keys.txt (slot0x25KeyX)
std::string config_savegame_path; ///< Path to config savegame (Sysdata 5) std::string config_savegame_path; ///< Path to config savegame (Sysdata 5)
std::string system_archives_path; ///< Path to system archives. std::string system_archives_path; ///< Path to system archives.
std::string system_titles_path; ///< Path to system titles. std::string system_titles_path; ///< Path to system titles.
std::string nand_data_path; ///< Path to NAND data. (Extdata and savedata) std::string nand_data_path; ///< Path to NAND data. (Extdata and savedata)
int version = 0; ///< Version of the dumper used. int version = 0; ///< Version of the dumper used.
}; };
// Version of the current dumper. // Version of the current dumper.
constexpr int CurrentDumperVersion = 4; constexpr int CurrentDumperVersion = 4;
class SDMCFile; class SDMCFile;
class NCCHContainer; class NCCHContainer;
class SDMCImporter { class SDMCImporter {
public: public:
/** /**
* Initializes the importer. * Initializes the importer.
* @param root_folder Path to the "Nintendo 3DS/<ID0>/<ID1>" folder. * @param root_folder Path to the "Nintendo 3DS/<ID0>/<ID1>" folder.
*/ */
explicit SDMCImporter(const Config& config); explicit SDMCImporter(const Config& config);
~SDMCImporter(); ~SDMCImporter();
/** /**
* Imports a specific content by its specifier, deleting it when failed. * Imports a specific content by its specifier, deleting it when failed.
* Blocks, but can be aborted on another thread if needed. * Blocks, but can be aborted on another thread if needed.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool ImportContent( bool ImportContent(
const ContentSpecifier& specifier, const ContentSpecifier& specifier,
const Common::ProgressCallback& callback = [](u64, u64) {}); const Common::ProgressCallback& callback = [](u64, u64) {});
/** /**
* Aborts current importing. * Aborts current importing.
*/ */
void AbortImporting(); void AbortImporting();
/** /**
* Dumps a content to CXI. * Dumps a content to CXI.
* Blocks, but can be aborted on another thread. * Blocks, but can be aborted on another thread.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool DumpCXI(const ContentSpecifier& specifier, std::string destination, bool DumpCXI(const ContentSpecifier& specifier, std::string destination,
const Common::ProgressCallback& callback, bool auto_filename = false); const Common::ProgressCallback& callback, bool auto_filename = false);
/** /**
* Aborts current CXI dumping. * Aborts current CXI dumping.
*/ */
void AbortDumpCXI(); void AbortDumpCXI();
/** /**
* Builds a CIA from a content. * Builds a CIA from a content.
* Blocks, but can be aborted on another thread. * Blocks, but can be aborted on another thread.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool BuildCIA(CIABuildType build_type, const ContentSpecifier& specifier, bool BuildCIA(CIABuildType build_type, const ContentSpecifier& specifier,
std::string destination, const Common::ProgressCallback& callback, std::string destination, const Common::ProgressCallback& callback,
bool auto_filename = false); bool auto_filename = false);
/** /**
* Checks if a content can be built as a legit CIA. * Checks if a content can be built as a legit CIA.
*/ */
bool CanBuildLegitCIA(const ContentSpecifier& specifier) const; bool CanBuildLegitCIA(const ContentSpecifier& specifier) const;
/** /**
* Aborts current CIA building * Aborts current CIA building
*/ */
void AbortBuildCIA(); void AbortBuildCIA();
/** /**
* Checks the contents of a title against its TMD hashes. * Checks the contents of a title against its TMD hashes.
*/ */
bool CheckTitleContents( bool CheckTitleContents(
const ContentSpecifier& specifier, const ContentSpecifier& specifier,
const Common::ProgressCallback& callback = [](u64, u64) {}); const Common::ProgressCallback& callback = [](u64, u64) {});
/** /**
* Gets a list of dumpable content specifiers. * Gets a list of dumpable content specifiers.
*/ */
std::vector<ContentSpecifier> ListContent() const; std::vector<ContentSpecifier> ListContent() const;
/** /**
* Returns whether the importer is in good state. * Returns whether the importer is in good state.
*/ */
bool IsGood() const; bool IsGood() const;
bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const; bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const;
bool LoadTMD(const ContentSpecifier& specifier, TitleMetadata& out) const; bool LoadTMD(const ContentSpecifier& specifier, TitleMetadata& out) const;
std::string GetTitleContentsPath(const ContentSpecifier& specifier) const; std::string GetTitleContentsPath(const ContentSpecifier& specifier) const;
std::shared_ptr<FileUtil::IOFile> OpenContent(const ContentSpecifier& specifier, std::shared_ptr<FileUtil::IOFile> OpenContent(const ContentSpecifier& specifier,
u32 content_id) const; u32 content_id) const;
std::shared_ptr<TicketDB>& GetTicketDB() { std::shared_ptr<TicketDB>& GetTicketDB() {
return ticket_db; return ticket_db;
} }
const std::shared_ptr<TicketDB>& GetTicketDB() const { const std::shared_ptr<TicketDB>& GetTicketDB() const {
return ticket_db; return ticket_db;
} }
private: private:
bool Init(); bool Init();
// Impl of ImportContent without deleting mechanism. // Impl of ImportContent without deleting mechanism.
bool ImportContentImpl( bool ImportContentImpl(
const ContentSpecifier& specifier, const ContentSpecifier& specifier,
const Common::ProgressCallback& callback = [](u64, u64) {}); const Common::ProgressCallback& callback = [](u64, u64) {});
bool ImportTitle(const ContentSpecifier& specifier, const Common::ProgressCallback& callback); bool ImportTitle(const ContentSpecifier& specifier, const Common::ProgressCallback& callback);
bool ImportNandTitle(const ContentSpecifier& specifier, bool ImportNandTitle(const ContentSpecifier& specifier,
const Common::ProgressCallback& callback); const Common::ProgressCallback& callback);
bool ImportSavegame(u64 id, const Common::ProgressCallback& callback); bool ImportSavegame(u64 id, const Common::ProgressCallback& callback);
bool ImportNandSavegame(u64 id, const Common::ProgressCallback& callback); bool ImportNandSavegame(u64 id, const Common::ProgressCallback& callback);
bool ImportExtdata(u64 id, const Common::ProgressCallback& callback); bool ImportExtdata(u64 id, const Common::ProgressCallback& callback);
bool ImportNandExtdata(u64 id, const Common::ProgressCallback& callback); bool ImportNandExtdata(u64 id, const Common::ProgressCallback& callback);
bool ImportSystemArchive(u64 id, const Common::ProgressCallback& callback); bool ImportSystemArchive(u64 id, const Common::ProgressCallback& callback);
bool ImportSysdata(u64 id, const Common::ProgressCallback& callback); bool ImportSysdata(u64 id, const Common::ProgressCallback& callback);
void ListTitle(std::vector<ContentSpecifier>& out) const; void ListTitle(std::vector<ContentSpecifier>& out) const;
void ListNandTitle(std::vector<ContentSpecifier>& out) const; void ListNandTitle(std::vector<ContentSpecifier>& out) const;
void ListNandSavegame(std::vector<ContentSpecifier>& out) const; void ListNandSavegame(std::vector<ContentSpecifier>& out) const;
void ListExtdata(std::vector<ContentSpecifier>& out) const; void ListExtdata(std::vector<ContentSpecifier>& out) const;
void ListSystemArchive(std::vector<ContentSpecifier>& out) const; void ListSystemArchive(std::vector<ContentSpecifier>& out) const;
void ListSysdata(std::vector<ContentSpecifier>& out) const; void ListSysdata(std::vector<ContentSpecifier>& out) const;
void DeleteContent(const ContentSpecifier& specifier) const; void DeleteContent(const ContentSpecifier& specifier) const;
void DeleteTitle(u64 id) const; void DeleteTitle(u64 id) const;
void DeleteNandTitle(u64 id) const; void DeleteNandTitle(u64 id) const;
void DeleteSavegame(u64 id) const; void DeleteSavegame(u64 id) const;
void DeleteExtdata(u64 id) const; void DeleteExtdata(u64 id) const;
void DeleteSystemArchive(u64 id) const; void DeleteSystemArchive(u64 id) const;
void DeleteSysdata(u64 id) const; void DeleteSysdata(u64 id) const;
bool is_good{}; bool is_good{};
Config config; Config config;
std::unique_ptr<SDMCDecryptor> sdmc_decryptor; std::unique_ptr<SDMCDecryptor> sdmc_decryptor;
FileDecryptor file_decryptor; FileDecryptor file_decryptor;
// Used for CIA building // Used for CIA building
std::unique_ptr<CIABuilder> cia_builder; std::unique_ptr<CIABuilder> cia_builder;
std::shared_ptr<TicketDB> ticket_db; std::shared_ptr<TicketDB> ticket_db;
// The NCCH used to dump CXIs. // The NCCH used to dump CXIs.
std::unique_ptr<NCCHContainer> dump_cxi_ncch; std::unique_ptr<NCCHContainer> dump_cxi_ncch;
std::unique_ptr<TitleDB> sdmc_title_db{}; std::unique_ptr<TitleDB> sdmc_title_db{};
std::unique_ptr<TitleDB> nand_title_db{}; std::unique_ptr<TitleDB> nand_title_db{};
}; };
/** /**
* Look for and load preset config for a SD card mounted at mount_point. * Look for and load preset config for a SD card mounted at mount_point.
* @return a list of preset config available. can be empty * @return a list of preset config available. can be empty
*/ */
std::vector<Config> LoadPresetConfig(std::string mount_point); std::vector<Config> LoadPresetConfig(std::string mount_point);
} // namespace Core } // namespace Core
+88 -88
View File
@@ -1,88 +1,88 @@
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
if (POLICY CMP0071) if (POLICY CMP0071)
cmake_policy(SET CMP0071 NEW) cmake_policy(SET CMP0071 NEW)
endif() endif()
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*) file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*)
add_executable(threeSD add_executable(threeSD
helpers/dpi_aware_dialog.cpp helpers/dpi_aware_dialog.cpp
helpers/dpi_aware_dialog.h helpers/dpi_aware_dialog.h
helpers/frontend_common.cpp helpers/frontend_common.cpp
helpers/frontend_common.h helpers/frontend_common.h
helpers/multi_job.cpp helpers/multi_job.cpp
helpers/multi_job.h helpers/multi_job.h
helpers/rate_limited_progress_dialog.cpp helpers/rate_limited_progress_dialog.cpp
helpers/rate_limited_progress_dialog.h helpers/rate_limited_progress_dialog.h
helpers/simple_job.cpp helpers/simple_job.cpp
helpers/simple_job.h helpers/simple_job.h
cia_build_dialog.cpp cia_build_dialog.cpp
cia_build_dialog.h cia_build_dialog.h
cia_build_dialog.ui cia_build_dialog.ui
import_dialog.cpp import_dialog.cpp
import_dialog.h import_dialog.h
import_dialog.ui import_dialog.ui
main.cpp main.cpp
main.h main.h
main.ui main.ui
select_files_dialog.cpp select_files_dialog.cpp
select_files_dialog.h select_files_dialog.h
select_files_dialog.ui select_files_dialog.ui
title_info_dialog.cpp title_info_dialog.cpp
title_info_dialog.h title_info_dialog.h
title_info_dialog.ui title_info_dialog.ui
utilities.cpp utilities.cpp
utilities.h utilities.h
utilities.ui utilities.ui
${THEMES} ${THEMES}
) )
target_link_libraries(threeSD PRIVATE common core) target_link_libraries(threeSD PRIVATE common core)
target_link_libraries(threeSD PRIVATE Qt5::Widgets) target_link_libraries(threeSD PRIVATE Qt5::Widgets)
target_link_libraries(threeSD PRIVATE qdevicewatcher) target_link_libraries(threeSD PRIVATE qdevicewatcher)
target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (APPLE) if (APPLE)
# set(MACOSX_ICON "../../dist/citra.icns") # set(MACOSX_ICON "../../dist/citra.icns")
# set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) # set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# target_sources(threeSD PRIVATE ${MACOSX_ICON}) # target_sources(threeSD PRIVATE ${MACOSX_ICON})
set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE) set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
elseif(WIN32) elseif(WIN32)
# compile as a win32 gui application instead of a console application # compile as a win32 gui application instead of a console application
target_link_libraries(threeSD PRIVATE Qt5::WinMain) target_link_libraries(threeSD PRIVATE Qt5::WinMain)
if(MSVC) if(MSVC)
set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
elseif(MINGW) elseif(MINGW)
set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows") set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
endif() endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# In Ubuntu, the executable would be recognized as a shared library otherwise. # In Ubuntu, the executable would be recognized as a shared library otherwise.
set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie") set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie")
endif() endif()
target_compile_definitions(threeSD PRIVATE target_compile_definitions(threeSD PRIVATE
# Use QStringBuilder for string concatenation to reduce # Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created. # the overall number of temporary strings created.
-DQT_USE_QSTRINGBUILDER -DQT_USE_QSTRINGBUILDER
# Disable implicit type narrowing in signal/slot connect() calls. # Disable implicit type narrowing in signal/slot connect() calls.
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function. # Disable unsafe overloads of QProcess' start() function.
-DQT_NO_PROCESS_COMBINED_ARGUMENT_START -DQT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
-DQT_NO_URL_CAST_FROM_STRING -DQT_NO_URL_CAST_FROM_STRING
# Disable automatic conversions from 8-bit strings (char *) to unicode QStrings # Disable automatic conversions from 8-bit strings (char *) to unicode QStrings
-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_FROM_ASCII
) )
if (MSVC) if (MSVC)
include(CopyQt5Deps) include(CopyQt5Deps)
copy_Qt5_deps(threeSD) copy_Qt5_deps(threeSD)
endif() endif()
+94 -94
View File
@@ -1,94 +1,94 @@
// Copyright 2021 threeSD Project // Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QFileDialog> #include <QFileDialog>
#include <QFileInfo> #include <QFileInfo>
#include <QMessageBox> #include <QMessageBox>
#include "common/assert.h" #include "common/assert.h"
#include "frontend/cia_build_dialog.h" #include "frontend/cia_build_dialog.h"
#include "ui_cia_build_dialog.h" #include "ui_cia_build_dialog.h"
CIABuildDialog::CIABuildDialog(QWidget* parent, bool is_dir_, bool is_nand, bool enable_legit, CIABuildDialog::CIABuildDialog(QWidget* parent, bool is_dir_, bool is_nand, bool enable_legit,
const QString& default_path) const QString& default_path)
: DPIAwareDialog(parent, 510, 260), ui(std::make_unique<Ui::CIABuildDialog>()), : DPIAwareDialog(parent, 510, 260), ui(std::make_unique<Ui::CIABuildDialog>()),
is_dir(is_dir_) { is_dir(is_dir_) {
ui->setupUi(this); ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
if (is_dir) { if (is_dir) {
setWindowTitle(tr("Batch Build CIA")); setWindowTitle(tr("Batch Build CIA"));
} }
if (is_nand) { if (is_nand) {
ui->pirateLegitButton->setVisible(false); ui->pirateLegitButton->setVisible(false);
ui->pirateLegitLabel->setVisible(false); ui->pirateLegitLabel->setVisible(false);
auto message = tr("Encrypted CIA with legit TMD, encrypted contents and legit ticket.<br>"); auto message = tr("Encrypted CIA with legit TMD, encrypted contents and legit ticket.<br>");
if (is_dir) { if (is_dir) {
message.append(tr( message.append(tr(
"Legit tickets for these titles do not include console-identifying information.")); "Legit tickets for these titles do not include console-identifying information."));
} else { } else {
message.append(tr( message.append(tr(
"Legit ticket for this title does not include console-identifying information.")); "Legit ticket for this title does not include console-identifying information."));
} }
ui->legitLabel->setText(message); ui->legitLabel->setText(message);
} }
if (!enable_legit) { if (!enable_legit) {
const auto message = const auto message =
is_dir ? tr("This option is not available as some of the titles are not legit.") is_dir ? tr("This option is not available as some of the titles are not legit.")
: tr("This option is not available as the title is not legit."); : tr("This option is not available as the title is not legit.");
ui->pirateLegitButton->setEnabled(false); ui->pirateLegitButton->setEnabled(false);
ui->pirateLegitLabel->setText(message); ui->pirateLegitLabel->setText(message);
ui->legitButton->setEnabled(false); ui->legitButton->setEnabled(false);
ui->legitLabel->setText(message); ui->legitLabel->setText(message);
} }
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] {
if (ui->destination->text().isEmpty()) { if (ui->destination->text().isEmpty()) {
const QString message = is_dir ? tr("Please specify destination folder.") const QString message = is_dir ? tr("Please specify destination folder.")
: tr("Please specify destination file."); : tr("Please specify destination file.");
QMessageBox::warning(this, tr("threeSD"), message); QMessageBox::warning(this, tr("threeSD"), message);
return; return;
} }
accept(); accept();
}); });
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &CIABuildDialog::reject); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &CIABuildDialog::reject);
if (is_dir) { if (is_dir) {
ui->destination->setText(default_path); ui->destination->setText(default_path);
} }
connect(ui->destinationExplore, &QToolButton::clicked, [this, default_path] { connect(ui->destinationExplore, &QToolButton::clicked, [this, default_path] {
QString path; QString path;
if (is_dir) { if (is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Batch Build CIA"), path = QFileDialog::getExistingDirectory(this, tr("Batch Build CIA"),
ui->destination->text()); ui->destination->text());
} else { } else {
const auto cur = ui->destination->text().isEmpty() const auto cur = ui->destination->text().isEmpty()
? default_path ? default_path
: QFileInfo(ui->destination->text()).path(); : QFileInfo(ui->destination->text()).path();
path = QFileDialog::getSaveFileName(this, tr("Build CIA"), cur, path = QFileDialog::getSaveFileName(this, tr("Build CIA"), cur,
tr("CTR Importable Archive (*.cia)")); tr("CTR Importable Archive (*.cia)"));
} }
if (!path.isEmpty()) { if (!path.isEmpty()) {
ui->destination->setText(path); ui->destination->setText(path);
} }
}); });
} }
CIABuildDialog::~CIABuildDialog() = default; CIABuildDialog::~CIABuildDialog() = default;
std::pair<QString, Core::CIABuildType> CIABuildDialog::GetResults() const { std::pair<QString, Core::CIABuildType> CIABuildDialog::GetResults() const {
Core::CIABuildType type; Core::CIABuildType type;
if (ui->standardButton->isChecked()) { if (ui->standardButton->isChecked()) {
type = Core::CIABuildType::Standard; type = Core::CIABuildType::Standard;
} else if (ui->pirateLegitButton->isChecked()) { } else if (ui->pirateLegitButton->isChecked()) {
type = Core::CIABuildType::PirateLegit; type = Core::CIABuildType::PirateLegit;
} else if (ui->legitButton->isChecked()) { } else if (ui->legitButton->isChecked()) {
type = Core::CIABuildType::Legit; type = Core::CIABuildType::Legit;
} else { } else {
UNREACHABLE(); UNREACHABLE();
} }
return {ui->destination->text(), type}; return {ui->destination->text(), type};
} }
+29 -29
View File
@@ -1,29 +1,29 @@
// Copyright 2021 threeSD Project // Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <memory> #include <memory>
#include <utility> #include <utility>
#include "core/file_sys/cia_common.h" #include "core/file_sys/cia_common.h"
#include "frontend/helpers/dpi_aware_dialog.h" #include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui { namespace Ui {
class CIABuildDialog; class CIABuildDialog;
} }
class CIABuildDialog : public DPIAwareDialog { class CIABuildDialog : public DPIAwareDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit CIABuildDialog(QWidget* parent, bool is_dir, bool is_nand, bool enable_legit, explicit CIABuildDialog(QWidget* parent, bool is_dir, bool is_nand, bool enable_legit,
const QString& default_path); const QString& default_path);
~CIABuildDialog(); ~CIABuildDialog();
std::pair<QString, Core::CIABuildType> GetResults() const; std::pair<QString, Core::CIABuildType> GetResults() const;
private: private:
std::unique_ptr<Ui::CIABuildDialog> ui; std::unique_ptr<Ui::CIABuildDialog> ui;
bool is_dir; bool is_dir;
}; };
+108 -108
View File
@@ -1,108 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>CIABuildDialog</class> <class>CIABuildDialog</class>
<widget class="QDialog" name="CIABuildDialog"> <widget class="QDialog" name="CIABuildDialog">
<property name="windowTitle"> <property name="windowTitle">
<string>Build CIA</string> <string>Build CIA</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Destination:</string> <string>Destination:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="destination"/> <widget class="QLineEdit" name="destination"/>
</item> </item>
<item> <item>
<widget class="QToolButton" name="destinationExplore"> <widget class="QToolButton" name="destinationExplore">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>Build Type</string> <string>Build Type</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<widget class="QRadioButton" name="standardButton"> <widget class="QRadioButton" name="standardButton">
<property name="text"> <property name="text">
<string>Standard</string> <string>Standard</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Recommended for general use.&lt;br&gt;Decrypted CIA with decrypted contents and standard ticket.</string> <string>Recommended for general use.&lt;br&gt;Decrypted CIA with decrypted contents and standard ticket.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="pirateLegitButton"> <widget class="QRadioButton" name="pirateLegitButton">
<property name="text"> <property name="text">
<string>Legit with standard ticket ("pirate legit")</string> <string>Legit with standard ticket ("pirate legit")</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="pirateLegitLabel"> <widget class="QLabel" name="pirateLegitLabel">
<property name="text"> <property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and standard ticket.</string> <string>Encrypted CIA with legit TMD, encrypted contents and standard ticket.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="legitButton"> <widget class="QRadioButton" name="legitButton">
<property name="text"> <property name="text">
<string>Legit</string> <string>Legit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="legitLabel"> <widget class="QLabel" name="legitLabel">
<property name="text"> <property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and legit ticket.&lt;br&gt;WARNING: Legit ticket may include console identifying information!</string> <string>Encrypted CIA with legit TMD, encrypted contents and legit ticket.&lt;br&gt;WARNING: Legit ticket may include console identifying information!</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set> <set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>
+69 -69
View File
@@ -1,69 +1,69 @@
// Copyright 2021 threeSD Project // Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QScreen> #include <QScreen>
#include <QWindow> #include <QWindow>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "frontend/helpers/dpi_aware_dialog.h" #include "frontend/helpers/dpi_aware_dialog.h"
DPIAwareDialog::DPIAwareDialog(QWidget* parent, int width, int height) DPIAwareDialog::DPIAwareDialog(QWidget* parent, int width, int height)
: QDialog(parent), original_width(width), original_height(height) {} : QDialog(parent), original_width(width), original_height(height) {}
DPIAwareDialog::~DPIAwareDialog() = default; DPIAwareDialog::~DPIAwareDialog() = default;
void DPIAwareDialog::showEvent(QShowEvent* event) { void DPIAwareDialog::showEvent(QShowEvent* event) {
QDialog::showEvent(event); QDialog::showEvent(event);
if (window_handle) { if (window_handle) {
return; return;
} }
// Initialize window_handle and connections // Initialize window_handle and connections
window_handle = windowHandle(); window_handle = windowHandle();
if (!window_handle) { if (!window_handle) {
return; return;
} }
#ifdef __APPLE__ #ifdef __APPLE__
// Note: macOS implements system level virtualization, so there's no need to connect here. // Note: macOS implements system level virtualization, so there's no need to connect here.
// but we still need to call SetContentSizes() at least once to set up the UI // but we still need to call SetContentSizes() at least once to set up the UI
// macOS style has more padding. Make the dialog larger for compensation. // macOS style has more padding. Make the dialog larger for compensation.
resize(original_width * 1.25, original_height * 1.25); resize(original_width * 1.25, original_height * 1.25);
SetContentSizes(); SetContentSizes();
#else #else
resized = false; resized = false;
connect(window_handle, &QWindow::screenChanged, this, &DPIAwareDialog::OnScreenChanged); connect(window_handle, &QWindow::screenChanged, this, &DPIAwareDialog::OnScreenChanged);
OnScreenChanged(); OnScreenChanged();
#endif #endif
} }
#ifndef __APPLE__ #ifndef __APPLE__
void DPIAwareDialog::resizeEvent(QResizeEvent* event) { void DPIAwareDialog::resizeEvent(QResizeEvent* event) {
QDialog::resizeEvent(event); QDialog::resizeEvent(event);
resized = true; resized = true;
} }
void DPIAwareDialog::OnScreenChanged() { void DPIAwareDialog::OnScreenChanged() {
// Resize according to DPI // Resize according to DPI
const double scaleX = window_handle->screen()->logicalDotsPerInchX() / 96.0; const double scaleX = window_handle->screen()->logicalDotsPerInchX() / 96.0;
const double scaleY = window_handle->screen()->logicalDotsPerInchY() / 96.0; const double scaleY = window_handle->screen()->logicalDotsPerInchY() / 96.0;
if (resized) { if (resized) {
const int new_width = static_cast<int>(scaleX * width() / previous_scaleX); const int new_width = static_cast<int>(scaleX * width() / previous_scaleX);
const int new_height = static_cast<int>(scaleY * height() / previous_scaleY); const int new_height = static_cast<int>(scaleY * height() / previous_scaleY);
setMinimumSize(0, 0); // Enforce this resize setMinimumSize(0, 0); // Enforce this resize
resize(new_width, new_height); resize(new_width, new_height);
} else { } else {
const int new_width = static_cast<int>(original_width * scaleX); const int new_width = static_cast<int>(original_width * scaleX);
const int new_height = static_cast<int>(original_height * scaleY); const int new_height = static_cast<int>(original_height * scaleY);
setMinimumSize(new_width, new_height); setMinimumSize(new_width, new_height);
adjustSize(); adjustSize();
resized = false; // This resize isn't user-initiated resized = false; // This resize isn't user-initiated
} }
SetContentSizes(previous_width, previous_height); SetContentSizes(previous_width, previous_height);
previous_scaleX = scaleX; previous_scaleX = scaleX;
previous_scaleY = scaleY; previous_scaleY = scaleY;
previous_width = width(); previous_width = width();
previous_height = height(); previous_height = height();
} }
#endif #endif
+39 -39
View File
@@ -1,39 +1,39 @@
// Copyright 2021 threeSD Project // Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <QDialog> #include <QDialog>
class DPIAwareDialog : public QDialog { class DPIAwareDialog : public QDialog {
public: public:
explicit DPIAwareDialog(QWidget* parent, int width, int height); explicit DPIAwareDialog(QWidget* parent, int width, int height);
~DPIAwareDialog() override; ~DPIAwareDialog() override;
protected: protected:
void showEvent(QShowEvent* event) override; void showEvent(QShowEvent* event) override;
// Called with two zeroes to set up content sizes that are relative to dialog size. Also called // Called with two zeroes to set up content sizes that are relative to dialog size. Also called
// when screen is changed, to update those sizes. // when screen is changed, to update those sizes.
virtual void SetContentSizes(int previous_width = 0, int previous_height = 0){}; virtual void SetContentSizes(int previous_width = 0, int previous_height = 0){};
private: private:
QWindow* window_handle{}; QWindow* window_handle{};
const int original_width{}; const int original_width{};
const int original_height{}; const int original_height{};
#ifndef __APPLE__ #ifndef __APPLE__
protected: protected:
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
private: private:
void OnScreenChanged(); void OnScreenChanged();
bool resized = false; // whether this dialog has been manually resized bool resized = false; // whether this dialog has been manually resized
double previous_scaleX{}; double previous_scaleX{};
double previous_scaleY{}; double previous_scaleY{};
int previous_width{}; int previous_width{};
int previous_height{}; int previous_height{};
#endif #endif
}; };
+21 -21
View File
@@ -1,21 +1,21 @@
// Copyright 2021 threeSD Project // Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <QObject> #include <QObject>
#include "frontend/helpers/frontend_common.h" #include "frontend/helpers/frontend_common.h"
QString ReadableByteSize(qulonglong size) { QString ReadableByteSize(qulonglong size) {
static const std::array<const char*, 6> units = {QT_TR_NOOP("B"), QT_TR_NOOP("KiB"), static const std::array<const char*, 6> units = {QT_TR_NOOP("B"), QT_TR_NOOP("KiB"),
QT_TR_NOOP("MiB"), QT_TR_NOOP("GiB"), QT_TR_NOOP("MiB"), QT_TR_NOOP("GiB"),
QT_TR_NOOP("TiB"), QT_TR_NOOP("PiB")}; QT_TR_NOOP("TiB"), QT_TR_NOOP("PiB")};
if (size == 0) if (size == 0)
return QStringLiteral("0"); return QStringLiteral("0");
int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)), int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)),
static_cast<int>(units.size() - 1)); static_cast<int>(units.size() - 1));
return QStringLiteral("%L1 %2") return QStringLiteral("%L1 %2")
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1) .arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
.arg(QObject::tr(units[digit_groups], "FrontendCommon")); .arg(QObject::tr(units[digit_groups], "FrontendCommon"));
} }
+9 -9
View File
@@ -1,9 +1,9 @@
// Copyright 2021 threeSD Project // Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <QString> #include <QString>
QString ReadableByteSize(qulonglong size); QString ReadableByteSize(qulonglong size);
+67 -67
View File
@@ -1,67 +1,67 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <chrono> #include <chrono>
#include "frontend/helpers/multi_job.h" #include "frontend/helpers/multi_job.h"
MultiJob::MultiJob(QObject* parent, Core::SDMCImporter& importer_, MultiJob::MultiJob(QObject* parent, Core::SDMCImporter& importer_,
std::vector<Core::ContentSpecifier> contents_, ExecuteFunc execute_func_, std::vector<Core::ContentSpecifier> contents_, ExecuteFunc execute_func_,
AbortFunc abort_func_) AbortFunc abort_func_)
: QThread(parent), importer(importer_), contents(std::move(contents_)), : QThread(parent), importer(importer_), contents(std::move(contents_)),
execute_func(std::move(execute_func_)), abort_func(abort_func_) {} execute_func(std::move(execute_func_)), abort_func(abort_func_) {}
MultiJob::~MultiJob() = default; MultiJob::~MultiJob() = default;
void MultiJob::run() { void MultiJob::run() {
u64 total_size = 0; u64 total_size = 0;
for (const auto& content : contents) { for (const auto& content : contents) {
total_size += content.maximum_size; total_size += content.maximum_size;
} }
std::size_t count = 0; std::size_t count = 0;
int eta = -1; int eta = -1;
const auto initial_time = std::chrono::steady_clock::now(); const auto initial_time = std::chrono::steady_clock::now();
const auto UpdateETA = [total_size, &eta, initial_time](u64 size_imported) { const auto UpdateETA = [total_size, &eta, initial_time](u64 size_imported) {
if (size_imported < 10 * 1024 * 1024) { // 10M Threshold if (size_imported < 10 * 1024 * 1024) { // 10M Threshold
return; return;
} }
using namespace std::chrono; using namespace std::chrono;
const u64 time_elapsed = const u64 time_elapsed =
duration_cast<milliseconds>(steady_clock::now() - initial_time).count(); duration_cast<milliseconds>(steady_clock::now() - initial_time).count();
eta = eta =
static_cast<int>(time_elapsed * (total_size - size_imported) / (size_imported) / 1000); static_cast<int>(time_elapsed * (total_size - size_imported) / (size_imported) / 1000);
}; };
const auto Callback = [this, &eta, &UpdateETA](u64 current_imported_size, const auto Callback = [this, &eta, &UpdateETA](u64 current_imported_size,
u64 total_imported_size, u64 /*total_size*/) { u64 total_imported_size, u64 /*total_size*/) {
UpdateETA(total_imported_size); UpdateETA(total_imported_size);
emit ProgressUpdated(current_imported_size, total_imported_size, eta); emit ProgressUpdated(current_imported_size, total_imported_size, eta);
}; };
Common::ProgressCallbackWrapper wrapper{total_size}; Common::ProgressCallbackWrapper wrapper{total_size};
for (const auto& content : contents) { for (const auto& content : contents) {
emit NextContent(count + 1, wrapper.current_done_size + wrapper.current_pending_size, emit NextContent(count + 1, wrapper.current_done_size + wrapper.current_pending_size,
content, eta); content, eta);
if (!execute_func(importer, content, wrapper.Wrap(Callback))) { if (!execute_func(importer, content, wrapper.Wrap(Callback))) {
if (!cancelled) { if (!cancelled) {
failed_contents.emplace_back(content); failed_contents.emplace_back(content);
} }
} }
count++; count++;
if (cancelled) { if (cancelled) {
break; break;
} }
} }
emit Completed(); emit Completed();
} }
void MultiJob::Cancel() { void MultiJob::Cancel() {
cancelled.store(true); cancelled.store(true);
abort_func(importer); abort_func(importer);
} }
std::vector<Core::ContentSpecifier> MultiJob::GetFailedContents() const { std::vector<Core::ContentSpecifier> MultiJob::GetFailedContents() const {
return failed_contents; return failed_contents;
} }
+56 -56
View File
@@ -1,56 +1,56 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <functional> #include <functional>
#include <QThread> #include <QThread>
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "core/importer.h" #include "core/importer.h"
class MultiJob : public QThread { class MultiJob : public QThread {
Q_OBJECT Q_OBJECT
public: public:
using ExecuteFunc = std::function<bool(Core::SDMCImporter&, const Core::ContentSpecifier&, using ExecuteFunc = std::function<bool(Core::SDMCImporter&, const Core::ContentSpecifier&,
const Common::ProgressCallback&)>; const Common::ProgressCallback&)>;
using AbortFunc = std::function<void(Core::SDMCImporter&)>; using AbortFunc = std::function<void(Core::SDMCImporter&)>;
explicit MultiJob(QObject* parent, Core::SDMCImporter& importer, explicit MultiJob(QObject* parent, Core::SDMCImporter& importer,
std::vector<Core::ContentSpecifier> contents, ExecuteFunc execute_func, std::vector<Core::ContentSpecifier> contents, ExecuteFunc execute_func,
AbortFunc abort_func); AbortFunc abort_func);
~MultiJob() override; ~MultiJob() override;
void run() override; void run() override;
void Cancel(); void Cancel();
std::vector<Core::ContentSpecifier> GetFailedContents() const; std::vector<Core::ContentSpecifier> GetFailedContents() const;
signals: signals:
/** /**
* Called when progress is updated on the current content. * Called when progress is updated on the current content.
* @param current_imported_size Imported size of the current content. * @param current_imported_size Imported size of the current content.
* @param total_imported_size Total imported size taking all previous contents into * @param total_imported_size Total imported size taking all previous contents into
* consideration. * consideration.
* @param eta ETA in seconds, 0 when not determined. * @param eta ETA in seconds, 0 when not determined.
*/ */
void ProgressUpdated(u64 current_imported_size, u64 total_imported_size, int eta); void ProgressUpdated(u64 current_imported_size, u64 total_imported_size, int eta);
/// Dumping of a content has been finished, go on to the next. Called at start as well. /// Dumping of a content has been finished, go on to the next. Called at start as well.
void NextContent(std::size_t count, u64 total_imported_size, void NextContent(std::size_t count, u64 total_imported_size,
const Core::ContentSpecifier& next_content, int eta); const Core::ContentSpecifier& next_content, int eta);
void Completed(); void Completed();
private: private:
std::atomic_bool cancelled{false}; std::atomic_bool cancelled{false};
Core::SDMCImporter& importer; Core::SDMCImporter& importer;
std::vector<Core::ContentSpecifier> contents; std::vector<Core::ContentSpecifier> contents;
std::vector<Core::ContentSpecifier> failed_contents; std::vector<Core::ContentSpecifier> failed_contents;
ExecuteFunc execute_func; ExecuteFunc execute_func;
AbortFunc abort_func; AbortFunc abort_func;
}; };
Q_DECLARE_METATYPE(Core::ContentSpecifier) Q_DECLARE_METATYPE(Core::ContentSpecifier)
@@ -1,34 +1,34 @@
// Copyright 2021 Pengfei Zhu // Copyright 2021 Pengfei Zhu
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "frontend/helpers/rate_limited_progress_dialog.h" #include "frontend/helpers/rate_limited_progress_dialog.h"
RateLimitedProgressDialog::RateLimitedProgressDialog(const QString& label_text, RateLimitedProgressDialog::RateLimitedProgressDialog(const QString& label_text,
const QString& cancel_button_text, int minimum, const QString& cancel_button_text, int minimum,
int maximum, QWidget* parent) int maximum, QWidget* parent)
: QProgressDialog(label_text, cancel_button_text, minimum, maximum, parent) { : QProgressDialog(label_text, cancel_button_text, minimum, maximum, parent) {
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
setWindowModality(Qt::WindowModal); setWindowModality(Qt::WindowModal);
setMinimumDuration(0); setMinimumDuration(0);
setValue(0); setValue(0);
} }
RateLimitedProgressDialog::~RateLimitedProgressDialog() = default; RateLimitedProgressDialog::~RateLimitedProgressDialog() = default;
void RateLimitedProgressDialog::Update(int progress, const QString& label_text) { void RateLimitedProgressDialog::Update(int progress, const QString& label_text) {
if (progress == maximum()) { // always set the maximum if (progress == maximum()) { // always set the maximum
setValue(progress); setValue(progress);
return; return;
} }
const auto current_time = std::chrono::steady_clock::now(); const auto current_time = std::chrono::steady_clock::now();
if (current_time - last_update_time < MinimumInterval) { if (current_time - last_update_time < MinimumInterval) {
return; return;
} }
setValue(progress); setValue(progress);
setLabelText(label_text); setLabelText(label_text);
last_update_time = current_time; last_update_time = current_time;
} }
@@ -1,21 +1,21 @@
// Copyright 2021 Pengfei Zhu // Copyright 2021 Pengfei Zhu
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <QProgressDialog> #include <QProgressDialog>
class RateLimitedProgressDialog : public QProgressDialog { class RateLimitedProgressDialog : public QProgressDialog {
public: public:
explicit RateLimitedProgressDialog(const QString& label_text, const QString& cancel_button_text, explicit RateLimitedProgressDialog(const QString& label_text, const QString& cancel_button_text,
int minimum, int maximum, QWidget* parent = nullptr); int minimum, int maximum, QWidget* parent = nullptr);
~RateLimitedProgressDialog() override; ~RateLimitedProgressDialog() override;
void Update(int progress, const QString& label_text); void Update(int progress, const QString& label_text);
private: private:
std::chrono::steady_clock::time_point last_update_time = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point last_update_time = std::chrono::steady_clock::now();
static constexpr auto MinimumInterval = std::chrono::milliseconds{100}; static constexpr auto MinimumInterval = std::chrono::milliseconds{100};
}; };
+54 -54
View File
@@ -1,54 +1,54 @@
// Copyright 2020 Pengfei Zhu // Copyright 2020 Pengfei Zhu
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QMessageBox> #include <QMessageBox>
#include "frontend/helpers/frontend_common.h" #include "frontend/helpers/frontend_common.h"
#include "frontend/helpers/rate_limited_progress_dialog.h" #include "frontend/helpers/rate_limited_progress_dialog.h"
#include "frontend/helpers/simple_job.h" #include "frontend/helpers/simple_job.h"
SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_) SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_)
: QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {} : QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {}
SimpleJob::~SimpleJob() = default; SimpleJob::~SimpleJob() = default;
void SimpleJob::run() { void SimpleJob::run() {
const bool ret = const bool ret =
execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); }); execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); });
if (ret || canceled) { if (ret || canceled) {
emit Completed(canceled); emit Completed(canceled);
} else { } else {
emit ErrorOccured(); emit ErrorOccured();
} }
} }
void SimpleJob::Cancel() { void SimpleJob::Cancel() {
canceled = true; canceled = true;
abort(); abort();
} }
void SimpleJob::StartWithProgressDialog(QWidget* widget) { void SimpleJob::StartWithProgressDialog(QWidget* widget) {
auto* dialog = new RateLimitedProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, widget); auto* dialog = new RateLimitedProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, widget);
connect(this, &SimpleJob::ProgressUpdated, this, [dialog](u64 current, u64 total) { connect(this, &SimpleJob::ProgressUpdated, this, [dialog](u64 current, u64 total) {
if (dialog->wasCanceled()) { if (dialog->wasCanceled()) {
return; return;
} }
// Try to map total to int range // Try to map total to int range
// This is equal to ceil(total / INT_MAX) // This is equal to ceil(total / INT_MAX)
const u64 multiplier = const u64 multiplier =
(total + std::numeric_limits<int>::max() - 1) / std::numeric_limits<int>::max(); (total + std::numeric_limits<int>::max() - 1) / std::numeric_limits<int>::max();
dialog->setMaximum(static_cast<int>(total / multiplier)); dialog->setMaximum(static_cast<int>(total / multiplier));
dialog->Update(static_cast<int>(current / multiplier), dialog->Update(static_cast<int>(current / multiplier),
tr("%1 / %2").arg(ReadableByteSize(current), ReadableByteSize(total))); tr("%1 / %2").arg(ReadableByteSize(current), ReadableByteSize(total)));
}); });
connect(this, &SimpleJob::ErrorOccured, this, [widget, dialog] { connect(this, &SimpleJob::ErrorOccured, this, [widget, dialog] {
QMessageBox::critical(widget, tr("threeSD"), QMessageBox::critical(widget, tr("threeSD"),
tr("Operation failed. Please refer to the log.")); tr("Operation failed. Please refer to the log."));
dialog->hide(); dialog->hide();
}); });
connect(this, &SimpleJob::Completed, dialog, &QProgressDialog::hide); connect(this, &SimpleJob::Completed, dialog, &QProgressDialog::hide);
connect(dialog, &QProgressDialog::canceled, this, &SimpleJob::Cancel); connect(dialog, &QProgressDialog::canceled, this, &SimpleJob::Cancel);
start(); start();
} }
+39 -39
View File
@@ -1,39 +1,39 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <functional> #include <functional>
#include <QThread> #include <QThread>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
/** /**
* Lightweight wrapper around QThread, for easy use with progressive jobs. * Lightweight wrapper around QThread, for easy use with progressive jobs.
*/ */
class SimpleJob : public QThread { class SimpleJob : public QThread {
Q_OBJECT Q_OBJECT
public: public:
using ExecuteFunc = std::function<bool(const Common::ProgressCallback&)>; using ExecuteFunc = std::function<bool(const Common::ProgressCallback&)>;
using AbortFunc = std::function<void()>; using AbortFunc = std::function<void()>;
explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort); explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort);
~SimpleJob() override; ~SimpleJob() override;
void run() override; void run() override;
void Cancel(); void Cancel();
void StartWithProgressDialog(QWidget* widget); void StartWithProgressDialog(QWidget* widget);
signals: signals:
void ProgressUpdated(u64 current, u64 total); void ProgressUpdated(u64 current, u64 total);
void Completed(bool canceled); void Completed(bool canceled);
void ErrorOccured(); void ErrorOccured();
private: private:
ExecuteFunc execute; ExecuteFunc execute;
AbortFunc abort; AbortFunc abort;
bool canceled{}; bool canceled{};
}; };
File diff suppressed because it is too large Load Diff
+87 -87
View File
@@ -1,87 +1,87 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <QPixmap> #include <QPixmap>
#include "core/file_sys/ncch_container.h" #include "core/file_sys/ncch_container.h"
#include "core/importer.h" #include "core/importer.h"
#include "helpers/dpi_aware_dialog.h" #include "helpers/dpi_aware_dialog.h"
class AdvancedMenu; class AdvancedMenu;
class MultiJob; class MultiJob;
class SimpleJob; class SimpleJob;
class QTreeWidgetItem; class QTreeWidgetItem;
namespace Ui { namespace Ui {
class ImportDialog; class ImportDialog;
} }
class ImportDialog final : public DPIAwareDialog { class ImportDialog final : public DPIAwareDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ImportDialog(QWidget* parent, const Core::Config& config); explicit ImportDialog(QWidget* parent, const Core::Config& config);
~ImportDialog() override; ~ImportDialog() override;
private: private:
void SetContentSizes(int previous_width, int previous_height) override; void SetContentSizes(int previous_width, int previous_height) override;
void RelistContent(); void RelistContent();
void RepopulateContent(); void RepopulateContent();
void UpdateSizeDisplay(); void UpdateSizeDisplay();
std::vector<Core::ContentSpecifier> GetSelectedContentList(); std::vector<Core::ContentSpecifier> GetSelectedContentList();
void InsertTopLevelItem(QString text, QPixmap icon = {}); void InsertTopLevelItem(QString text, QPixmap icon = {});
void InsertTopLevelItem(QString text, QPixmap icon, u64 total_size, QString exists); void InsertTopLevelItem(QString text, QPixmap icon, u64 total_size, QString exists);
// When replace_name and replace_icon are present they are used instead of those in `content`. // When replace_name and replace_icon are present they are used instead of those in `content`.
void InsertSecondLevelItem(std::size_t row, const Core::ContentSpecifier& content, void InsertSecondLevelItem(std::size_t row, const Core::ContentSpecifier& content,
std::size_t id, QString replace_name = {}, std::size_t id, QString replace_name = {},
QPixmap replace_icon = {}); QPixmap replace_icon = {});
Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const; Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const;
void OnContextMenu(const QPoint& point); void OnContextMenu(const QPoint& point);
void ShowAdvancedMenu(); void ShowAdvancedMenu();
void OnItemChanged(QTreeWidgetItem* item, int column); void OnItemChanged(QTreeWidgetItem* item, int column);
void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size); void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size);
void StartImporting(); void StartImporting();
void StartDumpingCXISingle(const Core::ContentSpecifier& content); void StartDumpingCXISingle(const Core::ContentSpecifier& content);
QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle
void StartBatchDumpingCXI(); void StartBatchDumpingCXI();
QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI
void StartBuildingCIASingle(const Core::ContentSpecifier& content); void StartBuildingCIASingle(const Core::ContentSpecifier& content);
QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle
void StartBatchBuildingCIA(); void StartBatchBuildingCIA();
QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA
std::unique_ptr<Ui::ImportDialog> ui; std::unique_ptr<Ui::ImportDialog> ui;
std::unique_ptr<Core::SDMCImporter> importer; std::unique_ptr<Core::SDMCImporter> importer;
const Core::Config config; const Core::Config config;
std::vector<Core::ContentSpecifier> contents; std::vector<Core::ContentSpecifier> contents;
u64 total_selected_size = 0; u64 total_selected_size = 0;
// HACK: Block advanced menu trigger once. // HACK: Block advanced menu trigger once.
bool block_advanced_menu = false; bool block_advanced_menu = false;
friend class AdvancedMenu; friend class AdvancedMenu;
// Whether the System Archive / System Data warning has been shown // Whether the System Archive / System Data warning has been shown
bool system_warning_shown = false; bool system_warning_shown = false;
// Whether the Applets warning has been shown // Whether the Applets warning has been shown
bool applet_warning_shown = false; bool applet_warning_shown = false;
// TODO: Why this won't work as locals? // TODO: Why this won't work as locals?
Core::ContentSpecifier current_content = {}; Core::ContentSpecifier current_content = {};
std::size_t current_count = 0; std::size_t current_count = 0;
}; };
+99 -99
View File
@@ -1,99 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>ImportDialog</class> <class>ImportDialog</class>
<widget class="QDialog" name="ImportDialog"> <widget class="QDialog" name="ImportDialog">
<property name="windowTitle"> <property name="windowTitle">
<string>Select Contents</string> <string>Select Contents</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QPushButton" name="advanced_button"> <widget class="QPushButton" name="advanced_button">
<property name="text"> <property name="text">
<string>Advanced...</string> <string>Advanced...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="title_view_button"> <widget class="QRadioButton" name="title_view_button">
<property name="text"> <property name="text">
<string>Title View</string> <string>Title View</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="group_view_button"> <widget class="QRadioButton" name="group_view_button">
<property name="text"> <property name="text">
<string>Group View</string> <string>Group View</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QTreeWidget" name="main"> <widget class="QTreeWidget" name="main">
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum> <enum>Qt::CustomContextMenu</enum>
</property> </property>
<column> <column>
<property name="text"> <property name="text">
<string>Name</string> <string>Name</string>
</property> </property>
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>Size</string> <string>Size</string>
</property> </property>
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>Exists</string> <string>Exists</string>
</property> </property>
</column> </column>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="availableSpace"> <widget class="QLabel" name="availableSpace">
<property name="text"> <property name="text">
<string>Available Space:</string> <string>Available Space:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QLabel" name="totalSize"> <widget class="QLabel" name="totalSize">
<property name="text"> <property name="text">
<string>Total Size:</string> <string>Total Size:</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Reset</set> <set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Reset</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>
+258 -258
View File
@@ -1,258 +1,258 @@
// Copyright 2014 Citra Emulator Project / 2019 threeSD Project // Copyright 2014 Citra Emulator Project / 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <regex> #include <regex>
#include <string> #include <string>
#include <QApplication> #include <QApplication>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QStorageInfo> #include <QStorageInfo>
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
#include <qdevicewatcher.h> #include <qdevicewatcher.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "frontend/import_dialog.h" #include "frontend/import_dialog.h"
#include "frontend/main.h" #include "frontend/main.h"
#include "frontend/utilities.h" #include "frontend/utilities.h"
#include "ui_main.h" #include "ui_main.h"
#ifdef __APPLE__ #ifdef __APPLE__
#include <unistd.h> #include <unistd.h>
#include "common/common_paths.h" #include "common/common_paths.h"
#endif #endif
#ifdef QT_STATICPLUGIN #ifdef QT_STATICPLUGIN
#include <QtPlugin> #include <QtPlugin>
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin) Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin)
#endif #endif
#endif #endif
bool IsConfigGood(const Core::Config& config) { bool IsConfigGood(const Core::Config& config) {
return !config.sdmc_path.empty() && !config.user_path.empty() && return !config.sdmc_path.empty() && !config.user_path.empty() &&
!config.movable_sed_path.empty() && !config.bootrom_path.empty(); !config.movable_sed_path.empty() && !config.bootrom_path.empty();
} }
MainDialog::MainDialog(QWidget* parent) MainDialog::MainDialog(QWidget* parent)
: DPIAwareDialog(parent, 640, 256), ui(std::make_unique<Ui::MainDialog>()) { : DPIAwareDialog(parent, 640, 256), ui(std::make_unique<Ui::MainDialog>()) {
ui->setupUi(this); ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(false);
ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)->setText(tr("Refresh")); ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)->setText(tr("Refresh"));
LoadPresetConfig(); LoadPresetConfig();
connect(ui->utilitiesButton, &QPushButton::clicked, [this] { connect(ui->utilitiesButton, &QPushButton::clicked, [this] {
UtilitiesDialog dialog(this); UtilitiesDialog dialog(this);
dialog.exec(); dialog.exec();
}); });
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) {
if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)) { if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)) {
LoadPresetConfig(); LoadPresetConfig();
} else if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) { } else if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) {
LaunchImportDialog(); LaunchImportDialog();
} }
}); });
QString destination_text{}; QString destination_text{};
const auto destination = FileUtil::GetUserPathType(); const auto destination = FileUtil::GetUserPathType();
if (destination == FileUtil::UserPathType::Normal) { if (destination == FileUtil::UserPathType::Normal) {
#ifdef __linux__ #ifdef __linux__
destination_text = tr("Non-Flatpak Citra Install"); destination_text = tr("Non-Flatpak Citra Install");
#else #else
destination_text = tr("User-wide Citra Install"); destination_text = tr("User-wide Citra Install");
#endif #endif
} else if (destination == FileUtil::UserPathType::Portable) { } else if (destination == FileUtil::UserPathType::Portable) {
destination_text = tr("Portable Citra Install"); destination_text = tr("Portable Citra Install");
} else if (destination == FileUtil::UserPathType::Flatpak) { } else if (destination == FileUtil::UserPathType::Flatpak) {
destination_text = tr("Flatpak Citra Install"); destination_text = tr("Flatpak Citra Install");
} else { } else {
UNREACHABLE(); UNREACHABLE();
} }
ui->importDestination->setText(tr("Import Destination: %1").arg(destination_text)); ui->importDestination->setText(tr("Import Destination: %1").arg(destination_text));
connect(ui->main, &QTreeWidget::itemSelectionChanged, [this] { connect(ui->main, &QTreeWidget::itemSelectionChanged, [this] {
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok) ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)
->setEnabled(!ui->main->selectedItems().empty()); ->setEnabled(!ui->main->selectedItems().empty());
}); });
connect(ui->main, &QTreeWidget::itemDoubleClicked, [this] { connect(ui->main, &QTreeWidget::itemDoubleClicked, [this] {
if (!ui->main->selectedItems().empty()) if (!ui->main->selectedItems().empty())
LaunchImportDialog(); LaunchImportDialog();
}); });
ui->main->setIndentation(4); ui->main->setIndentation(4);
// Set up device watcher // Set up device watcher
auto* device_watcher = new QDeviceWatcher(this); auto* device_watcher = new QDeviceWatcher(this);
device_watcher->start(); device_watcher->start();
connect(device_watcher, &QDeviceWatcher::deviceAdded, this, &MainDialog::LoadPresetConfig); connect(device_watcher, &QDeviceWatcher::deviceAdded, this, &MainDialog::LoadPresetConfig);
connect(device_watcher, &QDeviceWatcher::deviceChanged, this, &MainDialog::LoadPresetConfig); connect(device_watcher, &QDeviceWatcher::deviceChanged, this, &MainDialog::LoadPresetConfig);
connect(device_watcher, &QDeviceWatcher::deviceRemoved, this, &MainDialog::LoadPresetConfig); connect(device_watcher, &QDeviceWatcher::deviceRemoved, this, &MainDialog::LoadPresetConfig);
} }
MainDialog::~MainDialog() = default; MainDialog::~MainDialog() = default;
void MainDialog::SetContentSizes(int previous_width, int previous_height) { void MainDialog::SetContentSizes(int previous_width, int previous_height) {
const int current_width = width(); const int current_width = width();
if (previous_width == 0) { // first time if (previous_width == 0) { // first time
ui->main->setColumnWidth(0, 0.3 * current_width); ui->main->setColumnWidth(0, 0.3 * current_width);
ui->main->setColumnWidth(1, 0.4 * current_width); ui->main->setColumnWidth(1, 0.4 * current_width);
} else { // proportionally update column widths } else { // proportionally update column widths
for (int i : {0, 1}) { for (int i : {0, 1}) {
ui->main->setColumnWidth(i, ui->main->columnWidth(i) * current_width / previous_width); ui->main->setColumnWidth(i, ui->main->columnWidth(i) * current_width / previous_width);
} }
} }
} }
static const std::regex sdmc_path_regex{"(.+)([/\\\\])Nintendo 3DS/([0-9a-f]{32})/([0-9a-f]{32})/"}; static const std::regex sdmc_path_regex{"(.+)([/\\\\])Nintendo 3DS/([0-9a-f]{32})/([0-9a-f]{32})/"};
void MainDialog::LoadPresetConfig() { void MainDialog::LoadPresetConfig() {
ui->main->clear(); ui->main->clear();
preset_config_list.clear(); preset_config_list.clear();
for (const auto& storage : QStorageInfo::mountedVolumes()) { for (const auto& storage : QStorageInfo::mountedVolumes()) {
if (!storage.isValid() || !storage.isReady()) { if (!storage.isValid() || !storage.isReady()) {
continue; continue;
} }
auto list = Core::LoadPresetConfig(storage.rootPath().toStdString()); auto list = Core::LoadPresetConfig(storage.rootPath().toStdString());
for (std::size_t i = 0; i < list.size(); ++i) { for (std::size_t i = 0; i < list.size(); ++i) {
preset_config_list.emplace_back(list[i]); preset_config_list.emplace_back(list[i]);
QString path = storage.rootPath(); QString path = storage.rootPath();
if (path.endsWith(QLatin1Char{'/'}) || path.endsWith(QLatin1Char{'\\'})) { if (path.endsWith(QLatin1Char{'/'}) || path.endsWith(QLatin1Char{'\\'})) {
path.remove(path.size() - 1, 1); path.remove(path.size() - 1, 1);
} }
// Get ID0 // Get ID0
QString id0 = tr("Unknown"); QString id0 = tr("Unknown");
std::smatch match; std::smatch match;
if (std::regex_match(list[i].sdmc_path, match, sdmc_path_regex)) { if (std::regex_match(list[i].sdmc_path, match, sdmc_path_regex)) {
if (match.size() >= 5) { if (match.size() >= 5) {
id0 = QString::fromStdString(match[3].str()); id0 = QString::fromStdString(match[3].str());
} }
} }
// Get status // Get status
QString status = tr("Good"); QString status = tr("Good");
if (!IsConfigGood(list[i])) { if (!IsConfigGood(list[i])) {
status = tr("No Configuration Found"); status = tr("No Configuration Found");
} else if (list[i].version != Core::CurrentDumperVersion) { } else if (list[i].version != Core::CurrentDumperVersion) {
status = tr("Version Dismatch"); status = tr("Version Dismatch");
} else if (list[i].safe_mode_firm_path.empty() || } else if (list[i].safe_mode_firm_path.empty() ||
list[i].config_savegame_path.empty() || list[i].config_savegame_path.empty() ||
list[i].system_archives_path.empty()) { list[i].system_archives_path.empty()) {
status = tr("Missing System Files"); status = tr("Missing System Files");
} else if (list[i].seed_db_path.empty()) { } else if (list[i].seed_db_path.empty()) {
status = tr("Good, Missing Seeds"); status = tr("Good, Missing Seeds");
} }
auto* item = new QTreeWidgetItem{{path, id0, status}}; auto* item = new QTreeWidgetItem{{path, id0, status}};
ui->main->invisibleRootItem()->addChild(item); ui->main->invisibleRootItem()->addChild(item);
} }
} }
auto* item = new QTreeWidgetItem{{tr("Browse SD Card Root...")}}; auto* item = new QTreeWidgetItem{{tr("Browse SD Card Root...")}};
item->setFirstColumnSpanned(true); item->setFirstColumnSpanned(true);
ui->main->invisibleRootItem()->addChild(item); ui->main->invisibleRootItem()->addChild(item);
} }
void MainDialog::LaunchImportDialog() { void MainDialog::LaunchImportDialog() {
auto* item = ui->main->currentItem(); auto* item = ui->main->currentItem();
if (!item) { if (!item) {
return; return;
} }
Core::Config config; Core::Config config;
const auto index = ui->main->invisibleRootItem()->indexOfChild(item); const auto index = ui->main->invisibleRootItem()->indexOfChild(item);
if (index == ui->main->invisibleRootItem()->childCount() - 1) { if (index == ui->main->invisibleRootItem()->childCount() - 1) {
const QString path = QFileDialog::getExistingDirectory(this, tr("Select SD Card Root")); const QString path = QFileDialog::getExistingDirectory(this, tr("Select SD Card Root"));
if (path.isEmpty()) { if (path.isEmpty()) {
return; return;
} }
const auto& list = Core::LoadPresetConfig(path.toStdString()); const auto& list = Core::LoadPresetConfig(path.toStdString());
if (list.size() > 1) { if (list.size() > 1) {
QMessageBox::information( QMessageBox::information(
this, tr("threeSD"), this, tr("threeSD"),
tr("You have more than one 3DS data on your SD Card.\nthreeSD will " tr("You have more than one 3DS data on your SD Card.\nthreeSD will "
"select the first one for you.")); "select the first one for you."));
} else if (list.empty() || !IsConfigGood(list[0])) { } else if (list.empty() || !IsConfigGood(list[0])) {
QMessageBox::critical( QMessageBox::critical(
this, tr("Error"), this, tr("Error"),
tr("Could not load configuration.<br>Please check if you have followed the <a " tr("Could not load configuration.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>" "href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>"
"guide</a> correctly.")); "guide</a> correctly."));
return; return;
} }
config = list[0]; config = list[0];
} else { } else {
config = preset_config_list.at(index); config = preset_config_list.at(index);
} }
// Check config integrity // Check config integrity
if (!IsConfigGood(config)) { if (!IsConfigGood(config)) {
QMessageBox::critical( QMessageBox::critical(
this, tr("Error"), this, tr("Error"),
tr("Could not load configuration from this SD card. You need to prepare your SD card " tr("Could not load configuration from this SD card. You need to prepare your SD card "
"before using threeSD.<br>Please check if you have followed the <a " "before using threeSD.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>" "href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>"
"guide</a> correctly.")); "guide</a> correctly."));
return; return;
} }
if (config.version != Core::CurrentDumperVersion) { if (config.version != Core::CurrentDumperVersion) {
QMessageBox::critical(this, tr("Version Dismatch"), QMessageBox::critical(this, tr("Version Dismatch"),
tr("You are using an unsupported version of threeSDumper.<br>Please " tr("You are using an unsupported version of threeSDumper.<br>Please "
"ensure that you are using the most recent version of both " "ensure that you are using the most recent version of both "
"threeSD and threeSDumper and try again.")); "threeSD and threeSDumper and try again."));
return; return;
} }
if (config.safe_mode_firm_path.empty() || config.config_savegame_path.empty() || if (config.safe_mode_firm_path.empty() || config.config_savegame_path.empty() ||
config.system_archives_path.empty()) { config.system_archives_path.empty()) {
QMessageBox::warning( QMessageBox::warning(
this, tr("Warning"), this, tr("Warning"),
tr("Certain system files are missing from your configuration.<br>Some contents " tr("Certain system files are missing from your configuration.<br>Some contents "
"may not be importable, or may not run.<br>Please check if you have followed the <a " "may not be importable, or may not run.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>guide</a> " "href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>guide</a> "
"correctly.")); "correctly."));
} else if (config.seed_db_path.empty()) { } else if (config.seed_db_path.empty()) {
QMessageBox::warning(this, tr("Warning"), QMessageBox::warning(this, tr("Warning"),
tr("Seed database is missing from your configuration.<br>Your system " tr("Seed database is missing from your configuration.<br>Your system "
"likely does not have any seeds.<br>However, if it does have any, " "likely does not have any seeds.<br>However, if it does have any, "
"imported games using seed encryption may not work.")); "imported games using seed encryption may not work."));
} }
ImportDialog dialog(this, config); ImportDialog dialog(this, config);
dialog.exec(); dialog.exec();
} }
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
// Init settings params // Init settings params
QCoreApplication::setOrganizationName(QStringLiteral("zhaowenlan1779")); QCoreApplication::setOrganizationName(QStringLiteral("zhaowenlan1779"));
QCoreApplication::setApplicationName(QStringLiteral("threeSD")); QCoreApplication::setApplicationName(QStringLiteral("threeSD"));
#ifdef __APPLE__ #ifdef __APPLE__
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
chdir(bin_path.c_str()); chdir(bin_path.c_str());
#endif #endif
QApplication app(argc, argv); QApplication app(argc, argv);
QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons/default"))); QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons/default")));
QIcon::setThemeName(QStringLiteral(":/icons/default")); QIcon::setThemeName(QStringLiteral(":/icons/default"));
MainDialog main_dialog; MainDialog main_dialog;
main_dialog.show(); main_dialog.show();
return app.exec(); return app.exec();
} }
+30 -30
View File
@@ -1,30 +1,30 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <memory> #include <memory>
#include "core/importer.h" #include "core/importer.h"
#include "frontend/helpers/dpi_aware_dialog.h" #include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui { namespace Ui {
class MainDialog; class MainDialog;
} }
class MainDialog final : public DPIAwareDialog { class MainDialog final : public DPIAwareDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit MainDialog(QWidget* parent = nullptr); explicit MainDialog(QWidget* parent = nullptr);
~MainDialog() override; ~MainDialog() override;
private: private:
void SetContentSizes(int previous_width, int previous_height) override; void SetContentSizes(int previous_width, int previous_height) override;
void LoadPresetConfig(); void LoadPresetConfig();
void LaunchImportDialog(); void LaunchImportDialog();
std::vector<Core::Config> preset_config_list; std::vector<Core::Config> preset_config_list;
std::unique_ptr<Ui::MainDialog> ui; std::unique_ptr<Ui::MainDialog> ui;
}; };
+78 -78
View File
@@ -1,78 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>MainDialog</class> <class>MainDialog</class>
<widget class="QDialog" name="MainDialog"> <widget class="QDialog" name="MainDialog">
<property name="windowTitle"> <property name="windowTitle">
<string>threeSD</string> <string>threeSD</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Select your SD card root, or manually browse when not detected.</string> <string>Select your SD card root, or manually browse when not detected.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="utilitiesButton"> <widget class="QPushButton" name="utilitiesButton">
<property name="text"> <property name="text">
<string>Utilities...</string> <string>Utilities...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QTreeWidget" name="main"> <widget class="QTreeWidget" name="main">
<column> <column>
<property name="text"> <property name="text">
<string>Path</string> <string>Path</string>
</property> </property>
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>ID</string> <string>ID</string>
</property> </property>
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>Status</string> <string>Status</string>
</property> </property>
</column> </column>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="importDestination"/> <widget class="QLabel" name="importDestination"/>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Reset</set> <set>QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>
+55 -55
View File
@@ -1,55 +1,55 @@
// Copyright 2020 threeSD Project // Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include "frontend/select_files_dialog.h" #include "frontend/select_files_dialog.h"
#include "ui_select_files_dialog.h" #include "ui_select_files_dialog.h"
SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_) SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_)
: DPIAwareDialog(parent, 480, 96), ui(std::make_unique<Ui::SelectFilesDialog>()), : DPIAwareDialog(parent, 480, 96), ui(std::make_unique<Ui::SelectFilesDialog>()),
source_is_dir(source_is_dir_), destination_is_dir(destination_is_dir_) { source_is_dir(source_is_dir_), destination_is_dir(destination_is_dir_) {
ui->setupUi(this); ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] {
if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) { if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) {
QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields.")); QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields."));
return; return;
} }
accept(); accept();
}); });
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject);
connect(ui->sourceExplore, &QToolButton::clicked, [this] { connect(ui->sourceExplore, &QToolButton::clicked, [this] {
QString path; QString path;
if (source_is_dir) { if (source_is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
} else { } else {
path = QFileDialog::getOpenFileName(this, tr("Select File")); path = QFileDialog::getOpenFileName(this, tr("Select File"));
} }
if (!path.isEmpty()) { if (!path.isEmpty()) {
ui->source->setText(path); ui->source->setText(path);
} }
}); });
connect(ui->destinationExplore, &QToolButton::clicked, [this] { connect(ui->destinationExplore, &QToolButton::clicked, [this] {
QString path; QString path;
if (destination_is_dir) { if (destination_is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
} else { } else {
path = QFileDialog::getSaveFileName(this, tr("Select File")); path = QFileDialog::getSaveFileName(this, tr("Select File"));
} }
if (!path.isEmpty()) { if (!path.isEmpty()) {
ui->destination->setText(path); ui->destination->setText(path);
} }
}); });
} }
SelectFilesDialog::~SelectFilesDialog() = default; SelectFilesDialog::~SelectFilesDialog() = default;
std::pair<QString, QString> SelectFilesDialog::GetResults() const { std::pair<QString, QString> SelectFilesDialog::GetResults() const {
return {ui->source->text(), ui->destination->text()}; return {ui->source->text(), ui->destination->text()};
} }
+27 -27
View File
@@ -1,27 +1,27 @@
// Copyright 2020 threeSD Project // Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <memory> #include <memory>
#include "frontend/helpers/dpi_aware_dialog.h" #include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui { namespace Ui {
class SelectFilesDialog; class SelectFilesDialog;
} }
class SelectFilesDialog : public DPIAwareDialog { class SelectFilesDialog : public DPIAwareDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit SelectFilesDialog(QWidget* parent, bool source_is_dir, bool destination_is_dir); explicit SelectFilesDialog(QWidget* parent, bool source_is_dir, bool destination_is_dir);
~SelectFilesDialog() override; ~SelectFilesDialog() override;
std::pair<QString, QString> GetResults() const; std::pair<QString, QString> GetResults() const;
private: private:
std::unique_ptr<Ui::SelectFilesDialog> ui; std::unique_ptr<Ui::SelectFilesDialog> ui;
bool source_is_dir; // Whether Source should be a directory bool source_is_dir; // Whether Source should be a directory
bool destination_is_dir; // Whether Destination should be a directory bool destination_is_dir; // Whether Destination should be a directory
}; };
+68 -68
View File
@@ -1,68 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>SelectFilesDialog</class> <class>SelectFilesDialog</class>
<widget class="QDialog" name="SelectFilesDialog"> <widget class="QDialog" name="SelectFilesDialog">
<property name="windowTitle"> <property name="windowTitle">
<string>Select Files</string> <string>Select Files</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<layout class="QGridLayout"> <layout class="QGridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Source:</string> <string>Source:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="source"/> <widget class="QLineEdit" name="source"/>
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QToolButton" name="sourceExplore"> <widget class="QToolButton" name="sourceExplore">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Destination:</string> <string>Destination:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="destination"/> <widget class="QLineEdit" name="destination"/>
</item> </item>
<item row="1" column="2"> <item row="1" column="2">
<widget class="QToolButton" name="destinationExplore"> <widget class="QToolButton" name="destinationExplore">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set> <set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</ui> </ui>
+241 -241
View File
@@ -1,241 +1,241 @@
// Copyright 2021 threeSD Project // Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QAction> #include <QAction>
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QFileDialog> #include <QFileDialog>
#include <QImageWriter> #include <QImageWriter>
#include <QMessageBox> #include <QMessageBox>
#include <QPixmap> #include <QPixmap>
#include <fmt/format.h> #include <fmt/format.h>
#include "common/string_util.h" #include "common/string_util.h"
#include "core/db/title_db.h" #include "core/db/title_db.h"
#include "core/file_sys/ncch_container.h" #include "core/file_sys/ncch_container.h"
#include "core/file_sys/title_metadata.h" #include "core/file_sys/title_metadata.h"
#include "core/importer.h" #include "core/importer.h"
#include "frontend/helpers/simple_job.h" #include "frontend/helpers/simple_job.h"
#include "frontend/title_info_dialog.h" #include "frontend/title_info_dialog.h"
#include "ui_title_info_dialog.h" #include "ui_title_info_dialog.h"
TitleInfoDialog::TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer_, TitleInfoDialog::TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer_,
Core::ContentSpecifier specifier_) Core::ContentSpecifier specifier_)
: DPIAwareDialog(parent, 500, 360), ui(std::make_unique<Ui::TitleInfoDialog>()), : DPIAwareDialog(parent, 500, 360), ui(std::make_unique<Ui::TitleInfoDialog>()),
importer(importer_), specifier(std::move(specifier_)) { importer(importer_), specifier(std::move(specifier_)) {
ui->setupUi(this); ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
LoadInfo(); LoadInfo();
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept);
} }
TitleInfoDialog::~TitleInfoDialog() = default; TitleInfoDialog::~TitleInfoDialog() = default;
void TitleInfoDialog::LoadInfo() { void TitleInfoDialog::LoadInfo() {
// Load TMD & boot NCCH // Load TMD & boot NCCH
Core::TitleMetadata tmd; Core::TitleMetadata tmd;
Core::NCCHContainer ncch; Core::NCCHContainer ncch;
if (!importer.LoadTMD(specifier, tmd) || if (!importer.LoadTMD(specifier, tmd) ||
!ncch.OpenFile(importer.OpenContent(specifier, tmd.GetBootContentID()))) { !ncch.OpenFile(importer.OpenContent(specifier, tmd.GetBootContentID()))) {
QMessageBox::warning(this, tr("threeSD"), tr("Could not load title information.")); QMessageBox::warning(this, tr("threeSD"), tr("Could not load title information."));
reject(); reject();
return; return;
} }
// Load SMDH from boot NCCH // Load SMDH from boot NCCH
bool has_smdh = false; bool has_smdh = false;
std::vector<u8> smdh_buffer; std::vector<u8> smdh_buffer;
if (ncch.LoadSectionExeFS("icon", smdh_buffer) && smdh_buffer.size() == sizeof(Core::SMDH) && if (ncch.LoadSectionExeFS("icon", smdh_buffer) && smdh_buffer.size() == sizeof(Core::SMDH) &&
Core::IsValidSMDH(smdh_buffer)) { Core::IsValidSMDH(smdh_buffer)) {
has_smdh = true; has_smdh = true;
std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size()); std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size());
} }
// Basic info // Basic info
ui->versionLineEdit->setText(QString::fromStdString(tmd.GetTitleVersionString())); ui->versionLineEdit->setText(QString::fromStdString(tmd.GetTitleVersionString()));
LoadEncryption(ncch); LoadEncryption(ncch);
ui->titleIDLineEdit->setText(QStringLiteral("%1").arg(specifier.id, 16, 16, QLatin1Char{'0'})); ui->titleIDLineEdit->setText(QStringLiteral("%1").arg(specifier.id, 16, 16, QLatin1Char{'0'}));
// Icons // Icons
if (has_smdh) { if (has_smdh) {
LoadIcons(); LoadIcons();
} }
// Names // Names
if (has_smdh) { if (has_smdh) {
InitializeLanguageComboBox(); InitializeLanguageComboBox();
} else { } else {
ui->namesGroupBox->setVisible(false); ui->namesGroupBox->setVisible(false);
} }
// Checks // Checks
InitializeChecks(tmd); InitializeChecks(tmd);
} }
void TitleInfoDialog::LoadEncryption(Core::NCCHContainer& ncch) { void TitleInfoDialog::LoadEncryption(Core::NCCHContainer& ncch) {
static const std::unordered_map<Core::EncryptionType, const char*> EncryptionTypeMap{{ static const std::unordered_map<Core::EncryptionType, const char*> EncryptionTypeMap{{
{Core::EncryptionType::None, QT_TR_NOOP("None")}, {Core::EncryptionType::None, QT_TR_NOOP("None")},
{Core::EncryptionType::FixedKey, QT_TR_NOOP("FixedKey")}, {Core::EncryptionType::FixedKey, QT_TR_NOOP("FixedKey")},
{Core::EncryptionType::NCCHSecure1, QT_TR_NOOP("Secure1")}, {Core::EncryptionType::NCCHSecure1, QT_TR_NOOP("Secure1")},
{Core::EncryptionType::NCCHSecure2, QT_TR_NOOP("Secure2")}, {Core::EncryptionType::NCCHSecure2, QT_TR_NOOP("Secure2")},
{Core::EncryptionType::NCCHSecure3, QT_TR_NOOP("Secure3")}, {Core::EncryptionType::NCCHSecure3, QT_TR_NOOP("Secure3")},
{Core::EncryptionType::NCCHSecure4, QT_TR_NOOP("Secure4")}, {Core::EncryptionType::NCCHSecure4, QT_TR_NOOP("Secure4")},
}}; }};
Core::EncryptionType encryption = Core::EncryptionType::None; Core::EncryptionType encryption = Core::EncryptionType::None;
ncch.ReadEncryptionType(encryption); ncch.ReadEncryptionType(encryption);
bool seed_crypto = false; bool seed_crypto = false;
ncch.ReadSeedCrypto(seed_crypto); ncch.ReadSeedCrypto(seed_crypto);
QString encryption_text = tr(EncryptionTypeMap.at(encryption)); QString encryption_text = tr(EncryptionTypeMap.at(encryption));
if (seed_crypto) { if (seed_crypto) {
encryption_text.append(tr(" (Seed)")); encryption_text.append(tr(" (Seed)"));
} }
ui->encryptionLineEdit->setText(encryption_text); ui->encryptionLineEdit->setText(encryption_text);
} }
void TitleInfoDialog::LoadIcons() { void TitleInfoDialog::LoadIcons() {
ui->iconLargeLabel->setPixmap( ui->iconLargeLabel->setPixmap(
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(true).data()), 48, 48, QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(true).data()), 48, 48,
QImage::Format::Format_RGB16))); QImage::Format::Format_RGB16)));
QAction* save_icon_large = new QAction(tr("Save Icon (Large)"), this); QAction* save_icon_large = new QAction(tr("Save Icon (Large)"), this);
ui->iconLargeLabel->addAction(save_icon_large); ui->iconLargeLabel->addAction(save_icon_large);
connect(save_icon_large, &QAction::triggered, this, [this] { SaveIcon(true); }); connect(save_icon_large, &QAction::triggered, this, [this] { SaveIcon(true); });
ui->iconSmallLabel->setPixmap( ui->iconSmallLabel->setPixmap(
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(false).data()), 24, QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(false).data()), 24,
24, QImage::Format::Format_RGB16))); 24, QImage::Format::Format_RGB16)));
QAction* save_icon_small = new QAction(tr("Save Icon (Small)"), this); QAction* save_icon_small = new QAction(tr("Save Icon (Small)"), this);
ui->iconSmallLabel->addAction(save_icon_small); ui->iconSmallLabel->addAction(save_icon_small);
connect(save_icon_small, &QAction::triggered, this, [this] { SaveIcon(false); }); connect(save_icon_small, &QAction::triggered, this, [this] { SaveIcon(false); });
} }
void TitleInfoDialog::InitializeLanguageComboBox() { void TitleInfoDialog::InitializeLanguageComboBox() {
if (!ui->namesGroupBox->isEnabled()) { // SMDH not available if (!ui->namesGroupBox->isEnabled()) { // SMDH not available
return; return;
} }
// Corresponds to the indices of the languages defined in SMDH // Corresponds to the indices of the languages defined in SMDH
static constexpr std::array<const char*, 12> LanguageNames{{ static constexpr std::array<const char*, 12> LanguageNames{{
QT_TR_NOOP("Japanese"), QT_TR_NOOP("Japanese"),
QT_TR_NOOP("English"), QT_TR_NOOP("English"),
QT_TR_NOOP("French"), QT_TR_NOOP("French"),
QT_TR_NOOP("German"), QT_TR_NOOP("German"),
QT_TR_NOOP("Italian"), QT_TR_NOOP("Italian"),
QT_TR_NOOP("Spanish"), QT_TR_NOOP("Spanish"),
QT_TR_NOOP("Chinese (Simplified)"), QT_TR_NOOP("Chinese (Simplified)"),
QT_TR_NOOP("Korean"), QT_TR_NOOP("Korean"),
QT_TR_NOOP("Dutch"), QT_TR_NOOP("Dutch"),
QT_TR_NOOP("Portuguese"), QT_TR_NOOP("Portuguese"),
QT_TR_NOOP("Russian"), QT_TR_NOOP("Russian"),
QT_TR_NOOP("Chinese (Traditional)"), QT_TR_NOOP("Chinese (Traditional)"),
}}; }};
for (std::size_t i = 0; i < LanguageNames.size(); ++i) { for (std::size_t i = 0; i < LanguageNames.size(); ++i) {
const auto& title = smdh.titles.at(i); const auto& title = smdh.titles.at(i);
if (Common::UTF16BufferToUTF8(title.short_title).empty() && if (Common::UTF16BufferToUTF8(title.short_title).empty() &&
Common::UTF16BufferToUTF8(title.long_title).empty() && Common::UTF16BufferToUTF8(title.long_title).empty() &&
Common::UTF16BufferToUTF8(title.publisher).empty()) { Common::UTF16BufferToUTF8(title.publisher).empty()) {
// All empty, ignore this language // All empty, ignore this language
continue; continue;
} }
ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast<int>(i)); ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast<int>(i));
if (i == 1) { // English if (i == 1) { // English
ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1); ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1);
} }
} }
connect(ui->languageComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, connect(ui->languageComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&TitleInfoDialog::UpdateNames); &TitleInfoDialog::UpdateNames);
UpdateNames(); UpdateNames();
} }
void TitleInfoDialog::SaveIcon(bool large) { void TitleInfoDialog::SaveIcon(bool large) {
const auto types = QImageWriter::supportedImageFormats(); const auto types = QImageWriter::supportedImageFormats();
QStringList filters; QStringList filters;
for (const auto& type : types) { for (const auto& type : types) {
const QString extension = QString::fromUtf8(type); const QString extension = QString::fromUtf8(type);
filters << QStringLiteral("%1 Image (*.%2)").arg(extension.toUpper(), extension); filters << QStringLiteral("%1 Image (*.%2)").arg(extension.toUpper(), extension);
} }
static QString last_path; static QString last_path;
const QString path = QFileDialog::getSaveFileName(this, tr("Save Icon"), last_path, const QString path = QFileDialog::getSaveFileName(this, tr("Save Icon"), last_path,
filters.join(QStringLiteral(";;"))); filters.join(QStringLiteral(";;")));
if (path.isEmpty()) { if (path.isEmpty()) {
return; return;
} }
last_path = QFileInfo(path).path(); last_path = QFileInfo(path).path();
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
const auto& pixmap = large ? ui->iconLargeLabel->pixmap(Qt::ReturnByValue) const auto& pixmap = large ? ui->iconLargeLabel->pixmap(Qt::ReturnByValue)
: ui->iconSmallLabel->pixmap(Qt::ReturnByValue); : ui->iconSmallLabel->pixmap(Qt::ReturnByValue);
#else #else
const auto& pixmap = large ? *ui->iconLargeLabel->pixmap() : *ui->iconSmallLabel->pixmap(); const auto& pixmap = large ? *ui->iconLargeLabel->pixmap() : *ui->iconSmallLabel->pixmap();
#endif #endif
if (!pixmap.save(path)) { if (!pixmap.save(path)) {
QMessageBox::warning(this, tr("threeSD"), tr("Could not save icon.")); QMessageBox::warning(this, tr("threeSD"), tr("Could not save icon."));
} }
} }
void TitleInfoDialog::UpdateNames() { void TitleInfoDialog::UpdateNames() {
const auto& title = smdh.titles.at(ui->languageComboBox->currentData().toInt()); const auto& title = smdh.titles.at(ui->languageComboBox->currentData().toInt());
ui->shortTitleLineEdit->setText( ui->shortTitleLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.short_title))); QString::fromStdString(Common::UTF16BufferToUTF8(title.short_title)));
ui->longTitleLineEdit->setText( ui->longTitleLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.long_title))); QString::fromStdString(Common::UTF16BufferToUTF8(title.long_title)));
ui->publisherLineEdit->setText( ui->publisherLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.publisher))); QString::fromStdString(Common::UTF16BufferToUTF8(title.publisher)));
} }
void TitleInfoDialog::InitializeChecks(Core::TitleMetadata& tmd) { void TitleInfoDialog::InitializeChecks(Core::TitleMetadata& tmd) {
const bool tmd_legit = tmd.ValidateSignature() && tmd.VerifyHashes(); const bool tmd_legit = tmd.ValidateSignature() && tmd.VerifyHashes();
if (tmd_legit) { if (tmd_legit) {
ui->tmdCheckLabel->setText(tr("Legit")); ui->tmdCheckLabel->setText(tr("Legit"));
} else { } else {
ui->tmdCheckLabel->setText(tr("Illegit")); ui->tmdCheckLabel->setText(tr("Illegit"));
} }
if (const auto& ticket_db = importer.GetTicketDB(); if (const auto& ticket_db = importer.GetTicketDB();
ticket_db && ticket_db->tickets.count(specifier.id)) { ticket_db && ticket_db->tickets.count(specifier.id)) {
const bool ticket_legit = ticket_db->tickets.at(specifier.id).ValidateSignature(); const bool ticket_legit = ticket_db->tickets.at(specifier.id).ValidateSignature();
if (ticket_legit) { if (ticket_legit) {
ui->ticketCheckLabel->setText(tr("Legit")); ui->ticketCheckLabel->setText(tr("Legit"));
} else { } else {
ui->ticketCheckLabel->setText(tr("Illegit")); ui->ticketCheckLabel->setText(tr("Illegit"));
} }
} else { } else {
ui->ticketCheckLabel->setText(tr("Missing")); ui->ticketCheckLabel->setText(tr("Missing"));
} }
connect(ui->contentsCheckButton, &QPushButton::clicked, this, connect(ui->contentsCheckButton, &QPushButton::clicked, this,
&TitleInfoDialog::ExecuteContentsCheck); &TitleInfoDialog::ExecuteContentsCheck);
} }
void TitleInfoDialog::ExecuteContentsCheck() { void TitleInfoDialog::ExecuteContentsCheck() {
auto* job = new SimpleJob( auto* job = new SimpleJob(
this, this,
[this](const Common::ProgressCallback& callback) { [this](const Common::ProgressCallback& callback) {
contents_check_result = importer.CheckTitleContents(specifier, callback); contents_check_result = importer.CheckTitleContents(specifier, callback);
return true; return true;
}, },
[this] { importer.AbortImporting(); }); [this] { importer.AbortImporting(); });
connect(job, &SimpleJob::Completed, this, [this](bool canceled) { connect(job, &SimpleJob::Completed, this, [this](bool canceled) {
if (canceled) { if (canceled) {
return; return;
} }
ui->contentsCheckButton->setVisible(false); ui->contentsCheckButton->setVisible(false);
ui->contentsCheckLabel->setVisible(true); ui->contentsCheckLabel->setVisible(true);
if (contents_check_result) { if (contents_check_result) {
ui->contentsCheckLabel->setText(tr("OK")); ui->contentsCheckLabel->setText(tr("OK"));
} else { } else {
ui->contentsCheckLabel->setText(tr("Failed")); ui->contentsCheckLabel->setText(tr("Failed"));
} }
}); });
job->StartWithProgressDialog(this); job->StartWithProgressDialog(this);
} }
+47 -47
View File
@@ -1,47 +1,47 @@
// Copyright 2021 threeSD Project // Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <memory> #include <memory>
#include "core/file_sys/smdh.h" #include "core/file_sys/smdh.h"
#include "frontend/helpers/dpi_aware_dialog.h" #include "frontend/helpers/dpi_aware_dialog.h"
namespace Core { namespace Core {
struct Config; struct Config;
struct ContentSpecifier; struct ContentSpecifier;
class NCCHContainer; class NCCHContainer;
class SDMCImporter; class SDMCImporter;
class TitleMetadata; class TitleMetadata;
} // namespace Core } // namespace Core
namespace Ui { namespace Ui {
class TitleInfoDialog; class TitleInfoDialog;
} }
class TitleInfoDialog : public DPIAwareDialog { class TitleInfoDialog : public DPIAwareDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer, explicit TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer,
Core::ContentSpecifier specifier); Core::ContentSpecifier specifier);
~TitleInfoDialog(); ~TitleInfoDialog();
private: private:
void LoadInfo(); void LoadInfo();
void LoadEncryption(Core::NCCHContainer& ncch); void LoadEncryption(Core::NCCHContainer& ncch);
void LoadIcons(); void LoadIcons();
void InitializeLanguageComboBox(); void InitializeLanguageComboBox();
void InitializeChecks(Core::TitleMetadata& tmd); void InitializeChecks(Core::TitleMetadata& tmd);
void SaveIcon(bool large); void SaveIcon(bool large);
void UpdateNames(); void UpdateNames();
void ExecuteContentsCheck(); void ExecuteContentsCheck();
std::unique_ptr<Ui::TitleInfoDialog> ui; std::unique_ptr<Ui::TitleInfoDialog> ui;
Core::SDMCImporter& importer; Core::SDMCImporter& importer;
const Core::ContentSpecifier specifier; const Core::ContentSpecifier specifier;
Core::SMDH smdh{}; Core::SMDH smdh{};
bool contents_check_result = false; bool contents_check_result = false;
}; };
+238 -238
View File
@@ -1,238 +1,238 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>TitleInfoDialog</class> <class>TitleInfoDialog</class>
<widget class="QDialog" name="TitleInfoDialog"> <widget class="QDialog" name="TitleInfoDialog">
<property name="windowTitle"> <property name="windowTitle">
<string>Title Info</string> <string>Title Info</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>Info</string> <string>Info</string>
</property> </property>
<layout class="QGridLayout"> <layout class="QGridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Version:</string> <string>Version:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="versionLineEdit"> <widget class="QLineEdit" name="versionLineEdit">
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2" rowspan="2"> <item row="0" column="2" rowspan="2">
<widget class="QLabel" name="iconLargeLabel"> <widget class="QLabel" name="iconLargeLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>48</width> <width>48</width>
<height>48</height> <height>48</height>
</size> </size>
</property> </property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum> <enum>Qt::ActionsContextMenu</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="3" rowspan="2"> <item row="0" column="3" rowspan="2">
<widget class="QLabel" name="iconSmallLabel"> <widget class="QLabel" name="iconSmallLabel">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>24</width> <width>24</width>
<height>24</height> <height>24</height>
</size> </size>
</property> </property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum> <enum>Qt::ActionsContextMenu</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Encryption:</string> <string>Encryption:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="encryptionLineEdit"> <widget class="QLineEdit" name="encryptionLineEdit">
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Title ID:</string> <string>Title ID:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" colspan="3"> <item row="2" column="1" colspan="3">
<widget class="QLineEdit" name="titleIDLineEdit"> <widget class="QLineEdit" name="titleIDLineEdit">
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="namesGroupBox"> <widget class="QGroupBox" name="namesGroupBox">
<property name="title"> <property name="title">
<string>Names</string> <string>Names</string>
</property> </property>
<layout class="QGridLayout" columnstretch="0,20,10"> <layout class="QGridLayout" columnstretch="0,20,10">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Short Title:</string> <string>Short Title:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="shortTitleLineEdit"> <widget class="QLineEdit" name="shortTitleLineEdit">
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QComboBox" name="languageComboBox"/> <widget class="QComboBox" name="languageComboBox"/>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Long Title:</string> <string>Long Title:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" colspan="2"> <item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="longTitleLineEdit"> <widget class="QLineEdit" name="longTitleLineEdit">
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Publisher:</string> <string>Publisher:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" colspan="2"> <item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="publisherLineEdit"> <widget class="QLineEdit" name="publisherLineEdit">
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>Checks</string> <string>Checks</string>
</property> </property>
<layout class="QGridLayout"> <layout class="QGridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>TMD:</string> <string>TMD:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLabel" name="tmdCheckLabel"> <widget class="QLabel" name="tmdCheckLabel">
<property name="text"> <property name="text">
<string>Legit</string> <string>Legit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Ticket:</string> <string>Ticket:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLabel" name="ticketCheckLabel"> <widget class="QLabel" name="ticketCheckLabel">
<property name="text"> <property name="text">
<string>Legit</string> <string>Legit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="1" column="2">
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Contents:</string> <string>Contents:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QPushButton" name="contentsCheckButton"> <widget class="QPushButton" name="contentsCheckButton">
<property name="text"> <property name="text">
<string>Check</string> <string>Check</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLabel" name="contentsCheckLabel"> <widget class="QLabel" name="contentsCheckLabel">
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Legit</string> <string>Legit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="2"> <item row="2" column="2">
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</ui> </ui>
+302 -302
View File
@@ -1,302 +1,302 @@
// Copyright 2020 threeSD Project // Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QFileDialog> #include <QFileDialog>
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QMessageBox> #include <QMessageBox>
#include <QProgressDialog> #include <QProgressDialog>
#include <QtConcurrent/QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include "core/file_sys/data/data_container.h" #include "core/file_sys/data/data_container.h"
#include "core/file_sys/data/extdata.h" #include "core/file_sys/data/extdata.h"
#include "core/file_sys/data/savegame.h" #include "core/file_sys/data/savegame.h"
#include "core/file_sys/ncch_container.h" #include "core/file_sys/ncch_container.h"
#include "core/key/key.h" #include "core/key/key.h"
#include "core/sdmc_decryptor.h" #include "core/sdmc_decryptor.h"
#include "frontend/select_files_dialog.h" #include "frontend/select_files_dialog.h"
#include "frontend/utilities.h" #include "frontend/utilities.h"
#include "ui_utilities.h" #include "ui_utilities.h"
UtilitiesDialog::UtilitiesDialog(QWidget* parent) UtilitiesDialog::UtilitiesDialog(QWidget* parent)
: DPIAwareDialog(parent, 640, 384), ui(std::make_unique<Ui::UtilitiesDialog>()) { : DPIAwareDialog(parent, 640, 384), ui(std::make_unique<Ui::UtilitiesDialog>()) {
ui->setupUi(this); ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
connect(ui->useSdDecryption, &QCheckBox::clicked, [this] { connect(ui->useSdDecryption, &QCheckBox::clicked, [this] {
const bool checked = ui->useSdDecryption->isChecked(); const bool checked = ui->useSdDecryption->isChecked();
ui->boot9Path->setEnabled(checked); ui->boot9Path->setEnabled(checked);
ui->boot9PathExplore->setEnabled(checked); ui->boot9PathExplore->setEnabled(checked);
ui->movableSedPath->setEnabled(checked); ui->movableSedPath->setEnabled(checked);
ui->movableSedPathExplore->setEnabled(checked); ui->movableSedPathExplore->setEnabled(checked);
ui->sdmcPath->setEnabled(checked); ui->sdmcPath->setEnabled(checked);
ui->sdmcPathExplore->setEnabled(checked); ui->sdmcPathExplore->setEnabled(checked);
// First hide both, to avoid resizing the dialog // First hide both, to avoid resizing the dialog
ui->sdDecryptionLabel->setVisible(false); ui->sdDecryptionLabel->setVisible(false);
ui->sdDecryptionDisabledLabel->setVisible(false); ui->sdDecryptionDisabledLabel->setVisible(false);
ui->sdDecryptionLabel->setVisible(checked); ui->sdDecryptionLabel->setVisible(checked);
ui->sdDecryptionDisabledLabel->setVisible(!checked); ui->sdDecryptionDisabledLabel->setVisible(!checked);
ui->sdDecryption->setEnabled(checked); ui->sdDecryption->setEnabled(checked);
ui->extdataExtractionLabel->setVisible(false); ui->extdataExtractionLabel->setVisible(false);
ui->extdataExtractionDisabledLabel->setVisible(false); ui->extdataExtractionDisabledLabel->setVisible(false);
ui->extdataExtractionLabel->setVisible(checked); ui->extdataExtractionLabel->setVisible(checked);
ui->extdataExtractionDisabledLabel->setVisible(!checked); ui->extdataExtractionDisabledLabel->setVisible(!checked);
ui->extdataExtraction->setEnabled(checked); ui->extdataExtraction->setEnabled(checked);
ui->romfsExtractionLabel->setVisible(false); ui->romfsExtractionLabel->setVisible(false);
ui->romfsExtractionDisabledLabel->setVisible(false); ui->romfsExtractionDisabledLabel->setVisible(false);
ui->romfsExtractionLabel->setVisible(!checked); ui->romfsExtractionLabel->setVisible(!checked);
ui->romfsExtractionDisabledLabel->setVisible(checked); ui->romfsExtractionDisabledLabel->setVisible(checked);
ui->romfsExtraction->setEnabled(!checked); ui->romfsExtraction->setEnabled(!checked);
}); });
connect(ui->boot9PathExplore, &QToolButton::clicked, [this] { connect(ui->boot9PathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getOpenFileName(this, tr("Select File")); const QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
if (!path.isEmpty()) { if (!path.isEmpty()) {
ui->boot9Path->setText(path); ui->boot9Path->setText(path);
} }
}); });
connect(ui->movableSedPathExplore, &QToolButton::clicked, [this] { connect(ui->movableSedPathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getOpenFileName(this, tr("Select File")); const QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
if (!path.isEmpty()) { if (!path.isEmpty()) {
ui->movableSedPath->setText(path); ui->movableSedPath->setText(path);
} }
}); });
connect(ui->sdmcPathExplore, &QToolButton::clicked, [this] { connect(ui->sdmcPathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); const QString path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (!path.isEmpty()) { if (!path.isEmpty()) {
ui->sdmcPath->setText(path); ui->sdmcPath->setText(path);
} }
}); });
connect(ui->sdDecryption, &QPushButton::clicked, this, &UtilitiesDialog::SDDecryptionTool); connect(ui->sdDecryption, &QPushButton::clicked, this, &UtilitiesDialog::SDDecryptionTool);
connect(ui->savedataExtraction, &QPushButton::clicked, this, connect(ui->savedataExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::SaveDataExtractionTool); &UtilitiesDialog::SaveDataExtractionTool);
connect(ui->extdataExtraction, &QPushButton::clicked, this, connect(ui->extdataExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::ExtdataExtractionTool); &UtilitiesDialog::ExtdataExtractionTool);
connect(ui->romfsExtraction, &QPushButton::clicked, this, connect(ui->romfsExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::RomFSExtractionTool); &UtilitiesDialog::RomFSExtractionTool);
} }
UtilitiesDialog::~UtilitiesDialog() = default; UtilitiesDialog::~UtilitiesDialog() = default;
std::pair<QString, QString> UtilitiesDialog::GetFilePaths(bool source_is_dir, std::pair<QString, QString> UtilitiesDialog::GetFilePaths(bool source_is_dir,
bool destination_is_dir) { bool destination_is_dir) {
SelectFilesDialog dialog(this, source_is_dir, destination_is_dir); SelectFilesDialog dialog(this, source_is_dir, destination_is_dir);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
return dialog.GetResults(); return dialog.GetResults();
} else { } else {
return {}; return {};
} }
} }
bool UtilitiesDialog::LoadSDKeys() { bool UtilitiesDialog::LoadSDKeys() {
if (ui->boot9Path->text().isEmpty() || ui->movableSedPath->text().isEmpty()) { if (ui->boot9Path->text().isEmpty() || ui->movableSedPath->text().isEmpty()) {
QMessageBox::critical(this, tr("Error"), QMessageBox::critical(this, tr("Error"),
tr("Please select boot9.bin and movable.sed paths.")); tr("Please select boot9.bin and movable.sed paths."));
return false; return false;
} }
if (ui->sdmcPath->text().isEmpty()) { if (ui->sdmcPath->text().isEmpty()) {
QMessageBox::critical(this, tr("Error"), QMessageBox::critical(this, tr("Error"),
tr("Please select SDMC root (\"Nintendo 3DS/&lt;ID0>/&lt;ID1>\").")); tr("Please select SDMC root (\"Nintendo 3DS/&lt;ID0>/&lt;ID1>\")."));
return false; return false;
} }
Core::Key::ClearKeys(); Core::Key::ClearKeys();
Core::Key::LoadBootromKeys(ui->boot9Path->text().toStdString()); Core::Key::LoadBootromKeys(ui->boot9Path->text().toStdString());
Core::Key::LoadMovableSedKeys(ui->movableSedPath->text().toStdString()); Core::Key::LoadMovableSedKeys(ui->movableSedPath->text().toStdString());
if (!Core::Key::IsNormalKeyAvailable(Core::Key::SDKey)) { if (!Core::Key::IsNormalKeyAvailable(Core::Key::SDKey)) {
LOG_ERROR(Core, "SDKey is not available"); LOG_ERROR(Core, "SDKey is not available");
QMessageBox::critical(this, tr("Error"), QMessageBox::critical(this, tr("Error"),
tr("Could not load SD Key. Please check your files.")); tr("Could not load SD Key. Please check your files."));
return false; return false;
} }
return true; return true;
} }
void UtilitiesDialog::ShowProgressDialog(std::function<bool()> operation) { void UtilitiesDialog::ShowProgressDialog(std::function<bool()> operation) {
auto* dialog = new QProgressDialog(tr("Processing..."), tr("Cancel"), 0, 0, this); auto* dialog = new QProgressDialog(tr("Processing..."), tr("Cancel"), 0, 0, this);
dialog->setWindowFlags(dialog->windowFlags() & (~Qt::WindowContextHelpButtonHint)); dialog->setWindowFlags(dialog->windowFlags() & (~Qt::WindowContextHelpButtonHint));
dialog->setWindowModality(Qt::WindowModal); dialog->setWindowModality(Qt::WindowModal);
dialog->setCancelButton(nullptr); dialog->setCancelButton(nullptr);
dialog->setMinimumDuration(0); dialog->setMinimumDuration(0);
dialog->setValue(0); dialog->setValue(0);
using FutureWatcher = QFutureWatcher<void>; using FutureWatcher = QFutureWatcher<void>;
auto* future_watcher = new FutureWatcher(this); auto* future_watcher = new FutureWatcher(this);
connect(future_watcher, &FutureWatcher::finished, this, [this, dialog] { connect(future_watcher, &FutureWatcher::finished, this, [this, dialog] {
dialog->hide(); dialog->hide();
ShowResult(); ShowResult();
}); });
auto future = QtConcurrent::run([operation, this] { result = operation(); }); auto future = QtConcurrent::run([operation, this] { result = operation(); });
future_watcher->setFuture(future); future_watcher->setFuture(future);
} }
std::tuple<bool, std::string, std::string> UtilitiesDialog::GetSDMCRoot(const QString& source) { std::tuple<bool, std::string, std::string> UtilitiesDialog::GetSDMCRoot(const QString& source) {
QString sdmc_root = ui->sdmcPath->text().replace(QLatin1Char{'\\'}, QLatin1Char{'/'}); QString sdmc_root = ui->sdmcPath->text().replace(QLatin1Char{'\\'}, QLatin1Char{'/'});
if (!sdmc_root.endsWith(QLatin1Char{'/'})) { if (!sdmc_root.endsWith(QLatin1Char{'/'})) {
sdmc_root.append(QLatin1Char{'/'}); sdmc_root.append(QLatin1Char{'/'});
} }
if (!source.startsWith(sdmc_root)) { if (!source.startsWith(sdmc_root)) {
QMessageBox::critical(this, tr("Error"), tr("The file selected is not in SDMC root.")); QMessageBox::critical(this, tr("Error"), tr("The file selected is not in SDMC root."));
return {false, "", ""}; return {false, "", ""};
} }
const std::string relative_source = const std::string relative_source =
source.toStdString().substr(sdmc_root.toStdString().size() - 1); source.toStdString().substr(sdmc_root.toStdString().size() - 1);
return {true, sdmc_root.toStdString(), relative_source}; return {true, sdmc_root.toStdString(), relative_source};
} }
void UtilitiesDialog::SDDecryptionTool() { void UtilitiesDialog::SDDecryptionTool() {
if (!LoadSDKeys()) { if (!LoadSDKeys()) {
return; return;
} }
const auto& [source, destination] = GetFilePaths(false, false); const auto& [source, destination] = GetFilePaths(false, false);
if (source.isEmpty() || destination.isEmpty()) { if (source.isEmpty() || destination.isEmpty()) {
return; return;
} }
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source); const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) { if (!success) {
return; return;
} }
// TODO: Add Progress reporting // TODO: Add Progress reporting
ShowProgressDialog( ShowProgressDialog(
[sdmc_root = sdmc_root, relative_source = relative_source, destination = destination] { [sdmc_root = sdmc_root, relative_source = relative_source, destination = destination] {
Core::SDMCDecryptor decryptor(sdmc_root); Core::SDMCDecryptor decryptor(sdmc_root);
return decryptor.DecryptAndWriteFile(relative_source, destination.toStdString()); return decryptor.DecryptAndWriteFile(relative_source, destination.toStdString());
}); });
} }
void UtilitiesDialog::SaveDataExtractionTool() { void UtilitiesDialog::SaveDataExtractionTool() {
const bool decryption = ui->useSdDecryption->isChecked(); const bool decryption = ui->useSdDecryption->isChecked();
if (decryption && !LoadSDKeys()) { if (decryption && !LoadSDKeys()) {
return; return;
} }
const auto& [source, destination] = GetFilePaths(false, true); const auto& [source, destination] = GetFilePaths(false, true);
if (source.isEmpty() || destination.isEmpty()) { if (source.isEmpty() || destination.isEmpty()) {
return; return;
} }
if (decryption) { if (decryption) {
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source); const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) { if (!success) {
return; return;
} }
// TODO: Add Progress reporting // TODO: Add Progress reporting
ShowProgressDialog([sdmc_root = sdmc_root, relative_source = relative_source, ShowProgressDialog([sdmc_root = sdmc_root, relative_source = relative_source,
source = source, destination = destination] { source = source, destination = destination] {
const auto size = FileUtil::GetSize(source.toStdString()); const auto size = FileUtil::GetSize(source.toStdString());
std::vector<u8> data(size); std::vector<u8> data(size);
Core::SDMCFile file(sdmc_root, relative_source, "rb"); Core::SDMCFile file(sdmc_root, relative_source, "rb");
if (file.ReadBytes(data.data(), size) != size) { if (file.ReadBytes(data.data(), size) != size) {
return false; return false;
} }
Core::DataContainer container(data); Core::DataContainer container(data);
if (!container.IsGood()) { if (!container.IsGood()) {
return false; return false;
} }
std::vector<std::vector<u8>> container_data; std::vector<std::vector<u8>> container_data;
if (!container.GetIVFCLevel4Data(container_data)) { if (!container.GetIVFCLevel4Data(container_data)) {
return false; return false;
} }
Core::Savegame save(std::move(container_data)); Core::Savegame save(std::move(container_data));
if (!save.IsGood()) { if (!save.IsGood()) {
return false; return false;
} }
return save.Extract(destination.toStdString()); return save.Extract(destination.toStdString());
}); });
} else { } else {
// TODO: Add Progress reporting // TODO: Add Progress reporting
ShowProgressDialog([source = source, destination = destination] { ShowProgressDialog([source = source, destination = destination] {
FileUtil::IOFile file(source.toStdString(), "rb"); FileUtil::IOFile file(source.toStdString(), "rb");
std::vector<u8> data = file.GetData(); std::vector<u8> data = file.GetData();
if (data.empty()) { if (data.empty()) {
return false; return false;
} }
Core::DataContainer container(data); Core::DataContainer container(data);
if (!container.IsGood()) { if (!container.IsGood()) {
return false; return false;
} }
std::vector<std::vector<u8>> container_data; std::vector<std::vector<u8>> container_data;
if (!container.GetIVFCLevel4Data(container_data)) { if (!container.GetIVFCLevel4Data(container_data)) {
return false; return false;
} }
Core::Savegame save(std::move(container_data)); Core::Savegame save(std::move(container_data));
if (!save.IsGood()) { if (!save.IsGood()) {
return false; return false;
} }
return save.Extract(destination.toStdString()); return save.Extract(destination.toStdString());
}); });
} }
} }
void UtilitiesDialog::ExtdataExtractionTool() { void UtilitiesDialog::ExtdataExtractionTool() {
if (!LoadSDKeys()) { if (!LoadSDKeys()) {
return; return;
} }
const auto& [source, destination] = GetFilePaths(true, true); const auto& [source, destination] = GetFilePaths(true, true);
if (source.isEmpty() || destination.isEmpty()) { if (source.isEmpty() || destination.isEmpty()) {
return; return;
} }
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source); const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) { if (!success) {
return; return;
} }
// TODO: Add Progress reporting // TODO: Add Progress reporting
ShowProgressDialog( ShowProgressDialog(
[sdmc_root = sdmc_root, relative_source = relative_source, destination = destination] { [sdmc_root = sdmc_root, relative_source = relative_source, destination = destination] {
Core::SDMCDecryptor decryptor(sdmc_root); Core::SDMCDecryptor decryptor(sdmc_root);
Core::Extdata extdata(relative_source, decryptor); Core::Extdata extdata(relative_source, decryptor);
if (!extdata.IsGood()) { if (!extdata.IsGood()) {
return false; return false;
} }
return extdata.Extract(destination.toStdString()); return extdata.Extract(destination.toStdString());
}); });
} }
void UtilitiesDialog::RomFSExtractionTool() { void UtilitiesDialog::RomFSExtractionTool() {
const auto& [source, destination] = GetFilePaths(false, false); const auto& [source, destination] = GetFilePaths(false, false);
if (source.isEmpty() || destination.isEmpty()) { if (source.isEmpty() || destination.isEmpty()) {
return; return;
} }
ShowProgressDialog([source = source, destination = destination] { ShowProgressDialog([source = source, destination = destination] {
FileUtil::IOFile src_file(source.toStdString(), "rb"); FileUtil::IOFile src_file(source.toStdString(), "rb");
std::vector<u8> data = src_file.GetData(); std::vector<u8> data = src_file.GetData();
if (data.empty()) { if (data.empty()) {
return false; return false;
} }
const auto& shared_romfs = Core::LoadSharedRomFS(data); const auto& shared_romfs = Core::LoadSharedRomFS(data);
return FileUtil::WriteBytesToFile(destination.toStdString(), shared_romfs.data(), return FileUtil::WriteBytesToFile(destination.toStdString(), shared_romfs.data(),
shared_romfs.size()); shared_romfs.size());
}); });
} }
void UtilitiesDialog::ShowResult() { void UtilitiesDialog::ShowResult() {
if (result) { if (result) {
QMessageBox::information(this, tr("Success"), tr("Operation completed successfully.")); QMessageBox::information(this, tr("Success"), tr("Operation completed successfully."));
} else { } else {
QMessageBox::critical(this, tr("Error"), QMessageBox::critical(this, tr("Error"),
tr("An error occured while performing the operation.")); tr("An error occured while performing the operation."));
} }
} }
+49 -49
View File
@@ -1,49 +1,49 @@
// Copyright 2020 threeSD Project // Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <functional> #include <functional>
#include <memory> #include <memory>
#include "frontend/helpers/dpi_aware_dialog.h" #include "frontend/helpers/dpi_aware_dialog.h"
class QWidget; class QWidget;
namespace Ui { namespace Ui {
class UtilitiesDialog; class UtilitiesDialog;
} }
class UtilitiesDialog : public DPIAwareDialog { class UtilitiesDialog : public DPIAwareDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit UtilitiesDialog(QWidget* parent); explicit UtilitiesDialog(QWidget* parent);
~UtilitiesDialog() override; ~UtilitiesDialog() override;
private: private:
/** /**
* Open a dialog to ask the user for source and destination paths. * Open a dialog to ask the user for source and destination paths.
* @return {source, destination} * @return {source, destination}
*/ */
std::pair<QString, QString> GetFilePaths(bool source_is_dir, bool destination_is_dir); std::pair<QString, QString> GetFilePaths(bool source_is_dir, bool destination_is_dir);
bool LoadSDKeys(); bool LoadSDKeys();
void ShowProgressDialog(std::function<bool()> operation); void ShowProgressDialog(std::function<bool()> operation);
/** /**
* Gets SDMC root, and relative source path. * Gets SDMC root, and relative source path.
* @return {success, sdmc root, relative source path} * @return {success, sdmc root, relative source path}
*/ */
std::tuple<bool, std::string, std::string> GetSDMCRoot(const QString& source); std::tuple<bool, std::string, std::string> GetSDMCRoot(const QString& source);
void SDDecryptionTool(); void SDDecryptionTool();
void SaveDataExtractionTool(); void SaveDataExtractionTool();
void ExtdataExtractionTool(); void ExtdataExtractionTool();
void RomFSExtractionTool(); void RomFSExtractionTool();
void ShowResult(); void ShowResult();
bool result = false; /// Result of the last operation. bool result = false; /// Result of the last operation.
std::unique_ptr<Ui::UtilitiesDialog> ui; std::unique_ptr<Ui::UtilitiesDialog> ui;
}; };
+251 -251
View File
@@ -1,251 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>UtilitiesDialog</class> <class>UtilitiesDialog</class>
<widget class="QDialog" name="UtilitiesDialog"> <widget class="QDialog" name="UtilitiesDialog">
<property name="windowTitle"> <property name="windowTitle">
<string>threeSD Utilities</string> <string>threeSD Utilities</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>Encryption</string> <string>Encryption</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<widget class="QCheckBox" name="useSdDecryption"> <widget class="QCheckBox" name="useSdDecryption">
<property name="text"> <property name="text">
<string>Use SD Decryption (check this when your files are directly from SD Card)</string> <string>Use SD Decryption (check this when your files are directly from SD Card)</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QGridLayout"> <layout class="QGridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>boot9.bin</string> <string>boot9.bin</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="boot9Path"/> <widget class="QLineEdit" name="boot9Path"/>
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QToolButton" name="boot9PathExplore"> <widget class="QToolButton" name="boot9PathExplore">
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>movable.sed</string> <string>movable.sed</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="movableSedPath"/> <widget class="QLineEdit" name="movableSedPath"/>
</item> </item>
<item row="1" column="2"> <item row="1" column="2">
<widget class="QToolButton" name="movableSedPathExplore"> <widget class="QToolButton" name="movableSedPathExplore">
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>SDMC Root</string> <string>SDMC Root</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Path to "Nintendo 3DS/&lt;ID0>/&lt;ID1>" folder.</string> <string>Path to "Nintendo 3DS/&lt;ID0>/&lt;ID1>" folder.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLineEdit" name="sdmcPath"/> <widget class="QLineEdit" name="sdmcPath"/>
</item> </item>
<item row="2" column="2"> <item row="2" column="2">
<widget class="QToolButton" name="sdmcPathExplore"> <widget class="QToolButton" name="sdmcPathExplore">
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>SD Decryption</string> <string>SD Decryption</string>
</property> </property>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="sdDecryptionLabel"> <widget class="QLabel" name="sdDecryptionLabel">
<property name="text"> <property name="text">
<string>Decrypt files from your SD Card.</string> <string>Decrypt files from your SD Card.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="sdDecryptionDisabledLabel"> <widget class="QLabel" name="sdDecryptionDisabledLabel">
<property name="text"> <property name="text">
<string>SD Decryption must be enabled to use this tool.</string> <string>SD Decryption must be enabled to use this tool.</string>
</property> </property>
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="sdDecryption"> <widget class="QPushButton" name="sdDecryption">
<property name="text"> <property name="text">
<string>Open...</string> <string>Open...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>Save Data Extraction</string> <string>Save Data Extraction</string>
</property> </property>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="savedataExtractionLabel"> <widget class="QLabel" name="savedataExtractionLabel">
<property name="text"> <property name="text">
<string>Extract 3DS SD Savegames.</string> <string>Extract 3DS SD Savegames.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="savedataExtractionDisabledLabel"> <widget class="QLabel" name="savedataExtractionDisabledLabel">
<property name="text"> <property name="text">
<string>SD Decryption must be enabled to use this tool.</string> <string>SD Decryption must be enabled to use this tool.</string>
</property> </property>
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="savedataExtraction"> <widget class="QPushButton" name="savedataExtraction">
<property name="text"> <property name="text">
<string>Open...</string> <string>Open...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>Extdata Extraction</string> <string>Extdata Extraction</string>
</property> </property>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="extdataExtractionLabel"> <widget class="QLabel" name="extdataExtractionLabel">
<property name="text"> <property name="text">
<string>Extract 3DS Extra Data.</string> <string>Extract 3DS Extra Data.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="extdataExtractionDisabledLabel"> <widget class="QLabel" name="extdataExtractionDisabledLabel">
<property name="text"> <property name="text">
<string>SD Decryption must be enabled to use this tool.</string> <string>SD Decryption must be enabled to use this tool.</string>
</property> </property>
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="extdataExtraction"> <widget class="QPushButton" name="extdataExtraction">
<property name="text"> <property name="text">
<string>Open...</string> <string>Open...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>RomFS Extraction</string> <string>RomFS Extraction</string>
</property> </property>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="romfsExtractionLabel"> <widget class="QLabel" name="romfsExtractionLabel">
<property name="text"> <property name="text">
<string>Extract shared RomFS from NCCH. Useful for System Archives.</string> <string>Extract shared RomFS from NCCH. Useful for System Archives.</string>
</property> </property>
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="romfsExtractionDisabledLabel"> <widget class="QLabel" name="romfsExtractionDisabledLabel">
<property name="text"> <property name="text">
<string>SD Decryption must be disabled to use this tool.</string> <string>SD Decryption must be disabled to use this tool.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="romfsExtraction"> <widget class="QPushButton" name="romfsExtraction">
<property name="text"> <property name="text">
<string>Open...</string> <string>Open...</string>
</property> </property>
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</ui> </ui>