mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
9.0.2
This commit is contained in:
@@ -1,11 +1,7 @@
|
||||
export 'package:reboot_common/src/constant/authenticator.dart';
|
||||
export 'package:reboot_common/src/constant/game.dart';
|
||||
export 'package:reboot_common/src/constant/matchmaker.dart';
|
||||
export 'package:reboot_common/src/constant/os.dart';
|
||||
export 'package:reboot_common/src/constant/supabase.dart';
|
||||
|
||||
|
||||
export 'package:reboot_common/src/model/archive.dart';
|
||||
export 'package:reboot_common/src/model/fortnite_build.dart';
|
||||
export 'package:reboot_common/src/model/fortnite_version.dart';
|
||||
export 'package:reboot_common/src/model/game_instance.dart';
|
||||
@@ -13,12 +9,11 @@ export 'package:reboot_common/src/model/server_result.dart';
|
||||
export 'package:reboot_common/src/model/server_type.dart';
|
||||
export 'package:reboot_common/src/model/update_status.dart';
|
||||
export 'package:reboot_common/src/model/update_timer.dart';
|
||||
|
||||
export 'package:reboot_common/src/util/authenticator.dart';
|
||||
export 'package:reboot_common/src/util/build.dart';
|
||||
export 'package:reboot_common/src/util/dll.dart';
|
||||
export 'package:reboot_common/src/util/matchmaker.dart';
|
||||
export 'package:reboot_common/src/util/network.dart';
|
||||
export 'package:reboot_common/src/util/patcher.dart';
|
||||
export 'package:reboot_common/src/util/path.dart';
|
||||
export 'package:reboot_common/src/util/process.dart';
|
||||
export 'package:reboot_common/src/util/reboot.dart';
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
const String kDefaultAuthenticatorHost = "127.0.0.1";
|
||||
const String kDefaultAuthenticatorPort = "3551";
|
||||
const int kDefaultAuthenticatorPort = 3551;
|
||||
@@ -1,12 +1,13 @@
|
||||
const String kDefaultPlayerName = "Player";
|
||||
const String kDefaultGameServerHost = "127.0.0.1";
|
||||
const String kDefaultGameServerPort = "7777";
|
||||
const String shutdownLine = "FOnlineSubsystemGoogleCommon::Shutdown()";
|
||||
const List<String> corruptedBuildErrors = [
|
||||
const String kConsoleLine = "Region ";
|
||||
const String kShutdownLine = "FOnlineSubsystemGoogleCommon::Shutdown()";
|
||||
const List<String> kCorruptedBuildErrors = [
|
||||
"when 0 bytes remain",
|
||||
"Pak chunk signature verification failed!"
|
||||
];
|
||||
const List<String> cannotConnectErrors = [
|
||||
const List<String> kCannotConnectErrors = [
|
||||
"port 3551 failed: Connection refused",
|
||||
"Unable to login to Fortnite servers",
|
||||
"HTTP 400 response from ",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
const String kDefaultMatchmakerHost = "127.0.0.1";
|
||||
const String kDefaultMatchmakerPort = "8080";
|
||||
const int kDefaultMatchmakerPort = 8080;
|
||||
@@ -1 +0,0 @@
|
||||
const int appBarWidth = 2;
|
||||
@@ -1,2 +1,2 @@
|
||||
const String supabaseUrl = 'https://pocjparoguvaeeyjapjb.supabase.co';
|
||||
const String supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBvY2pwYXJvZ3V2YWVleWphcGpiIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTUzMTM4NTUsImV4cCI6MjAxMDg4OTg1NX0.BffJtbQvX1NVUy-9Nj4GVzUJXPK_1GyezDE0V5MRiao';
|
||||
const String supabaseUrl = 'https://drxuhdtyigthmjfhjgfl.supabase.co';
|
||||
const String supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRyeHVoZHR5aWd0aG1qZmhqZ2ZsIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODUzMDU4NjYsImV4cCI6MjAwMDg4MTg2Nn0.unuO67xf9CZgHi-3aXmC5p3RAktUfW7WwqDY-ccFN1M';
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
class ArchiveDownloadProgress {
|
||||
final double progress;
|
||||
final int? minutesLeft;
|
||||
final bool extracting;
|
||||
|
||||
ArchiveDownloadProgress(this.progress, this.minutesLeft, this.extracting);
|
||||
}
|
||||
|
||||
class ArchiveDownloadOptions {
|
||||
String archiveUrl;
|
||||
Directory destination;
|
||||
SendPort port;
|
||||
|
||||
ArchiveDownloadOptions(this.archiveUrl, this.destination, this.port);
|
||||
}
|
||||
@@ -1,6 +1,60 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
class FortniteBuild {
|
||||
final String identifier;
|
||||
final String version;
|
||||
final String link;
|
||||
final FortniteBuildSource source;
|
||||
|
||||
FortniteBuild({required this.version, required this.link});
|
||||
FortniteBuild({required this.identifier, required this.version, required this.link, required this.source});
|
||||
}
|
||||
|
||||
enum FortniteBuildSource {
|
||||
manifest,
|
||||
archive
|
||||
}
|
||||
|
||||
class FortniteBuildDownloadProgress {
|
||||
final double progress;
|
||||
final int? minutesLeft;
|
||||
final bool extracting;
|
||||
|
||||
FortniteBuildDownloadProgress(this.progress, this.minutesLeft, this.extracting);
|
||||
}
|
||||
|
||||
class FortniteBuildDownloadOptions {
|
||||
FortniteBuild build;
|
||||
Directory destination;
|
||||
SendPort port;
|
||||
|
||||
FortniteBuildDownloadOptions(this.build, this.destination, this.port);
|
||||
}
|
||||
|
||||
class FortniteBuildManifestChunk {
|
||||
List<int> chunksIds;
|
||||
String file;
|
||||
int fileSize;
|
||||
|
||||
FortniteBuildManifestChunk._internal(this.chunksIds, this.file, this.fileSize);
|
||||
|
||||
factory FortniteBuildManifestChunk.fromJson(json) => FortniteBuildManifestChunk._internal(
|
||||
List<int>.from(json["ChunksIds"] as List),
|
||||
json["File"],
|
||||
json["FileSize"]
|
||||
);
|
||||
}
|
||||
|
||||
class FortniteBuildManifestFile {
|
||||
String name;
|
||||
List<FortniteBuildManifestChunk> chunks;
|
||||
int size;
|
||||
|
||||
FortniteBuildManifestFile._internal(this.name, this.chunks, this.size);
|
||||
|
||||
factory FortniteBuildManifestFile.fromJson(json) => FortniteBuildManifestFile._internal(
|
||||
json["Name"],
|
||||
List<FortniteBuildManifestChunk>.from(json["Chunks"].map((chunk) => FortniteBuildManifestChunk.fromJson(chunk))),
|
||||
json["Size"]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,35 +8,18 @@ class GameInstance {
|
||||
final int? eacPid;
|
||||
int? observerPid;
|
||||
bool hosting;
|
||||
bool launched;
|
||||
bool tokenError;
|
||||
bool linkedHosting;
|
||||
GameInstance? child;
|
||||
|
||||
GameInstance(this.versionName, this.gamePid, this.launcherPid, this.eacPid, this.hosting, this.linkedHosting)
|
||||
: tokenError = false,
|
||||
assert(!linkedHosting || !hosting, "Only a game instance can have a linked hosting server");
|
||||
|
||||
GameInstance._fromJson(this.versionName, this.gamePid, this.launcherPid, this.eacPid, this.observerPid,
|
||||
this.hosting, this.tokenError, this.linkedHosting);
|
||||
|
||||
static GameInstance? fromJson(Map<String, dynamic>? json) {
|
||||
if(json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var gamePid = json["game"];
|
||||
if(gamePid == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var version = json["versionName"];
|
||||
var launcherPid = json["launcher"];
|
||||
var eacPid = json["eac"];
|
||||
var observerPid = json["observer"];
|
||||
var hosting = json["hosting"];
|
||||
var tokenError = json["tokenError"];
|
||||
var linkedHosting = json["linkedHosting"];
|
||||
return GameInstance._fromJson(version, gamePid, launcherPid, eacPid, observerPid, hosting, tokenError, linkedHosting);
|
||||
}
|
||||
GameInstance({
|
||||
required this.versionName,
|
||||
required this.gamePid,
|
||||
required this.launcherPid,
|
||||
required this.eacPid,
|
||||
required this.hosting,
|
||||
required this.child
|
||||
}): tokenError = false, launched = false;
|
||||
|
||||
void kill() {
|
||||
Process.killPid(gamePid, ProcessSignal.sigabrt);
|
||||
@@ -49,16 +32,6 @@ class GameInstance {
|
||||
if(observerPid != null) {
|
||||
Process.killPid(observerPid!, ProcessSignal.sigabrt);
|
||||
}
|
||||
child?.kill();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'versionName': versionName,
|
||||
'game': gamePid,
|
||||
'launcher': launcherPid,
|
||||
'eac': eacPid,
|
||||
'observer': observerPid,
|
||||
'hosting': hosting,
|
||||
'tokenError': tokenError,
|
||||
'linkedHosting': linkedHosting
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,30 +6,27 @@ import 'package:shelf_proxy/shelf_proxy.dart';
|
||||
|
||||
final authenticatorDirectory = Directory("${assetsDirectory.path}\\authenticator");
|
||||
final authenticatorStartExecutable = File("${authenticatorDirectory.path}\\lawinserver.exe");
|
||||
final authenticatorKillExecutable = File("${authenticatorDirectory.path}\\kill.bat");
|
||||
|
||||
Future<int> startEmbeddedAuthenticator(bool detached) async => startBackgroundProcess(
|
||||
executable: authenticatorStartExecutable,
|
||||
window: detached
|
||||
);
|
||||
|
||||
Future<HttpServer> startRemoteAuthenticatorProxy(Uri uri) async => await serve(proxyHandler(uri), kDefaultAuthenticatorHost, int.parse(kDefaultAuthenticatorPort));
|
||||
Future<HttpServer> startRemoteAuthenticatorProxy(Uri uri) async {
|
||||
print("CALLED: $uri");
|
||||
return await serve(proxyHandler(uri), kDefaultAuthenticatorHost, kDefaultAuthenticatorPort);
|
||||
}
|
||||
|
||||
Future<bool> isAuthenticatorPortFree() async => isPortFree(int.parse(kDefaultAuthenticatorPort));
|
||||
Future<bool> isAuthenticatorPortFree() async => await pingAuthenticator(kDefaultAuthenticatorHost, kDefaultAuthenticatorPort.toString()) == null;
|
||||
|
||||
Future<bool> freeAuthenticatorPort() async {
|
||||
await Process.run(authenticatorKillExecutable.path, []);
|
||||
var standardResult = await isAuthenticatorPortFree();
|
||||
await killProcessByPort(kDefaultAuthenticatorPort);
|
||||
final standardResult = await isAuthenticatorPortFree();
|
||||
if(standardResult) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var elevatedResult = await runElevatedProcess(authenticatorKillExecutable.path, "");
|
||||
if(!elevatedResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await isAuthenticatorPortFree();
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<Uri?> pingAuthenticator(String host, String port, [bool https=false]) async {
|
||||
@@ -48,7 +45,7 @@ Future<Uri?> pingAuthenticator(String host, String port, [bool https=false]) asy
|
||||
var response = await request.close();
|
||||
return response.statusCode == 200 || response.statusCode == 404 ? uri : null;
|
||||
}catch(_){
|
||||
return https || declaredScheme != null ? null : await pingAuthenticator(host, port, true);
|
||||
return https || declaredScheme != null || isLocalHost(host) ? null : await pingAuthenticator(host, port, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,62 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
const String kStopBuildDownloadSignal = "kill";
|
||||
|
||||
final Dio _dio = Dio();
|
||||
final String _manifestSourceUrl = "https://raw.githubusercontent.com/simplyblk/Fortnitebuilds/main/README.md";
|
||||
final String _archiveSourceUrl = "https://raw.githubusercontent.com/simplyblk/Fortnitebuilds/main/README.md";
|
||||
final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$");
|
||||
const String _manifestSourceUrl = "https://manifest.fnbuilds.services";
|
||||
const int _maxDownloadErrors = 30;
|
||||
|
||||
Future<List<FortniteBuild>> fetchBuilds(ignored) async {
|
||||
var response = await _dio.get<String>(
|
||||
_manifestSourceUrl,
|
||||
final results = await Future.wait([_fetchManifestBuilds(), _fetchArchiveBuilds()]);
|
||||
final data = <FortniteBuild>[];
|
||||
for(final result in results) {
|
||||
data.addAll(result);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<List<FortniteBuild>> _fetchManifestBuilds() async {
|
||||
try {
|
||||
final response = await _dio.get<String>("$_manifestSourceUrl/versions.json");
|
||||
final body = response.data;
|
||||
return jsonDecode(body!).map((version) {
|
||||
final nameParts = version.split("-");
|
||||
if(nameParts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final name = nameParts[1];
|
||||
return FortniteBuild(
|
||||
identifier: name,
|
||||
version: "Fortnite ${name}",
|
||||
link: "$_manifestSourceUrl/$name/$name.manifest",
|
||||
source: FortniteBuildSource.manifest
|
||||
);
|
||||
}).whereType<FortniteBuild>().toList();
|
||||
}catch(_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<FortniteBuild>> _fetchArchiveBuilds() async {
|
||||
final response = await _dio.get<String>(
|
||||
_archiveSourceUrl,
|
||||
options: Options(
|
||||
responseType: ResponseType.plain
|
||||
)
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception("Erroneous status code: ${response.statusCode}");
|
||||
return [];
|
||||
}
|
||||
|
||||
var results = <FortniteBuild>[];
|
||||
@@ -40,63 +78,154 @@ Future<List<FortniteBuild>> fetchBuilds(ignored) async {
|
||||
|
||||
var version = parts.first.trim();
|
||||
version = version.substring(0, version.indexOf("-"));
|
||||
results.add(FortniteBuild(version: "Fortnite $version", link: link));
|
||||
results.add(FortniteBuild(
|
||||
identifier: version,
|
||||
version: "Fortnite $version",
|
||||
link: link,
|
||||
source: FortniteBuildSource.archive
|
||||
));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
Future<void> downloadArchiveBuild(ArchiveDownloadOptions options) async {
|
||||
var stopped = _setupLifecycle(options);
|
||||
var outputDir = Directory("${options.destination.path}\\.build");
|
||||
outputDir.createSync(recursive: true);
|
||||
options.destination.createSync(recursive: true);
|
||||
var fileName = options.archiveUrl.substring(options.archiveUrl.lastIndexOf("/") + 1);
|
||||
var extension = path.extension(fileName);
|
||||
var tempFile = File("${outputDir.path}\\$fileName");
|
||||
if(tempFile.existsSync()) {
|
||||
tempFile.deleteSync(recursive: true);
|
||||
}
|
||||
Future<void> downloadArchiveBuild(FortniteBuildDownloadOptions options) async {
|
||||
try {
|
||||
final stopped = _setupLifecycle(options);
|
||||
switch(options.build.source) {
|
||||
case FortniteBuildSource.archive:
|
||||
final outputDir = Directory("${options.destination.path}\\.build");
|
||||
await outputDir.create(recursive: true);
|
||||
final fileName = options.build.link.substring(options.build.link.lastIndexOf("/") + 1);
|
||||
final extension = path.extension(fileName);
|
||||
final tempFile = File("${outputDir.path}\\$fileName");
|
||||
if(await tempFile.exists()) {
|
||||
await tempFile.delete(recursive: true);
|
||||
}
|
||||
|
||||
var startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
var response = _downloadFile(options, tempFile, startTime);
|
||||
await Future.any([stopped.future, response]);
|
||||
if(!stopped.isCompleted) {
|
||||
var awaitedResponse = await response;
|
||||
if (!awaitedResponse.statusCode.toString().startsWith("20")) {
|
||||
throw Exception("Erroneous status code: ${awaitedResponse.statusCode}");
|
||||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final response = _downloadArchive(options, tempFile, startTime);
|
||||
await Future.any([stopped.future, response]);
|
||||
if(!stopped.isCompleted) {
|
||||
var awaitedResponse = await response;
|
||||
if (!awaitedResponse.statusCode.toString().startsWith("20")) {
|
||||
options.port.send("Erroneous status code: ${awaitedResponse.statusCode}");
|
||||
return;
|
||||
}
|
||||
|
||||
await _extractArchive(stopped, extension, tempFile, options);
|
||||
}
|
||||
|
||||
delete(outputDir);
|
||||
break;
|
||||
case FortniteBuildSource.manifest:
|
||||
final response = await _dio.get<String>(options.build.link);
|
||||
final manifest = FortniteBuildManifestFile.fromJson(jsonDecode(response.data!));
|
||||
|
||||
final totalBytes = manifest.size;
|
||||
final outputDir = options.destination;
|
||||
await outputDir.create(recursive: true);
|
||||
|
||||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final codec = GZipCodec();
|
||||
var completedBytes = 0;
|
||||
var lastPercentage = 0.0;
|
||||
|
||||
final writers = manifest.chunks.map((chunkedFile) async {
|
||||
final outputFile = File('${outputDir.path}/${chunkedFile.file}');
|
||||
if(outputFile.existsSync()) {
|
||||
if(outputFile.lengthSync() != chunkedFile.fileSize) {
|
||||
await outputFile.delete();
|
||||
} else {
|
||||
completedBytes += chunkedFile.fileSize;
|
||||
final percentage = completedBytes * 100 / totalBytes;
|
||||
if(percentage - lastPercentage > 0.1) {
|
||||
_onProgress(
|
||||
startTime,
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
percentage,
|
||||
false,
|
||||
options
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await outputFile.parent.create(recursive: true);
|
||||
for(final chunkId in chunkedFile.chunksIds) {
|
||||
final response = await _dio.get<Uint8List>(
|
||||
"$_manifestSourceUrl/${options.build.identifier}/$chunkId.chunk",
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
headers: {
|
||||
"Accept-Encoding": "gzip"
|
||||
}
|
||||
),
|
||||
);
|
||||
var responseBody = response.data;
|
||||
if(responseBody == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final decodedBody = codec.decode(responseBody);
|
||||
await outputFile.writeAsBytes(
|
||||
decodedBody,
|
||||
mode: FileMode.append,
|
||||
flush: true
|
||||
);
|
||||
completedBytes += decodedBody.length;
|
||||
|
||||
final percentage = completedBytes * 100 / totalBytes;
|
||||
if(percentage - lastPercentage > 0.1) {
|
||||
_onProgress(
|
||||
startTime,
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
percentage,
|
||||
false,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
await Future.any([stopped.future, Future.wait(writers)]);
|
||||
options.port.send(FortniteBuildDownloadProgress(100, 0, true));
|
||||
break;
|
||||
}
|
||||
}catch(error, stackTrace) {
|
||||
options.port.send("$error\n$stackTrace");
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> _downloadArchive(FortniteBuildDownloadOptions options, File tempFile, int startTime, [int? byteStart = null, int errorsCount = 0]) async {
|
||||
var received = byteStart ?? 0;
|
||||
try {
|
||||
return await _dio.download(
|
||||
options.build.link,
|
||||
tempFile.path,
|
||||
onReceiveProgress: (data, length) {
|
||||
received = data;
|
||||
final percentage = (received / length) * 100;
|
||||
_onProgress(startTime, DateTime.now().millisecondsSinceEpoch, percentage, false, options);
|
||||
},
|
||||
deleteOnError: false,
|
||||
options: Options(
|
||||
headers: byteStart == null ? null : {
|
||||
"Range": "bytes=${byteStart}-"
|
||||
}
|
||||
)
|
||||
);
|
||||
}catch(error) {
|
||||
if(errorsCount >= _maxDownloadErrors) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await _extract(stopped, extension, tempFile, options);
|
||||
return await _downloadArchive(options, tempFile, startTime, received, errorsCount + 1);
|
||||
}
|
||||
|
||||
delete(outputDir);
|
||||
}
|
||||
|
||||
Future<Response> _downloadFile(ArchiveDownloadOptions options, File tempFile, int startTime, [int? byteStart = null]) {
|
||||
var received = byteStart ?? 0;
|
||||
return _dio.download(
|
||||
options.archiveUrl,
|
||||
tempFile.path,
|
||||
onReceiveProgress: (data, length) {
|
||||
received = data;
|
||||
var now = DateTime.now();
|
||||
var progress = (received / length) * 100;
|
||||
var msLeft = startTime + (now.millisecondsSinceEpoch - startTime) * length / received - now.millisecondsSinceEpoch;
|
||||
var minutesLeft = (msLeft / 1000 / 60).round();
|
||||
options.port.send(ArchiveDownloadProgress(progress, minutesLeft, false));
|
||||
},
|
||||
deleteOnError: false,
|
||||
options: Options(
|
||||
headers: byteStart == null ? null : {
|
||||
"Range": "bytes=${byteStart}-"
|
||||
}
|
||||
)
|
||||
).catchError((error) => _downloadFile(options, tempFile, startTime, received));
|
||||
}
|
||||
|
||||
Future<void> _extract(Completer<dynamic> stopped, String extension, File tempFile, ArchiveDownloadOptions options) async {
|
||||
Future<void> _extractArchive(Completer<dynamic> stopped, String extension, File tempFile, FortniteBuildDownloadOptions options) async {
|
||||
var startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
Process? process;
|
||||
switch (extension.toLowerCase()) {
|
||||
@@ -106,25 +235,20 @@ Future<void> _extract(Completer<dynamic> stopped, String extension, File tempFil
|
||||
["a", "-bsp1", '-o"${options.destination.path}"', tempFile.path]
|
||||
);
|
||||
process.stdout.listen((bytes) {
|
||||
var now = DateTime.now().millisecondsSinceEpoch;
|
||||
var data = utf8.decode(bytes);
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
final data = utf8.decode(bytes);
|
||||
if(data == "Everything is Ok") {
|
||||
options.port.send(ArchiveDownloadProgress(100, 0, true));
|
||||
options.port.send(FortniteBuildDownloadProgress(100, 0, true));
|
||||
return;
|
||||
}
|
||||
|
||||
var element = data.trim().split(" ")[0];
|
||||
final element = data.trim().split(" ")[0];
|
||||
if(!element.endsWith("%")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var percentage = int.parse(element.substring(0, element.length - 1));
|
||||
if(percentage == 0) {
|
||||
options.port.send(ArchiveDownloadProgress(percentage.toDouble(), null, true));
|
||||
return;
|
||||
}
|
||||
|
||||
_onProgress(startTime, now, percentage, options);
|
||||
final percentage = int.parse(element.substring(0, element.length - 1)).toDouble();
|
||||
_onProgress(startTime, now, percentage, true, options);
|
||||
});
|
||||
break;
|
||||
case ".rar":
|
||||
@@ -133,34 +257,29 @@ Future<void> _extract(Completer<dynamic> stopped, String extension, File tempFil
|
||||
["x", "-o+", tempFile.path, "*.*", options.destination.path]
|
||||
);
|
||||
process.stdout.listen((event) {
|
||||
var now = DateTime.now().millisecondsSinceEpoch;
|
||||
var data = utf8.decode(event);
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
final data = utf8.decode(event);
|
||||
data.replaceAll("\r", "")
|
||||
.replaceAll("\b", "")
|
||||
.trim()
|
||||
.split("\n")
|
||||
.forEach((entry) {
|
||||
if(entry == "All OK") {
|
||||
options.port.send(ArchiveDownloadProgress(100, 0, true));
|
||||
return;
|
||||
}
|
||||
if(entry == "All OK") {
|
||||
options.port.send(FortniteBuildDownloadProgress(100, 0, true));
|
||||
return;
|
||||
}
|
||||
|
||||
var element = _rarProgressRegex.firstMatch(entry)?.group(1);
|
||||
if(element == null) {
|
||||
return;
|
||||
}
|
||||
final element = _rarProgressRegex.firstMatch(entry)?.group(1);
|
||||
if(element == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var percentage = int.parse(element);
|
||||
if(percentage == 0) {
|
||||
options.port.send(ArchiveDownloadProgress(percentage.toDouble(), null, true));
|
||||
return;
|
||||
}
|
||||
|
||||
_onProgress(startTime, now, percentage, options);
|
||||
});
|
||||
final percentage = int.parse(element).toDouble();
|
||||
_onProgress(startTime, now, percentage, true, options);
|
||||
});
|
||||
});
|
||||
process.stderr.listen((event) {
|
||||
var data = utf8.decode(event);
|
||||
final data = utf8.decode(event);
|
||||
options.port.send(data);
|
||||
});
|
||||
break;
|
||||
@@ -171,17 +290,22 @@ Future<void> _extract(Completer<dynamic> stopped, String extension, File tempFil
|
||||
await Future.any([stopped.future, process.exitCode]);
|
||||
}
|
||||
|
||||
void _onProgress(int startTime, int now, int percentage, ArchiveDownloadOptions options) {
|
||||
var msLeft = startTime + (now - startTime) * 100 / percentage - now;
|
||||
var minutesLeft = (msLeft / 1000 / 60).round();
|
||||
options.port.send(ArchiveDownloadProgress(percentage.toDouble(), minutesLeft, true));
|
||||
void _onProgress(int startTime, int now, double percentage, bool extracting, FortniteBuildDownloadOptions options) {
|
||||
if(percentage == 0) {
|
||||
options.port.send(FortniteBuildDownloadProgress(percentage, null, extracting));
|
||||
return;
|
||||
}
|
||||
|
||||
final msLeft = startTime + (now - startTime) * 100 / percentage - now;
|
||||
final minutesLeft = (msLeft / 1000 / 60).round();
|
||||
options.port.send(FortniteBuildDownloadProgress(percentage, minutesLeft, extracting));
|
||||
}
|
||||
|
||||
Completer<dynamic> _setupLifecycle(ArchiveDownloadOptions options) {
|
||||
Completer<dynamic> _setupLifecycle(FortniteBuildDownloadOptions options) {
|
||||
var stopped = Completer();
|
||||
var lifecyclePort = ReceivePort();
|
||||
lifecyclePort.listen((message) {
|
||||
if(message == "kill") {
|
||||
if(message == kStopBuildDownloadSignal && !stopped.isCompleted) {
|
||||
stopped.complete();
|
||||
}
|
||||
});
|
||||
|
||||
64
common/lib/src/util/dll.dart
Normal file
64
common/lib/src/util/dll.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
bool _watcher = false;
|
||||
final File rebootDllFile = File("${assetsDirectory.path}\\dlls\\reboot.dll");
|
||||
const String kRebootDownloadUrl =
|
||||
"https://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/master/Release.zip";
|
||||
|
||||
Future<bool> hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force = false}) async {
|
||||
final lastUpdate = await _getLastUpdate(lastUpdateMs);
|
||||
final exists = await rebootDllFile.exists();
|
||||
final now = DateTime.now();
|
||||
return force || !exists || (hours > 0 && lastUpdate != null && now.difference(lastUpdate).inHours > hours);
|
||||
}
|
||||
|
||||
Future<void> downloadCriticalDll(String name, String outputPath) async {
|
||||
final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/tree/master/gui/assets/dlls/$name"));
|
||||
final output = File(outputPath);
|
||||
await output.parent.create(recursive: true);
|
||||
await output.writeAsBytes(response.bodyBytes);
|
||||
}
|
||||
|
||||
Future<int> downloadRebootDll(String url) async {
|
||||
Directory? outputDir;
|
||||
final now = DateTime.now();
|
||||
try {
|
||||
final response = await http.get(Uri.parse(url));
|
||||
outputDir = await installationDirectory.createTemp("reboot_out");
|
||||
final tempZip = File("${outputDir.path}\\reboot.zip");
|
||||
await tempZip.writeAsBytes(response.bodyBytes);
|
||||
await extractFileToDisk(tempZip.path, outputDir.path);
|
||||
final rebootDll = File(outputDir.listSync().firstWhere((element) => path.extension(element.path) == ".dll").path);
|
||||
await rebootDllFile.writeAsBytes(await rebootDll.readAsBytes());
|
||||
return now.millisecondsSinceEpoch;
|
||||
} finally{
|
||||
if(outputDir != null) {
|
||||
delete(outputDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<DateTime?> _getLastUpdate(int? lastUpdateMs) async {
|
||||
return lastUpdateMs != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(lastUpdateMs)
|
||||
: null;
|
||||
}
|
||||
|
||||
Stream<String> watchDlls() async* {
|
||||
if(_watcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
_watcher = true;
|
||||
await for(final event in rebootDllFile.parent.watch(events: FileSystemEvent.delete | FileSystemEvent.move)) {
|
||||
print(event);
|
||||
if (event.path.endsWith(".dll")) {
|
||||
yield event.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ini/ini.dart';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:sync/semaphore.dart';
|
||||
|
||||
@@ -75,21 +74,16 @@ Future<void> writeMatchmakingIp(String text) async {
|
||||
await matchmakerConfigFile.writeAsString(config.toString(), flush: true);
|
||||
}
|
||||
|
||||
Future<bool> isMatchmakerPortFree() async => isPortFree(int.parse(kDefaultMatchmakerPort));
|
||||
Future<bool> isMatchmakerPortFree() async => await pingMatchmaker(kDefaultMatchmakerHost, kDefaultMatchmakerPort.toString()) == null;
|
||||
|
||||
Future<bool> freeMatchmakerPort() async {
|
||||
await Process.run(matchmakerKillExecutable.path, []);
|
||||
var standardResult = await isMatchmakerPortFree();
|
||||
await killProcessByPort(kDefaultMatchmakerPort);
|
||||
final standardResult = await isMatchmakerPortFree();
|
||||
if(standardResult) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var elevatedResult = await runElevatedProcess(matchmakerKillExecutable.path, "");
|
||||
if(!elevatedResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await isMatchmakerPortFree();
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<Uri?> pingMatchmaker(String host, String port, [bool wss=false]) async {
|
||||
@@ -124,7 +118,7 @@ Future<Uri?> pingMatchmaker(String host, String port, [bool wss=false]) async {
|
||||
await socket.close();
|
||||
return result ? uri : null;
|
||||
}catch(_){
|
||||
return wss || declaredScheme != null ? null : await pingMatchmaker(host, port, true);
|
||||
return wss || declaredScheme != null || isLocalHost(host) ? null : await pingMatchmaker(host, port, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,95 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:win32/win32.dart';
|
||||
|
||||
const _AF_INET = 2;
|
||||
const _TCP_TABLE_OWNER_PID_LISTENER = 3;
|
||||
|
||||
final _getExtendedTcpTable = DynamicLibrary.open('iphlpapi.dll').lookupFunction<
|
||||
Int32 Function(Pointer, Pointer<Uint32>, Int32, Int32, Int32, Int32),
|
||||
int Function(Pointer, Pointer<Uint32>, int, int, int, int)>('GetExtendedTcpTable');
|
||||
|
||||
class _MIB_TCPROW_OWNER_PID extends Struct {
|
||||
@Uint32()
|
||||
external int dwState;
|
||||
|
||||
@Uint32()
|
||||
external int dwLocalAddr;
|
||||
|
||||
@Uint32()
|
||||
external int dwLocalPort;
|
||||
|
||||
@Uint32()
|
||||
external int dwRemoteAddr;
|
||||
|
||||
@Uint32()
|
||||
external int dwRemotePort;
|
||||
|
||||
@Uint32()
|
||||
external int dwOwningPid;
|
||||
}
|
||||
|
||||
class _MIB_TCPTABLE_OWNER_PID extends Struct {
|
||||
@Uint32()
|
||||
external int dwNumEntries;
|
||||
|
||||
@Array(1)
|
||||
external Array<_MIB_TCPROW_OWNER_PID> table;
|
||||
}
|
||||
|
||||
bool isLocalHost(String host) => host.trim() == "127.0.0.1"
|
||||
|| host.trim().toLowerCase() == "localhost"
|
||||
|| host.trim() == "0.0.0.0";
|
||||
|
||||
Future<bool> isPortFree(int port) async {
|
||||
try {
|
||||
final server = await ServerSocket.bind(InternetAddress.anyIPv4, port);
|
||||
await server.close();
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
bool killProcessByPort(int port) {
|
||||
var pTcpTable = calloc<_MIB_TCPTABLE_OWNER_PID>();
|
||||
final dwSize = calloc<DWORD>();
|
||||
dwSize.value = 0;
|
||||
|
||||
int result = _getExtendedTcpTable(
|
||||
nullptr,
|
||||
dwSize,
|
||||
FALSE,
|
||||
_AF_INET,
|
||||
_TCP_TABLE_OWNER_PID_LISTENER,
|
||||
0
|
||||
);
|
||||
if (result == ERROR_INSUFFICIENT_BUFFER) {
|
||||
pTcpTable = calloc<_MIB_TCPTABLE_OWNER_PID>(dwSize.value);
|
||||
result = _getExtendedTcpTable(
|
||||
pTcpTable,
|
||||
dwSize,
|
||||
FALSE,
|
||||
_AF_INET,
|
||||
_TCP_TABLE_OWNER_PID_LISTENER,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
if (result == NO_ERROR) {
|
||||
final table = pTcpTable.ref;
|
||||
for (int i = 0; i < table.dwNumEntries; i++) {
|
||||
final row = table.table[i];
|
||||
final localPort = _htons(row.dwLocalPort);
|
||||
if (localPort == port) {
|
||||
final pid = row.dwOwningPid;
|
||||
calloc.free(pTcpTable);
|
||||
calloc.free(dwSize);
|
||||
final hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||
if (hProcess != NULL) {
|
||||
final result = TerminateProcess(hProcess, 0);
|
||||
CloseHandle(hProcess);
|
||||
return result != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
calloc.free(pTcpTable);
|
||||
calloc.free(dwSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> resetWinNat() async {
|
||||
var binary = File("${assetsDirectory.path}\\misc\\winnat.bat");
|
||||
await runElevatedProcess(binary.path, "");
|
||||
}
|
||||
int _htons(int port) => ((port & 0xFF) << 8) | ((port >> 8) & 0xFF);
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
Directory get installationDirectory =>
|
||||
File(Platform.resolvedExecutable).parent;
|
||||
|
||||
@@ -4,10 +4,13 @@ import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:win32/win32.dart';
|
||||
|
||||
import '../constant/game.dart';
|
||||
|
||||
final _ntdll = DynamicLibrary.open('ntdll.dll');
|
||||
final _kernel32 = DynamicLibrary.open('kernel32.dll');
|
||||
final _CreateRemoteThread = _kernel32.lookupFunction<
|
||||
@@ -84,23 +87,23 @@ Future<void> injectDll(int pid, String dll) async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> runElevatedProcess(String executable, String args) async {
|
||||
var shellInput = calloc<SHELLEXECUTEINFO>();
|
||||
bool runElevatedProcess(String executable, String args) {
|
||||
final shellInput = calloc<SHELLEXECUTEINFO>();
|
||||
shellInput.ref.lpFile = executable.toNativeUtf16();
|
||||
shellInput.ref.lpParameters = args.toNativeUtf16();
|
||||
shellInput.ref.nShow = SW_HIDE;
|
||||
shellInput.ref.fMask = ES_AWAYMODE_REQUIRED;
|
||||
shellInput.ref.lpVerb = "runas".toNativeUtf16();
|
||||
shellInput.ref.cbSize = sizeOf<SHELLEXECUTEINFO>();
|
||||
var shellResult = ShellExecuteEx(shellInput);
|
||||
return shellResult == 1;
|
||||
final result = ShellExecuteEx(shellInput) == 1;
|
||||
free(shellInput);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void _startBackgroundProcess(_BackgroundProcessParameters params) {
|
||||
var args = params.args;
|
||||
var concatenatedArgs = args == null ? "" : " ${args.map((entry) => '"$entry"').join(" ")}";
|
||||
var executablePath = TEXT("${params.executable.path}$concatenatedArgs");
|
||||
var executablePath = TEXT('cmd.exe /k "${params.executable.path}"$concatenatedArgs');
|
||||
var startupInfo = calloc<STARTUPINFO>();
|
||||
var processInfo = calloc<PROCESS_INFORMATION>();
|
||||
var windowFlag = params.window ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW;
|
||||
@@ -203,4 +206,57 @@ Future<bool> watchProcess(int pid) async {
|
||||
var result = await completer.future;
|
||||
isolate.kill(priority: Isolate.immediate);
|
||||
return result;
|
||||
}
|
||||
|
||||
List<String> createRebootArgs(String username, String password, bool host, bool headless, String additionalArgs) {
|
||||
if(password.isEmpty) {
|
||||
username = '${_parseUsername(username, host)}@projectreboot.dev';
|
||||
}
|
||||
|
||||
password = password.isNotEmpty ? password : "Rebooted";
|
||||
var args = [
|
||||
"-epicapp=Fortnite",
|
||||
"-epicenv=Prod",
|
||||
"-epiclocale=en-us",
|
||||
"-epicportal",
|
||||
"-skippatchcheck",
|
||||
"-nobe",
|
||||
"-fromfl=eac",
|
||||
"-fltoken=3db3ba5dcbd2e16703f3978d",
|
||||
"-caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
|
||||
"-AUTH_LOGIN=$username",
|
||||
"-AUTH_PASSWORD=${password.isNotEmpty ? password : "Rebooted"}",
|
||||
"-AUTH_TYPE=epic"
|
||||
];
|
||||
|
||||
if(host && headless){
|
||||
args.addAll([
|
||||
"-nullrhi",
|
||||
"-nosplash",
|
||||
"-nosound",
|
||||
]);
|
||||
}
|
||||
|
||||
if(additionalArgs.isNotEmpty){
|
||||
args.addAll(additionalArgs.split(" "));
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
String _parseUsername(String username, bool host) {
|
||||
if(host) {
|
||||
return "Player${Random().nextInt(1000)}";
|
||||
}
|
||||
|
||||
if (username.isEmpty) {
|
||||
return kDefaultPlayerName;
|
||||
}
|
||||
|
||||
username = username.replaceAll(RegExp("[^A-Za-z0-9]"), "").trim();
|
||||
if(username.isEmpty){
|
||||
return kDefaultPlayerName;
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
|
||||
const String rebootDownloadUrl =
|
||||
"https://nightly.link/Milxnor/Project-Reboot-3.0/workflows/msbuild/main/Release.zip";
|
||||
final File rebootDllFile = File("${assetsDirectory.path}\\dlls\\reboot.dll");
|
||||
|
||||
List<String> createRebootArgs(String username, String password, bool host, String additionalArgs) {
|
||||
if(password.isEmpty) {
|
||||
username = '${_parseUsername(username, host)}@projectreboot.dev';
|
||||
}
|
||||
|
||||
password = password.isNotEmpty ? password : "Rebooted";
|
||||
var args = [
|
||||
"-epicapp=Fortnite",
|
||||
"-epicenv=Prod",
|
||||
"-epiclocale=en-us",
|
||||
"-epicportal",
|
||||
"-skippatchcheck",
|
||||
"-nobe",
|
||||
"-fromfl=eac",
|
||||
"-fltoken=3db3ba5dcbd2e16703f3978d",
|
||||
"-caldera=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X2lkIjoiYmU5ZGE1YzJmYmVhNDQwN2IyZjQwZWJhYWQ4NTlhZDQiLCJnZW5lcmF0ZWQiOjE2Mzg3MTcyNzgsImNhbGRlcmFHdWlkIjoiMzgxMGI4NjMtMmE2NS00NDU3LTliNTgtNGRhYjNiNDgyYTg2IiwiYWNQcm92aWRlciI6IkVhc3lBbnRpQ2hlYXQiLCJub3RlcyI6IiIsImZhbGxiYWNrIjpmYWxzZX0.VAWQB67RTxhiWOxx7DBjnzDnXyyEnX7OljJm-j2d88G_WgwQ9wrE6lwMEHZHjBd1ISJdUO1UVUqkfLdU5nofBQ",
|
||||
"-AUTH_LOGIN=$username",
|
||||
"-AUTH_PASSWORD=${password.isNotEmpty ? password : "Rebooted"}",
|
||||
"-AUTH_TYPE=epic"
|
||||
];
|
||||
|
||||
if(host){
|
||||
args.addAll([
|
||||
"-nullrhi",
|
||||
"-nosplash",
|
||||
"-nosound",
|
||||
]);
|
||||
}
|
||||
|
||||
if(additionalArgs.isNotEmpty){
|
||||
args.addAll(additionalArgs.split(" "));
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
String _parseUsername(String username, bool host) {
|
||||
if(host) {
|
||||
return "Player${Random().nextInt(1000)}";
|
||||
}
|
||||
|
||||
if (username.isEmpty) {
|
||||
return kDefaultPlayerName;
|
||||
}
|
||||
|
||||
username = username.replaceAll(RegExp("[^A-Za-z0-9]"), "").trim();
|
||||
if(username.isEmpty){
|
||||
return kDefaultPlayerName;
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
|
||||
Future<int> downloadRebootDll(String url, int? lastUpdateMs, {int hours = 24, bool force = false}) async {
|
||||
Directory? outputDir;
|
||||
var now = DateTime.now();
|
||||
try {
|
||||
var lastUpdate = await _getLastUpdate(lastUpdateMs);
|
||||
var exists = await rebootDllFile.exists();
|
||||
if (!force && lastUpdate != null && now.difference(lastUpdate).inHours <= hours && exists) {
|
||||
return lastUpdateMs!;
|
||||
}
|
||||
|
||||
var response = await http.get(Uri.parse(rebootDownloadUrl));
|
||||
outputDir = await installationDirectory.createTemp("reboot_out");
|
||||
var tempZip = File("${outputDir.path}\\reboot.zip");
|
||||
await tempZip.writeAsBytes(response.bodyBytes);
|
||||
await extractFileToDisk(tempZip.path, outputDir.path);
|
||||
var rebootDll = File(outputDir.listSync().firstWhere((element) => path.extension(element.path) == ".dll").path);
|
||||
if (!exists || sha1.convert(await rebootDllFile.readAsBytes()) != sha1.convert(await rebootDll.readAsBytes())) {
|
||||
await rebootDllFile.writeAsBytes(await rebootDll.readAsBytes());
|
||||
}
|
||||
|
||||
return now.millisecondsSinceEpoch;
|
||||
} finally{
|
||||
if(outputDir != null) {
|
||||
delete(outputDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<DateTime?> _getLastUpdate(int? lastUpdateMs) async {
|
||||
return lastUpdateMs != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(lastUpdateMs)
|
||||
: null;
|
||||
}
|
||||
Reference in New Issue
Block a user