Skip to content

commit c8b3117

abduznik edited this page May 23, 2026 · 1 revision

fix: map 3DS variants to n3ds folder on EmuDeck, bump to v0.5.1

Commit: c8b311735adefea7c6f68cd9b40306a0de36754c

Author: abduznik

Date: 2026-05-23

Why: Fixes a bug or regression in the existing codebase.

Files Changed

lib/core/constants/app_constants.dart      |   2 +-
 lib/core/romm/rom_constants.dart           |   5 +
 lib/core/romm/rom_scanner_service.dart     |   2 +-
 lib/core/romm/romm_service.dart            |   2 +-
 lib/core/storage/directory_service.dart    |  27 +++++-
 pubspec.yaml                               |   2 +-
 test/core/emudeck_n3ds_edge_case_test.dart | 151 +++++++++++++++++++++++++++++
 7 files changed, 186 insertions(+), 5 deletions(-)
  • lib/core/constants/app_constants.dart
  • lib/core/romm/rom_constants.dart
  • lib/core/romm/rom_scanner_service.dart
  • lib/core/romm/romm_service.dart
  • lib/core/storage/directory_service.dart
  • pubspec.yaml
  • test/core/emudeck_n3ds_edge_case_test.dart

Diff

diff --git a/lib/core/constants/app_constants.dart b/lib/core/constants/app_constants.dart
index f3a6993..4b6b0a5 100644
--- a/lib/core/constants/app_constants.dart
+++ b/lib/core/constants/app_constants.dart
@@ -1,3 +1,3 @@
 class AppConstants {
-  static const String version = '0.5.0'; // Unified version
+  static const String version = '0.5.1'; // Unified version
 }
diff --git a/lib/core/romm/rom_constants.dart b/lib/core/romm/rom_constants.dart
index 755b09f..bc31a97 100644
--- a/lib/core/romm/rom_constants.dart
+++ b/lib/core/romm/rom_constants.dart
@@ -24,6 +24,11 @@ class RomConstants {
     'megadrive': ['.md', '.bin', '.gen', '.zip', '.7z'],
     'genesis': ['.md', '.bin', '.gen', '.zip', '.7z'],
     '3ds': ['.3ds', '.cia', '.app'],
+    'n3ds': ['.3ds', '.cia', '.app'],
+    'nintendo-3ds': ['.3ds', '.cia', '.app'],
+    'nintendo3ds': ['.3ds', '.cia', '.app'],
+    'new-nintendo-3ds': ['.3ds', '.cia', '.app'],
+    'new-nintendo-3ds-xl': ['.3ds', '.cia', '.app'],
     'wiiu': ['.wua', '.rpx', '.wud', '.wux'],
   };
 
diff --git a/lib/core/romm/rom_scanner_service.dart b/lib/core/romm/rom_scanner_service.dart
index 3935613..2bbb830 100644
--- a/lib/core/romm/rom_scanner_service.dart
+++ b/lib/core/romm/rom_scanner_service.dart
@@ -45,7 +45,7 @@ class RomScannerService {
     final storedMTimes = _mappingService.getMTimes();
     for (final dir in platformDirs) {
       final platformSlug = p.basename(dir.path).toLowerCase();
-      final platformId = platformSlugToId[platformSlug];
+      final platformId = platformSlugToId[platformSlug] ?? platformSlugToId[DirectoryService.platformFolderCanonicalMap[platformSlug]];
       if (platformId == null) continue;
 
       final stat = await dir.stat();
diff --git a/lib/core/romm/romm_service.dart b/lib/core/romm/romm_service.dart
index 744d379..141189c 100644
--- a/lib/core/romm/romm_service.dart
+++ b/lib/core/romm/romm_service.dart
@@ -18,7 +18,7 @@ class RommService {
 
   RomMConfig get config => _config;
 
-  static const String _ua = 'Freegosy/0.5.0';
+  static const String _ua = 'Freegosy/0.5.1';
 
   void updateConfig(RomMConfig newConfig) {
     _config = newConfig;
diff --git a/lib/core/storage/directory_service.dart b/lib/core/storage/directory_service.dart
index 079fc3f..d4372ab 100644
--- a/lib/core/storage/directory_service.dart
+++ b/lib/core/storage/directory_service.dart
@@ -15,6 +15,22 @@ import 'package:freegosy/core/emulator/linux_strategies/emudeck_strategy.dart';
 import 'package:freegosy/core/emulator/linux_strategies/retrodeck_strategy.dart';
 
 class DirectoryService {
+  static const Map<String, String> platformFolderCanonicalMap = {
+    'n3ds': '3ds',
+    'nintendo-3ds': '3ds',
+    'nintendo3ds': '3ds',
+    'new-nintendo-3ds': '3ds',
+    'new-nintendo-3ds-xl': '3ds',
+  };
+
+  static const Map<String, String> _emudeckFolderAliases = {
+    '3ds': 'n3ds',
+    'nintendo-3ds': 'n3ds',
+    'nintendo3ds': 'n3ds',
+    'new-nintendo-3ds': 'n3ds',
+    'new-nintendo-3ds-xl': 'n3ds',
+  };
+
   static const String _romsRootPathKey = 'romsRootPath';
   static const String _emulatorsRootPathKey = 'emulatorsRootPath';
   static const String _linuxSyncPresetKey = 'linuxSyncPreset';
@@ -236,9 +252,18 @@ class DirectoryService {
 
   Future<String> getRomsDirectory() async => romsRootPath;
 
+  String _resolveFolderName(String platformSlug) {
+    final lower = platformSlug.toLowerCase();
+    if (linuxSyncPreset == 'emudeck') {
+      return _emudeckFolderAliases[lower] ?? lower;
+    }
+    return lower;
+  }
+
   Future<String> getRomDirectory(Game game) async {
     final platformSlug = game.platformSlug ?? 'unknown';
-    final dirPath = p.join(romsRootPath, platformSlug);
+    final folderName = _resolveFolderName(platformSlug);
+    final dirPath = p.join(romsRootPath, folderName);
     await _ensureDirectoryExists(dirPath);
     return dirPath;
   }
diff --git a/pubspec.yaml b/pubspec.yaml
index 1afa6da..1773fd0 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 # In Windows, build-name is used as the major, minor, and patch parts
 # of the product and file versions while build-number is used as the build suffix.
-version: 0.5.0+1
+version: 0.5.1+1
 
 
 environment:
diff --git a/test/core/emudeck_n3ds_edge_case_test.dart b/test/core/emudeck_n3ds_edge_case_test.dart
new file mode 100644
index 0000000..c3a731e
--- /dev/null
+++ b/test/core/emudeck_n3ds_edge_case_test.dart
@@ -0,0 +1,151 @@
+import 'dart:io';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:path/path.dart' as p;
+import 'package:freegosy/core/storage/directory_service.dart';
+import 'package:freegosy/core/romm/romm_models.dart';
+import 'package:freegosy/core/romm/rom_constants.dart';
+
+void main() {
+  group('EmuDeck n3ds folder edge case', () {
+    late Directory tempDir;
+
+    setUp(() async {
+      tempDir = await Directory.systemTemp.createTemp('freegosy_n3ds_test_');
+      SharedPreferences.setMockInitialValues({});
+    });
+
+    tearDown(() async {
+      if (await tempDir.exists()) {
+        await tempDir.delete(recursive: true);
+      }
+    });
+
+    group('DirectoryService.getRomDirectory', () {
+      Future<DirectoryService> createService(String preset) async {
+        final prefs = await SharedPreferences.getInstance();
+        final service = DirectoryService(prefs);
+        service.romsRootPath = tempDir.path;
+        service.linuxSyncPreset = preset;
+        return service;
+      }
+
+      test('maps 3ds platformSlug to n3ds folder on EmuDeck preset', () async {
+        final service = await createService('emudeck');
+        await Directory(p.join(tempDir.path, 'n3ds')).create(recursive: true);
+
+        final game = Game(id: '1', name: 'Test', platformSlug: '3ds', fileSize: 0);
+        final romDir = await service.getRomDirectory(game);
+
+        expect(romDir, p.join(tempDir.path, 'n3ds'));
+      });
+
+      test('maps all 3DS slug variants to n3ds folder on EmuDeck', () async {
+        final service = await createService('emudeck');
+        await Directory(p.join(tempDir.path, 'n3ds')).create(recursive: true);
+
+        final slugs = ['3ds', 'n3ds', 'nintendo-3ds', 'nintendo3ds', 'new-nintendo-3ds', 'new-nintendo-3ds-xl'];
+        for (final slug in slugs) {
+          final game = Game(id: 'x', name: 'Test', platformSlug: slug, fileSize: 0);
+          final romDir = await service.getRomDirectory(game);
+          expect(romDir, p.join(tempDir.path, 'n3ds'), reason: 'slug "$slug" should map to n3ds');
+        }
+      });
+
+      test('uses slug directly as folder name on default preset', () async {
+        final service = await createService('default');
+
+        final game = Game(id: '1', name: 'Test', platformSlug: '3ds', fileSize: 0);
+        final romDir = await service.getRomDirectory(game);
+
+        expect(romDir, p.join(tempDir.path, '3ds'));
+      });
+    });
+
+    group('DirectoryService.findExistingRomPath on EmuDeck', () {
+      test('finds 3ds game in n3ds folder on EmuDeck preset', () async {
+        final prefs = await SharedPreferences.getInstance();
+        final service = DirectoryService(prefs);
+        service.romsRootPath = tempDir.path;
+        service.linuxSyncPreset = 'emudeck';
+
+        await Directory(p.join(tempDir.path, 'n3ds')).create(recursive: true);
+        final romFile = File(p.join(tempDir.path, 'n3ds', 'TestGame.3ds'));
+        await romFile.writeAsString('fake rom');
+
+        final game = Game(
+          id: '1',
+          name: 'TestGame',
+          platformSlug: '3ds',
+          fileName: 'TestGame.3ds',
+          fsName: 'TestGame.3ds',
+          fileSize: 100,
+        );
+
+        final foundPath = await service.findExistingRomPath(game);
+
+        expect(foundPath, isNotNull);
+        expect(foundPath, endsWith(p.join('n3ds', 'TestGame.3ds')));
+      });
+
+      test('finds all 3DS slug variant games in n3ds folder on EmuDeck', () async {
+        final prefs = await SharedPreferences.getInstance();
+        final service = DirectoryService(prefs);
+        service.romsRootPath = tempDir.path;
+        service.linuxSyncPreset = 'emudeck';
+
+        await Directory(p.join(tempDir.path, 'n3ds')).create(recursive: true);
+
+        final slugs = ['3ds', 'n3ds', 'nintendo-3ds', 'nintendo3ds', 'new-nintendo-3ds', 'new-nintendo-3ds-xl'];
+        for (int i = 0; i < slugs.length; i++) {
+          final romFile = File(p.join(tempDir.path, 'n3ds', 'Game$i.3ds'));
+          await romFile.writeAsString('fake rom $i');
+
+          final game = Game(
+            id: '$i',
+            name: 'Game$i',
+            platformSlug: slugs[i],
+            fileName: 'Game$i.3ds',
+            fsName: 'Game$i.3ds',
+            fileSize: 100,
+          );
+
+          final foundPath = await service.findExistingRomPath(game);
+          expect(foundPath, isNotNull, reason: 'slug "${slugs[i]}" should find game in n3ds');
+          expect(foundPath, endsWith(p.join('n3ds', 'Game$i.3ds')));
+        }
+      });
+    });
+
+    group('Scanner folder alias map', () {
+      test('exposes platformFolderCanonicalMap for scanner reverse lookup', () {
+        expect(DirectoryService.platformFolderCanonicalMap['n3ds'], equals('3ds'));
+        expect(DirectoryService.platformFolderCanonicalMap['nintendo-3ds'], equals('3ds'));
+        expect(DirectoryService.platformFolderCanonicalMap['nintendo3ds'], equals('3ds'));
+        expect(DirectoryService.platformFolderCanonicalMap['new-nintendo-3ds'], equals('3ds'));
+        expect(DirectoryService.platformFolderCanonicalMap['new-nintendo-3ds-xl'], equals('3ds'));
+        expect(DirectoryService.platformFolderCanonicalMap['3ds'], isNull);
+      });
+    });
+
+    group('RomConstants platformExtensions for n3ds', () {
+      test('includes n3ds as a known platform extension key', () {
+        final extensions = RomConstants.platformExtensions['n3ds'];
+        expect(extensions, isNotNull);
+        expect(extensions!.contains('.3ds'), isTrue);
+        expect(extensions.contains('.cia'), isTrue);
+        expect(extensions.contains('.app'), isTrue);
+      });
+
+      test('includes all 3DS variant extension keys', () {
+        for (final slug in ['n3ds', 'nintendo-3ds', 'nintendo3ds', 'new-nintendo-3ds', 'new-nintendo-3ds-xl']) {
+          final extensions = RomConstants.platformExtensions[slug];
+          expect(extensions, isNotNull, reason: 'slug "$slug" should have extension entries');
+          expect(extensions!.contains('.3ds'), isTrue, reason: 'slug "$slug" missing .3ds');
+          expect(extensions.contains('.cia'), isTrue, reason: 'slug "$slug" missing .cia');
+          expect(extensions.contains('.app'), isTrue, reason: 'slug "$slug" missing .app');
+        }
+      });
+    });
+  });
+}

Clone this wiki locally