mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 19:22:22 +01:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70d83dc1c5 | ||
|
|
d53a577f0b | ||
|
|
c9ed6a5af3 | ||
|
|
4ea73d17c7 | ||
|
|
52abf5eb95 | ||
|
|
9c6cd6dd37 | ||
|
|
c3ede3b745 | ||
|
|
d2f0d176eb | ||
|
|
f9cf99a6b2 | ||
|
|
dc2d4c4377 | ||
|
|
5d8f6bf0fa | ||
|
|
9a000db3b7 | ||
|
|
4327541ac6 | ||
|
|
64dc971da4 | ||
|
|
d36da909ed | ||
|
|
90448eeaa1 | ||
|
|
b319479def |
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!
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
# Builds Archive
|
|
||||||
|
|
||||||
Builds are stored on a Cloudflare R2 instance at `https://builds.rebootfn.org/versions.json`.
|
|
||||||
If you want to move them to another AWS-compatible object storage, run:
|
|
||||||
```
|
|
||||||
python move.py
|
|
||||||
```
|
|
||||||
and provide the required parameters.
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
import boto3
|
|
||||||
|
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
def upload_url_to_s3(s3_client, bucket_name, url, object_key):
|
|
||||||
response = requests.get(url, stream=True, verify=False, headers={"Cookie": "_c_t_c=1"})
|
|
||||||
response.raise_for_status()
|
|
||||||
s3_client.upload_fileobj(response.raw, bucket_name, object_key)
|
|
||||||
return url, object_key
|
|
||||||
|
|
||||||
def derive_key_from_url(url, prefix=None):
|
|
||||||
parsed = urlparse(url)
|
|
||||||
filename = os.path.basename(parsed.path)
|
|
||||||
if prefix:
|
|
||||||
return f"{prefix}/{filename}"
|
|
||||||
else:
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description="Upload multiple URLs from versions.txt to an S3 bucket concurrently.")
|
|
||||||
parser.add_argument('--bucket', required=True, help="Name of the S3 bucket.")
|
|
||||||
parser.add_argument('--concurrency', required=True, type=int, help="Number of concurrent uploads.")
|
|
||||||
parser.add_argument('--versions-file', default='versions.txt', help="File containing one URL per line.")
|
|
||||||
parser.add_argument('--access-key', required=True, help="AWS Access Key ID.")
|
|
||||||
parser.add_argument('--secret-key', required=True, help="AWS Secret Access Key.")
|
|
||||||
parser.add_argument('--endpoint-url', required=True, help="Custom endpoint URL for S3 or S3-compatible storage.")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
bucket_name = args.bucket
|
|
||||||
concurrency = args.concurrency
|
|
||||||
versions_file = args.versions_file
|
|
||||||
access_key = args.access_key
|
|
||||||
secret_key = args.secret_key
|
|
||||||
endpoint_url = args.endpoint_url
|
|
||||||
|
|
||||||
with open(versions_file, 'r') as f:
|
|
||||||
urls = [line.strip() for line in f if line.strip()]
|
|
||||||
|
|
||||||
print(f"Uploading {len(urls)} files...")
|
|
||||||
s3_params = {}
|
|
||||||
if access_key and secret_key:
|
|
||||||
s3_params['aws_access_key_id'] = access_key
|
|
||||||
s3_params['aws_secret_access_key'] = secret_key
|
|
||||||
if endpoint_url:
|
|
||||||
s3_params['endpoint_url'] = endpoint_url
|
|
||||||
|
|
||||||
s3 = boto3.client('s3', **s3_params)
|
|
||||||
|
|
||||||
futures = []
|
|
||||||
with ThreadPoolExecutor(max_workers=concurrency) as executor:
|
|
||||||
for url in urls:
|
|
||||||
object_key = derive_key_from_url(url)
|
|
||||||
futures.append(executor.submit(upload_url_to_s3, s3, bucket_name, url, object_key))
|
|
||||||
for future in as_completed(futures):
|
|
||||||
try:
|
|
||||||
uploaded_url, uploaded_key = future.result()
|
|
||||||
print(f"Uploaded: {uploaded_url}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error uploading: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
https://builds.rebootfn.org/1.7.2.zip
|
|
||||||
https://builds.rebootfn.org/1.8.rar
|
|
||||||
https://builds.rebootfn.org/1.8.1.rar
|
|
||||||
https://builds.rebootfn.org/1.8.2.rar
|
|
||||||
https://builds.rebootfn.org/1.9.rar
|
|
||||||
https://builds.rebootfn.org/1.9.1.rar
|
|
||||||
https://builds.rebootfn.org/1.10.rar
|
|
||||||
https://builds.rebootfn.org/1.11.zip
|
|
||||||
https://builds.rebootfn.org/2.1.0.zip
|
|
||||||
https://builds.rebootfn.org/2.2.0.rar
|
|
||||||
https://builds.rebootfn.org/2.3.rar
|
|
||||||
https://builds.rebootfn.org/2.4.0.zip
|
|
||||||
https://builds.rebootfn.org/2.4.2.zip
|
|
||||||
https://builds.rebootfn.org/2.5.0.rar
|
|
||||||
https://builds.rebootfn.org/3.0.zip
|
|
||||||
https://builds.rebootfn.org/3.1.rar
|
|
||||||
https://builds.rebootfn.org/3.1.1.zip
|
|
||||||
https://builds.rebootfn.org/3.2.zip
|
|
||||||
https://builds.rebootfn.org/3.3.rar
|
|
||||||
https://builds.rebootfn.org/3.5.rar
|
|
||||||
https://builds.rebootfn.org/3.6.zip
|
|
||||||
https://builds.rebootfn.org/4.0.zip
|
|
||||||
https://builds.rebootfn.org/4.1.zip
|
|
||||||
https://builds.rebootfn.org/4.2.zip
|
|
||||||
https://builds.rebootfn.org/4.4.rar
|
|
||||||
https://builds.rebootfn.org/4.5.rar
|
|
||||||
https://builds.rebootfn.org/5.00.rar
|
|
||||||
https://builds.rebootfn.org/5.0.1.rar
|
|
||||||
https://builds.rebootfn.org/5.10.rar
|
|
||||||
https://builds.rebootfn.org/5.21.rar
|
|
||||||
https://builds.rebootfn.org/5.30.rar
|
|
||||||
https://builds.rebootfn.org/5.40.rar
|
|
||||||
https://builds.rebootfn.org/6.00.rar
|
|
||||||
https://builds.rebootfn.org/6.01.rar
|
|
||||||
https://builds.rebootfn.org/6.1.1.rar
|
|
||||||
https://builds.rebootfn.org/6.02.rar
|
|
||||||
https://builds.rebootfn.org/6.2.1.rar
|
|
||||||
https://builds.rebootfn.org/6.10.rar
|
|
||||||
https://builds.rebootfn.org/6.10.1.rar
|
|
||||||
https://builds.rebootfn.org/6.10.2.rar
|
|
||||||
https://builds.rebootfn.org/6.21.rar
|
|
||||||
https://builds.rebootfn.org/6.22.rar
|
|
||||||
https://builds.rebootfn.org/6.30.rar
|
|
||||||
https://builds.rebootfn.org/6.31.rar
|
|
||||||
https://builds.rebootfn.org/7.00.rar
|
|
||||||
https://builds.rebootfn.org/7.10.rar
|
|
||||||
https://builds.rebootfn.org/7.20.rar
|
|
||||||
https://builds.rebootfn.org/7.30.zip
|
|
||||||
https://builds.rebootfn.org/7.40.rar
|
|
||||||
https://builds.rebootfn.org/8.00.zip
|
|
||||||
https://builds.rebootfn.org/8.20.rar
|
|
||||||
https://builds.rebootfn.org/8.30.rar
|
|
||||||
https://builds.rebootfn.org/8.40.zip
|
|
||||||
https://builds.rebootfn.org/8.50.zip
|
|
||||||
https://builds.rebootfn.org/8.51.rar
|
|
||||||
https://builds.rebootfn.org/9.00.zip
|
|
||||||
https://builds.rebootfn.org/9.01.zip
|
|
||||||
https://builds.rebootfn.org/9.10.rar
|
|
||||||
https://builds.rebootfn.org/9.21.zip
|
|
||||||
https://builds.rebootfn.org/9.30.zip
|
|
||||||
https://builds.rebootfn.org/9.40.zip
|
|
||||||
https://builds.rebootfn.org/9.41.rar
|
|
||||||
https://builds.rebootfn.org/10.00.zip
|
|
||||||
https://builds.rebootfn.org/10.10.zip
|
|
||||||
https://builds.rebootfn.org/10.20.zip
|
|
||||||
https://builds.rebootfn.org/10.31.zip
|
|
||||||
https://builds.rebootfn.org/10.40.rar
|
|
||||||
https://builds.rebootfn.org/11.00.zip
|
|
||||||
https://builds.rebootfn.org/11.31.rar
|
|
||||||
https://builds.rebootfn.org/12.00.rar
|
|
||||||
https://builds.rebootfn.org/12.21.zip
|
|
||||||
https://builds.rebootfn.org/12.50.zip
|
|
||||||
https://builds.rebootfn.org/12.61.zip
|
|
||||||
https://builds.rebootfn.org/13.00.rar
|
|
||||||
https://builds.rebootfn.org/13.40.zip
|
|
||||||
https://builds.rebootfn.org/14.00.rar
|
|
||||||
https://builds.rebootfn.org/14.40.rar
|
|
||||||
https://builds.rebootfn.org/14.60.rar
|
|
||||||
https://builds.rebootfn.org/15.30.rar
|
|
||||||
https://builds.rebootfn.org/16.40.rar
|
|
||||||
https://builds.rebootfn.org/17.30.zip
|
|
||||||
https://builds.rebootfn.org/17.50.zip
|
|
||||||
https://builds.rebootfn.org/18.40.zip
|
|
||||||
https://builds.rebootfn.org/19.10.rar
|
|
||||||
https://builds.rebootfn.org/20.40.zip
|
|
||||||
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,87 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:args/args.dart';
|
|
||||||
import 'package:reboot_cli/src/game.dart';
|
|
||||||
import 'package:reboot_cli/src/reboot.dart';
|
|
||||||
import 'package:reboot_cli/src/server.dart';
|
|
||||||
import 'package:reboot_common/common.dart';
|
|
||||||
|
|
||||||
late String? username;
|
|
||||||
late bool host;
|
|
||||||
late bool verbose;
|
|
||||||
late String dll;
|
|
||||||
late FortniteVersion version;
|
|
||||||
late bool autoRestart;
|
|
||||||
|
|
||||||
void main(List<String> args) async {
|
|
||||||
stdout.writeln("Reboot Launcher");
|
|
||||||
stdout.writeln("Wrote by Auties00");
|
|
||||||
stdout.writeln("Version 1.0");
|
|
||||||
|
|
||||||
kill();
|
|
||||||
|
|
||||||
var parser = ArgParser()
|
|
||||||
..addOption("path", mandatory: true)
|
|
||||||
..addOption("username")
|
|
||||||
..addOption("server-type", allowed: ServerType.values.map((entry) => entry.name), defaultsTo: ServerType.embedded.name)
|
|
||||||
..addOption("server-host")
|
|
||||||
..addOption("server-port")
|
|
||||||
..addOption("matchmaking-address")
|
|
||||||
..addOption("dll", defaultsTo: rebootDllFile.path)
|
|
||||||
..addFlag("update", defaultsTo: true, negatable: true)
|
|
||||||
..addFlag("log", defaultsTo: false)
|
|
||||||
..addFlag("host", defaultsTo: false)
|
|
||||||
..addFlag("auto-restart", defaultsTo: false, negatable: true);
|
|
||||||
var result = parser.parse(args);
|
|
||||||
|
|
||||||
dll = result["dll"];
|
|
||||||
host = result["host"];
|
|
||||||
username = result["username"] ?? kDefaultPlayerName;
|
|
||||||
verbose = result["log"];
|
|
||||||
version = FortniteVersion(name: "Dummy", location: Directory(result["path"]));
|
|
||||||
|
|
||||||
await downloadRequiredDLLs();
|
|
||||||
if(result["update"]) {
|
|
||||||
stdout.writeln("Updating reboot dll...");
|
|
||||||
try {
|
|
||||||
await downloadRebootDll(kRebootDownloadUrl);
|
|
||||||
}catch(error){
|
|
||||||
stderr.writeln("Cannot update reboot dll: $error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout.writeln("Launching game...");
|
|
||||||
var executable = version.shippingExecutable;
|
|
||||||
if(executable == null){
|
|
||||||
throw Exception("Missing game executable at: ${version.location.path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
final serverHost = result["server-host"]?.trim();
|
|
||||||
if(serverHost?.isEmpty == true){
|
|
||||||
throw Exception("Missing host name");
|
|
||||||
}
|
|
||||||
|
|
||||||
final serverPort = result["server-port"]?.trim();
|
|
||||||
if(serverPort?.isEmpty == true){
|
|
||||||
throw Exception("Missing port");
|
|
||||||
}
|
|
||||||
|
|
||||||
final serverPortNumber = serverPort == null ? null : int.tryParse(serverPort);
|
|
||||||
if(serverPort != null && serverPortNumber == null){
|
|
||||||
throw Exception("Invalid port, use only numbers");
|
|
||||||
}
|
|
||||||
|
|
||||||
var started = await startServerCli(
|
|
||||||
serverHost,
|
|
||||||
serverPortNumber,
|
|
||||||
ServerType.values.firstWhere((element) => element.name == result["server-type"])
|
|
||||||
);
|
|
||||||
if(!started){
|
|
||||||
stderr.writeln("Cannot start server!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeMatchmakingIp(result["matchmaking-address"]);
|
|
||||||
autoRestart = result["auto-restart"];
|
|
||||||
await startGame();
|
|
||||||
}
|
|
||||||
437
cli/lib/main.dart
Normal file
437
cli/lib/main.dart
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
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 _buildDownload = Command(name: 'download', parameters: ['version', 'path'], subCommands: []);
|
||||||
|
const Command _build = Command(name: 'versions', parameters: [], subCommands: [_buildList, _buildImport, _buildDownload]);
|
||||||
|
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.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;
|
||||||
|
useDefaultPath = true;
|
||||||
|
|
||||||
|
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);
|
||||||
|
await _handleRootCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleRootCommand(CommandCall? command) async {
|
||||||
|
if(command == null) {
|
||||||
|
await _askRootCommand();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command.name) {
|
||||||
|
case 'versions':
|
||||||
|
await _handleBuildCommand(command.subCall);
|
||||||
|
break;
|
||||||
|
case 'play':
|
||||||
|
_handlePlayCommand(command.subCall);
|
||||||
|
break;
|
||||||
|
case 'host':
|
||||||
|
_handleHostCommand(command.subCall);
|
||||||
|
break;
|
||||||
|
case 'backend':
|
||||||
|
_handleBackendCommand(command.subCall);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await _askRootCommand();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _askRootCommand() async {
|
||||||
|
final commands = [_build.name, _play.name, _host.name, _backend.name];
|
||||||
|
final commandSelector = Select.withTheme(
|
||||||
|
prompt: ' Select a command:',
|
||||||
|
options: commands,
|
||||||
|
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
||||||
|
);
|
||||||
|
await _handleRootCommand(CommandCall(name: commands[commandSelector.interact()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleBuildCommand(CommandCall? call) async {
|
||||||
|
if(call == null) {
|
||||||
|
_askBuildCommand();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(call.name) {
|
||||||
|
case 'import':
|
||||||
|
await _handleBuildImportCommand(call);
|
||||||
|
break;
|
||||||
|
case 'download':
|
||||||
|
_handleBuildDownloadCommand(call);
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
_handleBuildListCommand(call);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_askBuildCommand();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleBuildListCommand(CommandCall commandCall) {
|
||||||
|
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.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() {
|
||||||
|
final commands = [_buildList.name, _buildImport.name, _buildDownload.name];
|
||||||
|
final commandSelector = Select.withTheme(
|
||||||
|
prompt: ' Select a version command:',
|
||||||
|
options: commands,
|
||||||
|
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '', successSuffix: '', errorPrefix: '❌')
|
||||||
|
);
|
||||||
|
_handleBuildCommand(CommandCall(name: commands[commandSelector.interact()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handlePlayCommand(CommandCall? call) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleHostCommand(CommandCall? call) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleBackendCommand(CommandCall? call) {
|
||||||
|
|
||||||
|
}
|
||||||
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,123 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:process_run/process_run.dart';
|
|
||||||
import 'package:reboot_cli/cli.dart';
|
|
||||||
import 'package:reboot_common/common.dart';
|
|
||||||
|
|
||||||
Process? _gameProcess;
|
|
||||||
Process? _launcherProcess;
|
|
||||||
Process? _eacProcess;
|
|
||||||
|
|
||||||
Future<void> startGame() async {
|
|
||||||
await _startLauncherProcess(version);
|
|
||||||
await _startEacProcess(version);
|
|
||||||
|
|
||||||
var executable = await version.shippingExecutable;
|
|
||||||
if (executable == null) {
|
|
||||||
throw Exception("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username == null) {
|
|
||||||
username = "Reboot${host ? 'Host' : 'Player'}";
|
|
||||||
stdout.writeln("No username was specified, using $username by default. Use --username to specify one");
|
|
||||||
}
|
|
||||||
|
|
||||||
_gameProcess = await Process.start(executable.path, createRebootArgs(username!, "", host, host, ""))
|
|
||||||
..exitCode.then((_) => _onClose())
|
|
||||||
..stdOutput.forEach((line) => _onGameOutput(line, dll, host, verbose));
|
|
||||||
_injectOrShowError("cobalt.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Future<void> _startLauncherProcess(FortniteVersion dummyVersion) async {
|
|
||||||
if (dummyVersion.launcherExecutable == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_launcherProcess = await Process.start(dummyVersion.launcherExecutable!.path, []);
|
|
||||||
suspend(_launcherProcess!.pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _startEacProcess(FortniteVersion dummyVersion) async {
|
|
||||||
if (dummyVersion.eacExecutable == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_eacProcess = await Process.start(dummyVersion.eacExecutable!.path, []);
|
|
||||||
suspend(_eacProcess!.pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onGameOutput(String line, String dll, bool hosting, bool verbose) {
|
|
||||||
if(verbose) {
|
|
||||||
stdout.writeln(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleGameOutput(
|
|
||||||
line: line,
|
|
||||||
host: hosting,
|
|
||||||
onDisplayAttached: () {}, // TODO: Support virtual desktops
|
|
||||||
onLoggedIn: onLoggedIn,
|
|
||||||
onMatchEnd: onMatchEnd,
|
|
||||||
onShutdown: onShutdown,
|
|
||||||
onTokenError: onTokenError,
|
|
||||||
onBuildCorrupted: onBuildCorrupted
|
|
||||||
);
|
|
||||||
|
|
||||||
if (line.contains(kShutdownLine)) {
|
|
||||||
_onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(kCannotConnectErrors.any((element) => line.contains(element))){
|
|
||||||
stderr.writeln("The backend doesn't work! Token expired");
|
|
||||||
_onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(line.contains("Region ")){
|
|
||||||
if(hosting) {
|
|
||||||
_injectOrShowError(dll, false);
|
|
||||||
}else {
|
|
||||||
_injectOrShowError("console.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
_injectOrShowError("memory.dll");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _kill() {
|
|
||||||
_gameProcess?.kill(ProcessSignal.sigabrt);
|
|
||||||
_launcherProcess?.kill(ProcessSignal.sigabrt);
|
|
||||||
_eacProcess?.kill(ProcessSignal.sigabrt);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _injectOrShowError(String binary, [bool locate = true]) async {
|
|
||||||
if (_gameProcess == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
stdout.writeln("Injecting $binary...");
|
|
||||||
var dll = locate ? File("${dllsDirectory.path}\\$binary") : File(binary);
|
|
||||||
if(!dll.existsSync()){
|
|
||||||
throw Exception("Cannot inject $dll: missing file");
|
|
||||||
}
|
|
||||||
|
|
||||||
await injectDll(_gameProcess!.pid, dll);
|
|
||||||
} catch (exception) {
|
|
||||||
throw Exception("Cannot inject binary: $binary");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onClose() {
|
|
||||||
_kill();
|
|
||||||
sleep(const Duration(seconds: 3));
|
|
||||||
stdout.writeln("The game was closed");
|
|
||||||
if(autoRestart){
|
|
||||||
stdout.writeln("Restarting automatically game");
|
|
||||||
startGame();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:archive/archive_io.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:reboot_common/common.dart';
|
|
||||||
|
|
||||||
// TODO: Use github
|
|
||||||
const String _baseDownload = "https://cdn.discordapp.com/attachments/1095351875961901057/1110968021373169674/cobalt.dll";
|
|
||||||
const String _consoleDownload = "https://cdn.discordapp.com/attachments/1095351875961901057/1110968095033524234/console.dll";
|
|
||||||
const String _memoryFixDownload = "https://cdn.discordapp.com/attachments/1095351875961901057/1110968141556756581/memory.dll";
|
|
||||||
const String _embeddedConfigDownload = "https://cdn.discordapp.com/attachments/1026121175878881290/1040679319351066644/embedded.zip";
|
|
||||||
|
|
||||||
Future<void> downloadRequiredDLLs() async {
|
|
||||||
stdout.writeln("Downloading necessary components...");
|
|
||||||
var consoleDll = File("${dllsDirectory.path}\\console.dll");
|
|
||||||
if(!consoleDll.existsSync()){
|
|
||||||
var response = await http.get(Uri.parse(_consoleDownload));
|
|
||||||
if(response.statusCode != 200){
|
|
||||||
throw Exception("Cannot download console.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
await consoleDll.writeAsBytes(response.bodyBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
var craniumDll = File("${dllsDirectory.path}\\cobalt.dll");
|
|
||||||
if(!craniumDll.existsSync()){
|
|
||||||
var response = await http.get(Uri.parse(_baseDownload));
|
|
||||||
if(response.statusCode != 200){
|
|
||||||
throw Exception("Cannot download cobalt.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
await craniumDll.writeAsBytes(response.bodyBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
var memoryFixDll = File("${dllsDirectory.path}\\memory.dll");
|
|
||||||
if(!memoryFixDll.existsSync()){
|
|
||||||
var response = await http.get(Uri.parse(_memoryFixDownload));
|
|
||||||
if(response.statusCode != 200){
|
|
||||||
throw Exception("Cannot download memory.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
await memoryFixDll.writeAsBytes(response.bodyBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!backendDirectory.existsSync()){
|
|
||||||
var response = await http.get(Uri.parse(_embeddedConfigDownload));
|
|
||||||
if(response.statusCode != 200){
|
|
||||||
throw Exception("Cannot download embedded server config");
|
|
||||||
}
|
|
||||||
|
|
||||||
var tempZip = File("${tempDirectory.path}/reboot_config.zip");
|
|
||||||
await tempZip.writeAsBytes(response.bodyBytes);
|
|
||||||
await extractFileToDisk(tempZip.path, backendDirectory.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:reboot_common/common.dart';
|
|
||||||
import 'package:reboot_common/src/util/backend.dart' as server;
|
|
||||||
|
|
||||||
Future<bool> startServerCli(String? host, int? port, ServerType type) async {
|
|
||||||
stdout.writeln("Starting backend server...");
|
|
||||||
switch(type){
|
|
||||||
case ServerType.local:
|
|
||||||
final result = await pingBackend(host ?? kDefaultBackendHost, port ?? kDefaultBackendPort);
|
|
||||||
if(result == null){
|
|
||||||
throw Exception("Local backend server is not running");
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout.writeln("Detected local backend server");
|
|
||||||
return true;
|
|
||||||
case ServerType.embedded:
|
|
||||||
stdout.writeln("Starting an embedded server...");
|
|
||||||
await server.startEmbeddedBackend(false);
|
|
||||||
var result = await pingBackend(host ?? kDefaultBackendHost, port ?? kDefaultBackendPort);
|
|
||||||
if(result == null){
|
|
||||||
throw Exception("Cannot start embedded server");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
case ServerType.remote:
|
|
||||||
if(host == null){
|
|
||||||
throw Exception("Missing host for remote server");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(port == null){
|
|
||||||
throw Exception("Missing host for remote server");
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout.writeln("Starting a reverse proxy to $host:$port");
|
|
||||||
return await _changeReverseProxyState(host, port) != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<HttpServer?> _changeReverseProxyState(String host, int port) async {
|
|
||||||
try{
|
|
||||||
var uri = await pingBackend(host, port);
|
|
||||||
if(uri == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await server.startRemoteBackendProxy(uri);
|
|
||||||
}catch(error){
|
|
||||||
throw Exception("Cannot start reverse proxy");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void kill() async {
|
|
||||||
try {
|
|
||||||
await Process.run("taskkill", ["/f", "/im", "FortniteLauncher.exe"]);
|
|
||||||
await Process.run("taskkill", ["/f", "/im", "FortniteClient-Win64-Shipping_EAC.exe"]);
|
|
||||||
}catch(_){
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user