mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
Release 9.2.0
This commit is contained in:
@@ -10,6 +10,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/update_status.dart';
|
||||
export 'package:reboot_common/src/model/update_timer.dart';
|
||||
export 'package:reboot_common/src/model/fortnite_server.dart';
|
||||
export 'package:reboot_common/src/model/dll.dart';
|
||||
export 'package:reboot_common/src/util/backend.dart';
|
||||
export 'package:reboot_common/src/util/build.dart';
|
||||
@@ -17,4 +18,5 @@ export 'package:reboot_common/src/util/dll.dart';
|
||||
export 'package:reboot_common/src/util/network.dart';
|
||||
export 'package:reboot_common/src/util/patcher.dart';
|
||||
export 'package:reboot_common/src/util/path.dart';
|
||||
export 'package:reboot_common/src/util/process.dart';
|
||||
export 'package:reboot_common/src/util/process.dart';
|
||||
export 'package:reboot_common/src/util/log.dart';
|
||||
@@ -1,2 +1,3 @@
|
||||
const String kDefaultBackendHost = "127.0.0.1";
|
||||
const int kDefaultBackendPort = 3551;
|
||||
const int kDefaultBackendPort = 3551;
|
||||
const int kDefaultXmppPort = 80;
|
||||
@@ -5,9 +5,9 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
extension FortniteVersionExtension on FortniteVersion {
|
||||
static DateTime _marker = DateTime.fromMicrosecondsSinceEpoch(0);
|
||||
static String _marker = "FortniteClient.mod";
|
||||
|
||||
static File? findExecutable(Directory directory, String name) {
|
||||
static File? findFile(Directory directory, String name) {
|
||||
try{
|
||||
final result = directory.listSync(recursive: true)
|
||||
.firstWhere((element) => path.basename(element.path) == name);
|
||||
@@ -18,39 +18,24 @@ extension FortniteVersionExtension on FortniteVersion {
|
||||
}
|
||||
|
||||
Future<File?> get shippingExecutable async {
|
||||
final result = findExecutable(location, "FortniteClient-Win64-Shipping.exe");
|
||||
final result = findFile(location, "FortniteClient-Win64-Shipping.exe");
|
||||
if(result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final lastModified = await _getLastModifiedTime(result);
|
||||
if(lastModified != _marker) {
|
||||
await Isolate.run(() => patchHeadless(result));
|
||||
await _setLastModifiedTime(result);
|
||||
final marker = findFile(location, _marker);
|
||||
if(marker != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
await Isolate.run(() => patchHeadless(result));
|
||||
await File("${location.path}\\$_marker").create();
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<void> _setLastModifiedTime(File result) async {
|
||||
try {
|
||||
await result.setLastModified(_marker);
|
||||
}catch(_) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
File? get launcherExecutable => findFile(location, "FortniteLauncher.exe");
|
||||
|
||||
Future<DateTime?> _getLastModifiedTime(File result) async {
|
||||
try {
|
||||
return await result.lastModified();
|
||||
}catch(_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
File? get eacExecutable => findFile(location, "FortniteClient-Win64-Shipping_EAC.exe");
|
||||
|
||||
File? get launcherExecutable => findExecutable(location, "FortniteLauncher.exe");
|
||||
|
||||
File? get eacExecutable => findExecutable(location, "FortniteClient-Win64-Shipping_EAC.exe");
|
||||
|
||||
File? get splashBitmap => findExecutable(location, "Splash.bmp");
|
||||
File? get splashBitmap => findFile(location, "Splash.bmp");
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:version/version.dart';
|
||||
|
||||
class FortniteBuild {
|
||||
final String identifier;
|
||||
final String version;
|
||||
final Version version;
|
||||
final String link;
|
||||
final bool available;
|
||||
|
||||
FortniteBuild({
|
||||
required this.identifier,
|
||||
required this.version,
|
||||
required this.link
|
||||
required this.link,
|
||||
required this.available
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
47
common/lib/src/model/fortnite_server.dart
Normal file
47
common/lib/src/model/fortnite_server.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
class FortniteServer {
|
||||
final String id;
|
||||
final String name;
|
||||
final String description;
|
||||
final String author;
|
||||
final String ip;
|
||||
final String version;
|
||||
final String? password;
|
||||
final DateTime timestamp;
|
||||
final bool discoverable;
|
||||
|
||||
FortniteServer({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.author,
|
||||
required this.ip,
|
||||
required this.version,
|
||||
required this.password,
|
||||
required this.timestamp,
|
||||
required this.discoverable
|
||||
});
|
||||
|
||||
factory FortniteServer.fromJson(json) => FortniteServer(
|
||||
id: json["id"],
|
||||
name: json["name"],
|
||||
description: json["description"],
|
||||
author: json["author"],
|
||||
ip: json["ip"],
|
||||
version: json["version"],
|
||||
password: json["password"],
|
||||
timestamp: json.containsKey("json") ? DateTime.parse(json["timestamp"]) : DateTime.now(),
|
||||
discoverable: json["discoverable"] ?? false
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"author": author,
|
||||
"ip": ip,
|
||||
"version": version,
|
||||
"password": password,
|
||||
"timestamp": timestamp.toString(),
|
||||
"discoverable": discoverable
|
||||
};
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:version/version.dart';
|
||||
|
||||
class FortniteVersion {
|
||||
String name;
|
||||
Version content;
|
||||
Directory location;
|
||||
|
||||
FortniteVersion.fromJson(json)
|
||||
: name = json["name"],
|
||||
: content = Version.parse(json["content"]),
|
||||
location = Directory(json["location"]);
|
||||
|
||||
FortniteVersion({required this.name, required this.location});
|
||||
FortniteVersion({required this.content, required this.location});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'content': content.toString(),
|
||||
'location': location.path
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => other is FortniteVersion && this.content == other.content;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class GameInstance {
|
||||
bool launched;
|
||||
bool movedToVirtualDesktop;
|
||||
bool tokenError;
|
||||
bool killed;
|
||||
GameInstance? child;
|
||||
|
||||
GameInstance({
|
||||
@@ -22,9 +23,19 @@ class GameInstance {
|
||||
required this.eacPid,
|
||||
required this.serverType,
|
||||
required this.child
|
||||
}): tokenError = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
|
||||
}): tokenError = false, killed = false, launched = false, movedToVirtualDesktop = false, injectedDlls = [];
|
||||
|
||||
void kill() {
|
||||
GameInstance? child = this;
|
||||
while(child != null) {
|
||||
child._kill();
|
||||
child = child.child;
|
||||
}
|
||||
}
|
||||
|
||||
void _kill() {
|
||||
launched = true;
|
||||
killed = true;
|
||||
Process.killPid(gamePid, ProcessSignal.sigabrt);
|
||||
if(launcherPid != null) {
|
||||
Process.killPid(launcherPid!, ProcessSignal.sigabrt);
|
||||
@@ -33,19 +44,6 @@ class GameInstance {
|
||||
Process.killPid(eacPid!, ProcessSignal.sigabrt);
|
||||
}
|
||||
}
|
||||
|
||||
bool get nestedHosting {
|
||||
GameInstance? child = this;
|
||||
while(child != null) {
|
||||
if(child.serverType != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
child = child.child;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum GameServerType {
|
||||
|
||||
@@ -4,6 +4,11 @@ class ServerResult {
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
ServerResult(this.type, {this.error, this.stackTrace});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServerResult{type: $type, error: $error, stackTrace: $stackTrace}';
|
||||
}
|
||||
}
|
||||
|
||||
enum ServerResultType {
|
||||
@@ -21,7 +26,8 @@ enum ServerResultType {
|
||||
freePortError,
|
||||
pingingRemote,
|
||||
pingingLocal,
|
||||
pingError;
|
||||
pingError,
|
||||
processError;
|
||||
|
||||
bool get isError => name.contains("Error");
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ Future<bool> isBackendPortFree() async => await pingBackend(kDefaultBackendHost,
|
||||
|
||||
Future<bool> freeBackendPort() async {
|
||||
await killProcessByPort(kDefaultBackendPort);
|
||||
await killProcessByPort(kDefaultXmppPort);
|
||||
final standardResult = await isBackendPortFree();
|
||||
if(standardResult) {
|
||||
return true;
|
||||
@@ -35,21 +36,24 @@ Future<bool> freeBackendPort() async {
|
||||
}
|
||||
|
||||
Future<Uri?> pingBackend(String host, int port, [bool https=false]) async {
|
||||
var hostName = host.replaceFirst("http://", "").replaceFirst("https://", "");
|
||||
var declaredScheme = host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||
final hostName = host.replaceFirst("http://", "").replaceFirst("https://", "");
|
||||
final declaredScheme = host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||
try{
|
||||
var uri = Uri(
|
||||
final uri = Uri(
|
||||
scheme: declaredScheme ?? (https ? "https" : "http"),
|
||||
host: hostName,
|
||||
port: port,
|
||||
path: "unknown"
|
||||
);
|
||||
var client = HttpClient()
|
||||
..connectionTimeout = const Duration(seconds: 5);
|
||||
var request = await client.getUrl(uri);
|
||||
var response = await request.close();
|
||||
return response.statusCode == 200 || response.statusCode == 404 ? uri : null;
|
||||
}catch(_){
|
||||
log("[BACKEND] Pinging $uri...");
|
||||
final client = HttpClient()
|
||||
..connectionTimeout = const Duration(seconds: 10);
|
||||
final request = await client.getUrl(uri);
|
||||
await request.close().timeout(const Duration(seconds: 10));
|
||||
log("[BACKEND] Ping successful");
|
||||
return uri;
|
||||
}catch(error){
|
||||
log("[BACKEND] Cannot ping backend: $error");
|
||||
return https || declaredScheme != null || isLocalHost(host) ? null : await pingBackend(host, port, true);
|
||||
}
|
||||
}
|
||||
@@ -59,16 +63,16 @@ Stream<String?> watchMatchmakingIp() async* {
|
||||
return;
|
||||
}
|
||||
|
||||
var observer = matchmakerConfigFile.parent.watch(events: FileSystemEvent.modify);
|
||||
final observer = matchmakerConfigFile.parent.watch(events: FileSystemEvent.modify);
|
||||
yield* observer.where((event) => event.path == matchmakerConfigFile.path).asyncMap((event) async {
|
||||
try {
|
||||
var config = Config.fromString(await matchmakerConfigFile.readAsString());
|
||||
var ip = config.get("GameServer", "ip");
|
||||
final config = Config.fromString(await matchmakerConfigFile.readAsString());
|
||||
final ip = config.get("GameServer", "ip");
|
||||
if(ip == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var port = config.get("GameServer", "port");
|
||||
final port = config.get("GameServer", "port");
|
||||
if(port == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -89,14 +93,14 @@ Stream<String?> watchMatchmakingIp() async* {
|
||||
}
|
||||
|
||||
Future<void> writeMatchmakingIp(String text) async {
|
||||
var exists = await matchmakerConfigFile.exists();
|
||||
final exists = await matchmakerConfigFile.exists();
|
||||
if(!exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
_semaphore.acquire();
|
||||
var splitIndex = text.indexOf(":");
|
||||
var ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
|
||||
final splitIndex = text.indexOf(":");
|
||||
final ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
|
||||
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort;
|
||||
if(port.isBlank) {
|
||||
port = kDefaultGameServerPort;
|
||||
@@ -104,7 +108,7 @@ Future<void> writeMatchmakingIp(String text) async {
|
||||
|
||||
_lastIp = ip;
|
||||
_lastPort = port;
|
||||
var config = Config.fromString(await matchmakerConfigFile.readAsString());
|
||||
final config = Config.fromString(await matchmakerConfigFile.readAsString());
|
||||
config.set("GameServer", "ip", ip);
|
||||
config.set("GameServer", "port", port);
|
||||
await matchmakerConfigFile.writeAsString(config.toString(), flush: true);
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:dio/io.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_common/src/extension/types.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
const String kStopBuildDownloadSignal = "kill";
|
||||
|
||||
@@ -23,7 +24,7 @@ Dio _buildDioInstance() {
|
||||
return dio;
|
||||
}
|
||||
|
||||
final String _archiveSourceUrl = "http://185.203.216.3/versions.json";
|
||||
final String _archiveSourceUrl = "https://raw.githubusercontent.com/simplyblk/Fortnitebuilds/main/README.md";
|
||||
final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$");
|
||||
const String _deniedConnectionError = "The connection was denied: your firewall might be blocking the download";
|
||||
const String _unavailableError = "The build downloader is not available right now";
|
||||
@@ -41,15 +42,35 @@ Future<List<FortniteBuild>> fetchBuilds(ignored) async {
|
||||
return [];
|
||||
}
|
||||
|
||||
final data = jsonDecode(response.data ?? "{}");
|
||||
var results = <FortniteBuild>[];
|
||||
for(final entry in data.entries) {
|
||||
results.add(FortniteBuild(
|
||||
identifier: entry.key,
|
||||
version: "${entry.value["title"]} (${entry.key})",
|
||||
link: entry.value["url"]
|
||||
));
|
||||
for (final line in response.data?.split("\n") ?? []) {
|
||||
if (!line.startsWith("|")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = line.substring(1, line.length - 1).split("|");
|
||||
if (parts.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var versionName = parts.first.trim();
|
||||
final separator = versionName.indexOf("-");
|
||||
if(separator != -1) {
|
||||
versionName = versionName.substring(0, separator);
|
||||
}
|
||||
|
||||
final link = parts.last.trim();
|
||||
try {
|
||||
results.add(FortniteBuild(
|
||||
version: Version.parse(versionName),
|
||||
link: link,
|
||||
available: link.endsWith(".zip") || link.endsWith(".rar")
|
||||
));
|
||||
} on FormatException {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
29
common/lib/src/util/log.dart
Normal file
29
common/lib/src/util/log.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:sync/semaphore.dart';
|
||||
|
||||
final File launcherLogFile = _createLoggingFile();
|
||||
final Semaphore _semaphore = Semaphore(1);
|
||||
|
||||
File _createLoggingFile() {
|
||||
final file = File("${logsDirectory.path}\\launcher.log");
|
||||
file.parent.createSync(recursive: true);
|
||||
if(file.existsSync()) {
|
||||
file.deleteSync();
|
||||
}
|
||||
file.createSync();
|
||||
return file;
|
||||
}
|
||||
|
||||
void log(String message) async {
|
||||
try {
|
||||
await _semaphore.acquire();
|
||||
print(message);
|
||||
await launcherLogFile.writeAsString("$message\n", mode: FileMode.append, flush: true);
|
||||
}catch(error) {
|
||||
print("[LOGGER_ERROR] An error occurred while logging: $error");
|
||||
}finally {
|
||||
_semaphore.release();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ Directory get installationDirectory =>
|
||||
Directory get dllsDirectory => Directory("${installationDirectory.path}\\dlls");
|
||||
|
||||
Directory get assetsDirectory {
|
||||
var directory = Directory("${installationDirectory.path}\\data\\flutter_assets\\assets");
|
||||
final directory = Directory("${installationDirectory.path}\\data\\flutter_assets\\assets");
|
||||
if(directory.existsSync()) {
|
||||
return directory;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ Future<bool> startElevatedProcess({required String executable, required String a
|
||||
return shellResult == 1;
|
||||
}
|
||||
|
||||
Future<Process> startProcess({required File executable, List<String>? args, bool useTempBatch = true, bool window = false, String? name}) async {
|
||||
Future<Process> startProcess({required File executable, List<String>? args, bool useTempBatch = true, bool window = false, String? name, Map<String, String>? environment}) async {
|
||||
final argsOrEmpty = args ?? [];
|
||||
if(useTempBatch) {
|
||||
final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process");
|
||||
@@ -115,6 +115,7 @@ Future<Process> startProcess({required File executable, List<String>? args, bool
|
||||
tempScriptFile.path,
|
||||
[],
|
||||
workingDirectory: executable.parent.path,
|
||||
environment: environment,
|
||||
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
|
||||
runInShell: window
|
||||
);
|
||||
@@ -202,6 +203,7 @@ Future<bool> watchProcess(int pid) async {
|
||||
return await completer.future;
|
||||
}
|
||||
|
||||
// TODO: Template
|
||||
List<String> createRebootArgs(String username, String password, bool host, GameServerType hostType, bool log, String additionalArgs) {
|
||||
if(password.isEmpty) {
|
||||
username = '${_parseUsername(username, host)}@projectreboot.dev';
|
||||
|
||||
Reference in New Issue
Block a user