mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
9.0.4
This commit is contained in:
@@ -57,9 +57,24 @@ void main(List<String> args) async {
|
|||||||
throw Exception("Missing game executable at: ${version.location.path}");
|
throw Exception("Missing game executable at: ${version.location.path}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final serverHost = result["server-host"]?.trim();
|
||||||
|
if(serverHost?.isEmpty == true){
|
||||||
|
throw Exception("Missing host name");
|
||||||
|
}
|
||||||
|
|
||||||
|
final serverPort = result["server-port"]?.trim();
|
||||||
|
if(serverPort?.isEmpty == true){
|
||||||
|
throw Exception("Missing port");
|
||||||
|
}
|
||||||
|
|
||||||
|
final serverPortNumber = serverPort == null ? null : int.tryParse(serverPort);
|
||||||
|
if(serverPort != null && serverPortNumber == null){
|
||||||
|
throw Exception("Invalid port, use only numbers");
|
||||||
|
}
|
||||||
|
|
||||||
var started = await startServerCli(
|
var started = await startServerCli(
|
||||||
result["server-host"],
|
serverHost,
|
||||||
result["server-port"],
|
serverPortNumber,
|
||||||
ServerType.values.firstWhere((element) => element.name == result["server-type"])
|
ServerType.values.firstWhere((element) => element.name == result["server-type"])
|
||||||
);
|
);
|
||||||
if(!started){
|
if(!started){
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ Future<void> startGame() async {
|
|||||||
|
|
||||||
|
|
||||||
Future<void> _startLauncherProcess(FortniteVersion dummyVersion) async {
|
Future<void> _startLauncherProcess(FortniteVersion dummyVersion) async {
|
||||||
if (dummyVersion.launcher == null) {
|
if (dummyVersion.launcherExecutable == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_launcherProcess = await Process.start(dummyVersion.launcher!.path, []);
|
_launcherProcess = await Process.start(dummyVersion.launcherExecutable!.path, []);
|
||||||
suspend(_launcherProcess!.pid);
|
suspend(_launcherProcess!.pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import 'dart:io';
|
|||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_common/src/util/authenticator.dart' as server;
|
import 'package:reboot_common/src/util/authenticator.dart' as server;
|
||||||
|
|
||||||
Future<bool> startServerCli(String? host, String? port, ServerType type) async {
|
Future<bool> startServerCli(String? host, int? port, ServerType type) async {
|
||||||
stdout.writeln("Starting backend server...");
|
stdout.writeln("Starting backend server...");
|
||||||
switch(type){
|
switch(type){
|
||||||
case ServerType.local:
|
case ServerType.local:
|
||||||
var result = await pingAuthenticator(host ?? kDefaultAuthenticatorHost, port ?? kDefaultAuthenticatorPort.toString());
|
var result = await pingAuthenticator(host ?? kDefaultAuthenticatorHost, port ?? kDefaultAuthenticatorPort);
|
||||||
if(result == null){
|
if(result == null){
|
||||||
throw Exception("Local backend server is not running");
|
throw Exception("Local backend server is not running");
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ Future<bool> startServerCli(String? host, String? port, ServerType type) async {
|
|||||||
case ServerType.embedded:
|
case ServerType.embedded:
|
||||||
stdout.writeln("Starting an embedded server...");
|
stdout.writeln("Starting an embedded server...");
|
||||||
await server.startEmbeddedAuthenticator(false);
|
await server.startEmbeddedAuthenticator(false);
|
||||||
var result = await pingAuthenticator(host ?? kDefaultAuthenticatorHost, port ?? kDefaultAuthenticatorPort.toString());
|
var result = await pingAuthenticator(host ?? kDefaultAuthenticatorHost, port ?? kDefaultAuthenticatorPort);
|
||||||
if(result == null){
|
if(result == null){
|
||||||
throw Exception("Cannot start embedded server");
|
throw Exception("Cannot start embedded server");
|
||||||
}
|
}
|
||||||
@@ -37,21 +37,7 @@ Future<bool> startServerCli(String? host, String? port, ServerType type) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HttpServer?> _changeReverseProxyState(String host, String port) async {
|
Future<HttpServer?> _changeReverseProxyState(String host, int 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{
|
try{
|
||||||
var uri = await pingAuthenticator(host, port);
|
var uri = await pingAuthenticator(host, port);
|
||||||
if(uri == null){
|
if(uri == null){
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export 'package:reboot_common/src/model/server_result.dart';
|
|||||||
export 'package:reboot_common/src/model/server_type.dart';
|
export 'package:reboot_common/src/model/server_type.dart';
|
||||||
export 'package:reboot_common/src/model/update_status.dart';
|
export 'package:reboot_common/src/model/update_status.dart';
|
||||||
export 'package:reboot_common/src/model/update_timer.dart';
|
export 'package:reboot_common/src/model/update_timer.dart';
|
||||||
|
export 'package:reboot_common/src/model/process.dart';
|
||||||
export 'package:reboot_common/src/util/authenticator.dart';
|
export 'package:reboot_common/src/util/authenticator.dart';
|
||||||
export 'package:reboot_common/src/util/build.dart';
|
export 'package:reboot_common/src/util/build.dart';
|
||||||
export 'package:reboot_common/src/util/dll.dart';
|
export 'package:reboot_common/src/util/dll.dart';
|
||||||
|
|||||||
23
common/lib/src/model/process.dart
Normal file
23
common/lib/src/model/process.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
class Win32Process {
|
||||||
|
final int pid;
|
||||||
|
final Stream<String> stdOutput;
|
||||||
|
final Stream<String> errorOutput;
|
||||||
|
|
||||||
|
Win32Process({
|
||||||
|
required this.pid,
|
||||||
|
required this.stdOutput,
|
||||||
|
required this.errorOutput
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrimitiveWin32Process {
|
||||||
|
final int pid;
|
||||||
|
final int stdOutputHandle;
|
||||||
|
final int errorOutputHandle;
|
||||||
|
|
||||||
|
PrimitiveWin32Process({
|
||||||
|
required this.pid,
|
||||||
|
required this.stdOutputHandle,
|
||||||
|
required this.errorOutputHandle
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,17 +7,15 @@ import 'package:shelf_proxy/shelf_proxy.dart';
|
|||||||
final authenticatorDirectory = Directory("${assetsDirectory.path}\\authenticator");
|
final authenticatorDirectory = Directory("${assetsDirectory.path}\\authenticator");
|
||||||
final authenticatorStartExecutable = File("${authenticatorDirectory.path}\\lawinserver.exe");
|
final authenticatorStartExecutable = File("${authenticatorDirectory.path}\\lawinserver.exe");
|
||||||
|
|
||||||
Future<int> startEmbeddedAuthenticator(bool detached) async => startBackgroundProcess(
|
Future<Win32Process> startEmbeddedAuthenticator(bool detached) async => startProcess(
|
||||||
executable: authenticatorStartExecutable,
|
executable: authenticatorStartExecutable,
|
||||||
window: detached
|
window: detached,
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<HttpServer> startRemoteAuthenticatorProxy(Uri uri) async {
|
Future<HttpServer> startRemoteAuthenticatorProxy(Uri uri) async => await serve(proxyHandler(uri), kDefaultAuthenticatorHost, kDefaultAuthenticatorPort);
|
||||||
print("CALLED: $uri");
|
|
||||||
return await serve(proxyHandler(uri), kDefaultAuthenticatorHost, kDefaultAuthenticatorPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> isAuthenticatorPortFree() async => await pingAuthenticator(kDefaultAuthenticatorHost, kDefaultAuthenticatorPort.toString()) == null;
|
Future<bool> isAuthenticatorPortFree() async => await pingAuthenticator(kDefaultAuthenticatorHost, kDefaultAuthenticatorPort) == null;
|
||||||
|
|
||||||
Future<bool> freeAuthenticatorPort() async {
|
Future<bool> freeAuthenticatorPort() async {
|
||||||
await killProcessByPort(kDefaultAuthenticatorPort);
|
await killProcessByPort(kDefaultAuthenticatorPort);
|
||||||
@@ -29,14 +27,14 @@ Future<bool> freeAuthenticatorPort() async {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uri?> pingAuthenticator(String host, String port, [bool https=false]) async {
|
Future<Uri?> pingAuthenticator(String host, int port, [bool https=false]) async {
|
||||||
var hostName = _getHostName(host);
|
var hostName = _getHostName(host);
|
||||||
var declaredScheme = _getScheme(host);
|
var declaredScheme = _getScheme(host);
|
||||||
try{
|
try{
|
||||||
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: port,
|
||||||
path: "unknown"
|
path: "unknown"
|
||||||
);
|
);
|
||||||
var client = HttpClient()
|
var client = HttpClient()
|
||||||
|
|||||||
@@ -5,18 +5,35 @@ import 'dart:isolate';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:dio/io.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
|
|
||||||
const String kStopBuildDownloadSignal = "kill";
|
const String kStopBuildDownloadSignal = "kill";
|
||||||
|
|
||||||
final Dio _dio = Dio();
|
final Dio _dio = _buildDioInstance();
|
||||||
|
Dio _buildDioInstance() {
|
||||||
|
final dio = Dio();
|
||||||
|
final httpClientAdapter = dio.httpClientAdapter as IOHttpClientAdapter;
|
||||||
|
httpClientAdapter.createHttpClient = () {
|
||||||
|
final client = HttpClient();
|
||||||
|
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
return dio;
|
||||||
|
}
|
||||||
|
|
||||||
final String _archiveSourceUrl = "https://raw.githubusercontent.com/simplyblk/Fortnitebuilds/main/README.md";
|
final String _archiveSourceUrl = "https://raw.githubusercontent.com/simplyblk/Fortnitebuilds/main/README.md";
|
||||||
final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$");
|
final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$");
|
||||||
const String _manifestSourceUrl = "https://manifest.fnbuilds.services";
|
const String _manifestSourceUrl = "https://manifest.fnbuilds.services";
|
||||||
const int _maxDownloadErrors = 30;
|
const int _maxDownloadErrors = 30;
|
||||||
|
|
||||||
Future<List<FortniteBuild>> fetchBuilds(ignored) async {
|
Future<List<FortniteBuild>> fetchBuilds(ignored) async {
|
||||||
|
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () =>
|
||||||
|
HttpClient()
|
||||||
|
..badCertificateCallback =
|
||||||
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
|
||||||
final results = await Future.wait([_fetchManifestBuilds(), _fetchArchiveBuilds()]);
|
final results = await Future.wait([_fetchManifestBuilds(), _fetchArchiveBuilds()]);
|
||||||
final data = <FortniteBuild>[];
|
final data = <FortniteBuild>[];
|
||||||
for(final result in results) {
|
for(final result in results) {
|
||||||
@@ -108,12 +125,6 @@ Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
|
|||||||
final response = _downloadArchive(options, tempFile, startTime);
|
final response = _downloadArchive(options, tempFile, startTime);
|
||||||
await Future.any([stopped.future, response]);
|
await Future.any([stopped.future, response]);
|
||||||
if(!stopped.isCompleted) {
|
if(!stopped.isCompleted) {
|
||||||
var awaitedResponse = await response;
|
|
||||||
if (!awaitedResponse.statusCode.toString().startsWith("20")) {
|
|
||||||
options.port.send("Erroneous status code: ${awaitedResponse.statusCode}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _extractArchive(stopped, extension, tempFile, options);
|
await _extractArchive(stopped, extension, tempFile, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,9 +222,23 @@ Future<Response> _downloadArchive(FortniteBuildDownloadOptions options, File tem
|
|||||||
},
|
},
|
||||||
deleteOnError: false,
|
deleteOnError: false,
|
||||||
options: Options(
|
options: Options(
|
||||||
headers: byteStart == null ? null : {
|
validateStatus: (statusCode) {
|
||||||
"Range": "bytes=${byteStart}-"
|
if(statusCode == 200) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(statusCode == 403 || statusCode == 503) {
|
||||||
|
throw Exception("The connection was denied: your firewall might be blocking the download");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception("The build downloader is not available right now");
|
||||||
|
},
|
||||||
|
headers: byteStart == null || byteStart <= 0 ? {
|
||||||
|
"Cookie": "_c_t_c=1"
|
||||||
|
} : {
|
||||||
|
"Cookie": "_c_t_c=1",
|
||||||
|
"Range": "bytes=${byteStart}-"
|
||||||
|
},
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}catch(error) {
|
}catch(error) {
|
||||||
@@ -226,17 +251,28 @@ Future<Response> _downloadArchive(FortniteBuildDownloadOptions options, File tem
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async {
|
Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async {
|
||||||
var startTime = DateTime.now().millisecondsSinceEpoch;
|
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
Process? process;
|
Win32Process? process;
|
||||||
switch (extension.toLowerCase()) {
|
switch (extension.toLowerCase()) {
|
||||||
case ".zip":
|
case ".zip":
|
||||||
process = await Process.start(
|
final sevenZip = File("${assetsDirectory.path}\\build\\7zip.exe");
|
||||||
"${assetsDirectory.path}\\build\\7zip.exe",
|
if(!sevenZip.existsSync()) {
|
||||||
["a", "-bsp1", '-o"${options.destination.path}"', tempFile.path]
|
throw "Corrupted installation: missing 7zip.exe";
|
||||||
|
}
|
||||||
|
|
||||||
|
process = await startProcess(
|
||||||
|
executable: sevenZip,
|
||||||
|
args: [
|
||||||
|
"x",
|
||||||
|
"-bsp1",
|
||||||
|
'-o"${options.destination.path}"',
|
||||||
|
"-y",
|
||||||
|
'"${tempFile.path}"'
|
||||||
|
],
|
||||||
);
|
);
|
||||||
process.stdout.listen((bytes) {
|
process.stdOutput.listen((data) {
|
||||||
|
print(data);
|
||||||
final now = DateTime.now().millisecondsSinceEpoch;
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
final data = utf8.decode(bytes);
|
|
||||||
if(data == "Everything is Ok") {
|
if(data == "Everything is Ok") {
|
||||||
options.port.send(FortniteBuildDownloadProgress(100, 0, true));
|
options.port.send(FortniteBuildDownloadProgress(100, 0, true));
|
||||||
return;
|
return;
|
||||||
@@ -252,42 +288,45 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case ".rar":
|
case ".rar":
|
||||||
process = await Process.start(
|
final winrar = File("${assetsDirectory.path}\\build\\winrar.exe");
|
||||||
"${assetsDirectory.path}\\build\\winrar.exe",
|
if(!winrar.existsSync()) {
|
||||||
["x", "-o+", tempFile.path, "*.*", options.destination.path]
|
throw "Corrupted installation: missing winrar.exe";
|
||||||
|
}
|
||||||
|
|
||||||
|
process = await startProcess(
|
||||||
|
executable: winrar,
|
||||||
|
args: [
|
||||||
|
"x",
|
||||||
|
"-o+",
|
||||||
|
tempFile.path,
|
||||||
|
"*.*",
|
||||||
|
options.destination.path
|
||||||
|
]
|
||||||
);
|
);
|
||||||
process.stdout.listen((event) {
|
process.stdOutput.listen((data) {
|
||||||
|
print(data);
|
||||||
final now = DateTime.now().millisecondsSinceEpoch;
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
final data = utf8.decode(event);
|
data.replaceAll("\r", "").replaceAll("\b", "").trim();
|
||||||
data.replaceAll("\r", "")
|
if(data == "All OK") {
|
||||||
.replaceAll("\b", "")
|
options.port.send(FortniteBuildDownloadProgress(100, 0, true));
|
||||||
.trim()
|
return;
|
||||||
.split("\n")
|
}
|
||||||
.forEach((entry) {
|
|
||||||
if(entry == "All OK") {
|
|
||||||
options.port.send(FortniteBuildDownloadProgress(100, 0, true));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final element = _rarProgressRegex.firstMatch(entry)?.group(1);
|
final element = _rarProgressRegex.firstMatch(data)?.group(1);
|
||||||
if(element == null) {
|
if(element == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final percentage = int.parse(element).toDouble();
|
final percentage = int.parse(element).toDouble();
|
||||||
_onProgress(startTime, now, percentage, true, options);
|
_onProgress(startTime, now, percentage, true, options);
|
||||||
});
|
|
||||||
});
|
|
||||||
process.stderr.listen((event) {
|
|
||||||
final data = utf8.decode(event);
|
|
||||||
options.port.send(data);
|
|
||||||
});
|
});
|
||||||
|
process.errorOutput.listen((data) => options.port.send(data));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError("Unexpected file extension: $extension}");
|
throw ArgumentError("Unexpected file extension: $extension}");
|
||||||
}
|
}
|
||||||
|
|
||||||
await Future.any([stopped.future, process.exitCode]);
|
await Future.any([stopped.future, watchProcess(process.pid)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onProgress(int startTime, int now, double percentage, bool extracting, FortniteBuildDownloadOptions options) {
|
void _onProgress(int startTime, int now, double percentage, bool extracting, FortniteBuildDownloadOptions options) {
|
||||||
|
|||||||
@@ -19,9 +19,13 @@ Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force =
|
|||||||
|
|
||||||
Future<void> downloadCriticalDll(String name, String outputPath) async {
|
Future<void> downloadCriticalDll(String name, String outputPath) async {
|
||||||
final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/tree/master/gui/assets/dlls/$name"));
|
final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/tree/master/gui/assets/dlls/$name"));
|
||||||
|
if(response.statusCode != 200) {
|
||||||
|
throw Exception("Cannot download $name: status code ${response.statusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
final output = File(outputPath);
|
final output = File(outputPath);
|
||||||
await output.parent.create(recursive: true);
|
await output.parent.create(recursive: true);
|
||||||
await output.writeAsBytes(response.bodyBytes);
|
await output.writeAsBytes(response.bodyBytes, flush: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> downloadRebootDll(String url) async {
|
Future<int> downloadRebootDll(String url) async {
|
||||||
@@ -29,12 +33,16 @@ Future<int> downloadRebootDll(String url) async {
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse(url));
|
final response = await http.get(Uri.parse(url));
|
||||||
|
if(response.statusCode != 200) {
|
||||||
|
throw Exception("Cannot download reboot.zip: status code ${response.statusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
outputDir = await installationDirectory.createTemp("reboot_out");
|
outputDir = await installationDirectory.createTemp("reboot_out");
|
||||||
final tempZip = File("${outputDir.path}\\reboot.zip");
|
final tempZip = File("${outputDir.path}\\reboot.zip");
|
||||||
await tempZip.writeAsBytes(response.bodyBytes);
|
await tempZip.writeAsBytes(response.bodyBytes, flush: true);
|
||||||
await extractFileToDisk(tempZip.path, outputDir.path);
|
await extractFileToDisk(tempZip.path, outputDir.path);
|
||||||
final rebootDll = File(outputDir.listSync().firstWhere((element) => path.extension(element.path) == ".dll").path);
|
final rebootDll = File(outputDir.listSync().firstWhere((element) => path.extension(element.path) == ".dll").path);
|
||||||
await rebootDllFile.writeAsBytes(await rebootDll.readAsBytes());
|
await rebootDllFile.writeAsBytes(await rebootDll.readAsBytes(), flush: true);
|
||||||
return now.millisecondsSinceEpoch;
|
return now.millisecondsSinceEpoch;
|
||||||
} finally{
|
} finally{
|
||||||
if(outputDir != null) {
|
if(outputDir != null) {
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ String? _lastIp;
|
|||||||
String? _lastPort;
|
String? _lastPort;
|
||||||
Semaphore _semaphore = Semaphore();
|
Semaphore _semaphore = Semaphore();
|
||||||
|
|
||||||
Future<int> startEmbeddedMatchmaker(bool detached) async => startBackgroundProcess(
|
Future<Win32Process> startEmbeddedMatchmaker(bool detached) async => startProcess(
|
||||||
executable: matchmakerStartExecutable,
|
executable: matchmakerStartExecutable,
|
||||||
window: detached
|
window: detached,
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Stream<String?> watchMatchmakingIp() async* {
|
Stream<String?> watchMatchmakingIp() async* {
|
||||||
@@ -74,7 +75,7 @@ Future<void> writeMatchmakingIp(String text) async {
|
|||||||
await matchmakerConfigFile.writeAsString(config.toString(), flush: true);
|
await matchmakerConfigFile.writeAsString(config.toString(), flush: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isMatchmakerPortFree() async => await pingMatchmaker(kDefaultMatchmakerHost, kDefaultMatchmakerPort.toString()) == null;
|
Future<bool> isMatchmakerPortFree() async => await pingMatchmaker(kDefaultMatchmakerHost, kDefaultMatchmakerPort) == null;
|
||||||
|
|
||||||
Future<bool> freeMatchmakerPort() async {
|
Future<bool> freeMatchmakerPort() async {
|
||||||
await killProcessByPort(kDefaultMatchmakerPort);
|
await killProcessByPort(kDefaultMatchmakerPort);
|
||||||
@@ -86,14 +87,14 @@ Future<bool> freeMatchmakerPort() async {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uri?> pingMatchmaker(String host, String port, [bool wss=false]) async {
|
Future<Uri?> pingMatchmaker(String host, int port, [bool wss=false]) async {
|
||||||
var hostName = _getHostName(host);
|
var hostName = _getHostName(host);
|
||||||
var declaredScheme = _getScheme(host);
|
var declaredScheme = _getScheme(host);
|
||||||
try{
|
try{
|
||||||
var uri = Uri(
|
var uri = Uri(
|
||||||
scheme: declaredScheme ?? (wss ? "wss" : "ws"),
|
scheme: declaredScheme ?? (wss ? "wss" : "ws"),
|
||||||
host: hostName,
|
host: hostName,
|
||||||
port: int.parse(port)
|
port: port
|
||||||
);
|
);
|
||||||
var completer = Completer<bool>();
|
var completer = Completer<bool>();
|
||||||
var socket = await WebSocket.connect(uri.toString());
|
var socket = await WebSocket.connect(uri.toString());
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ extension FortniteVersionExtension on FortniteVersion {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
File? get launcher => findExecutable(location, "FortniteLauncher.exe");
|
File? get launcherExecutable => findExecutable(location, "FortniteLauncher.exe");
|
||||||
|
|
||||||
File? get eacExecutable => findExecutable(location, "FortniteClient-Win64-Shipping_EAC.exe");
|
File? get eacExecutable => findExecutable(location, "FortniteClient-Win64-Shipping_EAC.exe");
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
// ignore_for_file: non_constant_identifier_names
|
// ignore_for_file: non_constant_identifier_names
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:reboot_common/src/model/process.dart';
|
||||||
import 'package:win32/win32.dart';
|
import 'package:win32/win32.dart';
|
||||||
|
|
||||||
import '../constant/game.dart';
|
import '../constant/game.dart';
|
||||||
@@ -30,15 +32,16 @@ final _CreateRemoteThread = _kernel32.lookupFunction<
|
|||||||
Pointer lpParameter,
|
Pointer lpParameter,
|
||||||
int dwCreationFlags,
|
int dwCreationFlags,
|
||||||
Pointer<Uint32> lpThreadId)>('CreateRemoteThread');
|
Pointer<Uint32> lpThreadId)>('CreateRemoteThread');
|
||||||
|
const chunkSize = 1024;
|
||||||
|
|
||||||
Future<void> injectDll(int pid, String dll) async {
|
Future<void> injectDll(int pid, String dll) async {
|
||||||
var process = OpenProcess(
|
final process = OpenProcess(
|
||||||
0x43A,
|
0x43A,
|
||||||
0,
|
0,
|
||||||
pid
|
pid
|
||||||
);
|
);
|
||||||
|
|
||||||
var processAddress = GetProcAddress(
|
final processAddress = GetProcAddress(
|
||||||
GetModuleHandle("KERNEL32".toNativeUtf16()),
|
GetModuleHandle("KERNEL32".toNativeUtf16()),
|
||||||
"LoadLibraryA".toNativeUtf8()
|
"LoadLibraryA".toNativeUtf8()
|
||||||
);
|
);
|
||||||
@@ -47,7 +50,7 @@ Future<void> injectDll(int pid, String dll) async {
|
|||||||
throw Exception("Cannot get process address for pid $pid");
|
throw Exception("Cannot get process address for pid $pid");
|
||||||
}
|
}
|
||||||
|
|
||||||
var dllAddress = VirtualAllocEx(
|
final dllAddress = VirtualAllocEx(
|
||||||
process,
|
process,
|
||||||
nullptr,
|
nullptr,
|
||||||
dll.length + 1,
|
dll.length + 1,
|
||||||
@@ -55,7 +58,7 @@ Future<void> injectDll(int pid, String dll) async {
|
|||||||
0x4
|
0x4
|
||||||
);
|
);
|
||||||
|
|
||||||
var writeMemoryResult = WriteProcessMemory(
|
final writeMemoryResult = WriteProcessMemory(
|
||||||
process,
|
process,
|
||||||
dllAddress,
|
dllAddress,
|
||||||
dll.toNativeUtf8(),
|
dll.toNativeUtf8(),
|
||||||
@@ -67,7 +70,7 @@ Future<void> injectDll(int pid, String dll) async {
|
|||||||
throw Exception("Memory write failed");
|
throw Exception("Memory write failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
var createThreadResult = _CreateRemoteThread(
|
final createThreadResult = _CreateRemoteThread(
|
||||||
process,
|
process,
|
||||||
nullptr,
|
nullptr,
|
||||||
0,
|
0,
|
||||||
@@ -81,90 +84,152 @@ Future<void> injectDll(int pid, String dll) async {
|
|||||||
throw Exception("Thread creation failed");
|
throw Exception("Thread creation failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
var closeResult = CloseHandle(process);
|
final closeResult = CloseHandle(process);
|
||||||
if(closeResult != 1){
|
if(closeResult != 1){
|
||||||
throw Exception("Cannot close handle");
|
throw Exception("Cannot close handle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool runElevatedProcess(String executable, String args) {
|
void _startProcess(_ProcessParameters params) {
|
||||||
final shellInput = calloc<SHELLEXECUTEINFO>();
|
final args = params.args;
|
||||||
shellInput.ref.lpFile = executable.toNativeUtf16();
|
final port = params.port;
|
||||||
shellInput.ref.lpParameters = args.toNativeUtf16();
|
final concatenatedArgs = args == null ? "" : " ${args.join(" ")}";
|
||||||
shellInput.ref.nShow = SW_HIDE;
|
final command = params.window ? 'cmd.exe /k ""${params.executable.path}"$concatenatedArgs"' : '"${params.executable.path}"$concatenatedArgs';
|
||||||
shellInput.ref.fMask = ES_AWAYMODE_REQUIRED;
|
print(command);
|
||||||
shellInput.ref.lpVerb = "runas".toNativeUtf16();
|
final processInfo = calloc<PROCESS_INFORMATION>();
|
||||||
shellInput.ref.cbSize = sizeOf<SHELLEXECUTEINFO>();
|
final lpStartupInfo = calloc<STARTUPINFO>();
|
||||||
final result = ShellExecuteEx(shellInput) == 1;
|
lpStartupInfo.ref.cb = sizeOf<STARTUPINFO>();
|
||||||
free(shellInput);
|
lpStartupInfo.ref.dwFlags |= STARTF_USESTDHANDLES;
|
||||||
return result;
|
final securityAttributes = calloc<SECURITY_ATTRIBUTES>();
|
||||||
}
|
securityAttributes.ref.nLength = sizeOf<SECURITY_ATTRIBUTES>();
|
||||||
|
securityAttributes.ref.bInheritHandle = TRUE;
|
||||||
void _startBackgroundProcess(_BackgroundProcessParameters params) {
|
final hStdOutRead = calloc<HANDLE>();
|
||||||
var args = params.args;
|
final hStdOutWrite = calloc<HANDLE>();
|
||||||
var concatenatedArgs = args == null ? "" : " ${args.map((entry) => '"$entry"').join(" ")}";
|
final hStdErrRead = calloc<HANDLE>();
|
||||||
var executablePath = TEXT('cmd.exe /k "${params.executable.path}"$concatenatedArgs');
|
final hStdErrWrite = calloc<HANDLE>();
|
||||||
var startupInfo = calloc<STARTUPINFO>();
|
if (CreatePipe(hStdOutRead, hStdOutWrite, securityAttributes, 0) == 0 || CreatePipe(hStdErrRead, hStdErrWrite, securityAttributes, 0) == 0) {
|
||||||
var processInfo = calloc<PROCESS_INFORMATION>();
|
final error = GetLastError();
|
||||||
var windowFlag = params.window ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW;
|
port.send("Cannot create process pipe: $error");
|
||||||
var success = CreateProcess(
|
return;
|
||||||
nullptr,
|
}
|
||||||
executablePath,
|
|
||||||
nullptr,
|
if(SetHandleInformation(hStdOutRead.value, HANDLE_FLAG_INHERIT, 0) == 0 || SetHandleInformation(hStdErrRead.value, HANDLE_FLAG_INHERIT, 0) == 0) {
|
||||||
nullptr,
|
final error = GetLastError();
|
||||||
FALSE,
|
port.send("Cannot set process pipe information: $error");
|
||||||
NORMAL_PRIORITY_CLASS | windowFlag | CREATE_NEW_PROCESS_GROUP,
|
|
||||||
nullptr,
|
|
||||||
TEXT(params.executable.parent.path),
|
|
||||||
startupInfo,
|
|
||||||
processInfo
|
|
||||||
);
|
|
||||||
if (success == 0) {
|
|
||||||
var error = GetLastError();
|
|
||||||
params.port.send("Cannot start process: $error");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pid = processInfo.ref.dwProcessId;
|
lpStartupInfo.ref.hStdOutput = hStdOutWrite.value;
|
||||||
free(startupInfo);
|
lpStartupInfo.ref.hStdError = hStdErrWrite.value;
|
||||||
|
final success = CreateProcess(
|
||||||
|
nullptr,
|
||||||
|
TEXT(command),
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
TRUE,
|
||||||
|
NORMAL_PRIORITY_CLASS | (params.window ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW) | CREATE_NEW_PROCESS_GROUP,
|
||||||
|
nullptr,
|
||||||
|
TEXT(params.executable.parent.path),
|
||||||
|
lpStartupInfo,
|
||||||
|
processInfo
|
||||||
|
);
|
||||||
|
if (success == 0) {
|
||||||
|
final error = GetLastError();
|
||||||
|
port.send("Cannot start process: $error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(processInfo.ref.hProcess);
|
||||||
|
CloseHandle(processInfo.ref.hThread);
|
||||||
|
CloseHandle(hStdOutWrite.value);
|
||||||
|
CloseHandle(hStdErrWrite.value);
|
||||||
|
final pid = processInfo.ref.dwProcessId;
|
||||||
|
free(lpStartupInfo);
|
||||||
free(processInfo);
|
free(processInfo);
|
||||||
params.port.send(pid);
|
port.send(PrimitiveWin32Process(
|
||||||
|
pid: pid,
|
||||||
|
stdOutputHandle: hStdOutRead.value,
|
||||||
|
errorOutputHandle: hStdErrRead.value
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BackgroundProcessParameters {
|
class _PipeReaderParams {
|
||||||
|
final int handle;
|
||||||
|
final SendPort port;
|
||||||
|
|
||||||
|
_PipeReaderParams(this.handle, this.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pipeToStreamChannelled(_PipeReaderParams params) {
|
||||||
|
final buf = calloc<Uint8>(chunkSize);
|
||||||
|
while(true) {
|
||||||
|
final bytesReadPtr = calloc<Uint32>();
|
||||||
|
final success = ReadFile(params.handle, buf, chunkSize, bytesReadPtr, nullptr);
|
||||||
|
if (success == FALSE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final bytesRead = bytesReadPtr.value;
|
||||||
|
if (bytesRead == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final lines = utf8.decode(buf.asTypedList(bytesRead)).split('\n');
|
||||||
|
for(final line in lines) {
|
||||||
|
params.port.send(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(params.handle);
|
||||||
|
free(buf);
|
||||||
|
Isolate.current.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<String> _pipeToStream(int pipeHandle) {
|
||||||
|
final port = ReceivePort();
|
||||||
|
Isolate.spawn(
|
||||||
|
_pipeToStreamChannelled,
|
||||||
|
_PipeReaderParams(pipeHandle, port.sendPort)
|
||||||
|
);
|
||||||
|
return port.map((event) => event as String);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProcessParameters {
|
||||||
File executable;
|
File executable;
|
||||||
List<String>? args;
|
List<String>? args;
|
||||||
bool window;
|
bool window;
|
||||||
SendPort port;
|
SendPort port;
|
||||||
|
|
||||||
_BackgroundProcessParameters(this.executable, this.args, this.window, this.port);
|
_ProcessParameters(this.executable, this.args, this.window, this.port);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> startBackgroundProcess({required File executable, List<String>? args, bool window = false}) async {
|
Future<Win32Process> startProcess({required File executable, List<String>? args, bool output = true, bool window = false}) async {
|
||||||
var completer = Completer<int>();
|
final completer = Completer<Win32Process>();
|
||||||
var port = ReceivePort();
|
final port = ReceivePort();
|
||||||
port.listen((message) => message is int ? completer.complete(message) : completer.completeError(message));
|
port.listen((message) {
|
||||||
var isolate = await Isolate.spawn(
|
if(message is PrimitiveWin32Process) {
|
||||||
_startBackgroundProcess,
|
completer.complete(Win32Process(
|
||||||
_BackgroundProcessParameters(executable, args, window, port.sendPort),
|
pid: message.pid,
|
||||||
|
stdOutput: _pipeToStream(message.stdOutputHandle),
|
||||||
|
errorOutput: _pipeToStream(message.errorOutputHandle)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
completer.completeError(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Isolate.spawn(
|
||||||
|
_startProcess,
|
||||||
|
_ProcessParameters(executable, args, window, port.sendPort),
|
||||||
errorsAreFatal: true
|
errorsAreFatal: true
|
||||||
);
|
);
|
||||||
var result = await completer.future;
|
return await completer.future;
|
||||||
isolate.kill(priority: Isolate.immediate);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int _NtResumeProcess(int hWnd) {
|
final _NtResumeProcess = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
|
||||||
final function = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
|
int Function(int hWnd)>('NtResumeProcess');
|
||||||
int Function(int hWnd)>('NtResumeProcess');
|
|
||||||
return function(hWnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
int _NtSuspendProcess(int hWnd) {
|
final _NtSuspendProcess = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
|
||||||
final function = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
|
int Function(int hWnd)>('NtSuspendProcess');
|
||||||
int Function(int hWnd)>('NtSuspendProcess');
|
|
||||||
return function(hWnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool suspend(int pid) {
|
bool suspend(int pid) {
|
||||||
final processHandle = OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, pid);
|
final processHandle = OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, pid);
|
||||||
@@ -214,7 +279,7 @@ List<String> createRebootArgs(String username, String password, bool host, bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
password = password.isNotEmpty ? password : "Rebooted";
|
password = password.isNotEmpty ? password : "Rebooted";
|
||||||
var args = [
|
final args = [
|
||||||
"-epicapp=Fortnite",
|
"-epicapp=Fortnite",
|
||||||
"-epicenv=Prod",
|
"-epicenv=Prod",
|
||||||
"-epiclocale=en-us",
|
"-epiclocale=en-us",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class AuthenticatorController extends ServerController {
|
|||||||
String get defaultHost => kDefaultAuthenticatorHost;
|
String get defaultHost => kDefaultAuthenticatorHost;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get defaultPort => kDefaultAuthenticatorPort.toString();
|
int get defaultPort => kDefaultAuthenticatorPort;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> get isPortFree => isAuthenticatorPortFree();
|
Future<bool> get isPortFree => isAuthenticatorPortFree();
|
||||||
@@ -28,8 +28,8 @@ class AuthenticatorController extends ServerController {
|
|||||||
RebootPageType get pageType => RebootPageType.authenticator;
|
RebootPageType get pageType => RebootPageType.authenticator;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> startEmbeddedInternal() => startEmbeddedAuthenticator(detached.value);
|
Future<Win32Process> startEmbeddedInternal() => startEmbeddedAuthenticator(detached.value);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uri?> pingServer(String host, String port) => pingAuthenticator(host, port);
|
Future<Uri?> pingServer(String host, int port) => pingAuthenticator(host, port);
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ class HostingController extends GetxController {
|
|||||||
description.addListener(() => _storage.write("description", description.text));
|
description.addListener(() => _storage.write("description", description.text));
|
||||||
password = TextEditingController(text: _storage.read("password") ?? "");
|
password = TextEditingController(text: _storage.read("password") ?? "");
|
||||||
password.addListener(() => _storage.write("password", password.text));
|
password.addListener(() => _storage.write("password", password.text));
|
||||||
discoverable = RxBool(_storage.read("discoverable") ?? true);
|
discoverable = RxBool(_storage.read("discoverable") ?? false);
|
||||||
discoverable.listen((value) => _storage.write("discoverable", value));
|
discoverable.listen((value) => _storage.write("discoverable", value));
|
||||||
headless = RxBool(_storage.read("headless") ?? true);
|
headless = RxBool(_storage.read("headless") ?? true);
|
||||||
headless.listen((value) => _storage.write("headless", value));
|
headless.listen((value) => _storage.write("headless", value));
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class MatchmakerController extends ServerController {
|
|||||||
String get defaultHost => kDefaultMatchmakerHost;
|
String get defaultHost => kDefaultMatchmakerHost;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get defaultPort => kDefaultMatchmakerPort.toString();
|
int get defaultPort => kDefaultMatchmakerPort;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> get isPortFree => isMatchmakerPortFree();
|
Future<bool> get isPortFree => isMatchmakerPortFree();
|
||||||
@@ -57,8 +57,8 @@ class MatchmakerController extends ServerController {
|
|||||||
RebootPageType get pageType => RebootPageType.matchmaker;
|
RebootPageType get pageType => RebootPageType.matchmaker;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> startEmbeddedInternal() => startEmbeddedMatchmaker(detached.value);
|
Future<Win32Process> startEmbeddedInternal() => startEmbeddedMatchmaker(detached.value);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uri?> pingServer(String host, String port) => pingMatchmaker(host, port);
|
Future<Uri?> pingServer(String host, int port) => pingMatchmaker(host, port);
|
||||||
}
|
}
|
||||||
@@ -51,9 +51,9 @@ abstract class ServerController extends GetxController {
|
|||||||
|
|
||||||
String get defaultHost;
|
String get defaultHost;
|
||||||
|
|
||||||
String get defaultPort;
|
int get defaultPort;
|
||||||
|
|
||||||
Future<Uri?> pingServer(String host, String port);
|
Future<Uri?> pingServer(String host, int port);
|
||||||
|
|
||||||
Future<bool> get isPortFree;
|
Future<bool> get isPortFree;
|
||||||
|
|
||||||
@@ -64,17 +64,17 @@ abstract class ServerController extends GetxController {
|
|||||||
Future<bool> freePort();
|
Future<bool> freePort();
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Future<int> startEmbeddedInternal();
|
Future<Win32Process> startEmbeddedInternal();
|
||||||
|
|
||||||
void reset() async {
|
void reset() async {
|
||||||
type.value = ServerType.values.elementAt(0);
|
type.value = ServerType.values.elementAt(0);
|
||||||
for (var type in ServerType.values) {
|
for (final type in ServerType.values) {
|
||||||
storage.write("${type.name}_host", null);
|
storage.write("${type.name}_host", null);
|
||||||
storage.write("${type.name}_port", null);
|
storage.write("${type.name}_port", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
host.text = type.value != ServerType.remote ? defaultHost : "";
|
host.text = type.value != ServerType.remote ? defaultHost : "";
|
||||||
port.text = defaultPort;
|
port.text = defaultPort.toString();
|
||||||
detached.value = false;
|
detached.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,48 +85,48 @@ abstract class ServerController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _readPort() =>
|
String _readPort() =>
|
||||||
storage.read("${type.value.name}_port") ?? defaultPort;
|
storage.read("${type.value.name}_port") ?? defaultPort.toString();
|
||||||
|
|
||||||
Stream<ServerResult> start() async* {
|
Stream<ServerResult> start() async* {
|
||||||
if(started.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(type() != ServerType.local) {
|
|
||||||
started.value = true;
|
|
||||||
yield ServerResult(ServerResultType.starting);
|
|
||||||
}else {
|
|
||||||
started.value = false;
|
|
||||||
if(port != defaultPort) {
|
|
||||||
yield ServerResult(ServerResultType.starting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var host = this.host.text.trim();
|
if(started.value) {
|
||||||
if (host.isEmpty) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final hostData = this.host.text.trim();
|
||||||
|
final portData = this.port.text.trim();
|
||||||
|
if(type() != ServerType.local) {
|
||||||
|
started.value = true;
|
||||||
|
yield ServerResult(ServerResultType.starting);
|
||||||
|
}else {
|
||||||
|
started.value = false;
|
||||||
|
if(portData != defaultPort) {
|
||||||
|
yield ServerResult(ServerResultType.starting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostData.isEmpty) {
|
||||||
yield ServerResult(ServerResultType.missingHostError);
|
yield ServerResult(ServerResultType.missingHostError);
|
||||||
started.value = false;
|
started.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var port = this.port.text.trim();
|
if (portData.isEmpty) {
|
||||||
if (port.isEmpty) {
|
|
||||||
yield ServerResult(ServerResultType.missingPortError);
|
yield ServerResult(ServerResultType.missingPortError);
|
||||||
started.value = false;
|
started.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var portNumber = int.tryParse(port);
|
final portNumber = int.tryParse(portData);
|
||||||
if (portNumber == null) {
|
if (portNumber == null) {
|
||||||
yield ServerResult(ServerResultType.illegalPortError);
|
yield ServerResult(ServerResultType.illegalPortError);
|
||||||
started.value = false;
|
started.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((type() != ServerType.local || port != defaultPort) && await isPortTaken) {
|
if ((type() != ServerType.local || portData != defaultPort) && await isPortTaken) {
|
||||||
yield ServerResult(ServerResultType.freeingPort);
|
yield ServerResult(ServerResultType.freeingPort);
|
||||||
var result = await freePort();
|
final result = await freePort();
|
||||||
yield ServerResult(result ? ServerResultType.freePortSuccess : ServerResultType.freePortError);
|
yield ServerResult(result ? ServerResultType.freePortSuccess : ServerResultType.freePortError);
|
||||||
if(!result) {
|
if(!result) {
|
||||||
started.value = false;
|
started.value = false;
|
||||||
@@ -136,8 +136,15 @@ abstract class ServerController extends GetxController {
|
|||||||
|
|
||||||
switch(type()){
|
switch(type()){
|
||||||
case ServerType.embedded:
|
case ServerType.embedded:
|
||||||
final pid = await startEmbeddedInternal();
|
final process = await startEmbeddedInternal();
|
||||||
watchProcess(pid).then((value) {
|
final processPid = process.pid;
|
||||||
|
if(processPid == null) {
|
||||||
|
yield ServerResult(ServerResultType.startError);
|
||||||
|
started.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
watchProcess(processPid).then((value) {
|
||||||
if(started()) {
|
if(started()) {
|
||||||
started.value = false;
|
started.value = false;
|
||||||
}
|
}
|
||||||
@@ -145,7 +152,7 @@ abstract class ServerController extends GetxController {
|
|||||||
break;
|
break;
|
||||||
case ServerType.remote:
|
case ServerType.remote:
|
||||||
yield ServerResult(ServerResultType.pingingRemote);
|
yield ServerResult(ServerResultType.pingingRemote);
|
||||||
var uriResult = await pingServer(host, port);
|
final uriResult = await pingServer(hostData, portNumber);
|
||||||
if(uriResult == null) {
|
if(uriResult == null) {
|
||||||
yield ServerResult(ServerResultType.pingError);
|
yield ServerResult(ServerResultType.pingError);
|
||||||
started.value = false;
|
started.value = false;
|
||||||
@@ -155,7 +162,7 @@ abstract class ServerController extends GetxController {
|
|||||||
remoteServer = await startRemoteAuthenticatorProxy(uriResult);
|
remoteServer = await startRemoteAuthenticatorProxy(uriResult);
|
||||||
break;
|
break;
|
||||||
case ServerType.local:
|
case ServerType.local:
|
||||||
if(port != defaultPort) {
|
if(portData != defaultPort) {
|
||||||
localServer = await startRemoteAuthenticatorProxy(Uri.parse("http://$defaultHost:$port"));
|
localServer = await startRemoteAuthenticatorProxy(Uri.parse("http://$defaultHost:$port"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +170,7 @@ abstract class ServerController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
yield ServerResult(ServerResultType.pingingLocal);
|
yield ServerResult(ServerResultType.pingingLocal);
|
||||||
var uriResult = await pingServer(defaultHost, defaultPort);
|
final uriResult = await pingServer(defaultHost, defaultPort);
|
||||||
if(uriResult == null) {
|
if(uriResult == null) {
|
||||||
yield ServerResult(ServerResultType.pingError);
|
yield ServerResult(ServerResultType.pingError);
|
||||||
remoteServer?.close(force: true);
|
remoteServer?.close(force: true);
|
||||||
@@ -195,7 +202,7 @@ abstract class ServerController extends GetxController {
|
|||||||
try{
|
try{
|
||||||
switch(type()){
|
switch(type()){
|
||||||
case ServerType.embedded:
|
case ServerType.embedded:
|
||||||
killProcessByPort(int.parse(defaultPort));
|
killProcessByPort(defaultPort);
|
||||||
break;
|
break;
|
||||||
case ServerType.remote:
|
case ServerType.remote:
|
||||||
await remoteServer?.close(force: true);
|
await remoteServer?.close(force: true);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ extension ServerControllerDialog on ServerController {
|
|||||||
var stream = toggle();
|
var stream = toggle();
|
||||||
var completer = Completer<bool>();
|
var completer = Completer<bool>();
|
||||||
worker = stream.listen((event) {
|
worker = stream.listen((event) {
|
||||||
|
print(event.type);
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case ServerResultType.starting:
|
case ServerResultType.starting:
|
||||||
showInfoBar(
|
showInfoBar(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ extension GameInstanceWatcher on GameInstance {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
observerPid = await startBackgroundProcess(
|
final process = await startProcess(
|
||||||
executable: _executable,
|
executable: _executable,
|
||||||
args: [
|
args: [
|
||||||
hostingController.uuid,
|
hostingController.uuid,
|
||||||
@@ -36,8 +36,10 @@ extension GameInstanceWatcher on GameInstance {
|
|||||||
launcherPid?.toString() ?? "-1",
|
launcherPid?.toString() ?? "-1",
|
||||||
eacPid?.toString() ?? "-1",
|
eacPid?.toString() ?? "-1",
|
||||||
hosting.toString()
|
hosting.toString()
|
||||||
]
|
],
|
||||||
|
|
||||||
);
|
);
|
||||||
|
observerPid = process.pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _nestedHosting {
|
bool get _nestedHosting {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import 'package:reboot_launcher/src/util/translations.dart';
|
|||||||
final UpdateController _updateController = Get.find<UpdateController>();
|
final UpdateController _updateController = Get.find<UpdateController>();
|
||||||
Future<void> downloadCriticalDllInteractive(String filePath) async {
|
Future<void> downloadCriticalDllInteractive(String filePath) async {
|
||||||
try {
|
try {
|
||||||
final fileName = path.basename(filePath);
|
final fileName = path.basename(filePath).toLowerCase();
|
||||||
if (fileName == "reboot.dll") {
|
if (fileName == "reboot.dll") {
|
||||||
_updateController.update(true);
|
_updateController.update(true);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
child: Obx(() => SizedBox(
|
child: Obx(() => SizedBox(
|
||||||
height: 48,
|
height: 48,
|
||||||
child: Button(
|
child: Button(
|
||||||
onPressed: () => _operation = CancelableOperation.fromFuture(_start()),
|
onPressed: () => _operation = CancelableOperation.fromFuture(_start()),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(_hasStarted ? _stopMessage : _startMessage)
|
child: Text(_hasStarted ? _stopMessage : _startMessage)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
@@ -72,11 +72,11 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
Future<void> _start() async {
|
Future<void> _start() async {
|
||||||
if (_hasStarted) {
|
if (_hasStarted) {
|
||||||
_onStop(
|
_onStop(
|
||||||
reason: _StopReason.normal
|
reason: _StopReason.normal
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_operation != null) {
|
if(_operation != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
if(_hostingController.started()){
|
if(_hostingController.started()){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_hostingController.automaticServer()) {
|
if(!_hostingController.automaticServer()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -160,10 +160,12 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<GameInstance?> _startGameProcesses(FortniteVersion version, bool host, GameInstance? linkedHosting) async {
|
Future<GameInstance?> _startGameProcesses(FortniteVersion version, bool host, GameInstance? linkedHosting) async {
|
||||||
final launcherProcess = await _createLauncherProcess(version);
|
final launcherProcess = await _createPausedProcess(version.launcherExecutable);
|
||||||
final eacProcess = await _createEacProcess(version);
|
print("Created launcher process");
|
||||||
|
final eacProcess = await _createPausedProcess(version.eacExecutable);
|
||||||
|
print("Created eac process");
|
||||||
final executable = await version.executable;
|
final executable = await version.executable;
|
||||||
final gameProcess = await _createGameProcess(executable!.path, host);
|
final gameProcess = await _createGameProcess(executable!, host);
|
||||||
if(gameProcess == null) {
|
if(gameProcess == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -187,7 +189,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> _createGameProcess(String gamePath, bool host) async {
|
Future<int?> _createGameProcess(File executable, bool host) async {
|
||||||
if(!_hasStarted) {
|
if(!_hasStarted) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -199,38 +201,32 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
_hostingController.headless.value,
|
_hostingController.headless.value,
|
||||||
_gameController.customLaunchArgs.text
|
_gameController.customLaunchArgs.text
|
||||||
);
|
);
|
||||||
final gameProcess = await Process.start(
|
final gameProcess = await startProcess(
|
||||||
gamePath,
|
executable: executable,
|
||||||
gameArgs
|
args: gameArgs,
|
||||||
|
window: false
|
||||||
);
|
);
|
||||||
gameProcess
|
gameProcess.stdOutput.listen((line) => _onGameOutput(line, host, false));
|
||||||
..exitCode.then((_) => _onStop(reason: _StopReason.exitCode))
|
gameProcess.errorOutput.listen((line) => _onGameOutput(line, host, true));
|
||||||
..outLines.forEach((line) => _onGameOutput(line, host, false))
|
watchProcess(gameProcess.pid).then((_) => _onStop(reason: _StopReason.exitCode));
|
||||||
..errLines.forEach((line) => _onGameOutput(line, host, true));
|
|
||||||
return gameProcess.pid;
|
return gameProcess.pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> _createLauncherProcess(FortniteVersion version) async {
|
Future<int?> _createPausedProcess(File? file) async {
|
||||||
final launcherFile = version.launcher;
|
if (file == null) {
|
||||||
if (launcherFile == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final launcherProcess = await Process.start(launcherFile.path, []);
|
final process = await startProcess(
|
||||||
final pid = launcherProcess.pid;
|
executable: file,
|
||||||
suspend(pid);
|
args: [],
|
||||||
return pid;
|
window: false,
|
||||||
}
|
output: false
|
||||||
|
);
|
||||||
Future<int?> _createEacProcess(FortniteVersion version) async {
|
print("Started process: ${process.pid}");
|
||||||
final eacFile = version.eacExecutable;
|
final pid = process.pid;
|
||||||
if (eacFile == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final eacProcess = await Process.start(eacFile.path, []);
|
|
||||||
final pid = eacProcess.pid;
|
|
||||||
suspend(pid);
|
suspend(pid);
|
||||||
|
print("Suspended");
|
||||||
return pid;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,7 +469,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return _settingsController.memoryLeakDll.text;
|
return _settingsController.memoryLeakDll.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> _getDllFileOrStop(_Injectable injectable, bool host) async {
|
Future<File?> _getDllFileOrStop(_Injectable injectable, bool host) async {
|
||||||
final path = _getDllPath(injectable);
|
final path = _getDllPath(injectable);
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
|
|||||||
12
gui/lib/test.dart
Normal file
12
gui/lib/test.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:reboot_common/common.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
final process = await startProcess(
|
||||||
|
executable: File("C:\\FortniteBuilds\\Fortnite 4.2\\Fortnite 4.2\\Fortnite1\\FortniteGame\\Binaries\\Win64\\FortniteClient-Win64-Shipping-Reboot.exe"),
|
||||||
|
args: "-epicapp=Fortnite -epicenv=Prod -epiclocale=en-us -epicportal -skippatchcheck -nobe -fromfl=eac -fltoken=3db3ba5dcbd2e16703f3978d -caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ -AUTH_LOGIN=Player698@projectreboot.dev -AUTH_PASSWORD=Rebooted -AUTH_TYPE=epic -nullrhi -nosplash -nosound".split(" ")
|
||||||
|
);
|
||||||
|
process.stdOutput.listen((event) => stdout.writeln(event));
|
||||||
|
process.errorOutput.listen((event) => stdout.writeln(event));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user