add networking

This commit is contained in:
izzy2lost
2026-02-13 01:42:17 -05:00
parent eff36ef873
commit 810918c5ff
7 changed files with 257 additions and 10 deletions
+2 -2
View File
@@ -29,8 +29,8 @@ android {
minSdk = 26
targetSdk = 36
versionCode = 20
versionName = "1.1.9"
versionCode = 21
versionName = "1.2.0"
ndk {
abiFilters += listOf("arm64-v8a")
@@ -18,8 +18,14 @@ SoundVolume = 100
MusicVolume = 150
LegacySoundDSP = 0
ForceFeedback = 0
; Net board TCP link settings (for Android<->PC or Android<->Android).
; Cab A default: PortIn=1970 PortOut=1971
; Cab B default: PortIn=1971 PortOut=1970 and AddressOut=<Cab A IP>
Network = 0
SimulateNet = 0
SimulateNet = 1
PortIn = 1970
PortOut = 1971
AddressOut = "127.0.0.1"
Outputs = none
LegacyReal3DTiming = 1
+29 -3
View File
@@ -56,7 +56,7 @@ set(EXCLUDE_REGEX
"/Src/Model3/53C810Disasm\\.cpp$|"
"/Src/OSD/.*\\.(c|cpp)$|"
"/Src/Graphics/.*\\.(c|cpp)$|"
"/Src/Network/.*\\.(c|cpp)$|"
"/Src/Network/TCPSendAsync\\.cpp$|"
"/Src/Sound/SCSPLFO\\.cpp$|"
"/Src/Pkgs/GL/.*\\.(c|cpp|h)$|"
"/Src/Pkgs/glew\\.c$|"
@@ -77,7 +77,7 @@ foreach(src ${SUPER3_SOURCES})
OR src MATCHES "/Src/Model3/53C810Disasm\\.cpp$"
OR src MATCHES "/Src/OSD/"
OR src MATCHES "/Src/Graphics/"
OR src MATCHES "/Src/Network/"
OR src MATCHES "/Src/Network/TCPSendAsync\\.cpp$"
OR src MATCHES "/Src/Sound/SCSPLFO\\.cpp$"
OR src MATCHES "/Src/Util/Test_"
OR src MATCHES "/Src/Pkgs/GL/"
@@ -95,6 +95,8 @@ endif()
# latest release tarball.
option(SUPER3_FETCH_SDL2 "Download and build SDL2 for Android" ON)
set(SDL2_VERSION "2.32.10")
option(SUPER3_FETCH_SDL2_NET "Download and build SDL2_net for Android" ON)
set(SDL2_NET_VERSION "2.2.0")
if(DEFINED SDL2_LOCAL_DIR)
message(STATUS "Using local SDL2 at ${SDL2_LOCAL_DIR}")
@@ -107,6 +109,29 @@ elseif(SUPER3_FETCH_SDL2)
FetchContent_MakeAvailable(SDL2)
endif()
if(DEFINED SDL2_NET_LOCAL_DIR)
message(STATUS "Using local SDL2_net at ${SDL2_NET_LOCAL_DIR}")
add_subdirectory("${SDL2_NET_LOCAL_DIR}" "${CMAKE_BINARY_DIR}/sdl2-net-local")
elseif(SUPER3_FETCH_SDL2_NET)
FetchContent_Declare(
SDL2_NET
URL "https://github.com/libsdl-org/SDL_net/archive/refs/tags/release-${SDL2_NET_VERSION}.zip"
)
FetchContent_MakeAvailable(SDL2_NET)
endif()
set(SDL2_NET_TARGET "")
if(TARGET SDL2_net::SDL2_net)
set(SDL2_NET_TARGET SDL2_net::SDL2_net)
elseif(TARGET SDL2_net::SDL2_net-static)
set(SDL2_NET_TARGET SDL2_net::SDL2_net-static)
elseif(TARGET SDL2_net)
set(SDL2_NET_TARGET SDL2_net)
endif()
if(SDL2_NET_TARGET STREQUAL "")
message(FATAL_ERROR "SDL2_net target not found. Enable SUPER3_FETCH_SDL2_NET or provide SDL2_NET_LOCAL_DIR.")
endif()
add_library(super3 SHARED
native-lib.cpp
android_shim.cpp
@@ -132,7 +157,7 @@ add_library(super3 SHARED
${M68K_GENERATED_SOURCES}
)
target_compile_definitions(super3 PRIVATE ANDROID __ANDROID__)
target_compile_definitions(super3 PRIVATE ANDROID __ANDROID__ NET_BOARD)
target_include_directories(super3 PRIVATE
"${REPO_ROOT}/Src"
@@ -154,4 +179,5 @@ target_link_libraries(super3 PRIVATE
${egl-lib}
${glesv3-lib}
SDL2::SDL2 # present when SUPER3_FETCH_SDL2 is ON
${SDL2_NET_TARGET}
)
+53 -4
View File
@@ -336,7 +336,10 @@ struct Super3Host {
config.Set("Outputs", "none");
config.Set("ForceFeedback", false);
config.Set("Network", false);
config.Set("SimulateNet", false);
config.Set("SimulateNet", true);
config.Set("PortIn", unsigned(1970));
config.Set("PortOut", unsigned(1971));
config.Set("AddressOut", "127.0.0.1");
config.Set("XResolution", "496");
config.Set("YResolution", "384");
config.Set("Throttle", true);
@@ -359,7 +362,7 @@ struct Super3Host {
config.Set("InputJoyRight", "KEY_RIGHT");
config.Set("InputSteeringLeft", "KEY_LEFT");
config.Set("InputSteeringRight", "KEY_RIGHT");
config.Set("InputAccelerator", "KEY_W");
config.Set("InputAccelerator", "KEY_W");
config.Set("InputBrake", "KEY_S");
config.Set("UISaveState", "KEY_F5");
config.Set("UIChangeSlot", "KEY_F6");
@@ -401,8 +404,6 @@ struct Super3Host {
config.Set("InputSystem", "sdl");
config.Set("Outputs", "none");
config.Set("ForceFeedback", false);
config.Set("Network", false);
config.Set("SimulateNet", false);
// Ensure required nodes exist / sane.
config.Set("PowerPCFrequency", "50");
@@ -431,6 +432,41 @@ struct Super3Host {
config.Set("YResolution", std::to_string(yRes));
}
// Ensure netboard keys exist and values are sane. The core expects these
// keys to exist when NET_BOARD is enabled.
{
bool networkEnabled = false;
bool simulateNet = true;
try { networkEnabled = config["Network"].ValueAsDefault<bool>(false); } catch (...) { networkEnabled = false; }
try { simulateNet = config["SimulateNet"].ValueAsDefault<bool>(true); } catch (...) { simulateNet = true; }
unsigned portIn = 1970;
unsigned portOut = 1971;
try { portIn = config["PortIn"].ValueAsDefault<unsigned>(1970); } catch (...) { portIn = 1970; }
try { portOut = config["PortOut"].ValueAsDefault<unsigned>(1971); } catch (...) { portOut = 1971; }
if (portIn == 0 || portIn > 65535) portIn = 1970;
if (portOut == 0 || portOut > 65535) portOut = 1971;
std::string addressOut = config["AddressOut"].ValueAsDefault<std::string>("127.0.0.1");
if (addressOut.size() >= 2 && addressOut.front() == '"' && addressOut.back() == '"')
addressOut = addressOut.substr(1, addressOut.size() - 2);
if (addressOut.empty())
addressOut = "127.0.0.1";
config.Set("Network", networkEnabled);
config.Set("SimulateNet", simulateNet);
config.Set("PortIn", portIn);
config.Set("PortOut", portOut);
config.Set("AddressOut", addressOut);
// Full netboard emulation remains thread-sensitive. Keep the Android
// default (simulated netboard) for easier, stable online setup.
if (networkEnabled && !simulateNet) {
config.Set("MultiThreaded", false);
config.Set("GPUMultiThreaded", false);
}
}
// Ensure touch zones always have a working keyboard mapping even if the user remaps to joystick-only.
ensureKeyboardFallback("InputCoin1", "KEY_5");
ensureKeyboardFallback("InputStart1", "KEY_1");
@@ -573,6 +609,19 @@ struct Super3Host {
ApplyAndroidHardOverrides();
ApplyGameHardOverrides(game.name);
ApplyAndroidTileBlit();
if (config["Network"].ValueAsDefault<bool>(false)) {
const bool simulate = config["SimulateNet"].ValueAsDefault<bool>(true);
const unsigned portIn = config["PortIn"].ValueAsDefault<unsigned>(1970);
const unsigned portOut = config["PortOut"].ValueAsDefault<unsigned>(1971);
const std::string addressOut = config["AddressOut"].ValueAsDefault<std::string>("127.0.0.1");
SDL_Log("NetBoard enabled (%s): PortIn=%u PortOut=%u AddressOut=%s",
simulate ? "simulated" : "emulated",
portIn,
portOut,
addressOut.c_str());
} else {
SDL_Log("NetBoard disabled.");
}
// Initialize inputs before attaching to model
if (!inputs.Initialize()) {
@@ -23,6 +23,11 @@ class IniSettingsActivity : AppCompatActivity() {
private lateinit var btnEmulateSound: MaterialButton
private lateinit var btnSoundVolume: MaterialButton
private lateinit var btnMusicVolume: MaterialButton
private lateinit var btnNetworkEnabled: MaterialButton
private lateinit var btnSimulateNet: MaterialButton
private lateinit var btnAddressOut: MaterialButton
private lateinit var btnPortIn: MaterialButton
private lateinit var btnPortOut: MaterialButton
private var iniDoc: DocumentFile? = null
private var iniLines: MutableList<String> = mutableListOf()
@@ -41,6 +46,11 @@ class IniSettingsActivity : AppCompatActivity() {
btnEmulateSound = findViewById(R.id.btn_emulate_sound)
btnSoundVolume = findViewById(R.id.btn_sound_volume)
btnMusicVolume = findViewById(R.id.btn_music_volume)
btnNetworkEnabled = findViewById(R.id.btn_network_enabled)
btnSimulateNet = findViewById(R.id.btn_simulate_net)
btnAddressOut = findViewById(R.id.btn_address_out)
btnPortIn = findViewById(R.id.btn_port_in)
btnPortOut = findViewById(R.id.btn_port_out)
toolbar.setNavigationOnClickListener { finish() }
onBackPressedDispatcher.addCallback(this) { finish() }
@@ -125,6 +135,55 @@ class IniSettingsActivity : AppCompatActivity() {
max = 200,
)
}
btnNetworkEnabled.setOnClickListener {
val enabled = btnNetworkEnabled.isChecked
applyUpdate(
updates = mapOf("Network" to if (enabled) "1" else "0"),
onApplied = { btnNetworkEnabled.isChecked = enabled },
onFailed = { btnNetworkEnabled.isChecked = !enabled },
)
}
btnSimulateNet.setOnClickListener {
val enabled = btnSimulateNet.isChecked
applyUpdate(
updates = mapOf("SimulateNet" to if (enabled) "1" else "0"),
onApplied = { btnSimulateNet.isChecked = enabled },
onFailed = { btnSimulateNet.isChecked = !enabled },
)
}
btnAddressOut.setOnClickListener {
val current = readIniStringUnquoted("AddressOut").orEmpty().ifBlank { "127.0.0.1" }
showTextDialog(
title = "Target address (AddressOut)",
key = "AddressOut",
current = current,
)
}
btnPortIn.setOnClickListener {
val current = readIniInt("PortIn") ?: 1970
showNumberDialog(
title = "Listen port (PortIn)",
key = "PortIn",
current = current,
min = 1,
max = 65535,
)
}
btnPortOut.setOnClickListener {
val current = readIniInt("PortOut") ?: 1971
showNumberDialog(
title = "Send port (PortOut)",
key = "PortOut",
current = current,
min = 1,
max = 65535,
)
}
}
private fun loadIni(treeUri: Uri) {
@@ -160,6 +219,18 @@ class IniSettingsActivity : AppCompatActivity() {
val music = readIniInt("MusicVolume") ?: 150
btnSoundVolume.text = "Sound volume: $sound"
btnMusicVolume.text = "Music volume: $music"
val networkEnabled = readIniBool("Network") ?: false
val simulateNet = readIniBool("SimulateNet") ?: true
val portIn = readIniInt("PortIn")?.coerceIn(1, 65535) ?: 1970
val portOut = readIniInt("PortOut")?.coerceIn(1, 65535) ?: 1971
val addressOut = readIniStringUnquoted("AddressOut").orEmpty().ifBlank { "127.0.0.1" }
btnNetworkEnabled.isChecked = networkEnabled
btnSimulateNet.isChecked = simulateNet
btnAddressOut.text = "Address out: $addressOut"
btnPortIn.text = "Listen port (PortIn): $portIn"
btnPortOut.text = "Send port (PortOut): $portOut"
}
private fun showNumberDialog(title: String, key: String, current: Int, min: Int, max: Int) {
@@ -192,6 +263,8 @@ class IniSettingsActivity : AppCompatActivity() {
"PowerPCFrequency" -> btnPpcFrequency.text = "PowerPC frequency: $clamped"
"SoundVolume" -> btnSoundVolume.text = "Sound volume: $clamped"
"MusicVolume" -> btnMusicVolume.text = "Music volume: $clamped"
"PortIn" -> btnPortIn.text = "Listen port (PortIn): $clamped"
"PortOut" -> btnPortOut.text = "Send port (PortOut): $clamped"
}
},
)
@@ -200,6 +273,38 @@ class IniSettingsActivity : AppCompatActivity() {
.show()
}
private fun showTextDialog(title: String, key: String, current: String) {
val view = layoutInflater.inflate(R.layout.dialog_number_input, null)
val inputLayout = view.findViewById<TextInputLayout>(R.id.number_input_layout)
val input = view.findViewById<TextInputEditText>(R.id.number_input)
inputLayout.hint = title
input.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
input.setText(current)
input.setSelection(input.text?.length ?: 0)
MaterialAlertDialogBuilder(this)
.setTitle(title)
.setView(view)
.setPositiveButton("Save") { _, _ ->
val raw = input.text?.toString()?.trim().orEmpty()
val value = raw.trim('"')
if (value.isBlank()) {
Toast.makeText(this, "Address cannot be empty", Toast.LENGTH_SHORT).show()
return@setPositiveButton
}
applyUpdate(
updates = mapOf(key to "\"$value\""),
onApplied = {
if (key == "AddressOut") {
btnAddressOut.text = "Address out: $value"
}
},
)
}
.setNegativeButton("Cancel", null)
.show()
}
private fun applyUpdate(
updates: Map<String, String>,
onApplied: () -> Unit,
@@ -253,6 +358,10 @@ class IniSettingsActivity : AppCompatActivity() {
return null
}
private fun readIniStringUnquoted(key: String): String? {
return readIniString(key)?.trim()?.trim('"')
}
private fun updateIniSection(
lines: List<String>,
section: String,
@@ -345,5 +454,10 @@ class IniSettingsActivity : AppCompatActivity() {
btnEmulateSound.isEnabled = enabled
btnSoundVolume.isEnabled = enabled
btnMusicVolume.isEnabled = enabled
btnNetworkEnabled.isEnabled = enabled
btnSimulateNet.isEnabled = enabled
btnAddressOut.isEnabled = enabled
btnPortIn.isEnabled = enabled
btnPortOut.isEnabled = enabled
}
}
@@ -92,6 +92,7 @@ class Super3Activity : SDLActivity() {
override fun getLibraries(): Array<String> = arrayOf(
"SDL2",
"SDL2_net",
"super3",
)
@@ -128,6 +128,57 @@
android:text="Music volume: 150"
style="?attr/materialButtonElevatedStyle" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/ini_settings_network_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Network"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnSurface" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_network_enabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:checkable="true"
android:text="Enable online play"
style="?attr/materialButtonElevatedStyle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_simulate_net"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:checkable="true"
android:text="Simulated net board (recommended)"
style="?attr/materialButtonElevatedStyle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_address_out"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Address out: 127.0.0.1"
style="?attr/materialButtonElevatedStyle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_port_in"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Listen port (PortIn): 1970"
style="?attr/materialButtonElevatedStyle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_port_out"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Send port (PortOut): 1971"
style="?attr/materialButtonElevatedStyle" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>