This commit is contained in:
Alessandro Autiero
2022-12-16 21:08:57 +01:00
parent ef7f34e0e3
commit 966b4b33fd
91 changed files with 388682 additions and 1005 deletions

333
lib/src/embedded/auth.dart Normal file
View File

@@ -0,0 +1,333 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:jaguar/http/context/context.dart';
import 'package:reboot_launcher/src/embedded/utils.dart';
import '../util/os.dart';
final Directory _profiles = Directory("${Platform.environment["UserProfile"]}\\.reboot_launcher\\backend\\profiles");
const String _token = "reboot_token";
const String _clientId = "reboot_client";
const String _device = "reboot_device";
const String _sessionId = "3c3662bcb661d6de679c636744c66b62";
List<Map<String, Object>> getAccounts(Context context) {
return context.query.getList("accountId").map(getAccount).toList();
}
Map<String, Object> getAccount(String account) {
return {"id": account, "displayName": _parseUsername(account), "externalAuths": {}};
}
Map<String, Object> getAccountInfo(Context context) {
var usernameId = context.pathParams.get("accountId")!;
var accountName = _parseUsername(usernameId);
return {
"id": usernameId,
"displayName": accountName,
"name": "Reboot",
"email": usernameId,
"failedLoginAttempts": 0,
"lastLogin": "2022-11-08T18:55:52.341Z",
"numberOfDisplayNameChanges": 0,
"ageGroup": "UNKNOWN",
"headless": false,
"country": "US",
"lastName": "Server",
"preferredLanguage": "en",
"canUpdateDisplayName": false,
"tfaEnabled": false,
"emailVerified": true,
"minorVerified": false,
"minorExpected": false,
"minorStatus": "UNKNOWN"
};
}
List<Map<String, Object>> getExternalAuths(Context context) => [];
Future<Map<String, Object>> getOAuthToken(Context context) async {
var usernameId = await _getUsername(context);
var accountName = _parseUsername(usernameId);
return {
"access_token": _token,
"expires_in": 28800,
"expires_at": "9999-12-02T01:12:01.100Z",
"token_type": "bearer",
"refresh_token": _token,
"refresh_expires": 86400,
"refresh_expires_at": "9999-12-02T01:12:01.100Z",
"account_id": usernameId,
"client_id": _clientId,
"internal_client": true,
"client_service": "fortnite",
"displayName": accountName,
"app": "fortnite",
"in_app_id": usernameId,
"device_id": _device
};
}
Future<String> _getUsername(Context context) async {
var params = await parseBody(context);
var username = params["username"];
return username ?? "unknown@projectreboot.dev";
}
Map<String, Object> verifyOAuthToken(Context context) {
return {
"token": _token,
"session_id": _sessionId,
"token_type": "bearer",
"client_id": _clientId,
"internal_client": true,
"client_service": "fortnite",
"account_id": "unknown",
"expires_in": 28800,
"expires_at": "9999-12-02T01:12:01.100Z",
"auth_method": "exchange_code",
"display_name": "unknown",
"app": "fortnite",
"in_app_id": "unknown",
"device_id": _device
};
}
List<Map<String, Object>> getExchange(Context context) => [];
List<String> getSsoDomains(Context context) => [
"unrealengine.com",
"unrealtournament.com",
"fortnite.com",
"epicgames.com"
];
String tryPlayOnPlatform(Context context) => "true";
List<Map<String, Object>> getFeatures(Context context) => [];
Map<String, Object?> getProfile(Context context){
var profileId = context.query.get("profileId");
if (profileId == null) {
return {"Error": "Profile not defined."};
}
var profileJson = _getProfileJson(profileId, context);
var profileFile = _getProfileFile(context);
var baseRevision = profileJson["rvn"] ?? 0;
var queryRevision = context.query.getInt("rvn") ?? -1;
var profileChanges = _getFullProfileUpdate(context, profileId, profileJson, queryRevision, baseRevision);
if(profileId == "athena" && !profileFile.existsSync()) {
profileFile.writeAsStringSync(json.encode(profileJson), flush: true);
}
return {
"profileRevision": baseRevision,
"profileId": profileId,
"profileChangesBaseRevision": baseRevision,
"profileChanges": profileChanges,
"profileCommandRevision": profileJson["commandRevision"] ?? 0,
"serverTime": "2022-11-08T18:55:52.341Z",
"responseVersion": 1
};
}
Map<String, dynamic> _getProfileJson(String profileId, Context context) {
if(profileId == "athena"){
var profile = _getProfileFile(context);
if(profile.existsSync()){
return json.decode(profile.readAsStringSync());
}
var body = loadEmbedded("profiles/$profileId.json").readAsStringSync();
return json.decode(body);
}
var profileJson = json.decode(loadEmbedded("profiles/$profileId.json").readAsStringSync());
return profileJson;
}
Future<Map<String, Object>> equipItem(Context context) async {
var profileFile = _getProfileFile(context);
var profileJson = json.decode(profileFile.readAsStringSync());
var baseRevision = profileJson["rvn"] ?? 0;
var queryRevision = context.query.getInt("rvn") ?? -1;
var body = json.decode(utf8.decode(await context.body));
var variant = _getReturnVariant(body, profileJson);
var change = _getStatsChanged(body, profileJson);
var profileChanges = _getProfileChanges(queryRevision, baseRevision, profileJson, change, body, variant);
profileFile.writeAsStringSync(json.encode(profileJson));
return {
"profileRevision": baseRevision,
"profileId": "athena",
"profileChangesBaseRevision": baseRevision,
"profileChanges": profileChanges,
"profileCommandRevision": profileJson["commandRevision"] ?? 0,
"serverTime": "2022-11-08T18:55:52.341Z",
"responseVersion": 1
};
}
List<dynamic> _getProfileChanges(int queryRevision, baseRevision, profileJson, bool change, body, bool variant) {
var changes = [];
if (change) {
var category = ("favorite_${body["slotName"] ?? "character"}")
.toLowerCase();
if (category == "favorite_itemwrap") {
category += "s";
}
profileJson["rvn"] = (profileJson["rvn"] ?? 0) + 1;
profileJson["commandRevision"] = (profileJson["commandRevision"] ?? 0) + 1;
changes.add({
"changeType": "statModified",
"name": category,
"value": profileJson["stats"]["attributes"][category]
});
if (variant) {
changes.add({
"changeType": "itemAttrChanged",
"itemId": body["itemToSlot"],
"attributeName": "variants",
"attributeValue": profileJson["items"][body["itemToSlot"]]["attributes"]["variants"]
});
}
}
if(queryRevision != baseRevision){
return [{
"changeType": "fullProfileUpdate",
"profile": profileJson
}];
}
return changes;
}
bool _getStatsChanged(body, profileJson) {
var slotName = body["slotName"];
if (slotName == null) {
return false;
}
switch (slotName) {
case "Character":
profileJson["stats"]["attributes"]["favorite_character"] =
body["itemToSlot"] ?? "";
return true;
case "Backpack":
profileJson["stats"]["attributes"]["favorite_backpack"] =
body["itemToSlot"] ?? "";
return true;
case "Pickaxe":
profileJson["stats"]["attributes"]["favorite_pickaxe"] =
body["itemToSlot"] ?? "";
return true;
case "Glider":
profileJson["stats"]["attributes"]["favorite_glider"] =
body["itemToSlot"] ?? "";
return true;
case "SkyDiveContrail":
profileJson["stats"]["attributes"]["favorite_skydivecontrail"] =
body["itemToSlot"] ?? "";
return true;
case "MusicPack":
profileJson["stats"]["attributes"]["favorite_musicpack"] =
body["itemToSlot"] ?? "";
return true;
case "LoadingScreen":
profileJson["stats"]["attributes"]["favorite_loadingscreen"] =
body["itemToSlot"] ?? "";
return true;
case "Dance":
var index = body["indexWithinSlot"] ?? 0;
if (index >= 0) {
profileJson["stats"]["attributes"]["favorite_dance"][index] =
body["itemToSlot"] ?? "";
}
return true;
case "ItemWrap":
var index = body["indexWithinSlot"] ?? 0;
if (index < 0) {
for (var i = 0; i < 7; i++) {
profileJson["stats"]["attributes"]["favorite_itemwraps"][i] =
body["itemToSlot"] ?? "";
}
} else {
profileJson["stats"]["attributes"]["favorite_itemwraps"][index] =
body["itemToSlot"] ?? "";
}
return true;
default:
return false;
}
}
bool _getReturnVariant(body, profileJson) {
var variantUpdates = body["variantUpdates"] ?? [];
if(!variantUpdates.toString().contains("active")){
return false;
}
try {
var variantJson = profileJson["items"][body["itemToSlot"]]["attributes"]["variants"] ?? [];
if (variantJson.isEmpty) {
variantJson = variantUpdates;
}
for (var i in variantJson) {
try {
if (variantJson[i]["channel"].toLowerCase() == body["variantUpdates"][i]["channel"].toLowerCase()) {
profileJson["items"][body["itemToSlot"]]["attributes"]["variants"][i]["active"] = body["variantUpdates"][i]["active"] ?? "";
}
} catch (_) {
// Ignored
}
}
return true;
} catch (_) {
// Ignored
}
return false;
}
List<Map<String, Object?>> _getFullProfileUpdate(Context context, String profileName, Map<String, dynamic> profileJson, int queryRevision, int baseRevision) {
if (queryRevision == baseRevision) {
return [];
}
if (profileName == "athena") {
var season = parseSeason(context);
profileJson["stats"]["attributes"]["season_num"] = season;
profileJson["stats"]["attributes"]["book_purchased"] = true;
profileJson["stats"]["attributes"]["book_level"] = 100;
profileJson["stats"]["attributes"]["season_match_boost"] = 100;
profileJson["stats"]["attributes"]["season_friend_match_boost"] = 100;
}
return [{
"changeType": "fullProfileUpdate",
"profile": profileJson
}];
}
String _parseUsername(String username) =>
username.contains("@") ? username.split("@")[0] : username;
File _getProfileFile(Context context) {
if(!_profiles.existsSync()){
_profiles.createSync(recursive: true);
}
return File("${_profiles.path}\\ClientProfile-${parseSeasonBuild(context)}.json");
}

View File

@@ -0,0 +1,36 @@
import 'dart:async';
import 'dart:io';
import 'package:jaguar/jaguar.dart';
class EmbeddedErrorWriter extends ErrorWriter {
static const String _errorName = "errors.com.lawinserver.common.not_found";
static const String _errorCode = "1004";
@override
FutureOr<Response> make404(Context ctx) {
stdout.writeln("Unknown path: ${ctx.uri} with method ${ctx.method}");
ctx.response.headers.set('X-Epic-Error-Name', _errorName);
ctx.response.headers.set('X-Epic-Error-Code', _errorCode);
return Response.json(
statusCode: 204,
{}
);
}
@override
FutureOr<Response> make500(Context ctx, Object error, [StackTrace? stack]) {
ctx.response.headers.set('X-Epic-Error-Name', _errorName);
ctx.response.headers.set('X-Epic-Error-Code', _errorCode);
return Response(
statusCode: 500,
body: {
"errorCode": _errorName,
"errorMessage": "Sorry the resource you were trying to find could not be found",
"numericErrorCode": _errorCode,
"originatingService": "any",
"intent": "prod"
}
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:jaguar/http/context/context.dart';
Map<String, Object?> getFortniteStatus(Context context) => {
"serviceInstanceId": "fortnite",
"status": "UP",
"message": "Fortnite is online",
"maintenanceUri": null,
"overrideCatalogIds": ["a7f138b2e51945ffbfdacc1af0541053"],
"allowedActions": [],
"banned": false,
"launcherInfoDTO": {
"appName": "Fortnite",
"catalogItemId": "4fe75bbc5a674f4f9b356b5c90567da5",
"namespace": "fn"
}
};
List<Map<String, Object?>> getBulkStatus(Context context) => [
{
"serviceInstanceId": "fortnite",
"status": "UP",
"message": "fortnite is up.",
"maintenanceUri": null,
"overrideCatalogIds": ["a7f138b2e51945ffbfdacc1af0541053"],
"allowedActions": ["PLAY", "DOWNLOAD"],
"banned": false,
"launcherInfoDTO": {
"appName": "Fortnite",
"catalogItemId": "4fe75bbc5a674f4f9b356b5c90567da5",
"namespace": "fn"
}
}
];

View File

@@ -0,0 +1,144 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:jaguar/http/context/context.dart';
import 'package:uuid/uuid.dart';
import 'package:jaguar/jaguar.dart';
String _build = "0";
String? _customIp;
Map<String, Object> getPlayerTicket(Context context){
var bucketId = context.query.get("bucketId");
if(bucketId == null){
return {"Error": "Missing bucket id"};
}
_build = bucketId.split(":")[0];
_customIp = context.query.get("player.option.customKey");
return {
"serviceUrl": "ws://127.0.0.1:8080",
"ticketType": "mms-player",
"payload": "69=",
"signature": "420="
};
}
Map<String, Object?> getSessionAccount(Context context) => {
"accountId": context.pathParams.get("accountId"),
"sessionId": context.pathParams.get("sessionId"),
"key": "AOJEv8uTFmUh7XM2328kq9rlAzeQ5xzWzPIiyKn2s7s="
};
Future<Map<String, Object?>> getMatch(Context context, String Function() ipQuery) async {
var ipAndPort = _customIp ?? ipQuery().trim();
var ip = ipAndPort.contains(":") ? ipAndPort.split(":")[0] : ipAndPort;
var port = ipAndPort.contains(":") ? int.parse(ipAndPort.split(":")[1]) : 7777;
return {
"id": context.pathParams.get("sessionId"),
"ownerId": _randomUUID(),
"ownerName": "[DS]fortnite-liveeugcec1c2e30ubrcore0a-z8hj-1968",
"serverName": "[DS]fortnite-liveeugcec1c2e30ubrcore0a-z8hj-1968",
"serverAddress": ip,
"serverPort": port,
"maxPublicPlayers": 220,
"openPublicPlayers": 175,
"maxPrivatePlayers": 0,
"openPrivatePlayers": 0,
"attributes": {
"REGION_s": "EU",
"GAMEMODE_s": "FORTATHENA",
"ALLOWBROADCASTING_b": true,
"SUBREGION_s": "GB",
"DCID_s": "FORTNITE-LIVEEUGCEC1C2E30UBRCORE0A-14840880",
"tenant_s": "Fortnite",
"MATCHMAKINGPOOL_s": "Any",
"STORMSHIELDDEFENSETYPE_i": 0,
"HOTFIXVERSION_i": 0,
"PLAYLISTNAME_s": "Playlist_DefaultSolo",
"SESSIONKEY_s": _randomUUID(),
"TENANT_s": "Fortnite",
"BEACONPORT_i": 15009
},
"publicPlayers": [],
"privatePlayers": [],
"totalPlayers": 45,
"allowJoinInProgress": false,
"shouldAdvertise": false,
"isDedicated": false,
"usesStats": false,
"allowInvites": false,
"usesPresence": false,
"allowJoinViaPresence": true,
"allowJoinViaPresenceFriendsOnly": false,
"buildUniqueId": _build,
"lastUpdated": "2022-11-08T18:55:52.341Z",
"started": false
};
}
List<Map<String, Object>> getMatchmakingRequests() => [];
void queueMatchmaking(WebSocket ws) {
var now = DateTime.now();
var ticketId = md5.convert(utf8.encode("1$now")).toString();
var matchId = md5.convert(utf8.encode("2$now")).toString();
var sessionId = md5.convert(utf8.encode("3$now")).toString();
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"state": "Connecting"
},
"name": "StatusUpdate"
})
));
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"totalPlayers": 1,
"connectedPlayers": 1,
"state": "Waiting"
},
"name": "StatusUpdate"
})
));
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"ticketId": ticketId,
"queuedPlayers": 0,
"estimatedWaitSec": 0,
"status": {},
"state": "Queued"
},
"name": "StatusUpdate"
})
));
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"matchId": matchId,
"state": "SessionAssignment"
},
"name": "StatusUpdate"
})
));
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"matchId": matchId,
"sessionId": sessionId,
"joinDelaySec": 1
},
"name": "Play"
})
));
}
String _randomUUID() => const Uuid().v4().replaceAll("-", "").toUpperCase();

1205
lib/src/embedded/misc.dart Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
import 'package:jaguar/http/context/context.dart';
import 'package:reboot_launcher/src/embedded/utils.dart';
Map<String, Object?> getPrivacy(Context context) => {
"accountId": context.pathParams.get("accountId"),
"optOutOfPublicLeaderboards": false
};
Future<Map<String, Object?>> postPrivacy(Context context) async {
var body = await parseBody(context);
return {
"accountId": context.pathParams.get("accountId"),
"optOutOfPublicLeaderboards": body["optOutOfPublicLeaderboards"]
};
}

View File

@@ -0,0 +1,138 @@
import "dart:async";
import "dart:io";
import "package:jaguar/jaguar.dart";
import "package:reboot_launcher/src/embedded/auth.dart";
import 'package:reboot_launcher/src/embedded/misc.dart';
import 'package:reboot_launcher/src/embedded/privacy.dart';
import "package:reboot_launcher/src/embedded/storage.dart";
import 'package:reboot_launcher/src/embedded/storefront.dart';
import "package:reboot_launcher/src/embedded/version.dart";
import '../util/server.dart';
import "error.dart";
import "lightswitch.dart";
import 'matchmaking.dart';
bool _loggingCapabilities = false;
Future<Jaguar> startEmbeddedServer(String Function() ipQuery) async {
var server = _createServer(ipQuery);
await server.serve(logRequests: true);
return server;
}
Future<Jaguar> startEmbeddedMatchmaker() async {
var server = _createMatchmaker();
server.serve(logRequests: true);
return server;
}
Jaguar _createServer(String Function() ipQuery) {
var server = Jaguar(address: "127.0.0.1", port: 3551, errorWriter: EmbeddedErrorWriter());
// Version
server.getJson("/fortnite/api/version", getVersion);
server.getJson("/fortnite/api/v2/versioncheck/*", hasUpdate);
server.getJson("/fortnite/api/v2/versioncheck*", hasUpdate);
server.getJson("/fortnite/api/versioncheck*", hasUpdate);
// Auth
server.getJson("/account/api/public/account/displayName/:accountId", getAccountInfo);
server.getJson("/account/api/public/account/:accountId", getAccountInfo);
server.getJson("/account/api/public/account/:accountId/externalAuths", getExternalAuths);
server.getJson("/account/api/public/account", getAccounts);
server.delete("/account/api/oauth/sessions/kill/*", (context) => Response(statusCode: 204));
server.getJson("/account/api/oauth/verify", verifyOAuthToken);
server.postJson("/account/api/oauth/token", getOAuthToken);
server.postJson("/account/api/oauth/exchange", getExchange);
server.getJson("/account/api/epicdomains/ssodomains", getSsoDomains);
server.post("/fortnite/api/game/v2/tryPlayOnPlatform/account/*", tryPlayOnPlatform);
server.post("/datarouter/api/v1/public/data/*", (context) => Response(statusCode: 204));
server.getJson("/fortnite/api/game/v2/enabled_features", getFeatures);
server.postJson("/fortnite/api/game/v2/grant_access/*", (context) => Response(statusCode: 204));
server.postJson("/fortnite/api/game/v2/profile/:profileId/client/EquipBattleRoyaleCustomization", equipItem);
server.postJson("/fortnite/api/game/v2/profile/:profileId/client/*", getProfile);
// Storage
server.getJson("/fortnite/api/cloudstorage/system", getStorageSettings);
server.get("/fortnite/api/cloudstorage/system/:file", getStorageSetting);
server.getJson("/fortnite/api/cloudstorage/user/:accountId", getStorageAccount);
server.getJson("/fortnite/api/cloudstorage/user/:accountId/:file", getStorageFile);
server.put("/fortnite/api/cloudstorage/user/:accountId/:file", addStorageFile);
// Status
server.getJson("/lightswitch/api/service/Fortnite/status", getFortniteStatus);
server.getJson("/lightswitch/api/service/bulk/status", getBulkStatus);
// Keychain and catalog
server.get("/fortnite/api/storefront/v2/catalog", getCatalog);
server.get("/fortnite/api/storefront/v2/keychain", getKeyChain);
server.get("/catalog/api/shared/bulk/offers", getOffers);
// Matchmaking
server.get("/fortnite/api/matchmaking/session/findPlayer/*", (context) => Response(statusCode: 200));
server.getJson("/fortnite/api/game/v2/matchmakingservice/ticket/player/*", getPlayerTicket);
server.getJson("/fortnite/api/game/v2/matchmaking/account/:accountId/session/:sessionId", getSessionAccount);
server.getJson("/fortnite/api/matchmaking/session/:sessionId", (context) => getMatch(context, ipQuery));
server.post("/fortnite/api/matchmaking/session/:accountId/join", (context) => Response(statusCode: 204));
server.postJson("/fortnite/api/matchmaking/session/matchMakingRequest", (context) => getMatchmakingRequests);
// Misc
server.getJson("/api/v1/events/Fortnite/download/*", getDownload);
server.getJson("/fortnite/api/receipts/v1/account/:accountId/receipts", getReceipts);
server.getJson("/content/api/pages/*", getContentPages);
server.getJson("/friends/api/v1/:accountId/settings", getFriendsSettings);
server.getJson("/friends/api/v1/:accountId/blocklist", getFriendsBlocklist);
server.getJson("/friends/api/public/blocklist/:accountId", getFriendsBlocklist);
server.getJson("/friends/api/public/friends/:accountId", getFriendsList);
server.getJson("/friends/api/public/list/fortnite/:accountId/recentPlayers", getRecentPlayers);
server.getJson("/fortnite/api/calendar/v1/timeline", getTimeline);
server.getJson("/fortnite/api/game/v2/events/tournamentandhistory/:accountId/EU/WindowsClient", getTournamentHistory);
server.get("/waitingroom/api/waitingroom", (context) => Response(statusCode: 204));
server.postJson("/api/v1/user/setting", (context) => []);
server.getJson("/eulatracking/api/public/agreements/fn/account/*", (context) => Response(statusCode: 204));
server.getJson("/socialban/api/public/v1/:accountId", getSocialBan);
server.getJson("/party/api/v1/Fortnite/user/*", getParty);
server.getJson("/friends/api/v1/*/settings", (context) => {});
server.getJson("/friends/api/v1/*/blocklist", (context) => {});
server.getJson("/friends/api/public/friends", (context) => []);
server.getJson("/friends/api/v1/:accountId/summary", (context) => []);
server.getJson("/friends/api/public/list/fortnite/*/recentPlayers", (context) => []);
server.getJson("/friends/api/public/blocklist/*", getBlockedFriends);
// Privacy
server.getJson("/fortnite/api/game/v2/privacy/account/:accountId", getPrivacy);
server.postJson("/fortnite/api/game/v2/privacy/account/:accountId", postPrivacy);
return _addLoggingCapabilities(server);
}
Jaguar _createMatchmaker(){
var server = Jaguar(address: "127.0.0.1", port: 8080);
WebSocket? ws;
server.wsStream(
"/",
(_, input) => ws = input,
after: [(_) => queueMatchmaking(ws!)]
);
return _addLoggingCapabilities(server);
}
Jaguar _addLoggingCapabilities(Jaguar server) {
if(_loggingCapabilities){
return server;
}
server.log.onRecord.listen((line) {
stdout.writeln(line);
serverLogFile.writeAsString("$line\n", mode: FileMode.append);
});
server.onException.add((ctx, exception, trace) {
stderr.writeln("An error occurred: $exception");
serverLogFile.writeAsString("An error occurred at ${ctx.uri}: \n$exception\n$trace\n", mode: FileMode.append);
});
_loggingCapabilities = true;
return server;
}

View File

@@ -0,0 +1,111 @@
import 'dart:convert';
import 'dart:io';
import 'package:jaguar/jaguar.dart';
import 'package:jaguar/http/context/context.dart';
import 'package:crypto/crypto.dart';
import 'package:reboot_launcher/src/embedded/utils.dart';
import '../util/os.dart';
final Directory _settings = Directory("${Platform.environment["UserProfile"]}\\.reboot_launcher\\backend\\settings");
const String _engineName = "DefaultEngine.ini";
final String _engineIni = loadEmbedded("config/$_engineName").readAsStringSync();
const String _gameName = "DefaultGame.ini";
final String _gameIni = loadEmbedded("config/$_gameName").readAsStringSync();
const String _runtimeName = "DefaultRuntimeOptions.ini";
final String _runtimeIni = loadEmbedded("config/$_runtimeName").readAsStringSync();
List<Map<String, Object>> getStorageSettings(Context context) => [
_getStorageSetting(_engineName, _engineIni),
_getStorageSetting(_gameName, _gameIni),
_getStorageSetting(_runtimeName, _runtimeIni)
];
Map<String, Object> _getStorageSetting(String name, String source){
var bytes = utf8.encode(source);
return {
"uniqueFilename": name,
"filename": name,
"hash": sha1.convert(bytes).toString(),
"hash256": sha256.convert(bytes).toString(),
"length": bytes.length,
"contentType": "application/octet-stream",
"uploaded": "2020-02-23T18:35:53.967Z",
"storageType": "S3",
"storageIds": {},
"doNotCache": true
};
}
Response getStorageSetting(Context context) {
switch(context.pathParams.get("file")){
case _engineName:
return Response(body: _engineIni);
case _gameName:
return Response(body: _gameIni);
case _runtimeName:
return Response(body: _runtimeIni);
default:
return Response();
}
}
Response getStorageFile(Context context) {
if (context.pathParams.get("file")?.toLowerCase() != "clientsettings.sav") {
return Response.json(
{"error": "File not found"},
statusCode: 404
);
}
var file = _getSettingsFile(context);
return Response(
body: file.existsSync() ? file.readAsBytesSync() : null,
headers: {"content-type": "application/octet-stream"}
);
}
List<Map<String, Object?>> getStorageAccount(Context context) {
var file = _getSettingsFile(context);
if (!file.existsSync()) {
return [];
}
var content = file.readAsBytesSync();
return [{
"uniqueFilename": "ClientSettings.Sav",
"filename": "ClientSettings.Sav",
"hash": sha1.convert(content).toString(),
"hash256": sha256.convert(content).toString(),
"length": content.length,
"contentType": "application/octet-stream",
"uploaded": "2020-02-23T18:35:53.967Z",
"storageType": "S3",
"storageIds": {},
"accountId": context.pathParams.get("accountId"),
"doNotCache": true
}];
}
Future<Response> addStorageFile(Context context) async {
if(!_settings.existsSync()){
await _settings.create(recursive: true);
}
var file = _getSettingsFile(context);
await file.writeAsBytes(await context.body);
return Response(statusCode: 204);
}
File _getSettingsFile(Context context) {
if(!_settings.existsSync()){
_settings.createSync(recursive: true);
}
return File("${_settings.path}\\ClientSettings-${parseSeasonBuild(context)}.Sav");
}

View File

@@ -0,0 +1,19 @@
import 'package:jaguar/http/context/context.dart';
import 'package:jaguar/http/response/response.dart';
import 'package:reboot_launcher/src/util/os.dart';
final String _keyChain = loadEmbedded("responses/keychain.json").readAsStringSync();
final String _catalog = loadEmbedded("responses/catalog.json").readAsStringSync();
Response getCatalog(Context context) {
if (context.headers.value("user-agent")?.contains("2870186") == true) {
return Response(statusCode: 404);
}
return Response(body: _catalog, headers: {"content-type": "application/json"});
}
Response getKeyChain(Context context) => Response(body: _keyChain, headers: {"content-type": "application/json"});
Map<String, Object> getOffers(Context context) => {};

View File

@@ -0,0 +1,42 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:math';
import 'package:jaguar/http/context/context.dart';
const String _chars =
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
final Random _random = Random();
String randomString(int length) => String.fromCharCodes(
Iterable.generate(length, (_) => _chars.codeUnitAt(_random.nextInt(_chars.length))));
double parseSeasonBuild(Context context){
String? userAgent = context.headers.value("user-agent");
if (userAgent == null) {
return 1.0;
}
try {
var build = userAgent.split("Release-")[1].split("-")[0];
if (build.split(".").length == 3) {
var value = build.split(".");
return double.parse("${value[0]}.${value[1]}${value[2]}");
}
return double.parse(build);
} catch (_) {
return 2.0;
}
}
int parseSeason(Context context) => int.parse(parseSeasonBuild(context).toString().split(".")[0]);
Future<HashMap<String, String?>> parseBody(Context context) async {
var params = HashMap<String, String?>();
utf8.decode(await context.req.body)
.split("&")
.map((entry) => MapEntry(entry.substring(0, entry.indexOf("=")), entry.substring(entry.indexOf("=") + 1)))
.forEach((element) => params[element.key] = Uri.decodeQueryComponent(element.value));
return params;
}

View File

@@ -0,0 +1,41 @@
import 'package:jaguar/http/context/context.dart';
import 'package:reboot_launcher/src/util/time.dart';
Map<String, Object> getVersion(Context context) => {
"app": "fortnite",
"serverDate": "2022-11-08T18:55:52.341Z",
"overridePropertiesVersion": "unknown",
"cln": "17951730",
"build": "444",
"moduleName": "Fortnite-Core",
"buildDate": "2021-10-27T21:00:51.697Z",
"version": "18.30",
"branch": "Release-18.30",
"modules": {
"Epic-LightSwitch-AccessControlCore": {
"cln": "17237679",
"build": "b2130",
"buildDate": "2021-08-19T18:56:08.144Z",
"version": "1.0.0",
"branch": "trunk"
},
"epic-xmpp-api-v1-base": {
"cln": "5131a23c1470acbd9c94fae695ef7d899c1a41d6",
"build": "b3595",
"buildDate": "2019-07-30T09:11:06.587Z",
"version": "0.0.1",
"branch": "master"
},
"epic-common-core": {
"cln": "17909521",
"build": "3217",
"buildDate": "2021-10-25T18:41:12.486Z",
"version": "3.0",
"branch": "TRUNK"
}
}
};
Map<String, Object> hasUpdate(Context context) => {
"type": "NO_UPDATE"
};