diff options
| author | crueter <swurl@swurl.xyz> | 2025-07-01 01:44:12 +0000 |
|---|---|---|
| committer | crueter <crueter@noreply.localhost> | 2025-07-01 01:44:12 +0000 |
| commit | 94c66f98bfa69f71f5d0623cecac8444e383d34d (patch) | |
| tree | 9232cf5aae0a4884afe1822f5b7d0db07d4f573a | |
| parent | 2fe728766e1643634e42050a04991f192219864e (diff) | |
[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>
34 files changed, 1977 insertions, 319 deletions
diff --git a/.ci/license-header.sh b/.ci/license-header.sh index 2f8b16c3cd..b65ef6303f 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -4,13 +4,12 @@ HEADER="$(cat "$PWD/.ci/license/header.txt")" 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" diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt index 75059251bd..2ae0377a95 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/ChatDialog.kt +++ b/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 - package org.yuzu.yuzu_emu.dialogs 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.databinding.DialogChatBinding import org.yuzu.yuzu_emu.databinding.ItemChatMessageBinding +import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.network.NetPlayManager import java.text.SimpleDateFormat import java.util.* @@ -82,7 +82,7 @@ class ChatDialog(context: Context) : BottomSheetDialog(context) { @SuppressLint("NotifyDataSetChanged") private fun sendMessage(message: String) { - val username = NetPlayManager.getUsername(context) + val username = StringSetting.WEB_USERNAME.getString() NetPlayManager.netPlaySendMessage(message) val chatMessage = ChatMessage( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt index ae588bdfa8..39391d6e53 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt +++ b/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 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.databinding.DialogLobbyBrowserBinding import org.yuzu.yuzu_emu.databinding.ItemLobbyRoomBinding +import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.network.NetPlayManager import java.util.Locale @@ -144,7 +145,7 @@ class LobbyBrowser(context: Context) : BottomSheetDialog(context) { } private fun joinRoom(room: NetPlayManager.RoomInfo, password: String) { - val username = NetPlayManager.getUsername(context) + val username = StringSetting.WEB_USERNAME.getString() Thread { val result = NetPlayManager.netPlayJoinRoom(room.ip, room.port, username, password) 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 85f949853d..dc8d5e5761 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 @@ -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 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.BottomSheetDialog 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.YuzuApplication 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.ItemButtonNetplayBinding 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.utils.CompatUtils import org.yuzu.yuzu_emu.utils.GameHelper @@ -180,9 +182,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { PopupMenu(view.context, view).apply { menuInflater.inflate(R.menu.menu_netplay_member, menu) 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 && - netPlayItems.name != NetPlayManager.getUsername(context) + netPlayItems.name != StringSetting.WEB_USERNAME.getString() setOnMenuItemClickListener { item -> if (item.itemId == R.id.action_kick) { NetPlayManager.netPlayKickUser(netPlayItems.name) @@ -297,13 +299,13 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { abstract class TextValidatorWatcher( private val btnConfirm: Button, - private val view: EditText, + private val layout: TextInputLayout, private val errorMessage: String ) : TextWatcher { - companion object { - val validStates: HashMap<EditText, Boolean> = hashMapOf() + val validStates: HashMap<TextInputLayout, Boolean> = hashMapOf() } + abstract fun validate(s: String): Boolean override fun beforeTextChanged( @@ -325,20 +327,20 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { override fun afterTextChanged(s: Editable?) { val input = s.toString() 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) } } // TODO(alekpop, crueter): Properly handle getting banned (both during and in future connects) private fun showNetPlayInputDialog(isCreateRoom: Boolean) { + TextValidatorWatcher.validStates.clear() val activity = CompatUtils.findActivity(context) val dialog = BottomSheetDialog(activity) - val validStates: HashMap<EditText, Boolean> = hashMapOf() - dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED dialog.behavior.skipCollapsed = @@ -347,6 +349,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { val binding = DialogMultiplayerRoomBinding.inflate(LayoutInflater.from(activity)) 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( if (isCreateRoom) R.string.multiplayer_create_room else R.string.multiplayer_join_room @@ -355,7 +362,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { // setup listeners etc val roomNameWatcher = object : TextValidatorWatcher( binding.btnConfirm, // TODO(alekpop, crueter): Figure out a better way to deal with this? - binding.roomName, + binding.layoutRoomName, context.getString( R.string.multiplayer_room_name_error ) @@ -367,25 +374,32 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { val preferredWatcher = object : TextValidatorWatcher( binding.btnConfirm, - binding.dropdownPreferredGameName, + binding.preferredGameName, context.getString(R.string.multiplayer_required) ) { override fun validate(s: String): Boolean { 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( binding.btnConfirm, - binding.ipAddress, + binding.layoutIpAddress, context.getString(R.string.multiplayer_ip_error) ) { override fun validate(s: String): Boolean { @@ -400,17 +414,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { val usernameWatcher = object : TextValidatorWatcher( binding.btnConfirm, - binding.username, + binding.layoutUsername, context.getString(R.string.multiplayer_username_error) ) { override fun validate(s: String): Boolean { - return s.length >= 5 + return s.length in 4..20 } } val portWatcher = object : TextValidatorWatcher( binding.btnConfirm, - binding.ipPort, + binding.layoutIpPort, context.getString(R.string.multiplayer_port_error) ) { override fun validate(s: String): Boolean { @@ -421,6 +435,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { if (isCreateRoom) { binding.roomName.addTextChangedListener(roomNameWatcher) binding.dropdownPreferredGameName.addTextChangedListener(preferredWatcher) + binding.dropdownLobbyVisibility.addTextChangedListener(visibilityWatcher) binding.dropdownPreferredGameName.apply { 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.ipPort.addTextChangedListener(portWatcher) binding.username.addTextChangedListener(usernameWatcher) + binding.ipAddress.setText(NetPlayManager.getRoomAddress(activity)) binding.ipPort.setText(NetPlayManager.getRoomPort(activity)) - binding.username.setText(NetPlayManager.getUsername(activity)) + binding.username.setText(StringSetting.WEB_USERNAME.getString()) // manually trigger text listeners if (isCreateRoom) { roomNameWatcher.afterTextChanged(binding.roomName.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) @@ -451,8 +482,10 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { usernameWatcher.afterTextChanged(binding.username.text) 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.maxPlayersContainer.visibility = if (isCreateRoom) View.VISIBLE else View.GONE + binding.maxPlayersLabel.text = context.getString( R.string.multiplayer_max_players_value, binding.maxPlayers.value.toInt() @@ -464,7 +497,6 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { } // TODO(alekpop, crueter): Room descriptions - // TODO(alekpop, crueter): Public room creation // TODO(alekpop, crueter): Preview preferred games binding.btnConfirm.setOnClickListener { binding.btnConfirm.isEnabled = false @@ -487,6 +519,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { val preferredIdx = gameNameList.indexOfFirst { it[0] == preferredGameName } 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 { val result = if (isCreateRoom) { NetPlayManager.netPlayCreateRoom( @@ -497,23 +532,26 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { preferredGameId, password, roomName, - maxPlayers + maxPlayers, + isPublic ) } else { NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password) } 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.setRoomAddress(activity, ipAddress) if (!isCreateRoom) NetPlayManager.setRoomAddress(activity, ipAddress) + Toast.makeText( YuzuApplication.appContext, if (isCreateRoom) R.string.multiplayer_create_room_success else R.string.multiplayer_join_room_success, Toast.LENGTH_LONG ).show() + dialog.dismiss() } else { Toast.makeText( @@ -521,6 +559,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { R.string.multiplayer_could_not_connect, Toast.LENGTH_LONG ).show() + binding.btnConfirm.isEnabled = true binding.btnConfirm.text = activity.getString(R.string.ok) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt index f0fe43c7a8..c7fb646e3a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt +++ b/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-License-Identifier: GPL-2.0-or-later @@ -9,9 +12,8 @@ enum class StringSetting(override val key: String) : AbstractStringSetting { DRIVER_PATH("driver_path"), 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) 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 9782aa4d2b..f5475a2f30 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 @@ -284,11 +284,32 @@ abstract class SettingsItem( descriptionId = R.string.use_custom_rtc_description ) ) + put( StringInputSetting( StringSetting.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)) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringInputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringInputSetting.kt index 1eb999416a..85076e680d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringInputSetting.kt +++ b/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-License-Identifier: GPL-2.0-or-later package org.yuzu.yuzu_emu.features.settings.model.view +import android.text.Editable import androidx.annotation.StringRes import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting @@ -11,7 +15,10 @@ class StringInputSetting( @StringRes titleId: Int = 0, titleString: String = "", @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) { override val type = TYPE_STRING_INPUT 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 7f562a1f41..1485d9a6cc 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 @@ -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-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.content.DialogInterface import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.EditText +import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -138,6 +145,46 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener stringInputBinding = DialogEditTextBinding.inflate(layoutInflater) val item = settingsViewModel.clickedItem as StringInputSetting 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()) .setTitle(item.title) .setView(stringInputBinding.root) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index f8f381db41..b09a611506 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/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(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>) { sl.apply { // TODO(crueter): reorganize this, this is awful + add(HeaderSetting(R.string.backend)) + add(IntSetting.RENDERER_ACCURACY.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_SCALING_FILTER.key) - add(IntSetting.FSR_SHARPENING_SLIDER.key) add(IntSetting.RENDERER_ANTI_ALIASING.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_ASPECT_RATIO.key) add(IntSetting.VERTICAL_ALIGNMENT.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) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/network/NetPlayManager.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/network/NetPlayManager.kt index 52706ea765..478dea6bdd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/network/NetPlayManager.kt +++ b/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 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.dialogs.ChatMessage import java.net.Inet4Address +import androidx.core.content.edit object NetPlayManager { external fun netPlayCreateRoom( @@ -26,7 +27,8 @@ object NetPlayManager { preferredGameId: Long, password: String, roomName: String, - maxPlayers: Int + maxPlayers: Int, + isPublic: Boolean ): Int external fun netPlayJoinRoom( @@ -125,17 +127,6 @@ object NetPlayManager { 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 { val prefs = PreferenceManager.getDefaultSharedPreferences(activity) val address = getIpAddressByWifi(activity) @@ -144,7 +135,7 @@ object NetPlayManager { fun setRoomAddress(activity: Activity, address: String) { val prefs = PreferenceManager.getDefaultSharedPreferences(activity) - prefs.edit().putString("NetPlayRoomAddress", address).apply() + prefs.edit { putString("NetPlayRoomAddress", address) } } fun getRoomPort(activity: Activity): String { @@ -154,7 +145,7 @@ object NetPlayManager { fun setRoomPort(activity: Activity, port: String) { val prefs = PreferenceManager.getDefaultSharedPreferences(activity) - prefs.edit().putString("NetPlayRoomPort", port).apply() + prefs.edit { putString("NetPlayRoomPort", port) } } private val chatMessages = mutableListOf<ChatMessage>() diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 4dd6f833b0..fe7c0658d4 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/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( JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port, 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>( multiplayer->NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port, Common::Android::GetJString(env, username), Common::Android::GetJString(env, preferredGameName), 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( 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 58b905d71d..3612f46eae 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 @@ -2,6 +2,7 @@ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -20,4 +21,13 @@ </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> diff --git a/src/android/app/src/main/res/layout/dialog_multiplayer_room.xml b/src/android/app/src/main/res/layout/dialog_multiplayer_room.xml index 6eb7214087..476dac4cfa 100644 --- a/src/android/app/src/main/res/layout/dialog_multiplayer_room.xml +++ b/src/android/app/src/main/res/layout/dialog_multiplayer_room.xml @@ -21,6 +21,7 @@ android:textColor="?attr/colorOnSurface" /> <com.google.android.material.textfield.TextInputLayout + android:id="@+id/layout_ip_address" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/multiplayer_ip_address" @@ -34,6 +35,7 @@ </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_height="wrap_content" android:hint="@string/multiplayer_ip_port" @@ -47,6 +49,7 @@ </com.google.android.material.textfield.TextInputLayout> <com.google.android.material.textfield.TextInputLayout + android:id="@+id/layout_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/multiplayer_username" @@ -89,6 +92,7 @@ </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_height="wrap_content" android:hint="@string/multiplayer_room_name" @@ -125,6 +129,21 @@ android:text="@string/multiplayer_max_players_value" /> </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 android:id="@+id/btn_confirm" android:layout_width="wrap_content" diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml new file mode 100644 index 0000000000..703429ddd5 --- /dev/null +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -0,0 +1,1453 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> + + <!-- General application strings --> + <string name="app_name" translatable="false">Eden</string> + <string name="app_disclaimer">Овај софтвер ће покренути игре за нинтендо прекидач игру. Нису укључени ни тастери за игру или тастери.<br /><br /> пре него што започнете, молимо вас да пронађете своје</string> + <string name="notice_notification_channel_name">Обавештења и грешке</string> + <string name="notice_notification_channel_id" translatable="false">noticesAndErrors</string> + <string name="notice_notification_channel_description">Приказује обавештења када нешто пође по злу.</string> + <string name="notification_permission_not_granted">Дозвола за обавештавање није одобрена!</string> + + <!-- Stats Overlay settings --> + <string name="enhanced_fps_suffix">(Побољшано)</string> + <string name="shaders_prefix">Зграда</string> + <string name="shaders_suffix">Схадер (с)</string> + <string name="system_info_label">Систем:</string> + <string name="show_stats_overlay">Покажи Статистика перформанси Статистика</string> + <string name="stats_overlay_customization">Прилагођавање</string> + <string name="stats_overlay_items">Видљивост</string> + <string name="stats_overlay_options">Прекривање перформанси</string> + <string name="enable_stats_overlay_">Омогући преклапање статистика перформанси</string> + <string name="stats_overlay_options_description">Конфигуришите које се информације приказују у прекривању статистике перформанси</string> + <string name="show_fps">Прикажи фпс</string> + <string name="show_fps_description">Прикажите тренутне оквире у секунди</string> + <string name="show_frametime">Показати фраметиме</string> + <string name="show_frametime_description">Прикажи тренутни фрамети</string> + <string name="show_speed">Брзина показати</string> + <string name="show_speed_description">Прикажи проценат брзине тренутне емулације</string> + <string name="show_app_ram_usage">Прикажи употребу апликације меморије</string> + <string name="show_app_ram_usage_description">Прикажи количину РАМ-а Емулатор користи</string> + <string name="show_system_ram_usage">Прикажи употребу система меморије</string> + <string name="show_system_ram_usage_description">Прикажите износ РАМ-а који користи систем</string> + <string name="show_bat_temperature">Прикажи температуру батерије</string> + <string name="show_bat_temperature_description">Прикажи тренутну температуру батерије у Целзијусу и Фахренхеиту</string> + <string name="show_shaders_building">Прикажи Схадерс Буилдинг</string> + <string name="show_shaders_building_description">Прикажи тренутни број саграђених Схадера</string> + <string name="overlay_position">Положај прекривања</string> + <string name="overlay_position_description">Одаберите где се приказује прекривање на екрану</string> + <string name="overlay_position_top_left">Горњи леви</string> + <string name="overlay_position_top_right">Горњи десни</string> + <string name="overlay_position_bottom_left">Доњи леви</string> + <string name="overlay_position_bottom_right">Дно десно</string> + <string name="overlay_position_center_top">Центар на врху</string> + <string name="overlay_position_center_bottom">Доњи део центра</string> + <string name="perf_overlay_background">Позадина прекривања</string> + <string name="perf_overlay_background_description">Додаје позадину иза прекривања за лакше читање</string> + + <!-- Device Overlay settings --> + <string name="show_soc_overlay">Прикажи прекривање информација о уређају</string> + <string name="enable_soc_overlay">Омогући прекривање уређаја</string> + <string name="soc_overlay_options">Прекривање уређаја</string> + <string name="soc_overlay_options_description">Конфигуришите које се информације приказују у прекривању уређаја</string> + + <string name="show_device_model">Прикажи модел уређаја</string> + <string name="show_device_model_description">Прикажите модел уређаја за хост</string> + <string name="show_gpu_model">Прикажи ГПУ модел</string> + <string name="show_gpu_model_description">Прикажите модел ГПУ-а у хосту</string> + <string name="show_soc_model">Прикажи соц модел</string> + <string name="show_soc_model_description">Прикажите модел домаћина СоЦ</string> + + <!-- Eden`s Veil --> + <string name="eden_veil">Еденова аукција</string> + <string name="eden_veil_description">Експериментална подешавања за побољшање перформанси и способности. Ова подешавања могу изазвати црне екране или друге проблеме у игри.</string> + <string name="eden_veil_warning_title">Експериментална подешавања</string> + <string name="eden_veil_warning_description">Подешавања која се налазе у оквиру ЕДЕН-ова вела су високо експериментални и могу проузроковати проблеме. Ако се ваша игра не покрене, онемогућите било какве проширења и промените продужено динамичко стање на 0.</string> + + <string name="veil_extensions">ГПУ екстензије</string> + <string name="dyna_state">Проширена динамичка држава</string> + <string name="dyna_state_description">Омогућава функције Вулкана да побољшају перформансе, приказивање и уштеде ресурсе за креирање на цевоводама, задржавајући нижи ЦПУ / ГПУ коришћење. Ови продужеци могу повећати температуру уређаја и ГПУ који припадају старијој А6кк линији не могу правилно реаговати. Подесите на 0 да бисте користили заоставштине емулираних формата.</string> + <string name="provoking_vertex">Провоцирајући врх</string> + <string name="provoking_vertex_description">Побољшава осветљење и вертификат руковања у одређеним играма. Подржан само на Вулкану 1.0+ ГПУ-у.</string> + <string name="descriptor_indexing">Индексирање дескриптора</string> + <string name="descriptor_indexing_description">Побољшава текстуру и руковање међуспремника, као и преводилачки слој Маквелл. Подржани од стране неких Вулкана 1.1 ГПУ-а и сви Вулкан 1.2+ ГПУ.</string> + + <string name="veil_renderer">Рендерер</string> + <string name="frame_interpolation">Побољшани оквирни пејсинг</string> + <string name="frame_interpolation_description">Осигурава глатку и доследан испоруку оквира синхронизацијом времена између оквира, смањење муцања и неуједначене анимације. Идеално за игре које доживљавају временски оквир нестабилност или микро-штитнике током играња.</string> + <string name="use_auto_stub">Користите ауто-стуб</string> + <string name="use_auto_stub_description">Аутоматски угушите мрежне услуге и функције. То може побољшати компатибилност, али може проузроковати пад рушења и питања стабилности.</string> + <string name="uninstall_firmware">Деинсталирајте фирмвер</string> + <string name="uninstall_firmware_description">Деинсталирање фирмвера ће га уклонити са уређаја и може утицати на компатибилност игара.</string> + <string name="firmware_uninstalling">Деинсталирање фирмвера</string> + <string name="firmware_uninstalled_success">Фирмваре је успешно деинсталирано</string> + + <string name="veil_misc">Остало</string> + <string name="use_sync_core">Синхронизујте брзину језгре</string> + <string name="use_sync_core_description">Синхронизујте језгро Тицк Брзина брзине максималне брзине да бисте побољшали перформансе без измене стварне брзине игре.</string> + <string name="use_lru_cache">Омогући ЛРУ кеш</string> + <string name="use_lru_cache_description">Омогућите или онемогућите најмање недавно коришћене (ЛРУ) кеш меморије, све веће перформансе штедећи употребу процеса ЦПУ-а. Неке игре са њом имају проблем, посебно тотк 1.2.1, тако да онемогућите ако се игра не покрене или се руши насумично.</string> + <string name="use_fast_cpu_time">Брзо време ЦПУ-а</string> + <string name="use_fast_cpu_time_description">Присиљава опомена ЦПУ-а како би се покренуло на вишим сатима, смањујући одређене ФПС ограничења. Ова опција је хакира и може проузроковати проблеме и слабији ЦПУ-ови могу видети смањене перформансе.</string> + <string name="fast_cpu_time">ЦПУ сат</string> + <string name="fast_cpu_time_description">Користите појачање (1700МХз) да бисте се покренули на највишим матичним тактом Свитцх \ 2000МХз (2000МХз) да бисте покренули на 2к сату.</string> + <string name="memory_layout">Изглед меморије</string> + <string name="memory_layout_description">(Експериментално) Промените изглед емулираног меморије. Ово постављање неће повећати перформансе, али може помоћи у играма које користе високе резолуције путем модова. Не користите на телефонима са 8 ГБ РАМ-а или мање. Ради само на динамичком (ЈИТ) бацкенд-у.</string> + + <!-- Shader Backend --> + <string name="shader_backend">Схадер Бацкенд</string> + <string name="shader_backend_description">Изаберите како се сјеначици саставе и преведете за ваш ГПУ.</string> + <string name="shader_backend_glsl">Универзалан</string> + <string name="shader_backend_glasm">Мали</string> + <string name="shader_backend_spirv">Адрено / Ксцлипсе</string> + + <!-- NVDEC Emulation --> + <string name="nvdec_emulation">НВДЕЦ Емулација</string> + <string name="nvdec_emulation_description">Изаберите како се видео декодирање (НВДЕЦ) обрађује током секс и увозних интросија.</string> + <string name="nvdec_emulation_cpu">ЦПУ</string> + <string name="nvdec_emulation_gpu">ГПУ</string> + <string name="nvdec_emulation_none">Ниједан</string> + + <!-- Optimize SPIRV output --> + <string name="never">Невер</string> + <string name="on_load">На оптерећење</string> + <string name="always">Увек</string> + + <!-- Multiplayer --> + <string name="multiplayer">Мултиплаиер</string> + <string name="multiplayer_description">Домаћин сопствене собе за играње или се придружите постојећем којој ћете се играти са људима</string> + <string name="multiplayer_room_title">Соба:%1$s</string> + <string name="multiplayer_console_id">ИД конзоле:%1$s</string> + <string name="multiplayer_create_room">Створити</string> + <string name="multiplayer_join_room">Придружити се</string> + <string name="multiplayer_public_room">Прегледајте јавне собе</string> + <string name="multiplayer_username">Корисничко име</string> + <string name="multiplayer_ip_address">ИП адреса</string> + <string name="multiplayer_ip_port">Лука</string> + <string name="multiplayer_create_room_success">Соба је успешно створена!</string> + <string name="multiplayer_join_room_success">Успешно се придружите собу!</string> + <string name="multiplayer_create_room_failed">Није успело креирање собе!</string> + <string name="multiplayer_join_room_failed">Није успело да се придружи соби!</string> + <string name="multiplayer_port_invalid">Неважећи порт!</string> + <string name="multiplayer_exit_room">Излазна соба</string> + <string name="multiplayer_network_error">Грешка у мрежи</string> + <string name="multiplayer_lost_connection">Изгубљена веза</string> + <string name="multiplayer_name_collision">Судар имена</string> + <string name="multiplayer_mac_collision">Мац судар</string> + <string name="multiplayer_console_id_collision">Судар конзоле</string> + <string name="multiplayer_wrong_version">Погрешна верзија</string> + <string name="multiplayer_wrong_password">Погрешна лозинка</string> + <string name="multiplayer_could_not_connect">Није се могао повезати</string> + <string name="multiplayer_room_is_full">Соба је пуна</string> + <string name="multiplayer_host_banned">Домаћин забрањен</string> + <string name="multiplayer_permission_denied">Дозвола одбијена</string> + <string name="multiplayer_no_such_user">Нема таквог корисника</string> + <string name="multiplayer_already_in_room">Већ у соби</string> + <string name="multiplayer_create_room_error">Креирајте грешку у соби</string> + <string name="multiplayer_host_kicked">Шутирао домаћин</string> + <string name="multiplayer_unknown_error">непозната грешка</string> + <string name="multiplayer_room_uninitialized">Соба неиницијализована</string> + <string name="multiplayer_room_idle">Собни у празном ходу</string> + <string name="multiplayer_room_joining">Спајање собе</string> + <string name="multiplayer_room_joined">Соба се придружила</string> + <string name="multiplayer_room_moderator">Модератор соба</string> + <string name="multiplayer_member_join">%1$s Придружен</string> + <string name="multiplayer_member_leave">%1$s лево</string> + <string name="multiplayer_member_kicked">%1$s шутран</string> + <string name="multiplayer_member_banned">%1$s забрањено</string> + <string name="multiplayer_address_unbanned">адреса необјављена</string> + <string name="multiplayer_kick_member">Избити</string> + <string name="multiplayer_chat_input_hint">Пошаљите поруке ......</string> + <string name="multiplayer_password">Лозинка</string> + <string name="multiplayer_room_name">Име собе</string> + <string name="multiplayer_room_name_invalid">Име собе мора бити између 3 и 20 знакова</string> + <string name="multiplayer_max_players">Максимални играчи (16)</string> + <string name="multiplayer_max_players_value">МАКС ИГРАЧИ:%d</string> + <string name="multiplayer_chat">Разговарати</string> + <string name="multiplayer_more_options">Више опција</string> + <string name="multiplayer_ip_copied">ИП адреса копирана у међуспремник</string> + <string name="multiplayer_server_address">Адреса сервера</string> + <string name="chat">Разговарати</string> + <string name="type_message">Унесите поруку ......</string> + <string name="send">Пошаљи</string> + <string name="send_message">Пошаљи поруку</string> + <string name="multiplayer_moderation">Умјереност</string> + <string name="multiplayer_moderation_title">Списак</string> + <string name="multiplayer_no_bans">Нема забрањених корисника</string> + <string name="multiplayer_unban_title">Унбан корисник</string> + <string name="multiplayer_unban">Унбанкати</string> + <string name="multiplayer_unban_message">Јесте ли сигурни да желите да убаците%1$s?</string> + <string name="multiplayer_ban">Корисник забране</string> + <string name="multiplayer_room_browser">Јавне собе</string> + <string name="multiplayer_no_rooms_found">Није пронађена ниједна јавне собе</string> + <string name="multiplayer_password_required">Потребна лозинка</string> + <string name="multiplayer_player_count">:%1$d/%2$d</string> + <string name="multiplayer_game">Игра</string> + <string name="multiplayer_no_game_info">Било која игра</string> + <string name="multiplayer_password_protected">Заштићена соба заштићена лозинком</string> + <string name="multiplayer_hide_full_rooms">Сакриј пуне собе</string> + <string name="multiplayer_hide_empty_rooms">Сакриј празне собе</string> + <string name="multiplayer_tap_refresh_to_check_again">Куцните на освежавање да бисте поново проверили</string> + <string name="multiplayer_search_public_lobbies">Претражите лобиес ...</string> + <string name="emulation_multiplayer">Мултиплаиер</string> + <string name="multiplayer_game_name">Префериране игре</string> + <string name="multiplayer_preferred_game_name">Преферирана игра</string> + <string name="multiplayer_no_game">Није пронађена није пронађена игара</string> + <string name="multiplayer_preferred_game_name_invalid">Морате одабрати преферирану игру да бисте угостили собу.</string> + <string name="cancel">Отказати</string> + <string name="ok">У реду</string> + <string name="refresh">Освежити</string> + <string name="room_list">Листа соба</string> + + <!-- Setup strings --> + <string name="welcome">Добродошли!</string> + <string name="welcome_description">Научите како се подесити и <b>Еден</b> скочи у емулацију.</string> + <string name="get_started">Започните</string> + <string name="keys">Тастери</string> + <string name="keys_description">Изаберите свој и лт; <b>prod.keys</b> датотека са дугметом испод.</string> + <string name="select_keys">Изаберите тастере</string> + <string name="games">Игре</string> + <string name="games_description">Изаберите свој и игре и ЛТ; Б> мапу са дугметом испод.</string> + <string name="done">Доношен</string> + <string name="done_description">Све је постављено. \ Нењои своје игре!</string> + <string name="text_continue">Настављати</string> + <string name="next">Следећи</string> + <string name="back">Назад</string> + <string name="add_games">Додајте игре</string> + <string name="add_games_description">Одаберите фасциклу за игре</string> + <string name="step_complete">Довршите!</string> + + <!-- Home strings --> + <string name="alphabetical">Азбучни</string> + <string name="view_list">Списак</string> + <string name="view_grid">Мрежа</string> + <string name="folder">Мапа</string> + <string name="pre_alpha_warning_title">Пре-Алпха софтвер</string> + <string name="pre_alpha_warning_description">"УПОЗОРЕЊЕ: Овај софтвер је у пре-алфа фази и може имати грешке и непотпуне примене функција."</string> + <string name="dont_show_again">Не покажи поново</string> + <string name="pre_alpha_warning">Пре-Алпха софтвер</string> + <string name="add_directory_success">"Нова играчка игара је успешно додата"</string> + <string name="home_games">Игре</string> + <string name="home_search">Тражити</string> + <string name="home_settings">Подешавања</string> + <string name="empty_gamelist">Није пронађена ниједна датотека или се још није одабрала директоријум игара.</string> + <string name="search_and_filter_games">Игре за претрагу и филтрирање</string> + <string name="select_games_folder">Изаберите Фолдер игре</string> + <string name="manage_game_folders">Управљајте мапама игара</string> + <string name="select_games_folder_description">Омогућује ЕДЕН-у да попуни листу игара</string> + <string name="add_games_warning">Прескочите одабир мапе игре?</string> + <string name="add_games_warning_description">Игре побеђују \ "т приказују се на листи игара ако је изабрана мапа н \" т.</string> + <string name="home_search_games">Претражите игре</string> + <string name="search_settings">Подешавања претраге</string> + <string name="games_dir_selected">Изабрани Игре Именик</string> + <string name="install_prod_keys">Инсталирајте prod.keys</string> + <string name="install_prod_keys_description">Потребан за дешифровање малопродајних игара</string> + <string name="install_prod_keys_warning">Прескочите додавање тастера?</string> + <string name="install_prod_keys_warning_description">Потребни су важећи тастери за опонашање малопродајних игара. Само хомебрев апликације ће функционисати ако наставите.</string> + <string name="notifications">Обавештења</string> + <string name="notifications_description">Одобрите дозволу за обавештење дугметом испод.</string> + <string name="give_permission">Дозвола за одобрење</string> + <string name="notification_warning">Прескочите давање дозволе за обавештавање?</string> + <string name="notification_warning_description">Еден је победио да вас може обавестити о важним информацијама.</string> + <string name="permission_denied">Дозвола одбијена</string> + <string name="permission_denied_description">Превише пута сте негирали ову дозволу и сада морате да га ручно одобри у системским подешавањима.</string> + <string name="about">О томе</string> + <string name="about_description">Изградите верзију, кредите и још много тога</string> + <string name="warning_help">Помоћи</string> + <string name="warning_skip">Прескочити</string> + <string name="warning_cancel">Отказати</string> + <string name="install_amiibo_keys">Инсталирајте Амиибо Кеис</string> + <string name="install_amiibo_keys_description">Потребно је користити Амиибо у игри</string> + <string name="invalid_keys_file">Изабрана је неважећа датотека тастера</string> + <string name="install_keys_success">Кључеви су успешно инсталирани</string> + <string name="reading_keys_failure">Грешка приликом чишћења кључева за шифровање</string> + <string name="install_prod_keys_failure_extension_description">Проверите да датотека кључева има. Екеис Ектенсион и покушајте поново.</string> + <string name="install_amiibo_keys_failure_extension_description">Проверите да датотека кључева има .бин екстензију и покушајте поново.</string> + <string name="invalid_keys_error">Неважеће кључеве за шифровање</string> + <string name="install_keys_failure_description">Изабрана датотека је нетачна или оштећена. Молим вас да вам умањите кључеве.</string> + <string name="gpu_driver_fetcher">ГПУ возач фетцхер</string> + <string name="gpu_driver_manager">ГПУ управљач возача</string> + <string name="install_gpu_driver">Инсталирајте ГПУ драјвер</string> + <string name="install_gpu_driver_description">Инсталирајте алтернативне управљачке програме за потенцијално боље перформансе или тачност</string> + <string name="advanced_settings">Напредна подешавања</string> + <string name="advanced_settings_game">Напредна подешавања:%1$s</string> + <string name="settings_description">Конфигуришите подешавања емулатора</string> + <string name="search_recently_played">Недавно одигран</string> + <string name="search_recently_added">Недавно је додато</string> + <string name="search_retail">Малопродаја</string> + <string name="search_homebrew">Домаћинство</string> + <string name="open_user_folder">Отворите директоријум ЕДЕН</string> + <string name="open_user_folder_description">Управљајте унутрашњим датотекама ЕДЕН-а</string> + <string name="theme_and_color_description">Измените изглед апликације</string> + <string name="no_file_manager">Није пронађен ниједан управник датотека</string> + <string name="notification_no_directory_link">Није могуће отворити директоријум ЕДЕН-а</string> + <string name="notification_no_directory_link_description">Молимо пронађите корисничку мапу са бочним панелом датотека менаџера и ручно.</string> + <string name="manage_save_data">Управљајте сачувајте податке</string> + <string name="manage_save_data_description">Спреми податке. Изаберите опцију испод.</string> + <string name="import_save_warning">Увоз сачувај податке</string> + <string name="import_save_warning_description">Ово ће пребрисати све постојеће сачувајте податке са испорученом датотеком. Јесте ли сигурни да желите да наставите?</string> + <string name="import_export_saves_description">Увоз или извоз сачувајте датотеке</string> + <string name="save_files_importing">Увоз сачуваних датотека ...</string> + <string name="save_files_exporting">Извоз сачувања датотека ...</string> + <string name="save_file_imported_success">Увезено је успешно</string> + <string name="save_file_invalid_zip_structure">Неважеће структуре директорија сачувања</string> + <string name="save_file_invalid_zip_structure_description">Прво име подмагера мора бити ИД наслова игре.</string> + <string name="import_saves">Увоз</string> + <string name="export_saves">Извоз</string> + <string name="install_firmware">Инсталирајте фирмвер</string> + <string name="install_firmware_description">Фирмваре мора бити у ЗИП архиви и потребно је за покретање неких игара</string> + <string name="firmware_installing">Инсталирање фирмвера</string> + <string name="firmware_installed_success">Фирмвер је успешно инсталиран</string> + <string name="firmware_installed_failure">Инсталација фирмвера није успела</string> + <string name="firmware_installed_failure_description">Проверите да ли су ФИРВВАРЕ НЦА датотеке у корену Зип и покушавају поново.</string> + <string name="firmware_uninstalled_failure">Деинсталација фирмвера није успела</string> + <string name="share_log">Дели се за уклањање погрешака</string> + <string name="share_log_description">Поделите ЕДЕН \ е-е датотеку дневника у проблематику за уклањање погрешака</string> + <string name="share_log_missing">Није пронађена датотека дневника</string> + <string name="install_game_content">Инсталирајте садржај игре</string> + <string name="install_game_content_description">Инсталирајте исправке игре или ДЛЦ</string> + <string name="installing_game_content">Инсталирање садржаја ...</string> + <string name="install_game_content_failure">Грешка у инсталирању датотека (а) у НАНД</string> + <string name="install_game_content_failure_description">Молимо вас да осигурате да су садржаји валидни и да је инсталирана датотека prod.keys.</string> + <string name="install_game_content_failure_base">Инсталација основних игара није дозвољено да се избегне могући сукоби.</string> + <string name="install_game_content_failure_file_extension">Подржани су само НСП и КСЦИ садржај. Проверите да ли постоје садржаји игара валидни.</string> + <string name="install_game_content_failed_count">%1$d Грешка у инсталацији</string> + <string name="install_game_content_success">Садржај игре (и) инсталирани су успешно</string> + <string name="install_game_content_success_install">%1$d инсталирано успешно</string> + <string name="install_game_content_success_overwrite">%1$d преписано успешно</string> + <string name="custom_driver_not_supported">Прилагођени управљачки програми нису подржани</string> + <string name="custom_driver_not_supported_description">Прилагођени управљачки програм за учитавање ИСН \ "т тренутно подржан за овај уређај. \ НХЕЦК Поново у будућности да бисте видели да ли је додата подршка!</string> + <string name="manage_yuzu_data">Управљајте подацима ЕДЕН-а</string> + <string name="manage_yuzu_data_description">Увоз / извозни софтвер, кључеви, кориснички подаци и још много тога!</string> + <string name="share_save_file">Схаре Спреми датотеку</string> + <string name="export_save_failed">Није успело извоз уштеде</string> + <string name="game_folders">Мапе игре</string> + <string name="deep_scan">Дубоко скенирање</string> + <string name="add_game_folder">Додајте мапу игара</string> + <string name="folder_already_added">Ова мапа је већ додата!</string> + <string name="game_folder_properties">Играње некретнина</string> + <plurals name="saves_import_failed" tools:ignore="MissingQuantity"> + <item quantity="one" tools:ignore="ImpliedQuantity">Увоз%d уштедите%d</item> + <item quantity="other">Увоз%d уносе%d</item> + </plurals> + <plurals name="saves_import_success" tools:ignore="MissingQuantity"> + <item quantity="one" tools:ignore="ImpliedQuantity">Успешно увезено%d уштеда</item> + <item quantity="other">Успешно увезени%d штеди</item> + </plurals> + <string name="no_save_data_found">Не пронађени су подаци сачувања</string> + <string name="verify_installed_content">Проверите инсталирани садржај</string> + <string name="verify_installed_content_description">Проверава све инсталиране садржаје за корупцију</string> + <string name="keys_missing">Шифра о шифрирањима недостају</string> + <string name="keys_missing_description">Фирмваре и малопродајне игре не могу се дешифровати</string> + + <!-- Applet launcher strings --> + <string name="qlaunch_applet">Клаунцх</string> + <string name="qlaunch_description">Покрените апликације са почетног екрана система</string> + <string name="applets">Апплет бацач</string> + <string name="applets_description">Покрените системске аплете помоћу инсталираног фирмвера</string> + <string name="applets_error_firmware">Фирмваре није инсталиран</string> + <string name="applets_error_applet">Апплет није доступан</string> + <string name="applets_error_description">prod.keys</string> + <string name="album_applet">Албум</string> + <string name="album_applet_description">Погледајте слике сачуване у фасцикли заслона корисника са системом Гледач фотографија</string> + <string name="mii_edit_applet">Хиљаде Едит</string> + <string name="mii_edit_applet_description">Прегледајте и уредите МИИС са системом уреднику</string> + <string name="cabinet_applet">Кабинет</string> + <string name="cabinet_applet_description">Уређивање и брисање података похрањених на Амиибо-у</string> + <string name="cabinet_launcher">Бацач ормара</string> + <string name="cabinet_nickname_and_owner">Подешавања надимак и власника</string> + <string name="cabinet_game_data_eraser">Брисање података о игри</string> + <string name="cabinet_restorer">Рестауратор</string> + <string name="cabinet_formatter">Форматтер</string> + + <!-- About screen strings --> + <string name="gaia_is_not_real">Гаиа није стварно</string> + <string name="copied_to_clipboard">Копирано у међуспремник</string> + <string name="about_app_description">Емулатор прекидача отвореног кода</string> + <string name="contributors">Сарадници</string> + <string name="contributors_description">Сарадници који су омогућили ЕДЕН за Андроид</string> + <string name="licenses_description">Пројекти који омогућавају ЕДЕН за Андроид</string> + <string name="build">Градити</string> + <string name="user_data">Кориснички подаци</string> + <string name="user_data_description">Увоз / извоз свих података апликација. \ Н \ н када увози податке о кориснику, сви постојећи кориснички подаци биће избрисани! \ Н \ НИМПОРТИНГ Подаци из ЦИТРОН-а могу проузроковати проблеме. Препоручује се ручно увоз свих потребних података.</string> + <string name="exporting_user_data">Извоз података корисника ...</string> + <string name="importing_user_data">Увоз података корисника ...</string> + <string name="import_user_data">Увоз корисничких података</string> + <string name="invalid_yuzu_backup">Неважећа резервна копија ЕДЕН-а</string> + <string name="user_data_export_success">Кориснички подаци су успешно извезени</string> + <string name="user_data_import_success">Корисничке податке увезене успешно</string> + <string name="user_data_export_cancelled">Извоз је отказан</string> + <string name="user_data_import_failed_description">Проверите да ли су мапе корисничких података у корену зип мапе и садрже конфигурацију датотека на Цонфиг / Цонфиг.ини и покушајте поново.</string> + + <!-- General settings strings --> + <string name="frame_limit_enable">Ограничити брзину</string> + <string name="frame_limit_enable_description">Ограничава брзину емулације на одређени проценат нормалне брзине.</string> + <string name="frame_limit_slider">Ограничите проценат брзине</string> + <string name="frame_limit_slider_description">Одређује проценат за ограничавање брзине емулације. 100% је нормална брзина. Вредности веће или ниже повећаће се или смањити ограничење брзине.</string> + <string name="cpu_backend">ЦПУ Бацкенд</string> + <string name="cpu_accuracy">ЦПУ тачност</string> + <string name="value_with_units">%1$s%2$s</string> + + <!-- System settings strings --> + <string name="device_name">Име уређаја</string> + <string name="use_docked_mode">Прикључен режим</string> + <string name="use_docked_mode_description">Повећава резолуцију, смањење перформанси. Ручни режим се користи када је онемогућено, снижавање резолуције и све веће перформансе.</string> + <string name="emulated_region">Емулирана регија</string> + <string name="emulated_language">Емулирани језик</string> + <string name="select_rtc_date">Изаберите РТЦ дате</string> + <string name="select_rtc_time">Изаберите РТЦ време</string> + <string name="use_custom_rtc">Цустом РТЦ</string> + <string name="use_custom_rtc_description">Омогућава вам да поставите прилагођени сат у реалном времену одвојено од тренутног времена система.</string> + <string name="set_custom_rtc">Подесите прилагођени РТЦ</string> + + <!-- Network settings strings --> + <string name="web_token">Веб токен</string> + <string name="web_token_description">Веб токен који се користи за стварање јавних лобија. То је низ од 48 знакова који садржи само мала слова А-З.</string> + <string name="network">Мрежа</string> + + <!-- Graphics settings strings --> + <string name="frame_skipping">ВИП: Фрамескип</string> + <string name="frame_skipping_description">Пребацивање оквира прескакање да бисте побољшали перформансе смањењем броја пружених оквира. Ова функција се и даље ради и биће омогућена у будућим издањима.</string> + <string name="renderer_accuracy">Ниво тачности</string> + <string name="renderer_resolution">Резолуција (ручно / прикључено)</string> + <string name="renderer_vsync">Всинц мод</string> + <string name="renderer_screen_layout">Оријентација</string> + <string name="renderer_aspect_ratio">Омјер аспекта</string> + <string name="renderer_scaling_filter">Филтер прилагођавања прозора</string> + <string name="fsr_sharpness">ФСР оштрина</string> + <string name="fsr_sharpness_description">Одређује колико ће се слика наоштрен трајати док користи ФСР \ "динамички контраст</string> + <string name="renderer_anti_aliasing">Метода против алиасирања</string> + <string name="renderer_force_max_clock">Формирајте максималне сатове (само адрено)</string> + <string name="renderer_force_max_clock_description">Снагује ГПУ да се покреће максималним могућим сатовима (топлотна ограничења ће се и даље примењивати).</string> + <string name="renderer_optimize_spirv_output">Оптимизирајте Спирев излаз</string> + <string name="renderer_optimize_spirv_output_description">Оптимизира састављена схадер за побољшање ефикасности ГПУ-а.</string> + <string name="renderer_asynchronous_shaders">Користите асинхроне схадера</string> + <string name="renderer_asynchronous_shaders_description">Саставља схадера асинхроно. Ово може умањити затезаче, али могу такође да уведе пропусте.</string> + <string name="use_fast_gpu_time">Користите брзи ГПУ време</string> + <string name="use_fast_gpu_time_description">Присиљава већину игара да се покрене у својој највишој родној резолуцији. Ова опција је хаковна и може проузроковати проблеме.</string> + <string name="fast_gpu_time">ГПУ оверклок фактора</string> + <string name="fast_gpu_time_description">Користите 128 за максималне перформансе и 512 за максималну верност графике.</string> + <string name="renderer_reactive_flushing">Користите реактивно испирање</string> + <string name="renderer_reactive_flushing_description">Побољшава тачност приказивања у неким играма по цени перформанси.</string> + <string name="use_disk_shader_cache">Предмеморија сјена диска</string> + <string name="use_disk_shader_cache_description">Смањује муцање локалном чувањем и учитавањем генерисаних схадера.</string> + <string name="anisotropic_filtering">Анисотропни филтрирање</string> + <string name="anisotropic_filtering_description">Побољшава квалитет текстура када се посматра у косим угловима</string> + + <!-- Debug settings strings --> + <string name="cpu">ЦПУ</string> + <string name="cpu_debug_mode">ЦПУ уклањање погрешака</string> + <string name="cpu_debug_mode_description">Ставља ЦПУ у спор режим уклањања погрешака.</string> + + <string name="gpu">ГПУ</string> + <string name="renderer_api">АПИ</string> + <string name="renderer_debug">Графички уклањање погрешака</string> + <string name="renderer_debug_description">Поставља графички АПИ на спорни режим уклањања погрешака.</string> + <string name="fastmem">Брбљање</string> + + <string name="log">Сечеља</string> + <string name="flush_by_line">Записници за уклањање погрешака по линији</string> + <string name="flush_by_line_description">Испушта ознаке за уклањање погрешака на сваком писменом линијском линији, олакшавање уклањања погрешака у случајевима пада или замрзавања.</string> + + <!-- Audio settings strings --> + <string name="audio_output_engine">Излазни мотор</string> + <string name="audio_volume">Запремина</string> + <string name="audio_volume_description">Одређује обим аудио излаза.</string> + + <!-- Input strings --> + <string name="buttons">Тастери</string> + <string name="button_a">А</string> + <string name="button_b">Б</string> + <string name="button_x">К</string> + <string name="button_y">И</string> + <string name="button_plus">Плус</string> + <string name="button_minus">Минус</string> + <string name="button_home">Дом</string> + <string name="button_capture">Заробити</string> + <string name="start_pause">Почетак / пауза</string> + <string name="dpad">Д-пад</string> + <string name="up">Уп</string> + <string name="down">Надиграти се</string> + <string name="left">Лево</string> + <string name="right">У праву</string> + <string name="left_stick">Левичар</string> + <string name="control_stick">Контролни штап</string> + <string name="right_stick">Десни штап</string> + <string name="c_stick">Ц-стицк</string> + <string name="pressed">Пресован</string> + <string name="range">Домет</string> + <string name="deadzone">Деадзоне</string> + <string name="modifier">Модификатор</string> + <string name="modifier_range">Распон модификатора</string> + <string name="triggers">Окидачи</string> + <string name="button_l">Л</string> + <string name="button_r">Р</string> + <string name="button_zl">Зл</string> + <string name="button_zr">Зр</string> + <string name="button_sl_left">Лефт сл</string> + <string name="button_sr_left">Лефт ср</string> + <string name="button_sl_right">Десни сл</string> + <string name="button_sr_right">Десно ср</string> + <string name="button_z">З</string> + <string name="invalid">Неважећи</string> + <string name="not_set">Није постављен</string> + <string name="unknown">Непознат</string> + <string name="qualified_hat">%1$s%2$s%3$sХАТ%4$s</string> + <string name="qualified_button">%1$s%2$s%3$sБУТТОН%4$s</string> + <string name="qualified_axis">АКСИС%1$s%2$s</string> + <string name="unused">Неискориштен</string> + <string name="input_prompt">Померите се или притисните улаз</string> + <string name="unsupported_input">Неподржани тип улаза</string> + <string name="input_mapping_filter">Филтер за мапирање уноса</string> + <string name="input_mapping_filter_description">Изаберите уређај за филтрирање уноса мапирања</string> + <string name="auto_map">Ауто-мапа контролер</string> + <string name="auto_map_description">Изаберите уређај да бисте покушали аутоматско мапирање</string> + <string name="attempted_auto_map">Покушај аутоматске мапе са%1$s</string> + <string name="controller_type">Врста контролера</string> + <string name="pro_controller">Про контролер</string> + <string name="handheld">Ручни</string> + <string name="dual_joycons">Дуал Јоицонс</string> + <string name="left_joycon">Леви ЈоиЦон</string> + <string name="right_joycon">Десни радовицон</string> + <string name="gamecube_controller">Гамецубе контролер</string> + <string name="invert_axis">Преокрет</string> + <string name="invert_button">Дугме за уметност</string> + <string name="toggle_button">Дугме за пребацивање</string> + <string name="turbo_button">Турбо дугме</string> + <string name="set_threshold">Подесити праг</string> + <string name="toggle_axis">Пребацивање оси</string> + <string name="connected">Повезан</string> + <string name="use_system_vibrator">Користите системски вибратор</string> + <string name="input_overlay">Улазно прекривање</string> + <string name="vibration">Вибрација</string> + <string name="vibration_strength">Снага вибрација</string> + <string name="profile">Профил</string> + <string name="create_new_profile">Креирајте нови профил</string> + <string name="enter_profile_name">Унесите име профила</string> + <string name="profile_name_already_exists">Име профила већ постоји</string> + <string name="invalid_profile_name">Неважеће име профила</string> + <string name="use_global_input_configuration">Користите глобалну улазну конфигурацију</string> + <string name="player_num_profile">Играч%d Профил</string> + <string name="delete_input_profile">Избриши унос профила</string> + <string name="delete_input_profile_description">Јесте ли сигурни да желите да избришете овај профил? Ово се не може надокнадити.</string> + <string name="stick_map_description">Померите штап улево и затим горе или притисните дугме</string> + <string name="button_map_description">Притисните дугме или преместите окидач / штап</string> + <string name="map_dpad_direction">Мапа на Д-Пад%1$s</string> + <string name="map_control">МАП до%1$s</string> + <string name="failed_to_load_profile">Профил за учитавање није успело</string> + <string name="failed_to_save_profile">Није успело да уштедите профил</string> + <string name="reset_mapping">Ресетујте мапирање</string> + <string name="reset_mapping_description">Јесте ли сигурни да желите да ресетујете све пресликавања за овај контролер за подразумевано? То се не може поништити.</string> + + <!-- Miscellaneous --> + <string name="slider_default">Подразумевано</string> + <string name="ini_saved">Сачувана подешавања</string> + <string name="gameid_saved">Сачувана подешавања за%1$s</string> + <string name="error_saving">Грешка уштеде%1$s.ини:%2$s</string> + <string name="unimplemented_menu">Једностављени мени</string> + <string name="loading">Учитавање ...</string> + <string name="shutting_down">Искључивање ...</string> + <string name="reset_setting_confirmation">Да ли желите да ресетујете ово постављање назад на задану вредност?</string> + <string name="reset_to_default">Ресетујте на подразумевано</string> + <string name="reset_to_default_description">Ресетује све напредне поставке</string> + <string name="reset_all_settings">Ресетујте сва подешавања?</string> + <string name="reset_all_settings_description">Све напредна подешавања ће се ресетовати на њихову подразумевану конфигурацију. То се не може поништити.</string> + <string name="settings_reset">Подешавање поставки</string> + <string name="close">Затворити</string> + <string name="learn_more">Сазнајте више</string> + <string name="auto">Аутомат</string> + <string name="submit">Поднети</string> + <string name="string_null">Нула</string> + <string name="string_import">Увоз</string> + <string name="export">Извоз</string> + <string name="export_failed">Извоз није успео</string> + <string name="import_failed">Увоз није успео</string> + <string name="cancelling">Отказивање</string> + <string name="install">Инсталирати</string> + <string name="fetch">Дохватити</string> + <string name="delete">Избрисати</string> + <string name="edit">Уредити</string> + <string name="export_success">Успешно извезено</string> + <string name="start">Почети</string> + <string name="clear">Чист</string> + <string name="global">Глобално</string> + <string name="custom">Обичај</string> + <string name="notice">Обавештење</string> + <string name="import_complete">Увоз комплетан</string> + <string name="more_options">Више опција</string> + <string name="use_global_setting">Користите Глобално подешавање</string> + <string name="operation_completed_successfully">Операција је успешно завршена</string> + <string name="retry">Поново покушати</string> + <string name="confirm">Потврдити</string> + <string name="load">Оптеретити</string> + <string name="save">Сачувати</string> + + <!-- GPU driver installation --> + <string name="select_gpu_driver">Изаберите ГПУ драјвер</string> + <string name="select_gpu_driver_title">Да ли желите да замените свој тренутни ГПУ управљачки програм?</string> + <string name="select_gpu_driver_install">Инсталирати</string> + <string name="select_gpu_driver_default">Подразумевано</string> + <string name="select_gpu_driver_use_default">Коришћење подразумеваног ГПУ драјвера</string> + <string name="select_gpu_driver_error">Изабран је неважећи управљачки програм</string> + <string name="driver_already_installed">Возач је већ инсталиран</string> + <string name="system_gpu_driver">Систем ГПУ драјвер</string> + <string name="installing_driver">Инсталирање управљачког програма ...</string> + + <!-- GPU driver fetcher --> + <string name="fetch_error">Грешка приликом дохваћања</string> + <string name="check_connection">Проверите везу и покушајте поново.</string> + <string name="show_releases">Објаснити издања</string> + <string name="view_full_release_notes">Белешке о ослобађању</string> + <string name="failed_to_fetch">Није успело дохватити</string> + <string name="error_during_fetch">Грешка током дохвата</string> + <string name="toggle_release_notes">Укључите белешке о ослобађању</string> + <string name="downloads">Преузимања</string> + <string name="show_downloads">Схов Довнлоадс</string> + <string name="hide_downloads">Хиде Довнлоадс</string> + <string name="failed_cache_dir">Именик у кеш меморију</string> + <string name="empty_response_body">Празно тело одговора</string> + <string name="successfully_installed">%1$s успешно инсталиран</string> + <string name="driver_failed_title">Инсталација возача није успела</string> + <string name="failed_install_driver">Није успело да се инсталира%1$s управљачки програм, да ли ваш систем подржава?</string> + <string name="driver_empty">Преузето управљачки програм је празан, проверите свој Интернет</string> + <string name="downloading">Преузимање ...</string> + <string name="installing">Инсталирање ...</string> + <string name="latest">Најновији</string> + <string name="recommended_driver">Препоручени управљачки програм:</string> + <string name="gpu_model">ГПУ модел:</string> + <string name="unsupported_gpu">Неподржани ГПУ</string> + <string name="unsupported_gpu_warning">Ваш ГПУ не подржава ињекцију управљачких програма. Покушај постављања прилагођених управљачких програма се не препоручује.</string> + + <!-- Preferences Screen --> + <string name="preferences_settings">Подешавања</string> + <string name="preferences_general">Општи</string> + <string name="preferences_system">Систем</string> + <string name="preferences_system_description">Доцдед мод, регион, језик</string> + <string name="preferences_graphics">Графика</string> + <string name="preferences_graphics_description">Ниво тачности, резолуција, сенчара</string> + <string name="preferences_audio">Аудио</string> + <string name="preferences_audio_description">Излазни мотор, запремина</string> + <string name="preferences_controls">Контрола</string> + <string name="preferences_controls_description">Унос контролера мапе</string> + <string name="preferences_player">Играч%d</string> + <string name="preferences_theme">Тема и боја</string> + <string name="preferences_debug">Ослободити збрајања</string> + <string name="preferences_debug_description">ЦПУ / ГПУ уклањање погрешака, графички АПИ, ФастМем</string> + + <!-- Game properties --> + <string name="info">Информације</string> + <string name="info_description">ИД програма, програмер, верзија</string> + <string name="per_game_settings">Подешавања по игри</string> + <string name="per_game_settings_description">Уређивање поставки специфично за ову игру</string> + <string name="launch_options">Лансирање цонфиг</string> + <string name="path">Стаза</string> + <string name="program_id">ИД програма</string> + <string name="developer">Разводнички</string> + <string name="version">Верзија</string> + <string name="copy_details">Детаљи копирања</string> + <string name="add_ons">Додаци</string> + <string name="add_ons_description">Пребаците модове, исправке и ДЛЦ</string> + <string name="clear_shader_cache">Цлеар Схадер Цацхе</string> + <string name="clear_shader_cache_description">Уклања све сјенадесете изграђене током репродукције ове игре</string> + <string name="clear_shader_cache_warning_description">Доживећете више муцајући док се схадер кеш регенерати</string> + <string name="cleared_shaders_successfully">Успешно очистили схадера</string> + <string name="addons_game">Аддонс:%1$s</string> + <string name="save_data">Сачувајте податке</string> + <string name="save_data_description">Управљајте Спреми податке специфично за ову игру</string> + <string name="delete_save_data">Избришите сачувајте податке</string> + <string name="delete_save_data_description">Уклања све Спреми податке специфичне за ову игру</string> + <string name="delete_save_data_warning_description">Ово неподобно уклања све податке о сачувању ове игре. Јесте ли сигурни да желите да наставите?</string> + <string name="save_data_deleted_successfully">Спремите податке који су успешно избрисани</string> + <string name="select_content_type">Врста садржаја</string> + <string name="updates_and_dlc">Ажурирања и ДЛЦ</string> + <string name="mods_and_cheats">Модови и варалице</string> + <string name="addon_notice">Важна Обавештење о Аддон-у</string> + <!-- "cheats/" "romfs/" and "exefs/ should not be translated --> + <string name="addon_notice_description">Да бисте инсталирали модс и варалице, морате да одаберете мапу која садржи шифре /, Ромфс / или ЕкеФС / директориј. Не можемо да проверимо да ли ће то бити компатибилно са вашом игром, па будите опрезни!</string> + <string name="invalid_directory">Неважећи директориј</string> + <!-- "cheats/" "romfs/" and "exefs/ should not be translated --> + <string name="invalid_directory_description">Молимо вас проверите да ли је име одабрано да садржи варалице /, Ромфс / или ЕкеФС / мапу и покушајте поново.</string> + <string name="addon_installed_successfully">Аддон је успешно инсталиран</string> + <string name="verifying_content">Верификација садржаја ...</string> + <string name="content_install_notice">Обавештење о инсталирању садржаја</string> + <string name="content_install_notice_description">Садржај који сте одабрали не одговара овој игри. Ионако нинсталл?</string> + <string name="confirm_uninstall">Потврдите деинсталирање</string> + <string name="confirm_uninstall_description">Јесте ли сигурни да желите да деинсталирате овај Аддон?</string> + <string name="verify_integrity">Проверите интегритет</string> + <string name="verifying">Провера ...</string> + <string name="verify_success">Провера интегритета је успела!</string> + <string name="verify_failure">Провера интегритета није успела!</string> + <string name="verify_failure_description">Садржај датотеке може бити оштећен</string> + <string name="verify_no_result">Провјера интегритета није могла да се изврши</string> + <string name="verify_no_result_description">Садржај датотеке није проверен за валидност</string> + <string name="verification_failed_for">Верификација није успела за следеће датотеке: \ н%1$s</string> + + <!-- ROM loading errors --> + <string name="loader_error_encrypted">Ваш РОМ је шифровани</string> + <string name="loader_error_encrypted_roms_description">Игра ЦартиДгес</string> + <string name="loader_error_encrypted_keys_description">prod.keys</string> + <string name="loader_error_video_core">Дошло је до грешке приликом иницијализације видео језгра</string> + <string name="loader_error_video_core_description">То обично узрокује некомпатибилни ГПУ драјвер. Инсталирање прилагођеног ГПУ управљачког програма може решити овај проблем.</string> + <string name="loader_error_invalid_format">Није могуће учитати РОМ</string> + <string name="loader_error_file_not_found">РОМ датотека не постоји</string> + + <!-- Emulation Menu --> + <string name="emulation_exit">Излазни емулација</string> + <string name="emulation_done">Доношен</string> + <string name="emulation_fps_counter">Фпс цоунтер</string> + <string name="emulation_thermal_indicator">Термички индикатор</string> + <string name="emulation_toggle_controls">Пребацивање контрола</string> + <string name="emulation_rel_stick_center">Релативни центар за штапић</string> + <string name="emulation_dpad_slide">Д-Пад Слиде</string> + <string name="emulation_haptics">Додирните ХАптицс</string> + <string name="emulation_show_overlay">Приказати прекривање</string> + <string name="emulation_toggle_all">Пребацивати све</string> + <string name="emulation_control_adjust">Подесити прекривање</string> + <string name="emulation_control_scale">Скала</string> + <string name="emulation_control_opacity">Непрозирност</string> + <string name="emulation_touch_overlay_reset">Ресетовање прекривања</string> + <string name="emulation_touch_overlay_edit">Уредити прекривање</string> + <string name="emulation_pause">Паузирање емулација</string> + <string name="emulation_unpause">Непознавање емулације</string> + <string name="emulation_input_overlay">Опције прекривања</string> + <string name="touchscreen">Екран осетљив на додир</string> + <string name="lock_drawer">Ладица за закључавање</string> + <string name="unlock_drawer">Откључај ладицу</string> + + <string name="load_settings">Учитавање поставки ...</string> + + <!-- Software keyboard --> + <string name="software_keyboard">Софтверска тастатура</string> + + <!-- Errors and warnings --> + <string name="abort_button">Престати</string> + <string name="continue_button">Настављати</string> + <string name="system_archive_not_found">Архива система није пронађена</string> + <string name="system_archive_not_found_message">%s недостаје. Молимо вас да избаците системске архиве. \ Нцонтинуинг Емулација може резултирати рушењем и грешкама.</string> + <string name="system_archive_general">Архива система</string> + <string name="save_load_error">Грешка уштеде / учитавање</string> + <string name="fatal_error">Фатална грешка</string> + <string name="fatal_error_message">Дошло је до кобне грешке. Проверите детаље о дневнику. \ НцонТинуинг Емулација може резултирати сударама и грешкама.</string> + <string name="performance_warning">Искључивање ове поставке значајно ће смањити перформансе емулације! За најбоље искуство, препоручује се да поставите ово подешавање омогућено.</string> + <string name="device_memory_inadequate">Уређај РАМ-а:%1$s \ н Рецоммсендед:%2$s</string> + <string name="memory_formatted">%1$s%2$s</string> + <string name="no_game_present">Нема презентације игре за покретање!</string> + + <!-- Region Names --> + <string name="region_japan">Јапан</string> + <string name="region_usa">САД</string> + <string name="region_europe">Европа</string> + <string name="region_australia">Аустралија</string> + <string name="region_china">Кина</string> + <string name="region_korea">Кореја</string> + <string name="region_taiwan">Тајван</string> + + <!-- Memory Layouts --> + <string name="memory_4gb">4ГБ (препоручује се)</string> + <string name="memory_6gb">6ГБ (несигуран)</string> + <string name="memory_8gb">8ГБ (несигурно)</string> + + <!--CPU clock speeds--> + <string name="clock_base">База (1000МХз)</string> + <string name="clock_boost">Боост (1700МХз)</string> + <string name="clock_fast">Брзи (2000МХз)</string> + + <!-- GPU overclock factors --> + <string name="gpu_low">Ниско (128)</string> + <string name="gpu_medium">Средња (256)</string> + <string name="gpu_high">Висок (512)</string> + + <!-- Language Names --> + <string name="language_japanese" translatable="false">日本語</string> + <string name="language_english" translatable="false">English</string> + <string name="language_french" translatable="false">Français</string> + <string name="language_german" translatable="false">Deutsch</string> + <string name="language_italian" translatable="false">Italiano</string> + <string name="language_spanish" translatable="false">Español</string> + <string name="language_chinese" translatable="false">简体中文</string> + <string name="language_korean" translatable="false">한국어</string> + <string name="language_dutch" translatable="false">Nederlands</string> + <string name="language_portuguese" translatable="false">Português</string> + <string name="language_russian" translatable="false">Русский</string> + <string name="language_taiwanese" translatable="false">台湾</string> + <string name="language_british_english" translatable="false">British English</string> + <string name="language_canadian_french" translatable="false">Français canadien</string> + <string name="language_latin_american_spanish" translatable="false">Español latinoamericano</string> + <string name="language_simplified_chinese" translatable="false">简体中文</string> + <string name="language_traditional_chinese" translatable="false">正體中文</string> + <string name="language_brazilian_portuguese" translatable="false">Português do Brasil</string> + + <!-- Memory Sizes --> + <string name="memory_byte">Бајт</string> + <string name="memory_byte_shorthand">Б</string> + <string name="memory_kilobyte">Кб</string> + <string name="memory_megabyte">МБ</string> + <string name="memory_gigabyte">ГБ</string> + <string name="memory_terabyte">Тб</string> + <string name="memory_petabyte">Пб</string> + <string name="memory_exabyte">Еб</string> + + <!-- Renderer APIs --> + <string name="renderer_vulkan">Вулкан</string> + <string name="renderer_none">Ниједан</string> + + <!-- Renderer Accuracy --> + <string name="renderer_accuracy_normal">Нормалан</string> + <string name="renderer_accuracy_high">Високо</string> + <string name="renderer_accuracy_extreme">Екстремни (спор)</string> + + <!-- ASTC Decoding Method --> + <string name="accelerate_astc">АСТЦ метода декодирања</string> + <string name="accelerate_astc_description">Изаберите како су текстуре са компримираним текстовима декодиране за приказивање: ЦПУ (споро, сигуран), ГПУ (Фаст, Препоручи) или ЦПУ АСИНЦ (без затезача, могу да изазове питања)</string> + + <!-- ASTC Decoding Method Choices --> + <string name="accelerate_astc_cpu">ЦПУ</string> + <string name="accelerate_astc_gpu">ГПУ</string> + <string name="accelerate_astc_async">ЦПУ асинхроно</string> + + <!-- ASTC Recompression Method --> + <string name="astc_recompression">Метода рекомпресије АСТЦ-а</string> + <string name="astc_recompression_description">Изаберите како се текстуре АСТЦ препоручују да би се побољшале компатибилност и перформансе. Омогућавање ове опције штеди ВРАМ, али може смањити квалитет текстуре.</string> + + <!-- ASTC Recompression Method Choices --> + <string name="astc_recompression_uncompressed">Некомпримован</string> + <string name="astc_recompression_bc1">БЦ1 (низак квалитет)</string> + <string name="astc_recompression_bc3">БЦ3 (средњи квалитет)</string> + + <!-- ASTC Recompression Method Choices --> + <string name="vram_usage_mode">ВРАМ режим употребе</string> + <string name="vram_usage_mode_description">Контролишите колико агресивно емулатор додељује и ослобађа ГПУ меморију.</string> + <string name="vram_usage_conservative">Конзервативан</string> + <string name="vram_usage_aggressive">Агресиван</string> + + <!-- Resolutions --> + <string name="resolution_quarter">0,25к (180п / 270п)</string> + <string name="resolution_half">0,5к (360п / 540п)</string> + <string name="resolution_three_quarter">0,75к (540п / 810п)</string> + <string name="resolution_one">1к (720п / 1080п)</string> + <string name="resolution_two">2к (1440п / 2160п) (споро)</string> + <string name="resolution_three">3к (2160п / 3240п) (споро)</string> + <string name="resolution_four">4к (2880п / 4320п) (споро)</string> + + <!-- Renderer VSync --> + <string name="renderer_vsync_immediate">Непосредан (искључен)</string> + <string name="renderer_vsync_mailbox">Поштански сандучић</string> + <string name="renderer_vsync_fifo">ФИФО (ОН)</string> + <string name="renderer_vsync_fifo_relaxed">ФИФО је опуштен</string> + + <!-- Scaling Filters --> + <string name="scaling_filter_nearest_neighbor">Најближи комшија</string> + <string name="scaling_filter_bilinear">Билинеарни</string> + <string name="scaling_filter_bicubic">Бицубиц</string> + <string name="scaling_filter_gaussian">Гауссиан</string> + <string name="scaling_filter_scale_force">Скалефорце</string> + <string name="scaling_filter_fsr">АМД ФиделитиФКС ™ Супер резолуција</string> + + <!-- Anti-Aliasing --> + <string name="anti_aliasing_none">Ниједан</string> + <string name="anti_aliasing_fxaa">ФКСАА</string> + <string name="anti_aliasing_smaa">СМАА</string> + + <!-- Screen Layouts --> + <string name="screen_layout_auto">Аутомат</string> + <string name="screen_layout_sensor_landscape">Сензорски пејзаж</string> + <string name="screen_layout_landscape">Пејзаж</string> + <string name="screen_layout_reverse_landscape">Обрнути пејзаж</string> + <string name="screen_layout_sensor_portrait">Портрет сензора</string> + <string name="screen_layout_portrait">Портрет</string> + <string name="screen_layout_reverse_portrait">Обрнути портрет</string> + + <!-- Aspect Ratios --> + <string name="ratio_default">Подразумевано (16: 9)</string> + <string name="ratio_force_four_three">Форце 4: 3</string> + <string name="ratio_force_twenty_one_nine">Натура 21: 9</string> + <string name="ratio_force_sixteen_ten">Сила 16:10</string> + <string name="ratio_stretch">Протезање до прозора</string> + + <!-- CPU Backend --> + <string name="cpu_backend_dynarmic">Динамина (споро)</string> + <string name="cpu_backend_nce">Извођење изворног кода (НЦЕ)</string> + + <!-- CPU Accuracy --> + <string name="cpu_accuracy_accurate">Тачан</string> + <string name="cpu_accuracy_unsafe">Несигуран</string> + <string name="cpu_accuracy_paranoid">Параноичан (споро)</string> + + <!-- Gamepad Buttons --> + <string name="gamepad_d_pad">Д-пад</string> + <string name="gamepad_left_stick">Левичар</string> + <string name="gamepad_right_stick">Десни штап</string> + <string name="gamepad_home">Дом</string> + <string name="gamepad_screenshot">Заслона</string> + + <!-- Disk shader cache --> + <string name="preparing_shaders">Припрема схадера</string> + <string name="building_shaders">Зграда схадера</string> + + <!-- Theme options --> + <string name="change_app_theme">Промените тему апликација</string> + <string name="theme_default">Подразумевано</string> + <string name="theme_material_you">Материјал ти</string> + + <!-- Theme Modes --> + <string name="change_theme_mode">Промените режим теме</string> + <string name="theme_mode_follow_system">Слиједите систем</string> + <string name="theme_mode_light">Светло</string> + <string name="theme_mode_dark">Мрак</string> + + <!-- Audio output engines --> + <string name="oboe">обоја</string> + <string name="cubeb">Цубеб</string> + + <!-- Anisotropic filtering options --> + <string name="multiplier_two">2к</string> + <string name="multiplier_four">4к</string> + <string name="multiplier_eight">8к</string> + <string name="multiplier_sixteen">16к</string> + + <!-- Black backgrounds theme --> + <string name="use_black_backgrounds">Црна позадина</string> + <string name="use_black_backgrounds_description">Када користите тамну тему, нанесите црну позадину.</string> + + <!-- Static Themes --> + <string name="static_theme_color">Боја теме</string> + <string name="violet">Виолет (подразумевано)</string> + <string name="blue">Плава</string> + <string name="cyan">Цијан</string> + <string name="red">Црвено</string> + <string name="green">Зеленило</string> + <string name="yellow">Жути</string> + <string name="orange">Наранџаста</string> + <string name="pink">Ружичаст</string> + <string name="gray">Сива</string> + + <!-- Picture-In-Picture --> + <string name="picture_in_picture">Слика на слици</string> + <string name="picture_in_picture_description">Смањите прозор када се постави у позадини</string> + <string name="pause">Пауза</string> + <string name="play">Играње</string> + <string name="mute">Немирати</string> + <string name="unmute">Имуцати</string> + + <!-- Emulation vertical alignment --> + <string name="vertical_alignment">Вертикално поравнање</string> + <string name="top">На врху</string> + <string name="center">Центер</string> + <string name="bottom">Дно</string> + + <!-- Applet Modes --> + <string name="applets_menu">Аплетс</string> + <string name="applets_menu_description">(ВИП) Промените Апплете Фронтендс и Подешавања</string> + + <string name="applet_hle">Прилагођена фронтенд</string> + <string name="applet_lle">Прави апплет</string> + + <string name="swkbd_applet">Софтверска тастатура</string> + + <!-- Licenses screen strings --> + <string name="licenses">Лиценце</string> + <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string> + <string name="license_fidelityfx_fsr_description">Висококвалитетни упглинг из АМД-а</string> + <string name="license_fidelityfx_fsr_link" translatable="false">https://github.com/GPUOpen-Effects/FidelityFX-FSR</string> + <string name="license_fidelityfx_fsr_copyright" translatable="false">Copyright © 2021 Advanced Micro Devices, Inc.</string> + <string name="license_fidelityfx_fsr_text" translatable="false"> +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the \"Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:\n\n + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software.\n\n + +THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + </string> + <string name="license_cubeb" translatable="false">cubeb</string> + <string name="license_cubeb_description" translatable="false">Cross platform audio library</string> + <string name="license_cubeb_link" translatable="false">https://github.com/mozilla/cubeb</string> + <string name="license_cubeb_copyright" translatable="false">Copyright © 2011 Mozilla Foundation</string> + <string name="license_cubeb_text" translatable="false"> +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies.\n\n + +THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + </string> + <string name="license_dynarmic" translatable="false">Dynarmic</string> + <string name="license_dynarmic_description" translatable="false">An ARM dynamic recompiler</string> + <string name="license_dynarmic_link" translatable="false">https://github.com/merryhime/dynarmic</string> + <string name="license_dynarmic_copyright" translatable="false">Copyright © 2017 merryhime</string> + <string name="license_dynarmic_text" translatable="false"> +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted.\n\n + +THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + </string> + <string name="license_ffmpeg" translatable="false">FFmpeg</string> + <string name="license_ffmpeg_description" translatable="false">FFmpeg is a collection of libraries and tools to process multimedia content such as audio, video, subtitles and related metadata.</string> + <string name="license_ffmpeg_link" translatable="false">https://github.com/FFmpeg/FFmpeg</string> + <string name="license_ffmpeg_copyright" translatable="false">Copyright © 1991, 1999 Free Software Foundation, Inc.</string> + <string name="license_ffmpeg_text" translatable="false"> +GNU LESSER GENERAL PUBLIC LICENSE\n +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called \"this License\"). +Each licensee is addressed as \"you\".\n\n + + A \"library\" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables.\n\n + + The \"Library\", below, refers to any such software library or work +which has been distributed under these terms. A \"work based on the +Library\" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term \"modification\".)\n\n + + \"Source code\" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library.\n\n + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does.\n\n + + 1. You may copy and distribute verbatim copies of the Library\"s +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library.\n\n + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee.\n\n + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions:\n\n + +a) The modified work must itself be a software library.\n\n + +b) You must cause the files modified to carry prominent notices +stating that you changed the files and the date of any change.\n\n + +c) You must cause the whole of the work to be licensed at no +charge to all third parties under the terms of this License.\n\n + +d) If a facility in the modified Library refers to a function or a +table of data to be supplied by an application program that uses +the facility, other than as an argument passed when the facility +is invoked, then you must make a good faith effort to ensure that, +in the event an application does not supply such function or +table, the facility still operates, and performs whatever part of +its purpose remains meaningful.\n\n + +(For example, a function in a library to compute square roots has +a purpose that is entirely well-defined independent of the +application. Therefore, Subsection 2d requires that any +application-supplied function or table used by this function must +be optional: if the application does not supply it, the square +root function must still compute square roots.)\n\n + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it.\n\n + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library.\n\n + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License.\n\n + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices.\n\n + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy.\n\n + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library.\n\n + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange.\n\n + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code.\n\n + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a \"work that uses the Library\". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License.\n\n + + However, linking a \"work that uses the Library\" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a \"work that uses the +library\". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables.\n\n + + When a \"work that uses the Library\" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law.\n\n + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.)\n\n + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself.\n\n + + 6. As an exception to the Sections above, you may also combine or +link a \"work that uses the Library\" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer\"s own use and reverse +engineering for debugging such modifications.\n\n + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things:\n\n + +a) Accompany the work with the complete corresponding +machine-readable source code for the Library including whatever +changes were used in the work (which must be distributed under +Sections 1 and 2 above); and, if the work is an executable linked +with the Library, with the complete machine-readable \"work that +uses the Library\", as object code and/or source code, so that the +user can modify the Library and then relink to produce a modified +executable containing the modified Library. (It is understood +that the user who changes the contents of definitions files in the +Library will not necessarily be able to recompile the application +to use the modified definitions.)\n\n + +b) Use a suitable shared library mechanism for linking with the +Library. A suitable mechanism is one that (1) uses at run time a +copy of the library already present on the user\"s computer system, +rather than copying library functions into the executable, and (2) +will operate properly with a modified version of the library, if +the user installs one, as long as the modified version is +interface-compatible with the version that the work was made with.\n\n + +c) Accompany the work with a written offer, valid for at +least three years, to give the same user the materials +specified in Subsection 6a, above, for a charge no more +than the cost of performing this distribution.\n\n + +d) If distribution of the work is made by offering access to copy +from a designated place, offer equivalent access to copy the above +specified materials from the same place.\n\n + +e) Verify that the user has already received a copy of these +materials or that you have already sent this user a copy.\n\n + + For an executable, the required form of the \"work that uses the +Library\" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable.\n\n + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute.\n\n + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things:\n\n + +a) Accompany the combined library with a copy of the same work +based on the Library, uncombined with any other library +facilities. This must be distributed under the terms of the +Sections above.\n\n + +b) Give prominent notice with the combined library of the fact +that part of it is a work based on the Library, and explaining +where to find the accompanying uncombined form of the same work.\n\n + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance.\n\n + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it.\n\n + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients\" exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License.\n\n + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library.\n\n + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances.\n\n + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice.\n\n + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License.\n\n + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License.\n\n + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns.\n\n + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version\", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation.\n\n + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally.\n\n + +NO WARRANTY\n\n + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + </string> + <string name="license_opus" translatable="false">Opus</string> + <string name="license_opus_description" translatable="false">Modern audio compression for the internet</string> + <string name="license_opus_link" translatable="false">https://github.com/xiph/opus</string> + <string name="license_opus_copyright" translatable="false">Copyright 2001–2011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, Erik de Castro Lopo</string> + <string name="license_opus_text" translatable="false"> +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met:\n\n + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer.\n\n + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution.\n\n + +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission.\n\n + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS\"\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n + +Opus is subject to the royalty-free patent licenses which are +specified at:\n\n + +Xiph.Org Foundation: +https://datatracker.ietf.org/ipr/1524/ \n\n + +Microsoft Corporation: +https://datatracker.ietf.org/ipr/1914/ \n\n + +Broadcom Corporation: +https://datatracker.ietf.org/ipr/1526/ + </string> + <string name="license_sirit" translatable="false">Sirit</string> + <string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string> + <string name="license_sirit_link" translatable="false">https://github.com/ReinUsesLisp/sirit</string> + <string name="license_sirit_copyright" translatable="false">Copyright © 2019, sirit All rights reserved.</string> + <string name="license_sirit_text" translatable="false"> +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met:\n +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer.\n +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution.\n +* Neither the name of the organization nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission.\n\n + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + </string> + <string name="license_adreno_tools" translatable="false">Adreno Tools</string> + <string name="license_adreno_tools_description" translatable="false">A library for applying rootless Adreno GPU driver modifications/replacements</string> + <string name="license_adreno_tools_link" translatable="false">https://github.com/bylaws/libadrenotools</string> + <string name="license_adreno_tools_copyright" translatable="false">Copyright © 2021, Billy Laws</string> + <string name="license_adreno_tools_text" translatable="false"> +BSD 2-Clause License\n\n + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met:\n\n + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer.\n\n + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution.\n\n + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + </string> +</resources> diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index bcd4949e00..8a8b89d44f 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -62,6 +62,7 @@ <item>@string/language_spanish</item> <item>@string/language_taiwanese</item> <item>@string/language_traditional_chinese</item> + <item>@string/language_serbian</item> </string-array> <integer-array name="languageValues"> @@ -83,6 +84,7 @@ <item>5</item> <item>11</item> <item>16</item> + <item>18</item> </integer-array> <string-array name="rendererApiNames"> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 7f05e4f985..9a937b37bb 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -204,18 +204,22 @@ <string name="emulation_multiplayer">Multiplayer</string> <string name="multiplayer_game_name">Preferred Games</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_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_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_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="cancel">Cancel</string> <string name="ok">Ok</string> <string name="refresh">Refresh</string> <string name="room_list">Room List</string> - + <string name="multiplayer_public_visibility">Public</string> + <string name="multiplayer_unlisted_visibility">Unlisted</string> <!-- Setup strings --> <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="set_custom_rtc">Set custom RTC</string> + <string name="generate">Generate</string> + <!-- Network settings strings --> <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_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> <!-- 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_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> @@ -820,6 +832,7 @@ <string name="language_simplified_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_serbian" translatable="false">српски</string> <!-- Memory Sizes --> <string name="memory_byte">Byte</string> diff --git a/src/common/android/multiplayer/multiplayer.cpp b/src/common/android/multiplayer/multiplayer.cpp index fe6408fced..26bb800ce2 100644 --- a/src/common/android/multiplayer/multiplayer.cpp +++ b/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 #include "common/android/id_cache.h" @@ -10,13 +10,17 @@ #include "network/network.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 <chrono> namespace IDCache = Common::Android; -AndroidMultiplayer::AndroidMultiplayer(Core::System& system_, +AndroidMultiplayer::AndroidMultiplayer(Core::System &system_, std::shared_ptr<Core::AnnounceMultiplayerSession> session) : system{system_}, announce_multiplayer_session(session) {} @@ -27,8 +31,8 @@ void AndroidMultiplayer::AddNetPlayMessage(jint type, jstring 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)); } @@ -46,7 +50,7 @@ bool AndroidMultiplayer::NetworkInit() { if (auto member = Network::GetRoomMember().lock()) { // 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 || state == Network::RoomMember::State::Moderator) { NetPlayStatus status; @@ -64,7 +68,7 @@ bool AndroidMultiplayer::NetworkInit() { AddNetPlayMessage(static_cast<int>(status), msg); } }); - member->BindOnError([this](const Network::RoomMember::Error& error) { + member->BindOnError([this](const Network::RoomMember::Error &error) { NetPlayStatus status; std::string msg; switch (error) { @@ -107,29 +111,30 @@ bool AndroidMultiplayer::NetworkInit() { } 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; std::string msg(chat.nickname); msg += ": "; @@ -140,10 +145,14 @@ bool AndroidMultiplayer::NetworkInit() { 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(); if (!member) { return NetPlayStatus::NETWORK_ERROR; @@ -168,17 +177,55 @@ NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string& ipaddress .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, - 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; } + // 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 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 for (int i = 0; i < 5; i++) { @@ -194,8 +241,9 @@ NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string& ipaddress 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(); if (!member) { return NetPlayStatus::NETWORK_ERROR; @@ -209,7 +257,8 @@ NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string& ipaddress, 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 std::this_thread::sleep_for(std::chrono::milliseconds(500)); @@ -226,7 +275,7 @@ NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string& ipaddress, 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 (room->GetState() != Network::RoomMember::State::Joined && 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()) { auto members = room->GetMemberInformation(); 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; }); 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()) { auto members = room->GetMemberInformation(); 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; }); 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()) { room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username); } @@ -278,7 +327,7 @@ std::vector<std::string> AndroidMultiplayer::NetPlayRoomInfo() { auto room_info = room->GetRoomInformation(); info_list.push_back(room_info.name + "|" + std::to_string(room_info.member_slots)); // all members - for (const auto& member : members) { + for (const auto &member: members) { info_list.push_back(member.nickname); } } @@ -368,14 +417,29 @@ std::vector<std::string> AndroidMultiplayer::NetPlayGetBanList() { auto [username_bans, ip_bans] = room->GetBanList(); // Add username bans - for (const auto& username : username_bans) { + for (const auto &username: username_bans) { ban_list.push_back(username); } // Add IP bans - for (const auto& ip : ip_bans) { + for (const auto &ip: ip_bans) { ban_list.push_back(ip); } } 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; +}
\ No newline at end of file diff --git a/src/common/android/multiplayer/multiplayer.h b/src/common/android/multiplayer/multiplayer.h index 591d3be8fd..39ea80f5b7 100644 --- a/src/common/android/multiplayer/multiplayer.h +++ b/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 #pragma once @@ -66,7 +66,7 @@ public: NetPlayStatus 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); + const std::string &room_name, int max_players, bool isPublic); NetPlayStatus NetPlayJoinRoom(const std::string &ipaddress, int port, const std::string &username, const std::string &password); diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 53065c083c..f66c24455f 100644 --- a/src/common/settings.cpp +++ b/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-License-Identifier: GPL-2.0-or-later @@ -118,7 +121,7 @@ void LogSettings() { LOG_INFO(Config, "yuzu Configuration:"); for (auto& [category, settings] : values.linkage.by_category) { 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. continue; } diff --git a/src/common/settings.h b/src/common/settings.h index e4239a3032..d7a8d201f9 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -527,7 +527,7 @@ struct Values { SwitchableSetting<Language, true> language_index{linkage, Language::EnglishAmerican, Language::Japanese, - Language::PortugueseBrazilian, + Language::Serbian, "language_index", Category::System}; SwitchableSetting<Region, true> region_index{linkage, Region::Usa, Region::Japan, @@ -700,9 +700,9 @@ struct Values { // WebService Setting<std::string> web_api_url{linkage, "api.ynet-fun.xyz", "web_api_url", Category::WebService}; - Setting<std::string> yuzu_username{linkage, std::string(), "yuzu_username", + Setting<std::string> eden_username{linkage, std::string(), "eden_username", 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 std::map<u64, std::vector<std::string>> disabled_addons; diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 9068001406..f0c5c96cc4 100644 --- a/src/common/settings_enums.h +++ b/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, Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin, - ChineseSimplified, ChineseTraditional, PortugueseBrazilian); + ChineseSimplified, ChineseTraditional, PortugueseBrazilian, Serbian); ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan); diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index 841a3455cf..156bff62dc 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/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"); Settings::values.web_api_url = web_api_url; 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 { LOG_INFO(Network, "Hosting a public room"); 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; } } diff --git a/src/network/announce_multiplayer_session.cpp b/src/network/announce_multiplayer_session.cpp index 35082cbd31..8836524c73 100644 --- a/src/network/announce_multiplayer_session.cpp +++ b/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-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 <future> #include <vector> @@ -13,9 +13,9 @@ #include "common/settings.h" #include "network/network.h" -#ifdef ENABLE_WEB_SERVICE +//#ifdef ENABLE_WEB_SERVICE #include "web_service/announce_room_json.h" -#endif +//#endif namespace Core { @@ -23,13 +23,13 @@ namespace Core { static constexpr std::chrono::seconds announce_time_interval(15); AnnounceMultiplayerSession::AnnounceMultiplayerSession() { -#ifdef ENABLE_WEB_SERVICE +//#ifdef ENABLE_WEB_SERVICE 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() { @@ -156,11 +156,11 @@ bool AnnounceMultiplayerSession::IsRunning() const { void AnnounceMultiplayerSession::UpdateCredentials() { 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(), - Settings::values.yuzu_username.GetValue(), - Settings::values.yuzu_token.GetValue()); -#endif + Settings::values.eden_username.GetValue(), + Settings::values.eden_token.GetValue()); +//#endif } } // namespace Core diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp index b766f4cbcd..7b6e34f315 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp +++ b/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-License-Identifier: GPL-2.0-or-later diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index ddbb5581f0..f2bf5a3228 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/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-License-Identifier: GPL-2.0-or-later diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index ee431bb731..e7cec364b6 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/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-License-Identifier: GPL-2.0-or-later diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index f7902fb773..d62b5b0853 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/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-License-Identifier: GPL-2.0-or-later +#include "yuzu/configuration/configure_web.h" #include <QIcon> #include <QMessageBox> + +#if QT_VERSION_MAJOR >= 6 +#include <QRegularExpressionValidator> +#else +#include <QRegExpValidator> +#endif + #include <QtConcurrent/QtConcurrentRun> #include "common/settings.h" #include "ui_configure_web.h" -#include "yuzu/configuration/configure_web.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 ui->discord_group->setVisible(false); @@ -60,87 +68,61 @@ void ConfigureWeb::changeEvent(QEvent* event) { void ConfigureWeb::RetranslateUI() { 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() { - 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() { 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) { diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h index cadbc0fa0a..3da7e5eccc 100644 --- a/src/yuzu/configuration/configure_web.h +++ b/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-License-Identifier: GPL-2.0-or-later #pragma once -#include <memory> -#include <QFutureWatcher> +#include <QRandomGenerator> + #include <QWidget> +#include <memory> namespace Ui { class ConfigureWeb; @@ -25,14 +29,12 @@ private: void changeEvent(QEvent* event) override; void RetranslateUI(); - void OnLoginChanged(); - void VerifyLogin(); - void OnLoginVerified(); - void SetConfiguration(); - bool user_verified = true; - QFutureWatcher<bool> verify_watcher; - std::unique_ptr<Ui::ConfigureWeb> ui; + QRandomGenerator *m_rng; + +private slots: + void GenerateToken(); + void VerifyLogin(); }; diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui index ac9b6954cd..d440fd0409 100644 --- a/src/yuzu/configuration/configure_web.ui +++ b/src/yuzu/configuration/configure_web.ui @@ -26,41 +26,28 @@ </property> <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> <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"> - <string>Verify</string> + <string>Token: </string> </property> </widget> </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 name="text"> - <string>Sign up</string> + <string>Username: </string> </property> </widget> </item> - <item row="0" column="1" colspan="3"> + <item row="0" column="1" colspan="4"> <widget class="QLineEdit" name="edit_username"> <property name="maxLength"> <number>20</number> @@ -70,25 +57,27 @@ </property> </widget> </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> - </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> </widget> </item> - <item row="1" column="1" colspan="3"> + <item row="1" column="1" colspan="4"> <widget class="QLineEdit" name="edit_token"> + <property name="inputMethodHints"> + <set>Qt::InputMethodHint::ImhLowercaseOnly</set> + </property> <property name="maxLength"> <number>80</number> </property> @@ -97,14 +86,27 @@ </property> </widget> </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"> - <string>What is my token?</string> + <string>Generate</string> </property> </widget> </item> - <item row="2" column="2"> + <item row="2" column="1" colspan="3"> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Orientation::Horizontal</enum> diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index f4484ce35b..17ee89b015 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/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, ChineseTraditional, tr("Traditional Chinese (正體中文)")), PAIR(Language, PortugueseBrazilian, tr("Brazilian Portuguese (português do Brasil)")), + PAIR(Language, Serbian, tr("Serbian (српски)")), }}); translations->insert({Settings::EnumMetadata<Settings::Region>::Index(), { diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 9002fdd0b9..13d2373f5d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3591,8 +3591,8 @@ void GMainWindow::OnMenuReportCompatibility() { 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 { QMessageBox::critical( this, tr("Missing yuzu Account"), diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 693cffe0d0..30f37c2b4a 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/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-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 <QFuture> #include <QIntValidator> @@ -38,9 +38,9 @@ DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent) ui->nickname->setValidator(validation.GetNickname()); ui->nickname->setText( 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 - 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->setText(QString::fromStdString(UISettings::values.multiplayer_ip.GetValue())); diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index fd047b4b5a..6471ad1632 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/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-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 <QColor> #include <QImage> @@ -59,9 +59,9 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list, // Restore the settings: ui->username->setText( 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 - ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); + ui->username->setText(QString::fromStdString(Settings::values.eden_username.GetValue())); } ui->room_name->setText( QString::fromStdString(UISettings::values.multiplayer_room_name.GetValue())); @@ -167,7 +167,7 @@ void HostRoomWindow::Host() { const bool created = room->Create(ui->room_name->text().toStdString(), 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); if (!created) { NetworkMessage::ErrorManager::ShowError( @@ -206,8 +206,8 @@ void HostRoomWindow::Host() { #ifdef ENABLE_WEB_SERVICE if (is_public) { 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()) { token = client.GetExternalJWT(room->GetVerifyUID()).returned_data; } diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index bc91145b97..93ff56a652 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/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-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 <QList> #include <QtConcurrent/QtConcurrentRun> @@ -67,9 +67,9 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, // Try find the best nickname by default 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( - QString::fromStdString(Settings::values.yuzu_username.GetValue())); + QString::fromStdString(Settings::values.eden_username.GetValue())); } else if (!GetProfileUsername().empty()) { ui->nickname->setText(QString::fromStdString(GetProfileUsername())); } else { @@ -189,11 +189,11 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid] { std::string token; #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(), - 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; if (token.empty()) { LOG_ERROR(WebService, "Could not get external JWT, verification may fail"); |
