diff --git a/.gitmodules b/.gitmodules index e7c38da..1259297 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "cryptopp"] path = externals/cryptopp/cryptopp url = https://github.com/weidai11/cryptopp.git +[submodule "qdevicewatcher"] + path = externals/qdevicewatcher/qdevicewatcher + url = https://github.com/wang-bin/qdevicewatcher.git diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 818c4fd..06ae28d 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -1,2 +1,8 @@ +# Crypto++ add_subdirectory(cryptopp) + +# fmt add_subdirectory(fmt) + +# QDeviceWatcher +add_subdirectory(qdevicewatcher) diff --git a/externals/qdevicewatcher/CMakeLists.txt b/externals/qdevicewatcher/CMakeLists.txt new file mode 100644 index 0000000..0d9f730 --- /dev/null +++ b/externals/qdevicewatcher/CMakeLists.txt @@ -0,0 +1,26 @@ +set(CMAKE_AUTOMOC ON) + +add_definitions(-DUNICODE -D_UNICODE) + +add_library(qdevicewatcher STATIC + qdevicewatcher/src/qdevicewatcher_p.h + qdevicewatcher/src/qdevicewatcher.cpp + qdevicewatcher/src/qdevicewatcher.h +) + +target_link_libraries(qdevicewatcher PRIVATE Qt5::Core) +target_include_directories(qdevicewatcher INTERFACE qdevicewatcher/src) + +if (WIN32) + target_sources(qdevicewatcher PRIVATE + qdevicewatcher/src/qdevicewatcher_win32.cpp + ) +elseif(APPLE) + target_sources(qdevicewatcher PRIVATE + qdevicewatcher/src/qdevicewatcher_mac.cpp + ) +elseif(UNIX) + target_sources(qdevicewatcher PRIVATE + qdevicewatcher/src/qdevicewatcher_linux.cpp + ) +endif() diff --git a/externals/qdevicewatcher/qdevicewatcher b/externals/qdevicewatcher/qdevicewatcher new file mode 160000 index 0000000..f873229 --- /dev/null +++ b/externals/qdevicewatcher/qdevicewatcher @@ -0,0 +1 @@ +Subproject commit f8732294685a227f40700eca61e7c370defa1efa diff --git a/src/common/logging/log.h b/src/common/logging/log.h index bdb1a15..4aa6838 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -14,6 +14,8 @@ #include #include "common/string_util.h" +#include + template void PrintLog(std::FILE* f, const std::string& log_class, const std::string& level, const std::string& color, const std::string& file, int line, const std::string& func, @@ -23,9 +25,13 @@ void PrintLog(std::FILE* f, const std::string& log_class, const std::string& lev std::chrono::steady_clock::now() - time_origin) .count(); const auto real_class = Common::ReplaceAll(log_class, "_", "."); - fmt::print(f, "\x1b{}[{:12.6f}] {} <{}> {}:{}:{}: " + format + "\x1b[0m\n", color, - us / 1000000.0, log_class, level, file, line, func, args...); - fflush(stderr); + try { + fmt::print(f, "\x1b{}[{:12.6f}] {} <{}> {}:{}:{}: " + format + "\x1b[0m\n", color, + us / 1000000.0, real_class, level, file, line, func, args...); + fflush(stderr); + } catch (...) { + std::cerr << "FMT failed with exception" << std::endl; + } } #ifdef _DEBUG diff --git a/src/common/misc.cpp b/src/common/misc.cpp index 68cb86c..8dc54fb 100644 --- a/src/common/misc.cpp +++ b/src/common/misc.cpp @@ -3,11 +3,11 @@ // Refer to the license.txt file included. #include +#include #ifdef _WIN32 #include #else #include -#include #endif #include "common/common_funcs.h" @@ -27,5 +27,5 @@ std::string GetLastErrorMsg() { strerror_r(errno, err_str, buff_size); #endif - return std::string(err_str, buff_size); + return std::string(err_str, strnlen(err_str, buff_size)); } diff --git a/src/core/data_container.cpp b/src/core/data_container.cpp index 087a595..2529e95 100644 --- a/src/core/data_container.cpp +++ b/src/core/data_container.cpp @@ -6,6 +6,8 @@ #include "common/assert.h" #include "core/data_container.h" +namespace Core { + constexpr u32 MakeMagic(char a, char b, char c, char d) { return a | b << 8 | c << 16 | d << 24; } @@ -150,3 +152,5 @@ std::vector> DataContainer::GetIVFCLevel4Data() const { return {GetPartitionData(0), GetPartitionData(1)}; } } + +} // namespace Core diff --git a/src/core/data_container.h b/src/core/data_container.h index 9cf36cd..18d09cf 100644 --- a/src/core/data_container.h +++ b/src/core/data_container.h @@ -10,6 +10,8 @@ #include "common/common_types.h" #include "common/swap.h" +namespace Core { + #pragma pack(push, 1) struct DataDescriptor { u64_le offset; @@ -127,3 +129,5 @@ private: std::vector partition_descriptors; std::vector partitions; }; + +} // namespace Core diff --git a/src/core/decryptor.cpp b/src/core/decryptor.cpp index e0a6475..ff20a87 100644 --- a/src/core/decryptor.cpp +++ b/src/core/decryptor.cpp @@ -15,6 +15,8 @@ #include "core/decryptor.h" #include "core/key/key.h" +namespace Core { + SDMCDecryptor::SDMCDecryptor(const std::string& root_folder_) : root_folder(root_folder_) { ASSERT_MSG(Key::IsNormalKeyAvailable(Key::SDKey), "SD Key must be available in order to decrypt"); @@ -89,3 +91,5 @@ std::vector SDMCDecryptor::DecryptFile(const std::string& source) const { aes.ProcessData(data.data(), encrypted_data.data(), encrypted_data.size()); return data; } + +} // namespace Core diff --git a/src/core/decryptor.h b/src/core/decryptor.h index c4d4540..c4dc91f 100644 --- a/src/core/decryptor.h +++ b/src/core/decryptor.h @@ -8,6 +8,8 @@ #include #include "common/common_types.h" +namespace Core { + class SDMCDecryptor { public: /** @@ -35,3 +37,5 @@ public: private: std::string root_folder; }; + +} // namespace Core diff --git a/src/core/importer.cpp b/src/core/importer.cpp index 1a0bf5d..04213b9 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -11,6 +11,8 @@ #include "core/inner_fat.h" #include "core/key/key.h" +namespace Core { + SDMCImporter::SDMCImporter(const Config& config_) : config(config_) { is_good = Init(); } @@ -305,7 +307,7 @@ std::vector LoadPresetConfig(std::string mount_point) { nullptr, mount_point + "Nintendo 3DS/", [&id_regex, &ProcessDirectory](u64* /*num_entries_out*/, const std::string& directory, const std::string& virtual_name) { - if (!FileUtil::IsDirectory(directory + virtual_name)) { + if (!FileUtil::IsDirectory(directory + virtual_name + "/")) { return true; } @@ -313,8 +315,10 @@ std::vector LoadPresetConfig(std::string mount_point) { return true; } - return ProcessDirectory(directory + virtual_name); + return ProcessDirectory(directory + virtual_name + "/"); }); return out; } + +} // namespace Core diff --git a/src/core/importer.h b/src/core/importer.h index 9707654..5a495db 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -9,6 +9,8 @@ #include #include "common/common_types.h" +namespace Core { + class SDMCDecryptor; /** @@ -99,3 +101,5 @@ private: * @return a list of preset config available. can be empty */ std::vector LoadPresetConfig(std::string mount_point); + +} // namespace Core diff --git a/src/core/inner_fat.cpp b/src/core/inner_fat.cpp index 5e12be4..4b6839a 100644 --- a/src/core/inner_fat.cpp +++ b/src/core/inner_fat.cpp @@ -9,6 +9,8 @@ #include "core/decryptor.h" #include "core/inner_fat.h" +namespace Core { + constexpr u32 MakeMagic(char a, char b, char c, char d) { return a | b << 8 | c << 16 | d << 24; } @@ -334,3 +336,5 @@ ArchiveFormatInfo SDExtdata::GetFormatInfo() const { return format_info; } + +} // namespace Core diff --git a/src/core/inner_fat.h b/src/core/inner_fat.h index 7fc5d1c..d5a198f 100644 --- a/src/core/inner_fat.h +++ b/src/core/inner_fat.h @@ -4,6 +4,8 @@ #pragma once +namespace Core { + #include #include #include @@ -194,3 +196,5 @@ private: std::string data_path; const SDMCDecryptor& decryptor; }; + +} // namespace Core diff --git a/src/core/key/arithmetic128.cpp b/src/core/key/arithmetic128.cpp index 9bd6104..fb2a16d 100644 --- a/src/core/key/arithmetic128.cpp +++ b/src/core/key/arithmetic128.cpp @@ -6,7 +6,7 @@ #include #include "core/key/arithmetic128.h" -namespace Key { +namespace Core::Key { AESKey Lrot128(const AESKey& in, u32 rot) { AESKey out; @@ -56,4 +56,4 @@ AESKey Xor128(const AESKey& a, const AESKey& b) { return out; } -} // namespace Key +} // namespace Core::Key diff --git a/src/core/key/arithmetic128.h b/src/core/key/arithmetic128.h index 3d3f2ee..7a1df44 100644 --- a/src/core/key/arithmetic128.h +++ b/src/core/key/arithmetic128.h @@ -7,11 +7,11 @@ #include "common/common_types.h" #include "core/key/key.h" -namespace Key { +namespace Core::Key { AESKey Lrot128(const AESKey& in, u32 rot); AESKey Add128(const AESKey& a, const AESKey& b); AESKey Add128(const AESKey& a, u64 b); AESKey Xor128(const AESKey& a, const AESKey& b); -} // namespace Key +} // namespace Core::Key diff --git a/src/core/key/key.cpp b/src/core/key/key.cpp index eb44b4c..7d3b52a 100644 --- a/src/core/key/key.cpp +++ b/src/core/key/key.cpp @@ -11,7 +11,7 @@ #include "core/key/arithmetic128.h" #include "core/key/key.h" -namespace Key { +namespace Core::Key { namespace { @@ -216,4 +216,4 @@ void SelectCommonKeyIndex(u8 index) { key_slots[KeySlotID::TicketCommonKey].SetKeyY(common_key_y_slots.at(index)); } -} // namespace Key +} // namespace Core::Key diff --git a/src/core/key/key.h b/src/core/key/key.h index 1fa4ec3..0fa5c2d 100644 --- a/src/core/key/key.h +++ b/src/core/key/key.h @@ -9,7 +9,7 @@ #include #include "common/common_types.h" -namespace Key { +namespace Core::Key { enum KeySlotID : std::size_t { @@ -69,4 +69,4 @@ AESKey GetNormalKey(std::size_t slot_id); void SelectCommonKeyIndex(u8 index); -} // namespace Key +} // namespace Core::Key diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 43cf8ca..2a369f8 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(threeSD 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) diff --git a/src/frontend/main.cpp b/src/frontend/main.cpp index f900b39..b769aea 100644 --- a/src/frontend/main.cpp +++ b/src/frontend/main.cpp @@ -2,23 +2,128 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include +#include +#include +#include "common/file_util.h" #include "frontend/main.h" #include "ui_main.h" #ifdef __APPLE__ -#include #include #include "common/common_paths.h" -#include "common/file_util.h" #endif MainDialog::MainDialog(QWidget* parent) : QDialog(parent), ui(std::make_unique()) { ui->setupUi(this); + + setFixedWidth(width()); + ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)->setText(tr("Refresh")); + + LoadPresetConfig(); + + connect(ui->advancedButton, &QPushButton::clicked, [this] { + if (ui->customGroupBox->isVisible()) { + HideAdvanced(); + } else { + ShowAdvanced(); + } + }); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { + if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)) { + LoadPresetConfig(); + } + }); + + // 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::LoadPresetConfig() { + ui->configSelect->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]); + ui->configSelect->addItem(QString::fromStdString(list[i].sdmc_path)); + } + } + + if (preset_config_list.empty()) { + // Clear the text + ui->sdmcPath->setText(QString{}); + ui->userPath->setText( + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))); + ui->movableSedPath->setText(QString{}); + ui->bootrom9Path->setText(QString{}); + ui->safeModeFirmPath->setText(QString{}); + ui->seeddbPath->setText(QString{}); + ui->secretSectorPath->setText(QString{}); + + ui->advancedButton->setVisible(false); + ShowAdvanced(); + ui->configSelect->addItem(tr("None")); + ui->configSelect->setCurrentText(tr("None")); + } else { + ui->advancedButton->setVisible(true); + if (ui->customGroupBox->isVisible()) { + HideAdvanced(); + } + } +} + +void MainDialog::ShowAdvanced() { + ui->configSelect->setEnabled(false); + ui->advancedButton->setText(tr("Hide Custom Config")); + ui->customGroupBox->setVisible(true); + + setMaximumHeight(1000000); + adjustSize(); + + const int index = ui->configSelect->currentIndex(); + ui->configSelect->addItem(tr("Custom")); + ui->configSelect->setCurrentText(tr("Custom")); + + if (index == -1) { + return; + } + + // Load preset data + const auto config = preset_config_list[static_cast(index)]; + ui->sdmcPath->setText(QString::fromStdString(config.sdmc_path)); + ui->userPath->setText(QString::fromStdString(config.user_path)); + ui->movableSedPath->setText(QString::fromStdString(config.movable_sed_path)); + ui->bootrom9Path->setText(QString::fromStdString(config.bootrom_path)); + ui->safeModeFirmPath->setText(QString::fromStdString(config.safe_mode_firm_path)); + ui->seeddbPath->setText(QString::fromStdString(config.seed_db_path)); + ui->secretSectorPath->setText(QString::fromStdString(config.secret_sector_path)); +} + +void MainDialog::HideAdvanced() { + ui->configSelect->setEnabled(true); + ui->advancedButton->setText(tr("Customize...")); + ui->customGroupBox->setVisible(false); + + LoadPresetConfig(); + + setMaximumHeight(130); + adjustSize(); +} + int main(int argc, char* argv[]) { // Init settings params QCoreApplication::setOrganizationName("zhaowenlan1779"); diff --git a/src/frontend/main.h b/src/frontend/main.h index 4c1d80f..cc703c1 100644 --- a/src/frontend/main.h +++ b/src/frontend/main.h @@ -6,6 +6,7 @@ #include #include +#include "core/importer.h" namespace Ui { class MainDialog; @@ -19,5 +20,10 @@ public: ~MainDialog() override; private: + void LoadPresetConfig(); + void ShowAdvanced(); + void HideAdvanced(); + + std::vector preset_config_list; std::unique_ptr ui; }; diff --git a/src/frontend/main.ui b/src/frontend/main.ui index e7f4aef..bba8ceb 100644 --- a/src/frontend/main.ui +++ b/src/frontend/main.ui @@ -7,7 +7,7 @@ 0 0 800 - 450 + 130 @@ -26,11 +26,11 @@ - - 0 - 0 - - + + 0 + 0 + + @@ -40,7 +40,7 @@ - Advanced... + Customize... @@ -54,160 +54,159 @@ - + Custom false - - - - - - - SDMC: - - - - - - - - - - ... - - - - + + + + + SDMC (Required) + + + Path to the Nintendo 3DS/<ID0>/<ID1> folder. + + - - - - - - User Directory: - - - - - - - - - - ... - - - - + + + + Path to the Nintendo 3DS/<ID0>/<ID1> folder. + + - - - - - - movable.sed: - - - - - - - - - - ... - - - - + + + + ... + + - - - - - - boot9.bin: - - - - - - - - - - ... - - - - + + + + Citra User Directory (Required) + + + Path to the User Directory of Citra to put imported content into. + + - - - - - - Safe mode firm Path: - - - - - - - - - - ... - - - - + + + + Path to the User Directory of Citra to put imported content into. + + - - - - - - seeddb.bin: - - - - - - - - - - ... - - - - + + + + ... + + - - - - - - sector0x96.bin: - - - - - - - - - - ... - - - - + + + + movable.sed (Required) + + + + + + + + + + + 0 + 0 + + + + ... + + + + + + + boot9.bin (Required) + + + + + + + + + + ... + + + + + + + Safe mode firm + + + Path to the safe mode firm.<br>This is a folder, and needs to contain another folder named 'new' or 'old' corresponding to the system's type.<br>The 'new' or 'old' folder should hold files from the 'content' folder of the firm title in NAND. (i.e. tmd and app) + + + + + + + Path to the safe mode firm.<br>This is a folder, and needs to contain another folder named 'new' or 'old' corresponding to the system's type.<br>The 'new' or 'old' folder should hold files from the 'content' folder of the firm title in NAND. (i.e. tmd and app) + + + + + + + ... + + + + + + + seeddb.bin + + + + + + + + + + ... + + + + + + + sector0x96.bin + + + + + + + + + + ... + +