mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 19:22:22 +01:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90448eeaa1 | ||
|
|
b319479def | ||
|
|
d5e41ed646 | ||
|
|
9e20ec86e6 | ||
|
|
004fc41292 | ||
|
|
ee466df630 | ||
|
|
fdb1d694d9 | ||
|
|
0cfa4af236 | ||
|
|
d42946c44b | ||
|
|
0a59a32c1b | ||
|
|
2046cb14f6 | ||
|
|
e3f7a1d2cc | ||
|
|
cd6752ed3f | ||
|
|
e1df46efd9 |
@@ -1,7 +1,7 @@
|
||||

|
||||
|
||||
GUI and CLI Launcher for [Project Reboot](https://github.com/Milxnor/Project-Reboot-3.0/)
|
||||
Join our discord at https://discord.gg/reboot
|
||||
Join our [Discord](https://discord.gg/rebootmp)
|
||||
|
||||
## Modules
|
||||
|
||||
|
||||
158
cli/lib/cli.dart
158
cli/lib/cli.dart
@@ -1,87 +1,89 @@
|
||||
import 'dart:io';
|
||||
import 'dart:collection';
|
||||
|
||||
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';
|
||||
class Parser {
|
||||
final List<Command> commands;
|
||||
|
||||
late String? username;
|
||||
late bool host;
|
||||
late bool verbose;
|
||||
late String dll;
|
||||
late FortniteVersion version;
|
||||
late bool autoRestart;
|
||||
Parser({required this.commands});
|
||||
|
||||
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");
|
||||
CommandCall? parse(List<String> args) {
|
||||
var position = 0;
|
||||
var allowedCommands = _toMap(commands);
|
||||
var allowedParameters = <String>{};
|
||||
Command? command;
|
||||
CommandCall? head;
|
||||
CommandCall? tail;
|
||||
String? parameterName;
|
||||
while(position < args.length) {
|
||||
final current = args[position].toLowerCase();
|
||||
if(parameterName != null) {
|
||||
tail?.parameters[parameterName] = current;
|
||||
parameterName = null;
|
||||
}else if(allowedParameters.contains(current.toLowerCase())) {
|
||||
parameterName = current.substring(2);
|
||||
if(args.elementAtOrNull(position + 1) == '"') {
|
||||
position++;
|
||||
}
|
||||
}else {
|
||||
final newCommand = allowedCommands[current];
|
||||
if(newCommand != null) {
|
||||
final newCall = CommandCall(name: newCommand.name);
|
||||
if(head == null) {
|
||||
head = newCall;
|
||||
tail = newCall;
|
||||
}
|
||||
if(tail != null) {
|
||||
tail.subCall = newCall;
|
||||
}
|
||||
tail = newCall;
|
||||
command = newCommand;
|
||||
allowedCommands = _toMap(newCommand.subCommands);
|
||||
allowedParameters = _toParameters(command);
|
||||
}
|
||||
}
|
||||
position++;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
stdout.writeln("Launching game...");
|
||||
var executable = version.shippingExecutable;
|
||||
if(executable == null){
|
||||
throw Exception("Missing game executable at: ${version.location.path}");
|
||||
}
|
||||
Set<String> _toParameters(Command? parent) => parent?.parameters
|
||||
.map((e) => '--${e.toLowerCase()}')
|
||||
.toSet() ?? {};
|
||||
|
||||
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"])
|
||||
Map<String, Command> _toMap(List<Command> children) => Map.fromIterable(
|
||||
children,
|
||||
key: (command) => command.name.toLowerCase(),
|
||||
value: (command) => command
|
||||
);
|
||||
if(!started){
|
||||
stderr.writeln("Cannot start server!");
|
||||
return;
|
||||
}
|
||||
|
||||
writeMatchmakingIp(result["matchmaking-address"]);
|
||||
autoRestart = result["auto-restart"];
|
||||
await startGame();
|
||||
}
|
||||
|
||||
class Command {
|
||||
final String name;
|
||||
final List<String> parameters;
|
||||
final List<Command> subCommands;
|
||||
|
||||
const Command({required this.name, required this.parameters, required this.subCommands});
|
||||
|
||||
@override
|
||||
String toString() => 'Command{name: $name, parameters: $parameters, subCommands: $subCommands}';
|
||||
}
|
||||
|
||||
class Parameter {
|
||||
final String name;
|
||||
final bool Function(String) validator;
|
||||
|
||||
const Parameter({required this.name, required this.validator});
|
||||
|
||||
@override
|
||||
String toString() => 'Parameter{name: $name, validator: $validator}';
|
||||
}
|
||||
|
||||
class CommandCall {
|
||||
final String name;
|
||||
final Map<String, String> parameters;
|
||||
CommandCall? subCall;
|
||||
|
||||
CommandCall({required this.name}) : parameters = {};
|
||||
|
||||
@override
|
||||
String toString() => 'CommandCall{name: $name, parameters: $parameters, subCall: $subCall}';
|
||||
}
|
||||
102
cli/lib/main.dart
Normal file
102
cli/lib/main.dart
Normal file
@@ -0,0 +1,102 @@
|
||||
import 'package:interact/interact.dart';
|
||||
import 'package:reboot_cli/cli.dart';
|
||||
import 'package:tint/tint.dart';
|
||||
|
||||
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: 'build', parameters: [], subCommands: [_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: []);
|
||||
|
||||
void main(List<String> args) {
|
||||
_welcome();
|
||||
|
||||
final parser = Parser(commands: [_build, _play, _host, _backend]);
|
||||
final command = parser.parse(args);
|
||||
print(command);
|
||||
_handleRootCommand(command);
|
||||
}
|
||||
|
||||
void _handleRootCommand(CommandCall? call) {
|
||||
switch(call == null ? null : call.name) {
|
||||
case 'build':
|
||||
_handleBuildCommand(call?.subCall);
|
||||
break;
|
||||
case 'play':
|
||||
_handleBuildCommand(call?.subCall);
|
||||
break;
|
||||
case 'host':
|
||||
_handleBuildCommand(call?.subCall);
|
||||
break;
|
||||
case 'backend':
|
||||
_handleBuildCommand(call?.subCall);
|
||||
break;
|
||||
default:
|
||||
_askRootCommand();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _askRootCommand() {
|
||||
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: '')
|
||||
);
|
||||
_handleRootCommand(CommandCall(name: commands[commandSelector.interact()]));
|
||||
}
|
||||
|
||||
void _handleBuildCommand(CommandCall? call) {
|
||||
switch(call == null ? null : call.name) {
|
||||
case 'import':
|
||||
_handleBuildImportCommand(call!);
|
||||
break;
|
||||
case 'download':
|
||||
_handleBuildDownloadCommand(call!);
|
||||
break;
|
||||
default:
|
||||
_askBuildCommand();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleBuildImportCommand(CommandCall call) {
|
||||
final version = call.parameters['path'];
|
||||
final path = call.parameters['path'];
|
||||
print(version);
|
||||
print(path);
|
||||
}
|
||||
|
||||
void _handleBuildDownloadCommand(CommandCall call) {
|
||||
|
||||
}
|
||||
|
||||
void _askBuildCommand() {
|
||||
final commands = [_buildImport.name, _buildDownload.name];
|
||||
final commandSelector = Select.withTheme(
|
||||
prompt: ' Select a build command:',
|
||||
options: commands,
|
||||
theme: Theme.colorfulTheme.copyWith(inputPrefix: '❓', inputSuffix: '')
|
||||
);
|
||||
_handleBuildCommand(CommandCall(name: commands[commandSelector.interact()]));
|
||||
}
|
||||
|
||||
void _handlePlayCommand(CommandCall? call) {
|
||||
|
||||
}
|
||||
|
||||
void _handleHostCommand(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());
|
||||
@@ -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(_){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
name: reboot_cli
|
||||
description: Command Line Interface for Project Reboot
|
||||
version: "1.0.0"
|
||||
version: "10.0.7"
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.0 <=3.3.4"
|
||||
sdk: ">=2.19.0 <=3.5.3"
|
||||
|
||||
dependencies:
|
||||
reboot_common:
|
||||
path: ./../common
|
||||
args: ^2.3.1
|
||||
process_run: ^0.13.1
|
||||
tint: ^2.0.1
|
||||
interact: ^2.2.0
|
||||
args: ^2.6.0
|
||||
|
||||
dependency_overrides:
|
||||
xml: ^6.3.0
|
||||
|
||||
@@ -22,4 +22,5 @@ const List<String> kCannotConnectErrors = [
|
||||
"UOnlineAccountCommon::ForceLogout"
|
||||
];
|
||||
const String kGameFinishedLine = "PlayersLeft: 1";
|
||||
const String kDisplayInitializedLine = "Display";
|
||||
const String kDisplayLine = "Display";
|
||||
const String kDisplayInitializedLine = "Initialized";
|
||||
@@ -1,9 +1,10 @@
|
||||
enum InjectableDll {
|
||||
console,
|
||||
starfall,
|
||||
reboot,
|
||||
auth,
|
||||
gameServer,
|
||||
memoryLeak
|
||||
}
|
||||
|
||||
extension InjectableDllVersionAware on InjectableDll {
|
||||
bool get isVersionDependent => this == InjectableDll.reboot;
|
||||
bool get isVersionDependent => this == InjectableDll.gameServer;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
class ServerResult {
|
||||
final ServerResultType type;
|
||||
final ServerImplementation? implementation;
|
||||
final Object? error;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
ServerResult(this.type, {this.error, this.stackTrace});
|
||||
ServerResult(this.type, {this.implementation, this.error, this.stackTrace});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -11,22 +14,32 @@ class ServerResult {
|
||||
}
|
||||
}
|
||||
|
||||
class ServerImplementation {
|
||||
final Process? process;
|
||||
final HttpServer? server;
|
||||
|
||||
ServerImplementation({this.process, this.server});
|
||||
}
|
||||
|
||||
enum ServerResultType {
|
||||
starting,
|
||||
startMissingHostError,
|
||||
startMissingPortError,
|
||||
startIllegalPortError,
|
||||
startFreeingPort,
|
||||
startFreePortSuccess,
|
||||
startFreePortError,
|
||||
startPingingRemote,
|
||||
startPingingLocal,
|
||||
startPingError,
|
||||
startedImplementation,
|
||||
startSuccess,
|
||||
startError,
|
||||
stopping,
|
||||
stopSuccess,
|
||||
stopError,
|
||||
missingHostError,
|
||||
missingPortError,
|
||||
illegalPortError,
|
||||
freeingPort,
|
||||
freePortSuccess,
|
||||
freePortError,
|
||||
pingingRemote,
|
||||
pingingLocal,
|
||||
pingError;
|
||||
stopError;
|
||||
|
||||
bool get isStart => name.contains("start");
|
||||
|
||||
bool get isError => name.contains("Error");
|
||||
|
||||
|
||||
@@ -15,6 +15,122 @@ 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* {
|
||||
Process? process;
|
||||
HttpServer? server;
|
||||
try {
|
||||
host = host.trim();
|
||||
port = port.trim();
|
||||
if(type != ServerType.local || port != kDefaultBackendPort.toString()) {
|
||||
yield ServerResult(ServerResultType.starting);
|
||||
}
|
||||
|
||||
if (host.isEmpty) {
|
||||
yield ServerResult(ServerResultType.startMissingHostError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (port.isEmpty) {
|
||||
yield ServerResult(ServerResultType.startMissingPortError);
|
||||
return;
|
||||
}
|
||||
|
||||
final portNumber = int.tryParse(port);
|
||||
if (portNumber == null) {
|
||||
yield ServerResult(ServerResultType.startIllegalPortError);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((type != ServerType.local || port != kDefaultBackendPort.toString()) && !(await isBackendPortFree())) {
|
||||
yield ServerResult(ServerResultType.startFreeingPort);
|
||||
final result = await freeBackendPort();
|
||||
if(!result) {
|
||||
yield ServerResult(ServerResultType.startFreePortError);
|
||||
return;
|
||||
}
|
||||
|
||||
yield ServerResult(ServerResultType.startFreePortSuccess);
|
||||
}
|
||||
|
||||
switch(type){
|
||||
case ServerType.embedded:
|
||||
process = await startEmbeddedBackend(detached, onError: onError);
|
||||
yield ServerResult(ServerResultType.startedImplementation, implementation: ServerImplementation(process: process));
|
||||
break;
|
||||
case ServerType.remote:
|
||||
yield ServerResult(ServerResultType.startPingingRemote);
|
||||
final uriResult = await pingBackend(host, portNumber);
|
||||
if(uriResult == null) {
|
||||
yield ServerResult(ServerResultType.startPingError);
|
||||
return;
|
||||
}
|
||||
|
||||
server = await startRemoteBackendProxy(uriResult);
|
||||
yield ServerResult(ServerResultType.startedImplementation, implementation: ServerImplementation(server: server));
|
||||
break;
|
||||
case ServerType.local:
|
||||
if(portNumber != kDefaultBackendPort) {
|
||||
yield ServerResult(ServerResultType.startPingingLocal);
|
||||
final uriResult = await pingBackend(kDefaultBackendHost, portNumber);
|
||||
if(uriResult == null) {
|
||||
yield ServerResult(ServerResultType.startPingError);
|
||||
return;
|
||||
}
|
||||
|
||||
server = await startRemoteBackendProxy(Uri.parse("http://$kDefaultBackendHost:$port"));
|
||||
yield ServerResult(ServerResultType.startedImplementation, implementation: ServerImplementation(server: server));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
yield ServerResult(ServerResultType.startPingingLocal);
|
||||
final uriResult = await pingBackend(kDefaultBackendHost, kDefaultBackendPort);
|
||||
if(uriResult == null) {
|
||||
yield ServerResult(ServerResultType.startPingError);
|
||||
process?.kill(ProcessSignal.sigterm);
|
||||
server?.close(force: true);
|
||||
return;
|
||||
}
|
||||
|
||||
yield ServerResult(ServerResultType.startSuccess);
|
||||
}catch(error, stackTrace) {
|
||||
yield ServerResult(
|
||||
ServerResultType.startError,
|
||||
error: error,
|
||||
stackTrace: stackTrace
|
||||
);
|
||||
process?.kill(ProcessSignal.sigterm);
|
||||
server?.close(force: true);
|
||||
}
|
||||
}
|
||||
|
||||
Stream<ServerResult> stopBackend({required ServerType type, required ServerImplementation? implementation}) 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(
|
||||
executable: backendStartExecutable,
|
||||
@@ -25,7 +141,9 @@ Future<Process> startEmbeddedBackend(bool detached, {void Function(String)? onEr
|
||||
log("[BACKEND] Error: $error");
|
||||
onError?.call(error);
|
||||
});
|
||||
process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode"));
|
||||
if(!detached) {
|
||||
process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode"));
|
||||
}
|
||||
return process;
|
||||
}
|
||||
|
||||
@@ -61,7 +179,7 @@ Future<Uri?> pingBackend(String host, int port, [bool https=false]) async {
|
||||
await request.close().timeout(const Duration(seconds: 10));
|
||||
log("[BACKEND] Ping successful");
|
||||
return uri;
|
||||
}catch(error){
|
||||
}catch(error) {
|
||||
log("[BACKEND] Cannot ping backend: $error");
|
||||
return https || declaredScheme != null || isLocalHost(host) ? null : await pingBackend(host, port, true);
|
||||
}
|
||||
|
||||
@@ -13,37 +13,99 @@ import 'package:http/http.dart' as http;
|
||||
|
||||
const String kStopBuildDownloadSignal = "kill";
|
||||
|
||||
final Uri _archiveSourceUrl = Uri.parse("https://builds.rebootfn.org/versions.json");
|
||||
final int _ariaPort = 6800;
|
||||
final Uri _ariaEndpoint = Uri.parse('http://localhost:$_ariaPort/jsonrpc');
|
||||
final Duration _ariaMaxSpawnTime = const Duration(seconds: 10);
|
||||
final String _ariaSecret = "RebootLauncher";
|
||||
final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$");
|
||||
final List<FortniteBuild> downloadableBuilds = [
|
||||
FortniteBuild(version: Version.parse("1.7.2"), link: "https://public.simplyblk.xyz/1.7.2.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("1.8"), link: "https://public.simplyblk.xyz/1.8.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("1.8.1"), link: "https://public.simplyblk.xyz/1.8.1.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("1.8.2"), link: "https://public.simplyblk.xyz/1.8.2.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("1.9"), link: "https://public.simplyblk.xyz/1.9.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("1.9.1"), link: "https://public.simplyblk.xyz/1.9.1.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("1.10"), link: "https://public.simplyblk.xyz/1.10.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("1.11"), link: "https://public.simplyblk.xyz/1.11.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("2.1.0"), link: "https://public.simplyblk.xyz/2.1.0.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("2.2.0"), link: "https://public.simplyblk.xyz/2.2.0.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("2.3"), link: "https://public.simplyblk.xyz/2.3.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("2.4.0"), link: "https://public.simplyblk.xyz/2.4.0.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("2.4.2"), link: "https://public.simplyblk.xyz/2.4.2.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("2.5.0"), link: "https://public.simplyblk.xyz/2.5.0.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("3.0"), link: "https://public.simplyblk.xyz/3.0.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("3.1"), link: "https://public.simplyblk.xyz/3.1.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("3.1.1"), link: "https://public.simplyblk.xyz/3.1.1.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("3.2"), link: "https://public.simplyblk.xyz/3.2.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("3.3"), link: "https://public.simplyblk.xyz/3.3.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("3.5"), link: "https://public.simplyblk.xyz/3.5.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("3.6"), link: "https://public.simplyblk.xyz/3.6.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("4.0"), link: "https://public.simplyblk.xyz/4.0.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("4.1"), link: "https://public.simplyblk.xyz/4.1.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("4.2"), link: "https://public.simplyblk.xyz/4.2.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("4.4"), link: "https://public.simplyblk.xyz/4.4.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("4.5"), link: "https://public.simplyblk.xyz/4.5.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("5.00"), link: "https://public.simplyblk.xyz/5.00.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("5.0.1"), link: "https://public.simplyblk.xyz/5.0.1.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("5.10"), link: "https://public.simplyblk.xyz/5.10.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("5.21"), link: "https://public.simplyblk.xyz/5.21.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("5.30"), link: "https://public.simplyblk.xyz/5.30.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("5.40"), link: "https://public.simplyblk.xyz/5.40.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.00"), link: "https://public.simplyblk.xyz/6.00.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.01"), link: "https://public.simplyblk.xyz/6.01.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.1.1"), link: "https://public.simplyblk.xyz/6.1.1.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.02"), link: "https://public.simplyblk.xyz/6.02.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.2.1"), link: "https://public.simplyblk.xyz/6.2.1.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.10"), link: "https://public.simplyblk.xyz/6.10.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.10.1"), link: "https://public.simplyblk.xyz/6.10.1.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.10.2"), link: "https://public.simplyblk.xyz/6.10.2.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.21"), link: "https://public.simplyblk.xyz/6.21.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.22"), link: "https://public.simplyblk.xyz/6.22.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.30"), link: "https://public.simplyblk.xyz/6.30.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("6.31"), link: "https://public.simplyblk.xyz/6.31.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("7.00"), link: "https://public.simplyblk.xyz/7.00.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("7.10"), link: "https://public.simplyblk.xyz/7.10.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("7.20"), link: "https://public.simplyblk.xyz/7.20.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("7.30"), link: "https://public.simplyblk.xyz/7.30.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("7.40"), link: "https://public.simplyblk.xyz/7.40.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("8.00"), link: "https://public.simplyblk.xyz/8.00.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("8.20"), link: "https://public.simplyblk.xyz/8.20.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("8.30"), link: "https://public.simplyblk.xyz/8.30.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("8.40"), link: "https://public.simplyblk.xyz/8.40.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("8.50"), link: "https://public.simplyblk.xyz/8.50.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("8.51"), link: "https://public.simplyblk.xyz/8.51.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("9.00"), link: "https://public.simplyblk.xyz/9.00.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("9.01"), link: "https://public.simplyblk.xyz/9.01.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("9.10"), link: "https://public.simplyblk.xyz/9.10.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("9.21"), link: "https://public.simplyblk.xyz/9.21.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("9.30"), link: "https://public.simplyblk.xyz/9.30.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("9.40"), link: "https://public.simplyblk.xyz/9.40.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("9.41"), link: "https://public.simplyblk.xyz/9.41.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("10.00"), link: "https://public.simplyblk.xyz/10.00.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("10.10"), link: "https://public.simplyblk.xyz/10.10.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("10.20"), link: "https://public.simplyblk.xyz/10.20.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("10.31"), link: "https://public.simplyblk.xyz/10.31.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("10.40"), link: "https://public.simplyblk.xyz/10.40.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("11.00"), link: "https://public.simplyblk.xyz/11.00.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("11.31"), link: "https://public.simplyblk.xyz/11.31.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("12.00"), link: "https://public.simplyblk.xyz/12.00.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("12.21"), link: "https://public.simplyblk.xyz/12.21.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("12.50"), link: "https://public.simplyblk.xyz/12.50.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("12.61"), link: "https://public.simplyblk.xyz/12.61.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("13.00"), link: "https://public.simplyblk.xyz/13.00.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("13.40"), link: "https://public.simplyblk.xyz/13.40.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("14.00"), link: "https://public.simplyblk.xyz/14.00.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("14.40"), link: "https://public.simplyblk.xyz/14.40.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("14.60"), link: "https://public.simplyblk.xyz/14.60.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("15.30"), link: "https://public.simplyblk.xyz/15.30.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("16.40"), link: "https://public.simplyblk.xyz/16.40.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("17.30"), link: "https://public.simplyblk.xyz/17.30.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("17.50"), link: "https://public.simplyblk.xyz/17.50.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("18.40"), link: "https://public.simplyblk.xyz/18.40.zip", available: true),
|
||||
FortniteBuild(version: Version.parse("19.10"), link: "https://public.simplyblk.xyz/19.10.rar", available: true),
|
||||
FortniteBuild(version: Version.parse("20.40"), link: "https://public.simplyblk.xyz/20.40.zip", available: true),
|
||||
];
|
||||
|
||||
Future<List<FortniteBuild>> fetchBuilds(ignored) async {
|
||||
final response = await http.get(_archiveSourceUrl);
|
||||
if (response.statusCode != 200) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return jsonDecode(response.body)
|
||||
.map((entry) {
|
||||
try {
|
||||
final fileUrl = entry as String;
|
||||
final fileName = Uri.parse(fileUrl).pathSegments.last;
|
||||
final fileNameWithoutExtension = path.basenameWithoutExtension(fileName);
|
||||
return FortniteBuild(
|
||||
version: Version.parse(fileNameWithoutExtension),
|
||||
link: entry,
|
||||
available: true
|
||||
);
|
||||
}catch(_) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.whereType<FortniteBuild>()
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
|
||||
final fileName = options.build.link.substring(options.build.link.lastIndexOf("/") + 1);
|
||||
@@ -134,17 +196,13 @@ Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
|
||||
}
|
||||
|
||||
Future<void> _startAriaServer() async {
|
||||
final running = await _isAriaRunning();
|
||||
if(running) {
|
||||
await killProcessByPort(_ariaPort);
|
||||
}
|
||||
|
||||
await stopDownloadServer();
|
||||
final aria2c = File("${assetsDirectory.path}\\build\\aria2c.exe");
|
||||
if(!aria2c.existsSync()) {
|
||||
throw "Missing aria2c.exe";
|
||||
}
|
||||
|
||||
await startProcess(
|
||||
final process = await startProcess(
|
||||
executable: aria2c,
|
||||
args: [
|
||||
"--max-connection-per-server=${Platform.numberOfProcessors}",
|
||||
@@ -153,10 +211,15 @@ Future<void> _startAriaServer() async {
|
||||
"--rpc-listen-all=true",
|
||||
"--rpc-allow-origin-all",
|
||||
"--rpc-secret=$_ariaSecret",
|
||||
"--rpc-listen-port=$_ariaPort"
|
||||
"--rpc-listen-port=$_ariaPort",
|
||||
"--file-allocation=none",
|
||||
"--check-certificate=false"
|
||||
],
|
||||
window: false
|
||||
window: false
|
||||
);
|
||||
process.stdOutput.listen((message) => log("[ARIA] Message: $message"));
|
||||
process.stdError.listen((error) => log("[ARIA] Error: $error"));
|
||||
process.exitCode.then((exitCode) => log("[ARIA] Exit code: $exitCode"));
|
||||
for(var i = 0; i < _ariaMaxSpawnTime.inSeconds; i++) {
|
||||
if(await _isAriaRunning()) {
|
||||
return;
|
||||
@@ -177,8 +240,8 @@ Future<bool> _isAriaRunning() async {
|
||||
"token:${_ariaSecret}"
|
||||
]
|
||||
};
|
||||
await http.post(_ariaEndpoint, body: jsonEncode(statusRequest));
|
||||
return true;
|
||||
final response = await http.post(_ariaEndpoint, body: jsonEncode(statusRequest));
|
||||
return response.statusCode == 200;
|
||||
}catch(_) {
|
||||
return false;
|
||||
}
|
||||
@@ -227,11 +290,16 @@ Future<void> _stopAriaDownload(String downloadId) async {
|
||||
]
|
||||
};
|
||||
await http.post(_ariaEndpoint, body: jsonEncode(addDownloadRequest));
|
||||
stopDownloadServer();
|
||||
}catch(error) {
|
||||
throw "Stop failed (${error})";
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stopDownloadServer() async {
|
||||
await killProcessByPort(_ariaPort);
|
||||
}
|
||||
|
||||
|
||||
Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async {
|
||||
Process? process;
|
||||
|
||||
@@ -5,14 +5,19 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
bool _watcher = false;
|
||||
final File rebootBeforeS20DllFile = File("${dllsDirectory.path}\\reboot.dll");
|
||||
final File rebootAboveS20DllFile = File("${dllsDirectory.path}\\rebootS20.dll");
|
||||
|
||||
const String kRebootBelowS20DownloadUrl =
|
||||
"https://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/Reboot.zip";
|
||||
const String kRebootAboveS20DownloadUrl =
|
||||
"https://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/RebootS20.zip";
|
||||
|
||||
const String _kRebootBelowS20FallbackDownloadUrl =
|
||||
"https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/RebootFallback.zip";
|
||||
const String _kRebootAboveS20FallbackDownloadUrl =
|
||||
"https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/RebootS20Fallback.zip";
|
||||
|
||||
Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force = false}) async {
|
||||
final lastUpdate = await _getLastUpdate(lastUpdateMs);
|
||||
final exists = await rebootBeforeS20DllFile.exists() && await rebootAboveS20DllFile.exists();
|
||||
@@ -20,7 +25,22 @@ Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force =
|
||||
return force || !exists || (hours > 0 && lastUpdate != null && now.difference(lastUpdate).inHours > hours);
|
||||
}
|
||||
|
||||
Future<void> downloadCriticalDll(String name, String outputPath) async {
|
||||
Future<void> downloadDependency(InjectableDll dll, String outputPath) async {
|
||||
String? name;
|
||||
switch(dll) {
|
||||
case InjectableDll.console:
|
||||
name = "console.dll";
|
||||
case InjectableDll.auth:
|
||||
name = "cobalt.dll";
|
||||
case InjectableDll.memoryLeak:
|
||||
name = "memory.dll";
|
||||
case InjectableDll.gameServer:
|
||||
name = null;
|
||||
}
|
||||
if(name == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name"));
|
||||
if(response.statusCode != 200) {
|
||||
throw Exception("Cannot download $name: status code ${response.statusCode}");
|
||||
@@ -31,12 +51,15 @@ Future<void> downloadCriticalDll(String name, String outputPath) async {
|
||||
await output.writeAsBytes(response.bodyBytes, flush: true);
|
||||
}
|
||||
|
||||
Future<void> downloadRebootDll(File file, String url) async {
|
||||
Future<void> downloadRebootDll(File file, String url, bool aboveS20) async {
|
||||
Directory? outputDir;
|
||||
try {
|
||||
final response = await http.get(Uri.parse(url));
|
||||
var response = await http.get(Uri.parse(url));
|
||||
if(response.statusCode != 200) {
|
||||
throw Exception("Cannot download reboot.zip: status code ${response.statusCode}");
|
||||
response = await http.get(Uri.parse(aboveS20 ? _kRebootAboveS20FallbackDownloadUrl : _kRebootBelowS20FallbackDownloadUrl));
|
||||
if(response.statusCode != 200) {
|
||||
throw Exception("status code ${response.statusCode}");
|
||||
}
|
||||
}
|
||||
|
||||
outputDir = await installationDirectory.createTemp("reboot_out");
|
||||
@@ -57,16 +80,3 @@ Future<DateTime?> _getLastUpdate(int? lastUpdateMs) async {
|
||||
? DateTime.fromMillisecondsSinceEpoch(lastUpdateMs)
|
||||
: null;
|
||||
}
|
||||
|
||||
Stream<String> watchDlls() async* {
|
||||
if(_watcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
_watcher = true;
|
||||
await for(final event in dllsDirectory.watch(events: FileSystemEvent.delete | FileSystemEvent.move)) {
|
||||
if (event.path.endsWith(".dll")) {
|
||||
yield event.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,20 +168,6 @@ bool resume(int pid) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> watchProcess(int pid) => Isolate.run(() {
|
||||
final processHandle = OpenProcess(FILE_ACCESS_RIGHTS.SYNCHRONIZE, FALSE, pid);
|
||||
if (processHandle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
WaitForSingleObject(processHandle, INFINITE);
|
||||
}finally {
|
||||
CloseHandle(processHandle);
|
||||
}
|
||||
});
|
||||
|
||||
List<String> createRebootArgs(String username, String password, bool host, GameServerType hostType, bool logging, String additionalArgs) {
|
||||
log("[PROCESS] Generating reboot args");
|
||||
if(password.isEmpty) {
|
||||
@@ -264,17 +250,13 @@ void handleGameOutput({
|
||||
}else if(line.contains(kGameFinishedLine) && host) {
|
||||
log("[FORTNITE_OUTPUT_HANDLER] Detected match end: $line");
|
||||
onMatchEnd();
|
||||
}else if(line.contains(kDisplayInitializedLine) && host) {
|
||||
}else if(line.contains(kDisplayLine) && line.contains(kDisplayInitializedLine) && host) {
|
||||
log("[FORTNITE_OUTPUT_HANDLER] Detected display attach: $line");
|
||||
onDisplayAttached();
|
||||
}
|
||||
}
|
||||
|
||||
String _parseUsername(String username, bool host) {
|
||||
if(host) {
|
||||
return "Player${Random().nextInt(1000)}";
|
||||
}
|
||||
|
||||
if (username.isEmpty) {
|
||||
return kDefaultPlayerName;
|
||||
}
|
||||
@@ -296,16 +278,8 @@ final class _ExtendedProcess implements Process {
|
||||
_stdout = attached ? delegate.stdout.asBroadcastStream() : null,
|
||||
_stderr = attached ? delegate.stderr.asBroadcastStream() : null;
|
||||
|
||||
|
||||
@override
|
||||
Future<int> get exitCode {
|
||||
try {
|
||||
return _delegate.exitCode;
|
||||
}catch(_) {
|
||||
return watchProcess(_delegate.pid)
|
||||
.then((_) => -1);
|
||||
}
|
||||
}
|
||||
Future<int> get exitCode => _delegate.exitCode;
|
||||
|
||||
@override
|
||||
bool kill([ProcessSignal signal = ProcessSignal.sigterm]) => _delegate.kill(signal);
|
||||
|
||||
@@ -1,16 +1,39 @@
|
||||
# reboot_launcher
|
||||
|
||||
Launcher for project reboot
|
||||
# Reboot Launcher
|
||||
|
||||
Welcome to the **Reboot Launcher**!
|
||||
This is a GUI application developed as part of the **Reboot Project**.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
### Running the Project
|
||||
To launch the project in development mode, simply run:
|
||||
```
|
||||
flutter run
|
||||
```
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
### Building the Project
|
||||
To create a production-ready build, use:
|
||||
```
|
||||
flutter build
|
||||
```
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
### Packaging the Project
|
||||
To package the application for distribution, run:
|
||||
```
|
||||
package.bat
|
||||
```
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
## Requirements
|
||||
- [Flutter SDK](https://flutter.dev/docs/get-started/install)
|
||||
- Supported operating systems: Windows
|
||||
|
||||
## Other platforms
|
||||
|
||||
Native support for these platforms is not currently planned, but Linux support is a priority for the 10.0 release cycle
|
||||
|
||||
- [Linux Tutorial using Proton](https://www.reddit.com/r/linux_gaming/comments/1fwa4l8/guide_running_a_fortnite_private_server_to_play/)
|
||||
- No tutorials are available for MacOS(got lost when the Reboot discord was banned), but it's possible to run Reboot using a compatibility layer
|
||||
|
||||
## Contributing
|
||||
Contributions are welcome! Feel free to open an issue or submit a pull request.
|
||||
BIN
gui/dependencies/dlls/RebootFallback.zip
Normal file
BIN
gui/dependencies/dlls/RebootFallback.zip
Normal file
Binary file not shown.
BIN
gui/dependencies/dlls/RebootS20Fallback.zip
Normal file
BIN
gui/dependencies/dlls/RebootS20Fallback.zip
Normal file
Binary file not shown.
BIN
gui/dependencies/dlls/cobalt.dll
Normal file
BIN
gui/dependencies/dlls/cobalt.dll
Normal file
Binary file not shown.
BIN
gui/dependencies/dlls/memory.dll
Normal file
BIN
gui/dependencies/dlls/memory.dll
Normal file
Binary file not shown.
@@ -128,7 +128,7 @@
|
||||
"importVersionDescription": "Import a new version of Fortnite into the launcher",
|
||||
"addLocalBuildName": "Add a version from this PC's local storage",
|
||||
"addLocalBuildDescription": "Versions coming from your local disk are not guaranteed to work",
|
||||
"addVersion": "Add version",
|
||||
"addVersion": "New version",
|
||||
"downloadBuildName": "Download any version from the cloud",
|
||||
"downloadBuildDescription": "Download any Fortnite build easily from the cloud",
|
||||
"downloadBuildContent": "Download build",
|
||||
@@ -216,7 +216,6 @@
|
||||
"downloadedVersion": "The download was completed successfully!",
|
||||
"download": "Download",
|
||||
"downloading": "Downloading...",
|
||||
"allocatingSpace": "Allocating disk space...",
|
||||
"startingDownload": "Starting download...",
|
||||
"extracting": "Extracting...",
|
||||
"buildProgress": "{progress}%",
|
||||
@@ -237,7 +236,7 @@
|
||||
"startGame": "Start fortnite",
|
||||
"stopGame": "Close fortnite",
|
||||
"waitingForGameServer": "Waiting for the game server to boot up...",
|
||||
"gameServerStartWarning": "The game server was started successfully, but Reboot didn't load",
|
||||
"gameServerStartWarning": "Unsupported version: the game server crashed while setting up the server",
|
||||
"gameServerStartLocalWarning": "The game server was started successfully, but other players can't join",
|
||||
"gameServerStarted": "The game server was started successfully",
|
||||
"gameClientStarted": "The game client was started successfully",
|
||||
|
||||
@@ -15,8 +15,8 @@ import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/error.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/home_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/error.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/home_page.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/util/url_protocol.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
@@ -82,9 +82,7 @@ Future<void> _startApp() async {
|
||||
errors.add(uncaughtError);
|
||||
} finally{
|
||||
log("[APP] Started applications with errors: $errors");
|
||||
runApp(RebootApplication(
|
||||
errors: errors,
|
||||
));
|
||||
runApp(RebootApplication(errors: errors));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,11 +169,12 @@ Future<void> _initWindow() async {
|
||||
}else {
|
||||
await windowManager.setAlignment(Alignment.center);
|
||||
}
|
||||
|
||||
await windowManager.setPreventClose(true);
|
||||
await windowManager.setResizable(true);
|
||||
if(isWin11) {
|
||||
await Window.setEffect(
|
||||
effect: WindowEffect.acrylic,
|
||||
color: Colors.transparent,
|
||||
color: Colors.green,
|
||||
dark: isDarkMode
|
||||
);
|
||||
}
|
||||
@@ -231,7 +230,6 @@ Future<List<Object>> _initStorage() async {
|
||||
errors.add(error);
|
||||
}
|
||||
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -253,7 +251,11 @@ class _RebootApplicationState extends State<RebootApplication> {
|
||||
}
|
||||
|
||||
void _handleErrors(List<Object?> errors) {
|
||||
errors.where((element) => element != null).forEach((element) => onError(element!, null, false));
|
||||
for(final error in errors) {
|
||||
if(error != null) {
|
||||
onError(error, null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/main.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_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/page_type.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/util/cryptography.dart';
|
||||
import 'package:reboot_launcher/src/util/keyboard.dart';
|
||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class BackendController extends GetxController {
|
||||
static const String storageName = "v2_backend_storage";
|
||||
@@ -22,10 +34,9 @@ class BackendController extends GetxController {
|
||||
late final Rx<PhysicalKeyboardKey> consoleKey;
|
||||
late final RxBool started;
|
||||
late final RxBool detached;
|
||||
StreamSubscription? worker;
|
||||
int? embeddedProcessPid;
|
||||
HttpServer? localServer;
|
||||
HttpServer? remoteServer;
|
||||
late final List<InfoBarEntry> _infoBars;
|
||||
StreamSubscription? _worker;
|
||||
ServerImplementation? _implementation;
|
||||
|
||||
BackendController() {
|
||||
_storage = appWithNoStorage ? null : GetStorage(storageName);
|
||||
@@ -35,11 +46,6 @@ class BackendController extends GetxController {
|
||||
host.text = _readHost();
|
||||
port.text = _readPort();
|
||||
_storage?.write("type", value.index);
|
||||
if (!started.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
stop();
|
||||
});
|
||||
host = TextEditingController(text: _readHost());
|
||||
host.addListener(() =>
|
||||
@@ -70,31 +76,30 @@ class BackendController extends GetxController {
|
||||
}
|
||||
});
|
||||
gameServerAddressFocusNode = FocusNode();
|
||||
consoleKey = Rx(_readConsoleKey());
|
||||
consoleKey = Rx(() {
|
||||
final consoleKeyValue = _storage?.read("console_key");
|
||||
if(consoleKeyValue == null) {
|
||||
return _kDefaultConsoleKey;
|
||||
}
|
||||
|
||||
final consoleKeyNumber = int.tryParse(consoleKeyValue.toString());
|
||||
if(consoleKeyNumber == null) {
|
||||
return _kDefaultConsoleKey;
|
||||
}
|
||||
|
||||
final consoleKey = PhysicalKeyboardKey(consoleKeyNumber);
|
||||
if(!consoleKey.isUnrealEngineKey) {
|
||||
return _kDefaultConsoleKey;
|
||||
}
|
||||
|
||||
return consoleKey;
|
||||
}());
|
||||
_writeConsoleKey(consoleKey.value);
|
||||
consoleKey.listen((newValue) {
|
||||
_storage?.write("console_key", newValue.usbHidUsage);
|
||||
_writeConsoleKey(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
PhysicalKeyboardKey _readConsoleKey() {
|
||||
final consoleKeyValue = _storage?.read("console_key");
|
||||
if(consoleKeyValue == null) {
|
||||
return _kDefaultConsoleKey;
|
||||
}
|
||||
|
||||
final consoleKeyNumber = int.tryParse(consoleKeyValue.toString());
|
||||
if(consoleKeyNumber == null) {
|
||||
return _kDefaultConsoleKey;
|
||||
}
|
||||
|
||||
final consoleKey = PhysicalKeyboardKey(consoleKeyNumber);
|
||||
if(!consoleKey.isUnrealEngineKey) {
|
||||
return _kDefaultConsoleKey;
|
||||
}
|
||||
|
||||
return consoleKey;
|
||||
_infoBars = [];
|
||||
}
|
||||
|
||||
Future<void> _writeConsoleKey(PhysicalKeyboardKey keyValue) async {
|
||||
@@ -103,6 +108,21 @@ class BackendController extends GetxController {
|
||||
await defaultInput.writeAsString("[/Script/Engine.InputSettings]\n+ConsoleKeys=Tilde\n+ConsoleKeys=${keyValue.unrealEngineName}", flush: true);
|
||||
}
|
||||
|
||||
String _readHost() {
|
||||
String? value = _storage?.read("${type.value.name}_host");
|
||||
if (value != null && value.isNotEmpty) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (type.value != ServerType.remote) {
|
||||
return kDefaultBackendHost;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
String _readPort() => _storage?.read("${type.value.name}_port") ?? kDefaultBackendPort.toString();
|
||||
|
||||
void joinLocalhost() {
|
||||
gameServerAddress.text = kDefaultGameServerHost;
|
||||
}
|
||||
@@ -121,179 +141,407 @@ class BackendController extends GetxController {
|
||||
detached.value = false;
|
||||
}
|
||||
|
||||
String _readHost() {
|
||||
String? value = _storage?.read("${type.value.name}_host");
|
||||
if (value != null && value.isNotEmpty) {
|
||||
return value;
|
||||
Future<bool> toggle() {
|
||||
if(started.value) {
|
||||
return stop(interactive: true);
|
||||
}else {
|
||||
return start(interactive: true);
|
||||
}
|
||||
|
||||
if (type.value != ServerType.remote) {
|
||||
return kDefaultBackendHost;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
String _readPort() =>
|
||||
_storage?.read("${type.value.name}_port") ?? kDefaultBackendPort.toString();
|
||||
Future<bool> start({required bool interactive}) async {
|
||||
if(started.value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Stream<ServerResult> start({required void Function() onExit, required void Function(String) onError}) async* {
|
||||
try {
|
||||
if(started.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
final serverType = type.value;
|
||||
final hostData = this.host.text.trim();
|
||||
final portData = this.port.text.trim();
|
||||
started.value = true;
|
||||
if(serverType != ServerType.local || portData != kDefaultBackendPort.toString()) {
|
||||
yield ServerResult(ServerResultType.starting);
|
||||
}
|
||||
|
||||
if (hostData.isEmpty) {
|
||||
yield ServerResult(ServerResultType.missingHostError);
|
||||
started.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (portData.isEmpty) {
|
||||
yield ServerResult(ServerResultType.missingPortError);
|
||||
started.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
final portNumber = int.tryParse(portData);
|
||||
if (portNumber == null) {
|
||||
yield ServerResult(ServerResultType.illegalPortError);
|
||||
started.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((serverType != ServerType.local || portData != kDefaultBackendPort.toString()) && !(await isBackendPortFree())) {
|
||||
yield ServerResult(ServerResultType.freeingPort);
|
||||
final result = await freeBackendPort();
|
||||
yield ServerResult(result ? ServerResultType.freePortSuccess : ServerResultType.freePortError);
|
||||
if(!result) {
|
||||
started.value = false;
|
||||
return;
|
||||
_cancel();
|
||||
final stream = startBackend(
|
||||
type: type.value,
|
||||
host: host.text,
|
||||
port: port.text,
|
||||
detached: detached.value,
|
||||
onError: (errorMessage) {
|
||||
stop(interactive: false);
|
||||
_showRebootInfoBar(
|
||||
translations.backendErrorMessage,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrl(launcherLogFile.uri),
|
||||
child: Text(translations.openLog),
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
final completer = Completer<bool>();
|
||||
InfoBarEntry? entry;
|
||||
_worker = stream.listen((event) {
|
||||
entry?.close();
|
||||
entry = _handeEvent(event, interactive);
|
||||
if(event.type.isError) {
|
||||
completer.complete(false);
|
||||
}else if(event.type.isSuccess) {
|
||||
completer.complete(true);
|
||||
}
|
||||
});
|
||||
return await completer.future;
|
||||
}
|
||||
|
||||
switch(serverType){
|
||||
case ServerType.embedded:
|
||||
final process = await startEmbeddedBackend(detached.value, onError: (errorMessage) {
|
||||
if(started.value) {
|
||||
started.value = false;
|
||||
onError(errorMessage);
|
||||
}
|
||||
});
|
||||
watchProcess(process.pid).then((_) {
|
||||
if(started.value) {
|
||||
started.value = false;
|
||||
onExit();
|
||||
}
|
||||
});
|
||||
embeddedProcessPid = process.pid;
|
||||
break;
|
||||
case ServerType.remote:
|
||||
yield ServerResult(ServerResultType.pingingRemote);
|
||||
final uriResult = await pingBackend(hostData, portNumber);
|
||||
if(uriResult == null) {
|
||||
yield ServerResult(ServerResultType.pingError);
|
||||
started.value = false;
|
||||
return;
|
||||
}
|
||||
Future<bool> stop({required bool interactive}) async {
|
||||
if(!started.value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
remoteServer = await startRemoteBackendProxy(uriResult);
|
||||
break;
|
||||
case ServerType.local:
|
||||
if(portNumber != kDefaultBackendPort) {
|
||||
yield ServerResult(ServerResultType.pingingLocal);
|
||||
final uriResult = await pingBackend(kDefaultBackendHost, portNumber);
|
||||
if(uriResult == null) {
|
||||
yield ServerResult(ServerResultType.pingError);
|
||||
started.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
localServer = await startRemoteBackendProxy(Uri.parse("http://$kDefaultBackendHost:$portData"));
|
||||
}else {
|
||||
// If the local server is running on port 3551 there is no reverse proxy running
|
||||
// We only need to check if everything is working
|
||||
started.value = false;
|
||||
}
|
||||
|
||||
break;
|
||||
_cancel();
|
||||
final stream = stopBackend(
|
||||
type: type.value,
|
||||
implementation: _implementation
|
||||
);
|
||||
final completer = Completer<bool>();
|
||||
InfoBarEntry? entry;
|
||||
_worker = stream.listen((event) {
|
||||
entry?.close();
|
||||
entry = _handeEvent(event, interactive);
|
||||
if(event.type.isError) {
|
||||
completer.complete(false);
|
||||
}else if(event.type.isSuccess) {
|
||||
completer.complete(true);
|
||||
}
|
||||
});
|
||||
return await completer.future;
|
||||
}
|
||||
|
||||
yield ServerResult(ServerResultType.pingingLocal);
|
||||
final uriResult = await pingBackend(kDefaultBackendHost, kDefaultBackendPort);
|
||||
if(uriResult == null) {
|
||||
yield ServerResult(ServerResultType.pingError);
|
||||
remoteServer?.close(force: true);
|
||||
localServer?.close(force: true);
|
||||
started.value = false;
|
||||
return;
|
||||
}
|
||||
void _cancel() {
|
||||
_worker?.cancel(); // Do not await or it will hang
|
||||
_infoBars.forEach((infoBar) => infoBar.close());
|
||||
_infoBars.clear();
|
||||
}
|
||||
|
||||
yield ServerResult(ServerResultType.startSuccess);
|
||||
}catch(error, stackTrace) {
|
||||
yield ServerResult(
|
||||
ServerResultType.startError,
|
||||
error: error,
|
||||
stackTrace: stackTrace
|
||||
);
|
||||
remoteServer?.close(force: true);
|
||||
localServer?.close(force: true);
|
||||
started.value = false;
|
||||
InfoBarEntry? _handeEvent(ServerResult event, bool interactive) {
|
||||
log("[BACKEND] Handling event: $event (interactive: $interactive, start: ${event.type.isStart}, error: ${event.type.isError})");
|
||||
started.value = event.type.isStart && !event.type.isError;
|
||||
switch (event.type) {
|
||||
case ServerResultType.starting:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.startingServer,
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startSuccess:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
type.value == ServerType.local ? translations.checkedServer : translations.startedServer,
|
||||
severity: InfoBarSeverity.success
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startError:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
type.value == ServerType.local ? translations.localServerError(event.error ?? translations.unknownError) : translations.startServerError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.stopping:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.stoppingServer,
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.stopSuccess:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.stoppedServer,
|
||||
severity: InfoBarSeverity.success
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.stopError:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.stopServerError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startMissingHostError:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.missingHostNameError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startMissingPortError:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.missingPortError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startIllegalPortError:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.illegalPortError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startFreeingPort:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.freeingPort,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startFreePortSuccess:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.freedPort,
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: infoBarShortDuration
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startFreePortError:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.freePortError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startPingingRemote:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.pingingServer(ServerType.remote.name),
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startPingingLocal:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.pingingServer(type.value.name),
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startPingError:
|
||||
if(interactive) {
|
||||
return _showRebootInfoBar(
|
||||
translations.pingError(type.value.name),
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
case ServerResultType.startedImplementation:
|
||||
_implementation = event.implementation;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Stream<ServerResult> stop() async* {
|
||||
if(!started.value) {
|
||||
Future<void> joinServer(String uuid, FortniteServer server) async {
|
||||
if(!kDebugMode && uuid == server.id) {
|
||||
_showRebootInfoBar(
|
||||
translations.joinSelfServer,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
yield ServerResult(ServerResultType.stopping);
|
||||
started.value = false;
|
||||
try{
|
||||
switch(type()){
|
||||
case ServerType.embedded:
|
||||
final embeddedProcessPid = this.embeddedProcessPid;
|
||||
if(embeddedProcessPid != null) {
|
||||
Process.killPid(embeddedProcessPid, ProcessSignal.sigterm);
|
||||
this.embeddedProcessPid = null;
|
||||
}
|
||||
break;
|
||||
case ServerType.remote:
|
||||
await remoteServer?.close(force: true);
|
||||
remoteServer = null;
|
||||
break;
|
||||
case ServerType.local:
|
||||
await localServer?.close(force: true);
|
||||
localServer = null;
|
||||
break;
|
||||
}
|
||||
yield ServerResult(ServerResultType.stopSuccess);
|
||||
}catch(error, stackTrace){
|
||||
yield ServerResult(
|
||||
ServerResultType.stopError,
|
||||
error: error,
|
||||
stackTrace: stackTrace
|
||||
final version = Get.find<GameController>()
|
||||
.getVersionByName(server.version.toString());
|
||||
if(version == null) {
|
||||
_showRebootInfoBar(
|
||||
translations.cannotJoinServerVersion(server.version.toString()),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
started.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
final hashedPassword = server.password;
|
||||
final hasPassword = hashedPassword != null;
|
||||
final embedded = type.value == ServerType.embedded;
|
||||
final author = server.author;
|
||||
final encryptedIp = server.ip;
|
||||
if(!hasPassword) {
|
||||
final valid = await _isServerValid(encryptedIp);
|
||||
if(!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
_onServerJoined(embedded, encryptedIp, author, version);
|
||||
return;
|
||||
}
|
||||
|
||||
final confirmPassword = await _askForPassword();
|
||||
if(confirmPassword == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!checkPassword(confirmPassword, hashedPassword)) {
|
||||
_showRebootInfoBar(
|
||||
translations.wrongServerPassword,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final decryptedIp = aes256Decrypt(encryptedIp, confirmPassword);
|
||||
final valid = await _isServerValid(decryptedIp);
|
||||
if(!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
_onServerJoined(embedded, decryptedIp, author, version);
|
||||
}
|
||||
|
||||
Stream<ServerResult> toggle({required void Function() onExit, required void Function(String) onError}) async* {
|
||||
if(started()) {
|
||||
yield* stop();
|
||||
Future<bool> _isServerValid(String address) async {
|
||||
final result = await pingGameServer(address);
|
||||
if(result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_showRebootInfoBar(
|
||||
translations.offlineServer,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<String?> _askForPassword() async {
|
||||
final confirmPasswordController = TextEditingController();
|
||||
final showPassword = RxBool(false);
|
||||
final showPasswordTrailing = RxBool(false);
|
||||
return await showRebootDialog<String?>(
|
||||
builder: (context) => FormDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InfoLabel(
|
||||
label: translations.serverPassword,
|
||||
child: Obx(() => TextFormBox(
|
||||
placeholder: translations.serverPasswordPlaceholder,
|
||||
controller: confirmPasswordController,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
obscureText: !showPassword.value,
|
||||
enableSuggestions: false,
|
||||
autofocus: true,
|
||||
autocorrect: false,
|
||||
onChanged: (text) => showPasswordTrailing.value = text.isNotEmpty,
|
||||
suffix: !showPasswordTrailing.value ? null : Button(
|
||||
onPressed: () => showPassword.value = !showPassword.value,
|
||||
style: ButtonStyle(
|
||||
shape: WidgetStateProperty.all(const CircleBorder()),
|
||||
backgroundColor: WidgetStateProperty.all(Colors.transparent)
|
||||
),
|
||||
child: Icon(
|
||||
showPassword.value ? FluentIcons.eye_off_24_regular : FluentIcons.eye_24_regular
|
||||
),
|
||||
)
|
||||
))
|
||||
),
|
||||
const SizedBox(height: 8.0)
|
||||
],
|
||||
),
|
||||
buttons: [
|
||||
DialogButton(
|
||||
text: translations.serverPasswordCancel,
|
||||
type: ButtonType.secondary
|
||||
),
|
||||
|
||||
DialogButton(
|
||||
text: translations.serverPasswordConfirm,
|
||||
type: ButtonType.primary,
|
||||
onTap: () => Navigator.of(context).pop(confirmPasswordController.text)
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void _onServerJoined(bool embedded, String decryptedIp, String author, FortniteVersion version) {
|
||||
if(embedded) {
|
||||
gameServerAddress.text = decryptedIp;
|
||||
pageIndex.value = RebootPageType.play.index;
|
||||
}else {
|
||||
yield* start(
|
||||
onExit: onExit,
|
||||
onError: onError
|
||||
);
|
||||
FlutterClipboard.controlC(decryptedIp);
|
||||
}
|
||||
Get.find<GameController>()
|
||||
.selectedVersion = version;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _showRebootInfoBar(
|
||||
embedded ? translations.joinedServer(author) : translations.copiedIp,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.success
|
||||
));
|
||||
}
|
||||
|
||||
InfoBarEntry _showRebootInfoBar(dynamic text, {
|
||||
InfoBarSeverity severity = InfoBarSeverity.info,
|
||||
bool loading = false,
|
||||
Duration? duration = infoBarShortDuration,
|
||||
void Function()? onDismissed,
|
||||
Widget? action
|
||||
}) {
|
||||
final result = showRebootInfoBar(
|
||||
text,
|
||||
severity: severity,
|
||||
loading: loading,
|
||||
duration: duration,
|
||||
onDismissed: onDismissed,
|
||||
action: action
|
||||
);
|
||||
if(severity == InfoBarSeverity.info || severity == InfoBarSeverity.success) {
|
||||
_infoBars.add(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<void> restart() async {
|
||||
if(started.value) {
|
||||
await stop(interactive: false);
|
||||
await start(interactive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,19 @@ import 'package:get_storage/get_storage.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/main.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:version/version.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class DllController extends GetxController {
|
||||
static const String storageName = "v2_dll_storage";
|
||||
|
||||
late final GetStorage? _storage;
|
||||
late final String originalDll;
|
||||
late final TextEditingController gameServerDll;
|
||||
late final TextEditingController customGameServerDll;
|
||||
late final TextEditingController unrealEngineConsoleDll;
|
||||
late final TextEditingController backendDll;
|
||||
late final TextEditingController memoryLeakDll;
|
||||
late final TextEditingController gameServerPort;
|
||||
late final Rx<UpdateTimer> timer;
|
||||
late final TextEditingController beforeS20Mirror;
|
||||
@@ -26,14 +27,13 @@ class DllController extends GetxController {
|
||||
late final RxBool customGameServer;
|
||||
late final RxnInt timestamp;
|
||||
late final Rx<UpdateStatus> status;
|
||||
InfoBarEntry? infoBarEntry;
|
||||
Future<bool>? _updater;
|
||||
|
||||
DllController() {
|
||||
_storage = appWithNoStorage ? null : GetStorage(storageName);
|
||||
gameServerDll = _createController("game_server", InjectableDll.reboot);
|
||||
customGameServerDll = _createController("game_server", InjectableDll.gameServer);
|
||||
unrealEngineConsoleDll = _createController("unreal_engine_console", InjectableDll.console);
|
||||
backendDll = _createController("backend", InjectableDll.starfall);
|
||||
backendDll = _createController("backend", InjectableDll.auth);
|
||||
memoryLeakDll = _createController("memory_leak", InjectableDll.memoryLeak);
|
||||
gameServerPort = TextEditingController(text: _storage?.read("game_server_port") ?? kDefaultGameServerPort);
|
||||
gameServerPort.addListener(() => _storage?.write("game_server_port", gameServerPort.text));
|
||||
final timerIndex = _storage?.read("timer");
|
||||
@@ -57,9 +57,9 @@ class DllController extends GetxController {
|
||||
}
|
||||
|
||||
void resetGame() {
|
||||
gameServerDll.text = getDefaultDllPath(InjectableDll.reboot);
|
||||
customGameServerDll.text = getDefaultDllPath(InjectableDll.gameServer);
|
||||
unrealEngineConsoleDll.text = getDefaultDllPath(InjectableDll.console);
|
||||
backendDll.text = getDefaultDllPath(InjectableDll.starfall);
|
||||
backendDll.text = getDefaultDllPath(InjectableDll.auth);
|
||||
}
|
||||
|
||||
void resetServer() {
|
||||
@@ -74,16 +74,7 @@ class DllController extends GetxController {
|
||||
}
|
||||
|
||||
Future<bool> updateGameServerDll({bool force = false, bool silent = false}) async {
|
||||
if(_updater != null) {
|
||||
return await _updater!;
|
||||
}
|
||||
|
||||
final result = _updateGameServerDll(force, silent);
|
||||
_updater = result;
|
||||
return await result;
|
||||
}
|
||||
|
||||
Future<bool> _updateGameServerDll(bool force, bool silent) async {
|
||||
InfoBarEntry? infoBarEntry;
|
||||
try {
|
||||
if(customGameServer.value) {
|
||||
status.value = UpdateStatus.success;
|
||||
@@ -109,8 +100,8 @@ class DllController extends GetxController {
|
||||
}
|
||||
await Future.wait(
|
||||
[
|
||||
downloadRebootDll(rebootBeforeS20DllFile, beforeS20Mirror.text),
|
||||
downloadRebootDll(rebootAboveS20DllFile, aboveS20Mirror.text),
|
||||
downloadRebootDll(rebootBeforeS20DllFile, beforeS20Mirror.text, false),
|
||||
downloadRebootDll(rebootAboveS20DllFile, aboveS20Mirror.text, true),
|
||||
Future.delayed(const Duration(seconds: 1))
|
||||
],
|
||||
eagerError: false
|
||||
@@ -148,42 +139,62 @@ class DllController extends GetxController {
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}finally {
|
||||
_updater = null;
|
||||
}
|
||||
}
|
||||
|
||||
(File, bool) getInjectableData(Version version, InjectableDll dll) {
|
||||
final defaultPath = canonicalize(getDefaultDllPath(dll));
|
||||
switch(dll){
|
||||
case InjectableDll.reboot:
|
||||
case InjectableDll.gameServer:
|
||||
if(customGameServer.value) {
|
||||
return (File(gameServerDll.text), true);
|
||||
return (File(customGameServerDll.text), true);
|
||||
}
|
||||
|
||||
return (version.major >= 20 ? rebootAboveS20DllFile : rebootBeforeS20DllFile, false);
|
||||
case InjectableDll.console:
|
||||
final ue4ConsoleFile = File(unrealEngineConsoleDll.text);
|
||||
return (ue4ConsoleFile, canonicalize(ue4ConsoleFile.path) != defaultPath);
|
||||
case InjectableDll.starfall:
|
||||
case InjectableDll.auth:
|
||||
final backendFile = File(backendDll.text);
|
||||
return (backendFile, canonicalize(backendFile.path) != defaultPath);
|
||||
case InjectableDll.memoryLeak:
|
||||
final memoryFile = File(memoryLeakDll.text);
|
||||
return (memoryFile, canonicalize(memoryFile.path) != defaultPath);
|
||||
}
|
||||
}
|
||||
|
||||
String getDefaultDllPath(InjectableDll dll) => "${dllsDirectory.path}\\${dll.name}.dll";
|
||||
TextEditingController getDllEditingController(InjectableDll dll) {
|
||||
switch(dll) {
|
||||
case InjectableDll.console:
|
||||
return unrealEngineConsoleDll;
|
||||
case InjectableDll.auth:
|
||||
return backendDll;
|
||||
case InjectableDll.gameServer:
|
||||
return customGameServerDll;
|
||||
case InjectableDll.memoryLeak:
|
||||
return memoryLeakDll;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> downloadCriticalDllInteractive(String filePath, {bool silent = false, bool force = false}) async {
|
||||
log("[DLL] Asking for $filePath(silent: $silent)");
|
||||
final fileName = basename(filePath).toLowerCase();
|
||||
log("[DLL] File name: $fileName");
|
||||
String getDefaultDllPath(InjectableDll dll) {
|
||||
switch(dll) {
|
||||
case InjectableDll.console:
|
||||
return "${dllsDirectory.path}\\console.dll";
|
||||
case InjectableDll.auth:
|
||||
return "${dllsDirectory.path}\\cobalt.dll";
|
||||
case InjectableDll.gameServer:
|
||||
return "${dllsDirectory.path}\\reboot.dll";
|
||||
case InjectableDll.memoryLeak:
|
||||
return "${dllsDirectory.path}\\memory.dll";
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> download(InjectableDll dll, String filePath, {bool silent = false, bool force = false}) async {
|
||||
log("[DLL] Asking for $dll at $filePath(silent: $silent, force: $force)");
|
||||
InfoBarEntry? entry;
|
||||
try {
|
||||
if (fileName.contains("reboot")) {
|
||||
log("[DLL] Downloading reboot.dll...");
|
||||
return await updateGameServerDll(
|
||||
silent: silent
|
||||
);
|
||||
if (dll == InjectableDll.gameServer) {
|
||||
return await updateGameServerDll(silent: silent);
|
||||
}
|
||||
|
||||
if(!force && File(filePath).existsSync()) {
|
||||
@@ -199,7 +210,7 @@ class DllController extends GetxController {
|
||||
duration: null
|
||||
);
|
||||
}
|
||||
await downloadCriticalDll(fileName, filePath);
|
||||
await downloadDependency(dll, filePath);
|
||||
entry?.close();
|
||||
if(!silent) {
|
||||
entry = await showRebootInfoBar(
|
||||
@@ -218,13 +229,13 @@ class DllController extends GetxController {
|
||||
error = error.toLowerCase();
|
||||
final completer = Completer();
|
||||
await showRebootInfoBar(
|
||||
translations.downloadDllError(error.toString(), fileName),
|
||||
translations.downloadDllError(error.toString(), dll.name),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error,
|
||||
onDismissed: () => completer.complete(null),
|
||||
action: Button(
|
||||
onPressed: () async {
|
||||
await downloadCriticalDllInteractive(filePath);
|
||||
await download(dll, filePath, silent: silent, force: force);
|
||||
completer.complete(null);
|
||||
},
|
||||
child: Text(translations.downloadDllRetry),
|
||||
@@ -234,6 +245,32 @@ class DllController extends GetxController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void guardFiles() {
|
||||
for(final injectable in InjectableDll.values) {
|
||||
final controller = getDllEditingController(injectable);
|
||||
final defaultPath = getDefaultDllPath(injectable);
|
||||
if (path.equals(controller.text, defaultPath)) {
|
||||
download(injectable, controller.text);
|
||||
}
|
||||
controller.addListener(() async {
|
||||
try {
|
||||
if (!path.equals(controller.text, defaultPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final filePath = controller.text;
|
||||
await for(final event in File(filePath).parent.watch(events: FileSystemEvent.delete | FileSystemEvent.move)) {
|
||||
if (path.equals(event.path, filePath)) {
|
||||
await download(injectable, filePath);
|
||||
}
|
||||
}
|
||||
} catch(_) {
|
||||
// Ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _UpdateTimerExtension on UpdateTimer {
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:get_storage/get_storage.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/main.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
@@ -1,323 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/util/cryptography.dart';
|
||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final List<InfoBarEntry> _infoBars = [];
|
||||
|
||||
extension ServerControllerDialog on BackendController {
|
||||
void cancelInteractive() {
|
||||
worker?.cancel(); // Do not await or it will hang
|
||||
_infoBars.forEach((infoBar) => infoBar.close());
|
||||
_infoBars.clear();
|
||||
}
|
||||
|
||||
Future<bool> toggleInteractive() async {
|
||||
cancelInteractive();
|
||||
final stream = toggle(
|
||||
onExit: () {
|
||||
cancelInteractive();
|
||||
_showRebootInfoBar(
|
||||
translations.backendProcessError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
},
|
||||
onError: (errorMessage) {
|
||||
cancelInteractive();
|
||||
_showRebootInfoBar(
|
||||
translations.backendErrorMessage,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
action: Button(
|
||||
onPressed: () => launchUrl(launcherLogFile.uri),
|
||||
child: Text(translations.openLog),
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
final completer = Completer<bool>();
|
||||
InfoBarEntry? entry;
|
||||
worker = stream.listen((event) {
|
||||
entry?.close();
|
||||
entry = _handeEvent(event);
|
||||
if(event.type.isError) {
|
||||
completer.complete(false);
|
||||
}else if(event.type.isSuccess) {
|
||||
completer.complete(true);
|
||||
}
|
||||
});
|
||||
|
||||
return await completer.future;
|
||||
}
|
||||
|
||||
InfoBarEntry _handeEvent(ServerResult event) {
|
||||
log("[BACKEND] Handling event: $event");
|
||||
switch (event.type) {
|
||||
case ServerResultType.starting:
|
||||
return _showRebootInfoBar(
|
||||
translations.startingServer,
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case ServerResultType.startSuccess:
|
||||
return _showRebootInfoBar(
|
||||
type.value == ServerType.local ? translations.checkedServer : translations.startedServer,
|
||||
severity: InfoBarSeverity.success
|
||||
);
|
||||
case ServerResultType.startError:
|
||||
print(event.stackTrace);
|
||||
return _showRebootInfoBar(
|
||||
type.value == ServerType.local ? translations.localServerError(event.error ?? translations.unknownError) : translations.startServerError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
case ServerResultType.stopping:
|
||||
return _showRebootInfoBar(
|
||||
translations.stoppingServer,
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case ServerResultType.stopSuccess:
|
||||
return _showRebootInfoBar(
|
||||
translations.stoppedServer,
|
||||
severity: InfoBarSeverity.success
|
||||
);
|
||||
case ServerResultType.stopError:
|
||||
return _showRebootInfoBar(
|
||||
translations.stopServerError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
case ServerResultType.missingHostError:
|
||||
return _showRebootInfoBar(
|
||||
translations.missingHostNameError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
case ServerResultType.missingPortError:
|
||||
return _showRebootInfoBar(
|
||||
translations.missingPortError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
case ServerResultType.illegalPortError:
|
||||
return _showRebootInfoBar(
|
||||
translations.illegalPortError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
case ServerResultType.freeingPort:
|
||||
return _showRebootInfoBar(
|
||||
translations.freeingPort,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case ServerResultType.freePortSuccess:
|
||||
return _showRebootInfoBar(
|
||||
translations.freedPort,
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: infoBarShortDuration
|
||||
);
|
||||
case ServerResultType.freePortError:
|
||||
return _showRebootInfoBar(
|
||||
translations.freePortError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
case ServerResultType.pingingRemote:
|
||||
return _showRebootInfoBar(
|
||||
translations.pingingServer(ServerType.remote.name),
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case ServerResultType.pingingLocal:
|
||||
return _showRebootInfoBar(
|
||||
translations.pingingServer(type.value.name),
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case ServerResultType.pingError:
|
||||
return _showRebootInfoBar(
|
||||
translations.pingError(type.value.name),
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> joinServerInteractive(String uuid, FortniteServer server) async {
|
||||
if(!kDebugMode && uuid == server.id) {
|
||||
_showRebootInfoBar(
|
||||
translations.joinSelfServer,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final gameController = Get.find<GameController>();
|
||||
final version = gameController.getVersionByName(server.version.toString());
|
||||
if(version == null) {
|
||||
_showRebootInfoBar(
|
||||
translations.cannotJoinServerVersion(server.version.toString()),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final hashedPassword = server.password;
|
||||
final hasPassword = hashedPassword != null;
|
||||
final embedded = type.value == ServerType.embedded;
|
||||
final author = server.author;
|
||||
final encryptedIp = server.ip;
|
||||
if(!hasPassword) {
|
||||
final valid = await _isServerValid(encryptedIp);
|
||||
if(!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
_onSuccess(gameController, embedded, encryptedIp, author, version);
|
||||
return;
|
||||
}
|
||||
|
||||
final confirmPassword = await _askForPassword();
|
||||
if(confirmPassword == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!checkPassword(confirmPassword, hashedPassword)) {
|
||||
_showRebootInfoBar(
|
||||
translations.wrongServerPassword,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final decryptedIp = aes256Decrypt(encryptedIp, confirmPassword);
|
||||
final valid = await _isServerValid(decryptedIp);
|
||||
if(!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
_onSuccess(gameController, embedded, decryptedIp, author, version);
|
||||
}
|
||||
|
||||
Future<bool> _isServerValid(String address) async {
|
||||
final result = await pingGameServer(address);
|
||||
if(result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_showRebootInfoBar(
|
||||
translations.offlineServer,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<String?> _askForPassword() async {
|
||||
final confirmPasswordController = TextEditingController();
|
||||
final showPassword = RxBool(false);
|
||||
final showPasswordTrailing = RxBool(false);
|
||||
return await showRebootDialog<String?>(
|
||||
builder: (context) => FormDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InfoLabel(
|
||||
label: translations.serverPassword,
|
||||
child: Obx(() => TextFormBox(
|
||||
placeholder: translations.serverPasswordPlaceholder,
|
||||
controller: confirmPasswordController,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
obscureText: !showPassword.value,
|
||||
enableSuggestions: false,
|
||||
autofocus: true,
|
||||
autocorrect: false,
|
||||
onChanged: (text) => showPasswordTrailing.value = text.isNotEmpty,
|
||||
suffix: !showPasswordTrailing.value ? null : Button(
|
||||
onPressed: () => showPassword.value = !showPassword.value,
|
||||
style: ButtonStyle(
|
||||
shape: WidgetStateProperty.all(const CircleBorder()),
|
||||
backgroundColor: WidgetStateProperty.all(Colors.transparent)
|
||||
),
|
||||
child: Icon(
|
||||
showPassword.value ? FluentIcons.eye_off_24_regular : FluentIcons.eye_24_regular
|
||||
),
|
||||
)
|
||||
))
|
||||
),
|
||||
const SizedBox(height: 8.0)
|
||||
],
|
||||
),
|
||||
buttons: [
|
||||
DialogButton(
|
||||
text: translations.serverPasswordCancel,
|
||||
type: ButtonType.secondary
|
||||
),
|
||||
|
||||
DialogButton(
|
||||
text: translations.serverPasswordConfirm,
|
||||
type: ButtonType.primary,
|
||||
onTap: () => Navigator.of(context).pop(confirmPasswordController.text)
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void _onSuccess(GameController controller, bool embedded, String decryptedIp, String author, FortniteVersion version) {
|
||||
if(embedded) {
|
||||
gameServerAddress.text = decryptedIp;
|
||||
pageIndex.value = RebootPageType.play.index;
|
||||
}else {
|
||||
FlutterClipboard.controlC(decryptedIp);
|
||||
}
|
||||
controller.selectedVersion = version;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _showRebootInfoBar(
|
||||
embedded ? translations.joinedServer(author) : translations.copiedIp,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.success
|
||||
));
|
||||
}
|
||||
|
||||
InfoBarEntry _showRebootInfoBar(dynamic text, {
|
||||
InfoBarSeverity severity = InfoBarSeverity.info,
|
||||
bool loading = false,
|
||||
Duration? duration = infoBarShortDuration,
|
||||
void Function()? onDismissed,
|
||||
Widget? action
|
||||
}) {
|
||||
final result = showRebootInfoBar(
|
||||
text,
|
||||
severity: severity,
|
||||
loading: loading,
|
||||
duration: duration,
|
||||
onDismissed: onDismissed,
|
||||
action: action
|
||||
);
|
||||
if(severity == InfoBarSeverity.info || severity == InfoBarSeverity.success) {
|
||||
_infoBars.add(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/home_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/home_page.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
|
||||
typedef WidgetBuilder = Widget Function(BuildContext, void Function());
|
||||
@@ -148,7 +148,7 @@ class _RenderAbsorbPointer extends RenderProxyBox {
|
||||
|
||||
// 32 is the height of the title bar (need this offset as the overlay area doesn't include it)
|
||||
// Not an optimal solution but it works (calculating it is kind of complicated)
|
||||
position = Offset(position.dx, position.dy + HomePage.kTitleBarHeight);
|
||||
position = Offset(position.dx, position.dy);
|
||||
final exclusionPosition = exclusion.localToGlobal(Offset.zero);
|
||||
final exclusionSize = Rect.fromLTRB(
|
||||
exclusionPosition.dx,
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/onboard.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/onboard.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
abstract class RebootPage extends StatefulWidget {
|
||||
@@ -3,16 +3,16 @@ import 'dart:collection';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/backend_page.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/browser_page.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/host_page.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/info_page.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/play_page.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/settings_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/info_bar_area.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/page/page.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/backend_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/browser_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/host_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/info_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/play_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/settings_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/window/info_bar_area.dart';
|
||||
|
||||
final StreamController<void> pagesController = StreamController.broadcast();
|
||||
bool hitBack = false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
@@ -9,22 +10,24 @@ const Duration _timeout = Duration(seconds: 5);
|
||||
Completer<bool> pingGameServerOrTimeout(String address, Duration timeout) {
|
||||
final completer = Completer<bool>();
|
||||
final start = DateTime.now();
|
||||
(() async {
|
||||
while (!completer.isCompleted && DateTime.now().millisecondsSinceEpoch - start.millisecondsSinceEpoch < timeout.inMilliseconds) {
|
||||
final result = await pingGameServer(address);
|
||||
if(result) {
|
||||
completer.complete(true);
|
||||
}else {
|
||||
await Future.delayed(_timeout);
|
||||
}
|
||||
}
|
||||
if(!completer.isCompleted) {
|
||||
completer.complete(false);
|
||||
}
|
||||
})();
|
||||
_pingGameServerOrTimeout(completer, start, timeout, address);
|
||||
return completer;
|
||||
}
|
||||
|
||||
Future<void> _pingGameServerOrTimeout(Completer<bool> completer, DateTime start, Duration timeout, String address) async {
|
||||
while (!completer.isCompleted && max(DateTime.now().millisecondsSinceEpoch - start.millisecondsSinceEpoch, 0) < timeout.inMilliseconds) {
|
||||
final result = await pingGameServer(address);
|
||||
if(result) {
|
||||
completer.complete(true);
|
||||
}else {
|
||||
await Future.delayed(_timeout);
|
||||
}
|
||||
}
|
||||
if(!completer.isCompleted) {
|
||||
completer.complete(false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> pingGameServer(String address) async {
|
||||
final split = address.split(":");
|
||||
var hostname = split[0];
|
||||
|
||||
@@ -492,3 +492,57 @@ int _convertToHString(String string) {
|
||||
extension WindowManagerExtension on WindowManager {
|
||||
Future<void> maximizeOrRestore() async => await windowManager.isMaximized() ? windowManager.restore() : windowManager.maximize();
|
||||
}
|
||||
|
||||
class WindowsDisk {
|
||||
static final String _nullTerminator = String.fromCharCode(0);
|
||||
|
||||
final String path;
|
||||
final int freeBytesAvailable;
|
||||
final int totalNumberOfBytes;
|
||||
|
||||
const WindowsDisk._internal(this.path, this.freeBytesAvailable, this.totalNumberOfBytes);
|
||||
|
||||
static List<WindowsDisk> available() {
|
||||
final buffer = malloc.allocate<Utf16>(MAX_PATH);
|
||||
try {
|
||||
final length = GetLogicalDriveStrings(MAX_PATH, buffer);
|
||||
if (length == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return buffer.toDartString(length: length)
|
||||
.split(_nullTerminator)
|
||||
.where((drive) => drive.length > 1)
|
||||
.map((driveName) {
|
||||
final freeBytesAvailable = calloc<Uint64>();
|
||||
final totalNumberOfBytes = calloc<Uint64>();
|
||||
final totalNumberOfFreeBytes = calloc<Uint64>();
|
||||
try {
|
||||
GetDiskFreeSpaceEx(
|
||||
driveName.toNativeUtf16(),
|
||||
freeBytesAvailable,
|
||||
totalNumberOfBytes,
|
||||
totalNumberOfFreeBytes
|
||||
);
|
||||
return WindowsDisk._internal(
|
||||
driveName,
|
||||
freeBytesAvailable.value,
|
||||
totalNumberOfBytes.value
|
||||
);
|
||||
} finally {
|
||||
calloc.free(freeBytesAvailable);
|
||||
calloc.free(totalNumberOfBytes);
|
||||
calloc.free(totalNumberOfFreeBytes);
|
||||
}
|
||||
})
|
||||
.toList(growable: false);
|
||||
} finally {
|
||||
calloc.free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WindowsDisk{path: $path, freeBytesAvailable: $freeBytesAvailable, totalNumberOfBytes: $totalNumberOfBytes}';
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/file/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||
|
||||
const double _kButtonDimensions = 30;
|
||||
const double _kButtonSpacing = 8;
|
||||
@@ -3,9 +3,9 @@ import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/profile.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/profile.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
|
||||
class ProfileWidget extends StatefulWidget {
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:skeletons/skeletons.dart';
|
||||
|
||||
@@ -80,15 +80,19 @@ class SettingTileState extends State<SettingTile> {
|
||||
)
|
||||
else
|
||||
widget.icon,
|
||||
|
||||
const SizedBox(width: 16.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
widget.title == null ? _skeletonTitle : widget.title!,
|
||||
widget.subtitle == null ? _skeletonSubtitle : widget.subtitle!,
|
||||
],
|
||||
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
widget.title == null ? _skeletonTitle : widget.title!,
|
||||
widget.subtitle == null ? _skeletonSubtitle : widget.subtitle!,
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
|
||||
_trailing
|
||||
],
|
||||
),
|
||||
@@ -12,9 +12,8 @@ import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/server.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/os.dart';
|
||||
@@ -111,7 +110,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
}
|
||||
|
||||
log("[${host ? 'HOST' : 'GAME'}] Checking backend(port: ${_backendController.type.value.name}, type: ${_backendController.type.value.name})...");
|
||||
final backendResult = _backendController.started() || await _backendController.toggleInteractive();
|
||||
final backendResult = _backendController.started() || await _backendController.toggle();
|
||||
if(!backendResult){
|
||||
log("[${host ? 'HOST' : 'GAME'}] Cannot start backend");
|
||||
_onStop(
|
||||
@@ -242,7 +241,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
}else{
|
||||
_gameController.instance.value = instance;
|
||||
}
|
||||
await _injectOrShowError(InjectableDll.starfall, host);
|
||||
await _injectOrShowError(InjectableDll.auth, host);
|
||||
log("[${host ? 'HOST' : 'GAME'}] Finished creating game instance");
|
||||
return instance;
|
||||
}
|
||||
@@ -251,7 +250,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
log("[${host ? 'HOST' : 'GAME'}] Generating instance args...");
|
||||
final gameArgs = createRebootArgs(
|
||||
host ? _hostingController.accountUsername.text : _gameController.username.text,
|
||||
host ? _hostingController.accountPassword.text :_gameController.password.text,
|
||||
host ? _hostingController.accountPassword.text : _gameController.password.text,
|
||||
host,
|
||||
hostType,
|
||||
false,
|
||||
@@ -398,6 +397,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
if(instance != null && !instance.launched) {
|
||||
instance.launched = true;
|
||||
instance.tokenError = false;
|
||||
await _injectOrShowError(InjectableDll.memoryLeak, host);
|
||||
if(!host){
|
||||
await _injectOrShowError(InjectableDll.console, host);
|
||||
_onGameClientInjected();
|
||||
@@ -406,7 +406,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
if(gameServerPort != null) {
|
||||
await killProcessByPort(gameServerPort);
|
||||
}
|
||||
await _injectOrShowError(InjectableDll.reboot, host);
|
||||
await _injectOrShowError(InjectableDll.gameServer, host);
|
||||
_onGameServerInjected();
|
||||
}
|
||||
}
|
||||
@@ -492,9 +492,10 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
|
||||
final pingOperation = pingGameServerOrTimeout(
|
||||
"$publicIp:$gameServerPort",
|
||||
const Duration(days: 365)
|
||||
const Duration(days: 1)
|
||||
);
|
||||
this._pingOperation = pingOperation;
|
||||
_gameServerInfoBar?.close();
|
||||
_gameServerInfoBar = showRebootInfoBar(
|
||||
translations.checkGameServerFixMessage(gameServerPort),
|
||||
action: Button(
|
||||
@@ -508,8 +509,9 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
final result = await pingOperation.future;
|
||||
_gameServerInfoBar?.close();
|
||||
return result;
|
||||
}finally {
|
||||
}catch(_) {
|
||||
_gameServerInfoBar?.close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +526,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
}
|
||||
await _operation?.cancel();
|
||||
_operation = null;
|
||||
_backendController.cancelInteractive();
|
||||
_backendController.stop(interactive: false);
|
||||
}
|
||||
|
||||
host = host ?? widget.host;
|
||||
@@ -627,7 +629,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
);
|
||||
break;
|
||||
case _StopReason.tokenError:
|
||||
_backendController.stop();
|
||||
_backendController.stop(interactive: false);
|
||||
showRebootInfoBar(
|
||||
translations.tokenError(instance == null ? translations.none : instance.injectedDlls.map((element) => element.name).join(", ")),
|
||||
severity: InfoBarSeverity.error,
|
||||
@@ -712,7 +714,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
}
|
||||
|
||||
log("[${host ? 'HOST' : 'GAME'}] Path does not exist, downloading critical dll again...");
|
||||
await _dllController.downloadCriticalDllInteractive(file.path, force: true);
|
||||
await _dllController.download(injectable, file.path, force: true);
|
||||
log("[${host ? 'HOST' : 'GAME'}] Downloaded dll again, retrying check...");
|
||||
return _getDllFileOrStop(version, injectable, host, true);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
Future<void> showResetDialog(Function() onConfirm) => showRebootDialog(
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
Future<void> showDllDeletedDialog(Function() onConfirm) => showRebootDialog(
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
@@ -5,16 +5,16 @@ import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/profile.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/backend_page.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/home_page.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/host_page.dart';
|
||||
import 'package:reboot_launcher/src/page/implementation/play_page.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/profile.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/backend_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/home_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/host_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/play_page.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/version_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/version/version_selector.dart';
|
||||
|
||||
void startOnboarding() {
|
||||
final gameController = Get.find<GameController>();
|
||||
@@ -2,8 +2,7 @@ import 'package:email_validator/email_validator.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/material.dart' show Icons;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
Future<bool> showProfileForm(BuildContext context, TextEditingController username, TextEditingController password) async{
|
||||
@@ -7,11 +7,11 @@ 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';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
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_selector.dart';
|
||||
import 'package:universal_disk_space/universal_disk_space.dart';
|
||||
import 'package:reboot_launcher/src/widget/file/file_selector.dart';
|
||||
import 'package:windows_taskbar/windows_taskbar.dart';
|
||||
|
||||
class AddVersionDialog extends StatefulWidget {
|
||||
@@ -35,20 +35,12 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
||||
final Rxn<double> _progress = Rxn();
|
||||
final RxInt _speed = RxInt(0);
|
||||
|
||||
late DiskSpace _diskSpace;
|
||||
late Future<List<FortniteBuild>> _fetchFuture;
|
||||
late Future _diskFuture;
|
||||
|
||||
SendPort? _downloadPort;
|
||||
Object? _error;
|
||||
StackTrace? _stackTrace;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_fetchFuture = compute(fetchBuilds, null);
|
||||
_diskSpace = DiskSpace();
|
||||
_diskFuture = _diskSpace.scan()
|
||||
.then((_) => _updateFormDefaults());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@@ -62,6 +54,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
||||
void _cancelDownload() {
|
||||
_downloadPort?.send(kStopBuildDownloadSignal);
|
||||
WindowsTaskbar.setProgressMode(TaskbarProgressMode.noProgress);
|
||||
stopDownloadServer();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -70,28 +63,10 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
||||
child: Obx(() {
|
||||
switch(_status.value){
|
||||
case _DownloadStatus.form:
|
||||
return FutureBuilder(
|
||||
future: Future.wait([_fetchFuture, _diskFuture]).then((_) async => await _fetchFuture),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _onDownloadError(snapshot.error, snapshot.stackTrace));
|
||||
}
|
||||
|
||||
final data = snapshot.data;
|
||||
if (data == null) {
|
||||
return ProgressDialog(
|
||||
text: translations.fetchingBuilds,
|
||||
showButton: widget.closable,
|
||||
onStop: () => Navigator.of(context).pop()
|
||||
);
|
||||
}
|
||||
|
||||
return Obx(() => FormDialog(
|
||||
content: _buildFormBody(data),
|
||||
buttons: _formButtons
|
||||
));
|
||||
}
|
||||
);
|
||||
return Obx(() => FormDialog(
|
||||
content: _buildFormBody(downloadableBuilds),
|
||||
buttons: _formButtons
|
||||
));
|
||||
case _DownloadStatus.downloading:
|
||||
case _DownloadStatus.extracting:
|
||||
return GenericDialog(
|
||||
@@ -244,12 +219,12 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
||||
),
|
||||
),
|
||||
|
||||
if(_progress.value != null && !_isAllocatingDiskSpace)
|
||||
if(_progress.value != null)
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
|
||||
if(_progress.value != null && !_isAllocatingDiskSpace)
|
||||
if(_progress.value != null)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -258,7 +233,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
||||
style: FluentTheme.maybeOf(context)?.typography.body,
|
||||
),
|
||||
|
||||
if(timeLeft != null)
|
||||
if(timeLeft != null && timeLeft != -1)
|
||||
Text(
|
||||
translations.timeLeft(timeLeft),
|
||||
style: FluentTheme.maybeOf(context)?.typography.body,
|
||||
@@ -272,7 +247,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ProgressBar(value: _isAllocatingDiskSpace ? null : _progress.value?.toDouble())
|
||||
child: ProgressBar(value: _progress.value?.toDouble())
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
@@ -291,15 +266,9 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
||||
return translations.startingDownload;
|
||||
}
|
||||
|
||||
if (_speed.value == 0) {
|
||||
return translations.allocatingSpace;
|
||||
}
|
||||
|
||||
return translations.downloading;
|
||||
}
|
||||
|
||||
bool get _isAllocatingDiskSpace => _status.value == _DownloadStatus.downloading && _speed.value == 0;
|
||||
|
||||
Widget _buildFormBody(List<FortniteBuild> builds) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -450,16 +419,15 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
||||
_build.value = null;
|
||||
}
|
||||
|
||||
if(_source.value != _BuildSource.local && _diskSpace.disks.isNotEmpty) {
|
||||
await _fetchFuture;
|
||||
final bestDisk = _diskSpace.disks
|
||||
.reduce((first, second) => first.availableSpace > second.availableSpace ? first : second);
|
||||
final disks = WindowsDisk.available();
|
||||
if(_source.value != _BuildSource.local && disks.isNotEmpty) {
|
||||
final bestDisk = disks.reduce((first, second) => first.freeBytesAvailable > second.freeBytesAvailable ? first : second);
|
||||
final build = _build.value;
|
||||
if(build == null){
|
||||
return;
|
||||
}
|
||||
|
||||
final pathText = "${bestDisk.devicePath}\\FortniteBuilds\\${build.version}";
|
||||
final pathText = "${bestDisk.path}FortniteBuilds\\${build.version}";
|
||||
_pathController.text = pathText;
|
||||
_pathController.selection = TextSelection.collapsed(offset: pathText.length);
|
||||
}
|
||||
@@ -5,16 +5,16 @@ import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/data.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/data.dart';
|
||||
import 'package:reboot_launcher/src/page/page.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/keyboard.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/server_start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/server_type_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/server/server_start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/server/server_type_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final GlobalKey<OverlayTargetState> backendTypeOverlayTargetKey = GlobalKey();
|
||||
@@ -162,7 +162,12 @@ class _BackendPageState extends RebootPageState<BackendPage> {
|
||||
key: backendDetachedOverlayTargetKey,
|
||||
child: ToggleSwitch(
|
||||
checked: _backendController.detached(),
|
||||
onChanged: (value) => _backendController.detached.value = value
|
||||
onChanged: (value) async {
|
||||
_backendController.detached.value = value;
|
||||
if(_backendController.started.value) {
|
||||
await _backendController.restart();
|
||||
}
|
||||
}
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
@@ -9,12 +8,11 @@ import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/server.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/page/page.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||
|
||||
class BrowsePage extends RebootPage {
|
||||
const BrowsePage({Key? key}) : super(key: key);
|
||||
@@ -211,10 +209,18 @@ class _BrowsePageState extends RebootPageState<BrowsePage> {
|
||||
icon: Icon(
|
||||
hasPassword ? FluentIcons.lock : FluentIcons.globe
|
||||
),
|
||||
title: Text("${_formatName(entry)} • ${entry.author}"),
|
||||
subtitle: Text("${_formatDescription(entry)} • ${_formatVersion(entry)}"),
|
||||
title: Text(
|
||||
"${_formatName(entry)} • ${entry.author}",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis
|
||||
),
|
||||
subtitle: Text(
|
||||
"${_formatDescription(entry)} • ${_formatVersion(entry)}",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis
|
||||
),
|
||||
content: Button(
|
||||
onPressed: () => _backendController.joinServerInteractive(_hostingController.uuid, entry),
|
||||
onPressed: () => _backendController.joinServer(_hostingController.uuid, entry),
|
||||
child: Text(_backendController.type.value == ServerType.embedded ? translations.joinServer : translations.copyIp),
|
||||
)
|
||||
);
|
||||
@@ -10,22 +10,21 @@ import 'package:get/get.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';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/dll.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/server.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_suggestion.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/dll.dart';
|
||||
import 'package:reboot_launcher/src/page/page.dart';
|
||||
import 'package:reboot_launcher/src/page/page_suggestion.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/info_bar_area.dart';
|
||||
import 'package:reboot_launcher/src/widget/profile_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/title_bar.dart';
|
||||
import 'package:reboot_launcher/src/widget/window/info_bar_area.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/profile_tile.dart';
|
||||
import 'package:version/version.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
@@ -33,7 +32,6 @@ final GlobalKey<OverlayTargetState> profileOverlayKey = GlobalKey();
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
static const double kDefaultPadding = 12.0;
|
||||
static const double kTitleBarHeight = 32;
|
||||
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
|
||||
@@ -43,6 +41,7 @@ class HomePage extends StatefulWidget {
|
||||
|
||||
class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepAliveClientMixin {
|
||||
final BackendController _backendController = Get.find<BackendController>();
|
||||
final GameController _gameController = Get.find<GameController>();
|
||||
final HostingController _hostingController = Get.find<HostingController>();
|
||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||
final DllController _dllController = Get.find<DllController>();
|
||||
@@ -76,6 +75,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
||||
|
||||
lastPage = index;
|
||||
_pageController.jumpToPage(index);
|
||||
pagesController.add(null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
||||
final uuid = uri.host;
|
||||
final server = _hostingController.findServerById(uuid);
|
||||
if(server != null) {
|
||||
_backendController.joinServerInteractive(_hostingController.uuid, server);
|
||||
_backendController.joinServer(_hostingController.uuid, server);
|
||||
}else {
|
||||
showRebootInfoBar(
|
||||
translations.noServerFound,
|
||||
@@ -134,37 +134,50 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
||||
dllsDirectory.createSync(recursive: true);
|
||||
}
|
||||
|
||||
final dummy = Version.parse("1");
|
||||
final dummyS20 = Version.parse("20");
|
||||
for(final injectable in InjectableDll.values) {
|
||||
_downloadDll(dummy, injectable);
|
||||
if(injectable.isVersionDependent) {
|
||||
_downloadDll(dummyS20, injectable);
|
||||
}
|
||||
}
|
||||
|
||||
watchDlls().listen((filePath) => showDllDeletedDialog(() {
|
||||
_dllController.downloadCriticalDllInteractive(filePath);
|
||||
}));
|
||||
}
|
||||
|
||||
void _downloadDll(Version version, InjectableDll injectable) {
|
||||
final (file, custom) = _dllController.getInjectableData(version, injectable);
|
||||
if(!custom) {
|
||||
_dllController.downloadCriticalDllInteractive(
|
||||
file.path,
|
||||
silent: false
|
||||
);
|
||||
}
|
||||
_dllController.guardFiles();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowClose() async {
|
||||
try {
|
||||
await windowManager.hide();
|
||||
}catch(error) {
|
||||
log("[WINDOW] Cannot hide window: $error");
|
||||
}
|
||||
|
||||
try {
|
||||
await _hostingController.discardServer();
|
||||
}catch(error) {
|
||||
log("[HOSTING] Cannot discard server: $error");
|
||||
log("[HOSTING] Cannot discard server on exit: $error");
|
||||
}
|
||||
|
||||
try {
|
||||
if(_backendController.started.value) {
|
||||
await _backendController.toggle();
|
||||
}
|
||||
}catch(error) {
|
||||
log("[BACKEND] Cannot stop backend on exit: $error");
|
||||
}
|
||||
|
||||
try {
|
||||
_gameController.instance.value?.kill();
|
||||
}catch(error) {
|
||||
log("[GAME] Cannot stop game on exit: $error");
|
||||
}
|
||||
|
||||
try {
|
||||
_hostingController.instance.value?.kill();
|
||||
}catch(error) {
|
||||
log("[HOST] Cannot stop host on exit: $error");
|
||||
}
|
||||
|
||||
try {
|
||||
await stopDownloadServer();
|
||||
}catch(error) {
|
||||
log("[ARIA] Cannot stop aria server on exit: $error");
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -252,62 +265,36 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
||||
_focused.value = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowEvent(String eventName) {
|
||||
if(eventName != "move") {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => log("[WINDOW] Event: $eventName ${_focused.value}"));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
_settingsController.language.value;
|
||||
loadTranslations(context);
|
||||
return Obx(() {
|
||||
return Container(
|
||||
return Container(
|
||||
color: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.93),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: HomePage.kTitleBarHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
_backButton,
|
||||
Expanded(child: _draggableArea),
|
||||
WindowTitleBar(focused: _focused())
|
||||
],
|
||||
)
|
||||
),
|
||||
Expanded(
|
||||
child: Navigator(
|
||||
key: appNavigatorKey,
|
||||
onPopPage: (page, data) => false,
|
||||
pages: [
|
||||
MaterialPage(
|
||||
child: Overlay(
|
||||
key: appOverlayKey,
|
||||
initialEntries: [
|
||||
OverlayEntry(
|
||||
maintainState: true,
|
||||
builder: (context) => Row(
|
||||
children: [
|
||||
_buildLateralView(),
|
||||
_buildBody()
|
||||
],
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Navigator(
|
||||
key: appNavigatorKey,
|
||||
onPopPage: (page, data) => false,
|
||||
pages: [
|
||||
MaterialPage(
|
||||
child: Overlay(
|
||||
key: appOverlayKey,
|
||||
initialEntries: [
|
||||
OverlayEntry(
|
||||
maintainState: true,
|
||||
builder: (context) => Row(
|
||||
children: [
|
||||
_buildLateralView(),
|
||||
_buildBody()
|
||||
],
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() => Expanded(
|
||||
@@ -538,42 +525,6 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _backButton => StreamBuilder(
|
||||
stream: pagesController.stream,
|
||||
builder: (context, _) => Button(
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(const EdgeInsets.symmetric(
|
||||
vertical: 12.0,
|
||||
horizontal: 16.0
|
||||
)),
|
||||
backgroundColor: WidgetStateProperty.all(Colors.transparent),
|
||||
shape: WidgetStateProperty.all(Border())
|
||||
),
|
||||
onPressed: appStack.isEmpty && !inDialog ? null : () {
|
||||
if(inDialog) {
|
||||
Navigator.of(appNavigatorKey.currentContext!).pop();
|
||||
}else {
|
||||
final lastPage = appStack.removeLast();
|
||||
pageStack.remove(lastPage);
|
||||
if (lastPage is int) {
|
||||
hitBack = true;
|
||||
pageIndex.value = lastPage;
|
||||
} else {
|
||||
Navigator.of(pageKey.currentContext!).pop();
|
||||
}
|
||||
}
|
||||
pagesController.add(null);
|
||||
},
|
||||
child: const Icon(FluentIcons.back, size: 12.0),
|
||||
)
|
||||
);
|
||||
|
||||
GestureDetector get _draggableArea => GestureDetector(
|
||||
onDoubleTap: windowManager.maximizeOrRestore,
|
||||
onHorizontalDragStart: (_) => windowManager.startDragging(),
|
||||
onVerticalDragStart: (_) => windowManager.startDragging()
|
||||
);
|
||||
|
||||
Widget get _autoSuggestBox => Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
@@ -10,18 +10,16 @@ import 'package:reboot_launcher/main.dart';
|
||||
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/data.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/data.dart';
|
||||
import 'package:reboot_launcher/src/page/page.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/file_setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/game_start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/version_selector_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/game/game_start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/version/version_selector_tile.dart';
|
||||
|
||||
final GlobalKey<OverlayTargetState> hostVersionOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> hostInfoOverlayTargetKey = GlobalKey();
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/onboard.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/onboard.dart';
|
||||
import 'package:reboot_launcher/src/page/page.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class InfoPage extends RebootPage {
|
||||
@@ -29,7 +29,7 @@ class InfoPage extends RebootPage {
|
||||
|
||||
class _InfoPageState extends RebootPageState<InfoPage> {
|
||||
static const String _kReportBugUrl = "https://github.com/Auties00/reboot_launcher/issues/new";
|
||||
static const String _kDiscordInviteUrl = "https://discord.gg/reboot";
|
||||
static const String _kDiscordInviteUrl = "https://discord.gg/rebootmp";
|
||||
|
||||
@override
|
||||
List<SettingTile> get settings => [
|
||||
@@ -1,15 +1,16 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/data.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/data.dart';
|
||||
import 'package:reboot_launcher/src/page/page.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/game_start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/version_selector_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/game/game_start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/version/version_selector_tile.dart';
|
||||
|
||||
final GlobalKey<OverlayTargetState> gameVersionOverlayTargetKey = GlobalKey();
|
||||
|
||||
@@ -34,6 +35,7 @@ class PlayPage extends RebootPage {
|
||||
|
||||
class _PlayPageState extends RebootPageState<PlayPage> {
|
||||
final GameController _gameController = Get.find<GameController>();
|
||||
final DllController _dllController = Get.find<DllController>();
|
||||
|
||||
@override
|
||||
Widget? get button => LaunchButton(
|
||||
@@ -81,6 +83,7 @@ class _PlayPageState extends RebootPageState<PlayPage> {
|
||||
content: Button(
|
||||
onPressed: () => showResetDialog(() {
|
||||
_gameController.reset();
|
||||
_dllController.resetGame();
|
||||
}),
|
||||
child: Text(translations.gameResetDefaultsContent),
|
||||
)
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter_gen/gen_l10n/reboot_localizations.dart';
|
||||
@@ -6,12 +9,12 @@ import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/page/page.dart';
|
||||
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/file_setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/file/file_setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class SettingsPage extends RebootPage {
|
||||
@@ -36,6 +39,7 @@ class SettingsPage extends RebootPage {
|
||||
class _SettingsPageState extends RebootPageState<SettingsPage> {
|
||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||
final DllController _dllController = Get.find<DllController>();
|
||||
int? _downloadFromMirrorId;
|
||||
|
||||
@override
|
||||
Widget? get button => null;
|
||||
@@ -62,7 +66,7 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
||||
onReset: () {
|
||||
final path = _dllController.getDefaultDllPath(InjectableDll.console);
|
||||
_dllController.unrealEngineConsoleDll.text = path;
|
||||
_dllController.downloadCriticalDllInteractive(path, force: true);
|
||||
_dllController.download(InjectableDll.console, path, force: true);
|
||||
}
|
||||
),
|
||||
createFileSetting(
|
||||
@@ -70,9 +74,19 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
||||
description: translations.settingsClientAuthDescription,
|
||||
controller: _dllController.backendDll,
|
||||
onReset: () {
|
||||
final path = _dllController.getDefaultDllPath(InjectableDll.starfall);
|
||||
final path = _dllController.getDefaultDllPath(InjectableDll.auth);
|
||||
_dllController.backendDll.text = path;
|
||||
_dllController.downloadCriticalDllInteractive(path, force: true);
|
||||
_dllController.download(InjectableDll.auth, path, force: true);
|
||||
}
|
||||
),
|
||||
createFileSetting(
|
||||
title: translations.settingsClientMemoryName,
|
||||
description: translations.settingsClientMemoryDescription,
|
||||
controller: _dllController.memoryLeakDll,
|
||||
onReset: () {
|
||||
final path = _dllController.getDefaultDllPath(InjectableDll.memoryLeak);
|
||||
_dllController.memoryLeakDll.text = path;
|
||||
_dllController.download(InjectableDll.memoryLeak, path, force: true);
|
||||
}
|
||||
),
|
||||
_internalFilesServerType,
|
||||
@@ -105,7 +119,6 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
||||
}
|
||||
|
||||
_dllController.customGameServer.value = entry.key;
|
||||
_dllController.infoBarEntry?.close();
|
||||
if(!entry.key) {
|
||||
_dllController.updateGameServerDll(
|
||||
force: true
|
||||
@@ -131,11 +144,7 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
||||
child: TextFormBox(
|
||||
placeholder: translations.settingsServerMirrorPlaceholder,
|
||||
controller: _dllController.beforeS20Mirror,
|
||||
onChanged: (value) {
|
||||
if(Uri.tryParse(value) != null) {
|
||||
_dllController.updateGameServerDll(force: true);
|
||||
}
|
||||
},
|
||||
onChanged: _scheduleMirrorDownload
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
@@ -174,16 +183,34 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
||||
return createFileSetting(
|
||||
title: translations.settingsOldServerFileName,
|
||||
description: translations.settingsServerFileDescription,
|
||||
controller: _dllController.gameServerDll,
|
||||
controller: _dllController.customGameServerDll,
|
||||
onReset: () {
|
||||
final path = _dllController.getDefaultDllPath(InjectableDll.reboot);
|
||||
_dllController.gameServerDll.text = path;
|
||||
_dllController.downloadCriticalDllInteractive(path);
|
||||
final path = _dllController.getDefaultDllPath(InjectableDll.gameServer);
|
||||
_dllController.customGameServerDll.text = path;
|
||||
_dllController.download(InjectableDll.gameServer, path);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
void _scheduleMirrorDownload(String value) async {
|
||||
if(_downloadFromMirrorId != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(Uri.tryParse(value) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final id = Random.secure().nextInt(1000000);
|
||||
_downloadFromMirrorId = id;
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
if(_downloadFromMirrorId == id) {
|
||||
await _dllController.updateGameServerDll(force: true);
|
||||
}
|
||||
_downloadFromMirrorId = null;
|
||||
}
|
||||
|
||||
Widget get _internalFilesNewServerSource => Obx(() {
|
||||
if(!_dllController.customGameServer.value) {
|
||||
return SettingTile(
|
||||
@@ -199,11 +226,7 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
||||
child: TextFormBox(
|
||||
placeholder: translations.settingsServerMirrorPlaceholder,
|
||||
controller: _dllController.aboveS20Mirror,
|
||||
onChanged: (value) {
|
||||
if(Uri.tryParse(value) != null) {
|
||||
_dllController.updateGameServerDll(force: true);
|
||||
}
|
||||
},
|
||||
onChanged: _scheduleMirrorDownload
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
@@ -263,7 +286,6 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
||||
text: Text(entry.text),
|
||||
onPressed: () {
|
||||
_dllController.timer.value = entry;
|
||||
_dllController.infoBarEntry?.close();
|
||||
_dllController.updateGameServerDll(
|
||||
force: true
|
||||
);
|
||||
@@ -4,7 +4,6 @@ import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/server.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
class ServerButton extends StatefulWidget {
|
||||
@@ -46,7 +45,7 @@ class _ServerButtonState extends State<ServerButton> {
|
||||
builder: (context, snapshot) => Obx(() => Text(_buttonText))
|
||||
),
|
||||
),
|
||||
onPressed: () => _controller.toggleInteractive()
|
||||
onPressed: () => _controller.toggle()
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -2,8 +2,8 @@ import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
class ServerTypeSelector extends StatefulWidget {
|
||||
@@ -32,18 +32,16 @@ class _ServerTypeSelectorState extends State<ServerTypeSelector> {
|
||||
));
|
||||
}
|
||||
|
||||
MenuFlyoutItem _createItem(ServerType type) {
|
||||
return MenuFlyoutItem(
|
||||
text: Text(type.label),
|
||||
onPressed: () async {
|
||||
_controller.stop();
|
||||
_controller.type.value = type;
|
||||
}
|
||||
);
|
||||
}
|
||||
MenuFlyoutItem _createItem(ServerType type) => MenuFlyoutItem(
|
||||
text: Text(type.label),
|
||||
onPressed: () async {
|
||||
await _controller.stop(interactive: false);
|
||||
_controller.type.value = type;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
extension ServerTypeExtension on ServerType {
|
||||
extension _ServerTypeExtension on ServerType {
|
||||
String get label {
|
||||
return this == ServerType.embedded ? translations.embedded
|
||||
: this == ServerType.remote ? translations.remote
|
||||
@@ -1,51 +0,0 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/widget/title_bar_buttons.dart';
|
||||
import 'package:system_theme/system_theme.dart';
|
||||
|
||||
class WindowTitleBar extends StatelessWidget {
|
||||
final bool focused;
|
||||
|
||||
const WindowTitleBar({Key? key, required this.focused}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var lightMode = FluentTheme.of(context).brightness.isLight;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
MinimizeWindowButton(
|
||||
colors: WindowButtonColors(
|
||||
iconNormal: focused || !isWin11 ? lightMode ? Colors.black : Colors.white : SystemTheme.accentColor.lighter,
|
||||
iconMouseDown: lightMode ? Colors.black : Colors.white,
|
||||
iconMouseOver: lightMode ? Colors.black : Colors.white,
|
||||
normal: Colors.transparent,
|
||||
mouseOver: _color,
|
||||
mouseDown: _color.withOpacity(0.7)),
|
||||
),
|
||||
MaximizeWindowButton(
|
||||
colors: WindowButtonColors(
|
||||
iconNormal: focused || !isWin11 ? lightMode ? Colors.black : Colors.white : SystemTheme.accentColor.lighter,
|
||||
iconMouseDown: lightMode ? Colors.black : Colors.white,
|
||||
iconMouseOver: lightMode ? Colors.black : Colors.white,
|
||||
normal: Colors.transparent,
|
||||
mouseOver: _color,
|
||||
mouseDown: _color.withOpacity(0.7)),
|
||||
),
|
||||
CloseWindowButton(
|
||||
colors: WindowButtonColors(
|
||||
iconNormal: focused || !isWin11 ? lightMode ? Colors.black : Colors.white : SystemTheme.accentColor.lighter,
|
||||
iconMouseDown: lightMode ? Colors.black : Colors.white,
|
||||
iconMouseOver: lightMode ? Colors.black : Colors.white,
|
||||
normal: Colors.transparent,
|
||||
mouseOver: Colors.red,
|
||||
mouseDown: Colors.red.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Color get _color =>
|
||||
SystemTheme.accentColor.accent;
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'title_bar_icons.dart';
|
||||
import 'title_bar_mouse.dart';
|
||||
|
||||
typedef WindowButtonIconBuilder = Widget Function(
|
||||
WindowButtonContext buttonContext);
|
||||
typedef WindowButtonBuilder = Widget Function(
|
||||
WindowButtonContext buttonContext, Widget icon);
|
||||
|
||||
class WindowButtonContext {
|
||||
BuildContext context;
|
||||
MouseState mouseState;
|
||||
Color? backgroundColor;
|
||||
Color iconColor;
|
||||
|
||||
WindowButtonContext(
|
||||
{required this.context,
|
||||
required this.mouseState,
|
||||
this.backgroundColor,
|
||||
required this.iconColor});
|
||||
}
|
||||
|
||||
class WindowButtonColors {
|
||||
late Color normal;
|
||||
late Color mouseOver;
|
||||
late Color mouseDown;
|
||||
late Color iconNormal;
|
||||
late Color iconMouseOver;
|
||||
late Color iconMouseDown;
|
||||
|
||||
WindowButtonColors(
|
||||
{Color? normal,
|
||||
Color? mouseOver,
|
||||
Color? mouseDown,
|
||||
Color? iconNormal,
|
||||
Color? iconMouseOver,
|
||||
Color? iconMouseDown}) {
|
||||
this.normal = normal ?? _defaultButtonColors.normal;
|
||||
this.mouseOver = mouseOver ?? _defaultButtonColors.mouseOver;
|
||||
this.mouseDown = mouseDown ?? _defaultButtonColors.mouseDown;
|
||||
this.iconNormal = iconNormal ?? _defaultButtonColors.iconNormal;
|
||||
this.iconMouseOver = iconMouseOver ?? _defaultButtonColors.iconMouseOver;
|
||||
this.iconMouseDown = iconMouseDown ?? _defaultButtonColors.iconMouseDown;
|
||||
}
|
||||
}
|
||||
|
||||
final _defaultButtonColors = WindowButtonColors(
|
||||
normal: Colors.transparent,
|
||||
iconNormal: const Color(0xFF805306),
|
||||
mouseOver: const Color(0xFF404040),
|
||||
mouseDown: const Color(0xFF202020),
|
||||
iconMouseOver: const Color(0xFFFFFFFF),
|
||||
iconMouseDown: const Color(0xFFF0F0F0));
|
||||
|
||||
class WindowButton extends StatelessWidget {
|
||||
final WindowButtonBuilder? builder;
|
||||
final WindowButtonIconBuilder? iconBuilder;
|
||||
late final WindowButtonColors colors;
|
||||
final bool animate;
|
||||
final EdgeInsets? padding;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
WindowButton(
|
||||
{Key? key,
|
||||
WindowButtonColors? colors,
|
||||
this.builder,
|
||||
@required this.iconBuilder,
|
||||
this.padding,
|
||||
this.onPressed,
|
||||
this.animate = false})
|
||||
: super(key: key) {
|
||||
this.colors = colors ?? _defaultButtonColors;
|
||||
}
|
||||
|
||||
Color getBackgroundColor(MouseState mouseState) {
|
||||
if (mouseState.isMouseDown) return colors.mouseDown;
|
||||
if (mouseState.isMouseOver) return colors.mouseOver;
|
||||
return colors.normal;
|
||||
}
|
||||
|
||||
Color getIconColor(MouseState mouseState) {
|
||||
if (mouseState.isMouseDown) return colors.iconMouseDown;
|
||||
if (mouseState.isMouseOver) return colors.iconMouseOver;
|
||||
return colors.iconNormal;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseStateBuilder(
|
||||
builder: (context, mouseState) {
|
||||
WindowButtonContext buttonContext = WindowButtonContext(
|
||||
mouseState: mouseState,
|
||||
context: context,
|
||||
backgroundColor: getBackgroundColor(mouseState),
|
||||
iconColor: getIconColor(mouseState));
|
||||
|
||||
var icon =
|
||||
(iconBuilder != null) ? iconBuilder!(buttonContext) : Container();
|
||||
var fadeOutColor =
|
||||
getBackgroundColor(MouseState()..isMouseOver = true)
|
||||
.withOpacity(0);
|
||||
var padding = this.padding ?? EdgeInsets.zero;
|
||||
var animationMs = mouseState.isMouseOver
|
||||
? (animate ? 100 : 0)
|
||||
: (animate ? 200 : 0);
|
||||
Widget iconWithPadding = Padding(padding: padding, child: icon);
|
||||
iconWithPadding = AnimatedContainer(
|
||||
curve: Curves.easeOut,
|
||||
duration: Duration(milliseconds: animationMs),
|
||||
color: buttonContext.backgroundColor ?? fadeOutColor,
|
||||
child: iconWithPadding);
|
||||
var button = (builder != null)
|
||||
? builder!(buttonContext, icon)
|
||||
: iconWithPadding;
|
||||
return SizedBox.square(dimension: 45, child: button);
|
||||
},
|
||||
onPressed: onPressed);
|
||||
}
|
||||
}
|
||||
|
||||
class MinimizeWindowButton extends WindowButton {
|
||||
MinimizeWindowButton(
|
||||
{Key? key,
|
||||
WindowButtonColors? colors,
|
||||
VoidCallback? onPressed,
|
||||
bool? animate})
|
||||
: super(
|
||||
key: key,
|
||||
colors: colors,
|
||||
animate: animate ?? false,
|
||||
iconBuilder: (buttonContext) =>
|
||||
MinimizeIcon(color: buttonContext.iconColor),
|
||||
onPressed: onPressed ?? () => windowManager.minimize());
|
||||
}
|
||||
|
||||
class MaximizeWindowButton extends WindowButton {
|
||||
MaximizeWindowButton(
|
||||
{Key? key,
|
||||
WindowButtonColors? colors,
|
||||
VoidCallback? onPressed,
|
||||
bool? animate})
|
||||
: super(
|
||||
key: key,
|
||||
colors: colors,
|
||||
animate: animate ?? false,
|
||||
iconBuilder: (buttonContext) =>
|
||||
MaximizeIcon(color: buttonContext.iconColor),
|
||||
onPressed: onPressed ??
|
||||
() => windowManager.maximizeOrRestore());
|
||||
}
|
||||
|
||||
final _defaultCloseButtonColors = WindowButtonColors(
|
||||
mouseOver: const Color(0xFFD32F2F),
|
||||
mouseDown: const Color(0xFFB71C1C),
|
||||
iconNormal: const Color(0xFF805306),
|
||||
iconMouseOver: const Color(0xFFFFFFFF));
|
||||
|
||||
class CloseWindowButton extends WindowButton {
|
||||
CloseWindowButton(
|
||||
{Key? key,
|
||||
WindowButtonColors? colors,
|
||||
VoidCallback? onPressed,
|
||||
bool? animate})
|
||||
: super(
|
||||
key: key,
|
||||
colors: colors ?? _defaultCloseButtonColors,
|
||||
animate: animate ?? false,
|
||||
iconBuilder: (buttonContext) =>
|
||||
CloseIcon(color: buttonContext.iconColor),
|
||||
onPressed: onPressed ?? () => windowManager.close());
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class CloseIcon extends StatelessWidget {
|
||||
final Color color;
|
||||
|
||||
const CloseIcon({Key? key, required this.color}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Stack(children: [
|
||||
Transform.rotate(
|
||||
angle: pi * .25,
|
||||
child:
|
||||
Center(child: Container(width: 14, height: 1, color: color))),
|
||||
Transform.rotate(
|
||||
angle: pi * -.25,
|
||||
child:
|
||||
Center(child: Container(width: 14, height: 1, color: color))),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
class MaximizeIcon extends StatelessWidget {
|
||||
final Color color;
|
||||
|
||||
const MaximizeIcon({Key? key, required this.color}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _AlignedPaint(_MaximizePainter(color));
|
||||
}
|
||||
|
||||
class _MaximizePainter extends _IconPainter {
|
||||
_MaximizePainter(Color color) : super(color);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint p = getPaint(color);
|
||||
canvas.drawRect(Rect.fromLTRB(0, 0, size.width - 1, size.height - 1), p);
|
||||
}
|
||||
}
|
||||
|
||||
class RestoreIcon extends StatelessWidget {
|
||||
final Color color;
|
||||
|
||||
const RestoreIcon({
|
||||
Key? key,
|
||||
required this.color,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _AlignedPaint(_RestorePainter(color));
|
||||
}
|
||||
|
||||
class _RestorePainter extends _IconPainter {
|
||||
_RestorePainter(Color color) : super(color);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint p = getPaint(color);
|
||||
canvas.drawRect(Rect.fromLTRB(0, 2, size.width - 2, size.height), p);
|
||||
canvas.drawLine(const Offset(2, 2), const Offset(2, 0), p);
|
||||
canvas.drawLine(const Offset(2, 0), Offset(size.width, 0), p);
|
||||
canvas.drawLine(
|
||||
Offset(size.width, 0), Offset(size.width, size.height - 2), p);
|
||||
canvas.drawLine(Offset(size.width, size.height - 2),
|
||||
Offset(size.width - 2, size.height - 2), p);
|
||||
}
|
||||
}
|
||||
|
||||
class MinimizeIcon extends StatelessWidget {
|
||||
final Color color;
|
||||
|
||||
const MinimizeIcon({Key? key, required this.color}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _AlignedPaint(_MinimizePainter(color));
|
||||
}
|
||||
|
||||
class _MinimizePainter extends _IconPainter {
|
||||
_MinimizePainter(Color color) : super(color);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint p = getPaint(color);
|
||||
canvas.drawLine(
|
||||
Offset(0, size.height / 2), Offset(size.width, size.height / 2), p);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _IconPainter extends CustomPainter {
|
||||
_IconPainter(this.color);
|
||||
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
class _AlignedPaint extends StatelessWidget {
|
||||
const _AlignedPaint(this.painter, {Key? key}) : super(key: key);
|
||||
final CustomPainter painter;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: CustomPaint(size: const Size(10, 10), painter: painter));
|
||||
}
|
||||
}
|
||||
|
||||
Paint getPaint(Color color, [bool isAntiAlias = false]) => Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..isAntiAlias = isAntiAlias
|
||||
..strokeWidth = 1;
|
||||
@@ -1,71 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
typedef MouseStateBuilderCB = Widget Function(
|
||||
BuildContext context, MouseState mouseState);
|
||||
|
||||
class MouseState {
|
||||
bool isMouseOver;
|
||||
bool isMouseDown;
|
||||
|
||||
MouseState() : isMouseOver = false, isMouseDown = false;
|
||||
}
|
||||
|
||||
class MouseStateBuilder extends StatefulWidget {
|
||||
final MouseStateBuilderCB builder;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
const MouseStateBuilder({Key? key, required this.builder, this.onPressed})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<MouseStateBuilder> createState() => _MouseStateBuilderState();
|
||||
}
|
||||
|
||||
class _MouseStateBuilderState extends State<MouseStateBuilder> {
|
||||
late MouseState _mouseState;
|
||||
|
||||
_MouseStateBuilderState() {
|
||||
_mouseState = MouseState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (event) {
|
||||
setState(() {
|
||||
_mouseState.isMouseOver = true;
|
||||
});
|
||||
},
|
||||
onExit: (event) {
|
||||
setState(() {
|
||||
_mouseState.isMouseOver = false;
|
||||
});
|
||||
},
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) {
|
||||
setState(() {
|
||||
_mouseState.isMouseDown = true;
|
||||
});
|
||||
},
|
||||
onTapCancel: () {
|
||||
setState(() {
|
||||
_mouseState.isMouseDown = false;
|
||||
});
|
||||
},
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_mouseState.isMouseDown = false;
|
||||
_mouseState.isMouseOver = false;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.onPressed != null) {
|
||||
widget.onPressed!();
|
||||
}
|
||||
});
|
||||
},
|
||||
onTapUp: (_) {},
|
||||
child: widget.builder(context, _mouseState)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/implementation/version.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/widget/message/version.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/version_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/version/version_selector.dart';
|
||||
|
||||
SettingTile buildVersionSelector({
|
||||
required GlobalKey<OverlayTargetState> key
|
||||
@@ -1,11 +1,15 @@
|
||||
name: reboot_launcher
|
||||
description: Graphical User Interface for Project Reboot
|
||||
version: "10.0.0"
|
||||
version: "10.0.7"
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <=4.0.0"
|
||||
# 3.19.0 is the last version that supports Windows 7/8/8.1 officially
|
||||
# I have no clue who is still using Windows 7, but some users requested support, so might as well add it
|
||||
# Repository Issue: https://github.com/Auties00/Reboot-Launcher/issues/58
|
||||
# Flutter issue: https://github.com/flutter/flutter/issues/140830#issuecomment-1936397549
|
||||
sdk: ">=3.0.0 <=3.19.0"
|
||||
|
||||
dependencies:
|
||||
# The flutter SDK
|
||||
@@ -18,8 +22,7 @@ dependencies:
|
||||
|
||||
# Windows UI 3
|
||||
fluent_ui: ^4.9.1
|
||||
flutter_acrylic:
|
||||
path: ./dependencies/flutter_acrylic
|
||||
flutter_acrylic: ^1.1.4
|
||||
fluentui_system_icons: ^1.1.258
|
||||
system_theme: ^3.1.1
|
||||
skeletons:
|
||||
@@ -40,6 +43,7 @@ dependencies:
|
||||
# Async helpers
|
||||
async: ^2.11.0
|
||||
sync: ^0.3.0
|
||||
synchronized: ^3.3.0+3
|
||||
|
||||
# State management
|
||||
get: ^4.6.6
|
||||
@@ -58,7 +62,6 @@ dependencies:
|
||||
|
||||
# Storage
|
||||
get_storage: ^2.1.1
|
||||
universal_disk_space: ^0.2.3
|
||||
path: ^1.9.0
|
||||
|
||||
# Translations
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <app_links/app_links_plugin_c_api.h>
|
||||
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
||||
#include <local_notifier/local_notifier_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||
#include <system_theme/system_theme_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
@@ -22,8 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
|
||||
LocalNotifierPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
|
||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
||||
SystemThemePluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SystemThemePlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
||||
@@ -6,7 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
app_links
|
||||
flutter_acrylic
|
||||
local_notifier
|
||||
screen_retriever
|
||||
screen_retriever_windows
|
||||
system_theme
|
||||
url_launcher_windows
|
||||
window_manager
|
||||
|
||||
@@ -36,7 +36,6 @@ Source: "{{SOURCE_DIR}}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdir
|
||||
Source: "..\..\dependencies\redist\VC_redist.x64.exe"; DestDir: {tmp}; Flags: dontcopy
|
||||
|
||||
[Run]
|
||||
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""Add-MpPreference -ExclusionPath '{app}'"""; Flags: runhidden
|
||||
Filename: "{app}\{{EXECUTABLE_NAME}}"; Description: "{cm:LaunchProgram,{{DISPLAY_NAME}}}"; Flags: runascurrentuser nowait postinstall skipifsilent
|
||||
Filename: "{tmp}\VC_redist.x64.exe"; StatusMsg: "{cm:InstallingVC2017redist}"; Parameters: "/quiet"; Check: VC2017RedistNeedsInstall; Flags: waituntilterminated
|
||||
|
||||
@@ -46,6 +45,44 @@ Name: "{autodesktop}\{{DISPLAY_NAME}}"; Filename: "{app}\{{EXECUTABLE_NAME}}"; T
|
||||
Name: "{userstartup}\{{DISPLAY_NAME}}"; Filename: "{app}\{{EXECUTABLE_NAME}}"; WorkingDir: "{app}"; Tasks: launchAtStartup
|
||||
|
||||
[Code]
|
||||
var
|
||||
Page: TInputOptionWizardPage;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
Page := CreateInputOptionPage(
|
||||
wpWelcome,
|
||||
' Allow DLL injection',
|
||||
' The Reboot Launcher needs to inject DLLs into Fortnite to create the game server',
|
||||
'Selecting the option below will add the Reboot Launcher to the Windows Exclusions list. ' +
|
||||
'This is necessary because DLL injection is often detected as a virus, but is necessary to modify Fortnite. ' +
|
||||
'This option was designed for advanced users who want to manually manage the exclusions list on their machine. ' +
|
||||
'If you do not trust the Reboot Launcher, you can audit the source code at https://github.com/Auties00/reboot_launcher and build it from source.',
|
||||
False,
|
||||
False
|
||||
);
|
||||
Page.Add('&Add the launcher to the Windows Exclusions list');
|
||||
Page.Values[0] := True;
|
||||
end;
|
||||
|
||||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
var
|
||||
ResultCode: Integer;
|
||||
InstallationDir: String;
|
||||
begin
|
||||
if (CurStep = ssPostInstall) and Page.Values[0] then
|
||||
begin
|
||||
InstallationDir := ExpandConstant('{app}');
|
||||
Exec('powershell.exe', '-ExecutionPolicy Bypass -Command ""Add-MpPreference -ExclusionPath ''' + InstallationDir + '''""' , '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
Log('Powershell exit code: ' + IntToStr(ResultCode));
|
||||
end;
|
||||
end;
|
||||
|
||||
function CompareVersion(version1, version2: String): Integer;
|
||||
var
|
||||
packVersion1, packVersion2: Int64;
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
#include <dwmapi.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "Windowsx.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||
@@ -160,52 +164,45 @@ LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
||||
}
|
||||
|
||||
LRESULT
|
||||
Win32Window::MessageHandler(HWND hwnd,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
switch (message) {
|
||||
case WM_DESTROY:
|
||||
Win32Window::MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept {
|
||||
switch (uMsg) {
|
||||
case WM_DESTROY: {
|
||||
window_handle_ = nullptr;
|
||||
Destroy();
|
||||
if (quit_on_close_) {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_DPICHANGED: {
|
||||
auto newRectSize = reinterpret_cast<RECT *>(lparam);
|
||||
auto newRectSize = reinterpret_cast<RECT *>(lParam);
|
||||
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||||
|
||||
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||||
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
SetWindowPos(hWnd, nullptr, newRectSize->left, newRectSize->top, newWidth,newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_SIZE: {
|
||||
RECT rect = GetClientArea();
|
||||
auto rect = GetClientArea();
|
||||
if (child_content_ != nullptr) {
|
||||
// Size and position the child window.
|
||||
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
||||
rect.bottom - rect.top, TRUE);
|
||||
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,rect.bottom - rect.top, TRUE);
|
||||
}
|
||||
return 0;
|
||||
return DefWindowProc(child_content_, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
case WM_ACTIVATE:
|
||||
case WM_ACTIVATE: {
|
||||
if (child_content_ != nullptr) {
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_NCCALCSIZE:
|
||||
return 0;
|
||||
default:
|
||||
return DefWindowProc(window_handle_, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||||
}
|
||||
|
||||
void Win32Window::Destroy() {
|
||||
OnDestroy();
|
||||
|
||||
@@ -228,8 +225,7 @@ void Win32Window::SetChildContent(HWND content) {
|
||||
SetParent(content, window_handle_);
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
||||
frame.bottom - frame.top, true);
|
||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,frame.bottom - frame.top, true);
|
||||
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Constants
|
||||
{
|
||||
constexpr auto API_URL = L"http://localhost:3551";
|
||||
|
||||
constexpr auto ProcessRequest = L"Could not set libcurl options for easy handle, processing HTTP request failed. Increase verbosity for additional information.";
|
||||
constexpr auto ProcessRequest_C2 = L"STAT_FCurlHttpRequest_ProcessRequest";
|
||||
constexpr auto URLOffset = L"ProcessRequest failed. URL '%s' is not a valid HTTP request. %p";
|
||||
constexpr auto Realloc = L"AbilitySystem.Debug.NextTarget";
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#include "Core.h"
|
||||
|
||||
void Core::Init()
|
||||
{
|
||||
FMemory::_Realloc = Memcury::Scanner::FindStringRef(Constants::Realloc)
|
||||
.ScanFor({ Memcury::ASM::MNEMONIC::CALL })
|
||||
.RelativeOffset(1)
|
||||
.GetAs<decltype(FMemory::_Realloc)>();
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "..\Utilities\memcury.h"
|
||||
|
||||
#include "Constants.h"
|
||||
|
||||
#include "Unreal\Memory.h"
|
||||
#include "Unreal\Array.h"
|
||||
#include "Unreal\String.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
void Init();
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include "Memory.h"
|
||||
|
||||
template <typename T>
|
||||
class TArray
|
||||
{
|
||||
friend class FString;
|
||||
|
||||
T* Data;
|
||||
int32_t NumElements;
|
||||
int32_t MaxElements;
|
||||
|
||||
public:
|
||||
|
||||
inline TArray()
|
||||
{
|
||||
Data = nullptr;
|
||||
NumElements = 0;
|
||||
MaxElements = 0;
|
||||
};
|
||||
|
||||
inline void Free()
|
||||
{
|
||||
FMemory::Free(Data);
|
||||
Data = nullptr;
|
||||
NumElements = 0;
|
||||
MaxElements = 0;
|
||||
}
|
||||
|
||||
inline void Reset()
|
||||
{
|
||||
Free();
|
||||
}
|
||||
|
||||
inline auto GetData()
|
||||
{
|
||||
return Data;
|
||||
}
|
||||
|
||||
inline int GetCount() const
|
||||
{
|
||||
return NumElements;
|
||||
}
|
||||
|
||||
inline int Num() const
|
||||
{
|
||||
return NumElements;
|
||||
}
|
||||
|
||||
inline auto& Get(const int Index)
|
||||
{
|
||||
return Data[Index];
|
||||
}
|
||||
|
||||
inline auto& First()
|
||||
{
|
||||
return Get(0);
|
||||
}
|
||||
|
||||
inline auto GetRef(const int Index, int Size = sizeof(T))
|
||||
{
|
||||
return (T*)((uint8_t*)Data + (Index * Size));
|
||||
}
|
||||
|
||||
inline T& operator[](int i)
|
||||
{
|
||||
return Get(i);
|
||||
};
|
||||
|
||||
inline const T& operator[](int i) const
|
||||
{
|
||||
return Get(i);
|
||||
};
|
||||
|
||||
inline bool Remove(const int Index, int Size = sizeof(T))
|
||||
{
|
||||
if (Index < NumElements)
|
||||
{
|
||||
if (Index != NumElements - 1)
|
||||
Get(Index) = Get(NumElements - 1);
|
||||
|
||||
--NumElements;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
inline bool Any(std::function<bool(T)> Func)
|
||||
{
|
||||
for (int i = 0; i < NumElements; ++i)
|
||||
{
|
||||
if (Func(Get(i)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline T Select(std::function<bool(T)> Func)
|
||||
{
|
||||
for (int i = 0; i < NumElements; ++i)
|
||||
{
|
||||
if (Func(Get(i)))
|
||||
return Get(i);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
inline void ForEach(std::function<void(T)> Func)
|
||||
{
|
||||
for (int i = 0; i < NumElements; ++i)
|
||||
{
|
||||
Func(Get(i));
|
||||
}
|
||||
}
|
||||
|
||||
inline int Count(std::function<bool(T)> Func)
|
||||
{
|
||||
int Num = 0;
|
||||
|
||||
for (int i = 0; i < NumElements; ++i)
|
||||
{
|
||||
if (Func(Get(i)))
|
||||
Num++;
|
||||
}
|
||||
return Num;
|
||||
}
|
||||
|
||||
inline int Find(const T& Item)
|
||||
{
|
||||
for (int i = 0; i < NumElements; i++)
|
||||
{
|
||||
if (this->operator[](i) == Item)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <cstdint>
|
||||
|
||||
class FMemory
|
||||
{
|
||||
public:
|
||||
static inline void* (*_Realloc)(void*, size_t, int64_t);
|
||||
|
||||
static void Free(void* Data)
|
||||
{
|
||||
_Realloc(Data, 0, 0);
|
||||
}
|
||||
|
||||
static void* Malloc(size_t Size)
|
||||
{
|
||||
return _Realloc(0, Size, 0);
|
||||
}
|
||||
|
||||
static void* Realloc(void* Data, size_t NewSize)
|
||||
{
|
||||
return _Realloc(Data, NewSize, 0);
|
||||
}
|
||||
|
||||
static void* Memmove(void* Dest, const void* Src, size_t Count)
|
||||
{
|
||||
return memmove(Dest, Src, Count);
|
||||
}
|
||||
|
||||
static int Memcmp(const void* Buf1, const void* Buf2, size_t Count)
|
||||
{
|
||||
return memcmp(Buf1, Buf2, Count);
|
||||
}
|
||||
|
||||
static void* Memset(void* Dest, uint8_t Char, size_t Count)
|
||||
{
|
||||
return memset(Dest, Char, Count);
|
||||
}
|
||||
|
||||
template< class T >
|
||||
static void Memset(T& Src, uint8_t ValueToSet)
|
||||
{
|
||||
Memset(&Src, ValueToSet, sizeof(T));
|
||||
}
|
||||
|
||||
static void* Memzero(void* Dest, size_t Count)
|
||||
{
|
||||
return ZeroMemory(Dest, Count);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static void Memzero(T& Src)
|
||||
{
|
||||
Memzero(&Src, sizeof(T));
|
||||
}
|
||||
|
||||
static void* Memcpy(void* Dest, const void* Src, size_t Count)
|
||||
{
|
||||
return memcpy(Dest, Src, Count);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static void Memcpy(T& Dest, const T& Src)
|
||||
{
|
||||
Memcpy(&Dest, &Src, sizeof(T));
|
||||
}
|
||||
|
||||
static void* Calloc(size_t NumElements, size_t ElementSize)
|
||||
{
|
||||
auto TotalSize = NumElements * ElementSize;
|
||||
auto Data = FMemory::Malloc(TotalSize);
|
||||
|
||||
if (!Data)
|
||||
return NULL;
|
||||
|
||||
FMemory::Memzero(Data, TotalSize);
|
||||
|
||||
return Data;
|
||||
}
|
||||
|
||||
static char* Strdup(const char* Str)
|
||||
{
|
||||
auto StrLen = strlen(Str) + 1;
|
||||
auto StrDup = (char*)FMemory::Malloc(StrLen);
|
||||
|
||||
FMemory::Memcpy(StrDup, Str, StrLen);
|
||||
|
||||
return StrDup;
|
||||
}
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#pragma once
|
||||
#include "Array.h"
|
||||
#include "Memory.h"
|
||||
|
||||
class FString : private TArray<wchar_t>
|
||||
{
|
||||
public:
|
||||
|
||||
inline FString()
|
||||
{
|
||||
Data = nullptr;
|
||||
NumElements = 0;
|
||||
MaxElements = 0;
|
||||
}
|
||||
|
||||
inline FString(const char* Other)
|
||||
{
|
||||
if (Other)
|
||||
{
|
||||
auto NumCharacters = (int)std::strlen(Other);
|
||||
MaxElements = NumElements = NumCharacters + 1;
|
||||
|
||||
Data = static_cast<wchar_t*>(FMemory::Malloc(NumElements * sizeof(wchar_t)));
|
||||
|
||||
size_t ConvertedChars = 0;
|
||||
mbstowcs_s(&ConvertedChars, Data, NumElements, Other, _TRUNCATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxElements = NumElements = 0;
|
||||
Data = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
inline FString(const wchar_t* Other)
|
||||
{
|
||||
MaxElements = NumElements = *Other ? (int)std::wcslen(Other) + 1 : 0;
|
||||
|
||||
if (NumElements && Other)
|
||||
{
|
||||
Data = static_cast<wchar_t*>(FMemory::Malloc(NumElements * 2));
|
||||
|
||||
memcpy_s(Data, NumElements * 2, Other, NumElements * 2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline auto c_str()
|
||||
{
|
||||
return Data;
|
||||
}
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#include "framework.h"
|
||||
|
||||
static void Main()
|
||||
{
|
||||
Sleep(7500);
|
||||
|
||||
Core::Init();
|
||||
Sinum::Init();
|
||||
}
|
||||
|
||||
bool DllMain(HMODULE hModule, DWORD dwReason, void* lpReserved)
|
||||
{
|
||||
if (dwReason == DLL_PROCESS_ATTACH)
|
||||
{
|
||||
Windows::Thread::Create(Main);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# Sinum Windows
|
||||
|
||||
https://github.com/projectnovafn/Sinum/tree/main/Windows
|
||||
|
||||
Modified to point to http://localhost:3551
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.7.34031.279
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Sinum", "Sinum.vcxproj", "{E7291B57-1B5B-497C-9C2E-C78A556A06CF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E7291B57-1B5B-497C-9C2E-C78A556A06CF}.Release|x64.ActiveCfg = Release|x64
|
||||
{E7291B57-1B5B-497C-9C2E-C78A556A06CF}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7975A2E1-0078-4AF8-AB10-0A71112857A5}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,162 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{e7291b57-1b5b-497c-9c2e-c78a556a06cf}</ProjectGuid>
|
||||
<RootNamespace>Sinum</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Core\Constants.h" />
|
||||
<ClInclude Include="Core\Unreal\Array.h" />
|
||||
<ClInclude Include="Core\Core.h" />
|
||||
<ClInclude Include="Core\Unreal\Memory.h" />
|
||||
<ClInclude Include="Core\Unreal\String.h" />
|
||||
<ClInclude Include="framework.h" />
|
||||
<ClInclude Include="Sinum\Sinum.h" />
|
||||
<ClInclude Include="Utilities\memcury.h" />
|
||||
<ClInclude Include="Utilities\Windows.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Core\Core.cpp" />
|
||||
<ClCompile Include="Main.cpp" />
|
||||
<ClCompile Include="Sinum\Sinum.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
</Project>
|
||||
@@ -1,57 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="framework.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Utilities\Windows.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Unreal\Array.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Constants.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Unreal\String.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Unreal\Memory.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Core.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Sinum\Sinum.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Utilities\memcury.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Sinum\Sinum.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Core\Core.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#include "Sinum.h"
|
||||
|
||||
bool Sinum::ProcessRequestHook(FCurlHttpRequest* Request)
|
||||
{
|
||||
std::wstring URL(Request->GetURL().c_str());
|
||||
size_t PathIndex = URL.find(L"ol.epicgames.com");
|
||||
|
||||
if (PathIndex != std::wstring::npos)
|
||||
{
|
||||
auto Path = URL.substr(PathIndex + 16);
|
||||
auto NewURL = Constants::API_URL + Path;
|
||||
|
||||
Request->SetURL(NewURL.c_str());
|
||||
}
|
||||
|
||||
return _ProcessRequest(Request);
|
||||
}
|
||||
|
||||
void Sinum::Init()
|
||||
{
|
||||
auto StringRef = Memcury::Scanner::FindStringRef(Constants::ProcessRequest);
|
||||
if (StringRef.IsValid())
|
||||
{
|
||||
_ProcessRequest = StringRef
|
||||
.ScanFor({ 0x48, 0x81, 0xEC }, false)
|
||||
.ScanFor({ 0x40 }, false)
|
||||
.GetAs<decltype(_ProcessRequest)>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ProcessRequest = Memcury::Scanner::FindStringRef(Constants::ProcessRequest_C2)
|
||||
.ScanFor({ 0x4C, 0x8B, 0xDC }, false)
|
||||
.GetAs<decltype(_ProcessRequest)>();
|
||||
}
|
||||
|
||||
*Memcury::Scanner::FindPointerRef(_ProcessRequest)
|
||||
.GetAs<void**>() = ProcessRequestHook;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#pragma once
|
||||
#include "../framework.h"
|
||||
|
||||
class FCurlHttpRequest
|
||||
{
|
||||
private:
|
||||
void** VTable;
|
||||
|
||||
public:
|
||||
|
||||
FString GetURL()
|
||||
{
|
||||
FString Result;
|
||||
return ((FString& (*)(FCurlHttpRequest*, FString&))(*VTable))(this, Result);
|
||||
}
|
||||
|
||||
void SetURL(FString URL)
|
||||
{
|
||||
((void (*)(FCurlHttpRequest*, FString&))(VTable[10]))(this, URL);
|
||||
}
|
||||
};
|
||||
|
||||
namespace Sinum
|
||||
{
|
||||
static bool (*_ProcessRequest)(FCurlHttpRequest*);
|
||||
static bool ProcessRequestHook(FCurlHttpRequest* Request);
|
||||
|
||||
void Init();
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#pragma once
|
||||
#include "../framework.h"
|
||||
|
||||
namespace Windows
|
||||
{
|
||||
namespace Thread
|
||||
{
|
||||
static HANDLE Create(void* Routine, void* Param = NULL)
|
||||
{
|
||||
return CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Routine, Param, 0, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) 2024 Project Nova LLC
|
||||
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
#include "Utilities\memcury.h"
|
||||
#include "Utilities\Windows.h"
|
||||
|
||||
#include "Core\Core.h"
|
||||
#include "Sinum\Sinum.h"
|
||||
Reference in New Issue
Block a user