diff --git a/src/citron/configuration/configure_per_game.cpp b/src/citron/configuration/configure_per_game.cpp index ad1c58a2b..dc732c8b3 100644 --- a/src/citron/configuration/configure_per_game.cpp +++ b/src/citron/configuration/configure_per_game.cpp @@ -14,14 +14,17 @@ #include #include +#include #include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -64,72 +67,81 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name_, std::vector& vk_device_records, Core::System& system_) -: QDialog(parent), -ui(std::make_unique()), title_id{title_id_}, file_name{file_name_}, system{system_}, -builder{std::make_unique(this, !system_.IsPoweredOn())}, -tab_group{std::make_shared>()} , -rainbow_timer{new QTimer(this)} { + : QDialog(parent), ui(std::make_unique()), title_id{title_id_}, + file_name{file_name_}, system{system_}, + builder{std::make_unique(this, !system_.IsPoweredOn())}, + tab_group{std::make_shared>()}, + rainbow_timer{new QTimer(this)} { + + ui->setupUi(this); + const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) - : fmt::format("{:016X}", title_id); + : fmt::format("{:016X}", title_id); game_config = std::make_unique(config_file_name, Config::ConfigType::PerGameConfig); + + // Create tab instances addons_tab = std::make_unique(system_, this); audio_tab = std::make_unique(system_, tab_group, *builder, this); cpu_tab = std::make_unique(system_, tab_group, *builder, this); graphics_advanced_tab = - std::make_unique(system_, tab_group, *builder, this); + std::make_unique(system_, tab_group, *builder, this); graphics_tab = std::make_unique( system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, - [](Settings::AspectRatio, Settings::ResolutionSetup) {}, tab_group, *builder, this); + [](Settings::AspectRatio, Settings::ResolutionSetup) {}, tab_group, *builder, this); input_tab = std::make_unique(system_, game_config.get(), this); linux_tab = std::make_unique(system_, tab_group, *builder, this); system_tab = std::make_unique(system_, tab_group, *builder, this); - ui->setupUi(this); - if (!UISettings::values.per_game_configure_geometry.isEmpty()) { restoreGeometry(UISettings::values.per_game_configure_geometry); } - ApplyStaticTheme(); - UpdateTheme(); // Run once to set initial colors + UpdateTheme(); connect(rainbow_timer, &QTimer::timeout, this, &ConfigurePerGame::UpdateTheme); - setMinimumHeight(400); + button_group = new QButtonGroup(this); + button_group->setExclusive(true); - layout()->setSizeConstraint(QLayout::SetDefaultConstraint); + const auto add_tab = [&](QWidget* widget, const QString& title) { + auto button = new QPushButton(title, this); + button->setCheckable(true); + button->setObjectName(QStringLiteral("tabButton")); + button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); - ui->tabWidget->addTab(addons_tab.get(), tr("Add-Ons")); + ui->tabButtonsLayout->addWidget(button); + button_group->addButton(button); - QScrollArea* system_scroll_area = new QScrollArea(this); - system_scroll_area->setWidgetResizable(true); - system_scroll_area->setWidget(system_tab.get()); - ui->tabWidget->addTab(system_scroll_area, tr("System")); + QScrollArea* scroll_area = new QScrollArea(this); + scroll_area->setWidgetResizable(true); + scroll_area->setWidget(widget); + ui->stackedWidget->addWidget(scroll_area); - ui->tabWidget->addTab(cpu_tab.get(), tr("CPU")); + connect(button, &QPushButton::clicked, this, [this, scroll_area]() { + ui->stackedWidget->setCurrentWidget(scroll_area); + }); + }; - QScrollArea* graphics_scroll_area = new QScrollArea(this); - graphics_scroll_area->setWidgetResizable(true); - graphics_scroll_area->setWidget(graphics_tab.get()); - ui->tabWidget->addTab(graphics_scroll_area, tr("Graphics")); - - QScrollArea* graphics_advanced_scroll_area = new QScrollArea(this); - graphics_advanced_scroll_area->setWidgetResizable(true); - graphics_advanced_scroll_area->setWidget(graphics_advanced_tab.get()); - ui->tabWidget->addTab(graphics_advanced_scroll_area, tr("Adv. Graphics")); - - ui->tabWidget->addTab(audio_tab.get(), tr("Audio")); - ui->tabWidget->addTab(input_tab.get(), tr("Input Profiles")); - - linux_tab->setVisible(false); + add_tab(addons_tab.get(), tr("Add-Ons")); + add_tab(system_tab.get(), tr("System")); + add_tab(cpu_tab.get(), tr("CPU")); + add_tab(graphics_tab.get(), tr("Graphics")); + add_tab(graphics_advanced_tab.get(), tr("Adv. Graphics")); + add_tab(audio_tab.get(), tr("Audio")); + add_tab(input_tab.get(), tr("Input Profiles")); #ifdef __unix__ - linux_tab->setVisible(true); - ui->tabWidget->addTab(linux_tab.get(), tr("Linux")); + add_tab(linux_tab.get(), tr("Linux")); #endif + ui->tabButtonsLayout->addStretch(); + + if (auto first_button = qobject_cast(button_group->buttons().first())) { + first_button->setChecked(true); + first_button->click(); + } + setFocusPolicy(Qt::ClickFocus); setWindowTitle(tr("Properties")); - addons_tab->SetTitleId(title_id); scene = new QGraphicsScene; @@ -137,11 +149,9 @@ rainbow_timer{new QTimer(this)} { if (system.IsPoweredOn()) { QPushButton* apply_button = ui->buttonBox->addButton(QDialogButtonBox::Apply); - connect(apply_button, &QAbstractButton::clicked, this, - &ConfigurePerGame::HandleApplyButtonClicked); + connect(apply_button, &QAbstractButton::clicked, this, &ConfigurePerGame::HandleApplyButtonClicked); } - // Connect trim XCI button connect(ui->trim_xci_button, &QPushButton::clicked, this, &ConfigurePerGame::OnTrimXCI); LoadConfiguration(); @@ -166,20 +176,18 @@ void ConfigurePerGame::ApplyConfiguration() { if (Settings::IsDockedMode() && Settings::values.players.GetValue()[0].controller_type == Settings::ControllerType::Handheld) { Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld); - Settings::values.use_docked_mode.SetGlobal(true); - } + Settings::values.use_docked_mode.SetGlobal(true); + } - system.ApplySettings(); - Settings::LogSettings(); - - game_config->SaveAllValues(); + system.ApplySettings(); + Settings::LogSettings(); + game_config->SaveAllValues(); } void ConfigurePerGame::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); } - QDialog::changeEvent(event); } @@ -197,89 +205,46 @@ void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file_) { LoadConfiguration(); } -void ConfigurePerGame::ApplyStaticTheme() { - QString raw_stylesheet = property("templateStyleSheet").toString(); - QString processed_stylesheet = raw_stylesheet; - - QColor accent_color(Theme::GetAccentColor()); - QString accent_color_hover = accent_color.lighter(115).name(QColor::HexRgb); - QString accent_color_pressed = accent_color.darker(120).name(QColor::HexRgb); - - processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color.name(QColor::HexRgb)); - processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover); - processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed); - - setStyleSheet(processed_stylesheet); - // Pass the processed stylesheet to the child tabs ONCE - graphics_tab->SetTemplateStyleSheet(processed_stylesheet); - system_tab->SetTemplateStyleSheet(processed_stylesheet); - audio_tab->SetTemplateStyleSheet(processed_stylesheet); - cpu_tab->SetTemplateStyleSheet(processed_stylesheet); - graphics_advanced_tab->SetTemplateStyleSheet(processed_stylesheet); -} - void ConfigurePerGame::UpdateTheme() { - if (!UISettings::values.enable_rainbow_mode.GetValue()) { + QString accent_color_str; + if (UISettings::values.enable_rainbow_mode.GetValue()) { + rainbow_hue += 0.003f; + if (rainbow_hue > 1.0f) { + rainbow_hue = 0.0f; + } + QColor accent_color = QColor::fromHsvF(rainbow_hue, 0.8f, 1.0f); + accent_color_str = accent_color.name(QColor::HexRgb); + if (!rainbow_timer->isActive()) { + rainbow_timer->start(150); + } + } else { if (rainbow_timer->isActive()) { rainbow_timer->stop(); - ApplyStaticTheme(); } - return; + accent_color_str = Theme::GetAccentColor(); } - rainbow_hue += 0.003f; // Even slower color transition for better performance - if (rainbow_hue > 1.0f) { - rainbow_hue = 0.0f; + QColor accent_color(accent_color_str); + const QString accent_color_hover = accent_color.lighter(115).name(QColor::HexRgb); + const QString accent_color_pressed = accent_color.darker(120).name(QColor::HexRgb); + + static QString cached_template_style_sheet; + if (cached_template_style_sheet.isEmpty()) { + cached_template_style_sheet = property("templateStyleSheet").toString(); } - QColor accent_color = QColor::fromHsvF(rainbow_hue, 0.8f, 1.0f); - QColor accent_color_hover = accent_color.lighter(115); - QColor accent_color_pressed = accent_color.darker(120); + QString style_sheet = cached_template_style_sheet; + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color_str); + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover); + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed); + setStyleSheet(style_sheet); - // Cache color names to avoid repeated string operations - const QString accent_color_name = accent_color.name(QColor::HexRgb); - const QString accent_color_hover_name = accent_color_hover.name(QColor::HexRgb); - const QString accent_color_pressed_name = accent_color_pressed.name(QColor::HexRgb); - - // Efficiently update only the necessary widgets - QString tab_style = QStringLiteral( - "QTabBar::tab:selected { background-color: %1; border-color: %1; }") - .arg(accent_color_name); - ui->tabWidget->tabBar()->setStyleSheet(tab_style); - - QString button_style = QStringLiteral( - "QPushButton { background-color: %1; color: #ffffff; border: none; padding: 10px 20px; border-radius: 6px; font-weight: bold; min-height: 20px; }" - "QPushButton:hover { background-color: %2; }" - "QPushButton:pressed { background-color: %3; }") - .arg(accent_color_name) - .arg(accent_color_hover_name) - .arg(accent_color_pressed_name); - - ui->buttonBox->button(QDialogButtonBox::Ok)->setStyleSheet(button_style); - ui->buttonBox->button(QDialogButtonBox::Cancel)->setStyleSheet(button_style); - if (auto* apply_button = ui->buttonBox->button(QDialogButtonBox::Apply)) { - apply_button->setStyleSheet(button_style); - } - - // Apply rainbow mode to the Trim XCI button - ui->trim_xci_button->setStyleSheet(button_style); - - // Create a temporary full stylesheet for the child tabs to update their internal widgets - QString child_stylesheet = property("templateStyleSheet").toString(); - child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color_name); - child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover_name); - child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed_name); - - // Pass the updated stylesheet to the child tabs - graphics_tab->SetTemplateStyleSheet(child_stylesheet); - system_tab->SetTemplateStyleSheet(child_stylesheet); - audio_tab->SetTemplateStyleSheet(child_stylesheet); - cpu_tab->SetTemplateStyleSheet(child_stylesheet); - graphics_advanced_tab->SetTemplateStyleSheet(child_stylesheet); - - if (!rainbow_timer->isActive()) { - rainbow_timer->start(150); // Further optimized 150ms interval for better performance - } + // This is the crucial part that passes the theme to the child tabs + graphics_tab->SetTemplateStyleSheet(style_sheet); + system_tab->SetTemplateStyleSheet(style_sheet); + audio_tab->SetTemplateStyleSheet(style_sheet); + cpu_tab->SetTemplateStyleSheet(style_sheet); + graphics_advanced_tab->SetTemplateStyleSheet(style_sheet); } void ConfigurePerGame::LoadConfiguration() { @@ -294,145 +259,108 @@ void ConfigurePerGame::LoadConfiguration() { const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), system.GetContentProvider()}; - const auto control = pm.GetControlMetadata(); - const auto loader = Loader::GetLoader(system, file); + const auto control = pm.GetControlMetadata(); + const auto loader = Loader::GetLoader(system, file); - if (control.first != nullptr) { - ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); - ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); - ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); - } else { - std::string title; - if (loader->ReadTitle(title) == Loader::ResultStatus::Success) - ui->display_name->setText(QString::fromStdString(title)); + if (control.first != nullptr) { + ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); + ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); + ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); + } else { + std::string title; + if (loader->ReadTitle(title) == Loader::ResultStatus::Success) + ui->display_name->setText(QString::fromStdString(title)); - FileSys::NACP nacp; - if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) - ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); + FileSys::NACP nacp; + if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) + ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); - ui->display_version->setText(QStringLiteral("1.0.0")); + ui->display_version->setText(QStringLiteral("1.0.0")); + } + + bool has_icon = false; + if (control.second != nullptr) { + const auto bytes = control.second->ReadAllBytes(); + if (map.loadFromData(bytes.data(), static_cast(bytes.size()))) { + has_icon = true; } - - if (control.second != nullptr) { - scene->clear(); - - QPixmap map; - const auto bytes = control.second->ReadAllBytes(); - map.loadFromData(bytes.data(), static_cast(bytes.size())); - - scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } else { - std::vector bytes; - if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { - scene->clear(); - - QPixmap map; - map.loadFromData(bytes.data(), static_cast(bytes.size())); - - scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } else { + std::vector bytes; + if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { + if (map.loadFromData(bytes.data(), static_cast(bytes.size()))) { + has_icon = true; } } + } - ui->display_filename->setText(QString::fromStdString(file->GetName())); + if (has_icon) { + scene->clear(); + scene->addPixmap(map); + ui->icon_view->fitInView(scene->itemsBoundingRect(), Qt::KeepAspectRatio); + } - ui->display_format->setText( - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + ui->display_filename->setText(QString::fromStdString(file->GetName())); + ui->display_format->setText( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + const auto valueText = ReadableByteSize(file->GetSize()); + ui->display_size->setText(valueText); - const auto valueText = ReadableByteSize(file->GetSize()); - ui->display_size->setText(valueText); + std::string base_build_id_hex; + std::string update_build_id_hex; + const auto file_type = loader->GetFileType(); - std::string base_build_id_hex; - std::string update_build_id_hex; - - const auto file_type = loader->GetFileType(); - - if (file_type == Loader::FileType::NSO) { - if (file->GetSize() >= 0x100) { + if (file_type == Loader::FileType::NSO) { + if (file->GetSize() >= 0x100) { + std::array header_data{}; + if (file->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { + std::array build_id{}; + std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); + base_build_id_hex = Common::HexToString(build_id, false); + } + } + } else if (file_type == Loader::FileType::DeconstructedRomDirectory) { + const auto main_dir = file->GetContainingDirectory(); + if (main_dir) { + const auto main_nso = main_dir->GetFile("main"); + if (main_nso && main_nso->GetSize() >= 0x100) { std::array header_data{}; - if (file->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { + if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { std::array build_id{}; std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); base_build_id_hex = Common::HexToString(build_id, false); } } - } else if (file_type == Loader::FileType::DeconstructedRomDirectory) { - const auto main_dir = file->GetContainingDirectory(); - if (main_dir) { - const auto main_nso = main_dir->GetFile("main"); - if (main_nso && main_nso->GetSize() >= 0x100) { - std::array header_data{}; - if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { - std::array build_id{}; - std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); - base_build_id_hex = Common::HexToString(build_id, false); - } - } - } - } else { - try { - if (file_type == Loader::FileType::XCI) { - try { - FileSys::XCI xci_temp(file); - if (xci_temp.GetStatus() == Loader::ResultStatus::Success) { - FileSys::XCI xci(file, title_id, 0); - if (xci.GetStatus() == Loader::ResultStatus::Success) { - auto program_nca = xci.GetNCAByType(FileSys::NCAContentType::Program); - if (program_nca && program_nca->GetStatus() == Loader::ResultStatus::Success) { - auto exefs = program_nca->GetExeFS(); - if (exefs) { - auto main_nso = exefs->GetFile("main"); - if (main_nso && main_nso->GetSize() >= 0x100) { - std::array header_data{}; - if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { - std::array build_id{}; - std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); - base_build_id_hex = Common::HexToString(build_id, false); - } + } + } else { + try { + if (file_type == Loader::FileType::XCI) { + try { + FileSys::XCI xci_temp(file); + if (xci_temp.GetStatus() == Loader::ResultStatus::Success) { + FileSys::XCI xci(file, title_id, 0); + if (xci.GetStatus() == Loader::ResultStatus::Success) { + auto program_nca = xci.GetNCAByType(FileSys::NCAContentType::Program); + if (program_nca && program_nca->GetStatus() == Loader::ResultStatus::Success) { + auto exefs = program_nca->GetExeFS(); + if (exefs) { + auto main_nso = exefs->GetFile("main"); + if (main_nso && main_nso->GetSize() >= 0x100) { + std::array header_data{}; + if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { + std::array build_id{}; + std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); + base_build_id_hex = Common::HexToString(build_id, false); } } } } } - } catch (...) { - const auto& content_provider = system.GetContentProvider(); - auto base_nca = content_provider.GetEntry(title_id, FileSys::ContentRecordType::Program); - if (base_nca && base_nca->GetStatus() == Loader::ResultStatus::Success) { - auto exefs = base_nca->GetExeFS(); - if (exefs) { - auto main_nso = exefs->GetFile("main"); - if (main_nso && main_nso->GetSize() >= 0x100) { - std::array header_data{}; - if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { - std::array build_id{}; - std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); - base_build_id_hex = Common::HexToString(build_id, false); - } - } - } - } } - } else if (file_type == Loader::FileType::NSP) { - FileSys::NSP nsp(file); - if (nsp.GetStatus() == Loader::ResultStatus::Success) { - auto exefs = nsp.GetExeFS(); - if (exefs) { - auto main_nso = exefs->GetFile("main"); - if (main_nso && main_nso->GetSize() >= 0x100) { - std::array header_data{}; - if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { - std::array build_id{}; - std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); - base_build_id_hex = Common::HexToString(build_id, false); - } - } - } - } - } else if (file_type == Loader::FileType::NCA) { - FileSys::NCA nca(file); - if (nca.GetStatus() == Loader::ResultStatus::Success) { - auto exefs = nca.GetExeFS(); + } catch (...) { + const auto& content_provider = system.GetContentProvider(); + auto base_nca = content_provider.GetEntry(title_id, FileSys::ContentRecordType::Program); + if (base_nca && base_nca->GetStatus() == Loader::ResultStatus::Success) { + auto exefs = base_nca->GetExeFS(); if (exefs) { auto main_nso = exefs->GetFile("main"); if (main_nso && main_nso->GetSize() >= 0x100) { @@ -446,124 +374,160 @@ void ConfigurePerGame::LoadConfiguration() { } } } - } catch (...) { + } else if (file_type == Loader::FileType::NSP) { + FileSys::NSP nsp(file); + if (nsp.GetStatus() == Loader::ResultStatus::Success) { + auto exefs = nsp.GetExeFS(); + if (exefs) { + auto main_nso = exefs->GetFile("main"); + if (main_nso && main_nso->GetSize() >= 0x100) { + std::array header_data{}; + if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { + std::array build_id{}; + std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); + base_build_id_hex = Common::HexToString(build_id, false); + } + } + } + } + } else if (file_type == Loader::FileType::NCA) { + FileSys::NCA nca(file); + if (nca.GetStatus() == Loader::ResultStatus::Success) { + auto exefs = nca.GetExeFS(); + if (exefs) { + auto main_nso = exefs->GetFile("main"); + if (main_nso && main_nso->GetSize() >= 0x100) { + std::array header_data{}; + if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { + std::array build_id{}; + std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); + base_build_id_hex = Common::HexToString(build_id, false); + } + } + } + } } - } - - try { - const FileSys::PatchManager pm_update{title_id, system.GetFileSystemController(), - system.GetContentProvider()}; - - const auto update_version = pm_update.GetGameVersion(); - if (update_version.has_value() && update_version.value() > 0) { - const auto& content_provider = system.GetContentProvider(); - const auto update_title_id = FileSys::GetUpdateTitleID(title_id); - auto update_nca = content_provider.GetEntry(update_title_id, FileSys::ContentRecordType::Program); - - if (update_nca && update_nca->GetStatus() == Loader::ResultStatus::Success) { - auto exefs = update_nca->GetExeFS(); - if (exefs) { - auto main_nso = exefs->GetFile("main"); - if (main_nso && main_nso->GetSize() >= 0x100) { - std::array header_data{}; - if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { - std::array build_id{}; - std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); - update_build_id_hex = Common::HexToString(build_id, false); - } - } - } - } - } - - if (update_build_id_hex.empty()) { - const auto& content_provider = system.GetContentProvider(); - const auto update_title_id = FileSys::GetUpdateTitleID(title_id); - auto update_nca = content_provider.GetEntry(update_title_id, FileSys::ContentRecordType::Program); - - if (update_nca && update_nca->GetStatus() == Loader::ResultStatus::Success) { - auto exefs = update_nca->GetExeFS(); - if (exefs) { - auto main_nso = exefs->GetFile("main"); - if (main_nso && main_nso->GetSize() >= 0x100) { - std::array header_data{}; - if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { - std::array build_id{}; - std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); - update_build_id_hex = Common::HexToString(build_id, false); - } - } - } - } - } - - if (update_build_id_hex.empty()) { - const auto patches = pm_update.GetPatches(); - for (const auto& patch : patches) { - if (patch.type == FileSys::PatchType::Update && patch.enabled) { - break; - } - } - } } catch (...) { } + } - const auto& system_build_id = system.GetApplicationProcessBuildID(); - const auto system_build_id_hex = Common::HexToString(system_build_id, false); + try { + const FileSys::PatchManager pm_update{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; - if (!system_build_id_hex.empty() && system_build_id_hex != std::string(64, '0')) { - if (!base_build_id_hex.empty() && system_build_id_hex != base_build_id_hex) { - update_build_id_hex = system_build_id_hex; - } else if (base_build_id_hex.empty()) { - base_build_id_hex = system_build_id_hex; + const auto update_version = pm_update.GetGameVersion(); + if (update_version.has_value() && update_version.value() > 0) { + const auto& content_provider = system.GetContentProvider(); + const auto update_title_id = FileSys::GetUpdateTitleID(title_id); + auto update_nca = content_provider.GetEntry(update_title_id, FileSys::ContentRecordType::Program); + + if (update_nca && update_nca->GetStatus() == Loader::ResultStatus::Success) { + auto exefs = update_nca->GetExeFS(); + if (exefs) { + auto main_nso = exefs->GetFile("main"); + if (main_nso && main_nso->GetSize() >= 0x100) { + std::array header_data{}; + if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { + std::array build_id{}; + std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); + update_build_id_hex = Common::HexToString(build_id, false); + } + } + } } } - bool update_detected = false; - if (update_build_id_hex.empty() && !base_build_id_hex.empty()) { - const auto update_version = pm.GetGameVersion(); - if (update_version.has_value() && update_version.value() > 0) { - update_detected = true; - } + if (update_build_id_hex.empty()) { + const auto& content_provider = system.GetContentProvider(); + const auto update_title_id = FileSys::GetUpdateTitleID(title_id); + auto update_nca = content_provider.GetEntry(update_title_id, FileSys::ContentRecordType::Program); - const auto patches = pm.GetPatches(); + if (update_nca && update_nca->GetStatus() == Loader::ResultStatus::Success) { + auto exefs = update_nca->GetExeFS(); + if (exefs) { + auto main_nso = exefs->GetFile("main"); + if (main_nso && main_nso->GetSize() >= 0x100) { + std::array header_data{}; + if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { + std::array build_id{}; + std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); + update_build_id_hex = Common::HexToString(build_id, false); + } + } + } + } + } + + if (update_build_id_hex.empty()) { + const auto patches = pm_update.GetPatches(); for (const auto& patch : patches) { if (patch.type == FileSys::PatchType::Update && patch.enabled) { - update_detected = true; break; } } } + } catch (...) { + } - bool has_base = !base_build_id_hex.empty() && base_build_id_hex != std::string(64, '0'); - bool has_update = !update_build_id_hex.empty() && update_build_id_hex != std::string(64, '0'); + const auto& system_build_id = system.GetApplicationProcessBuildID(); + const auto system_build_id_hex = Common::HexToString(system_build_id, false); - if (has_base) { - ui->display_build_id->setText(QString::fromStdString(base_build_id_hex)); - } else { - ui->display_build_id->setText(tr("Not Available")); + if (!system_build_id_hex.empty() && system_build_id_hex != std::string(64, '0')) { + if (!base_build_id_hex.empty() && system_build_id_hex != base_build_id_hex) { + update_build_id_hex = system_build_id_hex; + } else if (base_build_id_hex.empty()) { + base_build_id_hex = system_build_id_hex; + } + } + + bool update_detected = false; + if (update_build_id_hex.empty() && !base_build_id_hex.empty()) { + const auto update_version = pm.GetGameVersion(); + if (update_version.has_value() && update_version.value() > 0) { + update_detected = true; } - if (has_update) { - ui->display_update_build_id->setText(QString::fromStdString(update_build_id_hex)); - } else if (update_detected) { - ui->display_update_build_id->setText(tr("Available (Run game to show)")); - } else { - ui->display_update_build_id->setText(tr("Not Available")); + const auto patches = pm.GetPatches(); + for (const auto& patch : patches) { + if (patch.type == FileSys::PatchType::Update && patch.enabled) { + update_detected = true; + break; + } } + } + + bool has_base = !base_build_id_hex.empty() && base_build_id_hex != std::string(64, '0'); + bool has_update = !update_build_id_hex.empty() && update_build_id_hex != std::string(64, '0'); + + if (has_base) { + ui->display_build_id->setText(QString::fromStdString(base_build_id_hex)); + } else { + ui->display_build_id->setText(tr("Not Available")); + } + + if (has_update) { + ui->display_update_build_id->setText(QString::fromStdString(update_build_id_hex)); + } else if (update_detected) { + ui->display_update_build_id->setText(tr("Available (Run game to show)")); + } else { + ui->display_update_build_id->setText(tr("Not Available")); + } +} + +void ConfigurePerGame::resizeEvent(QResizeEvent* event) { + QDialog::resizeEvent(event); + if (scene && !scene->items().isEmpty()) { + ui->icon_view->fitInView(scene->itemsBoundingRect(), Qt::KeepAspectRatio); + } } void ConfigurePerGame::OnTrimXCI() { - // Use the stored file name from the constructor if (file_name.empty()) { QMessageBox::warning(this, tr("Trim XCI File"), tr("No file path available.")); return; } - // Convert to filesystem path with proper Unicode support const std::filesystem::path filepath = file_name; - - // Check if the file is an XCI file const std::string extension = filepath.extension().string(); if (extension != ".xci" && extension != ".XCI") { QMessageBox::warning(this, tr("Trim XCI File"), @@ -571,14 +535,12 @@ void ConfigurePerGame::OnTrimXCI() { return; } - // Check if file exists if (!std::filesystem::exists(filepath)) { QMessageBox::warning(this, tr("Trim XCI File"), tr("The game file no longer exists.")); return; } - // Initialize the trimmer Common::XCITrimmer trimmer(filepath); if (!trimmer.IsValid()) { QMessageBox::warning(this, tr("Trim XCI File"), @@ -592,7 +554,6 @@ void ConfigurePerGame::OnTrimXCI() { return; } - // Show file information const u64 current_size_mb = trimmer.GetFileSize() / (1024 * 1024); const u64 data_size_mb = trimmer.GetDataSize() / (1024 * 1024); const u64 savings_mb = trimmer.GetDiskSpaceSavings() / (1024 * 1024); @@ -605,7 +566,6 @@ void ConfigurePerGame::OnTrimXCI() { "This will remove unused space from the XCI file." ).arg(current_size_mb).arg(data_size_mb).arg(savings_mb); - // Create custom message box with three options QMessageBox msgBox(this); msgBox.setWindowTitle(tr("Trim XCI File")); msgBox.setText(info_message); @@ -641,31 +601,24 @@ void ConfigurePerGame::OnTrimXCI() { Common::U16StringFromBuffer(output_filename.utf16(), output_filename.size())}; } - // Pre-translate strings for use in lambda const QString checking_text = tr("Checking free space..."); const QString copying_text = tr("Copying file..."); - // Track last operation to detect changes size_t last_total = 0; QString current_operation; - // Show progress dialog QProgressDialog progress_dialog(tr("Preparing to trim XCI file..."), tr("Cancel"), 0, 100, this); progress_dialog.setWindowTitle(tr("Trim XCI File")); progress_dialog.setWindowModality(Qt::WindowModal); progress_dialog.setMinimumDuration(0); progress_dialog.show(); - // Progress callback auto progress_callback = [&](size_t current, size_t total) { if (total > 0) { - // Detect operation change (when total changes significantly) if (total != last_total) { last_total = total; if (current == 0 || current == total) { - // Likely switched operations if (total < current_size_mb * 1024 * 1024) { - // Smaller total = checking padding current_operation = checking_text; } } @@ -674,7 +627,6 @@ void ConfigurePerGame::OnTrimXCI() { const int percent = static_cast((current * 100) / total); progress_dialog.setValue(percent); - // Update label text based on operation if (!current_operation.isEmpty()) { const QString current_mb = QString::number(current / (1024.0 * 1024.0), 'f', 1); const QString total_mb = QString::number(total / (1024.0 * 1024.0), 'f', 1); @@ -695,16 +647,13 @@ void ConfigurePerGame::OnTrimXCI() { QCoreApplication::processEvents(); }; - // Cancel callback auto cancel_callback = [&]() -> bool { return progress_dialog.wasCanceled(); }; - // Perform the trim operation const auto result = trimmer.Trim(progress_callback, cancel_callback, output_path); progress_dialog.close(); - // Show result if (result == Common::XCITrimmer::OperationOutcome::Successful) { const QString success_message = is_save_as ? tr("XCI file successfully trimmed and saved as:\n%1") diff --git a/src/citron/configuration/configure_per_game.h b/src/citron/configuration/configure_per_game.h index c71cdeef3..68d6af024 100644 --- a/src/citron/configuration/configure_per_game.h +++ b/src/citron/configuration/configure_per_game.h @@ -10,6 +10,7 @@ #include #include +#include #include "configuration/shared_widget.h" #include "core/file_sys/vfs/vfs_types.h" @@ -19,12 +20,16 @@ #include "citron/configuration/qt_config.h" #include "citron/configuration/shared_translation.h" +class QButtonGroup; +class QGraphicsScene; +class QTimer; + namespace Core { - class System; +class System; } namespace InputCommon { - class InputSubsystem; +class InputSubsystem; } class ConfigurePerGameAddons; @@ -36,22 +41,14 @@ class ConfigureInputPerGame; class ConfigureLinuxTab; class ConfigureSystem; -class QGraphicsScene; -class QStandardItem; -class QStandardItemModel; -class QTreeView; -class QVBoxLayout; -class QTimer; // Forward declaration for the timer - namespace Ui { - class ConfigurePerGame; +class ConfigurePerGame; } class ConfigurePerGame : public QDialog { Q_OBJECT public: - // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263 explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name_, std::vector& vk_device_records, Core::System& system_); @@ -64,13 +61,14 @@ public slots: void accept() override; void OnTrimXCI(); +protected: + void resizeEvent(QResizeEvent* event) override; + private: void changeEvent(QEvent* event) override; void RetranslateUI(); void HandleApplyButtonClicked(); void LoadConfiguration(); - - // New, efficient theme update functions void ApplyStaticTheme(); void UpdateTheme(); @@ -97,4 +95,7 @@ private: QTimer* rainbow_timer; float rainbow_hue = 0.0f; + + QButtonGroup* button_group; + QPixmap map; }; diff --git a/src/citron/configuration/configure_per_game.ui b/src/citron/configuration/configure_per_game.ui index d6018be9e..73501c5d7 100644 --- a/src/citron/configuration/configure_per_game.ui +++ b/src/citron/configuration/configure_per_game.ui @@ -12,12 +12,12 @@ - 900 - 0 + 640 + 480 - Dialog + Properties QDialog { @@ -96,100 +96,34 @@ width: 0px; } - QPushButton.tabButton { - background-color: #383838; - color: #ffffff; - padding: 10px 14px; - margin: 2px; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - border-bottom-left-radius: 8px; - border-bottom-right-radius: 8px; - min-width: 85px; - max-width: 160px; - font-weight: 500; - border: 1px solid #3d3d3d; - text-align: center; + QPushButton#tabButton { + background-color: #383838; + color: #ffffff; + padding: 8px 18px; + margin-right: 2px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + border-bottom: none; + min-width: 100px; + font-weight: bold; + font-size: 10pt; + border: 1px solid #3d3d3d; } - QPushButton.tabButton:checked { - background-color: %%ACCENT_COLOR%%; - color: #ffffff; - font-weight: bold; - border-color: %%ACCENT_COLOR%%; + QPushButton#tabButton:checked { + background-color: %%ACCENT_COLOR%%; + color: #ffffff; + font-weight: bold; + border-color: %%ACCENT_COLOR%%; } - QPushButton.tabButton:hover:!checked { - background-color: #4d4d4d; - border-color: #5d5d5d; + QPushButton#tabButton:hover:!checked { + background-color: #4d4d4d; + border-color: #5d5d5d; } - QPushButton.tabButton:pressed { - background-color: %%ACCENT_COLOR_PRESSED%%; - } - - QTabWidget { - background-color: #2b2b2b; - border: none; - } - - QTabWidget::pane { - border: 1px solid #3d3d3d; - background-color: #2b2b2b; - border-radius: 8px; - margin: 0px; - padding: 0px; - } - - QTabWidget::tab-bar { - alignment: left; - } - - QTabBar { - background-color: #2b2b2b; - border: none; - } - - QTabBar::tab { - background-color: #383838; - color: #ffffff; - padding: 8px 14px; - margin-right: 2px; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - min-width: 85px; - font-weight: 500; - border: 1px solid #3d3d3d; - border-bottom: none; - } - - QTabBar::tab:selected { - background-color: %%ACCENT_COLOR%%; - color: #ffffff; - font-weight: bold; - border-color: %%ACCENT_COLOR%%; - } - - QTabBar::tab:hover:!selected { - background-color: #4d4d4d; - border-color: #5d5d5d; - } - - QTabBar QToolButton { - background-color: #383838; - border: 1px solid #3d3d3d; - border-radius: 4px; - padding: 4px; - margin: 2px; - } - - QTabBar QToolButton:hover { - background-color: #4d4d4d; - border-color: %%ACCENT_COLOR%%; - } - - QTabBar::scroller { - width: 30px; + QPushButton#tabButton:pressed { + background-color: %%ACCENT_COLOR_PRESSED%%; } QGroupBox { @@ -374,10 +308,20 @@ } QSlider::groove:horizontal { - border: 1px solid #5d5d5d; - height: 8px; - background-color: #3d3d3d; - border-radius: 4px; + height: 8px; + background: #3d3d3d; + border-radius: 4px; + border: 1px solid #5d5d5d; + } + + QSlider::sub-page:horizontal { + background: %%ACCENT_COLOR%%; + border-radius: 4px; + } + + QSlider::add-page:horizontal { + background: #3d3d3d; + border-radius: 4px; } QSlider::handle:horizontal { @@ -447,9 +391,6 @@ 0 - - Qt::ScrollBarAsNeeded - true @@ -459,9 +400,15 @@ 0 0 318 - 538 + 600 + + + 0 + 600 + + 0 @@ -484,7 +431,7 @@ - + 0 0 @@ -516,180 +463,97 @@ - - true - - - true - + true + true - - true - - - true - + true + true - - - Name - - + Name - - - Title ID - - + Title ID - - true - - - true - + true + true - - true - - - true - + true + true - - true - - - true - + true + true - - - Filename - - + Filename - - true - - - true - + true + true - - true - - - true - + true + true - - - Format - - + Format - - - Version - - + Version - - - Size - - + Size - - - Developer - - + Developer - - - Base Build ID - - + Base Build ID - - true - - - true - + true + true - - - Update Build ID - - + Update Build ID - - true - - - true - + true + true - - Trim XCI File - - - Remove unused space from XCI file to reduce file size - + Trim XCI File + Remove unused space from XCI file to reduce file size - - Qt::Vertical - - - - 20 - 40 - - + Qt::Vertical + 2040 @@ -702,16 +566,19 @@ - - - - + - + 0 0 + + + 16777215 + 60 + + Qt::ScrollBarAlwaysOff @@ -721,45 +588,37 @@ true - - - - 0 - 0 - 560 - 538 - - - - - - - true - - - - 0 - 0 - - - - -1 - - - false - - - false - - - false - - - + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + +