Binato/server/BanchoServer.ts

350 lines
12 KiB
TypeScript
Raw Normal View History

2022-11-16 15:25:46 +00:00
import config from "../config.json";
2022-11-16 11:59:23 +00:00
import { ConsoleHelper } from "../ConsoleHelper";
2022-11-16 15:25:46 +00:00
import { Database } from "./objects/Database";
import { UserArray } from "./objects/UserArray";
import { LatLng } from "./objects/LatLng";
2022-11-16 11:59:23 +00:00
import { Packets } from "./enums/Packets";
2022-11-16 15:25:46 +00:00
import { RedisClientType, createClient } from "redis";
import { replaceAll } from "./Util";
import { Request, Response } from "express";
import { User } from "./objects/User";
import * as osu from "osu-packet";
2022-11-16 11:59:23 +00:00
2022-11-16 15:25:46 +00:00
/*const
loginHandler = require("./loginHandler.js"),
parseUserData = require("./util/parseUserData.js"),
getUserFromToken = require("./util/getUserByToken.js"),
getUserById = require("./util/getUserById.js"),
bakedResponses = require("./bakedResponses.js"),
2022-11-16 15:25:46 +00:00
Streams = require("./Streams.js");*/
const DB:Database = new Database(config.database.address, config.database.port, config.database.username, config.database.password, config.database.name, async () => {
// Close any unclosed db matches on startup
DB.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
DB.query("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
});
2020-08-27 13:09:35 +01:00
// Users funkyArray for session storage
2022-11-16 15:25:46 +00:00
const users = new UserArray();
2020-08-27 13:09:35 +01:00
2021-05-15 00:24:39 +01:00
// Add the bot user
2022-11-16 15:25:46 +00:00
const botUser:User = users.add("bot", new User(3, "SillyBot", "bot", DB));
2020-09-07 19:23:06 +01:00
// Set the bot's position on the map
2022-11-16 15:25:46 +00:00
botUser.location = new LatLng(50, -32);
2020-09-07 19:23:06 +01:00
2022-11-16 15:25:46 +00:00
let redisClient:RedisClientType;
2020-09-07 19:23:06 +01:00
2022-11-16 15:25:46 +00:00
async function subscribeToChannel(channelName:string, callback:(message:string) => void) {
// Dup and connect new client for channel subscription (required)
2022-11-16 15:25:46 +00:00
const subscriptionClient:RedisClientType = redisClient.duplicate();
await subscriptionClient.connect();
// Subscribe to channel
await subscriptionClient.subscribe(channelName, callback);
2022-11-16 11:59:23 +00:00
ConsoleHelper.printRedis(`Subscribed to ${channelName} channel`);
}
2022-04-24 02:14:23 +01:00
if (config.redis.enabled) {
(async () => {
2022-11-16 15:25:46 +00:00
redisClient = createClient({
url: `redis://${replaceAll(config.redis.password, " ", "") == "" ? "" : `${config.redis.password}@`}${config.redis.address}:${config.redis.port}/${config.redis.database}`
});
2022-11-16 15:25:46 +00:00
redisClient.on('error', e => ConsoleHelper.printRedis(e));
const connectionStartTime = Date.now();
2022-11-16 15:25:46 +00:00
await redisClient.connect();
ConsoleHelper.printRedis(`Connected to redis server. Took ${Date.now() - connectionStartTime}ms`);
// Score submit update channel
subscribeToChannel("binato:update_user_stats", (message) => {
2022-11-16 15:25:46 +00:00
if (typeof(message) === "string") {
const user = users.getById(parseInt(message));
// Update user info
user.updateUserInfo(true);
2022-04-24 02:04:14 +01:00
2022-11-16 15:25:46 +00:00
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
}
});
})();
2022-11-16 15:25:46 +00:00
} else ConsoleHelper.printWarn("Redis is disabled!");
// User timeout interval
2020-08-27 13:09:35 +01:00
setInterval(() => {
2022-11-16 15:25:46 +00:00
for (let User of users.getIterableItems()) {
if (User.id == 3) continue; // Ignore the bot
// Bot: :(
2020-08-27 13:09:35 +01:00
2022-02-23 05:35:32 +00:00
// Logout this user, they're clearly gone.
if (Date.now() >= User.timeoutTime)
Logout(User);
}
2020-08-27 13:09:35 +01:00
}, 10000);
2022-08-26 12:52:00 +01:00
// Init stream class
2022-11-16 15:25:46 +00:00
//Streams.init();
2020-08-27 13:09:35 +01:00
// An array containing all chat channels
2022-11-16 15:25:46 +00:00
/*global.channels = [
{ channelName:"#osu", channelTopic:"The main channel", channelUserCount: 0, locked: false },
{ channelName:"#userlog", channelTopic:"Log about stuff doing go on yes very", channelUserCount: 0, locked: false },
{ channelName:"#lobby", channelTopic:"Talk about multiplayer stuff", channelUserCount: 0, locked: false },
{ channelName:"#english", channelTopic:"Talk in exclusively English", channelUserCount: 0, locked: false },
{ channelName:"#japanese", channelTopic:"Talk in exclusively Japanese", channelUserCount: 0, locked: false },
2022-11-16 15:25:46 +00:00
];*/
2020-08-27 13:09:35 +01:00
// Create a stream for each chat channel
2022-11-16 15:25:46 +00:00
/*for (let i = 0; i < global.channels.length; i++) {
2022-08-26 12:52:00 +01:00
Streams.addStream(global.channels[i].channelName, false);
2022-11-16 15:25:46 +00:00
}*/
2020-08-27 13:09:35 +01:00
// Add a stream for the multiplayer lobby
2022-11-16 15:25:46 +00:00
//Streams.addStream("multiplayer_lobby", false);
2021-01-26 12:26:46 +00:00
2020-08-27 13:09:35 +01:00
// Include packets
const ChangeAction = require("./Packets/ChangeAction.js"),
SendPublicMessage = require("./Packets/SendPublicMessage.js"),
Logout = require("./Packets/Logout.js"),
Spectator = require("./Spectator.js"),
SendPrivateMessage = require("./Packets/SendPrivateMessage.js"),
MultiplayerManager = require("./MultiplayerManager.js"),
2022-01-04 04:43:32 +00:00
SetAwayMessage = require("./Packets/SetAwayMessage.js"),
ChannelJoin = require("./Packets/ChannelJoin.js"),
ChannelPart = require("./Packets/ChannelPart.js"),
AddFriend = require("./Packets/AddFriend.js"),
RemoveFriend = require("./Packets/RemoveFriend.js"),
UserPresenceBundle = require("./Packets/UserPresenceBundle.js"),
UserPresence = require("./Packets/UserPresence.js"),
UserStatsRequest = require("./Packets/UserStatsRequest.js"),
MultiplayerInvite = require("./Packets/MultiplayerInvite.js"),
TourneyMatchSpecialInfo = require("./Packets/TourneyMatchSpecialInfo.js"),
TourneyMatchJoinChannel = require("./Packets/TourneyMatchSpecialInfo.js"),
TourneyMatchLeaveChannel = require("./Packets/TourneyLeaveMatchChannel.js");
2020-08-27 13:09:35 +01:00
2021-01-26 12:26:46 +00:00
// A class for managing everything multiplayer
2022-11-16 15:25:46 +00:00
const multiplayerManager:MultiplayerManager = new MultiplayerManager();
2021-01-26 12:26:46 +00:00
2022-11-16 15:25:46 +00:00
export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
// Get the client's token string and request data
2022-11-16 15:25:46 +00:00
const requestTokenString:string | undefined = req.header("osu-token"),
2022-11-16 11:59:23 +00:00
requestData:Buffer = packet;
2022-02-22 09:42:57 +00:00
// Server's response
2022-11-16 11:59:23 +00:00
let responseData:Buffer;
// Check if the user is logged in
if (requestTokenString == null) {
// Client doesn't have a token yet, let's auth them!
const userData = parseUserData(requestData);
2022-11-16 11:59:23 +00:00
ConsoleHelper.printBancho(`New client connection. [User: ${userData.username}]`);
await loginHandler(req, res, userData);
} else {
// Client has a token, let's see what they want.
try {
// Get the current user
2022-11-16 15:25:46 +00:00
const PacketUser:User = users.getByToken(requestTokenString);
// Make sure the client's token isn't invalid
if (PacketUser != null) {
2022-02-23 05:35:32 +00:00
// Update the session timeout time
PacketUser.timeoutTime = Date.now() + 60000;
// Create a new osu! packet reader
const osuPacketReader = new osu.Client.Reader(requestData);
// Parse current bancho packet
const PacketData = osuPacketReader.Parse();
// Go through each packet sent by the client
2022-11-16 15:25:46 +00:00
for (let CurrentPacket of PacketData) {
switch (CurrentPacket.id) {
2022-11-16 11:59:23 +00:00
case Packets.Client_ChangeAction:
ChangeAction(PacketUser, CurrentPacket.data);
break;
2022-11-16 11:59:23 +00:00
case Packets.Client_SendPublicMessage:
SendPublicMessage(PacketUser, CurrentPacket.data);
break;
2022-11-16 11:59:23 +00:00
case Packets.Client_Logout:
await Logout(PacketUser);
break;
2022-11-16 11:59:23 +00:00
case Packets.Client_RequestStatusUpdate:
UserPresenceBundle(PacketUser);
break;
2022-11-16 11:59:23 +00:00
case Packets.Client_StartSpectating:
Spectator.startSpectatingUser(PacketUser, CurrentPacket.data);
break;
2022-11-16 11:59:23 +00:00
case Packets.Client_SpectateFrames:
Spectator.sendSpectatorFrames(PacketUser, CurrentPacket.data);
break;
2022-11-16 11:59:23 +00:00
case Packets.Client_StopSpectating:
Spectator.stopSpectatingUser(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_sendPrivateMessage:
SendPrivateMessage(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_joinLobby:
global.MultiplayerManager.userEnterLobby(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_partLobby:
global.MultiplayerManager.userLeaveLobby(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_createMatch:
await global.MultiplayerManager.createMultiplayerMatch(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_joinMatch:
global.MultiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchChangeSlot:
PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchReady:
PacketUser.currentMatch.setStateReady(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchChangeSettings:
await PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchNotReady:
PacketUser.currentMatch.setStateNotReady(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_partMatch:
await global.MultiplayerManager.leaveMultiplayerMatch(PacketUser);
break;
// Also handles user kick if the slot has a user
2022-11-16 15:25:46 +00:00
case Packets.Client_matchLock:
PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchNoBeatmap:
PacketUser.currentMatch.missingBeatmap(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchSkipRequest:
PacketUser.currentMatch.matchSkip(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchHasBeatmap:
PacketUser.currentMatch.notMissingBeatmap(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchTransferHost:
PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchChangeMods:
PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchStart:
PacketUser.currentMatch.startMatch();
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchLoadComplete:
PacketUser.currentMatch.matchPlayerLoaded(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchComplete:
await PacketUser.currentMatch.onPlayerFinishMatch(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchScoreUpdate:
PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchFailed:
PacketUser.currentMatch.matchFailed(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_matchChangeTeam:
PacketUser.currentMatch.changeTeam(PacketUser);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_channelJoin:
ChannelJoin(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_channelPart:
ChannelPart(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_setAwayMessage:
2022-01-04 04:43:32 +00:00
SetAwayMessage(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_friendAdd:
AddFriend(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_friendRemove:
RemoveFriend(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_userStatsRequest:
UserStatsRequest(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_specialMatchInfoRequest:
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_specialJoinMatchChannel:
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_specialLeaveMatchChannel:
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_invite:
MultiplayerInvite(PacketUser, CurrentPacket.data);
break;
2022-11-16 15:25:46 +00:00
case Packets.Client_userPresenceRequest:
UserPresence(PacketUser, PacketUser.id); // Can't really think of a way to generalize this?
break;
default:
// Ignore client_beatmapInfoRequest and client_receiveUpdates
2022-09-30 11:38:41 +01:00
if (CurrentPacket.id == 68 || CurrentPacket.id == 79 || CurrentPacket.id == 4) break;
// Print out unimplemented packet
console.dir(CurrentPacket);
break;
}
}
2022-02-22 09:42:57 +00:00
2022-07-02 11:36:49 +01:00
responseData = PacketUser.queue;
2022-02-22 09:42:57 +00:00
PacketUser.clearQueue();
} else {
// User's token is invlid, force a reconnect
consoleHelper.printBancho(`Forced client re-login (Token is invalid)`);
responseData = bakedResponses("reconnect");
}
} catch (e) {
console.error(e);
} finally {
// Only send the headers that we absolutely have to
res.removeHeader('X-Powered-By');
res.removeHeader('Date');
res.writeHead(200, {
"Connection": "keep-alive",
"Keep-Alive": "timeout=5, max=100",
});
// Send the prepared packet(s) to the client
res.end(responseData);
}
}
2022-11-16 11:59:23 +00:00
};