-
-
Notifications
You must be signed in to change notification settings - Fork 14
commit e0c0510
abduznik edited this page May 23, 2026
·
1 revision
Commit: e0c05102bfa8f8a1d45412e18550d2281467acec
Author: Cool Contributor
Date: 2026-05-20
-
fix: dolphin save sync on linux
-
part 3
Why: Fixes a bug or regression in the existing codebase.
.../save/strategies/dolphin_save_strategy.dart | 76 ++++++++++++++++++++--
lib/ui/screens/library_actions.dart | 6 +-
2 files changed, 74 insertions(+), 8 deletions(-)
lib/core/save/strategies/dolphin_save_strategy.dartlib/ui/screens/library_actions.dart
diff --git a/lib/core/save/strategies/dolphin_save_strategy.dart b/lib/core/save/strategies/dolphin_save_strategy.dart
index 4b7112f..8b94419 100644
--- a/lib/core/save/strategies/dolphin_save_strategy.dart
+++ b/lib/core/save/strategies/dolphin_save_strategy.dart
@@ -1,4 +1,6 @@
+import 'dart:convert';
import 'dart:io' as io;
+import 'dart:io';
import 'dart:typed_data';
import 'package:path/path.dart' as p;
@@ -42,7 +44,17 @@ class DolphinSaveStrategy extends SaveStrategy {
}
}
- // 2. Fall back to app support directory
+ // 2. On Linux, if running via Flatpak, which is the default installation method of Dolphin
+ if (io.Platform.isLinux){
+ final home = io.Platform.environment['HOME'] ?? '';
+ final flatpakPath = "$home/.var/app/org.DolphinEmu.dolphin-emu/data/dolphin-emu";
+ if (await io.Directory(flatpakPath).exists()) {
+ return flatpakPath;
+ }
+ }
+
+
+ // 3. Fall back to app support directory
final resolvedPath = await _directoryService.getEmulatorAppSupportDirectory('Dolphin', platformSlug: platformSlug);
if (!await io.Directory(resolvedPath).exists()) {
throw Exception('Save directory not found for Dolphin at $resolvedPath. Please launch Dolphin at least once to generate save data.');
@@ -63,9 +75,59 @@ class DolphinSaveStrategy extends SaveStrategy {
return name.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), '');
}
- /// Extracts Game ID/Code from filename if possible.
+
+ bool _isValidGameIdByte(int byte) {
+ final c = String.fromCharCode(byte);
+
+ final isUpper =
+ c.codeUnitAt(0) >= 'A'.codeUnitAt(0) &&
+ c.codeUnitAt(0) <= 'Z'.codeUnitAt(0);
+
+ final isDigit =
+ c.codeUnitAt(0) >= '0'.codeUnitAt(0) &&
+ c.codeUnitAt(0) <= '9'.codeUnitAt(0);
+
+ return isUpper || isDigit;
+ }
+
+ /// Extracts Game ID/Code from file if possible.
/// Often formatted as [GAMEID] Name.ext or Name [GAMEID].ext
- String? _extractGameId(String filename) {
+ Future<String?> _extractGameId(String romPath) async {
+
+ // Attempt to read the game ID from the file
+ final allowedFileExtensions = [".rvz", ".iso"];
+ final fileExtension = p.extension(romPath).toLowerCase();
+ if (allowedFileExtensions.contains(fileExtension)) {
+
+ final file = await File(romPath).open(mode: FileMode.read);
+ final bytes = Uint8List(4);
+
+ int offset = 0;
+
+ switch (fileExtension){
+ case ".rvz":
+ offset = 0x58;
+ break;
+ case ".iso":
+ offset = 0x00;
+ break;
+ }
+
+ await file.setPosition(offset);
+ await file.readInto(bytes);
+ await file.close();
+
+ for (final b in bytes) {
+ if (!_isValidGameIdByte(b)) {
+ return null;
+ }
+ }
+
+ return ascii.decode(bytes);
+ }
+
+
+ final filename = p.basename(romPath);
final base = p.basenameWithoutExtension(filename);
// Look for 4 or 6 character uppercase alphanumeric codes
final match = RegExp(r'\[([A-Z0-9]{4,6})\]').firstMatch(base);
@@ -89,7 +151,7 @@ class DolphinSaveStrategy extends SaveStrategy {
if (isWii) {
// Wii saves are in Wii/title/00010000/[TITLE_ID_HEX]
- String? titleId = _extractGameId(p.basename(romPath));
+ String? titleId = await _extractGameId(romPath);
final String wiiBase = (isIntegratedEnv && p.basename(userDir).toLowerCase() == 'wii')
? userDir
@@ -143,10 +205,10 @@ class DolphinSaveStrategy extends SaveStrategy {
// GameCube fuzzy matching
final dir = io.Directory(rootSaveDir);
if (await dir.exists()) {
- final gameId = _extractGameId(p.basename(romPath))?.toUpperCase();
+ final gameId = (await _extractGameId(romPath))?.toUpperCase();
final normalizedTarget = _normalizeGameName(game.displayName);
- await for (final entity in dir.list()) {
+ await for (final entity in dir.list(recursive: true)) {
if (entity is! io.File) continue;
final filename = p.basename(entity.path).toUpperCase();
@@ -188,7 +250,7 @@ class DolphinSaveStrategy extends SaveStrategy {
final stateDir = p.join(userDir, 'StateSaves');
final stateDirObj = io.Directory(stateDir);
if (await stateDirObj.exists()) {
- final gameId = _extractGameId(p.basename(romPath))?.toUpperCase();
+ final gameId = (await _extractGameId(romPath))?.toUpperCase();
final romStem = p.basenameWithoutExtension(romPath).toUpperCase();
await for (final entity in stateDirObj.list()) {
diff --git a/lib/ui/screens/library_actions.dart b/lib/ui/screens/library_actions.dart
index d28531e..cb6fe31 100644
--- a/lib/ui/screens/library_actions.dart
+++ b/lib/ui/screens/library_actions.dart
@@ -532,7 +532,11 @@ mixin LibraryActionsMixin<T extends ConsumerStatefulWidget> on ConsumerState<T>
}
final dir = ref.read(directoryServiceProvider).asData?.value;
- final romPath = dir != null ? await dir.getRomFilePath(game) : '';
+ String romPath = '';
+ if (dir != null) {
+ romPath = await dir.findExistingRomPath(game) ?? await dir.getRomFilePath(game);
+ }
+
if (!context.mounted) return;
final syncMode = ref.read(retroarchSyncModeProvider);