mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
decoupled business logic from ui
This commit is contained in:
@@ -1,394 +1,162 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:process_run/shell.dart';
|
||||
import 'package:reboot_launcher/src/util/binary.dart';
|
||||
import 'package:reboot_launcher/src/util/node.dart';
|
||||
import 'package:reboot_launcher/src/util/server_standalone.dart';
|
||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||
import 'package:reboot_launcher/src/model/server_type.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shelf_proxy/shelf_proxy.dart';
|
||||
import 'package:shelf/shelf_io.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
Future<bool> checkLocalServer(BuildContext context, String host, String port, bool closeAutomatically) async {
|
||||
host = host.trim();
|
||||
if(host.isEmpty){
|
||||
showSnackbar(
|
||||
context, const Snackbar(content: Text("Missing host name")));
|
||||
return false;
|
||||
}
|
||||
final serverLocation = File("${Platform.environment["UserProfile"]}\\.reboot_launcher\\lawin\\Lawin.exe");
|
||||
const String _serverUrl =
|
||||
"https://cdn.discordapp.com/attachments/1026121175878881290/1031230792069820487/LawinServer.zip";
|
||||
|
||||
port = port.trim();
|
||||
if(port.isEmpty){
|
||||
showSnackbar(
|
||||
context, const Snackbar(content: Text("Missing port", textAlign: TextAlign.center)));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(int.tryParse(port) == null){
|
||||
showSnackbar(
|
||||
context, const Snackbar(content: Text("Invalid port, use only numbers", textAlign: TextAlign.center)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return await _showCheck(context, host, port, false, closeAutomatically) != null;
|
||||
Future<bool> downloadServer(ignored) async {
|
||||
var response = await http.get(Uri.parse(_serverUrl));
|
||||
var tempZip = File("${Platform.environment["Temp"]}/lawin.zip");
|
||||
await tempZip.writeAsBytes(response.bodyBytes);
|
||||
await extractFileToDisk(tempZip.path, serverLocation.parent.path);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Future<HttpServer?> changeReverseProxyState(BuildContext context, String host, String port, bool closeAutomatically, HttpServer? server) async {
|
||||
if(server != null){
|
||||
try{
|
||||
server.close(force: true);
|
||||
return null;
|
||||
}catch(error){
|
||||
_showStopProxyError(context, error);
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
host = host.trim();
|
||||
if(host.isEmpty){
|
||||
showSnackbar(
|
||||
context, const Snackbar(content: Text("Missing host name")));
|
||||
return null;
|
||||
}
|
||||
|
||||
port = port.trim();
|
||||
if(port.isEmpty){
|
||||
showSnackbar(
|
||||
context, const Snackbar(content: Text("Missing port", textAlign: TextAlign.center)));
|
||||
return null;
|
||||
}
|
||||
|
||||
if(int.tryParse(port) == null){
|
||||
showSnackbar(
|
||||
context, const Snackbar(content: Text("Invalid port, use only numbers", textAlign: TextAlign.center)));
|
||||
return null;
|
||||
}
|
||||
|
||||
try{
|
||||
var uri = await _showCheck(context, host, port, true, closeAutomatically);
|
||||
if(uri == null){
|
||||
return null;
|
||||
}
|
||||
|
||||
return await shelf_io.serve(proxyHandler(uri), "127.0.0.1", 3551);
|
||||
}catch(error){
|
||||
_showStartProxyError(context, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uri?> _showCheck(BuildContext context, String host, String port, bool remote, bool closeAutomatically) async {
|
||||
var future = ping(host, port);
|
||||
Uri? result;
|
||||
return await showDialog(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
content: FutureBuilder<Uri?>(
|
||||
future: future,
|
||||
builder: (context, snapshot) {
|
||||
if(snapshot.hasError){
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text("Cannot ping ${remote ? "remote" : "local"} server: ${snapshot.error}" , textAlign: TextAlign.center)
|
||||
);
|
||||
}
|
||||
|
||||
if(snapshot.connectionState == ConnectionState.done && !snapshot.hasData){
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
"The ${remote ? "remote" : "local"} server doesn't work correctly ${remote ? "or the IP and/or the port are incorrect" : ""}",
|
||||
textAlign: TextAlign.center
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
result = snapshot.data;
|
||||
if(snapshot.hasData){
|
||||
if(remote || closeAutomatically) {
|
||||
Navigator.of(context).pop(result);
|
||||
}
|
||||
|
||||
return const SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
"The server works correctly",
|
||||
textAlign: TextAlign.center
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return InfoLabel(
|
||||
label: "Pinging ${remote ? "remote" : "local"} lawin server...",
|
||||
child: const SizedBox(
|
||||
width: double.infinity,
|
||||
child: ProgressBar()
|
||||
)
|
||||
);
|
||||
}
|
||||
),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button(
|
||||
onPressed: () => Navigator.of(context).pop(result),
|
||||
child: const Text('Close'),
|
||||
))
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void _showStartProxyError(BuildContext context, Object error) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
content: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text("Cannot create the reverse proxy: $error", textAlign: TextAlign.center)
|
||||
),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Close'),
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void _showStopProxyError(BuildContext context, Object error) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
content: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text("Cannot kill the reverse proxy: $error", textAlign: TextAlign.center)
|
||||
),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Close'),
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> changeEmbeddedServerState(BuildContext context, bool running) async {
|
||||
if (running) {
|
||||
var releaseBat = await loadBinary("release.bat", false);
|
||||
await Process.run(releaseBat.path, []);
|
||||
return false;
|
||||
}
|
||||
|
||||
var free = await isLawinPortFree();
|
||||
if (!free) {
|
||||
var shouldKill = await _showAlreadyBindPortWarning(context);
|
||||
if (!shouldKill) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var releaseBat = await loadBinary("release.bat", false);
|
||||
await Process.run(releaseBat.path, []);
|
||||
}
|
||||
|
||||
var node = await hasNode();
|
||||
var useLocalNode = false;
|
||||
if(!node) {
|
||||
useLocalNode = true;
|
||||
if(!embeddedNode.existsSync()){
|
||||
var result = await _showNodeDownloadInfo(context);
|
||||
if(!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!serverLocation.existsSync()) {
|
||||
var result = await _showServerDownloadInfo(context);
|
||||
if(!result){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var serverRunner = File("${serverLocation.path}/start.bat");
|
||||
if (!serverRunner.existsSync()) {
|
||||
_showEmbeddedError(context, "missing file ${serverRunner.path}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var nodeModules = Directory("${serverLocation.path}/node_modules");
|
||||
if (!nodeModules.existsSync()) {
|
||||
await Process.run("${serverLocation.path}/install_packages.bat", [],
|
||||
workingDirectory: serverLocation.path);
|
||||
}
|
||||
|
||||
Future<bool> isLawinPortFree() async {
|
||||
try {
|
||||
var logFile = await loadBinary("server.txt", true);
|
||||
if(logFile.existsSync()){
|
||||
logFile.deleteSync();
|
||||
}
|
||||
|
||||
var process = await Process.start(
|
||||
!useLocalNode ? "node" : '"${embeddedNode.path}"',
|
||||
["index.js"],
|
||||
workingDirectory: serverLocation.path
|
||||
);
|
||||
process.outLines.forEach((line) => logFile.writeAsString("$line\n", mode: FileMode.append));
|
||||
process.errLines.forEach((line) => logFile.writeAsString("$line\n", mode: FileMode.append));
|
||||
return true;
|
||||
}catch(exception){
|
||||
_showEmbeddedError(context, exception.toString());
|
||||
return false;
|
||||
var portBat = await loadBinary("port.bat", true);
|
||||
var process = await Process.run(portBat.path, []);
|
||||
return !process.outText.contains(" LISTENING ");
|
||||
}catch(_){
|
||||
return ServerSocket.bind("127.0.0.1", 3551)
|
||||
.then((socket) => socket.close())
|
||||
.then((_) => true)
|
||||
.onError((error, _) => false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _showServerDownloadInfo(BuildContext context) async {
|
||||
var nodeFuture = compute(downloadServer, true);
|
||||
var result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
content: FutureBuilder(
|
||||
future: nodeFuture,
|
||||
builder: (context, snapshot) {
|
||||
if(snapshot.hasError){
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text("An error occurred while downloading: ${snapshot.error}",
|
||||
textAlign: TextAlign.center));
|
||||
}
|
||||
Future<void> freeLawinPort() async {
|
||||
var releaseBat = await loadBinary("release.bat", false);
|
||||
await Process.run(releaseBat.path, []);
|
||||
}
|
||||
|
||||
if(snapshot.hasData){
|
||||
return const SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text("The download was completed successfully!",
|
||||
textAlign: TextAlign.center)
|
||||
);
|
||||
}
|
||||
List<String> createRebootArgs(String username, bool headless) {
|
||||
var args = [
|
||||
"-skippatchcheck",
|
||||
"-epicapp=Fortnite",
|
||||
"-epicenv=Prod",
|
||||
"-epiclocale=en-us",
|
||||
"-epicportal",
|
||||
"-noeac",
|
||||
"-fromfl=be",
|
||||
"-fltoken=7ce411021b27b4343a44fdg8",
|
||||
"-caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
|
||||
"-AUTH_LOGIN=$username@projectreboot.dev",
|
||||
"-AUTH_PASSWORD=Rebooted",
|
||||
"-AUTH_TYPE=epic"
|
||||
];
|
||||
|
||||
return InfoLabel(
|
||||
label: "Downloading lawin server...",
|
||||
child: const SizedBox(
|
||||
width: double.infinity,
|
||||
child: ProgressBar()
|
||||
)
|
||||
);
|
||||
}
|
||||
),
|
||||
actions: [
|
||||
FutureBuilder(
|
||||
future: nodeFuture,
|
||||
builder: (builder, snapshot) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button(
|
||||
onPressed: () => Navigator.of(context).pop(snapshot.hasData && !snapshot.hasError),
|
||||
child: Text(!snapshot.hasData && !snapshot.hasError ? 'Stop' : 'Close'),
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
if(headless){
|
||||
args.addAll(["-nullrhi", "-nosplash", "-nosound"]);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
Future<Uri?> pingSelf() async => ping("127.0.0.1", "3551");
|
||||
|
||||
Future<Uri?> ping(String host, String port, [bool https=false]) async {
|
||||
var hostName = _getHostName(host);
|
||||
var declaredScheme = _getScheme(host);
|
||||
try{
|
||||
var uri = Uri(
|
||||
scheme: declaredScheme ?? (https ? "https" : "http"),
|
||||
host: hostName,
|
||||
port: int.parse(port)
|
||||
);
|
||||
var client = HttpClient()
|
||||
..connectionTimeout = const Duration(seconds: 5);
|
||||
var request = await client.getUrl(uri);
|
||||
var response = await request.close();
|
||||
var body = utf8.decode(await response.single);
|
||||
return response.statusCode == 200 && body.contains("Welcome to LawinServer!") ? uri : null;
|
||||
}catch(_){
|
||||
return https || declaredScheme != null ? null : await ping(host, port, true);
|
||||
}
|
||||
}
|
||||
|
||||
String? _getHostName(String host) => host.replaceFirst("http://", "").replaceFirst("https://", "");
|
||||
|
||||
String? _getScheme(String host) => host.startsWith("http://") ? "http" : host.startsWith("https://") ? "https" : null;
|
||||
|
||||
Future<ServerResult> checkServerPreconditions(String host, String port, ServerType type) async {
|
||||
host = host.trim();
|
||||
if(host.isEmpty){
|
||||
return ServerResult(
|
||||
type: ServerResultType.missingHostError
|
||||
);
|
||||
}
|
||||
|
||||
port = port.trim();
|
||||
if(port.isEmpty){
|
||||
return ServerResult(
|
||||
type: ServerResultType.missingPortError
|
||||
);
|
||||
}
|
||||
|
||||
if(int.tryParse(port) == null){
|
||||
return ServerResult(
|
||||
type: ServerResultType.illegalPortError
|
||||
);
|
||||
}
|
||||
|
||||
if(type == ServerType.embedded || type == ServerType.remote){
|
||||
var free = await isLawinPortFree();
|
||||
if (!free) {
|
||||
return ServerResult(
|
||||
type: ServerResultType.portTakenError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(type == ServerType.embedded && !serverLocation.existsSync()){
|
||||
return ServerResult(
|
||||
type: ServerResultType.serverDownloadRequiredError
|
||||
);
|
||||
}
|
||||
|
||||
return ServerResult(
|
||||
uri: ping(host, port),
|
||||
type: ServerResultType.canStart
|
||||
);
|
||||
|
||||
return result != null && result;
|
||||
}
|
||||
|
||||
void _showEmbeddedError(BuildContext context, String error) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
content: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
"Cannot start server: $error",
|
||||
textAlign: TextAlign.center
|
||||
)
|
||||
),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Close'),
|
||||
))
|
||||
],
|
||||
));
|
||||
Future<Process> startEmbeddedServer() async {
|
||||
return await Process.start(serverLocation.path, [], workingDirectory: serverLocation.parent.path);
|
||||
}
|
||||
|
||||
Future<bool> _showNodeDownloadInfo(BuildContext context) async {
|
||||
var nodeFuture = compute(downloadNode, true);
|
||||
var result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
content: FutureBuilder(
|
||||
future: nodeFuture,
|
||||
builder: (context, snapshot) {
|
||||
if(snapshot.hasError){
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text("An error occurred while downloading: ${snapshot.error}",
|
||||
textAlign: TextAlign.center));
|
||||
}
|
||||
|
||||
if(snapshot.hasData){
|
||||
return const SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text("The download was completed successfully!",
|
||||
textAlign: TextAlign.center)
|
||||
);
|
||||
}
|
||||
|
||||
return InfoLabel(
|
||||
label: "Downloading node...",
|
||||
child: const SizedBox(
|
||||
width: double.infinity,
|
||||
child: ProgressBar()
|
||||
)
|
||||
);
|
||||
}
|
||||
),
|
||||
actions: [
|
||||
FutureBuilder(
|
||||
future: nodeFuture,
|
||||
builder: (builder, snapshot) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button(
|
||||
onPressed: () => Navigator.of(context).pop(snapshot.hasData && !snapshot.hasError),
|
||||
child: Text(!snapshot.hasData && !snapshot.hasError ? 'Stop' : 'Close'),
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
|
||||
return result != null && result;
|
||||
Future<HttpServer> startRemoteServer(Uri uri) async {
|
||||
return await serve(proxyHandler(uri), "127.0.0.1", 3551);
|
||||
}
|
||||
|
||||
Future<bool> _showAlreadyBindPortWarning(BuildContext context) async {
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
content: const Text(
|
||||
"Port 3551 is already in use, do you want to kill the associated process?",
|
||||
textAlign: TextAlign.center),
|
||||
actions: [
|
||||
Button(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Close'),
|
||||
),
|
||||
FilledButton(
|
||||
child: const Text('Kill'),
|
||||
onPressed: () => Navigator.of(context).pop(true)),
|
||||
],
|
||||
)) ??
|
||||
false;
|
||||
class ServerResult {
|
||||
final Future<Uri?>? uri;
|
||||
final Object? error;
|
||||
final StackTrace? stackTrace;
|
||||
final ServerResultType type;
|
||||
|
||||
ServerResult({this.uri, this.error, this.stackTrace, required this.type});
|
||||
}
|
||||
|
||||
enum ServerResultType {
|
||||
missingHostError,
|
||||
missingPortError,
|
||||
illegalPortError,
|
||||
cannotPingServer,
|
||||
portTakenError,
|
||||
serverDownloadRequiredError,
|
||||
canStart,
|
||||
started,
|
||||
unknownError,
|
||||
stopped
|
||||
}
|
||||
Reference in New Issue
Block a user