Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions apps/scheduler/src/addDefaultMasterServers.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import { prisma } from "./prisma";

// 0.6 masters listen on port 8300, 0.7 masters on port 8283
export async function addDefaultMasterServers() {
await prisma.masterServer.createMany({
data: [
data: [1, 2, 3, 4].flatMap((index) => [
{
address: 'master1.teeworlds.com',
address: `master${index}.teeworlds.com`,
port: 8300,
},
{
address: 'master2.teeworlds.com',
port: 8300,
},
{
address: 'master3.teeworlds.com',
port: 8300,
},
{
address: 'master4.teeworlds.com',
port: 8300,
address: `master${index}.teeworlds.com`,
port: 8283,
},
],
]),
skipDuplicates: true,
})
}
104 changes: 92 additions & 12 deletions apps/worker/src/packet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,30 @@ export function unpackBool(packet: Packet): boolean {
return parseInt(unpackString(packet), 10) !== 0;
}

// 0.7 packed int format: ESDDDDDD EDDDDDDD EDD... (E: extend, S: sign, D: data)
export function unpackInt7(packet: Packet): number {
const masks = [0x7f, 0x7f, 0x7f, 0x0f];
const shifts = [6, 13, 20, 27];

const first = packet.data[packet.offset];
const sign = (first >> 6) & 1;
let value = first & 0x3f;

for (let i = 0; packet.data[packet.offset] & 0x80 && i < masks.length; i += 1) {
packet.offset += 1;
value |= (packet.data[packet.offset] & masks[i]) << shifts[i];
}

packet.offset += 1;
return sign ? ~value : value;
}

export enum ServerHeader {
Vanilla,
Legacy64,
Extended,
ExtendedMore,
Vanilla7,
}

export function headerBuffer(header: string): Buffer {
Expand All @@ -59,17 +78,39 @@ const SERVER_HEADER_LEGACY64 = headerBuffer('dtsf');
const SERVER_HEADER_EXTENDED = headerBuffer('iext');
const SERVER_HEADER_EXTENDED_MORE = headerBuffer('iex+');

export function unpackServerHeader(packet: Packet): ServerHeader {
unpackBytes(packet, 6);
const header = unpackBytes(packet, 8);
// 0.6 connless packets start with 6 padding bytes, 0.7 ones with a 9 bytes
// header: flags/version byte then two 4 bytes tokens. Returns undefined for
// anything else, like 0.7 control packets.
function unpackConnlessHeader(packet: Packet): { magic: Buffer, version7: boolean } | undefined {
if (packet.data[0] === 0xff) {
unpackBytes(packet, 6);
return { magic: unpackBytes(packet, 8), version7: false };
}

if ((packet.data[0] & 0xfc) >> 2 === 0x08) {
unpackBytes(packet, 9);
return { magic: unpackBytes(packet, 8), version7: true };
}

return undefined;
}

export function unpackServerHeader(packet: Packet): ServerHeader | undefined {
const connlessHeader = unpackConnlessHeader(packet);

if (header.equals(SERVER_HEADER_VANILLA)) {
return ServerHeader.Vanilla;
} else if (header.equals(SERVER_HEADER_LEGACY64)) {
if (connlessHeader === undefined) {
return undefined;
}

const { magic, version7 } = connlessHeader;

if (magic.equals(SERVER_HEADER_VANILLA)) {
return version7 ? ServerHeader.Vanilla7 : ServerHeader.Vanilla;
} else if (magic.equals(SERVER_HEADER_LEGACY64)) {
return ServerHeader.Legacy64;
} else if (header.equals(SERVER_HEADER_EXTENDED)) {
} else if (magic.equals(SERVER_HEADER_EXTENDED)) {
return ServerHeader.Extended;
} else if (header.equals(SERVER_HEADER_EXTENDED_MORE)) {
} else if (magic.equals(SERVER_HEADER_EXTENDED_MORE)) {
return ServerHeader.ExtendedMore;
}

Expand All @@ -82,13 +123,52 @@ export enum MasterHeader {

const MASTER_HEADER_VANILLA = headerBuffer('lis2');

export function unpackMasterHeader(packet: Packet): MasterHeader {
unpackBytes(packet, 6);
const header = unpackBytes(packet, 8);
export function unpackMasterHeader(packet: Packet): MasterHeader | undefined {
const connlessHeader = unpackConnlessHeader(packet);

if (header.equals(MASTER_HEADER_VANILLA)) {
if (connlessHeader === undefined) {
return undefined;
}

if (connlessHeader.magic.equals(MASTER_HEADER_VANILLA)) {
return MasterHeader.Vanilla;
}

throw new Error('Invalid master header');
}

const CTRLMSG_TOKEN = 0x05;

export function randomToken(): number {
return Math.floor(Math.random() * 0xfffffffe);
}

// 0.7 requires a token handshake before answering connless requests: send a
// control message with our token, padded to 512 bytes, and the server replies
// with the token to use in packConnless7().
export function packCtrlTokenRequest(myToken: number): Buffer {
const buffer = Buffer.alloc(7 + 1 + 512);
buffer[0] = 0x04;
buffer.writeUInt32BE(0xffffffff, 3);
buffer[7] = CTRLMSG_TOKEN;
buffer.writeUInt32BE(myToken, 8);
return buffer;
}

export function peekCtrlToken(packet: Packet): number | undefined {
const { data } = packet;

if ((data[0] & 0xfc) >> 2 !== 0x01 || data.length < 12 || data[7] !== CTRLMSG_TOKEN) {
return undefined;
}

return data.readUInt32BE(8);
}

export function packConnless7(serverToken: number, myToken: number, payload: Buffer): Buffer {
const header = Buffer.alloc(9);
header[0] = 0x21;
header.writeUInt32BE(serverToken, 1);
header.writeUInt32BE(myToken, 5);
return Buffer.concat([header, payload]);
}
71 changes: 62 additions & 9 deletions apps/worker/src/packets/gameServerInfo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Packet, ServerHeader, packetIsConsumed, unpackBool, unpackInt, unpackServerHeader, unpackString } from "../packet";
import { Packet, ServerHeader, packetIsConsumed, unpackBool, unpackInt, unpackInt7, unpackServerHeader, unpackString } from "../packet";

type Client = {
name: string;
Expand Down Expand Up @@ -58,19 +58,30 @@ function initGameServerInfoPacket(): GameServerInfoPacket {
};
}

function unpackGameServerInfoPacket(packet: Packet, gameServerInfoPacket: GameServerInfoPacket) {
function unpackGameServerInfoPacket(packet: Packet, gameServerInfoPacket: GameServerInfoPacket): boolean {
const header = unpackServerHeader(packet);

switch (header) {
case undefined:
return false;
case ServerHeader.Vanilla:
return unpackGameServerInfoVanilla(packet, gameServerInfoPacket);
unpackGameServerInfoVanilla(packet, gameServerInfoPacket);
break;
case ServerHeader.Legacy64:
return unpackGameServerInfoLegacy64(packet, gameServerInfoPacket);
unpackGameServerInfoLegacy64(packet, gameServerInfoPacket);
break;
case ServerHeader.Extended:
return unpackGameServerInfoExtended(packet, gameServerInfoPacket);
unpackGameServerInfoExtended(packet, gameServerInfoPacket);
break;
case ServerHeader.ExtendedMore:
return unpackGameServerInfoExtendedMore(packet, gameServerInfoPacket);
unpackGameServerInfoExtendedMore(packet, gameServerInfoPacket);
break;
case ServerHeader.Vanilla7:
unpackGameServerInfoVanilla7(packet, gameServerInfoPacket);
break;
}

return true;
}

function unpackGameServerInfoVanilla(packet: Packet, gameServerInfoPacket: GameServerInfoPacket) {
Expand Down Expand Up @@ -211,12 +222,54 @@ function unpackGameServerInfoExtendedMore(packet: Packet, gameServerInfoPacket:
}
}

export function unpackGameServerInfoPackets(packets: Packet[]): GameServerInfoPacket {
function unpackGameServerInfoVanilla7(packet: Packet, gameServerInfoPacket: GameServerInfoPacket) {
unpackInt7(packet); // token

gameServerInfoPacket.version = unpackString(packet);
gameServerInfoPacket.name = unpackString(packet);

unpackString(packet); // hostname

gameServerInfoPacket.map = unpackString(packet);
gameServerInfoPacket.gameType = unpackString(packet);

unpackInt7(packet); // flags
unpackInt7(packet); // skill level

gameServerInfoPacket.numPlayers = unpackInt7(packet);
gameServerInfoPacket.maxPlayers = unpackInt7(packet);

gameServerInfoPacket.numClients = unpackInt7(packet);
gameServerInfoPacket.maxClients = unpackInt7(packet);

while (!packetIsConsumed(packet)) {
const name = unpackString(packet);
const clan = unpackString(packet);
const country = unpackInt7(packet);
const score = unpackInt7(packet);
const playerType = unpackInt7(packet); // 1: spectator, 2: bot

addClient(gameServerInfoPacket, {
name,
clan,
country,
score,
inGame: (playerType & 1) === 0,

_origin: ServerHeader.Vanilla7,
});
}
}

export function unpackGameServerInfoPackets(packets: Packet[]): GameServerInfoPacket | undefined {
const gameServerInfoPacket = initGameServerInfoPacket();
let unpackedCount = 0;

for (const packet of packets) {
unpackGameServerInfoPacket(packet, gameServerInfoPacket);
if (unpackGameServerInfoPacket(packet, gameServerInfoPacket)) {
unpackedCount += 1;
}
}

return gameServerInfoPacket;
return unpackedCount > 0 ? gameServerInfoPacket : undefined;
}
2 changes: 2 additions & 0 deletions apps/worker/src/packets/masterServerInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ function unpackMasterPacket(packet: Packet): MasterServerPacketInfo {
const header = unpackMasterHeader(packet);

switch (header) {
case undefined:
return { gameServers: [] };
case MasterHeader.Vanilla:
return unpackMasterVanillaContent(packet);
}
Expand Down
Loading
Loading