-
-
Notifications
You must be signed in to change notification settings - Fork 14
commit 965243b
abduznik edited this page May 23, 2026
·
1 revision
Commit: 965243b109f208658515180b01ff2c4cd6b6efde
Author: abduznik
Date: 2026-04-17
Why: Adds a new feature or capability to the application.
.../linux_strategies/emudeck_strategy.dart | 169 ++++++++++++++++++++
.../linux_environment_strategy.dart | 31 ++++
.../linux_strategies/native_linux_strategy.dart | 55 +++++++
.../linux_strategies/retrodeck_strategy.dart | 63 ++++++++
lib/core/emulator/strategies/azahar_strategy.dart | 66 +-------
lib/core/emulator/strategies/cemu_strategy.dart | 43 +----
lib/core/emulator/strategies/dolphin_strategy.dart | 87 ++--------
.../emulator/strategies/duckstation_strategy.dart | 51 +-----
lib/core/emulator/strategies/eden_strategy.dart | 135 +++-------------
lib/core/emulator/strategies/flycast_strategy.dart | 51 +-----
lib/core/emulator/strategies/mame_strategy.dart | 39 +----
lib/core/emulator/strategies/melonds_strategy.dart | 54 +------
lib/core/emulator/strategies/mgba_strategy.dart | 47 +-----
lib/core/emulator/strategies/pcsx2_strategy.dart | 73 ++-------
lib/core/emulator/strategies/ppsspp_strategy.dart | 46 +-----
.../emulator/strategies/retroarch_strategy.dart | 62 +-------
lib/core/emulator/strategies/rpcs3_strategy.dart | 44 +----
lib/core/emulator/strategies/xemu_strategy.dart | 29 +---
lib/core/emulator/strategies/xenia_strategy.dart | 27 +---
.../save/strategies/dolphin_save_strategy.dart | 8 +-
lib/core/save/strategies/pcsx2_save_strategy.dart | 11 +-
.../save/strategies/retroarch_save_strategy.dart | 34 +++-
lib/core/storage/directory_service.dart | 177 ++++++++++++---------
lib/ui/screens/settings_screen.dart | 88 ++++++++++
macos/Runner.xcodeproj/project.pbxproj | 4 +
25 files changed, 656 insertions(+), 838 deletions(-)
lib/core/emulator/linux_strategies/emudeck_strategy.dartlib/core/emulator/linux_strategies/linux_environment_strategy.dartlib/core/emulator/linux_strategies/native_linux_strategy.dartlib/core/emulator/linux_strategies/retrodeck_strategy.dartlib/core/emulator/strategies/azahar_strategy.dartlib/core/emulator/strategies/cemu_strategy.dartlib/core/emulator/strategies/dolphin_strategy.dartlib/core/emulator/strategies/duckstation_strategy.dartlib/core/emulator/strategies/eden_strategy.dartlib/core/emulator/strategies/flycast_strategy.dartlib/core/emulator/strategies/mame_strategy.dartlib/core/emulator/strategies/melonds_strategy.dartlib/core/emulator/strategies/mgba_strategy.dartlib/core/emulator/strategies/pcsx2_strategy.dartlib/core/emulator/strategies/ppsspp_strategy.dartlib/core/emulator/strategies/retroarch_strategy.dartlib/core/emulator/strategies/rpcs3_strategy.dartlib/core/emulator/strategies/xemu_strategy.dartlib/core/emulator/strategies/xenia_strategy.dartlib/core/save/strategies/dolphin_save_strategy.dartlib/core/save/strategies/pcsx2_save_strategy.dartlib/core/save/strategies/retroarch_save_strategy.dartlib/core/storage/directory_service.dartlib/ui/screens/settings_screen.dartmacos/Runner.xcodeproj/project.pbxproj
diff --git a/lib/core/emulator/linux_strategies/emudeck_strategy.dart b/lib/core/emulator/linux_strategies/emudeck_strategy.dart
new file mode 100644
index 0000000..58d0404
--- /dev/null
+++ b/lib/core/emulator/linux_strategies/emudeck_strategy.dart
@@ -0,0 +1,169 @@
+import 'dart:io' as io;
+import 'package:path/path.dart' as p;
+import 'package:freegosy/core/romm/romm_models.dart';
+import 'linux_environment_strategy.dart';
+
+class EmuDeckStrategy extends LinuxEnvironmentStrategy {
+ @override
+ String get name => 'EmuDeck';
+
+ @override
+ String get id => 'emudeck';
+
+ @override
+ String getRomsRoot(String home, String? customPath, String? emudeckRoot) {
+ if (emudeckRoot != null) {
+ return customPath ?? p.join(emudeckRoot, 'Emulation/roms');
+ }
+ return customPath ?? p.join(home, 'ROMs');
+ }
+
+ @override
+ String getEmulatorsRoot(String home, String? customPath, String? emudeckRoot) {
+ if (emudeckRoot != null) {
+ return customPath ?? p.join(emudeckRoot, 'Emulation/tools');
+ }
+ return customPath ?? p.join(home, 'Emulators');
+ }
+
+ @override
+ String getEmulatorAppSupportDirectory(String home, String emulatorName, String? emudeckRoot, {String? platformSlug}) {
+ if (emudeckRoot != null) {
+ // EmuDeck folder names mapping (some are capitalized)
+ final Map<String, String> emudeckSavesMap = {
+ 'cemu': 'Cemu',
+ 'vita3k': 'Vita3K',
+ 'mame': 'MAME',
+ };
+
+ final emuFolderName = emudeckSavesMap[emulatorName.toLowerCase()] ?? emulatorName.toLowerCase();
+ final base = p.join(emudeckRoot, 'Emulation', 'saves', emuFolderName);
+
+ if (platformSlug != null) {
+ final slug = platformSlug.toLowerCase();
+ if (emulatorName.toLowerCase() == 'dolphin' || emulatorName.toLowerCase() == 'primehack') {
+ if (slug == 'gc' || slug == 'gamecube' || slug == 'ngc') return p.join(base, 'GC');
+ if (slug == 'wii') return p.join(base, 'Wii');
+ }
+ }
+
+ return platformSlug != null ? p.join(base, platformSlug) : base;
+ }
+ return p.join(home, '.config', emulatorName);
+ }
+
+ @override
+ String getBiosPath(String home, String? emudeckRoot) {
+ if (emudeckRoot != null) {
+ return p.join(emudeckRoot, 'Emulation', 'bios');
+ }
+ return p.join(home, 'Emulators', 'BIOS');
+ }
+
+ @override
+ Future<String?> findExecutable(String emulatorId, String executableName, String emulatorsRoot, String? emudeckRoot) async {
+ if (emudeckRoot != null) {
+ final masterLauncher = io.File(p.join(emudeckRoot, 'Emulation', 'tools', 'emu-launch.sh'));
+ if (await masterLauncher.exists()) {
+ return masterLauncher.path;
+ }
+
+ final Map<String, String> emudeckMap = {
+ 'rpcs3': 'rpcs3.sh',
+ 'pcsx2': 'pcsx2-qt.sh',
+ 'dolphin': 'dolphin-emu.sh',
+ 'xemu': 'xemu-emu.sh',
+ 'xenia_canary': 'xenia.sh',
+ 'citra': 'citra.sh',
+ 'azahar': 'azahar.sh',
+ 'duckstation': 'duckstation.sh',
+ 'melonds': 'melonds.sh',
+ 'mgba': 'mgba.sh',
+ 'ppsspp': 'ppsspp.sh',
+ 'retroarch': 'retroarch.sh',
+ 'mame': 'mame.sh',
+ 'cemu': 'cemu.sh',
+ 'flycast': 'flycast.sh',
+ 'vita3k': 'vita3k.sh',
+ 'ryujinx': 'ryujinx.sh',
+ };
+
+ final launcherName = emudeckMap[emulatorId] ?? '$emulatorId.sh';
+ final launcherFile = io.File(p.join(emudeckRoot, 'Emulation', 'tools', 'launchers', launcherName));
+ if (await launcherFile.exists()) {
+ return launcherFile.path;
+ }
+ }
+ return null;
+ }
+
+ bool isEmuLaunchScript(String path) => p.basename(path) == 'emu-launch.sh';
+
+ @override
+ Future<void> launch(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []}) async {
+ if (isEmuLaunchScript(exePath)) {
+ // Convert internal emulator ID to EmuDeck expected key if necessary
+ final emuKeyMap = {
+ 'pcsx2': 'pcsx2-Qt',
+ 'retroarch': 'retroarch',
+ 'xemu': 'xemu-emu',
+ 'dolphin': 'dolphin-emu',
+ 'citra': 'citra-qt',
+ 'azahar': 'azahar',
+ 'eden': 'eden',
+ 'ryujinx': 'ryujinx',
+ };
+ final emuKey = emuKeyMap[emulatorId] ?? emulatorId;
+ await io.Process.start('bash', [exePath, '-e', emuKey, ...args, romPath], mode: io.ProcessStartMode.detached);
+ } else if (exePath.endsWith('.sh')) {
+ await io.Process.start('bash', [exePath, ...args, romPath], mode: io.ProcessStartMode.detached);
+ } else {
+ await io.Process.start(exePath, [...args, romPath], mode: io.ProcessStartMode.detached);
+ }
+ }
+
+ @override
+ Future<io.Process?> launchWithHandle(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []}) async {
+ if (isEmuLaunchScript(exePath)) {
+ final emuKeyMap = {
+ 'pcsx2': 'pcsx2-Qt',
+ 'retroarch': 'retroarch',
+ 'xemu': 'xemu-emu',
+ 'dolphin': 'dolphin-emu',
+ 'citra': 'citra-qt',
+ 'azahar': 'azahar',
+ 'eden': 'eden',
+ 'ryujinx': 'ryujinx',
+ };
+ final emuKey = emuKeyMap[emulatorId] ?? emulatorId;
+ return await io.Process.start('bash', [exePath, '-e', emuKey, ...args, romPath], mode: io.ProcessStartMode.normal);
+ } else if (exePath.endsWith('.sh')) {
+ return await io.Process.start('bash', [exePath, ...args, romPath], mode: io.ProcessStartMode.normal);
+ } else {
+ return await io.Process.start(exePath, [...args, romPath], mode: io.ProcessStartMode.normal);
+ }
+ }
+
+ @override
+ Future<void> launchStandalone(String emulatorId, String exePath, {List<String> args = const []}) async {
+ if (isEmuLaunchScript(exePath)) {
+ final emuKeyMap = {
+ 'pcsx2': 'pcsx2-Qt',
+ 'retroarch': 'retroarch',
+ 'xemu': 'xemu-emu',
+ 'dolphin': 'dolphin-emu',
+ 'citra': 'citra-qt',
+ 'azahar': 'azahar',
+ 'eden': 'eden',
+ 'ryujinx': 'ryujinx',
+ };
+ final emuKey = emuKeyMap[emulatorId] ?? emulatorId;
+ await io.Process.start('bash', [exePath, '-e', emuKey, ...args], mode: io.ProcessStartMode.detached);
+ } else if (exePath.endsWith('.sh')) {
+ await io.Process.start('bash', [exePath, ...args], mode: io.ProcessStartMode.detached);
+ } else {
+ final exeDir = io.File(exePath).parent.path;
+ await io.Process.start(exePath, args, mode: io.ProcessStartMode.detached, workingDirectory: exeDir);
+ }
+ }
+}
diff --git a/lib/core/emulator/linux_strategies/linux_environment_strategy.dart b/lib/core/emulator/linux_strategies/linux_environment_strategy.dart
new file mode 100644
index 0000000..7fa1ad5
--- /dev/null
+++ b/lib/core/emulator/linux_strategies/linux_environment_strategy.dart
@@ -0,0 +1,31 @@
+import 'dart:io' as io;
+import 'package:freegosy/core/romm/romm_models.dart';
+
+abstract class LinuxEnvironmentStrategy {
+ String get name;
+ String get id;
+
+ /// Returns the root ROMs directory for this environment.
+ String getRomsRoot(String home, String? customPath, String? emudeckRoot);
+
+ /// Returns the root emulators/tools directory for this environment.
+ String getEmulatorsRoot(String home, String? customPath, String? emudeckRoot);
+
+ /// Returns the app support (save/config) directory for a specific emulator.
+ String getEmulatorAppSupportDirectory(String home, String emulatorName, String? emudeckRoot, {String? platformSlug});
+
+ /// Returns the BIOS directory for this environment.
+ String getBiosPath(String home, String? emudeckRoot);
+
+ /// Tries to find the executable for an emulator.
+ Future<String?> findExecutable(String emulatorId, String executableName, String emulatorsRoot, String? emudeckRoot);
+
+ /// Launches a game.
+ Future<void> launch(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []});
+
+ /// Launches a game and returns the process handle.
+ Future<io.Process?> launchWithHandle(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []});
+
+ /// Launches the emulator standalone.
+ Future<void> launchStandalone(String emulatorId, String exePath, {List<String> args = const []});
+}
diff --git a/lib/core/emulator/linux_strategies/native_linux_strategy.dart b/lib/core/emulator/linux_strategies/native_linux_strategy.dart
new file mode 100644
index 0000000..596f9ec
--- /dev/null
+++ b/lib/core/emulator/linux_strategies/native_linux_strategy.dart
@@ -0,0 +1,55 @@
+import 'dart:io' as io;
+import 'package:path/path.dart' as p;
+import 'package:freegosy/core/romm/romm_models.dart';
+import 'linux_environment_strategy.dart';
+
+class NativeLinuxStrategy extends LinuxEnvironmentStrategy {
+ @override
+ String get name => 'Default';
+
+ @override
+ String get id => 'default';
+
+ @override
+ String getRomsRoot(String home, String? customPath, String? emudeckRoot) {
+ return customPath ?? p.join(home, 'ROMs');
+ }
+
+ @override
+ String getEmulatorsRoot(String home, String? customPath, String? emudeckRoot) {
+ return customPath ?? p.join(home, 'Emulators');
+ }
+
+ @override
+ String getEmulatorAppSupportDirectory(String home, String emulatorName, String? emudeckRoot, {String? platformSlug}) {
+ return p.join(home, '.config', emulatorName);
+ }
+
+ @override
+ String getBiosPath(String home, String? emudeckRoot) {
+ return p.join(home, 'Emulators', 'BIOS');
+ }
+
+ @override
+ Future<String?> findExecutable(String emulatorId, String executableName, String emulatorsRoot, String? emudeckRoot) async {
+ final direct = io.File(p.join(emulatorsRoot, executableName));
+ if (await direct.exists()) return direct.path;
+ return null;
+ }
+
+ @override
+ Future<void> launch(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []}) async {
+ await io.Process.start(exePath, [...args, romPath], mode: io.ProcessStartMode.detached);
+ }
+
+ @override
+ Future<io.Process?> launchWithHandle(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []}) async {
+ return await io.Process.start(exePath, [...args, romPath], mode: io.ProcessStartMode.normal);
+ }
+
+ @override
+ Future<void> launchStandalone(String emulatorId, String exePath, {List<String> args = const []}) async {
+ final exeDir = io.File(exePath).parent.path;
+ await io.Process.start(exePath, args, mode: io.ProcessStartMode.detached, workingDirectory: exeDir);
+ }
+}
diff --git a/lib/core/emulator/linux_strategies/retrodeck_strategy.dart b/lib/core/emulator/linux_strategies/retrodeck_strategy.dart
new file mode 100644
index 0000000..0bb4589
--- /dev/null
+++ b/lib/core/emulator/linux_strategies/retrodeck_strategy.dart
@@ -0,0 +1,63 @@
+import 'dart:io' as io;
+import 'package:path/path.dart' as p;
+import 'package:freegosy/core/romm/romm_models.dart';
+import 'linux_environment_strategy.dart';
+
+class RetroDeckStrategy extends LinuxEnvironmentStrategy {
+ @override
+ String get name => 'RetroDECK';
+
+ @override
+ String get id => 'retrodeck';
+
+ @override
+ String getRomsRoot(String home, String? customPath, String? emudeckRoot) {
+ return customPath ?? p.join(home, 'retrodeck', 'roms');
+ }
+
+ @override
+ String getEmulatorsRoot(String home, String? customPath, String? emudeckRoot) {
+ return customPath ?? p.join(home, 'retrodeck', 'tools');
+ }
+
+ @override
+ String getEmulatorAppSupportDirectory(String home, String emulatorName, String? emudeckRoot, {String? platformSlug}) {
+ final base = p.join(home, '.var', 'app', 'net.retrodeck.retrodeck', 'config');
+
+ final Map<String, String> retrodeckMap = {
+ 'pcsx2': 'PCSX2',
+ 'dolphin': 'dolphin-emu',
+ 'ppsspp': 'ppsspp',
+ 'retroarch': 'retroarch',
+ };
+
+ final folderName = retrodeckMap[emulatorName.toLowerCase()] ?? emulatorName;
+ return p.join(base, folderName);
+ }
+
+ @override
+ String getBiosPath(String home, String? emudeckRoot) {
+ return p.join(home, '.var', 'app', 'net.retrodeck.retrodeck', 'config', 'bios');
+ }
+
+ @override
+ Future<String?> findExecutable(String emulatorId, String executableName, String emulatorsRoot, String? emudeckRoot) async {
+ // RetroDECK uses a single flatpak command for all emulators
+ return 'retrodeck-flatpak';
+ }
+
+ @override
+ Future<void> launch(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []}) async {
+ await io.Process.start('flatpak', ['run', 'net.retrodeck.retrodeck', romPath], mode: io.ProcessStartMode.detached);
+ }
+
+ @override
+ Future<io.Process?> launchWithHandle(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []}) async {
+ return await io.Process.start('flatpak', ['run', 'net.retrodeck.retrodeck', romPath], mode: io.ProcessStartMode.normal);
+ }
+
+ @override
+ Future<void> launchStandalone(String emulatorId, String exePath, {List<String> args = const []}) async {
+ await io.Process.start('flatpak', ['run', 'net.retrodeck.retrodeck'], mode: io.ProcessStartMode.detached);
+ }
+}
diff --git a/lib/core/emulator/strategies/azahar_strategy.dart b/lib/core/emulator/strategies/azahar_strategy.dart
index c22f0d2..5fbcb8e 100644
--- a/lib/core/emulator/strategies/azahar_strategy.dart
+++ b/lib/core/emulator/strategies/azahar_strategy.dart
@@ -66,35 +66,10 @@ class AzaharStrategy extends EmulatorStrategy {
final exePath = await _directoryService.findEmulatorExecutable(
emulatorId, getExecutableForPlatform(),
);
-
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isMacOS) {
- if (exePath.endsWith('.dylib')) {
- throw Exception('Found Azahar Libretro core. Please switch to RetroArch in Settings to play this game.');
- }
-
- // On macOS, launching via 'open -a App.app --args rom' is much more stable than launching internal binary
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await _ensure3dsSetup();
- await io.Process.run('open', [appBundlePath, '--args', romPath]);
- return;
- }
- }
- }
-
await _ensure3dsSetup();
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'azahar', romPath], mode: ProcessStartMode.detached);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- } else {
- await Process.start(exePath, [romPath], mode: ProcessStartMode.detached);
- }
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
@@ -102,20 +77,10 @@ class AzaharStrategy extends EmulatorStrategy {
final exePath = await _directoryService.findEmulatorExecutable(
emulatorId, getExecutableForPlatform(),
);
-
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isMacOS && exePath.endsWith('.dylib')) {
- throw Exception('Found Azahar Libretro core. Please switch to RetroArch in Settings to play this game.');
- }
-
await _ensure3dsSetup();
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'azahar', romPath], mode: ProcessStartMode.normal);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- }
- return await Process.start(exePath, [romPath], mode: ProcessStartMode.normal);
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath);
}
@override
@@ -125,32 +90,7 @@ class AzaharStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await io.Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = io.File(exePath).parent.path;
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'azahar'], mode: ProcessStartMode.detached, workingDirectory: exeDir);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached, workingDirectory: exeDir);
- } else {
- await Process.start(
- exePath,
- [],
- mode: ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- }
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/cemu_strategy.dart b/lib/core/emulator/strategies/cemu_strategy.dart
index 888f457..0ad4457 100644
--- a/lib/core/emulator/strategies/cemu_strategy.dart
+++ b/lib/core/emulator/strategies/cemu_strategy.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:io' as io;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -37,13 +36,7 @@ class CemuStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'cemu', romPath], mode: ProcessStartMode.detached);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- } else {
- await Process.start(exePath, ['-g', romPath], mode: ProcessStartMode.detached);
- }
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath, args: ['-g']);
}
@override
@@ -53,12 +46,7 @@ class CemuStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'cemu', romPath], mode: ProcessStartMode.normal);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- }
- return await Process.start(exePath, ['-g', romPath], mode: ProcessStartMode.normal);
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath, args: ['-g']);
}
@override
@@ -68,32 +56,7 @@ class CemuStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = File(exePath).parent.path;
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'cemu'], mode: ProcessStartMode.detached, workingDirectory: exeDir);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached, workingDirectory: exeDir);
- } else {
- await Process.start(
- exePath,
- [],
- mode: ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- }
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/dolphin_strategy.dart b/lib/core/emulator/strategies/dolphin_strategy.dart
index b1e6eeb..7c9d91a 100644
--- a/lib/core/emulator/strategies/dolphin_strategy.dart
+++ b/lib/core/emulator/strategies/dolphin_strategy.dart
@@ -39,25 +39,10 @@ class DolphinStrategy extends EmulatorStrategy {
if (exePath == null) {
throw Exception('$name not found. Please download it first.');
}
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start(
- 'bash',
- [exePath, '-e', 'dolphin-emu', romPath],
- mode: ProcessStartMode.detached,
- );
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start(
- 'bash',
- [exePath, romPath],
- mode: ProcessStartMode.detached,
- );
- } else {
- await Process.start(
- exePath,
- ['-b', '-e', romPath],
- mode: ProcessStartMode.detached,
- );
- }
+
+ // Dolphin needs -b -e to launch a game in batch mode and exit on close
+ final args = io.Platform.isLinux ? <String>[] : ['-b', '-e'];
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath, args: args);
}
@override
@@ -69,70 +54,26 @@ class DolphinStrategy extends EmulatorStrategy {
if (exePath == null) {
throw Exception('$name not found. Please download it first.');
}
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start(
- 'bash',
- [exePath, '-e', 'dolphin-emu', romPath],
- mode: ProcessStartMode.normal,
- );
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await Process.start(
- 'bash',
- [exePath, romPath],
- mode: ProcessStartMode.normal,
- );
- }
- return await Process.start(
+
+ final args = io.Platform.isLinux ? <String>[] : ['-b', '-e'];
+ return await _directoryService.launchGameWithHandle(
+ game,
+ romPath,
+ emulatorId,
exePath,
- ['-b', '-e', romPath],
- mode: ProcessStartMode.normal,
+ args: args,
);
}
@override
Future<void> launchStandalone() async {
final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId, getExecutableForPlatform(),
+ emulatorId,
+ getExecutableForPlatform(),
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- final exeDir = io.File(exePath).parent.path;
-
- if (io.Platform.isMacOS) {
- // Find the .app bundle path (folder ending in .app containing the executable)
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start(
- 'bash',
- [exePath, '-e', 'dolphin-emu'],
- mode: ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start(
- 'bash',
- [exePath],
- mode: ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- } else {
- await Process.start(
- exePath,
- [],
- mode: ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- }
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/duckstation_strategy.dart b/lib/core/emulator/strategies/duckstation_strategy.dart
index 832ea3c..411dc2a 100644
--- a/lib/core/emulator/strategies/duckstation_strategy.dart
+++ b/lib/core/emulator/strategies/duckstation_strategy.dart
@@ -37,16 +37,7 @@ class DuckstationStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'duckstation', romPath], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- return;
- }
- }
- await Process.start(exePath, ['-batch', romPath], mode: ProcessStartMode.detached);
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath, args: ['-batch']);
}
@override
@@ -56,14 +47,7 @@ class DuckstationStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'duckstation', romPath], mode: ProcessStartMode.normal);
- } else if (exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- }
- }
- return await Process.start(exePath, ['-batch', romPath], mode: ProcessStartMode.normal);
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath, args: ['-batch']);
}
@override
@@ -73,36 +57,7 @@ class DuckstationStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'duckstation'], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
- }
-
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = File(exePath).parent.path;
- await Process.start(
- exePath,
- [],
- mode: ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/eden_strategy.dart b/lib/core/emulator/strategies/eden_strategy.dart
index 8e4ef81..d311a8f 100644
--- a/lib/core/emulator/strategies/eden_strategy.dart
+++ b/lib/core/emulator/strategies/eden_strategy.dart
@@ -32,72 +32,38 @@ class EdenStrategy extends EmulatorStrategy {
@override
Future<void> launch(Game game, String romPath) async {
final resolvedPath = _resolveRomPath(romPath);
- final exePath = await _directoryService.findEmulatorExecutable(emulatorId, getExecutableForPlatform());
- if (exePath == null) return;
-
- String? workingDir;
- if (io.Platform.isMacOS) {
- workingDir = io.File(exePath).parent.path;
- }
-
- final args = [resolvedPath];
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await io.Process.start(
- 'bash',
- [exePath, '-e', 'eden', ...args],
- mode: io.ProcessStartMode.detached,
- workingDirectory: workingDir,
- );
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await io.Process.start(
- 'bash',
- [exePath, ...args],
- mode: io.ProcessStartMode.detached,
- workingDirectory: workingDir,
- );
- } else {
- await io.Process.start(
- exePath,
- args,
- mode: io.ProcessStartMode.detached,
- workingDirectory: workingDir,
- );
- }
+ final exePath = await _directoryService.findEmulatorExecutable(
+ emulatorId, getExecutableForPlatform(),
+ );
+ if (exePath == null) throw Exception('$name not found. Please download it first.');
+
+ await _directoryService.launchGame(game, resolvedPath, emulatorId, exePath);
}
@override
Future<io.Process?> launchWithHandle(Game game, String romPath) async {
final resolvedPath = _resolveRomPath(romPath);
- final exePath = await _directoryService.findEmulatorExecutable(emulatorId, getExecutableForPlatform());
- if (exePath == null) return null;
-
- String? workingDir;
- if (io.Platform.isMacOS) {
- workingDir = io.File(exePath).parent.path;
- }
+ final exePath = await _directoryService.findEmulatorExecutable(
+ emulatorId, getExecutableForPlatform(),
+ );
+ if (exePath == null) throw Exception('$name not found. Please download it first.');
+
+ return await _directoryService.launchGameWithHandle(game, resolvedPath, emulatorId, exePath);
+ }
- final args = [resolvedPath];
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await io.Process.start(
- 'bash',
- [exePath, '-e', 'eden', ...args],
- mode: io.ProcessStartMode.normal,
- workingDirectory: workingDir,
- );
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await io.Process.start(
- 'bash',
- [exePath, ...args],
- mode: io.ProcessStartMode.normal,
- workingDirectory: workingDir,
- );
- }
- return await io.Process.start(
- exePath,
- args,
- mode: io.ProcessStartMode.normal,
- workingDirectory: workingDir,
+ @override
+ Future<void> launchStandalone() async {
+ final exePath = await _directoryService.findEmulatorExecutable(
+ emulatorId, getExecutableForPlatform(),
);
+ if (exePath == null) throw Exception('$name not found. Please download it first.');
+
+ await _directoryService.launchStandalone(emulatorId, exePath);
+ }
+
+ @override
+ String resolveSavePath(Game game) {
+ return "";
}
String _resolveRomPath(String romPath) {
@@ -118,55 +84,4 @@ class EdenStrategy extends EmulatorStrategy {
}
return romPath;
}
-
- @override
- Future<void> launchStandalone() async {
- final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId, getExecutableForPlatform(),
- );
- if (exePath == null) throw Exception('$name not found. Please download it first.');
-
- final exeDir = io.File(exePath).parent.path;
-
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await io.Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await io.Process.start(
- 'bash',
- [exePath, '-e', 'eden'],
- mode: io.ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await io.Process.start(
- 'bash',
- [exePath],
- mode: io.ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- } else {
- await io.Process.start(
- exePath,
- [],
- mode: io.ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- }
- }
-
- @override
- String resolveSavePath(Game game) {
- return "";
- }
}
diff --git a/lib/core/emulator/strategies/flycast_strategy.dart b/lib/core/emulator/strategies/flycast_strategy.dart
index 78c7baa..b935e33 100644
--- a/lib/core/emulator/strategies/flycast_strategy.dart
+++ b/lib/core/emulator/strategies/flycast_strategy.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:io' as io;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -33,32 +32,21 @@ class FlycastStrategy extends EmulatorStrategy {
@override
Future<void> launch(Game game, String romPath) async {
final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId,
- getExecutableForPlatform(),
+ emulatorId, getExecutableForPlatform(),
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'flycast', romPath], mode: ProcessStartMode.detached);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- } else {
- await Process.start(exePath, [romPath], mode: ProcessStartMode.detached);
- }
+
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
Future<Process?> launchWithHandle(Game game, String romPath) async {
final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId,
- getExecutableForPlatform(),
+ emulatorId, getExecutableForPlatform(),
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'flycast', romPath], mode: ProcessStartMode.normal);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- }
- return await Process.start(exePath, [romPath], mode: ProcessStartMode.normal);
+
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath);
}
@override
@@ -68,32 +56,7 @@ class FlycastStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = File(exePath).parent.path;
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'flycast'], mode: ProcessStartMode.detached, workingDirectory: exeDir);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached, workingDirectory: exeDir);
- } else {
- await Process.start(
- exePath,
- [],
- mode: ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
- }
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/mame_strategy.dart b/lib/core/emulator/strategies/mame_strategy.dart
index f7e65d3..7a6b7c3 100644
--- a/lib/core/emulator/strategies/mame_strategy.dart
+++ b/lib/core/emulator/strategies/mame_strategy.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:io' as io;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -34,16 +33,7 @@ class MAMEStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'mame', romPath], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- return;
- }
- }
- await Process.start(exePath, [romPath], mode: ProcessStartMode.detached);
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
@@ -53,14 +43,7 @@ class MAMEStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'mame', romPath], mode: ProcessStartMode.normal);
- } else if (exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- }
- }
- return await Process.start(exePath, [romPath], mode: ProcessStartMode.normal);
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath);
}
@override
@@ -70,23 +53,7 @@ class MAMEStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'mame'], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
- }
-
- final exeDir = File(exePath).parent.path;
- await Process.start(
- exePath,
- [],
- mode: ProcessStartMode.detached,
- workingDirectory: exeDir,
- );
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/melonds_strategy.dart b/lib/core/emulator/strategies/melonds_strategy.dart
index bf8072a..849ff93 100644
--- a/lib/core/emulator/strategies/melonds_strategy.dart
+++ b/lib/core/emulator/strategies/melonds_strategy.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:io' as io;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -33,40 +32,21 @@ class MelonDSStrategy extends EmulatorStrategy {
@override
Future<void> launch(Game game, String romPath) async {
final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId,
- getExecutableForPlatform(),
+ emulatorId, getExecutableForPlatform(),
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'melonds', romPath], workingDirectory: File(exePath).parent.path, mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], workingDirectory: File(exePath).parent.path, mode: ProcessStartMode.detached);
- return;
- }
- }
- await Process.start(exePath, [romPath], workingDirectory: File(exePath).parent.path, mode: ProcessStartMode.detached);
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
Future<Process?> launchWithHandle(Game game, String romPath) async {
final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId,
- getExecutableForPlatform(),
+ emulatorId, getExecutableForPlatform(),
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- final workingDir = File(exePath).parent.path;
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'melonds', romPath], workingDirectory: workingDir, mode: ProcessStartMode.normal);
- } else if (exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], workingDirectory: workingDir, mode: ProcessStartMode.normal);
- }
- }
- return await Process.start(exePath, [romPath], workingDirectory: workingDir, mode: ProcessStartMode.normal);
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath);
}
@override
@@ -76,31 +56,7 @@ class MelonDSStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'melonds'], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
- }
-
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = File(exePath).parent.path;
- await Process.start(exePath, [], workingDirectory: exeDir, mode: ProcessStartMode.detached);
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/mgba_strategy.dart b/lib/core/emulator/strategies/mgba_strategy.dart
index 2075d3c..48c75e4 100644
--- a/lib/core/emulator/strategies/mgba_strategy.dart
+++ b/lib/core/emulator/strategies/mgba_strategy.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:io' as io;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -37,16 +36,7 @@ class MGBAStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'mgba', romPath], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- return;
- }
- }
- await Process.start(exePath, [romPath], mode: ProcessStartMode.detached);
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
@@ -56,14 +46,7 @@ class MGBAStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'mgba', romPath], mode: ProcessStartMode.normal);
- } else if (exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- }
- }
- return await Process.start(exePath, [romPath], mode: ProcessStartMode.normal);
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath);
}
@override
@@ -73,31 +56,7 @@ class MGBAStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'mgba'], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
- }
-
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = File(exePath).parent.path;
- await Process.start(exePath, [], mode: ProcessStartMode.detached, workingDirectory: exeDir);
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/pcsx2_strategy.dart b/lib/core/emulator/strategies/pcsx2_strategy.dart
index 6f98fce..0d12ffc 100644
--- a/lib/core/emulator/strategies/pcsx2_strategy.dart
+++ b/lib/core/emulator/strategies/pcsx2_strategy.dart
@@ -33,80 +33,39 @@ class Pcsx2Strategy extends EmulatorStrategy {
@override
Future<void> launch(Game game, String romPath) async {
final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId, getExecutableForPlatform(),
+ emulatorId,
+ getExecutableForPlatform(),
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
-
- if (io.Platform.isWindows) {
- final normalizedRom = romPath.replaceAll('/', '\\');
- final normalizedExe = exePath.replaceAll('/', '\\');
- await Process.start(normalizedExe, [normalizedRom], mode: ProcessStartMode.detached);
- } else if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'pcsx2-Qt', romPath], mode: ProcessStartMode.detached);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- } else {
- await Process.start(exePath, [romPath], mode: ProcessStartMode.detached);
- }
+
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
Future<Process?> launchWithHandle(Game game, String romPath) async {
final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId, getExecutableForPlatform(),
+ emulatorId,
+ getExecutableForPlatform(),
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
-
- if (io.Platform.isWindows) {
- final normalizedRom = romPath.replaceAll('/', '\\');
- final normalizedExe = exePath.replaceAll('/', '\\');
- return await Process.start(normalizedExe, [normalizedRom], mode: ProcessStartMode.normal);
- } else if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'pcsx2-Qt', romPath], mode: ProcessStartMode.normal);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- } else {
- return await Process.start(exePath, [romPath], mode: ProcessStartMode.normal);
- }
+
+ return await _directoryService.launchGameWithHandle(
+ game,
+ romPath,
+ emulatorId,
+ exePath,
+ );
}
@override
Future<void> launchStandalone() async {
final exePath = await _directoryService.findEmulatorExecutable(
- emulatorId, getExecutableForPlatform(),
+ emulatorId,
+ getExecutableForPlatform(),
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'pcsx2-Qt'], mode: ProcessStartMode.detached);
- return;
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
-
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = File(exePath).parent.path;
-
- if (io.Platform.isWindows) {
- final normalizedExe = exePath.replaceAll('/', '\\');
- final normalizedDir = exeDir.replaceAll('/', '\\');
- await Process.start(normalizedExe, [], mode: ProcessStartMode.detached, workingDirectory: normalizedDir);
- } else {
- await Process.start(exePath, [], mode: ProcessStartMode.detached, workingDirectory: exeDir);
- }
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/ppsspp_strategy.dart b/lib/core/emulator/strategies/ppsspp_strategy.dart
index fa8855c..70807d9 100644
--- a/lib/core/emulator/strategies/ppsspp_strategy.dart
+++ b/lib/core/emulator/strategies/ppsspp_strategy.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:io' as io;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -37,16 +36,7 @@ class PPSSPPStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'ppsspp', romPath], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- return;
- }
- }
- await Process.start(exePath, [romPath], mode: ProcessStartMode.detached);
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
@@ -56,14 +46,7 @@ class PPSSPPStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'ppsspp', romPath], mode: ProcessStartMode.normal);
- } else if (exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- }
- }
- return await Process.start(exePath, [romPath], mode: ProcessStartMode.normal);
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath);
}
@override
@@ -73,30 +56,7 @@ class PPSSPPStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'ppsspp'], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
- }
-
- if (io.Platform.isMacOS) {
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = File(exePath).parent.path;
- await Process.start(exePath, [], mode: ProcessStartMode.detached, workingDirectory: exeDir);
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/retroarch_strategy.dart b/lib/core/emulator/strategies/retroarch_strategy.dart
index 7f635b2..7bc1ea3 100644
--- a/lib/core/emulator/strategies/retroarch_strategy.dart
+++ b/lib/core/emulator/strategies/retroarch_strategy.dart
@@ -1,5 +1,5 @@
import 'dart:io' as io show Platform, File, Directory;
-import 'dart:io' show Process, ProcessStartMode;
+import 'dart:io' show Process;
import 'package:dio/dio.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
@@ -243,18 +243,8 @@ class RetroArchStrategy extends EmulatorStrategy {
await _ensure3dsSetup();
}
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'retroarch', normalizedRomPath], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, normalizedRomPath], mode: ProcessStartMode.detached);
- return;
- }
- }
-
if (coreName == null) {
- await Process.start(exePath, [normalizedRomPath], mode: ProcessStartMode.detached);
+ await _directoryService.launchGame(game, normalizedRomPath, emulatorId, exePath);
return;
}
@@ -270,11 +260,7 @@ class RetroArchStrategy extends EmulatorStrategy {
);
}
- await Process.start(
- exePath,
- ['-L', corePath, normalizedRomPath],
- mode: ProcessStartMode.detached,
- );
+ await _directoryService.launchGame(game, normalizedRomPath, emulatorId, exePath, args: ['-L', corePath]);
}
@override
@@ -299,16 +285,8 @@ class RetroArchStrategy extends EmulatorStrategy {
await _ensure3dsSetup();
}
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'retroarch', normalizedRomPath], mode: ProcessStartMode.normal);
- } else if (exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, normalizedRomPath], mode: ProcessStartMode.normal);
- }
- }
-
if (coreName == null) {
- return await Process.start(exePath, [normalizedRomPath], mode: ProcessStartMode.normal);
+ return await _directoryService.launchGameWithHandle(game, normalizedRomPath, emulatorId, exePath);
}
final corePath = await _resolveCorePath(exePath, coreName);
@@ -323,11 +301,7 @@ class RetroArchStrategy extends EmulatorStrategy {
);
}
- return await Process.start(
- exePath,
- ['-L', corePath, normalizedRomPath],
- mode: ProcessStartMode.normal,
- );
+ return await _directoryService.launchGameWithHandle(game, normalizedRomPath, emulatorId, exePath, args: ['-L', corePath]);
}
Future<void> downloadCore(String coreName, String coresDir, Dio dio) async {
@@ -371,31 +345,7 @@ class RetroArchStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux) {
- if (_directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'retroarch'], mode: ProcessStartMode.detached);
- return;
- } else if (exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
- }
-
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await io.Directory(appBundlePath).exists()) {
- await Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = io.File(exePath).parent.path;
- await Process.start(exePath, [], mode: ProcessStartMode.detached, workingDirectory: exeDir);
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/rpcs3_strategy.dart b/lib/core/emulator/strategies/rpcs3_strategy.dart
index 6ada03b..9ccccc2 100644
--- a/lib/core/emulator/strategies/rpcs3_strategy.dart
+++ b/lib/core/emulator/strategies/rpcs3_strategy.dart
@@ -1,6 +1,4 @@
import 'dart:io';
-import 'dart:io' as io;
-import 'package:path/path.dart' as p;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -38,14 +36,7 @@ class Rpcs3Strategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- final normalizedRomPath = p.normalize(romPath);
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'rpcs3', normalizedRomPath], mode: ProcessStartMode.detached);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, normalizedRomPath], mode: ProcessStartMode.detached);
- } else {
- await Process.start(exePath, [normalizedRomPath], mode: ProcessStartMode.detached);
- }
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
@@ -55,14 +46,7 @@ class Rpcs3Strategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- final normalizedRomPath = p.normalize(romPath);
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'rpcs3', normalizedRomPath], mode: ProcessStartMode.normal);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, normalizedRomPath], mode: ProcessStartMode.normal);
- } else {
- return await Process.start(exePath, [normalizedRomPath], mode: ProcessStartMode.normal);
- }
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath);
}
@override
@@ -72,29 +56,7 @@ class Rpcs3Strategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'rpcs3'], mode: ProcessStartMode.detached);
- return;
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
-
- if (io.Platform.isMacOS) {
- // Find the .app bundle path
- final parts = exePath.split('/');
- final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
- if (appIdx != -1) {
- final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
- if (await io.Directory(appBundlePath).exists()) {
- await io.Process.run('open', [appBundlePath]);
- return;
- }
- }
- }
-
- final exeDir = File(exePath).parent.path;
- await Process.start(exePath, [], mode: ProcessStartMode.detached, workingDirectory: exeDir);
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/xemu_strategy.dart b/lib/core/emulator/strategies/xemu_strategy.dart
index cff969f..8cdf111 100644
--- a/lib/core/emulator/strategies/xemu_strategy.dart
+++ b/lib/core/emulator/strategies/xemu_strategy.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:io' as io show Platform;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -34,14 +33,7 @@ class XemuStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'xemu-emu', '-dvd_path', romPath], mode: ProcessStartMode.detached);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- // EmuDeck scripts expect the ROM path directly
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- } else {
- await Process.start(exePath, ['-dvd_path', romPath], mode: ProcessStartMode.detached);
- }
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath, args: ['-dvd_path']);
}
@override
@@ -51,13 +43,7 @@ class XemuStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'xemu-emu', '-dvd_path', romPath], mode: ProcessStartMode.normal);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- } else {
- return await Process.start(exePath, ['-dvd_path', romPath], mode: ProcessStartMode.normal);
- }
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath, args: ['-dvd_path']);
}
@override
@@ -67,16 +53,7 @@ class XemuStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'xemu-emu'], mode: ProcessStartMode.detached);
- return;
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
-
- final exeDir = File(exePath).parent.path;
- await Process.start(exePath, [], mode: ProcessStartMode.detached, workingDirectory: exeDir);
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/emulator/strategies/xenia_strategy.dart b/lib/core/emulator/strategies/xenia_strategy.dart
index e4c4c51..552baef 100644
--- a/lib/core/emulator/strategies/xenia_strategy.dart
+++ b/lib/core/emulator/strategies/xenia_strategy.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:io' as io show Platform;
import 'package:freegosy/core/emulator/emulator_strategy.dart';
import 'package:freegosy/core/romm/romm_models.dart';
import 'package:freegosy/core/storage/directory_service.dart';
@@ -34,13 +33,7 @@ class XeniaStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'xenia', romPath], mode: ProcessStartMode.detached);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.detached);
- } else {
- await Process.start(exePath, [romPath], mode: ProcessStartMode.detached);
- }
+ await _directoryService.launchGame(game, romPath, emulatorId, exePath);
}
@override
@@ -50,13 +43,7 @@ class XeniaStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- return await Process.start('bash', [exePath, '-e', 'xenia', romPath], mode: ProcessStartMode.normal);
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- return await Process.start('bash', [exePath, romPath], mode: ProcessStartMode.normal);
- } else {
- return await Process.start(exePath, [romPath], mode: ProcessStartMode.normal);
- }
+ return await _directoryService.launchGameWithHandle(game, romPath, emulatorId, exePath);
}
@override
@@ -66,15 +53,7 @@ class XeniaStrategy extends EmulatorStrategy {
);
if (exePath == null) throw Exception('$name not found. Please download it first.');
- if (io.Platform.isLinux && _directoryService.isEmuLaunchScript(exePath)) {
- await Process.start('bash', [exePath, '-e', 'xenia'], mode: ProcessStartMode.detached);
- return;
- } else if (io.Platform.isLinux && exePath.endsWith('.sh')) {
- await Process.start('bash', [exePath], mode: ProcessStartMode.detached);
- return;
- }
- final exeDir = File(exePath).parent.path;
- await Process.start(exePath, [], mode: ProcessStartMode.detached, workingDirectory: exeDir);
+ await _directoryService.launchStandalone(emulatorId, exePath);
}
@override
diff --git a/lib/core/save/strategies/dolphin_save_strategy.dart b/lib/core/save/strategies/dolphin_save_strategy.dart
index a34dbeb..faf6c3d 100644
--- a/lib/core/save/strategies/dolphin_save_strategy.dart
+++ b/lib/core/save/strategies/dolphin_save_strategy.dart
@@ -73,7 +73,9 @@ class DolphinSaveStrategy extends SaveStrategy {
@override
Future<String?> getSaveDir(Game game, String romPath) async {
final userDir = await _getUserDir(platformSlug: game.platformSlug);
- final bool isEmuDeck = io.Platform.isLinux && userDir.contains('Emulation/saves');
+ final bool isIntegratedEnv = io.Platform.isLinux &&
+ (_directoryService.linuxSyncPreset == 'emudeck' ||
+ _directoryService.linuxSyncPreset == 'retrodeck');
final isWii = game.platformSlug?.toLowerCase() == 'wii';
@@ -81,7 +83,7 @@ class DolphinSaveStrategy extends SaveStrategy {
// Wii saves are in Wii/title/00010000/[TITLE_ID_HEX]
String? titleId = _extractGameId(p.basename(romPath));
- final String wiiBase = (isEmuDeck && p.basename(userDir) == 'Wii')
+ final String wiiBase = (isIntegratedEnv && p.basename(userDir).toLowerCase() == 'wii')
? userDir
: p.join(userDir, 'Wii');
@@ -97,7 +99,7 @@ class DolphinSaveStrategy extends SaveStrategy {
} else {
// GameCube
final region = _detectRegion(romPath);
- final String gcBase = (isEmuDeck && p.basename(userDir) == 'GC')
+ final String gcBase = (isIntegratedEnv && p.basename(userDir).toLowerCase() == 'gc')
? userDir
: p.join(userDir, 'GC');
diff --git a/lib/core/save/strategies/pcsx2_save_strategy.dart b/lib/core/save/strategies/pcsx2_save_strategy.dart
index 62ab192..5185e95 100644
--- a/lib/core/save/strategies/pcsx2_save_strategy.dart
+++ b/lib/core/save/strategies/pcsx2_save_strategy.dart
@@ -43,11 +43,15 @@ class Pcsx2SaveStrategy extends SaveStrategy {
}
}
- // 2. Linux & EmuDeck prioritized
+ // 2. Linux integration (EmuDeck / RetroDECK)
if (io.Platform.isLinux) {
final baseDir = await _directoryService.getEmulatorAppSupportDirectory('pcsx2');
- if (p.basename(baseDir) == 'saves') {
+ final bool isSteamDeckEnv = _directoryService.linuxSyncPreset == 'emudeck' ||
+ _directoryService.linuxSyncPreset == 'retrodeck';
+
+ if (isSteamDeckEnv && (p.basename(baseDir) == 'saves' || p.basename(baseDir) == 'PCSX2')) {
// EmuDeck mapping returns the folder containing the actual saves/cards
+ // RetroDECK mapping returns the PCSX2 folder which also contains memcards/sstates
return baseDir;
}
@@ -91,6 +95,9 @@ class Pcsx2SaveStrategy extends SaveStrategy {
final result = <io.File>[];
// Memory cards
+ // EmuDeck: saves/pcsx2/ (mapped as root)
+ // RetroDECK: PCSX2/memcards/
+ // Native: PCSX2/memcards/
final memcardsDir = io.Directory(isEmuDeck ? root : p.join(root, 'memcards'));
if (await memcardsDir.exists()) {
await for (final entity in memcardsDir.list()) {
diff --git a/lib/core/save/strategies/retroarch_save_strategy.dart b/lib/core/save/strategies/retroarch_save_strategy.dart
index 205f0c5..e8add90 100644
--- a/lib/core/save/strategies/retroarch_save_strategy.dart
+++ b/lib/core/save/strategies/retroarch_save_strategy.dart
@@ -101,8 +101,18 @@ class RetroArchSaveStrategy extends SaveStrategy {
if (io.Platform.isLinux) {
rootSaveDir = await getSaveDir(game, romPath);
final baseDir = await _directoryService.getEmulatorAppSupportDirectory('retroarch', platformSlug: slug);
- // states folder is next to saves folder in EmuDeck structure
- statesRoot = p.join(p.dirname(baseDir), 'states', coreInfo.statesFolder);
+
+ if (_directoryService.linuxSyncPreset == 'emudeck') {
+ // EmuDeck: saves are in Emulation/saves/retroarch, states in Emulation/states/retroarch
+ // baseDir is .../Emulation/saves/retroarch
+ final emulationRoot = p.dirname(p.dirname(baseDir));
+ statesRoot = p.join(emulationRoot, 'states', 'retroarch', coreInfo.statesFolder);
+ } else if (_directoryService.linuxSyncPreset == 'retrodeck') {
+ // RetroDECK: baseDir is .../retroarch/
+ statesRoot = p.join(baseDir, 'states', coreInfo.statesFolder);
+ } else {
+ statesRoot = p.join(p.dirname(baseDir), 'states', coreInfo.statesFolder);
+ }
} else {
final exePath = await _directoryService.findEmulatorExecutable('retroarch', 'RetroArch.exe');
if (exePath == null) return {};
@@ -234,9 +244,23 @@ class RetroArchSaveStrategy extends SaveStrategy {
if (io.Platform.isLinux) {
final baseDir = await _directoryService.getEmulatorAppSupportDirectory('retroarch', platformSlug: slug);
- targetDir = isState
- ? p.join(p.dirname(baseDir), 'states', coreInfo.statesFolder)
- : p.join(baseDir, coreInfo.saveFolder);
+
+ if (_directoryService.linuxSyncPreset == 'emudeck') {
+ if (isState) {
+ final emulationRoot = p.dirname(p.dirname(baseDir));
+ targetDir = p.join(emulationRoot, 'states', 'retroarch', coreInfo.statesFolder);
+ } else {
+ targetDir = p.join(baseDir, coreInfo.saveFolder);
+ }
+ } else if (_directoryService.linuxSyncPreset == 'retrodeck') {
+ targetDir = isState
+ ? p.join(baseDir, 'states', coreInfo.statesFolder)
+ : p.join(baseDir, 'saves', coreInfo.saveFolder);
+ } else {
+ targetDir = isState
+ ? p.join(p.dirname(baseDir), 'states', coreInfo.statesFolder)
+ : p.join(baseDir, coreInfo.saveFolder);
+ }
} else {
final exePath = await _directoryService.findEmulatorExecutable('retroarch', 'RetroArch.exe');
if (exePath == null) return false;
diff --git a/lib/core/storage/directory_service.dart b/lib/core/storage/directory_service.dart
index 10e2b11..7f1cebe 100644
--- a/lib/core/storage/directory_service.dart
+++ b/lib/core/storage/directory_service.dart
@@ -6,6 +6,10 @@ import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:freegosy/core/romm/romm_models.dart';
+import 'package:freegosy/core/emulator/linux_strategies/linux_environment_strategy.dart';
+import 'package:freegosy/core/emulator/linux_strategies/native_linux_strategy.dart';
+import 'package:freegosy/core/emulator/linux_strategies/emudeck_strategy.dart';
+import 'package:freegosy/core/emulator/linux_strategies/retrodeck_strategy.dart';
enum StorageError { none, pathNotFound, permissionDenied, unknown }
@@ -55,24 +59,45 @@ class DirectoryService {
String? emudeckRootPath;
final Map<String, String> _emulatorPathOverrides = {};
StorageStatus status = const StorageStatus();
+
+ LinuxEnvironmentStrategy? _linuxStrategy;
DirectoryService();
+ LinuxEnvironmentStrategy get activeLinuxEnvironment {
+ if (_linuxStrategy != null) return _linuxStrategy!;
+
+ switch (linuxSyncPreset) {
+ case 'emudeck':
+ _linuxStrategy = EmuDeckStrategy();
+ break;
+ case 'retrodeck':
+ _linuxStrategy = RetroDeckStrategy();
+ break;
+ default:
+ _linuxStrategy = NativeLinuxStrategy();
+ }
+ return _linuxStrategy!;
+ }
+
Future<StorageStatus> initialize() async {
try {
final prefs = await SharedPreferences.getInstance();
linuxSyncPreset = prefs.getString(_linuxSyncPresetKey) ?? 'default';
emudeckRootPath = prefs.getString(_emudeckRootPathKey);
+
+ // Reset strategy to force re-instantiation with correct preset
+ _linuxStrategy = null;
final String defaultBase = await getDefaultBase();
+ final home = io.Platform.environment['HOME'] ?? '';
- if (defaultTargetPlatform == TargetPlatform.linux &&
- linuxSyncPreset == 'emudeck' &&
- emudeckRootPath != null) {
- romsRootPath =
- prefs.getString(_romsRootPathKey) ?? p.join(emudeckRootPath!, 'Emulation/roms');
- emulatorsRootPath =
- prefs.getString(_emulatorsRootPathKey) ?? p.join(emudeckRootPath!, 'Emulation/tools');
+ if (defaultTargetPlatform == TargetPlatform.linux) {
+ final customRoms = prefs.getString(_romsRootPathKey);
+ final customEmus = prefs.getString(_emulatorsRootPathKey);
+
+ romsRootPath = activeLinuxEnvironment.getRomsRoot(home, customRoms, emudeckRootPath);
+ emulatorsRootPath = activeLinuxEnvironment.getEmulatorsRoot(home, customEmus, emudeckRootPath);
} else {
romsRootPath = prefs.getString(_romsRootPathKey) ?? '$defaultBase/ROMs';
emulatorsRootPath =
@@ -115,10 +140,10 @@ class DirectoryService {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_romsRootPathKey);
final base = await getDefaultBase();
- if (defaultTargetPlatform == TargetPlatform.linux &&
- linuxSyncPreset == 'emudeck' &&
- emudeckRootPath != null) {
- romsRootPath = p.join(emudeckRootPath!, 'Emulation/roms');
+ final home = io.Platform.environment['HOME'] ?? '';
+
+ if (defaultTargetPlatform == TargetPlatform.linux) {
+ romsRootPath = activeLinuxEnvironment.getRomsRoot(home, null, emudeckRootPath);
} else {
romsRootPath = '$base/ROMs';
}
@@ -129,10 +154,10 @@ class DirectoryService {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_emulatorsRootPathKey);
final base = await getDefaultBase();
- if (defaultTargetPlatform == TargetPlatform.linux &&
- linuxSyncPreset == 'emudeck' &&
- emudeckRootPath != null) {
- emulatorsRootPath = p.join(emudeckRootPath!, 'Emulation/tools');
+ final home = io.Platform.environment['HOME'] ?? '';
+
+ if (defaultTargetPlatform == TargetPlatform.linux) {
+ emulatorsRootPath = activeLinuxEnvironment.getEmulatorsRoot(home, null, emudeckRootPath);
} else {
emulatorsRootPath = '$base/Emulators';
}
@@ -421,36 +446,16 @@ class DirectoryService {
final appData = io.Platform.environment['APPDATA'] ?? '';
return p.join(appData, emulatorName);
} else if (io.Platform.isLinux) {
- if (linuxSyncPreset == 'emudeck' && emudeckRootPath != null) {
- // EmuDeck folder names mapping (some are capitalized)
- final Map<String, String> emudeckSavesMap = {
- 'cemu': 'Cemu',
- 'vita3k': 'Vita3K',
- 'mame': 'MAME',
- };
-
- final emuFolderName = emudeckSavesMap[emulatorName.toLowerCase()] ?? emulatorName.toLowerCase();
- final base = p.join(emudeckRootPath!, 'Emulation', 'saves', emuFolderName);
-
- if (platformSlug != null) {
- final slug = platformSlug.toLowerCase();
- if (emulatorName.toLowerCase() == 'dolphin' || emulatorName.toLowerCase() == 'primehack') {
- if (slug == 'gc' || slug == 'gamecube' || slug == 'ngc') return p.join(base, 'GC');
- if (slug == 'wii') return p.join(base, 'Wii');
- }
- }
-
- return platformSlug != null ? p.join(base, platformSlug) : base;
- }
final home = io.Platform.environment['HOME'] ?? '';
- return p.join(home, '.config', emulatorName);
+ return activeLinuxEnvironment.getEmulatorAppSupportDirectory(home, emulatorName, emudeckRootPath, platformSlug: platformSlug);
}
throw UnsupportedError('Platform not supported for save path resolution');
}
Future<String> getEmulatorBiosDirectory(String emulatorId, {String? platformSlug}) async {
- if (io.Platform.isLinux && linuxSyncPreset == 'emudeck' && emudeckRootPath != null) {
- return p.join(emudeckRootPath!, 'Emulation', 'bios');
+ if (io.Platform.isLinux) {
+ final home = io.Platform.environment['HOME'] ?? '';
+ return activeLinuxEnvironment.getBiosPath(home, emudeckRootPath);
}
final emuDir = await getEmulatorDirectory(emulatorId);
final dirPath = p.join(emuDir, 'BIOS');
@@ -493,41 +498,9 @@ class DirectoryService {
return direct.path;
}
- // EmuDeck support: Check for master emu-launch.sh first
- if (io.Platform.isLinux && emudeckRootPath != null) {
- final masterLauncher = File(p.join(emudeckRootPath!, 'Emulation', 'tools', 'emu-launch.sh'));
- if (await masterLauncher.exists()) {
- debugPrint("[DirectoryService] Found EmuDeck master launcher: ${masterLauncher.path}");
- return masterLauncher.path;
- }
-
- // Fallback to specific .sh launchers in [ROOT]/tools/launchers
- final Map<String, String> emudeckMap = {
- 'rpcs3': 'rpcs3.sh',
- 'pcsx2': 'pcsx2-qt.sh',
- 'dolphin': 'dolphin-emu.sh',
- 'xemu': 'xemu-emu.sh',
- 'xenia_canary': 'xenia.sh',
- 'citra': 'citra.sh',
- 'azahar': 'azahar.sh',
- 'duckstation': 'duckstation.sh',
- 'melonds': 'melonds.sh',
- 'mgba': 'mgba.sh',
- 'ppsspp': 'ppsspp.sh',
- 'retroarch': 'retroarch.sh',
- 'mame': 'mame.sh',
- 'cemu': 'cemu.sh',
- 'flycast': 'flycast.sh',
- 'vita3k': 'vita3k.sh',
- 'ryujinx': 'ryujinx.sh',
- };
-
- final launcherName = emudeckMap[emulatorId] ?? '$emulatorId.sh';
- final launcherFile = File(p.join(emudeckRootPath!, 'Emulation', 'tools', 'launchers', launcherName));
- if (await launcherFile.exists()) {
- debugPrint("[DirectoryService] Found EmuDeck launcher: ${launcherFile.path}");
- return launcherFile.path;
- }
+ if (io.Platform.isLinux) {
+ final envPath = await activeLinuxEnvironment.findExecutable(emulatorId, executableName, emulatorsRootPath, emudeckRootPath);
+ if (envPath != null) return envPath;
}
if (executableName.contains('/')) {
@@ -638,6 +611,62 @@ class DirectoryService {
return p.basename(path) == 'emu-launch.sh';
}
+ Future<void> launchGame(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []}) async {
+ if (io.Platform.isLinux) {
+ await activeLinuxEnvironment.launch(game, romPath, emulatorId, exePath, args: args);
+ } else {
+ if (io.Platform.isWindows) {
+ final normalizedRom = romPath.replaceAll('/', '\\');
+ final normalizedExe = exePath.replaceAll('/', '\\');
+ await Process.start(normalizedExe, [...args, normalizedRom], mode: io.ProcessStartMode.detached);
+ } else {
+ await Process.start(exePath, [...args, romPath], mode: io.ProcessStartMode.detached);
+ }
+ }
+ }
+
+ Future<Process?> launchGameWithHandle(Game game, String romPath, String emulatorId, String exePath, {List<String> args = const []}) async {
+ if (io.Platform.isLinux) {
+ return await activeLinuxEnvironment.launchWithHandle(game, romPath, emulatorId, exePath, args: args);
+ } else {
+ if (io.Platform.isWindows) {
+ final normalizedRom = romPath.replaceAll('/', '\\');
+ final normalizedExe = exePath.replaceAll('/', '\\');
+ return await Process.start(normalizedExe, [...args, normalizedRom], mode: io.ProcessStartMode.normal);
+ } else {
+ return await Process.start(exePath, [...args, romPath], mode: io.ProcessStartMode.normal);
+ }
+ }
+ }
+
+ Future<void> launchStandalone(String emulatorId, String exePath, {List<String> args = const []}) async {
+ if (io.Platform.isLinux) {
+ await activeLinuxEnvironment.launchStandalone(emulatorId, exePath, args: args);
+ } else {
+ if (io.Platform.isMacOS) {
+ // Find the .app bundle path
+ final parts = exePath.split('/');
+ final appIdx = parts.indexWhere((p) => p.endsWith('.app'));
+ if (appIdx != -1) {
+ final appBundlePath = parts.sublist(0, appIdx + 1).join('/');
+ if (await Directory(appBundlePath).exists()) {
+ await io.Process.run('open', [appBundlePath]);
+ return;
+ }
+ }
+ }
+
+ final exeDir = File(exePath).parent.path;
+ if (io.Platform.isWindows) {
+ final normalizedExe = exePath.replaceAll('/', '\\');
+ final normalizedDir = exeDir.replaceAll('/', '\\');
+ await Process.start(normalizedExe, args, mode: io.ProcessStartMode.detached, workingDirectory: normalizedDir);
+ } else {
+ await Process.start(exePath, args, mode: io.ProcessStartMode.detached, workingDirectory: exeDir);
+ }
+ }
+ }
+
Future<void> deleteRom(Game game) async {
final path = await findExistingRomPath(game);
if (path != null) {
diff --git a/lib/ui/screens/settings_screen.dart b/lib/ui/screens/settings_screen.dart
index 2b650cc..0802244 100644
--- a/lib/ui/screens/settings_screen.dart
+++ b/lib/ui/screens/settings_screen.dart
@@ -1,3 +1,5 @@
+import 'dart:io' as io;
+import 'package:path/path.dart' as p;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -514,6 +516,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
segments: const [
ButtonSegment(value: 'default', label: Text('Default')),
ButtonSegment(value: 'emudeck', label: Text('EmuDeck')),
+ ButtonSegment(value: 'retrodeck', label: Text('RetroDECK')),
],
selected: {directoryService.linuxSyncPreset},
onSelectionChanged: (selection) async {
@@ -542,10 +545,95 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
style: TextStyle(fontSize: 12, color: Colors.grey, fontStyle: FontStyle.italic),
),
],
+ if (directoryService.linuxSyncPreset == 'retrodeck') ...[
+ const SizedBox(height: 16),
+ const Text(
+ 'RetroDECK integration is active. Games will be launched via flatpak.',
+ style: TextStyle(fontSize: 14, color: Colors.deepPurpleAccent),
+ ),
+ const SizedBox(height: 8),
+ const Text(
+ 'Default paths: ~/retrodeck/roms/ for ROMs and ~/.var/app/net.retrodeck.retrodeck/config/ for saves.',
+ style: TextStyle(fontSize: 12, color: Colors.grey, fontStyle: FontStyle.italic),
+ ),
+ ],
+ // Detection suggestions
+ _buildDetectionSuggestions(directoryService),
],
);
}
+ Widget _buildDetectionSuggestions(DirectoryService directoryService) {
+ return FutureBuilder<Map<String, bool>>(
+ future: () async {
+ if (defaultTargetPlatform != TargetPlatform.linux) return <String, bool>{};
+
+ bool retrodeckFound = false;
+ try {
+ final result = await io.Process.run('flatpak', ['info', 'net.retrodeck.retrodeck']);
+ retrodeckFound = result.exitCode == 0;
+ } catch (_) {}
+
+ final home = io.Platform.environment['HOME'] ?? '';
+ final emudeckFound = await io.Directory(p.join(home, 'Emulation', 'roms')).exists();
+
+ return <String, bool>{'retrodeck': retrodeckFound, 'emudeck': emudeckFound};
+ }(),
+ builder: (context, snapshot) {
+ if (!snapshot.hasData || snapshot.data!.isEmpty) return const SizedBox.shrink();
+
+ final found = snapshot.data!;
+ if (!found['retrodeck']! && !found['emudeck']!) return const SizedBox.shrink();
+
+ final current = directoryService.linuxSyncPreset;
+ String? suggestion;
+ String? suggestionId;
+
+ if (found['retrodeck']! && current != 'retrodeck') {
+ suggestion = 'RetroDECK';
+ suggestionId = 'retrodeck';
+ } else if (found['emudeck']! && current == 'default') {
+ suggestion = 'EmuDeck';
+ suggestionId = 'emudeck';
+ }
+
+ if (suggestion == null) return const SizedBox.shrink();
+
+ return Padding(
+ padding: const EdgeInsets.only(top: 16),
+ child: Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: Colors.deepPurple.withValues(alpha: 0.1),
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(color: Colors.deepPurple.withValues(alpha: 0.3)),
+ ),
+ child: Row(
+ children: [
+ const Icon(Icons.info_outline, size: 20, color: Colors.deepPurpleAccent),
+ const SizedBox(width: 12),
+ Expanded(
+ child: Text(
+ 'We detected $suggestion on your system. Would you like to switch?',
+ style: const TextStyle(fontSize: 13),
+ ),
+ ),
+ TextButton(
+ onPressed: () async {
+ await directoryService.setLinuxSyncPreset(suggestionId!);
+ ref.invalidate(directoryServiceProvider);
+ if (mounted) setState(() {});
+ },
+ child: const Text('Switch'),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+
// --- Legal Section ---
Widget _buildLegalSection(BuildContext context) {
return Column(
diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj
index 18540de..7efb5c8 100644
--- a/macos/Runner.xcodeproj/project.pbxproj
+++ b/macos/Runner.xcodeproj/project.pbxproj
@@ -412,10 +412,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
+ inputPaths = (
+ );
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
+ outputPaths = (
+ );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";