mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
Added nullrhi option
This commit is contained in:
Binary file not shown.
@@ -36,15 +36,16 @@ class GameController extends GetxController {
|
||||
_selectedVersion = Rxn(decodedSelectedVersion);
|
||||
|
||||
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.addListener(() async {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,33 +12,29 @@ class FortniteVersion {
|
||||
|
||||
FortniteVersion({required this.name, required this.location});
|
||||
|
||||
static File findExecutable(Directory directory, String name) {
|
||||
if(path.basename(directory.path) == "FortniteGame"){
|
||||
return File("$directory/Binaries/Win64/$name");
|
||||
}
|
||||
|
||||
static File? findExecutable(Directory directory, String name) {
|
||||
try{
|
||||
var gameDirectory = directory.listSync(recursive: true)
|
||||
.firstWhereOrNull((element) => path.basename(element.path) == "FortniteGame");
|
||||
if(gameDirectory == null){
|
||||
return File("${directory.path}/Binaries/Win64/$name");
|
||||
var result = directory.listSync(recursive: true)
|
||||
.firstWhereOrNull((element) => path.basename(element.path) == name);
|
||||
if(result == null){
|
||||
return null;
|
||||
}
|
||||
|
||||
return File("${gameDirectory.path}/Binaries/Win64/$name");
|
||||
return File(result.path);
|
||||
}catch(_){
|
||||
return File("${directory.path}/Binaries/Win64/$name");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
File get executable {
|
||||
File? get executable {
|
||||
return findExecutable(location, "FortniteClient-Win64-Shipping.exe");
|
||||
}
|
||||
|
||||
File get launcher {
|
||||
File? get launcher {
|
||||
return findExecutable(location, "FortniteLauncher.exe");
|
||||
}
|
||||
|
||||
File get eacExecutable {
|
||||
File? get eacExecutable {
|
||||
return findExecutable(location, "FortniteClient-Win64-Shipping_EAC.exe");
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class InfoPage extends StatelessWidget {
|
||||
),
|
||||
const Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomLeft, child: Text("Version 3.6${kDebugMode ? '-DEBUG' : ''}")))
|
||||
alignment: Alignment.bottomLeft, child: Text("Version 3.7${kDebugMode ? '-DEBUG' : ''}")))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ Future<bool> injectDll(int pid, String dll) async {
|
||||
var process = await shell.run("./injector.exe -p $pid --inject \"$dll\"");
|
||||
var success = process.outText.contains("Successfully injected module");
|
||||
if (!success) {
|
||||
injectLogFile.writeAsString(process.outText, mode: FileMode.append);
|
||||
injectLogFile.writeAsString(process.outText);
|
||||
}
|
||||
|
||||
return success;
|
||||
|
||||
40
lib/src/util/patcher.dart
Normal file
40
lib/src/util/patcher.dart
Normal 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;
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class AddLocalVersion extends StatelessWidget {
|
||||
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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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/util/injector.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:win32_suspend_process/win32_suspend_process.dart';
|
||||
|
||||
@@ -35,7 +36,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
child: Obx(() => Tooltip(
|
||||
message: _gameController.started.value ? "Close the running Fortnite instance" : "Launch a new Fortnite instance",
|
||||
child: Button(
|
||||
onPressed: () => _onPressed(context),
|
||||
onPressed: _onPressed,
|
||||
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) {
|
||||
showSnackbar(
|
||||
context, const Snackbar(content: Text("Please type a username")));
|
||||
@@ -84,38 +85,103 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
try {
|
||||
_updateServerState(true);
|
||||
var version = _gameController.selectedVersionObs.value!;
|
||||
if (await version.launcher.exists()) {
|
||||
_gameController.launcherProcess = await Process.start(version.launcher.path, []);
|
||||
var hosting = _gameController.host.value;
|
||||
if (version.launcher != null) {
|
||||
_gameController.launcherProcess = await Process.start(version.launcher!.path, []);
|
||||
Win32Process(_gameController.launcherProcess!.pid).suspend();
|
||||
}
|
||||
|
||||
if (await version.eacExecutable.exists()) {
|
||||
_gameController.eacProcess = await Process.start(version.eacExecutable.path, []);
|
||||
if (version.eacExecutable != null) {
|
||||
_gameController.eacProcess = await Process.start(version.eacExecutable!.path, []);
|
||||
Win32Process(_gameController.eacProcess!.pid).suspend();
|
||||
}
|
||||
|
||||
_gameController.gameProcess = await Process.start(version.executable.path, _createProcessArguments())
|
||||
..exitCode.then((_) => _onStop())
|
||||
if(hosting){
|
||||
await patchExe(version.executable!);
|
||||
}
|
||||
|
||||
_gameController.gameProcess = await Process.start(version.executable!.path, _createProcessArguments())
|
||||
..exitCode.then((_) => _onEnd())
|
||||
..outLines.forEach(_onGameOutput);
|
||||
_injectOrShowError("cranium.dll");
|
||||
await _injectOrShowError("cranium.dll");
|
||||
|
||||
if(hosting){
|
||||
_showServerLaunchingWarning();
|
||||
}
|
||||
} catch (exception) {
|
||||
_updateServerState(false);
|
||||
_closeDialogIfOpen();
|
||||
_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()")) {
|
||||
_onStop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.contains("[UFortUIManagerWidget_NUI::SetUIState]") && line.contains("FrontEnd")) {
|
||||
_injectOrShowError(_gameController.host.value ? "reboot.dll" : "console.dll");
|
||||
if (line.contains("Game Engine Initialized") && !_gameController.host.value) {
|
||||
_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(
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
@@ -127,7 +193,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: ButtonState.all(Colors.red)),
|
||||
child: const Text('Close'),
|
||||
@@ -141,7 +207,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
_gameController.kill();
|
||||
}
|
||||
|
||||
void _injectOrShowError(String binary) async {
|
||||
Future<void> _injectOrShowError(String binary) async {
|
||||
var gameProcess = _gameController.gameProcess;
|
||||
if (gameProcess == null) {
|
||||
return;
|
||||
@@ -166,7 +232,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
}
|
||||
|
||||
List<String> _createProcessArguments() {
|
||||
return [
|
||||
var args = [
|
||||
"-epicapp=Fortnite",
|
||||
"-epicenv=Prod",
|
||||
"-epiclocale=en-us",
|
||||
@@ -179,5 +245,11 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
"-AUTH_PASSWORD=Rebooted",
|
||||
"-AUTH_TYPE=epic"
|
||||
];
|
||||
|
||||
if(_gameController.host.value){
|
||||
args.addAll(["-log", "-nullrhi", "-nosplash", "-nosound", "-unattended"]);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ class _SmartSwitchState extends State<SmartSwitch> {
|
||||
|
||||
double get _uncheckedOpacity => widget.enabled ? 0.8 : 0.5;
|
||||
|
||||
void _onChanged(checked) {
|
||||
void _onChanged(bool checked) {
|
||||
if (!widget.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => widget.value(checked));
|
||||
setState(() => widget.value.value = checked);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/widget/scan_local_version.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../controller/build_controller.dart';
|
||||
|
||||
@@ -142,7 +143,7 @@ class VersionSelector extends StatelessWidget {
|
||||
switch (result) {
|
||||
case 0:
|
||||
Navigator.of(context).pop();
|
||||
Process.run("explorer.exe", [version.location.path]);
|
||||
launchUrl(version.location.uri);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
|
||||
73
lib/test.dart
Normal file
73
lib/test.dart
Normal 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);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
name: reboot_launcher
|
||||
description: Launcher for project reboot
|
||||
version: "3.6.0"
|
||||
version: "3.7.0"
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
@@ -29,6 +29,7 @@ dependencies:
|
||||
get: ^4.6.5
|
||||
get_storage: ^2.0.3
|
||||
window_manager: ^0.2.7
|
||||
hex: ^0.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -48,7 +49,7 @@ msix_config:
|
||||
display_name: Reboot Launcher
|
||||
publisher_display_name: Auties00
|
||||
identity_name: 31868Auties00.RebootLauncher
|
||||
msix_version: 3.6.0.0
|
||||
msix_version: 3.7.0.0
|
||||
publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029
|
||||
logo_path: ./assets/icons/reboot.ico
|
||||
architecture: x64
|
||||
|
||||
Reference in New Issue
Block a user