From cbe8ec252becbdd60b5297a35dba2e1cd9f35344 Mon Sep 17 00:00:00 2001 From: Patbox <39821509+Patbox@users.noreply.github.com> Date: Wed, 13 Apr 2022 10:51:00 +0200 Subject: [PATCH] Update deno, add support for classicube --- LICENSE | 2 +- README.md | 2 +- server/core/commands.ts | 14 ++-- server/core/deps.ts | 9 ++- server/core/server.ts | 20 ++++-- server/core/types.ts | 7 +- server/deno/deps.ts | 4 +- server/deno/server.ts | 143 +++++++++++++++++++--------------------- 8 files changed, 104 insertions(+), 97 deletions(-) diff --git a/LICENSE b/LICENSE index 3bddddc..53a8339 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Patbox +Copyright (c) 2021, 2022 Patbox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a78e6a2..0541fd0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Cobblestone is a server for Minecraft Classic 0.30. It's written in Typescript a - Multiworld support - Easy to modify - It's written in TypeScript - Custom plugin support -- Works with Betacraft (~~including online mode~~) +- Works with Betacraft (including online mode) - Moddern Mojang UUID support (with online mode) - Stores worlds in [ClassicWorld](https://wiki.vg/ClassicWorld_file_format) format - Uses Deno - no more big `node_modules` folder diff --git a/server/core/commands.ts b/server/core/commands.ts index 9df8966..f36e1c6 100644 --- a/server/core/commands.ts +++ b/server/core/commands.ts @@ -61,8 +61,8 @@ export function setupCommands(server: Server, commands: Map) { let x = 0; let i = 0; const commandArray = Array.from(commands); - - while (true) { + + while (commandArray[x + page * 8]) { const cmd = commandArray[x + page * 8][1]; x += 1; @@ -89,7 +89,6 @@ export function setupCommands(server: Server, commands: Map) { } else { const pages = commands.get(command)?.help ?? []; size = pages.length; - if (size == 0) { return; } @@ -109,8 +108,13 @@ export function setupCommands(server: Server, commands: Map) { ctx.send(`&cThis help page doesn't exist!`); } } catch (e) { - server.logger.error(`${ctx.player?.username ?? 'Console'} tried to excute ${ctx.command} and it failed!`); - server.logger.error(e); + server.logger.error(`${ctx.player?.username ?? 'Console'} tried to execute ${ctx.command} and it failed!`); + if (e instanceof TypeError) { + server.logger.error(e.message); + if (e.stack) server.logger.error(e.stack); + } else { + server.logger.error(e); + } ctx.send('&cError occured while executing this command.'); } } catch { diff --git a/server/core/deps.ts b/server/core/deps.ts index 8b9ebe2..427ef31 100644 --- a/server/core/deps.ts +++ b/server/core/deps.ts @@ -1,8 +1,11 @@ export { gzip, ungzip } from 'https://cdn.skypack.dev/pako'; -export * as uuid from 'https://deno.land/std@0.121.0/uuid/mod.ts'; -export * as uuidHelpers from 'https://deno.land/std@0.121.0/uuid/_common.ts'; +export * as uuid from 'https://deno.land/std@0.122.0/uuid/mod.ts'; +export * as uuidHelpers from 'https://deno.land/std@0.122.0/uuid/_common.ts'; export * as opensimplex from 'https://deno.land/x/open_simplex_noise@v2.5.0/mod.ts'; export * as fractalnoise from 'https://deno.land/x/fractal_noise@v1.2.0/mod.ts'; export * as semver from 'https://deno.land/x/semver@v1.4.0/mod.ts'; export * as msgpack from 'https://deno.land/x/msgpack@v1.4/mod.ts'; -export * as FastNoiseLite from 'https://raw.githubusercontent.com/Auburn/FastNoiseLite/master/JavaScript/FastNoiseLite.js'; \ No newline at end of file +export * as FastNoiseLite from 'https://raw.githubusercontent.com/Auburn/FastNoiseLite/master/JavaScript/FastNoiseLite.js'; + +export * as base64 from 'https://deno.land/std@0.122.0/encoding/base64.ts'; +export * as hex from 'https://deno.land/std@0.122.0/encoding/hex.ts'; \ No newline at end of file diff --git a/server/core/server.ts b/server/core/server.ts index 0492694..0d69aa4 100644 --- a/server/core/server.ts +++ b/server/core/server.ts @@ -14,7 +14,7 @@ export class Server { // Main informations about server static readonly softwareName = 'Cobblestone'; static readonly softwareId = 'cobblestone'; - static readonly softwareVersion = '0.0.16'; + static readonly softwareVersion = '0.0.17'; static readonly targetGame = 'Minecraft Classic'; static readonly targetVersion = '0.30c'; @@ -29,7 +29,7 @@ export class Server { readonly targetProtocol = Server.targetProtocol; // Version of API, goes up when new methods are added - readonly _apiVersion = '0.0.16'; + readonly _apiVersion = '0.0.17'; // Minimal compatible API readonly _minimalApiVersion = '0.0.16'; @@ -320,10 +320,10 @@ export class Server { const result = await this.authenticatePlayer({ uuid: overrides?.uuid ?? playerInfo.username.toLowerCase(), username: overrides?.username ?? playerInfo.username, + authProvider: overrides?.authProvider ?? 'None', service: overrides?.service ?? 'Minecraft', secret: overrides?.secret ?? playerInfo.key, authenticated: overrides?.authenticated ?? false, - subService: overrides?.subService ?? null, }); if (result.allow) { @@ -332,11 +332,11 @@ export class Server { return; } - const subService = result.auth.subService ? `/${result.auth.subService}` : ''; + const authProvider = result.auth.authProvider ? `/${result.auth.authProvider}` : ''; this.logger.conn( result.auth.service == 'Unknown' ? `User ${result.auth.username} (${result.auth.uuid}) doesn't use any auth...` - : `User ${result.auth.username} (${result.auth.uuid}) is logged with ${result.auth.service}${subService} auth!` + : `User ${result.auth.username} (${result.auth.uuid}) is logged with ${result.auth.service} (${authProvider}) auth!` ); const player = new Player( @@ -924,13 +924,16 @@ export interface IConfig { defaultWorldName: string; - classicOnlineMode: boolean; + onlineMode: boolean; //useMineOnlineHeartbeat: boolean; //publicOnMineOnline: boolean; useBetaCraftHeartbeat: boolean; publicOnBetaCraft: boolean; + useClassiCubeHeartbeat: boolean; + publicOnClassiCube: boolean; + allowOffline: boolean; messages: { @@ -960,7 +963,7 @@ const defaultConfig: IConfig = { defaultWorldName: 'main', - classicOnlineMode: false, + onlineMode: true, //useMineOnlineHeartbeat: false, //publicOnMineOnline: false, @@ -968,6 +971,9 @@ const defaultConfig: IConfig = { useBetaCraftHeartbeat: false, publicOnBetaCraft: false, + useClassiCubeHeartbeat: false, + publicOnClassiCube: false, + allowOffline: true, messages: { diff --git a/server/core/types.ts b/server/core/types.ts index a785da5..7cae895 100644 --- a/server/core/types.ts +++ b/server/core/types.ts @@ -13,14 +13,15 @@ export interface Position { export type Nullable = null | T; -export type Services = 'Unknown' | 'Minecraft' | 'VoxelSrv' | 'ClassiCube'; +export type Services = 'Unknown' | 'Minecraft' | 'ClassiCube'; + +export type AuthProvider = 'None' | 'Mojang' | 'Betacraft' | 'ClassiCube'; -export type SubServices = 'Betacraft'; export interface AuthData { username: string; + authProvider: AuthProvider; service: Services; - subService: Nullable; uuid: Nullable; secret: Nullable; authenticated: boolean; diff --git a/server/deno/deps.ts b/server/deno/deps.ts index 2fe1135..2e5fdbf 100644 --- a/server/deno/deps.ts +++ b/server/deno/deps.ts @@ -1,3 +1,3 @@ export * as fs from 'https://deno.land/std@0.121.0/fs/mod.ts'; -export { createHash } from 'https://deno.land/std@0.121.0/hash/mod.ts'; -export { serve, serveTls } from 'https://deno.land/std@0.121.0/http/server.ts'; \ No newline at end of file +export { serve, serveTls } from 'https://deno.land/std@0.121.0/http/server.ts'; +export { crypto as crypto2 } from 'https://deno.land/std@0.121.0/_wasm_crypto/mod.ts'; \ No newline at end of file diff --git a/server/deno/server.ts b/server/deno/server.ts index 1e70c11..9a8f995 100644 --- a/server/deno/server.ts +++ b/server/deno/server.ts @@ -2,37 +2,28 @@ import { TpcConnectionHandler } from './networking/connection.ts'; import { Player, PlayerData } from '../core/player.ts'; import { IFileHelper, ILogger, Server } from '../core/server.ts'; import { World } from '../core/world/world.ts'; -import { fs, createHash } from './deps.ts'; -import { gzip, ungzip, msgpack, semver, uuid } from '../core/deps.ts'; -import { AuthData, Nullable, SubServices } from '../core/types.ts'; +import { fs, crypto2 } from './deps.ts'; +import { gzip, ungzip, msgpack, semver } from '../core/deps.ts'; +import { AuthData, AuthProvider, Nullable, Services } from '../core/types.ts'; const textEncoder = new TextEncoder(); export const defaultFolders = ['world', 'player', 'config', 'logs', 'plugins']; export class DenoServer extends Server { - //protected _saltMineOnline: string; - protected _saltBetaCraft: string; + protected _salt = >{}; _serverIcon: string | undefined; protected _shouldLoadPlugins: boolean; - static readonly denoVersion = '1.17'; - static readonly denoVersionMin = '1.17.0'; - static readonly denoVersionMax = '1.18.0'; + static readonly denoVersion = '1.20.x'; + static readonly denoVersionMin = '1.20.0'; + static readonly denoVersionMax = '1.21.0'; constructor(loadPlugins = true, devMode = false) { super(fileHelper, logger, devMode); this._shouldLoadPlugins = loadPlugins; - /*{ - const hash = createHash('md5'); - hash.update(uuid.v4()); - this._saltMineOnline = hash.toString(); - }*/ - { - const hash = createHash('md5'); - hash.update(uuid.v4.generate()); - this._saltBetaCraft = hash.toString(); - } + this._salt["Betacraft"] = crypto.randomUUID().replaceAll("-", ""); + this._salt["ClassiCube"] = crypto.randomUUID().replaceAll("-", ""); } async _startServer() { @@ -57,8 +48,7 @@ export class DenoServer extends Server { this._serverIcon = btoa(String.fromCharCode.apply(null, [...file])); } } catch (e) { - this.logger.error('Server icon (server-icon.png) is invalid!'); - this.logger.error(e); + this.logger.warn('Server icon (server-icon.png) is invalid or doesn\'t exist!'); } const listener = Deno.listen({ port: this.config.port }); @@ -76,15 +66,19 @@ export class DenoServer extends Server { this.logger.log(`&aListenning to connections on port ${this.config.port}`); - Deno.addSignalListener('SIGTERM', () => { - if (this.isShuttingDown) { - return; - } + try { + Deno.addSignalListener('SIGTERM', () => { + if (this.isShuttingDown) { + return; + } - this.stopServer(); + this.stopServer(); - setTimeout(() => Deno.exit(), 500); - }); + setTimeout(() => Deno.exit(), 500); + }); + } catch (e) { + //noop + } (async () => { const buf = new Uint8Array(1024); @@ -103,59 +97,37 @@ export class DenoServer extends Server { } })(); - const f = () => { + const heartBeat = () => { const players: string[] = []; Object.values(this.players).forEach((p) => players.push((p).username)); - /*try { - if (this.config.publicOnMineOnline) { - const obj = { - name: this.config.serverName, - ip: this.config.address, - port: this.config.port, - onlinemode: this.config.classicOnlineMode, - 'verify-names': this.config.classicOnlineMode, - md5: '90632803F45C15164587256A08C0ECB4', - whitelisted: false, - max: this.config.maxPlayerCount, - motd: this.config.serverMotd, - serverIcon: this._serverIcon, - players, - }; - - fetch('https://mineonline.codie.gg/api/servers', { - method: 'POST', - body: JSON.stringify(obj), - headers: { 'Content-Type': 'application/json' }, - }); - } - - if (this.config.useMineOnlineHeartbeat) { + try { + if (this.config.useBetaCraftHeartbeat) { fetch( - `https://mineonline.codie.gg/heartbeat.jsp?port=${this.config.port}&max=${this.config.maxPlayerCount}&name=${escape( + `https://betacraft.pl/heartbeat.jsp?port=${this.config.port}&max=${this.config.maxPlayerCount}&name=${escape( this.config.serverName - )}&public=${this.config.publicOnMineOnline ? 'True' : 'False'}&version=7&salt=${this._saltMineOnline}&users=${players.length}` + )}&public=${this.config.publicOnBetaCraft ? 'True' : 'False'}&version=7&salt=${this._salt["Betacraft"]}&users=${players.length}` ); } - } catch (e) { - this.logger.warn(`Couldn't send heartbeat to MineOnline!`); - }*/ + } catch (_e) { + this.logger.warn(`Couldn't send heartbeat to BetaCraft!`); + } try { - if (this.config.useBetaCraftHeartbeat) { + if (this.config.useClassiCubeHeartbeat) { fetch( - `https://betacraft.pl/heartbeat.jsp?port=${this.config.port}&max=${this.config.maxPlayerCount}&name=${escape( + `http://www.classicube.net/api/server/heartbeat/?port=${this.config.port}&max=${this.config.maxPlayerCount}&name=${escape( this.config.serverName - )}&public=${this.config.publicOnBetaCraft ? 'True' : 'False'}&version=7&salt=${this._saltBetaCraft}&users=${players.length}` + )}&public=${this.config.publicOnClassiCube ? 'True' : 'False'}&version=7&salt=${this._salt["ClassiCube"]}&users=${players.length}` ); } } catch (_e) { - this.logger.warn(`Couldn't send heartbeat to BetaCraft!`); + this.logger.warn(`Couldn't send heartbeat to ClassiCube!`); } }; - setTimeout(f, 2000); - setInterval(f, 1000 * 60); + setTimeout(heartBeat, 2000); + setInterval(heartBeat, 1000 * 60); } stopServer() { @@ -169,16 +141,25 @@ export class DenoServer extends Server { return { allow: true, auth: data }; } - if (this.config.classicOnlineMode) { - let subService: Nullable = null; + if (this.config.onlineMode) { + const encoder = new TextEncoder(); + const decoder = new TextDecoder(); + let service: Nullable = null; + let authProvider: AuthProvider = 'None'; + + const classicCheck = (provider: AuthProvider) => { + return decoder.decode(crypto2.digest("MD5", encoder.encode(this._salt[provider] + data.username), undefined)) == data.secret; + } - const hash = createHash('md5'); - hash.update(this._saltBetaCraft + data.username); - if (hash.toString() == data.secret) { - subService = 'Betacraft'; + if (classicCheck("Betacraft")) { + service = "Minecraft"; + authProvider = "Betacraft"; + } else if (classicCheck("ClassiCube")) { + service = "ClassiCube"; + authProvider = "ClassiCube"; } - if (subService != null) { + if (service == 'Minecraft') { const moj: { id: string; name: string; error?: string } = await ( await fetch('https://api.mojang.com/users/profiles/minecraft/' + data.username) ).json(); @@ -192,22 +173,34 @@ export class DenoServer extends Server { service: 'Minecraft', secret: null, authenticated: true, - subService: subService, + authProvider: authProvider, }, }; } + } else if (service != null) { + return { + allow: true, + auth: { + uuid: authProvider.toLowerCase() + '-' + data.username, + username: data.username, + service: service, + secret: null, + authenticated: true, + authProvider: authProvider, + }, + }; } } - if (this.config.allowOffline || !this.config.classicOnlineMode) { + if (this.config.allowOffline || !this.config.onlineMode) { return { auth: { - username: this.config.classicOnlineMode ? `*${data.username}` : data.username, + username: this.config.onlineMode ? `*${data.username}` : data.username, uuid: 'offline-' + data.username.toLowerCase(), secret: null, service: 'Unknown', authenticated: true, - subService: null, + authProvider: "None", }, allow: true, }; @@ -228,7 +221,7 @@ export class DenoServer extends Server { const colorsTag = /&[0-9a-fl-or]/gi; -export const logger: ILogger & { writeToLog: (t: string) => void; reopenFile: () => void; file?: Deno.File; openedAt?: number } = { +export const logger: ILogger & { writeToLog: (t: string) => void; reopenFile: () => void; file?: Deno.FsFile; openedAt?: number } = { log: (text: string) => { const out = `&8[&f${hourNow()}&8] &f${text}`;