summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt34
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt70
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt47
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt55
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt32
-rw-r--r--src/android/app/src/main/res/drawable/ic_firmware.xml10
-rw-r--r--src/android/app/src/main/res/drawable/ic_log.xml10
-rw-r--r--src/android/app/src/main/res/values/strings.xml9
9 files changed, 270 insertions, 33 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 67bcf8491..bdc337501 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -19,10 +19,10 @@ import androidx.appcompat.app.AppCompatActivity
19import androidx.core.app.ActivityCompat 19import androidx.core.app.ActivityCompat
20import androidx.core.app.NotificationCompat 20import androidx.core.app.NotificationCompat
21import androidx.core.app.NotificationManagerCompat 21import androidx.core.app.NotificationManagerCompat
22import androidx.core.content.ContextCompat
23import androidx.core.view.ViewCompat 22import androidx.core.view.ViewCompat
24import androidx.core.view.WindowInsetsCompat 23import androidx.core.view.WindowInsetsCompat
25import androidx.core.view.updatePadding 24import androidx.core.view.updatePadding
25import androidx.documentfile.provider.DocumentFile
26import androidx.fragment.app.Fragment 26import androidx.fragment.app.Fragment
27import androidx.fragment.app.activityViewModels 27import androidx.fragment.app.activityViewModels
28import androidx.navigation.fragment.findNavController 28import androidx.navigation.fragment.findNavController
@@ -40,6 +40,7 @@ import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
40import org.yuzu.yuzu_emu.model.HomeSetting 40import org.yuzu.yuzu_emu.model.HomeSetting
41import org.yuzu.yuzu_emu.model.HomeViewModel 41import org.yuzu.yuzu_emu.model.HomeViewModel
42import org.yuzu.yuzu_emu.ui.main.MainActivity 42import org.yuzu.yuzu_emu.ui.main.MainActivity
43import org.yuzu.yuzu_emu.utils.FileUtil
43import org.yuzu.yuzu_emu.utils.GpuDriverHelper 44import org.yuzu.yuzu_emu.utils.GpuDriverHelper
44 45
45class HomeSettingsFragment : Fragment() { 46class HomeSettingsFragment : Fragment() {
@@ -109,6 +110,16 @@ class HomeSettingsFragment : Fragment() {
109 R.drawable.ic_unlock 110 R.drawable.ic_unlock
110 ) { mainActivity.getProdKey.launch(arrayOf("*/*")) }, 111 ) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
111 HomeSetting( 112 HomeSetting(
113 R.string.install_firmware,
114 R.string.install_firmware_description,
115 R.drawable.ic_firmware
116 ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) },
117 HomeSetting(
118 R.string.share_log,
119 R.string.share_log_description,
120 R.drawable.ic_log
121 ) { shareLog() },
122 HomeSetting(
112 R.string.about, 123 R.string.about,
113 R.string.about_description, 124 R.string.about_description,
114 R.drawable.ic_info_outline 125 R.drawable.ic_info_outline
@@ -262,6 +273,29 @@ class HomeSettingsFragment : Fragment() {
262 .show() 273 .show()
263 } 274 }
264 275
276 private fun shareLog() {
277 val file = DocumentFile.fromSingleUri(
278 mainActivity,
279 DocumentsContract.buildDocumentUri(
280 DocumentProvider.AUTHORITY,
281 "${DocumentProvider.ROOT_ID}/log/yuzu_log.txt"
282 )
283 )!!
284 if (file.exists()) {
285 val intent = Intent(Intent.ACTION_SEND)
286 .setDataAndType(file.uri, FileUtil.TEXT_PLAIN)
287 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
288 .putExtra(Intent.EXTRA_STREAM, file.uri)
289 startActivity(Intent.createChooser(intent, getText(R.string.share_log)))
290 } else {
291 Toast.makeText(
292 requireContext(),
293 getText(R.string.share_log_missing),
294 Toast.LENGTH_SHORT
295 ).show()
296 }
297 }
298
265 private fun setInsets() = 299 private fun setInsets() =
266 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> 300 ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
267 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 301 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
index 5f107b37d..36e63bb9e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
@@ -23,17 +23,14 @@ import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.YuzuApplication 23import org.yuzu.yuzu_emu.YuzuApplication
24import org.yuzu.yuzu_emu.features.DocumentProvider 24import org.yuzu.yuzu_emu.features.DocumentProvider
25import org.yuzu.yuzu_emu.getPublicFilesDir 25import org.yuzu.yuzu_emu.getPublicFilesDir
26import java.io.BufferedInputStream 26import org.yuzu.yuzu_emu.utils.FileUtil
27import java.io.BufferedOutputStream 27import java.io.BufferedOutputStream
28import java.io.File 28import java.io.File
29import java.io.FileOutputStream 29import java.io.FileOutputStream
30import java.io.FilenameFilter 30import java.io.FilenameFilter
31import java.io.IOException
32import java.io.InputStream
33import java.time.LocalDateTime 31import java.time.LocalDateTime
34import java.time.format.DateTimeFormatter 32import java.time.format.DateTimeFormatter
35import java.util.zip.ZipEntry 33import java.util.zip.ZipEntry
36import java.util.zip.ZipInputStream
37import java.util.zip.ZipOutputStream 34import java.util.zip.ZipOutputStream
38 35
39class ImportExportSavesFragment : DialogFragment() { 36class ImportExportSavesFragment : DialogFragment() {
@@ -125,33 +122,6 @@ class ImportExportSavesFragment : DialogFragment() {
125 } 122 }
126 123
127 /** 124 /**
128 * Extracts the save files located in the given zip file and copies them to the saves folder.
129 * @exception IOException if the file was being created outside of the target directory
130 */
131 private fun unzip(zipStream: InputStream, destDir: File): Boolean {
132 val zis = ZipInputStream(BufferedInputStream(zipStream))
133 var entry: ZipEntry? = zis.nextEntry
134 while (entry != null) {
135 val entryName = entry.name
136 val entryFile = File(destDir, entryName)
137 if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
138 zis.close()
139 throw IOException("Entry is outside of the target dir: " + entryFile.name)
140 }
141 if (entry.isDirectory) {
142 entryFile.mkdirs()
143 } else {
144 entryFile.parentFile?.mkdirs()
145 entryFile.createNewFile()
146 entryFile.outputStream().use { fos -> zis.copyTo(fos) }
147 }
148 entry = zis.nextEntry
149 }
150 zis.close()
151 return true
152 }
153
154 /**
155 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent. 125 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
156 */ 126 */
157 private fun exportSave() { 127 private fun exportSave() {
@@ -204,7 +174,7 @@ class ImportExportSavesFragment : DialogFragment() {
204 174
205 try { 175 try {
206 CoroutineScope(Dispatchers.IO).launch { 176 CoroutineScope(Dispatchers.IO).launch {
207 unzip(inputZip, cacheSaveDir) 177 FileUtil.unzip(inputZip, cacheSaveDir)
208 cacheSaveDir.list(filterTitleId)?.forEach { savePath -> 178 cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
209 File(savesFolder, savePath).deleteRecursively() 179 File(savesFolder, savePath).deleteRecursively()
210 File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true) 180 File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
new file mode 100644
index 000000000..c7880d8cc
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.os.Bundle
8import android.widget.Toast
9import androidx.appcompat.app.AppCompatActivity
10import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels
12import androidx.lifecycle.ViewModelProvider
13import com.google.android.material.dialog.MaterialAlertDialogBuilder
14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
15import org.yuzu.yuzu_emu.model.TaskViewModel
16
17
18class IndeterminateProgressDialogFragment : DialogFragment() {
19 private val taskViewModel: TaskViewModel by activityViewModels()
20
21 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
22 val titleId = requireArguments().getInt(TITLE)
23
24 val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
25 progressBinding.progressBar.isIndeterminate = true
26 val dialog = MaterialAlertDialogBuilder(requireContext())
27 .setTitle(titleId)
28 .setView(progressBinding.root)
29 .create()
30 dialog.setCanceledOnTouchOutside(false)
31
32 taskViewModel.isComplete.observe(this) { complete ->
33 if (complete) {
34 dialog.dismiss()
35 when (val result = taskViewModel.result.value) {
36 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
37 is MessageDialogFragment -> result.show(
38 parentFragmentManager,
39 MessageDialogFragment.TAG
40 )
41 }
42 taskViewModel.clear()
43 }
44 }
45
46 if (taskViewModel.isRunning.value == false) {
47 taskViewModel.runTask()
48 }
49 return dialog
50 }
51
52 companion object {
53 const val TAG = "IndeterminateProgressDialogFragment"
54
55 private const val TITLE = "Title"
56
57 fun newInstance(
58 activity: AppCompatActivity,
59 titleId: Int,
60 task: () -> Any
61 ): IndeterminateProgressDialogFragment {
62 val dialog = IndeterminateProgressDialogFragment()
63 val args = Bundle()
64 ViewModelProvider(activity)[TaskViewModel::class.java].task = task
65 args.putInt(TITLE, titleId)
66 dialog.arguments = args
67 return dialog
68 }
69 }
70}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
new file mode 100644
index 000000000..27ea725a5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel
9import androidx.lifecycle.viewModelScope
10import kotlinx.coroutines.Dispatchers
11import kotlinx.coroutines.launch
12
13class TaskViewModel : ViewModel() {
14 private val _result = MutableLiveData<Any>()
15 val result: LiveData<Any> = _result
16
17 private val _isComplete = MutableLiveData<Boolean>()
18 val isComplete: LiveData<Boolean> = _isComplete
19
20 private val _isRunning = MutableLiveData<Boolean>()
21 val isRunning: LiveData<Boolean> = _isRunning
22
23 lateinit var task: () -> Any
24
25 init {
26 clear()
27 }
28
29 fun clear() {
30 _result.value = Any()
31 _isComplete.value = false
32 _isRunning.value = false
33 }
34
35 fun runTask() {
36 if (_isRunning.value == true) {
37 return
38 }
39 _isRunning.value = true
40
41 viewModelScope.launch(Dispatchers.IO) {
42 val res = task()
43 _result.postValue(res)
44 _isComplete.postValue(true)
45 }
46 }
47}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 134085210..124f62f08 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -38,10 +38,13 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
38import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel 38import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
39import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity 39import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
40import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 40import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
41import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
41import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 42import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
42import org.yuzu.yuzu_emu.model.GamesViewModel 43import org.yuzu.yuzu_emu.model.GamesViewModel
43import org.yuzu.yuzu_emu.model.HomeViewModel 44import org.yuzu.yuzu_emu.model.HomeViewModel
44import org.yuzu.yuzu_emu.utils.* 45import org.yuzu.yuzu_emu.utils.*
46import java.io.File
47import java.io.FilenameFilter
45import java.io.IOException 48import java.io.IOException
46 49
47class MainActivity : AppCompatActivity(), ThemeProvider { 50class MainActivity : AppCompatActivity(), ThemeProvider {
@@ -319,6 +322,58 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
319 } 322 }
320 } 323 }
321 324
325 val getFirmware =
326 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
327 if (result == null)
328 return@registerForActivityResult
329
330 val inputZip = contentResolver.openInputStream(result)
331 if (inputZip == null) {
332 Toast.makeText(
333 applicationContext,
334 getString(R.string.fatal_error),
335 Toast.LENGTH_LONG
336 ).show()
337 return@registerForActivityResult
338 }
339
340 val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
341
342 val firmwarePath =
343 File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
344 val cacheFirmwareDir = File("${cacheDir.path}/registered/")
345
346 val task: () -> Any = {
347 var messageToShow: Any
348 try {
349 FileUtil.unzip(inputZip, cacheFirmwareDir)
350 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
351 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
352 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
353 MessageDialogFragment.newInstance(
354 R.string.firmware_installed_failure,
355 R.string.firmware_installed_failure_description
356 )
357 } else {
358 firmwarePath.deleteRecursively()
359 cacheFirmwareDir.copyRecursively(firmwarePath, true)
360 getString(R.string.save_file_imported_success)
361 }
362 } catch (e: Exception) {
363 messageToShow = getString(R.string.fatal_error)
364 } finally {
365 cacheFirmwareDir.deleteRecursively()
366 }
367 messageToShow
368 }
369
370 IndeterminateProgressDialogFragment.newInstance(
371 this,
372 R.string.firmware_installing,
373 task
374 ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
375 }
376
322 val getAmiiboKey = 377 val getAmiiboKey =
323 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 378 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
324 if (result == null) 379 if (result == null)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index 0a7b323b1..593dad8d3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -9,10 +9,14 @@ import android.net.Uri
9import android.provider.DocumentsContract 9import android.provider.DocumentsContract
10import androidx.documentfile.provider.DocumentFile 10import androidx.documentfile.provider.DocumentFile
11import org.yuzu.yuzu_emu.model.MinimalDocumentFile 11import org.yuzu.yuzu_emu.model.MinimalDocumentFile
12import java.io.BufferedInputStream
13import java.io.File
12import java.io.FileOutputStream 14import java.io.FileOutputStream
13import java.io.IOException 15import java.io.IOException
14import java.io.InputStream 16import java.io.InputStream
15import java.net.URLDecoder 17import java.net.URLDecoder
18import java.util.zip.ZipEntry
19import java.util.zip.ZipInputStream
16 20
17object FileUtil { 21object FileUtil {
18 const val PATH_TREE = "tree" 22 const val PATH_TREE = "tree"
@@ -276,6 +280,34 @@ object FileUtil {
276 return false 280 return false
277 } 281 }
278 282
283 /**
284 * Extracts the given zip file into the given directory.
285 * @exception IOException if the file was being created outside of the target directory
286 */
287 @Throws(SecurityException::class)
288 fun unzip(zipStream: InputStream, destDir: File): Boolean {
289 ZipInputStream(BufferedInputStream(zipStream)).use { zis ->
290 var entry: ZipEntry? = zis.nextEntry
291 while (entry != null) {
292 val entryName = entry.name
293 val entryFile = File(destDir, entryName)
294 if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
295 throw SecurityException("Entry is outside of the target dir: " + entryFile.name)
296 }
297 if (entry.isDirectory) {
298 entryFile.mkdirs()
299 } else {
300 entryFile.parentFile?.mkdirs()
301 entryFile.createNewFile()
302 entryFile.outputStream().use { fos -> zis.copyTo(fos) }
303 }
304 entry = zis.nextEntry
305 }
306 }
307
308 return true
309 }
310
279 fun isRootTreeUri(uri: Uri): Boolean { 311 fun isRootTreeUri(uri: Uri): Boolean {
280 val paths = uri.pathSegments 312 val paths = uri.pathSegments
281 return paths.size == 2 && PATH_TREE == paths[0] 313 return paths.size == 2 && PATH_TREE == paths[0]
diff --git a/src/android/app/src/main/res/drawable/ic_firmware.xml b/src/android/app/src/main/res/drawable/ic_firmware.xml
new file mode 100644
index 000000000..61f3485e4
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_firmware.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="960"
5 android:viewportHeight="960"
6 android:tint="?attr/colorControlNormal">
7 <path
8 android:fillColor="@android:color/white"
9 android:pathData="M160,840Q127,840 103.5,816.5Q80,793 80,760L80,200Q80,167 103.5,143.5Q127,120 160,120L720,120Q753,120 776.5,143.5Q800,167 800,200L800,280L840,280Q857,280 868.5,291.5Q880,303 880,320Q880,337 868.5,348.5Q857,360 840,360L800,360L800,440L840,440Q857,440 868.5,451.5Q880,463 880,480Q880,497 868.5,508.5Q857,520 840,520L800,520L800,600L840,600Q857,600 868.5,611.5Q880,623 880,640Q880,657 868.5,668.5Q857,680 840,680L800,680L800,760Q800,793 776.5,816.5Q753,840 720,840L160,840ZM160,760L720,760Q720,760 720,760Q720,760 720,760L720,200Q720,200 720,200Q720,200 720,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760ZM280,680L400,680Q417,680 428.5,668.5Q440,657 440,640L440,560Q440,543 428.5,531.5Q417,520 400,520L280,520Q263,520 251.5,531.5Q240,543 240,560L240,640Q240,657 251.5,668.5Q263,680 280,680ZM520,400L600,400Q617,400 628.5,388.5Q640,377 640,360L640,320Q640,303 628.5,291.5Q617,280 600,280L520,280Q503,280 491.5,291.5Q480,303 480,320L480,360Q480,377 491.5,388.5Q503,400 520,400ZM280,480L400,480Q417,480 428.5,468.5Q440,457 440,440L440,320Q440,303 428.5,291.5Q417,280 400,280L280,280Q263,280 251.5,291.5Q240,303 240,320L240,440Q240,457 251.5,468.5Q263,480 280,480ZM520,680L600,680Q617,680 628.5,668.5Q640,657 640,640L640,480Q640,463 628.5,451.5Q617,440 600,440L520,440Q503,440 491.5,451.5Q480,463 480,480L480,640Q480,657 491.5,668.5Q503,680 520,680ZM160,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760L160,760Q160,760 160,760Q160,760 160,760L160,200Q160,200 160,200Q160,200 160,200Z"/>
10</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_log.xml b/src/android/app/src/main/res/drawable/ic_log.xml
new file mode 100644
index 000000000..f55b9ad85
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_log.xml
@@ -0,0 +1,10 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="960"
5 android:viewportHeight="960"
6 android:tint="?attr/colorControlNormal">
7 <path
8 android:fillColor="@android:color/white"
9 android:pathData="M360,720L600,720Q617,720 628.5,708.5Q640,697 640,680Q640,663 628.5,651.5Q617,640 600,640L360,640Q343,640 331.5,651.5Q320,663 320,680Q320,697 331.5,708.5Q343,720 360,720ZM360,560L600,560Q617,560 628.5,548.5Q640,537 640,520Q640,503 628.5,491.5Q617,480 600,480L360,480Q343,480 331.5,491.5Q320,503 320,520Q320,537 331.5,548.5Q343,560 360,560ZM240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L527,80Q543,80 557.5,86Q572,92 583,103L777,297Q788,308 794,322.5Q800,337 800,353L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,320L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L560,360Q543,360 531.5,348.5Q520,337 520,320ZM240,160L240,160L240,320Q240,337 240,348.5Q240,360 240,360L240,360L240,160L240,320Q240,337 240,348.5Q240,360 240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z"/>
10</vector>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index b86f45385..0ae69afb4 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -96,6 +96,15 @@
96 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string> 96 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
97 <string name="import_saves">Import</string> 97 <string name="import_saves">Import</string>
98 <string name="export_saves">Export</string> 98 <string name="export_saves">Export</string>
99 <string name="install_firmware">Install firmware</string>
100 <string name="install_firmware_description">Firmware must be in a ZIP archive and is needed to boot some games</string>
101 <string name="firmware_installing">Installing firmware</string>
102 <string name="firmware_installed_success">Firmware installed successfully</string>
103 <string name="firmware_installed_failure">Firmware installation failed</string>
104 <string name="firmware_installed_failure_description">Verify that the ZIP contains valid firmware and try again.</string>
105 <string name="share_log">Share debug logs</string>
106 <string name="share_log_description">Share yuzu\'s log file to debug issues</string>
107 <string name="share_log_missing">No log file found</string>
99 108
100 <!-- About screen strings --> 109 <!-- About screen strings -->
101 <string name="gaia_is_not_real">Gaia isn\'t real</string> 110 <string name="gaia_is_not_real">Gaia isn\'t real</string>