1
1
// ignore_for_file: non_constant_identifier_names
2
2
3
3
import 'dart:async' ;
4
+ import 'dart:collection' ;
4
5
import 'dart:ffi' ;
5
6
import 'dart:io' ;
6
7
import 'dart:isolate' ;
@@ -9,6 +10,7 @@ import 'dart:math';
9
10
import 'package:ffi/ffi.dart' ;
10
11
import 'package:path/path.dart' as path;
11
12
import 'package:reboot_common/common.dart' ;
13
+ import 'package:reboot_common/src/util/log.dart' ;
12
14
import 'package:sync/semaphore.dart' ;
13
15
import 'package:win32/win32.dart' ;
14
16
@@ -105,53 +107,45 @@ Future<bool> startElevatedProcess({required String executable, required String a
105
107
}
106
108
107
109
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 )" );
108
111
final argsOrEmpty = args ?? [];
112
+ final workingDirectory = _getWorkingDirectory (executable);
109
113
if (useTempBatch) {
110
114
final tempScriptDirectory = await tempDirectory.createTemp ("reboot_launcher_process" );
111
- final tempScriptFile = File ("${tempScriptDirectory .path }/ process.bat" );
115
+ final tempScriptFile = File ("${tempScriptDirectory .path }\\ process.bat" );
112
116
final command = window ? 'cmd.exe /k ""${executable .path }" ${argsOrEmpty .join (" " )}"' : '"${executable .path }" ${argsOrEmpty .join (" " )}' ;
113
117
await tempScriptFile.writeAsString (command, flush: true );
114
118
final process = await Process .start (
115
119
tempScriptFile.path,
116
120
[],
117
- workingDirectory: executable.parent.path ,
121
+ workingDirectory: workingDirectory ,
118
122
environment: environment,
119
123
mode: window ? ProcessStartMode .detachedWithStdio : ProcessStartMode .normal,
120
124
runInShell: window
121
125
);
122
- return _withLogger (name, executable, process, window );
126
+ return _ExtendedProcess ( process, true );
123
127
}
124
128
125
129
final process = await Process .start (
126
130
executable.path,
127
131
args ?? [],
128
- workingDirectory: executable.parent.path ,
132
+ workingDirectory: workingDirectory ,
129
133
mode: window ? ProcessStartMode .detachedWithStdio : ProcessStartMode .normal,
130
134
runInShell: window
131
135
);
132
- return _withLogger (name, executable, process, window );
136
+ return _ExtendedProcess ( process, true );
133
137
}
134
138
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 ;
153
148
}
154
- return extendedProcess;
155
149
}
156
150
157
151
final _NtResumeProcess = _ntdll.lookupFunction< Int32 Function (IntPtr hWnd),
@@ -203,47 +197,61 @@ Future<bool> watchProcess(int pid) async {
203
197
return await completer.future;
204
198
}
205
199
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" );
208
202
if (password.isEmpty) {
209
203
username = '${_parseUsername (username , host )}@projectreboot.dev' ;
210
204
}
211
205
212
206
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" ] = "" ;
230
228
}
231
229
232
230
if (host) {
233
- args.addAll ([
234
- "-nosplash" ,
235
- "-nosound"
236
- ]);
231
+ args["-nosplash" ] = "" ;
232
+ args["-nosound" ] = "" ;
237
233
if (hostType == GameServerType .headless){
238
- args. add ( "-nullrhi" ) ;
234
+ args[ "-nullrhi" ] = "" ;
239
235
}
240
236
}
241
237
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 " );
244
249
}
245
250
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 ();
247
255
}
248
256
249
257
void handleGameOutput ({
@@ -257,16 +265,22 @@ void handleGameOutput({
257
265
required void Function () onBuildCorrupted,
258
266
}) {
259
267
if (line.contains (kShutdownLine)) {
268
+ log ("[FORTNITE_OUTPUT_HANDLER] Detected shutdown: $line " );
260
269
onShutdown ();
261
270
}else if (kCorruptedBuildErrors.any ((element) => line.contains (element))){
271
+ log ("[FORTNITE_OUTPUT_HANDLER] Detected corrupt build: $line " );
262
272
onBuildCorrupted ();
263
273
}else if (kCannotConnectErrors.any ((element) => line.contains (element))){
274
+ log ("[FORTNITE_OUTPUT_HANDLER] Detected cannot connect error: $line " );
264
275
onTokenError ();
265
276
}else if (kLoggedInLines.every ((entry) => line.contains (entry))) {
277
+ log ("[FORTNITE_OUTPUT_HANDLER] Detected logged in: $line " );
266
278
onLoggedIn ();
267
279
}else if (line.contains (kGameFinishedLine) && host) {
280
+ log ("[FORTNITE_OUTPUT_HANDLER] Detected match end: $line " );
268
281
onMatchEnd ();
269
282
}else if (line.contains (kDisplayInitializedLine) && host) {
283
+ log ("[FORTNITE_OUTPUT_HANDLER] Detected display attach: $line " );
270
284
onDisplayAttached ();
271
285
}
272
286
}
0 commit comments