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"); const WebSocket = require('ws'); let latestServerInfo = null; const activeConnections = new Set(); express.post("/api/v1/server-info", async (req, res) => { try { if (!req.body.serverAddress || !req.body.serverPort) { return res.status(400).json({ error: "Missing required server address or port" }); } const { serverAddress, serverPort, sessionId, matchId, region, playlistName } = req.body; latestServerInfo = { serverAddress, serverPort: Number(serverPort), matchId: matchId || functions.MakeID().replace(/-/ig, ""), sessionId: sessionId || functions.MakeID().replace(/-/ig, ""), region: region || "EU", playlistName: playlistName || "Playlist_DefaultSolo", timestamp: Date.now() }; console.log(`[ServerInfo] Received new server info: ${serverAddress}:${serverPort}`); broadcastToAllConnections(); return res.status(200).json({ success: true }); } catch (error) { console.error("Error processing server info:", error); return res.status(500).json({ error: "Internal server error" }); } }); function broadcastToAllConnections() { if (!latestServerInfo) { console.log("[Matchmaking] No server info available to broadcast"); return; } console.log(`[Matchmaking] Broadcasting to ${activeConnections.size} active connections`); const broadcastMatchId = functions.MakeID().replace(/-/ig, ""); const broadcastSessionId = broadcastMatchId; activeConnections.forEach(ws => { if (ws.readyState === WebSocket.OPEN) { try { console.log(`[Matchmaking] Sending SessionAssignment to client`); ws.send( JSON.stringify({ payload: { matchId: broadcastMatchId, sessionId: broadcastSessionId, state: "SessionAssignment", }, name: "StatusUpdate", }) ); ws.sessionId = broadcastSessionId; ws.matchId = broadcastMatchId; } catch (err) { console.error("[Matchmaking] Error sending SessionAssignment:", err); } } }); setTimeout(() => { activeConnections.forEach(ws => { if (ws.readyState === WebSocket.OPEN) { try { console.log(`[Matchmaking] Sending Play message to client`); ws.send( JSON.stringify({ payload: { matchId: broadcastMatchId, sessionId: broadcastSessionId, joinDelaySec: 1, }, name: "Play", }) ); } catch (err) { console.error("[Matchmaking] Error sending Play message:", err); } } }); }, 2000); } function registerConnection(ws) { activeConnections.add(ws); console.log(`[Matchmaking] New connection registered, total: ${activeConnections.size}`); if (latestServerInfo && latestServerInfo.timestamp > Date.now() - (5 * 60 * 1000)) { console.log("[Matchmaking] Server info already available, sending to new connection"); const newMatchId = functions.MakeID().replace(/-/ig, ""); const newSessionId = newMatchId; setTimeout(() => { if (ws.readyState === WebSocket.OPEN) { ws.send( JSON.stringify({ payload: { matchId: newMatchId, sessionId: newSessionId, state: "SessionAssignment", }, name: "StatusUpdate", }) ); ws.sessionId = newSessionId; ws.matchId = newMatchId; setTimeout(() => { if (ws.readyState === WebSocket.OPEN) { ws.send( JSON.stringify({ payload: { matchId: newMatchId, sessionId: newSessionId, joinDelaySec: 1, }, name: "Play", }) ); } }, 2000); } }, 3000); } } function unregisterConnection(ws) { activeConnections.delete(ws); console.log(`[Matchmaking] Connection closed, total: ${activeConnections.size}`); } global.matchmakingFunctions = { registerConnection, unregisterConnection }; 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://${config.MatchmakerIP || "26.36.134.252"}`, "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) => { let serverAddress = config.GameServer.ip; let serverPort = Number(config.GameServer.port); let playlistName = "Playlist_DefaultSolo"; let region = "EU"; if (latestServerInfo) { console.log(`[Matchmaking] Using latest server info: ${latestServerInfo.serverAddress}:${latestServerInfo.serverPort}`); serverAddress = latestServerInfo.serverAddress; serverPort = latestServerInfo.serverPort; playlistName = latestServerInfo.playlistName; region = latestServerInfo.region; } else { console.log(`[Matchmaking] No server info available, using defaults`); } 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": serverAddress, "serverPort": serverPort, "maxPublicPlayers": 220, "openPublicPlayers": 175, "maxPrivatePlayers": 0, "openPrivatePlayers": 0, "attributes": { "REGION_s": region, "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": playlistName, "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", "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;