mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
10.0.9
This commit is contained in:
@@ -16,7 +16,12 @@ const Command _build = Command(name: 'versions', parameters: [], subCommands: [_
|
|||||||
const Command _play = Command(name: 'play', parameters: [], subCommands: []);
|
const Command _play = Command(name: 'play', parameters: [], subCommands: []);
|
||||||
const Command _host = Command(name: 'host', parameters: [], subCommands: []);
|
const Command _host = Command(name: 'host', parameters: [], subCommands: []);
|
||||||
const Command _backend = Command(name: 'backend', parameters: [], subCommands: []);
|
const Command _backend = Command(name: 'backend', parameters: [], subCommands: []);
|
||||||
final List<String> _versions = downloadableBuilds.map((build) => build.version.toString()).toList(growable: false);
|
final List<String> _versions = downloadableBuilds.map((build) => build.gameVersion).toList(growable: false);
|
||||||
|
const String _playVersionAction = 'Play';
|
||||||
|
const String _hostVersionAction = 'Host';
|
||||||
|
const String _deleteVersionAction = 'Delete';
|
||||||
|
const String _infoVersionAction = 'Info';
|
||||||
|
const List<String> _versionActions = [_playVersionAction, _hostVersionAction, _deleteVersionAction, _infoVersionAction];
|
||||||
|
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
enableLoggingToConsole = false;
|
enableLoggingToConsole = false;
|
||||||
@@ -98,19 +103,46 @@ Future<void> _handleBuildCommand(CommandCall? call) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleBuildListCommand(CommandCall commandCall) {
|
void _handleBuildListCommand(CommandCall commandCall) {
|
||||||
final versions = readVersions();
|
List<FortniteVersion> versions;
|
||||||
|
try {
|
||||||
|
versions = readVersions();
|
||||||
|
}catch(error) {
|
||||||
|
print("❌ $error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(versions.isEmpty) {
|
||||||
|
print("❌ No versions found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final versionSelector = Select.withTheme(
|
final versionSelector = Select.withTheme(
|
||||||
prompt: ' Select a version:',
|
prompt: ' Select a version:',
|
||||||
options: versions.map((version) => version.content.toString()).toList(growable: false),
|
options: versions.map((version) => version.gameVersion).toList(growable: false),
|
||||||
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
||||||
);
|
);
|
||||||
final version = versions[versionSelector.interact()];
|
final version = versions[versionSelector.interact()];
|
||||||
final actionSelector = Select.withTheme(
|
final actionSelector = Select.withTheme(
|
||||||
prompt: ' Select an action:',
|
prompt: ' Select an action:',
|
||||||
options: ['Play', 'Host', 'Delete', 'Open in Explorer'],
|
options: _versionActions,
|
||||||
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
||||||
);
|
);
|
||||||
actionSelector.interact();
|
final action = _versionActions[actionSelector.interact()];
|
||||||
|
switch(action) {
|
||||||
|
case _playVersionAction:
|
||||||
|
break;
|
||||||
|
case _hostVersionAction:
|
||||||
|
break;
|
||||||
|
case _deleteVersionAction:
|
||||||
|
break;
|
||||||
|
case _infoVersionAction:
|
||||||
|
print('');
|
||||||
|
print("""
|
||||||
|
🏷️ ${"Version: ".cyan()} ${version.gameVersion}
|
||||||
|
📁 ${"Location:".cyan()} ${version.location.path}
|
||||||
|
""".green());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleBuildImportCommand(CommandCall call) async {
|
Future<void> _handleBuildImportCommand(CommandCall call) async {
|
||||||
@@ -125,8 +157,8 @@ Future<void> _handleBuildImportCommand(CommandCall call) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final fortniteVersion = FortniteVersion(
|
final fortniteVersion = FortniteVersion(
|
||||||
name: "dummy",
|
name: '',
|
||||||
content: Version.parse(version),
|
gameVersion: version,
|
||||||
location: Directory(path)
|
location: Directory(path)
|
||||||
);
|
);
|
||||||
writeVersion(fortniteVersion);
|
writeVersion(fortniteVersion);
|
||||||
@@ -197,18 +229,32 @@ Future<bool> _checkBuildPath(String path, bool existing) async {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
final checker = Spinner.withTheme(
|
final checker = Spinner.withTheme(
|
||||||
icon: '✅',
|
icon: '✅',
|
||||||
rightPrompt: (status) => status != SpinnerStateType.inProgress ? 'Finished looking for FortniteClient-Win64-Shipping.exe' : 'Looking for FortniteClient-Win64-Shipping.exe...',
|
rightPrompt: (status) {
|
||||||
|
switch(status) {
|
||||||
|
case SpinnerStateType.inProgress:
|
||||||
|
return 'Looking for FortniteClient-Win64-Shipping.exe...';
|
||||||
|
case SpinnerStateType.done:
|
||||||
|
return 'Finished looking for FortniteClient-Win64-Shipping.exe';
|
||||||
|
case SpinnerStateType.failed:
|
||||||
|
return 'Failed to look for FortniteClient-Win64-Shipping.exe';
|
||||||
|
}
|
||||||
|
},
|
||||||
theme: Theme.colorfulTheme.copyWith(successSuffix: '', errorPrefix: '❌', spinners: '🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛'.split(' '))
|
theme: Theme.colorfulTheme.copyWith(successSuffix: '', errorPrefix: '❌', spinners: '🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛'.split(' '))
|
||||||
).interact();
|
).interact();
|
||||||
final result = await Future.wait([
|
|
||||||
Future.delayed(const Duration(seconds: 1)).then((_) => true),
|
final files = await findFiles(directory, "FortniteClient-Win64-Shipping.exe")
|
||||||
Isolate.run(() => FortniteVersionExtension.findFiles(directory, "FortniteClient-Win64-Shipping.exe") != null)
|
.withMinimumDuration(const Duration(seconds: 1));
|
||||||
]).then((values) => values.reduce((first, second) => first && second));
|
if(files.isEmpty) {
|
||||||
checker.done();
|
print("❌ Cannot find FortniteClient-Win64-Shipping.exe in $path");
|
||||||
if(!result) {
|
|
||||||
print("❌ Cannot find FortniteClient-Win64-Shipping.exe: $path");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(files.length > 1) {
|
||||||
|
print("❌ There must be only one executable named FortniteClient-Win64-Shipping.exe in $path");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
checker.done();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -309,7 +355,7 @@ Future<void> _handleBuildDownloadCommand(CommandCall call) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final parsedVersion = Version.parse(version);
|
final parsedVersion = Version.parse(version);
|
||||||
final build = downloadableBuilds.firstWhereOrNull((build) => build.version == parsedVersion);
|
final build = downloadableBuilds.firstWhereOrNull((build) => Version.parse(build.gameVersion) == parsedVersion);
|
||||||
if(build == null) {
|
if(build == null) {
|
||||||
print('');
|
print('');
|
||||||
print("❌ Cannot find mirror for version: $parsedVersion");
|
print("❌ Cannot find mirror for version: $parsedVersion");
|
||||||
@@ -340,7 +386,7 @@ Future<void> _handleBuildDownloadCommand(CommandCall call) async {
|
|||||||
receivePort.close();
|
receivePort.close();
|
||||||
final fortniteVersion = FortniteVersion(
|
final fortniteVersion = FortniteVersion(
|
||||||
name: "dummy",
|
name: "dummy",
|
||||||
content: parsedVersion,
|
gameVersion: version,
|
||||||
location: parsedDirectory
|
location: parsedDirectory
|
||||||
);
|
);
|
||||||
writeVersion(fortniteVersion);
|
writeVersion(fortniteVersion);
|
||||||
|
|||||||
@@ -9,10 +9,20 @@ List<FortniteVersion> readVersions() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
Iterable decodedVersionsJson = jsonDecode(file.readAsStringSync());
|
Iterable decodedVersionsJson = jsonDecode(file.readAsStringSync());
|
||||||
return decodedVersionsJson
|
return decodedVersionsJson
|
||||||
.map((entry) => FortniteVersion.fromJson(entry))
|
.map((entry) {
|
||||||
|
try {
|
||||||
|
return FortniteVersion.fromJson(entry);
|
||||||
|
}catch(error) {
|
||||||
|
throw "Cannot parse version: $error";
|
||||||
|
}
|
||||||
|
})
|
||||||
.toList();
|
.toList();
|
||||||
|
}catch(error) {
|
||||||
|
throw "Cannot parse versions: $error";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeVersion(FortniteVersion version) {
|
void writeVersion(FortniteVersion version) {
|
||||||
|
|||||||
@@ -8,3 +8,13 @@ extension IterableExtension<E> on Iterable<E> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension FutureExtension<T> on Future<T> {
|
||||||
|
Future<T> withMinimumDuration(Duration duration) async {
|
||||||
|
final result = await Future.wait([
|
||||||
|
Future.delayed(duration),
|
||||||
|
this
|
||||||
|
]);
|
||||||
|
return result.last;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,4 +30,5 @@ const String kShippingExe = "FortniteClient-Win64-Shipping.exe";
|
|||||||
const String kLauncherExe = "FortniteLauncher.exe";
|
const String kLauncherExe = "FortniteLauncher.exe";
|
||||||
const String kEacExe = "FortniteClient-Win64-Shipping_EAC.exe";
|
const String kEacExe = "FortniteClient-Win64-Shipping_EAC.exe";
|
||||||
const String kCrashReportExe = "CrashReportClient.exe";
|
const String kCrashReportExe = "CrashReportClient.exe";
|
||||||
|
const String kGFSDKAftermathLibDll = "GFSDK_Aftermath_Lib.dll";
|
||||||
final Version kMaxAllowedVersion = Version.parse("30.10");
|
final Version kMaxAllowedVersion = Version.parse("30.10");
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:version/version.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class GameInstance {
|
class GameInstance {
|
||||||
final String version;
|
final String version;
|
||||||
|
final bool host;
|
||||||
final int gamePid;
|
final int gamePid;
|
||||||
final int? launcherPid;
|
final int? launcherPid;
|
||||||
final int? eacPid;
|
final int? eacPid;
|
||||||
@@ -18,6 +18,7 @@ class GameInstance {
|
|||||||
|
|
||||||
GameInstance({
|
GameInstance({
|
||||||
required this.version,
|
required this.version,
|
||||||
|
required this.host,
|
||||||
required this.gamePid,
|
required this.gamePid,
|
||||||
required this.launcherPid,
|
required this.launcherPid,
|
||||||
required this.eacPid,
|
required this.eacPid,
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ final Semaphore _semaphore = Semaphore();
|
|||||||
String? _lastIp;
|
String? _lastIp;
|
||||||
String? _lastPort;
|
String? _lastPort;
|
||||||
|
|
||||||
Stream<ServerResult> startBackend({required ServerType type, required String host, required String port, required bool detached, required void Function(String) onError}) async* {
|
Stream<ServerResult> startBackend({
|
||||||
|
required ServerType type,
|
||||||
|
required String host,
|
||||||
|
required String port,
|
||||||
|
required bool detached,
|
||||||
|
required void Function(String) onError
|
||||||
|
}) async* {
|
||||||
Process? process;
|
Process? process;
|
||||||
HttpServer? server;
|
HttpServer? server;
|
||||||
try {
|
try {
|
||||||
@@ -147,7 +153,13 @@ Future<Process> startEmbeddedBackend(bool detached, {void Function(String)? onEr
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(!detached) {
|
if(!detached) {
|
||||||
process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode"));
|
process.exitCode.then((exitCode) {
|
||||||
|
if(!killed) {
|
||||||
|
log("[BACKEND] Exit code: $exitCode");
|
||||||
|
onError?.call("Exit code: $exitCode");
|
||||||
|
killed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ Future<String> extractGameVersion(Directory directory) => Isolate.run(() async {
|
|||||||
log("[VERSION] Engine build: $engineVersionBuild");
|
log("[VERSION] Engine build: $engineVersionBuild");
|
||||||
gameVersion = _buildToGameVersion[engineVersionBuild] ?? defaultGameVersion;
|
gameVersion = _buildToGameVersion[engineVersionBuild] ?? defaultGameVersion;
|
||||||
}
|
}
|
||||||
log("[VERSION] Returning $gameVersion");
|
log("[VERSION] Parsed game version: $gameVersion");
|
||||||
return gameVersion;
|
return gameVersion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,8 +243,8 @@
|
|||||||
"gameServerStarted": "The game server was started successfully",
|
"gameServerStarted": "The game server was started successfully",
|
||||||
"gameClientStarted": "The game client was started successfully",
|
"gameClientStarted": "The game client was started successfully",
|
||||||
"checkingGameServer": "Checking if other players can join the game server...",
|
"checkingGameServer": "Checking if other players can join the game server...",
|
||||||
"checkGameServerFixMessage": "Other players can't join the game server as port {port} isn't open",
|
"checkGameServerFixMessage": "The game server was started successfully, but other players can't join yet as port {port} isn't open",
|
||||||
"checkGameServerFixAction": "Fix",
|
"checkGameServerFixAction": "Learn more",
|
||||||
"infoName": "Info",
|
"infoName": "Info",
|
||||||
"emptyVersionName": "Empty version name",
|
"emptyVersionName": "Empty version name",
|
||||||
"versionAlreadyExists": "This version already exists",
|
"versionAlreadyExists": "This version already exists",
|
||||||
@@ -379,5 +379,9 @@
|
|||||||
"importedVersion": "Successfully imported version",
|
"importedVersion": "Successfully imported version",
|
||||||
"importVersionMissingShippingExeError": "Cannot import version: {name} should exist in the directory",
|
"importVersionMissingShippingExeError": "Cannot import version: {name} should exist in the directory",
|
||||||
"importVersionMultipleShippingExesError": "Cannot import version: only one {name} should exist in the directory",
|
"importVersionMultipleShippingExesError": "Cannot import version: only one {name} should exist in the directory",
|
||||||
"importVersionUnsupportedVersionError": "This version of Fortnite is not supported by the launcher"
|
"importVersionUnsupportedVersionError": "This version of Fortnite is not supported by the launcher",
|
||||||
|
"downloadManually": "Download manually",
|
||||||
|
"gameServerPortEqualsBackendPort": "The game server port cannot be {backendPort} as its reserved for the backend",
|
||||||
|
"gameServer": "game server",
|
||||||
|
"client": "client"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ class BackendController extends GetxController {
|
|||||||
port: port.text,
|
port: port.text,
|
||||||
detached: detached.value,
|
detached: detached.value,
|
||||||
onError: (errorMessage) {
|
onError: (errorMessage) {
|
||||||
|
if(started.value) {
|
||||||
stop(interactive: false);
|
stop(interactive: false);
|
||||||
Get.find<GameController>()
|
Get.find<GameController>()
|
||||||
.instance
|
.instance
|
||||||
@@ -182,6 +183,7 @@ class BackendController extends GetxController {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
final completer = Completer<bool>();
|
final completer = Completer<bool>();
|
||||||
InfoBarEntry? entry;
|
InfoBarEntry? entry;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const infoBarLongDuration = Duration(seconds: 4);
|
|||||||
const infoBarShortDuration = Duration(seconds: 2);
|
const infoBarShortDuration = Duration(seconds: 2);
|
||||||
const _height = 64.0;
|
const _height = 64.0;
|
||||||
|
|
||||||
InfoBarEntry showRebootInfoBar(dynamic text, {
|
InfoBarEntry showRebootInfoBar(String text, {
|
||||||
InfoBarSeverity severity = InfoBarSeverity.info,
|
InfoBarSeverity severity = InfoBarSeverity.info,
|
||||||
bool loading = false,
|
bool loading = false,
|
||||||
Duration? duration = infoBarShortDuration,
|
Duration? duration = infoBarShortDuration,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:local_notifier/local_notifier.dart';
|
import 'package:local_notifier/local_notifier.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
import 'package:port_forwarder/port_forwarder.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||||
@@ -14,7 +15,6 @@ import 'package:reboot_launcher/src/controller/game_controller.dart';
|
|||||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||||
import 'package:reboot_launcher/src/page/pages.dart';
|
|
||||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@@ -110,6 +110,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Backend works");
|
log("[${host ? 'HOST' : 'GAME'}] Backend works");
|
||||||
|
|
||||||
final headless = _hostingController.headless.value;
|
final headless = _hostingController.headless.value;
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($headless)");
|
log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($headless)");
|
||||||
final linkedHostingInstance = await _startMatchMakingServer(version, host, headless, false);
|
final linkedHostingInstance = await _startMatchMakingServer(version, host, headless, false);
|
||||||
@@ -121,6 +122,16 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(host || linkedHostingInstance != null) {
|
||||||
|
if (_dllController.gameServerPort.text == kDefaultBackendPort.toString()) {
|
||||||
|
_onStop(
|
||||||
|
reason: _StopReason.gameServerPortError,
|
||||||
|
host: host
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!host) {
|
if(!host) {
|
||||||
_showLaunchingGameClientWidget(version, headless, linkedHostingInstance != null);
|
_showLaunchingGameClientWidget(version, headless, linkedHostingInstance != null);
|
||||||
}else {
|
}else {
|
||||||
@@ -213,6 +224,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
log("[${host ? 'HOST' : 'GAME'}] Created game process: ${gameProcess}");
|
log("[${host ? 'HOST' : 'GAME'}] Created game process: ${gameProcess}");
|
||||||
final instance = GameInstance(
|
final instance = GameInstance(
|
||||||
version: version.gameVersion,
|
version: version.gameVersion,
|
||||||
|
host: host,
|
||||||
gamePid: gameProcess,
|
gamePid: gameProcess,
|
||||||
launcherPid: launcherProcess,
|
launcherPid: launcherProcess,
|
||||||
eacPid: eacProcess,
|
eacPid: eacProcess,
|
||||||
@@ -232,6 +244,22 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
|
|
||||||
Future<int?> _createGameProcess(FortniteVersion version, bool host, bool headless, GameInstance? linkedHosting) async {
|
Future<int?> _createGameProcess(FortniteVersion version, bool host, bool headless, GameInstance? linkedHosting) async {
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Starting game process...");
|
log("[${host ? 'HOST' : 'GAME'}] Starting game process...");
|
||||||
|
try {
|
||||||
|
log("[${host ? 'HOST' : 'GAME'}] Deleting $kGFSDKAftermathLibDll...");
|
||||||
|
final dlls = await findFiles(version.location, kGFSDKAftermathLibDll);
|
||||||
|
log("[${host ? 'HOST' : 'GAME'}] Found ${dlls.length} to delete for $kGFSDKAftermathLibDll");
|
||||||
|
for(final dll in dlls) {
|
||||||
|
log("[${host ? 'HOST' : 'GAME'}] Deleting ${dll.path}...");
|
||||||
|
final result = await delete(dll);
|
||||||
|
if(result) {
|
||||||
|
log("[${host ? 'HOST' : 'GAME'}] Deleted ${dll.path}");
|
||||||
|
}else {
|
||||||
|
log("[${host ? 'HOST' : 'GAME'}] Cannot delete ${dll.path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(_) {
|
||||||
|
|
||||||
|
}
|
||||||
final shippingExecutables = await findFiles(version.location, kShippingExe);
|
final shippingExecutables = await findFiles(version.location, kShippingExe);
|
||||||
if(shippingExecutables.isEmpty){
|
if(shippingExecutables.isEmpty){
|
||||||
log("[${host ? 'HOST' : 'GAME'}] No game executable found");
|
log("[${host ? 'HOST' : 'GAME'}] No game executable found");
|
||||||
@@ -421,36 +449,30 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
_gameClientInfoBar?.close();
|
_gameClientInfoBar?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
final theme = FluentTheme.of(appNavigatorKey.currentContext!);
|
|
||||||
try {
|
try {
|
||||||
_gameServerInfoBar = showRebootInfoBar(
|
|
||||||
translations.waitingForGameServer,
|
|
||||||
loading: true,
|
|
||||||
duration: null
|
|
||||||
);
|
|
||||||
final gameServerPort = _dllController.gameServerPort.text;
|
final gameServerPort = _dllController.gameServerPort.text;
|
||||||
final pingOperation = pingGameServerOrTimeout(
|
final started = await _checkLocalGameServer(gameServerPort);
|
||||||
"127.0.0.1:$gameServerPort",
|
if(!started) {
|
||||||
const Duration(minutes: 2)
|
if (_hostingController.instance.value?.killed != true) {
|
||||||
);
|
|
||||||
this._pingOperation = pingOperation;
|
|
||||||
final localPingResult = await pingOperation.future;
|
|
||||||
_gameServerInfoBar?.close();
|
|
||||||
if (!localPingResult) {
|
|
||||||
showRebootInfoBar(
|
showRebootInfoBar(
|
||||||
translations.gameServerStartWarning,
|
translations.gameServerStartWarning,
|
||||||
severity: InfoBarSeverity.error,
|
severity: InfoBarSeverity.error,
|
||||||
duration: infoBarLongDuration
|
duration: infoBarLongDuration
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_backendController.joinLocalhost();
|
_backendController.joinLocalhost();
|
||||||
final accessible = await _checkGameServer(theme, gameServerPort);
|
final accessible = await _checkPublicGameServer(gameServerPort);
|
||||||
if (!accessible) {
|
if (!accessible) {
|
||||||
showRebootInfoBar(
|
showRebootInfoBar(
|
||||||
translations.gameServerStartLocalWarning,
|
translations.gameServerStartLocalWarning,
|
||||||
severity: InfoBarSeverity.warning,
|
severity: InfoBarSeverity.warning,
|
||||||
duration: infoBarLongDuration
|
duration: infoBarLongDuration,
|
||||||
|
action: Button(
|
||||||
|
onPressed: () => launchUrlString("https://github.com/Auties00/reboot_launcher/blob/master/documentation/$currentLocale/PortForwarding.md"),
|
||||||
|
child: Text(translations.checkGameServerFixAction),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -469,7 +491,30 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _checkGameServer(FluentThemeData theme, String gameServerPort) async {
|
Future<bool> _checkLocalGameServer(String gameServerPort) async {
|
||||||
|
try {
|
||||||
|
_gameServerInfoBar = showRebootInfoBar(
|
||||||
|
translations.waitingForGameServer,
|
||||||
|
loading: true,
|
||||||
|
duration: null
|
||||||
|
);
|
||||||
|
final gameServerPort = _dllController.gameServerPort.text;
|
||||||
|
final pingOperation = pingGameServerOrTimeout(
|
||||||
|
"127.0.0.1:$gameServerPort",
|
||||||
|
const Duration(minutes: 2)
|
||||||
|
);
|
||||||
|
this._pingOperation = pingOperation;
|
||||||
|
final localPingResult = await pingOperation.future;
|
||||||
|
_gameServerInfoBar?.close();
|
||||||
|
return localPingResult;
|
||||||
|
}catch(_) {
|
||||||
|
_gameServerInfoBar?.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<bool> _checkPublicGameServer(String gameServerPort) async {
|
||||||
try {
|
try {
|
||||||
_gameServerInfoBar = showRebootInfoBar(
|
_gameServerInfoBar = showRebootInfoBar(
|
||||||
translations.checkingGameServer,
|
translations.checkingGameServer,
|
||||||
@@ -477,43 +522,64 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
duration: null
|
duration: null
|
||||||
);
|
);
|
||||||
final publicIp = await Ipify.ipv4();
|
final publicIp = await Ipify.ipv4();
|
||||||
final available = await pingGameServer("$publicIp:$gameServerPort");
|
var pingOperation = await pingGameServerOrTimeout(
|
||||||
if(available) {
|
"$publicIp:$gameServerPort",
|
||||||
|
const Duration(seconds: 10)
|
||||||
|
);
|
||||||
|
_pingOperation = pingOperation;
|
||||||
|
var publicPingResult = await pingOperation.future;
|
||||||
|
if (publicPingResult) {
|
||||||
_gameServerInfoBar?.close();
|
_gameServerInfoBar?.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final pingOperation = pingGameServerOrTimeout(
|
final gateway = await Gateway.discover();
|
||||||
|
if (gateway == null) {
|
||||||
|
_gameServerInfoBar?.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final forwarded = await gateway.openPort(
|
||||||
|
protocol: PortType.udp,
|
||||||
|
externalPort: int.parse(gameServerPort),
|
||||||
|
portDescription: "Reboot Game Server"
|
||||||
|
);
|
||||||
|
if (!forwarded) {
|
||||||
|
_gameServerInfoBar?.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the modem a couple of seconds just in case
|
||||||
|
// This is not technically necessary, but I can't guarantee that the modem has no race conditions
|
||||||
|
// So might as well wait
|
||||||
|
await Future.delayed(const Duration(seconds: 5));
|
||||||
|
|
||||||
|
pingOperation = await pingGameServerOrTimeout(
|
||||||
"$publicIp:$gameServerPort",
|
"$publicIp:$gameServerPort",
|
||||||
const Duration(days: 1)
|
const Duration(seconds: 10)
|
||||||
);
|
);
|
||||||
this._pingOperation = pingOperation;
|
_pingOperation = pingOperation;
|
||||||
|
publicPingResult = await pingOperation.future;
|
||||||
_gameServerInfoBar?.close();
|
_gameServerInfoBar?.close();
|
||||||
_gameServerInfoBar = showRebootInfoBar(
|
return publicPingResult;
|
||||||
translations.checkGameServerFixMessage(gameServerPort),
|
|
||||||
action: Button(
|
|
||||||
onPressed: () => launchUrlString("https://github.com/Auties00/reboot_launcher/blob/master/documentation/$currentLocale/PortForwarding.md"),
|
|
||||||
child: Text(translations.checkGameServerFixAction),
|
|
||||||
),
|
|
||||||
severity: InfoBarSeverity.warning,
|
|
||||||
duration: null,
|
|
||||||
loading: true
|
|
||||||
);
|
|
||||||
final result = await pingOperation.future;
|
|
||||||
_gameServerInfoBar?.close();
|
|
||||||
return result;
|
|
||||||
}catch(_) {
|
}catch(_) {
|
||||||
_gameServerInfoBar?.close();
|
_gameServerInfoBar?.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onStop({required _StopReason reason, required bool host, String? error, StackTrace? stackTrace}) async {
|
Future<void> _onStop({
|
||||||
|
required _StopReason reason,
|
||||||
|
required bool host,
|
||||||
|
String? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
bool interactive = true
|
||||||
|
}) async {
|
||||||
if(host) {
|
if(host) {
|
||||||
try {
|
try {
|
||||||
_pingOperation?.complete(false);
|
_pingOperation?.complete(false);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Ignore: might be running, don't bother checking
|
// Ignore: might have been already terminated, don't bother checking
|
||||||
} finally {
|
} finally {
|
||||||
_pingOperation = null;
|
_pingOperation = null;
|
||||||
}
|
}
|
||||||
@@ -545,11 +611,16 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
if(child != null) {
|
if(child != null) {
|
||||||
await _onStop(
|
await _onStop(
|
||||||
reason: reason,
|
reason: reason,
|
||||||
host: host
|
host: child.host,
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
interactive: false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setStarted(host, false);
|
_setStarted(host, false);
|
||||||
|
|
||||||
|
if(interactive) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if(host == true) {
|
if(host == true) {
|
||||||
_gameServerInfoBar?.close();
|
_gameServerInfoBar?.close();
|
||||||
@@ -595,8 +666,9 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case _StopReason.corruptedVersionError:
|
case _StopReason.corruptedVersionError:
|
||||||
|
final injectedDlls = instance?.injectedDlls ?? [];
|
||||||
showRebootInfoBar(
|
showRebootInfoBar(
|
||||||
translations.corruptedVersionError,
|
translations.corruptedVersionError(injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||||
severity: InfoBarSeverity.error,
|
severity: InfoBarSeverity.error,
|
||||||
duration: infoBarLongDuration,
|
duration: infoBarLongDuration,
|
||||||
action: Button(
|
action: Button(
|
||||||
@@ -634,7 +706,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
break;
|
break;
|
||||||
case _StopReason.crash:
|
case _StopReason.crash:
|
||||||
showRebootInfoBar(
|
showRebootInfoBar(
|
||||||
translations.fortniteCrashError(host ? "game server" : "client"),
|
translations.fortniteCrashError(host ? translations.gameServer : translations.client),
|
||||||
severity: InfoBarSeverity.error,
|
severity: InfoBarSeverity.error,
|
||||||
duration: infoBarLongDuration,
|
duration: infoBarLongDuration,
|
||||||
);
|
);
|
||||||
@@ -646,6 +718,14 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
duration: infoBarLongDuration,
|
duration: infoBarLongDuration,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case _StopReason.gameServerPortError:
|
||||||
|
showRebootInfoBar(
|
||||||
|
translations.gameServerPortEqualsBackendPort(kDefaultBackendPort),
|
||||||
|
severity: InfoBarSeverity.error,
|
||||||
|
duration: infoBarLongDuration,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,6 +845,7 @@ enum _StopReason {
|
|||||||
matchmakerError,
|
matchmakerError,
|
||||||
tokenError,
|
tokenError,
|
||||||
unknownError,
|
unknownError,
|
||||||
|
gameServerPortError,
|
||||||
exitCode,
|
exitCode,
|
||||||
crash;
|
crash;
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class _HostingPageState extends RebootPageState<HostPage> {
|
|||||||
FluentIcons.password_24_regular
|
FluentIcons.password_24_regular
|
||||||
),
|
),
|
||||||
title: Text(translations.hostGameServerPasswordName),
|
title: Text(translations.hostGameServerPasswordName),
|
||||||
subtitle: Text(translations.hostGameServerDescriptionDescription),
|
subtitle: Text(translations.hostGameServerPasswordDescription),
|
||||||
content: Obx(() => OverlayTarget(
|
content: Obx(() => OverlayTarget(
|
||||||
key: hostInfoPasswordOverlayTargetKey,
|
key: hostInfoPasswordOverlayTargetKey,
|
||||||
child: TextFormBox(
|
child: TextFormBox(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'dart:io';
|
|||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||||
@@ -12,6 +11,7 @@ import 'package:reboot_launcher/src/util/os.dart';
|
|||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:reboot_launcher/src/util/types.dart';
|
import 'package:reboot_launcher/src/util/types.dart';
|
||||||
import 'package:reboot_launcher/src/widget/file/file_selector.dart';
|
import 'package:reboot_launcher/src/widget/file/file_selector.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:windows_taskbar/windows_taskbar.dart';
|
import 'package:windows_taskbar/windows_taskbar.dart';
|
||||||
|
|
||||||
class DownloadVersionDialog extends StatefulWidget {
|
class DownloadVersionDialog extends StatefulWidget {
|
||||||
@@ -75,15 +75,24 @@ class _DownloadVersionDialogState extends State<DownloadVersionDialog> {
|
|||||||
buttons: _stopButton
|
buttons: _stopButton
|
||||||
);
|
);
|
||||||
case _DownloadStatus.error:
|
case _DownloadStatus.error:
|
||||||
return ErrorDialog(
|
final build = _build.value;
|
||||||
exception: _error ?? Exception(translations.unknownError),
|
var error = _error?.toString() ?? translations.unknownError;
|
||||||
stackTrace: _stackTrace,
|
|
||||||
errorMessageBuilder: (exception) {
|
|
||||||
var error = exception.toString();
|
|
||||||
error = error.after("Error: ")?.replaceAll(":", ",") ?? error.after(": ") ?? error;
|
error = error.after("Error: ")?.replaceAll(":", ",") ?? error.after(": ") ?? error;
|
||||||
error = error.toLowerCase();
|
error = error.toLowerCase();
|
||||||
return translations.downloadVersionError(error);
|
return InfoDialog(
|
||||||
}
|
text: translations.downloadVersionError(error),
|
||||||
|
buttons: [
|
||||||
|
DialogButton(
|
||||||
|
type: ButtonType.secondary,
|
||||||
|
text: translations.defaultDialogSecondaryAction
|
||||||
|
),
|
||||||
|
if(build != null)
|
||||||
|
DialogButton(
|
||||||
|
type: ButtonType.primary,
|
||||||
|
text: translations.downloadManually,
|
||||||
|
onTap: () => launchUrlString(build.link)
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
case _DownloadStatus.done:
|
case _DownloadStatus.done:
|
||||||
return InfoDialog(
|
return InfoDialog(
|
||||||
|
|||||||
@@ -266,9 +266,6 @@ class _VersionSelectorState extends State<VersionSelector> {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
GameController get gameController => _gameController;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _ContextualOption {
|
enum _ContextualOption {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: reboot_launcher
|
name: reboot_launcher
|
||||||
description: Graphical User Interface for Project Reboot
|
description: Graphical User Interface for Project Reboot
|
||||||
version: "10.0.8"
|
version: "10.0.9"
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ dependencies:
|
|||||||
file_picker: ^8.1.2
|
file_picker: ^8.1.2
|
||||||
url_launcher: ^6.3.0
|
url_launcher: ^6.3.0
|
||||||
local_notifier: ^0.1.6
|
local_notifier: ^0.1.6
|
||||||
|
port_forwarder: ^1.0.0
|
||||||
|
|
||||||
# Server browser
|
# Server browser
|
||||||
supabase_flutter: ^2.7.0
|
supabase_flutter: ^2.7.0
|
||||||
|
|||||||
Reference in New Issue
Block a user