[v0.0.1] very early state server

only basic messages, wip.
This commit is contained in:
BreadDEV 2024-03-04 20:19:32 +07:00
parent 8375980df2
commit ad23f95319
48 changed files with 1982 additions and 0 deletions

View 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();
}
}

View 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";
}
}

View file

@ -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;
}
}

View 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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -0,0 +1,7 @@
namespace Supercell.GUT.Logic.Message.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public class VersionedMessageAttribute(int messageType) : Attribute
{
public int MessageType { get; } = messageType;
}

View 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();
}
}

View 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>

View 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.
}
}

View 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();
}
}

View 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);
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}

View file

@ -0,0 +1,5 @@
namespace Supercell.GUT.Server.Network;
internal interface IGatewayEventListener
{
void OnConnect(IProtocolEntity entity);
}

View 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);
}

View file

@ -0,0 +1,6 @@
namespace Supercell.GUT.Server.Network;
internal interface IServerGateway
{
void Start();
Task ShutdownAsync();
}

View 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);
}

View 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;
}
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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"
});
}
}

View 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;
}
}

View 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);
}
}
}

View 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;
}
}

View 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>

View file

@ -0,0 +1,6 @@
{
"Gateway": {
"Host": "0.0.0.0",
"Port": 9339
}
}

View file

@ -0,0 +1,4 @@
[*.cs]
# IDE0290: Use primary constructor
csharp_style_prefer_primary_constructors = false

View 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;
}
}

View 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();
}

View file

@ -0,0 +1,8 @@
namespace Supercell.GUT.Titan.Debugging;
public class LogicException : Exception
{
public LogicException(string message) : base(message)
{
// LogicException.
}
}

View 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;
}

View 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()
{
;
}
}

View 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])]);
}
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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);
}

View 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;
}
}

View 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);
}
}

View file

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View 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
View 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