mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-07 16:59:07 +00:00
Preliminary work for a title info dialog
pending DRY fixes, etc
This commit is contained in:
@@ -521,6 +521,10 @@ bool SDMCImporter::LoadTMD(ContentType type, u64 id, TitleMetadata& out) const {
|
||||
}
|
||||
}
|
||||
|
||||
bool SDMCImporter::LoadTMD(const ContentSpecifier& specifier, TitleMetadata& out) const {
|
||||
return LoadTMD(specifier.type, specifier.id, out);
|
||||
}
|
||||
|
||||
// English short title name, extdata id, encryption, seed, icon
|
||||
using TitleData = std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>>;
|
||||
|
||||
@@ -659,7 +663,7 @@ void SDMCImporter::AbortDumpCXI() {
|
||||
}
|
||||
|
||||
bool SDMCImporter::CanBuildLegitCIA(const ContentSpecifier& specifier) const {
|
||||
if (!CanBuildCIA(specifier.type)) {
|
||||
if (!IsTitle(specifier.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -683,7 +687,7 @@ bool SDMCImporter::BuildCIA(CIABuildType build_type, const ContentSpecifier& spe
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanBuildCIA(specifier.type)) {
|
||||
if (!IsTitle(specifier.type)) {
|
||||
LOG_ERROR(Core, "Unsupported specifier type {}", static_cast<int>(specifier.type));
|
||||
return false;
|
||||
}
|
||||
|
||||
+4
-3
@@ -36,7 +36,7 @@ enum class ContentType {
|
||||
SystemApplet, // This should belong to System Title, but they cause problems so a new category.
|
||||
};
|
||||
|
||||
constexpr bool CanBuildCIA(ContentType type) {
|
||||
constexpr bool IsTitle(ContentType type) {
|
||||
return type == ContentType::Application || type == ContentType::Update ||
|
||||
type == ContentType::DLC || type == ContentType::SystemTitle ||
|
||||
type == ContentType::SystemApplet;
|
||||
@@ -175,6 +175,9 @@ public:
|
||||
*/
|
||||
bool IsGood() const;
|
||||
|
||||
bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const;
|
||||
bool LoadTMD(const ContentSpecifier& specifier, TitleMetadata& out) const;
|
||||
|
||||
private:
|
||||
bool Init();
|
||||
|
||||
@@ -207,8 +210,6 @@ private:
|
||||
void DeleteSystemArchive(u64 id) const;
|
||||
void DeleteSysdata(u64 id) const;
|
||||
|
||||
bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const;
|
||||
|
||||
bool is_good{};
|
||||
Config config;
|
||||
std::unique_ptr<SDMCDecryptor> sdmc_decryptor;
|
||||
|
||||
@@ -25,6 +25,9 @@ add_executable(threeSD
|
||||
select_files_dialog.cpp
|
||||
select_files_dialog.h
|
||||
select_files_dialog.ui
|
||||
title_info_dialog.cpp
|
||||
title_info_dialog.h
|
||||
title_info_dialog.ui
|
||||
utilities.cpp
|
||||
utilities.h
|
||||
utilities.ui
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "frontend/helpers/multi_job.h"
|
||||
#include "frontend/helpers/simple_job.h"
|
||||
#include "frontend/import_dialog.h"
|
||||
#include "frontend/title_info_dialog.h"
|
||||
#include "ui_import_dialog.h"
|
||||
|
||||
static QString ReadableByteSize(qulonglong size) {
|
||||
@@ -519,10 +520,15 @@ void ImportDialog::OnContextMenu(const QPoint& point) {
|
||||
connect(dump_cxi, &QAction::triggered,
|
||||
[this, specifier] { StartDumpingCXISingle(specifier); });
|
||||
}
|
||||
if (Core::CanBuildCIA(specifier.type)) {
|
||||
if (Core::IsTitle(specifier.type)) {
|
||||
QAction* build_cia = context_menu.addAction(tr("Build CIA..."));
|
||||
connect(build_cia, &QAction::triggered,
|
||||
[this, specifier] { StartBuildingCIASingle(specifier); });
|
||||
QAction* show_title_info = context_menu.addAction(tr("Show Title Info"));
|
||||
connect(show_title_info, &QAction::triggered, [this, specifier] {
|
||||
TitleInfoDialog dialog(this, config, *importer, specifier);
|
||||
dialog.exec();
|
||||
});
|
||||
}
|
||||
} else { // Top level
|
||||
if (!title_view) {
|
||||
@@ -837,7 +843,7 @@ void ImportDialog::StartBatchBuildingCIA() {
|
||||
|
||||
const auto removed_iter = std::remove_if(
|
||||
to_import.begin(), to_import.end(),
|
||||
[](const Core::ContentSpecifier& specifier) { return !Core::CanBuildCIA(specifier.type); });
|
||||
[](const Core::ContentSpecifier& specifier) { return !Core::IsTitle(specifier.type); });
|
||||
if (removed_iter == to_import.begin()) { // No Titles selected
|
||||
QMessageBox::critical(this, tr("threeSD"),
|
||||
tr("The contents selected are not supported.<br>You can only build "
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2021 threeSD Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QDesktopWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QPixmap>
|
||||
#include <fmt/format.h>
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/ncch_container.h"
|
||||
#include "core/file_sys/title_metadata.h"
|
||||
#include "core/importer.h"
|
||||
#include "frontend/title_info_dialog.h"
|
||||
#include "ui_title_info_dialog.h"
|
||||
|
||||
TitleInfoDialog::TitleInfoDialog(QWidget* parent, const Core::Config& config,
|
||||
Core::SDMCImporter& importer,
|
||||
const Core::ContentSpecifier& specifier)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::TitleInfoDialog>()) {
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
const double scale = qApp->desktop()->logicalDpiX() / 96.0;
|
||||
resize(static_cast<int>(width() * scale), static_cast<int>(height() * scale));
|
||||
|
||||
InitializeInfo(config, importer, specifier);
|
||||
InitializeLanguageComboBox();
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept);
|
||||
}
|
||||
|
||||
TitleInfoDialog::~TitleInfoDialog() = default;
|
||||
|
||||
void TitleInfoDialog::InitializeInfo(const Core::Config& config, Core::SDMCImporter& importer,
|
||||
const Core::ContentSpecifier& specifier) {
|
||||
Core::TitleMetadata tmd;
|
||||
if (!importer.LoadTMD(specifier, tmd)) {
|
||||
QMessageBox::warning(this, tr("threeSD"), tr("Could not load title information."));
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
ui->versionLineEdit->setText(QString::fromStdString(tmd.GetTitleVersionString()));
|
||||
ui->titleIDLineEdit->setText(QStringLiteral("%1").arg(specifier.id, 16, 16, QLatin1Char{'0'}));
|
||||
|
||||
const bool is_nand = specifier.type == Core::ContentType::SystemTitle ||
|
||||
specifier.type == Core::ContentType::SystemApplet;
|
||||
const auto physical_path =
|
||||
is_nand ? fmt::format("{}{:08x}/{:08x}/content/", config.system_titles_path,
|
||||
(specifier.id >> 32), (specifier.id & 0xFFFFFFFF))
|
||||
: fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path,
|
||||
(specifier.id >> 32), (specifier.id & 0xFFFFFFFF));
|
||||
const auto boot_content_path =
|
||||
fmt::format("{}{:08x}.app", physical_path, tmd.GetBootContentID());
|
||||
Core::NCCHContainer ncch;
|
||||
if (is_nand) {
|
||||
ncch.OpenFile(std::make_shared<FileUtil::IOFile>(boot_content_path, "rb"));
|
||||
} else {
|
||||
const auto relative_path = boot_content_path.substr(config.sdmc_path.size() - 1);
|
||||
ncch.OpenFile(std::make_shared<Core::SDMCFile>(config.sdmc_path, relative_path, "rb"));
|
||||
}
|
||||
|
||||
static const std::unordered_map<Core::EncryptionType, const char*> EncryptionTypeMap{{
|
||||
{Core::EncryptionType::None, QT_TR_NOOP("None")},
|
||||
{Core::EncryptionType::FixedKey, QT_TR_NOOP("FixedKey")},
|
||||
{Core::EncryptionType::NCCHSecure1, QT_TR_NOOP("Secure1")},
|
||||
{Core::EncryptionType::NCCHSecure2, QT_TR_NOOP("Secure2")},
|
||||
{Core::EncryptionType::NCCHSecure3, QT_TR_NOOP("Secure3")},
|
||||
{Core::EncryptionType::NCCHSecure4, QT_TR_NOOP("Secure4")},
|
||||
}};
|
||||
|
||||
Core::EncryptionType encryption = Core::EncryptionType::None;
|
||||
ncch.ReadEncryptionType(encryption);
|
||||
|
||||
bool seed_crypto = false;
|
||||
ncch.ReadSeedCrypto(seed_crypto);
|
||||
|
||||
QString encryption_text = tr(EncryptionTypeMap.at(encryption));
|
||||
if (seed_crypto) {
|
||||
encryption_text.append(tr(" (Seed)"));
|
||||
}
|
||||
ui->encryptionLineEdit->setText(encryption_text);
|
||||
|
||||
// Load SMDH
|
||||
std::vector<u8> smdh_buffer;
|
||||
if (!ncch.LoadSectionExeFS("icon", smdh_buffer) || smdh_buffer.size() != sizeof(Core::SMDH) ||
|
||||
!Core::IsValidSMDH(smdh_buffer)) {
|
||||
|
||||
LOG_WARNING(Core, "Failed to load SMDH");
|
||||
ui->namesGroupBox->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size());
|
||||
// Load icon
|
||||
ui->iconLargeLabel->setPixmap(
|
||||
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(true).data()), 48, 48,
|
||||
QImage::Format::Format_RGB16)));
|
||||
ui->iconSmallLabel->setPixmap(
|
||||
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(false).data()), 24,
|
||||
24, QImage::Format::Format_RGB16)));
|
||||
}
|
||||
|
||||
void TitleInfoDialog::InitializeLanguageComboBox() {
|
||||
if (!ui->namesGroupBox->isEnabled()) { // SMDH not available
|
||||
return;
|
||||
}
|
||||
// Corresponds to the indices of the languages defined in SMDH
|
||||
static constexpr std::array<const char*, 12> LanguageNames{{
|
||||
QT_TR_NOOP("Japanese"),
|
||||
QT_TR_NOOP("English"),
|
||||
QT_TR_NOOP("French"),
|
||||
QT_TR_NOOP("German"),
|
||||
QT_TR_NOOP("Italian"),
|
||||
QT_TR_NOOP("Spanish"),
|
||||
QT_TR_NOOP("Chinese (Simplified)"),
|
||||
QT_TR_NOOP("Korean"),
|
||||
QT_TR_NOOP("Dutch"),
|
||||
QT_TR_NOOP("Portuguese"),
|
||||
QT_TR_NOOP("Russian"),
|
||||
QT_TR_NOOP("Chinese (Traditional)"),
|
||||
}};
|
||||
for (std::size_t i = 0; i < LanguageNames.size(); ++i) {
|
||||
const auto& title = smdh.titles.at(i);
|
||||
if (Common::UTF16BufferToUTF8(title.short_title).empty() &&
|
||||
Common::UTF16BufferToUTF8(title.long_title).empty() &&
|
||||
Common::UTF16BufferToUTF8(title.publisher).empty()) {
|
||||
// All empty, ignore this language
|
||||
continue;
|
||||
}
|
||||
|
||||
ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast<int>(i));
|
||||
if (i == 1) { // English
|
||||
ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1);
|
||||
}
|
||||
}
|
||||
connect(ui->languageComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&TitleInfoDialog::UpdateNames);
|
||||
UpdateNames();
|
||||
}
|
||||
|
||||
void TitleInfoDialog::UpdateNames() {
|
||||
const auto& title = smdh.titles.at(ui->languageComboBox->currentData().toInt());
|
||||
ui->shortTitleLineEdit->setText(
|
||||
QString::fromStdString(Common::UTF16BufferToUTF8(title.short_title)));
|
||||
ui->longTitleLineEdit->setText(
|
||||
QString::fromStdString(Common::UTF16BufferToUTF8(title.long_title)));
|
||||
ui->publisherLineEdit->setText(
|
||||
QString::fromStdString(Common::UTF16BufferToUTF8(title.publisher)));
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2021 threeSD Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <QDialog>
|
||||
#include "core/file_sys/smdh.h"
|
||||
|
||||
namespace Core {
|
||||
class Config;
|
||||
class ContentSpecifier;
|
||||
class SDMCImporter;
|
||||
} // namespace Core
|
||||
|
||||
namespace Ui {
|
||||
class TitleInfoDialog;
|
||||
}
|
||||
|
||||
class TitleInfoDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TitleInfoDialog(QWidget* parent, const Core::Config& config,
|
||||
Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier);
|
||||
~TitleInfoDialog();
|
||||
|
||||
private:
|
||||
void InitializeInfo(const Core::Config& config, Core::SDMCImporter& importer,
|
||||
const Core::ContentSpecifier& specifier);
|
||||
void InitializeLanguageComboBox();
|
||||
void UpdateNames();
|
||||
|
||||
std::unique_ptr<Ui::TitleInfoDialog> ui;
|
||||
Core::SMDH smdh{};
|
||||
};
|
||||
@@ -0,0 +1,216 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TitleInfoDialog</class>
|
||||
<widget class="QDialog" name="TitleInfoDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>360</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Title Info</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>Info</string>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Version:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="versionLineEdit"/>
|
||||
</item>
|
||||
<item row="0" column="2" rowspan="2">
|
||||
<widget class="QLabel" name="iconLargeLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" rowspan="2">
|
||||
<widget class="QLabel" name="iconSmallLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Encryption:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="encryptionLineEdit"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Title ID:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="titleIDLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="namesGroupBox">
|
||||
<property name="title">
|
||||
<string>Names</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" columnstretch="0,20,10">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Short Title:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="shortTitleLineEdit"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="languageComboBox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Long Title:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="longTitleLineEdit"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Publisher:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="publisherLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>Checks</string>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>TMD:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="tmdCheckLabel">
|
||||
<property name="text">
|
||||
<string>Legit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Ticket:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="ticketCheckLabel">
|
||||
<property name="text">
|
||||
<string>Legit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Contents:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="contentsCheckButton">
|
||||
<property name="text">
|
||||
<string>Check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="contentsCheckLabel">
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Legit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</ui>
|
||||
Reference in New Issue
Block a user