Skip to content

Commit cd8c8e6

Browse files
committed
Release 9.2.3
1 parent a250501 commit cd8c8e6

File tree

11 files changed

+115
-64
lines changed

11 files changed

+115
-64
lines changed

common/lib/src/util/backend.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ final Semaphore _semaphore = Semaphore();
1515
String? _lastIp;
1616
String? _lastPort;
1717

18-
Future<Process> startEmbeddedBackend(bool detached) async => startProcess(
18+
Future<Process> startEmbeddedBackend(bool detached) async {
19+
final process = await startProcess(
1920
executable: backendStartExecutable,
2021
window: detached,
21-
);
22+
);
23+
process.stdOutput.listen((message) => log("[BACKEND] Message: $message"));
24+
process.stdError.listen((error) => log("[BACKEND] Error: $error"));
25+
process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode"));
26+
return process;
27+
}
2228

2329
Future<HttpServer> startRemoteBackendProxy(Uri uri) async => await serve(proxyHandler(uri), kDefaultBackendHost, kDefaultBackendPort);
2430

common/lib/src/util/log.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ final File launcherLogFile = _createLoggingFile();
77
final Semaphore _semaphore = Semaphore(1);
88

99
File _createLoggingFile() {
10-
final file = File("${logsDirectory.path}\\launcher.log");
10+
final file = File("${installationDirectory.path}\\launcher.log");
1111
file.parent.createSync(recursive: true);
1212
if(file.existsSync()) {
1313
file.deleteSync();

common/lib/src/util/path.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ Directory get assetsDirectory {
1414
return installationDirectory;
1515
}
1616

17-
Directory get logsDirectory =>
18-
Directory("${installationDirectory.path}\\logs");
19-
2017
Directory get settingsDirectory =>
2118
Directory("${installationDirectory.path}\\settings");
2219

common/lib/src/util/process.dart

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// ignore_for_file: non_constant_identifier_names
22

33
import 'dart:async';
4+
import 'dart:collection';
45
import 'dart:ffi';
56
import 'dart:io';
67
import 'dart:isolate';
@@ -9,6 +10,7 @@ import 'dart:math';
910
import 'package:ffi/ffi.dart';
1011
import 'package:path/path.dart' as path;
1112
import 'package:reboot_common/common.dart';
13+
import 'package:reboot_common/src/util/log.dart';
1214
import 'package:sync/semaphore.dart';
1315
import 'package:win32/win32.dart';
1416

@@ -105,53 +107,45 @@ Future<bool> startElevatedProcess({required String executable, required String a
105107
}
106108

107109
Future<Process> startProcess({required File executable, List<String>? args, bool useTempBatch = true, bool window = false, String? name, Map<String, String>? environment}) async {
110+
log("[PROCESS] Starting process on ${executable.path} with $args (useTempBatch: $useTempBatch, window: $window, name: $name, environment: $environment)");
108111
final argsOrEmpty = args ?? [];
112+
final workingDirectory = _getWorkingDirectory(executable);
109113
if(useTempBatch) {
110114
final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process");
111-
final tempScriptFile = File("${tempScriptDirectory.path}/process.bat");
115+
final tempScriptFile = File("${tempScriptDirectory.path}\\process.bat");
112116
final command = window ? 'cmd.exe /k ""${executable.path}" ${argsOrEmpty.join(" ")}"' : '"${executable.path}" ${argsOrEmpty.join(" ")}';
113117
await tempScriptFile.writeAsString(command, flush: true);
114118
final process = await Process.start(
115119
tempScriptFile.path,
116120
[],
117-
workingDirectory: executable.parent.path,
121+
workingDirectory: workingDirectory,
118122
environment: environment,
119123
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
120124
runInShell: window
121125
);
122-
return _withLogger(name, executable, process, window);
126+
return _ExtendedProcess(process, true);
123127
}
124128

125129
final process = await Process.start(
126130
executable.path,
127131
args ?? [],
128-
workingDirectory: executable.parent.path,
132+
workingDirectory: workingDirectory,
129133
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
130134
runInShell: window
131135
);
132-
return _withLogger(name, executable, process, window);
136+
return _ExtendedProcess(process, true);
133137
}
134138

135-
_ExtendedProcess _withLogger(String? name, File executable, Process process, bool window) {
136-
final extendedProcess = _ExtendedProcess(process, true);
137-
final loggingFile = File("${logsDirectory.path}\\${name ?? path.basenameWithoutExtension(executable.path)}-${DateTime.now().millisecondsSinceEpoch}.log");
138-
loggingFile.parent.createSync(recursive: true);
139-
if(loggingFile.existsSync()) {
140-
loggingFile.deleteSync();
141-
}
142-
143-
final semaphore = Semaphore(1);
144-
void logEvent(String event) async {
145-
await semaphore.acquire();
146-
await loggingFile.writeAsString("$event\n", mode: FileMode.append, flush: true);
147-
semaphore.release();
148-
}
149-
extendedProcess.stdOutput.listen(logEvent);
150-
extendedProcess.stdError.listen(logEvent);
151-
if(!window) {
152-
extendedProcess.exitCode.then((value) => logEvent("Process terminated with exit code: $value\n"));
139+
String? _getWorkingDirectory(File executable) {
140+
try {
141+
log("[PROCESS] Calculating working directory for $executable");
142+
final workingDirectory = executable.parent.resolveSymbolicLinksSync();
143+
log("[PROCESS] Using working directory: $workingDirectory");
144+
return workingDirectory;
145+
}catch(error) {
146+
log("[PROCESS] Cannot infer working directory: $error");
147+
return null;
153148
}
154-
return extendedProcess;
155149
}
156150

157151
final _NtResumeProcess = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
@@ -203,47 +197,61 @@ Future<bool> watchProcess(int pid) async {
203197
return await completer.future;
204198
}
205199

206-
// TODO: Template
207-
List<String> createRebootArgs(String username, String password, bool host, GameServerType hostType, bool log, String additionalArgs) {
200+
List<String> createRebootArgs(String username, String password, bool host, GameServerType hostType, bool logging, String additionalArgs) {
201+
log("[PROCESS] Generating reboot args");
208202
if(password.isEmpty) {
209203
username = '${_parseUsername(username, host)}@projectreboot.dev';
210204
}
211205

212206
password = password.isNotEmpty ? password : "Rebooted";
213-
final args = [
214-
"-epicapp=Fortnite",
215-
"-epicenv=Prod",
216-
"-epiclocale=en-us",
217-
"-epicportal",
218-
"-skippatchcheck",
219-
"-nobe",
220-
"-fromfl=eac",
221-
"-fltoken=3db3ba5dcbd2e16703f3978d",
222-
"-caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
223-
"-AUTH_LOGIN=$username",
224-
"-AUTH_PASSWORD=${password.isNotEmpty ? password : "Rebooted"}",
225-
"-AUTH_TYPE=epic"
226-
];
227-
228-
if(log) {
229-
args.add("-log");
207+
final args = LinkedHashMap<String, String>(
208+
equals: (a, b) => a.toUpperCase() == b.toUpperCase(),
209+
hashCode: (a) => a.toUpperCase().hashCode
210+
);
211+
args.addAll({
212+
"-epicapp": "Fortnite",
213+
"-epicenv": "Prod",
214+
"-epiclocale": "en-us",
215+
"-epicportal": "",
216+
"-skippatchcheck": "",
217+
"-nobe": "",
218+
"-fromfl": "eac",
219+
"-fltoken": "3db3ba5dcbd2e16703f3978d",
220+
"-caldera": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
221+
"-AUTH_LOGIN": username,
222+
"-AUTH_PASSWORD": password.isNotEmpty ? password : "Rebooted",
223+
"-AUTH_TYPE": "epic"
224+
});
225+
226+
if(logging) {
227+
args["-log"] = "";
230228
}
231229

232230
if(host) {
233-
args.addAll([
234-
"-nosplash",
235-
"-nosound"
236-
]);
231+
args["-nosplash"] = "";
232+
args["-nosound"] = "";
237233
if(hostType == GameServerType.headless){
238-
args.add("-nullrhi");
234+
args["-nullrhi"] = "";
239235
}
240236
}
241237

242-
if(additionalArgs.isNotEmpty){
243-
args.addAll(additionalArgs.split(" "));
238+
log("[PROCESS] Default args: $args");
239+
log("[PROCESS] Adding custom args: $additionalArgs");
240+
for(final additionalArg in additionalArgs.split(" ")) {
241+
log("[PROCESS] Processing custom arg: $additionalArg");
242+
final separatorIndex = additionalArg.indexOf("=");
243+
final argName = separatorIndex == -1 ? additionalArg : additionalArg.substring(0, separatorIndex);
244+
log("[PROCESS] Custom arg key: $argName");
245+
final argValue = separatorIndex == -1 || separatorIndex + 1 >= additionalArg.length ? "" : additionalArg.substring(separatorIndex + 1);
246+
log("[PROCESS] Custom arg value: $argValue");
247+
args[argName] = argValue;
248+
log("[PROCESS] Updated args: $args");
244249
}
245250

246-
return args;
251+
log("[PROCESS] Final args result: $args");
252+
return args.entries
253+
.map((entry) => entry.value.isEmpty ? entry.key : "${entry.key}=${entry.value}")
254+
.toList();
247255
}
248256

249257
void handleGameOutput({
@@ -257,16 +265,22 @@ void handleGameOutput({
257265
required void Function() onBuildCorrupted,
258266
}) {
259267
if (line.contains(kShutdownLine)) {
268+
log("[FORTNITE_OUTPUT_HANDLER] Detected shutdown: $line");
260269
onShutdown();
261270
}else if(kCorruptedBuildErrors.any((element) => line.contains(element))){
271+
log("[FORTNITE_OUTPUT_HANDLER] Detected corrupt build: $line");
262272
onBuildCorrupted();
263273
}else if(kCannotConnectErrors.any((element) => line.contains(element))){
274+
log("[FORTNITE_OUTPUT_HANDLER] Detected cannot connect error: $line");
264275
onTokenError();
265276
}else if(kLoggedInLines.every((entry) => line.contains(entry))) {
277+
log("[FORTNITE_OUTPUT_HANDLER] Detected logged in: $line");
266278
onLoggedIn();
267279
}else if(line.contains(kGameFinishedLine) && host) {
280+
log("[FORTNITE_OUTPUT_HANDLER] Detected match end: $line");
268281
onMatchEnd();
269282
}else if(line.contains(kDisplayInitializedLine) && host) {
283+
log("[FORTNITE_OUTPUT_HANDLER] Detected display attach: $line");
270284
onDisplayAttached();
271285
}
272286
}

gui/lib/l10n/reboot_en.arb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
"settingsClientDescription": "Configure the internal files used by the launcher for Fortnite",
8080
"settingsClientOptionsName": "Options",
8181
"settingsClientOptionsDescription": "Configure additional options for Fortnite",
82-
"settingsClientConsoleName": "Unreal engine console",
82+
"settingsClientConsoleName": "Unreal engine patcher",
8383
"settingsClientConsoleDescription": "Unlocks the Unreal Engine Console",
8484
"settingsClientConsoleKeyName": "Unreal engine console key",
8585
"settingsClientConsoleKeyDescription": "The keyboard key used to open the Unreal Engine console",
@@ -88,7 +88,7 @@
8888
"settingsClientMemoryName": "Memory patcher",
8989
"settingsClientMemoryDescription": "Prevents the client from crashing because of a memory leak",
9090
"settingsClientArgsName": "Custom launch arguments",
91-
"settingsClientArgsDescription": "Additional arguments to use when launching the game",
91+
"settingsClientArgsDescription": "Additional arguments to use when launching Fortnite",
9292
"settingsClientArgsPlaceholder": "Arguments...",
9393
"settingsServerName": "Internal files",
9494
"settingsServerSubtitle": "Configure the internal files used by the launcher for the game server",
@@ -156,6 +156,7 @@
156156
"launchingGameClientAndServer": "Launching the game client and server...",
157157
"startGameServer": "Start a game server",
158158
"usernameOrEmail": "Username/Email",
159+
"invalidEmail": "Invalid email",
159160
"usernameOrEmailPlaceholder": "Type your username or email",
160161
"password": "Password",
161162
"passwordPlaceholder": "Type your password, if you want to use one",

gui/lib/src/controller/game_controller.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ class GameController extends GetxController {
4141
password = TextEditingController(text: _storage?.read("password") ?? "");
4242
password.addListener(() => _storage?.write("password", password.text));
4343
customLaunchArgs = TextEditingController(text: _storage?.read("custom_launch_args") ?? "");
44-
customLaunchArgs.addListener(() =>
45-
_storage?.write("custom_launch_args", customLaunchArgs.text));
44+
customLaunchArgs.addListener(() => _storage?.write("custom_launch_args", customLaunchArgs.text));
4645
started = RxBool(false);
4746
instance = Rxn();
4847
consoleKey = Rx(_readConsoleKey());

gui/lib/src/controller/hosting_controller.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class HostingController extends GetxController {
2828
late final RxBool published;
2929
late final Rxn<GameInstance> instance;
3030
late final Rxn<Set<FortniteServer>> servers;
31+
late final TextEditingController customLaunchArgs;
3132
late final Semaphore _semaphore;
3233

3334
HostingController() {
@@ -62,6 +63,8 @@ class HostingController extends GetxController {
6263
servers.value = event;
6364
published.value = event.any((element) => element.id == uuid);
6465
});
66+
customLaunchArgs = TextEditingController(text: _storage?.read("custom_launch_args") ?? "");
67+
customLaunchArgs.addListener(() => _storage?.write("custom_launch_args", customLaunchArgs.text));
6568
_semaphore = Semaphore();
6669
}
6770

gui/lib/src/messenger/implementation/profile.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:email_validator/email_validator.dart';
12
import 'package:fluent_ui/fluent_ui.dart';
23
import 'package:flutter/material.dart' show Icons;
34
import 'package:get/get.dart';
@@ -23,6 +24,17 @@ Future<bool> showProfileForm(BuildContext context) async{
2324
label: translations.usernameOrEmail,
2425
child: TextFormBox(
2526
placeholder: translations.usernameOrEmailPlaceholder,
27+
validator: (text) {
28+
if(_gameController.password.text.isEmpty) {
29+
return null;
30+
}
31+
32+
if(EmailValidator.validate(_gameController.username.text)) {
33+
return null;
34+
}
35+
36+
return translations.invalidEmail;
37+
},
2638
controller: _gameController.username,
2739
autovalidateMode: AutovalidateMode.always,
2840
enableSuggestions: true,

gui/lib/src/page/implementation/host_page.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,17 @@ class _HostingPageState extends RebootPageState<HostPage> {
199199
title: Text(translations.settingsServerOptionsName),
200200
subtitle: Text(translations.settingsServerOptionsSubtitle),
201201
children: [
202+
SettingTile(
203+
icon: Icon(
204+
FluentIcons.options_24_regular
205+
),
206+
title: Text(translations.settingsClientArgsName),
207+
subtitle: Text(translations.settingsClientArgsDescription),
208+
content: TextFormBox(
209+
placeholder: translations.settingsClientArgsPlaceholder,
210+
controller: _hostingController.customLaunchArgs,
211+
)
212+
),
202213
SettingTile(
203214
icon: Icon(
204215
FluentIcons.window_console_20_regular
@@ -253,7 +264,7 @@ class _HostingPageState extends RebootPageState<HostPage> {
253264
FilteringTextInputFormatter.digitsOnly
254265
]
255266
)
256-
),
267+
)
257268
],
258269
);
259270

gui/lib/src/widget/game_start_button.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ class _LaunchButtonState extends State<LaunchButton> {
251251
host,
252252
hostType,
253253
false,
254-
""
254+
host ? _hostingController.customLaunchArgs.text : _gameController.customLaunchArgs.text
255255
);
256256
log("[${host ? 'HOST' : 'GAME'}] Generated game args: ${gameArgs.join(" ")}");
257257
final gameProcess = await startProcess(

0 commit comments

Comments
 (0)