changed comments to english

This commit is contained in:
BeZide93 2025-11-02 00:53:58 +01:00
parent fafbaedf98
commit 6447d3512c
2 changed files with 45 additions and 45 deletions

View file

@ -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 {

View file

@ -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(