Binato/server/BanchoServer.ts

348 lines
12 KiB
TypeScript
Raw Normal View History

2022-11-27 17:36:55 +00:00
import { Config } from "./interfaces/Config";
2022-11-16 11:59:23 +00:00
import { ConsoleHelper } from "../ConsoleHelper";
2022-11-19 15:06:03 +00:00
import { Channel } from "./objects/Channel";
2022-11-19 01:06:03 +00:00
import { ChatManager } from "./ChatManager";
2022-11-16 15:25:46 +00:00
import { Database } from "./objects/Database";
2022-11-27 17:37:28 +00:00
import { DataStreamArray } from "./objects/DataStreamArray";
2022-11-16 15:25:46 +00:00
import { LatLng } from "./objects/LatLng";
2022-11-17 00:29:07 +00:00
import { LoginProcess } from "./LoginProcess";
2022-11-16 11:59:23 +00:00
import { Packets } from "./enums/Packets";
2022-11-16 15:25:46 +00:00
import { replaceAll } from "./Util";
2022-11-17 00:29:07 +00:00
import { readFileSync } from "fs";
import { RedisClientType, createClient } from "redis";
2022-11-16 15:25:46 +00:00
import { Request, Response } from "express";
2022-11-27 17:37:28 +00:00
import { SpectatorManager } from "./SpectatorManager";
2022-11-17 00:29:07 +00:00
import { UserArray } from "./objects/UserArray";
2022-11-16 15:25:46 +00:00
import { User } from "./objects/User";
2022-11-19 01:06:03 +00:00
import { MultiplayerManager } from "./MultiplayerManager";
2022-11-23 00:48:28 +00:00
import { SharedContent } from "./interfaces/SharedContent";
2022-11-27 17:36:55 +00:00
const config:Config = JSON.parse(readFileSync("./config.json").toString()) as Config;
2022-11-17 00:29:07 +00:00
// TODO: Port osu-packet to TypeScript
const osu = require("osu-packet");
2022-11-16 11:59:23 +00:00
const sharedContent:any = {};
// NOTE: This function should only be used externaly in Binato.ts and in this file.
export function GetSharedContent() : SharedContent {
return sharedContent;
}
const DB:Database = sharedContent.database = new Database(config.database.address, config.database.port, config.database.username, config.database.password, config.database.name, async () => {
2022-11-16 15:25:46 +00:00
// 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
2022-11-19 01:06:03 +00:00
// User session storage
const users:UserArray = sharedContent.users = new UserArray();
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// Add the bot user
const botUser:User = users.add("bot", new User(3, "SillyBot", "bot", GetSharedContent()));
// Set the bot's position on the map
botUser.location = new LatLng(50, -32);
2022-11-19 01:06:03 +00:00
// DataStream storage
const streams:DataStreamArray = sharedContent.streams = new DataStreamArray();
2022-11-19 01:06:03 +00:00
// ChatManager
2022-11-23 00:48:28 +00:00
const chatManager:ChatManager = sharedContent.chatManager = new ChatManager(GetSharedContent());
2022-11-19 15:06:03 +00:00
chatManager.AddChatChannel("osu", "The main channel", true);
2022-11-19 01:06:03 +00:00
chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff");
chatManager.AddChatChannel("english", "Talk in exclusively English");
chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese");
2022-11-20 23:37:39 +00:00
const multiplayerManager:MultiplayerManager = sharedContent.mutiplayerManager = new MultiplayerManager(GetSharedContent());
2020-08-27 13:09:35 +01:00
2022-11-27 17:37:28 +00:00
const spectatorManager:SpectatorManager = new SpectatorManager(GetSharedContent());
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));
2022-11-17 00:29:07 +00:00
if (user != null) {
// Update user info
user.updateUserInfo(true);
2022-04-24 02:04:14 +01:00
2022-11-17 00:29:07 +00:00
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
}
2022-11-16 15:25:46 +00:00
}
});
})();
2022-11-16 15:25:46 +00:00
} else ConsoleHelper.printWarn("Redis is disabled!");
2022-11-19 15:06:03 +00:00
// Import packets
import { ChangeAction } from "./packets/ChangeAction";
import { Logout } from "./packets/Logout";
import { UserPresence } from "./packets/UserPresence";
import { UserStatsRequest } from "./packets/UserStatsRequest";
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
// 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()) {
2022-11-17 00:29:07 +00:00
if (User.uuid == "bot") continue; // Ignore the bot
2020-08-27 13:09:35 +01:00
2022-02-23 05:35:32 +00:00
// Logout this user, they're clearly gone.
2022-11-19 15:06:03 +00:00
if (Date.now() >= User.timeoutTime) {
Logout(User);
}
}
2020-08-27 13:09:35 +01:00
}, 10000);
2022-11-17 00:29:07 +00:00
const EMPTY_BUFFER = Buffer.alloc(0);
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) {
2022-11-17 00:29:07 +00:00
// Remove headers we don't need for Bancho
res.removeHeader('X-Powered-By');
res.removeHeader('Date');
// Get the client's token string and request data
2022-11-17 00:29:07 +00:00
const requestTokenString:string | undefined = req.header("osu-token");
// Check if the user is logged in
if (requestTokenString == null) {
2022-11-17 00:29:07 +00:00
// Only do this if we're absolutely sure that we're connected to the DB
if (DB.connected) {
// Client doesn't have a token yet, let's auth them!
await LoginProcess(req, res, packet, GetSharedContent());
2022-11-17 00:29:07 +00:00
DB.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [users.getLength() - 1]);
}
} else {
2022-11-17 00:29:07 +00:00
let responseData:Buffer | string = EMPTY_BUFFER;
// Client has a token, let's see what they want.
try {
// Get the current user
2022-11-17 00:29:07 +00:00
const PacketUser:User | undefined = 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
2022-11-17 00:29:07 +00:00
const osuPacketReader = new osu.Client.Reader(packet);
// 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) {
2022-11-19 01:06:03 +00:00
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:
2022-11-19 15:06:03 +00:00
let channel = chatManager.GetChannelByName(CurrentPacket.data.target);
if (channel instanceof Channel) {
channel.SendMessage(PacketUser, CurrentPacket.data.message);
}
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:
2022-11-27 17:37:28 +00:00
spectatorManager.startSpectating(PacketUser, CurrentPacket.data);
break;
2022-11-16 11:59:23 +00:00
case Packets.Client_SpectateFrames:
2022-11-27 17:37:28 +00:00
spectatorManager.spectatorFrames(PacketUser, CurrentPacket.data);
break;
2022-11-16 11:59:23 +00:00
case Packets.Client_StopSpectating:
2022-11-27 17:37:28 +00:00
spectatorManager.stopSpectating(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_SendPrivateMessage:
2022-11-19 01:06:03 +00:00
//SendPrivateMessage(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_JoinLobby:
2022-11-23 00:48:28 +00:00
multiplayerManager.JoinLobby(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_PartLobby:
2022-11-23 00:48:28 +00:00
multiplayerManager.LeaveLobby(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_CreateMatch:
2022-11-20 23:37:39 +00:00
await multiplayerManager.CreateMatch(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_JoinMatch:
2022-11-23 00:48:28 +00:00
multiplayerManager.JoinMatch(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchChangeSlot:
2022-11-23 00:48:28 +00:00
PacketUser.match?.moveToSlot(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchReady:
2022-11-23 00:48:28 +00:00
PacketUser.match?.setStateReady(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchChangeSettings:
2022-11-23 00:48:28 +00:00
await PacketUser.match?.updateMatch(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchNotReady:
2022-11-23 00:48:28 +00:00
PacketUser.match?.setStateNotReady(PacketUser);
break;
2022-11-23 00:48:28 +00:00
// TODO: Match leave so the matches actually close
2022-11-17 00:29:07 +00:00
case Packets.Client_PartMatch:
2022-11-19 01:06:03 +00:00
//await multiplayerManager.leaveMultiplayerMatch(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchLock:
2022-11-23 00:48:28 +00:00
PacketUser.match?.lockOrKick(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchNoBeatmap:
2022-11-23 00:48:28 +00:00
PacketUser.match?.missingBeatmap(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchSkipRequest:
2022-11-23 00:48:28 +00:00
PacketUser.match?.matchSkip(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchHasBeatmap:
2022-11-23 00:48:28 +00:00
PacketUser.match?.notMissingBeatmap(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchTransferHost:
2022-11-23 00:48:28 +00:00
PacketUser.match?.transferHost(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchChangeMods:
2022-11-23 00:48:28 +00:00
PacketUser.match?.updateMods(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchStart:
2022-11-23 00:48:28 +00:00
PacketUser.match?.startMatch();
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchLoadComplete:
2022-11-23 00:48:28 +00:00
PacketUser.match?.matchPlayerLoaded(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchComplete:
2022-11-23 00:48:28 +00:00
await PacketUser.match?.onPlayerFinishMatch(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchScoreUpdate:
2022-11-23 00:48:28 +00:00
PacketUser.match?.updatePlayerScore(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchFailed:
2022-11-23 00:48:28 +00:00
PacketUser.match?.matchFailed(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_MatchChangeTeam:
2022-11-23 00:48:28 +00:00
PacketUser.match?.changeTeam(PacketUser);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_ChannelJoin:
2022-11-19 01:06:03 +00:00
//ChannelJoin(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_ChannelPart:
2022-11-19 01:06:03 +00:00
//ChannelPart(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_SetAwayMessage:
2022-11-19 01:06:03 +00:00
//SetAwayMessage(PacketUser, CurrentPacket.data);
2022-01-04 04:43:32 +00:00
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_FriendAdd:
2022-11-19 01:06:03 +00:00
//AddFriend(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_FriendRemove:
2022-11-19 01:06:03 +00:00
//RemoveFriend(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_UserStatsRequest:
UserStatsRequest(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_SpecialMatchInfoRequest:
2022-11-19 01:06:03 +00:00
//TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_SpecialJoinMatchChannel:
2022-11-19 01:06:03 +00:00
//TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_SpecialLeaveMatchChannel:
2022-11-19 01:06:03 +00:00
//TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +00:00
case Packets.Client_Invite:
2022-11-19 01:06:03 +00:00
//MultiplayerInvite(PacketUser, CurrentPacket.data);
break;
2022-11-17 00:29:07 +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-11-19 01:06:03 +00:00
}
}
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 {
2022-11-17 00:29:07 +00:00
// Only do this if we're absolutely sure that we're connected to the DB
if (DB.connected) {
// User's token is invlid, force a reconnect
ConsoleHelper.printBancho(`Forced client re-login (Token is invalid)`);
responseData = "\u0005\u0000\u0000\u0004\u0000\u0000\u0000<30><30><EFBFBD><EFBFBD>\u0018\u0000\u0000\u0011\u0000\u0000\u0000\u000b\u000fReconnecting...";
}
}
} catch (e) {
console.error(e);
} finally {
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-17 00:29:07 +00:00
}