mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-03-14 01:32:25 +01:00
Implement "Set Up System Files" on Android (#653)
* Implement "Set Up System Files" on Android * Use correct strings + Remove chunks of unused code * Updated license header * SystemFilesFragment.kt: Use radio buttons for selecting O3DS/N3DS * HomeSettingsFragment.kt: Moved `Install CIA` above `Set Up System Files` * strings.xml: Updated system file setup button strings * android: Remove System Files Warning This warning is no longer relevant due to changes in how system files are installed --------- Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
parent
57b5f7da17
commit
42d77cd720
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -178,7 +178,9 @@ object NativeLibrary {
|
||||
|
||||
external fun getSystemTitleIds(systemType: Int, region: Int): LongArray
|
||||
|
||||
external fun downloadTitleFromNus(title: Long): InstallStatus
|
||||
external fun areSystemTitlesInstalled(): BooleanArray
|
||||
|
||||
external fun uninstallSystemFiles(old3DS: Boolean)
|
||||
|
||||
private var coreErrorAlertResult = false
|
||||
private val coreErrorAlertLock = Object()
|
||||
|
@ -1,152 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.NativeLibrary.InstallStatus
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.DialogProgressBarBinding
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
import org.citra.citra_emu.viewmodel.SystemFilesViewModel
|
||||
|
||||
class DownloadSystemFilesDialogFragment : DialogFragment() {
|
||||
private var _binding: DialogProgressBarBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val downloadViewModel: SystemFilesViewModel by activityViewModels()
|
||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||
|
||||
private lateinit var titles: LongArray
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
_binding = DialogProgressBarBinding.inflate(layoutInflater)
|
||||
|
||||
titles = requireArguments().getLongArray(TITLES)!!
|
||||
|
||||
binding.progressText.visibility = View.GONE
|
||||
|
||||
binding.progressBar.min = 0
|
||||
binding.progressBar.max = titles.size
|
||||
if (downloadViewModel.isDownloading.value != true) {
|
||||
binding.progressBar.progress = 0
|
||||
}
|
||||
|
||||
isCancelable = false
|
||||
return MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(binding.root)
|
||||
.setTitle(R.string.downloading_files)
|
||||
.setMessage(R.string.downloading_files_description)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
downloadViewModel.progress.collectLatest { binding.progressBar.progress = it }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
downloadViewModel.result.collect {
|
||||
when (it) {
|
||||
InstallStatus.Success -> {
|
||||
downloadViewModel.clear()
|
||||
dismiss()
|
||||
MessageDialogFragment.newInstance(R.string.download_success, 0)
|
||||
.show(requireActivity().supportFragmentManager, MessageDialogFragment.TAG)
|
||||
gamesViewModel.setShouldSwapData(true)
|
||||
}
|
||||
|
||||
InstallStatus.ErrorFailedToOpenFile,
|
||||
InstallStatus.ErrorEncrypted,
|
||||
InstallStatus.ErrorFileNotFound,
|
||||
InstallStatus.ErrorInvalid,
|
||||
InstallStatus.ErrorAborted -> {
|
||||
downloadViewModel.clear()
|
||||
dismiss()
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.download_failed,
|
||||
R.string.download_failed_description
|
||||
).show(requireActivity().supportFragmentManager, MessageDialogFragment.TAG)
|
||||
gamesViewModel.setShouldSwapData(true)
|
||||
}
|
||||
|
||||
InstallStatus.Cancelled -> {
|
||||
downloadViewModel.clear()
|
||||
dismiss()
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.download_cancelled,
|
||||
R.string.download_cancelled_description
|
||||
).show(requireActivity().supportFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
// Do nothing on null
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Consider using WorkManager here. While the home menu can only really amount to
|
||||
// about 150MBs, this could be a problem on inconsistent networks
|
||||
downloadViewModel.download(titles)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val alertDialog = dialog as AlertDialog
|
||||
val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
|
||||
negativeButton.setOnClickListener {
|
||||
downloadViewModel.cancel()
|
||||
dialog?.setTitle(R.string.cancelling)
|
||||
binding.progressBar.isIndeterminate = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "DownloadSystemFilesDialogFragment"
|
||||
|
||||
const val TITLES = "Titles"
|
||||
|
||||
fun newInstance(titles: LongArray): DownloadSystemFilesDialogFragment {
|
||||
val dialog = DownloadSystemFilesDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putLongArray(TITLES, titles)
|
||||
dialog.arguments = args
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -121,8 +121,14 @@ class HomeSettingsFragment : Fragment() {
|
||||
}
|
||||
),
|
||||
HomeSetting(
|
||||
R.string.system_files,
|
||||
R.string.system_files_description,
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_install,
|
||||
{ mainActivity.ciaFileInstaller.launch(true) }
|
||||
),
|
||||
HomeSetting(
|
||||
R.string.setup_system_files,
|
||||
R.string.setup_system_files_description,
|
||||
R.drawable.ic_system_update,
|
||||
{
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
@ -130,12 +136,6 @@ class HomeSettingsFragment : Fragment() {
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_systemFilesFragment)
|
||||
}
|
||||
),
|
||||
HomeSetting(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_install,
|
||||
{ mainActivity.ciaFileInstaller.launch(true) }
|
||||
),
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
|
@ -1,76 +1,60 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.fragments
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.HomeNavigationDirections
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.activities.EmulationActivity
|
||||
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||
import org.citra.citra_emu.databinding.FragmentSystemFilesBinding
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||
import org.citra.citra_emu.viewmodel.SystemFilesViewModel
|
||||
|
||||
class SystemFilesFragment : Fragment() {
|
||||
private var _binding: FragmentSystemFilesBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||
private val systemFilesViewModel: SystemFilesViewModel by activityViewModels()
|
||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||
|
||||
private lateinit var regionValues: IntArray
|
||||
|
||||
private val systemTypeDropdown = DropdownItem(R.array.systemFileTypeValues)
|
||||
private val systemRegionDropdown = DropdownItem(R.array.systemFileRegionValues)
|
||||
|
||||
private val SYS_TYPE = "SysType"
|
||||
private val REGION = "Region"
|
||||
private val REGION_START = "RegionStart"
|
||||
|
||||
private val homeMenuMap: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
private val WARNING_SHOWN = "SystemFilesWarningShown"
|
||||
|
||||
private class DropdownItem(val valuesId: Int) : AdapterView.OnItemClickListener {
|
||||
var position = 0
|
||||
|
||||
fun getValue(resources: Resources): Int {
|
||||
return resources.getIntArray(valuesId)[position]
|
||||
}
|
||||
|
||||
override fun onItemClick(p0: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
this.position = position
|
||||
}
|
||||
}
|
||||
private var setupStateCached: BooleanArray? = null
|
||||
private lateinit var regionValues: IntArray
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -92,61 +76,15 @@ class SystemFilesFragment : Fragment() {
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
if (!preferences.getBoolean(WARNING_SHOWN, false)) {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.home_menu_warning,
|
||||
R.string.home_menu_warning_description
|
||||
).show(childFragmentManager, MessageDialogFragment.TAG)
|
||||
preferences.edit()
|
||||
.putBoolean(WARNING_SHOWN, true)
|
||||
.apply()
|
||||
}
|
||||
|
||||
binding.toolbarSystemFiles.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
}
|
||||
|
||||
// TODO: Remove workaround for text filtering issue in material components when fixed
|
||||
// https://github.com/material-components/material-components-android/issues/1464
|
||||
binding.dropdownSystemType.isSaveEnabled = false
|
||||
binding.dropdownSystemRegion.isSaveEnabled = false
|
||||
binding.dropdownSystemRegionStart.isSaveEnabled = false
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
systemFilesViewModel.shouldRefresh.collect {
|
||||
if (it) {
|
||||
reloadUi()
|
||||
systemFilesViewModel.setShouldRefresh(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reloadUi()
|
||||
if (savedInstanceState != null) {
|
||||
setDropdownSelection(
|
||||
binding.dropdownSystemType,
|
||||
systemTypeDropdown,
|
||||
savedInstanceState.getInt(SYS_TYPE)
|
||||
)
|
||||
setDropdownSelection(
|
||||
binding.dropdownSystemRegion,
|
||||
systemRegionDropdown,
|
||||
savedInstanceState.getInt(REGION)
|
||||
)
|
||||
binding.dropdownSystemRegionStart
|
||||
.setText(savedInstanceState.getString(REGION_START), false)
|
||||
}
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putInt(SYS_TYPE, systemTypeDropdown.position)
|
||||
outState.putInt(REGION, systemRegionDropdown.position)
|
||||
outState.putString(REGION_START, binding.dropdownSystemRegionStart.text.toString())
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@ -154,6 +92,41 @@ class SystemFilesFragment : Fragment() {
|
||||
SystemSaveGame.save()
|
||||
}
|
||||
|
||||
private fun showProgressDialog(
|
||||
main_title: CharSequence,
|
||||
main_text: CharSequence
|
||||
): AlertDialog? {
|
||||
val context = requireContext()
|
||||
val progressIndicator = CircularProgressIndicator(context).apply {
|
||||
isIndeterminate = true
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.CENTER // Center the progress indicator
|
||||
).apply {
|
||||
setMargins(50, 50, 50, 50) // Add margins (left, top, right, bottom)
|
||||
}
|
||||
}
|
||||
|
||||
val pleaseWaitText = MaterialTextView(context).apply {
|
||||
text = main_text
|
||||
}
|
||||
|
||||
val container = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
setPadding(40, 40, 40, 40) // Optional: Add padding to the entire layout
|
||||
addView(pleaseWaitText)
|
||||
addView(progressIndicator)
|
||||
}
|
||||
|
||||
return MaterialAlertDialogBuilder(context)
|
||||
.setTitle(main_title)
|
||||
.setView(container)
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun reloadUi() {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
|
||||
@ -171,31 +144,151 @@ class SystemFilesFragment : Fragment() {
|
||||
gamesViewModel.setShouldSwapData(true)
|
||||
}
|
||||
|
||||
if (!NativeLibrary.areKeysAvailable()) {
|
||||
binding.apply {
|
||||
systemType.isEnabled = false
|
||||
systemRegion.isEnabled = false
|
||||
buttonDownloadHomeMenu.isEnabled = false
|
||||
textKeysMissing.visibility = View.VISIBLE
|
||||
textKeysMissingHelp.visibility = View.VISIBLE
|
||||
textKeysMissingHelp.text =
|
||||
Html.fromHtml(getString(R.string.how_to_get_keys), Html.FROM_HTML_MODE_LEGACY)
|
||||
textKeysMissingHelp.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
} else {
|
||||
populateDownloadOptions()
|
||||
binding.setupSystemFilesDescription?.apply {
|
||||
text = HtmlCompat.fromHtml(
|
||||
context.getString(R.string.setup_system_files_preamble),
|
||||
HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
binding.buttonDownloadHomeMenu.setOnClickListener {
|
||||
val titleIds = NativeLibrary.getSystemTitleIds(
|
||||
systemTypeDropdown.getValue(resources),
|
||||
systemRegionDropdown.getValue(resources)
|
||||
binding.buttonSetUpSystemFiles.setOnClickListener {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||
var textInputValue: String = preferences.getString("last_artic_base_addr", "")!!
|
||||
|
||||
val progressDialog = showProgressDialog(
|
||||
getText(R.string.setup_system_files),
|
||||
getString(R.string.setup_system_files_detect)
|
||||
)
|
||||
|
||||
DownloadSystemFilesDialogFragment.newInstance(titleIds).show(
|
||||
childFragmentManager,
|
||||
DownloadSystemFilesDialogFragment.TAG
|
||||
)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val setupState = setupStateCached ?: NativeLibrary.areSystemTitlesInstalled().also {
|
||||
setupStateCached = it
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
progressDialog?.dismiss()
|
||||
|
||||
inputBinding.editTextInput.setText(textInputValue)
|
||||
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
|
||||
textInputValue = text.toString()
|
||||
}
|
||||
|
||||
val buttonGroup = context?.let { it1 -> RadioGroup(it1) }!!
|
||||
|
||||
val buttonO3ds = context?.let { it1 ->
|
||||
RadioButton(it1).apply {
|
||||
text = context.getString(R.string.setup_system_files_o3ds)
|
||||
isChecked = false
|
||||
}
|
||||
}!!
|
||||
|
||||
val buttonN3ds = context?.let { it1 ->
|
||||
RadioButton(it1).apply {
|
||||
text = context.getString(R.string.setup_system_files_n3ds)
|
||||
isChecked = false
|
||||
}
|
||||
}!!
|
||||
|
||||
val textO3ds: String
|
||||
val textN3ds: String
|
||||
|
||||
val colorO3ds: Int
|
||||
val colorN3ds: Int
|
||||
|
||||
if (!setupStateCached!![0]) {
|
||||
textO3ds = getString(R.string.setup_system_files_possible)
|
||||
colorO3ds = R.color.citra_primary_blue
|
||||
|
||||
textN3ds = getString(R.string.setup_system_files_o3ds_needed)
|
||||
colorN3ds = R.color.citra_primary_yellow
|
||||
|
||||
buttonN3ds.isEnabled = false
|
||||
} else {
|
||||
textO3ds = getString(R.string.setup_system_files_completed)
|
||||
colorO3ds = R.color.citra_primary_green
|
||||
|
||||
if (!setupStateCached!![1]) {
|
||||
textN3ds = getString(R.string.setup_system_files_possible)
|
||||
colorN3ds = R.color.citra_primary_blue
|
||||
} else {
|
||||
textN3ds = getString(R.string.setup_system_files_completed)
|
||||
colorN3ds = R.color.citra_primary_green
|
||||
}
|
||||
}
|
||||
|
||||
val tooltipO3ds = context?.let { it1 ->
|
||||
MaterialTextView(it1).apply {
|
||||
text = textO3ds
|
||||
textSize = 12f
|
||||
setTextColor(ContextCompat.getColor(requireContext(), colorO3ds))
|
||||
}
|
||||
}
|
||||
|
||||
val tooltipN3ds = context?.let { it1 ->
|
||||
MaterialTextView(it1).apply {
|
||||
text = textN3ds
|
||||
textSize = 12f
|
||||
setTextColor(ContextCompat.getColor(requireContext(), colorN3ds))
|
||||
}
|
||||
}
|
||||
|
||||
buttonGroup.apply {
|
||||
addView(buttonO3ds)
|
||||
addView(tooltipO3ds)
|
||||
addView(buttonN3ds)
|
||||
addView(tooltipN3ds)
|
||||
}
|
||||
|
||||
inputBinding.root.apply {
|
||||
addView(buttonGroup)
|
||||
}
|
||||
|
||||
val dialog = context?.let {
|
||||
MaterialAlertDialogBuilder(it)
|
||||
.setView(inputBinding.root)
|
||||
.setTitle(getString(R.string.setup_system_files_enter_address))
|
||||
.setPositiveButton(android.R.string.ok) { diag, _ ->
|
||||
if (textInputValue.isNotEmpty() && !(!buttonO3ds.isChecked && !buttonN3ds.isChecked)) {
|
||||
preferences.edit()
|
||||
.putString("last_artic_base_addr", textInputValue)
|
||||
.apply()
|
||||
val menu = Game(
|
||||
title = getString(R.string.artic_base),
|
||||
path = if (buttonO3ds.isChecked) {
|
||||
"articinio://$textInputValue"
|
||||
} else {
|
||||
"articinin://$textInputValue"
|
||||
},
|
||||
filename = ""
|
||||
)
|
||||
val progressDialog2 = showProgressDialog(
|
||||
getText(R.string.setup_system_files),
|
||||
getString(
|
||||
R.string.setup_system_files_preparing
|
||||
)
|
||||
)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
NativeLibrary.uninstallSystemFiles(buttonO3ds.isChecked)
|
||||
withContext(Dispatchers.Main) {
|
||||
setupStateCached = null
|
||||
progressDialog2?.dismiss()
|
||||
val action =
|
||||
HomeNavigationDirections.actionGlobalEmulationActivity(
|
||||
menu
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
populateHomeMenuOptions()
|
||||
@ -211,51 +304,6 @@ class SystemFilesFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateDropdown(
|
||||
dropdown: MaterialAutoCompleteTextView,
|
||||
valuesId: Int,
|
||||
dropdownItem: DropdownItem
|
||||
) {
|
||||
val valuesAdapter = ArrayAdapter.createFromResource(
|
||||
requireContext(),
|
||||
valuesId,
|
||||
R.layout.support_simple_spinner_dropdown_item
|
||||
)
|
||||
dropdown.setAdapter(valuesAdapter)
|
||||
dropdown.onItemClickListener = dropdownItem
|
||||
}
|
||||
|
||||
private fun setDropdownSelection(
|
||||
dropdown: MaterialAutoCompleteTextView,
|
||||
dropdownItem: DropdownItem,
|
||||
selection: Int
|
||||
) {
|
||||
if (dropdown.adapter != null) {
|
||||
dropdown.setText(dropdown.adapter.getItem(selection).toString(), false)
|
||||
}
|
||||
dropdownItem.position = selection
|
||||
}
|
||||
|
||||
private fun populateDownloadOptions() {
|
||||
populateDropdown(binding.dropdownSystemType, R.array.systemFileTypes, systemTypeDropdown)
|
||||
populateDropdown(
|
||||
binding.dropdownSystemRegion,
|
||||
R.array.systemFileRegions,
|
||||
systemRegionDropdown
|
||||
)
|
||||
|
||||
setDropdownSelection(
|
||||
binding.dropdownSystemType,
|
||||
systemTypeDropdown,
|
||||
systemTypeDropdown.position
|
||||
)
|
||||
setDropdownSelection(
|
||||
binding.dropdownSystemRegion,
|
||||
systemRegionDropdown,
|
||||
systemRegionDropdown.position
|
||||
)
|
||||
}
|
||||
|
||||
private fun populateHomeMenuOptions() {
|
||||
regionValues = resources.getIntArray(R.array.systemFileRegionValues)
|
||||
val regionEntries = resources.getStringArray(R.array.systemFileRegions)
|
||||
@ -280,30 +328,4 @@ class SystemFilesFragment : Fragment() {
|
||||
binding.dropdownSystemRegionStart.setText(availableMenus.keys.first(), false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
val leftInsets = barInsets.left + cutoutInsets.left
|
||||
val rightInsets = barInsets.right + cutoutInsets.right
|
||||
|
||||
val mlpAppBar = binding.toolbarSystemFiles.layoutParams as ViewGroup.MarginLayoutParams
|
||||
mlpAppBar.leftMargin = leftInsets
|
||||
mlpAppBar.rightMargin = rightInsets
|
||||
binding.toolbarSystemFiles.layoutParams = mlpAppBar
|
||||
|
||||
val mlpScrollSystemFiles =
|
||||
binding.scrollSystemFiles.layoutParams as ViewGroup.MarginLayoutParams
|
||||
mlpScrollSystemFiles.leftMargin = leftInsets
|
||||
mlpScrollSystemFiles.rightMargin = rightInsets
|
||||
binding.scrollSystemFiles.layoutParams = mlpScrollSystemFiles
|
||||
|
||||
binding.scrollSystemFiles.updatePadding(bottom = barInsets.bottom)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
@ -1,139 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.yield
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.NativeLibrary.InstallStatus
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.min
|
||||
|
||||
class SystemFilesViewModel : ViewModel() {
|
||||
private var job: Job
|
||||
private val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.IO + job
|
||||
|
||||
val isDownloading get() = _isDownloading.asStateFlow()
|
||||
private val _isDownloading = MutableStateFlow(false)
|
||||
|
||||
val progress get() = _progress.asStateFlow()
|
||||
private val _progress = MutableStateFlow(0)
|
||||
|
||||
val result get() = _result.asStateFlow()
|
||||
private val _result = MutableStateFlow<InstallStatus?>(null)
|
||||
|
||||
val shouldRefresh get() = _shouldRefresh.asStateFlow()
|
||||
private val _shouldRefresh = MutableStateFlow(false)
|
||||
|
||||
private var cancelled = false
|
||||
|
||||
private val RETRY_AMOUNT = 3
|
||||
|
||||
init {
|
||||
job = Job()
|
||||
clear()
|
||||
}
|
||||
|
||||
fun setShouldRefresh(refresh: Boolean) {
|
||||
_shouldRefresh.value = refresh
|
||||
}
|
||||
|
||||
fun setProgress(progress: Int) {
|
||||
_progress.value = progress
|
||||
}
|
||||
|
||||
fun download(titles: LongArray) {
|
||||
if (isDownloading.value) {
|
||||
return
|
||||
}
|
||||
clear()
|
||||
_isDownloading.value = true
|
||||
Log.debug("System menu download started.")
|
||||
|
||||
val minExecutors = min(Runtime.getRuntime().availableProcessors(), titles.size)
|
||||
val segment = (titles.size / minExecutors)
|
||||
val atomicProgress = AtomicInteger(0)
|
||||
for (i in 0 until minExecutors) {
|
||||
val titlesSegment = if (i < minExecutors - 1) {
|
||||
titles.copyOfRange(i * segment, (i + 1) * segment)
|
||||
} else {
|
||||
titles.copyOfRange(i * segment, titles.size)
|
||||
}
|
||||
|
||||
CoroutineScope(coroutineContext).launch {
|
||||
titlesSegment.forEach { title: Long ->
|
||||
// Notify UI of cancellation before ending coroutine
|
||||
if (cancelled) {
|
||||
_result.value = InstallStatus.ErrorAborted
|
||||
cancelled = false
|
||||
}
|
||||
|
||||
// Takes a moment to see if the coroutine was cancelled
|
||||
yield()
|
||||
|
||||
// Retry downloading a title repeatedly
|
||||
for (j in 0 until RETRY_AMOUNT) {
|
||||
val result = tryDownloadTitle(title)
|
||||
if (result == InstallStatus.Success) {
|
||||
break
|
||||
} else if (j == RETRY_AMOUNT - 1) {
|
||||
_result.value = result
|
||||
return@launch
|
||||
}
|
||||
Log.warning("Download for title{$title} failed, retrying in 3s...")
|
||||
delay(3000L)
|
||||
}
|
||||
|
||||
Log.debug("Successfully installed title - $title")
|
||||
setProgress(atomicProgress.incrementAndGet())
|
||||
|
||||
Log.debug("System File Progress - ${atomicProgress.get()} / ${titles.size}")
|
||||
if (atomicProgress.get() == titles.size) {
|
||||
_result.value = InstallStatus.Success
|
||||
setShouldRefresh(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryDownloadTitle(title: Long): InstallStatus {
|
||||
val result = NativeLibrary.downloadTitleFromNus(title)
|
||||
if (result != InstallStatus.Success) {
|
||||
Log.error("Failed to install title $title with error - $result")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
Log.debug("Clearing")
|
||||
job.cancelChildren()
|
||||
job = Job()
|
||||
_progress.value = 0
|
||||
_result.value = null
|
||||
_isDownloading.value = false
|
||||
cancelled = false
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
Log.debug("Canceling system file download.")
|
||||
cancelled = true
|
||||
job.cancelChildren()
|
||||
job = Job()
|
||||
_progress.value = 0
|
||||
_result.value = InstallStatus.Cancelled
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/savestate.h"
|
||||
#include "core/system_titles.h"
|
||||
#include "jni/android_common/android_common.h"
|
||||
#include "jni/applets/mii_selector.h"
|
||||
#include "jni/applets/swkbd.h"
|
||||
@ -229,7 +230,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||
if (result == Core::System::ResultStatus::ShutdownRequested) {
|
||||
return result; // This also exits the emulation activity
|
||||
} else {
|
||||
InputManager::NDKMotionHandler()->DisableSensors();
|
||||
auto* handler = InputManager::NDKMotionHandler();
|
||||
if (handler) {
|
||||
handler->DisableSensors();
|
||||
}
|
||||
if (!HandleCoreError(result, system.GetStatusDetails())) {
|
||||
// Frontend requests us to abort
|
||||
// If the error was an Artic disconnect, return shutdown request.
|
||||
@ -238,7 +242,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
InputManager::NDKMotionHandler()->EnableSensors();
|
||||
handler = InputManager::NDKMotionHandler();
|
||||
if (handler) {
|
||||
handler->EnableSensors();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure no audio bleeds out while game is paused
|
||||
@ -435,11 +442,25 @@ jlongArray Java_org_citra_citra_1emu_NativeLibrary_getSystemTitleIds(JNIEnv* env
|
||||
return jTitles;
|
||||
}
|
||||
|
||||
jobject Java_org_citra_citra_1emu_NativeLibrary_downloadTitleFromNus([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jobject obj,
|
||||
jlong title) {
|
||||
[[maybe_unused]] const auto title_id = static_cast<u64>(title);
|
||||
return IDCache::GetJavaCiaInstallStatus(Service::AM::InstallStatus::ErrorAborted);
|
||||
jbooleanArray Java_org_citra_citra_1emu_NativeLibrary_areSystemTitlesInstalled(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||
const auto installed = Core::AreSystemTitlesInstalled();
|
||||
jbooleanArray jInstalled = env->NewBooleanArray(2);
|
||||
jboolean* elements = env->GetBooleanArrayElements(jInstalled, nullptr);
|
||||
|
||||
elements[0] = installed.first ? JNI_TRUE : JNI_FALSE;
|
||||
elements[1] = installed.second ? JNI_TRUE : JNI_FALSE;
|
||||
|
||||
env->ReleaseBooleanArrayElements(jInstalled, elements, 0);
|
||||
|
||||
return jInstalled;
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_NativeLibrary_uninstallSystemFiles(JNIEnv* env,
|
||||
[[maybe_unused]] jobject obj,
|
||||
jboolean old3ds) {
|
||||
Core::UninstallSystemFiles(old3ds ? Core::SystemTitleSet::Old3ds
|
||||
: Core::SystemTitleSet::New3ds);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static bool CheckKgslPresent() {
|
||||
@ -467,13 +488,19 @@ void Java_org_citra_citra_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] J
|
||||
[[maybe_unused]] jobject obj) {
|
||||
pause_emulation = false;
|
||||
running_cv.notify_all();
|
||||
InputManager::NDKMotionHandler()->EnableSensors();
|
||||
auto* handler = InputManager::NDKMotionHandler();
|
||||
if (handler) {
|
||||
handler->EnableSensors();
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jobject obj) {
|
||||
pause_emulation = true;
|
||||
InputManager::NDKMotionHandler()->DisableSensors();
|
||||
auto* handler = InputManager::NDKMotionHandler();
|
||||
if (handler) {
|
||||
handler->DisableSensors();
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env,
|
||||
|
@ -1,219 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator_about"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_about"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_system_files"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/system_files" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_system_files"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical"
|
||||
android:clipToPadding="false"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/download_system_files"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/system_type"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/system_type"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/dropdown_system_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/system_region"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/emulated_region"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/dropdown_system_region"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_download_home_menu"
|
||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/download" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_keys_missing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/keys_missing"
|
||||
android:textAlignment="viewStart"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_keys_missing_help"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:visibility="gone"
|
||||
tools:text="How to get keys?" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/boot_home_menu"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/system_region_start"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/emulated_region"
|
||||
android:enabled="false"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/dropdown_system_region_start"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_start_home_menu"
|
||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/start" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_run_system_setup"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/run_system_setup"
|
||||
android:textAlignment="viewStart"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/switch_run_system_setup"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_run_system_setup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_show_apps"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/show_home_apps"
|
||||
android:textAlignment="viewStart"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/switch_show_apps"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_show_apps"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -18,7 +18,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/system_files" />
|
||||
app:title="@string/setup_system_files" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
@ -39,77 +39,41 @@
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/setup_system_files"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/setupSystemFilesDescription"
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/download_system_files"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/system_type"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/system_type"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/dropdown_system_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/system_region"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/emulated_region"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/dropdown_system_region"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_download_home_menu"
|
||||
android:id="@+id/button_set_up_system_files"
|
||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/download" />
|
||||
android:text="@string/setup_tool_connect" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="?android:attr/listDivider"
|
||||
android:padding="40px" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_keys_missing"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/keys_missing"
|
||||
android:textAlignment="viewStart"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_keys_missing_help"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:visibility="gone"
|
||||
tools:text="How to get keys?" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/boot_home_menu"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
|
@ -286,17 +286,6 @@
|
||||
<item>6</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="systemFileTypes">
|
||||
<item>@string/system_type_minimal</item>
|
||||
<item>@string/system_type_old_3ds</item>
|
||||
<item>@string/system_type_new_3ds</item>
|
||||
</string-array>
|
||||
<integer-array name="systemFileTypeValues">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>4</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="soundOutputModes">
|
||||
<item>@string/mono</item>
|
||||
<item>@string/stereo</item>
|
||||
|
@ -9,6 +9,8 @@
|
||||
<string name="app_notification_channel_description">Azahar 3DS emulator notifications</string>
|
||||
<string name="app_notification_running">Azahar is Running</string>
|
||||
<string name="app_game_install_description">Next, you will need to select an Applications Folder. Azahar will display all of the 3DS ROMs inside of the selected folder in the app.\n\nCIA ROMs, updates and DLC will need to be installed separately by clicking on the folder icon and selecting Install CIA.</string>
|
||||
<string name="start">Start</string>
|
||||
<string name="cancelling">Cancelling…</string>
|
||||
|
||||
<!-- Home Strings -->
|
||||
<string name="grid_menu_core_settings">Settings</string>
|
||||
@ -142,31 +144,22 @@
|
||||
<string name="input_message_button_only">This control must be bound to a gamepad button!</string>
|
||||
|
||||
<!-- System files strings -->
|
||||
<string name="system_files">System Files</string>
|
||||
<string name="system_files_description">Download system files to get Mii files, boot the HOME menu, and more</string>
|
||||
<string name="download_system_files">Download System Files</string>
|
||||
<string name="setup_system_files">System Files</string>
|
||||
<string name="setup_system_files_description">Perform system file operations such as installing system files or booting the Home Menu</string>
|
||||
<string name="setup_tool_connect">Connect to Artic Setup Tool</string>
|
||||
<string name="setup_system_files_preamble"><![CDATA[Azahar needs files from a real console to be able to use some of its features. You can get such files with the <a href=https://github.com/azahar-emu/ArticSetupTool>Azahar Artic Setup Tool</a>.<br> Notes:<ul><li><b>This operation will install console unique files to Azahar, do not share your user or nand folders<br>after performing the setup process!</b></li><li>Old 3DS setup is needed for the New 3DS setup to work.</li><li>Both setup modes will work regardless of the model of the console running the setup tool.</li></ul>]]></string>
|
||||
<string name="setup_system_files_detect">Fetching current system files status, please wait...</string>
|
||||
<string name="setup_system_files_o3ds">Old 3DS Setup</string>
|
||||
<string name="setup_system_files_n3ds">New 3DS Setup</string>
|
||||
<string name="setup_system_files_possible">Setup is possible.</string>
|
||||
<string name="setup_system_files_o3ds_needed">Old 3DS setup is required first.</string>
|
||||
<string name="setup_system_files_completed">Setup already completed.</string>
|
||||
<string name="setup_system_files_enter_address">Enter Artic Setup Tool address</string>
|
||||
<string name="setup_system_files_preparing">Preparing setup, please wait...</string>
|
||||
<string name="boot_home_menu">Boot the HOME Menu</string>
|
||||
<string name="system_type">System Type</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="start">Start</string>
|
||||
<string name="keys_missing">Azahar is missing keys to download system files.</string>
|
||||
<string name="how_to_get_keys"><![CDATA[<a href="https://web.archive.org/web/20240304203412/https://citra-emu.org/wiki/aes-keys/">How to get keys?</a>]]></string>
|
||||
<string name="show_home_apps">Show HOME menu apps in Applications list</string>
|
||||
<string name="run_system_setup">Run System Setup when the HOME Menu is launched</string>
|
||||
<string name="system_type_minimal">Minimal</string>
|
||||
<string name="system_type_old_3ds">Old 3DS</string>
|
||||
<string name="system_type_new_3ds">New 3DS</string>
|
||||
<string name="downloading_files">Downloading Files…</string>
|
||||
<string name="downloading_files_description">Please do not close the app.</string>
|
||||
<string name="download_failed">Download Failed</string>
|
||||
<string name="download_failed_description">Please make sure you are connected to the internet and try again.</string>
|
||||
<string name="download_success">Download Complete!</string>
|
||||
<string name="download_cancelled">Download Cancelled</string>
|
||||
<string name="download_cancelled_description">Please restart the download to prevent issues with having incomplete system files.</string>
|
||||
<string name="cancelling">Cancelling…</string>
|
||||
<string name="home_menu">HOME Menu</string>
|
||||
<string name="home_menu_warning">System Files Warning</string>
|
||||
<string name="home_menu_warning_description">Due to how slow Android\'s storage access framework is for accessing Azahar\'s files, downloading multiple versions of system files can dramatically slow down loading for applications, save states, and the Applications list. Only download the files that you require to avoid any issues with loading speeds.</string>
|
||||
|
||||
<!-- Generic buttons (Shared with lots of stuff) -->
|
||||
<string name="generic_buttons">Buttons</string>
|
||||
|
@ -2139,6 +2139,8 @@ void GMainWindow::OnMenuSetUpSystemFiles() {
|
||||
QRadioButton radio1(&dialog);
|
||||
QRadioButton radio2(&dialog);
|
||||
if (!install_state.first) {
|
||||
radio1.setChecked(true);
|
||||
|
||||
radio1.setText(tr("(\u2139\uFE0F) Old 3DS setup"));
|
||||
radio1.setToolTip(tr("Setup is possible."));
|
||||
|
||||
@ -2150,14 +2152,17 @@ void GMainWindow::OnMenuSetUpSystemFiles() {
|
||||
radio1.setToolTip(tr("Setup completed."));
|
||||
|
||||
if (!install_state.second) {
|
||||
radio2.setChecked(true);
|
||||
|
||||
radio2.setText(tr("(\u2139\uFE0F) New 3DS setup"));
|
||||
radio2.setToolTip(tr("Setup is possible."));
|
||||
} else {
|
||||
radio1.setChecked(true);
|
||||
|
||||
radio2.setText(tr("(\u2705) New 3DS setup"));
|
||||
radio2.setToolTip(tr("Setup completed."));
|
||||
}
|
||||
}
|
||||
radio1.setChecked(true);
|
||||
layout.addWidget(&radio1);
|
||||
layout.addWidget(&radio2);
|
||||
|
||||
|
@ -1184,6 +1184,8 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) {
|
||||
void Module::ScanForTickets() {
|
||||
am_ticket_list.clear();
|
||||
|
||||
LOG_DEBUG(Service_AM, "Starting ticket scan");
|
||||
|
||||
std::string ticket_path = GetTicketDirectory();
|
||||
|
||||
FileUtil::FSTEntry entries;
|
||||
@ -1204,11 +1206,14 @@ void Module::ScanForTickets() {
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "Finished ticket scan");
|
||||
}
|
||||
|
||||
void Module::ScanForTitles(Service::FS::MediaType media_type) {
|
||||
am_title_list[static_cast<u32>(media_type)].clear();
|
||||
|
||||
LOG_DEBUG(Service_AM, "Starting title scan for media_type={}", static_cast<int>(media_type));
|
||||
|
||||
std::string title_path = GetMediaTitlePath(media_type);
|
||||
|
||||
FileUtil::FSTEntry entries;
|
||||
@ -1236,6 +1241,7 @@ void Module::ScanForTitles(Service::FS::MediaType media_type) {
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "Finished title scan for media_type={}", static_cast<int>(media_type));
|
||||
}
|
||||
|
||||
void Module::ScanForAllTitles() {
|
||||
@ -2272,7 +2278,7 @@ void Module::Interface::DeleteTicket(Kernel::HLERequestContext& ctx) {
|
||||
FileUtil::Delete(path);
|
||||
}
|
||||
|
||||
am->ScanForTickets();
|
||||
am->am_ticket_list.erase(range.first, range.second);
|
||||
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
@ -3279,7 +3285,8 @@ void Module::Interface::EndImportTicket(Kernel::HLERequestContext& ctx) {
|
||||
auto ticket_file = GetFileBackendFromSession<TicketFile>(ticket);
|
||||
if (ticket_file.Succeeded()) {
|
||||
rb.Push(ticket_file.Unwrap()->Commit());
|
||||
am->ScanForTickets();
|
||||
am->am_ticket_list.insert(std::make_pair(ticket_file.Unwrap()->GetTitleID(),
|
||||
ticket_file.Unwrap()->GetTicketID()));
|
||||
} else {
|
||||
rb.Push(ticket_file.Code());
|
||||
}
|
||||
@ -3416,7 +3423,6 @@ void Module::Interface::EndImportTitle(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
am->importing_title->cia_file.SetDone();
|
||||
am->ScanForTitles(am->importing_title->media_type);
|
||||
am->importing_title.reset();
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
@ -3825,7 +3831,7 @@ void Module::Interface::DeleteTicketId(Kernel::HLERequestContext& ctx) {
|
||||
auto path = GetTicketPath(title_id, ticket_id);
|
||||
FileUtil::Delete(path);
|
||||
|
||||
am->ScanForTickets();
|
||||
am->am_ticket_list.erase(it);
|
||||
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -740,8 +740,8 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
|
||||
Kernel::MappedBuffer& buffer = rp.PopMappedBuffer();
|
||||
|
||||
// Copy the buffer into a string without the \0 at the end of the buffer
|
||||
std::string url(url_size, '\0');
|
||||
buffer.Read(&url[0], 0, url_size - 1);
|
||||
std::string url(url_size - 1, '\0');
|
||||
buffer.Read(url.data(), 0, url_size - 1);
|
||||
|
||||
LOG_DEBUG(Service_HTTP, "called, url_size={}, url={}, method={}", url_size, url, method);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user