From d6e0dc579b7ce46a0fbd3b3860f4f9e1091ac656 Mon Sep 17 00:00:00 2001 From: Cjx8848 Date: Fri, 30 Jan 2026 14:30:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/dimensions/clientcommandhandler.ts | 86 +++-- app/dimensions/clientpackethandler.ts | 172 ++++++--- app/dimensions/configloader.ts | 232 ++++++++---- app/dimensions/language.ts | 128 ++++--- app/dimensions/listenserver.ts | 242 ++++++++----- app/dimensions/terrariaserver.ts | 125 ++++--- app/dimensions/terrariaserverpackethandler.ts | 337 ++++++++++++------ package-lock.json | 81 +---- 8 files changed, 896 insertions(+), 507 deletions(-) diff --git a/app/dimensions/clientcommandhandler.ts b/app/dimensions/clientcommandhandler.ts index 9f442ea..bec484d 100644 --- a/app/dimensions/clientcommandhandler.ts +++ b/app/dimensions/clientcommandhandler.ts @@ -1,5 +1,5 @@ -import Client from './client.js'; -import ClientState from './clientstate.js'; +import Client from "./client.js"; +import ClientState from "./clientstate.js"; export interface Command { name: string; @@ -13,12 +13,12 @@ export class ClientCommandHandler { /** * Turns a message into a command object, splitting the command name * from its arguments. - * + * * @param message The message to convert into a command object * @return The command object created */ public parseCommand(message: string): Command { - let args: string[] = message.split(' '); + let args: string[] = message.split(" "); let name: string = message.substr(1, args[0].length - 1); // Remove first arg as it is the command name @@ -28,47 +28,64 @@ export class ClientCommandHandler { /** * Handles any matching command from the client - * + * * @param command The command object with the name and args * @param client The client that is trying to use the command * @return whether or not the command was handled */ public handle(command: Command, client: Client): boolean { - let handled: boolean = false; + //let handled: boolean = false; if (client.servers[command.name]) { - if (client.server.name.toLowerCase() == command.name && client.connected) { - client.sendChatMessage(client.options.language.phrases.youAreAlreadyInthatDimension); + if ( + client.server.name.toLowerCase() == command.name && + client.connected + ) { + client.sendChatMessage( + client.options.language.phrases.youAreAlreadyInthatDimension, + ); } else { - if (client.state === ClientState.FullyConnected || client.state === ClientState.Disconnected) { - client.sendChatMessage(client.options.language.phrases.shiftingToDimension.replace("${name}", client.servers[command.name].name), "FF0000"); + if ( + client.state === ClientState.FullyConnected || + client.state === ClientState.Disconnected + ) { + client.sendChatMessage( + client.options.language.phrases.shiftingToDimension.replace( + "${name}", + client.servers[command.name].name, + ), + "FF0000", + ); client.changeServer(client.servers[command.name]); } else { - client.sendChatMessage(client.options.language.phrases.youNeedToWaitUntilConnected); + client.sendChatMessage( + client.options.language.phrases.youNeedToWaitUntilConnected, + ); } } - handled = true; + //handled = true; } else { switch (command.name) { case "who": - handled = this.handleWho(client); - break; - case "dimensions": - case client.options.language.phrases.dimensionsCommandName: - handled = this.handleDimensions(client); + this.handleWho(client); break; + //case "dimensions": + //case client.options.language.phrases.dimensionsCommandName: + //this.handleDimensions(client); + //break; + //CSFT case "void": - handled = this.handleVoid(client); + this.handleVoid(client); break; } } - - return handled; + //服务器处理 + return false; } /** * Adds a message denoting how many users exist in total on this Dimensions instance - * + * * @param args The command args * @param client The client executing who * @return Whether or not the who command was handled @@ -81,19 +98,20 @@ export class ClientCommandHandler { } // Try to make it come after the normal response - setTimeout(function() { - client.sendChatMessage(client.options.language.phrases.playerCount.replace("${total}", total.toString())); + //CSFT - 修改 + setTimeout(function () { + client.sendChatMessage( + client.options.language.phrases.playerCount.replace( + "${total}", + total.toString(), + ), + "00BFFF", + ); }, 100); return false; } - /** - * Gives the client a list of dimensions available prefixed with '/' - * - * @param args The command args - * @param client The client who is executing the command - * @return Whether the dimensions command was handled - */ + /* private handleDimensions(client: Client): boolean { let dimensionsList: string = ""; let dimensionNames: string[] = Object.keys(client.servers); @@ -101,7 +119,8 @@ export class ClientCommandHandler { let name: string = dimensionNames[i]; let hidden: boolean = client.servers[name].hidden; if (!hidden) { - dimensionsList += (i > 0 ? "[c/00B530:,] " : " ") + "/" + client.servers[name].name; + dimensionsList += + (i > 0 ? "[c/00B530:,] " : " ") + "/" + client.servers[name].name; } } @@ -111,9 +130,10 @@ export class ClientCommandHandler { return true; } + */ /** * Allows a user to disconnect from their current dimension leaving them without a server - * + * * @param args The command args * @param client The client executing the void command * @return Whether the void command was handled @@ -123,6 +143,6 @@ export class ClientCommandHandler { client.sendChatMessage(client.options.language.phrases.youEnteredTheVoid); return true; } -}; +} export default ClientCommandHandler; diff --git a/app/dimensions/clientpackethandler.ts b/app/dimensions/clientpackethandler.ts index 84887d3..ea9bb39 100644 --- a/app/dimensions/clientpackethandler.ts +++ b/app/dimensions/clientpackethandler.ts @@ -1,11 +1,26 @@ -import Item from './item.js'; -import Client from './client.js'; -import RawPacket from './packets/rawpacket.js'; -import { Command } from './clientcommandhandler.js'; -import ClientState from './clientstate.js'; -import ErrorHelper from './errorhelper.js'; - -import { ConnectRequestPacket, PlayerInfoPacket, PlayerBuffsSetPacket, PlayerBuffAddPacket, PlayerInventorySlotPacket, PlayerManaPacket, PlayerHealthPacket, PlayerUpdatePacket, ClientUuidPacket, NetModuleLoadPacket, ItemDropUpdatePacket, ItemOwnerPacket, PlayerSpawnPacket, Parser, } from "terraria-packet"; +import Item from "./item.js"; +import Client from "./client.js"; +import RawPacket from "./packets/rawpacket.js"; +import { Command } from "./clientcommandhandler.js"; +import ClientState from "./clientstate.js"; +import ErrorHelper from "./errorhelper.js"; + +import { + ConnectRequestPacket, + PlayerInfoPacket, + PlayerBuffsSetPacket, + PlayerBuffAddPacket, + PlayerInventorySlotPacket, + PlayerManaPacket, + PlayerHealthPacket, + PlayerUpdatePacket, + ClientUuidPacket, + NetModuleLoadPacket, + ItemDropUpdatePacket, + ItemOwnerPacket, + PlayerSpawnPacket, + Parser, +} from "terraria-packet"; class ClientPacketHandler { private currentClient!: Client; @@ -16,9 +31,15 @@ class ClientPacketHandler { let handled = false; for (let key in handlers) { let handler = handlers[key]; - if (typeof handler.priorPacketHandlers !== 'undefined' && typeof handler.priorPacketHandlers.clientHandler !== 'undefined') { + if ( + typeof handler.priorPacketHandlers !== "undefined" && + typeof handler.priorPacketHandlers.clientHandler !== "undefined" + ) { try { - handled = handler.priorPacketHandlers.clientHandler.handlePacket(client, packet); + handled = handler.priorPacketHandlers.clientHandler.handlePacket( + client, + packet, + ); if (handled) { break; } @@ -41,9 +62,15 @@ class ClientPacketHandler { let handled = false; for (let key in handlers) { let handler = handlers[key]; - if (typeof handler.postPacketHandlers !== 'undefined' && typeof handler.postPacketHandlers.clientHandler !== 'undefined') { + if ( + typeof handler.postPacketHandlers !== "undefined" && + typeof handler.postPacketHandlers.clientHandler !== "undefined" + ) { try { - handled = handler.postPacketHandlers.clientHandler.handlePacket(client, packet); + handled = handler.postPacketHandlers.clientHandler.handlePacket( + client, + packet, + ); if (handled) { break; } @@ -76,24 +103,30 @@ class ClientPacketHandler { switch (parsed.TAG) { case "ReaderError": if (parsed._0.error instanceof Error) { - client.logging.error(`Error parsing packet: ${parsed._0.context} ${parsed._0.error.message}`); + client.logging.error( + `Error parsing packet: ${parsed._0.context} ${parsed._0.error.message}`, + ); } else { - client.logging.error(`Error parsing packet: ${parsed._0.context}`); + client.logging.error( + `Error parsing packet: ${parsed._0.context}`, + ); } break; default: client.logging.error(`Error parsing packet: ${parsed.TAG}`); break; } - return null + return null; } else { switch (parsed) { case "IgnoredPacket": client.logging.info(`Ignoring packet: ${rawPacket.packetType}`); break; default: - client.logging.error(`Error parsing packet: ${rawPacket.packetType} ${parsed}`); - return null + client.logging.error( + `Error parsing packet: ${rawPacket.packetType} ${parsed}`, + ); + return null; break; } } @@ -171,7 +204,9 @@ class ClientPacketHandler { return rawPacket.data; } - private handleConnectRequest(connectRequest: ConnectRequestPacket.t): boolean { + private handleConnectRequest( + connectRequest: ConnectRequestPacket.t, + ): boolean { if (this.currentClient.version === "unknown") { this.currentClient.version = connectRequest?.version ?? "unknown"; } @@ -181,13 +216,25 @@ class ClientPacketHandler { /* Updates tracked visuals for player to restore them when they switch from * an SSC to a non-SSC server */ - private handlePlayerInfo(playerInfo: PlayerInfoPacket.t, rawPacket: RawPacket): boolean { + private handlePlayerInfo( + playerInfo: PlayerInfoPacket.t, + rawPacket: RawPacket, + ): boolean { const player = this.currentClient.player; if (player.name !== playerInfo.name) { if (player.allowedNameChange) { - this.currentClient.setName(playerInfo.name); + // CSFT - 修改 + if (player.name !== "") { + this.currentClient.disconnect("禁止在传送期间修改名称!"); + } else { + this.currentClient.setName(playerInfo.name); + } } else if (this.currentClient.options.nameChanges?.mode === "rewrite") { - const data = PlayerInfoPacket.toBuffer({ ...playerInfo, playerId: this.currentClient.player.id, name: player.name }); + const data = PlayerInfoPacket.toBuffer({ + ...playerInfo, + playerId: this.currentClient.player.id, + name: player.name, + }); if (data.TAG === "Ok") { rawPacket.data = data._0; } @@ -220,7 +267,10 @@ class ClientPacketHandler { /* Used to prevent invisibility buff from being sent to the server * for used when the config is set to blockInvis = true */ - private handleUpdatePlayerBuff(playerBuffsSet: PlayerBuffsSetPacket.t, rawPacket: RawPacket): boolean { + private handleUpdatePlayerBuff( + playerBuffsSet: PlayerBuffsSetPacket.t, + rawPacket: RawPacket, + ): boolean { let shouldBlockInvis = false; const blockInvis = this.currentClient.options.blockInvis; switch (blockInvis) { @@ -230,7 +280,13 @@ class ClientPacketHandler { case false: break; default: - shouldBlockInvis = blockInvis.enabled && blockInvis.servers.some(server => server.toLowerCase() === this.currentClient.server.name.toLowerCase()) + shouldBlockInvis = + blockInvis.enabled && + blockInvis.servers.some( + (server) => + server.toLowerCase() === + this.currentClient.server.name.toLowerCase(), + ); break; } @@ -242,9 +298,14 @@ class ClientPacketHandler { return buff; }); - const buf = PlayerBuffsSetPacket.toBuffer({ playerId: this.currentClient.player.id, buffs }) + const buf = PlayerBuffsSetPacket.toBuffer({ + playerId: this.currentClient.player.id, + buffs, + }); if (buf.TAG === "Error") { - this.currentClient.logging.error(`Error creating player buffs set: ${buf._0}`); + this.currentClient.logging.error( + `Error creating player buffs set: ${buf._0}`, + ); return true; } rawPacket.data = buf._0; @@ -271,7 +332,13 @@ class ClientPacketHandler { case false: break; default: - shouldBlockInvis = blockInvis.enabled && blockInvis.servers.some(server => server.toLowerCase() === this.currentClient.server.name.toLowerCase()) + shouldBlockInvis = + blockInvis.enabled && + blockInvis.servers.some( + (server) => + server.toLowerCase() === + this.currentClient.server.name.toLowerCase(), + ); break; } @@ -284,10 +351,21 @@ class ClientPacketHandler { /* Tracks the players inventory slots to restore them when they switch * from an SSC server to a Non-SSC server */ - private handlePlayerInventorySlot(playerInventorySlot: PlayerInventorySlotPacket.t): boolean { - if ((this.currentClient.state === ClientState.FreshConnection || this.currentClient.state === ClientState.ConnectionSwitchEstablished) && !this.currentClient.waitingCharacterRestore) { + private handlePlayerInventorySlot( + playerInventorySlot: PlayerInventorySlotPacket.t, + ): boolean { + if ( + (this.currentClient.state === ClientState.FreshConnection || + this.currentClient.state === ClientState.ConnectionSwitchEstablished) && + !this.currentClient.waitingCharacterRestore + ) { const { slot, stack, prefix, itemType } = playerInventorySlot; - this.currentClient.player.inventory[slot] = new Item(slot, stack, prefix, itemType); + this.currentClient.player.inventory[slot] = new Item( + slot, + stack, + prefix, + itemType, + ); } return false; @@ -296,8 +374,7 @@ class ClientPacketHandler { /* Tracks the player mana to restore it when they switch from an * SSC server to a Non-SSC server */ private handlePlayerMana(playerMana: PlayerManaPacket.t): boolean { - if (!this.currentClient.player.allowedManaChange) - return false; + if (!this.currentClient.player.allowedManaChange) return false; const { maxMana } = playerMana; this.currentClient.player.mana = maxMana; @@ -308,7 +385,10 @@ class ClientPacketHandler { /* Tracks the player HP to restore it when they switch from an * SSC server to a Non-SSC server */ - private handlePlayerHP(playerHealth: PlayerHealthPacket.t, rawPacket: RawPacket): boolean { + private handlePlayerHP( + playerHealth: PlayerHealthPacket.t, + rawPacket: RawPacket, + ): boolean { if (!this.currentClient.player.allowedLifeChange) { return false; } @@ -326,7 +406,10 @@ class ClientPacketHandler { return false; } - private handleUpdatePlayer(_playerUpdate: PlayerUpdatePacket.t, rawPacket: RawPacket): boolean { + private handleUpdatePlayer( + _playerUpdate: PlayerUpdatePacket.t, + rawPacket: RawPacket, + ): boolean { // Prevent this being sent too early (causing kicked for invalid operation) if (this.currentClient.state !== ClientState.FullyConnected) { this.currentClient.packetQueue.push({ rawPacket }); @@ -340,7 +423,10 @@ class ClientPacketHandler { * which causes them to be kicked. It also adds it to the packet queue * so that it may be sent when the client has fully connected (and wont * get kicked for sending it) */ - private handleUpdateItemDrop(_itemDropUpdate: ItemDropUpdatePacket.t, rawPacket: RawPacket): boolean { + private handleUpdateItemDrop( + _itemDropUpdate: ItemDropUpdatePacket.t, + rawPacket: RawPacket, + ): boolean { // Prevent this being sent too early (causing kicked for invalid operation) if (this.currentClient.state !== ClientState.FullyConnected) { this.currentClient.packetQueue.push({ rawPacket }); @@ -357,7 +443,10 @@ class ClientPacketHandler { * * Note: This packet is important for tShock SSC to work. If this was * prevented outright, SSC would be broken (inventory would be unchangable) */ - private handleUpdateItemOwner(_itemOwner: ItemOwnerPacket.t, rawPacket: RawPacket): boolean { + private handleUpdateItemOwner( + _itemOwner: ItemOwnerPacket.t, + rawPacket: RawPacket, + ): boolean { // Prevent this being sent too early (causing kicked for invalid operation) if (this.currentClient.state !== ClientState.FullyConnected) { this.currentClient.packetQueue.push({ rawPacket }); @@ -394,8 +483,12 @@ class ClientPacketHandler { // If chat message is a command if (chatMessage.length > 1 && chatMessage.substr(0, 1) === "/") { - let command: Command = this.currentClient.globalHandlers.command.parseCommand(chatMessage); - handled = this.currentClient.globalHandlers.command.handle(command, this.currentClient); + let command: Command = + this.currentClient.globalHandlers.command.parseCommand(chatMessage); + handled = this.currentClient.globalHandlers.command.handle( + command, + this.currentClient, + ); } return handled; @@ -415,15 +508,14 @@ class ClientPacketHandler { this.currentClient.packetQueue.push({ rawPacket: { data: packet.data, - packetType: packet.packetType - } + packetType: packet.packetType, + }, }); return true; } return false; } - } export default ClientPacketHandler; diff --git a/app/dimensions/configloader.ts b/app/dimensions/configloader.ts index 5a898fc..9e8cc89 100644 --- a/app/dimensions/configloader.ts +++ b/app/dimensions/configloader.ts @@ -1,12 +1,12 @@ -import RoutingServer from './routingserver.js'; -import * as Language from './language.js'; -import * as assert from 'assert'; -import * as path from 'path'; -import * as fs from 'fs'; -import * as yaml from 'yaml'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; -import { createRequire } from 'module'; +import RoutingServer from "./routingserver.js"; +import * as Language from "./language.js"; +import * as assert from "assert"; +import * as path from "path"; +import * as fs from "fs"; +import * as yaml from "yaml"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; +import { createRequire } from "module"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -65,11 +65,13 @@ export type EnabledBlackList = { port: number; apiKey: string; errorPolicy: "AllowJoining" | "DenyJoining"; -} +}; -export type BlackList = EnabledBlackList | { - enabled: false; -} +export type BlackList = + | EnabledBlackList + | { + enabled: false; + }; export interface RestApiResponse { name?: string; @@ -108,30 +110,37 @@ export interface NameChanges { exclusions: string[]; } -export type UnvalidatedDebuffOnSwitch = { - enabled: false; -} | { - enabled: true; - buffTypes?: number[]; - debuffTimeInSeconds?: number; -} - -export type DebuffOnSwitch = { - enabled: false; -} | { - enabled: true; - buffTypes: number[]; - debuffTimeInSeconds: number; -} - -export type DisconnectOnKick = { - type: "always"; -} | { - type: "never"; -} | { - type: "onKickReasonPrefix"; - kickReasonPrefixes: string[]; -} +export type UnvalidatedDebuffOnSwitch = + | { + enabled: false; + } + | { + enabled: true; + buffTypes?: number[]; + debuffTimeInSeconds?: number; + }; + +export type DebuffOnSwitch = + | { + enabled: false; + } + | { + enabled: true; + buffTypes: number[]; + debuffTimeInSeconds: number; + }; + +export type DisconnectOnKick = + | { + type: "always"; + } + | { + type: "never"; + } + | { + type: "onKickReasonPrefix"; + kickReasonPrefixes: string[]; + }; export interface UnvalidatedConfigOptions { socketTimeout: number; @@ -157,14 +166,14 @@ export interface ConfigOptions { socketNoDelay: boolean; fakeVersion: FakeVersion; restApi: RestApi; - blockInvis: boolean | { enabled: boolean, servers: string[] }; + blockInvis: boolean | { enabled: boolean; servers: string[] }; blacklist: BlackList; log: LogOptions; connectionLimit: ConnectionLimit; connectionRateLimit: ConnectionRateLimit; redis: RedisConfig; nameChanges?: NameChanges; - language: Language.LanguageDefinition, + language: Language.LanguageDefinition; debuffOnSwitch: DebuffOnSwitch; disconnectOnKick: DisconnectOnKick; hotReload: boolean; @@ -187,17 +196,21 @@ export interface Config { // that uses a yaml file. This means we can use fs to read that file avoiding the module cache and // soft link issue, as well as properly support hot reloading when dimensions is deployed in k8s. // Legacy config.js remains supported for compatibility. -export const oldConfigFilePath = path.resolve(__dirname, '../../config.js'); -export const configurationDirectory = path.resolve(__dirname, '../../configuration'); +export const oldConfigFilePath = path.resolve(__dirname, "../../config.cjs"); +export const configurationDirectory = path.resolve( + __dirname, + "../../configuration", +); -export let usingOldConfig = false +export let usingOldConfig = false; function loadLegacyConfigSync(fresh: boolean): UnvalidatedConfig { const resolvedPath = legacyRequire.resolve(oldConfigFilePath); if (fresh && legacyRequire.cache[resolvedPath]) { delete legacyRequire.cache[resolvedPath]; } const legacyModule = legacyRequire(resolvedPath); - const config = legacyModule?.ConfigSettings ?? legacyModule?.default ?? legacyModule; + const config = + legacyModule?.ConfigSettings ?? legacyModule?.default ?? legacyModule; if (!config || typeof config !== "object") { throw new Error("Legacy config.js did not export ConfigSettings"); } @@ -206,19 +219,30 @@ function loadLegacyConfigSync(fresh: boolean): UnvalidatedConfig { function loadConfigSync(): UnvalidatedConfig { // For initial load, we prefer YAML config if it exists (ESM-compatible) - if (fs.existsSync(path.resolve(configurationDirectory, 'config.yaml'))) { + if (fs.existsSync(path.resolve(configurationDirectory, "config.yaml"))) { usingOldConfig = false; - return yaml.parse(fs.readFileSync(path.resolve(configurationDirectory, 'config.yaml'), 'utf8')); + return yaml.parse( + fs.readFileSync( + path.resolve(configurationDirectory, "config.yaml"), + "utf8", + ), + ); } else if (fs.existsSync(oldConfigFilePath)) { usingOldConfig = true; return loadLegacyConfigSync(false); } else { - throw new Error("No config file found. Please create configuration/config.yaml or if using a legacy config make sure config.js is present"); + throw new Error( + "No config file found. Please create configuration/config.yaml or if using a legacy config make sure config.js is present", + ); } } function validateConfig(unvalidatedConfig: UnvalidatedConfig): Config { - const debuffOnSwitch = { enabled: true, buffTypes: [ /* Webbed */ 149, /* Stoned */ 156], debuffTimeInSeconds: 5 } + const debuffOnSwitch = { + enabled: true, + buffTypes: [/* Webbed */ 149, /* Stoned */ 156], + debuffTimeInSeconds: 5, + }; const disconnectOnKick: DisconnectOnKick = { type: "never" }; const blacklist: BlackList = { enabled: false }; let validatedConfig: Config = { @@ -235,31 +259,55 @@ function validateConfig(unvalidatedConfig: UnvalidatedConfig): Config { try { if (typeof unvalidatedConfig.options.nameChanges !== "undefined") { - assert.ok(unvalidatedConfig.options.nameChanges.mode === "legacy" || unvalidatedConfig.options.nameChanges.mode === "rewrite", "nameChanges.mode must be either 'legacy' or 'rewrite'"); - assert.ok(Array.isArray(unvalidatedConfig.options.nameChanges.exclusions), "nameChanges.exclusions must be an array"); + assert.ok( + unvalidatedConfig.options.nameChanges.mode === "legacy" || + unvalidatedConfig.options.nameChanges.mode === "rewrite", + "nameChanges.mode must be either 'legacy' or 'rewrite'", + ); + assert.ok( + Array.isArray(unvalidatedConfig.options.nameChanges.exclusions), + "nameChanges.exclusions must be an array", + ); } if (typeof unvalidatedConfig.options.debuffOnSwitch !== "undefined") { if (!unvalidatedConfig.options.debuffOnSwitch.enabled) { debuffOnSwitch.enabled = false; } else { - if (typeof unvalidatedConfig.options.debuffOnSwitch.buffTypes !== "undefined") { - debuffOnSwitch.buffTypes = unvalidatedConfig.options.debuffOnSwitch.buffTypes; + if ( + typeof unvalidatedConfig.options.debuffOnSwitch.buffTypes !== + "undefined" + ) { + debuffOnSwitch.buffTypes = + unvalidatedConfig.options.debuffOnSwitch.buffTypes; } - if (typeof unvalidatedConfig.options.debuffOnSwitch.debuffTimeInSeconds !== "undefined") - debuffOnSwitch.debuffTimeInSeconds = unvalidatedConfig.options.debuffOnSwitch.debuffTimeInSeconds; + if ( + typeof unvalidatedConfig.options.debuffOnSwitch + .debuffTimeInSeconds !== "undefined" + ) + debuffOnSwitch.debuffTimeInSeconds = + unvalidatedConfig.options.debuffOnSwitch.debuffTimeInSeconds; } } if (typeof unvalidatedConfig.options.disconnectOnKick !== "undefined") { - if (unvalidatedConfig.options.disconnectOnKick.type === "onKickReasonPrefix") { - assert.ok(Array.isArray(unvalidatedConfig.options.disconnectOnKick.kickReasonPrefixes), "disconnectOnKick.kickReasonPrefixes must be an array"); + if ( + unvalidatedConfig.options.disconnectOnKick.type === "onKickReasonPrefix" + ) { + assert.ok( + Array.isArray( + unvalidatedConfig.options.disconnectOnKick.kickReasonPrefixes, + ), + "disconnectOnKick.kickReasonPrefixes must be an array", + ); validatedConfig.options.disconnectOnKick = { type: "onKickReasonPrefix", - kickReasonPrefixes: unvalidatedConfig.options.disconnectOnKick.kickReasonPrefixes, - } + kickReasonPrefixes: + unvalidatedConfig.options.disconnectOnKick.kickReasonPrefixes, + }; } else { - unvalidatedConfig.options.disconnectOnKick.type = unvalidatedConfig.options.disconnectOnKick.type; + unvalidatedConfig.options.disconnectOnKick.type = + unvalidatedConfig.options.disconnectOnKick.type; } } @@ -275,7 +323,10 @@ function validateConfig(unvalidatedConfig: UnvalidatedConfig): Config { validatedConfig.options.language = Language.chinese; break; default: - console.log("Unrecognised language:", unvalidatedConfig.options.language); + console.log( + "Unrecognised language:", + unvalidatedConfig.options.language, + ); process.exit(1); } } @@ -287,37 +338,68 @@ function validateConfig(unvalidatedConfig: UnvalidatedConfig): Config { }; } - validatedConfig.options.blacklist.enabled = unvalidatedConfig.options.blacklist.enabled ?? false; + validatedConfig.options.blacklist.enabled = + unvalidatedConfig.options.blacklist.enabled ?? false; if (validatedConfig.options.blacklist.enabled) { - assert.ok(typeof unvalidatedConfig.options.blacklist.hostname !== "undefined", "Blacklist enabled but no hostname provided"); - assert.ok(typeof unvalidatedConfig.options.blacklist.path !== "undefined", "Blacklist enabled but no path provided"); - assert.ok(typeof unvalidatedConfig.options.blacklist.port !== "undefined", "Blacklist enabled but no port provided"); - assert.ok(typeof unvalidatedConfig.options.blacklist.apiKey !== "undefined", "Blacklist enabled but no api key provided"); - assert.ok(unvalidatedConfig.options.blacklist.errorPolicy === "AllowJoining" || unvalidatedConfig.options.blacklist.errorPolicy === "DenyJoining", "Blacklist errorPolicy must be either 'AllowJoining' or 'DenyJoining'"); - - validatedConfig.options.blacklist.hostname = unvalidatedConfig.options.blacklist.hostname!; - validatedConfig.options.blacklist.path = unvalidatedConfig.options.blacklist.path!; - validatedConfig.options.blacklist.port = unvalidatedConfig.options.blacklist.port!; - validatedConfig.options.blacklist.apiKey = unvalidatedConfig.options.blacklist.apiKey!; - validatedConfig.options.blacklist.errorPolicy = unvalidatedConfig.options.blacklist.errorPolicy!; + assert.ok( + typeof unvalidatedConfig.options.blacklist.hostname !== "undefined", + "Blacklist enabled but no hostname provided", + ); + assert.ok( + typeof unvalidatedConfig.options.blacklist.path !== "undefined", + "Blacklist enabled but no path provided", + ); + assert.ok( + typeof unvalidatedConfig.options.blacklist.port !== "undefined", + "Blacklist enabled but no port provided", + ); + assert.ok( + typeof unvalidatedConfig.options.blacklist.apiKey !== "undefined", + "Blacklist enabled but no api key provided", + ); + assert.ok( + unvalidatedConfig.options.blacklist.errorPolicy === "AllowJoining" || + unvalidatedConfig.options.blacklist.errorPolicy === "DenyJoining", + "Blacklist errorPolicy must be either 'AllowJoining' or 'DenyJoining'", + ); + + validatedConfig.options.blacklist.hostname = + unvalidatedConfig.options.blacklist.hostname!; + validatedConfig.options.blacklist.path = + unvalidatedConfig.options.blacklist.path!; + validatedConfig.options.blacklist.port = + unvalidatedConfig.options.blacklist.port!; + validatedConfig.options.blacklist.apiKey = + unvalidatedConfig.options.blacklist.apiKey!; + validatedConfig.options.blacklist.errorPolicy = + unvalidatedConfig.options.blacklist.errorPolicy!; } } catch (e) { console.log("Error validating config:"); - throw e + throw e; } return validatedConfig; } export function reloadConfig(): Config { - if (fs.existsSync(path.resolve(configurationDirectory, 'config.yaml'))) { + if (fs.existsSync(path.resolve(configurationDirectory, "config.yaml"))) { usingOldConfig = false; - return validateConfig(yaml.parse(fs.readFileSync(path.resolve(configurationDirectory, 'config.yaml'), 'utf8'))); + return validateConfig( + yaml.parse( + fs.readFileSync( + path.resolve(configurationDirectory, "config.yaml"), + "utf8", + ), + ), + ); } else if (fs.existsSync(oldConfigFilePath)) { usingOldConfig = true; return validateConfig(loadLegacyConfigSync(true)); } else { - throw new Error("No config file found. Please use configuration/config.yaml or config.js"); + throw new Error( + "No config file found. Please use configuration/config.yaml or config.js", + ); } } diff --git a/app/dimensions/language.ts b/app/dimensions/language.ts index 1f26721..230e84e 100644 --- a/app/dimensions/language.ts +++ b/app/dimensions/language.ts @@ -1,50 +1,50 @@ export interface LanguageDefinition { - englishName: string, - name: string, - isoCode: string, - phrases: LanguagePhrases, + englishName: string; + name: string; + isoCode: string; + phrases: LanguagePhrases; } export interface LanguagePhrasesOverrides { - nameAlreadyOnServer?: string, - characterNameLengthOutOfRange?: string, - areYouEvenConnected?: string, - youAreAlreadyInthatDimension?: string, - shiftingToDimension?: string, - youNeedToWaitUntilConnected?: string, - playerCount?: string, - availableDimensions?: string, - youEnteredTheVoid?: string, - dimensionDisconnectedYou?: string, - reason?: string, - dimensionsCommandName?: string, - specifyADimensionToTravel?: string, - dimensionDropped?: string, - blacklisted?: string, - blacklistCheckError?: string, - invalidPacketLength?: string, - close?: string, + nameAlreadyOnServer?: string; + characterNameLengthOutOfRange?: string; + areYouEvenConnected?: string; + youAreAlreadyInthatDimension?: string; + shiftingToDimension?: string; + youNeedToWaitUntilConnected?: string; + playerCount?: string; + availableDimensions?: string; + youEnteredTheVoid?: string; + dimensionDisconnectedYou?: string; + reason?: string; + dimensionsCommandName?: string; + specifyADimensionToTravel?: string; + dimensionDropped?: string; + blacklisted?: string; + blacklistCheckError?: string; + invalidPacketLength?: string; + close?: string; } interface LanguagePhrases { - nameAlreadyOnServer: string, - characterNameLengthOutOfRange: string, - areYouEvenConnected: string, - youAreAlreadyInthatDimension: string, - shiftingToDimension: string, - youNeedToWaitUntilConnected: string, - playerCount: string, - availableDimensions: string, - youEnteredTheVoid: string, - dimensionDisconnectedYou: string, - reason: string, - dimensionsCommandName: string, - specifyADimensionToTravel: string, - dimensionDropped: string, - blacklisted: string, - blacklistCheckError: string, - invalidPacketLength: string, - close: string, + nameAlreadyOnServer: string; + characterNameLengthOutOfRange: string; + areYouEvenConnected: string; + youAreAlreadyInthatDimension: string; + shiftingToDimension: string; + youNeedToWaitUntilConnected: string; + playerCount: string; + availableDimensions: string; + youEnteredTheVoid: string; + dimensionDisconnectedYou: string; + reason: string; + dimensionsCommandName: string; + specifyADimensionToTravel: string; + dimensionDropped: string; + blacklisted: string; + blacklistCheckError: string; + invalidPacketLength: string; + close: string; } export const english: LanguageDefinition = { @@ -53,11 +53,13 @@ export const english: LanguageDefinition = { isoCode: "en", phrases: { nameAlreadyOnServer: "Someone called ${name} is already on the server.", - characterNameLengthOutOfRange: "A character name must be between (inclusive) 2 to 20 characters long.", + characterNameLengthOutOfRange: + "A character name must be between (inclusive) 2 to 20 characters long.", areYouEvenConnected: "Are you even connected?", youAreAlreadyInthatDimension: "You are already in that Dimension.", shiftingToDimension: "Shifting to the ${name} Dimension", - youNeedToWaitUntilConnected: "You need to wait until you have fully connected to your current Dimension.", + youNeedToWaitUntilConnected: + "You need to wait until you have fully connected to your current Dimension.", playerCount: "There are ${total} players across all Dimensions.", availableDimensions: "Available Dimensions: ", youEnteredTheVoid: "You have entered the Void. You will soon disappear.", @@ -80,25 +82,33 @@ export const chinese: LanguageDefinition = { name: "汉语", isoCode: "zn", phrases: { - nameAlreadyOnServer: "${name} 已经进入了服务器", + nameAlreadyOnServer: "${name} 已经在服务器中", characterNameLengthOutOfRange: "昵称长度必须在2至20个字符之间", - areYouEvenConnected: "请确保你已经连接", - youAreAlreadyInthatDimension: "你已经进入了那个世界", - shiftingToDimension: "正在传送到 ${name}", - youNeedToWaitUntilConnected: "请等待传送", - playerCount: "当前共有 ${total} 名玩家在线", - availableDimensions: "可传送的世界: ", - youEnteredTheVoid: "你已断线,即将被踢出世界", - dimensionDisconnectedYou: "你已与当前世界断开连接", - reason: "原因:: ", - dimensionsCommandName: "世界", - specifyADimensionToTravel: "请选择需要传送的[c/FF00CC:世界]:", - dimensionDropped: "你所在的世界已与您断开连接", + areYouEvenConnected: + "[i:3459]CSFT[i:3459] [c/ff8080:请][c/ffda80:确][c/c8ff80:保][c/6dff80:你][c/12ff80:已][c/49ffc8:经][c/a4daed:连][c/fe80c0:接]", + youAreAlreadyInthatDimension: + "[i:3459]CSFT[i:3459] [c/ff8080:你][c/ffda80:已][c/c8ff80:经][c/6dff80:进][c/12ff80:入][c/49ffc8:了][c/a4daed:此][c/fe80c0:服]", + shiftingToDimension: + "[i:3459]CSFT[i:3459] [c/ff8080:正][c/ffff80:在][c/80ff80:传][c/00ff80:送][c/80ffff:到] ${name}", + youNeedToWaitUntilConnected: + "[i:3459]CSFT[i:3459] [c/ff8080:请][c/ffda80:等][c/c8ff80:待][c/6dff80:传][c/12ff80:送][c/49ffc8:.][c/a4daed:.][c/fe80c0:.]", + playerCount: "[i:3459]CSFT[i:3459] 当前维度有 ${total} 玩家在线", + availableDimensions: + "[i:3459]CSFT[i:3459] [c/ff8080:可][c/ffcf80:传][c/dfff80:送][c/8fff80:的][c/40ff80:服][c/0fff8f:务][c/5fffdf:器][c/afcfe7::] ", + youEnteredTheVoid: + "[i:3459]CSFT[i:3459] [c/ff8080:你][c/ffaa80:已][c/ffd480:掉][c/ffff80:线][c/d4ff80:,][c/aaff80:即][c/80ff80:将][c/55ff80:被][c/2aff80:踢][c/00ff80:出][c/2affaa:服][c/55ffd4:务][c/80ffff:器][c/aad4ea:.][c/d4aad5:.][c/fe80c0:.]", + dimensionDisconnectedYou: + "[i:3459]CSFT[i:3459] [c/ff8080:你][c/ffb980:已][c/fff380:与][c/d0ff80:当][c/97ff80:前][c/5dff80:服][c/22ff80:务][c/17ff97:器][c/51ffd0:断][c/8bf3f9:开][c/c5b9dc:连][c/fe80c0:接]", + reason: "原因:", + dimensionsCommandName: "服务器", + specifyADimensionToTravel: + "[i:3459]CSFT[i:3459] [c/ff8080:请][c/ffda80:选][c/c8ff80:择][c/6dff80:需][c/12ff80:要][c/49ffc8:传][c/a4daed:送][c/fe80c0:的][c/FF00CC:分区]:", + dimensionDropped: + "[i:3459]CSFT[i:3459] [c/ff8080:服][c/ffc680:务][c/f0ff80:器][c/aaff80:暂][c/63ff80:时][c/1cff80:关][c/2affaa:闭][c/71fff0:.][c/b8c6e3:.][c/fe80c0:.]", blacklisted: "您已被服务器列入黑名单。", blacklistCheckError: "检查您是否被列入黑名单时出现错误。", - // TODO: This is google translated, should be reviewed by native speaker - invalidPacketLength: "客户端违反协议:数据包长度无效。", - // TODO: This is google translated, should be reviewed by native speaker - close: "服务器正在重新启动。 请重新加入。", + invalidPacketLength: + "[c/ff8080:客][c/ffaa80:户][c/ffd480:端][c/ffff80:违][c/d4ff80:反][c/aaff80:协][c/80ff80:议][c/55ff80::][c/2aff80:数][c/00ff80:据][c/2affaa:包][c/55ffd4:长][c/80ffff:度][c/aad4ea:无][c/d4aad5:效][c/fe80c0:。]", + close: "服务器正在重新启动,请重新加入。", }, }; diff --git a/app/dimensions/listenserver.ts b/app/dimensions/listenserver.ts index 94faeea..ca50f06 100644 --- a/app/dimensions/listenserver.ts +++ b/app/dimensions/listenserver.ts @@ -1,23 +1,26 @@ -import * as Net from 'net'; -import { v4 as uuidv4 } from 'uuid'; -import RawPacket from './packets/rawpacket.js'; -import { getProperIP } from './utils.js'; -import Client from './client.js'; -import ClientArgs from './clientargs.js'; -import ServerDetails from './serverdetails.js'; -import GlobalHandlers from './globalhandlers.js'; -import { ConfigListenServer, ConfigOptions } from './configloader.js'; -import RoutingServer from './routingserver.js'; -import Blacklist from './blacklist.js'; -import GlobalTracking from './globaltracking.js'; -import ListenServerArgs from './listenserverargs.js'; -import NetworkText from '@popstarfreas/packetfactory/networktext'; -import StringUtils from './stringutils.js'; -import ErrorHelper from './errorhelper.js'; -import BlacklistCheckClient from './blacklistcheckclient.js'; -import * as winston from 'winston'; -import { RawSocketWriteContext, RawSocketWriteReason } from './extension/index.js'; -import { DisconnectPacket, StatusPacket } from 'terraria-packet'; +import * as Net from "net"; +import { v4 as uuidv4 } from "uuid"; +import RawPacket from "./packets/rawpacket.js"; +import { getProperIP } from "./utils.js"; +import Client from "./client.js"; +import ClientArgs from "./clientargs.js"; +import ServerDetails from "./serverdetails.js"; +import GlobalHandlers from "./globalhandlers.js"; +import { ConfigListenServer, ConfigOptions } from "./configloader.js"; +import RoutingServer from "./routingserver.js"; +import Blacklist from "./blacklist.js"; +import GlobalTracking from "./globaltracking.js"; +import ListenServerArgs from "./listenserverargs.js"; +import NetworkText from "@popstarfreas/packetfactory/networktext"; +import StringUtils from "./stringutils.js"; +import ErrorHelper from "./errorhelper.js"; +import BlacklistCheckClient from "./blacklistcheckclient.js"; +import * as winston from "winston"; +import { + RawSocketWriteContext, + RawSocketWriteReason, +} from "./extension/index.js"; +import { DisconnectPacket, StatusPacket } from "terraria-packet"; /** * Listens on a specified port and routes users balancing amounts between routing servers it handles @@ -63,26 +66,24 @@ export class ListenServer { clientCount: 0, disabled: false, disabledTimeout: null, - failedConnAttempts: 0 + failedConnAttempts: 0, }; } - this.ServerHandleError = this.handleError.bind(this); this.ServerHandleStart = this.handleStart.bind(this); // Listen Server this.server = Net.createServer(); - this.server.on('connection', (socket) => { - this.handleSocket(socket) - .catch((e) => { - if (this.options.log.clientError) { - this.logging.error(`Socket Error: ${ErrorHelper.toMessage(e)}`); - } - }); + this.server.on("connection", (socket) => { + this.handleSocket(socket).catch((e) => { + if (this.options.log.clientError) { + this.logging.error(`Socket Error: ${ErrorHelper.toMessage(e)}`); + } + }); }); this.server.listen(this.port, this.ServerHandleStart); - this.server.on('error', this.ServerHandleError); + this.server.on("error", this.ServerHandleError); if (this.options.connectionRateLimit.enabled) { this.startConnectionRateLimitTimer(); @@ -110,7 +111,12 @@ export class ListenServer { // Even if the server has been disabled, if we have no current choice, we must use it if (!details.disabled || currentClientCount === null) { // Favour either lower player count or non-disability - if (currentClientCount === null || chosenServer === null || details.clientCount < currentClientCount || this.serversDetails[chosenServer.name].disabled) { + if ( + currentClientCount === null || + chosenServer === null || + details.clientCount < currentClientCount || + this.serversDetails[chosenServer.name].disabled + ) { chosenServer = this.routingServers[i]; currentClientCount = details.clientCount; } @@ -141,7 +147,7 @@ export class ListenServer { let details: ServerDetails; for (let i = 0; i < this.routingServers.length; i++) { if (this.serversDetails[this.routingServers[i].name]) { - details = this.serversDetails[this.routingServers[i].name] + details = this.serversDetails[this.routingServers[i].name]; details.disabled = false; details.failedConnAttempts = 0; } else { @@ -149,7 +155,7 @@ export class ListenServer { clientCount: 0, disabled: false, disabledTimeout: null, - failedConnAttempts: 0 + failedConnAttempts: 0, }; } } @@ -161,13 +167,22 @@ export class ListenServer { public shutdown(): void { this.logging.info(`Server on ${this.port} is now shutting down.`); for (let i: number = 0; i < this.clients.length; i++) { - this.clients[i].server.socket.removeListener('data', this.clients[i].ServerHandleData); - this.clients[i].server.socket.removeListener('error', this.clients[i].ServerHandleError); - this.clients[i].server.socket.removeListener('close', this.clients[i].ServerHandleClose); + this.clients[i].server.socket.removeListener( + "data", + this.clients[i].ServerHandleData, + ); + this.clients[i].server.socket.removeListener( + "error", + this.clients[i].ServerHandleError, + ); + this.clients[i].server.socket.removeListener( + "close", + this.clients[i].ServerHandleClose, + ); this.clients[i].disconnect(this.options.language.phrases.close); } this.clients = []; - this.server.removeListener('error', this.ServerHandleError); + this.server.removeListener("error", this.ServerHandleError); this.server.close(); // Reset counts @@ -196,14 +211,14 @@ export class ListenServer { private writeToSocketWithHooks( socket: Net.Socket, packet: Buffer, - reason: RawSocketWriteContext['reason'], - clientArgs?: ClientArgs + reason: RawSocketWriteContext["reason"], + clientArgs?: ClientArgs, ): boolean { const context: RawSocketWriteContext = { socket: socket, remoteAddress: socket.remoteAddress, reason: reason, - clientArgs: clientArgs + clientArgs: clientArgs, }; const packetWrapper = { packet: packet }; @@ -211,7 +226,10 @@ export class ListenServer { for (const extension of Object.values(this.globalHandlers.extensions)) { if (extension.rawSocketWritePreHandler) { try { - const blocked = extension.rawSocketWritePreHandler(context, packetWrapper); + const blocked = extension.rawSocketWritePreHandler( + context, + packetWrapper, + ); if (blocked) { return false; } @@ -255,11 +273,11 @@ export class ListenServer { private disconnectClient( socket: Net.Socket, reason: string, - hookReason: RawSocketWriteContext['reason'] = RawSocketWriteReason.Other + hookReason: RawSocketWriteContext["reason"] = RawSocketWriteReason.Other, ): void { let kickPacket = DisconnectPacket.toBuffer({ - reason: new NetworkText(0, reason) - }) + reason: new NetworkText(0, reason), + }); if (!socket.destroyed) { switch (kickPacket.TAG) { @@ -267,7 +285,9 @@ export class ListenServer { this.writeToSocketWithHooks(socket, kickPacket._0, hookReason); break; case "Error": - this.logging.error(`Error creating disconnect packet: ${kickPacket._0}`); + this.logging.error( + `Error creating disconnect packet: ${kickPacket._0}`, + ); break; } @@ -303,8 +323,12 @@ export class ListenServer { * @param socket The socket of a new client */ private async handleSocket(socket: Net.Socket): Promise { - if ((this.options.connectionLimit.enabled && this.enforceConnectionLimit(socket)) - || this.options.connectionRateLimit.enabled && this.enforceConnectionRateLimit(socket)) { + if ( + (this.options.connectionLimit.enabled && + this.enforceConnectionLimit(socket)) || + (this.options.connectionRateLimit.enabled && + this.enforceConnectionRateLimit(socket)) + ) { socket.removeAllListeners(); return; } @@ -326,8 +350,7 @@ export class ListenServer { } } } - } - catch (error) { + } catch (error) { console.log(error); } @@ -360,8 +383,11 @@ export class ListenServer { if (counter + 1 > this.options.connectionLimit.connectionLimitPerIP) { this.disconnectClient( socket, - StringUtils.format(this.options.connectionLimit.kickReason, this.options.connectionLimit.connectionLimitPerIP), - RawSocketWriteReason.ConnectionLimitExceeded + StringUtils.format( + this.options.connectionLimit.kickReason, + this.options.connectionLimit.connectionLimitPerIP, + ), + RawSocketWriteReason.ConnectionLimitExceeded, ); connectionDropped = true; } else { @@ -382,7 +408,10 @@ export class ListenServer { } const count = this.connectRateTracker.get(ip); if (typeof count !== "undefined") { - if (count + 1 > this.options.connectionRateLimit.connectionRateLimitPerIP) { + if ( + count + 1 > + this.options.connectionRateLimit.connectionRateLimitPerIP + ) { socket.destroy(); connectionDropped = true; } else { @@ -402,7 +431,9 @@ export class ListenServer { private async setupNewSocket(socket: Net.Socket): Promise { let chosenServer: RoutingServer | null = this.chooseServer(); if (chosenServer === null) { - this.logging.warn(`No servers available for ListenServer[Port: ${this.port}]`); + this.logging.warn( + `No servers available for ListenServer[Port: ${this.port}]`, + ); socket.destroy(); const ip = socket.remoteAddress; if (typeof ip !== "undefined") { @@ -425,7 +456,7 @@ export class ListenServer { servers: this.servers, options: this.options, globalTracking: this.globalTracking, - logging: this.logging + logging: this.logging, }; // When the blacklist is enabled, clients must first send their initial data @@ -439,7 +470,10 @@ export class ListenServer { }); client.setupCallbacks({ - clientAcceptedCb: (bufferPacket: Buffer, packetsReceived: RawPacket[]) => { + clientAcceptedCb: ( + bufferPacket: Buffer, + packetsReceived: RawPacket[], + ) => { const index = this.checkingClients.indexOf(client); if (index > -1) { this.checkingClients.splice(index, 1); @@ -453,14 +487,24 @@ export class ListenServer { } this.kickBlacklisted(clientArgs); }, - errorCheckingBlacklistCb: (bufferPacket: Buffer, packetsReceived: RawPacket[], e: Error) => { - this.logging.error(`Error checking blacklist: ${ErrorHelper.toMessage(e)}`); + errorCheckingBlacklistCb: ( + bufferPacket: Buffer, + packetsReceived: RawPacket[], + e: Error, + ) => { + this.logging.error( + `Error checking blacklist: ${ErrorHelper.toMessage(e)}`, + ); if (configuration.errorPolicy === "DenyJoining") { const index = this.checkingClients.indexOf(client); if (index > -1) { this.checkingClients.splice(index, 1); } - this.disconnectClient(socket, this.options.language.phrases.blacklistCheckError, RawSocketWriteReason.BlacklistCheck); + this.disconnectClient( + socket, + this.options.language.phrases.blacklistCheckError, + RawSocketWriteReason.BlacklistCheck, + ); } else { const index = this.checkingClients.indexOf(client); if (index > -1) { @@ -470,12 +514,18 @@ export class ListenServer { } }, packetErrorCheckingBlacklistCb: (e: Error) => { - this.logging.error(`Packet error checking blacklist: ${ErrorHelper.toMessage(e)}`); + this.logging.error( + `Packet error checking blacklist: ${ErrorHelper.toMessage(e)}`, + ); const index = this.checkingClients.indexOf(client); if (index > -1) { this.checkingClients.splice(index, 1); } - this.disconnectClient(socket, this.options.language.phrases.blacklistCheckError, RawSocketWriteReason.BlacklistCheck); + this.disconnectClient( + socket, + this.options.language.phrases.blacklistCheckError, + RawSocketWriteReason.BlacklistCheck, + ); }, disconnectCb: () => { const index = this.checkingClients.indexOf(client); @@ -486,7 +536,7 @@ export class ListenServer { if (typeof ip !== "undefined") { this.decrementConnectionTracker(ip); } - } + }, }); this.checkingClients.push(client); } else { @@ -494,12 +544,18 @@ export class ListenServer { } } - private setupNewClient(clientArgs: ClientArgs, bufferPacket: Buffer | undefined, packetsAlreadyReceived: RawPacket[]): Client { + private setupNewClient( + clientArgs: ClientArgs, + bufferPacket: Buffer | undefined, + packetsAlreadyReceived: RawPacket[], + ): Client { let client = new Client(clientArgs); this.clients.push(client); if (this.options.log.clientConnect) { - this.logging.info(`[Client: ${getProperIP(client.socket.remoteAddress)} connected [${clientArgs.server.name}: ${this.serversDetails[clientArgs.server.name].clientCount + 1}]`); + this.logging.info( + `[Client: ${getProperIP(client.socket.remoteAddress)} connected [${clientArgs.server.name}: ${this.serversDetails[clientArgs.server.name].clientCount + 1}]`, + ); } this.hookSocketError(client.socket, client); @@ -507,7 +563,9 @@ export class ListenServer { this.hookSocketClose(client.socket, client); this.hookSocketData(client.socket, client); - client.handleDataSend(Buffer.concat(packetsAlreadyReceived.map((packet) => packet.data))); + client.handleDataSend( + Buffer.concat(packetsAlreadyReceived.map((packet) => packet.data)), + ); if (bufferPacket !== undefined) { client.handleDataSend(bufferPacket); } @@ -522,10 +580,16 @@ export class ListenServer { * @return Whether or not the ip is blacklisted */ private kickBlacklisted(client: ClientArgs): void { - this.disconnectClient(client.socket, this.options.language.phrases.blacklisted, RawSocketWriteReason.BlacklistCheck); + this.disconnectClient( + client.socket, + this.options.language.phrases.blacklisted, + RawSocketWriteReason.BlacklistCheck, + ); if (this.options.log.clientBlocked) { - this.logging.info(`${process.pid}] Client: ${getProperIP(client.socket.remoteAddress)} was blocked from joining.`); + this.logging.info( + `${process.pid}] Client: ${getProperIP(client.socket.remoteAddress)} was blocked from joining.`, + ); } } @@ -542,13 +606,18 @@ export class ListenServer { flags: { hideStatusTextPercent: true, statusTextHasShadows: true, - runCheckBytes: false - } - }) + runCheckBytes: false, + }, + }); switch (statusPacket.TAG) { case "Ok": - this.writeToSocketWithHooks(client.socket, statusPacket._0, RawSocketWriteReason.BlacklistCheck, client); + this.writeToSocketWithHooks( + client.socket, + statusPacket._0, + RawSocketWriteReason.BlacklistCheck, + client, + ); break; case "Error": this.logging.error(`Error creating status packet: ${statusPacket._0}`); @@ -563,12 +632,12 @@ export class ListenServer { * @param client The client object associated with the socket */ private hookSocketError(socket: Net.Socket, client: Client): void { - socket.once('error', (e: Error) => { + socket.once("error", (e: Error) => { try { client.handleError(e); } catch (e) { if (this.options.log.clientError) { - this.logging.error(`handleError Error: ${ErrorHelper.toMessage(e)}`) + this.logging.error(`handleError Error: ${ErrorHelper.toMessage(e)}`); } } }); @@ -581,7 +650,7 @@ export class ListenServer { * @param client The client object associated with the socket */ private hookSocketTimeout(socket: Net.Socket, client: Client): void { - socket.once('timeout', () => { + socket.once("timeout", () => { if (this.options.log.clientTimeouts) { this.logging.warn(`Socket Timeout: ${client.getName()} ${client.ID}`); } @@ -621,7 +690,10 @@ export class ListenServer { } } - private extensionSocketClosePreHandlers(socket: Net.Socket, client: Client): boolean { + private extensionSocketClosePreHandlers( + socket: Net.Socket, + client: Client, + ): boolean { for (const extension of Object.values(this.globalHandlers.extensions)) { if (extension.socketClosePreHandler) { try { @@ -665,7 +737,7 @@ export class ListenServer { * @param client The client object associated with the socket */ private hookSocketClose(socket: Net.Socket, client: Client): void { - socket.once('close', () => { + socket.once("close", () => { if (this.extensionSocketClosePreHandlers(socket, client)) { return; } @@ -676,8 +748,10 @@ export class ListenServer { this.decrementConnectionTracker(ip); } if (this.options.log.clientDisconnect) { - const logMessage = `[${process.pid}] Client: ${getProperIP(ip)} disconnected ${client.server.name}: ${this.serversDetails[client.server.name].clientCount - 1}]`; - this.logging.info(logMessage); + //const logMessage = `[${process.pid}] Client: ${getProperIP(ip)} disconnected ${client.server.name}: ${this.serversDetails[client.server.name].clientCount - 1}]`; + //this.logging.info(logMessage); + //CSFT + //TODO } client.handleClose(); for (let i: number = 0; i < this.clients.length; i++) { @@ -688,7 +762,9 @@ export class ListenServer { } } catch (e) { if (this.options.log.clientError) { - this.logging.error(`SocketCloseEvent ERROR: ${ErrorHelper.toMessage(e)}`); + this.logging.error( + `SocketCloseEvent ERROR: ${ErrorHelper.toMessage(e)}`, + ); } } @@ -705,12 +781,14 @@ export class ListenServer { * @param client The client object associated with the socket */ private hookSocketData(socket: Net.Socket, client: Client): void { - socket.on('data', (data: Buffer) => { + socket.on("data", (data: Buffer) => { try { client.handleDataSend(data); } catch (e) { if (this.options.log.clientError) { - this.logging.error(`HandleDataSend ERROR: ${ErrorHelper.toMessage(e)}`); + this.logging.error( + `HandleDataSend ERROR: ${ErrorHelper.toMessage(e)}`, + ); } } }); @@ -724,7 +802,9 @@ export class ListenServer { * @param error The error object containing the error information */ private handleError(error: Error) { - this.logging.error(` Server on ${this.port} encountered an error: ${ErrorHelper.toMessage(error)}.`); + this.logging.error( + ` Server on ${this.port} encountered an error: ${ErrorHelper.toMessage(error)}.`, + ); } } diff --git a/app/dimensions/terrariaserver.ts b/app/dimensions/terrariaserver.ts index c99ccc3..6acb96c 100644 --- a/app/dimensions/terrariaserver.ts +++ b/app/dimensions/terrariaserver.ts @@ -1,16 +1,18 @@ -import { BuffersPackets, getPacketsFromBuffer } from './utils.js'; -import terrariaServerPacketHandler, { PacketSource } from './terrariaserverpackethandler.js'; -import PacketTypes from './packettypes.js'; -import Client from './client.js'; -import * as Net from 'net'; -import Point from './point.js'; -import RawPacket from './packets/rawpacket.js'; -import Entities from './entities.js'; -import ClientState from './clientstate.js'; -import ErrorHelper from './errorhelper.js'; +import { BuffersPackets, getPacketsFromBuffer } from "./utils.js"; +import terrariaServerPacketHandler, { + PacketSource, +} from "./terrariaserverpackethandler.js"; +import PacketTypes from "./packettypes.js"; +import Client from "./client.js"; +import * as Net from "net"; +import Point from "./point.js"; +import RawPacket from "./packets/rawpacket.js"; +import Entities from "./entities.js"; +import ClientState from "./clientstate.js"; +import ErrorHelper from "./errorhelper.js"; interface PacketQueueItem { - rawPacket: RawPacket, + rawPacket: RawPacket; } /* Used to track information specific to the current server that a client is on @@ -46,7 +48,7 @@ class TerrariaServer { this.name = ""; this.spawn = { x: 0, - y: 0 + y: 0, }; this.bufferPacket = Buffer.allocUnsafe(0); this.afterClosed = null; @@ -54,7 +56,7 @@ class TerrariaServer { items: [], NPCs: [], players: [], - pylons: [] + pylons: [], }; this.isSSC = false; this.packetQueue = []; @@ -73,19 +75,21 @@ class TerrariaServer { public sendDirect(buf: Buffer): void { if (this.socket.writable) { this.socket.write(buf); - Object.values(this.client.globalHandlers.extensions).forEach((extension) => { - if (extension.sendPacketToServerEvent) { - try { - extension.sendPacketToServerEvent(this, buf); - } catch (error) { - if (this.client.options.log.extensionError) { - const name = extension.name ?? "unknown"; - const logMessage = `[${process.pid}] Extension ${name} Server Send Packet Event Error: ${ErrorHelper.toMessage(error)}`; - this.client.logging.info(logMessage); + Object.values(this.client.globalHandlers.extensions).forEach( + (extension) => { + if (extension.sendPacketToServerEvent) { + try { + extension.sendPacketToServerEvent(this, buf); + } catch (error) { + if (this.client.options.log.extensionError) { + const name = extension.name ?? "unknown"; + const logMessage = `[${process.pid}] Extension ${name} Server Send Packet Event Error: ${ErrorHelper.toMessage(error)}`; + this.client.logging.info(logMessage); + } } } - } - }); + }, + ); } } @@ -102,10 +106,14 @@ class TerrariaServer { let entireDataInfo: BuffersPackets = getPacketsFromBuffer(entireData); if (entireDataInfo.type === "InvalidPacketLength") { - this.client.logging.error(`Terraria Server Packet Length Error: Received Packet Length ${entireDataInfo.length}`); - this.client.sendChatMessage("Disconnected from dimension due to a packet length error. Please try again."); + this.client.logging.error( + `Terraria Server Packet Length Error: Received Packet Length ${entireDataInfo.length}`, + ); + this.client.sendChatMessage( + "Disconnected from dimension due to a packet length error. Please try again.", + ); this.client.disconnectFromServer(); - return + return; } // Update buffer packet to the new incomplete packet (if any) @@ -118,14 +126,20 @@ class TerrariaServer { let packets: RawPacket[] = entireDataInfo.packets; packets.forEach((packet: RawPacket) => { try { - const buf = this.getPacketHandler().handlePacket(this, packet, PacketSource.TerrariaServer); + const buf = this.getPacketHandler().handlePacket( + this, + packet, + PacketSource.TerrariaServer, + ); //const buf = packet.data; if (buf !== null) { allowedPackets.push(buf); } } catch (e) { if (this.client.options.log.tServerError) { - this.client.logging.error(`TS handle packet error. PacketType: ${PacketTypes[packet.packetType]} (${packet.packetType}): ${ErrorHelper.toMessage(e)}. Data: ${packet.data.toString("hex")}`); + this.client.logging.error( + `TS handle packet error. PacketType: ${PacketTypes[packet.packetType]} (${packet.packetType}): ${ErrorHelper.toMessage(e)}. Data: ${packet.data.toString("hex")}`, + ); } } }); @@ -141,7 +155,9 @@ class TerrariaServer { } } catch (e) { if (this.client.options.log.tServerError) { - this.client.logging.error(`TS Handle Data Error: ${ErrorHelper.toMessage(e)}`); + this.client.logging.error( + `TS Handle Data Error: ${ErrorHelper.toMessage(e)}`, + ); } } } @@ -150,7 +166,11 @@ class TerrariaServer { public sendWaitingPackets(): void { if (!this.socket.destroyed && this.packetQueue.length > 0) { for (const packet of this.packetQueue) { - const packetData = this.getPacketHandler().handlePacket(this, packet.rawPacket, PacketSource.Dimensions); + const packetData = this.getPacketHandler().handlePacket( + this, + packet.rawPacket, + PacketSource.Dimensions, + ); if (packetData !== null) { this.client.sendDirect(packetData); } @@ -167,7 +187,7 @@ class TerrariaServer { let handled = false; for (let key in handlers) { let handler = handlers[key]; - if (typeof handler.serverDisconnectPreHandler !== 'undefined') { + if (typeof handler.serverDisconnectPreHandler !== "undefined") { try { handled = handler.serverDisconnectPreHandler(this); if (handled) { @@ -193,7 +213,7 @@ class TerrariaServer { let handled = false; for (let key in handlers) { let handler = handlers[key]; - if (typeof handler.serverDisconnectHandler !== 'undefined') { + if (typeof handler.serverDisconnectHandler !== "undefined") { try { handled = handler.serverDisconnectHandler(this); if (handled) { @@ -213,7 +233,7 @@ class TerrariaServer { } /* Decrements server counts when the socket connection to the TerrariaServer - * is closed, sends a message to the client and runs any handlers of this + * is closed, sends a message to the client and runs any handlers of this * event through extensions currently loaded */ public handleClose(): void { this.client.connected = false; @@ -225,7 +245,9 @@ class TerrariaServer { } } catch (e) { if (this.client.options.log.tServerError) { - this.client.logging.error(`handleClose ERROR: ${ErrorHelper.toMessage(e)}`); + this.client.logging.error( + `handleClose ERROR: ${ErrorHelper.toMessage(e)}`, + ); } } @@ -244,22 +266,17 @@ class TerrariaServer { return; } - let dimensionsList: string = ""; - let dimensionNames: string[] = Object.keys(this.client.servers); - for (var i = 0; i < dimensionNames.length; i++) { - let name: string = dimensionNames[i]; - let hidden: boolean = this.client.servers[name].hidden; - if (!hidden) { - dimensionsList += (i > 0 ? ", " : " ") + "/" + dimensionNames[i]; - } - } - + //CSFT - 标记 if (!this.client.wasKicked) { - this.client.sendChatMessage(this.client.options.language.phrases.dimensionDropped, "00BFFF"); - this.client.sendChatMessage(this.client.options.language.phrases.specifyADimensionToTravel + dimensionsList, "00BFFF"); - } else { - this.client.sendChatMessage(this.client.options.language.phrases.specifyADimensionToTravel + dimensionsList, "00BFFF"); - this.client.wasKicked = false; + this.client.sendChatMessage( + "[CSFT亚共体]你与服务器断开了连接!\n将在5秒后自动踢出地图..", + "FF6A6A", + ); + setTimeout(() => { + this.client.disconnect( + "[CSFT亚共体]服务器已关闭或无法进入\n请稍后重试...", + ); + }, 5000); } this.client.state = ClientState.Disconnected; @@ -268,8 +285,8 @@ class TerrariaServer { /* Checks the type of error, if it is because a server is down, the failed connection attempts * property is incremented until it reaches 3 at which point it is marked as closed and will not - * be used by clients. - * + * be used by clients. + * * TODO: Handle non-refused errors when the host itself is offline */ public handleError(error: Error): void { let matches: RegExpMatchArray | null = / E([A-z]*?) /.exec(error.message); @@ -287,7 +304,9 @@ class TerrariaServer { } if (this.client.options.log.tServerError) { - this.client.logging.error(`TerrariaServer Socket Error: ${ErrorHelper.toMessage(error)}`); + this.client.logging.error( + `TerrariaServer Socket Error: ${ErrorHelper.toMessage(error)}`, + ); } } } diff --git a/app/dimensions/terrariaserverpackethandler.ts b/app/dimensions/terrariaserverpackethandler.ts index 890568e..3fd08ce 100644 --- a/app/dimensions/terrariaserverpackethandler.ts +++ b/app/dimensions/terrariaserverpackethandler.ts @@ -1,18 +1,30 @@ -import PacketTypes from './packettypes.js'; -import { getProperIP } from './utils.js'; -import NPC from './npc.js'; -import TerrariaServer from './terrariaserver.js'; -import Client from './client.js'; -import RawPacket from './packets/rawpacket.js'; -import * as Net from 'net'; -import Item from './item.js'; -import Player from './player.js'; -import ClientState from './clientstate.js'; -import ErrorHelper from './errorhelper.js'; - -import { WorldInfoPacket, PlayerInfoPacket, NpcUpdatePacket, ItemDropUpdatePacket, PlayerSpawnPacket, NetModuleLoadPacket, DisconnectPacket, PlayerActivePacket, PlayerInventorySlotPacket, DimensionsUpdatePacket, Parser, } from "terraria-packet"; -import NetworkText from '@popstarfreas/packetfactory/networktext'; -import PacketWriter from '@popstarfreas/packetfactory/packetwriter'; +import PacketTypes from "./packettypes.js"; +import { getProperIP } from "./utils.js"; +import NPC from "./npc.js"; +import TerrariaServer from "./terrariaserver.js"; +import Client from "./client.js"; +import RawPacket from "./packets/rawpacket.js"; +import * as Net from "net"; +import Item from "./item.js"; +import Player from "./player.js"; +import ClientState from "./clientstate.js"; +import ErrorHelper from "./errorhelper.js"; + +import { + WorldInfoPacket, + PlayerInfoPacket, + NpcUpdatePacket, + ItemDropUpdatePacket, + PlayerSpawnPacket, + NetModuleLoadPacket, + DisconnectPacket, + PlayerActivePacket, + PlayerInventorySlotPacket, + Parser, +} from "terraria-packet"; +import NetworkText from "@popstarfreas/packetfactory/networktext"; +import PacketWriter from "@popstarfreas/packetfactory/packetwriter"; +import PacketReader from "@popstarfreas/packetfactory/packetreader"; /*let arr = [ Buffer.from("4f040a6d55bf8b244514ae7ad53fe6d7ce8ecd6ddfcd71c1a2230837e20ab2464251b72cd360b0b4a0e31ee2dd6920170c829c81260bee19191cf823d84065f10fb85ccb0bb6149954c30de5cc448cccc6ef5555f7ce8913f4eb7aef7bdffbdeab9aaebf7a427c2584f805cfa35f9f533b8e9c7e04635ca61f1997ab6da77fc062b622447ec41b7bced40110a4dd9d7b12d9422276665c82d86c25d5c8716e575b7e7c6f5c4f5b9f902b0d1cb9a3df734a4bf1b7d03f513a85a5742116781af1b3d05f70ec5ce81728656efd25db85d013f623de65dcbc417fce0ce72dc39c91cf7086f1c82718c6170ca598c678e9e32518392ed8df616ee1d9cab89aae693cf71a7d269965a1552cd36529f36851ee7e6c6418e548b3bca4899d0027710274a1dff88a735fb18c0482d7c3b80681cc6e9e22bddeab87f1cd2c49cbd850c2f282c8402dda3645a45b443d102f6999d4cc52301fed6d51fb8cdeb684e4129e4c357ae74ca9b8709851e32d63a132169aaf159afc6fa1896854b4858cef65ca56c56976e3e0ee5ff488addc885194d8ab933566159923a507ad513683f1fb7edc9cc279543d0daa7b13f158eed505387cbdb28124b1411509baeb1b762278752ca8e2a9933a64d5fd38289016e21f90e6d9d66ff8efd4d9d663e9f753669b857820eb6cf891f70f3f0be66b6f3605c7808216aa76fde99c8a875e50287812fb5a4459b029a9075c9c9e38169fa89135a4d43691da0de1ea301ec929eb6e29f1ae0a2e05cd3d8a9a8ba0b968359f06cd45d05c04cd45d07cda686e0a1e8a28fdd8cb79c813514d3b27de77cc439bb12049af315ced10324731bb01ce3d45c93350133f6896aa3f563b96acdab648b3c64a12fba22243b22dcf4c81ced7ad8a36194ee8dc5fd61bf853c1285a225919da246128d3f7667fa4fbb692c62a98949dc48f2414017a59e76816063d27c14884c619218c6f288fde925ee4235bbd5aad145b50f581790598e14bd56a50ada257cd56c98d11e0b9ef49bfc5b563a6c4fb00592fc30ef36d2cda34c823f48fe9610c4e703dce1c01bd0b7bc54f079f697264c965aec2c79b015b00bc087b75df55a9718a3ff46f463fc166b0d760bb99e03263c07a142013842e332484aefd27d485cd617978cf22d621c73701877743c6757825df398758bc1bc2843b032437e179071ba96ff30bc670877b043ecf50d071f61b84dd1e597d0bde3eafe304dec67a88f5eb9cce8b028b031ec82de398e21253058ee7991136c112c4586530e48c4d048adec67507df55209e82a5b6f666acc9d12ee4de451778bdecf727506ec06e04ce1edf9aef3d1d6e4d9df80ee11e03d5e392bc9b4c18d003f0bdef6f4e15c5f6d6280701d467ca0f423f99716944f479a29c616c1ef213607072432309677d685c47ab981a8e069f1a7c10ac9f3edfe8d808c56ede9c4f03340db4144ca6bff53d64ed48c8d81433e3e1e0ac69abbf09353b4867be541d581f1e605f387ebaa6a9e323192838f21de100bb4c60101d633b476757a4b821c7fd3f85fffd0b", "hex"), @@ -37,7 +49,7 @@ import PacketWriter from '@popstarfreas/packetfactory/packetwriter'; // to make sure extensions can do something with it. export enum PacketSource { TerrariaServer, - Dimensions + Dimensions, } /** @@ -57,14 +69,25 @@ class TerrariaServerPacketHandler { * @param packet The packet that is being sent * @return Whether or not the packet was handled (and should not be sent) */ - private runPriorHandlers(server: TerrariaServer, packet: RawPacket, source: PacketSource): boolean { + private runPriorHandlers( + server: TerrariaServer, + packet: RawPacket, + source: PacketSource, + ): boolean { let handlers = server.client.globalHandlers.extensions; let handled = false; for (let key in handlers) { let handler = handlers[key]; - if (typeof handler.priorPacketHandlers !== 'undefined' && typeof handler.priorPacketHandlers.serverHandler !== 'undefined') { + if ( + typeof handler.priorPacketHandlers !== "undefined" && + typeof handler.priorPacketHandlers.serverHandler !== "undefined" + ) { try { - handled = handler.priorPacketHandlers.serverHandler.handlePacket(server, packet, source); + handled = handler.priorPacketHandlers.serverHandler.handlePacket( + server, + packet, + source, + ); if (handled) { break; } @@ -88,14 +111,25 @@ class TerrariaServerPacketHandler { * @param packet The packet that is being sent * @return Whether or not the packet was handled (and should not be sent) */ - private runPostHandlers(server: TerrariaServer, packet: RawPacket, source: PacketSource): boolean { + private runPostHandlers( + server: TerrariaServer, + packet: RawPacket, + source: PacketSource, + ): boolean { let handlers = server.client.globalHandlers.extensions; let handled = false; for (let key in handlers) { let handler = handlers[key]; - if (typeof handler.postPacketHandlers !== 'undefined' && typeof handler.postPacketHandlers.serverHandler !== 'undefined') { + if ( + typeof handler.postPacketHandlers !== "undefined" && + typeof handler.postPacketHandlers.serverHandler !== "undefined" + ) { try { - handled = handler.postPacketHandlers.serverHandler.handlePacket(server, packet, source); + handled = handler.postPacketHandlers.serverHandler.handlePacket( + server, + packet, + source, + ); if (handled) { break; } @@ -119,7 +153,11 @@ class TerrariaServerPacketHandler { * @param packet The packet that is being sent * @return The packet data (either origin or modified) */ - public handlePacket(server: TerrariaServer, packet: RawPacket, source: PacketSource): Buffer | null { + public handlePacket( + server: TerrariaServer, + packet: RawPacket, + source: PacketSource, + ): Buffer | null { this.currentServer = server; let priorHandled: boolean = this.runPriorHandlers(server, packet, source); @@ -128,9 +166,7 @@ class TerrariaServerPacketHandler { } // Parse everything except TileSectionSend because that is a big packet - const parsedResult = Parser.parse(packet.data, true, [ - "TileSectionSend" - ]); + const parsedResult = Parser.parse(packet.data, true, ["TileSectionSend"]); let handled: boolean = false; if (parsedResult.TAG === "Error") { @@ -139,21 +175,31 @@ class TerrariaServerPacketHandler { switch (parseError.TAG) { case "ReaderError": if (parseError._0.error instanceof Error) { - server.client.logging.error(`Error parsing packet: ${parseError._0.context} ${parseError._0.error.message}`); + server.client.logging.error( + `Error parsing packet: ${parseError._0.context} ${parseError._0.error.message}`, + ); } else { - server.client.logging.error(`Error parsing packet: ${parseError._0.context}`); + server.client.logging.error( + `Error parsing packet: ${parseError._0.context}`, + ); } break; default: - server.client.logging.error(`Error parsing packet: ${PacketTypes[packet.packetType]} ${parseError.TAG}`); + server.client.logging.error( + `Error parsing packet: ${PacketTypes[packet.packetType]} ${parseError.TAG}`, + ); break; } } else { switch (parseError) { case "IgnoredPacket": - server.client.logging.info(`Ignoring packet: ${PacketTypes[packet.packetType]}`); + server.client.logging.info( + `Ignoring packet: ${PacketTypes[packet.packetType]}`, + ); default: - server.client.logging.error(`Error parsing packet: ${PacketTypes[packet.packetType]} ${parseError}`); + server.client.logging.error( + `Error parsing packet: ${PacketTypes[packet.packetType]} ${parseError}`, + ); break; } } @@ -178,7 +224,7 @@ class TerrariaServerPacketHandler { handled = this.handleCompleteConnectionAndSpawn(); break; case "DimensionsUpdate": - handled = this.handleDimensionsUpdate(parsed._0); + handled = this.handleDimensionsUpdate(packet); break; case "NpcUpdate": handled = this.handleNPCUpdate(parsed._0); @@ -235,7 +281,7 @@ class TerrariaServerPacketHandler { if (!client.ingame) { client.disconnect(reason); } else { - var color = "C8FF00"; + var color = "FF6A6A"; var message = client.options.language.phrases.dimensionDisconnectedYou; const disconnectOnKick = client.options.disconnectOnKick; switch (disconnectOnKick.type) { @@ -253,11 +299,24 @@ class TerrariaServerPacketHandler { } break; } + //CSFT - 修改 client.sendChatMessage(message, color); - client.sendChatMessage(new NetworkText(1, client.options.language.phrases.reason + "{0}", [reason]), color); + client.sendChatMessage( + new NetworkText(1, client.options.language.phrases.reason + "{0}", [ + reason, + ]), + color, + ); client.wasKicked = true; client.connected = false; - + setTimeout(() => { + client.disconnect( + new NetworkText(1, client.options.language.phrases.reason + "{0}", [ + reason, + ]), + ); + }, 5000); + //client.disconnect_CSFT(new NetworkText(1, client.options.language.phrases.reason + "{0}", [reason])); if (this.socket) { this.socket.destroy(); } @@ -277,12 +336,13 @@ class TerrariaServerPacketHandler { // Send IP Address if (!this.currentServer.isVanilla) { - let ip: string = getProperIP(this.currentServer.client.socket.remoteAddress as string) as string; + let ip: string = getProperIP( + this.currentServer.client.socket.remoteAddress as string, + ) as string; const packetData = new PacketWriter() .setType(PacketTypes.DimensionsUpdate) .packInt16(0) // Type - .packString(ip) - .data; + .packString(ip).data; this.currentServer.sendDirect(packetData); } @@ -299,7 +359,10 @@ class TerrariaServerPacketHandler { private handleWorldInfo(worldInfo: WorldInfoPacket.t): boolean { this.currentServer.isSSC = worldInfo.eventInfo.serverSidedCharacters; - if (this.currentServer.client.waitingCharacterRestore && !this.currentServer.isSSC) { + if ( + this.currentServer.client.waitingCharacterRestore && + !this.currentServer.isSSC + ) { this.restoreInventory(this.currentServer.client); this.restoreLife(this.currentServer.client); this.restoreMana(this.currentServer.client); @@ -307,7 +370,10 @@ class TerrariaServerPacketHandler { } this.currentServer.client.waitingCharacterRestore = false; - if (this.currentServer.client.state === ClientState.ConnectionSwitchEstablished) { + if ( + this.currentServer.client.state === + ClientState.ConnectionSwitchEstablished + ) { this.currentServer.spawn.x = worldInfo.spawnX; this.currentServer.spawn.y = worldInfo.spawnY; @@ -316,8 +382,7 @@ class TerrariaServerPacketHandler { let getSection = new PacketWriter() .setType(PacketTypes.GetSectionOrRequestSync) .packSingle(-1) - .packSingle(-1) - .data; + .packSingle(-1).data; this.currentServer.sendDirect(getSection); this.currentServer.client.state = ClientState.FinalisingSwitch; @@ -327,8 +392,7 @@ class TerrariaServerPacketHandler { let dimensionsUpdate = new PacketWriter() .setType(PacketTypes.DimensionsUpdate) .packInt16(this.currentServer.client.routingInformation.type) - .packString(this.currentServer.client.routingInformation.info) - .data; + .packString(this.currentServer.client.routingInformation.info).data; this.currentServer.sendDirect(dimensionsUpdate); this.currentServer.client.routingInformation = null; } @@ -357,14 +421,19 @@ class TerrariaServerPacketHandler { numberOfDeathsPve: 0, numberOfDeathsPvp: 0, team: 0, - }) + }); if (spawnPlayer.TAG === "Error") { - this.currentServer.client.logging.error(`Error creating spawn player packet: ${spawnPlayer._0}`); + this.currentServer.client.logging.error( + `Error creating spawn player packet: ${spawnPlayer._0}`, + ); return true; } - if (typeof server.client !== 'undefined' && typeof server.client.socket !== 'undefined') { + if ( + typeof server.client !== "undefined" && + typeof server.client.socket !== "undefined" + ) { server.sendDirect(spawnPlayer._0); if (!server.client.preventSpawnOnJoin) { @@ -373,7 +442,6 @@ class TerrariaServerPacketHandler { } } - if (server.client.state === ClientState.FinishinedSendingInventory) { server.client.state = ClientState.FullyConnected; //server.client.sendWaitingPackets(); @@ -406,44 +474,75 @@ class TerrariaServerPacketHandler { * @param packet The dimensions update packet * @return Whether or not the packet has been handled (and is not to be sent) */ - private handleDimensionsUpdate(dimensionsUpdate: DimensionsUpdatePacket.t): boolean { - switch (dimensionsUpdate) { - case "GamemodesJoinMode": - return true; - default: - switch (dimensionsUpdate.TAG) { - case "RealIpAddress": - return true; - case "SwitchServer": - if (this.currentServer.client.servers[dimensionsUpdate._0.toLowerCase()]) { - const phrases = this.currentServer.client.options.language.phrases; - this.currentServer.client.sendChatMessage(phrases.shiftingToDimension.replace("${name}", dimensionsUpdate._0), "FF0000"); - this.currentServer.client.changeServer(this.currentServer.client.servers[dimensionsUpdate._0.toLowerCase()], { - preventSpawnOnJoin: false - }); - } - return true; - case "SwitchServerManual": - const currentServerIp = this.currentServer.socket.remoteAddress; - let ip = dimensionsUpdate._0; - const port: number = dimensionsUpdate._1; - if (ip === "127.0.0.1" && typeof currentServerIp !== "undefined") { - ip = currentServerIp; - } - this.currentServer.client.changeServer({ - name: `${ip}:${port}`, - serverIP: ip, - serverPort: port, - hidden: false, - isVanilla: false, - }, { - preventSpawnOnJoin: false - }); - return true; - default: - return true; + private handleDimensionsUpdate(packet: RawPacket): boolean { + const reader: PacketReader = new PacketReader(packet.data); + const messageType: number = reader.readInt16(); + const messageContent: string = reader.readString(); + + // Switch server + if (messageType === 2) { + if (this.currentServer.client.servers[messageContent.toLowerCase()]) { + if (messageContent.toLowerCase() === this.currentServer.name) { + this.currentServer.client.sendChatMessage( + "[i:3459]CSFT[i:3459] 你当前已经在此服务器了!", + "00CED1", + ); + } else { + const phrases = this.currentServer.client.options.language.phrases; + this.currentServer.client.sendChatMessage( + phrases.shiftingToDimension.replace("${name}", messageContent), + "00CED1", + ); + this.currentServer.client.changeServer( + this.currentServer.client.servers[messageContent.toLowerCase()], + { + preventSpawnOnJoin: false, + }, + ); } + } + } + + if (messageType === 3) { + const currentServerIp = this.currentServer.socket.remoteAddress; + let ip = messageContent; + const port: number = reader.readUInt16(); + if (ip === "127.0.0.1" && typeof currentServerIp !== "undefined") { + ip = currentServerIp; + } + this.currentServer.client.changeServer( + { + name: `${ip}:${port}`, + serverIP: ip, + serverPort: port, + hidden: false, + isVanilla: false, + }, + { + preventSpawnOnJoin: false, + }, + ); + } + + if (messageType === 4) { + if (this.currentServer.client.servers[messageContent.toLowerCase()]) { + const extraJoinInformation: string = reader.readString(); + const phrases = this.currentServer.client.options.language.phrases; + this.currentServer.client.sendChatMessage( + phrases.shiftingToDimension.replace("${name}", messageContent), + "00CED1", + ); + this.currentServer.client.changeServer( + this.currentServer.client.servers[messageContent.toLowerCase()], + { + preventSpawnOnJoin: false, + extraJoinInformation, + }, + ); + } } + + return true; } /** @@ -462,9 +561,14 @@ class TerrariaServerPacketHandler { if (npcTypeId === 0 || zeroLife) { this.currentServer.entityTracking.NPCs[npcSlotId] = undefined; } else { - let npc: NPC | undefined = this.currentServer.entityTracking.NPCs[npcSlotId] + let npc: NPC | undefined = + this.currentServer.entityTracking.NPCs[npcSlotId]; if (npc === undefined) { - this.currentServer.entityTracking.NPCs[npcSlotId] = new NPC(npcSlotId, npcTypeId, life === "Max" ? 1 : life._0); + this.currentServer.entityTracking.NPCs[npcSlotId] = new NPC( + npcSlotId, + npcTypeId, + life === "Max" ? 1 : life._0, + ); } else { npc.life = life === "Max" ? 1 : life._0; npc.type = npcTypeId; @@ -480,10 +584,17 @@ class TerrariaServerPacketHandler { * @param packet The update item drop packet * @return Whether or not this packet was handled (and should not be sent) */ - private handleUpdateItemDrop(itemDropUpdate: ItemDropUpdatePacket.t): boolean { + private handleUpdateItemDrop( + itemDropUpdate: ItemDropUpdatePacket.t, + ): boolean { const { itemDropId, stack, prefix, itemId } = itemDropUpdate; if (itemDropId > 0) { - this.currentServer.entityTracking.items[itemDropId] = new Item(itemDropId, stack, prefix, itemId); + this.currentServer.entityTracking.items[itemDropId] = new Item( + itemDropId, + stack, + prefix, + itemId, + ); } else { this.currentServer.entityTracking.items[itemDropId] = undefined; } @@ -512,7 +623,10 @@ class TerrariaServerPacketHandler { * * @param packet The player inventory slot packet */ - private handlePlayerInventorySlot(inventorySlot: PlayerInventorySlotPacket.t, rawPacket: RawPacket): boolean { + private handlePlayerInventorySlot( + inventorySlot: PlayerInventorySlotPacket.t, + rawPacket: RawPacket, + ): boolean { let handled = false; const { playerId } = inventorySlot; if (playerId === this.currentServer.client.player.id) { @@ -520,8 +634,8 @@ class TerrariaServerPacketHandler { this.currentServer.packetQueue.push({ rawPacket: { data: rawPacket.data, - packetType: rawPacket.packetType - } + packetType: rawPacket.packetType, + }, }); handled = true; } @@ -530,14 +644,30 @@ class TerrariaServerPacketHandler { return handled; } - private handlePlayerInfo(playerInfo: PlayerInfoPacket.t, rawPacket: RawPacket): boolean { - const nameMismatchesRequireRewrite = this.currentServer.client.options.nameChanges?.mode === "rewrite"; - const isAboutCurrentClient = playerInfo.playerId === this.currentServer.client.player.id; - const isMismatchedName = this.currentServer.client.player.name !== playerInfo.name; - const isAllowedToRename = (this.currentServer.client.options.nameChanges?.exclusions.indexOf(this.currentServer.name) ?? -1) > -1; - if (nameMismatchesRequireRewrite && isAboutCurrentClient && isMismatchedName) { + private handlePlayerInfo( + playerInfo: PlayerInfoPacket.t, + rawPacket: RawPacket, + ): boolean { + const nameMismatchesRequireRewrite = + this.currentServer.client.options.nameChanges?.mode === "rewrite"; + const isAboutCurrentClient = + playerInfo.playerId === this.currentServer.client.player.id; + const isMismatchedName = + this.currentServer.client.player.name !== playerInfo.name; + const isAllowedToRename = + (this.currentServer.client.options.nameChanges?.exclusions.indexOf( + this.currentServer.name, + ) ?? -1) > -1; + if ( + nameMismatchesRequireRewrite && + isAboutCurrentClient && + isMismatchedName + ) { if (!isAllowedToRename) { - const playerInfoPacket = PlayerInfoPacket.toBuffer({ ...playerInfo, name: this.currentServer.client.player.name }); + const playerInfoPacket = PlayerInfoPacket.toBuffer({ + ...playerInfo, + name: this.currentServer.client.player.name, + }); if (playerInfoPacket.TAG === "Ok") { rawPacket.data = playerInfoPacket._0; } @@ -559,13 +689,14 @@ class TerrariaServerPacketHandler { this.currentServer.entityTracking.pylons.push({ x: x, y: y, - type: pylonType + type: pylonType, }); break; case "Removed": - this.currentServer.entityTracking.pylons = this.currentServer.entityTracking.pylons.filter(pylon => { - pylon.x !== x || pylon.y !== y || pylon.type !== pylonType; - }); + this.currentServer.entityTracking.pylons = + this.currentServer.entityTracking.pylons.filter((pylon) => { + pylon.x !== x || pylon.y !== y || pylon.type !== pylonType; + }); break; case "RequestTeleport": break; @@ -621,6 +752,6 @@ class TerrariaServerPacketHandler { private restoreVisuals(client: Client): void { client.player.setVisuals(); } -}; +} export default TerrariaServerPacketHandler; diff --git a/package-lock.json b/package-lock.json index 48d9f33..fbf0bc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "@popstarfreas/dimensions", - "version": "6.3.0", + "version": "7.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@popstarfreas/dimensions", - "version": "6.3.0", + "version": "7.0.0", "license": "ISC", "dependencies": { + "@popstarfreas/packetfactory": "^7.1.3", "@rescript/runtime": "^12.0.0", "glob": "^11.0.3", "redis": "^3.1.2", - "require-nocache": "^1.0.0", - "terraria-packet": "github:popstarfreas/rescript-terrariapacket#d0d004ba0488940cfc7b885b3095f661f4d686b4", + "terraria-packet": "github:popstarfreas/rescript-terrariapacket#b17eacb4100769755bdd9db66009770335cb6232", "utf8": "^3.0.0", "uuid": "^11.1.0", "winston": "^3.17.0", @@ -29,7 +29,6 @@ "@types/uuid": "^10.0.0", "jasmine": "^3.4.0", "mitm": "^1.7.3", - "rescript": "^11.0.0", "typescript": "^5.8.3" } }, @@ -92,13 +91,11 @@ } }, "node_modules/@popstarfreas/packetfactory": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@popstarfreas/packetfactory/-/packetfactory-6.3.0.tgz", - "integrity": "sha512-1Ch07cpHeT4TYSVfqWRkAEuBo51XoEx7CyFKJs/Unj+LvJA5UnJ0P4YB3h4L9gqvOQRg4iCA+NvhmHXKxYHsRg==", - "license": "ISC", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@popstarfreas/packetfactory/-/packetfactory-7.2.1.tgz", + "integrity": "sha512-gn4Z3vqKo7Y+3HshxjBT3i1m8+n/sTcsgUD+vqGdGQCBM4/iGX5W4FW5PRmYJtLoPsZY1VX3vBw0fMQCLfaaLw==", "dependencies": { - "@popstarfreas/rescript-nodejs": "git+https://github.com/popstarfreas/rescript-nodejs.git#f3bd284a3ef17a3fc7cc81ccb7c362b278ea66c7", - "utf8": "^2.1.2" + "@popstarfreas/rescript-nodejs": "git+https://github.com/popstarfreas/rescript-nodejs.git#f3bd284a3ef17a3fc7cc81ccb7c362b278ea66c7" }, "optionalDependencies": { "rescript": "^12.0.0" @@ -141,12 +138,6 @@ "@rescript/win32-x64": "12.0.0" } }, - "node_modules/@popstarfreas/packetfactory/node_modules/utf8": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", - "integrity": "sha512-QXo+O/QkLP/x1nyi54uQiG0XrODxdysuQvE5dtVqv7F5K2Qb6FsN+qbr6KhF5wQ20tfcV3VQp0/2x1e1MRSPWg==", - "license": "MIT" - }, "node_modules/@popstarfreas/rescript-nodejs": { "version": "16.1.0", "resolved": "git+ssh://git@github.com/popstarfreas/rescript-nodejs.git#f3bd284a3ef17a3fc7cc81ccb7c362b278ea66c7", @@ -832,18 +823,13 @@ "node": ">=4" } }, - "node_modules/require-nocache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/require-nocache/-/require-nocache-1.0.0.tgz", - "integrity": "sha512-nemgZOwvrnGtMH6DQ7n17RQNrQ779BBUiPJTeqrdXNE4ytaoX+HTflXvNlyKooJoFbNUJEtZmm9FlbmjxUXDgA==", - "license": "MIT" - }, "node_modules/rescript": { "version": "11.1.4", "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.4.tgz", "integrity": "sha512-0bGU0bocihjSC6MsE3TMjHjY0EUpchyrREquLS8VsZ3ohSMD+VHUEwimEfB3kpBI1vYkw3UFZ3WD8R28guz/Vw==", "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE", + "peer": true, "bin": { "bsc": "bsc", "bstracing": "lib/bstracing", @@ -1049,50 +1035,19 @@ } }, "node_modules/terraria-packet": { - "version": "1.2.0", - "resolved": "git+ssh://git@github.com/popstarfreas/rescript-terrariapacket.git#d0d004ba0488940cfc7b885b3095f661f4d686b4", - "integrity": "sha512-589jdNNzpMW/9EMWKf7ihAlX1d2IU/bXIhi2qK2h6Irec4NCeXa3Dk0ZNKuxF9DhXrIwm+VgU4TmHundpz8hRg==", + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/popstarfreas/rescript-terrariapacket.git#b17eacb4100769755bdd9db66009770335cb6232", + "integrity": "sha512-vE046tRBMi//mlC5YcGPYLqB5mEECodj9R0aNePmFBZg7AKIVIxDt54oSRhGaLko1r4xibP+Dyx+SuxnqYp+4Q==", "dependencies": { - "@popstarfreas/packetfactory": "^6.3.0", + "@popstarfreas/packetfactory": "^7.1.3", "@rescript/core": "^1.6.1", - "rescript": "^12.0.0" + "@rescript/runtime": "^12.0.1" } }, - "node_modules/terraria-packet/node_modules/rescript": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-12.0.0.tgz", - "integrity": "sha512-DGcZI2L5W0c6FuEnspLE0MIe1UtTt1VsW/vQfzBFCEBxSsQtoA6YRHUB8Puwnb30PHqZiFK1ADhn6UgA8LWK0A==", - "license": "SEE LICENSE IN LICENSE", - "workspaces": [ - "packages/playground", - "packages/@rescript/*", - "tests/dependencies/**", - "tests/analysis_tests/**", - "tests/docstring_tests", - "tests/gentype_tests/**", - "tests/tools_tests", - "scripts/res" - ], - "dependencies": { - "@rescript/runtime": "12.0.0" - }, - "bin": { - "bsc": "cli/bsc.js", - "bstracing": "cli/bstracing.js", - "rescript": "cli/rescript.js", - "rescript-legacy": "cli/rescript-legacy.js", - "rescript-tools": "cli/rescript-tools.js" - }, - "engines": { - "node": ">=20.11.0" - }, - "optionalDependencies": { - "@rescript/darwin-arm64": "12.0.0", - "@rescript/darwin-x64": "12.0.0", - "@rescript/linux-arm64": "12.0.0", - "@rescript/linux-x64": "12.0.0", - "@rescript/win32-x64": "12.0.0" - } + "node_modules/terraria-packet/node_modules/@rescript/runtime": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@rescript/runtime/-/runtime-12.1.0.tgz", + "integrity": "sha512-bvr9RfvBD+JS/6foWCA4l2fLXmUXN0KGqylXQPHt09QxUghqgoCiaWVHaHSx5dOIk/jAPlGQ7zB5yVeMas/EFQ==" }, "node_modules/text-hex": { "version": "1.0.0", From fa70934b54e7a9186c7853dd1ff646c95089dd83 Mon Sep 17 00:00:00 2001 From: Cjx8848 Date: Fri, 30 Jan 2026 14:59:12 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=90=8C=E6=AD=A5dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/dimensions/terrariaserverpackethandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/dimensions/terrariaserverpackethandler.ts b/app/dimensions/terrariaserverpackethandler.ts index 913ee74..24aae53 100644 --- a/app/dimensions/terrariaserverpackethandler.ts +++ b/app/dimensions/terrariaserverpackethandler.ts @@ -20,7 +20,6 @@ import { DisconnectPacket, PlayerActivePacket, PlayerInventorySlotPacket, - DimensionsUpdatePacket, Parser, } from "terraria-packet"; import NetworkText from "@popstarfreas/packetfactory/networktext"; From 2793c868c1b40a472f7ddd3bb9cab4761720af3b Mon Sep 17 00:00:00 2001 From: Cjx8848 Date: Tue, 3 Feb 2026 13:07:59 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/dimensions/terrariaserverpackethandler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/dimensions/terrariaserverpackethandler.ts b/app/dimensions/terrariaserverpackethandler.ts index 24aae53..3785f51 100644 --- a/app/dimensions/terrariaserverpackethandler.ts +++ b/app/dimensions/terrariaserverpackethandler.ts @@ -382,7 +382,8 @@ class TerrariaServerPacketHandler { let getSection = new PacketWriter() .setType(PacketTypes.GetSectionOrRequestSync) .packSingle(-1) - .packSingle(-1).data; + .packSingle(-1) + .packSingle(0).data; this.currentServer.sendDirect(getSection); this.currentServer.client.state = ClientState.FinalisingSwitch;