diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake
index e65827290d..3e9bc26d69 100644
--- a/CMakeModules/DownloadExternals.cmake
+++ b/CMakeModules/DownloadExternals.cmake
@@ -133,7 +133,7 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
else()
set(prefix "${base_path}/${target}/${arch_path}")
- set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} -m qtbase)
+ set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} -m qt_base)
if (YUZU_USE_QT_MULTIMEDIA)
set(install_args ${install_args} qtmultimedia)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt
index ede2cfafa4..cd3e9a4474 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt
@@ -34,6 +34,7 @@ import org.yuzu.yuzu_emu.databinding.ItemBanListBinding
import org.yuzu.yuzu_emu.databinding.ItemButtonNetplayBinding
import org.yuzu.yuzu_emu.databinding.ItemTextNetplayBinding
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.network.NetPlayManager
import org.yuzu.yuzu_emu.utils.CompatUtils
import org.yuzu.yuzu_emu.utils.GameHelper
@@ -102,8 +103,16 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
dismiss()
}
btnLobbyBrowser.setOnClickListener {
- LobbyBrowser(context).show()
- dismiss()
+ if (!NetDataValidators.username()) {
+ Toast.makeText(
+ context,
+ R.string.multiplayer_nickname_invalid,
+ Toast.LENGTH_LONG
+ ).show()
+ } else {
+ LobbyBrowser(context).show()
+ dismiss()
+ }
}
}
}
@@ -368,7 +377,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
)
) {
override fun validate(s: String): Boolean {
- return s.length in 3..20
+ return NetDataValidators.roomName(s)
}
}
@@ -378,7 +387,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
context.getString(R.string.multiplayer_required)
) {
override fun validate(s: String): Boolean {
- return s.isNotEmpty()
+ return NetDataValidators.notEmpty(s)
}
}
@@ -388,12 +397,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
context.getString(R.string.multiplayer_token_required)
) {
override fun validate(s: String): Boolean {
- if (s != context.getString(R.string.multiplayer_public_visibility)) {
- return true;
- }
-
- val token = StringSetting.WEB_TOKEN.getString()
- return token.matches(Regex("[a-z]{48}"))
+ return NetDataValidators.roomVisibility(s, context)
}
}
@@ -403,12 +407,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
context.getString(R.string.multiplayer_ip_error)
) {
override fun validate(s: String): Boolean {
- return try {
- InetAddress.getByName(s)
- s.length >= 7
- } catch (_: Exception) {
- false
- }
+ return NetDataValidators.ipAddress(s)
}
}
@@ -418,7 +417,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
context.getString(R.string.multiplayer_username_error)
) {
override fun validate(s: String): Boolean {
- return s.length in 4..20
+ return NetDataValidators.username(s)
}
}
@@ -428,7 +427,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
context.getString(R.string.multiplayer_port_error)
) {
override fun validate(s: String): Boolean {
- return s.toIntOrNull() in 1..65535
+ return NetDataValidators.port(s)
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index ab35a9180c..a269cab254 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -20,6 +20,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
@@ -300,9 +301,7 @@ abstract class SettingsItem(
val chars = "abcdefghijklmnopqrstuvwxyz"
(1..48).map { chars.random() }.joinToString("")
},
- validator = { s ->
- s?.matches(Regex("[a-z]{48}")) == true
- },
+ validator = NetDataValidators::token,
errorId = R.string.multiplayer_token_error
)
)
@@ -312,9 +311,7 @@ abstract class SettingsItem(
StringSetting.WEB_USERNAME,
titleId = R.string.web_username,
descriptionId = R.string.web_username_description,
- validator = { s ->
- s?.length in 4..20
- },
+ validator = NetDataValidators::username,
errorId = R.string.multiplayer_username_error
)
)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
index e50ebe50f4..aa17d05e34 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
@@ -154,8 +154,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
stringInputBinding.generate.setOnClickListener {
stringInputBinding.editText.setText(onGenerate())
}
- } else {
- stringInputBinding.generate.isVisible = false
}
val validator = item.validator
@@ -179,8 +177,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
}
override fun afterTextChanged(s: Editable?) {
- stringInputBinding.editText.error =
- if (validator(s.toString())) null else requireContext().getString(item.errorId)
+ val isValid = validator(s.toString())
+ stringInputBinding.editTextLayout.isErrorEnabled = !isValid
+ stringInputBinding.editTextLayout.error = if (isValid) null else requireContext().getString(item.errorId)
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/network/NetDataValidators.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/network/NetDataValidators.kt
new file mode 100644
index 0000000000..b3edf35d8e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/network/NetDataValidators.kt
@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.network
+
+import android.content.Context
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import java.net.InetAddress
+
+object NetDataValidators {
+ fun roomName(s: String): Boolean {
+ return s.length in 3..20
+ }
+
+ fun notEmpty(s: String): Boolean {
+ return s.isNotEmpty()
+ }
+
+ fun token(s: String?): Boolean {
+ return s?.matches(Regex("[a-z]{48}")) == true
+ }
+
+ fun token(): Boolean {
+ return token(StringSetting.WEB_TOKEN.getString())
+ }
+
+ fun roomVisibility(s: String, context: Context): Boolean {
+ if (s != context.getString(R.string.multiplayer_public_visibility)) {
+ return true;
+ }
+
+ return token()
+ }
+
+ fun ipAddress(s: String): Boolean {
+ return try {
+ InetAddress.getByName(s)
+ s.length >= 7
+ } catch (_: Exception) {
+ false
+ }
+ }
+
+ fun username(s: String?): Boolean {
+ return s?.matches(Regex("^[ a-zA-Z0-9._-]{4,20}$")) == true
+ }
+
+ fun username(): Boolean {
+ return username(StringSetting.WEB_USERNAME.getString())
+ }
+
+ fun port(s: String): Boolean {
+ return s.toIntOrNull() in 1..65535
+ }
+}
\ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/dialog_edit_text.xml b/src/android/app/src/main/res/layout/dialog_edit_text.xml
index 3612f46eae..bc7864a395 100644
--- a/src/android/app/src/main/res/layout/dialog_edit_text.xml
+++ b/src/android/app/src/main/res/layout/dialog_edit_text.xml
@@ -27,6 +27,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@string/generate"
+ android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/edit_text_layout"
app:layout_constraintTop_toBottomOf="@+id/edit_text_layout" />
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index e611e66c1f..3814105ea0 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -148,18 +148,18 @@
Failed to join room
Name is too short
Invalid address
- Invalid port!
+ Invalid port
Exit Room
Network error
Lost connection
- Name collision
+ Username already taken
MAC Address collision
Console ID collision
Wrong version
Wrong password
Could not connect
Room is full
- Host banned
+ You are banned from this room
Permission denied
No such user
Already in room
@@ -221,7 +221,8 @@
Required
Web Token required, go to Advanced Settings -> System -> Network
Invalid IP format
- Must be between 4–20 characters
+ Must be between 4–20 characters, and contain alphanumeric characters, periods, dashes, underscores, and spaces only
+ Username invalid, ensure it is set properly in System -> Network
Must be 48 characters, and lowercase a-z only
Must be between 1 and 65535
Cancel
@@ -467,7 +468,7 @@
Web Token
Web token used for creating public lobbies. It is a 48-character string containing only lowercase a-z.
Web Username
- Username to be shown in multiplayer lobbies. It must be 4–20 characters.
+ Username to be shown in multiplayer lobbies. It must be 4–20 characters, containing only alphanumeric characters, dashes, periods, underscores, and spaces.
Network
diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.cpp b/src/video_core/host1x/ffmpeg/ffmpeg.cpp
index d6eff2bdd7..3170f53884 100644
--- a/src/video_core/host1x/ffmpeg/ffmpeg.cpp
+++ b/src/video_core/host1x/ffmpeg/ffmpeg.cpp
@@ -23,32 +23,48 @@ namespace FFmpeg {
namespace {
-constexpr AVPixelFormat PreferredGpuFormat = AV_PIX_FMT_NV12;
-constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P;
constexpr std::array PreferredGpuDecoders = {
-#ifdef _WIN32
+#if defined (_WIN32)
AV_HWDEVICE_TYPE_CUDA,
AV_HWDEVICE_TYPE_D3D11VA,
AV_HWDEVICE_TYPE_DXVA2,
#elif defined(__FreeBSD__)
AV_HWDEVICE_TYPE_VDPAU,
#elif defined(__unix__)
+ AV_HWDEVICE_TYPE_CUDA,
AV_HWDEVICE_TYPE_VAAPI,
+ AV_HWDEVICE_TYPE_VDPAU,
#endif
AV_HWDEVICE_TYPE_VULKAN,
};
AVPixelFormat GetGpuFormat(AVCodecContext* codec_context, const AVPixelFormat* pix_fmts) {
- for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
- if (*p == codec_context->pix_fmt) {
- return codec_context->pix_fmt;
- }
- }
+ // Check if there is a pixel format supported by the GPU decoder.
+ const auto desc = av_pix_fmt_desc_get(codec_context->pix_fmt);
+ if (desc && desc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
+ for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
+ if (*p == codec_context->pix_fmt) {
+ return codec_context->pix_fmt;
+ }
+ }
+ }
- LOG_INFO(HW_GPU, "Could not find compatible GPU AV format, falling back to CPU");
+ // Another check to confirm if there is a pixel format supported by specific GPU decoders.
+ for (int i = 0;; i++) {
+ const AVCodecHWConfig* config = avcodec_get_hw_config(codec_context->codec, i);
+ if (!config) {
+ break;
+ }
+
+ if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) && (config->device_type == AV_HWDEVICE_TYPE_CUDA || config->device_type == AV_HWDEVICE_TYPE_VAAPI)) {
+ return config->pix_fmt;
+ }
+ }
+
+ // Fallback to CPU decoder.
+ LOG_INFO(HW_GPU, "Could not find compatible GPU pixel format, falling back to CPU");
av_buffer_unref(&codec_context->hw_device_ctx);
- codec_context->pix_fmt = PreferredCpuFormat;
return codec_context->pix_fmt;
}
@@ -58,7 +74,7 @@ std::string AVError(int errnum) {
return errbuf;
}
-} // namespace
+}
Packet::Packet(std::span data) {
m_packet = av_packet_alloc();
@@ -81,16 +97,16 @@ Frame::~Frame() {
Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
const AVCodecID av_codec = [&] {
switch (codec) {
- case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
- return AV_CODEC_ID_H264;
- case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
- return AV_CODEC_ID_VP8;
- case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
- return AV_CODEC_ID_VP9;
- default:
- UNIMPLEMENTED_MSG("Unknown codec {}", codec);
- return AV_CODEC_ID_NONE;
- }
+ case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
+ return AV_CODEC_ID_H264;
+ case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
+ return AV_CODEC_ID_VP8;
+ case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
+ return AV_CODEC_ID_VP9;
+ default:
+ UNIMPLEMENTED_MSG("Unknown codec {}", codec);
+ return AV_CODEC_ID_NONE;
+ }
}();
m_codec = avcodec_find_decoder(av_codec);
@@ -103,6 +119,7 @@ bool Decoder::SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceTyp
LOG_DEBUG(HW_GPU, "{} decoder does not support device type {}", m_codec->name, av_hwdevice_get_type_name(type));
break;
}
+
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == type) {
LOG_INFO(HW_GPU, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
*out_pix_fmt = config->pix_fmt;
@@ -215,11 +232,11 @@ bool DecoderContext::OpenContext(const Decoder& decoder) {
}
bool DecoderContext::SendPacket(const Packet& packet) {
- m_temp_frame = std::make_shared();
if (const int ret = avcodec_send_packet(m_codec_context, packet.GetPacket()); ret < 0 && ret != AVERROR_EOF) {
LOG_ERROR(HW_GPU, "avcodec_send_packet error: {}", AVError(ret));
return false;
}
+
return true;
}
@@ -237,14 +254,15 @@ std::shared_ptr DecoderContext::ReceiveFrame() {
return {};
}
- const auto desc = av_pix_fmt_desc_get(intermediate_frame->GetPixelFormat());
- if (m_codec_context->hw_device_ctx && (desc && desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
- m_temp_frame->SetFormat(PreferredGpuFormat);
+ m_temp_frame = std::make_shared();
+ if (m_codec_context->hw_device_ctx) {
+ m_temp_frame->SetFormat(AV_PIX_FMT_NV12);
if (int ret = av_hwframe_transfer_data(m_temp_frame->GetFrame(), intermediate_frame->GetFrame(), 0); ret < 0) {
LOG_ERROR(HW_GPU, "av_hwframe_transfer_data error: {}", AVError(ret));
return {};
}
} else {
+ m_temp_frame->SetFormat(AV_PIX_FMT_YUV420P);
m_temp_frame = std::move(intermediate_frame);
}
@@ -287,4 +305,4 @@ std::shared_ptr DecodeApi::ReceiveFrame() {
return m_decoder_context->ReceiveFrame();
}
-} // namespace FFmpeg
+}
diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.h b/src/video_core/host1x/ffmpeg/ffmpeg.h
index 6200fa691d..0864fd0cbe 100644
--- a/src/video_core/host1x/ffmpeg/ffmpeg.h
+++ b/src/video_core/host1x/ffmpeg/ffmpeg.h
@@ -26,7 +26,6 @@ extern "C" {
#include
#include
-// Works quite fine, and omits the hacky ffmpeg building for now...
#if defined(__FreeBSD__)
#include
#else