mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 16:49:04 +00:00
Normalize line endings that got messed up for whatever reason
This commit is contained in:
+88
-88
@@ -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()
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.<br>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.<br>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.<br>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.<br>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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
+839
-839
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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()};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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/<ID0>/<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/<ID0>/<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
@@ -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
@@ -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/<ID0>/<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/<ID0>/<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>
|
||||
|
||||
Reference in New Issue
Block a user