Skip to content

commit 610c5c2

abduznik edited this page May 23, 2026 · 1 revision

feat: add system log viewer and improve PS3 detection

Commit: 610c5c2285ed7d011e16fbb27fa7f994b52a74cc

Author: abduznik

Date: 2026-04-28

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

Files Changed

lib/core/storage/directory_service.dart | 25 +++++++++---
 lib/core/storage/logger_service.dart    | 50 +++++++++++++++++++++++
 lib/main.dart                           |  3 ++
 lib/ui/screens/settings_screen.dart     | 72 +++++++++++++++++++++++++++++++++
 4 files changed, 144 insertions(+), 6 deletions(-)
  • lib/core/storage/directory_service.dart
  • lib/core/storage/logger_service.dart
  • lib/main.dart
  • lib/ui/screens/settings_screen.dart

Diff

diff --git a/lib/core/storage/directory_service.dart b/lib/core/storage/directory_service.dart
index b01f152..a070962 100644
--- a/lib/core/storage/directory_service.dart
+++ b/lib/core/storage/directory_service.dart
@@ -87,7 +87,7 @@ class DirectoryService {
     'nes': ['.nes', '.zip'],
     'psx': ['.bin', '.cue', '.iso', '.img', '.chd'],
     'ps2': ['.iso', '.bin', '.chd'],
-    'ps3': ['.pkg', '.iso'],
+    'ps3': ['.pkg', '.iso', '.bin', '.edat'],
     'psp': ['.iso', '.cso', '.pbp'],
     'gc': ['.iso', '.gcm', '.rvz', '.wbfs'],
     'gamecube': ['.iso', '.gcm', '.rvz', '.wbfs'],
@@ -452,6 +452,8 @@ class DirectoryService {
     final romDir = await getRomDirectory(game);
     final platformLower = game.platformSlug?.toLowerCase();
     
+    debugPrint('[Matching] Searching for ${game.name} (Platform: $platformLower) in $romDir');
+
     // Names to check (in order of priority)
     final namesToCheck = <String>[];
     if (game.fsName != null) namesToCheck.add(game.fsName!);
@@ -466,10 +468,14 @@ class DirectoryService {
         final lowerName = name.toLowerCase();
         
         // Try exact name match (files)
-        if (index.files.containsKey(lowerName)) return index.files[lowerName];
+        if (index.files.containsKey(lowerName)) {
+          debugPrint('[Matching] Index hit (file): $lowerName');
+          return index.files[lowerName];
+        }
         
         // Try name match (dirs)
         if (index.dirs.containsKey(lowerName)) {
+          debugPrint('[Matching] Index hit (dir): $lowerName');
           final found = await _findMainRomInFolder(game, index.dirs[lowerName]!);
           if (found != null) return found;
         }
@@ -579,13 +585,18 @@ class DirectoryService {
       } catch (_) {}
     }
 
+    debugPrint('[Matching] No match found for ${game.name}');
     return null;
   }
 
   /// Finds the largest ROM-like file in a folder.
   Future<String?> _findMainRomInFolder(Game game, String folderPath) async {
-    final isWindowsGame = ['windows', 'pc', 'win'].contains(game.platformSlug?.toLowerCase() ?? '');
-    if (isWindowsGame) return p.absolute(folderPath);
+    final platform = game.platformSlug?.toLowerCase() ?? '';
+    final isFolderBased = ['windows', 'pc', 'win', 'ps3', 'switch', 'nintendo-switch'].contains(platform);
+    if (isFolderBased) {
+      // For folder-based platforms, we can often just return the folder path if it's a direct match
+      // But we still try to find a "main" file inside first for better emulator compatibility
+    }
 
     final extensions = _platformExtensions[game.platformSlug?.toLowerCase()] ?? [];
     
@@ -609,10 +620,12 @@ class DirectoryService {
     } catch (_) {}
 
     if (largestFile != null) {
-      // If there's only one ROM-like file, or one is significantly larger (e.g. 2x the next one)
-      // we can be fairly certain it's the main ROM.
       return p.absolute(largestFile.path);
     }
+    
+    // Fallback for PS3/Switch folders that might not have a "known" extension but are valid
+    if (isFolderBased) return p.absolute(folderPath);
+    
     return null;
   }
 
diff --git a/lib/core/storage/logger_service.dart b/lib/core/storage/logger_service.dart
new file mode 100644
index 0000000..1689861
--- /dev/null
+++ b/lib/core/storage/logger_service.dart
@@ -0,0 +1,50 @@
+import 'dart:async';
+import 'package:flutter/foundation.dart';
+
+class LogEntry {
+  final DateTime timestamp;
+  final String message;
+  final String? level;
+
+  LogEntry(this.message, {this.level, DateTime? timestamp}) 
+      : timestamp = timestamp ?? DateTime.now();
+
+  @override
+  String toString() => '[${timestamp.toIso8601String().substring(11, 19)}] ${level != null ? '[$level] ' : ''}$message';
+}
+
+class LoggerService {
+  static final LoggerService _instance = LoggerService._internal();
+  factory LoggerService() => _instance;
+  LoggerService._internal();
+
+  final List<LogEntry> _logs = [];
+  final StreamController<List<LogEntry>> _controller = StreamController<List<LogEntry>>.broadcast();
+
+  List<LogEntry> get logs => List.unmodifiable(_logs);
+  Stream<List<LogEntry>> get logStream => _controller.stream;
+
+  void log(String message, {String? level}) {
+    final entry = LogEntry(message, level: level);
+    _logs.add(entry);
+    if (_logs.length > 500) {
+      _logs.removeAt(0);
+    }
+    _controller.add(logs);
+  }
+
+  void clear() {
+    _logs.clear();
+    _controller.add(logs);
+  }
+
+  static void init() {
+    final originalDebugPrint = debugPrint;
+    debugPrint = (String? message, {int? wrapWidth}) {
+      if (message != null) {
+        LoggerService().log(message);
+      }
+      originalDebugPrint(message, wrapWidth: wrapWidth);
+    };
+  }
+}
diff --git a/lib/main.dart b/lib/main.dart
index 17b12cf..04119c2 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -6,10 +6,13 @@ import 'app.dart';
 import 'core/save/backup_entry.dart';
 import 'providers/shared_prefs_provider.dart';
 
+import 'core/storage/logger_service.dart';
+
 final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
 
 void main() async {
   WidgetsFlutterBinding.ensureInitialized();
+  LoggerService.init();
   Hive.registerAdapter(BackupEntryAdapter());
   await Hive.initFlutter();
   await Hive.openBox<List>('freegosy_backups');
diff --git a/lib/ui/screens/settings_screen.dart b/lib/ui/screens/settings_screen.dart
index 3947de2..53e3bac 100644
--- a/lib/ui/screens/settings_screen.dart
+++ b/lib/ui/screens/settings_screen.dart
@@ -12,6 +12,7 @@ import '../../providers/shared_prefs_provider.dart';
 import '../../providers/downloaded_games_cache_provider.dart';
 import '../../core/romm/romm_service.dart';
 import '../../core/romm/romm_models.dart';
+import '../../core/storage/logger_service.dart';
 import 'settings_emulators_section.dart';
 import 'settings_display_section.dart';
 import 'settings_custom_emulators_section.dart';
@@ -365,9 +366,80 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
           label: Text(isScanning ? 'Scanning...' : 'Force Full ROM Scan'),
         );
       }),
+      const SizedBox(height: 12),
+      OutlinedButton.icon(
+        onPressed: () => _showLogsDialog(context),
+        icon: const Icon(Icons.receipt_long),
+        label: const Text('View Console Logs'),
+      ),
     ]);
   }
 
+  void _showLogsDialog(BuildContext context) {
+    showDialog(
+      context: context,
+      builder: (context) => Dialog(
+        child: Container(
+          width: double.infinity,
+          height: MediaQuery.of(context).size.height * 0.8,
+          padding: const EdgeInsets.all(16),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Row(
+                mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                children: [
+                  const Text('System Logs', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
+                  Row(
+                    children: [
+                      IconButton(
+                        icon: const Icon(Icons.delete_sweep),
+                        onPressed: () => LoggerService().clear(),
+                        tooltip: 'Clear Logs',
+                      ),
+                      IconButton(
+                        icon: const Icon(Icons.close),
+                        onPressed: () => Navigator.pop(context),
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+              const Divider(),
+              Expanded(
+                child: StreamBuilder<List<LogEntry>>(
+                  stream: LoggerService().logStream,
+                  initialData: LoggerService().logs,
+                  builder: (context, snapshot) {
+                    final logs = snapshot.data ?? [];
+                    return ListView.builder(
+                      reverse: true,
+                      itemCount: logs.length,
+                      itemBuilder: (context, index) {
+                        final log = logs[logs.length - 1 - index];
+                        return Padding(
+                          padding: const EdgeInsets.symmetric(vertical: 2),
+                          child: Text(
+                            log.toString(),
+                            style: const TextStyle(
+                              fontFamily: 'monospace',
+                              fontSize: 12,
+                              color: Colors.white70,
+                            ),
+                          ),
+                        );
+                      },
+                    );
+                  },
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
   Widget _buildPathRow({required String label, required String currentPath, required Function(String?)? onChanged, VoidCallback? onReset}) {
     return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
       Text(label, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),

Clone this wiki locally