mirror of
https://github.com/thebreaddev/Supercell.GUT.git
synced 2024-11-10 07:44:37 +00:00
[v0.0.1] very early state server
only basic messages, wip.
This commit is contained in:
parent
8375980df2
commit
ad23f95319
48 changed files with 1982 additions and 0 deletions
43
Supercell.GUT.Logic/Base/LogicBase.cs
Normal file
43
Supercell.GUT.Logic/Base/LogicBase.cs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
using Supercell.GUT.Titan.Encoding;
|
||||||
|
using Supercell.GUT.Titan.Encoding.Streamed;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Logic.Base;
|
||||||
|
|
||||||
|
public abstract class LogicBase
|
||||||
|
{
|
||||||
|
public int LogicDataVersion { get; set; }
|
||||||
|
|
||||||
|
public LogicBase(int logicDataVersion)
|
||||||
|
{
|
||||||
|
this.LogicDataVersion = logicDataVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Encode(ChecksumEncoder checksumEncoder)
|
||||||
|
{
|
||||||
|
checksumEncoder.WriteInt(this.LogicDataVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Decode(ByteStream byteStream)
|
||||||
|
{
|
||||||
|
this.LogicDataVersion = byteStream.ReadInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Destruct()
|
||||||
|
{
|
||||||
|
this.LogicDataVersion = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Copy(LogicBase logicBase)
|
||||||
|
{
|
||||||
|
this.Destruct();
|
||||||
|
|
||||||
|
ByteStream byteStream = new ByteStream(0);
|
||||||
|
|
||||||
|
logicBase.Encode(byteStream);
|
||||||
|
byteStream.ResetOffset();
|
||||||
|
|
||||||
|
this.Decode(byteStream);
|
||||||
|
|
||||||
|
byteStream.Destruct();
|
||||||
|
}
|
||||||
|
}
|
14
Supercell.GUT.Logic/LogicVersion.cs
Normal file
14
Supercell.GUT.Logic/LogicVersion.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace Supercell.GUT.Logic;
|
||||||
|
|
||||||
|
public static class LogicVersion
|
||||||
|
{
|
||||||
|
public static int GetVersionNumber(int major, int build, int minor)
|
||||||
|
{
|
||||||
|
return minor | (major << 20) | (build << 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetKey()
|
||||||
|
{
|
||||||
|
return "9o23ljkmsdfsdippwe0qr2ke1jejhjhjdfb121fpWE802lss";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Supercell.GUT.Logic.Message.Attributes;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Logic.Message.Account;
|
||||||
|
|
||||||
|
[VersionedMessage(20102)]
|
||||||
|
public class CreateAccountFailedMessage : VersionedMessage
|
||||||
|
{
|
||||||
|
public int ErrorCode { get; set; }
|
||||||
|
|
||||||
|
public CreateAccountFailedMessage() : base(0)
|
||||||
|
{
|
||||||
|
this.ErrorCode = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Encode()
|
||||||
|
{
|
||||||
|
base.Encode();
|
||||||
|
|
||||||
|
this.ByteStream.WriteInt(this.ErrorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Decode()
|
||||||
|
{
|
||||||
|
base.Decode();
|
||||||
|
|
||||||
|
this.ErrorCode = this.ByteStream.ReadInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetMessageType()
|
||||||
|
{
|
||||||
|
return 20102;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetServiceNodeType()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
63
Supercell.GUT.Logic/Message/Account/CreateAccountMessage.cs
Normal file
63
Supercell.GUT.Logic/Message/Account/CreateAccountMessage.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using Supercell.GUT.Logic.Message.Attributes;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Logic.Message.Account;
|
||||||
|
|
||||||
|
[VersionedMessage(10103)]
|
||||||
|
public class CreateAccountMessage : VersionedMessage
|
||||||
|
{
|
||||||
|
public string? FacebookId { get; set; }
|
||||||
|
public string? GameCenterId { get; set; }
|
||||||
|
|
||||||
|
public CreateAccountMessage() : base(0)
|
||||||
|
{
|
||||||
|
this.FacebookId = null;
|
||||||
|
this.GameCenterId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateAccountMessage(int messageVersion) : base(messageVersion)
|
||||||
|
{
|
||||||
|
this.FacebookId = null;
|
||||||
|
this.GameCenterId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Destruct()
|
||||||
|
{
|
||||||
|
base.Destruct();
|
||||||
|
|
||||||
|
this.FacebookId = null;
|
||||||
|
this.GameCenterId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetMessageType()
|
||||||
|
{
|
||||||
|
return 10103;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetServiceNodeType()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Encode()
|
||||||
|
{
|
||||||
|
base.Encode();
|
||||||
|
|
||||||
|
this.ByteStream.WriteString(null);
|
||||||
|
this.ByteStream.WriteString(this.FacebookId);
|
||||||
|
this.ByteStream.WriteString(this.GameCenterId);
|
||||||
|
this.ByteStream.WriteString(null);
|
||||||
|
this.ByteStream.WriteString(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Decode()
|
||||||
|
{
|
||||||
|
base.Decode();
|
||||||
|
|
||||||
|
this.ByteStream.ReadString();
|
||||||
|
this.FacebookId = this.ByteStream.ReadString();
|
||||||
|
this.GameCenterId = this.ByteStream.ReadString();
|
||||||
|
this.ByteStream.ReadString();
|
||||||
|
this.ByteStream.ReadString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
using Supercell.GUT.Logic.Message.Attributes;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Logic.Message.Account;
|
||||||
|
|
||||||
|
[VersionedMessage(20101)]
|
||||||
|
public class CreateAccountOkMessage : VersionedMessage
|
||||||
|
{
|
||||||
|
public int AccountIdHigherInt { get; set; }
|
||||||
|
public int AccountIdLowerInt { get; set; }
|
||||||
|
public string? SessionKey { get; set; }
|
||||||
|
|
||||||
|
public CreateAccountOkMessage() : base(0)
|
||||||
|
{
|
||||||
|
this.AccountIdHigherInt = 0;
|
||||||
|
this.AccountIdLowerInt = 0;
|
||||||
|
this.SessionKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Encode()
|
||||||
|
{
|
||||||
|
base.Encode();
|
||||||
|
|
||||||
|
this.ByteStream.WriteInt(this.AccountIdHigherInt);
|
||||||
|
this.ByteStream.WriteInt(this.AccountIdLowerInt);
|
||||||
|
this.ByteStream.WriteString(this.SessionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Decode()
|
||||||
|
{
|
||||||
|
base.Decode();
|
||||||
|
|
||||||
|
this.AccountIdHigherInt = this.ByteStream.ReadInt();
|
||||||
|
this.AccountIdLowerInt = this.ByteStream.ReadInt();
|
||||||
|
this.SessionKey = this.ByteStream.ReadString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetMessageType()
|
||||||
|
{
|
||||||
|
return 20101;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetServiceNodeType()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Destruct()
|
||||||
|
{
|
||||||
|
base.Destruct();
|
||||||
|
|
||||||
|
this.AccountIdHigherInt = 0;
|
||||||
|
this.AccountIdLowerInt = 0;
|
||||||
|
this.SessionKey = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
using Supercell.GUT.Logic.Message.Attributes;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
using Supercell.GUT.Titan.Util;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Logic.Message.Account;
|
||||||
|
|
||||||
|
[VersionedMessage(20112)]
|
||||||
|
public class SecureConnectionOkMessage : VersionedMessage
|
||||||
|
{
|
||||||
|
public string Nonce { get; set; }
|
||||||
|
|
||||||
|
public SecureConnectionOkMessage() : base(0)
|
||||||
|
{
|
||||||
|
this.Nonce = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Destruct()
|
||||||
|
{
|
||||||
|
base.Destruct();
|
||||||
|
|
||||||
|
this.Nonce = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetMessageType()
|
||||||
|
{
|
||||||
|
return 20112;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetServiceNodeType()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Encode()
|
||||||
|
{
|
||||||
|
base.Encode();
|
||||||
|
|
||||||
|
this.ByteStream.WriteString(this.Nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Decode()
|
||||||
|
{
|
||||||
|
string nonce = this.Nonce;
|
||||||
|
|
||||||
|
base.Decode();
|
||||||
|
|
||||||
|
string? value = this.ByteStream.ReadString();
|
||||||
|
|
||||||
|
this.Nonce = LogicStringUtil.SafeString(nonce, value, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
using Supercell.GUT.Logic.Message.Attributes;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Logic.Message.Account;
|
||||||
|
|
||||||
|
[VersionedMessage(10105)]
|
||||||
|
public class StartSecureConnectionMessage : VersionedMessage
|
||||||
|
{
|
||||||
|
public StartSecureConnectionMessage() : base(0)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Destruct()
|
||||||
|
{
|
||||||
|
base.Destruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetMessageType()
|
||||||
|
{
|
||||||
|
return 10105;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetServiceNodeType()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Encode()
|
||||||
|
{
|
||||||
|
base.Encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Decode()
|
||||||
|
{
|
||||||
|
base.Decode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Supercell.GUT.Logic.Message.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class VersionedMessageAttribute(int messageType) : Attribute
|
||||||
|
{
|
||||||
|
public int MessageType { get; } = messageType;
|
||||||
|
}
|
40
Supercell.GUT.Logic/Message/GUTMessageFactory.cs
Normal file
40
Supercell.GUT.Logic/Message/GUTMessageFactory.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using Supercell.GUT.Logic.Message.Attributes;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Logic.Message;
|
||||||
|
|
||||||
|
public class GUTMessageFactory : LogicMessageFactory
|
||||||
|
{
|
||||||
|
private readonly ImmutableDictionary<int, Type> s_types;
|
||||||
|
|
||||||
|
public GUTMessageFactory() : base()
|
||||||
|
{
|
||||||
|
this.s_types = CreateMessageMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PiranhaMessage? CreateMessageByType(int messageType)
|
||||||
|
{
|
||||||
|
return this.s_types.TryGetValue(messageType, out Type? type) ?
|
||||||
|
Activator.CreateInstance(type) as PiranhaMessage : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableDictionary<int, Type> CreateMessageMap()
|
||||||
|
{
|
||||||
|
var builder = ImmutableDictionary.CreateBuilder<int, Type>();
|
||||||
|
|
||||||
|
IEnumerable<Type> types = Assembly.GetExecutingAssembly().GetTypes()
|
||||||
|
.Where(t => t.GetCustomAttribute<VersionedMessageAttribute>() != null);
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
VersionedMessageAttribute attribute = type.GetCustomAttribute<VersionedMessageAttribute>()!;
|
||||||
|
|
||||||
|
if (!builder.TryAdd(attribute.MessageType, type))
|
||||||
|
throw new Exception($"Piranha message with type {attribute.MessageType} defined twice!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToImmutable();
|
||||||
|
}
|
||||||
|
}
|
13
Supercell.GUT.Logic/Supercell.GUT.Logic.csproj
Normal file
13
Supercell.GUT.Logic/Supercell.GUT.Logic.csproj
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Supercell.GUT.Titan\Supercell.GUT.Titan.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
40
Supercell.GUT.Server/Debugging/ServerDebuggerListener.cs
Normal file
40
Supercell.GUT.Server/Debugging/ServerDebuggerListener.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Supercell.GUT.Titan.Debugging;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Debugging;
|
||||||
|
internal class ServerDebuggerListener : IDebuggerListener
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly ILogger _hudPrintLogger;
|
||||||
|
|
||||||
|
public ServerDebuggerListener(ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_logger = loggerFactory.CreateLogger("Logic");
|
||||||
|
_hudPrintLogger = loggerFactory.CreateLogger("HudPrint");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnError(string log)
|
||||||
|
{
|
||||||
|
_logger.LogError("{debuggerMessage}", log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHudPrint(string log)
|
||||||
|
{
|
||||||
|
_hudPrintLogger.LogInformation("{debuggerMessage}", log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPrint(string log)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("{debuggerMessage}", log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnWarning(string log)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("{debuggerMessage}", log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Detach()
|
||||||
|
{
|
||||||
|
// Detach.
|
||||||
|
}
|
||||||
|
}
|
28
Supercell.GUT.Server/GUTServer.cs
Normal file
28
Supercell.GUT.Server/GUTServer.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Supercell.GUT.Server.Network;
|
||||||
|
using Supercell.GUT.Titan.Debugging;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server;
|
||||||
|
internal class GUTServer : IHostedService
|
||||||
|
{
|
||||||
|
private readonly IServerGateway _gateway;
|
||||||
|
|
||||||
|
public GUTServer(IServerGateway serverGateway, IDebuggerListener debuggerListener)
|
||||||
|
{
|
||||||
|
_gateway = serverGateway;
|
||||||
|
|
||||||
|
Debugger.SetListener(debuggerListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_gateway.Start();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _gateway.ShutdownAsync();
|
||||||
|
}
|
||||||
|
}
|
96
Supercell.GUT.Server/Network/Connection/ClientConnection.cs
Normal file
96
Supercell.GUT.Server/Network/Connection/ClientConnection.cs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
using Supercell.GUT.Logic;
|
||||||
|
using Supercell.GUT.Server.Protocol;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Network.Connection;
|
||||||
|
internal class ClientConnection
|
||||||
|
{
|
||||||
|
private const int ReceiveBufferSize = 16384;
|
||||||
|
private const int ReceiveTimeoutMs = 30000;
|
||||||
|
|
||||||
|
private readonly IConnectionListener _listener;
|
||||||
|
private readonly MessageManager _messageManager;
|
||||||
|
|
||||||
|
private readonly byte[] _receiveBuffer;
|
||||||
|
private IProtocolEntity? _protocolEntity;
|
||||||
|
private DateTime _lastKeepAliveTime;
|
||||||
|
|
||||||
|
public bool IsAlive => (DateTime.Now - _lastKeepAliveTime).TotalSeconds < 30.0f;
|
||||||
|
|
||||||
|
public ClientConnection(IConnectionListener listener, MessageManager messageManager)
|
||||||
|
{
|
||||||
|
_listener = listener;
|
||||||
|
_listener.OnSend = SendAsync;
|
||||||
|
_listener.RecvCallback = OnMessage;
|
||||||
|
|
||||||
|
_messageManager = messageManager;
|
||||||
|
|
||||||
|
_receiveBuffer = GC.AllocateUninitializedArray<byte>(ReceiveBufferSize);
|
||||||
|
_lastKeepAliveTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunAsync()
|
||||||
|
{
|
||||||
|
int receiveBufferIndex = 0;
|
||||||
|
Memory<byte> receiveBufferMem = _receiveBuffer.AsMemory();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int readAmount = await ReceiveAsync(receiveBufferMem[receiveBufferIndex..], ReceiveTimeoutMs);
|
||||||
|
if (readAmount == 0) break;
|
||||||
|
|
||||||
|
receiveBufferIndex += readAmount;
|
||||||
|
int consumedBytes = await _listener.OnReceive(receiveBufferMem, receiveBufferIndex);
|
||||||
|
|
||||||
|
if (consumedBytes > 0)
|
||||||
|
{
|
||||||
|
Buffer.BlockCopy(_receiveBuffer, consumedBytes, _receiveBuffer, 0, receiveBufferIndex -= consumedBytes);
|
||||||
|
}
|
||||||
|
else if (consumedBytes < 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendMessage(PiranhaMessage message)
|
||||||
|
{
|
||||||
|
await _listener.Send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshKeepAliveTime()
|
||||||
|
{
|
||||||
|
_lastKeepAliveTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetProtocolEntity(IProtocolEntity protocolEntity)
|
||||||
|
{
|
||||||
|
_protocolEntity = protocolEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IProtocolEntity ProtocolEntity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _protocolEntity ?? throw new InvalidOperationException("Trying to access _protocolEntity when it's NULL!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnMessage(PiranhaMessage message)
|
||||||
|
{
|
||||||
|
await _messageManager.ReceiveMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<int> ReceiveAsync(Memory<byte> buffer, int timeoutMs)
|
||||||
|
{
|
||||||
|
CancellationTokenSource cts = new(TimeSpan.FromMilliseconds(timeoutMs));
|
||||||
|
return await ProtocolEntity.ReceiveAsync(buffer, cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask SendAsync(Memory<byte> buffer)
|
||||||
|
{
|
||||||
|
await ProtocolEntity.SendAsync(buffer, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNonce(string nonce)
|
||||||
|
{
|
||||||
|
_listener.InitEncryption(LogicVersion.GetKey(), nonce);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Network.Connection;
|
||||||
|
internal class ClientConnectionManager : IGatewayEventListener
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
|
public ClientConnectionManager(ILogger<ClientConnectionManager> logger, IServiceScopeFactory scopeFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnConnect(IProtocolEntity entity)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("New connection from {endPoint}", entity.RemoteEndPoint);
|
||||||
|
_ = RunSessionAsync(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunSessionAsync(IProtocolEntity entity)
|
||||||
|
{
|
||||||
|
using IServiceScope scope = _scopeFactory.CreateScope();
|
||||||
|
|
||||||
|
ClientConnection session = scope.ServiceProvider.GetRequiredService<ClientConnection>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
session.SetProtocolEntity(entity);
|
||||||
|
await session.RunAsync();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { /* Operation was canceled. */ }
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unhandled exception occurred while processing session, trace:\n{exception}", exception);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
entity.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Network.Connection;
|
||||||
|
internal interface IConnectionListener
|
||||||
|
{
|
||||||
|
public delegate ValueTask SendCallback(Memory<byte> buffer);
|
||||||
|
public delegate Task ReceiveCallback(PiranhaMessage message);
|
||||||
|
|
||||||
|
SendCallback OnSend { set; }
|
||||||
|
ReceiveCallback RecvCallback { set; }
|
||||||
|
|
||||||
|
ValueTask<int> OnReceive(Memory<byte> buffer, int size);
|
||||||
|
|
||||||
|
Task Send(PiranhaMessage message);
|
||||||
|
|
||||||
|
void InitEncryption(string key, string nonce);
|
||||||
|
}
|
5
Supercell.GUT.Server/Network/IGatewayEventListener.cs
Normal file
5
Supercell.GUT.Server/Network/IGatewayEventListener.cs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
namespace Supercell.GUT.Server.Network;
|
||||||
|
internal interface IGatewayEventListener
|
||||||
|
{
|
||||||
|
void OnConnect(IProtocolEntity entity);
|
||||||
|
}
|
10
Supercell.GUT.Server/Network/IProtocolEntity.cs
Normal file
10
Supercell.GUT.Server/Network/IProtocolEntity.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Network;
|
||||||
|
public interface IProtocolEntity : IDisposable
|
||||||
|
{
|
||||||
|
EndPoint RemoteEndPoint { get; }
|
||||||
|
|
||||||
|
ValueTask<int> ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken);
|
||||||
|
ValueTask<int> SendAsync(Memory<byte> buffer, CancellationToken cancellationToken);
|
||||||
|
}
|
6
Supercell.GUT.Server/Network/IServerGateway.cs
Normal file
6
Supercell.GUT.Server/Network/IServerGateway.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Supercell.GUT.Server.Network;
|
||||||
|
internal interface IServerGateway
|
||||||
|
{
|
||||||
|
void Start();
|
||||||
|
Task ShutdownAsync();
|
||||||
|
}
|
10
Supercell.GUT.Server/Network/Options/GatewayOptions.cs
Normal file
10
Supercell.GUT.Server/Network/Options/GatewayOptions.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Network.Options;
|
||||||
|
internal class GatewayOptions
|
||||||
|
{
|
||||||
|
public required string Host { get; set; }
|
||||||
|
public required int Port { get; set; }
|
||||||
|
|
||||||
|
public IPEndPoint ListenEndPoint => new(IPAddress.Parse(Host), Port);
|
||||||
|
}
|
17
Supercell.GUT.Server/Network/Tcp/SocketExtensions.cs
Normal file
17
Supercell.GUT.Server/Network/Tcp/SocketExtensions.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Network.Tcp;
|
||||||
|
internal static class SocketExtensions
|
||||||
|
{
|
||||||
|
public static async ValueTask<Socket?> AcceptSocketAsync(this Socket socket, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await socket.AcceptAsync(ct);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
Supercell.GUT.Server/Network/Tcp/TcpGateway.cs
Normal file
60
Supercell.GUT.Server/Network/Tcp/TcpGateway.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Supercell.GUT.Server.Network.Options;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Network.Tcp;
|
||||||
|
internal class TcpGateway : IServerGateway
|
||||||
|
{
|
||||||
|
private const int TcpBacklog = 100;
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IOptions<GatewayOptions> _options;
|
||||||
|
private readonly IGatewayEventListener _listener;
|
||||||
|
private readonly Socket _socket;
|
||||||
|
|
||||||
|
private CancellationTokenSource? _listenCancellation;
|
||||||
|
private Task? _listenTask;
|
||||||
|
|
||||||
|
public TcpGateway(IOptions<GatewayOptions> options, ILogger<TcpGateway> logger, IGatewayEventListener listener)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_options = options;
|
||||||
|
_listener = listener;
|
||||||
|
_socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
_socket.Bind(_options.Value.ListenEndPoint);
|
||||||
|
_socket.Listen(TcpBacklog);
|
||||||
|
|
||||||
|
_listenCancellation = new();
|
||||||
|
_listenTask = RunAsync(_listenCancellation.Token);
|
||||||
|
|
||||||
|
_logger.LogInformation("Gateway is listening at {ipEndPoint}", _options.Value.ListenEndPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Socket? socket = await _socket.AcceptSocketAsync(cancellationToken);
|
||||||
|
if (socket == null) break;
|
||||||
|
|
||||||
|
_listener.OnConnect(new TcpSocketEntity(socket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ShutdownAsync()
|
||||||
|
{
|
||||||
|
if (_listenCancellation != null)
|
||||||
|
{
|
||||||
|
await _listenCancellation.CancelAsync();
|
||||||
|
await _listenTask!;
|
||||||
|
}
|
||||||
|
|
||||||
|
_socket.Close();
|
||||||
|
}
|
||||||
|
}
|
30
Supercell.GUT.Server/Network/Tcp/TcpSocketEntity.cs
Normal file
30
Supercell.GUT.Server/Network/Tcp/TcpSocketEntity.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Network.Tcp;
|
||||||
|
internal class TcpSocketEntity : IProtocolEntity
|
||||||
|
{
|
||||||
|
private readonly Socket _socket;
|
||||||
|
|
||||||
|
public TcpSocketEntity(Socket socket)
|
||||||
|
{
|
||||||
|
_socket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint!;
|
||||||
|
|
||||||
|
public ValueTask<int> ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _socket.ReceiveAsync(buffer, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<int> SendAsync(Memory<byte> buffer, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _socket.SendAsync(buffer, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_socket.Close();
|
||||||
|
}
|
||||||
|
}
|
42
Supercell.GUT.Server/Program.cs
Normal file
42
Supercell.GUT.Server/Program.cs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Supercell.GUT.Logic.Message;
|
||||||
|
using Supercell.GUT.Server.Debugging;
|
||||||
|
using Supercell.GUT.Server.Network;
|
||||||
|
using Supercell.GUT.Server.Network.Connection;
|
||||||
|
using Supercell.GUT.Server.Network.Options;
|
||||||
|
using Supercell.GUT.Server.Network.Tcp;
|
||||||
|
using Supercell.GUT.Server.Protocol;
|
||||||
|
using Supercell.GUT.Server.Protocol.Extensions;
|
||||||
|
using Supercell.GUT.Titan.Debugging;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server;
|
||||||
|
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
private const string GatewayOptionsSection = "Gateway";
|
||||||
|
|
||||||
|
private static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.Title = "Battle Buddies Server Emulator";
|
||||||
|
|
||||||
|
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.Configure<GatewayOptions>(builder.Configuration.GetRequiredSection(GatewayOptionsSection));
|
||||||
|
|
||||||
|
builder.Services.AddHandlers();
|
||||||
|
builder.Services.AddSingleton<IDebuggerListener, ServerDebuggerListener>();
|
||||||
|
builder.Services.AddSingleton<IServerGateway, TcpGateway>();
|
||||||
|
builder.Services.AddSingleton<IGatewayEventListener, ClientConnectionManager>();
|
||||||
|
builder.Services.AddScoped<ClientConnection>();
|
||||||
|
builder.Services.AddScoped<IConnectionListener, Messaging>();
|
||||||
|
builder.Services.AddSingleton<LogicMessageFactory, GUTMessageFactory>();
|
||||||
|
builder.Services.AddScoped<MessageManager>();
|
||||||
|
|
||||||
|
builder.Services.AddHostedService<GUTServer>();
|
||||||
|
|
||||||
|
await builder.Build().RunAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Supercell.GUT.Server.Protocol.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
internal class MessageHandlerAttribute : Attribute
|
||||||
|
{
|
||||||
|
public int MessageType { get; }
|
||||||
|
|
||||||
|
public MessageHandlerAttribute(int messageType)
|
||||||
|
{
|
||||||
|
MessageType = messageType;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Supercell.GUT.Server.Protocol.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
internal class ServiceNodeAttribute : Attribute
|
||||||
|
{
|
||||||
|
public int ServiceNodeType { get; }
|
||||||
|
|
||||||
|
public ServiceNodeAttribute(int serviceNodeType)
|
||||||
|
{
|
||||||
|
ServiceNodeType = serviceNodeType;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Supercell.GUT.Server.Protocol.Attributes;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Protocol.Extensions;
|
||||||
|
internal static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddHandlers(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
IEnumerable<Type> types = Assembly.GetExecutingAssembly().GetTypes()
|
||||||
|
.Where(t => t.GetCustomAttribute<ServiceNodeAttribute>() != null);
|
||||||
|
|
||||||
|
foreach (Type type in types)
|
||||||
|
{
|
||||||
|
services.AddScoped(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Supercell.GUT.Logic.Message.Account;
|
||||||
|
using Supercell.GUT.Server.Network.Connection;
|
||||||
|
using Supercell.GUT.Server.Protocol.Attributes;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Protocol.Handlers;
|
||||||
|
|
||||||
|
[ServiceNode(1)]
|
||||||
|
internal class AccountMessageHandler : MessageHandlerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly ClientConnection _connection;
|
||||||
|
|
||||||
|
public AccountMessageHandler(ClientConnection connection, ILogger<AccountMessageHandler> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MessageHandler(10105)]
|
||||||
|
public async Task OnStartSecureConnection(StartSecureConnectionMessage startSecureConnectionMessage)
|
||||||
|
{
|
||||||
|
string nonce = "nonce";
|
||||||
|
|
||||||
|
await _connection.SendMessage(new SecureConnectionOkMessage()
|
||||||
|
{
|
||||||
|
Nonce = nonce
|
||||||
|
});
|
||||||
|
|
||||||
|
_connection.SetNonce(nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MessageHandler(10103)]
|
||||||
|
public async Task OnCreateAccount(CreateAccountMessage createAccountMessage)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Creating account! FacebookId: {fid} | GameCenterId: {gcid}",
|
||||||
|
createAccountMessage.FacebookId,
|
||||||
|
createAccountMessage.GameCenterId);
|
||||||
|
|
||||||
|
await _connection.SendMessage(new CreateAccountOkMessage()
|
||||||
|
{
|
||||||
|
AccountIdHigherInt = 0,
|
||||||
|
AccountIdLowerInt = 1,
|
||||||
|
SessionKey = "telegram_is_@BL4D3_BR43D"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
36
Supercell.GUT.Server/Protocol/Handlers/MessageHandlerBase.cs
Normal file
36
Supercell.GUT.Server/Protocol/Handlers/MessageHandlerBase.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Reflection;
|
||||||
|
using Supercell.GUT.Server.Protocol.Attributes;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Protocol.Handlers;
|
||||||
|
internal abstract class MessageHandlerBase
|
||||||
|
{
|
||||||
|
private readonly ImmutableDictionary<int, MethodInfo> _handlerMethods;
|
||||||
|
|
||||||
|
public MessageHandlerBase()
|
||||||
|
{
|
||||||
|
var builder = ImmutableDictionary.CreateBuilder<int, MethodInfo>();
|
||||||
|
|
||||||
|
foreach (var method in GetType().GetMethods())
|
||||||
|
{
|
||||||
|
MessageHandlerAttribute? attribute = method.GetCustomAttribute<MessageHandlerAttribute>();
|
||||||
|
if (attribute == null) continue;
|
||||||
|
|
||||||
|
builder.Add(attribute.MessageType, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
_handlerMethods = builder.ToImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HandleMessage(PiranhaMessage message)
|
||||||
|
{
|
||||||
|
if (_handlerMethods.TryGetValue(message.GetMessageType(), out var method))
|
||||||
|
{
|
||||||
|
await (Task)method.Invoke(this, new object[] { message })!;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
57
Supercell.GUT.Server/Protocol/MessageManager.cs
Normal file
57
Supercell.GUT.Server/Protocol/MessageManager.cs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Supercell.GUT.Server.Protocol.Attributes;
|
||||||
|
using Supercell.GUT.Server.Protocol.Handlers;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Protocol;
|
||||||
|
internal class MessageManager
|
||||||
|
{
|
||||||
|
private static readonly ImmutableDictionary<int, Type> s_handlerServiceTypes;
|
||||||
|
|
||||||
|
static MessageManager()
|
||||||
|
{
|
||||||
|
var builder = ImmutableDictionary.CreateBuilder<int, Type>();
|
||||||
|
|
||||||
|
IEnumerable<Type> types = Assembly.GetExecutingAssembly().GetTypes()
|
||||||
|
.Where(t => t.GetCustomAttribute<ServiceNodeAttribute>() != null);
|
||||||
|
|
||||||
|
foreach (Type type in types)
|
||||||
|
{
|
||||||
|
int serviceNodeType = type.GetCustomAttribute<ServiceNodeAttribute>()!.ServiceNodeType;
|
||||||
|
builder.Add(serviceNodeType, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_handlerServiceTypes = builder.ToImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public MessageManager(IServiceProvider serviceProvider, ILogger<MessageManager> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReceiveMessage(PiranhaMessage message)
|
||||||
|
{
|
||||||
|
int serviceNodeType = message.GetServiceNodeType();
|
||||||
|
|
||||||
|
if (s_handlerServiceTypes.TryGetValue(serviceNodeType, out Type? handlerType))
|
||||||
|
{
|
||||||
|
MessageHandlerBase handler = (_serviceProvider.GetRequiredService(handlerType) as MessageHandlerBase)!;
|
||||||
|
|
||||||
|
if (!await handler.HandleMessage(message))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Handler for message {type} not implemented in {svcName}", message.GetMessageType(), handler.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Handler for service node type {svcType} is not defined!", serviceNodeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
Supercell.GUT.Server/Protocol/Messaging.cs
Normal file
124
Supercell.GUT.Server/Protocol/Messaging.cs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Supercell.GUT.Server.Network.Connection;
|
||||||
|
using Supercell.GUT.Titan.Encryption;
|
||||||
|
using Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Server.Protocol;
|
||||||
|
internal class Messaging : IConnectionListener
|
||||||
|
{
|
||||||
|
private const int HeaderSize = 7;
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly LogicMessageFactory _factory;
|
||||||
|
|
||||||
|
private IConnectionListener.SendCallback? _sendCallback;
|
||||||
|
private IConnectionListener.ReceiveCallback? _receiveCallback;
|
||||||
|
|
||||||
|
private RC4Encrypter? _encrypter;
|
||||||
|
private RC4Encrypter? _decrypter;
|
||||||
|
|
||||||
|
public Messaging(LogicMessageFactory factory, ILogger<Messaging> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitEncryption(string key, string nonce)
|
||||||
|
{
|
||||||
|
_decrypter = new RC4Encrypter(key, nonce);
|
||||||
|
_encrypter = new RC4Encrypter(key, nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<int> OnReceive(Memory<byte> buffer, int size)
|
||||||
|
{
|
||||||
|
int consumedBytes = 0;
|
||||||
|
|
||||||
|
while (size >= HeaderSize)
|
||||||
|
{
|
||||||
|
ReadHeader(buffer.Span, out int messageType, out int length, out int messageVersion);
|
||||||
|
if (size < HeaderSize + length) break;
|
||||||
|
|
||||||
|
size -= length + HeaderSize;
|
||||||
|
consumedBytes += length + HeaderSize;
|
||||||
|
|
||||||
|
byte[] encryptedBytes = buffer.Slice(HeaderSize, length).ToArray();
|
||||||
|
buffer = buffer[consumedBytes..];
|
||||||
|
|
||||||
|
byte[] encodingBytes;
|
||||||
|
|
||||||
|
_decrypter?.Encrypt(encryptedBytes);
|
||||||
|
|
||||||
|
encodingBytes = encryptedBytes;
|
||||||
|
|
||||||
|
int encodingLength = length;
|
||||||
|
|
||||||
|
PiranhaMessage? message = _factory.CreateMessageByType(messageType);
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Ignoring message of unknown type {messageType}", messageType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.MessageVersion = (short)messageVersion;
|
||||||
|
message.ByteStream.SetByteArray(encodingBytes, encodingLength);
|
||||||
|
message.Decode();
|
||||||
|
|
||||||
|
await _receiveCallback!(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Send(PiranhaMessage message)
|
||||||
|
{
|
||||||
|
if (message.ByteStream.Offset == 0) message.Encode();
|
||||||
|
|
||||||
|
byte[] encodingBytes = message.ByteStream.Buffer.Take(message.ByteStream.Offset).ToArray();
|
||||||
|
|
||||||
|
_encrypter?.Encrypt(encodingBytes);
|
||||||
|
|
||||||
|
byte[] fullPayload = new byte[encodingBytes.Length + HeaderSize];
|
||||||
|
|
||||||
|
WriteHeader(fullPayload, message, encodingBytes.Length);
|
||||||
|
encodingBytes.CopyTo(fullPayload, HeaderSize);
|
||||||
|
|
||||||
|
await _sendCallback!(fullPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConnectionListener.SendCallback OnSend
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_sendCallback = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConnectionListener.ReceiveCallback RecvCallback
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_receiveCallback = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReadHeader(ReadOnlySpan<byte> buffer, out int messageType, out int encodingLength, out int messageVersion)
|
||||||
|
{
|
||||||
|
messageType = buffer[0] << 8 | buffer[1];
|
||||||
|
encodingLength = buffer[2] << 16 | buffer[3] << 8 | buffer[4];
|
||||||
|
messageVersion = buffer[5] << 8 | buffer[6];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteHeader(Span<byte> buffer, PiranhaMessage message, int length)
|
||||||
|
{
|
||||||
|
int messageType = message.GetMessageType();
|
||||||
|
int messageVersion = message.MessageVersion;
|
||||||
|
|
||||||
|
buffer[0] = (byte)(messageType >> 8);
|
||||||
|
buffer[1] = (byte)messageType;
|
||||||
|
buffer[2] = (byte)(length >> 16);
|
||||||
|
buffer[3] = (byte)(length >> 8);
|
||||||
|
buffer[4] = (byte)length;
|
||||||
|
buffer[5] = (byte)(messageVersion >> 8);
|
||||||
|
buffer[6] = (byte)messageVersion;
|
||||||
|
}
|
||||||
|
}
|
25
Supercell.GUT.Server/Supercell.GUT.Server.csproj
Normal file
25
Supercell.GUT.Server/Supercell.GUT.Server.csproj
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Supercell.GUT.Titan\Supercell.GUT.Titan.csproj" />
|
||||||
|
<ProjectReference Include="..\Supercell.GUT.Logic\Supercell.GUT.Logic.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
6
Supercell.GUT.Server/appsettings.json
Normal file
6
Supercell.GUT.Server/appsettings.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"Gateway": {
|
||||||
|
"Host": "0.0.0.0",
|
||||||
|
"Port": 9339
|
||||||
|
}
|
||||||
|
}
|
4
Supercell.GUT.Titan/.editorconfig
Normal file
4
Supercell.GUT.Titan/.editorconfig
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# IDE0290: Use primary constructor
|
||||||
|
csharp_style_prefer_primary_constructors = false
|
40
Supercell.GUT.Titan/Debugging/Debugger.cs
Normal file
40
Supercell.GUT.Titan/Debugging/Debugger.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
namespace Supercell.GUT.Titan.Debugging;
|
||||||
|
public static class Debugger
|
||||||
|
{
|
||||||
|
private static IDebuggerListener? _listener;
|
||||||
|
|
||||||
|
public static void Print(string log)
|
||||||
|
{
|
||||||
|
_listener?.OnPrint(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warning(string log)
|
||||||
|
{
|
||||||
|
_listener?.OnWarning(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(string log)
|
||||||
|
{
|
||||||
|
_listener?.OnError(log);
|
||||||
|
throw new LogicException(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HudPrint(string log)
|
||||||
|
{
|
||||||
|
_listener?.OnHudPrint(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DoAssert(bool condition, string message)
|
||||||
|
{
|
||||||
|
if (!condition)
|
||||||
|
Error(message);
|
||||||
|
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetListener(IDebuggerListener listener)
|
||||||
|
{
|
||||||
|
_listener?.Detach();
|
||||||
|
_listener = listener;
|
||||||
|
}
|
||||||
|
}
|
9
Supercell.GUT.Titan/Debugging/IDebuggerListener.cs
Normal file
9
Supercell.GUT.Titan/Debugging/IDebuggerListener.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Supercell.GUT.Titan.Debugging;
|
||||||
|
public interface IDebuggerListener
|
||||||
|
{
|
||||||
|
void OnPrint(string log);
|
||||||
|
void OnWarning(string log);
|
||||||
|
void OnError(string log);
|
||||||
|
void OnHudPrint(string log);
|
||||||
|
void Detach();
|
||||||
|
}
|
8
Supercell.GUT.Titan/Debugging/LogicException.cs
Normal file
8
Supercell.GUT.Titan/Debugging/LogicException.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Supercell.GUT.Titan.Debugging;
|
||||||
|
public class LogicException : Exception
|
||||||
|
{
|
||||||
|
public LogicException(string message) : base(message)
|
||||||
|
{
|
||||||
|
// LogicException.
|
||||||
|
}
|
||||||
|
}
|
79
Supercell.GUT.Titan/Encoding/ChecksumEncoder.cs
Normal file
79
Supercell.GUT.Titan/Encoding/ChecksumEncoder.cs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
using Supercell.GUT.Titan.Math;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Titan.Encoding;
|
||||||
|
public class ChecksumEncoder
|
||||||
|
{
|
||||||
|
private int _checksum;
|
||||||
|
private int _snapshotChecksum;
|
||||||
|
|
||||||
|
public ChecksumEncoder()
|
||||||
|
{
|
||||||
|
IsChecksumEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsCheckSumOnlyMode => true;
|
||||||
|
|
||||||
|
public virtual ChecksumEncoder WriteBoolean(bool value) => UpdateCheckSum(value ? 13 : 7);
|
||||||
|
|
||||||
|
public virtual ChecksumEncoder WriteByte(byte value) => UpdateCheckSum(value + 11);
|
||||||
|
|
||||||
|
public virtual ChecksumEncoder WriteShort(short value) => UpdateCheckSum(value + 19);
|
||||||
|
|
||||||
|
public virtual ChecksumEncoder WriteInt(int value) => UpdateCheckSum(value + 9);
|
||||||
|
|
||||||
|
public virtual void WriteLong(LogicLong value) => value.Encode(this);
|
||||||
|
|
||||||
|
public virtual ChecksumEncoder WriteString(string? value) =>
|
||||||
|
UpdateCheckSum(value != null ? value.Length + 28 : 27);
|
||||||
|
|
||||||
|
public virtual ChecksumEncoder WriteBytes(ReadOnlySpan<byte> value) =>
|
||||||
|
UpdateCheckSum(value.Length + 28);
|
||||||
|
|
||||||
|
public bool IsChecksumEnabled { get; private set; }
|
||||||
|
|
||||||
|
public int CheckSum => IsChecksumEnabled ? _checksum : _snapshotChecksum;
|
||||||
|
|
||||||
|
public ChecksumEncoder EnableCheckSum(bool enable)
|
||||||
|
{
|
||||||
|
if (!IsChecksumEnabled && enable)
|
||||||
|
{
|
||||||
|
_checksum = _snapshotChecksum;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_snapshotChecksum = _checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsChecksumEnabled = enable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChecksumEncoder ResetCheckSum()
|
||||||
|
{
|
||||||
|
_checksum = 0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
ChecksumEncoder? otherEncoder = obj as ChecksumEncoder;
|
||||||
|
return otherEncoder?.CheckSum == CheckSum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
// original: Debugger::error("ChecksumEncoder hashCode not designed");
|
||||||
|
return CheckSum;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private ChecksumEncoder UpdateCheckSum(int value)
|
||||||
|
{
|
||||||
|
_checksum = value + Ror4(_checksum, 31);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int Ror4(int v, int c) => v >> c | v << 32 - c;
|
||||||
|
}
|
302
Supercell.GUT.Titan/Encoding/Streamed/ByteStream.cs
Normal file
302
Supercell.GUT.Titan/Encoding/Streamed/ByteStream.cs
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
using Supercell.GUT.Titan.Math;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using TextEncoding = System.Text.Encoding;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Titan.Encoding.Streamed;
|
||||||
|
|
||||||
|
public class ByteStream : ChecksumEncoder
|
||||||
|
{
|
||||||
|
public byte[] Buffer { get; set; }
|
||||||
|
|
||||||
|
private int _bitIndex;
|
||||||
|
private int _length;
|
||||||
|
|
||||||
|
public int Offset { get; set; }
|
||||||
|
|
||||||
|
public int Length => _length > 0 ? _length : Offset;
|
||||||
|
|
||||||
|
public ByteStream(byte[] buffer, int length)
|
||||||
|
{
|
||||||
|
Buffer = buffer;
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteStream(int capacity)
|
||||||
|
{
|
||||||
|
Buffer = new byte[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteStream()
|
||||||
|
{
|
||||||
|
Buffer = new byte[10];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetByteArray(byte[] buffer, int length)
|
||||||
|
{
|
||||||
|
Buffer = buffer;
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ReadBoolean()
|
||||||
|
{
|
||||||
|
if (_bitIndex == 0)
|
||||||
|
++Offset;
|
||||||
|
|
||||||
|
bool value = (Buffer[Offset - 1] & 1 << _bitIndex) != 0;
|
||||||
|
_bitIndex = _bitIndex + 1 & 7;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte ReadByte()
|
||||||
|
{
|
||||||
|
_bitIndex = 0;
|
||||||
|
return Buffer[Offset++];
|
||||||
|
}
|
||||||
|
|
||||||
|
public short ReadShort()
|
||||||
|
{
|
||||||
|
_bitIndex = 0;
|
||||||
|
|
||||||
|
short ret = BinaryPrimitives.ReadInt16BigEndian(Buffer.AsSpan()[Offset..]);
|
||||||
|
Offset += 2;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ReadInt()
|
||||||
|
{
|
||||||
|
_bitIndex = 0;
|
||||||
|
|
||||||
|
int ret = BinaryPrimitives.ReadInt32BigEndian(Buffer.AsSpan()[Offset..]);
|
||||||
|
Offset += 4;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ReadVInt()
|
||||||
|
{
|
||||||
|
_bitIndex = 0;
|
||||||
|
|
||||||
|
int value = 0;
|
||||||
|
byte byteValue = Buffer[Offset++];
|
||||||
|
|
||||||
|
if ((byteValue & 0x40) != 0)
|
||||||
|
{
|
||||||
|
value |= byteValue & 0x3F;
|
||||||
|
|
||||||
|
if ((byteValue & 0x80) != 0)
|
||||||
|
{
|
||||||
|
value |= ((byteValue = Buffer[Offset++]) & 0x7F) << 6;
|
||||||
|
|
||||||
|
if ((byteValue & 0x80) != 0)
|
||||||
|
{
|
||||||
|
value |= ((byteValue = Buffer[Offset++]) & 0x7F) << 13;
|
||||||
|
|
||||||
|
if ((byteValue & 0x80) != 0)
|
||||||
|
{
|
||||||
|
value |= ((byteValue = Buffer[Offset++]) & 0x7F) << 20;
|
||||||
|
|
||||||
|
if ((byteValue & 0x80) != 0)
|
||||||
|
{
|
||||||
|
value |= ((_ = Buffer[Offset++]) & 0x7F) << 27;
|
||||||
|
return (int)(value | 0x80000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(value | 0xF8000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(value | 0xFFF00000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(value | 0xFFFFE000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(value | 0xFFFFFFC0);
|
||||||
|
}
|
||||||
|
|
||||||
|
value |= byteValue & 0x3F;
|
||||||
|
|
||||||
|
if ((byteValue & 0x80) != 0)
|
||||||
|
{
|
||||||
|
value |= ((byteValue = Buffer[Offset++]) & 0x7F) << 6;
|
||||||
|
|
||||||
|
if ((byteValue & 0x80) != 0)
|
||||||
|
{
|
||||||
|
value |= ((byteValue = Buffer[Offset++]) & 0x7F) << 13;
|
||||||
|
|
||||||
|
if ((byteValue & 0x80) != 0)
|
||||||
|
{
|
||||||
|
value |= ((byteValue = Buffer[Offset++]) & 0x7F) << 20;
|
||||||
|
|
||||||
|
if ((byteValue & 0x80) != 0)
|
||||||
|
{
|
||||||
|
value |= ((_ = Buffer[Offset++]) & 0x7F) << 27;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogicLong ReadLong()
|
||||||
|
{
|
||||||
|
LogicLong ll = new();
|
||||||
|
ll.Decode(this);
|
||||||
|
|
||||||
|
return ll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? ReadString()
|
||||||
|
{
|
||||||
|
int length = ReadInt();
|
||||||
|
if (length is < 0 or > 900000)
|
||||||
|
return null;
|
||||||
|
else if (length == 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
string ret = TextEncoding.UTF8.GetString(Buffer, Offset, length);
|
||||||
|
Offset += length;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadStringReference()
|
||||||
|
{
|
||||||
|
int length = ReadInt();
|
||||||
|
if (length is <= 0 or > 900000) return string.Empty;
|
||||||
|
|
||||||
|
string ret = TextEncoding.UTF8.GetString(Buffer, Offset, length);
|
||||||
|
Offset += length;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ReadBytes(int length)
|
||||||
|
{
|
||||||
|
_bitIndex = 0;
|
||||||
|
|
||||||
|
byte[] ret = Buffer.Skip(Offset).Take(length).ToArray();
|
||||||
|
Offset += length;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ChecksumEncoder WriteBoolean(bool value)
|
||||||
|
{
|
||||||
|
base.WriteBoolean(value);
|
||||||
|
|
||||||
|
if (_bitIndex == 0)
|
||||||
|
{
|
||||||
|
EnsureCapacity(1);
|
||||||
|
Buffer[Offset++] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer[Offset - 1] |= (byte)(value ? 1 : 0);
|
||||||
|
_bitIndex = _bitIndex + 1 & 7;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ChecksumEncoder WriteByte(byte value)
|
||||||
|
{
|
||||||
|
base.WriteByte(value);
|
||||||
|
EnsureCapacity(1);
|
||||||
|
|
||||||
|
Buffer[Offset++] = value;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ChecksumEncoder WriteShort(short value)
|
||||||
|
{
|
||||||
|
base.WriteShort(value);
|
||||||
|
EnsureCapacity(2);
|
||||||
|
|
||||||
|
BinaryPrimitives.WriteInt16BigEndian(Buffer.AsSpan()[Offset..], value);
|
||||||
|
Offset += 2;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ChecksumEncoder WriteInt(int value)
|
||||||
|
{
|
||||||
|
base.WriteInt(value);
|
||||||
|
EnsureCapacity(4);
|
||||||
|
|
||||||
|
WriteIntToByteArray(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ChecksumEncoder WriteString(string? value)
|
||||||
|
{
|
||||||
|
base.WriteString(value);
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
WriteIntToByteArray(-1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = TextEncoding.UTF8.GetByteCount(value);
|
||||||
|
WriteIntToByteArray(size);
|
||||||
|
|
||||||
|
EnsureCapacity(size);
|
||||||
|
TextEncoding.UTF8.GetBytes(value, Buffer.AsSpan()[Offset..]);
|
||||||
|
|
||||||
|
Offset += size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ChecksumEncoder WriteBytes(ReadOnlySpan<byte> value)
|
||||||
|
{
|
||||||
|
base.WriteBytes(value);
|
||||||
|
|
||||||
|
WriteIntToByteArray(value.Length);
|
||||||
|
|
||||||
|
EnsureCapacity(value.Length);
|
||||||
|
value.CopyTo(Buffer.AsSpan()[Offset..]);
|
||||||
|
Offset += value.Length;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsCheckSumOnlyMode => false;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void WriteIntToByteArray(int value)
|
||||||
|
{
|
||||||
|
EnsureCapacity(4);
|
||||||
|
|
||||||
|
BinaryPrimitives.WriteInt32BigEndian(Buffer.AsSpan()[Offset..], value);
|
||||||
|
Offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnsureCapacity(int capacity)
|
||||||
|
{
|
||||||
|
_bitIndex = 0;
|
||||||
|
int bufferLength = Buffer.Length;
|
||||||
|
|
||||||
|
if (Offset + capacity > bufferLength)
|
||||||
|
{
|
||||||
|
byte[] tmpBuffer = new byte[Buffer.Length + capacity + 100];
|
||||||
|
System.Buffer.BlockCopy(Buffer, 0, tmpBuffer, 0, bufferLength);
|
||||||
|
Buffer = tmpBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAtEnd()
|
||||||
|
{
|
||||||
|
return Offset >= Buffer.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetOffset()
|
||||||
|
{
|
||||||
|
this.Offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destruct()
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
54
Supercell.GUT.Titan/Encryption/RC4Encrypter.cs
Normal file
54
Supercell.GUT.Titan/Encryption/RC4Encrypter.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
namespace Supercell.GUT.Titan.Encryption;
|
||||||
|
|
||||||
|
public class RC4Encrypter
|
||||||
|
{
|
||||||
|
private byte[]? m_key;
|
||||||
|
private byte m_x;
|
||||||
|
private byte m_y;
|
||||||
|
|
||||||
|
public RC4Encrypter(string baseKey, string nonce)
|
||||||
|
{
|
||||||
|
this.InitState(baseKey, nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitState(string baseKey, string nonce)
|
||||||
|
{
|
||||||
|
string key = baseKey + nonce;
|
||||||
|
|
||||||
|
this.m_key = new byte[256];
|
||||||
|
this.m_x = 0;
|
||||||
|
this.m_y = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
this.m_key[i] = (byte)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, j = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
j = (byte)(j + this.m_key[i] + key[i % key.Length]);
|
||||||
|
|
||||||
|
(this.m_key[j], this.m_key[i]) = (this.m_key[i], this.m_key[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < key.Length; i++)
|
||||||
|
{
|
||||||
|
this.m_x += 1;
|
||||||
|
this.m_y += this.m_key[this.m_x];
|
||||||
|
|
||||||
|
(this.m_key[this.m_x], this.m_key[this.m_y]) = (this.m_key[this.m_y], this.m_key[this.m_x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Encrypt(byte[] input)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < input.Length; i++)
|
||||||
|
{
|
||||||
|
this.m_x += 1;
|
||||||
|
this.m_y += this.m_key![this.m_x];
|
||||||
|
|
||||||
|
(this.m_key[this.m_x], this.m_key[this.m_y]) = (this.m_key[this.m_y], this.m_key[this.m_x]);
|
||||||
|
input[i] = (byte)(input[i] ^ this.m_key[(byte)(this.m_key[this.m_x] + this.m_key[this.m_y])]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
Supercell.GUT.Titan/Math/LogicLong.cs
Normal file
40
Supercell.GUT.Titan/Math/LogicLong.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using Supercell.GUT.Titan.Encoding;
|
||||||
|
using Supercell.GUT.Titan.Encoding.Streamed;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Titan.Math;
|
||||||
|
|
||||||
|
public class LogicLong
|
||||||
|
{
|
||||||
|
public int HigherInt { get; set; }
|
||||||
|
public int LowerInt { get; set; }
|
||||||
|
|
||||||
|
public LogicLong(long longValue)
|
||||||
|
{
|
||||||
|
this.HigherInt = (int)(longValue >> 32);
|
||||||
|
this.LowerInt = (int)longValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogicLong()
|
||||||
|
{
|
||||||
|
this.HigherInt = 0;
|
||||||
|
this.LowerInt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogicLong(int higherInt, int lowerInt)
|
||||||
|
{
|
||||||
|
this.HigherInt = higherInt;
|
||||||
|
this.LowerInt = lowerInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Encode(ChecksumEncoder checksumEncoder)
|
||||||
|
{
|
||||||
|
checksumEncoder.WriteInt(this.HigherInt);
|
||||||
|
checksumEncoder.WriteInt(this.LowerInt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Decode(ByteStream byteStream)
|
||||||
|
{
|
||||||
|
this.HigherInt = byteStream.ReadInt();
|
||||||
|
this.LowerInt = byteStream.ReadInt();
|
||||||
|
}
|
||||||
|
}
|
151
Supercell.GUT.Titan/Math/LogicMath.cs
Normal file
151
Supercell.GUT.Titan/Math/LogicMath.cs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
namespace Supercell.GUT.Titan.Math;
|
||||||
|
|
||||||
|
public static class LogicMath
|
||||||
|
{
|
||||||
|
public static readonly int[] DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30];
|
||||||
|
|
||||||
|
public static readonly int[] SQRT_TABLE = [
|
||||||
|
0x00, 0x10, 0x16, 0x1B, 0x20, 0x23, 0x27, 0x2A, 0x2D,
|
||||||
|
0x30, 0x32, 0x35, 0x37, 0x39, 0x3B, 0x3D, 0x40, 0x41,
|
||||||
|
0x43, 0x45, 0x47, 0x49, 0x4B, 0x4C, 0x4E, 0x50, 0x51,
|
||||||
|
0x53, 0x54, 0x56, 0x57, 0x59, 0x5A, 0x5B, 0x5D, 0x5E,
|
||||||
|
0x60, 0x61, 0x62, 0x63, 0x65, 0x66, 0x67, 0x68, 0x6A,
|
||||||
|
0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74,
|
||||||
|
0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D,
|
||||||
|
0x7E, 0x80, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
|
||||||
|
0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||||
|
0x90, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x96,
|
||||||
|
0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9B, 0x9C, 0x9D, 0x9E,
|
||||||
|
0x9F, 0xA0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA3, 0xA4, 0xA5,
|
||||||
|
0xA6, 0xA7, 0xA7, 0xA8, 0xA9, 0xAA, 0xAA, 0xAB, 0xAC,
|
||||||
|
0xAD, 0xAD, 0xAE, 0xAF, 0xB0, 0xB0, 0xB1, 0xB2, 0xB2,
|
||||||
|
0xB3, 0xB4, 0xB5, 0xB5, 0xB6, 0xB7, 0xB7, 0xB8, 0xB9,
|
||||||
|
0xB9, 0xBA, 0xBB, 0xBB, 0xBC, 0xBD, 0xBD, 0xBE, 0xBF,
|
||||||
|
0xC0, 0xC0, 0xC1, 0xC1, 0xC2, 0xC3, 0xC3, 0xC4, 0xC5,
|
||||||
|
0xC5, 0xC6, 0xC7, 0xC7, 0xC8, 0xC9, 0xC9, 0xCA, 0xCB,
|
||||||
|
0xCB, 0xCC, 0xCC, 0xCD, 0xCE, 0xCE, 0xCF, 0xD0, 0xD0,
|
||||||
|
0xD1, 0xD1, 0xD2, 0xD3, 0xD3, 0xD4, 0xD4, 0xD5, 0xD6,
|
||||||
|
0xD6, 0xD7, 0xD7, 0xD8, 0xD9, 0xD9, 0xDA, 0xDA, 0xDB,
|
||||||
|
0xDB, 0xDC, 0xDD, 0xDD, 0xDE, 0xDE, 0xDF, 0xE0, 0xE0,
|
||||||
|
0xE1, 0xE1, 0xE2, 0xE2, 0xE3, 0xE3, 0xE4, 0xE5, 0xE5,
|
||||||
|
0xE6, 0xE6, 0xE7, 0xE7, 0xE8, 0xE8, 0xE9, 0xEA, 0xEA,
|
||||||
|
0xEB, 0xEB, 0xEC, 0xEC, 0xED, 0xED, 0xEE, 0xEE, 0xEF,
|
||||||
|
0xF0, 0xF0, 0xF1, 0xF1, 0xF2, 0xF2, 0xF3, 0xF3, 0xF4,
|
||||||
|
0xF4, 0xF5, 0xF5, 0xF6, 0xF6, 0xF7, 0xF7, 0xF8, 0xF8,
|
||||||
|
0xF9, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFC, 0xFC, 0xFD,
|
||||||
|
0xFD, 0xFE, 0xFE, 0xFF, 0x00, 0x00, 0x00
|
||||||
|
];
|
||||||
|
|
||||||
|
public static int Max(int a1, int a2)
|
||||||
|
{
|
||||||
|
return a1 > a2 ? a1 : a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Min(int a1, int a2)
|
||||||
|
{
|
||||||
|
return a1 < a2 ? a1 : a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetDaysInMonth(int month, bool isLeapYear)
|
||||||
|
{
|
||||||
|
if (month != 1)
|
||||||
|
return LogicMath.DAYS_IN_MONTH[month];
|
||||||
|
|
||||||
|
if (!isLeapYear)
|
||||||
|
return LogicMath.DAYS_IN_MONTH[month];
|
||||||
|
|
||||||
|
return 29;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Sqrt(int value)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
int v4;
|
||||||
|
int v5;
|
||||||
|
int v6;
|
||||||
|
int v7;
|
||||||
|
|
||||||
|
if (value < 0x10000)
|
||||||
|
{
|
||||||
|
if (value < 256)
|
||||||
|
{
|
||||||
|
result = -1;
|
||||||
|
if (value >= 0)
|
||||||
|
return LogicMath.SQRT_TABLE[value] >> 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value < 4096)
|
||||||
|
{
|
||||||
|
if (value < 1024)
|
||||||
|
v7 = LogicMath.SQRT_TABLE[value & 0xFFFFFFFC] >> 3;
|
||||||
|
else
|
||||||
|
v7 = LogicMath.SQRT_TABLE[value >> 4] >> 2;
|
||||||
|
}
|
||||||
|
else if (value < 0x4000)
|
||||||
|
{
|
||||||
|
v7 = LogicMath.SQRT_TABLE[value >> 6] >> 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
v7 = LogicMath.SQRT_TABLE[value >> 8];
|
||||||
|
}
|
||||||
|
|
||||||
|
result = v7 + 1;
|
||||||
|
|
||||||
|
if (result * result > value)
|
||||||
|
return v7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value < 0x1000000)
|
||||||
|
{
|
||||||
|
if (value < 0x100000)
|
||||||
|
{
|
||||||
|
if (value < 0x40000)
|
||||||
|
v6 = 2 * LogicMath.SQRT_TABLE[value >> 0xA];
|
||||||
|
else
|
||||||
|
v6 = 4 * LogicMath.SQRT_TABLE[value >> 0xC];
|
||||||
|
}
|
||||||
|
else if (value < 0x400000)
|
||||||
|
{
|
||||||
|
v6 = 8 * LogicMath.SQRT_TABLE[value >> 0xE];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
v6 = 16 * LogicMath.SQRT_TABLE[value >> 0x10];
|
||||||
|
}
|
||||||
|
|
||||||
|
v5 = value / v6 + (v6 | 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value < 0x10000000)
|
||||||
|
{
|
||||||
|
if (value < 0x4000000)
|
||||||
|
v4 = 32 * LogicMath.SQRT_TABLE[value >> 0x12];
|
||||||
|
else
|
||||||
|
v4 = LogicMath.SQRT_TABLE[value >> 0x14] << 6;
|
||||||
|
}
|
||||||
|
else if (value < 0x40000000)
|
||||||
|
{
|
||||||
|
v4 = LogicMath.SQRT_TABLE[value >> 0x16] << 7;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = 0xFFFF;
|
||||||
|
if (value == 0x7FFFFFFF)
|
||||||
|
return result;
|
||||||
|
v4 = LogicMath.SQRT_TABLE[value >> 0x18] << 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
v5 = value / (((v4 | 1) + value / v4) >> 1) + (((v4 | 1) + value / v4) >> 1) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (v5 >> 1) - ((v5 >> 1) * (v5 >> 1) > value ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
37
Supercell.GUT.Titan/Math/LogicRandom.cs
Normal file
37
Supercell.GUT.Titan/Math/LogicRandom.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
namespace Supercell.GUT.Titan.Math;
|
||||||
|
|
||||||
|
public class LogicRandom
|
||||||
|
{
|
||||||
|
public int IteratedRandomSeed { get; set; }
|
||||||
|
|
||||||
|
public LogicRandom()
|
||||||
|
{
|
||||||
|
this.IteratedRandomSeed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destruct()
|
||||||
|
{
|
||||||
|
this.IteratedRandomSeed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Rand(int max)
|
||||||
|
{
|
||||||
|
if (max >= 1)
|
||||||
|
{
|
||||||
|
int v3 = this.IteratedRandomSeed;
|
||||||
|
if (this.IteratedRandomSeed == 0)
|
||||||
|
v3 = -1;
|
||||||
|
int v4 = v3 ^ (v3 << 13) ^ ((v3 ^ (v3 << 13)) >> 0x11);
|
||||||
|
int v5 = v4 ^ (32 * v4);
|
||||||
|
this.IteratedRandomSeed = v5;
|
||||||
|
int temp;
|
||||||
|
if (v5 > -1)
|
||||||
|
temp = v5;
|
||||||
|
else
|
||||||
|
temp = -v5;
|
||||||
|
return temp % max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
16
Supercell.GUT.Titan/Message/LogicMessageFactory.cs
Normal file
16
Supercell.GUT.Titan/Message/LogicMessageFactory.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
public abstract class LogicMessageFactory
|
||||||
|
{
|
||||||
|
public LogicMessageFactory()
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Destruct()
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract PiranhaMessage? CreateMessageByType(int messageType);
|
||||||
|
}
|
40
Supercell.GUT.Titan/Message/PiranhaMessage.cs
Normal file
40
Supercell.GUT.Titan/Message/PiranhaMessage.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using Supercell.GUT.Titan.Encoding.Streamed;
|
||||||
|
|
||||||
|
namespace Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
public abstract class PiranhaMessage
|
||||||
|
{
|
||||||
|
public ByteStream ByteStream { get; }
|
||||||
|
|
||||||
|
public int MessageVersion { get; set; }
|
||||||
|
|
||||||
|
public PiranhaMessage(int messageVersion)
|
||||||
|
{
|
||||||
|
this.ByteStream = new ByteStream();
|
||||||
|
this.MessageVersion = messageVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Encode()
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Decode()
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int GetServiceNodeType();
|
||||||
|
public abstract int GetMessageType();
|
||||||
|
|
||||||
|
public int GetEncodingLength()
|
||||||
|
{
|
||||||
|
return this.ByteStream.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Destruct()
|
||||||
|
{
|
||||||
|
this.ByteStream.Destruct();
|
||||||
|
this.MessageVersion = 0;
|
||||||
|
}
|
||||||
|
}
|
31
Supercell.GUT.Titan/Message/VersionedMessage.cs
Normal file
31
Supercell.GUT.Titan/Message/VersionedMessage.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
namespace Supercell.GUT.Titan.Message;
|
||||||
|
|
||||||
|
public abstract class VersionedMessage : PiranhaMessage
|
||||||
|
{
|
||||||
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
public VersionedMessage() : base(0)
|
||||||
|
{
|
||||||
|
this.Version = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VersionedMessage(int messageVersion) : base(messageVersion)
|
||||||
|
{
|
||||||
|
this.Version = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Encode()
|
||||||
|
{
|
||||||
|
this.ByteStream.WriteInt(this.Version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Decode()
|
||||||
|
{
|
||||||
|
this.Version = this.ByteStream.ReadInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVersion(int major, int build, int minor)
|
||||||
|
{
|
||||||
|
this.Version = minor | (major << 20) | (build << 12);
|
||||||
|
}
|
||||||
|
}
|
9
Supercell.GUT.Titan/Supercell.GUT.Titan.csproj
Normal file
9
Supercell.GUT.Titan/Supercell.GUT.Titan.csproj
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
18
Supercell.GUT.Titan/Util/LogicStringUtil.cs
Normal file
18
Supercell.GUT.Titan/Util/LogicStringUtil.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace Supercell.GUT.Titan.Util;
|
||||||
|
|
||||||
|
public static class LogicStringUtil
|
||||||
|
{
|
||||||
|
public static string SafeString(string a1, string? a2, string a3)
|
||||||
|
{
|
||||||
|
if (a2 != null)
|
||||||
|
{
|
||||||
|
a1 = a2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a1 = a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a1;
|
||||||
|
}
|
||||||
|
}
|
37
Supercell.GUT.sln
Normal file
37
Supercell.GUT.sln
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.8.34316.72
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Supercell.GUT.Server", "Supercell.GUT.Server\Supercell.GUT.Server.csproj", "{8D194446-D48D-4DF6-9DCE-0DA2E53B2570}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Supercell.GUT.Titan", "Supercell.GUT.Titan\Supercell.GUT.Titan.csproj", "{8CBA9BFB-D58D-48B2-9139-17C15A5142A6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Supercell.GUT.Logic", "Supercell.GUT.Logic\Supercell.GUT.Logic.csproj", "{4441B345-1B99-4DE6-9764-B34E3ED8A982}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{8D194446-D48D-4DF6-9DCE-0DA2E53B2570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8D194446-D48D-4DF6-9DCE-0DA2E53B2570}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8D194446-D48D-4DF6-9DCE-0DA2E53B2570}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8D194446-D48D-4DF6-9DCE-0DA2E53B2570}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8CBA9BFB-D58D-48B2-9139-17C15A5142A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8CBA9BFB-D58D-48B2-9139-17C15A5142A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8CBA9BFB-D58D-48B2-9139-17C15A5142A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8CBA9BFB-D58D-48B2-9139-17C15A5142A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4441B345-1B99-4DE6-9764-B34E3ED8A982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4441B345-1B99-4DE6-9764-B34E3ED8A982}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4441B345-1B99-4DE6-9764-B34E3ED8A982}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4441B345-1B99-4DE6-9764-B34E3ED8A982}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {0ACCBE5B-CC82-4584-943D-22CCC07012D2}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
Loading…
Reference in a new issue