mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
Added CLI
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
netstat -ano|find ":3551"
|
|
||||||
382
lib/cli.dart
Normal file
382
lib/cli.dart
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:args/args.dart';
|
||||||
|
import 'package:process_run/shell.dart';
|
||||||
|
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
||||||
|
import 'package:reboot_launcher/src/model/game_type.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/binary.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/injector.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/patcher.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/reboot.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/server_standalone.dart';
|
||||||
|
import 'package:shelf_proxy/shelf_proxy.dart';
|
||||||
|
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
||||||
|
import 'dart:ffi';
|
||||||
|
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:win32/win32.dart';
|
||||||
|
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
const String _craniumDownload = "https://cdn.discordapp.com/attachments/1001161930599317524/1027684488718860309/cranium.dll";
|
||||||
|
const String _consoleDownload = "https://cdn.discordapp.com/attachments/1001161930599317524/1027684489184432188/console.dll";
|
||||||
|
const String _injectorDownload = "https://cdn.discordapp.com/attachments/1001161930599317524/1027686593697435799/injector.exe";
|
||||||
|
|
||||||
|
Process? _gameProcess;
|
||||||
|
|
||||||
|
void main(List<String> args){
|
||||||
|
handleCLI(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _getControllerJson(String name) async {
|
||||||
|
var folder = await _getWindowsPath(FOLDERID_Documents);
|
||||||
|
if(folder == null){
|
||||||
|
throw Exception("Missing documents folder");
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = File("$folder/$name.gs");
|
||||||
|
if(!file.existsSync()){
|
||||||
|
return HashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonDecode(file.readAsStringSync());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _getWindowsPath(String folderID) {
|
||||||
|
final Pointer<Pointer<Utf16>> pathPtrPtr = calloc<Pointer<Utf16>>();
|
||||||
|
final Pointer<GUID> knownFolderID = calloc<GUID>()..ref.setGUID(folderID);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final int hr = SHGetKnownFolderPath(
|
||||||
|
knownFolderID,
|
||||||
|
KF_FLAG_DEFAULT,
|
||||||
|
NULL,
|
||||||
|
pathPtrPtr,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
if (hr == E_INVALIDARG || hr == E_FAIL) {
|
||||||
|
throw WindowsException(hr);
|
||||||
|
}
|
||||||
|
return Future<String?>.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String path = pathPtrPtr.value.toDartString();
|
||||||
|
return Future<String>.value(path);
|
||||||
|
} finally {
|
||||||
|
calloc.free(pathPtrPtr);
|
||||||
|
calloc.free(knownFolderID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleCLI(List<String> args) async {
|
||||||
|
stdout.writeln("Reboot Launcher CLI Tool");
|
||||||
|
stdout.writeln("Wrote by Auties00");
|
||||||
|
stdout.writeln("Version 3.10");
|
||||||
|
|
||||||
|
var gameJson = await _getControllerJson("game1");
|
||||||
|
var serverJson = await _getControllerJson("server1");
|
||||||
|
var versions = _getVersions(gameJson);
|
||||||
|
var parser = ArgParser()
|
||||||
|
..addCommand("list")..addCommand("launch")
|
||||||
|
..addOption("version", defaultsTo: gameJson["version"])
|
||||||
|
..addOption("username")
|
||||||
|
..addOption("server-type", allowed: ["embedded", "remote"], defaultsTo: serverJson["embedded"] ?? true ? "embedded" : "remote")
|
||||||
|
..addOption("server-host", defaultsTo: serverJson["host"])
|
||||||
|
..addOption("server-port", defaultsTo: serverJson["port"])
|
||||||
|
..addOption("type", allowed: ["client", "server", "headless_server"], defaultsTo: _getDefaultType(gameJson))
|
||||||
|
..addFlag("update", defaultsTo: true, negatable: true)
|
||||||
|
..addFlag("log", defaultsTo: false);
|
||||||
|
var result = parser.parse(args);
|
||||||
|
if (result.command?.name == "list") {
|
||||||
|
stdout.writeln("Versions list: ");
|
||||||
|
versions.map((entry) => "${entry.location.path}(${entry.name})")
|
||||||
|
.forEach((element) => stdout.writeln(element));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = _getType(result);
|
||||||
|
var username = result["username"];
|
||||||
|
username ??= gameJson["${type == GameType.client ? "game" : "server"}_username"];
|
||||||
|
|
||||||
|
var dummyVersion = _createVersion(gameJson["version"], result["version"], versions);
|
||||||
|
await _updateDLLs();
|
||||||
|
if(result["update"]) {
|
||||||
|
stdout.writeln("Updating DLL...");
|
||||||
|
await downloadRebootDll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.writeln("Launching game(type: ${type.name})...");
|
||||||
|
await _startLauncherProcess(dummyVersion);
|
||||||
|
await _startEacProcess(dummyVersion);
|
||||||
|
if (result["type"] == "headless_server") {
|
||||||
|
await patchExe(dummyVersion.executable!);
|
||||||
|
}
|
||||||
|
|
||||||
|
var started = await _startServerIfNeeded(result);
|
||||||
|
if(!started){
|
||||||
|
stderr.writeln("Cannot start server!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _startGameProcess(dummyVersion, type != GameType.client, result);
|
||||||
|
await _injectOrShowError("cranium.dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
GameType _getType(ArgResults result) {
|
||||||
|
var type = result["type"];
|
||||||
|
switch(type){
|
||||||
|
case "client":
|
||||||
|
return GameType.client;
|
||||||
|
|
||||||
|
case "server":
|
||||||
|
return GameType.server;
|
||||||
|
|
||||||
|
case "headless_server":
|
||||||
|
return GameType.headlessServer;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Exception("Unknown game type: $result. Use --type only with client, server or headless_server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getDefaultType(Map<String, dynamic> json){
|
||||||
|
var type = GameType.values.elementAt(json["type"] ?? 0);
|
||||||
|
switch(type){
|
||||||
|
case GameType.client:
|
||||||
|
return "client";
|
||||||
|
case GameType.server:
|
||||||
|
return "server";
|
||||||
|
case GameType.headlessServer:
|
||||||
|
return "headless_server";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateDLLs() async {
|
||||||
|
stdout.writeln("Downloading necessary components...");
|
||||||
|
var consoleDll = await loadBinary("console.dll", true);
|
||||||
|
if(!consoleDll.existsSync()){
|
||||||
|
var response = await http.get(Uri.parse(_consoleDownload));
|
||||||
|
if(response.statusCode != 200){
|
||||||
|
throw Exception("Cannot download console.dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
await consoleDll.writeAsBytes(response.bodyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var craniumDll = await loadBinary("cranium.dll", true);
|
||||||
|
if(!craniumDll.existsSync()){
|
||||||
|
var response = await http.get(Uri.parse(_craniumDownload));
|
||||||
|
if(response.statusCode != 200){
|
||||||
|
throw Exception("Cannot download cranium.dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
await craniumDll.writeAsBytes(response.bodyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var injectorExe = await loadBinary("injector.exe", true);
|
||||||
|
if(!injectorExe.existsSync()){
|
||||||
|
var response = await http.get(Uri.parse(_injectorDownload));
|
||||||
|
if(response.statusCode != 200){
|
||||||
|
throw Exception("Cannot download injector");
|
||||||
|
}
|
||||||
|
|
||||||
|
await injectorExe.writeAsBytes(response.bodyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FortniteVersion> _getVersions(Map<String, dynamic> gameJson) {
|
||||||
|
Iterable iterable = jsonDecode(gameJson["versions"] ?? "[]");
|
||||||
|
return iterable.map((entry) => FortniteVersion.fromJson(entry))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startGameProcess(FortniteVersion dummyVersion, bool host, ArgResults result) async {
|
||||||
|
var gamePath = dummyVersion.executable?.path;
|
||||||
|
if (gamePath == null) {
|
||||||
|
throw Exception("${dummyVersion.location
|
||||||
|
.path} no longer contains a Fortnite executable. Did you delete it?");
|
||||||
|
}
|
||||||
|
|
||||||
|
var username = result["username"];
|
||||||
|
if (username == null) {
|
||||||
|
username = "Reboot${host ? 'Host' : 'Player'}";
|
||||||
|
stdout.writeln("No username was specified, using $username by default. Use --username to specify one");
|
||||||
|
}
|
||||||
|
|
||||||
|
var verbose = result["log"];
|
||||||
|
_gameProcess = await Process.start(gamePath, createRebootArgs(username, result["type"] == "headless_server"))
|
||||||
|
..exitCode.then((_) => _onClose())
|
||||||
|
..outLines.forEach((line) => _onGameOutput(line, host, verbose));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClose() {
|
||||||
|
stdout.writeln("The game was closed");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startEacProcess(FortniteVersion dummyVersion) async {
|
||||||
|
if (dummyVersion.eacExecutable == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var process = await Process.start(dummyVersion.eacExecutable!.path, []);
|
||||||
|
Win32Process(process.pid).suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startLauncherProcess(FortniteVersion dummyVersion) async {
|
||||||
|
if (dummyVersion.launcher == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var process = await Process.start(dummyVersion.launcher!.path, []);
|
||||||
|
Win32Process(process.pid).suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _startServerIfNeeded(ArgResults result) async {
|
||||||
|
stdout.writeln("Starting lawin server...");
|
||||||
|
if (!await isLawinPortFree()) {
|
||||||
|
stdout.writeln("A lawin server is already active");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result["server-type"] == "embedded") {
|
||||||
|
stdout.writeln("Starting an embedded server...");
|
||||||
|
return await _changeEmbeddedServerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = result["server-host"];
|
||||||
|
var port = result["server-port"];
|
||||||
|
stdout.writeln("Starting a reverse proxy to $host:$port");
|
||||||
|
return await _changeReverseProxyState(host, port) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _changeEmbeddedServerState() async {
|
||||||
|
var nodeProcess = await Process.run("where", ["node"]);
|
||||||
|
if(nodeProcess.exitCode != 0) {
|
||||||
|
throw Exception("Missing node, cannot start embedded server");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!serverLocation.existsSync()) {
|
||||||
|
await downloadServer(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverRunner = File("${serverLocation.path}/start.bat");
|
||||||
|
if (!(await serverRunner.exists())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeModules = Directory("${serverLocation.path}/node_modules");
|
||||||
|
if (!(await nodeModules.exists())) {
|
||||||
|
await Process.run("${serverLocation.path}/install_packages.bat", [],
|
||||||
|
workingDirectory: serverLocation.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Process.start(serverRunner.path, [], workingDirectory: serverLocation.path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<HttpServer?> _changeReverseProxyState(String host, String 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{
|
||||||
|
var uri = await ping(host, port);
|
||||||
|
if(uri == null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await shelf_io.serve(proxyHandler(uri), "127.0.0.1", 3551);
|
||||||
|
}catch(error){
|
||||||
|
throw Exception("Cannot start reverse proxy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FortniteVersion _createVersion(String? versionName, String? versionPath, List<FortniteVersion> versions) {
|
||||||
|
if(versionName != null){
|
||||||
|
try {
|
||||||
|
return versions.firstWhere((element) => versionName == element.name);
|
||||||
|
}catch(_){
|
||||||
|
throw Exception("Cannot find version $versionName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versionPath == null) {
|
||||||
|
throw Exception(
|
||||||
|
"Specify a version using --version or open the launcher GUI and select it manually");
|
||||||
|
}
|
||||||
|
|
||||||
|
return FortniteVersion(name: "dummy", location: Directory(versionPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onGameOutput(String line, bool host, bool verbose) {
|
||||||
|
if(verbose) {
|
||||||
|
stdout.writeln(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.contains("FOnlineSubsystemGoogleCommon::Shutdown()")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.contains("port 3551 failed: Connection refused")){
|
||||||
|
stderr.writeln("Connection refused from lawin server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.contains("HTTP 400 response from ")){
|
||||||
|
stderr.writeln("Connection refused from lawin server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.contains("Network failure when attempting to check platform restrictions")){
|
||||||
|
stderr.writeln("Expired token, please reopen the game");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.contains("Game Engine Initialized") && !host) {
|
||||||
|
_injectOrShowError("console.dll");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.contains("Region") && host){
|
||||||
|
_injectOrShowError("reboot.dll");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _injectOrShowError(String binary) async {
|
||||||
|
if (_gameProcess == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
stdout.writeln("Injecting $binary...");
|
||||||
|
var dll = await loadBinary(binary, true);
|
||||||
|
var success = await injectDll(_gameProcess!.pid, dll.path, true);
|
||||||
|
if (success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onInjectError(binary);
|
||||||
|
} catch (exception) {
|
||||||
|
_onInjectError(binary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onInjectError(String binary) {
|
||||||
|
stderr.writeln(injectLogFile.readAsStringSync());
|
||||||
|
throw Exception("Cannot inject binary: $binary");
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart'
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
|
import 'package:reboot_launcher/cli.dart';
|
||||||
import 'package:reboot_launcher/src/controller/build_controller.dart';
|
import 'package:reboot_launcher/src/controller/build_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
||||||
@@ -14,9 +15,14 @@ import 'package:reboot_launcher/src/util/binary.dart';
|
|||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:system_theme/system_theme.dart';
|
import 'package:system_theme/system_theme.dart';
|
||||||
|
|
||||||
void main() async {
|
void main(List<String> args) async {
|
||||||
await Directory(safeBinariesDirectory)
|
await Directory(safeBinariesDirectory)
|
||||||
.create(recursive: true);
|
.create(recursive: true);
|
||||||
|
if(args.isNotEmpty){
|
||||||
|
handleCLI(args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await SystemTheme.accentColor.load();
|
await SystemTheme.accentColor.load();
|
||||||
await GetStorage.init("game");
|
await GetStorage.init("game");
|
||||||
@@ -34,6 +40,7 @@ void main() async {
|
|||||||
appWindow.title = "Reboot Launcher";
|
appWindow.title = "Reboot Launcher";
|
||||||
appWindow.show();
|
appWindow.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
runApp(const RebootApplication());
|
runApp(const RebootApplication());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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/binary.dart';
|
||||||
import 'package:reboot_launcher/src/util/server.dart';
|
import 'package:reboot_launcher/src/util/server.dart';
|
||||||
|
|
||||||
|
import '../util/server_standalone.dart';
|
||||||
|
|
||||||
class ServerController extends GetxController {
|
class ServerController extends GetxController {
|
||||||
late final TextEditingController host;
|
late final TextEditingController host;
|
||||||
late final TextEditingController port;
|
late final TextEditingController port;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
class FortniteVersion {
|
class FortniteVersion {
|
||||||
@@ -16,11 +16,7 @@ class FortniteVersion {
|
|||||||
static File? findExecutable(Directory directory, String name) {
|
static File? findExecutable(Directory directory, String name) {
|
||||||
try{
|
try{
|
||||||
var result = directory.listSync(recursive: true)
|
var result = directory.listSync(recursive: true)
|
||||||
.firstWhereOrNull((element) => path.basename(element.path) == name);
|
.firstWhere((element) => path.basename(element.path) == name);
|
||||||
if(result == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return File(result.path);
|
return File(result.path);
|
||||||
}catch(_){
|
}catch(_){
|
||||||
return null;
|
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
|
// 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
|
// Update: it was a missing permission error, it could be refactored now
|
||||||
Future<bool> injectDll(int pid, String dll) async {
|
Future<bool> injectDll(int pid, String dll, [bool useSafeBinariesHome = false]) async {
|
||||||
var shell = Shell(workingDirectory: internalBinariesDirectory);
|
var shell = Shell(
|
||||||
|
commandVerbose: false,
|
||||||
|
commentVerbose: false,
|
||||||
|
workingDirectory: useSafeBinariesHome ? safeBinariesDirectory : internalBinariesDirectory
|
||||||
|
);
|
||||||
var process = await shell.run("./injector.exe -p $pid --inject \"$dll\"");
|
var process = await shell.run("./injector.exe -p $pid --inject \"$dll\"");
|
||||||
var success = process.outText.contains("Successfully injected module");
|
var success = process.outText.contains("Successfully injected module");
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:archive/archive_io.dart';
|
import 'package:archive/archive_io.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:reboot_launcher/src/util/binary.dart';
|
import 'package:reboot_launcher/src/util/binary.dart';
|
||||||
@@ -31,17 +30,13 @@ Future<int> downloadRebootDll(int? lastUpdateMs) async {
|
|||||||
await extractFileToDisk(tempZip.path, outputDir.path);
|
await extractFileToDisk(tempZip.path, outputDir.path);
|
||||||
|
|
||||||
var rebootDll = outputDir.listSync()
|
var rebootDll = outputDir.listSync()
|
||||||
.firstWhereOrNull((element) => path.extension(element.path) == ".dll");
|
.firstWhere((element) => path.extension(element.path) == ".dll");
|
||||||
if(rebootDll == null){
|
|
||||||
throw Exception("Missing reboot dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await File(rebootDll.path).readAsBytes())) {
|
if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await File(rebootDll.path).readAsBytes())) {
|
||||||
outputDir.delete();
|
outputDir.delete(recursive: true);
|
||||||
return lastUpdateMs ?? now.millisecondsSinceEpoch;
|
return lastUpdateMs ?? now.millisecondsSinceEpoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
await rebootDll.rename(oldRebootDll.path);
|
await rebootDll.rename(oldRebootDll.path);
|
||||||
outputDir.delete();
|
outputDir.delete(recursive: true);
|
||||||
return now.millisecondsSinceEpoch;
|
return now.millisecondsSinceEpoch;
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,13 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:archive/archive_io.dart';
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/foundation.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/binary.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/server_standalone.dart';
|
||||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||||
import 'package:shelf_proxy/shelf_proxy.dart';
|
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 {
|
Future<HttpServer?> changeReverseProxyState(BuildContext context, String host, String port, HttpServer? server) async {
|
||||||
if(server != null){
|
if(server != null){
|
||||||
try{
|
try{
|
||||||
@@ -92,7 +53,7 @@ Future<HttpServer?> changeReverseProxyState(BuildContext context, String host, S
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Uri?> _showReverseProxyCheck(BuildContext context, String host, String port) async {
|
Future<Uri?> _showReverseProxyCheck(BuildContext context, String host, String port) async {
|
||||||
var future = _pingServer(host, port);
|
var future = ping(host, port);
|
||||||
return await showDialog(
|
return await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ContentDialog(
|
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) {
|
void _showStartProxyError(BuildContext context, Object error) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
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:url_launcher/url_launcher.dart';
|
||||||
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
||||||
|
|
||||||
import '../util/os.dart';
|
import '../util/server_standalone.dart';
|
||||||
|
|
||||||
class LaunchButton extends StatefulWidget {
|
class LaunchButton extends StatefulWidget {
|
||||||
const LaunchButton(
|
const LaunchButton(
|
||||||
@@ -111,7 +111,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_gameController.gameProcess = await Process.start(gamePath, _createProcessArguments())
|
_gameController.gameProcess = await Process.start(gamePath, createRebootArgs(_gameController.username.text, hosting))
|
||||||
..exitCode.then((_) => _onEnd())
|
..exitCode.then((_) => _onEnd())
|
||||||
..outLines.forEach(_onGameOutput);
|
..outLines.forEach(_onGameOutput);
|
||||||
await _injectOrShowError("cranium.dll");
|
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 {
|
Future<void> _showUnsupportedHeadless() async {
|
||||||
if(!mounted){
|
if(!mounted){
|
||||||
return;
|
return;
|
||||||
@@ -300,6 +325,13 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
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) {
|
if (line.contains("Game Engine Initialized") && _gameController.type.value == GameType.client) {
|
||||||
_injectOrShowError("console.dll");
|
_injectOrShowError("console.dll");
|
||||||
return;
|
return;
|
||||||
@@ -365,26 +397,4 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
showSnackbar(context, Snackbar(content: Text("Cannot inject $binary")));
|
showSnackbar(context, Snackbar(content: Text("Cannot inject $binary")));
|
||||||
launchUrl(injectLogFile.uri);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ dependencies:
|
|||||||
get_storage: ^2.0.3
|
get_storage: ^2.0.3
|
||||||
window_manager: ^0.2.7
|
window_manager: ^0.2.7
|
||||||
shelf_proxy: ^1.0.2
|
shelf_proxy: ^1.0.2
|
||||||
|
args: ^2.3.1
|
||||||
|
github: ^9.4.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
win32: ^3.0.0
|
win32: ^3.0.0
|
||||||
|
|||||||
@@ -7,24 +7,25 @@ auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
|
|||||||
|
|
||||||
#include "flutter_window.h"
|
#include "flutter_window.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <io.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||||
// Attach to console when present (e.g., 'flutter run') or create a
|
std::vector<std::string> command_line_arguments = GetCommandLineArguments();
|
||||||
// new console when running with a debugger.
|
if (!command_line_arguments.empty() || (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent())) {
|
||||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
|
||||||
CreateAndAttachConsole();
|
CreateAndAttachConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Initialize COM, so that it is available for use in the library and/or
|
// Initialize COM, so that it is available for use in the library and/or
|
||||||
// plugins.
|
// plugins.
|
||||||
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||||
|
|
||||||
flutter::DartProject project(L"data");
|
flutter::DartProject project(L"data");
|
||||||
|
|
||||||
std::vector<std::string> command_line_arguments =
|
|
||||||
GetCommandLineArguments();
|
|
||||||
|
|
||||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||||
|
|
||||||
FlutterWindow window(project);
|
FlutterWindow window(project);
|
||||||
@@ -33,6 +34,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
|||||||
if (!window.CreateAndShow(L"reboot_launcher", origin, size)) {
|
if (!window.CreateAndShow(L"reboot_launcher", origin, size)) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.SetQuitOnClose(true);
|
window.SetQuitOnClose(true);
|
||||||
|
|
||||||
::MSG msg;
|
::MSG msg;
|
||||||
@@ -42,5 +44,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
|||||||
}
|
}
|
||||||
|
|
||||||
::CoUninitialize();
|
::CoUninitialize();
|
||||||
|
std::cout << "Done" << std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user