mirror of
https://github.com/PabloMK7/citra.git
synced 2025-09-10 04:40:05 +00:00
Add Android support
This commit is contained in:
parent
78c48a46e0
commit
7412586f5b
6 changed files with 125 additions and 15 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
9
src/android/app/src/main/res/drawable/ic_network.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_network.xml
Normal 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>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue