olgfopgkdg
This commit is contained in:
parent
53a12461ce
commit
5ed106b7d4
15 changed files with 3123 additions and 112 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,6 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
build/
|
||||||
|
bundle/
|
||||||
tHMM.ds
|
tHMM.ds
|
||||||
server-stats.log
|
server-stats.log
|
||||||
config.json
|
config.json
|
14
Binato.ts
14
Binato.ts
|
@ -1,11 +1,11 @@
|
||||||
import { ChatHistory } from "./server/ChatHistory";
|
import { ChatHistory } from "./server/ChatHistory";
|
||||||
import compression from "compression";
|
import compression from "compression";
|
||||||
import config from "./config.json";
|
|
||||||
import { ConsoleHelper } from "./ConsoleHelper";
|
import { ConsoleHelper } from "./ConsoleHelper";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { HandleRequest } from "./server/BanchoServer";
|
import { HandleRequest } from "./server/BanchoServer";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { Registry, collectDefaultMetrics } from "prom-client";
|
import { Registry, collectDefaultMetrics } from "prom-client";
|
||||||
|
const config:any = JSON.parse(readFileSync(__dirname + "/config.json").toString());
|
||||||
|
|
||||||
const binatoApp:express.Application = express();
|
const binatoApp:express.Application = express();
|
||||||
|
|
||||||
|
@ -48,13 +48,7 @@ binatoApp.use((req, res) => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "POST":
|
case "POST":
|
||||||
// Make sure this address should respond to bancho requests
|
HandleRequest(req, res, packet);
|
||||||
// Bancho addresses: c, c1, c2, c3, c4, c5, c6, ce
|
|
||||||
// Just looking for the first character being "c" *should* be enough
|
|
||||||
if (req.headers.host != null && req.headers.host.split(".")[0][0] == "c")
|
|
||||||
HandleRequest(req, res, packet);
|
|
||||||
else
|
|
||||||
res.status(400).send("400 | Bad Request!<br>Binato only accepts POST requests on Bancho subdomains.<hr>Binato");
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -62,4 +56,6 @@ binatoApp.use((req, res) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
binatoApp.listen(config.express.port, () => ConsoleHelper.printBancho(`Binato is up! Listening at port ${config.express.port}`));
|
2441
package-lock.json
generated
2441
package-lock.json
generated
File diff suppressed because it is too large
Load diff
21
package.json
21
package.json
|
@ -4,25 +4,36 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "Binato.ts",
|
"main": "Binato.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:run": "nodemon --watch './**/*.ts' Binato.ts"
|
"dev:run": "nodemon --watch './**/*.ts' Binato.ts",
|
||||||
|
"pack": "webpack",
|
||||||
|
"build": "tsc --build",
|
||||||
|
"_clean": "tsc --build --clean"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/compression": "^1.7.2",
|
"aes256": "^1.1.0",
|
||||||
"@types/express": "^4.17.14",
|
|
||||||
"@types/node": "^18.11.9",
|
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"mysql2": "^2.3.3",
|
"mysql2": "^2.3.3",
|
||||||
|
"node-fetch": "^2.6.7",
|
||||||
"osu-packet": "^4.1.2",
|
"osu-packet": "^4.1.2",
|
||||||
"prom-client": "^14.1.0",
|
"prom-client": "^14.1.0",
|
||||||
"redis": "^4.5.0"
|
"redis": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/compression": "^1.7.2",
|
||||||
|
"@types/express": "^4.17.14",
|
||||||
|
"@types/node": "^18.11.9",
|
||||||
|
"@types/node-fetch": "^2.6.2",
|
||||||
"nodemon": "^2.0.20",
|
"nodemon": "^2.0.20",
|
||||||
"ts-node": "^10.9.1"
|
"ts-loader": "^9.4.1",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "^4.9.3",
|
||||||
|
"webpack": "^5.75.0",
|
||||||
|
"webpack-cli": "^4.10.0",
|
||||||
|
"webpack-node-externals": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import config from "../config.json";
|
|
||||||
import { ConsoleHelper } from "../ConsoleHelper";
|
import { ConsoleHelper } from "../ConsoleHelper";
|
||||||
import { Database } from "./objects/Database";
|
import { Database } from "./objects/Database";
|
||||||
import { UserArray } from "./objects/UserArray";
|
|
||||||
import { LatLng } from "./objects/LatLng";
|
import { LatLng } from "./objects/LatLng";
|
||||||
|
import { LoginProcess } from "./LoginProcess";
|
||||||
import { Packets } from "./enums/Packets";
|
import { Packets } from "./enums/Packets";
|
||||||
import { RedisClientType, createClient } from "redis";
|
|
||||||
import { replaceAll } from "./Util";
|
import { replaceAll } from "./Util";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { RedisClientType, createClient } from "redis";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
|
import { UserArray } from "./objects/UserArray";
|
||||||
import { User } from "./objects/User";
|
import { User } from "./objects/User";
|
||||||
import * as osu from "osu-packet";
|
const config:any = JSON.parse(readFileSync(__dirname + "/config.json").toString());
|
||||||
|
// TODO: Port osu-packet to TypeScript
|
||||||
|
const osu = require("osu-packet");
|
||||||
|
|
||||||
/*const
|
/*const
|
||||||
loginHandler = require("./loginHandler.js"),
|
loginHandler = require("./loginHandler.js"),
|
||||||
|
@ -59,10 +62,12 @@ if (config.redis.enabled) {
|
||||||
subscribeToChannel("binato:update_user_stats", (message) => {
|
subscribeToChannel("binato:update_user_stats", (message) => {
|
||||||
if (typeof(message) === "string") {
|
if (typeof(message) === "string") {
|
||||||
const user = users.getById(parseInt(message));
|
const user = users.getById(parseInt(message));
|
||||||
// Update user info
|
if (user != null) {
|
||||||
user.updateUserInfo(true);
|
// Update user info
|
||||||
|
user.updateUserInfo(true);
|
||||||
|
|
||||||
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
@ -71,12 +76,11 @@ if (config.redis.enabled) {
|
||||||
// User timeout interval
|
// User timeout interval
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
for (let User of users.getIterableItems()) {
|
for (let User of users.getIterableItems()) {
|
||||||
if (User.id == 3) continue; // Ignore the bot
|
if (User.uuid == "bot") continue; // Ignore the bot
|
||||||
// Bot: :(
|
|
||||||
|
|
||||||
// Logout this user, they're clearly gone.
|
// Logout this user, they're clearly gone.
|
||||||
if (Date.now() >= User.timeoutTime)
|
// if (Date.now() >= User.timeoutTime)
|
||||||
Logout(User);
|
// Logout(User);
|
||||||
}
|
}
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
|
@ -101,7 +105,7 @@ setInterval(() => {
|
||||||
//Streams.addStream("multiplayer_lobby", false);
|
//Streams.addStream("multiplayer_lobby", false);
|
||||||
|
|
||||||
// Include packets
|
// Include packets
|
||||||
const ChangeAction = require("./Packets/ChangeAction.js"),
|
/*const ChangeAction = require("./Packets/ChangeAction.js"),
|
||||||
SendPublicMessage = require("./Packets/SendPublicMessage.js"),
|
SendPublicMessage = require("./Packets/SendPublicMessage.js"),
|
||||||
Logout = require("./Packets/Logout.js"),
|
Logout = require("./Packets/Logout.js"),
|
||||||
Spectator = require("./Spectator.js"),
|
Spectator = require("./Spectator.js"),
|
||||||
|
@ -118,30 +122,37 @@ const ChangeAction = require("./Packets/ChangeAction.js"),
|
||||||
MultiplayerInvite = require("./Packets/MultiplayerInvite.js"),
|
MultiplayerInvite = require("./Packets/MultiplayerInvite.js"),
|
||||||
TourneyMatchSpecialInfo = require("./Packets/TourneyMatchSpecialInfo.js"),
|
TourneyMatchSpecialInfo = require("./Packets/TourneyMatchSpecialInfo.js"),
|
||||||
TourneyMatchJoinChannel = require("./Packets/TourneyMatchSpecialInfo.js"),
|
TourneyMatchJoinChannel = require("./Packets/TourneyMatchSpecialInfo.js"),
|
||||||
TourneyMatchLeaveChannel = require("./Packets/TourneyLeaveMatchChannel.js");
|
TourneyMatchLeaveChannel = require("./Packets/TourneyLeaveMatchChannel.js");*/
|
||||||
|
|
||||||
// A class for managing everything multiplayer
|
// A class for managing everything multiplayer
|
||||||
const multiplayerManager:MultiplayerManager = new MultiplayerManager();
|
//const multiplayerManager:MultiplayerManager = new MultiplayerManager();
|
||||||
|
|
||||||
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||||
|
|
||||||
export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
|
// 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
|
// Get the client's token string and request data
|
||||||
const requestTokenString:string | undefined = req.header("osu-token"),
|
const requestTokenString:string | undefined = req.header("osu-token");
|
||||||
requestData:Buffer = packet;
|
|
||||||
|
|
||||||
// Server's response
|
|
||||||
let responseData:Buffer;
|
|
||||||
|
|
||||||
// Check if the user is logged in
|
// Check if the user is logged in
|
||||||
if (requestTokenString == null) {
|
if (requestTokenString == null) {
|
||||||
// Client doesn't have a token yet, let's auth them!
|
// Only do this if we're absolutely sure that we're connected to the DB
|
||||||
const userData = parseUserData(requestData);
|
if (DB.connected) {
|
||||||
ConsoleHelper.printBancho(`New client connection. [User: ${userData.username}]`);
|
// Client doesn't have a token yet, let's auth them!
|
||||||
await loginHandler(req, res, userData);
|
|
||||||
|
await LoginProcess(req, res, packet, DB, users);
|
||||||
|
DB.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [users.getLength() - 1]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let responseData:Buffer | string = EMPTY_BUFFER;
|
||||||
|
|
||||||
// Client has a token, let's see what they want.
|
// Client has a token, let's see what they want.
|
||||||
try {
|
try {
|
||||||
// Get the current user
|
// Get the current user
|
||||||
const PacketUser:User = users.getByToken(requestTokenString);
|
const PacketUser:User | undefined = users.getByToken(requestTokenString);
|
||||||
|
|
||||||
// Make sure the client's token isn't invalid
|
// Make sure the client's token isn't invalid
|
||||||
if (PacketUser != null) {
|
if (PacketUser != null) {
|
||||||
|
@ -149,13 +160,13 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
PacketUser.timeoutTime = Date.now() + 60000;
|
PacketUser.timeoutTime = Date.now() + 60000;
|
||||||
|
|
||||||
// Create a new osu! packet reader
|
// Create a new osu! packet reader
|
||||||
const osuPacketReader = new osu.Client.Reader(requestData);
|
const osuPacketReader = new osu.Client.Reader(packet);
|
||||||
// Parse current bancho packet
|
// Parse current bancho packet
|
||||||
const PacketData = osuPacketReader.Parse();
|
const PacketData = osuPacketReader.Parse();
|
||||||
|
|
||||||
// Go through each packet sent by the client
|
// Go through each packet sent by the client
|
||||||
for (let CurrentPacket of PacketData) {
|
for (let CurrentPacket of PacketData) {
|
||||||
switch (CurrentPacket.id) {
|
/*switch (CurrentPacket.id) {
|
||||||
case Packets.Client_ChangeAction:
|
case Packets.Client_ChangeAction:
|
||||||
ChangeAction(PacketUser, CurrentPacket.data);
|
ChangeAction(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
@ -184,136 +195,136 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
Spectator.stopSpectatingUser(PacketUser);
|
Spectator.stopSpectatingUser(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_sendPrivateMessage:
|
case Packets.Client_SendPrivateMessage:
|
||||||
SendPrivateMessage(PacketUser, CurrentPacket.data);
|
SendPrivateMessage(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_joinLobby:
|
case Packets.Client_JoinLobby:
|
||||||
global.MultiplayerManager.userEnterLobby(PacketUser);
|
multiplayerManager.userEnterLobby(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_partLobby:
|
case Packets.Client_PartLobby:
|
||||||
global.MultiplayerManager.userLeaveLobby(PacketUser);
|
multiplayerManager.userLeaveLobby(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_createMatch:
|
case Packets.Client_CreateMatch:
|
||||||
await global.MultiplayerManager.createMultiplayerMatch(PacketUser, CurrentPacket.data);
|
await multiplayerManager.createMultiplayerMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_joinMatch:
|
case Packets.Client_JoinMatch:
|
||||||
global.MultiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data);
|
multiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchChangeSlot:
|
case Packets.Client_MatchChangeSlot:
|
||||||
PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchReady:
|
case Packets.Client_MatchReady:
|
||||||
PacketUser.currentMatch.setStateReady(PacketUser);
|
PacketUser.currentMatch.setStateReady(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchChangeSettings:
|
case Packets.Client_MatchChangeSettings:
|
||||||
await PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data);
|
await PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchNotReady:
|
case Packets.Client_MatchNotReady:
|
||||||
PacketUser.currentMatch.setStateNotReady(PacketUser);
|
PacketUser.currentMatch.setStateNotReady(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_partMatch:
|
case Packets.Client_PartMatch:
|
||||||
await global.MultiplayerManager.leaveMultiplayerMatch(PacketUser);
|
await multiplayerManager.leaveMultiplayerMatch(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Also handles user kick if the slot has a user
|
// Also handles user kick if the slot has a user
|
||||||
case Packets.Client_matchLock:
|
case Packets.Client_MatchLock:
|
||||||
PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchNoBeatmap:
|
case Packets.Client_MatchNoBeatmap:
|
||||||
PacketUser.currentMatch.missingBeatmap(PacketUser);
|
PacketUser.currentMatch.missingBeatmap(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchSkipRequest:
|
case Packets.Client_MatchSkipRequest:
|
||||||
PacketUser.currentMatch.matchSkip(PacketUser);
|
PacketUser.currentMatch.matchSkip(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchHasBeatmap:
|
case Packets.Client_MatchHasBeatmap:
|
||||||
PacketUser.currentMatch.notMissingBeatmap(PacketUser);
|
PacketUser.currentMatch.notMissingBeatmap(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchTransferHost:
|
case Packets.Client_MatchTransferHost:
|
||||||
PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchChangeMods:
|
case Packets.Client_MatchChangeMods:
|
||||||
PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchStart:
|
case Packets.Client_MatchStart:
|
||||||
PacketUser.currentMatch.startMatch();
|
PacketUser.currentMatch.startMatch();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchLoadComplete:
|
case Packets.Client_MatchLoadComplete:
|
||||||
PacketUser.currentMatch.matchPlayerLoaded(PacketUser);
|
PacketUser.currentMatch.matchPlayerLoaded(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchComplete:
|
case Packets.Client_MatchComplete:
|
||||||
await PacketUser.currentMatch.onPlayerFinishMatch(PacketUser);
|
await PacketUser.currentMatch.onPlayerFinishMatch(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchScoreUpdate:
|
case Packets.Client_MatchScoreUpdate:
|
||||||
PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data);
|
PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchFailed:
|
case Packets.Client_MatchFailed:
|
||||||
PacketUser.currentMatch.matchFailed(PacketUser);
|
PacketUser.currentMatch.matchFailed(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_matchChangeTeam:
|
case Packets.Client_MatchChangeTeam:
|
||||||
PacketUser.currentMatch.changeTeam(PacketUser);
|
PacketUser.currentMatch.changeTeam(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_channelJoin:
|
case Packets.Client_ChannelJoin:
|
||||||
ChannelJoin(PacketUser, CurrentPacket.data);
|
ChannelJoin(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_channelPart:
|
case Packets.Client_ChannelPart:
|
||||||
ChannelPart(PacketUser, CurrentPacket.data);
|
ChannelPart(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_setAwayMessage:
|
case Packets.Client_SetAwayMessage:
|
||||||
SetAwayMessage(PacketUser, CurrentPacket.data);
|
SetAwayMessage(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_friendAdd:
|
case Packets.Client_FriendAdd:
|
||||||
AddFriend(PacketUser, CurrentPacket.data);
|
AddFriend(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_friendRemove:
|
case Packets.Client_FriendRemove:
|
||||||
RemoveFriend(PacketUser, CurrentPacket.data);
|
RemoveFriend(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_userStatsRequest:
|
case Packets.Client_UserStatsRequest:
|
||||||
UserStatsRequest(PacketUser, CurrentPacket.data);
|
UserStatsRequest(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_specialMatchInfoRequest:
|
case Packets.Client_SpecialMatchInfoRequest:
|
||||||
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_specialJoinMatchChannel:
|
case Packets.Client_SpecialJoinMatchChannel:
|
||||||
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_specialLeaveMatchChannel:
|
case Packets.Client_SpecialLeaveMatchChannel:
|
||||||
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_invite:
|
case Packets.Client_Invite:
|
||||||
MultiplayerInvite(PacketUser, CurrentPacket.data);
|
MultiplayerInvite(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_userPresenceRequest:
|
case Packets.Client_UserPresenceRequest:
|
||||||
UserPresence(PacketUser, PacketUser.id); // Can't really think of a way to generalize this?
|
UserPresence(PacketUser, PacketUser.id); // Can't really think of a way to generalize this?
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -323,22 +334,22 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
// Print out unimplemented packet
|
// Print out unimplemented packet
|
||||||
console.dir(CurrentPacket);
|
console.dir(CurrentPacket);
|
||||||
break;
|
break;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
responseData = PacketUser.queue;
|
responseData = PacketUser.queue;
|
||||||
PacketUser.clearQueue();
|
PacketUser.clearQueue();
|
||||||
} else {
|
} else {
|
||||||
// User's token is invlid, force a reconnect
|
// Only do this if we're absolutely sure that we're connected to the DB
|
||||||
consoleHelper.printBancho(`Forced client re-login (Token is invalid)`);
|
if (DB.connected) {
|
||||||
responseData = bakedResponses("reconnect");
|
// 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) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
// Only send the headers that we absolutely have to
|
|
||||||
res.removeHeader('X-Powered-By');
|
|
||||||
res.removeHeader('Date');
|
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
"Connection": "keep-alive",
|
"Connection": "keep-alive",
|
||||||
"Keep-Alive": "timeout=5, max=100",
|
"Keep-Alive": "timeout=5, max=100",
|
||||||
|
@ -347,4 +358,4 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
res.end(responseData);
|
res.end(responseData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
264
server/Country.ts
Normal file
264
server/Country.ts
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
const countryCodes = {
|
||||||
|
LV: 132,
|
||||||
|
AD: 3,
|
||||||
|
LT: 130,
|
||||||
|
KM: 116,
|
||||||
|
QA: 182,
|
||||||
|
VA: 0,
|
||||||
|
PK: 173,
|
||||||
|
KI: 115,
|
||||||
|
SS: 0,
|
||||||
|
KH: 114,
|
||||||
|
NZ: 166,
|
||||||
|
TO: 215,
|
||||||
|
KZ: 122,
|
||||||
|
GA: 76,
|
||||||
|
BW: 35,
|
||||||
|
AX: 247,
|
||||||
|
GE: 79,
|
||||||
|
UA: 222,
|
||||||
|
CR: 50,
|
||||||
|
AE: 0,
|
||||||
|
NE: 157,
|
||||||
|
ZA: 240,
|
||||||
|
SK: 196,
|
||||||
|
BV: 34,
|
||||||
|
SH: 0,
|
||||||
|
PT: 179,
|
||||||
|
SC: 189,
|
||||||
|
CO: 49,
|
||||||
|
GP: 86,
|
||||||
|
GY: 93,
|
||||||
|
CM: 47,
|
||||||
|
TJ: 211,
|
||||||
|
AF: 5,
|
||||||
|
IE: 101,
|
||||||
|
AL: 8,
|
||||||
|
BG: 24,
|
||||||
|
JO: 110,
|
||||||
|
MU: 149,
|
||||||
|
PM: 0,
|
||||||
|
LA: 0,
|
||||||
|
IO: 104,
|
||||||
|
KY: 121,
|
||||||
|
SA: 187,
|
||||||
|
KN: 0,
|
||||||
|
OM: 167,
|
||||||
|
CY: 54,
|
||||||
|
BQ: 0,
|
||||||
|
BT: 33,
|
||||||
|
WS: 236,
|
||||||
|
ES: 67,
|
||||||
|
LR: 128,
|
||||||
|
RW: 186,
|
||||||
|
AQ: 12,
|
||||||
|
PW: 180,
|
||||||
|
JE: 250,
|
||||||
|
TN: 214,
|
||||||
|
ZW: 243,
|
||||||
|
JP: 111,
|
||||||
|
BB: 20,
|
||||||
|
VN: 233,
|
||||||
|
HN: 96,
|
||||||
|
KP: 0,
|
||||||
|
WF: 235,
|
||||||
|
EC: 62,
|
||||||
|
HU: 99,
|
||||||
|
GF: 80,
|
||||||
|
GQ: 87,
|
||||||
|
TW: 220,
|
||||||
|
MC: 135,
|
||||||
|
BE: 22,
|
||||||
|
PN: 176,
|
||||||
|
SZ: 205,
|
||||||
|
CZ: 55,
|
||||||
|
LY: 0,
|
||||||
|
IN: 103,
|
||||||
|
FM: 0,
|
||||||
|
PY: 181,
|
||||||
|
PH: 172,
|
||||||
|
MN: 142,
|
||||||
|
GG: 248,
|
||||||
|
CC: 39,
|
||||||
|
ME: 242,
|
||||||
|
DO: 60,
|
||||||
|
KR: 0,
|
||||||
|
PL: 174,
|
||||||
|
MT: 148,
|
||||||
|
MM: 141,
|
||||||
|
AW: 17,
|
||||||
|
MV: 150,
|
||||||
|
BD: 21,
|
||||||
|
NR: 164,
|
||||||
|
AT: 15,
|
||||||
|
GW: 92,
|
||||||
|
FR: 74,
|
||||||
|
LI: 126,
|
||||||
|
CF: 41,
|
||||||
|
DZ: 61,
|
||||||
|
MA: 134,
|
||||||
|
VG: 0,
|
||||||
|
NC: 156,
|
||||||
|
IQ: 105,
|
||||||
|
BN: 0,
|
||||||
|
BF: 23,
|
||||||
|
BO: 30,
|
||||||
|
GB: 77,
|
||||||
|
CU: 51,
|
||||||
|
LU: 131,
|
||||||
|
YT: 238,
|
||||||
|
NO: 162,
|
||||||
|
SM: 198,
|
||||||
|
GL: 83,
|
||||||
|
IS: 107,
|
||||||
|
AO: 11,
|
||||||
|
MH: 138,
|
||||||
|
SE: 191,
|
||||||
|
ZM: 241,
|
||||||
|
FJ: 70,
|
||||||
|
SL: 197,
|
||||||
|
CH: 43,
|
||||||
|
RU: 0,
|
||||||
|
CW: 0,
|
||||||
|
CX: 53,
|
||||||
|
TF: 208,
|
||||||
|
NL: 161,
|
||||||
|
AU: 16,
|
||||||
|
FI: 69,
|
||||||
|
MS: 147,
|
||||||
|
GH: 81,
|
||||||
|
BY: 36,
|
||||||
|
IL: 102,
|
||||||
|
VC: 0,
|
||||||
|
NG: 159,
|
||||||
|
HT: 98,
|
||||||
|
LS: 129,
|
||||||
|
MR: 146,
|
||||||
|
YE: 237,
|
||||||
|
MP: 144,
|
||||||
|
SX: 0,
|
||||||
|
RE: 183,
|
||||||
|
RO: 184,
|
||||||
|
NP: 163,
|
||||||
|
CG: 0,
|
||||||
|
FO: 73,
|
||||||
|
CI: 0,
|
||||||
|
TH: 210,
|
||||||
|
HK: 94,
|
||||||
|
TK: 212,
|
||||||
|
XK: 0,
|
||||||
|
DM: 59,
|
||||||
|
LC: 0,
|
||||||
|
ID: 100,
|
||||||
|
MG: 137,
|
||||||
|
JM: 109,
|
||||||
|
IT: 108,
|
||||||
|
CA: 38,
|
||||||
|
TZ: 221,
|
||||||
|
GI: 82,
|
||||||
|
KG: 113,
|
||||||
|
NU: 165,
|
||||||
|
TV: 219,
|
||||||
|
LB: 124,
|
||||||
|
SY: 0,
|
||||||
|
PR: 177,
|
||||||
|
NI: 160,
|
||||||
|
KE: 112,
|
||||||
|
MO: 0,
|
||||||
|
SR: 201,
|
||||||
|
VI: 0,
|
||||||
|
SV: 203,
|
||||||
|
HM: 0,
|
||||||
|
CD: 0,
|
||||||
|
BI: 26,
|
||||||
|
BM: 28,
|
||||||
|
MW: 151,
|
||||||
|
TM: 213,
|
||||||
|
GT: 90,
|
||||||
|
AG: 0,
|
||||||
|
UM: 0,
|
||||||
|
US: 225,
|
||||||
|
AR: 13,
|
||||||
|
DJ: 57,
|
||||||
|
KW: 120,
|
||||||
|
MY: 153,
|
||||||
|
FK: 71,
|
||||||
|
EG: 64,
|
||||||
|
BA: 0,
|
||||||
|
CN: 48,
|
||||||
|
GN: 85,
|
||||||
|
PS: 178,
|
||||||
|
SO: 200,
|
||||||
|
IM: 249,
|
||||||
|
GS: 0,
|
||||||
|
BR: 31,
|
||||||
|
GM: 84,
|
||||||
|
PF: 170,
|
||||||
|
PA: 168,
|
||||||
|
PG: 171,
|
||||||
|
BH: 25,
|
||||||
|
TG: 209,
|
||||||
|
GU: 91,
|
||||||
|
CK: 45,
|
||||||
|
MF: 252,
|
||||||
|
VE: 230,
|
||||||
|
CL: 46,
|
||||||
|
TR: 217,
|
||||||
|
UG: 223,
|
||||||
|
GD: 78,
|
||||||
|
TT: 218,
|
||||||
|
TL: 0,
|
||||||
|
MD: 0,
|
||||||
|
MK: 0,
|
||||||
|
ST: 202,
|
||||||
|
CV: 52,
|
||||||
|
MQ: 145,
|
||||||
|
GR: 88,
|
||||||
|
HR: 97,
|
||||||
|
BZ: 37,
|
||||||
|
UZ: 227,
|
||||||
|
DK: 58,
|
||||||
|
SN: 199,
|
||||||
|
ET: 68,
|
||||||
|
VU: 234,
|
||||||
|
ER: 66,
|
||||||
|
BJ: 27,
|
||||||
|
LK: 127,
|
||||||
|
NA: 155,
|
||||||
|
AS: 14,
|
||||||
|
SG: 192,
|
||||||
|
PE: 169,
|
||||||
|
IR: 0,
|
||||||
|
MX: 152,
|
||||||
|
TD: 207,
|
||||||
|
AZ: 18,
|
||||||
|
AM: 9,
|
||||||
|
BL: 0,
|
||||||
|
SJ: 195,
|
||||||
|
SB: 188,
|
||||||
|
NF: 158,
|
||||||
|
RS: 239,
|
||||||
|
DE: 56,
|
||||||
|
EH: 65,
|
||||||
|
EE: 63,
|
||||||
|
SD: 190,
|
||||||
|
ML: 140,
|
||||||
|
TC: 206,
|
||||||
|
MZ: 154,
|
||||||
|
BS: 32,
|
||||||
|
UY: 226,
|
||||||
|
SI: 194,
|
||||||
|
AI: 7
|
||||||
|
};
|
||||||
|
|
||||||
|
const countryCodeKeys = Object.keys(countryCodes);
|
||||||
|
|
||||||
|
export function getCountryID(code:string) : number {
|
||||||
|
// Get id of a country from a 2 char code
|
||||||
|
/*const upperCode:string = code.toUpperCase();
|
||||||
|
if (code in countryCodes) {
|
||||||
|
return countryCodes[upperCode];
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
227
server/LoginProcess.ts
Normal file
227
server/LoginProcess.ts
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
import { ConsoleHelper } from "../ConsoleHelper";
|
||||||
|
import { Database } from "./objects/Database";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import { getCountryID } from "./Country";
|
||||||
|
import { generateSession } from "./Util";
|
||||||
|
import { LatLng } from "./objects/LatLng";
|
||||||
|
import { LoginInfo } from "./objects/LoginInfo";
|
||||||
|
import { Logout } from "./packets/Logout";
|
||||||
|
import { pbkdf2 } from "crypto";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { UserArray } from "./objects/UserArray";
|
||||||
|
import { User } from "./objects/User";
|
||||||
|
const config:any = JSON.parse(readFileSync(__dirname + "/config.json").toString());
|
||||||
|
const { decrypt: aesDecrypt } = require("aes256");
|
||||||
|
const osu = require("osu-packet");
|
||||||
|
|
||||||
|
function incorrectLoginResponse() {
|
||||||
|
const osuPacketWriter = new osu.Bancho.Writer;
|
||||||
|
osuPacketWriter.LoginReply(-1);
|
||||||
|
return [
|
||||||
|
osuPacketWriter.toBuffer,
|
||||||
|
{
|
||||||
|
'cho-protocol': 19,
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Keep-Alive': 'timeout=5, max=100',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredPWChangeResponse() {
|
||||||
|
const osuPacketWriter = new osu.Bancho.Writer;
|
||||||
|
osuPacketWriter.Announce("As part of migration to a new password system you are required to change your password. Please log in on the website and change your password.");
|
||||||
|
osuPacketWriter.LoginReply(-1);
|
||||||
|
return [
|
||||||
|
osuPacketWriter.toBuffer,
|
||||||
|
{
|
||||||
|
'cho-protocol': 19,
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Keep-Alive': 'timeout=5, max=100',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LoginTypes {
|
||||||
|
CURRENT,
|
||||||
|
OLD_MD5,
|
||||||
|
OLD_AES
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestLogin(loginInfo:LoginInfo | undefined, database:Database) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
// Check if there is any login information provided
|
||||||
|
if (loginInfo == null) return resolve(incorrectLoginResponse());
|
||||||
|
|
||||||
|
const userDBData:any = await database.query("SELECT * FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||||
|
|
||||||
|
// Make sure a user was found in the database
|
||||||
|
if (userDBData == null) return resolve(incorrectLoginResponse());
|
||||||
|
// Make sure the username is the same as the login info
|
||||||
|
if (userDBData.username !== loginInfo.username) return resolve(incorrectLoginResponse());
|
||||||
|
/*
|
||||||
|
1: Old MD5 password
|
||||||
|
2: Old AES password
|
||||||
|
*/
|
||||||
|
if (userDBData.has_old_password === LoginTypes.OLD_MD5) {
|
||||||
|
if (userDBData.password_hash !== loginInfo.password)
|
||||||
|
return resolve(incorrectLoginResponse());
|
||||||
|
|
||||||
|
return resolve(requiredPWChangeResponse());
|
||||||
|
} else if (userDBData.has_old_password === LoginTypes.OLD_AES) {
|
||||||
|
if (aesDecrypt(config.database.key, userDBData.password_hash) !== loginInfo.password)
|
||||||
|
return resolve(resolve(incorrectLoginResponse()));
|
||||||
|
|
||||||
|
return resolve(requiredPWChangeResponse());
|
||||||
|
} else {
|
||||||
|
pbkdf2(loginInfo.password, userDBData.password_salt, config.database.pbkdf2.itterations, config.database.pbkdf2.keylength, "sha512", (err, derivedKey) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
} else {
|
||||||
|
if (derivedKey.toString("hex") !== userDBData.password_hash)
|
||||||
|
return resolve(incorrectLoginResponse());
|
||||||
|
|
||||||
|
return resolve(undefined); // We good
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function LoginProcess(req:Request, res:Response, packet:Buffer, database:Database, users:UserArray) {
|
||||||
|
const loginInfo = LoginInfo.From(packet);
|
||||||
|
const loginStartTime = Date.now();
|
||||||
|
|
||||||
|
const loginCheck:any = await TestLogin(loginInfo, database);
|
||||||
|
if (loginCheck != null) {
|
||||||
|
res.writeHead(200, loginCheck[1]);
|
||||||
|
return res.end(loginCheck[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginInfo == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ConsoleHelper.printBancho(`New client connection. [User: ${loginInfo.username}]`);
|
||||||
|
|
||||||
|
// Get users IP for getting location
|
||||||
|
// Get cloudflare requestee IP first
|
||||||
|
let requestIP = req.get("cf-connecting-ip");
|
||||||
|
|
||||||
|
// Get IP of requestee since we are probably behind a reverse proxy
|
||||||
|
if (requestIP == null)
|
||||||
|
requestIP = req.get("X-Real-IP");
|
||||||
|
|
||||||
|
// Just get the requestee IP (we are not behind a reverse proxy)
|
||||||
|
// if (requestIP == null)
|
||||||
|
// requestIP = req.remote_addr;
|
||||||
|
|
||||||
|
// Make sure requestIP is never null
|
||||||
|
if (requestIP == null)
|
||||||
|
requestIP = "";
|
||||||
|
|
||||||
|
|
||||||
|
let userCountryCode:string, userLocation:LatLng;
|
||||||
|
// Check if it is a local or null IP
|
||||||
|
if (!requestIP.includes("192.168.") && !requestIP.includes("127.0.") && requestIP != "") {
|
||||||
|
// Set location to null island
|
||||||
|
userCountryCode = "XX";
|
||||||
|
userLocation = new LatLng(0, 0);
|
||||||
|
} else {
|
||||||
|
// Get user's location using zxq
|
||||||
|
const userLocationRequest = await fetch(`https://ip.zxq.co/${requestIP}`);
|
||||||
|
const userLocationData:any = await userLocationRequest.json();
|
||||||
|
const userLatLng:Array<string> = userLocationData.loc.split(",");
|
||||||
|
userCountryCode = userLocationData.country;
|
||||||
|
userLocation = new LatLng(parseFloat(userLatLng[0]), parseFloat(userLatLng[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get information about the user from the database
|
||||||
|
const userDB = await database.query("SELECT id FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||||
|
|
||||||
|
// Create a token for the client
|
||||||
|
const newClientToken:string = await generateSession();
|
||||||
|
const isTourneyClient = loginInfo.version.includes("tourney");
|
||||||
|
|
||||||
|
// Make sure user is not already connected, kick off if so.
|
||||||
|
const connectedUser = users.getByUsername(loginInfo.username);
|
||||||
|
if (connectedUser != null && !isTourneyClient && !connectedUser.isTourneyUser) {
|
||||||
|
Logout(connectedUser, database);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retreive the newly created user
|
||||||
|
const newUser:User = users.add(newClientToken, new User(userDB.id, loginInfo.username, newClientToken, database));
|
||||||
|
// Set tourney client flag
|
||||||
|
newUser.isTourneyUser = isTourneyClient;
|
||||||
|
newUser.location = userLocation;
|
||||||
|
|
||||||
|
// Get user's data from the database
|
||||||
|
newUser.updateUserInfo();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Save the country id for the same reason as above
|
||||||
|
newUser.countryID = getCountryID(userCountryCode);
|
||||||
|
|
||||||
|
// We're ready to start putting together a login packet
|
||||||
|
// Create an osu! Packet writer
|
||||||
|
let osuPacketWriter = new osu.Bancho.Writer;
|
||||||
|
|
||||||
|
// The reply id is the user's id in any other case than an error in which case negative numbers are used
|
||||||
|
osuPacketWriter.LoginReply(newUser.id);
|
||||||
|
// Current bancho protocol version. Defined in Binato.js
|
||||||
|
osuPacketWriter.ProtocolNegotiation(19);
|
||||||
|
// Permission level 4 is osu!supporter
|
||||||
|
osuPacketWriter.LoginPermissions(4);
|
||||||
|
|
||||||
|
// After sending the user their friends list send them the online users
|
||||||
|
//UserPresenceBundle(newUser);
|
||||||
|
|
||||||
|
// Set title screen image
|
||||||
|
//osuPacketWriter.TitleUpdate("http://puu.sh/jh7t7/20c04029ad.png|https://osu.ppy.sh/news/123912240253");
|
||||||
|
|
||||||
|
// Add user panel data packets
|
||||||
|
//UserPresence(newUser, newUser.id);
|
||||||
|
//StatusUpdate(newUser, newUser.id);
|
||||||
|
|
||||||
|
// peppy pls, why
|
||||||
|
osuPacketWriter.ChannelListingComplete();
|
||||||
|
|
||||||
|
// Add user to #osu
|
||||||
|
osuPacketWriter.ChannelJoinSuccess("#osu");
|
||||||
|
//if (!Streams.isUserInStream("#osu", newUser.uuid))
|
||||||
|
// Streams.addUserToStream("#osu", newUser.uuid);
|
||||||
|
|
||||||
|
// List all channels out to the client
|
||||||
|
/*for (let i = 0; i < global.channels.length; i++) {
|
||||||
|
osuPacketWriter.ChannelAvailable({
|
||||||
|
channelName: global.channels[i].channelName,
|
||||||
|
channelTopic: global.channels[i].channelTopic,
|
||||||
|
channelUserCount: global.channels[i].channelUserCount
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Construct user's friends list
|
||||||
|
const userFriends = await database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]);
|
||||||
|
let friendsArray = [];
|
||||||
|
for (let i = 0; i < userFriends.length; i++) {
|
||||||
|
friendsArray.push(userFriends[i].friendsWith);
|
||||||
|
}
|
||||||
|
// Send user's friends list
|
||||||
|
osuPacketWriter.FriendsList(friendsArray);
|
||||||
|
|
||||||
|
osuPacketWriter.Announce(`Welcome back ${loginInfo.username}!`);
|
||||||
|
|
||||||
|
res.removeHeader('X-Powered-By');
|
||||||
|
res.removeHeader('Date');
|
||||||
|
// Complete login
|
||||||
|
res.writeHead(200, {
|
||||||
|
"cho-token": newUser.uuid,
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Keep-Alive": "timeout=5, max=100",
|
||||||
|
});
|
||||||
|
res.end(osuPacketWriter.toBuffer, () => {
|
||||||
|
ConsoleHelper.printBancho(`User login finished, took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
3
server/MultiplayerManager.ts
Normal file
3
server/MultiplayerManager.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export abstract class MultiplayerManager {
|
||||||
|
|
||||||
|
}
|
|
@ -5,4 +5,22 @@ function escapeRegExp(string:string) {
|
||||||
|
|
||||||
export function replaceAll(inputString:string, toReplace:string, toReplaceWith:string) {
|
export function replaceAll(inputString:string, toReplace:string, toReplaceWith:string) {
|
||||||
return inputString.replace(`/:${escapeRegExp(toReplace)}:/g`, toReplaceWith);
|
return inputString.replace(`/:${escapeRegExp(toReplace)}:/g`, toReplaceWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
|
|
||||||
|
export function generateSession() : Promise<string> {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
randomBytes(12, (err, buf) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(buf.toString("hex"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateSessionSync() : string {
|
||||||
|
return randomBytes(12).toString("hex");
|
||||||
}
|
}
|
|
@ -5,6 +5,8 @@ export class Database {
|
||||||
private connectionPool:Pool;
|
private connectionPool:Pool;
|
||||||
private static readonly CONNECTION_LIMIT = 128;
|
private static readonly CONNECTION_LIMIT = 128;
|
||||||
|
|
||||||
|
public connected:boolean = false;
|
||||||
|
|
||||||
public constructor(databaseAddress:string, databasePort:number = 3306, databaseUsername:string, databasePassword:string, databaseName:string, connectedCallback:Function) {
|
public constructor(databaseAddress:string, databasePort:number = 3306, databaseUsername:string, databasePassword:string, databaseName:string, connectedCallback:Function) {
|
||||||
this.connectionPool = createPool({
|
this.connectionPool = createPool({
|
||||||
connectionLimit: Database.CONNECTION_LIMIT,
|
connectionLimit: Database.CONNECTION_LIMIT,
|
||||||
|
@ -16,22 +18,32 @@ export class Database {
|
||||||
});
|
});
|
||||||
|
|
||||||
const classCreationTime:number = Date.now();
|
const classCreationTime:number = Date.now();
|
||||||
|
let lastQueryFinished = true;
|
||||||
const connectionCheckInterval = setInterval(() => {
|
const connectionCheckInterval = setInterval(() => {
|
||||||
this.query("SELECT name FROM osu_info LIMIT 1")
|
if (lastQueryFinished) {
|
||||||
|
lastQueryFinished = false;
|
||||||
|
this.query("SELECT name FROM osu_info LIMIT 1")
|
||||||
.then(data => {
|
.then(data => {
|
||||||
ConsoleHelper.printBancho(`Connected to database. Took ${Date.now() - classCreationTime}ms`);
|
if (!this.connected) {
|
||||||
clearInterval(connectionCheckInterval);
|
this.connected = true;
|
||||||
|
ConsoleHelper.printBancho(`Connected to database. Took ${Date.now() - classCreationTime}ms`);
|
||||||
|
clearInterval(connectionCheckInterval);
|
||||||
|
lastQueryFinished = true;
|
||||||
|
|
||||||
connectedCallback();
|
connectedCallback();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {});
|
.catch(err => {
|
||||||
}, 17); // Roughly 6 times per sec
|
lastQueryFinished = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public query(query = "", data?:Array<any>) {
|
public query(query = "", data?:Array<any>) {
|
||||||
const limited = query.includes("LIMIT 1");
|
const limited = query.includes("LIMIT 1");
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
this.connectionPool.getConnection((err, connection) => {
|
this.connectionPool.getConnection((err, connection) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
|
33
server/objects/LoginInfo.ts
Normal file
33
server/objects/LoginInfo.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
export class LoginInfo {
|
||||||
|
public username:string;
|
||||||
|
public password:string;
|
||||||
|
public version:string;
|
||||||
|
public timeOffset:number;
|
||||||
|
// TODO: Parse client data
|
||||||
|
public clientData:string;
|
||||||
|
|
||||||
|
private constructor(username:string, password:string, version:string, timeOffset:number, clientData:string) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.version = version;
|
||||||
|
this.timeOffset = timeOffset;
|
||||||
|
this.clientData = clientData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static From(data:Buffer | string) : LoginInfo | undefined {
|
||||||
|
if (data instanceof Buffer) {
|
||||||
|
data = data.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginData:Array<string> = data.split("\n");
|
||||||
|
const extraData:Array<string> = loginData[2].split("|");
|
||||||
|
|
||||||
|
if (loginData.length !== 4 || extraData.length !== 5) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Parse client data
|
||||||
|
|
||||||
|
return new LoginInfo(loginData[0], loginData[1], extraData[0], parseInt(extraData[1]), extraData[3].split(":")[2]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { Database } from "./Database";
|
import { Database } from "./Database";
|
||||||
import { LatLng } from "./LatLng";
|
import { LatLng } from "./LatLng";
|
||||||
import { RankingModes } from "../enums/RankingModes";
|
import { RankingModes } from "../enums/RankingModes";
|
||||||
const StatusUpdate = require("./Packets/StatusUpdate.js");
|
//const StatusUpdate = require("./Packets/StatusUpdate.js");
|
||||||
|
|
||||||
const rankingModes = [
|
const rankingModes = [
|
||||||
"pp_raw",
|
"pp_raw",
|
||||||
|
@ -89,9 +89,9 @@ export class User {
|
||||||
|
|
||||||
// Gets the user's score information from the database and caches it
|
// Gets the user's score information from the database and caches it
|
||||||
async updateUserInfo(forceUpdate:boolean = false) : Promise<void> {
|
async updateUserInfo(forceUpdate:boolean = false) : Promise<void> {
|
||||||
const userScoreDB:any = await this.dbConnection.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
|
const userScoreDB = await this.dbConnection.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
|
||||||
const mappedRankingMode = rankingModes[this.rankingMode];
|
const mappedRankingMode = rankingModes[this.rankingMode];
|
||||||
const userRankDB:any = await this.dbConnection.query(`SELECT user_id, ${mappedRankingMode} FROM users_modes_info WHERE mode_id = ? ORDER BY ${mappedRankingMode} DESC`, [this.playMode]);
|
const userRankDB = await this.dbConnection.query(`SELECT user_id, ${mappedRankingMode} FROM users_modes_info WHERE mode_id = ? ORDER BY ${mappedRankingMode} DESC`, [this.playMode]);
|
||||||
|
|
||||||
if (userScoreDB == null || userRankDB == null) throw "fuck";
|
if (userScoreDB == null || userRankDB == null) throw "fuck";
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ export class User {
|
||||||
else this.pp = 0;
|
else this.pp = 0;
|
||||||
|
|
||||||
if (userScoreUpdate || forceUpdate) {
|
if (userScoreUpdate || forceUpdate) {
|
||||||
StatusUpdate(this, this.id);
|
//StatusUpdate(this, this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
7
server/packets/Logout.ts
Normal file
7
server/packets/Logout.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { ConsoleHelper } from "../../ConsoleHelper";
|
||||||
|
import { Database } from "../objects/Database";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
|
||||||
|
export async function Logout(user:User, database:Database) {
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
|
15
webpack.config.js
Normal file
15
webpack.config.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const path = require('path');
|
||||||
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
target: 'node',
|
||||||
|
externals: [ nodeExternals() ],
|
||||||
|
entry: './build/Binato.js',
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'bundle'),
|
||||||
|
filename: 'Binato.js',
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in a new issue