KCP gameserver with packet dispatch, scene streaming, combat, gacha and the shop, plus a small HTTP server for the login flow and the wish history page. Game data under assets is pulled out of the client instead of being typed by hand. The excel tables come from the packed bin container and the spawn points from the compiled scene scripts.
114 lines
3.5 KiB
C#
114 lines
3.5 KiB
C#
using System.Net;
|
|
using Yuanshen.Common.Security;
|
|
using Yuanshen.Gameserver.Controllers.Dispatching;
|
|
using Yuanshen.Gameserver.Controllers.Result;
|
|
using Yuanshen.Protocol;
|
|
using Google.Protobuf;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Yuanshen.Gameserver.Network.Session;
|
|
internal abstract class NetSession : IDisposable
|
|
{
|
|
public IPEndPoint EndPoint => _networkUnit!.RemoteEndPoint;
|
|
|
|
public long SessionId { get; private set; }
|
|
private INetworkUnit? _networkUnit;
|
|
|
|
private readonly ILogger _logger;
|
|
private readonly NetSessionManager _sessionManager;
|
|
private readonly NetCommandDispatcher _commandDispatcher;
|
|
|
|
protected byte[] EncryptionKey { get; private set; }
|
|
|
|
public NetSession(ILogger<NetSession> logger, NetSessionManager sessionManager, NetCommandDispatcher commandDispatcher)
|
|
{
|
|
_logger = logger;
|
|
_sessionManager = sessionManager;
|
|
_commandDispatcher = commandDispatcher;
|
|
|
|
EncryptionKey = MhySecurity.InitialKey;
|
|
}
|
|
|
|
public abstract ValueTask RunAsync();
|
|
public abstract ValueTask SendAsync(NetPacket packet);
|
|
|
|
public void Establish(long sessionId, INetworkUnit networkUnit)
|
|
{
|
|
SessionId = sessionId;
|
|
_networkUnit = networkUnit;
|
|
|
|
_sessionManager.Add(this);
|
|
}
|
|
|
|
public async Task NotifyAsync<TNotify>(CmdType cmdType, TNotify notify) where TNotify : IMessage<TNotify>
|
|
{
|
|
await SendAsync(new()
|
|
{
|
|
CmdType = cmdType,
|
|
Head = Memory<byte>.Empty,
|
|
Body = notify.ToByteArray()
|
|
});
|
|
}
|
|
|
|
protected async ValueTask<int> ConsumePacketsAsync(Memory<byte> buffer)
|
|
{
|
|
if (buffer.Length < 12)
|
|
return 0;
|
|
|
|
int consumed = 0;
|
|
do
|
|
{
|
|
(NetPacket? packet, int bytesConsumed) = NetPacket.DecodeFrom(buffer[consumed..]);
|
|
consumed += bytesConsumed;
|
|
|
|
if (packet == null)
|
|
return consumed;
|
|
|
|
IResult? result = await _commandDispatcher.InvokeHandler(packet);
|
|
if (result != null)
|
|
{
|
|
while (result.NextPacket(out NetPacket? serverPacket))
|
|
{
|
|
await SendAsync(serverPacket);
|
|
|
|
if (serverPacket.CmdType == CmdType.GetPlayerTokenRsp)
|
|
{
|
|
InitializeEncryption(1337);
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (_unhandled.Add(packet.CmdType))
|
|
{
|
|
_logger.LogWarning("No handler for command {cmdType} ({cmdId})", packet.CmdType, (int)packet.CmdType);
|
|
}
|
|
} while (buffer.Length - consumed >= 12);
|
|
|
|
return consumed;
|
|
}
|
|
|
|
private readonly HashSet<CmdType> _unhandled = new();
|
|
|
|
private void InitializeEncryption(ulong seed)
|
|
{
|
|
EncryptionKey = MhySecurity.GenerateSecretKey(seed);
|
|
}
|
|
|
|
protected async ValueTask<int> ReadWithTimeoutAsync(Memory<byte> buffer, int timeoutSeconds)
|
|
{
|
|
using CancellationTokenSource cancellationTokenSource = new(TimeSpan.FromSeconds(timeoutSeconds));
|
|
return await _networkUnit!.ReceiveAsync(buffer, cancellationTokenSource.Token);
|
|
}
|
|
|
|
protected async ValueTask WriteWithTimeoutAsync(Memory<byte> buffer, int timeoutSeconds)
|
|
{
|
|
using CancellationTokenSource cancellationTokenSource = new(TimeSpan.FromSeconds(timeoutSeconds));
|
|
await _networkUnit!.SendAsync(buffer, cancellationTokenSource.Token);
|
|
}
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
_networkUnit?.Dispose();
|
|
_ = _sessionManager.TryRemove(this);
|
|
}
|
|
}
|