mirror of
https://git.ryujinx.app/kenji-nx/ryujinx.git
synced 2025-12-16 04:37:02 +00:00
changed comments to english
This commit is contained in:
parent
f2bf79ab6b
commit
249e61969e
5 changed files with 77 additions and 73 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue