mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-03-13 09:12:27 +01:00
Implement "Set Up System Files" on Android
This commit is contained in:
parent
57b5f7da17
commit
5e06d5d5e7
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -178,7 +178,9 @@ object NativeLibrary {
|
|||||||
|
|
||||||
external fun getSystemTitleIds(systemType: Int, region: Int): LongArray
|
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 var coreErrorAlertResult = false
|
||||||
private val coreErrorAlertLock = Object()
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -8,14 +8,21 @@ import android.content.res.Resources
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
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.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@ -23,28 +30,34 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView
|
import com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||||
|
import com.google.android.material.textview.MaterialTextView
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.HomeNavigationDirections
|
import org.citra.citra_emu.HomeNavigationDirections
|
||||||
import org.citra.citra_emu.NativeLibrary
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.activities.EmulationActivity
|
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.databinding.FragmentSystemFilesBinding
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
import org.citra.citra_emu.model.Game
|
import org.citra.citra_emu.model.Game
|
||||||
import org.citra.citra_emu.utils.SystemSaveGame
|
import org.citra.citra_emu.utils.SystemSaveGame
|
||||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||||
import org.citra.citra_emu.viewmodel.SystemFilesViewModel
|
|
||||||
|
|
||||||
class SystemFilesFragment : Fragment() {
|
class SystemFilesFragment : Fragment() {
|
||||||
private var _binding: FragmentSystemFilesBinding? = null
|
private var _binding: FragmentSystemFilesBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||||
private val systemFilesViewModel: SystemFilesViewModel by activityViewModels()
|
|
||||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||||
|
|
||||||
private lateinit var regionValues: IntArray
|
private lateinit var regionValues: IntArray
|
||||||
@ -60,6 +73,8 @@ class SystemFilesFragment : Fragment() {
|
|||||||
|
|
||||||
private val WARNING_SHOWN = "SystemFilesWarningShown"
|
private val WARNING_SHOWN = "SystemFilesWarningShown"
|
||||||
|
|
||||||
|
private var setupStateCached: BooleanArray? = null
|
||||||
|
|
||||||
private class DropdownItem(val valuesId: Int) : AdapterView.OnItemClickListener {
|
private class DropdownItem(val valuesId: Int) : AdapterView.OnItemClickListener {
|
||||||
var position = 0
|
var position = 0
|
||||||
|
|
||||||
@ -109,33 +124,10 @@ class SystemFilesFragment : Fragment() {
|
|||||||
|
|
||||||
// TODO: Remove workaround for text filtering issue in material components when fixed
|
// TODO: Remove workaround for text filtering issue in material components when fixed
|
||||||
// https://github.com/material-components/material-components-android/issues/1464
|
// https://github.com/material-components/material-components-android/issues/1464
|
||||||
binding.dropdownSystemType.isSaveEnabled = false
|
|
||||||
binding.dropdownSystemRegion.isSaveEnabled = false
|
|
||||||
binding.dropdownSystemRegionStart.isSaveEnabled = false
|
binding.dropdownSystemRegionStart.isSaveEnabled = false
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
|
||||||
systemFilesViewModel.shouldRefresh.collect {
|
|
||||||
if (it) {
|
|
||||||
reloadUi()
|
|
||||||
systemFilesViewModel.setShouldRefresh(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadUi()
|
reloadUi()
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
setDropdownSelection(
|
|
||||||
binding.dropdownSystemType,
|
|
||||||
systemTypeDropdown,
|
|
||||||
savedInstanceState.getInt(SYS_TYPE)
|
|
||||||
)
|
|
||||||
setDropdownSelection(
|
|
||||||
binding.dropdownSystemRegion,
|
|
||||||
systemRegionDropdown,
|
|
||||||
savedInstanceState.getInt(REGION)
|
|
||||||
)
|
|
||||||
binding.dropdownSystemRegionStart
|
binding.dropdownSystemRegionStart
|
||||||
.setText(savedInstanceState.getString(REGION_START), false)
|
.setText(savedInstanceState.getString(REGION_START), false)
|
||||||
}
|
}
|
||||||
@ -154,6 +146,41 @@ class SystemFilesFragment : Fragment() {
|
|||||||
SystemSaveGame.save()
|
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() {
|
private fun reloadUi() {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
|
|
||||||
@ -171,31 +198,175 @@ class SystemFilesFragment : Fragment() {
|
|||||||
gamesViewModel.setShouldSwapData(true)
|
gamesViewModel.setShouldSwapData(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!NativeLibrary.areKeysAvailable()) {
|
binding.setupSystemFilesDescription?.apply {
|
||||||
binding.apply {
|
text = HtmlCompat.fromHtml(
|
||||||
systemType.isEnabled = false
|
context.getString(R.string.setup_system_files_description),
|
||||||
systemRegion.isEnabled = false
|
HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||||
buttonDownloadHomeMenu.isEnabled = false
|
)
|
||||||
textKeysMissing.visibility = View.VISIBLE
|
movementMethod = LinkMovementMethod.getInstance()
|
||||||
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.buttonDownloadHomeMenu.setOnClickListener {
|
binding.buttonSetUpSystemFiles.setOnClickListener {
|
||||||
val titleIds = NativeLibrary.getSystemTitleIds(
|
val inflater = LayoutInflater.from(context)
|
||||||
systemTypeDropdown.getValue(resources),
|
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||||
systemRegionDropdown.getValue(resources)
|
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(
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
childFragmentManager,
|
val setupState = setupStateCached ?: NativeLibrary.areSystemTitlesInstalled().also {
|
||||||
DownloadSystemFilesDialogFragment.TAG
|
setupStateCached = it
|
||||||
)
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
progressDialog?.dismiss()
|
||||||
|
|
||||||
|
inputBinding.editTextInput.setText(textInputValue)
|
||||||
|
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
|
||||||
|
textInputValue = text.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val switchOption1 = context?.let { it1 ->
|
||||||
|
SwitchMaterial(it1).apply {
|
||||||
|
text = context.getString(R.string.setup_system_files_o3ds)
|
||||||
|
isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val switchOption2 = context?.let { it1 ->
|
||||||
|
SwitchMaterial(it1).apply {
|
||||||
|
text = context.getString(R.string.setup_system_files_n3ds)
|
||||||
|
isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setupStateCached!![0] && !setupStateCached!![1]) {
|
||||||
|
switchOption2?.isChecked = true
|
||||||
|
} else {
|
||||||
|
switchOption1?.isChecked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switchOption1?.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
switchOption2!!.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchOption2?.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
switchOption1!!.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchOption1?.setOnClickListener {
|
||||||
|
if (!switchOption1.isChecked && !switchOption2!!.isChecked) {
|
||||||
|
switchOption1.isChecked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchOption2?.setOnClickListener {
|
||||||
|
if (!switchOption2.isChecked && !switchOption1!!.isChecked) {
|
||||||
|
switchOption2.isChecked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
switchOption2?.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 tooltipOption1 = context?.let { it1 ->
|
||||||
|
MaterialTextView(it1).apply {
|
||||||
|
text = textO3ds
|
||||||
|
textSize = 12f
|
||||||
|
setTextColor(ContextCompat.getColor(requireContext(), colorO3ds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val tooltipOption2 = context?.let { it1 ->
|
||||||
|
MaterialTextView(it1).apply {
|
||||||
|
text = textN3ds
|
||||||
|
textSize = 12f
|
||||||
|
setTextColor(ContextCompat.getColor(requireContext(), colorN3ds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputBinding.root.apply {
|
||||||
|
addView(switchOption1)
|
||||||
|
addView(tooltipOption1)
|
||||||
|
addView(switchOption2)
|
||||||
|
addView(tooltipOption2)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
preferences.edit()
|
||||||
|
.putString("last_artic_base_addr", textInputValue)
|
||||||
|
.apply()
|
||||||
|
val menu = Game(
|
||||||
|
title = getString(R.string.artic_base),
|
||||||
|
path = if (switchOption1!!.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(switchOption1.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()
|
populateHomeMenuOptions()
|
||||||
@ -236,26 +407,6 @@ class SystemFilesFragment : Fragment() {
|
|||||||
dropdownItem.position = selection
|
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() {
|
private fun populateHomeMenuOptions() {
|
||||||
regionValues = resources.getIntArray(R.array.systemFileRegionValues)
|
regionValues = resources.getIntArray(R.array.systemFileRegionValues)
|
||||||
val regionEntries = resources.getStringArray(R.array.systemFileRegions)
|
val regionEntries = resources.getStringArray(R.array.systemFileRegions)
|
||||||
|
@ -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/hle/service/nfc/nfc.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/savestate.h"
|
#include "core/savestate.h"
|
||||||
|
#include "core/system_titles.h"
|
||||||
#include "jni/android_common/android_common.h"
|
#include "jni/android_common/android_common.h"
|
||||||
#include "jni/applets/mii_selector.h"
|
#include "jni/applets/mii_selector.h"
|
||||||
#include "jni/applets/swkbd.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) {
|
if (result == Core::System::ResultStatus::ShutdownRequested) {
|
||||||
return result; // This also exits the emulation activity
|
return result; // This also exits the emulation activity
|
||||||
} else {
|
} else {
|
||||||
InputManager::NDKMotionHandler()->DisableSensors();
|
auto* handler = InputManager::NDKMotionHandler();
|
||||||
|
if (handler) {
|
||||||
|
handler->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 the error was an Artic disconnect, return shutdown request.
|
||||||
@ -238,7 +242,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
InputManager::NDKMotionHandler()->EnableSensors();
|
handler = InputManager::NDKMotionHandler();
|
||||||
|
if (handler) {
|
||||||
|
handler->EnableSensors();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Ensure no audio bleeds out while game is paused
|
// 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;
|
return jTitles;
|
||||||
}
|
}
|
||||||
|
|
||||||
jobject Java_org_citra_citra_1emu_NativeLibrary_downloadTitleFromNus([[maybe_unused]] JNIEnv* env,
|
jbooleanArray Java_org_citra_citra_1emu_NativeLibrary_areSystemTitlesInstalled(
|
||||||
[[maybe_unused]] jobject obj,
|
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
jlong title) {
|
const auto installed = Core::AreSystemTitlesInstalled();
|
||||||
[[maybe_unused]] const auto title_id = static_cast<u64>(title);
|
jbooleanArray jInstalled = env->NewBooleanArray(2);
|
||||||
return IDCache::GetJavaCiaInstallStatus(Service::AM::InstallStatus::ErrorAborted);
|
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() {
|
[[maybe_unused]] static bool CheckKgslPresent() {
|
||||||
@ -467,13 +488,19 @@ void Java_org_citra_citra_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] J
|
|||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
pause_emulation = false;
|
pause_emulation = false;
|
||||||
running_cv.notify_all();
|
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,
|
void Java_org_citra_citra_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
pause_emulation = true;
|
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,
|
void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env,
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button_download_home_menu"
|
android:id="@+id/button_set_up_system_files"
|
||||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -39,77 +39,41 @@
|
|||||||
android:paddingBottom="16dp">
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<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"
|
style="@style/TextAppearance.Material3.TitleSmall"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/download_system_files"
|
|
||||||
android:textAlignment="viewStart" />
|
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
|
<Button
|
||||||
android:id="@+id/button_download_home_menu"
|
android:id="@+id/button_set_up_system_files"
|
||||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:text="@string/setup_tool_connect" />
|
||||||
android:text="@string/download" />
|
|
||||||
|
<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
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/text_keys_missing"
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="24dp"
|
||||||
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:text="@string/boot_home_menu"
|
android:text="@string/boot_home_menu"
|
||||||
android:textAlignment="viewStart" />
|
android:textAlignment="viewStart" />
|
||||||
|
|
||||||
|
@ -785,5 +785,16 @@
|
|||||||
<string name="delay_start_lle_modules_description">Delays the start of the app when LLE modules are enabled.</string>
|
<string name="delay_start_lle_modules_description">Delays the start of the app when LLE modules are enabled.</string>
|
||||||
<string name="deterministic_async_operations">Deterministic Async Operations</string>
|
<string name="deterministic_async_operations">Deterministic Async Operations</string>
|
||||||
<string name="deterministic_async_operations_description">Makes async operations deterministic for debugging. Enabling this may cause freezes.</string>
|
<string name="deterministic_async_operations_description">Makes async operations deterministic for debugging. Enabling this may cause freezes.</string>
|
||||||
|
<string name="setup_system_files">Set Up System Files</string>
|
||||||
|
<string name="setup_tool_connect">Connect to Artic Setup Tool</string>
|
||||||
|
<string name="setup_system_files_description"><![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>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -2139,6 +2139,8 @@ void GMainWindow::OnMenuSetUpSystemFiles() {
|
|||||||
QRadioButton radio1(&dialog);
|
QRadioButton radio1(&dialog);
|
||||||
QRadioButton radio2(&dialog);
|
QRadioButton radio2(&dialog);
|
||||||
if (!install_state.first) {
|
if (!install_state.first) {
|
||||||
|
radio1.setChecked(true);
|
||||||
|
|
||||||
radio1.setText(tr("(\u2139\uFE0F) Old 3DS setup"));
|
radio1.setText(tr("(\u2139\uFE0F) Old 3DS setup"));
|
||||||
radio1.setToolTip(tr("Setup is possible."));
|
radio1.setToolTip(tr("Setup is possible."));
|
||||||
|
|
||||||
@ -2150,14 +2152,17 @@ void GMainWindow::OnMenuSetUpSystemFiles() {
|
|||||||
radio1.setToolTip(tr("Setup completed."));
|
radio1.setToolTip(tr("Setup completed."));
|
||||||
|
|
||||||
if (!install_state.second) {
|
if (!install_state.second) {
|
||||||
|
radio2.setChecked(true);
|
||||||
|
|
||||||
radio2.setText(tr("(\u2139\uFE0F) New 3DS setup"));
|
radio2.setText(tr("(\u2139\uFE0F) New 3DS setup"));
|
||||||
radio2.setToolTip(tr("Setup is possible."));
|
radio2.setToolTip(tr("Setup is possible."));
|
||||||
} else {
|
} else {
|
||||||
|
radio1.setChecked(true);
|
||||||
|
|
||||||
radio2.setText(tr("(\u2705) New 3DS setup"));
|
radio2.setText(tr("(\u2705) New 3DS setup"));
|
||||||
radio2.setToolTip(tr("Setup completed."));
|
radio2.setToolTip(tr("Setup completed."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
radio1.setChecked(true);
|
|
||||||
layout.addWidget(&radio1);
|
layout.addWidget(&radio1);
|
||||||
layout.addWidget(&radio2);
|
layout.addWidget(&radio2);
|
||||||
|
|
||||||
|
@ -1184,6 +1184,8 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) {
|
|||||||
void Module::ScanForTickets() {
|
void Module::ScanForTickets() {
|
||||||
am_ticket_list.clear();
|
am_ticket_list.clear();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_AM, "Starting ticket scan");
|
||||||
|
|
||||||
std::string ticket_path = GetTicketDirectory();
|
std::string ticket_path = GetTicketDirectory();
|
||||||
|
|
||||||
FileUtil::FSTEntry entries;
|
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) {
|
void Module::ScanForTitles(Service::FS::MediaType media_type) {
|
||||||
am_title_list[static_cast<u32>(media_type)].clear();
|
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);
|
std::string title_path = GetMediaTitlePath(media_type);
|
||||||
|
|
||||||
FileUtil::FSTEntry entries;
|
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() {
|
void Module::ScanForAllTitles() {
|
||||||
@ -2272,7 +2278,7 @@ void Module::Interface::DeleteTicket(Kernel::HLERequestContext& ctx) {
|
|||||||
FileUtil::Delete(path);
|
FileUtil::Delete(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
am->ScanForTickets();
|
am->am_ticket_list.erase(range.first, range.second);
|
||||||
|
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
}
|
}
|
||||||
@ -3279,7 +3285,8 @@ void Module::Interface::EndImportTicket(Kernel::HLERequestContext& ctx) {
|
|||||||
auto ticket_file = GetFileBackendFromSession<TicketFile>(ticket);
|
auto ticket_file = GetFileBackendFromSession<TicketFile>(ticket);
|
||||||
if (ticket_file.Succeeded()) {
|
if (ticket_file.Succeeded()) {
|
||||||
rb.Push(ticket_file.Unwrap()->Commit());
|
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 {
|
} else {
|
||||||
rb.Push(ticket_file.Code());
|
rb.Push(ticket_file.Code());
|
||||||
}
|
}
|
||||||
@ -3416,7 +3423,6 @@ void Module::Interface::EndImportTitle(Kernel::HLERequestContext& ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
am->importing_title->cia_file.SetDone();
|
am->importing_title->cia_file.SetDone();
|
||||||
am->ScanForTitles(am->importing_title->media_type);
|
|
||||||
am->importing_title.reset();
|
am->importing_title.reset();
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
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);
|
auto path = GetTicketPath(title_id, ticket_id);
|
||||||
FileUtil::Delete(path);
|
FileUtil::Delete(path);
|
||||||
|
|
||||||
am->ScanForTickets();
|
am->am_ticket_list.erase(it);
|
||||||
|
|
||||||
rb.Push(ResultSuccess);
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -740,8 +740,8 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
|
|||||||
Kernel::MappedBuffer& buffer = rp.PopMappedBuffer();
|
Kernel::MappedBuffer& buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
// Copy the buffer into a string without the \0 at the end of the buffer
|
// Copy the buffer into a string without the \0 at the end of the buffer
|
||||||
std::string url(url_size, '\0');
|
std::string url(url_size - 1, '\0');
|
||||||
buffer.Read(&url[0], 0, url_size - 1);
|
buffer.Read(url.data(), 0, url_size - 1);
|
||||||
|
|
||||||
LOG_DEBUG(Service_HTTP, "called, url_size={}, url={}, method={}", url_size, url, method);
|
LOG_DEBUG(Service_HTTP, "called, url_size={}, url={}, method={}", url_size, url, method);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user