This commit is contained in:
Alessandro Autiero
2024-06-15 17:57:17 +02:00
parent 2bf084d120
commit e24f4e97b3
144 changed files with 442 additions and 278 deletions

View File

@@ -0,0 +1,27 @@
const Express = require("express");
const express = Express.Router();
express.get("/affiliate/api/public/affiliates/slug/:slug", async (req, res) => {
const SupportedCodes = require("./../responses/SAC.json");
var ValidCode = false;
SupportedCodes.forEach(code => {
if (req.params.slug.toLowerCase() == code.toLowerCase()) {
ValidCode = true;
return res.json({
"id": code,
"slug": code,
"displayName": code,
"status": "ACTIVE",
"verified": false
});
}
})
if (ValidCode == false) {
res.status(404);
res.json({});
}
})
module.exports = express;

View File

@@ -0,0 +1,162 @@
const Express = require("express");
const express = Express.Router();
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const functions = require("./functions.js");
express.use((req, res, next) => {
// Get raw body in encoding latin1 for ClientSettings
if (req.originalUrl.toLowerCase().startsWith("/fortnite/api/cloudstorage/user/") && req.method == "PUT") {
req.rawBody = "";
req.setEncoding("latin1");
req.on("data", (chunk) => req.rawBody += chunk);
req.on("end", () => next());
}
else return next();
})
express.get("/fortnite/api/cloudstorage/system", async (req, res) => {
const memory = functions.GetVersionInfo(req);
if (memory.build >= 9.40 && memory.build <= 10.40) {
return res.status(404).end();
}
const dir = path.join(__dirname, "..", "CloudStorage")
var CloudFiles = [];
fs.readdirSync(dir).forEach(name => {
if (name.toLowerCase().endsWith(".ini")) {
const ParsedFile = fs.readFileSync(path.join(dir, name), 'utf-8');
const ParsedStats = fs.statSync(path.join(dir, name));
CloudFiles.push({
"uniqueFilename": name,
"filename": name,
"hash": crypto.createHash('sha1').update(ParsedFile).digest('hex'),
"hash256": crypto.createHash('sha256').update(ParsedFile).digest('hex'),
"length": ParsedFile.length,
"contentType": "application/octet-stream",
"uploaded": ParsedStats.mtime,
"storageType": "S3",
"storageIds": {},
"doNotCache": true
})
}
});
res.json(CloudFiles)
})
express.get("/fortnite/api/cloudstorage/system/:file", async (req, res) => {
const file = path.join(__dirname, "..", "CloudStorage", req.params.file);
if (fs.existsSync(file)) {
const ParsedFile = fs.readFileSync(file);
return res.status(200).send(ParsedFile).end();
} else {
res.status(200);
res.end();
}
})
express.get("/fortnite/api/cloudstorage/user/*/:file", async (req, res) => {
try {
if (!fs.existsSync(path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings"))) {
fs.mkdirSync(path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings"));
}
} catch (err) {}
res.set("Content-Type", "application/octet-stream")
if (req.params.file.toLowerCase() != "clientsettings.sav") {
return res.status(404).json({
"error": "file not found"
});
}
const memory = functions.GetVersionInfo(req);
var currentBuildID = memory.CL;
let file;
if (process.env.LOCALAPPDATA) file = path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings", `ClientSettings-${currentBuildID}.Sav`);
else file = path.join(__dirname, "..", "ClientSettings", `ClientSettings-${currentBuildID}.Sav`);
if (fs.existsSync(file)) {
const ParsedFile = fs.readFileSync(file);
return res.status(200).send(ParsedFile).end();
} else {
res.status(200);
res.end();
}
})
express.get("/fortnite/api/cloudstorage/user/:accountId", async (req, res) => {
try {
if (!fs.existsSync(path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings"))) {
fs.mkdirSync(path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings"));
}
} catch (err) {}
res.set("Content-Type", "application/json")
const memory = functions.GetVersionInfo(req);
var currentBuildID = memory.CL;
let file;
if (process.env.LOCALAPPDATA) file = path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings", `ClientSettings-${currentBuildID}.Sav`);
else file = path.join(__dirname, "..", "ClientSettings", `ClientSettings-${currentBuildID}.Sav`);
if (fs.existsSync(file)) {
const ParsedFile = fs.readFileSync(file, 'latin1');
const ParsedStats = fs.statSync(file);
return res.json([{
"uniqueFilename": "ClientSettings.Sav",
"filename": "ClientSettings.Sav",
"hash": crypto.createHash('sha1').update(ParsedFile).digest('hex'),
"hash256": crypto.createHash('sha256').update(ParsedFile).digest('hex'),
"length": Buffer.byteLength(ParsedFile),
"contentType": "application/octet-stream",
"uploaded": ParsedStats.mtime,
"storageType": "S3",
"storageIds": {},
"accountId": req.params.accountId,
"doNotCache": true
}]);
} else {
return res.json([]);
}
})
express.put("/fortnite/api/cloudstorage/user/*/:file", async (req, res) => {
try {
if (!fs.existsSync(path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings"))) {
fs.mkdirSync(path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings"));
}
} catch (err) {}
if (req.params.file.toLowerCase() != "clientsettings.sav") {
return res.status(404).json({
"error": "file not found"
});
}
const memory = functions.GetVersionInfo(req);
var currentBuildID = memory.CL;
let file;
if (process.env.LOCALAPPDATA) file = path.join(process.env.LOCALAPPDATA, "LawinServer", "ClientSettings", `ClientSettings-${currentBuildID}.Sav`);
else file = path.join(__dirname, "..", "ClientSettings", `ClientSettings-${currentBuildID}.Sav`);
fs.writeFileSync(file, req.rawBody, 'latin1');
res.status(204).end();
})
module.exports = express;

View File

@@ -0,0 +1,24 @@
const Express = require("express");
const express = Express.Router();
const functions = require("./functions.js");
express.get("/content/api/pages/*", async (req, res) => {
const contentpages = functions.getContentPages(req);
res.json(contentpages)
})
express.post("/api/v1/fortnite-br/surfaces/motd/target", async (req, res) => {
const motdTarget = JSON.parse(JSON.stringify(require("./../responses/Athena/motdTarget.json")));
try {
motdTarget.contentItems.forEach(item => {
item.contentFields.title = item.contentFields.title[req.body.language];
item.contentFields.body = item.contentFields.body[req.body.language];
})
} catch (err) {}
res.json(motdTarget)
})
module.exports = express;

View File

@@ -0,0 +1,45 @@
const Express = require("express");
const express = Express.Router();
const discovery = require("./../responses/Athena/Discovery/discovery_frontend.json");
express.post("*/discovery/surface/*", async (req, res) => {
res.json(discovery);
})
express.post("/links/api/fn/mnemonic", async (req, res) => {
var MnemonicArray = [];
for (var i in discovery.Panels[0].Pages[0].results) {
MnemonicArray.push(discovery.Panels[0].Pages[0].results[i].linkData)
}
res.json(MnemonicArray);
})
express.get("/links/api/fn/mnemonic/:playlist/related", async (req, res) => {
var response = {
"parentLinks": [],
"links": {}
};
if (req.params.playlist) {
for (var i in discovery.Panels[0].Pages[0].results) {
var linkData = discovery.Panels[0].Pages[0].results[i].linkData;
if (linkData.mnemonic == req.params.playlist) {
response.links[req.params.playlist] = linkData;
}
}
}
res.json(response);
})
express.get("/links/api/fn/mnemonic/*", async (req, res) => {
for (var i in discovery.Panels[0].Pages[0].results) {
if (discovery.Panels[0].Pages[0].results[i].linkData.mnemonic == req.url.split("/").slice(-1)[0]) {
res.json(discovery.Panels[0].Pages[0].results[i].linkData);
}
}
})
module.exports = express;

View File

@@ -0,0 +1,122 @@
const Express = require("express");
const express = Express.Router();
const fs = require("fs");
const friendslist = require("./../responses/friendslist.json");
const friendslist2 = require("./../responses/friendslist2.json");
const functions = require("./functions.js");
express.get("/friends/api/v1/*/settings", async (req, res) => {
res.json({})
})
express.get("/friends/api/v1/*/blocklist", async (req, res) => {
res.json([])
})
express.get("/friends/api/public/friends/:accountId", async (req, res) => {
const memory = functions.GetVersionInfo(req);
if (!friendslist.find(i => i.accountId == req.params.accountId)) {
var FriendObject = {
"accountId": req.params.accountId,
"status": "ACCEPTED",
"direction": "OUTBOUND",
"created": new Date().toISOString(),
"favorite": false
};
friendslist.push(FriendObject)
friendslist2.friends.push({
"accountId": FriendObject.accountId,
"groups": [],
"mutual": 0,
"alias": "",
"note": "",
"favorite": FriendObject.favorite,
"created": FriendObject.created
})
functions.sendXmppMessageToAll({
"payload": FriendObject,
"type": "com.epicgames.friends.core.apiobjects.Friend",
"timestamp": FriendObject.created
})
functions.sendXmppMessageToAll({
"type": "FRIENDSHIP_REQUEST",
"timestamp": FriendObject.created,
"from": FriendObject.accountId,
"status": FriendObject.status
})
fs.writeFileSync("./responses/friendslist.json", JSON.stringify(friendslist, null, 2));
fs.writeFileSync("./responses/friendslist2.json", JSON.stringify(friendslist2, null, 2));
}
if (memory.season >= 7) {
var friends = JSON.parse(JSON.stringify(friendslist))
friends.splice(friendslist.findIndex(i => i.accountId == req.params.accountId), 1)
return res.json(friends);
}
res.json(friendslist)
})
express.get("/friends/api/v1/:accountId/summary", async (req, res) => {
if (!friendslist2.friends.find(i => i.accountId == req.params.accountId)) {
var FriendObject = {
"accountId": req.params.accountId,
"groups": [],
"mutual": 0,
"alias": "",
"note": "",
"favorite": false,
"created": new Date().toISOString()
};
friendslist2.friends.push(FriendObject)
friendslist.push({
"accountId": FriendObject.accountId,
"status": "ACCEPTED",
"direction": "OUTBOUND",
"created": FriendObject.created,
"favorite": FriendObject.favorite
})
functions.sendXmppMessageToAll({
"payload": {
"accountId": FriendObject.accountId,
"status": "ACCEPTED",
"direction": "OUTBOUND",
"created": FriendObject.created,
"favorite": FriendObject.favorite
},
"type": "com.epicgames.friends.core.apiobjects.Friend",
"timestamp": FriendObject.created
})
functions.sendXmppMessageToAll({
"type": "FRIENDSHIP_REQUEST",
"timestamp": FriendObject.created,
"from": FriendObject.accountId,
"status": "ACCEPTED"
})
fs.writeFileSync("./responses/friendslist.json", JSON.stringify(friendslist, null, 2));
fs.writeFileSync("./responses/friendslist2.json", JSON.stringify(friendslist2, null, 2));
}
res.json(friendslist2)
})
express.get("/friends/api/public/list/fortnite/*/recentPlayers", async (req, res) => {
res.json([])
})
express.get("/friends/api/public/blocklist/*", async (req, res) => {
res.json({
"blockedUsers": []
})
})
module.exports = express;

View File

@@ -0,0 +1,374 @@
const XMLBuilder = require("xmlbuilder");
const uuid = require("uuid");
async function sleep(ms) {
await new Promise((resolve, reject) => {
setTimeout(resolve, ms);
})
}
function GetVersionInfo(req) {
var memory = {
season: 0,
build: 0.0,
CL: "",
lobby: ""
}
if (req.headers["user-agent"])
{
var CL = "";
try {
var BuildID = req.headers["user-agent"].split("-")[3].split(",")[0]
if (!Number.isNaN(Number(BuildID))) {
CL = BuildID;
}
if (Number.isNaN(Number(BuildID))) {
var BuildID = req.headers["user-agent"].split("-")[3].split(" ")[0]
if (!Number.isNaN(Number(BuildID))) {
CL = BuildID;
}
}
} catch (err) {
try {
var BuildID = req.headers["user-agent"].split("-")[1].split("+")[0]
if (!Number.isNaN(Number(BuildID))) {
CL = BuildID;
}
} catch (err) {}
}
try {
var Build = req.headers["user-agent"].split("Release-")[1].split("-")[0];
if (Build.split(".").length == 3) {
Value = Build.split(".");
Build = Value[0] + "." + Value[1] + Value[2];
}
memory.season = Number(Build.split(".")[0]);
memory.build = Number(Build);
memory.CL = CL;
memory.lobby = `LobbySeason${memory.season}`;
if (Number.isNaN(memory.season)) {
throw new Error();
}
} catch (err) {
memory.season = 2;
memory.build = 2.0;
memory.CL = CL;
memory.lobby = "LobbyWinterDecor";
}
}
return memory;
}
function getItemShop() {
const catalog = JSON.parse(JSON.stringify(require("./../responses/catalog.json")));
const CatalogConfig = require("./../Config/catalog_config.json");
try {
for (var value in CatalogConfig) {
if (Array.isArray(CatalogConfig[value].itemGrants)) {
if (CatalogConfig[value].itemGrants.length != 0) {
const CatalogEntry = {"devName":"","offerId":"","fulfillmentIds":[],"dailyLimit":-1,"weeklyLimit":-1,"monthlyLimit":-1,"categories":[],"prices":[{"currencyType":"MtxCurrency","currencySubType":"","regularPrice":0,"finalPrice":0,"saleExpiration":"9999-12-02T01:12:00Z","basePrice":0}],"meta":{"SectionId":"Featured","TileSize":"Small"},"matchFilter":"","filterWeight":0,"appStoreId":[],"requirements":[],"offerType":"StaticPrice","giftInfo":{"bIsEnabled":false,"forcedGiftBoxTemplateId":"","purchaseRequirements":[],"giftRecordIds":[]},"refundable":true,"metaInfo":[{"key":"SectionId","value":"Featured"},{"key":"TileSize","value":"Small"}],"displayAssetPath":"","itemGrants":[],"sortPriority":0,"catalogGroupPriority":0};
if (value.toLowerCase().startsWith("daily")) {
catalog.storefronts.forEach((storefront, i) => {
if (storefront.name == "BRDailyStorefront") {
CatalogEntry.requirements = [];
CatalogEntry.itemGrants = [];
for (var x in CatalogConfig[value].itemGrants) {
if (typeof CatalogConfig[value].itemGrants[x] == "string") {
if (CatalogConfig[value].itemGrants[x].length != 0) {
CatalogEntry.devName = CatalogConfig[value].itemGrants[0]
CatalogEntry.offerId = CatalogConfig[value].itemGrants[0]
CatalogEntry.requirements.push({ "requirementType": "DenyOnItemOwnership", "requiredId": CatalogConfig[value].itemGrants[x], "minQuantity": 1 })
CatalogEntry.itemGrants.push({ "templateId": CatalogConfig[value].itemGrants[x], "quantity": 1 });
}
}
}
CatalogEntry.prices[0].basePrice = CatalogConfig[value].price
CatalogEntry.prices[0].regularPrice = CatalogConfig[value].price
CatalogEntry.prices[0].finalPrice = CatalogConfig[value].price
// Make featured items appear on the left side of the screen
CatalogEntry.sortPriority = -1
if (CatalogEntry.itemGrants.length != 0) {
catalog.storefronts[i].catalogEntries.push(CatalogEntry);
}
}
})
}
if (value.toLowerCase().startsWith("featured")) {
catalog.storefronts.forEach((storefront, i) => {
if (storefront.name == "BRWeeklyStorefront") {
CatalogEntry.requirements = [];
CatalogEntry.itemGrants = [];
for (var x in CatalogConfig[value].itemGrants) {
if (typeof CatalogConfig[value].itemGrants[x] == "string") {
if (CatalogConfig[value].itemGrants[x].length != 0) {
CatalogEntry.devName = CatalogConfig[value].itemGrants[0]
CatalogEntry.offerId = CatalogConfig[value].itemGrants[0]
CatalogEntry.requirements.push({ "requirementType": "DenyOnItemOwnership", "requiredId": CatalogConfig[value].itemGrants[x], "minQuantity": 1 })
CatalogEntry.itemGrants.push({ "templateId": CatalogConfig[value].itemGrants[x], "quantity": 1 });
}
}
}
CatalogEntry.prices[0].basePrice = CatalogConfig[value].price
CatalogEntry.prices[0].regularPrice = CatalogConfig[value].price
CatalogEntry.prices[0].finalPrice = CatalogConfig[value].price
CatalogEntry.meta.TileSize = "Normal"
CatalogEntry.metaInfo[1].value = "Normal"
if (CatalogEntry.itemGrants.length != 0) {
catalog.storefronts[i].catalogEntries.push(CatalogEntry);
}
}
})
}
}
}
}
} catch (err) {}
return catalog;
}
function getTheater(req) {
const memory = GetVersionInfo(req);
var theater = JSON.stringify(require("./../responses/Campaign/worldstw.json"));
var Season = "Season" + memory.season;
try {
if (memory.build >= 15.30) {
theater = theater.replace(/\/Game\//ig, "\/SaveTheWorld\/");
theater = theater.replace(/\"DataTable\'\/SaveTheWorld\//ig, "\"DataTable\'\/Game\/");
}
var date = new Date();
var hour = date.getHours();
// Set the 24-hour StW mission refresh date for version season 9 and above
if (memory.season >= 9) {
date.setHours(23, 59, 59, 999);
} else {
// Set the 6-hour StW mission refresh date for versions below season 9
if (hour < 6) {
date.setHours(5, 59, 59, 999);
} else if (hour < 12) {
date.setHours(11, 59, 59, 999);
} else if (hour < 18) {
date.setHours(17, 59, 59, 999);
} else {
date.setHours(23, 59, 59, 999);
}
}
date = date.toISOString();
theater = theater.replace(/2017-07-25T23:59:59.999Z/ig, date);
} catch (err) {}
theater = JSON.parse(theater)
if (theater.hasOwnProperty("Seasonal")) {
if (theater.Seasonal.hasOwnProperty(Season)) {
theater.theaters = theater.theaters.concat(theater.Seasonal[Season].theaters);
theater.missions = theater.missions.concat(theater.Seasonal[Season].missions);
theater.missionAlerts = theater.missionAlerts.concat(theater.Seasonal[Season].missionAlerts);
}
delete theater.Seasonal;
}
return theater;
}
function getContentPages(req) {
const memory = GetVersionInfo(req);
const contentpages = JSON.parse(JSON.stringify(require("./../responses/contentpages.json")));
var Language = "en";
if (req.headers["accept-language"]) {
if (req.headers["accept-language"].includes("-") && req.headers["accept-language"] != "es-419" && req.headers["accept-language"] != "pt-BR") {
Language = req.headers["accept-language"].split("-")[0];
} else {
Language = req.headers["accept-language"];
}
}
const modes = ["saveTheWorldUnowned", "battleRoyale", "creative", "saveTheWorld"];
const news = ["savetheworldnews", "battleroyalenews"]
const motdnews = ["battleroyalenews", "battleroyalenewsv2"]
try {
modes.forEach(mode => {
contentpages.subgameselectdata[mode].message.title = contentpages.subgameselectdata[mode].message.title[Language]
contentpages.subgameselectdata[mode].message.body = contentpages.subgameselectdata[mode].message.body[Language]
})
} catch (err) {}
try {
if (memory.build < 5.30) {
news.forEach(mode => {
contentpages[mode].news.messages[0].image = "https://fortnite-public-service-prod11.ol.epicgames.com/images/discord-s.png";
contentpages[mode].news.messages[1].image = "https://fortnite-public-service-prod11.ol.epicgames.com/images/lawin-s.png";
})
}
} catch (err) {}
try {
motdnews.forEach(news => {
contentpages[news].news.motds.forEach(motd => {
motd.title = motd.title[Language];
motd.body = motd.body[Language];
})
})
} catch (err) {}
try {
const backgrounds = contentpages.dynamicbackgrounds.backgrounds.backgrounds;
const season = `season${memory.season}${memory.season >= 21 ? "00" : ""}`;
backgrounds[0].stage = season;
backgrounds[1].stage = season;
if (memory.season == 10) {
backgrounds[0].stage = "seasonx";
backgrounds[1].stage = "seasonx";
} else if (memory.build == 11.31 || memory.build == 11.40) {
backgrounds[0].stage = "Winter19";
backgrounds[1].stage = "Winter19";
} else if (memory.build == 19.01) {
backgrounds[0].stage = "winter2021";
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/t-bp19-lobby-xmas-2048x1024-f85d2684b4af.png";
contentpages.subgameinfo.battleroyale.image = "https://cdn2.unrealengine.com/19br-wf-subgame-select-512x1024-16d8bb0f218f.jpg";
contentpages.specialoffervideo.bSpecialOfferEnabled = "true";
} else if (memory.season == 20) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/t-bp20-lobby-2048x1024-d89eb522746c.png";
if (memory.build == 20.40) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/t-bp20-40-armadillo-glowup-lobby-2048x2048-2048x2048-3b83b887cc7f.jpg";
}
} else if (memory.season == 21) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/s21-lobby-background-2048x1024-2e7112b25dc3.jpg";
if (memory.build == 21.10) {
backgrounds[0].stage = "season2100";
}
if (memory.build == 21.30) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/nss-lobbybackground-2048x1024-f74a14565061.jpg";
backgrounds[0].stage = "season2130";
}
} else if (memory.season == 22) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/t-bp22-lobby-square-2048x2048-2048x2048-e4e90c6e8018.jpg";
} else if (memory.season == 23) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/t-bp23-lobby-2048x1024-2048x1024-26f2c1b27f63.png";
if (memory.build == 23.10) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/t-bp23-winterfest-lobby-square-2048x2048-2048x2048-277a476e5ca6.png";
contentpages.specialoffervideo.bSpecialOfferEnabled = "true";
}
} else if (memory.season == 24) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/t-ch4s2-bp-lobby-4096x2048-edde08d15f7e.jpg"
}
else if (memory.season == 25) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/s25-lobby-4k-4096x2048-4a832928e11f.jpg";
backgrounds[1].backgroundimage = "https://cdn2.unrealengine.com/fn-shop-ch4s3-04-1920x1080-785ce1d90213.png";
if (memory.build == 25.11) {
backgrounds[0].backgroundimage = "https://cdn2.unrealengine.com/t-s25-14dos-lobby-4096x2048-2be24969eee3.jpg";
}
}
} catch (err) {}
return contentpages;
}
function MakeSurvivorAttributes(templateId) {
const SurvivorData = require("./../responses/Campaign/survivorData.json");
var SurvivorAttributes = {
"level": 1,
"item_seen": false,
"squad_slot_idx": -1,
"building_slot_used": -1
};
if (SurvivorData.fixedAttributes.hasOwnProperty(templateId)) {
SurvivorAttributes = {...SurvivorAttributes, ...SurvivorData.fixedAttributes[templateId]};
}
if (!SurvivorAttributes.hasOwnProperty("gender")) {
SurvivorAttributes.gender = (Math.floor(Math.random() * (1 - 3) + 3)).toString(); // Set a random survivor gender ("1" = male, "2" = female)
}
if (!SurvivorAttributes.hasOwnProperty("managerSynergy")) {
var randomNumber = Math.floor(Math.random() * SurvivorData.bonuses.length);
SurvivorAttributes.set_bonus = SurvivorData.bonuses[randomNumber];
}
if (!SurvivorAttributes.hasOwnProperty("personality")) {
var randomNumber = Math.floor(Math.random() * SurvivorData.personalities.length);
SurvivorAttributes.personality = SurvivorData.personalities[randomNumber];
}
if (!SurvivorAttributes.hasOwnProperty("portrait")) {
portraitFactor = SurvivorAttributes.personality;
if (SurvivorAttributes.hasOwnProperty("managerSynergy")) {
portraitFactor = SurvivorAttributes.managerSynergy;
}
var gender = SurvivorAttributes.gender
var randomNumber = Math.floor(Math.random() * SurvivorData.portraits[portraitFactor][gender].length);
SurvivorAttributes.portrait = SurvivorData.portraits[portraitFactor][gender][randomNumber];
}
return SurvivorAttributes;
}
function MakeID() {
return uuid.v4();
}
function sendXmppMessageToAll(body) {
if (global.Clients) {
if (typeof body == "object") body = JSON.stringify(body);
global.Clients.forEach(ClientData => {
ClientData.client.send(XMLBuilder.create("message")
.attribute("from", "xmpp-admin@prod.ol.epicgames.com")
.attribute("xmlns", "jabber:client")
.attribute("to", ClientData.jid)
.element("body", `${body}`).up().toString());
});
}
}
function DecodeBase64(str) {
return Buffer.from(str, 'base64').toString()
}
module.exports = {
sleep,
GetVersionInfo,
getItemShop,
getTheater,
getContentPages,
MakeSurvivorAttributes,
MakeID,
sendXmppMessageToAll,
DecodeBase64
}

View File

@@ -0,0 +1,47 @@
const Express = require("express");
const express = Express.Router();
express.get("/lightswitch/api/service/Fortnite/status", async (req, res) => {
res.json({
"serviceInstanceId": "fortnite",
"status": "UP",
"message": "Fortnite is online",
"maintenanceUri": null,
"overrideCatalogIds": [
"a7f138b2e51945ffbfdacc1af0541053"
],
"allowedActions": [],
"banned": false,
"launcherInfoDTO": {
"appName": "Fortnite",
"catalogItemId": "4fe75bbc5a674f4f9b356b5c90567da5",
"namespace": "fn"
}
});
})
express.get("/lightswitch/api/service/bulk/status", async (req, res) => {
res.json(
[{
"serviceInstanceId": "fortnite",
"status": "UP",
"message": "fortnite is up.",
"maintenanceUri": null,
"overrideCatalogIds": [
"a7f138b2e51945ffbfdacc1af0541053"
],
"allowedActions": [
"PLAY",
"DOWNLOAD"
],
"banned": false,
"launcherInfoDTO": {
"appName": "Fortnite",
"catalogItemId": "4fe75bbc5a674f4f9b356b5c90567da5",
"namespace": "fn"
}
}]
)
})
module.exports = express;

494
backend/structure/main.js Normal file
View File

@@ -0,0 +1,494 @@
const Express = require("express");
const express = Express.Router();
const fs = require("fs");
const path = require("path");
const functions = require("./functions.js");
express.get("/clearitemsforshop", async (req, res) => {
res.set("Content-Type", "text/plain");
const athena = require("./../profiles/athena.json");
const CatalogConfig = require("./../Config/catalog_config.json");
var StatChanged = false;
for (var value in CatalogConfig) {
for (var i in CatalogConfig[value].itemGrants) {
if (Array.isArray(CatalogConfig[value].itemGrants)) {
for (var key in athena.items) {
if (typeof CatalogConfig[value].itemGrants[i] == "string") {
if (CatalogConfig[value].itemGrants[i].length != 0) {
if (CatalogConfig[value].itemGrants[i].toLowerCase() == athena.items[key].templateId.toLowerCase()) {
delete athena.items[key]
StatChanged = true;
}
}
}
}
}
}
}
if (StatChanged == true) {
athena.rvn += 1;
athena.commandRevision += 1;
fs.writeFileSync("./profiles/athena.json", JSON.stringify(athena, null, 2));
res.send('Success');
} else {
res.send('Failed, there are no items to remove')
}
})
express.get("/eulatracking/api/shared/agreements/fn*", async (req, res) => {
res.json({})
})
express.get("/fortnite/api/game/v2/friendcodes/*/epic", async (req, res) => {
res.json([{
"codeId": "L4WINS3RV3R",
"codeType": "CodeToken:FounderFriendInvite",
"dateCreated": "2024-04-02T21:37:00.420Z"
},
{
"codeId": "playeereq",
"codeType": "CodeToken:FounderFriendInvite_XBOX",
"dateCreated": "2024-04-02T21:37:00.420Z"
},
{
"codeId": "lawinscodelol",
"codeType": "CodeToken:MobileInvite",
"dateCreated": "2024-04-02T21:37:00.420Z"
}])
})
express.get("/launcher/api/public/distributionpoints/", async (req, res) => {
res.json({
"distributions": [
"https://epicgames-download1.akamaized.net/",
"https://download.epicgames.com/",
"https://download2.epicgames.com/",
"https://download3.epicgames.com/",
"https://download4.epicgames.com/",
"https://lawinserver.ol.epicgames.com/"
]
});
})
express.get("/launcher/api/public/assets/*", async (req, res) => {
res.json({
"appName": "FortniteContentBuilds",
"labelName": "LawinServer",
"buildVersion": "++Fortnite+Release-20.00-CL-19458861-Windows",
"catalogItemId": "5cb97847cee34581afdbc445400e2f77",
"expires": "9999-12-31T23:59:59.999Z",
"items": {
"MANIFEST": {
"signature": "LawinServer",
"distribution": "https://lawinserver.ol.epicgames.com/",
"path": "Builds/Fortnite/Content/CloudDir/LawinServer.manifest",
"hash": "55bb954f5596cadbe03693e1c06ca73368d427f3",
"additionalDistributions": []
},
"CHUNKS": {
"signature": "LawinServer",
"distribution": "https://lawinserver.ol.epicgames.com/",
"path": "Builds/Fortnite/Content/CloudDir/LawinServer.manifest",
"additionalDistributions": []
}
},
"assetId": "FortniteContentBuilds"
});
})
express.get("/Builds/Fortnite/Content/CloudDir/*.manifest", async (req, res) => {
res.set("Content-Type", "application/octet-stream")
const manifest = fs.readFileSync(path.join(__dirname, "..", "responses", "CloudDir", "LawinServer.manifest"));
res.status(200).send(manifest).end();
})
express.get("/Builds/Fortnite/Content/CloudDir/*.chunk", async (req, res) => {
res.set("Content-Type", "application/octet-stream")
const chunk = fs.readFileSync(path.join(__dirname, "..", "responses", "CloudDir", "LawinServer.chunk"));
res.status(200).send(chunk).end();
})
express.get("/Builds/Fortnite/Content/CloudDir/*.ini", async (req, res) => {
const ini = fs.readFileSync(path.join(__dirname, "..", "responses", "CloudDir", "Full.ini"));
res.status(200).send(ini).end();
})
express.post("/fortnite/api/game/v2/grant_access/*", async (req, res) => {
res.json({});
res.status(204);
})
express.post("/api/v1/user/setting", async (req, res) => {
res.json([]);
})
express.get("/waitingroom/api/waitingroom", async (req, res) => {
res.status(204);
res.end();
})
express.get("/socialban/api/public/v1/*", async (req, res) => {
res.json({
"bans": [],
"warnings": []
});
})
express.get("/fortnite/api/game/v2/events/tournamentandhistory/*/EU/WindowsClient", async (req, res) => {
res.json({});
})
express.get("/fortnite/api/statsv2/account/:accountId", async (req, res) => {
res.json({
"startTime": 0,
"endTime": 0,
"stats": {},
"accountId": req.params.accountId
});
})
express.get("/statsproxy/api/statsv2/account/:accountId", async (req, res) => {
res.json({
"startTime": 0,
"endTime": 0,
"stats": {},
"accountId": req.params.accountId
});
})
express.get("/fortnite/api/stats/accountId/:accountId/bulk/window/alltime", async (req, res) => {
res.json({
"startTime": 0,
"endTime": 0,
"stats": {},
"accountId": req.params.accountId
})
})
express.post("/fortnite/api/feedback/*", async (req, res) => {
res.status(200);
res.end();
})
express.post("/fortnite/api/statsv2/query", async (req, res) => {
res.json([]);
})
express.post("/statsproxy/api/statsv2/query", async (req, res) => {
res.json([]);
})
express.post("/fortnite/api/game/v2/events/v2/setSubgroup/*", async (req, res) => {
res.status(204);
res.end();
})
express.get("/fortnite/api/game/v2/enabled_features", async (req, res) => {
res.json([])
})
express.get("/api/v1/events/Fortnite/download/*", async (req, res) => {
res.json({})
})
express.get("/fortnite/api/game/v2/twitch/*", async (req, res) => {
res.status(200);
res.end();
})
express.get("/fortnite/api/game/v2/world/info", async (req, res) => {
const worldstw = functions.getTheater(req);
res.json(worldstw)
})
express.post("/fortnite/api/game/v2/chat/*/*/*/pc", async (req, res) => {
res.json({ "GlobalChatRooms": [{"roomName":"lawinserverglobal"}] })
})
express.post("/fortnite/api/game/v2/chat/*/recommendGeneralChatRooms/pc", async (req, res) => {
res.json({})
})
express.get("/presence/api/v1/_/*/last-online", async (req, res) => {
res.json({})
})
express.get("/fortnite/api/receipts/v1/account/*/receipts", async (req, res) => {
res.json([])
})
express.get("/fortnite/api/game/v2/leaderboards/cohort/:accountId", async (req, res) => {
res.json({
"accountId": req.params.accountId,
"cohortAccounts": [
req.params.accountId,
"Lawin",
"TI93",
"PRO100KatYT",
"Playeereq",
"Matteoki"
],
"expiresAt": "9999-12-31T00:00:00.000Z",
"playlist": req.query.playlist
})
})
express.post("/fortnite/api/leaderboards/type/group/stat/:statName/window/:statWindow", async (req, res) => {
var entries = [];
for (var i = 0; i < req.body.length; i++) {
entries.push({
"accountId": req.body[i],
"value": Math.floor(Math.random() * 68) + 1
})
}
res.json({
"entries": entries,
"statName": req.params.statName,
"statWindow": req.params.statWindow
})
res.end();
});
express.post("/fortnite/api/leaderboards/type/global/stat/:statName/window/:statWindow", async (req, res) => {
var HeroNames = [
"Hawk",
"Banshee",
"Wildcat",
"Jonsey",
"Spitfire",
"Ramirez",
"Headhunter",
"Renegade",
"Harper",
"Knox",
"Hype",
"Bull",
"Hazard",
"Penny",
"Izza",
"Kyle",
"Luna",
"Crash",
"Edge",
"Scorpion",
"Scorch",
"Ken",
"Mari",
"Sarah",
"Grizzly",
"Eagle Eye",
"Southie",
"A.C.",
"Buzz",
"Quinn",
"Jess",
"Deadeye"
]
var entries = [];
for (var i = 0; i < HeroNames.length; i++) {
entries.push({
"accountId": HeroNames[i],
"value": Math.floor(Math.random() * 68) + 1
})
}
res.json({
"entries": entries,
"statName": req.params.statName,
"statWindow": req.params.statWindow
})
res.end();
});
express.get("/fortnite/api/game/v2/homebase/allowed-name-chars", async (req, res) => {
res.json({
"ranges": [
48,
57,
65,
90,
97,
122,
192,
255,
260,
265,
280,
281,
286,
287,
304,
305,
321,
324,
346,
347,
350,
351,
377,
380,
1024,
1279,
1536,
1791,
4352,
4607,
11904,
12031,
12288,
12351,
12352,
12543,
12592,
12687,
12800,
13055,
13056,
13311,
13312,
19903,
19968,
40959,
43360,
43391,
44032,
55215,
55216,
55295,
63744,
64255,
65072,
65103,
65281,
65470,
131072,
173791,
194560,
195103
],
"singlePoints": [
32,
39,
45,
46,
95,
126
],
"excludedPoints": [
208,
215,
222,
247
]
})
})
express.post("/datarouter/api/v1/public/data", async (req, res) => {
res.status(204);
res.end();
})
express.post("/api/v1/assets/Fortnite/*/*", async (req, res) => {
if (req.body.hasOwnProperty("FortCreativeDiscoverySurface") && req.body.FortCreativeDiscoverySurface == 0) {
const discovery_api_assets = require("./../responses/Athena/Discovery/discovery_api_assets.json");
res.json(discovery_api_assets)
}
else {
res.json({
"FortCreativeDiscoverySurface": {
"meta": {
"promotion": req.body.FortCreativeDiscoverySurface || 0
},
"assets": {}
}
})
}
})
express.get("/region", async (req, res) => {
res.json({
"continent": {
"code": "EU",
"geoname_id": 6255148,
"names": {
"de": "Europa",
"en": "Europe",
"es": "Europa",
"fr": "Europe",
"ja": "ヨーロッパ",
"pt-BR": "Europa",
"ru": "Европа",
"zh-CN": "欧洲"
}
},
"country": {
"geoname_id": 2635167,
"is_in_european_union": false,
"iso_code": "GB",
"names": {
"de": "UK",
"en": "United Kingdom",
"es": "RU",
"fr": "Royaume Uni",
"ja": "英国",
"pt-BR": "Reino Unido",
"ru": "Британия",
"zh-CN": "英国"
}
},
"subdivisions": [
{
"geoname_id": 6269131,
"iso_code": "ENG",
"names": {
"de": "England",
"en": "England",
"es": "Inglaterra",
"fr": "Angleterre",
"ja": "イングランド",
"pt-BR": "Inglaterra",
"ru": "Англия",
"zh-CN": "英格兰"
}
},
{
"geoname_id": 3333157,
"iso_code": "KEC",
"names": {
"en": "Royal Kensington and Chelsea"
}
}
]
})
})
// Parental Controls
express.all("/v1/epic-settings/public/users/*/values", async (req, res) => {
res.json({})
})
express.get("/fortnite/api/game/v2/br-inventory/account/*", async (req, res) => {
res.json({
"stash": {
"globalcash": 5000
}
})
})
module.exports = express;

View File

@@ -0,0 +1,77 @@
const functions = require("./functions.js");
module.exports = async (ws) => {
const ticketId = functions.MakeID().replace(/-/gi, "");
const matchId = functions.MakeID().replace(/-/gi, "");
const sessionId = functions.MakeID().replace(/-/gi, "");
Connecting();
Waiting();
Queued();
SessionAssignment();
Join();
function Connecting() {
ws.send(
JSON.stringify({
payload: {
state: "Connecting",
},
name: "StatusUpdate",
})
);
}
function Waiting() {
ws.send(
JSON.stringify({
payload: {
totalPlayers: 1,
connectedPlayers: 1,
state: "Waiting",
},
name: "StatusUpdate",
})
);
}
function Queued() {
ws.send(
JSON.stringify({
payload: {
ticketId: ticketId,
queuedPlayers: 0,
estimatedWaitSec: 0,
status: {},
state: "Queued",
},
name: "StatusUpdate",
})
);
}
function SessionAssignment() {
ws.send(
JSON.stringify({
payload: {
matchId: matchId,
state: "SessionAssignment",
},
name: "StatusUpdate",
})
);
}
function Join() {
ws.send(
JSON.stringify({
payload: {
matchId: matchId,
sessionId: sessionId,
joinDelaySec: 1,
},
name: "Play",
})
);
}
};

View File

@@ -0,0 +1,87 @@
const Express = require("express");
const express = Express.Router();
const fs = require("fs");
const path = require("path");
const iniparser = require("ini");
const config = iniparser.parse(fs.readFileSync(path.join(__dirname, "..", "Config", "config.ini")).toString());
const functions = require("./functions.js");
express.get("/fortnite/api/matchmaking/session/findPlayer/*", async (req, res) => {
res.status(200);
res.end();
})
express.get("/fortnite/api/game/v2/matchmakingservice/ticket/player/*", async (req, res) => {
res.cookie("currentbuildUniqueId", req.query.bucketId.split(":")[0]);
res.json({
"serviceUrl": "ws://127.0.0.1:80",
"ticketType": "mms-player",
"payload": "69=",
"signature": "420="
})
res.end();
})
express.get("/fortnite/api/game/v2/matchmaking/account/:accountId/session/:sessionId", async (req, res) => {
res.json({
"accountId": req.params.accountId,
"sessionId": req.params.sessionId,
"key": "AOJEv8uTFmUh7XM2328kq9rlAzeQ5xzWzPIiyKn2s7s="
})
})
express.get("/fortnite/api/matchmaking/session/:session_id", async (req, res) => {
res.json({
"id": req.params.session_id,
"ownerId": functions.MakeID().replace(/-/ig, "").toUpperCase(),
"ownerName": "[DS]fortnite-liveeugcec1c2e30ubrcore0a-z8hj-1968",
"serverName": "[DS]fortnite-liveeugcec1c2e30ubrcore0a-z8hj-1968",
"serverAddress": config.GameServer.ip,
"serverPort": Number(config.GameServer.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": functions.MakeID().replace(/-/ig, "").toUpperCase(),
"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": req.cookies.currentbuildUniqueId || "0", // buildUniqueId is different for every build, this uses the netver of the version you're currently using
"lastUpdated": new Date().toISOString(),
"started": false
})
})
express.post("/fortnite/api/matchmaking/session/*/join", async (req, res) => {
res.status(204);
res.end();
})
express.post("/fortnite/api/matchmaking/session/matchMakingRequest", async (req, res) => {
res.json([])
})
module.exports = express;

7964
backend/structure/mcp.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
const Express = require("express");
const express = Express.Router();
const functions = require("./functions.js");
express.get("/party/api/v1/Fortnite/user/*", async (req, res) => {
res.json({
"current": [],
"pending": [],
"invites": [],
"pings": []
});
})
express.post("/party/api/v1/Fortnite/parties", async (req, res) => {
if (!req.body.join_info) return res.json({});
if (!req.body.join_info.connection) return res.json({});
res.json({
"id": functions.MakeID().replace(/-/ig, ""),
"created_at": new Date().toISOString(),
"updated_at": new Date().toISOString(),
"config": {
"type": "DEFAULT",
...req.body.config,
"discoverability": "ALL",
"sub_type": "default",
"invite_ttl": 14400,
"intention_ttl": 60
},
"members": [{
"account_id": (req.body.join_info.connection.id || "").split("@prod")[0],
"meta": req.body.join_info.meta || {},
"connections": [
{
"id": req.body.join_info.connection.id || "",
"connected_at": new Date().toISOString(),
"updated_at": new Date().toISOString(),
"yield_leadership": false,
"meta": req.body.join_info.connection.meta || {}
}
],
"revision": 0,
"updated_at": new Date().toISOString(),
"joined_at": new Date().toISOString(),
"role": "CAPTAIN"
}],
"applicants": [],
"meta": req.body.meta || {},
"invites": [],
"revision": 0,
"intentions": []
})
})
express.all("/party/api/v1/Fortnite/parties/*", async (req, res) => {
res.status(204)
res.end()
})
module.exports = express;

View File

@@ -0,0 +1,22 @@
const Express = require("express");
const express = Express.Router();
const fs = require("fs");
const privacy = require("./../responses/privacy.json");
express.get("/fortnite/api/game/v2/privacy/account/:accountId", async (req, res) => {
privacy.accountId = req.params.accountId;
res.json(privacy);
})
express.post("/fortnite/api/game/v2/privacy/account/:accountId", async (req, res) => {
privacy.accountId = req.params.accountId;
privacy.optOutOfPublicLeaderboards = req.body.optOutOfPublicLeaderboards;
fs.writeFileSync("./responses/privacy.json", JSON.stringify(privacy, null, 2));
res.json(privacy);
res.end();
})
module.exports = express;

View File

@@ -0,0 +1,23 @@
const Express = require("express");
const express = Express.Router();
const functions = require("./functions.js");
const catalog = functions.getItemShop();
const keychain = require("./../responses/keychain.json");
express.get("/fortnite/api/storefront/v2/catalog", async (req, res) => {
if (req.headers["user-agent"].includes("2870186")) {
return res.status(404).end();
}
res.json(catalog);
})
express.get("/fortnite/api/storefront/v2/keychain", async (req, res) => {
res.json(keychain)
})
express.get("/catalog/api/shared/bulk/offers", async (req, res) => {
res.json({});
})
module.exports = express;

File diff suppressed because it is too large Load Diff

193
backend/structure/user.js Normal file
View File

@@ -0,0 +1,193 @@
const Express = require("express");
const express = Express.Router();
const fs = require("fs");
const path = require("path");
const iniparser = require("ini");
const config = iniparser.parse(fs.readFileSync(path.join(__dirname, "..", "Config", "config.ini")).toString());
var Memory_CurrentAccountID = config.Config.displayName;
express.get("/account/api/public/account", async (req, res) => {
var response = [];
if (typeof req.query.accountId == "string") {
var accountId = req.query.accountId;
if (accountId.includes("@")) accountId = accountId.split("@")[0];
response.push({
"id": accountId,
"displayName": accountId,
"externalAuths": {}
})
}
if (Array.isArray(req.query.accountId)) {
for (var x in req.query.accountId) {
var accountId = req.query.accountId[x];
if (accountId.includes("@")) accountId = accountId.split("@")[0];
response.push({
"id": accountId,
"displayName": accountId,
"externalAuths": {}
})
}
}
res.json(response)
})
express.get("/account/api/public/account/:accountId", async (req, res) => {
if (config.Config.bUseConfigDisplayName == false) {
Memory_CurrentAccountID = req.params.accountId;
}
if (Memory_CurrentAccountID.includes("@")) Memory_CurrentAccountID = Memory_CurrentAccountID.split("@")[0];
res.json({
"id": req.params.accountId,
"displayName": Memory_CurrentAccountID,
"name": "Lawin",
"email": Memory_CurrentAccountID + "@lawin.com",
"failedLoginAttempts": 0,
"lastLogin": new Date().toISOString(),
"numberOfDisplayNameChanges": 0,
"ageGroup": "UNKNOWN",
"headless": false,
"country": "US",
"lastName": "Server",
"preferredLanguage": "en",
"canUpdateDisplayName": false,
"tfaEnabled": false,
"emailVerified": true,
"minorVerified": false,
"minorExpected": false,
"minorStatus": "NOT_MINOR",
"cabinedMode": false,
"hasHashedEmail": false
})
})
express.get("/sdk/v1/*", async (req, res) => {
const sdk = require("./../responses/sdkv1.json");
res.json(sdk)
})
express.post("/auth/v1/oauth/token", async (req, res) => {
res.json({
"access_token": "lawinstokenlol",
"token_type": "bearer",
"expires_in": 28800,
"expires_at": "9999-12-31T23:59:59.999Z",
"deployment_id": "lawinsdeploymentidlol",
"organization_id": "lawinsorganizationidlol",
"product_id": "prod-fn",
"sandbox_id": "fn"
})
})
express.get("/epic/id/v2/sdk/accounts", async (req, res) => {
res.json([{
"accountId": Memory_CurrentAccountID,
"displayName": Memory_CurrentAccountID,
"preferredLanguage": "en",
"cabinedMode": false,
"empty": false
}])
})
express.post("/epic/oauth/v2/token", async (req, res) => {
res.json({
"scope": "basic_profile friends_list openid presence",
"token_type": "bearer",
"access_token": "lawinstokenlol",
"expires_in": 28800,
"expires_at": "9999-12-31T23:59:59.999Z",
"refresh_token": "lawinstokenlol",
"refresh_expires_in": 86400,
"refresh_expires_at": "9999-12-31T23:59:59.999Z",
"account_id": Memory_CurrentAccountID,
"client_id": "lawinsclientidlol",
"application_id": "lawinsapplicationidlol",
"selected_account_id": Memory_CurrentAccountID,
"id_token": "lawinstokenlol"
})
})
express.get("/account/api/public/account/*/externalAuths", async (req, res) => {
res.json([])
})
express.delete("/account/api/oauth/sessions/kill", async (req, res) => {
res.status(204);
res.end();
})
express.delete("/account/api/oauth/sessions/kill/*", async (req, res) => {
res.status(204);
res.end();
})
express.get("/account/api/oauth/verify", async (req, res) => {
res.json({
"token": "lawinstokenlol",
"session_id": "3c3662bcb661d6de679c636744c66b62",
"token_type": "bearer",
"client_id": "lawinsclientidlol",
"internal_client": true,
"client_service": "fortnite",
"account_id": Memory_CurrentAccountID,
"expires_in": 28800,
"expires_at": "9999-12-02T01:12:01.100Z",
"auth_method": "exchange_code",
"display_name": Memory_CurrentAccountID,
"app": "fortnite",
"in_app_id": Memory_CurrentAccountID,
"device_id": "lawinsdeviceidlol"
})
})
express.post("/account/api/oauth/token", async (req, res) => {
if (config.Config.bUseConfigDisplayName == false) {
Memory_CurrentAccountID = req.body.username || "LawinServer"
}
if (Memory_CurrentAccountID.includes("@")) Memory_CurrentAccountID = Memory_CurrentAccountID.split("@")[0];
res.json({
"access_token": "lawinstokenlol",
"expires_in": 28800,
"expires_at": "9999-12-02T01:12:01.100Z",
"token_type": "bearer",
"refresh_token": "lawinstokenlol",
"refresh_expires": 86400,
"refresh_expires_at": "9999-12-02T01:12:01.100Z",
"account_id": Memory_CurrentAccountID,
"client_id": "lawinsclientidlol",
"internal_client": true,
"client_service": "fortnite",
"displayName": Memory_CurrentAccountID,
"app": "fortnite",
"in_app_id": Memory_CurrentAccountID,
"device_id": "lawinsdeviceidlol"
})
})
express.post("/account/api/oauth/exchange", async (req, res) => {
res.json({})
})
express.get("/account/api/epicdomains/ssodomains", async (req, res) => {
res.json([
"unrealengine.com",
"unrealtournament.com",
"fortnite.com",
"epicgames.com"
])
})
express.post("/fortnite/api/game/v2/tryPlayOnPlatform/account/*", async (req, res) => {
res.setHeader("Content-Type", "text/plain");
res.send(true);
})
module.exports = express;

View File

@@ -0,0 +1,59 @@
const Express = require("express");
const express = Express.Router();
express.get("/fortnite/api/version", async (req, res) => {
res.json({
"app": "fortnite",
"serverDate": new Date().toISOString(),
"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"
}
}
});
})
express.get("/fortnite/api/v2/versioncheck/*", async (req, res) => {
res.json({
"type": "NO_UPDATE"
})
})
express.get("/fortnite/api/v2/versioncheck*", async (req, res) => {
res.json({
"type": "NO_UPDATE"
})
})
express.get("/fortnite/api/versioncheck*", async (req, res) => {
res.json({
"type": "NO_UPDATE"
})
})
module.exports = express;

275
backend/structure/xmpp.js Normal file
View File

@@ -0,0 +1,275 @@
const WebSocket = require("ws").Server;
const XMLBuilder = require("xmlbuilder");
const XMLParser = require("xml-parser");
const functions = require("./../structure/functions.js");
const matchmaker = require("./matchmaker.js");
const port = 80;
const wss = new WebSocket({ port: port }, () => console.log("XMPP and Matchmaker started listening on port", port));
wss.on("error", (err) => {
console.log("XMPP and Matchmaker \x1b[31mFAILED\x1b[0m to start hosting.");
})
global.Clients = [];
wss.on('connection', async (ws) => {
ws.on('error', () => {});
if (ws.protocol.toLowerCase() != "xmpp") return matchmaker(ws);
var accountId = "";
var jid = "";
var id = "";
var ID = functions.MakeID();
var Authenticated = false;
ws.on('message', async (message) => {
if (Buffer.isBuffer(message)) message = message.toString();
const msg = XMLParser(message);
if (!msg.root) return Error(ws);
switch (msg.root.name) {
case "open":
ws.send(XMLBuilder.create("open")
.attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-framing")
.attribute("from", "prod.ol.epicgames.com")
.attribute("id", ID)
.attribute("version", "1.0")
.attribute("xml:lang", "en").toString())
if (Authenticated == true) {
ws.send(XMLBuilder.create("stream:features").attribute("xmlns:stream", "http://etherx.jabber.org/streams")
.element("ver").attribute("xmlns", "urn:xmpp:features:rosterver").up()
.element("starttls").attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls").up()
.element("bind").attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-bind").up()
.element("compression").attribute("xmlns", "http://jabber.org/features/compress")
.element("method", "zlib").up().up()
.element("session").attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-session").up().toString())
} else {
ws.send(XMLBuilder.create("stream:features").attribute("xmlns:stream", "http://etherx.jabber.org/streams")
.element("mechanisms").attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl")
.element("mechanism", "PLAIN").up().up()
.element("ver").attribute("xmlns", "urn:xmpp:features:rosterver").up()
.element("starttls").attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls").up()
.element("compression").attribute("xmlns", "http://jabber.org/features/compress")
.element("method", "zlib").up().up()
.element("auth").attribute("xmlns", "http://jabber.org/features/iq-auth").up().toString())
}
break;
case "auth":
if (!msg.root.content) return Error(ws);
if (!functions.DecodeBase64(msg.root.content)) return Error(ws);
if (!functions.DecodeBase64(msg.root.content).includes("\u0000")) return Error(ws);
var decodedBase64 = functions.DecodeBase64(msg.root.content).split("\u0000");
if (global.Clients.find(i => i.accountId == decodedBase64[1])) return Error(ws);
accountId = decodedBase64[1];
if (decodedBase64 && accountId && decodedBase64.length == 3) {
Authenticated = true;
console.log(`An xmpp client with the account id ${accountId} has logged in.`);
ws.send(XMLBuilder.create("success").attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl").toString());
} else {
return Error(ws);
}
break;
case "iq":
switch (msg.root.attributes.id) {
case "_xmpp_bind1":
if (!msg.root.children.find(i => i.name == "bind")) return;
if (!msg.root.children.find(i => i.name == "bind").children.find(i => i.name == "resource")) return;
var resource = msg.root.children.find(i => i.name == "bind").children.find(i => i.name == "resource").content;
jid = `${accountId}@prod.ol.epicgames.com/${resource}`;
id = `${accountId}@prod.ol.epicgames.com`;
ws.send(XMLBuilder.create("iq")
.attribute("to", jid)
.attribute("id", "_xmpp_bind1")
.attribute("xmlns", "jabber:client")
.attribute("type", "result")
.element("bind")
.attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-bind")
.element("jid", jid).up().up().toString())
break;
case "_xmpp_session1":
if (!global.Clients.find(i => i.client == ws)) return Error(ws);
var xml = XMLBuilder.create("iq")
.attribute("to", jid)
.attribute("from", "prod.ol.epicgames.com")
.attribute("id", "_xmpp_session1")
.attribute("xmlns", "jabber:client")
.attribute("type", "result");
ws.send(xml.toString());
getPresenceFromAll(ws);
break;
default:
if (!global.Clients.find(i => i.client == ws)) return Error(ws);
var xml = XMLBuilder.create("iq")
.attribute("to", jid)
.attribute("from", "prod.ol.epicgames.com")
.attribute("id", msg.root.attributes.id)
.attribute("xmlns", "jabber:client")
.attribute("type", "result");
ws.send(xml.toString());
}
break;
case "message":
if (!global.Clients.find(i => i.client == ws)) return Error(ws);
if (!msg.root.children.find(i => i.name == "body")) return;
var body = msg.root.children.find(i => i.name == "body").content;
if (msg.root.attributes.type) {
if (msg.root.attributes.type == "chat") {
if (!msg.root.attributes.to) return;
var receiver = global.Clients.find(i => i.id == msg.root.attributes.to);
var sender = global.Clients.find(i => i.client == ws);
if (!receiver || !sender) return;
if (receiver == sender) return;
receiver.client.send(XMLBuilder.create("message")
.attribute("to", receiver.jid)
.attribute("from", sender.jid)
.attribute("xmlns", "jabber:client")
.attribute("type", "chat")
.element("body", body).up().toString())
return;
}
}
if (ifJSON(body)) {
var object = JSON.parse(body);
if (object.hasOwnProperty("type")) {
if (typeof object.type == "string") {
switch (object.type.toLowerCase()) {
case "com.epicgames.party.invitation":
if (!msg.root.attributes.to) return;
var sender = global.Clients.find(i => i.client == ws);
var receiver = global.Clients.find(i => i.id == msg.root.attributes.to);
if (!receiver) return;
receiver.client.send(XMLBuilder.create("message")
.attribute("from", sender.jid)
.attribute("id", msg.root.attributes.id)
.attribute("to", receiver.jid)
.attribute("xmlns", "jabber:client")
.element("body", body).up().toString())
break;
default:
ws.send(XMLBuilder.create("message")
.attribute("from", jid)
.attribute("id", msg.root.attributes.id)
.attribute("to", jid)
.attribute("xmlns", "jabber:client")
.element("body", body).up().toString());
}
}
}
}
break;
case "presence":
if (!global.Clients.find(i => i.client == ws)) return Error(ws);
if (!msg.root.children.find(i => i.name == "status")) return;
if (!ifJSON(msg.root.children.find(i => i.name == "status").content)) return;
var body = msg.root.children.find(i => i.name == "status").content;
var away = false;
if (msg.root.children.find(i => i.name == "show")) away = true;
updatePresenceForAll(ws, body, away, false)
break;
}
if (!global.Clients.find(i => i.client == ws)) {
if (accountId && jid && ID && id && Authenticated == true) {
global.Clients.push({ "client": ws, "accountId": accountId, "jid": jid, "id": id, "lastPresenceUpdate": { "away": false, "status": "{}" } });
}
}
})
ws.on('close', () => RemoveClient(ws))
})
function RemoveClient(ws) {
if (global.Clients.find(i => i.client == ws)) {
updatePresenceForAll(ws, "{}", false, true);
console.log(`An xmpp client with the account id ${global.Clients.find(i => i.client == ws).accountId} has logged out.`);
global.Clients.splice(global.Clients.findIndex(i => i.client == ws), 1);
}
}
function Error(ws) {
ws.send(XMLBuilder.create("close").attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-framing").toString());
ws.close();
}
function updatePresenceForAll(ws, body, away, offline) {
if (global.Clients.find(i => i.client == ws)) {
var SenderData = global.Clients.find(i => i.client == ws);
var SenderIndex = global.Clients.findIndex(i => i.client == ws);
global.Clients[SenderIndex].lastPresenceUpdate.away = away;
global.Clients[SenderIndex].lastPresenceUpdate.status = body;
global.Clients.forEach(ClientData => {
var xml = XMLBuilder.create("presence")
.attribute("to", ClientData.jid)
.attribute("xmlns", "jabber:client")
.attribute("from", SenderData.jid)
if (offline == true) xml = xml.attribute("type", "unavailable");
else xml = xml.attribute("type", "available")
if (away == true) xml = xml.element("show", "away").up().element("status", body).up();
else xml = xml.element("status", body).up();
ClientData.client.send(xml.toString())
})
} else {
return Error(ws);
}
}
function getPresenceFromAll(ws) {
if (global.Clients.find(i => i.client == ws)) {
var SenderData = global.Clients.find(i => i.client == ws);
global.Clients.forEach(ClientData => {
var xml = XMLBuilder.create("presence")
.attribute("to", SenderData.jid)
.attribute("xmlns", "jabber:client")
.attribute("from", ClientData.jid)
if (ClientData.lastPresenceUpdate.away == true) xml = xml.attribute("type", "available").element("show", "away").up().element("status", ClientData.lastPresenceUpdate.status).up();
else xml = xml.attribute("type", "available").element("status", ClientData.lastPresenceUpdate.status).up();
SenderData.client.send(xml.toString())
})
} else {
return Error(ws);
}
}
function ifJSON(str) {
try {
JSON.parse(str)
} catch (err) {
return false;
}
return true;
}