Skip to content

commit 59ce492

abduznik edited this page May 23, 2026 · 1 revision

feat: implement strict verification for game matching to prevent false positives

Commit: 59ce4927943af70492e24e8a8276d0f40740679f

Author: abduznik

Date: 2026-04-28

Why: Adds a new feature or capability to the application.

Files Changed

lib/core/romm/rom_scanner_service.dart  | 25 ++++++++++++++++++++++---
 lib/core/storage/directory_service.dart | 12 +++++++-----
 2 files changed, 29 insertions(+), 8 deletions(-)
  • lib/core/romm/rom_scanner_service.dart
  • lib/core/storage/directory_service.dart

Diff

diff --git a/lib/core/romm/rom_scanner_service.dart b/lib/core/romm/rom_scanner_service.dart
index bc15360..11d3c95 100644
--- a/lib/core/romm/rom_scanner_service.dart
+++ b/lib/core/romm/rom_scanner_service.dart
@@ -133,15 +133,34 @@ class RomScannerService {
         // Strategy B: Clean name match
         if (matchedGame == null) {
           final fNameClean = _cleanName(fileNameNoExt);
-          matchedGame = platformGames.cast<Game?>().firstWhere((g) {
-            if (g == null) return false;
+          final candidates = platformGames.where((g) {
             if (_cleanName(g.name) == fNameClean) return true;
             final gFileNoExt = p.basenameWithoutExtension(g.fileName ?? '').toLowerCase();
             if (_cleanName(gFileNoExt) == fNameClean) return true;
             final gFsNoExt = p.basenameWithoutExtension(g.fsName ?? '').toLowerCase();
             if (_cleanName(gFsNoExt) == fNameClean) return true;
             return false;
-          }, orElse: () => null);
+          }).toList();
+
+          if (candidates.length == 1) {
+            final candidate = candidates.first;
+            // Additional confirmation: If both have sizes, they should be reasonably close
+            final localSize = index.fileSizes[entityPath] ?? (romsIndex?.fileSizes[entityPath] ?? 0);
+            if (candidate.fileSize > 0 && localSize > 0) {
+              // Allow for small differences in size (e.g. metadata or padding) but not completely different
+              final diff = (candidate.fileSize - localSize).abs();
+              if (diff < 1024 * 1024 * 10) { // < 10MB difference
+                matchedGame = candidate;
+              } else {
+                debugPrint('[Scanner] Fuzzy name match for $fileName -> ${candidate.name} REJECTED due to size mismatch ($localSize vs ${candidate.fileSize})');
+              }
+            } else {
+              // One side has no size, we have to trust the unique name match
+              matchedGame = candidate;
+            }
+          } else if (candidates.length > 1) {
+            debugPrint('[Scanner] Ambiguous match for $fileName: ${candidates.length} candidates found. Skipping.');
+          }
         }
 
         if (matchedGame != null) {
diff --git a/lib/core/storage/directory_service.dart b/lib/core/storage/directory_service.dart
index a070962..844303f 100644
--- a/lib/core/storage/directory_service.dart
+++ b/lib/core/storage/directory_service.dart
@@ -488,11 +488,13 @@ class DirectoryService {
         }
       }
       
-      // Fuzzy match in index
+      // Fuzzy match in index (Last resort, only if name is very similar and not ambiguous)
       for (final name in namesToCheck) {
         final lowerName = name.toLowerCase();
         for (final entry in index.files.entries) {
-          if (entry.key.startsWith(lowerName) && !entry.key.endsWith('.part')) {
+          // Strict startsWith: only if the match is very close (e.g. adding a small suffix)
+          if (entry.key.startsWith(lowerName) && entry.key.length < lowerName.length + 5 && !entry.key.endsWith('.part')) {
+            // debugPrint('[Matching] Strict startsWith hit: ${entry.key}');
             return entry.value;
           }
         }
@@ -575,9 +577,9 @@ class DirectoryService {
       try {
         await for (final entity in pDir.list()) {
           if (entity is File) {
-            final fname = p.basename(entity.path);
-            final sanitizedFName = fname.replaceAll(RegExp(r'[<>:"/\\|?*]'), ' ').replaceAll(RegExp(r'\s+'), ' ').trim();
-            if (sanitizedFName.toLowerCase().startsWith(baseName.toLowerCase()) && !fname.toLowerCase().endsWith('.part')) {
+            final fname = p.basename(entity.path).toLowerCase();
+            final target = baseName.toLowerCase();
+            if (fname == target || fname == '$target.iso' || fname == '$target.bin' || fname == '$target.pkg') {
               return p.absolute(entity.path);
             }
           }

Clone this wiki locally