mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 19:22:22 +01:00
Added CLI
This commit is contained in:
@@ -6,6 +6,8 @@ import 'package:get_storage/get_storage.dart';
|
||||
import 'package:reboot_launcher/src/util/binary.dart';
|
||||
import 'package:reboot_launcher/src/util/server.dart';
|
||||
|
||||
import '../util/server_standalone.dart';
|
||||
|
||||
class ServerController extends GetxController {
|
||||
late final TextEditingController host;
|
||||
late final TextEditingController port;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class FortniteVersion {
|
||||
@@ -16,11 +16,7 @@ class FortniteVersion {
|
||||
static File? findExecutable(Directory directory, String name) {
|
||||
try{
|
||||
var result = directory.listSync(recursive: true)
|
||||
.firstWhereOrNull((element) => path.basename(element.path) == name);
|
||||
if(result == null){
|
||||
return null;
|
||||
}
|
||||
|
||||
.firstWhere((element) => path.basename(element.path) == name);
|
||||
return File(result.path);
|
||||
}catch(_){
|
||||
return null;
|
||||
|
||||
@@ -7,8 +7,12 @@ File injectLogFile = File("${Platform.environment["Temp"]}/server.txt");
|
||||
|
||||
// This can be done easily with win32 apis but for some reason it doesn't work on all machines
|
||||
// Update: it was a missing permission error, it could be refactored now
|
||||
Future<bool> injectDll(int pid, String dll) async {
|
||||
var shell = Shell(workingDirectory: internalBinariesDirectory);
|
||||
Future<bool> injectDll(int pid, String dll, [bool useSafeBinariesHome = false]) async {
|
||||
var shell = Shell(
|
||||
commandVerbose: false,
|
||||
commentVerbose: false,
|
||||
workingDirectory: useSafeBinariesHome ? safeBinariesDirectory : internalBinariesDirectory
|
||||
);
|
||||
var process = await shell.run("./injector.exe -p $pid --inject \"$dll\"");
|
||||
var success = process.outText.contains("Successfully injected module");
|
||||
if (!success) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_launcher/src/util/binary.dart';
|
||||
@@ -31,17 +30,13 @@ Future<int> downloadRebootDll(int? lastUpdateMs) async {
|
||||
await extractFileToDisk(tempZip.path, outputDir.path);
|
||||
|
||||
var rebootDll = outputDir.listSync()
|
||||
.firstWhereOrNull((element) => path.extension(element.path) == ".dll");
|
||||
if(rebootDll == null){
|
||||
throw Exception("Missing reboot dll");
|
||||
}
|
||||
|
||||
.firstWhere((element) => path.extension(element.path) == ".dll");
|
||||
if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await File(rebootDll.path).readAsBytes())) {
|
||||
outputDir.delete();
|
||||
outputDir.delete(recursive: true);
|
||||
return lastUpdateMs ?? now.millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
await rebootDll.rename(oldRebootDll.path);
|
||||
outputDir.delete();
|
||||
outputDir.delete(recursive: true);
|
||||
return now.millisecondsSinceEpoch;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,13 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:process_run/shell.dart';
|
||||
import 'package:reboot_launcher/src/util/binary.dart';
|
||||
import 'package:reboot_launcher/src/util/server_standalone.dart';
|
||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||
import 'package:shelf_proxy/shelf_proxy.dart';
|
||||
|
||||
final serverLocation = Directory("${Platform.environment["UserProfile"]}/.reboot_launcher/lawin");
|
||||
const String _serverUrl =
|
||||
"https://github.com/Lawin0129/LawinServer/archive/refs/heads/main.zip";
|
||||
const String _portableServerUrl =
|
||||
"https://cdn.discordapp.com/attachments/998020695223193673/1019999251994005504/LawinServer.exe";
|
||||
|
||||
Future<bool> downloadServer(bool portable) async {
|
||||
if(!portable){
|
||||
var response = await http.get(Uri.parse(_serverUrl));
|
||||
var tempZip = File("${Platform.environment["Temp"]}/lawin.zip");
|
||||
await tempZip.writeAsBytes(response.bodyBytes);
|
||||
await extractFileToDisk(tempZip.path, serverLocation.parent.path);
|
||||
var result = Directory("${serverLocation.parent.path}/LawinServer-main");
|
||||
await result.rename("${serverLocation.parent.path}/${path.basename(serverLocation.path)}");
|
||||
await Process.run("${serverLocation.path}/install_packages.bat", [], workingDirectory: serverLocation.path);
|
||||
await updateEngineConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
var response = await http.get(Uri.parse(_portableServerUrl));
|
||||
var server = await loadBinary("LawinServer.exe", true);
|
||||
await server.writeAsBytes(response.bodyBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> updateEngineConfig() async {
|
||||
var engine = File("${serverLocation.path}/CloudStorage/DefaultEngine.ini");
|
||||
var patchedEngine = await loadBinary("DefaultEngine.ini", true);
|
||||
await engine.writeAsString(await patchedEngine.readAsString());
|
||||
}
|
||||
|
||||
Future<bool> isLawinPortFree() async {
|
||||
var portBat = await loadBinary("port.bat", false);
|
||||
var process = await Process.run(portBat.path, []);
|
||||
return !process.outText.contains(" LISTENING "); // Goofy way, best we got
|
||||
}
|
||||
|
||||
Future<HttpServer?> changeReverseProxyState(BuildContext context, String host, String port, HttpServer? server) async {
|
||||
if(server != null){
|
||||
try{
|
||||
@@ -92,7 +53,7 @@ Future<HttpServer?> changeReverseProxyState(BuildContext context, String host, S
|
||||
}
|
||||
|
||||
Future<Uri?> _showReverseProxyCheck(BuildContext context, String host, String port) async {
|
||||
var future = _pingServer(host, port);
|
||||
var future = ping(host, port);
|
||||
return await showDialog(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
@@ -138,30 +99,6 @@ Future<Uri?> _showReverseProxyCheck(BuildContext context, String host, String po
|
||||
);
|
||||
}
|
||||
|
||||
Future<Uri?> _pingServer(String host, String port, [bool https=false]) async {
|
||||
var hostName = _getHostName(host);
|
||||
var declaredScheme = _getScheme(host);
|
||||
try{
|
||||
var uri = Uri(
|
||||
scheme: declaredScheme ?? (https ? "https" : "http"),
|
||||
host: hostName,
|
||||
port: int.parse(port)
|
||||
);
|
||||
var client = HttpClient()
|
||||
..connectionTimeout = const Duration(seconds: 5);
|
||||
var request = await client.getUrl(uri);
|
||||
var response = await request.close();
|
||||
return response.statusCode == 200 ? uri : null;
|
||||
}catch(_){
|
||||
return https || declaredScheme != null ? null : await _pingServer(host, port, true);
|
||||
}
|
||||
}
|
||||
|
||||
String? _getHostName(String host) => host.replaceFirst("http://", "").replaceFirst("https://", "");
|
||||
|
||||
String? _getScheme(String host) => host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||
|
||||
|
||||
void _showStartProxyError(BuildContext context, Object error) {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
||||
90
lib/src/util/server_standalone.dart
Normal file
90
lib/src/util/server_standalone.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:process_run/shell.dart';
|
||||
import 'package:reboot_launcher/src/util/binary.dart';
|
||||
|
||||
final serverLocation = Directory("${Platform.environment["UserProfile"]}/.reboot_launcher/lawin");
|
||||
const String _serverUrl =
|
||||
"https://github.com/Lawin0129/LawinServer/archive/refs/heads/main.zip";
|
||||
const String _portableServerUrl =
|
||||
"https://cdn.discordapp.com/attachments/998020695223193673/1019999251994005504/LawinServer.exe";
|
||||
|
||||
Future<bool> downloadServer(bool portable) async {
|
||||
if(!portable){
|
||||
var response = await http.get(Uri.parse(_serverUrl));
|
||||
var tempZip = File("${Platform.environment["Temp"]}/lawin.zip");
|
||||
await tempZip.writeAsBytes(response.bodyBytes);
|
||||
await extractFileToDisk(tempZip.path, serverLocation.parent.path);
|
||||
var result = Directory("${serverLocation.parent.path}/LawinServer-main");
|
||||
await result.rename("${serverLocation.parent.path}/${path.basename(serverLocation.path)}");
|
||||
await Process.run("${serverLocation.path}/install_packages.bat", [], workingDirectory: serverLocation.path);
|
||||
await updateEngineConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
var response = await http.get(Uri.parse(_portableServerUrl));
|
||||
var server = await loadBinary("LawinServer.exe", true);
|
||||
await server.writeAsBytes(response.bodyBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> updateEngineConfig() async {
|
||||
var engine = File("${serverLocation.path}/CloudStorage/DefaultEngine.ini");
|
||||
var patchedEngine = await loadBinary("DefaultEngine.ini", true);
|
||||
await engine.writeAsString(await patchedEngine.readAsString());
|
||||
}
|
||||
|
||||
Future<bool> isLawinPortFree() async {
|
||||
return ServerSocket.bind("localhost", 3551)
|
||||
.then((socket) => socket.close())
|
||||
.then((_) => true)
|
||||
.onError((error, _) => false);
|
||||
}
|
||||
|
||||
List<String> createRebootArgs(String username, bool headless) {
|
||||
var args = [
|
||||
"-epicapp=Fortnite",
|
||||
"-epicenv=Prod",
|
||||
"-epiclocale=en-us",
|
||||
"-epicportal",
|
||||
"-skippatchcheck",
|
||||
"-fromfl=eac",
|
||||
"-fltoken=3db3ba5dcbd2e16703f3978d",
|
||||
"-caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
|
||||
"-AUTH_LOGIN=$username@projectreboot.dev",
|
||||
"-AUTH_PASSWORD=Rebooted",
|
||||
"-AUTH_TYPE=epic"
|
||||
];
|
||||
|
||||
if(headless){
|
||||
args.addAll(["-nullrhi", "-nosplash", "-nosound"]);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
Future<Uri?> ping(String host, String port, [bool https=false]) async {
|
||||
var hostName = _getHostName(host);
|
||||
var declaredScheme = _getScheme(host);
|
||||
try{
|
||||
var uri = Uri(
|
||||
scheme: declaredScheme ?? (https ? "https" : "http"),
|
||||
host: hostName,
|
||||
port: int.parse(port)
|
||||
);
|
||||
var client = HttpClient()
|
||||
..connectionTimeout = const Duration(seconds: 5);
|
||||
var request = await client.getUrl(uri);
|
||||
var response = await request.close();
|
||||
return response.statusCode == 200 ? uri : null;
|
||||
}catch(_){
|
||||
return https || declaredScheme != null ? null : await ping(host, port, true);
|
||||
}
|
||||
}
|
||||
|
||||
String? _getHostName(String host) => host.replaceFirst("http://", "").replaceFirst("https://", "");
|
||||
|
||||
String? _getScheme(String host) => host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||
@@ -15,7 +15,7 @@ import 'package:reboot_launcher/src/util/server.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
||||
|
||||
import '../util/os.dart';
|
||||
import '../util/server_standalone.dart';
|
||||
|
||||
class LaunchButton extends StatefulWidget {
|
||||
const LaunchButton(
|
||||
@@ -111,7 +111,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
return;
|
||||
}
|
||||
|
||||
_gameController.gameProcess = await Process.start(gamePath, _createProcessArguments())
|
||||
_gameController.gameProcess = await Process.start(gamePath, createRebootArgs(_gameController.username.text, hosting))
|
||||
..exitCode.then((_) => _onEnd())
|
||||
..outLines.forEach(_onGameOutput);
|
||||
await _injectOrShowError("cranium.dll");
|
||||
@@ -210,6 +210,31 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showTokenError() async {
|
||||
if(!mounted){
|
||||
return;
|
||||
}
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
content: const SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text("A token error occurred, restart the game and try again", textAlign: TextAlign.center)
|
||||
),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Close'),
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showUnsupportedHeadless() async {
|
||||
if(!mounted){
|
||||
return;
|
||||
@@ -300,6 +325,13 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
return;
|
||||
}
|
||||
|
||||
if(line.contains("Network failure when attempting to check platform restrictions")){
|
||||
_fail = true;
|
||||
_closeDialogIfOpen(false);
|
||||
_showTokenError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.contains("Game Engine Initialized") && _gameController.type.value == GameType.client) {
|
||||
_injectOrShowError("console.dll");
|
||||
return;
|
||||
@@ -365,26 +397,4 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
showSnackbar(context, Snackbar(content: Text("Cannot inject $binary")));
|
||||
launchUrl(injectLogFile.uri);
|
||||
}
|
||||
|
||||
List<String> _createProcessArguments() {
|
||||
var args = [
|
||||
"-epicapp=Fortnite",
|
||||
"-epicenv=Prod",
|
||||
"-epiclocale=en-us",
|
||||
"-epicportal",
|
||||
"-skippatchcheck",
|
||||
"-fromfl=eac",
|
||||
"-fltoken=3db3ba5dcbd2e16703f3978d",
|
||||
"-caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
|
||||
"-AUTH_LOGIN=${_gameController.username.text}@projectreboot.dev",
|
||||
"-AUTH_PASSWORD=Rebooted",
|
||||
"-AUTH_TYPE=epic"
|
||||
];
|
||||
|
||||
if(_gameController.type.value == GameType.headlessServer){
|
||||
args.addAll(["-nullrhi", "-nosplash", "-nosound"]);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user