mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a775e2f3f | ||
|
|
2bf084d120 |
21
README.md
21
README.md
@@ -1,9 +1,14 @@
|
|||||||
# Reboot Launcher
|

|
||||||
|
|
||||||

|
GUI and CLI Launcher for [Project Reboot](https://github.com/Milxnor/Project-Reboot-3.0/)
|
||||||

|
Join our discord at https://discord.gg/reboot
|
||||||

|
|
||||||

|
## Modules
|
||||||

|
|
||||||

|
- COMMON: Shared business logic for CLI and GUI modules
|
||||||

|
- CLI: Work in progress command line interface to host a Fortnite Server on a Windows VPS easily, developed in Dart
|
||||||
|
- GUI: Stable graphical user interface to play and host Fortnite S0-14
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Check the releases section
|
||||||
@@ -5,7 +5,6 @@ import 'package:reboot_cli/src/game.dart';
|
|||||||
import 'package:reboot_cli/src/reboot.dart';
|
import 'package:reboot_cli/src/reboot.dart';
|
||||||
import 'package:reboot_cli/src/server.dart';
|
import 'package:reboot_cli/src/server.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_common/src/util/matchmaker.dart' as matchmaker;
|
|
||||||
|
|
||||||
late String? username;
|
late String? username;
|
||||||
late bool host;
|
late bool host;
|
||||||
@@ -82,7 +81,7 @@ void main(List<String> args) async {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
matchmaker.writeMatchmakingIp(result["matchmaking-address"]);
|
writeMatchmakingIp(result["matchmaking-address"]);
|
||||||
autoRestart = result["auto-restart"];
|
autoRestart = result["auto-restart"];
|
||||||
await startGame();
|
await startGame();
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ Future<void> startGame() async {
|
|||||||
|
|
||||||
_gameProcess = await Process.start(executable.path, createRebootArgs(username!, "", host, host, ""))
|
_gameProcess = await Process.start(executable.path, createRebootArgs(username!, "", host, host, ""))
|
||||||
..exitCode.then((_) => _onClose())
|
..exitCode.then((_) => _onClose())
|
||||||
..outLines.forEach((line) => _onGameOutput(line, dll, host, verbose));
|
..stdOutput.forEach((line) => _onGameOutput(line, dll, host, verbose));
|
||||||
_injectOrShowError("cobalt.dll");
|
_injectOrShowError("cobalt.dll");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +52,17 @@ void _onGameOutput(String line, String dll, bool hosting, bool verbose) {
|
|||||||
stdout.writeln(line);
|
stdout.writeln(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleGameOutput(
|
||||||
|
line: line,
|
||||||
|
host: hosting,
|
||||||
|
onDisplayAttached: () {}, // TODO: Support virtual desktops
|
||||||
|
onLoggedIn: onLoggedIn,
|
||||||
|
onMatchEnd: onMatchEnd,
|
||||||
|
onShutdown: onShutdown,
|
||||||
|
onTokenError: onTokenError,
|
||||||
|
onBuildCorrupted: onBuildCorrupted
|
||||||
|
);
|
||||||
|
|
||||||
if (line.contains(kShutdownLine)) {
|
if (line.contains(kShutdownLine)) {
|
||||||
_onClose();
|
_onClose();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
extension ProcessExtension on Process {
|
extension ProcessExtension on Process {
|
||||||
Stream<String> get stdOutput => this.stdout.expand((event) => utf8.decode(event).split("\n"));
|
Stream<String> get stdOutput => this.stdout.expand((event) => utf8.decode(event, allowMalformed: true).split("\n"));
|
||||||
|
|
||||||
Stream<String> get stdError => this.stderr.expand((event) => utf8.decode(event).split("\n"));
|
Stream<String> get stdError => this.stderr.expand((event) => utf8.decode(event, allowMalformed: true).split("\n"));
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:reboot_common/common.dart';
|
||||||
|
|
||||||
|
|
||||||
class GameInstance {
|
class GameInstance {
|
||||||
final String versionName;
|
final String versionName;
|
||||||
final int gamePid;
|
final int gamePid;
|
||||||
final int? launcherPid;
|
final int? launcherPid;
|
||||||
final int? eacPid;
|
final int? eacPid;
|
||||||
|
final List<InjectableDll> injectedDlls;
|
||||||
bool hosting;
|
bool hosting;
|
||||||
bool launched;
|
bool launched;
|
||||||
bool movedToVirtualDesktop;
|
bool movedToVirtualDesktop;
|
||||||
@@ -19,7 +22,7 @@ class GameInstance {
|
|||||||
required this.eacPid,
|
required this.eacPid,
|
||||||
required this.hosting,
|
required this.hosting,
|
||||||
required this.child
|
required this.child
|
||||||
}): tokenError = false, launched = false, movedToVirtualDesktop = false;
|
}): tokenError = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
|
||||||
|
|
||||||
void kill() {
|
void kill() {
|
||||||
Process.killPid(gamePid, ProcessSignal.sigabrt);
|
Process.killPid(gamePid, ProcessSignal.sigabrt);
|
||||||
|
|||||||
@@ -145,9 +145,11 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
|||||||
'"${tempFile.path}"'
|
'"${tempFile.path}"'
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
var completed = false;
|
||||||
process.stdOutput.listen((data) {
|
process.stdOutput.listen((data) {
|
||||||
final now = DateTime.now().millisecondsSinceEpoch;
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
if(data.toLowerCase().contains("everything is ok")) {
|
if(data.toLowerCase().contains("everything is ok")) {
|
||||||
|
completed = true;
|
||||||
_onProgress(startTime, now, 100, true, options);
|
_onProgress(startTime, now, 100, true, options);
|
||||||
process?.kill(ProcessSignal.sigabrt);
|
process?.kill(ProcessSignal.sigabrt);
|
||||||
return;
|
return;
|
||||||
@@ -166,6 +168,11 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
|||||||
_onError(data, options);
|
_onError(data, options);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
process.exitCode.then((_) {
|
||||||
|
if(!completed) {
|
||||||
|
_onError("Corrupted zip archive", options);
|
||||||
|
}
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case ".rar":
|
case ".rar":
|
||||||
final winrar = File("${assetsDirectory.path}\\build\\winrar.exe");
|
final winrar = File("${assetsDirectory.path}\\build\\winrar.exe");
|
||||||
@@ -183,10 +190,12 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
|||||||
'"${options.destination.path}"'
|
'"${options.destination.path}"'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
var completed = false;
|
||||||
process.stdOutput.listen((data) {
|
process.stdOutput.listen((data) {
|
||||||
final now = DateTime.now().millisecondsSinceEpoch;
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
data = data.replaceAll("\r", "").replaceAll("\b", "").trim();
|
data = data.replaceAll("\r", "").replaceAll("\b", "").trim();
|
||||||
if(data == "All OK") {
|
if(data == "All OK") {
|
||||||
|
completed = true;
|
||||||
_onProgress(startTime, now, 100, true, options);
|
_onProgress(startTime, now, 100, true, options);
|
||||||
process?.kill(ProcessSignal.sigabrt);
|
process?.kill(ProcessSignal.sigabrt);
|
||||||
return;
|
return;
|
||||||
@@ -205,6 +214,11 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
|||||||
_onError(data, options);
|
_onError(data, options);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
process.exitCode.then((_) {
|
||||||
|
if(!completed) {
|
||||||
|
_onError("Corrupted rar archive", options);
|
||||||
|
}
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError("Unexpected file extension: $extension}");
|
throw ArgumentError("Unexpected file extension: $extension}");
|
||||||
|
|||||||
@@ -238,6 +238,31 @@ List<String> createRebootArgs(String username, String password, bool host, bool
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleGameOutput({
|
||||||
|
required String line,
|
||||||
|
required bool host,
|
||||||
|
required void Function() onDisplayAttached,
|
||||||
|
required void Function() onLoggedIn,
|
||||||
|
required void Function() onMatchEnd,
|
||||||
|
required void Function() onShutdown,
|
||||||
|
required void Function() onTokenError,
|
||||||
|
required void Function() onBuildCorrupted,
|
||||||
|
}) {
|
||||||
|
if (line.contains(kShutdownLine)) {
|
||||||
|
onShutdown();
|
||||||
|
}else if(kCorruptedBuildErrors.any((element) => line.contains(element))){
|
||||||
|
onBuildCorrupted();
|
||||||
|
}else if(kCannotConnectErrors.any((element) => line.contains(element))){
|
||||||
|
onTokenError();
|
||||||
|
}else if(kLoggedInLines.every((entry) => line.contains(entry))) {
|
||||||
|
onLoggedIn();
|
||||||
|
}else if(line.contains(kGameFinishedLine) && host) {
|
||||||
|
onMatchEnd();
|
||||||
|
}else if(line.contains(kDisplayInitializedLine) && host) {
|
||||||
|
onDisplayAttached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _parseUsername(String username, bool host) {
|
String _parseUsername(String username, bool host) {
|
||||||
if(host) {
|
if(host) {
|
||||||
return "Player${Random().nextInt(1000)}";
|
return "Player${Random().nextInt(1000)}";
|
||||||
|
|||||||
@@ -260,7 +260,7 @@
|
|||||||
"missingExecutableError": "Missing Fortnite executable: usually this means that the installation was moved or deleted",
|
"missingExecutableError": "Missing Fortnite executable: usually this means that the installation was moved or deleted",
|
||||||
"corruptedVersionError": "Corrupted Fortnite installation: please download it again from the launcher or change version",
|
"corruptedVersionError": "Corrupted Fortnite installation: please download it again from the launcher or change version",
|
||||||
"corruptedDllError": "Cannot inject dll: {error}",
|
"corruptedDllError": "Cannot inject dll: {error}",
|
||||||
"tokenError": "Cannot log in into Fortnite: authentication error",
|
"tokenError": "Cannot log in into Fortnite: authentication error (injected dlls: {dlls})",
|
||||||
"unknownFortniteError": "An unknown error occurred while launching Fortnite: {error}",
|
"unknownFortniteError": "An unknown error occurred while launching Fortnite: {error}",
|
||||||
"serverNoLongerAvailable": "{owner}'s server is no longer available",
|
"serverNoLongerAvailable": "{owner}'s server is no longer available",
|
||||||
"serverNoLongerAvailableUnnamed": "The previous server is no longer available",
|
"serverNoLongerAvailableUnnamed": "The previous server is no longer available",
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
|||||||
import 'package:reboot_launcher/src/controller/build_controller.dart';
|
import 'package:reboot_launcher/src/controller/build_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/info_controller.dart';
|
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/update_controller.dart';
|
import 'package:reboot_launcher/src/controller/update_controller.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
|
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
|
||||||
@@ -25,6 +24,7 @@ import 'package:reboot_launcher/src/dialog/implementation/error.dart';
|
|||||||
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
|
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/home_page.dart';
|
import 'package:reboot_launcher/src/page/implementation/home_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/info_page.dart';
|
import 'package:reboot_launcher/src/page/implementation/info_page.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/log.dart';
|
||||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
@@ -33,24 +33,30 @@ import 'package:system_theme/system_theme.dart';
|
|||||||
import 'package:url_protocol/url_protocol.dart';
|
import 'package:url_protocol/url_protocol.dart';
|
||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
import 'package:win32/win32.dart';
|
||||||
|
|
||||||
const double kDefaultWindowWidth = 1536;
|
const double kDefaultWindowWidth = 1164;
|
||||||
const double kDefaultWindowHeight = 1224;
|
const double kDefaultWindowHeight = 864;
|
||||||
const String kCustomUrlSchema = "Reboot";
|
const String kCustomUrlSchema = "Reboot";
|
||||||
|
|
||||||
Version? appVersion;
|
Version? appVersion;
|
||||||
|
bool appWithNoStorage = false;
|
||||||
|
|
||||||
void main() => runZonedGuarded(
|
void main() {
|
||||||
|
log("[APP] Called");
|
||||||
|
runZonedGuarded(
|
||||||
() => _startApp(),
|
() => _startApp(),
|
||||||
(error, stack) => onError(error, stack, false),
|
(error, stack) => onError(error, stack, false),
|
||||||
zoneSpecification: ZoneSpecification(
|
zoneSpecification: ZoneSpecification(
|
||||||
handleUncaughtError: (self, parent, zone, error, stacktrace) => onError(error, stacktrace, false)
|
handleUncaughtError: (self, parent, zone, error, stacktrace) => onError(error, stacktrace, false)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _startApp() async {
|
Future<void> _startApp() async {
|
||||||
final errors = <Object>[];
|
final errors = <Object>[];
|
||||||
try {
|
try {
|
||||||
|
log("[APP] Starting application");
|
||||||
final pathError = await _initPath();
|
final pathError = await _initPath();
|
||||||
if(pathError != null) {
|
if(pathError != null) {
|
||||||
errors.add(pathError);
|
errors.add(pathError);
|
||||||
@@ -66,10 +72,6 @@ Future<void> _startApp() async {
|
|||||||
errors.add(notificationsError);
|
errors.add(notificationsError);
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
_initWindow();
|
|
||||||
|
|
||||||
final tilesError = InfoPage.initInfoTiles();
|
final tilesError = InfoPage.initInfoTiles();
|
||||||
if(tilesError != null) {
|
if(tilesError != null) {
|
||||||
errors.add(tilesError);
|
errors.add(tilesError);
|
||||||
@@ -80,22 +82,24 @@ Future<void> _startApp() async {
|
|||||||
errors.add(versionError);
|
errors.add(versionError);
|
||||||
}
|
}
|
||||||
|
|
||||||
final storageError = await _initStorage();
|
final storageErrors = await _initStorage();
|
||||||
if(storageError != null) {
|
errors.addAll(storageErrors);
|
||||||
errors.add(storageError);
|
|
||||||
}
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
_initWindow();
|
||||||
|
|
||||||
final urlError = await _initUrlHandler();
|
final urlError = await _initUrlHandler();
|
||||||
if(urlError != null) {
|
if(urlError != null) {
|
||||||
errors.add(urlError);
|
errors.add(urlError);
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkGameServer();
|
|
||||||
}catch(uncaughtError) {
|
}catch(uncaughtError) {
|
||||||
errors.add(uncaughtError);
|
errors.add(uncaughtError);
|
||||||
} finally{
|
} finally{
|
||||||
runApp(const RebootApplication());
|
log("[APP] Started applications with errors: $errors");
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => _handleErrors(errors));
|
runApp(RebootApplication(
|
||||||
|
errors: errors,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,10 +136,6 @@ Future<Object?> _initPath() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleErrors(List<Object?> errors) {
|
|
||||||
errors.where((element) => element != null).forEach((element) => onError(element!, null, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Object?> _initVersion() async {
|
Future<Object?> _initVersion() async {
|
||||||
try {
|
try {
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
@@ -146,67 +146,17 @@ Future<Object?> _initVersion() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _checkGameServer() async {
|
|
||||||
try {
|
|
||||||
var backendController = Get.find<BackendController>();
|
|
||||||
var address = backendController.gameServerAddress.text;
|
|
||||||
if(isLocalHost(address)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await pingGameServer(address);
|
|
||||||
if(result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldOwner = backendController.gameServerOwner.value;
|
|
||||||
backendController.joinLocalHost();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => showInfoBar(
|
|
||||||
oldOwner == null ? translations.serverNoLongerAvailableUnnamed : translations.serverNoLongerAvailable(oldOwner),
|
|
||||||
severity: InfoBarSeverity.warning,
|
|
||||||
duration: infoBarLongDuration
|
|
||||||
));
|
|
||||||
}catch(_) {
|
|
||||||
// Intended behaviour
|
|
||||||
// Just ignore the error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Object?> _initUrlHandler() async {
|
Future<Object?> _initUrlHandler() async {
|
||||||
try {
|
try {
|
||||||
registerProtocolHandler(kCustomUrlSchema, arguments: ['%s']);
|
registerProtocolHandler(kCustomUrlSchema, arguments: ['%s']);
|
||||||
var appLinks = AppLinks();
|
|
||||||
var initialUrl = await appLinks.getInitialLink();
|
|
||||||
if(initialUrl != null) {
|
|
||||||
_joinServer(initialUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
appLinks.uriLinkStream.listen(_joinServer);
|
|
||||||
return null;
|
return null;
|
||||||
}catch(error) {
|
}catch(error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _joinServer(Uri uri) {
|
|
||||||
var hostingController = Get.find<HostingController>();
|
|
||||||
var backendController = Get.find<BackendController>();
|
|
||||||
var uuid = _parseCustomUrl(uri);
|
|
||||||
var server = hostingController.findServerById(uuid);
|
|
||||||
if(server != null) {
|
|
||||||
backendController.joinServer(hostingController.uuid, server);
|
|
||||||
}else {
|
|
||||||
showInfoBar(
|
|
||||||
translations.noServerFound,
|
|
||||||
duration: infoBarLongDuration,
|
|
||||||
severity: InfoBarSeverity.error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _parseCustomUrl(Uri uri) => uri.host;
|
|
||||||
|
|
||||||
void _initWindow() => doWhenWindowReady(() async {
|
void _initWindow() => doWhenWindowReady(() async {
|
||||||
|
try {
|
||||||
await SystemTheme.accentColor.load();
|
await SystemTheme.accentColor.load();
|
||||||
await windowManager.ensureInitialized();
|
await windowManager.ensureInitialized();
|
||||||
await Window.initialize();
|
await Window.initialize();
|
||||||
@@ -231,32 +181,69 @@ void _initWindow() => doWhenWindowReady(() async {
|
|||||||
dark: SchedulerBinding.instance.platformDispatcher.platformBrightness.isDark
|
dark: SchedulerBinding.instance.platformDispatcher.platformBrightness.isDark
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}catch(error, stackTrace) {
|
||||||
|
onError(error, stackTrace, false);
|
||||||
|
}finally {
|
||||||
appWindow.show();
|
appWindow.show();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Object?> _initStorage() async {
|
Future<List<Object>> _initStorage() async {
|
||||||
|
final errors = <Object>[];
|
||||||
try {
|
try {
|
||||||
await GetStorage("game", settingsDirectory.path).initStorage;
|
await GetStorage("game", settingsDirectory.path).initStorage;
|
||||||
await GetStorage("backend", settingsDirectory.path).initStorage;
|
await GetStorage("backend", settingsDirectory.path).initStorage;
|
||||||
await GetStorage("update", settingsDirectory.path).initStorage;
|
await GetStorage("update", settingsDirectory.path).initStorage;
|
||||||
await GetStorage("settings", settingsDirectory.path).initStorage;
|
await GetStorage("settings", settingsDirectory.path).initStorage;
|
||||||
await GetStorage("hosting", settingsDirectory.path).initStorage;
|
await GetStorage("hosting", settingsDirectory.path).initStorage;
|
||||||
Get.put(GameController());
|
|
||||||
Get.put(BackendController());
|
|
||||||
Get.put(BuildController());
|
|
||||||
Get.put(SettingsController());
|
|
||||||
Get.put(HostingController());
|
|
||||||
Get.put(InfoController());
|
|
||||||
Get.put(UpdateController());
|
|
||||||
return null;
|
|
||||||
}catch(error) {
|
}catch(error) {
|
||||||
return error;
|
appWithNoStorage = true;
|
||||||
|
errors.add("The Reboot Launcher configuration in ${settingsDirectory.path} cannot be accessed: running with in memory storage");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Get.put(GameController());
|
||||||
|
}catch(error) {
|
||||||
|
errors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Get.put(BackendController());
|
||||||
|
}catch(error) {
|
||||||
|
errors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Get.put(BuildController());
|
||||||
|
}catch(error) {
|
||||||
|
errors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Get.put(HostingController());
|
||||||
|
}catch(error) {
|
||||||
|
errors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Get.put(UpdateController());
|
||||||
|
}catch(error) {
|
||||||
|
errors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Get.put(SettingsController());
|
||||||
|
}catch(error) {
|
||||||
|
errors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RebootApplication extends StatefulWidget {
|
class RebootApplication extends StatefulWidget {
|
||||||
const RebootApplication({Key? key}) : super(key: key);
|
final List<Object> errors;
|
||||||
|
const RebootApplication({Key? key, required this.errors}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RebootApplication> createState() => _RebootApplicationState();
|
State<RebootApplication> createState() => _RebootApplicationState();
|
||||||
@@ -265,6 +252,16 @@ class RebootApplication extends StatefulWidget {
|
|||||||
class _RebootApplicationState extends State<RebootApplication> {
|
class _RebootApplicationState extends State<RebootApplication> {
|
||||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => _handleErrors(widget.errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleErrors(List<Object?> errors) {
|
||||||
|
errors.where((element) => element != null).forEach((element) => onError(element!, null, false));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Obx(() => FluentApp(
|
Widget build(BuildContext context) => Obx(() => FluentApp(
|
||||||
locale: Locale(_settingsController.language.value),
|
locale: Locale(_settingsController.language.value),
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
|
import 'package:reboot_launcher/main.dart';
|
||||||
|
|
||||||
class BackendController extends GetxController {
|
class BackendController extends GetxController {
|
||||||
late final GetStorage storage;
|
late final GetStorage? storage;
|
||||||
late final TextEditingController host;
|
late final TextEditingController host;
|
||||||
late final TextEditingController port;
|
late final TextEditingController port;
|
||||||
late final Rx<ServerType> type;
|
late final Rx<ServerType> type;
|
||||||
@@ -21,13 +22,13 @@ class BackendController extends GetxController {
|
|||||||
HttpServer? remoteServer;
|
HttpServer? remoteServer;
|
||||||
|
|
||||||
BackendController() {
|
BackendController() {
|
||||||
storage = GetStorage("backend");
|
storage = appWithNoStorage ? null : GetStorage("backend");
|
||||||
started = RxBool(false);
|
started = RxBool(false);
|
||||||
type = Rx(ServerType.values.elementAt(storage.read("type") ?? 0));
|
type = Rx(ServerType.values.elementAt(storage?.read("type") ?? 0));
|
||||||
type.listen((value) {
|
type.listen((value) {
|
||||||
host.text = _readHost();
|
host.text = _readHost();
|
||||||
port.text = _readPort();
|
port.text = _readPort();
|
||||||
storage.write("type", value.index);
|
storage?.write("type", value.index);
|
||||||
if (!started.value) {
|
if (!started.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -36,13 +37,13 @@ class BackendController extends GetxController {
|
|||||||
});
|
});
|
||||||
host = TextEditingController(text: _readHost());
|
host = TextEditingController(text: _readHost());
|
||||||
host.addListener(() =>
|
host.addListener(() =>
|
||||||
storage.write("${type.value.name}_host", host.text));
|
storage?.write("${type.value.name}_host", host.text));
|
||||||
port = TextEditingController(text: _readPort());
|
port = TextEditingController(text: _readPort());
|
||||||
port.addListener(() =>
|
port.addListener(() =>
|
||||||
storage.write("${type.value.name}_port", port.text));
|
storage?.write("${type.value.name}_port", port.text));
|
||||||
detached = RxBool(storage.read("detached") ?? false);
|
detached = RxBool(storage?.read("detached") ?? false);
|
||||||
detached.listen((value) => storage.write("detached", value));
|
detached.listen((value) => storage?.write("detached", value));
|
||||||
gameServerAddress = TextEditingController(text: storage.read("game_server_address") ?? "127.0.0.1");
|
gameServerAddress = TextEditingController(text: storage?.read("game_server_address") ?? "127.0.0.1");
|
||||||
var lastValue = gameServerAddress.text;
|
var lastValue = gameServerAddress.text;
|
||||||
writeMatchmakingIp(lastValue);
|
writeMatchmakingIp(lastValue);
|
||||||
gameServerAddress.addListener(() {
|
gameServerAddress.addListener(() {
|
||||||
@@ -53,7 +54,7 @@ class BackendController extends GetxController {
|
|||||||
|
|
||||||
lastValue = newValue;
|
lastValue = newValue;
|
||||||
gameServerAddress.selection = TextSelection.collapsed(offset: newValue.length);
|
gameServerAddress.selection = TextSelection.collapsed(offset: newValue.length);
|
||||||
storage.write("game_server_address", newValue);
|
storage?.write("game_server_address", newValue);
|
||||||
writeMatchmakingIp(newValue);
|
writeMatchmakingIp(newValue);
|
||||||
});
|
});
|
||||||
watchMatchmakingIp().listen((event) {
|
watchMatchmakingIp().listen((event) {
|
||||||
@@ -62,15 +63,15 @@ class BackendController extends GetxController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
gameServerAddressFocusNode = FocusNode();
|
gameServerAddressFocusNode = FocusNode();
|
||||||
gameServerOwner = RxnString(storage.read("game_server_owner"));
|
gameServerOwner = RxnString(storage?.read("game_server_owner"));
|
||||||
gameServerOwner.listen((value) => storage.write("game_server_owner", value));
|
gameServerOwner.listen((value) => storage?.write("game_server_owner", value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() async {
|
void reset() async {
|
||||||
type.value = ServerType.values.elementAt(0);
|
type.value = ServerType.values.elementAt(0);
|
||||||
for (final type in ServerType.values) {
|
for (final type in ServerType.values) {
|
||||||
storage.write("${type.name}_host", null);
|
storage?.write("${type.name}_host", null);
|
||||||
storage.write("${type.name}_port", null);
|
storage?.write("${type.name}_port", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
host.text = type.value != ServerType.remote ? kDefaultBackendHost : "";
|
host.text = type.value != ServerType.remote ? kDefaultBackendHost : "";
|
||||||
@@ -79,7 +80,7 @@ class BackendController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _readHost() {
|
String _readHost() {
|
||||||
String? value = storage.read("${type.value.name}_host");
|
String? value = storage?.read("${type.value.name}_host");
|
||||||
if (value != null && value.isNotEmpty) {
|
if (value != null && value.isNotEmpty) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -92,7 +93,7 @@ class BackendController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _readPort() =>
|
String _readPort() =>
|
||||||
storage.read("${type.value.name}_port") ?? kDefaultBackendPort.toString();
|
storage?.read("${type.value.name}_port") ?? kDefaultBackendPort.toString();
|
||||||
|
|
||||||
Stream<ServerResult> start() async* {
|
Stream<ServerResult> start() async* {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import 'package:get_storage/get_storage.dart';
|
|||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/util/keyboard.dart';
|
import 'package:reboot_launcher/src/util/keyboard.dart';
|
||||||
|
|
||||||
|
import '../../main.dart';
|
||||||
|
|
||||||
class GameController extends GetxController {
|
class GameController extends GetxController {
|
||||||
static const PhysicalKeyboardKey _kDefaultConsoleKey = PhysicalKeyboardKey(0x00070041);
|
static const PhysicalKeyboardKey _kDefaultConsoleKey = PhysicalKeyboardKey(0x00070041);
|
||||||
|
|
||||||
late final GetStorage _storage;
|
late final GetStorage? _storage;
|
||||||
late final TextEditingController username;
|
late final TextEditingController username;
|
||||||
late final TextEditingController password;
|
late final TextEditingController password;
|
||||||
late final TextEditingController customLaunchArgs;
|
late final TextEditingController customLaunchArgs;
|
||||||
@@ -23,38 +25,37 @@ class GameController extends GetxController {
|
|||||||
late final Rx<PhysicalKeyboardKey> consoleKey;
|
late final Rx<PhysicalKeyboardKey> consoleKey;
|
||||||
|
|
||||||
GameController() {
|
GameController() {
|
||||||
_storage = GetStorage("game");
|
_storage = appWithNoStorage ? null : GetStorage("game");
|
||||||
Iterable decodedVersionsJson = jsonDecode(
|
Iterable decodedVersionsJson = jsonDecode(_storage?.read("versions") ?? "[]");
|
||||||
_storage.read("versions") ?? "[]");
|
final decodedVersions = decodedVersionsJson
|
||||||
var decodedVersions = decodedVersionsJson
|
|
||||||
.map((entry) => FortniteVersion.fromJson(entry))
|
.map((entry) => FortniteVersion.fromJson(entry))
|
||||||
.toList();
|
.toList();
|
||||||
versions = Rx(decodedVersions);
|
versions = Rx(decodedVersions);
|
||||||
versions.listen((data) => _saveVersions());
|
versions.listen((data) => _saveVersions());
|
||||||
var decodedSelectedVersionName = _storage.read("version");
|
final decodedSelectedVersionName = _storage?.read("version");
|
||||||
var decodedSelectedVersion = decodedVersions.firstWhereOrNull((
|
final decodedSelectedVersion = decodedVersions.firstWhereOrNull((
|
||||||
element) => element.name == decodedSelectedVersionName);
|
element) => element.name == decodedSelectedVersionName);
|
||||||
_selectedVersion = Rxn(decodedSelectedVersion);
|
_selectedVersion = Rxn(decodedSelectedVersion);
|
||||||
username = TextEditingController(
|
username = TextEditingController(
|
||||||
text: _storage.read("username") ?? kDefaultPlayerName);
|
text: _storage?.read("username") ?? kDefaultPlayerName);
|
||||||
username.addListener(() => _storage.write("username", username.text));
|
username.addListener(() => _storage?.write("username", username.text));
|
||||||
password = TextEditingController(text: _storage.read("password") ?? "");
|
password = TextEditingController(text: _storage?.read("password") ?? "");
|
||||||
password.addListener(() => _storage.write("password", password.text));
|
password.addListener(() => _storage?.write("password", password.text));
|
||||||
customLaunchArgs = TextEditingController(text: _storage.read("custom_launch_args") ?? "");
|
customLaunchArgs = TextEditingController(text: _storage?.read("custom_launch_args") ?? "");
|
||||||
customLaunchArgs.addListener(() =>
|
customLaunchArgs.addListener(() =>
|
||||||
_storage.write("custom_launch_args", customLaunchArgs.text));
|
_storage?.write("custom_launch_args", customLaunchArgs.text));
|
||||||
started = RxBool(false);
|
started = RxBool(false);
|
||||||
instance = Rxn();
|
instance = Rxn();
|
||||||
consoleKey = Rx(_readConsoleKey());
|
consoleKey = Rx(_readConsoleKey());
|
||||||
_writeConsoleKey(consoleKey.value);
|
_writeConsoleKey(consoleKey.value);
|
||||||
consoleKey.listen((newValue) {
|
consoleKey.listen((newValue) {
|
||||||
_storage.write("console_key", newValue.usbHidUsage);
|
_storage?.write("console_key", newValue.usbHidUsage);
|
||||||
_writeConsoleKey(newValue);
|
_writeConsoleKey(newValue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalKeyboardKey _readConsoleKey() {
|
PhysicalKeyboardKey _readConsoleKey() {
|
||||||
final consoleKeyValue = _storage.read("console_key");
|
final consoleKeyValue = _storage?.read("console_key");
|
||||||
if(consoleKeyValue == null) {
|
if(consoleKeyValue == null) {
|
||||||
return _kDefaultConsoleKey;
|
return _kDefaultConsoleKey;
|
||||||
}
|
}
|
||||||
@@ -113,7 +114,7 @@ class GameController extends GetxController {
|
|||||||
|
|
||||||
Future<void> _saveVersions() async {
|
Future<void> _saveVersions() async {
|
||||||
var serialized = jsonEncode(versions.value.map((entry) => entry.toJson()).toList());
|
var serialized = jsonEncode(versions.value.map((entry) => entry.toJson()).toList());
|
||||||
await _storage.write("versions", serialized);
|
await _storage?.write("versions", serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasVersions => versions.value.isNotEmpty;
|
bool get hasVersions => versions.value.isNotEmpty;
|
||||||
@@ -124,7 +125,7 @@ class GameController extends GetxController {
|
|||||||
|
|
||||||
set selectedVersion(FortniteVersion? version) {
|
set selectedVersion(FortniteVersion? version) {
|
||||||
_selectedVersion.value = version;
|
_selectedVersion.value = version;
|
||||||
_storage.write("version", version?.name);
|
_storage?.write("version", version?.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateVersion(FortniteVersion version, Function(FortniteVersion) function) {
|
void updateVersion(FortniteVersion version, Function(FortniteVersion) function) {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
|
import 'package:reboot_launcher/main.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class HostingController extends GetxController {
|
class HostingController extends GetxController {
|
||||||
late final GetStorage _storage;
|
late final GetStorage? _storage;
|
||||||
late final String uuid;
|
late final String uuid;
|
||||||
late final TextEditingController name;
|
late final TextEditingController name;
|
||||||
late final TextEditingController description;
|
late final TextEditingController description;
|
||||||
@@ -22,23 +23,23 @@ class HostingController extends GetxController {
|
|||||||
late final Rxn<Set<Map<String, dynamic>>> servers;
|
late final Rxn<Set<Map<String, dynamic>>> servers;
|
||||||
|
|
||||||
HostingController() {
|
HostingController() {
|
||||||
_storage = GetStorage("hosting");
|
_storage = appWithNoStorage ? null : GetStorage("hosting");
|
||||||
uuid = _storage.read("uuid") ?? const Uuid().v4();
|
uuid = _storage?.read("uuid") ?? const Uuid().v4();
|
||||||
_storage.write("uuid", uuid);
|
_storage?.write("uuid", uuid);
|
||||||
name = TextEditingController(text: _storage.read("name"));
|
name = TextEditingController(text: _storage?.read("name"));
|
||||||
name.addListener(() => _storage.write("name", name.text));
|
name.addListener(() => _storage?.write("name", name.text));
|
||||||
description = TextEditingController(text: _storage.read("description"));
|
description = TextEditingController(text: _storage?.read("description"));
|
||||||
description.addListener(() => _storage.write("description", description.text));
|
description.addListener(() => _storage?.write("description", description.text));
|
||||||
password = TextEditingController(text: _storage.read("password") ?? "");
|
password = TextEditingController(text: _storage?.read("password") ?? "");
|
||||||
password.addListener(() => _storage.write("password", password.text));
|
password.addListener(() => _storage?.write("password", password.text));
|
||||||
discoverable = RxBool(_storage.read("discoverable") ?? false);
|
discoverable = RxBool(_storage?.read("discoverable") ?? false);
|
||||||
discoverable.listen((value) => _storage.write("discoverable", value));
|
discoverable.listen((value) => _storage?.write("discoverable", value));
|
||||||
headless = RxBool(_storage.read("headless") ?? true);
|
headless = RxBool(_storage?.read("headless") ?? true);
|
||||||
headless.listen((value) => _storage.write("headless", value));
|
headless.listen((value) => _storage?.write("headless", value));
|
||||||
virtualDesktop = RxBool(_storage.read("virtual_desktop") ?? true);
|
virtualDesktop = RxBool(_storage?.read("virtual_desktop") ?? true);
|
||||||
virtualDesktop.listen((value) => _storage.write("virtual_desktop", value));
|
virtualDesktop.listen((value) => _storage?.write("virtual_desktop", value));
|
||||||
autoRestart = RxBool(_storage.read("auto_restart") ?? true);
|
autoRestart = RxBool(_storage?.read("auto_restart") ?? true);
|
||||||
autoRestart.listen((value) => _storage.write("auto_restart", value));
|
autoRestart.listen((value) => _storage?.write("auto_restart", value));
|
||||||
started = RxBool(false);
|
started = RxBool(false);
|
||||||
published = RxBool(false);
|
published = RxBool(false);
|
||||||
showPassword = RxBool(false);
|
showPassword = RxBool(false);
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class InfoController extends GetxController {
|
|
||||||
List<String>? links;
|
|
||||||
Map<String, String> linksData;
|
|
||||||
|
|
||||||
InfoController() : linksData = {};
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ import 'package:version/version.dart';
|
|||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
class UpdateController {
|
class UpdateController {
|
||||||
late final GetStorage _storage;
|
late final GetStorage? _storage;
|
||||||
late final RxnInt timestamp;
|
late final RxnInt timestamp;
|
||||||
late final Rx<UpdateStatus> status;
|
late final Rx<UpdateStatus> status;
|
||||||
late final Rx<UpdateTimer> timer;
|
late final Rx<UpdateTimer> timer;
|
||||||
@@ -21,17 +21,17 @@ class UpdateController {
|
|||||||
Future? _updater;
|
Future? _updater;
|
||||||
|
|
||||||
UpdateController() {
|
UpdateController() {
|
||||||
_storage = GetStorage("update");
|
_storage = appWithNoStorage ? null : GetStorage("update");
|
||||||
timestamp = RxnInt(_storage.read("ts"));
|
timestamp = RxnInt(_storage?.read("ts"));
|
||||||
timestamp.listen((value) => _storage.write("ts", value));
|
timestamp.listen((value) => _storage?.write("ts", value));
|
||||||
var timerIndex = _storage.read("timer");
|
var timerIndex = _storage?.read("timer");
|
||||||
timer = Rx(timerIndex == null ? UpdateTimer.hour : UpdateTimer.values.elementAt(timerIndex));
|
timer = Rx(timerIndex == null ? UpdateTimer.hour : UpdateTimer.values.elementAt(timerIndex));
|
||||||
timer.listen((value) => _storage.write("timer", value.index));
|
timer.listen((value) => _storage?.write("timer", value.index));
|
||||||
url = TextEditingController(text: _storage.read("update_url") ?? kRebootDownloadUrl);
|
url = TextEditingController(text: _storage?.read("update_url") ?? kRebootDownloadUrl);
|
||||||
url.addListener(() => _storage.write("update_url", url.text));
|
url.addListener(() => _storage?.write("update_url", url.text));
|
||||||
status = Rx(UpdateStatus.waiting);
|
status = Rx(UpdateStatus.waiting);
|
||||||
customGameServer = RxBool(_storage.read("custom_game_server") ?? false);
|
customGameServer = RxBool(_storage?.read("custom_game_server") ?? false);
|
||||||
customGameServer.listen((value) => _storage.write("custom_game_server", value));
|
customGameServer.listen((value) => _storage?.write("custom_game_server", value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> notifyLauncherUpdate() async {
|
Future<void> notifyLauncherUpdate() async {
|
||||||
@@ -65,17 +65,17 @@ class UpdateController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateReboot([bool force = false]) async {
|
Future<void> updateReboot({bool force = false, bool silent = false}) async {
|
||||||
if(_updater != null) {
|
if(_updater != null) {
|
||||||
return await _updater;
|
return await _updater;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = _updateReboot(force);
|
final result = _updateReboot(force, silent);
|
||||||
_updater = result;
|
_updater = result;
|
||||||
return await result;
|
return await result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateReboot([bool force = false]) async {
|
Future<void> _updateReboot(bool force, bool silent) async {
|
||||||
try {
|
try {
|
||||||
if(customGameServer.value) {
|
if(customGameServer.value) {
|
||||||
status.value = UpdateStatus.success;
|
status.value = UpdateStatus.success;
|
||||||
@@ -92,23 +92,29 @@ class UpdateController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!silent) {
|
||||||
infoBarEntry = showInfoBar(
|
infoBarEntry = showInfoBar(
|
||||||
translations.downloadingDll("reboot"),
|
translations.downloadingDll("reboot"),
|
||||||
loading: true,
|
loading: true,
|
||||||
duration: null
|
duration: null
|
||||||
);
|
);
|
||||||
|
}
|
||||||
timestamp.value = await downloadRebootDll(url.text);
|
timestamp.value = await downloadRebootDll(url.text);
|
||||||
status.value = UpdateStatus.success;
|
status.value = UpdateStatus.success;
|
||||||
infoBarEntry?.close();
|
infoBarEntry?.close();
|
||||||
|
if(!silent) {
|
||||||
infoBarEntry = showInfoBar(
|
infoBarEntry = showInfoBar(
|
||||||
translations.downloadDllSuccess("reboot"),
|
translations.downloadDllSuccess("reboot"),
|
||||||
severity: InfoBarSeverity.success,
|
severity: InfoBarSeverity.success,
|
||||||
duration: infoBarShortDuration
|
duration: infoBarShortDuration
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}catch(message) {
|
}catch(message) {
|
||||||
|
if(!silent) {
|
||||||
infoBarEntry?.close();
|
infoBarEntry?.close();
|
||||||
var error = message.toString();
|
var error = message.toString();
|
||||||
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
|
error =
|
||||||
|
error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
|
||||||
error = error.toLowerCase();
|
error = error.toLowerCase();
|
||||||
status.value = UpdateStatus.error;
|
status.value = UpdateStatus.error;
|
||||||
showInfoBar(
|
showInfoBar(
|
||||||
@@ -116,10 +122,14 @@ class UpdateController {
|
|||||||
duration: infoBarLongDuration,
|
duration: infoBarLongDuration,
|
||||||
severity: InfoBarSeverity.error,
|
severity: InfoBarSeverity.error,
|
||||||
action: Button(
|
action: Button(
|
||||||
onPressed: () => updateReboot(true),
|
onPressed: () => updateReboot(
|
||||||
|
force: true,
|
||||||
|
silent: silent
|
||||||
|
),
|
||||||
child: Text(translations.downloadDllRetry),
|
child: Text(translations.downloadDllRetry),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}finally {
|
}finally {
|
||||||
_updater = null;
|
_updater = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
|||||||
import 'package:reboot_launcher/src/page/pages.dart';
|
import 'package:reboot_launcher/src/page/pages.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
|
|
||||||
|
import '../../util/log.dart';
|
||||||
|
|
||||||
|
|
||||||
String? lastError;
|
String? lastError;
|
||||||
|
|
||||||
void onError(Object exception, StackTrace? stackTrace, bool framework) {
|
void onError(Object exception, StackTrace? stackTrace, bool framework) {
|
||||||
if(!kDebugMode) {
|
log("[ERROR] $exception");
|
||||||
return;
|
log("[STACKTRACE] $stackTrace");
|
||||||
}
|
|
||||||
|
|
||||||
if(pageKey.currentContext == null || pageKey.currentState?.mounted == false){
|
if(pageKey.currentContext == null || pageKey.currentState?.mounted == false){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,26 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' show MaterialPage;
|
import 'package:flutter/material.dart' show MaterialPage;
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
|
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/update_controller.dart';
|
import 'package:reboot_launcher/src/controller/update_controller.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
||||||
|
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/implementation/dll.dart';
|
import 'package:reboot_launcher/src/dialog/implementation/dll.dart';
|
||||||
|
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_suggestion.dart';
|
import 'package:reboot_launcher/src/page/abstract/page_suggestion.dart';
|
||||||
import 'package:reboot_launcher/src/page/pages.dart';
|
import 'package:reboot_launcher/src/page/pages.dart';
|
||||||
import 'package:reboot_launcher/src/util/dll.dart';
|
import 'package:reboot_launcher/src/util/dll.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:reboot_launcher/src/widget/info_bar_area.dart';
|
import 'package:reboot_launcher/src/widget/info_bar_area.dart';
|
||||||
@@ -33,6 +39,8 @@ class HomePage extends StatefulWidget {
|
|||||||
class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepAliveClientMixin {
|
class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepAliveClientMixin {
|
||||||
static const double _kDefaultPadding = 12.0;
|
static const double _kDefaultPadding = 12.0;
|
||||||
|
|
||||||
|
final BackendController _backendController = Get.find<BackendController>();
|
||||||
|
final HostingController _hostingController = Get.find<HostingController>();
|
||||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||||
final UpdateController _updateController = Get.find<UpdateController>();
|
final UpdateController _updateController = Get.find<UpdateController>();
|
||||||
final GlobalKey _searchKey = GlobalKey();
|
final GlobalKey _searchKey = GlobalKey();
|
||||||
@@ -45,9 +53,62 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
windowManager.addListener(this);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _checkUpdates());
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
windowManager.addListener(this);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_checkUpdates();
|
||||||
|
_initAppLink();
|
||||||
|
_checkGameServer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initAppLink() async {
|
||||||
|
final appLinks = AppLinks();
|
||||||
|
final initialUrl = await appLinks.getInitialLink();
|
||||||
|
if(initialUrl != null) {
|
||||||
|
_joinServer(initialUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
appLinks.uriLinkStream.listen(_joinServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _joinServer(Uri uri) {
|
||||||
|
final uuid = uri.host;
|
||||||
|
final server = _hostingController.findServerById(uuid);
|
||||||
|
if(server != null) {
|
||||||
|
_backendController.joinServer(_hostingController.uuid, server);
|
||||||
|
}else {
|
||||||
|
showInfoBar(
|
||||||
|
translations.noServerFound,
|
||||||
|
duration: infoBarLongDuration,
|
||||||
|
severity: InfoBarSeverity.error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkGameServer() async {
|
||||||
|
try {
|
||||||
|
final address = _backendController.gameServerAddress.text;
|
||||||
|
if(isLocalHost(address)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await pingGameServer(address);
|
||||||
|
if(result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldOwner = _backendController.gameServerOwner.value;
|
||||||
|
_backendController.joinLocalHost();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => showInfoBar(
|
||||||
|
oldOwner == null ? translations.serverNoLongerAvailableUnnamed : translations.serverNoLongerAvailable(oldOwner),
|
||||||
|
severity: InfoBarSeverity.warning,
|
||||||
|
duration: infoBarLongDuration
|
||||||
|
));
|
||||||
|
}catch(_) {
|
||||||
|
// Intended behaviour
|
||||||
|
// Just ignore the error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkUpdates() {
|
void _checkUpdates() {
|
||||||
@@ -58,7 +119,10 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(final injectable in InjectableDll.values) {
|
for(final injectable in InjectableDll.values) {
|
||||||
downloadCriticalDllInteractive("${injectable.name}.dll");
|
downloadCriticalDllInteractive(
|
||||||
|
injectable.path,
|
||||||
|
silent: true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchDlls().listen((filePath) => showDllDeletedDialog(() {
|
watchDlls().listen((filePath) => showDllDeletedDialog(() {
|
||||||
@@ -157,7 +221,8 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
|||||||
super.build(context);
|
super.build(context);
|
||||||
_settingsController.language.value;
|
_settingsController.language.value;
|
||||||
loadTranslations(context);
|
loadTranslations(context);
|
||||||
return Obx(() => NavigationPaneTheme(
|
return Obx(() {
|
||||||
|
return NavigationPaneTheme(
|
||||||
data: NavigationPaneThemeData(
|
data: NavigationPaneThemeData(
|
||||||
backgroundColor: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.93),
|
backgroundColor: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.93),
|
||||||
),
|
),
|
||||||
@@ -203,8 +268,8 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
|||||||
onOpenSearch: () => _searchFocusNode.requestFocus(),
|
onOpenSearch: () => _searchFocusNode.requestFocus(),
|
||||||
transitionBuilder: (child, animation) => child
|
transitionBuilder: (child, animation) => child
|
||||||
)
|
)
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget get _backButton => StreamBuilder(
|
Widget get _backButton => StreamBuilder(
|
||||||
|
|||||||
@@ -69,9 +69,11 @@ class InfoPage extends RebootPage {
|
|||||||
class _InfoPageState extends RebootPageState<InfoPage> {
|
class _InfoPageState extends RebootPageState<InfoPage> {
|
||||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||||
RxInt _counter = RxInt(kDebugMode ? 0 : 180);
|
RxInt _counter = RxInt(kDebugMode ? 0 : 180);
|
||||||
|
late bool _showButton;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
_showButton = _settingsController.firstRun.value;
|
||||||
if(_settingsController.firstRun.value) {
|
if(_settingsController.firstRun.value) {
|
||||||
Timer.periodic(const Duration(seconds: 1), (timer) {
|
Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
if (_counter.value <= 0) {
|
if (_counter.value <= 0) {
|
||||||
@@ -89,11 +91,12 @@ class _InfoPageState extends RebootPageState<InfoPage> {
|
|||||||
List<Widget> get settings => InfoPage._infoTiles;
|
List<Widget> get settings => InfoPage._infoTiles;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? get button => Obx(() {
|
Widget? get button {
|
||||||
if(!_settingsController.firstRun.value) {
|
if(!_showButton) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Obx(() {
|
||||||
final totalSecondsLeft = _counter.value;
|
final totalSecondsLeft = _counter.value;
|
||||||
final minutesLeft = totalSecondsLeft ~/ 60;
|
final minutesLeft = totalSecondsLeft ~/ 60;
|
||||||
final secondsLeft = totalSecondsLeft % 60;
|
final secondsLeft = totalSecondsLeft % 60;
|
||||||
@@ -101,7 +104,10 @@ class _InfoPageState extends RebootPageState<InfoPage> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 48,
|
height: 48,
|
||||||
child: Button(
|
child: Button(
|
||||||
onPressed: totalSecondsLeft <= 0 ? () => pageIndex.value = RebootPageType.play.index : null,
|
onPressed: totalSecondsLeft <= 0 ? () {
|
||||||
|
_showButton = false;
|
||||||
|
pageIndex.value = RebootPageType.play.index;
|
||||||
|
} : null,
|
||||||
child: Text(
|
child: Text(
|
||||||
totalSecondsLeft <= 0 ? "I have read the instructions"
|
totalSecondsLeft <= 0 ? "I have read the instructions"
|
||||||
: "Read the instructions for at least ${secondsLeft == 0 ? '$minutesLeft minute${minutesLeft > 1 ? 's' : ''}' : minutesLeft == 0 ? '$secondsLeft second${secondsLeft > 1 ? 's' : ''}' : '$minutesLeft minute${minutesLeft > 1 ? 's' : ''} and $secondsLeft second${secondsLeft > 1 ? 's' : ''}'}"
|
: "Read the instructions for at least ${secondsLeft == 0 ? '$minutesLeft minute${minutesLeft > 1 ? 's' : ''}' : minutesLeft == 0 ? '$secondsLeft second${secondsLeft > 1 ? 's' : ''}' : '$minutesLeft minute${minutesLeft > 1 ? 's' : ''} and $secondsLeft second${secondsLeft > 1 ? 's' : ''}'}"
|
||||||
@@ -109,4 +115,5 @@ class _InfoPageState extends RebootPageState<InfoPage> {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import 'package:reboot_launcher/src/controller/game_controller.dart';
|
|||||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/update_controller.dart';
|
import 'package:reboot_launcher/src/controller/update_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
|
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/implementation/data.dart';
|
import 'package:reboot_launcher/src/dialog/implementation/data.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
|
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
|
||||||
@@ -65,8 +66,10 @@ class _HostingPageState extends RebootPageState<HostPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget get button => const LaunchButton(
|
Widget get button => LaunchButton(
|
||||||
host: true
|
host: true,
|
||||||
|
startLabel: translations.startHosting,
|
||||||
|
stopLabel: translations.stopHosting
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -194,6 +197,8 @@ class _HostingPageState extends RebootPageState<HostPage> {
|
|||||||
title: Text(translations.settingsServerTypeName),
|
title: Text(translations.settingsServerTypeName),
|
||||||
subtitle: Text(translations.settingsServerTypeDescription),
|
subtitle: Text(translations.settingsServerTypeDescription),
|
||||||
content: Obx(() => DropDownButton(
|
content: Obx(() => DropDownButton(
|
||||||
|
onOpen: () => inDialog = true,
|
||||||
|
onClose: () => inDialog = false,
|
||||||
leading: Text(_updateController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName),
|
leading: Text(_updateController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName),
|
||||||
items: {
|
items: {
|
||||||
false: translations.settingsServerTypeEmbeddedName,
|
false: translations.settingsServerTypeEmbeddedName,
|
||||||
@@ -209,7 +214,9 @@ class _HostingPageState extends RebootPageState<HostPage> {
|
|||||||
_updateController.customGameServer.value = entry.key;
|
_updateController.customGameServer.value = entry.key;
|
||||||
_updateController.infoBarEntry?.close();
|
_updateController.infoBarEntry?.close();
|
||||||
if(!entry.key) {
|
if(!entry.key) {
|
||||||
_updateController.updateReboot(true);
|
_updateController.updateReboot(
|
||||||
|
force: true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)).toList()
|
)).toList()
|
||||||
@@ -256,13 +263,17 @@ class _HostingPageState extends RebootPageState<HostPage> {
|
|||||||
title: Text(translations.settingsServerTimerName),
|
title: Text(translations.settingsServerTimerName),
|
||||||
subtitle: Text(translations.settingsServerTimerSubtitle),
|
subtitle: Text(translations.settingsServerTimerSubtitle),
|
||||||
content: Obx(() => DropDownButton(
|
content: Obx(() => DropDownButton(
|
||||||
|
onOpen: () => inDialog = true,
|
||||||
|
onClose: () => inDialog = false,
|
||||||
leading: Text(_updateController.timer.value.text),
|
leading: Text(_updateController.timer.value.text),
|
||||||
items: UpdateTimer.values.map((entry) => MenuFlyoutItem(
|
items: UpdateTimer.values.map((entry) => MenuFlyoutItem(
|
||||||
text: Text(entry.text),
|
text: Text(entry.text),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_updateController.timer.value = entry;
|
_updateController.timer.value = entry;
|
||||||
_updateController.infoBarEntry?.close();
|
_updateController.infoBarEntry?.close();
|
||||||
_updateController.updateReboot(true);
|
_updateController.updateReboot(
|
||||||
|
force: true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
)).toList()
|
)).toList()
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/implementation/data.dart';
|
import 'package:reboot_launcher/src/dialog/implementation/data.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||||
@@ -46,6 +47,8 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
|||||||
title: Text(translations.settingsUtilsLanguageName),
|
title: Text(translations.settingsUtilsLanguageName),
|
||||||
subtitle: Text(translations.settingsUtilsLanguageDescription),
|
subtitle: Text(translations.settingsUtilsLanguageDescription),
|
||||||
content: Obx(() => DropDownButton(
|
content: Obx(() => DropDownButton(
|
||||||
|
onOpen: () => inDialog = true,
|
||||||
|
onClose: () => inDialog = false,
|
||||||
leading: Text(_getLocaleName(_settingsController.language.value)),
|
leading: Text(_getLocaleName(_settingsController.language.value)),
|
||||||
items: AppLocalizations.supportedLocales.map((locale) => MenuFlyoutItem(
|
items: AppLocalizations.supportedLocales.map((locale) => MenuFlyoutItem(
|
||||||
text: Text(_getLocaleName(locale.languageCode)),
|
text: Text(_getLocaleName(locale.languageCode)),
|
||||||
@@ -60,6 +63,8 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
|||||||
title: Text(translations.settingsUtilsThemeName),
|
title: Text(translations.settingsUtilsThemeName),
|
||||||
subtitle: Text(translations.settingsUtilsThemeDescription),
|
subtitle: Text(translations.settingsUtilsThemeDescription),
|
||||||
content: Obx(() => DropDownButton(
|
content: Obx(() => DropDownButton(
|
||||||
|
onOpen: () => inDialog = true,
|
||||||
|
onClose: () => inDialog = false,
|
||||||
leading: Text(_settingsController.themeMode.value.title),
|
leading: Text(_settingsController.themeMode.value.title),
|
||||||
items: ThemeMode.values.map((themeMode) => MenuFlyoutItem(
|
items: ThemeMode.values.map((themeMode) => MenuFlyoutItem(
|
||||||
text: Text(themeMode.title),
|
text: Text(themeMode.title),
|
||||||
|
|||||||
@@ -13,40 +13,51 @@ import 'package:reboot_launcher/src/util/translations.dart';
|
|||||||
final UpdateController _updateController = Get.find<UpdateController>();
|
final UpdateController _updateController = Get.find<UpdateController>();
|
||||||
final Map<String, Future<void>> _operations = {};
|
final Map<String, Future<void>> _operations = {};
|
||||||
|
|
||||||
Future<void> downloadCriticalDllInteractive(String filePath) {
|
Future<void> downloadCriticalDllInteractive(String filePath, {bool silent = false}) {
|
||||||
final old = _operations[filePath];
|
final old = _operations[filePath];
|
||||||
if(old != null) {
|
if(old != null) {
|
||||||
return old;
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newRun = _downloadCriticalDllInteractive(filePath);
|
final newRun = _downloadCriticalDllInteractive(filePath, silent);
|
||||||
_operations[filePath] = newRun;
|
_operations[filePath] = newRun;
|
||||||
return newRun;
|
return newRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _downloadCriticalDllInteractive(String filePath) async {
|
Future<void> _downloadCriticalDllInteractive(String filePath, bool silent) async {
|
||||||
final fileName = path.basename(filePath).toLowerCase();
|
final fileName = path.basename(filePath).toLowerCase();
|
||||||
InfoBarEntry? entry;
|
InfoBarEntry? entry;
|
||||||
try {
|
try {
|
||||||
if (fileName == "reboot.dll") {
|
if (fileName == "reboot.dll") {
|
||||||
await _updateController.updateReboot(true);
|
await _updateController.updateReboot(
|
||||||
|
silent: silent
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(File(filePath).existsSync()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final fileNameWithoutExtension = path.basenameWithoutExtension(filePath);
|
final fileNameWithoutExtension = path.basenameWithoutExtension(filePath);
|
||||||
|
if(!silent) {
|
||||||
entry = showInfoBar(
|
entry = showInfoBar(
|
||||||
translations.downloadingDll(fileNameWithoutExtension),
|
translations.downloadingDll(fileNameWithoutExtension),
|
||||||
loading: true,
|
loading: true,
|
||||||
duration: null
|
duration: null
|
||||||
);
|
);
|
||||||
|
}
|
||||||
await downloadCriticalDll(fileName, filePath);
|
await downloadCriticalDll(fileName, filePath);
|
||||||
entry.close();
|
entry?.close();
|
||||||
|
if(!silent) {
|
||||||
entry = await showInfoBar(
|
entry = await showInfoBar(
|
||||||
translations.downloadDllSuccess(fileNameWithoutExtension),
|
translations.downloadDllSuccess(fileNameWithoutExtension),
|
||||||
severity: InfoBarSeverity.success,
|
severity: InfoBarSeverity.success,
|
||||||
duration: infoBarShortDuration
|
duration: infoBarShortDuration
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}catch(message) {
|
}catch(message) {
|
||||||
|
if(!silent) {
|
||||||
entry?.close();
|
entry?.close();
|
||||||
var error = message.toString();
|
var error = message.toString();
|
||||||
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
|
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
|
||||||
@@ -66,6 +77,7 @@ Future<void> _downloadCriticalDllInteractive(String filePath) async {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
await completer.future;
|
await completer.future;
|
||||||
|
}
|
||||||
}finally {
|
}finally {
|
||||||
_operations.remove(fileName);
|
_operations.remove(fileName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ File _createLoggingFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void log(String message) async {
|
void log(String message) async {
|
||||||
|
try {
|
||||||
await _semaphore.acquire();
|
await _semaphore.acquire();
|
||||||
|
print(message);
|
||||||
await _loggingFile.writeAsString("$message\n", mode: FileMode.append, flush: true);
|
await _loggingFile.writeAsString("$message\n", mode: FileMode.append, flush: true);
|
||||||
|
}catch(error) {
|
||||||
|
print(error);
|
||||||
|
}finally {
|
||||||
_semaphore.release();
|
_semaphore.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -305,10 +305,25 @@ final class Win32Process extends Struct {
|
|||||||
external int HWndLength;
|
external int HWndLength;
|
||||||
|
|
||||||
external Pointer<Uint32> HWnd;
|
external Pointer<Uint32> HWnd;
|
||||||
|
|
||||||
|
external Pointer<Utf16> excluded;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _filter(int HWnd, int lParam) {
|
int _filter(int HWnd, int lParam) {
|
||||||
final structure = Pointer.fromAddress(lParam).cast<Win32Process>();
|
final structure = Pointer.fromAddress(lParam).cast<Win32Process>();
|
||||||
|
if(structure.ref.excluded != nullptr) {
|
||||||
|
final excludedWindowName = structure.ref.excluded.toDartString();
|
||||||
|
final windowNameLength = GetWindowTextLength(HWnd);
|
||||||
|
if(windowNameLength > 0) {
|
||||||
|
final windowNamePointer = calloc<Uint16>(windowNameLength + 1).cast<Utf16>();
|
||||||
|
GetWindowText(HWnd, windowNamePointer, windowNameLength);
|
||||||
|
final windowName = windowNamePointer.toDartString(length: windowNameLength);
|
||||||
|
if(windowName.toLowerCase().contains(excludedWindowName.toLowerCase())) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final pidPointer = calloc<Uint32>();
|
final pidPointer = calloc<Uint32>();
|
||||||
GetWindowThreadProcessId(HWnd, pidPointer);
|
GetWindowThreadProcessId(HWnd, pidPointer);
|
||||||
final pid = pidPointer.value;
|
final pid = pidPointer.value;
|
||||||
@@ -330,9 +345,13 @@ int _filter(int HWnd, int lParam) {
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> _getHWnds(int pid) {
|
List<int> _getHWnds(int pid, String? excludedWindowName) {
|
||||||
final result = calloc<Win32Process>();
|
final result = calloc<Win32Process>();
|
||||||
result.ref.pid = pid;
|
result.ref.pid = pid;
|
||||||
|
if(excludedWindowName != null) {
|
||||||
|
result.ref.excluded = excludedWindowName.toNativeUtf16();
|
||||||
|
}
|
||||||
|
|
||||||
EnumWindows(Pointer.fromFunction<EnumWindowsProc>(_filter, TRUE), result.address);
|
EnumWindows(Pointer.fromFunction<EnumWindowsProc>(_filter, TRUE), result.address);
|
||||||
final length = result.ref.HWndLength;
|
final length = result.ref.HWndLength;
|
||||||
final HWndsPointer = result.ref.HWnd;
|
final HWndsPointer = result.ref.HWnd;
|
||||||
@@ -400,24 +419,26 @@ class VirtualDesktopManager {
|
|||||||
|
|
||||||
List<IVirtualDesktop> getDesktops() => windowManager.getDesktops();
|
List<IVirtualDesktop> getDesktops() => windowManager.getDesktops();
|
||||||
|
|
||||||
Future<void> moveWindowToDesktop(int pid, IVirtualDesktop desktop, {Duration pollTime = const Duration(seconds: 1)}) async {
|
Future<bool> moveWindowToDesktop(int pid, IVirtualDesktop desktop, {Duration pollTime = const Duration(seconds: 1), int remainingPolls = 10, String? excludedWindowName}) async {
|
||||||
final hWNDs = _getHWnds(pid);
|
for(final hWND in _getHWnds(pid, excludedWindowName)) {
|
||||||
if(hWNDs.isEmpty) {
|
|
||||||
await Future.delayed(pollTime);
|
|
||||||
await moveWindowToDesktop(pid, desktop, pollTime: pollTime);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(final hWND in hWNDs) {
|
|
||||||
final window = applicationViewCollection.getViewForHWnd(hWND);
|
final window = applicationViewCollection.getViewForHWnd(hWND);
|
||||||
if(window != null) {
|
if(window != null) {
|
||||||
windowManager.moveWindowToDesktop(window, desktop);
|
windowManager.moveWindowToDesktop(window, desktop);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(remainingPolls <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
await Future.delayed(pollTime);
|
await Future.delayed(pollTime);
|
||||||
await moveWindowToDesktop(pid, desktop, pollTime: pollTime);
|
return await moveWindowToDesktop(
|
||||||
|
pid,
|
||||||
|
desktop,
|
||||||
|
pollTime: pollTime,
|
||||||
|
remainingPolls: remainingPolls - 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
IVirtualDesktop createDesktop() => windowManager.createDesktop();
|
IVirtualDesktop createDesktop() => windowManager.createDesktop();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:async/async.dart';
|
import 'package:async/async.dart';
|
||||||
import 'package:dart_ipify/dart_ipify.dart';
|
import 'package:dart_ipify/dart_ipify.dart';
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:local_notifier/local_notifier.dart';
|
import 'package:local_notifier/local_notifier.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
@@ -12,7 +13,6 @@ import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
|||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/update_controller.dart';
|
|
||||||
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
|
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
|
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
|
||||||
@@ -27,10 +27,10 @@ import 'package:reboot_launcher/src/util/translations.dart';
|
|||||||
|
|
||||||
class LaunchButton extends StatefulWidget {
|
class LaunchButton extends StatefulWidget {
|
||||||
final bool host;
|
final bool host;
|
||||||
final String? startLabel;
|
final String startLabel;
|
||||||
final String? stopLabel;
|
final String stopLabel;
|
||||||
|
|
||||||
const LaunchButton({Key? key, required this.host, this.startLabel, this.stopLabel}) : super(key: key);
|
const LaunchButton({Key? key, required this.host, required this.startLabel, required this.stopLabel}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LaunchButton> createState() => _LaunchButtonState();
|
State<LaunchButton> createState() => _LaunchButtonState();
|
||||||
@@ -43,7 +43,6 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
final HostingController _hostingController = Get.find<HostingController>();
|
final HostingController _hostingController = Get.find<HostingController>();
|
||||||
final BackendController _backendController = Get.find<BackendController>();
|
final BackendController _backendController = Get.find<BackendController>();
|
||||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||||
final UpdateController _updateController = Get.find<UpdateController>();
|
|
||||||
InfoBarEntry? _gameClientInfoBar;
|
InfoBarEntry? _gameClientInfoBar;
|
||||||
InfoBarEntry? _gameServerInfoBar;
|
InfoBarEntry? _gameServerInfoBar;
|
||||||
CancelableOperation? _operation;
|
CancelableOperation? _operation;
|
||||||
@@ -60,52 +59,42 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
onPressed: () => _operation = CancelableOperation.fromFuture(_toggle()),
|
onPressed: () => _operation = CancelableOperation.fromFuture(_toggle()),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(_hasStarted ? _stopMessage : _startMessage)
|
child: Text((widget.host ? _hostingController.started() : _gameController.started()) ? widget.stopLabel : widget.startLabel)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
bool get _hasStarted => widget.host ? _hostingController.started() : _gameController.started();
|
|
||||||
|
|
||||||
void _setStarted(bool hosting, bool started) => hosting ? _hostingController.started.value = started : _gameController.started.value = started;
|
void _setStarted(bool hosting, bool started) => hosting ? _hostingController.started.value = started : _gameController.started.value = started;
|
||||||
|
|
||||||
String get _startMessage => widget.startLabel ?? (widget.host ? translations.startHosting : translations.startGame);
|
Future<void> _toggle({bool? host, bool forceGUI = false}) async {
|
||||||
|
host ??= widget.host;
|
||||||
String get _stopMessage => widget.stopLabel ?? (widget.host ? translations.stopHosting : translations.stopGame);
|
log("[${host ? 'HOST' : 'GAME'}] Toggling state(forceGUI: $forceGUI)");
|
||||||
|
if (host ? _hostingController.started() : _gameController.started()) {
|
||||||
Future<void> _toggle({bool forceGUI = false}) async {
|
log("[${host ? 'HOST' : 'GAME'}] User asked to close the current instance");
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Toggling state(forceGUI: $forceGUI)");
|
|
||||||
if (_hasStarted) {
|
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] User asked to close the current instance");
|
|
||||||
_onStop(
|
_onStop(
|
||||||
reason: _StopReason.normal
|
reason: _StopReason.normal
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_operation != null) {
|
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Already started, ignoring user action");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final version = _gameController.selectedVersion;
|
final version = _gameController.selectedVersion;
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Version data: $version");
|
log("[${host ? 'HOST' : 'GAME'}] Version data: $version");
|
||||||
if(version == null){
|
if(version == null){
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] No version selected");
|
log("[${host ? 'HOST' : 'GAME'}] No version selected");
|
||||||
_onStop(
|
_onStop(
|
||||||
reason: _StopReason.missingVersionError
|
reason: _StopReason.missingVersionError
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Setting started...");
|
log("[${host ? 'HOST' : 'GAME'}] Setting started...");
|
||||||
_setStarted(widget.host, true);
|
_setStarted(host, true);
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Set started");
|
log("[${host ? 'HOST' : 'GAME'}] Set started");
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Checking dlls: ${InjectableDll.values}");
|
log("[${host ? 'HOST' : 'GAME'}] Checking dlls: ${InjectableDll.values}");
|
||||||
for (final injectable in InjectableDll.values) {
|
for (final injectable in InjectableDll.values) {
|
||||||
if(await _getDllFileOrStop(injectable, widget.host) == null) {
|
if(await _getDllFileOrStop(injectable, host) == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +102,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
try {
|
try {
|
||||||
final executable = version.gameExecutable;
|
final executable = version.gameExecutable;
|
||||||
if(executable == null){
|
if(executable == null){
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] No executable found");
|
log("[${host ? 'HOST' : 'GAME'}] No executable found");
|
||||||
_onStop(
|
_onStop(
|
||||||
reason: _StopReason.missingExecutableError,
|
reason: _StopReason.missingExecutableError,
|
||||||
error: version.location.path
|
error: version.location.path
|
||||||
@@ -121,27 +110,27 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Checking backend(port: ${_backendController.type.value.name}, type: ${_backendController.type.value.name})...");
|
log("[${host ? 'HOST' : 'GAME'}] Checking backend(port: ${_backendController.type.value.name}, type: ${_backendController.type.value.name})...");
|
||||||
final backendResult = _backendController.started() || await _backendController.toggleInteractive();
|
final backendResult = _backendController.started() || await _backendController.toggleInteractive();
|
||||||
if(!backendResult){
|
if(!backendResult){
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Cannot start backend");
|
log("[${host ? 'HOST' : 'GAME'}] Cannot start backend");
|
||||||
_onStop(
|
_onStop(
|
||||||
reason: _StopReason.backendError
|
reason: _StopReason.backendError
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Backend works");
|
log("[${host ? 'HOST' : 'GAME'}] Backend works");
|
||||||
final headless = !forceGUI && _hostingController.headless.value;
|
final headless = !forceGUI && _hostingController.headless.value;
|
||||||
final virtualDesktop = _hostingController.virtualDesktop.value;
|
final virtualDesktop = _hostingController.virtualDesktop.value;
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($headless)");
|
log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($headless)");
|
||||||
final linkedHostingInstance = await _startMatchMakingServer(version, headless, virtualDesktop, false);
|
final linkedHostingInstance = await _startMatchMakingServer(version, host, headless, virtualDesktop, false);
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Implicit game server result: $linkedHostingInstance");
|
log("[${host ? 'HOST' : 'GAME'}] Implicit game server result: $linkedHostingInstance");
|
||||||
await _startGameProcesses(version, widget.host, headless, virtualDesktop, linkedHostingInstance);
|
await _startGameProcesses(version, host, headless, virtualDesktop, linkedHostingInstance);
|
||||||
if(!widget.host) {
|
if(!host) {
|
||||||
_showLaunchingGameClientWidget();
|
_showLaunchingGameClientWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(linkedHostingInstance != null || widget.host){
|
if(linkedHostingInstance != null || host){
|
||||||
_showLaunchingGameServerWidget();
|
_showLaunchingGameServerWidget();
|
||||||
}
|
}
|
||||||
} catch (exception, stackTrace) {
|
} catch (exception, stackTrace) {
|
||||||
@@ -153,34 +142,34 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GameInstance?> _startMatchMakingServer(FortniteVersion version, bool headless, bool virtualDesktop, bool forceLinkedHosting) async {
|
Future<GameInstance?> _startMatchMakingServer(FortniteVersion version, bool host, bool headless, bool virtualDesktop, bool forceLinkedHosting) async {
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Checking if a server needs to be started automatically...");
|
log("[${host ? 'HOST' : 'GAME'}] Checking if a server needs to be started automatically...");
|
||||||
if(widget.host){
|
if(host){
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] The user clicked on Start hosting, so it's not necessary");
|
log("[${host ? 'HOST' : 'GAME'}] The user clicked on Start hosting, so it's not necessary");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_backendController.type.value != ServerType.embedded || !isLocalHost(_backendController.gameServerAddress.text)) {
|
if(_backendController.type.value != ServerType.embedded || !isLocalHost(_backendController.gameServerAddress.text)) {
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Backend is not set to embedded and/or not pointing to the local game server");
|
log("[${host ? 'HOST' : 'GAME'}] Backend is not set to embedded and/or not pointing to the local game server");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_hostingController.started()){
|
if(_hostingController.started()){
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] The user has already manually started the hosting server");
|
log("[${host ? 'HOST' : 'GAME'}] The user has already manually started the hosting server");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final response = forceLinkedHosting || await _askForAutomaticGameServer();
|
final response = forceLinkedHosting || await _askForAutomaticGameServer();
|
||||||
if(!response) {
|
if(!response) {
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] The user disabled the automatic server");
|
log("[${host ? 'HOST' : 'GAME'}] The user disabled the automatic server");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Starting implicit game server...");
|
log("[${host ? 'HOST' : 'GAME'}] Starting implicit game server...");
|
||||||
final instance = await _startGameProcesses(version, true, headless, virtualDesktop, null);
|
final instance = await _startGameProcesses(version, true, headless, virtualDesktop, null);
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Started implicit game server...");
|
log("[${host ? 'HOST' : 'GAME'}] Started implicit game server...");
|
||||||
_setStarted(true, true);
|
_setStarted(true, true);
|
||||||
log("[${widget.host ? 'HOST' : 'GAME'}] Set implicit game server as started");
|
log("[${host ? 'HOST' : 'GAME'}] Set implicit game server as started");
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,11 +234,6 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> _createGameProcess(FortniteVersion version, File executable, bool host, bool headless, bool virtualDesktop, GameInstance? linkedHosting) async {
|
Future<int?> _createGameProcess(FortniteVersion version, File executable, bool host, bool headless, bool virtualDesktop, GameInstance? linkedHosting) async {
|
||||||
if(!_hasStarted) {
|
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Discarding start game process request as the state is no longer started");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Generating instance args...");
|
log("[${host ? 'HOST' : 'GAME'}] Generating instance args...");
|
||||||
final gameArgs = createRebootArgs(
|
final gameArgs = createRebootArgs(
|
||||||
_gameController.username.text,
|
_gameController.username.text,
|
||||||
@@ -265,40 +249,52 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
wrapProcess: false,
|
wrapProcess: false,
|
||||||
name: "${version.name}-${host ? 'HOST' : 'GAME'}"
|
name: "${version.name}-${host ? 'HOST' : 'GAME'}"
|
||||||
);
|
);
|
||||||
gameProcess.stdOutput.listen((line) => _onGameOutput(line, version, host, virtualDesktop, false));
|
void onGameOutput(String line, bool error) {
|
||||||
gameProcess.stdError.listen((line) => _onGameOutput(line, version, host, virtualDesktop, true));
|
log("[${host ? 'HOST' : 'GAME'}] ${error ? '[ERROR]' : '[MESSAGE]'} $line");
|
||||||
watchProcess(gameProcess.pid).then((_) async {
|
|
||||||
|
handleGameOutput(
|
||||||
|
line: line,
|
||||||
|
host: host,
|
||||||
|
onShutdown: () => _onStop(reason: _StopReason.normal),
|
||||||
|
onTokenError: () => _onStop(reason: _StopReason.tokenError),
|
||||||
|
onBuildCorrupted: () => _onStop(reason: _StopReason.corruptedVersionError),
|
||||||
|
onLoggedIn: () =>_onLoggedIn(host),
|
||||||
|
onMatchEnd: () => _onMatchEnd(version, virtualDesktop),
|
||||||
|
onDisplayAttached: () => _onDisplayAttached(headless, virtualDesktop, version)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
gameProcess.stdOutput.listen((line) => onGameOutput(line, false));
|
||||||
|
gameProcess.stdError.listen((line) => onGameOutput(line, true));
|
||||||
|
gameProcess.exitCode.then((_) async {
|
||||||
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
||||||
if(instance == null) {
|
if(instance == null) {
|
||||||
|
log("[${host ? 'HOST' : 'GAME'}] Called exit code, but the game process is no longer running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!host || !headless || instance.launched) {
|
if(!host || instance.launched) {
|
||||||
_onStop(reason: _StopReason.exitCode);
|
log("[${host ? 'HOST' : 'GAME'}] Called exit code(headless: $headless, launched: ${instance.launched}): stop signal");
|
||||||
|
_onStop(
|
||||||
|
reason: _StopReason.exitCode,
|
||||||
|
host: host
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _restartGameServer(version, virtualDesktop, _StopReason.exitCode);
|
log("[${host ? 'HOST' : 'GAME'}] Called exit code(headless: $headless, launched: ${instance.launched}): restart signal");
|
||||||
|
instance.launched = true;
|
||||||
|
await _onStop(
|
||||||
|
reason: _StopReason.exitCode,
|
||||||
|
host: true
|
||||||
|
);
|
||||||
|
await _toggle(
|
||||||
|
forceGUI: true,
|
||||||
|
host: true
|
||||||
|
);
|
||||||
});
|
});
|
||||||
return gameProcess.pid;
|
return gameProcess.pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _restartGameServer(FortniteVersion version, bool virtualDesktop, _StopReason reason) async {
|
|
||||||
if (widget.host) {
|
|
||||||
await _onStop(reason: reason);
|
|
||||||
_toggle(forceGUI: true);
|
|
||||||
} else {
|
|
||||||
await _onStop(reason: reason, host: true);
|
|
||||||
final linkedHostingInstance =
|
|
||||||
await _startMatchMakingServer(version, false, virtualDesktop, true);
|
|
||||||
_gameController.instance.value?.child = linkedHostingInstance;
|
|
||||||
if (linkedHostingInstance != null) {
|
|
||||||
_setStarted(true, true);
|
|
||||||
_showLaunchingGameServerWidget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int?> _createPausedProcess(FortniteVersion version, File? file) async {
|
Future<int?> _createPausedProcess(FortniteVersion version, File? file) async {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -314,20 +310,80 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return pid;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onGameOutput(String line, FortniteVersion version, bool host, bool virtualDesktop, bool error) async {
|
Future<void> _onDisplayAttached(bool headless, bool virtualDesktop, FortniteVersion version) async {
|
||||||
if (line.contains(kShutdownLine)) {
|
if(!headless && virtualDesktop) {
|
||||||
_onStop(
|
final hostingInstance = _hostingController.instance.value;
|
||||||
reason: _StopReason.normal
|
if(hostingInstance != null && !hostingInstance.movedToVirtualDesktop) {
|
||||||
|
hostingInstance.movedToVirtualDesktop = true;
|
||||||
|
try {
|
||||||
|
final windowManager = VirtualDesktopManager.getInstance();
|
||||||
|
_virtualDesktop = windowManager.createDesktop();
|
||||||
|
windowManager.setDesktopName(_virtualDesktop!, "${version.name} Server (Reboot Launcher)");
|
||||||
|
var success = false;
|
||||||
|
try {
|
||||||
|
success = await windowManager.moveWindowToDesktop(
|
||||||
|
hostingInstance.gamePid,
|
||||||
|
_virtualDesktop!,
|
||||||
|
excludedWindowName: "Reboot"
|
||||||
);
|
);
|
||||||
}else if(kCorruptedBuildErrors.any((element) => line.contains(element))){
|
}catch(error) {
|
||||||
_onStop(
|
log("[VIRTUAL_DESKTOP] $error");
|
||||||
reason: _StopReason.corruptedVersionError
|
success = false;
|
||||||
|
}
|
||||||
|
if(!success) {
|
||||||
|
try {
|
||||||
|
windowManager.removeDesktop(_virtualDesktop!);
|
||||||
|
}catch(error) {
|
||||||
|
log("[VIRTUAL_DESKTOP] $error");
|
||||||
|
}finally {
|
||||||
|
_virtualDesktop = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(error) {
|
||||||
|
log("[VIRTUAL_DESKTOP] $error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onMatchEnd(FortniteVersion version, bool virtualDesktop) {
|
||||||
|
if(_hostingController.autoRestart.value) {
|
||||||
|
final notification = LocalNotification(
|
||||||
|
title: translations.gameServerEnd,
|
||||||
|
body: translations.gameServerRestart(_kRebootDelay.inSeconds),
|
||||||
);
|
);
|
||||||
}else if(kCannotConnectErrors.any((element) => line.contains(element))){
|
notification.show();
|
||||||
_onStop(
|
Future.delayed(_kRebootDelay).then((_) async {
|
||||||
reason: _StopReason.tokenError
|
log("[RESTARTER] Stopping server...");
|
||||||
|
await _onStop(
|
||||||
|
reason: _StopReason.normal,
|
||||||
|
host: true
|
||||||
);
|
);
|
||||||
}else if(kLoggedInLines.every((entry) => line.contains(entry))) {
|
log("[RESTARTER] Stopped server");
|
||||||
|
log("[RESTARTER] Starting server...");
|
||||||
|
await _toggle(
|
||||||
|
host: true
|
||||||
|
);
|
||||||
|
log("[RESTARTER] Started server");
|
||||||
|
});
|
||||||
|
}else {
|
||||||
|
final notification = LocalNotification(
|
||||||
|
title: translations.gameServerEnd,
|
||||||
|
body: translations.gameServerShutdown(_kRebootDelay.inSeconds)
|
||||||
|
);
|
||||||
|
notification.show();
|
||||||
|
Future.delayed(_kRebootDelay).then((_) {
|
||||||
|
log("[RESTARTER] Stopping server...");
|
||||||
|
_onStop(
|
||||||
|
reason: _StopReason.normal,
|
||||||
|
host: true
|
||||||
|
);
|
||||||
|
log("[RESTARTER] Stopped server");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoggedIn(bool host) async {
|
||||||
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
||||||
if(instance != null && !instance.launched) {
|
if(instance != null && !instance.launched) {
|
||||||
instance.launched = true;
|
instance.launched = true;
|
||||||
@@ -345,51 +401,6 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
_onGameServerInjected();
|
_onGameServerInjected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else if(line.contains(kGameFinishedLine) && host) {
|
|
||||||
if(_hostingController.autoRestart.value) {
|
|
||||||
final notification = LocalNotification(
|
|
||||||
title: translations.gameServerEnd,
|
|
||||||
body: translations.gameServerRestart(_kRebootDelay.inSeconds),
|
|
||||||
);
|
|
||||||
notification.show();
|
|
||||||
Future.delayed(_kRebootDelay).then((_) {
|
|
||||||
_restartGameServer(version, virtualDesktop, _StopReason.normal);
|
|
||||||
});
|
|
||||||
}else {
|
|
||||||
final notification = LocalNotification(
|
|
||||||
title: translations.gameServerEnd,
|
|
||||||
body: translations.gameServerShutdown(_kRebootDelay.inSeconds)
|
|
||||||
);
|
|
||||||
notification.show();
|
|
||||||
Future.delayed(_kRebootDelay).then((_) {
|
|
||||||
_onStop(reason: _StopReason.normal, host: true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}else if(line.contains(kDisplayInitializedLine) && host && virtualDesktop) {
|
|
||||||
final hostingInstance = _hostingController.instance.value;
|
|
||||||
if(hostingInstance != null && !hostingInstance.movedToVirtualDesktop) {
|
|
||||||
hostingInstance.movedToVirtualDesktop = true;
|
|
||||||
try {
|
|
||||||
final windowManager = VirtualDesktopManager.getInstance();
|
|
||||||
_virtualDesktop = windowManager.createDesktop();
|
|
||||||
windowManager.setDesktopName(_virtualDesktop!, "${version.name} Server (Reboot Launcher)");
|
|
||||||
try {
|
|
||||||
await windowManager.moveWindowToDesktop(hostingInstance.gamePid, _virtualDesktop!);
|
|
||||||
}catch(error) {
|
|
||||||
log("[VIRTUAL_DESKTOP] $error");
|
|
||||||
try {
|
|
||||||
windowManager.removeDesktop(_virtualDesktop!);
|
|
||||||
}catch(error) {
|
|
||||||
log("[VIRTUAL_DESKTOP] $error");
|
|
||||||
}finally {
|
|
||||||
_virtualDesktop = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch(error) {
|
|
||||||
log("[VIRTUAL_DESKTOP] $error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onGameClientInjected() {
|
void _onGameClientInjected() {
|
||||||
@@ -411,11 +422,11 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
duration: null
|
duration: null
|
||||||
);
|
);
|
||||||
final gameServerPort = _settingsController.gameServerPort.text;
|
final gameServerPort = _settingsController.gameServerPort.text;
|
||||||
_gameServerInfoBar?.close();
|
|
||||||
final localPingResult = await pingGameServer(
|
final localPingResult = await pingGameServer(
|
||||||
"127.0.0.1:$gameServerPort",
|
"127.0.0.1:$gameServerPort",
|
||||||
timeout: const Duration(minutes: 2)
|
timeout: const Duration(minutes: 2)
|
||||||
);
|
);
|
||||||
|
_gameServerInfoBar?.close();
|
||||||
if (!localPingResult) {
|
if (!localPingResult) {
|
||||||
showInfoBar(
|
showInfoBar(
|
||||||
translations.gameServerStartWarning,
|
translations.gameServerStartWarning,
|
||||||
@@ -424,7 +435,6 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_backendController.joinLocalHost();
|
_backendController.joinLocalHost();
|
||||||
final accessible = await _checkGameServer(theme, gameServerPort);
|
final accessible = await _checkGameServer(theme, gameServerPort);
|
||||||
if (!accessible) {
|
if (!accessible) {
|
||||||
@@ -487,6 +497,20 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onStop({required _StopReason reason, bool? host, String? error, StackTrace? stackTrace}) async {
|
Future<void> _onStop({required _StopReason reason, bool? host, String? error, StackTrace? stackTrace}) async {
|
||||||
|
if(host == null) {
|
||||||
|
await _operation?.cancel();
|
||||||
|
_operation = null;
|
||||||
|
await _backendController.worker?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
host = host ?? widget.host;
|
||||||
|
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
||||||
|
if(host){
|
||||||
|
_hostingController.instance.value = null;
|
||||||
|
}else {
|
||||||
|
_gameController.instance.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
if(_virtualDesktop != null) {
|
if(_virtualDesktop != null) {
|
||||||
try {
|
try {
|
||||||
final instance = VirtualDesktopManager.getInstance();
|
final instance = VirtualDesktopManager.getInstance();
|
||||||
@@ -496,20 +520,12 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(host == null) {
|
|
||||||
await _operation?.cancel();
|
|
||||||
_operation = null;
|
|
||||||
await _backendController.worker?.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
host = host ?? widget.host;
|
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Called stop with reason $reason, error data $error $stackTrace");
|
log("[${host ? 'HOST' : 'GAME'}] Called stop with reason $reason, error data $error $stackTrace");
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Caller: ${StackTrace.current}");
|
log("[${host ? 'HOST' : 'GAME'}] Caller: ${StackTrace.current}");
|
||||||
if(host) {
|
if(host) {
|
||||||
_hostingController.discardServer();
|
_hostingController.discardServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
|
||||||
if(instance != null) {
|
if(instance != null) {
|
||||||
if(reason == _StopReason.normal) {
|
if(reason == _StopReason.normal) {
|
||||||
instance.launched = true;
|
instance.launched = true;
|
||||||
@@ -518,25 +534,21 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
instance.kill();
|
instance.kill();
|
||||||
final child = instance.child;
|
final child = instance.child;
|
||||||
if(child != null) {
|
if(child != null) {
|
||||||
_onStop(
|
await _onStop(
|
||||||
reason: reason,
|
reason: reason,
|
||||||
host: child.hosting
|
host: child.hosting
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(host){
|
|
||||||
_hostingController.instance.value = null;
|
|
||||||
}else {
|
|
||||||
_gameController.instance.value = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setStarted(host, false);
|
_setStarted(host, false);
|
||||||
if(host) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if(host == true) {
|
||||||
_gameServerInfoBar?.close();
|
_gameServerInfoBar?.close();
|
||||||
}else {
|
}else {
|
||||||
_gameClientInfoBar?.close();
|
_gameClientInfoBar?.close();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
switch(reason) {
|
switch(reason) {
|
||||||
case _StopReason.backendError:
|
case _StopReason.backendError:
|
||||||
@@ -558,7 +570,6 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case _StopReason.exitCode:
|
case _StopReason.exitCode:
|
||||||
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
|
||||||
if(instance != null && !instance.launched) {
|
if(instance != null && !instance.launched) {
|
||||||
showInfoBar(
|
showInfoBar(
|
||||||
translations.corruptedVersionError,
|
translations.corruptedVersionError,
|
||||||
@@ -566,7 +577,6 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
duration: infoBarLongDuration,
|
duration: infoBarLongDuration,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case _StopReason.corruptedVersionError:
|
case _StopReason.corruptedVersionError:
|
||||||
showInfoBar(
|
showInfoBar(
|
||||||
@@ -584,7 +594,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
break;
|
break;
|
||||||
case _StopReason.tokenError:
|
case _StopReason.tokenError:
|
||||||
showInfoBar(
|
showInfoBar(
|
||||||
translations.tokenError,
|
translations.tokenError(instance?.injectedDlls.map((element) => element.name).join(", ") ?? "none"),
|
||||||
severity: InfoBarSeverity.error,
|
severity: InfoBarSeverity.error,
|
||||||
duration: infoBarLongDuration,
|
duration: infoBarLongDuration,
|
||||||
);
|
);
|
||||||
@@ -622,6 +632,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
|
|
||||||
log("[${hosting ? 'HOST' : 'GAME'}] Trying to inject ${injectable.name}...");
|
log("[${hosting ? 'HOST' : 'GAME'}] Trying to inject ${injectable.name}...");
|
||||||
await injectDll(gameProcess, dllPath);
|
await injectDll(gameProcess, dllPath);
|
||||||
|
instance.injectedDlls.add(injectable);
|
||||||
log("[${hosting ? 'HOST' : 'GAME'}] Injected ${injectable.name}");
|
log("[${hosting ? 'HOST' : 'GAME'}] Injected ${injectable.name}");
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
log("[${hosting ? 'HOST' : 'GAME'}] Cannot inject ${injectable.name}: $error $stackTrace");
|
log("[${hosting ? 'HOST' : 'GAME'}] Cannot inject ${injectable.name}: $error $stackTrace");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
|
|
||||||
class ServerTypeSelector extends StatefulWidget {
|
class ServerTypeSelector extends StatefulWidget {
|
||||||
@@ -18,6 +19,7 @@ class _ServerTypeSelectorState extends State<ServerTypeSelector> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(() => DropDownButton(
|
return Obx(() => DropDownButton(
|
||||||
|
onOpen: () => inDialog = true,
|
||||||
leading: Text(_controller.type.value.label),
|
leading: Text(_controller.type.value.label),
|
||||||
items: ServerType.values
|
items: ServerType.values
|
||||||
.map((type) => _createItem(type))
|
.map((type) => _createItem(type))
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:reboot_launcher/src/util/translations.dart';
|
|||||||
import 'package:reboot_launcher/src/widget/add_local_version.dart';
|
import 'package:reboot_launcher/src/widget/add_local_version.dart';
|
||||||
import 'package:reboot_launcher/src/widget/add_server_version.dart';
|
import 'package:reboot_launcher/src/widget/add_server_version.dart';
|
||||||
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
||||||
|
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class VersionSelector extends StatefulWidget {
|
class VersionSelector extends StatefulWidget {
|
||||||
@@ -44,7 +45,13 @@ class _VersionSelectorState extends State<VersionSelector> {
|
|||||||
child: FlyoutTarget(
|
child: FlyoutTarget(
|
||||||
controller: _flyoutController,
|
controller: _flyoutController,
|
||||||
child: DropDownButton(
|
child: DropDownButton(
|
||||||
leading: Text(_gameController.selectedVersion?.name ?? translations.selectVersion),
|
onOpen: () => inDialog = true,
|
||||||
|
onClose: () => inDialog = false,
|
||||||
|
leading: Text(
|
||||||
|
_gameController.selectedVersion?.name ?? translations.selectVersion,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
items: _createSelectorItems(context)
|
items: _createSelectorItems(context)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,5 +10,11 @@ SettingTile get versionSelectSettingTile => SettingTile(
|
|||||||
),
|
),
|
||||||
title: Text(translations.selectFortniteName),
|
title: Text(translations.selectFortniteName),
|
||||||
subtitle: Text(translations.selectFortniteDescription),
|
subtitle: Text(translations.selectFortniteDescription),
|
||||||
content: const VersionSelector()
|
contentWidth: null,
|
||||||
|
content: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: SettingTile.kDefaultContentWidth,
|
||||||
|
),
|
||||||
|
child: const VersionSelector()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
name: reboot_launcher
|
name: reboot_launcher
|
||||||
description: Graphical User Interface for Project Reboot
|
description: Graphical User Interface for Project Reboot
|
||||||
version: "9.1.0"
|
version: "9.1.3"
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ AppPublisher={{PUBLISHER_NAME}}
|
|||||||
AppPublisherURL={{PUBLISHER_URL}}
|
AppPublisherURL={{PUBLISHER_URL}}
|
||||||
AppSupportURL={{PUBLISHER_URL}}
|
AppSupportURL={{PUBLISHER_URL}}
|
||||||
AppUpdatesURL={{PUBLISHER_URL}}
|
AppUpdatesURL={{PUBLISHER_URL}}
|
||||||
DefaultDirName={autopf}\{{DISPLAY_NAME}};
|
DefaultDirName={autopf}\{{DISPLAY_NAME}}
|
||||||
DisableProgramGroupPage=yes
|
DisableProgramGroupPage=yes
|
||||||
OutputBaseFilename={{OUTPUT_BASE_FILENAME}}
|
OutputBaseFilename={{OUTPUT_BASE_FILENAME}}
|
||||||
Compression=zip
|
Compression=zip
|
||||||
@@ -28,8 +28,11 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce
|
||||||
Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Dirs]
|
||||||
|
Name: "{app}"; Permissions: everyone-full
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "{{SOURCE_DIR}}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "{{SOURCE_DIR}}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Permissions: everyone-full
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""Add-MpPreference -ExclusionPath '{app}'"""; Flags: runhidden
|
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""Add-MpPreference -ExclusionPath '{app}'"""; Flags: runhidden
|
||||||
|
|||||||
Reference in New Issue
Block a user