mirror of
https://git.ryujinx.app/kenji-nx/ryujinx.git
synced 2025-12-15 19:37:05 +00:00
changed comments to english
This commit is contained in:
parent
fafbaedf98
commit
6447d3512c
2 changed files with 45 additions and 45 deletions
|
|
@ -30,15 +30,15 @@ private fun isHex16(name: String): Boolean =
|
||||||
|
|
||||||
data class SaveFolderMeta(
|
data class SaveFolderMeta(
|
||||||
val dir: File,
|
val dir: File,
|
||||||
val indexHex: String, // z.B. 0000000000000008 oder 000000000000000a
|
val indexHex: String, // e.g., 0000000000000008 or 000000000000000a
|
||||||
val titleId: String?, // aus TITLEID.txt (lowercase) oder geerbter Wert
|
val titleId: String?, // from TITLEID.txt (lowercase) or inherited value
|
||||||
val titleName: String?, // zweite Zeile aus TITLEID.txt, falls vorhanden
|
val titleName: String?, // second line from TITLEID.txt, if present
|
||||||
val hasMarker: Boolean // true, wenn dieser Ordner die TITLEID.txt selbst hat
|
val hasMarker: Boolean // true if this folder itself contains TITLEID.txt
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scannt .../bis/user/save; ordnet nummerierte Ordner (16 Hex-Zeichen) per „Marker-Vererbung“:
|
* Scans .../bis/user/save; assigns numbered folders (16 hex chars) via “marker inheritance”:
|
||||||
* Ein Ordner ohne TITLEID.txt gehört zum zuletzt gesehenen Ordner mit TITLEID.txt davor.
|
* A folder without TITLEID.txt belongs to the most recent preceding folder that does contain TITLEID.txt.
|
||||||
*/
|
*/
|
||||||
fun listSaveFolders(activity: Activity): List<SaveFolderMeta> {
|
fun listSaveFolders(activity: Activity): List<SaveFolderMeta> {
|
||||||
val root = savesRootExternal(activity)
|
val root = savesRootExternal(activity)
|
||||||
|
|
@ -72,18 +72,18 @@ fun listSaveFolders(activity: Activity): List<SaveFolderMeta> {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== TitleID-Kandidaten (Base/Update tolerant) & Gruppierung ===== */
|
/* ===== TitleID candidates (base/update tolerant) & grouping ===== */
|
||||||
|
|
||||||
private fun hex16CandidatesForSaves(id: String): List<String> {
|
private fun hex16CandidatesForSaves(id: String): List<String> {
|
||||||
val lc = id.trim().lowercase(Locale.ROOT)
|
val lc = id.trim().lowercase(Locale.ROOT)
|
||||||
if (!isHex16(lc)) return listOf(lc)
|
if (!isHex16(lc)) return listOf(lc)
|
||||||
val head = lc.substring(0, 13) // erste 13 Zeichen
|
val head = lc.substring(0, 13) // first 13 characters
|
||||||
val base = head + "000" // ...000
|
val base = head + "000" // ...000
|
||||||
val upd = head + "800" // ...800
|
val upd = head + "800" // ...800
|
||||||
return listOf(lc, base, upd).distinct()
|
return listOf(lc, base, upd).distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Alle Save-Ordner einer TitleID-Gruppe (Marker-Vererbung), Base/Update tolerant. */
|
/** All save folders of a TitleID group (marker inheritance), tolerant of base/update IDs. */
|
||||||
private fun listSaveGroupForTitle(activity: Activity, titleId: String): List<SaveFolderMeta> {
|
private fun listSaveGroupForTitle(activity: Activity, titleId: String): List<SaveFolderMeta> {
|
||||||
val candidates = hex16CandidatesForSaves(titleId)
|
val candidates = hex16CandidatesForSaves(titleId)
|
||||||
val metas = listSaveFolders(activity)
|
val metas = listSaveFolders(activity)
|
||||||
|
|
@ -93,7 +93,7 @@ private fun listSaveGroupForTitle(activity: Activity, titleId: String): List<Sav
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bevorzugt den Ordner mit TITLEID.txt; Fallback: lexikografisch kleinster der Gruppe. */
|
/** Prefer the folder with TITLEID.txt; fallback: lexicographically smallest in the group. */
|
||||||
private fun pickSaveDirWithMarker(activity: Activity, titleId: String): File? {
|
private fun pickSaveDirWithMarker(activity: Activity, titleId: String): File? {
|
||||||
val group = listSaveGroupForTitle(activity, titleId)
|
val group = listSaveGroupForTitle(activity, titleId)
|
||||||
val marker = group.firstOrNull { it.hasMarker }?.dir
|
val marker = group.firstOrNull { it.hasMarker }?.dir
|
||||||
|
|
@ -110,8 +110,8 @@ private fun sanitizeFileName(s: String): String =
|
||||||
s.replace(Regex("""[\\/:*?"<>|]"""), "_").trim().ifBlank { "save" }
|
s.replace(Regex("""[\\/:*?"<>|]"""), "_").trim().ifBlank { "save" }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schreibt eine ZIP im Format: ZIP enthält Ordner "TITLEID_UPPER/…" und darin
|
* Writes a ZIP with the structure: the ZIP contains folder "TITLEID_UPPER/…" and inside it
|
||||||
* den reinen Inhalt des Ordners "0" (nicht den Ordner 0 selbst).
|
* the raw contents of folder "0" (not the folder "0" itself).
|
||||||
*/
|
*/
|
||||||
fun exportSaveToZip(
|
fun exportSaveToZip(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
|
|
@ -168,7 +168,7 @@ fun exportSaveToZip(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hilfsname für CreateDocument: "<Name>_save_YYYY-MM-DD.zip" (aus Marker-Ordner) */
|
/** Helper name for CreateDocument: "<Name>_save_YYYY-MM-DD.zip" (derived from marker folder) */
|
||||||
fun buildSuggestedExportName(activity: Activity, titleId: String): String {
|
fun buildSuggestedExportName(activity: Activity, titleId: String): String {
|
||||||
val primary = pickSaveDirWithMarker(activity, titleId)
|
val primary = pickSaveDirWithMarker(activity, titleId)
|
||||||
val displayName = if (primary != null) {
|
val displayName = if (primary != null) {
|
||||||
|
|
@ -214,7 +214,7 @@ private fun clearDirectory(dir: File) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erwartet ZIP mit Struktur: TITLEID_UPPER/… – schreibt NUR in den Ordner mit TITLEID.txt.
|
* Expects a ZIP with structure: TITLEID_UPPER/… — writes ONLY into the folder that has TITLEID.txt.
|
||||||
*/
|
*/
|
||||||
fun importSaveFromZip(
|
fun importSaveFromZip(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
|
|
@ -223,11 +223,11 @@ fun importSaveFromZip(
|
||||||
): ImportResult {
|
): ImportResult {
|
||||||
val cr: ContentResolver = activity.contentResolver
|
val cr: ContentResolver = activity.contentResolver
|
||||||
|
|
||||||
// TitelID-Ordner im ZIP finden
|
// Find TitleID folder inside the ZIP
|
||||||
var titleIdFromZip: String? = null
|
var titleIdFromZip: String? = null
|
||||||
var totalBytes = 0L
|
var totalBytes = 0L
|
||||||
|
|
||||||
// Pass 1: Top-Level TitelID und Total ermitteln
|
// Pass 1: determine top-level TitleID and total size
|
||||||
runCatching {
|
runCatching {
|
||||||
cr.openInputStream(zipUri)?.use { ins ->
|
cr.openInputStream(zipUri)?.use { ins ->
|
||||||
ZipInputStream(BufferedInputStream(ins)).use { zis ->
|
ZipInputStream(BufferedInputStream(ins)).use { zis ->
|
||||||
|
|
@ -248,7 +248,7 @@ fun importSaveFromZip(
|
||||||
if (titleIdFromZip == null) return ImportResult(false, "error importing save. missing TITLEID folder")
|
if (titleIdFromZip == null) return ImportResult(false, "error importing save. missing TITLEID folder")
|
||||||
val tidZip = titleIdFromZip!!.lowercase(Locale.ROOT)
|
val tidZip = titleIdFromZip!!.lowercase(Locale.ROOT)
|
||||||
|
|
||||||
// Größe nur unterhalb /<TITLEID>/… summieren
|
// Sum size only for paths under /<TITLEID>/…
|
||||||
runCatching {
|
runCatching {
|
||||||
cr.openInputStream(zipUri)?.use { ins ->
|
cr.openInputStream(zipUri)?.use { ins ->
|
||||||
ZipInputStream(BufferedInputStream(ins)).use { zis ->
|
ZipInputStream(BufferedInputStream(ins)).use { zis ->
|
||||||
|
|
@ -264,18 +264,18 @@ fun importSaveFromZip(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ziel: NUR der Marker-Ordner
|
// Target: ONLY the marker folder
|
||||||
val targetRoot = pickSaveDirWithMarker(activity, tidZip)
|
val targetRoot = pickSaveDirWithMarker(activity, tidZip)
|
||||||
?: pickSaveDirWithMarker(activity, tidZip.uppercase(Locale.ROOT))
|
?: pickSaveDirWithMarker(activity, tidZip.uppercase(Locale.ROOT))
|
||||||
?: return ImportResult(false, "error importing save. start game once.")
|
?: return ImportResult(false, "error importing save. start game once.")
|
||||||
|
|
||||||
// 0/1 vorbereiten (leeren)
|
// Prepare 0/1 (clear)
|
||||||
val zero = File(targetRoot, "0").apply { mkdirs() }
|
val zero = File(targetRoot, "0").apply { mkdirs() }
|
||||||
val one = File(targetRoot, "1").apply { mkdirs() }
|
val one = File(targetRoot, "1").apply { mkdirs() }
|
||||||
clearDirectory(zero)
|
clearDirectory(zero)
|
||||||
clearDirectory(one)
|
clearDirectory(one)
|
||||||
|
|
||||||
// Pass 2: extrahieren
|
// Pass 2: extract
|
||||||
var written = 0L
|
var written = 0L
|
||||||
val buf = ByteArray(DEFAULT_BUFFER_SIZE)
|
val buf = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
|
|
||||||
|
|
@ -296,13 +296,13 @@ fun importSaveFromZip(
|
||||||
out1.parentFile?.mkdirs()
|
out1.parentFile?.mkdirs()
|
||||||
|
|
||||||
if (!ensureInside(zero, out0) || !ensureInside(one, out1)) {
|
if (!ensureInside(zero, out0) || !ensureInside(one, out1)) {
|
||||||
// Zip-Slip Schutz
|
// Zip-slip protection
|
||||||
zis.closeEntry()
|
zis.closeEntry()
|
||||||
ze = zis.nextEntry
|
ze = zis.nextEntry
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Einmal lesen, zweimal schreiben
|
// Read once, write twice
|
||||||
val os0 = out0.outputStream()
|
val os0 = out0.outputStream()
|
||||||
val os1 = out1.outputStream()
|
val os1 = out1.outputStream()
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ class HomeViews {
|
||||||
var isFabVisible by remember { mutableStateOf(true) }
|
var isFabVisible by remember { mutableStateOf(true) }
|
||||||
val isNavigating = remember { mutableStateOf(false) }
|
val isNavigating = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Save Manager State
|
// Save Manager state
|
||||||
val openSavesDialog = remember { mutableStateOf(false) }
|
val openSavesDialog = remember { mutableStateOf(false) }
|
||||||
val saveImportBusy = remember { mutableStateOf(false) }
|
val saveImportBusy = remember { mutableStateOf(false) }
|
||||||
val saveExportBusy = remember { mutableStateOf(false) }
|
val saveExportBusy = remember { mutableStateOf(false) }
|
||||||
|
|
@ -143,7 +143,7 @@ class HomeViews {
|
||||||
contract = ActivityResultContracts.OpenDocument()
|
contract = ActivityResultContracts.OpenDocument()
|
||||||
) { uri: Uri? ->
|
) { uri: Uri? ->
|
||||||
val act = activity
|
val act = activity
|
||||||
// Guard auf ausgewähltes Spiel – optional
|
// Optional guard: ensure a selected game
|
||||||
val tIdNow = viewModel.mainViewModel?.selected?.titleId.orEmpty()
|
val tIdNow = viewModel.mainViewModel?.selected?.titleId.orEmpty()
|
||||||
if (uri != null && act != null && tIdNow.isNotEmpty()) {
|
if (uri != null && act != null && tIdNow.isNotEmpty()) {
|
||||||
saveImportBusy.value = true
|
saveImportBusy.value = true
|
||||||
|
|
@ -195,7 +195,7 @@ class HomeViews {
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
// NEW: Launcher für Amiibo (OpenDocument)
|
// NEW: Launcher for Amiibo (OpenDocument)
|
||||||
val pickAmiiboLauncher = rememberLauncherForActivityResult(
|
val pickAmiiboLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.OpenDocument()
|
contract = ActivityResultContracts.OpenDocument()
|
||||||
) { uri: Uri? ->
|
) { uri: Uri? ->
|
||||||
|
|
@ -220,7 +220,7 @@ class HomeViews {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW: Cheats Import (.txt)
|
// NEW: Cheats import (.txt)
|
||||||
val importCheatLauncher = rememberLauncherForActivityResult(
|
val importCheatLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.OpenDocument()
|
contract = ActivityResultContracts.OpenDocument()
|
||||||
) { uri: Uri? ->
|
) { uri: Uri? ->
|
||||||
|
|
@ -228,7 +228,7 @@ class HomeViews {
|
||||||
val act = viewModel.activity
|
val act = viewModel.activity
|
||||||
val titleId = gm?.titleId ?: ""
|
val titleId = gm?.titleId ?: ""
|
||||||
if (uri != null && act != null && titleId.isNotEmpty()) {
|
if (uri != null && act != null && titleId.isNotEmpty()) {
|
||||||
// nur .txt akzeptieren
|
// accept only .txt
|
||||||
val okExt = runCatching {
|
val okExt = runCatching {
|
||||||
DocumentFile.fromSingleUri(act, uri)?.name?.lowercase()?.endsWith(".txt") == true
|
DocumentFile.fromSingleUri(act, uri)?.name?.lowercase()?.endsWith(".txt") == true
|
||||||
}.getOrElse { false }
|
}.getOrElse { false }
|
||||||
|
|
@ -240,7 +240,7 @@ class HomeViews {
|
||||||
val res = importCheatTxt(act, titleId, uri)
|
val res = importCheatTxt(act, titleId, uri)
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
Toast.makeText(act, "Imported: ${res.getOrNull()?.name}", Toast.LENGTH_SHORT).show()
|
Toast.makeText(act, "Imported: ${res.getOrNull()?.name}", Toast.LENGTH_SHORT).show()
|
||||||
// danach Liste aktualisieren
|
// then refresh list
|
||||||
cheatsForSelected.value = loadCheatsFromDisk(act, titleId)
|
cheatsForSelected.value = loadCheatsFromDisk(act, titleId)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(act, "Import failed: ${res.exceptionOrNull()?.message}", Toast.LENGTH_LONG).show()
|
Toast.makeText(act, "Import failed: ${res.exceptionOrNull()?.message}", Toast.LENGTH_LONG).show()
|
||||||
|
|
@ -255,7 +255,7 @@ class HomeViews {
|
||||||
val act = viewModel.activity
|
val act = viewModel.activity
|
||||||
val titleId = gm?.titleId ?: ""
|
val titleId = gm?.titleId ?: ""
|
||||||
if (uri != null && act != null && titleId.isNotEmpty()) {
|
if (uri != null && act != null && titleId.isNotEmpty()) {
|
||||||
// Persist permission (lesen)
|
// Persist permission (read)
|
||||||
try {
|
try {
|
||||||
act.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
act.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {}
|
||||||
|
|
@ -277,7 +277,7 @@ class HomeViews {
|
||||||
"Copying… ${(prog.fraction * 100).toInt()}%"
|
"Copying… ${(prog.fraction * 100).toInt()}%"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Liste aktualisieren
|
// refresh list
|
||||||
modsForSelected.value = listMods(act, titleId)
|
modsForSelected.value = listMods(act, titleId)
|
||||||
modsImportBusy.value = false
|
modsImportBusy.value = false
|
||||||
|
|
||||||
|
|
@ -438,7 +438,7 @@ class HomeViews {
|
||||||
Icon(Icons.Filled.Settings, contentDescription = "Settings")
|
Icon(Icons.Filled.Settings, contentDescription = "Settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = query.value,
|
value = query.value,
|
||||||
|
|
@ -453,13 +453,13 @@ class HomeViews {
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
colors = OutlinedTextFieldDefaults.colors(
|
colors = OutlinedTextFieldDefaults.colors(
|
||||||
focusedContainerColor = Color.Transparent,
|
focusedContainerColor = Color.Transparent,
|
||||||
unfocusedContainerColor = Color.Transparent,
|
unfocusedContainerColor = Color.Transparent,
|
||||||
disabledContainerColor = Color.Transparent,
|
disabledContainerColor = Color.Transparent,
|
||||||
errorContainerColor = Color.Transparent,
|
errorContainerColor = Color.Transparent,
|
||||||
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
||||||
unfocusedBorderColor = MaterialTheme.colorScheme.outline,
|
unfocusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -711,12 +711,12 @@ class HomeViews {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
// LINKS: Import .txt
|
// LEFT: Import .txt
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
importCheatLauncher.launch(arrayOf("text/plain", "text/*", "*/*"))
|
importCheatLauncher.launch(arrayOf("text/plain", "text/*", "*/*"))
|
||||||
}) { Text("Import .txt") }
|
}) { Text("Import .txt") }
|
||||||
|
|
||||||
// RECHTS: Cancel + Save
|
// RIGHT: Cancel + Save
|
||||||
Row {
|
Row {
|
||||||
TextButton(onClick = { openCheatsDialog.value = false }) { Text("Cancel") }
|
TextButton(onClick = { openCheatsDialog.value = false }) { Text("Cancel") }
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
|
|
@ -803,7 +803,7 @@ class HomeViews {
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Import-Zeile
|
// Import row
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -833,7 +833,7 @@ class HomeViews {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Liste der Mods
|
// List of mods
|
||||||
if (modsForSelected.value.isEmpty()) {
|
if (modsForSelected.value.isEmpty()) {
|
||||||
Text("No mods found for this title.")
|
Text("No mods found for this title.")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -898,7 +898,7 @@ class HomeViews {
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
// Import-Button
|
// Import button
|
||||||
androidx.compose.material3.Button(
|
androidx.compose.material3.Button(
|
||||||
enabled = !saveImportBusy.value && !saveExportBusy.value &&
|
enabled = !saveImportBusy.value && !saveExportBusy.value &&
|
||||||
(viewModel.mainViewModel?.selected?.titleId?.isNotEmpty() == true),
|
(viewModel.mainViewModel?.selected?.titleId?.isNotEmpty() == true),
|
||||||
|
|
@ -909,7 +909,7 @@ class HomeViews {
|
||||||
}
|
}
|
||||||
) { Text("Import ZIP") }
|
) { Text("Import ZIP") }
|
||||||
|
|
||||||
// Export-Button
|
// Export button
|
||||||
androidx.compose.material3.Button(
|
androidx.compose.material3.Button(
|
||||||
enabled = !saveImportBusy.value && !saveExportBusy.value &&
|
enabled = !saveImportBusy.value && !saveExportBusy.value &&
|
||||||
(viewModel.mainViewModel?.selected?.titleId?.isNotEmpty() == true),
|
(viewModel.mainViewModel?.selected?.titleId?.isNotEmpty() == true),
|
||||||
|
|
@ -953,7 +953,7 @@ class HomeViews {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Shortcut-Dialog
|
// --- Shortcut dialog
|
||||||
if (showShortcutDialog.value) {
|
if (showShortcutDialog.value) {
|
||||||
val gm = viewModel.mainViewModel?.selected
|
val gm = viewModel.mainViewModel?.selected
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue