This commit is contained in:
Alessandro Autiero
2024-12-09 12:14:41 +01:00
parent 6f91ad0404
commit eb7745cc4d
37 changed files with 820 additions and 695 deletions

View File

@@ -1,4 +1,5 @@
const String kDefaultPlayerName = "Player"; const String kDefaultPlayerName = "Player";
const String kDefaultHostName = "Host";
const String kDefaultGameServerHost = "127.0.0.1"; const String kDefaultGameServerHost = "127.0.0.1";
const String kDefaultGameServerPort = "7777"; const String kDefaultGameServerPort = "7777";
const String kInitializedLine = "Game Engine Initialized"; const String kInitializedLine = "Game Engine Initialized";

View File

@@ -1,6 +1,9 @@
enum InjectableDll { enum InjectableDll {
console, console,
sinum, starfall,
reboot, reboot,
memory }
extension InjectableDllVersionAware on InjectableDll {
bool get isVersionDependent => this == InjectableDll.reboot;
} }

View File

@@ -17,13 +17,15 @@ class FortniteBuild {
class FortniteBuildDownloadProgress { class FortniteBuildDownloadProgress {
final double progress; final double progress;
final int? minutesLeft; final int? timeLeft;
final bool extracting; final bool extracting;
final int speed;
FortniteBuildDownloadProgress({ FortniteBuildDownloadProgress({
required this.progress, required this.progress,
required this.extracting, required this.extracting,
this.minutesLeft, required this.timeLeft,
required this.speed
}); });
} }

View File

@@ -1,10 +1,11 @@
import 'dart:io'; import 'dart:io';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:version/version.dart';
class GameInstance { class GameInstance {
final String versionName; final Version version;
final int gamePid; final int gamePid;
final int? launcherPid; final int? launcherPid;
final int? eacPid; final int? eacPid;
@@ -17,7 +18,7 @@ class GameInstance {
GameInstance? child; GameInstance? child;
GameInstance({ GameInstance({
required this.versionName, required this.version,
required this.gamePid, required this.gamePid,
required this.launcherPid, required this.launcherPid,
required this.eacPid, required this.eacPid,

View File

@@ -3,165 +3,243 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:dio/dio.dart';
import 'package:dio/io.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/extension/types.dart'; import 'package:reboot_common/src/extension/types.dart';
import 'package:uuid/uuid.dart';
import 'package:version/version.dart'; import 'package:version/version.dart';
import 'package:http/http.dart' as http;
const String kStopBuildDownloadSignal = "kill"; const String kStopBuildDownloadSignal = "kill";
final Dio _dio = _buildDioInstance(); final Uri _archiveSourceUrl = Uri.parse("https://builds.rebootfn.org/versions.json");
Dio _buildDioInstance() { final int _ariaPort = 6800;
final dio = Dio(); final Uri _ariaEndpoint = Uri.parse('http://localhost:$_ariaPort/jsonrpc');
final httpClientAdapter = dio.httpClientAdapter as IOHttpClientAdapter; final Duration _ariaMaxSpawnTime = const Duration(seconds: 10);
httpClientAdapter.createHttpClient = () { final String _ariaSecret = "RebootLauncher";
final client = HttpClient();
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
return client;
};
return dio;
}
final String _archiveSourceUrl = "https://raw.githubusercontent.com/simplyblk/Fortnitebuilds/main/README.md";
final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$"); final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$");
const String _deniedConnectionError = "The connection was denied: your firewall might be blocking the download";
const String _unavailableError = "The build downloader is not available right now";
const String _genericError = "The build downloader is not working correctly";
const int _maxErrors = 100;
Future<List<FortniteBuild>> fetchBuilds(ignored) async { Future<List<FortniteBuild>> fetchBuilds(ignored) async {
final response = await _dio.get<String>( final response = await http.get(_archiveSourceUrl);
_archiveSourceUrl,
options: Options(
responseType: ResponseType.plain
)
);
if (response.statusCode != 200) { if (response.statusCode != 200) {
return []; return [];
} }
var results = <FortniteBuild>[]; return jsonDecode(response.body)
for (final line in response.data?.split("\n") ?? []) { .map((entry) {
if (!line.startsWith("|")) {
continue;
}
var parts = line.substring(1, line.length - 1).split("|");
if (parts.isEmpty) {
continue;
}
var versionName = parts.first.trim();
final separator = versionName.indexOf("-");
if(separator != -1) {
versionName = versionName.substring(0, separator);
}
final link = parts.last.trim();
try { try {
results.add(FortniteBuild( final fileUrl = entry as String;
version: Version.parse(versionName), final fileName = Uri.parse(fileUrl).pathSegments.last;
link: link, final fileNameWithoutExtension = path.basenameWithoutExtension(fileName);
available: link.endsWith(".zip") || link.endsWith(".rar") return FortniteBuild(
)); version: Version.parse(fileNameWithoutExtension),
} on FormatException { link: entry,
// Ignore available: true
);
}catch(_) {
return null;
} }
})
.whereType<FortniteBuild>()
.toList();
} }
return results;
}
Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async { Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
final fileName = options.build.link.substring(options.build.link.lastIndexOf("/") + 1);
final outputFile = File("${options.destination.path}\\.build\\$fileName");
try { try {
final stopped = _setupLifecycle(options); final stopped = _setupLifecycle(options);
final outputDir = Directory("${options.destination.path}\\.build"); await outputFile.parent.create(recursive: true);
await outputDir.create(recursive: true);
final fileName = options.build.link.substring(options.build.link.lastIndexOf("/") + 1);
final extension = path.extension(fileName);
final tempFile = File("${outputDir.path}\\$fileName");
if(await tempFile.exists()) {
await tempFile.delete(recursive: true);
}
final startTime = DateTime.now().millisecondsSinceEpoch; final downloadItemCompleter = Completer<File>();
final response = _downloadArchive(options, stopped, tempFile, startTime);
await Future.any([stopped.future, response]);
if(!stopped.isCompleted) {
await _extractArchive(stopped, extension, tempFile, options);
}
delete(outputDir); await _startAriaServer();
}catch(error) { final downloadId = await _startAriaDownload(options, outputFile);
_onError(error, options); Timer.periodic(const Duration(seconds: 5), (Timer timer) async {
}
}
Future<void> _downloadArchive(FortniteBuildDownloadOptions options, Completer stopped, File tempFile, int startTime, [int? byteStart = null, int errorsCount = 0]) async {
var received = byteStart ?? 0;
try { try {
await _dio.download( final statusRequestId = Uuid().toString().replaceAll("-", "");
options.build.link, final statusRequest = {
tempFile.path, "jsonrcp": "2.0",
onReceiveProgress: (data, length) { "id": statusRequestId,
if(stopped.isCompleted) { "method": "aria2.tellStatus",
throw StateError("Download interrupted"); "params": [
"token:${_ariaSecret}",
downloadId
]
};
final statusResponse = await http.post(_ariaEndpoint, body: jsonEncode(statusRequest));
final statusResponseJson = jsonDecode(statusResponse.body) as Map?;
if(statusResponseJson == null) {
downloadItemCompleter.completeError("Invalid download status (invalid JSON)");
timer.cancel();
return;
} }
received = data; final result = statusResponseJson["result"];
final percentage = (received / length) * 100; final files = result["files"] as List?;
_onProgress(startTime, percentage < 1 ? null : DateTime.now().millisecondsSinceEpoch, percentage, false, options); if(files == null || files.isEmpty) {
}, downloadItemCompleter.completeError("Download aborted");
deleteOnError: false, timer.cancel();
options: Options( return;
validateStatus: (statusCode) {
if(statusCode == 200) {
return true;
} }
if(statusCode == 403 || statusCode == 503) { final error = result["errorCode"];
throw _deniedConnectionError; if(error != null) {
final errorCode = int.tryParse(error);
if(errorCode == 0) {
final path = File(files[0]["path"]);
downloadItemCompleter.complete(path);
}else if(errorCode == 3) {
downloadItemCompleter.completeError("This build is not available yet");
}else {
final errorMessage = result["errorMessage"];
downloadItemCompleter.completeError("$errorMessage (error code $errorCode)");
} }
if(statusCode == 404) { timer.cancel();
throw _unavailableError; return;
} }
throw _genericError; final speed = int.parse(result["downloadSpeed"] ?? "0");
}, final completedLength = int.parse(files[0]["completedLength"] ?? "0");
headers: byteStart == null || byteStart <= 0 ? { final totalLength = int.parse(files[0]["length"] ?? "0");
"Cookie": "_c_t_c=1"
} : { final percentage = completedLength * 100 / totalLength;
"Cookie": "_c_t_c=1", final minutesLeft = speed == 0 ? -1 : ((totalLength - completedLength) / speed / 60).round();
"Range": "bytes=${byteStart}-" _onProgress(
}, options.port,
) percentage,
speed,
minutesLeft,
false
); );
}catch(error) { }catch(error) {
if(stopped.isCompleted) { throw "Invalid download status (${error})";
return;
} }
});
if(errorsCount > _maxErrors || error.toString().contains(_deniedConnectionError) || error.toString().contains(_unavailableError)) { await Future.any([stopped.future, downloadItemCompleter.future]);
if(!stopped.isCompleted) {
final extension = path.extension(fileName);
await _extractArchive(stopped, extension, await downloadItemCompleter.future, options);
}else {
await _stopAriaDownload(downloadId);
}
}catch(error) {
_onError(error, options); _onError(error, options);
return; }finally {
delete(outputFile);
}
} }
await _downloadArchive(options, stopped, tempFile, startTime, received, errorsCount + 1); Future<void> _startAriaServer() async {
final running = await _isAriaRunning();
if(running) {
await killProcessByPort(_ariaPort);
}
final aria2c = File("${assetsDirectory.path}\\build\\aria2c.exe");
if(!aria2c.existsSync()) {
throw "Missing aria2c.exe";
}
await startProcess(
executable: aria2c,
args: [
"--max-connection-per-server=${Platform.numberOfProcessors}",
"--split=${Platform.numberOfProcessors}",
"--enable-rpc",
"--rpc-listen-all=true",
"--rpc-allow-origin-all",
"--rpc-secret=$_ariaSecret",
"--rpc-listen-port=$_ariaPort"
],
window: false
);
for(var i = 0; i < _ariaMaxSpawnTime.inSeconds; i++) {
if(await _isAriaRunning()) {
return;
}
await Future.delayed(const Duration(seconds: 1));
}
throw "cannot start download server (timeout exceeded)";
}
Future<bool> _isAriaRunning() async {
try {
final statusRequestId = Uuid().toString().replaceAll("-", "");
final statusRequest = {
"jsonrcp": "2.0",
"id": statusRequestId,
"method": "aria2.getVersion",
"params": [
"token:${_ariaSecret}"
]
};
await http.post(_ariaEndpoint, body: jsonEncode(statusRequest));
return true;
}catch(_) {
return false;
} }
} }
Future<String> _startAriaDownload(FortniteBuildDownloadOptions options, File outputFile) async {
http.Response? addDownloadResponse;
try {
final addDownloadRequestId = Uuid().toString().replaceAll("-", "");
final addDownloadRequest = {
"jsonrcp": "2.0",
"id": addDownloadRequestId,
"method": "aria2.addUri",
"params": [
"token:${_ariaSecret}",
[options.build.link],
{
"dir": outputFile.parent.path,
"out": path.basename(outputFile.path)
}
]
};
addDownloadResponse = await http.post(_ariaEndpoint, body: jsonEncode(addDownloadRequest));
final addDownloadResponseJson = jsonDecode(addDownloadResponse.body);
final downloadId = addDownloadResponseJson is Map ? addDownloadResponseJson['result'] : null;
if(downloadId == null) {
throw "Start failed (${addDownloadResponse.body})";
}
return downloadId;
}catch(error) {
throw "Start failed (${addDownloadResponse?.body ?? error})";
}
}
Future<void> _stopAriaDownload(String downloadId) async {
try {
final addDownloadRequestId = Uuid().toString().replaceAll("-", "");
final addDownloadRequest = {
"jsonrcp": "2.0",
"id": addDownloadRequestId,
"method": "aria2.forceRemove",
"params": [
"token:${_ariaSecret}",
downloadId
]
};
await http.post(_ariaEndpoint, body: jsonEncode(addDownloadRequest));
}catch(error) {
throw "Stop failed (${error})";
}
}
Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async { Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async {
final startTime = DateTime.now().millisecondsSinceEpoch;
Process? process; Process? process;
switch (extension.toLowerCase()) { switch (extension.toLowerCase()) {
case ".zip": case ".zip":
final sevenZip = File("${assetsDirectory.path}\\build\\7zip.exe"); final sevenZip = File("${assetsDirectory.path}\\build\\7zip.exe");
if(!sevenZip.existsSync()) { if(!sevenZip.existsSync()) {
throw "Corrupted installation: missing 7zip.exe"; throw "Missing 7zip.exe";
} }
process = await startProcess( process = await startProcess(
@@ -176,10 +254,15 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
); );
var completed = false; var completed = false;
process.stdOutput.listen((data) { process.stdOutput.listen((data) {
final now = DateTime.now().millisecondsSinceEpoch;
if(data.toLowerCase().contains("everything is ok")) { if(data.toLowerCase().contains("everything is ok")) {
completed = true; completed = true;
_onProgress(startTime, now, 100, true, options); _onProgress(
options.port,
100,
0,
-1,
true
);
process?.kill(ProcessSignal.sigabrt); process?.kill(ProcessSignal.sigabrt);
return; return;
} }
@@ -190,7 +273,13 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
} }
final percentage = int.parse(element.substring(0, element.length - 1)).toDouble(); final percentage = int.parse(element.substring(0, element.length - 1)).toDouble();
_onProgress(startTime, now, percentage, true, options); _onProgress(
options.port,
percentage,
0,
-1,
true
);
}); });
process.stdError.listen((data) { process.stdError.listen((data) {
if(!data.isBlank) { if(!data.isBlank) {
@@ -206,7 +295,7 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
case ".rar": case ".rar":
final winrar = File("${assetsDirectory.path}\\build\\winrar.exe"); final winrar = File("${assetsDirectory.path}\\build\\winrar.exe");
if(!winrar.existsSync()) { if(!winrar.existsSync()) {
throw "Corrupted installation: missing winrar.exe"; throw "Missing winrar.exe";
} }
process = await startProcess( process = await startProcess(
@@ -221,11 +310,16 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
); );
var completed = false; var completed = false;
process.stdOutput.listen((data) { process.stdOutput.listen((data) {
final now = DateTime.now().millisecondsSinceEpoch;
data = data.replaceAll("\r", "").replaceAll("\b", "").trim(); data = data.replaceAll("\r", "").replaceAll("\b", "").trim();
if(data == "All OK") { if(data == "All OK") {
completed = true; completed = true;
_onProgress(startTime, now, 100, true, options); _onProgress(
options.port,
100,
0,
-1,
true
);
process?.kill(ProcessSignal.sigabrt); process?.kill(ProcessSignal.sigabrt);
return; return;
} }
@@ -236,7 +330,13 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
} }
final percentage = int.parse(element).toDouble(); final percentage = int.parse(element).toDouble();
_onProgress(startTime, now, percentage, true, options); _onProgress(
options.port,
percentage,
0,
-1,
true
);
}); });
process.stdError.listen((data) { process.stdError.listen((data) {
if(!data.isBlank) { if(!data.isBlank) {
@@ -257,21 +357,22 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
process.kill(ProcessSignal.sigabrt); process.kill(ProcessSignal.sigabrt);
} }
void _onProgress(int startTime, int? now, double percentage, bool extracting, FortniteBuildDownloadOptions options) { void _onProgress(SendPort port, double percentage, int speed, int minutesLeft, bool extracting) {
if(percentage == 0) { if(percentage == 0) {
options.port.send(FortniteBuildDownloadProgress( port.send(FortniteBuildDownloadProgress(
progress: percentage, progress: percentage,
extracting: extracting extracting: extracting,
timeLeft: null,
speed: speed
)); ));
return; return;
} }
final msLeft = now == null ? null : startTime + (now - startTime) * 100 / percentage - now; port.send(FortniteBuildDownloadProgress(
final minutesLeft = msLeft == null ? null : (msLeft / 1000 / 60).round();
options.port.send(FortniteBuildDownloadProgress(
progress: percentage, progress: percentage,
extracting: extracting, extracting: extracting,
minutesLeft: minutesLeft timeLeft: minutesLeft,
speed: speed
)); ));
} }
@@ -292,3 +393,4 @@ Completer<dynamic> _setupLifecycle(FortniteBuildDownloadOptions options) {
options.port.send(lifecyclePort.sendPort); options.port.send(lifecyclePort.sendPort);
return stopped; return stopped;
} }

View File

@@ -9,9 +9,9 @@ bool _watcher = false;
final File rebootBeforeS20DllFile = File("${dllsDirectory.path}\\reboot.dll"); final File rebootBeforeS20DllFile = File("${dllsDirectory.path}\\reboot.dll");
final File rebootAboveS20DllFile = File("${dllsDirectory.path}\\rebootS20.dll"); final File rebootAboveS20DllFile = File("${dllsDirectory.path}\\rebootS20.dll");
const String kRebootBelowS20DownloadUrl = const String kRebootBelowS20DownloadUrl =
"http://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/Release.zip"; "https://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/Reboot.zip";
const String kRebootAboveS20DownloadUrl = const String kRebootAboveS20DownloadUrl =
"http://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/aboveS20/Release.zip"; "https://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/RebootS20.zip";
Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force = false}) async { Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force = false}) async {
final lastUpdate = await _getLastUpdate(lastUpdateMs); final lastUpdate = await _getLastUpdate(lastUpdateMs);

View File

@@ -8,10 +8,7 @@ import 'dart:isolate';
import 'dart:math'; import 'dart:math';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
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:win32/win32.dart'; import 'package:win32/win32.dart';
final _ntdll = DynamicLibrary.open('ntdll.dll'); final _ntdll = DynamicLibrary.open('ntdll.dll');
@@ -98,8 +95,8 @@ Future<bool> startElevatedProcess({required String executable, required String a
var shellInput = calloc<SHELLEXECUTEINFO>(); var shellInput = calloc<SHELLEXECUTEINFO>();
shellInput.ref.lpFile = executable.toNativeUtf16(); shellInput.ref.lpFile = executable.toNativeUtf16();
shellInput.ref.lpParameters = args.toNativeUtf16(); shellInput.ref.lpParameters = args.toNativeUtf16();
shellInput.ref.nShow = window ? SW_SHOWNORMAL : SW_HIDE; shellInput.ref.nShow = window ? SHOW_WINDOW_CMD.SW_SHOWNORMAL : SHOW_WINDOW_CMD.SW_HIDE;
shellInput.ref.fMask = ES_AWAYMODE_REQUIRED; shellInput.ref.fMask = EXECUTION_STATE.ES_AWAYMODE_REQUIRED;
shellInput.ref.lpVerb = "runas".toNativeUtf16(); shellInput.ref.lpVerb = "runas".toNativeUtf16();
shellInput.ref.cbSize = sizeOf<SHELLEXECUTEINFO>(); shellInput.ref.cbSize = sizeOf<SHELLEXECUTEINFO>();
return ShellExecuteEx(shellInput) == 1; return ShellExecuteEx(shellInput) == 1;
@@ -154,47 +151,36 @@ final _NtSuspendProcess = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
int Function(int hWnd)>('NtSuspendProcess'); int Function(int hWnd)>('NtSuspendProcess');
bool suspend(int pid) { bool suspend(int pid) {
final processHandle = OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, pid); final processHandle = OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_SUSPEND_RESUME, FALSE, pid);
final result = _NtSuspendProcess(processHandle);
CloseHandle(processHandle);
return result == 0;
}
bool resume(int pid) {
final processHandle = OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, pid);
final result = _NtResumeProcess(processHandle);
CloseHandle(processHandle);
return result == 0;
}
void _watchProcess(int pid) {
final processHandle = OpenProcess(SYNCHRONIZE, FALSE, pid);
try { try {
WaitForSingleObject(processHandle, INFINITE); return _NtSuspendProcess(processHandle) == 0;
} finally { } finally {
CloseHandle(processHandle); CloseHandle(processHandle);
} }
} }
Future<bool> watchProcess(int pid) async { bool resume(int pid) {
var completer = Completer<bool>(); final processHandle = OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_SUSPEND_RESUME, FALSE, pid);
var exitPort = ReceivePort(); try {
exitPort.listen((_) { return _NtResumeProcess(processHandle) == 0;
if(!completer.isCompleted) { } finally {
completer.complete(true); CloseHandle(processHandle);
}
}
Future<void> watchProcess(int pid) => Isolate.run(() {
final processHandle = OpenProcess(FILE_ACCESS_RIGHTS.SYNCHRONIZE, FALSE, pid);
if (processHandle == 0) {
return;
}
try {
WaitForSingleObject(processHandle, INFINITE);
}finally {
CloseHandle(processHandle);
} }
}); });
var errorPort = ReceivePort();
errorPort.listen((_) => completer.complete(false));
await Isolate.spawn(
_watchProcess,
pid,
onExit: exitPort.sendPort,
onError: errorPort.sendPort,
errorsAreFatal: true
);
return await completer.future;
}
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 logging, String additionalArgs) {
log("[PROCESS] Generating reboot args"); log("[PROCESS] Generating reboot args");

View File

@@ -7,7 +7,6 @@ environment:
sdk: ">=3.0.0 <=4.0.0" sdk: ">=3.0.0 <=4.0.0"
dependencies: dependencies:
dio: ^5.7.0
win32: ^5.5.4 win32: ^5.5.4
ffi: ^2.1.3 ffi: ^2.1.3
path: ^1.9.0 path: ^1.9.0
@@ -17,7 +16,7 @@ dependencies:
ini: ^2.1.0 ini: ^2.1.0
shelf_proxy: ^1.0.2 shelf_proxy: ^1.0.2
sync: ^0.3.0 sync: ^0.3.0
uuid: ^3.0.6 uuid: ^4.5.1
shelf_web_socket: ^2.0.0 shelf_web_socket: ^2.0.0
version: ^3.0.2 version: ^3.0.2

BIN
gui/assets/build/aria2c.exe Normal file

Binary file not shown.

View File

@@ -1,2 +0,0 @@
taskkill /f /im winrar.exe
taskkill /f /im tar.exe

Binary file not shown.

Binary file not shown.

View File

@@ -76,7 +76,7 @@
"playGameServerCustomContent": "Enter IP", "playGameServerCustomContent": "Enter IP",
"settingsName": "Settings", "settingsName": "Settings",
"settingsClientName": "Internal files", "settingsClientName": "Internal files",
"settingsClientDescription": "Configure the internal files used by the launcher for Fortnite", "settingsClientDescription": "Configure the internal files used by the launcher",
"settingsClientOptionsName": "Options", "settingsClientOptionsName": "Options",
"settingsClientOptionsDescription": "Configure additional options for Fortnite", "settingsClientOptionsDescription": "Configure additional options for Fortnite",
"settingsClientConsoleName": "Unreal engine patcher", "settingsClientConsoleName": "Unreal engine patcher",
@@ -94,20 +94,19 @@
"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",
"settingsServerOptionsName": "Options", "settingsServerOptionsName": "Options",
"settingsServerOptionsSubtitle": "Configure additional options for the game server", "settingsServerOptionsSubtitle": "Configure additional options for the game server",
"settingsServerTypeName": "Type", "settingsServerTypeName": "Game server type",
"settingsServerTypeDescription": "The type of game server to inject", "settingsServerTypeDescription": "The type of game server to inject",
"settingsServerTypeEmbeddedName": "Embedded", "settingsServerTypeEmbeddedName": "Embedded",
"settingsServerTypeCustomName": "Custom", "settingsServerTypeCustomName": "Custom",
"settingsOldServerFileName": "Game server (Before Fortnite Season 20)", "settingsOldServerFileName": "Game server",
"settingsNewServerFileName": "Game server (After Fortnite Season 20)",
"settingsServerFileDescription": "The file injected to create the game server", "settingsServerFileDescription": "The file injected to create the game server",
"settingsServerPortName": "Port", "settingsServerPortName": "Port",
"settingsServerPortDescription": "The port the launcher expects the game server to be hosted on", "settingsServerPortDescription": "The port the launcher expects the game server to be hosted on",
"settingsServerOldMirrorName": "Update mirror (Before Fortnite Season 20)", "settingsServerOldMirrorName": "Update mirror (Before season 20)",
"settingsServerNewMirrorName": "Update mirror (After Fortnite Season 20)", "settingsServerNewMirrorName": "Update mirror (Season 20 and above)",
"settingsServerMirrorDescription": "The URL used to update the game server dll", "settingsServerMirrorDescription": "The URL used to update the game server dll",
"settingsServerMirrorPlaceholder": "mirror", "settingsServerMirrorPlaceholder": "mirror",
"settingsServerTimerName": "Update timer", "settingsServerTimerName": "Game server updater",
"settingsServerTimerSubtitle": "Determines when the game server should be updated", "settingsServerTimerSubtitle": "Determines when the game server should be updated",
"settingsUtilsName": "Launcher", "settingsUtilsName": "Launcher",
"settingsUtilsSubtitle": "This section contains settings related to the launcher", "settingsUtilsSubtitle": "This section contains settings related to the launcher",
@@ -217,6 +216,8 @@
"downloadedVersion": "The download was completed successfully!", "downloadedVersion": "The download was completed successfully!",
"download": "Download", "download": "Download",
"downloading": "Downloading...", "downloading": "Downloading...",
"allocatingSpace": "Allocating disk space...",
"startingDownload": "Starting download...",
"extracting": "Extracting...", "extracting": "Extracting...",
"buildProgress": "{progress}%", "buildProgress": "{progress}%",
"buildInstallationDirectory": "Installation directory", "buildInstallationDirectory": "Installation directory",
@@ -326,6 +327,8 @@
"backendErrorMessage": "The backend reported an unexpected error", "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",
"hostAccountText": "The host tab shows different credentials compared to the play tab.\nIf you are advanced user, you can set a different email and password\nhere if the backend you are using needs authentication.",
"hostAccountAction": "I understand",
"welcomeAction": "Take the tour", "welcomeAction": "Take the tour",
"startOnboardingText": "Start by choosing a username: this will be visible to other players on Fortnite.\nIf you are advanced user, you can set the email and password here if the backend\nyou are using supports authentication.", "startOnboardingText": "Start by choosing a username: this will be visible to other players on Fortnite.\nIf you are advanced user, you can set the email and password here if the backend\nyou are using supports authentication.",
"startOnboardingActionLabel": "Let's do it", "startOnboardingActionLabel": "Let's do it",

View File

@@ -1,8 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:file_picker/file_picker.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart'; import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:flutter_gen/gen_l10n/reboot_localizations.dart'; import 'package:flutter_gen/gen_l10n/reboot_localizations.dart';
@@ -154,23 +152,24 @@ Future<Object?> _initUrlHandler() async {
} }
} }
void _initWindow() => doWhenWindowReady(() async { Future<void> _initWindow() async {
try { try {
await SystemTheme.accentColor.load(); await SystemTheme.accentColor.load();
await windowManager.ensureInitialized(); await windowManager.ensureInitialized();
await Window.initialize(); await Window.initialize();
var settingsController = Get.find<SettingsController>(); var settingsController = Get.find<SettingsController>();
var size = Size(settingsController.width, settingsController.height); var size = Size(settingsController.width, settingsController.height);
appWindow.size = size; await windowManager.setSize(size);
var offsetX = settingsController.offsetX; var offsetX = settingsController.offsetX;
var offsetY = settingsController.offsetY; var offsetY = settingsController.offsetY;
if(offsetX != null && offsetY != null) { if(offsetX != null && offsetY != null) {
appWindow.position = Offset( final position = Offset(
offsetX, offsetX,
offsetY offsetY
); );
await windowManager.setPosition(position);
}else { }else {
appWindow.alignment = Alignment.center; await windowManager.setAlignment(Alignment.center);
} }
if(isWin11) { if(isWin11) {
@@ -183,9 +182,9 @@ void _initWindow() => doWhenWindowReady(() async {
}catch(error, stackTrace) { }catch(error, stackTrace) {
onError(error, stackTrace, false); onError(error, stackTrace, false);
}finally { }finally {
appWindow.show(); windowManager.show();
}
} }
});
Future<List<Object>> _initStorage() async { Future<List<Object>> _initStorage() async {
final errors = <Object>[]; final errors = <Object>[];

View File

@@ -10,7 +10,7 @@ import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/util/keyboard.dart'; import 'package:reboot_launcher/src/util/keyboard.dart';
class BackendController extends GetxController { class BackendController extends GetxController {
static const String storageName = "backend_storage"; static const String storageName = "v2_backend_storage";
static const PhysicalKeyboardKey _kDefaultConsoleKey = PhysicalKeyboardKey(0x00070041); static const PhysicalKeyboardKey _kDefaultConsoleKey = PhysicalKeyboardKey(0x00070041);
late final GetStorage? _storage; late final GetStorage? _storage;

View File

@@ -9,16 +9,16 @@ import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart'; import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/util/translations.dart';
import 'package:version/version.dart';
class DllController extends GetxController { class DllController extends GetxController {
static const String storageName = "dll_storage"; static const String storageName = "v2_dll_storage";
late final GetStorage? _storage; late final GetStorage? _storage;
late final String originalDll; late final String originalDll;
late final TextEditingController gameServerDll; late final TextEditingController gameServerDll;
late final TextEditingController unrealEngineConsoleDll; late final TextEditingController unrealEngineConsoleDll;
late final TextEditingController backendDll; late final TextEditingController backendDll;
late final TextEditingController memoryLeakDll;
late final TextEditingController gameServerPort; late final TextEditingController gameServerPort;
late final Rx<UpdateTimer> timer; late final Rx<UpdateTimer> timer;
late final TextEditingController beforeS20Mirror; late final TextEditingController beforeS20Mirror;
@@ -33,8 +33,7 @@ class DllController extends GetxController {
_storage = appWithNoStorage ? null : GetStorage(storageName); _storage = appWithNoStorage ? null : GetStorage(storageName);
gameServerDll = _createController("game_server", InjectableDll.reboot); gameServerDll = _createController("game_server", InjectableDll.reboot);
unrealEngineConsoleDll = _createController("unreal_engine_console", InjectableDll.console); unrealEngineConsoleDll = _createController("unreal_engine_console", InjectableDll.console);
backendDll = _createController("backend", InjectableDll.cobalt); backendDll = _createController("backend", InjectableDll.starfall);
memoryLeakDll = _createController("memory_leak", InjectableDll.memory);
gameServerPort = TextEditingController(text: _storage?.read("game_server_port") ?? kDefaultGameServerPort); gameServerPort = TextEditingController(text: _storage?.read("game_server_port") ?? kDefaultGameServerPort);
gameServerPort.addListener(() => _storage?.write("game_server_port", gameServerPort.text)); gameServerPort.addListener(() => _storage?.write("game_server_port", gameServerPort.text));
final timerIndex = _storage?.read("timer"); final timerIndex = _storage?.read("timer");
@@ -60,8 +59,7 @@ class DllController extends GetxController {
void resetGame() { void resetGame() {
gameServerDll.text = getDefaultDllPath(InjectableDll.reboot); gameServerDll.text = getDefaultDllPath(InjectableDll.reboot);
unrealEngineConsoleDll.text = getDefaultDllPath(InjectableDll.console); unrealEngineConsoleDll.text = getDefaultDllPath(InjectableDll.console);
backendDll.text = getDefaultDllPath(InjectableDll.cobalt); backendDll.text = getDefaultDllPath(InjectableDll.starfall);
memoryLeakDll.text = getDefaultDllPath(InjectableDll.memory);
} }
void resetServer() { void resetServer() {
@@ -155,27 +153,21 @@ class DllController extends GetxController {
} }
} }
(File, bool) getInjectableData(InjectableDll dll) { (File, bool) getInjectableData(Version version, InjectableDll dll) {
final defaultPath = canonicalize(getDefaultDllPath(dll)); final defaultPath = canonicalize(getDefaultDllPath(dll));
switch(dll){ switch(dll){
case InjectableDll.reboot: case InjectableDll.reboot:
if(customGameServer.value) { if(customGameServer.value) {
final file = File(gameServerDll.text); return (File(gameServerDll.text), true);
if(file.existsSync()) {
return (file, true);
}
} }
return (rebootBeforeS20DllFile, false); return (version.major >= 20 ? rebootAboveS20DllFile : rebootBeforeS20DllFile, false);
case InjectableDll.console: case InjectableDll.console:
final ue4ConsoleFile = File(unrealEngineConsoleDll.text); final ue4ConsoleFile = File(unrealEngineConsoleDll.text);
return (ue4ConsoleFile, canonicalize(ue4ConsoleFile.path) != defaultPath); return (ue4ConsoleFile, canonicalize(ue4ConsoleFile.path) != defaultPath);
case InjectableDll.cobalt: case InjectableDll.starfall:
final backendFile = File(backendDll.text); final backendFile = File(backendDll.text);
return (backendFile, canonicalize(backendFile.path) != defaultPath); return (backendFile, canonicalize(backendFile.path) != defaultPath);
case InjectableDll.memory:
final memoryLeakFile = File(memoryLeakDll.text);
return (memoryLeakFile, canonicalize(memoryLeakFile.path) != defaultPath);
} }
} }
@@ -187,7 +179,7 @@ class DllController extends GetxController {
log("[DLL] File name: $fileName"); log("[DLL] File name: $fileName");
InfoBarEntry? entry; InfoBarEntry? entry;
try { try {
if (fileName == "reboot.dll") { if (fileName.contains("reboot")) {
log("[DLL] Downloading reboot.dll..."); log("[DLL] Downloading reboot.dll...");
return await updateGameServerDll( return await updateGameServerDll(
silent: silent silent: silent

View File

@@ -8,7 +8,7 @@ import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/main.dart';
class GameController extends GetxController { class GameController extends GetxController {
static const String storageName = "game_storage"; static const String storageName = "v2_game_storage";
late final GetStorage? _storage; late final GetStorage? _storage;
late final TextEditingController username; late final TextEditingController username;

View File

@@ -12,10 +12,12 @@ 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"; static const String storageName = "v2_hosting_storage";
late final GetStorage? _storage; late final GetStorage? _storage;
late final String uuid; late final String uuid;
late final TextEditingController accountUsername;
late final TextEditingController accountPassword;
late final TextEditingController name; late final TextEditingController name;
late final FocusNode nameFocusNode; late final FocusNode nameFocusNode;
late final TextEditingController description; late final TextEditingController description;
@@ -37,6 +39,10 @@ class HostingController extends GetxController {
_storage = appWithNoStorage ? null : GetStorage(storageName); _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);
accountUsername = TextEditingController(text: _storage?.read("account_username") ?? kDefaultHostName);
accountUsername.addListener(() => _storage?.write("account_username", accountUsername.text));
accountPassword = TextEditingController(text: _storage?.read("account_password") ?? "");
accountPassword.addListener(() => _storage?.write("account_password", password.text));
name = TextEditingController(text: _storage?.read("name")); name = TextEditingController(text: _storage?.read("name"));
name.addListener(() => _storage?.write("name", name.text)); name.addListener(() => _storage?.write("name", name.text));
description = TextEditingController(text: _storage?.read("description")); description = TextEditingController(text: _storage?.read("description"));
@@ -152,6 +158,8 @@ class HostingController extends GetxController {
} }
void reset() { void reset() {
accountUsername.text = kDefaultHostName;
accountPassword.text = "";
name.text = ""; name.text = "";
description.text = ""; description.text = "";
showPassword.value = false; showPassword.value = false;

View File

@@ -13,7 +13,7 @@ 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"; static const String storageName = "v2_settings_storage";
late final GetStorage? _storage; late final GetStorage? _storage;
late final RxString language; late final RxString language;

View File

@@ -17,6 +17,7 @@ import 'package:reboot_launcher/src/util/translations.dart';
import 'package:reboot_launcher/src/widget/version_selector.dart'; import 'package:reboot_launcher/src/widget/version_selector.dart';
void startOnboarding() { void startOnboarding() {
final gameController = Get.find<GameController>();
final settingsController = Get.find<SettingsController>(); final settingsController = Get.find<SettingsController>();
settingsController.firstRun.value = false; settingsController.firstRun.value = false;
profileOverlayKey.currentState!.showOverlay( profileOverlayKey.currentState!.showOverlay(
@@ -27,7 +28,7 @@ void startOnboarding() {
label: translations.startOnboardingActionLabel, label: translations.startOnboardingActionLabel,
onTap: () async { onTap: () async {
onClose(); onClose();
await showProfileForm(context); await showProfileForm(context, gameController.username, gameController.password);
_promptPlayPage(); _promptPlayPage();
} }
) )
@@ -78,6 +79,22 @@ void _promptServerBrowserPage() {
context: context, context: context,
label: translations.promptServerBrowserPageActionLabel, label: translations.promptServerBrowserPageActionLabel,
onTap: () { onTap: () {
onClose();
_promptHostAccount();
}
)
);
}
void _promptHostAccount() {
pageIndex.value = RebootPageType.host.index;
profileOverlayKey.currentState!.showOverlay(
text: translations.hostAccountText,
offset: Offset(27.5, 17.5),
actionBuilder: (context, onClose) => _buildActionButton(
context: context,
label: translations.hostAccountAction,
onTap: () async {
onClose(); onClose();
_promptHostPage(); _promptHostPage();
} }
@@ -86,7 +103,6 @@ void _promptServerBrowserPage() {
} }
void _promptHostPage() { void _promptHostPage() {
pageIndex.value = RebootPageType.host.index;
pageOverlayTargetKey.currentState!.showOverlay( pageOverlayTargetKey.currentState!.showOverlay(
text: translations.promptHostPageText, text: translations.promptHostPageText,
actionBuilder: (context, onClose) => _buildActionButton( actionBuilder: (context, onClose) => _buildActionButton(

View File

@@ -6,13 +6,11 @@ import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart'; import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/util/translations.dart';
final GameController _gameController = Get.find<GameController>(); Future<bool> showProfileForm(BuildContext context, TextEditingController username, TextEditingController password) async{
Future<bool> showProfileForm(BuildContext context) async{
final showPassword = RxBool(false); final showPassword = RxBool(false);
final oldUsername = _gameController.username.text; final oldUsername = username.text;
final showPasswordTrailing = RxBool(oldUsername.isNotEmpty); final showPasswordTrailing = RxBool(oldUsername.isNotEmpty);
final oldPassword = _gameController.password.text; final oldPassword = password.text;
final result = await showRebootDialog<bool?>( final result = await showRebootDialog<bool?>(
builder: (context) => Obx(() => FormDialog( builder: (context) => Obx(() => FormDialog(
content: Column( content: Column(
@@ -25,17 +23,17 @@ Future<bool> showProfileForm(BuildContext context) async{
child: TextFormBox( child: TextFormBox(
placeholder: translations.usernameOrEmailPlaceholder, placeholder: translations.usernameOrEmailPlaceholder,
validator: (text) { validator: (text) {
if(_gameController.password.text.isEmpty) { if(password.text.isEmpty) {
return null; return null;
} }
if(EmailValidator.validate(_gameController.username.text)) { if(EmailValidator.validate(username.text)) {
return null; return null;
} }
return translations.invalidEmail; return translations.invalidEmail;
}, },
controller: _gameController.username, controller: username,
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
enableSuggestions: true, enableSuggestions: true,
autofocus: true, autofocus: true,
@@ -47,7 +45,7 @@ Future<bool> showProfileForm(BuildContext context) async{
label: translations.password, label: translations.password,
child: TextFormBox( child: TextFormBox(
placeholder: translations.passwordPlaceholder, placeholder: translations.passwordPlaceholder,
controller: _gameController.password, controller: password,
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
obscureText: !showPassword.value, obscureText: !showPassword.value,
enableSuggestions: false, enableSuggestions: false,
@@ -87,7 +85,7 @@ Future<bool> showProfileForm(BuildContext context) async{
return true; return true;
} }
_gameController.username.text = oldUsername; username.text = oldUsername;
_gameController.password.text = oldPassword; password.text = oldPassword;
return false; return false;
} }

View File

@@ -33,16 +33,15 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
final Rxn<FortniteBuild> _build = Rxn(); final Rxn<FortniteBuild> _build = Rxn();
final RxnInt _timeLeft = RxnInt(); final RxnInt _timeLeft = RxnInt();
final Rxn<double> _progress = Rxn(); final Rxn<double> _progress = Rxn();
final RxInt _speed = RxInt(0);
late DiskSpace _diskSpace; late DiskSpace _diskSpace;
late Future<List<FortniteBuild>> _fetchFuture; late Future<List<FortniteBuild>> _fetchFuture;
late Future _diskFuture; late Future _diskFuture;
Isolate? _isolate;
SendPort? _downloadPort; SendPort? _downloadPort;
Object? _error; Object? _error;
StackTrace? _stackTrace; StackTrace? _stackTrace;
bool _selecting = false;
@override @override
void initState() { void initState() {
@@ -61,9 +60,8 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
} }
void _cancelDownload() { void _cancelDownload() {
Process.run('${assetsDirectory.path}\\build\\stop.bat', []);
_downloadPort?.send(kStopBuildDownloadSignal); _downloadPort?.send(kStopBuildDownloadSignal);
_isolate?.kill(priority: Isolate.immediate); WindowsTaskbar.setProgressMode(TaskbarProgressMode.noProgress);
} }
@override @override
@@ -158,7 +156,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
final communicationPort = ReceivePort(); final communicationPort = ReceivePort();
communicationPort.listen((message) { communicationPort.listen((message) {
if(message is FortniteBuildDownloadProgress) { if(message is FortniteBuildDownloadProgress) {
_onProgress(build, message.progress, message.minutesLeft, message.extracting); _onProgress(build, message);
}else if(message is SendPort) { }else if(message is SendPort) {
_downloadPort = message; _downloadPort = message;
}else { }else {
@@ -172,7 +170,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
); );
final errorPort = ReceivePort(); final errorPort = ReceivePort();
errorPort.listen((message) => _onDownloadError(message, null)); errorPort.listen((message) => _onDownloadError(message, null));
_isolate = await Isolate.spawn( await Isolate.spawn(
downloadArchiveBuild, downloadArchiveBuild,
options, options,
onError: errorPort.sendPort, onError: errorPort.sendPort,
@@ -212,23 +210,24 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
_stackTrace = stackTrace; _stackTrace = stackTrace;
} }
void _onProgress(FortniteBuild build, double progress, int? timeLeft, bool extracting) { void _onProgress(FortniteBuild build, FortniteBuildDownloadProgress message) {
if (!mounted) { if (!mounted) {
return; return;
} }
if(progress >= 100 && extracting) { if(message.progress >= 100 && message.extracting) {
_onDownloadComplete(build); _onDownloadComplete(build);
return; return;
} }
_status.value = extracting ? _DownloadStatus.extracting : _DownloadStatus.downloading; _status.value = message.extracting ? _DownloadStatus.extracting : _DownloadStatus.downloading;
if(progress >= 0) { if(message.progress >= 0) {
WindowsTaskbar.setProgress(progress.round(), 100); WindowsTaskbar.setProgress(message.progress.round(), 100);
} }
_timeLeft.value = timeLeft; _timeLeft.value = message.timeLeft;
_progress.value = progress; _progress.value = message.progress;
_speed.value = message.speed;
} }
Widget get _progressBody { Widget get _progressBody {
@@ -239,16 +238,18 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
_status.value == _DownloadStatus.downloading ? translations.downloading : translations.extracting, _statusText,
style: FluentTheme.maybeOf(context)?.typography.body, style: FluentTheme.maybeOf(context)?.typography.body,
textAlign: TextAlign.start, textAlign: TextAlign.start,
), ),
), ),
if(_progress.value != null && !_isAllocatingDiskSpace)
const SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
if(_progress.value != null && !_isAllocatingDiskSpace)
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -271,7 +272,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ProgressBar(value: (_progress.value ?? 0).toDouble()) child: ProgressBar(value: _isAllocatingDiskSpace ? null : _progress.value?.toDouble())
), ),
const SizedBox( const SizedBox(
@@ -281,6 +282,24 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
); );
} }
String get _statusText {
if (_status.value != _DownloadStatus.downloading) {
return translations.extracting;
}
if (_progress.value == null) {
return translations.startingDownload;
}
if (_speed.value == 0) {
return translations.allocatingSpace;
}
return translations.downloading;
}
bool get _isAllocatingDiskSpace => _status.value == _DownloadStatus.downloading && _speed.value == 0;
Widget _buildFormBody(List<FortniteBuild> builds) { Widget _buildFormBody(List<FortniteBuild> builds) {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@@ -3,7 +3,6 @@ import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:app_links/app_links.dart'; import 'package:app_links/app_links.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show MaterialPage; import 'package:flutter/material.dart' show MaterialPage;
@@ -27,6 +26,7 @@ import 'package:reboot_launcher/src/util/translations.dart';
import 'package:reboot_launcher/src/widget/info_bar_area.dart'; import 'package:reboot_launcher/src/widget/info_bar_area.dart';
import 'package:reboot_launcher/src/widget/profile_tile.dart'; import 'package:reboot_launcher/src/widget/profile_tile.dart';
import 'package:reboot_launcher/src/widget/title_bar.dart'; import 'package:reboot_launcher/src/widget/title_bar.dart';
import 'package:version/version.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
final GlobalKey<OverlayTargetState> profileOverlayKey = GlobalKey(); final GlobalKey<OverlayTargetState> profileOverlayKey = GlobalKey();
@@ -58,7 +58,6 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
@override @override
void initState() { void initState() {
super.initState(); super.initState();
windowManager.setPreventClose(true);
windowManager.addListener(this); windowManager.addListener(this);
_syncPageViewWithNavigator(); _syncPageViewWithNavigator();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -111,7 +110,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
return; return;
} }
var result = await pingGameServer(address); final result = await pingGameServer(address);
if(result) { if(result) {
return; return;
} }
@@ -135,13 +134,12 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
dllsDirectory.createSync(recursive: true); dllsDirectory.createSync(recursive: true);
} }
final dummy = Version.parse("1");
final dummyS20 = Version.parse("20");
for(final injectable in InjectableDll.values) { for(final injectable in InjectableDll.values) {
final (file, custom) = _dllController.getInjectableData(injectable); _downloadDll(dummy, injectable);
if(!custom) { if(injectable.isVersionDependent) {
_dllController.downloadCriticalDllInteractive( _downloadDll(dummyS20, injectable);
file.path,
silent: true
);
} }
} }
@@ -150,12 +148,22 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
})); }));
} }
void _downloadDll(Version version, InjectableDll injectable) {
final (file, custom) = _dllController.getInjectableData(version, injectable);
if(!custom) {
_dllController.downloadCriticalDllInteractive(
file.path,
silent: false
);
}
}
@override @override
void onWindowClose() async { void onWindowClose() async {
try { try {
await _hostingController.discardServer(); await _hostingController.discardServer();
}finally { }catch(error) {
exit(0); // Force closing log("[HOSTING] Cannot discard server: $error");
} }
} }
@@ -220,14 +228,18 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
@override @override
void onWindowResized() { void onWindowResized() {
_settingsController.saveWindowSize(appWindow.size);
_focused.value = true; _focused.value = true;
windowManager.getSize().then((size) {
_settingsController.saveWindowSize(size);
});
} }
@override @override
void onWindowMoved() { void onWindowMoved() {
_settingsController.saveWindowOffset(appWindow.position);
_focused.value = true; _focused.value = true;
windowManager.getPosition().then((position) {
_settingsController.saveWindowOffset(position);
});
} }
@override @override
@@ -449,9 +461,12 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ProfileWidget( Obx(() {
pageIndex.value;
return ProfileWidget(
overlayKey: profileOverlayKey overlayKey: profileOverlayKey
), );
}),
_autoSuggestBox, _autoSuggestBox,
const SizedBox(height: 12.0), const SizedBox(height: 12.0),
_buildNavigationTrail() _buildNavigationTrail()
@@ -554,7 +569,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
); );
GestureDetector get _draggableArea => GestureDetector( GestureDetector get _draggableArea => GestureDetector(
onDoubleTap: appWindow.maximizeOrRestore, onDoubleTap: windowManager.maximizeOrRestore,
onHorizontalDragStart: (_) => windowManager.startDragging(), onHorizontalDragStart: (_) => windowManager.startDragging(),
onVerticalDragStart: (_) => windowManager.startDragging() onVerticalDragStart: (_) => windowManager.startDragging()
); );

View File

@@ -53,7 +53,6 @@ class HostPage extends RebootPage {
class _HostingPageState extends RebootPageState<HostPage> { 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 DllController _dllController = Get.find<DllController>(); final DllController _dllController = Get.find<DllController>();
late final RxBool _showPasswordTrailing = RxBool(_hostingController.password.text.isNotEmpty); late final RxBool _showPasswordTrailing = RxBool(_hostingController.password.text.isNotEmpty);
@@ -85,7 +84,6 @@ class _HostingPageState extends RebootPageState<HostPage> {
key: hostVersionOverlayTargetKey key: hostVersionOverlayTargetKey
), ),
_options, _options,
_internalFiles,
_share, _share,
_resetDefaults _resetDefaults
]; ];
@@ -270,220 +268,6 @@ class _HostingPageState extends RebootPageState<HostPage> {
], ],
); );
SettingTile get _internalFiles => SettingTile(
icon: Icon(
FluentIcons.archive_settings_24_regular
),
title: Text(translations.settingsServerName),
subtitle: Text(translations.settingsServerSubtitle),
children: [
_internalFilesServerType,
_internalFilesUpdateTimer,
_internalFilesOldServerSource,
_internalFilesNewServerSource,
],
);
Widget get _internalFilesServerType => SettingTile(
icon: Icon(
FluentIcons.games_24_regular
),
title: Text(translations.settingsServerTypeName),
subtitle: Text(translations.settingsServerTypeDescription),
contentWidth: SettingTile.kDefaultContentWidth + 30,
content: Obx(() => DropDownButton(
onOpen: () => inDialog = true,
onClose: () => inDialog = false,
leading: Text(_dllController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName),
items: {
false: translations.settingsServerTypeEmbeddedName,
true: translations.settingsServerTypeCustomName
}.entries.map((entry) => MenuFlyoutItem(
text: Text(entry.value),
onPressed: () {
final oldValue = _dllController.customGameServer.value;
if(oldValue == entry.key) {
return;
}
_dllController.customGameServer.value = entry.key;
_dllController.infoBarEntry?.close();
if(!entry.key) {
_dllController.updateGameServerDll(
force: true
);
}
}
)).toList()
))
);
Widget get _internalFilesOldServerSource => Obx(() {
if(!_dllController.customGameServer.value) {
return SettingTile(
icon: Icon(
FluentIcons.globe_24_regular
),
title: Text(translations.settingsServerOldMirrorName),
subtitle: Text(translations.settingsServerMirrorDescription),
contentWidth: SettingTile.kDefaultContentWidth + 30,
content: Row(
children: [
Expanded(
child: TextFormBox(
placeholder: translations.settingsServerMirrorPlaceholder,
controller: _dllController.beforeS20Mirror,
onChanged: (value) {
if(Uri.tryParse(value) != null) {
_dllController.updateGameServerDll(force: true);
}
},
),
),
const SizedBox(width: 8.0),
Button(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero)
),
onPressed: () => _dllController.updateGameServerDll(force: true),
child: SizedBox.square(
dimension: 30,
child: Icon(
FluentIcons.arrow_download_24_regular
),
)
),
const SizedBox(width: 8.0),
Button(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero)
),
onPressed: () {
_dllController.beforeS20Mirror.text = kRebootBelowS20DownloadUrl;
_dllController.updateGameServerDll(force: true);
},
child: SizedBox.square(
dimension: 30,
child: Icon(
FluentIcons.arrow_reset_24_regular
),
)
)
],
)
);
}else {
return createFileSetting(
title: translations.settingsOldServerFileName,
description: translations.settingsServerFileDescription,
controller: _dllController.gameServerDll,
onReset: () {
final path = _dllController.getDefaultDllPath(InjectableDll.reboot);
_dllController.gameServerDll.text = path;
_dllController.downloadCriticalDllInteractive(path);
}
);
}
});
Widget get _internalFilesNewServerSource => Obx(() {
if(!_dllController.customGameServer.value) {
return SettingTile(
icon: Icon(
FluentIcons.globe_24_regular
),
title: Text(translations.settingsServerNewMirrorName),
subtitle: Text(translations.settingsServerMirrorDescription),
contentWidth: SettingTile.kDefaultContentWidth + 30,
content: Row(
children: [
Expanded(
child: TextFormBox(
placeholder: translations.settingsServerMirrorPlaceholder,
controller: _dllController.aboveS20Mirror,
onChanged: (value) {
if(Uri.tryParse(value) != null) {
_dllController.updateGameServerDll(force: true);
}
},
),
),
const SizedBox(width: 8.0),
Button(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero)
),
onPressed: () => _dllController.updateGameServerDll(force: true),
child: SizedBox.square(
dimension: 30,
child: Icon(
FluentIcons.arrow_download_24_regular
),
)
),
const SizedBox(width: 8.0),
Button(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero)
),
onPressed: () {
_dllController.aboveS20Mirror.text = kRebootBelowS20DownloadUrl;
_dllController.updateGameServerDll(force: true);
},
child: SizedBox.square(
dimension: 30,
child: Icon(
FluentIcons.arrow_reset_24_regular
),
)
)
],
)
);
}else {
return createFileSetting(
title: translations.settingsNewServerFileName,
description: translations.settingsServerFileDescription,
controller: _dllController.gameServerDll,
onReset: () {
final path = _dllController.getDefaultDllPath(InjectableDll.reboot);
_dllController.gameServerDll.text = path;
_dllController.downloadCriticalDllInteractive(path);
}
);
}
});
Widget get _internalFilesUpdateTimer => Obx(() {
if(_dllController.customGameServer.value) {
return const SizedBox.shrink();
}
return SettingTile(
icon: Icon(
FluentIcons.timer_24_regular
),
title: Text(translations.settingsServerTimerName),
subtitle: Text(translations.settingsServerTimerSubtitle),
contentWidth: SettingTile.kDefaultContentWidth + 30,
content: Obx(() => DropDownButton(
onOpen: () => inDialog = true,
onClose: () => inDialog = false,
leading: Text(_dllController.timer.value.text),
items: UpdateTimer.values.map((entry) => MenuFlyoutItem(
text: Text(entry.text),
onPressed: () {
_dllController.timer.value = entry;
_dllController.infoBarEntry?.close();
_dllController.updateGameServerDll(
force: true
);
}
)).toList()
))
);
});
SettingTile get _share => SettingTile( SettingTile get _share => SettingTile(
icon: Icon( icon: Icon(
FluentIcons.link_24_regular FluentIcons.link_24_regular
@@ -554,8 +338,8 @@ class _HostingPageState extends RebootPageState<HostPage> {
try { try {
_hostingController.publishServer( _hostingController.publishServer(
_gameController.username.text, _hostingController.accountUsername.text,
_hostingController.instance.value!.versionName _hostingController.instance.value!.version.toString()
); );
} catch(error) { } catch(error) {
_showCannotUpdateGameServer(error); _showCannotUpdateGameServer(error);
@@ -590,13 +374,3 @@ class _HostingPageState extends RebootPageState<HostPage> {
duration: infoBarLongDuration duration: infoBarLongDuration
); );
} }
extension _UpdateTimerExtension on UpdateTimer {
String get text {
if (this == UpdateTimer.never) {
return translations.updateGameServerDllNever;
}
return translations.updateGameServerDllEvery(name);
}
}

View File

@@ -1,16 +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/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/data.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';
import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/util/translations.dart';
import 'package:reboot_launcher/src/widget/file_setting_tile.dart';
import 'package:reboot_launcher/src/widget/game_start_button.dart'; import 'package:reboot_launcher/src/widget/game_start_button.dart';
import 'package:reboot_launcher/src/widget/setting_tile.dart'; import 'package:reboot_launcher/src/widget/setting_tile.dart';
import 'package:reboot_launcher/src/widget/version_selector_tile.dart'; import 'package:reboot_launcher/src/widget/version_selector_tile.dart';
@@ -38,7 +34,6 @@ class PlayPage extends RebootPage {
class _PlayPageState extends RebootPageState<PlayPage> { class _PlayPageState extends RebootPageState<PlayPage> {
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final DllController _dllController = Get.find<DllController>();
@override @override
Widget? get button => LaunchButton( Widget? get button => LaunchButton(
@@ -53,50 +48,9 @@ class _PlayPageState extends RebootPageState<PlayPage> {
key: gameVersionOverlayTargetKey key: gameVersionOverlayTargetKey
), ),
_options, _options,
_internalFiles,
_resetDefaults _resetDefaults
]; ];
SettingTile get _internalFiles => SettingTile(
icon: Icon(
FluentIcons.archive_settings_24_regular
),
title: Text(translations.settingsClientName),
subtitle: Text(translations.settingsClientDescription),
children: [
createFileSetting(
title: translations.settingsClientConsoleName,
description: translations.settingsClientConsoleDescription,
controller: _dllController.unrealEngineConsoleDll,
onReset: () {
final path = _dllController.getDefaultDllPath(InjectableDll.console);
_dllController.unrealEngineConsoleDll.text = path;
_dllController.downloadCriticalDllInteractive(path, force: true);
}
),
createFileSetting(
title: translations.settingsClientAuthName,
description: translations.settingsClientAuthDescription,
controller: _dllController.backendDll,
onReset: () {
final path = _dllController.getDefaultDllPath(InjectableDll.cobalt);
_dllController.backendDll.text = path;
_dllController.downloadCriticalDllInteractive(path, force: true);
}
),
createFileSetting(
title: translations.settingsClientMemoryName,
description: translations.settingsClientMemoryDescription,
controller: _dllController.memoryLeakDll,
onReset: () {
final path = _dllController.getDefaultDllPath(InjectableDll.memory);
_dllController.memoryLeakDll.text = path;
_dllController.downloadCriticalDllInteractive(path, force: true);
}
),
],
);
SettingTile get _options => SettingTile( SettingTile get _options => SettingTile(
icon: Icon( icon: Icon(
FluentIcons.options_24_regular FluentIcons.options_24_regular
@@ -127,7 +81,6 @@ class _PlayPageState extends RebootPageState<PlayPage> {
content: Button( content: Button(
onPressed: () => showResetDialog(() { onPressed: () => showResetDialog(() {
_gameController.reset(); _gameController.reset();
_dllController.resetGame();
}), }),
child: Text(translations.gameResetDefaultsContent), child: Text(translations.gameResetDefaultsContent),
) )

View File

@@ -4,11 +4,13 @@ import 'package:flutter_gen/gen_l10n/reboot_localizations.dart';
import 'package:flutter_localized_locales/flutter_localized_locales.dart'; import 'package:flutter_localized_locales/flutter_localized_locales.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/src/controller/dll_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';
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';
import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/util/translations.dart';
import 'package:reboot_launcher/src/widget/file_setting_tile.dart';
import 'package:reboot_launcher/src/widget/setting_tile.dart'; import 'package:reboot_launcher/src/widget/setting_tile.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -33,6 +35,7 @@ class SettingsPage extends RebootPage {
class _SettingsPageState extends RebootPageState<SettingsPage> { class _SettingsPageState extends RebootPageState<SettingsPage> {
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
final DllController _dllController = Get.find<DllController>();
@override @override
Widget? get button => null; Widget? get button => null;
@@ -41,9 +44,235 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
List<Widget> get settings => [ List<Widget> get settings => [
_language, _language,
_theme, _theme,
_internalFiles,
_installationDirectory, _installationDirectory,
]; ];
SettingTile get _internalFiles => SettingTile(
icon: Icon(
FluentIcons.archive_settings_24_regular
),
title: Text(translations.settingsClientName),
subtitle: Text(translations.settingsClientDescription),
children: [
createFileSetting(
title: translations.settingsClientConsoleName,
description: translations.settingsClientConsoleDescription,
controller: _dllController.unrealEngineConsoleDll,
onReset: () {
final path = _dllController.getDefaultDllPath(InjectableDll.console);
_dllController.unrealEngineConsoleDll.text = path;
_dllController.downloadCriticalDllInteractive(path, force: true);
}
),
createFileSetting(
title: translations.settingsClientAuthName,
description: translations.settingsClientAuthDescription,
controller: _dllController.backendDll,
onReset: () {
final path = _dllController.getDefaultDllPath(InjectableDll.starfall);
_dllController.backendDll.text = path;
_dllController.downloadCriticalDllInteractive(path, force: true);
}
),
_internalFilesServerType,
_internalFilesUpdateTimer,
_internalFilesServerSource,
_internalFilesNewServerSource,
],
);
Widget get _internalFilesServerType => SettingTile(
icon: Icon(
FluentIcons.games_24_regular
),
title: Text(translations.settingsServerTypeName),
subtitle: Text(translations.settingsServerTypeDescription),
contentWidth: SettingTile.kDefaultContentWidth + 30,
content: Obx(() => DropDownButton(
onOpen: () => inDialog = true,
onClose: () => inDialog = false,
leading: Text(_dllController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName),
items: {
false: translations.settingsServerTypeEmbeddedName,
true: translations.settingsServerTypeCustomName
}.entries.map((entry) => MenuFlyoutItem(
text: Text(entry.value),
onPressed: () {
final oldValue = _dllController.customGameServer.value;
if(oldValue == entry.key) {
return;
}
_dllController.customGameServer.value = entry.key;
_dllController.infoBarEntry?.close();
if(!entry.key) {
_dllController.updateGameServerDll(
force: true
);
}
}
)).toList()
))
);
Widget get _internalFilesServerSource => Obx(() {
if(!_dllController.customGameServer.value) {
return SettingTile(
icon: Icon(
FluentIcons.globe_24_regular
),
title: Text(translations.settingsServerOldMirrorName),
subtitle: Text(translations.settingsServerMirrorDescription),
contentWidth: SettingTile.kDefaultContentWidth + 30,
content: Row(
children: [
Expanded(
child: TextFormBox(
placeholder: translations.settingsServerMirrorPlaceholder,
controller: _dllController.beforeS20Mirror,
onChanged: (value) {
if(Uri.tryParse(value) != null) {
_dllController.updateGameServerDll(force: true);
}
},
),
),
const SizedBox(width: 8.0),
Button(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero)
),
onPressed: () => _dllController.updateGameServerDll(force: true),
child: SizedBox.square(
dimension: 30,
child: Icon(
FluentIcons.arrow_download_24_regular
),
)
),
const SizedBox(width: 8.0),
Button(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero)
),
onPressed: () {
_dllController.beforeS20Mirror.text = kRebootBelowS20DownloadUrl;
_dllController.updateGameServerDll(force: true);
},
child: SizedBox.square(
dimension: 30,
child: Icon(
FluentIcons.arrow_reset_24_regular
),
)
)
],
)
);
}else {
return createFileSetting(
title: translations.settingsOldServerFileName,
description: translations.settingsServerFileDescription,
controller: _dllController.gameServerDll,
onReset: () {
final path = _dllController.getDefaultDllPath(InjectableDll.reboot);
_dllController.gameServerDll.text = path;
_dllController.downloadCriticalDllInteractive(path);
}
);
}
});
Widget get _internalFilesNewServerSource => Obx(() {
if(!_dllController.customGameServer.value) {
return SettingTile(
icon: Icon(
FluentIcons.globe_24_regular
),
title: Text(translations.settingsServerNewMirrorName),
subtitle: Text(translations.settingsServerMirrorDescription),
contentWidth: SettingTile.kDefaultContentWidth + 30,
content: Row(
children: [
Expanded(
child: TextFormBox(
placeholder: translations.settingsServerMirrorPlaceholder,
controller: _dllController.aboveS20Mirror,
onChanged: (value) {
if(Uri.tryParse(value) != null) {
_dllController.updateGameServerDll(force: true);
}
},
),
),
const SizedBox(width: 8.0),
Button(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero)
),
onPressed: () => _dllController.updateGameServerDll(force: true),
child: SizedBox.square(
dimension: 30,
child: Icon(
FluentIcons.arrow_download_24_regular
),
)
),
const SizedBox(width: 8.0),
Button(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero)
),
onPressed: () {
_dllController.aboveS20Mirror.text = kRebootBelowS20DownloadUrl;
_dllController.updateGameServerDll(force: true);
},
child: SizedBox.square(
dimension: 30,
child: Icon(
FluentIcons.arrow_reset_24_regular
),
)
)
],
)
);
}else {
return const SizedBox();
}
});
Widget get _internalFilesUpdateTimer => Obx(() {
if(_dllController.customGameServer.value) {
return const SizedBox.shrink();
}
return SettingTile(
icon: Icon(
FluentIcons.timer_24_regular
),
title: Text(translations.settingsServerTimerName),
subtitle: Text(translations.settingsServerTimerSubtitle),
contentWidth: SettingTile.kDefaultContentWidth + 30,
content: Obx(() => DropDownButton(
onOpen: () => inDialog = true,
onClose: () => inDialog = false,
leading: Text(_dllController.timer.value.text),
items: UpdateTimer.values.map((entry) => MenuFlyoutItem(
text: Text(entry.text),
onPressed: () {
_dllController.timer.value = entry;
_dllController.infoBarEntry?.close();
_dllController.updateGameServerDll(
force: true
);
}
)).toList()
))
);
});
SettingTile get _language => SettingTile( SettingTile get _language => SettingTile(
icon: Icon( icon: Icon(
FluentIcons.local_language_24_regular FluentIcons.local_language_24_regular
@@ -112,3 +341,13 @@ extension _ThemeModeExtension on ThemeMode {
} }
} }
} }
extension _UpdateTimerExtension on UpdateTimer {
String get text {
if (this == UpdateTimer.never) {
return translations.updateGameServerDllNever;
}
return translations.updateGameServerDllEvery(name);
}
}

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
@@ -5,8 +6,39 @@ import 'package:reboot_common/common.dart';
const Duration _timeout = Duration(seconds: 5); const Duration _timeout = Duration(seconds: 5);
Future<bool> pingGameServer(String address, {Duration? timeout}) async { Completer<bool> pingGameServerOrTimeout(String address, Duration timeout) {
Future<bool> ping(String hostname, int port) async { final completer = Completer<bool>();
final start = DateTime.now();
(() async {
while (!completer.isCompleted && DateTime.now().millisecondsSinceEpoch - start.millisecondsSinceEpoch < timeout.inMilliseconds) {
final result = await pingGameServer(address);
if(result) {
completer.complete(true);
}else {
await Future.delayed(_timeout);
}
}
if(!completer.isCompleted) {
completer.complete(false);
}
})();
return completer;
}
Future<bool> pingGameServer(String address) async {
final split = address.split(":");
var hostname = split[0];
if(isLocalHost(hostname)) {
hostname = "127.0.0.1";
}
final port = int.parse(split.length > 1 ? split[1] : kDefaultGameServerPort);
return await _ping(hostname, port)
.timeout(_timeout, onTimeout: () => false);
}
Future<bool> _ping(String hostname, int port) async {
log("[MATCHMAKER] Pinging $hostname:$port"); log("[MATCHMAKER] Pinging $hostname:$port");
RawDatagramSocket? socket; RawDatagramSocket? socket;
try { try {
@@ -35,29 +67,3 @@ Future<bool> pingGameServer(String address, {Duration? timeout}) async {
socket?.close(); socket?.close();
} }
} }
final start = DateTime.now();
var firstTime = true;
final split = address.split(":");
var hostname = split[0];
if(isLocalHost(hostname)) {
hostname = "127.0.0.1";
}
final port = int.parse(split.length > 1 ? split[1] : kDefaultGameServerPort);
while (firstTime || (timeout != null && DateTime.now().millisecondsSinceEpoch - start.millisecondsSinceEpoch < timeout.inMilliseconds)) {
final result = await ping(hostname, port)
.timeout(_timeout, onTimeout: () => false);
if(result) {
return true;
}
if(firstTime) {
firstTime = false;
}else {
await Future.delayed(_timeout);
}
}
return false;
}

View File

@@ -7,6 +7,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:win32/win32.dart'; import 'package:win32/win32.dart';
import 'package:window_manager/window_manager.dart';
final RegExp _winBuildRegex = RegExp(r'(?<=\(Build )(.*)(?=\))'); final RegExp _winBuildRegex = RegExp(r'(?<=\(Build )(.*)(?=\))');
@@ -487,3 +488,7 @@ int _convertToHString(String string) {
free(hString); free(hString);
} }
} }
extension WindowManagerExtension on WindowManager {
Future<void> maximizeOrRestore() async => await windowManager.isMaximized() ? windowManager.restore() : windowManager.maximize();
}

View File

@@ -13,6 +13,7 @@ import 'package:reboot_launcher/src/widget/setting_tile.dart';
const double _kButtonDimensions = 30; const double _kButtonDimensions = 30;
const double _kButtonSpacing = 8; const double _kButtonSpacing = 8;
// FIXME: If the user clicks on the reset button, the text field checker won't be called
SettingTile createFileSetting({required String title, required String description, required TextEditingController controller, required void Function() onReset}) { SettingTile createFileSetting({required String title, required String description, required TextEditingController controller, required void Function() onReset}) {
final obx = RxString(controller.text); final obx = RxString(controller.text);
controller.addListener(() => obx.value = controller.text); controller.addListener(() => obx.value = controller.text);

View File

@@ -12,7 +12,6 @@ import 'package:reboot_launcher/src/controller/backend_controller.dart';
import 'package:reboot_launcher/src/controller/dll_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/messenger/abstract/dialog.dart'; import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart'; import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
import 'package:reboot_launcher/src/messenger/implementation/server.dart'; import 'package:reboot_launcher/src/messenger/implementation/server.dart';
@@ -22,6 +21,7 @@ import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/util/translations.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:version/version.dart';
class LaunchButton extends StatefulWidget { class LaunchButton extends StatefulWidget {
final bool host; final bool host;
@@ -41,12 +41,11 @@ class _LaunchButtonState extends State<LaunchButton> {
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 DllController _dllController = Get.find<DllController>();
final SettingsController _settingsController = Get.find<SettingsController>();
InfoBarEntry? _gameClientInfoBar; InfoBarEntry? _gameClientInfoBar;
InfoBarEntry? _gameServerInfoBar; InfoBarEntry? _gameServerInfoBar;
CancelableOperation? _operation; CancelableOperation? _operation;
CancelableOperation? _pingOperation; Completer? _pingOperation;
IVirtualDesktop? _virtualDesktop; IVirtualDesktop? _virtualDesktop;
@override @override
@@ -95,7 +94,7 @@ class _LaunchButtonState extends State<LaunchButton> {
log("[${host ? 'HOST' : 'GAME'}] Set started"); log("[${host ? 'HOST' : 'GAME'}] Set started");
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(version.content, injectable, host) == null) {
return; return;
} }
} }
@@ -230,7 +229,7 @@ class _LaunchButtonState extends State<LaunchButton> {
log("[${host ? 'HOST' : 'GAME'}] Created game process: ${gameProcess}"); log("[${host ? 'HOST' : 'GAME'}] Created game process: ${gameProcess}");
final instance = GameInstance( final instance = GameInstance(
versionName: version.content.toString(), version: version.content,
gamePid: gameProcess, gamePid: gameProcess,
launcherPid: launcherProcess, launcherPid: launcherProcess,
eacPid: eacProcess, eacPid: eacProcess,
@@ -243,7 +242,7 @@ class _LaunchButtonState extends State<LaunchButton> {
}else{ }else{
_gameController.instance.value = instance; _gameController.instance.value = instance;
} }
await _injectOrShowError(InjectableDll.sinum, host); await _injectOrShowError(InjectableDll.starfall, host);
log("[${host ? 'HOST' : 'GAME'}] Finished creating game instance"); log("[${host ? 'HOST' : 'GAME'}] Finished creating game instance");
return instance; return instance;
} }
@@ -251,8 +250,8 @@ class _LaunchButtonState extends State<LaunchButton> {
Future<int?> _createGameProcess(FortniteVersion version, File executable, bool host, GameServerType hostType, GameInstance? linkedHosting) async { Future<int?> _createGameProcess(FortniteVersion version, File executable, bool host, GameServerType hostType, GameInstance? linkedHosting) async {
log("[${host ? 'HOST' : 'GAME'}] Generating instance args..."); log("[${host ? 'HOST' : 'GAME'}] Generating instance args...");
final gameArgs = createRebootArgs( final gameArgs = createRebootArgs(
_gameController.username.text, host ? _hostingController.accountUsername.text : _gameController.username.text,
_gameController.password.text, host ? _hostingController.accountPassword.text :_gameController.password.text,
host, host,
hostType, hostType,
false, false,
@@ -399,7 +398,6 @@ class _LaunchButtonState extends State<LaunchButton> {
if(instance != null && !instance.launched) { if(instance != null && !instance.launched) {
instance.launched = true; instance.launched = true;
instance.tokenError = false; instance.tokenError = false;
await _injectOrShowError(InjectableDll.memory, host);
if(!host){ if(!host){
await _injectOrShowError(InjectableDll.console, host); await _injectOrShowError(InjectableDll.console, host);
_onGameClientInjected(); _onGameClientInjected();
@@ -438,11 +436,12 @@ class _LaunchButtonState extends State<LaunchButton> {
duration: null duration: null
); );
final gameServerPort = _dllController.gameServerPort.text; final gameServerPort = _dllController.gameServerPort.text;
this._pingOperation = await CancelableOperation.fromFuture(pingGameServer( final pingOperation = pingGameServerOrTimeout(
"127.0.0.1:$gameServerPort", "127.0.0.1:$gameServerPort",
timeout: const Duration(minutes: 2) const Duration(minutes: 2)
)); );
final localPingResult = (await _pingOperation?.value) ?? false; this._pingOperation = pingOperation;
final localPingResult = await pingOperation.future;
_gameServerInfoBar?.close(); _gameServerInfoBar?.close();
if (!localPingResult) { if (!localPingResult) {
showRebootInfoBar( showRebootInfoBar(
@@ -464,8 +463,8 @@ class _LaunchButtonState extends State<LaunchButton> {
} }
await _hostingController.publishServer( await _hostingController.publishServer(
_gameController.username.text, _hostingController.accountUsername.text,
_hostingController.instance.value!.versionName, _hostingController.instance.value!.version.toString(),
); );
showRebootInfoBar( showRebootInfoBar(
translations.gameServerStarted, translations.gameServerStarted,
@@ -485,18 +484,17 @@ class _LaunchButtonState extends State<LaunchButton> {
duration: null duration: null
); );
final publicIp = await Ipify.ipv4(); final publicIp = await Ipify.ipv4();
this._pingOperation = CancelableOperation.fromFuture(pingGameServer("$publicIp:$gameServerPort")); final available = await pingGameServer("$publicIp:$gameServerPort");
final externalResult = (await _pingOperation?.value) ?? false; if(available) {
if (externalResult) { _gameServerInfoBar?.close();
return true; return true;
} }
_gameServerInfoBar?.close(); final pingOperation = pingGameServerOrTimeout(
this._pingOperation = CancelableOperation.fromFuture(pingGameServer(
"$publicIp:$gameServerPort", "$publicIp:$gameServerPort",
timeout: const Duration(days: 365) const Duration(days: 365)
)); );
final future = await _pingOperation?.value ?? false; this._pingOperation = pingOperation;
_gameServerInfoBar = showRebootInfoBar( _gameServerInfoBar = showRebootInfoBar(
translations.checkGameServerFixMessage(gameServerPort), translations.checkGameServerFixMessage(gameServerPort),
action: Button( action: Button(
@@ -507,7 +505,9 @@ class _LaunchButtonState extends State<LaunchButton> {
duration: null, duration: null,
loading: true loading: true
); );
return await future; final result = await pingOperation.future;
_gameServerInfoBar?.close();
return result;
}finally { }finally {
_gameServerInfoBar?.close(); _gameServerInfoBar?.close();
} }
@@ -515,8 +515,13 @@ 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(); try {
_pingOperation?.complete(false);
}catch(_) {
// Ignore: might be running, don't bother checking
} finally {
_pingOperation = null; _pingOperation = null;
}
await _operation?.cancel(); await _operation?.cancel();
_operation = null; _operation = null;
_backendController.cancelInteractive(); _backendController.cancelInteractive();
@@ -524,9 +529,6 @@ 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;
@@ -550,11 +552,11 @@ class _LaunchButtonState extends State<LaunchButton> {
} }
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,
@@ -591,7 +593,7 @@ class _LaunchButtonState extends State<LaunchButton> {
); );
break; break;
case _StopReason.exitCode: case _StopReason.exitCode:
if(!instance.launched) { if(instance != null && !instance.launched) {
showRebootInfoBar( showRebootInfoBar(
translations.corruptedVersionError, translations.corruptedVersionError,
severity: InfoBarSeverity.error, severity: InfoBarSeverity.error,
@@ -627,7 +629,7 @@ class _LaunchButtonState extends State<LaunchButton> {
case _StopReason.tokenError: case _StopReason.tokenError:
_backendController.stop(); _backendController.stop();
showRebootInfoBar( showRebootInfoBar(
translations.tokenError(instance.injectedDlls.map((element) => element.name).join(", ")), translations.tokenError(instance == null ? translations.none : instance.injectedDlls.map((element) => element.name).join(", ")),
severity: InfoBarSeverity.error, severity: InfoBarSeverity.error,
duration: infoBarLongDuration, duration: infoBarLongDuration,
action: Button( action: Button(
@@ -663,7 +665,7 @@ class _LaunchButtonState extends State<LaunchButton> {
try { try {
final gameProcess = instance.gamePid; final gameProcess = instance.gamePid;
log("[${hosting ? 'HOST' : 'GAME'}] Injecting ${injectable.name} into process with pid $gameProcess"); log("[${hosting ? 'HOST' : 'GAME'}] Injecting ${injectable.name} into process with pid $gameProcess");
final dllPath = await _getDllFileOrStop(injectable, hosting); final dllPath = await _getDllFileOrStop(instance.version, injectable, hosting);
log("[${hosting ? 'HOST' : 'GAME'}] File to inject for ${injectable.name} at path $dllPath"); log("[${hosting ? 'HOST' : 'GAME'}] File to inject for ${injectable.name} at path $dllPath");
if(dllPath == null) { if(dllPath == null) {
log("[${hosting ? 'HOST' : 'GAME'}] The file doesn't exist"); log("[${hosting ? 'HOST' : 'GAME'}] The file doesn't exist");
@@ -690,9 +692,9 @@ class _LaunchButtonState extends State<LaunchButton> {
} }
} }
Future<File?> _getDllFileOrStop(InjectableDll injectable, bool host, [bool isRetry = false]) async { Future<File?> _getDllFileOrStop(Version version, 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) = _dllController.getInjectableData(injectable); final (file, customDll) = _dllController.getInjectableData(version, 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");
@@ -712,7 +714,7 @@ class _LaunchButtonState extends State<LaunchButton> {
log("[${host ? 'HOST' : 'GAME'}] Path does not exist, downloading critical dll again..."); log("[${host ? 'HOST' : 'GAME'}] Path does not exist, downloading critical dll again...");
await _dllController.downloadCriticalDllInteractive(file.path, force: true); 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(version, injectable, host, true);
} }
InfoBarEntry _showLaunchingGameServerWidget() => _gameServerInfoBar = showRebootInfoBar( InfoBarEntry _showLaunchingGameServerWidget() => _gameServerInfoBar = showRebootInfoBar(

View File

@@ -2,8 +2,11 @@ import 'package:fluent_ui/fluent_ui.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/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/messenger/abstract/overlay.dart'; import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
import 'package:reboot_launcher/src/messenger/implementation/profile.dart'; import 'package:reboot_launcher/src/messenger/implementation/profile.dart';
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
import 'package:reboot_launcher/src/page/pages.dart';
class ProfileWidget extends StatefulWidget { class ProfileWidget extends StatefulWidget {
final GlobalKey<OverlayTargetState> overlayKey; final GlobalKey<OverlayTargetState> overlayKey;
@@ -15,6 +18,7 @@ class ProfileWidget extends StatefulWidget {
class _ProfileWidgetState extends State<ProfileWidget> { class _ProfileWidgetState extends State<ProfileWidget> {
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final HostingController _hostingController = Get.find<HostingController>();
@override @override
Widget build(BuildContext context) => OverlayTarget( Widget build(BuildContext context) => OverlayTarget(
@@ -22,7 +26,7 @@ class _ProfileWidgetState extends State<ProfileWidget> {
child: HoverButton( child: HoverButton(
margin: const EdgeInsets.all(8.0), margin: const EdgeInsets.all(8.0),
onPressed: () async { onPressed: () async {
if(await showProfileForm(context)) { if(await showProfileForm(context, _username, _password)) {
setState(() {}); setState(() {});
} }
}, },
@@ -57,7 +61,7 @@ class _ProfileWidgetState extends State<ProfileWidget> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
_username, _usernameLabel,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w600 fontWeight: FontWeight.w600
@@ -65,7 +69,7 @@ class _ProfileWidgetState extends State<ProfileWidget> {
maxLines: 1 maxLines: 1
), ),
Text( Text(
_email, _emailLabel,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w100 fontWeight: FontWeight.w100
@@ -81,8 +85,8 @@ class _ProfileWidgetState extends State<ProfileWidget> {
), ),
); );
String get _username { String get _usernameLabel {
var username = _gameController.username.text; final username = _username.text;
if(username.isEmpty) { if(username.isEmpty) {
return kDefaultPlayerName; return kDefaultPlayerName;
} }
@@ -96,8 +100,8 @@ class _ProfileWidgetState extends State<ProfileWidget> {
return result.substring(0, 1).toUpperCase() + result.substring(1); return result.substring(0, 1).toUpperCase() + result.substring(1);
} }
String get _email { String get _emailLabel {
var username = _gameController.username.text; final username = _username.text;
if(username.isEmpty) { if(username.isEmpty) {
return "$kDefaultPlayerName@projectreboot.dev"; return "$kDefaultPlayerName@projectreboot.dev";
} }
@@ -108,4 +112,7 @@ class _ProfileWidgetState extends State<ProfileWidget> {
return "$username@projectreboot.dev".toLowerCase(); return "$username@projectreboot.dev".toLowerCase();
} }
TextEditingController get _username => pageIndex.value == RebootPageType.host.index ? _hostingController.accountUsername : _gameController.username;
TextEditingController get _password => pageIndex.value == RebootPageType.host.index ? _hostingController.accountPassword : _gameController.password;
} }

View File

@@ -1,5 +1,6 @@
import 'package:bitsdojo_window/bitsdojo_window.dart' show appWindow;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:window_manager/window_manager.dart';
import 'title_bar_icons.dart'; import 'title_bar_icons.dart';
import 'title_bar_mouse.dart'; import 'title_bar_mouse.dart';
@@ -132,7 +133,7 @@ class MinimizeWindowButton extends WindowButton {
animate: animate ?? false, animate: animate ?? false,
iconBuilder: (buttonContext) => iconBuilder: (buttonContext) =>
MinimizeIcon(color: buttonContext.iconColor), MinimizeIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.minimize()); onPressed: onPressed ?? () => windowManager.minimize());
} }
class MaximizeWindowButton extends WindowButton { class MaximizeWindowButton extends WindowButton {
@@ -148,7 +149,7 @@ class MaximizeWindowButton extends WindowButton {
iconBuilder: (buttonContext) => iconBuilder: (buttonContext) =>
MaximizeIcon(color: buttonContext.iconColor), MaximizeIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? onPressed: onPressed ??
() => appWindow.maximizeOrRestore()); () => windowManager.maximizeOrRestore());
} }
final _defaultCloseButtonColors = WindowButtonColors( final _defaultCloseButtonColors = WindowButtonColors(
@@ -169,5 +170,5 @@ class CloseWindowButton extends WindowButton {
animate: animate ?? false, animate: animate ?? false,
iconBuilder: (buttonContext) => iconBuilder: (buttonContext) =>
CloseIcon(color: buttonContext.iconColor), CloseIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.close()); onPressed: onPressed ?? () => windowManager.close());
} }

View File

@@ -1,6 +1,6 @@
name: reboot_launcher name: reboot_launcher
description: Graphical User Interface for Project Reboot description: Graphical User Interface for Project Reboot
version: "9.2.7" version: "10.0.0"
publish_to: 'none' publish_to: 'none'
@@ -28,7 +28,6 @@ dependencies:
ref: main ref: main
# Window management # Window management
bitsdojo_window: ^0.1.6
window_manager: ^0.4.2 window_manager: ^0.4.2
# Extract zip archives (for example the reboot.zip) # Extract zip archives (for example the reboot.zip)

View File

@@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h> #include <app_links/app_links_plugin_c_api.h>
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
#include <flutter_acrylic/flutter_acrylic_plugin.h> #include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <local_notifier/local_notifier_plugin.h> #include <local_notifier/local_notifier_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever/screen_retriever_plugin.h>
@@ -19,8 +18,6 @@
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar( AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi")); registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
BitsdojoWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
FlutterAcrylicPluginRegisterWithRegistrar( FlutterAcrylicPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterAcrylicPlugin")); registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
LocalNotifierPluginRegisterWithRegistrar( LocalNotifierPluginRegisterWithRegistrar(

View File

@@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links app_links
bitsdojo_window_windows
flutter_acrylic flutter_acrylic
local_notifier local_notifier
screen_retriever screen_retriever

View File

@@ -1,6 +1,3 @@
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
#include <cstdlib> #include <cstdlib>
#include <flutter/dart_project.h> #include <flutter/dart_project.h>

View File

@@ -121,7 +121,7 @@ bool Win32Window::CreateAndShow(const std::wstring &title,
HWND window = CreateWindow( HWND window = CreateWindow(
window_class, window_class,
title.c_str(), title.c_str(),
WS_OVERLAPPED | WS_BORDER | WS_THICKFRAME, WS_OVERLAPPEDWINDOW,
Scale(origin.x, scale_factor), Scale(origin.x, scale_factor),
Scale(origin.y, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.width, scale_factor),
@@ -198,6 +198,9 @@ Win32Window::MessageHandler(HWND hwnd,
SetFocus(child_content_); SetFocus(child_content_);
} }
return 0; return 0;
case WM_NCCALCSIZE:
return 0;
} }
return DefWindowProc(window_handle_, message, wparam, lparam); return DefWindowProc(window_handle_, message, wparam, lparam);