mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d53a577f0b | ||
|
|
c9ed6a5af3 | ||
|
|
4ea73d17c7 | ||
|
|
52abf5eb95 | ||
|
|
9c6cd6dd37 | ||
|
|
c3ede3b745 | ||
|
|
d2f0d176eb | ||
|
|
f9cf99a6b2 | ||
|
|
dc2d4c4377 | ||
|
|
5d8f6bf0fa | ||
|
|
9a000db3b7 | ||
|
|
4327541ac6 | ||
|
|
64dc971da4 | ||
|
|
d36da909ed |
15
README.md
15
README.md
@@ -1,16 +1,25 @@
|
|||||||

|

|
||||||
|
|
||||||
GUI and CLI Launcher for [Project Reboot](https://github.com/Milxnor/Project-Reboot-3.0/)
|
GUI and CLI Launcher for [Project Reboot](https://github.com/Milxnor/Project-Reboot-3.0/)
|
||||||
|
|
||||||
Join our [Discord](https://discord.gg/rebootmp)
|
Join our [Discord](https://discord.gg/rebootmp)
|
||||||
|
|
||||||
|
Install the launcher easily from the [releases](https://github.com/Auties00/Reboot-Launcher/releases/) section
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
- COMMON: Shared business logic for CLI and GUI modules
|
- COMMON: Shared business logic for CLI and GUI modules
|
||||||
|
|
||||||
- CLI: Work in progress command line interface to host a Fortnite Server on a Windows VPS easily, developed in Dart
|
- CLI: Work in progress command line interface to host a Fortnite Server on a Windows VPS easily, developed in Dart
|
||||||
|
|
||||||
- GUI: Stable graphical user interface to play and host Fortnite S0-14
|
- GUI: Stable graphical user interface to play and host Fortnite S0-14
|
||||||

|
|
||||||
|
|
||||||
|
## Preview
|
||||||
|
|
||||||
## Installation
|
- GUI
|
||||||
|
|
||||||
Check the releases section
|

|
||||||
|
|
||||||
|
- CLI
|
||||||
|
|
||||||
|
Coming soon!
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7932,6 +7932,35 @@ express.post("/fortnite/api/game/v2/profile/*/client/SetHeroCosmeticVariants", a
|
|||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// any dedicated_server request
|
||||||
|
express.post("/fortnite/api/game/v2/profile/*/dedicated_server/*", async (req, res) => {
|
||||||
|
const profile = require(`./../profiles/${req.query.profileId || "athena"}.json`);
|
||||||
|
|
||||||
|
// do not change any of these or you will end up breaking it
|
||||||
|
var ApplyProfileChanges = [];
|
||||||
|
var BaseRevision = profile.rvn || 0;
|
||||||
|
var QueryRevision = req.query.rvn || -1;
|
||||||
|
|
||||||
|
// this doesn't work properly on version v12.20 and above but whatever
|
||||||
|
if (QueryRevision != BaseRevision) {
|
||||||
|
ApplyProfileChanges = [{
|
||||||
|
"changeType": "fullProfileUpdate",
|
||||||
|
"profile": profile
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
"profileRevision": profile.rvn || 0,
|
||||||
|
"profileId": req.query.profileId || "athena",
|
||||||
|
"profileChangesBaseRevision": BaseRevision,
|
||||||
|
"profileChanges": ApplyProfileChanges,
|
||||||
|
"profileCommandRevision": profile.commandRevision || 0,
|
||||||
|
"serverTime": new Date().toISOString(),
|
||||||
|
"responseVersion": 1
|
||||||
|
})
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
// any mcp request that doesn't have something assigned to it
|
// any mcp request that doesn't have something assigned to it
|
||||||
express.post("/fortnite/api/game/v2/profile/*/client/*", async (req, res) => {
|
express.post("/fortnite/api/game/v2/profile/*/client/*", async (req, res) => {
|
||||||
const profile = require(`./../profiles/${req.query.profileId || "athena"}.json`);
|
const profile = require(`./../profiles/${req.query.profileId || "athena"}.json`);
|
||||||
@@ -1,60 +1,100 @@
|
|||||||
import 'package:interact/interact.dart';
|
import 'dart:io';
|
||||||
import 'package:reboot_cli/cli.dart';
|
import 'dart:isolate';
|
||||||
import 'package:tint/tint.dart';
|
|
||||||
|
|
||||||
|
import 'package:interact_cli/interact_cli.dart';
|
||||||
|
import 'package:reboot_cli/src/controller/config.dart';
|
||||||
|
import 'package:reboot_cli/src/util/console.dart';
|
||||||
|
import 'package:reboot_cli/src/util/extensions.dart';
|
||||||
|
import 'package:reboot_common/common.dart';
|
||||||
|
import 'package:tint/tint.dart';
|
||||||
|
import 'package:version/version.dart';
|
||||||
|
|
||||||
|
const Command _buildList = Command(name: 'list', parameters: [], subCommands: []);
|
||||||
const Command _buildImport = Command(name: 'import', parameters: ['version', 'path'], subCommands: []);
|
const Command _buildImport = Command(name: 'import', parameters: ['version', 'path'], subCommands: []);
|
||||||
const Command _buildDownload = Command(name: 'download', parameters: ['version', 'path'], subCommands: []);
|
const Command _buildDownload = Command(name: 'download', parameters: ['version', 'path'], subCommands: []);
|
||||||
const Command _build = Command(name: 'build', parameters: [], subCommands: [_buildImport, _buildDownload]);
|
const Command _build = Command(name: 'versions', parameters: [], subCommands: [_buildList, _buildImport, _buildDownload]);
|
||||||
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.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) {
|
void main(List<String> args) async {
|
||||||
_welcome();
|
enableLoggingToConsole = false;
|
||||||
|
useDefaultPath = true;
|
||||||
|
|
||||||
final parser = Parser(commands: [_build, _play, _host, _backend]);
|
print("""
|
||||||
|
🎮 Reboot Launcher
|
||||||
|
🔥 Launch, manage, and play Fortnite using Project Reboot!
|
||||||
|
🚀 Developed by Auties00 - Version 10.0.7
|
||||||
|
""".green());
|
||||||
|
|
||||||
|
final parser = ConsoleParser(
|
||||||
|
commands: [
|
||||||
|
_build,
|
||||||
|
_play,
|
||||||
|
_host,
|
||||||
|
_backend
|
||||||
|
]
|
||||||
|
);
|
||||||
final command = parser.parse(args);
|
final command = parser.parse(args);
|
||||||
print(command);
|
await _handleRootCommand(command);
|
||||||
_handleRootCommand(command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleRootCommand(CommandCall? call) {
|
Future<void> _handleRootCommand(CommandCall? command) async {
|
||||||
switch(call == null ? null : call.name) {
|
if(command == null) {
|
||||||
case 'build':
|
await _askRootCommand();
|
||||||
_handleBuildCommand(call?.subCall);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command.name) {
|
||||||
|
case 'versions':
|
||||||
|
await _handleBuildCommand(command.subCall);
|
||||||
break;
|
break;
|
||||||
case 'play':
|
case 'play':
|
||||||
_handleBuildCommand(call?.subCall);
|
_handlePlayCommand(command.subCall);
|
||||||
break;
|
break;
|
||||||
case 'host':
|
case 'host':
|
||||||
_handleBuildCommand(call?.subCall);
|
_handleHostCommand(command.subCall);
|
||||||
break;
|
break;
|
||||||
case 'backend':
|
case 'backend':
|
||||||
_handleBuildCommand(call?.subCall);
|
_handleBackendCommand(command.subCall);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
_askRootCommand();
|
await _askRootCommand();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _askRootCommand() {
|
Future<void> _askRootCommand() async {
|
||||||
final commands = [_build.name, _play.name, _host.name, _backend.name];
|
final commands = [_build.name, _play.name, _host.name, _backend.name];
|
||||||
final commandSelector = Select.withTheme(
|
final commandSelector = Select.withTheme(
|
||||||
prompt: ' Select a command:',
|
prompt: ' Select a command:',
|
||||||
options: commands,
|
options: commands,
|
||||||
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '')
|
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
||||||
);
|
);
|
||||||
_handleRootCommand(CommandCall(name: commands[commandSelector.interact()]));
|
await _handleRootCommand(CommandCall(name: commands[commandSelector.interact()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleBuildCommand(CommandCall? call) {
|
Future<void> _handleBuildCommand(CommandCall? call) async {
|
||||||
switch(call == null ? null : call.name) {
|
if(call == null) {
|
||||||
|
_askBuildCommand();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(call.name) {
|
||||||
case 'import':
|
case 'import':
|
||||||
_handleBuildImportCommand(call!);
|
await _handleBuildImportCommand(call);
|
||||||
break;
|
break;
|
||||||
case 'download':
|
case 'download':
|
||||||
_handleBuildDownloadCommand(call!);
|
_handleBuildDownloadCommand(call);
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
_handleBuildListCommand(call);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
_askBuildCommand();
|
_askBuildCommand();
|
||||||
@@ -62,23 +102,324 @@ void _handleBuildCommand(CommandCall? call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleBuildImportCommand(CommandCall call) {
|
void _handleBuildListCommand(CommandCall commandCall) {
|
||||||
final version = call.parameters['path'];
|
List<FortniteVersion> versions;
|
||||||
final path = call.parameters['path'];
|
try {
|
||||||
print(version);
|
versions = readVersions();
|
||||||
print(path);
|
}catch(error) {
|
||||||
|
print("❌ $error");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleBuildDownloadCommand(CommandCall call) {
|
if(versions.isEmpty) {
|
||||||
|
print("❌ No versions found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final versionSelector = Select.withTheme(
|
||||||
|
prompt: ' Select a version:',
|
||||||
|
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: _versionActions,
|
||||||
|
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
||||||
|
);
|
||||||
|
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 {
|
||||||
|
final version = _getOrPromptVersion(call);
|
||||||
|
if(version == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final path = await _getOrPromptPath(call, true);
|
||||||
|
if(path == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final fortniteVersion = FortniteVersion(
|
||||||
|
name: '',
|
||||||
|
gameVersion: version,
|
||||||
|
location: Directory(path)
|
||||||
|
);
|
||||||
|
writeVersion(fortniteVersion);
|
||||||
|
print('');
|
||||||
|
print('✅ Imported build: ${version.green()}');
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _getOrPromptVersion(CommandCall call) {
|
||||||
|
final version = call.parameters['version'];
|
||||||
|
if(version != null) {
|
||||||
|
final result = version.trim();
|
||||||
|
if (_versions.contains(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
print('');
|
||||||
|
print("❌ Unknown version: $result");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.write('❓ Type a version: ');
|
||||||
|
final result = runAutoComplete(_autocompleteVersion).trim();
|
||||||
|
if(_versions.contains(result)) {
|
||||||
|
print('✅ Type a version: ${result.green()}');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
print('');
|
||||||
|
print("❌ Unknown version: $version");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _getOrPromptPath(CommandCall call, bool existing) async {
|
||||||
|
var path = call.parameters['path'];
|
||||||
|
if(path != null) {
|
||||||
|
final result = path.trim();
|
||||||
|
final check = await _checkBuildPath(result, existing);
|
||||||
|
if(!check) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.write('❓ Type a path: ');
|
||||||
|
final result = runAutoComplete(_autocompletePath).trim();
|
||||||
|
final check = await _checkBuildPath(result, existing);
|
||||||
|
if(!check) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
print('✅ Type a path: ${result.green()}');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _checkBuildPath(String path, bool existing) async {
|
||||||
|
final directory = Directory(path);
|
||||||
|
if(!directory.existsSync()) {
|
||||||
|
if(existing) {
|
||||||
|
print('');
|
||||||
|
print("❌ Unknown path: $path");
|
||||||
|
return false;
|
||||||
|
}else {
|
||||||
|
directory.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
final checker = Spinner.withTheme(
|
||||||
|
icon: '✅',
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _autocompleteVersion(String input) => input.isEmpty ? null : _versions.firstWhereOrNull((version) => version.toLowerCase().startsWith(input.toLowerCase()));
|
||||||
|
|
||||||
|
String? _autocompletePath(String path) {
|
||||||
|
try {
|
||||||
|
if (path.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String separator = Platform.isWindows ? '\\' : '/';
|
||||||
|
path = path.replaceAll('\\', separator);
|
||||||
|
|
||||||
|
if (FileSystemEntity.isFileSync(path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(FileSystemEntity.isDirectorySync(path)) {
|
||||||
|
return path.endsWith(separator) ? null : path + separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
final lastSeparatorIndex = path.lastIndexOf(separator);
|
||||||
|
String directoryPath;
|
||||||
|
String partialName;
|
||||||
|
String prefixPath;
|
||||||
|
if (lastSeparatorIndex == -1) {
|
||||||
|
directoryPath = '';
|
||||||
|
partialName = path;
|
||||||
|
prefixPath = '';
|
||||||
|
} else {
|
||||||
|
directoryPath = path.substring(0, lastSeparatorIndex);
|
||||||
|
partialName = path.substring(lastSeparatorIndex + 1);
|
||||||
|
prefixPath = path.substring(0, lastSeparatorIndex + 1);
|
||||||
|
if (directoryPath.isEmpty && lastSeparatorIndex == 0) {
|
||||||
|
directoryPath = separator;
|
||||||
|
} else if (directoryPath.isEmpty) {
|
||||||
|
directoryPath = '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final dir = Directory(directoryPath);
|
||||||
|
if (!dir.existsSync()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final entries = dir.listSync();
|
||||||
|
final matches = <FileSystemEntity>[];
|
||||||
|
for (var entry in entries) {
|
||||||
|
final name = entry.path.split(separator).last;
|
||||||
|
if (name.startsWith(partialName)) {
|
||||||
|
matches.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches.sort((a, b) {
|
||||||
|
final aIsDir = a is Directory;
|
||||||
|
final bIsDir = b is Directory;
|
||||||
|
|
||||||
|
if (aIsDir != bIsDir) {
|
||||||
|
return aIsDir ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final aName = a.path.split(separator).last;
|
||||||
|
final bName = b.path.split(separator).last;
|
||||||
|
|
||||||
|
if (aName.length != bName.length) {
|
||||||
|
return aName.length - bName.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aName.compareTo(bName);
|
||||||
|
});
|
||||||
|
|
||||||
|
final bestMatch = matches.first;
|
||||||
|
final bestMatchName = bestMatch.path.split(separator).last;
|
||||||
|
|
||||||
|
var result = prefixPath + bestMatchName;
|
||||||
|
if (bestMatch is Directory) {
|
||||||
|
result = result.endsWith(separator) ? result : result + separator;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> _handleBuildDownloadCommand(CommandCall call) async {
|
||||||
|
final version = _getOrPromptVersion(call);
|
||||||
|
if(version == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final parsedVersion = Version.parse(version);
|
||||||
|
final build = downloadableBuilds.firstWhereOrNull((build) => Version.parse(build.gameVersion) == parsedVersion);
|
||||||
|
if(build == null) {
|
||||||
|
print('');
|
||||||
|
print("❌ Cannot find mirror for version: $parsedVersion");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final path = await _getOrPromptPath(call, false);
|
||||||
|
if(path == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double progress = 0;
|
||||||
|
bool extracting = false;
|
||||||
|
final downloader = Spinner.withTheme(
|
||||||
|
icon: '✅',
|
||||||
|
rightPrompt: (status) => status != SpinnerStateType.inProgress ? 'Finished ${extracting ? 'extracting' : 'downloading'} ${parsedVersion.toString()}' : '${extracting ? 'Extracting' : 'Downloading'} ${parsedVersion.toString()} (${progress.round()}%)...',
|
||||||
|
theme: Theme.colorfulTheme.copyWith(successSuffix: '', errorPrefix: '❌', spinners: '🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛'.split(' '))
|
||||||
|
).interact();
|
||||||
|
final parsedDirectory = Directory(path);
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
SendPort? sendPort;
|
||||||
|
receivePort.listen((message) {
|
||||||
|
if(message is FortniteBuildDownloadProgress) {
|
||||||
|
if(message.progress >= 100) {
|
||||||
|
sendPort?.send(kStopBuildDownloadSignal);
|
||||||
|
stopDownloadServer();
|
||||||
|
downloader.done();
|
||||||
|
receivePort.close();
|
||||||
|
final fortniteVersion = FortniteVersion(
|
||||||
|
name: "dummy",
|
||||||
|
gameVersion: version,
|
||||||
|
location: parsedDirectory
|
||||||
|
);
|
||||||
|
writeVersion(fortniteVersion);
|
||||||
|
print('');
|
||||||
|
print('✅ Downloaded build: ${version.green()}');
|
||||||
|
}else {
|
||||||
|
progress = message.progress;
|
||||||
|
extracting = message.extracting;
|
||||||
|
}
|
||||||
|
}else if(message is SendPort) {
|
||||||
|
sendPort = message;
|
||||||
|
}else {
|
||||||
|
sendPort?.send(kStopBuildDownloadSignal);
|
||||||
|
stopDownloadServer();
|
||||||
|
downloader.done();
|
||||||
|
receivePort.close();
|
||||||
|
print("❌ Cannot download build: $message");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final options = FortniteBuildDownloadOptions(
|
||||||
|
build,
|
||||||
|
parsedDirectory,
|
||||||
|
receivePort.sendPort
|
||||||
|
);
|
||||||
|
await downloadArchiveBuild(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _askBuildCommand() {
|
void _askBuildCommand() {
|
||||||
final commands = [_buildImport.name, _buildDownload.name];
|
final commands = [_buildList.name, _buildImport.name, _buildDownload.name];
|
||||||
final commandSelector = Select.withTheme(
|
final commandSelector = Select.withTheme(
|
||||||
prompt: ' Select a build command:',
|
prompt: ' Select a version command:',
|
||||||
options: commands,
|
options: commands,
|
||||||
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '')
|
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
||||||
);
|
);
|
||||||
_handleBuildCommand(CommandCall(name: commands[commandSelector.interact()]));
|
_handleBuildCommand(CommandCall(name: commands[commandSelector.interact()]));
|
||||||
}
|
}
|
||||||
@@ -94,9 +435,3 @@ void _handleHostCommand(CommandCall? call) {
|
|||||||
void _handleBackendCommand(CommandCall? call) {
|
void _handleBackendCommand(CommandCall? call) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _welcome() => print("""
|
|
||||||
🎮 Reboot Launcher
|
|
||||||
🔥 Launch, manage, and play Fortnite using Project Reboot!
|
|
||||||
🚀 Developed by Auties00 - Version 10.0.7
|
|
||||||
""".green());
|
|
||||||
0
cli/lib/src/command/backend.dart
Normal file
0
cli/lib/src/command/backend.dart
Normal file
0
cli/lib/src/command/commands.dart
Normal file
0
cli/lib/src/command/commands.dart
Normal file
0
cli/lib/src/command/config.dart
Normal file
0
cli/lib/src/command/config.dart
Normal file
0
cli/lib/src/command/host.dart
Normal file
0
cli/lib/src/command/host.dart
Normal file
0
cli/lib/src/command/play.dart
Normal file
0
cli/lib/src/command/play.dart
Normal file
0
cli/lib/src/command/versions.dart
Normal file
0
cli/lib/src/command/versions.dart
Normal file
34
cli/lib/src/controller/config.dart
Normal file
34
cli/lib/src/controller/config.dart
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:reboot_common/common.dart';
|
||||||
|
|
||||||
|
List<FortniteVersion> readVersions() {
|
||||||
|
final file = _versionsFile;
|
||||||
|
if(!file.existsSync()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
final versions = readVersions();
|
||||||
|
versions.add(version);
|
||||||
|
_versionsFile.writeAsString(jsonEncode(versions.map((version) => version.toJson()).toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
File get _versionsFile => File('${installationDirectory.path}/versions.json');
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import 'dart:collection';
|
import 'dart:io';
|
||||||
|
import 'package:dart_console/dart_console.dart';
|
||||||
|
|
||||||
class Parser {
|
typedef AutoComplete = String? Function(String);
|
||||||
|
|
||||||
|
class ConsoleParser {
|
||||||
final List<Command> commands;
|
final List<Command> commands;
|
||||||
|
|
||||||
Parser({required this.commands});
|
ConsoleParser({required this.commands});
|
||||||
|
|
||||||
CommandCall? parse(List<String> args) {
|
CommandCall? parse(List<String> args) {
|
||||||
var position = 0;
|
var position = 0;
|
||||||
@@ -87,3 +90,71 @@ class CommandCall {
|
|||||||
@override
|
@override
|
||||||
String toString() => 'CommandCall{name: $name, parameters: $parameters, subCall: $subCall}';
|
String toString() => 'CommandCall{name: $name, parameters: $parameters, subCall: $subCall}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String runAutoComplete(AutoComplete completion) {
|
||||||
|
final console = Console();
|
||||||
|
console.rawMode = true;
|
||||||
|
final position = console.cursorPosition!;
|
||||||
|
|
||||||
|
var currentInput = '';
|
||||||
|
var running = true;
|
||||||
|
var result = '';
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
final key = console.readKey();
|
||||||
|
switch (key.controlChar) {
|
||||||
|
case ControlCharacter.ctrlC:
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
case ControlCharacter.enter:
|
||||||
|
_eraseUntil(console, position);
|
||||||
|
console.write(currentInput);
|
||||||
|
console.writeLine();
|
||||||
|
result = currentInput;
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
case ControlCharacter.tab:
|
||||||
|
final suggestion = completion(currentInput);
|
||||||
|
if (suggestion != null) {
|
||||||
|
_eraseUntil(console, position);
|
||||||
|
currentInput = suggestion;
|
||||||
|
console.write(currentInput);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ControlCharacter.backspace:
|
||||||
|
if (currentInput.isNotEmpty) {
|
||||||
|
currentInput = currentInput.substring(0, currentInput.length - 1);
|
||||||
|
_eraseUntil(console, position);
|
||||||
|
console.write(currentInput);
|
||||||
|
_showSuggestion(console, position, currentInput, completion);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
currentInput += key.char;
|
||||||
|
console.write(key.char);
|
||||||
|
_showSuggestion(console, position, currentInput, completion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _eraseUntil(Console console, Coordinate position) {
|
||||||
|
console.cursorPosition = position;
|
||||||
|
stdout.write('\x1b[K');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSuggestion(Console console, Coordinate position, String input, AutoComplete completion) {
|
||||||
|
final suggestion = completion(input);
|
||||||
|
if(suggestion == null) {
|
||||||
|
_eraseUntil(console, position);
|
||||||
|
console.write(input);
|
||||||
|
}else if(suggestion.length > input.length) {
|
||||||
|
final remaining = suggestion.substring(input.length);
|
||||||
|
final cursorPosition = console.cursorPosition;
|
||||||
|
console.setForegroundColor(ConsoleColor.brightBlack);
|
||||||
|
console.write(remaining);
|
||||||
|
console.resetColorAttributes();
|
||||||
|
console.cursorPosition = cursorPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
cli/lib/src/util/extensions.dart
Normal file
20
cli/lib/src/util/extensions.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
extension IterableExtension<E> on Iterable<E> {
|
||||||
|
E? firstWhereOrNull(bool test(E element)) {
|
||||||
|
for (final element in this) {
|
||||||
|
if (test(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,14 +5,15 @@ version: "10.0.7"
|
|||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.19.0 <=3.5.3"
|
sdk: ">=3.0.0 <=3.5.3"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
reboot_common:
|
reboot_common:
|
||||||
path: ./../common
|
path: ./../common
|
||||||
tint: ^2.0.1
|
tint: ^2.0.1
|
||||||
interact: ^2.2.0
|
interact_cli: ^2.4.0
|
||||||
args: ^2.6.0
|
args: ^2.6.0
|
||||||
|
version: ^3.0.2
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
xml: ^6.3.0
|
xml: ^6.3.0
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
export 'package:reboot_common/src/constant/backend.dart';
|
export 'package:reboot_common/src/backend/auth_backend_constants.dart';
|
||||||
export 'package:reboot_common/src/constant/game.dart';
|
export 'package:reboot_common/src/backend/auth_backend_helper.dart';
|
||||||
export 'package:reboot_common/src/constant/supabase.dart';
|
export 'package:reboot_common/src/backend/auth_backend_result.dart';
|
||||||
export 'package:reboot_common/src/extension/path.dart';
|
export 'package:reboot_common/src/backend/auth_backend_type.dart';
|
||||||
export 'package:reboot_common/src/extension/process.dart';
|
|
||||||
export 'package:reboot_common/src/model/fortnite_build.dart';
|
export 'package:reboot_common/src/browser/server_browser_client.dart';
|
||||||
export 'package:reboot_common/src/model/fortnite_version.dart';
|
export 'package:reboot_common/src/browser/server_browser_entry.dart';
|
||||||
export 'package:reboot_common/src/model/game_instance.dart';
|
export 'package:reboot_common/src/browser/server_browser_event.dart';
|
||||||
export 'package:reboot_common/src/model/server_result.dart';
|
export 'package:reboot_common/src/browser/server_browser_state.dart';
|
||||||
export 'package:reboot_common/src/model/server_type.dart';
|
|
||||||
export 'package:reboot_common/src/model/update_status.dart';
|
export 'package:reboot_common/src/game/game_build.dart';
|
||||||
export 'package:reboot_common/src/model/update_timer.dart';
|
export 'package:reboot_common/src/game/game_constants.dart';
|
||||||
export 'package:reboot_common/src/model/fortnite_server.dart';
|
export 'package:reboot_common/src/game/game_dll.dart';
|
||||||
export 'package:reboot_common/src/model/dll.dart';
|
export 'package:reboot_common/src/game/game_downloader.dart';
|
||||||
export 'package:reboot_common/src/util/backend.dart';
|
export 'package:reboot_common/src/game/game_instance.dart';
|
||||||
export 'package:reboot_common/src/util/build.dart';
|
export 'package:reboot_common/src/game/game_metadata.dart';
|
||||||
export 'package:reboot_common/src/util/dll.dart';
|
export 'package:reboot_common/src/game/game_version.dart';
|
||||||
export 'package:reboot_common/src/util/network.dart';
|
|
||||||
export 'package:reboot_common/src/util/patcher.dart';
|
export 'package:reboot_common/src/util/extensions.dart';
|
||||||
export 'package:reboot_common/src/util/path.dart';
|
export 'package:reboot_common/src/util/logger.dart';
|
||||||
export 'package:reboot_common/src/util/process.dart';
|
export 'package:reboot_common/src/util/os.dart';
|
||||||
export 'package:reboot_common/src/util/log.dart';
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:ini/ini.dart';
|
import 'package:ini/ini.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_common/src/extension/types.dart';
|
|
||||||
import 'package:shelf/shelf_io.dart';
|
import 'package:shelf/shelf_io.dart';
|
||||||
import 'package:shelf_proxy/shelf_proxy.dart';
|
import 'package:shelf_proxy/shelf_proxy.dart';
|
||||||
import 'package:sync/semaphore.dart';
|
import 'package:sync/semaphore.dart';
|
||||||
@@ -15,87 +15,95 @@ 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* {
|
typedef BackendErrorHandler = void Function(String);
|
||||||
|
|
||||||
|
Stream<AuthBackendResult> startAuthBackend({
|
||||||
|
required AuthBackendType type,
|
||||||
|
required String host,
|
||||||
|
required String port,
|
||||||
|
required bool detached,
|
||||||
|
required BackendErrorHandler? onError
|
||||||
|
}) async* {
|
||||||
Process? process;
|
Process? process;
|
||||||
HttpServer? server;
|
HttpServer? server;
|
||||||
try {
|
try {
|
||||||
host = host.trim();
|
host = host.trim();
|
||||||
port = port.trim();
|
port = port.trim();
|
||||||
if(type != ServerType.local || port != kDefaultBackendPort.toString()) {
|
if(type != AuthBackendType.local || port != kDefaultBackendPort.toString()) {
|
||||||
yield ServerResult(ServerResultType.starting);
|
yield AuthBackendResult(AuthBackendResultType.starting);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host.isEmpty) {
|
if (host.isEmpty) {
|
||||||
yield ServerResult(ServerResultType.startMissingHostError);
|
yield AuthBackendResult(AuthBackendResultType.startMissingHostError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (port.isEmpty) {
|
if (port.isEmpty) {
|
||||||
yield ServerResult(ServerResultType.startMissingPortError);
|
yield AuthBackendResult(AuthBackendResultType.startMissingPortError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final portNumber = int.tryParse(port);
|
final portNumber = int.tryParse(port);
|
||||||
if (portNumber == null) {
|
if (portNumber == null) {
|
||||||
yield ServerResult(ServerResultType.startIllegalPortError);
|
yield AuthBackendResult(AuthBackendResultType.startIllegalPortError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((type != ServerType.local || port != kDefaultBackendPort.toString()) && !(await isBackendPortFree())) {
|
if ((type != AuthBackendType.local || port != kDefaultBackendPort.toString()) && !(await isAuthBackendPortFree())) {
|
||||||
yield ServerResult(ServerResultType.startFreeingPort);
|
yield AuthBackendResult(AuthBackendResultType.startFreeingPort);
|
||||||
final result = await freeBackendPort();
|
final result = await freeAuthBackendPort();
|
||||||
if(!result) {
|
if(!result) {
|
||||||
yield ServerResult(ServerResultType.startFreePortError);
|
yield AuthBackendResult(AuthBackendResultType.startFreePortError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield ServerResult(ServerResultType.startFreePortSuccess);
|
yield AuthBackendResult(AuthBackendResultType.startFreePortSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(type){
|
switch(type){
|
||||||
case ServerType.embedded:
|
case AuthBackendType.embedded:
|
||||||
process = await startEmbeddedBackend(detached, onError: onError);
|
process = await _startEmbedded(detached, onError: onError);
|
||||||
yield ServerResult(ServerResultType.startedImplementation, implementation: ServerImplementation(process: process));
|
yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(process: process));
|
||||||
break;
|
break;
|
||||||
case ServerType.remote:
|
case AuthBackendType.remote:
|
||||||
yield ServerResult(ServerResultType.startPingingRemote);
|
yield AuthBackendResult(AuthBackendResultType.startPingingRemote);
|
||||||
final uriResult = await pingBackend(host, portNumber);
|
final uriResult = await _ping(host, portNumber);
|
||||||
if(uriResult == null) {
|
if(uriResult == null) {
|
||||||
yield ServerResult(ServerResultType.startPingError);
|
yield AuthBackendResult(AuthBackendResultType.startPingError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
server = await startRemoteBackendProxy(uriResult);
|
server = await _startRemote(uriResult);
|
||||||
yield ServerResult(ServerResultType.startedImplementation, implementation: ServerImplementation(server: server));
|
yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(server: server));
|
||||||
break;
|
break;
|
||||||
case ServerType.local:
|
case AuthBackendType.local:
|
||||||
if(portNumber != kDefaultBackendPort) {
|
if(portNumber != kDefaultBackendPort) {
|
||||||
yield ServerResult(ServerResultType.startPingingLocal);
|
yield AuthBackendResult(AuthBackendResultType.startPingingLocal);
|
||||||
final uriResult = await pingBackend(kDefaultBackendHost, portNumber);
|
final uriResult = await _ping(kDefaultBackendHost, portNumber);
|
||||||
if(uriResult == null) {
|
if(uriResult == null) {
|
||||||
yield ServerResult(ServerResultType.startPingError);
|
yield AuthBackendResult(AuthBackendResultType.startPingError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
server = await startRemoteBackendProxy(Uri.parse("http://$kDefaultBackendHost:$port"));
|
server = await _startRemote(Uri.parse("http://$kDefaultBackendHost:$port"));
|
||||||
yield ServerResult(ServerResultType.startedImplementation, implementation: ServerImplementation(server: server));
|
yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(server: server));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield ServerResult(ServerResultType.startPingingLocal);
|
yield AuthBackendResult(AuthBackendResultType.startPingingLocal);
|
||||||
final uriResult = await pingBackend(kDefaultBackendHost, kDefaultBackendPort);
|
final uriResult = await _ping(kDefaultBackendHost, kDefaultBackendPort);
|
||||||
if(uriResult == null) {
|
if(uriResult == null) {
|
||||||
yield ServerResult(ServerResultType.startPingError);
|
yield AuthBackendResult(AuthBackendResultType.startPingError);
|
||||||
process?.kill(ProcessSignal.sigterm);
|
process?.kill(ProcessSignal.sigterm);
|
||||||
server?.close(force: true);
|
server?.close(force: true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield ServerResult(ServerResultType.startSuccess);
|
yield AuthBackendResult(AuthBackendResultType.startSuccess);
|
||||||
}catch(error, stackTrace) {
|
}catch(error, stackTrace) {
|
||||||
yield ServerResult(
|
yield AuthBackendResult(
|
||||||
ServerResultType.startError,
|
AuthBackendResultType.startError,
|
||||||
error: error,
|
error: error,
|
||||||
stackTrace: stackTrace
|
stackTrace: stackTrace
|
||||||
);
|
);
|
||||||
@@ -104,65 +112,65 @@ Stream<ServerResult> startBackend({required ServerType type, required String hos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<ServerResult> stopBackend({required ServerType type, required ServerImplementation? implementation}) async* {
|
Future<Process> _startEmbedded(bool detached, {BackendErrorHandler? onError}) async {
|
||||||
yield ServerResult(ServerResultType.stopping);
|
|
||||||
try{
|
|
||||||
switch(type){
|
|
||||||
case ServerType.embedded:
|
|
||||||
final process = implementation?.process;
|
|
||||||
if(process != null) {
|
|
||||||
Process.killPid(process.pid, ProcessSignal.sigterm);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ServerType.remote:
|
|
||||||
await implementation?.server?.close(force: true);
|
|
||||||
break;
|
|
||||||
case ServerType.local:
|
|
||||||
await implementation?.server?.close(force: true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
yield ServerResult(ServerResultType.stopSuccess);
|
|
||||||
}catch(error, stackTrace){
|
|
||||||
yield ServerResult(
|
|
||||||
ServerResultType.stopError,
|
|
||||||
error: error,
|
|
||||||
stackTrace: stackTrace
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Process> startEmbeddedBackend(bool detached, {void Function(String)? onError}) async {
|
|
||||||
final process = await startProcess(
|
final process = await startProcess(
|
||||||
executable: backendStartExecutable,
|
executable: backendStartExecutable,
|
||||||
window: detached,
|
window: detached,
|
||||||
);
|
);
|
||||||
process.stdOutput.listen((message) => log("[BACKEND] Message: $message"));
|
process.stdOutput.listen((message) => log("[BACKEND] Message: $message"));
|
||||||
|
var killed = false;
|
||||||
process.stdError.listen((error) {
|
process.stdError.listen((error) {
|
||||||
|
if(!killed) {
|
||||||
log("[BACKEND] Error: $error");
|
log("[BACKEND] Error: $error");
|
||||||
|
killed = true;
|
||||||
|
process.kill(ProcessSignal.sigterm);
|
||||||
onError?.call(error);
|
onError?.call(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<HttpServer> startRemoteBackendProxy(Uri uri) async => await serve(proxyHandler(uri), kDefaultBackendHost, kDefaultBackendPort);
|
Future<HttpServer> _startRemote(Uri uri) async => await serve(proxyHandler(uri), kDefaultBackendHost, kDefaultBackendPort);
|
||||||
|
|
||||||
Future<bool> isBackendPortFree() async => await pingBackend(kDefaultBackendHost, kDefaultBackendPort) == null;
|
Stream<AuthBackendResult> stopAuthBackend({required AuthBackendType type, required AuthBackendImplementation? implementation}) async* {
|
||||||
|
yield AuthBackendResult(AuthBackendResultType.stopping);
|
||||||
Future<bool> freeBackendPort() async {
|
try{
|
||||||
await killProcessByPort(kDefaultBackendPort);
|
switch(type){
|
||||||
await killProcessByPort(kDefaultXmppPort);
|
case AuthBackendType.embedded:
|
||||||
final standardResult = await isBackendPortFree();
|
final process = implementation?.process;
|
||||||
if(standardResult) {
|
if(process != null) {
|
||||||
return true;
|
Process.killPid(process.pid, ProcessSignal.sigterm);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AuthBackendType.remote:
|
||||||
|
await implementation?.server?.close(force: true);
|
||||||
|
break;
|
||||||
|
case AuthBackendType.local:
|
||||||
|
await implementation?.server?.close(force: true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
yield AuthBackendResult(AuthBackendResultType.stopSuccess);
|
||||||
|
}catch(error, stackTrace){
|
||||||
|
yield AuthBackendResult(
|
||||||
|
AuthBackendResultType.stopError,
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
Future<bool> isAuthBackendPortFree() async => await _ping(kDefaultBackendHost, kDefaultBackendPort) == null;
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uri?> pingBackend(String host, int port, [bool https=false]) async {
|
Future<Uri?> _ping(String host, int port, [bool https=false]) async {
|
||||||
final hostName = host.replaceFirst("http://", "").replaceFirst("https://", "");
|
final hostName = host.replaceFirst("http://", "").replaceFirst("https://", "");
|
||||||
final declaredScheme = host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
final declaredScheme = host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||||
try{
|
try{
|
||||||
@@ -181,7 +189,7 @@ Future<Uri?> pingBackend(String host, int port, [bool https=false]) async {
|
|||||||
return uri;
|
return uri;
|
||||||
}catch(error) {
|
}catch(error) {
|
||||||
log("[BACKEND] Cannot ping backend: $error");
|
log("[BACKEND] Cannot ping backend: $error");
|
||||||
return https || declaredScheme != null || isLocalHost(host) ? null : await pingBackend(host, port, true);
|
return https || declaredScheme != null || isLocalHost(host) ? null : await _ping(host, port, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +227,18 @@ Stream<String?> watchMatchmakingIp() async* {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> writeMatchmakingIp(String text) async {
|
Future<bool> freeAuthBackendPort() async {
|
||||||
|
await killProcessByPort(kDefaultBackendPort);
|
||||||
|
await killProcessByPort(kDefaultXmppPort);
|
||||||
|
final standardResult = await isAuthBackendPortFree();
|
||||||
|
if(standardResult) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> writeAuthBackendMatchmakingIp(String text) async {
|
||||||
final exists = await matchmakerConfigFile.exists();
|
final exists = await matchmakerConfigFile.exists();
|
||||||
if(!exists) {
|
if(!exists) {
|
||||||
return;
|
return;
|
||||||
@@ -229,7 +248,7 @@ Future<void> writeMatchmakingIp(String text) async {
|
|||||||
final splitIndex = text.indexOf(":");
|
final splitIndex = text.indexOf(":");
|
||||||
final ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
|
final ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
|
||||||
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort;
|
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort;
|
||||||
if(port.isBlank) {
|
if(port.isBlankOrEmpty) {
|
||||||
port = kDefaultGameServerPort;
|
port = kDefaultGameServerPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
class ServerResult {
|
class AuthBackendResult {
|
||||||
final ServerResultType type;
|
final AuthBackendResultType type;
|
||||||
final ServerImplementation? implementation;
|
final AuthBackendImplementation? implementation;
|
||||||
final Object? error;
|
final Object? error;
|
||||||
final StackTrace? stackTrace;
|
final StackTrace? stackTrace;
|
||||||
|
|
||||||
ServerResult(this.type, {this.implementation, this.error, this.stackTrace});
|
AuthBackendResult(this.type, {this.implementation, this.error, this.stackTrace});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@@ -14,14 +14,14 @@ class ServerResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerImplementation {
|
class AuthBackendImplementation {
|
||||||
final Process? process;
|
final Process? process;
|
||||||
final HttpServer? server;
|
final HttpServer? server;
|
||||||
|
|
||||||
ServerImplementation({this.process, this.server});
|
AuthBackendImplementation({this.process, this.server});
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServerResultType {
|
enum AuthBackendResultType {
|
||||||
starting,
|
starting,
|
||||||
startMissingHostError,
|
startMissingHostError,
|
||||||
startMissingPortError,
|
startMissingPortError,
|
||||||
@@ -39,9 +39,9 @@ enum ServerResultType {
|
|||||||
stopSuccess,
|
stopSuccess,
|
||||||
stopError;
|
stopError;
|
||||||
|
|
||||||
bool get isStart => name.contains("start");
|
bool get isStart => name.startsWith("start");
|
||||||
|
|
||||||
bool get isError => name.contains("Error");
|
bool get isError => name.endsWith("Error");
|
||||||
|
|
||||||
bool get isSuccess => this == ServerResultType.startSuccess || this == ServerResultType.stopSuccess;
|
bool get isSuccess => this == AuthBackendResultType.startSuccess || this == AuthBackendResultType.stopSuccess;
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user