Compare commits

...

8 Commits

Author SHA1 Message Date
TellowKrinkle 5e3ad282a8 common: Fix build on ryml < 0.11 2026-07-11 23:13:53 -05:00
PCSX2 Bot 091b3a8694 [ci skip] Qt: Update Base Translation. 2026-07-12 02:18:27 +02:00
KamFretoZ 42ce97aca5 Qt: Clean up Setup Wizard's labels 2026-07-11 15:41:27 +02:00
KamFretoZ a4df5c300e Qt/Launcher: Fix StartupWMClass Value 2026-07-11 15:41:27 +02:00
KamFretoZ 4bf51e735a Qt: Add Create PCSX2 shortcut option to setup wizard 2026-07-11 15:41:27 +02:00
KamFretoZ d7e7c8164c Qt: Decouple CreateShortcut from the ShortcutCreationDialog 2026-07-11 15:41:27 +02:00
TJnotJT eb5e3fcd13 GS/HW: Make all vertices the same color in certain flat shading cases.
Fixes issues with AA1 with flat shading.
2026-07-10 10:16:51 +02:00
TheLastRar 8f8968cbb1 Deps: Revert workaround for MSVC ICE with PlutoVG
This reverts commit 7ade24fc77.
2026-07-09 18:40:47 -04:00
19 changed files with 699 additions and 778 deletions
@@ -3,7 +3,7 @@ Version=1.0
Terminal=false
Type=Application
Name=PCSX2
StartupWMClass=PCSX2
StartupWMClass=pcsx2-qt
GenericName=PlayStation 2 Emulator
Comment=Sony PlayStation 2 emulator
Exec=pcsx2-qt
@@ -471,7 +471,6 @@ echo "Building PlutoVG..."
rmdir /S /Q "plutovg-%PLUTOVG%"
%SEVENZIP% x "plutovg-%PLUTOVG%.zip" || goto error
cd "plutovg-%PLUTOVG%" || goto error
%PATCH% -p1 < "%SCRIPTDIR%\plutovg-workaround-msvc-ice.patch" || goto error
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DPLUTOVG_BUILD_EXAMPLES=OFF -B build -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error
@@ -1,52 +0,0 @@
diff --git a/source/plutovg-font.c b/source/plutovg-font.c
index 0ad89b1..110de7f 100644
--- a/source/plutovg-font.c
+++ b/source/plutovg-font.c
@@ -836,7 +836,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
size_t family_index = 0;
if(unicode_family_name) {
const stbtt_uint8* family_name = unicode_family_name;
- while(family_length) {
+ //while(family_length) {
+ for (int i = family_length; i > 0; i -= 2) {
stbtt_uint16 ch = family_name[0] * 256 + family_name[1];
if(ch < 0x80) {
entry->family[family_index++] = ch;
@@ -853,7 +854,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
entry->family[family_index++] = (0x80 + ((c) & 0x3f));
family_name += 2;
- family_length -= 2;
+ //family_length -= 2;
+ i -= 2;
} else {
entry->family[family_index++] = (0xe0 + (ch >> 12));
entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f));
@@ -861,7 +863,7 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
}
family_name += 2;
- family_length -= 2;
+ //family_length -= 2;
}
entry->family[family_index] = '\0';
@@ -902,7 +904,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
};
const stbtt_uint8* family_name = roman_family_name;
- while(family_length) {
+ //while(family_length) {
+ for (int i2 = family_length; i2 > 0; i2--) {
stbtt_uint16 ch = MAC_ROMAN_TABLE[family_name[0]];
if(ch < 0x80) {
entry->family[family_index++] = ch;
@@ -916,7 +919,7 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
}
family_name += 1;
- family_length -= 1;
+ //family_length -= 1;
}
entry->family[family_index] = '\0';
-5
View File
@@ -2014,11 +2014,6 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
vtx.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
vtx.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (first vertex in DX)
vtx.c = i0 == 0 ? vtx.c : (i1 == 0 ? other.c : opposite.c);
#endif
}
return vtx;
@@ -455,11 +455,6 @@ void main()
VSout.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
VSout.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (last vertex in GL)
vtx.c = i0 == 2 ? vtx.c : (i1 == 2 ? other.c : opposite.c);
#endif
}
#endif
-5
View File
@@ -450,11 +450,6 @@ void main()
vsOut.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
vsOut.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (last vertex in VK)
vtx.c = i0 == 2 ? vtx.c : (i1 == 2 ? other.c : opposite.c);
#endif
}
#endif
+14 -4
View File
@@ -119,10 +119,16 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
#else
callbacks.m_user_data = static_cast<void*>(&context);
callbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location location, void* user_data) {
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
Error::SetString(context->error, std::string(msg, msg_len));
std::longjmp(context->env, 1);
if (RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data))
{
Error::SetString(context->error, std::string(msg, msg_len));
std::longjmp(context->env, 1);
}
else
{
std::string description(msg, msg_len);
pxFailRel(description.c_str());
}
};
#endif
@@ -145,7 +151,11 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
// Callbacks passed to ryml::Tree are used for value parsing errors later,
// so we need to clear the context before it goes out of scope.
#if RYML_VERSION_MAJOR > 0 || RYML_VERSION_MINOR >= 11
callbacks.set_user_data(nullptr);
#else
callbacks.m_user_data = nullptr;
#endif
tree.callbacks(callbacks);
return tree;
+466
View File
@@ -4,10 +4,12 @@
#include "QtUtils.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QLocale>
#include <QtCore/QtGlobal>
#include <QtCore/QMetaObject>
#include <QtCore/QStandardPaths>
#include <QtGui/QAction>
#include <QtGui/QDesktopServices>
#include <QtGui/QIcon>
@@ -15,6 +17,7 @@
#include <QtGui/QPainter>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QInputDialog>
@@ -27,21 +30,35 @@
#include <QtWidgets/QTableView>
#include <QtWidgets/QTreeView>
#include <fmt/format.h>
#ifdef Q_OS_LINUX
#include <QtGui/private/qtx11extras_p.h>
#endif
#include <algorithm>
#include <array>
#include <cstdlib>
#include <map>
#include "common/CocoaTools.h"
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "pcsx2/Config.h"
#include "QtHost.h"
#if defined(_WIN32)
#include "common/RedtapeWindows.h"
#include "common/RedtapeWilCom.h"
#include <Shlobj.h>
#include <shobjidl.h>
#include <comdef.h>
#else
#include <sys/stat.h>
#include <cerrno>
#include <cstring>
#endif
namespace QtUtils
@@ -549,4 +566,453 @@ namespace QtUtils
const QString flag_path = QStringLiteral("%1/icons/flags/%2.svg").arg(QtHost::GetResourcesBasePath()).arg(country_code.toLower());
return QIcon(flag_path);
}
bool IsRunningInFlatpak()
{
// Checks for the existence of the `.flatpak-info` file which seems to be always present inside flatpak sandboxes.
return FileSystem::FileExists("/.flatpak-info");
}
bool IsRunningInAppImage()
{
// The AppImage runtime sets APPIMAGE environment variable so we can check for that.
return std::getenv("APPIMAGE") != nullptr;
}
void CreateShortcut(QWidget* parent, const std::string& name, const std::string& game_path,
std::vector<std::string> passed_cli_args, const std::string& custom_args,
const std::string& icon_path, bool is_desktop, bool prompt_for_destination)
{
const auto tr_msg = [](const char* str) {
return QCoreApplication::translate("CreateShortcut", str);
};
#if defined(_WIN32)
if (name.empty())
{
Console.Error("Cannot create shortcuts without a name.");
return;
}
// Sanitize filename
const std::string clean_name = Path::SanitizeFileName(name);
std::string clean_path = game_path.empty() ? std::string() : Path::ToNativePath(Path::RealPath(game_path));
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Get path to Desktop or per-user Start Menu\Programs directory
// https://superuser.com/questions/1489874/how-can-i-get-the-real-path-of-desktop-in-windows-explorer/1789849#1789849
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
std::string link_file;
if (wil::unique_cotaskmem_string directory; SUCCEEDED(SHGetKnownFolderPath(is_desktop ? FOLDERID_Desktop : FOLDERID_Programs, 0, NULL, &directory)))
{
std::string directory_utf8 = StringUtil::WideStringToUTF8String(directory.get());
if (is_desktop)
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", directory_utf8, clean_name));
else
{
const std::string pcsx2_start_menu_dir = Path::ToNativePath(fmt::format("{}/PCSX2", directory_utf8));
if (!FileSystem::EnsureDirectoryExists(pcsx2_start_menu_dir.c_str(), false))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not create start menu directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", pcsx2_start_menu_dir, clean_name));
}
}
else
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), is_desktop ? tr_msg("'Desktop' directory not found") : tr_msg("User's 'Start Menu\\Programs' directory not found"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Check if the same shortcut already exists
if (prompt_for_destination && FileSystem::FileExists(link_file.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= EscapeShortcutCommandLine(&arg);
if (!clean_path.empty())
lossless &= EscapeShortcutCommandLine(&clean_path);
if (!lossless)
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string final_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
if (!custom_args.empty())
{
if (!final_args.empty())
final_args += ' ';
final_args += custom_args;
}
if (!clean_path.empty())
{
if (!final_args.empty())
final_args += ' ';
final_args += fmt::format("-- {}", clean_path);
}
Console.WriteLnFmt("Creating a shortcut '{}' with arguments '{}'", link_file, final_args);
const auto str_error = [](HRESULT hr) -> std::string {
_com_error err(hr);
const TCHAR* errMsg = err.ErrorMessage();
return fmt::format("{} [{}]", StringUtil::WideStringToUTF8String(errMsg), hr);
};
// Construct the shortcut
// https://stackoverflow.com/questions/3906974/how-to-programmatically-create-a-shortcut-using-win32
HRESULT res = CoInitialize(NULL);
if (FAILED(res))
{
Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res));
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("CoInitialize failed (%1)").arg(QString::fromStdString(str_error(res))), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
wil::unique_couninitialize_call co_cleanup;
const auto report_error = [&](const QString& reason) {
Console.ErrorFmt("Failed to create shortcut: {}", reason.toStdString());
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), reason, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
};
wil::com_ptr_nothrow<IShellLink> pShellLink = wil::CoCreateInstanceNoThrow<IShellLink>(CLSID_ShellLink);
wil::com_ptr_nothrow<IPersistFile> pPersistFile;
if (!pShellLink)
{
report_error(tr_msg("CoCreateInstance failed"));
return;
}
// Set path to the executable
const std::wstring target_file = StringUtil::UTF8StringToWideString(FileSystem::GetProgramPath());
res = pShellLink->SetPath(target_file.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetPath failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the working directory
const std::wstring working_dir = StringUtil::UTF8StringToWideString(Path::GetDirectory(FileSystem::GetProgramPath()));
res = pShellLink->SetWorkingDirectory(working_dir.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetWorkingDirectory failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the description (shown as the shortcut's tooltip)
const std::wstring description = tr_msg("PlayStation 2 Emulator").toStdWString();
res = pShellLink->SetDescription(description.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetDescription failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the launch arguments
if (!final_args.empty())
{
const std::wstring target_cli_args = StringUtil::UTF8StringToWideString(final_args);
res = pShellLink->SetArguments(target_cli_args.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetArguments failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
}
// Set the icon
std::string final_icon_path;
if (!icon_path.empty())
{
final_icon_path = Path::ToNativePath(icon_path);
if (!FileSystem::FileExists(final_icon_path.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
}
else
{
final_icon_path = Path::ToNativePath(Path::Combine(Path::GetDirectory(FileSystem::GetProgramPath()), "resources/icons/AppIconLarge.ico"));
}
const std::wstring w_icon_path = StringUtil::UTF8StringToWideString(final_icon_path);
res = pShellLink->SetIconLocation(w_icon_path.c_str(), 0);
if (FAILED(res))
{
report_error(tr_msg("SetIconLocation failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Use the IPersistFile object to save the shell link
res = pShellLink.query_to(&pPersistFile);
if (FAILED(res))
{
report_error(tr_msg("QueryInterface failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Save shortcut link to disk
const std::wstring w_link_file = StringUtil::UTF8StringToWideString(link_file);
res = pPersistFile->Save(w_link_file.c_str(), TRUE);
if (FAILED(res))
{
report_error(tr_msg("Failed to save the shortcut (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
#else
if (name.empty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Cannot create a shortcut without a title."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
const bool is_flatpak = IsRunningInFlatpak();
// Sanitize filename and game path
const std::string clean_name = Path::SanitizeFileName(name);
std::string clean_path = game_path.empty() ? std::string() : Path::Canonicalize(Path::RealPath(game_path));
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find the executable path
std::string executable_path = FileSystem::GetPackagePath();
if (executable_path.empty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Executable path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find the destination directory
const QString link_dir = QStandardPaths::writableLocation(is_desktop ? QStandardPaths::DesktopLocation : QStandardPaths::ApplicationsLocation);
if (link_dir.isEmpty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not determine the shortcut destination directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
const std::string link_path = fmt::format("{}/{}.desktop", link_dir.toStdString(), clean_name);
std::string icon_name;
if (!icon_path.empty())
{
if (!FileSystem::FileExists(icon_path.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
icon_name = icon_path;
}
else
{
if (is_flatpak) // Flatpak
{
executable_path = "flatpak run net.pcsx2.PCSX2";
icon_name = "net.pcsx2.PCSX2";
}
else
{
// Copy PCSX2 icon
icon_name = "PCSX2";
const std::string icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).toStdString());
const std::string icon_path_dest = fmt::format("{}/{}.png", icon_dest, icon_name);
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path_dest.c_str(), true);
}
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= EscapeShortcutCommandLine(&arg);
if (!is_flatpak)
lossless &= EscapeShortcutCommandLine(&executable_path);
if (!clean_path.empty())
lossless &= EscapeShortcutCommandLine(&clean_path);
if (!lossless)
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
// Assembling the .desktop file
std::string final_args = executable_path;
if (!cmdline.empty())
final_args += fmt::format(" {}", cmdline);
if (!custom_args.empty())
final_args += fmt::format(" {}", custom_args);
if (!clean_path.empty())
final_args += fmt::format(" -- {}", clean_path);
std::string file_content =
"[Desktop Entry]\n"
"Version=1.0\n"
"Type=Application\n"
"Terminal=false\n"
"StartupWMClass=pcsx2-qt\n"
"Exec=" +
final_args + "\n" +
"Name=" +
name + "\n" +
"GenericName=PlayStation 2 Emulator\n"
"Icon=" +
icon_name + "\n" +
"Comment=Sony PlayStation 2 Emulator\n"
"Keywords=game;emulator;\n"
"Categories=Game;Emulator;\n";
std::string_view sv(file_content);
QString final_path = QString::fromStdString(link_path);
if (prompt_for_destination)
{
const QString filter(tr_msg("Desktop Shortcut Files (*.desktop)"));
final_path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent, tr_msg("Select Shortcut Save Destination"), final_path, filter));
if (final_path.isEmpty())
return;
}
else
{
const std::string link_dir(Path::GetDirectory(link_path));
if (!link_dir.empty() && !FileSystem::EnsureDirectoryExists(link_dir.c_str(), true))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not create the shortcut directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
}
// Write to .desktop file
if (!FileSystem::WriteStringToFile(final_path.toStdString().c_str(), sv))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Failed to create .desktop file"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
if (chmod(final_path.toStdString().c_str(), S_IRWXU) != 0) // enables user to execute file
Console.ErrorFmt("Failed to change file permissions for .desktop file: {} ({})", strerror(errno), errno);
#endif
}
bool EscapeShortcutCommandLine(std::string* arg)
{
#ifdef _WIN32
if (!arg->empty() && arg->find_first_of(" \t\n\v\"") == std::string::npos)
return true;
std::string temp;
temp.reserve(arg->length() + 10);
temp += '"';
for (auto it = arg->begin();; ++it)
{
int backslash_count = 0;
while (it != arg->end() && *it == '\\')
{
++it;
++backslash_count;
}
if (it == arg->end())
{
temp.append(backslash_count * 2, '\\');
break;
}
if (*it == '"')
{
temp.append(backslash_count * 2 + 1, '\\');
temp += '"';
}
else
{
temp.append(backslash_count, '\\');
temp += *it;
}
}
temp += '"';
*arg = std::move(temp);
return true;
#else
const char* carg = arg->c_str();
const char* cend = carg + arg->size();
const char* RESERVED_CHARS = " \t\n\\\"'\\\\><~|%&;$*?#()`"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
const char* next = carg + std::strcspn(carg, RESERVED_CHARS);
if (next == cend)
return true; // No escaping needed, don't modify
bool lossless = true;
std::string temp = "\"";
const char* NOT_VALID_IN_QUOTE = "%`$\"\\\n"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
while (true)
{
next = carg + std::strcspn(carg, NOT_VALID_IN_QUOTE);
temp.append(carg, next);
carg = next;
if (carg == cend)
break;
switch (*carg)
{
case '"':
case '`':
temp.push_back('\\');
temp.push_back(*carg);
break;
case '\\':
temp.append("\\\\\\\\");
break;
case '$':
temp.push_back('\\');
temp.push_back('\\');
temp.push_back(*carg);
break;
case '%':
temp.push_back('%');
temp.push_back(*carg);
break;
default:
temp.push_back(' ');
lossless = false;
break;
}
++carg;
}
temp.push_back('"');
*arg = std::move(temp);
return lossless;
#endif
}
} // namespace QtUtils
+17
View File
@@ -17,9 +17,11 @@
#include <QtGui/QScreen>
#include <functional>
#include <initializer_list>
#include <string>
#include <string_view>
#include <type_traits>
#include <optional>
#include <vector>
#include "common/Console.h"
@@ -209,4 +211,19 @@ namespace QtUtils
/// Gets a flag icon for a given language code
/// Returns an empty QIcon if no flag is available for the language
QIcon GetFlagIconForLanguage(const QString& language_code);
/// Returns true if PCSX2 is running inside a Flatpak sandbox.
bool IsRunningInFlatpak();
/// Returns true if PCSX2 is running from an AppImage.
bool IsRunningInAppImage();
/// Creates a desktop or launcher (Start Menu / applications) shortcuts
void CreateShortcut(QWidget* parent, const std::string& name, const std::string& game_path,
std::vector<std::string> passed_cli_args, const std::string& custom_args,
const std::string& icon_path, bool is_desktop, bool prompt_for_destination = true);
/// Escapes the given string for use as a shortcut command line argument.
/// Returns whether the escaping operation was lossless.
bool EscapeShortcutCommandLine(std::string* arg);
} // namespace QtUtils
+22
View File
@@ -88,6 +88,11 @@ void SetupWizardDialog::nextPage()
const int current_page = m_ui.pages->currentIndex();
if (current_page == Page_Complete)
{
if (m_ui.createDesktopShortcut->isChecked())
QtUtils::CreateShortcut(this, "PCSX2", std::string(), {}, std::string(), std::string(), true, false);
if (m_ui.addToApplicationMenu->isChecked())
QtUtils::CreateShortcut(this, "PCSX2", std::string(), {}, std::string(), std::string(), false, false);
accept();
return;
}
@@ -177,6 +182,7 @@ void SetupWizardDialog::setupUi()
setupGameListPage();
setupControllerPage();
setupRetroAchievementsPage();
setupCompletePage();
}
void SetupWizardDialog::setupLanguagePage()
@@ -435,6 +441,22 @@ void SetupWizardDialog::setupRetroAchievementsPage()
refreshRetroAchievementsLoginState();
}
void SetupWizardDialog::setupCompletePage()
{
#if defined(_WIN32)
const bool can_create_shortcuts = true;
m_ui.addToApplicationMenu->setText(tr("Add PCSX2 to the Start Menu"));
#elif defined(__linux__)
// Only offer shortcuts for the AppImage since Flatpak creates its own launcher, and
// third-party builds (AUR, COPR, etc.) ship their own .desktop file.
const bool can_create_shortcuts = QtUtils::IsRunningInAppImage();
#else
const bool can_create_shortcuts = false;
#endif
m_ui.createDesktopShortcut->setVisible(can_create_shortcuts);
m_ui.addToApplicationMenu->setVisible(can_create_shortcuts);
}
void SetupWizardDialog::refreshRetroAchievementsLoginState()
{
const std::string username(Host::GetBaseStringSettingValue("Achievements", "Username"));
+1
View File
@@ -66,6 +66,7 @@ private:
void setupGameListPage();
void setupControllerPage();
void setupRetroAchievementsPage();
void setupCompletePage();
void refreshRetroAchievementsLoginState();
void pageChangedTo(int page);
+47 -25
View File
@@ -23,7 +23,7 @@
<number>10</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QVBoxLayout" name="pagesLayout">
<item>
<widget class="QLabel" name="logo">
<property name="sizePolicy">
@@ -61,7 +61,6 @@
<widget class="QLabel" name="labelLanguage">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -127,7 +126,7 @@
<property name="currentIndex">
<number>4</number>
</property>
<widget class="QWidget" name="page">
<widget class="QWidget" name="SetupWizardPages">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>4</number>
@@ -145,7 +144,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="IntroLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;h1 style=&quot; margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:xx-large; font-weight:700;&quot;&gt;Welcome to PCSX2!&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;This wizard will help guide you through the configuration steps required to use the application. It is recommended if this is your first time installing PCSX2 that you view the setup guide &lt;a href=&quot;https://pcsx2.net/docs/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;By default, PCSX2 will connect to the server at &lt;a href=&quot;https://pcsx2.net/&quot;&gt;pcsx2.net&lt;/a&gt; to check for updates, and if available and confirmed, download update packages from &lt;a href=&quot;https://github.com/&quot;&gt;github.com&lt;/a&gt;. If you do not wish for PCSX2 to make any network connections on startup, you should uncheck the Automatic Updates option now. The Automatic Update setting can be changed later at any time in Interface Settings.&lt;/p&gt;&lt;p&gt;Please choose a language and theme to begin.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -164,7 +163,7 @@
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<layout class="QFormLayout" name="languageLayout">
<item row="0" column="0">
<widget class="QLabel" name="languageLabel">
<property name="text">
@@ -222,7 +221,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<widget class="QWidget" name="pageBios">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
@@ -237,7 +236,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<widget class="QLabel" name="biosIntro">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;PCSX2 requires a PS2 BIOS in order to run.&lt;/p&gt;&lt;p&gt;For legal reasons, you must obtain a BIOS &lt;strong&gt;from an actual PS2 unit that you own&lt;/strong&gt; (borrowing doesn't count).&lt;/p&gt;&lt;p&gt;Once dumped, this BIOS image should be placed in the bios folder within the data directory shown below, or you can instruct PCSX2 to scan an alternative directory.&lt;/p&gt;&lt;p&gt;A guide for dumping your BIOS can be found &lt;a href=&quot;https://pcsx2.net/docs/setup/bios/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -256,7 +255,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<layout class="QHBoxLayout" name="biosPathLayout">
<item>
<widget class="QLabel" name="biosSearchDirectoryLabel">
<property name="text">
@@ -310,7 +309,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QHBoxLayout" name="biosActions">
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
@@ -342,7 +341,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<widget class="QWidget" name="pageDumps">
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
@@ -357,7 +356,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_6">
<widget class="QLabel" name="dumpsIntro">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;PCSX2 will automatically scan and identify games from the selected directories below, and populate the game list.&lt;br&gt;These games should be dumped from discs you own. Guides for dumping discs can be found &lt;a href=&quot;https://pcsx2.net/docs/setup/dumping&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Supported formats for dumps include:&lt;/p&gt;&lt;p&gt;&lt;ul&gt;&lt;li&gt;.bin/.iso (ISO Disc Images)&lt;/li&gt;&lt;li&gt;.mdf (Media Descriptor File)&lt;/li&gt;&lt;li&gt;.chd (Compressed Hunks of Data)&lt;/li&gt;&lt;li&gt;.cso (Compressed ISO)&lt;/li&gt;&lt;li&gt;.zso (Compressed ISO)&lt;/li&gt;&lt;li&gt;.gz (Gzip Compressed ISO)&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -376,7 +375,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<layout class="QHBoxLayout" name="dumpsLayout">
<item>
<widget class="QLabel" name="searchDirectoryListLabel">
<property name="text">
@@ -474,7 +473,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_8">
<widget class="QLabel" name="controllerIntro">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, PCSX2 will map your keyboard to the virtual PS2 controller.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:704;&quot;&gt;To use an external controller, you must map it first. &lt;/span&gt;On this screen, you can automatically map any controller which is currently connected. If your controller is not currently connected, you can plug it in now.&lt;/p&gt;&lt;p&gt;To change controller bindings in more detail, or use multi-tap, open the Settings menu and choose Controllers once you have completed the Setup Wizard.&lt;/p&gt;&lt;p&gt;Guides for configuring controllers can be found &lt;a href=&quot;https://pcsx2.net/docs/post/controllers/&quot;&gt;&lt;span style=&quot;&quot;&gt;here&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -487,7 +486,7 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="controller1Group">
<property name="title">
<string>Controller Port 1</string>
</property>
@@ -496,14 +495,14 @@
<widget class="QComboBox" name="controller1Type"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_12">
<widget class="QLabel" name="controllerMappedLabel">
<property name="text">
<string>Controller Mapped To:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_11">
<widget class="QLabel" name="controller1Label">
<property name="text">
<string>Controller Type:</string>
</property>
@@ -513,12 +512,15 @@
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<layout class="QHBoxLayout" name="controller1MappingLayout">
<item>
<widget class="QLabel" name="controller1Mapping">
<property name="text">
<string>Default (Keyboard)</string>
</property>
<property name="buddy">
<cstring>controller1Type</cstring>
</property>
</widget>
</item>
<item>
@@ -554,13 +556,13 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<widget class="QGroupBox" name="controller2Group">
<property name="title">
<string>Controller Port 2</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_13">
<widget class="QLabel" name="controller2Label">
<property name="text">
<string>Controller Type:</string>
</property>
@@ -573,19 +575,22 @@
<widget class="QComboBox" name="controller2Type"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_14">
<widget class="QLabel" name="controller2MappedLabel">
<property name="text">
<string>Controller Mapped To:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<layout class="QHBoxLayout" name="controller2MappingLayout">
<item>
<widget class="QLabel" name="controller2Mapping">
<property name="text">
<string>Default (Keyboard)</string>
</property>
<property name="buddy">
<cstring>controller2Type</cstring>
</property>
</widget>
</item>
<item>
@@ -653,7 +658,7 @@
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<layout class="QHBoxLayout" name="raLogoLayout">
<item>
<widget class="QLabel" name="raLogo">
<property name="sizePolicy">
@@ -736,6 +741,9 @@
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>raEnableAchievements</cstring>
</property>
</widget>
</item>
<item>
@@ -885,7 +893,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_5">
<widget class="QWidget" name="pageComplete">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="leftMargin">
<number>0</number>
@@ -900,7 +908,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_9">
<widget class="QLabel" name="completeIntro">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;h1 style=&quot; margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:xx-large; font-weight:700;&quot;&gt;Setup Complete!&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;You are now ready to run games.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Warning: Do not run untrusted programs in PCSX2. It is not a sandbox and cannot protect your computer from malicious software.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Further options are available under the settings menu. You can also use the Big Picture UI for navigation entirely with a gamepad.&lt;/p&gt;&lt;p&gt;We hope you enjoy using PCSX2.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -912,12 +920,26 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="createDesktopShortcut">
<property name="text">
<string>Create Desktop shortcut</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="addToApplicationMenu">
<property name="text">
<string>Add PCSX2 to the Application Menu</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="navigationLayout">
<property name="spacing">
<number>6</number>
</property>
+4 -433
View File
@@ -3,23 +3,14 @@
#include "ShortcutCreationDialog.h"
#include "QtHost.h"
#include <fmt/format.h>
#include "QtUtils.h"
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "VMManager.h"
#if defined(_WIN32)
#include "common/RedtapeWilCom.h"
#include <shlobj.h>
#include <shobjidl.h>
#include <comdef.h>
#endif
ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path)
: QDialog(parent)
, m_title(title)
@@ -100,7 +91,7 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
m_ui.resetIconButton->setEnabled(false);
});
if (std::getenv("container"))
if (QtUtils::IsRunningInFlatpak())
{
m_ui.portableModeToggle->setEnabled(false);
m_ui.shortcutDesktop->setEnabled(false);
@@ -108,7 +99,7 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
}
connect(m_ui.dialogButtons, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_ui.dialogButtons, &QDialogButtonBox::accepted, this, [&]() {
connect(m_ui.dialogButtons, &QDialogButtonBox::accepted, this, [this]() {
std::vector<std::string> args;
if (m_ui.portableModeToggle->isChecked())
@@ -162,430 +153,10 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
std::string custom_args = m_ui.customArgsInput->text().toStdString();
std::string icon_path = m_ui.iconPath->text().toStdString();
ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), args, custom_args, icon_path, m_ui.shortcutDesktop->isChecked());
QtUtils::CreateShortcut(this, m_title.toStdString(), m_path.toStdString(), std::move(args), custom_args, icon_path, m_ui.shortcutDesktop->isChecked());
accept();
});
}
void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::string game_path, std::vector<std::string> passed_cli_args, std::string custom_args, const std::string icon_path, bool is_desktop)
{
#if defined(_WIN32)
if (name.empty())
{
Console.Error("Cannot create shortcuts without a name.");
return;
}
// Sanitize filename
const std::string clean_name = Path::SanitizeFileName(name).c_str();
std::string clean_path = Path::ToNativePath(Path::RealPath(game_path)).c_str();
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Get path to Desktop or per-user Start Menu\Programs directory
// https://superuser.com/questions/1489874/how-can-i-get-the-real-path-of-desktop-in-windows-explorer/1789849#1789849
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
std::string link_file;
if (wil::unique_cotaskmem_string directory; SUCCEEDED(SHGetKnownFolderPath(is_desktop ? FOLDERID_Desktop : FOLDERID_Programs, 0, NULL, &directory)))
{
std::string directory_utf8 = StringUtil::WideStringToUTF8String(directory.get());
if (is_desktop)
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", directory_utf8, clean_name));
else
{
const std::string pcsx2_start_menu_dir = Path::ToNativePath(fmt::format("{}/PCSX2", directory_utf8));
if (!FileSystem::EnsureDirectoryExists(pcsx2_start_menu_dir.c_str(), false))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Could not create start menu directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", pcsx2_start_menu_dir, clean_name));
}
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), is_desktop ? tr("'Desktop' directory not found") : tr("User's 'Start Menu\\Programs' directory not found"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Check if the same shortcut already exists
if (FileSystem::FileExists(link_file.c_str()))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
if (!lossless)
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path);
std::string combined_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
std::string final_args = fmt::format("{} {} -- {}", combined_args, custom_args, clean_path);
Console.WriteLnFmt("Creating a shortcut '{}' with arguments '{}'", link_file, final_args);
const auto str_error = [](HRESULT hr) -> std::string {
_com_error err(hr);
const TCHAR* errMsg = err.ErrorMessage();
return fmt::format("{} [{}]", StringUtil::WideStringToUTF8String(errMsg), hr);
};
// Construct the shortcut
// https://stackoverflow.com/questions/3906974/how-to-programmatically-create-a-shortcut-using-win32
HRESULT res = CoInitialize(NULL);
if (FAILED(res))
{
Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res));
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("CoInitialize failed (%1)").arg(str_error(res)), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
wil::unique_couninitialize_call co_cleanup;
const auto report_error = [&](const QString& reason) {
Console.ErrorFmt("Failed to create shortcut: {}", reason.toStdString());
QMessageBox::critical(this, tr("Failed to create shortcut"), reason, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
};
wil::com_ptr_nothrow<IShellLink> pShellLink = wil::CoCreateInstanceNoThrow<IShellLink>(CLSID_ShellLink);
wil::com_ptr_nothrow<IPersistFile> pPersistFile;
if (!pShellLink)
{
report_error(tr("CoCreateInstance failed"));
return;
}
// Set path to the executable
const std::wstring target_file = StringUtil::UTF8StringToWideString(FileSystem::GetProgramPath());
res = pShellLink->SetPath(target_file.c_str());
if (FAILED(res))
{
report_error(tr("SetPath failed (%1)").arg(str_error(res)));
return;
}
// Set the working directory
const std::wstring working_dir = StringUtil::UTF8StringToWideString(FileSystem::GetWorkingDirectory());
res = pShellLink->SetWorkingDirectory(working_dir.c_str());
if (FAILED(res))
{
report_error(tr("SetWorkingDirectory failed (%1)").arg(str_error(res)));
return;
}
// Set the launch arguments
if (!final_args.empty())
{
const std::wstring target_cli_args = StringUtil::UTF8StringToWideString(final_args);
res = pShellLink->SetArguments(target_cli_args.c_str());
if (FAILED(res))
{
report_error(tr("SetArguments failed (%1)").arg(str_error(res)));
return;
}
}
// Set the icon
std::string final_icon_path;
if (!icon_path.empty())
{
final_icon_path = Path::ToNativePath(icon_path);
if (!FileSystem::FileExists(final_icon_path.c_str()))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
}
else
{
final_icon_path = Path::ToNativePath(Path::Combine(Path::GetDirectory(FileSystem::GetProgramPath()), "resources/icons/AppIconLarge.ico"));
}
const std::wstring w_icon_path = StringUtil::UTF8StringToWideString(final_icon_path);
res = pShellLink->SetIconLocation(w_icon_path.c_str(), 0);
if (FAILED(res))
{
report_error(tr("SetIconLocation failed (%1)").arg(str_error(res)));
return;
}
// Use the IPersistFile object to save the shell link
res = pShellLink.query_to(&pPersistFile);
if (FAILED(res))
{
report_error(tr("QueryInterface failed (%1)").arg(str_error(res)));
return;
}
// Save shortcut link to disk
const std::wstring w_link_file = StringUtil::UTF8StringToWideString(link_file);
res = pPersistFile->Save(w_link_file.c_str(), TRUE);
if (FAILED(res))
{
report_error(tr("Failed to save the shortcut (%1)").arg(str_error(res)));
return;
}
#else
if (name.empty())
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Cannot create a shortcut without a title."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
bool is_flatpak = (std::getenv("container"));
// Sanitize filename and game path
const std::string clean_name = Path::SanitizeFileName(name);
std::string clean_path = Path::Canonicalize(Path::RealPath(game_path));
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find the executable path
std::string executable_path = FileSystem::GetPackagePath();
if (executable_path.empty())
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Executable path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find home directory
std::string link_path;
const char* home = std::getenv("HOME");
const char* xdg_desktop_dir = std::getenv("XDG_DESKTOP_DIR");
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
if (home)
{
if (is_desktop)
{
if (xdg_desktop_dir)
link_path = fmt::format("{}/{}.desktop", xdg_desktop_dir, clean_name);
else
link_path = fmt::format("{}/Desktop/{}.desktop", home, clean_name);
}
else
{
if (xdg_data_home)
link_path = fmt::format("{}/applications/{}.desktop", xdg_data_home, clean_name);
else
link_path = fmt::format("{}/.local/share/applications/{}.desktop", home, clean_name);
}
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Path to the Home directory is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string icon_name;
if (!icon_path.empty())
{
if (!FileSystem::FileExists(icon_path.c_str()))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
icon_name = icon_path;
}
else
{
// Copy PCSX2 icon
std::string icon_dest;
if (xdg_data_home)
icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", xdg_data_home);
else
icon_dest = fmt::format("{}/.local/share/icons/hicolor/512x512/apps/", home);
if (is_flatpak) // Flatpak
{
executable_path = "flatpak run net.pcsx2.PCSX2";
icon_name = "net.pcsx2.PCSX2";
}
else
{
icon_name = "PCSX2";
std::string icon_path_dest = fmt::format("{}/{}.png", icon_dest, icon_name).c_str();
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
if (!FileSystem::FileExists(icon_path_dest.c_str()))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path_dest.c_str(), false);
}
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
if (!lossless)
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
// Further string sanitization
if (!is_flatpak)
ShortcutCreationDialog::EscapeShortcutCommandLine(&executable_path);
ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path);
// Assembling the .desktop file
std::string final_args;
final_args = fmt::format("{} {} {} -- {}", executable_path, cmdline, custom_args, clean_path);
std::string file_content =
"[Desktop Entry]\n"
"Encoding=UTF-8\n"
"Version=1.0\n"
"Type=Application\n"
"Terminal=false\n"
"StartupWMClass=PCSX2\n"
"Exec=" +
final_args + "\n" +
"Name=" +
clean_name + "\n" +
"Icon=" +
icon_name + "\n" +
"Categories=Game;Emulator;\n";
std::string_view sv(file_content);
// Prompt user for shortcut saving destination
QString final_path(QStringLiteral("%1").arg(QString::fromStdString(link_path)));
const QString filter(tr("Desktop Shortcut Files (*.desktop)"));
final_path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Select Shortcut Save Destination"), final_path, filter));
if (final_path.isEmpty())
return;
// Write to .desktop file
if (!FileSystem::WriteStringToFile(final_path.toStdString().c_str(), sv))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Failed to create .desktop file"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
if (chmod(final_path.toStdString().c_str(), S_IRWXU) != 0) // enables user to execute file
Console.ErrorFmt("Failed to change file permissions for .desktop file: {} ({})", strerror(errno), errno);
#endif
}
bool ShortcutCreationDialog::EscapeShortcutCommandLine(std::string* arg)
{
#ifdef _WIN32
if (!arg->empty() && arg->find_first_of(" \t\n\v\"") == std::string::npos)
return true;
std::string temp;
temp.reserve(arg->length() + 10);
temp += '"';
for (auto it = arg->begin();; ++it)
{
int backslash_count = 0;
while (it != arg->end() && *it == '\\')
{
++it;
++backslash_count;
}
if (it == arg->end())
{
temp.append(backslash_count * 2, '\\');
break;
}
if (*it == '"')
{
temp.append(backslash_count * 2 + 1, '\\');
temp += '"';
}
else
{
temp.append(backslash_count, '\\');
temp += *it;
}
}
temp += '"';
*arg = std::move(temp);
return true;
#else
const char* carg = arg->c_str();
const char* cend = carg + arg->size();
const char* RESERVED_CHARS = " \t\n\\\"'\\\\><~|%&;$*?#()`"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
const char* next = carg + std::strcspn(carg, RESERVED_CHARS);
if (next == cend)
return true; // No escaping needed, don't modify
bool lossless = true;
std::string temp = "\"";
const char* NOT_VALID_IN_QUOTE = "%`$\"\\\n"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
while (true)
{
next = carg + std::strcspn(carg, NOT_VALID_IN_QUOTE);
temp.append(carg, next);
carg = next;
if (carg == cend)
break;
switch (*carg)
{
case '"':
case '`':
temp.push_back('\\');
temp.push_back(*carg);
break;
case '\\':
temp.append("\\\\\\\\");
break;
case '$':
temp.push_back('\\');
temp.push_back('\\');
temp.push_back(*carg);
break;
case '%':
temp.push_back('%');
temp.push_back(*carg);
break;
default:
temp.push_back(' ');
lossless = false;
break;
}
++carg;
}
temp.push_back('"');
*arg = std::move(temp);
return lossless;
#endif
}
#include "moc_ShortcutCreationDialog.cpp"
-7
View File
@@ -14,13 +14,6 @@ public:
ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path);
~ShortcutCreationDialog() = default;
/// Create desktop shortcut for games
void CreateShortcut(const std::string name, const std::string game_path, std::vector<std::string> passed_cli_args, std::string custom_args, const std::string icon_path, bool is_desktop);
/// Escapes the given string for use with command line arguments.
/// Returns a bool that indicates whether the escaping operation are lossless or not.
bool EscapeShortcutCommandLine(std::string* cmdline);
protected:
const QString m_title;
const QString m_path;
+100 -212
View File
@@ -6419,42 +6419,42 @@ Do you want to overwrite?</source>
<context>
<name>FileOperations</name>
<message>
<location filename="../QtUtils.cpp" line="280"/>
<location filename="../QtUtils.cpp" line="297"/>
<source>Failed to show file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../QtUtils.cpp" line="281"/>
<location filename="../QtUtils.cpp" line="298"/>
<source>Failed to show file in file explorer.
The file was: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../QtUtils.cpp" line="289"/>
<location filename="../QtUtils.cpp" line="306"/>
<source>Show in Explorer</source>
<extracomment>Windows action to show a file in Windows Explorer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../QtUtils.cpp" line="292"/>
<location filename="../QtUtils.cpp" line="309"/>
<source>Show in Finder</source>
<extracomment>macOS action to show a file in Finder</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../QtUtils.cpp" line="295"/>
<location filename="../QtUtils.cpp" line="312"/>
<source>Open Containing Directory</source>
<extracomment>Linux/*NIX: Opens the system file manager to the directory containing a selected file</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../QtUtils.cpp" line="303"/>
<location filename="../QtUtils.cpp" line="320"/>
<source>Failed to open URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../QtUtils.cpp" line="304"/>
<location filename="../QtUtils.cpp" line="321"/>
<source>Failed to open URL.
The URL was: %1</source>
@@ -23225,233 +23225,243 @@ Do you want to continue?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="69"/>
<location filename="../SetupWizardDialog.ui" line="68"/>
<source>Language</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="76"/>
<location filename="../SetupWizardDialog.ui" line="75"/>
<source>BIOS Image</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="83"/>
<location filename="../SetupWizardDialog.ui" line="82"/>
<source>Game Directories</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="90"/>
<location filename="../SetupWizardDialog.ui" line="89"/>
<source>Controller Setup</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="97"/>
<location filename="../SetupWizardDialog.ui" line="700"/>
<location filename="../SetupWizardDialog.ui" line="96"/>
<location filename="../SetupWizardDialog.ui" line="705"/>
<source>RetroAchievements</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="104"/>
<location filename="../SetupWizardDialog.ui" line="103"/>
<source>Complete</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="171"/>
<location filename="../SetupWizardDialog.ui" line="170"/>
<source>Language:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="191"/>
<location filename="../SetupWizardDialog.ui" line="190"/>
<source>Theme:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="217"/>
<location filename="../SetupWizardDialog.ui" line="216"/>
<source>Enable Automatic Updates</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="242"/>
<location filename="../SetupWizardDialog.ui" line="241"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;PCSX2 requires a PS2 BIOS in order to run.&lt;/p&gt;&lt;p&gt;For legal reasons, you must obtain a BIOS &lt;strong&gt;from an actual PS2 unit that you own&lt;/strong&gt; (borrowing doesn&apos;t count).&lt;/p&gt;&lt;p&gt;Once dumped, this BIOS image should be placed in the bios folder within the data directory shown below, or you can instruct PCSX2 to scan an alternative directory.&lt;/p&gt;&lt;p&gt;A guide for dumping your BIOS can be found &lt;a href=&quot;https://pcsx2.net/docs/setup/bios/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="263"/>
<location filename="../SetupWizardDialog.ui" line="262"/>
<source>BIOS Directory:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="276"/>
<location filename="../SetupWizardDialog.ui" line="275"/>
<source>Browse...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="283"/>
<location filename="../SetupWizardDialog.ui" line="282"/>
<source>Reset</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="302"/>
<location filename="../SetupWizardDialog.ui" line="301"/>
<source>Filename</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="307"/>
<location filename="../SetupWizardDialog.ui" line="306"/>
<source>Version</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="330"/>
<location filename="../SetupWizardDialog.ui" line="329"/>
<source>Open BIOS Folder...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="337"/>
<location filename="../SetupWizardDialog.ui" line="336"/>
<source>Refresh List</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="362"/>
<location filename="../SetupWizardDialog.ui" line="361"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;PCSX2 will automatically scan and identify games from the selected directories below, and populate the game list.&lt;br&gt;These games should be dumped from discs you own. Guides for dumping discs can be found &lt;a href=&quot;https://pcsx2.net/docs/setup/dumping&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Supported formats for dumps include:&lt;/p&gt;&lt;p&gt;&lt;ul&gt;&lt;li&gt;.bin/.iso (ISO Disc Images)&lt;/li&gt;&lt;li&gt;.mdf (Media Descriptor File)&lt;/li&gt;&lt;li&gt;.chd (Compressed Hunks of Data)&lt;/li&gt;&lt;li&gt;.cso (Compressed ISO)&lt;/li&gt;&lt;li&gt;.zso (Compressed ISO)&lt;/li&gt;&lt;li&gt;.gz (Gzip Compressed ISO)&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="383"/>
<location filename="../SetupWizardDialog.ui" line="382"/>
<source>Search Directories (will be scanned for games)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="412"/>
<location filename="../SetupWizardDialog.ui" line="411"/>
<source>Add...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="431"/>
<location filename="../SetupWizardDialog.cpp" line="272"/>
<location filename="../SetupWizardDialog.ui" line="430"/>
<location filename="../SetupWizardDialog.cpp" line="278"/>
<source>Remove</source>
<extracomment>Part of the right-click menu for game directory entries</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="447"/>
<location filename="../SetupWizardDialog.ui" line="446"/>
<source>Search Directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="452"/>
<location filename="../SetupWizardDialog.ui" line="451"/>
<source>Scan Recursively</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="722"/>
<location filename="../SetupWizardDialog.ui" line="727"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;justify&quot;&gt;PCSX2 supports RetroAchievements, a service that adds achievements, leaderboards, and extra challenges to classic games.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;Sign in below to link your account and start earning achievements in supported titles, or create a free account at &lt;a href=&quot;https://retroachievements.org/createaccount.php&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#27bf73;&quot;&gt;retroachievements.org/createaccount.php&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;Using RetroAchievements is completely optional and not required to use PCSX2.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="747"/>
<location filename="../SetupWizardDialog.ui" line="755"/>
<source>RAIntegration is being used, built-in RetroAchievements support is disabled.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="781"/>
<location filename="../SetupWizardDialog.ui" line="789"/>
<source>Enable Achievements</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="788"/>
<location filename="../SetupWizardDialog.ui" line="796"/>
<source>Enable Hardcore Mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="811"/>
<location filename="../SetupWizardDialog.ui" line="819"/>
<source>Account</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="817"/>
<location filename="../SetupWizardDialog.cpp" line="455"/>
<location filename="../SetupWizardDialog.ui" line="825"/>
<location filename="../SetupWizardDialog.cpp" line="477"/>
<source>Not Logged In.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="846"/>
<location filename="../SetupWizardDialog.ui" line="854"/>
<source>View Profile...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="853"/>
<location filename="../SetupWizardDialog.cpp" line="456"/>
<location filename="../SetupWizardDialog.ui" line="861"/>
<location filename="../SetupWizardDialog.cpp" line="478"/>
<source>Login...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="863"/>
<location filename="../SetupWizardDialog.ui" line="871"/>
<source>You can customize additional RetroAchievements options later in Settings &gt; Achievements.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="905"/>
<location filename="../SetupWizardDialog.ui" line="913"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;h1 style=&quot; margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:xx-large; font-weight:700;&quot;&gt;Setup Complete!&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;You are now ready to run games.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Warning: Do not run untrusted programs in PCSX2. It is not a sandbox and cannot protect your computer from malicious software.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Further options are available under the settings menu. You can also use the Big Picture UI for navigation entirely with a gamepad.&lt;/p&gt;&lt;p&gt;We hope you enjoy using PCSX2.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="150"/>
<location filename="../SetupWizardDialog.ui" line="149"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;h1 style=&quot; margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:xx-large; font-weight:700;&quot;&gt;Welcome to PCSX2!&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;This wizard will help guide you through the configuration steps required to use the application. It is recommended if this is your first time installing PCSX2 that you view the setup guide &lt;a href=&quot;https://pcsx2.net/docs/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;By default, PCSX2 will connect to the server at &lt;a href=&quot;https://pcsx2.net/&quot;&gt;pcsx2.net&lt;/a&gt; to check for updates, and if available and confirmed, download update packages from &lt;a href=&quot;https://github.com/&quot;&gt;github.com&lt;/a&gt;. If you do not wish for PCSX2 to make any network connections on startup, you should uncheck the Automatic Updates option now. The Automatic Update setting can be changed later at any time in Interface Settings.&lt;/p&gt;&lt;p&gt;Please choose a language and theme to begin.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="479"/>
<location filename="../SetupWizardDialog.ui" line="478"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, PCSX2 will map your keyboard to the virtual PS2 controller.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:704;&quot;&gt;To use an external controller, you must map it first. &lt;/span&gt;On this screen, you can automatically map any controller which is currently connected. If your controller is not currently connected, you can plug it in now.&lt;/p&gt;&lt;p&gt;To change controller bindings in more detail, or use multi-tap, open the Settings menu and choose Controllers once you have completed the Setup Wizard.&lt;/p&gt;&lt;p&gt;Guides for configuring controllers can be found &lt;a href=&quot;https://pcsx2.net/docs/post/controllers/&quot;&gt;&lt;span style=&quot;&quot;&gt;here&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="492"/>
<location filename="../SetupWizardDialog.ui" line="491"/>
<source>Controller Port 1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="501"/>
<location filename="../SetupWizardDialog.ui" line="578"/>
<location filename="../SetupWizardDialog.ui" line="500"/>
<location filename="../SetupWizardDialog.ui" line="580"/>
<source>Controller Mapped To:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="508"/>
<location filename="../SetupWizardDialog.ui" line="565"/>
<location filename="../SetupWizardDialog.ui" line="507"/>
<location filename="../SetupWizardDialog.ui" line="567"/>
<source>Controller Type:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="520"/>
<location filename="../SetupWizardDialog.ui" line="587"/>
<location filename="../SetupWizardDialog.cpp" line="404"/>
<location filename="../SetupWizardDialog.ui" line="519"/>
<location filename="../SetupWizardDialog.ui" line="589"/>
<location filename="../SetupWizardDialog.cpp" line="410"/>
<source>Default (Keyboard)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="540"/>
<location filename="../SetupWizardDialog.ui" line="607"/>
<location filename="../SetupWizardDialog.ui" line="542"/>
<location filename="../SetupWizardDialog.ui" line="612"/>
<source>Automatic Mapping</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="559"/>
<location filename="../SetupWizardDialog.ui" line="561"/>
<source>Controller Port 2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="940"/>
<location filename="../SetupWizardDialog.ui" line="926"/>
<source>Create Desktop shortcut</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="933"/>
<source>Add PCSX2 to the Application Menu</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="962"/>
<source>&amp;Back</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="947"/>
<location filename="../SetupWizardDialog.cpp" line="140"/>
<location filename="../SetupWizardDialog.ui" line="969"/>
<location filename="../SetupWizardDialog.cpp" line="145"/>
<source>&amp;Next</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.ui" line="957"/>
<location filename="../SetupWizardDialog.ui" line="979"/>
<source>&amp;Cancel</source>
<translation type="unfinished"></translation>
</message>
@@ -23474,73 +23484,78 @@ Are you sure you want to continue?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="140"/>
<location filename="../SetupWizardDialog.cpp" line="145"/>
<source>&amp;Finish</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="146"/>
<location filename="../SetupWizardDialog.cpp" line="151"/>
<source>Cancel Setup</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="147"/>
<location filename="../SetupWizardDialog.cpp" line="152"/>
<source>Are you sure you want to cancel PCSX2 setup?
Any changes have been saved, and the wizard will run again next time you start PCSX2.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="275"/>
<location filename="../SetupWizardDialog.cpp" line="281"/>
<source>Open Directory...</source>
<extracomment>Part of the right-click menu for game directory entries</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="287"/>
<location filename="../SetupWizardDialog.cpp" line="293"/>
<source>Select Search Directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="292"/>
<location filename="../SetupWizardDialog.cpp" line="298"/>
<source>Scan Recursively?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="293"/>
<location filename="../SetupWizardDialog.cpp" line="299"/>
<source>Would you like to scan the directory &quot;%1&quot; recursively?
Scanning recursively takes more time, but will identify files in subdirectories.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="404"/>
<location filename="../SetupWizardDialog.cpp" line="410"/>
<source>Default (None)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="448"/>
<source>Add PCSX2 to the Start Menu</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="470"/>
<source>Username: %1
Login token generated on %2.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="451"/>
<location filename="../SetupWizardDialog.cpp" line="473"/>
<source>Logout</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="520"/>
<location filename="../SetupWizardDialog.cpp" line="542"/>
<source>No devices available</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="533"/>
<location filename="../SetupWizardDialog.cpp" line="555"/>
<source>Automatic Binding</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SetupWizardDialog.cpp" line="534"/>
<location filename="../SetupWizardDialog.cpp" line="556"/>
<source>No generic bindings were generated for device &apos;%1&apos;. The controller/source may not support automatic mapping.</source>
<translation type="unfinished"></translation>
</message>
@@ -23695,192 +23710,65 @@ Login token generated on %2.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="29"/>
<location filename="../ShortcutCreationDialog.cpp" line="20"/>
<source>Create Shortcut For %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="33"/>
<location filename="../ShortcutCreationDialog.cpp" line="24"/>
<source>Start Menu</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="35"/>
<location filename="../ShortcutCreationDialog.cpp" line="26"/>
<source>Application Launcher</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="45"/>
<location filename="../ShortcutCreationDialog.cpp" line="36"/>
<source>Select ELF File</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="45"/>
<location filename="../ShortcutCreationDialog.cpp" line="36"/>
<source>ELF Files (*.elf);;All Files (*.*)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="51"/>
<location filename="../ShortcutCreationDialog.cpp" line="42"/>
<source>Select Save State File</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="51"/>
<location filename="../ShortcutCreationDialog.cpp" line="42"/>
<source>Save States (*.p2s);;All Files (*.*)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="77"/>
<location filename="../ShortcutCreationDialog.cpp" line="68"/>
<source>Icon Files (*.ico);;All Files (*.*)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="79"/>
<location filename="../ShortcutCreationDialog.cpp" line="70"/>
<source>Image Files (*.png *.jpg *.svg *.webp);;All Files (*.*)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="81"/>
<location filename="../ShortcutCreationDialog.cpp" line="72"/>
<source>Select Icon</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="87"/>
<location filename="../ShortcutCreationDialog.cpp" line="78"/>
<source>Invalid Icon</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="87"/>
<location filename="../ShortcutCreationDialog.cpp" line="78"/>
<source>The selected file could not be loaded as an icon.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="185"/>
<location filename="../ShortcutCreationDialog.cpp" line="205"/>
<location filename="../ShortcutCreationDialog.cpp" line="214"/>
<location filename="../ShortcutCreationDialog.cpp" line="221"/>
<location filename="../ShortcutCreationDialog.cpp" line="232"/>
<location filename="../ShortcutCreationDialog.cpp" line="254"/>
<location filename="../ShortcutCreationDialog.cpp" line="262"/>
<location filename="../ShortcutCreationDialog.cpp" line="311"/>
<location filename="../ShortcutCreationDialog.cpp" line="349"/>
<location filename="../ShortcutCreationDialog.cpp" line="360"/>
<location filename="../ShortcutCreationDialog.cpp" line="368"/>
<location filename="../ShortcutCreationDialog.cpp" line="396"/>
<location filename="../ShortcutCreationDialog.cpp" line="405"/>
<location filename="../ShortcutCreationDialog.cpp" line="441"/>
<location filename="../ShortcutCreationDialog.cpp" line="483"/>
<source>Failed to create shortcut</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="185"/>
<location filename="../ShortcutCreationDialog.cpp" line="360"/>
<source>Filename contains illegal character.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="205"/>
<source>Could not create start menu directory.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="214"/>
<source>&apos;Desktop&apos; directory not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="214"/>
<source>User&apos;s &apos;Start Menu\Programs&apos; directory not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="221"/>
<source>A shortcut with the same name already exists.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="232"/>
<location filename="../ShortcutCreationDialog.cpp" line="441"/>
<source>File path contains invalid character(s).</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="254"/>
<source>CoInitialize failed (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="270"/>
<source>CoCreateInstance failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="279"/>
<source>SetPath failed (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="288"/>
<source>SetWorkingDirectory failed (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="299"/>
<source>SetArguments failed (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="311"/>
<location filename="../ShortcutCreationDialog.cpp" line="405"/>
<source>The selected icon file does not exist.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="324"/>
<source>SetIconLocation failed (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="332"/>
<source>QueryInterface failed (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="341"/>
<source>Failed to save the shortcut (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="349"/>
<source>Cannot create a shortcut without a title.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="368"/>
<source>Executable path is empty.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="396"/>
<source>Path to the Home directory is empty.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="473"/>
<source>Desktop Shortcut Files (*.desktop)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="475"/>
<source>Select Shortcut Save Destination</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ShortcutCreationDialog.cpp" line="483"/>
<source>Failed to create .desktop file</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>StackModel</name>
+25 -20
View File
@@ -5331,31 +5331,36 @@ bool GSRendererHW::VerifyIndices()
return true;
}
// Fix the colors in vertices in case the API only supports "provoking first vertex"
// (i.e., when using flat shading the color comes from the first vertex, unlike PS2
// which is "provoking last vertex").
void GSRendererHW::HandleProvokingVertexFirst()
void GSRendererHW::HandleFlatShadedVertices()
{
// Early exit conditions:
if (g_gs_device->Features().provoking_vertex_last || // device supports provoking last vertex
m_conf.vs.iip || // we are doing Gouraud shading
m_vt.m_primclass == GS_POINT_CLASS || // drawing points (one vertex per primitive; color is unambiguous)
m_vt.m_primclass == GS_SPRITE_CLASS) // drawing sprites (handled by the sprites -> triangles expand shader)
// These cases might need fixing.
const bool maybe_fix_vertices = !m_conf.vs.iip &&
(!g_gs_device->Features().provoking_vertex_last || IsCoverageAlphaSupported());
// These cases definitely don't need fixing.
const bool dont_fix_vertices = m_vt.m_primclass == GS_POINT_CLASS || m_vt.m_primclass == GS_SPRITE_CLASS;
if (!maybe_fix_vertices || dont_fix_vertices)
return;
const int n = GSUtil::GetClassVertexCount(m_vt.m_primclass);
// If all first/last vertices have the same color there is nothing to do.
bool first_eq_last = true;
// If all vertices of each prim have the same color there is nothing to do.
bool prims_flat = true;
for (u32 i = 0; i < m_index->tail; i += n)
{
if (m_vertex->buff[m_index->buff[i]].RGBAQ.U32[0] != m_vertex->buff[m_index->buff[i + n - 1]].RGBAQ.U32[0])
for (u32 j = 0; j < n - 1; j++)
{
first_eq_last = false;
break;
if (m_vertex->buff[m_index->buff[i + j]].RGBAQ.U32[0] != m_vertex->buff[m_index->buff[i + n - 1]].RGBAQ.U32[0])
{
prims_flat = false;
break;
}
}
if (!prims_flat)
break;
}
if (first_eq_last)
if (prims_flat)
return;
// De-index the vertices using the copy buffer
@@ -5369,11 +5374,11 @@ void GSRendererHW::HandleProvokingVertexFirst()
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
// Put correct color in the first vertex
// Make all vertices the same color to simplify handling in expand shaders.
for (u32 i = 0; i < m_index->tail; i += n)
{
m_vertex->buff[i].RGBAQ.U32[0] = m_vertex->buff[i + n - 1].RGBAQ.U32[0];
m_vertex->buff[i + n - 1].RGBAQ.U32[0] = 0xff; // Make last vertex red for debugging if used improperly
for (u32 j = 0; j < n - 1; j++)
m_vertex->buff[i + j].RGBAQ.U32[0] = m_vertex->buff[i + n - 1].RGBAQ.U32[0];
}
}
@@ -9450,7 +9455,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
DetermineROVUsage(rt, ds);
ConvertTextureTypeROV(rt, ds);
// Barriers must be determined before indices are modified via HandleProvokingVertexFirst/SetupIA.
// Barriers must be determined before indices are modified via HandleFlatShadedVertices/SetupIA.
// This also computes the drawlist if needed.
DetermineBarriers(rt, tex);
@@ -9475,7 +9480,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.scissor = (date_options.enabled && !date_options.barrier) ? m_conf.drawarea : scissor;
HandleProvokingVertexFirst();
HandleFlatShadedVertices();
SetupIA(rtscale, vs_scale_x, vs_scale_y, m_channel_shuffle_width != 0, no_rt);
+1 -1
View File
@@ -215,7 +215,7 @@ private:
void DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm);
void ResetStates();
void HandleProvokingVertexFirst();
void HandleFlatShadedVertices();
void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup, const bool no_rt);
void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex);
u32 EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr);
-6
View File
@@ -555,12 +555,6 @@ vertex MainVSOut vs_main_expand(
out.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
out.interior = 0;
if (NOT_IIP)
{
// Get the provoking vertex color (first vertex in Metal)
out.fc = i0 == 0 ? out.fc : (i1 == 0 ? other.fc : opposite.fc);
}
}
return out;
+1 -1
View File
@@ -3,4 +3,4 @@
/// Version number for GS and other shaders. Increment whenever any of the contents of the
/// shaders change, to invalidate the cache.
static constexpr u32 SHADER_CACHE_VERSION = 106; // Last changed in PR 14643
static constexpr u32 SHADER_CACHE_VERSION = 107; // Last changed in PR 14634