mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 19:22:22 +01:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62dae468bf | ||
|
|
a9af28273a | ||
|
|
232bf8fbfc | ||
|
|
a787c4efc9 | ||
|
|
4c3fe9bc65 | ||
|
|
3f88d5ed80 | ||
|
|
582270849e | ||
|
|
1ef4e76768 | ||
|
|
cd8c8e6dd9 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
backend/**/* linguist-vendored
|
||||||
@@ -8,6 +8,8 @@ Join our discord at https://discord.gg/reboot
|
|||||||
- COMMON: Shared business logic for CLI and GUI modules
|
- COMMON: Shared business logic for CLI and GUI modules
|
||||||
- CLI: Work in progress command line interface to host a Fortnite Server on a Windows VPS easily, developed in Dart
|
- CLI: Work in progress command line interface to host a Fortnite Server on a Windows VPS easily, developed in Dart
|
||||||
- GUI: Stable graphical user interface to play and host Fortnite S0-14
|
- GUI: Stable graphical user interface to play and host Fortnite S0-14
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
2
backend/index.js
vendored
2
backend/index.js
vendored
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,19 @@ final Semaphore _semaphore = Semaphore();
|
|||||||
String? _lastIp;
|
String? _lastIp;
|
||||||
String? _lastPort;
|
String? _lastPort;
|
||||||
|
|
||||||
Future<Process> startEmbeddedBackend(bool detached) async => startProcess(
|
Future<Process> startEmbeddedBackend(bool detached, {void Function(String)? onError}) async {
|
||||||
|
final process = await startProcess(
|
||||||
executable: backendStartExecutable,
|
executable: backendStartExecutable,
|
||||||
window: detached,
|
window: detached,
|
||||||
);
|
);
|
||||||
|
process.stdOutput.listen((message) => log("[BACKEND] Message: $message"));
|
||||||
|
process.stdError.listen((error) {
|
||||||
|
log("[BACKEND] Error: $error");
|
||||||
|
onError?.call(error);
|
||||||
|
});
|
||||||
|
process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode"));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
Future<HttpServer> startRemoteBackendProxy(Uri uri) async => await serve(proxyHandler(uri), kDefaultBackendHost, kDefaultBackendPort);
|
Future<HttpServer> startRemoteBackendProxy(Uri uri) async => await serve(proxyHandler(uri), kDefaultBackendHost, kDefaultBackendPort);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ final File launcherLogFile = _createLoggingFile();
|
|||||||
final Semaphore _semaphore = Semaphore(1);
|
final Semaphore _semaphore = Semaphore(1);
|
||||||
|
|
||||||
File _createLoggingFile() {
|
File _createLoggingFile() {
|
||||||
final file = File("${logsDirectory.path}\\launcher.log");
|
final file = File("${installationDirectory.path}\\launcher.log");
|
||||||
file.parent.createSync(recursive: true);
|
file.parent.createSync(recursive: true);
|
||||||
if(file.existsSync()) {
|
if(file.existsSync()) {
|
||||||
file.deleteSync();
|
file.deleteSync();
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ Directory get assetsDirectory {
|
|||||||
return installationDirectory;
|
return installationDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory get logsDirectory =>
|
|
||||||
Directory("${installationDirectory.path}\\logs");
|
|
||||||
|
|
||||||
Directory get settingsDirectory =>
|
Directory get settingsDirectory =>
|
||||||
Directory("${installationDirectory.path}\\settings");
|
Directory("${installationDirectory.path}\\settings");
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// ignore_for_file: non_constant_identifier_names
|
// ignore_for_file: non_constant_identifier_names
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
@@ -9,6 +10,7 @@ import 'dart:math';
|
|||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
|
import 'package:reboot_common/src/util/log.dart';
|
||||||
import 'package:sync/semaphore.dart';
|
import 'package:sync/semaphore.dart';
|
||||||
import 'package:win32/win32.dart';
|
import 'package:win32/win32.dart';
|
||||||
|
|
||||||
@@ -100,58 +102,49 @@ 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 {
|
||||||
|
log("[PROCESS] Starting process on ${executable.path} with $args (useTempBatch: $useTempBatch, window: $window, name: $name, environment: $environment)");
|
||||||
final argsOrEmpty = args ?? [];
|
final argsOrEmpty = args ?? [];
|
||||||
|
final workingDirectory = _getWorkingDirectory(executable);
|
||||||
if(useTempBatch) {
|
if(useTempBatch) {
|
||||||
final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process");
|
final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process");
|
||||||
final tempScriptFile = File("${tempScriptDirectory.path}/process.bat");
|
final tempScriptFile = File("${tempScriptDirectory.path}\\process.bat");
|
||||||
final command = window ? 'cmd.exe /k ""${executable.path}" ${argsOrEmpty.join(" ")}"' : '"${executable.path}" ${argsOrEmpty.join(" ")}';
|
final command = window ? 'cmd.exe /k ""${executable.path}" ${argsOrEmpty.join(" ")}"' : '"${executable.path}" ${argsOrEmpty.join(" ")}';
|
||||||
await tempScriptFile.writeAsString(command, flush: true);
|
await tempScriptFile.writeAsString(command, flush: true);
|
||||||
final process = await Process.start(
|
final process = await Process.start(
|
||||||
tempScriptFile.path,
|
tempScriptFile.path,
|
||||||
[],
|
[],
|
||||||
workingDirectory: executable.parent.path,
|
workingDirectory: workingDirectory,
|
||||||
environment: environment,
|
environment: environment,
|
||||||
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
|
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
|
||||||
runInShell: window
|
runInShell: window
|
||||||
);
|
);
|
||||||
return _withLogger(name, executable, process, window);
|
return _ExtendedProcess(process, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
final process = await Process.start(
|
final process = await Process.start(
|
||||||
executable.path,
|
executable.path,
|
||||||
args ?? [],
|
args ?? [],
|
||||||
workingDirectory: executable.parent.path,
|
workingDirectory: workingDirectory,
|
||||||
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
|
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
|
||||||
runInShell: window
|
runInShell: window
|
||||||
);
|
);
|
||||||
return _withLogger(name, executable, process, window);
|
return _ExtendedProcess(process, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ExtendedProcess _withLogger(String? name, File executable, Process process, bool window) {
|
String? _getWorkingDirectory(File executable) {
|
||||||
final extendedProcess = _ExtendedProcess(process, true);
|
try {
|
||||||
final loggingFile = File("${logsDirectory.path}\\${name ?? path.basenameWithoutExtension(executable.path)}-${DateTime.now().millisecondsSinceEpoch}.log");
|
log("[PROCESS] Calculating working directory for $executable");
|
||||||
loggingFile.parent.createSync(recursive: true);
|
final workingDirectory = executable.parent.resolveSymbolicLinksSync();
|
||||||
if(loggingFile.existsSync()) {
|
log("[PROCESS] Using working directory: $workingDirectory");
|
||||||
loggingFile.deleteSync();
|
return workingDirectory;
|
||||||
|
}catch(error) {
|
||||||
|
log("[PROCESS] Cannot infer working directory: $error");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final semaphore = Semaphore(1);
|
|
||||||
void logEvent(String event) async {
|
|
||||||
await semaphore.acquire();
|
|
||||||
await loggingFile.writeAsString("$event\n", mode: FileMode.append, flush: true);
|
|
||||||
semaphore.release();
|
|
||||||
}
|
|
||||||
extendedProcess.stdOutput.listen(logEvent);
|
|
||||||
extendedProcess.stdError.listen(logEvent);
|
|
||||||
if(!window) {
|
|
||||||
extendedProcess.exitCode.then((value) => logEvent("Process terminated with exit code: $value\n"));
|
|
||||||
}
|
|
||||||
return extendedProcess;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final _NtResumeProcess = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
|
final _NtResumeProcess = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
|
||||||
@@ -203,47 +196,61 @@ Future<bool> watchProcess(int pid) async {
|
|||||||
return await completer.future;
|
return await completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Template
|
List<String> createRebootArgs(String username, String password, bool host, GameServerType hostType, bool logging, String additionalArgs) {
|
||||||
List<String> createRebootArgs(String username, String password, bool host, GameServerType hostType, bool log, String additionalArgs) {
|
log("[PROCESS] Generating reboot args");
|
||||||
if(password.isEmpty) {
|
if(password.isEmpty) {
|
||||||
username = '${_parseUsername(username, host)}@projectreboot.dev';
|
username = '${_parseUsername(username, host)}@projectreboot.dev';
|
||||||
}
|
}
|
||||||
|
|
||||||
password = password.isNotEmpty ? password : "Rebooted";
|
password = password.isNotEmpty ? password : "Rebooted";
|
||||||
final args = [
|
final args = LinkedHashMap<String, String>(
|
||||||
"-epicapp=Fortnite",
|
equals: (a, b) => a.toUpperCase() == b.toUpperCase(),
|
||||||
"-epicenv=Prod",
|
hashCode: (a) => a.toUpperCase().hashCode
|
||||||
"-epiclocale=en-us",
|
);
|
||||||
"-epicportal",
|
args.addAll({
|
||||||
"-skippatchcheck",
|
"-epicapp": "Fortnite",
|
||||||
"-nobe",
|
"-epicenv": "Prod",
|
||||||
"-fromfl=eac",
|
"-epiclocale": "en-us",
|
||||||
"-fltoken=3db3ba5dcbd2e16703f3978d",
|
"-epicportal": "",
|
||||||
"-caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
|
"-skippatchcheck": "",
|
||||||
"-AUTH_LOGIN=$username",
|
"-nobe": "",
|
||||||
"-AUTH_PASSWORD=${password.isNotEmpty ? password : "Rebooted"}",
|
"-fromfl": "eac",
|
||||||
"-AUTH_TYPE=epic"
|
"-fltoken": "3db3ba5dcbd2e16703f3978d",
|
||||||
];
|
"-caldera": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
|
||||||
|
"-AUTH_LOGIN": username,
|
||||||
|
"-AUTH_PASSWORD": password.isNotEmpty ? password : "Rebooted",
|
||||||
|
"-AUTH_TYPE": "epic"
|
||||||
|
});
|
||||||
|
|
||||||
if(log) {
|
if(logging) {
|
||||||
args.add("-log");
|
args["-log"] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(host) {
|
if(host) {
|
||||||
args.addAll([
|
args["-nosplash"] = "";
|
||||||
"-nosplash",
|
args["-nosound"] = "";
|
||||||
"-nosound"
|
|
||||||
]);
|
|
||||||
if(hostType == GameServerType.headless){
|
if(hostType == GameServerType.headless){
|
||||||
args.add("-nullrhi");
|
args["-nullrhi"] = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(additionalArgs.isNotEmpty){
|
log("[PROCESS] Default args: $args");
|
||||||
args.addAll(additionalArgs.split(" "));
|
log("[PROCESS] Adding custom args: $additionalArgs");
|
||||||
|
for(final additionalArg in additionalArgs.split(" ")) {
|
||||||
|
log("[PROCESS] Processing custom arg: $additionalArg");
|
||||||
|
final separatorIndex = additionalArg.indexOf("=");
|
||||||
|
final argName = separatorIndex == -1 ? additionalArg : additionalArg.substring(0, separatorIndex);
|
||||||
|
log("[PROCESS] Custom arg key: $argName");
|
||||||
|
final argValue = separatorIndex == -1 || separatorIndex + 1 >= additionalArg.length ? "" : additionalArg.substring(separatorIndex + 1);
|
||||||
|
log("[PROCESS] Custom arg value: $argValue");
|
||||||
|
args[argName] = argValue;
|
||||||
|
log("[PROCESS] Updated args: $args");
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
log("[PROCESS] Final args result: $args");
|
||||||
|
return args.entries
|
||||||
|
.map((entry) => entry.value.isEmpty ? entry.key : "${entry.key}=${entry.value}")
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleGameOutput({
|
void handleGameOutput({
|
||||||
@@ -257,16 +264,22 @@ void handleGameOutput({
|
|||||||
required void Function() onBuildCorrupted,
|
required void Function() onBuildCorrupted,
|
||||||
}) {
|
}) {
|
||||||
if (line.contains(kShutdownLine)) {
|
if (line.contains(kShutdownLine)) {
|
||||||
|
log("[FORTNITE_OUTPUT_HANDLER] Detected shutdown: $line");
|
||||||
onShutdown();
|
onShutdown();
|
||||||
}else if(kCorruptedBuildErrors.any((element) => line.contains(element))){
|
}else if(kCorruptedBuildErrors.any((element) => line.contains(element))){
|
||||||
|
log("[FORTNITE_OUTPUT_HANDLER] Detected corrupt build: $line");
|
||||||
onBuildCorrupted();
|
onBuildCorrupted();
|
||||||
}else if(kCannotConnectErrors.any((element) => line.contains(element))){
|
}else if(kCannotConnectErrors.any((element) => line.contains(element))){
|
||||||
|
log("[FORTNITE_OUTPUT_HANDLER] Detected cannot connect error: $line");
|
||||||
onTokenError();
|
onTokenError();
|
||||||
}else if(kLoggedInLines.every((entry) => line.contains(entry))) {
|
}else if(kLoggedInLines.every((entry) => line.contains(entry))) {
|
||||||
|
log("[FORTNITE_OUTPUT_HANDLER] Detected logged in: $line");
|
||||||
onLoggedIn();
|
onLoggedIn();
|
||||||
}else if(line.contains(kGameFinishedLine) && host) {
|
}else if(line.contains(kGameFinishedLine) && host) {
|
||||||
|
log("[FORTNITE_OUTPUT_HANDLER] Detected match end: $line");
|
||||||
onMatchEnd();
|
onMatchEnd();
|
||||||
}else if(line.contains(kDisplayInitializedLine) && host) {
|
}else if(line.contains(kDisplayInitializedLine) && host) {
|
||||||
|
log("[FORTNITE_OUTPUT_HANDLER] Detected display attach: $line");
|
||||||
onDisplayAttached();
|
onDisplayAttached();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,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.
@@ -79,7 +79,7 @@
|
|||||||
"settingsClientDescription": "Configure the internal files used by the launcher for Fortnite",
|
"settingsClientDescription": "Configure the internal files used by the launcher for Fortnite",
|
||||||
"settingsClientOptionsName": "Options",
|
"settingsClientOptionsName": "Options",
|
||||||
"settingsClientOptionsDescription": "Configure additional options for Fortnite",
|
"settingsClientOptionsDescription": "Configure additional options for Fortnite",
|
||||||
"settingsClientConsoleName": "Unreal engine console",
|
"settingsClientConsoleName": "Unreal engine patcher",
|
||||||
"settingsClientConsoleDescription": "Unlocks the Unreal Engine Console",
|
"settingsClientConsoleDescription": "Unlocks the Unreal Engine Console",
|
||||||
"settingsClientConsoleKeyName": "Unreal engine console key",
|
"settingsClientConsoleKeyName": "Unreal engine console key",
|
||||||
"settingsClientConsoleKeyDescription": "The keyboard key used to open the Unreal Engine console",
|
"settingsClientConsoleKeyDescription": "The keyboard key used to open the Unreal Engine console",
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
"settingsClientMemoryName": "Memory patcher",
|
"settingsClientMemoryName": "Memory patcher",
|
||||||
"settingsClientMemoryDescription": "Prevents the client from crashing because of a memory leak",
|
"settingsClientMemoryDescription": "Prevents the client from crashing because of a memory leak",
|
||||||
"settingsClientArgsName": "Custom launch arguments",
|
"settingsClientArgsName": "Custom launch arguments",
|
||||||
"settingsClientArgsDescription": "Additional arguments to use when launching the game",
|
"settingsClientArgsDescription": "Additional arguments to use when launching Fortnite",
|
||||||
"settingsClientArgsPlaceholder": "Arguments...",
|
"settingsClientArgsPlaceholder": "Arguments...",
|
||||||
"settingsServerName": "Internal files",
|
"settingsServerName": "Internal files",
|
||||||
"settingsServerSubtitle": "Configure the internal files used by the launcher for the game server",
|
"settingsServerSubtitle": "Configure the internal files used by the launcher for the game server",
|
||||||
@@ -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",
|
||||||
@@ -147,6 +145,7 @@
|
|||||||
"defaultServerName": "Reboot Game Server",
|
"defaultServerName": "Reboot Game Server",
|
||||||
"defaultServerDescription": "Just another server",
|
"defaultServerDescription": "Just another server",
|
||||||
"downloadingDll": "Downloading {name} dll...",
|
"downloadingDll": "Downloading {name} dll...",
|
||||||
|
"dllAlreadyExists": "The {name} was already downloaded",
|
||||||
"downloadDllSuccess": "The {name} dll was downloaded successfully",
|
"downloadDllSuccess": "The {name} dll was downloaded successfully",
|
||||||
"downloadDllError": "An error occurred while downloading {name}: {error}",
|
"downloadDllError": "An error occurred while downloading {name}: {error}",
|
||||||
"downloadDllRetry": "Retry",
|
"downloadDllRetry": "Retry",
|
||||||
@@ -156,6 +155,7 @@
|
|||||||
"launchingGameClientAndServer": "Launching the game client and server...",
|
"launchingGameClientAndServer": "Launching the game client and server...",
|
||||||
"startGameServer": "Start a game server",
|
"startGameServer": "Start a game server",
|
||||||
"usernameOrEmail": "Username/Email",
|
"usernameOrEmail": "Username/Email",
|
||||||
|
"invalidEmail": "Invalid email",
|
||||||
"usernameOrEmailPlaceholder": "Type your username or email",
|
"usernameOrEmailPlaceholder": "Type your username or email",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"passwordPlaceholder": "Type your password, if you want to use one",
|
"passwordPlaceholder": "Type your password, if you want to use one",
|
||||||
@@ -261,6 +261,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",
|
||||||
@@ -320,6 +321,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",
|
||||||
@@ -359,9 +361,12 @@
|
|||||||
"promptBackendDetachedActionLabel": "Next",
|
"promptBackendDetachedActionLabel": "Next",
|
||||||
"promptInfoTabText": "The Info tab contains useful links to report bugs and receive support",
|
"promptInfoTabText": "The Info tab contains useful links to report bugs and receive support",
|
||||||
"promptInfoTabActionLabel": "Next",
|
"promptInfoTabActionLabel": "Next",
|
||||||
"promptSettingsTabText": "The Settings tab contains options to customize and reset the launcher",
|
"promptSettingsTabText": "The Settings tab contains options to customize the launcher",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,24 +135,20 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final serverType = type.value;
|
||||||
final hostData = this.host.text.trim();
|
final hostData = this.host.text.trim();
|
||||||
final portData = this.port.text.trim();
|
final portData = this.port.text.trim();
|
||||||
if(type() != ServerType.local) {
|
started.value = true;
|
||||||
started.value = true;
|
if(serverType != ServerType.local || portData != kDefaultBackendPort.toString()) {
|
||||||
yield ServerResult(ServerResultType.starting);
|
yield ServerResult(ServerResultType.starting);
|
||||||
}else {
|
|
||||||
started.value = false;
|
|
||||||
if(portData != kDefaultBackendPort.toString()) {
|
|
||||||
yield ServerResult(ServerResultType.starting);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hostData.isEmpty) {
|
if (hostData.isEmpty) {
|
||||||
@@ -136,7 +170,7 @@ class BackendController extends GetxController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((type() != ServerType.local || portData != kDefaultBackendPort.toString()) && !(await isBackendPortFree())) {
|
if ((serverType != ServerType.local || portData != kDefaultBackendPort.toString()) && !(await isBackendPortFree())) {
|
||||||
yield ServerResult(ServerResultType.freeingPort);
|
yield ServerResult(ServerResultType.freeingPort);
|
||||||
final result = await freeBackendPort();
|
final result = await freeBackendPort();
|
||||||
yield ServerResult(result ? ServerResultType.freePortSuccess : ServerResultType.freePortError);
|
yield ServerResult(result ? ServerResultType.freePortSuccess : ServerResultType.freePortError);
|
||||||
@@ -146,9 +180,20 @@ class BackendController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(type()){
|
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:
|
||||||
@@ -173,6 +218,10 @@ class BackendController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
localServer = await startRemoteBackendProxy(Uri.parse("http://$kDefaultBackendHost:$portData"));
|
localServer = await startRemoteBackendProxy(Uri.parse("http://$kDefaultBackendHost:$portData"));
|
||||||
|
}else {
|
||||||
|
// If the local server is running on port 3551 there is no reverse proxy running
|
||||||
|
// We only need to check if everything is working
|
||||||
|
started.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
249
gui/lib/src/controller/dll_controller.dart
Normal file
249
gui/lib/src/controller/dll_controller.dart
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
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 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEditingController _createController(String key, InjectableDll dll) {
|
||||||
|
final controller = TextEditingController(text: _storage?.read(key) ?? getDefaultDllPath(dll));
|
||||||
|
controller.addListener(() => _storage?.write(key, controller.text));
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetGame() {
|
||||||
|
gameServerDll.text = getDefaultDllPath(InjectableDll.reboot);
|
||||||
|
unrealEngineConsoleDll.text = getDefaultDllPath(InjectableDll.console);
|
||||||
|
backendDll.text = getDefaultDllPath(InjectableDll.cobalt);
|
||||||
|
memoryLeakDll.text = getDefaultDllPath(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(getDefaultDllPath(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 getDefaultDllPath(InjectableDll dll) => "${dllsDirectory.path}\\${dll.name}.dll";
|
||||||
|
|
||||||
|
Future<bool> downloadCriticalDllInteractive(String filePath, {bool silent = false, bool force = false}) async {
|
||||||
|
log("[DLL] Asking for $filePath(silent: $silent)");
|
||||||
|
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(!force && 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
@@ -41,41 +36,9 @@ class GameController extends GetxController {
|
|||||||
password = TextEditingController(text: _storage?.read("password") ?? "");
|
password = TextEditingController(text: _storage?.read("password") ?? "");
|
||||||
password.addListener(() => _storage?.write("password", password.text));
|
password.addListener(() => _storage?.write("password", password.text));
|
||||||
customLaunchArgs = TextEditingController(text: _storage?.read("custom_launch_args") ?? "");
|
customLaunchArgs = TextEditingController(text: _storage?.read("custom_launch_args") ?? "");
|
||||||
customLaunchArgs.addListener(() =>
|
customLaunchArgs.addListener(() => _storage?.write("custom_launch_args", customLaunchArgs.text));
|
||||||
_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() {
|
||||||
@@ -83,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -28,10 +30,11 @@ class HostingController extends GetxController {
|
|||||||
late final RxBool published;
|
late final RxBool published;
|
||||||
late final Rxn<GameInstance> instance;
|
late final Rxn<GameInstance> instance;
|
||||||
late final Rxn<Set<FortniteServer>> servers;
|
late final Rxn<Set<FortniteServer>> servers;
|
||||||
|
late final TextEditingController customLaunchArgs;
|
||||||
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"));
|
||||||
@@ -62,6 +65,8 @@ class HostingController extends GetxController {
|
|||||||
servers.value = event;
|
servers.value = event;
|
||||||
published.value = event.any((element) => element.id == uuid);
|
published.value = event.any((element) => element.id == uuid);
|
||||||
});
|
});
|
||||||
|
customLaunchArgs = TextEditingController(text: _storage?.read("custom_launch_args") ?? "");
|
||||||
|
customLaunchArgs.addListener(() => _storage?.write("custom_launch_args", customLaunchArgs.text));
|
||||||
_semaphore = Semaphore();
|
_semaphore = Semaphore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,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) {
|
||||||
|
|||||||
@@ -15,37 +15,20 @@ 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 final RxBool debug;
|
||||||
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 +37,9 @@ 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 = {};
|
debug = RxBool(false);
|
||||||
}
|
|
||||||
|
|
||||||
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 +54,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 +78,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 +87,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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -23,19 +23,18 @@ InfoBarEntry showRebootInfoBar(dynamic text, {
|
|||||||
|
|
||||||
Widget _buildOverlay(text, Widget? action, bool loading, InfoBarSeverity severity) => ConstrainedBox(
|
Widget _buildOverlay(text, Widget? action, bool loading, InfoBarSeverity severity) => ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minWidth: double.infinity,
|
minHeight: _height
|
||||||
minHeight: _height
|
|
||||||
),
|
),
|
||||||
child: Mica(
|
child: Mica(
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
child: InfoBar(
|
child: InfoBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
if(text is Widget)
|
Expanded(
|
||||||
text,
|
child: text is Widget ? text : Text(text)
|
||||||
if(text is String)
|
),
|
||||||
Text(text),
|
|
||||||
if(action != null)
|
if(action != null)
|
||||||
action
|
action
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:email_validator/email_validator.dart';
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/material.dart' show Icons;
|
import 'package:flutter/material.dart' show Icons;
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -23,6 +24,17 @@ Future<bool> showProfileForm(BuildContext context) async{
|
|||||||
label: translations.usernameOrEmail,
|
label: translations.usernameOrEmail,
|
||||||
child: TextFormBox(
|
child: TextFormBox(
|
||||||
placeholder: translations.usernameOrEmailPlaceholder,
|
placeholder: translations.usernameOrEmailPlaceholder,
|
||||||
|
validator: (text) {
|
||||||
|
if(_gameController.password.text.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(EmailValidator.validate(_gameController.username.text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return translations.invalidEmail;
|
||||||
|
},
|
||||||
controller: _gameController.username,
|
controller: _gameController.username,
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
enableSuggestions: true,
|
enableSuggestions: true,
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -307,7 +315,9 @@ extension ServerControllerDialog on BackendController {
|
|||||||
onDismissed: onDismissed,
|
onDismissed: onDismissed,
|
||||||
action: action
|
action: action
|
||||||
);
|
);
|
||||||
_infoBars.add(result);
|
if(severity == InfoBarSeverity.info || severity == InfoBarSeverity.success) {
|
||||||
|
_infoBars.add(result);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
|
||||||
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/messenger/implementation/onboard.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
|
|
||||||
abstract class RebootPage extends StatefulWidget {
|
abstract class RebootPage extends StatefulWidget {
|
||||||
const RebootPage({super.key});
|
const RebootPage({super.key});
|
||||||
@@ -19,32 +24,110 @@ abstract class RebootPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class RebootPageState<T extends RebootPage> extends State<T> with AutomaticKeepAliveClientMixin<T> {
|
abstract class RebootPageState<T extends RebootPage> extends State<T> with AutomaticKeepAliveClientMixin<T> {
|
||||||
|
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
var buttonWidget = button;
|
var buttonWidget = button;
|
||||||
if(buttonWidget == null) {
|
if(buttonWidget == null) {
|
||||||
return _listView;
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildFirstLaunchInfo(),
|
||||||
|
_buildDebugInfo(),
|
||||||
|
Expanded(
|
||||||
|
child: _listView
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
_buildFirstLaunchInfo(),
|
||||||
|
_buildDebugInfo(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _listView,
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _listView,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8.0,
|
||||||
|
),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 1000
|
||||||
|
),
|
||||||
|
child: buttonWidget
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
|
||||||
height: 8.0,
|
|
||||||
),
|
|
||||||
ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(
|
|
||||||
maxWidth: 1000
|
|
||||||
),
|
|
||||||
child: buttonWidget
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildFirstLaunchInfo() => Obx(() {
|
||||||
|
if(!_settingsController.firstRun.value) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 8.0
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: InfoBar(
|
||||||
|
title: Text(translations.welcomeTitle),
|
||||||
|
severity: InfoBarSeverity.warning,
|
||||||
|
isLong: true,
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Text(translations.welcomeDescription)
|
||||||
|
),
|
||||||
|
action: Button(
|
||||||
|
child: Text(translations.welcomeAction),
|
||||||
|
onPressed: () => startOnboarding(),
|
||||||
|
),
|
||||||
|
onClose: () => _settingsController.firstRun.value = false
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Widget _buildDebugInfo() => Obx(() {
|
||||||
|
if(!_settingsController.debug.value) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 8.0
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: InfoBar(
|
||||||
|
title: Text("Debug mode is enabled"),
|
||||||
|
severity: InfoBarSeverity.warning,
|
||||||
|
isLong: true,
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Text( "• Automatic dll injection is disabled\n"
|
||||||
|
"• The game server cannot start automatically\n"
|
||||||
|
"• The game server runs in a normal window")
|
||||||
|
),
|
||||||
|
onClose: () {
|
||||||
|
_settingsController.debug.value = false;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
ListView get _listView => ListView.builder(
|
ListView get _listView => ListView.builder(
|
||||||
itemCount: settings.length,
|
itemCount: settings.length,
|
||||||
itemBuilder: (context, index) => settings[index],
|
itemBuilder: (context, index) => settings[index],
|
||||||
|
|||||||
@@ -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 ?? ""),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
@@ -199,6 +201,17 @@ class _HostingPageState extends RebootPageState<HostPage> {
|
|||||||
title: Text(translations.settingsServerOptionsName),
|
title: Text(translations.settingsServerOptionsName),
|
||||||
subtitle: Text(translations.settingsServerOptionsSubtitle),
|
subtitle: Text(translations.settingsServerOptionsSubtitle),
|
||||||
children: [
|
children: [
|
||||||
|
SettingTile(
|
||||||
|
icon: Icon(
|
||||||
|
FluentIcons.options_24_regular
|
||||||
|
),
|
||||||
|
title: Text(translations.settingsClientArgsName),
|
||||||
|
subtitle: Text(translations.settingsClientArgsDescription),
|
||||||
|
content: TextFormBox(
|
||||||
|
placeholder: translations.settingsClientArgsPlaceholder,
|
||||||
|
controller: _hostingController.customLaunchArgs,
|
||||||
|
)
|
||||||
|
),
|
||||||
SettingTile(
|
SettingTile(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
FluentIcons.window_console_20_regular
|
FluentIcons.window_console_20_regular
|
||||||
@@ -208,11 +221,12 @@ 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(_hostingController.type.value.translatedName),
|
leading: Text(_settingsController.debug.value ? GameServerType.window.translatedName : _hostingController.type.value.translatedName),
|
||||||
items: GameServerType.values.map((entry) => MenuFlyoutItem(
|
items: GameServerType.values.map((entry) => MenuFlyoutItem(
|
||||||
text: Text(entry.translatedName),
|
text: Text(entry.translatedName),
|
||||||
onPressed: () => _hostingController.type.value = entry
|
onPressed: () => _hostingController.type.value = entry
|
||||||
)).toList()
|
)).toList(),
|
||||||
|
disabled: _settingsController.debug.value
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
SettingTile(
|
SettingTile(
|
||||||
@@ -246,14 +260,14 @@ 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: [
|
||||||
FilteringTextInputFormatter.digitsOnly
|
FilteringTextInputFormatter.digitsOnly
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -273,22 +287,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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -297,18 +311,23 @@ 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,
|
||||||
|
onReset: () {
|
||||||
|
final path = _dllController.getDefaultDllPath(InjectableDll.reboot);
|
||||||
|
_dllController.gameServerDll.text = path;
|
||||||
|
_dllController.downloadCriticalDllInteractive(path);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if(_settingsController.customGameServer.value) {
|
if(_dllController.customGameServer.value) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,41 +337,81 @@ class _HostingPageState extends RebootPageState<HostPage> {
|
|||||||
),
|
),
|
||||||
title: Text(translations.settingsServerMirrorName),
|
title: Text(translations.settingsServerMirrorName),
|
||||||
subtitle: Text(translations.settingsServerMirrorDescription),
|
subtitle: Text(translations.settingsServerMirrorDescription),
|
||||||
content: TextFormBox(
|
content: Row(
|
||||||
placeholder: translations.settingsServerMirrorPlaceholder,
|
children: [
|
||||||
controller: _settingsController.url,
|
Expanded(
|
||||||
validator: _checkUpdateUrl
|
child: TextFormBox(
|
||||||
|
placeholder: translations.settingsServerMirrorPlaceholder,
|
||||||
|
controller: _dllController.url,
|
||||||
|
validator: _checkUpdateUrl
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
Button(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: ButtonState.all(EdgeInsets.zero)
|
||||||
|
),
|
||||||
|
onPressed: () => _dllController.url.text = kRebootDownloadUrl,
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: 30,
|
||||||
|
child: Icon(
|
||||||
|
FluentIcons.arrow_reset_24_regular
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if(_settingsController.customGameServer.value) {
|
if(_dllController.customGameServer.value) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return SettingTile(
|
return SettingTile(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
FluentIcons.timer_24_regular
|
FluentIcons.timer_24_regular
|
||||||
),
|
),
|
||||||
title: Text(translations.settingsServerTimerName),
|
title: Text(translations.settingsServerTimerName),
|
||||||
subtitle: Text(translations.settingsServerTimerSubtitle),
|
subtitle: Text(translations.settingsServerTimerSubtitle),
|
||||||
content: Obx(() => DropDownButton(
|
content: Row(
|
||||||
onOpen: () => inDialog = true,
|
children: [
|
||||||
onClose: () => inDialog = false,
|
Expanded(
|
||||||
leading: Text(_settingsController.timer.value.text),
|
child: Obx(() => DropDownButton(
|
||||||
items: UpdateTimer.values.map((entry) => MenuFlyoutItem(
|
onOpen: () => inDialog = true,
|
||||||
text: Text(entry.text),
|
onClose: () => inDialog = false,
|
||||||
onPressed: () {
|
leading: Text(_dllController.timer.value.text),
|
||||||
_settingsController.timer.value = entry;
|
items: UpdateTimer.values.map((entry) => MenuFlyoutItem(
|
||||||
_settingsController.infoBarEntry?.close();
|
text: Text(entry.text),
|
||||||
_settingsController.updateReboot(
|
onPressed: () {
|
||||||
force: true
|
_dllController.timer.value = entry;
|
||||||
);
|
_dllController.infoBarEntry?.close();
|
||||||
}
|
_dllController.updateGameServerDll(
|
||||||
)).toList()
|
force: true
|
||||||
))
|
);
|
||||||
|
}
|
||||||
|
)).toList()
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
Button(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: ButtonState.all(EdgeInsets.zero)
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_dllController.updateGameServerDll(force: true);
|
||||||
|
},
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: 30,
|
||||||
|
child: Icon(
|
||||||
|
FluentIcons.arrow_download_24_regular
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}),
|
})
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -420,7 +479,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),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
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_common/common.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,48 +40,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
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildFirstLaunchInfo(),
|
|
||||||
Expanded(
|
|
||||||
child: super.build(context),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFirstLaunchInfo() => Obx(() {
|
|
||||||
if(!_settingsController.firstRun.value) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
bottom: 8.0
|
|
||||||
),
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: InfoBar(
|
|
||||||
title: Text(translations.welcomeTitle),
|
|
||||||
severity: InfoBarSeverity.warning,
|
|
||||||
isLong: true,
|
|
||||||
content: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: Text(translations.welcomeDescription)
|
|
||||||
),
|
|
||||||
action: Button(
|
|
||||||
child: Text(translations.welcomeAction),
|
|
||||||
onPressed: () => startOnboarding(),
|
|
||||||
),
|
|
||||||
onClose: () => _settingsController.firstRun.value = false
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? get button => LaunchButton(
|
Widget? get button => LaunchButton(
|
||||||
@@ -94,6 +56,7 @@ class _PlayPageState extends RebootPageState<PlayPage> {
|
|||||||
),
|
),
|
||||||
_options,
|
_options,
|
||||||
_internalFiles,
|
_internalFiles,
|
||||||
|
_resetDefaults
|
||||||
];
|
];
|
||||||
|
|
||||||
SettingTile get _internalFiles => SettingTile(
|
SettingTile get _internalFiles => SettingTile(
|
||||||
@@ -106,17 +69,32 @@ 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,
|
||||||
|
onReset: () {
|
||||||
|
final path = _dllController.getDefaultDllPath(InjectableDll.console);
|
||||||
|
_dllController.unrealEngineConsoleDll.text = path;
|
||||||
|
_dllController.downloadCriticalDllInteractive(path, force: true);
|
||||||
|
}
|
||||||
),
|
),
|
||||||
createFileSetting(
|
createFileSetting(
|
||||||
title: translations.settingsClientAuthName,
|
title: translations.settingsClientAuthName,
|
||||||
description: translations.settingsClientAuthDescription,
|
description: translations.settingsClientAuthDescription,
|
||||||
controller: _settingsController.backendDll
|
controller: _dllController.backendDll,
|
||||||
|
onReset: () {
|
||||||
|
final path = _dllController.getDefaultDllPath(InjectableDll.cobalt);
|
||||||
|
_dllController.backendDll.text = path;
|
||||||
|
_dllController.downloadCriticalDllInteractive(path, force: true);
|
||||||
|
}
|
||||||
),
|
),
|
||||||
createFileSetting(
|
createFileSetting(
|
||||||
title: translations.settingsClientMemoryName,
|
title: translations.settingsClientMemoryName,
|
||||||
description: translations.settingsClientMemoryDescription,
|
description: translations.settingsClientMemoryDescription,
|
||||||
controller: _settingsController.memoryLeakDll
|
controller: _dllController.memoryLeakDll,
|
||||||
|
onReset: () {
|
||||||
|
final path = _dllController.getDefaultDllPath(InjectableDll.memory);
|
||||||
|
_dllController.memoryLeakDll.text = path;
|
||||||
|
_dllController.downloadCriticalDllInteractive(path, force: true);
|
||||||
|
}
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -141,4 +119,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),
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -42,8 +42,8 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
|||||||
List<Widget> get settings => [
|
List<Widget> get settings => [
|
||||||
_language,
|
_language,
|
||||||
_theme,
|
_theme,
|
||||||
_resetDefaults,
|
_debugMode,
|
||||||
_installationDirectory
|
_installationDirectory,
|
||||||
];
|
];
|
||||||
|
|
||||||
SettingTile get _language => SettingTile(
|
SettingTile get _language => SettingTile(
|
||||||
@@ -89,18 +89,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
|
||||||
@@ -112,6 +100,29 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
|||||||
child: Text(translations.settingsUtilsInstallationDirectoryContent),
|
child: Text(translations.settingsUtilsInstallationDirectoryContent),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
SettingTile get _debugMode => SettingTile(
|
||||||
|
icon: Icon(
|
||||||
|
FluentIcons.developer_board_24_regular
|
||||||
|
),
|
||||||
|
title: Text("Debug mode"),
|
||||||
|
subtitle: Text("Whether the launcher should disable automatic features for troubleshooting"),
|
||||||
|
contentWidth: null,
|
||||||
|
content: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_settingsController.debug.value ? translations.on : translations.off
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 16.0
|
||||||
|
),
|
||||||
|
Obx(() => ToggleSwitch(
|
||||||
|
checked: _settingsController.debug.value,
|
||||||
|
onChanged: (value) => _settingsController.debug.value = value
|
||||||
|
))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
extension _ThemeModeExtension on ThemeMode {
|
extension _ThemeModeExtension on ThemeMode {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ Future<bool> pingGameServer(String address, {Duration? timeout}) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final start = DateTime.now();
|
final start = DateTime.now();
|
||||||
var firstTime = true;
|
var firstTime = true;
|
||||||
final split = address.split(":");
|
final split = address.split(":");
|
||||||
|
|||||||
@@ -2,26 +2,55 @@ import 'dart:io';
|
|||||||
|
|
||||||
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:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
||||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||||
|
|
||||||
SettingTile createFileSetting({required String title, required String description, required TextEditingController controller}) => SettingTile(
|
SettingTile createFileSetting({required String title, required String description, required TextEditingController controller, required void Function() onReset}) {
|
||||||
|
final obx = RxString(controller.text);
|
||||||
|
controller.addListener(() => obx.value = controller.text);
|
||||||
|
return SettingTile(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
FluentIcons.document_24_regular
|
FluentIcons.document_24_regular
|
||||||
),
|
),
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
subtitle: Text(description),
|
subtitle: Text(description),
|
||||||
content: FileSelector(
|
content: Row(
|
||||||
placeholder: translations.selectPathPlaceholder,
|
children: [
|
||||||
windowTitle: translations.selectPathWindowTitle,
|
Expanded(
|
||||||
controller: controller,
|
child: FileSelector(
|
||||||
validator: _checkDll,
|
placeholder: translations.selectPathPlaceholder,
|
||||||
extension: "dll",
|
windowTitle: translations.selectPathWindowTitle,
|
||||||
folder: false,
|
controller: controller,
|
||||||
validatorMode: AutovalidateMode.always
|
validator: _checkDll,
|
||||||
|
extension: "dll",
|
||||||
|
folder: false,
|
||||||
|
validatorMode: AutovalidateMode.always
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
Obx(() => Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: _checkDll(obx.value) == null ? 0.0 : 20.0
|
||||||
|
),
|
||||||
|
child: Button(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: ButtonState.all(EdgeInsets.zero)
|
||||||
|
),
|
||||||
|
onPressed: onReset,
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: 30,
|
||||||
|
child: Icon(
|
||||||
|
FluentIcons.arrow_reset_24_regular
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
))
|
||||||
|
],
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String? _checkDll(String? text) {
|
String? _checkDll(String? text) {
|
||||||
if (text == null || text.isEmpty) {
|
if (text == null || text.isEmpty) {
|
||||||
|
|||||||
@@ -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,11 +40,13 @@ 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 DllController _dllController = Get.find<DllController>();
|
||||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||||
|
|
||||||
InfoBarEntry? _gameClientInfoBar;
|
InfoBarEntry? _gameClientInfoBar;
|
||||||
InfoBarEntry? _gameServerInfoBar;
|
InfoBarEntry? _gameServerInfoBar;
|
||||||
CancelableOperation? _operation;
|
CancelableOperation? _operation;
|
||||||
|
CancelableOperation? _pingOperation;
|
||||||
IVirtualDesktop? _virtualDesktop;
|
IVirtualDesktop? _virtualDesktop;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -93,10 +96,6 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
log("[${host ? 'HOST' : 'GAME'}] Checking dlls: ${InjectableDll.values}");
|
log("[${host ? 'HOST' : 'GAME'}] Checking dlls: ${InjectableDll.values}");
|
||||||
for (final injectable in InjectableDll.values) {
|
for (final injectable in InjectableDll.values) {
|
||||||
if(await _getDllFileOrStop(injectable, host) == null) {
|
if(await _getDllFileOrStop(injectable, host) == null) {
|
||||||
_onStop(
|
|
||||||
reason: _StopReason.missingCustomDllError,
|
|
||||||
error: injectable.name,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +121,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Backend works");
|
log("[${host ? 'HOST' : 'GAME'}] Backend works");
|
||||||
final serverType = _hostingController.type.value;
|
final serverType = _settingsController.debug.value ? GameServerType.window : _hostingController.type.value;
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($serverType)");
|
log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($serverType)");
|
||||||
final linkedHostingInstance = await _startMatchMakingServer(version, host, serverType, false);
|
final linkedHostingInstance = await _startMatchMakingServer(version, host, serverType, false);
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Implicit game server result: $linkedHostingInstance");
|
log("[${host ? 'HOST' : 'GAME'}] Implicit game server result: $linkedHostingInstance");
|
||||||
@@ -138,6 +137,12 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}else {
|
}else {
|
||||||
_showLaunchingGameServerWidget();
|
_showLaunchingGameServerWidget();
|
||||||
}
|
}
|
||||||
|
} on ProcessException catch (exception, stackTrace) {
|
||||||
|
_onStop(
|
||||||
|
reason: _StopReason.corruptedVersionError,
|
||||||
|
error: exception.toString(),
|
||||||
|
stackTrace: stackTrace
|
||||||
|
);
|
||||||
} catch (exception, stackTrace) {
|
} catch (exception, stackTrace) {
|
||||||
_onStop(
|
_onStop(
|
||||||
reason: _StopReason.unknownError,
|
reason: _StopReason.unknownError,
|
||||||
@@ -154,6 +159,11 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(_settingsController.debug.value) {
|
||||||
|
log("[${host ? 'HOST' : 'GAME'}] The user is on debug mode, not asking for auto server");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if(!forceLinkedHosting && _backendController.type.value == ServerType.embedded && !isLocalHost(_backendController.gameServerAddress.text)) {
|
if(!forceLinkedHosting && _backendController.type.value == ServerType.embedded && !isLocalHost(_backendController.gameServerAddress.text)) {
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Backend is not set to embedded and/or not pointing to the local game server");
|
log("[${host ? 'HOST' : 'GAME'}] Backend is not set to embedded and/or not pointing to the local game server");
|
||||||
return null;
|
return null;
|
||||||
@@ -251,7 +261,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
host,
|
host,
|
||||||
hostType,
|
hostType,
|
||||||
false,
|
false,
|
||||||
""
|
host ? _hostingController.customLaunchArgs.text : _gameController.customLaunchArgs.text
|
||||||
);
|
);
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Generated game args: ${gameArgs.join(" ")}");
|
log("[${host ? 'HOST' : 'GAME'}] Generated game args: ${gameArgs.join(" ")}");
|
||||||
final gameProcess = await startProcess(
|
final gameProcess = await startProcess(
|
||||||
@@ -263,15 +273,29 @@ 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: () {
|
||||||
onBuildCorrupted: () => _onStop(reason: _StopReason.corruptedVersionError),
|
if(_settingsController.debug.value) {
|
||||||
|
log("[PROCESS] Ignoring token error because debug mode is on");
|
||||||
|
}else {
|
||||||
|
_onStop(reason: _StopReason.tokenError);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBuildCorrupted: () {
|
||||||
|
if(instance == null) {
|
||||||
|
return;
|
||||||
|
}else if(!instance.launched) {
|
||||||
|
_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 +415,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,11 +448,12 @@ 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(
|
this._pingOperation = await CancelableOperation.fromFuture(pingGameServer(
|
||||||
"127.0.0.1:$gameServerPort",
|
"127.0.0.1:$gameServerPort",
|
||||||
timeout: const Duration(minutes: 2)
|
timeout: const Duration(minutes: 2)
|
||||||
);
|
));
|
||||||
|
final localPingResult = (await _pingOperation?.value) ?? false;
|
||||||
_gameServerInfoBar?.close();
|
_gameServerInfoBar?.close();
|
||||||
if (!localPingResult) {
|
if (!localPingResult) {
|
||||||
showRebootInfoBar(
|
showRebootInfoBar(
|
||||||
@@ -471,16 +496,18 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
duration: null
|
duration: null
|
||||||
);
|
);
|
||||||
final publicIp = await Ipify.ipv4();
|
final publicIp = await Ipify.ipv4();
|
||||||
final externalResult = await pingGameServer("$publicIp:$gameServerPort");
|
this._pingOperation = CancelableOperation.fromFuture(pingGameServer("$publicIp:$gameServerPort"));
|
||||||
|
final externalResult = (await _pingOperation?.value) ?? false;
|
||||||
if (externalResult) {
|
if (externalResult) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_gameServerInfoBar?.close();
|
_gameServerInfoBar?.close();
|
||||||
final future = pingGameServer(
|
this._pingOperation = CancelableOperation.fromFuture(pingGameServer(
|
||||||
"$publicIp:$gameServerPort",
|
"$publicIp:$gameServerPort",
|
||||||
timeout: const Duration(days: 365)
|
timeout: const Duration(days: 365)
|
||||||
);
|
));
|
||||||
|
final future = await _pingOperation?.value ?? false;
|
||||||
_gameServerInfoBar = showRebootInfoBar(
|
_gameServerInfoBar = showRebootInfoBar(
|
||||||
translations.checkGameServerFixMessage(gameServerPort),
|
translations.checkGameServerFixMessage(gameServerPort),
|
||||||
action: Button(
|
action: Button(
|
||||||
@@ -499,6 +526,8 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
|
|
||||||
Future<void> _onStop({required _StopReason reason, bool? host, String? error, StackTrace? stackTrace}) async {
|
Future<void> _onStop({required _StopReason reason, bool? host, String? error, StackTrace? stackTrace}) async {
|
||||||
if(host == null) {
|
if(host == null) {
|
||||||
|
await _pingOperation?.cancel();
|
||||||
|
_pingOperation = null;
|
||||||
await _operation?.cancel();
|
await _operation?.cancel();
|
||||||
_operation = null;
|
_operation = null;
|
||||||
_backendController.cancelInteractive();
|
_backendController.cancelInteractive();
|
||||||
@@ -506,6 +535,10 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
|
|
||||||
host = host ?? widget.host;
|
host = host ?? widget.host;
|
||||||
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
final instance = host ? _hostingController.instance.value : _gameController.instance.value;
|
||||||
|
if(instance == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(host){
|
if(host){
|
||||||
_hostingController.instance.value = null;
|
_hostingController.instance.value = null;
|
||||||
}else {
|
}else {
|
||||||
@@ -527,19 +560,17 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
_hostingController.discardServer();
|
_hostingController.discardServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(instance != null) {
|
if(reason == _StopReason.normal) {
|
||||||
if(reason == _StopReason.normal) {
|
instance.launched = true;
|
||||||
instance.launched = true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
instance.kill();
|
instance.kill();
|
||||||
final child = instance.child;
|
final child = instance.child;
|
||||||
if(child != null) {
|
if(child != null) {
|
||||||
await _onStop(
|
await _onStop(
|
||||||
reason: reason,
|
reason: reason,
|
||||||
host: child.serverType != null
|
host: child.serverType != null
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setStarted(host, false);
|
_setStarted(host, false);
|
||||||
@@ -571,7 +602,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case _StopReason.exitCode:
|
case _StopReason.exitCode:
|
||||||
if(instance != null && !instance.launched) {
|
if(!instance.launched) {
|
||||||
showRebootInfoBar(
|
showRebootInfoBar(
|
||||||
translations.corruptedVersionError,
|
translations.corruptedVersionError,
|
||||||
severity: InfoBarSeverity.error,
|
severity: InfoBarSeverity.error,
|
||||||
@@ -605,8 +636,9 @@ 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(", ")),
|
||||||
severity: InfoBarSeverity.error,
|
severity: InfoBarSeverity.error,
|
||||||
duration: infoBarLongDuration,
|
duration: infoBarLongDuration,
|
||||||
action: Button(
|
action: Button(
|
||||||
@@ -615,6 +647,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),
|
||||||
@@ -648,6 +687,10 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log("[${hosting ? 'HOST' : 'GAME'}] Trying to inject ${injectable.name}...");
|
log("[${hosting ? 'HOST' : 'GAME'}] Trying to inject ${injectable.name}...");
|
||||||
|
if(_settingsController.debug.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await injectDll(gameProcess, dllPath);
|
await injectDll(gameProcess, dllPath);
|
||||||
instance.injectedDlls.add(injectable);
|
instance.injectedDlls.add(injectable);
|
||||||
log("[${hosting ? 'HOST' : 'GAME'}] Injected ${injectable.name}");
|
log("[${hosting ? 'HOST' : 'GAME'}] Injected ${injectable.name}");
|
||||||
@@ -664,7 +707,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");
|
||||||
@@ -672,13 +715,17 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Path doesn't exist");
|
log("[${host ? 'HOST' : 'GAME'}] Path doesn't exist");
|
||||||
if(customDll || isRetry) {
|
if(customDll) {
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Custom dll -> no recovery");
|
log("[${host ? 'HOST' : 'GAME'}] Custom dll -> no recovery");
|
||||||
|
_onStop(
|
||||||
|
reason: _StopReason.missingCustomDllError,
|
||||||
|
error: injectable.name,
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
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, force: true);
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -695,7 +742,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
loading: true,
|
loading: true,
|
||||||
duration: null,
|
duration: null,
|
||||||
action: Obx(() {
|
action: Obx(() {
|
||||||
if(_hostingController.started.value || linkedHosting) {
|
if(_settingsController.debug.value || _hostingController.started.value || linkedHosting) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -731,7 +778,8 @@ enum _StopReason {
|
|||||||
matchmakerError,
|
matchmakerError,
|
||||||
tokenError,
|
tokenError,
|
||||||
unknownError,
|
unknownError,
|
||||||
exitCode;
|
exitCode,
|
||||||
|
crash;
|
||||||
|
|
||||||
bool get isError => name.contains("Error");
|
bool get isError => name.contains("Error");
|
||||||
}
|
}
|
||||||
@@ -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.2"
|
version: "9.2.6"
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
@@ -74,6 +74,9 @@ dependencies:
|
|||||||
package_info_plus: ^8.0.0
|
package_info_plus: ^8.0.0
|
||||||
version: ^3.0.2
|
version: ^3.0.2
|
||||||
|
|
||||||
|
# Validate profile
|
||||||
|
email_validator: ^3.0.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
xml: ^6.3.0
|
xml: ^6.3.0
|
||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
@@ -98,4 +101,9 @@ flutter:
|
|||||||
- assets/backend/profiles/
|
- assets/backend/profiles/
|
||||||
- assets/backend/public/
|
- assets/backend/public/
|
||||||
- assets/backend/responses/
|
- assets/backend/responses/
|
||||||
|
- assets/backend/responses/Athena/
|
||||||
|
- assets/backend/responses/Athena/BattlePass/
|
||||||
|
- assets/backend/responses/Athena/Discovery/
|
||||||
|
- assets/backend/responses/Campaign/
|
||||||
|
- assets/backend/responses/CloudDir/
|
||||||
- assets/build/
|
- assets/build/
|
||||||
Reference in New Issue
Block a user