mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-07-20 03:35:46 +00:00
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:
parent
7e13da47af
commit
76fa525592
99 changed files with 1470 additions and 498 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,6 +1,9 @@
|
||||||
# SPDX-FileCopyrightText: 2013 Citra Emulator Project
|
# SPDX-FileCopyrightText: 2013 Citra Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
# SPDX-FileCopyrightText: Eden Emulator Project
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# Build directory
|
# Build directory
|
||||||
[Bb]uild*/
|
[Bb]uild*/
|
||||||
doc-build/
|
doc-build/
|
||||||
|
@ -36,3 +39,7 @@ CMakeSettings.json
|
||||||
# Windows global filetypes
|
# Windows global filetypes
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# Artifacts
|
||||||
|
eden-windows-msvc
|
||||||
|
artifacts
|
||||||
|
*.AppImage*
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
# SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.22)
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
# SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# This function downloads a binary library package from our external repo.
|
# This function downloads a binary library package from our external repo.
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
@ -162,7 +165,7 @@ android {
|
||||||
arguments(
|
arguments(
|
||||||
"-DENABLE_QT=0", // Don't use QT
|
"-DENABLE_QT=0", // Don't use QT
|
||||||
"-DENABLE_SDL2=0", // Don't use SDL
|
"-DENABLE_SDL2=0", // Don't use SDL
|
||||||
"-DENABLE_WEB_SERVICE=0", // Don't use telemetry
|
"-DENABLE_WEB_SERVICE=1", // Enable web service
|
||||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||||
"-DYUZU_USE_BUNDLED_VCPKG=ON",
|
"-DYUZU_USE_BUNDLED_VCPKG=ON",
|
||||||
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
|
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
|
||||||
|
@ -176,9 +179,9 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.create<Delete>("ktlintReset") {
|
tasks.register<Delete>("ktlintReset", fun Delete.() {
|
||||||
delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
|
delete(File(layout.buildDirectory.toString() + File.separator + "intermediates/ktLint"))
|
||||||
}
|
})
|
||||||
|
|
||||||
val showFormatHelp = {
|
val showFormatHelp = {
|
||||||
logger.lifecycle(
|
logger.lifecycle(
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
-->
|
-->
|
||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: Eden Emulator Project
|
SPDX-FileCopyrightText: Eden Emulator Project
|
||||||
|
@ -15,6 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
|
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
@ -270,8 +270,7 @@ object NativeLibrary {
|
||||||
NetPlayManager.clearChat()
|
NetPlayManager.clearChat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
external fun initMultiplayer()
|
||||||
external fun netPlayInit()
|
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,12 +1,9 @@
|
||||||
// Copyright 2024 Mandarine Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.dialogs
|
package org.yuzu.yuzu_emu.dialogs
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
@ -16,6 +13,7 @@ import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -32,10 +30,13 @@ import org.yuzu.yuzu_emu.databinding.ItemButtonNetplayBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ItemTextNetplayBinding
|
import org.yuzu.yuzu_emu.databinding.ItemTextNetplayBinding
|
||||||
import org.yuzu.yuzu_emu.utils.CompatUtils
|
import org.yuzu.yuzu_emu.utils.CompatUtils
|
||||||
import org.yuzu.yuzu_emu.network.NetPlayManager
|
import org.yuzu.yuzu_emu.network.NetPlayManager
|
||||||
|
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||||
|
|
||||||
class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
||||||
private lateinit var adapter: NetPlayAdapter
|
private lateinit var adapter: NetPlayAdapter
|
||||||
|
|
||||||
|
private val gameNameList: MutableList<Array<String>> = mutableListOf()
|
||||||
|
private val gameIdList: MutableList<Array<Long>> = mutableListOf()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -71,6 +72,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
||||||
else -> {
|
else -> {
|
||||||
DialogMultiplayerConnectBinding.inflate(layoutInflater).apply {
|
DialogMultiplayerConnectBinding.inflate(layoutInflater).apply {
|
||||||
setContentView(root)
|
setContentView(root)
|
||||||
|
for (game in GameHelper.cachedGameList) {
|
||||||
|
val gameName = game.title
|
||||||
|
if (gameNameList.none { it[0] == gameName }) {
|
||||||
|
gameNameList.add(arrayOf(gameName))
|
||||||
|
}
|
||||||
|
|
||||||
|
val gameId = game.programId.toLong()
|
||||||
|
if (gameIdList.none { it[0] == gameId }) {
|
||||||
|
gameIdList.add(arrayOf(gameId))
|
||||||
|
}
|
||||||
|
}
|
||||||
btnCreate.setOnClickListener {
|
btnCreate.setOnClickListener {
|
||||||
showNetPlayInputDialog(true)
|
showNetPlayInputDialog(true)
|
||||||
dismiss()
|
dismiss()
|
||||||
|
@ -79,6 +91,10 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
||||||
showNetPlayInputDialog(false)
|
showNetPlayInputDialog(false)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
btnLobbyBrowser.setOnClickListener {
|
||||||
|
LobbyBrowser(context).show()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,10 +224,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
||||||
override fun getItemCount() = netPlayItems.size
|
override fun getItemCount() = netPlayItems.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun refreshAdapterItems() {
|
fun refreshAdapterItems() {
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
NetPlayManager.setOnAdapterRefreshListener() { type, msg ->
|
NetPlayManager.setOnAdapterRefreshListener() { _, _ ->
|
||||||
handler.post {
|
handler.post {
|
||||||
adapter.netPlayItems.clear()
|
adapter.netPlayItems.clear()
|
||||||
adapter.loadMultiplayerMenu()
|
adapter.loadMultiplayerMenu()
|
||||||
|
@ -244,6 +261,17 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
||||||
binding.ipPort.setText(NetPlayManager.getRoomPort(activity))
|
binding.ipPort.setText(NetPlayManager.getRoomPort(activity))
|
||||||
binding.username.setText(NetPlayManager.getUsername(activity))
|
binding.username.setText(NetPlayManager.getUsername(activity))
|
||||||
|
|
||||||
|
binding.dropdownPreferredGameName.apply {
|
||||||
|
setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
activity,
|
||||||
|
R.layout.dropdown_item,
|
||||||
|
gameNameList.map { it[0] }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.preferredGameName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
|
||||||
binding.roomName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
|
binding.roomName.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
|
||||||
binding.maxPlayersContainer.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
|
binding.maxPlayersContainer.visibility = if (isCreateRoom) View.VISIBLE else View.GONE
|
||||||
binding.maxPlayersLabel.text = context.getString(R.string.multiplayer_max_players_value, binding.maxPlayers.value.toInt())
|
binding.maxPlayersLabel.text = context.getString(R.string.multiplayer_max_players_value, binding.maxPlayers.value.toInt())
|
||||||
|
@ -259,6 +287,8 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
||||||
val ipAddress = binding.ipAddress.text.toString()
|
val ipAddress = binding.ipAddress.text.toString()
|
||||||
val username = binding.username.text.toString()
|
val username = binding.username.text.toString()
|
||||||
val portStr = binding.ipPort.text.toString()
|
val portStr = binding.ipPort.text.toString()
|
||||||
|
val preferredGameName = binding.dropdownPreferredGameName.text.toString()
|
||||||
|
val preferredGameId = gameIdList[gameNameList.indexOfFirst { it[0] == preferredGameName }][0]
|
||||||
val password = binding.password.text.toString()
|
val password = binding.password.text.toString()
|
||||||
val port = portStr.toIntOrNull() ?: run {
|
val port = portStr.toIntOrNull() ?: run {
|
||||||
Toast.makeText(activity, R.string.multiplayer_port_invalid, Toast.LENGTH_LONG).show()
|
Toast.makeText(activity, R.string.multiplayer_port_invalid, Toast.LENGTH_LONG).show()
|
||||||
|
@ -276,6 +306,13 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCreateRoom && preferredGameName.isEmpty()) {
|
||||||
|
Toast.makeText(activity, R.string.multiplayer_preferred_game_name_invalid, Toast.LENGTH_LONG).show();
|
||||||
|
binding.btnConfirm.isEnabled = false
|
||||||
|
binding.btnConfirm.text = activity.getString(R.string.original_button_text)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
if (ipAddress.length < 7 || username.length < 5) {
|
if (ipAddress.length < 7 || username.length < 5) {
|
||||||
Toast.makeText(activity, R.string.multiplayer_input_invalid, Toast.LENGTH_LONG).show()
|
Toast.makeText(activity, R.string.multiplayer_input_invalid, Toast.LENGTH_LONG).show()
|
||||||
binding.btnConfirm.isEnabled = true
|
binding.btnConfirm.isEnabled = true
|
||||||
|
@ -283,7 +320,7 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
|
||||||
} else {
|
} else {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
val result = if (isCreateRoom) {
|
val result = if (isCreateRoom) {
|
||||||
NetPlayManager.netPlayCreateRoom(ipAddress, port, username, password, roomName, maxPlayers)
|
NetPlayManager.netPlayCreateRoom(ipAddress, port, username, preferredGameName, preferredGameId, password, roomName, maxPlayers)
|
||||||
} else {
|
} else {
|
||||||
NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password)
|
NetPlayManager.netPlayJoinRoom(ipAddress, port, username, password)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
enum class StringSetting(override val key: String) : AbstractStringSetting {
|
enum class StringSetting(override val key: String) : AbstractStringSetting {
|
||||||
DRIVER_PATH("driver_path"),
|
DRIVER_PATH("driver_path"),
|
||||||
DEVICE_NAME("device_name");
|
DEVICE_NAME("device_name"),
|
||||||
|
WEB_TOKEN("yuzu_token"),
|
||||||
|
;
|
||||||
|
|
||||||
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)
|
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)
|
||||||
|
|
||||||
|
|
|
@ -274,6 +274,13 @@ abstract class SettingsItem(
|
||||||
descriptionId = R.string.use_custom_rtc_description
|
descriptionId = R.string.use_custom_rtc_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
put(
|
||||||
|
StringInputSetting(
|
||||||
|
StringSetting.WEB_TOKEN,
|
||||||
|
titleId = R.string.web_token,
|
||||||
|
descriptionId = R.string.web_token_description
|
||||||
|
)
|
||||||
|
)
|
||||||
put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))
|
put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
@ -200,6 +200,10 @@ class SettingsFragmentPresenter(
|
||||||
add(IntSetting.LANGUAGE_INDEX.key)
|
add(IntSetting.LANGUAGE_INDEX.key)
|
||||||
add(BooleanSetting.USE_CUSTOM_RTC.key)
|
add(BooleanSetting.USE_CUSTOM_RTC.key)
|
||||||
add(LongSetting.CUSTOM_RTC.key)
|
add(LongSetting.CUSTOM_RTC.key)
|
||||||
|
|
||||||
|
// TODO(alekpop): Add functionality
|
||||||
|
// add(HeaderSetting(R.string.network))
|
||||||
|
// add(StringSetting.WEB_TOKEN.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
@ -128,7 +124,7 @@ class HomeSettingsFragment : Fragment() {
|
||||||
R.string.multiplayer_description,
|
R.string.multiplayer_description,
|
||||||
R.drawable.ic_two_users,
|
R.drawable.ic_two_users,
|
||||||
{
|
{
|
||||||
val action = mainActivity.displayMultiplayerDialog()
|
mainActivity.displayMultiplayerDialog()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.model
|
package org.yuzu.yuzu_emu.model
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.model
|
package org.yuzu.yuzu_emu.model
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
// Copyright 2024 Mandarine Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.network
|
package org.yuzu.yuzu_emu.network
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
|
@ -19,10 +17,27 @@ import androidx.preference.PreferenceManager
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.dialogs.ChatMessage
|
import org.yuzu.yuzu_emu.dialogs.ChatMessage
|
||||||
|
import java.net.Inet4Address
|
||||||
|
|
||||||
object NetPlayManager {
|
object NetPlayManager {
|
||||||
external fun netPlayCreateRoom(ipAddress: String, port: Int, username: String, password: String, roomName: String, maxPlayers: Int): Int
|
external fun netPlayCreateRoom(
|
||||||
external fun netPlayJoinRoom(ipAddress: String, port: Int, username: String, password: String): Int
|
ipAddress: String,
|
||||||
|
port: Int,
|
||||||
|
username: String,
|
||||||
|
preferredGameName: String,
|
||||||
|
preferredGameId: Long,
|
||||||
|
password: String,
|
||||||
|
roomName: String,
|
||||||
|
maxPlayers: Int
|
||||||
|
): Int
|
||||||
|
|
||||||
|
external fun netPlayJoinRoom(
|
||||||
|
ipAddress: String,
|
||||||
|
port: Int,
|
||||||
|
username: String,
|
||||||
|
password: String
|
||||||
|
): Int
|
||||||
|
|
||||||
external fun netPlayRoomInfo(): Array<String>
|
external fun netPlayRoomInfo(): Array<String>
|
||||||
external fun netPlayIsJoined(): Boolean
|
external fun netPlayIsJoined(): Boolean
|
||||||
external fun netPlayIsHostedRoom(): Boolean
|
external fun netPlayIsHostedRoom(): Boolean
|
||||||
|
@ -33,6 +48,28 @@ object NetPlayManager {
|
||||||
external fun netPlayGetBanList(): Array<String>
|
external fun netPlayGetBanList(): Array<String>
|
||||||
external fun netPlayBanUser(username: String)
|
external fun netPlayBanUser(username: String)
|
||||||
external fun netPlayUnbanUser(username: String)
|
external fun netPlayUnbanUser(username: String)
|
||||||
|
external fun netPlayGetPublicRooms(): Array<String>
|
||||||
|
|
||||||
|
data class RoomInfo(
|
||||||
|
val name: String,
|
||||||
|
val hasPassword: Boolean,
|
||||||
|
val maxPlayers: Int,
|
||||||
|
val ip: String,
|
||||||
|
val port: Int,
|
||||||
|
val description: String,
|
||||||
|
val owner: String,
|
||||||
|
val preferredGameId: Long,
|
||||||
|
val preferredGameName: String,
|
||||||
|
val members: MutableList<RoomMember> = mutableListOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RoomMember(
|
||||||
|
val username: String,
|
||||||
|
val nickname: String,
|
||||||
|
val gameId: Long,
|
||||||
|
val gameName: String
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
private var messageListener: ((Int, String) -> Unit)? = null
|
private var messageListener: ((Int, String) -> Unit)? = null
|
||||||
private var adapterRefreshListener: ((Int, String) -> Unit)? = null
|
private var adapterRefreshListener: ((Int, String) -> Unit)? = null
|
||||||
|
@ -41,11 +78,57 @@ object NetPlayManager {
|
||||||
messageListener = listener
|
messageListener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPublicRooms(): List<RoomInfo> {
|
||||||
|
val roomData = netPlayGetPublicRooms()
|
||||||
|
val rooms = mutableMapOf<String, RoomInfo>()
|
||||||
|
|
||||||
|
for (data in roomData) {
|
||||||
|
val parts = data.split("|")
|
||||||
|
|
||||||
|
if (parts[0] == "MEMBER" && parts.size >= 6) {
|
||||||
|
val roomName = parts[1]
|
||||||
|
val member = RoomMember(
|
||||||
|
username = parts[2],
|
||||||
|
nickname = parts[3],
|
||||||
|
gameId = parts[4].toLongOrNull() ?: 0L,
|
||||||
|
gameName = parts[5]
|
||||||
|
)
|
||||||
|
rooms[roomName]?.members?.add(member)
|
||||||
|
} else if (parts.size >= 9) {
|
||||||
|
val roomInfo = RoomInfo(
|
||||||
|
name = parts[0],
|
||||||
|
hasPassword = parts[1] == "1",
|
||||||
|
maxPlayers = parts[2].toIntOrNull() ?: 0,
|
||||||
|
ip = parts[3],
|
||||||
|
port = parts[4].toIntOrNull() ?: 0,
|
||||||
|
description = parts[5],
|
||||||
|
owner = parts[6],
|
||||||
|
preferredGameId = parts[7].toLongOrNull() ?: 0L,
|
||||||
|
preferredGameName = parts[8]
|
||||||
|
)
|
||||||
|
rooms[roomInfo.name] = roomInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rooms.values.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshRoomListAsync(callback: (List<RoomInfo>) -> Unit) {
|
||||||
|
Thread {
|
||||||
|
val rooms = getPublicRooms()
|
||||||
|
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
callback(rooms)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
fun setOnAdapterRefreshListener(listener: (Int, String) -> Unit) {
|
fun setOnAdapterRefreshListener(listener: (Int, String) -> Unit) {
|
||||||
adapterRefreshListener = listener
|
adapterRefreshListener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUsername(activity: Context): String { val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
fun getUsername(activity: Context): String {
|
||||||
|
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
val name = "Eden${(Math.random() * 100).toInt()}"
|
val name = "Eden${(Math.random() * 100).toInt()}"
|
||||||
return prefs.getString("NetPlayUsername", name) ?: name
|
return prefs.getString("NetPlayUsername", name) ?: name
|
||||||
}
|
}
|
||||||
|
@ -103,22 +186,27 @@ object NetPlayManager {
|
||||||
if (parts.size == 2) {
|
if (parts.size == 2) {
|
||||||
val nickname = parts[0].trim()
|
val nickname = parts[0].trim()
|
||||||
val chatMessage = parts[1].trim()
|
val chatMessage = parts[1].trim()
|
||||||
addChatMessage(ChatMessage(
|
addChatMessage(
|
||||||
|
ChatMessage(
|
||||||
nickname = nickname,
|
nickname = nickname,
|
||||||
username = "",
|
username = "",
|
||||||
message = chatMessage
|
message = chatMessage
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NetPlayStatus.MEMBER_JOIN,
|
NetPlayStatus.MEMBER_JOIN,
|
||||||
NetPlayStatus.MEMBER_LEAVE,
|
NetPlayStatus.MEMBER_LEAVE,
|
||||||
NetPlayStatus.MEMBER_KICKED,
|
NetPlayStatus.MEMBER_KICKED,
|
||||||
NetPlayStatus.MEMBER_BANNED -> {
|
NetPlayStatus.MEMBER_BANNED -> {
|
||||||
addChatMessage(ChatMessage(
|
addChatMessage(
|
||||||
|
ChatMessage(
|
||||||
nickname = "System",
|
nickname = "System",
|
||||||
username = "",
|
username = "",
|
||||||
message = message
|
message = message
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,35 +247,60 @@ object NetPlayManager {
|
||||||
NetPlayStatus.ROOM_MODERATOR -> context.getString(R.string.multiplayer_room_moderator)
|
NetPlayStatus.ROOM_MODERATOR -> context.getString(R.string.multiplayer_room_moderator)
|
||||||
NetPlayStatus.MEMBER_JOIN -> context.getString(R.string.multiplayer_member_join, msg)
|
NetPlayStatus.MEMBER_JOIN -> context.getString(R.string.multiplayer_member_join, msg)
|
||||||
NetPlayStatus.MEMBER_LEAVE -> context.getString(R.string.multiplayer_member_leave, msg)
|
NetPlayStatus.MEMBER_LEAVE -> context.getString(R.string.multiplayer_member_leave, msg)
|
||||||
NetPlayStatus.MEMBER_KICKED -> context.getString(R.string.multiplayer_member_kicked, msg)
|
NetPlayStatus.MEMBER_KICKED -> context.getString(
|
||||||
NetPlayStatus.MEMBER_BANNED -> context.getString(R.string.multiplayer_member_banned, msg)
|
R.string.multiplayer_member_kicked,
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
|
||||||
|
NetPlayStatus.MEMBER_BANNED -> context.getString(
|
||||||
|
R.string.multiplayer_member_banned,
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
|
||||||
NetPlayStatus.ADDRESS_UNBANNED -> context.getString(R.string.multiplayer_address_unbanned)
|
NetPlayStatus.ADDRESS_UNBANNED -> context.getString(R.string.multiplayer_address_unbanned)
|
||||||
NetPlayStatus.CHAT_MESSAGE -> msg
|
NetPlayStatus.CHAT_MESSAGE -> msg
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isConnectedToWifi(activity: Activity): Boolean {
|
||||||
|
val connectivityManager = activity.getSystemService(ConnectivityManager::class.java)
|
||||||
|
val network = connectivityManager.activeNetwork
|
||||||
|
val capabilities = connectivityManager.getNetworkCapabilities(network)
|
||||||
|
return capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||||
|
}
|
||||||
|
|
||||||
fun getIpAddressByWifi(activity: Activity): String {
|
fun getIpAddressByWifi(activity: Activity): String {
|
||||||
var ipAddress = 0
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
val wifiManager = activity.getSystemService(WifiManager::class.java)
|
// For Android 12 (API 31) and above
|
||||||
val wifiInfo = wifiManager.connectionInfo
|
val connectivityManager = activity.getSystemService(ConnectivityManager::class.java)
|
||||||
if (wifiInfo != null) {
|
val network = connectivityManager.activeNetwork
|
||||||
ipAddress = wifiInfo.ipAddress
|
val capabilities = connectivityManager.getNetworkCapabilities(network)
|
||||||
}
|
|
||||||
|
|
||||||
if (ipAddress == 0) {
|
if (capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) {
|
||||||
val dhcpInfo = wifiManager.dhcpInfo
|
val linkProperties = connectivityManager.getLinkProperties(network)
|
||||||
if (dhcpInfo != null) {
|
linkProperties?.linkAddresses?.firstOrNull { it.address is Inet4Address }?.let {
|
||||||
ipAddress = dhcpInfo.ipAddress
|
return it.address.hostAddress ?: "192.168.0.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (ipAddress == 0) {
|
// For Android 11 (API 30) and below
|
||||||
"192.168.0.1"
|
try {
|
||||||
} else {
|
val connectivityManager = activity.getSystemService(ConnectivityManager::class.java)
|
||||||
Formatter.formatIpAddress(ipAddress)
|
val network = connectivityManager.activeNetwork
|
||||||
|
if (network != null) {
|
||||||
|
val linkProperties = connectivityManager.getLinkProperties(network)
|
||||||
|
linkProperties?.linkAddresses?.firstOrNull { it.address is Inet4Address }?.let {
|
||||||
|
return it.address.hostAddress ?: "192.168.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
return "192.168.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
fun getBanList(): List<String> {
|
fun getBanList(): List<String> {
|
||||||
return netPlayGetBanList().toList()
|
return netPlayGetBanList().toList()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.ui
|
package org.yuzu.yuzu_emu.ui
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.ui.main
|
package org.yuzu.yuzu_emu.ui.main
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -72,9 +68,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
|
|
||||||
ThemeHelper.ThemeChangeListener(this)
|
ThemeHelper.ThemeChangeListener(this)
|
||||||
ThemeHelper.setTheme(this)
|
ThemeHelper.setTheme(this)
|
||||||
NativeLibrary.netPlayInit()
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
NativeLibrary.initMultiplayer()
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
// Copyright 2024 Mandarine Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
@ -13,11 +16,15 @@ import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.model.GameDir
|
import org.yuzu.yuzu_emu.model.GameDir
|
||||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
object GameHelper {
|
object GameHelper {
|
||||||
private const val KEY_OLD_GAME_PATH = "game_path"
|
private const val KEY_OLD_GAME_PATH = "game_path"
|
||||||
const val KEY_GAMES = "Games"
|
const val KEY_GAMES = "Games"
|
||||||
|
|
||||||
|
var cachedGameList = mutableListOf<Game>()
|
||||||
|
|
||||||
private lateinit var preferences: SharedPreferences
|
private lateinit var preferences: SharedPreferences
|
||||||
|
|
||||||
fun getGames(): List<Game> {
|
fun getGames(): List<Game> {
|
||||||
|
@ -29,7 +36,7 @@ object GameHelper {
|
||||||
val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: ""
|
val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: ""
|
||||||
if (oldGamesDir.isNotEmpty()) {
|
if (oldGamesDir.isNotEmpty()) {
|
||||||
gameDirs.add(GameDir(oldGamesDir, true))
|
gameDirs.add(GameDir(oldGamesDir, true))
|
||||||
preferences.edit().remove(KEY_OLD_GAME_PATH).apply()
|
preferences.edit() { remove(KEY_OLD_GAME_PATH) }
|
||||||
}
|
}
|
||||||
gameDirs.addAll(NativeConfig.getGameDirs())
|
gameDirs.addAll(NativeConfig.getGameDirs())
|
||||||
|
|
||||||
|
@ -44,7 +51,7 @@ object GameHelper {
|
||||||
|
|
||||||
val badDirs = mutableListOf<Int>()
|
val badDirs = mutableListOf<Int>()
|
||||||
gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
|
gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
|
||||||
val gameDirUri = Uri.parse(gameDir.uriString)
|
val gameDirUri = gameDir.uriString.toUri()
|
||||||
val isValid = FileUtil.isTreeUriValid(gameDirUri)
|
val isValid = FileUtil.isTreeUriValid(gameDirUri)
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
addGamesRecursive(
|
addGamesRecursive(
|
||||||
|
@ -72,11 +79,12 @@ object GameHelper {
|
||||||
games.forEach {
|
games.forEach {
|
||||||
serializedGames.add(Json.encodeToString(it))
|
serializedGames.add(Json.encodeToString(it))
|
||||||
}
|
}
|
||||||
preferences.edit()
|
preferences.edit() {
|
||||||
.remove(KEY_GAMES)
|
remove(KEY_GAMES)
|
||||||
.putStringSet(KEY_GAMES, serializedGames)
|
.putStringSet(KEY_GAMES, serializedGames)
|
||||||
.apply()
|
}
|
||||||
|
|
||||||
|
cachedGameList = games.toMutableList()
|
||||||
return games.toList()
|
return games.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,12 +68,17 @@
|
||||||
#include "video_core/vulkan_common/vulkan_instance.h"
|
#include "video_core/vulkan_common/vulkan_instance.h"
|
||||||
#include "video_core/vulkan_common/vulkan_surface.h"
|
#include "video_core/vulkan_common/vulkan_surface.h"
|
||||||
#include "video_core/shader_notify.h"
|
#include "video_core/shader_notify.h"
|
||||||
|
#include "network/announce_multiplayer_session.h"
|
||||||
|
|
||||||
#define jconst [[maybe_unused]] const auto
|
#define jconst [[maybe_unused]] const auto
|
||||||
#define jauto [[maybe_unused]] auto
|
#define jauto [[maybe_unused]] auto
|
||||||
|
|
||||||
static EmulationSession s_instance;
|
static EmulationSession s_instance;
|
||||||
|
|
||||||
|
//Abdroid Multiplayer which can be initialized with parameters
|
||||||
|
std::unique_ptr<AndroidMultiplayer> multiplayer{nullptr};
|
||||||
|
std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
|
||||||
|
|
||||||
EmulationSession::EmulationSession() {
|
EmulationSession::EmulationSession() {
|
||||||
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
|
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||||
}
|
}
|
||||||
|
@ -916,12 +921,33 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobje
|
||||||
return ContentManager::AreKeysPresent();
|
return ContentManager::AreKeysPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_org_yuzu_yuzu_1emu_NativeLibrary_initMultiplayer(
|
||||||
|
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
|
if (multiplayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>();
|
||||||
|
|
||||||
|
multiplayer = std::make_unique<AndroidMultiplayer>(s_instance.System(), announce_multiplayer_session);
|
||||||
|
multiplayer->NetworkInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jobjectArray JNICALL
|
||||||
|
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetPublicRooms(
|
||||||
|
JNIEnv *env, [[maybe_unused]] jobject obj) {
|
||||||
|
return Common::Android::ToJStringArray(env, multiplayer->NetPlayGetPublicRooms());
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayCreateRoom(
|
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayCreateRoom(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
|
JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
|
||||||
jstring username, jstring password, jstring room_name, jint max_players) {
|
jstring username, jstring preferredGameName, jlong preferredGameId, jstring password,
|
||||||
|
jstring room_name, jint max_players) {
|
||||||
return static_cast<jint>(
|
return static_cast<jint>(
|
||||||
NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port,
|
multiplayer->NetPlayCreateRoom(Common::Android::GetJString(env, ipaddress), port,
|
||||||
Common::Android::GetJString(env, username), Common::Android::GetJString(env, password),
|
Common::Android::GetJString(env, username), Common::Android::GetJString(env, preferredGameName),
|
||||||
|
preferredGameId,Common::Android::GetJString(env, password),
|
||||||
Common::Android::GetJString(env, room_name), max_players));
|
Common::Android::GetJString(env, room_name), max_players));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,70 +955,63 @@ JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayJoi
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
|
JNIEnv* env, [[maybe_unused]] jobject obj, jstring ipaddress, jint port,
|
||||||
jstring username, jstring password) {
|
jstring username, jstring password) {
|
||||||
return static_cast<jint>(
|
return static_cast<jint>(
|
||||||
NetPlayJoinRoom(Common::Android::GetJString(env, ipaddress), port,
|
multiplayer->NetPlayJoinRoom(Common::Android::GetJString(env, ipaddress), port,
|
||||||
Common::Android::GetJString(env, username), Common::Android::GetJString(env, password)));
|
Common::Android::GetJString(env, username), Common::Android::GetJString(env, password)));
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jobjectArray JNICALL
|
JNIEXPORT jobjectArray JNICALL
|
||||||
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayRoomInfo(
|
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayRoomInfo(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
return Common::Android::ToJStringArray(env, NetPlayRoomInfo());
|
return Common::Android::ToJStringArray(env, multiplayer->NetPlayRoomInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsJoined(
|
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsJoined(
|
||||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
return NetPlayIsJoined();
|
return multiplayer->NetPlayIsJoined();
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsHostedRoom(
|
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsHostedRoom(
|
||||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
return NetPlayIsHostedRoom();
|
return multiplayer->NetPlayIsHostedRoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlaySendMessage(
|
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlaySendMessage(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring msg) {
|
JNIEnv* env, [[maybe_unused]] jobject obj, jstring msg) {
|
||||||
NetPlaySendMessage(Common::Android::GetJString(env, msg));
|
multiplayer->NetPlaySendMessage(Common::Android::GetJString(env, msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayKickUser(
|
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayKickUser(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
|
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
|
||||||
NetPlayKickUser(Common::Android::GetJString(env, username));
|
multiplayer->NetPlayKickUser(Common::Android::GetJString(env, username));
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayLeaveRoom(
|
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayLeaveRoom(
|
||||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
NetPlayLeaveRoom();
|
multiplayer->NetPlayLeaveRoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsModerator(
|
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayIsModerator(
|
||||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
return NetPlayIsModerator();
|
return multiplayer->NetPlayIsModerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jobjectArray JNICALL
|
JNIEXPORT jobjectArray JNICALL
|
||||||
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetBanList(
|
Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayGetBanList(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
return Common::Android::ToJStringArray(env, NetPlayGetBanList());
|
return Common::Android::ToJStringArray(env, multiplayer->NetPlayGetBanList());
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayBanUser(
|
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayBanUser(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
|
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
|
||||||
NetPlayBanUser(Common::Android::GetJString(env, username));
|
multiplayer->NetPlayBanUser(Common::Android::GetJString(env, username));
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayUnbanUser(
|
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayUnbanUser(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
|
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
|
||||||
NetPlayUnbanUser(Common::Android::GetJString(env, username));
|
multiplayer->NetPlayUnbanUser(Common::Android::GetJString(env, username));
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_org_yuzu_yuzu_1emu_NativeLibrary_netPlayInit(
|
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
|
||||||
NetworkInit(&EmulationSession::GetInstance().System().GetRoomNetwork());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
9
src/android/app/src/main/res/drawable/ic_ip.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_ip.xml
Normal 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>
|
10
src/android/app/src/main/res/drawable/ic_joined.xml
Normal file
10
src/android/app/src/main/res/drawable/ic_joined.xml
Normal 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>
|
9
src/android/app/src/main/res/drawable/ic_multiplayer.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_multiplayer.xml
Normal 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>
|
220
src/android/app/src/main/res/layout/dialog_lobby_browser.xml
Normal file
220
src/android/app/src/main/res/layout/dialog_lobby_browser.xml
Normal 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>
|
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="140dp"
|
android:layout_width="140dp"
|
||||||
android:layout_height="140dp"
|
android:layout_height="114dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginBottom="24dp"
|
android:layout_marginBottom="24dp"
|
||||||
|
@ -38,19 +38,45 @@
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btn_lobby_browser"
|
||||||
|
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||||
|
android:layout_width="175dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/multiplayer_public_room"
|
||||||
|
app:cornerRadius="16dp"
|
||||||
|
app:icon="@drawable/ic_search" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginBottom="8dp">
|
android:layout_marginBottom="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn_join"
|
android:id="@+id/btn_join"
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/multiplayer_join_room"
|
android:text="@string/multiplayer_join_room"
|
||||||
app:icon="@drawable/ic_install"
|
app:cornerRadius="16dp"
|
||||||
app:cornerRadius="16dp" />
|
app:icon="@drawable/ic_install" />
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:layout_width="16dp"
|
android:layout_width="16dp"
|
||||||
|
@ -58,13 +84,13 @@
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn_create"
|
android:id="@+id/btn_create"
|
||||||
style="@style/Widget.Material3.Button"
|
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/multiplayer_create_room"
|
android:text="@string/multiplayer_create_room"
|
||||||
app:icon="@drawable/ic_add"
|
app:cornerRadius="16dp"
|
||||||
app:cornerRadius="16dp" />
|
app:icon="@drawable/ic_add" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,21 @@
|
||||||
android:inputType="text" />
|
android:inputType="text" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||||
|
android:id="@+id/preferred_game_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:hint="@string/multiplayer_preferred_game_name">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||||
|
android:id="@+id/dropdown_preferred_game_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="none" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -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>
|
9
src/android/app/src/main/res/layout/dropdown_item.xml
Normal file
9
src/android/app/src/main/res/layout/dropdown_item.xml
Normal 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" />
|
113
src/android/app/src/main/res/layout/item_lobby_room.xml
Normal file
113
src/android/app/src/main/res/layout/item_lobby_room.xml
Normal 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>
|
4
src/android/app/src/main/res/values-fr-rSN/strings.xml
Normal file
4
src/android/app/src/main/res/values-fr-rSN/strings.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Eden</string>
|
||||||
|
</resources>
|
|
@ -399,6 +399,11 @@
|
||||||
<string name="use_custom_rtc_description">Allows you to set a custom real-time clock separate from your current system time.</string>
|
<string name="use_custom_rtc_description">Allows you to set a custom real-time clock separate from your current system time.</string>
|
||||||
<string name="set_custom_rtc">Set custom RTC</string>
|
<string name="set_custom_rtc">Set custom RTC</string>
|
||||||
|
|
||||||
|
<!-- Network settings strings -->
|
||||||
|
<string name="web_token">Web Token</string>
|
||||||
|
<string name="web_token_description">Web token used for creating public lobbies. It is a 48-character string containing only lowercase a-z.</string>
|
||||||
|
<string name="network">Network</string>
|
||||||
|
|
||||||
<!-- Graphics settings strings -->
|
<!-- Graphics settings strings -->
|
||||||
<string name="frame_skipping">WIP: Frameskip</string>
|
<string name="frame_skipping">WIP: Frameskip</string>
|
||||||
<string name="frame_skipping_description">Toggle frame skipping to improve performance by reducing the number of rendered frames. This feature is still being worked on and will be enabled in future releases.</string>
|
<string name="frame_skipping_description">Toggle frame skipping to improve performance by reducing the number of rendered frames. This feature is still being worked on and will be enabled in future releases.</string>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "android_common.h"
|
#include "android_common.h"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
@ -94,10 +94,10 @@ static jmethodID s_yuzu_input_device_get_supports_vibration;
|
||||||
static jmethodID s_yuzu_input_device_vibrate;
|
static jmethodID s_yuzu_input_device_vibrate;
|
||||||
static jmethodID s_yuzu_input_device_get_axes;
|
static jmethodID s_yuzu_input_device_get_axes;
|
||||||
static jmethodID s_yuzu_input_device_has_keys;
|
static jmethodID s_yuzu_input_device_has_keys;
|
||||||
|
|
||||||
static jmethodID s_add_netplay_message;
|
static jmethodID s_add_netplay_message;
|
||||||
static jmethodID s_clear_chat;
|
static jmethodID s_clear_chat;
|
||||||
|
|
||||||
|
|
||||||
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
||||||
|
|
||||||
namespace Common::Android {
|
namespace Common::Android {
|
||||||
|
@ -405,7 +405,6 @@ jmethodID ClearChat() {
|
||||||
return s_clear_chat;
|
return s_clear_chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
@ -605,7 +604,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
|
||||||
// UnInitialize applets
|
// UnInitialize applets
|
||||||
SoftwareKeyboard::CleanupJNI(env);
|
SoftwareKeyboard::CleanupJNI(env);
|
||||||
|
|
||||||
NetworkShutdown();
|
AndroidMultiplayer::NetworkShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
// Copyright 2024 Mandarine Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "common/android/id_cache.h"
|
#include "common/android/id_cache.h"
|
||||||
|
@ -19,35 +15,38 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
namespace IDCache = Common::Android;
|
namespace IDCache = Common::Android;
|
||||||
Network::RoomNetwork* room_network;
|
|
||||||
|
|
||||||
void AddNetPlayMessage(jint type, jstring msg) {
|
AndroidMultiplayer::AndroidMultiplayer(Core::System& system_,
|
||||||
|
std::shared_ptr<Core::AnnounceMultiplayerSession> session)
|
||||||
|
: system{system_}, announce_multiplayer_session(session) {}
|
||||||
|
|
||||||
|
AndroidMultiplayer::~AndroidMultiplayer() = default;
|
||||||
|
|
||||||
|
void AndroidMultiplayer::AddNetPlayMessage(jint type, jstring msg) {
|
||||||
IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||||
IDCache::GetAddNetPlayMessage(), type, msg);
|
IDCache::GetAddNetPlayMessage(), type, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddNetPlayMessage(int type, const std::string& msg) {
|
void AndroidMultiplayer::AddNetPlayMessage(int type, const std::string& msg) {
|
||||||
JNIEnv* env = IDCache::GetEnvForThread();
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
AddNetPlayMessage(type, Common::Android::ToJString(env, msg));
|
AddNetPlayMessage(type, Common::Android::ToJString(env, msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearChat() {
|
void AndroidMultiplayer::ClearChat() {
|
||||||
IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
IDCache::GetEnvForThread()->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||||
IDCache::ClearChat());
|
IDCache::ClearChat());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AndroidMultiplayer::NetworkInit() {
|
||||||
bool NetworkInit(Network::RoomNetwork* room_network_) {
|
bool result = Network::Init();
|
||||||
room_network = room_network_;
|
|
||||||
bool result = room_network->Init();
|
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto member = room_network->GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
// register the network structs to use in slots and signals
|
// register the network structs to use in slots and signals
|
||||||
member->BindOnStateChanged([](const Network::RoomMember::State& state) {
|
member->BindOnStateChanged([this](const Network::RoomMember::State& state) {
|
||||||
if (state == Network::RoomMember::State::Joined ||
|
if (state == Network::RoomMember::State::Joined ||
|
||||||
state == Network::RoomMember::State::Moderator) {
|
state == Network::RoomMember::State::Moderator) {
|
||||||
NetPlayStatus status;
|
NetPlayStatus status;
|
||||||
|
@ -65,7 +64,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
|
||||||
AddNetPlayMessage(static_cast<int>(status), msg);
|
AddNetPlayMessage(static_cast<int>(status), msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
member->BindOnError([](const Network::RoomMember::Error& error) {
|
member->BindOnError([this](const Network::RoomMember::Error& error) {
|
||||||
NetPlayStatus status;
|
NetPlayStatus status;
|
||||||
std::string msg;
|
std::string msg;
|
||||||
switch (error) {
|
switch (error) {
|
||||||
|
@ -108,7 +107,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
|
||||||
}
|
}
|
||||||
AddNetPlayMessage(static_cast<int>(status), msg);
|
AddNetPlayMessage(static_cast<int>(status), msg);
|
||||||
});
|
});
|
||||||
member->BindOnStatusMessageReceived([](const Network::StatusMessageEntry& status_message) {
|
member->BindOnStatusMessageReceived([this](const Network::StatusMessageEntry& status_message) {
|
||||||
NetPlayStatus status = NetPlayStatus::NO_ERROR;
|
NetPlayStatus status = NetPlayStatus::NO_ERROR;
|
||||||
std::string msg(status_message.nickname);
|
std::string msg(status_message.nickname);
|
||||||
switch (status_message.type) {
|
switch (status_message.type) {
|
||||||
|
@ -130,7 +129,7 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
|
||||||
}
|
}
|
||||||
AddNetPlayMessage(static_cast<int>(status), msg);
|
AddNetPlayMessage(static_cast<int>(status), msg);
|
||||||
});
|
});
|
||||||
member->BindOnChatMessageReceived([](const Network::ChatEntry& chat) {
|
member->BindOnChatMessageReceived([this](const Network::ChatEntry& chat) {
|
||||||
NetPlayStatus status = NetPlayStatus::CHAT_MESSAGE;
|
NetPlayStatus status = NetPlayStatus::CHAT_MESSAGE;
|
||||||
std::string msg(chat.nickname);
|
std::string msg(chat.nickname);
|
||||||
msg += ": ";
|
msg += ": ";
|
||||||
|
@ -141,13 +140,11 @@ bool NetworkInit(Network::RoomNetwork* room_network_) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
|
NetPlayStatus AndroidMultiplayer::NetPlayCreateRoom(const std::string& ipaddress, int port,
|
||||||
const std::string& username, const std::string& password,
|
const std::string& username, const std::string &preferredGameName,
|
||||||
|
const u64 &preferredGameId, const std::string& password,
|
||||||
const std::string& room_name, int max_players) {
|
const std::string& room_name, int max_players) {
|
||||||
|
auto member = Network::GetRoomMember().lock();
|
||||||
__android_log_print(ANDROID_LOG_INFO, "NetPlay", "NetPlayCreateRoom called with ipaddress: %s, port: %d, username: %s, room_name: %s, max_players: %d", ipaddress.c_str(), port, username.c_str(), room_name.c_str(), max_players);
|
|
||||||
|
|
||||||
auto member = room_network->GetRoomMember().lock();
|
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return NetPlayStatus::NETWORK_ERROR;
|
return NetPlayStatus::NETWORK_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -156,7 +153,7 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
|
||||||
return NetPlayStatus::ALREADY_IN_ROOM;
|
return NetPlayStatus::ALREADY_IN_ROOM;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto room = room_network->GetRoom().lock();
|
auto room = Network::GetRoom().lock();
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return NetPlayStatus::NETWORK_ERROR;
|
return NetPlayStatus::NETWORK_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -167,14 +164,14 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
|
||||||
|
|
||||||
// Placeholder game info
|
// Placeholder game info
|
||||||
const AnnounceMultiplayerRoom::GameInfo game{
|
const AnnounceMultiplayerRoom::GameInfo game{
|
||||||
.name = "Default Game",
|
.name = preferredGameName,
|
||||||
.id = 0, // Default program ID
|
.id = preferredGameId,
|
||||||
};
|
};
|
||||||
|
|
||||||
port = (port == 0) ? Network::DefaultRoomPort : static_cast<u16>(port);
|
port = (port == 0) ? Network::DefaultRoomPort : static_cast<u16>(port);
|
||||||
|
|
||||||
if (!room->Create(room_name, "", ipaddress, static_cast<u16>(port), password,
|
if (!room->Create(room_name, "", ipaddress, static_cast<u16>(port), password,
|
||||||
static_cast<u32>(std::min(max_players, 16)), username, game, nullptr, {})) {
|
static_cast<u32>(std::min(max_players, 16)), username, game, std::make_unique<Network::VerifyUser::NullBackend>(), {})) {
|
||||||
return NetPlayStatus::CREATE_ROOM_ERROR;
|
return NetPlayStatus::CREATE_ROOM_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,9 +194,9 @@ NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
|
||||||
return NetPlayStatus::CREATE_ROOM_ERROR;
|
return NetPlayStatus::CREATE_ROOM_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port,
|
NetPlayStatus AndroidMultiplayer::NetPlayJoinRoom(const std::string& ipaddress, int port,
|
||||||
const std::string& username, const std::string& password) {
|
const std::string& username, const std::string& password) {
|
||||||
auto member = room_network->GetRoomMember().lock();
|
auto member = Network::GetRoomMember().lock();
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return NetPlayStatus::NETWORK_ERROR;
|
return NetPlayStatus::NETWORK_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -229,8 +226,8 @@ NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port,
|
||||||
return NetPlayStatus::WRONG_PASSWORD;
|
return NetPlayStatus::WRONG_PASSWORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlaySendMessage(const std::string& msg) {
|
void AndroidMultiplayer::NetPlaySendMessage(const std::string& msg) {
|
||||||
if (auto room = room_network->GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
if (room->GetState() != Network::RoomMember::State::Joined &&
|
if (room->GetState() != Network::RoomMember::State::Joined &&
|
||||||
room->GetState() != Network::RoomMember::State::Moderator) {
|
room->GetState() != Network::RoomMember::State::Moderator) {
|
||||||
|
|
||||||
|
@ -240,8 +237,8 @@ void NetPlaySendMessage(const std::string& msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlayKickUser(const std::string& username) {
|
void AndroidMultiplayer::NetPlayKickUser(const std::string& username) {
|
||||||
if (auto room = room_network->GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
auto members = room->GetMemberInformation();
|
auto members = room->GetMemberInformation();
|
||||||
auto it = std::find_if(members.begin(), members.end(),
|
auto it = std::find_if(members.begin(), members.end(),
|
||||||
[&username](const Network::RoomMember::MemberInformation& member) {
|
[&username](const Network::RoomMember::MemberInformation& member) {
|
||||||
|
@ -253,8 +250,8 @@ void NetPlayKickUser(const std::string& username) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlayBanUser(const std::string& username) {
|
void AndroidMultiplayer::NetPlayBanUser(const std::string& username) {
|
||||||
if (auto room = room_network->GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
auto members = room->GetMemberInformation();
|
auto members = room->GetMemberInformation();
|
||||||
auto it = std::find_if(members.begin(), members.end(),
|
auto it = std::find_if(members.begin(), members.end(),
|
||||||
[&username](const Network::RoomMember::MemberInformation& member) {
|
[&username](const Network::RoomMember::MemberInformation& member) {
|
||||||
|
@ -266,15 +263,15 @@ void NetPlayBanUser(const std::string& username) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlayUnbanUser(const std::string& username) {
|
void AndroidMultiplayer::NetPlayUnbanUser(const std::string& username) {
|
||||||
if (auto room = room_network->GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username);
|
room->SendModerationRequest(Network::RoomMessageTypes::IdModUnban, username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> NetPlayRoomInfo() {
|
std::vector<std::string> AndroidMultiplayer::NetPlayRoomInfo() {
|
||||||
std::vector<std::string> info_list;
|
std::vector<std::string> info_list;
|
||||||
if (auto room = room_network->GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
auto members = room->GetMemberInformation();
|
auto members = room->GetMemberInformation();
|
||||||
if (!members.empty()) {
|
if (!members.empty()) {
|
||||||
// name and max players
|
// name and max players
|
||||||
|
@ -289,8 +286,8 @@ std::vector<std::string> NetPlayRoomInfo() {
|
||||||
return info_list;
|
return info_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetPlayIsJoined() {
|
bool AndroidMultiplayer::NetPlayIsJoined() {
|
||||||
auto member = room_network->GetRoomMember().lock();
|
auto member = Network::GetRoomMember().lock();
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -299,17 +296,17 @@ bool NetPlayIsJoined() {
|
||||||
member->GetState() == Network::RoomMember::State::Moderator);
|
member->GetState() == Network::RoomMember::State::Moderator);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetPlayIsHostedRoom() {
|
bool AndroidMultiplayer::NetPlayIsHostedRoom() {
|
||||||
if (auto room = room_network->GetRoom().lock()) {
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
return room->GetState() == Network::Room::State::Open;
|
return room->GetState() == Network::Room::State::Open;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlayLeaveRoom() {
|
void AndroidMultiplayer::NetPlayLeaveRoom() {
|
||||||
if (auto room = room_network->GetRoom().lock()) {
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
// if you are in a room, leave it
|
// if you are in a room, leave it
|
||||||
if (auto member = room_network->GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->Leave();
|
member->Leave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,21 +319,52 @@ void NetPlayLeaveRoom() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkShutdown() {
|
void AndroidMultiplayer::NetworkShutdown() {
|
||||||
room_network->Shutdown();
|
Network::Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetPlayIsModerator() {
|
bool AndroidMultiplayer::NetPlayIsModerator() {
|
||||||
auto member = room_network->GetRoomMember().lock();
|
auto member = Network::GetRoomMember().lock();
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return member->GetState() == Network::RoomMember::State::Moderator;
|
return member->GetState() == Network::RoomMember::State::Moderator;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> NetPlayGetBanList() {
|
std::vector<std::string> AndroidMultiplayer::NetPlayGetPublicRooms() {
|
||||||
|
std::vector<std::string> room_list;
|
||||||
|
|
||||||
|
if (auto session = announce_multiplayer_session.lock()) {
|
||||||
|
auto rooms = session->GetRoomList();
|
||||||
|
for (const auto &room: rooms) {
|
||||||
|
room_list.push_back(room.information.name + "|" +
|
||||||
|
(room.has_password ? "1" : "0") + "|" +
|
||||||
|
std::to_string(room.information.member_slots) + "|" +
|
||||||
|
room.ip + "|" +
|
||||||
|
std::to_string(room.information.port) + "|" +
|
||||||
|
room.information.description + "|" +
|
||||||
|
room.information.host_username + "|" +
|
||||||
|
std::to_string(room.information.preferred_game.id) + "|" +
|
||||||
|
room.information.preferred_game.name + "|" +
|
||||||
|
room.information.preferred_game.version);
|
||||||
|
|
||||||
|
for (const auto &member: room.members) {
|
||||||
|
room_list.push_back("MEMBER|" + room.information.name + "|" +
|
||||||
|
member.username + "|" +
|
||||||
|
member.nickname + "|" +
|
||||||
|
std::to_string(member.game.id) + "|" +
|
||||||
|
member.game.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return room_list;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> AndroidMultiplayer::NetPlayGetBanList() {
|
||||||
std::vector<std::string> ban_list;
|
std::vector<std::string> ban_list;
|
||||||
if (auto room = room_network->GetRoom().lock()) {
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
auto [username_bans, ip_bans] = room->GetBanList();
|
auto [username_bans, ip_bans] = room->GetBanList();
|
||||||
|
|
||||||
// Add username bans
|
// Add username bans
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
// Copyright 2024 Mandarine Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
@ -12,6 +8,12 @@
|
||||||
|
|
||||||
#include <common/common_types.h>
|
#include <common/common_types.h>
|
||||||
#include <network/network.h>
|
#include <network/network.h>
|
||||||
|
#include <network/announce_multiplayer_session.h>
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
class AnnounceMultiplayerSession;
|
||||||
|
}
|
||||||
|
|
||||||
enum class NetPlayStatus : s32 {
|
enum class NetPlayStatus : s32 {
|
||||||
NO_ERROR,
|
NO_ERROR,
|
||||||
|
@ -48,21 +50,53 @@ enum class NetPlayStatus : s32 {
|
||||||
CHAT_MESSAGE,
|
CHAT_MESSAGE,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool NetworkInit(Network::RoomNetwork* room_network);
|
class AndroidMultiplayer {
|
||||||
NetPlayStatus NetPlayCreateRoom(const std::string& ipaddress, int port,
|
public:
|
||||||
const std::string& username, const std::string& password,
|
explicit AndroidMultiplayer(Core::System& system,
|
||||||
const std::string& room_name, int max_players);
|
std::shared_ptr<Core::AnnounceMultiplayerSession> session);
|
||||||
NetPlayStatus NetPlayJoinRoom(const std::string& ipaddress, int port,
|
~AndroidMultiplayer();
|
||||||
const std::string& username, const std::string& password);
|
|
||||||
std::vector<std::string> NetPlayRoomInfo();
|
bool NetworkInit();
|
||||||
bool NetPlayIsJoined();
|
|
||||||
bool NetPlayIsHostedRoom();
|
void AddNetPlayMessage(int status, const std::string& msg);
|
||||||
bool NetPlayIsModerator();
|
void AddNetPlayMessage(jint type, jstring msg);
|
||||||
void NetPlaySendMessage(const std::string& msg);
|
|
||||||
void NetPlayKickUser(const std::string& username);
|
void ClearChat();
|
||||||
void NetPlayBanUser(const std::string& username);
|
|
||||||
void NetPlayLeaveRoom();
|
NetPlayStatus NetPlayCreateRoom(const std::string &ipaddress, int port,
|
||||||
std::string NetPlayGetConsoleId();
|
const std::string &username, const std::string &preferredGameName,
|
||||||
void NetworkShutdown();
|
const u64 &preferredGameId, const std::string &password,
|
||||||
std::vector<std::string> NetPlayGetBanList();
|
const std::string &room_name, int max_players);
|
||||||
void NetPlayUnbanUser(const std::string& username);
|
|
||||||
|
NetPlayStatus NetPlayJoinRoom(const std::string &ipaddress, int port,
|
||||||
|
const std::string &username, const std::string &password);
|
||||||
|
|
||||||
|
std::vector<std::string> NetPlayRoomInfo();
|
||||||
|
|
||||||
|
bool NetPlayIsJoined();
|
||||||
|
|
||||||
|
bool NetPlayIsHostedRoom();
|
||||||
|
|
||||||
|
bool NetPlayIsModerator();
|
||||||
|
|
||||||
|
void NetPlaySendMessage(const std::string &msg);
|
||||||
|
|
||||||
|
void NetPlayKickUser(const std::string &username);
|
||||||
|
|
||||||
|
void NetPlayBanUser(const std::string &username);
|
||||||
|
|
||||||
|
void NetPlayLeaveRoom();
|
||||||
|
|
||||||
|
static void NetworkShutdown();
|
||||||
|
|
||||||
|
std::vector<std::string> NetPlayGetBanList();
|
||||||
|
|
||||||
|
void NetPlayUnbanUser(const std::string &username);
|
||||||
|
|
||||||
|
std::vector<std::string> NetPlayGetPublicRooms();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Core::System& system;
|
||||||
|
static std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) ;
|
||||||
|
std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
@ -111,7 +111,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||||
|
|
||||||
struct System::Impl {
|
struct System::Impl {
|
||||||
explicit Impl(System& system)
|
explicit Impl(System& system)
|
||||||
: kernel{system}, fs_controller{system}, hid_core{}, room_network{}, cpu_manager{system},
|
: kernel{system}, fs_controller{system}, hid_core{}, cpu_manager{system},
|
||||||
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
|
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
|
||||||
|
|
||||||
u64 program_id;
|
u64 program_id;
|
||||||
|
@ -421,7 +421,7 @@ struct System::Impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadOverrides(program_id);
|
LoadOverrides(program_id);
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
Network::GameInfo game_info;
|
Network::GameInfo game_info;
|
||||||
game_info.name = name;
|
game_info.name = name;
|
||||||
game_info.id = params.program_id;
|
game_info.id = params.program_id;
|
||||||
|
@ -466,7 +466,7 @@ struct System::Impl {
|
||||||
stop_event = {};
|
stop_event = {};
|
||||||
Network::RestartSocketOperations();
|
Network::RestartSocketOperations();
|
||||||
|
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
Network::GameInfo game_info{};
|
Network::GameInfo game_info{};
|
||||||
room_member->SendGameInfo(game_info);
|
room_member->SendGameInfo(game_info);
|
||||||
}
|
}
|
||||||
|
@ -520,7 +520,6 @@ struct System::Impl {
|
||||||
std::unique_ptr<Core::DeviceMemory> device_memory;
|
std::unique_ptr<Core::DeviceMemory> device_memory;
|
||||||
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
||||||
Core::HID::HIDCore hid_core;
|
Core::HID::HIDCore hid_core;
|
||||||
Network::RoomNetwork room_network;
|
|
||||||
|
|
||||||
CpuManager cpu_manager;
|
CpuManager cpu_manager;
|
||||||
std::atomic_bool is_powered_on{};
|
std::atomic_bool is_powered_on{};
|
||||||
|
@ -979,14 +978,6 @@ const Core::Debugger& System::GetDebugger() const {
|
||||||
return *impl->debugger;
|
return *impl->debugger;
|
||||||
}
|
}
|
||||||
|
|
||||||
Network::RoomNetwork& System::GetRoomNetwork() {
|
|
||||||
return impl->room_network;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Network::RoomNetwork& System::GetRoomNetwork() const {
|
|
||||||
return impl->room_network;
|
|
||||||
}
|
|
||||||
|
|
||||||
Tools::RenderdocAPI& System::GetRenderdocAPI() {
|
Tools::RenderdocAPI& System::GetRenderdocAPI() {
|
||||||
return *impl->renderdoc_api;
|
return *impl->renderdoc_api;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
@ -104,10 +107,6 @@ namespace Core::HID {
|
||||||
class HIDCore;
|
class HIDCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Network {
|
|
||||||
class RoomNetwork;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Tools {
|
namespace Tools {
|
||||||
class RenderdocAPI;
|
class RenderdocAPI;
|
||||||
}
|
}
|
||||||
|
@ -380,12 +379,6 @@ public:
|
||||||
[[nodiscard]] Core::Debugger& GetDebugger();
|
[[nodiscard]] Core::Debugger& GetDebugger();
|
||||||
[[nodiscard]] const Core::Debugger& GetDebugger() const;
|
[[nodiscard]] const Core::Debugger& GetDebugger() const;
|
||||||
|
|
||||||
/// Gets a mutable reference to the Room Network.
|
|
||||||
[[nodiscard]] Network::RoomNetwork& GetRoomNetwork();
|
|
||||||
|
|
||||||
/// Gets an immutable reference to the Room Network.
|
|
||||||
[[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
|
|
||||||
|
|
||||||
[[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
|
[[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
|
||||||
|
|
||||||
void SetExitLocked(bool locked);
|
void SetExitLocked(bool locked);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "core/hle/service/ldn/lan_discovery.h"
|
#include "core/hle/service/ldn/lan_discovery.h"
|
||||||
#include "core/internal_network/network.h"
|
#include "core/internal_network/network.h"
|
||||||
#include "core/internal_network/network_interface.h"
|
#include "core/internal_network/network_interface.h"
|
||||||
|
@ -33,9 +36,8 @@ void LanStation::OverrideInfo() {
|
||||||
node_info->is_connected = connected ? 1 : 0;
|
node_info->is_connected = connected ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_)
|
LANDiscovery::LANDiscovery()
|
||||||
: stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}),
|
: stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}){}
|
||||||
room_network{room_network_} {}
|
|
||||||
|
|
||||||
LANDiscovery::~LANDiscovery() {
|
LANDiscovery::~LANDiscovery() {
|
||||||
if (inited) {
|
if (inited) {
|
||||||
|
@ -410,7 +412,7 @@ void LANDiscovery::OnNetworkInfoChanged() {
|
||||||
|
|
||||||
Network::IPv4Address LANDiscovery::GetLocalIp() const {
|
Network::IPv4Address LANDiscovery::GetLocalIp() const {
|
||||||
Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF};
|
Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->IsConnected()) {
|
if (room_member->IsConnected()) {
|
||||||
local_ip = room_member->GetFakeIpAddress();
|
local_ip = room_member->GetFakeIpAddress();
|
||||||
}
|
}
|
||||||
|
@ -468,7 +470,7 @@ void LANDiscovery::SendBroadcast(Network::LDNPacketType type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LANDiscovery::SendPacket(const Network::LDNPacket& packet) {
|
void LANDiscovery::SendPacket(const Network::LDNPacket& packet) {
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->IsConnected()) {
|
if (room_member->IsConnected()) {
|
||||||
room_member->SendLdnPacket(packet);
|
room_member->SendLdnPacket(packet);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
@ -47,7 +50,7 @@ class LANDiscovery {
|
||||||
public:
|
public:
|
||||||
using LanEventFunc = std::function<void()>;
|
using LanEventFunc = std::function<void()>;
|
||||||
|
|
||||||
LANDiscovery(Network::RoomNetwork& room_network_);
|
LANDiscovery();
|
||||||
~LANDiscovery();
|
~LANDiscovery();
|
||||||
|
|
||||||
State GetState() const;
|
State GetState() const;
|
||||||
|
@ -127,7 +130,5 @@ protected:
|
||||||
std::optional<Ipv4Address> host_ip;
|
std::optional<Ipv4Address> host_ip;
|
||||||
|
|
||||||
LanEventFunc lan_event;
|
LanEventFunc lan_event;
|
||||||
|
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
} // namespace Service::LDN
|
} // namespace Service::LDN
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
@ -22,7 +25,7 @@ namespace Service::LDN {
|
||||||
IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& system_)
|
IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& system_)
|
||||||
: ServiceFramework{system_, "IUserLocalCommunicationService"},
|
: ServiceFramework{system_, "IUserLocalCommunicationService"},
|
||||||
service_context{system, "IUserLocalCommunicationService"},
|
service_context{system, "IUserLocalCommunicationService"},
|
||||||
room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
|
lan_discovery{} {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, C<&IUserLocalCommunicationService::GetState>, "GetState"},
|
{0, C<&IUserLocalCommunicationService::GetState>, "GetState"},
|
||||||
|
@ -65,7 +68,7 @@ IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& sys
|
||||||
|
|
||||||
IUserLocalCommunicationService::~IUserLocalCommunicationService() {
|
IUserLocalCommunicationService::~IUserLocalCommunicationService() {
|
||||||
if (is_initialized) {
|
if (is_initialized) {
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
room_member->Unbind(ldn_packet_received);
|
room_member->Unbind(ldn_packet_received);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +106,7 @@ Result IUserLocalCommunicationService::GetIpv4Address(Out<Ipv4Address> out_curre
|
||||||
*out_subnet_mask = {Network::TranslateIPv4(network_interface->subnet_mask)};
|
*out_subnet_mask = {Network::TranslateIPv4(network_interface->subnet_mask)};
|
||||||
|
|
||||||
// When we're connected to a room, spoof the hosts IP address
|
// When we're connected to a room, spoof the hosts IP address
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->IsConnected()) {
|
if (room_member->IsConnected()) {
|
||||||
*out_current_address = room_member->GetFakeIpAddress();
|
*out_current_address = room_member->GetFakeIpAddress();
|
||||||
}
|
}
|
||||||
|
@ -280,7 +283,7 @@ Result IUserLocalCommunicationService::Initialize(ClientProcessId aruid) {
|
||||||
const auto network_interface = Network::GetSelectedNetworkInterface();
|
const auto network_interface = Network::GetSelectedNetworkInterface();
|
||||||
R_UNLESS(network_interface, ResultAirplaneModeEnabled);
|
R_UNLESS(network_interface, ResultAirplaneModeEnabled);
|
||||||
|
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
ldn_packet_received = room_member->BindOnLdnPacketReceived(
|
ldn_packet_received = room_member->BindOnLdnPacketReceived(
|
||||||
[this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
|
[this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
|
||||||
} else {
|
} else {
|
||||||
|
@ -295,7 +298,7 @@ Result IUserLocalCommunicationService::Initialize(ClientProcessId aruid) {
|
||||||
|
|
||||||
Result IUserLocalCommunicationService::Finalize() {
|
Result IUserLocalCommunicationService::Finalize() {
|
||||||
LOG_INFO(Service_LDN, "called");
|
LOG_INFO(Service_LDN, "called");
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
room_member->Unbind(ldn_packet_received);
|
room_member->Unbind(ldn_packet_received);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/hle/service/cmif_types.h"
|
#include "core/hle/service/cmif_types.h"
|
||||||
|
@ -13,10 +16,6 @@ namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Network {
|
|
||||||
class RoomNetwork;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Service::LDN {
|
namespace Service::LDN {
|
||||||
|
|
||||||
class IUserLocalCommunicationService final
|
class IUserLocalCommunicationService final
|
||||||
|
@ -91,7 +90,6 @@ private:
|
||||||
|
|
||||||
KernelHelpers::ServiceContext service_context;
|
KernelHelpers::ServiceContext service_context;
|
||||||
Kernel::KEvent* state_change_event;
|
Kernel::KEvent* state_change_event;
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
LANDiscovery lan_discovery;
|
LANDiscovery lan_discovery;
|
||||||
|
|
||||||
// Callback identifier for the OnLDNPacketReceived event.
|
// Callback identifier for the OnLDNPacketReceived event.
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/service/ipc_helpers.h"
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
|
@ -406,7 +409,7 @@ void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) {
|
||||||
}();
|
}();
|
||||||
|
|
||||||
// When we're connected to a room, spoof the hosts IP address
|
// When we're connected to a room, spoof the hosts IP address
|
||||||
if (auto room_member = network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->IsConnected()) {
|
if (room_member->IsConnected()) {
|
||||||
network_profile_data.ip_setting_data.ip_address_setting.ip_address =
|
network_profile_data.ip_setting_data.ip_address_setting.ip_address =
|
||||||
room_member->GetFakeIpAddress();
|
room_member->GetFakeIpAddress();
|
||||||
|
@ -454,7 +457,7 @@ void IGeneralService::GetCurrentIpAddress(HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// When we're connected to a room, spoof the hosts IP address
|
// When we're connected to a room, spoof the hosts IP address
|
||||||
if (auto room_member = network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->IsConnected()) {
|
if (room_member->IsConnected()) {
|
||||||
ipv4 = room_member->GetFakeIpAddress();
|
ipv4 = room_member->GetFakeIpAddress();
|
||||||
}
|
}
|
||||||
|
@ -513,7 +516,7 @@ void IGeneralService::GetCurrentIpConfigInfo(HLERequestContext& ctx) {
|
||||||
}();
|
}();
|
||||||
|
|
||||||
// When we're connected to a room, spoof the hosts IP address
|
// When we're connected to a room, spoof the hosts IP address
|
||||||
if (auto room_member = network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->IsConnected()) {
|
if (room_member->IsConnected()) {
|
||||||
ip_config_info.ip_address_setting.ip_address = room_member->GetFakeIpAddress();
|
ip_config_info.ip_address_setting.ip_address = room_member->GetFakeIpAddress();
|
||||||
}
|
}
|
||||||
|
@ -643,7 +646,7 @@ void IGeneralService::GetCurrentAccessPoint(HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
IGeneralService::IGeneralService(Core::System& system_)
|
IGeneralService::IGeneralService(Core::System& system_)
|
||||||
: ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
|
: ServiceFramework{system_, "IGeneralService"} {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{1, &IGeneralService::GetClientId, "GetClientId"},
|
{1, &IGeneralService::GetClientId, "GetClientId"},
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
@ -9,10 +12,6 @@ namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Network {
|
|
||||||
class RoomNetwork;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Service::NIFM {
|
namespace Service::NIFM {
|
||||||
|
|
||||||
void LoopProcess(Core::System& system);
|
void LoopProcess(Core::System& system);
|
||||||
|
@ -42,8 +41,6 @@ private:
|
||||||
void ConfirmSystemAvailability(HLERequestContext& ctx);
|
void ConfirmSystemAvailability(HLERequestContext& ctx);
|
||||||
void SetBackgroundRequestEnabled(HLERequestContext& ctx);
|
void SetBackgroundRequestEnabled(HLERequestContext& ctx);
|
||||||
void GetCurrentAccessPoint(HLERequestContext& ctx);
|
void GetCurrentAccessPoint(HLERequestContext& ctx);
|
||||||
|
|
||||||
Network::RoomNetwork& network;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::NIFM
|
} // namespace Service::NIFM
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
@ -508,9 +511,9 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco
|
||||||
|
|
||||||
LOG_INFO(Service, "New socket fd={}", fd);
|
LOG_INFO(Service, "New socket fd={}", fd);
|
||||||
|
|
||||||
auto room_member = room_network.GetRoomMember().lock();
|
auto room_member = Network::GetRoomMember().lock();
|
||||||
if (room_member && room_member->IsConnected()) {
|
if (room_member && room_member->IsConnected()) {
|
||||||
descriptor.socket = std::make_shared<Network::ProxySocket>(room_network);
|
descriptor.socket = std::make_shared<Network::ProxySocket>();
|
||||||
} else {
|
} else {
|
||||||
descriptor.socket = std::make_shared<Network::Socket>();
|
descriptor.socket = std::make_shared<Network::Socket>();
|
||||||
}
|
}
|
||||||
|
@ -970,7 +973,7 @@ void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
BSD::BSD(Core::System& system_, const char* name)
|
BSD::BSD(Core::System& system_, const char* name)
|
||||||
: ServiceFramework{system_, name}, room_network{system_.GetRoomNetwork()} {
|
: ServiceFramework{system_, name} {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, &BSD::RegisterClient, "RegisterClient"},
|
{0, &BSD::RegisterClient, "RegisterClient"},
|
||||||
|
@ -1012,7 +1015,7 @@ BSD::BSD(Core::System& system_, const char* name)
|
||||||
|
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
|
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
proxy_packet_received = room_member->BindOnProxyPacketReceived(
|
proxy_packet_received = room_member->BindOnProxyPacketReceived(
|
||||||
[this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
|
[this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
|
||||||
} else {
|
} else {
|
||||||
|
@ -1021,7 +1024,7 @@ BSD::BSD(Core::System& system_, const char* name)
|
||||||
}
|
}
|
||||||
|
|
||||||
BSD::~BSD() {
|
BSD::~BSD() {
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
room_member->Unbind(proxy_packet_received);
|
room_member->Unbind(proxy_packet_received);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -179,8 +182,6 @@ private:
|
||||||
|
|
||||||
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
|
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
|
||||||
|
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
|
|
||||||
/// Callback to parse and handle a received wifi packet.
|
/// Callback to parse and handle a received wifi packet.
|
||||||
void OnProxyPacketReceived(const Network::ProxyPacket& packet);
|
void OnProxyPacketReceived(const Network::ProxyPacket& packet);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
@ -18,7 +21,7 @@
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {}
|
ProxySocket::ProxySocket() noexcept {}
|
||||||
|
|
||||||
ProxySocket::~ProxySocket() {
|
ProxySocket::~ProxySocket() {
|
||||||
if (fd == INVALID_SOCKET) {
|
if (fd == INVALID_SOCKET) {
|
||||||
|
@ -187,7 +190,7 @@ std::pair<s32, Errno> ProxySocket::Send(std::span<const u8> message, int flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxySocket::SendPacket(ProxyPacket& packet) {
|
void ProxySocket::SendPacket(ProxyPacket& packet) {
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->IsConnected()) {
|
if (room_member->IsConnected()) {
|
||||||
packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(),
|
packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(),
|
||||||
packet.data.size());
|
packet.data.size());
|
||||||
|
@ -205,7 +208,7 @@ std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message
|
||||||
return {static_cast<s32>(message.size()), Errno::SUCCESS};
|
return {static_cast<s32>(message.size()), Errno::SUCCESS};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (!room_member->IsConnected()) {
|
if (!room_member->IsConnected()) {
|
||||||
return {static_cast<s32>(message.size()), Errno::SUCCESS};
|
return {static_cast<s32>(message.size()), Errno::SUCCESS};
|
||||||
}
|
}
|
||||||
|
@ -222,7 +225,7 @@ std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message
|
||||||
// If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address,
|
// If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address,
|
||||||
// replace it with a "fake" routing address
|
// replace it with a "fake" routing address
|
||||||
if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) {
|
if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) {
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
packet.local_endpoint.ip = room_member->GetFakeIpAddress();
|
packet.local_endpoint.ip = room_member->GetFakeIpAddress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@ -14,11 +17,9 @@
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
class RoomNetwork;
|
|
||||||
|
|
||||||
class ProxySocket : public SocketBase {
|
class ProxySocket : public SocketBase {
|
||||||
public:
|
public:
|
||||||
explicit ProxySocket(RoomNetwork& room_network_) noexcept;
|
explicit ProxySocket() noexcept;
|
||||||
~ProxySocket() override;
|
~ProxySocket() override;
|
||||||
|
|
||||||
void HandleProxyPacket(const ProxyPacket& packet) override;
|
void HandleProxyPacket(const ProxyPacket& packet) override;
|
||||||
|
@ -92,8 +93,6 @@ private:
|
||||||
Protocol protocol;
|
Protocol protocol;
|
||||||
|
|
||||||
std::mutex packets_mutex;
|
std::mutex packets_mutex;
|
||||||
|
|
||||||
RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Network
|
} // namespace Network
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2025 eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -55,20 +55,20 @@ static void PrintHelp(const char* argv0) {
|
||||||
LOG_INFO(Network,
|
LOG_INFO(Network,
|
||||||
"Usage: {}"
|
"Usage: {}"
|
||||||
" [options] <filename>\n"
|
" [options] <filename>\n"
|
||||||
"--room-name The name of the room\n"
|
"-n, --room-name The name of the room\n"
|
||||||
"--room-description The room description\n"
|
"-d, --room-description The room description\n"
|
||||||
"--bind-address The bind address for the room\n"
|
"-s, --bind-address The bind address for the room\n"
|
||||||
"--port The port used for the room\n"
|
"-p, --port The port used for the room\n"
|
||||||
"--max_members The maximum number of players for this room\n"
|
"-m, --max-members The maximum number of players for this room\n"
|
||||||
"--password The password for the room\n"
|
"-w, --password The password for the room\n"
|
||||||
"--preferred-game The preferred game for this room\n"
|
"-g, --preferred-game The preferred game for this room\n"
|
||||||
"--preferred-game-id The preferred game-id for this room\n"
|
"-i, --preferred-game-id The preferred game-id for this room\n"
|
||||||
"--username The username used for announce\n"
|
"-u, --username The username used for announce\n"
|
||||||
"--token The token used for announce\n"
|
"-t, --token The token used for announce\n"
|
||||||
"--web-api-url yuzu Web API url\n"
|
"-a, --web-api-url yuzu Web API url\n"
|
||||||
"--ban-list-file The file for storing the room ban list\n"
|
"-b, --ban-list-file The file for storing the room ban list\n"
|
||||||
"--log-file The file for storing the room log\n"
|
"-l, --log-file The file for storing the room log\n"
|
||||||
"--enable-yuzu-mods Allow yuzu Community Moderators to moderate on your room\n"
|
"-e, --enable-mods Allow Community Moderators to moderate on your room\n"
|
||||||
"-h, --help Display this help and exit\n"
|
"-h, --help Display this help and exit\n"
|
||||||
"-v, --version Output version information and exit\n",
|
"-v, --version Output version information and exit\n",
|
||||||
argv0);
|
argv0);
|
||||||
|
@ -209,19 +209,18 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
|
||||||
std::string token;
|
std::string token;
|
||||||
std::string web_api_url;
|
std::string web_api_url;
|
||||||
std::string ban_list_file;
|
std::string ban_list_file;
|
||||||
std::string log_file = "yuzu-room.log";
|
std::string log_file = "eden-room.log";
|
||||||
std::string bind_address;
|
std::string bind_address;
|
||||||
u64 preferred_game_id = 0;
|
u64 preferred_game_id = 0;
|
||||||
u32 port = Network::DefaultRoomPort;
|
u32 port = Network::DefaultRoomPort;
|
||||||
u32 max_members = 16;
|
u32 max_members = 16;
|
||||||
|
|
||||||
// TODO(alekpop): Implement this into main executable, for --room and a few others.
|
|
||||||
static struct option long_options[] = {
|
static struct option long_options[] = {
|
||||||
{"room-name", required_argument, 0, 'n'},
|
{"room-name", required_argument, 0, 'n'},
|
||||||
{"room-description", required_argument, 0, 'd'},
|
{"room-description", required_argument, 0, 'd'},
|
||||||
{"bind-address", required_argument, 0, 's'},
|
{"bind-address", required_argument, 0, 's'},
|
||||||
{"port", required_argument, 0, 'p'},
|
{"port", required_argument, 0, 'p'},
|
||||||
{"max_members", required_argument, 0, 'm'},
|
{"max-members", required_argument, 0, 'm'},
|
||||||
{"password", required_argument, 0, 'w'},
|
{"password", required_argument, 0, 'w'},
|
||||||
{"preferred-game", required_argument, 0, 'g'},
|
{"preferred-game", required_argument, 0, 'g'},
|
||||||
{"preferred-game-id", required_argument, 0, 'i'},
|
{"preferred-game-id", required_argument, 0, 'i'},
|
||||||
|
@ -230,7 +229,7 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
|
||||||
{"web-api-url", required_argument, 0, 'a'},
|
{"web-api-url", required_argument, 0, 'a'},
|
||||||
{"ban-list-file", required_argument, 0, 'b'},
|
{"ban-list-file", required_argument, 0, 'b'},
|
||||||
{"log-file", required_argument, 0, 'l'},
|
{"log-file", required_argument, 0, 'l'},
|
||||||
{"enable-yuzu-mods", no_argument, 0, 'e'},
|
{"enable-mods", no_argument, 0, 'e'},
|
||||||
{"help", no_argument, 0, 'h'},
|
{"help", no_argument, 0, 'h'},
|
||||||
{"version", no_argument, 0, 'v'},
|
{"version", no_argument, 0, 'v'},
|
||||||
// Entry option
|
// Entry option
|
||||||
|
@ -243,7 +242,9 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
|
||||||
while (optind < argc) {
|
while (optind < argc) {
|
||||||
int arg = getopt_long(argc, argv, "n:d:s:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index);
|
int arg = getopt_long(argc, argv, "n:d:s:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index);
|
||||||
if (arg != -1) {
|
if (arg != -1) {
|
||||||
switch (static_cast<char>(arg)) {
|
char carg = static_cast<char>(arg);
|
||||||
|
|
||||||
|
switch (carg) {
|
||||||
case 'n':
|
case 'n':
|
||||||
room_name.assign(optarg);
|
room_name.assign(optarg);
|
||||||
break;
|
break;
|
||||||
|
@ -289,6 +290,8 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
|
||||||
case 'v':
|
case 'v':
|
||||||
PrintVersion();
|
PrintVersion();
|
||||||
std::exit(0);
|
std::exit(0);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -373,9 +376,8 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
|
||||||
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
|
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Network::RoomNetwork network{};
|
Network::Init();
|
||||||
network.Init();
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
if (auto room = network.GetRoom().lock()) {
|
|
||||||
AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
|
AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
|
||||||
.id = preferred_game_id};
|
.id = preferred_game_id};
|
||||||
if (!room->Create(room_name, room_description, bind_address, static_cast<u16>(port),
|
if (!room->Create(room_name, room_description, bind_address, static_cast<u16>(port),
|
||||||
|
@ -385,7 +387,7 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
|
||||||
std::exit(-1);
|
std::exit(-1);
|
||||||
}
|
}
|
||||||
LOG_INFO(Network, "Room is open. Close with Q+Enter...");
|
LOG_INFO(Network, "Room is open. Close with Q+Enter...");
|
||||||
auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>(network);
|
auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>();
|
||||||
if (announce) {
|
if (announce) {
|
||||||
announce_session->Start();
|
announce_session->Start();
|
||||||
}
|
}
|
||||||
|
@ -407,7 +409,7 @@ void LaunchRoom(int argc, char** argv, bool called_by_option)
|
||||||
}
|
}
|
||||||
room->Destroy();
|
room->Destroy();
|
||||||
}
|
}
|
||||||
network.Shutdown();
|
Network::Shutdown();
|
||||||
detached_tasks.WaitForAllTasks();
|
detached_tasks.WaitForAllTasks();
|
||||||
std::exit(0);
|
std::exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -19,8 +22,7 @@ namespace Core {
|
||||||
// Time between room is announced to web_service
|
// Time between room is announced to web_service
|
||||||
static constexpr std::chrono::seconds announce_time_interval(15);
|
static constexpr std::chrono::seconds announce_time_interval(15);
|
||||||
|
|
||||||
AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_)
|
AnnounceMultiplayerSession::AnnounceMultiplayerSession() {
|
||||||
: room_network{room_network_} {
|
|
||||||
#ifdef ENABLE_WEB_SERVICE
|
#ifdef ENABLE_WEB_SERVICE
|
||||||
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
|
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
|
||||||
Settings::values.yuzu_username.GetValue(),
|
Settings::values.yuzu_username.GetValue(),
|
||||||
|
@ -31,7 +33,7 @@ AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& roo
|
||||||
}
|
}
|
||||||
|
|
||||||
WebService::WebResult AnnounceMultiplayerSession::Register() {
|
WebService::WebResult AnnounceMultiplayerSession::Register() {
|
||||||
auto room = room_network.GetRoom().lock();
|
auto room = Network::GetRoom().lock();
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return WebService::WebResult{WebService::WebResult::Code::LibError,
|
return WebService::WebResult{WebService::WebResult::Code::LibError,
|
||||||
"Network is not initialized", ""};
|
"Network is not initialized", ""};
|
||||||
|
@ -120,7 +122,7 @@ void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
|
||||||
std::future<WebService::WebResult> future;
|
std::future<WebService::WebResult> future;
|
||||||
while (!shutdown_event.WaitUntil(update_time)) {
|
while (!shutdown_event.WaitUntil(update_time)) {
|
||||||
update_time += announce_time_interval;
|
update_time += announce_time_interval;
|
||||||
auto room = room_network.GetRoom().lock();
|
auto room = Network::GetRoom().lock();
|
||||||
if (!room) {
|
if (!room) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
@ -15,7 +18,6 @@
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
class Room;
|
class Room;
|
||||||
class RoomNetwork;
|
|
||||||
} // namespace Network
|
} // namespace Network
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
@ -28,7 +30,8 @@ namespace Core {
|
||||||
class AnnounceMultiplayerSession {
|
class AnnounceMultiplayerSession {
|
||||||
public:
|
public:
|
||||||
using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>;
|
using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>;
|
||||||
AnnounceMultiplayerSession(Network::RoomNetwork& room_network_);
|
|
||||||
|
AnnounceMultiplayerSession();
|
||||||
~AnnounceMultiplayerSession();
|
~AnnounceMultiplayerSession();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,8 +94,6 @@ private:
|
||||||
std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend;
|
std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend;
|
||||||
|
|
||||||
std::atomic_bool registered = false; ///< Whether the room has been registered
|
std::atomic_bool registered = false; ///< Whether the room has been registered
|
||||||
|
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "enet/enet.h"
|
#include "enet/enet.h"
|
||||||
|
@ -8,40 +11,38 @@
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
RoomNetwork::RoomNetwork() {
|
static std::shared_ptr<RoomMember> g_room_member; ///< RoomMember (Client) for network games
|
||||||
m_room = std::make_shared<Room>();
|
static std::shared_ptr<Room> g_room; ///< Room (Server) for network games
|
||||||
m_room_member = std::make_shared<RoomMember>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RoomNetwork::Init() {
|
bool Init() {
|
||||||
if (enet_initialize() != 0) {
|
if (enet_initialize() != 0) {
|
||||||
LOG_ERROR(Network, "Error initializing ENet");
|
LOG_ERROR(Network, "Error initializing ENet");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_room = std::make_shared<Room>();
|
g_room = std::make_shared<Room>();
|
||||||
m_room_member = std::make_shared<RoomMember>();
|
g_room_member = std::make_shared<RoomMember>();
|
||||||
LOG_DEBUG(Network, "initialized OK");
|
LOG_DEBUG(Network, "initialized OK");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::weak_ptr<Room> RoomNetwork::GetRoom() {
|
std::weak_ptr<Room> GetRoom() {
|
||||||
return m_room;
|
return g_room;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::weak_ptr<RoomMember> RoomNetwork::GetRoomMember() {
|
std::weak_ptr<RoomMember> GetRoomMember() {
|
||||||
return m_room_member;
|
return g_room_member;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomNetwork::Shutdown() {
|
void Shutdown() {
|
||||||
if (m_room_member) {
|
if (g_room_member) {
|
||||||
if (m_room_member->IsConnected())
|
if (g_room_member->IsConnected())
|
||||||
m_room_member->Leave();
|
g_room_member->Leave();
|
||||||
m_room_member.reset();
|
g_room_member.reset();
|
||||||
}
|
}
|
||||||
if (m_room) {
|
if (g_room) {
|
||||||
if (m_room->GetState() == Room::State::Open)
|
if (g_room->GetState() == Room::State::Open)
|
||||||
m_room->Destroy();
|
g_room->Destroy();
|
||||||
m_room.reset();
|
g_room.reset();
|
||||||
}
|
}
|
||||||
enet_deinitialize();
|
enet_deinitialize();
|
||||||
LOG_DEBUG(Network, "shutdown OK");
|
LOG_DEBUG(Network, "shutdown OK");
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -9,25 +12,16 @@
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
class RoomNetwork {
|
/// Initializes and registers the network device, the room, and the room member.
|
||||||
public:
|
bool Init();
|
||||||
RoomNetwork();
|
|
||||||
|
|
||||||
/// Initializes and registers the network device, the room, and the room member.
|
/// Returns a pointer to the room handle
|
||||||
bool Init();
|
std::weak_ptr<Room> GetRoom();
|
||||||
|
|
||||||
/// Returns a pointer to the room handle
|
/// Returns a pointer to the room member handle
|
||||||
std::weak_ptr<Room> GetRoom();
|
std::weak_ptr<RoomMember> GetRoomMember();
|
||||||
|
|
||||||
/// Returns a pointer to the room member handle
|
/// Unregisters the network device, the room, and the room member and shut them down.
|
||||||
std::weak_ptr<RoomMember> GetRoomMember();
|
void Shutdown();
|
||||||
|
|
||||||
/// Unregisters the network device, the room, and the room member and shut them down.
|
|
||||||
void Shutdown();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<RoomMember> m_room_member; ///< RoomMember (Client) for network games
|
|
||||||
std::shared_ptr<Room> m_room; ///< Room (Server) for network games
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Network
|
} // namespace Network
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
# SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "yuzu/configuration/configure_ui.h"
|
#include "yuzu/configuration/configure_ui.h"
|
||||||
|
|
|
@ -9,30 +9,30 @@
|
||||||
#include "yuzu/configuration/configure_web.h"
|
#include "yuzu/configuration/configure_web.h"
|
||||||
#include "yuzu/uisettings.h"
|
#include "yuzu/uisettings.h"
|
||||||
|
|
||||||
static constexpr char token_delimiter{':'};
|
// static constexpr char token_delimiter{':'};
|
||||||
|
|
||||||
static std::string GenerateDisplayToken(const std::string& username, const std::string& token) {
|
// static std::string GenerateDisplayToken(const std::string& username, const std::string& token) {
|
||||||
if (username.empty() || token.empty()) {
|
// if (username.empty() || token.empty()) {
|
||||||
return {};
|
// return {};
|
||||||
}
|
// }
|
||||||
|
|
||||||
const std::string unencoded_display_token{username + token_delimiter + token};
|
// const std::string unencoded_display_token{username + token_delimiter + token};
|
||||||
QByteArray b{unencoded_display_token.c_str()};
|
// QByteArray b{unencoded_display_token.c_str()};
|
||||||
QByteArray b64 = b.toBase64();
|
// QByteArray b64 = b.toBase64();
|
||||||
return b64.toStdString();
|
// return b64.toStdString();
|
||||||
}
|
// }
|
||||||
|
|
||||||
static std::string UsernameFromDisplayToken(const std::string& display_token) {
|
// static std::string UsernameFromDisplayToken(const std::string& display_token) {
|
||||||
const std::string unencoded_display_token{
|
// const std::string unencoded_display_token{
|
||||||
QByteArray::fromBase64(display_token.c_str()).toStdString()};
|
// QByteArray::fromBase64(display_token.c_str()).toStdString()};
|
||||||
return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
|
// return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
|
||||||
}
|
// }
|
||||||
|
|
||||||
static std::string TokenFromDisplayToken(const std::string& display_token) {
|
// static std::string TokenFromDisplayToken(const std::string& display_token) {
|
||||||
const std::string unencoded_display_token{
|
// const std::string unencoded_display_token{
|
||||||
QByteArray::fromBase64(display_token.c_str()).toStdString()};
|
// QByteArray::fromBase64(display_token.c_str()).toStdString()};
|
||||||
return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
|
// return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
|
||||||
}
|
// }
|
||||||
|
|
||||||
ConfigureWeb::ConfigureWeb(QWidget* parent)
|
ConfigureWeb::ConfigureWeb(QWidget* parent)
|
||||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
|
||||||
|
@ -66,7 +66,7 @@ void ConfigureWeb::RetranslateUI() {
|
||||||
"color:#039be5;\">Sign up</span></a>"));
|
"color:#039be5;\">Sign up</span></a>"));
|
||||||
|
|
||||||
ui->web_token_info_link->setText(
|
ui->web_token_info_link->setText(
|
||||||
tr("<a href='https://eden-emulator.github.io/wiki/yuzu-web-service/'><span style=\"text-decoration: "
|
tr("<a href='https://evilperson1337.notion.site/Hosting-a-Room-Inside-of-Eden-20457c2edaf680108abac6215a79acdb'><span style=\"text-decoration: "
|
||||||
"underline; color:#039be5;\">What is my token?</span></a>"));
|
"underline; color:#039be5;\">What is my token?</span></a>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,14 +76,10 @@ void ConfigureWeb::SetConfiguration() {
|
||||||
ui->web_signup_link->setOpenExternalLinks(true);
|
ui->web_signup_link->setOpenExternalLinks(true);
|
||||||
ui->web_token_info_link->setOpenExternalLinks(true);
|
ui->web_token_info_link->setOpenExternalLinks(true);
|
||||||
|
|
||||||
if (Settings::values.yuzu_username.GetValue().empty()) {
|
ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
|
||||||
ui->username->setText(tr("Unspecified"));
|
ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token.GetValue()));
|
||||||
} else {
|
// ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken(
|
||||||
ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
|
// Settings::values.yuzu_username.GetValue(), Settings::values.yuzu_token.GetValue())));
|
||||||
}
|
|
||||||
|
|
||||||
ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken(
|
|
||||||
Settings::values.yuzu_username.GetValue(), Settings::values.yuzu_token.GetValue())));
|
|
||||||
|
|
||||||
// Connect after setting the values, to avoid calling OnLoginChanged now
|
// Connect after setting the values, to avoid calling OnLoginChanged now
|
||||||
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
|
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
|
||||||
|
@ -95,15 +91,8 @@ void ConfigureWeb::SetConfiguration() {
|
||||||
|
|
||||||
void ConfigureWeb::ApplyConfiguration() {
|
void ConfigureWeb::ApplyConfiguration() {
|
||||||
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
|
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
|
||||||
if (user_verified) {
|
Settings::values.yuzu_username = ui->edit_username->text().toStdString();
|
||||||
Settings::values.yuzu_username =
|
Settings::values.yuzu_token = ui->edit_token->text().toStdString();
|
||||||
UsernameFromDisplayToken(ui->edit_token->text().toStdString());
|
|
||||||
Settings::values.yuzu_token = TokenFromDisplayToken(ui->edit_token->text().toStdString());
|
|
||||||
} else {
|
|
||||||
QMessageBox::warning(
|
|
||||||
this, tr("Token not verified"),
|
|
||||||
tr("Token was not verified. The change to your token has not been saved."));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::OnLoginChanged() {
|
void ConfigureWeb::OnLoginChanged() {
|
||||||
|
@ -124,30 +113,34 @@ void ConfigureWeb::OnLoginChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::VerifyLogin() {
|
void ConfigureWeb::VerifyLogin() {
|
||||||
ui->button_verify_login->setDisabled(true);
|
QMessageBox::warning(this,
|
||||||
ui->button_verify_login->setText(tr("Verifying..."));
|
tr("Warning"),
|
||||||
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
|
tr("Verification is currently nonfunctional, instead generate a random "
|
||||||
ui->label_token_verified->setToolTip(tr("Verifying..."));
|
"48-character string with only lowercase a-z."));
|
||||||
|
// ui->button_verify_login->setDisabled(true);
|
||||||
|
// ui->button_verify_login->setText(tr("Verifying..."));
|
||||||
|
// ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
|
||||||
|
// ui->label_token_verified->setToolTip(tr("Verifying..."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::OnLoginVerified() {
|
void ConfigureWeb::OnLoginVerified() {
|
||||||
ui->button_verify_login->setEnabled(true);
|
// ui->button_verify_login->setEnabled(true);
|
||||||
ui->button_verify_login->setText(tr("Verify"));
|
// ui->button_verify_login->setText(tr("Verify"));
|
||||||
if (verify_watcher.result()) {
|
// if (verify_watcher.result()) {
|
||||||
user_verified = true;
|
// user_verified = true;
|
||||||
|
|
||||||
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
|
// ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
|
||||||
ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
|
// ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
|
||||||
ui->username->setText(
|
// ui->username->setText(
|
||||||
QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
|
// QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
|
||||||
} else {
|
// } else {
|
||||||
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
|
// ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
|
||||||
ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
|
// ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
|
||||||
ui->username->setText(tr("Unspecified"));
|
// ui->username->setText(tr("Unspecified"));
|
||||||
QMessageBox::critical(this, tr("Verification failed"),
|
// QMessageBox::critical(this, tr("Verification failed"),
|
||||||
tr("Verification failed. Check that you have entered your token "
|
// tr("Verification failed. Check that you have entered your token "
|
||||||
"correctly, and that your internet connection is working."));
|
// "correctly, and that your internet connection is working."));
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
|
void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="layoutDirection">
|
<property name="layoutDirection">
|
||||||
<enum>Qt::RightToLeft</enum>
|
<enum>Qt::LayoutDirection::RightToLeft</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Verify</string>
|
<string>Verify</string>
|
||||||
|
@ -52,13 +52,23 @@
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="web_signup_link">
|
<widget class="QLabel" name="web_signup_link">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Sign up</string>
|
<string>Sign up</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1" colspan="3">
|
<item row="0" column="1" colspan="3">
|
||||||
<widget class="QLabel" name="username"/>
|
<widget class="QLineEdit" name="edit_username">
|
||||||
|
<property name="maxLength">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="echoMode">
|
||||||
|
<enum>QLineEdit::EchoMode::Normal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_token">
|
<widget class="QLabel" name="label_token">
|
||||||
|
@ -83,7 +93,7 @@
|
||||||
<number>80</number>
|
<number>80</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="echoMode">
|
<property name="echoMode">
|
||||||
<enum>QLineEdit::Password</enum>
|
<enum>QLineEdit::EchoMode::Normal</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -97,7 +107,7 @@
|
||||||
<item row="2" column="2">
|
<item row="2" column="2">
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
@ -143,7 +153,7 @@
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
|
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
@ -351,7 +351,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||||
|
|
||||||
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(system->GetProfileManager());
|
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(system->GetProfileManager());
|
||||||
|
|
||||||
system->GetRoomNetwork().Init();
|
Network::Init();
|
||||||
|
|
||||||
RegisterMetaTypes();
|
RegisterMetaTypes();
|
||||||
|
|
||||||
|
@ -5183,7 +5183,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
|
||||||
render_window->close();
|
render_window->close();
|
||||||
multiplayer_state->Close();
|
multiplayer_state->Close();
|
||||||
system->HIDCore().UnloadInputDevices();
|
system->HIDCore().UnloadInputDevices();
|
||||||
system->GetRoomNetwork().Shutdown();
|
Network::Shutdown();
|
||||||
|
|
||||||
QWidget::closeEvent(event);
|
QWidget::closeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
@ -27,8 +30,7 @@
|
||||||
|
|
||||||
class ChatMessage {
|
class ChatMessage {
|
||||||
public:
|
public:
|
||||||
explicit ChatMessage(const Network::ChatEntry& chat, Network::RoomNetwork& room_network,
|
explicit ChatMessage(const Network::ChatEntry& chat, QTime ts = {}) {
|
||||||
QTime ts = {}) {
|
|
||||||
/// Convert the time to their default locale defined format
|
/// Convert the time to their default locale defined format
|
||||||
QLocale locale;
|
QLocale locale;
|
||||||
timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
|
timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
|
||||||
|
@ -38,7 +40,7 @@ public:
|
||||||
|
|
||||||
// Check for user pings
|
// Check for user pings
|
||||||
QString cur_nickname, cur_username;
|
QString cur_nickname, cur_username;
|
||||||
if (auto room = room_network.GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
cur_nickname = QString::fromStdString(room->GetNickname());
|
cur_nickname = QString::fromStdString(room->GetNickname());
|
||||||
cur_username = QString::fromStdString(room->GetUsername());
|
cur_username = QString::fromStdString(room->GetUsername());
|
||||||
}
|
}
|
||||||
|
@ -202,10 +204,9 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::C
|
||||||
|
|
||||||
ChatRoom::~ChatRoom() = default;
|
ChatRoom::~ChatRoom() = default;
|
||||||
|
|
||||||
void ChatRoom::Initialize(Network::RoomNetwork* room_network_) {
|
void ChatRoom::Initialize() {
|
||||||
room_network = room_network_;
|
|
||||||
// setup the callbacks for network updates
|
// setup the callbacks for network updates
|
||||||
if (auto member = room_network->GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->BindOnChatMessageReceived(
|
member->BindOnChatMessageReceived(
|
||||||
[this](const Network::ChatEntry& chat) { emit ChatReceived(chat); });
|
[this](const Network::ChatEntry& chat) { emit ChatReceived(chat); });
|
||||||
member->BindOnStatusMessageReceived(
|
member->BindOnStatusMessageReceived(
|
||||||
|
@ -239,7 +240,7 @@ void ChatRoom::AppendChatMessage(const QString& msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) {
|
void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) {
|
||||||
if (auto room = room_network->GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
auto members = room->GetMemberInformation();
|
auto members = room->GetMemberInformation();
|
||||||
auto it = std::find_if(members.begin(), members.end(),
|
auto it = std::find_if(members.begin(), members.end(),
|
||||||
[&nickname](const Network::RoomMember::MemberInformation& member) {
|
[&nickname](const Network::RoomMember::MemberInformation& member) {
|
||||||
|
@ -259,7 +260,7 @@ bool ChatRoom::ValidateMessage(const std::string& msg) {
|
||||||
|
|
||||||
void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) {
|
void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) {
|
||||||
// TODO(B3N30): change title
|
// TODO(B3N30): change title
|
||||||
if (auto room_member = room_network->GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
SetPlayerList(room_member->GetMemberInformation());
|
SetPlayerList(room_member->GetMemberInformation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,7 +279,7 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
|
||||||
if (!ValidateMessage(chat.message)) {
|
if (!ValidateMessage(chat.message)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (auto room = room_network->GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
// get the id of the player
|
// get the id of the player
|
||||||
auto members = room->GetMemberInformation();
|
auto members = room->GetMemberInformation();
|
||||||
auto it = std::find_if(members.begin(), members.end(),
|
auto it = std::find_if(members.begin(), members.end(),
|
||||||
|
@ -296,7 +297,7 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto player = std::distance(members.begin(), it);
|
auto player = std::distance(members.begin(), it);
|
||||||
ChatMessage m(chat, *room_network);
|
ChatMessage m(chat);
|
||||||
if (m.ContainsPing()) {
|
if (m.ContainsPing()) {
|
||||||
emit UserPinged();
|
emit UserPinged();
|
||||||
}
|
}
|
||||||
|
@ -335,7 +336,7 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRoom::OnSendChat() {
|
void ChatRoom::OnSendChat() {
|
||||||
if (auto room_member = room_network->GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (!room_member->IsConnected()) {
|
if (!room_member->IsConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -357,7 +358,7 @@ void ChatRoom::OnSendChat() {
|
||||||
LOG_INFO(Network, "Cannot find self in the player list when sending a message.");
|
LOG_INFO(Network, "Cannot find self in the player list when sending a message.");
|
||||||
}
|
}
|
||||||
auto player = std::distance(members.begin(), it);
|
auto player = std::distance(members.begin(), it);
|
||||||
ChatMessage m(chat, *room_network);
|
ChatMessage m(chat);
|
||||||
room_member->SendChatMessage(message);
|
room_member->SendChatMessage(message);
|
||||||
AppendChatMessage(m.GetPlayerChatMessage(player));
|
AppendChatMessage(m.GetPlayerChatMessage(player));
|
||||||
ui->chat_message->clear();
|
ui->chat_message->clear();
|
||||||
|
@ -451,7 +452,7 @@ void ChatRoom::PopupContextMenu(const QPoint& menu_location) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cur_nickname;
|
std::string cur_nickname;
|
||||||
if (auto room = room_network->GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
cur_nickname = room->GetNickname();
|
cur_nickname = room->GetNickname();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -30,7 +33,7 @@ class ChatRoom : public QWidget {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChatRoom(QWidget* parent);
|
explicit ChatRoom(QWidget* parent);
|
||||||
void Initialize(Network::RoomNetwork* room_network);
|
void Initialize();
|
||||||
void RetranslateUi();
|
void RetranslateUi();
|
||||||
void SetPlayerList(const Network::RoomMember::MemberList& member_list);
|
void SetPlayerList(const Network::RoomMember::MemberList& member_list);
|
||||||
void Clear();
|
void Clear();
|
||||||
|
@ -66,7 +69,6 @@ private:
|
||||||
std::unique_ptr<Ui::ChatRoom> ui;
|
std::unique_ptr<Ui::ChatRoom> ui;
|
||||||
std::unordered_set<std::string> block_list;
|
std::unordered_set<std::string> block_list;
|
||||||
std::unordered_map<std::string, QPixmap> icon_cache;
|
std::unordered_map<std::string, QPixmap> icon_cache;
|
||||||
Network::RoomNetwork* room_network;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Network::ChatEntry);
|
Q_DECLARE_METATYPE(Network::ChatEntry);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
@ -18,14 +21,14 @@
|
||||||
#include "yuzu/multiplayer/moderation_dialog.h"
|
#include "yuzu/multiplayer/moderation_dialog.h"
|
||||||
#include "yuzu/multiplayer/state.h"
|
#include "yuzu/multiplayer/state.h"
|
||||||
|
|
||||||
ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_)
|
ClientRoomWindow::ClientRoomWindow(QWidget* parent)
|
||||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||||
ui(std::make_unique<Ui::ClientRoom>()), room_network{room_network_} {
|
ui(std::make_unique<Ui::ClientRoom>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->chat->Initialize(&room_network);
|
ui->chat->Initialize();
|
||||||
|
|
||||||
// setup the callbacks for network updates
|
// setup the callbacks for network updates
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->BindOnRoomInformationChanged(
|
member->BindOnRoomInformationChanged(
|
||||||
[this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); });
|
[this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); });
|
||||||
member->BindOnStateChanged(
|
member->BindOnStateChanged(
|
||||||
|
@ -44,7 +47,7 @@ ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_n
|
||||||
ui->disconnect->setDefault(false);
|
ui->disconnect->setDefault(false);
|
||||||
ui->disconnect->setAutoDefault(false);
|
ui->disconnect->setAutoDefault(false);
|
||||||
connect(ui->moderation, &QPushButton::clicked, [this] {
|
connect(ui->moderation, &QPushButton::clicked, [this] {
|
||||||
ModerationDialog dialog(room_network, this);
|
ModerationDialog dialog(this);
|
||||||
dialog.exec();
|
dialog.exec();
|
||||||
});
|
});
|
||||||
ui->moderation->setDefault(false);
|
ui->moderation->setDefault(false);
|
||||||
|
@ -90,7 +93,7 @@ void ClientRoomWindow::Disconnect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientRoomWindow::UpdateView() {
|
void ClientRoomWindow::UpdateView() {
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
if (member->IsConnected()) {
|
if (member->IsConnected()) {
|
||||||
ui->chat->Enable();
|
ui->chat->Enable();
|
||||||
ui->disconnect->setEnabled(true);
|
ui->disconnect->setEnabled(true);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "yuzu/multiplayer/chat_room.h"
|
#include "yuzu/multiplayer/chat_room.h"
|
||||||
|
@ -13,7 +16,7 @@ class ClientRoomWindow : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_);
|
explicit ClientRoomWindow(QWidget* parent);
|
||||||
~ClientRoomWindow();
|
~ClientRoomWindow();
|
||||||
|
|
||||||
void RetranslateUi();
|
void RetranslateUi();
|
||||||
|
@ -35,5 +38,4 @@ private:
|
||||||
|
|
||||||
QStandardItemModel* player_list;
|
QStandardItemModel* player_list;
|
||||||
std::unique_ptr<Ui::ClientRoom> ui;
|
std::unique_ptr<Ui::ClientRoom> ui;
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QIntValidator>
|
#include <QIntValidator>
|
||||||
|
@ -24,8 +27,7 @@ enum class ConnectionType : u8 { TraversalServer, IP };
|
||||||
|
|
||||||
DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
|
DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
|
||||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||||
ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{
|
ui(std::make_unique<Ui::DirectConnect>()), system{system_} {
|
||||||
system.GetRoomNetwork()} {
|
|
||||||
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
@ -71,7 +73,7 @@ void DirectConnectWindow::Connect() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (const auto member = room_network.GetRoomMember().lock()) {
|
if (const auto member = Network::GetRoomMember().lock()) {
|
||||||
// Prevent the user from trying to join a room while they are already joining.
|
// Prevent the user from trying to join a room while they are already joining.
|
||||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||||
return;
|
return;
|
||||||
|
@ -104,7 +106,7 @@ void DirectConnectWindow::Connect() {
|
||||||
|
|
||||||
// attempt to connect in a different thread
|
// attempt to connect in a different thread
|
||||||
QFuture<void> f = QtConcurrent::run([&] {
|
QFuture<void> f = QtConcurrent::run([&] {
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
auto port = UISettings::values.multiplayer_port.GetValue();
|
auto port = UISettings::values.multiplayer_port.GetValue();
|
||||||
room_member->Join(ui->nickname->text().toStdString(),
|
room_member->Join(ui->nickname->text().toStdString(),
|
||||||
ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP,
|
ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP,
|
||||||
|
@ -129,7 +131,7 @@ void DirectConnectWindow::EndConnecting() {
|
||||||
void DirectConnectWindow::OnConnection() {
|
void DirectConnectWindow::OnConnection() {
|
||||||
EndConnecting();
|
EndConnecting();
|
||||||
|
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->IsConnected()) {
|
if (room_member->IsConnected()) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -45,5 +48,4 @@ private:
|
||||||
std::unique_ptr<Ui::DirectConnect> ui;
|
std::unique_ptr<Ui::DirectConnect> ui;
|
||||||
Validation validation;
|
Validation validation;
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
@ -32,8 +35,7 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
|
||||||
Core::System& system_)
|
Core::System& system_)
|
||||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||||
ui(std::make_unique<Ui::HostRoom>()),
|
ui(std::make_unique<Ui::HostRoom>()),
|
||||||
announce_multiplayer_session(session), system{system_}, room_network{
|
announce_multiplayer_session(session), system{system_} {
|
||||||
system.GetRoomNetwork()} {
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
// set up validation for all of the fields
|
// set up validation for all of the fields
|
||||||
|
@ -137,7 +139,7 @@ void HostRoomWindow::Host() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||||
return;
|
return;
|
||||||
} else if (member->IsConnected()) {
|
} else if (member->IsConnected()) {
|
||||||
|
@ -161,7 +163,7 @@ void HostRoomWindow::Host() {
|
||||||
if (ui->load_ban_list->isChecked()) {
|
if (ui->load_ban_list->isChecked()) {
|
||||||
ban_list = UISettings::values.multiplayer_ban_list;
|
ban_list = UISettings::values.multiplayer_ban_list;
|
||||||
}
|
}
|
||||||
if (auto room = room_network.GetRoom().lock()) {
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
const bool created =
|
const bool created =
|
||||||
room->Create(ui->room_name->text().toStdString(),
|
room->Create(ui->room_name->text().toStdString(),
|
||||||
ui->room_description->toPlainText().toStdString(), "", port, password,
|
ui->room_description->toPlainText().toStdString(), "", port, password,
|
||||||
|
@ -190,7 +192,7 @@ void HostRoomWindow::Host() {
|
||||||
QString::fromStdString(result.result_string),
|
QString::fromStdString(result.result_string),
|
||||||
QMessageBox::Ok);
|
QMessageBox::Ok);
|
||||||
ui->host->setEnabled(true);
|
ui->host->setEnabled(true);
|
||||||
if (auto room = room_network.GetRoom().lock()) {
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
room->Destroy();
|
room->Destroy();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -206,7 +208,7 @@ void HostRoomWindow::Host() {
|
||||||
WebService::Client client(Settings::values.web_api_url.GetValue(),
|
WebService::Client client(Settings::values.web_api_url.GetValue(),
|
||||||
Settings::values.yuzu_username.GetValue(),
|
Settings::values.yuzu_username.GetValue(),
|
||||||
Settings::values.yuzu_token.GetValue());
|
Settings::values.yuzu_token.GetValue());
|
||||||
if (auto room = room_network.GetRoom().lock()) {
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
|
token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
|
||||||
}
|
}
|
||||||
if (token.empty()) {
|
if (token.empty()) {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -59,7 +62,6 @@ private:
|
||||||
ComboBoxProxyModel* proxy;
|
ComboBoxProxyModel* proxy;
|
||||||
Validation validation;
|
Validation validation;
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QtConcurrent/QtConcurrentRun>
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
|
@ -28,8 +31,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
|
||||||
std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
|
std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
|
||||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||||
ui(std::make_unique<Ui::Lobby>()),
|
ui(std::make_unique<Ui::Lobby>()),
|
||||||
announce_multiplayer_session(session), system{system_}, room_network{
|
announce_multiplayer_session(session), system{system_} {
|
||||||
system.GetRoomNetwork()} {
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
// setup the watcher for background connections
|
// setup the watcher for background connections
|
||||||
|
@ -146,7 +148,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto member = room_network.GetRoomMember().lock()) {
|
if (const auto member = Network::GetRoomMember().lock()) {
|
||||||
// Prevent the user from trying to join a room while they are already joining.
|
// Prevent the user from trying to join a room while they are already joining.
|
||||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||||
return;
|
return;
|
||||||
|
@ -184,7 +186,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||||
proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString();
|
proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString();
|
||||||
|
|
||||||
// attempt to connect in a different thread
|
// attempt to connect in a different thread
|
||||||
QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid, this] {
|
QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid] {
|
||||||
std::string token;
|
std::string token;
|
||||||
#ifdef ENABLE_WEB_SERVICE
|
#ifdef ENABLE_WEB_SERVICE
|
||||||
if (!Settings::values.yuzu_username.GetValue().empty() &&
|
if (!Settings::values.yuzu_username.GetValue().empty() &&
|
||||||
|
@ -200,7 +202,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password,
|
room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password,
|
||||||
token);
|
token);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -102,7 +105,6 @@ private:
|
||||||
QFutureWatcher<void>* watcher;
|
QFutureWatcher<void>* watcher;
|
||||||
Validation validation;
|
Validation validation;
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QStandardItem>
|
#include <QStandardItem>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
|
@ -16,13 +19,13 @@ enum {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent)
|
ModerationDialog::ModerationDialog(QWidget* parent)
|
||||||
: QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()), room_network{room_network_} {
|
: QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
qRegisterMetaType<Network::Room::BanList>();
|
qRegisterMetaType<Network::Room::BanList>();
|
||||||
|
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
callback_handle_status_message = member->BindOnStatusMessageReceived(
|
callback_handle_status_message = member->BindOnStatusMessageReceived(
|
||||||
[this](const Network::StatusMessageEntry& status_message) {
|
[this](const Network::StatusMessageEntry& status_message) {
|
||||||
emit StatusMessageReceived(status_message);
|
emit StatusMessageReceived(status_message);
|
||||||
|
@ -55,20 +58,20 @@ ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget*
|
||||||
|
|
||||||
ModerationDialog::~ModerationDialog() {
|
ModerationDialog::~ModerationDialog() {
|
||||||
if (callback_handle_status_message) {
|
if (callback_handle_status_message) {
|
||||||
if (auto room = room_network.GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
room->Unbind(callback_handle_status_message);
|
room->Unbind(callback_handle_status_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback_handle_ban_list) {
|
if (callback_handle_ban_list) {
|
||||||
if (auto room = room_network.GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
room->Unbind(callback_handle_ban_list);
|
room->Unbind(callback_handle_ban_list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModerationDialog::LoadBanList() {
|
void ModerationDialog::LoadBanList() {
|
||||||
if (auto room = room_network.GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
ui->refresh->setEnabled(false);
|
ui->refresh->setEnabled(false);
|
||||||
ui->refresh->setText(tr("Refreshing"));
|
ui->refresh->setText(tr("Refreshing"));
|
||||||
ui->unban->setEnabled(false);
|
ui->unban->setEnabled(false);
|
||||||
|
@ -97,7 +100,7 @@ void ModerationDialog::PopulateBanList(const Network::Room::BanList& ban_list) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModerationDialog::SendUnbanRequest(const QString& subject) {
|
void ModerationDialog::SendUnbanRequest(const QString& subject) {
|
||||||
if (auto room = room_network.GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
room->SendModerationRequest(Network::IdModUnban, subject.toStdString());
|
room->SendModerationRequest(Network::IdModUnban, subject.toStdString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -19,7 +22,7 @@ class ModerationDialog : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent = nullptr);
|
explicit ModerationDialog(QWidget* parent = nullptr);
|
||||||
~ModerationDialog();
|
~ModerationDialog();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -36,8 +39,6 @@ private:
|
||||||
QStandardItemModel* model;
|
QStandardItemModel* model;
|
||||||
Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message;
|
Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message;
|
||||||
Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list;
|
Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list;
|
||||||
|
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Network::Room::BanList);
|
Q_DECLARE_METATYPE(Network::Room::BanList);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
@ -22,8 +25,8 @@
|
||||||
MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_,
|
MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_,
|
||||||
QAction* leave_room_, QAction* show_room_, Core::System& system_)
|
QAction* leave_room_, QAction* show_room_, Core::System& system_)
|
||||||
: QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_),
|
: QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_),
|
||||||
show_room(show_room_), system{system_}, room_network{system.GetRoomNetwork()} {
|
show_room(show_room_), system{system_} {
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
// register the network structs to use in slots and signals
|
// register the network structs to use in slots and signals
|
||||||
state_callback_handle = member->BindOnStateChanged(
|
state_callback_handle = member->BindOnStateChanged(
|
||||||
[this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); });
|
[this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); });
|
||||||
|
@ -37,7 +40,7 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
|
||||||
qRegisterMetaType<Network::RoomMember::State>();
|
qRegisterMetaType<Network::RoomMember::State>();
|
||||||
qRegisterMetaType<Network::RoomMember::Error>();
|
qRegisterMetaType<Network::RoomMember::Error>();
|
||||||
qRegisterMetaType<WebService::WebResult>();
|
qRegisterMetaType<WebService::WebResult>();
|
||||||
announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>(room_network);
|
announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>();
|
||||||
announce_multiplayer_session->BindErrorCallback(
|
announce_multiplayer_session->BindErrorCallback(
|
||||||
[this](const WebService::WebResult& result) { emit AnnounceFailed(result); });
|
[this](const WebService::WebResult& result) { emit AnnounceFailed(result); });
|
||||||
connect(this, &MultiplayerState::AnnounceFailed, this, &MultiplayerState::OnAnnounceFailed);
|
connect(this, &MultiplayerState::AnnounceFailed, this, &MultiplayerState::OnAnnounceFailed);
|
||||||
|
@ -62,13 +65,13 @@ MultiplayerState::~MultiplayerState() = default;
|
||||||
|
|
||||||
void MultiplayerState::Close() {
|
void MultiplayerState::Close() {
|
||||||
if (state_callback_handle) {
|
if (state_callback_handle) {
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->Unbind(state_callback_handle);
|
member->Unbind(state_callback_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error_callback_handle) {
|
if (error_callback_handle) {
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->Unbind(error_callback_handle);
|
member->Unbind(error_callback_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,9 +259,9 @@ void MultiplayerState::OnCreateRoom() {
|
||||||
bool MultiplayerState::OnCloseRoom() {
|
bool MultiplayerState::OnCloseRoom() {
|
||||||
if (!NetworkMessage::WarnCloseRoom())
|
if (!NetworkMessage::WarnCloseRoom())
|
||||||
return false;
|
return false;
|
||||||
if (auto room = room_network.GetRoom().lock()) {
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
// if you are in a room, leave it
|
// if you are in a room, leave it
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->Leave();
|
member->Leave();
|
||||||
LOG_DEBUG(Frontend, "Left the room (as a client)");
|
LOG_DEBUG(Frontend, "Left the room (as a client)");
|
||||||
}
|
}
|
||||||
|
@ -292,10 +295,10 @@ void MultiplayerState::HideNotification() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiplayerState::OnOpenNetworkRoom() {
|
void MultiplayerState::OnOpenNetworkRoom() {
|
||||||
if (auto member = room_network.GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
if (member->IsConnected()) {
|
if (member->IsConnected()) {
|
||||||
if (client_room == nullptr) {
|
if (client_room == nullptr) {
|
||||||
client_room = new ClientRoomWindow(this, room_network);
|
client_room = new ClientRoomWindow(this);
|
||||||
connect(client_room, &ClientRoomWindow::ShowNotification, this,
|
connect(client_room, &ClientRoomWindow::ShowNotification, this,
|
||||||
&MultiplayerState::ShowNotification);
|
&MultiplayerState::ShowNotification);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
@ -105,7 +108,6 @@ private:
|
||||||
|
|
||||||
bool show_notification = false;
|
bool show_notification = false;
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
Network::RoomNetwork& room_network;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(WebService::WebResult);
|
Q_DECLARE_METATYPE(WebService::WebResult);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -404,7 +407,7 @@ int main(int argc, char** argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_multiplayer) {
|
if (use_multiplayer) {
|
||||||
if (auto member = system.GetRoomNetwork().GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->BindOnChatMessageReceived(OnMessageReceived);
|
member->BindOnChatMessageReceived(OnMessageReceived);
|
||||||
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
||||||
member->BindOnStateChanged(OnStateChanged);
|
member->BindOnStateChanged(OnStateChanged);
|
||||||
|
|
|
@ -38,8 +38,7 @@
|
||||||
"description": "Enable web services (telemetry, etc.)",
|
"description": "Enable web services (telemetry, etc.)",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"name": "openssl",
|
"name": "openssl"
|
||||||
"platform": "windows"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue