Skip to content

Commit 618a1bc

Browse files
Merge v1.2.0 system packs / System-Packs
Validated custom system packs, import hardening, bilingual GitHub documentation, and the v1.2.0 release candidate.
2 parents 13f28bd + 2b19348 commit 618a1bc

32 files changed

Lines changed: 2960 additions & 155 deletions

File tree

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,74 @@
1-
name: Bug report
2-
description: Report a problem with scanning, detection, extraction, updates, or UI.
3-
title: "[Bug]: "
1+
name: Fehler melden / Bug report
2+
description: Melde ein Problem mit Scan, Erkennung, Einsortieren, Updates oder UI. / Report an app problem.
3+
title: "[Fehler / Bug]: "
44
labels: ["bug"]
55
body:
66
- type: markdown
77
attributes:
88
value: |
9+
Danke, dass du Thor ROM Butler verbesserst. Bitte keine ROMs oder urheberrechtlich geschützten Dateien anhängen.
10+
911
Thanks for helping improve Thor ROM Butler. Please do not attach ROMs or copyrighted files.
1012
- type: input
1113
id: app-version
1214
attributes:
13-
label: App version
15+
label: App-Version / App version
1416
placeholder: "0.4.0"
1517
validations:
1618
required: true
1719
- type: input
1820
id: device
1921
attributes:
20-
label: Device and Android version
22+
label: Gerät und Android-Version / Device and Android version
2123
placeholder: "AYN Thor, Android 13"
2224
validations:
2325
required: true
2426
- type: dropdown
2527
id: area
2628
attributes:
27-
label: Area
29+
label: Bereich / Area
2830
options:
29-
- Scan
30-
- Detection
31-
- Review / assignment
32-
- Extraction / moving
33-
- Update check
34-
- Settings
35-
- Other
31+
- Scan / Scan
32+
- Erkennung / Detection
33+
- Zuordnung / Review
34+
- Einsortieren / Sorting
35+
- Update-Prüfung / Update check
36+
- Einstellungen / Settings
37+
- Sonstiges / Other
3638
validations:
3739
required: true
3840
- type: input
3941
id: archive
4042
attributes:
41-
label: Archive type and size
43+
label: Archivtyp und Größe / Archive type and size
4244
placeholder: "7z, 3.2 GB, LZMA2 if known"
4345
- type: textarea
4446
id: steps
4547
attributes:
46-
label: Steps to reproduce
47-
placeholder: "1. Put archive in Downloads\n2. Scan\n3. Tap Sort in ROMs"
48+
label: Schritte zum Nachstellen / Steps to reproduce
49+
placeholder: "1. Archiv in Downloads ablegen / Put archive in Downloads\n2. Scan starten / Start scan\n3. Einsortieren antippen / Tap Sort"
4850
validations:
4951
required: true
5052
- type: textarea
5153
id: expected
5254
attributes:
53-
label: Expected behavior
55+
label: Erwartetes Verhalten / Expected behavior
5456
validations:
5557
required: true
5658
- type: textarea
5759
id: actual
5860
attributes:
59-
label: Actual behavior
61+
label: Tatsächliches Verhalten / Actual behavior
6062
validations:
6163
required: true
6264
- type: textarea
6365
id: log
6466
attributes:
65-
label: Log output
66-
description: Use the Log screen's share button when possible.
67+
label: Log-Ausgabe / Log output
68+
description: Möglichst über die Teilen-Schaltfläche im Log exportieren. / Prefer the Log screen's share button.
6769
render: text
6870
- type: textarea
6971
id: screenshots
7072
attributes:
7173
label: Screenshots
72-
description: Optional. Blur file names if needed.
74+
description: Optional. Dateinamen bei Bedarf unkenntlich machen. / Blur file names if needed.

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
blank_issues_enabled: true
22
contact_links:
3-
- name: Releases and APK downloads
3+
- name: Releases und APK-Downloads / Releases and APK downloads
44
url: https://github.com/Strugglechen1337/ThorROMButler/releases
5-
about: Download the latest APK from GitHub Releases.
5+
about: Neueste APK über GitHub Releases herunterladen. / Download the latest APK from GitHub Releases.
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
name: Feature request
2-
description: Suggest an improvement or new supported system.
3-
title: "[Feature]: "
1+
name: Funktionswunsch / Feature request
2+
description: Schlage eine Verbesserung oder ein neues System vor. / Suggest an improvement or system.
3+
title: "[Wunsch / Feature]: "
44
labels: ["enhancement"]
55
body:
66
- type: textarea
77
id: problem
88
attributes:
9-
label: Problem or workflow
10-
placeholder: "What are you trying to do?"
9+
label: Problem oder Ablauf / Problem or workflow
10+
placeholder: "Was möchtest du erreichen? / What are you trying to do?"
1111
validations:
1212
required: true
1313
- type: textarea
1414
id: solution
1515
attributes:
16-
label: Suggested solution
17-
placeholder: "What should Thor ROM Butler do?"
16+
label: Lösungsvorschlag / Suggested solution
17+
placeholder: "Was soll Thor ROM Butler tun? / What should Thor ROM Butler do?"
1818
validations:
1919
required: true
2020
- type: textarea
2121
id: alternatives
2222
attributes:
23-
label: Alternatives considered
23+
label: Erwogene Alternativen / Alternatives considered
2424
- type: textarea
2525
id: context
2626
attributes:
27-
label: Additional context
28-
description: Device, emulator frontend, file types, screenshots, etc.
27+
label: Zusätzlicher Kontext / Additional context
28+
description: Gerät, Emulator-Frontend, Dateitypen, Screenshots usw. / Device, frontend, file types, screenshots, etc.

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ jobs:
2929

3030
- name: Build debug APK
3131
run: ./gradlew assembleDebug
32+
33+
- name: Compile Android UI tests
34+
run: ./gradlew assembleDebugAndroidTest

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,42 @@
22

33
All notable user-facing changes are documented here.
44

5+
## 1.2.0
6+
7+
### Deutsch
8+
9+
- Versionierte System-Packs: Die eingebauten Systemdefinitionen liegen jetzt
10+
als geprüftes JSON-Schema v1 mit sicherem Code-Fallback vor.
11+
- Eigene Systeme können in den Einstellungen angelegt, bearbeitet und entfernt
12+
sowie über `ThorRomButler-system-pack.json` importiert/exportiert werden.
13+
- Pack-Größe, Felder, IDs, Zielordner, Endungen und Magic-Regeln werden vor der
14+
Aktivierung geprüft. Eingebaute Systeme können nicht überschrieben werden.
15+
- Konfligierende Endungen werden sichtbar gewarnt und bleiben für die
16+
automatische Erkennung `UNKNOWN`.
17+
- Eine Importvorschau zeigt Pack-Name, Systemanzahl und Konflikte, bevor eigene
18+
Systeme ersetzt werden.
19+
- Einstellungs-Backups werden vollständig validiert und in einer atomaren
20+
DataStore-Transaktion übernommen; unsichere Zielordner werden abgelehnt.
21+
- GitHub-Beschreibung und Issue-Vorlagen sind durchgehend Deutsch/Englisch;
22+
Compose-Tests sichern Pack-Vorschau und Editor-Validierung ab.
23+
24+
### English
25+
26+
- Versioned system packs: built-in system definitions now use a validated JSON
27+
schema v1 with a safe code fallback.
28+
- Custom systems can be created, edited, and removed in Settings, then imported
29+
or exported through `ThorRomButler-system-pack.json`.
30+
- Pack size, fields, IDs, target folders, extensions, and magic rules are
31+
validated before activation. Built-in systems cannot be overwritten.
32+
- Conflicting extensions are shown as warnings and remain `UNKNOWN` for
33+
automatic detection.
34+
- An import preview shows the pack name, system count, and conflicts before
35+
custom systems are replaced.
36+
- Settings backups are validated completely and applied in one atomic DataStore
37+
transaction; unsafe target folders are rejected.
38+
- The GitHub description and issue forms are fully bilingual; Compose tests
39+
cover the pack preview and editor validation.
40+
541
## 1.1.1
642

743
### Deutsch

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ läuft aber auf jedem Android-Smartphone ab Android 13.
3434
- 🧠 **Detection Engine**: bestimmt das Zielsystem über Dateiendungen und Magic
3535
Bytes (inkl. ISO-, RVZ- und CHD-Header) - mit ehrlichen Confidence-Leveln
3636
(*sicher* / *wahrscheinlich* / *unbekannt*)
37+
- 🧩 **System-Packs & eigene Systeme**: zusätzliche Systeme lokal in den
38+
Einstellungen anlegen oder als geprüftes JSON-Pack importieren/exportieren.
39+
Vor dem Import zeigt eine Vorschau Inhalt und Konflikte; Mehrdeutigkeiten
40+
werden niemals automatisch zugeordnet.
3741
- 🛡️ **Keine Automatik bei Unklarheit**: Nur eindeutig erkannte ROMs bekommen
3842
einen Zielordner-Vorschlag. Du entscheidest immer selbst - einzeln oder mit
3943
"Alle übernehmen".
@@ -79,6 +83,10 @@ Game Gear · Saturn · Sega 32X · Atari 2600 · Atari 7800 · Atari Lynx ·
7983
PC Engine / TurboGrafx-16 · Neo Geo Pocket (Color) · WonderSwan (Color) ·
8084
Arcade (MAME) · Neo Geo
8185

86+
Weitere Systeme lassen sich lokal als eigenes System-Pack ergänzen. Das
87+
[Schema v1](docs/system-pack-schema-v1.md) ist dokumentiert; es gibt keine
88+
automatischen oder ungeprüften Pack-Downloads.
89+
8290
### Unterstützte Archive
8391

8492
| Format | Status |
@@ -185,6 +193,9 @@ newer.
185193
- 🧠 **Detection engine**: identifies the target system from file extensions and
186194
magic bytes, including ISO, RVZ, and CHD headers, with honest confidence levels
187195
(*certain* / *probable* / *unknown*)
196+
- 🧩 **System packs & custom systems**: add systems locally in Settings or
197+
import/export them as a validated JSON pack. A preview shows contents and
198+
conflicts before installation; ambiguities are never assigned automatically.
188199
- 🛡️ **No automation when unclear**: only clearly identified ROMs receive a
189200
suggested target folder. You always decide what gets applied, one item at a
190201
time or in bulk.
@@ -228,6 +239,10 @@ Game Gear · Saturn · Sega 32X · Atari 2600 · Atari 7800 · Atari Lynx ·
228239
PC Engine / TurboGrafx-16 · Neo Geo Pocket (Color) · WonderSwan (Color) ·
229240
Arcade (MAME) · Neo Geo
230241

242+
More systems can be added locally through a custom system pack. The
243+
[v1 schema](docs/system-pack-schema-v1.md#english) is documented; packs are
244+
never downloaded or activated automatically.
245+
231246
### Supported Archives
232247

233248
| Format | Status |

app/build.gradle.kts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ android {
2626
applicationId = "dev.thor.rombutler"
2727
minSdk = 33
2828
targetSdk = 37
29-
versionCode = 16
30-
versionName = "1.1.1"
29+
versionCode = 17
30+
versionName = "1.2.0"
3131

3232
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
3333
}
@@ -110,4 +110,11 @@ dependencies {
110110
testImplementation(libs.junit)
111111
testImplementation(libs.truth)
112112
testImplementation(libs.kotlinx.coroutines.test)
113+
testImplementation(libs.json)
114+
115+
androidTestImplementation(platform(libs.androidx.compose.bom))
116+
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
117+
androidTestImplementation(libs.androidx.test.runner)
118+
androidTestImplementation(libs.androidx.test.ext.junit)
119+
debugImplementation(libs.androidx.compose.ui.test.manifest)
113120
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package dev.thor.rombutler.ui.settings
2+
3+
import androidx.compose.ui.test.assertIsEnabled
4+
import androidx.compose.ui.test.assertIsNotEnabled
5+
import androidx.compose.ui.test.assertIsDisplayed
6+
import androidx.compose.ui.test.hasText
7+
import androidx.compose.ui.test.junit4.v2.createComposeRule
8+
import androidx.compose.ui.test.onNodeWithTag
9+
import androidx.compose.ui.test.onNodeWithText
10+
import androidx.compose.ui.test.performClick
11+
import androidx.compose.ui.test.performTextClearance
12+
import androidx.compose.ui.test.performTextInput
13+
import androidx.test.ext.junit.runners.AndroidJUnit4
14+
import dev.thor.rombutler.domain.detection.SystemRegistry
15+
import dev.thor.rombutler.domain.model.Confidence
16+
import dev.thor.rombutler.domain.model.SystemDefinition
17+
import dev.thor.rombutler.domain.model.SystemPack
18+
import dev.thor.rombutler.ui.theme.ThorRomButlerTheme
19+
import org.junit.Assert.assertEquals
20+
import org.junit.Assert.assertTrue
21+
import org.junit.Rule
22+
import org.junit.Test
23+
import org.junit.runner.RunWith
24+
25+
@RunWith(AndroidJUnit4::class)
26+
class SystemPackDialogsTest {
27+
28+
@get:Rule
29+
val compose = createComposeRule()
30+
31+
@Test
32+
fun importPreviewShowsExactPackAndConflictsBeforeConfirmation() {
33+
val registry = SystemRegistry()
34+
val custom = SystemDefinition(
35+
id = "preview",
36+
displayName = "Preview System",
37+
esdeFolder = "preview",
38+
extensions = mapOf("gba" to Confidence.CERTAIN),
39+
)
40+
val preview = SystemPackImportPreview(
41+
pack = SystemPack(1, "user.preview", "Preview Pack", listOf(custom)),
42+
conflicts = registry.conflictsForCustomSystems(listOf(custom)),
43+
)
44+
val registryState = registry.state.value
45+
var confirmed = false
46+
47+
compose.setContent {
48+
ThorRomButlerTheme {
49+
SystemPackManagerDialog(
50+
state = registryState,
51+
importPreview = preview,
52+
onSave = { _, _ -> },
53+
onDelete = {},
54+
onRequestImport = {},
55+
onConfirmImport = { confirmed = true },
56+
onCancelImport = {},
57+
onExport = {},
58+
onDismiss = {},
59+
)
60+
}
61+
}
62+
63+
compose.onNodeWithTag(SystemPackTestTags.IMPORT_PREVIEW).assertIsDisplayed()
64+
compose.onNodeWithText("Preview Pack").assertIsDisplayed()
65+
compose.onNode(hasText(".gba", substring = true)).assertIsDisplayed()
66+
compose.onNodeWithTag(SystemPackTestTags.CONFIRM_IMPORT).performClick()
67+
compose.runOnIdle { assertTrue(confirmed) }
68+
}
69+
70+
@Test
71+
fun editorRejectsBuiltInIdAndAcceptsSafeCustomDefinition() {
72+
val registry = SystemRegistry()
73+
val registryState = registry.state.value
74+
var saved: SystemDefinition? = null
75+
76+
compose.setContent {
77+
ThorRomButlerTheme {
78+
SystemPackManagerDialog(
79+
state = registryState,
80+
importPreview = null,
81+
onSave = { _, definition -> saved = definition },
82+
onDelete = {},
83+
onRequestImport = {},
84+
onConfirmImport = {},
85+
onCancelImport = {},
86+
onExport = {},
87+
onDismiss = {},
88+
)
89+
}
90+
}
91+
92+
compose.onNodeWithTag(SystemPackTestTags.ADD_SYSTEM).performClick()
93+
compose.onNodeWithTag(SystemPackTestTags.NAME_FIELD).performTextInput("Custom Handheld")
94+
compose.onNodeWithTag(SystemPackTestTags.ID_FIELD).performTextInput("gba")
95+
compose.onNodeWithTag(SystemPackTestTags.FOLDER_FIELD).performTextInput("customhandheld")
96+
compose.onNodeWithTag(SystemPackTestTags.EXTENSIONS_FIELD).performTextInput("gba")
97+
compose.onNodeWithTag(SystemPackTestTags.SAVE_SYSTEM).assertIsNotEnabled()
98+
99+
compose.onNodeWithTag(SystemPackTestTags.ID_FIELD).performTextClearance()
100+
compose.onNodeWithTag(SystemPackTestTags.ID_FIELD).performTextInput("customhandheld")
101+
compose.onNodeWithTag(SystemPackTestTags.SAVE_SYSTEM).assertIsEnabled().performClick()
102+
103+
compose.runOnIdle { assertEquals("customhandheld", saved?.id) }
104+
}
105+
}

app/src/main/java/dev/thor/rombutler/MainViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import dev.thor.rombutler.data.files.IncomingFile
1010
import dev.thor.rombutler.data.update.GitHubUpdateChecker
1111
import dev.thor.rombutler.data.update.UpdateAvailability
1212
import dev.thor.rombutler.di.IoDispatcher
13+
import dev.thor.rombutler.domain.detection.SystemRegistry
1314
import dev.thor.rombutler.domain.repository.SettingsRepository
1415
import dev.thor.rombutler.ui.navigation.Routes
1516
import kotlinx.coroutines.flow.MutableStateFlow
@@ -34,12 +35,15 @@ class MainViewModel @Inject constructor(
3435
private val settingsRepository: SettingsRepository,
3536
private val updateChecker: GitHubUpdateChecker,
3637
private val updateAvailability: UpdateAvailability,
38+
private val systemRegistry: SystemRegistry,
3739
@param:IoDispatcher private val ioDispatcher: CoroutineDispatcher,
3840
) : ViewModel() {
3941

4042
/** `null` while settings are still loading (splash keeps showing). */
4143
val startDestination: StateFlow<String?> = settingsRepository.settings
4244
.map { settings ->
45+
// Activate persisted custom systems before the first scan screen is shown.
46+
systemRegistry.applyCustomPackJson(settings.customSystemPackJson)
4347
if (settings.isSetupComplete && Environment.isExternalStorageManager()) {
4448
Routes.SCAN
4549
} else {

0 commit comments

Comments
 (0)