mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
9.1.4
This commit is contained in:
BIN
gui/assets/images/backend.png
Normal file
BIN
gui/assets/images/backend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 522 B |
2
gui/assets/info/en/faq/12. Can I get skins in game
Normal file
2
gui/assets/info/en/faq/12. Can I get skins in game
Normal file
@@ -0,0 +1,2 @@
|
||||
No, skins don't work in Reboot.
|
||||
This is because Epic asked us to remove them.
|
||||
@@ -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".
|
||||
3
gui/assets/info/en/questions/1. What is Project Reboot
Normal file
3
gui/assets/info/en/questions/1. What is Project Reboot
Normal file
@@ -0,0 +1,3 @@
|
||||
A Fortnite game server created by Milxnor
|
||||
A Minecraft game server created by Chief Keef
|
||||
I don't know
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
3
gui/assets/info/en/questions/3. What is 127.0.0.1
Normal file
3
gui/assets/info/en/questions/3. What is 127.0.0.1
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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?
|
||||
3
gui/assets/info/en/questions/9. What is a backend
Normal file
3
gui/assets/info/en/questions/9. What is a backend
Normal 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
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/
|
||||
@@ -15,7 +15,6 @@ DisableProgramGroupPage=yes
|
||||
OutputBaseFilename={{OUTPUT_BASE_FILENAME}}
|
||||
Compression=zip
|
||||
SolidCompression=yes
|
||||
SetupIconFile={{SETUP_ICON_FILE}}
|
||||
WizardStyle=modern
|
||||
PrivilegesRequired=admin
|
||||
ArchitecturesAllowed=x64
|
||||
|
||||
Reference in New Issue
Block a user