diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt
index d5ca09906..8d3a53456 100644
--- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt
@@ -1,16 +1,30 @@
 package org.ryujinx.android.viewmodels
 
+import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.text.intl.Locale
 import androidx.compose.ui.text.toLowerCase
+import androidx.documentfile.provider.DocumentFile
 import com.anggrayudi.storage.SimpleStorageHelper
+import com.anggrayudi.storage.callback.FileCallback
+import com.anggrayudi.storage.file.DocumentFileCompat
+import com.anggrayudi.storage.file.DocumentFileType
+import com.anggrayudi.storage.file.copyFileTo
 import com.anggrayudi.storage.file.getAbsolutePath
 import com.google.gson.Gson
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import org.ryujinx.android.MainActivity
 import java.io.File
+import java.util.LinkedList
+import java.util.Queue
 import kotlin.math.max
 
 class TitleUpdateViewModel(val titleId: String) {
+    private var basePath: String
+    private var updateJsonName = "updates.json"
+    private var stagingUpdateJsonName = "staging_updates.json"
     private var storageHelper: SimpleStorageHelper
     var pathsState: SnapshotStateList<String>? = null
 
@@ -56,19 +70,123 @@ class TitleUpdateViewModel(val titleId: String) {
         storageHelper.openFilePicker(UpdateRequestCode)
     }
 
-    fun save(index: Int) {
+    fun save(
+        index: Int,
+        isCopying: MutableState<Boolean>,
+        openDialog: MutableState<Boolean>,
+        copyProgress: MutableState<Float>,
+        currentProgressName: MutableState<String>
+    ) {
         data?.apply {
             this.selected = ""
-            if(paths.isNotEmpty() && index > 0)
-            {
+            if (paths.isNotEmpty() && index > 0) {
                 val ind = max(index - 1, paths.count() - 1)
                 this.selected = paths[ind]
             }
             val gson = Gson()
-            val json = gson.toJson(this)
-            jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
-            File(jsonPath).mkdirs()
-            File("$jsonPath/updates.json").writeText(json)
+            var json = gson.toJson(this)
+            File(basePath).mkdirs()
+            File("$basePath/$stagingUpdateJsonName").writeText(json)
+
+            // Copy updates to internal data folder
+            val updatePath = "$basePath/update"
+            File(updatePath).mkdirs()
+
+            val ioScope = CoroutineScope(Dispatchers.IO)
+
+            var metadata = TitleUpdateMetadata()
+            var queue: Queue<String> = LinkedList()
+
+            var callback: FileCallback? = null
+
+            fun copy(path: String) {
+                isCopying.value = true
+                val documentFile = DocumentFileCompat.fromFullPath(
+                    storageHelper.storage.context,
+                    path,
+                    DocumentFileType.FILE
+                )
+                documentFile?.apply {
+                    val stagedPath = "$basePath/${name}"
+                    if (!File(stagedPath).exists()) {
+                        var file = this
+                        ioScope.launch {
+                            file.copyFileTo(
+                                storageHelper.storage.context,
+                                File(updatePath),
+                                callback = callback!!
+                            )
+
+                        }
+
+                        metadata.paths.add(stagedPath)
+                    }
+                }
+            }
+
+            fun finish() {
+                val savedUpdates = mutableListOf<String>()
+                File(updatePath).listFiles()?.forEach { savedUpdates.add(it.absolutePath) }
+                var missingFiles =
+                    savedUpdates.filter { i -> paths.find { it.endsWith(File(i).name) } == null }
+                for (path in missingFiles) {
+                    File(path).delete()
+                }
+
+                val selectedName = File(selected).name
+                val newSelectedPath = "$updatePath/$selectedName"
+                if (File(newSelectedPath).exists()) {
+                    metadata.selected = newSelectedPath
+                }
+
+                json = gson.toJson(metadata)
+                File("$basePath/$updateJsonName").writeText(json)
+
+                openDialog.value = false
+                isCopying.value = false
+            }
+            callback = object : FileCallback() {
+                override fun onFailed(errorCode: FileCallback.ErrorCode) {
+                    super.onFailed(errorCode)
+                }
+
+                override fun onStart(file: Any, workerThread: Thread): Long {
+                    copyProgress.value = 0f
+
+                    (file as DocumentFile)?.apply {
+                        currentProgressName.value = "Copying ${file.name}"
+                    }
+                    return super.onStart(file, workerThread)
+                }
+
+                override fun onReport(report: Report) {
+                    super.onReport(report)
+
+                    copyProgress.value = report.progress / 100f
+                }
+
+                override fun onCompleted(result: Any) {
+                    super.onCompleted(result)
+
+                    if (queue.isNotEmpty())
+                        copy(queue.remove())
+                    else {
+                        finish()
+                    }
+                }
+            }
+            for (path in paths) {
+                queue.add(path)
+            }
+
+            ioScope.launch {
+                if (queue.isNotEmpty()) {
+                    copy(queue.remove())
+                } else {
+                    finish()
+                }
+
+            }
         }
     }
 
@@ -84,12 +202,14 @@ class TitleUpdateViewModel(val titleId: String) {
     private var jsonPath: String
 
     init {
-        jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) + "/updates.json"
+        basePath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
+        val stagingJson = "${basePath}/${stagingUpdateJsonName}"
+        jsonPath = "${basePath}/${updateJsonName}"
 
         data = TitleUpdateMetadata()
-        if (File(jsonPath).exists()) {
+        if (File(stagingJson).exists()) {
             val gson = Gson()
-            data = gson.fromJson(File(jsonPath).readText(), TitleUpdateMetadata::class.java)
+            data = gson.fromJson(File(stagingJson).readText(), TitleUpdateMetadata::class.java)
 
             data?.apply {
                 val existingPaths = mutableListOf<String>()
@@ -115,4 +235,4 @@ class TitleUpdateViewModel(val titleId: String) {
 data class TitleUpdateMetadata(
     var selected: String = "",
     var paths: MutableList<String> = mutableListOf()
-)
\ No newline at end of file
+)
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt
index ff0900268..558bc559b 100644
--- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt
@@ -11,6 +11,7 @@ import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
+import androidx.compose.material3.LinearProgressIndicator
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.RadioButton
 import androidx.compose.material3.Surface
@@ -35,11 +36,16 @@ class TitleUpdateViews {
 
             val selected = remember { mutableStateOf(0) }
             viewModel.data?.apply {
-                    selected.value = paths.indexOf(this.selected) + 1
+                selected.value = paths.indexOf(this.selected) + 1
             }
 
             Column(modifier = Modifier.padding(16.dp)) {
-
+                val isCopying = remember {
+                    mutableStateOf(false)
+                }
+                val copyProgress = remember {
+                    mutableStateOf(0.0f)
+                }
                 Column {
                     Text(text = "Updates for ${name}", textAlign = TextAlign.Center)
                     Surface(
@@ -50,17 +56,19 @@ class TitleUpdateViews {
                     ) {
                         Column(
                             modifier = Modifier
-                                .height(300.dp)
+                                .height(250.dp)
                                 .fillMaxWidth()
                         ) {
                             Row(modifier = Modifier.padding(8.dp)) {
                                 RadioButton(
                                     selected = (selected.value == 0),
-                                    onClick = { selected.value = 0
-                                        })
+                                    onClick = {
+                                        selected.value = 0
+                                    })
                                 Text(
                                     text = "None",
-                                    modifier = Modifier.fillMaxWidth()
+                                    modifier = Modifier
+                                        .fillMaxWidth()
                                         .align(Alignment.CenterVertically)
                                 )
                             }
@@ -79,7 +87,8 @@ class TitleUpdateViews {
                                         onClick = { selected.value = i })
                                     Text(
                                         text = path,
-                                        modifier = Modifier.fillMaxWidth()
+                                        modifier = Modifier
+                                            .fillMaxWidth()
                                             .align(Alignment.CenterVertically)
                                     )
                                 }
@@ -113,12 +122,22 @@ class TitleUpdateViews {
                     }
 
                 }
+                var currentProgressName = remember {
+                    mutableStateOf("Starting Copy")
+                }
+                if (isCopying.value) {
+                    Text(text = "Copying updates to local storage")
+                    Text(text = currentProgressName.value)
+                    LinearProgressIndicator(
+                        modifier = Modifier.fillMaxWidth(),
+                        progress = copyProgress.value
+                    )
+                }
                 Spacer(modifier = Modifier.height(18.dp))
                 TextButton(
                     modifier = Modifier.align(Alignment.End),
                     onClick = {
-                        openDialog.value = false
-                        viewModel.save(selected.value)
+                        viewModel.save(selected.value, isCopying, openDialog, copyProgress, currentProgressName)
                     },
                 ) {
                     Text("Save")
@@ -126,4 +145,4 @@ class TitleUpdateViews {
             }
         }
     }
-}
\ No newline at end of file
+}