Skip to content

Commit

Permalink
Fix online mode (#307)
Browse files Browse the repository at this point in the history
* Update packages

* Save skin properties to player class

* Fix Uuid being read wrong

* Some quick additions

Microsoft apparently can't have a working one

* Refactor MinecraftAPI and its depends

* Fix client class

* Add throttler and do some refactoring

- Adds a connection throttler to prevent login spam
- Rename AesStream to EncryptedMinecraftStream
- Delete CipherStream cause we don't need it
- Make sure we don't use finalizer since its not needed and properly use dispose

* Cleanup PlayerInfo (tried to fix capes)

I was hoping this would've fixed capes but it did not
  • Loading branch information
Tides authored Jan 6, 2023
1 parent b76834e commit fed258a
Show file tree
Hide file tree
Showing 27 changed files with 381 additions and 631 deletions.
2 changes: 2 additions & 0 deletions Obsidian.API/_Interfaces/IPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public interface IPlayer : ILiving
public Container EnderInventory { get; }
public BaseContainer? OpenedContainer { get; set; }

public List<SkinProperty> SkinProperties { get; set; }

public string Username { get; }

public Guid Uuid { get; }
Expand Down
8 changes: 8 additions & 0 deletions Obsidian.API/_Types/SkinProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Obsidian.API;
public sealed class SkinProperty
{
public required string Name { get; set; }

public required string Value { get; set; }
public string? Signature { get; set; }
}
2 changes: 1 addition & 1 deletion Obsidian.Tests/Encryption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public async Task TestEncryption(byte[] testData)
random.NextBytes(sharedKey);

await using var memoryStream = new MemoryStream();
await using var stream = new AesStream(memoryStream, sharedKey);
await using var stream = new EncryptedMinecraftStream(memoryStream, sharedKey);

await stream.WriteAsync(testData);
memoryStream.Position = 0;
Expand Down
103 changes: 49 additions & 54 deletions Obsidian/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
using Obsidian.Utilities.Mojang;
using Obsidian.Utilities.Registry;
using Obsidian.WorldData;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
Expand Down Expand Up @@ -84,7 +83,7 @@ public sealed class Client : IDisposable
/// <summary>
/// The mojang user that the client and player is associated with.
/// </summary>
private MojangUser? cachedMojangUser;
private CachedUser? cachedUser;

/// <summary>
/// Which packets are in queue to be sent to the client.
Expand Down Expand Up @@ -268,9 +267,29 @@ public async Task StartConnectionAsync()
switch (id)
{
case 0x00:
{
if (this.Server.Config.CanThrottle)
{
string ip = ((IPEndPoint)socket.RemoteEndPoint!).Address.ToString();

if (Server.throttler.TryGetValue(ip, out var timeLeft))
{
if (DateTimeOffset.UtcNow < timeLeft)
{
this.Logger.LogInformation("{ip} has been throttled for reconnecting too fast.", ip);
await this.DisconnectAsync("Connection Throttled! Please wait before reconnecting.");
this.Disconnect();
}
}
else
{
Server.throttler.TryAdd(ip, DateTimeOffset.UtcNow.AddMilliseconds(this.Server.Config.ConnectionThrottle));
}
}

await HandleLoginStartAsync(data);
break;

}
case 0x01:
await HandleEncryptionResponseAsync(data);
break;
Expand Down Expand Up @@ -309,15 +328,8 @@ public async Task StartConnectionAsync()
await Server.Events.InvokePlayerLeaveAsync(new PlayerLeaveEventArgs(Player, DateTimeOffset.Now));
}

if (socket.Connected)
{
socket.Close();

if (Player is not null)
_ = Server.OnlinePlayers.TryRemove(Player.Uuid, out _);

Disconnected?.Invoke(this);
}
Disconnected?.Invoke(this);
this.Dispose();//Dispose client after
}

private async Task HandleServerStatusRequestAsync()
Expand Down Expand Up @@ -374,35 +386,34 @@ private async Task HandleLoginStartAsync(byte[] data)

if (config.OnlineMode)
{
cachedMojangUser = await MinecraftAPI.GetUserAndSkinAsync(loginStart.Username);
cachedUser = await UserCache.GetUserFromUuidAsync(loginStart.PlayerUuid ?? throw new NullReferenceException(nameof(loginStart.PlayerUuid)));

if (cachedMojangUser is null)
if (cachedUser is null)
{
await DisconnectAsync("Account not found in the Mojang database");
return;
}
else if (config.WhitelistEnabled && !config.Whitelisted.Any(x => x.UUID == cachedMojangUser.Id))
else if (config.WhitelistEnabled && !config.Whitelisted.Any(x => x.Id == cachedUser.Id))
{
await DisconnectAsync("You are not whitelisted on this server\nContact server administrator");
return;
}

Player = new Player(loginStart.PlayerUuid ?? Guid.Parse(this.cachedMojangUser.Id), loginStart.Username, this, world);
Player = new Player(loginStart.PlayerUuid ?? this.cachedUser.Id, loginStart.Username, this, world);

packetCryptography.GenerateKeyPair();

// Attempt to encrypt the connection
_ = packetCryptography.GenerateKeyPair();
var (publicKey, randomToken) = packetCryptography.GeneratePublicKeyAndToken();

var values = packetCryptography.GeneratePublicKeyAndToken();
this.randomToken = randomToken;

SendPacket(new EncryptionRequest
{
PublicKey = values.publicKey,
VerifyToken = randomToken = values.randomToken
PublicKey = publicKey,
VerifyToken = randomToken
});
}
else if (config.WhitelistEnabled && !config.Whitelisted.Any(x => x.Nickname == username))
else if (config.WhitelistEnabled && !config.Whitelisted.Any(x => x.Name == username))
{
await DisconnectAsync("You are not whitelisted on this server\nContact server administrator");
}
Expand Down Expand Up @@ -432,35 +443,25 @@ private async Task HandleEncryptionResponseAsync(byte[] data)

sharedKey = packetCryptography.Decrypt(encryptionResponse.SharedSecret);

if (encryptionResponse.HasVerifyToken)
{
var decryptedToken = packetCryptography.Decrypt(encryptionResponse.VerifyToken);
var decryptedToken = packetCryptography.Decrypt(encryptionResponse.VerifyToken);

if (!decryptedToken.SequenceEqual(randomToken))
{
await DisconnectAsync("Invalid token...");
return;
}
}
else
if (!decryptedToken.SequenceEqual(randomToken))
{
this.messageSigningData = new()
{
Salt = encryptionResponse.Salt,
MessageSignature = encryptionResponse.MessageSignature,
};
await DisconnectAsync("Invalid token...");
return;
}

var serverId = sharedKey.Concat(packetCryptography.PublicKey).MinecraftShaDigest();
if (await MinecraftAPI.HasJoined(Player.Username, serverId) is null)
if (await UserCache.HasJoinedAsync(Player.Username, serverId) is not MojangUser user)
{
Logger.LogWarning("Failed to auth {Username}", Player.Username);
await DisconnectAsync("Unable to authenticate...");
return;
}

this.Player.SkinProperties = user.Properties;
EncryptionEnabled = true;
minecraftStream = new AesStream(networkStream, sharedKey);
minecraftStream = new EncryptedMinecraftStream(networkStream, sharedKey);

// TODO: Fix compression
//await this.SetCompression();
Expand All @@ -484,7 +485,7 @@ private async Task ConnectAsync()

await QueuePacketAsync(new LoginSuccess(Player.Uuid, Player.Username)
{
SkinProperties = cachedMojangUser?.Properties ?? new(),
SkinProperties = this.Player.SkinProperties,
});

Logger.LogDebug("Sent Login success to user {Username} {UUID}", Player.Username, Player.Uuid);
Expand Down Expand Up @@ -650,10 +651,8 @@ internal async Task AddPlayerToListAsync(IPlayer player)
Name = player.Username,
};

if (config.OnlineMode && await MinecraftAPI.GetUserAndSkinAsync(player.Uuid.ToString("N")) is MojangUser userWithSkin)
{
addAction.Properties.AddRange(userWithSkin.Properties);
}
if (config.OnlineMode)
addAction.Properties.AddRange(player.SkinProperties);

var list = new List<InfoAction>()
{
Expand Down Expand Up @@ -683,10 +682,8 @@ internal async Task SendPlayerInfoAsync()
Name = player.Username,
};

if (config.OnlineMode && await MinecraftAPI.GetUserAndSkinAsync(player.Uuid.ToString("N")) is MojangUser userWithSkin)
{
addPlayerInforAction.Properties.AddRange(userWithSkin.Properties);
}
if (config.OnlineMode)
addPlayerInforAction.Properties.AddRange(player.SkinProperties);

var list = new List<InfoAction>
{
Expand Down Expand Up @@ -767,23 +764,21 @@ internal void Disconnect()
{
cancellationSource.Cancel();
Disconnected?.Invoke(this);

this.Dispose();
}

public void Dispose()
{
if (disposed)
return;
disposed = true;

GC.SuppressFinalize(this);
disposed = true;

minecraftStream.Dispose();
socket.Dispose();
cancellationSource?.Dispose();
}

~Client()
{
Dispose();
GC.SuppressFinalize(this);
}
}
2 changes: 2 additions & 0 deletions Obsidian/Entities/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public class Player : Living, IPlayer

public BaseContainer OpenedContainer { get; set; }

public List<SkinProperty> SkinProperties { get; set; } = new();

public Vector? LastDeathLocation { get; set; }

public ItemStack LastClickedItem { get; internal set; }
Expand Down
13 changes: 11 additions & 2 deletions Obsidian/Globals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,24 @@ public static class Globals
new DefaultEnumConverter<EClickAction>(),
new DefaultEnumConverter<CraftingBookCategory>(),
new DefaultEnumConverter<CookingBookCategory>(),
new HexColorConverter()
new HexColorConverter(),
new GuidJsonConverter()
},
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
}

public class SnakeCaseNamingPolicy : JsonNamingPolicy
public sealed class SnakeCaseNamingPolicy : JsonNamingPolicy
{
public static SnakeCaseNamingPolicy Instance { get; } = new SnakeCaseNamingPolicy();

public override string ConvertName(string name) => name.ToSnakeCase();
}

file class GuidJsonConverter : JsonConverter<Guid>
{
public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
Guid.Parse(reader.GetString()!);

public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString("N"));
}
12 changes: 5 additions & 7 deletions Obsidian/Net/Actions/PlayerInfo/AddPlayerInfoAction.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Obsidian.Utilities.Mojang;

namespace Obsidian.Net.Actions.PlayerInfo;
namespace Obsidian.Net.Actions.PlayerInfo;

public class AddPlayerInfoAction : InfoAction
{
Expand All @@ -16,16 +14,16 @@ public async override Task WriteAsync(MinecraftStream stream)

await stream.WriteVarIntAsync(this.Properties.Count);

foreach (var props in this.Properties)
await props.WriteAsync(stream);
foreach (var property in this.Properties)
await stream.WriteSkinPropertyAsync(property);
}

public override void Write(MinecraftStream stream)
{
stream.WriteString(Name, 16);
stream.WriteVarInt(Properties.Count);

foreach (var properties in Properties)
properties.Write(stream);
foreach (var property in Properties)
stream.WriteSkinProperty(property);
}
}
4 changes: 2 additions & 2 deletions Obsidian/Net/Actions/PlayerInfo/InfoAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public abstract class InfoAction
{
public abstract PlayerInfoAction Type { get; }

public virtual Task WriteAsync(MinecraftStream stream) => Task.CompletedTask;
public abstract Task WriteAsync(MinecraftStream stream);

public virtual void Write(MinecraftStream stream) { }
public abstract void Write(MinecraftStream stream);
}
2 changes: 2 additions & 0 deletions Obsidian/Net/Actions/PlayerInfo/UpdateListedInfoAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public override void Write(MinecraftStream stream)
{
stream.WriteBoolean(this.Listed);
}

public override async Task WriteAsync(MinecraftStream stream) => await stream.WriteBooleanAsync(this.Listed);
}
Loading

0 comments on commit fed258a

Please sign in to comment.