Skip to content

Commit

Permalink
Minor fixes (#411)
Browse files Browse the repository at this point in the history
* Update protocol name

* Sync player position before sending chunks

* Cache users the way regular servers do + other things

* Oops
  • Loading branch information
Tides authored Dec 11, 2023
1 parent 694c50d commit d910d51
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 71 deletions.
5 changes: 3 additions & 2 deletions Obsidian.API/_Enums/ProtocolVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
36 changes: 19 additions & 17 deletions Obsidian/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public sealed class Client : IDisposable
/// <summary>
/// The mojang user that the client and player is associated with.
/// </summary>
private CachedUser? cachedUser;
private CachedProfile? cachedUser;

/// <summary>
/// Which packets are in queue to be sent to the client.
Expand Down Expand Up @@ -168,7 +168,7 @@ public sealed class Client : IDisposable
/// </summary>
public string? Brand { get; set; }

public Client(ConnectionContext connectionContext, int playerId,
public Client(ConnectionContext connectionContext, int playerId,
ILoggerFactory loggerFactory, IUserCache playerCache,
Server server)
{
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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...");
Expand Down Expand Up @@ -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));
Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion Obsidian/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
79 changes: 31 additions & 48 deletions Obsidian/Services/IUserCache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Obsidian.Entities;
using Microsoft.Extensions.Logging;
using Obsidian.Entities;
using Obsidian.Utilities.Mojang;
using System.Diagnostics;
using System.IO;
Expand All @@ -10,94 +11,79 @@
using System.Web;

namespace Obsidian.Services;
public sealed class UserCache(HttpClient httpClient) : IUserCache
public sealed class UserCache(HttpClient httpClient, ILogger<UserCache> 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<UserCache> logger = logger;

private ConcurrentDictionary<Guid, CachedUser> cachedUsers;
private List<CachedProfile> cachedUsers = new();

public ConcurrentDictionary<Guid, Player> OnlinePlayers { get; } = new ();

public async Task<CachedUser?> GetCachedUserFromNameAsync(string username)
public async Task<CachedProfile?> 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<MojangUser>($"{userWithNameEndpoint}{escapedUsername}", Globals.JsonOptions);
var user = await httpClient.GetFromJsonAsync<MojangProfile>($"{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<CachedUser> GetCachedUserFromUuidAsync(Guid uuid)
public async Task<CachedProfile> 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<MojangUser>($"{userWithIdEndpoint}{escapedUuid}", Globals.JsonOptions) ?? throw new UnreachableException();
user = new()
var mojangProfile = await httpClient.GetFromJsonAsync<MojangProfile>($"{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<MojangUser?> HasJoinedAsync(string username, string serverId)
public async Task<MojangProfile?> HasJoinedAsync(string username, string serverId)
{
var escapedUsername = Sanitize(username);
var escapedServerId = Sanitize(serverId);

return await httpClient.GetFromJsonAsync<MojangUser>($"{verifySessionEndpoint}?username={escapedUsername}&serverId={escapedServerId}", Globals.JsonOptions);
return await httpClient.GetFromJsonAsync<MojangProfile>($"{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)
Expand All @@ -107,8 +93,7 @@ public async Task LoadAsync(CancellationToken cancellationToken = default)
if (sr.Length == 0)
return;

var userCache = await JsonSerializer.DeserializeAsync<Dictionary<Guid, CachedUser>>(sr, Globals.JsonOptions, cancellationToken);

var userCache = await JsonSerializer.DeserializeAsync<List<CachedProfile>>(sr, Globals.JsonOptions, cancellationToken);
if (userCache is null)
return;

Expand All @@ -120,13 +105,11 @@ public async Task LoadAsync(CancellationToken cancellationToken = default)

public interface IUserCache
{
public ConcurrentDictionary<Guid, Player> OnlinePlayers { get; }

public Task<CachedUser?> GetCachedUserFromNameAsync(string username);
public Task<CachedProfile?> GetCachedUserFromNameAsync(string username);

public Task<CachedUser> GetCachedUserFromUuidAsync(Guid uuid);
public Task<CachedProfile> GetCachedUserFromUuidAsync(Guid uuid);

public Task<MojangUser?> HasJoinedAsync(string username, string serverId);
public Task<MojangProfile?> HasJoinedAsync(string username, string serverId);

public Task SaveAsync(CancellationToken cancellationToken = default);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Obsidian.Utilities.Mojang;

public sealed class MojangUser
public sealed class MojangProfile
{
public required Guid Id { get; init; }

Expand All @@ -15,11 +15,11 @@ public sealed class MojangUser
public List<SkinProperty>? 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; }

Expand Down

0 comments on commit d910d51

Please sign in to comment.