Normalize line endings that got messed up for whatever reason

This commit is contained in:
Pengfei
2021-08-24 18:49:11 +08:00
parent adb4325d79
commit 8bcaa0b711
36 changed files with 5213 additions and 5212 deletions
+88 -88
View File
@@ -1,88 +1,88 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
if (POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)
endif()
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*)
add_executable(threeSD
helpers/dpi_aware_dialog.cpp
helpers/dpi_aware_dialog.h
helpers/frontend_common.cpp
helpers/frontend_common.h
helpers/multi_job.cpp
helpers/multi_job.h
helpers/rate_limited_progress_dialog.cpp
helpers/rate_limited_progress_dialog.h
helpers/simple_job.cpp
helpers/simple_job.h
cia_build_dialog.cpp
cia_build_dialog.h
cia_build_dialog.ui
import_dialog.cpp
import_dialog.h
import_dialog.ui
main.cpp
main.h
main.ui
select_files_dialog.cpp
select_files_dialog.h
select_files_dialog.ui
title_info_dialog.cpp
title_info_dialog.h
title_info_dialog.ui
utilities.cpp
utilities.h
utilities.ui
${THEMES}
)
target_link_libraries(threeSD PRIVATE common core)
target_link_libraries(threeSD PRIVATE Qt5::Widgets)
target_link_libraries(threeSD PRIVATE qdevicewatcher)
target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (APPLE)
# set(MACOSX_ICON "../../dist/citra.icns")
# set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# target_sources(threeSD PRIVATE ${MACOSX_ICON})
set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
elseif(WIN32)
# compile as a win32 gui application instead of a console application
target_link_libraries(threeSD PRIVATE Qt5::WinMain)
if(MSVC)
set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
elseif(MINGW)
set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# In Ubuntu, the executable would be recognized as a shared library otherwise.
set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie")
endif()
target_compile_definitions(threeSD PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
-DQT_USE_QSTRINGBUILDER
# Disable implicit type narrowing in signal/slot connect() calls.
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function.
-DQT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
-DQT_NO_URL_CAST_FROM_STRING
# Disable automatic conversions from 8-bit strings (char *) to unicode QStrings
-DQT_NO_CAST_FROM_ASCII
)
if (MSVC)
include(CopyQt5Deps)
copy_Qt5_deps(threeSD)
endif()
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
if (POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)
endif()
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*)
add_executable(threeSD
helpers/dpi_aware_dialog.cpp
helpers/dpi_aware_dialog.h
helpers/frontend_common.cpp
helpers/frontend_common.h
helpers/multi_job.cpp
helpers/multi_job.h
helpers/rate_limited_progress_dialog.cpp
helpers/rate_limited_progress_dialog.h
helpers/simple_job.cpp
helpers/simple_job.h
cia_build_dialog.cpp
cia_build_dialog.h
cia_build_dialog.ui
import_dialog.cpp
import_dialog.h
import_dialog.ui
main.cpp
main.h
main.ui
select_files_dialog.cpp
select_files_dialog.h
select_files_dialog.ui
title_info_dialog.cpp
title_info_dialog.h
title_info_dialog.ui
utilities.cpp
utilities.h
utilities.ui
${THEMES}
)
target_link_libraries(threeSD PRIVATE common core)
target_link_libraries(threeSD PRIVATE Qt5::Widgets)
target_link_libraries(threeSD PRIVATE qdevicewatcher)
target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (APPLE)
# set(MACOSX_ICON "../../dist/citra.icns")
# set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# target_sources(threeSD PRIVATE ${MACOSX_ICON})
set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
elseif(WIN32)
# compile as a win32 gui application instead of a console application
target_link_libraries(threeSD PRIVATE Qt5::WinMain)
if(MSVC)
set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
elseif(MINGW)
set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# In Ubuntu, the executable would be recognized as a shared library otherwise.
set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie")
endif()
target_compile_definitions(threeSD PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
-DQT_USE_QSTRINGBUILDER
# Disable implicit type narrowing in signal/slot connect() calls.
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function.
-DQT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
-DQT_NO_URL_CAST_FROM_STRING
# Disable automatic conversions from 8-bit strings (char *) to unicode QStrings
-DQT_NO_CAST_FROM_ASCII
)
if (MSVC)
include(CopyQt5Deps)
copy_Qt5_deps(threeSD)
endif()
+94 -94
View File
@@ -1,94 +1,94 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopWidget>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
#include "common/assert.h"
#include "frontend/cia_build_dialog.h"
#include "ui_cia_build_dialog.h"
CIABuildDialog::CIABuildDialog(QWidget* parent, bool is_dir_, bool is_nand, bool enable_legit,
const QString& default_path)
: DPIAwareDialog(parent, 510, 260), ui(std::make_unique<Ui::CIABuildDialog>()),
is_dir(is_dir_) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
if (is_dir) {
setWindowTitle(tr("Batch Build CIA"));
}
if (is_nand) {
ui->pirateLegitButton->setVisible(false);
ui->pirateLegitLabel->setVisible(false);
auto message = tr("Encrypted CIA with legit TMD, encrypted contents and legit ticket.<br>");
if (is_dir) {
message.append(tr(
"Legit tickets for these titles do not include console-identifying information."));
} else {
message.append(tr(
"Legit ticket for this title does not include console-identifying information."));
}
ui->legitLabel->setText(message);
}
if (!enable_legit) {
const auto message =
is_dir ? tr("This option is not available as some of the titles are not legit.")
: tr("This option is not available as the title is not legit.");
ui->pirateLegitButton->setEnabled(false);
ui->pirateLegitLabel->setText(message);
ui->legitButton->setEnabled(false);
ui->legitLabel->setText(message);
}
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] {
if (ui->destination->text().isEmpty()) {
const QString message = is_dir ? tr("Please specify destination folder.")
: tr("Please specify destination file.");
QMessageBox::warning(this, tr("threeSD"), message);
return;
}
accept();
});
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &CIABuildDialog::reject);
if (is_dir) {
ui->destination->setText(default_path);
}
connect(ui->destinationExplore, &QToolButton::clicked, [this, default_path] {
QString path;
if (is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Batch Build CIA"),
ui->destination->text());
} else {
const auto cur = ui->destination->text().isEmpty()
? default_path
: QFileInfo(ui->destination->text()).path();
path = QFileDialog::getSaveFileName(this, tr("Build CIA"), cur,
tr("CTR Importable Archive (*.cia)"));
}
if (!path.isEmpty()) {
ui->destination->setText(path);
}
});
}
CIABuildDialog::~CIABuildDialog() = default;
std::pair<QString, Core::CIABuildType> CIABuildDialog::GetResults() const {
Core::CIABuildType type;
if (ui->standardButton->isChecked()) {
type = Core::CIABuildType::Standard;
} else if (ui->pirateLegitButton->isChecked()) {
type = Core::CIABuildType::PirateLegit;
} else if (ui->legitButton->isChecked()) {
type = Core::CIABuildType::Legit;
} else {
UNREACHABLE();
}
return {ui->destination->text(), type};
}
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopWidget>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
#include "common/assert.h"
#include "frontend/cia_build_dialog.h"
#include "ui_cia_build_dialog.h"
CIABuildDialog::CIABuildDialog(QWidget* parent, bool is_dir_, bool is_nand, bool enable_legit,
const QString& default_path)
: DPIAwareDialog(parent, 510, 260), ui(std::make_unique<Ui::CIABuildDialog>()),
is_dir(is_dir_) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
if (is_dir) {
setWindowTitle(tr("Batch Build CIA"));
}
if (is_nand) {
ui->pirateLegitButton->setVisible(false);
ui->pirateLegitLabel->setVisible(false);
auto message = tr("Encrypted CIA with legit TMD, encrypted contents and legit ticket.<br>");
if (is_dir) {
message.append(tr(
"Legit tickets for these titles do not include console-identifying information."));
} else {
message.append(tr(
"Legit ticket for this title does not include console-identifying information."));
}
ui->legitLabel->setText(message);
}
if (!enable_legit) {
const auto message =
is_dir ? tr("This option is not available as some of the titles are not legit.")
: tr("This option is not available as the title is not legit.");
ui->pirateLegitButton->setEnabled(false);
ui->pirateLegitLabel->setText(message);
ui->legitButton->setEnabled(false);
ui->legitLabel->setText(message);
}
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] {
if (ui->destination->text().isEmpty()) {
const QString message = is_dir ? tr("Please specify destination folder.")
: tr("Please specify destination file.");
QMessageBox::warning(this, tr("threeSD"), message);
return;
}
accept();
});
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &CIABuildDialog::reject);
if (is_dir) {
ui->destination->setText(default_path);
}
connect(ui->destinationExplore, &QToolButton::clicked, [this, default_path] {
QString path;
if (is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Batch Build CIA"),
ui->destination->text());
} else {
const auto cur = ui->destination->text().isEmpty()
? default_path
: QFileInfo(ui->destination->text()).path();
path = QFileDialog::getSaveFileName(this, tr("Build CIA"), cur,
tr("CTR Importable Archive (*.cia)"));
}
if (!path.isEmpty()) {
ui->destination->setText(path);
}
});
}
CIABuildDialog::~CIABuildDialog() = default;
std::pair<QString, Core::CIABuildType> CIABuildDialog::GetResults() const {
Core::CIABuildType type;
if (ui->standardButton->isChecked()) {
type = Core::CIABuildType::Standard;
} else if (ui->pirateLegitButton->isChecked()) {
type = Core::CIABuildType::PirateLegit;
} else if (ui->legitButton->isChecked()) {
type = Core::CIABuildType::Legit;
} else {
UNREACHABLE();
}
return {ui->destination->text(), type};
}
+29 -29
View File
@@ -1,29 +1,29 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <utility>
#include "core/file_sys/cia_common.h"
#include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui {
class CIABuildDialog;
}
class CIABuildDialog : public DPIAwareDialog {
Q_OBJECT
public:
explicit CIABuildDialog(QWidget* parent, bool is_dir, bool is_nand, bool enable_legit,
const QString& default_path);
~CIABuildDialog();
std::pair<QString, Core::CIABuildType> GetResults() const;
private:
std::unique_ptr<Ui::CIABuildDialog> ui;
bool is_dir;
};
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <utility>
#include "core/file_sys/cia_common.h"
#include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui {
class CIABuildDialog;
}
class CIABuildDialog : public DPIAwareDialog {
Q_OBJECT
public:
explicit CIABuildDialog(QWidget* parent, bool is_dir, bool is_nand, bool enable_legit,
const QString& default_path);
~CIABuildDialog();
std::pair<QString, Core::CIABuildType> GetResults() const;
private:
std::unique_ptr<Ui::CIABuildDialog> ui;
bool is_dir;
};
+108 -108
View File
@@ -1,108 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CIABuildDialog</class>
<widget class="QDialog" name="CIABuildDialog">
<property name="windowTitle">
<string>Build CIA</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel">
<property name="text">
<string>Destination:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="destination"/>
</item>
<item>
<widget class="QToolButton" name="destinationExplore">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Build Type</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="standardButton">
<property name="text">
<string>Standard</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel">
<property name="text">
<string>Recommended for general use.&lt;br&gt;Decrypted CIA with decrypted contents and standard ticket.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="pirateLegitButton">
<property name="text">
<string>Legit with standard ticket ("pirate legit")</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pirateLegitLabel">
<property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and standard ticket.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="legitButton">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="legitLabel">
<property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and legit ticket.&lt;br&gt;WARNING: Legit ticket may include console identifying information!</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CIABuildDialog</class>
<widget class="QDialog" name="CIABuildDialog">
<property name="windowTitle">
<string>Build CIA</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel">
<property name="text">
<string>Destination:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="destination"/>
</item>
<item>
<widget class="QToolButton" name="destinationExplore">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Build Type</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="standardButton">
<property name="text">
<string>Standard</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel">
<property name="text">
<string>Recommended for general use.&lt;br&gt;Decrypted CIA with decrypted contents and standard ticket.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="pirateLegitButton">
<property name="text">
<string>Legit with standard ticket ("pirate legit")</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pirateLegitLabel">
<property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and standard ticket.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="legitButton">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="legitLabel">
<property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and legit ticket.&lt;br&gt;WARNING: Legit ticket may include console identifying information!</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+69 -69
View File
@@ -1,69 +1,69 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QScreen>
#include <QWindow>
#include "common/logging/log.h"
#include "frontend/helpers/dpi_aware_dialog.h"
DPIAwareDialog::DPIAwareDialog(QWidget* parent, int width, int height)
: QDialog(parent), original_width(width), original_height(height) {}
DPIAwareDialog::~DPIAwareDialog() = default;
void DPIAwareDialog::showEvent(QShowEvent* event) {
QDialog::showEvent(event);
if (window_handle) {
return;
}
// Initialize window_handle and connections
window_handle = windowHandle();
if (!window_handle) {
return;
}
#ifdef __APPLE__
// Note: macOS implements system level virtualization, so there's no need to connect here.
// but we still need to call SetContentSizes() at least once to set up the UI
// macOS style has more padding. Make the dialog larger for compensation.
resize(original_width * 1.25, original_height * 1.25);
SetContentSizes();
#else
resized = false;
connect(window_handle, &QWindow::screenChanged, this, &DPIAwareDialog::OnScreenChanged);
OnScreenChanged();
#endif
}
#ifndef __APPLE__
void DPIAwareDialog::resizeEvent(QResizeEvent* event) {
QDialog::resizeEvent(event);
resized = true;
}
void DPIAwareDialog::OnScreenChanged() {
// Resize according to DPI
const double scaleX = window_handle->screen()->logicalDotsPerInchX() / 96.0;
const double scaleY = window_handle->screen()->logicalDotsPerInchY() / 96.0;
if (resized) {
const int new_width = static_cast<int>(scaleX * width() / previous_scaleX);
const int new_height = static_cast<int>(scaleY * height() / previous_scaleY);
setMinimumSize(0, 0); // Enforce this resize
resize(new_width, new_height);
} else {
const int new_width = static_cast<int>(original_width * scaleX);
const int new_height = static_cast<int>(original_height * scaleY);
setMinimumSize(new_width, new_height);
adjustSize();
resized = false; // This resize isn't user-initiated
}
SetContentSizes(previous_width, previous_height);
previous_scaleX = scaleX;
previous_scaleY = scaleY;
previous_width = width();
previous_height = height();
}
#endif
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QScreen>
#include <QWindow>
#include "common/logging/log.h"
#include "frontend/helpers/dpi_aware_dialog.h"
DPIAwareDialog::DPIAwareDialog(QWidget* parent, int width, int height)
: QDialog(parent), original_width(width), original_height(height) {}
DPIAwareDialog::~DPIAwareDialog() = default;
void DPIAwareDialog::showEvent(QShowEvent* event) {
QDialog::showEvent(event);
if (window_handle) {
return;
}
// Initialize window_handle and connections
window_handle = windowHandle();
if (!window_handle) {
return;
}
#ifdef __APPLE__
// Note: macOS implements system level virtualization, so there's no need to connect here.
// but we still need to call SetContentSizes() at least once to set up the UI
// macOS style has more padding. Make the dialog larger for compensation.
resize(original_width * 1.25, original_height * 1.25);
SetContentSizes();
#else
resized = false;
connect(window_handle, &QWindow::screenChanged, this, &DPIAwareDialog::OnScreenChanged);
OnScreenChanged();
#endif
}
#ifndef __APPLE__
void DPIAwareDialog::resizeEvent(QResizeEvent* event) {
QDialog::resizeEvent(event);
resized = true;
}
void DPIAwareDialog::OnScreenChanged() {
// Resize according to DPI
const double scaleX = window_handle->screen()->logicalDotsPerInchX() / 96.0;
const double scaleY = window_handle->screen()->logicalDotsPerInchY() / 96.0;
if (resized) {
const int new_width = static_cast<int>(scaleX * width() / previous_scaleX);
const int new_height = static_cast<int>(scaleY * height() / previous_scaleY);
setMinimumSize(0, 0); // Enforce this resize
resize(new_width, new_height);
} else {
const int new_width = static_cast<int>(original_width * scaleX);
const int new_height = static_cast<int>(original_height * scaleY);
setMinimumSize(new_width, new_height);
adjustSize();
resized = false; // This resize isn't user-initiated
}
SetContentSizes(previous_width, previous_height);
previous_scaleX = scaleX;
previous_scaleY = scaleY;
previous_width = width();
previous_height = height();
}
#endif
+39 -39
View File
@@ -1,39 +1,39 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
class DPIAwareDialog : public QDialog {
public:
explicit DPIAwareDialog(QWidget* parent, int width, int height);
~DPIAwareDialog() override;
protected:
void showEvent(QShowEvent* event) override;
// Called with two zeroes to set up content sizes that are relative to dialog size. Also called
// when screen is changed, to update those sizes.
virtual void SetContentSizes(int previous_width = 0, int previous_height = 0){};
private:
QWindow* window_handle{};
const int original_width{};
const int original_height{};
#ifndef __APPLE__
protected:
void resizeEvent(QResizeEvent* event) override;
private:
void OnScreenChanged();
bool resized = false; // whether this dialog has been manually resized
double previous_scaleX{};
double previous_scaleY{};
int previous_width{};
int previous_height{};
#endif
};
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
class DPIAwareDialog : public QDialog {
public:
explicit DPIAwareDialog(QWidget* parent, int width, int height);
~DPIAwareDialog() override;
protected:
void showEvent(QShowEvent* event) override;
// Called with two zeroes to set up content sizes that are relative to dialog size. Also called
// when screen is changed, to update those sizes.
virtual void SetContentSizes(int previous_width = 0, int previous_height = 0){};
private:
QWindow* window_handle{};
const int original_width{};
const int original_height{};
#ifndef __APPLE__
protected:
void resizeEvent(QResizeEvent* event) override;
private:
void OnScreenChanged();
bool resized = false; // whether this dialog has been manually resized
double previous_scaleX{};
double previous_scaleY{};
int previous_width{};
int previous_height{};
#endif
};
+21 -21
View File
@@ -1,21 +1,21 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cmath>
#include <QObject>
#include "frontend/helpers/frontend_common.h"
QString ReadableByteSize(qulonglong size) {
static const std::array<const char*, 6> units = {QT_TR_NOOP("B"), QT_TR_NOOP("KiB"),
QT_TR_NOOP("MiB"), QT_TR_NOOP("GiB"),
QT_TR_NOOP("TiB"), QT_TR_NOOP("PiB")};
if (size == 0)
return QStringLiteral("0");
int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)),
static_cast<int>(units.size() - 1));
return QStringLiteral("%L1 %2")
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
.arg(QObject::tr(units[digit_groups], "FrontendCommon"));
}
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cmath>
#include <QObject>
#include "frontend/helpers/frontend_common.h"
QString ReadableByteSize(qulonglong size) {
static const std::array<const char*, 6> units = {QT_TR_NOOP("B"), QT_TR_NOOP("KiB"),
QT_TR_NOOP("MiB"), QT_TR_NOOP("GiB"),
QT_TR_NOOP("TiB"), QT_TR_NOOP("PiB")};
if (size == 0)
return QStringLiteral("0");
int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)),
static_cast<int>(units.size() - 1));
return QStringLiteral("%L1 %2")
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
.arg(QObject::tr(units[digit_groups], "FrontendCommon"));
}
+9 -9
View File
@@ -1,9 +1,9 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QString>
QString ReadableByteSize(qulonglong size);
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QString>
QString ReadableByteSize(qulonglong size);
+67 -67
View File
@@ -1,67 +1,67 @@
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include "frontend/helpers/multi_job.h"
MultiJob::MultiJob(QObject* parent, Core::SDMCImporter& importer_,
std::vector<Core::ContentSpecifier> contents_, ExecuteFunc execute_func_,
AbortFunc abort_func_)
: QThread(parent), importer(importer_), contents(std::move(contents_)),
execute_func(std::move(execute_func_)), abort_func(abort_func_) {}
MultiJob::~MultiJob() = default;
void MultiJob::run() {
u64 total_size = 0;
for (const auto& content : contents) {
total_size += content.maximum_size;
}
std::size_t count = 0;
int eta = -1;
const auto initial_time = std::chrono::steady_clock::now();
const auto UpdateETA = [total_size, &eta, initial_time](u64 size_imported) {
if (size_imported < 10 * 1024 * 1024) { // 10M Threshold
return;
}
using namespace std::chrono;
const u64 time_elapsed =
duration_cast<milliseconds>(steady_clock::now() - initial_time).count();
eta =
static_cast<int>(time_elapsed * (total_size - size_imported) / (size_imported) / 1000);
};
const auto Callback = [this, &eta, &UpdateETA](u64 current_imported_size,
u64 total_imported_size, u64 /*total_size*/) {
UpdateETA(total_imported_size);
emit ProgressUpdated(current_imported_size, total_imported_size, eta);
};
Common::ProgressCallbackWrapper wrapper{total_size};
for (const auto& content : contents) {
emit NextContent(count + 1, wrapper.current_done_size + wrapper.current_pending_size,
content, eta);
if (!execute_func(importer, content, wrapper.Wrap(Callback))) {
if (!cancelled) {
failed_contents.emplace_back(content);
}
}
count++;
if (cancelled) {
break;
}
}
emit Completed();
}
void MultiJob::Cancel() {
cancelled.store(true);
abort_func(importer);
}
std::vector<Core::ContentSpecifier> MultiJob::GetFailedContents() const {
return failed_contents;
}
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include "frontend/helpers/multi_job.h"
MultiJob::MultiJob(QObject* parent, Core::SDMCImporter& importer_,
std::vector<Core::ContentSpecifier> contents_, ExecuteFunc execute_func_,
AbortFunc abort_func_)
: QThread(parent), importer(importer_), contents(std::move(contents_)),
execute_func(std::move(execute_func_)), abort_func(abort_func_) {}
MultiJob::~MultiJob() = default;
void MultiJob::run() {
u64 total_size = 0;
for (const auto& content : contents) {
total_size += content.maximum_size;
}
std::size_t count = 0;
int eta = -1;
const auto initial_time = std::chrono::steady_clock::now();
const auto UpdateETA = [total_size, &eta, initial_time](u64 size_imported) {
if (size_imported < 10 * 1024 * 1024) { // 10M Threshold
return;
}
using namespace std::chrono;
const u64 time_elapsed =
duration_cast<milliseconds>(steady_clock::now() - initial_time).count();
eta =
static_cast<int>(time_elapsed * (total_size - size_imported) / (size_imported) / 1000);
};
const auto Callback = [this, &eta, &UpdateETA](u64 current_imported_size,
u64 total_imported_size, u64 /*total_size*/) {
UpdateETA(total_imported_size);
emit ProgressUpdated(current_imported_size, total_imported_size, eta);
};
Common::ProgressCallbackWrapper wrapper{total_size};
for (const auto& content : contents) {
emit NextContent(count + 1, wrapper.current_done_size + wrapper.current_pending_size,
content, eta);
if (!execute_func(importer, content, wrapper.Wrap(Callback))) {
if (!cancelled) {
failed_contents.emplace_back(content);
}
}
count++;
if (cancelled) {
break;
}
}
emit Completed();
}
void MultiJob::Cancel() {
cancelled.store(true);
abort_func(importer);
}
std::vector<Core::ContentSpecifier> MultiJob::GetFailedContents() const {
return failed_contents;
}
+56 -56
View File
@@ -1,56 +1,56 @@
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <functional>
#include <QThread>
#include "common/progress_callback.h"
#include "core/importer.h"
class MultiJob : public QThread {
Q_OBJECT
public:
using ExecuteFunc = std::function<bool(Core::SDMCImporter&, const Core::ContentSpecifier&,
const Common::ProgressCallback&)>;
using AbortFunc = std::function<void(Core::SDMCImporter&)>;
explicit MultiJob(QObject* parent, Core::SDMCImporter& importer,
std::vector<Core::ContentSpecifier> contents, ExecuteFunc execute_func,
AbortFunc abort_func);
~MultiJob() override;
void run() override;
void Cancel();
std::vector<Core::ContentSpecifier> GetFailedContents() const;
signals:
/**
* Called when progress is updated on the current content.
* @param current_imported_size Imported size of the current content.
* @param total_imported_size Total imported size taking all previous contents into
* consideration.
* @param eta ETA in seconds, 0 when not determined.
*/
void ProgressUpdated(u64 current_imported_size, u64 total_imported_size, int eta);
/// Dumping of a content has been finished, go on to the next. Called at start as well.
void NextContent(std::size_t count, u64 total_imported_size,
const Core::ContentSpecifier& next_content, int eta);
void Completed();
private:
std::atomic_bool cancelled{false};
Core::SDMCImporter& importer;
std::vector<Core::ContentSpecifier> contents;
std::vector<Core::ContentSpecifier> failed_contents;
ExecuteFunc execute_func;
AbortFunc abort_func;
};
Q_DECLARE_METATYPE(Core::ContentSpecifier)
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <functional>
#include <QThread>
#include "common/progress_callback.h"
#include "core/importer.h"
class MultiJob : public QThread {
Q_OBJECT
public:
using ExecuteFunc = std::function<bool(Core::SDMCImporter&, const Core::ContentSpecifier&,
const Common::ProgressCallback&)>;
using AbortFunc = std::function<void(Core::SDMCImporter&)>;
explicit MultiJob(QObject* parent, Core::SDMCImporter& importer,
std::vector<Core::ContentSpecifier> contents, ExecuteFunc execute_func,
AbortFunc abort_func);
~MultiJob() override;
void run() override;
void Cancel();
std::vector<Core::ContentSpecifier> GetFailedContents() const;
signals:
/**
* Called when progress is updated on the current content.
* @param current_imported_size Imported size of the current content.
* @param total_imported_size Total imported size taking all previous contents into
* consideration.
* @param eta ETA in seconds, 0 when not determined.
*/
void ProgressUpdated(u64 current_imported_size, u64 total_imported_size, int eta);
/// Dumping of a content has been finished, go on to the next. Called at start as well.
void NextContent(std::size_t count, u64 total_imported_size,
const Core::ContentSpecifier& next_content, int eta);
void Completed();
private:
std::atomic_bool cancelled{false};
Core::SDMCImporter& importer;
std::vector<Core::ContentSpecifier> contents;
std::vector<Core::ContentSpecifier> failed_contents;
ExecuteFunc execute_func;
AbortFunc abort_func;
};
Q_DECLARE_METATYPE(Core::ContentSpecifier)
@@ -1,34 +1,34 @@
// Copyright 2021 Pengfei Zhu
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "frontend/helpers/rate_limited_progress_dialog.h"
RateLimitedProgressDialog::RateLimitedProgressDialog(const QString& label_text,
const QString& cancel_button_text, int minimum,
int maximum, QWidget* parent)
: QProgressDialog(label_text, cancel_button_text, minimum, maximum, parent) {
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
setWindowModality(Qt::WindowModal);
setMinimumDuration(0);
setValue(0);
}
RateLimitedProgressDialog::~RateLimitedProgressDialog() = default;
void RateLimitedProgressDialog::Update(int progress, const QString& label_text) {
if (progress == maximum()) { // always set the maximum
setValue(progress);
return;
}
const auto current_time = std::chrono::steady_clock::now();
if (current_time - last_update_time < MinimumInterval) {
return;
}
setValue(progress);
setLabelText(label_text);
last_update_time = current_time;
}
// Copyright 2021 Pengfei Zhu
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "frontend/helpers/rate_limited_progress_dialog.h"
RateLimitedProgressDialog::RateLimitedProgressDialog(const QString& label_text,
const QString& cancel_button_text, int minimum,
int maximum, QWidget* parent)
: QProgressDialog(label_text, cancel_button_text, minimum, maximum, parent) {
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
setWindowModality(Qt::WindowModal);
setMinimumDuration(0);
setValue(0);
}
RateLimitedProgressDialog::~RateLimitedProgressDialog() = default;
void RateLimitedProgressDialog::Update(int progress, const QString& label_text) {
if (progress == maximum()) { // always set the maximum
setValue(progress);
return;
}
const auto current_time = std::chrono::steady_clock::now();
if (current_time - last_update_time < MinimumInterval) {
return;
}
setValue(progress);
setLabelText(label_text);
last_update_time = current_time;
}
@@ -1,21 +1,21 @@
// Copyright 2021 Pengfei Zhu
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <chrono>
#include <QProgressDialog>
class RateLimitedProgressDialog : public QProgressDialog {
public:
explicit RateLimitedProgressDialog(const QString& label_text, const QString& cancel_button_text,
int minimum, int maximum, QWidget* parent = nullptr);
~RateLimitedProgressDialog() override;
void Update(int progress, const QString& label_text);
private:
std::chrono::steady_clock::time_point last_update_time = std::chrono::steady_clock::now();
static constexpr auto MinimumInterval = std::chrono::milliseconds{100};
};
// Copyright 2021 Pengfei Zhu
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <chrono>
#include <QProgressDialog>
class RateLimitedProgressDialog : public QProgressDialog {
public:
explicit RateLimitedProgressDialog(const QString& label_text, const QString& cancel_button_text,
int minimum, int maximum, QWidget* parent = nullptr);
~RateLimitedProgressDialog() override;
void Update(int progress, const QString& label_text);
private:
std::chrono::steady_clock::time_point last_update_time = std::chrono::steady_clock::now();
static constexpr auto MinimumInterval = std::chrono::milliseconds{100};
};
+54 -54
View File
@@ -1,54 +1,54 @@
// Copyright 2020 Pengfei Zhu
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QMessageBox>
#include "frontend/helpers/frontend_common.h"
#include "frontend/helpers/rate_limited_progress_dialog.h"
#include "frontend/helpers/simple_job.h"
SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_)
: QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {}
SimpleJob::~SimpleJob() = default;
void SimpleJob::run() {
const bool ret =
execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); });
if (ret || canceled) {
emit Completed(canceled);
} else {
emit ErrorOccured();
}
}
void SimpleJob::Cancel() {
canceled = true;
abort();
}
void SimpleJob::StartWithProgressDialog(QWidget* widget) {
auto* dialog = new RateLimitedProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, widget);
connect(this, &SimpleJob::ProgressUpdated, this, [dialog](u64 current, u64 total) {
if (dialog->wasCanceled()) {
return;
}
// Try to map total to int range
// This is equal to ceil(total / INT_MAX)
const u64 multiplier =
(total + std::numeric_limits<int>::max() - 1) / std::numeric_limits<int>::max();
dialog->setMaximum(static_cast<int>(total / multiplier));
dialog->Update(static_cast<int>(current / multiplier),
tr("%1 / %2").arg(ReadableByteSize(current), ReadableByteSize(total)));
});
connect(this, &SimpleJob::ErrorOccured, this, [widget, dialog] {
QMessageBox::critical(widget, tr("threeSD"),
tr("Operation failed. Please refer to the log."));
dialog->hide();
});
connect(this, &SimpleJob::Completed, dialog, &QProgressDialog::hide);
connect(dialog, &QProgressDialog::canceled, this, &SimpleJob::Cancel);
start();
}
// Copyright 2020 Pengfei Zhu
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QMessageBox>
#include "frontend/helpers/frontend_common.h"
#include "frontend/helpers/rate_limited_progress_dialog.h"
#include "frontend/helpers/simple_job.h"
SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_)
: QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {}
SimpleJob::~SimpleJob() = default;
void SimpleJob::run() {
const bool ret =
execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); });
if (ret || canceled) {
emit Completed(canceled);
} else {
emit ErrorOccured();
}
}
void SimpleJob::Cancel() {
canceled = true;
abort();
}
void SimpleJob::StartWithProgressDialog(QWidget* widget) {
auto* dialog = new RateLimitedProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, widget);
connect(this, &SimpleJob::ProgressUpdated, this, [dialog](u64 current, u64 total) {
if (dialog->wasCanceled()) {
return;
}
// Try to map total to int range
// This is equal to ceil(total / INT_MAX)
const u64 multiplier =
(total + std::numeric_limits<int>::max() - 1) / std::numeric_limits<int>::max();
dialog->setMaximum(static_cast<int>(total / multiplier));
dialog->Update(static_cast<int>(current / multiplier),
tr("%1 / %2").arg(ReadableByteSize(current), ReadableByteSize(total)));
});
connect(this, &SimpleJob::ErrorOccured, this, [widget, dialog] {
QMessageBox::critical(widget, tr("threeSD"),
tr("Operation failed. Please refer to the log."));
dialog->hide();
});
connect(this, &SimpleJob::Completed, dialog, &QProgressDialog::hide);
connect(dialog, &QProgressDialog::canceled, this, &SimpleJob::Cancel);
start();
}
+39 -39
View File
@@ -1,39 +1,39 @@
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <QThread>
#include "common/common_types.h"
#include "common/progress_callback.h"
/**
* Lightweight wrapper around QThread, for easy use with progressive jobs.
*/
class SimpleJob : public QThread {
Q_OBJECT
public:
using ExecuteFunc = std::function<bool(const Common::ProgressCallback&)>;
using AbortFunc = std::function<void()>;
explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort);
~SimpleJob() override;
void run() override;
void Cancel();
void StartWithProgressDialog(QWidget* widget);
signals:
void ProgressUpdated(u64 current, u64 total);
void Completed(bool canceled);
void ErrorOccured();
private:
ExecuteFunc execute;
AbortFunc abort;
bool canceled{};
};
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <QThread>
#include "common/common_types.h"
#include "common/progress_callback.h"
/**
* Lightweight wrapper around QThread, for easy use with progressive jobs.
*/
class SimpleJob : public QThread {
Q_OBJECT
public:
using ExecuteFunc = std::function<bool(const Common::ProgressCallback&)>;
using AbortFunc = std::function<void()>;
explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort);
~SimpleJob() override;
void run() override;
void Cancel();
void StartWithProgressDialog(QWidget* widget);
signals:
void ProgressUpdated(u64 current, u64 total);
void Completed(bool canceled);
void ErrorOccured();
private:
ExecuteFunc execute;
AbortFunc abort;
bool canceled{};
};
File diff suppressed because it is too large Load Diff
+87 -87
View File
@@ -1,87 +1,87 @@
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <QPixmap>
#include "core/file_sys/ncch_container.h"
#include "core/importer.h"
#include "helpers/dpi_aware_dialog.h"
class AdvancedMenu;
class MultiJob;
class SimpleJob;
class QTreeWidgetItem;
namespace Ui {
class ImportDialog;
}
class ImportDialog final : public DPIAwareDialog {
Q_OBJECT
public:
explicit ImportDialog(QWidget* parent, const Core::Config& config);
~ImportDialog() override;
private:
void SetContentSizes(int previous_width, int previous_height) override;
void RelistContent();
void RepopulateContent();
void UpdateSizeDisplay();
std::vector<Core::ContentSpecifier> GetSelectedContentList();
void InsertTopLevelItem(QString text, QPixmap icon = {});
void InsertTopLevelItem(QString text, QPixmap icon, u64 total_size, QString exists);
// When replace_name and replace_icon are present they are used instead of those in `content`.
void InsertSecondLevelItem(std::size_t row, const Core::ContentSpecifier& content,
std::size_t id, QString replace_name = {},
QPixmap replace_icon = {});
Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const;
void OnContextMenu(const QPoint& point);
void ShowAdvancedMenu();
void OnItemChanged(QTreeWidgetItem* item, int column);
void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size);
void StartImporting();
void StartDumpingCXISingle(const Core::ContentSpecifier& content);
QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle
void StartBatchDumpingCXI();
QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI
void StartBuildingCIASingle(const Core::ContentSpecifier& content);
QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle
void StartBatchBuildingCIA();
QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA
std::unique_ptr<Ui::ImportDialog> ui;
std::unique_ptr<Core::SDMCImporter> importer;
const Core::Config config;
std::vector<Core::ContentSpecifier> contents;
u64 total_selected_size = 0;
// HACK: Block advanced menu trigger once.
bool block_advanced_menu = false;
friend class AdvancedMenu;
// Whether the System Archive / System Data warning has been shown
bool system_warning_shown = false;
// Whether the Applets warning has been shown
bool applet_warning_shown = false;
// TODO: Why this won't work as locals?
Core::ContentSpecifier current_content = {};
std::size_t current_count = 0;
};
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <QPixmap>
#include "core/file_sys/ncch_container.h"
#include "core/importer.h"
#include "helpers/dpi_aware_dialog.h"
class AdvancedMenu;
class MultiJob;
class SimpleJob;
class QTreeWidgetItem;
namespace Ui {
class ImportDialog;
}
class ImportDialog final : public DPIAwareDialog {
Q_OBJECT
public:
explicit ImportDialog(QWidget* parent, const Core::Config& config);
~ImportDialog() override;
private:
void SetContentSizes(int previous_width, int previous_height) override;
void RelistContent();
void RepopulateContent();
void UpdateSizeDisplay();
std::vector<Core::ContentSpecifier> GetSelectedContentList();
void InsertTopLevelItem(QString text, QPixmap icon = {});
void InsertTopLevelItem(QString text, QPixmap icon, u64 total_size, QString exists);
// When replace_name and replace_icon are present they are used instead of those in `content`.
void InsertSecondLevelItem(std::size_t row, const Core::ContentSpecifier& content,
std::size_t id, QString replace_name = {},
QPixmap replace_icon = {});
Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const;
void OnContextMenu(const QPoint& point);
void ShowAdvancedMenu();
void OnItemChanged(QTreeWidgetItem* item, int column);
void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size);
void StartImporting();
void StartDumpingCXISingle(const Core::ContentSpecifier& content);
QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle
void StartBatchDumpingCXI();
QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI
void StartBuildingCIASingle(const Core::ContentSpecifier& content);
QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle
void StartBatchBuildingCIA();
QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA
std::unique_ptr<Ui::ImportDialog> ui;
std::unique_ptr<Core::SDMCImporter> importer;
const Core::Config config;
std::vector<Core::ContentSpecifier> contents;
u64 total_selected_size = 0;
// HACK: Block advanced menu trigger once.
bool block_advanced_menu = false;
friend class AdvancedMenu;
// Whether the System Archive / System Data warning has been shown
bool system_warning_shown = false;
// Whether the Applets warning has been shown
bool applet_warning_shown = false;
// TODO: Why this won't work as locals?
Core::ContentSpecifier current_content = {};
std::size_t current_count = 0;
};
+99 -99
View File
@@ -1,99 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImportDialog</class>
<widget class="QDialog" name="ImportDialog">
<property name="windowTitle">
<string>Select Contents</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QPushButton" name="advanced_button">
<property name="text">
<string>Advanced...</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QRadioButton" name="title_view_button">
<property name="text">
<string>Title View</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="group_view_button">
<property name="text">
<string>Group View</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="main">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
<column>
<property name="text">
<string>Exists</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="availableSpace">
<property name="text">
<string>Available Space:</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="totalSize">
<property name="text">
<string>Total Size:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImportDialog</class>
<widget class="QDialog" name="ImportDialog">
<property name="windowTitle">
<string>Select Contents</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QPushButton" name="advanced_button">
<property name="text">
<string>Advanced...</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QRadioButton" name="title_view_button">
<property name="text">
<string>Title View</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="group_view_button">
<property name="text">
<string>Group View</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="main">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
<column>
<property name="text">
<string>Exists</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="availableSpace">
<property name="text">
<string>Available Space:</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="totalSize">
<property name="text">
<string>Total Size:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+258 -258
View File
@@ -1,258 +1,258 @@
// Copyright 2014 Citra Emulator Project / 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <regex>
#include <string>
#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QStorageInfo>
#include <QTreeWidgetItem>
#include <qdevicewatcher.h>
#include "common/assert.h"
#include "common/file_util.h"
#include "frontend/import_dialog.h"
#include "frontend/main.h"
#include "frontend/utilities.h"
#include "ui_main.h"
#ifdef __APPLE__
#include <unistd.h>
#include "common/common_paths.h"
#endif
#ifdef QT_STATICPLUGIN
#include <QtPlugin>
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin)
#endif
#endif
bool IsConfigGood(const Core::Config& config) {
return !config.sdmc_path.empty() && !config.user_path.empty() &&
!config.movable_sed_path.empty() && !config.bootrom_path.empty();
}
MainDialog::MainDialog(QWidget* parent)
: DPIAwareDialog(parent, 640, 256), ui(std::make_unique<Ui::MainDialog>()) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(false);
ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)->setText(tr("Refresh"));
LoadPresetConfig();
connect(ui->utilitiesButton, &QPushButton::clicked, [this] {
UtilitiesDialog dialog(this);
dialog.exec();
});
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) {
if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)) {
LoadPresetConfig();
} else if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) {
LaunchImportDialog();
}
});
QString destination_text{};
const auto destination = FileUtil::GetUserPathType();
if (destination == FileUtil::UserPathType::Normal) {
#ifdef __linux__
destination_text = tr("Non-Flatpak Citra Install");
#else
destination_text = tr("User-wide Citra Install");
#endif
} else if (destination == FileUtil::UserPathType::Portable) {
destination_text = tr("Portable Citra Install");
} else if (destination == FileUtil::UserPathType::Flatpak) {
destination_text = tr("Flatpak Citra Install");
} else {
UNREACHABLE();
}
ui->importDestination->setText(tr("Import Destination: %1").arg(destination_text));
connect(ui->main, &QTreeWidget::itemSelectionChanged, [this] {
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)
->setEnabled(!ui->main->selectedItems().empty());
});
connect(ui->main, &QTreeWidget::itemDoubleClicked, [this] {
if (!ui->main->selectedItems().empty())
LaunchImportDialog();
});
ui->main->setIndentation(4);
// Set up device watcher
auto* device_watcher = new QDeviceWatcher(this);
device_watcher->start();
connect(device_watcher, &QDeviceWatcher::deviceAdded, this, &MainDialog::LoadPresetConfig);
connect(device_watcher, &QDeviceWatcher::deviceChanged, this, &MainDialog::LoadPresetConfig);
connect(device_watcher, &QDeviceWatcher::deviceRemoved, this, &MainDialog::LoadPresetConfig);
}
MainDialog::~MainDialog() = default;
void MainDialog::SetContentSizes(int previous_width, int previous_height) {
const int current_width = width();
if (previous_width == 0) { // first time
ui->main->setColumnWidth(0, 0.3 * current_width);
ui->main->setColumnWidth(1, 0.4 * current_width);
} else { // proportionally update column widths
for (int i : {0, 1}) {
ui->main->setColumnWidth(i, ui->main->columnWidth(i) * current_width / previous_width);
}
}
}
static const std::regex sdmc_path_regex{"(.+)([/\\\\])Nintendo 3DS/([0-9a-f]{32})/([0-9a-f]{32})/"};
void MainDialog::LoadPresetConfig() {
ui->main->clear();
preset_config_list.clear();
for (const auto& storage : QStorageInfo::mountedVolumes()) {
if (!storage.isValid() || !storage.isReady()) {
continue;
}
auto list = Core::LoadPresetConfig(storage.rootPath().toStdString());
for (std::size_t i = 0; i < list.size(); ++i) {
preset_config_list.emplace_back(list[i]);
QString path = storage.rootPath();
if (path.endsWith(QLatin1Char{'/'}) || path.endsWith(QLatin1Char{'\\'})) {
path.remove(path.size() - 1, 1);
}
// Get ID0
QString id0 = tr("Unknown");
std::smatch match;
if (std::regex_match(list[i].sdmc_path, match, sdmc_path_regex)) {
if (match.size() >= 5) {
id0 = QString::fromStdString(match[3].str());
}
}
// Get status
QString status = tr("Good");
if (!IsConfigGood(list[i])) {
status = tr("No Configuration Found");
} else if (list[i].version != Core::CurrentDumperVersion) {
status = tr("Version Dismatch");
} else if (list[i].safe_mode_firm_path.empty() ||
list[i].config_savegame_path.empty() ||
list[i].system_archives_path.empty()) {
status = tr("Missing System Files");
} else if (list[i].seed_db_path.empty()) {
status = tr("Good, Missing Seeds");
}
auto* item = new QTreeWidgetItem{{path, id0, status}};
ui->main->invisibleRootItem()->addChild(item);
}
}
auto* item = new QTreeWidgetItem{{tr("Browse SD Card Root...")}};
item->setFirstColumnSpanned(true);
ui->main->invisibleRootItem()->addChild(item);
}
void MainDialog::LaunchImportDialog() {
auto* item = ui->main->currentItem();
if (!item) {
return;
}
Core::Config config;
const auto index = ui->main->invisibleRootItem()->indexOfChild(item);
if (index == ui->main->invisibleRootItem()->childCount() - 1) {
const QString path = QFileDialog::getExistingDirectory(this, tr("Select SD Card Root"));
if (path.isEmpty()) {
return;
}
const auto& list = Core::LoadPresetConfig(path.toStdString());
if (list.size() > 1) {
QMessageBox::information(
this, tr("threeSD"),
tr("You have more than one 3DS data on your SD Card.\nthreeSD will "
"select the first one for you."));
} else if (list.empty() || !IsConfigGood(list[0])) {
QMessageBox::critical(
this, tr("Error"),
tr("Could not load configuration.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>"
"guide</a> correctly."));
return;
}
config = list[0];
} else {
config = preset_config_list.at(index);
}
// Check config integrity
if (!IsConfigGood(config)) {
QMessageBox::critical(
this, tr("Error"),
tr("Could not load configuration from this SD card. You need to prepare your SD card "
"before using threeSD.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>"
"guide</a> correctly."));
return;
}
if (config.version != Core::CurrentDumperVersion) {
QMessageBox::critical(this, tr("Version Dismatch"),
tr("You are using an unsupported version of threeSDumper.<br>Please "
"ensure that you are using the most recent version of both "
"threeSD and threeSDumper and try again."));
return;
}
if (config.safe_mode_firm_path.empty() || config.config_savegame_path.empty() ||
config.system_archives_path.empty()) {
QMessageBox::warning(
this, tr("Warning"),
tr("Certain system files are missing from your configuration.<br>Some contents "
"may not be importable, or may not run.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>guide</a> "
"correctly."));
} else if (config.seed_db_path.empty()) {
QMessageBox::warning(this, tr("Warning"),
tr("Seed database is missing from your configuration.<br>Your system "
"likely does not have any seeds.<br>However, if it does have any, "
"imported games using seed encryption may not work."));
}
ImportDialog dialog(this, config);
dialog.exec();
}
int main(int argc, char* argv[]) {
// Init settings params
QCoreApplication::setOrganizationName(QStringLiteral("zhaowenlan1779"));
QCoreApplication::setApplicationName(QStringLiteral("threeSD"));
#ifdef __APPLE__
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
chdir(bin_path.c_str());
#endif
QApplication app(argc, argv);
QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons/default")));
QIcon::setThemeName(QStringLiteral(":/icons/default"));
MainDialog main_dialog;
main_dialog.show();
return app.exec();
}
// Copyright 2014 Citra Emulator Project / 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <regex>
#include <string>
#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QStorageInfo>
#include <QTreeWidgetItem>
#include <qdevicewatcher.h>
#include "common/assert.h"
#include "common/file_util.h"
#include "frontend/import_dialog.h"
#include "frontend/main.h"
#include "frontend/utilities.h"
#include "ui_main.h"
#ifdef __APPLE__
#include <unistd.h>
#include "common/common_paths.h"
#endif
#ifdef QT_STATICPLUGIN
#include <QtPlugin>
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin)
#endif
#endif
bool IsConfigGood(const Core::Config& config) {
return !config.sdmc_path.empty() && !config.user_path.empty() &&
!config.movable_sed_path.empty() && !config.bootrom_path.empty();
}
MainDialog::MainDialog(QWidget* parent)
: DPIAwareDialog(parent, 640, 256), ui(std::make_unique<Ui::MainDialog>()) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(false);
ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)->setText(tr("Refresh"));
LoadPresetConfig();
connect(ui->utilitiesButton, &QPushButton::clicked, [this] {
UtilitiesDialog dialog(this);
dialog.exec();
});
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) {
if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)) {
LoadPresetConfig();
} else if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) {
LaunchImportDialog();
}
});
QString destination_text{};
const auto destination = FileUtil::GetUserPathType();
if (destination == FileUtil::UserPathType::Normal) {
#ifdef __linux__
destination_text = tr("Non-Flatpak Citra Install");
#else
destination_text = tr("User-wide Citra Install");
#endif
} else if (destination == FileUtil::UserPathType::Portable) {
destination_text = tr("Portable Citra Install");
} else if (destination == FileUtil::UserPathType::Flatpak) {
destination_text = tr("Flatpak Citra Install");
} else {
UNREACHABLE();
}
ui->importDestination->setText(tr("Import Destination: %1").arg(destination_text));
connect(ui->main, &QTreeWidget::itemSelectionChanged, [this] {
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)
->setEnabled(!ui->main->selectedItems().empty());
});
connect(ui->main, &QTreeWidget::itemDoubleClicked, [this] {
if (!ui->main->selectedItems().empty())
LaunchImportDialog();
});
ui->main->setIndentation(4);
// Set up device watcher
auto* device_watcher = new QDeviceWatcher(this);
device_watcher->start();
connect(device_watcher, &QDeviceWatcher::deviceAdded, this, &MainDialog::LoadPresetConfig);
connect(device_watcher, &QDeviceWatcher::deviceChanged, this, &MainDialog::LoadPresetConfig);
connect(device_watcher, &QDeviceWatcher::deviceRemoved, this, &MainDialog::LoadPresetConfig);
}
MainDialog::~MainDialog() = default;
void MainDialog::SetContentSizes(int previous_width, int previous_height) {
const int current_width = width();
if (previous_width == 0) { // first time
ui->main->setColumnWidth(0, 0.3 * current_width);
ui->main->setColumnWidth(1, 0.4 * current_width);
} else { // proportionally update column widths
for (int i : {0, 1}) {
ui->main->setColumnWidth(i, ui->main->columnWidth(i) * current_width / previous_width);
}
}
}
static const std::regex sdmc_path_regex{"(.+)([/\\\\])Nintendo 3DS/([0-9a-f]{32})/([0-9a-f]{32})/"};
void MainDialog::LoadPresetConfig() {
ui->main->clear();
preset_config_list.clear();
for (const auto& storage : QStorageInfo::mountedVolumes()) {
if (!storage.isValid() || !storage.isReady()) {
continue;
}
auto list = Core::LoadPresetConfig(storage.rootPath().toStdString());
for (std::size_t i = 0; i < list.size(); ++i) {
preset_config_list.emplace_back(list[i]);
QString path = storage.rootPath();
if (path.endsWith(QLatin1Char{'/'}) || path.endsWith(QLatin1Char{'\\'})) {
path.remove(path.size() - 1, 1);
}
// Get ID0
QString id0 = tr("Unknown");
std::smatch match;
if (std::regex_match(list[i].sdmc_path, match, sdmc_path_regex)) {
if (match.size() >= 5) {
id0 = QString::fromStdString(match[3].str());
}
}
// Get status
QString status = tr("Good");
if (!IsConfigGood(list[i])) {
status = tr("No Configuration Found");
} else if (list[i].version != Core::CurrentDumperVersion) {
status = tr("Version Dismatch");
} else if (list[i].safe_mode_firm_path.empty() ||
list[i].config_savegame_path.empty() ||
list[i].system_archives_path.empty()) {
status = tr("Missing System Files");
} else if (list[i].seed_db_path.empty()) {
status = tr("Good, Missing Seeds");
}
auto* item = new QTreeWidgetItem{{path, id0, status}};
ui->main->invisibleRootItem()->addChild(item);
}
}
auto* item = new QTreeWidgetItem{{tr("Browse SD Card Root...")}};
item->setFirstColumnSpanned(true);
ui->main->invisibleRootItem()->addChild(item);
}
void MainDialog::LaunchImportDialog() {
auto* item = ui->main->currentItem();
if (!item) {
return;
}
Core::Config config;
const auto index = ui->main->invisibleRootItem()->indexOfChild(item);
if (index == ui->main->invisibleRootItem()->childCount() - 1) {
const QString path = QFileDialog::getExistingDirectory(this, tr("Select SD Card Root"));
if (path.isEmpty()) {
return;
}
const auto& list = Core::LoadPresetConfig(path.toStdString());
if (list.size() > 1) {
QMessageBox::information(
this, tr("threeSD"),
tr("You have more than one 3DS data on your SD Card.\nthreeSD will "
"select the first one for you."));
} else if (list.empty() || !IsConfigGood(list[0])) {
QMessageBox::critical(
this, tr("Error"),
tr("Could not load configuration.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>"
"guide</a> correctly."));
return;
}
config = list[0];
} else {
config = preset_config_list.at(index);
}
// Check config integrity
if (!IsConfigGood(config)) {
QMessageBox::critical(
this, tr("Error"),
tr("Could not load configuration from this SD card. You need to prepare your SD card "
"before using threeSD.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>"
"guide</a> correctly."));
return;
}
if (config.version != Core::CurrentDumperVersion) {
QMessageBox::critical(this, tr("Version Dismatch"),
tr("You are using an unsupported version of threeSDumper.<br>Please "
"ensure that you are using the most recent version of both "
"threeSD and threeSDumper and try again."));
return;
}
if (config.safe_mode_firm_path.empty() || config.config_savegame_path.empty() ||
config.system_archives_path.empty()) {
QMessageBox::warning(
this, tr("Warning"),
tr("Certain system files are missing from your configuration.<br>Some contents "
"may not be importable, or may not run.<br>Please check if you have followed the <a "
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>guide</a> "
"correctly."));
} else if (config.seed_db_path.empty()) {
QMessageBox::warning(this, tr("Warning"),
tr("Seed database is missing from your configuration.<br>Your system "
"likely does not have any seeds.<br>However, if it does have any, "
"imported games using seed encryption may not work."));
}
ImportDialog dialog(this, config);
dialog.exec();
}
int main(int argc, char* argv[]) {
// Init settings params
QCoreApplication::setOrganizationName(QStringLiteral("zhaowenlan1779"));
QCoreApplication::setApplicationName(QStringLiteral("threeSD"));
#ifdef __APPLE__
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
chdir(bin_path.c_str());
#endif
QApplication app(argc, argv);
QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons/default")));
QIcon::setThemeName(QStringLiteral(":/icons/default"));
MainDialog main_dialog;
main_dialog.show();
return app.exec();
}
+30 -30
View File
@@ -1,30 +1,30 @@
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/importer.h"
#include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui {
class MainDialog;
}
class MainDialog final : public DPIAwareDialog {
Q_OBJECT
public:
explicit MainDialog(QWidget* parent = nullptr);
~MainDialog() override;
private:
void SetContentSizes(int previous_width, int previous_height) override;
void LoadPresetConfig();
void LaunchImportDialog();
std::vector<Core::Config> preset_config_list;
std::unique_ptr<Ui::MainDialog> ui;
};
// Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/importer.h"
#include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui {
class MainDialog;
}
class MainDialog final : public DPIAwareDialog {
Q_OBJECT
public:
explicit MainDialog(QWidget* parent = nullptr);
~MainDialog() override;
private:
void SetContentSizes(int previous_width, int previous_height) override;
void LoadPresetConfig();
void LaunchImportDialog();
std::vector<Core::Config> preset_config_list;
std::unique_ptr<Ui::MainDialog> ui;
};
+78 -78
View File
@@ -1,78 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainDialog</class>
<widget class="QDialog" name="MainDialog">
<property name="windowTitle">
<string>threeSD</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel">
<property name="text">
<string>Select your SD card root, or manually browse when not detected.</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="utilitiesButton">
<property name="text">
<string>Utilities...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="main">
<column>
<property name="text">
<string>Path</string>
</property>
</column>
<column>
<property name="text">
<string>ID</string>
</property>
</column>
<column>
<property name="text">
<string>Status</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="importDestination"/>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainDialog</class>
<widget class="QDialog" name="MainDialog">
<property name="windowTitle">
<string>threeSD</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel">
<property name="text">
<string>Select your SD card root, or manually browse when not detected.</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="utilitiesButton">
<property name="text">
<string>Utilities...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="main">
<column>
<property name="text">
<string>Path</string>
</property>
</column>
<column>
<property name="text">
<string>ID</string>
</property>
</column>
<column>
<property name="text">
<string>Status</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="importDestination"/>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+55 -55
View File
@@ -1,55 +1,55 @@
// Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopWidget>
#include <QFileDialog>
#include <QMessageBox>
#include "frontend/select_files_dialog.h"
#include "ui_select_files_dialog.h"
SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_)
: DPIAwareDialog(parent, 480, 96), ui(std::make_unique<Ui::SelectFilesDialog>()),
source_is_dir(source_is_dir_), destination_is_dir(destination_is_dir_) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] {
if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) {
QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields."));
return;
}
accept();
});
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject);
connect(ui->sourceExplore, &QToolButton::clicked, [this] {
QString path;
if (source_is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
} else {
path = QFileDialog::getOpenFileName(this, tr("Select File"));
}
if (!path.isEmpty()) {
ui->source->setText(path);
}
});
connect(ui->destinationExplore, &QToolButton::clicked, [this] {
QString path;
if (destination_is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
} else {
path = QFileDialog::getSaveFileName(this, tr("Select File"));
}
if (!path.isEmpty()) {
ui->destination->setText(path);
}
});
}
SelectFilesDialog::~SelectFilesDialog() = default;
std::pair<QString, QString> SelectFilesDialog::GetResults() const {
return {ui->source->text(), ui->destination->text()};
}
// Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopWidget>
#include <QFileDialog>
#include <QMessageBox>
#include "frontend/select_files_dialog.h"
#include "ui_select_files_dialog.h"
SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_)
: DPIAwareDialog(parent, 480, 96), ui(std::make_unique<Ui::SelectFilesDialog>()),
source_is_dir(source_is_dir_), destination_is_dir(destination_is_dir_) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] {
if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) {
QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields."));
return;
}
accept();
});
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject);
connect(ui->sourceExplore, &QToolButton::clicked, [this] {
QString path;
if (source_is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
} else {
path = QFileDialog::getOpenFileName(this, tr("Select File"));
}
if (!path.isEmpty()) {
ui->source->setText(path);
}
});
connect(ui->destinationExplore, &QToolButton::clicked, [this] {
QString path;
if (destination_is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
} else {
path = QFileDialog::getSaveFileName(this, tr("Select File"));
}
if (!path.isEmpty()) {
ui->destination->setText(path);
}
});
}
SelectFilesDialog::~SelectFilesDialog() = default;
std::pair<QString, QString> SelectFilesDialog::GetResults() const {
return {ui->source->text(), ui->destination->text()};
}
+27 -27
View File
@@ -1,27 +1,27 @@
// Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui {
class SelectFilesDialog;
}
class SelectFilesDialog : public DPIAwareDialog {
Q_OBJECT
public:
explicit SelectFilesDialog(QWidget* parent, bool source_is_dir, bool destination_is_dir);
~SelectFilesDialog() override;
std::pair<QString, QString> GetResults() const;
private:
std::unique_ptr<Ui::SelectFilesDialog> ui;
bool source_is_dir; // Whether Source should be a directory
bool destination_is_dir; // Whether Destination should be a directory
};
// Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "frontend/helpers/dpi_aware_dialog.h"
namespace Ui {
class SelectFilesDialog;
}
class SelectFilesDialog : public DPIAwareDialog {
Q_OBJECT
public:
explicit SelectFilesDialog(QWidget* parent, bool source_is_dir, bool destination_is_dir);
~SelectFilesDialog() override;
std::pair<QString, QString> GetResults() const;
private:
std::unique_ptr<Ui::SelectFilesDialog> ui;
bool source_is_dir; // Whether Source should be a directory
bool destination_is_dir; // Whether Destination should be a directory
};
+68 -68
View File
@@ -1,68 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SelectFilesDialog</class>
<widget class="QDialog" name="SelectFilesDialog">
<property name="windowTitle">
<string>Select Files</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Source:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="source"/>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="sourceExplore">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Destination:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="destination"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="destinationExplore">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SelectFilesDialog</class>
<widget class="QDialog" name="SelectFilesDialog">
<property name="windowTitle">
<string>Select Files</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Source:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="source"/>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="sourceExplore">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Destination:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="destination"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="destinationExplore">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
</ui>
+241 -241
View File
@@ -1,241 +1,241 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QAction>
#include <QDesktopWidget>
#include <QFileDialog>
#include <QImageWriter>
#include <QMessageBox>
#include <QPixmap>
#include <fmt/format.h>
#include "common/string_util.h"
#include "core/db/title_db.h"
#include "core/file_sys/ncch_container.h"
#include "core/file_sys/title_metadata.h"
#include "core/importer.h"
#include "frontend/helpers/simple_job.h"
#include "frontend/title_info_dialog.h"
#include "ui_title_info_dialog.h"
TitleInfoDialog::TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer_,
Core::ContentSpecifier specifier_)
: DPIAwareDialog(parent, 500, 360), ui(std::make_unique<Ui::TitleInfoDialog>()),
importer(importer_), specifier(std::move(specifier_)) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
LoadInfo();
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept);
}
TitleInfoDialog::~TitleInfoDialog() = default;
void TitleInfoDialog::LoadInfo() {
// Load TMD & boot NCCH
Core::TitleMetadata tmd;
Core::NCCHContainer ncch;
if (!importer.LoadTMD(specifier, tmd) ||
!ncch.OpenFile(importer.OpenContent(specifier, tmd.GetBootContentID()))) {
QMessageBox::warning(this, tr("threeSD"), tr("Could not load title information."));
reject();
return;
}
// Load SMDH from boot NCCH
bool has_smdh = false;
std::vector<u8> smdh_buffer;
if (ncch.LoadSectionExeFS("icon", smdh_buffer) && smdh_buffer.size() == sizeof(Core::SMDH) &&
Core::IsValidSMDH(smdh_buffer)) {
has_smdh = true;
std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size());
}
// Basic info
ui->versionLineEdit->setText(QString::fromStdString(tmd.GetTitleVersionString()));
LoadEncryption(ncch);
ui->titleIDLineEdit->setText(QStringLiteral("%1").arg(specifier.id, 16, 16, QLatin1Char{'0'}));
// Icons
if (has_smdh) {
LoadIcons();
}
// Names
if (has_smdh) {
InitializeLanguageComboBox();
} else {
ui->namesGroupBox->setVisible(false);
}
// Checks
InitializeChecks(tmd);
}
void TitleInfoDialog::LoadEncryption(Core::NCCHContainer& ncch) {
static const std::unordered_map<Core::EncryptionType, const char*> EncryptionTypeMap{{
{Core::EncryptionType::None, QT_TR_NOOP("None")},
{Core::EncryptionType::FixedKey, QT_TR_NOOP("FixedKey")},
{Core::EncryptionType::NCCHSecure1, QT_TR_NOOP("Secure1")},
{Core::EncryptionType::NCCHSecure2, QT_TR_NOOP("Secure2")},
{Core::EncryptionType::NCCHSecure3, QT_TR_NOOP("Secure3")},
{Core::EncryptionType::NCCHSecure4, QT_TR_NOOP("Secure4")},
}};
Core::EncryptionType encryption = Core::EncryptionType::None;
ncch.ReadEncryptionType(encryption);
bool seed_crypto = false;
ncch.ReadSeedCrypto(seed_crypto);
QString encryption_text = tr(EncryptionTypeMap.at(encryption));
if (seed_crypto) {
encryption_text.append(tr(" (Seed)"));
}
ui->encryptionLineEdit->setText(encryption_text);
}
void TitleInfoDialog::LoadIcons() {
ui->iconLargeLabel->setPixmap(
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(true).data()), 48, 48,
QImage::Format::Format_RGB16)));
QAction* save_icon_large = new QAction(tr("Save Icon (Large)"), this);
ui->iconLargeLabel->addAction(save_icon_large);
connect(save_icon_large, &QAction::triggered, this, [this] { SaveIcon(true); });
ui->iconSmallLabel->setPixmap(
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(false).data()), 24,
24, QImage::Format::Format_RGB16)));
QAction* save_icon_small = new QAction(tr("Save Icon (Small)"), this);
ui->iconSmallLabel->addAction(save_icon_small);
connect(save_icon_small, &QAction::triggered, this, [this] { SaveIcon(false); });
}
void TitleInfoDialog::InitializeLanguageComboBox() {
if (!ui->namesGroupBox->isEnabled()) { // SMDH not available
return;
}
// Corresponds to the indices of the languages defined in SMDH
static constexpr std::array<const char*, 12> LanguageNames{{
QT_TR_NOOP("Japanese"),
QT_TR_NOOP("English"),
QT_TR_NOOP("French"),
QT_TR_NOOP("German"),
QT_TR_NOOP("Italian"),
QT_TR_NOOP("Spanish"),
QT_TR_NOOP("Chinese (Simplified)"),
QT_TR_NOOP("Korean"),
QT_TR_NOOP("Dutch"),
QT_TR_NOOP("Portuguese"),
QT_TR_NOOP("Russian"),
QT_TR_NOOP("Chinese (Traditional)"),
}};
for (std::size_t i = 0; i < LanguageNames.size(); ++i) {
const auto& title = smdh.titles.at(i);
if (Common::UTF16BufferToUTF8(title.short_title).empty() &&
Common::UTF16BufferToUTF8(title.long_title).empty() &&
Common::UTF16BufferToUTF8(title.publisher).empty()) {
// All empty, ignore this language
continue;
}
ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast<int>(i));
if (i == 1) { // English
ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1);
}
}
connect(ui->languageComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&TitleInfoDialog::UpdateNames);
UpdateNames();
}
void TitleInfoDialog::SaveIcon(bool large) {
const auto types = QImageWriter::supportedImageFormats();
QStringList filters;
for (const auto& type : types) {
const QString extension = QString::fromUtf8(type);
filters << QStringLiteral("%1 Image (*.%2)").arg(extension.toUpper(), extension);
}
static QString last_path;
const QString path = QFileDialog::getSaveFileName(this, tr("Save Icon"), last_path,
filters.join(QStringLiteral(";;")));
if (path.isEmpty()) {
return;
}
last_path = QFileInfo(path).path();
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
const auto& pixmap = large ? ui->iconLargeLabel->pixmap(Qt::ReturnByValue)
: ui->iconSmallLabel->pixmap(Qt::ReturnByValue);
#else
const auto& pixmap = large ? *ui->iconLargeLabel->pixmap() : *ui->iconSmallLabel->pixmap();
#endif
if (!pixmap.save(path)) {
QMessageBox::warning(this, tr("threeSD"), tr("Could not save icon."));
}
}
void TitleInfoDialog::UpdateNames() {
const auto& title = smdh.titles.at(ui->languageComboBox->currentData().toInt());
ui->shortTitleLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.short_title)));
ui->longTitleLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.long_title)));
ui->publisherLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.publisher)));
}
void TitleInfoDialog::InitializeChecks(Core::TitleMetadata& tmd) {
const bool tmd_legit = tmd.ValidateSignature() && tmd.VerifyHashes();
if (tmd_legit) {
ui->tmdCheckLabel->setText(tr("Legit"));
} else {
ui->tmdCheckLabel->setText(tr("Illegit"));
}
if (const auto& ticket_db = importer.GetTicketDB();
ticket_db && ticket_db->tickets.count(specifier.id)) {
const bool ticket_legit = ticket_db->tickets.at(specifier.id).ValidateSignature();
if (ticket_legit) {
ui->ticketCheckLabel->setText(tr("Legit"));
} else {
ui->ticketCheckLabel->setText(tr("Illegit"));
}
} else {
ui->ticketCheckLabel->setText(tr("Missing"));
}
connect(ui->contentsCheckButton, &QPushButton::clicked, this,
&TitleInfoDialog::ExecuteContentsCheck);
}
void TitleInfoDialog::ExecuteContentsCheck() {
auto* job = new SimpleJob(
this,
[this](const Common::ProgressCallback& callback) {
contents_check_result = importer.CheckTitleContents(specifier, callback);
return true;
},
[this] { importer.AbortImporting(); });
connect(job, &SimpleJob::Completed, this, [this](bool canceled) {
if (canceled) {
return;
}
ui->contentsCheckButton->setVisible(false);
ui->contentsCheckLabel->setVisible(true);
if (contents_check_result) {
ui->contentsCheckLabel->setText(tr("OK"));
} else {
ui->contentsCheckLabel->setText(tr("Failed"));
}
});
job->StartWithProgressDialog(this);
}
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QAction>
#include <QDesktopWidget>
#include <QFileDialog>
#include <QImageWriter>
#include <QMessageBox>
#include <QPixmap>
#include <fmt/format.h>
#include "common/string_util.h"
#include "core/db/title_db.h"
#include "core/file_sys/ncch_container.h"
#include "core/file_sys/title_metadata.h"
#include "core/importer.h"
#include "frontend/helpers/simple_job.h"
#include "frontend/title_info_dialog.h"
#include "ui_title_info_dialog.h"
TitleInfoDialog::TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer_,
Core::ContentSpecifier specifier_)
: DPIAwareDialog(parent, 500, 360), ui(std::make_unique<Ui::TitleInfoDialog>()),
importer(importer_), specifier(std::move(specifier_)) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
LoadInfo();
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept);
}
TitleInfoDialog::~TitleInfoDialog() = default;
void TitleInfoDialog::LoadInfo() {
// Load TMD & boot NCCH
Core::TitleMetadata tmd;
Core::NCCHContainer ncch;
if (!importer.LoadTMD(specifier, tmd) ||
!ncch.OpenFile(importer.OpenContent(specifier, tmd.GetBootContentID()))) {
QMessageBox::warning(this, tr("threeSD"), tr("Could not load title information."));
reject();
return;
}
// Load SMDH from boot NCCH
bool has_smdh = false;
std::vector<u8> smdh_buffer;
if (ncch.LoadSectionExeFS("icon", smdh_buffer) && smdh_buffer.size() == sizeof(Core::SMDH) &&
Core::IsValidSMDH(smdh_buffer)) {
has_smdh = true;
std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size());
}
// Basic info
ui->versionLineEdit->setText(QString::fromStdString(tmd.GetTitleVersionString()));
LoadEncryption(ncch);
ui->titleIDLineEdit->setText(QStringLiteral("%1").arg(specifier.id, 16, 16, QLatin1Char{'0'}));
// Icons
if (has_smdh) {
LoadIcons();
}
// Names
if (has_smdh) {
InitializeLanguageComboBox();
} else {
ui->namesGroupBox->setVisible(false);
}
// Checks
InitializeChecks(tmd);
}
void TitleInfoDialog::LoadEncryption(Core::NCCHContainer& ncch) {
static const std::unordered_map<Core::EncryptionType, const char*> EncryptionTypeMap{{
{Core::EncryptionType::None, QT_TR_NOOP("None")},
{Core::EncryptionType::FixedKey, QT_TR_NOOP("FixedKey")},
{Core::EncryptionType::NCCHSecure1, QT_TR_NOOP("Secure1")},
{Core::EncryptionType::NCCHSecure2, QT_TR_NOOP("Secure2")},
{Core::EncryptionType::NCCHSecure3, QT_TR_NOOP("Secure3")},
{Core::EncryptionType::NCCHSecure4, QT_TR_NOOP("Secure4")},
}};
Core::EncryptionType encryption = Core::EncryptionType::None;
ncch.ReadEncryptionType(encryption);
bool seed_crypto = false;
ncch.ReadSeedCrypto(seed_crypto);
QString encryption_text = tr(EncryptionTypeMap.at(encryption));
if (seed_crypto) {
encryption_text.append(tr(" (Seed)"));
}
ui->encryptionLineEdit->setText(encryption_text);
}
void TitleInfoDialog::LoadIcons() {
ui->iconLargeLabel->setPixmap(
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(true).data()), 48, 48,
QImage::Format::Format_RGB16)));
QAction* save_icon_large = new QAction(tr("Save Icon (Large)"), this);
ui->iconLargeLabel->addAction(save_icon_large);
connect(save_icon_large, &QAction::triggered, this, [this] { SaveIcon(true); });
ui->iconSmallLabel->setPixmap(
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(smdh.GetIcon(false).data()), 24,
24, QImage::Format::Format_RGB16)));
QAction* save_icon_small = new QAction(tr("Save Icon (Small)"), this);
ui->iconSmallLabel->addAction(save_icon_small);
connect(save_icon_small, &QAction::triggered, this, [this] { SaveIcon(false); });
}
void TitleInfoDialog::InitializeLanguageComboBox() {
if (!ui->namesGroupBox->isEnabled()) { // SMDH not available
return;
}
// Corresponds to the indices of the languages defined in SMDH
static constexpr std::array<const char*, 12> LanguageNames{{
QT_TR_NOOP("Japanese"),
QT_TR_NOOP("English"),
QT_TR_NOOP("French"),
QT_TR_NOOP("German"),
QT_TR_NOOP("Italian"),
QT_TR_NOOP("Spanish"),
QT_TR_NOOP("Chinese (Simplified)"),
QT_TR_NOOP("Korean"),
QT_TR_NOOP("Dutch"),
QT_TR_NOOP("Portuguese"),
QT_TR_NOOP("Russian"),
QT_TR_NOOP("Chinese (Traditional)"),
}};
for (std::size_t i = 0; i < LanguageNames.size(); ++i) {
const auto& title = smdh.titles.at(i);
if (Common::UTF16BufferToUTF8(title.short_title).empty() &&
Common::UTF16BufferToUTF8(title.long_title).empty() &&
Common::UTF16BufferToUTF8(title.publisher).empty()) {
// All empty, ignore this language
continue;
}
ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast<int>(i));
if (i == 1) { // English
ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1);
}
}
connect(ui->languageComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&TitleInfoDialog::UpdateNames);
UpdateNames();
}
void TitleInfoDialog::SaveIcon(bool large) {
const auto types = QImageWriter::supportedImageFormats();
QStringList filters;
for (const auto& type : types) {
const QString extension = QString::fromUtf8(type);
filters << QStringLiteral("%1 Image (*.%2)").arg(extension.toUpper(), extension);
}
static QString last_path;
const QString path = QFileDialog::getSaveFileName(this, tr("Save Icon"), last_path,
filters.join(QStringLiteral(";;")));
if (path.isEmpty()) {
return;
}
last_path = QFileInfo(path).path();
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
const auto& pixmap = large ? ui->iconLargeLabel->pixmap(Qt::ReturnByValue)
: ui->iconSmallLabel->pixmap(Qt::ReturnByValue);
#else
const auto& pixmap = large ? *ui->iconLargeLabel->pixmap() : *ui->iconSmallLabel->pixmap();
#endif
if (!pixmap.save(path)) {
QMessageBox::warning(this, tr("threeSD"), tr("Could not save icon."));
}
}
void TitleInfoDialog::UpdateNames() {
const auto& title = smdh.titles.at(ui->languageComboBox->currentData().toInt());
ui->shortTitleLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.short_title)));
ui->longTitleLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.long_title)));
ui->publisherLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.publisher)));
}
void TitleInfoDialog::InitializeChecks(Core::TitleMetadata& tmd) {
const bool tmd_legit = tmd.ValidateSignature() && tmd.VerifyHashes();
if (tmd_legit) {
ui->tmdCheckLabel->setText(tr("Legit"));
} else {
ui->tmdCheckLabel->setText(tr("Illegit"));
}
if (const auto& ticket_db = importer.GetTicketDB();
ticket_db && ticket_db->tickets.count(specifier.id)) {
const bool ticket_legit = ticket_db->tickets.at(specifier.id).ValidateSignature();
if (ticket_legit) {
ui->ticketCheckLabel->setText(tr("Legit"));
} else {
ui->ticketCheckLabel->setText(tr("Illegit"));
}
} else {
ui->ticketCheckLabel->setText(tr("Missing"));
}
connect(ui->contentsCheckButton, &QPushButton::clicked, this,
&TitleInfoDialog::ExecuteContentsCheck);
}
void TitleInfoDialog::ExecuteContentsCheck() {
auto* job = new SimpleJob(
this,
[this](const Common::ProgressCallback& callback) {
contents_check_result = importer.CheckTitleContents(specifier, callback);
return true;
},
[this] { importer.AbortImporting(); });
connect(job, &SimpleJob::Completed, this, [this](bool canceled) {
if (canceled) {
return;
}
ui->contentsCheckButton->setVisible(false);
ui->contentsCheckLabel->setVisible(true);
if (contents_check_result) {
ui->contentsCheckLabel->setText(tr("OK"));
} else {
ui->contentsCheckLabel->setText(tr("Failed"));
}
});
job->StartWithProgressDialog(this);
}
+47 -47
View File
@@ -1,47 +1,47 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "core/file_sys/smdh.h"
#include "frontend/helpers/dpi_aware_dialog.h"
namespace Core {
struct Config;
struct ContentSpecifier;
class NCCHContainer;
class SDMCImporter;
class TitleMetadata;
} // namespace Core
namespace Ui {
class TitleInfoDialog;
}
class TitleInfoDialog : public DPIAwareDialog {
Q_OBJECT
public:
explicit TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer,
Core::ContentSpecifier specifier);
~TitleInfoDialog();
private:
void LoadInfo();
void LoadEncryption(Core::NCCHContainer& ncch);
void LoadIcons();
void InitializeLanguageComboBox();
void InitializeChecks(Core::TitleMetadata& tmd);
void SaveIcon(bool large);
void UpdateNames();
void ExecuteContentsCheck();
std::unique_ptr<Ui::TitleInfoDialog> ui;
Core::SDMCImporter& importer;
const Core::ContentSpecifier specifier;
Core::SMDH smdh{};
bool contents_check_result = false;
};
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "core/file_sys/smdh.h"
#include "frontend/helpers/dpi_aware_dialog.h"
namespace Core {
struct Config;
struct ContentSpecifier;
class NCCHContainer;
class SDMCImporter;
class TitleMetadata;
} // namespace Core
namespace Ui {
class TitleInfoDialog;
}
class TitleInfoDialog : public DPIAwareDialog {
Q_OBJECT
public:
explicit TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer,
Core::ContentSpecifier specifier);
~TitleInfoDialog();
private:
void LoadInfo();
void LoadEncryption(Core::NCCHContainer& ncch);
void LoadIcons();
void InitializeLanguageComboBox();
void InitializeChecks(Core::TitleMetadata& tmd);
void SaveIcon(bool large);
void UpdateNames();
void ExecuteContentsCheck();
std::unique_ptr<Ui::TitleInfoDialog> ui;
Core::SDMCImporter& importer;
const Core::ContentSpecifier specifier;
Core::SMDH smdh{};
bool contents_check_result = false;
};
+238 -238
View File
@@ -1,238 +1,238 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TitleInfoDialog</class>
<widget class="QDialog" name="TitleInfoDialog">
<property name="windowTitle">
<string>Title Info</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="title">
<string>Info</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Version:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="versionLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2" rowspan="2">
<widget class="QLabel" name="iconLargeLabel">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
</widget>
</item>
<item row="0" column="3" rowspan="2">
<widget class="QLabel" name="iconSmallLabel">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Encryption:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="encryptionLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Title ID:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="3">
<widget class="QLineEdit" name="titleIDLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="namesGroupBox">
<property name="title">
<string>Names</string>
</property>
<layout class="QGridLayout" columnstretch="0,20,10">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Short Title:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="shortTitleLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="languageComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Long Title:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="longTitleLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Publisher:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="publisherLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Checks</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>TMD:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="tmdCheckLabel">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Ticket:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="ticketCheckLabel">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Contents:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="contentsCheckButton">
<property name="text">
<string>Check</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="contentsCheckLabel">
<property name="visible">
<bool>false</bool>
</property>
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="2" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TitleInfoDialog</class>
<widget class="QDialog" name="TitleInfoDialog">
<property name="windowTitle">
<string>Title Info</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="title">
<string>Info</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Version:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="versionLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2" rowspan="2">
<widget class="QLabel" name="iconLargeLabel">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
</widget>
</item>
<item row="0" column="3" rowspan="2">
<widget class="QLabel" name="iconSmallLabel">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Encryption:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="encryptionLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Title ID:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="3">
<widget class="QLineEdit" name="titleIDLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="namesGroupBox">
<property name="title">
<string>Names</string>
</property>
<layout class="QGridLayout" columnstretch="0,20,10">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Short Title:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="shortTitleLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="languageComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Long Title:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="longTitleLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Publisher:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="publisherLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Checks</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>TMD:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="tmdCheckLabel">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Ticket:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="ticketCheckLabel">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Contents:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="contentsCheckButton">
<property name="text">
<string>Check</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="contentsCheckLabel">
<property name="visible">
<bool>false</bool>
</property>
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="2" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</ui>
+302 -302
View File
@@ -1,302 +1,302 @@
// Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopWidget>
#include <QFileDialog>
#include <QFutureWatcher>
#include <QMessageBox>
#include <QProgressDialog>
#include <QtConcurrent/QtConcurrentRun>
#include "core/file_sys/data/data_container.h"
#include "core/file_sys/data/extdata.h"
#include "core/file_sys/data/savegame.h"
#include "core/file_sys/ncch_container.h"
#include "core/key/key.h"
#include "core/sdmc_decryptor.h"
#include "frontend/select_files_dialog.h"
#include "frontend/utilities.h"
#include "ui_utilities.h"
UtilitiesDialog::UtilitiesDialog(QWidget* parent)
: DPIAwareDialog(parent, 640, 384), ui(std::make_unique<Ui::UtilitiesDialog>()) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
connect(ui->useSdDecryption, &QCheckBox::clicked, [this] {
const bool checked = ui->useSdDecryption->isChecked();
ui->boot9Path->setEnabled(checked);
ui->boot9PathExplore->setEnabled(checked);
ui->movableSedPath->setEnabled(checked);
ui->movableSedPathExplore->setEnabled(checked);
ui->sdmcPath->setEnabled(checked);
ui->sdmcPathExplore->setEnabled(checked);
// First hide both, to avoid resizing the dialog
ui->sdDecryptionLabel->setVisible(false);
ui->sdDecryptionDisabledLabel->setVisible(false);
ui->sdDecryptionLabel->setVisible(checked);
ui->sdDecryptionDisabledLabel->setVisible(!checked);
ui->sdDecryption->setEnabled(checked);
ui->extdataExtractionLabel->setVisible(false);
ui->extdataExtractionDisabledLabel->setVisible(false);
ui->extdataExtractionLabel->setVisible(checked);
ui->extdataExtractionDisabledLabel->setVisible(!checked);
ui->extdataExtraction->setEnabled(checked);
ui->romfsExtractionLabel->setVisible(false);
ui->romfsExtractionDisabledLabel->setVisible(false);
ui->romfsExtractionLabel->setVisible(!checked);
ui->romfsExtractionDisabledLabel->setVisible(checked);
ui->romfsExtraction->setEnabled(!checked);
});
connect(ui->boot9PathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
if (!path.isEmpty()) {
ui->boot9Path->setText(path);
}
});
connect(ui->movableSedPathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
if (!path.isEmpty()) {
ui->movableSedPath->setText(path);
}
});
connect(ui->sdmcPathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (!path.isEmpty()) {
ui->sdmcPath->setText(path);
}
});
connect(ui->sdDecryption, &QPushButton::clicked, this, &UtilitiesDialog::SDDecryptionTool);
connect(ui->savedataExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::SaveDataExtractionTool);
connect(ui->extdataExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::ExtdataExtractionTool);
connect(ui->romfsExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::RomFSExtractionTool);
}
UtilitiesDialog::~UtilitiesDialog() = default;
std::pair<QString, QString> UtilitiesDialog::GetFilePaths(bool source_is_dir,
bool destination_is_dir) {
SelectFilesDialog dialog(this, source_is_dir, destination_is_dir);
if (dialog.exec() == QDialog::Accepted) {
return dialog.GetResults();
} else {
return {};
}
}
bool UtilitiesDialog::LoadSDKeys() {
if (ui->boot9Path->text().isEmpty() || ui->movableSedPath->text().isEmpty()) {
QMessageBox::critical(this, tr("Error"),
tr("Please select boot9.bin and movable.sed paths."));
return false;
}
if (ui->sdmcPath->text().isEmpty()) {
QMessageBox::critical(this, tr("Error"),
tr("Please select SDMC root (\"Nintendo 3DS/&lt;ID0>/&lt;ID1>\")."));
return false;
}
Core::Key::ClearKeys();
Core::Key::LoadBootromKeys(ui->boot9Path->text().toStdString());
Core::Key::LoadMovableSedKeys(ui->movableSedPath->text().toStdString());
if (!Core::Key::IsNormalKeyAvailable(Core::Key::SDKey)) {
LOG_ERROR(Core, "SDKey is not available");
QMessageBox::critical(this, tr("Error"),
tr("Could not load SD Key. Please check your files."));
return false;
}
return true;
}
void UtilitiesDialog::ShowProgressDialog(std::function<bool()> operation) {
auto* dialog = new QProgressDialog(tr("Processing..."), tr("Cancel"), 0, 0, this);
dialog->setWindowFlags(dialog->windowFlags() & (~Qt::WindowContextHelpButtonHint));
dialog->setWindowModality(Qt::WindowModal);
dialog->setCancelButton(nullptr);
dialog->setMinimumDuration(0);
dialog->setValue(0);
using FutureWatcher = QFutureWatcher<void>;
auto* future_watcher = new FutureWatcher(this);
connect(future_watcher, &FutureWatcher::finished, this, [this, dialog] {
dialog->hide();
ShowResult();
});
auto future = QtConcurrent::run([operation, this] { result = operation(); });
future_watcher->setFuture(future);
}
std::tuple<bool, std::string, std::string> UtilitiesDialog::GetSDMCRoot(const QString& source) {
QString sdmc_root = ui->sdmcPath->text().replace(QLatin1Char{'\\'}, QLatin1Char{'/'});
if (!sdmc_root.endsWith(QLatin1Char{'/'})) {
sdmc_root.append(QLatin1Char{'/'});
}
if (!source.startsWith(sdmc_root)) {
QMessageBox::critical(this, tr("Error"), tr("The file selected is not in SDMC root."));
return {false, "", ""};
}
const std::string relative_source =
source.toStdString().substr(sdmc_root.toStdString().size() - 1);
return {true, sdmc_root.toStdString(), relative_source};
}
void UtilitiesDialog::SDDecryptionTool() {
if (!LoadSDKeys()) {
return;
}
const auto& [source, destination] = GetFilePaths(false, false);
if (source.isEmpty() || destination.isEmpty()) {
return;
}
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) {
return;
}
// TODO: Add Progress reporting
ShowProgressDialog(
[sdmc_root = sdmc_root, relative_source = relative_source, destination = destination] {
Core::SDMCDecryptor decryptor(sdmc_root);
return decryptor.DecryptAndWriteFile(relative_source, destination.toStdString());
});
}
void UtilitiesDialog::SaveDataExtractionTool() {
const bool decryption = ui->useSdDecryption->isChecked();
if (decryption && !LoadSDKeys()) {
return;
}
const auto& [source, destination] = GetFilePaths(false, true);
if (source.isEmpty() || destination.isEmpty()) {
return;
}
if (decryption) {
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) {
return;
}
// TODO: Add Progress reporting
ShowProgressDialog([sdmc_root = sdmc_root, relative_source = relative_source,
source = source, destination = destination] {
const auto size = FileUtil::GetSize(source.toStdString());
std::vector<u8> data(size);
Core::SDMCFile file(sdmc_root, relative_source, "rb");
if (file.ReadBytes(data.data(), size) != size) {
return false;
}
Core::DataContainer container(data);
if (!container.IsGood()) {
return false;
}
std::vector<std::vector<u8>> container_data;
if (!container.GetIVFCLevel4Data(container_data)) {
return false;
}
Core::Savegame save(std::move(container_data));
if (!save.IsGood()) {
return false;
}
return save.Extract(destination.toStdString());
});
} else {
// TODO: Add Progress reporting
ShowProgressDialog([source = source, destination = destination] {
FileUtil::IOFile file(source.toStdString(), "rb");
std::vector<u8> data = file.GetData();
if (data.empty()) {
return false;
}
Core::DataContainer container(data);
if (!container.IsGood()) {
return false;
}
std::vector<std::vector<u8>> container_data;
if (!container.GetIVFCLevel4Data(container_data)) {
return false;
}
Core::Savegame save(std::move(container_data));
if (!save.IsGood()) {
return false;
}
return save.Extract(destination.toStdString());
});
}
}
void UtilitiesDialog::ExtdataExtractionTool() {
if (!LoadSDKeys()) {
return;
}
const auto& [source, destination] = GetFilePaths(true, true);
if (source.isEmpty() || destination.isEmpty()) {
return;
}
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) {
return;
}
// TODO: Add Progress reporting
ShowProgressDialog(
[sdmc_root = sdmc_root, relative_source = relative_source, destination = destination] {
Core::SDMCDecryptor decryptor(sdmc_root);
Core::Extdata extdata(relative_source, decryptor);
if (!extdata.IsGood()) {
return false;
}
return extdata.Extract(destination.toStdString());
});
}
void UtilitiesDialog::RomFSExtractionTool() {
const auto& [source, destination] = GetFilePaths(false, false);
if (source.isEmpty() || destination.isEmpty()) {
return;
}
ShowProgressDialog([source = source, destination = destination] {
FileUtil::IOFile src_file(source.toStdString(), "rb");
std::vector<u8> data = src_file.GetData();
if (data.empty()) {
return false;
}
const auto& shared_romfs = Core::LoadSharedRomFS(data);
return FileUtil::WriteBytesToFile(destination.toStdString(), shared_romfs.data(),
shared_romfs.size());
});
}
void UtilitiesDialog::ShowResult() {
if (result) {
QMessageBox::information(this, tr("Success"), tr("Operation completed successfully."));
} else {
QMessageBox::critical(this, tr("Error"),
tr("An error occured while performing the operation."));
}
}
// Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopWidget>
#include <QFileDialog>
#include <QFutureWatcher>
#include <QMessageBox>
#include <QProgressDialog>
#include <QtConcurrent/QtConcurrentRun>
#include "core/file_sys/data/data_container.h"
#include "core/file_sys/data/extdata.h"
#include "core/file_sys/data/savegame.h"
#include "core/file_sys/ncch_container.h"
#include "core/key/key.h"
#include "core/sdmc_decryptor.h"
#include "frontend/select_files_dialog.h"
#include "frontend/utilities.h"
#include "ui_utilities.h"
UtilitiesDialog::UtilitiesDialog(QWidget* parent)
: DPIAwareDialog(parent, 640, 384), ui(std::make_unique<Ui::UtilitiesDialog>()) {
ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
connect(ui->useSdDecryption, &QCheckBox::clicked, [this] {
const bool checked = ui->useSdDecryption->isChecked();
ui->boot9Path->setEnabled(checked);
ui->boot9PathExplore->setEnabled(checked);
ui->movableSedPath->setEnabled(checked);
ui->movableSedPathExplore->setEnabled(checked);
ui->sdmcPath->setEnabled(checked);
ui->sdmcPathExplore->setEnabled(checked);
// First hide both, to avoid resizing the dialog
ui->sdDecryptionLabel->setVisible(false);
ui->sdDecryptionDisabledLabel->setVisible(false);
ui->sdDecryptionLabel->setVisible(checked);
ui->sdDecryptionDisabledLabel->setVisible(!checked);
ui->sdDecryption->setEnabled(checked);
ui->extdataExtractionLabel->setVisible(false);
ui->extdataExtractionDisabledLabel->setVisible(false);
ui->extdataExtractionLabel->setVisible(checked);
ui->extdataExtractionDisabledLabel->setVisible(!checked);
ui->extdataExtraction->setEnabled(checked);
ui->romfsExtractionLabel->setVisible(false);
ui->romfsExtractionDisabledLabel->setVisible(false);
ui->romfsExtractionLabel->setVisible(!checked);
ui->romfsExtractionDisabledLabel->setVisible(checked);
ui->romfsExtraction->setEnabled(!checked);
});
connect(ui->boot9PathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
if (!path.isEmpty()) {
ui->boot9Path->setText(path);
}
});
connect(ui->movableSedPathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
if (!path.isEmpty()) {
ui->movableSedPath->setText(path);
}
});
connect(ui->sdmcPathExplore, &QToolButton::clicked, [this] {
const QString path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (!path.isEmpty()) {
ui->sdmcPath->setText(path);
}
});
connect(ui->sdDecryption, &QPushButton::clicked, this, &UtilitiesDialog::SDDecryptionTool);
connect(ui->savedataExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::SaveDataExtractionTool);
connect(ui->extdataExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::ExtdataExtractionTool);
connect(ui->romfsExtraction, &QPushButton::clicked, this,
&UtilitiesDialog::RomFSExtractionTool);
}
UtilitiesDialog::~UtilitiesDialog() = default;
std::pair<QString, QString> UtilitiesDialog::GetFilePaths(bool source_is_dir,
bool destination_is_dir) {
SelectFilesDialog dialog(this, source_is_dir, destination_is_dir);
if (dialog.exec() == QDialog::Accepted) {
return dialog.GetResults();
} else {
return {};
}
}
bool UtilitiesDialog::LoadSDKeys() {
if (ui->boot9Path->text().isEmpty() || ui->movableSedPath->text().isEmpty()) {
QMessageBox::critical(this, tr("Error"),
tr("Please select boot9.bin and movable.sed paths."));
return false;
}
if (ui->sdmcPath->text().isEmpty()) {
QMessageBox::critical(this, tr("Error"),
tr("Please select SDMC root (\"Nintendo 3DS/&lt;ID0>/&lt;ID1>\")."));
return false;
}
Core::Key::ClearKeys();
Core::Key::LoadBootromKeys(ui->boot9Path->text().toStdString());
Core::Key::LoadMovableSedKeys(ui->movableSedPath->text().toStdString());
if (!Core::Key::IsNormalKeyAvailable(Core::Key::SDKey)) {
LOG_ERROR(Core, "SDKey is not available");
QMessageBox::critical(this, tr("Error"),
tr("Could not load SD Key. Please check your files."));
return false;
}
return true;
}
void UtilitiesDialog::ShowProgressDialog(std::function<bool()> operation) {
auto* dialog = new QProgressDialog(tr("Processing..."), tr("Cancel"), 0, 0, this);
dialog->setWindowFlags(dialog->windowFlags() & (~Qt::WindowContextHelpButtonHint));
dialog->setWindowModality(Qt::WindowModal);
dialog->setCancelButton(nullptr);
dialog->setMinimumDuration(0);
dialog->setValue(0);
using FutureWatcher = QFutureWatcher<void>;
auto* future_watcher = new FutureWatcher(this);
connect(future_watcher, &FutureWatcher::finished, this, [this, dialog] {
dialog->hide();
ShowResult();
});
auto future = QtConcurrent::run([operation, this] { result = operation(); });
future_watcher->setFuture(future);
}
std::tuple<bool, std::string, std::string> UtilitiesDialog::GetSDMCRoot(const QString& source) {
QString sdmc_root = ui->sdmcPath->text().replace(QLatin1Char{'\\'}, QLatin1Char{'/'});
if (!sdmc_root.endsWith(QLatin1Char{'/'})) {
sdmc_root.append(QLatin1Char{'/'});
}
if (!source.startsWith(sdmc_root)) {
QMessageBox::critical(this, tr("Error"), tr("The file selected is not in SDMC root."));
return {false, "", ""};
}
const std::string relative_source =
source.toStdString().substr(sdmc_root.toStdString().size() - 1);
return {true, sdmc_root.toStdString(), relative_source};
}
void UtilitiesDialog::SDDecryptionTool() {
if (!LoadSDKeys()) {
return;
}
const auto& [source, destination] = GetFilePaths(false, false);
if (source.isEmpty() || destination.isEmpty()) {
return;
}
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) {
return;
}
// TODO: Add Progress reporting
ShowProgressDialog(
[sdmc_root = sdmc_root, relative_source = relative_source, destination = destination] {
Core::SDMCDecryptor decryptor(sdmc_root);
return decryptor.DecryptAndWriteFile(relative_source, destination.toStdString());
});
}
void UtilitiesDialog::SaveDataExtractionTool() {
const bool decryption = ui->useSdDecryption->isChecked();
if (decryption && !LoadSDKeys()) {
return;
}
const auto& [source, destination] = GetFilePaths(false, true);
if (source.isEmpty() || destination.isEmpty()) {
return;
}
if (decryption) {
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) {
return;
}
// TODO: Add Progress reporting
ShowProgressDialog([sdmc_root = sdmc_root, relative_source = relative_source,
source = source, destination = destination] {
const auto size = FileUtil::GetSize(source.toStdString());
std::vector<u8> data(size);
Core::SDMCFile file(sdmc_root, relative_source, "rb");
if (file.ReadBytes(data.data(), size) != size) {
return false;
}
Core::DataContainer container(data);
if (!container.IsGood()) {
return false;
}
std::vector<std::vector<u8>> container_data;
if (!container.GetIVFCLevel4Data(container_data)) {
return false;
}
Core::Savegame save(std::move(container_data));
if (!save.IsGood()) {
return false;
}
return save.Extract(destination.toStdString());
});
} else {
// TODO: Add Progress reporting
ShowProgressDialog([source = source, destination = destination] {
FileUtil::IOFile file(source.toStdString(), "rb");
std::vector<u8> data = file.GetData();
if (data.empty()) {
return false;
}
Core::DataContainer container(data);
if (!container.IsGood()) {
return false;
}
std::vector<std::vector<u8>> container_data;
if (!container.GetIVFCLevel4Data(container_data)) {
return false;
}
Core::Savegame save(std::move(container_data));
if (!save.IsGood()) {
return false;
}
return save.Extract(destination.toStdString());
});
}
}
void UtilitiesDialog::ExtdataExtractionTool() {
if (!LoadSDKeys()) {
return;
}
const auto& [source, destination] = GetFilePaths(true, true);
if (source.isEmpty() || destination.isEmpty()) {
return;
}
const auto& [success, sdmc_root, relative_source] = GetSDMCRoot(source);
if (!success) {
return;
}
// TODO: Add Progress reporting
ShowProgressDialog(
[sdmc_root = sdmc_root, relative_source = relative_source, destination = destination] {
Core::SDMCDecryptor decryptor(sdmc_root);
Core::Extdata extdata(relative_source, decryptor);
if (!extdata.IsGood()) {
return false;
}
return extdata.Extract(destination.toStdString());
});
}
void UtilitiesDialog::RomFSExtractionTool() {
const auto& [source, destination] = GetFilePaths(false, false);
if (source.isEmpty() || destination.isEmpty()) {
return;
}
ShowProgressDialog([source = source, destination = destination] {
FileUtil::IOFile src_file(source.toStdString(), "rb");
std::vector<u8> data = src_file.GetData();
if (data.empty()) {
return false;
}
const auto& shared_romfs = Core::LoadSharedRomFS(data);
return FileUtil::WriteBytesToFile(destination.toStdString(), shared_romfs.data(),
shared_romfs.size());
});
}
void UtilitiesDialog::ShowResult() {
if (result) {
QMessageBox::information(this, tr("Success"), tr("Operation completed successfully."));
} else {
QMessageBox::critical(this, tr("Error"),
tr("An error occured while performing the operation."));
}
}
+49 -49
View File
@@ -1,49 +1,49 @@
// Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include "frontend/helpers/dpi_aware_dialog.h"
class QWidget;
namespace Ui {
class UtilitiesDialog;
}
class UtilitiesDialog : public DPIAwareDialog {
Q_OBJECT
public:
explicit UtilitiesDialog(QWidget* parent);
~UtilitiesDialog() override;
private:
/**
* Open a dialog to ask the user for source and destination paths.
* @return {source, destination}
*/
std::pair<QString, QString> GetFilePaths(bool source_is_dir, bool destination_is_dir);
bool LoadSDKeys();
void ShowProgressDialog(std::function<bool()> operation);
/**
* Gets SDMC root, and relative source path.
* @return {success, sdmc root, relative source path}
*/
std::tuple<bool, std::string, std::string> GetSDMCRoot(const QString& source);
void SDDecryptionTool();
void SaveDataExtractionTool();
void ExtdataExtractionTool();
void RomFSExtractionTool();
void ShowResult();
bool result = false; /// Result of the last operation.
std::unique_ptr<Ui::UtilitiesDialog> ui;
};
// Copyright 2020 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include "frontend/helpers/dpi_aware_dialog.h"
class QWidget;
namespace Ui {
class UtilitiesDialog;
}
class UtilitiesDialog : public DPIAwareDialog {
Q_OBJECT
public:
explicit UtilitiesDialog(QWidget* parent);
~UtilitiesDialog() override;
private:
/**
* Open a dialog to ask the user for source and destination paths.
* @return {source, destination}
*/
std::pair<QString, QString> GetFilePaths(bool source_is_dir, bool destination_is_dir);
bool LoadSDKeys();
void ShowProgressDialog(std::function<bool()> operation);
/**
* Gets SDMC root, and relative source path.
* @return {success, sdmc root, relative source path}
*/
std::tuple<bool, std::string, std::string> GetSDMCRoot(const QString& source);
void SDDecryptionTool();
void SaveDataExtractionTool();
void ExtdataExtractionTool();
void RomFSExtractionTool();
void ShowResult();
bool result = false; /// Result of the last operation.
std::unique_ptr<Ui::UtilitiesDialog> ui;
};
+251 -251
View File
@@ -1,251 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UtilitiesDialog</class>
<widget class="QDialog" name="UtilitiesDialog">
<property name="windowTitle">
<string>threeSD Utilities</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="title">
<string>Encryption</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="useSdDecryption">
<property name="text">
<string>Use SD Decryption (check this when your files are directly from SD Card)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>boot9.bin</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="boot9Path"/>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="boot9PathExplore">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>movable.sed</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="movableSedPath"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="movableSedPathExplore">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>SDMC Root</string>
</property>
<property name="toolTip">
<string>Path to "Nintendo 3DS/&lt;ID0>/&lt;ID1>" folder.</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="sdmcPath"/>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="sdmcPathExplore">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>SD Decryption</string>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="sdDecryptionLabel">
<property name="text">
<string>Decrypt files from your SD Card.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sdDecryptionDisabledLabel">
<property name="text">
<string>SD Decryption must be enabled to use this tool.</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="sdDecryption">
<property name="text">
<string>Open...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Save Data Extraction</string>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="savedataExtractionLabel">
<property name="text">
<string>Extract 3DS SD Savegames.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="savedataExtractionDisabledLabel">
<property name="text">
<string>SD Decryption must be enabled to use this tool.</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="savedataExtraction">
<property name="text">
<string>Open...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Extdata Extraction</string>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="extdataExtractionLabel">
<property name="text">
<string>Extract 3DS Extra Data.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="extdataExtractionDisabledLabel">
<property name="text">
<string>SD Decryption must be enabled to use this tool.</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="extdataExtraction">
<property name="text">
<string>Open...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>RomFS Extraction</string>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="romfsExtractionLabel">
<property name="text">
<string>Extract shared RomFS from NCCH. Useful for System Archives.</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="romfsExtractionDisabledLabel">
<property name="text">
<string>SD Decryption must be disabled to use this tool.</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="romfsExtraction">
<property name="text">
<string>Open...</string>
</property>
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UtilitiesDialog</class>
<widget class="QDialog" name="UtilitiesDialog">
<property name="windowTitle">
<string>threeSD Utilities</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="title">
<string>Encryption</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="useSdDecryption">
<property name="text">
<string>Use SD Decryption (check this when your files are directly from SD Card)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>boot9.bin</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="boot9Path"/>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="boot9PathExplore">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>movable.sed</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="movableSedPath"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="movableSedPathExplore">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>SDMC Root</string>
</property>
<property name="toolTip">
<string>Path to "Nintendo 3DS/&lt;ID0>/&lt;ID1>" folder.</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="sdmcPath"/>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="sdmcPathExplore">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>SD Decryption</string>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="sdDecryptionLabel">
<property name="text">
<string>Decrypt files from your SD Card.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sdDecryptionDisabledLabel">
<property name="text">
<string>SD Decryption must be enabled to use this tool.</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="sdDecryption">
<property name="text">
<string>Open...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Save Data Extraction</string>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="savedataExtractionLabel">
<property name="text">
<string>Extract 3DS SD Savegames.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="savedataExtractionDisabledLabel">
<property name="text">
<string>SD Decryption must be enabled to use this tool.</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="savedataExtraction">
<property name="text">
<string>Open...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Extdata Extraction</string>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="extdataExtractionLabel">
<property name="text">
<string>Extract 3DS Extra Data.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="extdataExtractionDisabledLabel">
<property name="text">
<string>SD Decryption must be enabled to use this tool.</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="extdataExtraction">
<property name="text">
<string>Open...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>RomFS Extraction</string>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="romfsExtractionLabel">
<property name="text">
<string>Extract shared RomFS from NCCH. Useful for System Archives.</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="romfsExtractionDisabledLabel">
<property name="text">
<string>SD Decryption must be disabled to use this tool.</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="romfsExtraction">
<property name="text">
<string>Open...</string>
</property>
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</ui>