mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
4
backend/README.md
Normal file
4
backend/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Backend
|
||||||
|
Fork of LawinV1
|
||||||
|
Awaiting rewrite in Dart
|
||||||
|
Use build.bat to generate the executable
|
||||||
0
backend/install_packages.bat
Normal file
0
backend/install_packages.bat
Normal file
0
backend/public/images/discord-s.png
Normal file
0
backend/public/images/discord-s.png
Normal file
0
backend/public/images/discord.png
Normal file
0
backend/public/images/discord.png
Normal file
0
backend/public/images/lawin-s.png
Normal file
0
backend/public/images/lawin-s.png
Normal file
0
backend/public/images/lawin.jpg
Normal file
0
backend/public/images/lawin.jpg
Normal file
0
backend/public/images/motd-s.png
Normal file
0
backend/public/images/motd-s.png
Normal file
0
backend/public/images/motd.png
Normal file
0
backend/public/images/motd.png
Normal file
0
backend/public/images/seasonx.png
Normal file
0
backend/public/images/seasonx.png
Normal file
0
backend/responses/CloudDir/LawinServer.chunk
Normal file
0
backend/responses/CloudDir/LawinServer.chunk
Normal file
0
backend/responses/CloudDir/LawinServer.manifest
Normal file
0
backend/responses/CloudDir/LawinServer.manifest
Normal file
@@ -51,7 +51,7 @@ void main(List<String> args) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stdout.writeln("Launching game...");
|
stdout.writeln("Launching game...");
|
||||||
var executable = version.gameExecutable;
|
var executable = version.shippingExecutable;
|
||||||
if(executable == null){
|
if(executable == null){
|
||||||
throw Exception("Missing game executable at: ${version.location.path}");
|
throw Exception("Missing game executable at: ${version.location.path}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Future<void> startGame() async {
|
|||||||
await _startLauncherProcess(version);
|
await _startLauncherProcess(version);
|
||||||
await _startEacProcess(version);
|
await _startEacProcess(version);
|
||||||
|
|
||||||
var executable = await version.gameExecutable;
|
var executable = await version.shippingExecutable;
|
||||||
if (executable == null) {
|
if (executable == null) {
|
||||||
throw Exception("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?");
|
throw Exception("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import 'package:path/path.dart' as path;
|
|||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
|
|
||||||
extension FortniteVersionExtension on FortniteVersion {
|
extension FortniteVersionExtension on FortniteVersion {
|
||||||
|
static DateTime _marker = DateTime.fromMicrosecondsSinceEpoch(0);
|
||||||
|
|
||||||
static File? findExecutable(Directory directory, String name) {
|
static File? findExecutable(Directory directory, String name) {
|
||||||
try{
|
try{
|
||||||
final result = directory.listSync(recursive: true)
|
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 shippingExecutable async {
|
||||||
|
final result = findExecutable(location, "FortniteClient-Win64-Shipping.exe");
|
||||||
Future<File?> get headlessGameExecutable async {
|
if(result == null) {
|
||||||
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) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final output = File("${original.parent.path}\\FortniteClient-Win64-Shipping-Headless.exe");
|
final lastModified = await result.lastModified();
|
||||||
await original.copy(output.path);
|
if(lastModified != _marker) {
|
||||||
await Isolate.run(() => patchHeadless(output));
|
print("Applying patch");
|
||||||
return output;
|
await Isolate.run(() => patchHeadless(result));
|
||||||
|
await result.setLastModified(_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
File? get launcherExecutable => findExecutable(location, "FortniteLauncher.exe");
|
File? get launcherExecutable => findExecutable(location, "FortniteLauncher.exe");
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ class FortniteVersion {
|
|||||||
'name': name,
|
'name': name,
|
||||||
'location': location.path
|
'location': location.path
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ class GameInstance {
|
|||||||
final int? launcherPid;
|
final int? launcherPid;
|
||||||
final int? eacPid;
|
final int? eacPid;
|
||||||
final List<InjectableDll> injectedDlls;
|
final List<InjectableDll> injectedDlls;
|
||||||
bool hosting;
|
final GameServerType? serverType;
|
||||||
bool launched;
|
bool launched;
|
||||||
bool movedToVirtualDesktop;
|
bool movedToVirtualDesktop;
|
||||||
bool tokenError;
|
bool tokenError;
|
||||||
@@ -20,7 +20,7 @@ class GameInstance {
|
|||||||
required this.gamePid,
|
required this.gamePid,
|
||||||
required this.launcherPid,
|
required this.launcherPid,
|
||||||
required this.eacPid,
|
required this.eacPid,
|
||||||
required this.hosting,
|
required this.serverType,
|
||||||
required this.child
|
required this.child
|
||||||
}): tokenError = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
|
}): tokenError = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class GameInstance {
|
|||||||
bool get nestedHosting {
|
bool get nestedHosting {
|
||||||
GameInstance? child = this;
|
GameInstance? child = this;
|
||||||
while(child != null) {
|
while(child != null) {
|
||||||
if(child.hosting) {
|
if(child.serverType != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,4 +46,10 @@ class GameInstance {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GameServerType {
|
||||||
|
headless,
|
||||||
|
virtualWindow,
|
||||||
|
window
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
final response = _downloadArchive(options, tempFile, startTime);
|
final response = _downloadArchive(options, stopped, tempFile, startTime);
|
||||||
await Future.any([stopped.future, response]);
|
await Future.any([stopped.future, response]);
|
||||||
if(!stopped.isCompleted) {
|
if(!stopped.isCompleted) {
|
||||||
await _extractArchive(stopped, extension, tempFile, options);
|
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;
|
var received = byteStart ?? 0;
|
||||||
try {
|
try {
|
||||||
await _dio.download(
|
await _dio.download(
|
||||||
options.build.link,
|
options.build.link,
|
||||||
tempFile.path,
|
tempFile.path,
|
||||||
onReceiveProgress: (data, length) {
|
onReceiveProgress: (data, length) {
|
||||||
|
if(stopped.isCompleted) {
|
||||||
|
throw StateError("Download interrupted");
|
||||||
|
}
|
||||||
|
|
||||||
received = data;
|
received = data;
|
||||||
final percentage = (received / length) * 100;
|
final percentage = (received / length) * 100;
|
||||||
_onProgress(startTime, percentage < 1 ? null : DateTime.now().millisecondsSinceEpoch, percentage, false, options);
|
_onProgress(startTime, percentage < 1 ? null : DateTime.now().millisecondsSinceEpoch, percentage, false, options);
|
||||||
@@ -116,12 +120,16 @@ Future<void> _downloadArchive(FortniteBuildDownloadOptions options, File tempFil
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}catch(error) {
|
}catch(error) {
|
||||||
|
if(stopped.isCompleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(errorsCount > _maxErrors || error.toString().contains(_deniedConnectionError) || error.toString().contains(_unavailableError)) {
|
if(errorsCount > _maxErrors || error.toString().contains(_deniedConnectionError) || error.toString().contains(_unavailableError)) {
|
||||||
_onError(error, options);
|
_onError(error, options);
|
||||||
return;
|
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]);
|
await Future.any([stopped.future, process.exitCode]);
|
||||||
|
process.kill(ProcessSignal.sigabrt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onProgress(int startTime, int? now, double percentage, bool extracting, FortniteBuildDownloadOptions options) {
|
void _onProgress(int startTime, int? now, double percentage, bool extracting, FortniteBuildDownloadOptions options) {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ 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 {
|
||||||
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"));
|
final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name"));
|
||||||
if(response.statusCode != 200) {
|
if(response.statusCode != 200) {
|
||||||
throw Exception("Cannot download $name: status code ${response.statusCode}");
|
throw Exception("Cannot download $name: status code ${response.statusCode}");
|
||||||
|
|||||||
@@ -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
|
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([
|
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
|
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 =>
|
Future<bool> patchHeadless(File file) async =>
|
||||||
_patch(file, _originalHeadless, _patchedHeadless);
|
await _patch(file, _originalHeadless, _patchedHeadless);
|
||||||
|
|
||||||
Future<bool> patchMatchmaking(File file) async =>
|
Future<bool> patchMatchmaking(File file) async =>
|
||||||
await _patch(file, _originalMatchmaking, _patchedMatchmaking);
|
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");
|
throw Exception("Cannot mutate length of binary file");
|
||||||
}
|
}
|
||||||
|
|
||||||
final read = await file.readAsBytes();
|
final source = await file.readAsBytes();
|
||||||
final length = await file.length();
|
|
||||||
var readOffset = 0;
|
var readOffset = 0;
|
||||||
var patchOffset = -1;
|
var patchOffset = -1;
|
||||||
var patchCount = 0;
|
var patchCount = 0;
|
||||||
while(readOffset < length){
|
while(readOffset < source.length){
|
||||||
if(read[readOffset] == original[patchCount]){
|
if(source[readOffset] == original[patchCount]){
|
||||||
if(patchOffset == -1) {
|
if(patchOffset == -1) {
|
||||||
patchOffset = readOffset;
|
patchOffset = readOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(++patchCount == original.length) {
|
if(readOffset - patchOffset + 1 == original.length) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patchCount++;
|
||||||
}else {
|
}else {
|
||||||
patchOffset = -1;
|
patchOffset = -1;
|
||||||
|
patchCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
readOffset++;
|
readOffset++;
|
||||||
@@ -55,10 +58,10 @@ Future<bool> _patch(File file, Uint8List original, Uint8List patched) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(var i = 0; i < patched.length; i++) {
|
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;
|
return true;
|
||||||
}catch(_){
|
}catch(_){
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -104,9 +104,9 @@ Future<bool> startElevatedProcess({required String executable, required String a
|
|||||||
return shellResult == 1;
|
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 ?? [];
|
final argsOrEmpty = args ?? [];
|
||||||
if(wrapProcess) {
|
if(useTempBatch) {
|
||||||
final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process");
|
final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process");
|
||||||
final tempScriptFile = File("${tempScriptDirectory.path}/process.bat");
|
final tempScriptFile = File("${tempScriptDirectory.path}/process.bat");
|
||||||
final command = window ? 'cmd.exe /k ""${executable.path}" ${argsOrEmpty.join(" ")}"' : '"${executable.path}" ${argsOrEmpty.join(" ")}';
|
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;
|
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) {
|
if(password.isEmpty) {
|
||||||
username = '${_parseUsername(username, host)}@projectreboot.dev';
|
username = '${_parseUsername(username, host)}@projectreboot.dev';
|
||||||
}
|
}
|
||||||
@@ -223,12 +223,18 @@ List<String> createRebootArgs(String username, String password, bool host, bool
|
|||||||
"-AUTH_TYPE=epic"
|
"-AUTH_TYPE=epic"
|
||||||
];
|
];
|
||||||
|
|
||||||
if(host && headless){
|
if(log) {
|
||||||
|
args.add("-log");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(host) {
|
||||||
args.addAll([
|
args.addAll([
|
||||||
"-nullrhi",
|
|
||||||
"-nosplash",
|
"-nosplash",
|
||||||
"-nosound",
|
"-nosound"
|
||||||
]);
|
]);
|
||||||
|
if(hostType == GameServerType.headless){
|
||||||
|
args.add("-nullrhi");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(additionalArgs.isNotEmpty){
|
if(additionalArgs.isNotEmpty){
|
||||||
|
|||||||
BIN
gui/assets/images/backend.png
Normal file
BIN
gui/assets/images/backend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 522 B |
2
gui/assets/info/en/faq/12. Can I get skins in game
Normal file
2
gui/assets/info/en/faq/12. Can I get skins in game
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
No, skins don't work in Reboot.
|
||||||
|
This is because Epic asked us to remove them.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user