From 12343ebf86350fdf7436bb6aa9ac37bacedf26c4 Mon Sep 17 00:00:00 2001 From: Tom Pratt Date: Wed, 8 Apr 2026 15:20:19 +0100 Subject: [PATCH] Store netplay BootSessionData and use it to run the netplay game --- .../org/dolphinemu/dolphinemu/NativeLibrary.kt | 6 ++++++ .../dolphinemu/features/netplay/Netplay.kt | 17 +++++++++++++++++ .../features/netplay/ui/NetplaySetupActivity.kt | 9 +++++++++ .../dolphinemu/fragments/EmulationFragment.kt | 7 +++++++ Source/Android/jni/AndroidCommon/IDCache.cpp | 15 ++++++++++++++- Source/Android/jni/AndroidCommon/IDCache.h | 2 ++ Source/Android/jni/MainAndroid.cpp | 17 +++++++++++++++++ .../Android/jni/NetPlay/NetPlayUICallbacks.cpp | 13 ++++++++++--- 8 files changed, 82 insertions(+), 4 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt index a4d9bac35a..c5224ec3eb 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt @@ -342,6 +342,12 @@ object NativeLibrary { @JvmStatic external fun RunSystemMenu() + /** + * Begins emulation for a netplay session, using the BootSessionData provided by the host. + */ + @JvmStatic + external fun RunNetPlay(paths: Array, riivolution: Boolean) + @JvmStatic external fun ChangeDisc(path: String) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/Netplay.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/Netplay.kt index aaaca2b4fb..6f8532a15c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/Netplay.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/Netplay.kt @@ -4,12 +4,23 @@ package org.dolphinemu.dolphinemu.features.netplay import androidx.annotation.Keep +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionType object Netplay { @Keep private var netPlayClientPointer: Long = 0 + @Keep + private var bootSessionDataPointer: Long = 0 + + val isLaunching: Boolean + get() = bootSessionDataPointer != 0L + + private val _launchGame = Channel(Channel.CONFLATED) + val launchGame = _launchGame.receiveAsFlow() + @JvmStatic external fun getNickname(): String @@ -103,4 +114,10 @@ object Netplay { @JvmStatic private external fun Join(): Long + + @JvmStatic + fun onBootGame(gameFilePath: String, bootSessionDataPointer: Long) { + this.bootSessionDataPointer = bootSessionDataPointer + _launchGame.trySend(gameFilePath) + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt index 71d83ddf81..ca1a892901 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt @@ -46,7 +46,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.dolphinemu.dolphinemu.R +import org.dolphinemu.dolphinemu.activities.EmulationActivity +import org.dolphinemu.dolphinemu.features.netplay.Netplay import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionRole import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionType import org.dolphinemu.dolphinemu.features.netplay.model.NetplaySetupViewModel @@ -64,6 +69,10 @@ class NetplaySetupActivity : AppCompatActivity(), ThemeProvider { enableEdgeToEdge() super.onCreate(savedInstanceState) + Netplay.launchGame + .onEach { EmulationActivity.launch(this, it, false) } + .launchIn(lifecycleScope) + viewModel = ViewModelProvider(this)[NetplaySetupViewModel::class.java] setContent { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt index e4698ba43c..4ee4aee8aa 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt @@ -13,6 +13,7 @@ import androidx.fragment.app.Fragment import org.dolphinemu.dolphinemu.NativeLibrary import org.dolphinemu.dolphinemu.activities.EmulationActivity import org.dolphinemu.dolphinemu.databinding.FragmentEmulationBinding +import org.dolphinemu.dolphinemu.features.netplay.Netplay import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting import org.dolphinemu.dolphinemu.features.settings.model.Settings import org.dolphinemu.dolphinemu.overlay.InputOverlay @@ -214,6 +215,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { if (launchSystemMenu) { Log.debug("[EmulationFragment] Starting emulation thread for the Wii Menu.") NativeLibrary.RunSystemMenu() + } else if (Netplay.isLaunching) { + Log.debug("[EmulationFragment] Starting emulation thread for Netplay.") + val paths = requireNotNull(gamePaths) { + "Cannot start emulation without any game paths" + } + NativeLibrary.RunNetPlay(paths, riivolution) } else { Log.debug("[EmulationFragment] Starting emulation thread.") val paths = requireNotNull(gamePaths) { diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 1cd6e257e6..9c41869f5b 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -30,7 +30,8 @@ static jfieldID s_game_file_cache_manager_instance; static jclass s_netplay_class; static jfieldID s_net_play_client_pointer; -static jmethodID s_netplay_on_msg_start_game; +static jfieldID s_netplay_boot_session_data_pointer; +static jmethodID s_netplay_on_boot_game; static jclass s_analytics_class; static jmethodID s_get_analytics_value; @@ -247,6 +248,16 @@ jfieldID GetNetPlayClientPointer() return s_net_play_client_pointer; } +jfieldID GetNetplayBootSessionDataPointer() +{ + return s_netplay_boot_session_data_pointer; +} + +jmethodID GetNetplayOnBootGame() +{ + return s_netplay_on_boot_game; +} + jclass GetPairClass() { return s_pair_class; @@ -655,6 +666,8 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) env->FindClass("org/dolphinemu/dolphinemu/features/netplay/Netplay"); s_netplay_class = reinterpret_cast(env->NewGlobalRef(netplay_class)); s_net_play_client_pointer = env->GetStaticFieldID(netplay_class, "netPlayClientPointer", "J"); + s_netplay_boot_session_data_pointer = env->GetStaticFieldID(netplay_class, "bootSessionDataPointer", "J"); + s_netplay_on_boot_game = env->GetStaticMethodID(netplay_class, "onBootGame", "(Ljava/lang/String;J)V"); env->DeleteLocalRef(netplay_class); const jclass analytics_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Analytics"); diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index e13646eb5a..1879ff2afb 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -33,6 +33,8 @@ jfieldID GetGameFileCacheManagerInstance(); jclass GetNetplayClass(); jfieldID GetNetPlayClientPointer(); +jfieldID GetNetplayBootSessionDataPointer(); +jmethodID GetNetplayOnBootGame(); jclass GetPairClass(); jmethodID GetPairConstructor(); diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 3855cb4c18..b638bf260a 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -35,6 +35,7 @@ #include "Core/AchievementManager.h" #include "Core/Boot/Boot.h" +#include "jni/NetPlay/NetPlayUICallbacks.h" #include "Core/BootManager.h" #include "Core/CommonTitles.h" #include "Core/ConfigLoaders/GameConfigLoader.h" @@ -614,6 +615,22 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2ZLjava_la BootSessionData(GetJString(env, jSavestate), delete_state)); } +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RunNetPlay( + JNIEnv* env, jclass, jobjectArray jPaths, jboolean jRiivolution) +{ + auto boot_session_data = std::unique_ptr(reinterpret_cast( + env->GetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetplayBootSessionDataPointer()))); + if (!boot_session_data) + { + env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetDisplayToastMsg(), + ToJString(env, "Netplay: no boot session data"), JNI_TRUE); + env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetFinishEmulationActivity()); + return; + } + env->SetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetplayBootSessionDataPointer(), 0); + Run(env, JStringArrayToVector(env, jPaths), jRiivolution, std::move(*boot_session_data)); +} + JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RunSystemMenu(JNIEnv* env, jclass) { diff --git a/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp b/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp index 31bc977e2a..27e7d36ee6 100644 --- a/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp +++ b/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp @@ -1,8 +1,10 @@ // Copyright 2003 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "jni/NetPlay/NetPlayUICallbacks.h" - +#include "UICommon/GameFile.h" +#include "NetPlayUICallbacks.h" +#include "Core/Boot/Boot.h" +#include "jni/AndroidCommon/AndroidCommon.h" #include "jni/AndroidCommon/IDCache.h" namespace NetPlay { @@ -14,7 +16,12 @@ NetPlayUICallbacks::NetPlayUICallbacks(std::vector) {} +void NetPlayUICallbacks::BootGame(const std::string& filename, std::unique_ptr boot_session_data) { + JNIEnv* env = IDCache::GetEnvForThread(); + env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnBootGame(), + ToJString(env, filename), reinterpret_cast(boot_session_data.release())); +} + void NetPlayUICallbacks::StopGame() {} bool NetPlayUICallbacks::IsHosting() const { return false; } void NetPlayUICallbacks::Update() {}