From bfe15e43d98ce052769fef7d76691a6294e6b43c Mon Sep 17 00:00:00 2001 From: Alessandro Autiero Date: Sat, 14 Sep 2024 12:37:56 +0200 Subject: [PATCH 1/2] Released 9.2.7 --- common/lib/src/util/build.dart | 4 +- common/pubspec.yaml | 2 +- gui/lib/l10n/reboot_en.arb | 4 +- gui/lib/src/controller/dll_controller.dart | 21 +- .../src/messenger/implementation/error.dart | 9 +- .../src/messenger/implementation/version.dart | 18 +- .../src/page/implementation/backend_page.dart | 4 +- .../src/page/implementation/home_page.dart | 78 +++-- .../src/page/implementation/host_page.dart | 266 +++++++++--------- .../page/implementation/settings_page.dart | 4 +- gui/lib/src/util/types.dart | 11 + gui/lib/src/widget/file_selector.dart | 2 +- gui/lib/src/widget/file_setting_tile.dart | 128 ++++++--- gui/lib/src/widget/info_bar_area.dart | 33 ++- gui/pubspec.yaml | 2 +- 15 files changed, 331 insertions(+), 255 deletions(-) diff --git a/common/lib/src/util/build.dart b/common/lib/src/util/build.dart index 390c137..85db2a9 100644 --- a/common/lib/src/util/build.dart +++ b/common/lib/src/util/build.dart @@ -133,9 +133,9 @@ Future _downloadArchive(FortniteBuildDownloadOptions options, Completer st throw _genericError; }, headers: byteStart == null || byteStart <= 0 ? { - "Cookie": "_c_t_c=1" + } : { - "Cookie": "_c_t_c=1", + "Range": "bytes=${byteStart}-" }, ) diff --git a/common/pubspec.yaml b/common/pubspec.yaml index 1fff3ba..06d0be2 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -7,7 +7,7 @@ environment: sdk: ">=3.0.0 <=4.0.0" dependencies: - dio: ^5.3.2 + dio: ^5.7.0 win32: 3.0.0 ffi: ^2.1.0 path: ^1.8.3 diff --git a/gui/lib/l10n/reboot_en.arb b/gui/lib/l10n/reboot_en.arb index 9560ed0..bd91976 100644 --- a/gui/lib/l10n/reboot_en.arb +++ b/gui/lib/l10n/reboot_en.arb @@ -368,5 +368,7 @@ "automaticGameServerDialogStart": "Start server", "gameResetDefaultsName": "Reset", "gameResetDefaultsDescription": "Resets the game's settings to their default values", - "gameResetDefaultsContent": "Reset" + "gameResetDefaultsContent": "Reset", + "selectFile": "Select a file", + "reset": "Reset" } diff --git a/gui/lib/src/controller/dll_controller.dart b/gui/lib/src/controller/dll_controller.dart index fd67697..b953248 100644 --- a/gui/lib/src/controller/dll_controller.dart +++ b/gui/lib/src/controller/dll_controller.dart @@ -109,7 +109,9 @@ class DllController extends GetxController { duration: null ); } - timestamp.value = await downloadRebootDll(url.text); + final result = downloadRebootDll(url.text); + timestamp.value = await Future.wait([result, Future.delayed(const Duration(seconds: 1))], eagerError: false) + .then((_) => result); status.value = UpdateStatus.success; infoBarEntry?.close(); if(!silent) { @@ -126,15 +128,18 @@ class DllController extends GetxController { error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error; error = error.toLowerCase(); status.value = UpdateStatus.error; - showRebootInfoBar( - translations.downloadDllError("reboot.dll", error.toString()), + infoBarEntry = showRebootInfoBar( + translations.downloadDllError(error.toString(), "reboot.dll"), duration: infoBarLongDuration, severity: InfoBarSeverity.error, action: Button( - onPressed: () => updateGameServerDll( - force: true, - silent: silent - ), + onPressed: () async { + infoBarEntry?.close(); + updateGameServerDll( + force: true, + silent: silent + ); + }, child: Text(translations.downloadDllRetry), ) ); @@ -215,7 +220,7 @@ class DllController extends GetxController { error = error.toLowerCase(); final completer = Completer(); await showRebootInfoBar( - translations.downloadDllError(fileName, error.toString()), + translations.downloadDllError(error.toString(), fileName), duration: infoBarLongDuration, severity: InfoBarSeverity.error, onDismissed: () => completer.complete(null), diff --git a/gui/lib/src/messenger/implementation/error.dart b/gui/lib/src/messenger/implementation/error.dart index 802aa6b..7d9bad9 100644 --- a/gui/lib/src/messenger/implementation/error.dart +++ b/gui/lib/src/messenger/implementation/error.dart @@ -18,9 +18,12 @@ void onError(Object exception, StackTrace? stackTrace, bool framework) { } lastError = exception.toString(); - final route = ModalRoute.of(pageKey.currentContext!); - if(route != null && !route.isCurrent){ - Navigator.of(pageKey.currentContext!).pop(false); + if(inDialog){ + final context = pageKey.currentContext; + if(context != null) { + Navigator.of(context).pop(false); + inDialog = false; + } } WidgetsBinding.instance.addPostFrameCallback((timeStamp) => showRebootDialog( diff --git a/gui/lib/src/messenger/implementation/version.dart b/gui/lib/src/messenger/implementation/version.dart index 27297cd..068788b 100644 --- a/gui/lib/src/messenger/implementation/version.dart +++ b/gui/lib/src/messenger/implementation/version.dart @@ -5,10 +5,13 @@ import 'dart:isolate'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/messenger/abstract/dialog.dart'; +import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/translations.dart'; +import 'package:reboot_launcher/src/util/types.dart'; import 'package:reboot_launcher/src/widget/file_selector.dart'; import 'package:universal_disk_space/universal_disk_space.dart'; import 'package:windows_taskbar/windows_taskbar.dart'; @@ -41,6 +44,7 @@ class _AddVersionDialogState extends State { SendPort? _downloadPort; Object? _error; StackTrace? _stackTrace; + bool _selecting = false; @override void initState() { @@ -102,7 +106,12 @@ class _AddVersionDialogState extends State { return ErrorDialog( exception: _error ?? Exception(translations.unknownError), stackTrace: _stackTrace, - errorMessageBuilder: (exception) => translations.downloadVersionError(exception.toString()) + errorMessageBuilder: (exception) { + var error = exception.toString(); + error = error.after("Error: ")?.replaceAll(":", ",") ?? error.after(": ") ?? error; + error = error.toLowerCase(); + return translations.downloadVersionError(error); + } ); case _DownloadStatus.done: return InfoDialog( @@ -274,7 +283,8 @@ class _AddVersionDialogState extends State { ); } - Widget _buildFormBody(List builds) => Column( + Widget _buildFormBody(List builds) { + return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -292,7 +302,8 @@ class _AddVersionDialogState extends State { windowTitle: _source.value == _BuildSource.local ? translations.gameFolderPlaceWindowTitle : translations.buildInstallationDirectoryWindowTitle, controller: _pathController, validator: _source.value == _BuildSource.local ? _checkGameFolder : _checkDownloadDestination, - folder: true + folder: true, + allowNavigator: true ), const SizedBox( @@ -300,6 +311,7 @@ class _AddVersionDialogState extends State { ) ], ); + } String? _checkGameFolder(text) { if (text == null || text.isEmpty) { diff --git a/gui/lib/src/page/implementation/backend_page.dart b/gui/lib/src/page/implementation/backend_page.dart index a043b3f..bde6d25 100644 --- a/gui/lib/src/page/implementation/backend_page.dart +++ b/gui/lib/src/page/implementation/backend_page.dart @@ -153,9 +153,9 @@ class _BackendPageState extends RebootPageState { contentWidth: null, content: Row( children: [ - Text( + Obx(() => Text( _backendController.detached.value ? translations.on : translations.off - ), + )), const SizedBox( width: 16.0 ), diff --git a/gui/lib/src/page/implementation/home_page.dart b/gui/lib/src/page/implementation/home_page.dart index 31808d2..8d292b5 100644 --- a/gui/lib/src/page/implementation/home_page.dart +++ b/gui/lib/src/page/implementation/home_page.dart @@ -298,48 +298,46 @@ class _HomePageState extends State with WindowListener, AutomaticKeepA }); } - Widget _buildBody() { - return Expanded( - child: Padding( - padding: EdgeInsets.only( - left: HomePage.kDefaultPadding, - right: HomePage.kDefaultPadding * 2, - top: HomePage.kDefaultPadding, - bottom: HomePage.kDefaultPadding * 2 - ), - child: Column( - children: [ - Expanded( - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 1000 - ), - child: Center( - child: Column( - children: [ - _buildBodyHeader(), - const SizedBox(height: 24.0), - Expanded( - child: Stack( - fit: StackFit.loose, - children: [ - _buildBodyContent(), - InfoBarArea( - key: infoBarAreaKey - ) - ], - ) - ), - ], - ), + Widget _buildBody() => Expanded( + child: Padding( + padding: EdgeInsets.only( + left: HomePage.kDefaultPadding, + right: HomePage.kDefaultPadding * 2, + top: HomePage.kDefaultPadding, + bottom: HomePage.kDefaultPadding * 2 + ), + child: Column( + children: [ + Expanded( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 1000 + ), + child: Center( + child: Column( + children: [ + _buildBodyHeader(), + const SizedBox(height: 24.0), + Expanded( + child: Stack( + fit: StackFit.loose, + children: [ + _buildBodyContent(), + InfoBarArea( + key: infoBarAreaKey + ) + ], + ) + ), + ], ), ), - ) - ], - ) - ), - ); - } + ), + ) + ], + ) + ), + ); Widget _buildBodyContent() => PageView.builder( controller: _pageController, diff --git a/gui/lib/src/page/implementation/host_page.dart b/gui/lib/src/page/implementation/host_page.dart index 351b14f..1d74321 100644 --- a/gui/lib/src/page/implementation/host_page.dart +++ b/gui/lib/src/page/implementation/host_page.dart @@ -82,7 +82,7 @@ class _HostingPageState extends RebootPageState { List get settings => [ _information, buildVersionSelector( - key: hostVersionOverlayTargetKey + key: hostVersionOverlayTargetKey ), _options, _internalFiles, @@ -175,9 +175,9 @@ class _HostingPageState extends RebootPageState { contentWidth: null, content: Obx(() => Row( children: [ - Text( + Obx(() => Text( _hostingController.discoverable.value ? translations.on : translations.off - ), + )), const SizedBox( width: 16.0 ), @@ -226,7 +226,7 @@ class _HostingPageState extends RebootPageState { text: Text(entry.translatedName), onPressed: () => _hostingController.type.value = entry )).toList(), - disabled: _settingsController.debug.value + disabled: _settingsController.debug.value )), ), SettingTile( @@ -238,9 +238,9 @@ class _HostingPageState extends RebootPageState { contentWidth: null, content: Row( children: [ - Text( + Obx(() => Text( _hostingController.autoRestart.value ? translations.on : translations.off - ), + )), const SizedBox( width: 16.0 ), @@ -278,151 +278,143 @@ class _HostingPageState extends RebootPageState { title: Text(translations.settingsServerName), subtitle: Text(translations.settingsServerSubtitle), children: [ - SettingTile( - icon: Icon( - FluentIcons.timer_24_regular - ), - title: Text(translations.settingsServerTypeName), - subtitle: Text(translations.settingsServerTypeDescription), - content: Obx(() => DropDownButton( - onOpen: () => inDialog = true, - onClose: () => inDialog = false, - leading: Text(_dllController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName), - items: { - false: translations.settingsServerTypeEmbeddedName, - true: translations.settingsServerTypeCustomName - }.entries.map((entry) => MenuFlyoutItem( - text: Text(entry.value), - onPressed: () { - final oldValue = _dllController.customGameServer.value; - if(oldValue == entry.key) { - return; - } + _internalFilesServerType, + _internalFilesUpdateTimer, + _internalFilesServerSource + ], + ); - _dllController.customGameServer.value = entry.key; - _dllController.infoBarEntry?.close(); - if(!entry.key) { - _dllController.updateGameServerDll( - force: true - ); - } - } - )).toList() - )) + Widget get _internalFilesServerType => SettingTile( + icon: Icon( + FluentIcons.games_24_regular ), - Obx(() { - if(!_dllController.customGameServer.value) { - return const SizedBox.shrink(); - } + title: Text(translations.settingsServerTypeName), + subtitle: Text(translations.settingsServerTypeDescription), + contentWidth: SettingTile.kDefaultContentWidth + 30, + content: Obx(() => DropDownButton( + onOpen: () => inDialog = true, + onClose: () => inDialog = false, + leading: Text(_dllController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName), + items: { + false: translations.settingsServerTypeEmbeddedName, + true: translations.settingsServerTypeCustomName + }.entries.map((entry) => MenuFlyoutItem( + text: Text(entry.value), + onPressed: () { + final oldValue = _dllController.customGameServer.value; + if(oldValue == entry.key) { + return; + } - return createFileSetting( - title: translations.settingsServerFileName, - description: translations.settingsServerFileDescription, - controller: _dllController.gameServerDll, - onReset: () { - final path = _dllController.getDefaultDllPath(InjectableDll.reboot); - _dllController.gameServerDll.text = path; - _dllController.downloadCriticalDllInteractive(path); - } - ); - }), - Obx(() { - if(_dllController.customGameServer.value) { - return const SizedBox.shrink(); - } + _dllController.customGameServer.value = entry.key; + _dllController.infoBarEntry?.close(); + if(!entry.key) { + _dllController.updateGameServerDll( + force: true + ); + } + } + )).toList() + )) + ); - return SettingTile( - icon: Icon( - FluentIcons.globe_24_regular - ), - title: Text(translations.settingsServerMirrorName), - subtitle: Text(translations.settingsServerMirrorDescription), - content: Row( - children: [ - Expanded( - child: TextFormBox( - placeholder: translations.settingsServerMirrorPlaceholder, - controller: _dllController.url, - validator: _checkUpdateUrl - ), - ), - const SizedBox(width: 8.0), - Button( - style: ButtonStyle( - padding: ButtonState.all(EdgeInsets.zero) - ), - onPressed: () => _dllController.url.text = kRebootDownloadUrl, - child: SizedBox.square( - dimension: 30, - child: Icon( - FluentIcons.arrow_reset_24_regular - ), - ) - ) - ], - ) - ); - }), - Obx(() { - if(_dllController.customGameServer.value) { - return const SizedBox.shrink(); - } - - return SettingTile( - icon: Icon( - FluentIcons.timer_24_regular - ), - title: Text(translations.settingsServerTimerName), - subtitle: Text(translations.settingsServerTimerSubtitle), - content: Row( - children: [ - Expanded( - child: Obx(() => DropDownButton( - onOpen: () => inDialog = true, - onClose: () => inDialog = false, - leading: Text(_dllController.timer.value.text), - items: UpdateTimer.values.map((entry) => MenuFlyoutItem( - text: Text(entry.text), - onPressed: () { - _dllController.timer.value = entry; - _dllController.infoBarEntry?.close(); - _dllController.updateGameServerDll( - force: true - ); - } - )).toList() - )), - ), - const SizedBox(width: 8.0), - Button( - style: ButtonStyle( - padding: ButtonState.all(EdgeInsets.zero) - ), - onPressed: () { - _dllController.updateGameServerDll(force: true); + Widget get _internalFilesServerSource => Obx(() { + if(!_dllController.customGameServer.value) { + return SettingTile( + icon: Icon( + FluentIcons.globe_24_regular + ), + title: Text(translations.settingsServerMirrorName), + subtitle: Text(translations.settingsServerMirrorDescription), + contentWidth: SettingTile.kDefaultContentWidth + 30, + content: Row( + children: [ + Expanded( + child: TextFormBox( + placeholder: translations.settingsServerMirrorPlaceholder, + controller: _dllController.url, + onChanged: (value) { + if(Uri.tryParse(value) != null) { + _dllController.updateGameServerDll(force: true); + } }, + ), + ), + const SizedBox(width: 8.0), + Button( + style: ButtonStyle( + padding: ButtonState.all(EdgeInsets.zero) + ), + onPressed: () => _dllController.updateGameServerDll(force: true), child: SizedBox.square( dimension: 30, child: Icon( FluentIcons.arrow_download_24_regular ), ) - ) - ], - ) - ); - }) - ], - ); + ), + const SizedBox(width: 8.0), + Button( + style: ButtonStyle( + padding: ButtonState.all(EdgeInsets.zero) + ), + onPressed: () { + _dllController.url.text = kRebootDownloadUrl; + _dllController.updateGameServerDll(force: true); + }, + child: SizedBox.square( + dimension: 30, + child: Icon( + FluentIcons.arrow_reset_24_regular + ), + ) + ) + ], + ) + ); + }else { + return createFileSetting( + title: translations.settingsServerFileName, + description: translations.settingsServerFileDescription, + controller: _dllController.gameServerDll, + onReset: () { + final path = _dllController.getDefaultDllPath(InjectableDll.reboot); + _dllController.gameServerDll.text = path; + _dllController.downloadCriticalDllInteractive(path); + } + ); + } + }); - - String? _checkUpdateUrl(String? text) { - if (text == null || text.isEmpty) { - return translations.emptyURL; + Widget get _internalFilesUpdateTimer => Obx(() { + if(_dllController.customGameServer.value) { + return const SizedBox.shrink(); } - return null; - } + return SettingTile( + icon: Icon( + FluentIcons.timer_24_regular + ), + title: Text(translations.settingsServerTimerName), + subtitle: Text(translations.settingsServerTimerSubtitle), + contentWidth: SettingTile.kDefaultContentWidth + 30, + content: Obx(() => DropDownButton( + onOpen: () => inDialog = true, + onClose: () => inDialog = false, + leading: Text(_dllController.timer.value.text), + items: UpdateTimer.values.map((entry) => MenuFlyoutItem( + text: Text(entry.text), + onPressed: () { + _dllController.timer.value = entry; + _dllController.infoBarEntry?.close(); + _dllController.updateGameServerDll( + force: true + ); + } + )).toList() + )) + ); + }); SettingTile get _share => SettingTile( icon: Icon( diff --git a/gui/lib/src/page/implementation/settings_page.dart b/gui/lib/src/page/implementation/settings_page.dart index 9d50a42..ab5983e 100644 --- a/gui/lib/src/page/implementation/settings_page.dart +++ b/gui/lib/src/page/implementation/settings_page.dart @@ -110,9 +110,9 @@ class _SettingsPageState extends RebootPageState { contentWidth: null, content: Row( children: [ - Text( + Obx(() => Text( _settingsController.debug.value ? translations.on : translations.off - ), + )), const SizedBox( width: 16.0 ), diff --git a/gui/lib/src/util/types.dart b/gui/lib/src/util/types.dart index dd8c68a..706d461 100644 --- a/gui/lib/src/util/types.dart +++ b/gui/lib/src/util/types.dart @@ -5,4 +5,15 @@ extension IterableExtension on Iterable { } return null; } +} + +extension StringExtension on String { + String? after(String leading) { + final index = indexOf(leading); + if(index == -1) { + return null; + } + + return substring(index + leading.length); + } } \ No newline at end of file diff --git a/gui/lib/src/widget/file_selector.dart b/gui/lib/src/widget/file_selector.dart index 4111ae2..4c08b32 100644 --- a/gui/lib/src/widget/file_selector.dart +++ b/gui/lib/src/widget/file_selector.dart @@ -19,10 +19,10 @@ class FileSelector extends StatefulWidget { required this.controller, required this.validator, required this.folder, + required this.allowNavigator, this.label, this.extension, this.validatorMode, - this.allowNavigator = true, Key? key}) : assert(folder || extension != null, "Missing extension for file selector"), super(key: key); diff --git a/gui/lib/src/widget/file_setting_tile.dart b/gui/lib/src/widget/file_setting_tile.dart index 8d33e6b..796216a 100644 --- a/gui/lib/src/widget/file_setting_tile.dart +++ b/gui/lib/src/widget/file_setting_tile.dart @@ -1,70 +1,120 @@ import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; +import 'package:fluent_ui/fluent_ui.dart' as fluentIcons show FluentIcons; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; +import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; +import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/widget/file_selector.dart'; import 'package:reboot_launcher/src/widget/setting_tile.dart'; +const double _kButtonDimensions = 30; +const double _kButtonSpacing = 8; + SettingTile createFileSetting({required String title, required String description, required TextEditingController controller, required void Function() onReset}) { final obx = RxString(controller.text); controller.addListener(() => obx.value = controller.text); - return SettingTile( - icon: Icon( - FluentIcons.document_24_regular - ), - title: Text(title), - subtitle: Text(description), - content: Row( + final selecting = RxBool(false); + return SettingTile( + icon: Icon( + FluentIcons.document_24_regular + ), + title: Text(title), + subtitle: Text(description), + contentWidth: SettingTile.kDefaultContentWidth + _kButtonDimensions * 2 + _kButtonSpacing * 2, + content: Row( children: [ - Expanded( - child: FileSelector( - placeholder: translations.selectPathPlaceholder, - windowTitle: translations.selectPathWindowTitle, - controller: controller, - validator: _checkDll, - extension: "dll", - folder: false, - validatorMode: AutovalidateMode.always - ), + Expanded( + child: FileSelector( + placeholder: translations.selectPathPlaceholder, + windowTitle: translations.selectPathWindowTitle, + controller: controller, + validator: _checkDll, + extension: "dll", + folder: false, + validatorMode: AutovalidateMode.always, + allowNavigator: false, ), - const SizedBox(width: 8.0), - Obx(() => Padding( - padding: EdgeInsets.only( - bottom: _checkDll(obx.value) == null ? 0.0 : 20.0 + ), + const SizedBox(width: _kButtonSpacing), + Obx(() => Padding( + padding: EdgeInsets.only( + bottom: _checkDll(obx.value) == null ? 0.0 : 20.0 + ), + child: Tooltip( + message: translations.selectFile, + child: Button( + style: ButtonStyle( + padding: ButtonState.all(EdgeInsets.zero) + ), + onPressed: () => _onPressed(selecting, controller), + child: SizedBox.square( + dimension: _kButtonDimensions, + child: Icon( + fluentIcons.FluentIcons.open_folder_horizontal + ), + ) ), + ), + )), + const SizedBox(width: _kButtonSpacing), + Obx(() => Padding( + padding: EdgeInsets.only( + bottom: _checkDll(obx.value) == null ? 0.0 : 20.0 + ), + child: Tooltip( + message: translations.reset, child: Button( style: ButtonStyle( padding: ButtonState.all(EdgeInsets.zero) ), onPressed: onReset, child: SizedBox.square( - dimension: 30, - child: Icon( - FluentIcons.arrow_reset_24_regular - ), + dimension: _kButtonDimensions, + child: Icon( + FluentIcons.arrow_reset_24_regular + ), ) ), - )) + ), + )) ], - ) -); + ) + ); +} + +void _onPressed(RxBool selecting, TextEditingController controller) { + if(selecting.value){ + return; + } + + selecting.value = true; + compute(openFilePicker, "dll") + .then((value) => _updateText(controller, value)) + .then((_) => selecting.value = false); +} + +void _updateText(TextEditingController controller, String? value) { + final text = value ?? controller.text; + controller.text = text; + controller.selection = TextSelection.collapsed(offset: text.length); } String? _checkDll(String? text) { - if (text == null || text.isEmpty) { - return translations.invalidDllPath; - } + if (text == null || text.isEmpty) { + return translations.invalidDllPath; + } - final file = File(text); - if (!file.existsSync()) { - return translations.dllDoesNotExist; - } + final file = File(text); + if (!file.existsSync()) { + return translations.dllDoesNotExist; + } - if (!text.endsWith(".dll")) { - return translations.invalidDllExtension; - } + if (!text.endsWith(".dll")) { + return translations.invalidDllExtension; + } - return null; + return null; } \ No newline at end of file diff --git a/gui/lib/src/widget/info_bar_area.dart b/gui/lib/src/widget/info_bar_area.dart index f730c12..9443bbc 100644 --- a/gui/lib/src/widget/info_bar_area.dart +++ b/gui/lib/src/widget/info_bar_area.dart @@ -24,19 +24,22 @@ class InfoBarAreaState extends State { } @override - Widget build(BuildContext context) => Obx(() => Padding( - padding: EdgeInsets.only( - bottom: hasPageButton ? 72.0 : 16.0 - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: _children.value.map((child) => Padding( - padding: EdgeInsets.only( - top: 12.0 - ), - child: child - )).toList(growable: false) - ), - )); + Widget build(BuildContext context) => StreamBuilder( + stream: pagesController.stream, + builder: (context, _) => Obx(() => Padding( + padding: EdgeInsets.only( + bottom: hasPageButton ? 72.0 : 16.0 + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: _children.value.map((child) => Padding( + padding: EdgeInsets.only( + top: 12.0 + ), + child: child + )).toList(growable: false) + ), + )) + ); } \ No newline at end of file diff --git a/gui/pubspec.yaml b/gui/pubspec.yaml index 5f5bafb..93b094f 100644 --- a/gui/pubspec.yaml +++ b/gui/pubspec.yaml @@ -1,6 +1,6 @@ name: reboot_launcher description: Graphical User Interface for Project Reboot -version: "9.2.6" +version: "9.2.7" publish_to: 'none' From dfebe74518f6dc8cfdce583181db67916bc94f72 Mon Sep 17 00:00:00 2001 From: Alessandro Autiero Date: Mon, 21 Oct 2024 20:32:23 +0200 Subject: [PATCH 2/2] Switched to sinum --- common/lib/src/model/dll.dart | 2 +- common/lib/src/util/build.dart | 4 +- common/lib/src/util/dll.dart | 17 +- common/pubspec.yaml | 14 +- gui/dependencies/dlls/sinum.dll | Bin 0 -> 22528 bytes gui/lib/l10n/reboot_en.arb | 6 +- gui/lib/main.dart | 5 +- gui/lib/src/controller/dll_controller.dart | 30 +- .../src/controller/hosting_controller.dart | 32 +- .../src/controller/settings_controller.dart | 4 - gui/lib/src/messenger/abstract/dialog.dart | 4 +- .../src/messenger/implementation/onboard.dart | 4 +- .../src/messenger/implementation/profile.dart | 4 +- .../src/messenger/implementation/server.dart | 4 +- .../src/messenger/implementation/version.dart | 2 - gui/lib/src/page/abstract/page.dart | 32 - .../src/page/implementation/backend_page.dart | 1 - .../src/page/implementation/browser_page.dart | 4 +- .../src/page/implementation/home_page.dart | 8 +- .../src/page/implementation/host_page.dart | 94 +- .../src/page/implementation/play_page.dart | 2 - .../page/implementation/settings_page.dart | 25 - gui/lib/src/util/os.dart | 31 +- gui/lib/src/util/url_protocol.dart | 63 + gui/lib/src/widget/file_setting_tile.dart | 8 +- gui/lib/src/widget/game_start_button.dart | 23 +- gui/lib/src/widget/version_selector.dart | 6 +- gui/pubspec.yaml | 43 +- redirect/Core/Constants.h | 13 + redirect/Core/Core.cpp | 11 + redirect/Core/Core.h | 16 + redirect/Core/Unreal/Array.h | 143 ++ redirect/Core/Unreal/Memory.h | 92 ++ redirect/Core/Unreal/String.h | 54 + redirect/Main.cpp | 21 + redirect/README.md | 5 + redirect/Sinum.sln | 22 + redirect/Sinum.vcxproj | 162 +++ redirect/Sinum.vcxproj.filters | 57 + redirect/Sinum.vcxproj.user | 4 + redirect/Sinum/Sinum.cpp | 40 + redirect/Sinum/Sinum.h | 31 + redirect/Utilities/Windows.h | 15 + redirect/Utilities/memcury.h | 1210 +++++++++++++++++ redirect/framework.h | 10 + 45 files changed, 2185 insertions(+), 193 deletions(-) create mode 100644 gui/dependencies/dlls/sinum.dll create mode 100644 gui/lib/src/util/url_protocol.dart create mode 100644 redirect/Core/Constants.h create mode 100644 redirect/Core/Core.cpp create mode 100644 redirect/Core/Core.h create mode 100644 redirect/Core/Unreal/Array.h create mode 100644 redirect/Core/Unreal/Memory.h create mode 100644 redirect/Core/Unreal/String.h create mode 100644 redirect/Main.cpp create mode 100644 redirect/README.md create mode 100644 redirect/Sinum.sln create mode 100644 redirect/Sinum.vcxproj create mode 100644 redirect/Sinum.vcxproj.filters create mode 100644 redirect/Sinum.vcxproj.user create mode 100644 redirect/Sinum/Sinum.cpp create mode 100644 redirect/Sinum/Sinum.h create mode 100644 redirect/Utilities/Windows.h create mode 100644 redirect/Utilities/memcury.h create mode 100644 redirect/framework.h diff --git a/common/lib/src/model/dll.dart b/common/lib/src/model/dll.dart index dcefa86..b54398f 100644 --- a/common/lib/src/model/dll.dart +++ b/common/lib/src/model/dll.dart @@ -1,6 +1,6 @@ enum InjectableDll { console, - cobalt, + sinum, reboot, memory } diff --git a/common/lib/src/util/build.dart b/common/lib/src/util/build.dart index 85db2a9..390c137 100644 --- a/common/lib/src/util/build.dart +++ b/common/lib/src/util/build.dart @@ -133,9 +133,9 @@ Future _downloadArchive(FortniteBuildDownloadOptions options, Completer st throw _genericError; }, headers: byteStart == null || byteStart <= 0 ? { - + "Cookie": "_c_t_c=1" } : { - + "Cookie": "_c_t_c=1", "Range": "bytes=${byteStart}-" }, ) diff --git a/common/lib/src/util/dll.dart b/common/lib/src/util/dll.dart index f0e20d7..4579a99 100644 --- a/common/lib/src/util/dll.dart +++ b/common/lib/src/util/dll.dart @@ -6,13 +6,16 @@ import 'package:path/path.dart' as path; import 'package:reboot_common/common.dart'; bool _watcher = false; -final File rebootDllFile = File("${dllsDirectory.path}\\reboot.dll"); -const String kRebootDownloadUrl = +final File rebootBeforeS20DllFile = File("${dllsDirectory.path}\\reboot.dll"); +final File rebootAboveS20DllFile = File("${dllsDirectory.path}\\rebootS20.dll"); +const String kRebootBelowS20DownloadUrl = "http://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/Release.zip"; +const String kRebootAboveS20DownloadUrl = + "http://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/aboveS20/Release.zip"; Future hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force = false}) async { final lastUpdate = await _getLastUpdate(lastUpdateMs); - final exists = await rebootDllFile.exists(); + final exists = await rebootBeforeS20DllFile.exists() && await rebootAboveS20DllFile.exists(); final now = DateTime.now(); return force || !exists || (hours > 0 && lastUpdate != null && now.difference(lastUpdate).inHours > hours); } @@ -28,9 +31,8 @@ Future downloadCriticalDll(String name, String outputPath) async { await output.writeAsBytes(response.bodyBytes, flush: true); } -Future downloadRebootDll(String url) async { +Future downloadRebootDll(File file, String url) async { Directory? outputDir; - final now = DateTime.now(); try { final response = await http.get(Uri.parse(url)); if(response.statusCode != 200) { @@ -42,8 +44,7 @@ Future downloadRebootDll(String url) async { await tempZip.writeAsBytes(response.bodyBytes, flush: true); await extractFileToDisk(tempZip.path, outputDir.path); final rebootDll = File(outputDir.listSync().firstWhere((element) => path.extension(element.path) == ".dll").path); - await rebootDllFile.writeAsBytes(await rebootDll.readAsBytes(), flush: true); - return now.millisecondsSinceEpoch; + await file.writeAsBytes(await rebootDll.readAsBytes(), flush: true); } finally{ if(outputDir != null) { delete(outputDir); @@ -63,7 +64,7 @@ Stream watchDlls() async* { } _watcher = true; - await for(final event in rebootDllFile.parent.watch(events: FileSystemEvent.delete | FileSystemEvent.move)) { + await for(final event in dllsDirectory.watch(events: FileSystemEvent.delete | FileSystemEvent.move)) { if (event.path.endsWith(".dll")) { yield event.path; } diff --git a/common/pubspec.yaml b/common/pubspec.yaml index 06d0be2..8b527ff 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -8,12 +8,12 @@ environment: dependencies: dio: ^5.7.0 - win32: 3.0.0 - ffi: ^2.1.0 - path: ^1.8.3 - http: ^1.1.0 - crypto: ^3.0.2 - archive: ^3.3.7 + win32: ^5.5.4 + ffi: ^2.1.3 + path: ^1.9.0 + http: ^1.2.2 + crypto: ^3.0.5 + archive: ^3.6.1 ini: ^2.1.0 shelf_proxy: ^1.0.2 sync: ^0.3.0 @@ -22,4 +22,4 @@ dependencies: version: ^3.0.2 dev_dependencies: - flutter_lints: ^2.0.1 \ No newline at end of file + flutter_lints: ^5.0.0 \ No newline at end of file diff --git a/gui/dependencies/dlls/sinum.dll b/gui/dependencies/dlls/sinum.dll new file mode 100644 index 0000000000000000000000000000000000000000..615a6eca9ab70b0f3dc404b8ccbf491efdb5f20b GIT binary patch literal 22528 zcmeHv4}4U`_3vzUHwk~j-C!0&MOkoxAOayl8wk{0l7(5l!3YUZ5t5K>AT`ORo81ts z)Zl`!T({CzTia(_q_kq~YiWNz670VXK@tQdV5{*@YpQL%F{o(!gZQW3-}lVDn`{EK z{rq0v`@G-#_3GR+b7tnunKNh3oH=(x(d~PfnK5QT(lo}}0O@gw_uqaEpnBZor^c}- zM?E{G%_KiNrDRi0qoX0<-x#QR+F}d7Mp@cS{6U)KqRF+9PLD>|w0xzGU{rZ%)`4uRF)4I>sc8o58YyxlYIKnGa|a z`4$ra$|o_FEKoy`?FE8_h$o4aqp9904lXVaVnm<9l}wOeEsT*|Sp#D|v@r2!4`XYP zcX7t@;yaDX1t%f~?qbq=d)CSZq#%-cZaDa}{G2Cp1D9?ObyLje$z! zg-lwIA$lc936D#(^VRy%kYvR6AW^$jR2X_(j4c@|{VPWjaINIwvktDj#FZmFnpTv< zn6x^^qbpN+WDJk;R4F}4v;PuW!IcgkKI&N8aWe7Y%5AAUYW1NX-*JXZo+BgbBX>Ac zSBo*C)_3O-nGblxq`b_-?WvkQ1j6S#Ou@9LGQkX2PN`2g2;Sk$=1M1xS2luP)cQD4 ziI|80*QVuyLIOkJy)zakcw4ThXyvUoAvPCa>}Wk$61| zI+NvTm0H(51}-IOX3>j)t8LQE(l_5nuFUbil)mnQS1EC(dh<)1w%| z_Mw=XorjOi;EH#E&uWJh2GDTw<`wG4ldujX?55e?Xel}!8_}T&9gYol$l}U#Y8U!c zYaO)4Sn{mu{B@5vPJll4P5uOnWbeBkBsKf%04Kf5QSdRB=blu@dB8a?W8)L-r}MCfvr*NbgWu!?2Pc)a;{hlZo!0U zM-)2OaOFd;yvHLltWKoFneEAan@oU9{Wj?AA#3E~O0ARg{NDzb%F5M59{wN4FZA*D50l7Bx2rdCb2pD>oZ;=So4rcR zkhpZORVdhz8AXa~z&k4zm%4P{Sq!b&_soJ|x$-=%!FD(>lC1y=vD6{iZpoI?mqRgZ z1ukgEQp^_YkaC7Nl!~k8NB!?G7MMu7Gm@#DcVe-<(C@=gf9&KxOZQ;XGM1{CE2}ek z_(Ms$@0TPq?Fd(-!AX8!i1Ek_S#e{MUD$C#7p41li%C8KtG$QFZU9;LVeCq1jnxWe zf^nY?kb4~G*-Vm$4R35P&3=J)*y>m=Od+I^z7$%p)Fq`S%aM8;?_8QmJ7`yOoU5km zhjA1Ds~2&z8LHW(z6O=WjyRTr^Jtl%B1imC5g956jdsC=$k98Tj z_TPiO!Sm;~Ogf_>6O68mA_k0o30))s9GQ_Ii_KGL^a>iivw$Q(P>g@f3>Bn9T(jRx z!nq-1vp_c&C{LSX(Pm9K$F=7?+m8p= z=f<{a3>H~>Bu1KkAN0_D(3E?|t0WgG>r>@fJ;CK%`5m9t<2AqL*&Yi{EtC$WB4wC8 z3!iaI55_PdEygRK@#dch{0_L_HLmS%hsJBCFod8!plQ-~V*PuyPOcs6{}Qk4^qM=p z5sOoK(%Fp}?l>=PABS<{nv};o3!Rt<8H@F)-dWd3+vx;JJPUfHo$G1GZ2L%9xa(ob zVZ2XB_bmZdJ(7W{9kJjz9&LuneiJ5p*+Bnz&F+P^oWwp?_fiXGfDYa~dbxPiGMNkt z77{}$6Zou`Wq3{SYMj@iY1EQWwXWz=4ZwlvKPyKIov>uTArt(0SU+ek9SYL+uM5Uk z+4!u}gDZV0J-d=tdh?Y~>zFr^?2RlqlcwtgMy)^r+M-FqGrQWu&@&*D#l zKQxLA%tEfL95%^Isq{+2HxJiu_<4bi!|e)Af1Sg0}#$x8LKP9ps{Lt)nn+ z{uQAM{8v=O1E=LRX=f+tZulc<^VOtLk|xy6oFI~so8Ix zMw*XROI7!SluUM^Lyp|YmEZ8ldYe~yjz_+jsm=pO?qil~z|9?G%#YFGxrUa@y&jfL z&H8sbqwoC-ozdcah6NI$fdLloD7vXaApri+E-@@Hx1 z(?TwvNSmEph!g&tE*Ig?rNbd17UDzWP?-lRUh`2n z?}3|vyj>tC3FOl{zA;BSWX7%P6qssRl*)orIUFtrj;B3Rt4(Shn>p)rC>1J@wBvZ_ zZMxPLeu7iS8-CX$?YIm2$9HI@4mw4PX^WZM$;$roZG}#5BE8+j>ZpoOx=ayuX z9e;*)S)>sLOm|6KyK;4}__feaxgyAuzl|+hhLhw~NH2bN5F~#Ex7+a9JQ;3~^0If< zY41~mPO|cz7Y?%G#o?>+3vYR69rw2B4)m8`J3jQbYe`;&72rYRo=Htddez?iZfW~K z5(#TL?AZ}epp(5>j#N3b<>&?zdXS5nWgHtati&O<~eYDvf8 z?#(~SrGhSRewVcKJ39I}ZW(fP{?kx#=H2#*a6x8zqswP_^PiLMy9S}D4SZ)b-?4o{W5)-iz};AxsuHDUk>?r{t;>W z3@DDc|L6u=j?~zYG9?>BG(oa)o0RSaW)p^SiD3$NWl1&{oNG4*;J9vhiM|W#9OJlh zxRGhc&FDzzM|~$C5T4%=Tt)izPp01y`Vm)7tJD8VRtLUP^i~Yo6I?qOUl=)_S%{S1$MhAYrx zXupg-2K$c%jVvc0D$i{=ZJ)oI42|*u9YcRSYaBzupDI2qqfWEGc@-(NozeM)`*FGY zMKp%Q$mF|7j}JpFpp8A^E|;y|GYTabee4{(+&?j)orBtu{ck{Y5r-o#n#Y(*5O(E+ zIRCg}CGHgKw0r<@Ucw|JV2J>sCs&grTdzc#%oUTYU=pQQ zx;5Bfn@}2xLv6*r+j`1y<=`7t@9}D}>ZJ=7EeV~+ao4K;6({5#Ia-RyN~l#<8)#XD)cs2z}FE(WP>-l+hA~E<$oJv6kb*c6lLC?chebhnlLMNx{cFPhU{{lS=6Mm5d=u$iIsYZ;UJZ5fte40W z%zvdwTGGKOq=ad4Y1uL8AHu6eFRB)rI6q7Q9z6HZ!KIYG$uV$vz%JbzBO=kd7a>Yi;3)1T2P^qF6SYZ1vQ)r2zTQA7@f0FLg?@52*;pb9#{(BUydoSUN z7ahy`=e8lL0)ui6p4&MdU2aOKCv&`j=4M8$R1e`{%NQX%2UTEK9R};d3e&YP@iIs= z=(m$UJWhR(8o`AoR8lUK5d1}aDLfLAB%Ad)Y#(8K(|#>-x7uL*tUbuX_QB+~S_xkr zS%z5et=Gv>%Xr)g)jr%Wb7R#@B9@IbU#QFlS@B8U@W&+EPk6+$vwA^Fa4qf()!{{D zEOb8HVT!DKAhPbO!JDI&t9WIP2RA0=dD;YPZvunp!FRk8oQ>&(GBmp%5rR%ezO81z z6Z!rOtRf;>H0xRn-fuyV2M|cySuNe;dCbs>6j^ZM1eX5aZ4i1$zrgpc^;hhPS3SE@5JEQWbG7i%+7ROf-5bclSU8RR4FgPkECsaJ4y zxp?Ex-=sTkGVZv0)M9nG?@s=TaGgpi8vi)F@pD2z*3KKjxUeqEU{3qUsz!e9Ocm-? z^sbtPn*a}QeHY=u@1R5OE7hHzuk&r#3&%X)IE#(o`3{e)`*HLZvwAg{EQ}W5P7GYM z^y`3F76iqbf!g|G^@rmbgDphO6)|kddNaLGEhoZB6U{B4#iH0bL$>(#iB0yrx#;7NfH*D>~b+ zazu;3wBVjNuf#Bai}Uy4v0jREtUJfJ9AO_ZoPxVZX{%x_!x~~*FcmQ#SsYqK8_v^; zd0=xbLtS;m^E3E7BBT+I`5*KAf~ha}5w)~j^w8}84y{*;t}RrGCUbMUa>9enJ*iM> zm(4Nng1q1;t=*k}Jn&9G9L$~~Y@(q{z;~h45XDgd+HQB)iQJDwgb0^_heW0n*H~kn z$z`4oRl6WO`N1grByjBlB~0Zzg;+OZQ^v#Dc%R2|*D0!o~BSZn7oVdpZwt4vX^u7X*YmhQ16* zOZuQbCQe7fcG{*xN5ZUl5X4W0@ds3SQCx7MW}ip1rPE-vdLOmXopP9Iij2I-&9A@- zqiICyGimpX!7OMBxDzfW)(R~Rk;=pCCAG&u!y#*d3c*ik$LiHd|6M0i#PDGcqV6wUzRuKN*>lm2U zQoRb&LZc}@u2`mJi+S{(!0r?3N`Ux;p;LYHP$r0zjy^RS`z&gG!v=ErR1-0sKVk}w7QbsyUqB<_OpD)7 zkiNh(Ul4xF6wIR61l(YPquZeOLLoZT9NbU)t*RM7Ius)%>F~m%C!M0m8crVRA$)~J zF_VnGuHQk@Z@BP!ER`$o%gTFDmVF?C?XN~9} zezHKGm@7wv5Sw7~UE8(LO=>0)#FzDO9t%3jtab=CgHWDhPw9@}b6i2tCT%>3xwX@p zwGX6(jKsHceKU%1(cSnmiv@JA?>H>Zhr6MVd*b_^c6+vK_XmpgarEC2yC;oYVm-o? zjR;d$#JUF+s`QZ|8Ao{Wjl^^)SG^W9Q*Q)K^?%|Oo^}oJqzgX=ueoPjyHBad#R~NZ zxhfs~-`5%V2wvHqJ#caHd3lXPj<_--#aJ7jY6QAnWO_ct@<45+1$Gw5>} zqf4~#fN8UbE7rZ^AS3M7(YQ8Kqz?YMW;X+=^w6c60z*Fq781ipX!u3@!OTz<-HZmS zeseBf71m6l8$mQj7&_{97f0r8n)9q3prZLPU{Uut_@p(@jGL*vu0DzPK${~ z9>ObW4_ya*M7B8MNcih%u{aVP?;Z;dHCjFq=72p~MFMoe6lwOyV5_+DZv!BjtI?GZ z;g@n;z%=_bs=-_r@Nktg4;;wwop9ilBUnIU$;R>#vVjUXoXONUG&}tsK<2j|<9`%0m5#=Y%aeb86y-u8Y?z0X^yoKVi?9(P9zOr9~_(mwaL_m=tULlKM9 zGv+jyv?w3Ys^VM6*w9)YPJ3b|IG%RyCQag<)~}+#S{Pd#OE$4!*5dNyBo>^qxPic@ ztOq~%7+cVRFG>h!(SMkY^xgKxj6%iw+qp1C;Vx5Q#CitV{uJPjBhyBPr!dMt%0GAa zbYdHf6e^_TmN*;IQ*VrF9PJs?j=o<*mrkn%Y!LOrNSX;*>8Up}r0|z@#Mvyyeg+?a zMXvrMvZ%YNaSPs)8XH00)Qz8hIQW=u{bLS&yqMatup_iuv9_XhxGS~O`h8?@$6^ud ze&pQeI~>8haF?Ug8by&#?1)uCt_`ia3$N`d%IK!hw=iF`GPz3cGP=6|F_P5cPE8kMlj7J~pK_&JxG#K#; zssWuvam|hPidxZVNP7{DimFsW`b#t^Bcg&ig7l6qs)SM&^T-Ywh8Ph@ciY1>s@UH< zb`hUR#FnD>fB`OtSuVy6$OTwJ>!{hsfu_=fZ35E(f;eUo48xH4Nq}ZQO(7zT6 z%kKyUdDutWuE>V_CJmu@Y&_zD@E%49-z4Tyo__Cxe6%Q2FV|AplPM(9-iwby$mfF9 z+;)B#%Yg@!u0iN z%6;7=$EdX(d9*Pg-7OG1fxSv~s(;cmNuZO37;V%_{d7GN#{iFB-9esdbn(vt`p3gF zptVod?0>;Xh`FcYBYB`tKNILU7~c2mdfz=G`nFxBFOHGHN1OIO)mh?=JEF zrg;BByx$S;OwiM&jTY~Z1%~9HP3sl;*Twti;(d+4r{aCk)PX&OIYS;riW^spXT118 zkp__s`GerbwbG+td%o-rzcC;y$KB_dMOID+uW&1_)S}8_%Y`1f@2JyYa$K!wL-Ju zLwc3Zf}!{E`yX7Bz!r`S+?(WRfkmywiYXq8v-*6G;L~$e%zdG!&>Sn=QD6z;7Imr! zCkOq2@maM^DEIKo6Yjimr%ju76ZW7s?Kbhw$9quv@yyVDSL==A5@AC9wAY6baFs%` z4*bkQ@i@c1CMb<9>~ns?m1qk@tUmi&abh}1d3Vz$?^9l;qiHu?0s8JiK5-c2xuRE9 z>CIf!Y!`^co1wm#a2W;;q)7ey<{P9?E~j!8f^W#TP=1Y`-%EKJdGM^fg>MYG$G7D{ zU1HLM?MgdBI)iiBUF5<}ajff`PQMmNKs(e7VD^a0g!X#l9kkY>F$@XO$7dF@iu8K` zIhsZ>J6Z8GD5dZKipteva0`hNHugzy_OIvvO9|{L)u*-BfKM8*%YfYmyl;)s&VWxE zu-ky|7%=Ixdb;cf42c& zHrgLE%Dav7J_9xwu-kx788A-o_7UaU)q2f1gTH2@9B+TtC|ivB9~tEb4S25sHyCiK z0UZXMYV_+i$`eM&;gJQbg?x-SJh(_NcaMN^ej)}wUjENQnE7E(3)jcx^K=-eySG9w zyEf=>z-U))Gw=rN_Uh%4_ze|Rj*43R2~S0^#$V4^*3Fl0S6Y8(y?=AP!`EEtYtWkt z7DWF*pkj-o-nZFN>#N@w+(Z?P!9Y#@Mn}-^chvgpH?ksMU1cb+W#PhN&%%W(eU(IF z;lgFUpplQy$0$1JFW9R6q53LR#Qz40+PK*UR>NwMf@}+0iPVUkkJYg`tPpSm3$cyJ zFGs!^H6^Tq1%UA(Pfrtu_6LUeivJzfAb%_A&*yxBg$tL~)K{$(vsmG)X2VL3fG-#d z)H~`!wY3ewfFK-yQ5c;RX2fIqKl_Xr?f-r;uRzzEhNU{p^B83VURu7z{udG#ecQT&nmIPwQLh|qzMaIF1rE$Ax=8%DL{P)?W&lA)gvDS zY!q7I5Zbf>bVAUSTHyVlAB2Ya(XtWcYUBcdK8(}|jdNhM3e;7hwU1qoas%p#6Cdgu z(X$>J=U^P#SHg;s3!vunpilKQay9ys7W+VB4$5Bis1&Ve1RpR>cn6@(G%H=#hxn*K zuPTAnd8GL}#Hc!_b)X*SpDu>SDi}M!5t%`{+(YeS3H99xdFn$n;tw5Mly@N!-NYwx-0pH*w!HWo(-Cc` zeKF?Jg1R+GmwGN&M>OM4A|EtP;&Y&U$V?};vtiuC=W^rIJPst#Oq>_dCOSBCFZ)mc z#&L;yEF*Iz;1lT%ltWgi!x??iLv_@bp2Yc3`}mWIOj1iy%l+t&dp>IeB-~6GiJOo# zT)yVsz}OQ=F{EF8nz5ruVal^@p<0`7{@uYvso{2#ptW!nneTpTj@D9^tZ`Qj11Yj^}n~Clbu~zy%FQ0 zoJw>GR^YQ>0ZO<<4#(lk7h~s!;k=^Vz;K*sM}Lt#M6X!1OC63A?QFwvC8AyCaGYr8 z0InQyg}ARmd+9Hxc-bj@1nhZs2YA%mIGg;n!8-D9Xx~1ZG>E_JuDhx>V9IP$k-=ji zSn2e-#n=tQaJP%Gn-g&*db^fkIKAC2;3&>Fr&<3S@HHzKJ4^hCvJ(q|nCMEz?kAkS z7CD6L1}<|5mrFPmxP&#EPdK{M+)VWKzBdw%m7^U%{O{9gZ?weA5DVFjtf6uvvaA?5 z8_9A*qbTE$5r2~(|J!vUP>y!uZ`7UH;;1hN?%^Sv;2Xui`cnHjTEh(esnp z=$zyRlf}d=_rg99#JWz*Nn_67Wag~8hB@b7&77tQEc4ui-i+??F;H8_Qp&tz;TQU>Y-uF4_G-s@n` z35T{+pIA=pBV@KDL1rX-$fN-_`)f!+LDzz@(uAyr;X!+N8L}jKqHQAHgm$AyW>XoP z4*sdUI-9ZIBhgtw(3E6iNf^tNk;*cvkV;Zm2KHV?j;$fxp33aOQOu6@wgYF+N$({( zq@y2Wr{9U4{X@{9=Q-qGN+_p`J)Fu$uSjm7KH!dB0c(|m)M4~VMSdyLt48@&)F#&5lxETQ-HK#7CdETg$c^MTaWtC< z8BK)jCPH=-G1kN!d#~OSdTi5k%`4)VAxLuUA^gfA9OcL#ISBlv>f$X2%Jp2_zo0Y0 zkshzrAq`D7o}R>Z&j4LgRj1-WK+ObQN_NJgxFM(zbaJg;}!K)wLYxzjf5)*1p>bMpwWyy zXt?i*jTaAx5VG@4+YEo)n$Q-`i;JeS`@8ZRZu){ezraq zV`XIp&CN^kPeB>12VkG30t?DzgYmnvDj)9T{w+8)M%GmN8@7<=2|8tvNo7rSO=a08 zA%y^}43&C>Zz|R_Cj0^aUB4X3z_6Y4DZR)K~lCBbC+GG@^c0!HUx5CEg+r zI4+x~^PGs&F$);_iqJt>Lj_F*VWP5ngo{A0-mk~s=!S6FRx~dNwB9dfQi&KR0+gHB zL{=xlow)I^vPR#gvg(>zB2CMb$;vAI_0=^SLjhk|eFauyb6G`TV-r43(>MrRAe3>} zV3n`FsV3mBuY)$wKgpEX!e8%et_hX}D>l^ngzSCIzRFP0S2nC-0>-0ud}w*2 zP6Z6ru}=@>%Mf{LsF_{YID2zV{p`v>aP}r&MZ@gcd9&xuHdcK^J^tmdU`?GbvB|$% zRs?GcDjE>E^cDCKcEi6v^;g2le*c}JhNYo;5tQ}R2Lo7rixoAiY68JfMQv$4hOJ`k zPu9|UT@9)RSAMAwA7lHiD}BMsR_7$_oZ^7L5}Sb?wU%I-HTBpuaTGgX_BI*?e_$#0 z6L>8S_!>#Xdr?>D+Ys8g(HDs08Z5Vke1R>+zCg7E>d(PoN6Z)ptr>PjFqcOxd{qa539_6{!lN*U_Q$u{JZu2B(C%Jf1e^Cig1D z{L929(k;9ztc|8qi+ah*rH{+F^qS))cAvz#pM!X|CfBh&&friJ9#0GY7rtWP z>EhRgBw_)8tFJQX1Ku+UJfKYr;6qn~KG6aEDdlM_z^fdLJqf%6a4r(v*9d+KDHT^9 zg5O4(ggn9TAzhFBLBRN3jNm9I_yL}v4XFWnf)kMTAWv|Tfp-8NLfQ*_4Del~1IV8P zybbrAgUBb|a}w`BbQhxE>$!?`xD|J>666W?AXOnx_p&9p|By_I0XHDgd=u|AbU!+U zv>ojUetkCL4agJx7SciF34RwThI|b0cSt?RQ_O7A9GyPFQY50k2Jn6(Pw-JAzaQ|V zktg_jBmW1$!nv@`x9Kp5JkcSz%g7TvV&snk-kNR93s6OR75oq!J5R?Gq<`d$=8kWR ztkK8^0goaPeS$CNK#gugf51C%_bEZ1V01p#0(tuP8lOe#LB1Q%a)VBj;0s79@I8QD zM1{^FUki9Y66ra?ygcX<@Fjrw*2>1h*WU{$FNExnZwCA-67lm0U=I@Q7lIkM>rtNW z%ilwyHU|LBi*PQZFU3H@NQ;oi_gnT;q%7p|4VS%ul#M(EYep?b+vWN_mF}1yLskUo z?nroobZ?|QLAv`Wxw(b(v#+fcj30Rr`ni)Y|N=fcLyO}@H{#@Tf>l>vXFzdAU((qFf*qOopH)4UlD z9K1Ev@FrI!jz*;B#%ZB2lEMG(WtYBw2Od56XxpQ)N4p;Fc{F=p-afvsc;A|R<@+}6 zYuMMkuVvrE`}XcTu&;Z6&;H*1>i#v4H9Xe*7(0-8z;Ph!K=y&W0~83gL6!&hcJCe7 PoAu~_cd`Cgng4$P*gYsv literal 0 HcmV?d00001 diff --git a/gui/lib/l10n/reboot_en.arb b/gui/lib/l10n/reboot_en.arb index bd91976..365d1b6 100644 --- a/gui/lib/l10n/reboot_en.arb +++ b/gui/lib/l10n/reboot_en.arb @@ -98,11 +98,13 @@ "settingsServerTypeDescription": "The type of game server to inject", "settingsServerTypeEmbeddedName": "Embedded", "settingsServerTypeCustomName": "Custom", - "settingsServerFileName": "Implementation", + "settingsOldServerFileName": "Game server (Before Fortnite Season 20)", + "settingsNewServerFileName": "Game server (After Fortnite Season 20)", "settingsServerFileDescription": "The file injected to create the game server", "settingsServerPortName": "Port", "settingsServerPortDescription": "The port the launcher expects the game server to be hosted on", - "settingsServerMirrorName": "Update mirror", + "settingsServerOldMirrorName": "Update mirror (Before Fortnite Season 20)", + "settingsServerNewMirrorName": "Update mirror (After Fortnite Season 20)", "settingsServerMirrorDescription": "The URL used to update the game server dll", "settingsServerMirrorPlaceholder": "mirror", "settingsServerTimerName": "Update timer", diff --git a/gui/lib/main.dart b/gui/lib/main.dart index e54d4aa..bf29107 100644 --- a/gui/lib/main.dart +++ b/gui/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter_acrylic/flutter_acrylic.dart'; import 'package:flutter_gen/gen_l10n/reboot_localizations.dart'; @@ -19,9 +20,9 @@ import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/messenger/implementation/error.dart'; import 'package:reboot_launcher/src/page/implementation/home_page.dart'; import 'package:reboot_launcher/src/util/os.dart'; +import 'package:reboot_launcher/src/util/url_protocol.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:system_theme/system_theme.dart'; -import 'package:url_protocol/url_protocol.dart'; import 'package:version/version.dart'; import 'package:window_manager/window_manager.dart'; @@ -146,7 +147,7 @@ Future _initVersion() async { Future _initUrlHandler() async { try { - registerProtocolHandler(kCustomUrlSchema, arguments: ['%s']); + registerUrlProtocol(kCustomUrlSchema, arguments: ['%s']); return null; }catch(error) { return error; diff --git a/gui/lib/src/controller/dll_controller.dart b/gui/lib/src/controller/dll_controller.dart index b953248..d6f588c 100644 --- a/gui/lib/src/controller/dll_controller.dart +++ b/gui/lib/src/controller/dll_controller.dart @@ -4,15 +4,11 @@ import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; -import 'package:http/http.dart' as http; import 'package:path/path.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:version/version.dart'; -import 'package:yaml/yaml.dart'; class DllController extends GetxController { static const String storageName = "dll_storage"; @@ -25,7 +21,8 @@ class DllController extends GetxController { late final TextEditingController memoryLeakDll; late final TextEditingController gameServerPort; late final Rx timer; - late final TextEditingController url; + late final TextEditingController beforeS20Mirror; + late final TextEditingController aboveS20Mirror; late final RxBool customGameServer; late final RxnInt timestamp; late final Rx status; @@ -43,8 +40,10 @@ class DllController extends GetxController { final timerIndex = _storage?.read("timer"); timer = Rx(timerIndex == null ? UpdateTimer.hour : UpdateTimer.values.elementAt(timerIndex)); timer.listen((value) => _storage?.write("timer", value.index)); - url = TextEditingController(text: _storage?.read("update_url") ?? kRebootDownloadUrl); - url.addListener(() => _storage?.write("update_url", url.text)); + beforeS20Mirror = TextEditingController(text: _storage?.read("update_url") ?? kRebootBelowS20DownloadUrl); + beforeS20Mirror.addListener(() => _storage?.write("update_url", beforeS20Mirror.text)); + aboveS20Mirror = TextEditingController(text: _storage?.read("old_update_url") ?? kRebootAboveS20DownloadUrl); + aboveS20Mirror.addListener(() => _storage?.write("new_update_url", aboveS20Mirror.text)); status = Rx(UpdateStatus.waiting); customGameServer = RxBool(_storage?.read("custom_game_server") ?? false); customGameServer.listen((value) => _storage?.write("custom_game_server", value)); @@ -68,7 +67,8 @@ class DllController extends GetxController { void resetServer() { gameServerPort.text = kDefaultGameServerPort; timer.value = UpdateTimer.hour; - url.text = kRebootDownloadUrl; + beforeS20Mirror.text = kRebootBelowS20DownloadUrl; + aboveS20Mirror.text = kRebootAboveS20DownloadUrl; status.value = UpdateStatus.waiting; customGameServer.value = false; timestamp.value = null; @@ -109,9 +109,15 @@ class DllController extends GetxController { duration: null ); } - final result = downloadRebootDll(url.text); - timestamp.value = await Future.wait([result, Future.delayed(const Duration(seconds: 1))], eagerError: false) - .then((_) => result); + await Future.wait( + [ + downloadRebootDll(rebootBeforeS20DllFile, beforeS20Mirror.text), + downloadRebootDll(rebootAboveS20DllFile, aboveS20Mirror.text), + Future.delayed(const Duration(seconds: 1)) + ], + eagerError: false + ); + timestamp.value = DateTime.now().millisecondsSinceEpoch; status.value = UpdateStatus.success; infoBarEntry?.close(); if(!silent) { @@ -160,7 +166,7 @@ class DllController extends GetxController { } } - return (rebootDllFile, false); + return (rebootBeforeS20DllFile, false); case InjectableDll.console: final ue4ConsoleFile = File(unrealEngineConsoleDll.text); return (ue4ConsoleFile, canonicalize(ue4ConsoleFile.path) != defaultPath); diff --git a/gui/lib/src/controller/hosting_controller.dart b/gui/lib/src/controller/hosting_controller.dart index f1fd440..ca63d4e 100644 --- a/gui/lib/src/controller/hosting_controller.dart +++ b/gui/lib/src/controller/hosting_controller.dart @@ -56,20 +56,36 @@ class HostingController extends GetxController { published = RxBool(false); showPassword = RxBool(false); instance = Rxn(); - final supabase = Supabase.instance.client; servers = Rxn(); - supabase.from("hosting_v2") - .stream(primaryKey: ['id']) - .map((event) => event.map((element) => FortniteServer.fromJson(element)).where((element) => element.ip.isNotEmpty).toSet()) - .listen((event) { - servers.value = event; - published.value = event.any((element) => element.id == uuid); - }); + _listenServers(); customLaunchArgs = TextEditingController(text: _storage?.read("custom_launch_args") ?? ""); customLaunchArgs.addListener(() => _storage?.write("custom_launch_args", customLaunchArgs.text)); _semaphore = Semaphore(); } + void _listenServers([int attempt = 0]) { + log("[SUPABASE] Listening..."); + final supabase = Supabase.instance.client; + supabase.from("hosting_v2") + .stream(primaryKey: ['id']) + .map((event) => event.map((element) => FortniteServer.fromJson(element)).where((element) => element.ip.isNotEmpty).toSet()) + .listen( + _onNewServer, + onError: (error) async { + log("[SUPABASE] Error: ${error}"); + await Future.delayed(Duration(seconds: attempt * 5)); + _listenServers(attempt + 1); + }, + cancelOnError: true + ); + } + + void _onNewServer(Set event) { + log("[SUPABASE] New event: ${event}"); + servers.value = event; + published.value = event.any((element) => element.id == uuid); + } + Future publishServer(String author, String version) async { try { _semaphore.acquire(); diff --git a/gui/lib/src/controller/settings_controller.dart b/gui/lib/src/controller/settings_controller.dart index 4286310..5a2d4f7 100644 --- a/gui/lib/src/controller/settings_controller.dart +++ b/gui/lib/src/controller/settings_controller.dart @@ -1,11 +1,9 @@ import 'dart:async'; -import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart' as http; -import 'package:path/path.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart'; @@ -21,7 +19,6 @@ class SettingsController extends GetxController { late final RxString language; late final Rx themeMode; late final RxBool firstRun; - late final RxBool debug; late double width; late double height; late double? offsetX; @@ -39,7 +36,6 @@ class SettingsController extends GetxController { language.listen((value) => _storage?.write("language", value)); firstRun = RxBool(_storage?.read("first_run_tutorial") ?? true); firstRun.listen((value) => _storage?.write("first_run_tutorial", value)); - debug = RxBool(false); } void saveWindowSize(Size size) { diff --git a/gui/lib/src/messenger/abstract/dialog.dart b/gui/lib/src/messenger/abstract/dialog.dart index 1c96404..ff7797b 100644 --- a/gui/lib/src/messenger/abstract/dialog.dart +++ b/gui/lib/src/messenger/abstract/dialog.dart @@ -300,7 +300,7 @@ class _DialogButtonState extends State { Widget get _primaryButton => Button( style: ButtonStyle( - backgroundColor: ButtonState.all(FluentTheme.of(context).accentColor) + backgroundColor: WidgetStateProperty.all(FluentTheme.of(context).accentColor) ), onPressed: widget.onTap!, child: Text(widget.text!), @@ -308,7 +308,7 @@ class _DialogButtonState extends State { Widget get _secondaryButton => Button( style: widget.color != null ? ButtonStyle( - backgroundColor: ButtonState.all(widget.color!) + backgroundColor: WidgetStateProperty.all(widget.color!) ) : null, onPressed: widget.onTap ?? _onDefaultSecondaryActionTap, child: Text(widget.text ?? translations.defaultDialogSecondaryAction), diff --git a/gui/lib/src/messenger/implementation/onboard.dart b/gui/lib/src/messenger/implementation/onboard.dart index 57c9962..77425e7 100644 --- a/gui/lib/src/messenger/implementation/onboard.dart +++ b/gui/lib/src/messenger/implementation/onboard.dart @@ -62,7 +62,7 @@ void _promptPlayVersion() { onTap: () async { onClose(); if(!hasBuilds) { - await VersionSelector.openDownloadDialog(closable: false); + await VersionSelector.openDownloadDialog(); } _promptServerBrowserPage(); } @@ -339,7 +339,7 @@ Widget _buildActionButton({ required void Function() onTap, }) => Button( style: themed ? ButtonStyle( - backgroundColor: ButtonState.all(FluentTheme.of(context).accentColor) + backgroundColor: WidgetStateProperty.all(FluentTheme.of(context).accentColor) ) : null, child: Text(label), onPressed: onTap diff --git a/gui/lib/src/messenger/implementation/profile.dart b/gui/lib/src/messenger/implementation/profile.dart index f892f0a..9e2847f 100644 --- a/gui/lib/src/messenger/implementation/profile.dart +++ b/gui/lib/src/messenger/implementation/profile.dart @@ -56,8 +56,8 @@ Future showProfileForm(BuildContext context) async{ suffix: Button( onPressed: () => showPassword.value = !showPassword.value, style: ButtonStyle( - shape: ButtonState.all(const CircleBorder()), - backgroundColor: ButtonState.all(Colors.transparent) + shape: WidgetStateProperty.all(const CircleBorder()), + backgroundColor: WidgetStateProperty.all(Colors.transparent) ), child: Icon( showPassword.value ? Icons.visibility_off : Icons.visibility, diff --git a/gui/lib/src/messenger/implementation/server.dart b/gui/lib/src/messenger/implementation/server.dart index cd5665e..b4286c3 100644 --- a/gui/lib/src/messenger/implementation/server.dart +++ b/gui/lib/src/messenger/implementation/server.dart @@ -257,8 +257,8 @@ extension ServerControllerDialog on BackendController { suffix: !showPasswordTrailing.value ? null : Button( onPressed: () => showPassword.value = !showPassword.value, style: ButtonStyle( - shape: ButtonState.all(const CircleBorder()), - backgroundColor: ButtonState.all(Colors.transparent) + shape: WidgetStateProperty.all(const CircleBorder()), + backgroundColor: WidgetStateProperty.all(Colors.transparent) ), child: Icon( showPassword.value ? FluentIcons.eye_off_24_regular : FluentIcons.eye_24_regular diff --git a/gui/lib/src/messenger/implementation/version.dart b/gui/lib/src/messenger/implementation/version.dart index 068788b..4f09453 100644 --- a/gui/lib/src/messenger/implementation/version.dart +++ b/gui/lib/src/messenger/implementation/version.dart @@ -5,11 +5,9 @@ import 'dart:isolate'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/messenger/abstract/dialog.dart'; -import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/util/types.dart'; import 'package:reboot_launcher/src/widget/file_selector.dart'; diff --git a/gui/lib/src/page/abstract/page.dart b/gui/lib/src/page/abstract/page.dart index 69350db..59bd82f 100644 --- a/gui/lib/src/page/abstract/page.dart +++ b/gui/lib/src/page/abstract/page.dart @@ -1,6 +1,5 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:get/get.dart'; -import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/messenger/implementation/onboard.dart'; import 'package:reboot_launcher/src/page/abstract/page_type.dart'; @@ -35,7 +34,6 @@ abstract class RebootPageState extends State with Autom crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildFirstLaunchInfo(), - _buildDebugInfo(), Expanded( child: _listView ) @@ -47,7 +45,6 @@ abstract class RebootPageState extends State with Autom crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildFirstLaunchInfo(), - _buildDebugInfo(), Expanded( child: Column( children: [ @@ -99,35 +96,6 @@ abstract class RebootPageState extends State with Autom ); }); - Widget _buildDebugInfo() => Obx(() { - if(!_settingsController.debug.value) { - return const SizedBox.shrink(); - } - - return Padding( - padding: const EdgeInsets.only( - bottom: 8.0 - ), - child: SizedBox( - width: double.infinity, - child: InfoBar( - title: Text("Debug mode is enabled"), - severity: InfoBarSeverity.warning, - isLong: true, - content: SizedBox( - width: double.infinity, - child: Text( "• Automatic dll injection is disabled\n" - "• The game server cannot start automatically\n" - "• The game server runs in a normal window") - ), - onClose: () { - _settingsController.debug.value = false; - }, - ), - ) - ); - }); - ListView get _listView => ListView.builder( itemCount: settings.length, itemBuilder: (context, index) => settings[index], diff --git a/gui/lib/src/page/implementation/backend_page.dart b/gui/lib/src/page/implementation/backend_page.dart index bde6d25..79955a2 100644 --- a/gui/lib/src/page/implementation/backend_page.dart +++ b/gui/lib/src/page/implementation/backend_page.dart @@ -5,7 +5,6 @@ import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/backend_controller.dart'; -import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/messenger/abstract/info_bar.dart'; import 'package:reboot_launcher/src/messenger/abstract/overlay.dart'; import 'package:reboot_launcher/src/messenger/implementation/data.dart'; diff --git a/gui/lib/src/page/implementation/browser_page.dart b/gui/lib/src/page/implementation/browser_page.dart index c802350..febc280 100644 --- a/gui/lib/src/page/implementation/browser_page.dart +++ b/gui/lib/src/page/implementation/browser_page.dart @@ -276,8 +276,8 @@ class _BrowsePageState extends RebootPageState { _filterControllerStream.add(""); }, style: ButtonStyle( - backgroundColor: ButtonState.all(Colors.transparent), - shape: ButtonState.all(Border()) + backgroundColor: WidgetStateProperty.all(Colors.transparent), + shape: WidgetStateProperty.all(Border()) ), child: _searchBarIconData ); diff --git a/gui/lib/src/page/implementation/home_page.dart b/gui/lib/src/page/implementation/home_page.dart index 8d292b5..e7d9da5 100644 --- a/gui/lib/src/page/implementation/home_page.dart +++ b/gui/lib/src/page/implementation/home_page.dart @@ -498,7 +498,7 @@ class _HomePageState extends State with WindowListener, AutomaticKeepA decoration: BoxDecoration( color: ButtonThemeData.uncheckedInputColor( FluentTheme.of(context), - pageIndex.value == index ? {ButtonStates.hovering} : states, + pageIndex.value == index ? {WidgetState.hovered} : states, transparentWhenNone: true, ), borderRadius: BorderRadius.all(Radius.circular(6.0)) @@ -527,12 +527,12 @@ class _HomePageState extends State with WindowListener, AutomaticKeepA stream: pagesController.stream, builder: (context, _) => Button( style: ButtonStyle( - padding: ButtonState.all(const EdgeInsets.symmetric( + padding: WidgetStateProperty.all(const EdgeInsets.symmetric( vertical: 12.0, horizontal: 16.0 )), - backgroundColor: ButtonState.all(Colors.transparent), - shape: ButtonState.all(Border()) + backgroundColor: WidgetStateProperty.all(Colors.transparent), + shape: WidgetStateProperty.all(Border()) ), onPressed: appStack.isEmpty && !inDialog ? null : () { if(inDialog) { diff --git a/gui/lib/src/page/implementation/host_page.dart b/gui/lib/src/page/implementation/host_page.dart index 1d74321..44aea3c 100644 --- a/gui/lib/src/page/implementation/host_page.dart +++ b/gui/lib/src/page/implementation/host_page.dart @@ -155,8 +155,8 @@ class _HostingPageState extends RebootPageState { suffix: Button( onPressed: () => _hostingController.showPassword.value = !_hostingController.showPassword.value, style: ButtonStyle( - shape: ButtonState.all(const CircleBorder()), - backgroundColor: ButtonState.all(Colors.transparent) + shape: WidgetStateProperty.all(const CircleBorder()), + backgroundColor: WidgetStateProperty.all(Colors.transparent) ), child: Icon( _hostingController.showPassword.value ? FluentIcons.eye_off_24_filled : FluentIcons.eye_24_filled, @@ -221,12 +221,11 @@ class _HostingPageState extends RebootPageState { content: Obx(() => DropDownButton( onOpen: () => inDialog = true, onClose: () => inDialog = false, - leading: Text(_settingsController.debug.value ? GameServerType.window.translatedName : _hostingController.type.value.translatedName), + leading: Text(_hostingController.type.value.translatedName), items: GameServerType.values.map((entry) => MenuFlyoutItem( text: Text(entry.translatedName), onPressed: () => _hostingController.type.value = entry - )).toList(), - disabled: _settingsController.debug.value + )).toList() )), ), SettingTile( @@ -280,7 +279,8 @@ class _HostingPageState extends RebootPageState { children: [ _internalFilesServerType, _internalFilesUpdateTimer, - _internalFilesServerSource + _internalFilesOldServerSource, + _internalFilesNewServerSource, ], ); @@ -318,13 +318,13 @@ class _HostingPageState extends RebootPageState { )) ); - Widget get _internalFilesServerSource => Obx(() { + Widget get _internalFilesOldServerSource => Obx(() { if(!_dllController.customGameServer.value) { return SettingTile( icon: Icon( FluentIcons.globe_24_regular ), - title: Text(translations.settingsServerMirrorName), + title: Text(translations.settingsServerOldMirrorName), subtitle: Text(translations.settingsServerMirrorDescription), contentWidth: SettingTile.kDefaultContentWidth + 30, content: Row( @@ -332,7 +332,7 @@ class _HostingPageState extends RebootPageState { Expanded( child: TextFormBox( placeholder: translations.settingsServerMirrorPlaceholder, - controller: _dllController.url, + controller: _dllController.beforeS20Mirror, onChanged: (value) { if(Uri.tryParse(value) != null) { _dllController.updateGameServerDll(force: true); @@ -343,7 +343,7 @@ class _HostingPageState extends RebootPageState { const SizedBox(width: 8.0), Button( style: ButtonStyle( - padding: ButtonState.all(EdgeInsets.zero) + padding: WidgetStateProperty.all(EdgeInsets.zero) ), onPressed: () => _dllController.updateGameServerDll(force: true), child: SizedBox.square( @@ -356,10 +356,10 @@ class _HostingPageState extends RebootPageState { const SizedBox(width: 8.0), Button( style: ButtonStyle( - padding: ButtonState.all(EdgeInsets.zero) + padding: WidgetStateProperty.all(EdgeInsets.zero) ), onPressed: () { - _dllController.url.text = kRebootDownloadUrl; + _dllController.beforeS20Mirror.text = kRebootBelowS20DownloadUrl; _dllController.updateGameServerDll(force: true); }, child: SizedBox.square( @@ -374,7 +374,75 @@ class _HostingPageState extends RebootPageState { ); }else { return createFileSetting( - title: translations.settingsServerFileName, + title: translations.settingsOldServerFileName, + description: translations.settingsServerFileDescription, + controller: _dllController.gameServerDll, + onReset: () { + final path = _dllController.getDefaultDllPath(InjectableDll.reboot); + _dllController.gameServerDll.text = path; + _dllController.downloadCriticalDllInteractive(path); + } + ); + } + }); + + Widget get _internalFilesNewServerSource => Obx(() { + if(!_dllController.customGameServer.value) { + return SettingTile( + icon: Icon( + FluentIcons.globe_24_regular + ), + title: Text(translations.settingsServerNewMirrorName), + subtitle: Text(translations.settingsServerMirrorDescription), + contentWidth: SettingTile.kDefaultContentWidth + 30, + content: Row( + children: [ + Expanded( + child: TextFormBox( + placeholder: translations.settingsServerMirrorPlaceholder, + controller: _dllController.aboveS20Mirror, + onChanged: (value) { + if(Uri.tryParse(value) != null) { + _dllController.updateGameServerDll(force: true); + } + }, + ), + ), + const SizedBox(width: 8.0), + Button( + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero) + ), + onPressed: () => _dllController.updateGameServerDll(force: true), + child: SizedBox.square( + dimension: 30, + child: Icon( + FluentIcons.arrow_download_24_regular + ), + ) + ), + const SizedBox(width: 8.0), + Button( + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero) + ), + onPressed: () { + _dllController.aboveS20Mirror.text = kRebootBelowS20DownloadUrl; + _dllController.updateGameServerDll(force: true); + }, + child: SizedBox.square( + dimension: 30, + child: Icon( + FluentIcons.arrow_reset_24_regular + ), + ) + ) + ], + ) + ); + }else { + return createFileSetting( + title: translations.settingsNewServerFileName, description: translations.settingsServerFileDescription, controller: _dllController.gameServerDll, onReset: () { diff --git a/gui/lib/src/page/implementation/play_page.dart b/gui/lib/src/page/implementation/play_page.dart index e26e7da..9a501f3 100644 --- a/gui/lib/src/page/implementation/play_page.dart +++ b/gui/lib/src/page/implementation/play_page.dart @@ -7,7 +7,6 @@ import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/messenger/abstract/overlay.dart'; import 'package:reboot_launcher/src/messenger/implementation/data.dart'; -import 'package:reboot_launcher/src/messenger/implementation/onboard.dart'; import 'package:reboot_launcher/src/page/abstract/page.dart'; import 'package:reboot_launcher/src/page/abstract/page_type.dart'; import 'package:reboot_launcher/src/util/translations.dart'; @@ -38,7 +37,6 @@ class PlayPage extends RebootPage { } class _PlayPageState extends RebootPageState { - final SettingsController _settingsController = Get.find(); final GameController _gameController = Get.find(); final DllController _dllController = Get.find(); diff --git a/gui/lib/src/page/implementation/settings_page.dart b/gui/lib/src/page/implementation/settings_page.dart index ab5983e..ae01871 100644 --- a/gui/lib/src/page/implementation/settings_page.dart +++ b/gui/lib/src/page/implementation/settings_page.dart @@ -6,7 +6,6 @@ import 'package:get/get.dart'; import 'package:reboot_common/common.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/implementation/data.dart'; import 'package:reboot_launcher/src/page/abstract/page.dart'; import 'package:reboot_launcher/src/page/abstract/page_type.dart'; import 'package:reboot_launcher/src/util/translations.dart'; @@ -42,7 +41,6 @@ class _SettingsPageState extends RebootPageState { List get settings => [ _language, _theme, - _debugMode, _installationDirectory, ]; @@ -100,29 +98,6 @@ class _SettingsPageState extends RebootPageState { child: Text(translations.settingsUtilsInstallationDirectoryContent), ) ); - - SettingTile get _debugMode => SettingTile( - icon: Icon( - FluentIcons.developer_board_24_regular - ), - title: Text("Debug mode"), - subtitle: Text("Whether the launcher should disable automatic features for troubleshooting"), - contentWidth: null, - content: Row( - children: [ - Obx(() => Text( - _settingsController.debug.value ? translations.on : translations.off - )), - const SizedBox( - width: 16.0 - ), - Obx(() => ToggleSwitch( - checked: _settingsController.debug.value, - onChanged: (value) => _settingsController.debug.value = value - )) - ], - ) - ); } extension _ThemeModeExtension on ThemeMode { diff --git a/gui/lib/src/util/os.dart b/gui/lib/src/util/os.dart index 470de94..fc22ab9 100644 --- a/gui/lib/src/util/os.dart +++ b/gui/lib/src/util/os.dart @@ -24,10 +24,13 @@ bool get isWin11 { return intBuild != null && intBuild > 22000; } -Future openFolderPicker(String title) async => - await FilePicker.platform.getDirectoryPath(dialogTitle: title); +Future openFolderPicker(String title) async { + FilePicker.platform = FilePickerWindows(); + return await FilePicker.platform.getDirectoryPath(dialogTitle: title); +} Future openFilePicker(String extension) async { + FilePicker.platform = FilePickerWindows(); var result = await FilePicker.platform.pickFiles( type: FileType.custom, allowMultiple: false, @@ -93,7 +96,7 @@ class IVirtualDesktop extends IUnknown { throw WindowsException(code); } - return convertFromHString(result.value); + return _convertFromHString(result.value); } } @@ -280,7 +283,7 @@ class _IVirtualDesktopManagerInternal extends IUnknown { HRESULT Function(Pointer, COMObject, Int8)>>>() .value .asFunction()( - ptr.ref.lpVtbl, desktop.ptr.ref, convertToHString(newName)); + ptr.ref.lpVtbl, desktop.ptr.ref, _convertToHString(newName)); if (code != 0) { throw WindowsException(code); } @@ -369,7 +372,7 @@ List _getHWnds(int pid, String? excludedWindowName) { result.ref.excluded = excludedWindowName.toNativeUtf16(); } - EnumWindows(Pointer.fromFunction(_filter, TRUE), result.address); + EnumWindows(Pointer.fromFunction(_filter, TRUE), result.address); final length = result.ref.HWndLength; final HWndsPointer = result.ref.HWnd; if(HWndsPointer == nullptr) { @@ -397,7 +400,7 @@ class VirtualDesktopManager { } final hr = CoInitializeEx( - nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + nullptr, COINIT.COINIT_APARTMENTTHREADED | COINIT.COINIT_DISABLE_OLE1DDE); if (FAILED(hr)) { throw WindowsException(hr); } @@ -468,3 +471,19 @@ class VirtualDesktopManager { void setDesktopName(IVirtualDesktop desktop, String newName) => windowManager.setDesktopName(desktop, newName); } + +String _convertFromHString(int hstring) => + WindowsGetStringRawBuffer(hstring, nullptr).toDartString(); + +int _convertToHString(String string) { + final hString = calloc(); + final stringPtr = string.toNativeUtf16(); + try { + final hr = WindowsCreateString(stringPtr, string.length, hString); + if (FAILED(hr)) throw WindowsException(hr); + return hString.value; + } finally { + free(stringPtr); + free(hString); + } +} \ No newline at end of file diff --git a/gui/lib/src/util/url_protocol.dart b/gui/lib/src/util/url_protocol.dart new file mode 100644 index 0000000..30b9599 --- /dev/null +++ b/gui/lib/src/util/url_protocol.dart @@ -0,0 +1,63 @@ +import 'dart:io'; + +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart'; + +final _hive = HKEY_CURRENT_USER; + +void registerUrlProtocol(String scheme, {String? executable, List? arguments}) { + final prefix = _regPrefix(scheme); + final capitalized = scheme[0].toUpperCase() + scheme.substring(1); + final args = _getArguments(arguments).map((a) => _sanitize(a)); + final cmd = + '${executable ?? Platform.resolvedExecutable} ${args.join(' ')}'; + _regCreateStringKey(_hive, prefix, '', 'URL:$capitalized'); + _regCreateStringKey(_hive, prefix, 'URL Protocol', ''); + _regCreateStringKey(_hive, prefix + '\\shell\\open\\command', '', cmd); +} + +void unregisterUrlProtocol(String scheme) { + final txtKey = TEXT(_regPrefix(scheme)); + try { + RegDeleteTree(HKEY_CURRENT_USER, txtKey); + } finally { + free(txtKey); + } +} + +String _regPrefix(String scheme) => 'SOFTWARE\\Classes\\$scheme'; + +int _regCreateStringKey(int hKey, String key, String valueName, String data) { + final txtKey = TEXT(key); + final txtValue = TEXT(valueName); + final txtData = TEXT(data); + try { + return RegSetKeyValue( + hKey, + txtKey, + txtValue, + REG_VALUE_TYPE.REG_SZ, + txtData, + txtData.length * 2 + 2, + ); + } finally { + free(txtKey); + free(txtValue); + free(txtData); + } +} + +String _sanitize(String value) { + value = value.replaceAll(r'%s', '%1').replaceAll(r'"', '\\"'); + return '"$value"'; +} + +List _getArguments(List? arguments) { + if (arguments == null) return ['%s']; + + if (arguments.isEmpty && !arguments.any((e) => e.contains('%s'))) { + throw ArgumentError('arguments must contain at least 1 instance of "%s"'); + } + + return arguments; +} \ No newline at end of file diff --git a/gui/lib/src/widget/file_setting_tile.dart b/gui/lib/src/widget/file_setting_tile.dart index 796216a..5da3d46 100644 --- a/gui/lib/src/widget/file_setting_tile.dart +++ b/gui/lib/src/widget/file_setting_tile.dart @@ -1,7 +1,7 @@ import 'dart:io'; -import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; import 'package:fluent_ui/fluent_ui.dart' as fluentIcons show FluentIcons; +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'; @@ -23,7 +23,7 @@ SettingTile createFileSetting({required String title, required String descriptio ), title: Text(title), subtitle: Text(description), - contentWidth: SettingTile.kDefaultContentWidth + _kButtonDimensions * 2 + _kButtonSpacing * 2, + contentWidth: SettingTile.kDefaultContentWidth + _kButtonDimensions, content: Row( children: [ Expanded( @@ -47,7 +47,7 @@ SettingTile createFileSetting({required String title, required String descriptio message: translations.selectFile, child: Button( style: ButtonStyle( - padding: ButtonState.all(EdgeInsets.zero) + padding: WidgetStateProperty.all(EdgeInsets.zero) ), onPressed: () => _onPressed(selecting, controller), child: SizedBox.square( @@ -68,7 +68,7 @@ SettingTile createFileSetting({required String title, required String descriptio message: translations.reset, child: Button( style: ButtonStyle( - padding: ButtonState.all(EdgeInsets.zero) + padding: WidgetStateProperty.all(EdgeInsets.zero) ), onPressed: onReset, child: SizedBox.square( diff --git a/gui/lib/src/widget/game_start_button.dart b/gui/lib/src/widget/game_start_button.dart index 19ba49e..130c7e3 100644 --- a/gui/lib/src/widget/game_start_button.dart +++ b/gui/lib/src/widget/game_start_button.dart @@ -121,7 +121,7 @@ class _LaunchButtonState extends State { return; } log("[${host ? 'HOST' : 'GAME'}] Backend works"); - final serverType = _settingsController.debug.value ? GameServerType.window : _hostingController.type.value; + final serverType = _hostingController.type.value; log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($serverType)"); final linkedHostingInstance = await _startMatchMakingServer(version, host, serverType, false); log("[${host ? 'HOST' : 'GAME'}] Implicit game server result: $linkedHostingInstance"); @@ -159,11 +159,6 @@ class _LaunchButtonState extends State { return null; } - if(_settingsController.debug.value) { - log("[${host ? 'HOST' : 'GAME'}] The user is on debug mode, not asking for auto server"); - return null; - } - if(!forceLinkedHosting && _backendController.type.value == ServerType.embedded && !isLocalHost(_backendController.gameServerAddress.text)) { log("[${host ? 'HOST' : 'GAME'}] Backend is not set to embedded and/or not pointing to the local game server"); return null; @@ -248,7 +243,7 @@ class _LaunchButtonState extends State { }else{ _gameController.instance.value = instance; } - await _injectOrShowError(InjectableDll.cobalt, host); + await _injectOrShowError(InjectableDll.sinum, host); log("[${host ? 'HOST' : 'GAME'}] Finished creating game instance"); return instance; } @@ -280,13 +275,7 @@ class _LaunchButtonState extends State { line: line, host: host, onShutdown: () => _onStop(reason: _StopReason.normal), - onTokenError: () { - if(_settingsController.debug.value) { - log("[PROCESS] Ignoring token error because debug mode is on"); - }else { - _onStop(reason: _StopReason.tokenError); - } - }, + onTokenError: () => _onStop(reason: _StopReason.tokenError), onBuildCorrupted: () { if(instance == null) { return; @@ -687,10 +676,6 @@ class _LaunchButtonState extends State { } log("[${hosting ? 'HOST' : 'GAME'}] Trying to inject ${injectable.name}..."); - if(_settingsController.debug.value) { - return; - } - await injectDll(gameProcess, dllPath); instance.injectedDlls.add(injectable); log("[${hosting ? 'HOST' : 'GAME'}] Injected ${injectable.name}"); @@ -742,7 +727,7 @@ class _LaunchButtonState extends State { loading: true, duration: null, action: Obx(() { - if(_settingsController.debug.value || _hostingController.started.value || linkedHosting) { + if(_hostingController.started.value || linkedHosting) { return const SizedBox.shrink(); } diff --git a/gui/lib/src/widget/version_selector.dart b/gui/lib/src/widget/version_selector.dart index e47fef7..089def7 100644 --- a/gui/lib/src/widget/version_selector.dart +++ b/gui/lib/src/widget/version_selector.dart @@ -15,11 +15,11 @@ import 'package:url_launcher/url_launcher.dart'; class VersionSelector extends StatefulWidget { const VersionSelector({Key? key}) : super(key: key); - static Future openDownloadDialog({bool closable = true}) => showRebootDialog( + static Future openDownloadDialog() => showRebootDialog( builder: (context) => AddVersionDialog( - closable: closable, + closable: true, ), - dismissWithEsc: closable + dismissWithEsc: true ); @override diff --git a/gui/pubspec.yaml b/gui/pubspec.yaml index 93b094f..338b5d6 100644 --- a/gui/pubspec.yaml +++ b/gui/pubspec.yaml @@ -17,53 +17,50 @@ dependencies: path: ./../common # Windows UI 3 - fluent_ui: ^4.8.7 + fluent_ui: ^4.9.1 flutter_acrylic: path: ./dependencies/flutter_acrylic - fluentui_system_icons: ^1.1.238 - system_theme: ^2.0.0 + fluentui_system_icons: ^1.1.258 + system_theme: ^3.1.1 skeletons: git: url: https://github.com/talok/skeletons ref: main # Window management - bitsdojo_window: ^0.1.5 - window_manager: ^0.3.8 + bitsdojo_window: ^0.1.6 + window_manager: ^0.4.2 # Extract zip archives (for example the reboot.zip) - archive: ^3.3.1 + archive: ^3.6.1 # Cryptographic functions - crypto: ^3.0.2 bcrypt: ^1.1.3 - pointycastle: ^3.7.3 + pointycastle: ^3.9.1 # Async helpers - async: ^2.8.2 + async: ^2.11.0 sync: ^0.3.0 # State management - get: ^4.6.5 + get: ^4.6.6 # Native utilities clipboard: ^0.1.3 - app_links: ^6.0.2 - url_protocol: ^1.0.0 + app_links: ^6.3.2 windows_taskbar: ^1.1.2 - file_picker: ^8.0.3 - url_launcher: ^6.1.5 + file_picker: ^8.1.2 + url_launcher: ^6.3.0 local_notifier: ^0.1.6 # Server browser - supabase_flutter: ^2.5.2 - uuid: ^3.0.6 + supabase_flutter: ^2.7.0 dart_ipify: ^1.1.1 # Storage - get_storage: ^2.0.3 + get_storage: ^2.1.1 universal_disk_space: ^0.2.3 - path: ^1.8.3 + path: ^1.9.0 # Translations intl: any @@ -71,23 +68,17 @@ dependencies: # Auto updater yaml: ^3.1.2 - package_info_plus: ^8.0.0 + package_info_plus: ^8.0.2 version: ^3.0.2 # Validate profile email_validator: ^3.0.0 -dependency_overrides: - xml: ^6.3.0 - http: ^0.13.5 - win32: ^3.0.0 - ffi: ^2.0.0 - dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^4.0.0 + flutter_lints: ^5.0.0 flutter: uses-material-design: true diff --git a/redirect/Core/Constants.h b/redirect/Core/Constants.h new file mode 100644 index 0000000..eab0f82 --- /dev/null +++ b/redirect/Core/Constants.h @@ -0,0 +1,13 @@ +// Copyright (c) 2024 Project Nova LLC + +#pragma once + +namespace Constants +{ + constexpr auto API_URL = L"http://localhost:3551"; + + constexpr auto ProcessRequest = L"Could not set libcurl options for easy handle, processing HTTP request failed. Increase verbosity for additional information."; + constexpr auto ProcessRequest_C2 = L"STAT_FCurlHttpRequest_ProcessRequest"; + constexpr auto URLOffset = L"ProcessRequest failed. URL '%s' is not a valid HTTP request. %p"; + constexpr auto Realloc = L"AbilitySystem.Debug.NextTarget"; +} \ No newline at end of file diff --git a/redirect/Core/Core.cpp b/redirect/Core/Core.cpp new file mode 100644 index 0000000..36eb653 --- /dev/null +++ b/redirect/Core/Core.cpp @@ -0,0 +1,11 @@ +// Copyright (c) 2024 Project Nova LLC + +#include "Core.h" + +void Core::Init() +{ + FMemory::_Realloc = Memcury::Scanner::FindStringRef(Constants::Realloc) + .ScanFor({ Memcury::ASM::MNEMONIC::CALL }) + .RelativeOffset(1) + .GetAs(); +} \ No newline at end of file diff --git a/redirect/Core/Core.h b/redirect/Core/Core.h new file mode 100644 index 0000000..e86e31e --- /dev/null +++ b/redirect/Core/Core.h @@ -0,0 +1,16 @@ +// Copyright (c) 2024 Project Nova LLC + +#pragma once + +#include "..\Utilities\memcury.h" + +#include "Constants.h" + +#include "Unreal\Memory.h" +#include "Unreal\Array.h" +#include "Unreal\String.h" + +namespace Core +{ + void Init(); +} \ No newline at end of file diff --git a/redirect/Core/Unreal/Array.h b/redirect/Core/Unreal/Array.h new file mode 100644 index 0000000..31eeca6 --- /dev/null +++ b/redirect/Core/Unreal/Array.h @@ -0,0 +1,143 @@ +// Copyright (c) 2024 Project Nova LLC + +#pragma once +#include +#include "Memory.h" + +template +class TArray +{ + friend class FString; + + T* Data; + int32_t NumElements; + int32_t MaxElements; + +public: + + inline TArray() + { + Data = nullptr; + NumElements = 0; + MaxElements = 0; + }; + + inline void Free() + { + FMemory::Free(Data); + Data = nullptr; + NumElements = 0; + MaxElements = 0; + } + + inline void Reset() + { + Free(); + } + + inline auto GetData() + { + return Data; + } + + inline int GetCount() const + { + return NumElements; + } + + inline int Num() const + { + return NumElements; + } + + inline auto& Get(const int Index) + { + return Data[Index]; + } + + inline auto& First() + { + return Get(0); + } + + inline auto GetRef(const int Index, int Size = sizeof(T)) + { + return (T*)((uint8_t*)Data + (Index * Size)); + } + + inline T& operator[](int i) + { + return Get(i); + }; + + inline const T& operator[](int i) const + { + return Get(i); + }; + + inline bool Remove(const int Index, int Size = sizeof(T)) + { + if (Index < NumElements) + { + if (Index != NumElements - 1) + Get(Index) = Get(NumElements - 1); + + --NumElements; + + return true; + } + return false; + }; + + inline bool Any(std::function Func) + { + for (int i = 0; i < NumElements; ++i) + { + if (Func(Get(i))) + return true; + } + return false; + } + + inline T Select(std::function Func) + { + for (int i = 0; i < NumElements; ++i) + { + if (Func(Get(i))) + return Get(i); + } + + return NULL; + } + + inline void ForEach(std::function Func) + { + for (int i = 0; i < NumElements; ++i) + { + Func(Get(i)); + } + } + + inline int Count(std::function Func) + { + int Num = 0; + + for (int i = 0; i < NumElements; ++i) + { + if (Func(Get(i))) + Num++; + } + return Num; + } + + inline int Find(const T& Item) + { + for (int i = 0; i < NumElements; i++) + { + if (this->operator[](i) == Item) + return i; + } + + return -1; + } +}; \ No newline at end of file diff --git a/redirect/Core/Unreal/Memory.h b/redirect/Core/Unreal/Memory.h new file mode 100644 index 0000000..b33a7a1 --- /dev/null +++ b/redirect/Core/Unreal/Memory.h @@ -0,0 +1,92 @@ +// Copyright (c) 2024 Project Nova LLC + +#pragma once +#include +#include + +class FMemory +{ +public: + static inline void* (*_Realloc)(void*, size_t, int64_t); + + static void Free(void* Data) + { + _Realloc(Data, 0, 0); + } + + static void* Malloc(size_t Size) + { + return _Realloc(0, Size, 0); + } + + static void* Realloc(void* Data, size_t NewSize) + { + return _Realloc(Data, NewSize, 0); + } + + static void* Memmove(void* Dest, const void* Src, size_t Count) + { + return memmove(Dest, Src, Count); + } + + static int Memcmp(const void* Buf1, const void* Buf2, size_t Count) + { + return memcmp(Buf1, Buf2, Count); + } + + static void* Memset(void* Dest, uint8_t Char, size_t Count) + { + return memset(Dest, Char, Count); + } + + template< class T > + static void Memset(T& Src, uint8_t ValueToSet) + { + Memset(&Src, ValueToSet, sizeof(T)); + } + + static void* Memzero(void* Dest, size_t Count) + { + return ZeroMemory(Dest, Count); + } + + template + static void Memzero(T& Src) + { + Memzero(&Src, sizeof(T)); + } + + static void* Memcpy(void* Dest, const void* Src, size_t Count) + { + return memcpy(Dest, Src, Count); + } + + template + static void Memcpy(T& Dest, const T& Src) + { + Memcpy(&Dest, &Src, sizeof(T)); + } + + static void* Calloc(size_t NumElements, size_t ElementSize) + { + auto TotalSize = NumElements * ElementSize; + auto Data = FMemory::Malloc(TotalSize); + + if (!Data) + return NULL; + + FMemory::Memzero(Data, TotalSize); + + return Data; + } + + static char* Strdup(const char* Str) + { + auto StrLen = strlen(Str) + 1; + auto StrDup = (char*)FMemory::Malloc(StrLen); + + FMemory::Memcpy(StrDup, Str, StrLen); + + return StrDup; + } +}; \ No newline at end of file diff --git a/redirect/Core/Unreal/String.h b/redirect/Core/Unreal/String.h new file mode 100644 index 0000000..5b54328 --- /dev/null +++ b/redirect/Core/Unreal/String.h @@ -0,0 +1,54 @@ +// Copyright (c) 2024 Project Nova LLC + +#pragma once +#include "Array.h" +#include "Memory.h" + +class FString : private TArray +{ +public: + + inline FString() + { + Data = nullptr; + NumElements = 0; + MaxElements = 0; + } + + inline FString(const char* Other) + { + if (Other) + { + auto NumCharacters = (int)std::strlen(Other); + MaxElements = NumElements = NumCharacters + 1; + + Data = static_cast(FMemory::Malloc(NumElements * sizeof(wchar_t))); + + size_t ConvertedChars = 0; + mbstowcs_s(&ConvertedChars, Data, NumElements, Other, _TRUNCATE); + } + else + { + MaxElements = NumElements = 0; + Data = nullptr; + } + }; + + inline FString(const wchar_t* Other) + { + MaxElements = NumElements = *Other ? (int)std::wcslen(Other) + 1 : 0; + + if (NumElements && Other) + { + Data = static_cast(FMemory::Malloc(NumElements * 2)); + + memcpy_s(Data, NumElements * 2, Other, NumElements * 2); + } + }; + + + inline auto c_str() + { + return Data; + } +}; \ No newline at end of file diff --git a/redirect/Main.cpp b/redirect/Main.cpp new file mode 100644 index 0000000..3c5d452 --- /dev/null +++ b/redirect/Main.cpp @@ -0,0 +1,21 @@ +// Copyright (c) 2024 Project Nova LLC + +#include "framework.h" + +static void Main() +{ + Sleep(7500); + + Core::Init(); + Sinum::Init(); +} + +bool DllMain(HMODULE hModule, DWORD dwReason, void* lpReserved) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + Windows::Thread::Create(Main); + } + + return true; +} \ No newline at end of file diff --git a/redirect/README.md b/redirect/README.md new file mode 100644 index 0000000..af7172a --- /dev/null +++ b/redirect/README.md @@ -0,0 +1,5 @@ +# Sinum Windows + +https://github.com/projectnovafn/Sinum/tree/main/Windows + +Modified to point to http://localhost:3551 \ No newline at end of file diff --git a/redirect/Sinum.sln b/redirect/Sinum.sln new file mode 100644 index 0000000..8d4591e --- /dev/null +++ b/redirect/Sinum.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34031.279 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Sinum", "Sinum.vcxproj", "{E7291B57-1B5B-497C-9C2E-C78A556A06CF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E7291B57-1B5B-497C-9C2E-C78A556A06CF}.Release|x64.ActiveCfg = Release|x64 + {E7291B57-1B5B-497C-9C2E-C78A556A06CF}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7975A2E1-0078-4AF8-AB10-0A71112857A5} + EndGlobalSection +EndGlobal diff --git a/redirect/Sinum.vcxproj b/redirect/Sinum.vcxproj new file mode 100644 index 0000000..a0be3a0 --- /dev/null +++ b/redirect/Sinum.vcxproj @@ -0,0 +1,162 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {e7291b57-1b5b-497c-9c2e-c78a556a06cf} + Sinum + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpplatest + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpplatest + MaxSpeed + + + Windows + true + true + false + false + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/redirect/Sinum.vcxproj.filters b/redirect/Sinum.vcxproj.filters new file mode 100644 index 0000000..abf4cda --- /dev/null +++ b/redirect/Sinum.vcxproj.filters @@ -0,0 +1,57 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/redirect/Sinum.vcxproj.user b/redirect/Sinum.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/redirect/Sinum.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/redirect/Sinum/Sinum.cpp b/redirect/Sinum/Sinum.cpp new file mode 100644 index 0000000..86ca8f1 --- /dev/null +++ b/redirect/Sinum/Sinum.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2024 Project Nova LLC + +#include "Sinum.h" + +bool Sinum::ProcessRequestHook(FCurlHttpRequest* Request) +{ + std::wstring URL(Request->GetURL().c_str()); + size_t PathIndex = URL.find(L"ol.epicgames.com"); + + if (PathIndex != std::wstring::npos) + { + auto Path = URL.substr(PathIndex + 16); + auto NewURL = Constants::API_URL + Path; + + Request->SetURL(NewURL.c_str()); + } + + return _ProcessRequest(Request); +} + +void Sinum::Init() +{ + auto StringRef = Memcury::Scanner::FindStringRef(Constants::ProcessRequest); + if (StringRef.IsValid()) + { + _ProcessRequest = StringRef + .ScanFor({ 0x48, 0x81, 0xEC }, false) + .ScanFor({ 0x40 }, false) + .GetAs(); + } + else + { + _ProcessRequest = Memcury::Scanner::FindStringRef(Constants::ProcessRequest_C2) + .ScanFor({ 0x4C, 0x8B, 0xDC }, false) + .GetAs(); + } + + *Memcury::Scanner::FindPointerRef(_ProcessRequest) + .GetAs() = ProcessRequestHook; +} \ No newline at end of file diff --git a/redirect/Sinum/Sinum.h b/redirect/Sinum/Sinum.h new file mode 100644 index 0000000..e9fb292 --- /dev/null +++ b/redirect/Sinum/Sinum.h @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Project Nova LLC + +#pragma once +#include "../framework.h" + +class FCurlHttpRequest +{ +private: + void** VTable; + +public: + + FString GetURL() + { + FString Result; + return ((FString& (*)(FCurlHttpRequest*, FString&))(*VTable))(this, Result); + } + + void SetURL(FString URL) + { + ((void (*)(FCurlHttpRequest*, FString&))(VTable[10]))(this, URL); + } +}; + +namespace Sinum +{ + static bool (*_ProcessRequest)(FCurlHttpRequest*); + static bool ProcessRequestHook(FCurlHttpRequest* Request); + + void Init(); +} \ No newline at end of file diff --git a/redirect/Utilities/Windows.h b/redirect/Utilities/Windows.h new file mode 100644 index 0000000..29113b1 --- /dev/null +++ b/redirect/Utilities/Windows.h @@ -0,0 +1,15 @@ +// Copyright (c) 2024 Project Nova LLC + +#pragma once +#include "../framework.h" + +namespace Windows +{ + namespace Thread + { + static HANDLE Create(void* Routine, void* Param = NULL) + { + return CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Routine, Param, 0, NULL); + } + } +} \ No newline at end of file diff --git a/redirect/Utilities/memcury.h b/redirect/Utilities/memcury.h new file mode 100644 index 0000000..2b90f6a --- /dev/null +++ b/redirect/Utilities/memcury.h @@ -0,0 +1,1210 @@ +#pragma once + +/* + Memcury is a single-header file library for memory manipulation in C++. + + Containers: + -PE::Address: A pointer container. + -PE::Section: Portable executable section container for internal usage. + + Modules: + -Scanner: + -Constructors: + -Default: Takes a pointer to start the scanning from. + -FindPattern: Finds a pattern in memory. + -FindStringRef: Finds a string reference in memory, supports all types of strings. + -Functions: + -SetTargetModule: Sets the target module for the scanner. + -ScanFor: Scans for a byte(s) near the current address. + -FindFunctionBoundary: Finds the boundary of a function near the current address. + -RelativeOffset: Gets the relative offset of the current address. + -AbsoluteOffset: Gets the absolute offset of the current address. + -GetAs: Gets the current address as a type. + -Get: Gets the current address as an int64. + + -TrampolineHook: + -Constructors: + -Default: Takes a pointer pointer to the target function and a pointer to the hook function. + -Functions: + -Commit: Commits the hook. + -Revert: Reverts the hook. + -Toggle: Toggles the hook on\off. + + -VEHHook: + -Functions: + -Init: Initializes the VEH Hook system. + -AddHook: Adds a hook to the VEH Hook system. + -RemoveHook: Removes a hook from the VEH Hook system. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma comment(lib, "Dbghelp.lib") + +#define MemcuryAssert(cond) \ + if (!(cond)) \ + { \ + MessageBoxA(nullptr, #cond, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); \ + } + +#define MemcuryAssertM(cond, msg) \ + if (!(cond)) \ + { \ + MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); \ + } + +#define MemcuryThrow(msg) \ + MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); + +namespace Memcury +{ + extern "C" IMAGE_DOS_HEADER __ImageBase; + + inline auto GetCurrentModule() -> HMODULE + { + return reinterpret_cast(&__ImageBase); + } + + namespace Util + { + template + constexpr static auto IsInRange(T value, T min, T max) -> bool + { + return value >= min && value < max; + } + + constexpr auto StrHash(const char* str, int h = 0) -> unsigned int + { + return !str[h] ? 5381 : (StrHash(str, h + 1) * 33) ^ str[h]; + } + + inline auto IsSamePage(void* A, void* B) -> bool + { + MEMORY_BASIC_INFORMATION InfoA; + if (!VirtualQuery(A, &InfoA, sizeof(InfoA))) + { + return true; + } + + MEMORY_BASIC_INFORMATION InfoB; + if (!VirtualQuery(B, &InfoB, sizeof(InfoB))) + { + return true; + } + + return InfoA.BaseAddress == InfoB.BaseAddress; + } + + inline auto GetModuleStartAndEnd() -> std::pair + { + auto HModule = GetCurrentModule(); + auto NTHeaders = reinterpret_cast((uintptr_t)HModule + reinterpret_cast((uintptr_t)HModule)->e_lfanew); + + uintptr_t dllStart = (uintptr_t)HModule; + uintptr_t dllEnd = (uintptr_t)HModule + NTHeaders->OptionalHeader.SizeOfImage; + + return { dllStart, dllEnd }; + } + + inline auto CopyToClipboard(std::string str) + { + auto mem = GlobalAlloc(GMEM_FIXED, str.size() + 1); + memcpy(mem, str.c_str(), str.size() + 1); + + OpenClipboard(nullptr); + EmptyClipboard(); + SetClipboardData(CF_TEXT, mem); + CloseClipboard(); + + GlobalFree(mem); + } + } + + namespace Safety + { + enum class ExceptionMode + { + None, + CatchDllExceptionsOnly, + CatchAllExceptions + }; + + static auto FreezeCurrentThread() -> void + { + SuspendThread(GetCurrentThread()); + } + + static auto PrintStack(CONTEXT* ctx) -> void + { + STACKFRAME64 stack; + memset(&stack, 0, sizeof(STACKFRAME64)); + + auto process = GetCurrentProcess(); + auto thread = GetCurrentThread(); + + SymInitialize(process, NULL, TRUE); + + bool result; + DWORD64 displacement = 0; + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]{ 0 }; + char name[256]{ 0 }; + char module[256]{ 0 }; + + PSYMBOL_INFO symbolInfo = (PSYMBOL_INFO)buffer; + + for (ULONG frame = 0;; frame++) + { + result = StackWalk64( + IMAGE_FILE_MACHINE_AMD64, + process, + thread, + &stack, + ctx, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL); + + if (!result) + break; + + symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO); + symbolInfo->MaxNameLen = MAX_SYM_NAME; + SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbolInfo); + + HMODULE hModule = NULL; + lstrcpyA(module, ""); + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (const wchar_t*)(stack.AddrPC.Offset), &hModule); + + if (hModule != NULL) + GetModuleFileNameA(hModule, module, 256); + + printf("[%lu] Name: %s - Address: %p - Module: %s\n", frame, symbolInfo->Name, (void*)symbolInfo->Address, module); + } + } + + template + auto MemcuryGlobalHandler(EXCEPTION_POINTERS* ExceptionInfo) -> long + { + auto [dllStart, dllEnd] = Util::GetModuleStartAndEnd(); + + if constexpr (mode == ExceptionMode::CatchDllExceptionsOnly) + { + if (!Util::IsInRange(ExceptionInfo->ContextRecord->Rip, dllStart, dllEnd)) + { + return EXCEPTION_CONTINUE_SEARCH; + } + } + + auto message = std::format("Memcury caught an exception at [{:x}]\nPress Yes if you want the address to be copied to your clipboard", ExceptionInfo->ContextRecord->Rip); + if (MessageBoxA(nullptr, message.c_str(), "Error", MB_ICONERROR | MB_YESNO) == IDYES) + { + std::string clip = std::format("{:x}", ExceptionInfo->ContextRecord->Rip); + Util::CopyToClipboard(clip); + } + + PrintStack(ExceptionInfo->ContextRecord); + + FreezeCurrentThread(); + + return EXCEPTION_EXECUTE_HANDLER; + } + + template + static auto SetExceptionMode() -> void + { + SetUnhandledExceptionFilter(MemcuryGlobalHandler); + } + } + + namespace Globals + { + constexpr const bool bLogging = true; + + inline const char* moduleName = nullptr; + } + + namespace ASM + { + //@todo: this whole namespace needs a rework, should somehow make this more modern and less ugly. + enum MNEMONIC : uint8_t + { + JMP_REL8 = 0xEB, + JMP_REL32 = 0xE9, + JMP_EAX = 0xE0, + CALL = 0xE8, + LEA = 0x8D, + CDQ = 0x99, + CMOVL = 0x4C, + CMOVS = 0x48, + CMOVNS = 0x49, + NOP = 0x90, + INT3 = 0xCC, + RETN_REL8 = 0xC2, + RETN = 0xC3, + POP = 0x58, + MAXOP = 0x5F, + CMOVNO = 0x41, + NONE = 0x00, + PUSH = 0x40 + }; + + constexpr int SIZE_OF_JMP_RELATIVE_INSTRUCTION = 5; + constexpr int SIZE_OF_JMP_ABSLOUTE_INSTRUCTION = 13; + + constexpr auto MnemonicToString(MNEMONIC e) -> const char* + { + switch (e) + { + case JMP_REL8: + return "JMP_REL8"; + case JMP_REL32: + return "JMP_REL32"; + case JMP_EAX: + return "JMP_EAX"; + case CALL: + return "CALL"; + case LEA: + return "LEA"; + case CDQ: + return "CDQ"; + case CMOVL: + return "CMOVL"; + case CMOVS: + return "CMOVS"; + case CMOVNS: + return "CMOVNS"; + case NOP: + return "NOP"; + case INT3: + return "INT3"; + case RETN_REL8: + return "RETN_REL8"; + case RETN: + return "RETN"; + case NONE: + return "NONE"; + default: + return "UNKNOWN"; + } + } + + constexpr auto Mnemonic(const char* s) -> MNEMONIC + { + switch (Util::StrHash(s)) + { + case Util::StrHash("JMP_REL8"): + return JMP_REL8; + case Util::StrHash("JMP_REL32"): + return JMP_REL32; + case Util::StrHash("JMP_EAX"): + return JMP_EAX; + case Util::StrHash("CALL"): + return CALL; + case Util::StrHash("LEA"): + return LEA; + case Util::StrHash("CDQ"): + return CDQ; + case Util::StrHash("CMOVL"): + return CMOVL; + case Util::StrHash("CMOVS"): + return CMOVS; + case Util::StrHash("CMOVNS"): + return CMOVNS; + case Util::StrHash("NOP"): + return NOP; + case Util::StrHash("INT3"): + return INT3; + case Util::StrHash("RETN_REL8"): + return RETN_REL8; + case Util::StrHash("RETN"): + return RETN; + default: + return NONE; + } + } + + inline auto byteIsA(uint8_t byte, MNEMONIC opcode) -> bool + { + return byte == opcode; + } + + inline auto byteIsAscii(uint8_t byte) -> bool + { + static constexpr bool isAscii[0x100] = { + false, false, false, false, false, false, false, false, false, true, true, false, false, true, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false + }; + + return isAscii[byte]; + } + + inline bool isJump(uint8_t byte) + { + return byte >= 0x70 && byte <= 0x7F; + } + + static auto pattern2bytes(const char* pattern) -> std::vector + { + auto bytes = std::vector{}; + const auto start = const_cast(pattern); + const auto end = const_cast(pattern) + strlen(pattern); + + for (auto current = start; current < end; ++current) + { + if (*current == '?') + { + ++current; + if (*current == '?') + ++current; + bytes.push_back(-1); + } + else + { + bytes.push_back(strtoul(current, ¤t, 16)); + } + } + return bytes; + } + } + + namespace PE + { + inline auto SetCurrentModule(const char* moduleName) -> void + { + Globals::moduleName = moduleName; + } + + inline auto GetModuleBase() -> uintptr_t + { + return reinterpret_cast(GetModuleHandleA(Globals::moduleName)); + } + + inline auto GetDOSHeader() -> PIMAGE_DOS_HEADER + { + return reinterpret_cast(GetModuleBase()); + } + + inline auto GetNTHeaders() -> PIMAGE_NT_HEADERS + { + return reinterpret_cast(GetModuleBase() + GetDOSHeader()->e_lfanew); + } + + class Address + { + uintptr_t _address; + + public: + Address() + { + _address = 0; + } + + Address(uintptr_t address) + : _address(address) + { + } + + Address(void* address) + : _address(reinterpret_cast(address)) + { + } + + auto operator=(uintptr_t address) -> Address + { + _address = address; + return *this; + } + + auto operator=(void* address) -> Address + { + _address = reinterpret_cast(address); + return *this; + } + + auto operator+(uintptr_t offset) -> Address + { + return Address(_address + offset); + } + + bool operator>(uintptr_t offset) + { + return _address > offset; + } + + bool operator>(Address address) + { + return _address > address._address; + } + + bool operator<(uintptr_t offset) + { + return _address < offset; + } + + bool operator<(Address address) + { + return _address < address._address; + } + + bool operator>=(uintptr_t offset) + { + return _address >= offset; + } + + bool operator>=(Address address) + { + return _address >= address._address; + } + + bool operator<=(uintptr_t offset) + { + return _address <= offset; + } + + bool operator<=(Address address) + { + return _address <= address._address; + } + + bool operator==(uintptr_t offset) + { + return _address == offset; + } + + bool operator==(Address address) + { + return _address == address._address; + } + + bool operator!=(uintptr_t offset) + { + return _address != offset; + } + + bool operator!=(Address address) + { + return _address != address._address; + } + + auto RelativeOffset(uint32_t offset) -> Address + { + _address = ((_address + offset + 4) + *(int32_t*)(_address + offset)); + return *this; + } + + auto AbsoluteOffset(uint32_t offset) -> Address + { + _address = _address + offset; + return *this; + } + + auto Jump() -> Address + { + if (ASM::isJump(*reinterpret_cast(_address))) + { + UINT8 toSkip = *reinterpret_cast(_address + 1); + _address = _address + 2 + toSkip; + } + + return *this; + } + + auto Get() -> uintptr_t + { + return _address; + } + + template + auto GetAs() -> T + { + return reinterpret_cast(_address); + } + + auto IsValid() -> bool + { + return _address != 0; + } + }; + + class Section + { + public: + std::string sectionName; + IMAGE_SECTION_HEADER rawSection; + + static auto GetAllSections() -> std::vector
+ { + std::vector
sections; + + auto sectionsSize = GetNTHeaders()->FileHeader.NumberOfSections; + auto section = IMAGE_FIRST_SECTION(GetNTHeaders()); + + for (WORD i = 0; i < sectionsSize; i++, section++) + { + auto secName = std::string((char*)section->Name); + + sections.push_back({ secName, *section }); + } + + return sections; + } + + static auto GetSection(std::string sectionName) -> Section + { + for (auto& section : GetAllSections()) + { + if (section.sectionName == sectionName) + { + return section; + } + } + + MemcuryThrow("Section not found"); + return Section{}; + } + + auto GetSectionSize() -> uint32_t + { + return rawSection.Misc.VirtualSize; + } + + auto GetSectionStart() -> Address + { + return Address(GetModuleBase() + rawSection.VirtualAddress); + } + + auto GetSectionEnd() -> Address + { + return Address(GetSectionStart() + GetSectionSize()); + } + + auto isInSection(Address address) -> bool + { + return address >= GetSectionStart() && address < GetSectionEnd(); + } + }; + } + + class Scanner + { + PE::Address _address; + + public: + Scanner(PE::Address address) + : _address(address) + { + } + + static auto SetTargetModule(const char* moduleName) -> void + { + PE::SetCurrentModule(moduleName); + } + + static auto FindPatternEx(HANDLE handle, const char* pattern, const char* mask, uint64_t begin, uint64_t end) -> Scanner + { + auto scan = [](const char* pattern, const char* mask, char* begin, unsigned int size) -> char* + { + size_t patternLen = strlen(mask); + for (unsigned int i = 0; i < size - patternLen; i++) + { + bool found = true; + for (unsigned int j = 0; j < patternLen; j++) + { + if (mask[j] != '?' && pattern[j] != *(begin + i + j)) + { + found = false; + break; + } + } + + if (found) + return (begin + i); + } + return nullptr; + }; + + uint64_t match = NULL; + SIZE_T bytesRead; + char* buffer = nullptr; + MEMORY_BASIC_INFORMATION mbi = { 0 }; + + uint64_t curr = begin; + + for (uint64_t curr = begin; curr < end; curr += mbi.RegionSize) + { + if (!VirtualQueryEx(handle, (void*)curr, &mbi, sizeof(mbi))) + continue; + + if (mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS) + continue; + + buffer = new char[mbi.RegionSize]; + + if (ReadProcessMemory(handle, mbi.BaseAddress, buffer, mbi.RegionSize, &bytesRead)) + { + char* internalAddr = scan(pattern, mask, buffer, (unsigned int)bytesRead); + + if (internalAddr != nullptr) + { + match = curr + (uint64_t)(internalAddr - buffer); + break; + } + } + } + delete[] buffer; + + MemcuryAssertM(match != 0, "FindPatternEx return nullptr"); + + return Scanner(match); + } + + static auto FindPatternEx(HANDLE handle, const char* sig) -> Scanner + { + char pattern[100]; + char mask[100]; + + char lastChar = ' '; + unsigned int j = 0; + + for (unsigned int i = 0; i < strlen(sig); i++) + { + if ((sig[i] == '?' || sig[i] == '*') && (lastChar != '?' && lastChar != '*')) + { + pattern[j] = mask[j] = '?'; + j++; + } + + else if (isspace(lastChar)) + { + pattern[j] = lastChar = (char)strtol(&sig[i], 0, 16); + mask[j] = 'x'; + j++; + } + lastChar = sig[i]; + } + pattern[j] = mask[j] = '\0'; + + auto module = (uint64_t)GetModuleHandle(nullptr); + + return FindPatternEx(handle, pattern, mask, module, module + Memcury::PE::GetNTHeaders()->OptionalHeader.SizeOfImage); + } + + static auto FindPattern(const char* signature) -> Scanner + { + PE::Address add{ nullptr }; + + const auto sizeOfImage = PE::GetNTHeaders()->OptionalHeader.SizeOfImage; + auto patternBytes = ASM::pattern2bytes(signature); + const auto scanBytes = reinterpret_cast(PE::GetModuleBase()); + + const auto s = patternBytes.size(); + const auto d = patternBytes.data(); + + for (auto i = 0ul; i < sizeOfImage - s; ++i) + { + bool found = true; + for (auto j = 0ul; j < s; ++j) + { + if (scanBytes[i + j] != d[j] && d[j] != -1) + { + found = false; + break; + } + } + + if (found) + { + add = reinterpret_cast(&scanBytes[i]); + break; + } + } + + MemcuryAssertM(add != 0, "FindPattern return nullptr"); + + return Scanner(add); + } + + // Supports wide and normal strings both std and pointers + template + static auto FindStringRef(T string) -> Scanner + { + PE::Address add{ nullptr }; + + constexpr auto bIsWide = std::is_same::value; + constexpr auto bIsChar = std::is_same::value; + + constexpr auto bIsPtr = bIsWide || bIsChar; + + auto textSection = PE::Section::GetSection(".text"); + auto rdataSection = PE::Section::GetSection(".rdata"); + + const auto scanBytes = reinterpret_cast(textSection.GetSectionStart().Get()); + + // scan only text section + for (DWORD i = 0x0; i < textSection.GetSectionSize(); i++) + { + if ((scanBytes[i] == ASM::CMOVL || scanBytes[i] == ASM::CMOVS) && scanBytes[i + 1] == ASM::LEA) + { + auto stringAdd = PE::Address(&scanBytes[i]).RelativeOffset(3); + + // Check if the string is in the .rdata section + if (rdataSection.isInSection(stringAdd)) + { + auto strBytes = stringAdd.GetAs(); + + // Check if the first char is printable + if (ASM::byteIsAscii(strBytes[0])) + { + if constexpr (!bIsPtr) + { + typedef T::value_type char_type; + + auto lea = stringAdd.GetAs(); + + T leaT(lea); + + if (leaT == string) + { + add = PE::Address(&scanBytes[i]); + } + } + else + { + auto lea = stringAdd.GetAs(); + + if constexpr (bIsWide) + { + if (wcscmp(string, lea) == 0) + { + add = PE::Address(&scanBytes[i]); + } + } + else + { + if (strcmp(string, lea) == 0) + { + add = PE::Address(&scanBytes[i]); + } + } + } + } + } + } + } + + MemcuryAssertM(add != 0, "FindStringRef return nullptr"); + + return Scanner(add); + } + + static auto FindPointerRef(void* pointer) -> Scanner + { + PE::Address add{ nullptr }; + + auto rdataSection = PE::Section::GetSection(".rdata"); + const auto scanBytes = reinterpret_cast(rdataSection.GetSectionStart().Get()); + + for (DWORD i = 0; i < rdataSection.GetSectionSize(); i++) + { + auto currentPointer = *reinterpret_cast(scanBytes + i); + + if (currentPointer == pointer) + { + add = PE::Address(&scanBytes[i]); + break; + } + } + + MemcuryAssertM(add != 0, "FindPointerRef return nullptr"); + + return Scanner(add); + } + + auto Jump() -> Scanner + { + _address.Jump(); + return *this; + } + + auto ScanFor(std::vector opcodesToFind, bool forward = true, int toSkip = 0) -> Scanner + { + const auto scanBytes = _address.GetAs(); + + for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--) + { + bool found = true; + + for (int k = 0; k < opcodesToFind.size() && found; k++) + { + if (opcodesToFind[k] == -1) + continue; + found = opcodesToFind[k] == scanBytes[i + k]; + } + + if (found) + { + _address = &scanBytes[i]; + if (toSkip != 0) + { + return ScanFor(opcodesToFind, forward, toSkip - 1); + } + + break; + } + } + + return *this; + } + + auto RelativeOffset(uint32_t offset) -> Scanner + { + _address.RelativeOffset(offset); + + return *this; + } + + auto AbsoluteOffset(uint32_t offset) -> Scanner + { + _address.AbsoluteOffset(offset); + + return *this; + } + + template + auto GetAs() -> T + { + return _address.GetAs(); + } + + auto Get() -> uintptr_t + { + return _address.Get(); + } + + auto IsValid() -> bool + { + return _address.IsValid(); + } + }; + + /* Bad don't use it tbh... */ + class TrampolineHook + { + void** originalFunctionPtr; + PE::Address originalFunction; + PE::Address hookFunction; + PE::Address allocatedPage; + std::vector restore; + + void PointToCodeIfNot(PE::Address& ptr) + { + auto bytes = ptr.GetAs(); + + if (ASM::byteIsA(bytes[0], ASM::MNEMONIC::JMP_REL32)) + { + ptr = bytes + 5 + *(int32_t*)&bytes[1]; + } + } + + void* AllocatePageNearAddress(void* targetAddr) + { + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + const uint64_t PAGE_SIZE = sysInfo.dwPageSize; + + uint64_t startAddr = (uint64_t(targetAddr) & ~(PAGE_SIZE - 1)); // round down to nearest page boundary + uint64_t minAddr = min(startAddr - 0x7FFFFF00, (uint64_t)sysInfo.lpMinimumApplicationAddress); + uint64_t maxAddr = max(startAddr + 0x7FFFFF00, (uint64_t)sysInfo.lpMaximumApplicationAddress); + + uint64_t startPage = (startAddr - (startAddr % PAGE_SIZE)); + + for (uint64_t pageOffset = 1; pageOffset; pageOffset++) + { + uint64_t byteOffset = pageOffset * PAGE_SIZE; + uint64_t highAddr = startPage + byteOffset; + uint64_t lowAddr = (startPage > byteOffset) ? startPage - byteOffset : 0; + + bool needsExit = highAddr > maxAddr && lowAddr < minAddr; + + if (highAddr < maxAddr) + { + void* outAddr = VirtualAlloc((void*)highAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (outAddr) + return outAddr; + } + + if (lowAddr > minAddr) + { + void* outAddr = VirtualAlloc((void*)lowAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (outAddr != nullptr) + return outAddr; + } + + if (needsExit) + { + break; + } + } + + return nullptr; + } + + void WriteAbsoluteJump(void* jumpLocation, void* destination) + { + uint8_t absJumpInstructions[] = { + ASM::Mnemonic("CMOVNS"), 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr + 0x41, 0xFF, 0xE2 // jmp r10 + }; + + auto destination64 = (uint64_t)destination; + memcpy(&absJumpInstructions[2], &destination64, sizeof(destination64)); + memcpy(jumpLocation, absJumpInstructions, sizeof(absJumpInstructions)); + } + + uintptr_t PrepareRestore() + { + /* + This is not a correct way to do it at all, since not all functions sub from the stack + This needs so much more tests, but it works for now. + */ + + Scanner scanner(originalFunction); + scanner.ScanFor({ 0x48, 0x83, 0xEC }); // sub rsp + + auto restoreSize = scanner.Get() - originalFunction.Get(); + + MemcuryAssert(restoreSize > 0 && restoreSize < 0x100); + + restore.reserve(restoreSize); + for (auto i = 0; i < restoreSize; i++) + { + restore.push_back(originalFunction.GetAs()[i]); + } + + return restoreSize; + } + + void WriteRestore() + { + auto restorePtr = allocatedPage + ASM::SIZE_OF_JMP_ABSLOUTE_INSTRUCTION + 2; + + memcpy(restorePtr.GetAs(), restore.data(), restore.size()); + + *originalFunctionPtr = restorePtr.GetAs(); + + // Write a jump back to where the execution should resume + restorePtr.AbsoluteOffset((uint32_t)restore.size()); + + auto contuineExecution = originalFunction + restore.size(); + + WriteAbsoluteJump(restorePtr.GetAs(), contuineExecution.GetAs()); + } + + auto PrepareJMPInstruction(uint64_t dst) + { + uint8_t bytes[5] = { ASM::Mnemonic("JMP_REL32"), 0x0, 0x0, 0x0, 0x0 }; + + const uint64_t relAddr = dst - (originalFunction.Get() + ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); + memcpy(bytes + 1, &relAddr, 4); + + return std::move(bytes); + } + + bool IsHooked() + { + return originalFunction.GetAs()[0] == ASM::Mnemonic("JMP_REL32"); + } + + public: + TrampolineHook(void** originalFunction, void* hookFunction) + { + this->originalFunctionPtr = originalFunction; + + this->originalFunction = *originalFunction; + this->hookFunction = hookFunction; + + PointToCodeIfNot(this->originalFunction); + PointToCodeIfNot(this->hookFunction); + }; + + bool Commit() + { + auto fnStart = originalFunction.GetAs(); + + auto restoreSize = PrepareRestore(); + + if (!allocatedPage.IsValid()) + { + allocatedPage = AllocatePageNearAddress(fnStart); + } + + memset(allocatedPage.GetAs(), ASM::MNEMONIC::INT3, 0x1000); + + WriteAbsoluteJump(allocatedPage.GetAs(), hookFunction.GetAs()); + + DWORD oldProtect; + VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); + + auto jmpInstruction = PrepareJMPInstruction(allocatedPage.Get()); + + WriteRestore(); + + memset(fnStart, ASM::MNEMONIC::INT3, restoreSize); + memcpy(fnStart, jmpInstruction, ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); + + return true; + } + + bool Revert() + { + auto fnStart = originalFunction.GetAs(); + + DWORD oldProtect; + VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); + + memcpy(fnStart, restore.data(), restore.size()); + + *originalFunctionPtr = originalFunction.GetAs(); + + // VirtualFree(allocatedPage.GetAs(), 0x1000, MEM_RELEASE); + + return true; + } + + auto Toggle() + { + if (IsHooked()) + Revert(); + else + Commit(); + + return IsHooked(); + } + }; + + namespace VEHHook + { + struct HOOK_INFO + { + void* Original; + void* Detour; + + HOOK_INFO(void* Original, void* Detour) + : Original(Original) + , Detour(Detour) + { + } + }; + + inline std::vector Hooks; + inline std::vector HookProtections; + inline HANDLE ExceptionHandler; + + inline long Handler(EXCEPTION_POINTERS* Exception) + { + if (Exception->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) + { + auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Rip = Exception->ContextRecord->Rip](const HOOK_INFO& Hook) + { return Hook.Original == (void*)Rip; }); + if (Itr != Hooks.end()) + { + Exception->ContextRecord->Rip = (uintptr_t)Itr->Detour; + } + + Exception->ContextRecord->EFlags |= 0x100; // SINGLE_STEP_FLAG + + return EXCEPTION_CONTINUE_EXECUTION; + } + else if (Exception->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) + { + // TODO: find a way to only vp the function that about to get executed + for (auto& Hook : Hooks) + { + DWORD dwOldProtect; + VirtualProtect(Hook.Original, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &dwOldProtect); + } + + return EXCEPTION_CONTINUE_EXECUTION; + } + + return EXCEPTION_CONTINUE_SEARCH; + } + + inline bool Init() + { + if (ExceptionHandler == nullptr) + { + ExceptionHandler = AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)Handler); + } + return ExceptionHandler != nullptr; + } + + inline bool AddHook(void* Target, void* Detour) + { + if (ExceptionHandler == nullptr) + { + return false; + } + + if (Util::IsSamePage(Target, Detour)) + { + return false; + } + + if (!VirtualProtect(Target, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &HookProtections.emplace_back())) + { + HookProtections.pop_back(); + return false; + } + + Hooks.emplace_back(Target, Detour); + return true; + } + + inline bool RemoveHook(void* Original) + { + auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Original](const HOOK_INFO& Hook) + { return Hook.Original == Original; }); + + if (Itr == Hooks.end()) + { + return false; + } + + const auto ProtItr = HookProtections.begin() + std::distance(Hooks.begin(), Itr); + Hooks.erase(Itr); + + DWORD dwOldProtect; + bool Ret = VirtualProtect(Original, 1, *ProtItr, &dwOldProtect); + HookProtections.erase(ProtItr); + + return false; + } + } +} \ No newline at end of file diff --git a/redirect/framework.h b/redirect/framework.h new file mode 100644 index 0000000..621b6dd --- /dev/null +++ b/redirect/framework.h @@ -0,0 +1,10 @@ +// Copyright (c) 2024 Project Nova LLC + +#pragma once +#include + +#include "Utilities\memcury.h" +#include "Utilities\Windows.h" + +#include "Core\Core.h" +#include "Sinum\Sinum.h" \ No newline at end of file