Add Android support

This commit is contained in:
PabloMK7 2024-05-12 01:34:34 +02:00
parent 78c48a46e0
commit 7412586f5b
6 changed files with 125 additions and 15 deletions

View file

@ -183,13 +183,13 @@ object NativeLibrary {
private var coreErrorAlertResult = false private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object() private val coreErrorAlertLock = Object()
private fun onCoreErrorImpl(title: String, message: String) { private fun onCoreErrorImpl(title: String, message: String, canContinue: Boolean) {
val emulationActivity = sEmulationActivity.get() val emulationActivity = sEmulationActivity.get()
if (emulationActivity == null) { if (emulationActivity == null) {
Log.error("[NativeLibrary] EmulationActivity not present") Log.error("[NativeLibrary] EmulationActivity not present")
return return
} }
val fragment = CoreErrorDialogFragment.newInstance(title, message) val fragment = CoreErrorDialogFragment.newInstance(title, message, canContinue)
fragment.show(emulationActivity.supportFragmentManager, CoreErrorDialogFragment.TAG) fragment.show(emulationActivity.supportFragmentManager, CoreErrorDialogFragment.TAG)
} }
@ -207,6 +207,7 @@ object NativeLibrary {
} }
val title: String val title: String
val message: String val message: String
val canContinue: Boolean
when (error) { when (error) {
CoreError.ErrorSystemFiles -> { CoreError.ErrorSystemFiles -> {
title = emulationActivity.getString(R.string.system_archive_not_found) title = emulationActivity.getString(R.string.system_archive_not_found)
@ -214,16 +215,25 @@ object NativeLibrary {
R.string.system_archive_not_found_message, R.string.system_archive_not_found_message,
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
) )
canContinue = true
} }
CoreError.ErrorSavestate -> { CoreError.ErrorSavestate -> {
title = emulationActivity.getString(R.string.save_load_error) title = emulationActivity.getString(R.string.save_load_error)
message = details 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 -> { CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error) title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message) message = emulationActivity.getString(R.string.fatal_error_message)
canContinue = true
} }
else -> { else -> {
@ -232,7 +242,7 @@ object NativeLibrary {
} }
// Show the AlertDialog on the main thread. // 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. // Wait for the lock to notify that it is complete.
synchronized(coreErrorAlertLock) { synchronized(coreErrorAlertLock) {
@ -346,6 +356,11 @@ object NativeLibrary {
return return
} }
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
emulationActivity.finish()
return
}
emulationActivity.runOnUiThread { emulationActivity.runOnUiThread {
EmulationErrorDialogFragment.newInstance(resultCode).showNow( EmulationErrorDialogFragment.newInstance(resultCode).showNow(
emulationActivity.supportFragmentManager, emulationActivity.supportFragmentManager,
@ -361,14 +376,21 @@ object NativeLibrary {
emulationActivity = requireActivity() as EmulationActivity emulationActivity = requireActivity() as EmulationActivity
var captionId = R.string.loader_error_invalid_format 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 captionId = R.string.loader_error_encrypted
} }
if (result == ErrorArticDisconnected) {
captionId = R.string.artic_base
}
val alert = MaterialAlertDialogBuilder(requireContext()) val alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(captionId) .setTitle(captionId)
.setMessage( .setMessage(
Html.fromHtml( Html.fromHtml(
if (result == ErrorArticDisconnected)
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
else
CitraApplication.appContext.resources.getString(R.string.redump_games), CitraApplication.appContext.resources.getString(R.string.redump_games),
Html.FROM_HTML_MODE_LEGACY Html.FROM_HTML_MODE_LEGACY
) )
@ -398,7 +420,10 @@ object NativeLibrary {
const val ErrorLoader = 4 const val ErrorLoader = 4
const val ErrorLoader_ErrorEncrypted = 5 const val ErrorLoader_ErrorEncrypted = 5
const val ErrorLoader_ErrorInvalidFormat = 6 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 ShutdownRequested = 11
const val ErrorUnknown = 12 const val ErrorUnknown = 12
@ -619,6 +644,7 @@ object NativeLibrary {
enum class CoreError { enum class CoreError {
ErrorSystemFiles, ErrorSystemFiles,
ErrorSavestate, ErrorSavestate,
ErrorArticDisconnected,
ErrorUnknown ErrorUnknown
} }
@ -633,23 +659,33 @@ object NativeLibrary {
} }
class CoreErrorDialogFragment : DialogFragment() { class CoreErrorDialogFragment : DialogFragment() {
private var userChosen = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val title = requireArguments().getString(TITLE) val title = requireArguments().getString(TITLE)
val message = requireArguments().getString(MESSAGE) val message = requireArguments().getString(MESSAGE)
return MaterialAlertDialogBuilder(requireContext()) val canContinue = requireArguments().getBoolean(CAN_CONTINUE)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(title) .setTitle(title)
.setMessage(message) .setMessage(message)
.setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int -> if (canContinue) {
dialog.setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int ->
coreErrorAlertResult = true coreErrorAlertResult = true
userChosen = true
} }
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> }
dialog.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
coreErrorAlertResult = false coreErrorAlertResult = false
}.show() userChosen = true
}
return dialog.show()
} }
override fun onDismiss(dialog: DialogInterface) { override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog) super.onDismiss(dialog)
coreErrorAlertResult = true val canContinue = requireArguments().getBoolean(CAN_CONTINUE)
if (!userChosen) {
coreErrorAlertResult = canContinue
}
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() } synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
} }
@ -658,12 +694,14 @@ object NativeLibrary {
const val TITLE = "title" const val TITLE = "title"
const val MESSAGE = "message" 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 frag = CoreErrorDialogFragment()
val args = Bundle() val args = Bundle()
args.putString(TITLE, title) args.putString(TITLE, title)
args.putString(MESSAGE, message) args.putString(MESSAGE, message)
args.putBoolean(CAN_CONTINUE, canContinue)
frag.arguments = args frag.arguments = args
return frag return frag
} }

View file

@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@ -23,14 +24,19 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.HomeNavigationDirections
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.HomeSettingAdapter 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.databinding.FragmentHomeSettingsBinding
import org.citra.citra_emu.features.settings.model.Settings 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.ui.SettingsActivity
import org.citra.citra_emu.features.settings.utils.SettingsFile 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.model.HomeSetting
import org.citra.citra_emu.ui.main.MainActivity import org.citra.citra_emu.ui.main.MainActivity
import org.citra.citra_emu.utils.GameHelper import org.citra.citra_emu.utils.GameHelper
@ -76,6 +82,41 @@ class HomeSettingsFragment : Fragment() {
R.drawable.ic_settings, R.drawable.ic_settings,
{ SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } { 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( HomeSetting(
R.string.system_files, R.string.system_files,
R.string.system_files_description, R.string.system_files_description,

View file

@ -82,6 +82,7 @@ static jobject ToJavaCoreError(Core::System::ResultStatus result) {
static const std::map<Core::System::ResultStatus, const char*> CoreErrorNameMap{ static const std::map<Core::System::ResultStatus, const char*> CoreErrorNameMap{
{Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"}, {Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"},
{Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"}, {Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"},
{Core::System::ResultStatus::ErrorArticDisconnected, "ErrorArticDisconnected"},
{Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"}, {Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"},
}; };
@ -178,6 +179,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
auto app_loader = Loader::GetLoader(filepath); auto app_loader = Loader::GetLoader(filepath);
if (app_loader) { if (app_loader) {
app_loader->ReadProgramId(program_id); app_loader->ReadProgramId(program_id);
system.RegisterAppLoaderEarly(app_loader);
GameSettings::LoadOverrides(program_id); GameSettings::LoadOverrides(program_id);
} }
system.ApplySettings(); system.ApplySettings();
@ -231,6 +233,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
InputManager::NDKMotionHandler()->DisableSensors(); InputManager::NDKMotionHandler()->DisableSensors();
if (!HandleCoreError(result, system.GetStatusDetails())) { if (!HandleCoreError(result, system.GetStatusDetails())) {
// Frontend requests us to abort // 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; return result;
} }
InputManager::NDKMotionHandler()->EnableSensors(); InputManager::NDKMotionHandler()->EnableSensors();
@ -314,7 +320,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en
if (stop_run || pause_emulation) { if (stop_run || pause_emulation) {
return; return;
} }
if (window) {
window->TryPresenting(); window->TryPresenting();
}
} }
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver( void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver(

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M200,840q-33,0 -56.5,-23.5T120,760q0,-33 23.5,-56.5T200,680q33,0 56.5,23.5T280,760q0,33 -23.5,56.5T200,840ZM680,840q0,-117 -44,-218.5T516,444q-76,-76 -177.5,-120T120,280v-120q142,0 265,53t216,146q93,93 146,216t53,265L680,840ZM440,840q0,-67 -25,-124.5T346,614q-44,-44 -101.5,-69T120,520v-120q92,0 171.5,34.5T431,529q60,60 94.5,139.5T560,840L440,840Z"/>
</vector>

View file

@ -657,4 +657,11 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="november">Noviembre</string> <string name="november">Noviembre</string>
<string name="december">Diciembre</string> <string name="december">Diciembre</string>
<!-- Artic base -->
<string name="artic_server_comm_error">Fallo de comunicación con el servidor Artic Base. La emulación se detendrá.</string>
<string name="artic_base">Artic Base</string>
<string name="artic_base_connect">Conectar con Artic Base</string>
<string name="artic_base_connect_description">Conectar con una consola real que esté ejecutando un servidor Artic Base</string>
<string name="artic_base_enter_address">Introduce la dirección del servidor Artic Base</string>
</resources> </resources>

View file

@ -683,4 +683,11 @@
<string name="november">November</string> <string name="november">November</string>
<string name="december">December</string> <string name="december">December</string>
<!-- Artic base -->
<string name="artic_server_comm_error">Failed to communicate with the Artic Base server. Emulation will stop.</string>
<string name="artic_base">Artic Base</string>
<string name="artic_base_connect_description">Connect to a real console that is running an Artic Base server</string>
<string name="artic_base_connect">Connect to Artic Base</string>
<string name="artic_base_enter_address">Enter Artic Base server address</string>
</resources> </resources>