diff --git a/lawin/CloudStorage/DefaultEngine.ini b/backend/CloudStorage/DefaultEngine.ini similarity index 100% rename from lawin/CloudStorage/DefaultEngine.ini rename to backend/CloudStorage/DefaultEngine.ini diff --git a/lawin/CloudStorage/DefaultGame.ini b/backend/CloudStorage/DefaultGame.ini similarity index 100% rename from lawin/CloudStorage/DefaultGame.ini rename to backend/CloudStorage/DefaultGame.ini diff --git a/lawin/CloudStorage/DefaultInput.ini b/backend/CloudStorage/DefaultInput.ini similarity index 100% rename from lawin/CloudStorage/DefaultInput.ini rename to backend/CloudStorage/DefaultInput.ini diff --git a/lawin/CloudStorage/DefaultRuntimeOptions.ini b/backend/CloudStorage/DefaultRuntimeOptions.ini similarity index 100% rename from lawin/CloudStorage/DefaultRuntimeOptions.ini rename to backend/CloudStorage/DefaultRuntimeOptions.ini diff --git a/lawin/Config/catalog_config.json b/backend/Config/catalog_config.json similarity index 100% rename from lawin/Config/catalog_config.json rename to backend/Config/catalog_config.json diff --git a/lawin/Config/config.ini b/backend/Config/config.ini similarity index 100% rename from lawin/Config/config.ini rename to backend/Config/config.ini diff --git a/lawin/LICENSE b/backend/LICENSE similarity index 100% rename from lawin/LICENSE rename to backend/LICENSE diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..e8805f8 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,4 @@ +# Backend +Fork of LawinV1 +Awaiting rewrite in Dart +Use build.bat to generate the executable \ No newline at end of file diff --git a/lawin/build.bat b/backend/build.bat similarity index 100% rename from lawin/build.bat rename to backend/build.bat diff --git a/lawin/index.js b/backend/index.js similarity index 100% rename from lawin/index.js rename to backend/index.js diff --git a/backend/install_packages.bat b/backend/install_packages.bat new file mode 100644 index 0000000..e69de29 diff --git a/lawin/package-lock.json b/backend/package-lock.json similarity index 100% rename from lawin/package-lock.json rename to backend/package-lock.json diff --git a/lawin/package.json b/backend/package.json similarity index 100% rename from lawin/package.json rename to backend/package.json diff --git a/lawin/profiles/athena.json b/backend/profiles/athena.json similarity index 100% rename from lawin/profiles/athena.json rename to backend/profiles/athena.json diff --git a/lawin/profiles/campaign.json b/backend/profiles/campaign.json similarity index 100% rename from lawin/profiles/campaign.json rename to backend/profiles/campaign.json diff --git a/lawin/profiles/collection_book_people0.json b/backend/profiles/collection_book_people0.json similarity index 100% rename from lawin/profiles/collection_book_people0.json rename to backend/profiles/collection_book_people0.json diff --git a/lawin/profiles/collection_book_schematics0.json b/backend/profiles/collection_book_schematics0.json similarity index 100% rename from lawin/profiles/collection_book_schematics0.json rename to backend/profiles/collection_book_schematics0.json diff --git a/lawin/profiles/collections.json b/backend/profiles/collections.json similarity index 100% rename from lawin/profiles/collections.json rename to backend/profiles/collections.json diff --git a/lawin/profiles/common_core.json b/backend/profiles/common_core.json similarity index 100% rename from lawin/profiles/common_core.json rename to backend/profiles/common_core.json diff --git a/lawin/profiles/common_public.json b/backend/profiles/common_public.json similarity index 100% rename from lawin/profiles/common_public.json rename to backend/profiles/common_public.json diff --git a/lawin/profiles/creative.json b/backend/profiles/creative.json similarity index 100% rename from lawin/profiles/creative.json rename to backend/profiles/creative.json diff --git a/lawin/profiles/metadata.json b/backend/profiles/metadata.json similarity index 100% rename from lawin/profiles/metadata.json rename to backend/profiles/metadata.json diff --git a/lawin/profiles/outpost0.json b/backend/profiles/outpost0.json similarity index 100% rename from lawin/profiles/outpost0.json rename to backend/profiles/outpost0.json diff --git a/lawin/profiles/profile0.json b/backend/profiles/profile0.json similarity index 100% rename from lawin/profiles/profile0.json rename to backend/profiles/profile0.json diff --git a/lawin/profiles/theater0.json b/backend/profiles/theater0.json similarity index 100% rename from lawin/profiles/theater0.json rename to backend/profiles/theater0.json diff --git a/backend/public/images/discord-s.png b/backend/public/images/discord-s.png new file mode 100644 index 0000000..e69de29 diff --git a/backend/public/images/discord.png b/backend/public/images/discord.png new file mode 100644 index 0000000..e69de29 diff --git a/backend/public/images/lawin-s.png b/backend/public/images/lawin-s.png new file mode 100644 index 0000000..e69de29 diff --git a/backend/public/images/lawin.jpg b/backend/public/images/lawin.jpg new file mode 100644 index 0000000..e69de29 diff --git a/backend/public/images/motd-s.png b/backend/public/images/motd-s.png new file mode 100644 index 0000000..e69de29 diff --git a/backend/public/images/motd.png b/backend/public/images/motd.png new file mode 100644 index 0000000..e69de29 diff --git a/backend/public/images/seasonx.png b/backend/public/images/seasonx.png new file mode 100644 index 0000000..e69de29 diff --git a/lawin/responses/Athena/BattlePass/Season10.json b/backend/responses/Athena/BattlePass/Season10.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season10.json rename to backend/responses/Athena/BattlePass/Season10.json diff --git a/lawin/responses/Athena/BattlePass/Season2.json b/backend/responses/Athena/BattlePass/Season2.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season2.json rename to backend/responses/Athena/BattlePass/Season2.json diff --git a/lawin/responses/Athena/BattlePass/Season3.json b/backend/responses/Athena/BattlePass/Season3.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season3.json rename to backend/responses/Athena/BattlePass/Season3.json diff --git a/lawin/responses/Athena/BattlePass/Season4.json b/backend/responses/Athena/BattlePass/Season4.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season4.json rename to backend/responses/Athena/BattlePass/Season4.json diff --git a/lawin/responses/Athena/BattlePass/Season5.json b/backend/responses/Athena/BattlePass/Season5.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season5.json rename to backend/responses/Athena/BattlePass/Season5.json diff --git a/lawin/responses/Athena/BattlePass/Season6.json b/backend/responses/Athena/BattlePass/Season6.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season6.json rename to backend/responses/Athena/BattlePass/Season6.json diff --git a/lawin/responses/Athena/BattlePass/Season7.json b/backend/responses/Athena/BattlePass/Season7.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season7.json rename to backend/responses/Athena/BattlePass/Season7.json diff --git a/lawin/responses/Athena/BattlePass/Season8.json b/backend/responses/Athena/BattlePass/Season8.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season8.json rename to backend/responses/Athena/BattlePass/Season8.json diff --git a/lawin/responses/Athena/BattlePass/Season9.json b/backend/responses/Athena/BattlePass/Season9.json similarity index 100% rename from lawin/responses/Athena/BattlePass/Season9.json rename to backend/responses/Athena/BattlePass/Season9.json diff --git a/lawin/responses/Athena/Discovery/discovery_api_assets.json b/backend/responses/Athena/Discovery/discovery_api_assets.json similarity index 100% rename from lawin/responses/Athena/Discovery/discovery_api_assets.json rename to backend/responses/Athena/Discovery/discovery_api_assets.json diff --git a/lawin/responses/Athena/Discovery/discovery_frontend.json b/backend/responses/Athena/Discovery/discovery_frontend.json similarity index 100% rename from lawin/responses/Athena/Discovery/discovery_frontend.json rename to backend/responses/Athena/Discovery/discovery_frontend.json diff --git a/lawin/responses/Athena/SeasonData.json b/backend/responses/Athena/SeasonData.json similarity index 100% rename from lawin/responses/Athena/SeasonData.json rename to backend/responses/Athena/SeasonData.json diff --git a/lawin/responses/Athena/motdTarget.json b/backend/responses/Athena/motdTarget.json similarity index 100% rename from lawin/responses/Athena/motdTarget.json rename to backend/responses/Athena/motdTarget.json diff --git a/lawin/responses/Athena/winterfestRewards.json b/backend/responses/Athena/winterfestRewards.json similarity index 100% rename from lawin/responses/Athena/winterfestRewards.json rename to backend/responses/Athena/winterfestRewards.json diff --git a/lawin/responses/Campaign/cardPackData.json b/backend/responses/Campaign/cardPackData.json similarity index 100% rename from lawin/responses/Campaign/cardPackData.json rename to backend/responses/Campaign/cardPackData.json diff --git a/lawin/responses/Campaign/dailyRewards.json b/backend/responses/Campaign/dailyRewards.json similarity index 100% rename from lawin/responses/Campaign/dailyRewards.json rename to backend/responses/Campaign/dailyRewards.json diff --git a/lawin/responses/Campaign/expeditionData.json b/backend/responses/Campaign/expeditionData.json similarity index 100% rename from lawin/responses/Campaign/expeditionData.json rename to backend/responses/Campaign/expeditionData.json diff --git a/lawin/responses/Campaign/rewards.json b/backend/responses/Campaign/rewards.json similarity index 100% rename from lawin/responses/Campaign/rewards.json rename to backend/responses/Campaign/rewards.json diff --git a/lawin/responses/Campaign/survivorData.json b/backend/responses/Campaign/survivorData.json similarity index 100% rename from lawin/responses/Campaign/survivorData.json rename to backend/responses/Campaign/survivorData.json diff --git a/lawin/responses/Campaign/transformItemIDS.json b/backend/responses/Campaign/transformItemIDS.json similarity index 100% rename from lawin/responses/Campaign/transformItemIDS.json rename to backend/responses/Campaign/transformItemIDS.json diff --git a/lawin/responses/Campaign/worldstw.json b/backend/responses/Campaign/worldstw.json similarity index 100% rename from lawin/responses/Campaign/worldstw.json rename to backend/responses/Campaign/worldstw.json diff --git a/lawin/responses/CloudDir/Full.ini b/backend/responses/CloudDir/Full.ini similarity index 100% rename from lawin/responses/CloudDir/Full.ini rename to backend/responses/CloudDir/Full.ini diff --git a/backend/responses/CloudDir/LawinServer.chunk b/backend/responses/CloudDir/LawinServer.chunk new file mode 100644 index 0000000..e69de29 diff --git a/backend/responses/CloudDir/LawinServer.manifest b/backend/responses/CloudDir/LawinServer.manifest new file mode 100644 index 0000000..e69de29 diff --git a/lawin/responses/SAC.json b/backend/responses/SAC.json similarity index 100% rename from lawin/responses/SAC.json rename to backend/responses/SAC.json diff --git a/lawin/responses/catalog.json b/backend/responses/catalog.json similarity index 100% rename from lawin/responses/catalog.json rename to backend/responses/catalog.json diff --git a/lawin/responses/contentpages.json b/backend/responses/contentpages.json similarity index 100% rename from lawin/responses/contentpages.json rename to backend/responses/contentpages.json diff --git a/lawin/responses/friendslist.json b/backend/responses/friendslist.json similarity index 100% rename from lawin/responses/friendslist.json rename to backend/responses/friendslist.json diff --git a/lawin/responses/friendslist2.json b/backend/responses/friendslist2.json similarity index 100% rename from lawin/responses/friendslist2.json rename to backend/responses/friendslist2.json diff --git a/lawin/responses/keychain.json b/backend/responses/keychain.json similarity index 100% rename from lawin/responses/keychain.json rename to backend/responses/keychain.json diff --git a/lawin/responses/privacy.json b/backend/responses/privacy.json similarity index 100% rename from lawin/responses/privacy.json rename to backend/responses/privacy.json diff --git a/lawin/responses/quests.json b/backend/responses/quests.json similarity index 100% rename from lawin/responses/quests.json rename to backend/responses/quests.json diff --git a/lawin/responses/sdkv1.json b/backend/responses/sdkv1.json similarity index 100% rename from lawin/responses/sdkv1.json rename to backend/responses/sdkv1.json diff --git a/lawin/start.bat b/backend/start.bat similarity index 100% rename from lawin/start.bat rename to backend/start.bat diff --git a/lawin/structure/affiliate.js b/backend/structure/affiliate.js similarity index 100% rename from lawin/structure/affiliate.js rename to backend/structure/affiliate.js diff --git a/lawin/structure/cloudstorage.js b/backend/structure/cloudstorage.js similarity index 100% rename from lawin/structure/cloudstorage.js rename to backend/structure/cloudstorage.js diff --git a/lawin/structure/contentpages.js b/backend/structure/contentpages.js similarity index 100% rename from lawin/structure/contentpages.js rename to backend/structure/contentpages.js diff --git a/lawin/structure/discovery.js b/backend/structure/discovery.js similarity index 100% rename from lawin/structure/discovery.js rename to backend/structure/discovery.js diff --git a/lawin/structure/friends.js b/backend/structure/friends.js similarity index 100% rename from lawin/structure/friends.js rename to backend/structure/friends.js diff --git a/lawin/structure/functions.js b/backend/structure/functions.js similarity index 100% rename from lawin/structure/functions.js rename to backend/structure/functions.js diff --git a/lawin/structure/lightswitch.js b/backend/structure/lightswitch.js similarity index 100% rename from lawin/structure/lightswitch.js rename to backend/structure/lightswitch.js diff --git a/lawin/structure/main.js b/backend/structure/main.js similarity index 100% rename from lawin/structure/main.js rename to backend/structure/main.js diff --git a/lawin/structure/matchmaker.js b/backend/structure/matchmaker.js similarity index 100% rename from lawin/structure/matchmaker.js rename to backend/structure/matchmaker.js diff --git a/lawin/structure/matchmaking.js b/backend/structure/matchmaking.js similarity index 100% rename from lawin/structure/matchmaking.js rename to backend/structure/matchmaking.js diff --git a/lawin/structure/mcp.js b/backend/structure/mcp.js similarity index 100% rename from lawin/structure/mcp.js rename to backend/structure/mcp.js diff --git a/lawin/structure/party.js b/backend/structure/party.js similarity index 100% rename from lawin/structure/party.js rename to backend/structure/party.js diff --git a/lawin/structure/privacy.js b/backend/structure/privacy.js similarity index 100% rename from lawin/structure/privacy.js rename to backend/structure/privacy.js diff --git a/lawin/structure/storefront.js b/backend/structure/storefront.js similarity index 100% rename from lawin/structure/storefront.js rename to backend/structure/storefront.js diff --git a/lawin/structure/timeline.js b/backend/structure/timeline.js similarity index 100% rename from lawin/structure/timeline.js rename to backend/structure/timeline.js diff --git a/lawin/structure/user.js b/backend/structure/user.js similarity index 100% rename from lawin/structure/user.js rename to backend/structure/user.js diff --git a/lawin/structure/version.js b/backend/structure/version.js similarity index 100% rename from lawin/structure/version.js rename to backend/structure/version.js diff --git a/lawin/structure/xmpp.js b/backend/structure/xmpp.js similarity index 100% rename from lawin/structure/xmpp.js rename to backend/structure/xmpp.js diff --git a/cli/lib/cli.dart b/cli/lib/cli.dart index 2323653..511d4ee 100644 --- a/cli/lib/cli.dart +++ b/cli/lib/cli.dart @@ -51,7 +51,7 @@ void main(List args) async { } stdout.writeln("Launching game..."); - var executable = version.gameExecutable; + var executable = version.shippingExecutable; if(executable == null){ throw Exception("Missing game executable at: ${version.location.path}"); } diff --git a/cli/lib/src/game.dart b/cli/lib/src/game.dart index b562097..10ecbdb 100644 --- a/cli/lib/src/game.dart +++ b/cli/lib/src/game.dart @@ -12,7 +12,7 @@ Future startGame() async { await _startLauncherProcess(version); await _startEacProcess(version); - var executable = await version.gameExecutable; + var executable = await version.shippingExecutable; if (executable == null) { throw Exception("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?"); } diff --git a/common/lib/src/extension/path.dart b/common/lib/src/extension/path.dart index 0d5d792..2f847c3 100644 --- a/common/lib/src/extension/path.dart +++ b/common/lib/src/extension/path.dart @@ -5,6 +5,8 @@ import 'package:path/path.dart' as path; import 'package:reboot_common/common.dart'; extension FortniteVersionExtension on FortniteVersion { + static DateTime _marker = DateTime.fromMicrosecondsSinceEpoch(0); + static File? findExecutable(Directory directory, String name) { try{ final result = directory.listSync(recursive: true) @@ -15,23 +17,20 @@ extension FortniteVersionExtension on FortniteVersion { } } - File? get gameExecutable => findExecutable(location, "FortniteClient-Win64-Shipping.exe"); - - Future get headlessGameExecutable async { - final result = findExecutable(location, "FortniteClient-Win64-Shipping-Headless.exe"); - if(result != null) { - return result; - } - - final original = findExecutable(location, "FortniteClient-Win64-Shipping.exe"); - if(original == null) { + Future get shippingExecutable async { + final result = findExecutable(location, "FortniteClient-Win64-Shipping.exe"); + if(result == null) { return null; } - final output = File("${original.parent.path}\\FortniteClient-Win64-Shipping-Headless.exe"); - await original.copy(output.path); - await Isolate.run(() => patchHeadless(output)); - return output; + final lastModified = await result.lastModified(); + if(lastModified != _marker) { + print("Applying patch"); + await Isolate.run(() => patchHeadless(result)); + await result.setLastModified(_marker); + } + + return result; } File? get launcherExecutable => findExecutable(location, "FortniteLauncher.exe"); diff --git a/common/lib/src/model/fortnite_version.dart b/common/lib/src/model/fortnite_version.dart index b8d1971..92586fc 100644 --- a/common/lib/src/model/fortnite_version.dart +++ b/common/lib/src/model/fortnite_version.dart @@ -14,4 +14,4 @@ class FortniteVersion { 'name': name, 'location': location.path }; -} +} \ No newline at end of file diff --git a/common/lib/src/model/game_instance.dart b/common/lib/src/model/game_instance.dart index a4773ed..0853a4e 100644 --- a/common/lib/src/model/game_instance.dart +++ b/common/lib/src/model/game_instance.dart @@ -9,7 +9,7 @@ class GameInstance { final int? launcherPid; final int? eacPid; final List injectedDlls; - bool hosting; + final GameServerType? serverType; bool launched; bool movedToVirtualDesktop; bool tokenError; @@ -20,7 +20,7 @@ class GameInstance { required this.gamePid, required this.launcherPid, required this.eacPid, - required this.hosting, + required this.serverType, required this.child }): tokenError = false, launched = false, movedToVirtualDesktop = false, injectedDlls = []; @@ -37,7 +37,7 @@ class GameInstance { bool get nestedHosting { GameInstance? child = this; while(child != null) { - if(child.hosting) { + if(child.serverType != null) { return true; } @@ -46,4 +46,10 @@ class GameInstance { return false; } +} + +enum GameServerType { + headless, + virtualWindow, + window } \ No newline at end of file diff --git a/common/lib/src/util/build.dart b/common/lib/src/util/build.dart index e6d0246..433c245 100644 --- a/common/lib/src/util/build.dart +++ b/common/lib/src/util/build.dart @@ -67,7 +67,7 @@ Future downloadArchiveBuild(FortniteBuildDownloadOptions options) async { } final startTime = DateTime.now().millisecondsSinceEpoch; - final response = _downloadArchive(options, tempFile, startTime); + final response = _downloadArchive(options, stopped, tempFile, startTime); await Future.any([stopped.future, response]); if(!stopped.isCompleted) { await _extractArchive(stopped, extension, tempFile, options); @@ -79,13 +79,17 @@ Future downloadArchiveBuild(FortniteBuildDownloadOptions options) async { } } -Future _downloadArchive(FortniteBuildDownloadOptions options, File tempFile, int startTime, [int? byteStart = null, int errorsCount = 0]) async { +Future _downloadArchive(FortniteBuildDownloadOptions options, Completer stopped, File tempFile, int startTime, [int? byteStart = null, int errorsCount = 0]) async { var received = byteStart ?? 0; try { await _dio.download( options.build.link, tempFile.path, onReceiveProgress: (data, length) { + if(stopped.isCompleted) { + throw StateError("Download interrupted"); + } + received = data; final percentage = (received / length) * 100; _onProgress(startTime, percentage < 1 ? null : DateTime.now().millisecondsSinceEpoch, percentage, false, options); @@ -116,12 +120,16 @@ Future _downloadArchive(FortniteBuildDownloadOptions options, File tempFil ) ); }catch(error) { + if(stopped.isCompleted) { + return; + } + if(errorsCount > _maxErrors || error.toString().contains(_deniedConnectionError) || error.toString().contains(_unavailableError)) { _onError(error, options); return; } - await _downloadArchive(options, tempFile, startTime, received, errorsCount + 1); + await _downloadArchive(options, stopped, tempFile, startTime, received, errorsCount + 1); } } @@ -225,6 +233,7 @@ Future _extractArchive(Completer stopped, String extension, File } await Future.any([stopped.future, process.exitCode]); + process.kill(ProcessSignal.sigabrt); } void _onProgress(int startTime, int? now, double percentage, bool extracting, FortniteBuildDownloadOptions options) { diff --git a/common/lib/src/util/dll.dart b/common/lib/src/util/dll.dart index abdbd13..f0e20d7 100644 --- a/common/lib/src/util/dll.dart +++ b/common/lib/src/util/dll.dart @@ -18,7 +18,6 @@ Future hasRebootDllUpdate(int? lastUpdateMs, {int hours = 24, bool force = } Future downloadCriticalDll(String name, String outputPath) async { - print("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name"); final response = await http.get(Uri.parse("https://github.com/Auties00/reboot_launcher/raw/master/gui/dependencies/dlls/$name")); if(response.statusCode != 200) { throw Exception("Cannot download $name: status code ${response.statusCode}"); diff --git a/common/lib/src/util/patcher.dart b/common/lib/src/util/patcher.dart index 0181844..07868c4 100644 --- a/common/lib/src/util/patcher.dart +++ b/common/lib/src/util/patcher.dart @@ -9,6 +9,7 @@ final Uint8List _patchedHeadless = 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 ]); +// Not used right now final Uint8List _originalMatchmaking = Uint8List.fromList([ 63, 0, 69, 0, 110, 0, 99, 0, 114, 0, 121, 0, 112, 0, 116, 0, 105, 0, 111, 0, 110, 0, 84, 0, 111, 0, 107, 0, 101, 0, 110, 0, 61 ]); @@ -18,7 +19,7 @@ final Uint8List _patchedMatchmaking = Uint8List.fromList([ ]); Future patchHeadless(File file) async => - _patch(file, _originalHeadless, _patchedHeadless); + await _patch(file, _originalHeadless, _patchedHeadless); Future patchMatchmaking(File file) async => await _patch(file, _originalMatchmaking, _patchedMatchmaking); @@ -29,22 +30,24 @@ Future _patch(File file, Uint8List original, Uint8List patched) async { throw Exception("Cannot mutate length of binary file"); } - final read = await file.readAsBytes(); - final length = await file.length(); + final source = await file.readAsBytes(); var readOffset = 0; var patchOffset = -1; var patchCount = 0; - while(readOffset < length){ - if(read[readOffset] == original[patchCount]){ + while(readOffset < source.length){ + if(source[readOffset] == original[patchCount]){ if(patchOffset == -1) { patchOffset = readOffset; } - if(++patchCount == original.length) { + if(readOffset - patchOffset + 1 == original.length) { break; } + + patchCount++; }else { patchOffset = -1; + patchCount = 0; } readOffset++; @@ -55,10 +58,10 @@ Future _patch(File file, Uint8List original, Uint8List patched) async { } for(var i = 0; i < patched.length; i++) { - read[patchOffset + i] = patched[i]; + source[patchOffset + i] = patched[i]; } - await file.writeAsBytes(read, flush: true); + await file.writeAsBytes(source, flush: true); return true; }catch(_){ return false; diff --git a/common/lib/src/util/process.dart b/common/lib/src/util/process.dart index 1649fc6..77fccb1 100644 --- a/common/lib/src/util/process.dart +++ b/common/lib/src/util/process.dart @@ -104,9 +104,9 @@ Future startElevatedProcess({required String executable, required String a return shellResult == 1; } -Future startProcess({required File executable, List? args, bool wrapProcess = true, bool window = false, String? name}) async { +Future startProcess({required File executable, List? args, bool useTempBatch = true, bool window = false, String? name}) async { final argsOrEmpty = args ?? []; - if(wrapProcess) { + if(useTempBatch) { final tempScriptDirectory = await tempDirectory.createTemp("reboot_launcher_process"); final tempScriptFile = File("${tempScriptDirectory.path}/process.bat"); final command = window ? 'cmd.exe /k ""${executable.path}" ${argsOrEmpty.join(" ")}"' : '"${executable.path}" ${argsOrEmpty.join(" ")}'; @@ -202,7 +202,7 @@ Future watchProcess(int pid) async { return await completer.future; } -List createRebootArgs(String username, String password, bool host, bool headless, String additionalArgs) { +List createRebootArgs(String username, String password, bool host, GameServerType hostType, bool log, String additionalArgs) { if(password.isEmpty) { username = '${_parseUsername(username, host)}@projectreboot.dev'; } @@ -223,12 +223,18 @@ List createRebootArgs(String username, String password, bool host, bool "-AUTH_TYPE=epic" ]; - if(host && headless){ + if(log) { + args.add("-log"); + } + + if(host) { args.addAll([ - "-nullrhi", "-nosplash", - "-nosound", + "-nosound" ]); + if(hostType == GameServerType.headless){ + args.add("-nullrhi"); + } } if(additionalArgs.isNotEmpty){ diff --git a/gui/assets/images/backend.png b/gui/assets/images/backend.png new file mode 100644 index 0000000..0e484f1 Binary files /dev/null and b/gui/assets/images/backend.png differ diff --git a/gui/assets/info/en/1. What is Project Reboot b/gui/assets/info/en/faq/1. What is Project Reboot similarity index 100% rename from gui/assets/info/en/1. What is Project Reboot rename to gui/assets/info/en/faq/1. What is Project Reboot diff --git a/gui/assets/info/en/10. Corrupted build b/gui/assets/info/en/faq/10. Corrupted build similarity index 100% rename from gui/assets/info/en/10. Corrupted build rename to gui/assets/info/en/faq/10. Corrupted build diff --git a/gui/assets/info/en/11. LawinV2 and backends with email and password authentication b/gui/assets/info/en/faq/11. LawinV2 and backends with email and password authentication similarity index 100% rename from gui/assets/info/en/11. LawinV2 and backends with email and password authentication rename to gui/assets/info/en/faq/11. LawinV2 and backends with email and password authentication diff --git a/gui/assets/info/en/faq/12. Can I get skins in game b/gui/assets/info/en/faq/12. Can I get skins in game new file mode 100644 index 0000000..66ec1f4 --- /dev/null +++ b/gui/assets/info/en/faq/12. Can I get skins in game @@ -0,0 +1,2 @@ +No, skins don't work in Reboot. +This is because Epic asked us to remove them. \ No newline at end of file diff --git a/gui/assets/info/en/2. What is a Fortnite game server b/gui/assets/info/en/faq/2. What is a Fortnite game server similarity index 100% rename from gui/assets/info/en/2. What is a Fortnite game server rename to gui/assets/info/en/faq/2. What is a Fortnite game server diff --git a/gui/assets/info/en/3. Types of Fortnite game server b/gui/assets/info/en/faq/3. Types of Fortnite game server similarity index 100% rename from gui/assets/info/en/3. Types of Fortnite game server rename to gui/assets/info/en/faq/3. Types of Fortnite game server diff --git a/gui/assets/info/en/4. How can others join my game server b/gui/assets/info/en/faq/4. How can others join my game server similarity index 100% rename from gui/assets/info/en/4. How can others join my game server rename to gui/assets/info/en/faq/4. How can others join my game server diff --git a/gui/assets/info/en/5. What is a backend b/gui/assets/info/en/faq/5. What is a backend similarity index 100% rename from gui/assets/info/en/5. What is a backend rename to gui/assets/info/en/faq/5. What is a backend diff --git a/gui/assets/info/en/6. What is the Unreal Engine console b/gui/assets/info/en/faq/6. What is the Unreal Engine console similarity index 100% rename from gui/assets/info/en/6. What is the Unreal Engine console rename to gui/assets/info/en/faq/6. What is the Unreal Engine console diff --git a/gui/assets/info/en/7. I cannot open Fortnite because of an authentication error b/gui/assets/info/en/faq/7. I cannot open Fortnite because of an authentication error similarity index 100% rename from gui/assets/info/en/7. I cannot open Fortnite because of an authentication error rename to gui/assets/info/en/faq/7. I cannot open Fortnite because of an authentication error diff --git a/gui/assets/info/en/8. Why do I see two Fortnite versions opened on my PC b/gui/assets/info/en/faq/8. Why do I see two Fortnite versions opened on my PC similarity index 100% rename from gui/assets/info/en/8. Why do I see two Fortnite versions opened on my PC rename to gui/assets/info/en/faq/8. Why do I see two Fortnite versions opened on my PC diff --git a/gui/assets/info/en/9. I cannot enter in a match when I'm in Fortnite b/gui/assets/info/en/faq/9. I cannot enter in a match when I'm in Fortnite similarity index 64% rename from gui/assets/info/en/9. I cannot enter in a match when I'm in Fortnite rename to gui/assets/info/en/faq/9. I cannot enter in a match when I'm in Fortnite index 607662d..f98b80e 100644 --- a/gui/assets/info/en/9. I cannot enter in a match when I'm in Fortnite +++ b/gui/assets/info/en/faq/9. I cannot enter in a match when I'm in Fortnite @@ -1,2 +1,3 @@ As explained in the "What is the Unreal Engine console?" section, the "Play" button doesn't work in many Fortnite versions. -Instead, you need to click the key assigned to the Unreal Engine console, by default F8 or the tilde(the button above tab), and type open 127.0.0.1 \ No newline at end of file +Instead, you need to click the key assigned to the Unreal Engine console, by default F8 or the tilde(the button above tab), and type open 127.0.0.1 +If that doesn't work, go to the Host page and make sure that you are running a game server by clicking "Start Hosting". \ No newline at end of file diff --git a/gui/assets/info/en/questions/1. What is Project Reboot b/gui/assets/info/en/questions/1. What is Project Reboot new file mode 100644 index 0000000..ae2e2a4 --- /dev/null +++ b/gui/assets/info/en/questions/1. What is Project Reboot @@ -0,0 +1,3 @@ +A Fortnite game server created by Milxnor +A Minecraft game server created by Chief Keef +I don't know \ No newline at end of file diff --git a/gui/assets/info/en/questions/10. Which of these is a bug that you should report on Discord b/gui/assets/info/en/questions/10. Which of these is a bug that you should report on Discord new file mode 100644 index 0000000..b0354bb --- /dev/null +++ b/gui/assets/info/en/questions/10. Which of these is a bug that you should report on Discord @@ -0,0 +1,3 @@ +A version I downloaded from the launcher crashes +A version of Fortnite I downloaded from the internet crashes +I can't enter in game from the lobby \ No newline at end of file diff --git a/gui/assets/info/en/questions/11. I can't enter in game from the Fortnite lobby b/gui/assets/info/en/questions/11. I can't enter in game from the Fortnite lobby new file mode 100644 index 0000000..7d7c6a5 --- /dev/null +++ b/gui/assets/info/en/questions/11. I can't enter in game from the Fortnite lobby @@ -0,0 +1,3 @@ +Click the Unreal Engine Key(F8 or the tilde by default) and type open 127.0.0.1 +Report a bug on Discord because there is a clearly a bug +Cry \ No newline at end of file diff --git a/gui/assets/info/en/questions/12. If you get an authentication error, what should you do b/gui/assets/info/en/questions/12. If you get an authentication error, what should you do new file mode 100644 index 0000000..180f58b --- /dev/null +++ b/gui/assets/info/en/questions/12. If you get an authentication error, what should you do @@ -0,0 +1,3 @@ +Switch my backend to embedded and try again before reporting a bug on Discord +Report a bug on Discord immediately and flood the chat +Cry \ No newline at end of file diff --git a/gui/assets/info/en/questions/13. Can I have skins in-game b/gui/assets/info/en/questions/13. Can I have skins in-game new file mode 100644 index 0000000..3f91cac --- /dev/null +++ b/gui/assets/info/en/questions/13. Can I have skins in-game @@ -0,0 +1,3 @@ +No, it's not possible +Yes, I just have to send Auties a message +Yes, let me ask on Discord how to get them \ No newline at end of file diff --git a/gui/assets/info/en/questions/2. Which seasons are supported b/gui/assets/info/en/questions/2. Which seasons are supported new file mode 100644 index 0000000..b73604f --- /dev/null +++ b/gui/assets/info/en/questions/2. Which seasons are supported @@ -0,0 +1,3 @@ +Only the ones I can download from the launcher +Any season between season 0 and 15 works +Depends on the day of the week \ No newline at end of file diff --git a/gui/assets/info/en/questions/3. What is 127.0.0.1 b/gui/assets/info/en/questions/3. What is 127.0.0.1 new file mode 100644 index 0000000..9627e4e --- /dev/null +++ b/gui/assets/info/en/questions/3. What is 127.0.0.1 @@ -0,0 +1,3 @@ +The address of your local machine where the game server will be hosted +Playboi Carti's hiding spot +I don't know \ No newline at end of file diff --git a/gui/assets/info/en/questions/4. What is a Fortnite game server b/gui/assets/info/en/questions/4. What is a Fortnite game server new file mode 100644 index 0000000..f5b0d8c --- /dev/null +++ b/gui/assets/info/en/questions/4. What is a Fortnite game server @@ -0,0 +1,3 @@ +A Fortnite window used to host matches instead of playing the game +A standalone piece of software to host matches +A stolen Epic games server we got by paying Tim Apple \ No newline at end of file diff --git a/gui/assets/info/en/questions/5. Can I play if I haven't started a game server b/gui/assets/info/en/questions/5. Can I play if I haven't started a game server new file mode 100644 index 0000000..d9b16ce --- /dev/null +++ b/gui/assets/info/en/questions/5. Can I play if I haven't started a game server @@ -0,0 +1,3 @@ +No, I will be kicked back to the lobby, unless I've joined someone else's server +Yes, one will be started automatically as soon as I click play +Yes, it's not needed to play \ No newline at end of file diff --git a/gui/assets/info/en/questions/6. What is an headless game server b/gui/assets/info/en/questions/6. What is an headless game server new file mode 100644 index 0000000..32d24f0 --- /dev/null +++ b/gui/assets/info/en/questions/6. What is an headless game server @@ -0,0 +1,3 @@ +A game server that doesn't render the Fortnite window to save memory +A game server hosted on someone else's PC +A game server running in Milxnor's mind \ No newline at end of file diff --git a/gui/assets/info/en/questions/7. Why do I see two Fortnite games when I play b/gui/assets/info/en/questions/7. Why do I see two Fortnite games when I play new file mode 100644 index 0000000..15776aa --- /dev/null +++ b/gui/assets/info/en/questions/7. Why do I see two Fortnite games when I play @@ -0,0 +1,3 @@ +Some seasons don't support a headless server, so the other frozen Fortnite is the game server +The launcher is bugged: I must report a bug on Discord +Auties hardcoded a crypto miner in Fortnite: by opening two games he gets more cash \ No newline at end of file diff --git a/gui/assets/info/en/questions/8. How can other players join my game b/gui/assets/info/en/questions/8. How can other players join my game new file mode 100644 index 0000000..d91d6c2 --- /dev/null +++ b/gui/assets/info/en/questions/8. How can other players join my game @@ -0,0 +1,3 @@ +I can open port 7777 on my router or use a private VPN service +I don't have to do anything, it's all automatic +What are you talking about? \ No newline at end of file diff --git a/gui/assets/info/en/questions/9. What is a backend b/gui/assets/info/en/questions/9. What is a backend new file mode 100644 index 0000000..efb1a4e --- /dev/null +++ b/gui/assets/info/en/questions/9. What is a backend @@ -0,0 +1,3 @@ +A piece of software to emulate Epic Games' servers for authentication +A piece of software that makes the launcher work correctly +I don't know \ No newline at end of file diff --git a/gui/lib/l10n/reboot_en.arb b/gui/lib/l10n/reboot_en.arb index da2843d..dd6e4f8 100644 --- a/gui/lib/l10n/reboot_en.arb +++ b/gui/lib/l10n/reboot_en.arb @@ -31,10 +31,6 @@ "hostGameServerPasswordDescription": "The password of your game server, if you need one", "hostGameServerDiscoverableName": "Discoverable", "hostGameServerDiscoverableDescription": "Make your server available to other players on the server browser", - "hostHeadlessName": "Headless", - "hostHeadlessDescription": "Runs Fortnite without graphics to optimize resources usage, may not work for old seasons", - "hostVirtualDesktopName": "Virtual desktop", - "hostVirtualDesktopDescription": "Runs Fortnite in a virtual desktop if headless is not supported", "hostAutomaticRestartName": "Automatic restart", "hostAutomaticRestartDescription": "Automatically restarts your game server when the match ends", "hostShareName": "Share", @@ -298,5 +294,18 @@ "updateAvailableAction": "Download", "gameServerEnd": "The match ended", "gameServerRestart": "The server will restart in {timeInSeconds} seconds", - "gameServerShutdown": "The server will shutdown in {timeInSeconds} seconds" + "gameServerShutdown": "The server will shutdown in {timeInSeconds} seconds", + "quiz": "Quiz", + "startQuiz": "I have read the instructions", + "checkQuiz": "Check answers", + "quizFailed": "You got a score of {right}/{total}: you have {tries} left", + "quizSuccess": "You got all the questions right: thanks for reading the instructions!", + "quizZeroTriesLeft": "zero tries", + "quizOneTryLeft": "one try", + "quizTwoTriesLeft": "two tries", + "gameServerTypeName": "Type", + "gameServerTypeDescription": "The type of game server to use", + "gameServerTypeHeadless": "Background process", + "gameServerTypeVirtualWindow": "Virtual window", + "gameServerTypeWindow": "Normal window" } diff --git a/gui/lib/src/controller/backend_controller.dart b/gui/lib/src/controller/backend_controller.dart index 0b78831..7937a34 100644 --- a/gui/lib/src/controller/backend_controller.dart +++ b/gui/lib/src/controller/backend_controller.dart @@ -43,7 +43,8 @@ class BackendController extends GetxController { storage?.write("${type.value.name}_port", port.text)); detached = RxBool(storage?.read("detached") ?? false); detached.listen((value) => storage?.write("detached", value)); - gameServerAddress = TextEditingController(text: storage?.read("game_server_address") ?? "127.0.0.1"); + final address = storage?.read("game_server_address"); + gameServerAddress = TextEditingController(text: address == null || address.isEmpty ? "127.0.0.1" : address); var lastValue = gameServerAddress.text; writeMatchmakingIp(lastValue); gameServerAddress.addListener(() { @@ -76,6 +77,7 @@ class BackendController extends GetxController { host.text = type.value != ServerType.remote ? kDefaultBackendHost : ""; port.text = kDefaultBackendPort.toString(); + gameServerAddress.text = "127.0.0.1"; detached.value = false; } diff --git a/gui/lib/src/controller/hosting_controller.dart b/gui/lib/src/controller/hosting_controller.dart index 5687d24..be1d88b 100644 --- a/gui/lib/src/controller/hosting_controller.dart +++ b/gui/lib/src/controller/hosting_controller.dart @@ -14,8 +14,7 @@ class HostingController extends GetxController { late final TextEditingController password; late final RxBool showPassword; late final RxBool discoverable; - late final RxBool headless; - late final RxBool virtualDesktop; + late final Rx type; late final RxBool autoRestart; late final RxBool started; late final RxBool published; @@ -34,10 +33,8 @@ class HostingController extends GetxController { password.addListener(() => _storage?.write("password", password.text)); discoverable = RxBool(_storage?.read("discoverable") ?? false); discoverable.listen((value) => _storage?.write("discoverable", value)); - headless = RxBool(_storage?.read("headless") ?? true); - headless.listen((value) => _storage?.write("headless", value)); - virtualDesktop = RxBool(_storage?.read("virtual_desktop") ?? true); - virtualDesktop.listen((value) => _storage?.write("virtual_desktop", value)); + type = Rx(GameServerType.values.elementAt(_storage?.read("type") ?? GameServerType.headless.index)); + type.listen((value) => _storage?.write("type", value.index)); autoRestart = RxBool(_storage?.read("auto_restart") ?? true); autoRestart.listen((value) => _storage?.write("auto_restart", value)); started = RxBool(false); @@ -64,8 +61,8 @@ class HostingController extends GetxController { discoverable.value = false; started.value = false; instance.value = null; - headless.value = true; - virtualDesktop.value = true; + type.value = GameServerType.headless; + autoRestart.value = true; } Map? findServerById(String uuid) { diff --git a/gui/lib/src/page/implementation/home_page.dart b/gui/lib/src/page/implementation/home_page.dart index 6927875..c944fa3 100644 --- a/gui/lib/src/page/implementation/home_page.dart +++ b/gui/lib/src/page/implementation/home_page.dart @@ -29,6 +29,8 @@ import 'package:reboot_launcher/src/widget/profile_tile.dart'; import 'package:reboot_launcher/src/widget/title_bar.dart'; import 'package:window_manager/window_manager.dart'; +import 'info_page.dart'; + class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @@ -221,6 +223,7 @@ class _HomePageState extends State with WindowListener, AutomaticKeepA super.build(context); _settingsController.language.value; loadTranslations(context); + // InfoPage.initInfoTiles(); return Obx(() { return NavigationPaneTheme( data: NavigationPaneThemeData( @@ -394,7 +397,7 @@ class _PaneBody extends StatefulWidget { class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin { final SettingsController _settingsController = Get.find(); - final PageController _pageController = PageController(keepPage: true); + final PageController _pageController = PageController(keepPage: true, initialPage: pageIndex.value); @override bool get wantKeepAlive => true; @@ -402,7 +405,15 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin @override void initState() { super.initState(); - pageIndex.listen((index) => _pageController.jumpToPage(index)); + var lastPage = pageIndex.value; + pageIndex.listen((index) { + if(index == lastPage) { + return; + } + + lastPage = index; + _pageController.jumpToPage(index); + }); } @override @@ -437,6 +448,10 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin elements.add(TextSpan( text: pages[pageIndex.value].name, recognizer: pageStack.isNotEmpty ? (TapGestureRecognizer()..onTap = () { + if(inDialog) { + return; + } + for(var i = 0; i < pageStack.length; i++) { Navigator.of(pageKey.currentContext!).pop(); final element = pageStack.removeLast(); @@ -461,6 +476,10 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin elements.add(TextSpan( text: innerPage, recognizer: i == pageStack.length - 1 ? null : (TapGestureRecognizer()..onTap = () { + if(inDialog) { + return; + } + for(var j = 0; j < i - 1; j++) { Navigator.of(pageKey.currentContext!).pop(); final element = pageStack.removeLast(); diff --git a/gui/lib/src/page/implementation/info_page.dart b/gui/lib/src/page/implementation/info_page.dart index df3ae6c..19d02b8 100644 --- a/gui/lib/src/page/implementation/info_page.dart +++ b/gui/lib/src/page/implementation/info_page.dart @@ -1,12 +1,11 @@ -import 'dart:async'; import 'dart:collection'; import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; -import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; +import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart'; import 'package:reboot_launcher/src/page/abstract/page.dart'; import 'package:reboot_launcher/src/page/abstract/page_type.dart'; import 'package:reboot_launcher/src/page/pages.dart'; @@ -15,12 +14,14 @@ import 'package:path/path.dart' as path; import 'package:reboot_launcher/src/widget/info_tile.dart'; class InfoPage extends RebootPage { - static late final List _infoTiles; + static late List _infoTiles; + static late List<_QuizEntry> _quizEntries; + static Object? initInfoTiles() { try { - final directory = Directory("${assetsDirectory.path}\\info\\$currentLocale"); - final map = SplayTreeMap(); - for(final entry in directory.listSync()) { + final faqDirectory = Directory("${assetsDirectory.path}\\info\\$currentLocale\\faq"); + final infoTiles = SplayTreeMap(); + for(final entry in faqDirectory.listSync()) { if(entry is File) { final name = Uri.decodeQueryComponent(path.basename(entry.path)); final splitter = name.indexOf("."); @@ -34,16 +35,42 @@ class InfoPage extends RebootPage { } final questionName = Uri.decodeQueryComponent(name.substring(splitter + 2)); - map[index] = InfoTile( + infoTiles[index] = InfoTile( title: Text(questionName), content: Text(entry.readAsStringSync()) ); } } - _infoTiles = map.values.toList(growable: false); + _infoTiles = infoTiles.values.toList(growable: false); + + final questionsDirectory = Directory("${assetsDirectory.path}\\info\\$currentLocale\\questions"); + final questions = SplayTreeMap(); + for(final entry in questionsDirectory.listSync()) { + if(entry is File) { + final name = Uri.decodeQueryComponent(path.basename(entry.path)); + final splitter = name.indexOf("."); + if(splitter == -1) { + continue; + } + + final index = int.tryParse(name.substring(0, splitter)); + if(index == null) { + continue; + } + + final questionName = Uri.decodeQueryComponent(name.substring(splitter + 2)); + questions[index] = _QuizEntry( + question: questionName, + options: entry.readAsStringSync().split("\n") + ); + } + } + _quizEntries = questions.values.toList(growable: false); + return null; }catch(error) { _infoTiles = []; + _quizEntries = []; return error; } } @@ -60,7 +87,7 @@ class InfoPage extends RebootPage { String get iconAsset => "assets/images/info.png"; @override - bool hasButton(String? routeName) => false; + bool hasButton(String? pageName) => Get.find().firstRun.value && pageName != null; @override RebootPageType get type => RebootPageType.info; @@ -68,22 +95,14 @@ class InfoPage extends RebootPage { class _InfoPageState extends RebootPageState { final SettingsController _settingsController = Get.find(); - RxInt _counter = RxInt(kDebugMode ? 0 : 180); - late bool _showButton; + late final Rxn _quizPage; @override void initState() { - _showButton = _settingsController.firstRun.value; - if(_settingsController.firstRun.value) { - Timer.periodic(const Duration(seconds: 1), (timer) { - if (_counter.value <= 0) { - _settingsController.firstRun.value = false; - timer.cancel(); - } else { - _counter.value = _counter.value - 1; - } - }); - } + _quizPage = Rxn(_settingsController.firstRun.value ? _QuizRoute( + entries: InfoPage._quizEntries, + onSuccess: () => _quizPage.value = null + ) : null); super.initState(); } @@ -92,28 +111,183 @@ class _InfoPageState extends RebootPageState { @override Widget? get button { - if(!_showButton) { - return const SizedBox.shrink(); + if(_quizPage.value == null) { + return null; } return Obx(() { - final totalSecondsLeft = _counter.value; - final minutesLeft = totalSecondsLeft ~/ 60; - final secondsLeft = totalSecondsLeft % 60; + final page = _quizPage.value; + if(page == null) { + return const SizedBox.shrink(); + } + return SizedBox( width: double.infinity, height: 48, child: Button( - onPressed: totalSecondsLeft <= 0 ? () { - _showButton = false; - pageIndex.value = RebootPageType.play.index; - } : null, + onPressed: () => Navigator.of(context).push(PageRouteBuilder( + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + settings: RouteSettings( + name: translations.quiz + ), + pageBuilder: (context, incoming, outgoing) => page + )), child: Text( - totalSecondsLeft <= 0 ? "I have read the instructions" - : "Read the instructions for at least ${secondsLeft == 0 ? '$minutesLeft minute${minutesLeft > 1 ? 's' : ''}' : minutesLeft == 0 ? '$secondsLeft second${secondsLeft > 1 ? 's' : ''}' : '$minutesLeft minute${minutesLeft > 1 ? 's' : ''} and $secondsLeft second${secondsLeft > 1 ? 's' : ''}'}" + translations.startQuiz ), ) ); }); } +} + +class _QuizRoute extends StatefulWidget { + final List<_QuizEntry> entries; + final void Function() onSuccess; + const _QuizRoute({ + required this.entries, + required this.onSuccess + }); + + @override + State<_QuizRoute> createState() => _QuizRouteState(); +} + +class _QuizRouteState extends State<_QuizRoute> with AutomaticKeepAliveClientMixin { + final SettingsController _settingsController = Get.find(); + late final List _selectedIndexes = List.generate(widget.entries.length, (_) => RxInt(-1)); + int _triesLeft = 3; + + @override + bool get wantKeepAlive => true; + + @override + Widget build(BuildContext context) { + super.build(context); + return Column( + children: [ + Expanded( + child: ListView( + children: widget.entries.indexed.expand((entry) { + final selectedIndex = _selectedIndexes[entry.$1]; + return [ + Text( + "${entry.$1 + 1}. ${entry.$2.question}", + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.w600 + ), + ), + const SizedBox(height: 12.0), + ...entry.$2.options.indexed.map((value) => Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Obx(() => RadioButton( + checked: value.$1 == selectedIndex.value, + content: Text(value.$2, textAlign: TextAlign.center), + onChanged: (_) => selectedIndex.value = value.$1 + )), + )), + const SizedBox(height: 12.0) + ]; + }).toList() + ), + ), + const SizedBox( + height: 8.0, + ), + SizedBox( + width: double.infinity, + height: 48, + child: Obx(() { + var clickable = true; + for(final index in _selectedIndexes) { + if(index.value == -1) { + clickable = false; + break; + } + } + + return Button( + onPressed: clickable ? () async { + if(_triesLeft <= 0) { + return; + } + + var right = 0; + final total = widget.entries.length; + for(var i = 0; i < total; i++) { + final selectedIndex = _selectedIndexes[i].value; + final correctIndex = widget.entries[i].correctIndex; + if(selectedIndex == correctIndex) { + right++; + } + } + + if(right == total) { + widget.onSuccess(); + showInfoBar( + translations.quizSuccess, + severity: InfoBarSeverity.success + ); + _settingsController.firstRun.value = false; + Navigator.of(context).pop(); + pageIndex.value = RebootPageType.play.index; + return; + } + + switch(--_triesLeft) { + case 0: + showInfoBar( + translations.quizFailed( + right, + total, + translations.quizZeroTriesLeft + ), + severity: InfoBarSeverity.error + ); + await Future.delayed(const Duration(seconds: 1)); + exit(0); + case 1: + showInfoBar( + translations.quizFailed( + right, + total, + translations.quizOneTryLeft + ), + severity: InfoBarSeverity.error + ); + break; + case 2: + showInfoBar( + translations.quizFailed( + right, + total, + translations.quizTwoTriesLeft + ), + severity: InfoBarSeverity.error + ); + break; + } + } : null, + child: Text(translations.checkQuiz), + ); + }, + ), + ) + ], + ); + } +} + +class _QuizEntry { + final String question; + final List options; + late final int correctIndex; + + _QuizEntry({required this.question, required this.options}) { + final correct = options.first; + options.shuffle(); + correctIndex = options.indexOf(correct); + } } \ No newline at end of file diff --git a/gui/lib/src/page/implementation/server_host_page.dart b/gui/lib/src/page/implementation/server_host_page.dart index e3c32b2..a327cd8 100644 --- a/gui/lib/src/page/implementation/server_host_page.dart +++ b/gui/lib/src/page/implementation/server_host_page.dart @@ -29,7 +29,7 @@ class HostPage extends RebootPage { const HostPage({Key? key}) : super(key: key); @override - String get name => "Host"; + String get name => translations.hostName; @override String get iconAsset => "assets/images/host.png"; @@ -289,51 +289,23 @@ class _HostingPageState extends RebootPageState { title: Text(translations.settingsServerOptionsName), subtitle: Text(translations.settingsServerOptionsSubtitle), children: [ - Obx(() => SettingTile( + SettingTile( icon: Icon( FluentIcons.window_console_20_regular ), - title: Text(translations.hostHeadlessName), - subtitle: Text(translations.hostHeadlessDescription), - contentWidth: null, - content: Row( - children: [ - Text( - _hostingController.headless.value ? translations.on : translations.off - ), - const SizedBox( - width: 16.0 - ), - ToggleSwitch( - checked: _hostingController.headless.value, - onChanged: (value) => _hostingController.headless.value = value - ), - ], - ), - )), - Obx(() => SettingTile( - icon: Icon( - FluentIcons.desktop_edit_24_regular - ), - title: Text(translations.hostVirtualDesktopName), - subtitle: Text(translations.hostVirtualDesktopDescription), - contentWidth: null, - content: Row( - children: [ - Text( - _hostingController.virtualDesktop.value ? translations.on : translations.off - ), - const SizedBox( - width: 16.0 - ), - ToggleSwitch( - checked: _hostingController.virtualDesktop.value, - onChanged: (value) => _hostingController.virtualDesktop.value = value - ), - ], - ), - )), - Obx(() => SettingTile( + title: Text(translations.gameServerTypeName), + subtitle: Text(translations.gameServerTypeDescription), + content: Obx(() => DropDownButton( + onOpen: () => inDialog = true, + onClose: () => inDialog = false, + leading: Text(_hostingController.type.value.translatedName), + items: GameServerType.values.map((entry) => MenuFlyoutItem( + text: Text(entry.translatedName), + onPressed: () => _hostingController.type.value = entry + )).toList() + )), + ), + SettingTile( icon: Icon( FluentIcons.arrow_reset_24_regular ), @@ -348,13 +320,13 @@ class _HostingPageState extends RebootPageState { const SizedBox( width: 16.0 ), - ToggleSwitch( + Obx(() => ToggleSwitch( checked: _hostingController.autoRestart.value, onChanged: (value) => _hostingController.autoRestart.value = value - ), + )), ], ), - )), + ), SettingTile( icon: Icon( fluentUi.FluentIcons.number_field diff --git a/gui/lib/src/util/os.dart b/gui/lib/src/util/os.dart index 6a71d36..a220549 100644 --- a/gui/lib/src/util/os.dart +++ b/gui/lib/src/util/os.dart @@ -6,6 +6,7 @@ import 'package:ffi/ffi.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/scheduler.dart'; import 'package:win32/win32.dart'; +import 'package:file_picker/file_picker.dart'; final RegExp _winBuildRegex = RegExp(r'(?<=\(Build )(.*)(?=\))'); @@ -23,6 +24,22 @@ bool get isWin11 { return intBuild != null && intBuild > 22000; } +Future openFolderPicker(String title) async => + await FilePicker.platform.getDirectoryPath(dialogTitle: title); + +Future openFilePicker(String extension) async { + var result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowMultiple: false, + allowedExtensions: [extension] + ); + if(result == null || result.files.isEmpty){ + return null; + } + + return result.files.first.path; +} + bool get isDarkMode => SchedulerBinding.instance.platformDispatcher.platformBrightness.isDark; diff --git a/gui/lib/src/util/picker.dart b/gui/lib/src/util/picker.dart deleted file mode 100644 index 29eb2b8..0000000 --- a/gui/lib/src/util/picker.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:file_picker/file_picker.dart'; - -Future openFolderPicker(String title) async => - await FilePicker.platform.getDirectoryPath(dialogTitle: title); - -Future openFilePicker(String extension) async { - var result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowMultiple: false, - allowedExtensions: [extension] - ); - if(result == null || result.files.isEmpty){ - return null; - } - - return result.files.first.path; -} \ No newline at end of file diff --git a/gui/lib/src/util/translations.dart b/gui/lib/src/util/translations.dart index 12ff042..f301284 100644 --- a/gui/lib/src/util/translations.dart +++ b/gui/lib/src/util/translations.dart @@ -1,6 +1,7 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter_gen/gen_l10n/reboot_localizations.dart'; import 'package:intl/intl.dart'; +import 'package:reboot_common/common.dart'; AppLocalizations? _translations; bool _init = false; @@ -19,3 +20,16 @@ void loadTranslations(BuildContext context) { } String get currentLocale => Intl.getCurrentLocale().split("_")[0]; + +extension GameServerTypeExtension on GameServerType { + String get translatedName { + switch(this) { + case GameServerType.headless: + return translations.gameServerTypeHeadless; + case GameServerType.virtualWindow: + return translations.gameServerTypeVirtualWindow; + case GameServerType.window: + return translations.gameServerTypeWindow; + } + } +} diff --git a/gui/lib/src/widget/add_server_version.dart b/gui/lib/src/widget/add_server_version.dart index c1ce6d1..d79edc6 100644 --- a/gui/lib/src/widget/add_server_version.dart +++ b/gui/lib/src/widget/add_server_version.dart @@ -38,6 +38,7 @@ class _AddServerVersionState extends State { late Future _fetchFuture; late Future _diskFuture; + Isolate? _isolate; SendPort? _downloadPort; Object? _error; StackTrace? _stackTrace; @@ -65,6 +66,7 @@ class _AddServerVersionState extends State { void _cancelDownload() { Process.run('${assetsDirectory.path}\\build\\stop.bat', []); _downloadPort?.send(kStopBuildDownloadSignal); + _isolate?.kill(priority: Isolate.immediate); } @override @@ -147,7 +149,7 @@ class _AddServerVersionState extends State { ); final errorPort = ReceivePort(); errorPort.listen((message) => _onDownloadError(message, null)); - await Isolate.spawn( + _isolate = await Isolate.spawn( downloadArchiveBuild, options, onError: errorPort.sendPort, diff --git a/gui/lib/src/widget/file_selector.dart b/gui/lib/src/widget/file_selector.dart index f40b5ea..4111ae2 100644 --- a/gui/lib/src/widget/file_selector.dart +++ b/gui/lib/src/widget/file_selector.dart @@ -1,6 +1,6 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/foundation.dart'; -import 'package:reboot_launcher/src/util/picker.dart'; +import 'package:reboot_launcher/src/util/os.dart'; class FileSelector extends StatefulWidget { final String placeholder; diff --git a/gui/lib/src/widget/game_start_button.dart b/gui/lib/src/widget/game_start_button.dart index e7f3e01..27dedcb 100644 --- a/gui/lib/src/widget/game_start_button.dart +++ b/gui/lib/src/widget/game_start_button.dart @@ -68,9 +68,9 @@ class _LaunchButtonState extends State { void _setStarted(bool hosting, bool started) => hosting ? _hostingController.started.value = started : _gameController.started.value = started; - Future _toggle({bool? host, bool forceGUI = false}) async { + Future _toggle({bool? host}) async { host ??= widget.host; - log("[${host ? 'HOST' : 'GAME'}] Toggling state(forceGUI: $forceGUI)"); + log("[${host ? 'HOST' : 'GAME'}] Toggling state"); if (host ? _hostingController.started() : _gameController.started()) { log("[${host ? 'HOST' : 'GAME'}] User asked to close the current instance"); _onStop( @@ -100,7 +100,7 @@ class _LaunchButtonState extends State { } try { - final executable = version.gameExecutable; + final executable = await version.shippingExecutable; if(executable == null){ log("[${host ? 'HOST' : 'GAME'}] No executable found"); _onStop( @@ -120,12 +120,11 @@ class _LaunchButtonState extends State { return; } log("[${host ? 'HOST' : 'GAME'}] Backend works"); - final headless = !forceGUI && _hostingController.headless.value; - final virtualDesktop = _hostingController.virtualDesktop.value; - log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($headless)"); - final linkedHostingInstance = await _startMatchMakingServer(version, host, headless, virtualDesktop, false); + final serverType = _hostingController.type.value; + log("[${host ? 'HOST' : 'GAME'}] Implicit game server metadata: headless($serverType)"); + final linkedHostingInstance = await _startMatchMakingServer(version, host, serverType, false); log("[${host ? 'HOST' : 'GAME'}] Implicit game server result: $linkedHostingInstance"); - await _startGameProcesses(version, host, headless, virtualDesktop, linkedHostingInstance); + await _startGameProcesses(version, host, serverType, linkedHostingInstance); if(!host) { _showLaunchingGameClientWidget(); } @@ -142,14 +141,14 @@ class _LaunchButtonState extends State { } } - Future _startMatchMakingServer(FortniteVersion version, bool host, bool headless, bool virtualDesktop, bool forceLinkedHosting) async { + Future _startMatchMakingServer(FortniteVersion version, bool host, GameServerType hostType, bool forceLinkedHosting) async { log("[${host ? 'HOST' : 'GAME'}] Checking if a server needs to be started automatically..."); if(host){ log("[${host ? 'HOST' : 'GAME'}] The user clicked on Start hosting, so it's not necessary"); return null; } - if(_backendController.type.value != ServerType.embedded || !isLocalHost(_backendController.gameServerAddress.text)) { + if(_backendController.type.value == ServerType.embedded && !isLocalHost(_backendController.gameServerAddress.text)) { log("[${host ? 'HOST' : 'GAME'}] Backend is not set to embedded and/or not pointing to the local game server"); return null; } @@ -166,7 +165,7 @@ class _LaunchButtonState extends State { } log("[${host ? 'HOST' : 'GAME'}] Starting implicit game server..."); - final instance = await _startGameProcesses(version, true, headless, virtualDesktop, null); + final instance = await _startGameProcesses(version, true, hostType, null); log("[${host ? 'HOST' : 'GAME'}] Started implicit game server..."); _setStarted(true, true); log("[${host ? 'HOST' : 'GAME'}] Set implicit game server as started"); @@ -195,7 +194,7 @@ class _LaunchButtonState extends State { return result; } - Future _startGameProcesses(FortniteVersion version, bool host, bool headless, bool virtualDesktop, GameInstance? linkedHosting) async { + Future _startGameProcesses(FortniteVersion version, bool host, GameServerType hostType, GameInstance? linkedHosting) async { log("[${host ? 'HOST' : 'GAME'}] Starting game process..."); log("[${host ? 'HOST' : 'GAME'}] Starting paused launcher..."); final launcherProcess = await _createPausedProcess(version, version.launcherExecutable); @@ -205,9 +204,9 @@ class _LaunchButtonState extends State { final eacProcess = await _createPausedProcess(version, version.eacExecutable); log("[${host ? 'HOST' : 'GAME'}] Started paused eac: $eacProcess"); - final executable = host && headless ? await version.headlessGameExecutable : version.gameExecutable; + final executable = await version.shippingExecutable; log("[${host ? 'HOST' : 'GAME'}] Using game path: ${executable?.path}"); - final gameProcess = await _createGameProcess(version, executable!, host, headless, virtualDesktop, linkedHosting); + final gameProcess = await _createGameProcess(version, executable!, host, hostType, linkedHosting); if(gameProcess == null) { log("[${host ? 'HOST' : 'GAME'}] No game process was created"); return null; @@ -219,7 +218,7 @@ class _LaunchButtonState extends State { gamePid: gameProcess, launcherPid: launcherProcess, eacPid: eacProcess, - hosting: host, + serverType: host ? hostType : null, child: linkedHosting ); if(host){ @@ -233,20 +232,21 @@ class _LaunchButtonState extends State { return instance; } - Future _createGameProcess(FortniteVersion version, File executable, bool host, bool headless, bool virtualDesktop, GameInstance? linkedHosting) async { + Future _createGameProcess(FortniteVersion version, File executable, bool host, GameServerType hostType, GameInstance? linkedHosting) async { log("[${host ? 'HOST' : 'GAME'}] Generating instance args..."); final gameArgs = createRebootArgs( _gameController.username.text, _gameController.password.text, host, - _hostingController.headless.value, + hostType, + false, "" ); - log("[${host ? 'HOST' : 'GAME'}] Generated game args: $gameArgs"); + log("[${host ? 'HOST' : 'GAME'}] Generated game args: ${gameArgs.join(" ")}"); final gameProcess = await startProcess( executable: executable, args: gameArgs, - wrapProcess: false, + useTempBatch: false, name: "${version.name}-${host ? 'HOST' : 'GAME'}" ); void onGameOutput(String line, bool error) { @@ -259,8 +259,8 @@ class _LaunchButtonState extends State { onTokenError: () => _onStop(reason: _StopReason.tokenError), onBuildCorrupted: () => _onStop(reason: _StopReason.corruptedVersionError), onLoggedIn: () =>_onLoggedIn(host), - onMatchEnd: () => _onMatchEnd(version, virtualDesktop), - onDisplayAttached: () => _onDisplayAttached(headless, virtualDesktop, version) + onMatchEnd: () => _onMatchEnd(version), + onDisplayAttached: () => _onDisplayAttached(host, hostType, version) ); } gameProcess.stdOutput.listen((line) => onGameOutput(line, false)); @@ -272,24 +272,10 @@ class _LaunchButtonState extends State { return; } - if(!host || instance.launched) { - log("[${host ? 'HOST' : 'GAME'}] Called exit code(headless: $headless, launched: ${instance.launched}): stop signal"); - _onStop( - reason: _StopReason.exitCode, - host: host - ); - return; - } - - log("[${host ? 'HOST' : 'GAME'}] Called exit code(headless: $headless, launched: ${instance.launched}): restart signal"); - instance.launched = true; - await _onStop( + log("[${host ? 'HOST' : 'GAME'}] Called exit code(launched: ${instance.launched}): stop signal"); + _onStop( reason: _StopReason.exitCode, - host: true - ); - await _toggle( - forceGUI: true, - host: true + host: host ); }); return gameProcess.pid; @@ -302,7 +288,7 @@ class _LaunchButtonState extends State { final process = await startProcess( executable: file, - wrapProcess: false, + useTempBatch: false, name: "${version.name}-${basenameWithoutExtension(file.path)}" ); final pid = process.pid; @@ -310,8 +296,8 @@ class _LaunchButtonState extends State { return pid; } - Future _onDisplayAttached(bool headless, bool virtualDesktop, FortniteVersion version) async { - if(!headless && virtualDesktop) { + Future _onDisplayAttached(bool host, GameServerType type, FortniteVersion version) async { + if(host && type == GameServerType.virtualWindow) { final hostingInstance = _hostingController.instance.value; if(hostingInstance != null && !hostingInstance.movedToVirtualDesktop) { hostingInstance.movedToVirtualDesktop = true; @@ -346,7 +332,7 @@ class _LaunchButtonState extends State { } } - void _onMatchEnd(FortniteVersion version, bool virtualDesktop) { + void _onMatchEnd(FortniteVersion version) { if(_hostingController.autoRestart.value) { final notification = LocalNotification( title: translations.gameServerEnd, @@ -536,7 +522,7 @@ class _LaunchButtonState extends State { if(child != null) { await _onStop( reason: reason, - host: child.hosting + host: child.serverType != null ); } } diff --git a/gui/pubspec.yaml b/gui/pubspec.yaml index 6704b54..07a3bbd 100644 --- a/gui/pubspec.yaml +++ b/gui/pubspec.yaml @@ -1,6 +1,6 @@ name: reboot_launcher description: Graphical User Interface for Project Reboot -version: "9.1.3" +version: "9.1.4" publish_to: 'none' @@ -99,4 +99,5 @@ flutter: - assets/backend/public/ - assets/backend/responses/ - assets/build/ - - assets/info/en/ \ No newline at end of file + - assets/info/en/faq/ + - assets/info/en/questions/ \ No newline at end of file diff --git a/gui/windows/packaging/exe/custom-inno-setup-script.iss b/gui/windows/packaging/exe/custom-inno-setup-script.iss index 41fac78..7d170f6 100644 --- a/gui/windows/packaging/exe/custom-inno-setup-script.iss +++ b/gui/windows/packaging/exe/custom-inno-setup-script.iss @@ -15,7 +15,6 @@ DisableProgramGroupPage=yes OutputBaseFilename={{OUTPUT_BASE_FILENAME}} Compression=zip SolidCompression=yes -SetupIconFile={{SETUP_ICON_FILE}} WizardStyle=modern PrivilegesRequired=admin ArchitecturesAllowed=x64 diff --git a/lawin/README.md b/lawin/README.md deleted file mode 100644 index 55e56a7..0000000 --- a/lawin/README.md +++ /dev/null @@ -1,77 +0,0 @@ -
- LawinServer Logo - - ### LawinServer is a private server that supports all Fortnite versions! - -
-
- -## Credits -| Name | Contributions | -| --------------- | ----------- | -| [Lawin](https://github.com/Lawin0129) | Creator | -| [PRO100KatYT](https://github.com/PRO100KatYT) | Contributor & Maintainer | - -## Features: - -### Save the World: -- CloudStorage and ClientSettings (Settings saving) -- Llama purchasing and opening with random loot and choice packs -- Every Hero, Weapon, Defender and Resource -- All Founder's Packs rewards screen (togglable in the config) -- Refreshing, sending, collecting and aborting expeditions -- Crafting items in Backpack -- Transferring items to and from Storage -- Modifying and upgrading Schematic perks -- Supercharging items -- Leveling up and Evolving items -- Upgrading item rarity -- Hero, Defender, Survivor, Team Perk and Gadget equipping -- Research level resetting and upgrading -- Upgrade level resetting and upgrading -- Autofill survivors -- Recycling and destroying items -- Collection Book slotting and unslotting -- Claiming Daily Rewards -- Claiming Quest and Collection Book Rewards -- Modifying quickbars in Backpack -- Activating XP Boosts -- Correct Events in Frontend up to Season 11 (Can change) -- Buying Skill Tree perks -- Quests pinning -- Switching between Hero Loadouts -- Favoriting items -- Marking items as seen -- Changing items in Locker -- Changing banner icon and banner color -- Changing items edit styles -- Support a Creator with specific codes -- Fully working daily challenges system (New daily challenge every day, replacing daily challenges, etc...) -- Item transformation -- Event Quests from Season 2 up to Season X & Season 24 (Can change) - -### Battle Royale: -- CloudStorage and ClientSettings (Settings saving) -- Winterfest presents opening (11.31, 19.01 & 23.10) -- Purchasing Item Shop items -- Refunding cosmetics in the refund tab -- Favoriting items -- Marking items as seen -- Changing items in Locker -- Changing banner icon and banner color -- Changing items edit styles -- Support a Creator with specific codes -- Fully working daily challenges system (New daily challenge every day, replacing daily challenges, etc...) -- Completed Location & Discovery quests (discovered map in game & in lobby) for Chapter 2, 3 & 4 (Can change) -- Seasonal Quests from Season 3 up to Season 21 (Can change) -- Purchasable battle pass from Season 2 to Season 10 (Can change) -- Discovery Tab -- Leaderboards (v1) -- Configurable backend sided in-game events - check out the Events seciton in config.ini for more details -- Joining gameservers using the matchmaker - check out the GameServer seciton in config.ini for more details - -## How to host/use LawinServer -1) Install [NodeJS](https://nodejs.org/en/) -2) Run "install_packages.bat" (This file isn't required after the packages are installed.) -3) Run "start.bat", It should say "Started listening on port 3551" -4) Use something to redirect the fortnite servers to localhost:3551 (Which could be fiddler, ssl bypass that redirects servers, etc...) diff --git a/lawin/install_packages.bat b/lawin/install_packages.bat deleted file mode 100644 index 63b2a60..0000000 --- a/lawin/install_packages.bat +++ /dev/null @@ -1,2 +0,0 @@ -npm i -pause \ No newline at end of file diff --git a/lawin/public/images/discord-s.png b/lawin/public/images/discord-s.png deleted file mode 100644 index df71d46..0000000 Binary files a/lawin/public/images/discord-s.png and /dev/null differ diff --git a/lawin/public/images/discord.png b/lawin/public/images/discord.png deleted file mode 100644 index 8934573..0000000 Binary files a/lawin/public/images/discord.png and /dev/null differ diff --git a/lawin/public/images/lawin-s.png b/lawin/public/images/lawin-s.png deleted file mode 100644 index 05c8009..0000000 Binary files a/lawin/public/images/lawin-s.png and /dev/null differ diff --git a/lawin/public/images/lawin.jpg b/lawin/public/images/lawin.jpg deleted file mode 100644 index 2f20b79..0000000 Binary files a/lawin/public/images/lawin.jpg and /dev/null differ diff --git a/lawin/public/images/motd-s.png b/lawin/public/images/motd-s.png deleted file mode 100644 index 2911415..0000000 Binary files a/lawin/public/images/motd-s.png and /dev/null differ diff --git a/lawin/public/images/motd.png b/lawin/public/images/motd.png deleted file mode 100644 index 041c9d8..0000000 Binary files a/lawin/public/images/motd.png and /dev/null differ diff --git a/lawin/public/images/seasonx.png b/lawin/public/images/seasonx.png deleted file mode 100644 index 0a863c8..0000000 Binary files a/lawin/public/images/seasonx.png and /dev/null differ diff --git a/lawin/responses/CloudDir/LawinServer.chunk b/lawin/responses/CloudDir/LawinServer.chunk deleted file mode 100644 index 9cdf877..0000000 Binary files a/lawin/responses/CloudDir/LawinServer.chunk and /dev/null differ diff --git a/lawin/responses/CloudDir/LawinServer.manifest b/lawin/responses/CloudDir/LawinServer.manifest deleted file mode 100644 index b29aa32..0000000 Binary files a/lawin/responses/CloudDir/LawinServer.manifest and /dev/null differ