Released 9.2.5

This commit is contained in:
Alessandro Autiero
2024-08-18 20:29:09 +02:00
parent 582270849e
commit 4c3fe9bc65
21 changed files with 503 additions and 383 deletions

View File

@@ -35,7 +35,7 @@ express.use(require("./structure/matchmaking.js"));
express.use(require("./structure/cloudstorage.js")); express.use(require("./structure/cloudstorage.js"));
express.use(require("./structure/mcp.js")); express.use(require("./structure/mcp.js"));
const port = process.env.PORT || 3551; const port = 3551;
express.listen(port, () => { express.listen(port, () => {
console.log("LawinServer started listening on port", port); console.log("LawinServer started listening on port", port);

View File

@@ -11,7 +11,7 @@ const List<String> kCorruptedBuildErrors = [
"Critical error", "Critical error",
"when 0 bytes remain", "when 0 bytes remain",
"Pak chunk signature verification failed!", "Pak chunk signature verification failed!",
"Couldn't find pak signature file" "LogWindows:Error: Fatal error!"
]; ];
const List<String> kCannotConnectErrors = [ const List<String> kCannotConnectErrors = [
"port 3551 failed: Connection refused", "port 3551 failed: Connection refused",

View File

@@ -9,9 +9,22 @@ extension FortniteVersionExtension on FortniteVersion {
static File? findFile(Directory directory, String name) { static File? findFile(Directory directory, String name) {
try{ try{
final result = directory.listSync(recursive: true) for(final child in directory.listSync()) {
.firstWhere((element) => path.basename(element.path) == name); if(child is Directory) {
return File(result.path); if(!path.basename(child.path).startsWith("\.")) {
final result = findFile(child, name);
if(result != null) {
return result;
}
}
}else if(child is File) {
if(path.basename(child.path) == name) {
return child;
}
}
}
return null;
}catch(_){ }catch(_){
return null; return null;
} }

View File

@@ -15,13 +15,16 @@ final Semaphore _semaphore = Semaphore();
String? _lastIp; String? _lastIp;
String? _lastPort; String? _lastPort;
Future<Process> startEmbeddedBackend(bool detached) async { Future<Process> startEmbeddedBackend(bool detached, {void Function(String)? onError}) async {
final process = await startProcess( final process = await startProcess(
executable: backendStartExecutable, executable: backendStartExecutable,
window: detached, window: detached,
); );
process.stdOutput.listen((message) => log("[BACKEND] Message: $message")); process.stdOutput.listen((message) => log("[BACKEND] Message: $message"));
process.stdError.listen((error) => log("[BACKEND] Error: $error")); process.stdError.listen((error) {
log("[BACKEND] Error: $error");
onError?.call(error);
});
process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode")); process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode"));
return process; return process;
} }

View File

@@ -102,8 +102,7 @@ Future<bool> startElevatedProcess({required String executable, required String a
shellInput.ref.fMask = ES_AWAYMODE_REQUIRED; shellInput.ref.fMask = ES_AWAYMODE_REQUIRED;
shellInput.ref.lpVerb = "runas".toNativeUtf16(); shellInput.ref.lpVerb = "runas".toNativeUtf16();
shellInput.ref.cbSize = sizeOf<SHELLEXECUTEINFO>(); shellInput.ref.cbSize = sizeOf<SHELLEXECUTEINFO>();
var shellResult = ShellExecuteEx(shellInput); return ShellExecuteEx(shellInput) == 1;
return shellResult == 1;
} }
Future<Process> startProcess({required File executable, List<String>? args, bool useTempBatch = true, bool window = false, String? name, Map<String, String>? environment}) async { Future<Process> startProcess({required File executable, List<String>? args, bool useTempBatch = true, bool window = false, String? name, Map<String, String>? environment}) async {
@@ -313,7 +312,14 @@ final class _ExtendedProcess implements Process {
@override @override
Future<int> get exitCode => _delegate.exitCode; Future<int> get exitCode {
try {
return _delegate.exitCode;
}catch(_) {
return watchProcess(_delegate.pid)
.then((_) => -1);
}
}
@override @override
bool kill([ProcessSignal signal = ProcessSignal.sigterm]) => _delegate.kill(signal); bool kill([ProcessSignal signal = ProcessSignal.sigterm]) => _delegate.kill(signal);

Binary file not shown.

View File

@@ -118,9 +118,7 @@
"settingsUtilsResetDefaultsName": "Reset settings", "settingsUtilsResetDefaultsName": "Reset settings",
"settingsUtilsResetDefaultsSubtitle": "Resets the launcher's settings to their default values", "settingsUtilsResetDefaultsSubtitle": "Resets the launcher's settings to their default values",
"settingsUtilsDialogTitle": "Do you want to reset all the setting in this tab to their default values? This action is irreversible", "settingsUtilsDialogTitle": "Do you want to reset all the setting in this tab to their default values? This action is irreversible",
"settingsUtilsResetDefaultsContent": "Reset",
"settingsUtilsDialogSecondaryAction": "Close", "settingsUtilsDialogSecondaryAction": "Close",
"settingsUtilsDialogPrimaryAction": "Reset",
"selectFortniteName": "Fortnite version", "selectFortniteName": "Fortnite version",
"selectFortniteDescription": "Select the version of Fortnite you want to use", "selectFortniteDescription": "Select the version of Fortnite you want to use",
"manageVersionsName": "Manage versions", "manageVersionsName": "Manage versions",
@@ -262,6 +260,7 @@
"missingCustomDllError": "The custom {dll}.dll doesn't exist: check your settings", "missingCustomDllError": "The custom {dll}.dll doesn't exist: check your settings",
"tokenError": "Cannot log in into Fortnite: authentication error (injected dlls: {dlls})", "tokenError": "Cannot log in into Fortnite: authentication error (injected dlls: {dlls})",
"unknownFortniteError": "An unknown error occurred while launching Fortnite: {error}", "unknownFortniteError": "An unknown error occurred while launching Fortnite: {error}",
"fortniteCrashError": "The {name} crashed after being launched",
"serverNoLongerAvailableUnnamed": "The previous server is no longer available", "serverNoLongerAvailableUnnamed": "The previous server is no longer available",
"noServerFound": "No server found: invalid or expired link", "noServerFound": "No server found: invalid or expired link",
"settingsUtilsThemeName": "Theme", "settingsUtilsThemeName": "Theme",
@@ -321,6 +320,7 @@
"none": "none", "none": "none",
"openLog": "Open log", "openLog": "Open log",
"backendProcessError": "The backend shut down unexpectedly", "backendProcessError": "The backend shut down unexpectedly",
"backendErrorMessage": "The backend reported an unexpected error",
"welcomeTitle": "Welcome to Reboot Launcher", "welcomeTitle": "Welcome to Reboot Launcher",
"welcomeDescription": "If you have never used a Fortnite game server, or this launcher in particular, please click on take a tour\nPlease don't ask for support on Discord without taking the tour: this helps me prioritize real bugs\nYou can always take the tour again in the Info tab", "welcomeDescription": "If you have never used a Fortnite game server, or this launcher in particular, please click on take a tour\nPlease don't ask for support on Discord without taking the tour: this helps me prioritize real bugs\nYou can always take the tour again in the Info tab",
"welcomeAction": "Take the tour", "welcomeAction": "Take the tour",
@@ -364,5 +364,8 @@
"promptSettingsTabActionLabel": "Done", "promptSettingsTabActionLabel": "Done",
"automaticGameServerDialogContent": "The launcher detected that you are not running a game server, but that your matchmaker is set to your local machine. If you don't want to join another player's server, you should start a game server. This is necessary to be able to play!", "automaticGameServerDialogContent": "The launcher detected that you are not running a game server, but that your matchmaker is set to your local machine. If you don't want to join another player's server, you should start a game server. This is necessary to be able to play!",
"automaticGameServerDialogIgnore": "Ignore", "automaticGameServerDialogIgnore": "Ignore",
"automaticGameServerDialogStart": "Start server" "automaticGameServerDialogStart": "Start server",
"gameResetDefaultsName": "Reset",
"gameResetDefaultsDescription": "Resets the game's settings to their default values",
"gameResetDefaultsContent": "Reset"
} }

View File

@@ -12,6 +12,7 @@ import 'package:local_notifier/local_notifier.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/backend_controller.dart'; import 'package:reboot_launcher/src/controller/backend_controller.dart';
import 'package:reboot_launcher/src/controller/dll_controller.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart';
@@ -188,10 +189,11 @@ void _initWindow() => doWhenWindowReady(() async {
Future<List<Object>> _initStorage() async { Future<List<Object>> _initStorage() async {
final errors = <Object>[]; final errors = <Object>[];
try { try {
await GetStorage("game_storage", settingsDirectory.path).initStorage; await GetStorage(GameController.storageName, settingsDirectory.path).initStorage;
await GetStorage("backend_storage", settingsDirectory.path).initStorage; await GetStorage(BackendController.storageName, settingsDirectory.path).initStorage;
await GetStorage("settings_storage", settingsDirectory.path).initStorage; await GetStorage(SettingsController.storageName, settingsDirectory.path).initStorage;
await GetStorage("hosting_storage", settingsDirectory.path).initStorage; await GetStorage(HostingController.storageName, settingsDirectory.path).initStorage;
await GetStorage(DllController.storageName, settingsDirectory.path).initStorage;
}catch(error) { }catch(error) {
appWithNoStorage = true; appWithNoStorage = true;
errors.add("The Reboot Launcher configuration in ${settingsDirectory.path} cannot be accessed: running with in memory storage"); errors.add("The Reboot Launcher configuration in ${settingsDirectory.path} cannot be accessed: running with in memory storage");
@@ -223,6 +225,12 @@ Future<List<Object>> _initStorage() async {
errors.add(error); errors.add(error);
} }
try {
Get.put(DllController());
}catch(error) {
errors.add(error);
}
return errors; return errors;
} }

View File

@@ -2,18 +2,24 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.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_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/util/keyboard.dart';
class BackendController extends GetxController { class BackendController extends GetxController {
late final GetStorage? storage; static const String storageName = "backend_storage";
static const PhysicalKeyboardKey _kDefaultConsoleKey = PhysicalKeyboardKey(0x00070041);
late final GetStorage? _storage;
late final TextEditingController host; late final TextEditingController host;
late final TextEditingController port; late final TextEditingController port;
late final Rx<ServerType> type; late final Rx<ServerType> type;
late final TextEditingController gameServerAddress; late final TextEditingController gameServerAddress;
late final FocusNode gameServerAddressFocusNode; late final FocusNode gameServerAddressFocusNode;
late final Rx<PhysicalKeyboardKey> consoleKey;
late final RxBool started; late final RxBool started;
late final RxBool detached; late final RxBool detached;
StreamSubscription? worker; StreamSubscription? worker;
@@ -22,13 +28,13 @@ class BackendController extends GetxController {
HttpServer? remoteServer; HttpServer? remoteServer;
BackendController() { BackendController() {
storage = appWithNoStorage ? null : GetStorage("backend_storage"); _storage = appWithNoStorage ? null : GetStorage(storageName);
started = RxBool(false); started = RxBool(false);
type = Rx(ServerType.values.elementAt(storage?.read("type") ?? 0)); type = Rx(ServerType.values.elementAt(_storage?.read("type") ?? 0));
type.listen((value) { type.listen((value) {
host.text = _readHost(); host.text = _readHost();
port.text = _readPort(); port.text = _readPort();
storage?.write("type", value.index); _storage?.write("type", value.index);
if (!started.value) { if (!started.value) {
return; return;
} }
@@ -37,13 +43,13 @@ class BackendController extends GetxController {
}); });
host = TextEditingController(text: _readHost()); host = TextEditingController(text: _readHost());
host.addListener(() => host.addListener(() =>
storage?.write("${type.value.name}_host", host.text)); _storage?.write("${type.value.name}_host", host.text));
port = TextEditingController(text: _readPort()); port = TextEditingController(text: _readPort());
port.addListener(() => port.addListener(() =>
storage?.write("${type.value.name}_port", port.text)); _storage?.write("${type.value.name}_port", port.text));
detached = RxBool(storage?.read("detached") ?? false); detached = RxBool(_storage?.read("detached") ?? false);
detached.listen((value) => storage?.write("detached", value)); detached.listen((value) => _storage?.write("detached", value));
final address = storage?.read("game_server_address"); final address = _storage?.read("game_server_address");
gameServerAddress = TextEditingController(text: address == null || address.isEmpty ? "127.0.0.1" : address); gameServerAddress = TextEditingController(text: address == null || address.isEmpty ? "127.0.0.1" : address);
var lastValue = gameServerAddress.text; var lastValue = gameServerAddress.text;
writeMatchmakingIp(lastValue); writeMatchmakingIp(lastValue);
@@ -55,7 +61,7 @@ class BackendController extends GetxController {
lastValue = newValue; lastValue = newValue;
gameServerAddress.selection = TextSelection.collapsed(offset: newValue.length); gameServerAddress.selection = TextSelection.collapsed(offset: newValue.length);
storage?.write("game_server_address", newValue); _storage?.write("game_server_address", newValue);
writeMatchmakingIp(newValue); writeMatchmakingIp(newValue);
}); });
watchMatchmakingIp().listen((event) { watchMatchmakingIp().listen((event) {
@@ -64,6 +70,37 @@ class BackendController extends GetxController {
} }
}); });
gameServerAddressFocusNode = FocusNode(); gameServerAddressFocusNode = FocusNode();
consoleKey = Rx(_readConsoleKey());
_writeConsoleKey(consoleKey.value);
consoleKey.listen((newValue) {
_storage?.write("console_key", newValue.usbHidUsage);
_writeConsoleKey(newValue);
});
}
PhysicalKeyboardKey _readConsoleKey() {
final consoleKeyValue = _storage?.read("console_key");
if(consoleKeyValue == null) {
return _kDefaultConsoleKey;
}
final consoleKeyNumber = int.tryParse(consoleKeyValue.toString());
if(consoleKeyNumber == null) {
return _kDefaultConsoleKey;
}
final consoleKey = PhysicalKeyboardKey(consoleKeyNumber);
if(!consoleKey.isUnrealEngineKey) {
return _kDefaultConsoleKey;
}
return consoleKey;
}
Future<void> _writeConsoleKey(PhysicalKeyboardKey keyValue) async {
final defaultInput = File("${backendDirectory.path}\\CloudStorage\\DefaultInput.ini");
await defaultInput.parent.create(recursive: true);
await defaultInput.writeAsString("[/Script/Engine.InputSettings]\n+ConsoleKeys=Tilde\n+ConsoleKeys=${keyValue.unrealEngineName}", flush: true);
} }
void joinLocalhost() { void joinLocalhost() {
@@ -73,18 +110,19 @@ class BackendController extends GetxController {
void reset() async { void reset() async {
type.value = ServerType.values.elementAt(0); type.value = ServerType.values.elementAt(0);
for (final type in ServerType.values) { for (final type in ServerType.values) {
storage?.write("${type.name}_host", null); _storage?.write("${type.name}_host", null);
storage?.write("${type.name}_port", null); _storage?.write("${type.name}_port", null);
} }
host.text = type.value != ServerType.remote ? kDefaultBackendHost : ""; host.text = type.value != ServerType.remote ? kDefaultBackendHost : "";
port.text = kDefaultBackendPort.toString(); port.text = kDefaultBackendPort.toString();
gameServerAddress.text = "127.0.0.1"; gameServerAddress.text = "127.0.0.1";
consoleKey.value = _kDefaultConsoleKey;
detached.value = false; detached.value = false;
} }
String _readHost() { String _readHost() {
String? value = storage?.read("${type.value.name}_host"); String? value = _storage?.read("${type.value.name}_host");
if (value != null && value.isNotEmpty) { if (value != null && value.isNotEmpty) {
return value; return value;
} }
@@ -97,9 +135,9 @@ class BackendController extends GetxController {
} }
String _readPort() => String _readPort() =>
storage?.read("${type.value.name}_port") ?? kDefaultBackendPort.toString(); _storage?.read("${type.value.name}_port") ?? kDefaultBackendPort.toString();
Stream<ServerResult> start() async* { Stream<ServerResult> start({required void Function() onExit, required void Function(String) onError}) async* {
try { try {
if(started.value) { if(started.value) {
return; return;
@@ -144,7 +182,18 @@ class BackendController extends GetxController {
switch(serverType){ switch(serverType){
case ServerType.embedded: case ServerType.embedded:
final process = await startEmbeddedBackend(detached.value); final process = await startEmbeddedBackend(detached.value, onError: (errorMessage) {
if(started.value) {
started.value = false;
onError(errorMessage);
}
});
watchProcess(process.pid).then((_) {
if(started.value) {
started.value = false;
onExit();
}
});
embeddedProcessPid = process.pid; embeddedProcessPid = process.pid;
break; break;
case ServerType.remote: case ServerType.remote:
@@ -237,11 +286,14 @@ class BackendController extends GetxController {
} }
} }
Stream<ServerResult> toggle() async* { Stream<ServerResult> toggle({required void Function() onExit, required void Function(String) onError}) async* {
if(started()) { if(started()) {
yield* stop(); yield* stop();
}else { }else {
yield* start(); yield* start(
onExit: onExit,
onError: onError
);
} }
} }
} }

View File

@@ -0,0 +1,265 @@
import 'dart:async';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
import 'package:reboot_launcher/src/util/translations.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:version/version.dart';
import 'package:yaml/yaml.dart';
class DllController extends GetxController {
static const String storageName = "dll_storage";
late final GetStorage? _storage;
late final String originalDll;
late final TextEditingController gameServerDll;
late final TextEditingController unrealEngineConsoleDll;
late final TextEditingController backendDll;
late final TextEditingController memoryLeakDll;
late final TextEditingController gameServerPort;
late final Rx<UpdateTimer> timer;
late final TextEditingController url;
late final RxBool customGameServer;
late final RxnInt timestamp;
late final Map<String, Future<bool>> _operations;
late final Rx<UpdateStatus> status;
InfoBarEntry? infoBarEntry;
Future<bool>? _updater;
DllController() {
_storage = appWithNoStorage ? null : GetStorage(storageName);
gameServerDll = _createController("game_server", InjectableDll.reboot);
unrealEngineConsoleDll = _createController("unreal_engine_console", InjectableDll.console);
backendDll = _createController("backend", InjectableDll.cobalt);
memoryLeakDll = _createController("memory_leak", InjectableDll.memory);
gameServerPort = TextEditingController(text: _storage?.read("game_server_port") ?? kDefaultGameServerPort);
gameServerPort.addListener(() => _storage?.write("game_server_port", gameServerPort.text));
final timerIndex = _storage?.read("timer");
timer = Rx(timerIndex == null ? UpdateTimer.hour : UpdateTimer.values.elementAt(timerIndex));
timer.listen((value) => _storage?.write("timer", value.index));
url = TextEditingController(text: _storage?.read("update_url") ?? kRebootDownloadUrl);
url.addListener(() => _storage?.write("update_url", url.text));
status = Rx(UpdateStatus.waiting);
customGameServer = RxBool(_storage?.read("custom_game_server") ?? false);
customGameServer.listen((value) => _storage?.write("custom_game_server", value));
timestamp = RxnInt(_storage?.read("ts"));
timestamp.listen((value) => _storage?.write("ts", value));
_operations = {};
}
TextEditingController _createController(String key, InjectableDll dll) {
final controller = TextEditingController(text: _storage?.read(key) ?? _getDefaultPath(dll));
controller.addListener(() => _storage?.write(key, controller.text));
return controller;
}
void resetGame() {
gameServerDll.text = _getDefaultPath(InjectableDll.reboot);
unrealEngineConsoleDll.text = _getDefaultPath(InjectableDll.console);
backendDll.text = _getDefaultPath(InjectableDll.cobalt);
memoryLeakDll.text = _getDefaultPath(InjectableDll.memory);
}
void resetServer() {
gameServerPort.text = kDefaultGameServerPort;
timer.value = UpdateTimer.hour;
url.text = kRebootDownloadUrl;
status.value = UpdateStatus.waiting;
customGameServer.value = false;
timestamp.value = null;
updateGameServerDll();
}
Future<bool> updateGameServerDll({bool force = false, bool silent = false}) async {
if(_updater != null) {
return await _updater!;
}
final result = _updateGameServerDll(force, silent);
_updater = result;
return await result;
}
Future<bool> _updateGameServerDll(bool force, bool silent) async {
try {
if(customGameServer.value) {
status.value = UpdateStatus.success;
return true;
}
final needsUpdate = await hasRebootDllUpdate(
timestamp.value,
hours: timer.value.hours,
force: force
);
if(!needsUpdate) {
status.value = UpdateStatus.success;
return true;
}
if(!silent) {
infoBarEntry = showRebootInfoBar(
translations.downloadingDll("reboot"),
loading: true,
duration: null
);
}
timestamp.value = await downloadRebootDll(url.text);
status.value = UpdateStatus.success;
infoBarEntry?.close();
if(!silent) {
infoBarEntry = showRebootInfoBar(
translations.downloadDllSuccess("reboot"),
severity: InfoBarSeverity.success,
duration: infoBarShortDuration
);
}
return true;
}catch(message) {
infoBarEntry?.close();
var error = message.toString();
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
error = error.toLowerCase();
status.value = UpdateStatus.error;
showRebootInfoBar(
translations.downloadDllError("reboot.dll", error.toString()),
duration: infoBarLongDuration,
severity: InfoBarSeverity.error,
action: Button(
onPressed: () => updateGameServerDll(
force: true,
silent: silent
),
child: Text(translations.downloadDllRetry),
)
);
return false;
}finally {
_updater = null;
}
}
(File, bool) getInjectableData(InjectableDll dll) {
final defaultPath = canonicalize(_getDefaultPath(dll));
switch(dll){
case InjectableDll.reboot:
if(customGameServer.value) {
final file = File(gameServerDll.text);
if(file.existsSync()) {
return (file, true);
}
}
return (rebootDllFile, false);
case InjectableDll.console:
final ue4ConsoleFile = File(unrealEngineConsoleDll.text);
return (ue4ConsoleFile, canonicalize(ue4ConsoleFile.path) != defaultPath);
case InjectableDll.cobalt:
final backendFile = File(backendDll.text);
return (backendFile, canonicalize(backendFile.path) != defaultPath);
case InjectableDll.memory:
final memoryLeakFile = File(memoryLeakDll.text);
return (memoryLeakFile, canonicalize(memoryLeakFile.path) != defaultPath);
}
}
String _getDefaultPath(InjectableDll dll) => "${dllsDirectory.path}\\${dll.name}.dll";
Future<bool> downloadCriticalDllInteractive(String filePath, {bool silent = false}) {
log("[DLL] Asking for $filePath(silent: $silent)");
final old = _operations[filePath];
if(old != null) {
log("[DLL] Download task already exists");
return old;
}
log("[DLL] Creating new download task...");
final newRun = _downloadCriticalDllInteractive(filePath, silent);
_operations[filePath] = newRun;
return newRun;
}
Future<bool> _downloadCriticalDllInteractive(String filePath, bool silent) async {
final fileName = basename(filePath).toLowerCase();
log("[DLL] File name: $fileName");
InfoBarEntry? entry;
try {
if (fileName == "reboot.dll") {
log("[DLL] Downloading reboot.dll...");
return await updateGameServerDll(
silent: silent
);
}
if(File(filePath).existsSync()) {
log("[DLL] File already exists");
return true;
}
final fileNameWithoutExtension = basenameWithoutExtension(filePath);
if(!silent) {
entry = showRebootInfoBar(
translations.downloadingDll(fileNameWithoutExtension),
loading: true,
duration: null
);
}
await downloadCriticalDll(fileName, filePath);
entry?.close();
if(!silent) {
entry = await showRebootInfoBar(
translations.downloadDllSuccess(fileNameWithoutExtension),
severity: InfoBarSeverity.success,
duration: infoBarShortDuration
);
}
return true;
}catch(message) {
log("[DLL] Error: $message");
entry?.close();
var error = message.toString();
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
error = error.toLowerCase();
final completer = Completer();
await showRebootInfoBar(
translations.downloadDllError(fileName, error.toString()),
duration: infoBarLongDuration,
severity: InfoBarSeverity.error,
onDismissed: () => completer.complete(null),
action: Button(
onPressed: () async {
await downloadCriticalDllInteractive(filePath);
completer.complete(null);
},
child: Text(translations.downloadDllRetry),
)
);
await completer.future;
return false;
}finally {
_operations.remove(fileName);
}
}
}
extension _UpdateTimerExtension on UpdateTimer {
int get hours {
switch(this) {
case UpdateTimer.never:
return -1;
case UpdateTimer.hour:
return 1;
case UpdateTimer.day:
return 24;
case UpdateTimer.week:
return 24 * 7;
}
}
}

View File

@@ -1,18 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.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_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/util/keyboard.dart'; import 'package:reboot_launcher/main.dart';
import '../../main.dart';
class GameController extends GetxController { class GameController extends GetxController {
static const PhysicalKeyboardKey _kDefaultConsoleKey = PhysicalKeyboardKey(0x00070041); static const String storageName = "game_storage";
late final GetStorage? _storage; late final GetStorage? _storage;
late final TextEditingController username; late final TextEditingController username;
@@ -22,10 +18,9 @@ class GameController extends GetxController {
late final Rxn<FortniteVersion> _selectedVersion; late final Rxn<FortniteVersion> _selectedVersion;
late final RxBool started; late final RxBool started;
late final Rxn<GameInstance> instance; late final Rxn<GameInstance> instance;
late final Rx<PhysicalKeyboardKey> consoleKey;
GameController() { GameController() {
_storage = appWithNoStorage ? null : GetStorage("game_storage"); _storage = appWithNoStorage ? null : GetStorage(storageName);
Iterable decodedVersionsJson = jsonDecode(_storage?.read("versions") ?? "[]"); Iterable decodedVersionsJson = jsonDecode(_storage?.read("versions") ?? "[]");
final decodedVersions = decodedVersionsJson final decodedVersions = decodedVersionsJson
.map((entry) => FortniteVersion.fromJson(entry)) .map((entry) => FortniteVersion.fromJson(entry))
@@ -44,37 +39,6 @@ class GameController extends GetxController {
customLaunchArgs.addListener(() => _storage?.write("custom_launch_args", customLaunchArgs.text)); customLaunchArgs.addListener(() => _storage?.write("custom_launch_args", customLaunchArgs.text));
started = RxBool(false); started = RxBool(false);
instance = Rxn(); instance = Rxn();
consoleKey = Rx(_readConsoleKey());
_writeConsoleKey(consoleKey.value);
consoleKey.listen((newValue) {
_storage?.write("console_key", newValue.usbHidUsage);
_writeConsoleKey(newValue);
});
}
PhysicalKeyboardKey _readConsoleKey() {
final consoleKeyValue = _storage?.read("console_key");
if(consoleKeyValue == null) {
return _kDefaultConsoleKey;
}
final consoleKeyNumber = int.tryParse(consoleKeyValue.toString());
if(consoleKeyNumber == null) {
return _kDefaultConsoleKey;
}
final consoleKey = PhysicalKeyboardKey(consoleKeyNumber);
if(!consoleKey.isUnrealEngineKey) {
return _kDefaultConsoleKey;
}
return consoleKey;
}
Future<void> _writeConsoleKey(PhysicalKeyboardKey keyValue) async {
final defaultInput = File("${backendDirectory.path}\\CloudStorage\\DefaultInput.ini");
await defaultInput.parent.create(recursive: true);
await defaultInput.writeAsString("[/Script/Engine.InputSettings]\n+ConsoleKeys=Tilde\n+ConsoleKeys=${keyValue.unrealEngineName}", flush: true);
} }
void reset() { void reset() {
@@ -82,6 +46,7 @@ class GameController extends GetxController {
password.text = ""; password.text = "";
customLaunchArgs.text = ""; customLaunchArgs.text = "";
versions.value = []; versions.value = [];
_selectedVersion.value = null;
instance.value = null; instance.value = null;
} }

View File

@@ -12,6 +12,8 @@ import 'package:sync/semaphore.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class HostingController extends GetxController { class HostingController extends GetxController {
static const String storageName = "hosting_storage";
late final GetStorage? _storage; late final GetStorage? _storage;
late final String uuid; late final String uuid;
late final TextEditingController name; late final TextEditingController name;
@@ -32,7 +34,7 @@ class HostingController extends GetxController {
late final Semaphore _semaphore; late final Semaphore _semaphore;
HostingController() { HostingController() {
_storage = appWithNoStorage ? null : GetStorage("hosting_storage"); _storage = appWithNoStorage ? null : GetStorage(storageName);
uuid = _storage?.read("uuid") ?? const Uuid().v4(); uuid = _storage?.read("uuid") ?? const Uuid().v4();
_storage?.write("uuid", uuid); _storage?.write("uuid", uuid);
name = TextEditingController(text: _storage?.read("name")); name = TextEditingController(text: _storage?.read("name"));
@@ -138,10 +140,10 @@ class HostingController extends GetxController {
description.text = ""; description.text = "";
showPassword.value = false; showPassword.value = false;
discoverable.value = false; discoverable.value = false;
started.value = false;
instance.value = null; instance.value = null;
type.value = GameServerType.headless; type.value = GameServerType.headless;
autoRestart.value = true; autoRestart.value = true;
customLaunchArgs.text = "";
} }
FortniteServer? findServerById(String uuid) { FortniteServer? findServerById(String uuid) {

View File

@@ -15,37 +15,19 @@ import 'package:version/version.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
class SettingsController extends GetxController { class SettingsController extends GetxController {
static const String storageName = "settings_storage";
late final GetStorage? _storage; late final GetStorage? _storage;
late final String originalDll;
late final TextEditingController gameServerDll;
late final TextEditingController unrealEngineConsoleDll;
late final TextEditingController backendDll;
late final TextEditingController memoryLeakDll;
late final TextEditingController gameServerPort;
late final RxString language; late final RxString language;
late final Rx<ThemeMode> themeMode; late final Rx<ThemeMode> themeMode;
late final RxnInt timestamp;
late final Rx<UpdateStatus> status;
late final Rx<UpdateTimer> timer;
late final TextEditingController url;
late final RxBool customGameServer;
late final RxBool firstRun; late final RxBool firstRun;
late final Map<String, Future<bool>> _operations;
late double width; late double width;
late double height; late double height;
late double? offsetX; late double? offsetX;
late double? offsetY; late double? offsetY;
InfoBarEntry? infoBarEntry;
Future<bool>? _updater;
SettingsController() { SettingsController() {
_storage = appWithNoStorage ? null : GetStorage("settings_storage"); _storage = appWithNoStorage ? null : GetStorage(storageName);
gameServerDll = _createController("game_server", InjectableDll.reboot);
unrealEngineConsoleDll = _createController("unreal_engine_console", InjectableDll.console);
backendDll = _createController("backend", InjectableDll.cobalt);
memoryLeakDll = _createController("memory_leak", InjectableDll.memory);
gameServerPort = TextEditingController(text: _storage?.read("game_server_port") ?? kDefaultGameServerPort);
gameServerPort.addListener(() => _storage?.write("game_server_port", gameServerPort.text));
width = _storage?.read("width") ?? kDefaultWindowWidth; width = _storage?.read("width") ?? kDefaultWindowWidth;
height = _storage?.read("height") ?? kDefaultWindowHeight; height = _storage?.read("height") ?? kDefaultWindowHeight;
offsetX = _storage?.read("offset_x"); offsetX = _storage?.read("offset_x");
@@ -54,25 +36,8 @@ class SettingsController extends GetxController {
themeMode.listen((value) => _storage?.write("theme", value.index)); themeMode.listen((value) => _storage?.write("theme", value.index));
language = RxString(_storage?.read("language") ?? currentLocale); language = RxString(_storage?.read("language") ?? currentLocale);
language.listen((value) => _storage?.write("language", value)); language.listen((value) => _storage?.write("language", value));
timestamp = RxnInt(_storage?.read("ts"));
timestamp.listen((value) => _storage?.write("ts", value));
final timerIndex = _storage?.read("timer");
timer = Rx(timerIndex == null ? UpdateTimer.hour : UpdateTimer.values.elementAt(timerIndex));
timer.listen((value) => _storage?.write("timer", value.index));
url = TextEditingController(text: _storage?.read("update_url") ?? kRebootDownloadUrl);
url.addListener(() => _storage?.write("update_url", url.text));
status = Rx(UpdateStatus.waiting);
customGameServer = RxBool(_storage?.read("custom_game_server") ?? false);
customGameServer.listen((value) => _storage?.write("custom_game_server", value));
firstRun = RxBool(_storage?.read("first_run_tutorial") ?? true); firstRun = RxBool(_storage?.read("first_run_tutorial") ?? true);
firstRun.listen((value) => _storage?.write("first_run_tutorial", value)); firstRun.listen((value) => _storage?.write("first_run_tutorial", value));
_operations = {};
}
TextEditingController _createController(String key, InjectableDll dll) {
final controller = TextEditingController(text: _storage?.read(key) ?? _getDefaultPath(dll));
controller.addListener(() => _storage?.write(key, controller.text));
return controller;
} }
void saveWindowSize(Size size) { void saveWindowSize(Size size) {
@@ -87,32 +52,18 @@ class SettingsController extends GetxController {
_storage?.write("offset_y", offsetY); _storage?.write("offset_y", offsetY);
} }
void reset(){
gameServerDll.text = _getDefaultPath(InjectableDll.reboot);
unrealEngineConsoleDll.text = _getDefaultPath(InjectableDll.console);
backendDll.text = _getDefaultPath(InjectableDll.cobalt);
memoryLeakDll.text = _getDefaultPath(InjectableDll.memory);
gameServerPort.text = kDefaultGameServerPort;
timestamp.value = null;
timer.value = UpdateTimer.never;
url.text = kRebootDownloadUrl;
status.value = UpdateStatus.waiting;
customGameServer.value = false;
updateReboot();
}
Future<void> notifyLauncherUpdate() async { Future<void> notifyLauncherUpdate() async {
if(appVersion == null) { if (appVersion == null) {
return; return;
} }
final pubspec = await _getPubspecYaml(); final pubspec = await _getPubspecYaml();
if(pubspec == null) { if (pubspec == null) {
return; return;
} }
final latestVersion = Version.parse(pubspec["version"]); final latestVersion = Version.parse(pubspec["version"]);
if(latestVersion <= appVersion) { if (latestVersion <= appVersion) {
return; return;
} }
@@ -125,7 +76,8 @@ class SettingsController extends GetxController {
child: Text(translations.updateAvailableAction), child: Text(translations.updateAvailableAction),
onPressed: () { onPressed: () {
infoBar.close(); infoBar.close();
launchUrl(Uri.parse("https://github.com/Auties00/reboot_launcher/releases")); launchUrl(Uri.parse(
"https://github.com/Auties00/reboot_launcher/releases"));
}, },
) )
); );
@@ -133,201 +85,16 @@ class SettingsController extends GetxController {
Future<dynamic> _getPubspecYaml() async { Future<dynamic> _getPubspecYaml() async {
try { try {
final pubspecResponse = await http.get(Uri.parse("https://raw.githubusercontent.com/Auties00/reboot_launcher/master/gui/pubspec.yaml")); final pubspecResponse = await http.get(Uri.parse(
if(pubspecResponse.statusCode != 200) { "https://raw.githubusercontent.com/Auties00/reboot_launcher/master/gui/pubspec.yaml"));
if (pubspecResponse.statusCode != 200) {
return null; return null;
} }
return loadYaml(pubspecResponse.body); return loadYaml(pubspecResponse.body);
}catch(error) { } catch (error) {
log("[UPDATER] Cannot check for updates: $error"); log("[UPDATER] Cannot check for updates: $error");
return null; return null;
} }
} }
Future<bool> updateReboot({bool force = false, bool silent = false}) async {
if(_updater != null) {
return await _updater!;
}
final result = _updateReboot(force, silent);
_updater = result;
return await result;
}
Future<bool> _updateReboot(bool force, bool silent) async {
try {
if(customGameServer.value) {
status.value = UpdateStatus.success;
return true;
}
final needsUpdate = await hasRebootDllUpdate(
timestamp.value,
hours: timer.value.hours,
force: force
);
if(!needsUpdate) {
status.value = UpdateStatus.success;
return true;
}
if(!silent) {
infoBarEntry = showRebootInfoBar(
translations.downloadingDll("reboot"),
loading: true,
duration: null
);
}
timestamp.value = await downloadRebootDll(url.text);
status.value = UpdateStatus.success;
infoBarEntry?.close();
if(!silent) {
infoBarEntry = showRebootInfoBar(
translations.downloadDllSuccess("reboot"),
severity: InfoBarSeverity.success,
duration: infoBarShortDuration
);
}
return true;
}catch(message) {
infoBarEntry?.close();
var error = message.toString();
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
error = error.toLowerCase();
status.value = UpdateStatus.error;
showRebootInfoBar(
translations.downloadDllError("reboot.dll", error.toString()),
duration: infoBarLongDuration,
severity: InfoBarSeverity.error,
action: Button(
onPressed: () => updateReboot(
force: true,
silent: silent
),
child: Text(translations.downloadDllRetry),
)
);
return false;
}finally {
_updater = null;
}
}
(File, bool) getInjectableData(InjectableDll dll) {
final defaultPath = canonicalize(_getDefaultPath(dll));
switch(dll){
case InjectableDll.reboot:
if(customGameServer.value) {
final file = File(gameServerDll.text);
if(file.existsSync()) {
return (file, true);
}
}
return (rebootDllFile, false);
case InjectableDll.console:
final ue4ConsoleFile = File(unrealEngineConsoleDll.text);
return (ue4ConsoleFile, canonicalize(ue4ConsoleFile.path) != defaultPath);
case InjectableDll.cobalt:
final backendFile = File(backendDll.text);
return (backendFile, canonicalize(backendFile.path) != defaultPath);
case InjectableDll.memory:
final memoryLeakFile = File(memoryLeakDll.text);
return (memoryLeakFile, canonicalize(memoryLeakFile.path) != defaultPath);
}
}
String _getDefaultPath(InjectableDll dll) => "${dllsDirectory.path}\\${dll.name}.dll";
Future<bool> downloadCriticalDllInteractive(String filePath, {bool silent = false}) {
log("[DLL] Asking for $filePath(silent: $silent)");
final old = _operations[filePath];
if(old != null) {
log("[DLL] Download task already exists");
return old;
}
log("[DLL] Creating new download task...");
final newRun = _downloadCriticalDllInteractive(filePath, silent);
_operations[filePath] = newRun;
return newRun;
}
Future<bool> _downloadCriticalDllInteractive(String filePath, bool silent) async {
final fileName = basename(filePath).toLowerCase();
log("[DLL] File name: $fileName");
InfoBarEntry? entry;
try {
if (fileName == "reboot.dll") {
log("[DLL] Downloading reboot.dll...");
return await updateReboot(
silent: silent
);
}
if(File(filePath).existsSync()) {
log("[DLL] File already exists");
return true;
}
final fileNameWithoutExtension = basenameWithoutExtension(filePath);
if(!silent) {
entry = showRebootInfoBar(
translations.downloadingDll(fileNameWithoutExtension),
loading: true,
duration: null
);
}
await downloadCriticalDll(fileName, filePath);
entry?.close();
if(!silent) {
entry = await showRebootInfoBar(
translations.downloadDllSuccess(fileNameWithoutExtension),
severity: InfoBarSeverity.success,
duration: infoBarShortDuration
);
}
return true;
}catch(message) {
log("[DLL] Error: $message");
entry?.close();
var error = message.toString();
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
error = error.toLowerCase();
final completer = Completer();
await showRebootInfoBar(
translations.downloadDllError(fileName, error.toString()),
duration: infoBarLongDuration,
severity: InfoBarSeverity.error,
onDismissed: () => completer.complete(null),
action: Button(
onPressed: () async {
await downloadCriticalDllInteractive(filePath);
completer.complete(null);
},
child: Text(translations.downloadDllRetry),
)
);
await completer.future;
return false;
}finally {
_operations.remove(fileName);
}
}
}
extension _UpdateTimerExtension on UpdateTimer {
int get hours {
switch(this) {
case UpdateTimer.never:
return -1;
case UpdateTimer.hour:
return 1;
case UpdateTimer.day:
return 24;
case UpdateTimer.week:
return 24 * 7;
}
}
} }

View File

@@ -15,6 +15,7 @@ import 'package:reboot_launcher/src/page/pages.dart';
import 'package:reboot_launcher/src/util/cryptography.dart'; import 'package:reboot_launcher/src/util/cryptography.dart';
import 'package:reboot_launcher/src/util/matchmaker.dart'; import 'package:reboot_launcher/src/util/matchmaker.dart';
import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/util/translations.dart';
import 'package:url_launcher/url_launcher.dart';
final List<InfoBarEntry> _infoBars = []; final List<InfoBarEntry> _infoBars = [];
@@ -27,7 +28,27 @@ extension ServerControllerDialog on BackendController {
Future<bool> toggleInteractive() async { Future<bool> toggleInteractive() async {
cancelInteractive(); cancelInteractive();
final stream = toggle(); final stream = toggle(
onExit: () {
cancelInteractive();
_showRebootInfoBar(
translations.backendProcessError,
severity: InfoBarSeverity.error
);
},
onError: (errorMessage) {
cancelInteractive();
_showRebootInfoBar(
translations.backendErrorMessage,
severity: InfoBarSeverity.error,
duration: infoBarLongDuration,
action: Button(
onPressed: () => launchUrl(launcherLogFile.uri),
child: Text(translations.openLog),
)
);
}
);
final completer = Completer<bool>(); final completer = Completer<bool>();
InfoBarEntry? entry; InfoBarEntry? entry;
worker = stream.listen((event) { worker = stream.listen((event) {
@@ -54,19 +75,6 @@ extension ServerControllerDialog on BackendController {
duration: null duration: null
); );
case ServerResultType.startSuccess: case ServerResultType.startSuccess:
final embeddedProcessPid = this.embeddedProcessPid;
if(embeddedProcessPid != null) {
watchProcess(embeddedProcessPid).then((_) {
if(started.value) {
started.value = false;
_showRebootInfoBar(
translations.backendProcessError,
severity: InfoBarSeverity.error
);
}
});
}
return _showRebootInfoBar( return _showRebootInfoBar(
type.value == ServerType.local ? translations.checkedServer : translations.startedServer, type.value == ServerType.local ? translations.checkedServer : translations.startedServer,
severity: InfoBarSeverity.success severity: InfoBarSeverity.success

View File

@@ -43,7 +43,6 @@ class BackendPage extends RebootPage {
} }
class _BackendPageState extends RebootPageState<BackendPage> { class _BackendPageState extends RebootPageState<BackendPage> {
final GameController _gameController = Get.find<GameController>();
final BackendController _backendController = Get.find<BackendController>(); final BackendController _backendController = Get.find<BackendController>();
InfoBarEntry? _infoBarEntry; InfoBarEntry? _infoBarEntry;
@@ -56,7 +55,7 @@ class _BackendPageState extends RebootPageState<BackendPage> {
} }
if(keyEvent.physicalKey.isUnrealEngineKey) { if(keyEvent.physicalKey.isUnrealEngineKey) {
_gameController.consoleKey.value = keyEvent.physicalKey; _backendController.consoleKey.value = keyEvent.physicalKey;
} }
_infoBarEntry?.close(); _infoBarEntry?.close();
@@ -194,7 +193,7 @@ class _BackendPageState extends RebootPageState<BackendPage> {
duration: null duration: null
); );
}, },
child: Text(_gameController.consoleKey.value.unrealEnginePrettyName ?? ""), child: Text(_backendController.consoleKey.value.unrealEnginePrettyName ?? ""),
), ),
) )
); );

View File

@@ -10,6 +10,7 @@ import 'package:flutter/material.dart' show MaterialPage;
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/backend_controller.dart'; import 'package:reboot_launcher/src/controller/backend_controller.dart';
import 'package:reboot_launcher/src/controller/dll_controller.dart';
import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_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/messenger/abstract/dialog.dart'; import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
@@ -44,6 +45,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
final BackendController _backendController = Get.find<BackendController>(); final BackendController _backendController = Get.find<BackendController>();
final HostingController _hostingController = Get.find<HostingController>(); final HostingController _hostingController = Get.find<HostingController>();
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
final DllController _dllController = Get.find<DllController>();
final GlobalKey _searchKey = GlobalKey(); final GlobalKey _searchKey = GlobalKey();
final FocusNode _searchFocusNode = FocusNode(); final FocusNode _searchFocusNode = FocusNode();
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
@@ -134,9 +136,9 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
} }
for(final injectable in InjectableDll.values) { for(final injectable in InjectableDll.values) {
final (file, custom) = _settingsController.getInjectableData(injectable); final (file, custom) = _dllController.getInjectableData(injectable);
if(!custom) { if(!custom) {
_settingsController.downloadCriticalDllInteractive( _dllController.downloadCriticalDllInteractive(
file.path, file.path,
silent: true silent: true
); );
@@ -144,7 +146,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
} }
watchDlls().listen((filePath) => showDllDeletedDialog(() { watchDlls().listen((filePath) => showDllDeletedDialog(() {
_settingsController.downloadCriticalDllInteractive(filePath); _dllController.downloadCriticalDllInteractive(filePath);
})); }));
} }

View File

@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/controller/dll_controller.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart';
@@ -53,6 +54,7 @@ class _HostingPageState extends RebootPageState<HostPage> {
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final HostingController _hostingController = Get.find<HostingController>(); final HostingController _hostingController = Get.find<HostingController>();
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
final DllController _dllController = Get.find<DllController>();
late final RxBool _showPasswordTrailing = RxBool(_hostingController.password.text.isNotEmpty); late final RxBool _showPasswordTrailing = RxBool(_hostingController.password.text.isNotEmpty);
@@ -257,7 +259,7 @@ class _HostingPageState extends RebootPageState<HostPage> {
contentWidth: 64, contentWidth: 64,
content: TextFormBox( content: TextFormBox(
placeholder: translations.settingsServerPortName, placeholder: translations.settingsServerPortName,
controller: _settingsController.gameServerPort, controller: _dllController.gameServerPort,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
textAlign: TextAlign.center, textAlign: TextAlign.center,
inputFormatters: [ inputFormatters: [
@@ -284,22 +286,22 @@ class _HostingPageState extends RebootPageState<HostPage> {
content: Obx(() => DropDownButton( content: Obx(() => DropDownButton(
onOpen: () => inDialog = true, onOpen: () => inDialog = true,
onClose: () => inDialog = false, onClose: () => inDialog = false,
leading: Text(_settingsController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName), leading: Text(_dllController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName),
items: { items: {
false: translations.settingsServerTypeEmbeddedName, false: translations.settingsServerTypeEmbeddedName,
true: translations.settingsServerTypeCustomName true: translations.settingsServerTypeCustomName
}.entries.map((entry) => MenuFlyoutItem( }.entries.map((entry) => MenuFlyoutItem(
text: Text(entry.value), text: Text(entry.value),
onPressed: () { onPressed: () {
final oldValue = _settingsController.customGameServer.value; final oldValue = _dllController.customGameServer.value;
if(oldValue == entry.key) { if(oldValue == entry.key) {
return; return;
} }
_settingsController.customGameServer.value = entry.key; _dllController.customGameServer.value = entry.key;
_settingsController.infoBarEntry?.close(); _dllController.infoBarEntry?.close();
if(!entry.key) { if(!entry.key) {
_settingsController.updateReboot( _dllController.updateGameServerDll(
force: true force: true
); );
} }
@@ -308,18 +310,18 @@ class _HostingPageState extends RebootPageState<HostPage> {
)) ))
), ),
Obx(() { Obx(() {
if(!_settingsController.customGameServer.value) { if(!_dllController.customGameServer.value) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return createFileSetting( return createFileSetting(
title: translations.settingsServerFileName, title: translations.settingsServerFileName,
description: translations.settingsServerFileDescription, description: translations.settingsServerFileDescription,
controller: _settingsController.gameServerDll controller: _dllController.gameServerDll
); );
}), }),
Obx(() { Obx(() {
if(_settingsController.customGameServer.value) { if(_dllController.customGameServer.value) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@@ -331,13 +333,13 @@ class _HostingPageState extends RebootPageState<HostPage> {
subtitle: Text(translations.settingsServerMirrorDescription), subtitle: Text(translations.settingsServerMirrorDescription),
content: TextFormBox( content: TextFormBox(
placeholder: translations.settingsServerMirrorPlaceholder, placeholder: translations.settingsServerMirrorPlaceholder,
controller: _settingsController.url, controller: _dllController.url,
validator: _checkUpdateUrl validator: _checkUpdateUrl
) )
); );
}), }),
Obx(() { Obx(() {
if(_settingsController.customGameServer.value) { if(_dllController.customGameServer.value) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@@ -350,13 +352,13 @@ class _HostingPageState extends RebootPageState<HostPage> {
content: Obx(() => DropDownButton( content: Obx(() => DropDownButton(
onOpen: () => inDialog = true, onOpen: () => inDialog = true,
onClose: () => inDialog = false, onClose: () => inDialog = false,
leading: Text(_settingsController.timer.value.text), leading: Text(_dllController.timer.value.text),
items: UpdateTimer.values.map((entry) => MenuFlyoutItem( items: UpdateTimer.values.map((entry) => MenuFlyoutItem(
text: Text(entry.text), text: Text(entry.text),
onPressed: () { onPressed: () {
_settingsController.timer.value = entry; _dllController.timer.value = entry;
_settingsController.infoBarEntry?.close(); _dllController.infoBarEntry?.close();
_settingsController.updateReboot( _dllController.updateGameServerDll(
force: true force: true
); );
} }
@@ -431,7 +433,10 @@ class _HostingPageState extends RebootPageState<HostPage> {
title: Text(translations.hostResetName), title: Text(translations.hostResetName),
subtitle: Text(translations.hostResetDescription), subtitle: Text(translations.hostResetDescription),
content: Button( content: Button(
onPressed: () => showResetDialog(_hostingController.reset), onPressed: () => showResetDialog(() {
_hostingController.reset();
_dllController.resetServer();
}),
child: Text(translations.hostResetContent), child: Text(translations.hostResetContent),
) )
); );

View File

@@ -1,9 +1,11 @@
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/dll_controller.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/game_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/messenger/abstract/overlay.dart'; import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
import 'package:reboot_launcher/src/messenger/implementation/data.dart';
import 'package:reboot_launcher/src/messenger/implementation/onboard.dart'; import 'package:reboot_launcher/src/messenger/implementation/onboard.dart';
import 'package:reboot_launcher/src/page/abstract/page.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/abstract/page_type.dart';
@@ -37,6 +39,7 @@ class PlayPage extends RebootPage {
class _PlayPageState extends RebootPageState<PlayPage> { class _PlayPageState extends RebootPageState<PlayPage> {
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final DllController _dllController = Get.find<DllController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -94,6 +97,7 @@ class _PlayPageState extends RebootPageState<PlayPage> {
), ),
_options, _options,
_internalFiles, _internalFiles,
_resetDefaults
]; ];
SettingTile get _internalFiles => SettingTile( SettingTile get _internalFiles => SettingTile(
@@ -106,17 +110,17 @@ class _PlayPageState extends RebootPageState<PlayPage> {
createFileSetting( createFileSetting(
title: translations.settingsClientConsoleName, title: translations.settingsClientConsoleName,
description: translations.settingsClientConsoleDescription, description: translations.settingsClientConsoleDescription,
controller: _settingsController.unrealEngineConsoleDll controller: _dllController.unrealEngineConsoleDll
), ),
createFileSetting( createFileSetting(
title: translations.settingsClientAuthName, title: translations.settingsClientAuthName,
description: translations.settingsClientAuthDescription, description: translations.settingsClientAuthDescription,
controller: _settingsController.backendDll controller: _dllController.backendDll
), ),
createFileSetting( createFileSetting(
title: translations.settingsClientMemoryName, title: translations.settingsClientMemoryName,
description: translations.settingsClientMemoryDescription, description: translations.settingsClientMemoryDescription,
controller: _settingsController.memoryLeakDll controller: _dllController.memoryLeakDll
), ),
], ],
); );
@@ -141,4 +145,19 @@ class _PlayPageState extends RebootPageState<PlayPage> {
) )
] ]
); );
SettingTile get _resetDefaults => SettingTile(
icon: Icon(
FluentIcons.arrow_reset_24_regular
),
title: Text(translations.gameResetDefaultsName),
subtitle: Text(translations.gameResetDefaultsDescription),
content: Button(
onPressed: () => showResetDialog(() {
_gameController.reset();
_dllController.resetGame();
}),
child: Text(translations.gameResetDefaultsContent),
)
);
} }

View File

@@ -42,7 +42,6 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
List<Widget> get settings => [ List<Widget> get settings => [
_language, _language,
_theme, _theme,
_resetDefaults,
_installationDirectory _installationDirectory
]; ];
@@ -89,18 +88,6 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
)) ))
); );
SettingTile get _resetDefaults => SettingTile(
icon: Icon(
FluentIcons.arrow_reset_24_regular
),
title: Text(translations.settingsUtilsResetDefaultsName),
subtitle: Text(translations.settingsUtilsResetDefaultsSubtitle),
content: Button(
onPressed: () => showResetDialog(_settingsController.reset),
child: Text(translations.settingsUtilsResetDefaultsContent),
)
);
SettingTile get _installationDirectory => SettingTile( SettingTile get _installationDirectory => SettingTile(
icon: Icon( icon: Icon(
FluentIcons.folder_24_regular FluentIcons.folder_24_regular

View File

@@ -9,6 +9,7 @@ import 'package:local_notifier/local_notifier.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/backend_controller.dart'; import 'package:reboot_launcher/src/controller/backend_controller.dart';
import 'package:reboot_launcher/src/controller/dll_controller.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart';
@@ -39,7 +40,7 @@ class _LaunchButtonState extends State<LaunchButton> {
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final HostingController _hostingController = Get.find<HostingController>(); final HostingController _hostingController = Get.find<HostingController>();
final BackendController _backendController = Get.find<BackendController>(); final BackendController _backendController = Get.find<BackendController>();
final SettingsController _settingsController = Get.find<SettingsController>(); final DllController _dllController = Get.find<DllController>();
InfoBarEntry? _gameClientInfoBar; InfoBarEntry? _gameClientInfoBar;
InfoBarEntry? _gameServerInfoBar; InfoBarEntry? _gameServerInfoBar;
@@ -263,15 +264,21 @@ class _LaunchButtonState extends State<LaunchButton> {
"OPENSSL_ia32cap": "~0x20000000" "OPENSSL_ia32cap": "~0x20000000"
} }
); );
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
void onGameOutput(String line, bool error) { void onGameOutput(String line, bool error) {
log("[${host ? 'HOST' : 'GAME'}] ${error ? '[ERROR]' : '[MESSAGE]'} $line"); log("[${host ? 'HOST' : 'GAME'}] ${error ? '[ERROR]' : '[MESSAGE]'} $line");
handleGameOutput( handleGameOutput(
line: line, line: line,
host: host, host: host,
onShutdown: () => _onStop(reason: _StopReason.normal), onShutdown: () => _onStop(reason: _StopReason.normal),
onTokenError: () => _onStop(reason: _StopReason.tokenError), onTokenError: () => _onStop(reason: _StopReason.tokenError),
onBuildCorrupted: () => _onStop(reason: _StopReason.corruptedVersionError), onBuildCorrupted: () {
if(instance?.launched == false) {
_onStop(reason: _StopReason.corruptedVersionError);
}else {
_onStop(reason: _StopReason.crash);
}
},
onLoggedIn: () =>_onLoggedIn(host), onLoggedIn: () =>_onLoggedIn(host),
onMatchEnd: () => _onMatchEnd(version), onMatchEnd: () => _onMatchEnd(version),
onDisplayAttached: () => _onDisplayAttached(host, hostType, version) onDisplayAttached: () => _onDisplayAttached(host, hostType, version)
@@ -391,7 +398,7 @@ class _LaunchButtonState extends State<LaunchButton> {
await _injectOrShowError(InjectableDll.console, host); await _injectOrShowError(InjectableDll.console, host);
_onGameClientInjected(); _onGameClientInjected();
}else { }else {
final gameServerPort = int.tryParse(_settingsController.gameServerPort.text); final gameServerPort = int.tryParse(_dllController.gameServerPort.text);
if(gameServerPort != null) { if(gameServerPort != null) {
await killProcessByPort(gameServerPort); await killProcessByPort(gameServerPort);
} }
@@ -424,7 +431,7 @@ class _LaunchButtonState extends State<LaunchButton> {
loading: true, loading: true,
duration: null duration: null
); );
final gameServerPort = _settingsController.gameServerPort.text; final gameServerPort = _dllController.gameServerPort.text;
final localPingResult = await pingGameServer( final localPingResult = await pingGameServer(
"127.0.0.1:$gameServerPort", "127.0.0.1:$gameServerPort",
timeout: const Duration(minutes: 2) timeout: const Duration(minutes: 2)
@@ -605,6 +612,7 @@ class _LaunchButtonState extends State<LaunchButton> {
); );
break; break;
case _StopReason.tokenError: case _StopReason.tokenError:
_backendController.stop();
showRebootInfoBar( showRebootInfoBar(
translations.tokenError(instance?.injectedDlls.map((element) => element.name).join(", ") ?? translations.none), translations.tokenError(instance?.injectedDlls.map((element) => element.name).join(", ") ?? translations.none),
severity: InfoBarSeverity.error, severity: InfoBarSeverity.error,
@@ -615,6 +623,13 @@ class _LaunchButtonState extends State<LaunchButton> {
) )
); );
break; break;
case _StopReason.crash:
showRebootInfoBar(
translations.fortniteCrashError(host ? "game server" : "client"),
severity: InfoBarSeverity.error,
duration: infoBarLongDuration,
);
break;
case _StopReason.unknownError: case _StopReason.unknownError:
showRebootInfoBar( showRebootInfoBar(
translations.unknownFortniteError(error ?? translations.unknownError), translations.unknownFortniteError(error ?? translations.unknownError),
@@ -664,7 +679,7 @@ class _LaunchButtonState extends State<LaunchButton> {
Future<File?> _getDllFileOrStop(InjectableDll injectable, bool host, [bool isRetry = false]) async { Future<File?> _getDllFileOrStop(InjectableDll injectable, bool host, [bool isRetry = false]) async {
log("[${host ? 'HOST' : 'GAME'}] Checking dll ${injectable}..."); log("[${host ? 'HOST' : 'GAME'}] Checking dll ${injectable}...");
final (file, customDll) = _settingsController.getInjectableData(injectable); final (file, customDll) = _dllController.getInjectableData(injectable);
log("[${host ? 'HOST' : 'GAME'}] Path: ${file.path}, custom: $customDll"); log("[${host ? 'HOST' : 'GAME'}] Path: ${file.path}, custom: $customDll");
if(await file.exists()) { if(await file.exists()) {
log("[${host ? 'HOST' : 'GAME'}] Path exists"); log("[${host ? 'HOST' : 'GAME'}] Path exists");
@@ -678,7 +693,7 @@ class _LaunchButtonState extends State<LaunchButton> {
} }
log("[${host ? 'HOST' : 'GAME'}] Path does not exist, downloading critical dll again..."); log("[${host ? 'HOST' : 'GAME'}] Path does not exist, downloading critical dll again...");
await _settingsController.downloadCriticalDllInteractive(file.path); await _dllController.downloadCriticalDllInteractive(file.path);
log("[${host ? 'HOST' : 'GAME'}] Downloaded dll again, retrying check..."); log("[${host ? 'HOST' : 'GAME'}] Downloaded dll again, retrying check...");
return _getDllFileOrStop(injectable, host, true); return _getDllFileOrStop(injectable, host, true);
} }
@@ -731,7 +746,8 @@ enum _StopReason {
matchmakerError, matchmakerError,
tokenError, tokenError,
unknownError, unknownError,
exitCode; exitCode,
crash;
bool get isError => name.contains("Error"); bool get isError => name.contains("Error");
} }

View File

@@ -1,6 +1,6 @@
name: reboot_launcher name: reboot_launcher
description: Graphical User Interface for Project Reboot description: Graphical User Interface for Project Reboot
version: "9.2.4" version: "9.2.5"
publish_to: 'none' publish_to: 'none'