diff options
| author | Aleksandr Popovich <alekpopo@pm.me> | 2025-06-05 18:59:47 +0000 |
|---|---|---|
| committer | crueter <crueter@noreply.localhost> | 2025-06-05 18:59:47 +0000 |
| commit | 76fa5255926da8f6339e7be5bf106646acedf78a (patch) | |
| tree | 8ea533a7d748a9a5404e15a1063b11a832b2f007 /src | |
| parent | 7e13da47af71814d612c6c060be68e8ba84271ff (diff) | |
Added the public lobby to android. (#125)
This is adapted from kleidis old PR to Azahar. Changes from it:
- Fixed inconsistent button styling in the dialog for connection
- Allowed to hide both empty and full rooms.
- Proper serving of preferred games
- Enables web service for android by default
- Better implementation of multiplayer.cpp that works with oop
Also fixes the room network class and turns it into a static namespace
in network
Signed-off-by: Aleksandr Popovich <alekpopo@pm.me>
Co-authored-by: swurl <swurl@swurl.xyz>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/125
Co-authored-by: Aleksandr Popovich <alekpopo@pm.me>
Co-committed-by: Aleksandr Popovich <alekpopo@pm.me>
Diffstat (limited to 'src')
95 files changed, 1464 insertions, 498 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 6c5592c1f5..de5d891f01 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -1,4 +1,7 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: Copyright yuzu/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 import android.annotation.SuppressLint @@ -162,7 +165,7 @@ android { arguments( "-DENABLE_QT=0", // Don't use QT "-DENABLE_SDL2=0", // Don't use SDL - "-DENABLE_WEB_SERVICE=0", // Don't use telemetry + "-DENABLE_WEB_SERVICE=1", // Enable web service "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-DYUZU_USE_BUNDLED_VCPKG=ON", "-DYUZU_USE_BUNDLED_FFMPEG=ON", @@ -176,9 +179,9 @@ android { } } -tasks.create<Delete>("ktlintReset") { - delete(File(buildDir.path + File.separator + "intermediates/ktLint")) -} +tasks.register<Delete>("ktlintReset", fun Delete.() { + delete(File(layout.buildDirectory.toString() + File.separator + "intermediates/ktLint")) +}) val showFormatHelp = { logger.lifecycle( diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 0a43b13e1c..e4885e5126 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,9 @@ <!-- SPDX-FileCopyrightText: 2023 yuzu Emulator Project SPDX-License-Identifier: GPL-3.0-or-later + +SPDX-FileCopyrightText: 2025 Eden Emulator Project +SPDX-License-Identifier: GPL-3.0-or-later --> <!-- SPDX-FileCopyrightText: Eden Emulator Project @@ -15,6 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later <uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 0be943eb86..c8f44c060e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -270,8 +270,7 @@ object NativeLibrary { NetPlayManager.clearChat() } - - external fun netPlayInit() + external fun initMultiplayer() @Keep @JvmStatic diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 496fc99e4c..8622aa658e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 671e897448..2596ab0e93 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.adapters 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 9f7e80e7d0..25122fb79b 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,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later 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 new file mode 100644 index 0000000000..ae588bdfa8 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/LobbyBrowser.kt @@ -0,0 +1,252 @@ +// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.dialogs + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.Configuration +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import androidx.core.content.getSystemService +import androidx.core.widget.doOnTextChanged +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +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.TextInputEditText +import info.debatty.java.stringsimilarity.Jaccard +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.network.NetPlayManager +import java.util.Locale + +class LobbyBrowser(context: Context) : BottomSheetDialog(context) { + private lateinit var binding: DialogLobbyBrowserBinding + private lateinit var adapter: LobbyRoomAdapter + private val handler = Handler(Looper.getMainLooper()) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + behavior.state = BottomSheetBehavior.STATE_EXPANDED + behavior.skipCollapsed = + context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + + binding = DialogLobbyBrowserBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.emptyRefreshButton.setOnClickListener { + binding.progressBar.visibility = View.VISIBLE + refreshRoomList() + } + + setupRecyclerView() + setupRefreshButton() + refreshRoomList() + setupSearchBar() + } + + private fun setupRecyclerView() { + adapter = LobbyRoomAdapter { room -> handleRoomSelection(room) } + + binding.roomList.apply { + layoutManager = LinearLayoutManager(context) + adapter = this@LobbyBrowser.adapter + addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) + } + } + + private fun setupRefreshButton() { + binding.refreshButton.setOnClickListener { + binding.refreshButton.isEnabled = false + binding.progressBar.visibility = View.VISIBLE + refreshRoomList() + } + } + + private fun setupSearchBar() { + binding.chipHideFull.setOnCheckedChangeListener { _, _ -> adapter.filterAndSearch() } + binding.chipHideEmpty.setOnCheckedChangeListener { _, _ -> adapter.filterAndSearch() } + + binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int -> + if (text.toString().isNotEmpty()) { + binding.clearButton.visibility = View.VISIBLE + } else { + binding.clearButton.visibility = View.INVISIBLE + } + } + + binding.searchText.setOnEditorActionListener { v, action, _ -> + if (action == EditorInfo.IME_ACTION_DONE) { + v.clearFocus() + + val imm = context.getSystemService<InputMethodManager>() + imm?.hideSoftInputFromWindow(v.windowToken, 0) + + adapter.filterAndSearch() + true + } else { + false + } + } + + binding.btnSubmit.setOnClickListener { adapter.filterAndSearch() } + + binding.clearButton.setOnClickListener { + binding.searchText.setText("") + adapter.updateRooms(NetPlayManager.getPublicRooms()) + } + } + + private fun refreshRoomList() { + NetPlayManager.refreshRoomListAsync { rooms -> + binding.emptyView.visibility = if (rooms.isEmpty()) View.VISIBLE else View.GONE + binding.roomList.visibility = if (rooms.isEmpty()) View.GONE else View.VISIBLE + binding.appbar.visibility = if (rooms.isEmpty()) View.GONE else View.VISIBLE + adapter.updateRooms(rooms) + adapter.filterAndSearch() + binding.refreshButton.isEnabled = true + binding.progressBar.visibility = View.GONE + } + } + + private fun handleRoomSelection(room: NetPlayManager.RoomInfo) { + if (room.hasPassword) { + showPasswordDialog(room) + } else { + joinRoom(room, "") + } + } + + private fun showPasswordDialog(room: NetPlayManager.RoomInfo) { + val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_password_input, null) + val passwordInput = dialogView.findViewById<TextInputEditText>(R.id.password_input) + + MaterialAlertDialogBuilder(context) + .setTitle(context.getString(R.string.multiplayer_password_required)) + .setView(dialogView) + .setPositiveButton(R.string.multiplayer_join_room) { _, _ -> + joinRoom(room, passwordInput.text.toString()) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + private fun joinRoom(room: NetPlayManager.RoomInfo, password: String) { + val username = NetPlayManager.getUsername(context) + + Thread { + val result = NetPlayManager.netPlayJoinRoom(room.ip, room.port, username, password) + + handler.post { + if (result == 0) { + dismiss() + NetPlayDialog(context).show() + } + } + }.start() + } + + inner class LobbyRoomAdapter(private val onRoomSelected: (NetPlayManager.RoomInfo) -> Unit) : + RecyclerView.Adapter<LobbyRoomAdapter.RoomViewHolder>() { + + private val rooms = mutableListOf<NetPlayManager.RoomInfo>() + + inner class RoomViewHolder(private val binding: ItemLobbyRoomBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(room: NetPlayManager.RoomInfo) { + binding.roomName.text = room.name + binding.roomOwner.text = room.owner + binding.playerCount.text = context.getString( + R.string.multiplayer_player_count, + room.members.size, + room.maxPlayers + ) + + binding.lockIcon.visibility = if (room.hasPassword) View.VISIBLE else View.GONE + + if (room.preferredGameName.isNotEmpty() && room.preferredGameId != 0L) { + binding.gameName.text = room.preferredGameName + } else { + binding.gameName.text = context.getString(R.string.multiplayer_no_game_info) + } + + itemView.setOnClickListener { onRoomSelected(room) } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RoomViewHolder { + val binding = ItemLobbyRoomBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return RoomViewHolder(binding) + } + + override fun onBindViewHolder(holder: RoomViewHolder, position: Int) { + holder.bind(rooms[position]) + } + + override fun getItemCount() = rooms.size + + @SuppressLint("NotifyDataSetChanged") + fun updateRooms(newRooms: List<NetPlayManager.RoomInfo>) { + rooms.clear() + rooms.addAll(newRooms) + notifyDataSetChanged() + } + + fun filterAndSearch() { + if (binding.searchText.text.toString().isEmpty() && + !binding.chipHideFull.isChecked && !binding.chipHideEmpty.isChecked + ) { + adapter.updateRooms(NetPlayManager.getPublicRooms()) + return + } + + val baseList = NetPlayManager.getPublicRooms() + val filteredList = baseList.filter { room -> + (!binding.chipHideFull.isChecked || room.members.size < room.maxPlayers) && + (!binding.chipHideEmpty.isChecked || room.members.isNotEmpty()) + } + + if (binding.searchText.text.toString().isEmpty() && + (binding.chipHideFull.isChecked || binding.chipHideEmpty.isChecked) + ) { + adapter.updateRooms(filteredList) + return + } + + val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault()) + val searchAlgorithm = if (searchTerm.length > 1) Jaccard(2) else JaroWinkler() + val sortedList: List<NetPlayManager.RoomInfo> = filteredList.mapNotNull { room -> + val roomName = room.name.lowercase(Locale.getDefault()) + + val score = searchAlgorithm.similarity(roomName, searchTerm) + if (score > 0.03) { + ScoreItem(score, room) + } else { + null + } + }.sortedByDescending { it -> + it.score + }.map { it.item } + adapter.updateRooms(sortedList) + + } + } + + private inner class ScoreItem(val score: Double, val item: NetPlayManager.RoomInfo) +} 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 494b8524ff..a95b0255d3 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,12 +1,9 @@ -// Copyright 2024 Mandarine Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.dialogs +import android.annotation.SuppressLint import android.content.Context import org.yuzu.yuzu_emu.R import android.content.res.Configuration @@ -16,6 +13,7 @@ import android.os.Looper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ArrayAdapter import android.widget.PopupMenu import android.widget.Toast import androidx.recyclerview.widget.LinearLayoutManager @@ -32,10 +30,13 @@ import org.yuzu.yuzu_emu.databinding.ItemButtonNetplayBinding import org.yuzu.yuzu_emu.databinding.ItemTextNetplayBinding import org.yuzu.yuzu_emu.utils.CompatUtils import org.yuzu.yuzu_emu.network.NetPlayManager +import org.yuzu.yuzu_emu.utils.GameHelper class NetPlayDialog(context: Context) : BottomSheetDialog(context) { private lateinit var adapter: NetPlayAdapter + private val gameNameList: MutableList<Array<String>> = mutableListOf() + private val gameIdList: MutableList<Array<Long>> = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -71,6 +72,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { else -> { DialogMultiplayerConnectBinding.inflate(layoutInflater).apply { setContentView(root) + for (game in GameHelper.cachedGameList) { + val gameName = game.title + if (gameNameList.none { it[0] == gameName }) { + gameNameList.add(arrayOf(gameName)) + } + + val gameId = game.programId.toLong() + if (gameIdList.none { it[0] == gameId }) { + gameIdList.add(arrayOf(gameId)) + } + } btnCreate.setOnClickListener { showNetPlayInputDialog(true) dismiss() @@ -79,6 +91,10 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { showNetPlayInputDialog(false) dismiss() } + btnLobbyBrowser.setOnClickListener { + LobbyBrowser(context).show() + dismiss() + } } } } @@ -208,10 +224,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { override fun getItemCount() = netPlayItems.size } + @SuppressLint("NotifyDataSetChanged") fun refreshAdapterItems() { val handler = Handler(Looper.getMainLooper()) - NetPlayManager.setOnAdapterRefreshListener() { type, msg -> + NetPlayManager.setOnAdapterRefreshListener() { _, _ -> handler.post { adapter.netPlayItems.clear() adapter.loadMultiplayerMenu() @@ -244,6 +261,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { binding.ipPort.setText(NetPlayManager.getRoomPort(activity)) binding.username.setText(NetPlayManager.getUsername(activity)) + binding.dropdownPreferredGameName.apply { + setAdapter( + ArrayAdapter( + activity, + R.layout.dropdown_item, + gameNameList.map { it[0] } + ) + ) + } + + binding.preferredGameName.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()) @@ -259,6 +287,8 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { val ipAddress = binding.ipAddress.text.toString() val username = binding.username.text.toString() val portStr = binding.ipPort.text.toString() + val preferredGameName = binding.dropdownPreferredGameName.text.toString() + val preferredGameId = gameIdList[gameNameList.indexOfFirst { it[0] == preferredGameName }][0] val password = binding.password.text.toString() val port = portStr.toIntOrNull() ?: run { Toast.makeText(activity, R.string.multiplayer_port_invalid, Toast.LENGTH_LONG).show() @@ -276,6 +306,13 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { return@setOnClickListener } + if (isCreateRoom && preferredGameName.isEmpty()) { + Toast.makeText(activity, R.string.multiplayer_preferred_game_name_invalid, Toast.LENGTH_LONG).show(); + binding.btnConfirm.isEnabled = false + binding.btnConfirm.text = activity.getString(R.string.original_button_text) + return@setOnClickListener + } + if (ipAddress.length < 7 || username.length < 5) { Toast.makeText(activity, R.string.multiplayer_input_invalid, Toast.LENGTH_LONG).show() binding.btnConfirm.isEnabled = true @@ -283,7 +320,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { } else { Handler(Looper.getMainLooper()).post { val result = if (isCreateRoom) { - NetPlayManager.netPlayCreateRoom(ipAddress, port, username, password, roomName, maxPlayers) + NetPlayManager.netPlayCreateRoom(ipAddress, port, username, preferredGameName, preferredGameId, password, roomName, maxPlayers) } else { NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password) } @@ -397,4 +434,4 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) { } } -}
\ No newline at end of file +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 3a44e30bdb..c482e16492 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later 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 6f16cf5b19..6eb6eb6d52 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 @@ -7,7 +7,9 @@ import org.yuzu.yuzu_emu.utils.NativeConfig enum class StringSetting(override val key: String) : AbstractStringSetting { DRIVER_PATH("driver_path"), - DEVICE_NAME("device_name"); + DEVICE_NAME("device_name"), + WEB_TOKEN("yuzu_token"), + ; 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 b0944c5f18..70d510b328 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 @@ -274,6 +274,13 @@ 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 + ) + ) put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc)) put( SingleChoiceSetting( 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 4bfe503b1d..08080a26f2 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 @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.features.settings.ui @@ -200,6 +200,10 @@ class SettingsFragmentPresenter( add(IntSetting.LANGUAGE_INDEX.key) 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) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt index fc627473a2..2879310007 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt index b027547cef..cc95544d25 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt index 5229e25475..573549d84b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt index f39bb1affd..3ab171a8d4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt index ce5c85011d..47b84a403a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 2f5918b59a..a79ce4619a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt index 83ae5622a2..87b1533408 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt index 3cdee36d9c..7863e40ff5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index 8dbd69121b..f55edb418e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index 319d2d6a19..f9ecd6f49f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -1,10 +1,6 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - - package org.yuzu.yuzu_emu.fragments import android.Manifest @@ -128,7 +124,7 @@ class HomeSettingsFragment : Fragment() { R.string.multiplayer_description, R.drawable.ic_two_users, { - val action = mainActivity.displayMultiplayerDialog() + mainActivity.displayMultiplayerDialog() }, ) ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt index 9c39f7b166..33146c82ca 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt index 5fdcea29fa..52d9ef43d4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index bf622b9299..6828b32e84 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index 72a15ccef3..8e4b566766 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.model diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index eb1a329e8d..08eee8c815 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.model 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 1570f04468..d1f538b38a 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,16 +1,14 @@ -// Copyright 2024 Mandarine Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later - package org.yuzu.yuzu_emu.network import android.app.Activity import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.net.wifi.WifiManager +import android.os.Build import android.os.Handler import android.os.Looper import android.text.format.Formatter @@ -19,10 +17,27 @@ import androidx.preference.PreferenceManager import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.dialogs.ChatMessage +import java.net.Inet4Address object NetPlayManager { - external fun netPlayCreateRoom(ipAddress: String, port: Int, username: String, password: String, roomName: String, maxPlayers: Int): Int - external fun netPlayJoinRoom(ipAddress: String, port: Int, username: String, password: String): Int + external fun netPlayCreateRoom( + ipAddress: String, + port: Int, + username: String, + preferredGameName: String, + preferredGameId: Long, + password: String, + roomName: String, + maxPlayers: Int + ): Int + + external fun netPlayJoinRoom( + ipAddress: String, + port: Int, + username: String, + password: String + ): Int + external fun netPlayRoomInfo(): Array<String> external fun netPlayIsJoined(): Boolean external fun netPlayIsHostedRoom(): Boolean @@ -33,6 +48,28 @@ object NetPlayManager { external fun netPlayGetBanList(): Array<String> external fun netPlayBanUser(username: String) external fun netPlayUnbanUser(username: String) + external fun netPlayGetPublicRooms(): Array<String> + + data class RoomInfo( + val name: String, + val hasPassword: Boolean, + val maxPlayers: Int, + val ip: String, + val port: Int, + val description: String, + val owner: String, + val preferredGameId: Long, + val preferredGameName: String, + val members: MutableList<RoomMember> = mutableListOf() + ) + + data class RoomMember( + val username: String, + val nickname: String, + val gameId: Long, + val gameName: String + ) + private var messageListener: ((Int, String) -> Unit)? = null private var adapterRefreshListener: ((Int, String) -> Unit)? = null @@ -41,11 +78,57 @@ object NetPlayManager { messageListener = listener } + fun getPublicRooms(): List<RoomInfo> { + val roomData = netPlayGetPublicRooms() + val rooms = mutableMapOf<String, RoomInfo>() + + for (data in roomData) { + val parts = data.split("|") + + if (parts[0] == "MEMBER" && parts.size >= 6) { + val roomName = parts[1] + val member = RoomMember( + username = parts[2], + nickname = parts[3], + gameId = parts[4].toLongOrNull() ?: 0L, + gameName = parts[5] + ) + rooms[roomName]?.members?.add(member) + } else if (parts.size >= 9) { + val roomInfo = RoomInfo( + name = parts[0], + hasPassword = parts[1] == "1", + maxPlayers = parts[2].toIntOrNull() ?: 0, + ip = parts[3], + port = parts[4].toIntOrNull() ?: 0, + description = parts[5], + owner = parts[6], + preferredGameId = parts[7].toLongOrNull() ?: 0L, + preferredGameName = parts[8] + ) + rooms[roomInfo.name] = roomInfo + } + } + + return rooms.values.toList() + } + + fun refreshRoomListAsync(callback: (List<RoomInfo>) -> Unit) { + Thread { + val rooms = getPublicRooms() + + Handler(Looper.getMainLooper()).post { + callback(rooms) + } + }.start() + } + fun setOnAdapterRefreshListener(listener: (Int, String) -> Unit) { adapterRefreshListener = listener } - fun getUsername(activity: Context): String { val prefs = PreferenceManager.getDefaultSharedPreferences(activity) + fun getUsername(activity: Context): String { + val prefs = PreferenceManager.getDefaultSharedPreferences(activity) val name = "Eden${(Math.random() * 100).toInt()}" return prefs.getString("NetPlayUsername", name) ?: name } @@ -103,31 +186,36 @@ object NetPlayManager { if (parts.size == 2) { val nickname = parts[0].trim() val chatMessage = parts[1].trim() - addChatMessage(ChatMessage( - nickname = nickname, - username = "", - message = chatMessage - )) + addChatMessage( + ChatMessage( + nickname = nickname, + username = "", + message = chatMessage + ) + ) } } + NetPlayStatus.MEMBER_JOIN, NetPlayStatus.MEMBER_LEAVE, NetPlayStatus.MEMBER_KICKED, NetPlayStatus.MEMBER_BANNED -> { - addChatMessage(ChatMessage( - nickname = "System", - username = "", - message = message - )) + addChatMessage( + ChatMessage( + nickname = "System", + username = "", + message = message + ) + ) } } - Handler(Looper.getMainLooper()).post { - if (!isChatOpen) { - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } + Handler(Looper.getMainLooper()).post { + if (!isChatOpen) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } + } messageListener?.invoke(type, msg) @@ -159,34 +247,59 @@ object NetPlayManager { NetPlayStatus.ROOM_MODERATOR -> context.getString(R.string.multiplayer_room_moderator) NetPlayStatus.MEMBER_JOIN -> context.getString(R.string.multiplayer_member_join, msg) NetPlayStatus.MEMBER_LEAVE -> context.getString(R.string.multiplayer_member_leave, msg) - NetPlayStatus.MEMBER_KICKED -> context.getString(R.string.multiplayer_member_kicked, msg) - NetPlayStatus.MEMBER_BANNED -> context.getString(R.string.multiplayer_member_banned, msg) + NetPlayStatus.MEMBER_KICKED -> context.getString( + R.string.multiplayer_member_kicked, + msg + ) + + NetPlayStatus.MEMBER_BANNED -> context.getString( + R.string.multiplayer_member_banned, + msg + ) + NetPlayStatus.ADDRESS_UNBANNED -> context.getString(R.string.multiplayer_address_unbanned) NetPlayStatus.CHAT_MESSAGE -> msg else -> "" } } + fun isConnectedToWifi(activity: Activity): Boolean { + val connectivityManager = activity.getSystemService(ConnectivityManager::class.java) + val network = connectivityManager.activeNetwork + val capabilities = connectivityManager.getNetworkCapabilities(network) + return capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true + } + fun getIpAddressByWifi(activity: Activity): String { - var ipAddress = 0 - val wifiManager = activity.getSystemService(WifiManager::class.java) - val wifiInfo = wifiManager.connectionInfo - if (wifiInfo != null) { - ipAddress = wifiInfo.ipAddress - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // For Android 12 (API 31) and above + val connectivityManager = activity.getSystemService(ConnectivityManager::class.java) + val network = connectivityManager.activeNetwork + val capabilities = connectivityManager.getNetworkCapabilities(network) - if (ipAddress == 0) { - val dhcpInfo = wifiManager.dhcpInfo - if (dhcpInfo != null) { - ipAddress = dhcpInfo.ipAddress + if (capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) { + val linkProperties = connectivityManager.getLinkProperties(network) + linkProperties?.linkAddresses?.firstOrNull { it.address is Inet4Address }?.let { + return it.address.hostAddress ?: "192.168.0.1" + } } } - return if (ipAddress == 0) { - "192.168.0.1" - } else { - Formatter.formatIpAddress(ipAddress) + // For Android 11 (API 30) and below + try { + val connectivityManager = activity.getSystemService(ConnectivityManager::class.java) + val network = connectivityManager.activeNetwork + if (network != null) { + val linkProperties = connectivityManager.getLinkProperties(network) + linkProperties?.linkAddresses?.firstOrNull { it.address is Inet4Address }?.let { + return it.address.hostAddress ?: "192.168.0.1" + } + } + } catch (e: Exception) { + e.printStackTrace() } + + return "192.168.0.1" } fun getBanList(): List<String> { @@ -223,4 +336,4 @@ object NetPlayManager { const val ADDRESS_UNBANNED = 26 const val CHAT_MESSAGE = 27 } -}
\ No newline at end of file +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index b0121ad132..893aa5f41a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.ui diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index eb8837b8f1..3e3759d7eb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -1,10 +1,6 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - - package org.yuzu.yuzu_emu.ui.main import android.content.Intent @@ -72,9 +68,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { ThemeHelper.ThemeChangeListener(this) ThemeHelper.setTheme(this) - NativeLibrary.netPlayInit() - super.onCreate(savedInstanceState) + NativeLibrary.initMultiplayer() binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CompatUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CompatUtils.kt index 6467f7c931..640aa8f80c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CompatUtils.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CompatUtils.kt @@ -1,8 +1,4 @@ -// Copyright 2024 Mandarine Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.utils diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 579b600f1a..e27bc94696 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + package org.yuzu.yuzu_emu.utils import android.content.SharedPreferences @@ -13,11 +16,15 @@ import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.GameDir import org.yuzu.yuzu_emu.model.MinimalDocumentFile +import androidx.core.content.edit +import androidx.core.net.toUri object GameHelper { private const val KEY_OLD_GAME_PATH = "game_path" const val KEY_GAMES = "Games" + var cachedGameList = mutableListOf<Game>() + private lateinit var preferences: SharedPreferences fun getGames(): List<Game> { @@ -29,7 +36,7 @@ object GameHelper { val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: "" if (oldGamesDir.isNotEmpty()) { gameDirs.add(GameDir(oldGamesDir, true)) - preferences.edit().remove(KEY_OLD_GAME_PATH).apply() + preferences.edit() { remove(KEY_OLD_GAME_PATH) } } gameDirs.addAll(NativeConfig.getGameDirs()) @@ -44,7 +51,7 @@ object GameHelper { val badDirs = mutableListOf<Int>() gameDirs.forEachIndexed { index: Int, gameDir: GameDir -> - val gameDirUri = Uri.parse(gameDir.uriString) + val gameDirUri = gameDir.uriString.toUri() val isValid = FileUtil.isTreeUriValid(gameDirUri) if (isValid) { addGamesRecursive( @@ -72,11 +79,12 @@ object GameHelper { games.forEach { serializedGames.add(Json.encodeToString(it)) } - preferences.edit() - .remove(KEY_GAMES) - .putStringSet(KEY_GAMES, serializedGames) - .apply() + preferences.edit() { + remove(KEY_GAMES) + .putStringSet(KEY_GAMES, serializedGames) + } + cachedGameList = games.toMutableList() return games.toList() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt index 89ec3672ca..a035628588 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.utils diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index f18fc2e180..7a450c068a 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -68,12 +68,17 @@ #include "video_core/vulkan_common/vulkan_instance.h" #include "video_core/vulkan_common/vulkan_surface.h" #include "video_core/shader_notify.h" +#include "network/announce_multiplayer_session.h" #define jconst [[maybe_unused]] const auto #define jauto [[maybe_unused]] auto static EmulationSession s_instance; +//Abdroid Multiplayer which can be initialized with parameters +std::unique_ptr<AndroidMultiplayer> multiplayer{nullptr}; +std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; + EmulationSession::EmulationSession() { m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); } @@ -916,12 +921,33 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobje return ContentManager::AreKeysPresent(); } +JNIEXPORT void JNICALL +Java_org_yuzu_yuzu_1emu_NativeLibrary_initMultiplayer( + JNIEnv* env, [[maybe_unused]] jobject obj) { + if (multiplayer) { + return; + } + + announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>(); + + multiplayer = std::make_unique<AndroidMultiplayer>(s_instance.System(), announce_multiplayer_session); + multiplayer->NetworkInit(); +} + +JNIEXPORT jobjectArray JNICALL +Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetPublicRooms( + JNIEnv *env, [[maybe_unused]] jobject obj) { + return Common::Android::ToJStringArray(env, multiplayer->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 password, jstring room_name, jint max_players) { + jstring username, jstring preferredGameName, jlong preferredGameId, jstring password, + jstring room_name, jint max_players) { return static_cast<jint>( - NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port, - Common::Android::GetJString(env, username), Common::Android::GetJString(env, password), + 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)); } @@ -929,70 +955,63 @@ JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayJoi JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port, jstring username, jstring password) { return static_cast<jint>( - NetPlayJoinRoom(Common::Android::GetJString(env, ipaddress), port, + multiplayer->NetPlayJoinRoom(Common::Android::GetJString(env, ipaddress), port, Common::Android::GetJString(env, username), Common::Android::GetJString(env, password))); } JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayRoomInfo( JNIEnv* env, [[maybe_unused]] jobject obj) { - return Common::Android::ToJStringArray(env, NetPlayRoomInfo()); + return Common::Android::ToJStringArray(env, multiplayer->NetPlayRoomInfo()); } JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsJoined( [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { - return NetPlayIsJoined(); + return multiplayer->NetPlayIsJoined(); } JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsHostedRoom( [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { - return NetPlayIsHostedRoom(); + return multiplayer->NetPlayIsHostedRoom(); } JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlaySendMessage( JNIEnv* env, [[maybe_unused]] jobject obj, jstring msg) { - NetPlaySendMessage(Common::Android::GetJString(env, msg)); + multiplayer->NetPlaySendMessage(Common::Android::GetJString(env, msg)); } JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayKickUser( JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) { - NetPlayKickUser(Common::Android::GetJString(env, username)); + multiplayer->NetPlayKickUser(Common::Android::GetJString(env, username)); } JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayLeaveRoom( [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { - NetPlayLeaveRoom(); + multiplayer->NetPlayLeaveRoom(); } JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsModerator( [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { - return NetPlayIsModerator(); + return multiplayer->NetPlayIsModerator(); } JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetBanList( JNIEnv* env, [[maybe_unused]] jobject obj) { - return Common::Android::ToJStringArray(env, NetPlayGetBanList()); + return Common::Android::ToJStringArray(env, multiplayer->NetPlayGetBanList()); } JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayBanUser( JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) { - NetPlayBanUser(Common::Android::GetJString(env, username)); + multiplayer->NetPlayBanUser(Common::Android::GetJString(env, username)); } JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayUnbanUser( JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) { - NetPlayUnbanUser(Common::Android::GetJString(env, username)); -} - -JNIEXPORT void JNICALL -Java_org_yuzu_yuzu_1emu_NativeLibrary_netPlayInit( - JNIEnv* env, [[maybe_unused]] jobject obj) { - NetworkInit(&EmulationSession::GetInstance().System().GetRoomNetwork()); + multiplayer->NetPlayUnbanUser(Common::Android::GetJString(env, username)); } - } // extern "C" diff --git a/src/android/app/src/main/res/drawable/ic_ip.xml b/src/android/app/src/main/res/drawable/ic_ip.xml new file mode 100644 index 0000000000..ee02b72e29 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_ip.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="?attr/colorControlNormal" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z"/> +</vector> diff --git a/src/android/app/src/main/res/drawable/ic_joined.xml b/src/android/app/src/main/res/drawable/ic_joined.xml new file mode 100644 index 0000000000..c8f3dd5225 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_joined.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/> +</vector> diff --git a/src/android/app/src/main/res/drawable/ic_multiplayer.xml b/src/android/app/src/main/res/drawable/ic_multiplayer.xml new file mode 100644 index 0000000000..8248dc2c24 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_multiplayer.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FF000000" + android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/> +</vector> diff --git a/src/android/app/src/main/res/layout/dialog_lobby_browser.xml b/src/android/app/src/main/res/layout/dialog_lobby_browser.xml new file mode 100644 index 0000000000..a016e48823 --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_lobby_browser.xml @@ -0,0 +1,220 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout 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"> + + <com.google.android.material.appbar.AppBarLayout + android:id="@+id/appbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:elevation="0dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_scrollFlags="scroll|enterAlways|snap"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingTop="16dp" + android:paddingHorizontal="16dp" + android:gravity="center_vertical"> + + <Space + android:layout_width="48dp" + android:layout_height="0dp" /> + + <TextView + android:id="@+id/title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center" + android:text="@string/multiplayer_room_browser" + android:textAppearance="@style/TextAppearance.Material3.TitleLarge" /> + + <FrameLayout + android:layout_width="48dp" + android:layout_height="48dp"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/refresh_button" + style="@style/Widget.Material3.Button.IconButton" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:text="@string/refresh" + app:icon="@drawable/ic_refresh" /> + + <ProgressBar + android:id="@+id/progress_bar" + style="?android:attr/progressBarStyleSmall" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:visibility="gone" /> + </FrameLayout> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/search_background" + style="?attr/materialCardViewFilledStyle" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_marginStart="16dp" + android:layout_marginTop="12dp" + app:cardCornerRadius="24dp"> + + <LinearLayout + android:id="@+id/search_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginStart="16dp" + android:layout_marginEnd="48dp" + android:orientation="horizontal"> + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:layout_marginEnd="16dp" + android:src="@drawable/ic_search" + app:tint="?attr/colorOnSurfaceVariant" /> + + <EditText + android:id="@+id/search_text" + android:layout_width="180dp" + android:layout_height="match_parent" + android:background="@android:color/transparent" + android:hint="@string/multiplayer_search_public_lobbies" + android:imeOptions="flagNoFullscreen" + android:inputType="text" + android:maxLines="1" /> + + </LinearLayout> + + <ImageView + android:id="@+id/clear_button" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical|end" + android:layout_marginEnd="16dp" + android:background="?attr/selectableItemBackground" + android:src="@drawable/ic_clear" + android:visibility="invisible" + app:tint="?attr/colorOnSurfaceVariant" + tools:visibility="visible" /> + + </com.google.android.material.card.MaterialCardView> + + <com.google.android.material.button.MaterialButton + android:id="@+id/btn_submit" + style="@style/Widget.Material3.Button.ElevatedButton" + android:layout_width="110dp" + android:layout_height="48dp" + android:layout_marginTop="12dp" + android:text="@string/submit" /> + </LinearLayout> + + + <HorizontalScrollView + android:id="@+id/horizontalScrollView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fadingEdge="horizontal" + android:scrollbars="none" + android:layout_marginVertical="8dp" + android:clipToPadding="false" + android:paddingHorizontal="16dp"> + + <LinearLayout + android:id="@+id/chips" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:clipToPadding="false"> + + <com.google.android.material.chip.Chip + android:id="@+id/chip_hide_empty" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:checkable="true" + android:checked="false" + android:text="@string/multiplayer_hide_empty_rooms" + app:chipCornerRadius="16dp" /> + + <com.google.android.material.chip.Chip + android:id="@+id/chip_hide_full" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:checkable="true" + android:checked="false" + android:text="@string/multiplayer_hide_full_rooms" + app:chipCornerRadius="16dp" /> + + </LinearLayout> + </HorizontalScrollView> + </LinearLayout> + </com.google.android.material.appbar.AppBarLayout> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/room_list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false" + android:contentDescription="@string/room_list" + android:paddingBottom="16dp" + android:scrollbars="vertical" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + + <LinearLayout + android:id="@+id/empty_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="vertical" + android:gravity="center" + android:padding="32dp" + android:visibility="gone"> + + <ImageView + android:layout_width="72dp" + android:layout_height="72dp" + android:layout_marginBottom="16dp" + android:src="@drawable/ic_refresh" + android:alpha="0.5" + app:tint="?attr/colorOnSurface" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/multiplayer_no_rooms_found" + android:textAppearance="@style/TextAppearance.Material3.TitleMedium" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/multiplayer_tap_refresh_to_check_again" + android:textAppearance="@style/TextAppearance.Material3.BodyMedium" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/empty_refresh_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="@string/refresh" + app:icon="@drawable/ic_refresh" /> + </LinearLayout> + +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/src/android/app/src/main/res/layout/dialog_multiplayer_connect.xml b/src/android/app/src/main/res/layout/dialog_multiplayer_connect.xml index 36a77d3951..a33f65896e 100644 --- a/src/android/app/src/main/res/layout/dialog_multiplayer_connect.xml +++ b/src/android/app/src/main/res/layout/dialog_multiplayer_connect.xml @@ -28,7 +28,7 @@ <ImageView android:layout_width="140dp" - android:layout_height="140dp" + android:layout_height="114dp" android:layout_gravity="center" android:layout_marginTop="16dp" android:layout_marginBottom="24dp" @@ -38,19 +38,45 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal" + android:orientation="horizontal"> + + <Space + android:layout_width="20dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/btn_lobby_browser" + style="@style/Widget.Material3.Button.ElevatedButton" + android:layout_width="175dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/multiplayer_public_room" + app:cornerRadius="16dp" + app:icon="@drawable/ic_search" /> + + <Space + android:layout_width="20dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" - android:layout_marginBottom="8dp"> + android:layout_marginBottom="8dp" + android:orientation="horizontal"> <com.google.android.material.button.MaterialButton android:id="@+id/btn_join" - style="@style/Widget.Material3.Button.TonalButton" + style="@style/Widget.Material3.Button.ElevatedButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/multiplayer_join_room" - app:icon="@drawable/ic_install" - app:cornerRadius="16dp" /> + app:cornerRadius="16dp" + app:icon="@drawable/ic_install" /> <Space android:layout_width="16dp" @@ -58,13 +84,13 @@ <com.google.android.material.button.MaterialButton android:id="@+id/btn_create" - style="@style/Widget.Material3.Button" + style="@style/Widget.Material3.Button.ElevatedButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/multiplayer_create_room" - app:icon="@drawable/ic_add" - app:cornerRadius="16dp" /> + app:cornerRadius="16dp" + app:icon="@drawable/ic_add" /> </LinearLayout> 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 53afda9319..c79be8a2ca 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 @@ -60,6 +60,21 @@ </com.google.android.material.textfield.TextInputLayout> <com.google.android.material.textfield.TextInputLayout + style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu" + android:id="@+id/preferred_game_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp" + android:hint="@string/multiplayer_preferred_game_name"> + + <com.google.android.material.textfield.MaterialAutoCompleteTextView + android:id="@+id/dropdown_preferred_game_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="none" /> + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/multiplayer_password" diff --git a/src/android/app/src/main/res/layout/dialog_password_input.xml b/src/android/app/src/main/res/layout/dialog_password_input.xml new file mode 100644 index 0000000000..f21a63952b --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_password_input.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <com.google.android.material.textfield.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/multiplayer_password" + android:layout_marginTop="8dp"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/password_input" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" /> + + </com.google.android.material.textfield.TextInputLayout> +</LinearLayout>
\ No newline at end of file diff --git a/src/android/app/src/main/res/layout/dropdown_item.xml b/src/android/app/src/main/res/layout/dropdown_item.xml new file mode 100644 index 0000000000..e620712e11 --- /dev/null +++ b/src/android/app/src/main/res/layout/dropdown_item.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/dropdown_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:ellipsize="end" + android:maxLines="1" + android:textAppearance="?attr/textAppearanceSubtitle1" /> diff --git a/src/android/app/src/main/res/layout/item_lobby_room.xml b/src/android/app/src/main/res/layout/item_lobby_room.xml new file mode 100644 index 0000000000..b69090e212 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_lobby_room.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.google.android.material.card.MaterialCardView 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" + style="?attr/materialCardViewFilledStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="24dp" + android:layout_marginHorizontal="12dp" + android:background="?attr/selectableItemBackground" + android:backgroundTint="?attr/colorSurfaceVariant" + android:clickable="true" + android:focusable="true"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="12dp"> + + <ImageView + android:id="@+id/lock_icon" + android:layout_width="20dp" + android:layout_height="20dp" + android:contentDescription="@string/multiplayer_password_protected" + android:src="@drawable/ic_lock" + android:visibility="gone" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:tint="?attr/colorOnSurface" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginEnd="8dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintEnd_toStartOf="@id/lock_icon" + app:layout_constraintBottom_toBottomOf="parent"> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/room_name" + style="@style/TextAppearance.Material3.BodyMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="viewStart" + android:textSize="16sp" + android:textStyle="bold" + tools:text="Room Name" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/room_owner" + style="@style/TextAppearance.Material3.BodySmall" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="5dp" + android:textAlignment="viewStart" + android:textSize="14sp" + tools:text="Hosted by: Owner" /> + + <LinearLayout + android:id="@+id/game_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="5dp" + android:orientation="horizontal"> + + <ImageView + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_gravity="center_vertical" + android:contentDescription="@string/multiplayer_game" + android:src="@drawable/ic_controller" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/game_name" + style="@style/TextAppearance.Material3.LabelMedium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:ellipsize="end" + android:singleLine="true" + android:textStyle="bold" + tools:text="Game Name" /> + </LinearLayout> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + + <ImageView + android:layout_width="16dp" + android:layout_height="16dp" + android:src="@drawable/ic_user" + android:contentDescription="@string/multiplayer_player_count" + app:tint="?attr/colorAccent" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/player_count" + style="@style/TextAppearance.Material3.BodyMedium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:textColor="?attr/colorAccent" + tools:text="2/4" /> + </LinearLayout> + </androidx.constraintlayout.widget.ConstraintLayout> +</com.google.android.material.card.MaterialCardView>
\ No newline at end of file diff --git a/src/android/app/src/main/res/values-fr-rSN/strings.xml b/src/android/app/src/main/res/values-fr-rSN/strings.xml new file mode 100644 index 0000000000..96b0b3a560 --- /dev/null +++ b/src/android/app/src/main/res/values-fr-rSN/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Eden</string> +</resources>
\ No newline at end of file diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index e953666d20..ed5dfca596 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -399,6 +399,11 @@ <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> + <!-- 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="network">Network</string> + <!-- Graphics settings strings --> <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> diff --git a/src/common/android/android_common.cpp b/src/common/android/android_common.cpp index 0df2dd7b35..a1cf081695 100644 --- a/src/common/android/android_common.cpp +++ b/src/common/android/android_common.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "android_common.h" diff --git a/src/common/android/android_common.h b/src/common/android/android_common.h index 05e2c2515b..a16add3bdf 100644 --- a/src/common/android/android_common.h +++ b/src/common/android/android_common.h @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index 649fafca09..2625e55c35 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include <jni.h> @@ -94,10 +94,10 @@ static jmethodID s_yuzu_input_device_get_supports_vibration; static jmethodID s_yuzu_input_device_vibrate; static jmethodID s_yuzu_input_device_get_axes; static jmethodID s_yuzu_input_device_has_keys; + static jmethodID s_add_netplay_message; static jmethodID s_clear_chat; - static constexpr jint JNI_VERSION = JNI_VERSION_1_6; namespace Common::Android { @@ -405,7 +405,6 @@ jmethodID ClearChat() { return s_clear_chat; } - #ifdef __cplusplus extern "C" { #endif @@ -605,7 +604,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { // UnInitialize applets SoftwareKeyboard::CleanupJNI(env); - NetworkShutdown(); + AndroidMultiplayer::NetworkShutdown(); } #ifdef __cplusplus diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h index 0b64bbc866..cbfbf36be8 100644 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/common/android/multiplayer/multiplayer.cpp b/src/common/android/multiplayer/multiplayer.cpp index 41d46d71b3..fe6408fced 100644 --- a/src/common/android/multiplayer/multiplayer.cpp +++ b/src/common/android/multiplayer/multiplayer.cpp @@ -1,8 +1,4 @@ -// Copyright 2024 Mandarine Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "common/android/id_cache.h" @@ -19,35 +15,38 @@ #include <chrono> namespace IDCache = Common::Android; -Network::RoomNetwork* room_network; -void AddNetPlayMessage(jint type, jstring msg) { +AndroidMultiplayer::AndroidMultiplayer(Core::System& system_, + std::shared_ptr<Core::AnnounceMultiplayerSession> session) + : system{system_}, announce_multiplayer_session(session) {} + +AndroidMultiplayer::~AndroidMultiplayer() = default; + +void AndroidMultiplayer::AddNetPlayMessage(jint type, jstring msg) { IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetAddNetPlayMessage(), type, msg); } -void AddNetPlayMessage(int type, const std::string& msg) { +void AndroidMultiplayer::AddNetPlayMessage(int type, const std::string& msg) { JNIEnv* env = IDCache::GetEnvForThread(); AddNetPlayMessage(type, Common::Android::ToJString(env, msg)); } -void ClearChat() { +void AndroidMultiplayer::ClearChat() { IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::ClearChat()); } - -bool NetworkInit(Network::RoomNetwork* room_network_) { - room_network = room_network_; - bool result = room_network->Init(); +bool AndroidMultiplayer::NetworkInit() { + bool result = Network::Init(); if (!result) { return false; } - if (auto member = room_network->GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { // register the network structs to use in slots and signals - member->BindOnStateChanged([](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; @@ -65,7 +64,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) { AddNetPlayMessage(static_cast<int>(status), msg); } }); - member->BindOnError([](const Network::RoomMember::Error& error) { + member->BindOnError([this](const Network::RoomMember::Error& error) { NetPlayStatus status; std::string msg; switch (error) { @@ -108,7 +107,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) { } AddNetPlayMessage(static_cast<int>(status), msg); }); - member->BindOnStatusMessageReceived([](const Network::StatusMessageEntry& status_message) { + member->BindOnStatusMessageReceived([this](const Network::StatusMessageEntry& status_message) { NetPlayStatus status = NetPlayStatus::NO_ERROR; std::string msg(status_message.nickname); switch (status_message.type) { @@ -130,7 +129,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) { } AddNetPlayMessage(static_cast<int>(status), msg); }); - member->BindOnChatMessageReceived([](const Network::ChatEntry& chat) { + member->BindOnChatMessageReceived([this](const Network::ChatEntry& chat) { NetPlayStatus status = NetPlayStatus::CHAT_MESSAGE; std::string msg(chat.nickname); msg += ": "; @@ -141,13 +140,11 @@ bool NetworkInit(Network::RoomNetwork* room_network_) { return true; } -NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port, - const std::string& username, const std::string& password, +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) { - - __android_log_print(ANDROID_LOG_INFO, "NetPlay", "NetPlayCreateRoom called with ipaddress: %s, port: %d, username: %s, room_name: %s, max_players: %d", ipaddress.c_str(), port, username.c_str(), room_name.c_str(), max_players); - - auto member = room_network->GetRoomMember().lock(); + auto member = Network::GetRoomMember().lock(); if (!member) { return NetPlayStatus::NETWORK_ERROR; } @@ -156,7 +153,7 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port, return NetPlayStatus::ALREADY_IN_ROOM; } - auto room = room_network->GetRoom().lock(); + auto room = Network::GetRoom().lock(); if (!room) { return NetPlayStatus::NETWORK_ERROR; } @@ -167,14 +164,14 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port, // Placeholder game info const AnnounceMultiplayerRoom::GameInfo game{ - .name = "Default Game", - .id = 0, // Default program ID + .name = preferredGameName, + .id = preferredGameId, }; 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, nullptr, {})) { + static_cast<u32>(std::min(max_players, 16)), username, game, std::make_unique<Network::VerifyUser::NullBackend>(), {})) { return NetPlayStatus::CREATE_ROOM_ERROR; } @@ -197,9 +194,9 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port, return NetPlayStatus::CREATE_ROOM_ERROR; } -NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port, +NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string& ipaddress, int port, const std::string& username, const std::string& password) { - auto member = room_network->GetRoomMember().lock(); + auto member = Network::GetRoomMember().lock(); if (!member) { return NetPlayStatus::NETWORK_ERROR; } @@ -229,8 +226,8 @@ NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port, return NetPlayStatus::WRONG_PASSWORD; } -void NetPlaySendMessage(const std::string& msg) { - if (auto room = room_network->GetRoomMember().lock()) { +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) { @@ -240,8 +237,8 @@ void NetPlaySendMessage(const std::string& msg) { } } -void NetPlayKickUser(const std::string& username) { - if (auto room = room_network->GetRoomMember().lock()) { +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) { @@ -253,8 +250,8 @@ void NetPlayKickUser(const std::string& username) { } } -void NetPlayBanUser(const std::string& username) { - if (auto room = room_network->GetRoomMember().lock()) { +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) { @@ -266,15 +263,15 @@ void NetPlayBanUser(const std::string& username) { } } -void NetPlayUnbanUser(const std::string& username) { - if (auto room = room_network->GetRoomMember().lock()) { +void AndroidMultiplayer::NetPlayUnbanUser(const std::string& username) { + if (auto room = Network::GetRoomMember().lock()) { room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username); } } -std::vector<std::string> NetPlayRoomInfo() { +std::vector<std::string> AndroidMultiplayer::NetPlayRoomInfo() { std::vector<std::string> info_list; - if (auto room = room_network->GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { auto members = room->GetMemberInformation(); if (!members.empty()) { // name and max players @@ -289,8 +286,8 @@ std::vector<std::string> NetPlayRoomInfo() { return info_list; } -bool NetPlayIsJoined() { - auto member = room_network->GetRoomMember().lock(); +bool AndroidMultiplayer::NetPlayIsJoined() { + auto member = Network::GetRoomMember().lock(); if (!member) { return false; } @@ -299,17 +296,17 @@ bool NetPlayIsJoined() { member->GetState() == Network::RoomMember::State::Moderator); } -bool NetPlayIsHostedRoom() { - if (auto room = room_network->GetRoom().lock()) { +bool AndroidMultiplayer::NetPlayIsHostedRoom() { + if (auto room = Network::GetRoom().lock()) { return room->GetState() == Network::Room::State::Open; } return false; } -void NetPlayLeaveRoom() { - if (auto room = room_network->GetRoom().lock()) { +void AndroidMultiplayer::NetPlayLeaveRoom() { + if (auto room = Network::GetRoom().lock()) { // if you are in a room, leave it - if (auto member = room_network->GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { member->Leave(); } @@ -322,21 +319,52 @@ void NetPlayLeaveRoom() { } } -void NetworkShutdown() { - room_network->Shutdown(); +void AndroidMultiplayer::NetworkShutdown() { + Network::Shutdown(); } -bool NetPlayIsModerator() { - auto member = room_network->GetRoomMember().lock(); +bool AndroidMultiplayer::NetPlayIsModerator() { + auto member = Network::GetRoomMember().lock(); if (!member) { return false; } return member->GetState() == Network::RoomMember::State::Moderator; } -std::vector<std::string> NetPlayGetBanList() { +std::vector<std::string> AndroidMultiplayer::NetPlayGetPublicRooms() { + std::vector<std::string> room_list; + + if (auto session = announce_multiplayer_session.lock()) { + auto rooms = session->GetRoomList(); + for (const auto &room: rooms) { + room_list.push_back(room.information.name + "|" + + (room.has_password ? "1" : "0") + "|" + + std::to_string(room.information.member_slots) + "|" + + room.ip + "|" + + std::to_string(room.information.port) + "|" + + room.information.description + "|" + + room.information.host_username + "|" + + std::to_string(room.information.preferred_game.id) + "|" + + room.information.preferred_game.name + "|" + + room.information.preferred_game.version); + + for (const auto &member: room.members) { + room_list.push_back("MEMBER|" + room.information.name + "|" + + member.username + "|" + + member.nickname + "|" + + std::to_string(member.game.id) + "|" + + member.game.name); + } + } + + } + return room_list; + +} + +std::vector<std::string> AndroidMultiplayer::NetPlayGetBanList() { std::vector<std::string> ban_list; - if (auto room = room_network->GetRoom().lock()) { + if (auto room = Network::GetRoom().lock()) { auto [username_bans, ip_bans] = room->GetBanList(); // Add username bans diff --git a/src/common/android/multiplayer/multiplayer.h b/src/common/android/multiplayer/multiplayer.h index 4f32c514e0..591d3be8fd 100644 --- a/src/common/android/multiplayer/multiplayer.h +++ b/src/common/android/multiplayer/multiplayer.h @@ -1,8 +1,4 @@ -// Copyright 2024 Mandarine Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -12,6 +8,12 @@ #include <common/common_types.h> #include <network/network.h> +#include <network/announce_multiplayer_session.h> + +namespace Core { + class System; + class AnnounceMultiplayerSession; +} enum class NetPlayStatus : s32 { NO_ERROR, @@ -48,21 +50,53 @@ enum class NetPlayStatus : s32 { CHAT_MESSAGE, }; -bool NetworkInit(Network::RoomNetwork* room_network); -NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port, - const std::string& username, const std::string& password, - const std::string& room_name, int max_players); -NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port, - const std::string& username, const std::string& password); -std::vector<std::string> NetPlayRoomInfo(); -bool NetPlayIsJoined(); -bool NetPlayIsHostedRoom(); -bool NetPlayIsModerator(); -void NetPlaySendMessage(const std::string& msg); -void NetPlayKickUser(const std::string& username); -void NetPlayBanUser(const std::string& username); -void NetPlayLeaveRoom(); -std::string NetPlayGetConsoleId(); -void NetworkShutdown(); -std::vector<std::string> NetPlayGetBanList(); -void NetPlayUnbanUser(const std::string& username); +class AndroidMultiplayer { +public: + explicit AndroidMultiplayer(Core::System& system, + std::shared_ptr<Core::AnnounceMultiplayerSession> session); + ~AndroidMultiplayer(); + + bool NetworkInit(); + + void AddNetPlayMessage(int status, const std::string& msg); + void AddNetPlayMessage(jint type, jstring msg); + + void ClearChat(); + + 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); + + NetPlayStatus NetPlayJoinRoom(const std::string &ipaddress, int port, + const std::string &username, const std::string &password); + + std::vector<std::string> NetPlayRoomInfo(); + + bool NetPlayIsJoined(); + + bool NetPlayIsHostedRoom(); + + bool NetPlayIsModerator(); + + void NetPlaySendMessage(const std::string &msg); + + void NetPlayKickUser(const std::string &username); + + void NetPlayBanUser(const std::string &username); + + void NetPlayLeaveRoom(); + + static void NetworkShutdown(); + + std::vector<std::string> NetPlayGetBanList(); + + void NetPlayUnbanUser(const std::string &username); + + std::vector<std::string> NetPlayGetPublicRooms(); + +private: + Core::System& system; + static std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) ; + std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; +}; diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h index 6f7ccfa7f0..419890481a 100644 --- a/src/common/announce_multiplayer_room.h +++ b/src/common/announce_multiplayer_room.h @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/core/core.cpp b/src/core/core.cpp index 61b8182c08..0058c09b6e 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include <array> @@ -111,7 +111,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, struct System::Impl { explicit Impl(System& system) - : kernel{system}, fs_controller{system}, hid_core{}, room_network{}, cpu_manager{system}, + : kernel{system}, fs_controller{system}, hid_core{}, cpu_manager{system}, reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {} u64 program_id; @@ -421,7 +421,7 @@ struct System::Impl { } LoadOverrides(program_id); - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { Network::GameInfo game_info; game_info.name = name; game_info.id = params.program_id; @@ -466,7 +466,7 @@ struct System::Impl { stop_event = {}; Network::RestartSocketOperations(); - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { Network::GameInfo game_info{}; room_member->SendGameInfo(game_info); } @@ -520,7 +520,6 @@ struct System::Impl { std::unique_ptr<Core::DeviceMemory> device_memory; std::unique_ptr<AudioCore::AudioCore> audio_core; Core::HID::HIDCore hid_core; - Network::RoomNetwork room_network; CpuManager cpu_manager; std::atomic_bool is_powered_on{}; @@ -979,14 +978,6 @@ const Core::Debugger& System::GetDebugger() const { return *impl->debugger; } -Network::RoomNetwork& System::GetRoomNetwork() { - return impl->room_network; -} - -const Network::RoomNetwork& System::GetRoomNetwork() const { - return impl->room_network; -} - Tools::RenderdocAPI& System::GetRenderdocAPI() { return *impl->renderdoc_api; } diff --git a/src/core/core.h b/src/core/core.h index 0cc1108b80..99b1c5641f 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #pragma once #include <cstddef> @@ -104,10 +107,6 @@ namespace Core::HID { class HIDCore; } -namespace Network { -class RoomNetwork; -} - namespace Tools { class RenderdocAPI; } @@ -380,12 +379,6 @@ public: [[nodiscard]] Core::Debugger& GetDebugger(); [[nodiscard]] const Core::Debugger& GetDebugger() const; - /// Gets a mutable reference to the Room Network. - [[nodiscard]] Network::RoomNetwork& GetRoomNetwork(); - - /// Gets an immutable reference to the Room Network. - [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; - [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI(); void SetExitLocked(bool locked); diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp index b9db19618a..7d677e6664 100644 --- a/src/core/hle/service/ldn/lan_discovery.cpp +++ b/src/core/hle/service/ldn/lan_discovery.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu 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 + #include "core/hle/service/ldn/lan_discovery.h" #include "core/internal_network/network.h" #include "core/internal_network/network_interface.h" @@ -33,9 +36,8 @@ void LanStation::OverrideInfo() { node_info->is_connected = connected ? 1 : 0; } -LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_) - : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}), - room_network{room_network_} {} +LANDiscovery::LANDiscovery() + : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}){} LANDiscovery::~LANDiscovery() { if (inited) { @@ -410,7 +412,7 @@ void LANDiscovery::OnNetworkInfoChanged() { Network::IPv4Address LANDiscovery::GetLocalIp() const { Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF}; - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { local_ip = room_member->GetFakeIpAddress(); } @@ -468,7 +470,7 @@ void LANDiscovery::SendBroadcast(Network::LDNPacketType type) { } void LANDiscovery::SendPacket(const Network::LDNPacket& packet) { - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { room_member->SendLdnPacket(packet); } diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h index 8f7a8dfc45..0d15583172 100644 --- a/src/core/hle/service/ldn/lan_discovery.h +++ b/src/core/hle/service/ldn/lan_discovery.h @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu 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 + #pragma once #include <array> @@ -47,7 +50,7 @@ class LANDiscovery { public: using LanEventFunc = std::function<void()>; - LANDiscovery(Network::RoomNetwork& room_network_); + LANDiscovery(); ~LANDiscovery(); State GetState() const; @@ -127,7 +130,5 @@ protected: std::optional<Ipv4Address> host_ip; LanEventFunc lan_event; - - Network::RoomNetwork& room_network; }; } // namespace Service::LDN diff --git a/src/core/hle/service/ldn/user_local_communication_service.cpp b/src/core/hle/service/ldn/user_local_communication_service.cpp index f28368962f..69170a879d 100644 --- a/src/core/hle/service/ldn/user_local_communication_service.cpp +++ b/src/core/hle/service/ldn/user_local_communication_service.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #include <memory> #include "core/core.h" @@ -22,7 +25,7 @@ namespace Service::LDN { IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& system_) : ServiceFramework{system_, "IUserLocalCommunicationService"}, service_context{system, "IUserLocalCommunicationService"}, - room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} { + lan_discovery{} { // clang-format off static const FunctionInfo functions[] = { {0, C<&IUserLocalCommunicationService::GetState>, "GetState"}, @@ -65,7 +68,7 @@ IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& sys IUserLocalCommunicationService::~IUserLocalCommunicationService() { if (is_initialized) { - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { room_member->Unbind(ldn_packet_received); } } @@ -103,7 +106,7 @@ Result IUserLocalCommunicationService::GetIpv4Address(Out<Ipv4Address> out_curre *out_subnet_mask = {Network::TranslateIPv4(network_interface->subnet_mask)}; // When we're connected to a room, spoof the hosts IP address - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { *out_current_address = room_member->GetFakeIpAddress(); } @@ -280,7 +283,7 @@ Result IUserLocalCommunicationService::Initialize(ClientProcessId aruid) { const auto network_interface = Network::GetSelectedNetworkInterface(); R_UNLESS(network_interface, ResultAirplaneModeEnabled); - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { ldn_packet_received = room_member->BindOnLdnPacketReceived( [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); }); } else { @@ -295,7 +298,7 @@ Result IUserLocalCommunicationService::Initialize(ClientProcessId aruid) { Result IUserLocalCommunicationService::Finalize() { LOG_INFO(Service_LDN, "called"); - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { room_member->Unbind(ldn_packet_received); } diff --git a/src/core/hle/service/ldn/user_local_communication_service.h b/src/core/hle/service/ldn/user_local_communication_service.h index 6698d10d22..55320fbc91 100644 --- a/src/core/hle/service/ldn/user_local_communication_service.h +++ b/src/core/hle/service/ldn/user_local_communication_service.h @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #pragma once #include "core/hle/service/cmif_types.h" @@ -13,10 +16,6 @@ namespace Core { class System; } -namespace Network { -class RoomNetwork; -} - namespace Service::LDN { class IUserLocalCommunicationService final @@ -91,7 +90,6 @@ private: KernelHelpers::ServiceContext service_context; Kernel::KEvent* state_change_event; - Network::RoomNetwork& room_network; LANDiscovery lan_discovery; // Callback identifier for the OnLDNPacketReceived event. diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index d758e9aa3d..bb6e719cb0 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu 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 + #include "core/core.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/ipc_helpers.h" @@ -406,7 +409,7 @@ void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) { }(); // When we're connected to a room, spoof the hosts IP address - if (auto room_member = network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { network_profile_data.ip_setting_data.ip_address_setting.ip_address = room_member->GetFakeIpAddress(); @@ -454,7 +457,7 @@ void IGeneralService::GetCurrentIpAddress(HLERequestContext& ctx) { } // When we're connected to a room, spoof the hosts IP address - if (auto room_member = network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { ipv4 = room_member->GetFakeIpAddress(); } @@ -513,7 +516,7 @@ void IGeneralService::GetCurrentIpConfigInfo(HLERequestContext& ctx) { }(); // When we're connected to a room, spoof the hosts IP address - if (auto room_member = network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { ip_config_info.ip_address_setting.ip_address = room_member->GetFakeIpAddress(); } @@ -643,7 +646,7 @@ void IGeneralService::GetCurrentAccessPoint(HLERequestContext& ctx) { } IGeneralService::IGeneralService(Core::System& system_) - : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} { + : ServiceFramework{system_, "IGeneralService"} { // clang-format off static const FunctionInfo functions[] = { {1, &IGeneralService::GetClientId, "GetClientId"}, diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index 4682a4a273..7071fa90d7 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu 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 + #pragma once #include "core/hle/service/service.h" @@ -9,10 +12,6 @@ namespace Core { class System; } -namespace Network { -class RoomNetwork; -} - namespace Service::NIFM { void LoopProcess(Core::System& system); @@ -42,8 +41,6 @@ private: void ConfirmSystemAvailability(HLERequestContext& ctx); void SetBackgroundRequestEnabled(HLERequestContext& ctx); void GetCurrentAccessPoint(HLERequestContext& ctx); - - Network::RoomNetwork& network; }; } // namespace Service::NIFM diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index ef03203349..4be70e0559 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu 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 + #include <array> #include <memory> #include <utility> @@ -508,9 +511,9 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco LOG_INFO(Service, "New socket fd={}", fd); - auto room_member = room_network.GetRoomMember().lock(); + auto room_member = Network::GetRoomMember().lock(); if (room_member && room_member->IsConnected()) { - descriptor.socket = std::make_shared<Network::ProxySocket>(room_network); + descriptor.socket = std::make_shared<Network::ProxySocket>(); } else { descriptor.socket = std::make_shared<Network::Socket>(); } @@ -970,7 +973,7 @@ void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) { } BSD::BSD(Core::System& system_, const char* name) - : ServiceFramework{system_, name}, room_network{system_.GetRoomNetwork()} { + : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, &BSD::RegisterClient, "RegisterClient"}, @@ -1012,7 +1015,7 @@ BSD::BSD(Core::System& system_, const char* name) RegisterHandlers(functions); - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { proxy_packet_received = room_member->BindOnProxyPacketReceived( [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); }); } else { @@ -1021,7 +1024,7 @@ BSD::BSD(Core::System& system_, const char* name) } BSD::~BSD() { - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { room_member->Unbind(proxy_packet_received); } } diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 4f69d382c9..ed6b576a65 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu 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 + #pragma once #include <memory> @@ -179,8 +182,6 @@ private: std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors; - Network::RoomNetwork& room_network; - /// Callback to parse and handle a received wifi packet. void OnProxyPacketReceived(const Network::ProxyPacket& packet); diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 3d99bdf5ce..0f5ef0741f 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include <algorithm> diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp index ce0dee9705..eba1e28c5e 100644 --- a/src/core/internal_network/socket_proxy.cpp +++ b/src/core/internal_network/socket_proxy.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu 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 + #include <chrono> #include <thread> @@ -18,7 +21,7 @@ namespace Network { -ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {} +ProxySocket::ProxySocket() noexcept {} ProxySocket::~ProxySocket() { if (fd == INVALID_SOCKET) { @@ -187,7 +190,7 @@ std::pair<s32, Errno> ProxySocket::Send(std::span<const u8> message, int flags) } void ProxySocket::SendPacket(ProxyPacket& packet) { - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(), packet.data.size()); @@ -205,7 +208,7 @@ std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message return {static_cast<s32>(message.size()), Errno::SUCCESS}; } - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (!room_member->IsConnected()) { return {static_cast<s32>(message.size()), Errno::SUCCESS}; } @@ -222,7 +225,7 @@ std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message // If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address, // replace it with a "fake" routing address if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) { - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { packet.local_endpoint.ip = room_member->GetFakeIpAddress(); } } diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h index 70500cf4a2..93cf0757af 100644 --- a/src/core/internal_network/socket_proxy.h +++ b/src/core/internal_network/socket_proxy.h @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu 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 + #pragma once #include <mutex> @@ -14,11 +17,9 @@ namespace Network { -class RoomNetwork; - class ProxySocket : public SocketBase { public: - explicit ProxySocket(RoomNetwork& room_network_) noexcept; + explicit ProxySocket() noexcept; ~ProxySocket() override; void HandleProxyPacket(const ProxyPacket& packet) override; @@ -92,8 +93,6 @@ private: Protocol protocol; std::mutex packets_mutex; - - RoomNetwork& room_network; }; } // namespace Network diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index f02407780f..afba0ad4c8 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/src/dedicated_room/yuzu_room.cpp @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2025 eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include <chrono> @@ -55,22 +55,22 @@ static void PrintHelp(const char* argv0) { LOG_INFO(Network, "Usage: {}" " [options] <filename>\n" - "--room-name The name of the room\n" - "--room-description The room description\n" - "--bind-address The bind address for the room\n" - "--port The port used for the room\n" - "--max_members The maximum number of players for this room\n" - "--password The password for the room\n" - "--preferred-game The preferred game for this room\n" - "--preferred-game-id The preferred game-id for this room\n" - "--username The username used for announce\n" - "--token The token used for announce\n" - "--web-api-url yuzu Web API url\n" - "--ban-list-file The file for storing the room ban list\n" - "--log-file The file for storing the room log\n" - "--enable-yuzu-mods Allow yuzu Community Moderators to moderate on your room\n" - "-h, --help Display this help and exit\n" - "-v, --version Output version information and exit\n", + "-n, --room-name The name of the room\n" + "-d, --room-description The room description\n" + "-s, --bind-address The bind address for the room\n" + "-p, --port The port used for the room\n" + "-m, --max-members The maximum number of players for this room\n" + "-w, --password The password for the room\n" + "-g, --preferred-game The preferred game for this room\n" + "-i, --preferred-game-id The preferred game-id for this room\n" + "-u, --username The username used for announce\n" + "-t, --token The token used for announce\n" + "-a, --web-api-url yuzu Web API url\n" + "-b, --ban-list-file The file for storing the room ban list\n" + "-l, --log-file The file for storing the room log\n" + "-e, --enable-mods Allow Community Moderators to moderate on your room\n" + "-h, --help Display this help and exit\n" + "-v, --version Output version information and exit\n", argv0); } @@ -209,19 +209,18 @@ void LaunchRoom(int argc, char** argv, bool called_by_option) std::string token; std::string web_api_url; std::string ban_list_file; - std::string log_file = "yuzu-room.log"; + std::string log_file = "eden-room.log"; std::string bind_address; u64 preferred_game_id = 0; u32 port = Network::DefaultRoomPort; u32 max_members = 16; - // TODO(alekpop): Implement this into main executable, for --room and a few others. static struct option long_options[] = { {"room-name", required_argument, 0, 'n'}, {"room-description", required_argument, 0, 'd'}, {"bind-address", required_argument, 0, 's'}, {"port", required_argument, 0, 'p'}, - {"max_members", required_argument, 0, 'm'}, + {"max-members", required_argument, 0, 'm'}, {"password", required_argument, 0, 'w'}, {"preferred-game", required_argument, 0, 'g'}, {"preferred-game-id", required_argument, 0, 'i'}, @@ -230,7 +229,7 @@ void LaunchRoom(int argc, char** argv, bool called_by_option) {"web-api-url", required_argument, 0, 'a'}, {"ban-list-file", required_argument, 0, 'b'}, {"log-file", required_argument, 0, 'l'}, - {"enable-yuzu-mods", no_argument, 0, 'e'}, + {"enable-mods", no_argument, 0, 'e'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, // Entry option @@ -243,7 +242,9 @@ void LaunchRoom(int argc, char** argv, bool called_by_option) while (optind < argc) { int arg = getopt_long(argc, argv, "n:d:s:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index); if (arg != -1) { - switch (static_cast<char>(arg)) { + char carg = static_cast<char>(arg); + + switch (carg) { case 'n': room_name.assign(optarg); break; @@ -289,6 +290,8 @@ void LaunchRoom(int argc, char** argv, bool called_by_option) case 'v': PrintVersion(); std::exit(0); + default: + break; } } } @@ -373,9 +376,8 @@ void LaunchRoom(int argc, char** argv, bool called_by_option) verify_backend = std::make_unique<Network::VerifyUser::NullBackend>(); } - Network::RoomNetwork network{}; - network.Init(); - if (auto room = network.GetRoom().lock()) { + Network::Init(); + if (auto room = Network::GetRoom().lock()) { AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, .id = preferred_game_id}; if (!room->Create(room_name, room_description, bind_address, static_cast<u16>(port), @@ -385,7 +387,7 @@ void LaunchRoom(int argc, char** argv, bool called_by_option) std::exit(-1); } LOG_INFO(Network, "Room is open. Close with Q+Enter..."); - auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>(network); + auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>(); if (announce) { announce_session->Start(); } @@ -407,7 +409,7 @@ void LaunchRoom(int argc, char** argv, bool called_by_option) } room->Destroy(); } - network.Shutdown(); + Network::Shutdown(); detached_tasks.WaitForAllTasks(); std::exit(0); } diff --git a/src/network/announce_multiplayer_session.cpp b/src/network/announce_multiplayer_session.cpp index 6737ce85ad..35082cbd31 100644 --- a/src/network/announce_multiplayer_session.cpp +++ b/src/network/announce_multiplayer_session.cpp @@ -1,6 +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 + #include <chrono> #include <future> #include <vector> @@ -19,8 +22,7 @@ namespace Core { // Time between room is announced to web_service static constexpr std::chrono::seconds announce_time_interval(15); -AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_) - : room_network{room_network_} { +AnnounceMultiplayerSession::AnnounceMultiplayerSession() { #ifdef ENABLE_WEB_SERVICE backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(), Settings::values.yuzu_username.GetValue(), @@ -31,7 +33,7 @@ AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& roo } WebService::WebResult AnnounceMultiplayerSession::Register() { - auto room = room_network.GetRoom().lock(); + auto room = Network::GetRoom().lock(); if (!room) { return WebService::WebResult{WebService::WebResult::Code::LibError, "Network is not initialized", ""}; @@ -120,7 +122,7 @@ void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { std::future<WebService::WebResult> future; while (!shutdown_event.WaitUntil(update_time)) { update_time += announce_time_interval; - auto room = room_network.GetRoom().lock(); + auto room = Network::GetRoom().lock(); if (!room) { break; } diff --git a/src/network/announce_multiplayer_session.h b/src/network/announce_multiplayer_session.h index db790f7d2d..9d9673d97a 100644 --- a/src/network/announce_multiplayer_session.h +++ b/src/network/announce_multiplayer_session.h @@ -1,6 +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 + #pragma once #include <atomic> @@ -15,7 +18,6 @@ namespace Network { class Room; -class RoomNetwork; } // namespace Network namespace Core { @@ -28,7 +30,8 @@ namespace Core { class AnnounceMultiplayerSession { public: using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>; - AnnounceMultiplayerSession(Network::RoomNetwork& room_network_); + + AnnounceMultiplayerSession(); ~AnnounceMultiplayerSession(); /** @@ -91,8 +94,6 @@ private: std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend; std::atomic_bool registered = false; ///< Whether the room has been registered - - Network::RoomNetwork& room_network; }; } // namespace Core diff --git a/src/network/network.cpp b/src/network/network.cpp index 6652a186bb..0314b51d2c 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -1,6 +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 + #include "common/assert.h" #include "common/logging/log.h" #include "enet/enet.h" @@ -8,40 +11,38 @@ namespace Network { -RoomNetwork::RoomNetwork() { - m_room = std::make_shared<Room>(); - m_room_member = std::make_shared<RoomMember>(); -} +static std::shared_ptr<RoomMember> g_room_member; ///< RoomMember (Client) for network games +static std::shared_ptr<Room> g_room; ///< Room (Server) for network games -bool RoomNetwork::Init() { +bool Init() { if (enet_initialize() != 0) { LOG_ERROR(Network, "Error initializing ENet"); return false; } - m_room = std::make_shared<Room>(); - m_room_member = std::make_shared<RoomMember>(); + g_room = std::make_shared<Room>(); + g_room_member = std::make_shared<RoomMember>(); LOG_DEBUG(Network, "initialized OK"); return true; } -std::weak_ptr<Room> RoomNetwork::GetRoom() { - return m_room; +std::weak_ptr<Room> GetRoom() { + return g_room; } -std::weak_ptr<RoomMember> RoomNetwork::GetRoomMember() { - return m_room_member; +std::weak_ptr<RoomMember> GetRoomMember() { + return g_room_member; } -void RoomNetwork::Shutdown() { - if (m_room_member) { - if (m_room_member->IsConnected()) - m_room_member->Leave(); - m_room_member.reset(); +void Shutdown() { + if (g_room_member) { + if (g_room_member->IsConnected()) + g_room_member->Leave(); + g_room_member.reset(); } - if (m_room) { - if (m_room->GetState() == Room::State::Open) - m_room->Destroy(); - m_room.reset(); + if (g_room) { + if (g_room->GetState() == Room::State::Open) + g_room->Destroy(); + g_room.reset(); } enet_deinitialize(); LOG_DEBUG(Network, "shutdown OK"); diff --git a/src/network/network.h b/src/network/network.h index e4de207b27..1e1a19a3b9 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -1,6 +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 + #pragma once #include <memory> @@ -9,25 +12,16 @@ namespace Network { -class RoomNetwork { -public: - RoomNetwork(); - - /// Initializes and registers the network device, the room, and the room member. - bool Init(); - - /// Returns a pointer to the room handle - std::weak_ptr<Room> GetRoom(); +/// Initializes and registers the network device, the room, and the room member. +bool Init(); - /// Returns a pointer to the room member handle - std::weak_ptr<RoomMember> GetRoomMember(); +/// Returns a pointer to the room handle +std::weak_ptr<Room> GetRoom(); - /// Unregisters the network device, the room, and the room member and shut them down. - void Shutdown(); +/// Returns a pointer to the room member handle +std::weak_ptr<RoomMember> GetRoomMember(); -private: - std::shared_ptr<RoomMember> m_room_member; ///< RoomMember (Client) for network games - std::shared_ptr<Room> m_room; ///< Room (Server) for network games -}; +/// Unregisters the network device, the room, and the room member and shut them down. +void Shutdown(); } // namespace Network diff --git a/src/network/room.cpp b/src/network/room.cpp index 49d7ed3313..a4acdad859 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/network/room.h b/src/network/room.h index 780629ada3..a2ae792431 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 440ef3c46d..1319511989 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +# SPDX-FileCopyrightText: 2025 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later set(CMAKE_AUTOMOC ON) diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 8d773753c1..fb75096a7c 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include <algorithm> diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp index a6c0cd6c77..dd8a75ef8e 100644 --- a/src/yuzu/configuration/configure_touch_from_button.cpp +++ b/src/yuzu/configuration/configure_touch_from_button.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include <QInputDialog> diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 8c3bbf1af4..67221d2485 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "yuzu/configuration/configure_ui.h" diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index a513e1c1b0..f7902fb773 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -9,30 +9,30 @@ #include "yuzu/configuration/configure_web.h" #include "yuzu/uisettings.h" -static constexpr char token_delimiter{':'}; - -static std::string GenerateDisplayToken(const std::string& username, const std::string& token) { - if (username.empty() || token.empty()) { - return {}; - } - - const std::string unencoded_display_token{username + token_delimiter + token}; - QByteArray b{unencoded_display_token.c_str()}; - QByteArray b64 = b.toBase64(); - return b64.toStdString(); -} - -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)); -} - -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); -} +// static constexpr char token_delimiter{':'}; + +// static std::string GenerateDisplayToken(const std::string& username, const std::string& token) { +// if (username.empty() || token.empty()) { +// return {}; +// } + +// const std::string unencoded_display_token{username + token_delimiter + token}; +// QByteArray b{unencoded_display_token.c_str()}; +// QByteArray b64 = b.toBase64(); +// return b64.toStdString(); +// } + +// 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)); +// } + +// 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); +// } ConfigureWeb::ConfigureWeb(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) { @@ -66,7 +66,7 @@ void ConfigureWeb::RetranslateUI() { "color:#039be5;\">Sign up</span></a>")); ui->web_token_info_link->setText( - tr("<a href='https://eden-emulator.github.io/wiki/yuzu-web-service/'><span style=\"text-decoration: " + 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>")); } @@ -76,14 +76,10 @@ void ConfigureWeb::SetConfiguration() { ui->web_signup_link->setOpenExternalLinks(true); ui->web_token_info_link->setOpenExternalLinks(true); - if (Settings::values.yuzu_username.GetValue().empty()) { - ui->username->setText(tr("Unspecified")); - } else { - ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); - } - - ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken( - Settings::values.yuzu_username.GetValue(), Settings::values.yuzu_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()))); // Connect after setting the values, to avoid calling OnLoginChanged now connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); @@ -95,15 +91,8 @@ void ConfigureWeb::SetConfiguration() { void ConfigureWeb::ApplyConfiguration() { UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); - if (user_verified) { - Settings::values.yuzu_username = - UsernameFromDisplayToken(ui->edit_token->text().toStdString()); - Settings::values.yuzu_token = TokenFromDisplayToken(ui->edit_token->text().toStdString()); - } else { - QMessageBox::warning( - this, tr("Token not verified"), - tr("Token was not verified. The change to your token has not been saved.")); - } + Settings::values.yuzu_username = ui->edit_username->text().toStdString(); + Settings::values.yuzu_token = ui->edit_token->text().toStdString(); } void ConfigureWeb::OnLoginChanged() { @@ -124,30 +113,34 @@ void ConfigureWeb::OnLoginChanged() { } void ConfigureWeb::VerifyLogin() { - 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...")); + 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...")); } 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.")); - } + // 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.")); + // } } void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) { diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui index 942fb05b26..ac9b6954cd 100644 --- a/src/yuzu/configuration/configure_web.ui +++ b/src/yuzu/configuration/configure_web.ui @@ -43,7 +43,7 @@ </sizepolicy> </property> <property name="layoutDirection"> - <enum>Qt::RightToLeft</enum> + <enum>Qt::LayoutDirection::RightToLeft</enum> </property> <property name="text"> <string>Verify</string> @@ -52,13 +52,23 @@ </item> <item row="2" column="0"> <widget class="QLabel" name="web_signup_link"> + <property name="enabled"> + <bool>false</bool> + </property> <property name="text"> <string>Sign up</string> </property> </widget> </item> <item row="0" column="1" colspan="3"> - <widget class="QLabel" name="username"/> + <widget class="QLineEdit" name="edit_username"> + <property name="maxLength"> + <number>20</number> + </property> + <property name="echoMode"> + <enum>QLineEdit::EchoMode::Normal</enum> + </property> + </widget> </item> <item row="1" column="0"> <widget class="QLabel" name="label_token"> @@ -83,7 +93,7 @@ <number>80</number> </property> <property name="echoMode"> - <enum>QLineEdit::Password</enum> + <enum>QLineEdit::EchoMode::Normal</enum> </property> </widget> </item> @@ -97,7 +107,7 @@ <item row="2" column="2"> <spacer name="horizontalSpacer"> <property name="orientation"> - <enum>Qt::Horizontal</enum> + <enum>Qt::Orientation::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> @@ -143,7 +153,7 @@ <item> <spacer name="verticalSpacer"> <property name="orientation"> - <enum>Qt::Vertical</enum> + <enum>Qt::Orientation::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 3fec5b7f61..1cfa37c9e4 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include <regex> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7e31fdae4d..c1f71237ad 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project +// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include <cinttypes> @@ -351,7 +351,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(system->GetProfileManager()); - system->GetRoomNetwork().Init(); + Network::Init(); RegisterMetaTypes(); @@ -5183,7 +5183,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { render_window->close(); multiplayer_state->Close(); system->HIDCore().UnloadInputDevices(); - system->GetRoomNetwork().Shutdown(); + Network::Shutdown(); QWidget::closeEvent(event); } diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 4463616b45..5925266b40 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -1,6 +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 + #include <array> #include <future> #include <QColor> @@ -27,8 +30,7 @@ class ChatMessage { public: - explicit ChatMessage(const Network::ChatEntry& chat, Network::RoomNetwork& room_network, - QTime ts = {}) { + explicit ChatMessage(const Network::ChatEntry& chat, QTime ts = {}) { /// Convert the time to their default locale defined format QLocale locale; timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat); @@ -38,7 +40,7 @@ public: // Check for user pings QString cur_nickname, cur_username; - if (auto room = room_network.GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { cur_nickname = QString::fromStdString(room->GetNickname()); cur_username = QString::fromStdString(room->GetUsername()); } @@ -202,10 +204,9 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::C ChatRoom::~ChatRoom() = default; -void ChatRoom::Initialize(Network::RoomNetwork* room_network_) { - room_network = room_network_; +void ChatRoom::Initialize() { // setup the callbacks for network updates - if (auto member = room_network->GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { member->BindOnChatMessageReceived( [this](const Network::ChatEntry& chat) { emit ChatReceived(chat); }); member->BindOnStatusMessageReceived( @@ -239,7 +240,7 @@ void ChatRoom::AppendChatMessage(const QString& msg) { } void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) { - if (auto room = room_network->GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { auto members = room->GetMemberInformation(); auto it = std::find_if(members.begin(), members.end(), [&nickname](const Network::RoomMember::MemberInformation& member) { @@ -259,7 +260,7 @@ bool ChatRoom::ValidateMessage(const std::string& msg) { void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) { // TODO(B3N30): change title - if (auto room_member = room_network->GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { SetPlayerList(room_member->GetMemberInformation()); } } @@ -278,7 +279,7 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) { if (!ValidateMessage(chat.message)) { return; } - if (auto room = room_network->GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { // get the id of the player auto members = room->GetMemberInformation(); auto it = std::find_if(members.begin(), members.end(), @@ -296,7 +297,7 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) { return; } auto player = std::distance(members.begin(), it); - ChatMessage m(chat, *room_network); + ChatMessage m(chat); if (m.ContainsPing()) { emit UserPinged(); } @@ -335,7 +336,7 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_ } void ChatRoom::OnSendChat() { - if (auto room_member = room_network->GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (!room_member->IsConnected()) { return; } @@ -357,7 +358,7 @@ void ChatRoom::OnSendChat() { LOG_INFO(Network, "Cannot find self in the player list when sending a message."); } auto player = std::distance(members.begin(), it); - ChatMessage m(chat, *room_network); + ChatMessage m(chat); room_member->SendChatMessage(message); AppendChatMessage(m.GetPlayerChatMessage(player)); ui->chat_message->clear(); @@ -451,7 +452,7 @@ void ChatRoom::PopupContextMenu(const QPoint& menu_location) { } std::string cur_nickname; - if (auto room = room_network->GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { cur_nickname = room->GetNickname(); } diff --git a/src/yuzu/multiplayer/chat_room.h b/src/yuzu/multiplayer/chat_room.h index dd71ea4cdd..f295ea7a7c 100644 --- a/src/yuzu/multiplayer/chat_room.h +++ b/src/yuzu/multiplayer/chat_room.h @@ -1,6 +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 + #pragma once #include <memory> @@ -30,7 +33,7 @@ class ChatRoom : public QWidget { public: explicit ChatRoom(QWidget* parent); - void Initialize(Network::RoomNetwork* room_network); + void Initialize(); void RetranslateUi(); void SetPlayerList(const Network::RoomMember::MemberList& member_list); void Clear(); @@ -66,7 +69,6 @@ private: std::unique_ptr<Ui::ChatRoom> ui; std::unordered_set<std::string> block_list; std::unordered_map<std::string, QPixmap> icon_cache; - Network::RoomNetwork* room_network; }; Q_DECLARE_METATYPE(Network::ChatEntry); diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp index caf34a414f..3453ae34d5 100644 --- a/src/yuzu/multiplayer/client_room.cpp +++ b/src/yuzu/multiplayer/client_room.cpp @@ -1,6 +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 + #include <future> #include <QColor> #include <QImage> @@ -18,14 +21,14 @@ #include "yuzu/multiplayer/moderation_dialog.h" #include "yuzu/multiplayer/state.h" -ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_) +ClientRoomWindow::ClientRoomWindow(QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique<Ui::ClientRoom>()), room_network{room_network_} { + ui(std::make_unique<Ui::ClientRoom>()) { ui->setupUi(this); - ui->chat->Initialize(&room_network); + ui->chat->Initialize(); // setup the callbacks for network updates - if (auto member = room_network.GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { member->BindOnRoomInformationChanged( [this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); }); member->BindOnStateChanged( @@ -44,7 +47,7 @@ ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_n ui->disconnect->setDefault(false); ui->disconnect->setAutoDefault(false); connect(ui->moderation, &QPushButton::clicked, [this] { - ModerationDialog dialog(room_network, this); + ModerationDialog dialog(this); dialog.exec(); }); ui->moderation->setDefault(false); @@ -90,7 +93,7 @@ void ClientRoomWindow::Disconnect() { } void ClientRoomWindow::UpdateView() { - if (auto member = room_network.GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { if (member->IsConnected()) { ui->chat->Enable(); ui->disconnect->setEnabled(true); diff --git a/src/yuzu/multiplayer/client_room.h b/src/yuzu/multiplayer/client_room.h index f338e3c592..6990c56e50 100644 --- a/src/yuzu/multiplayer/client_room.h +++ b/src/yuzu/multiplayer/client_room.h @@ -1,6 +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 + #pragma once #include "yuzu/multiplayer/chat_room.h" @@ -13,7 +16,7 @@ class ClientRoomWindow : public QDialog { Q_OBJECT public: - explicit ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_); + explicit ClientRoomWindow(QWidget* parent); ~ClientRoomWindow(); void RetranslateUi(); @@ -35,5 +38,4 @@ private: QStandardItemModel* player_list; std::unique_ptr<Ui::ClientRoom> ui; - Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 74da97e218..693cffe0d0 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -1,6 +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 + #include <QComboBox> #include <QFuture> #include <QIntValidator> @@ -24,8 +27,7 @@ enum class ConnectionType : u8 { TraversalServer, IP }; DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{ - system.GetRoomNetwork()} { + ui(std::make_unique<Ui::DirectConnect>()), system{system_} { ui->setupUi(this); @@ -71,7 +73,7 @@ void DirectConnectWindow::Connect() { return; } } - if (const auto member = room_network.GetRoomMember().lock()) { + if (const auto member = Network::GetRoomMember().lock()) { // Prevent the user from trying to join a room while they are already joining. if (member->GetState() == Network::RoomMember::State::Joining) { return; @@ -104,7 +106,7 @@ void DirectConnectWindow::Connect() { // attempt to connect in a different thread QFuture<void> f = QtConcurrent::run([&] { - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { auto port = UISettings::values.multiplayer_port.GetValue(); room_member->Join(ui->nickname->text().toStdString(), ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP, @@ -129,7 +131,7 @@ void DirectConnectWindow::EndConnecting() { void DirectConnectWindow::OnConnection() { EndConnecting(); - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { close(); } diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h index b8f66cfb2b..e7efaecbd2 100644 --- a/src/yuzu/multiplayer/direct_connect.h +++ b/src/yuzu/multiplayer/direct_connect.h @@ -1,6 +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 + #pragma once #include <memory> @@ -45,5 +48,4 @@ private: std::unique_ptr<Ui::DirectConnect> ui; Validation validation; Core::System& system; - Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index 09f783ad9f..fd047b4b5a 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -1,6 +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 + #include <future> #include <QColor> #include <QImage> @@ -32,8 +35,7 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list, Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), ui(std::make_unique<Ui::HostRoom>()), - announce_multiplayer_session(session), system{system_}, room_network{ - system.GetRoomNetwork()} { + announce_multiplayer_session(session), system{system_} { ui->setupUi(this); // set up validation for all of the fields @@ -137,7 +139,7 @@ void HostRoomWindow::Host() { return; } } - if (auto member = room_network.GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { if (member->GetState() == Network::RoomMember::State::Joining) { return; } else if (member->IsConnected()) { @@ -161,7 +163,7 @@ void HostRoomWindow::Host() { if (ui->load_ban_list->isChecked()) { ban_list = UISettings::values.multiplayer_ban_list; } - if (auto room = room_network.GetRoom().lock()) { + if (auto room = Network::GetRoom().lock()) { const bool created = room->Create(ui->room_name->text().toStdString(), ui->room_description->toPlainText().toStdString(), "", port, password, @@ -190,7 +192,7 @@ void HostRoomWindow::Host() { QString::fromStdString(result.result_string), QMessageBox::Ok); ui->host->setEnabled(true); - if (auto room = room_network.GetRoom().lock()) { + if (auto room = Network::GetRoom().lock()) { room->Destroy(); } return; @@ -206,7 +208,7 @@ void HostRoomWindow::Host() { WebService::Client client(Settings::values.web_api_url.GetValue(), Settings::values.yuzu_username.GetValue(), Settings::values.yuzu_token.GetValue()); - if (auto room = room_network.GetRoom().lock()) { + if (auto room = Network::GetRoom().lock()) { token = client.GetExternalJWT(room->GetVerifyUID()).returned_data; } if (token.empty()) { diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h index ae816e2e03..d9b08d2c8c 100644 --- a/src/yuzu/multiplayer/host_room.h +++ b/src/yuzu/multiplayer/host_room.h @@ -1,6 +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 + #pragma once #include <memory> @@ -59,7 +62,6 @@ private: ComboBoxProxyModel* proxy; Validation validation; Core::System& system; - Network::RoomNetwork& room_network; }; /** diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 52df2456e5..bc91145b97 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -1,6 +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 + #include <QInputDialog> #include <QList> #include <QtConcurrent/QtConcurrentRun> @@ -28,8 +31,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), ui(std::make_unique<Ui::Lobby>()), - announce_multiplayer_session(session), system{system_}, room_network{ - system.GetRoomNetwork()} { + announce_multiplayer_session(session), system{system_} { ui->setupUi(this); // setup the watcher for background connections @@ -146,7 +148,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { } } - if (const auto member = room_network.GetRoomMember().lock()) { + if (const auto member = Network::GetRoomMember().lock()) { // Prevent the user from trying to join a room while they are already joining. if (member->GetState() == Network::RoomMember::State::Joining) { return; @@ -184,7 +186,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString(); // attempt to connect in a different thread - QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid, this] { + 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() && @@ -200,7 +202,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { } } #endif - if (auto room_member = room_network.GetRoomMember().lock()) { + if (auto room_member = Network::GetRoomMember().lock()) { room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password, token); } diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index e78c9cae3c..4d4e7bd5e4 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -1,6 +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 + #pragma once #include <memory> @@ -102,7 +105,6 @@ private: QFutureWatcher<void>* watcher; Validation validation; Core::System& system; - Network::RoomNetwork& room_network; }; /** diff --git a/src/yuzu/multiplayer/moderation_dialog.cpp b/src/yuzu/multiplayer/moderation_dialog.cpp index c9b8ed397a..1a06d661d5 100644 --- a/src/yuzu/multiplayer/moderation_dialog.cpp +++ b/src/yuzu/multiplayer/moderation_dialog.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 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 + #include <QStandardItem> #include <QStandardItemModel> #include "network/network.h" @@ -16,13 +19,13 @@ enum { }; } -ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent) - : QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()), room_network{room_network_} { +ModerationDialog::ModerationDialog(QWidget* parent) + : QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()) { ui->setupUi(this); qRegisterMetaType<Network::Room::BanList>(); - if (auto member = room_network.GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { callback_handle_status_message = member->BindOnStatusMessageReceived( [this](const Network::StatusMessageEntry& status_message) { emit StatusMessageReceived(status_message); @@ -55,20 +58,20 @@ ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget* ModerationDialog::~ModerationDialog() { if (callback_handle_status_message) { - if (auto room = room_network.GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { room->Unbind(callback_handle_status_message); } } if (callback_handle_ban_list) { - if (auto room = room_network.GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { room->Unbind(callback_handle_ban_list); } } } void ModerationDialog::LoadBanList() { - if (auto room = room_network.GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { ui->refresh->setEnabled(false); ui->refresh->setText(tr("Refreshing")); ui->unban->setEnabled(false); @@ -97,7 +100,7 @@ void ModerationDialog::PopulateBanList(const Network::Room::BanList& ban_list) { } void ModerationDialog::SendUnbanRequest(const QString& subject) { - if (auto room = room_network.GetRoomMember().lock()) { + if (auto room = Network::GetRoomMember().lock()) { room->SendModerationRequest(Network::IdModUnban, subject.toStdString()); } } diff --git a/src/yuzu/multiplayer/moderation_dialog.h b/src/yuzu/multiplayer/moderation_dialog.h index e9e5daff79..4d32a6895e 100644 --- a/src/yuzu/multiplayer/moderation_dialog.h +++ b/src/yuzu/multiplayer/moderation_dialog.h @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 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 + #pragma once #include <memory> @@ -19,7 +22,7 @@ class ModerationDialog : public QDialog { Q_OBJECT public: - explicit ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent = nullptr); + explicit ModerationDialog(QWidget* parent = nullptr); ~ModerationDialog(); signals: @@ -36,8 +39,6 @@ private: QStandardItemModel* model; Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message; Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list; - - Network::RoomNetwork& room_network; }; Q_DECLARE_METATYPE(Network::Room::BanList); diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index d82ca9aee5..d5529712b0 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 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 + #include <QAction> #include <QApplication> #include <QIcon> @@ -22,8 +25,8 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_, QAction* leave_room_, QAction* show_room_, Core::System& system_) : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_), - show_room(show_room_), system{system_}, room_network{system.GetRoomNetwork()} { - if (auto member = room_network.GetRoomMember().lock()) { + show_room(show_room_), system{system_} { + if (auto member = Network::GetRoomMember().lock()) { // register the network structs to use in slots and signals state_callback_handle = member->BindOnStateChanged( [this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); }); @@ -37,7 +40,7 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis qRegisterMetaType<Network::RoomMember::State>(); qRegisterMetaType<Network::RoomMember::Error>(); qRegisterMetaType<WebService::WebResult>(); - announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>(room_network); + announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>(); announce_multiplayer_session->BindErrorCallback( [this](const WebService::WebResult& result) { emit AnnounceFailed(result); }); connect(this, &MultiplayerState::AnnounceFailed, this, &MultiplayerState::OnAnnounceFailed); @@ -62,13 +65,13 @@ MultiplayerState::~MultiplayerState() = default; void MultiplayerState::Close() { if (state_callback_handle) { - if (auto member = room_network.GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { member->Unbind(state_callback_handle); } } if (error_callback_handle) { - if (auto member = room_network.GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { member->Unbind(error_callback_handle); } } @@ -256,9 +259,9 @@ void MultiplayerState::OnCreateRoom() { bool MultiplayerState::OnCloseRoom() { if (!NetworkMessage::WarnCloseRoom()) return false; - if (auto room = room_network.GetRoom().lock()) { + if (auto room = Network::GetRoom().lock()) { // if you are in a room, leave it - if (auto member = room_network.GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { member->Leave(); LOG_DEBUG(Frontend, "Left the room (as a client)"); } @@ -292,10 +295,10 @@ void MultiplayerState::HideNotification() { } void MultiplayerState::OnOpenNetworkRoom() { - if (auto member = room_network.GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { if (member->IsConnected()) { if (client_room == nullptr) { - client_room = new ClientRoomWindow(this, room_network); + client_room = new ClientRoomWindow(this); connect(client_room, &ClientRoomWindow::ShowNotification, this, &MultiplayerState::ShowNotification); } diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h index d6149838f8..4d3dde2162 100644 --- a/src/yuzu/multiplayer/state.h +++ b/src/yuzu/multiplayer/state.h @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 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 + #pragma once #include <QWidget> @@ -105,7 +108,6 @@ private: bool show_notification = false; Core::System& system; - Network::RoomNetwork& room_network; }; Q_DECLARE_METATYPE(WebService::WebResult); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 35dc58e4e9..005f5bf1db 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: 2014 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 + #include <chrono> #include <iostream> #include <memory> @@ -404,7 +407,7 @@ int main(int argc, char** argv) { } if (use_multiplayer) { - if (auto member = system.GetRoomNetwork().GetRoomMember().lock()) { + if (auto member = Network::GetRoomMember().lock()) { member->BindOnChatMessageReceived(OnMessageReceived); member->BindOnStatusMessageReceived(OnStatusMessageReceived); member->BindOnStateChanged(OnStateChanged); |
