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"
}
/* -------- Pfade -------- */
/* -------- Paths -------- */
private fun cheatsDirExternal(activity: Activity, titleId: String): File {
val base = activity.getExternalFilesDir(null) // /storage/emulated/0/Android/data/<pkg>/files
@ -37,7 +37,7 @@ private fun parseCheatNames(text: String): List<String> {
.toList()
}
/* -------- Public: Cheats laden -------- */
/* -------- Public: Load cheats -------- */
fun loadCheatsFromDisk(activity: Activity, titleId: String): List<CheatItem> {
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() }))
}
/* -------- Public: Auswahl SOFORT auf Disk anwenden -------- */
/* -------- Public: Apply selection IMMEDIATELY on disk -------- */
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 allTxt = dirs.flatMap { d ->
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 { "" }
if (text.isEmpty()) return
// Enabled-Set normalisieren: Keys sind "<BUILDID>-<SectionName>"
// Normalize enabled set: keys are "<BUILDID>-<SectionName>"
val enabledSections = enabledKeys.asSequence()
.mapNotNull { key ->
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 {
fun looksHexName(p: File): Boolean {
@ -121,8 +121,8 @@ private fun sectionNameFromHeader(line: String): String {
}
/**
* Entfernt EIN führendes Kommentarzeichen (';') + optionales Leerzeichen.
* Nur am absoluten Zeilenanfang (keine führenden Spaces erlaubt).
* Removes ONE leading comment marker (';') + optional space.
* Only at absolute column 0 (no leading spaces allowed).
*/
private fun uncommentOnce(raw: String): String {
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.
* Atmosphère nutzt ';' das verwenden wir ausschließlich.
* Comments out the line if it doesn't already start with ';'.
* Atmosphère uses ';' we strictly use that here as well.
*/
private fun commentOut(raw: String): String {
val t = raw.trimStart()
@ -143,12 +143,12 @@ private fun commentOut(raw: String): String {
}
/**
* Schreibt die Datei neu:
* - Keine Marker einfügen
* - Pro Section den Body gemäß enabled/disabled (enabledSections) kommentieren/entkommentieren
* - Reine Kommentar-/Leerzeilen (nur ';') bleiben erhalten
* Rewrites the file:
* - Do not insert markers
* - For each section, comment/uncomment the body according to enabledSections
* - 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>) {
while (lines.isNotEmpty() && lines.last().trim().isEmpty()) {
lines.removeAt(lines.lastIndex)
@ -156,18 +156,18 @@ private fun trimTrailingBlankLines(lines: MutableList<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()
trimTrailingBlankLines(buf)
return if (buf.isEmpty()) "" else buf.joinToString("\n") + "\n\n"
}
/**
* Schreibt die Datei neu:
* - Keine Marker einfügen
* - Pro Section den Body gemäß enabled/disabled (enabledSections) kommentieren/entkommentieren
* - Reine Kommentar-/Leerzeilen bleiben erhalten
* - Zwischen Sections genau EINE Leerzeile, am Ende genau EIN Newline.
* Rewrites the file:
* - Do not insert markers
* - For each section, comment/uncomment the body according to enabledSections
* - Preserve pure comment/blank lines
* - Ensure exactly ONE blank line between sections, and exactly ONE trailing newline at EOF.
*/
private fun rewriteCheatFile(original: String, enabledSections: Set<String>): String {
val lines = original.replace("\uFEFF", "").lines()
@ -183,18 +183,18 @@ private fun rewriteCheatFile(original: String, enabledSections: Set<String>): St
fun flushCurrent() {
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)
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')
out.append('[').append(sec).append(']').append('\n')
if (enabled) {
// Entkommentieren (nur ein führendes ';' an Spalte 0)
// Uncomment (only one leading ';' at column 0)
for (l in currentBlock) {
val trimmed = l.trim()
if (trimmed.isEmpty() || (trimmed.startsWith(";") && trimmed.length <= 1)) {
@ -210,7 +210,7 @@ private fun rewriteCheatFile(original: String, enabledSections: Set<String>): St
}
}
} 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) {
val t = l.trim()
if (t.isEmpty() || t.startsWith(";")) {
@ -242,20 +242,22 @@ private fun rewriteCheatFile(original: String, enabledSections: Set<String>): St
}
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)
if (headerText.isNotEmpty()) {
out.insert(0, headerText)
}
// Globale Normalisierung: 3+ Newlines -> 2, und am Ende genau EIN '\n'
var result = out.toString()
.replace(Regex("\n{3,}"), "\n\n") // nie mehr als 1 Leerzeile zwischen Abschnitten
.trimEnd() + "\n" // genau ein Newline am Ende
// Global normalization: collapse 3+ newlines to 2, and ensure exactly ONE trailing '\n'
val result = out.toString()
.replace(Regex("\n{3,}"), "\n\n") // never more than 1 blank line between sections
.trimEnd() + "\n" // exactly one newline at the end
return result
}
private fun cheatsDirPreferredForWrite(activity: Activity, titleId: String): File {
// Preferred write location: external app-specific storage
val dir = cheatsDirExternal(activity, titleId)
if (!dir.exists()) dir.mkdirs()
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.
* Gibt das Zieldatei-Objekt zurück, wenn erfolgreich.
* Imports a .txt from a SAF Uri into the title's cheats folder.
* Returns the destination File on success.
*/
fun importCheatTxt(activity: Activity, titleId: String, source: Uri): Result<File> {
return runCatching {
// Lese-Rechte ggf. dauerhaft sichern
// Persist read permission if possible
try {
activity.contentResolver.takePersistableUriPermission(
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,
// aber wir belassen die Datei so wie geliefert.
// After import: we could re-scan/normalize immediately,
// but we leave the file as provided.
target
}
}

View file

@ -10,7 +10,7 @@ import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
/* -------- Pfade -------- */
/* -------- Paths -------- */
private fun modsRootExternal(activity: Activity): File {
// /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 {
// TITLEID muss groß geschrieben sein
// TITLEID must be uppercase
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)
}
/* -------- Auflisten & Löschen -------- */
/* -------- List & Delete -------- */
fun listMods(activity: Activity, titleId: String): List<String> {
val titleIdUpper = titleId.trim().uppercase()
val dir = modsTitleDir(activity, titleIdUpper)
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 }
?.sortedBy { it.lowercase() }
?: emptyList()
@ -67,7 +67,7 @@ data class ImportProgress(
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(
val imported: List<String>,
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 importedMods = linkedSetOf<String>() // Reihenfolge stabil
val importedMods = linkedSetOf<String>() // stable order
return try {
activity.contentResolver.openInputStream(zipUri).use { raw ->
@ -103,15 +103,15 @@ fun importModsZip(
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
while (entry != null) {
val rawName = entry.name.replace('\\', '/') // normalisieren
// Sicherheitsfilter & leere Namen überspringen
val rawName = entry.name.replace('\\', '/') // normalize
// Safety filter & skip empty names
if (rawName.isBlank() || rawName.startsWith("/") || rawName.contains("..")) {
zis.closeEntry()
entry = zis.nextEntry
continue
}
// Top-Level: erster Segment vor dem ersten '/'
// Top-level: first segment before the first '/'
val slash = rawName.indexOf('/')
val topLevel = if (slash > 0) rawName.substring(0, slash) else rawName
if (topLevel.isBlank()) {
@ -120,18 +120,18 @@ fun importModsZip(
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 ""
// 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()) {
// Datei direkt im Top-Level (z.B. NAME.txt) ignorieren
// File directly at top level (e.g., NAME.txt) → ignore
zis.closeEntry()
entry = zis.nextEntry
continue
}
// Mod-Ordner vorbereiten (einmalig: ggf. alten Ordner entfernen)
// Prepare mod folder (once; remove old folder if present)
if (preparedMods.add(topLevel)) {
val modFolder = modDir(activity, titleIdUpper, topLevel)
if (modFolder.exists()) modFolder.safeDeleteRecursively()
@ -139,9 +139,9 @@ fun importModsZip(
importedMods += topLevel
}
// Zielpfad: .../TITLEID/<topLevel>/<relPath>
// Destination path: .../TITLEID/<topLevel>/<relPath>
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), "")
} else {
File(modDir(activity, titleIdUpper, topLevel), relPath)
@ -170,7 +170,7 @@ fun importModsZip(
ImportModsResult(imported = importedMods.toList(), ok = importedMods.isNotEmpty())
} catch (t: Throwable) {
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 ->
runCatching { modDir(activity, titleIdUpper, name).safeDeleteRecursively() }
}

View file

@ -148,14 +148,14 @@ class HomeViews {
val importBusy = remember { mutableStateOf(false) }
val importStatusText = remember { mutableStateOf("") }
// Shortcut-Dialog-State
// Shortcut dialog state
val showShortcutDialog = remember { mutableStateOf(false) }
val shortcutName = remember { mutableStateOf("") }
val context = LocalContext.current
val activity = LocalContext.current as? Activity
// NEW: Cheats Import (.txt)
// NEW: Cheats import (.txt)
val importCheatLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument()
) { uri: Uri? ->
@ -163,7 +163,7 @@ class HomeViews {
val act = viewModel.activity
val titleId = gm?.titleId ?: ""
if (uri != null && act != null && titleId.isNotEmpty()) {
// nur .txt akzeptieren
// only accept .txt
val okExt = runCatching {
DocumentFile.fromSingleUri(act, uri)?.name?.lowercase()?.endsWith(".txt") == true
}.getOrElse { false }
@ -175,7 +175,7 @@ class HomeViews {
val res = importCheatTxt(act, titleId, uri)
if (res.isSuccess) {
Toast.makeText(act, "Imported: ${res.getOrNull()?.name}", Toast.LENGTH_SHORT).show()
// danach Liste aktualisieren
// then refresh list
cheatsForSelected.value = loadCheatsFromDisk(act, titleId)
} else {
Toast.makeText(act, "Import failed: ${res.exceptionOrNull()?.message}", Toast.LENGTH_LONG).show()
@ -190,7 +190,7 @@ class HomeViews {
val act = viewModel.activity
val titleId = gm?.titleId ?: ""
if (uri != null && act != null && titleId.isNotEmpty()) {
// Persist permission (lesen)
// Persist permission (read)
try {
act.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
} catch (_: Exception) {}
@ -212,7 +212,7 @@ class HomeViews {
"Copying… ${(prog.fraction * 100).toInt()}%"
}
// Liste aktualisieren
// refresh list
modsForSelected.value = listMods(act, titleId)
importBusy.value = false
@ -519,7 +519,7 @@ class HomeViews {
thread {
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 tId = gm.titleId ?: ""
val act = viewModel.activity
@ -555,7 +555,7 @@ class HomeViews {
IconButton(onClick = {
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 tId = gmSel.titleId ?: ""
val act = viewModel.activity
@ -693,12 +693,12 @@ class HomeViews {
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
// LINKS: Import .txt
// LEFT: Import .txt
TextButton(onClick = {
importCheatLauncher.launch(arrayOf("text/plain", "text/*", "*/*"))
}) { Text("Import .txt") }
// RECHTS: Cancel + Save
// RIGHT: Cancel + Save
Row {
TextButton(onClick = { openCheatsDialog.value = false }) { Text("Cancel") }
TextButton(onClick = {
@ -788,7 +788,7 @@ class HomeViews {
modifier = Modifier.padding(bottom = 8.dp)
)
// Import-Zeile
// Import row
Row(
modifier = Modifier
.fillMaxWidth()
@ -818,7 +818,7 @@ class HomeViews {
)
}
// Liste der Mods
// List of mods
if (modsForSelected.value.isEmpty()) {
Text("No mods found for this title.")
} else {
@ -865,7 +865,7 @@ class HomeViews {
}
}
}
// --- Shortcut-Dialog
// --- Shortcut dialog
if (showShortcutDialog.value) {
val gm = viewModel.mainViewModel?.selected
AlertDialog(
@ -972,7 +972,7 @@ class HomeViews {
thread {
showLoading.value = true
// NEW: Push Cheats vor dem Start
// NEW: Push cheats before start
val tId = gameModel.titleId ?: ""
val act = viewModel.activity
@ -1069,7 +1069,7 @@ class HomeViews {
thread {
showLoading.value = true
// NEW: Push Cheats vor dem Start
// NEW: Push cheats before start
val tId = gameModel.titleId ?: ""
val act = viewModel.activity

View file

@ -531,7 +531,7 @@
Dynamic="Required All" />
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Rgltr.IRegulatorManager"
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]]"
Dynamic="Required All" />
<Type Name="Ryujinx.HLE.HOS.Tamper.Operations.OpMov`1[[System.UInt16]]"
@ -550,7 +550,7 @@
Dynamic="Required All" />
</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="System.Linq.Expressions" Dynamic="Required All" />
</Application>

View file

@ -51,7 +51,7 @@ namespace Ryujinx.HLE.HOS
_programs.Enqueue(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;
}
@ -141,11 +141,13 @@ namespace Ryujinx.HLE.HOS
// Re-enqueue the tampering program because the process is still valid.
_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)
{
return true;
}
Logger.Debug?.Print(LogClass.TamperMachine, $"Running tampering program {program.Name}");
try
@ -171,7 +173,7 @@ namespace Ryujinx.HLE.HOS
// Logger.Debug?.Print(LogClass.TamperMachine, ex.Message);
//}
// NEU: kompletter Stacktrace
// NEW: full stack trace
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);
}
}