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>
This commit is contained in:
Aleksandr Popovich 2025-06-05 18:59:47 +00:00 committed by crueter
parent 7e13da47af
commit 76fa525592
99 changed files with 1470 additions and 498 deletions

7
.gitignore vendored
View file

@ -1,6 +1,9 @@
# SPDX-FileCopyrightText: 2013 Citra Emulator Project # SPDX-FileCopyrightText: 2013 Citra Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# Build directory # Build directory
[Bb]uild*/ [Bb]uild*/
doc-build/ doc-build/
@ -36,3 +39,7 @@ CMakeSettings.json
# Windows global filetypes # Windows global filetypes
Thumbs.db Thumbs.db
# Artifacts
eden-windows-msvc
artifacts
*.AppImage*

View file

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
cmake_minimum_required(VERSION 3.22) cmake_minimum_required(VERSION 3.22)

View file

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
# This function downloads a binary library package from our external repo. # This function downloads a binary library package from our external repo.

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -162,7 +165,7 @@ android {
arguments( arguments(
"-DENABLE_QT=0", // Don't use QT "-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL "-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 "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DYUZU_USE_BUNDLED_VCPKG=ON", "-DYUZU_USE_BUNDLED_VCPKG=ON",
"-DYUZU_USE_BUNDLED_FFMPEG=ON", "-DYUZU_USE_BUNDLED_FFMPEG=ON",
@ -176,9 +179,9 @@ android {
} }
} }
tasks.create<Delete>("ktlintReset") { tasks.register<Delete>("ktlintReset", fun Delete.() {
delete(File(buildDir.path + File.separator + "intermediates/ktLint")) delete(File(layout.buildDirectory.toString() + File.separator + "intermediates/ktLint"))
} })
val showFormatHelp = { val showFormatHelp = {
logger.lifecycle( logger.lifecycle(

View file

@ -3,6 +3,9 @@
<!-- <!--
SPDX-FileCopyrightText: 2023 yuzu Emulator Project SPDX-FileCopyrightText: 2023 yuzu Emulator Project
SPDX-License-Identifier: GPL-3.0-or-later 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 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.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" /> <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.INTERNET" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// 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-License-Identifier: GPL-3.0-or-later
@ -270,8 +270,7 @@ object NativeLibrary {
NetPlayManager.clearChat() NetPlayManager.clearChat()
} }
external fun initMultiplayer()
external fun netPlayInit()
@Keep @Keep
@JvmStatic @JvmStatic

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// 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-License-Identifier: GPL-3.0-or-later

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.adapters package org.yuzu.yuzu_emu.adapters

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -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)
}

View file

@ -1,12 +1,9 @@
// Copyright 2024 Mandarine Project // SPDX-FileCopyrightText: 2025 Eden Emulator 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-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.dialogs package org.yuzu.yuzu_emu.dialogs
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import android.content.res.Configuration import android.content.res.Configuration
@ -16,6 +13,7 @@ import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.Toast import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager 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.databinding.ItemTextNetplayBinding
import org.yuzu.yuzu_emu.utils.CompatUtils import org.yuzu.yuzu_emu.utils.CompatUtils
import org.yuzu.yuzu_emu.network.NetPlayManager import org.yuzu.yuzu_emu.network.NetPlayManager
import org.yuzu.yuzu_emu.utils.GameHelper
class NetPlayDialog(context: Context) : BottomSheetDialog(context) { class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
private lateinit var adapter: NetPlayAdapter private lateinit var adapter: NetPlayAdapter
private val gameNameList: MutableList<Array<String>> = mutableListOf()
private val gameIdList: MutableList<Array<Long>> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -71,6 +72,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
else -> { else -> {
DialogMultiplayerConnectBinding.inflate(layoutInflater).apply { DialogMultiplayerConnectBinding.inflate(layoutInflater).apply {
setContentView(root) 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 { btnCreate.setOnClickListener {
showNetPlayInputDialog(true) showNetPlayInputDialog(true)
dismiss() dismiss()
@ -79,6 +91,10 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
showNetPlayInputDialog(false) showNetPlayInputDialog(false)
dismiss() dismiss()
} }
btnLobbyBrowser.setOnClickListener {
LobbyBrowser(context).show()
dismiss()
}
} }
} }
} }
@ -208,10 +224,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
override fun getItemCount() = netPlayItems.size override fun getItemCount() = netPlayItems.size
} }
@SuppressLint("NotifyDataSetChanged")
fun refreshAdapterItems() { fun refreshAdapterItems() {
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
NetPlayManager.setOnAdapterRefreshListener() { type, msg -> NetPlayManager.setOnAdapterRefreshListener() { _, _ ->
handler.post { handler.post {
adapter.netPlayItems.clear() adapter.netPlayItems.clear()
adapter.loadMultiplayerMenu() adapter.loadMultiplayerMenu()
@ -244,6 +261,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
binding.ipPort.setText(NetPlayManager.getRoomPort(activity)) binding.ipPort.setText(NetPlayManager.getRoomPort(activity))
binding.username.setText(NetPlayManager.getUsername(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.roomName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.maxPlayersContainer.visibility = if (isCreateRoom) View.VISIBLE else View.GONE binding.maxPlayersContainer.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.maxPlayersLabel.text = context.getString(R.string.multiplayer_max_players_value, binding.maxPlayers.value.toInt()) 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 ipAddress = binding.ipAddress.text.toString()
val username = binding.username.text.toString() val username = binding.username.text.toString()
val portStr = binding.ipPort.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 password = binding.password.text.toString()
val port = portStr.toIntOrNull() ?: run { val port = portStr.toIntOrNull() ?: run {
Toast.makeText(activity, R.string.multiplayer_port_invalid, Toast.LENGTH_LONG).show() Toast.makeText(activity, R.string.multiplayer_port_invalid, Toast.LENGTH_LONG).show()
@ -276,6 +306,13 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
return@setOnClickListener 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) { if (ipAddress.length < 7 || username.length < 5) {
Toast.makeText(activity, R.string.multiplayer_input_invalid, Toast.LENGTH_LONG).show() Toast.makeText(activity, R.string.multiplayer_input_invalid, Toast.LENGTH_LONG).show()
binding.btnConfirm.isEnabled = true binding.btnConfirm.isEnabled = true
@ -283,7 +320,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
} else { } else {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
val result = if (isCreateRoom) { val result = if (isCreateRoom) {
NetPlayManager.netPlayCreateRoom(ipAddress, port, username, password, roomName, maxPlayers) NetPlayManager.netPlayCreateRoom(ipAddress, port, username, preferredGameName, preferredGameId, password, roomName, maxPlayers)
} else { } else {
NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password) NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password)
} }

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -7,7 +7,9 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class StringSetting(override val key: String) : AbstractStringSetting { enum class StringSetting(override val key: String) : AbstractStringSetting {
DRIVER_PATH("driver_path"), 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) override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)

View file

@ -274,6 +274,13 @@ abstract class SettingsItem(
descriptionId = R.string.use_custom_rtc_description 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(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))
put( put(
SingleChoiceSetting( SingleChoiceSetting(

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
@ -200,6 +200,10 @@ class SettingsFragmentPresenter(
add(IntSetting.LANGUAGE_INDEX.key) add(IntSetting.LANGUAGE_INDEX.key)
add(BooleanSetting.USE_CUSTOM_RTC.key) add(BooleanSetting.USE_CUSTOM_RTC.key)
add(LongSetting.CUSTOM_RTC.key) add(LongSetting.CUSTOM_RTC.key)
// TODO(alekpop): Add functionality
// add(HeaderSetting(R.string.network))
// add(StringSetting.WEB_TOKEN.key)
} }
} }

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// 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-License-Identifier: GPL-3.0-or-later

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -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-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 package org.yuzu.yuzu_emu.fragments
import android.Manifest import android.Manifest
@ -128,7 +124,7 @@ class HomeSettingsFragment : Fragment() {
R.string.multiplayer_description, R.string.multiplayer_description,
R.drawable.ic_two_users, R.drawable.ic_two_users,
{ {
val action = mainActivity.displayMultiplayerDialog() mainActivity.displayMultiplayerDialog()
}, },
) )
) )

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model

View file

@ -1,16 +1,14 @@
// Copyright 2024 Mandarine Project // SPDX-FileCopyrightText: 2025 Eden Emulator 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-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.network package org.yuzu.yuzu_emu.network
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.format.Formatter import android.text.format.Formatter
@ -19,10 +17,27 @@ import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.dialogs.ChatMessage import org.yuzu.yuzu_emu.dialogs.ChatMessage
import java.net.Inet4Address
object NetPlayManager { object NetPlayManager {
external fun netPlayCreateRoom(ipAddress: String, port: Int, username: String, password: String, roomName: String, maxPlayers: Int): Int external fun netPlayCreateRoom(
external fun netPlayJoinRoom(ipAddress: String, port: Int, username: String, password: String): Int 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 netPlayRoomInfo(): Array<String>
external fun netPlayIsJoined(): Boolean external fun netPlayIsJoined(): Boolean
external fun netPlayIsHostedRoom(): Boolean external fun netPlayIsHostedRoom(): Boolean
@ -33,6 +48,28 @@ object NetPlayManager {
external fun netPlayGetBanList(): Array<String> external fun netPlayGetBanList(): Array<String>
external fun netPlayBanUser(username: String) external fun netPlayBanUser(username: String)
external fun netPlayUnbanUser(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 messageListener: ((Int, String) -> Unit)? = null
private var adapterRefreshListener: ((Int, String) -> Unit)? = null private var adapterRefreshListener: ((Int, String) -> Unit)? = null
@ -41,11 +78,57 @@ object NetPlayManager {
messageListener = listener 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) { fun setOnAdapterRefreshListener(listener: (Int, String) -> Unit) {
adapterRefreshListener = listener 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()}" val name = "Eden${(Math.random() * 100).toInt()}"
return prefs.getString("NetPlayUsername", name) ?: name return prefs.getString("NetPlayUsername", name) ?: name
} }
@ -103,22 +186,27 @@ object NetPlayManager {
if (parts.size == 2) { if (parts.size == 2) {
val nickname = parts[0].trim() val nickname = parts[0].trim()
val chatMessage = parts[1].trim() val chatMessage = parts[1].trim()
addChatMessage(ChatMessage( addChatMessage(
ChatMessage(
nickname = nickname, nickname = nickname,
username = "", username = "",
message = chatMessage message = chatMessage
)) )
)
} }
} }
NetPlayStatus.MEMBER_JOIN, NetPlayStatus.MEMBER_JOIN,
NetPlayStatus.MEMBER_LEAVE, NetPlayStatus.MEMBER_LEAVE,
NetPlayStatus.MEMBER_KICKED, NetPlayStatus.MEMBER_KICKED,
NetPlayStatus.MEMBER_BANNED -> { NetPlayStatus.MEMBER_BANNED -> {
addChatMessage(ChatMessage( addChatMessage(
ChatMessage(
nickname = "System", nickname = "System",
username = "", username = "",
message = message message = message
)) )
)
} }
} }
@ -159,35 +247,60 @@ object NetPlayManager {
NetPlayStatus.ROOM_MODERATOR -> context.getString(R.string.multiplayer_room_moderator) NetPlayStatus.ROOM_MODERATOR -> context.getString(R.string.multiplayer_room_moderator)
NetPlayStatus.MEMBER_JOIN -> context.getString(R.string.multiplayer_member_join, msg) NetPlayStatus.MEMBER_JOIN -> context.getString(R.string.multiplayer_member_join, msg)
NetPlayStatus.MEMBER_LEAVE -> context.getString(R.string.multiplayer_member_leave, 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_KICKED -> context.getString(
NetPlayStatus.MEMBER_BANNED -> context.getString(R.string.multiplayer_member_banned, msg) 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.ADDRESS_UNBANNED -> context.getString(R.string.multiplayer_address_unbanned)
NetPlayStatus.CHAT_MESSAGE -> msg NetPlayStatus.CHAT_MESSAGE -> msg
else -> "" 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 { fun getIpAddressByWifi(activity: Activity): String {
var ipAddress = 0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val wifiManager = activity.getSystemService(WifiManager::class.java) // For Android 12 (API 31) and above
val wifiInfo = wifiManager.connectionInfo val connectivityManager = activity.getSystemService(ConnectivityManager::class.java)
if (wifiInfo != null) { val network = connectivityManager.activeNetwork
ipAddress = wifiInfo.ipAddress val capabilities = connectivityManager.getNetworkCapabilities(network)
}
if (ipAddress == 0) { if (capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) {
val dhcpInfo = wifiManager.dhcpInfo val linkProperties = connectivityManager.getLinkProperties(network)
if (dhcpInfo != null) { linkProperties?.linkAddresses?.firstOrNull { it.address is Inet4Address }?.let {
ipAddress = dhcpInfo.ipAddress return it.address.hostAddress ?: "192.168.0.1"
}
} }
} }
return if (ipAddress == 0) { // For Android 11 (API 30) and below
"192.168.0.1" try {
} else { val connectivityManager = activity.getSystemService(ConnectivityManager::class.java)
Formatter.formatIpAddress(ipAddress) 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> { fun getBanList(): List<String> {
return netPlayGetBanList().toList() return netPlayGetBanList().toList()

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.ui package org.yuzu.yuzu_emu.ui

View file

@ -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-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 package org.yuzu.yuzu_emu.ui.main
import android.content.Intent import android.content.Intent
@ -72,9 +68,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
ThemeHelper.ThemeChangeListener(this) ThemeHelper.ThemeChangeListener(this)
ThemeHelper.setTheme(this) ThemeHelper.setTheme(this)
NativeLibrary.netPlayInit()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
NativeLibrary.initMultiplayer()
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)

View file

@ -1,8 +1,4 @@
// Copyright 2024 Mandarine Project // SPDX-FileCopyrightText: 2025 Eden Emulator 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-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences 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.Game
import org.yuzu.yuzu_emu.model.GameDir import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.MinimalDocumentFile import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import androidx.core.content.edit
import androidx.core.net.toUri
object GameHelper { object GameHelper {
private const val KEY_OLD_GAME_PATH = "game_path" private const val KEY_OLD_GAME_PATH = "game_path"
const val KEY_GAMES = "Games" const val KEY_GAMES = "Games"
var cachedGameList = mutableListOf<Game>()
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
fun getGames(): List<Game> { fun getGames(): List<Game> {
@ -29,7 +36,7 @@ object GameHelper {
val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: "" val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: ""
if (oldGamesDir.isNotEmpty()) { if (oldGamesDir.isNotEmpty()) {
gameDirs.add(GameDir(oldGamesDir, true)) gameDirs.add(GameDir(oldGamesDir, true))
preferences.edit().remove(KEY_OLD_GAME_PATH).apply() preferences.edit() { remove(KEY_OLD_GAME_PATH) }
} }
gameDirs.addAll(NativeConfig.getGameDirs()) gameDirs.addAll(NativeConfig.getGameDirs())
@ -44,7 +51,7 @@ object GameHelper {
val badDirs = mutableListOf<Int>() val badDirs = mutableListOf<Int>()
gameDirs.forEachIndexed { index: Int, gameDir: GameDir -> gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
val gameDirUri = Uri.parse(gameDir.uriString) val gameDirUri = gameDir.uriString.toUri()
val isValid = FileUtil.isTreeUriValid(gameDirUri) val isValid = FileUtil.isTreeUriValid(gameDirUri)
if (isValid) { if (isValid) {
addGamesRecursive( addGamesRecursive(
@ -72,11 +79,12 @@ object GameHelper {
games.forEach { games.forEach {
serializedGames.add(Json.encodeToString(it)) serializedGames.add(Json.encodeToString(it))
} }
preferences.edit() preferences.edit() {
.remove(KEY_GAMES) remove(KEY_GAMES)
.putStringSet(KEY_GAMES, serializedGames) .putStringSet(KEY_GAMES, serializedGames)
.apply() }
cachedGameList = games.toMutableList()
return games.toList() return games.toList()
} }

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 // 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_instance.h"
#include "video_core/vulkan_common/vulkan_surface.h" #include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/shader_notify.h" #include "video_core/shader_notify.h"
#include "network/announce_multiplayer_session.h"
#define jconst [[maybe_unused]] const auto #define jconst [[maybe_unused]] const auto
#define jauto [[maybe_unused]] auto #define jauto [[maybe_unused]] auto
static EmulationSession s_instance; 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() { EmulationSession::EmulationSession() {
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); 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(); 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( JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayCreateRoom(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port, JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
jstring username, jstring 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>( return static_cast<jint>(
NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port, multiplayer->NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port,
Common::Android::GetJString(env, username), Common::Android::GetJString(env, password), Common::Android::GetJString(env, username), Common::Android::GetJString(env, preferredGameName),
preferredGameId,Common::Android::GetJString(env, password),
Common::Android::GetJString(env, room_name), max_players)); Common::Android::GetJString(env, room_name), max_players));
} }
@ -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, JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
jstring username, jstring password) { jstring username, jstring password) {
return static_cast<jint>( 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))); Common::Android::GetJString(env, username), Common::Android::GetJString(env, password)));
} }
JNIEXPORT jobjectArray JNICALL JNIEXPORT jobjectArray JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayRoomInfo( Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayRoomInfo(
JNIEnv* env, [[maybe_unused]] jobject obj) { JNIEnv* env, [[maybe_unused]] jobject obj) {
return Common::Android::ToJStringArray(env, NetPlayRoomInfo()); return Common::Android::ToJStringArray(env, multiplayer->NetPlayRoomInfo());
} }
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsJoined( Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsJoined(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
return NetPlayIsJoined(); return multiplayer->NetPlayIsJoined();
} }
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsHostedRoom( Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsHostedRoom(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
return NetPlayIsHostedRoom(); return multiplayer->NetPlayIsHostedRoom();
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlaySendMessage( Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlaySendMessage(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring msg) { 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( JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayKickUser(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) { 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( JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayLeaveRoom(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
NetPlayLeaveRoom(); multiplayer->NetPlayLeaveRoom();
} }
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsModerator( Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsModerator(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
return NetPlayIsModerator(); return multiplayer->NetPlayIsModerator();
} }
JNIEXPORT jobjectArray JNICALL JNIEXPORT jobjectArray JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetBanList( Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetBanList(
JNIEnv* env, [[maybe_unused]] jobject obj) { 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( JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayBanUser(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) { 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( JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayUnbanUser(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) { JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
NetPlayUnbanUser(Common::Android::GetJString(env, username)); multiplayer->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());
}
} // extern "C" } // extern "C"

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -28,7 +28,7 @@
<ImageView <ImageView
android:layout_width="140dp" android:layout_width="140dp"
android:layout_height="140dp" android:layout_height="114dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
@ -38,19 +38,45 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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_marginHorizontal="16dp"
android:layout_marginBottom="8dp"> android:layout_marginBottom="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_join" android:id="@+id/btn_join"
style="@style/Widget.Material3.Button.TonalButton" style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/multiplayer_join_room" android:text="@string/multiplayer_join_room"
app:icon="@drawable/ic_install" app:cornerRadius="16dp"
app:cornerRadius="16dp" /> app:icon="@drawable/ic_install" />
<Space <Space
android:layout_width="16dp" android:layout_width="16dp"
@ -58,13 +84,13 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_create" android:id="@+id/btn_create"
style="@style/Widget.Material3.Button" style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/multiplayer_create_room" android:text="@string/multiplayer_create_room"
app:icon="@drawable/ic_add" app:cornerRadius="16dp"
app:cornerRadius="16dp" /> app:icon="@drawable/ic_add" />
</LinearLayout> </LinearLayout>

View file

@ -59,6 +59,21 @@
android:inputType="text" /> android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout> </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 <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -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>

View file

@ -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" />

View file

@ -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>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Eden</string>
</resources>

View file

@ -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="use_custom_rtc_description">Allows you to set a custom real-time clock separate from your current system time.</string>
<string name="set_custom_rtc">Set custom RTC</string> <string name="set_custom_rtc">Set custom RTC</string>
<!-- 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 --> <!-- Graphics settings strings -->
<string name="frame_skipping">WIP: Frameskip</string> <string name="frame_skipping">WIP: Frameskip</string>
<string name="frame_skipping_description">Toggle frame skipping to improve performance by reducing the number of rendered frames. This feature is still being worked on and will be enabled in future releases.</string> <string name="frame_skipping_description">Toggle frame skipping to improve performance by reducing the number of rendered frames. This feature is still being worked on and will be enabled in future releases.</string>

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 // SPDX-License-Identifier: GPL-3.0-or-later
#include "android_common.h" #include "android_common.h"

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 // SPDX-License-Identifier: GPL-3.0-or-later
#include <jni.h> #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_vibrate;
static jmethodID s_yuzu_input_device_get_axes; static jmethodID s_yuzu_input_device_get_axes;
static jmethodID s_yuzu_input_device_has_keys; static jmethodID s_yuzu_input_device_has_keys;
static jmethodID s_add_netplay_message; static jmethodID s_add_netplay_message;
static jmethodID s_clear_chat; static jmethodID s_clear_chat;
static constexpr jint JNI_VERSION = JNI_VERSION_1_6; static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
namespace Common::Android { namespace Common::Android {
@ -405,7 +405,6 @@ jmethodID ClearChat() {
return s_clear_chat; return s_clear_chat;
} }
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -605,7 +604,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
// UnInitialize applets // UnInitialize applets
SoftwareKeyboard::CleanupJNI(env); SoftwareKeyboard::CleanupJNI(env);
NetworkShutdown(); AndroidMultiplayer::NetworkShutdown();
} }
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // 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 // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once

View file

@ -1,8 +1,4 @@
// Copyright 2024 Mandarine Project // SPDX-FileCopyrightText: 2025 Eden Emulator 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-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "common/android/id_cache.h" #include "common/android/id_cache.h"
@ -19,35 +15,38 @@
#include <chrono> #include <chrono>
namespace IDCache = Common::Android; 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::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
IDCache::GetAddNetPlayMessage(), type, msg); 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(); JNIEnv* env = IDCache::GetEnvForThread();
AddNetPlayMessage(type, Common::Android::ToJString(env, msg)); AddNetPlayMessage(type, Common::Android::ToJString(env, msg));
} }
void ClearChat() { void AndroidMultiplayer::ClearChat() {
IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
IDCache::ClearChat()); IDCache::ClearChat());
} }
bool AndroidMultiplayer::NetworkInit() {
bool NetworkInit(Network::RoomNetwork* room_network_) { bool result = Network::Init();
room_network = room_network_;
bool result = room_network->Init();
if (!result) { if (!result) {
return false; 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 // 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 || if (state == Network::RoomMember::State::Joined ||
state == Network::RoomMember::State::Moderator) { state == Network::RoomMember::State::Moderator) {
NetPlayStatus status; NetPlayStatus status;
@ -65,7 +64,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
AddNetPlayMessage(static_cast<int>(status), msg); AddNetPlayMessage(static_cast<int>(status), msg);
} }
}); });
member->BindOnError([](const Network::RoomMember::Error& error) { member->BindOnError([this](const Network::RoomMember::Error& error) {
NetPlayStatus status; NetPlayStatus status;
std::string msg; std::string msg;
switch (error) { switch (error) {
@ -108,7 +107,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
} }
AddNetPlayMessage(static_cast<int>(status), msg); 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; NetPlayStatus status = NetPlayStatus::NO_ERROR;
std::string msg(status_message.nickname); std::string msg(status_message.nickname);
switch (status_message.type) { switch (status_message.type) {
@ -130,7 +129,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
} }
AddNetPlayMessage(static_cast<int>(status), msg); AddNetPlayMessage(static_cast<int>(status), msg);
}); });
member->BindOnChatMessageReceived([](const Network::ChatEntry& chat) { member->BindOnChatMessageReceived([this](const Network::ChatEntry& chat) {
NetPlayStatus status = NetPlayStatus::CHAT_MESSAGE; NetPlayStatus status = NetPlayStatus::CHAT_MESSAGE;
std::string msg(chat.nickname); std::string msg(chat.nickname);
msg += ": "; msg += ": ";
@ -141,13 +140,11 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
return true; return true;
} }
NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port, NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string& ipaddress, int port,
const std::string& username, const std::string& password, const std::string& username, const std::string &preferredGameName,
const u64 &preferredGameId, const std::string& password,
const std::string& room_name, int max_players) { const std::string& room_name, int max_players) {
auto member = Network::GetRoomMember().lock();
__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();
if (!member) { if (!member) {
return NetPlayStatus::NETWORK_ERROR; return NetPlayStatus::NETWORK_ERROR;
} }
@ -156,7 +153,7 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
return NetPlayStatus::ALREADY_IN_ROOM; return NetPlayStatus::ALREADY_IN_ROOM;
} }
auto room = room_network->GetRoom().lock(); auto room = Network::GetRoom().lock();
if (!room) { if (!room) {
return NetPlayStatus::NETWORK_ERROR; return NetPlayStatus::NETWORK_ERROR;
} }
@ -167,14 +164,14 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
// Placeholder game info // Placeholder game info
const AnnounceMultiplayerRoom::GameInfo game{ const AnnounceMultiplayerRoom::GameInfo game{
.name = "Default Game", .name = preferredGameName,
.id = 0, // Default program ID .id = preferredGameId,
}; };
port = (port == 0) ? Network::DefaultRoomPort : static_cast<u16>(port); port = (port == 0) ? Network::DefaultRoomPort : static_cast<u16>(port);
if (!room->Create(room_name, "", ipaddress, static_cast<u16>(port), password, if (!room->Create(room_name, "", ipaddress, static_cast<u16>(port), password,
static_cast<u32>(std::min(max_players, 16)), username, game, nullptr, {})) { static_cast<u32>(std::min(max_players, 16)), username, game, std::make_unique<Network::VerifyUser::NullBackend>(), {})) {
return NetPlayStatus::CREATE_ROOM_ERROR; return NetPlayStatus::CREATE_ROOM_ERROR;
} }
@ -197,9 +194,9 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
return NetPlayStatus::CREATE_ROOM_ERROR; 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) { const std::string& username, const std::string& password) {
auto member = room_network->GetRoomMember().lock(); auto member = Network::GetRoomMember().lock();
if (!member) { if (!member) {
return NetPlayStatus::NETWORK_ERROR; return NetPlayStatus::NETWORK_ERROR;
} }
@ -229,8 +226,8 @@ NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port,
return NetPlayStatus::WRONG_PASSWORD; return NetPlayStatus::WRONG_PASSWORD;
} }
void NetPlaySendMessage(const std::string& msg) { void AndroidMultiplayer::NetPlaySendMessage(const std::string& msg) {
if (auto room = room_network->GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
if (room->GetState() != Network::RoomMember::State::Joined && if (room->GetState() != Network::RoomMember::State::Joined &&
room->GetState() != Network::RoomMember::State::Moderator) { room->GetState() != Network::RoomMember::State::Moderator) {
@ -240,8 +237,8 @@ void NetPlaySendMessage(const std::string& msg) {
} }
} }
void NetPlayKickUser(const std::string& username) { void AndroidMultiplayer::NetPlayKickUser(const std::string& username) {
if (auto room = room_network->GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
auto members = room->GetMemberInformation(); auto members = room->GetMemberInformation();
auto it = std::find_if(members.begin(), members.end(), auto it = std::find_if(members.begin(), members.end(),
[&username](const Network::RoomMember::MemberInformation& member) { [&username](const Network::RoomMember::MemberInformation& member) {
@ -253,8 +250,8 @@ void NetPlayKickUser(const std::string& username) {
} }
} }
void NetPlayBanUser(const std::string& username) { void AndroidMultiplayer::NetPlayBanUser(const std::string& username) {
if (auto room = room_network->GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
auto members = room->GetMemberInformation(); auto members = room->GetMemberInformation();
auto it = std::find_if(members.begin(), members.end(), auto it = std::find_if(members.begin(), members.end(),
[&username](const Network::RoomMember::MemberInformation& member) { [&username](const Network::RoomMember::MemberInformation& member) {
@ -266,15 +263,15 @@ void NetPlayBanUser(const std::string& username) {
} }
} }
void NetPlayUnbanUser(const std::string& username) { void AndroidMultiplayer::NetPlayUnbanUser(const std::string& username) {
if (auto room = room_network->GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username); room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username);
} }
} }
std::vector<std::string> NetPlayRoomInfo() { std::vector<std::string> AndroidMultiplayer::NetPlayRoomInfo() {
std::vector<std::string> info_list; std::vector<std::string> info_list;
if (auto room = room_network->GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
auto members = room->GetMemberInformation(); auto members = room->GetMemberInformation();
if (!members.empty()) { if (!members.empty()) {
// name and max players // name and max players
@ -289,8 +286,8 @@ std::vector<std::string> NetPlayRoomInfo() {
return info_list; return info_list;
} }
bool NetPlayIsJoined() { bool AndroidMultiplayer::NetPlayIsJoined() {
auto member = room_network->GetRoomMember().lock(); auto member = Network::GetRoomMember().lock();
if (!member) { if (!member) {
return false; return false;
} }
@ -299,17 +296,17 @@ bool NetPlayIsJoined() {
member->GetState() == Network::RoomMember::State::Moderator); member->GetState() == Network::RoomMember::State::Moderator);
} }
bool NetPlayIsHostedRoom() { bool AndroidMultiplayer::NetPlayIsHostedRoom() {
if (auto room = room_network->GetRoom().lock()) { if (auto room = Network::GetRoom().lock()) {
return room->GetState() == Network::Room::State::Open; return room->GetState() == Network::Room::State::Open;
} }
return false; return false;
} }
void NetPlayLeaveRoom() { void AndroidMultiplayer::NetPlayLeaveRoom() {
if (auto room = room_network->GetRoom().lock()) { if (auto room = Network::GetRoom().lock()) {
// if you are in a room, leave it // if you are in a room, leave it
if (auto member = room_network->GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
member->Leave(); member->Leave();
} }
@ -322,21 +319,52 @@ void NetPlayLeaveRoom() {
} }
} }
void NetworkShutdown() { void AndroidMultiplayer::NetworkShutdown() {
room_network->Shutdown(); Network::Shutdown();
} }
bool NetPlayIsModerator() { bool AndroidMultiplayer::NetPlayIsModerator() {
auto member = room_network->GetRoomMember().lock(); auto member = Network::GetRoomMember().lock();
if (!member) { if (!member) {
return false; return false;
} }
return member->GetState() == Network::RoomMember::State::Moderator; 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; 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(); auto [username_bans, ip_bans] = room->GetBanList();
// Add username bans // Add username bans

View file

@ -1,8 +1,4 @@
// Copyright 2024 Mandarine Project // SPDX-FileCopyrightText: 2025 Eden Emulator 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-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once
@ -12,6 +8,12 @@
#include <common/common_types.h> #include <common/common_types.h>
#include <network/network.h> #include <network/network.h>
#include <network/announce_multiplayer_session.h>
namespace Core {
class System;
class AnnounceMultiplayerSession;
}
enum class NetPlayStatus : s32 { enum class NetPlayStatus : s32 {
NO_ERROR, NO_ERROR,
@ -48,21 +50,53 @@ enum class NetPlayStatus : s32 {
CHAT_MESSAGE, CHAT_MESSAGE,
}; };
bool NetworkInit(Network::RoomNetwork* room_network); class AndroidMultiplayer {
NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port, public:
const std::string& username, const std::string& password, explicit AndroidMultiplayer(Core::System& system,
const std::string& room_name, int max_players); std::shared_ptr<Core::AnnounceMultiplayerSession> session);
NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port, ~AndroidMultiplayer();
const std::string& username, const std::string& password);
std::vector<std::string> NetPlayRoomInfo(); bool NetworkInit();
bool NetPlayIsJoined();
bool NetPlayIsHostedRoom(); void AddNetPlayMessage(int status, const std::string& msg);
bool NetPlayIsModerator(); void AddNetPlayMessage(jint type, jstring msg);
void NetPlaySendMessage(const std::string& msg);
void NetPlayKickUser(const std::string& username); void ClearChat();
void NetPlayBanUser(const std::string& username);
void NetPlayLeaveRoom(); NetPlayStatus NetPlayCreateRoom(const std::string &ipaddress, int port,
std::string NetPlayGetConsoleId(); const std::string &username, const std::string &preferredGameName,
void NetworkShutdown(); const u64 &preferredGameId, const std::string &password,
std::vector<std::string> NetPlayGetBanList(); const std::string &room_name, int max_players);
void NetPlayUnbanUser(const std::string& username);
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;
};

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
#include <array> #include <array>
@ -111,7 +111,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl { struct System::Impl {
explicit Impl(System& system) 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{} {} reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
u64 program_id; u64 program_id;
@ -421,7 +421,7 @@ struct System::Impl {
} }
LoadOverrides(program_id); LoadOverrides(program_id);
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info; Network::GameInfo game_info;
game_info.name = name; game_info.name = name;
game_info.id = params.program_id; game_info.id = params.program_id;
@ -466,7 +466,7 @@ struct System::Impl {
stop_event = {}; stop_event = {};
Network::RestartSocketOperations(); Network::RestartSocketOperations();
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info{}; Network::GameInfo game_info{};
room_member->SendGameInfo(game_info); room_member->SendGameInfo(game_info);
} }
@ -520,7 +520,6 @@ struct System::Impl {
std::unique_ptr<Core::DeviceMemory> device_memory; std::unique_ptr<Core::DeviceMemory> device_memory;
std::unique_ptr<AudioCore::AudioCore> audio_core; std::unique_ptr<AudioCore::AudioCore> audio_core;
Core::HID::HIDCore hid_core; Core::HID::HIDCore hid_core;
Network::RoomNetwork room_network;
CpuManager cpu_manager; CpuManager cpu_manager;
std::atomic_bool is_powered_on{}; std::atomic_bool is_powered_on{};
@ -979,14 +978,6 @@ const Core::Debugger& System::GetDebugger() const {
return *impl->debugger; 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() { Tools::RenderdocAPI& System::GetRenderdocAPI() {
return *impl->renderdoc_api; return *impl->renderdoc_api;
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once
#include <cstddef> #include <cstddef>
@ -104,10 +107,6 @@ namespace Core::HID {
class HIDCore; class HIDCore;
} }
namespace Network {
class RoomNetwork;
}
namespace Tools { namespace Tools {
class RenderdocAPI; class RenderdocAPI;
} }
@ -380,12 +379,6 @@ public:
[[nodiscard]] Core::Debugger& GetDebugger(); [[nodiscard]] Core::Debugger& GetDebugger();
[[nodiscard]] const Core::Debugger& GetDebugger() const; [[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(); [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
void SetExitLocked(bool locked); void SetExitLocked(bool locked);

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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/hle/service/ldn/lan_discovery.h"
#include "core/internal_network/network.h" #include "core/internal_network/network.h"
#include "core/internal_network/network_interface.h" #include "core/internal_network/network_interface.h"
@ -33,9 +36,8 @@ void LanStation::OverrideInfo() {
node_info->is_connected = connected ? 1 : 0; node_info->is_connected = connected ? 1 : 0;
} }
LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_) LANDiscovery::LANDiscovery()
: stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}), : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}){}
room_network{room_network_} {}
LANDiscovery::~LANDiscovery() { LANDiscovery::~LANDiscovery() {
if (inited) { if (inited) {
@ -410,7 +412,7 @@ void LANDiscovery::OnNetworkInfoChanged() {
Network::IPv4Address LANDiscovery::GetLocalIp() const { Network::IPv4Address LANDiscovery::GetLocalIp() const {
Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF}; 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()) { if (room_member->IsConnected()) {
local_ip = room_member->GetFakeIpAddress(); local_ip = room_member->GetFakeIpAddress();
} }
@ -468,7 +470,7 @@ void LANDiscovery::SendBroadcast(Network::LDNPacketType type) {
} }
void LANDiscovery::SendPacket(const Network::LDNPacket& packet) { 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()) { if (room_member->IsConnected()) {
room_member->SendLdnPacket(packet); room_member->SendLdnPacket(packet);
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <array> #include <array>
@ -47,7 +50,7 @@ class LANDiscovery {
public: public:
using LanEventFunc = std::function<void()>; using LanEventFunc = std::function<void()>;
LANDiscovery(Network::RoomNetwork& room_network_); LANDiscovery();
~LANDiscovery(); ~LANDiscovery();
State GetState() const; State GetState() const;
@ -127,7 +130,5 @@ protected:
std::optional<Ipv4Address> host_ip; std::optional<Ipv4Address> host_ip;
LanEventFunc lan_event; LanEventFunc lan_event;
Network::RoomNetwork& room_network;
}; };
} // namespace Service::LDN } // namespace Service::LDN

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // 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 <memory>
#include "core/core.h" #include "core/core.h"
@ -22,7 +25,7 @@ namespace Service::LDN {
IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& system_) IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& system_)
: ServiceFramework{system_, "IUserLocalCommunicationService"}, : ServiceFramework{system_, "IUserLocalCommunicationService"},
service_context{system, "IUserLocalCommunicationService"}, service_context{system, "IUserLocalCommunicationService"},
room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} { lan_discovery{} {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, C<&IUserLocalCommunicationService::GetState>, "GetState"}, {0, C<&IUserLocalCommunicationService::GetState>, "GetState"},
@ -65,7 +68,7 @@ IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& sys
IUserLocalCommunicationService::~IUserLocalCommunicationService() { IUserLocalCommunicationService::~IUserLocalCommunicationService() {
if (is_initialized) { if (is_initialized) {
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
room_member->Unbind(ldn_packet_received); 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)}; *out_subnet_mask = {Network::TranslateIPv4(network_interface->subnet_mask)};
// When we're connected to a room, spoof the hosts IP address // 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()) { if (room_member->IsConnected()) {
*out_current_address = room_member->GetFakeIpAddress(); *out_current_address = room_member->GetFakeIpAddress();
} }
@ -280,7 +283,7 @@ Result IUserLocalCommunicationService::Initialize(ClientProcessId aruid) {
const auto network_interface = Network::GetSelectedNetworkInterface(); const auto network_interface = Network::GetSelectedNetworkInterface();
R_UNLESS(network_interface, ResultAirplaneModeEnabled); 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( ldn_packet_received = room_member->BindOnLdnPacketReceived(
[this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); }); [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
} else { } else {
@ -295,7 +298,7 @@ Result IUserLocalCommunicationService::Initialize(ClientProcessId aruid) {
Result IUserLocalCommunicationService::Finalize() { Result IUserLocalCommunicationService::Finalize() {
LOG_INFO(Service_LDN, "called"); 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); room_member->Unbind(ldn_packet_received);
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // 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 #pragma once
#include "core/hle/service/cmif_types.h" #include "core/hle/service/cmif_types.h"
@ -13,10 +16,6 @@ namespace Core {
class System; class System;
} }
namespace Network {
class RoomNetwork;
}
namespace Service::LDN { namespace Service::LDN {
class IUserLocalCommunicationService final class IUserLocalCommunicationService final
@ -91,7 +90,6 @@ private:
KernelHelpers::ServiceContext service_context; KernelHelpers::ServiceContext service_context;
Kernel::KEvent* state_change_event; Kernel::KEvent* state_change_event;
Network::RoomNetwork& room_network;
LANDiscovery lan_discovery; LANDiscovery lan_discovery;
// Callback identifier for the OnLDNPacketReceived event. // Callback identifier for the OnLDNPacketReceived event.

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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/core.h"
#include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.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 // 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()) { if (room_member->IsConnected()) {
network_profile_data.ip_setting_data.ip_address_setting.ip_address = network_profile_data.ip_setting_data.ip_address_setting.ip_address =
room_member->GetFakeIpAddress(); room_member->GetFakeIpAddress();
@ -454,7 +457,7 @@ void IGeneralService::GetCurrentIpAddress(HLERequestContext& ctx) {
} }
// When we're connected to a room, spoof the hosts IP address // 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()) { if (room_member->IsConnected()) {
ipv4 = room_member->GetFakeIpAddress(); 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 // 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()) { if (room_member->IsConnected()) {
ip_config_info.ip_address_setting.ip_address = room_member->GetFakeIpAddress(); 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_) IGeneralService::IGeneralService(Core::System& system_)
: ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} { : ServiceFramework{system_, "IGeneralService"} {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{1, &IGeneralService::GetClientId, "GetClientId"}, {1, &IGeneralService::GetClientId, "GetClientId"},

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
@ -9,10 +12,6 @@ namespace Core {
class System; class System;
} }
namespace Network {
class RoomNetwork;
}
namespace Service::NIFM { namespace Service::NIFM {
void LoopProcess(Core::System& system); void LoopProcess(Core::System& system);
@ -42,8 +41,6 @@ private:
void ConfirmSystemAvailability(HLERequestContext& ctx); void ConfirmSystemAvailability(HLERequestContext& ctx);
void SetBackgroundRequestEnabled(HLERequestContext& ctx); void SetBackgroundRequestEnabled(HLERequestContext& ctx);
void GetCurrentAccessPoint(HLERequestContext& ctx); void GetCurrentAccessPoint(HLERequestContext& ctx);
Network::RoomNetwork& network;
}; };
} // namespace Service::NIFM } // namespace Service::NIFM

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <array>
#include <memory> #include <memory>
#include <utility> #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); 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()) { if (room_member && room_member->IsConnected()) {
descriptor.socket = std::make_shared<Network::ProxySocket>(room_network); descriptor.socket = std::make_shared<Network::ProxySocket>();
} else { } else {
descriptor.socket = std::make_shared<Network::Socket>(); 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) BSD::BSD(Core::System& system_, const char* name)
: ServiceFramework{system_, name}, room_network{system_.GetRoomNetwork()} { : ServiceFramework{system_, name} {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"}, {0, &BSD::RegisterClient, "RegisterClient"},
@ -1012,7 +1015,7 @@ BSD::BSD(Core::System& system_, const char* name)
RegisterHandlers(functions); RegisterHandlers(functions);
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
proxy_packet_received = room_member->BindOnProxyPacketReceived( proxy_packet_received = room_member->BindOnProxyPacketReceived(
[this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); }); [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
} else { } else {
@ -1021,7 +1024,7 @@ BSD::BSD(Core::System& system_, const char* name)
} }
BSD::~BSD() { BSD::~BSD() {
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
room_member->Unbind(proxy_packet_received); room_member->Unbind(proxy_packet_received);
} }
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <memory> #include <memory>
@ -179,8 +182,6 @@ private:
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors; std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
Network::RoomNetwork& room_network;
/// Callback to parse and handle a received wifi packet. /// Callback to parse and handle a received wifi packet.
void OnProxyPacketReceived(const Network::ProxyPacket& packet); void OnProxyPacketReceived(const Network::ProxyPacket& packet);

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm> #include <algorithm>

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <chrono>
#include <thread> #include <thread>
@ -18,7 +21,7 @@
namespace Network { namespace Network {
ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {} ProxySocket::ProxySocket() noexcept {}
ProxySocket::~ProxySocket() { ProxySocket::~ProxySocket() {
if (fd == INVALID_SOCKET) { 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) { void ProxySocket::SendPacket(ProxyPacket& packet) {
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
if (room_member->IsConnected()) { if (room_member->IsConnected()) {
packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(), packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(),
packet.data.size()); 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}; 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()) { if (!room_member->IsConnected()) {
return {static_cast<s32>(message.size()), Errno::SUCCESS}; 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, // If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address,
// replace it with a "fake" routing 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 (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(); packet.local_endpoint.ip = room_member->GetFakeIpAddress();
} }
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <mutex> #include <mutex>
@ -14,11 +17,9 @@
namespace Network { namespace Network {
class RoomNetwork;
class ProxySocket : public SocketBase { class ProxySocket : public SocketBase {
public: public:
explicit ProxySocket(RoomNetwork& room_network_) noexcept; explicit ProxySocket() noexcept;
~ProxySocket() override; ~ProxySocket() override;
void HandleProxyPacket(const ProxyPacket& packet) override; void HandleProxyPacket(const ProxyPacket& packet) override;
@ -92,8 +93,6 @@ private:
Protocol protocol; Protocol protocol;
std::mutex packets_mutex; std::mutex packets_mutex;
RoomNetwork& room_network;
}; };
} // namespace Network } // namespace Network

View file

@ -4,7 +4,7 @@
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project // SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // 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 // SPDX-License-Identifier: GPL-3.0-or-later
#include <chrono> #include <chrono>
@ -55,20 +55,20 @@ static void PrintHelp(const char* argv0) {
LOG_INFO(Network, LOG_INFO(Network,
"Usage: {}" "Usage: {}"
" [options] <filename>\n" " [options] <filename>\n"
"--room-name The name of the room\n" "-n, --room-name The name of the room\n"
"--room-description The room description\n" "-d, --room-description The room description\n"
"--bind-address The bind address for the room\n" "-s, --bind-address The bind address for the room\n"
"--port The port used for the room\n" "-p, --port The port used for the room\n"
"--max_members The maximum number of players for this room\n" "-m, --max-members The maximum number of players for this room\n"
"--password The password for the room\n" "-w, --password The password for the room\n"
"--preferred-game The preferred game for this room\n" "-g, --preferred-game The preferred game for this room\n"
"--preferred-game-id The preferred game-id for this room\n" "-i, --preferred-game-id The preferred game-id for this room\n"
"--username The username used for announce\n" "-u, --username The username used for announce\n"
"--token The token used for announce\n" "-t, --token The token used for announce\n"
"--web-api-url yuzu Web API url\n" "-a, --web-api-url yuzu Web API url\n"
"--ban-list-file The file for storing the room ban list\n" "-b, --ban-list-file The file for storing the room ban list\n"
"--log-file The file for storing the room log\n" "-l, --log-file The file for storing the room log\n"
"--enable-yuzu-mods Allow yuzu Community Moderators to moderate on your room\n" "-e, --enable-mods Allow Community Moderators to moderate on your room\n"
"-h, --help Display this help and exit\n" "-h, --help Display this help and exit\n"
"-v, --version Output version information and exit\n", "-v, --version Output version information and exit\n",
argv0); argv0);
@ -209,19 +209,18 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
std::string token; std::string token;
std::string web_api_url; std::string web_api_url;
std::string ban_list_file; std::string ban_list_file;
std::string log_file = "yuzu-room.log"; std::string log_file = "eden-room.log";
std::string bind_address; std::string bind_address;
u64 preferred_game_id = 0; u64 preferred_game_id = 0;
u32 port = Network::DefaultRoomPort; u32 port = Network::DefaultRoomPort;
u32 max_members = 16; u32 max_members = 16;
// TODO(alekpop): Implement this into main executable, for --room and a few others.
static struct option long_options[] = { static struct option long_options[] = {
{"room-name", required_argument, 0, 'n'}, {"room-name", required_argument, 0, 'n'},
{"room-description", required_argument, 0, 'd'}, {"room-description", required_argument, 0, 'd'},
{"bind-address", required_argument, 0, 's'}, {"bind-address", required_argument, 0, 's'},
{"port", required_argument, 0, 'p'}, {"port", required_argument, 0, 'p'},
{"max_members", required_argument, 0, 'm'}, {"max-members", required_argument, 0, 'm'},
{"password", required_argument, 0, 'w'}, {"password", required_argument, 0, 'w'},
{"preferred-game", required_argument, 0, 'g'}, {"preferred-game", required_argument, 0, 'g'},
{"preferred-game-id", required_argument, 0, 'i'}, {"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'}, {"web-api-url", required_argument, 0, 'a'},
{"ban-list-file", required_argument, 0, 'b'}, {"ban-list-file", required_argument, 0, 'b'},
{"log-file", required_argument, 0, 'l'}, {"log-file", required_argument, 0, 'l'},
{"enable-yuzu-mods", no_argument, 0, 'e'}, {"enable-mods", no_argument, 0, 'e'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'}, {"version", no_argument, 0, 'v'},
// Entry option // Entry option
@ -243,7 +242,9 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
while (optind < argc) { 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); 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) { if (arg != -1) {
switch (static_cast<char>(arg)) { char carg = static_cast<char>(arg);
switch (carg) {
case 'n': case 'n':
room_name.assign(optarg); room_name.assign(optarg);
break; break;
@ -289,6 +290,8 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
case 'v': case 'v':
PrintVersion(); PrintVersion();
std::exit(0); 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>(); verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
} }
Network::RoomNetwork network{}; Network::Init();
network.Init(); if (auto room = Network::GetRoom().lock()) {
if (auto room = network.GetRoom().lock()) {
AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
.id = preferred_game_id}; .id = preferred_game_id};
if (!room->Create(room_name, room_description, bind_address, static_cast<u16>(port), 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); std::exit(-1);
} }
LOG_INFO(Network, "Room is open. Close with Q+Enter..."); 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) { if (announce) {
announce_session->Start(); announce_session->Start();
} }
@ -407,7 +409,7 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
} }
room->Destroy(); room->Destroy();
} }
network.Shutdown(); Network::Shutdown();
detached_tasks.WaitForAllTasks(); detached_tasks.WaitForAllTasks();
std::exit(0); std::exit(0);
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <chrono>
#include <future> #include <future>
#include <vector> #include <vector>
@ -19,8 +22,7 @@ namespace Core {
// Time between room is announced to web_service // Time between room is announced to web_service
static constexpr std::chrono::seconds announce_time_interval(15); static constexpr std::chrono::seconds announce_time_interval(15);
AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_) AnnounceMultiplayerSession::AnnounceMultiplayerSession() {
: room_network{room_network_} {
#ifdef ENABLE_WEB_SERVICE #ifdef ENABLE_WEB_SERVICE
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(), backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
Settings::values.yuzu_username.GetValue(), Settings::values.yuzu_username.GetValue(),
@ -31,7 +33,7 @@ AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& roo
} }
WebService::WebResult AnnounceMultiplayerSession::Register() { WebService::WebResult AnnounceMultiplayerSession::Register() {
auto room = room_network.GetRoom().lock(); auto room = Network::GetRoom().lock();
if (!room) { if (!room) {
return WebService::WebResult{WebService::WebResult::Code::LibError, return WebService::WebResult{WebService::WebResult::Code::LibError,
"Network is not initialized", ""}; "Network is not initialized", ""};
@ -120,7 +122,7 @@ void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
std::future<WebService::WebResult> future; std::future<WebService::WebResult> future;
while (!shutdown_event.WaitUntil(update_time)) { while (!shutdown_event.WaitUntil(update_time)) {
update_time += announce_time_interval; update_time += announce_time_interval;
auto room = room_network.GetRoom().lock(); auto room = Network::GetRoom().lock();
if (!room) { if (!room) {
break; break;
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <atomic> #include <atomic>
@ -15,7 +18,6 @@
namespace Network { namespace Network {
class Room; class Room;
class RoomNetwork;
} // namespace Network } // namespace Network
namespace Core { namespace Core {
@ -28,7 +30,8 @@ namespace Core {
class AnnounceMultiplayerSession { class AnnounceMultiplayerSession {
public: public:
using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>; using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>;
AnnounceMultiplayerSession(Network::RoomNetwork& room_network_);
AnnounceMultiplayerSession();
~AnnounceMultiplayerSession(); ~AnnounceMultiplayerSession();
/** /**
@ -91,8 +94,6 @@ private:
std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend; std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend;
std::atomic_bool registered = false; ///< Whether the room has been registered std::atomic_bool registered = false; ///< Whether the room has been registered
Network::RoomNetwork& room_network;
}; };
} // namespace Core } // namespace Core

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "enet/enet.h" #include "enet/enet.h"
@ -8,40 +11,38 @@
namespace Network { namespace Network {
RoomNetwork::RoomNetwork() { static std::shared_ptr<RoomMember> g_room_member; ///< RoomMember (Client) for network games
m_room = std::make_shared<Room>(); static std::shared_ptr<Room> g_room; ///< Room (Server) for network games
m_room_member = std::make_shared<RoomMember>();
}
bool RoomNetwork::Init() { bool Init() {
if (enet_initialize() != 0) { if (enet_initialize() != 0) {
LOG_ERROR(Network, "Error initializing ENet"); LOG_ERROR(Network, "Error initializing ENet");
return false; return false;
} }
m_room = std::make_shared<Room>(); g_room = std::make_shared<Room>();
m_room_member = std::make_shared<RoomMember>(); g_room_member = std::make_shared<RoomMember>();
LOG_DEBUG(Network, "initialized OK"); LOG_DEBUG(Network, "initialized OK");
return true; return true;
} }
std::weak_ptr<Room> RoomNetwork::GetRoom() { std::weak_ptr<Room> GetRoom() {
return m_room; return g_room;
} }
std::weak_ptr<RoomMember> RoomNetwork::GetRoomMember() { std::weak_ptr<RoomMember> GetRoomMember() {
return m_room_member; return g_room_member;
} }
void RoomNetwork::Shutdown() { void Shutdown() {
if (m_room_member) { if (g_room_member) {
if (m_room_member->IsConnected()) if (g_room_member->IsConnected())
m_room_member->Leave(); g_room_member->Leave();
m_room_member.reset(); g_room_member.reset();
} }
if (m_room) { if (g_room) {
if (m_room->GetState() == Room::State::Open) if (g_room->GetState() == Room::State::Open)
m_room->Destroy(); g_room->Destroy();
m_room.reset(); g_room.reset();
} }
enet_deinitialize(); enet_deinitialize();
LOG_DEBUG(Network, "shutdown OK"); LOG_DEBUG(Network, "shutdown OK");

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <memory> #include <memory>
@ -9,25 +12,16 @@
namespace Network { namespace Network {
class RoomNetwork { /// Initializes and registers the network device, the room, and the room member.
public: bool Init();
RoomNetwork();
/// Initializes and registers the network device, the room, and the room member. /// Returns a pointer to the room handle
bool Init(); std::weak_ptr<Room> GetRoom();
/// Returns a pointer to the room handle /// Returns a pointer to the room member handle
std::weak_ptr<Room> GetRoom(); std::weak_ptr<RoomMember> GetRoomMember();
/// Returns a pointer to the room member handle /// Unregisters the network device, the room, and the room member and shut them down.
std::weak_ptr<RoomMember> GetRoomMember(); void Shutdown();
/// Unregisters the network device, the room, and the room member and shut them down.
void Shutdown();
private:
std::shared_ptr<RoomMember> m_room_member; ///< RoomMember (Client) for network games
std::shared_ptr<Room> m_room; ///< Room (Server) for network games
};
} // namespace Network } // namespace Network

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 // SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 // SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm> #include <algorithm>

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
#include <QInputDialog> #include <QInputDialog>

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
#include "yuzu/configuration/configure_ui.h" #include "yuzu/configuration/configure_ui.h"

View file

@ -9,30 +9,30 @@
#include "yuzu/configuration/configure_web.h" #include "yuzu/configuration/configure_web.h"
#include "yuzu/uisettings.h" #include "yuzu/uisettings.h"
static constexpr char token_delimiter{':'}; // static constexpr char token_delimiter{':'};
static std::string GenerateDisplayToken(const std::string& username, const std::string& token) { // static std::string GenerateDisplayToken(const std::string& username, const std::string& token) {
if (username.empty() || token.empty()) { // if (username.empty() || token.empty()) {
return {}; // return {};
} // }
const std::string unencoded_display_token{username + token_delimiter + token}; // const std::string unencoded_display_token{username + token_delimiter + token};
QByteArray b{unencoded_display_token.c_str()}; // QByteArray b{unencoded_display_token.c_str()};
QByteArray b64 = b.toBase64(); // QByteArray b64 = b.toBase64();
return b64.toStdString(); // return b64.toStdString();
} // }
static std::string UsernameFromDisplayToken(const std::string& display_token) { // static std::string UsernameFromDisplayToken(const std::string& display_token) {
const std::string unencoded_display_token{ // const std::string unencoded_display_token{
QByteArray::fromBase64(display_token.c_str()).toStdString()}; // QByteArray::fromBase64(display_token.c_str()).toStdString()};
return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter)); // return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
} // }
static std::string TokenFromDisplayToken(const std::string& display_token) { // static std::string TokenFromDisplayToken(const std::string& display_token) {
const std::string unencoded_display_token{ // const std::string unencoded_display_token{
QByteArray::fromBase64(display_token.c_str()).toStdString()}; // QByteArray::fromBase64(display_token.c_str()).toStdString()};
return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1); // return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
} // }
ConfigureWeb::ConfigureWeb(QWidget* parent) ConfigureWeb::ConfigureWeb(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) { : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
@ -66,7 +66,7 @@ void ConfigureWeb::RetranslateUI() {
"color:#039be5;\">Sign up</span></a>")); "color:#039be5;\">Sign up</span></a>"));
ui->web_token_info_link->setText( 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>")); "underline; color:#039be5;\">What is my token?</span></a>"));
} }
@ -76,14 +76,10 @@ void ConfigureWeb::SetConfiguration() {
ui->web_signup_link->setOpenExternalLinks(true); ui->web_signup_link->setOpenExternalLinks(true);
ui->web_token_info_link->setOpenExternalLinks(true); ui->web_token_info_link->setOpenExternalLinks(true);
if (Settings::values.yuzu_username.GetValue().empty()) { ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
ui->username->setText(tr("Unspecified")); ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token.GetValue()));
} else { // ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken(
ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); // Settings::values.yuzu_username.GetValue(), 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 after setting the values, to avoid calling OnLoginChanged now
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
@ -95,15 +91,8 @@ void ConfigureWeb::SetConfiguration() {
void ConfigureWeb::ApplyConfiguration() { void ConfigureWeb::ApplyConfiguration() {
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
if (user_verified) { Settings::values.yuzu_username = ui->edit_username->text().toStdString();
Settings::values.yuzu_username = Settings::values.yuzu_token = ui->edit_token->text().toStdString();
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."));
}
} }
void ConfigureWeb::OnLoginChanged() { void ConfigureWeb::OnLoginChanged() {
@ -124,30 +113,34 @@ void ConfigureWeb::OnLoginChanged() {
} }
void ConfigureWeb::VerifyLogin() { void ConfigureWeb::VerifyLogin() {
ui->button_verify_login->setDisabled(true); QMessageBox::warning(this,
ui->button_verify_login->setText(tr("Verifying...")); tr("Warning"),
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16)); tr("Verification is currently nonfunctional, instead generate a random "
ui->label_token_verified->setToolTip(tr("Verifying...")); "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() { void ConfigureWeb::OnLoginVerified() {
ui->button_verify_login->setEnabled(true); // ui->button_verify_login->setEnabled(true);
ui->button_verify_login->setText(tr("Verify")); // ui->button_verify_login->setText(tr("Verify"));
if (verify_watcher.result()) { // if (verify_watcher.result()) {
user_verified = true; // user_verified = true;
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16)); // ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
ui->label_token_verified->setToolTip(tr("Verified", "Tooltip")); // ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
ui->username->setText( // ui->username->setText(
QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString()))); // QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
} else { // } else {
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16)); // ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip")); // ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
ui->username->setText(tr("Unspecified")); // ui->username->setText(tr("Unspecified"));
QMessageBox::critical(this, tr("Verification failed"), // QMessageBox::critical(this, tr("Verification failed"),
tr("Verification failed. Check that you have entered your token " // tr("Verification failed. Check that you have entered your token "
"correctly, and that your internet connection is working.")); // "correctly, and that your internet connection is working."));
} // }
} }
void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) { void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {

View file

@ -43,7 +43,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
<enum>Qt::RightToLeft</enum> <enum>Qt::LayoutDirection::RightToLeft</enum>
</property> </property>
<property name="text"> <property name="text">
<string>Verify</string> <string>Verify</string>
@ -52,13 +52,23 @@
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="web_signup_link"> <widget class="QLabel" name="web_signup_link">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>Sign up</string> <string>Sign up</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" colspan="3"> <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>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label_token"> <widget class="QLabel" name="label_token">
@ -83,7 +93,7 @@
<number>80</number> <number>80</number>
</property> </property>
<property name="echoMode"> <property name="echoMode">
<enum>QLineEdit::Password</enum> <enum>QLineEdit::EchoMode::Normal</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -97,7 +107,7 @@
<item row="2" column="2"> <item row="2" column="2">
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -143,7 +153,7 @@
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
#include <regex> #include <regex>

View file

@ -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 // SPDX-License-Identifier: GPL-3.0-or-later
#include <cinttypes> #include <cinttypes>
@ -351,7 +351,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(system->GetProfileManager()); play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(system->GetProfileManager());
system->GetRoomNetwork().Init(); Network::Init();
RegisterMetaTypes(); RegisterMetaTypes();
@ -5183,7 +5183,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
render_window->close(); render_window->close();
multiplayer_state->Close(); multiplayer_state->Close();
system->HIDCore().UnloadInputDevices(); system->HIDCore().UnloadInputDevices();
system->GetRoomNetwork().Shutdown(); Network::Shutdown();
QWidget::closeEvent(event); QWidget::closeEvent(event);
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <array>
#include <future> #include <future>
#include <QColor> #include <QColor>
@ -27,8 +30,7 @@
class ChatMessage { class ChatMessage {
public: public:
explicit ChatMessage(const Network::ChatEntry& chat, Network::RoomNetwork& room_network, explicit ChatMessage(const Network::ChatEntry& chat, QTime ts = {}) {
QTime ts = {}) {
/// Convert the time to their default locale defined format /// Convert the time to their default locale defined format
QLocale locale; QLocale locale;
timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat); timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
@ -38,7 +40,7 @@ public:
// Check for user pings // Check for user pings
QString cur_nickname, cur_username; 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_nickname = QString::fromStdString(room->GetNickname());
cur_username = QString::fromStdString(room->GetUsername()); 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; ChatRoom::~ChatRoom() = default;
void ChatRoom::Initialize(Network::RoomNetwork* room_network_) { void ChatRoom::Initialize() {
room_network = room_network_;
// setup the callbacks for network updates // setup the callbacks for network updates
if (auto member = room_network->GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
member->BindOnChatMessageReceived( member->BindOnChatMessageReceived(
[this](const Network::ChatEntry& chat) { emit ChatReceived(chat); }); [this](const Network::ChatEntry& chat) { emit ChatReceived(chat); });
member->BindOnStatusMessageReceived( member->BindOnStatusMessageReceived(
@ -239,7 +240,7 @@ void ChatRoom::AppendChatMessage(const QString& msg) {
} }
void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) { 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 members = room->GetMemberInformation();
auto it = std::find_if(members.begin(), members.end(), auto it = std::find_if(members.begin(), members.end(),
[&nickname](const Network::RoomMember::MemberInformation& member) { [&nickname](const Network::RoomMember::MemberInformation& member) {
@ -259,7 +260,7 @@ bool ChatRoom::ValidateMessage(const std::string& msg) {
void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) { void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) {
// TODO(B3N30): change title // TODO(B3N30): change title
if (auto room_member = room_network->GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
SetPlayerList(room_member->GetMemberInformation()); SetPlayerList(room_member->GetMemberInformation());
} }
} }
@ -278,7 +279,7 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
if (!ValidateMessage(chat.message)) { if (!ValidateMessage(chat.message)) {
return; return;
} }
if (auto room = room_network->GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
// get the id of the player // get the id of the player
auto members = room->GetMemberInformation(); auto members = room->GetMemberInformation();
auto it = std::find_if(members.begin(), members.end(), auto it = std::find_if(members.begin(), members.end(),
@ -296,7 +297,7 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
return; return;
} }
auto player = std::distance(members.begin(), it); auto player = std::distance(members.begin(), it);
ChatMessage m(chat, *room_network); ChatMessage m(chat);
if (m.ContainsPing()) { if (m.ContainsPing()) {
emit UserPinged(); emit UserPinged();
} }
@ -335,7 +336,7 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_
} }
void ChatRoom::OnSendChat() { void ChatRoom::OnSendChat() {
if (auto room_member = room_network->GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
if (!room_member->IsConnected()) { if (!room_member->IsConnected()) {
return; return;
} }
@ -357,7 +358,7 @@ void ChatRoom::OnSendChat() {
LOG_INFO(Network, "Cannot find self in the player list when sending a message."); LOG_INFO(Network, "Cannot find self in the player list when sending a message.");
} }
auto player = std::distance(members.begin(), it); auto player = std::distance(members.begin(), it);
ChatMessage m(chat, *room_network); ChatMessage m(chat);
room_member->SendChatMessage(message); room_member->SendChatMessage(message);
AppendChatMessage(m.GetPlayerChatMessage(player)); AppendChatMessage(m.GetPlayerChatMessage(player));
ui->chat_message->clear(); ui->chat_message->clear();
@ -451,7 +452,7 @@ void ChatRoom::PopupContextMenu(const QPoint& menu_location) {
} }
std::string cur_nickname; std::string cur_nickname;
if (auto room = room_network->GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
cur_nickname = room->GetNickname(); cur_nickname = room->GetNickname();
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <memory> #include <memory>
@ -30,7 +33,7 @@ class ChatRoom : public QWidget {
public: public:
explicit ChatRoom(QWidget* parent); explicit ChatRoom(QWidget* parent);
void Initialize(Network::RoomNetwork* room_network); void Initialize();
void RetranslateUi(); void RetranslateUi();
void SetPlayerList(const Network::RoomMember::MemberList& member_list); void SetPlayerList(const Network::RoomMember::MemberList& member_list);
void Clear(); void Clear();
@ -66,7 +69,6 @@ private:
std::unique_ptr<Ui::ChatRoom> ui; std::unique_ptr<Ui::ChatRoom> ui;
std::unordered_set<std::string> block_list; std::unordered_set<std::string> block_list;
std::unordered_map<std::string, QPixmap> icon_cache; std::unordered_map<std::string, QPixmap> icon_cache;
Network::RoomNetwork* room_network;
}; };
Q_DECLARE_METATYPE(Network::ChatEntry); Q_DECLARE_METATYPE(Network::ChatEntry);

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <future>
#include <QColor> #include <QColor>
#include <QImage> #include <QImage>
@ -18,14 +21,14 @@
#include "yuzu/multiplayer/moderation_dialog.h" #include "yuzu/multiplayer/moderation_dialog.h"
#include "yuzu/multiplayer/state.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), : 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->setupUi(this);
ui->chat->Initialize(&room_network); ui->chat->Initialize();
// setup the callbacks for network updates // setup the callbacks for network updates
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
member->BindOnRoomInformationChanged( member->BindOnRoomInformationChanged(
[this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); }); [this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); });
member->BindOnStateChanged( member->BindOnStateChanged(
@ -44,7 +47,7 @@ ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_n
ui->disconnect->setDefault(false); ui->disconnect->setDefault(false);
ui->disconnect->setAutoDefault(false); ui->disconnect->setAutoDefault(false);
connect(ui->moderation, &QPushButton::clicked, [this] { connect(ui->moderation, &QPushButton::clicked, [this] {
ModerationDialog dialog(room_network, this); ModerationDialog dialog(this);
dialog.exec(); dialog.exec();
}); });
ui->moderation->setDefault(false); ui->moderation->setDefault(false);
@ -90,7 +93,7 @@ void ClientRoomWindow::Disconnect() {
} }
void ClientRoomWindow::UpdateView() { void ClientRoomWindow::UpdateView() {
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
if (member->IsConnected()) { if (member->IsConnected()) {
ui->chat->Enable(); ui->chat->Enable();
ui->disconnect->setEnabled(true); ui->disconnect->setEnabled(true);

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include "yuzu/multiplayer/chat_room.h" #include "yuzu/multiplayer/chat_room.h"
@ -13,7 +16,7 @@ class ClientRoomWindow : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_); explicit ClientRoomWindow(QWidget* parent);
~ClientRoomWindow(); ~ClientRoomWindow();
void RetranslateUi(); void RetranslateUi();
@ -35,5 +38,4 @@ private:
QStandardItemModel* player_list; QStandardItemModel* player_list;
std::unique_ptr<Ui::ClientRoom> ui; std::unique_ptr<Ui::ClientRoom> ui;
Network::RoomNetwork& room_network;
}; };

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <QComboBox>
#include <QFuture> #include <QFuture>
#include <QIntValidator> #include <QIntValidator>
@ -24,8 +27,7 @@ enum class ConnectionType : u8 { TraversalServer, IP };
DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent) DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{ ui(std::make_unique<Ui::DirectConnect>()), system{system_} {
system.GetRoomNetwork()} {
ui->setupUi(this); ui->setupUi(this);
@ -71,7 +73,7 @@ void DirectConnectWindow::Connect() {
return; 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. // Prevent the user from trying to join a room while they are already joining.
if (member->GetState() == Network::RoomMember::State::Joining) { if (member->GetState() == Network::RoomMember::State::Joining) {
return; return;
@ -104,7 +106,7 @@ void DirectConnectWindow::Connect() {
// attempt to connect in a different thread // attempt to connect in a different thread
QFuture<void> f = QtConcurrent::run([&] { 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(); auto port = UISettings::values.multiplayer_port.GetValue();
room_member->Join(ui->nickname->text().toStdString(), room_member->Join(ui->nickname->text().toStdString(),
ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP, ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP,
@ -129,7 +131,7 @@ void DirectConnectWindow::EndConnecting() {
void DirectConnectWindow::OnConnection() { void DirectConnectWindow::OnConnection() {
EndConnecting(); EndConnecting();
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
if (room_member->IsConnected()) { if (room_member->IsConnected()) {
close(); close();
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <memory> #include <memory>
@ -45,5 +48,4 @@ private:
std::unique_ptr<Ui::DirectConnect> ui; std::unique_ptr<Ui::DirectConnect> ui;
Validation validation; Validation validation;
Core::System& system; Core::System& system;
Network::RoomNetwork& room_network;
}; };

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <future>
#include <QColor> #include <QColor>
#include <QImage> #include <QImage>
@ -32,8 +35,7 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
Core::System& system_) Core::System& system_)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui(std::make_unique<Ui::HostRoom>()), ui(std::make_unique<Ui::HostRoom>()),
announce_multiplayer_session(session), system{system_}, room_network{ announce_multiplayer_session(session), system{system_} {
system.GetRoomNetwork()} {
ui->setupUi(this); ui->setupUi(this);
// set up validation for all of the fields // set up validation for all of the fields
@ -137,7 +139,7 @@ void HostRoomWindow::Host() {
return; return;
} }
} }
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
if (member->GetState() == Network::RoomMember::State::Joining) { if (member->GetState() == Network::RoomMember::State::Joining) {
return; return;
} else if (member->IsConnected()) { } else if (member->IsConnected()) {
@ -161,7 +163,7 @@ void HostRoomWindow::Host() {
if (ui->load_ban_list->isChecked()) { if (ui->load_ban_list->isChecked()) {
ban_list = UISettings::values.multiplayer_ban_list; ban_list = UISettings::values.multiplayer_ban_list;
} }
if (auto room = room_network.GetRoom().lock()) { if (auto room = Network::GetRoom().lock()) {
const bool created = const bool created =
room->Create(ui->room_name->text().toStdString(), room->Create(ui->room_name->text().toStdString(),
ui->room_description->toPlainText().toStdString(), "", port, password, ui->room_description->toPlainText().toStdString(), "", port, password,
@ -190,7 +192,7 @@ void HostRoomWindow::Host() {
QString::fromStdString(result.result_string), QString::fromStdString(result.result_string),
QMessageBox::Ok); QMessageBox::Ok);
ui->host->setEnabled(true); ui->host->setEnabled(true);
if (auto room = room_network.GetRoom().lock()) { if (auto room = Network::GetRoom().lock()) {
room->Destroy(); room->Destroy();
} }
return; return;
@ -206,7 +208,7 @@ void HostRoomWindow::Host() {
WebService::Client client(Settings::values.web_api_url.GetValue(), WebService::Client client(Settings::values.web_api_url.GetValue(),
Settings::values.yuzu_username.GetValue(), Settings::values.yuzu_username.GetValue(),
Settings::values.yuzu_token.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; token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
} }
if (token.empty()) { if (token.empty()) {

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <memory> #include <memory>
@ -59,7 +62,6 @@ private:
ComboBoxProxyModel* proxy; ComboBoxProxyModel* proxy;
Validation validation; Validation validation;
Core::System& system; Core::System& system;
Network::RoomNetwork& room_network;
}; };
/** /**

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <QInputDialog>
#include <QList> #include <QList>
#include <QtConcurrent/QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
@ -28,8 +31,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_) std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui(std::make_unique<Ui::Lobby>()), ui(std::make_unique<Ui::Lobby>()),
announce_multiplayer_session(session), system{system_}, room_network{ announce_multiplayer_session(session), system{system_} {
system.GetRoomNetwork()} {
ui->setupUi(this); ui->setupUi(this);
// setup the watcher for background connections // 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. // Prevent the user from trying to join a room while they are already joining.
if (member->GetState() == Network::RoomMember::State::Joining) { if (member->GetState() == Network::RoomMember::State::Joining) {
return; return;
@ -184,7 +186,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString(); proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString();
// attempt to connect in a different thread // 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; std::string token;
#ifdef ENABLE_WEB_SERVICE #ifdef ENABLE_WEB_SERVICE
if (!Settings::values.yuzu_username.GetValue().empty() && if (!Settings::values.yuzu_username.GetValue().empty() &&
@ -200,7 +202,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
} }
} }
#endif #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, room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password,
token); token);
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <memory> #include <memory>
@ -102,7 +105,6 @@ private:
QFutureWatcher<void>* watcher; QFutureWatcher<void>* watcher;
Validation validation; Validation validation;
Core::System& system; Core::System& system;
Network::RoomNetwork& room_network;
}; };
/** /**

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <QStandardItem>
#include <QStandardItemModel> #include <QStandardItemModel>
#include "network/network.h" #include "network/network.h"
@ -16,13 +19,13 @@ enum {
}; };
} }
ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent) ModerationDialog::ModerationDialog(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()), room_network{room_network_} { : QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()) {
ui->setupUi(this); ui->setupUi(this);
qRegisterMetaType<Network::Room::BanList>(); qRegisterMetaType<Network::Room::BanList>();
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
callback_handle_status_message = member->BindOnStatusMessageReceived( callback_handle_status_message = member->BindOnStatusMessageReceived(
[this](const Network::StatusMessageEntry& status_message) { [this](const Network::StatusMessageEntry& status_message) {
emit StatusMessageReceived(status_message); emit StatusMessageReceived(status_message);
@ -55,20 +58,20 @@ ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget*
ModerationDialog::~ModerationDialog() { ModerationDialog::~ModerationDialog() {
if (callback_handle_status_message) { if (callback_handle_status_message) {
if (auto room = room_network.GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
room->Unbind(callback_handle_status_message); room->Unbind(callback_handle_status_message);
} }
} }
if (callback_handle_ban_list) { if (callback_handle_ban_list) {
if (auto room = room_network.GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
room->Unbind(callback_handle_ban_list); room->Unbind(callback_handle_ban_list);
} }
} }
} }
void ModerationDialog::LoadBanList() { void ModerationDialog::LoadBanList() {
if (auto room = room_network.GetRoomMember().lock()) { if (auto room = Network::GetRoomMember().lock()) {
ui->refresh->setEnabled(false); ui->refresh->setEnabled(false);
ui->refresh->setText(tr("Refreshing")); ui->refresh->setText(tr("Refreshing"));
ui->unban->setEnabled(false); ui->unban->setEnabled(false);
@ -97,7 +100,7 @@ void ModerationDialog::PopulateBanList(const Network::Room::BanList& ban_list) {
} }
void ModerationDialog::SendUnbanRequest(const QString& subject) { 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()); room->SendModerationRequest(Network::IdModUnban, subject.toStdString());
} }
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <memory> #include <memory>
@ -19,7 +22,7 @@ class ModerationDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent = nullptr); explicit ModerationDialog(QWidget* parent = nullptr);
~ModerationDialog(); ~ModerationDialog();
signals: signals:
@ -36,8 +39,6 @@ private:
QStandardItemModel* model; QStandardItemModel* model;
Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message; Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message;
Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list; Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list;
Network::RoomNetwork& room_network;
}; };
Q_DECLARE_METATYPE(Network::Room::BanList); Q_DECLARE_METATYPE(Network::Room::BanList);

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <QAction>
#include <QApplication> #include <QApplication>
#include <QIcon> #include <QIcon>
@ -22,8 +25,8 @@
MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_, MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_,
QAction* leave_room_, QAction* show_room_, Core::System& system_) QAction* leave_room_, QAction* show_room_, Core::System& system_)
: QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_), : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_),
show_room(show_room_), system{system_}, room_network{system.GetRoomNetwork()} { show_room(show_room_), system{system_} {
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
// register the network structs to use in slots and signals // register the network structs to use in slots and signals
state_callback_handle = member->BindOnStateChanged( state_callback_handle = member->BindOnStateChanged(
[this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); }); [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::State>();
qRegisterMetaType<Network::RoomMember::Error>(); qRegisterMetaType<Network::RoomMember::Error>();
qRegisterMetaType<WebService::WebResult>(); 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( announce_multiplayer_session->BindErrorCallback(
[this](const WebService::WebResult& result) { emit AnnounceFailed(result); }); [this](const WebService::WebResult& result) { emit AnnounceFailed(result); });
connect(this, &MultiplayerState::AnnounceFailed, this, &MultiplayerState::OnAnnounceFailed); connect(this, &MultiplayerState::AnnounceFailed, this, &MultiplayerState::OnAnnounceFailed);
@ -62,13 +65,13 @@ MultiplayerState::~MultiplayerState() = default;
void MultiplayerState::Close() { void MultiplayerState::Close() {
if (state_callback_handle) { if (state_callback_handle) {
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
member->Unbind(state_callback_handle); member->Unbind(state_callback_handle);
} }
} }
if (error_callback_handle) { if (error_callback_handle) {
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
member->Unbind(error_callback_handle); member->Unbind(error_callback_handle);
} }
} }
@ -256,9 +259,9 @@ void MultiplayerState::OnCreateRoom() {
bool MultiplayerState::OnCloseRoom() { bool MultiplayerState::OnCloseRoom() {
if (!NetworkMessage::WarnCloseRoom()) if (!NetworkMessage::WarnCloseRoom())
return false; return false;
if (auto room = room_network.GetRoom().lock()) { if (auto room = Network::GetRoom().lock()) {
// if you are in a room, leave it // if you are in a room, leave it
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
member->Leave(); member->Leave();
LOG_DEBUG(Frontend, "Left the room (as a client)"); LOG_DEBUG(Frontend, "Left the room (as a client)");
} }
@ -292,10 +295,10 @@ void MultiplayerState::HideNotification() {
} }
void MultiplayerState::OnOpenNetworkRoom() { void MultiplayerState::OnOpenNetworkRoom() {
if (auto member = room_network.GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
if (member->IsConnected()) { if (member->IsConnected()) {
if (client_room == nullptr) { if (client_room == nullptr) {
client_room = new ClientRoomWindow(this, room_network); client_room = new ClientRoomWindow(this);
connect(client_room, &ClientRoomWindow::ShowNotification, this, connect(client_room, &ClientRoomWindow::ShowNotification, this,
&MultiplayerState::ShowNotification); &MultiplayerState::ShowNotification);
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 #pragma once
#include <QWidget> #include <QWidget>
@ -105,7 +108,6 @@ private:
bool show_notification = false; bool show_notification = false;
Core::System& system; Core::System& system;
Network::RoomNetwork& room_network;
}; };
Q_DECLARE_METATYPE(WebService::WebResult); Q_DECLARE_METATYPE(WebService::WebResult);

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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 <chrono>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
@ -404,7 +407,7 @@ int main(int argc, char** argv) {
} }
if (use_multiplayer) { if (use_multiplayer) {
if (auto member = system.GetRoomNetwork().GetRoomMember().lock()) { if (auto member = Network::GetRoomMember().lock()) {
member->BindOnChatMessageReceived(OnMessageReceived); member->BindOnChatMessageReceived(OnMessageReceived);
member->BindOnStatusMessageReceived(OnStatusMessageReceived); member->BindOnStatusMessageReceived(OnStatusMessageReceived);
member->BindOnStateChanged(OnStateChanged); member->BindOnStateChanged(OnStateChanged);

View file

@ -38,8 +38,7 @@
"description": "Enable web services (telemetry, etc.)", "description": "Enable web services (telemetry, etc.)",
"dependencies": [ "dependencies": [
{ {
"name": "openssl", "name": "openssl"
"platform": "windows"
} }
] ]
} }