Browse Source

[frontend, web] refactor: web service frontend rewrite (#221)

- Automatic verification based on regex
- Token generation button
- Removed unneeded links
- public lobby creation [android]

Signed-off-by: crueter <swurl@swurl.xyz>
Co-authored-by: Aleksandr Popovich <alekpopo@pm.me>
Co-authored-by: Aleksandr Popovich <alekpopo@proton.me>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/221
Co-authored-by: crueter <swurl@swurl.xyz>
Co-committed-by: crueter <swurl@swurl.xyz>
remotes/1775994239183119240/tmp_refs/heads/ui-gram
crueter 1 year ago
committed by crueter
parent
commit
94c66f98bf
No known key found for this signature in database GPG Key ID: BA8734FD0EE46976
  1. 11
      .ci/license-header.sh
  2. 6
      src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt
  3. 5
      src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt
  4. 91
      src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt
  5. 8
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
  6. 23
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
  7. 9
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringInputSetting.kt
  8. 47
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
  9. 26
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
  10. 21
      src/android/app/src/main/java/org/yuzu/yuzu_emu/network/NetPlayManager.kt
  11. 4
      src/android/app/src/main/jni/native.cpp
  12. 10
      src/android/app/src/main/res/layout/dialog_edit_text.xml
  13. 19
      src/android/app/src/main/res/layout/dialog_multiplayer_room.xml
  14. 1453
      src/android/app/src/main/res/values-sr/strings.xml
  15. 2
      src/android/app/src/main/res/values/arrays.xml
  16. 17
      src/android/app/src/main/res/values/strings.xml
  17. 160
      src/common/android/multiplayer/multiplayer.cpp
  18. 4
      src/common/android/multiplayer/multiplayer.h
  19. 5
      src/common/settings.cpp
  20. 6
      src/common/settings.h
  21. 2
      src/common/settings_enums.h
  22. 10
      src/dedicated_room/yuzu_room.cpp
  23. 30
      src/network/announce_multiplayer_session.cpp
  24. 2
      src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp
  25. 3
      src/video_core/renderer_vulkan/blit_image.cpp
  26. 3
      src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
  27. 162
      src/yuzu/configuration/configure_web.cpp
  28. 20
      src/yuzu/configuration/configure_web.h
  29. 88
      src/yuzu/configuration/configure_web.ui
  30. 1
      src/yuzu/configuration/shared_translation.cpp
  31. 4
      src/yuzu/main.cpp
  32. 10
      src/yuzu/multiplayer/direct_connect.cpp
  33. 16
      src/yuzu/multiplayer/host_room.cpp
  34. 18
      src/yuzu/multiplayer/lobby.cpp

11
.ci/license-header.sh

@ -4,13 +4,12 @@ HEADER="$(cat "$PWD/.ci/license/header.txt")"
echo "Getting branch changes" echo "Getting branch changes"
# I created this cursed POSIX abomination only to discover a better solution
#BRANCH=`git rev-parse --abbrev-ref HEAD`
#COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"`
#RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}"
#FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r`
BRANCH=`git rev-parse --abbrev-ref HEAD`
COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"`
RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}"
FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r`
FILES=$(git diff --name-only master)
#FILES=$(git diff --name-only master)
echo "Done" echo "Done"

6
src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.dialogs package org.yuzu.yuzu_emu.dialogs
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -19,6 +18,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogChatBinding import org.yuzu.yuzu_emu.databinding.DialogChatBinding
import org.yuzu.yuzu_emu.databinding.ItemChatMessageBinding import org.yuzu.yuzu_emu.databinding.ItemChatMessageBinding
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.network.NetPlayManager import org.yuzu.yuzu_emu.network.NetPlayManager
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -82,7 +82,7 @@ class ChatDialog(context: Context) : BottomSheetDialog(context) {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun sendMessage(message: String) { private fun sendMessage(message: String) {
val username = NetPlayManager.getUsername(context)
val username = StringSetting.WEB_USERNAME.getString()
NetPlayManager.netPlaySendMessage(message) NetPlayManager.netPlaySendMessage(message)
val chatMessage = ChatMessage( val chatMessage = ChatMessage(

5
src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.dialogs package org.yuzu.yuzu_emu.dialogs
@ -28,6 +28,7 @@ import info.debatty.java.stringsimilarity.JaroWinkler
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogLobbyBrowserBinding import org.yuzu.yuzu_emu.databinding.DialogLobbyBrowserBinding
import org.yuzu.yuzu_emu.databinding.ItemLobbyRoomBinding import org.yuzu.yuzu_emu.databinding.ItemLobbyRoomBinding
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.network.NetPlayManager import org.yuzu.yuzu_emu.network.NetPlayManager
import java.util.Locale import java.util.Locale
@ -144,7 +145,7 @@ class LobbyBrowser(context: Context) : BottomSheetDialog(context) {
} }
private fun joinRoom(room: NetPlayManager.RoomInfo, password: String) { private fun joinRoom(room: NetPlayManager.RoomInfo, password: String) {
val username = NetPlayManager.getUsername(context)
val username = StringSetting.WEB_USERNAME.getString()
Thread { Thread {
val result = NetPlayManager.netPlayJoinRoom(room.ip, room.port, username, password) val result = NetPlayManager.netPlayJoinRoom(room.ip, room.port, username, password)

91
src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/NetPlayDialog.kt

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.dialogs package org.yuzu.yuzu_emu.dialogs
@ -24,6 +24,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.DialogMultiplayerConnectBinding import org.yuzu.yuzu_emu.databinding.DialogMultiplayerConnectBinding
@ -32,6 +33,7 @@ import org.yuzu.yuzu_emu.databinding.DialogMultiplayerRoomBinding
import org.yuzu.yuzu_emu.databinding.ItemBanListBinding import org.yuzu.yuzu_emu.databinding.ItemBanListBinding
import org.yuzu.yuzu_emu.databinding.ItemButtonNetplayBinding import org.yuzu.yuzu_emu.databinding.ItemButtonNetplayBinding
import org.yuzu.yuzu_emu.databinding.ItemTextNetplayBinding import org.yuzu.yuzu_emu.databinding.ItemTextNetplayBinding
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.network.NetPlayManager import org.yuzu.yuzu_emu.network.NetPlayManager
import org.yuzu.yuzu_emu.utils.CompatUtils import org.yuzu.yuzu_emu.utils.CompatUtils
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
@ -180,9 +182,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
PopupMenu(view.context, view).apply { PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.menu_netplay_member, menu) menuInflater.inflate(R.menu.menu_netplay_member, menu)
menu.findItem(R.id.action_kick).isEnabled = isModerator && menu.findItem(R.id.action_kick).isEnabled = isModerator &&
netPlayItems.name != NetPlayManager.getUsername(context)
netPlayItems.name != StringSetting.WEB_USERNAME.getString()
menu.findItem(R.id.action_ban).isEnabled = isModerator && menu.findItem(R.id.action_ban).isEnabled = isModerator &&
netPlayItems.name != NetPlayManager.getUsername(context)
netPlayItems.name != StringSetting.WEB_USERNAME.getString()
setOnMenuItemClickListener { item -> setOnMenuItemClickListener { item ->
if (item.itemId == R.id.action_kick) { if (item.itemId == R.id.action_kick) {
NetPlayManager.netPlayKickUser(netPlayItems.name) NetPlayManager.netPlayKickUser(netPlayItems.name)
@ -297,13 +299,13 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
abstract class TextValidatorWatcher( abstract class TextValidatorWatcher(
private val btnConfirm: Button, private val btnConfirm: Button,
private val view: EditText,
private val layout: TextInputLayout,
private val errorMessage: String private val errorMessage: String
) : TextWatcher { ) : TextWatcher {
companion object { companion object {
val validStates: HashMap<EditText, Boolean> = hashMapOf()
val validStates: HashMap<TextInputLayout, Boolean> = hashMapOf()
} }
abstract fun validate(s: String): Boolean abstract fun validate(s: String): Boolean
override fun beforeTextChanged( override fun beforeTextChanged(
@ -325,20 +327,20 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
override fun afterTextChanged(s: Editable?) { override fun afterTextChanged(s: Editable?) {
val input = s.toString() val input = s.toString()
val isValid = validate(input) val isValid = validate(input)
view.error = if (isValid) null else errorMessage
layout.isErrorEnabled = !isValid
layout.error = if (isValid) null else errorMessage
validStates.put(view, isValid)
validStates[layout] = isValid
btnConfirm.isEnabled = !validStates.containsValue(false) btnConfirm.isEnabled = !validStates.containsValue(false)
} }
} }
// TODO(alekpop, crueter): Properly handle getting banned (both during and in future connects) // TODO(alekpop, crueter): Properly handle getting banned (both during and in future connects)
private fun showNetPlayInputDialog(isCreateRoom: Boolean) { private fun showNetPlayInputDialog(isCreateRoom: Boolean) {
TextValidatorWatcher.validStates.clear()
val activity = CompatUtils.findActivity(context) val activity = CompatUtils.findActivity(context)
val dialog = BottomSheetDialog(activity) val dialog = BottomSheetDialog(activity)
val validStates: HashMap<EditText, Boolean> = hashMapOf()
dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
dialog.behavior.skipCollapsed = dialog.behavior.skipCollapsed =
@ -347,6 +349,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
val binding = DialogMultiplayerRoomBinding.inflate(LayoutInflater.from(activity)) val binding = DialogMultiplayerRoomBinding.inflate(LayoutInflater.from(activity))
dialog.setContentView(binding.root) dialog.setContentView(binding.root)
val visibilityList: List<String> = listOf(
context.getString(R.string.multiplayer_public_visibility),
context.getString(R.string.multiplayer_unlisted_visibility),
)
binding.textTitle.text = activity.getString( binding.textTitle.text = activity.getString(
if (isCreateRoom) R.string.multiplayer_create_room if (isCreateRoom) R.string.multiplayer_create_room
else R.string.multiplayer_join_room else R.string.multiplayer_join_room
@ -355,7 +362,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
// setup listeners etc // setup listeners etc
val roomNameWatcher = object : TextValidatorWatcher( val roomNameWatcher = object : TextValidatorWatcher(
binding.btnConfirm, // TODO(alekpop, crueter): Figure out a better way to deal with this? binding.btnConfirm, // TODO(alekpop, crueter): Figure out a better way to deal with this?
binding.roomName,
binding.layoutRoomName,
context.getString( context.getString(
R.string.multiplayer_room_name_error R.string.multiplayer_room_name_error
) )
@ -367,25 +374,32 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
val preferredWatcher = object : TextValidatorWatcher( val preferredWatcher = object : TextValidatorWatcher(
binding.btnConfirm, binding.btnConfirm,
binding.dropdownPreferredGameName,
binding.preferredGameName,
context.getString(R.string.multiplayer_required) context.getString(R.string.multiplayer_required)
) { ) {
override fun validate(s: String): Boolean { override fun validate(s: String): Boolean {
return s.isNotEmpty() return s.isNotEmpty()
} }
}
override fun afterTextChanged(s: Editable?) {
super.afterTextChanged(s)
val visibilityWatcher = object : TextValidatorWatcher(
binding.btnConfirm,
binding.lobbyVisibility,
context.getString(R.string.multiplayer_token_required)
) {
override fun validate(s: String): Boolean {
if (s != context.getString(R.string.multiplayer_public_visibility)) {
return true;
}
// special case: remove dropdown arrow
val input = s.toString()
binding.preferredGameName.isEndIconVisible = validate(input)
val token = StringSetting.WEB_TOKEN.getString()
return token.matches(Regex("[a-z]{48}"))
} }
} }
val ipWatcher = object : TextValidatorWatcher( val ipWatcher = object : TextValidatorWatcher(
binding.btnConfirm, binding.btnConfirm,
binding.ipAddress,
binding.layoutIpAddress,
context.getString(R.string.multiplayer_ip_error) context.getString(R.string.multiplayer_ip_error)
) { ) {
override fun validate(s: String): Boolean { override fun validate(s: String): Boolean {
@ -400,17 +414,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
val usernameWatcher = object : TextValidatorWatcher( val usernameWatcher = object : TextValidatorWatcher(
binding.btnConfirm, binding.btnConfirm,
binding.username,
binding.layoutUsername,
context.getString(R.string.multiplayer_username_error) context.getString(R.string.multiplayer_username_error)
) { ) {
override fun validate(s: String): Boolean { override fun validate(s: String): Boolean {
return s.length >= 5
return s.length in 4..20
} }
} }
val portWatcher = object : TextValidatorWatcher( val portWatcher = object : TextValidatorWatcher(
binding.btnConfirm, binding.btnConfirm,
binding.ipPort,
binding.layoutIpPort,
context.getString(R.string.multiplayer_port_error) context.getString(R.string.multiplayer_port_error)
) { ) {
override fun validate(s: String): Boolean { override fun validate(s: String): Boolean {
@ -421,6 +435,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
if (isCreateRoom) { if (isCreateRoom) {
binding.roomName.addTextChangedListener(roomNameWatcher) binding.roomName.addTextChangedListener(roomNameWatcher)
binding.dropdownPreferredGameName.addTextChangedListener(preferredWatcher) binding.dropdownPreferredGameName.addTextChangedListener(preferredWatcher)
binding.dropdownLobbyVisibility.addTextChangedListener(visibilityWatcher)
binding.dropdownPreferredGameName.apply { binding.dropdownPreferredGameName.apply {
setAdapter( setAdapter(
@ -431,19 +446,35 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
) )
) )
} }
binding.dropdownLobbyVisibility.setText(context.getString(R.string.multiplayer_unlisted_visibility))
binding.dropdownLobbyVisibility.apply {
setAdapter(
ArrayAdapter(
activity,
R.layout.dropdown_item,
visibilityList
)
)
}
} }
binding.ipAddress.addTextChangedListener(ipWatcher) binding.ipAddress.addTextChangedListener(ipWatcher)
binding.ipPort.addTextChangedListener(portWatcher) binding.ipPort.addTextChangedListener(portWatcher)
binding.username.addTextChangedListener(usernameWatcher) binding.username.addTextChangedListener(usernameWatcher)
binding.ipAddress.setText(NetPlayManager.getRoomAddress(activity))
binding.ipPort.setText(NetPlayManager.getRoomPort(activity)) binding.ipPort.setText(NetPlayManager.getRoomPort(activity))
binding.username.setText(NetPlayManager.getUsername(activity))
binding.username.setText(StringSetting.WEB_USERNAME.getString())
// manually trigger text listeners // manually trigger text listeners
if (isCreateRoom) { if (isCreateRoom) {
roomNameWatcher.afterTextChanged(binding.roomName.text) roomNameWatcher.afterTextChanged(binding.roomName.text)
preferredWatcher.afterTextChanged(binding.dropdownPreferredGameName.text) preferredWatcher.afterTextChanged(binding.dropdownPreferredGameName.text)
// It's not needed here, the watcher is called by the initial set method
// visibilityWatcher.afterTextChanged(binding.dropdownLobbyVisibility.text)
} }
ipWatcher.afterTextChanged(binding.ipAddress.text) ipWatcher.afterTextChanged(binding.ipAddress.text)
@ -451,8 +482,10 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
usernameWatcher.afterTextChanged(binding.username.text) usernameWatcher.afterTextChanged(binding.username.text)
binding.preferredGameName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE binding.preferredGameName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.lobbyVisibility.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.roomName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE binding.roomName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.maxPlayersContainer.visibility = if (isCreateRoom) View.VISIBLE else View.GONE binding.maxPlayersContainer.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.maxPlayersLabel.text = context.getString( binding.maxPlayersLabel.text = context.getString(
R.string.multiplayer_max_players_value, R.string.multiplayer_max_players_value,
binding.maxPlayers.value.toInt() binding.maxPlayers.value.toInt()
@ -464,7 +497,6 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
} }
// TODO(alekpop, crueter): Room descriptions // TODO(alekpop, crueter): Room descriptions
// TODO(alekpop, crueter): Public room creation
// TODO(alekpop, crueter): Preview preferred games // TODO(alekpop, crueter): Preview preferred games
binding.btnConfirm.setOnClickListener { binding.btnConfirm.setOnClickListener {
binding.btnConfirm.isEnabled = false binding.btnConfirm.isEnabled = false
@ -487,6 +519,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
val preferredIdx = gameNameList.indexOfFirst { it[0] == preferredGameName } val preferredIdx = gameNameList.indexOfFirst { it[0] == preferredGameName }
val preferredGameId = if (preferredIdx == -1) 0 else gameIdList[preferredIdx][0] val preferredGameId = if (preferredIdx == -1) 0 else gameIdList[preferredIdx][0]
val visibility = binding.dropdownLobbyVisibility.text.toString()
val isPublic = visibility == context.getString(R.string.multiplayer_public_visibility)
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
val result = if (isCreateRoom) { val result = if (isCreateRoom) {
NetPlayManager.netPlayCreateRoom( NetPlayManager.netPlayCreateRoom(
@ -497,23 +532,26 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
preferredGameId, preferredGameId,
password, password,
roomName, roomName,
maxPlayers
maxPlayers,
isPublic
) )
} else { } else {
NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password) NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password)
} }
if (result == 0) { if (result == 0) {
// TODO(alekpop, crueter): These need to be moved as settings, editable in a tab
NetPlayManager.setUsername(activity, username)
StringSetting.WEB_USERNAME.setString(username)
NetPlayManager.setRoomPort(activity, portStr) NetPlayManager.setRoomPort(activity, portStr)
NetPlayManager.setRoomAddress(activity, ipAddress)
if (!isCreateRoom) NetPlayManager.setRoomAddress(activity, ipAddress) if (!isCreateRoom) NetPlayManager.setRoomAddress(activity, ipAddress)
Toast.makeText( Toast.makeText(
YuzuApplication.appContext, YuzuApplication.appContext,
if (isCreateRoom) R.string.multiplayer_create_room_success if (isCreateRoom) R.string.multiplayer_create_room_success
else R.string.multiplayer_join_room_success, else R.string.multiplayer_join_room_success,
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
dialog.dismiss() dialog.dismiss()
} else { } else {
Toast.makeText( Toast.makeText(
@ -521,6 +559,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
R.string.multiplayer_could_not_connect, R.string.multiplayer_could_not_connect,
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
binding.btnConfirm.isEnabled = true binding.btnConfirm.isEnabled = true
binding.btnConfirm.text = activity.getString(R.string.ok) binding.btnConfirm.text = activity.getString(R.string.ok)
} }

8
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -9,9 +12,8 @@ enum class StringSetting(override val key: String) : AbstractStringSetting {
DRIVER_PATH("driver_path"), DRIVER_PATH("driver_path"),
DEVICE_NAME("device_name"), DEVICE_NAME("device_name"),
// TODO(crueter, alekpop): Netplay/settings needs to be properly worked into settings
WEB_TOKEN("yuzu_token"),
WEB_USERNAME("yuzu_username"),
WEB_TOKEN("eden_token"),
WEB_USERNAME("eden_username"),
; ;
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal) override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)

23
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt

@ -284,11 +284,32 @@ abstract class SettingsItem(
descriptionId = R.string.use_custom_rtc_description descriptionId = R.string.use_custom_rtc_description
) )
) )
put( put(
StringInputSetting( StringInputSetting(
StringSetting.WEB_TOKEN, StringSetting.WEB_TOKEN,
titleId = R.string.web_token, titleId = R.string.web_token,
descriptionId = R.string.web_token_description
descriptionId = R.string.web_token_description,
onGenerate = {
val chars = "abcdefghijklmnopqrstuvwxyz"
(1..48).map { chars.random() }.joinToString("")
},
validator = { s ->
s?.matches(Regex("[a-z]{48}")) == true
},
errorId = R.string.multiplayer_token_error
)
)
put(
StringInputSetting(
StringSetting.WEB_USERNAME,
titleId = R.string.web_username,
descriptionId = R.string.web_username_description,
validator = { s ->
s?.length in 4..20
},
errorId = R.string.multiplayer_username_error
) )
) )
put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc)) put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))

9
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringInputSetting.kt

@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project // SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.model.view package org.yuzu.yuzu_emu.features.settings.model.view
import android.text.Editable
import androidx.annotation.StringRes import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
@ -11,7 +15,10 @@ class StringInputSetting(
@StringRes titleId: Int = 0, @StringRes titleId: Int = 0,
titleString: String = "", titleString: String = "",
@StringRes descriptionId: Int = 0, @StringRes descriptionId: Int = 0,
descriptionString: String = ""
descriptionString: String = "",
val onGenerate: (() -> String)? = null,
val validator: ((s: String?) -> Boolean)? = null,
@StringRes val errorId: Int = 0
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { ) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_STRING_INPUT override val type = TYPE_STRING_INPUT

47
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -6,9 +9,13 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -138,6 +145,46 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater) stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
val item = settingsViewModel.clickedItem as StringInputSetting val item = settingsViewModel.clickedItem as StringInputSetting
stringInputBinding.editText.setText(item.getSelectedValue()) stringInputBinding.editText.setText(item.getSelectedValue())
val onGenerate = item.onGenerate
stringInputBinding.generate.isVisible = onGenerate != null
if (onGenerate != null) {
stringInputBinding.generate.setOnClickListener {
stringInputBinding.editText.setText(onGenerate())
}
}
val validator = item.validator
if (validator != null) {
val watcher = object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int
) {
}
override fun afterTextChanged(s: Editable?) {
stringInputBinding.editText.error =
if (validator(s.toString())) null else requireContext().getString(item.errorId)
}
}
stringInputBinding.editText.addTextChangedListener(watcher)
watcher.afterTextChanged(stringInputBinding.editText.text)
}
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(item.title) .setTitle(item.title)
.setView(stringInputBinding.root) .setView(stringInputBinding.root)

26
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt

@ -212,30 +212,38 @@ class SettingsFragmentPresenter(
add(BooleanSetting.USE_CUSTOM_RTC.key) add(BooleanSetting.USE_CUSTOM_RTC.key)
add(LongSetting.CUSTOM_RTC.key) add(LongSetting.CUSTOM_RTC.key)
// TODO(alekpop): Add functionality
// add(HeaderSetting(R.string.network))
// add(StringSetting.WEB_TOKEN.key)
add(HeaderSetting(R.string.network))
add(StringSetting.WEB_TOKEN.key)
add(StringSetting.WEB_USERNAME.key)
} }
} }
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
sl.apply { sl.apply {
// TODO(crueter): reorganize this, this is awful // TODO(crueter): reorganize this, this is awful
add(HeaderSetting(R.string.backend))
add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.RENDERER_ACCURACY.key)
add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_RESOLUTION.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
add(HeaderSetting(R.string.processing))
add(IntSetting.RENDERER_VSYNC.key) add(IntSetting.RENDERER_VSYNC.key)
add(IntSetting.RENDERER_SCALING_FILTER.key)
add(IntSetting.FSR_SHARPENING_SLIDER.key)
add(IntSetting.RENDERER_ANTI_ALIASING.key) add(IntSetting.RENDERER_ANTI_ALIASING.key)
add(IntSetting.MAX_ANISOTROPY.key) add(IntSetting.MAX_ANISOTROPY.key)
add(IntSetting.RENDERER_SCALING_FILTER.key)
add(IntSetting.FSR_SHARPENING_SLIDER.key)
add(HeaderSetting(R.string.display))
add(IntSetting.RENDERER_SCREEN_LAYOUT.key) add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
add(IntSetting.RENDERER_ASPECT_RATIO.key) add(IntSetting.RENDERER_ASPECT_RATIO.key)
add(IntSetting.VERTICAL_ALIGNMENT.key) add(IntSetting.VERTICAL_ALIGNMENT.key)
add(BooleanSetting.PICTURE_IN_PICTURE.key) add(BooleanSetting.PICTURE_IN_PICTURE.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
} }
} }

21
src/android/app/src/main/java/org/yuzu/yuzu_emu/network/NetPlayManager.kt

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.network package org.yuzu.yuzu_emu.network
@ -16,6 +16,7 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.dialogs.ChatMessage import org.yuzu.yuzu_emu.dialogs.ChatMessage
import java.net.Inet4Address import java.net.Inet4Address
import androidx.core.content.edit
object NetPlayManager { object NetPlayManager {
external fun netPlayCreateRoom( external fun netPlayCreateRoom(
@ -26,7 +27,8 @@ object NetPlayManager {
preferredGameId: Long, preferredGameId: Long,
password: String, password: String,
roomName: String, roomName: String,
maxPlayers: Int
maxPlayers: Int,
isPublic: Boolean
): Int ): Int
external fun netPlayJoinRoom( external fun netPlayJoinRoom(
@ -125,17 +127,6 @@ object NetPlayManager {
adapterRefreshListener = listener adapterRefreshListener = listener
} }
fun getUsername(activity: Context): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
val name = "Eden${(Math.random() * 100).toInt()}"
return prefs.getString("NetPlayUsername", name) ?: name
}
fun setUsername(activity: Activity, name: String) {
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
prefs.edit().putString("NetPlayUsername", name).apply()
}
fun getRoomAddress(activity: Activity): String { fun getRoomAddress(activity: Activity): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(activity) val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
val address = getIpAddressByWifi(activity) val address = getIpAddressByWifi(activity)
@ -144,7 +135,7 @@ object NetPlayManager {
fun setRoomAddress(activity: Activity, address: String) { fun setRoomAddress(activity: Activity, address: String) {
val prefs = PreferenceManager.getDefaultSharedPreferences(activity) val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
prefs.edit().putString("NetPlayRoomAddress", address).apply()
prefs.edit { putString("NetPlayRoomAddress", address) }
} }
fun getRoomPort(activity: Activity): String { fun getRoomPort(activity: Activity): String {
@ -154,7 +145,7 @@ object NetPlayManager {
fun setRoomPort(activity: Activity, port: String) { fun setRoomPort(activity: Activity, port: String) {
val prefs = PreferenceManager.getDefaultSharedPreferences(activity) val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
prefs.edit().putString("NetPlayRoomPort", port).apply()
prefs.edit { putString("NetPlayRoomPort", port) }
} }
private val chatMessages = mutableListOf<ChatMessage>() private val chatMessages = mutableListOf<ChatMessage>()

4
src/android/app/src/main/jni/native.cpp

@ -975,12 +975,12 @@ Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetPublicRooms(
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayCreateRoom( JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayCreateRoom(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port, JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
jstring username, jstring preferredGameName, jlong preferredGameId, jstring password, jstring username, jstring preferredGameName, jlong preferredGameId, jstring password,
jstring room_name, jint max_players) {
jstring room_name, jint max_players, jboolean isPublic) {
return static_cast<jint>( return static_cast<jint>(
multiplayer->NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port, multiplayer->NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port,
Common::Android::GetJString(env, username), Common::Android::GetJString(env, preferredGameName), Common::Android::GetJString(env, username), Common::Android::GetJString(env, preferredGameName),
preferredGameId,Common::Android::GetJString(env, password), preferredGameId,Common::Android::GetJString(env, password),
Common::Android::GetJString(env, room_name), max_players));
Common::Android::GetJString(env, room_name), max_players, isPublic));
} }
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayJoinRoom( JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayJoinRoom(

10
src/android/app/src/main/res/layout/dialog_edit_text.xml

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -20,4 +21,13 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/generate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@string/generate"
app:layout_constraintEnd_toEndOf="@+id/edit_text_layout"
app:layout_constraintTop_toBottomOf="@+id/edit_text_layout" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

19
src/android/app/src/main/res/layout/dialog_multiplayer_room.xml

@ -21,6 +21,7 @@
android:textColor="?attr/colorOnSurface" /> android:textColor="?attr/colorOnSurface" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_ip_address"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/multiplayer_ip_address" android:hint="@string/multiplayer_ip_address"
@ -34,6 +35,7 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_ip_port"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/multiplayer_ip_port" android:hint="@string/multiplayer_ip_port"
@ -47,6 +49,7 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_username"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/multiplayer_username" android:hint="@string/multiplayer_username"
@ -89,6 +92,7 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_room_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/multiplayer_room_name" android:hint="@string/multiplayer_room_name"
@ -125,6 +129,21 @@
android:text="@string/multiplayer_max_players_value" /> android:text="@string/multiplayer_max_players_value" />
</LinearLayout> </LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/lobby_visibility"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/multiplayer_lobby_type"
android:padding="8dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/dropdown_lobby_visibility"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_confirm" android:id="@+id/btn_confirm"
android:layout_width="wrap_content" android:layout_width="wrap_content"

1453
src/android/app/src/main/res/values-sr/strings.xml
File diff suppressed because it is too large
View File

2
src/android/app/src/main/res/values/arrays.xml

@ -62,6 +62,7 @@
<item>@string/language_spanish</item> <item>@string/language_spanish</item>
<item>@string/language_taiwanese</item> <item>@string/language_taiwanese</item>
<item>@string/language_traditional_chinese</item> <item>@string/language_traditional_chinese</item>
<item>@string/language_serbian</item>
</string-array> </string-array>
<integer-array name="languageValues"> <integer-array name="languageValues">
@ -83,6 +84,7 @@
<item>5</item> <item>5</item>
<item>11</item> <item>11</item>
<item>16</item> <item>16</item>
<item>18</item>
</integer-array> </integer-array>
<string-array name="rendererApiNames"> <string-array name="rendererApiNames">

17
src/android/app/src/main/res/values/strings.xml

@ -204,18 +204,22 @@
<string name="emulation_multiplayer">Multiplayer</string> <string name="emulation_multiplayer">Multiplayer</string>
<string name="multiplayer_game_name">Preferred Games</string> <string name="multiplayer_game_name">Preferred Games</string>
<string name="multiplayer_preferred_game_name">Preferred Game</string> <string name="multiplayer_preferred_game_name">Preferred Game</string>
<string name="multiplayer_lobby_type">Lobby Type</string>
<string name="multiplayer_no_game">No Games Found</string> <string name="multiplayer_no_game">No Games Found</string>
<string name="multiplayer_preferred_game_name_invalid">You must choose a Preferred Game to host a room.</string> <string name="multiplayer_preferred_game_name_invalid">You must choose a Preferred Game to host a room.</string>
<string name="multiplayer_room_name_error">Must be between 3 and 20 characters</string> <string name="multiplayer_room_name_error">Must be between 3 and 20 characters</string>
<string name="multiplayer_required">Required</string> <string name="multiplayer_required">Required</string>
<string name="multiplayer_token_required">Web Token required, go to Advanced Settings -> System -> Network</string>
<string name="multiplayer_ip_error">Invalid IP format</string> <string name="multiplayer_ip_error">Invalid IP format</string>
<string name="multiplayer_username_error">Must be at least 5 characters</string>
<string name="multiplayer_username_error">Must be between 4–20 characters</string>
<string name="multiplayer_token_error">Must be 48 characters, and lowercase a-z only</string>
<string name="multiplayer_port_error">Must be between 1 and 65535</string> <string name="multiplayer_port_error">Must be between 1 and 65535</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="ok">Ok</string> <string name="ok">Ok</string>
<string name="refresh">Refresh</string> <string name="refresh">Refresh</string>
<string name="room_list">Room List</string> <string name="room_list">Room List</string>
<string name="multiplayer_public_visibility">Public</string>
<string name="multiplayer_unlisted_visibility">Unlisted</string>
<!-- Setup strings --> <!-- Setup strings -->
<string name="welcome">Welcome!</string> <string name="welcome">Welcome!</string>
@ -443,12 +447,20 @@
<string name="use_custom_rtc_description">Allows you to set a custom real-time clock separate from your current system time.</string> <string name="use_custom_rtc_description">Allows you to set a custom real-time clock separate from your current system time.</string>
<string name="set_custom_rtc">Set custom RTC</string> <string name="set_custom_rtc">Set custom RTC</string>
<string name="generate">Generate</string>
<!-- Network settings strings --> <!-- Network settings strings -->
<string name="web_token">Web Token</string> <string name="web_token">Web Token</string>
<string name="web_token_description">Web token used for creating public lobbies. It is a 48-character string containing only lowercase a-z.</string> <string name="web_token_description">Web token used for creating public lobbies. It is a 48-character string containing only lowercase a-z.</string>
<string name="web_username">Web Username</string>
<string name="web_username_description">Username to be shown in multiplayer lobbies. It must be 4–20 characters.</string>
<string name="network">Network</string> <string name="network">Network</string>
<!-- Graphics settings strings --> <!-- Graphics settings strings -->
<string name="backend">Backend</string>
<string name="display">Display</string>
<string name="processing">Post-Processing</string>
<string name="frame_skipping">WIP: Frameskip</string> <string name="frame_skipping">WIP: Frameskip</string>
<string name="frame_skipping_description">Toggle frame skipping to improve performance by reducing the number of rendered frames. This feature is still being worked on and will be enabled in future releases.</string> <string name="frame_skipping_description">Toggle frame skipping to improve performance by reducing the number of rendered frames. This feature is still being worked on and will be enabled in future releases.</string>
<string name="renderer_accuracy">Accuracy level</string> <string name="renderer_accuracy">Accuracy level</string>
@ -820,6 +832,7 @@
<string name="language_simplified_chinese" translatable="false">简体中文</string> <string name="language_simplified_chinese" translatable="false">简体中文</string>
<string name="language_traditional_chinese" translatable="false">正體中文</string> <string name="language_traditional_chinese" translatable="false">正體中文</string>
<string name="language_brazilian_portuguese" translatable="false">Português do Brasil</string> <string name="language_brazilian_portuguese" translatable="false">Português do Brasil</string>
<string name="language_serbian" translatable="false">српски</string>
<!-- Memory Sizes --> <!-- Memory Sizes -->
<string name="memory_byte">Byte</string> <string name="memory_byte">Byte</string>

160
src/common/android/multiplayer/multiplayer.cpp

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "common/android/id_cache.h" #include "common/android/id_cache.h"
@ -10,13 +10,17 @@
#include "network/network.h" #include "network/network.h"
#include "android/log.h" #include "android/log.h"
#include "common/settings.h"
#include "web_service/web_backend.h"
#include "web_service/verify_user_jwt.h"
#include "web_service/web_result.h"
#include <thread> #include <thread>
#include <chrono> #include <chrono>
namespace IDCache = Common::Android; namespace IDCache = Common::Android;
AndroidMultiplayer::AndroidMultiplayer(Core::System& system_,
AndroidMultiplayer::AndroidMultiplayer(Core::System &system_,
std::shared_ptr<Core::AnnounceMultiplayerSession> session) std::shared_ptr<Core::AnnounceMultiplayerSession> session)
: system{system_}, announce_multiplayer_session(session) {} : system{system_}, announce_multiplayer_session(session) {}
@ -27,8 +31,8 @@ void AndroidMultiplayer::AddNetPlayMessage(jint type, jstring msg) {
IDCache::GetAddNetPlayMessage(), type, msg); IDCache::GetAddNetPlayMessage(), type, msg);
} }
void AndroidMultiplayer::AddNetPlayMessage(int type, const std::string& msg) {
JNIEnv* env = IDCache::GetEnvForThread();
void AndroidMultiplayer::AddNetPlayMessage(int type, const std::string &msg) {
JNIEnv *env = IDCache::GetEnvForThread();
AddNetPlayMessage(type, Common::Android::ToJString(env, msg)); AddNetPlayMessage(type, Common::Android::ToJString(env, msg));
} }
@ -46,7 +50,7 @@ bool AndroidMultiplayer::NetworkInit() {
if (auto member = Network::GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
// register the network structs to use in slots and signals // register the network structs to use in slots and signals
member->BindOnStateChanged([this](const Network::RoomMember::State& state) {
member->BindOnStateChanged([this](const Network::RoomMember::State &state) {
if (state == Network::RoomMember::State::Joined || if (state == Network::RoomMember::State::Joined ||
state == Network::RoomMember::State::Moderator) { state == Network::RoomMember::State::Moderator) {
NetPlayStatus status; NetPlayStatus status;
@ -64,7 +68,7 @@ bool AndroidMultiplayer::NetworkInit() {
AddNetPlayMessage(static_cast<int>(status), msg); AddNetPlayMessage(static_cast<int>(status), msg);
} }
}); });
member->BindOnError([this](const Network::RoomMember::Error& error) {
member->BindOnError([this](const Network::RoomMember::Error &error) {
NetPlayStatus status; NetPlayStatus status;
std::string msg; std::string msg;
switch (error) { switch (error) {
@ -107,29 +111,30 @@ bool AndroidMultiplayer::NetworkInit() {
} }
AddNetPlayMessage(static_cast<int>(status), msg); AddNetPlayMessage(static_cast<int>(status), msg);
}); });
member->BindOnStatusMessageReceived([this](const Network::StatusMessageEntry& status_message) {
NetPlayStatus status = NetPlayStatus::NO_ERROR;
std::string msg(status_message.nickname);
switch (status_message.type) {
case Network::IdMemberJoin:
status = NetPlayStatus::MEMBER_JOIN;
break;
case Network::IdMemberLeave:
status = NetPlayStatus::MEMBER_LEAVE;
break;
case Network::IdMemberKicked:
status = NetPlayStatus::MEMBER_KICKED;
break;
case Network::IdMemberBanned:
status = NetPlayStatus::MEMBER_BANNED;
break;
case Network::IdAddressUnbanned:
status = NetPlayStatus::ADDRESS_UNBANNED;
break;
}
AddNetPlayMessage(static_cast<int>(status), msg);
});
member->BindOnChatMessageReceived([this](const Network::ChatEntry& chat) {
member->BindOnStatusMessageReceived(
[this](const Network::StatusMessageEntry &status_message) {
NetPlayStatus status = NetPlayStatus::NO_ERROR;
std::string msg(status_message.nickname);
switch (status_message.type) {
case Network::IdMemberJoin:
status = NetPlayStatus::MEMBER_JOIN;
break;
case Network::IdMemberLeave:
status = NetPlayStatus::MEMBER_LEAVE;
break;
case Network::IdMemberKicked:
status = NetPlayStatus::MEMBER_KICKED;
break;
case Network::IdMemberBanned:
status = NetPlayStatus::MEMBER_BANNED;
break;
case Network::IdAddressUnbanned:
status = NetPlayStatus::ADDRESS_UNBANNED;
break;
}
AddNetPlayMessage(static_cast<int>(status), msg);
});
member->BindOnChatMessageReceived([this](const Network::ChatEntry &chat) {
NetPlayStatus status = NetPlayStatus::CHAT_MESSAGE; NetPlayStatus status = NetPlayStatus::CHAT_MESSAGE;
std::string msg(chat.nickname); std::string msg(chat.nickname);
msg += ": "; msg += ": ";
@ -140,10 +145,14 @@ bool AndroidMultiplayer::NetworkInit() {
return true; return true;
} }
NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string& ipaddress, int port,
const std::string& username, const std::string &preferredGameName,
const u64 &preferredGameId, const std::string& password,
const std::string& room_name, int max_players) {
NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string &ipaddress, int port,
const std::string &username,
const std::string &preferredGameName,
const u64 &preferredGameId,
const std::string &password,
const std::string &room_name, int max_players,
bool isPublic) {
auto member = Network::GetRoomMember().lock(); auto member = Network::GetRoomMember().lock();
if (!member) { if (!member) {
return NetPlayStatus::NETWORK_ERROR; return NetPlayStatus::NETWORK_ERROR;
@ -168,17 +177,55 @@ NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string& ipaddress
.id = preferredGameId, .id = preferredGameId,
}; };
port = (port == 0) ? Network::DefaultRoomPort : static_cast<u16>(port);
port = (port == 0) ? Network::DefaultRoomPort : static_cast<u16>(port);
if (!room->Create(room_name, "", ipaddress, static_cast<u16>(port), password, if (!room->Create(room_name, "", ipaddress, static_cast<u16>(port), password,
static_cast<u32>(std::min(max_players, 16)), username, game, std::make_unique<Network::VerifyUser::NullBackend>(), {})) {
static_cast<u32>(std::min(max_players, 16)), username, game,
CreateVerifyBackend(isPublic), {})) {
return NetPlayStatus::CREATE_ROOM_ERROR; return NetPlayStatus::CREATE_ROOM_ERROR;
} }
// public announce session
if (isPublic) {
if (auto session = announce_multiplayer_session.lock()) {
WebService::WebResult result = session->Register();
if (result.result_code != WebService::WebResult::Code::Success) {
LOG_ERROR(WebService, "Failed to announce public room lobby");
room->Destroy();
return NetPlayStatus::CREATE_ROOM_ERROR;
}
session->Start();
} else {
LOG_ERROR(Network, "Failed to start announce session");
}
}
// Failsafe timer to avoid joining before creation // Failsafe timer to avoid joining before creation
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
member->Join(username, ipaddress.c_str(), static_cast<u16>(port), 0, Network::NoPreferredIP, password, "");
std::string token;
// TODO(alekpop): properly handle the compile definition, it's not working right
//#ifdef ENABLE_WEB_SERVICE
// LOG_INFO(WebService, "Web Service enabled");
if (isPublic) {
WebService::Client client(Settings::values.web_api_url.GetValue(),
Settings::values.eden_username.GetValue(),
Settings::values.eden_token.GetValue());
token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
if (token.empty()) {
LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
} else {
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
}
}
//#else
// LOG_INFO(WebService, "Web Service disabled");
//#endif
member->Join(username, ipaddress.c_str(), static_cast<u16>(port), 0, Network::NoPreferredIP,
password, token);
// Failsafe timer to avoid joining before creation // Failsafe timer to avoid joining before creation
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
@ -194,8 +241,9 @@ NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string& ipaddress
return NetPlayStatus::CREATE_ROOM_ERROR; return NetPlayStatus::CREATE_ROOM_ERROR;
} }
NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string& ipaddress, int port,
const std::string& username, const std::string& password) {
NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string &ipaddress, int port,
const std::string &username,
const std::string &password) {
auto member = Network::GetRoomMember().lock(); auto member = Network::GetRoomMember().lock();
if (!member) { if (!member) {
return NetPlayStatus::NETWORK_ERROR; return NetPlayStatus::NETWORK_ERROR;
@ -209,7 +257,8 @@ NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string& ipaddress,
return NetPlayStatus::ALREADY_IN_ROOM; return NetPlayStatus::ALREADY_IN_ROOM;
} }
member->Join(username, ipaddress.c_str(), static_cast<u16>(port), 0, Network::NoPreferredIP, password, "");
member->Join(username, ipaddress.c_str(), static_cast<u16>(port), 0, Network::NoPreferredIP,
password, "");
// Wait a bit for the connection and join process to complete // Wait a bit for the connection and join process to complete
std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::this_thread::sleep_for(std::chrono::milliseconds(500));
@ -226,7 +275,7 @@ NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string& ipaddress,
return NetPlayStatus::WRONG_PASSWORD; return NetPlayStatus::WRONG_PASSWORD;
} }
void AndroidMultiplayer::NetPlaySendMessage(const std::string& msg) {
void AndroidMultiplayer::NetPlaySendMessage(const std::string &msg) {
if (auto room = Network::GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
if (room->GetState() != Network::RoomMember::State::Joined && if (room->GetState() != Network::RoomMember::State::Joined &&
room->GetState() != Network::RoomMember::State::Moderator) { room->GetState() != Network::RoomMember::State::Moderator) {
@ -237,11 +286,11 @@ void AndroidMultiplayer::NetPlaySendMessage(const std::string& msg) {
} }
} }
void AndroidMultiplayer::NetPlayKickUser(const std::string& username) {
void AndroidMultiplayer::NetPlayKickUser(const std::string &username) {
if (auto room = Network::GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
auto members = room->GetMemberInformation(); auto members = room->GetMemberInformation();
auto it = std::find_if(members.begin(), members.end(), auto it = std::find_if(members.begin(), members.end(),
[&username](const Network::RoomMember::MemberInformation& member) {
[&username](const Network::RoomMember::MemberInformation &member) {
return member.nickname == username; return member.nickname == username;
}); });
if (it != members.end()) { if (it != members.end()) {
@ -250,11 +299,11 @@ void AndroidMultiplayer::NetPlayKickUser(const std::string& username) {
} }
} }
void AndroidMultiplayer::NetPlayBanUser(const std::string& username) {
void AndroidMultiplayer::NetPlayBanUser(const std::string &username) {
if (auto room = Network::GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
auto members = room->GetMemberInformation(); auto members = room->GetMemberInformation();
auto it = std::find_if(members.begin(), members.end(), auto it = std::find_if(members.begin(), members.end(),
[&username](const Network::RoomMember::MemberInformation& member) {
[&username](const Network::RoomMember::MemberInformation &member) {
return member.nickname == username; return member.nickname == username;
}); });
if (it != members.end()) { if (it != members.end()) {
@ -263,7 +312,7 @@ void AndroidMultiplayer::NetPlayBanUser(const std::string& username) {
} }
} }
void AndroidMultiplayer::NetPlayUnbanUser(const std::string& username) {
void AndroidMultiplayer::NetPlayUnbanUser(const std::string &username) {
if (auto room = Network::GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username); room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username);
} }
@ -278,7 +327,7 @@ std::vector<std::string> AndroidMultiplayer::NetPlayRoomInfo() {
auto room_info = room->GetRoomInformation(); auto room_info = room->GetRoomInformation();
info_list.push_back(room_info.name + "|" + std::to_string(room_info.member_slots)); info_list.push_back(room_info.name + "|" + std::to_string(room_info.member_slots));
// all members // all members
for (const auto& member : members) {
for (const auto &member: members) {
info_list.push_back(member.nickname); info_list.push_back(member.nickname);
} }
} }
@ -368,14 +417,29 @@ std::vector<std::string> AndroidMultiplayer::NetPlayGetBanList() {
auto [username_bans, ip_bans] = room->GetBanList(); auto [username_bans, ip_bans] = room->GetBanList();
// Add username bans // Add username bans
for (const auto& username : username_bans) {
for (const auto &username: username_bans) {
ban_list.push_back(username); ban_list.push_back(username);
} }
// Add IP bans // Add IP bans
for (const auto& ip : ip_bans) {
for (const auto &ip: ip_bans) {
ban_list.push_back(ip); ban_list.push_back(ip);
} }
} }
return ban_list; return ban_list;
} }
std::unique_ptr<Network::VerifyUser::Backend> AndroidMultiplayer::CreateVerifyBackend(bool use_validation) {
std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
if (use_validation) {
//#ifdef ENABLE_WEB_SERVICE
verify_backend =
std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
//#else
// verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
//#endif
} else {
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
}
return verify_backend;
}

4
src/common/android/multiplayer/multiplayer.h

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once
@ -66,7 +66,7 @@ public:
NetPlayStatus NetPlayCreateRoom(const std::string &ipaddress, int port, NetPlayStatus NetPlayCreateRoom(const std::string &ipaddress, int port,
const std::string &username, const std::string &preferredGameName, const std::string &username, const std::string &preferredGameName,
const u64 &preferredGameId, const std::string &password, const u64 &preferredGameId, const std::string &password,
const std::string &room_name, int max_players);
const std::string &room_name, int max_players, bool isPublic);
NetPlayStatus NetPlayJoinRoom(const std::string &ipaddress, int port, NetPlayStatus NetPlayJoinRoom(const std::string &ipaddress, int port,
const std::string &username, const std::string &password); const std::string &username, const std::string &password);

5
src/common/settings.cpp

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -118,7 +121,7 @@ void LogSettings() {
LOG_INFO(Config, "yuzu Configuration:"); LOG_INFO(Config, "yuzu Configuration:");
for (auto& [category, settings] : values.linkage.by_category) { for (auto& [category, settings] : values.linkage.by_category) {
for (const auto& setting : settings) { for (const auto& setting : settings) {
if (setting->Id() == values.yuzu_token.Id()) {
if (setting->Id() == values.eden_token.Id()) {
// Hide the token secret, for security reasons. // Hide the token secret, for security reasons.
continue; continue;
} }

6
src/common/settings.h

@ -527,7 +527,7 @@ struct Values {
SwitchableSetting<Language, true> language_index{linkage, SwitchableSetting<Language, true> language_index{linkage,
Language::EnglishAmerican, Language::EnglishAmerican,
Language::Japanese, Language::Japanese,
Language::PortugueseBrazilian,
Language::Serbian,
"language_index", "language_index",
Category::System}; Category::System};
SwitchableSetting<Region, true> region_index{linkage, Region::Usa, Region::Japan, SwitchableSetting<Region, true> region_index{linkage, Region::Usa, Region::Japan,
@ -700,9 +700,9 @@ struct Values {
// WebService // WebService
Setting<std::string> web_api_url{linkage, "api.ynet-fun.xyz", "web_api_url", Setting<std::string> web_api_url{linkage, "api.ynet-fun.xyz", "web_api_url",
Category::WebService}; Category::WebService};
Setting<std::string> yuzu_username{linkage, std::string(), "yuzu_username",
Setting<std::string> eden_username{linkage, std::string(), "eden_username",
Category::WebService}; Category::WebService};
Setting<std::string> yuzu_token{linkage, std::string(), "yuzu_token", Category::WebService};
Setting<std::string> eden_token{linkage, std::string(), "eden_token", Category::WebService};
// Add-Ons // Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons; std::map<u64, std::vector<std::string>> disabled_addons;

2
src/common/settings_enums.h

@ -111,7 +111,7 @@ ENUM(AudioMode, Mono, Stereo, Surround);
ENUM(Language, Japanese, EnglishAmerican, French, German, Italian, Spanish, Chinese, Korean, Dutch, ENUM(Language, Japanese, EnglishAmerican, French, German, Italian, Spanish, Chinese, Korean, Dutch,
Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin, Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin,
ChineseSimplified, ChineseTraditional, PortugueseBrazilian);
ChineseSimplified, ChineseTraditional, PortugueseBrazilian, Serbian);
ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan); ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan);

10
src/dedicated_room/yuzu_room.cpp

@ -345,14 +345,14 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
LOG_INFO(Network, "Hosting a public room"); LOG_INFO(Network, "Hosting a public room");
Settings::values.web_api_url = web_api_url; Settings::values.web_api_url = web_api_url;
PadToken(token); PadToken(token);
Settings::values.yuzu_username = UsernameFromDisplayToken(token);
username = Settings::values.yuzu_username.GetValue();
Settings::values.yuzu_token = TokenFromDisplayToken(token);
Settings::values.eden_username = UsernameFromDisplayToken(token);
username = Settings::values.eden_username.GetValue();
Settings::values.eden_token = TokenFromDisplayToken(token);
} else { } else {
LOG_INFO(Network, "Hosting a public room"); LOG_INFO(Network, "Hosting a public room");
Settings::values.web_api_url = web_api_url; Settings::values.web_api_url = web_api_url;
Settings::values.yuzu_username = username;
Settings::values.yuzu_token = token;
Settings::values.eden_username = username;
Settings::values.eden_token = token;
} }
} }

30
src/network/announce_multiplayer_session.cpp

@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono> #include <chrono>
#include <future> #include <future>
#include <vector> #include <vector>
@ -13,9 +13,9 @@
#include "common/settings.h" #include "common/settings.h"
#include "network/network.h" #include "network/network.h"
#ifdef ENABLE_WEB_SERVICE
//#ifdef ENABLE_WEB_SERVICE
#include "web_service/announce_room_json.h" #include "web_service/announce_room_json.h"
#endif
//#endif
namespace Core { namespace Core {
@ -23,13 +23,13 @@ namespace Core {
static constexpr std::chrono::seconds announce_time_interval(15); static constexpr std::chrono::seconds announce_time_interval(15);
AnnounceMultiplayerSession::AnnounceMultiplayerSession() { AnnounceMultiplayerSession::AnnounceMultiplayerSession() {
#ifdef ENABLE_WEB_SERVICE
//#ifdef ENABLE_WEB_SERVICE
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(), backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
Settings::values.yuzu_username.GetValue(),
Settings::values.yuzu_token.GetValue());
#else
backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>();
#endif
Settings::values.eden_username.GetValue(),
Settings::values.eden_token.GetValue());
//#else
// backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>();
//#endif
} }
WebService::WebResult AnnounceMultiplayerSession::Register() { WebService::WebResult AnnounceMultiplayerSession::Register() {
@ -156,11 +156,11 @@ bool AnnounceMultiplayerSession::IsRunning() const {
void AnnounceMultiplayerSession::UpdateCredentials() { void AnnounceMultiplayerSession::UpdateCredentials() {
ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running"); ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running");
#ifdef ENABLE_WEB_SERVICE
//#ifdef ENABLE_WEB_SERVICE
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(), backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
Settings::values.yuzu_username.GetValue(),
Settings::values.yuzu_token.GetValue());
#endif
Settings::values.eden_username.GetValue(),
Settings::values.eden_token.GetValue());
//#endif
} }
} // namespace Core } // namespace Core

2
src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp

@ -1,5 +1,3 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later

3
src/video_core/renderer_vulkan/blit_image.cpp

@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later

3
src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp

@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later

162
src/yuzu/configuration/configure_web.cpp

@ -1,44 +1,52 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "yuzu/configuration/configure_web.h"
#include <QIcon> #include <QIcon>
#include <QMessageBox> #include <QMessageBox>
#if QT_VERSION_MAJOR >= 6
#include <QRegularExpressionValidator>
#else
#include <QRegExpValidator>
#endif
#include <QtConcurrent/QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include "common/settings.h" #include "common/settings.h"
#include "ui_configure_web.h" #include "ui_configure_web.h"
#include "yuzu/configuration/configure_web.h"
#include "yuzu/uisettings.h" #include "yuzu/uisettings.h"
// static constexpr char token_delimiter{':'};
ConfigureWeb::ConfigureWeb(QWidget* parent)
: QWidget(parent)
, ui(std::make_unique<Ui::ConfigureWeb>())
, m_rng{QRandomGenerator::system()}
{
ui->setupUi(this);
// static std::string GenerateDisplayToken(const std::string& username, const std::string& token) {
// if (username.empty() || token.empty()) {
// return {};
// }
QString user_regex = QStringLiteral(".{4,20}");
QString token_regex = QStringLiteral("[a-z]{48}");
// const std::string unencoded_display_token{username + token_delimiter + token};
// QByteArray b{unencoded_display_token.c_str()};
// QByteArray b64 = b.toBase64();
// return b64.toStdString();
// }
#if QT_VERSION_MAJOR >= 6
QRegularExpressionValidator *username_validator = new QRegularExpressionValidator(this);
QRegularExpressionValidator *token_validator = new QRegularExpressionValidator(this);
// static std::string UsernameFromDisplayToken(const std::string& display_token) {
// const std::string unencoded_display_token{
// QByteArray::fromBase64(display_token.c_str()).toStdString()};
// return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
// }
username_validator->setRegularExpression(QRegularExpression(user_regex));
token_validator->setRegularExpression(QRegularExpression(token_regex));
#else
QRegExpValidator *username_validator = new QRegExpValidator(this);
QRegExpValidator *token_validator = new QRegExpValidator(this);
// static std::string TokenFromDisplayToken(const std::string& display_token) {
// const std::string unencoded_display_token{
// QByteArray::fromBase64(display_token.c_str()).toStdString()};
// return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
// }
username_validator->setRegExp(QRegExp(user_regex));
token_validator->setRegExp(QRegExp(token_regex));
#endif
ConfigureWeb::ConfigureWeb(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
ui->setupUi(this);
connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);
ui->edit_username->setValidator(username_validator);
ui->edit_token->setValidator(token_validator);
connect(ui->button_generate, &QPushButton::clicked, this, &ConfigureWeb::GenerateToken);
#ifndef USE_DISCORD_PRESENCE #ifndef USE_DISCORD_PRESENCE
ui->discord_group->setVisible(false); ui->discord_group->setVisible(false);
@ -60,87 +68,61 @@ void ConfigureWeb::changeEvent(QEvent* event) {
void ConfigureWeb::RetranslateUI() { void ConfigureWeb::RetranslateUI() {
ui->retranslateUi(this); ui->retranslateUi(this);
ui->web_signup_link->setText(
tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; "
"color:#039be5;\">Sign up</span></a>"));
ui->web_token_info_link->setText(
tr("<a href='https://evilperson1337.notion.site/Hosting-a-Room-Inside-of-Eden-20457c2edaf680108abac6215a79acdb'><span style=\"text-decoration: "
"underline; color:#039be5;\">What is my token?</span></a>"));
} }
void ConfigureWeb::SetConfiguration() { void ConfigureWeb::SetConfiguration() {
ui->web_credentials_disclaimer->setWordWrap(true);
connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::VerifyLogin);
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::VerifyLogin);
ui->web_signup_link->setOpenExternalLinks(true);
ui->web_token_info_link->setOpenExternalLinks(true);
ui->edit_username->setText(QString::fromStdString(Settings::values.eden_username.GetValue()));
ui->edit_token->setText(QString::fromStdString(Settings::values.eden_token.GetValue()));
ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token.GetValue()));
// ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken(
// Settings::values.yuzu_username.GetValue(), Settings::values.yuzu_token.GetValue())));
VerifyLogin();
// Connect after setting the values, to avoid calling OnLoginChanged now
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue());
}
user_verified = true;
void ConfigureWeb::GenerateToken() {
constexpr size_t length = 48;
QString set = QStringLiteral("abcdefghijklmnopqrstuvwxyz");
QString result;
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue());
for (size_t i = 0; i < length; ++i) {
size_t idx = m_rng->bounded(set.length());
result.append(set.at(idx));
}
ui->edit_token->setText(result);
} }
void ConfigureWeb::ApplyConfiguration() { void ConfigureWeb::ApplyConfiguration() {
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
Settings::values.yuzu_username = ui->edit_username->text().toStdString();
Settings::values.yuzu_token = ui->edit_token->text().toStdString();
Settings::values.eden_username = ui->edit_username->text().toStdString();
Settings::values.eden_token = ui->edit_token->text().toStdString();
} }
void ConfigureWeb::OnLoginChanged() {
if (ui->edit_token->text().isEmpty()) {
user_verified = true;
// Empty = no icon
ui->label_token_verified->setPixmap(QPixmap());
ui->label_token_verified->setToolTip(QString());
} else {
user_verified = false;
void ConfigureWeb::VerifyLogin() {
const QPixmap checked = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
const QPixmap failed = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
// Show an info icon if it's been changed, clearer than showing failure
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
ui->label_token_verified->setToolTip(
tr("Unverified, please click Verify before saving configuration", "Tooltip"));
}
}
const bool username_good = ui->edit_username->hasAcceptableInput();
const bool token_good = ui->edit_token->hasAcceptableInput();
void ConfigureWeb::VerifyLogin() {
QMessageBox::warning(this,
tr("Warning"),
tr("Verification is currently nonfunctional, instead generate a random "
"48-character string with only lowercase a-z."));
// ui->button_verify_login->setDisabled(true);
// ui->button_verify_login->setText(tr("Verifying..."));
// ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
// ui->label_token_verified->setToolTip(tr("Verifying..."));
}
if (username_good) {
ui->label_username_verified->setPixmap(checked);
ui->label_username_verified->setToolTip(tr("All Good", "Tooltip"));
} else {
ui->label_username_verified->setPixmap(failed);
ui->label_username_verified->setToolTip(tr("Must be between 4-20 characters", "Tooltip"));
}
void ConfigureWeb::OnLoginVerified() {
// ui->button_verify_login->setEnabled(true);
// ui->button_verify_login->setText(tr("Verify"));
// if (verify_watcher.result()) {
// user_verified = true;
// ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
// ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
// ui->username->setText(
// QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
// } else {
// ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
// ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
// ui->username->setText(tr("Unspecified"));
// QMessageBox::critical(this, tr("Verification failed"),
// tr("Verification failed. Check that you have entered your token "
// "correctly, and that your internet connection is working."));
// }
if (token_good) {
ui->label_token_verified->setPixmap(checked);
ui->label_token_verified->setToolTip(tr("All Good", "Tooltip"));
} else {
ui->label_token_verified->setPixmap(failed);
ui->label_token_verified->setToolTip(tr("Must be 48 characters, and lowercase a-z", "Tooltip"));
}
} }
void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) { void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {

20
src/yuzu/configuration/configure_web.h

@ -1,11 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <memory>
#include <QFutureWatcher>
#include <QRandomGenerator>
#include <QWidget> #include <QWidget>
#include <memory>
namespace Ui { namespace Ui {
class ConfigureWeb; class ConfigureWeb;
@ -25,14 +29,12 @@ private:
void changeEvent(QEvent* event) override; void changeEvent(QEvent* event) override;
void RetranslateUI(); void RetranslateUI();
void OnLoginChanged();
void VerifyLogin();
void OnLoginVerified();
void SetConfiguration(); void SetConfiguration();
bool user_verified = true;
QFutureWatcher<bool> verify_watcher;
std::unique_ptr<Ui::ConfigureWeb> ui; std::unique_ptr<Ui::ConfigureWeb> ui;
QRandomGenerator *m_rng;
private slots:
void GenerateToken();
void VerifyLogin();
}; };

88
src/yuzu/configuration/configure_web.ui

@ -25,42 +25,29 @@
<string>eden Web Service</string> <string>eden Web Service</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayoutYuzuWebService"> <layout class="QVBoxLayout" name="verticalLayoutYuzuWebService">
<item>
<widget class="QLabel" name="web_credentials_disclaimer">
<property name="text">
<string>By providing your username and token, you agree to allow eden to collect additional usage data, which may include user identifying information.</string>
</property>
</widget>
</item>
<item> <item>
<layout class="QGridLayout" name="gridLayoutYuzuUsername"> <layout class="QGridLayout" name="gridLayoutYuzuUsername">
<item row="2" column="3">
<widget class="QPushButton" name="button_verify_login">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::LayoutDirection::RightToLeft</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_token">
<property name="text"> <property name="text">
<string>Verify</string>
<string>Token: </string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QLabel" name="web_signup_link">
<property name="enabled">
<bool>false</bool>
<item row="0" column="0">
<widget class="QLabel" name="label_username">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Sign up</string>
<string>Username: </string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" colspan="3">
<item row="0" column="1" colspan="4">
<widget class="QLineEdit" name="edit_username"> <widget class="QLineEdit" name="edit_username">
<property name="maxLength"> <property name="maxLength">
<number>20</number> <number>20</number>
@ -70,25 +57,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="label_token">
<property name="text">
<string>Token: </string>
<item row="1" column="5">
<widget class="QLabel" name="label_token_verified">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
</widget>
</item>
<item row="1" column="4">
<widget class="QLabel" name="label_token_verified"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_username">
<property name="text">
<string>Username: </string>
<property name="minimumSize">
<size>
<width>24</width>
<height>0</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" colspan="3">
<item row="1" column="1" colspan="4">
<widget class="QLineEdit" name="edit_token"> <widget class="QLineEdit" name="edit_token">
<property name="inputMethodHints">
<set>Qt::InputMethodHint::ImhLowercaseOnly</set>
</property>
<property name="maxLength"> <property name="maxLength">
<number>80</number> <number>80</number>
</property> </property>
@ -97,14 +86,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QLabel" name="web_token_info_link">
<item row="0" column="5">
<widget class="QLabel" name="label_username_verified">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QPushButton" name="button_generate">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text"> <property name="text">
<string>What is my token?</string>
<string>Generate</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="2">
<item row="2" column="1" colspan="3">
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>

1
src/yuzu/configuration/shared_translation.cpp

@ -602,6 +602,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent)
PAIR(Language, ChineseSimplified, tr("Simplified Chinese")), PAIR(Language, ChineseSimplified, tr("Simplified Chinese")),
PAIR(Language, ChineseTraditional, tr("Traditional Chinese (正體中文)")), PAIR(Language, ChineseTraditional, tr("Traditional Chinese (正體中文)")),
PAIR(Language, PortugueseBrazilian, tr("Brazilian Portuguese (português do Brasil)")), PAIR(Language, PortugueseBrazilian, tr("Brazilian Portuguese (português do Brasil)")),
PAIR(Language, Serbian, tr("Serbian (српски)")),
}}); }});
translations->insert({Settings::EnumMetadata<Settings::Region>::Index(), translations->insert({Settings::EnumMetadata<Settings::Region>::Index(),
{ {

4
src/yuzu/main.cpp

@ -3591,8 +3591,8 @@ void GMainWindow::OnMenuReportCompatibility() {
return; return;
} }
if (!Settings::values.yuzu_token.GetValue().empty() &&
!Settings::values.yuzu_username.GetValue().empty()) {
if (!Settings::values.eden_token.GetValue().empty() &&
!Settings::values.eden_username.GetValue().empty()) {
} else { } else {
QMessageBox::critical( QMessageBox::critical(
this, tr("Missing yuzu Account"), this, tr("Missing yuzu Account"),

10
src/yuzu/multiplayer/direct_connect.cpp

@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QComboBox> #include <QComboBox>
#include <QFuture> #include <QFuture>
#include <QIntValidator> #include <QIntValidator>
@ -38,9 +38,9 @@ DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
ui->nickname->setValidator(validation.GetNickname()); ui->nickname->setValidator(validation.GetNickname());
ui->nickname->setText( ui->nickname->setText(
QString::fromStdString(UISettings::values.multiplayer_nickname.GetValue())); QString::fromStdString(UISettings::values.multiplayer_nickname.GetValue()));
if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
if (ui->nickname->text().isEmpty() && !Settings::values.eden_username.GetValue().empty()) {
// Use yuzu Web Service user name as nickname by default // Use yuzu Web Service user name as nickname by default
ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
ui->nickname->setText(QString::fromStdString(Settings::values.eden_username.GetValue()));
} }
ui->ip->setValidator(validation.GetIP()); ui->ip->setValidator(validation.GetIP());
ui->ip->setText(QString::fromStdString(UISettings::values.multiplayer_ip.GetValue())); ui->ip->setText(QString::fromStdString(UISettings::values.multiplayer_ip.GetValue()));

16
src/yuzu/multiplayer/host_room.cpp

@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <future> #include <future>
#include <QColor> #include <QColor>
#include <QImage> #include <QImage>
@ -59,9 +59,9 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
// Restore the settings: // Restore the settings:
ui->username->setText( ui->username->setText(
QString::fromStdString(UISettings::values.multiplayer_room_nickname.GetValue())); QString::fromStdString(UISettings::values.multiplayer_room_nickname.GetValue()));
if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
if (ui->username->text().isEmpty() && !Settings::values.eden_username.GetValue().empty()) {
// Use eden Web Service user name as nickname by default // Use eden Web Service user name as nickname by default
ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
ui->username->setText(QString::fromStdString(Settings::values.eden_username.GetValue()));
} }
ui->room_name->setText( ui->room_name->setText(
QString::fromStdString(UISettings::values.multiplayer_room_name.GetValue())); QString::fromStdString(UISettings::values.multiplayer_room_name.GetValue()));
@ -167,7 +167,7 @@ void HostRoomWindow::Host() {
const bool created = const bool created =
room->Create(ui->room_name->text().toStdString(), room->Create(ui->room_name->text().toStdString(),
ui->room_description->toPlainText().toStdString(), "", port, password, ui->room_description->toPlainText().toStdString(), "", port, password,
ui->max_player->value(), Settings::values.yuzu_username.GetValue(),
ui->max_player->value(), Settings::values.eden_username.GetValue(),
game, CreateVerifyBackend(is_public), ban_list); game, CreateVerifyBackend(is_public), ban_list);
if (!created) { if (!created) {
NetworkMessage::ErrorManager::ShowError( NetworkMessage::ErrorManager::ShowError(
@ -206,8 +206,8 @@ void HostRoomWindow::Host() {
#ifdef ENABLE_WEB_SERVICE #ifdef ENABLE_WEB_SERVICE
if (is_public) { if (is_public) {
WebService::Client client(Settings::values.web_api_url.GetValue(), WebService::Client client(Settings::values.web_api_url.GetValue(),
Settings::values.yuzu_username.GetValue(),
Settings::values.yuzu_token.GetValue());
Settings::values.eden_username.GetValue(),
Settings::values.eden_token.GetValue());
if (auto room = Network::GetRoom().lock()) { if (auto room = Network::GetRoom().lock()) {
token = client.GetExternalJWT(room->GetVerifyUID()).returned_data; token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
} }

18
src/yuzu/multiplayer/lobby.cpp

@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QInputDialog> #include <QInputDialog>
#include <QList> #include <QList>
#include <QtConcurrent/QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
@ -67,9 +67,9 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
// Try find the best nickname by default // Try find the best nickname by default
if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("eden")) { if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("eden")) {
if (!Settings::values.yuzu_username.GetValue().empty()) {
if (!Settings::values.eden_username.GetValue().empty()) {
ui->nickname->setText( ui->nickname->setText(
QString::fromStdString(Settings::values.yuzu_username.GetValue()));
QString::fromStdString(Settings::values.eden_username.GetValue()));
} else if (!GetProfileUsername().empty()) { } else if (!GetProfileUsername().empty()) {
ui->nickname->setText(QString::fromStdString(GetProfileUsername())); ui->nickname->setText(QString::fromStdString(GetProfileUsername()));
} else { } else {
@ -189,11 +189,11 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid] { QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid] {
std::string token; std::string token;
#ifdef ENABLE_WEB_SERVICE #ifdef ENABLE_WEB_SERVICE
if (!Settings::values.yuzu_username.GetValue().empty() &&
!Settings::values.yuzu_token.GetValue().empty()) {
if (!Settings::values.eden_username.GetValue().empty() &&
!Settings::values.eden_token.GetValue().empty()) {
WebService::Client client(Settings::values.web_api_url.GetValue(), WebService::Client client(Settings::values.web_api_url.GetValue(),
Settings::values.yuzu_username.GetValue(),
Settings::values.yuzu_token.GetValue());
Settings::values.eden_username.GetValue(),
Settings::values.eden_token.GetValue());
token = client.GetExternalJWT(verify_uid).returned_data; token = client.GetExternalJWT(verify_uid).returned_data;
if (token.empty()) { if (token.empty()) {
LOG_ERROR(WebService, "Could not get external JWT, verification may fail"); LOG_ERROR(WebService, "Could not get external JWT, verification may fail");

Loading…
Cancel
Save