mirror of
https://github.com/izzy2lost/Super3.git
synced 2026-07-05 15:18:38 -07:00
add networking
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user