mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
<feat: New release>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
const String kDefaultPlayerName = "Player";
|
||||
const String kDefaultGameServerPort = "7777";
|
||||
const String shutdownLine = "FOnlineSubsystemGoogleCommon::Shutdown()";
|
||||
const List<String> corruptedBuildErrors = [
|
||||
"when 0 bytes remain",
|
||||
|
||||
@@ -5,7 +5,7 @@ class GameInstance {
|
||||
final int gamePid;
|
||||
final int? launcherPid;
|
||||
final int? eacPid;
|
||||
int? watchPid;
|
||||
int? observerPid;
|
||||
bool hosting;
|
||||
bool tokenError;
|
||||
bool linkedHosting;
|
||||
@@ -14,14 +14,27 @@ class GameInstance {
|
||||
: tokenError = false,
|
||||
assert(!linkedHosting || !hosting, "Only a game instance can have a linked hosting server");
|
||||
|
||||
GameInstance.fromJson(Map<String, dynamic>? json) :
|
||||
gamePid = json?["game"] ?? -1,
|
||||
launcherPid = json?["launcher"],
|
||||
eacPid = json?["eac"],
|
||||
watchPid = json?["watchPid"],
|
||||
hosting = json?["hosting"] ?? false,
|
||||
tokenError = json?["tokenError"] ?? false,
|
||||
linkedHosting = json?["linkedHosting"] ?? false;
|
||||
GameInstance._fromJson(this.gamePid, this.launcherPid, this.eacPid, this.observerPid,
|
||||
this.hosting, this.tokenError, this.linkedHosting);
|
||||
|
||||
static GameInstance? fromJson(Map<String, dynamic>? json) {
|
||||
if(json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var gamePid = json["game"];
|
||||
if(gamePid == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var launcherPid = json["launcher"];
|
||||
var eacPid = json["eac"];
|
||||
var observerPid = json["observer"];
|
||||
var hosting = json["hosting"];
|
||||
var tokenError = json["tokenError"];
|
||||
var linkedHosting = json["linkedHosting"];
|
||||
return GameInstance._fromJson(gamePid, launcherPid, eacPid, observerPid, hosting, tokenError, linkedHosting);
|
||||
}
|
||||
|
||||
void kill() {
|
||||
Process.killPid(gamePid, ProcessSignal.sigabrt);
|
||||
@@ -31,8 +44,8 @@ class GameInstance {
|
||||
if(eacPid != null) {
|
||||
Process.killPid(eacPid!, ProcessSignal.sigabrt);
|
||||
}
|
||||
if(watchPid != null) {
|
||||
Process.killPid(watchPid!, ProcessSignal.sigabrt);
|
||||
if(observerPid != null) {
|
||||
Process.killPid(observerPid!, ProcessSignal.sigabrt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +53,7 @@ class GameInstance {
|
||||
'game': gamePid,
|
||||
'launcher': launcherPid,
|
||||
'eac': eacPid,
|
||||
'watch': watchPid,
|
||||
'observer': observerPid,
|
||||
'hosting': hosting,
|
||||
'tokenError': tokenError,
|
||||
'linkedHosting': linkedHosting
|
||||
|
||||
@@ -7,6 +7,12 @@ class ServerResult {
|
||||
}
|
||||
|
||||
enum ServerResultType {
|
||||
starting,
|
||||
startSuccess,
|
||||
startError,
|
||||
stopping,
|
||||
stopSuccess,
|
||||
stopError,
|
||||
missingHostError,
|
||||
missingPortError,
|
||||
illegalPortError,
|
||||
@@ -15,9 +21,7 @@ enum ServerResultType {
|
||||
freePortError,
|
||||
pingingRemote,
|
||||
pingingLocal,
|
||||
pingError,
|
||||
startSuccess,
|
||||
startError;
|
||||
pingError;
|
||||
|
||||
bool get isError => name.contains("Error");
|
||||
}
|
||||
@@ -1,48 +1,30 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:process_run/process_run.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:shelf/shelf_io.dart';
|
||||
import 'package:shelf_proxy/shelf_proxy.dart';
|
||||
|
||||
final authenticatorDirectory = Directory("${assetsDirectory.path}\\authenticator");
|
||||
final authenticatorStartExecutable = File("${authenticatorDirectory.path}\\lawinserver.exe");
|
||||
final authenticatorKillExecutable = File("${authenticatorDirectory.path}\\kill.bat");
|
||||
|
||||
final authenticatorLogFile = File("${logsDirectory.path}\\authenticator.log");
|
||||
final authenticatorDirectory = Directory("${assetsDirectory.path}\\lawin");
|
||||
final authenticatorExecutable = File("${authenticatorDirectory.path}\\run.bat");
|
||||
|
||||
Future<Process> startEmbeddedAuthenticator(bool detached) async {
|
||||
if(!authenticatorExecutable.existsSync()) {
|
||||
throw StateError("${authenticatorExecutable.path} doesn't exist");
|
||||
}
|
||||
|
||||
var process = await Process.start(
|
||||
authenticatorExecutable.path,
|
||||
[],
|
||||
workingDirectory: authenticatorDirectory.path,
|
||||
mode: detached ? ProcessStartMode.detached : ProcessStartMode.normal
|
||||
);
|
||||
if(!detached) {
|
||||
authenticatorLogFile.createSync(recursive: true);
|
||||
process.outLines.forEach((element) => authenticatorLogFile.writeAsStringSync("$element\n", mode: FileMode.append));
|
||||
process.errLines.forEach((element) => authenticatorLogFile.writeAsStringSync("$element\n", mode: FileMode.append));
|
||||
}
|
||||
return process;
|
||||
}
|
||||
Future<int> startEmbeddedAuthenticator(bool detached) async => startBackgroundProcess(
|
||||
executable: authenticatorStartExecutable,
|
||||
window: detached
|
||||
);
|
||||
|
||||
Future<HttpServer> startRemoteAuthenticatorProxy(Uri uri) async => await serve(proxyHandler(uri), kDefaultAuthenticatorHost, int.parse(kDefaultAuthenticatorPort));
|
||||
|
||||
Future<bool> isAuthenticatorPortFree() async => isPortFree(int.parse(kDefaultAuthenticatorPort));
|
||||
|
||||
Future<bool> freeAuthenticatorPort() async {
|
||||
var releaseBat = File("${assetsDirectory.path}\\lawin\\kill_lawin.bat");
|
||||
await Process.run(releaseBat.path, []);
|
||||
await Process.run(authenticatorKillExecutable.path, []);
|
||||
var standardResult = await isAuthenticatorPortFree();
|
||||
if(standardResult) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var elevatedResult = await runElevatedProcess(releaseBat.path, "");
|
||||
var elevatedResult = await runElevatedProcess(authenticatorKillExecutable.path, "");
|
||||
if(!elevatedResult) {
|
||||
return false;
|
||||
}
|
||||
@@ -50,9 +32,7 @@ Future<bool> freeAuthenticatorPort() async {
|
||||
return await isAuthenticatorPortFree();
|
||||
}
|
||||
|
||||
Future<Uri?> pingSelf(String port) async => ping(kDefaultAuthenticatorHost, port);
|
||||
|
||||
Future<Uri?> ping(String host, String port, [bool https=false]) async {
|
||||
Future<Uri?> pingAuthenticator(String host, String port, [bool https=false]) async {
|
||||
var hostName = _getHostName(host);
|
||||
var declaredScheme = _getScheme(host);
|
||||
try{
|
||||
@@ -66,10 +46,9 @@ Future<Uri?> ping(String host, String port, [bool https=false]) async {
|
||||
..connectionTimeout = const Duration(seconds: 5);
|
||||
var request = await client.getUrl(uri);
|
||||
var response = await request.close();
|
||||
var body = utf8.decode(await response.single);
|
||||
return body.contains("epicgames") || body.contains("lawinserver") ? uri : null;
|
||||
return response.statusCode == 200 || response.statusCode == 404 ? uri : null;
|
||||
}catch(_){
|
||||
return https || declaredScheme != null ? null : await ping(host, port, true);
|
||||
return https || declaredScheme != null ? null : await pingAuthenticator(host, port, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ Future<void> _extract(Completer<dynamic> stopped, String extension, File tempFil
|
||||
break;
|
||||
case '.rar':
|
||||
process = await Process.start(
|
||||
'${assetsDirectory.path}\\misc\\winrar.exe',
|
||||
'${assetsDirectory.path}\\build\\winrar.exe',
|
||||
['x', tempFile.path, '*.*', options.destination.path],
|
||||
mode: ProcessStartMode.inheritStdio
|
||||
);
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ini/ini.dart';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
final matchmakerDirectory = Directory("${assetsDirectory.path}\\matchmaker");
|
||||
final matchmakerStartExecutable = File("${matchmakerDirectory.path}\\fortmatchmaker.exe");
|
||||
final matchmakerKillExecutable = File("${authenticatorDirectory.path}\\kill.bat");
|
||||
|
||||
Future<int> startEmbeddedMatchmaker(bool detached) async => startBackgroundProcess(
|
||||
executable: matchmakerStartExecutable,
|
||||
window: detached
|
||||
);
|
||||
|
||||
Future<void> writeMatchmakingIp(String text) async {
|
||||
var file = File("${assetsDirectory.path}\\lawin\\Config\\config.ini");
|
||||
var file = File("${authenticatorDirectory}\\Config\\config.ini");
|
||||
if(!file.existsSync()){
|
||||
return;
|
||||
}
|
||||
|
||||
var splitIndex = text.indexOf(":");
|
||||
var ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
|
||||
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : "7777";
|
||||
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort;
|
||||
var config = Config.fromString(file.readAsStringSync());
|
||||
config.set("GameServer", "ip", ip);
|
||||
config.set("GameServer", "port", port);
|
||||
@@ -22,17 +32,57 @@ Future<void> writeMatchmakingIp(String text) async {
|
||||
Future<bool> isMatchmakerPortFree() async => isPortFree(int.parse(kDefaultMatchmakerPort));
|
||||
|
||||
Future<bool> freeMatchmakerPort() async {
|
||||
var releaseBat = File("${assetsDirectory.path}\\lawin\\kill_matchmaker.bat");
|
||||
await Process.run(releaseBat.path, []);
|
||||
await Process.run(matchmakerKillExecutable.path, []);
|
||||
var standardResult = await isMatchmakerPortFree();
|
||||
if(standardResult) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var elevatedResult = await runElevatedProcess(releaseBat.path, "");
|
||||
var elevatedResult = await runElevatedProcess(matchmakerKillExecutable.path, "");
|
||||
if(!elevatedResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await isMatchmakerPortFree();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uri?> pingMatchmaker(String host, String port, [bool wss=false]) async {
|
||||
var hostName = _getHostName(host);
|
||||
var declaredScheme = _getScheme(host);
|
||||
try{
|
||||
var uri = Uri(
|
||||
scheme: declaredScheme ?? (wss ? "wss" : "ws"),
|
||||
host: hostName,
|
||||
port: int.parse(port)
|
||||
);
|
||||
var completer = Completer<bool>();
|
||||
var socket = await WebSocket.connect(uri.toString());
|
||||
socket.listen(
|
||||
(data) {
|
||||
if(!completer.isCompleted) {
|
||||
completer.complete(true);
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
if(!completer.isCompleted) {
|
||||
completer.complete(false);
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
if(!completer.isCompleted) {
|
||||
completer.complete(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
var result = await completer.future;
|
||||
await socket.close();
|
||||
return result ? uri : null;
|
||||
}catch(_){
|
||||
return wss || declaredScheme != null ? null : await pingMatchmaker(host, port, true);
|
||||
}
|
||||
}
|
||||
|
||||
String? _getHostName(String host) => host.replaceFirst("ws://", "").replaceFirst("wss://", "");
|
||||
|
||||
String? _getScheme(String host) => host.startsWith("ws://") ? "ws" : host.startsWith("wss://") ? "wss" : null;
|
||||
|
||||
|
||||
@@ -17,6 +17,6 @@ Future<bool> isPortFree(int port) async {
|
||||
}
|
||||
|
||||
Future<void> resetWinNat() async {
|
||||
var binary = File("${authenticatorDirectory.path}\\winnat.bat");
|
||||
var binary = File("${assetsDirectory.path}\\misc\\winnat.bat");
|
||||
await runElevatedProcess(binary.path, "");
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
@@ -96,31 +97,58 @@ Future<bool> runElevatedProcess(String executable, String args) async {
|
||||
}
|
||||
|
||||
|
||||
int startBackgroundProcess(String executable, List<String> args) {
|
||||
var executablePath = TEXT('$executable ${args.map((entry) => '"$entry"').join(" ")}');
|
||||
void _startBackgroundProcess(_BackgroundProcessParameters params) {
|
||||
var args = params.args;
|
||||
var concatenatedArgs = args == null ? "" : " ${args.map((entry) => '"$entry"').join(" ")}";
|
||||
var executablePath = TEXT("${params.executable.path}$concatenatedArgs");
|
||||
var startupInfo = calloc<STARTUPINFO>();
|
||||
var processInfo = calloc<PROCESS_INFORMATION>();
|
||||
var windowFlag = params.window ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW;
|
||||
var success = CreateProcess(
|
||||
nullptr,
|
||||
executablePath,
|
||||
nullptr,
|
||||
nullptr,
|
||||
FALSE,
|
||||
CREATE_NO_WINDOW,
|
||||
nullptr,
|
||||
NORMAL_PRIORITY_CLASS | windowFlag | CREATE_NEW_PROCESS_GROUP,
|
||||
nullptr,
|
||||
TEXT(params.executable.parent.path),
|
||||
startupInfo,
|
||||
processInfo
|
||||
);
|
||||
if (success == 0) {
|
||||
var error = GetLastError();
|
||||
throw Exception("Cannot start process: $error");
|
||||
params.port.send("Cannot start process: $error");
|
||||
return;
|
||||
}
|
||||
|
||||
var pid = processInfo.ref.dwProcessId;
|
||||
free(startupInfo);
|
||||
free(processInfo);
|
||||
return pid;
|
||||
params.port.send(pid);
|
||||
}
|
||||
|
||||
class _BackgroundProcessParameters {
|
||||
File executable;
|
||||
List<String>? args;
|
||||
bool window;
|
||||
SendPort port;
|
||||
|
||||
_BackgroundProcessParameters(this.executable, this.args, this.window, this.port);
|
||||
}
|
||||
|
||||
Future<int> startBackgroundProcess({required File executable, List<String>? args, bool window = false}) async {
|
||||
var completer = Completer<int>();
|
||||
var port = ReceivePort();
|
||||
port.listen((message) => message is int ? completer.complete(message) : completer.completeError(message));
|
||||
var isolate = await Isolate.spawn(
|
||||
_startBackgroundProcess,
|
||||
_BackgroundProcessParameters(executable, args, window, port.sendPort),
|
||||
errorsAreFatal: true
|
||||
);
|
||||
var result = await completer.future;
|
||||
isolate.kill(priority: Isolate.immediate);
|
||||
return result;
|
||||
}
|
||||
|
||||
int _NtResumeProcess(int hWnd) {
|
||||
@@ -165,12 +193,14 @@ Future<bool> watchProcess(int pid) async {
|
||||
});
|
||||
var errorPort = ReceivePort();
|
||||
errorPort.listen((_) => completer.complete(false));
|
||||
await Isolate.spawn(
|
||||
var isolate = await Isolate.spawn(
|
||||
_watchProcess,
|
||||
pid,
|
||||
onExit: exitPort.sendPort,
|
||||
onError: errorPort.sendPort,
|
||||
errorsAreFatal: true
|
||||
);
|
||||
return completer.future;
|
||||
var result = await completer.future;
|
||||
isolate.kill(priority: Isolate.immediate);
|
||||
return result;
|
||||
}
|
||||
@@ -49,13 +49,13 @@ List<String> createRebootArgs(String username, String password, bool host, Strin
|
||||
}
|
||||
|
||||
|
||||
Future<int> downloadRebootDll(String url, int? lastUpdateMs) async {
|
||||
Future<int> downloadRebootDll(String url, int? lastUpdateMs, {int hours = 24, bool force = false}) async {
|
||||
Directory? outputDir;
|
||||
var now = DateTime.now();
|
||||
try {
|
||||
var lastUpdate = await _getLastUpdate(lastUpdateMs);
|
||||
var exists = await rebootDllFile.exists();
|
||||
if (lastUpdate != null && now.difference(lastUpdate).inHours <= 24 && exists) {
|
||||
if (!force && lastUpdate != null && now.difference(lastUpdate).inHours <= hours && exists) {
|
||||
return lastUpdateMs!;
|
||||
}
|
||||
|
||||
@@ -70,15 +70,7 @@ Future<int> downloadRebootDll(String url, int? lastUpdateMs) async {
|
||||
}
|
||||
|
||||
return now.millisecondsSinceEpoch;
|
||||
}catch(message) {
|
||||
if(url == rebootDownloadUrl){
|
||||
var asset = File('${assetsDirectory.path}\\dlls\\reboot.dll');
|
||||
await rebootDllFile.writeAsBytes(asset.readAsBytesSync());
|
||||
return now.millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
throw Exception("Cannot download reboot.zip, invalid zip: $message");
|
||||
}finally{
|
||||
} finally{
|
||||
if(outputDir != null) {
|
||||
delete(outputDir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user