diff --git a/Obsidian.API/_Enums/ProtocolVersion.cs b/Obsidian.API/_Enums/ProtocolVersion.cs index c72dabc93..ed02a7b32 100644 --- a/Obsidian.API/_Enums/ProtocolVersion.cs +++ b/Obsidian.API/_Enums/ProtocolVersion.cs @@ -52,6 +52,7 @@ public enum ProtocolVersion [Description("1.20.2")] v1_20_2 = 764, - [Description("1.20.3")] - v1_20_3 = 765 + //1.20.3 same pvn + [Description("1.20.4")] + v1_20_4 = 765 } diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs index 483eb5db0..511b9d80d 100644 --- a/Obsidian/Client.cs +++ b/Obsidian/Client.cs @@ -95,7 +95,7 @@ public sealed class Client : IDisposable /// /// The mojang user that the client and player is associated with. /// - private CachedUser? cachedUser; + private CachedProfile? cachedUser; /// /// Which packets are in queue to be sent to the client. @@ -168,7 +168,7 @@ public sealed class Client : IDisposable /// public string? Brand { get; set; } - public Client(ConnectionContext connectionContext, int playerId, + public Client(ConnectionContext connectionContext, int playerId, ILoggerFactory loggerFactory, IUserCache playerCache, Server server) { @@ -416,25 +416,25 @@ private async Task HandleLoginStartAsync(byte[] data) var username = this.server.Configuration.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; var world = (World)this.server.DefaultWorld; - Logger.LogDebug("Received login request from user {Username}", loginStart.Username); + Logger.LogDebug("Received login request from user {Username}", username); await this.server.DisconnectIfConnectedAsync(username); if (this.server.Configuration.OnlineMode) { - cachedUser = await this.userCache.GetCachedUserFromNameAsync(loginStart.Username ?? throw new NullReferenceException(nameof(loginStart.PlayerUuid))); + cachedUser = await this.userCache.GetCachedUserFromNameAsync(loginStart.Username ?? throw new NullReferenceException(nameof(loginStart.Username))); if (cachedUser is null) { await DisconnectAsync("Account not found in the Mojang database"); return; } - else if (this.server.Configuration.WhitelistEnabled && !this.server.Configuration.Whitelisted.Any(x => x.Id == cachedUser.Id)) + else if (this.server.Configuration.WhitelistEnabled && !this.server.Configuration.Whitelisted.Any(x => x.Id == cachedUser.Uuid)) { await DisconnectAsync("You are not whitelisted on this server\nContact server administrator"); return; } - Player = new Player(this.cachedUser.Id, loginStart.Username, this, world); + Player = new Player(this.cachedUser.Uuid, loginStart.Username, this, world); packetCryptography.GenerateKeyPair(); var (publicKey, randomToken) = packetCryptography.GeneratePublicKeyAndToken(); @@ -487,7 +487,7 @@ private async Task HandleEncryptionResponseAsync(byte[] data) } var serverId = sharedKey.Concat(packetCryptography.PublicKey).MinecraftShaDigest(); - if (await this.userCache.HasJoinedAsync(Player.Username, serverId) is not MojangUser user) + if (await this.userCache.HasJoinedAsync(Player.Username, serverId) is not MojangProfile user) { Logger.LogWarning("Failed to auth {Username}", Player.Username); await DisconnectAsync("Unable to authenticate..."); @@ -556,6 +556,17 @@ await QueuePacketAsync(new UpdateRecipeBookPacket await SendPlayerListDecoration(); await SendPlayerInfoAsync(); await this.QueuePacketAsync(new GameEventPacket(ChangeGameStateReason.StartWaitingForLevelChunks)); + + Player.TeleportId = Globals.Random.Next(0, 999); + await QueuePacketAsync(new SynchronizePlayerPositionPacket + { + Position = Player.Position, + Yaw = 0, + Pitch = 0, + Flags = PositionFlags.None, + TeleportId = Player.TeleportId + }); + await Player.UpdateChunksAsync(distance: 7); await SendInfoAsync(); await this.server.Events.PlayerJoin.InvokeAsync(new PlayerJoinEventArgs(Player, this.server, DateTimeOffset.Now)); @@ -566,17 +577,8 @@ internal async Task SendInfoAsync() { if (Player is null) throw new UnreachableException("Player is null, which means the client has not yet logged in."); - - Player.TeleportId = Globals.Random.Next(0, 999); + await QueuePacketAsync(new SetDefaultSpawnPositionPacket(Player.world.LevelData.SpawnPosition)); - await QueuePacketAsync(new SynchronizePlayerPositionPacket - { - Position = Player.Position, - Yaw = 0, - Pitch = 0, - Flags = PositionFlags.None, - TeleportId = Player.TeleportId - }); await SendTimeUpdateAsync(); await SendWeatherUpdateAsync(); diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 7ff9f2008..271cc7801 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -50,7 +50,7 @@ public static string VERSION } } #endif - public const ProtocolVersion DefaultProtocol = ProtocolVersion.v1_20_3; + public const ProtocolVersion DefaultProtocol = ProtocolVersion.v1_20_4; public const string PersistentDataPath = "persistentdata"; public const string PermissionPath = "permissions"; diff --git a/Obsidian/Services/IUserCache.cs b/Obsidian/Services/IUserCache.cs index 700285c45..9a450b0bc 100644 --- a/Obsidian/Services/IUserCache.cs +++ b/Obsidian/Services/IUserCache.cs @@ -1,4 +1,5 @@ -using Obsidian.Entities; +using Microsoft.Extensions.Logging; +using Obsidian.Entities; using Obsidian.Utilities.Mojang; using System.Diagnostics; using System.IO; @@ -10,94 +11,79 @@ using System.Web; namespace Obsidian.Services; -public sealed class UserCache(HttpClient httpClient) : IUserCache +public sealed class UserCache(HttpClient httpClient, ILogger logger) : IUserCache { private const string userWithNameEndpoint = "https://api.mojang.com/users/profiles/minecraft/"; private const string userWithIdEndpoint = "https://sessionserver.mojang.com/session/minecraft/profile/"; private const string verifySessionEndpoint = "https://sessionserver.mojang.com/session/minecraft/hasJoined"; private readonly FileInfo cacheFile = new("usercache.json"); + private readonly ILogger logger = logger; - private ConcurrentDictionary cachedUsers; + private List cachedUsers = new(); - public ConcurrentDictionary OnlinePlayers { get; } = new (); - - public async Task GetCachedUserFromNameAsync(string username) + public async Task GetCachedUserFromNameAsync(string username) { var escapedUsername = Sanitize(username); - CachedUser? cachedUser; - if (cachedUsers.Any(x => x.Value.Name == username)) - { - cachedUser = cachedUsers.First(x => x.Value.Name == username).Value; - - if (!cachedUser.Expired) - return cachedUser; - } + var cachedUser = this.cachedUsers.FirstOrDefault(x => x.Name == username); + if (cachedUser != null && !cachedUser.Expired) + return cachedUser; - var user = await httpClient.GetFromJsonAsync($"{userWithNameEndpoint}{escapedUsername}", Globals.JsonOptions); + var user = await httpClient.GetFromJsonAsync($"{userWithNameEndpoint}{escapedUsername}", Globals.JsonOptions); if (user is null) return null; - if (cachedUsers.TryGetValue(user.Id, out cachedUser)) - { - if (cachedUser.Expired) - { - cachedUser.ExpiresOn = DateTimeOffset.UtcNow.AddMonths(1); - cachedUser.Name = user.Name; - } - - return cachedUser; - } - cachedUser = new() { - Id = user.Id, + Uuid = user.Id, Name = user.Name, ExpiresOn = DateTimeOffset.UtcNow.AddMonths(1) }; - cachedUsers.TryAdd(cachedUser.Id, cachedUser); + cachedUsers.Add(cachedUser); return cachedUser; } - public async Task GetCachedUserFromUuidAsync(Guid uuid) + public async Task GetCachedUserFromUuidAsync(Guid uuid) { - if (cachedUsers.TryGetValue(uuid, out var user) && !user.Expired) - return user; + var cachedUser = this.cachedUsers.FirstOrDefault(x => x.Uuid == uuid); + if (cachedUser != null && !cachedUser.Expired) + return cachedUser; var escapedUuid = Sanitize(uuid.ToString("N")); - var mojangUser = await httpClient.GetFromJsonAsync($"{userWithIdEndpoint}{escapedUuid}", Globals.JsonOptions) ?? throw new UnreachableException(); - user = new() + var mojangProfile = await httpClient.GetFromJsonAsync($"{userWithIdEndpoint}{escapedUuid}", Globals.JsonOptions) ?? throw new UnreachableException(); + + cachedUser = new() { - Name = mojangUser!.Name, - Id = uuid, + Name = mojangProfile!.Name, + Uuid = uuid, ExpiresOn = DateTimeOffset.UtcNow.AddMonths(1) }; - cachedUsers.TryAdd(uuid, user); + cachedUsers.Add(cachedUser); - return user; + return cachedUser; } - public async Task HasJoinedAsync(string username, string serverId) + public async Task HasJoinedAsync(string username, string serverId) { var escapedUsername = Sanitize(username); var escapedServerId = Sanitize(serverId); - return await httpClient.GetFromJsonAsync($"{verifySessionEndpoint}?username={escapedUsername}&serverId={escapedServerId}", Globals.JsonOptions); + return await httpClient.GetFromJsonAsync($"{verifySessionEndpoint}?username={escapedUsername}&serverId={escapedServerId}", Globals.JsonOptions); } public async Task SaveAsync(CancellationToken cancellationToken = default) { await using var sw = cacheFile.Open(FileMode.Truncate, FileAccess.Write); - await JsonSerializer.SerializeAsync(sw, cachedUsers, Globals.JsonOptions); + await JsonSerializer.SerializeAsync(sw, cachedUsers, Globals.JsonOptions, cancellationToken); - await sw.FlushAsync(); + await sw.FlushAsync(cancellationToken); } public async Task LoadAsync(CancellationToken cancellationToken = default) @@ -107,8 +93,7 @@ public async Task LoadAsync(CancellationToken cancellationToken = default) if (sr.Length == 0) return; - var userCache = await JsonSerializer.DeserializeAsync>(sr, Globals.JsonOptions, cancellationToken); - + var userCache = await JsonSerializer.DeserializeAsync>(sr, Globals.JsonOptions, cancellationToken); if (userCache is null) return; @@ -120,13 +105,11 @@ public async Task LoadAsync(CancellationToken cancellationToken = default) public interface IUserCache { - public ConcurrentDictionary OnlinePlayers { get; } - - public Task GetCachedUserFromNameAsync(string username); + public Task GetCachedUserFromNameAsync(string username); - public Task GetCachedUserFromUuidAsync(Guid uuid); + public Task GetCachedUserFromUuidAsync(Guid uuid); - public Task HasJoinedAsync(string username, string serverId); + public Task HasJoinedAsync(string username, string serverId); public Task SaveAsync(CancellationToken cancellationToken = default); diff --git a/Obsidian/Utilities/Mojang/MojangUser.cs b/Obsidian/Utilities/Mojang/MojangProfile.cs similarity index 81% rename from Obsidian/Utilities/Mojang/MojangUser.cs rename to Obsidian/Utilities/Mojang/MojangProfile.cs index bd6e00c16..6932fa6d0 100644 --- a/Obsidian/Utilities/Mojang/MojangUser.cs +++ b/Obsidian/Utilities/Mojang/MojangProfile.cs @@ -2,7 +2,7 @@ namespace Obsidian.Utilities.Mojang; -public sealed class MojangUser +public sealed class MojangProfile { public required Guid Id { get; init; } @@ -15,11 +15,11 @@ public sealed class MojangUser public List? Properties { get; init; } } -public sealed class CachedUser +public sealed class CachedProfile { public required string Name { get; set; } - public required Guid Id { get; init; } + public required Guid Uuid { get; init; } public DateTimeOffset ExpiresOn { get; set; }