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

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

View File

@@ -0,0 +1,2 @@
No, skins don't work in Reboot.
This is because Epic asked us to remove them.

View File

@@ -1,2 +1,3 @@
As explained in the "What is the Unreal Engine console?" section, the "Play" button doesn't work in many Fortnite versions.
Instead, you need to click the key assigned to the Unreal Engine console, by default F8 or the tilde(the button above tab), and type open 127.0.0.1
Instead, you need to click the key assigned to the Unreal Engine console, by default F8 or the tilde(the button above tab), and type open 127.0.0.1
If that doesn't work, go to the Host page and make sure that you are running a game server by clicking "Start Hosting".

View File

@@ -0,0 +1,3 @@
A Fortnite game server created by Milxnor
A Minecraft game server created by Chief Keef
I don't know

View File

@@ -0,0 +1,3 @@
A version I downloaded from the launcher crashes
A version of Fortnite I downloaded from the internet crashes
I can't enter in game from the lobby

View File

@@ -0,0 +1,3 @@
Click the Unreal Engine Key(F8 or the tilde by default) and type open 127.0.0.1
Report a bug on Discord because there is a clearly a bug
Cry

View File

@@ -0,0 +1,3 @@
Switch my backend to embedded and try again before reporting a bug on Discord
Report a bug on Discord immediately and flood the chat
Cry

View File

@@ -0,0 +1,3 @@
No, it's not possible
Yes, I just have to send Auties a message
Yes, let me ask on Discord how to get them

View File

@@ -0,0 +1,3 @@
Only the ones I can download from the launcher
Any season between season 0 and 15 works
Depends on the day of the week

View File

@@ -0,0 +1,3 @@
The address of your local machine where the game server will be hosted
Playboi Carti's hiding spot
I don't know

View File

@@ -0,0 +1,3 @@
A Fortnite window used to host matches instead of playing the game
A standalone piece of software to host matches
A stolen Epic games server we got by paying Tim Apple

View File

@@ -0,0 +1,3 @@
No, I will be kicked back to the lobby, unless I've joined someone else's server
Yes, one will be started automatically as soon as I click play
Yes, it's not needed to play

View File

@@ -0,0 +1,3 @@
A game server that doesn't render the Fortnite window to save memory
A game server hosted on someone else's PC
A game server running in Milxnor's mind

View File

@@ -0,0 +1,3 @@
Some seasons don't support a headless server, so the other frozen Fortnite is the game server
The launcher is bugged: I must report a bug on Discord
Auties hardcoded a crypto miner in Fortnite: by opening two games he gets more cash

View File

@@ -0,0 +1,3 @@
I can open port 7777 on my router or use a private VPN service
I don't have to do anything, it's all automatic
What are you talking about?

View File

@@ -0,0 +1,3 @@
A piece of software to emulate Epic Games' servers for authentication
A piece of software that makes the launcher work correctly
I don't know

View File

@@ -31,10 +31,6 @@
"hostGameServerPasswordDescription": "The password of your game server, if you need one",
"hostGameServerDiscoverableName": "Discoverable",
"hostGameServerDiscoverableDescription": "Make your server available to other players on the server browser",
"hostHeadlessName": "Headless",
"hostHeadlessDescription": "Runs Fortnite without graphics to optimize resources usage, may not work for old seasons",
"hostVirtualDesktopName": "Virtual desktop",
"hostVirtualDesktopDescription": "Runs Fortnite in a virtual desktop if headless is not supported",
"hostAutomaticRestartName": "Automatic restart",
"hostAutomaticRestartDescription": "Automatically restarts your game server when the match ends",
"hostShareName": "Share",
@@ -298,5 +294,18 @@
"updateAvailableAction": "Download",
"gameServerEnd": "The match ended",
"gameServerRestart": "The server will restart in {timeInSeconds} seconds",
"gameServerShutdown": "The server will shutdown in {timeInSeconds} seconds"
"gameServerShutdown": "The server will shutdown in {timeInSeconds} seconds",
"quiz": "Quiz",
"startQuiz": "I have read the instructions",
"checkQuiz": "Check answers",
"quizFailed": "You got a score of {right}/{total}: you have {tries} left",
"quizSuccess": "You got all the questions right: thanks for reading the instructions!",
"quizZeroTriesLeft": "zero tries",
"quizOneTryLeft": "one try",
"quizTwoTriesLeft": "two tries",
"gameServerTypeName": "Type",
"gameServerTypeDescription": "The type of game server to use",
"gameServerTypeHeadless": "Background process",
"gameServerTypeVirtualWindow": "Virtual window",
"gameServerTypeWindow": "Normal window"
}

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
);
}
}

View File

@@ -1,6 +1,6 @@
name: reboot_launcher
description: Graphical User Interface for Project Reboot
version: "9.1.3"
version: "9.1.4"
publish_to: 'none'
@@ -99,4 +99,5 @@ flutter:
- assets/backend/public/
- assets/backend/responses/
- assets/build/
- assets/info/en/
- assets/info/en/faq/
- assets/info/en/questions/

View File

@@ -15,7 +15,6 @@ DisableProgramGroupPage=yes
OutputBaseFilename={{OUTPUT_BASE_FILENAME}}
Compression=zip
SolidCompression=yes
SetupIconFile={{SETUP_ICON_FILE}}
WizardStyle=modern
PrivilegesRequired=admin
ArchitecturesAllowed=x64