Latest version

This commit is contained in:
Alessandro Autiero
2022-12-30 03:03:59 +01:00
parent 966b4b33fd
commit 013d15d7ff
43 changed files with 519 additions and 244 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -19,11 +19,7 @@ late String dll;
late FortniteVersion version; late FortniteVersion version;
late bool autoRestart; late bool autoRestart;
void main(List<String> args){ void main(List<String> args) async {
handleCLI(args);
}
Future<void> handleCLI(List<String> args) async {
stdout.writeln("Reboot Launcher"); stdout.writeln("Reboot Launcher");
stdout.writeln("Wrote by Auties00"); stdout.writeln("Wrote by Auties00");
stdout.writeln("Version 5.3"); stdout.writeln("Version 5.3");

View File

@@ -4,7 +4,6 @@ import 'dart:io';
import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart' import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart'
show WinDesktopWindow; show WinDesktopWindow;
import 'package:dart_vlc/dart_vlc.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
@@ -21,17 +20,10 @@ import 'package:window_manager/window_manager.dart';
final GlobalKey appKey = GlobalKey(); final GlobalKey appKey = GlobalKey();
void main(List<String> args) async { void main() async {
await Directory(safeBinariesDirectory) await Directory(safeBinariesDirectory)
.create(recursive: true); .create(recursive: true);
if(args.isNotEmpty){
handleCLI(args);
return;
}
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
DartVLC.initialize();
await SystemTheme.accentColor.load(); await SystemTheme.accentColor.load();
await GetStorage.init("game"); await GetStorage.init("game");
await GetStorage.init("server"); await GetStorage.init("server");

View File

@@ -1,3 +1,4 @@
import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
@@ -6,6 +7,7 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart'; import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/model/game_instance.dart';
import 'package:reboot_launcher/src/model/game_type.dart'; import 'package:reboot_launcher/src/model/game_type.dart';
class GameController extends GetxController { class GameController extends GetxController {
@@ -15,11 +17,10 @@ class GameController extends GetxController {
late final Rx<List<FortniteVersion>> versions; late final Rx<List<FortniteVersion>> versions;
late final Rxn<FortniteVersion> _selectedVersion; late final Rxn<FortniteVersion> _selectedVersion;
late final Rx<GameType> type; late final Rx<GameType> type;
late final HashMap<GameType, GameInstance> gameInstancesMap;
late final RxBool started; late final RxBool started;
late bool updated;
Future? updater; Future? updater;
Process? gameProcess;
Process? launcherProcess;
Process? eacProcess;
GameController() { GameController() {
_storage = GetStorage("game"); _storage = GetStorage("game");
@@ -40,19 +41,22 @@ class GameController extends GetxController {
type = Rx(GameType.values.elementAt(_storage.read("type") ?? 0)); type = Rx(GameType.values.elementAt(_storage.read("type") ?? 0));
type.listen((value) { type.listen((value) {
_storage.write("type", value.index); _storage.write("type", value.index);
username.text = _storage.read("${type.value == GameType.client ? 'game' : 'host'}_username") ?? ""; username.text = _readUsername();
}); });
username = TextEditingController(text: _storage.read("${type.value == GameType.client ? 'game' : 'host'}_username") ?? ""); username = TextEditingController(text: _readUsername());
username.addListener(() => _storage.write("${type.value == GameType.client ? 'game' : 'host'}_username", username.text)); username.addListener(() => _storage.write("${type.value == GameType.client ? 'game' : 'host'}_username", username.text));
gameInstancesMap= HashMap();
started = RxBool(false); started = RxBool(false);
updated = false;
} }
void kill() { String _readUsername() {
gameProcess?.kill(ProcessSignal.sigabrt); var client = type.value == GameType.client;
launcherProcess?.kill(ProcessSignal.sigabrt); return _storage.read("${client ? 'game' : 'host'}_username") ?? (client ? "" : "HostingServer");
eacProcess?.kill(ProcessSignal.sigabrt);
} }
FortniteVersion? getVersionByName(String name) { FortniteVersion? getVersionByName(String name) {
@@ -86,6 +90,8 @@ class GameController extends GetxController {
Rxn<FortniteVersion> get selectedVersionObs => _selectedVersion; Rxn<FortniteVersion> get selectedVersionObs => _selectedVersion;
GameInstance? get currentGameInstance => gameInstancesMap[type()];
set selectedVersion(FortniteVersion? version) { set selectedVersion(FortniteVersion? version) {
_selectedVersion(version); _selectedVersion(version);
_storage.write("version", version?.name); _storage.write("version", version?.name);

View File

@@ -1,7 +1,7 @@
import 'package:dart_vlc/dart_vlc.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/src/model/tutorial_page.dart';
import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/os.dart';
import 'dart:ui'; import 'dart:ui';
@@ -13,11 +13,13 @@ class SettingsController extends GetxController {
late final TextEditingController authDll; late final TextEditingController authDll;
late final TextEditingController matchmakingIp; late final TextEditingController matchmakingIp;
late final Rx<PaneDisplayMode> displayType; late final Rx<PaneDisplayMode> displayType;
late final RxBool doNotAskAgain;
late Rx<TutorialPage> tutorialPage;
late double width; late double width;
late double height; late double height;
late double? offsetX; late double? offsetX;
late double? offsetY; late double? offsetY;
Player? player; late double scrollingDistance;
SettingsController() { SettingsController() {
_storage = GetStorage("settings"); _storage = GetStorage("settings");
@@ -31,11 +33,18 @@ class SettingsController extends GetxController {
_storage.write("ip", text); _storage.write("ip", text);
}); });
doNotAskAgain = RxBool(_storage.read("do_not_ask_again") ?? false);
doNotAskAgain.listen((value) => _storage.write("do_not_ask_again", value));
width = _storage.read("width") ?? window.physicalSize.width; width = _storage.read("width") ?? window.physicalSize.width;
height = _storage.read("height") ?? window.physicalSize.height; height = _storage.read("height") ?? window.physicalSize.height;
offsetX = _storage.read("offset_x"); offsetX = _storage.read("offset_x");
offsetY = _storage.read("offset_y"); offsetY = _storage.read("offset_y");
displayType = Rx(PaneDisplayMode.top); displayType = Rx(PaneDisplayMode.top);
scrollingDistance = 0.0;
tutorialPage = Rx(TutorialPage.start);
} }
TextEditingController _createController(String key, String name) { TextEditingController _createController(String key, String name) {

View File

@@ -29,7 +29,7 @@ class GenericDialog extends AbstractDialog {
), ),
ContentDialog( ContentDialog(
style: ContentDialogThemeData( style: ContentDialogThemeData(
padding: padding ?? const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 5.0) padding: padding ?? const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 5.0)
), ),
content: header, content: header,

View File

@@ -1,6 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart'; import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart'; import 'package:reboot_launcher/src/dialog/dialog.dart';
@@ -159,7 +159,7 @@ extension ServerControllerDialog on ServerController {
builder: (context) => builder: (context) =>
FutureBuilderDialog( FutureBuilderDialog(
future: Future.wait([ future: Future.wait([
pingSelf(port.text), compute(pingSelf, port.text),
Future.delayed(const Duration(seconds: 1)) Future.delayed(const Duration(seconds: 1))
]), ]),
loadingMessage: "Pinging ${type().id} server...", loadingMessage: "Pinging ${type().id} server...",

View File

@@ -105,7 +105,7 @@ Jaguar _createServer(String Function() ipQuery) {
server.getJson("/fortnite/api/game/v2/privacy/account/:accountId", getPrivacy); server.getJson("/fortnite/api/game/v2/privacy/account/:accountId", getPrivacy);
server.postJson("/fortnite/api/game/v2/privacy/account/:accountId", postPrivacy); server.postJson("/fortnite/api/game/v2/privacy/account/:accountId", postPrivacy);
return _addLoggingCapabilities(server); return server;
} }
Jaguar _createMatchmaker(){ Jaguar _createMatchmaker(){
var server = Jaguar(address: "127.0.0.1", port: 8080); var server = Jaguar(address: "127.0.0.1", port: 8080);

View File

@@ -0,0 +1,15 @@
import 'dart:io';
class GameInstance {
final Process gameProcess;
final Process? launcherProcess;
final Process? eacProcess;
GameInstance(this.gameProcess, this.launcherProcess, this.eacProcess);
void kill() {
gameProcess.kill(ProcessSignal.sigabrt);
launcherProcess?.kill(ProcessSignal.sigabrt);
eacProcess?.kill(ProcessSignal.sigabrt);
}
}

View File

@@ -0,0 +1,5 @@
enum TutorialPage {
start,
someoneElse,
yourOwn
}

View File

@@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:ui'; import 'dart:ui';
import 'package:bitsdojo_window/bitsdojo_window.dart' hide WindowBorder; import 'package:bitsdojo_window/bitsdojo_window.dart' hide WindowBorder;
@@ -18,6 +17,7 @@ import 'package:reboot_launcher/src/widget/os/window_buttons.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import '../controller/settings_controller.dart'; import '../controller/settings_controller.dart';
import '../model/tutorial_page.dart';
import 'info_page.dart'; import 'info_page.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@@ -31,7 +31,6 @@ class _HomePageState extends State<HomePage> with WindowListener {
static const double _headerSize = 48.0; static const double _headerSize = 48.0;
static const double _sectionSize = 100.0; static const double _sectionSize = 100.0;
static const double _defaultPadding = 12.0; static const double _defaultPadding = 12.0;
static const double _openMenuSize = 320.0;
static const int _headerButtonCount = 3; static const int _headerButtonCount = 3;
static const int _sectionButtonCount = 4; static const int _sectionButtonCount = 4;
@@ -45,6 +44,7 @@ class _HomePageState extends State<HomePage> with WindowListener {
final Rxn<List<NavigationPaneItem>> _searchItems = Rxn(); final Rxn<List<NavigationPaneItem>> _searchItems = Rxn();
final RxBool _focused = RxBool(true); final RxBool _focused = RxBool(true);
final RxInt _index = RxInt(0); final RxInt _index = RxInt(0);
bool _navigated = false;
bool _shouldMaximize = false; bool _shouldMaximize = false;
@@ -125,7 +125,11 @@ class _HomePageState extends State<HomePage> with WindowListener {
child: Obx(() => Stack( child: Obx(() => Stack(
children: [ children: [
_createNavigationView(), _createNavigationView(),
_createTitleBar(), if(_settingsController.displayType() == PaneDisplayMode.top)
Align(
alignment: Alignment.topRight,
child: WindowTitleBar(focused: _focused())
),
if(_settingsController.displayType() == PaneDisplayMode.top) if(_settingsController.displayType() == PaneDisplayMode.top)
_createTopDisplayGestures(), _createTopDisplayGestures(),
if(_focused() && isWin11) if(_focused() && isWin11)
@@ -161,49 +165,82 @@ class _HomePageState extends State<HomePage> with WindowListener {
child: child child: child
); );
NavigationView _createNavigationView() => NavigationView( NavigationView _createNavigationView() {
paneBodyBuilder: (body) => _createPage(body), return NavigationView(
pane: NavigationPane( paneBodyBuilder: (body) => _createPage(body),
size: const NavigationPaneSize( pane: NavigationPane(
topHeight: _headerSize size: const NavigationPaneSize(
topHeight: _headerSize
),
selected: _selectedIndex,
onChanged: _onIndexChanged,
displayMode: _settingsController.displayType(),
items: _createItems(),
indicator: const EndNavigationIndicator(),
footerItems: _createFooterItems(),
header: _settingsController.displayType() != PaneDisplayMode.open ? null : const SizedBox(height: _defaultPadding),
autoSuggestBox: _createAutoSuggestBox(),
autoSuggestBoxReplacement: _settingsController.displayType() == PaneDisplayMode.top ? null : const Icon(FluentIcons.search),
), ),
selected: _selectedIndex, onOpenSearch: () => _searchFocusNode.requestFocus(),
onChanged: (index) { transitionBuilder: _settingsController.displayType() == PaneDisplayMode.top ? null : (child, animation) => child
_settingsController.player?.pause(); );
_index.value = index; }
},
displayMode: _settingsController.displayType(),
indicator: const EndNavigationIndicator(),
items: _createItems(),
footerItems: _createFooterItems(),
header: _settingsController.displayType() != PaneDisplayMode.open ? null : const SizedBox(height: _defaultPadding),
autoSuggestBox: _settingsController.displayType() == PaneDisplayMode.top ? null : TextBox(
key: _searchKey,
controller: _searchController,
placeholder: 'Search',
focusNode: _searchFocusNode
),
autoSuggestBoxReplacement: _settingsController.displayType() == PaneDisplayMode.top ? null : const Icon(FluentIcons.search),
),
onOpenSearch: () => _searchFocusNode.requestFocus(),
transitionBuilder: _settingsController.displayType() == PaneDisplayMode.top ? null : (child, animation) => child
);
RenderObjectWidget _createPage(Widget? body) => Padding( void _onIndexChanged(int index) {
padding: _createPagePadding(), _index.value = index;
child: body _navigated = true;
); }
EdgeInsets _createPagePadding() { TextBox? _createAutoSuggestBox() {
if (_settingsController.displayType() == PaneDisplayMode.top) { if (_settingsController.displayType() == PaneDisplayMode.top) {
return const EdgeInsets.all(_defaultPadding); return null;
} }
return const EdgeInsets.only( return TextBox(
top: 32, key: _searchKey,
left: _defaultPadding, controller: _searchController,
right: _defaultPadding, placeholder: 'Search',
bottom: _defaultPadding focusNode: _searchFocusNode
);
}
RenderObjectWidget _createPage(Widget? body) {
if(_settingsController.displayType() == PaneDisplayMode.top){
return Padding(
padding: const EdgeInsets.all(_defaultPadding),
child: body
);
}
return Column(
children: [
Row(
children: [
Expanded(
child: _createWindowGestures(
child: Container(
height: _headerSize,
color: Colors.transparent
)
)
),
WindowTitleBar(focused: _focused())
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: _defaultPadding,
right: _defaultPadding,
bottom: _defaultPadding
),
child: body
)
)
],
); );
} }
@@ -232,7 +269,8 @@ class _HomePageState extends State<HomePage> with WindowListener {
PaneItem( PaneItem(
title: const Text("Tutorial"), title: const Text("Tutorial"),
icon: const Icon(FluentIcons.info), icon: const Icon(FluentIcons.info),
body: const InfoPage() body: const InfoPage(),
onTap: _onTutorial
) )
]; ];
@@ -259,10 +297,22 @@ class _HomePageState extends State<HomePage> with WindowListener {
PaneItem( PaneItem(
title: const Text("Tutorial"), title: const Text("Tutorial"),
icon: const Icon(FluentIcons.info), icon: const Icon(FluentIcons.info),
body: const InfoPage() body: const InfoPage(),
onTap: _onTutorial
) )
]; ];
void _onTutorial() {
if(!_navigated){
setState(() {
_settingsController.tutorialPage.value = TutorialPage.start;
_settingsController.scrollingDistance = 0;
});
}
_navigated = false;
}
bool _calculateSize() { bool _calculateSize() {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_settingsController.saveWindowSize(); _settingsController.saveWindowSize();
@@ -288,35 +338,5 @@ class _HomePageState extends State<HomePage> with WindowListener {
return true; return true;
} }
Widget _createTitleBar() => Align(
alignment: Alignment.topRight,
child: _createTitleBarContent(),
);
Widget _createTitleBarContent() {
if(_settingsController.displayType() == PaneDisplayMode.top) {
return WindowTitleBar(focused: _focused());
}
return Row(
children: [
SizedBox(
width: _settingsController.displayType() == PaneDisplayMode.open ? _openMenuSize : _headerSize,
height: _headerSize
),
Expanded(
child: _createWindowGestures(
child: Container(
height: _headerSize,
color: Colors.transparent
)
)
),
WindowTitleBar(focused: _focused())
],
);
}
String get searchValue => _searchController.text; String get searchValue => _searchController.text;
} }

View File

@@ -1,11 +1,9 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:dart_vlc/dart_vlc.dart';
import 'package:fluent_ui/fluent_ui.dart' hide Card;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controller/settings_controller.dart'; import '../controller/settings_controller.dart';
import '../model/tutorial_page.dart';
class InfoPage extends StatefulWidget { class InfoPage extends StatefulWidget {
const InfoPage({Key? key}) : super(key: key); const InfoPage({Key? key}) : super(key: key);
@@ -15,36 +13,149 @@ class InfoPage extends StatefulWidget {
} }
class _InfoPageState extends State<InfoPage> { class _InfoPageState extends State<InfoPage> {
final List<String> _elseTitles = [
"Open the settings tab",
"Type the ip address of the host, including the port if it's not 7777\n The complete address should follow the schema ip:port",
"Open the home page",
"Type your username if you haven't already",
"Select the exact version that the host is using from the dropdown menu\n If necessary, install it using the download button",
"As you want to play, select client from the dropdown menu",
"Click launch to open the game",
"Once you are in game, click PLAY to enter in-game\n If this doesn't work open the Fortnite console by clicking the button above tab\n If nothing happens, make sure that your keyboard locale is set to English\n Type 'open TYPE_THE_IP' without the quotes, for example: open 85.182.12.1"
];
final List<String> _ownTitles = [
"Open the settings tab",
"Type 127.0.0.1 as the matchmaking host",
"Open the home page",
"Type your username if you haven't already",
"Select the version you want to host\n If necessary, install it using the download button",
"As you want to host, select Headless Server from the dropdown menu\n If the headless server doesn't work for your version, use the normal server instead",
"Click launch to start the server and wait until the Reboot GUI shows up",
"To allow your friends to join your server, follow the instructions on playit.gg\n If you are an advanced user, open port 7777 on your router\n Finally, share your playit ip or public IPv4 address with your friends\n If you just want to play by yourself, skip this step",
"When you want to start the game, click on the 'Start Bus Countdown' button",
"If you also want to play, start a client by selecting Client from the dropdown menu\n Don't close or open again the launcher, use the same window",
"Click launch to open the game",
"Once you are in game, click PLAY to enter in-game\n If this doesn't work open the Fortnite console by clicking the button above tab\n If nothing happens, make sure that your keyboard locale is set to English\n Type 'open TYPE_THE_IP' without the quotes, for example: open 85.182.12.1"
];
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
late final ScrollController _controller;
@override @override
void initState() { void initState() {
if(_settingsController.player == null){ _controller = ScrollController(initialScrollOffset: _settingsController.scrollingDistance);
var player = Player(id: 1); _controller.addListener(() {
player.open( _settingsController.scrollingDistance = _controller.offset;
Media.network("https://cdn.discordapp.com/attachments/1006260074416701450/1038844107986055190/tutorial.mp4") });
);
_settingsController.player = player;
}
_settingsController.player?.play();
super.initState(); super.initState();
} }
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( switch(_settingsController.tutorialPage()) {
width: double.infinity, case TutorialPage.start:
height: double.infinity, return _createHomeScreen();
child: Card( case TutorialPage.someoneElse:
child: Video( return _createInstructions(false);
player: _settingsController.player, case TutorialPage.yourOwn:
height: MediaQuery.of(context).size.height * 0.85, return _createInstructions(true);
width: MediaQuery.of(context).size.width * 0.90, }
scale: 1.0, }
showControls: true,
SizedBox _createInstructions(bool own) {
var titles = own ? _ownTitles : _elseTitles;
var codeName = own ? "own" : "else";
return SizedBox.expand(
child: ListView.separated(
controller: _controller,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.only(
right: 20.0
),
child: Card(
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
child: ListTile(
title: SelectableText("${index + 1}. ${titles[index]}"),
subtitle: Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Image.asset("assets/images/tutorial_${codeName}_${index + 1}.png"),
)
)
),
),
separatorBuilder: (context, index) => const SizedBox(height: 8.0),
itemCount: titles.length,
) )
), );
}
Widget _createHomeScreen() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_createCardWidget(
text: "Play on someone else's server",
description: "If one of your friends is hosting a game server, click here",
onClick: () => setState(() => _settingsController.tutorialPage.value = TutorialPage.someoneElse)
),
const SizedBox(
width: 8.0,
),
_createCardWidget(
text: "Host your own server",
description: "If you want to create your own server to invite your friends or to play around by yourself, click here",
onClick: () => setState(() => _settingsController.tutorialPage.value = TutorialPage.yourOwn)
)
]
);
}
Widget _createCardWidget({required String text, required String description, required Function() onClick}) {
return Expanded(
child: SizedBox(
height: double.infinity,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: onClick,
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
text,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold
),
),
const SizedBox(
height: 8.0,
),
Text(
description,
textAlign: TextAlign.center
),
],
)
)
)
)
)
)
); );
} }
} }

View File

@@ -1,4 +1,6 @@
import 'dart:async';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -30,7 +32,8 @@ class _LauncherPageState extends State<LauncherPage> {
void initState() { void initState() {
if(_gameController.updater == null){ if(_gameController.updater == null){
_gameController.updater = compute(downloadRebootDll, _updateTime) _gameController.updater = compute(downloadRebootDll, _updateTime)
..then((value) => _updateTime = value); ..then((value) => _updateTime = value)
..then((value) => _gameController.updated = true);
_buildController.cancelledDownload _buildController.cancelledDownload
.listen((value) => value ? _onCancelWarning() : {}); .listen((value) => value ? _onCancelWarning() : {});
} }
@@ -65,7 +68,7 @@ class _LauncherPageState extends State<LauncherPage> {
return FutureBuilder( return FutureBuilder(
future: _gameController.updater ?? Future.value(true), future: _gameController.updater ?? Future.value(true),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData && !snapshot.hasError) { if (!_gameController.updated && !snapshot.hasData && !snapshot.hasError) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [

View File

@@ -4,6 +4,7 @@ import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart'; import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/dialog/snackbar.dart';
import 'package:reboot_launcher/src/model/server_type.dart'; import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/widget/shared/smart_switch.dart'; import 'package:reboot_launcher/src/widget/shared/smart_switch.dart';
@@ -24,17 +25,16 @@ class SettingsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Tooltip( Tooltip(
message: "The hostname of the server that hosts the multiplayer matches", message:
child: Obx(() => SmartInput( "The hostname of the server that hosts the multiplayer matches",
label: "Matchmaking Host", child: Obx(() => SmartInput(
placeholder: label: "Matchmaking Host",
"Type the hostname of the server that hosts the multiplayer matches", placeholder:
controller: _settingsController.matchmakingIp, "Type the hostname of the server that hosts the multiplayer matches",
validatorMode: AutovalidateMode.always, controller: _settingsController.matchmakingIp,
validator: checkMatchmaking, validatorMode: AutovalidateMode.always,
enabled: _serverController.type() == ServerType.embedded validator: checkMatchmaking,
)) enabled: _serverController.type() == ServerType.embedded))),
),
Tooltip( Tooltip(
message: "The dll that is injected when a server is launched", message: "The dll that is injected when a server is launched",
child: FileSelector( child: FileSelector(
@@ -63,13 +63,25 @@ class SettingsPage extends StatelessWidget {
message: "The dll that is injected to make the game work", message: "The dll that is injected to make the game work",
child: FileSelector( child: FileSelector(
label: "Cranium DLL", label: "Cranium DLL",
placeholder: "Type the path to the dll used for authentication", placeholder:
"Type the path to the dll used for authentication",
controller: _settingsController.authDll, controller: _settingsController.authDll,
windowTitle: "Select a dll", windowTitle: "Select a dll",
folder: false, folder: false,
extension: "dll", extension: "dll",
validator: checkDll, validator: checkDll,
validatorMode: AutovalidateMode.always)) validatorMode: AutovalidateMode.always)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Version Status"),
const SizedBox(height: 6.0),
Button(
child: const Text("6.0${kDebugMode ? '-DEBUG' : '-RELEASE'}"),
onPressed: () => showMessage("What a nice launcher")
)
],
)
]); ]);
} }
} }

View File

@@ -1,9 +0,0 @@
import 'dart:async';
extension FutureExtension<T> on Future<T> {
bool isCompleted() {
final completer = Completer<T>();
then(completer.complete).catchError(completer.completeError);
return completer.isCompleted;
}
}

View File

@@ -35,7 +35,10 @@ class GameTypeSelector extends StatelessWidget {
child: Text(type.name) child: Text(type.name)
) )
), ),
onPressed: () => _gameController.type(type) onPressed: () {
_gameController.type(type);
_gameController.started.value = _gameController.currentGameInstance != null;
}
); );
} }
} }

View File

@@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:async/async.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -11,6 +10,7 @@ import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart'; import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/game_dialogs.dart'; import 'package:reboot_launcher/src/dialog/game_dialogs.dart';
import 'package:reboot_launcher/src/dialog/server_dialogs.dart'; import 'package:reboot_launcher/src/dialog/server_dialogs.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/model/game_type.dart'; import 'package:reboot_launcher/src/model/game_type.dart';
import 'package:reboot_launcher/src/model/server_type.dart'; import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/os.dart';
@@ -21,9 +21,12 @@ import 'package:reboot_launcher/src/util/server.dart';
import 'package:win32_suspend_process/win32_suspend_process.dart'; import 'package:win32_suspend_process/win32_suspend_process.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../../../main.dart'; import 'package:reboot_launcher/src/../main.dart';
import '../../controller/settings_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart';
import '../../dialog/snackbar.dart'; import 'package:reboot_launcher/src/dialog/snackbar.dart';
import 'package:reboot_launcher/src/model/game_instance.dart';
import '../shared/smart_check_box.dart';
class LaunchButton extends StatefulWidget { class LaunchButton extends StatefulWidget {
const LaunchButton( const LaunchButton(
@@ -35,6 +38,7 @@ class LaunchButton extends StatefulWidget {
} }
class _LaunchButtonState extends State<LaunchButton> { class _LaunchButtonState extends State<LaunchButton> {
final String _shutdownLine = "FOnlineSubsystemGoogleCommon::Shutdown()";
final List<String> _errorStrings = [ final List<String> _errorStrings = [
"port 3551 failed: Connection refused", "port 3551 failed: Connection refused",
"Unable to login to Fortnite servers", "Unable to login to Fortnite servers",
@@ -43,7 +47,6 @@ class _LaunchButtonState extends State<LaunchButton> {
"UOnlineAccountCommon::ForceLogout" "UOnlineAccountCommon::ForceLogout"
]; ];
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final ServerController _serverController = Get.find<ServerController>(); final ServerController _serverController = Get.find<ServerController>();
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
@@ -76,83 +79,180 @@ class _LaunchButtonState extends State<LaunchButton> {
void _onPressed() async { void _onPressed() async {
if (_gameController.started()) { if (_gameController.started()) {
_onStop(); _onStop(_gameController.type());
return;
}
if (_gameController.username.text.isEmpty && _gameController.type() != GameType.client) {
showMessage("Missing username");
_gameController.started.value = false;
return; return;
} }
_gameController.started.value = true; _gameController.started.value = true;
if (_gameController.selectedVersionObs.value == null) { if (_gameController.username.text.isEmpty) {
showMessage("No version is selected"); if(_serverController.type() != ServerType.local){
_gameController.started.value = false; showMessage("Missing username");
return; _onStop(_gameController.type());
return;
}
showMessage("No username: expecting self sign in");
} }
if (_gameController.selectedVersionObs.value == null) {
showMessage("No version is selected");
_onStop(_gameController.type());
return;
}
try { try {
await _resetLogFile();
var version = _gameController.selectedVersionObs.value!; var version = _gameController.selectedVersionObs.value!;
var gamePath = version.executable?.path; var gamePath = version.executable?.path;
if(gamePath == null){ if(gamePath == null){
_onError("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?", null); _onError("${version.location.path} no longer contains a Fortnite executable, did you delete it?", null);
_onStop(); _onStop(_gameController.type());
return; return;
} }
if (version.launcher != null) { var result = await _serverController.start(required: true, askPortKill: false);
_gameController.launcherProcess = await Process.start(version.launcher!.path, []);
Win32Process(_gameController.launcherProcess!.pid).suspend();
}
if (version.eacExecutable != null) {
_gameController.eacProcess = await Process.start(version.eacExecutable!.path, []);
Win32Process(_gameController.eacProcess!.pid).suspend();
}
var result = await _serverController.start(
required: true,
askPortKill: false,
);
if(!result){ if(!result){
showMessage("Cannot launch the game as the backend didn't start up correctly"); showMessage("Cannot launch the game as the backend didn't start up correctly");
_onStop(); _onStop(_gameController.type());
return; return;
} }
if(_logFile != null && await _logFile!.exists()){
await _logFile!.delete();
}
await compute(patchMatchmaking, version.executable!); await compute(patchMatchmaking, version.executable!);
await compute(patchHeadless, version.executable!); await compute(patchHeadless, version.executable!);
var headlessHosting = _gameController.type() == GameType.headlessServer; await _startMatchMakingServer();
var arguments = createRebootArgs(_gameController.username.text, _gameController.type.value); await _startGameProcesses(version, _gameController.type());
_gameController.gameProcess = await Process.start(gamePath, arguments)
..exitCode.then((_) => _onEnd()) if(_gameController.type() == GameType.headlessServer){
..outLines.forEach((line) => _onGameOutput(line))
..errLines.forEach((line) => _onGameOutput(line));
_injectOrShowError(Injectable.cranium);
if(headlessHosting){
await _showServerLaunchingWarning(); await _showServerLaunchingWarning();
} }
} catch (exception, stacktrace) { } catch (exception, stacktrace) {
_closeDialogIfOpen(false); _closeDialogIfOpen(false);
_onError(exception, stacktrace); _onError(exception, stacktrace);
_onStop(); _onStop(_gameController.type());
} }
} }
void _onEnd() { Future<void> _startGameProcesses(FortniteVersion version, GameType type) async {
var launcherProcess = await _createLauncherProcess(version);
var eacProcess = await _createEacProcess(version);
var gameProcess = await _createGameProcess(version.executable!.path, type);
_gameController.gameInstancesMap[type] = GameInstance(gameProcess, launcherProcess, eacProcess);
_injectOrShowError(Injectable.cranium, type);
}
Future<void> _startMatchMakingServer() async {
if(_gameController.type() != GameType.client || _settingsController.doNotAskAgain()){
return;
}
var matchmakingIp = _settingsController.matchmakingIp.text;
if(!matchmakingIp.contains("127.0.0.1") && !matchmakingIp.contains("localhost")) {
return;
}
var headlessServer = _gameController.gameInstancesMap[GameType.headlessServer] != null;
var server = _gameController.gameInstancesMap[GameType.server] != null;
if(headlessServer || server){
return;
}
var controller = CheckboxController();
var result = await showDialog<bool>(
context: context,
builder: (context) => ContentDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
width: double.infinity,
child: Text(
"The matchmaking ip is set to the local machine, but no server is running. "
"If you want to start a match for your friends or just test out Reboot, you need to start a server, either now from this prompt or later manually.",
textAlign: TextAlign.start,
)
),
const SizedBox(height: 12.0),
SmartCheckBox(
controller: controller,
content: const Text("Don't ask again")
)
],
),
actions: [
Button(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Ignore'),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Start a server'),
)
],
)
) ?? false;
_settingsController.doNotAskAgain.value = controller.value;
if(!result){
return;
}
var version = _gameController.selectedVersionObs.value!;
_startGameProcesses(
version,
GameType.headlessServer
);
}
Future<Process> _createGameProcess(String gamePath, GameType type) async {
var gameProcess = await Process.start(gamePath, createRebootArgs(_gameController.username.text, type));
gameProcess
..exitCode.then((_) => _onEnd(type))
..outLines.forEach((line) => _onGameOutput(line, type))
..errLines.forEach((line) => _onGameOutput(line, type));
return gameProcess;
}
Future<void> _resetLogFile() async {
if(_logFile != null && await _logFile!.exists()){
await _logFile!.delete();
}
}
Future<Process?> _createLauncherProcess(FortniteVersion version) async {
var launcherFile = version.launcher;
if (launcherFile == null) {
return null;
}
var launcherProcess = await Process.start(launcherFile.path, []);
Win32Process(launcherProcess.pid).suspend();
return launcherProcess;
}
Future<Process?> _createEacProcess(FortniteVersion version) async {
var eacFile = version.eacExecutable;
if (eacFile == null) {
return null;
}
var eacProcess = await Process.start(eacFile.path, []);
Win32Process(eacProcess.pid).suspend();
return eacProcess;
}
void _onEnd(GameType type) {
if(_fail){ if(_fail){
return; return;
} }
_closeDialogIfOpen(false); _closeDialogIfOpen(false);
_onStop(); _onStop(type);
} }
void _closeDialogIfOpen(bool success) { void _closeDialogIfOpen(bool success) {
@@ -169,27 +269,24 @@ class _LaunchButtonState extends State<LaunchButton> {
context: appKey.currentContext!, context: appKey.currentContext!,
builder: (context) => ProgressDialog( builder: (context) => ProgressDialog(
text: "Launching headless server...", text: "Launching headless server...",
onStop: () { onStop: () =>_onEnd(_gameController.type())
Navigator.of(context).pop(false);
_onStop();
}
) )
); ) ?? false;
if(result != null && result){ if(result){
return; return;
} }
_onStop(); _onStop(_gameController.type());
} }
void _onGameOutput(String line) { void _onGameOutput(String line, GameType type) {
if(_logFile != null){ if(_logFile != null){
_logFile!.writeAsString("$line\n", mode: FileMode.append); _logFile!.writeAsString("$line\n", mode: FileMode.append);
} }
if (line.contains("FOnlineSubsystemGoogleCommon::Shutdown()")) { if (line.contains(_shutdownLine)) {
_onStop(); _onStop(type);
return; return;
} }
@@ -205,14 +302,14 @@ class _LaunchButtonState extends State<LaunchButton> {
} }
if(line.contains("Region ")){ if(line.contains("Region ")){
if(_gameController.type.value == GameType.client){ if(type == GameType.client){
_injectOrShowError(Injectable.console); _injectOrShowError(Injectable.console, type);
}else { }else {
_injectOrShowError(Injectable.reboot) _injectOrShowError(Injectable.reboot, type)
.then((value) => _closeDialogIfOpen(true)); .then((value) => _closeDialogIfOpen(true));
} }
_injectOrShowError(Injectable.memoryFix); _injectOrShowError(Injectable.memoryFix, type);
} }
} }
@@ -239,13 +336,16 @@ class _LaunchButtonState extends State<LaunchButton> {
); );
} }
void _onStop() { void _onStop(GameType type) {
_gameController.started.value = false; _gameController.gameInstancesMap[type]?.kill();
_gameController.kill(); _gameController.gameInstancesMap.remove(type);
if(type == _gameController.type()) {
_gameController.started.value = false;
}
} }
Future<void> _injectOrShowError(Injectable injectable) async { Future<void> _injectOrShowError(Injectable injectable, GameType type) async {
var gameProcess = _gameController.gameProcess; var gameProcess = _gameController.gameInstancesMap[type]?.gameProcess;
if (gameProcess == null) { if (gameProcess == null) {
return; return;
} }
@@ -255,25 +355,19 @@ class _LaunchButtonState extends State<LaunchButton> {
if(!dllPath.existsSync()) { if(!dllPath.existsSync()) {
await _downloadMissingDll(injectable); await _downloadMissingDll(injectable);
if(!dllPath.existsSync()){ if(!dllPath.existsSync()){
_onDllFail(dllPath); _onDllFail(dllPath, type);
return; return;
} }
} }
await injectDll(gameProcess.pid, dllPath.path); await injectDll(gameProcess.pid, dllPath.path);
} catch (exception) { } catch (exception) {
showSnackbar( showMessage("Cannot inject $injectable.dll: $exception");
appKey.currentContext!, _onStop(type);
Snackbar(
content: Text("Cannot inject $injectable.dll: $exception", textAlign: TextAlign.center),
extended: true
)
);
_onStop();
} }
} }
void _onDllFail(File dllPath) { void _onDllFail(File dllPath, GameType type) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if(_fail){ if(_fail){
return; return;
@@ -282,7 +376,7 @@ class _LaunchButtonState extends State<LaunchButton> {
_fail = true; _fail = true;
_closeDialogIfOpen(false); _closeDialogIfOpen(false);
showMissingDllError(path.basename(dllPath.path)); showMissingDllError(path.basename(dllPath.path));
_onStop(); _onStop(type);
}); });
} }

View File

@@ -12,8 +12,8 @@ import 'package:reboot_launcher/src/dialog/add_local_version.dart';
import 'package:reboot_launcher/src/widget/shared/smart_check_box.dart'; import 'package:reboot_launcher/src/widget/shared/smart_check_box.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../dialog/add_server_version.dart'; import 'package:reboot_launcher/src/dialog/add_server_version.dart';
import '../../util/checks.dart'; import 'package:reboot_launcher/src/util/checks.dart';
import '../shared/file_selector.dart'; import '../shared/file_selector.dart';
class VersionSelector extends StatefulWidget { class VersionSelector extends StatefulWidget {

View File

@@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:reboot_launcher/src/dialog/snackbar.dart'; import 'package:reboot_launcher/src/dialog/snackbar.dart';
import '../../util/selector.dart'; import 'package:reboot_launcher/src/util/selector.dart';
class FileSelector extends StatefulWidget { class FileSelector extends StatefulWidget {
final String label; final String label;

View File

@@ -1,6 +1,6 @@
name: reboot_launcher name: reboot_launcher
description: Launcher for project reboot description: Launcher for project reboot
version: "5.4.0" version: "6.0.0"
publish_to: 'none' publish_to: 'none'
@@ -13,7 +13,7 @@ dependencies:
bitsdojo_window: bitsdojo_window:
path: ./dependencies/bitsdojo_window-0.1.5 path: ./dependencies/bitsdojo_window-0.1.5
fluent_ui: ^4.0.3+1 fluent_ui: ^4.1.3
bitsdojo_window_windows: ^0.1.5 bitsdojo_window_windows: ^0.1.5
system_theme: ^2.0.0 system_theme: ^2.0.0
http: ^0.13.5 http: ^0.13.5
@@ -41,7 +41,6 @@ dependencies:
jaguar: ^3.1.3 jaguar: ^3.1.3
hex: ^0.2.0 hex: ^0.2.0
uuid: ^3.0.6 uuid: ^3.0.6
dart_vlc: ^0.4.0
dependency_overrides: dependency_overrides:
win32: ^3.0.0 win32: ^3.0.0
@@ -67,7 +66,7 @@ msix_config:
display_name: Reboot Launcher display_name: Reboot Launcher
publisher_display_name: Auties00 publisher_display_name: Auties00
identity_name: 31868Auties00.RebootLauncher identity_name: 31868Auties00.RebootLauncher
msix_version: 5.4.0.0 msix_version: 6.0.0.0
publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029 publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029
logo_path: ./assets/icons/reboot.ico logo_path: ./assets/icons/reboot.ico
architecture: x64 architecture: x64

View File

@@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h> #include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
#include <dart_vlc/dart_vlc_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever/screen_retriever_plugin.h>
#include <system_theme/system_theme_plugin.h> #include <system_theme/system_theme_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
@@ -16,8 +15,6 @@
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
BitsdojoWindowPluginRegisterWithRegistrar( BitsdojoWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
DartVlcPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DartVlcPlugin"));
ScreenRetrieverPluginRegisterWithRegistrar( ScreenRetrieverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
SystemThemePluginRegisterWithRegistrar( SystemThemePluginRegisterWithRegistrar(

View File

@@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
bitsdojo_window_windows bitsdojo_window_windows
dart_vlc
screen_retriever screen_retriever
system_theme system_theme
url_launcher_windows url_launcher_windows

View File

@@ -13,13 +13,37 @@ auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
bool CheckOneInstance()
{
HANDLE m_hStartEvent = CreateEventW( NULL, FALSE, FALSE, L"reboot_launcher");
if(m_hStartEvent == NULL)
{
CloseHandle( m_hStartEvent );
return false;
}
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle( m_hStartEvent );
m_hStartEvent = NULL;
return false;
}
return true;
}
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) { _In_ wchar_t *command_line, _In_ int show_command) {
std::vector<std::string> command_line_arguments = GetCommandLineArguments(); if(!CheckOneInstance()){
if (!command_line_arguments.empty() || (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent())) { return false;
CreateAndAttachConsole();
} }
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
std::vector<std::string> command_line_arguments = GetCommandLineArguments();
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or // Initialize COM, so that it is available for use in the library and/or
// plugins. // plugins.
@@ -44,6 +68,5 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
} }
::CoUninitialize(); ::CoUninitialize();
std::cout << "Done" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }