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 _host = Command(name: 'host', 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 {
|
||||
enableLoggingToConsole = false;
|
||||
@@ -98,19 +103,46 @@ Future<void> _handleBuildCommand(CommandCall? call) async {
|
||||
}
|
||||
|
||||
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(
|
||||
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: '❌')
|
||||
);
|
||||
final version = versions[versionSelector.interact()];
|
||||
final actionSelector = Select.withTheme(
|
||||
prompt: ' Select an action:',
|
||||
options: ['Play', 'Host', 'Delete', 'Open in Explorer'],
|
||||
options: _versionActions,
|
||||
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 {
|
||||
@@ -125,8 +157,8 @@ Future<void> _handleBuildImportCommand(CommandCall call) async {
|
||||
}
|
||||
|
||||
final fortniteVersion = FortniteVersion(
|
||||
name: "dummy",
|
||||
content: Version.parse(version),
|
||||
name: '',
|
||||
gameVersion: version,
|
||||
location: Directory(path)
|
||||
);
|
||||
writeVersion(fortniteVersion);
|
||||
@@ -197,18 +229,32 @@ Future<bool> _checkBuildPath(String path, bool existing) async {
|
||||
if (existing) {
|
||||
final checker = Spinner.withTheme(
|
||||
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(' '))
|
||||
).interact();
|
||||
final result = await Future.wait([
|
||||
Future.delayed(const Duration(seconds: 1)).then((_) => true),
|
||||
Isolate.run(() => FortniteVersionExtension.findFiles(directory, "FortniteClient-Win64-Shipping.exe") != null)
|
||||
]).then((values) => values.reduce((first, second) => first && second));
|
||||
checker.done();
|
||||
if(!result) {
|
||||
print("❌ Cannot find FortniteClient-Win64-Shipping.exe: $path");
|
||||
|
||||
final files = await findFiles(directory, "FortniteClient-Win64-Shipping.exe")
|
||||
.withMinimumDuration(const Duration(seconds: 1));
|
||||
if(files.isEmpty) {
|
||||
print("❌ Cannot find FortniteClient-Win64-Shipping.exe in $path");
|
||||
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;
|
||||
@@ -309,7 +355,7 @@ Future<void> _handleBuildDownloadCommand(CommandCall call) async {
|
||||
}
|
||||
|
||||
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) {
|
||||
print('');
|
||||
print("❌ Cannot find mirror for version: $parsedVersion");
|
||||
@@ -339,8 +385,8 @@ Future<void> _handleBuildDownloadCommand(CommandCall call) async {
|
||||
downloader.done();
|
||||
receivePort.close();
|
||||
final fortniteVersion = FortniteVersion(
|
||||
name: "dummy",
|
||||
content: parsedVersion,
|
||||
name: "dummy",
|
||||
gameVersion: version,
|
||||
location: parsedDirectory
|
||||
);
|
||||
writeVersion(fortniteVersion);
|
||||
|
||||
@@ -9,10 +9,20 @@ List<FortniteVersion> readVersions() {
|
||||
return [];
|
||||
}
|
||||
|
||||
Iterable decodedVersionsJson = jsonDecode(file.readAsStringSync());
|
||||
return decodedVersionsJson
|
||||
.map((entry) => FortniteVersion.fromJson(entry))
|
||||
.toList();
|
||||
try {
|
||||
Iterable decodedVersionsJson = jsonDecode(file.readAsStringSync());
|
||||
return decodedVersionsJson
|
||||
.map((entry) {
|
||||
try {
|
||||
return FortniteVersion.fromJson(entry);
|
||||
}catch(error) {
|
||||
throw "Cannot parse version: $error";
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
}catch(error) {
|
||||
throw "Cannot parse versions: $error";
|
||||
}
|
||||
}
|
||||
|
||||
void writeVersion(FortniteVersion version) {
|
||||
|
||||
@@ -7,4 +7,14 @@ extension IterableExtension<E> on Iterable<E> {
|
||||
}
|
||||
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 kEacExe = "FortniteClient-Win64-Shipping_EAC.exe";
|
||||
const String kCrashReportExe = "CrashReportClient.exe";
|
||||
const String kGFSDKAftermathLibDll = "GFSDK_Aftermath_Lib.dll";
|
||||
final Version kMaxAllowedVersion = Version.parse("30.10");
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
|
||||
class GameInstance {
|
||||
final String version;
|
||||
final bool host;
|
||||
final int gamePid;
|
||||
final int? launcherPid;
|
||||
final int? eacPid;
|
||||
@@ -18,6 +18,7 @@ class GameInstance {
|
||||
|
||||
GameInstance({
|
||||
required this.version,
|
||||
required this.host,
|
||||
required this.gamePid,
|
||||
required this.launcherPid,
|
||||
required this.eacPid,
|
||||
|
||||
@@ -15,7 +15,13 @@ final Semaphore _semaphore = Semaphore();
|
||||
String? _lastIp;
|
||||
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;
|
||||
HttpServer? server;
|
||||
try {
|
||||
@@ -147,7 +153,13 @@ Future<Process> startEmbeddedBackend(bool detached, {void Function(String)? onEr
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ Future<String> extractGameVersion(Directory directory) => Isolate.run(() async {
|
||||
log("[VERSION] Engine build: $engineVersionBuild");
|
||||
gameVersion = _buildToGameVersion[engineVersionBuild] ?? defaultGameVersion;
|
||||
}
|
||||
log("[VERSION] Returning $gameVersion");
|
||||
log("[VERSION] Parsed game version: $gameVersion");
|
||||
return gameVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,8 +243,8 @@
|
||||
"gameServerStarted": "The game server was started successfully",
|
||||
"gameClientStarted": "The game client was started successfully",
|
||||
"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",
|
||||
"checkGameServerFixAction": "Fix",
|
||||
"checkGameServerFixMessage": "The game server was started successfully, but other players can't join yet as port {port} isn't open",
|
||||
"checkGameServerFixAction": "Learn more",
|
||||
"infoName": "Info",
|
||||
"emptyVersionName": "Empty version name",
|
||||
"versionAlreadyExists": "This version already exists",
|
||||
@@ -379,5 +379,9 @@
|
||||
"importedVersion": "Successfully imported version",
|
||||
"importVersionMissingShippingExeError": "Cannot import version: {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,24 +163,26 @@ class BackendController extends GetxController {
|
||||
port: port.text,
|
||||
detached: detached.value,
|
||||
onError: (errorMessage) {
|
||||
stop(interactive: false);
|
||||
Get.find<GameController>()
|
||||
.instance
|
||||
.value
|
||||
?.kill();
|
||||
Get.find<HostingController>()
|
||||
.instance
|
||||
.value
|
||||
?.kill();
|
||||
_showRebootInfoBar(
|
||||
translations.backendErrorMessage,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrl(launcherLogFile.uri),
|
||||
child: Text(translations.openLog),
|
||||
)
|
||||
);
|
||||
if(started.value) {
|
||||
stop(interactive: false);
|
||||
Get.find<GameController>()
|
||||
.instance
|
||||
.value
|
||||
?.kill();
|
||||
Get.find<HostingController>()
|
||||
.instance
|
||||
.value
|
||||
?.kill();
|
||||
_showRebootInfoBar(
|
||||
translations.backendErrorMessage,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrl(launcherLogFile.uri),
|
||||
child: Text(translations.openLog),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
final completer = Completer<bool>();
|
||||
|
||||
@@ -5,7 +5,7 @@ const infoBarLongDuration = Duration(seconds: 4);
|
||||
const infoBarShortDuration = Duration(seconds: 2);
|
||||
const _height = 64.0;
|
||||
|
||||
InfoBarEntry showRebootInfoBar(dynamic text, {
|
||||
InfoBarEntry showRebootInfoBar(String text, {
|
||||
InfoBarSeverity severity = InfoBarSeverity.info,
|
||||
bool loading = false,
|
||||
Duration? duration = infoBarShortDuration,
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:local_notifier/local_notifier.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:port_forwarder/port_forwarder.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_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/messenger/dialog.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/translations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -110,6 +110,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
return;
|
||||
}
|
||||
log("[${host ? 'HOST' : 'GAME'}] Backend works");
|
||||
|
||||
final headless = _hostingController.headless.value;
|
||||
log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($headless)");
|
||||
final linkedHostingInstance = await _startMatchMakingServer(version, host, headless, false);
|
||||
@@ -121,6 +122,16 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
return;
|
||||
}
|
||||
|
||||
if(host || linkedHostingInstance != null) {
|
||||
if (_dllController.gameServerPort.text == kDefaultBackendPort.toString()) {
|
||||
_onStop(
|
||||
reason: _StopReason.gameServerPortError,
|
||||
host: host
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!host) {
|
||||
_showLaunchingGameClientWidget(version, headless, linkedHostingInstance != null);
|
||||
}else {
|
||||
@@ -131,7 +142,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
reason: _StopReason.corruptedVersionError,
|
||||
error: exception.toString(),
|
||||
stackTrace: stackTrace,
|
||||
host: host
|
||||
host: host
|
||||
);
|
||||
} catch (exception, stackTrace) {
|
||||
_onStop(
|
||||
@@ -213,6 +224,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
log("[${host ? 'HOST' : 'GAME'}] Created game process: ${gameProcess}");
|
||||
final instance = GameInstance(
|
||||
version: version.gameVersion,
|
||||
host: host,
|
||||
gamePid: gameProcess,
|
||||
launcherPid: launcherProcess,
|
||||
eacPid: eacProcess,
|
||||
@@ -232,6 +244,22 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
|
||||
Future<int?> _createGameProcess(FortniteVersion version, bool host, bool headless, GameInstance? linkedHosting) async {
|
||||
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);
|
||||
if(shippingExecutables.isEmpty){
|
||||
log("[${host ? 'HOST' : 'GAME'}] No game executable found");
|
||||
@@ -421,36 +449,30 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
_gameClientInfoBar?.close();
|
||||
}
|
||||
|
||||
final theme = FluentTheme.of(appNavigatorKey.currentContext!);
|
||||
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();
|
||||
if (!localPingResult) {
|
||||
showRebootInfoBar(
|
||||
translations.gameServerStartWarning,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
final started = await _checkLocalGameServer(gameServerPort);
|
||||
if(!started) {
|
||||
if (_hostingController.instance.value?.killed != true) {
|
||||
showRebootInfoBar(
|
||||
translations.gameServerStartWarning,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_backendController.joinLocalhost();
|
||||
final accessible = await _checkGameServer(theme, gameServerPort);
|
||||
final accessible = await _checkPublicGameServer(gameServerPort);
|
||||
if (!accessible) {
|
||||
showRebootInfoBar(
|
||||
translations.gameServerStartLocalWarning,
|
||||
severity: InfoBarSeverity.warning,
|
||||
duration: infoBarLongDuration
|
||||
translations.gameServerStartLocalWarning,
|
||||
severity: InfoBarSeverity.warning,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrlString("https://github.com/Auties00/reboot_launcher/blob/master/documentation/$currentLocale/PortForwarding.md"),
|
||||
child: Text(translations.checkGameServerFixAction),
|
||||
),
|
||||
);
|
||||
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 {
|
||||
_gameServerInfoBar = showRebootInfoBar(
|
||||
translations.checkingGameServer,
|
||||
@@ -477,43 +522,64 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
duration: null
|
||||
);
|
||||
final publicIp = await Ipify.ipv4();
|
||||
final available = await pingGameServer("$publicIp:$gameServerPort");
|
||||
if(available) {
|
||||
var pingOperation = await pingGameServerOrTimeout(
|
||||
"$publicIp:$gameServerPort",
|
||||
const Duration(seconds: 10)
|
||||
);
|
||||
_pingOperation = pingOperation;
|
||||
var publicPingResult = await pingOperation.future;
|
||||
if (publicPingResult) {
|
||||
_gameServerInfoBar?.close();
|
||||
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",
|
||||
const Duration(days: 1)
|
||||
const Duration(seconds: 10)
|
||||
);
|
||||
this._pingOperation = pingOperation;
|
||||
_pingOperation = pingOperation;
|
||||
publicPingResult = await pingOperation.future;
|
||||
_gameServerInfoBar?.close();
|
||||
_gameServerInfoBar = showRebootInfoBar(
|
||||
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;
|
||||
return publicPingResult;
|
||||
}catch(_) {
|
||||
_gameServerInfoBar?.close();
|
||||
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) {
|
||||
try {
|
||||
_pingOperation?.complete(false);
|
||||
} catch (_) {
|
||||
// Ignore: might be running, don't bother checking
|
||||
// Ignore: might have been already terminated, don't bother checking
|
||||
} finally {
|
||||
_pingOperation = null;
|
||||
}
|
||||
@@ -545,107 +611,121 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
if(child != null) {
|
||||
await _onStop(
|
||||
reason: reason,
|
||||
host: host
|
||||
host: child.host,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
interactive: false
|
||||
);
|
||||
}
|
||||
|
||||
_setStarted(host, false);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if(host == true) {
|
||||
_gameServerInfoBar?.close();
|
||||
}else {
|
||||
_gameClientInfoBar?.close();
|
||||
}
|
||||
});
|
||||
|
||||
switch(reason) {
|
||||
case _StopReason.backendError:
|
||||
case _StopReason.matchmakerError:
|
||||
case _StopReason.normal:
|
||||
break;
|
||||
case _StopReason.missingVersionError:
|
||||
showRebootInfoBar(
|
||||
translations.missingVersionError,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.missingExecutableError:
|
||||
showRebootInfoBar(
|
||||
translations.missingExecutableError,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.multipleExecutablesError:
|
||||
showRebootInfoBar(
|
||||
translations.multipleExecutablesError(error ?? translations.unknown),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.exitCode:
|
||||
if(instance != null && !instance.launched) {
|
||||
final injectedDlls = instance.injectedDlls;
|
||||
if(interactive) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if(host == true) {
|
||||
_gameServerInfoBar?.close();
|
||||
}else {
|
||||
_gameClientInfoBar?.close();
|
||||
}
|
||||
});
|
||||
|
||||
switch(reason) {
|
||||
case _StopReason.backendError:
|
||||
case _StopReason.matchmakerError:
|
||||
case _StopReason.normal:
|
||||
break;
|
||||
case _StopReason.missingVersionError:
|
||||
showRebootInfoBar(
|
||||
translations.corruptedVersionError(injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||
translations.missingVersionError,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case _StopReason.corruptedVersionError:
|
||||
showRebootInfoBar(
|
||||
translations.corruptedVersionError,
|
||||
break;
|
||||
case _StopReason.missingExecutableError:
|
||||
showRebootInfoBar(
|
||||
translations.missingExecutableError,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrl(launcherLogFile.uri),
|
||||
child: Text(translations.openLog),
|
||||
)
|
||||
);
|
||||
break;
|
||||
case _StopReason.corruptedDllError:
|
||||
showRebootInfoBar(
|
||||
translations.corruptedDllError(error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.missingCustomDllError:
|
||||
showRebootInfoBar(
|
||||
translations.missingCustomDllError(error!),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.tokenError:
|
||||
_backendController.stop(interactive: false);
|
||||
final injectedDlls = instance?.injectedDlls;
|
||||
showRebootInfoBar(
|
||||
translations.tokenError(injectedDlls == null || injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||
);
|
||||
break;
|
||||
case _StopReason.multipleExecutablesError:
|
||||
showRebootInfoBar(
|
||||
translations.multipleExecutablesError(error ?? translations.unknown),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrl(launcherLogFile.uri),
|
||||
child: Text(translations.openLog),
|
||||
)
|
||||
);
|
||||
break;
|
||||
case _StopReason.crash:
|
||||
showRebootInfoBar(
|
||||
translations.fortniteCrashError(host ? "game server" : "client"),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.unknownError:
|
||||
showRebootInfoBar(
|
||||
translations.unknownFortniteError(error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
);
|
||||
break;
|
||||
case _StopReason.exitCode:
|
||||
if(instance != null && !instance.launched) {
|
||||
final injectedDlls = instance.injectedDlls;
|
||||
showRebootInfoBar(
|
||||
translations.corruptedVersionError(injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case _StopReason.corruptedVersionError:
|
||||
final injectedDlls = instance?.injectedDlls ?? [];
|
||||
showRebootInfoBar(
|
||||
translations.corruptedVersionError(injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrl(launcherLogFile.uri),
|
||||
child: Text(translations.openLog),
|
||||
)
|
||||
);
|
||||
break;
|
||||
case _StopReason.corruptedDllError:
|
||||
showRebootInfoBar(
|
||||
translations.corruptedDllError(error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.missingCustomDllError:
|
||||
showRebootInfoBar(
|
||||
translations.missingCustomDllError(error!),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.tokenError:
|
||||
_backendController.stop(interactive: false);
|
||||
final injectedDlls = instance?.injectedDlls;
|
||||
showRebootInfoBar(
|
||||
translations.tokenError(injectedDlls == null || injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrl(launcherLogFile.uri),
|
||||
child: Text(translations.openLog),
|
||||
)
|
||||
);
|
||||
break;
|
||||
case _StopReason.crash:
|
||||
showRebootInfoBar(
|
||||
translations.fortniteCrashError(host ? translations.gameServer : translations.client),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.unknownError:
|
||||
showRebootInfoBar(
|
||||
translations.unknownFortniteError(error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.gameServerPortError:
|
||||
showRebootInfoBar(
|
||||
translations.gameServerPortEqualsBackendPort(kDefaultBackendPort),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,9 +778,9 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
if(customDll) {
|
||||
log("[${host ? 'HOST' : 'GAME'}] Custom dll -> no recovery");
|
||||
_onStop(
|
||||
reason: _StopReason.missingCustomDllError,
|
||||
error: injectable.name,
|
||||
host: host
|
||||
reason: _StopReason.missingCustomDllError,
|
||||
error: injectable.name,
|
||||
host: host
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -765,6 +845,7 @@ enum _StopReason {
|
||||
matchmakerError,
|
||||
tokenError,
|
||||
unknownError,
|
||||
gameServerPortError,
|
||||
exitCode,
|
||||
crash;
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ class _HostingPageState extends RebootPageState<HostPage> {
|
||||
FluentIcons.password_24_regular
|
||||
),
|
||||
title: Text(translations.hostGameServerPasswordName),
|
||||
subtitle: Text(translations.hostGameServerDescriptionDescription),
|
||||
subtitle: Text(translations.hostGameServerPasswordDescription),
|
||||
content: Obx(() => OverlayTarget(
|
||||
key: hostInfoPasswordOverlayTargetKey,
|
||||
child: TextFormBox(
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.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/types.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';
|
||||
|
||||
class DownloadVersionDialog extends StatefulWidget {
|
||||
@@ -75,15 +75,24 @@ class _DownloadVersionDialogState extends State<DownloadVersionDialog> {
|
||||
buttons: _stopButton
|
||||
);
|
||||
case _DownloadStatus.error:
|
||||
return ErrorDialog(
|
||||
exception: _error ?? Exception(translations.unknownError),
|
||||
stackTrace: _stackTrace,
|
||||
errorMessageBuilder: (exception) {
|
||||
var error = exception.toString();
|
||||
error = error.after("Error: ")?.replaceAll(":", ",") ?? error.after(": ") ?? error;
|
||||
error = error.toLowerCase();
|
||||
return translations.downloadVersionError(error);
|
||||
}
|
||||
final build = _build.value;
|
||||
var error = _error?.toString() ?? translations.unknownError;
|
||||
error = error.after("Error: ")?.replaceAll(":", ",") ?? error.after(": ") ?? error;
|
||||
error = error.toLowerCase();
|
||||
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:
|
||||
return InfoDialog(
|
||||
|
||||
@@ -266,9 +266,6 @@ class _VersionSelectorState extends State<VersionSelector> {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
GameController get gameController => _gameController;
|
||||
}
|
||||
|
||||
enum _ContextualOption {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: reboot_launcher
|
||||
description: Graphical User Interface for Project Reboot
|
||||
version: "10.0.8"
|
||||
version: "10.0.9"
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
@@ -54,6 +54,7 @@ dependencies:
|
||||
file_picker: ^8.1.2
|
||||
url_launcher: ^6.3.0
|
||||
local_notifier: ^0.1.6
|
||||
port_forwarder: ^1.0.0
|
||||
|
||||
# Server browser
|
||||
supabase_flutter: ^2.7.0
|
||||
|
||||
Reference in New Issue
Block a user