mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
ip for click to play
This commit is contained in:
358
lib/cli.dart
358
lib/cli.dart
@@ -1,102 +1,46 @@
|
|||||||
import 'dart:collection';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:args/args.dart';
|
import 'package:args/args.dart';
|
||||||
import 'package:process_run/shell.dart';
|
import 'package:reboot_launcher/src/cli/compatibility.dart';
|
||||||
|
import 'package:reboot_launcher/src/cli/config.dart';
|
||||||
|
import 'package:reboot_launcher/src/cli/game.dart';
|
||||||
|
import 'package:reboot_launcher/src/cli/reboot.dart';
|
||||||
|
import 'package:reboot_launcher/src/cli/server.dart';
|
||||||
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
||||||
import 'package:reboot_launcher/src/model/game_type.dart';
|
import 'package:reboot_launcher/src/model/game_type.dart';
|
||||||
import 'package:reboot_launcher/src/model/server_type.dart';
|
|
||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:reboot_launcher/src/util/injector.dart';
|
|
||||||
import 'package:reboot_launcher/src/util/patcher.dart';
|
import 'package:reboot_launcher/src/util/patcher.dart';
|
||||||
import 'package:reboot_launcher/src/util/reboot.dart';
|
import 'package:reboot_launcher/src/util/reboot.dart';
|
||||||
import 'package:reboot_launcher/src/util/server.dart';
|
|
||||||
import 'package:shelf_proxy/shelf_proxy.dart';
|
|
||||||
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
|
||||||
import 'dart:ffi';
|
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
import 'package:win32/win32.dart';
|
|
||||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
// Needed because binaries can't be loaded in any other way
|
|
||||||
const String _craniumDownload = "https://cdn.discordapp.com/attachments/1026121175878881290/1031230848323825675/cranium.dll";
|
|
||||||
const String _consoleDownload = "https://cdn.discordapp.com/attachments/1026121175878881290/1031230848005046373/console.dll";
|
|
||||||
|
|
||||||
Process? _gameProcess;
|
|
||||||
Process? _eacProcess;
|
|
||||||
Process? _launcherProcess;
|
|
||||||
|
|
||||||
void main(List<String> args){
|
void main(List<String> args){
|
||||||
handleCLI(args);
|
handleCLI(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> _getControllerJson(String name) async {
|
|
||||||
var folder = await _getWindowsPath(FOLDERID_Documents);
|
|
||||||
if(folder == null){
|
|
||||||
throw Exception("Missing documents folder");
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = File("$folder/$name.gs");
|
|
||||||
if(!file.existsSync()){
|
|
||||||
return HashMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonDecode(file.readAsStringSync());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> _getWindowsPath(String folderID) {
|
|
||||||
final Pointer<Pointer<Utf16>> pathPtrPtr = calloc<Pointer<Utf16>>();
|
|
||||||
final Pointer<GUID> knownFolderID = calloc<GUID>()..ref.setGUID(folderID);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final int hr = SHGetKnownFolderPath(
|
|
||||||
knownFolderID,
|
|
||||||
KF_FLAG_DEFAULT,
|
|
||||||
NULL,
|
|
||||||
pathPtrPtr,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
if (hr == E_INVALIDARG || hr == E_FAIL) {
|
|
||||||
throw WindowsException(hr);
|
|
||||||
}
|
|
||||||
return Future<String?>.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String path = pathPtrPtr.value.toDartString();
|
|
||||||
return Future<String>.value(path);
|
|
||||||
} finally {
|
|
||||||
calloc.free(pathPtrPtr);
|
|
||||||
calloc.free(knownFolderID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> handleCLI(List<String> args) async {
|
Future<void> handleCLI(List<String> args) async {
|
||||||
stdout.writeln("Reboot Launcher");
|
stdout.writeln("Reboot Launcher");
|
||||||
stdout.writeln("Wrote by Auties00");
|
stdout.writeln("Wrote by Auties00");
|
||||||
stdout.writeln("Version 3.13");
|
stdout.writeln("Version 4.4");
|
||||||
|
|
||||||
_killOld();
|
kill();
|
||||||
|
|
||||||
var gameJson = await _getControllerJson("game");
|
var gameJson = await getControllerJson("game");
|
||||||
var serverJson = await _getControllerJson("server");
|
var serverJson = await getControllerJson("server");
|
||||||
var settingsJson = await _getControllerJson("settings");
|
var settingsJson = await getControllerJson("settings");
|
||||||
var versions = _getVersions(gameJson);
|
var versions = getVersions(gameJson);
|
||||||
var parser = ArgParser()
|
var parser = ArgParser()
|
||||||
..addCommand("list")
|
..addCommand("list")
|
||||||
..addCommand("launch")
|
..addCommand("launch")
|
||||||
..addOption("version", defaultsTo: gameJson["version"])
|
..addOption("version", defaultsTo: gameJson["version"])
|
||||||
..addOption("username")
|
..addOption("username")
|
||||||
..addOption("server-type", allowed: _getServerTypes(), defaultsTo: _getDefaultServerType(serverJson))
|
..addOption("server-type", allowed: getServerTypes(), defaultsTo: getDefaultServerType(serverJson))
|
||||||
..addOption("server-host")
|
..addOption("server-host")
|
||||||
..addOption("server-port")
|
..addOption("server-port")
|
||||||
..addOption("dll", defaultsTo: settingsJson["reboot"] ?? (await loadBinary("reboot.dll", true)).path)
|
..addOption("dll", defaultsTo: settingsJson["reboot"] ?? (await loadBinary("reboot.dll", true)).path)
|
||||||
..addOption("type", allowed: _getTypes(), defaultsTo: _getDefaultType(gameJson))
|
..addOption("type", allowed: getGameTypes(), defaultsTo: getDefaultGameType(gameJson))
|
||||||
..addFlag("update", defaultsTo: settingsJson["auto_update"] ?? true, negatable: true)
|
..addFlag("update", defaultsTo: settingsJson["auto_update"] ?? true, negatable: true)
|
||||||
..addFlag("log", defaultsTo: false);
|
..addFlag("log", defaultsTo: false)
|
||||||
|
..addFlag("memory-fix", defaultsTo: false, negatable: true);
|
||||||
var result = parser.parse(args);
|
var result = parser.parse(args);
|
||||||
if (result.command?.name == "list") {
|
if (result.command?.name == "list") {
|
||||||
stdout.writeln("Versions list: ");
|
stdout.writeln("Versions list: ");
|
||||||
@@ -106,220 +50,44 @@ Future<void> handleCLI(List<String> args) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dll = result["dll"];
|
var dll = result["dll"];
|
||||||
var type = _getType(result);
|
var type = getGameType(result);
|
||||||
var username = result["username"];
|
var username = result["username"];
|
||||||
username ??= gameJson["${type == GameType.client ? "game" : "server"}_username"];
|
username ??= gameJson["${type == GameType.client ? "game" : "server"}_username"];
|
||||||
var verbose = result["log"];
|
var verbose = result["log"];
|
||||||
|
|
||||||
var dummyVersion = _createVersion(gameJson["version"], result["version"], versions);
|
var dummyVersion = _createVersion(gameJson["version"], result["version"], result["memory-fix"], versions);
|
||||||
await _updateDLLs();
|
await downloadRequiredDLLs();
|
||||||
if(result["update"]) {
|
if(result["update"]) {
|
||||||
stdout.writeln("Updating reboot dll...");
|
stdout.writeln("Updating reboot dll...");
|
||||||
await downloadRebootDll(0);
|
await downloadRebootDll(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout.writeln("Launching game(type: ${type.name})...");
|
stdout.writeln("Launching game(type: ${type.name})...");
|
||||||
await _startLauncherProcess(dummyVersion);
|
if(dummyVersion.executable == null){
|
||||||
if (result["type"] == "headless_server") {
|
throw Exception("Missing game executable at: ${dummyVersion.location.path}");
|
||||||
if(dummyVersion.executable == null){
|
|
||||||
throw Exception("Missing game executable at: ${dummyVersion.location.path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
await patch(dummyVersion.executable!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverType = _getServerType(result);
|
if (result["type"] == "headless_server") {
|
||||||
|
await patchHeadless(dummyVersion.executable!);
|
||||||
|
}else if(result["type"] == "client"){
|
||||||
|
await patchMatchmaking(dummyVersion.executable!);
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverType = getServerType(result);
|
||||||
var host = result["server-host"] ?? serverJson["${serverType.id}_host"];
|
var host = result["server-host"] ?? serverJson["${serverType.id}_host"];
|
||||||
var port = result["server-port"] ?? serverJson["${serverType.id}_port"];
|
var port = result["server-port"] ?? serverJson["${serverType.id}_port"];
|
||||||
var started = await _startServerIfNeeded(host, port, serverType);
|
var started = await startServer(host, port, serverType);
|
||||||
if(!started){
|
if(!started){
|
||||||
stderr.writeln("Cannot start server!");
|
stderr.writeln("Cannot start server!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _startGameProcess(username, type, verbose, dll, dummyVersion);
|
await startGame(username, type, verbose, dll, dummyVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _killOld() async {
|
FortniteVersion _createVersion(String? versionName, String? versionPath, bool memoryFix, List<FortniteVersion> versions) {
|
||||||
var shell = Shell(
|
|
||||||
commandVerbose: false,
|
|
||||||
commentVerbose: false,
|
|
||||||
verbose: false
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
await shell.run("taskkill /f /im FortniteLauncher.exe");
|
|
||||||
await shell.run("taskkill /f /im FortniteClient-Win64-Shipping_EAC.exe");
|
|
||||||
}catch(_){
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<String> _getTypes() => GameType.values.map((entry) => entry.id);
|
|
||||||
|
|
||||||
Iterable<String> _getServerTypes() => ServerType.values.map((entry) => entry.id);
|
|
||||||
|
|
||||||
String _getDefaultServerType(Map<String, dynamic> json) {
|
|
||||||
var type = ServerType.values.elementAt(json["type"] ?? 0);
|
|
||||||
return type.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameType _getType(ArgResults result) {
|
|
||||||
var type = GameType.of(result["type"]);
|
|
||||||
if(type == null){
|
|
||||||
throw Exception("Unknown game type: $result. Use --type only with ${_getTypes().join(", ")}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerType _getServerType(ArgResults result) {
|
|
||||||
var type = ServerType.of(result["server-type"]);
|
|
||||||
if(type == null){
|
|
||||||
throw Exception("Unknown server type: $result. Use --server-type only with ${_getServerTypes().join(", ")}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getDefaultType(Map<String, dynamic> json){
|
|
||||||
var type = GameType.values.elementAt(json["type"] ?? 0);
|
|
||||||
switch(type){
|
|
||||||
case GameType.client:
|
|
||||||
return "client";
|
|
||||||
case GameType.server:
|
|
||||||
return "server";
|
|
||||||
case GameType.headlessServer:
|
|
||||||
return "headless_server";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateDLLs() async {
|
|
||||||
stdout.writeln("Downloading necessary components...");
|
|
||||||
var consoleDll = await loadBinary("console.dll", true);
|
|
||||||
if(!consoleDll.existsSync()){
|
|
||||||
var response = await http.get(Uri.parse(_consoleDownload));
|
|
||||||
if(response.statusCode != 200){
|
|
||||||
throw Exception("Cannot download console.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
await consoleDll.writeAsBytes(response.bodyBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
var craniumDll = await loadBinary("cranium.dll", true);
|
|
||||||
if(!craniumDll.existsSync()){
|
|
||||||
var response = await http.get(Uri.parse(_craniumDownload));
|
|
||||||
if(response.statusCode != 200){
|
|
||||||
throw Exception("Cannot download cranium.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
await craniumDll.writeAsBytes(response.bodyBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FortniteVersion> _getVersions(Map<String, dynamic> gameJson) {
|
|
||||||
Iterable iterable = jsonDecode(gameJson["versions"] ?? "[]");
|
|
||||||
return iterable.map((entry) => FortniteVersion.fromJson(entry))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _startGameProcess(String? username, GameType type, bool verbose, String dll, FortniteVersion version) async {
|
|
||||||
var gamePath = version.executable?.path;
|
|
||||||
if (gamePath == null) {
|
|
||||||
throw Exception("${version.location
|
|
||||||
.path} no longer contains a Fortnite executable. Did you delete it?");
|
|
||||||
}
|
|
||||||
|
|
||||||
var hosting = type != GameType.client;
|
|
||||||
if (username == null) {
|
|
||||||
username = "Reboot${hosting ? 'Host' : 'Player'}";
|
|
||||||
stdout.writeln("No username was specified, using $username by default. Use --username to specify one");
|
|
||||||
}
|
|
||||||
|
|
||||||
_gameProcess = await Process.start(gamePath, createRebootArgs(username, type == GameType.headlessServer))
|
|
||||||
..exitCode.then((_) => _onClose())
|
|
||||||
..outLines.forEach((line) => _onGameOutput(line, dll, hosting, verbose));
|
|
||||||
await _injectOrShowError("cranium.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onClose() {
|
|
||||||
_kill();
|
|
||||||
stdout.writeln("The game was closed");
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _startLauncherProcess(FortniteVersion dummyVersion) async {
|
|
||||||
if (dummyVersion.launcher == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_launcherProcess = await Process.start(dummyVersion.launcher!.path, []);
|
|
||||||
Win32Process(_launcherProcess!.pid).suspend();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _startServerIfNeeded(String? host, String? port, ServerType type) async {
|
|
||||||
stdout.writeln("Starting lawin server...");
|
|
||||||
switch(type){
|
|
||||||
case ServerType.local:
|
|
||||||
var result = await ping(host ?? "127.0.0.1", port ?? "3551");
|
|
||||||
if(result == null){
|
|
||||||
throw Exception("Local lawin server is not running");
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout.writeln("Detected local lawin server");
|
|
||||||
return true;
|
|
||||||
case ServerType.embedded:
|
|
||||||
stdout.writeln("Starting an embedded server...");
|
|
||||||
return await _changeEmbeddedServerState();
|
|
||||||
case ServerType.remote:
|
|
||||||
if(host == null){
|
|
||||||
throw Exception("Missing host for remote server");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(port == null){
|
|
||||||
throw Exception("Missing host for remote server");
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout.writeln("Starting a reverse proxy to $host:$port");
|
|
||||||
return await _changeReverseProxyState(host, port) != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _changeEmbeddedServerState() async {
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<HttpServer?> _changeReverseProxyState(String host, String port) async {
|
|
||||||
host = host.trim();
|
|
||||||
if(host.isEmpty){
|
|
||||||
throw Exception("Missing host name");
|
|
||||||
}
|
|
||||||
|
|
||||||
port = port.trim();
|
|
||||||
if(port.isEmpty){
|
|
||||||
throw Exception("Missing port");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(int.tryParse(port) == null){
|
|
||||||
throw Exception("Invalid port, use only numbers");
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
var uri = await ping(host, port);
|
|
||||||
if(uri == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await shelf_io.serve(proxyHandler(uri), "127.0.0.1", 3551);
|
|
||||||
}catch(error){
|
|
||||||
throw Exception("Cannot start reverse proxy");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FortniteVersion _createVersion(String? versionName, String? versionPath, List<FortniteVersion> versions) {
|
|
||||||
if (versionPath != null) {
|
if (versionPath != null) {
|
||||||
return FortniteVersion(name: "dummy", location: Directory(versionPath));
|
return FortniteVersion(name: "dummy", location: Directory(versionPath), memoryFix: memoryFix);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(versionName != null){
|
if(versionName != null){
|
||||||
@@ -332,68 +100,4 @@ FortniteVersion _createVersion(String? versionName, String? versionPath, List<Fo
|
|||||||
|
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"Specify a version using --version or open the launcher GUI and select it manually");
|
"Specify a version using --version or open the launcher GUI and select it manually");
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void _onGameOutput(String line, String rebootDll, bool host, bool verbose) {
|
|
||||||
if(verbose) {
|
|
||||||
stdout.writeln(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.contains("FOnlineSubsystemGoogleCommon::Shutdown()")) {
|
|
||||||
_onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(line.contains("port 3551 failed: Connection refused")){
|
|
||||||
stderr.writeln("Connection refused from lawin server");
|
|
||||||
_onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(line.contains("HTTP 400 response from ") || line.contains("Unable to login to Fortnite servers")){
|
|
||||||
stderr.writeln("Connection refused from lawin server");
|
|
||||||
_onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(line.contains("Network failure when attempting to check platform restrictions") || line.contains("UOnlineAccountCommon::ForceLogout")){
|
|
||||||
stderr.writeln("Expired token, please reopen the game");
|
|
||||||
_kill();
|
|
||||||
_onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.contains("Game Engine Initialized") && !host) {
|
|
||||||
_injectOrShowError("console.dll");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(line.contains("Region") && host){
|
|
||||||
_injectOrShowError(rebootDll, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _kill() {
|
|
||||||
_gameProcess?.kill(ProcessSignal.sigabrt);
|
|
||||||
_eacProcess?.kill(ProcessSignal.sigabrt);
|
|
||||||
_launcherProcess?.kill(ProcessSignal.sigabrt);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _injectOrShowError(String binary, [bool locate = true]) async {
|
|
||||||
if (_gameProcess == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
stdout.writeln("Injecting $binary...");
|
|
||||||
var dll = locate ? await loadBinary(binary, true) : File(binary);
|
|
||||||
if(!dll.existsSync()){
|
|
||||||
throw Exception("Cannot inject $dll: missing file");
|
|
||||||
}
|
|
||||||
|
|
||||||
await injectDll(_gameProcess!.pid, dll.path);
|
|
||||||
} catch (exception) {
|
|
||||||
throw Exception("Cannot inject binary: $binary");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
@@ -12,6 +13,7 @@ import 'package:reboot_launcher/src/controller/game_controller.dart';
|
|||||||
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
import 'package:reboot_launcher/src/page/home_page.dart';
|
import 'package:reboot_launcher/src/page/home_page.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/error.dart';
|
||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:system_theme/system_theme.dart';
|
import 'package:system_theme/system_theme.dart';
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ void main(List<String> args) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
await SystemTheme.accentColor.load();
|
await SystemTheme.accentColor.load();
|
||||||
await GetStorage.init("game");
|
await GetStorage.init("game");
|
||||||
await GetStorage.init("server");
|
await GetStorage.init("server");
|
||||||
@@ -45,7 +48,10 @@ void main(List<String> args) async {
|
|||||||
appWindow.show();
|
appWindow.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
runApp(const RebootApplication());
|
runZonedGuarded(() =>
|
||||||
|
runApp(const RebootApplication()),
|
||||||
|
(error, stack) => onError(error, stack, false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RebootApplication extends StatefulWidget {
|
class RebootApplication extends StatefulWidget {
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import 'dart:io';
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
|
import 'package:reboot_launcher/src/dialog/server_dialogs.dart';
|
||||||
import 'package:reboot_launcher/src/util/server.dart';
|
import 'package:reboot_launcher/src/util/server.dart';
|
||||||
|
|
||||||
|
import '../dialog/snackbar.dart';
|
||||||
import '../model/server_type.dart';
|
import '../model/server_type.dart';
|
||||||
|
|
||||||
class ServerController extends GetxController {
|
class ServerController extends GetxController {
|
||||||
@@ -17,6 +19,7 @@ class ServerController extends GetxController {
|
|||||||
late final Rx<ServerType> type;
|
late final Rx<ServerType> type;
|
||||||
late final RxBool warning;
|
late final RxBool warning;
|
||||||
late RxBool started;
|
late RxBool started;
|
||||||
|
late int embeddedServerCounter;
|
||||||
Process? embeddedServer;
|
Process? embeddedServer;
|
||||||
HttpServer? reverseProxy;
|
HttpServer? reverseProxy;
|
||||||
|
|
||||||
@@ -36,7 +39,7 @@ class ServerController extends GetxController {
|
|||||||
if(value == ServerType.remote){
|
if(value == ServerType.remote){
|
||||||
reverseProxy?.close(force: true);
|
reverseProxy?.close(force: true);
|
||||||
reverseProxy = null;
|
reverseProxy = null;
|
||||||
started(false);
|
started.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +56,8 @@ class ServerController extends GetxController {
|
|||||||
warning.listen((value) => _storage.write("lawin_value", value));
|
warning.listen((value) => _storage.write("lawin_value", value));
|
||||||
|
|
||||||
started = RxBool(false);
|
started = RxBool(false);
|
||||||
|
|
||||||
|
embeddedServerCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _readHost() {
|
String _readHost() {
|
||||||
@@ -65,8 +70,9 @@ class ServerController extends GetxController {
|
|||||||
return _storage.read("${type.value.id}_port") ?? _serverPort;
|
return _storage.read("${type.value.id}_port") ?? _serverPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ServerResult> start() async {
|
Future<ServerResult> start(bool needsFreePort) async {
|
||||||
var result = await checkServerPreconditions(host.text, port.text, type.value);
|
var lastCounter = ++embeddedServerCounter;
|
||||||
|
var result = await checkServerPreconditions(host.text, port.text, type.value, needsFreePort);
|
||||||
if(result.type != ServerResultType.canStart){
|
if(result.type != ServerResultType.canStart){
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -74,7 +80,16 @@ class ServerController extends GetxController {
|
|||||||
try{
|
try{
|
||||||
switch(type()){
|
switch(type()){
|
||||||
case ServerType.embedded:
|
case ServerType.embedded:
|
||||||
embeddedServer = await startEmbeddedServer();
|
await _startEmbeddedServer();
|
||||||
|
embeddedServer?.exitCode.then((value) async {
|
||||||
|
if (!started() || lastCounter != embeddedServerCounter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
started.value = false;
|
||||||
|
await freeLawinPort();
|
||||||
|
showUnexpectedError();
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case ServerType.remote:
|
case ServerType.remote:
|
||||||
var uriResult = await result.uri!;
|
var uriResult = await result.uri!;
|
||||||
@@ -97,21 +112,34 @@ class ServerController extends GetxController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var myself = await pingSelf();
|
var myself = await pingSelf(port.text);
|
||||||
if(myself == null){
|
if(myself == null){
|
||||||
return ServerResult(
|
return ServerResult(
|
||||||
type: ServerResultType.cannotPingServer
|
type: ServerResultType.cannotPingServer,
|
||||||
|
pid: embeddedServer?.pid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
started(true);
|
|
||||||
return ServerResult(
|
return ServerResult(
|
||||||
type: ServerResultType.started
|
type: ServerResultType.started
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _startEmbeddedServer() async {
|
||||||
|
var result = await startEmbeddedServer();
|
||||||
|
if(result != null){
|
||||||
|
embeddedServer = result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showMessage("The server is corrupted, trying to fix it");
|
||||||
|
await serverLocation.parent.delete(recursive: true);
|
||||||
|
await downloadServerInteractive(true);
|
||||||
|
await _startEmbeddedServer();
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> stop() async {
|
Future<bool> stop() async {
|
||||||
started(false);
|
started.value = false;
|
||||||
try{
|
try{
|
||||||
switch(type()){
|
switch(type()){
|
||||||
case ServerType.embedded:
|
case ServerType.embedded:
|
||||||
@@ -125,7 +153,7 @@ class ServerController extends GetxController {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}catch(_){
|
}catch(_){
|
||||||
started(true);
|
started.value = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
import 'package:ini/ini.dart';
|
||||||
import 'package:reboot_launcher/src/model/game_type.dart';
|
|
||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:system_theme/system_theme.dart';
|
import 'package:reboot_launcher/src/util/server.dart';
|
||||||
|
|
||||||
class SettingsController extends GetxController {
|
class SettingsController extends GetxController {
|
||||||
late final GetStorage _storage;
|
late final GetStorage _storage;
|
||||||
@@ -15,7 +12,7 @@ class SettingsController extends GetxController {
|
|||||||
late final TextEditingController rebootDll;
|
late final TextEditingController rebootDll;
|
||||||
late final TextEditingController consoleDll;
|
late final TextEditingController consoleDll;
|
||||||
late final TextEditingController craniumDll;
|
late final TextEditingController craniumDll;
|
||||||
late final RxBool autoUpdate;
|
late final TextEditingController matchmakingIp;
|
||||||
|
|
||||||
SettingsController() {
|
SettingsController() {
|
||||||
_storage = GetStorage("settings");
|
_storage = GetStorage("settings");
|
||||||
@@ -23,9 +20,23 @@ class SettingsController extends GetxController {
|
|||||||
rebootDll = _createController("reboot", "reboot.dll");
|
rebootDll = _createController("reboot", "reboot.dll");
|
||||||
consoleDll = _createController("console", "console.dll");
|
consoleDll = _createController("console", "console.dll");
|
||||||
craniumDll = _createController("cranium", "cranium.dll");
|
craniumDll = _createController("cranium", "cranium.dll");
|
||||||
autoUpdate = RxBool(_storage.read("auto_update") ?? true);
|
matchmakingIp = TextEditingController(text: _storage.read("ip") ?? "127.0.0.1");
|
||||||
|
matchmakingIp.addListener(() async {
|
||||||
|
var text = matchmakingIp.text;
|
||||||
|
_storage.write("ip", text);
|
||||||
|
if(await serverConfig.exists()){
|
||||||
|
var config = Config.fromString(await serverConfig.readAsString());
|
||||||
|
if(text.contains(":")){
|
||||||
|
config.set("GameServer", "ip", text.substring(0, text.indexOf(":")));
|
||||||
|
config.set("GameServer", "port", text.substring(text.indexOf(":") + 1));
|
||||||
|
}else {
|
||||||
|
config.set("GameServer", "ip", text);
|
||||||
|
config.set("GameServer", "port", "7777");
|
||||||
|
}
|
||||||
|
|
||||||
autoUpdate.listen((value) => _storage.write("auto_update", value));
|
serverConfig.writeAsString(config.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEditingController _createController(String key, String name) {
|
TextEditingController _createController(String key, String name) {
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import 'package:reboot_launcher/src/dialog/dialog_button.dart';
|
|||||||
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
||||||
|
|
||||||
import '../util/checks.dart';
|
import '../util/checks.dart';
|
||||||
import '../widget/os/file_selector.dart';
|
import '../widget/shared/file_selector.dart';
|
||||||
|
import '../widget/shared/smart_check_box.dart';
|
||||||
|
|
||||||
class AddLocalVersion extends StatelessWidget {
|
class AddLocalVersion extends StatelessWidget {
|
||||||
final GameController _gameController = Get.find<GameController>();
|
final GameController _gameController = Get.find<GameController>();
|
||||||
final TextEditingController _nameController = TextEditingController();
|
final TextEditingController _nameController = TextEditingController();
|
||||||
final TextEditingController _gamePathController = TextEditingController();
|
final TextEditingController _gamePathController = TextEditingController();
|
||||||
|
final CheckboxController _injectMemoryFixController = CheckboxController();
|
||||||
|
|
||||||
AddLocalVersion({Key? key})
|
AddLocalVersion({Key? key})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
@@ -47,6 +49,15 @@ class AddLocalVersion extends StatelessWidget {
|
|||||||
folder: true
|
folder: true
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(
|
||||||
|
height: 16.0
|
||||||
|
),
|
||||||
|
|
||||||
|
SmartCheckBox(
|
||||||
|
controller: _injectMemoryFixController,
|
||||||
|
content: const Text("Inject memory leak fix")
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 8.0),
|
const SizedBox(height: 8.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -61,7 +72,9 @@ class AddLocalVersion extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
_gameController.addVersion(FortniteVersion(
|
_gameController.addVersion(FortniteVersion(
|
||||||
name: _nameController.text,
|
name: _nameController.text,
|
||||||
location: Directory(_gamePathController.text)));
|
location: Directory(_gamePathController.text),
|
||||||
|
memoryFix: _injectMemoryFixController.value
|
||||||
|
));
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import 'package:reboot_launcher/src/widget/home/version_name_input.dart';
|
|||||||
|
|
||||||
import '../util/checks.dart';
|
import '../util/checks.dart';
|
||||||
import '../widget/home/build_selector.dart';
|
import '../widget/home/build_selector.dart';
|
||||||
import '../widget/os/file_selector.dart';
|
import '../widget/shared/file_selector.dart';
|
||||||
|
import '../widget/shared/smart_check_box.dart';
|
||||||
import 'dialog.dart';
|
import 'dialog.dart';
|
||||||
|
|
||||||
class AddServerVersion extends StatefulWidget {
|
class AddServerVersion extends StatefulWidget {
|
||||||
@@ -30,6 +31,7 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
|||||||
final BuildController _buildController = Get.find<BuildController>();
|
final BuildController _buildController = Get.find<BuildController>();
|
||||||
final TextEditingController _nameController = TextEditingController();
|
final TextEditingController _nameController = TextEditingController();
|
||||||
final TextEditingController _pathController = TextEditingController();
|
final TextEditingController _pathController = TextEditingController();
|
||||||
|
final CheckboxController _injectMemoryFixController = CheckboxController();
|
||||||
late Future _future;
|
late Future _future;
|
||||||
DownloadStatus _status = DownloadStatus.none;
|
DownloadStatus _status = DownloadStatus.none;
|
||||||
String _timeLeft = "00:00:00";
|
String _timeLeft = "00:00:00";
|
||||||
@@ -97,7 +99,12 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
case DownloadStatus.error:
|
case DownloadStatus.error:
|
||||||
return [DialogButton(type: ButtonType.only)];
|
return [
|
||||||
|
DialogButton(
|
||||||
|
type: ButtonType.only,
|
||||||
|
onTap: () => Navigator.of(context).pop(),
|
||||||
|
)
|
||||||
|
];
|
||||||
default:
|
default:
|
||||||
return [
|
return [
|
||||||
DialogButton(
|
DialogButton(
|
||||||
@@ -154,7 +161,9 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
|||||||
_status = DownloadStatus.done;
|
_status = DownloadStatus.done;
|
||||||
_gameController.addVersion(FortniteVersion(
|
_gameController.addVersion(FortniteVersion(
|
||||||
name: _nameController.text,
|
name: _nameController.text,
|
||||||
location: Directory(_pathController.text)));
|
location: Directory(_pathController.text),
|
||||||
|
memoryFix: _injectMemoryFixController.value
|
||||||
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +239,7 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
|||||||
padding: const EdgeInsets.only(bottom: 16),
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Text("An error was occurred while downloading:$_error",
|
child: Text("An error occurred while downloading:$_error",
|
||||||
textAlign: TextAlign.center)),
|
textAlign: TextAlign.center)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -269,24 +278,27 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
if(_manifestDownloadProcess != null)
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"${_downloadProgress.round()}%",
|
"${_downloadProgress.round()}%",
|
||||||
style: FluentTheme.maybeOf(context)?.typography.body,
|
style: FluentTheme.maybeOf(context)?.typography.body,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if(_manifestDownloadProcess != null)
|
||||||
Text(
|
Text(
|
||||||
"Time left: $_timeLeft",
|
"Time left: $_timeLeft",
|
||||||
style: FluentTheme.maybeOf(context)?.typography.body,
|
style: FluentTheme.maybeOf(context)?.typography.body,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if(_manifestDownloadProcess != null)
|
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ProgressBar(value: _downloadProgress.toDouble())),
|
child: ProgressBar(value: _downloadProgress.toDouble())),
|
||||||
@@ -313,7 +325,13 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
|||||||
windowTitle: "Select download destination",
|
windowTitle: "Select download destination",
|
||||||
controller: _pathController,
|
controller: _pathController,
|
||||||
validator: checkDownloadDestination,
|
validator: checkDownloadDestination,
|
||||||
folder: true),
|
folder: true
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
SmartCheckBox(
|
||||||
|
controller: _injectMemoryFixController,
|
||||||
|
content: const Text("Inject memory leak fix")
|
||||||
|
),
|
||||||
const SizedBox(height: 8.0),
|
const SizedBox(height: 8.0),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class FormDialog extends AbstractDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DialogButton _createFormButton(DialogButton entry, BuildContext context) {
|
DialogButton _createFormButton(DialogButton entry, BuildContext context) {
|
||||||
if (entry.type == ButtonType.secondary) {
|
if (entry.type != ButtonType.primary) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +102,9 @@ class InfoDialog extends AbstractDialog {
|
|||||||
|
|
||||||
class ProgressDialog extends AbstractDialog {
|
class ProgressDialog extends AbstractDialog {
|
||||||
final String text;
|
final String text;
|
||||||
|
final Function()? onStop;
|
||||||
|
|
||||||
const ProgressDialog({required this.text, super.key});
|
const ProgressDialog({required this.text, this.onStop, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -118,8 +119,9 @@ class ProgressDialog extends AbstractDialog {
|
|||||||
),
|
),
|
||||||
buttons: [
|
buttons: [
|
||||||
DialogButton(
|
DialogButton(
|
||||||
text: "Close",
|
text: "Close",
|
||||||
type: ButtonType.only
|
type: ButtonType.only,
|
||||||
|
onTap: onStop
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ class _DialogButtonState extends State<DialogButton> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDefaultSecondaryActionTap() => Navigator.of(context).pop(null);
|
void _onDefaultSecondaryActionTap() {
|
||||||
|
Navigator.of(context).pop(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ButtonType {
|
enum ButtonType {
|
||||||
|
|||||||
@@ -14,49 +14,53 @@ import '../util/server.dart';
|
|||||||
extension ServerControllerDialog on ServerController {
|
extension ServerControllerDialog on ServerController {
|
||||||
static Semaphore semaphore = Semaphore();
|
static Semaphore semaphore = Semaphore();
|
||||||
|
|
||||||
Future<bool> changeStateInteractive(bool ignorePrompts, [bool isRetry = false]) async {
|
Future<bool> changeStateInteractive(bool onlyIfNeeded, [bool isRetry = false]) async {
|
||||||
try {
|
try{
|
||||||
semaphore.acquire();
|
semaphore.acquire();
|
||||||
if (type() == ServerType.local) {
|
if (type() == ServerType.local) {
|
||||||
return _checkLocalServerInteractive(ignorePrompts);
|
return _checkLocalServerInteractive(onlyIfNeeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldStarted = started();
|
var oldStarted = started();
|
||||||
started(!started());
|
if(oldStarted && onlyIfNeeded){
|
||||||
if (oldStarted) {
|
return true;
|
||||||
var result = await stop();
|
|
||||||
if (!result) {
|
|
||||||
started(true);
|
|
||||||
_showCannotStopError();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await start();
|
started.value = !started.value;
|
||||||
var handled = await _handleResultType(result, ignorePrompts, isRetry);
|
return await _doStateChange(oldStarted, onlyIfNeeded, isRetry);
|
||||||
if (!handled) {
|
|
||||||
started(false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
embeddedServer?.exitCode.then((value) {
|
|
||||||
if (!started()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_showUnexpectedError();
|
|
||||||
started(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return handled;
|
|
||||||
}finally{
|
}finally{
|
||||||
semaphore.release();
|
semaphore.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _handleResultType(ServerResult result, bool ignorePrompts, bool isRetry) async {
|
Future<bool> _doStateChange(bool oldStarted, bool onlyIfNeeded, bool isRetry) async {
|
||||||
|
if (oldStarted) {
|
||||||
|
var result = await stop();
|
||||||
|
if (!result) {
|
||||||
|
started.value = true;
|
||||||
|
_showCannotStopError();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await start(!onlyIfNeeded);
|
||||||
|
if(result.type == ServerResultType.ignoreStart) {
|
||||||
|
started.value = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handled = await _handleResultType(oldStarted, onlyIfNeeded, isRetry, result);
|
||||||
|
if (!handled) {
|
||||||
|
started.value = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _handleResultType(bool oldStarted, bool onlyIfNeeded, bool isRetry, ServerResult result) async {
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case ServerResultType.missingHostError:
|
case ServerResultType.missingHostError:
|
||||||
_showMissingHostError();
|
_showMissingHostError();
|
||||||
@@ -68,6 +72,10 @@ extension ServerControllerDialog on ServerController {
|
|||||||
_showIllegalPortError();
|
_showIllegalPortError();
|
||||||
return false;
|
return false;
|
||||||
case ServerResultType.cannotPingServer:
|
case ServerResultType.cannotPingServer:
|
||||||
|
if(!started() || result.pid != embeddedServer?.pid){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_showPingErrorDialog();
|
_showPingErrorDialog();
|
||||||
return false;
|
return false;
|
||||||
case ServerResultType.portTakenError:
|
case ServerResultType.portTakenError:
|
||||||
@@ -82,18 +90,18 @@ extension ServerControllerDialog on ServerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await freeLawinPort();
|
await freeLawinPort();
|
||||||
return changeStateInteractive(ignorePrompts, true);
|
return _doStateChange(oldStarted, onlyIfNeeded, true);
|
||||||
case ServerResultType.serverDownloadRequiredError:
|
case ServerResultType.serverDownloadRequiredError:
|
||||||
if (isRetry) {
|
if (isRetry) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await _downloadServerInteractive();
|
var result = await downloadServerInteractive(false);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return changeStateInteractive(ignorePrompts, true);
|
return _doStateChange(oldStarted, onlyIfNeeded, true);
|
||||||
case ServerResultType.unknownError:
|
case ServerResultType.unknownError:
|
||||||
showDialog(
|
showDialog(
|
||||||
context: appKey.currentContext!,
|
context: appKey.currentContext!,
|
||||||
@@ -106,6 +114,7 @@ extension ServerControllerDialog on ServerController {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
|
case ServerResultType.ignoreStart:
|
||||||
case ServerResultType.started:
|
case ServerResultType.started:
|
||||||
return true;
|
return true;
|
||||||
case ServerResultType.canStart:
|
case ServerResultType.canStart:
|
||||||
@@ -116,7 +125,7 @@ extension ServerControllerDialog on ServerController {
|
|||||||
|
|
||||||
Future<bool> _checkLocalServerInteractive(bool ignorePrompts) async {
|
Future<bool> _checkLocalServerInteractive(bool ignorePrompts) async {
|
||||||
try {
|
try {
|
||||||
var future = pingSelf();
|
var future = pingSelf(port.text);
|
||||||
if(!ignorePrompts) {
|
if(!ignorePrompts) {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: appKey.currentContext!,
|
context: appKey.currentContext!,
|
||||||
@@ -138,22 +147,6 @@ extension ServerControllerDialog on ServerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _downloadServerInteractive() async {
|
|
||||||
var download = compute(downloadServer, true);
|
|
||||||
return await showDialog<bool>(
|
|
||||||
context: appKey.currentContext!,
|
|
||||||
builder: (context) =>
|
|
||||||
FutureBuilderDialog(
|
|
||||||
future: download,
|
|
||||||
loadingMessage: "Downloading server...",
|
|
||||||
loadedBody: FutureBuilderDialog.ofMessage(
|
|
||||||
"The server was downloaded successfully"),
|
|
||||||
errorMessageBuilder: (
|
|
||||||
message) => "Cannot download server: $message"
|
|
||||||
)
|
|
||||||
) ?? download.isCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showPortTakenError() async {
|
Future<void> _showPortTakenError() async {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: appKey.currentContext!,
|
context: appKey.currentContext!,
|
||||||
@@ -186,6 +179,10 @@ extension ServerControllerDialog on ServerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showPingErrorDialog() {
|
void _showPingErrorDialog() {
|
||||||
|
if(!started.value){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: appKey.currentContext!,
|
context: appKey.currentContext!,
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
@@ -196,6 +193,10 @@ extension ServerControllerDialog on ServerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showCannotStopError() {
|
void _showCannotStopError() {
|
||||||
|
if(!started.value){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: appKey.currentContext!,
|
context: appKey.currentContext!,
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
@@ -205,12 +206,12 @@ extension ServerControllerDialog on ServerController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showUnexpectedError() {
|
void showUnexpectedError() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: appKey.currentContext!,
|
context: appKey.currentContext!,
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
const InfoDialog(
|
const InfoDialog(
|
||||||
text: "The lawin terminated died unexpectedly"
|
text: "The lawin server died unexpectedly"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -226,4 +227,21 @@ extension ServerControllerDialog on ServerController {
|
|||||||
void _showMissingHostError() {
|
void _showMissingHostError() {
|
||||||
showMessage("Missing the host name for lawin server");
|
showMessage("Missing the host name for lawin server");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> downloadServerInteractive(bool closeAutomatically) async {
|
||||||
|
var download = compute(downloadServer, true);
|
||||||
|
return await showDialog<bool>(
|
||||||
|
context: appKey.currentContext!,
|
||||||
|
builder: (context) =>
|
||||||
|
FutureBuilderDialog(
|
||||||
|
future: download,
|
||||||
|
loadingMessage: "Downloading server...",
|
||||||
|
loadedBody: FutureBuilderDialog.ofMessage(
|
||||||
|
"The server was downloaded successfully"),
|
||||||
|
errorMessageBuilder: (
|
||||||
|
message) => "Cannot download server: $message",
|
||||||
|
closeAutomatically: closeAutomatically
|
||||||
|
)
|
||||||
|
) ?? download.isCompleted();
|
||||||
}
|
}
|
||||||
@@ -5,12 +5,14 @@ import 'package:path/path.dart' as path;
|
|||||||
class FortniteVersion {
|
class FortniteVersion {
|
||||||
String name;
|
String name;
|
||||||
Directory location;
|
Directory location;
|
||||||
|
bool memoryFix;
|
||||||
|
|
||||||
FortniteVersion.fromJson(json)
|
FortniteVersion.fromJson(json)
|
||||||
: name = json["name"],
|
: name = json["name"],
|
||||||
location = Directory(json["location"]);
|
location = Directory(json["location"]),
|
||||||
|
memoryFix = json["memory_fix"] ?? false;
|
||||||
|
|
||||||
FortniteVersion({required this.name, required this.location});
|
FortniteVersion({required this.name, required this.location, required this.memoryFix});
|
||||||
|
|
||||||
static File? findExecutable(Directory directory, String name) {
|
static File? findExecutable(Directory directory, String name) {
|
||||||
try{
|
try{
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
const String _discordLink = "https://discord.gg/NJU4QjxSMF";
|
|
||||||
|
|
||||||
class InfoPage extends StatelessWidget {
|
class InfoPage extends StatelessWidget {
|
||||||
const InfoPage({Key? key}) : super(key: key);
|
const InfoPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@@ -42,8 +43,8 @@ class InfoPage extends StatelessWidget {
|
|||||||
|
|
||||||
Button _createDiscordButton() {
|
Button _createDiscordButton() {
|
||||||
return Button(
|
return Button(
|
||||||
child: const Text("Join the discord"),
|
child: const Text("Open file directory"),
|
||||||
onPressed: () => launchUrl(Uri.parse(_discordLink)));
|
onPressed: () => launchUrl(Directory(safeBinariesDirectory).uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
CircleAvatar _createAutiesAvatar() {
|
CircleAvatar _createAutiesAvatar() {
|
||||||
@@ -55,7 +56,7 @@ class InfoPage extends StatelessWidget {
|
|||||||
Align _createVersionInfo() {
|
Align _createVersionInfo() {
|
||||||
return const Align(
|
return const Align(
|
||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
child: Text("Version 4.0${kDebugMode ? '-DEBUG' : ''}")
|
child: Text("Version 4.4${kDebugMode ? '-DEBUG' : ''}")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,9 @@ import 'package:reboot_launcher/src/widget/home/game_type_selector.dart';
|
|||||||
import 'package:reboot_launcher/src/widget/home/launch_button.dart';
|
import 'package:reboot_launcher/src/widget/home/launch_button.dart';
|
||||||
import 'package:reboot_launcher/src/widget/home/username_box.dart';
|
import 'package:reboot_launcher/src/widget/home/username_box.dart';
|
||||||
import 'package:reboot_launcher/src/widget/home/version_selector.dart';
|
import 'package:reboot_launcher/src/widget/home/version_selector.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
|
|
||||||
import '../controller/settings_controller.dart';
|
import '../controller/settings_controller.dart';
|
||||||
import '../util/reboot.dart';
|
import '../util/reboot.dart';
|
||||||
import '../widget/shared/warning_info.dart';
|
|
||||||
|
|
||||||
class LauncherPage extends StatefulWidget {
|
class LauncherPage extends StatefulWidget {
|
||||||
const LauncherPage(
|
const LauncherPage(
|
||||||
@@ -33,10 +31,9 @@ class _LauncherPageState extends State<LauncherPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if(_gameController.updater == null && _settingsController.autoUpdate.value){
|
if(_gameController.updater == null){
|
||||||
_gameController.updater = compute(downloadRebootDll, _updateTime)
|
_gameController.updater = compute(downloadRebootDll, _updateTime)
|
||||||
..then((value) => _updateTime = value)
|
..then((value) => _updateTime = value);
|
||||||
..onError(_saveError);
|
|
||||||
_buildController.cancelledDownload
|
_buildController.cancelledDownload
|
||||||
.listen((value) => value ? _onCancelWarning() : {});
|
.listen((value) => value ? _onCancelWarning() : {});
|
||||||
}
|
}
|
||||||
@@ -54,13 +51,6 @@ class _LauncherPageState extends State<LauncherPage> {
|
|||||||
storage.write("last_update", updateTime);
|
storage.write("last_update", updateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveError(Object? error, StackTrace stackTrace) async {
|
|
||||||
var errorFile = await loadBinary("error.txt", true);
|
|
||||||
errorFile.writeAsString(
|
|
||||||
"Error: $error\nStacktrace: $stackTrace", mode: FileMode.write);
|
|
||||||
throw Exception("Cannot update reboot.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onCancelWarning() {
|
void _onCancelWarning() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if(!mounted) {
|
if(!mounted) {
|
||||||
@@ -78,7 +68,7 @@ class _LauncherPageState extends State<LauncherPage> {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _gameController.updater,
|
future: _gameController.updater ?? Future.value(true),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData && !snapshot.hasError) {
|
if (!snapshot.hasData && !snapshot.hasError) {
|
||||||
return Row(
|
return Row(
|
||||||
@@ -114,11 +104,12 @@ class _LauncherPageState extends State<LauncherPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _createUpdateError(AsyncSnapshot<Object?> snapshot) {
|
Widget _createUpdateError(AsyncSnapshot<Object?> snapshot) {
|
||||||
return WarningInfo(
|
return const SizedBox(
|
||||||
text: "Cannot update Reboot DLL",
|
width: double.infinity,
|
||||||
icon: FluentIcons.info,
|
child: InfoBar(
|
||||||
severity: InfoBarSeverity.warning,
|
title: Text("Cannot update dll"),
|
||||||
onPressed: () => loadBinary("error.txt", true).then((file) => launchUrl(file.uri))
|
severity: InfoBarSeverity.warning
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:reboot_launcher/src/widget/server/host_input.dart';
|
|||||||
import 'package:reboot_launcher/src/widget/server/server_type_selector.dart';
|
import 'package:reboot_launcher/src/widget/server/server_type_selector.dart';
|
||||||
import 'package:reboot_launcher/src/widget/server/port_input.dart';
|
import 'package:reboot_launcher/src/widget/server/port_input.dart';
|
||||||
import 'package:reboot_launcher/src/widget/server/server_button.dart';
|
import 'package:reboot_launcher/src/widget/server/server_button.dart';
|
||||||
import 'package:reboot_launcher/src/widget/shared/warning_info.dart';
|
|
||||||
|
|
||||||
class ServerPage extends StatelessWidget {
|
class ServerPage extends StatelessWidget {
|
||||||
final ServerController _serverController = Get.find<ServerController>();
|
final ServerController _serverController = Get.find<ServerController>();
|
||||||
@@ -21,10 +20,13 @@ class ServerPage extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if(_serverController.warning.value)
|
if(_serverController.warning.value)
|
||||||
WarningInfo(
|
SizedBox(
|
||||||
text: "The lawin server handles authentication and parties, not game hosting",
|
width: double.infinity,
|
||||||
icon: FluentIcons.accept,
|
child: InfoBar(
|
||||||
onPressed: () => _serverController.warning.value = false
|
title: const Text("The lawin server handles authentication and parties, not game hosting"),
|
||||||
|
severity: InfoBarSeverity.info,
|
||||||
|
onClose: () => _serverController.warning.value = false
|
||||||
|
),
|
||||||
),
|
),
|
||||||
HostInput(),
|
HostInput(),
|
||||||
PortInput(),
|
PortInput(),
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get_storage/get_storage.dart';
|
||||||
|
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/model/server_type.dart';
|
||||||
import 'package:reboot_launcher/src/widget/shared/smart_switch.dart';
|
import 'package:reboot_launcher/src/widget/shared/smart_switch.dart';
|
||||||
|
|
||||||
import '../util/checks.dart';
|
import '../util/checks.dart';
|
||||||
import '../widget/os/file_selector.dart';
|
import '../widget/shared/file_selector.dart';
|
||||||
|
import '../widget/shared/smart_input.dart';
|
||||||
|
|
||||||
class SettingsPage extends StatelessWidget {
|
class SettingsPage extends StatelessWidget {
|
||||||
|
final ServerController _serverController = Get.find<ServerController>();
|
||||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||||
|
|
||||||
SettingsPage({Key? key}) : super(key: key);
|
SettingsPage({Key? key}) : super(key: key);
|
||||||
@@ -17,48 +22,57 @@ class SettingsPage extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
FileSelector(
|
Tooltip(
|
||||||
label: "Reboot DLL",
|
message: "The hostname of the server that hosts the multiplayer matches",
|
||||||
placeholder: "Type the path to the reboot dll",
|
child: Obx(() => SmartInput(
|
||||||
controller: _settingsController.rebootDll,
|
label: "Matchmaking Host",
|
||||||
windowTitle: "Select a dll",
|
placeholder:
|
||||||
folder: false,
|
"Type the hostname of the server that hosts the multiplayer matches",
|
||||||
extension: "dll",
|
controller: _settingsController.matchmakingIp,
|
||||||
validator: checkDll,
|
validatorMode: AutovalidateMode.always,
|
||||||
validatorMode: AutovalidateMode.always
|
validator: checkMatchmaking,
|
||||||
),
|
enabled: _serverController.type() == ServerType.embedded
|
||||||
|
))
|
||||||
FileSelector(
|
),
|
||||||
label: "Console DLL",
|
Tooltip(
|
||||||
placeholder: "Type the path to the console dll",
|
message: "The dll that is injected when a server is launched",
|
||||||
controller: _settingsController.consoleDll,
|
child: FileSelector(
|
||||||
windowTitle: "Select a dll",
|
label: "Reboot DLL",
|
||||||
folder: false,
|
placeholder: "Type the path to the reboot dll",
|
||||||
extension: "dll",
|
controller: _settingsController.rebootDll,
|
||||||
validator: checkDll,
|
windowTitle: "Select a dll",
|
||||||
validatorMode: AutovalidateMode.always
|
folder: false,
|
||||||
),
|
extension: "dll",
|
||||||
|
validator: checkDll,
|
||||||
FileSelector(
|
validatorMode: AutovalidateMode.always),
|
||||||
label: "Cranium DLL",
|
),
|
||||||
placeholder: "Type the path to the cranium dll",
|
Tooltip(
|
||||||
controller: _settingsController.craniumDll,
|
message: "The dll that is injected when a client is launched",
|
||||||
windowTitle: "Select a dll",
|
child: FileSelector(
|
||||||
folder: false,
|
label: "Console DLL",
|
||||||
extension: "dll",
|
placeholder: "Type the path to the console dll",
|
||||||
validator: checkDll,
|
controller: _settingsController.consoleDll,
|
||||||
validatorMode: AutovalidateMode.always
|
windowTitle: "Select a dll",
|
||||||
),
|
folder: false,
|
||||||
|
extension: "dll",
|
||||||
SmartSwitch(
|
validator: checkDll,
|
||||||
value: _settingsController.autoUpdate,
|
validatorMode: AutovalidateMode.always),
|
||||||
label: "Update DLLs"
|
),
|
||||||
)
|
Tooltip(
|
||||||
]
|
message: "The dll that is injected to make the game work",
|
||||||
),
|
child: FileSelector(
|
||||||
|
label: "Cranium DLL",
|
||||||
|
placeholder: "Type the path to the cranium dll",
|
||||||
|
controller: _settingsController.craniumDll,
|
||||||
|
windowTitle: "Select a dll",
|
||||||
|
folder: false,
|
||||||
|
extension: "dll",
|
||||||
|
validator: checkDll,
|
||||||
|
validatorMode: AutovalidateMode.always))
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,5 +52,13 @@ String? checkDll(String? text) {
|
|||||||
return "This file is not a dll";
|
return "This file is not a dll";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? checkMatchmaking(String? text) {
|
||||||
|
if (text == null || text.isEmpty) {
|
||||||
|
return "Empty hostname";
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
|
|
||||||
const int appBarSize = 2;
|
const int appBarSize = 2;
|
||||||
final RegExp _regex = RegExp(r'(?<=\(Build )(.*)(?=\))');
|
final RegExp _regex = RegExp(r'(?<=\(Build )(.*)(?=\))');
|
||||||
|
|
||||||
@@ -16,22 +13,6 @@ bool get isWin11 {
|
|||||||
return intBuild != null && intBuild > 22000;
|
return intBuild != null && intBuild > 22000;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> openFolderPicker(String title) async =>
|
|
||||||
await FilePicker.platform.getDirectoryPath(dialogTitle: title);
|
|
||||||
|
|
||||||
Future<String?> openFilePicker(String extension) async {
|
|
||||||
var result = await FilePicker.platform.pickFiles(
|
|
||||||
type: FileType.custom,
|
|
||||||
allowMultiple: false,
|
|
||||||
allowedExtensions: [extension]
|
|
||||||
);
|
|
||||||
if(result == null || result.files.isEmpty){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.files.first.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> loadBinary(String binary, bool safe) async{
|
Future<File> loadBinary(String binary, bool safe) async{
|
||||||
var safeBinary = File("$safeBinariesDirectory\\$binary");
|
var safeBinary = File("$safeBinariesDirectory\\$binary");
|
||||||
if(await safeBinary.exists()){
|
if(await safeBinary.exists()){
|
||||||
|
|||||||
@@ -1,40 +1,58 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
final Uint8List _original = Uint8List.fromList([
|
final Uint8List _originalHeadless = Uint8List.fromList([
|
||||||
45, 0, 105, 0, 110, 0, 118, 0, 105, 0, 116, 0, 101, 0, 115, 0, 101, 0, 115, 0, 115, 0, 105, 0, 111, 0, 110, 0, 32, 0, 45, 0, 105, 0, 110, 0, 118, 0, 105, 0, 116, 0, 101, 0, 102, 0, 114, 0, 111, 0, 109, 0, 32, 0, 45, 0, 112, 0, 97, 0, 114, 0, 116, 0, 121, 0, 95, 0, 106, 0, 111, 0, 105, 0, 110, 0, 105, 0, 110, 0, 102, 0, 111, 0, 95, 0, 116, 0, 111, 0, 107, 0, 101, 0, 110, 0, 32, 0, 45, 0, 114, 0, 101, 0, 112, 0, 108, 0, 97, 0, 121, 0
|
45, 0, 105, 0, 110, 0, 118, 0, 105, 0, 116, 0, 101, 0, 115, 0, 101, 0, 115, 0, 115, 0, 105, 0, 111, 0, 110, 0, 32, 0, 45, 0, 105, 0, 110, 0, 118, 0, 105, 0, 116, 0, 101, 0, 102, 0, 114, 0, 111, 0, 109, 0, 32, 0, 45, 0, 112, 0, 97, 0, 114, 0, 116, 0, 121, 0, 95, 0, 106, 0, 111, 0, 105, 0, 110, 0, 105, 0, 110, 0, 102, 0, 111, 0, 95, 0, 116, 0, 111, 0, 107, 0, 101, 0, 110, 0, 32, 0, 45, 0, 114, 0, 101, 0, 112, 0, 108, 0, 97, 0, 121, 0
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final Uint8List _patched = Uint8List.fromList([
|
final Uint8List _patchedHeadless = Uint8List.fromList([
|
||||||
45, 0, 108, 0, 111, 0, 103, 0, 32, 0, 45, 0, 110, 0, 111, 0, 115, 0, 112, 0, 108, 0, 97, 0, 115, 0, 104, 0, 32, 0, 45, 0, 110, 0, 111, 0, 115, 0, 111, 0, 117, 0, 110, 0, 100, 0, 32, 0, 45, 0, 110, 0, 117, 0, 108, 0, 108, 0, 114, 0, 104, 0, 105, 0, 32, 0, 45, 0, 117, 0, 115, 0, 101, 0, 111, 0, 108, 0, 100, 0, 105, 0, 116, 0, 101, 0, 109, 0, 99, 0, 97, 0, 114, 0, 100, 0, 115, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0
|
45, 0, 108, 0, 111, 0, 103, 0, 32, 0, 45, 0, 110, 0, 111, 0, 115, 0, 112, 0, 108, 0, 97, 0, 115, 0, 104, 0, 32, 0, 45, 0, 110, 0, 111, 0, 115, 0, 111, 0, 117, 0, 110, 0, 100, 0, 32, 0, 45, 0, 110, 0, 117, 0, 108, 0, 108, 0, 114, 0, 104, 0, 105, 0, 32, 0, 45, 0, 117, 0, 115, 0, 101, 0, 111, 0, 108, 0, 100, 0, 105, 0, 116, 0, 101, 0, 109, 0, 99, 0, 97, 0, 114, 0, 100, 0, 115, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Future<bool> patch(File file) async {
|
final Uint8List _originalMatchmaking = Uint8List.fromList([
|
||||||
if(_original.length != _patched.length){
|
63, 0, 69, 0, 110, 0, 99, 0, 114, 0, 121, 0, 112, 0, 116, 0, 105, 0, 111, 0, 110, 0, 84, 0, 111, 0, 107, 0, 101, 0, 110, 0, 61
|
||||||
throw Exception("Cannot mutate length of binary file");
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
var read = await file.readAsBytes();
|
final Uint8List _patchedMatchmaking = Uint8List.fromList([
|
||||||
var length = await file.length();
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
var offset = 0;
|
]);
|
||||||
var counter = 0;
|
|
||||||
while(offset < length){
|
Future<bool> patchHeadless(File file) async =>
|
||||||
if(read[offset] == _original[counter]){
|
_patch(file, _originalHeadless, _patchedHeadless);
|
||||||
counter++;
|
|
||||||
}else {
|
Future<bool> patchMatchmaking(File file) async =>
|
||||||
counter = 0;
|
await _patch(file, _originalMatchmaking, _patchedMatchmaking);
|
||||||
|
|
||||||
|
Future<bool> _patch(File file, Uint8List original, Uint8List patched) async {
|
||||||
|
try {
|
||||||
|
if(original.length != patched.length){
|
||||||
|
throw Exception("Cannot mutate length of binary file");
|
||||||
}
|
}
|
||||||
|
|
||||||
offset++;
|
var read = await file.readAsBytes();
|
||||||
if(counter == _original.length){
|
var length = await file.length();
|
||||||
for(var index = 0; index < _patched.length; index++){
|
var offset = 0;
|
||||||
read[offset - counter + index] = _patched[index];
|
var counter = 0;
|
||||||
|
while(offset < length){
|
||||||
|
if(read[offset] == original[counter]){
|
||||||
|
counter++;
|
||||||
|
}else {
|
||||||
|
counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
await file.writeAsBytes(read, mode: FileMode.write);
|
offset++;
|
||||||
return true;
|
if(counter == original.length){
|
||||||
}
|
for(var index = 0; index < patched.length; index++){
|
||||||
}
|
read[offset - counter + index] = patched[index];
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
await file.writeAsBytes(read, mode: FileMode.write);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}catch(_){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -28,12 +28,12 @@ Future<int> downloadRebootDll(int? lastUpdateMs) async {
|
|||||||
|
|
||||||
var outputDir = await tempDirectory.createTemp("reboot");
|
var outputDir = await tempDirectory.createTemp("reboot");
|
||||||
await extractFileToDisk(tempZip.path, outputDir.path);
|
await extractFileToDisk(tempZip.path, outputDir.path);
|
||||||
|
|
||||||
var rebootDll = outputDir.listSync()
|
var rebootDll = outputDir.listSync()
|
||||||
.firstWhere((element) => path.extension(element.path) == ".dll");
|
.firstWhere((element) => path.extension(element.path) == ".dll");
|
||||||
if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await File(rebootDll.path).readAsBytes())) {
|
if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await File(rebootDll.path).readAsBytes())) {
|
||||||
outputDir.delete(recursive: true);
|
outputDir.delete(recursive: true);
|
||||||
return lastUpdateMs ?? now.millisecondsSinceEpoch;
|
return now.millisecondsSinceEpoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
await rebootDll.rename(oldRebootDll.path);
|
await rebootDll.rename(oldRebootDll.path);
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import 'package:reboot_launcher/src/util/os.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shelf_proxy/shelf_proxy.dart';
|
import 'package:shelf_proxy/shelf_proxy.dart';
|
||||||
import 'package:shelf/shelf_io.dart';
|
import 'package:shelf/shelf_io.dart';
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
|
|
||||||
final serverLocation = File("${Platform.environment["UserProfile"]}\\.reboot_launcher\\lawin\\Lawin.exe");
|
final serverLocation = File("${Platform.environment["UserProfile"]}\\.reboot_launcher\\lawin_new\\Lawin.exe");
|
||||||
|
final serverConfig = File("${Platform.environment["UserProfile"]}\\.reboot_launcher\\lawin_new\\Config\\config.ini");
|
||||||
|
final serverLogFile = File("${Platform.environment["UserProfile"]}\\.reboot_launcher\\server.txt");
|
||||||
|
|
||||||
const String _serverUrl =
|
const String _serverUrl =
|
||||||
"https://cdn.discordapp.com/attachments/1026121175878881290/1031230792069820487/LawinServer.zip";
|
"https://cdn.discordapp.com/attachments/1031262639457828910/1034506676843327549/lawin.zip";
|
||||||
|
|
||||||
Future<bool> downloadServer(ignored) async {
|
Future<bool> downloadServer(ignored) async {
|
||||||
var response = await http.get(Uri.parse(_serverUrl));
|
var response = await http.get(Uri.parse(_serverUrl));
|
||||||
@@ -63,7 +65,7 @@ List<String> createRebootArgs(String username, bool headless) {
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uri?> pingSelf() async => ping("127.0.0.1", "3551");
|
Future<Uri?> pingSelf(String port) async => ping("127.0.0.1", port);
|
||||||
|
|
||||||
Future<Uri?> ping(String host, String port, [bool https=false]) async {
|
Future<Uri?> ping(String host, String port, [bool https=false]) async {
|
||||||
var hostName = _getHostName(host);
|
var hostName = _getHostName(host);
|
||||||
@@ -72,14 +74,15 @@ Future<Uri?> ping(String host, String port, [bool https=false]) async {
|
|||||||
var uri = Uri(
|
var uri = Uri(
|
||||||
scheme: declaredScheme ?? (https ? "https" : "http"),
|
scheme: declaredScheme ?? (https ? "https" : "http"),
|
||||||
host: hostName,
|
host: hostName,
|
||||||
port: int.parse(port)
|
port: int.parse(port),
|
||||||
|
path: "unknown"
|
||||||
);
|
);
|
||||||
var client = HttpClient()
|
var client = HttpClient()
|
||||||
..connectionTimeout = const Duration(seconds: 5);
|
..connectionTimeout = const Duration(seconds: 5);
|
||||||
var request = await client.getUrl(uri);
|
var request = await client.getUrl(uri);
|
||||||
var response = await request.close();
|
var response = await request.close();
|
||||||
var body = utf8.decode(await response.single);
|
var body = utf8.decode(await response.single);
|
||||||
return response.statusCode == 200 && body.contains("Welcome to LawinServer!") ? uri : null;
|
return body.contains("epicgames") || body.contains("lawinserver") ? uri : null;
|
||||||
}catch(_){
|
}catch(_){
|
||||||
return https || declaredScheme != null ? null : await ping(host, port, true);
|
return https || declaredScheme != null ? null : await ping(host, port, true);
|
||||||
}
|
}
|
||||||
@@ -89,7 +92,7 @@ String? _getHostName(String host) => host.replaceFirst("http://", "").replaceFir
|
|||||||
|
|
||||||
String? _getScheme(String host) => host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
String? _getScheme(String host) => host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||||
|
|
||||||
Future<ServerResult> checkServerPreconditions(String host, String port, ServerType type) async {
|
Future<ServerResult> checkServerPreconditions(String host, String port, ServerType type, bool needsFreePort) async {
|
||||||
host = host.trim();
|
host = host.trim();
|
||||||
if(host.isEmpty){
|
if(host.isEmpty){
|
||||||
return ServerResult(
|
return ServerResult(
|
||||||
@@ -113,6 +116,13 @@ Future<ServerResult> checkServerPreconditions(String host, String port, ServerTy
|
|||||||
if(type == ServerType.embedded || type == ServerType.remote){
|
if(type == ServerType.embedded || type == ServerType.remote){
|
||||||
var free = await isLawinPortFree();
|
var free = await isLawinPortFree();
|
||||||
if (!free) {
|
if (!free) {
|
||||||
|
if(!needsFreePort) {
|
||||||
|
return ServerResult(
|
||||||
|
uri: pingSelf(port),
|
||||||
|
type: ServerResultType.ignoreStart
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return ServerResult(
|
return ServerResult(
|
||||||
type: ServerResultType.portTakenError
|
type: ServerResultType.portTakenError
|
||||||
);
|
);
|
||||||
@@ -131,21 +141,42 @@ Future<ServerResult> checkServerPreconditions(String host, String port, ServerTy
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Process> startEmbeddedServer() async {
|
Future<Process?> startEmbeddedServer() async {
|
||||||
return await Process.start(serverLocation.path, [], workingDirectory: serverLocation.parent.path);
|
await resetServerLog();
|
||||||
|
try {
|
||||||
|
var process = await Process.start(serverLocation.path, [], workingDirectory: serverLocation.parent.path);
|
||||||
|
process.outLines.forEach((line) => serverLogFile.writeAsString("$line\n", mode: FileMode.append));
|
||||||
|
process.errLines.forEach((line) => serverLogFile.writeAsString("$line\n", mode: FileMode.append));
|
||||||
|
return process;
|
||||||
|
} on ProcessException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HttpServer> startRemoteServer(Uri uri) async {
|
Future<HttpServer> startRemoteServer(Uri uri) async {
|
||||||
return await serve(proxyHandler(uri), "127.0.0.1", 3551);
|
return await serve(proxyHandler(uri), "127.0.0.1", 3551);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> resetServerLog() async {
|
||||||
|
try {
|
||||||
|
if(await serverLogFile.exists()) {
|
||||||
|
await serverLogFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
await serverLogFile.create();
|
||||||
|
}catch(_){
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ServerResult {
|
class ServerResult {
|
||||||
final Future<Uri?>? uri;
|
final Future<Uri?>? uri;
|
||||||
|
final int? pid;
|
||||||
final Object? error;
|
final Object? error;
|
||||||
final StackTrace? stackTrace;
|
final StackTrace? stackTrace;
|
||||||
final ServerResultType type;
|
final ServerResultType type;
|
||||||
|
|
||||||
ServerResult({this.uri, this.error, this.stackTrace, required this.type});
|
ServerResult({this.uri, this.pid, this.error, this.stackTrace, required this.type});
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServerResultType {
|
enum ServerResultType {
|
||||||
@@ -156,7 +187,8 @@ enum ServerResultType {
|
|||||||
portTakenError,
|
portTakenError,
|
||||||
serverDownloadRequiredError,
|
serverDownloadRequiredError,
|
||||||
canStart,
|
canStart,
|
||||||
|
ignoreStart,
|
||||||
started,
|
started,
|
||||||
unknownError,
|
unknownError,
|
||||||
stopped
|
stopped,
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@ import 'package:reboot_launcher/src/util/injector.dart';
|
|||||||
import 'package:reboot_launcher/src/util/patcher.dart';
|
import 'package:reboot_launcher/src/util/patcher.dart';
|
||||||
import 'package:reboot_launcher/src/util/reboot.dart';
|
import 'package:reboot_launcher/src/util/reboot.dart';
|
||||||
import 'package:reboot_launcher/src/util/server.dart';
|
import 'package:reboot_launcher/src/util/server.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
@@ -55,10 +54,10 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Obx(() => Tooltip(
|
child: Obx(() => Tooltip(
|
||||||
message: _gameController.started.value ? "Close the running Fortnite instance" : "Launch a new Fortnite instance",
|
message: _gameController.started() ? "Close the running Fortnite instance" : "Launch a new Fortnite instance",
|
||||||
child: Button(
|
child: Button(
|
||||||
onPressed: _onPressed,
|
onPressed: _onPressed,
|
||||||
child: Text(_gameController.started.value ? "Close" : "Launch")
|
child: Text(_gameController.started() ? "Close" : "Launch")
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
@@ -66,41 +65,38 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onPressed() async {
|
void _onPressed() async {
|
||||||
|
if (_gameController.started()) {
|
||||||
|
_onStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameController.started.value = true;
|
||||||
if (_gameController.username.text.isEmpty) {
|
if (_gameController.username.text.isEmpty) {
|
||||||
showMessage("Missing in-game username");
|
showMessage("Missing in-game username");
|
||||||
_updateServerState(false);
|
_gameController.started.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_gameController.selectedVersionObs.value == null) {
|
if (_gameController.selectedVersionObs.value == null) {
|
||||||
showMessage("No version is selected");
|
showMessage("No version is selected");
|
||||||
_updateServerState(false);
|
_gameController.started.value = false;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_gameController.started.value) {
|
|
||||||
_onStop();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_updateServerState(true);
|
|
||||||
var version = _gameController.selectedVersionObs.value!;
|
var version = _gameController.selectedVersionObs.value!;
|
||||||
var hosting = _gameController.type.value == GameType.headlessServer;
|
var gamePath = version.executable?.path;
|
||||||
|
if(gamePath == null){
|
||||||
|
_onError("${version.location.path} no longer contains a Fortnite executable. Did you delete it?", null);
|
||||||
|
_onStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (version.launcher != null) {
|
if (version.launcher != null) {
|
||||||
_gameController.launcherProcess = await Process.start(version.launcher!.path, []);
|
_gameController.launcherProcess = await Process.start(version.launcher!.path, []);
|
||||||
Win32Process(_gameController.launcherProcess!.pid).suspend();
|
Win32Process(_gameController.launcherProcess!.pid).suspend();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(hosting){
|
|
||||||
await patch(version.executable!);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!mounted){
|
|
||||||
_onStop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _serverController.changeStateInteractive(true);
|
var result = await _serverController.changeStateInteractive(true);
|
||||||
if(!result){
|
if(!result){
|
||||||
_onStop();
|
_onStop();
|
||||||
@@ -111,19 +107,16 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
await _logFile!.delete();
|
await _logFile!.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
var gamePath = version.executable?.path;
|
|
||||||
if(gamePath == null){
|
|
||||||
_onError("${version.location.path} no longer contains a Fortnite executable. Did you delete it?", null);
|
|
||||||
_onStop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_gameController.gameProcess = await Process.start(gamePath, createRebootArgs(_gameController.username.text, hosting))
|
await patch(version.executable!);
|
||||||
|
|
||||||
|
var headlessHosting = _gameController.type() == GameType.headlessServer;
|
||||||
|
var arguments = createRebootArgs(_gameController.username.text, headlessHosting);
|
||||||
|
_gameController.gameProcess = await Process.start(gamePath, arguments)
|
||||||
..exitCode.then((_) => _onEnd())
|
..exitCode.then((_) => _onEnd())
|
||||||
..outLines.forEach(_onGameOutput);
|
..outLines.forEach((line) => _onGameOutput(line, version.memoryFix))
|
||||||
await _injectOrShowError(Injectable.cranium);
|
..errLines.forEach((line) => _onGameOutput(line, version.memoryFix));
|
||||||
|
if(headlessHosting){
|
||||||
if(hosting){
|
|
||||||
await _showServerLaunchingWarning();
|
await _showServerLaunchingWarning();
|
||||||
}
|
}
|
||||||
} catch (exception, stacktrace) {
|
} catch (exception, stacktrace) {
|
||||||
@@ -133,12 +126,15 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateServerState(bool value) async {
|
Future<bool> patch(File file) async {
|
||||||
if (_gameController.started.value == value) {
|
switch(_gameController.type()){
|
||||||
return;
|
case GameType.client:
|
||||||
|
return await compute(patchMatchmaking, file);
|
||||||
|
case GameType.server:
|
||||||
|
return false;
|
||||||
|
case GameType.headlessServer:
|
||||||
|
return await compute(patchHeadless, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
_gameController.started(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onEnd() {
|
void _onEnd() {
|
||||||
@@ -170,15 +166,12 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
|
|
||||||
var result = await showDialog<bool>(
|
var result = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => InfoDialog.ofOnly(
|
builder: (context) => ProgressDialog(
|
||||||
text: "Launching headless reboot server...",
|
text: "Launching headless server...",
|
||||||
button: DialogButton(
|
onStop: () {
|
||||||
type: ButtonType.only,
|
Navigator.of(context).pop(false);
|
||||||
onTap: () {
|
_onStop();
|
||||||
Navigator.of(context).pop(false);
|
}
|
||||||
_onStop();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -189,7 +182,11 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
_onStop();
|
_onStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onGameOutput(String line) {
|
void _onGameOutput(String line, bool memoryFix) {
|
||||||
|
if(kDebugMode){
|
||||||
|
print(line);
|
||||||
|
}
|
||||||
|
|
||||||
if(_logFile != null){
|
if(_logFile != null){
|
||||||
_logFile!.writeAsString("$line\n", mode: FileMode.append);
|
_logFile!.writeAsString("$line\n", mode: FileMode.append);
|
||||||
}
|
}
|
||||||
@@ -220,13 +217,22 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(line.contains("Region")){
|
if(line.contains("Platform has ")){
|
||||||
|
_injectOrShowError(Injectable.cranium);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.contains("Login: Completing Sign-in")){
|
||||||
if(_gameController.type.value == GameType.client){
|
if(_gameController.type.value == GameType.client){
|
||||||
_injectOrShowError(Injectable.console);
|
_injectOrShowError(Injectable.console);
|
||||||
}else {
|
}else {
|
||||||
_injectOrShowError(Injectable.reboot)
|
_injectOrShowError(Injectable.reboot)
|
||||||
.then((value) => _closeDialogIfOpen(true));
|
.then((value) => _closeDialogIfOpen(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(memoryFix){
|
||||||
|
_injectOrShowError(Injectable.memoryFix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +248,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onStop() {
|
void _onStop() {
|
||||||
_updateServerState(false);
|
_gameController.started.value = false;
|
||||||
_gameController.kill();
|
_gameController.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +259,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var dllPath = _getDllPath(injectable);
|
var dllPath = await _getDllPath(injectable);
|
||||||
if(!dllPath.existsSync()) {
|
if(!dllPath.existsSync()) {
|
||||||
await _downloadMissingDll(injectable);
|
await _downloadMissingDll(injectable);
|
||||||
if(!dllPath.existsSync()){
|
if(!dllPath.existsSync()){
|
||||||
@@ -284,7 +290,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
File _getDllPath(Injectable injectable){
|
Future<File> _getDllPath(Injectable injectable) async {
|
||||||
switch(injectable){
|
switch(injectable){
|
||||||
case Injectable.reboot:
|
case Injectable.reboot:
|
||||||
return File(_settingsController.rebootDll.text);
|
return File(_settingsController.rebootDll.text);
|
||||||
@@ -292,6 +298,8 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return File(_settingsController.consoleDll.text);
|
return File(_settingsController.consoleDll.text);
|
||||||
case Injectable.cranium:
|
case Injectable.cranium:
|
||||||
return File(_settingsController.craniumDll.text);
|
return File(_settingsController.craniumDll.text);
|
||||||
|
case Injectable.memoryFix:
|
||||||
|
return await loadBinary("fix.dll", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,5 +316,6 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
enum Injectable {
|
enum Injectable {
|
||||||
console,
|
console,
|
||||||
cranium,
|
cranium,
|
||||||
reboot
|
reboot,
|
||||||
|
memoryFix
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/dialog/snackbar.dart';
|
||||||
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
||||||
import 'package:reboot_launcher/src/dialog/add_local_version.dart';
|
import 'package:reboot_launcher/src/dialog/add_local_version.dart';
|
||||||
import 'package:reboot_launcher/src/widget/shared/smart_check_box.dart';
|
import 'package:reboot_launcher/src/widget/shared/smart_check_box.dart';
|
||||||
@@ -119,7 +120,7 @@ class _VersionSelectorState extends State<VersionSelector> {
|
|||||||
context: context,
|
context: context,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
builder: (context) => MenuFlyout(
|
builder: (context) => MenuFlyout(
|
||||||
items: ContextualOption.values
|
items: ContextualOption.getValues(version.memoryFix)
|
||||||
.map((entry) => _createOption(context, entry))
|
.map((entry) => _createOption(context, entry))
|
||||||
.toList()
|
.toList()
|
||||||
)
|
)
|
||||||
@@ -172,8 +173,26 @@ class _VersionSelectorState extends State<VersionSelector> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case ContextualOption.enableMemoryFix:
|
||||||
|
if(!mounted){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case null:
|
version.memoryFix = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showMessage("Enabled memory fix");
|
||||||
|
break;
|
||||||
|
case ContextualOption.disableMemoryFix:
|
||||||
|
if(!mounted){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
version.memoryFix = false;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showMessage("Disabled memory fix");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,11 +299,21 @@ class _VersionSelectorState extends State<VersionSelector> {
|
|||||||
enum ContextualOption {
|
enum ContextualOption {
|
||||||
openExplorer,
|
openExplorer,
|
||||||
rename,
|
rename,
|
||||||
|
enableMemoryFix,
|
||||||
|
disableMemoryFix,
|
||||||
delete;
|
delete;
|
||||||
|
|
||||||
|
static List<ContextualOption> getValues(bool memoryFix){
|
||||||
|
return memoryFix
|
||||||
|
? [ContextualOption.openExplorer, ContextualOption.rename, ContextualOption.disableMemoryFix, ContextualOption.delete]
|
||||||
|
: [ContextualOption.openExplorer, ContextualOption.rename, ContextualOption.enableMemoryFix, ContextualOption.delete];
|
||||||
|
}
|
||||||
|
|
||||||
String get name {
|
String get name {
|
||||||
return this == ContextualOption.openExplorer ? "Open in explorer"
|
return this == ContextualOption.openExplorer ? "Open in explorer"
|
||||||
: this == ContextualOption.rename ? "Rename"
|
: this == ContextualOption.rename ? "Rename"
|
||||||
|
: this == ContextualOption.enableMemoryFix ? "Enable memory leak fix"
|
||||||
|
: this == ContextualOption.disableMemoryFix ? "Disable memory leak fix"
|
||||||
: "Delete";
|
: "Delete";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:reboot_launcher/src/dialog/snackbar.dart';
|
|
||||||
|
|
||||||
import '../../util/os.dart';
|
|
||||||
|
|
||||||
class FileSelector extends StatefulWidget {
|
|
||||||
final String label;
|
|
||||||
final String placeholder;
|
|
||||||
final String windowTitle;
|
|
||||||
final bool allowNavigator;
|
|
||||||
final TextEditingController controller;
|
|
||||||
final String? Function(String?) validator;
|
|
||||||
final AutovalidateMode? validatorMode;
|
|
||||||
final String? extension;
|
|
||||||
final bool folder;
|
|
||||||
|
|
||||||
const FileSelector(
|
|
||||||
{required this.label,
|
|
||||||
required this.placeholder,
|
|
||||||
required this.windowTitle,
|
|
||||||
required this.controller,
|
|
||||||
required this.validator,
|
|
||||||
required this.folder,
|
|
||||||
this.extension,
|
|
||||||
this.validatorMode,
|
|
||||||
this.allowNavigator = true,
|
|
||||||
Key? key})
|
|
||||||
: assert(folder || extension != null, "Missing extension for file selector"),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<FileSelector> createState() => _FileSelectorState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FileSelectorState extends State<FileSelector> {
|
|
||||||
bool _selecting = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InfoLabel(
|
|
||||||
label: widget.label,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextFormBox(
|
|
||||||
controller: widget.controller,
|
|
||||||
placeholder: widget.placeholder,
|
|
||||||
validator: widget.validator,
|
|
||||||
autovalidateMode: widget.validatorMode ?? AutovalidateMode.onUserInteraction
|
|
||||||
)
|
|
||||||
),
|
|
||||||
if (widget.allowNavigator) const SizedBox(width: 8.0),
|
|
||||||
if (widget.allowNavigator)
|
|
||||||
Tooltip(
|
|
||||||
message: "Select a ${widget.folder ? 'folder' : 'file'}",
|
|
||||||
child: Button(
|
|
||||||
onPressed: _onPressed,
|
|
||||||
child: const Icon(FluentIcons.open_folder_horizontal)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onPressed() {
|
|
||||||
if(_selecting){
|
|
||||||
showMessage("Folder selector is already opened");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_selecting = true;
|
|
||||||
if(widget.folder) {
|
|
||||||
compute(openFolderPicker, widget.windowTitle)
|
|
||||||
.then((value) => widget.controller.text = value ?? widget.controller.text)
|
|
||||||
.then((_) => _selecting = false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
compute(openFilePicker, widget.extension!)
|
|
||||||
.then((value) => widget.controller.text = value ?? widget.controller.text)
|
|
||||||
.then((_) => _selecting = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,8 @@ class SmartInput extends StatelessWidget {
|
|||||||
final bool enabled;
|
final bool enabled;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final bool readOnly;
|
final bool readOnly;
|
||||||
|
final AutovalidateMode validatorMode;
|
||||||
|
final String? Function(String?)? validator;
|
||||||
|
|
||||||
const SmartInput(
|
const SmartInput(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
@@ -17,12 +19,14 @@ class SmartInput extends StatelessWidget {
|
|||||||
this.onTap,
|
this.onTap,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
this.readOnly = false,
|
this.readOnly = false,
|
||||||
this.type = TextInputType.text})
|
this.type = TextInputType.text,
|
||||||
|
this.validatorMode = AutovalidateMode.disabled,
|
||||||
|
this.validator})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextBox(
|
return TextFormBox(
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
header: label,
|
header: label,
|
||||||
@@ -30,6 +34,8 @@ class SmartInput extends StatelessWidget {
|
|||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
readOnly: readOnly,
|
readOnly: readOnly,
|
||||||
|
autovalidateMode: validatorMode,
|
||||||
|
validator: validator
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
|
||||||
|
|
||||||
class WarningInfo extends StatelessWidget {
|
|
||||||
final String text;
|
|
||||||
final VoidCallback onPressed;
|
|
||||||
final IconData icon;
|
|
||||||
final InfoBarSeverity severity;
|
|
||||||
|
|
||||||
const WarningInfo(
|
|
||||||
{Key? key,
|
|
||||||
required this.text,
|
|
||||||
required this.icon,
|
|
||||||
required this.onPressed,
|
|
||||||
this.severity = InfoBarSeverity.info})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InfoBar(
|
|
||||||
severity: severity,
|
|
||||||
title: Text(text),
|
|
||||||
action: IconButton(
|
|
||||||
icon: Icon(icon),
|
|
||||||
onPressed: onPressed
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
name: reboot_launcher
|
name: reboot_launcher
|
||||||
description: Launcher for project reboot
|
description: Launcher for project reboot
|
||||||
version: "4.0.0"
|
version: "4.4.0"
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@ dependencies:
|
|||||||
win32: 3.0.0
|
win32: 3.0.0
|
||||||
clipboard: ^0.1.3
|
clipboard: ^0.1.3
|
||||||
sync: ^0.3.0
|
sync: ^0.3.0
|
||||||
|
ini: ^2.1.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
win32: ^3.0.0
|
win32: ^3.0.0
|
||||||
@@ -46,6 +47,7 @@ dev_dependencies:
|
|||||||
|
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^2.0.1
|
||||||
msix: ^3.6.3
|
msix: ^3.6.3
|
||||||
|
hex: ^0.2.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
@@ -58,7 +60,7 @@ msix_config:
|
|||||||
display_name: Reboot Launcher
|
display_name: Reboot Launcher
|
||||||
publisher_display_name: Auties00
|
publisher_display_name: Auties00
|
||||||
identity_name: 31868Auties00.RebootLauncher
|
identity_name: 31868Auties00.RebootLauncher
|
||||||
msix_version: 4.0.0.0
|
msix_version: 4.4.0.0
|
||||||
publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029
|
publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029
|
||||||
logo_path: ./assets/icons/reboot.ico
|
logo_path: ./assets/icons/reboot.ico
|
||||||
architecture: x64
|
architecture: x64
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
flutter_distributor package --platform windows --targets exe
|
flutter_distributor package --platform windows --targets exe
|
||||||
flutter pub run msix:create
|
flutter pub run msix:create
|
||||||
dart compile exe ./../lib/cli.dart --output ./../dist/cli/reboot.exe
|
dart compile exe ./lib/cli.dart --output ./dist/cli/reboot.exe
|
||||||
|
|||||||
Reference in New Issue
Block a user