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-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# Build directory
[Bb]uild*/
doc-build/
@ -36,3 +39,7 @@ CMakeSettings.json
# Windows global filetypes
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
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
# 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
import android.annotation.SuppressLint
@ -162,7 +165,7 @@ android {
arguments(
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL
"-DENABLE_WEB_SERVICE=0", // Don't use telemetry
"-DENABLE_WEB_SERVICE=1", // Enable web service
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DYUZU_USE_BUNDLED_VCPKG=ON",
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
@ -176,9 +179,9 @@ android {
}
}
tasks.create<Delete>("ktlintReset") {
delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
}
tasks.register<Delete>("ktlintReset", fun Delete.() {
delete(File(layout.buildDirectory.toString() + File.separator + "intermediates/ktLint"))
})
val showFormatHelp = {
logger.lifecycle(

View file

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

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@ -270,8 +270,7 @@ object NativeLibrary {
NetPlayManager.clearChat()
}
external fun netPlayInit()
external fun initMultiplayer()
@Keep
@JvmStatic

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

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

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
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.dialogs
import android.annotation.SuppressLint
import android.content.Context
import org.yuzu.yuzu_emu.R
import android.content.res.Configuration
@ -16,6 +13,7 @@ import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.PopupMenu
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
@ -32,10 +30,13 @@ import org.yuzu.yuzu_emu.databinding.ItemButtonNetplayBinding
import org.yuzu.yuzu_emu.databinding.ItemTextNetplayBinding
import org.yuzu.yuzu_emu.utils.CompatUtils
import org.yuzu.yuzu_emu.network.NetPlayManager
import org.yuzu.yuzu_emu.utils.GameHelper
class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
private lateinit var adapter: NetPlayAdapter
private val gameNameList: MutableList<Array<String>> = mutableListOf()
private val gameIdList: MutableList<Array<Long>> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -71,6 +72,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
else -> {
DialogMultiplayerConnectBinding.inflate(layoutInflater).apply {
setContentView(root)
for (game in GameHelper.cachedGameList) {
val gameName = game.title
if (gameNameList.none { it[0] == gameName }) {
gameNameList.add(arrayOf(gameName))
}
val gameId = game.programId.toLong()
if (gameIdList.none { it[0] == gameId }) {
gameIdList.add(arrayOf(gameId))
}
}
btnCreate.setOnClickListener {
showNetPlayInputDialog(true)
dismiss()
@ -79,6 +91,10 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
showNetPlayInputDialog(false)
dismiss()
}
btnLobbyBrowser.setOnClickListener {
LobbyBrowser(context).show()
dismiss()
}
}
}
}
@ -208,10 +224,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
override fun getItemCount() = netPlayItems.size
}
@SuppressLint("NotifyDataSetChanged")
fun refreshAdapterItems() {
val handler = Handler(Looper.getMainLooper())
NetPlayManager.setOnAdapterRefreshListener() { type, msg ->
NetPlayManager.setOnAdapterRefreshListener() { _, _ ->
handler.post {
adapter.netPlayItems.clear()
adapter.loadMultiplayerMenu()
@ -244,6 +261,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
binding.ipPort.setText(NetPlayManager.getRoomPort(activity))
binding.username.setText(NetPlayManager.getUsername(activity))
binding.dropdownPreferredGameName.apply {
setAdapter(
ArrayAdapter(
activity,
R.layout.dropdown_item,
gameNameList.map { it[0] }
)
)
}
binding.preferredGameName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.roomName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.maxPlayersContainer.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
binding.maxPlayersLabel.text = context.getString(R.string.multiplayer_max_players_value, binding.maxPlayers.value.toInt())
@ -259,6 +287,8 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
val ipAddress = binding.ipAddress.text.toString()
val username = binding.username.text.toString()
val portStr = binding.ipPort.text.toString()
val preferredGameName = binding.dropdownPreferredGameName.text.toString()
val preferredGameId = gameIdList[gameNameList.indexOfFirst { it[0] == preferredGameName }][0]
val password = binding.password.text.toString()
val port = portStr.toIntOrNull() ?: run {
Toast.makeText(activity, R.string.multiplayer_port_invalid, Toast.LENGTH_LONG).show()
@ -276,6 +306,13 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
return@setOnClickListener
}
if (isCreateRoom && preferredGameName.isEmpty()) {
Toast.makeText(activity, R.string.multiplayer_preferred_game_name_invalid, Toast.LENGTH_LONG).show();
binding.btnConfirm.isEnabled = false
binding.btnConfirm.text = activity.getString(R.string.original_button_text)
return@setOnClickListener
}
if (ipAddress.length < 7 || username.length < 5) {
Toast.makeText(activity, R.string.multiplayer_input_invalid, Toast.LENGTH_LONG).show()
binding.btnConfirm.isEnabled = true
@ -283,7 +320,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
} else {
Handler(Looper.getMainLooper()).post {
val result = if (isCreateRoom) {
NetPlayManager.netPlayCreateRoom(ipAddress, port, username, password, roomName, maxPlayers)
NetPlayManager.netPlayCreateRoom(ipAddress, port, username, preferredGameName, preferredGameId, password, roomName, maxPlayers)
} else {
NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password)
}
@ -397,4 +434,4 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
}
}
}
}

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

View file

@ -7,7 +7,9 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class StringSetting(override val key: String) : AbstractStringSetting {
DRIVER_PATH("driver_path"),
DEVICE_NAME("device_name");
DEVICE_NAME("device_name"),
WEB_TOKEN("yuzu_token"),
;
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)

View file

@ -274,6 +274,13 @@ abstract class SettingsItem(
descriptionId = R.string.use_custom_rtc_description
)
)
put(
StringInputSetting(
StringSetting.WEB_TOKEN,
titleId = R.string.web_token,
descriptionId = R.string.web_token_description
)
)
put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))
put(
SingleChoiceSetting(

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

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

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
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
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
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
package org.yuzu.yuzu_emu.fragments

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

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
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
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
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-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.Manifest
@ -128,7 +124,7 @@ class HomeSettingsFragment : Fragment() {
R.string.multiplayer_description,
R.drawable.ic_two_users,
{
val action = mainActivity.displayMultiplayerDialog()
mainActivity.displayMultiplayerDialog()
},
)
)

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

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
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
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
package org.yuzu.yuzu_emu.model

View file

@ -1,16 +1,14 @@
// Copyright 2024 Mandarine Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.network
import android.app.Activity
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.text.format.Formatter
@ -19,10 +17,27 @@ import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.dialogs.ChatMessage
import java.net.Inet4Address
object NetPlayManager {
external fun netPlayCreateRoom(ipAddress: String, port: Int, username: String, password: String, roomName: String, maxPlayers: Int): Int
external fun netPlayJoinRoom(ipAddress: String, port: Int, username: String, password: String): Int
external fun netPlayCreateRoom(
ipAddress: String,
port: Int,
username: String,
preferredGameName: String,
preferredGameId: Long,
password: String,
roomName: String,
maxPlayers: Int
): Int
external fun netPlayJoinRoom(
ipAddress: String,
port: Int,
username: String,
password: String
): Int
external fun netPlayRoomInfo(): Array<String>
external fun netPlayIsJoined(): Boolean
external fun netPlayIsHostedRoom(): Boolean
@ -33,6 +48,28 @@ object NetPlayManager {
external fun netPlayGetBanList(): Array<String>
external fun netPlayBanUser(username: String)
external fun netPlayUnbanUser(username: String)
external fun netPlayGetPublicRooms(): Array<String>
data class RoomInfo(
val name: String,
val hasPassword: Boolean,
val maxPlayers: Int,
val ip: String,
val port: Int,
val description: String,
val owner: String,
val preferredGameId: Long,
val preferredGameName: String,
val members: MutableList<RoomMember> = mutableListOf()
)
data class RoomMember(
val username: String,
val nickname: String,
val gameId: Long,
val gameName: String
)
private var messageListener: ((Int, String) -> Unit)? = null
private var adapterRefreshListener: ((Int, String) -> Unit)? = null
@ -41,11 +78,57 @@ object NetPlayManager {
messageListener = listener
}
fun getPublicRooms(): List<RoomInfo> {
val roomData = netPlayGetPublicRooms()
val rooms = mutableMapOf<String, RoomInfo>()
for (data in roomData) {
val parts = data.split("|")
if (parts[0] == "MEMBER" && parts.size >= 6) {
val roomName = parts[1]
val member = RoomMember(
username = parts[2],
nickname = parts[3],
gameId = parts[4].toLongOrNull() ?: 0L,
gameName = parts[5]
)
rooms[roomName]?.members?.add(member)
} else if (parts.size >= 9) {
val roomInfo = RoomInfo(
name = parts[0],
hasPassword = parts[1] == "1",
maxPlayers = parts[2].toIntOrNull() ?: 0,
ip = parts[3],
port = parts[4].toIntOrNull() ?: 0,
description = parts[5],
owner = parts[6],
preferredGameId = parts[7].toLongOrNull() ?: 0L,
preferredGameName = parts[8]
)
rooms[roomInfo.name] = roomInfo
}
}
return rooms.values.toList()
}
fun refreshRoomListAsync(callback: (List<RoomInfo>) -> Unit) {
Thread {
val rooms = getPublicRooms()
Handler(Looper.getMainLooper()).post {
callback(rooms)
}
}.start()
}
fun setOnAdapterRefreshListener(listener: (Int, String) -> Unit) {
adapterRefreshListener = listener
}
fun getUsername(activity: Context): String { val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
fun getUsername(activity: Context): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
val name = "Eden${(Math.random() * 100).toInt()}"
return prefs.getString("NetPlayUsername", name) ?: name
}
@ -103,31 +186,36 @@ object NetPlayManager {
if (parts.size == 2) {
val nickname = parts[0].trim()
val chatMessage = parts[1].trim()
addChatMessage(ChatMessage(
nickname = nickname,
username = "",
message = chatMessage
))
addChatMessage(
ChatMessage(
nickname = nickname,
username = "",
message = chatMessage
)
)
}
}
NetPlayStatus.MEMBER_JOIN,
NetPlayStatus.MEMBER_LEAVE,
NetPlayStatus.MEMBER_KICKED,
NetPlayStatus.MEMBER_BANNED -> {
addChatMessage(ChatMessage(
nickname = "System",
username = "",
message = message
))
addChatMessage(
ChatMessage(
nickname = "System",
username = "",
message = message
)
)
}
}
Handler(Looper.getMainLooper()).post {
if (!isChatOpen) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
Handler(Looper.getMainLooper()).post {
if (!isChatOpen) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
messageListener?.invoke(type, msg)
@ -159,34 +247,59 @@ object NetPlayManager {
NetPlayStatus.ROOM_MODERATOR -> context.getString(R.string.multiplayer_room_moderator)
NetPlayStatus.MEMBER_JOIN -> context.getString(R.string.multiplayer_member_join, msg)
NetPlayStatus.MEMBER_LEAVE -> context.getString(R.string.multiplayer_member_leave, msg)
NetPlayStatus.MEMBER_KICKED -> context.getString(R.string.multiplayer_member_kicked, msg)
NetPlayStatus.MEMBER_BANNED -> context.getString(R.string.multiplayer_member_banned, msg)
NetPlayStatus.MEMBER_KICKED -> context.getString(
R.string.multiplayer_member_kicked,
msg
)
NetPlayStatus.MEMBER_BANNED -> context.getString(
R.string.multiplayer_member_banned,
msg
)
NetPlayStatus.ADDRESS_UNBANNED -> context.getString(R.string.multiplayer_address_unbanned)
NetPlayStatus.CHAT_MESSAGE -> msg
else -> ""
}
}
fun getIpAddressByWifi(activity: Activity): String {
var ipAddress = 0
val wifiManager = activity.getSystemService(WifiManager::class.java)
val wifiInfo = wifiManager.connectionInfo
if (wifiInfo != null) {
ipAddress = wifiInfo.ipAddress
}
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
}
if (ipAddress == 0) {
val dhcpInfo = wifiManager.dhcpInfo
if (dhcpInfo != null) {
ipAddress = dhcpInfo.ipAddress
fun getIpAddressByWifi(activity: Activity): String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// For Android 12 (API 31) and above
val connectivityManager = activity.getSystemService(ConnectivityManager::class.java)
val network = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(network)
if (capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) {
val linkProperties = connectivityManager.getLinkProperties(network)
linkProperties?.linkAddresses?.firstOrNull { it.address is Inet4Address }?.let {
return it.address.hostAddress ?: "192.168.0.1"
}
}
}
return if (ipAddress == 0) {
"192.168.0.1"
} else {
Formatter.formatIpAddress(ipAddress)
// For Android 11 (API 30) and below
try {
val connectivityManager = activity.getSystemService(ConnectivityManager::class.java)
val network = connectivityManager.activeNetwork
if (network != null) {
val linkProperties = connectivityManager.getLinkProperties(network)
linkProperties?.linkAddresses?.firstOrNull { it.address is Inet4Address }?.let {
return it.address.hostAddress ?: "192.168.0.1"
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return "192.168.0.1"
}
fun getBanList(): List<String> {
@ -223,4 +336,4 @@ object NetPlayManager {
const val ADDRESS_UNBANNED = 26
const val CHAT_MESSAGE = 27
}
}
}

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
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-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.ui.main
import android.content.Intent
@ -72,9 +68,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
ThemeHelper.ThemeChangeListener(this)
ThemeHelper.setTheme(this)
NativeLibrary.netPlayInit()
super.onCreate(savedInstanceState)
NativeLibrary.initMultiplayer()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

View file

@ -1,8 +1,4 @@
// Copyright 2024 Mandarine Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils

View file

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

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
package org.yuzu.yuzu_emu.utils

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@ -68,12 +68,17 @@
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/shader_notify.h"
#include "network/announce_multiplayer_session.h"
#define jconst [[maybe_unused]] const auto
#define jauto [[maybe_unused]] auto
static EmulationSession s_instance;
//Abdroid Multiplayer which can be initialized with parameters
std::unique_ptr<AndroidMultiplayer> multiplayer{nullptr};
std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
EmulationSession::EmulationSession() {
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
}
@ -916,12 +921,33 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobje
return ContentManager::AreKeysPresent();
}
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_NativeLibrary_initMultiplayer(
JNIEnv* env, [[maybe_unused]] jobject obj) {
if (multiplayer) {
return;
}
announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>();
multiplayer = std::make_unique<AndroidMultiplayer>(s_instance.System(), announce_multiplayer_session);
multiplayer->NetworkInit();
}
JNIEXPORT jobjectArray JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetPublicRooms(
JNIEnv *env, [[maybe_unused]] jobject obj) {
return Common::Android::ToJStringArray(env, multiplayer->NetPlayGetPublicRooms());
}
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayCreateRoom(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
jstring username, jstring password, jstring room_name, jint max_players) {
jstring username, jstring preferredGameName, jlong preferredGameId, jstring password,
jstring room_name, jint max_players) {
return static_cast<jint>(
NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port,
Common::Android::GetJString(env, username), Common::Android::GetJString(env, password),
multiplayer->NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port,
Common::Android::GetJString(env, username), Common::Android::GetJString(env, preferredGameName),
preferredGameId,Common::Android::GetJString(env, password),
Common::Android::GetJString(env, room_name), max_players));
}
@ -929,70 +955,63 @@ JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayJoi
JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
jstring username, jstring password) {
return static_cast<jint>(
NetPlayJoinRoom(Common::Android::GetJString(env, ipaddress), port,
multiplayer->NetPlayJoinRoom(Common::Android::GetJString(env, ipaddress), port,
Common::Android::GetJString(env, username), Common::Android::GetJString(env, password)));
}
JNIEXPORT jobjectArray JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayRoomInfo(
JNIEnv* env, [[maybe_unused]] jobject obj) {
return Common::Android::ToJStringArray(env, NetPlayRoomInfo());
return Common::Android::ToJStringArray(env, multiplayer->NetPlayRoomInfo());
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsJoined(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
return NetPlayIsJoined();
return multiplayer->NetPlayIsJoined();
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsHostedRoom(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
return NetPlayIsHostedRoom();
return multiplayer->NetPlayIsHostedRoom();
}
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlaySendMessage(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring msg) {
NetPlaySendMessage(Common::Android::GetJString(env, msg));
multiplayer->NetPlaySendMessage(Common::Android::GetJString(env, msg));
}
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayKickUser(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
NetPlayKickUser(Common::Android::GetJString(env, username));
multiplayer->NetPlayKickUser(Common::Android::GetJString(env, username));
}
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayLeaveRoom(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
NetPlayLeaveRoom();
multiplayer->NetPlayLeaveRoom();
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsModerator(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
return NetPlayIsModerator();
return multiplayer->NetPlayIsModerator();
}
JNIEXPORT jobjectArray JNICALL
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetBanList(
JNIEnv* env, [[maybe_unused]] jobject obj) {
return Common::Android::ToJStringArray(env, NetPlayGetBanList());
return Common::Android::ToJStringArray(env, multiplayer->NetPlayGetBanList());
}
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayBanUser(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
NetPlayBanUser(Common::Android::GetJString(env, username));
multiplayer->NetPlayBanUser(Common::Android::GetJString(env, username));
}
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayUnbanUser(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
NetPlayUnbanUser(Common::Android::GetJString(env, username));
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"

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

View file

@ -59,6 +59,21 @@
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:id="@+id/preferred_game_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:hint="@string/multiplayer_preferred_game_name">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/dropdown_preferred_game_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

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="set_custom_rtc">Set custom RTC</string>
<!-- Network settings strings -->
<string name="web_token">Web Token</string>
<string name="web_token_description">Web token used for creating public lobbies. It is a 48-character string containing only lowercase a-z.</string>
<string name="network">Network</string>
<!-- Graphics settings strings -->
<string name="frame_skipping">WIP: Frameskip</string>
<string name="frame_skipping_description">Toggle frame skipping to improve performance by reducing the number of rendered frames. This feature is still being worked on and will be enabled in future releases.</string>

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "android_common.h"

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View file

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

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View file

@ -1,8 +1,4 @@
// Copyright 2024 Mandarine Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "common/android/id_cache.h"
@ -19,35 +15,38 @@
#include <chrono>
namespace IDCache = Common::Android;
Network::RoomNetwork* room_network;
void AddNetPlayMessage(jint type, jstring msg) {
AndroidMultiplayer::AndroidMultiplayer(Core::System& system_,
std::shared_ptr<Core::AnnounceMultiplayerSession> session)
: system{system_}, announce_multiplayer_session(session) {}
AndroidMultiplayer::~AndroidMultiplayer() = default;
void AndroidMultiplayer::AddNetPlayMessage(jint type, jstring msg) {
IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
IDCache::GetAddNetPlayMessage(), type, msg);
}
void AddNetPlayMessage(int type, const std::string& msg) {
void AndroidMultiplayer::AddNetPlayMessage(int type, const std::string& msg) {
JNIEnv* env = IDCache::GetEnvForThread();
AddNetPlayMessage(type, Common::Android::ToJString(env, msg));
}
void ClearChat() {
void AndroidMultiplayer::ClearChat() {
IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
IDCache::ClearChat());
}
bool NetworkInit(Network::RoomNetwork* room_network_) {
room_network = room_network_;
bool result = room_network->Init();
bool AndroidMultiplayer::NetworkInit() {
bool result = Network::Init();
if (!result) {
return false;
}
if (auto member = room_network->GetRoomMember().lock()) {
if (auto member = Network::GetRoomMember().lock()) {
// register the network structs to use in slots and signals
member->BindOnStateChanged([](const Network::RoomMember::State& state) {
member->BindOnStateChanged([this](const Network::RoomMember::State& state) {
if (state == Network::RoomMember::State::Joined ||
state == Network::RoomMember::State::Moderator) {
NetPlayStatus status;
@ -65,7 +64,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
AddNetPlayMessage(static_cast<int>(status), msg);
}
});
member->BindOnError([](const Network::RoomMember::Error& error) {
member->BindOnError([this](const Network::RoomMember::Error& error) {
NetPlayStatus status;
std::string msg;
switch (error) {
@ -108,7 +107,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
}
AddNetPlayMessage(static_cast<int>(status), msg);
});
member->BindOnStatusMessageReceived([](const Network::StatusMessageEntry& status_message) {
member->BindOnStatusMessageReceived([this](const Network::StatusMessageEntry& status_message) {
NetPlayStatus status = NetPlayStatus::NO_ERROR;
std::string msg(status_message.nickname);
switch (status_message.type) {
@ -130,7 +129,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
}
AddNetPlayMessage(static_cast<int>(status), msg);
});
member->BindOnChatMessageReceived([](const Network::ChatEntry& chat) {
member->BindOnChatMessageReceived([this](const Network::ChatEntry& chat) {
NetPlayStatus status = NetPlayStatus::CHAT_MESSAGE;
std::string msg(chat.nickname);
msg += ": ";
@ -141,13 +140,11 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
return true;
}
NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
const std::string& username, const std::string& password,
NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string& ipaddress, int port,
const std::string& username, const std::string &preferredGameName,
const u64 &preferredGameId, const std::string& password,
const std::string& room_name, int max_players) {
__android_log_print(ANDROID_LOG_INFO, "NetPlay", "NetPlayCreateRoom called with ipaddress: %s, port: %d, username: %s, room_name: %s, max_players: %d", ipaddress.c_str(), port, username.c_str(), room_name.c_str(), max_players);
auto member = room_network->GetRoomMember().lock();
auto member = Network::GetRoomMember().lock();
if (!member) {
return NetPlayStatus::NETWORK_ERROR;
}
@ -156,7 +153,7 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
return NetPlayStatus::ALREADY_IN_ROOM;
}
auto room = room_network->GetRoom().lock();
auto room = Network::GetRoom().lock();
if (!room) {
return NetPlayStatus::NETWORK_ERROR;
}
@ -167,14 +164,14 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
// Placeholder game info
const AnnounceMultiplayerRoom::GameInfo game{
.name = "Default Game",
.id = 0, // Default program ID
.name = preferredGameName,
.id = preferredGameId,
};
port = (port == 0) ? Network::DefaultRoomPort : static_cast<u16>(port);
if (!room->Create(room_name, "", ipaddress, static_cast<u16>(port), password,
static_cast<u32>(std::min(max_players, 16)), username, game, nullptr, {})) {
static_cast<u32>(std::min(max_players, 16)), username, game, std::make_unique<Network::VerifyUser::NullBackend>(), {})) {
return NetPlayStatus::CREATE_ROOM_ERROR;
}
@ -197,9 +194,9 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
return NetPlayStatus::CREATE_ROOM_ERROR;
}
NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port,
NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string& ipaddress, int port,
const std::string& username, const std::string& password) {
auto member = room_network->GetRoomMember().lock();
auto member = Network::GetRoomMember().lock();
if (!member) {
return NetPlayStatus::NETWORK_ERROR;
}
@ -229,8 +226,8 @@ NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port,
return NetPlayStatus::WRONG_PASSWORD;
}
void NetPlaySendMessage(const std::string& msg) {
if (auto room = room_network->GetRoomMember().lock()) {
void AndroidMultiplayer::NetPlaySendMessage(const std::string& msg) {
if (auto room = Network::GetRoomMember().lock()) {
if (room->GetState() != Network::RoomMember::State::Joined &&
room->GetState() != Network::RoomMember::State::Moderator) {
@ -240,8 +237,8 @@ void NetPlaySendMessage(const std::string& msg) {
}
}
void NetPlayKickUser(const std::string& username) {
if (auto room = room_network->GetRoomMember().lock()) {
void AndroidMultiplayer::NetPlayKickUser(const std::string& username) {
if (auto room = Network::GetRoomMember().lock()) {
auto members = room->GetMemberInformation();
auto it = std::find_if(members.begin(), members.end(),
[&username](const Network::RoomMember::MemberInformation& member) {
@ -253,8 +250,8 @@ void NetPlayKickUser(const std::string& username) {
}
}
void NetPlayBanUser(const std::string& username) {
if (auto room = room_network->GetRoomMember().lock()) {
void AndroidMultiplayer::NetPlayBanUser(const std::string& username) {
if (auto room = Network::GetRoomMember().lock()) {
auto members = room->GetMemberInformation();
auto it = std::find_if(members.begin(), members.end(),
[&username](const Network::RoomMember::MemberInformation& member) {
@ -266,15 +263,15 @@ void NetPlayBanUser(const std::string& username) {
}
}
void NetPlayUnbanUser(const std::string& username) {
if (auto room = room_network->GetRoomMember().lock()) {
void AndroidMultiplayer::NetPlayUnbanUser(const std::string& username) {
if (auto room = Network::GetRoomMember().lock()) {
room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username);
}
}
std::vector<std::string> NetPlayRoomInfo() {
std::vector<std::string> AndroidMultiplayer::NetPlayRoomInfo() {
std::vector<std::string> info_list;
if (auto room = room_network->GetRoomMember().lock()) {
if (auto room = Network::GetRoomMember().lock()) {
auto members = room->GetMemberInformation();
if (!members.empty()) {
// name and max players
@ -289,8 +286,8 @@ std::vector<std::string> NetPlayRoomInfo() {
return info_list;
}
bool NetPlayIsJoined() {
auto member = room_network->GetRoomMember().lock();
bool AndroidMultiplayer::NetPlayIsJoined() {
auto member = Network::GetRoomMember().lock();
if (!member) {
return false;
}
@ -299,17 +296,17 @@ bool NetPlayIsJoined() {
member->GetState() == Network::RoomMember::State::Moderator);
}
bool NetPlayIsHostedRoom() {
if (auto room = room_network->GetRoom().lock()) {
bool AndroidMultiplayer::NetPlayIsHostedRoom() {
if (auto room = Network::GetRoom().lock()) {
return room->GetState() == Network::Room::State::Open;
}
return false;
}
void NetPlayLeaveRoom() {
if (auto room = room_network->GetRoom().lock()) {
void AndroidMultiplayer::NetPlayLeaveRoom() {
if (auto room = Network::GetRoom().lock()) {
// if you are in a room, leave it
if (auto member = room_network->GetRoomMember().lock()) {
if (auto member = Network::GetRoomMember().lock()) {
member->Leave();
}
@ -322,21 +319,52 @@ void NetPlayLeaveRoom() {
}
}
void NetworkShutdown() {
room_network->Shutdown();
void AndroidMultiplayer::NetworkShutdown() {
Network::Shutdown();
}
bool NetPlayIsModerator() {
auto member = room_network->GetRoomMember().lock();
bool AndroidMultiplayer::NetPlayIsModerator() {
auto member = Network::GetRoomMember().lock();
if (!member) {
return false;
}
return member->GetState() == Network::RoomMember::State::Moderator;
}
std::vector<std::string> NetPlayGetBanList() {
std::vector<std::string> AndroidMultiplayer::NetPlayGetPublicRooms() {
std::vector<std::string> room_list;
if (auto session = announce_multiplayer_session.lock()) {
auto rooms = session->GetRoomList();
for (const auto &room: rooms) {
room_list.push_back(room.information.name + "|" +
(room.has_password ? "1" : "0") + "|" +
std::to_string(room.information.member_slots) + "|" +
room.ip + "|" +
std::to_string(room.information.port) + "|" +
room.information.description + "|" +
room.information.host_username + "|" +
std::to_string(room.information.preferred_game.id) + "|" +
room.information.preferred_game.name + "|" +
room.information.preferred_game.version);
for (const auto &member: room.members) {
room_list.push_back("MEMBER|" + room.information.name + "|" +
member.username + "|" +
member.nickname + "|" +
std::to_string(member.game.id) + "|" +
member.game.name);
}
}
}
return room_list;
}
std::vector<std::string> AndroidMultiplayer::NetPlayGetBanList() {
std::vector<std::string> ban_list;
if (auto room = room_network->GetRoom().lock()) {
if (auto room = Network::GetRoom().lock()) {
auto [username_bans, ip_bans] = room->GetBanList();
// Add username bans

View file

@ -1,8 +1,4 @@
// Copyright 2024 Mandarine Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@ -12,6 +8,12 @@
#include <common/common_types.h>
#include <network/network.h>
#include <network/announce_multiplayer_session.h>
namespace Core {
class System;
class AnnounceMultiplayerSession;
}
enum class NetPlayStatus : s32 {
NO_ERROR,
@ -48,21 +50,53 @@ enum class NetPlayStatus : s32 {
CHAT_MESSAGE,
};
bool NetworkInit(Network::RoomNetwork* room_network);
NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
const std::string& username, const std::string& password,
const std::string& room_name, int max_players);
NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port,
const std::string& username, const std::string& password);
std::vector<std::string> NetPlayRoomInfo();
bool NetPlayIsJoined();
bool NetPlayIsHostedRoom();
bool NetPlayIsModerator();
void NetPlaySendMessage(const std::string& msg);
void NetPlayKickUser(const std::string& username);
void NetPlayBanUser(const std::string& username);
void NetPlayLeaveRoom();
std::string NetPlayGetConsoleId();
void NetworkShutdown();
std::vector<std::string> NetPlayGetBanList();
void NetPlayUnbanUser(const std::string& username);
class AndroidMultiplayer {
public:
explicit AndroidMultiplayer(Core::System& system,
std::shared_ptr<Core::AnnounceMultiplayerSession> session);
~AndroidMultiplayer();
bool NetworkInit();
void AddNetPlayMessage(int status, const std::string& msg);
void AddNetPlayMessage(jint type, jstring msg);
void ClearChat();
NetPlayStatus NetPlayCreateRoom(const std::string &ipaddress, int port,
const std::string &username, const std::string &preferredGameName,
const u64 &preferredGameId, const std::string &password,
const std::string &room_name, int max_players);
NetPlayStatus NetPlayJoinRoom(const std::string &ipaddress, int port,
const std::string &username, const std::string &password);
std::vector<std::string> NetPlayRoomInfo();
bool NetPlayIsJoined();
bool NetPlayIsHostedRoom();
bool NetPlayIsModerator();
void NetPlaySendMessage(const std::string &msg);
void NetPlayKickUser(const std::string &username);
void NetPlayBanUser(const std::string &username);
void NetPlayLeaveRoom();
static void NetworkShutdown();
std::vector<std::string> NetPlayGetBanList();
void NetPlayUnbanUser(const std::string &username);
std::vector<std::string> NetPlayGetPublicRooms();
private:
Core::System& system;
static std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) ;
std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
};

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <memory>
#include <utility>
@ -508,9 +511,9 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco
LOG_INFO(Service, "New socket fd={}", fd);
auto room_member = room_network.GetRoomMember().lock();
auto room_member = Network::GetRoomMember().lock();
if (room_member && room_member->IsConnected()) {
descriptor.socket = std::make_shared<Network::ProxySocket>(room_network);
descriptor.socket = std::make_shared<Network::ProxySocket>();
} else {
descriptor.socket = std::make_shared<Network::Socket>();
}
@ -970,7 +973,7 @@ void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) {
}
BSD::BSD(Core::System& system_, const char* name)
: ServiceFramework{system_, name}, room_network{system_.GetRoomNetwork()} {
: ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},
@ -1012,7 +1015,7 @@ BSD::BSD(Core::System& system_, const char* name)
RegisterHandlers(functions);
if (auto room_member = room_network.GetRoomMember().lock()) {
if (auto room_member = Network::GetRoomMember().lock()) {
proxy_packet_received = room_member->BindOnProxyPacketReceived(
[this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
} else {
@ -1021,7 +1024,7 @@ BSD::BSD(Core::System& system_, const char* name)
}
BSD::~BSD() {
if (auto room_member = room_network.GetRoomMember().lock()) {
if (auto room_member = Network::GetRoomMember().lock()) {
room_member->Unbind(proxy_packet_received);
}
}

View file

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

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
#include <algorithm>

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <chrono>
#include <thread>
@ -18,7 +21,7 @@
namespace Network {
ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {}
ProxySocket::ProxySocket() noexcept {}
ProxySocket::~ProxySocket() {
if (fd == INVALID_SOCKET) {
@ -187,7 +190,7 @@ std::pair<s32, Errno> ProxySocket::Send(std::span<const u8> message, int flags)
}
void ProxySocket::SendPacket(ProxyPacket& packet) {
if (auto room_member = room_network.GetRoomMember().lock()) {
if (auto room_member = Network::GetRoomMember().lock()) {
if (room_member->IsConnected()) {
packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(),
packet.data.size());
@ -205,7 +208,7 @@ std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message
return {static_cast<s32>(message.size()), Errno::SUCCESS};
}
if (auto room_member = room_network.GetRoomMember().lock()) {
if (auto room_member = Network::GetRoomMember().lock()) {
if (!room_member->IsConnected()) {
return {static_cast<s32>(message.size()), Errno::SUCCESS};
}
@ -222,7 +225,7 @@ std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message
// If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address,
// replace it with a "fake" routing address
if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) {
if (auto room_member = room_network.GetRoomMember().lock()) {
if (auto room_member = Network::GetRoomMember().lock()) {
packet.local_endpoint.ip = room_member->GetFakeIpAddress();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

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
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
#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
#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
#include "yuzu/configuration/configure_ui.h"

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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