[UI] Fix bad UTF8 related crashes in Qt string processing

This commit is contained in:
Herman S.
2025-11-02 19:25:13 +09:00
parent 470cb50613
commit 8ae3ff887a
12 changed files with 190 additions and 127 deletions
+6 -4
View File
@@ -39,6 +39,7 @@
#include "xenia/ui/notification_widget_qt.h"
#include "xenia/ui/postprocessing_dialog_qt.h"
#include "xenia/ui/profile_dialog_qt.h"
#include "xenia/ui/qt_util.h"
#include "xenia/ui/xmp_dialog_qt.h"
#if XE_PLATFORM_WIN32
@@ -219,6 +220,7 @@ namespace app {
using xe::ui::FileDropEvent;
using xe::ui::KeyEvent;
using xe::ui::MenuItem;
using xe::ui::SafeQString;
using xe::ui::UIEvent;
using namespace xe::hid;
@@ -1113,9 +1115,9 @@ void EmulatorWindow::ExportScreenshot(const xe::ui::RawImage& image) {
app_context_.CallInUIThread([this, notification_text]() {
auto* qt_window = dynamic_cast<ui::QtWindow*>(window_.get());
if (qt_window) {
auto* notification = new NotificationWidgetQt(
qt_window->qwindow(), "Screenshot Created!",
QString::fromStdString(notification_text), 3000);
auto* notification =
new NotificationWidgetQt(qt_window->qwindow(), "Screenshot Created!",
SafeQString(notification_text), 3000);
notification->Show();
}
});
@@ -2151,7 +2153,7 @@ void EmulatorWindow::DisplayHotKeysConfig() {
// Show Qt message box
QMessageBox msgBox;
msgBox.setWindowTitle("Controller Hotkeys");
msgBox.setText(QString::fromStdString(msg));
msgBox.setText(SafeQString(msg));
msgBox.setIcon(QMessageBox::Information);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
+7 -4
View File
@@ -33,6 +33,7 @@
#include "xenia/kernel/xam/achievement_manager.h"
#include "xenia/kernel/xam/profile_manager.h"
#include "xenia/kernel/xam/xam_state.h"
#include "xenia/ui/qt_util.h"
namespace xe {
namespace kernel {
@@ -45,6 +46,8 @@ struct Achievement;
namespace xe {
namespace ui {
using xe::ui::SafeQString;
AchievementsDialogQt::AchievementsDialogQt(
QWidget* parent, kernel::KernelState* kernel_state,
const kernel::xam::TitleInfo* title_info,
@@ -401,7 +404,7 @@ QString AchievementsDialogQt::GetAchievementTitle(
if (!title.empty() && title.back() == '\0') {
title.pop_back();
}
return QString::fromStdString(title);
return SafeQString(title);
}
QString AchievementsDialogQt::GetAchievementDescription(
@@ -415,7 +418,7 @@ QString AchievementsDialogQt::GetAchievementDescription(
if (!desc.empty() && desc.back() == '\0') {
desc.pop_back();
}
return QString::fromStdString(desc);
return SafeQString(desc);
} else {
// Hide description when checkbox is not checked
return QString();
@@ -428,7 +431,7 @@ QString AchievementsDialogQt::GetAchievementDescription(
if (!desc.empty() && desc.back() == '\0') {
desc.pop_back();
}
return QString::fromStdString(desc);
return SafeQString(desc);
}
QString AchievementsDialogQt::GetUnlockedTime(
@@ -442,7 +445,7 @@ QString AchievementsDialogQt::GetUnlockedTime(
chrono::WinSystemClock::to_sys(achievement.unlock_time.to_time_point());
auto unlock_time = std::chrono::system_clock::to_time_t(unlock_tp);
return QString::fromStdString(
return SafeQString(
fmt::format("{:%Y-%m-%d %H:%M}", fmt::localtime(unlock_time)));
}
+27 -28
View File
@@ -28,6 +28,7 @@
#include "xenia/base/logging.h"
#include "xenia/config.h"
#include "xenia/ui/config_helpers.h"
#include "xenia/ui/qt_util.h"
#if XE_PLATFORM_LINUX
#include <unistd.h>
@@ -40,6 +41,8 @@ namespace {
// Use the shared enum options from config_helpers.h
using xe::ui::GetKnownEnumOptions;
using xe::ui::SafeQString;
using xe::ui::SafeStdString;
#if XE_PLATFORM_LINUX
// Check if a command exists in PATH by searching each directory
@@ -178,7 +181,7 @@ void ConfigDialogQt::SetupUI() {
// Add categories in order
for (const auto& category_name : category_order_) {
category_list_->addItem(QString::fromStdString(category_name));
category_list_->addItem(SafeQString(category_name));
auto it = categories_.find(category_name);
if (it != categories_.end()) {
CreateCategoryPage(category_name, it->second);
@@ -227,14 +230,13 @@ void ConfigDialogQt::CreateCategoryPage(
for (auto* var_info : vars) {
// Create label with tooltip - left aligned
auto* label = new QLabel(QString::fromStdString(var_info->name));
auto* label = new QLabel(SafeQString(var_info->name));
label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
if (!var_info->description.empty()) {
// Format tooltip with rich text to enable word wrapping
QString tooltip =
QString("<p style='white-space: pre-wrap; max-width: 400px;'>%1</p>")
.arg(QString::fromStdString(var_info->description)
.toHtmlEscaped());
.arg(SafeQString(var_info->description).toHtmlEscaped());
label->setToolTip(tooltip);
label->setToolTipDuration(5000); // Show for 5 seconds
}
@@ -297,7 +299,7 @@ QWidget* ConfigDialogQt::CreateEditorWidget(ConfigVarInfo* var_info) {
int current_index = -1;
for (size_t i = 0; i < options.size(); ++i) {
combo->addItem(QString::fromStdString(options[i]));
combo->addItem(SafeQString(options[i]));
if (options[i] == var_info->pending_value) {
current_index = static_cast<int>(i);
}
@@ -318,22 +320,21 @@ QWidget* ConfigDialogQt::CreateEditorWidget(ConfigVarInfo* var_info) {
layout->setContentsMargins(0, 0, 0, 0);
auto* line_edit = new QLineEdit();
line_edit->setText(QString::fromStdString(var_info->pending_value));
line_edit->setText(SafeQString(var_info->pending_value));
connect(line_edit, &QLineEdit::textChanged, this,
&ConfigDialogQt::OnValueChanged);
layout->addWidget(line_edit);
auto* browse_button = new QPushButton("Browse...");
connect(
browse_button, &QPushButton::clicked, [this, line_edit, var_info]() {
QString path = QFileDialog::getExistingDirectory(
this,
QString::fromStdString("Select Directory for " + var_info->name),
line_edit->text());
if (!path.isEmpty()) {
line_edit->setText(path);
}
});
connect(browse_button, &QPushButton::clicked,
[this, line_edit, var_info]() {
QString path = QFileDialog::getExistingDirectory(
this, SafeQString("Select Directory for " + var_info->name),
line_edit->text());
if (!path.isEmpty()) {
line_edit->setText(path);
}
});
layout->addWidget(browse_button);
return container;
@@ -358,7 +359,7 @@ QWidget* ConfigDialogQt::CreateEditorWidget(ConfigVarInfo* var_info) {
} else {
// Text input for all other types (string, double, etc.)
auto* line_edit = new QLineEdit();
line_edit->setText(QString::fromStdString(var_info->pending_value));
line_edit->setText(SafeQString(var_info->pending_value));
connect(line_edit, &QLineEdit::textChanged, this,
&ConfigDialogQt::OnValueChanged);
return line_edit;
@@ -370,16 +371,16 @@ std::string ConfigDialogQt::GetEditorValue(QWidget* editor,
if (auto* checkbox = qobject_cast<QCheckBox*>(editor)) {
return checkbox->isChecked() ? "true" : "false";
} else if (auto* combo = qobject_cast<QComboBox*>(editor)) {
return combo->currentText().toStdString();
return SafeStdString(combo->currentText());
} else if (auto* spinbox = qobject_cast<QSpinBox*>(editor)) {
return std::to_string(spinbox->value());
} else if (auto* line_edit = qobject_cast<QLineEdit*>(editor)) {
return line_edit->text().toStdString();
return SafeStdString(line_edit->text());
} else if (auto* container = qobject_cast<QWidget*>(editor)) {
// For path containers, find the QLineEdit child
auto* line_edit = container->findChild<QLineEdit*>();
if (line_edit) {
return line_edit->text().toStdString();
return SafeStdString(line_edit->text());
}
}
return "";
@@ -548,7 +549,7 @@ void ConfigDialogQt::ResetToDefaults() {
checkbox->setChecked(default_value == "true");
} else if (auto* combo =
qobject_cast<QComboBox*>(var_info.editor_widget)) {
int index = combo->findText(QString::fromStdString(default_value));
int index = combo->findText(SafeQString(default_value));
if (index >= 0) {
combo->setCurrentIndex(index);
}
@@ -561,12 +562,12 @@ void ConfigDialogQt::ResetToDefaults() {
}
} else if (auto* line_edit =
qobject_cast<QLineEdit*>(var_info.editor_widget)) {
line_edit->setText(QString::fromStdString(default_value));
line_edit->setText(SafeQString(default_value));
} else if (auto* container =
qobject_cast<QWidget*>(var_info.editor_widget)) {
auto* line_edit = container->findChild<QLineEdit*>();
if (line_edit) {
line_edit->setText(QString::fromStdString(default_value));
line_edit->setText(SafeQString(default_value));
}
}
}
@@ -655,8 +656,7 @@ void ConfigDialogQt::OnDiscardClicked() {
checkbox->setChecked(var_info.current_value == "true");
} else if (auto* combo =
qobject_cast<QComboBox*>(var_info.editor_widget)) {
int index =
combo->findText(QString::fromStdString(var_info.current_value));
int index = combo->findText(SafeQString(var_info.current_value));
if (index >= 0) {
combo->setCurrentIndex(index);
}
@@ -669,13 +669,12 @@ void ConfigDialogQt::OnDiscardClicked() {
}
} else if (auto* line_edit =
qobject_cast<QLineEdit*>(var_info.editor_widget)) {
line_edit->setText(QString::fromStdString(var_info.current_value));
line_edit->setText(SafeQString(var_info.current_value));
} else if (auto* container =
qobject_cast<QWidget*>(var_info.editor_widget)) {
auto* line_edit = container->findChild<QLineEdit*>();
if (line_edit) {
line_edit->setText(
QString::fromStdString(var_info.current_value));
line_edit->setText(SafeQString(var_info.current_value));
}
}
}
+7 -5
View File
@@ -21,11 +21,14 @@
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/system.h"
#include "xenia/ui/qt_util.h"
#include "xenia/xbox.h"
namespace xe {
namespace ui {
using xe::ui::SafeQString;
ContentInstallDialogQt::ContentInstallDialogQt(
QWidget* parent, const std::filesystem::path& content_root,
std::shared_ptr<std::vector<Emulator::ContentInstallEntry>> entries)
@@ -199,11 +202,11 @@ void ContentInstallDialogQt::UpdateProgress() {
// Update name
widgets.name_label->setText(
QString("Name: %1").arg(QString::fromStdString(entry.name_)));
QString("Name: %1").arg(SafeQString(entry.name_)));
// Update path with link
QString path_str =
QString::fromStdString(xe::path_to_utf8(entry.data_installation_path_));
SafeQString(xe::path_to_utf8(entry.data_installation_path_));
widgets.path_label->setText(
QString("Installation Path: <a href=\"%1\">%1</a>").arg(path_str));
@@ -212,8 +215,7 @@ void ContentInstallDialogQt::UpdateProgress() {
auto it = XContentTypeMap.find(entry.content_type_);
if (it != XContentTypeMap.end()) {
widgets.type_label->setText(
QString("Content Type: %1")
.arg(QString::fromStdString(it->second)));
QString("Content Type: %1").arg(SafeQString(it->second)));
}
} else {
widgets.type_label->setText("");
@@ -230,7 +232,7 @@ void ContentInstallDialogQt::UpdateProgress() {
entry.installation_result_);
}
widgets.status_label->setText(QString::fromStdString(result));
widgets.status_label->setText(SafeQString(result));
// Update progress bar
if (entry.content_size_ > 0) {
+4 -1
View File
@@ -18,6 +18,7 @@
#include "xenia/base/filesystem.h"
#include "xenia/base/logging.h"
#include "xenia/base/string.h"
#include "xenia/ui/qt_util.h"
#include "xenia/ui/window_qt.h"
#if XE_PLATFORM_WIN32
@@ -27,6 +28,8 @@
namespace xe {
namespace ui {
using xe::ui::SafeQString;
#if XE_PLATFORM_WIN32
// Detect if running under Wine by checking for wine_get_version in ntdll
static bool IsRunningOnWine() {
@@ -85,7 +88,7 @@ bool QtFilePicker::Show(Window* parent_window) {
}
// Use the custom title set via set_title(), or default based on mode
QString title = QString::fromStdString(this->title());
QString title = SafeQString(this->title());
auto* qt_window =
parent_window ? dynamic_cast<QtWindow*>(parent_window) : nullptr;
+34 -32
View File
@@ -36,6 +36,7 @@
#include "xenia/base/logging.h"
#include "xenia/config.h"
#include "xenia/ui/config_helpers.h"
#include "xenia/ui/qt_util.h"
namespace xe {
namespace app {
@@ -44,6 +45,8 @@ namespace {
// Use the shared enum options from config_helpers.h
using xe::ui::GetKnownEnumOptions;
using xe::ui::SafeQString;
using xe::ui::SafeStdString;
// Helper to convert a RapidJSON value to string
std::string JsonValueToString(const rapidjson::Value& value) {
@@ -114,8 +117,8 @@ GameConfigDialogQt::GameConfigDialogQt(QWidget* parent,
GameConfigDialogQt::~GameConfigDialogQt() = default;
void GameConfigDialogQt::SetupUI() {
setWindowTitle(QString::fromStdString(
fmt::format("Game Config Overrides - {}", game_title_)));
setWindowTitle(
SafeQString(fmt::format("Game Config Overrides - {}", game_title_)));
setMinimumSize(800, 500);
resize(900, 600);
@@ -253,8 +256,7 @@ void GameConfigDialogQt::LoadConfigOverrides() {
overrides_table_->insertRow(row);
// Column 0: Variable name (non-editable text)
auto* name_item =
new QTableWidgetItem(QString::fromStdString(var_name));
auto* name_item = new QTableWidgetItem(SafeQString(var_name));
name_item->setFlags(name_item->flags() & ~Qt::ItemIsEditable);
overrides_table_->setItem(row, 0, name_item);
@@ -276,9 +278,9 @@ void GameConfigDialogQt::LoadConfigOverrides() {
} catch (const std::exception& e) {
XELOGE("Failed to load game config {}: {}", xe::path_to_utf8(config_path),
e.what());
QMessageBox::warning(this, "Error Loading Config",
QString::fromStdString(fmt::format(
"Failed to load game config: {}", e.what())));
QMessageBox::warning(
this, "Error Loading Config",
SafeQString(fmt::format("Failed to load game config: {}", e.what())));
}
has_unsaved_changes_ = false;
@@ -303,7 +305,7 @@ void GameConfigDialogQt::SaveConfigOverrides() {
continue;
}
std::string var_name = var_item->text().toStdString();
std::string var_name = SafeStdString(var_item->text());
// Get value from the cell widget, not from a text item
QWidget* value_widget = overrides_table_->cellWidget(row, 1);
@@ -391,9 +393,9 @@ void GameConfigDialogQt::SaveConfigOverrides() {
} catch (const std::exception& e) {
XELOGE("Failed to save game config {}: {}", xe::path_to_utf8(config_path),
e.what());
QMessageBox::critical(this, "Error Saving Config",
QString::fromStdString(fmt::format(
"Failed to save game config: {}", e.what())));
QMessageBox::critical(
this, "Error Saving Config",
SafeQString(fmt::format("Failed to save game config: {}", e.what())));
}
}
@@ -412,7 +414,7 @@ QWidget* GameConfigDialogQt::CreateEditorForCvar(
auto* combo = new QComboBox();
combo->addItem("true");
combo->addItem("false");
combo->setCurrentText(QString::fromStdString(current_value));
combo->setCurrentText(SafeQString(current_value));
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this, combo]() {
has_unsaved_changes_ = true;
@@ -432,7 +434,7 @@ QWidget* GameConfigDialogQt::CreateEditorForCvar(
int current_index = -1;
for (size_t i = 0; i < options.size(); ++i) {
combo->addItem(QString::fromStdString(options[i]));
combo->addItem(SafeQString(options[i]));
if (options[i] == current_value) {
current_index = static_cast<int>(i);
}
@@ -461,7 +463,7 @@ QWidget* GameConfigDialogQt::CreateEditorForCvar(
layout->setContentsMargins(0, 0, 0, 0);
auto* line_edit = new QLineEdit();
line_edit->setText(QString::fromStdString(current_value));
line_edit->setText(SafeQString(current_value));
connect(line_edit, &QLineEdit::textChanged, [this, container]() {
has_unsaved_changes_ = true;
// Find which row this widget belongs to and update its bold state
@@ -477,7 +479,7 @@ QWidget* GameConfigDialogQt::CreateEditorForCvar(
auto* browse_button = new QPushButton("Browse...");
connect(browse_button, &QPushButton::clicked, [this, line_edit, var]() {
QString path = QFileDialog::getExistingDirectory(
this, QString::fromStdString("Select Directory for " + var->name()),
this, SafeQString("Select Directory for " + var->name()),
line_edit->text());
if (!path.isEmpty()) {
line_edit->setText(path);
@@ -515,7 +517,7 @@ QWidget* GameConfigDialogQt::CreateEditorForCvar(
} else {
// Text input for all other types (string, double, etc.)
auto* line_edit = new QLineEdit();
line_edit->setText(QString::fromStdString(current_value));
line_edit->setText(SafeQString(current_value));
connect(line_edit, &QLineEdit::textChanged, [this, line_edit]() {
has_unsaved_changes_ = true;
// Find which row this widget belongs to and update its bold state
@@ -532,16 +534,16 @@ QWidget* GameConfigDialogQt::CreateEditorForCvar(
std::string GameConfigDialogQt::GetEditorValue(QWidget* editor) {
if (auto* combo = qobject_cast<QComboBox*>(editor)) {
return combo->currentText().toStdString();
return SafeStdString(combo->currentText());
} else if (auto* spinbox = qobject_cast<QSpinBox*>(editor)) {
return std::to_string(spinbox->value());
} else if (auto* line_edit = qobject_cast<QLineEdit*>(editor)) {
return line_edit->text().toStdString();
return SafeStdString(line_edit->text());
} else if (auto* container = qobject_cast<QWidget*>(editor)) {
// For path containers, find the QLineEdit child
auto* line_edit = container->findChild<QLineEdit*>();
if (line_edit) {
return line_edit->text().toStdString();
return SafeStdString(line_edit->text());
}
}
return "";
@@ -553,7 +555,7 @@ void GameConfigDialogQt::UpdateRowModifiedState(int row) {
return;
}
std::string var_name = name_item->text().toStdString();
std::string var_name = SafeStdString(name_item->text());
QWidget* value_widget = overrides_table_->cellWidget(row, 1);
if (!value_widget) {
return;
@@ -599,8 +601,8 @@ void GameConfigDialogQt::OnAddOverrideClicked() {
for (const auto& name : cvar_names) {
auto* var = (*cvar::ConfigVars)[name];
QString item_text =
QString::fromStdString(fmt::format("{} ({})", name, var->category()));
combo->addItem(item_text, QString::fromStdString(name));
SafeQString(fmt::format("{} ({})", name, var->category()));
combo->addItem(item_text, SafeQString(name));
}
}
@@ -632,7 +634,7 @@ void GameConfigDialogQt::OnAddOverrideClicked() {
return;
}
selected_var = (*cvar::ConfigVars)[var_name.toStdString()];
selected_var = (*cvar::ConfigVars)[SafeStdString(var_name)];
if (!selected_var) {
return;
}
@@ -678,7 +680,7 @@ void GameConfigDialogQt::OnAddOverrideClicked() {
QString value;
if (current_editor) {
value = QString::fromStdString(GetEditorValue(current_editor));
value = SafeQString(GetEditorValue(current_editor));
}
if (var_name.isEmpty()) {
@@ -710,7 +712,7 @@ void GameConfigDialogQt::OnAddOverrideClicked() {
// We need to create a new one since the dialog's editor will be destroyed
if (selected_var) {
QWidget* value_editor =
CreateEditorForCvar(selected_var, value.toStdString());
CreateEditorForCvar(selected_var, SafeStdString(value));
overrides_table_->setCellWidget(row, 1, value_editor);
}
@@ -784,7 +786,7 @@ void GameConfigDialogQt::LoadRecommendedSettings() {
if (!parse_result) {
QMessageBox::warning(
this, "Error Parsing Settings",
QString::fromStdString(
SafeQString(
fmt::format("Failed to parse recommended settings: {} at offset {}",
rapidjson::GetParseError_En(parse_result.Code()),
parse_result.Offset())));
@@ -827,7 +829,7 @@ void GameConfigDialogQt::LoadRecommendedSettings() {
has_unsaved_changes_ = true;
QMessageBox::information(
this, "Settings Loaded",
QString::fromStdString(
SafeQString(
fmt::format("Loaded {} recommended setting{} for this game.\n\n"
"Don't forget to save your changes!",
settings_applied, settings_applied == 1 ? "" : "s")));
@@ -855,13 +857,13 @@ bool GameConfigDialogQt::ApplyRecommendedSetting(const std::string& var_name,
// Check if this setting is already in the table
for (int row = 0; row < overrides_table_->rowCount(); ++row) {
auto* item = overrides_table_->item(row, 0);
if (item && item->text().toStdString() == var_name) {
if (item && SafeStdString(item->text()) == var_name) {
// Update the existing entry
QWidget* value_widget = overrides_table_->cellWidget(row, 1);
if (value_widget) {
// Update the widget with the new value
if (auto* combo = qobject_cast<QComboBox*>(value_widget)) {
int index = combo->findText(QString::fromStdString(value));
int index = combo->findText(SafeQString(value));
if (index >= 0) {
combo->setCurrentIndex(index);
}
@@ -872,12 +874,12 @@ bool GameConfigDialogQt::ApplyRecommendedSetting(const std::string& var_name,
XELOGW("Failed to parse integer value for {}: {}", var_name, value);
}
} else if (auto* line_edit = qobject_cast<QLineEdit*>(value_widget)) {
line_edit->setText(QString::fromStdString(value));
line_edit->setText(SafeQString(value));
} else if (auto* container = qobject_cast<QWidget*>(value_widget)) {
// For path containers, find the QLineEdit child
auto* line_edit = container->findChild<QLineEdit*>();
if (line_edit) {
line_edit->setText(QString::fromStdString(value));
line_edit->setText(SafeQString(value));
}
}
}
@@ -890,7 +892,7 @@ bool GameConfigDialogQt::ApplyRecommendedSetting(const std::string& var_name,
overrides_table_->insertRow(row);
// Column 0: Variable name (non-editable text)
auto* name_item = new QTableWidgetItem(QString::fromStdString(var_name));
auto* name_item = new QTableWidgetItem(SafeQString(var_name));
name_item->setFlags(name_item->flags() & ~Qt::ItemIsEditable);
overrides_table_->setItem(row, 0, name_item);
+34 -22
View File
@@ -48,10 +48,14 @@
#include "xenia/ui/game_config_dialog_qt.h"
#include "xenia/ui/profile_dialog_qt.h"
#include "xenia/ui/profile_editor_dialog_qt.h"
#include "xenia/ui/qt_util.h"
namespace xe {
namespace app {
using xe::ui::SafeQString;
using xe::ui::SafeStdString;
GameListDialogQt::GameListDialogQt(QWidget* parent,
EmulatorWindow* emulator_window)
: QWidget(parent), emulator_window_(emulator_window) {
@@ -597,9 +601,8 @@ void GameListDialogQt::PopulateTable() {
// Apply filter
if (!filter_text.isEmpty()) {
QString title = QString::fromStdString(entry.title_name).toLower();
QString path =
QString::fromStdString(entry.path_to_file.string()).toLower();
QString title = SafeQString(entry.title_name).toLower();
QString path = SafeQString(entry.path_to_file.string()).toLower();
if (!title.contains(filter_text) && !path.contains(filter_text)) {
continue;
@@ -661,9 +664,20 @@ void GameListDialogQt::PopulateTable() {
bool file_corrupted = entry.title_name.empty();
// Title - large font
QString display_title = file_corrupted
? QString("File Corrupted")
: QString::fromStdString(entry.title_name);
QString display_title;
if (file_corrupted) {
display_title = QString("File Corrupted");
} else {
// Use fromUtf8 with error handling for potentially invalid data
display_title =
QString::fromUtf8(entry.title_name.c_str(),
static_cast<qsizetype>(entry.title_name.size()));
// If conversion failed or resulted in empty string, mark as corrupted
if (display_title.isEmpty()) {
display_title = QString("File Corrupted");
file_corrupted = true;
}
}
auto* title_label = new QLabel(display_title);
title_label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
title_label->setAttribute(Qt::WA_TransparentForMouseEvents);
@@ -678,8 +692,7 @@ void GameListDialogQt::PopulateTable() {
// Path - smaller font (only show if path is available)
if (!entry.path_to_file.empty()) {
auto* path_label =
new QLabel(QString::fromStdString(entry.path_to_file.string()));
auto* path_label = new QLabel(SafeQString(entry.path_to_file.string()));
path_label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
path_label->setAttribute(Qt::WA_TransparentForMouseEvents);
QFont path_font = path_label->font();
@@ -721,8 +734,8 @@ void GameListDialogQt::PopulateTable() {
row_layout->addWidget(title_container, 1); // Stretch factor
// Last played
auto* last_played_label = new QLabel(
QString::fromStdString(FormatLastPlayed(entry.last_run_time)));
auto* last_played_label =
new QLabel(SafeQString(FormatLastPlayed(entry.last_run_time)));
last_played_label->setAlignment(Qt::AlignVCenter | Qt::AlignRight);
last_played_label->setMinimumWidth(200);
last_played_label->setAttribute(Qt::WA_TransparentForMouseEvents);
@@ -734,7 +747,7 @@ void GameListDialogQt::PopulateTable() {
// Store the path and title_id in the row for later retrieval
table_widget_->setItem(row, 0, new QTableWidgetItem());
table_widget_->item(row, 0)->setData(
Qt::UserRole, QString::fromStdString(entry.path_to_file.string()));
Qt::UserRole, SafeQString(entry.path_to_file.string()));
table_widget_->item(row, 0)->setData(Qt::UserRole + 1, entry.title_id);
}
}
@@ -766,7 +779,7 @@ void GameListDialogQt::OnGameDoubleClicked(int row, int column) {
return;
}
std::filesystem::path path = path_str.toStdString();
std::filesystem::path path = SafeStdString(path_str);
LaunchGame(path, title_id);
}
@@ -784,7 +797,7 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) {
QString path_str = item->data(Qt::UserRole).toString();
uint32_t title_id = item->data(Qt::UserRole + 1).toUInt();
std::filesystem::path path = path_str.toStdString();
std::filesystem::path path = SafeStdString(path_str);
bool has_path = !path_str.isEmpty();
// Get profile manager
@@ -872,7 +885,7 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) {
QString title_name;
for (const auto& entry : game_entries_) {
if (entry.title_id == title_id) {
title_name = QString::fromStdString(entry.title_name);
title_name = SafeQString(entry.title_name);
break;
}
}
@@ -938,16 +951,16 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) {
QString title_name;
for (const auto& entry : game_entries_) {
if (entry.title_id == title_id) {
title_name = QString::fromStdString(entry.title_name);
title_name = SafeQString(entry.title_name);
break;
}
}
if (title_name.isEmpty()) {
title_name = QString::fromStdString(fmt::format("{:08X}", title_id));
title_name = SafeQString(fmt::format("{:08X}", title_id));
}
auto* dialog = new GameConfigDialogQt(this, emulator_window_, title_id,
title_name.toStdString());
SafeStdString(title_name));
dialog->exec();
delete dialog;
} else if (selected == compatibility_canary_action &&
@@ -957,7 +970,7 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) {
"https://github.com/xenia-canary/game-compatibility/issues";
const std::string url =
fmt::format("{}?q=is%3Aissue+is%3Aopen+{:08X}", base_url, title_id);
QDesktopServices::openUrl(QUrl(QString::fromStdString(url)));
QDesktopServices::openUrl(QUrl(SafeQString(url)));
} else if (selected == compatibility_master_action &&
compatibility_master_action) {
// Open Master game compatibility page with this title ID
@@ -965,7 +978,7 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) {
"https://github.com/xenia-project/game-compatibility/issues";
const std::string url =
fmt::format("{}?q=is%3Aissue+is%3Aopen+{:08X}", base_url, title_id);
QDesktopServices::openUrl(QUrl(QString::fromStdString(url)));
QDesktopServices::openUrl(QUrl(SafeQString(url)));
} else if (selected == remove_action) {
RemoveTitleFromDashboard(title_id);
}
@@ -1139,7 +1152,7 @@ void GameListDialogQt::OnSelectionChanged() {
QString path_str = item->data(Qt::UserRole).toString();
uint32_t title_id = item->data(Qt::UserRole + 1).toUInt();
if (!path_str.isEmpty()) {
selected_game_path_ = path_str.toStdString();
selected_game_path_ = SafeStdString(path_str);
selected_game_title_id_ = title_id;
UpdatePlayButtonState();
return;
@@ -1226,8 +1239,7 @@ void GameListDialogQt::UpdateProfileButtonState() {
auto accounts = profile_manager->GetAccounts();
auto account_it = accounts->find(profile->xuid());
if (account_it != accounts->end()) {
QString gamertag =
QString::fromStdString(account_it->second.GetGamertagString());
QString gamertag = SafeQString(account_it->second.GetGamertagString());
profile_label_->setText(gamertag);
profile_question_label_->setVisible(
false); // Hide question mark when logged in
+7 -4
View File
@@ -21,6 +21,7 @@
#include "xenia/app/emulator_window.h"
#include "xenia/base/cvar.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/ui/qt_util.h"
DECLARE_bool(postprocess_dither);
DECLARE_double(postprocess_ffx_cas_additional_sharpness);
@@ -31,6 +32,8 @@ DECLARE_string(postprocess_scaling_and_sharpening);
namespace xe {
namespace app {
using xe::ui::SafeQString;
PostProcessingDialogQt::PostProcessingDialogQt(QWidget* parent,
EmulatorWindow* emulator_window)
: QDialog(parent), emulator_window_(emulator_window) {
@@ -446,8 +449,8 @@ void PostProcessingDialogQt::OnFsrSharpnessChanged(int value) {
presenter->SetGuestOutputPaintConfigFromUIThread(config);
// Update label
fsr_sharpness_value_label_->setText(QString::fromStdString(
fmt::format("{} %", static_cast<int>(fsr_sharpness * 100))));
fsr_sharpness_value_label_->setText(
SafeQString(fmt::format("{} %", static_cast<int>(fsr_sharpness * 100))));
// Update cvar
emulator_window_->UpdateFsrSharpnessCvar(fsr_sharpness);
@@ -477,8 +480,8 @@ void PostProcessingDialogQt::OnCasSharpnessChanged(int value) {
presenter->SetGuestOutputPaintConfigFromUIThread(config);
// Update label
cas_sharpness_value_label_->setText(QString::fromStdString(
fmt::format("{} %", static_cast<int>(cas_sharpness * 100))));
cas_sharpness_value_label_->setText(
SafeQString(fmt::format("{} %", static_cast<int>(cas_sharpness * 100))));
// Update cvar
emulator_window_->UpdateCasSharpnessCvar(cas_sharpness);
+9 -6
View File
@@ -25,11 +25,15 @@
#include "xenia/kernel/xam/ui/title_info_ui.h"
#include "xenia/kernel/xam/xam_state.h"
#include "xenia/ui/profile_editor_dialog_qt.h"
#include "xenia/ui/qt_util.h"
#include "xenia/ui/window_qt.h"
namespace xe {
namespace app {
using xe::ui::SafeQString;
using xe::ui::SafeStdString;
ProfileDialogQt::ProfileDialogQt(QWidget* parent,
EmulatorWindow* emulator_window)
: QDialog(parent), emulator_window_(emulator_window) {
@@ -206,7 +210,7 @@ void ProfileDialogQt::PopulateProfileList() {
const uint8_t user_index =
profile_manager->GetUserIndexAssignedToProfile(xuid);
QString gamertag = QString::fromStdString(account.GetGamertagString());
QString gamertag = SafeQString(account.GetGamertagString());
QString status = (user_index == XUserIndexAny)
? " (Not logged in)"
: fmt::format(" (Slot {})", user_index + 1).c_str();
@@ -327,8 +331,8 @@ void ProfileDialogQt::OnProfileContextMenu(const QPoint& pos) {
// Login to slot submenu
QMenu* login_slot_menu = context_menu.addMenu("Login to slot:");
for (uint8_t i = 1; i <= XUserMaxUserCount; i++) {
QAction* slot_action = login_slot_menu->addAction(
QString::fromStdString(fmt::format("slot {}", i)));
QAction* slot_action =
login_slot_menu->addAction(SafeQString(fmt::format("slot {}", i)));
connect(slot_action, &QAction::triggered, [=, this]() {
profile_manager->Login(xuid, i - 1);
RefreshProfiles();
@@ -382,8 +386,7 @@ void ProfileDialogQt::OnProfileContextMenu(const QPoint& pos) {
return;
}
QString gamertag =
QString::fromStdString(profile_it->second.GetGamertagString());
QString gamertag = SafeQString(profile_it->second.GetGamertagString());
QMessageBox::StandardButton reply = QMessageBox::question(
this, "Delete Profile",
@@ -442,7 +445,7 @@ void ProfileDialogQt::OnCreateProfileClicked() {
}
// Create the profile
std::string gamertag_string = gamertag.toStdString();
std::string gamertag_string = SafeStdString(gamertag);
bool autologin = (profile_manager->GetAccountCount() == 0);
if (profile_manager->CreateProfile(gamertag_string, autologin, false)) {
+14 -17
View File
@@ -30,11 +30,14 @@
#include "xenia/kernel/xam/user_settings.h"
#include "xenia/kernel/xam/user_tracker.h"
#include "xenia/kernel/xam/xam_state.h"
#include "xenia/ui/qt_util.h"
namespace xe {
namespace app {
using namespace kernel::xam;
using xe::ui::SafeQString;
using xe::ui::SafeStdString;
// Language names
static const char* kLanguageNames[] = {nullptr,
@@ -361,7 +364,7 @@ void ProfileEditorDialogQt::SetupUI() {
const auto profile = xam_state ? xam_state->GetUserProfile(xuid_) : nullptr;
setWindowTitle(QString("Gamercard Editor - %1")
.arg(QString::fromStdString(current_data_.gamertag)));
.arg(SafeQString(current_data_.gamertag)));
setModal(false); // Non-modal like ProfileDialogQt
setAttribute(Qt::WA_DeleteOnClose);
setMinimumSize(900, 700);
@@ -416,8 +419,7 @@ void ProfileEditorDialogQt::SetupUI() {
// Gamertag
profile_layout->addWidget(new QLabel("Gamertag:"), row, 0);
gamertag_edit_ =
new QLineEdit(QString::fromStdString(current_data_.gamertag));
gamertag_edit_ = new QLineEdit(SafeQString(current_data_.gamertag));
gamertag_edit_->setMaxLength(15);
connect(gamertag_edit_, &QLineEdit::textChanged, this,
&ProfileEditorDialogQt::UpdateGamertagValidation);
@@ -430,24 +432,21 @@ void ProfileEditorDialogQt::SetupUI() {
// Gamer Name
profile_layout->addWidget(new QLabel("Gamer Name:"), row, 0);
gamer_name_edit_ =
new QLineEdit(QString::fromStdString(current_data_.gamer_name));
gamer_name_edit_ = new QLineEdit(SafeQString(current_data_.gamer_name));
gamer_name_edit_->setMaxLength(130); // 0x104 bytes = 130 UTF-16 characters
gamer_name_edit_->setEnabled(profile != nullptr);
profile_layout->addWidget(gamer_name_edit_, row++, 1);
// Gamer Motto
profile_layout->addWidget(new QLabel("Gamer Motto:"), row, 0);
gamer_motto_edit_ =
new QLineEdit(QString::fromStdString(current_data_.gamer_motto));
gamer_motto_edit_ = new QLineEdit(SafeQString(current_data_.gamer_motto));
gamer_motto_edit_->setMaxLength(22); // 0x2C bytes = 22 UTF-16 characters
gamer_motto_edit_->setEnabled(profile != nullptr);
profile_layout->addWidget(gamer_motto_edit_, row++, 1);
// Gamer Bio
profile_layout->addWidget(new QLabel("Gamer Bio:"), row, 0);
gamer_bio_edit_ =
new QTextEdit(QString::fromStdString(current_data_.gamer_bio));
gamer_bio_edit_ = new QTextEdit(SafeQString(current_data_.gamer_bio));
gamer_bio_edit_->setEnabled(profile != nullptr);
gamer_bio_edit_->setMaximumHeight(100);
// Limit bio to 500 characters (0x3E8 bytes = 500 UTF-16 characters)
@@ -503,15 +502,13 @@ void ProfileEditorDialogQt::SetupUI() {
// Online XUID (read-only)
online_layout->addWidget(new QLabel("Online XUID:"), row, 0);
online_xuid_edit_ =
new QLineEdit(QString::fromStdString(current_data_.online_xuid));
online_xuid_edit_ = new QLineEdit(SafeQString(current_data_.online_xuid));
online_xuid_edit_->setReadOnly(true);
online_layout->addWidget(online_xuid_edit_, row++, 1);
// Online Domain (read-only)
online_layout->addWidget(new QLabel("Online Domain:"), row, 0);
online_domain_edit_ =
new QLineEdit(QString::fromStdString(current_data_.online_domain));
online_domain_edit_ = new QLineEdit(SafeQString(current_data_.online_domain));
online_domain_edit_->setReadOnly(true);
online_layout->addWidget(online_domain_edit_, row++, 1);
@@ -731,7 +728,7 @@ void ProfileEditorDialogQt::LoadProfileIcon() {
}
bool ProfileEditorDialogQt::ValidateGamertagInput(const QString& text) {
std::string gamertag = text.toStdString();
std::string gamertag = SafeStdString(text);
return ProfileManager::IsGamertagValid(gamertag);
}
@@ -762,7 +759,7 @@ void ProfileEditorDialogQt::OnChangeIconClicked() {
return;
}
std::filesystem::path path = file_path.toStdString();
std::filesystem::path path = SafeStdString(file_path);
if (!IsFilePngImage(path)) {
QMessageBox::warning(this, "Invalid File",
@@ -820,7 +817,7 @@ void ProfileEditorDialogQt::SaveProfileData() {
auto account = account_original;
// Update gamertag
std::string gamertag_str = gamertag_edit_->text().toStdString();
std::string gamertag_str = SafeStdString(gamertag_edit_->text());
std::u16string gamertag = xe::to_utf16(gamertag_str);
string_util::copy_truncating(account.gamertag, gamertag,
std::size(account.gamertag));
@@ -864,7 +861,7 @@ void ProfileEditorDialogQt::SaveProfileData() {
size_t max_utf16_chars) {
// Convert QString to std::u16string using xe::to_utf16 for proper
// conversion
std::string utf8_text = text.toStdString();
std::string utf8_text = SafeStdString(text);
std::u16string new_value = xe::to_utf16(utf8_text);
// Truncate if exceeds max length (in UTF-16 characters)
+35
View File
@@ -0,0 +1,35 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_QT_UTIL_H_
#define XENIA_UI_QT_UTIL_H_
#include <QString>
#include <string>
namespace xe {
namespace ui {
// Safely convert std::string to QString, handling invalid UTF-8 data
// Uses QString::fromUtf8 which is more robust than fromStdString
inline QString SafeQString(const std::string& str) {
return QString::fromUtf8(str.c_str(), static_cast<qsizetype>(str.size()));
}
// Safely convert QString to std::string, handling invalid UTF-8 data
// Uses QString::toUtf8 which is more robust than toStdString
inline std::string SafeStdString(const QString& qstr) {
QByteArray utf8 = qstr.toUtf8();
return std::string(utf8.constData(), utf8.size());
}
} // namespace ui
} // namespace xe
#endif // XENIA_UI_QT_UTIL_H_
+6 -4
View File
@@ -16,10 +16,13 @@
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/app/emulator_window.h"
#include "xenia/ui/qt_util.h"
namespace xe {
namespace app {
using xe::ui::SafeQString;
XmpDialogQt::XmpDialogQt(QWidget* parent, EmulatorWindow* emulator_window)
: QDialog(parent), emulator_window_(emulator_window) {
SetupUI();
@@ -246,8 +249,8 @@ void XmpDialogQt::UpdatePlayerState() {
float volume = audio_player->GetVolume()->load();
volume_slider_->blockSignals(true);
volume_slider_->setValue(static_cast<int>(volume * 100));
volume_value_label_->setText(QString::fromStdString(
fmt::format("{}%", static_cast<int>(volume * 100))));
volume_value_label_->setText(
SafeQString(fmt::format("{}%", static_cast<int>(volume * 100))));
volume_slider_->blockSignals(false);
}
@@ -287,8 +290,7 @@ void XmpDialogQt::OnVolumeChanged(int value) {
audio_player->SetVolume(volume);
// Update label
volume_value_label_->setText(
QString::fromStdString(fmt::format("{}%", value)));
volume_value_label_->setText(SafeQString(fmt::format("{}%", value)));
}
} // namespace app