Added settings tab

This commit is contained in:
Alessandro Autiero
2022-10-07 19:18:19 +02:00
parent 55467152c9
commit 07481c303e
17 changed files with 258 additions and 110 deletions

View File

@@ -20,6 +20,7 @@ import 'package:win32/win32.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:http/http.dart' as http;
// Needed because binaries can't be loaded in any other way
const String _craniumDownload = "https://cdn.discordapp.com/attachments/1001161930599317524/1027684488718860309/cranium.dll";
const String _consoleDownload = "https://cdn.discordapp.com/attachments/1001161930599317524/1027684489184432188/console.dll";
const String _injectorDownload = "https://cdn.discordapp.com/attachments/1001161930599317524/1027686593697435799/injector.exe";
@@ -74,20 +75,23 @@ Future<String?> _getWindowsPath(String folderID) {
Future<void> handleCLI(List<String> args) async {
stdout.writeln("Reboot Launcher CLI Tool");
stdout.writeln("Wrote by Auties00");
stdout.writeln("Version 3.10");
stdout.writeln("Version 3.11");
var gameJson = await _getControllerJson("game1");
var serverJson = await _getControllerJson("server1");
var gameJson = await _getControllerJson("game");
var serverJson = await _getControllerJson("server");
var settingsJson = await _getControllerJson("settings");
var versions = _getVersions(gameJson);
var parser = ArgParser()
..addCommand("list")..addCommand("launch")
..addCommand("list")
..addCommand("launch")
..addOption("version", defaultsTo: gameJson["version"])
..addOption("username")
..addOption("server-type", allowed: ["embedded", "remote"], defaultsTo: serverJson["embedded"] ?? true ? "embedded" : "remote")
..addOption("server-host", defaultsTo: serverJson["host"])
..addOption("server-port", defaultsTo: serverJson["port"])
..addOption("dll", defaultsTo: settingsJson["reboot"] ?? await loadBinary("reboot.dll", true))
..addOption("type", allowed: ["client", "server", "headless_server"], defaultsTo: _getDefaultType(gameJson))
..addFlag("update", defaultsTo: true, negatable: true)
..addFlag("update", defaultsTo: settingsJson["auto_update"] ?? true, negatable: true)
..addFlag("log", defaultsTo: false);
var result = parser.parse(args);
if (result.command?.name == "list") {
@@ -104,7 +108,7 @@ Future<void> handleCLI(List<String> args) async {
var dummyVersion = _createVersion(gameJson["version"], result["version"], versions);
await _updateDLLs();
if(result["update"]) {
stdout.writeln("Updating DLL...");
stdout.writeln("Updating reboot dll...");
await downloadRebootDll(0);
}
@@ -121,7 +125,7 @@ Future<void> handleCLI(List<String> args) async {
return;
}
await _startGameProcess(dummyVersion, type != GameType.client, result);
await _startGameProcess(dummyVersion, result["dll"], type != GameType.client, result);
await _injectOrShowError("cranium.dll");
}
@@ -193,7 +197,7 @@ List<FortniteVersion> _getVersions(Map<String, dynamic> gameJson) {
.toList();
}
Future<void> _startGameProcess(FortniteVersion dummyVersion, bool host, ArgResults result) async {
Future<void> _startGameProcess(FortniteVersion dummyVersion, String rebootDll, bool host, ArgResults result) async {
var gamePath = dummyVersion.executable?.path;
if (gamePath == null) {
throw Exception("${dummyVersion.location
@@ -209,7 +213,7 @@ Future<void> _startGameProcess(FortniteVersion dummyVersion, bool host, ArgResul
var verbose = result["log"];
_gameProcess = await Process.start(gamePath, createRebootArgs(username, result["type"] == "headless_server"))
..exitCode.then((_) => _onClose())
..outLines.forEach((line) => _onGameOutput(line, host, verbose));
..outLines.forEach((line) => _onGameOutput(line, rebootDll, host, verbose));
}
void _onClose() {
@@ -323,7 +327,7 @@ FortniteVersion _createVersion(String? versionName, String? versionPath, List<Fo
return FortniteVersion(name: "dummy", location: Directory(versionPath));
}
void _onGameOutput(String line, bool host, bool verbose) {
void _onGameOutput(String line, String rebootDll, bool host, bool verbose) {
if(verbose) {
stdout.writeln(line);
}
@@ -353,18 +357,22 @@ void _onGameOutput(String line, bool host, bool verbose) {
}
if(line.contains("Region") && host){
_injectOrShowError("reboot.dll");
_injectOrShowError(rebootDll, false);
}
}
Future<void> _injectOrShowError(String binary) async {
Future<void> _injectOrShowError(String binary, [bool locate = true]) async {
if (_gameProcess == null) {
return;
}
try {
stdout.writeln("Injecting $binary...");
var dll = await loadBinary(binary, true);
var dll = locate ? await loadBinary(binary, true) : File(binary);
if(!dll.existsSync()){
throw Exception("Cannot inject $dll: missing file");
}
var success = await injectDll(_gameProcess!.pid, dll.path, true);
if (success) {
return;

View File

@@ -10,6 +10,7 @@ import 'package:reboot_launcher/cli.dart';
import 'package:reboot_launcher/src/controller/build_controller.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:reboot_launcher/src/util/binary.dart';
import 'package:reboot_launcher/src/util/os.dart';
@@ -28,9 +29,11 @@ void main(List<String> args) async {
await GetStorage.init("game");
await GetStorage.init("server");
await GetStorage.init("update");
await GetStorage.init("settings");
Get.put(GameController());
Get.put(ServerController());
Get.put(BuildController());
Get.put(SettingsController());
doWhenWindowReady(() {
const size = Size(600, 365);
var window = appWindow as WinDesktopWindow;
@@ -52,6 +55,8 @@ class RebootApplication extends StatefulWidget {
}
class _RebootApplicationState extends State<RebootApplication> {
final SettingsController _settingsController = Get.find<SettingsController>();
@override
Widget build(BuildContext context) {
final color = SystemTheme.accentColor.accent.toAccentColor();
@@ -60,23 +65,20 @@ class _RebootApplicationState extends State<RebootApplication> {
themeMode: ThemeMode.system,
debugShowCheckedModeBanner: false,
color: color,
darkTheme: ThemeData(
brightness: Brightness.dark,
accentColor: color,
visualDensity: VisualDensity.standard,
focusTheme: FocusThemeData(
glowFactor: is10footScreen() ? 2.0 : 0.0,
),
),
theme: ThemeData(
brightness: Brightness.light,
accentColor: color,
visualDensity: VisualDensity.standard,
focusTheme: FocusThemeData(
glowFactor: is10footScreen() ? 2.0 : 0.0,
),
),
darkTheme: _createTheme(Brightness.dark),
theme: _createTheme(Brightness.light),
home: const HomePage(),
);
}
ThemeData _createTheme(Brightness brightness) {
return ThemeData(
brightness: brightness,
accentColor: SystemTheme.accentColor.accent.toAccentColor(),
visualDensity: VisualDensity.standard,
focusTheme: FocusThemeData(
glowFactor: is10footScreen() ? 2.0 : 0.0,
),
);
}
}

View File

@@ -43,9 +43,7 @@ class GameController extends GetxController {
});
username = TextEditingController(text: _storage.read("${type.value == GameType.client ? 'game' : 'host'}_username") ?? "");
username.addListener(() async {
await _storage.write("${type.value == GameType.client ? 'game' : 'host'}_username", username.text);
});
username.addListener(() => _storage.write("${type.value == GameType.client ? 'game' : 'host'}_username", username.text));
started = RxBool(false);
}

View File

@@ -0,0 +1,41 @@
import 'dart:convert';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/model/game_type.dart';
import 'package:reboot_launcher/src/util/binary.dart';
import 'package:system_theme/system_theme.dart';
class SettingsController extends GetxController {
late final GetStorage _storage;
late final String originalDll;
late final TextEditingController rebootDll;
late final TextEditingController consoleDll;
late final TextEditingController craniumDll;
late final RxBool autoUpdate;
SettingsController() {
_storage = GetStorage("settings");
rebootDll = _createController("reboot", "reboot.dll");
consoleDll = _createController("console", "console.dll");
craniumDll = _createController("cranium", "cranium.dll");
autoUpdate = RxBool(_storage.read("auto_update") ?? true);
autoUpdate.listen((value) => _storage.write("auto_update", value));
}
TextEditingController _createController(String key, String name) {
var controller = TextEditingController(text: _storage.read(key) ?? "$safeBinariesDirectory\\$name");
controller.addListener(() {
if(controller.text.isEmpty || !File(controller.text).existsSync()) {
return;
}
_storage.write(key, controller.text);
});
return controller;
}
}

View File

@@ -1,6 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/page/info_page.dart';
import 'package:reboot_launcher/src/page/settings_page.dart';
import 'package:reboot_launcher/src/page/launcher_page.dart';
import 'package:reboot_launcher/src/page/server_page.dart';
import 'package:reboot_launcher/src/util/os.dart';
@@ -54,7 +54,7 @@ class _HomePageState extends State<HomePage> with WindowListener {
items: [
_createPane("Home", FluentIcons.game),
_createPane("Lawin", FluentIcons.server_enviroment),
_createPane("Info", FluentIcons.info),
_createPane("Settings", FluentIcons.settings)
],
trailing: WindowTitleBar(focused: _focused)),
content: NavigationBody(
@@ -62,7 +62,7 @@ class _HomePageState extends State<HomePage> with WindowListener {
children: [
const LauncherPage(),
ServerPage(),
const InfoPage()
SettingsPage()
]
)
),

View File

@@ -1,38 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:url_launcher/url_launcher.dart';
const String _discordLink = "https://discord.gg/NJU4QjxSMF";
class InfoPage extends StatelessWidget {
const InfoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
const Expanded(child: SizedBox()),
Column(
children: [
const CircleAvatar(
radius: 48,
backgroundImage: AssetImage("assets/images/auties.png")),
const SizedBox(
height: 16.0,
),
const Text("Made by Auties00"),
const SizedBox(
height: 16.0,
),
Button(
child: const Text("Join the discord"),
onPressed: () => launchUrl(Uri.parse(_discordLink))),
],
),
const Expanded(
child: Align(
alignment: Alignment.bottomLeft, child: Text("Version 3.10${kDebugMode ? '-DEBUG' : ''}")))
],
);
}
}

View File

@@ -13,6 +13,7 @@ import 'package:reboot_launcher/src/widget/username_box.dart';
import 'package:reboot_launcher/src/widget/version_selector.dart';
import 'package:url_launcher/url_launcher.dart';
import '../controller/settings_controller.dart';
import '../util/binary.dart';
import '../util/reboot.dart';
import '../widget/warning_info.dart';
@@ -29,10 +30,11 @@ class LauncherPage extends StatefulWidget {
class _LauncherPageState extends State<LauncherPage> {
final GameController _gameController = Get.find<GameController>();
final BuildController _buildController = Get.find<BuildController>();
final SettingsController _settingsController = Get.find<SettingsController>();
@override
void initState() {
if(_gameController.updater == null) {
if(_gameController.updater == null && _settingsController.autoUpdate.value){
_gameController.updater = compute(downloadRebootDll, _updateTime)
..then((value) => _updateTime = value)
..onError(_saveError);

View File

@@ -0,0 +1,86 @@
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/widget/file_selector.dart';
import 'package:reboot_launcher/src/widget/smart_switch.dart';
class SettingsPage extends StatelessWidget {
final SettingsController _settingsController = Get.find<SettingsController>();
SettingsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Form(
autovalidateMode: AutovalidateMode.always,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FileSelector(
label: "Reboot DLL",
placeholder: "Type the path to the reboot dll",
controller: _settingsController.rebootDll,
windowTitle: "Select a dll",
folder: false,
extension: "dll",
validator: _checkDll
),
FileSelector(
label: "Console DLL",
placeholder: "Type the path to the console dll",
controller: _settingsController.consoleDll,
windowTitle: "Select a dll",
folder: false,
extension: "dll",
validator: _checkDll
),
FileSelector(
label: "Cranium DLL",
placeholder: "Type the path to the cranium dll",
controller: _settingsController.craniumDll,
windowTitle: "Select a dll",
folder: false,
extension: "dll",
validator: _checkDll
),
SmartSwitch(
value: _settingsController.autoUpdate,
label: "Update DLLs"
),
],
)
),
const Align(
alignment: Alignment.bottomRight,
child: Text("Version 3.11${kDebugMode ? '-DEBUG' : ''}")
)
],
);
}
String? _checkDll(String? text) {
if (text == null || text.isEmpty) {
return "Empty dll path";
}
if (!File(text).existsSync()) {
return "This dll doesn't exist";
}
if (!text.endsWith(".dll")) {
return "This file is not a dll";
}
return null;
}
}

View File

@@ -1,7 +1,7 @@
import 'dart:io';
Future<File> loadBinary(String binary, bool safe) async{
var safeBinary = File("$safeBinariesDirectory/$binary");
var safeBinary = File("$safeBinariesDirectory\\$binary");
if(await safeBinary.exists()){
return safeBinary;
}

View File

@@ -16,9 +16,22 @@ bool get isWin11 {
return intBuild != null && intBuild > 22000;
}
Future<String?> openFilePicker(String title) async =>
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;
}
Future<List<Directory>> scanInstallations(String input) => Directory(input)
.list(recursive: true)
.handleError((_) {}, test: (e) => e is FileSystemException)

View File

@@ -4,7 +4,7 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/widget/select_file.dart';
import 'package:reboot_launcher/src/widget/file_selector.dart';
class AddLocalVersion extends StatelessWidget {
final GameController _gameController = Get.find<GameController>();
@@ -76,12 +76,14 @@ class AddLocalVersion extends StatelessWidget {
},
),
SelectFile(
FileSelector(
label: "Location",
placeholder: "Type the game folder",
windowTitle: "Select game folder",
controller: _gamePathController,
validator: _checkGameFolder)
validator: _checkGameFolder,
folder: true
)
],
);
}

View File

@@ -10,7 +10,7 @@ import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/util/binary.dart';
import 'package:reboot_launcher/src/util/build.dart';
import 'package:reboot_launcher/src/widget/select_file.dart';
import 'package:reboot_launcher/src/widget/file_selector.dart';
import 'package:reboot_launcher/src/widget/version_name_input.dart';
import 'build_selector.dart';
@@ -242,12 +242,13 @@ class _AddServerVersionState extends State<AddServerVersion> {
VersionNameInput(controller: _nameController),
SelectFile(
FileSelector(
label: "Destination",
placeholder: "Type the download destination",
windowTitle: "Select download destination",
controller: _pathController,
validator: _checkDownloadDestination
validator: _checkDownloadDestination,
folder: true
),
],
);

View File

@@ -3,29 +3,34 @@ import 'package:flutter/foundation.dart';
import '../util/os.dart';
class SelectFile extends StatefulWidget {
class FileSelector extends StatefulWidget {
final String label;
final String placeholder;
final String windowTitle;
final bool allowNavigator;
final TextEditingController controller;
final String? Function(String?) validator;
final String? extension;
final bool folder;
const SelectFile(
const FileSelector(
{required this.label,
required this.placeholder,
required this.windowTitle,
required this.controller,
required this.validator,
required this.folder,
this.extension,
this.allowNavigator = true,
Key? key})
: super(key: key);
: assert(folder || extension != null, "Missing extension for file selector"),
super(key: key);
@override
State<SelectFile> createState() => _SelectFileState();
State<FileSelector> createState() => _FileSelectorState();
}
class _SelectFileState extends State<SelectFile> {
class _FileSelectorState extends State<FileSelector> {
bool _selecting = false;
@override
@@ -47,7 +52,7 @@ class _SelectFileState extends State<SelectFile> {
Padding(
padding: const EdgeInsets.only(bottom: 21.0),
child: Tooltip(
message: "Select a folder",
message: "Select a ${widget.folder ? 'folder' : 'file'}",
child: Button(
onPressed: _onPressed,
child: const Icon(FluentIcons.open_folder_horizontal)
@@ -66,7 +71,14 @@ class _SelectFileState extends State<SelectFile> {
}
_selecting = true;
compute(openFilePicker, "Select the game folder")
if(widget.folder) {
compute(openFolderPicker, widget.windowTitle)
.then((value) => widget.controller.text = value ?? "")
.then((_) => _selecting = false);
return;
}
compute(openFilePicker, widget.extension!)
.then((value) => widget.controller.text = value ?? "")
.then((_) => _selecting = false);
}

View File

@@ -15,6 +15,7 @@ import 'package:reboot_launcher/src/util/server.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:win32_suspend_process/win32_suspend_process.dart';
import '../controller/settings_controller.dart';
import '../util/server_standalone.dart';
class LaunchButton extends StatefulWidget {
@@ -29,6 +30,7 @@ class LaunchButton extends StatefulWidget {
class _LaunchButtonState extends State<LaunchButton> {
final GameController _gameController = Get.find<GameController>();
final ServerController _serverController = Get.find<ServerController>();
final SettingsController _settingsController = Get.find<SettingsController>();
File? _logFile;
bool _fail = false;
@@ -114,7 +116,7 @@ class _LaunchButtonState extends State<LaunchButton> {
_gameController.gameProcess = await Process.start(gamePath, createRebootArgs(_gameController.username.text, hosting))
..exitCode.then((_) => _onEnd())
..outLines.forEach(_onGameOutput);
await _injectOrShowError("cranium.dll");
await _injectOrShowError(Injectable.cranium);
if(hosting){
await _showServerLaunchingWarning();
@@ -333,12 +335,12 @@ class _LaunchButtonState extends State<LaunchButton> {
}
if (line.contains("Game Engine Initialized") && _gameController.type.value == GameType.client) {
_injectOrShowError("console.dll");
_injectOrShowError(Injectable.console);
return;
}
if(line.contains("Region") && _gameController.type.value != GameType.client){
_injectOrShowError("reboot.dll")
_injectOrShowError(Injectable.reboot)
.then((value) => _closeDialogIfOpen(true));
}
}
@@ -374,22 +376,33 @@ class _LaunchButtonState extends State<LaunchButton> {
_gameController.kill();
}
Future<void> _injectOrShowError(String binary) async {
Future<void> _injectOrShowError(Injectable injectable) async {
var gameProcess = _gameController.gameProcess;
if (gameProcess == null) {
return;
}
try {
var dll = await loadBinary(binary, true);
var success = await injectDll(gameProcess.pid, dll.path);
var dllPath = _getDllPath(injectable);
var success = await injectDll(gameProcess.pid, dllPath);
if (success) {
return;
}
_onInjectError(binary);
_onInjectError(injectable.name);
} catch (exception) {
_onInjectError(binary);
_onInjectError(injectable.name);
}
}
String _getDllPath(Injectable injectable){
switch(injectable){
case Injectable.reboot:
return _settingsController.rebootDll.text;
case Injectable.console:
return _settingsController.consoleDll.text;
case Injectable.cranium:
return _settingsController.craniumDll.text;
}
}
@@ -398,3 +411,9 @@ class _LaunchButtonState extends State<LaunchButton> {
launchUrl(injectLogFile.uri);
}
}
enum Injectable {
console,
cranium,
reboot
}

View File

@@ -4,7 +4,7 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as path;
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/widget/select_file.dart';
import 'package:reboot_launcher/src/widget/file_selector.dart';
class ScanLocalVersion extends StatefulWidget {
@@ -83,12 +83,14 @@ class _ScanLocalVersionState extends State<ScanLocalVersion> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectFile(
FileSelector(
label: "Location",
placeholder: "Type the folder to scan",
windowTitle: "Select the folder to scan",
controller: _folderController,
validator: _checkScanFolder)
validator: _checkScanFolder,
folder: true
)
],
);
}

View File

@@ -1,7 +1,7 @@
import 'package:fluent_ui/fluent_ui.dart';
class SmartInput extends StatelessWidget {
final String label;
final String? label;
final String placeholder;
final TextEditingController controller;
final TextInputType type;
@@ -11,13 +11,13 @@ class SmartInput extends StatelessWidget {
const SmartInput(
{Key? key,
required this.label,
required this.placeholder,
required this.controller,
this.onTap,
this.enabled = true,
this.populate = false,
this.type = TextInputType.text})
required this.placeholder,
required this.controller,
this.label,
this.onTap,
this.enabled = true,
this.populate = false,
this.type = TextInputType.text})
: super(key: key);
@override