Merge branch 'fixPropertiesWindowScaling' into 'main'

fix: Properties Window Scaling

See merge request citron/emulator!75
This commit is contained in:
Zephyron
2025-10-03 10:53:00 +10:00
3 changed files with 527 additions and 474 deletions

View File

@@ -53,7 +53,7 @@
<item> <item>
<widget class="QComboBox" name="profile_player_1"> <widget class="QComboBox" name="profile_player_1">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@@ -88,7 +88,7 @@
<item> <item>
<widget class="QComboBox" name="profile_player_2"> <widget class="QComboBox" name="profile_player_2">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@@ -123,7 +123,7 @@
<item> <item>
<widget class="QComboBox" name="profile_player_3"> <widget class="QComboBox" name="profile_player_3">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@@ -158,7 +158,7 @@
<item> <item>
<widget class="QComboBox" name="profile_player_4"> <widget class="QComboBox" name="profile_player_4">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@@ -193,7 +193,7 @@
<item> <item>
<widget class="QComboBox" name="profile_player_5"> <widget class="QComboBox" name="profile_player_5">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@@ -228,7 +228,7 @@
<item> <item>
<widget class="QComboBox" name="profile_player_6"> <widget class="QComboBox" name="profile_player_6">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@@ -263,7 +263,7 @@
<item> <item>
<widget class="QComboBox" name="profile_player_7"> <widget class="QComboBox" name="profile_player_7">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@@ -298,7 +298,7 @@
<item> <item>
<widget class="QComboBox" name="profile_player_8"> <widget class="QComboBox" name="profile_player_8">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>

View File

@@ -51,28 +51,33 @@
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
std::vector<VkDeviceInfo::Record>& vk_device_records, std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_) Core::System& system_)
: QDialog(parent), : QDialog(parent),
ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_}, ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_},
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())}, builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} { tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); 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()) 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<QtConfig>(config_file_name, Config::ConfigType::PerGameConfig); game_config = std::make_unique<QtConfig>(config_file_name, Config::ConfigType::PerGameConfig);
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this); addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this); audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this);
cpu_tab = std::make_unique<ConfigureCpu>(system_, tab_group, *builder, this); cpu_tab = std::make_unique<ConfigureCpu>(system_, tab_group, *builder, this);
graphics_advanced_tab = graphics_advanced_tab =
std::make_unique<ConfigureGraphicsAdvanced>(system_, tab_group, *builder, this); std::make_unique<ConfigureGraphicsAdvanced>(system_, tab_group, *builder, this);
graphics_tab = std::make_unique<ConfigureGraphics>( graphics_tab = std::make_unique<ConfigureGraphics>(
system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, 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<ConfigureInputPerGame>(system_, game_config.get(), this); input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this);
linux_tab = std::make_unique<ConfigureLinuxTab>(system_, tab_group, *builder, this); linux_tab = std::make_unique<ConfigureLinuxTab>(system_, tab_group, *builder, this);
system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this); system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this);
ui->setupUi(this); ui->setupUi(this);
// THIS IS THE NEW FIX: Force a minimum height on the window.
setMinimumHeight(400);
layout()->setSizeConstraint(QLayout::SetDefaultConstraint);
ui->tabWidget->addTab(addons_tab.get(), tr("Add-Ons")); ui->tabWidget->addTab(addons_tab.get(), tr("Add-Ons"));
ui->tabWidget->addTab(system_tab.get(), tr("System")); ui->tabWidget->addTab(system_tab.get(), tr("System"));
ui->tabWidget->addTab(cpu_tab.get(), tr("CPU")); ui->tabWidget->addTab(cpu_tab.get(), tr("CPU"));
@@ -83,10 +88,10 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
// Only show Linux tab on Unix // Only show Linux tab on Unix
linux_tab->setVisible(false); linux_tab->setVisible(false);
#ifdef __unix__ #ifdef __unix__
linux_tab->setVisible(true); linux_tab->setVisible(true);
ui->tabWidget->addTab(linux_tab.get(), tr("Linux")); ui->tabWidget->addTab(linux_tab.get(), tr("Linux"));
#endif #endif
setFocusPolicy(Qt::ClickFocus); setFocusPolicy(Qt::ClickFocus);
setWindowTitle(tr("Properties")); setWindowTitle(tr("Properties"));
@@ -115,15 +120,15 @@ void ConfigurePerGame::ApplyConfiguration() {
input_tab->ApplyConfiguration(); input_tab->ApplyConfiguration();
if (Settings::IsDockedMode() && Settings::values.players.GetValue()[0].controller_type == if (Settings::IsDockedMode() && Settings::values.players.GetValue()[0].controller_type ==
Settings::ControllerType::Handheld) { Settings::ControllerType::Handheld) {
Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::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(); system.ApplySettings();
Settings::LogSettings(); Settings::LogSettings();
game_config->SaveAllValues(); game_config->SaveAllValues();
} }
void ConfigurePerGame::changeEvent(QEvent* event) { void ConfigurePerGame::changeEvent(QEvent* event) {
@@ -159,300 +164,300 @@ void ConfigurePerGame::LoadConfiguration() {
QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper()); QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()}; system.GetContentProvider()};
const auto control = pm.GetControlMetadata(); const auto control = pm.GetControlMetadata();
const auto loader = Loader::GetLoader(system, file); const auto loader = Loader::GetLoader(system, file);
if (control.first != nullptr) { if (control.first != nullptr) {
ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
} else { } else {
std::string title; std::string title;
if (loader->ReadTitle(title) == Loader::ResultStatus::Success) if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
ui->display_name->setText(QString::fromStdString(title)); ui->display_name->setText(QString::fromStdString(title));
FileSys::NACP nacp; FileSys::NACP nacp;
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success)
ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName()));
ui->display_version->setText(QStringLiteral("1.0.0")); ui->display_version->setText(QStringLiteral("1.0.0"));
} }
if (control.second != nullptr) { if (control.second != nullptr) {
scene->clear();
QPixmap map;
const auto bytes = control.second->ReadAllBytes();
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
std::vector<u8> bytes;
if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
scene->clear(); scene->clear();
QPixmap map; QPixmap map;
const auto bytes = control.second->ReadAllBytes();
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} } else {
} std::vector<u8> bytes;
if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
scene->clear();
ui->display_filename->setText(QString::fromStdString(file->GetName())); QPixmap map;
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
ui->display_format->setText( scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
const auto valueText = ReadableByteSize(file->GetSize());
ui->display_size->setText(valueText);
// Display Build ID(s) if available
std::string base_build_id_hex;
std::string update_build_id_hex;
// Try to get build ID based on file type
const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::NSO) {
// For NSO files, read the build ID directly from the header
if (file->GetSize() >= 0x100) {
std::array<u8, 0x100> header_data{};
if (file->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
// Build ID is at offset 0x40 in NSO header
std::array<u8, 0x20> 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) {
// For deconstructed ROM directories, read from the main NSO file ui->display_filename->setText(QString::fromStdString(file->GetName()));
const auto main_dir = file->GetContainingDirectory();
if (main_dir) { ui->display_format->setText(
const auto main_nso = main_dir->GetFile("main"); QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
if (main_nso && main_nso->GetSize() >= 0x100) {
const auto valueText = ReadableByteSize(file->GetSize());
ui->display_size->setText(valueText);
// Display Build ID(s) if available
std::string base_build_id_hex;
std::string update_build_id_hex;
// Try to get build ID based on file type
const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::NSO) {
// For NSO files, read the build ID directly from the header
if (file->GetSize() >= 0x100) {
std::array<u8, 0x100> header_data{}; std::array<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { if (file->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
// Build ID is at offset 0x40 in NSO header // Build ID is at offset 0x40 in NSO header
std::array<u8, 0x20> build_id{}; std::array<u8, 0x20> build_id{};
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20);
base_build_id_hex = Common::HexToString(build_id, false); base_build_id_hex = Common::HexToString(build_id, false);
} }
} }
} else if (file_type == Loader::FileType::DeconstructedRomDirectory) {
// For deconstructed ROM directories, read from the main NSO file
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<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
// Build ID is at offset 0x40 in NSO header
std::array<u8, 0x20> build_id{};
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20);
base_build_id_hex = Common::HexToString(build_id, false);
}
}
}
} else {
// For other file types (XCI, NSP, NCA), try to extract build ID directly
try {
if (file_type == Loader::FileType::XCI) {
// For XCI files, try to construct with the proper parameters
try {
// First try to get the program ID from the XCI to use proper parameters
FileSys::XCI xci_temp(file);
if (xci_temp.GetStatus() == Loader::ResultStatus::Success) {
// Try to get the program NCA from the secure partition
FileSys::XCI xci(file, title_id, 0); // Use detected title_id
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<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> build_id{};
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20);
base_build_id_hex = Common::HexToString(build_id, false);
}
}
}
}
}
}
} catch (...) {
// XCI might be encrypted or have other issues
// Fall back to checking if it's installed in the content provider
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<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> 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) {
// For NSP files, try to construct and parse directly
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<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> 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) {
// For NCA files, try to construct and parse directly
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<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> build_id{};
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20);
base_build_id_hex = Common::HexToString(build_id, false);
}
}
}
}
}
} catch (...) {
// If anything fails, continue without the build ID
}
} }
} else {
// For other file types (XCI, NSP, NCA), try to extract build ID directly // Try to get update build ID from patch manager and content provider
try { try {
if (file_type == Loader::FileType::XCI) { // Method 1: Try through patch manager (more reliable for updates)
// For XCI files, try to construct with the proper parameters const FileSys::PatchManager pm_update{title_id, system.GetFileSystemController(),
try { system.GetContentProvider()};
// First try to get the program ID from the XCI to use proper parameters
FileSys::XCI xci_temp(file); // Check if patch manager has update information
if (xci_temp.GetStatus() == Loader::ResultStatus::Success) { const auto update_version = pm_update.GetGameVersion();
// Try to get the program NCA from the secure partition if (update_version.has_value() && update_version.value() > 0) {
FileSys::XCI xci(file, title_id, 0); // Use detected title_id // There's an update, try to get its build ID through the patch manager
if (xci.GetStatus() == Loader::ResultStatus::Success) { // The patch manager should have access to the update NCA
auto program_nca = xci.GetNCAByType(FileSys::NCAContentType::Program);
if (program_nca && program_nca->GetStatus() == Loader::ResultStatus::Success) { // Try to get the update NCA through the patch manager's content provider
auto exefs = program_nca->GetExeFS(); const auto& content_provider = system.GetContentProvider();
if (exefs) { const auto update_title_id = FileSys::GetUpdateTitleID(title_id);
auto main_nso = exefs->GetFile("main"); auto update_nca = content_provider.GetEntry(update_title_id, FileSys::ContentRecordType::Program);
if (main_nso && main_nso->GetSize() >= 0x100) {
std::array<u8, 0x100> header_data{}; if (update_nca && update_nca->GetStatus() == Loader::ResultStatus::Success) {
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { auto exefs = update_nca->GetExeFS();
std::array<u8, 0x20> build_id{}; if (exefs) {
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); auto main_nso = exefs->GetFile("main");
base_build_id_hex = Common::HexToString(build_id, false); if (main_nso && main_nso->GetSize() >= 0x100) {
} std::array<u8, 0x100> header_data{};
} if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
} std::array<u8, 0x20> build_id{};
} std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20);
} update_build_id_hex = Common::HexToString(build_id, false);
} }
} catch (...) {
// XCI might be encrypted or have other issues
// Fall back to checking if it's installed in the content provider
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<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> 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) {
// For NSP files, try to construct and parse directly
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<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> 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) {
// For NCA files, try to construct and parse directly // Method 2: If patch manager approach didn't work, try direct content provider access
FileSys::NCA nca(file); if (update_build_id_hex.empty()) {
if (nca.GetStatus() == Loader::ResultStatus::Success) { const auto& content_provider = system.GetContentProvider();
auto exefs = nca.GetExeFS(); const auto update_title_id = FileSys::GetUpdateTitleID(title_id);
if (exefs) { auto update_nca = content_provider.GetEntry(update_title_id, FileSys::ContentRecordType::Program);
auto main_nso = exefs->GetFile("main");
if (main_nso && main_nso->GetSize() >= 0x100) { if (update_nca && update_nca->GetStatus() == Loader::ResultStatus::Success) {
std::array<u8, 0x100> header_data{}; auto exefs = update_nca->GetExeFS();
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { if (exefs) {
std::array<u8, 0x20> build_id{}; auto main_nso = exefs->GetFile("main");
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); if (main_nso && main_nso->GetSize() >= 0x100) {
base_build_id_hex = Common::HexToString(build_id, false); std::array<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> build_id{};
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20);
update_build_id_hex = Common::HexToString(build_id, false);
}
} }
} }
} }
} }
}
// Method 3: Try to use the patch manager's GetPatches to detect updates
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) {
// There's an enabled update patch, but we couldn't get its build ID
// This indicates an update is available but not currently loaded
break;
}
}
}
} catch (...) { } catch (...) {
// If anything fails, continue without the build ID // If update build ID extraction fails, continue with just base
} }
}
// Try to get update build ID from patch manager and content provider // Try to get the actual running build ID from system (this will be the update if one is applied)
try { const auto& system_build_id = system.GetApplicationProcessBuildID();
// Method 1: Try through patch manager (more reliable for updates) const auto system_build_id_hex = Common::HexToString(system_build_id, false);
const FileSys::PatchManager pm_update{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
// Check if patch manager has update information // If we have a system build ID and it's different from the base, it's likely the update
const auto update_version = pm_update.GetGameVersion(); if (!system_build_id_hex.empty() && system_build_id_hex != std::string(64, '0')) {
if (update_version.has_value() && update_version.value() > 0) { if (!base_build_id_hex.empty() && system_build_id_hex != base_build_id_hex) {
// There's an update, try to get its build ID through the patch manager // System build ID is different from base, so it's the update
// The patch manager should have access to the update NCA update_build_id_hex = system_build_id_hex;
} else if (base_build_id_hex.empty()) {
// Try to get the update NCA through the patch manager's content provider // No base build ID found, use system as base
const auto& content_provider = system.GetContentProvider(); base_build_id_hex = system_build_id_hex;
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<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> build_id{};
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20);
update_build_id_hex = Common::HexToString(build_id, false);
}
}
}
} }
} }
// Method 2: If patch manager approach didn't work, try direct content provider access // Additional check: if we still don't have an update build ID but we know there's an update
if (update_build_id_hex.empty()) { // (from the Add-Ons tab showing v1.0.1 etc), try alternative detection methods
const auto& content_provider = system.GetContentProvider(); bool update_detected = false;
const auto update_title_id = FileSys::GetUpdateTitleID(title_id); if (update_build_id_hex.empty() && !base_build_id_hex.empty()) {
auto update_nca = content_provider.GetEntry(update_title_id, FileSys::ContentRecordType::Program); // Check if the patch manager indicates an update is available
const auto update_version = pm.GetGameVersion();
if (update_nca && update_nca->GetStatus() == Loader::ResultStatus::Success) { if (update_version.has_value() && update_version.value() > 0) {
auto exefs = update_nca->GetExeFS(); update_detected = true;
if (exefs) {
auto main_nso = exefs->GetFile("main");
if (main_nso && main_nso->GetSize() >= 0x100) {
std::array<u8, 0x100> header_data{};
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
std::array<u8, 0x20> build_id{};
std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20);
update_build_id_hex = Common::HexToString(build_id, false);
}
}
}
} }
}
// Method 3: Try to use the patch manager's GetPatches to detect updates // Also check patches
if (update_build_id_hex.empty()) { const auto patches = pm.GetPatches();
const auto patches = pm_update.GetPatches();
for (const auto& patch : patches) { for (const auto& patch : patches) {
if (patch.type == FileSys::PatchType::Update && patch.enabled) { if (patch.type == FileSys::PatchType::Update && patch.enabled) {
// There's an enabled update patch, but we couldn't get its build ID update_detected = true;
// This indicates an update is available but not currently loaded
break; break;
} }
} }
} }
} catch (...) {
// If update build ID extraction fails, continue with just base
}
// Try to get the actual running build ID from system (this will be the update if one is applied) // Display the Build IDs in separate fields
const auto& system_build_id = system.GetApplicationProcessBuildID(); bool has_base = !base_build_id_hex.empty() && base_build_id_hex != std::string(64, '0');
const auto system_build_id_hex = Common::HexToString(system_build_id, false); bool has_update = !update_build_id_hex.empty() && update_build_id_hex != std::string(64, '0');
// If we have a system build ID and it's different from the base, it's likely the update // Set Base Build ID
if (!system_build_id_hex.empty() && system_build_id_hex != std::string(64, '0')) { if (has_base) {
if (!base_build_id_hex.empty() && system_build_id_hex != base_build_id_hex) { ui->display_build_id->setText(QString::fromStdString(base_build_id_hex));
// System build ID is different from base, so it's the update } else {
update_build_id_hex = system_build_id_hex; ui->display_build_id->setText(tr("Not Available"));
} else if (base_build_id_hex.empty()) {
// No base build ID found, use system as base
base_build_id_hex = system_build_id_hex;
}
}
// Additional check: if we still don't have an update build ID but we know there's an update
// (from the Add-Ons tab showing v1.0.1 etc), try alternative detection methods
bool update_detected = false;
if (update_build_id_hex.empty() && !base_build_id_hex.empty()) {
// Check if the patch manager indicates an update is available
const auto update_version = pm.GetGameVersion();
if (update_version.has_value() && update_version.value() > 0) {
update_detected = true;
} }
// Also check patches // Set Update Build ID
const auto patches = pm.GetPatches(); if (has_update) {
for (const auto& patch : patches) { ui->display_update_build_id->setText(QString::fromStdString(update_build_id_hex));
if (patch.type == FileSys::PatchType::Update && patch.enabled) { } else if (update_detected) {
update_detected = true; ui->display_update_build_id->setText(tr("Available (Run game to show)"));
break; } else {
} ui->display_update_build_id->setText(tr("Not Available"));
} }
}
// Display the Build IDs in separate fields
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');
// Set Base Build ID
if (has_base) {
ui->display_build_id->setText(QString::fromStdString(base_build_id_hex));
} else {
ui->display_build_id->setText(tr("Not Available"));
}
// Set Update Build ID
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"));
}
} }

View File

@@ -23,220 +23,262 @@
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred"> <sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="title"> <property name="minimumSize">
<string>Info</string> <size>
<width>320</width>
<height>0</height>
</size>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <property name="horizontalScrollBarPolicy">
<item alignment="Qt::AlignHCenter"> <enum>Qt::ScrollBarAlwaysOff</enum>
<widget class="QGraphicsView" name="icon_view"> </property>
<property name="sizePolicy"> <property name="widgetResizable">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum"> <bool>true</bool>
<horstretch>0</horstretch> </property>
<verstretch>0</verstretch> <widget class="QWidget" name="scrollAreaWidgetContents">
</sizepolicy> <property name="geometry">
</property> <rect>
<property name="minimumSize"> <x>0</x>
<size> <y>0</y>
<width>256</width> <width>318</width>
<height>256</height> <height>538</height>
</size> </rect>
</property> </property>
<property name="maximumSize"> <layout class="QVBoxLayout" name="verticalLayout_5">
<size> <property name="leftMargin">
<width>256</width> <number>0</number>
<height>256</height> </property>
</size> <property name="topMargin">
</property> <number>0</number>
<property name="verticalScrollBarPolicy"> </property>
<enum>Qt::ScrollBarAlwaysOff</enum> <property name="rightMargin">
</property> <number>0</number>
<property name="horizontalScrollBarPolicy"> </property>
<enum>Qt::ScrollBarAlwaysOff</enum> <property name="bottomMargin">
</property> <number>0</number>
<property name="interactive"> </property>
<bool>false</bool> <item>
</property> <!-- The original GroupBox is now INSIDE the scroll area -->
</widget> <widget class="QGroupBox" name="groupBox">
</item> <property name="title">
<item> <string>Info</string>
<layout class="QGridLayout" name="gridLayout_2"> </property>
<item row="6" column="1"> <layout class="QVBoxLayout" name="verticalLayout">
<widget class="QLineEdit" name="display_size"> <item alignment="Qt::AlignHCenter">
<property name="enabled"> <widget class="QGraphicsView" name="icon_view">
<bool>true</bool> <property name="sizePolicy">
</property> <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<property name="readOnly"> <horstretch>0</horstretch>
<bool>true</bool> <verstretch>0</verstretch>
</property> </sizepolicy>
</widget> </property>
</item> <property name="minimumSize">
<item row="3" column="1"> <size>
<widget class="QLineEdit" name="display_version"> <width>256</width>
<property name="enabled"> <height>256</height>
<bool>true</bool> </size>
</property> </property>
<property name="readOnly"> <property name="maximumSize">
<bool>true</bool> <size>
</property> <width>256</width>
</widget> <height>256</height>
</item> </size>
<item row="1" column="0"> </property>
<widget class="QLabel" name="label"> <property name="verticalScrollBarPolicy">
<property name="text"> <enum>Qt::ScrollBarAlwaysOff</enum>
<string>Name</string> </property>
</property> <property name="horizontalScrollBarPolicy">
</widget> <enum>Qt::ScrollBarAlwaysOff</enum>
</item> </property>
<item row="4" column="0"> <property name="interactive">
<widget class="QLabel" name="label_4"> <bool>false</bool>
<property name="text"> </property>
<string>Title ID</string> </widget>
</property> </item>
</widget> <item>
</item> <layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="1"> <item row="6" column="1">
<widget class="QLineEdit" name="display_title_id"> <widget class="QLineEdit" name="display_size">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="3" column="1">
<widget class="QLineEdit" name="display_filename"> <widget class="QLineEdit" name="display_version">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="1" column="0">
<widget class="QLineEdit" name="display_format"> <widget class="QLabel" name="label">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>Name</string>
</property> </property>
<property name="readOnly"> </widget>
<bool>true</bool> </item>
</property> <item row="4" column="0">
</widget> <widget class="QLabel" name="label_4">
</item> <property name="text">
<item row="7" column="0"> <string>Title ID</string>
<widget class="QLabel" name="label_7"> </property>
<property name="text"> </widget>
<string>Filename</string> </item>
</property> <item row="4" column="1">
</widget> <widget class="QLineEdit" name="display_title_id">
</item> <property name="enabled">
<item row="1" column="1"> <bool>true</bool>
<widget class="QLineEdit" name="display_name"> </property>
<property name="enabled"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="readOnly"> </widget>
<bool>true</bool> </item>
</property> <item row="7" column="1">
</widget> <widget class="QLineEdit" name="display_filename">
</item> <property name="enabled">
<item row="2" column="1"> <bool>true</bool>
<widget class="QLineEdit" name="display_developer"> </property>
<property name="enabled"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="readOnly"> </widget>
<bool>true</bool> </item>
</property> <item row="5" column="1">
</widget> <widget class="QLineEdit" name="display_format">
</item> <property name="enabled">
<item row="5" column="0"> <bool>true</bool>
<widget class="QLabel" name="label_5"> </property>
<property name="text"> <property name="readOnly">
<string>Format</string> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="7" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_7">
<property name="text"> <property name="text">
<string>Version</string> <string>Filename</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0"> <item row="1" column="1">
<widget class="QLabel" name="label_6"> <widget class="QLineEdit" name="display_name">
<property name="text"> <property name="enabled">
<string>Size</string> <bool>true</bool>
</property> </property>
</widget> <property name="readOnly">
</item> <bool>true</bool>
<item row="2" column="0"> </property>
<widget class="QLabel" name="label_2"> </widget>
<property name="text"> </item>
<string>Developer</string> <item row="2" column="1">
</property> <widget class="QLineEdit" name="display_developer">
</widget> <property name="enabled">
</item> <bool>true</bool>
<item row="8" column="0"> </property>
<widget class="QLabel" name="label_build_id"> <property name="readOnly">
<property name="text"> <bool>true</bool>
<string>Base Build ID</string> </property>
</property> </widget>
</widget> </item>
</item> <item row="5" column="0">
<item row="8" column="1"> <widget class="QLabel" name="label_5">
<widget class="QLineEdit" name="display_build_id"> <property name="text">
<property name="enabled"> <string>Format</string>
<bool>true</bool> </property>
</property> </widget>
<property name="readOnly"> </item>
<bool>true</bool> <item row="3" column="0">
</property> <widget class="QLabel" name="label_3">
</widget> <property name="text">
</item> <string>Version</string>
<item row="9" column="0"> </property>
<widget class="QLabel" name="label_update_build_id"> </widget>
<property name="text"> </item>
<string>Update Build ID</string> <item row="6" column="0">
</property> <widget class="QLabel" name="label_6">
</widget> <property name="text">
</item> <string>Size</string>
<item row="9" column="1"> </property>
<widget class="QLineEdit" name="display_update_build_id"> </widget>
<property name="enabled"> </item>
<bool>true</bool> <item row="2" column="0">
</property> <widget class="QLabel" name="label_2">
<property name="readOnly"> <property name="text">
<bool>true</bool> <string>Developer</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> <item row="8" column="0">
</item> <widget class="QLabel" name="label_build_id">
<item> <property name="text">
<spacer name="verticalSpacer"> <string>Base Build ID</string>
<property name="orientation"> </property>
<enum>Qt::Vertical</enum> </widget>
</property> </item>
<property name="sizeHint" stdset="0"> <item row="8" column="1">
<size> <widget class="QLineEdit" name="display_build_id">
<width>20</width> <property name="enabled">
<height>40</height> <bool>true</bool>
</size> </property>
</property> <property name="readOnly">
</spacer> <bool>true</bool>
</item> </property>
</layout> </widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_update_build_id">
<property name="text">
<string>Update Build ID</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="display_update_build_id">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
<!-- END OF NEW SCROLL AREA -->
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="VerticalLayout"> <layout class="QVBoxLayout" name="VerticalLayout">
@@ -248,6 +290,12 @@
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex"> <property name="currentIndex">
<number>-1</number> <number>-1</number>
</property> </property>