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