This commit is contained in:
Alessandro Autiero
2024-06-15 17:57:17 +02:00
parent 2bf084d120
commit e24f4e97b3
144 changed files with 442 additions and 278 deletions

View File

@@ -5,6 +5,8 @@ import 'package:path/path.dart' as path;
import 'package:reboot_common/common.dart';
extension FortniteVersionExtension on FortniteVersion {
static DateTime _marker = DateTime.fromMicrosecondsSinceEpoch(0);
static File? findExecutable(Directory directory, String name) {
try{
final result = directory.listSync(recursive: true)
@@ -15,23 +17,20 @@ extension FortniteVersionExtension on FortniteVersion {
}
}
File? get gameExecutable => findExecutable(location, "FortniteClient-Win64-Shipping.exe");
Future<File?> get headlessGameExecutable async {
final result = findExecutable(location, "FortniteClient-Win64-Shipping-Headless.exe");
if(result != null) {
return result;
}
final original = findExecutable(location, "FortniteClient-Win64-Shipping.exe");
if(original == null) {
Future<File?> get shippingExecutable async {
final result = findExecutable(location, "FortniteClient-Win64-Shipping.exe");
if(result == null) {
return null;
}
final output = File("${original.parent.path}\\FortniteClient-Win64-Shipping-Headless.exe");
await original.copy(output.path);
await Isolate.run(() => patchHeadless(output));
return output;
final lastModified = await result.lastModified();
if(lastModified != _marker) {
print("Applying patch");
await Isolate.run(() => patchHeadless(result));
await result.setLastModified(_marker);
}
return result;
}
File? get launcherExecutable => findExecutable(location, "FortniteLauncher.exe");

View File

@@ -14,4 +14,4 @@ class FortniteVersion {
'name': name,
'location': location.path
};
}
}

View File

@@ -9,7 +9,7 @@ class GameInstance {
final int? launcherPid;
final int? eacPid;
final List<InjectableDll> injectedDlls;
bool hosting;
final GameServerType? serverType;
bool launched;
bool movedToVirtualDesktop;
bool tokenError;
@@ -20,7 +20,7 @@ class GameInstance {
required this.gamePid,
required this.launcherPid,
required this.eacPid,
required this.hosting,
required this.serverType,
required this.child
}): tokenError = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
@@ -37,7 +37,7 @@ class GameInstance {
bool get nestedHosting {
GameInstance? child = this;
while(child != null) {
if(child.hosting) {
if(child.serverType != null) {
return true;
}
@@ -46,4 +46,10 @@ class GameInstance {
return false;
}
}
enum GameServerType {
headless,
virtualWindow,
window
}

View File

@@ -67,7 +67,7 @@ Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
}
final startTime = DateTime.now().millisecondsSinceEpoch;
final response = _downloadArchive(options, tempFile, startTime);
final response = _downloadArchive(options, stopped, tempFile, startTime);
await Future.any([stopped.future, response]);
if(!stopped.isCompleted) {
await _extractArchive(stopped, extension, tempFile, options);
@@ -79,13 +79,17 @@ Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
}
}
Future<void> _downloadArchive(FortniteBuildDownloadOptions options, File tempFile, int startTime, [int? byteStart = null, int errorsCount = 0]) async {
Future<void> _downloadArchive(FortniteBuildDownloadOptions options, Completer stopped, File tempFile, int startTime, [int? byteStart = null, int errorsCount = 0]) async {
var received = byteStart ?? 0;
try {
await _dio.download(
options.build.link,
tempFile.path,
onReceiveProgress: (data, length) {
if(stopped.isCompleted) {
throw StateError("Download interrupted");
}
received = data;
final percentage = (received / length) * 100;
_onProgress(startTime, percentage < 1 ? null : DateTime.now().millisecondsSinceEpoch, percentage, false, options);
@@ -116,12 +120,16 @@ Future<void> _downloadArchive(FortniteBuildDownloadOptions options, File tempFil
)
);
}catch(error) {
if(stopped.isCompleted) {
return;
}
if(errorsCount > _maxErrors || error.toString().contains(_deniedConnectionError) || error.toString().contains(_unavailableError)) {
_onError(error, options);
return;
}
await _downloadArchive(options, tempFile, startTime, received, errorsCount + 1);
await _downloadArchive(options, stopped, tempFile, startTime, received, errorsCount + 1);
}
}
@@ -225,6 +233,7 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
}
await Future.any([stopped.future, process.exitCode]);
process.kill(ProcessSignal.sigabrt);
}
void _onProgress(int startTime, int? now, double percentage, bool extracting, FortniteBuildDownloadOptions options) {

View File

@@ -18,7 +18,6 @@ Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force =
}
Future<void> downloadCriticalDll(String name, String outputPath) async {
print("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name");
final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name"));
if(response.statusCode != 200) {
throw Exception("Cannot download $name: status code ${response.statusCode}");

View File

@@ -9,6 +9,7 @@ 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
]);
// Not used right now
final Uint8List _originalMatchmaking = Uint8List.fromList([
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
]);
@@ -18,7 +19,7 @@ final Uint8List _patchedMatchmaking = Uint8List.fromList([
]);
Future<bool> patchHeadless(File file) async =>
_patch(file, _originalHeadless, _patchedHeadless);
await _patch(file, _originalHeadless, _patchedHeadless);
Future<bool> patchMatchmaking(File file) async =>
await _patch(file, _originalMatchmaking, _patchedMatchmaking);
@@ -29,22 +30,24 @@ Future<bool> _patch(File file, Uint8List original, Uint8List patched) async {
throw Exception("Cannot mutate length of binary file");
}
final read = await file.readAsBytes();
final length = await file.length();
final source = await file.readAsBytes();
var readOffset = 0;
var patchOffset = -1;
var patchCount = 0;
while(readOffset < length){
if(read[readOffset] == original[patchCount]){
while(readOffset < source.length){
if(source[readOffset] == original[patchCount]){
if(patchOffset == -1) {
patchOffset = readOffset;
}
if(++patchCount == original.length) {
if(readOffset - patchOffset + 1 == original.length) {
break;
}
patchCount++;
}else {
patchOffset = -1;
patchCount = 0;
}
readOffset++;
@@ -55,10 +58,10 @@ Future<bool> _patch(File file, Uint8List original, Uint8List patched) async {
}
for(var i = 0; i < patched.length; i++) {
read[patchOffset + i] = patched[i];
source[patchOffset + i] = patched[i];
}
await file.writeAsBytes(read, flush: true);
await file.writeAsBytes(source, flush: true);
return true;
}catch(_){
return false;

View File

@@ -104,9 +104,9 @@ Future<bool> startElevatedProcess({required String executable, required String a
return shellResult == 1;
}
Future<Process> startProcess({required File executable, List<String>? args, bool wrapProcess = true, bool window = false, String? name}) async {
Future<Process> startProcess({required File executable, List<String>? args, bool useTempBatch = true, bool window = false, String? name}) async {
final argsOrEmpty = args ?? [];
if(wrapProcess) {
if(useTempBatch) {
final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process");
final tempScriptFile = File("${tempScriptDirectory.path}/process.bat");
final command = window ? 'cmd.exe /k ""${executable.path}" ${argsOrEmpty.join(" ")}"' : '"${executable.path}" ${argsOrEmpty.join(" ")}';
@@ -202,7 +202,7 @@ Future<bool> watchProcess(int pid) async {
return await completer.future;
}
List<String> createRebootArgs(String username, String password, bool host, bool headless, String additionalArgs) {
List<String> createRebootArgs(String username, String password, bool host, GameServerType hostType, bool log, String additionalArgs) {
if(password.isEmpty) {
username = '${_parseUsername(username, host)}@projectreboot.dev';
}
@@ -223,12 +223,18 @@ List<String> createRebootArgs(String username, String password, bool host, bool
"-AUTH_TYPE=epic"
];
if(host && headless){
if(log) {
args.add("-log");
}
if(host) {
args.addAll([
"-nullrhi",
"-nosplash",
"-nosound",
"-nosound"
]);
if(hostType == GameServerType.headless){
args.add("-nullrhi");
}
}
if(additionalArgs.isNotEmpty){