changed comments to english

This commit is contained in:
BeZide93 2025-11-02 00:48:07 +01:00
parent f2bf79ab6b
commit 249e61969e
5 changed files with 77 additions and 73 deletions

View file

@ -12,7 +12,7 @@ data class CheatItem(val buildId: String, val name: String) {
val key get() = "$buildId-$name" val key get() = "$buildId-$name"
} }
/* -------- Pfade -------- */ /* -------- Paths -------- */
private fun cheatsDirExternal(activity: Activity, titleId: String): File { private fun cheatsDirExternal(activity: Activity, titleId: String): File {
val base = activity.getExternalFilesDir(null) // /storage/emulated/0/Android/data/<pkg>/files val base = activity.getExternalFilesDir(null) // /storage/emulated/0/Android/data/<pkg>/files
@ -37,7 +37,7 @@ private fun parseCheatNames(text: String): List<String> {
.toList() .toList()
} }
/* -------- Public: Cheats laden -------- */ /* -------- Public: Load cheats -------- */
fun loadCheatsFromDisk(activity: Activity, titleId: String): List<CheatItem> { fun loadCheatsFromDisk(activity: Activity, titleId: String): List<CheatItem> {
val dirs = allCheatDirs(activity, titleId) val dirs = allCheatDirs(activity, titleId)
@ -62,10 +62,10 @@ fun loadCheatsFromDisk(activity: Activity, titleId: String): List<CheatItem> {
.sortedWith(compareBy({ it.buildId.lowercase() }, { it.name.lowercase() })) .sortedWith(compareBy({ it.buildId.lowercase() }, { it.name.lowercase() }))
} }
/* -------- Public: Auswahl SOFORT auf Disk anwenden -------- */ /* -------- Public: Apply selection IMMEDIATELY on disk -------- */
fun applyCheatSelectionOnDisk(activity: Activity, titleId: String, enabledKeys: Set<String>) { fun applyCheatSelectionOnDisk(activity: Activity, titleId: String, enabledKeys: Set<String>) {
// Wir wählen genau EINE BUILDID-Datei (die „beste“), und schalten darin Sections. // We pick exactly ONE BUILDID file (the “best”) and toggle sections within it.
val dirs = allCheatDirs(activity, titleId) val dirs = allCheatDirs(activity, titleId)
val allTxt = dirs.flatMap { d -> val allTxt = dirs.flatMap { d ->
d.listFiles { f -> f.isFile && f.name.endsWith(".txt", ignoreCase = true) }?.toList() ?: emptyList() d.listFiles { f -> f.isFile && f.name.endsWith(".txt", ignoreCase = true) }?.toList() ?: emptyList()
@ -79,7 +79,7 @@ fun applyCheatSelectionOnDisk(activity: Activity, titleId: String, enabledKeys:
val text = runCatching { buildFile.readText(Charset.forName("UTF-8")) }.getOrElse { "" } val text = runCatching { buildFile.readText(Charset.forName("UTF-8")) }.getOrElse { "" }
if (text.isEmpty()) return if (text.isEmpty()) return
// Enabled-Set normalisieren: Keys sind "<BUILDID>-<SectionName>" // Normalize enabled set: keys are "<BUILDID>-<SectionName>"
val enabledSections = enabledKeys.asSequence() val enabledSections = enabledKeys.asSequence()
.mapNotNull { key -> .mapNotNull { key ->
val dash = key.indexOf('-') val dash = key.indexOf('-')
@ -97,7 +97,7 @@ fun applyCheatSelectionOnDisk(activity: Activity, titleId: String, enabledKeys:
} }
} }
/* -------- Implementierung: Auswahl anwenden (nur ';' als Kommentar) -------- */ /* -------- Implementation: apply selection (use ';' only for comments) -------- */
private fun pickBestBuildFile(files: List<File>): File { private fun pickBestBuildFile(files: List<File>): File {
fun looksHexName(p: File): Boolean { fun looksHexName(p: File): Boolean {
@ -121,8 +121,8 @@ private fun sectionNameFromHeader(line: String): String {
} }
/** /**
* Entfernt EIN führendes Kommentarzeichen (';') + optionales Leerzeichen. * Removes ONE leading comment marker (';') + optional space.
* Nur am absoluten Zeilenanfang (keine führenden Spaces erlaubt). * Only at absolute column 0 (no leading spaces allowed).
*/ */
private fun uncommentOnce(raw: String): String { private fun uncommentOnce(raw: String): String {
if (raw.isEmpty()) return raw if (raw.isEmpty()) return raw
@ -132,8 +132,8 @@ private fun uncommentOnce(raw: String): String {
} }
/** /**
* Kommentiert die Zeile aus, wenn sie nicht bereits mit ';' beginnt. * Comments out the line if it doesn't already start with ';'.
* Atmosphère nutzt ';' das verwenden wir ausschließlich. * Atmosphère uses ';' we strictly use that here as well.
*/ */
private fun commentOut(raw: String): String { private fun commentOut(raw: String): String {
val t = raw.trimStart() val t = raw.trimStart()
@ -143,12 +143,12 @@ private fun commentOut(raw: String): String {
} }
/** /**
* Schreibt die Datei neu: * Rewrites the file:
* - Keine Marker einfügen * - Do not insert markers
* - Pro Section den Body gemäß enabled/disabled (enabledSections) kommentieren/entkommentieren * - For each section, comment/uncomment the body according to enabledSections
* - Reine Kommentar-/Leerzeilen (nur ';') bleiben erhalten * - Preserve pure comment/blank lines (only ';')
*/ */
// Hilfsfunktionen: trailing Blankzeilen trimmen / Header normalisieren // Helpers: trim trailing blank lines / normalize header
private fun trimTrailingBlankLines(lines: MutableList<String>) { private fun trimTrailingBlankLines(lines: MutableList<String>) {
while (lines.isNotEmpty() && lines.last().trim().isEmpty()) { while (lines.isNotEmpty() && lines.last().trim().isEmpty()) {
lines.removeAt(lines.lastIndex) lines.removeAt(lines.lastIndex)
@ -156,18 +156,18 @@ private fun trimTrailingBlankLines(lines: MutableList<String>) {
} }
private fun joinHeaderBufferOnce(header: List<String>): String { private fun joinHeaderBufferOnce(header: List<String>): String {
// Header-Zeilen unverändert, aber trailing Blanks entfernen und genau 1 Leerzeile danach // Keep header lines unchanged, but remove trailing blanks and insert exactly one blank line after
val buf = header.toMutableList() val buf = header.toMutableList()
trimTrailingBlankLines(buf) trimTrailingBlankLines(buf)
return if (buf.isEmpty()) "" else buf.joinToString("\n") + "\n\n" return if (buf.isEmpty()) "" else buf.joinToString("\n") + "\n\n"
} }
/** /**
* Schreibt die Datei neu: * Rewrites the file:
* - Keine Marker einfügen * - Do not insert markers
* - Pro Section den Body gemäß enabled/disabled (enabledSections) kommentieren/entkommentieren * - For each section, comment/uncomment the body according to enabledSections
* - Reine Kommentar-/Leerzeilen bleiben erhalten * - Preserve pure comment/blank lines
* - Zwischen Sections genau EINE Leerzeile, am Ende genau EIN Newline. * - Ensure exactly ONE blank line between sections, and exactly ONE trailing newline at EOF.
*/ */
private fun rewriteCheatFile(original: String, enabledSections: Set<String>): String { private fun rewriteCheatFile(original: String, enabledSections: Set<String>): String {
val lines = original.replace("\uFEFF", "").lines() val lines = original.replace("\uFEFF", "").lines()
@ -183,18 +183,18 @@ private fun rewriteCheatFile(original: String, enabledSections: Set<String>): St
fun flushCurrent() { fun flushCurrent() {
val sec = currentSection ?: return val sec = currentSection ?: return
// trailing Blankzeilen im Block entfernen, damit keine doppelten Abstände wachsen // Remove trailing blank lines in the block to avoid growing gaps
trimTrailingBlankLines(currentBlock) trimTrailingBlankLines(currentBlock)
val enabled = enabledSections.contains(sec.lowercase()) val enabled = enabledSections.contains(sec.lowercase())
// Zwischen Sections genau eine Leerzeile einfügen (aber nicht vor der ersten) // Insert exactly one blank line between sections (but not before the first)
if (wroteAnySection) out.append('\n') if (wroteAnySection) out.append('\n')
out.append('[').append(sec).append(']').append('\n') out.append('[').append(sec).append(']').append('\n')
if (enabled) { if (enabled) {
// Entkommentieren (nur ein führendes ';' an Spalte 0) // Uncomment (only one leading ';' at column 0)
for (l in currentBlock) { for (l in currentBlock) {
val trimmed = l.trim() val trimmed = l.trim()
if (trimmed.isEmpty() || (trimmed.startsWith(";") && trimmed.length <= 1)) { if (trimmed.isEmpty() || (trimmed.startsWith(";") && trimmed.length <= 1)) {
@ -210,7 +210,7 @@ private fun rewriteCheatFile(original: String, enabledSections: Set<String>): St
} }
} }
} else { } else {
// Disablen: alles, was nicht schon mit ';' beginnt und nicht leer ist, auskommentieren // Disable: anything not starting with ';' and not blank gets commented out
for (l in currentBlock) { for (l in currentBlock) {
val t = l.trim() val t = l.trim()
if (t.isEmpty() || t.startsWith(";")) { if (t.isEmpty() || t.startsWith(";")) {
@ -242,20 +242,22 @@ private fun rewriteCheatFile(original: String, enabledSections: Set<String>): St
} }
flushCurrent() flushCurrent()
// Header vorn einsetzen (mit genau einer Leerzeile danach, falls vorhanden) // Prepend header (with exactly one blank line after, if present)
val headerText = joinHeaderBufferOnce(headerBuffer) val headerText = joinHeaderBufferOnce(headerBuffer)
if (headerText.isNotEmpty()) { if (headerText.isNotEmpty()) {
out.insert(0, headerText) out.insert(0, headerText)
} }
// Globale Normalisierung: 3+ Newlines -> 2, und am Ende genau EIN '\n' // Global normalization: collapse 3+ newlines to 2, and ensure exactly ONE trailing '\n'
var result = out.toString() val result = out.toString()
.replace(Regex("\n{3,}"), "\n\n") // nie mehr als 1 Leerzeile zwischen Abschnitten .replace(Regex("\n{3,}"), "\n\n") // never more than 1 blank line between sections
.trimEnd() + "\n" // genau ein Newline am Ende .trimEnd() + "\n" // exactly one newline at the end
return result return result
} }
private fun cheatsDirPreferredForWrite(activity: Activity, titleId: String): File { private fun cheatsDirPreferredForWrite(activity: Activity, titleId: String): File {
// Preferred write location: external app-specific storage
val dir = cheatsDirExternal(activity, titleId) val dir = cheatsDirExternal(activity, titleId)
if (!dir.exists()) dir.mkdirs() if (!dir.exists()) dir.mkdirs()
return dir return dir
@ -285,12 +287,12 @@ private fun uniqueFile(targetDir: File, baseName: String): File {
} }
/** /**
* Importiert eine .txt aus einem SAF-Uri in den Cheats-Ordner des Titels. * Imports a .txt from a SAF Uri into the title's cheats folder.
* Gibt das Zieldatei-Objekt zurück, wenn erfolgreich. * Returns the destination File on success.
*/ */
fun importCheatTxt(activity: Activity, titleId: String, source: Uri): Result<File> { fun importCheatTxt(activity: Activity, titleId: String, source: Uri): Result<File> {
return runCatching { return runCatching {
// Lese-Rechte ggf. dauerhaft sichern // Persist read permission if possible
try { try {
activity.contentResolver.takePersistableUriPermission( activity.contentResolver.takePersistableUriPermission(
source, source,
@ -310,8 +312,8 @@ fun importCheatTxt(activity: Activity, titleId: String, source: Uri): Result<Fil
} }
} }
// nach Import: optional sofort neu einlesen/normalisieren wäre möglich, // After import: we could re-scan/normalize immediately,
// aber wir belassen die Datei so wie geliefert. // but we leave the file as provided.
target target
} }
} }

View file

@ -10,7 +10,7 @@ import java.io.InputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
/* -------- Pfade -------- */ /* -------- Paths -------- */
private fun modsRootExternal(activity: Activity): File { private fun modsRootExternal(activity: Activity): File {
// /storage/emulated/0/Android/data/<pkg>/files/sdcard/atmosphere/contents // /storage/emulated/0/Android/data/<pkg>/files/sdcard/atmosphere/contents
@ -18,7 +18,7 @@ private fun modsRootExternal(activity: Activity): File {
} }
private fun modsTitleDir(activity: Activity, titleIdUpper: String): File { private fun modsTitleDir(activity: Activity, titleIdUpper: String): File {
// TITLEID muss groß geschrieben sein // TITLEID must be uppercase
return File(modsRootExternal(activity), titleIdUpper) return File(modsRootExternal(activity), titleIdUpper)
} }
@ -26,14 +26,14 @@ private fun modDir(activity: Activity, titleIdUpper: String, modName: String): F
return File(modsTitleDir(activity, titleIdUpper), modName) return File(modsTitleDir(activity, titleIdUpper), modName)
} }
/* -------- Auflisten & Löschen -------- */ /* -------- List & Delete -------- */
fun listMods(activity: Activity, titleId: String): List<String> { fun listMods(activity: Activity, titleId: String): List<String> {
val titleIdUpper = titleId.trim().uppercase() val titleIdUpper = titleId.trim().uppercase()
val dir = modsTitleDir(activity, titleIdUpper) val dir = modsTitleDir(activity, titleIdUpper)
if (!dir.exists() || !dir.isDirectory) return emptyList() if (!dir.exists() || !dir.isDirectory) return emptyList()
return dir.listFiles { f -> f.isDirectory } // NAME-Ordner return dir.listFiles { f -> f.isDirectory } // NAME folders
?.map { it.name } ?.map { it.name }
?.sortedBy { it.lowercase() } ?.sortedBy { it.lowercase() }
?: emptyList() ?: emptyList()
@ -67,7 +67,7 @@ data class ImportProgress(
get() = if (totalBytes <= 0) 0f else (bytesRead.coerceAtMost(totalBytes).toFloat() / totalBytes.toFloat()) get() = if (totalBytes <= 0) 0f else (bytesRead.coerceAtMost(totalBytes).toFloat() / totalBytes.toFloat())
} }
// NEU: Multi-Import. Top-Level-Ordner in der ZIP sind die Mod-Namen. // NEW: Multi-import. Top-level folders inside the ZIP are treated as mod names.
data class ImportModsResult( data class ImportModsResult(
val imported: List<String>, val imported: List<String>,
val ok: Boolean val ok: Boolean
@ -91,9 +91,9 @@ fun importModsZip(
} }
} }
// Für jeden Top-Level-Ordner (Mod-Name) einmalig vorbereiten (ggf. alten Ordner löschen). // Prepare each top-level folder (mod name) once (delete existing folder if present).
val preparedMods = mutableSetOf<String>() val preparedMods = mutableSetOf<String>()
val importedMods = linkedSetOf<String>() // Reihenfolge stabil val importedMods = linkedSetOf<String>() // stable order
return try { return try {
activity.contentResolver.openInputStream(zipUri).use { raw -> activity.contentResolver.openInputStream(zipUri).use { raw ->
@ -103,15 +103,15 @@ fun importModsZip(
val buffer = ByteArray(DEFAULT_BUFFER_SIZE) val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
while (entry != null) { while (entry != null) {
val rawName = entry.name.replace('\\', '/') // normalisieren val rawName = entry.name.replace('\\', '/') // normalize
// Sicherheitsfilter & leere Namen überspringen // Safety filter & skip empty names
if (rawName.isBlank() || rawName.startsWith("/") || rawName.contains("..")) { if (rawName.isBlank() || rawName.startsWith("/") || rawName.contains("..")) {
zis.closeEntry() zis.closeEntry()
entry = zis.nextEntry entry = zis.nextEntry
continue continue
} }
// Top-Level: erster Segment vor dem ersten '/' // Top-level: first segment before the first '/'
val slash = rawName.indexOf('/') val slash = rawName.indexOf('/')
val topLevel = if (slash > 0) rawName.substring(0, slash) else rawName val topLevel = if (slash > 0) rawName.substring(0, slash) else rawName
if (topLevel.isBlank()) { if (topLevel.isBlank()) {
@ -120,18 +120,18 @@ fun importModsZip(
continue continue
} }
// restlicher Pfad innerhalb des Mod-Ordners // Remaining path inside the mod folder
val relPath = if (slash >= 0 && slash + 1 < rawName.length) rawName.substring(slash + 1) else "" val relPath = if (slash >= 0 && slash + 1 < rawName.length) rawName.substring(slash + 1) else ""
// Nur Einträge verarbeiten, die innerhalb eines Modordners liegen (wir wollen NAME/... Strukturen) // Only process entries that are inside a mod folder (we expect NAME/... structures)
if (relPath.isBlank() && entry.isDirectory.not()) { if (relPath.isBlank() && entry.isDirectory.not()) {
// Datei direkt im Top-Level (z.B. NAME.txt) ignorieren // File directly at top level (e.g., NAME.txt) → ignore
zis.closeEntry() zis.closeEntry()
entry = zis.nextEntry entry = zis.nextEntry
continue continue
} }
// Mod-Ordner vorbereiten (einmalig: ggf. alten Ordner entfernen) // Prepare mod folder (once; remove old folder if present)
if (preparedMods.add(topLevel)) { if (preparedMods.add(topLevel)) {
val modFolder = modDir(activity, titleIdUpper, topLevel) val modFolder = modDir(activity, titleIdUpper, topLevel)
if (modFolder.exists()) modFolder.safeDeleteRecursively() if (modFolder.exists()) modFolder.safeDeleteRecursively()
@ -139,9 +139,9 @@ fun importModsZip(
importedMods += topLevel importedMods += topLevel
} }
// Zielpfad: .../TITLEID/<topLevel>/<relPath> // Destination path: .../TITLEID/<topLevel>/<relPath>
val dest = if (relPath.isBlank()) { val dest = if (relPath.isBlank()) {
// nur ein Ordner-Eintrag (NAME/ oder NAME/exefs/) // just a directory entry (NAME/ or NAME/exefs/)
File(modDir(activity, titleIdUpper, topLevel), "") File(modDir(activity, titleIdUpper, topLevel), "")
} else { } else {
File(modDir(activity, titleIdUpper, topLevel), relPath) File(modDir(activity, titleIdUpper, topLevel), relPath)
@ -170,7 +170,7 @@ fun importModsZip(
ImportModsResult(imported = importedMods.toList(), ok = importedMods.isNotEmpty()) ImportModsResult(imported = importedMods.toList(), ok = importedMods.isNotEmpty())
} catch (t: Throwable) { } catch (t: Throwable) {
Log.w("ModFs", "importModsZip failed: ${t.message}") Log.w("ModFs", "importModsZip failed: ${t.message}")
// Best effort: schon angelegte Mods sauber entfernen // Best effort: clean up already created mod folders
importedMods.forEach { name -> importedMods.forEach { name ->
runCatching { modDir(activity, titleIdUpper, name).safeDeleteRecursively() } runCatching { modDir(activity, titleIdUpper, name).safeDeleteRecursively() }
} }

View file

@ -148,14 +148,14 @@ class HomeViews {
val importBusy = remember { mutableStateOf(false) } val importBusy = remember { mutableStateOf(false) }
val importStatusText = remember { mutableStateOf("") } val importStatusText = remember { mutableStateOf("") }
// Shortcut-Dialog-State // Shortcut dialog state
val showShortcutDialog = remember { mutableStateOf(false) } val showShortcutDialog = remember { mutableStateOf(false) }
val shortcutName = remember { mutableStateOf("") } val shortcutName = remember { mutableStateOf("") }
val context = LocalContext.current val context = LocalContext.current
val activity = LocalContext.current as? Activity val activity = LocalContext.current as? Activity
// NEW: Cheats Import (.txt) // NEW: Cheats import (.txt)
val importCheatLauncher = rememberLauncherForActivityResult( val importCheatLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument() contract = ActivityResultContracts.OpenDocument()
) { uri: Uri? -> ) { uri: Uri? ->
@ -163,7 +163,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 // only accept .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 }
@ -175,7 +175,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()
@ -190,7 +190,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) {}
@ -212,7 +212,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)
importBusy.value = false importBusy.value = false
@ -519,7 +519,7 @@ class HomeViews {
thread { thread {
showLoading.value = true showLoading.value = true
// NEW: Push Cheats vor dem Start (Auto-Start Pfad) // NEW: Push cheats before start (auto-start path)
val gm = viewModel.mainViewModel.loadGameModel.value!! val gm = viewModel.mainViewModel.loadGameModel.value!!
val tId = gm.titleId ?: "" val tId = gm.titleId ?: ""
val act = viewModel.activity val act = viewModel.activity
@ -555,7 +555,7 @@ class HomeViews {
IconButton(onClick = { IconButton(onClick = {
if (viewModel.mainViewModel?.selected != null) { if (viewModel.mainViewModel?.selected != null) {
// NEW: Push Cheats vor dem Start (Run-Button) // NEW: Push cheats before start (Run button)
val gmSel = viewModel.mainViewModel!!.selected!! val gmSel = viewModel.mainViewModel!!.selected!!
val tId = gmSel.titleId ?: "" val tId = gmSel.titleId ?: ""
val act = viewModel.activity val act = viewModel.activity
@ -693,12 +693,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 = {
@ -788,7 +788,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()
@ -818,7 +818,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 {
@ -865,7 +865,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(
@ -972,7 +972,7 @@ class HomeViews {
thread { thread {
showLoading.value = true showLoading.value = true
// NEW: Push Cheats vor dem Start // NEW: Push cheats before start
val tId = gameModel.titleId ?: "" val tId = gameModel.titleId ?: ""
val act = viewModel.activity val act = viewModel.activity
@ -1069,7 +1069,7 @@ class HomeViews {
thread { thread {
showLoading.value = true showLoading.value = true
// NEW: Push Cheats vor dem Start // NEW: Push cheats before start
val tId = gameModel.titleId ?: "" val tId = gameModel.titleId ?: ""
val act = viewModel.activity val act = viewModel.activity

View file

@ -531,7 +531,7 @@
Dynamic="Required All" /> Dynamic="Required All" />
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Rgltr.IRegulatorManager" <Type Name="Ryujinx.HLE.HOS.Services.Pcv.Rgltr.IRegulatorManager"
Dynamic="Required All" /> Dynamic="Required All" />
<!-- Explizit die generischen Tamper-Operationen rooten, die per MakeGenericType gebaut werden --> <!-- Explicitly root the generic tamper operations that are constructed via MakeGenericType -->
<Type Name="Ryujinx.HLE.HOS.Tamper.Operations.OpMov`1[[System.Byte]]" <Type Name="Ryujinx.HLE.HOS.Tamper.Operations.OpMov`1[[System.Byte]]"
Dynamic="Required All" /> Dynamic="Required All" />
<Type Name="Ryujinx.HLE.HOS.Tamper.Operations.OpMov`1[[System.UInt16]]" <Type Name="Ryujinx.HLE.HOS.Tamper.Operations.OpMov`1[[System.UInt16]]"
@ -550,7 +550,7 @@
Dynamic="Required All" /> Dynamic="Required All" />
</Assembly> </Assembly>
<!-- AOT/Trimming: dynamic binder & expression tree runtime behalten --> <!-- AOT/Trimming: keep the dynamic binder & expression tree runtime -->
<Assembly Name="Microsoft.CSharp" Dynamic="Required All" /> <Assembly Name="Microsoft.CSharp" Dynamic="Required All" />
<Assembly Name="System.Linq.Expressions" Dynamic="Required All" /> <Assembly Name="System.Linq.Expressions" Dynamic="Required All" />
</Application> </Application>

View file

@ -51,7 +51,7 @@ namespace Ryujinx.HLE.HOS
_programs.Enqueue(program); _programs.Enqueue(program);
_programDictionary.TryAdd($"{buildId}-{name}", program); _programDictionary.TryAdd($"{buildId}-{name}", program);
// NEU: Standardmäßig einschalten (bei Android gibt es (noch) keine UI, die EnableCheats aufruft) // NEW: Enable by default (on Android there's currently no UI that calls EnableCheats).
program.IsEnabled = true; program.IsEnabled = true;
} }
@ -141,11 +141,13 @@ namespace Ryujinx.HLE.HOS
// Re-enqueue the tampering program because the process is still valid. // Re-enqueue the tampering program because the process is still valid.
_programs.Enqueue(program); _programs.Enqueue(program);
// NEU: Wenn der Cheat (noch) disabled ist nur weiter rotieren, nicht ausführen.
// NEW: If the cheat is (still) disabled, keep rotating but do not execute.
if (!program.IsEnabled) if (!program.IsEnabled)
{ {
return true; return true;
} }
Logger.Debug?.Print(LogClass.TamperMachine, $"Running tampering program {program.Name}"); Logger.Debug?.Print(LogClass.TamperMachine, $"Running tampering program {program.Name}");
try try
@ -171,7 +173,7 @@ namespace Ryujinx.HLE.HOS
// Logger.Debug?.Print(LogClass.TamperMachine, ex.Message); // Logger.Debug?.Print(LogClass.TamperMachine, ex.Message);
//} //}
// NEU: kompletter Stacktrace // NEW: full stack trace
Logger.Debug?.Print(LogClass.TamperMachine, ex.ToString()); Logger.Debug?.Print(LogClass.TamperMachine, ex.ToString());
} }
@ -191,7 +193,7 @@ namespace Ryujinx.HLE.HOS
} }
} }
// Clear the input because player one is not conected. // Clear the input because player one is not connected.
Volatile.Write(ref _pressedKeys, 0); Volatile.Write(ref _pressedKeys, 0);
} }
} }