From e36c0762aa3da9692cd6e2881607d9245d02a408 Mon Sep 17 00:00:00 2001 From: Alessandro Autiero Date: Tue, 6 Sep 2022 14:19:45 +0200 Subject: [PATCH] Switched to getx for state management Fixed last remaining bug --- lib/src/controller/build_controller.dart | 12 ++ lib/src/controller/game_controller.dart | 90 +++++++++++++ lib/src/controller/server_controller.dart | 34 +++++ lib/src/controller/warning_controller.dart | 7 + lib/src/util/binary.dart | 29 +++++ lib/src/util/build.dart | 142 +++++++++++++++++++++ lib/src/widget/restart_warning.dart | 15 +++ 7 files changed, 329 insertions(+) create mode 100644 lib/src/controller/build_controller.dart create mode 100644 lib/src/controller/game_controller.dart create mode 100644 lib/src/controller/server_controller.dart create mode 100644 lib/src/controller/warning_controller.dart create mode 100644 lib/src/util/binary.dart create mode 100644 lib/src/util/build.dart create mode 100644 lib/src/widget/restart_warning.dart diff --git a/lib/src/controller/build_controller.dart b/lib/src/controller/build_controller.dart new file mode 100644 index 0000000..791fd84 --- /dev/null +++ b/lib/src/controller/build_controller.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import 'package:reboot_launcher/src/model/fortnite_build.dart'; + +class BuildController extends GetxController { + List? builds; + FortniteBuild? _selectedBuild; + + FortniteBuild get selectedBuild => _selectedBuild ?? builds!.elementAt(0); + + set selectedBuild(FortniteBuild build) => _selectedBuild = build; +} diff --git a/lib/src/controller/game_controller.dart b/lib/src/controller/game_controller.dart new file mode 100644 index 0000000..1316ce5 --- /dev/null +++ b/lib/src/controller/game_controller.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; + +import 'package:reboot_launcher/src/model/fortnite_version.dart'; + +class GameController extends GetxController { + late final GetStorage _storage; + late final TextEditingController username; + late final TextEditingController version; + late final Rx> versions; + late final Rxn _selectedVersion; + late final RxBool host; + late final RxBool started; + Process? gameProcess; + Process? launcherProcess; + Process? eacProcess; + + GameController() { + _storage = GetStorage("game"); + + username = TextEditingController(text: _storage.read("username")); + username.addListener(() async { + await _storage.write("username", username.text); + }); + + Iterable decodedVersionsJson = + jsonDecode(_storage.read("versions") ?? "[]"); + var decodedVersions = decodedVersionsJson + .map((entry) => FortniteVersion.fromJson(entry)) + .toList(); + versions = Rx(decodedVersions); + versions.listen((data) => saveVersions()); + + var decodedSelectedVersionName = _storage.read("version"); + var decodedSelectedVersion = decodedVersions.firstWhereOrNull( + (element) => element.name == decodedSelectedVersionName); + _selectedVersion = Rxn(decodedSelectedVersion); + + host = RxBool(_storage.read("host") ?? false); + host.listen((value) => _storage.write("host", value)); + + started = RxBool(false); + } + + void kill() { + gameProcess?.kill(ProcessSignal.sigabrt); + launcherProcess?.kill(ProcessSignal.sigabrt); + eacProcess?.kill(ProcessSignal.sigabrt); + } + + FortniteVersion? getVersionByName(String name) { + return versions.value.firstWhereOrNull((element) => element.name == name); + } + + void addVersion(FortniteVersion version) { + versions.update((val) => val?.add(version)); + } + + FortniteVersion removeVersionByName(String versionName) { + var version = + versions.value.firstWhere((element) => element.name == versionName); + removeVersion(version); + return version; + } + + void removeVersion(FortniteVersion version) { + versions.update((val) => val?.remove(version)); + } + + Future saveVersions() async { + var serialized = + jsonEncode(versions.value.map((entry) => entry.toJson()).toList()); + await _storage.write("versions", serialized); + } + + bool get hasVersions => versions.value.isNotEmpty; + + bool get hasNoVersions => versions.value.isEmpty; + + Rxn get selectedVersionObs => _selectedVersion; + + set selectedVersion(FortniteVersion? version) { + _selectedVersion(version); + _storage.write("version", version?.name); + } +} diff --git a/lib/src/controller/server_controller.dart b/lib/src/controller/server_controller.dart new file mode 100644 index 0000000..a4a29ce --- /dev/null +++ b/lib/src/controller/server_controller.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; + +import 'package:reboot_launcher/src/util/binary.dart'; + +class ServerController extends GetxController { + late final TextEditingController host; + late final TextEditingController port; + late final RxBool embedded; + late final RxBool started; + Process? process; + + ServerController() { + var storage = GetStorage("server"); + host = TextEditingController(text: storage.read("host")); + host.addListener(() => storage.write("host", host.text)); + + port = TextEditingController(text: storage.read("port")); + port.addListener(() => storage.write("port", port.text)); + + embedded = RxBool(storage.read("embedded") ?? true); + embedded.listen((value) => storage.write("embedded", value)); + + started = RxBool(false); + } + + Future kill() async { + var release = await loadBinary("release.bat", false); + return Process.run(release.path, []); + } +} \ No newline at end of file diff --git a/lib/src/controller/warning_controller.dart b/lib/src/controller/warning_controller.dart new file mode 100644 index 0000000..ef89e6b --- /dev/null +++ b/lib/src/controller/warning_controller.dart @@ -0,0 +1,7 @@ +import 'package:get/get.dart'; + +import 'package:reboot_launcher/src/model/fortnite_build.dart'; + +class WarningController extends GetxController { + RxBool warning = RxBool(false); +} diff --git a/lib/src/util/binary.dart b/lib/src/util/binary.dart new file mode 100644 index 0000000..a71b6d4 --- /dev/null +++ b/lib/src/util/binary.dart @@ -0,0 +1,29 @@ +import 'dart:io'; + +Future loadBinary(String binary, bool safe) async{ + var safeBinary = File("$safeBinariesDirectory/$binary"); + if(await safeBinary.exists()){ + return safeBinary; + } + + var internal = _locateInternalBinary(binary); + if(!safe){ + return internal; + } + + if(await internal.exists()){ + await internal.copy(safeBinary.path); + } + + return safeBinary; +} + +File _locateInternalBinary(String binary){ + return File("$internalBinariesDirectory\\$binary"); +} + +String get internalBinariesDirectory => + "${File(Platform.resolvedExecutable).parent.path}\\data\\flutter_assets\\assets\\binaries"; + +String get safeBinariesDirectory => + "${Platform.environment["UserProfile"]}\\.reboot_launcher"; \ No newline at end of file diff --git a/lib/src/util/build.dart b/lib/src/util/build.dart new file mode 100644 index 0000000..2cf4926 --- /dev/null +++ b/lib/src/util/build.dart @@ -0,0 +1,142 @@ +import 'dart:io'; +import 'dart:math'; +import 'package:http/http.dart' as http; +import 'package:path/path.dart' as path; +import 'package:reboot_launcher/src/util/version.dart' as parser; +import 'package:html/parser.dart' show parse; + +import 'package:reboot_launcher/src/model/fortnite_build.dart'; + +import 'package:process_run/shell.dart'; +import 'package:reboot_launcher/src/util/binary.dart'; + +const _userAgent = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"; + +final _cookieRegex = RegExp("cookie=\"(.*?);"); +final _manifestSourceUrl = Uri.parse( + "https://github.com/VastBlast/FortniteManifestArchive/blob/main/README.md"); +final _archiveCookieUrl = Uri.parse("http://allinstaller.xyz/rel"); +final _archiveSourceUrl = Uri.parse("http://allinstaller.xyz/rel?i=1"); + +Future> fetchBuilds() async => [ + ...await _fetchArchives(), + ...await _fetchManifests() + ]..sort((first, second) => first.version.compareTo(second.version)); + +Future> _fetchArchives() async { + var cookieResponse = await http.get(_archiveCookieUrl); + var cookie = _cookieRegex.firstMatch(cookieResponse.body)?.group(1)?.trim(); + var response = + await http.get(_archiveSourceUrl, headers: {"Cookie": cookie!}); + if (response.statusCode != 200) { + throw Exception("Erroneous status code: ${response.statusCode}"); + } + + var document = parse(response.body); + var results = []; + for (var build in document.querySelectorAll("a[href^='https']")) { + var version = parser.tryParse(build.text.replaceAll("Build ", "")); + if (version == null) { + continue; + } + + results.add(FortniteBuild( + version: version, link: build.attributes["href"]!, hasManifest: false)); + } + + return results; +} + +Future> _fetchManifests() async { + var response = await http.get(_manifestSourceUrl); + if (response.statusCode != 200) { + throw Exception("Erroneous status code: ${response.statusCode}"); + } + + var document = parse(response.body); + var table = document.querySelector("table"); + if (table == null) { + throw Exception("Missing data table"); + } + + var results = []; + for (var tableEntry in table.querySelectorAll("tbody > tr")) { + var children = tableEntry.querySelectorAll("td"); + + var name = children[0].text; + var separator = name.indexOf("-") + 1; + var version = parser + .tryParse(name.substring(separator, name.indexOf("-", separator))); + if (version == null) { + continue; + } + + var link = children[2].firstChild!.attributes["href"]!; + results.add(FortniteBuild(version: version, link: link, hasManifest: true)); + } + + return results; +} + +Future downloadManifestBuild( + String manifestUrl, String destination, Function(double) onProgress) async { + var buildExe = await loadBinary("build.exe", false); + var process = await Process.start(buildExe.path, [manifestUrl, destination]); + + process.errLines + .where((message) => message.contains("%")) + .forEach((message) => onProgress(double.parse(message.split("%")[0]))); + + return process; +} + +Future downloadArchiveBuild(String archiveUrl, String destination, + Function(double) onProgress, Function() onRar) async { + var tempFile = File( + "${Platform.environment["Temp"]}\\FortniteBuild${Random.secure().nextInt(1000000)}.rar"); + try { + var client = http.Client(); + var request = http.Request("GET", Uri.parse(archiveUrl)); + request.headers["User-Agent"] = _userAgent; + var response = await client.send(request); + if (response.statusCode != 200) { + throw Exception("Erroneous status code: ${response.statusCode}"); + } + + var length = response.contentLength!; + var received = 0; + var sink = tempFile.openWrite(); + await response.stream.map((s) { + received += s.length; + onProgress((received / length) * 100); + return s; + }).pipe(sink); + onRar(); + + var output = Directory(destination); + await output.create(recursive: true); + var shell = Shell(workingDirectory: internalBinariesDirectory); + await shell.run("./winrar.exe x ${tempFile.path} *.* \"${output.path}\""); + var children = output.listSync(); + if(children.isEmpty){ + throw Exception("Missing extracted directory"); // No content + } + + if(children.length != 1){ + return; // Already in the correct schema + } + + // Extract directories from wrapper directory + var wrapper = Directory(children[0].path); + for(var entry in wrapper.listSync()){ + await entry.rename("${output.path}/${path.basename(entry.path)}"); + } + + await wrapper.delete(); + } finally { + if (await tempFile.exists()) { + tempFile.delete(); + } + } +} diff --git a/lib/src/widget/restart_warning.dart b/lib/src/widget/restart_warning.dart new file mode 100644 index 0000000..6a2c0b5 --- /dev/null +++ b/lib/src/widget/restart_warning.dart @@ -0,0 +1,15 @@ +import 'package:fluent_ui/fluent_ui.dart'; + +class RestartWarning extends StatelessWidget { + const RestartWarning({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const InfoBar( + title: Text('Node Installation'), + content: Text('Restart the launcher to run the server'), + isLong: true, + severity: InfoBarSeverity.warning + ); + } +}