mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5e41ed646 | ||
|
|
9e20ec86e6 | ||
|
|
004fc41292 | ||
|
|
ee466df630 | ||
|
|
fdb1d694d9 | ||
|
|
0cfa4af236 | ||
|
|
d42946c44b | ||
|
|
0a59a32c1b |
@@ -1,7 +1,7 @@
|
|||||||

|

|
||||||
|
|
||||||
GUI and CLI Launcher for [Project Reboot](https://github.com/Milxnor/Project-Reboot-3.0/)
|
GUI and CLI Launcher for [Project Reboot](https://github.com/Milxnor/Project-Reboot-3.0/)
|
||||||
Join our discord at https://discord.gg/reboot
|
Join our [Discord](https://discord.gg/rebootmp)
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
Builds are stored on a Cloudflare R2 instance at `https://builds.rebootfn.org/versions.json`.
|
Builds are stored on a Cloudflare R2 instance at `https://builds.rebootfn.org/versions.json`.
|
||||||
If you want to move them to another AWS-compatible object storage, run:
|
If you want to move them to another AWS-compatible object storage, run:
|
||||||
```
|
```
|
||||||
move.ps1
|
python move.py
|
||||||
```
|
```
|
||||||
and provide the required parameters.
|
and provide the required parameters.
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
param(
|
|
||||||
[Parameter(Mandatory=$true)]
|
|
||||||
[string]$UrlListPath, # Path to a text file with one URL per line
|
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
|
||||||
[string]$BucketName, # Name of the R2 bucket
|
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
|
||||||
[string]$AccessKey, # Your R2 access key
|
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
|
||||||
[string]$SecretKey, # Your R2 secret key
|
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
|
||||||
[string]$EndPointURL, # Your R2 endpoint URL, e.g. https://<account_id>.r2.cloudflarestorage.com
|
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
|
||||||
[int]$MaxConcurrentConnections = 16, # Number of concurrent connections for each file download
|
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
|
||||||
[int]$SplitCount = 16, # Number of segments to split the download into
|
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
|
||||||
[string]$AwsRegion = "auto" # Region; often "auto" works for R2, but can be set if needed
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set AWS environment variables for this session
|
|
||||||
$Env:AWS_ACCESS_KEY_ID = $AccessKey
|
|
||||||
$Env:AWS_SECRET_ACCESS_KEY = $SecretKey
|
|
||||||
$Env:AWS_REGION = $AwsRegion # If required, or leave as "auto"
|
|
||||||
|
|
||||||
# Read all URLs from file
|
|
||||||
$Urls = Get-Content $UrlListPath | Where-Object { $_ -and $_. Trim() -ne "" }
|
|
||||||
|
|
||||||
# Ensure aria2 is available
|
|
||||||
if (-not (Get-Command aria2c -ErrorAction SilentlyContinue)) {
|
|
||||||
Write-Error "aria2c not found in PATH. Please install aria2."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ensure aws CLI is available
|
|
||||||
if (-not (Get-Command aws -ErrorAction SilentlyContinue)) {
|
|
||||||
Write-Error "aws CLI not found in PATH. Please install AWS CLI."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function Process-Url {
|
|
||||||
param(
|
|
||||||
[string]$Url,
|
|
||||||
[string]$BucketName,
|
|
||||||
[string]$EndPointURL,
|
|
||||||
[int]$MaxConcurrentConnections,
|
|
||||||
[int]$SplitCount
|
|
||||||
)
|
|
||||||
|
|
||||||
# Extract the filename from the URL
|
|
||||||
$FileName = Split-Path -Leaf $Url
|
|
||||||
|
|
||||||
try {
|
|
||||||
Write-Host "Downloading: $Url"
|
|
||||||
|
|
||||||
# Use aria2c to download with multiple connections
|
|
||||||
& aria2c `
|
|
||||||
--max-connection-per-server=$MaxConcurrentConnections `
|
|
||||||
--split=$SplitCount `
|
|
||||||
--out=$FileName `
|
|
||||||
--check-certificate=false `
|
|
||||||
--header="Cookie: _c_t_c=1" `
|
|
||||||
$Url
|
|
||||||
|
|
||||||
if (!(Test-Path $FileName)) {
|
|
||||||
Write-Host "Failed to download $Url"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Uploading $FileName to R2 bucket: $BucketName"
|
|
||||||
& aws s3 cp $FileName "s3://$BucketName/$FileName" --endpoint-url $EndPointURL
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Write-Host "Failed to upload $FileName to R2"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Upload successful. Deleting local file: $FileName"
|
|
||||||
Remove-Item $FileName -Force
|
|
||||||
|
|
||||||
Write-Host "Completed processing of $FileName."
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
Write-Host "Error processing $Url"
|
|
||||||
Write-Host $_
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Process each URL sequentially here. If you'd like to run multiple URLs in parallel,
|
|
||||||
# you could replace the foreach loop with a ForEach-Object -Parallel block.
|
|
||||||
foreach ($Url in $Urls) {
|
|
||||||
Process-Url -Url $Url -BucketName $BucketName -EndPointURL $EndPointURL -MaxConcurrentConnections $MaxConcurrentConnections -SplitCount $SplitCount
|
|
||||||
}
|
|
||||||
66
archive/move.py
Normal file
66
archive/move.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import boto3
|
||||||
|
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
def upload_url_to_s3(s3_client, bucket_name, url, object_key):
|
||||||
|
response = requests.get(url, stream=True, verify=False, headers={"Cookie": "_c_t_c=1"})
|
||||||
|
response.raise_for_status()
|
||||||
|
s3_client.upload_fileobj(response.raw, bucket_name, object_key)
|
||||||
|
return url, object_key
|
||||||
|
|
||||||
|
def derive_key_from_url(url, prefix=None):
|
||||||
|
parsed = urlparse(url)
|
||||||
|
filename = os.path.basename(parsed.path)
|
||||||
|
if prefix:
|
||||||
|
return f"{prefix}/{filename}"
|
||||||
|
else:
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Upload multiple URLs from versions.txt to an S3 bucket concurrently.")
|
||||||
|
parser.add_argument('--bucket', required=True, help="Name of the S3 bucket.")
|
||||||
|
parser.add_argument('--concurrency', required=True, type=int, help="Number of concurrent uploads.")
|
||||||
|
parser.add_argument('--versions-file', default='versions.txt', help="File containing one URL per line.")
|
||||||
|
parser.add_argument('--access-key', required=True, help="AWS Access Key ID.")
|
||||||
|
parser.add_argument('--secret-key', required=True, help="AWS Secret Access Key.")
|
||||||
|
parser.add_argument('--endpoint-url', required=True, help="Custom endpoint URL for S3 or S3-compatible storage.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
bucket_name = args.bucket
|
||||||
|
concurrency = args.concurrency
|
||||||
|
versions_file = args.versions_file
|
||||||
|
access_key = args.access_key
|
||||||
|
secret_key = args.secret_key
|
||||||
|
endpoint_url = args.endpoint_url
|
||||||
|
|
||||||
|
with open(versions_file, 'r') as f:
|
||||||
|
urls = [line.strip() for line in f if line.strip()]
|
||||||
|
|
||||||
|
print(f"Uploading {len(urls)} files...")
|
||||||
|
s3_params = {}
|
||||||
|
if access_key and secret_key:
|
||||||
|
s3_params['aws_access_key_id'] = access_key
|
||||||
|
s3_params['aws_secret_access_key'] = secret_key
|
||||||
|
if endpoint_url:
|
||||||
|
s3_params['endpoint_url'] = endpoint_url
|
||||||
|
|
||||||
|
s3 = boto3.client('s3', **s3_params)
|
||||||
|
|
||||||
|
futures = []
|
||||||
|
with ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||||
|
for url in urls:
|
||||||
|
object_key = derive_key_from_url(url)
|
||||||
|
futures.append(executor.submit(upload_url_to_s3, s3, bucket_name, url, object_key))
|
||||||
|
for future in as_completed(futures):
|
||||||
|
try:
|
||||||
|
uploaded_url, uploaded_key = future.result()
|
||||||
|
print(f"Uploaded: {uploaded_url}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error uploading: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -82,4 +82,4 @@ https://builds.rebootfn.org/17.30.zip
|
|||||||
https://builds.rebootfn.org/17.50.zip
|
https://builds.rebootfn.org/17.50.zip
|
||||||
https://builds.rebootfn.org/18.40.zip
|
https://builds.rebootfn.org/18.40.zip
|
||||||
https://builds.rebootfn.org/19.10.rar
|
https://builds.rebootfn.org/19.10.rar
|
||||||
https://builds.rebootfn.org/20.40.zip"
|
https://builds.rebootfn.org/20.40.zip
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
enum InjectableDll {
|
enum InjectableDll {
|
||||||
console,
|
console,
|
||||||
starfall,
|
auth,
|
||||||
reboot,
|
gameServer,
|
||||||
|
memoryLeak
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InjectableDllVersionAware on InjectableDll {
|
extension InjectableDllVersionAware on InjectableDll {
|
||||||
bool get isVersionDependent => this == InjectableDll.reboot;
|
bool get isVersionDependent => this == InjectableDll.gameServer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ Future<Uri?> pingBackend(String host, int port, [bool https=false]) async {
|
|||||||
await request.close().timeout(const Duration(seconds: 10));
|
await request.close().timeout(const Duration(seconds: 10));
|
||||||
log("[BACKEND] Ping successful");
|
log("[BACKEND] Ping successful");
|
||||||
return uri;
|
return uri;
|
||||||
}catch(error){
|
}catch(error) {
|
||||||
log("[BACKEND] Cannot ping backend: $error");
|
log("[BACKEND] Cannot ping backend: $error");
|
||||||
return https || declaredScheme != null || isLocalHost(host) ? null : await pingBackend(host, port, true);
|
return https || declaredScheme != null || isLocalHost(host) ? null : await pingBackend(host, port, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,17 +134,13 @@ Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _startAriaServer() async {
|
Future<void> _startAriaServer() async {
|
||||||
final running = await _isAriaRunning();
|
await stopDownloadServer();
|
||||||
if(running) {
|
|
||||||
await killProcessByPort(_ariaPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
final aria2c = File("${assetsDirectory.path}\\build\\aria2c.exe");
|
final aria2c = File("${assetsDirectory.path}\\build\\aria2c.exe");
|
||||||
if(!aria2c.existsSync()) {
|
if(!aria2c.existsSync()) {
|
||||||
throw "Missing aria2c.exe";
|
throw "Missing aria2c.exe";
|
||||||
}
|
}
|
||||||
|
|
||||||
await startProcess(
|
final process = await startProcess(
|
||||||
executable: aria2c,
|
executable: aria2c,
|
||||||
args: [
|
args: [
|
||||||
"--max-connection-per-server=${Platform.numberOfProcessors}",
|
"--max-connection-per-server=${Platform.numberOfProcessors}",
|
||||||
@@ -153,10 +149,14 @@ Future<void> _startAriaServer() async {
|
|||||||
"--rpc-listen-all=true",
|
"--rpc-listen-all=true",
|
||||||
"--rpc-allow-origin-all",
|
"--rpc-allow-origin-all",
|
||||||
"--rpc-secret=$_ariaSecret",
|
"--rpc-secret=$_ariaSecret",
|
||||||
"--rpc-listen-port=$_ariaPort"
|
"--rpc-listen-port=$_ariaPort",
|
||||||
|
"--file-allocation=none"
|
||||||
],
|
],
|
||||||
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++) {
|
for(var i = 0; i < _ariaMaxSpawnTime.inSeconds; i++) {
|
||||||
if(await _isAriaRunning()) {
|
if(await _isAriaRunning()) {
|
||||||
return;
|
return;
|
||||||
@@ -177,8 +177,8 @@ Future<bool> _isAriaRunning() async {
|
|||||||
"token:${_ariaSecret}"
|
"token:${_ariaSecret}"
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
await http.post(_ariaEndpoint, body: jsonEncode(statusRequest));
|
final response = await http.post(_ariaEndpoint, body: jsonEncode(statusRequest));
|
||||||
return true;
|
return response.statusCode == 200;
|
||||||
}catch(_) {
|
}catch(_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -227,11 +227,16 @@ Future<void> _stopAriaDownload(String downloadId) async {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
await http.post(_ariaEndpoint, body: jsonEncode(addDownloadRequest));
|
await http.post(_ariaEndpoint, body: jsonEncode(addDownloadRequest));
|
||||||
|
stopDownloadServer();
|
||||||
}catch(error) {
|
}catch(error) {
|
||||||
throw "Stop failed (${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 {
|
Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async {
|
||||||
Process? process;
|
Process? process;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:http/http.dart' as http;
|
|||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
|
|
||||||
bool _watcher = false;
|
|
||||||
final File rebootBeforeS20DllFile = File("${dllsDirectory.path}\\reboot.dll");
|
final File rebootBeforeS20DllFile = File("${dllsDirectory.path}\\reboot.dll");
|
||||||
final File rebootAboveS20DllFile = File("${dllsDirectory.path}\\rebootS20.dll");
|
final File rebootAboveS20DllFile = File("${dllsDirectory.path}\\rebootS20.dll");
|
||||||
const String kRebootBelowS20DownloadUrl =
|
const String kRebootBelowS20DownloadUrl =
|
||||||
@@ -20,7 +19,22 @@ Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force =
|
|||||||
return force || !exists || (hours > 0 && lastUpdate != null && now.difference(lastUpdate).inHours > hours);
|
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 = "starfall.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"));
|
final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name"));
|
||||||
if(response.statusCode != 200) {
|
if(response.statusCode != 200) {
|
||||||
throw Exception("Cannot download $name: status code ${response.statusCode}");
|
throw Exception("Cannot download $name: status code ${response.statusCode}");
|
||||||
@@ -56,17 +70,4 @@ Future<DateTime?> _getLastUpdate(int? lastUpdateMs) async {
|
|||||||
return lastUpdateMs != null
|
return lastUpdateMs != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(lastUpdateMs)
|
? DateTime.fromMillisecondsSinceEpoch(lastUpdateMs)
|
||||||
: null;
|
: 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
gui/dependencies/dlls/memory.dll
Normal file
BIN
gui/dependencies/dlls/memory.dll
Normal file
Binary file not shown.
@@ -216,7 +216,6 @@
|
|||||||
"downloadedVersion": "The download was completed successfully!",
|
"downloadedVersion": "The download was completed successfully!",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"downloading": "Downloading...",
|
"downloading": "Downloading...",
|
||||||
"allocatingSpace": "Allocating disk space...",
|
|
||||||
"startingDownload": "Starting download...",
|
"startingDownload": "Starting download...",
|
||||||
"extracting": "Extracting...",
|
"extracting": "Extracting...",
|
||||||
"buildProgress": "{progress}%",
|
"buildProgress": "{progress}%",
|
||||||
@@ -237,7 +236,7 @@
|
|||||||
"startGame": "Start fortnite",
|
"startGame": "Start fortnite",
|
||||||
"stopGame": "Close fortnite",
|
"stopGame": "Close fortnite",
|
||||||
"waitingForGameServer": "Waiting for the game server to boot up...",
|
"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",
|
"gameServerStartLocalWarning": "The game server was started successfully, but other players can't join",
|
||||||
"gameServerStarted": "The game server was started successfully",
|
"gameServerStarted": "The game server was started successfully",
|
||||||
"gameClientStarted": "The game client 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/game_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/hosting_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/controller/settings_controller.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/error.dart';
|
import 'package:reboot_launcher/src/widget/message/error.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/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:reboot_launcher/src/util/url_protocol.dart';
|
import 'package:reboot_launcher/src/util/url_protocol.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
@@ -82,9 +82,7 @@ Future<void> _startApp() async {
|
|||||||
errors.add(uncaughtError);
|
errors.add(uncaughtError);
|
||||||
} finally{
|
} finally{
|
||||||
log("[APP] Started applications with errors: $errors");
|
log("[APP] Started applications with errors: $errors");
|
||||||
runApp(RebootApplication(
|
runApp(RebootApplication(errors: errors));
|
||||||
errors: errors,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,11 +170,11 @@ Future<void> _initWindow() async {
|
|||||||
await windowManager.setAlignment(Alignment.center);
|
await windowManager.setAlignment(Alignment.center);
|
||||||
}
|
}
|
||||||
await windowManager.setPreventClose(true);
|
await windowManager.setPreventClose(true);
|
||||||
|
await windowManager.setResizable(true);
|
||||||
if(isWin11) {
|
if(isWin11) {
|
||||||
await Window.setEffect(
|
await Window.setEffect(
|
||||||
effect: WindowEffect.acrylic,
|
effect: WindowEffect.acrylic,
|
||||||
color: Colors.transparent,
|
color: Colors.green,
|
||||||
dark: isDarkMode
|
dark: isDarkMode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -232,7 +230,6 @@ Future<List<Object>> _initStorage() async {
|
|||||||
errors.add(error);
|
errors.add(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +251,11 @@ class _RebootApplicationState extends State<RebootApplication> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleErrors(List<Object?> errors) {
|
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
|
@override
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
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:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/main.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/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 {
|
class BackendController extends GetxController {
|
||||||
static const String storageName = "v2_backend_storage";
|
static const String storageName = "v2_backend_storage";
|
||||||
@@ -22,6 +34,7 @@ class BackendController extends GetxController {
|
|||||||
late final Rx<PhysicalKeyboardKey> consoleKey;
|
late final Rx<PhysicalKeyboardKey> consoleKey;
|
||||||
late final RxBool started;
|
late final RxBool started;
|
||||||
late final RxBool detached;
|
late final RxBool detached;
|
||||||
|
late final List<InfoBarEntry> _infoBars;
|
||||||
StreamSubscription? worker;
|
StreamSubscription? worker;
|
||||||
int? embeddedProcessPid;
|
int? embeddedProcessPid;
|
||||||
HttpServer? localServer;
|
HttpServer? localServer;
|
||||||
@@ -70,31 +83,30 @@ class BackendController extends GetxController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
gameServerAddressFocusNode = FocusNode();
|
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);
|
_writeConsoleKey(consoleKey.value);
|
||||||
consoleKey.listen((newValue) {
|
consoleKey.listen((newValue) {
|
||||||
_storage?.write("console_key", newValue.usbHidUsage);
|
_storage?.write("console_key", newValue.usbHidUsage);
|
||||||
_writeConsoleKey(newValue);
|
_writeConsoleKey(newValue);
|
||||||
});
|
});
|
||||||
}
|
_infoBars = [];
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _writeConsoleKey(PhysicalKeyboardKey keyValue) async {
|
Future<void> _writeConsoleKey(PhysicalKeyboardKey keyValue) async {
|
||||||
@@ -103,6 +115,21 @@ class BackendController extends GetxController {
|
|||||||
await defaultInput.writeAsString("[/Script/Engine.InputSettings]\n+ConsoleKeys=Tilde\n+ConsoleKeys=${keyValue.unrealEngineName}", flush: true);
|
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() {
|
void joinLocalhost() {
|
||||||
gameServerAddress.text = kDefaultGameServerHost;
|
gameServerAddress.text = kDefaultGameServerHost;
|
||||||
}
|
}
|
||||||
@@ -121,22 +148,44 @@ class BackendController extends GetxController {
|
|||||||
detached.value = false;
|
detached.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _readHost() {
|
Future<bool> toggleInteractive() async {
|
||||||
String? value = _storage?.read("${type.value.name}_host");
|
_cancel();
|
||||||
if (value != null && value.isNotEmpty) {
|
final stream = started.value ? stop() : start(
|
||||||
return value;
|
onExit: () {
|
||||||
}
|
_cancel();
|
||||||
|
_showRebootInfoBar(
|
||||||
|
translations.backendProcessError,
|
||||||
|
severity: InfoBarSeverity.error
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onError: (errorMessage) {
|
||||||
|
_cancel();
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (type.value != ServerType.remote) {
|
return await completer.future;
|
||||||
return kDefaultBackendHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _readPort() =>
|
|
||||||
_storage?.read("${type.value.name}_port") ?? kDefaultBackendPort.toString();
|
|
||||||
|
|
||||||
Stream<ServerResult> start({required void Function() onExit, required void Function(String) onError}) async* {
|
Stream<ServerResult> start({required void Function() onExit, required void Function(String) onError}) async* {
|
||||||
try {
|
try {
|
||||||
if(started.value) {
|
if(started.value) {
|
||||||
@@ -286,14 +335,267 @@ class BackendController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<ServerResult> toggle({required void Function() onExit, required void Function(String) onError}) async* {
|
void _cancel() {
|
||||||
if(started()) {
|
worker?.cancel(); // Do not await or it will hang
|
||||||
yield* stop();
|
_infoBars.forEach((infoBar) => infoBar.close());
|
||||||
}else {
|
_infoBars.clear();
|
||||||
yield* start(
|
}
|
||||||
onExit: onExit,
|
|
||||||
onError: onError
|
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> joinServer(String uuid, FortniteServer server) async {
|
||||||
|
if(!kDebugMode && uuid == server.id) {
|
||||||
|
_showRebootInfoBar(
|
||||||
|
translations.joinSelfServer,
|
||||||
|
duration: infoBarLongDuration,
|
||||||
|
severity: InfoBarSeverity.error
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final version = Get.find<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,10 @@ import 'package:get_storage/get_storage.dart';
|
|||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/main.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:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
class DllController extends GetxController {
|
class DllController extends GetxController {
|
||||||
static const String storageName = "v2_dll_storage";
|
static const String storageName = "v2_dll_storage";
|
||||||
@@ -19,6 +20,7 @@ class DllController extends GetxController {
|
|||||||
late final TextEditingController gameServerDll;
|
late final TextEditingController gameServerDll;
|
||||||
late final TextEditingController unrealEngineConsoleDll;
|
late final TextEditingController unrealEngineConsoleDll;
|
||||||
late final TextEditingController backendDll;
|
late final TextEditingController backendDll;
|
||||||
|
late final TextEditingController memoryLeakDll;
|
||||||
late final TextEditingController gameServerPort;
|
late final TextEditingController gameServerPort;
|
||||||
late final Rx<UpdateTimer> timer;
|
late final Rx<UpdateTimer> timer;
|
||||||
late final TextEditingController beforeS20Mirror;
|
late final TextEditingController beforeS20Mirror;
|
||||||
@@ -27,13 +29,13 @@ class DllController extends GetxController {
|
|||||||
late final RxnInt timestamp;
|
late final RxnInt timestamp;
|
||||||
late final Rx<UpdateStatus> status;
|
late final Rx<UpdateStatus> status;
|
||||||
InfoBarEntry? infoBarEntry;
|
InfoBarEntry? infoBarEntry;
|
||||||
Future<bool>? _updater;
|
|
||||||
|
|
||||||
DllController() {
|
DllController() {
|
||||||
_storage = appWithNoStorage ? null : GetStorage(storageName);
|
_storage = appWithNoStorage ? null : GetStorage(storageName);
|
||||||
gameServerDll = _createController("game_server", InjectableDll.reboot);
|
gameServerDll = _createController("game_server", InjectableDll.gameServer);
|
||||||
unrealEngineConsoleDll = _createController("unreal_engine_console", InjectableDll.console);
|
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 = TextEditingController(text: _storage?.read("game_server_port") ?? kDefaultGameServerPort);
|
||||||
gameServerPort.addListener(() => _storage?.write("game_server_port", gameServerPort.text));
|
gameServerPort.addListener(() => _storage?.write("game_server_port", gameServerPort.text));
|
||||||
final timerIndex = _storage?.read("timer");
|
final timerIndex = _storage?.read("timer");
|
||||||
@@ -57,9 +59,9 @@ class DllController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void resetGame() {
|
void resetGame() {
|
||||||
gameServerDll.text = getDefaultDllPath(InjectableDll.reboot);
|
gameServerDll.text = getDefaultDllPath(InjectableDll.gameServer);
|
||||||
unrealEngineConsoleDll.text = getDefaultDllPath(InjectableDll.console);
|
unrealEngineConsoleDll.text = getDefaultDllPath(InjectableDll.console);
|
||||||
backendDll.text = getDefaultDllPath(InjectableDll.starfall);
|
backendDll.text = getDefaultDllPath(InjectableDll.auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetServer() {
|
void resetServer() {
|
||||||
@@ -74,16 +76,6 @@ class DllController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updateGameServerDll({bool force = false, bool silent = false}) async {
|
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 {
|
|
||||||
try {
|
try {
|
||||||
if(customGameServer.value) {
|
if(customGameServer.value) {
|
||||||
status.value = UpdateStatus.success;
|
status.value = UpdateStatus.success;
|
||||||
@@ -148,15 +140,13 @@ class DllController extends GetxController {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}finally {
|
|
||||||
_updater = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(File, bool) getInjectableData(Version version, InjectableDll dll) {
|
(File, bool) getInjectableData(Version version, InjectableDll dll) {
|
||||||
final defaultPath = canonicalize(getDefaultDllPath(dll));
|
final defaultPath = canonicalize(getDefaultDllPath(dll));
|
||||||
switch(dll){
|
switch(dll){
|
||||||
case InjectableDll.reboot:
|
case InjectableDll.gameServer:
|
||||||
if(customGameServer.value) {
|
if(customGameServer.value) {
|
||||||
return (File(gameServerDll.text), true);
|
return (File(gameServerDll.text), true);
|
||||||
}
|
}
|
||||||
@@ -165,25 +155,47 @@ class DllController extends GetxController {
|
|||||||
case InjectableDll.console:
|
case InjectableDll.console:
|
||||||
final ue4ConsoleFile = File(unrealEngineConsoleDll.text);
|
final ue4ConsoleFile = File(unrealEngineConsoleDll.text);
|
||||||
return (ue4ConsoleFile, canonicalize(ue4ConsoleFile.path) != defaultPath);
|
return (ue4ConsoleFile, canonicalize(ue4ConsoleFile.path) != defaultPath);
|
||||||
case InjectableDll.starfall:
|
case InjectableDll.auth:
|
||||||
final backendFile = File(backendDll.text);
|
final backendFile = File(backendDll.text);
|
||||||
return (backendFile, canonicalize(backendFile.path) != defaultPath);
|
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 gameServerDll;
|
||||||
|
case InjectableDll.memoryLeak:
|
||||||
|
return memoryLeakDll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> downloadCriticalDllInteractive(String filePath, {bool silent = false, bool force = false}) async {
|
String getDefaultDllPath(InjectableDll dll) {
|
||||||
log("[DLL] Asking for $filePath(silent: $silent)");
|
switch(dll) {
|
||||||
final fileName = basename(filePath).toLowerCase();
|
case InjectableDll.console:
|
||||||
log("[DLL] File name: $fileName");
|
return "${dllsDirectory.path}\\console.dll";
|
||||||
|
case InjectableDll.auth:
|
||||||
|
return "${dllsDirectory.path}\\starfall.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;
|
InfoBarEntry? entry;
|
||||||
try {
|
try {
|
||||||
if (fileName.contains("reboot")) {
|
if (dll == InjectableDll.gameServer) {
|
||||||
log("[DLL] Downloading reboot.dll...");
|
return await updateGameServerDll(silent: silent);
|
||||||
return await updateGameServerDll(
|
|
||||||
silent: silent
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!force && File(filePath).existsSync()) {
|
if(!force && File(filePath).existsSync()) {
|
||||||
@@ -199,7 +211,7 @@ class DllController extends GetxController {
|
|||||||
duration: null
|
duration: null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await downloadCriticalDll(fileName, filePath);
|
await downloadDependency(dll, filePath);
|
||||||
entry?.close();
|
entry?.close();
|
||||||
if(!silent) {
|
if(!silent) {
|
||||||
entry = await showRebootInfoBar(
|
entry = await showRebootInfoBar(
|
||||||
@@ -218,13 +230,13 @@ class DllController extends GetxController {
|
|||||||
error = error.toLowerCase();
|
error = error.toLowerCase();
|
||||||
final completer = Completer();
|
final completer = Completer();
|
||||||
await showRebootInfoBar(
|
await showRebootInfoBar(
|
||||||
translations.downloadDllError(error.toString(), fileName),
|
translations.downloadDllError(error.toString(), dll.name),
|
||||||
duration: infoBarLongDuration,
|
duration: infoBarLongDuration,
|
||||||
severity: InfoBarSeverity.error,
|
severity: InfoBarSeverity.error,
|
||||||
onDismissed: () => completer.complete(null),
|
onDismissed: () => completer.complete(null),
|
||||||
action: Button(
|
action: Button(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await downloadCriticalDllInteractive(filePath);
|
await download(dll, filePath, silent: silent, force: force);
|
||||||
completer.complete(null);
|
completer.complete(null);
|
||||||
},
|
},
|
||||||
child: Text(translations.downloadDllRetry),
|
child: Text(translations.downloadDllRetry),
|
||||||
@@ -234,6 +246,32 @@ class DllController extends GetxController {
|
|||||||
return false;
|
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 {
|
extension _UpdateTimerExtension on UpdateTimer {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:get_storage/get_storage.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/main.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:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:clipboard/clipboard.dart';
|
import 'package:clipboard/clipboard.dart';
|
||||||
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
|
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
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/page/pages.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.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:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/rendering.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';
|
import 'package:reboot_launcher/src/page/pages.dart';
|
||||||
|
|
||||||
typedef WidgetBuilder = Widget Function(BuildContext, void Function());
|
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)
|
// 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)
|
// 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 exclusionPosition = exclusion.localToGlobal(Offset.zero);
|
||||||
final exclusionSize = Rect.fromLTRB(
|
final exclusionSize = Rect.fromLTRB(
|
||||||
exclusionPosition.dx,
|
exclusionPosition.dx,
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/onboard.dart';
|
import 'package:reboot_launcher/src/widget/message/onboard.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
|
|
||||||
abstract class RebootPage extends StatefulWidget {
|
abstract class RebootPage extends StatefulWidget {
|
||||||
@@ -3,16 +3,16 @@ import 'dart:collection';
|
|||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/backend_page.dart';
|
import 'package:reboot_launcher/src/widget/page/backend_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/browser_page.dart';
|
import 'package:reboot_launcher/src/widget/page/browser_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/host_page.dart';
|
import 'package:reboot_launcher/src/widget/page/host_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/info_page.dart';
|
import 'package:reboot_launcher/src/widget/page/info_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/play_page.dart';
|
import 'package:reboot_launcher/src/widget/page/play_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/settings_page.dart';
|
import 'package:reboot_launcher/src/widget/page/settings_page.dart';
|
||||||
import 'package:reboot_launcher/src/widget/info_bar_area.dart';
|
import 'package:reboot_launcher/src/widget/window/info_bar_area.dart';
|
||||||
|
|
||||||
final StreamController<void> pagesController = StreamController.broadcast();
|
final StreamController<void> pagesController = StreamController.broadcast();
|
||||||
bool hitBack = false;
|
bool hitBack = false;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
|
|
||||||
@@ -9,22 +10,24 @@ const Duration _timeout = Duration(seconds: 5);
|
|||||||
Completer<bool> pingGameServerOrTimeout(String address, Duration timeout) {
|
Completer<bool> pingGameServerOrTimeout(String address, Duration timeout) {
|
||||||
final completer = Completer<bool>();
|
final completer = Completer<bool>();
|
||||||
final start = DateTime.now();
|
final start = DateTime.now();
|
||||||
(() async {
|
_pingGameServerOrTimeout(completer, start, timeout, address);
|
||||||
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);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
return completer;
|
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 {
|
Future<bool> pingGameServer(String address) async {
|
||||||
final split = address.split(":");
|
final split = address.split(":");
|
||||||
var hostname = split[0];
|
var hostname = split[0];
|
||||||
|
|||||||
@@ -491,4 +491,58 @@ int _convertToHString(String string) {
|
|||||||
|
|
||||||
extension WindowManagerExtension on WindowManager {
|
extension WindowManagerExtension on WindowManager {
|
||||||
Future<void> maximizeOrRestore() async => await windowManager.isMaximized() ? windowManager.restore() : windowManager.maximize();
|
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:get/get.dart';
|
||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
import 'package:reboot_launcher/src/widget/file/file_selector.dart';
|
||||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||||
|
|
||||||
const double _kButtonDimensions = 30;
|
const double _kButtonDimensions = 30;
|
||||||
const double _kButtonSpacing = 8;
|
const double _kButtonSpacing = 8;
|
||||||
@@ -3,9 +3,9 @@ import 'package:get/get.dart';
|
|||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_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/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/profile.dart';
|
import 'package:reboot_launcher/src/widget/message/profile.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/page/pages.dart';
|
import 'package:reboot_launcher/src/page/pages.dart';
|
||||||
|
|
||||||
class ProfileWidget extends StatefulWidget {
|
class ProfileWidget extends StatefulWidget {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
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:reboot_launcher/src/page/pages.dart';
|
||||||
import 'package:skeletons/skeletons.dart';
|
import 'package:skeletons/skeletons.dart';
|
||||||
|
|
||||||
@@ -80,15 +80,19 @@ class SettingTileState extends State<SettingTile> {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
widget.icon,
|
widget.icon,
|
||||||
|
|
||||||
const SizedBox(width: 16.0),
|
const SizedBox(width: 16.0),
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Expanded(
|
||||||
children: [
|
child: Column(
|
||||||
widget.title == null ? _skeletonTitle : widget.title!,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
widget.subtitle == null ? _skeletonSubtitle : widget.subtitle!,
|
children: [
|
||||||
],
|
widget.title == null ? _skeletonTitle : widget.title!,
|
||||||
|
widget.subtitle == null ? _skeletonSubtitle : widget.subtitle!,
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
_trailing
|
_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/dll_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_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/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
import 'package:reboot_launcher/src/messenger/dialog.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/messenger/implementation/server.dart';
|
|
||||||
import 'package:reboot_launcher/src/page/pages.dart';
|
import 'package:reboot_launcher/src/page/pages.dart';
|
||||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
@@ -242,7 +241,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}else{
|
}else{
|
||||||
_gameController.instance.value = instance;
|
_gameController.instance.value = instance;
|
||||||
}
|
}
|
||||||
await _injectOrShowError(InjectableDll.starfall, host);
|
await _injectOrShowError(InjectableDll.auth, host);
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Finished creating game instance");
|
log("[${host ? 'HOST' : 'GAME'}] Finished creating game instance");
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -398,6 +397,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
if(instance != null && !instance.launched) {
|
if(instance != null && !instance.launched) {
|
||||||
instance.launched = true;
|
instance.launched = true;
|
||||||
instance.tokenError = false;
|
instance.tokenError = false;
|
||||||
|
await _injectOrShowError(InjectableDll.memoryLeak, host);
|
||||||
if(!host){
|
if(!host){
|
||||||
await _injectOrShowError(InjectableDll.console, host);
|
await _injectOrShowError(InjectableDll.console, host);
|
||||||
_onGameClientInjected();
|
_onGameClientInjected();
|
||||||
@@ -406,7 +406,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
if(gameServerPort != null) {
|
if(gameServerPort != null) {
|
||||||
await killProcessByPort(gameServerPort);
|
await killProcessByPort(gameServerPort);
|
||||||
}
|
}
|
||||||
await _injectOrShowError(InjectableDll.reboot, host);
|
await _injectOrShowError(InjectableDll.gameServer, host);
|
||||||
_onGameServerInjected();
|
_onGameServerInjected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,7 +492,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
|
|
||||||
final pingOperation = pingGameServerOrTimeout(
|
final pingOperation = pingGameServerOrTimeout(
|
||||||
"$publicIp:$gameServerPort",
|
"$publicIp:$gameServerPort",
|
||||||
const Duration(days: 365)
|
const Duration(days: 1)
|
||||||
);
|
);
|
||||||
this._pingOperation = pingOperation;
|
this._pingOperation = pingOperation;
|
||||||
_gameServerInfoBar = showRebootInfoBar(
|
_gameServerInfoBar = showRebootInfoBar(
|
||||||
@@ -524,7 +524,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
await _operation?.cancel();
|
await _operation?.cancel();
|
||||||
_operation = null;
|
_operation = null;
|
||||||
_backendController.cancelInteractive();
|
_backendController.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
host = host ?? widget.host;
|
host = host ?? widget.host;
|
||||||
@@ -712,7 +712,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log("[${host ? 'HOST' : 'GAME'}] Path does not exist, downloading critical dll again...");
|
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...");
|
log("[${host ? 'HOST' : 'GAME'}] Downloaded dll again, retrying check...");
|
||||||
return _getDllFileOrStop(version, injectable, host, true);
|
return _getDllFileOrStop(version, injectable, host, true);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
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';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
|
|
||||||
Future<void> showResetDialog(Function() onConfirm) => showRebootDialog(
|
Future<void> showResetDialog(Function() onConfirm) => showRebootDialog(
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
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';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
|
|
||||||
Future<void> showDllDeletedDialog(Function() onConfirm) => showRebootDialog(
|
Future<void> showDllDeletedDialog(Function() onConfirm) => showRebootDialog(
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:reboot_common/common.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/page/pages.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.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/game_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/hosting_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/controller/settings_controller.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/profile.dart';
|
import 'package:reboot_launcher/src/widget/message/profile.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/backend_page.dart';
|
import 'package:reboot_launcher/src/widget/page/backend_page.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/implementation/host_page.dart';
|
import 'package:reboot_launcher/src/widget/page/host_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/implementation/play_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/page/pages.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.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() {
|
void startOnboarding() {
|
||||||
final gameController = Get.find<GameController>();
|
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:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/material.dart' show Icons;
|
import 'package:flutter/material.dart' show Icons;
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
|
|
||||||
Future<bool> showProfileForm(BuildContext context, TextEditingController username, TextEditingController password) async{
|
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:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_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/dialog.dart';
|
||||||
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:reboot_launcher/src/util/types.dart';
|
import 'package:reboot_launcher/src/util/types.dart';
|
||||||
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
import 'package:reboot_launcher/src/widget/file/file_selector.dart';
|
||||||
import 'package:universal_disk_space/universal_disk_space.dart';
|
|
||||||
import 'package:windows_taskbar/windows_taskbar.dart';
|
import 'package:windows_taskbar/windows_taskbar.dart';
|
||||||
|
|
||||||
class AddVersionDialog extends StatefulWidget {
|
class AddVersionDialog extends StatefulWidget {
|
||||||
@@ -35,9 +35,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
|||||||
final Rxn<double> _progress = Rxn();
|
final Rxn<double> _progress = Rxn();
|
||||||
final RxInt _speed = RxInt(0);
|
final RxInt _speed = RxInt(0);
|
||||||
|
|
||||||
late DiskSpace _diskSpace;
|
|
||||||
late Future<List<FortniteBuild>> _fetchFuture;
|
late Future<List<FortniteBuild>> _fetchFuture;
|
||||||
late Future _diskFuture;
|
|
||||||
|
|
||||||
SendPort? _downloadPort;
|
SendPort? _downloadPort;
|
||||||
Object? _error;
|
Object? _error;
|
||||||
@@ -45,10 +43,10 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_fetchFuture = compute(fetchBuilds, null);
|
_fetchFuture = compute(fetchBuilds, null).then((value) {
|
||||||
_diskSpace = DiskSpace();
|
_updateFormDefaults();
|
||||||
_diskFuture = _diskSpace.scan()
|
return value;
|
||||||
.then((_) => _updateFormDefaults());
|
});
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +69,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
|||||||
switch(_status.value){
|
switch(_status.value){
|
||||||
case _DownloadStatus.form:
|
case _DownloadStatus.form:
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: Future.wait([_fetchFuture, _diskFuture]).then((_) async => await _fetchFuture),
|
future: _fetchFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _onDownloadError(snapshot.error, snapshot.stackTrace));
|
WidgetsBinding.instance.addPostFrameCallback((_) => _onDownloadError(snapshot.error, snapshot.stackTrace));
|
||||||
@@ -244,12 +242,12 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if(_progress.value != null && !_isAllocatingDiskSpace)
|
if(_progress.value != null)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
),
|
),
|
||||||
|
|
||||||
if(_progress.value != null && !_isAllocatingDiskSpace)
|
if(_progress.value != null)
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@@ -272,7 +270,7 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
|||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ProgressBar(value: _isAllocatingDiskSpace ? null : _progress.value?.toDouble())
|
child: ProgressBar(value: _progress.value?.toDouble())
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -291,15 +289,9 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
|||||||
return translations.startingDownload;
|
return translations.startingDownload;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_speed.value == 0) {
|
|
||||||
return translations.allocatingSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
return translations.downloading;
|
return translations.downloading;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _isAllocatingDiskSpace => _status.value == _DownloadStatus.downloading && _speed.value == 0;
|
|
||||||
|
|
||||||
Widget _buildFormBody(List<FortniteBuild> builds) {
|
Widget _buildFormBody(List<FortniteBuild> builds) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -450,16 +442,16 @@ class _AddVersionDialogState extends State<AddVersionDialog> {
|
|||||||
_build.value = null;
|
_build.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_source.value != _BuildSource.local && _diskSpace.disks.isNotEmpty) {
|
final disks = WindowsDisk.available();
|
||||||
await _fetchFuture;
|
if(_source.value != _BuildSource.local && disks.isNotEmpty) {
|
||||||
final bestDisk = _diskSpace.disks
|
final bestDisk = disks.reduce((first, second) => first.freeBytesAvailable > second.freeBytesAvailable ? first : second);
|
||||||
.reduce((first, second) => first.availableSpace > second.availableSpace ? first : second);
|
|
||||||
final build = _build.value;
|
final build = _build.value;
|
||||||
if(build == null){
|
if(build == null){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final pathText = "${bestDisk.devicePath}\\FortniteBuilds\\${build.version}";
|
print("${bestDisk.path}\\FortniteBuilds\\${build.version}");
|
||||||
|
final pathText = "${bestDisk.path}FortniteBuilds\\${build.version}";
|
||||||
_pathController.text = pathText;
|
_pathController.text = pathText;
|
||||||
_pathController.selection = TextSelection.collapsed(offset: pathText.length);
|
_pathController.selection = TextSelection.collapsed(offset: pathText.length);
|
||||||
}
|
}
|
||||||
@@ -5,16 +5,16 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/backend_controller.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/info_bar.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/data.dart';
|
import 'package:reboot_launcher/src/widget/message/data.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/util/keyboard.dart';
|
import 'package:reboot_launcher/src/util/keyboard.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.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/server_start_button.dart';
|
||||||
import 'package:reboot_launcher/src/widget/server_type_selector.dart';
|
import 'package:reboot_launcher/src/widget/server/server_type_selector.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.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
final GlobalKey<OverlayTargetState> backendTypeOverlayTargetKey = GlobalKey();
|
final GlobalKey<OverlayTargetState> backendTypeOverlayTargetKey = GlobalKey();
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
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/backend_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_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/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/server.dart';
|
import 'package:reboot_launcher/src/page/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
|
||||||
import 'package:reboot_launcher/src/util/translations.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 {
|
class BrowsePage extends RebootPage {
|
||||||
const BrowsePage({Key? key}) : super(key: key);
|
const BrowsePage({Key? key}) : super(key: key);
|
||||||
@@ -211,10 +209,18 @@ class _BrowsePageState extends RebootPageState<BrowsePage> {
|
|||||||
icon: Icon(
|
icon: Icon(
|
||||||
hasPassword ? FluentIcons.lock : FluentIcons.globe
|
hasPassword ? FluentIcons.lock : FluentIcons.globe
|
||||||
),
|
),
|
||||||
title: Text("${_formatName(entry)} • ${entry.author}"),
|
title: Text(
|
||||||
subtitle: Text("${_formatDescription(entry)} • ${_formatVersion(entry)}"),
|
"${_formatName(entry)} • ${entry.author}",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
"${_formatDescription(entry)} • ${_formatVersion(entry)}",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis
|
||||||
|
),
|
||||||
content: Button(
|
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),
|
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_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/backend_controller.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/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/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_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/dialog.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/messenger/abstract/overlay.dart';
|
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/dll.dart';
|
import 'package:reboot_launcher/src/widget/message/dll.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/server.dart';
|
import 'package:reboot_launcher/src/page/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/page_suggestion.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_suggestion.dart';
|
|
||||||
import 'package:reboot_launcher/src/page/pages.dart';
|
import 'package:reboot_launcher/src/page/pages.dart';
|
||||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||||
import 'package:reboot_launcher/src/util/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.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/window/info_bar_area.dart';
|
||||||
import 'package:reboot_launcher/src/widget/profile_tile.dart';
|
import 'package:reboot_launcher/src/widget/fluent/profile_tile.dart';
|
||||||
import 'package:reboot_launcher/src/widget/title_bar.dart';
|
|
||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ final GlobalKey<OverlayTargetState> profileOverlayKey = GlobalKey();
|
|||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
static const double kDefaultPadding = 12.0;
|
static const double kDefaultPadding = 12.0;
|
||||||
static const double kTitleBarHeight = 32;
|
|
||||||
|
|
||||||
const HomePage({Key? key}) : super(key: key);
|
const HomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@@ -43,6 +41,7 @@ class HomePage extends StatefulWidget {
|
|||||||
|
|
||||||
class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepAliveClientMixin {
|
class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepAliveClientMixin {
|
||||||
final BackendController _backendController = Get.find<BackendController>();
|
final BackendController _backendController = Get.find<BackendController>();
|
||||||
|
final GameController _gameController = Get.find<GameController>();
|
||||||
final HostingController _hostingController = Get.find<HostingController>();
|
final HostingController _hostingController = Get.find<HostingController>();
|
||||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||||
final DllController _dllController = Get.find<DllController>();
|
final DllController _dllController = Get.find<DllController>();
|
||||||
@@ -93,7 +92,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
|||||||
final uuid = uri.host;
|
final uuid = uri.host;
|
||||||
final server = _hostingController.findServerById(uuid);
|
final server = _hostingController.findServerById(uuid);
|
||||||
if(server != null) {
|
if(server != null) {
|
||||||
_backendController.joinServerInteractive(_hostingController.uuid, server);
|
_backendController.joinServer(_hostingController.uuid, server);
|
||||||
}else {
|
}else {
|
||||||
showRebootInfoBar(
|
showRebootInfoBar(
|
||||||
translations.noServerFound,
|
translations.noServerFound,
|
||||||
@@ -134,40 +133,50 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
|||||||
dllsDirectory.createSync(recursive: true);
|
dllsDirectory.createSync(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
final dummy = Version.parse("1");
|
_dllController.guardFiles();
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onWindowClose() async {
|
void onWindowClose() async {
|
||||||
|
try {
|
||||||
|
await windowManager.hide();
|
||||||
|
}catch(error) {
|
||||||
|
log("[WINDOW] Cannot hide window: $error");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _hostingController.discardServer();
|
await _hostingController.discardServer();
|
||||||
}catch(error) {
|
}catch(error) {
|
||||||
log("[HOSTING] Cannot discard server: $error");
|
log("[HOSTING] Cannot discard server on exit: $error");
|
||||||
}finally {
|
|
||||||
// Force closing because the backend might be running, but we want the process to exit
|
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(_backendController.started.value) {
|
||||||
|
await _backendController.toggleInteractive();
|
||||||
|
}
|
||||||
|
}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
|
@override
|
||||||
@@ -255,62 +264,36 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
|||||||
_focused.value = true;
|
_focused.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void onWindowEvent(String eventName) {
|
|
||||||
if(eventName != "move") {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => log("[WINDOW] Event: $eventName ${_focused.value}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
_settingsController.language.value;
|
_settingsController.language.value;
|
||||||
loadTranslations(context);
|
loadTranslations(context);
|
||||||
return Obx(() {
|
return Container(
|
||||||
return Container(
|
|
||||||
color: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.93),
|
color: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.93),
|
||||||
child: Column(
|
child: Navigator(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
key: appNavigatorKey,
|
||||||
children: [
|
onPopPage: (page, data) => false,
|
||||||
SizedBox(
|
pages: [
|
||||||
height: HomePage.kTitleBarHeight,
|
MaterialPage(
|
||||||
child: Row(
|
child: Overlay(
|
||||||
children: [
|
key: appOverlayKey,
|
||||||
_backButton,
|
initialEntries: [
|
||||||
Expanded(child: _draggableArea),
|
OverlayEntry(
|
||||||
WindowTitleBar(focused: _focused())
|
maintainState: true,
|
||||||
],
|
builder: (context) => Row(
|
||||||
)
|
children: [
|
||||||
),
|
_buildLateralView(),
|
||||||
Expanded(
|
_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(
|
Widget _buildBody() => Expanded(
|
||||||
@@ -571,12 +554,6 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
GestureDetector get _draggableArea => GestureDetector(
|
|
||||||
onDoubleTap: windowManager.maximizeOrRestore,
|
|
||||||
onHorizontalDragStart: (_) => windowManager.startDragging(),
|
|
||||||
onVerticalDragStart: (_) => windowManager.startDragging()
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget get _autoSuggestBox => Padding(
|
Widget get _autoSuggestBox => Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16.0,
|
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/dll_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_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/hosting_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/dialog.dart';
|
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart';
|
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
import 'package:reboot_launcher/src/widget/message/data.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/data.dart';
|
import 'package:reboot_launcher/src/page/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
|
||||||
import 'package:reboot_launcher/src/util/translations.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/game_start_button.dart';
|
||||||
import 'package:reboot_launcher/src/widget/game_start_button.dart';
|
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
import 'package:reboot_launcher/src/widget/version/version_selector_tile.dart';
|
||||||
import 'package:reboot_launcher/src/widget/version_selector_tile.dart';
|
|
||||||
|
|
||||||
final GlobalKey<OverlayTargetState> hostVersionOverlayTargetKey = GlobalKey();
|
final GlobalKey<OverlayTargetState> hostVersionOverlayTargetKey = GlobalKey();
|
||||||
final GlobalKey<OverlayTargetState> hostInfoOverlayTargetKey = GlobalKey();
|
final GlobalKey<OverlayTargetState> hostInfoOverlayTargetKey = GlobalKey();
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/onboard.dart';
|
import 'package:reboot_launcher/src/widget/message/onboard.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.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';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class InfoPage extends RebootPage {
|
class InfoPage extends RebootPage {
|
||||||
@@ -29,7 +29,7 @@ class InfoPage extends RebootPage {
|
|||||||
|
|
||||||
class _InfoPageState extends RebootPageState<InfoPage> {
|
class _InfoPageState extends RebootPageState<InfoPage> {
|
||||||
static const String _kReportBugUrl = "https://github.com/Auties00/reboot_launcher/issues/new";
|
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
|
@override
|
||||||
List<SettingTile> get settings => [
|
List<SettingTile> get settings => [
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||||
import 'package:get/get.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/controller/game_controller.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/abstract/overlay.dart';
|
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||||
import 'package:reboot_launcher/src/messenger/implementation/data.dart';
|
import 'package:reboot_launcher/src/widget/message/data.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.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/game/game_start_button.dart';
|
||||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||||
import 'package:reboot_launcher/src/widget/version_selector_tile.dart';
|
import 'package:reboot_launcher/src/widget/version/version_selector_tile.dart';
|
||||||
|
|
||||||
final GlobalKey<OverlayTargetState> gameVersionOverlayTargetKey = GlobalKey();
|
final GlobalKey<OverlayTargetState> gameVersionOverlayTargetKey = GlobalKey();
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ class PlayPage extends RebootPage {
|
|||||||
|
|
||||||
class _PlayPageState extends RebootPageState<PlayPage> {
|
class _PlayPageState extends RebootPageState<PlayPage> {
|
||||||
final GameController _gameController = Get.find<GameController>();
|
final GameController _gameController = Get.find<GameController>();
|
||||||
|
final DllController _dllController = Get.find<DllController>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? get button => LaunchButton(
|
Widget? get button => LaunchButton(
|
||||||
@@ -81,6 +83,7 @@ class _PlayPageState extends RebootPageState<PlayPage> {
|
|||||||
content: Button(
|
content: Button(
|
||||||
onPressed: () => showResetDialog(() {
|
onPressed: () => showResetDialog(() {
|
||||||
_gameController.reset();
|
_gameController.reset();
|
||||||
|
_dllController.resetGame();
|
||||||
}),
|
}),
|
||||||
child: Text(translations.gameResetDefaultsContent),
|
child: Text(translations.gameResetDefaultsContent),
|
||||||
)
|
)
|
||||||
@@ -6,12 +6,12 @@ import 'package:get/get.dart';
|
|||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/settings_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/dialog.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page.dart';
|
import 'package:reboot_launcher/src/page/page.dart';
|
||||||
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
|
import 'package:reboot_launcher/src/page/page_type.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.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/file/file_setting_tile.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.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class SettingsPage extends RebootPage {
|
class SettingsPage extends RebootPage {
|
||||||
@@ -62,7 +62,7 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
|||||||
onReset: () {
|
onReset: () {
|
||||||
final path = _dllController.getDefaultDllPath(InjectableDll.console);
|
final path = _dllController.getDefaultDllPath(InjectableDll.console);
|
||||||
_dllController.unrealEngineConsoleDll.text = path;
|
_dllController.unrealEngineConsoleDll.text = path;
|
||||||
_dllController.downloadCriticalDllInteractive(path, force: true);
|
_dllController.download(InjectableDll.console, path, force: true);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
createFileSetting(
|
createFileSetting(
|
||||||
@@ -70,9 +70,19 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
|||||||
description: translations.settingsClientAuthDescription,
|
description: translations.settingsClientAuthDescription,
|
||||||
controller: _dllController.backendDll,
|
controller: _dllController.backendDll,
|
||||||
onReset: () {
|
onReset: () {
|
||||||
final path = _dllController.getDefaultDllPath(InjectableDll.starfall);
|
final path = _dllController.getDefaultDllPath(InjectableDll.auth);
|
||||||
_dllController.backendDll.text = path;
|
_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,
|
_internalFilesServerType,
|
||||||
@@ -176,9 +186,9 @@ class _SettingsPageState extends RebootPageState<SettingsPage> {
|
|||||||
description: translations.settingsServerFileDescription,
|
description: translations.settingsServerFileDescription,
|
||||||
controller: _dllController.gameServerDll,
|
controller: _dllController.gameServerDll,
|
||||||
onReset: () {
|
onReset: () {
|
||||||
final path = _dllController.getDefaultDllPath(InjectableDll.reboot);
|
final path = _dllController.getDefaultDllPath(InjectableDll.gameServer);
|
||||||
_dllController.gameServerDll.text = path;
|
_dllController.gameServerDll.text = path;
|
||||||
_dllController.downloadCriticalDllInteractive(path);
|
_dllController.download(InjectableDll.gameServer, path);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/backend_controller.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';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
|
|
||||||
class ServerButton extends StatefulWidget {
|
class ServerButton extends StatefulWidget {
|
||||||
@@ -2,8 +2,8 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/backend_controller.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/dialog.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/util/translations.dart';
|
||||||
|
|
||||||
class ServerTypeSelector extends StatefulWidget {
|
class ServerTypeSelector extends StatefulWidget {
|
||||||
@@ -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:get/get.dart';
|
||||||
import 'package:reboot_common/common.dart';
|
import 'package:reboot_common/common.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_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/dialog.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/messenger/implementation/version.dart';
|
import 'package:reboot_launcher/src/widget/message/version.dart';
|
||||||
import 'package:reboot_launcher/src/util/translations.dart';
|
import 'package:reboot_launcher/src/util/translations.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
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/util/translations.dart';
|
||||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
import 'package:reboot_launcher/src/widget/fluent/setting_tile.dart';
|
||||||
import 'package:reboot_launcher/src/widget/version_selector.dart';
|
import 'package:reboot_launcher/src/widget/version/version_selector.dart';
|
||||||
|
|
||||||
SettingTile buildVersionSelector({
|
SettingTile buildVersionSelector({
|
||||||
required GlobalKey<OverlayTargetState> key
|
required GlobalKey<OverlayTargetState> key
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
name: reboot_launcher
|
name: reboot_launcher
|
||||||
description: Graphical User Interface for Project Reboot
|
description: Graphical User Interface for Project Reboot
|
||||||
version: "10.0.1"
|
version: "10.0.5"
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
environment:
|
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:
|
dependencies:
|
||||||
# The flutter SDK
|
# The flutter SDK
|
||||||
@@ -18,8 +22,7 @@ dependencies:
|
|||||||
|
|
||||||
# Windows UI 3
|
# Windows UI 3
|
||||||
fluent_ui: ^4.9.1
|
fluent_ui: ^4.9.1
|
||||||
flutter_acrylic:
|
flutter_acrylic: ^1.1.4
|
||||||
path: ./dependencies/flutter_acrylic
|
|
||||||
fluentui_system_icons: ^1.1.258
|
fluentui_system_icons: ^1.1.258
|
||||||
system_theme: ^3.1.1
|
system_theme: ^3.1.1
|
||||||
skeletons:
|
skeletons:
|
||||||
@@ -58,7 +61,6 @@ dependencies:
|
|||||||
|
|
||||||
# Storage
|
# Storage
|
||||||
get_storage: ^2.1.1
|
get_storage: ^2.1.1
|
||||||
universal_disk_space: ^0.2.3
|
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <app_links/app_links_plugin_c_api.h>
|
#include <app_links/app_links_plugin_c_api.h>
|
||||||
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
||||||
#include <local_notifier/local_notifier_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 <system_theme/system_theme_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
@@ -22,8 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
|
registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
|
||||||
LocalNotifierPluginRegisterWithRegistrar(
|
LocalNotifierPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
|
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
|
||||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
||||||
SystemThemePluginRegisterWithRegistrar(
|
SystemThemePluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("SystemThemePlugin"));
|
registry->GetRegistrarForPlugin("SystemThemePlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
app_links
|
app_links
|
||||||
flutter_acrylic
|
flutter_acrylic
|
||||||
local_notifier
|
local_notifier
|
||||||
screen_retriever
|
screen_retriever_windows
|
||||||
system_theme
|
system_theme
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
window_manager
|
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
|
Source: "..\..\dependencies\redist\VC_redist.x64.exe"; DestDir: {tmp}; Flags: dontcopy
|
||||||
|
|
||||||
[Run]
|
[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: "{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
|
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
|
Name: "{userstartup}\{{DISPLAY_NAME}}"; Filename: "{app}\{{EXECUTABLE_NAME}}"; WorkingDir: "{app}"; Tasks: launchAtStartup
|
||||||
|
|
||||||
[Code]
|
[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;
|
function CompareVersion(version1, version2: String): Integer;
|
||||||
var
|
var
|
||||||
packVersion1, packVersion2: Int64;
|
packVersion1, packVersion2: Int64;
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
#include <dwmapi.h>
|
#include <dwmapi.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "Windowsx.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||||
@@ -160,52 +164,45 @@ LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
|||||||
}
|
}
|
||||||
|
|
||||||
LRESULT
|
LRESULT
|
||||||
Win32Window::MessageHandler(HWND hwnd,
|
Win32Window::MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept {
|
||||||
UINT const message,
|
switch (uMsg) {
|
||||||
WPARAM const wparam,
|
case WM_DESTROY: {
|
||||||
LPARAM const lparam) noexcept {
|
|
||||||
switch (message) {
|
|
||||||
case WM_DESTROY:
|
|
||||||
window_handle_ = nullptr;
|
window_handle_ = nullptr;
|
||||||
Destroy();
|
Destroy();
|
||||||
if (quit_on_close_) {
|
if (quit_on_close_) {
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
case WM_DPICHANGED: {
|
case WM_DPICHANGED: {
|
||||||
auto newRectSize = reinterpret_cast<RECT *>(lparam);
|
auto newRectSize = reinterpret_cast<RECT *>(lParam);
|
||||||
LONG newWidth = newRectSize->right - newRectSize->left;
|
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
case WM_SIZE: {
|
case WM_SIZE: {
|
||||||
RECT rect = GetClientArea();
|
auto rect = GetClientArea();
|
||||||
if (child_content_ != nullptr) {
|
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) {
|
if (child_content_ != nullptr) {
|
||||||
SetFocus(child_content_);
|
SetFocus(child_content_);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
case WM_NCCALCSIZE:
|
default:
|
||||||
return 0;
|
return DefWindowProc(window_handle_, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32Window::Destroy() {
|
void Win32Window::Destroy() {
|
||||||
OnDestroy();
|
OnDestroy();
|
||||||
|
|
||||||
@@ -228,8 +225,7 @@ void Win32Window::SetChildContent(HWND content) {
|
|||||||
SetParent(content, window_handle_);
|
SetParent(content, window_handle_);
|
||||||
RECT frame = GetClientArea();
|
RECT frame = GetClientArea();
|
||||||
|
|
||||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,frame.bottom - frame.top, true);
|
||||||
frame.bottom - frame.top, true);
|
|
||||||
|
|
||||||
SetFocus(child_content_);
|
SetFocus(child_content_);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user