This commit is contained in:
Alessandro Autiero
2024-06-15 17:57:17 +02:00
parent 2bf084d120
commit e24f4e97b3
144 changed files with 442 additions and 278 deletions

View File

@@ -43,7 +43,8 @@ class BackendController extends GetxController {
storage?.write("${type.value.name}_port", port.text));
detached = RxBool(storage?.read("detached") ?? false);
detached.listen((value) => storage?.write("detached", value));
gameServerAddress = TextEditingController(text: storage?.read("game_server_address") ?? "127.0.0.1");
final address = storage?.read("game_server_address");
gameServerAddress = TextEditingController(text: address == null || address.isEmpty ? "127.0.0.1" : address);
var lastValue = gameServerAddress.text;
writeMatchmakingIp(lastValue);
gameServerAddress.addListener(() {
@@ -76,6 +77,7 @@ class BackendController extends GetxController {
host.text = type.value != ServerType.remote ? kDefaultBackendHost : "";
port.text = kDefaultBackendPort.toString();
gameServerAddress.text = "127.0.0.1";
detached.value = false;
}

View File

@@ -14,8 +14,7 @@ class HostingController extends GetxController {
late final TextEditingController password;
late final RxBool showPassword;
late final RxBool discoverable;
late final RxBool headless;
late final RxBool virtualDesktop;
late final Rx<GameServerType> type;
late final RxBool autoRestart;
late final RxBool started;
late final RxBool published;
@@ -34,10 +33,8 @@ class HostingController extends GetxController {
password.addListener(() => _storage?.write("password", password.text));
discoverable = RxBool(_storage?.read("discoverable") ?? false);
discoverable.listen((value) => _storage?.write("discoverable", value));
headless = RxBool(_storage?.read("headless") ?? true);
headless.listen((value) => _storage?.write("headless", value));
virtualDesktop = RxBool(_storage?.read("virtual_desktop") ?? true);
virtualDesktop.listen((value) => _storage?.write("virtual_desktop", value));
type = Rx(GameServerType.values.elementAt(_storage?.read("type") ?? GameServerType.headless.index));
type.listen((value) => _storage?.write("type", value.index));
autoRestart = RxBool(_storage?.read("auto_restart") ?? true);
autoRestart.listen((value) => _storage?.write("auto_restart", value));
started = RxBool(false);
@@ -64,8 +61,8 @@ class HostingController extends GetxController {
discoverable.value = false;
started.value = false;
instance.value = null;
headless.value = true;
virtualDesktop.value = true;
type.value = GameServerType.headless;
autoRestart.value = true;
}
Map<String, dynamic>? findServerById(String uuid) {

View File

@@ -29,6 +29,8 @@ import 'package:reboot_launcher/src/widget/profile_tile.dart';
import 'package:reboot_launcher/src/widget/title_bar.dart';
import 'package:window_manager/window_manager.dart';
import 'info_page.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@@ -221,6 +223,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
super.build(context);
_settingsController.language.value;
loadTranslations(context);
// InfoPage.initInfoTiles();
return Obx(() {
return NavigationPaneTheme(
data: NavigationPaneThemeData(
@@ -394,7 +397,7 @@ class _PaneBody extends StatefulWidget {
class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin {
final SettingsController _settingsController = Get.find<SettingsController>();
final PageController _pageController = PageController(keepPage: true);
final PageController _pageController = PageController(keepPage: true, initialPage: pageIndex.value);
@override
bool get wantKeepAlive => true;
@@ -402,7 +405,15 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin
@override
void initState() {
super.initState();
pageIndex.listen((index) => _pageController.jumpToPage(index));
var lastPage = pageIndex.value;
pageIndex.listen((index) {
if(index == lastPage) {
return;
}
lastPage = index;
_pageController.jumpToPage(index);
});
}
@override
@@ -437,6 +448,10 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin
elements.add(TextSpan(
text: pages[pageIndex.value].name,
recognizer: pageStack.isNotEmpty ? (TapGestureRecognizer()..onTap = () {
if(inDialog) {
return;
}
for(var i = 0; i < pageStack.length; i++) {
Navigator.of(pageKey.currentContext!).pop();
final element = pageStack.removeLast();
@@ -461,6 +476,10 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin
elements.add(TextSpan(
text: innerPage,
recognizer: i == pageStack.length - 1 ? null : (TapGestureRecognizer()..onTap = () {
if(inDialog) {
return;
}
for(var j = 0; j < i - 1; j++) {
Navigator.of(pageKey.currentContext!).pop();
final element = pageStack.removeLast();

View File

@@ -1,12 +1,11 @@
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.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/pages.dart';
@@ -15,12 +14,14 @@ import 'package:path/path.dart' as path;
import 'package:reboot_launcher/src/widget/info_tile.dart';
class InfoPage extends RebootPage {
static late final List<InfoTile> _infoTiles;
static late List<InfoTile> _infoTiles;
static late List<_QuizEntry> _quizEntries;
static Object? initInfoTiles() {
try {
final directory = Directory("${assetsDirectory.path}\\info\\$currentLocale");
final map = SplayTreeMap<int, InfoTile>();
for(final entry in directory.listSync()) {
final faqDirectory = Directory("${assetsDirectory.path}\\info\\$currentLocale\\faq");
final infoTiles = SplayTreeMap<int, InfoTile>();
for(final entry in faqDirectory.listSync()) {
if(entry is File) {
final name = Uri.decodeQueryComponent(path.basename(entry.path));
final splitter = name.indexOf(".");
@@ -34,16 +35,42 @@ class InfoPage extends RebootPage {
}
final questionName = Uri.decodeQueryComponent(name.substring(splitter + 2));
map[index] = InfoTile(
infoTiles[index] = InfoTile(
title: Text(questionName),
content: Text(entry.readAsStringSync())
);
}
}
_infoTiles = map.values.toList(growable: false);
_infoTiles = infoTiles.values.toList(growable: false);
final questionsDirectory = Directory("${assetsDirectory.path}\\info\\$currentLocale\\questions");
final questions = SplayTreeMap<int, _QuizEntry>();
for(final entry in questionsDirectory.listSync()) {
if(entry is File) {
final name = Uri.decodeQueryComponent(path.basename(entry.path));
final splitter = name.indexOf(".");
if(splitter == -1) {
continue;
}
final index = int.tryParse(name.substring(0, splitter));
if(index == null) {
continue;
}
final questionName = Uri.decodeQueryComponent(name.substring(splitter + 2));
questions[index] = _QuizEntry(
question: questionName,
options: entry.readAsStringSync().split("\n")
);
}
}
_quizEntries = questions.values.toList(growable: false);
return null;
}catch(error) {
_infoTiles = [];
_quizEntries = [];
return error;
}
}
@@ -60,7 +87,7 @@ class InfoPage extends RebootPage {
String get iconAsset => "assets/images/info.png";
@override
bool hasButton(String? routeName) => false;
bool hasButton(String? pageName) => Get.find<SettingsController>().firstRun.value && pageName != null;
@override
RebootPageType get type => RebootPageType.info;
@@ -68,22 +95,14 @@ class InfoPage extends RebootPage {
class _InfoPageState extends RebootPageState<InfoPage> {
final SettingsController _settingsController = Get.find<SettingsController>();
RxInt _counter = RxInt(kDebugMode ? 0 : 180);
late bool _showButton;
late final Rxn<Widget> _quizPage;
@override
void initState() {
_showButton = _settingsController.firstRun.value;
if(_settingsController.firstRun.value) {
Timer.periodic(const Duration(seconds: 1), (timer) {
if (_counter.value <= 0) {
_settingsController.firstRun.value = false;
timer.cancel();
} else {
_counter.value = _counter.value - 1;
}
});
}
_quizPage = Rxn(_settingsController.firstRun.value ? _QuizRoute(
entries: InfoPage._quizEntries,
onSuccess: () => _quizPage.value = null
) : null);
super.initState();
}
@@ -92,28 +111,183 @@ class _InfoPageState extends RebootPageState<InfoPage> {
@override
Widget? get button {
if(!_showButton) {
return const SizedBox.shrink();
if(_quizPage.value == null) {
return null;
}
return Obx(() {
final totalSecondsLeft = _counter.value;
final minutesLeft = totalSecondsLeft ~/ 60;
final secondsLeft = totalSecondsLeft % 60;
final page = _quizPage.value;
if(page == null) {
return const SizedBox.shrink();
}
return SizedBox(
width: double.infinity,
height: 48,
child: Button(
onPressed: totalSecondsLeft <= 0 ? () {
_showButton = false;
pageIndex.value = RebootPageType.play.index;
} : null,
onPressed: () => Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
settings: RouteSettings(
name: translations.quiz
),
pageBuilder: (context, incoming, outgoing) => page
)),
child: Text(
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' : ''}'}"
translations.startQuiz
),
)
);
});
}
}
class _QuizRoute extends StatefulWidget {
final List<_QuizEntry> entries;
final void Function() onSuccess;
const _QuizRoute({
required this.entries,
required this.onSuccess
});
@override
State<_QuizRoute> createState() => _QuizRouteState();
}
class _QuizRouteState extends State<_QuizRoute> with AutomaticKeepAliveClientMixin {
final SettingsController _settingsController = Get.find<SettingsController>();
late final List<RxInt> _selectedIndexes = List.generate(widget.entries.length, (_) => RxInt(-1));
int _triesLeft = 3;
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Column(
children: [
Expanded(
child: ListView(
children: widget.entries.indexed.expand((entry) {
final selectedIndex = _selectedIndexes[entry.$1];
return [
Text(
"${entry.$1 + 1}. ${entry.$2.question}",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w600
),
),
const SizedBox(height: 12.0),
...entry.$2.options.indexed.map<Widget>((value) => Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Obx(() => RadioButton(
checked: value.$1 == selectedIndex.value,
content: Text(value.$2, textAlign: TextAlign.center),
onChanged: (_) => selectedIndex.value = value.$1
)),
)),
const SizedBox(height: 12.0)
];
}).toList()
),
),
const SizedBox(
height: 8.0,
),
SizedBox(
width: double.infinity,
height: 48,
child: Obx(() {
var clickable = true;
for(final index in _selectedIndexes) {
if(index.value == -1) {
clickable = false;
break;
}
}
return Button(
onPressed: clickable ? () async {
if(_triesLeft <= 0) {
return;
}
var right = 0;
final total = widget.entries.length;
for(var i = 0; i < total; i++) {
final selectedIndex = _selectedIndexes[i].value;
final correctIndex = widget.entries[i].correctIndex;
if(selectedIndex == correctIndex) {
right++;
}
}
if(right == total) {
widget.onSuccess();
showInfoBar(
translations.quizSuccess,
severity: InfoBarSeverity.success
);
_settingsController.firstRun.value = false;
Navigator.of(context).pop();
pageIndex.value = RebootPageType.play.index;
return;
}
switch(--_triesLeft) {
case 0:
showInfoBar(
translations.quizFailed(
right,
total,
translations.quizZeroTriesLeft
),
severity: InfoBarSeverity.error
);
await Future.delayed(const Duration(seconds: 1));
exit(0);
case 1:
showInfoBar(
translations.quizFailed(
right,
total,
translations.quizOneTryLeft
),
severity: InfoBarSeverity.error
);
break;
case 2:
showInfoBar(
translations.quizFailed(
right,
total,
translations.quizTwoTriesLeft
),
severity: InfoBarSeverity.error
);
break;
}
} : null,
child: Text(translations.checkQuiz),
);
},
),
)
],
);
}
}
class _QuizEntry {
final String question;
final List<String> options;
late final int correctIndex;
_QuizEntry({required this.question, required this.options}) {
final correct = options.first;
options.shuffle();
correctIndex = options.indexOf(correct);
}
}

View File

@@ -29,7 +29,7 @@ class HostPage extends RebootPage {
const HostPage({Key? key}) : super(key: key);
@override
String get name => "Host";
String get name => translations.hostName;
@override
String get iconAsset => "assets/images/host.png";
@@ -289,51 +289,23 @@ class _HostingPageState extends RebootPageState<HostPage> {
title: Text(translations.settingsServerOptionsName),
subtitle: Text(translations.settingsServerOptionsSubtitle),
children: [
Obx(() => SettingTile(
SettingTile(
icon: Icon(
FluentIcons.window_console_20_regular
),
title: Text(translations.hostHeadlessName),
subtitle: Text(translations.hostHeadlessDescription),
contentWidth: null,
content: Row(
children: [
Text(
_hostingController.headless.value ? translations.on : translations.off
),
const SizedBox(
width: 16.0
),
ToggleSwitch(
checked: _hostingController.headless.value,
onChanged: (value) => _hostingController.headless.value = value
),
],
),
)),
Obx(() => SettingTile(
icon: Icon(
FluentIcons.desktop_edit_24_regular
),
title: Text(translations.hostVirtualDesktopName),
subtitle: Text(translations.hostVirtualDesktopDescription),
contentWidth: null,
content: Row(
children: [
Text(
_hostingController.virtualDesktop.value ? translations.on : translations.off
),
const SizedBox(
width: 16.0
),
ToggleSwitch(
checked: _hostingController.virtualDesktop.value,
onChanged: (value) => _hostingController.virtualDesktop.value = value
),
],
),
)),
Obx(() => SettingTile(
title: Text(translations.gameServerTypeName),
subtitle: Text(translations.gameServerTypeDescription),
content: Obx(() => DropDownButton(
onOpen: () => inDialog = true,
onClose: () => inDialog = false,
leading: Text(_hostingController.type.value.translatedName),
items: GameServerType.values.map((entry) => MenuFlyoutItem(
text: Text(entry.translatedName),
onPressed: () => _hostingController.type.value = entry
)).toList()
)),
),
SettingTile(
icon: Icon(
FluentIcons.arrow_reset_24_regular
),
@@ -348,13 +320,13 @@ class _HostingPageState extends RebootPageState<HostPage> {
const SizedBox(
width: 16.0
),
ToggleSwitch(
Obx(() => ToggleSwitch(
checked: _hostingController.autoRestart.value,
onChanged: (value) => _hostingController.autoRestart.value = value
),
)),
],
),
)),
),
SettingTile(
icon: Icon(
fluentUi.FluentIcons.number_field

View File

@@ -6,6 +6,7 @@ import 'package:ffi/ffi.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/scheduler.dart';
import 'package:win32/win32.dart';
import 'package:file_picker/file_picker.dart';
final RegExp _winBuildRegex = RegExp(r'(?<=\(Build )(.*)(?=\))');
@@ -23,6 +24,22 @@ bool get isWin11 {
return intBuild != null && intBuild > 22000;
}
Future<String?> openFolderPicker(String title) async =>
await FilePicker.platform.getDirectoryPath(dialogTitle: title);
Future<String?> openFilePicker(String extension) async {
var result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowMultiple: false,
allowedExtensions: [extension]
);
if(result == null || result.files.isEmpty){
return null;
}
return result.files.first.path;
}
bool get isDarkMode =>
SchedulerBinding.instance.platformDispatcher.platformBrightness.isDark;

View File

@@ -1,17 +0,0 @@
import 'package:file_picker/file_picker.dart';
Future<String?> openFolderPicker(String title) async =>
await FilePicker.platform.getDirectoryPath(dialogTitle: title);
Future<String?> openFilePicker(String extension) async {
var result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowMultiple: false,
allowedExtensions: [extension]
);
if(result == null || result.files.isEmpty){
return null;
}
return result.files.first.path;
}

View File

@@ -1,6 +1,7 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_gen/gen_l10n/reboot_localizations.dart';
import 'package:intl/intl.dart';
import 'package:reboot_common/common.dart';
AppLocalizations? _translations;
bool _init = false;
@@ -19,3 +20,16 @@ void loadTranslations(BuildContext context) {
}
String get currentLocale => Intl.getCurrentLocale().split("_")[0];
extension GameServerTypeExtension on GameServerType {
String get translatedName {
switch(this) {
case GameServerType.headless:
return translations.gameServerTypeHeadless;
case GameServerType.virtualWindow:
return translations.gameServerTypeVirtualWindow;
case GameServerType.window:
return translations.gameServerTypeWindow;
}
}
}

View File

@@ -38,6 +38,7 @@ class _AddServerVersionState extends State<AddServerVersion> {
late Future _fetchFuture;
late Future _diskFuture;
Isolate? _isolate;
SendPort? _downloadPort;
Object? _error;
StackTrace? _stackTrace;
@@ -65,6 +66,7 @@ class _AddServerVersionState extends State<AddServerVersion> {
void _cancelDownload() {
Process.run('${assetsDirectory.path}\\build\\stop.bat', []);
_downloadPort?.send(kStopBuildDownloadSignal);
_isolate?.kill(priority: Isolate.immediate);
}
@override
@@ -147,7 +149,7 @@ class _AddServerVersionState extends State<AddServerVersion> {
);
final errorPort = ReceivePort();
errorPort.listen((message) => _onDownloadError(message, null));
await Isolate.spawn(
_isolate = await Isolate.spawn(
downloadArchiveBuild,
options,
onError: errorPort.sendPort,

View File

@@ -1,6 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:reboot_launcher/src/util/picker.dart';
import 'package:reboot_launcher/src/util/os.dart';
class FileSelector extends StatefulWidget {
final String placeholder;

View File

@@ -68,9 +68,9 @@ class _LaunchButtonState extends State<LaunchButton> {
void _setStarted(bool hosting, bool started) => hosting ? _hostingController.started.value = started : _gameController.started.value = started;
Future<void> _toggle({bool? host, bool forceGUI = false}) async {
Future<void> _toggle({bool? host}) async {
host ??= widget.host;
log("[${host ? 'HOST' : 'GAME'}] Toggling state(forceGUI: $forceGUI)");
log("[${host ? 'HOST' : 'GAME'}] Toggling state");
if (host ? _hostingController.started() : _gameController.started()) {
log("[${host ? 'HOST' : 'GAME'}] User asked to close the current instance");
_onStop(
@@ -100,7 +100,7 @@ class _LaunchButtonState extends State<LaunchButton> {
}
try {
final executable = version.gameExecutable;
final executable = await version.shippingExecutable;
if(executable == null){
log("[${host ? 'HOST' : 'GAME'}] No executable found");
_onStop(
@@ -120,12 +120,11 @@ class _LaunchButtonState extends State<LaunchButton> {
return;
}
log("[${host ? 'HOST' : 'GAME'}] Backend works");
final headless = !forceGUI && _hostingController.headless.value;
final virtualDesktop = _hostingController.virtualDesktop.value;
log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($headless)");
final linkedHostingInstance = await _startMatchMakingServer(version, host, headless, virtualDesktop, false);
final serverType = _hostingController.type.value;
log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($serverType)");
final linkedHostingInstance = await _startMatchMakingServer(version, host, serverType, false);
log("[${host ? 'HOST' : 'GAME'}] Implicit game server result: $linkedHostingInstance");
await _startGameProcesses(version, host, headless, virtualDesktop, linkedHostingInstance);
await _startGameProcesses(version, host, serverType, linkedHostingInstance);
if(!host) {
_showLaunchingGameClientWidget();
}
@@ -142,14 +141,14 @@ class _LaunchButtonState extends State<LaunchButton> {
}
}
Future<GameInstance?> _startMatchMakingServer(FortniteVersion version, bool host, bool headless, bool virtualDesktop, bool forceLinkedHosting) async {
Future<GameInstance?> _startMatchMakingServer(FortniteVersion version, bool host, GameServerType hostType, bool forceLinkedHosting) async {
log("[${host ? 'HOST' : 'GAME'}] Checking if a server needs to be started automatically...");
if(host){
log("[${host ? 'HOST' : 'GAME'}] The user clicked on Start hosting, so it's not necessary");
return null;
}
if(_backendController.type.value != ServerType.embedded || !isLocalHost(_backendController.gameServerAddress.text)) {
if(_backendController.type.value == ServerType.embedded && !isLocalHost(_backendController.gameServerAddress.text)) {
log("[${host ? 'HOST' : 'GAME'}] Backend is not set to embedded and/or not pointing to the local game server");
return null;
}
@@ -166,7 +165,7 @@ class _LaunchButtonState extends State<LaunchButton> {
}
log("[${host ? 'HOST' : 'GAME'}] Starting implicit game server...");
final instance = await _startGameProcesses(version, true, headless, virtualDesktop, null);
final instance = await _startGameProcesses(version, true, hostType, null);
log("[${host ? 'HOST' : 'GAME'}] Started implicit game server...");
_setStarted(true, true);
log("[${host ? 'HOST' : 'GAME'}] Set implicit game server as started");
@@ -195,7 +194,7 @@ class _LaunchButtonState extends State<LaunchButton> {
return result;
}
Future<GameInstance?> _startGameProcesses(FortniteVersion version, bool host, bool headless, bool virtualDesktop, GameInstance? linkedHosting) async {
Future<GameInstance?> _startGameProcesses(FortniteVersion version, bool host, GameServerType hostType, GameInstance? linkedHosting) async {
log("[${host ? 'HOST' : 'GAME'}] Starting game process...");
log("[${host ? 'HOST' : 'GAME'}] Starting paused launcher...");
final launcherProcess = await _createPausedProcess(version, version.launcherExecutable);
@@ -205,9 +204,9 @@ class _LaunchButtonState extends State<LaunchButton> {
final eacProcess = await _createPausedProcess(version, version.eacExecutable);
log("[${host ? 'HOST' : 'GAME'}] Started paused eac: $eacProcess");
final executable = host && headless ? await version.headlessGameExecutable : version.gameExecutable;
final executable = await version.shippingExecutable;
log("[${host ? 'HOST' : 'GAME'}] Using game path: ${executable?.path}");
final gameProcess = await _createGameProcess(version, executable!, host, headless, virtualDesktop, linkedHosting);
final gameProcess = await _createGameProcess(version, executable!, host, hostType, linkedHosting);
if(gameProcess == null) {
log("[${host ? 'HOST' : 'GAME'}] No game process was created");
return null;
@@ -219,7 +218,7 @@ class _LaunchButtonState extends State<LaunchButton> {
gamePid: gameProcess,
launcherPid: launcherProcess,
eacPid: eacProcess,
hosting: host,
serverType: host ? hostType : null,
child: linkedHosting
);
if(host){
@@ -233,20 +232,21 @@ class _LaunchButtonState extends State<LaunchButton> {
return instance;
}
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, GameServerType hostType, GameInstance? linkedHosting) async {
log("[${host ? 'HOST' : 'GAME'}] Generating instance args...");
final gameArgs = createRebootArgs(
_gameController.username.text,
_gameController.password.text,
host,
_hostingController.headless.value,
hostType,
false,
""
);
log("[${host ? 'HOST' : 'GAME'}] Generated game args: $gameArgs");
log("[${host ? 'HOST' : 'GAME'}] Generated game args: ${gameArgs.join(" ")}");
final gameProcess = await startProcess(
executable: executable,
args: gameArgs,
wrapProcess: false,
useTempBatch: false,
name: "${version.name}-${host ? 'HOST' : 'GAME'}"
);
void onGameOutput(String line, bool error) {
@@ -259,8 +259,8 @@ class _LaunchButtonState extends State<LaunchButton> {
onTokenError: () => _onStop(reason: _StopReason.tokenError),
onBuildCorrupted: () => _onStop(reason: _StopReason.corruptedVersionError),
onLoggedIn: () =>_onLoggedIn(host),
onMatchEnd: () => _onMatchEnd(version, virtualDesktop),
onDisplayAttached: () => _onDisplayAttached(headless, virtualDesktop, version)
onMatchEnd: () => _onMatchEnd(version),
onDisplayAttached: () => _onDisplayAttached(host, hostType, version)
);
}
gameProcess.stdOutput.listen((line) => onGameOutput(line, false));
@@ -272,24 +272,10 @@ class _LaunchButtonState extends State<LaunchButton> {
return;
}
if(!host || instance.launched) {
log("[${host ? 'HOST' : 'GAME'}] Called exit code(headless: $headless, launched: ${instance.launched}): stop signal");
_onStop(
reason: _StopReason.exitCode,
host: host
);
return;
}
log("[${host ? 'HOST' : 'GAME'}] Called exit code(headless: $headless, launched: ${instance.launched}): restart signal");
instance.launched = true;
await _onStop(
log("[${host ? 'HOST' : 'GAME'}] Called exit code(launched: ${instance.launched}): stop signal");
_onStop(
reason: _StopReason.exitCode,
host: true
);
await _toggle(
forceGUI: true,
host: true
host: host
);
});
return gameProcess.pid;
@@ -302,7 +288,7 @@ class _LaunchButtonState extends State<LaunchButton> {
final process = await startProcess(
executable: file,
wrapProcess: false,
useTempBatch: false,
name: "${version.name}-${basenameWithoutExtension(file.path)}"
);
final pid = process.pid;
@@ -310,8 +296,8 @@ class _LaunchButtonState extends State<LaunchButton> {
return pid;
}
Future<void> _onDisplayAttached(bool headless, bool virtualDesktop, FortniteVersion version) async {
if(!headless && virtualDesktop) {
Future<void> _onDisplayAttached(bool host, GameServerType type, FortniteVersion version) async {
if(host && type == GameServerType.virtualWindow) {
final hostingInstance = _hostingController.instance.value;
if(hostingInstance != null && !hostingInstance.movedToVirtualDesktop) {
hostingInstance.movedToVirtualDesktop = true;
@@ -346,7 +332,7 @@ class _LaunchButtonState extends State<LaunchButton> {
}
}
void _onMatchEnd(FortniteVersion version, bool virtualDesktop) {
void _onMatchEnd(FortniteVersion version) {
if(_hostingController.autoRestart.value) {
final notification = LocalNotification(
title: translations.gameServerEnd,
@@ -536,7 +522,7 @@ class _LaunchButtonState extends State<LaunchButton> {
if(child != null) {
await _onStop(
reason: reason,
host: child.hosting
host: child.serverType != null
);
}
}