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