Skip to content

Commit a787c4e

Browse files
authored
Merge pull request #86 from Auties00/_onLoggedIn
Release 9.2.5
2 parents 3f88d5e + 4c3fe9b commit a787c4e

24 files changed

+627
-454
lines changed

backend/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ express.use(require("./structure/matchmaking.js"));
3535
express.use(require("./structure/cloudstorage.js"));
3636
express.use(require("./structure/mcp.js"));
3737

38-
const port = process.env.PORT || 3551;
38+
const port = 3551;
3939
express.listen(port, () => {
4040
console.log("LawinServer started listening on port", port);
4141

common/lib/src/constant/game.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const List<String> kCorruptedBuildErrors = [
1111
"Critical error",
1212
"when 0 bytes remain",
1313
"Pak chunk signature verification failed!",
14-
"Couldn't find pak signature file"
14+
"LogWindows:Error: Fatal error!"
1515
];
1616
const List<String> kCannotConnectErrors = [
1717
"port 3551 failed: Connection refused",

common/lib/src/extension/path.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,22 @@ extension FortniteVersionExtension on FortniteVersion {
99

1010
static File? findFile(Directory directory, String name) {
1111
try{
12-
final result = directory.listSync(recursive: true)
13-
.firstWhere((element) => path.basename(element.path) == name);
14-
return File(result.path);
12+
for(final child in directory.listSync()) {
13+
if(child is Directory) {
14+
if(!path.basename(child.path).startsWith("\.")) {
15+
final result = findFile(child, name);
16+
if(result != null) {
17+
return result;
18+
}
19+
}
20+
}else if(child is File) {
21+
if(path.basename(child.path) == name) {
22+
return child;
23+
}
24+
}
25+
}
26+
27+
return null;
1528
}catch(_){
1629
return null;
1730
}

common/lib/src/util/backend.dart

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,19 @@ 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, {void Function(String)? onError}) 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) {
25+
log("[BACKEND] Error: $error");
26+
onError?.call(error);
27+
});
28+
process.exitCode.then((exitCode) => log("[BACKEND] Exit code: $exitCode"));
29+
return process;
30+
}
2231

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

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: 74 additions & 54 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

@@ -100,58 +102,49 @@ Future<bool> startElevatedProcess({required String executable, required String a
100102
shellInput.ref.fMask = ES_AWAYMODE_REQUIRED;
101103
shellInput.ref.lpVerb = "runas".toNativeUtf16();
102104
shellInput.ref.cbSize = sizeOf<SHELLEXECUTEINFO>();
103-
var shellResult = ShellExecuteEx(shellInput);
104-
return shellResult == 1;
105+
return ShellExecuteEx(shellInput) == 1;
105106
}
106107

107108
Future<Process> startProcess({required File executable, List<String>? args, bool useTempBatch = true, bool window = false, String? name, Map<String, String>? environment}) async {
109+
log("[PROCESS] Starting process on ${executable.path} with $args (useTempBatch: $useTempBatch, window: $window, name: $name, environment: $environment)");
108110
final argsOrEmpty = args ?? [];
111+
final workingDirectory = _getWorkingDirectory(executable);
109112
if(useTempBatch) {
110113
final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process");
111-
final tempScriptFile = File("${tempScriptDirectory.path}/process.bat");
114+
final tempScriptFile = File("${tempScriptDirectory.path}\\process.bat");
112115
final command = window ? 'cmd.exe /k ""${executable.path}" ${argsOrEmpty.join(" ")}"' : '"${executable.path}" ${argsOrEmpty.join(" ")}';
113116
await tempScriptFile.writeAsString(command, flush: true);
114117
final process = await Process.start(
115118
tempScriptFile.path,
116119
[],
117-
workingDirectory: executable.parent.path,
120+
workingDirectory: workingDirectory,
118121
environment: environment,
119122
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
120123
runInShell: window
121124
);
122-
return _withLogger(name, executable, process, window);
125+
return _ExtendedProcess(process, true);
123126
}
124127

125128
final process = await Process.start(
126129
executable.path,
127130
args ?? [],
128-
workingDirectory: executable.parent.path,
131+
workingDirectory: workingDirectory,
129132
mode: window ? ProcessStartMode.detachedWithStdio : ProcessStartMode.normal,
130133
runInShell: window
131134
);
132-
return _withLogger(name, executable, process, window);
135+
return _ExtendedProcess(process, true);
133136
}
134137

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"));
138+
String? _getWorkingDirectory(File executable) {
139+
try {
140+
log("[PROCESS] Calculating working directory for $executable");
141+
final workingDirectory = executable.parent.resolveSymbolicLinksSync();
142+
log("[PROCESS] Using working directory: $workingDirectory");
143+
return workingDirectory;
144+
}catch(error) {
145+
log("[PROCESS] Cannot infer working directory: $error");
146+
return null;
153147
}
154-
return extendedProcess;
155148
}
156149

157150
final _NtResumeProcess = _ntdll.lookupFunction<Int32 Function(IntPtr hWnd),
@@ -203,47 +196,61 @@ Future<bool> watchProcess(int pid) async {
203196
return await completer.future;
204197
}
205198

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

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

232229
if(host) {
233-
args.addAll([
234-
"-nosplash",
235-
"-nosound"
236-
]);
230+
args["-nosplash"] = "";
231+
args["-nosound"] = "";
237232
if(hostType == GameServerType.headless){
238-
args.add("-nullrhi");
233+
args["-nullrhi"] = "";
239234
}
240235
}
241236

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

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

249256
void handleGameOutput({
@@ -257,16 +264,22 @@ void handleGameOutput({
257264
required void Function() onBuildCorrupted,
258265
}) {
259266
if (line.contains(kShutdownLine)) {
267+
log("[FORTNITE_OUTPUT_HANDLER] Detected shutdown: $line");
260268
onShutdown();
261269
}else if(kCorruptedBuildErrors.any((element) => line.contains(element))){
270+
log("[FORTNITE_OUTPUT_HANDLER] Detected corrupt build: $line");
262271
onBuildCorrupted();
263272
}else if(kCannotConnectErrors.any((element) => line.contains(element))){
273+
log("[FORTNITE_OUTPUT_HANDLER] Detected cannot connect error: $line");
264274
onTokenError();
265275
}else if(kLoggedInLines.every((entry) => line.contains(entry))) {
276+
log("[FORTNITE_OUTPUT_HANDLER] Detected logged in: $line");
266277
onLoggedIn();
267278
}else if(line.contains(kGameFinishedLine) && host) {
279+
log("[FORTNITE_OUTPUT_HANDLER] Detected match end: $line");
268280
onMatchEnd();
269281
}else if(line.contains(kDisplayInitializedLine) && host) {
282+
log("[FORTNITE_OUTPUT_HANDLER] Detected display attach: $line");
270283
onDisplayAttached();
271284
}
272285
}
@@ -299,7 +312,14 @@ final class _ExtendedProcess implements Process {
299312

300313

301314
@override
302-
Future<int> get exitCode => _delegate.exitCode;
315+
Future<int> get exitCode {
316+
try {
317+
return _delegate.exitCode;
318+
}catch(_) {
319+
return watchProcess(_delegate.pid)
320+
.then((_) => -1);
321+
}
322+
}
303323

304324
@override
305325
bool kill([ProcessSignal signal = ProcessSignal.sigterm]) => _delegate.kill(signal);

gui/assets/backend/lawinserver.exe

-20 Bytes
Binary file not shown.

gui/lib/l10n/reboot_en.arb

Lines changed: 9 additions & 5 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",
@@ -118,9 +118,7 @@
118118
"settingsUtilsResetDefaultsName": "Reset settings",
119119
"settingsUtilsResetDefaultsSubtitle": "Resets the launcher's settings to their default values",
120120
"settingsUtilsDialogTitle": "Do you want to reset all the setting in this tab to their default values? This action is irreversible",
121-
"settingsUtilsResetDefaultsContent": "Reset",
122121
"settingsUtilsDialogSecondaryAction": "Close",
123-
"settingsUtilsDialogPrimaryAction": "Reset",
124122
"selectFortniteName": "Fortnite version",
125123
"selectFortniteDescription": "Select the version of Fortnite you want to use",
126124
"manageVersionsName": "Manage versions",
@@ -156,6 +154,7 @@
156154
"launchingGameClientAndServer": "Launching the game client and server...",
157155
"startGameServer": "Start a game server",
158156
"usernameOrEmail": "Username/Email",
157+
"invalidEmail": "Invalid email",
159158
"usernameOrEmailPlaceholder": "Type your username or email",
160159
"password": "Password",
161160
"passwordPlaceholder": "Type your password, if you want to use one",
@@ -261,6 +260,7 @@
261260
"missingCustomDllError": "The custom {dll}.dll doesn't exist: check your settings",
262261
"tokenError": "Cannot log in into Fortnite: authentication error (injected dlls: {dlls})",
263262
"unknownFortniteError": "An unknown error occurred while launching Fortnite: {error}",
263+
"fortniteCrashError": "The {name} crashed after being launched",
264264
"serverNoLongerAvailableUnnamed": "The previous server is no longer available",
265265
"noServerFound": "No server found: invalid or expired link",
266266
"settingsUtilsThemeName": "Theme",
@@ -320,6 +320,7 @@
320320
"none": "none",
321321
"openLog": "Open log",
322322
"backendProcessError": "The backend shut down unexpectedly",
323+
"backendErrorMessage": "The backend reported an unexpected error",
323324
"welcomeTitle": "Welcome to Reboot Launcher",
324325
"welcomeDescription": "If you have never used a Fortnite game server, or this launcher in particular, please click on take a tour\nPlease don't ask for support on Discord without taking the tour: this helps me prioritize real bugs\nYou can always take the tour again in the Info tab",
325326
"welcomeAction": "Take the tour",
@@ -363,5 +364,8 @@
363364
"promptSettingsTabActionLabel": "Done",
364365
"automaticGameServerDialogContent": "The launcher detected that you are not running a game server, but that your matchmaker is set to your local machine. If you don't want to join another player's server, you should start a game server. This is necessary to be able to play!",
365366
"automaticGameServerDialogIgnore": "Ignore",
366-
"automaticGameServerDialogStart": "Start server"
367+
"automaticGameServerDialogStart": "Start server",
368+
"gameResetDefaultsName": "Reset",
369+
"gameResetDefaultsDescription": "Resets the game's settings to their default values",
370+
"gameResetDefaultsContent": "Reset"
367371
}

0 commit comments

Comments
 (0)