This commit is contained in:
Alessandro Autiero
2025-03-23 20:26:13 +01:00
parent 9a000db3b7
commit 5d8f6bf0fa
16 changed files with 205 additions and 117 deletions

View File

@@ -1,7 +1,6 @@
export 'package:reboot_common/src/constant/backend.dart'; export 'package:reboot_common/src/constant/backend.dart';
export 'package:reboot_common/src/constant/game.dart'; export 'package:reboot_common/src/constant/game.dart';
export 'package:reboot_common/src/constant/supabase.dart'; export 'package:reboot_common/src/constant/supabase.dart';
export 'package:reboot_common/src/extension/process.dart';
export 'package:reboot_common/src/model/fortnite_build.dart'; export 'package:reboot_common/src/model/fortnite_build.dart';
export 'package:reboot_common/src/model/fortnite_version.dart'; export 'package:reboot_common/src/model/fortnite_version.dart';
export 'package:reboot_common/src/model/game_instance.dart'; export 'package:reboot_common/src/model/game_instance.dart';
@@ -16,3 +15,4 @@ export 'package:reboot_common/src/util/downloader.dart';
export 'package:reboot_common/src/util/os.dart'; export 'package:reboot_common/src/util/os.dart';
export 'package:reboot_common/src/util/log.dart'; export 'package:reboot_common/src/util/log.dart';
export 'package:reboot_common/src/util/game.dart'; export 'package:reboot_common/src/util/game.dart';
export 'package:reboot_common/src/util/extensions.dart';

View File

@@ -29,4 +29,5 @@ const String kDisplayInitializedLine = "Initialized";
const String kShippingExe = "FortniteClient-Win64-Shipping.exe"; const String kShippingExe = "FortniteClient-Win64-Shipping.exe";
const String kLauncherExe = "FortniteLauncher.exe"; const String kLauncherExe = "FortniteLauncher.exe";
const String kEacExe = "FortniteClient-Win64-Shipping_EAC.exe"; const String kEacExe = "FortniteClient-Win64-Shipping_EAC.exe";
const String kCrashReportExe = "CrashReportClient.exe";
final Version kMaxAllowedVersion = Version.parse("30.10"); final Version kMaxAllowedVersion = Version.parse("30.10");

View File

@@ -1,15 +0,0 @@
extension StringExtension on String {
bool get isBlank {
if(isEmpty) {
return true;
}
for(var char in this.split("")) {
if(char != " ") {
return false;
}
}
return true;
}
}

View File

@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:ini/ini.dart'; import 'package:ini/ini.dart';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_common/src/extension/types.dart';
import 'package:shelf/shelf_io.dart'; import 'package:shelf/shelf_io.dart';
import 'package:shelf_proxy/shelf_proxy.dart'; import 'package:shelf_proxy/shelf_proxy.dart';
import 'package:sync/semaphore.dart'; import 'package:sync/semaphore.dart';
@@ -234,7 +234,7 @@ Future<void> writeMatchmakingIp(String text) async {
final splitIndex = text.indexOf(":"); final splitIndex = text.indexOf(":");
final ip = splitIndex != -1 ? text.substring(0, splitIndex) : text; final ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort; var port = splitIndex != -1 ? text.substring(splitIndex + 1) : kDefaultGameServerPort;
if(port.isBlank) { if(port.isBlankOrEmpty) {
port = kDefaultGameServerPort; port = kDefaultGameServerPort;
} }

View File

@@ -8,7 +8,6 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:isolate'; import 'dart:isolate';
import 'package:reboot_common/src/extension/types.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@@ -361,7 +360,7 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
); );
}); });
process.stdError.listen((data) { process.stdError.listen((data) {
if(!data.isBlank) { if(!data.isBlankOrEmpty) {
_onError(data, options); _onError(data, options);
} }
}); });
@@ -418,7 +417,7 @@ Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File
); );
}); });
process.stdError.listen((data) { process.stdError.listen((data) {
if(!data.isBlank) { if(!data.isBlankOrEmpty) {
_onError(data, options); _onError(data, options);
} }
}); });

View File

@@ -6,3 +6,19 @@ extension ProcessExtension on Process {
Stream<String> get stdError => this.stderr.expand((event) => utf8.decode(event, allowMalformed: true).split("\n")); Stream<String> get stdError => this.stderr.expand((event) => utf8.decode(event, allowMalformed: true).split("\n"));
} }
extension StringExtension on String {
bool get isBlankOrEmpty {
if(isEmpty) {
return true;
}
for(var char in this.split("")) {
if(char != " ") {
return false;
}
}
return true;
}
}

View File

@@ -6,6 +6,7 @@ import 'dart:typed_data';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:win32/win32.dart'; import 'package:win32/win32.dart';
import 'package:path/path.dart' as path;
final DynamicLibrary _shell32 = DynamicLibrary.open('shell32.dll'); final DynamicLibrary _shell32 = DynamicLibrary.open('shell32.dll');
final SHGetPropertyStoreFromParsingName = final SHGetPropertyStoreFromParsingName =
@@ -32,6 +33,122 @@ final Uint8List _patchedMatchmaking = Uint8List.fromList([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]); ]);
// https://github.com/polynite/fn-releases
const Map<int, String> _buildToGameVersion = {
2870186: "1.0.0",
3700114: "1.7.2",
3724489: "1.8.0",
3729133: "1.8.1",
3741772: "1.8.2",
3757339: "1.9",
3775276: "1.9.1",
3790078: "1.10",
3807424: "1.11",
3825894: "2.1",
3841827: "2.2",
3847564: "2.3",
3858292: "2.4",
3870737: "2.4.2",
3889387: "2.5",
3901517: "3.0.0",
3915963: "3.1",
3917250: "3.1.1",
3935073: "3.2",
3942182: "3.3",
4008490: "3.5",
4019403: "3.6",
4039451: "4.0",
4053532: "4.1",
4072250: "4.2",
4117433: "4.4",
4127312: "4.4.1",
4159770: "4.5",
4204761: "5.0",
4214610: "5.01",
4240749: "5.10",
4288479: "5.21",
4305896: "5.30",
4352937: "5.40",
4363240: "5.41",
4395664: "6.0",
4424678: "6.01",
4461277: "6.0.2",
4464155: "6.10",
4476098: "6.10.1",
4480234: "6.10.2",
4526925: "6.21",
4543176: "6.22",
4573279: "6.31",
4629139: "7.0",
4667333: "7.10",
4727874: "7.20",
4834550: "7.30",
5046157: "7.40",
5203069: "8.00",
5625478: "8.20",
5793395: "8.30",
6005771: "8.40",
6058028: "8.50",
6165369: "8.51",
6337466: "9.00",
6428087: "9.01",
6639283: "9.10",
6922310: "9.21",
7095426: "9.30",
7315705: "9.40",
7609292: "9.41",
7704164: "10.00",
7955722: "10.10",
8456527: "10.20",
8723043: "10.31",
9380822: "10.40",
9603448: "11.00",
9901083: "11.10",
10708866: "11.30",
10800459: "11.31",
11265652: "11.50",
11556442: "12.00",
11883027: "12.10",
12353830: "12.21",
12905909: "12.41",
13137020: "12.50",
13498980: "12.61",
14113327: "13.40",
14211474: "14.00",
14456520: "14.30",
14550713: "14.40",
14786821: "14.60",
14835335: "15.00",
15014719: "15.10",
15341163: "15.30",
15526472: "15.50",
15913292: "16.10",
16163563: "16.30",
16218553: "16.40",
16469788: "16.50",
16745144: "17.10",
17004569: "17.30",
17269705: "17.40",
17388565: "17.50",
17468642: "18.00",
17661844: "18.10",
17745267: "18.20",
17811397: "18.21",
17882303: "18.30",
18163738: "18.40",
18489740: "19.01",
18675304: "19.10",
19458861: "20.00",
19598943: "20.10",
19751212: "20.20",
19950687: "20.30",
20244966: "20.40",
20463113: "21.00",
20696680: "21.10",
21035704: "21.20",
21657658: "21.50",
};
Future<bool> patchHeadless(File file) async => Future<bool> patchHeadless(File file) async =>
await _patch(file, _originalHeadless, _patchedHeadless); await _patch(file, _originalHeadless, _patchedHeadless);
@@ -179,8 +296,23 @@ String _parseUsername(String username, bool host) {
return username; return username;
} }
Future<String> extractGameVersion(String filePath, String defaultGameVersion) => Isolate.run(() { // Parsing the version is not that easy
final filePathPtr = filePath.toNativeUtf16(); // Also on some versions the shipping exe has it as well, but not on all: that's why i'm using the crash report client
// ++Fortnite+Release-34.10-CL-40567068
// 4.16.0-3700114+++Fortnite+Release-Cert
// 4.19.0-3870737+++Fortnite+Release-Next
// 4.20.0-4008490+++Fortnite+Release-3.5
Future<String> extractGameVersion(Directory directory) => Isolate.run(() async {
log("[VERSION] Looking for $kCrashReportExe in ${directory.path}");
final defaultGameVersion = path.basename(directory.path);
final crashReportClients = await findFiles(directory, kCrashReportExe);
if (crashReportClients.isEmpty) {
log("[VERSION] Didn't find a unique match: $crashReportClients");
return defaultGameVersion;
}
log("[VERSION] Extracting game version from ${crashReportClients.last.path}(default: $defaultGameVersion)");
final filePathPtr = crashReportClients.last.path.toNativeUtf16();
final pPropertyStore = calloc<COMObject>(); final pPropertyStore = calloc<COMObject>();
final iidPropertyStore = GUIDFromString(IID_IPropertyStore); final iidPropertyStore = GUIDFromString(IID_IPropertyStore);
final ret = SHGetPropertyStoreFromParsingName( final ret = SHGetPropertyStoreFromParsingName(
@@ -195,8 +327,9 @@ Future<String> extractGameVersion(String filePath, String defaultGameVersion) =>
calloc.free(iidPropertyStore); calloc.free(iidPropertyStore);
if (FAILED(ret)) { if (FAILED(ret)) {
log("[VERSION] Using default value");
calloc.free(pPropertyStore); calloc.free(pPropertyStore);
throw WindowsException(ret); return defaultGameVersion;
} }
final propertyStore = IPropertyStore(pPropertyStore); final propertyStore = IPropertyStore(pPropertyStore);
@@ -206,7 +339,8 @@ Future<String> extractGameVersion(String filePath, String defaultGameVersion) =>
final count = countPtr.value; final count = countPtr.value;
calloc.free(countPtr); calloc.free(countPtr);
if (FAILED(hrCount)) { if (FAILED(hrCount)) {
throw WindowsException(hrCount); log("[VERSION] Using default value");
return defaultGameVersion;
} }
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
@@ -222,48 +356,20 @@ Future<String> extractGameVersion(String filePath, String defaultGameVersion) =>
if (!FAILED(hrValue)) { if (!FAILED(hrValue)) {
if (pv.ref.vt == VARENUM.VT_LPWSTR) { if (pv.ref.vt == VARENUM.VT_LPWSTR) {
final valueStr = pv.ref.pwszVal.toDartString(); final valueStr = pv.ref.pwszVal.toDartString();
if (valueStr.contains("+++Fortnite")) { final headerIndex = valueStr.indexOf("++Fortnite");
var gameVersion = valueStr.substring(valueStr.lastIndexOf("-") + 1); if (headerIndex != -1) {
if(gameVersion == "Cert") { log("[VERSION] Found value string: $valueStr");
var gameVersion = valueStr.substring(valueStr.indexOf("-", headerIndex) + 1);
log("[VERSION] Game version: $gameVersion");
if(gameVersion == "Cert" || gameVersion == "Next") {
final engineVersion = valueStr.substring(0, valueStr.indexOf("+")); final engineVersion = valueStr.substring(0, valueStr.indexOf("+"));
log("[VERSION] Engine version: $engineVersion");
final engineVersionParts = engineVersion.split("-"); final engineVersionParts = engineVersion.split("-");
final engineVersionBuild = int.parse(engineVersionParts[1]); final engineVersionBuild = int.parse(engineVersionParts[1]);
switch (engineVersionBuild) { log("[VERSION] Engine build: $engineVersionBuild");
case 2870186: gameVersion = _buildToGameVersion[engineVersionBuild] ?? defaultGameVersion;
gameVersion = "OT6.5";
break;
case 3700114:
gameVersion = "1.7.2";
break;
case 3724489:
gameVersion = "1.8.0";
break;
case 3729133:
gameVersion = "1.8.1";
break;
case 3741772:
gameVersion = "1.8.2";
break;
case 3757339:
gameVersion = "1.9";
break;
case 3775276:
gameVersion = "1.9.1";
break;
case 3790078:
gameVersion = "1.10";
break;
case 3807424:
gameVersion = "1.11";
break;
case 3825894:
gameVersion = "2.1";
break;
default:
gameVersion = defaultGameVersion;
break;
}
} }
log("[VERSION] Returning $gameVersion");
return gameVersion; return gameVersion;
} }
} }
@@ -272,5 +378,6 @@ Future<String> extractGameVersion(String filePath, String defaultGameVersion) =>
calloc.free(pv); calloc.free(pv);
} }
log("[VERSION] Using default value");
return defaultGameVersion; return defaultGameVersion;
}); });

View File

@@ -1,10 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:sync/semaphore.dart'; import 'package:synchronized/extension.dart';
final File launcherLogFile = _createLoggingFile(); final File launcherLogFile = _createLoggingFile();
final Semaphore _semaphore = Semaphore(1);
bool enableLoggingToConsole = true; bool enableLoggingToConsole = true;
File _createLoggingFile() { File _createLoggingFile() {
@@ -19,14 +18,14 @@ File _createLoggingFile() {
void log(String message) async { void log(String message) async {
try { try {
await _semaphore.acquire();
if(enableLoggingToConsole) { if(enableLoggingToConsole) {
print(message); print(message);
} }
await launcherLogFile.writeAsString("$message\n", mode: FileMode.append, flush: true);
launcherLogFile.synchronized(() async {
await launcherLogFile.writeAsString("$message\n", mode: FileMode.append, flush: true);
});
}catch(error) { }catch(error) {
print("[LOGGER_ERROR] An error occurred while logging: $error"); print("[LOGGER_ERROR] An error occurred while logging: $error");
}finally {
_semaphore.release();
} }
} }

View File

@@ -19,6 +19,7 @@ dependencies:
uuid: ^4.5.1 uuid: ^4.5.1
shelf_web_socket: ^2.0.0 shelf_web_socket: ^2.0.0
version: ^3.0.2 version: ^3.0.2
synchronized: ^3.3.0+3
dev_dependencies: dev_dependencies:
flutter_lints: ^5.0.0 flutter_lints: ^5.0.0

View File

@@ -396,7 +396,7 @@ class BackendController extends GetxController {
} }
final version = Get.find<GameController>() final version = Get.find<GameController>()
.getVersionByName(server.version.toString()); .getVersionByGame(server.version.toString());
if(version == null) { if(version == null) {
_showRebootInfoBar( _showRebootInfoBar(
translations.cannotJoinServerVersion(server.version.toString()), translations.cannotJoinServerVersion(server.version.toString()),

View File

@@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:reboot_common/common.dart'; import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/main.dart';
import 'package:version/version.dart';
class GameController extends GetxController { class GameController extends GetxController {
static const String storageName = "v3_game_storage"; static const String storageName = "v3_game_storage";
@@ -56,6 +57,21 @@ class GameController extends GetxController {
return versions.value.firstWhereOrNull((element) => element.name == name); return versions.value.firstWhereOrNull((element) => element.name == name);
} }
FortniteVersion? getVersionByGame(String gameVersion) {
gameVersion = gameVersion.trim();
final parsedGameVersion = Version.parse(gameVersion);
return versions.value.firstWhereOrNull((element) {
final compare = element.gameVersion.trim();
try {
final parsedCompare = Version.parse(compare);
return parsedCompare.major == parsedGameVersion.major
&& parsedCompare.minor == parsedGameVersion.minor;
} on FormatException {
return compare == gameVersion;
}
});
}
void addVersion(FortniteVersion version) { void addVersion(FortniteVersion version) {
versions.update((val) => val?.add(version)); versions.update((val) => val?.add(version));
selectedVersion.value = version; selectedVersion.value = version;

View File

@@ -47,37 +47,6 @@ Future<String?> openFilePicker(String extension) async {
bool get isDarkMode => bool get isDarkMode =>
SchedulerBinding.instance.platformDispatcher.platformBrightness.isDark; SchedulerBinding.instance.platformDispatcher.platformBrightness.isDark;
class _ServiceProvider10 extends IUnknown {
static const String _CLSID = "{C2F03A33-21F5-47FA-B4BB-156362A2F239}";
static const String _IID = "{6D5140C1-7436-11CE-8034-00AA006009FA}";
_ServiceProvider10._internal(Pointer<COMObject> ptr) : super(ptr);
factory _ServiceProvider10.createInstance() =>
_ServiceProvider10._internal(COMObject.createFromID(_CLSID, _IID));
Pointer<COMObject> queryService(String classId, String instanceId) {
final result = calloc<COMObject>();
final code = (ptr.ref.vtable + 3)
.cast<
Pointer<
NativeFunction<
HRESULT Function(Pointer, Pointer<GUID>, Pointer<GUID>,
Pointer<COMObject>)>>>()
.value
.asFunction<
int Function(Pointer, Pointer<GUID>, Pointer<GUID>,
Pointer<COMObject>)>()(ptr.ref.lpVtbl,
GUIDFromString(classId), GUIDFromString(instanceId), result);
if (code != 0) {
free(result);
throw WindowsException(code);
}
return result;
}
}
extension WindowManagerExtension on WindowManager { extension WindowManagerExtension on WindowManager {
Future<void> maximizeOrRestore() async => await windowManager.isMaximized() ? windowManager.restore() : windowManager.maximize(); Future<void> maximizeOrRestore() async => await windowManager.isMaximized() ? windowManager.restore() : windowManager.maximize();
} }

View File

@@ -1,7 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_gen/gen_l10n/reboot_localizations.dart'; import 'package:flutter_gen/gen_l10n/reboot_localizations.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:reboot_common/common.dart';
AppLocalizations? _translations; AppLocalizations? _translations;
bool _init = false; bool _init = false;

View File

@@ -38,7 +38,6 @@ SettingTile createFileSetting({
controller: controller, controller: controller,
validator: (text) { validator: (text) {
final result = _checkDll(text); final result = _checkDll(text);
print("Called validator: $result");
obx.value = result; obx.value = result;
return result; return result;
}, },

View File

@@ -180,7 +180,7 @@ class _BrowsePageState extends RebootPageState<BrowsePage> {
case _Filter.accessible: case _Filter.accessible:
return element.password == null; return element.password == null;
case _Filter.playable: case _Filter.playable:
return _gameController.getVersionByName(element.version) != null; return _gameController.getVersionByGame(element.version) != null;
} }
}).toList(); }).toList();
final sort = _sort.value; final sort = _sort.value;

View File

@@ -147,27 +147,24 @@ class _ImportVersionDialogState extends State<ImportVersionDialog> {
final name = _nameController.text.trim(); final name = _nameController.text.trim();
final directory = Directory(_pathController.text.trim()); final directory = Directory(_pathController.text.trim());
final files = await Future.wait([ final shippingExes = await Future.wait([
Future.delayed(const Duration(seconds: 1)).then((_) => <File>[]), Future.delayed(const Duration(seconds: 1)).then((_) => <File>[]),
findFiles(directory, kShippingExe).then((files) async { findFiles(directory, kShippingExe)
if(files.length == 1) {
await patchHeadless(files.first);
}
return files;
})
]).then((values) => values.expand((entry) => entry).toList()); ]).then((values) => values.expand((entry) => entry).toList());
if (files.isEmpty) { if (shippingExes.isEmpty) {
_validator.value = _ImportState.missingShippingExeError; _validator.value = _ImportState.missingShippingExeError;
return; return;
} }
if(files.length != 1) { if(shippingExes.length != 1) {
_validator.value = _ImportState.multipleShippingExesError; _validator.value = _ImportState.multipleShippingExesError;
return; return;
} }
final gameVersion = await extractGameVersion(files.first.path, path.basename(directory.path)); await patchHeadless(shippingExes.first);
final gameVersion = await extractGameVersion(directory);
try { try {
if(Version.parse(gameVersion) >= kMaxAllowedVersion) { if(Version.parse(gameVersion) >= kMaxAllowedVersion) {
_validator.value = _ImportState.unsupportedVersionError; _validator.value = _ImportState.unsupportedVersionError;
@@ -181,13 +178,13 @@ class _ImportVersionDialogState extends State<ImportVersionDialog> {
final version = FortniteVersion( final version = FortniteVersion(
name: name, name: name,
gameVersion: gameVersion, gameVersion: gameVersion,
location: files.first.parent location: shippingExes.first.parent
); );
_gameController.addVersion(version); _gameController.addVersion(version);
}else { }else {
widget.version?.name = name; widget.version?.name = name;
widget.version?.gameVersion = gameVersion; widget.version?.gameVersion = gameVersion;
widget.version?.location = files.first.parent; widget.version?.location = shippingExes.first.parent;
} }
_validator.value = _ImportState.success; _validator.value = _ImportState.success;
} }