Added nullrhi option

This commit is contained in:
Alessandro Autiero
2022-09-27 22:00:31 +02:00
parent 52af8ac646
commit 908936c76c
12 changed files with 226 additions and 42 deletions

Binary file not shown.

View File

@@ -36,15 +36,16 @@ class GameController extends GetxController {
_selectedVersion = Rxn(decodedSelectedVersion); _selectedVersion = Rxn(decodedSelectedVersion);
host = RxBool(_storage.read("host") ?? false); host = RxBool(_storage.read("host") ?? false);
host.listen((value) {
_storage.write("host", value);
username.text = _storage.read("${host.value ? 'host' : 'game'}_username") ?? "";
});
username = TextEditingController(text: _storage.read("${host.value ? 'host' : 'game'}_username") ?? ""); username = TextEditingController(text: _storage.read("${host.value ? 'host' : 'game'}_username") ?? "");
username.addListener(() async { username.addListener(() async {
await _storage.write("${host.value ? 'host' : 'game'}_username", username.text); await _storage.write("${host.value ? 'host' : 'game'}_username", username.text);
}); });
host.listen((value) => _storage.write("host", value));
host.listen((value) => username.text = _storage.read("${host.value ? 'host' : 'game'}_username") ?? "");
started = RxBool(false); started = RxBool(false);
} }

View File

@@ -12,33 +12,29 @@ class FortniteVersion {
FortniteVersion({required this.name, required this.location}); FortniteVersion({required this.name, required this.location});
static File findExecutable(Directory directory, String name) { static File? findExecutable(Directory directory, String name) {
if(path.basename(directory.path) == "FortniteGame"){
return File("$directory/Binaries/Win64/$name");
}
try{ try{
var gameDirectory = directory.listSync(recursive: true) var result = directory.listSync(recursive: true)
.firstWhereOrNull((element) => path.basename(element.path) == "FortniteGame"); .firstWhereOrNull((element) => path.basename(element.path) == name);
if(gameDirectory == null){ if(result == null){
return File("${directory.path}/Binaries/Win64/$name"); return null;
} }
return File("${gameDirectory.path}/Binaries/Win64/$name"); return File(result.path);
}catch(_){ }catch(_){
return File("${directory.path}/Binaries/Win64/$name"); return null;
} }
} }
File get executable { File? get executable {
return findExecutable(location, "FortniteClient-Win64-Shipping.exe"); return findExecutable(location, "FortniteClient-Win64-Shipping.exe");
} }
File get launcher { File? get launcher {
return findExecutable(location, "FortniteLauncher.exe"); return findExecutable(location, "FortniteLauncher.exe");
} }
File get eacExecutable { File? get eacExecutable {
return findExecutable(location, "FortniteClient-Win64-Shipping_EAC.exe"); return findExecutable(location, "FortniteClient-Win64-Shipping_EAC.exe");
} }

View File

@@ -31,7 +31,7 @@ class InfoPage extends StatelessWidget {
), ),
const Expanded( const Expanded(
child: Align( child: Align(
alignment: Alignment.bottomLeft, child: Text("Version 3.6${kDebugMode ? '-DEBUG' : ''}"))) alignment: Alignment.bottomLeft, child: Text("Version 3.7${kDebugMode ? '-DEBUG' : ''}")))
], ],
); );
} }

View File

@@ -12,7 +12,7 @@ Future<bool> injectDll(int pid, String dll) async {
var process = await shell.run("./injector.exe -p $pid --inject \"$dll\""); var process = await shell.run("./injector.exe -p $pid --inject \"$dll\"");
var success = process.outText.contains("Successfully injected module"); var success = process.outText.contains("Successfully injected module");
if (!success) { if (!success) {
injectLogFile.writeAsString(process.outText, mode: FileMode.append); injectLogFile.writeAsString(process.outText);
} }
return success; return success;

40
lib/src/util/patcher.dart Normal file
View File

@@ -0,0 +1,40 @@
import 'dart:io';
import 'dart:typed_data';
final Uint8List _original = Uint8List.fromList([
45, 0, 105, 0, 110, 0, 118, 0, 105, 0, 116, 0, 101, 0, 115, 0, 101, 0, 115, 0, 115, 0, 105, 0, 111, 0, 110, 0, 32, 0, 45, 0, 105, 0, 110, 0, 118, 0, 105, 0, 116, 0, 101, 0, 102, 0, 114, 0, 111, 0, 109, 0, 32, 0, 45, 0, 112, 0, 97, 0, 114, 0, 116, 0, 121, 0, 95, 0, 106, 0, 111, 0, 105, 0, 110, 0, 105, 0, 110, 0, 102, 0, 111, 0, 95, 0, 116, 0, 111, 0, 107, 0, 101, 0, 110, 0, 32, 0, 45, 0, 114, 0, 101, 0, 112, 0, 108, 0, 97, 0, 121, 0
]);
final Uint8List _patched = Uint8List.fromList([
45, 0, 108, 0, 111, 0, 103, 0, 32, 0, 45, 0, 110, 0, 111, 0, 115, 0, 112, 0, 108, 0, 97, 0, 115, 0, 104, 0, 32, 0, 45, 0, 110, 0, 111, 0, 115, 0, 111, 0, 117, 0, 110, 0, 100, 0, 32, 0, 45, 0, 110, 0, 117, 0, 108, 0, 108, 0, 114, 0, 104, 0, 105, 0, 32, 0, 45, 0, 117, 0, 115, 0, 101, 0, 111, 0, 108, 0, 100, 0, 105, 0, 116, 0, 101, 0, 109, 0, 99, 0, 97, 0, 114, 0, 100, 0, 115, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0
]);
Future<bool> patchExe(File file) async {
if(_original.length != _patched.length){
throw Exception("Cannot mutate length of binary file");
}
var read = await file.readAsBytes();
var length = await file.length();
var offset = 0;
var counter = 0;
while(offset < length){
if(read[offset] == _original[counter]){
counter++;
}else {
counter = 0;
}
offset++;
if(counter == _original.length){
for(var index = 0; index < _patched.length; index++){
read[offset - counter + index] = _patched[index];
}
await file.writeAsBytes(read, mode: FileMode.write);
return true;
}
}
return false;
}

View File

@@ -98,7 +98,7 @@ class AddLocalVersion extends StatelessWidget {
return "Directory doesn't exist"; return "Directory doesn't exist";
} }
if (!FortniteVersion.findExecutable(directory, "FortniteClient-Win64-Shipping.exe").existsSync()) { if (FortniteVersion.findExecutable(directory, "FortniteClient-Win64-Shipping.exe") == null) {
return "Invalid game path"; return "Invalid game path";
} }

View File

@@ -8,6 +8,7 @@ import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart'; import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/util/injector.dart'; import 'package:reboot_launcher/src/util/injector.dart';
import 'package:reboot_launcher/src/util/binary.dart'; import 'package:reboot_launcher/src/util/binary.dart';
import 'package:reboot_launcher/src/util/patcher.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:win32_suspend_process/win32_suspend_process.dart'; import 'package:win32_suspend_process/win32_suspend_process.dart';
@@ -35,7 +36,7 @@ class _LaunchButtonState extends State<LaunchButton> {
child: Obx(() => Tooltip( child: Obx(() => Tooltip(
message: _gameController.started.value ? "Close the running Fortnite instance" : "Launch a new Fortnite instance", message: _gameController.started.value ? "Close the running Fortnite instance" : "Launch a new Fortnite instance",
child: Button( child: Button(
onPressed: () => _onPressed(context), onPressed: _onPressed,
child: Text(_gameController.started.value ? "Close" : "Launch") child: Text(_gameController.started.value ? "Close" : "Launch")
), ),
)), )),
@@ -43,7 +44,7 @@ class _LaunchButtonState extends State<LaunchButton> {
); );
} }
void _onPressed(BuildContext context) async { void _onPressed() async {
if (_gameController.username.text.isEmpty) { if (_gameController.username.text.isEmpty) {
showSnackbar( showSnackbar(
context, const Snackbar(content: Text("Please type a username"))); context, const Snackbar(content: Text("Please type a username")));
@@ -84,38 +85,103 @@ class _LaunchButtonState extends State<LaunchButton> {
try { try {
_updateServerState(true); _updateServerState(true);
var version = _gameController.selectedVersionObs.value!; var version = _gameController.selectedVersionObs.value!;
if (await version.launcher.exists()) { var hosting = _gameController.host.value;
_gameController.launcherProcess = await Process.start(version.launcher.path, []); if (version.launcher != null) {
_gameController.launcherProcess = await Process.start(version.launcher!.path, []);
Win32Process(_gameController.launcherProcess!.pid).suspend(); Win32Process(_gameController.launcherProcess!.pid).suspend();
} }
if (await version.eacExecutable.exists()) { if (version.eacExecutable != null) {
_gameController.eacProcess = await Process.start(version.eacExecutable.path, []); _gameController.eacProcess = await Process.start(version.eacExecutable!.path, []);
Win32Process(_gameController.eacProcess!.pid).suspend(); Win32Process(_gameController.eacProcess!.pid).suspend();
} }
_gameController.gameProcess = await Process.start(version.executable.path, _createProcessArguments()) if(hosting){
..exitCode.then((_) => _onStop()) await patchExe(version.executable!);
}
_gameController.gameProcess = await Process.start(version.executable!.path, _createProcessArguments())
..exitCode.then((_) => _onEnd())
..outLines.forEach(_onGameOutput); ..outLines.forEach(_onGameOutput);
_injectOrShowError("cranium.dll"); await _injectOrShowError("cranium.dll");
if(hosting){
_showServerLaunchingWarning();
}
} catch (exception) { } catch (exception) {
_updateServerState(false); _closeDialogIfOpen();
_onError(exception); _onError(exception);
} }
} }
void _onGameOutput(line) { void _onEnd() {
_closeDialogIfOpen();
_onStop();
}
void _closeDialogIfOpen() {
if(!mounted){
return;
}
var route = ModalRoute.of(context);
if(route != null && !route.isCurrent){
Navigator.of(context).pop(false);
}
}
void _showServerLaunchingWarning() async {
var result = await showDialog<bool>(
context: context,
builder: (context) => ContentDialog(
content: const InfoLabel(
label: "Launching reboot server...",
child: SizedBox(
width: double.infinity,
child: ProgressBar()
)
),
actions: [
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: () {
Navigator.of(context).pop(false);
_onStop();
},
style: ButtonStyle(
backgroundColor: ButtonState.all(Colors.red)),
child: const Text('Cancel'),
)
)
],
)
);
if(result != null && result){
return;
}
_onStop();
}
void _onGameOutput(String line) {
if (line.contains("FOnlineSubsystemGoogleCommon::Shutdown()")) { if (line.contains("FOnlineSubsystemGoogleCommon::Shutdown()")) {
_onStop(); _onStop();
return; return;
} }
if (line.contains("[UFortUIManagerWidget_NUI::SetUIState]") && line.contains("FrontEnd")) { if (line.contains("Game Engine Initialized") && !_gameController.host.value) {
_injectOrShowError(_gameController.host.value ? "reboot.dll" : "console.dll"); _injectOrShowError("console.dll");
}
if(line.contains("added to UI Party led ") && _gameController.host.value){
_injectOrShowError("reboot.dll")
.then((value) => Navigator.of(context).pop(true));
} }
} }
Future<Object?> _onError(exception) { Future<Object?> _onError(Object exception) {
return showDialog( return showDialog(
context: context, context: context,
builder: (context) => ContentDialog( builder: (context) => ContentDialog(
@@ -127,7 +193,7 @@ class _LaunchButtonState extends State<LaunchButton> {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: FilledButton( child: FilledButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(true),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: ButtonState.all(Colors.red)), backgroundColor: ButtonState.all(Colors.red)),
child: const Text('Close'), child: const Text('Close'),
@@ -141,7 +207,7 @@ class _LaunchButtonState extends State<LaunchButton> {
_gameController.kill(); _gameController.kill();
} }
void _injectOrShowError(String binary) async { Future<void> _injectOrShowError(String binary) async {
var gameProcess = _gameController.gameProcess; var gameProcess = _gameController.gameProcess;
if (gameProcess == null) { if (gameProcess == null) {
return; return;
@@ -166,7 +232,7 @@ class _LaunchButtonState extends State<LaunchButton> {
} }
List<String> _createProcessArguments() { List<String> _createProcessArguments() {
return [ var args = [
"-epicapp=Fortnite", "-epicapp=Fortnite",
"-epicenv=Prod", "-epicenv=Prod",
"-epiclocale=en-us", "-epiclocale=en-us",
@@ -179,5 +245,11 @@ class _LaunchButtonState extends State<LaunchButton> {
"-AUTH_PASSWORD=Rebooted", "-AUTH_PASSWORD=Rebooted",
"-AUTH_TYPE=epic" "-AUTH_TYPE=epic"
]; ];
if(_gameController.host.value){
args.addAll(["-log", "-nullrhi", "-nosplash", "-nosound", "-unattended"]);
}
return args;
} }
} }

View File

@@ -50,11 +50,11 @@ class _SmartSwitchState extends State<SmartSwitch> {
double get _uncheckedOpacity => widget.enabled ? 0.8 : 0.5; double get _uncheckedOpacity => widget.enabled ? 0.8 : 0.5;
void _onChanged(checked) { void _onChanged(bool checked) {
if (!widget.enabled) { if (!widget.enabled) {
return; return;
} }
setState(() => widget.value(checked)); setState(() => widget.value.value = checked);
} }
} }

View File

@@ -15,6 +15,7 @@ import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/widget/scan_local_version.dart'; import 'package:reboot_launcher/src/widget/scan_local_version.dart';
import 'package:url_launcher/url_launcher.dart';
import '../controller/build_controller.dart'; import '../controller/build_controller.dart';
@@ -142,7 +143,7 @@ class VersionSelector extends StatelessWidget {
switch (result) { switch (result) {
case 0: case 0:
Navigator.of(context).pop(); Navigator.of(context).pop();
Process.run("explorer.exe", [version.location.path]); launchUrl(version.location.uri);
break; break;
case 1: case 1:

73
lib/test.dart Normal file
View File

@@ -0,0 +1,73 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:hex/hex.dart';
const String _original = "2d0069006e007600690074006500730065007300730069006f006e0020002d0069006e007600690074006500660072006f006d0020002d00700061007200740079005f006a006f0069006e0069006e0066006f005f0074006f006b0065006e0020002d007200650070006c0061007900";
const String _patched = "2d006c006f00670020002d006e006f00730070006c0061007300680020002d006e006f0073006f0075006e00640020002d006e0075006c006c0072006800690020002d007500730065006f006c0064006900740065006d00630061007200640073002000200020002000200020002000";
final Uint8List _originalBinary = Uint8List.fromList([
45, 0, 105, 0, 110, 0, 118, 0, 105, 0, 116, 0, 101, 0, 115, 0, 101, 0, 115, 0, 115, 0, 105, 0, 111, 0, 110, 0, 32, 0, 45, 0, 105, 0, 110, 0, 118, 0, 105, 0, 116, 0, 101, 0, 102, 0, 114, 0, 111, 0, 109, 0, 32, 0, 45, 0, 112, 0, 97, 0, 114, 0, 116, 0, 121, 0, 95, 0, 106, 0, 111, 0, 105, 0, 110, 0, 105, 0, 110, 0, 102, 0, 111, 0, 95, 0, 116, 0, 111, 0, 107, 0, 101, 0, 110, 0, 32, 0, 45, 0, 114, 0, 101, 0, 112, 0, 108, 0, 97, 0, 121, 0
]);
final Uint8List _patchedBinary = Uint8List.fromList([
45, 0, 108, 0, 111, 0, 103, 0, 32, 0, 45, 0, 110, 0, 111, 0, 115, 0, 112, 0, 108, 0, 97, 0, 115, 0, 104, 0, 32, 0, 45, 0, 110, 0, 111, 0, 115, 0, 111, 0, 117, 0, 110, 0, 100, 0, 32, 0, 45, 0, 110, 0, 117, 0, 108, 0, 108, 0, 114, 0, 104, 0, 105, 0, 32, 0, 45, 0, 117, 0, 115, 0, 101, 0, 111, 0, 108, 0, 100, 0, 105, 0, 116, 0, 101, 0, 109, 0, 99, 0, 97, 0, 114, 0, 100, 0, 115, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0, 32, 0
]);
Future<String> patchExeHex(File file) async {
Future<String> replaceBinary(File file, String original, String replacement) async {
var read = await file.readAsBytes();
var hex = HEX.encode(read);
var fixed = hex.replaceAll(original, replacement);
return fixed;
}
return await replaceBinary(file, _original, _patched);
}
Future<String> patchExeBinary(File file) async {
Future<String> replaceBinary(File file, Uint8List original, Uint8List replacement) async {
if(original.length != replacement.length){
throw Exception("Cannot mutate length of binary file");
}
var read = await file.readAsBytes();
var length = await file.length();
var offset = 0;
var counter = 0;
while(offset < length){
if(read[offset] == original[counter]){
counter++;
}else {
counter = 0;
}
offset++;
if(counter == original.length){
for(var index = 0; index < replacement.length; index++){
read[offset - counter + index] = replacement[index];
}
return HEX.encode(read);
}
}
throw Exception("No match");
}
return await replaceBinary(file, _originalBinary, _patchedBinary);
}
void main() async {
var file = File("D:\\Fortnite73\\FortniteGame\\Binaries\\Win64\\FortniteClient-Win64-Shipping.exe");
var hexed = await patchExeHex(file);
var binary = await patchExeBinary(file);
var offset = 0;
while(offset < hexed.length){
if(hexed[offset] != binary[offset]){
print("Difference ${hexed[offset]} != ${binary[offset]} at $offset");
}
offset++;
}
print(hexed == binary);
}

View File

@@ -1,6 +1,6 @@
name: reboot_launcher name: reboot_launcher
description: Launcher for project reboot description: Launcher for project reboot
version: "3.6.0" version: "3.7.0"
publish_to: 'none' publish_to: 'none'
@@ -29,6 +29,7 @@ dependencies:
get: ^4.6.5 get: ^4.6.5
get_storage: ^2.0.3 get_storage: ^2.0.3
window_manager: ^0.2.7 window_manager: ^0.2.7
hex: ^0.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -48,7 +49,7 @@ msix_config:
display_name: Reboot Launcher display_name: Reboot Launcher
publisher_display_name: Auties00 publisher_display_name: Auties00
identity_name: 31868Auties00.RebootLauncher identity_name: 31868Auties00.RebootLauncher
msix_version: 3.6.0.0 msix_version: 3.7.0.0
publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029 publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029
logo_path: ./assets/icons/reboot.ico logo_path: ./assets/icons/reboot.ico
architecture: x64 architecture: x64