mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +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