/// <summary> /// Starts this server. /// </summary> public async Task StartServerAsync() { this.StartTime = DateTimeOffset.Now; this.Logger.LogInformation($"Launching Obsidian Server v{Version} with ID {Id}"); // Check if MPDM and OM are enabled, if so, we can't handle connections if (this.Config.MulitplayerDebugMode && this.Config.OnlineMode) { this.Logger.LogError("Incompatible Config: Multiplayer debug mode can't be enabled at the same time as online mode since usernames will be overwritten"); this.StopServer(); return; } await Registry.RegisterBlocksAsync(); await Registry.RegisterItemsAsync(); await Registry.RegisterBiomesAsync(); await Registry.RegisterDimensionsAsync(); await Registry.RegisterTagsAsync(); PacketHandler.RegisterHandlers(); this.Logger.LogInformation($"Loading properties..."); await(this.Operators as OperatorList).InitializeAsync(); await this.RegisterDefaultAsync(); this.Logger.LogInformation("Loading plugins..."); Directory.CreateDirectory(Path.Join(ServerFolderPath, "plugins")); // Creates if doesn't exist. this.PluginManager.DirectoryWatcher.Filters = new[] { ".cs", ".dll" }; this.PluginManager.DefaultPermissions = API.Plugins.PluginPermissions.All; this.PluginManager.DirectoryWatcher.Watch(Path.Join(ServerFolderPath, "plugins")); await Task.WhenAll(Config.DownloadPlugins.Select(path => PluginManager.LoadPluginAsync(path))); this.World = new World("world", this); if (!this.World.Load()) { if (!this.WorldGenerators.TryGetValue(this.Config.Generator, out WorldGenerator value)) { this.Logger.LogWarning($"Unknown generator type {this.Config.Generator}"); } var gen = value ?? new SuperflatGenerator(); this.Logger.LogInformation($"Creating new {gen.Id} ({gen}) world..."); this.World.Init(gen); // TODO: save world } if (!this.Config.OnlineMode) { this.Logger.LogInformation($"Starting in offline mode..."); } _ = Task.Run(this.ServerLoop); this.Logger.LogDebug($"Listening for new clients..."); this.tcpListener.Start(); while (!this.cts.IsCancellationRequested) { if (this.tcpListener.Pending()) { var tcp = await this.tcpListener.AcceptTcpClientAsync(); this.Logger.LogDebug($"New connection from client with IP {tcp.Client.RemoteEndPoint}"); var client = new Client(tcp, this.Config, Math.Max(0, this.clients.Count + this.World.TotalLoadedEntities()), this); this.clients.Add(client); _ = Task.Run(client.StartConnectionAsync); } await Task.Delay(50); } this.Logger.LogWarning("Server is shutting down..."); }
private Task <Packet> GetNextPacketAsync() => this.compressionEnabled ? PacketHandler.ReadCompressedPacketAsync(this.minecraftStream) : PacketHandler.ReadPacketAsync(this.minecraftStream);
public async Task StartConnectionAsync() { _ = Task.Run(ProcessQueue); while (!Cancellation.IsCancellationRequested && this.tcp.Connected) { Packet packet = await this.GetNextPacketAsync(); if (this.State == ClientState.Play && packet.data.Length < 1) { this.Disconnect(); } switch (this.State) { case ClientState.Status: //server ping/list switch (packet.id) { case 0x00: var status = new ServerStatus(Server); await this.SendPacketAsync(new RequestResponse(status)); break; case 0x01: await this.SendPacketAsync(new PingPong(packet.data)); this.Disconnect(); break; } break; case ClientState.Handshaking: if (packet.id == 0x00) { if (packet == null) { throw new InvalidOperationException(); } var handshake = await PacketSerializer.FastDeserializeAsync <Handshake>(packet.data); var nextState = handshake.NextState; if (nextState != ClientState.Status && nextState != ClientState.Login) { this.Logger.LogDebug($"Client sent unexpected state ({(int)nextState}), forcing it to disconnect"); await this.DisconnectAsync(ChatMessage.Simple("you seem suspicious")); } this.State = nextState; this.Logger.LogInformation($"Handshaking with client (protocol: {handshake.Version}, server: {handshake.ServerAddress}:{handshake.ServerPort})"); } else { //Handle legacy ping stuff } break; case ClientState.Login: switch (packet.id) { default: this.Logger.LogError("Client in state Login tried to send an unimplemented packet. Forcing it to disconnect."); await this.DisconnectAsync("Unknown Packet Id."); break; case 0x00: var loginStart = await PacketSerializer.FastDeserializeAsync <LoginStart>(packet.data); string username = config.MulitplayerDebugMode ? $"Player{Program.Random.Next(1, 999)}" : loginStart.Username; this.Logger.LogDebug($"Received login request from user {loginStart.Username}"); await this.Server.DisconnectIfConnectedAsync(username); if (this.config.OnlineMode) { var user = await MinecraftAPI.GetUserAsync(loginStart.Username); this.Player = new Player(Guid.Parse(user.Id), loginStart.Username, this) { World = this.Server.World }; this.packetCryptography.GenerateKeyPair(); var values = this.packetCryptography.GeneratePublicKeyAndToken(); this.randomToken = values.randomToken; await this.SendPacketAsync(new EncryptionRequest(values.publicKey, this.randomToken)); break; } this.Player = new Player(UUIDFactory.CreateUUID(3, 1, $"OfflinePlayer:{username}"), username, this) { World = this.Server.World }; //await this.SetCompression(); await this.ConnectAsync(); break; case 0x01: var encryptionResponse = PacketSerializer.FastDeserialize <EncryptionResponse>(packet.data); this.sharedKey = this.packetCryptography.Decrypt(encryptionResponse.SharedSecret); var decryptedToken = this.packetCryptography.Decrypt(encryptionResponse.VerifyToken); if (!decryptedToken.SequenceEqual(this.randomToken)) { await this.DisconnectAsync("Invalid token.."); break; } var serverId = sharedKey.Concat(this.packetCryptography.PublicKey).ToArray().MinecraftShaDigest(); JoinedResponse response = await MinecraftAPI.HasJoined(this.Player.Username, serverId); if (response is null) { this.Logger.LogWarning($"Failed to auth {this.Player.Username}"); await this.DisconnectAsync("Unable to authenticate.."); break; } this.encryptionEnabled = true; this.minecraftStream = new AesStream(this.debugStream ?? (Stream)this.tcp.GetStream(), this.sharedKey); //await this.SetCompression(); await ConnectAsync(); break; case 0x02: // Login Plugin Response break; } break; case ClientState.Play: //await this.Logger.LogDebugAsync($"Received Play packet with Packet ID 0x{packet.id.ToString("X")}"); await PacketHandler.HandlePlayPackets(packet, this); break; } //await Task.Delay(50); } Logger.LogInformation($"Disconnected client"); if (this.State == ClientState.Play) { await this.Server.Events.InvokePlayerLeaveAsync(new PlayerLeaveEventArgs(this.Player)); } if (tcp.Connected) { this.tcp.Close(); if (this.Player != null) { this.Server.OnlinePlayers.TryRemove(this.Player.Uuid, out var _); } } }
private async Task ConnectAsync(Guid uuid) { await PacketHandler.CreateAsync(new LoginSuccess(uuid, this.Player.Username), this.MinecraftStream); this.Logger.LogDebug($"Sent Login success to user {this.Player.Username} {this.Player.Uuid.ToString()}"); this.State = ClientState.Play; this.Player.Gamemode = Gamemode.Creative; await PacketHandler.CreateAsync(new JoinGame((int)(EntityId.Player | (EntityId)this.PlayerId), Gamemode.Creative, 0, 0, "default", true), this.MinecraftStream); this.Logger.LogDebug("Sent Join Game packet."); await PacketHandler.CreateAsync(new SpawnPosition(new Position(0, 100, 0)), this.MinecraftStream); this.Logger.LogDebug("Sent Spawn Position packet."); await PacketHandler.CreateAsync(new PlayerPositionLook(new Transform(0, 105, 0), PositionFlags.NONE, 0), this.MinecraftStream); this.Logger.LogDebug("Sent Position packet."); using (var stream = new MinecraftStream()) { await stream.WriteStringAsync("obsidian"); await PacketHandler.CreateAsync(new PluginMessage("minecraft:brand", stream.ToArray()), this.MinecraftStream); } this.Logger.LogDebug("Sent server brand."); this.OriginServer.Broadcast(string.Format(this.Config.JoinMessage, this.Player.Username)); await this.OriginServer.Events.InvokePlayerJoin(new PlayerJoinEventArgs(this, DateTimeOffset.Now)); await this.SendDeclareCommandsAsync(); await this.SendPlayerInfoAsync(); await this.SendPlayerListHeaderFooterAsync(string.IsNullOrWhiteSpace(OriginServer.Config.Header)?null : ChatMessage.Simple(OriginServer.Config.Header), string.IsNullOrWhiteSpace(OriginServer.Config.Footer)?null : ChatMessage.Simple(OriginServer.Config.Footer)); this.Logger.LogDebug("Sent player list decoration"); await this.SendChunkAsync(OriginServer.WorldGenerator.GenerateChunk(new Chunk(0, 0))); await this.SendChunkAsync(OriginServer.WorldGenerator.GenerateChunk(new Chunk(-1, 0))); await this.SendChunkAsync(OriginServer.WorldGenerator.GenerateChunk(new Chunk(0, -1))); await this.SendChunkAsync(OriginServer.WorldGenerator.GenerateChunk(new Chunk(-1, -1))); //await OriginServer.world.resendBaseChunksAsync(10, 0, 0, 0, 0, this); this.Logger.LogDebug("Sent chunk"); /*if (this.OriginServer.Config.OnlineMode) * { * await this.OriginServer.SendNewPlayer(this.PlayerId, this.Player.Uuid, new Transform * { * X = 0, * * Y = 105, * * Z = 0, * * Pitch = 1, * * Yaw = 1 * }); * } * else * { * await this.OriginServer.SendNewPlayer(this.PlayerId, this.Player.Uuid3, new Transform * { * X = 0, * * Y = 105, * * Z = 0, * * Pitch = 1, * * Yaw = 1 * }); * }*/ }
internal async Task DisconnectAsync(ChatMessage reason) { await PacketHandler.CreateAsync(new Disconnect(reason, this.State), this.MinecraftStream); }
private async Task <Packet> GetNextPacketAsync() { return(await PacketHandler.ReadFromStreamAsync(this.MinecraftStream)); }
public async Task StartConnectionAsync() { while (!Cancellation.IsCancellationRequested && this.Tcp.Connected) { Packet packet = this.Compressed ? await this.GetNextCompressedPacketAsync() : await this.GetNextPacketAsync(); Packet returnPacket; if (this.State == ClientState.Play && packet.PacketData.Length < 1) { this.Disconnect(); } switch (this.State) { case ClientState.Status: //server ping/list switch (packet.PacketId) { case 0x00: var status = new ServerStatus(OriginServer); await PacketHandler.CreateAsync(new RequestResponse(status), this.MinecraftStream); break; case 0x01: await PacketHandler.CreateAsync(new PingPong(packet.PacketData), this.MinecraftStream); this.Disconnect(); break; } break; case ClientState.Handshaking: if (packet.PacketId == 0x00) { if (packet == null) { throw new InvalidOperationException(); } var handshake = await PacketHandler.CreateAsync(new Handshake(packet.PacketData)); var nextState = handshake.NextState; if (nextState != ClientState.Status && nextState != ClientState.Login) { this.Logger.LogDebug($"Client sent unexpected state ({(int)nextState}), forcing it to disconnect"); await this.DisconnectAsync(Chat.ChatMessage.Simple("you seem suspicious")); } this.State = nextState; this.Logger.LogMessage($"Handshaking with client (protocol: {handshake.Version}, server: {handshake.ServerAddress}:{handshake.ServerPort})"); } else { //Handle legacy ping stuff } break; case ClientState.Login: switch (packet.PacketId) { default: this.Logger.LogError("Client in state Login tried to send an unimplemented packet. Forcing it to disconnect."); await this.DisconnectAsync(ChatMessage.Simple("Unknown Packet Id.")); break; case 0x00: var loginStart = await PacketHandler.CreateAsync(new LoginStart(packet.PacketData)); string username = loginStart.Username; if (Config.MulitplayerDebugMode) { username = $"Player{new Random().Next(1, 999)}"; this.Logger.LogDebug($"Overriding username from {loginStart.Username} to {username}"); } this.Logger.LogDebug($"Received login request from user {loginStart.Username}"); if (this.OriginServer.CheckPlayerOnline(username)) { await this.OriginServer.Clients.FirstOrDefault(c => c.Player.Username == username).DisconnectAsync(Chat.ChatMessage.Simple("Logged in from another location")); } if (this.Config.OnlineMode) { var users = await MinecraftAPI.GetUsersAsync(new string[] { loginStart.Username }); var uid = users.FirstOrDefault(); var uuid = Guid.Parse(uid.Id); this.Player = new Player(uuid, loginStart.Username, this); PacketCryptography.GenerateKeyPair(); var pubKey = PacketCryptography.PublicKeyToAsn(); this.Token = PacketCryptography.GetRandomToken(); returnPacket = await PacketHandler.CreateAsync(new EncryptionRequest(pubKey, this.Token), this.MinecraftStream); break; } this.Player = new Player(Guid.NewGuid(), username, this); await ConnectAsync(this.Player.Uuid); break; case 0x01: var encryptionResponse = await PacketHandler.CreateAsync(new EncryptionResponse(packet.PacketData)); JoinedResponse response; this.SharedKey = PacketCryptography.Decrypt(encryptionResponse.SharedSecret); var dec2 = PacketCryptography.Decrypt(encryptionResponse.VerifyToken); var dec2Base64 = Convert.ToBase64String(dec2); var tokenBase64 = Convert.ToBase64String(this.Token); if (!dec2Base64.Equals(tokenBase64)) { await this.DisconnectAsync(Chat.ChatMessage.Simple("Invalid token..")); break; } var encodedKey = PacketCryptography.PublicKeyToAsn(); var serverId = PacketCryptography.MinecraftShaDigest(SharedKey.Concat(encodedKey).ToArray()); response = await MinecraftAPI.HasJoined(this.Player.Username, serverId); if (response is null) { this.Logger.LogWarning($"Failed to auth {this.Player.Username}"); await this.DisconnectAsync(Chat.ChatMessage.Simple("Unable to authenticate..")); break; } this.EncryptionEnabled = true; this.MinecraftStream = new AesStream(this.Tcp.GetStream(), this.SharedKey); await ConnectAsync(new Guid(response.Id)); break; case 0x02: // Login Plugin Response break; } break; case ClientState.Play: ///this.Logger.LogDebugAsync($"Received Play packet with Packet ID 0x{packet.PacketId.ToString("X")}"); await PacketHandler.HandlePlayPackets(packet, this); break; } } Logger.LogMessage($"Disconnected client"); if (this.IsPlaying) { await this.OriginServer.Events.InvokePlayerLeave(new PlayerLeaveEventArgs(this)); } this.OriginServer.Broadcast(string.Format(this.Config.LeaveMessage, this.Player.Username)); this.Player = null; if (Tcp.Connected) { this.Tcp.Close(); } }
internal async Task SendPlayerListHeaderFooterAsync(ChatMessage header, ChatMessage footer) { await PacketHandler.CreateAsync(new PlayerListHeaderFooter(header, footer), this.MinecraftStream); this.Logger.LogDebug("Sent Player List Footer Header packet."); }
internal async Task SendDeclareCommandsAsync() { var packet = new DeclareCommands(); var node = new CommandNode() { Type = CommandNodeType.Root }; foreach (Qmmands.Command command in this.OriginServer.Commands.GetAllCommands()) { var commandNode = new CommandNode() { Name = command.Name, Type = CommandNodeType.Literal }; foreach (Qmmands.Parameter parameter in command.Parameters) { var parameterNode = new CommandNode() { Name = parameter.Name, Type = CommandNodeType.Argument, }; Type type = parameter.Type; if (type == typeof(string)) { parameterNode.Parser = new StringCommandParser(parameter.IsRemainder ? StringType.GreedyPhrase : StringType.QuotablePhrase); } else if (type == typeof(double)) { parameterNode.Parser = new EmptyFlagsCommandParser("brigadier:double"); } else if (type == typeof(float)) { parameterNode.Parser = new EmptyFlagsCommandParser("brigadier:float"); } else if (type == typeof(int)) { parameterNode.Parser = new EmptyFlagsCommandParser("brigadier:integer"); } else if (type == typeof(bool)) { parameterNode.Parser = new CommandParser("brigadier:bool"); } else { continue; } commandNode.Children.Add(parameterNode); } if (commandNode.Children.Count > 0) { commandNode.Children[0].Type |= CommandNodeType.IsExecutabe; } else { commandNode.Type |= CommandNodeType.IsExecutabe; } node.Children.Add(commandNode); packet.AddNode(node); } await PacketHandler.CreateAsync(packet, this.MinecraftStream); this.Logger.LogDebug("Sent Declare Commands packet."); }
internal async Task SendEntity(EntityPacket packet) { await PacketHandler.CreateAsync(packet, this.MinecraftStream); this.Logger.LogDebug($"Sent entity with id {packet.Id} for player {this.Player.Username}"); }
internal async Task SendSpawnMobAsync(int id, Guid uuid, int type, Transform transform, byte headPitch, Velocity velocity, Entity entity) { await PacketHandler.CreateAsync(new SpawnMob(id, uuid, type, transform, headPitch, velocity, entity), this.MinecraftStream); this.Logger.LogDebug($"Spawned entity with id {id} for player {this.Player.Username}"); }
internal async Task SendPlayerLookPositionAsync(Transform poslook, PositionFlags posflags, int tpid = 0) { await PacketHandler.CreateAsync(new PlayerPositionLook(poslook, posflags, tpid), this.MinecraftStream); }