From 7412586f5b62de890f9e186b3fcb28116ec18826 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sun, 12 May 2024 01:34:34 +0200 Subject: [PATCH] Add Android support --- .../java/org/citra/citra_emu/NativeLibrary.kt | 66 +++++++++++++++---- .../fragments/HomeSettingsFragment.kt | 41 ++++++++++++ src/android/app/src/main/jni/native.cpp | 10 ++- .../app/src/main/res/drawable/ic_network.xml | 9 +++ .../app/src/main/res/values-es/strings.xml | 7 ++ .../app/src/main/res/values/strings.xml | 7 ++ 6 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 src/android/app/src/main/res/drawable/ic_network.xml diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index bfbe658f8..84a62d898 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -183,13 +183,13 @@ object NativeLibrary { private var coreErrorAlertResult = false private val coreErrorAlertLock = Object() - private fun onCoreErrorImpl(title: String, message: String) { + private fun onCoreErrorImpl(title: String, message: String, canContinue: Boolean) { val emulationActivity = sEmulationActivity.get() if (emulationActivity == null) { Log.error("[NativeLibrary] EmulationActivity not present") return } - val fragment = CoreErrorDialogFragment.newInstance(title, message) + val fragment = CoreErrorDialogFragment.newInstance(title, message, canContinue) fragment.show(emulationActivity.supportFragmentManager, CoreErrorDialogFragment.TAG) } @@ -207,6 +207,7 @@ object NativeLibrary { } val title: String val message: String + val canContinue: Boolean when (error) { CoreError.ErrorSystemFiles -> { title = emulationActivity.getString(R.string.system_archive_not_found) @@ -214,16 +215,25 @@ object NativeLibrary { R.string.system_archive_not_found_message, details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } ) + canContinue = true } CoreError.ErrorSavestate -> { title = emulationActivity.getString(R.string.save_load_error) message = details + canContinue = true + } + + CoreError.ErrorArticDisconnected -> { + title = emulationActivity.getString(R.string.artic_base) + message = emulationActivity.getString(R.string.artic_server_comm_error) + canContinue = false } CoreError.ErrorUnknown -> { title = emulationActivity.getString(R.string.fatal_error) message = emulationActivity.getString(R.string.fatal_error_message) + canContinue = true } else -> { @@ -232,7 +242,7 @@ object NativeLibrary { } // Show the AlertDialog on the main thread. - emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) }) + emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message, canContinue) }) // Wait for the lock to notify that it is complete. synchronized(coreErrorAlertLock) { @@ -346,6 +356,11 @@ object NativeLibrary { return } + if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) { + emulationActivity.finish() + return + } + emulationActivity.runOnUiThread { EmulationErrorDialogFragment.newInstance(resultCode).showNow( emulationActivity.supportFragmentManager, @@ -361,16 +376,23 @@ object NativeLibrary { emulationActivity = requireActivity() as EmulationActivity var captionId = R.string.loader_error_invalid_format - if (requireArguments().getInt(RESULT_CODE) == ErrorLoader_ErrorEncrypted) { + val result = requireArguments().getInt(RESULT_CODE) + if (result == ErrorLoader_ErrorEncrypted) { captionId = R.string.loader_error_encrypted } + if (result == ErrorArticDisconnected) { + captionId = R.string.artic_base + } val alert = MaterialAlertDialogBuilder(requireContext()) .setTitle(captionId) .setMessage( Html.fromHtml( - CitraApplication.appContext.resources.getString(R.string.redump_games), - Html.FROM_HTML_MODE_LEGACY + if (result == ErrorArticDisconnected) + CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error) + else + CitraApplication.appContext.resources.getString(R.string.redump_games), + Html.FROM_HTML_MODE_LEGACY ) ) .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> @@ -398,7 +420,10 @@ object NativeLibrary { const val ErrorLoader = 4 const val ErrorLoader_ErrorEncrypted = 5 const val ErrorLoader_ErrorInvalidFormat = 6 - const val ErrorSystemFiles = 7 + const val ErrorLoader_ErrorGBATitle = 7 + const val ErrorSystemFiles = 8 + const val ErrorSavestate = 9 + const val ErrorArticDisconnected = 10 const val ShutdownRequested = 11 const val ErrorUnknown = 12 @@ -619,6 +644,7 @@ object NativeLibrary { enum class CoreError { ErrorSystemFiles, ErrorSavestate, + ErrorArticDisconnected, ErrorUnknown } @@ -633,23 +659,33 @@ object NativeLibrary { } class CoreErrorDialogFragment : DialogFragment() { + private var userChosen = false override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val title = requireArguments().getString(TITLE) val message = requireArguments().getString(MESSAGE) - return MaterialAlertDialogBuilder(requireContext()) + val canContinue = requireArguments().getBoolean(CAN_CONTINUE) + val dialog = MaterialAlertDialogBuilder(requireContext()) .setTitle(title) .setMessage(message) - .setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int -> + if (canContinue) { + dialog.setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int -> coreErrorAlertResult = true + userChosen = true } - .setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> - coreErrorAlertResult = false - }.show() + } + dialog.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> + coreErrorAlertResult = false + userChosen = true + } + return dialog.show() } override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog) - coreErrorAlertResult = true + val canContinue = requireArguments().getBoolean(CAN_CONTINUE) + if (!userChosen) { + coreErrorAlertResult = canContinue + } synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() } } @@ -658,12 +694,14 @@ object NativeLibrary { const val TITLE = "title" const val MESSAGE = "message" + const val CAN_CONTINUE = "canContinue" - fun newInstance(title: String, message: String): CoreErrorDialogFragment { + fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment { val frag = CoreErrorDialogFragment() val args = Bundle() args.putString(TITLE, title) args.putString(MESSAGE, message) + args.putBoolean(CAN_CONTINUE, canContinue) frag.arguments = args return frag } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt index b19fdd7b9..091616054 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt @@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import androidx.core.widget.doOnTextChanged import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -23,14 +24,19 @@ import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.transition.MaterialSharedAxis import org.citra.citra_emu.CitraApplication +import org.citra.citra_emu.HomeNavigationDirections import org.citra.citra_emu.R import org.citra.citra_emu.adapters.HomeSettingAdapter +import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding import org.citra.citra_emu.features.settings.model.Settings +import org.citra.citra_emu.features.settings.model.StringSetting import org.citra.citra_emu.features.settings.ui.SettingsActivity import org.citra.citra_emu.features.settings.utils.SettingsFile +import org.citra.citra_emu.model.Game import org.citra.citra_emu.model.HomeSetting import org.citra.citra_emu.ui.main.MainActivity import org.citra.citra_emu.utils.GameHelper @@ -76,6 +82,41 @@ class HomeSettingsFragment : Fragment() { R.drawable.ic_settings, { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } ), + HomeSetting( + R.string.artic_base_connect, + R.string.artic_base_connect_description, + R.drawable.ic_network, + { + val inflater = LayoutInflater.from(context) + val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater) + var textInputValue: String = "" + + inputBinding.editTextInput.setText(textInputValue) + inputBinding.editTextInput.doOnTextChanged { text, _, _, _ -> + textInputValue = text.toString() + } + + val dialog = context?.let { + MaterialAlertDialogBuilder(it) + .setView(inputBinding.root) + .setTitle(getString(R.string.artic_base_enter_address)) + .setPositiveButton(android.R.string.ok) { _, _ -> + if (textInputValue.isNotEmpty()) { + val menu = Game( + title = getString(R.string.artic_base), + path = "articbase://$textInputValue", + filename = "" + ) + val action = + HomeNavigationDirections.actionGlobalEmulationActivity(menu) + binding.root.findNavController().navigate(action) + } + } + .setNegativeButton(android.R.string.cancel) {_, _ -> } + .show() + } + } + ), HomeSetting( R.string.system_files, R.string.system_files_description, diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 13dbc0f2c..d84562e1a 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -82,6 +82,7 @@ static jobject ToJavaCoreError(Core::System::ResultStatus result) { static const std::map CoreErrorNameMap{ {Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"}, {Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"}, + {Core::System::ResultStatus::ErrorArticDisconnected, "ErrorArticDisconnected"}, {Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"}, }; @@ -178,6 +179,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { auto app_loader = Loader::GetLoader(filepath); if (app_loader) { app_loader->ReadProgramId(program_id); + system.RegisterAppLoaderEarly(app_loader); GameSettings::LoadOverrides(program_id); } system.ApplySettings(); @@ -231,6 +233,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { InputManager::NDKMotionHandler()->DisableSensors(); if (!HandleCoreError(result, system.GetStatusDetails())) { // Frontend requests us to abort + // If the error was an Artic disconnect, return shutdown request. + if (result == Core::System::ResultStatus::ErrorArticDisconnected) { + return Core::System::ResultStatus::ShutdownRequested; + } return result; } InputManager::NDKMotionHandler()->EnableSensors(); @@ -314,7 +320,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en if (stop_run || pause_emulation) { return; } - window->TryPresenting(); + if (window) { + window->TryPresenting(); + } } void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver( diff --git a/src/android/app/src/main/res/drawable/ic_network.xml b/src/android/app/src/main/res/drawable/ic_network.xml new file mode 100644 index 000000000..91559b988 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_network.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 7755c81cc..7c7ba9e53 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -657,4 +657,11 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Noviembre Diciembre + + Fallo de comunicación con el servidor Artic Base. La emulación se detendrá. + Artic Base + Conectar con Artic Base + Conectar con una consola real que esté ejecutando un servidor Artic Base + Introduce la dirección del servidor Artic Base + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 781e78da1..6614e97f1 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -683,4 +683,11 @@ November December + + Failed to communicate with the Artic Base server. Emulation will stop. + Artic Base + Connect to a real console that is running an Artic Base server + Connect to Artic Base + Enter Artic Base server address +