Replacing Networking (#271)

- [X] Removing Kestrel & Libuv
- [X] Cleaning up NetState
- [X] Removing System.IO.Pipelines
- [X] Cleaning up packet reading
- [X] Adds a maximum of 5000 sockets (configurable) to prevent OOM
- [X] Replaces the AsyncState with a thread-safe wrapped boolean called NetworkState
- [X] Removes Parallel.ForEach (no perf gain)
- [X] Removes custom houses compression on another thread
- [X] Test high load scenarios

Bumps release version
This commit is contained in:
Kamron Batman 2020-10-20 20:55:19 -07:00 committed by GitHub
parent 8603e31023
commit 369a27b800
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1780 additions and 1569 deletions

View file

@ -2,16 +2,7 @@
"Argon2.Bindings.dll",
"BouncyCastle.Crypto.dll",
"MailKit.dll",
"Microsoft.AspNetCore.Connections.Abstractions.dll",
"Microsoft.AspNetCore.Http.Features.dll",
"Microsoft.Extensions.Configuration.Abstractions.dll",
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
"Microsoft.Extensions.FileProviders.Abstractions.dll",
"Microsoft.Extensions.Hosting.Abstractions.dll",
"Microsoft.Extensions.Logging.Abstractions.dll",
"Microsoft.Extensions.Primitives.dll",
"MimeKit.dll",
"System.IO.Pipelines.dll",
"Zlib.Bindings.dll",
"UOContent.dll"
]

View file

@ -0,0 +1,101 @@
using System;
using System.IO;
using System.Text;
using Server.Network;
using Xunit;
using Xunit.Abstractions;
namespace Server.Tests.Network
{
public class PacketReaderTests
{
private (Encoding, Type) GetEncoding(string value) =>
value.ToUpper() switch
{
"UTF8" => (Utility.UTF8, typeof(byte)),
"UNICODELE" => (Utility.UnicodeLE, typeof(char)),
"UNICODE" => (Utility.Unicode, typeof(char)),
_ => (Encoding.ASCII, typeof(byte))
};
private ITestOutputHelper _outputHelper;
public PacketReaderTests(ITestOutputHelper outputHelper) => _outputHelper = outputHelper;
[Theory]
// First only, beginning
[InlineData("Test String", "ASCII", false, -1, 1024, 1024, 0)]
[InlineData("Test String", "UTF8", false, -1, 1024, 1024, 0)]
[InlineData("Test String", "Unicode", false, -1, 1024, 1024, 0)]
[InlineData("Test String", "UnicodeLE", false, -1, 1024, 1024, 0)]
// Second only
[InlineData("Test String", "ASCII", false, -1, 1024, 1024, 1030)]
[InlineData("Test String", "UTF8", false, -1, 1024, 1024, 1030)]
[InlineData("Test String", "Unicode", false, -1, 1024, 1024, 1030)]
[InlineData("Test String", "UnicodeLE", false, -1, 1024, 1024, 1030)]
public void TestReadString(
string value,
string encodingStr,
bool isSafe,
int fixedLength,
int firstSize,
int secondSize,
int offset
)
{
var (encoding, byteType) = GetEncoding(encodingStr);
var bytes = encoding.GetBytes(value);
Span<byte> expectedBytes = bytes.AsSpan(0, fixedLength > -1 ? Math.Min(fixedLength, bytes.Length) : bytes.Length);
if (offset + expectedBytes.Length > firstSize + secondSize)
{
throw new ArgumentException("Test failed due to bad assumptions. Out of memory exception would be thrown");
}
Span<byte> first = stackalloc byte[firstSize];
first.Clear(); // Just in case
int bytesWrittenFirst = 0;
if (offset < first.Length)
{
bytesWrittenFirst = Math.Min(first.Length, expectedBytes.Length);
expectedBytes.Slice(offset, bytesWrittenFirst).CopyTo(first);
}
Span<byte> second = stackalloc byte[secondSize];
second.Clear(); // Just in case
if (offset > first.Length || expectedBytes.Length > bytesWrittenFirst)
{
var secondOffset = Math.Max(offset - first.Length, 0);
var secondSlice = second.Slice(secondOffset, second.Length - secondOffset);
expectedBytes.Slice(bytesWrittenFirst, expectedBytes.Length - bytesWrittenFirst).CopyTo(secondSlice);
}
// _outputHelper.WriteLine(HexStringConverter.GetString(first));
// _outputHelper.WriteLine(HexStringConverter.GetString(second));
var reader = new PacketReader(first, second);
reader.Seek(offset, SeekOrigin.Begin);
_outputHelper.WriteLine(reader.Position.ToString());
var actual = byteType switch
{
var b when b == typeof(char) => reader.ReadString<char>(encoding, isSafe, fixedLength),
_ => reader.ReadString<byte>(encoding, isSafe, fixedLength)
};
_outputHelper.WriteLine(actual);
if (fixedLength > 0 && fixedLength < value.Length)
{
value = value.Substring(0, fixedLength);
}
Assert.Equal(value, actual);
}
}
}

View file

@ -1,11 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using Server.Accounting;
using Server.Network;
using Xunit;
@ -111,12 +107,7 @@ namespace Server.Tests.Network.Packets
var account = new TestAccount(new[] { firstMobile, null, null, null, null });
var ns = new NetState(
new TestConnectionContext
{
RemoteEndPoint = IPEndPoint.Parse("127.0.0.1")
}
)
var ns = new NetState(null)
{
Account = account,
ProtocolChanges = protocolChanges
@ -653,13 +644,5 @@ namespace Server.Tests.Network.Packets
public int CompareTo(TestAccount other) => other == null ? 1 : Username.CompareTo(other.Username);
}
internal class TestConnectionContext : ConnectionContext
{
public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; }
public override IDictionary<object, object> Items { get; set; }
public override IDuplexPipe Transport { get; set; }
}
}
}

View file

@ -59,12 +59,7 @@ namespace Server.Tests.Network.Packets
[Fact]
public void TestFastGumpPacket()
{
var ns = new NetState(
new AccountPacketTests.TestConnectionContext
{
RemoteEndPoint = IPEndPoint.Parse("127.0.0.1")
}
);
var ns = new NetState(null);
var gump = new ResurrectGump(2);
@ -134,12 +129,7 @@ namespace Server.Tests.Network.Packets
[Fact]
public void TestPackedGumpPacket()
{
var ns = new NetState(
new AccountPacketTests.TestConnectionContext
{
RemoteEndPoint = IPEndPoint.Parse("127.0.0.1")
}
)
var ns = new NetState(null)
{
ProtocolChanges = ProtocolChanges.Unpack
};
@ -194,9 +184,8 @@ namespace Server.Tests.Network.Packets
layoutList.Add(str);
}
var memOwner = SlabMemoryPool.Shared.Rent(bufferLength);
var buffer = memOwner.Memory.Span;
var rawBuffer = ArrayPool<byte>.Shared.Rent(bufferLength);
Span<byte> buffer = rawBuffer;
var bufferPos = 0;
foreach (var layout in layoutList)
@ -211,12 +200,12 @@ namespace Server.Tests.Network.Packets
#endif
expectedData.WritePacked(ref pos, buffer.Slice(0, bufferPos));
memOwner.Dispose();
ArrayPool<byte>.Shared.Return(rawBuffer);
expectedData.Write(ref pos, gump.Strings.Count);
bufferLength = gump.Strings.Sum(str => 2 + str.Length * 2);
memOwner = SlabMemoryPool.Shared.Rent(bufferLength);
buffer = memOwner.Memory.Span;
rawBuffer = ArrayPool<byte>.Shared.Rent(bufferLength);
buffer = rawBuffer;
bufferPos = 0;
foreach (var str in gump.Strings)
@ -225,6 +214,7 @@ namespace Server.Tests.Network.Packets
}
expectedData.WritePacked(ref pos, buffer.Slice(0, bufferPos));
ArrayPool<byte>.Shared.Return(rawBuffer);
// Length
expectedData.Slice(1, 2).Write((ushort)pos);

View file

@ -154,7 +154,7 @@ namespace Server.Tests.Network.Packets
Span<byte> expectedData = stackalloc byte[14];
var pos = 0;
expectedData.Write(ref pos, (byte)0x6E);
expectedData.Write(ref pos, (byte)0x6E); // Packet ID
expectedData.Write(ref pos, mobile);
expectedData.Write(ref pos, (ushort)action);
expectedData.Write(ref pos, (ushort)frameCount);
@ -240,12 +240,7 @@ namespace Server.Tests.Network.Packets
};
beheld.DefaultMobileInit();
var ns = new NetState(
new AccountPacketTests.TestConnectionContext
{
RemoteEndPoint = IPEndPoint.Parse("127.0.0.1")
}
)
var ns = new NetState(null)
{
ProtocolChanges = changes
};
@ -642,12 +637,7 @@ namespace Server.Tests.Network.Packets
beheld.FacialHairItemID = facialHairItemId;
beheld.FacialHairHue = facialHairHue;
var ns = new NetState(
new AccountPacketTests.TestConnectionContext
{
RemoteEndPoint = IPEndPoint.Parse("127.0.0.1")
}
)
var ns = new NetState(null)
{
ProtocolChanges = protocolChanges
};

View file

@ -0,0 +1,214 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Server.Network;
using Xunit;
namespace Server.Tests.Network
{
public class PipeTests
{
private async void DelayedExecute(Action action)
{
await Task.Delay(5);
action();
}
[Fact]
public async void Await()
{
var pipe = new Pipe<byte>(new byte[100]);
var reader = pipe.Reader;
var writer = pipe.Writer;
DelayedExecute(() =>
{
// Write some data into the pipe
var buffer = writer.GetAvailable();
Assert.True(buffer.Length == 99);
buffer.CopyFrom(new byte[] { 1 });
buffer.CopyFrom(new byte[] { 2 });
buffer.CopyFrom(new byte[] { 3 });
writer.Advance(3);
writer.Flush();
});
var result = await reader;
Assert.True(result.Buffer[0].Count == 3);
}
private bool _signal;
private void Consumer(object state)
{
var reader = ((Pipe<byte>)state).Reader;
int count = 0;
byte expected_value = 0xFA;
while (count < 0x8000000)
{
var result = reader.TryRead();
for (int i = 0; i < result.Buffer[0].Count; i++)
{
Assert.True(result.Buffer[0][i] == expected_value);
count++;
if (count == 0x1000)
{
expected_value = 0xAC;
}
}
for (int i = 0; i < result.Buffer[1].Count; i++)
{
Assert.True(result.Buffer[1][i] == expected_value);
count++;
if (count == 0x1000)
{
expected_value = 0xAC;
}
}
reader.Advance((uint)result.Length);
}
_signal = true;
}
[Fact]
public async void Threading()
{
var pipe = new Pipe<byte>(new byte[0x1001]);
ThreadPool.UnsafeQueueUserWorkItem(Consumer, pipe);
var writer = pipe.Writer;
int count = 0;
byte expected_value = 0xFA;
while (count < 0x8000000)
{
var result = writer.GetAvailable();
if (result.Length < 16)
{
continue;
}
result.CopyFrom(new[] { expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value });
writer.Advance(16);
count += 16;
if (count == 0x1000)
{
expected_value = 0xAC;
}
}
while (_signal == false) { }
_signal = false;
}
[Fact]
public void Wrap()
{
var pipe = new Pipe<byte>(new byte[10]);
var reader = pipe.Reader;
var writer = pipe.Writer;
var result = writer.GetAvailable();
Assert.True(result.Length == 9);
Assert.True(reader.GetRemaining() == 0);
result = reader.TryRead();
Assert.True(result.Length == 0);
writer.Advance(7);
result = writer.GetAvailable();
Assert.True(result.Length == 2);
Assert.True(reader.GetRemaining() == 7);
result = reader.TryRead();
Assert.True(result.Length == 7);
reader.Advance(4);
result = writer.GetAvailable();
Assert.True(result.Length == 6);
Assert.True(reader.GetRemaining() == 3);
result = reader.TryRead();
Assert.True(result.Length == 3);
writer.Advance(3);
result = writer.GetAvailable();
Assert.True(result.Length == 3);
Assert.True(reader.GetRemaining() == 6);
result = reader.TryRead();
Assert.True(result.Length == 6);
}
[Fact]
public void Match()
{
var pipe = new Pipe<byte>(new byte[10]);
var reader = pipe.Reader;
var writer = pipe.Writer;
for (uint i = 0; i < 9; i++)
{
writer.Advance(i);
writer.Flush();
Assert.True(reader.GetRemaining() == i);
reader.Advance(i);
}
}
[Fact]
public void Sequence()
{
var pipe = new Pipe<byte>(new byte[10]);
var reader = pipe.Reader;
var writer = pipe.Writer;
var buffer = writer.GetAvailable();
Assert.True(buffer.Length == 9);
buffer.CopyFrom(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 });
writer.Advance(9);
writer.Flush();
Assert.True(reader.GetRemaining() == 9);
buffer = reader.TryRead();
for (int i = 0; i < 9; i++)
{
Assert.True(buffer.Buffer[0][i] == i);
}
reader.Advance(4);
buffer = reader.TryRead();
Assert.True(buffer.Length == 5);
Assert.True(buffer.Buffer[0][0] == 4);
Assert.True(buffer.Buffer[0][1] == 5);
Assert.True(buffer.Buffer[0][2] == 6);
Assert.True(buffer.Buffer[0][3] == 7);
Assert.True(buffer.Buffer[0][4] == 8);
}
}
}

View file

@ -1,58 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Runtime.InteropServices;
namespace System.Buffers
{
/// <summary>
/// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller
/// blocks. The
/// individual blocks are then treated as independent array segments.
/// </summary>
public sealed class MemoryPoolBlock : IMemoryOwner<byte>
{
private readonly int _length;
private readonly int _offset;
/// <summary>
/// This object cannot be instantiated outside of the static Create method
/// </summary>
internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length)
{
_offset = offset;
_length = length;
Pool = pool;
Slab = slab;
Memory = MemoryMarshal.CreateFromPinnedArray(slab.Array, _offset, _length);
}
/// <summary>
/// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
/// </summary>
public SlabMemoryPool Pool { get; }
/// <summary>
/// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
/// </summary>
public MemoryPoolSlab Slab { get; }
public Memory<byte> Memory { get; }
public void Dispose()
{
Pool.Return(this);
}
~MemoryPoolBlock()
{
Pool.RefreshBlock(Slab, _offset, _length);
}
public void Lease()
{
}
}
}

View file

@ -1,11 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace System.Buffers
{
public static class SlabMemoryPoolFactory
{
public static MemoryPool<byte> Create() => CreateSlabMemoryPool();
public static MemoryPool<byte> CreateSlabMemoryPool() => new SlabMemoryPool();
}
}

View file

@ -1,82 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Runtime.InteropServices;
namespace System.Buffers
{
/// <summary>
/// Slab tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller
/// blocks. The
/// individual blocks are then treated as independent array segments.
/// </summary>
public class MemoryPoolSlab : IDisposable
{
/// <summary>
/// This handle pins the managed array in memory until the slab is disposed. This prevents it from being
/// relocated and enables any subsections of the array to be used as native memory pointers to P/Invoked API calls.
/// </summary>
private GCHandle _gcHandle;
private bool _isDisposed;
public MemoryPoolSlab(byte[] data)
{
Array = data;
_gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
NativePointer = _gcHandle.AddrOfPinnedObject();
}
/// <summary>
/// True as long as the blocks from this slab are to be considered returnable to the pool. In order to shrink the
/// memory pool size an entire slab must be removed. That is done by (1) setting IsActive to false and removing the
/// slab from the pool's _slabs collection, (2) as each block currently in use is Return()ed to the pool it will
/// be allowed to be garbage collected rather than re-pooled, and (3) when all block tracking objects are garbage
/// collected and the slab is no longer references the slab will be garbage collected and the memory unpinned will
/// be unpinned by the slab's Dispose.
/// </summary>
public bool IsActive => !_isDisposed;
public IntPtr NativePointer { get; private set; }
public byte[] Array { get; private set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public static MemoryPoolSlab Create(int length)
{
// allocate and pin requested memory length
var array = new byte[length];
// allocate and return slab tracking object
return new MemoryPoolSlab(array);
}
protected void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
Array = null;
NativePointer = IntPtr.Zero;
if (_gcHandle.IsAllocated)
{
_gcHandle.Free();
}
}
~MemoryPoolSlab()
{
Dispose(false);
}
}
}

View file

@ -1,53 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Runtime.CompilerServices;
namespace System.Buffers
{
public static class MemoryPoolThrowHelper
{
public enum ExceptionArgument
{
size,
offset,
length,
MemoryPoolBlock,
MemoryPool
}
public static void ThrowArgumentOutOfRangeException(int sourceLength, int offset)
{
throw GetArgumentOutOfRangeException(sourceLength, offset);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(int sourceLength, int offset) =>
(uint)offset > (uint)sourceLength
? new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.offset))
: new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.length));
public static void ThrowArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize)
{
throw GetArgumentOutOfRangeException_BufferRequestTooLarge(maxSize);
}
public static void ThrowObjectDisposedException(ExceptionArgument argument)
{
throw GetObjectDisposedException(argument);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize) =>
new ArgumentOutOfRangeException(
GetArgumentName(ExceptionArgument.size),
$"Cannot allocate more than {maxSize} bytes in a single buffer"
);
[MethodImpl(MethodImplOptions.NoInlining)]
private static ObjectDisposedException GetObjectDisposedException(ExceptionArgument argument) =>
new ObjectDisposedException(GetArgumentName(argument));
private static string GetArgumentName(ExceptionArgument argument) => argument.ToString();
}
}

View file

@ -1,210 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Concurrent;
using System.Threading;
namespace System.Buffers
{
/// <summary>
/// Used to allocate and distribute re-usable blocks of memory.
/// </summary>
public sealed class SlabMemoryPool : MemoryPool<byte>
{
/// <summary>
/// The size of a block. 4096 is chosen because most operating systems use 4k pages.
/// </summary>
private const int _blockSize = 4096;
/// <summary>
/// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the
/// memory
/// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not
/// negatively
/// affect memory management's compactification.
/// </summary>
private const int _blockCount = 32;
/// <summary>
/// This default value passed in to Rent to use the default value for the pool.
/// </summary>
private const int AnySize = -1;
/// <summary>
/// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
/// </summary>
private static readonly int _slabLength = _blockSize * _blockCount;
/// <summary>
/// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking
/// objects
/// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is
/// re-added.
/// </summary>
private readonly ConcurrentQueue<MemoryPoolBlock> _blocks = new ConcurrentQueue<MemoryPoolBlock>();
private readonly object _disposeSync = new object();
/// <summary>
/// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and
/// slab.IsActive,
/// the blocks will be added to _blocks when returned.
/// </summary>
private readonly ConcurrentStack<MemoryPoolSlab> _slabs = new ConcurrentStack<MemoryPoolSlab>();
/// <summary>
/// This is part of implementing the IDisposable pattern.
/// </summary>
private bool _isDisposed; // To detect redundant calls
private int _totalAllocatedBlocks;
/// <summary>
/// Max allocation block size for pooled blocks,
/// larger values can be leased but they will be disposed after use rather than returned to the pool.
/// </summary>
public override int MaxBufferSize { get; } = _blockSize;
/// <summary>
/// The size of a block. 4096 is chosen because most operating systems use 4k pages.
/// </summary>
public static int BlockSize => _blockSize;
public override IMemoryOwner<byte> Rent(int size = AnySize)
{
if (size > _blockSize)
{
MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize);
}
var block = Lease();
return block;
}
/// <summary>
/// Called to take a block from the pool.
/// </summary>
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
private MemoryPoolBlock Lease()
{
if (_isDisposed)
{
MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool);
}
if (_blocks.TryDequeue(out var block))
{
// block successfully taken from the stack - return it
block.Lease();
return block;
}
// no blocks available - grow the pool
block = AllocateSlab();
block.Lease();
return block;
}
/// <summary>
/// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of
/// the
/// block tracking objects, and adds them all to the pool.
/// </summary>
private MemoryPoolBlock AllocateSlab()
{
#pragma warning disable CA2000 // Dispose objects before losing scope
var slab = MemoryPoolSlab.Create(_slabLength);
#pragma warning restore CA2000 // Dispose objects before losing scope
_slabs.Push(slab);
var basePtr = slab.NativePointer;
// Page align the blocks
var offset = (int)((((ulong)basePtr + _blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr);
var blockCount = (_slabLength - offset) / _blockSize;
Interlocked.Add(ref _totalAllocatedBlocks, blockCount);
MemoryPoolBlock block = null;
for (var i = 0; i < blockCount; i++)
{
block = new MemoryPoolBlock(this, slab, offset, _blockSize);
if (i != blockCount - 1) // last block
{
Return(block);
}
offset += _blockSize;
}
return block;
}
/// <summary>
/// Called to return a block to the pool. Once Return has been called the memory no longer belongs to the caller, and
/// Very Bad Things will happen if the memory is read of modified subsequently. If a caller fails to call Return and the
/// block tracking object is garbage collected, the block tracking object's finalizer will automatically re-create and
/// return
/// a new tracking object into the pool. This will only happen if there is a bug in the server, however it is necessary to
/// avoid
/// leaving "dead zones" in the slab due to lost block tracking objects.
/// </summary>
/// <param name="block">The block to return. It must have been acquired by calling Lease on the same memory pool instance.</param>
internal void Return(MemoryPoolBlock block)
{
if (!_isDisposed)
{
_blocks.Enqueue(block);
}
else
{
GC.SuppressFinalize(block);
}
}
// This method can ONLY be called from the finalizer of MemoryPoolBlock
internal void RefreshBlock(MemoryPoolSlab slab, int offset, int length)
{
lock (_disposeSync)
{
if (!_isDisposed && slab?.IsActive == true)
// Need to make a new object because this one is being finalized
// Note, this must be called within the _disposeSync lock because the block
// could be disposed at the same time as the finalizer.
{
Return(new MemoryPoolBlock(this, slab, offset, length));
}
}
}
protected override void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
lock (_disposeSync)
{
_isDisposed = true;
if (disposing)
{
while (_slabs.TryPop(out var slab))
// dispose managed state (managed objects).
{
slab.Dispose();
}
}
// Discard blocks in pool
while (_blocks.TryDequeue(out var block))
{
GC.SuppressFinalize(block);
}
}
}
}
}

View file

@ -14,19 +14,19 @@
*************************************************************************/
using System;
using Microsoft.AspNetCore.Connections;
using System.Net.Sockets;
namespace Server
{
public class SocketConnectEventArgs : EventArgs
{
public SocketConnectEventArgs(ConnectionContext c)
public SocketConnectEventArgs(Socket c)
{
Context = c;
Connection = c;
AllowConnection = true;
}
public ConnectionContext Context { get; }
public Socket Connection { get; }
public bool AllowConnection { get; set; }
}

View file

@ -2,7 +2,7 @@
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ServerStartup.cs *
* File: InvalidThreadException.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
@ -13,25 +13,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Server.Network
namespace Server.Exceptions
{
public class ServerStartup
public class InvalidThreadException : Exception
{
private readonly IMessagePumpService _messagePumpService;
public ServerStartup(IMessagePumpService messagePumpService) => _messagePumpService = messagePumpService;
public void ConfigureServices(IServiceCollection services)
public InvalidThreadException(string methodName) : base($"{methodName} executed on an invalid thread.")
{
}
public void Configure(IApplicationBuilder app)
{
// Run async?
Task.Run(() => Core.RunEventLoop(_messagePumpService));
}
}
}

View file

@ -0,0 +1,26 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: MaxConnectionsException.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
namespace Server.Exceptions
{
public class MaxConnectionsException : Exception
{
public MaxConnectionsException() : base("Maximum connections exceeded")
{
}
}
}

View file

@ -3404,16 +3404,9 @@ namespace Server
{
_processing = true;
if (m_DeltaQueue.Count >= 512)
for (var i = 0; i < m_DeltaQueue.Count; i++)
{
Parallel.ForEach(m_DeltaQueue, i => i.ProcessDelta());
}
else
{
for (var i = 0; i < m_DeltaQueue.Count; i++)
{
m_DeltaQueue[i].ProcessDelta();
}
m_DeltaQueue[i].ProcessDelta();
}
m_DeltaQueue.Clear();

View file

@ -24,7 +24,6 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Server.Json;
using Server.Network;
@ -41,23 +40,10 @@ namespace Server
private static TimeSpan m_ProfileTime;
private static bool? m_IsRunningFromXUnit;
/*
* DateTime.Now and DateTime.UtcNow are based on actual system clock time.
* The resolution is acceptable but large clock jumps are possible and cause issues.
* GetTickCount and GetTickCount64 have poor resolution.
* Stopwatch.GetTimestamp() (QueryPerformanceCounter) is high resolution, but
* somewhat expensive to call because of its difference to DateTime.Now,
* which is why Stopwatch has been used to verify HRT before calling GetTimestamp(),
* enabling the usage of DateTime.UtcNow instead.
*/
private static readonly double m_HighFrequency = 1000.0 / Stopwatch.Frequency;
private static readonly double m_LowFrequency = 1000.0 / TimeSpan.TicksPerSecond;
private static int m_CycleIndex = 1;
private static readonly float[] m_CyclesPerSecond = new float[100];
private static readonly AutoResetEvent m_Signal = new AutoResetEvent(true);
// private static readonly AutoResetEvent m_Signal = new AutoResetEvent(true);
private static int m_ItemCount;
private static int m_MobileCount;
@ -115,12 +101,8 @@ namespace Server
public static Thread Thread { get; private set; }
public static long TickCount => (long)Ticks;
public static double Ticks =>
Stopwatch.IsHighResolution
? Stopwatch.GetTimestamp() * m_HighFrequency
: DateTime.UtcNow.Ticks * m_LowFrequency;
// Milliseconds
public static long TickCount => Stopwatch.GetTimestamp() * 1000L / Stopwatch.Frequency;
public static bool MultiProcessor { get; private set; }
@ -345,7 +327,7 @@ namespace Server
public static void Set()
{
m_Signal.Set();
// m_Signal.Set();
}
public static void Main(string[] args)
@ -466,22 +448,16 @@ namespace Server
m.Tiles.Force();
}
TcpServer.Start();
EventSink.InvokeServerStarted();
// Start net socket server
_tcpHost = TcpServer.CreateWebHostBuilder().Build();
// Run indefinitely and block
_tcpHost.RunAsync(ClosingTokenSource.Token).Wait();
RunEventLoop();
}
private static IWebHost _tcpHost;
public static void RunEventLoop(IMessagePumpService messagePumpService)
public static void RunEventLoop()
{
try
{
var last = TickCount;
long last = TickCount;
const int sampleInterval = 100;
const float ticksPerSecond = 1000.0f * sampleInterval;
@ -490,18 +466,21 @@ namespace Server
while (!Closing)
{
m_Signal.WaitOne();
// m_Signal.WaitOne();
Task.WaitAll(
Task.Run(Mobile.ProcessDeltaQueue),
Task.Run(Item.ProcessDeltaQueue)
);
Mobile.ProcessDeltaQueue();
Item.ProcessDeltaQueue();
Timer.Slice();
messagePumpService.DoWork();
// Handle networking
TcpServer.Slice();
NetState.HandleAllReceives();
NetState.FlushAll();
NetState.ProcessDisposedQueue();
// Execute other stuff
if (sample++ % sampleInterval != 0)
{
continue;

View file

@ -8532,17 +8532,9 @@ namespace Server
{
_processing = true;
if (m_DeltaQueue.Count >= 512)
while (m_DeltaQueue.TryDequeue(out var m))
{
Parallel.ForEach(m_DeltaQueue, m => m.ProcessDelta());
m_DeltaQueue.Clear();
}
else
{
while (m_DeltaQueue.TryDequeue(out var m))
{
m.ProcessDelta();
}
m.ProcessDelta();
}
_processing = false;

View file

@ -18,9 +18,9 @@ namespace Server.Network
: new Point3D(m_Reader.ReadInt16(), m_Reader.ReadInt16(), m_Reader.ReadByte());
public string ReadUnicodeStringSafe() =>
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeStringSafe(m_Reader.ReadUInt16());
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadBigUniSafe(m_Reader.ReadUInt16());
public string ReadUnicodeString() =>
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeString(m_Reader.ReadUInt16());
m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadBigUni(m_Reader.ReadUInt16());
}
}

View file

@ -1,69 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: MessagePumpService.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Buffers;
using System.Collections.Concurrent;
namespace Server.Network
{
public interface IMessagePumpService
{
void QueueWork(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive);
void DoWork();
}
public class MessagePumpService : IMessagePumpService
{
private readonly ConcurrentQueue<Work> m_WorkQueue = new ConcurrentQueue<Work>();
public void QueueWork(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive)
{
m_WorkQueue.Enqueue(new Work(ns, memOwner, length, onReceive));
Core.Set();
}
public void DoWork()
{
var count = 0;
while (!m_WorkQueue.IsEmpty && count++ < 250)
{
if (!m_WorkQueue.TryDequeue(out var work))
{
break;
}
var seq = new ReadOnlySequence<byte>(work.MemoryOwner.Memory.Slice(0, work.Length));
work.OnReceive(work.State, new PacketReader(seq));
work.MemoryOwner.Dispose();
}
}
private class Work
{
public readonly int Length;
public readonly IMemoryOwner<byte> MemoryOwner;
public readonly OnPacketReceive OnReceive;
public readonly NetState State;
public Work(NetState ns, IMemoryOwner<byte> memOwner, int length, OnPacketReceive onReceive)
{
State = ns;
MemoryOwner = memOwner;
OnReceive = onReceive;
Length = length;
}
}
}
}

View file

@ -1,13 +1,28 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: NetState.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Server.Accounting;
using Server.Exceptions;
using Server.Gumps;
using Server.HuePickers;
using Server.Items;
@ -17,41 +32,66 @@ namespace Server.Network
{
public delegate void NetStateCreatedCallback(NetState ns);
public class AsyncState
{
public AsyncState(bool paused) => Paused = paused;
public bool Paused { get; set; }
}
public partial class NetState : IComparable<NetState>
{
private static readonly AsyncState m_PauseState = new AsyncState(true);
private static readonly AsyncState m_ResumeState = new AsyncState(false);
private static AsyncState m_AsyncState = m_ResumeState;
private static int IncomingPipeSize = 1024 * 64;
private static int OutgoingPipeSize = 1024 * 256;
private static int GumpCap = 512;
private static int HuePickerCap = 512;
private static int MenuCap = 512;
private static readonly ConcurrentQueue<NetState> m_Disposed = new ConcurrentQueue<NetState>();
private static NetworkState m_NetworkState = NetworkState.ResumeState;
public static NetStateCreatedCallback CreatedCallback { get; set; }
private readonly string m_ToString;
internal int m_AuthID;
private int m_Disposing;
internal int m_Seed;
private ClientVersion m_Version;
private byte[] m_IncomingBuffer;
private Pipe<byte> m_IncomingPipe;
private byte[] m_OutgoingBuffer;
private Pipe<byte> m_OutgoingPipe;
private long m_NextCheckActivity;
private volatile bool m_Running;
public NetState(ConnectionContext connection)
internal int m_AuthID;
internal int m_Seed;
public static void Configure()
{
IncomingPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.incomingPipeSize", IncomingPipeSize);
OutgoingPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.outgoingPipeSize", OutgoingPipeSize);
GumpCap = ServerConfiguration.GetOrUpdateSetting("netstate.gumpCap", GumpCap);
HuePickerCap = ServerConfiguration.GetOrUpdateSetting("netstate.huePickerCap", HuePickerCap);
MenuCap = ServerConfiguration.GetOrUpdateSetting("netstate.menuCap", MenuCap);
}
public static void Initialize()
{
var checkAliveDuration = TimeSpan.FromMinutes(1.5);
Timer.DelayCall(checkAliveDuration, checkAliveDuration, CheckAllAlive);
}
public NetState(Socket connection)
{
m_Running = false;
Connection = connection;
Seeded = false;
Gumps = new List<Gump>();
HuePickers = new List<HuePicker>();
Menus = new List<IMenu>();
Trades = new List<SecureTrade>();
m_IncomingBuffer = new byte[IncomingPipeSize];
m_IncomingPipe = new Pipe<byte>(m_IncomingBuffer);
m_OutgoingBuffer = new byte[OutgoingPipeSize];
m_OutgoingPipe = new Pipe<byte>(m_OutgoingBuffer);
m_NextCheckActivity = Core.TickCount + 30000;
try
{
Address = Utility.Intern(((IPEndPoint)Connection.RemoteEndPoint).Address);
m_ToString = Address.ToString();
Address = Utility.Intern((Connection?.RemoteEndPoint as IPEndPoint)?.Address);
m_ToString = Address?.ToString() ?? "(error)";
}
catch (Exception ex)
{
@ -62,14 +102,6 @@ namespace Server.Network
ConnectedOn = DateTime.UtcNow;
connection.ConnectionClosed.Register(
() =>
{
TcpServer.Instances.Remove(this);
Dispose();
}
);
CreatedCallback?.Invoke(this);
}
@ -80,21 +112,20 @@ namespace Server.Network
public DateTime ThrottledUntil { get; set; }
public IPAddress Address { get; }
public static AsyncState AsyncState => m_AsyncState;
public IPacketEncoder PacketEncoder { get; set; }
public static NetStateCreatedCallback CreatedCallback { get; set; }
public bool SentFirstPacket { get; set; }
public bool BlockAllPackets { get; set; }
public List<SecureTrade> Trades { get; }
public bool Running => m_Running;
public bool Seeded { get; set; }
public ConnectionContext Connection { get; private set; }
public Socket Connection { get; private set; }
public bool CompressionEnabled { get; set; }
@ -106,12 +137,6 @@ namespace Server.Network
public List<IMenu> Menus { get; private set; }
public static int GumpCap { get; set; } = 512;
public static int HuePickerCap { get; set; } = 512;
public static int MenuCap { get; set; } = 512;
public CityInfo[] CityInfo { get; set; }
public Mobile Mobile { get; set; }
@ -208,11 +233,13 @@ namespace Server.Network
return newTrade.From.Container;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteConsole(string text)
{
Console.WriteLine("Client: {0}: {1}", this, text);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteConsole(string format, params object[] args)
{
WriteConsole(string.Format(format, args));
@ -318,15 +345,15 @@ namespace Server.Network
public static void Pause()
{
m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_PauseState);
NetworkState.Pause(ref m_NetworkState);
}
public static void Resume()
{
m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_ResumeState);
NetworkState.Resume(ref m_NetworkState);
}
public virtual async void Send(Packet p)
public virtual void Send(Packet p)
{
if (Connection == null || BlockAllPackets)
{
@ -334,83 +361,101 @@ namespace Server.Network
return;
}
var outPipe = Connection.Transport.Output;
var currentThread = Thread.CurrentThread;
if (currentThread != Core.Thread)
{
Console.Error.WriteLine("Core: Attempted to send packet outside core thread! [{0}]", currentThread.ManagedThreadId);
#if DEBUG
throw new InvalidThreadException(nameof(Send));
#endif
}
var writer = m_OutgoingPipe.Writer;
try
{
// TODO: Rented memory
ReadOnlyMemory<byte> buffer = p.Compile(CompressionEnabled, out var length);
byte[] buffer = p.Compile(CompressionEnabled, out var length);
if (buffer.Length > 0 && length > 0)
{
var result = await outPipe.WriteAsync(buffer.Slice(0, length));
var result = writer.GetAvailable();
if (result.IsCanceled || result.IsCompleted)
if (result.Length >= length)
{
Dispose();
return;
result.CopyFrom(buffer.AsSpan(0, length));
writer.Advance((uint)length);
// Flush at the end of the game loop
}
else
{
WriteConsole("Too much data pending, disconnecting...");
Dispose();
}
}
else
{
WriteConsole("Didn't write anything!");
}
p.OnSend();
}
catch (SocketException ex)
{
Console.WriteLine(ex);
TraceException(ex);
Dispose();
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
TraceException(ex);
#endif
Dispose();
}
}
public async Task ProcessIncoming(IMessagePumpService messagePumpService)
internal void Start()
{
var inPipe = Connection.Transport.Input;
if (Connection == null || m_Running)
{
return;
}
m_Running = true;
ThreadPool.UnsafeQueueUserWorkItem(RecvTask, null);
ThreadPool.UnsafeQueueUserWorkItem(SendTask, null);
}
private async void SendTask(object state)
{
var reader = m_OutgoingPipe.Reader;
try
{
while (true)
while (m_Running)
{
if (AsyncState.Paused)
var result = await reader.Read();
if (result.Length <= 0)
{
continue;
}
var result = await inPipe.ReadAsync();
if (result.IsCanceled || result.IsCompleted)
var buffer = result.Buffer;
var bytesWritten = await Connection.SendAsync(buffer, SocketFlags.None);
if (bytesWritten > 0)
{
return;
m_NextCheckActivity = Core.TickCount + 90000;
reader.Advance((uint)bytesWritten);
}
var seq = result.Buffer;
if (seq.IsEmpty)
{
break;
}
var pos = PacketHandlers.ProcessPacket(messagePumpService, this, seq);
if (pos <= 0)
{
break;
}
inPipe.AdvanceTo(seq.Slice(0, pos).End);
}
}
catch (SocketException ex)
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
TraceException(ex);
}
catch
{
// Console.WriteLine(ex);
#endif
}
finally
{
@ -418,12 +463,165 @@ namespace Server.Network
}
}
private async void RecvTask(object state)
{
var socket = Connection;
var writer = m_IncomingPipe.Writer;
try
{
while (m_Running)
{
if (m_NetworkState == NetworkState.PauseState)
{
continue;
}
var result = writer.GetAvailable();
if (result.Length <= 0)
{
continue;
}
var buffer = result.Buffer;
var bytesWritten = await socket.ReceiveAsync(buffer, SocketFlags.None);
if (bytesWritten <= 0)
{
break;
}
writer.Advance((uint)bytesWritten);
m_NextCheckActivity = Core.TickCount + 90000;
// No need to flush
}
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
TraceException(ex);
#endif
}
finally
{
Dispose();
}
}
public static void HandleAllReceives()
{
var clients = TcpServer.Instances;
for (int i = 0; i < clients.Count; ++i)
{
clients[i].HandleReceive();
}
}
public void HandleReceive()
{
if (Connection == null)
{
return;
}
try
{
var reader = m_IncomingPipe.Reader;
// Process as many packets as we can synchronously
while (true)
{
var result = reader.TryRead();
if (result.Length <= 0)
{
return;
}
var bytesProcessed = PacketHandlers.ProcessPacket(this, result.Buffer);
if (bytesProcessed <= 0)
{
// Error
// TODO: Throw exception instead?
if (bytesProcessed < 0)
{
Dispose();
}
return;
}
reader.Advance((uint)bytesProcessed);
}
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine(ex);
TraceException(ex);
#endif
Dispose();
}
}
public void Flush()
{
if (Connection != null)
{
m_OutgoingPipe.Writer.Flush();
}
}
public static void FlushAll()
{
var clients = TcpServer.Instances;
for (int i = 0; i < clients.Count; ++i)
{
clients[i].Flush();
}
}
public void CheckAlive(long curTicks)
{
if (Connection != null && m_NextCheckActivity - curTicks < 0)
{
WriteConsole("Disconnecting due to inactivity...");
Dispose();
}
}
public static void CheckAllAlive()
{
try
{
long curTicks = Core.TickCount;
var clients = TcpServer.Instances;
for (int i = 0; i < clients.Count; ++i)
{
clients[i].CheckAlive(curTicks);
}
}
catch (Exception ex)
{
TraceException(ex);
}
}
public bool CheckEncrypted(int packetID)
{
if (!SentFirstPacket && packetID != 0xF0 && packetID != 0xF1 && packetID != 0xCF && packetID != 0x80 &&
packetID != 0x91 && packetID != 0xA4 && packetID != 0xEF)
{
Console.WriteLine("Client: {0}: Encrypted client detected, disconnecting", this);
WriteConsole("Encrypted client detected, disconnecting");
Dispose();
return true;
}
@ -456,26 +654,25 @@ namespace Server.Network
public virtual void Dispose()
{
var disposing = Interlocked.Exchange(ref m_Disposing, 1);
if (disposing == 1)
if (Connection == null || Interlocked.Exchange(ref m_Disposing, 1) != 0)
{
return;
}
m_OutgoingPipe.Writer.Close();
try
{
Connection.Transport.Input.Complete();
Connection.Transport.Output.Complete();
Connection.Abort();
Task.Run(Connection.DisposeAsync).Wait();
Connection.Shutdown(SocketShutdown.Both);
}
catch (Exception ex)
{
TraceException(ex);
}
Connection = null;
m_Disposed.Enqueue(this);
finally
{
m_Disposed.Enqueue(this);
}
}
public static void ProcessDisposedQueue()
@ -498,6 +695,12 @@ namespace Server.Network
ns.Mobile = null;
}
ns.m_Running = false;
ns.Connection = null;
ns.m_IncomingBuffer = null;
ns.m_IncomingPipe = null;
ns.m_OutgoingBuffer = null;
ns.m_OutgoingPipe = null;
ns.Gumps.Clear();
ns.Menus.Clear();
ns.HuePickers.Clear();
@ -505,6 +708,8 @@ namespace Server.Network
ns.ServerInfo = null;
ns.CityInfo = null;
TcpServer.Instances.Remove(ns);
if (a != null)
{
ns.WriteConsole("Disconnected. [{0} Online] [{1}]", TcpServer.Instances.Count, a);

View file

@ -0,0 +1,38 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: NetworkState.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Runtime.CompilerServices;
using System.Threading;
namespace Server.Network
{
public class NetworkState
{
public static readonly NetworkState PauseState = new NetworkState(true);
public static readonly NetworkState ResumeState = new NetworkState(false);
public bool Paused { get; }
internal NetworkState(bool paused) => Paused = paused;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Pause(ref NetworkState state) =>
Interlocked.Exchange(ref state, PauseState)?.Paused != true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Resume(ref NetworkState state) =>
Interlocked.Exchange(ref state, ResumeState)?.Paused == true;
}
}

View file

@ -193,9 +193,8 @@ namespace Server.Network
if (compress)
{
var buffer = ArrayPool<byte>.Shared.Rent(CompressorBufferSize);
NetworkCompression.Compress(m_CompiledBuffer, 0, length, buffer, out length);
var compressorBuffer = ArrayPool<byte>.Shared.Rent(CompressorBufferSize);
NetworkCompression.Compress(m_CompiledBuffer, 0, length, compressorBuffer, out var compressedLength);
if (length <= 0)
{
@ -214,28 +213,23 @@ namespace Server.Network
length
);
op.WriteLine(new StackTrace());
ArrayPool<byte>.Shared.Return(compressorBuffer);
}
else
{
m_CompiledLength = length;
if ((m_State & State.Static) != 0)
{
m_CompiledBuffer = new byte[length];
Buffer.BlockCopy(buffer, 0, m_CompiledBuffer, 0, length);
ArrayPool<byte>.Shared.Return(buffer);
}
else
{
m_CompiledBuffer = buffer;
m_State |= State.Buffered;
}
m_CompiledBuffer = compressorBuffer;
m_CompiledLength = compressedLength;
}
}
else if (length > 0)
else
{
m_CompiledLength = length;
}
if (length > 0)
{
var old = m_CompiledBuffer;
m_CompiledLength = length;
if ((m_State & State.Static) != 0)
{
@ -243,11 +237,17 @@ namespace Server.Network
}
else
{
// Release it later using Release()
m_CompiledBuffer = ArrayPool<byte>.Shared.Rent(length);
m_State |= State.Buffered;
}
Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, length);
if (compress)
{
ArrayPool<byte>.Shared.Return(old);
}
}
PacketWriter.ReleaseInstance(Stream);

View file

@ -1,5 +1,19 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PacketHandlers.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using Server.ContextMenus;
@ -34,9 +48,6 @@ namespace Server.Network
{
public delegate void PlayCharCallback(NetState state, bool val);
private const int BadFood = unchecked((int)0xBAADF00D);
private const int BadUOTD = unchecked((int)0xFFCEFFCE);
private const int m_AuthIDWindowSize = 128;
private static readonly PacketHandler[] m_6017Handlers = new PacketHandler[0x100];
@ -55,8 +66,6 @@ namespace Server.Network
private static readonly Dictionary<int, AuthIDPersistence> m_AuthIDWindow =
new Dictionary<int, AuthIDPersistence>(m_AuthIDWindowSize);
private static readonly MemoryPool<byte> _memoryPool = SlabMemoryPoolFactory.Create();
static PacketHandlers()
{
Register(0x00, 104, false, CreateCharacter);
@ -272,15 +281,11 @@ namespace Server.Network
}
}
public static int ProcessPacket(IMessagePumpService pump, NetState ns, in ReadOnlySequence<byte> seq)
public static int ProcessPacket(NetState ns, ArraySegment<byte>[] segments)
{
var r = new PacketReader(seq);
var reader = new PacketReader(segments);
if (!r.TryReadByte(out var packetId))
{
ns.Dispose();
return -1;
}
var packetId = reader.ReadByte();
if (!ns.Seeded)
{
@ -292,12 +297,11 @@ namespace Server.Network
}
else
{
var seed = (packetId << 24) | (r.ReadByte() << 16) | (r.ReadByte() << 8) | r.ReadByte();
var seed = (packetId << 24) | (reader.ReadByte() << 16) | (reader.ReadByte() << 8) | reader.ReadByte();
if (seed == 0)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", ns);
ns.Dispose();
ns.WriteConsole("Invalid client detected, disconnecting");
return -1;
}
@ -310,7 +314,6 @@ namespace Server.Network
if (ns.CheckEncrypted(packetId))
{
ns.Dispose();
return -1;
}
@ -319,34 +322,29 @@ namespace Server.Network
if (handler == null)
{
r.Trace(ns);
reader.Trace(ns);
return -1;
}
var packetLength = handler.Length;
if (handler.Length <= 0 && r.Length >= 3)
if (handler.Length <= 0 && reader.Length >= 3)
{
packetLength = r.ReadUInt16();
packetLength = reader.ReadUInt16();
if (packetLength < 3)
{
ns.Dispose();
return -1;
}
}
if (r.Length < packetLength)
// Not enough data, let's wait for more to come in
if (reader.Length < packetLength)
{
return 0;
}
if (handler.Ingame && ns.Mobile?.Deleted != false)
{
Console.WriteLine(
"Client: {0}: Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.",
ns,
packetId
);
ns.Dispose();
ns.WriteConsole("Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.", ns, packetId);
return -1;
}
@ -357,14 +355,7 @@ namespace Server.Network
ns.ThrottledUntil = DateTime.UtcNow + throttled;
}
var packet = seq.Slice(r.Position);
var length = (int)packet.Length;
var memOwner = _memoryPool.Rent(length);
// TODO: This is slow, find another way
packet.CopyTo(memOwner.Memory.Span);
pump.QueueWork(ns, memOwner, length, handler.OnReceive);
handler.OnReceive(ns, reader);
return packetLength;
}
@ -403,9 +394,8 @@ namespace Server.Network
{
if (ph.Ingame && state.Mobile == null)
{
Console.WriteLine(
"Client: {0}: Sent ingame packet (0xD7x{1:X2}) before having been attached to a mobile",
state,
state.WriteConsole(
"Sent ingame packet (0xD7x{0:X2}) before having been attached to a mobile",
packetId
);
state.Dispose();
@ -432,7 +422,7 @@ namespace Server.Network
if (targ != null)
{
EventSink.InvokeRenameRequest(from, targ, reader.ReadStringSafe());
EventSink.InvokeRenameRequest(from, targ, reader.ReadAsciiSafe());
}
}
@ -526,7 +516,7 @@ namespace Server.Network
if (flag == 0x02)
{
var msgSize = (int)reader.Remaining;
var msgSize = reader.Remaining;
if (msgSize / 7 > 100)
{
@ -616,7 +606,7 @@ namespace Server.Network
Serial serial = reader.ReadUInt32();
int unk = reader.ReadByte();
var lang = reader.ReadString(3);
var lang = reader.ReadAscii(3);
if (serial.IsItem)
{
@ -692,10 +682,10 @@ namespace Server.Network
int v1 = reader.ReadByte();
int v2 = reader.ReadUInt16();
int v3 = reader.ReadByte();
var s1 = reader.ReadString(32);
var s2 = reader.ReadString(32);
var s3 = reader.ReadString(32);
var s4 = reader.ReadString(32);
var s1 = reader.ReadAscii(32);
var s2 = reader.ReadAscii(32);
var s3 = reader.ReadAscii(32);
var s4 = reader.ReadAscii(32);
int v4 = reader.ReadUInt16();
int v5 = reader.ReadUInt16();
var v6 = reader.ReadInt32();
@ -710,7 +700,7 @@ namespace Server.Network
public static void TextCommand(NetState state, PacketReader reader)
{
int type = reader.ReadByte();
var command = reader.ReadString();
var command = reader.ReadAscii();
var m = state.Mobile;
@ -794,7 +784,7 @@ namespace Server.Network
}
default:
{
Console.WriteLine("Client: {0}: Unknown text-command type 0x{1:X2}: {2}", state, type, command);
state.WriteConsole("Unknown text-command type 0x{0:X2}: {1}", state, type, command);
break;
}
}
@ -805,7 +795,7 @@ namespace Server.Network
var serial = reader.ReadUInt32();
var prompt = reader.ReadInt32();
var type = reader.ReadInt32();
var text = reader.ReadStringSafe();
var text = reader.ReadAsciiSafe();
if (text.Length > 128)
{
@ -835,8 +825,8 @@ namespace Server.Network
var serial = reader.ReadUInt32();
var prompt = reader.ReadInt32();
var type = reader.ReadInt32();
var lang = reader.ReadString(4);
var text = reader.ReadUnicodeStringLESafe();
var lang = reader.ReadAscii(4);
var text = reader.ReadLittleUniSafe();
if (text.Length > 128)
{
@ -922,7 +912,7 @@ namespace Server.Network
return;
}
var text = reader.ReadUnicodeString(length);
var text = reader.ReadBigUni(length);
EventSink.InvokeChangeProfileRequest(beholder, beheld, text);
@ -1259,7 +1249,7 @@ namespace Server.Network
return;
}
var text = reader.ReadUnicodeStringSafe(textLength);
var text = reader.ReadBigUniSafe(textLength);
textEntries[j] = new TextRelay(entryID, text);
}
@ -1336,7 +1326,7 @@ namespace Server.Network
var type = (MessageType)reader.ReadByte();
int hue = reader.ReadInt16();
reader.ReadInt16(); // font
var text = reader.ReadStringSafe().Trim();
var text = reader.ReadAsciiSafe().Trim();
if (text.Length <= 0 || text.Length > 128)
{
@ -1358,7 +1348,7 @@ namespace Server.Network
var type = (MessageType)reader.ReadByte();
int hue = reader.ReadInt16();
reader.ReadInt16(); // font
var lang = reader.ReadString(4);
var lang = reader.ReadAscii(4);
string text;
var isEncoded = (type & MessageType.Encoded) != 0;
@ -1401,13 +1391,13 @@ namespace Server.Network
}
}
text = reader.ReadUTF8StringSafe();
text = reader.ReadUTF8Safe();
keywords = keyList.ToArray();
}
else
{
text = reader.ReadUnicodeStringSafe();
text = reader.ReadBigUniSafe();
keywords = m_EmptyInts;
}
@ -1603,9 +1593,8 @@ namespace Server.Network
{
if (state.Mobile == null)
{
Console.WriteLine(
"Client: {0}: Sent in-game packet (0xBFx{1:X2}) before having been attached to a mobile",
state,
state.WriteConsole(
"Sent in-game packet (0xBFx{0:X2}) before having been attached to a mobile",
packetID
);
}
@ -1804,13 +1793,13 @@ namespace Server.Network
PartyCommands.Handler?.OnPrivateMessage(
state.Mobile,
World.FindMobile(reader.ReadUInt32()),
reader.ReadUnicodeStringSafe()
reader.ReadBigUniSafe()
);
}
public static void PartyMessage_PublicMessage(NetState state, PacketReader reader)
{
PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadUnicodeStringSafe());
PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadBigUniSafe());
}
public static void PartyMessage_SetCanLoot(NetState state, PacketReader reader)
@ -1980,7 +1969,7 @@ namespace Server.Network
public static void Language(NetState state, PacketReader reader)
{
var lang = reader.ReadString(4);
var lang = reader.ReadAscii(4);
if (state.Mobile != null)
{
@ -1991,12 +1980,12 @@ namespace Server.Network
public static void AssistVersion(NetState state, PacketReader reader)
{
var unk = reader.ReadInt32();
var av = reader.ReadString();
var av = reader.ReadAscii();
}
public static void ClientVersion(NetState state, PacketReader reader)
{
var version = state.Version = new CV(reader.ReadString());
var version = state.Version = new CV(reader.ReadAscii());
EventSink.InvokeClientVersionReceived(state, version);
}
@ -2006,7 +1995,7 @@ namespace Server.Network
reader.ReadUInt16();
int type = reader.ReadUInt16();
var version = state.Version = new CV(reader.ReadString());
var version = state.Version = new CV(reader.ReadAscii());
EventSink.InvokeClientVersionReceived(state, version);
}
@ -2046,7 +2035,7 @@ namespace Server.Network
{
reader.ReadInt32(); // 0xEDEDEDED
var name = reader.ReadString(30);
var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
@ -2074,7 +2063,7 @@ namespace Server.Network
if (check != null && check.Map != Map.Internal && check != m)
{
Console.WriteLine("Login: {0}: Account in use", state);
state.WriteConsole("Account in use");
state.Send(new PopupMessage(PMMessage.CharInWorld));
return;
}
@ -2217,7 +2206,7 @@ namespace Server.Network
var unk1 = reader.ReadInt32();
var unk2 = reader.ReadInt32();
int unk3 = reader.ReadByte();
var name = reader.ReadString(30);
var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
var flags = reader.ReadInt32();
@ -2292,7 +2281,7 @@ namespace Server.Network
if (check != null && check.Map != Map.Internal)
{
Console.WriteLine("Login: {0}: Account in use", state);
state.WriteConsole("Account in use");
state.Send(new PopupMessage(PMMessage.CharInWorld));
return;
}
@ -2353,7 +2342,7 @@ namespace Server.Network
var unk1 = reader.ReadInt32();
var unk2 = reader.ReadInt32();
int unk3 = reader.ReadByte();
var name = reader.ReadString(30);
var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
var flags = reader.ReadInt32();
@ -2417,7 +2406,7 @@ namespace Server.Network
if (check != null && check.Map != Map.Internal)
{
Console.WriteLine("Login: {0}: Account in use", state);
state.WriteConsole("Account in use");
state.Send(new PopupMessage(PMMessage.CharInWorld));
return;
}
@ -2530,27 +2519,27 @@ namespace Server.Network
}
else if (ClientVerification)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
return;
}
if (state.m_AuthID != 0 && authID != state.m_AuthID)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
return;
}
if (state.m_AuthID == 0 && authID != state.m_Seed)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
return;
}
var username = reader.ReadString(30);
var password = reader.ReadString(30);
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
var e = new GameLoginEventArgs(state, username, password);
@ -2559,7 +2548,7 @@ namespace Server.Network
if (e.Accepted)
{
state.CityInfo = e.CityInfo;
state.CompressionEnabled = true;
// state.CompressionEnabled = true;
state.Send(SupportedFeatures.Instantiate(state));
@ -2606,7 +2595,7 @@ namespace Server.Network
if (state.m_Seed == 0)
{
Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state);
state.WriteConsole("Invalid client detected, disconnecting");
state.Dispose();
return;
}
@ -2631,15 +2620,15 @@ namespace Server.Network
var z = reader.ReadSByte();
var map = reader.ReadByte();
var account = reader.ReadString(32);
var character = reader.ReadString(32);
var ip = reader.ReadString(15);
var account = reader.ReadAscii(32);
var character = reader.ReadAscii(32);
var ip = reader.ReadAscii(15);
var unk1 = reader.ReadInt32();
var exception = reader.ReadInt32();
var process = reader.ReadString(100);
var report = reader.ReadString(100);
var process = reader.ReadAscii(100);
var report = reader.ReadAscii(100);
reader.ReadByte(); // 0x00
@ -2663,8 +2652,8 @@ namespace Server.Network
state.SentFirstPacket = true;
var username = reader.ReadString(30);
var password = reader.ReadString(30);
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
var e = new AccountLoginEventArgs(state, username, password);
@ -2707,7 +2696,7 @@ namespace Server.Network
state.Dispose();
}
public static void EquipMacro(NetState ns, PacketReader reader)
public static void EquipMacro(NetState state, PacketReader reader)
{
int count = reader.ReadByte();
var serialList = new List<Serial>(count);
@ -2716,10 +2705,10 @@ namespace Server.Network
serialList.Add(reader.ReadUInt32());
}
EventSink.InvokeEquipMacro(ns.Mobile, serialList);
EventSink.InvokeEquipMacro(state.Mobile, serialList);
}
public static void UnequipMacro(NetState ns, PacketReader reader)
public static void UnequipMacro(NetState state, PacketReader reader)
{
int count = reader.ReadByte();
var layers = new List<Layer>(count);
@ -2728,30 +2717,30 @@ namespace Server.Network
layers.Add((Layer)reader.ReadUInt16());
}
EventSink.InvokeUnequipMacro(ns.Mobile, layers);
EventSink.InvokeUnequipMacro(state.Mobile, layers);
}
public static void TargetedSpell(NetState ns, PacketReader reader)
public static void TargetedSpell(NetState state, PacketReader reader)
{
var spellId = (short)(reader.ReadInt16() - 1); // zero based;
EventSink.InvokeTargetedSpell(ns.Mobile, World.FindEntity(reader.ReadUInt32()), spellId);
EventSink.InvokeTargetedSpell(state.Mobile, World.FindEntity(reader.ReadUInt32()), spellId);
}
public static void TargetedSkillUse(NetState ns, PacketReader reader)
public static void TargetedSkillUse(NetState state, PacketReader reader)
{
var skillId = reader.ReadInt16();
EventSink.InvokeTargetedSkillUse(ns.Mobile, World.FindEntity(reader.ReadUInt32()), skillId);
EventSink.InvokeTargetedSkillUse(state.Mobile, World.FindEntity(reader.ReadUInt32()), skillId);
}
public static void TargetByResourceMacro(NetState ns, PacketReader reader)
public static void TargetByResourceMacro(NetState state, PacketReader reader)
{
Serial serial = reader.ReadUInt32();
if (serial.IsItem)
{
EventSink.InvokeTargetByResourceMacro(ns.Mobile, World.FindItem(serial), reader.ReadInt16());
EventSink.InvokeTargetByResourceMacro(state.Mobile, World.FindItem(serial), reader.ReadInt16());
}
}

View file

@ -1,39 +1,68 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PacketReader.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Data;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Server.Network
{
public ref struct PacketReader
{
private SequenceReader<byte> m_Reader;
public Span<byte> First;
public Span<byte> Second;
public SequencePosition Position => m_Reader.Position;
public long Length => m_Reader.Length;
public long Consumed => m_Reader.Consumed;
public long Remaining => m_Reader.Remaining;
public int Length { get; }
public int Position { get; private set; }
public int Remaining => Length - Position;
public PacketReader(ReadOnlySequence<byte> seq) => m_Reader = new SequenceReader<byte>(seq);
public PacketReader(ArraySegment<byte>[] buffers)
{
First = buffers[0];
Second = buffers[1];
Position = 0;
Length = First.Length + Second.Length;
}
public byte Peek() => m_Reader.TryPeek(out var value) ? value : (byte)0;
public PacketReader(Span<byte> first, Span<byte> second)
{
First = first;
Second = second;
Position = 0;
Length = first.Length + second.Length;
}
public void Trace(NetState state)
{
// We don't have data, so nothing to trace
if (First.Length == 0)
{
return;
}
try
{
using var sw = new StreamWriter("Packets.log", true);
var buffer = m_Reader.Sequence.ToArray();
if (buffer.Length > 0)
{
sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, buffer[0]);
}
sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, First[0]);
using (var ms = new MemoryStream(buffer))
{
Utility.FormatBuffer(sw, ms, buffer.Length);
}
Utility.FormatBuffer(sw, First.ToArray(), new Memory<byte>(Second.ToArray()));
sw.WriteLine();
sw.WriteLine();
@ -44,319 +73,279 @@ namespace Server.Network
}
}
public SequencePosition Seek(long offset, SeekOrigin origin)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
switch (origin)
if (Position < First.Length)
{
case SeekOrigin.Begin:
if (offset < m_Reader.Consumed)
{
m_Reader.Rewind(m_Reader.Consumed - Math.Max(offset, 0L));
}
else
{
m_Reader.Advance(offset - m_Reader.Consumed);
}
break;
case SeekOrigin.Current:
if (offset < 0)
{
m_Reader.Rewind(Math.Min(m_Reader.Consumed, offset * -1));
}
else
{
m_Reader.Advance(Math.Min(m_Reader.Remaining, offset));
}
break;
case SeekOrigin.End:
var count = m_Reader.Remaining - offset;
if (count < 0)
{
m_Reader.Rewind(count * -1);
}
else if (count > 0)
{
m_Reader.Advance(count);
}
break;
return First[Position++];
}
return m_Reader.Position;
if (Position < Length)
{
return Second[Position++ - First.Length];
}
throw new OutOfMemoryException();
}
public bool TryReadByte(out byte value) => m_Reader.TryRead(out value);
public int ReadInt32() => m_Reader.TryReadBigEndian(out int value) ? value : 0;
public short ReadInt16() => m_Reader.TryReadBigEndian(out short value) ? value : (short)0;
public byte ReadByte() => m_Reader.TryRead(out var value) ? value : (byte)0;
public uint ReadUInt32() => (uint)ReadInt32();
public ushort ReadUInt16() => (ushort)ReadInt16();
public sbyte ReadSByte() => (sbyte)ReadByte();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReadBoolean() => ReadByte() > 0;
public string ReadUnicodeStringLE()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public sbyte ReadSByte() => (sbyte)ReadByte();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public short ReadInt16()
{
var sb = new StringBuilder();
short value;
while (m_Reader.TryReadLittleEndian(out short c) && c != 0)
if (Position < First.Length)
{
sb.Append((char)c);
}
return sb.ToString();
}
public string ReadUnicodeStringLE(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0)
{
sb.Append((char)c);
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength);
}
return sb.ToString();
}
public string ReadUnicodeStringLESafe(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0)
{
if (IsSafeChar(c))
if (!BinaryPrimitives.TryReadInt16BigEndian(First.Slice(Position), out value))
{
sb.Append((char)c);
// Not enough space. Split the spans
return (short)((ReadByte() >> 8) | ReadByte());
}
}
if (fixedLength > 0)
else if (!BinaryPrimitives.TryReadInt16BigEndian(Second.Slice(Position - First.Length), out value))
{
m_Reader.Advance(fixedLength * 2);
throw new OutOfMemoryException();
}
return sb.ToString();
Position += 2;
return value;
}
public string ReadUnicodeStringLESafe()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUInt16()
{
var sb = new StringBuilder();
ushort value;
while (m_Reader.TryReadLittleEndian(out short c) && c != 0)
if (Position < First.Length)
{
if (IsSafeChar(c))
if (!BinaryPrimitives.TryReadUInt16BigEndian(First.Slice(Position), out value))
{
sb.Append((char)c);
// Not enough space. Split the spans
return (ushort)((ReadByte() >> 8) | ReadByte());
}
}
else if (!BinaryPrimitives.TryReadUInt16BigEndian(Second.Slice(Position - First.Length), out value))
{
throw new OutOfMemoryException();
}
return sb.ToString();
Position += 2;
return value;
}
public string ReadUnicodeStringSafe()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt32()
{
var sb = new StringBuilder();
int value;
while (m_Reader.TryReadBigEndian(out short c) && c != 0)
if (Position < First.Length)
{
if (IsSafeChar(c))
if (!BinaryPrimitives.TryReadInt32BigEndian(First.Slice(Position), out value))
{
sb.Append((char)c);
// Not enough space. Split the spans
return (ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte();
}
}
return sb.ToString();
}
public string ReadUnicodeString()
{
var sb = new StringBuilder();
while (m_Reader.TryReadBigEndian(out short c) && c != 0)
else if (!BinaryPrimitives.TryReadInt32BigEndian(Second.Slice(Position - First.Length), out value))
{
sb.Append((char)c);
throw new OutOfMemoryException();
}
return sb.ToString();
Position += 4;
return value;
}
private static bool IsSafeChar(int c) => c >= 0x20 && c < 0xFFFE;
public string ReadUTF8StringSafe(int fixedLength)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadUInt32()
{
string s;
uint value;
if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0'))
if (Position < First.Length)
{
s = Utility.UTF8.GetString(span.Length > fixedLength ? span.Slice(0, fixedLength) : span);
if (!BinaryPrimitives.TryReadUInt32BigEndian(First.Slice(Position), out value))
{
// Not enough space. Split the spans
return (uint)((ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte());
}
}
else if (!BinaryPrimitives.TryReadUInt32BigEndian(Second.Slice(Position - First.Length), out value))
{
throw new OutOfMemoryException();
}
Position += 4;
return value;
}
private static bool IsSafeChar(ushort c) => c >= 0x20 && c < 0xFFFE;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString<T>(Encoding encoding, bool safeString = false, int fixedLength = -1) where T : struct, IEquatable<T>
{
int sizeT = Unsafe.SizeOf<T>();
if (sizeT > 2)
{
throw new InvalidConstraintException("ReadString only accepts byte, sbyte, char, short, and ushort as a constraint");
}
bool isFixedLength = fixedLength > -1;
var remaining = Remaining;
int size;
// Not fixed length
if (isFixedLength)
{
size = fixedLength * sizeT;
if (fixedLength > Remaining)
{
throw new OutOfMemoryException();
}
}
else
{
var size = Math.Min(m_Reader.Remaining, fixedLength);
s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, size).ToArray());
m_Reader.Advance(size);
size = remaining - (remaining & (sizeT - 1));
}
var sb = new StringBuilder(s.Length);
Span<byte> span;
int index;
for (var i = 0; i < s.Length; ++i)
if (Position < First.Length)
{
if (IsSafeChar(s[i]))
// Find terminator
index = MemoryMarshal
.Cast<byte, T>(First.Slice(Position, Math.Min(size, First.Length)))
.IndexOf(default(T)) * sizeT;
if (index < 0)
{
sb.Append(s[i]);
remaining = size - First.Length;
// We don't have a terminator, but a fixed size to the end of the first span, so stop there
if (remaining <= 0)
{
index = First.Length;
}
else
{
index = MemoryMarshal
.Cast<byte, T>(Second.Slice(0, remaining))
.IndexOf(default(T)) * sizeT;
int secondLength = index < 0 ? remaining : index;
int length = First.Length + secondLength;
// Assume no strings should be too long for the stack
Span<byte> bytes = stackalloc byte[length];
First.Slice(Position).CopyTo(bytes);
Second.Slice(0, secondLength).CopyTo(bytes.Slice(First.Length));
Position += length;
return GetString(bytes, encoding, safeString);
}
}
}
return sb.ToString();
}
public string ReadUTF8StringSafe()
{
string s;
if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0'))
{
s = Utility.UTF8.GetString(span);
span = First.Slice(Position, index);
}
else
{
s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray());
m_Reader.Advance(m_Reader.Remaining);
}
span = Second.Slice( Position - First.Length, Math.Min(remaining, size));
index = MemoryMarshal.Cast<byte, T>(span).IndexOf(default(T)) * sizeT;
var sb = new StringBuilder(s.Length);
for (var i = 0; i < s.Length; ++i)
{
if (IsSafeChar(s[i]))
if (index >= 0)
{
sb.Append(s[i]);
span = span.Slice(0, index);
}
}
return sb.ToString();
Position += isFixedLength ? size : index;
return GetString(span, encoding, safeString);
}
public string ReadUTF8String() =>
Utility.UTF8.GetString(
m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0')
? span
: m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray()
);
public string ReadString()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetString(Span<byte> span, Encoding encoding, bool safeString = false)
{
var sb = new StringBuilder();
string s = encoding.GetString(span);
while (m_Reader.TryRead(out var c))
if (!safeString)
{
sb.Append((char)c);
return s;
}
return sb.ToString();
}
ReadOnlySpan<char> chars = s.AsSpan();
public string ReadStringSafe()
{
var sb = new StringBuilder();
StringBuilder stringBuilder = null;
while (m_Reader.TryRead(out var c))
for (int i = 0, last = 0; i < chars.Length; i++)
{
if (IsSafeChar(c))
if (!IsSafeChar(chars[i]) || stringBuilder != null && i == chars.Length - 1)
{
sb.Append((char)c);
(stringBuilder ??= new StringBuilder()).Append(chars.Slice(last, i - last));
last = i + 1; // Skip the unsafe char
}
}
return sb.ToString();
return stringBuilder?.ToString() ?? s;
}
public string ReadUnicodeStringSafe(int fixedLength)
{
var sb = new StringBuilder();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadLittleUniSafe(int fixedLength) => ReadString<char>(Utility.UnicodeLE, true, fixedLength);
while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadLittleUniSafe() => ReadString<char>(Utility.UnicodeLE, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadLittleUni(int fixedLength) => ReadString<char>(Utility.UnicodeLE, false, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadLittleUni() => ReadString<char>(Utility.UnicodeLE);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadBigUniSafe(int fixedLength) => ReadString<char>(Utility.Unicode, true, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadBigUniSafe() => ReadString<char>(Utility.Unicode, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadBigUni(int fixedLength) => ReadString<char>(Utility.Unicode, false, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadBigUni() => ReadString<char>(Utility.Unicode);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadUTF8Safe(int fixedLength) => ReadString<byte>(Utility.UTF8, true, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadUTF8Safe() => ReadString<byte>(Utility.UTF8, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadUTF8() => ReadString<byte>(Utility.UTF8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadAsciiSafe(int fixedLength) => ReadString<byte>(Encoding.ASCII, true, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadAsciiSafe() => ReadString<byte>(Encoding.ASCII, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadAscii(int fixedLength) => ReadString<byte>(Encoding.ASCII, false, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadAscii() => ReadString<byte>(Encoding.ASCII);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Seek(int offset, SeekOrigin origin) =>
Position = origin switch
{
if (IsSafeChar(c))
{
sb.Append((char)c);
}
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength * 2);
}
return sb.ToString();
}
public string ReadUnicodeString(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0)
{
sb.Append((char)c);
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength * 2);
}
return sb.ToString();
}
public string ReadStringSafe(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0)
{
if (IsSafeChar(c))
{
sb.Append((char)c);
}
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength);
}
return sb.ToString();
}
public string ReadString(int fixedLength)
{
var sb = new StringBuilder();
while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0)
{
sb.Append((char)c);
}
if (fixedLength > 0)
{
m_Reader.Advance(fixedLength);
}
return sb.ToString();
}
SeekOrigin.Begin => offset,
SeekOrigin.End => Length - offset,
_ => Position + offset // Current
};
}
}

View file

@ -32,8 +32,7 @@ namespace Server.Network
private static readonly MessageLocalized[] m_Cache_CliLocCmp = new MessageLocalized[5000];
public MessageLocalized(
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name,
string args
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name, string args
) : base(0xC1)
{
name ??= "";

View file

@ -0,0 +1,356 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Pipe.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Server.Network
{
public interface IPipeTask<T> : INotifyCompletion
{
public IPipeTask<T> GetAwaiter();
public bool IsCompleted { get; }
public T GetResult();
public void OnCompleted(Action continuation);
}
public class Pipe<T>
{
public struct Result<T>
{
public ArraySegment<T>[] Buffer { get; }
public bool IsClosed { get; set; }
public int Length
{
get
{
var length = 0;
for (int i = 0; i < Buffer.Length; i++)
{
length += Buffer[i].Count;
}
return length;
}
}
public void CopyFrom(ReadOnlySpan<T> bytes)
{
var remaining = bytes.Length;
var offset = 0;
if (remaining == 0)
{
return;
}
for (int i = 0; i < Buffer.Length; i++)
{
var buffer = Buffer[i];
var sz = Math.Min(remaining, buffer.Count);
bytes.Slice(offset, sz).CopyTo(buffer);
remaining -= sz;
offset += sz;
if (remaining == 0)
{
return;
}
}
throw new OutOfMemoryException();
}
public Result(int segments)
{
IsClosed = false;
Buffer = new ArraySegment<T>[segments];
}
}
public class PipeWriter<T>
{
private readonly Pipe<T> _pipe;
public PipeWriter(Pipe<T> pipe) => _pipe = pipe;
public Result<T> GetAvailable()
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
var result = new Result<T>(2) { IsClosed = _pipe._closed };
if (read <= write)
{
var readZero = read == 0;
var sz = _pipe.Size - write - (readZero ? 1 : 0);
result.Buffer[0] = sz == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)write, (int)sz);
result.Buffer[1] = readZero ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, 0, (int)read - 1);
}
else
{
var sz = read - write - 1;
result.Buffer[0] = sz == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)write, (int)sz);
result.Buffer[1] = ArraySegment<T>.Empty;
}
return result;
}
public void Advance(uint bytes)
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
if (bytes == 0)
{
return;
}
if (bytes > _pipe.Size - 1)
{
throw new InvalidOperationException();
}
if (read <= write)
{
if (bytes > read + _pipe.Size - write - 1)
{
throw new InvalidOperationException();
}
var sz = Math.Min(bytes, _pipe.Size - write);
write += sz;
if (write > _pipe.Size - 1)
{
write = 0;
}
bytes -= sz;
if (bytes > 0)
{
if (bytes >= read)
{
throw new InvalidOperationException();
}
write = bytes;
}
}
else
{
if (bytes > read - write - 1)
{
throw new InvalidOperationException();
}
write += bytes;
}
// It's never valid to advance the write pointer to become equal to
// the read pointer. Check that here.
if (write == read)
{
throw new InvalidOperationException("Write index equals read index after advance");
}
_pipe._writeIdx = write;
}
public void Close()
{
_pipe._closed = true;
Flush();
}
public void Flush()
{
var waiting = _pipe._awaitBeginning;
if (!waiting)
{
return;
}
Action continuation;
do
{
continuation = _pipe._readerContinuation;
} while (continuation == null);
_pipe._readerContinuation = null;
_pipe._awaitBeginning = false;
ThreadPool.UnsafeQueueUserWorkItem(state => continuation(), true);
}
}
public class PipeReader<T> : IPipeTask<Result<T>>
{
private readonly Pipe<T> _pipe;
internal PipeReader(Pipe<T> pipe) => _pipe = pipe;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint GetRemaining()
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
if (read <= write)
{
return write - read;
}
return write + _pipe.Size - read;
}
public Result<T> TryRead()
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
var result = new Result<T>(2) { IsClosed = _pipe._closed };
if (read <= write)
{
result.Buffer[0] = write - read == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)read, (int)(write - read));
result.Buffer[1] = ArraySegment<T>.Empty;
}
else
{
result.Buffer[0] = _pipe.Size - read == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, (int)read, (int)(_pipe.Size - read));
result.Buffer[1] = write == 0 ? ArraySegment<T>.Empty : new ArraySegment<T>(_pipe._buffer, 0, (int)write);
}
return result;
}
public IPipeTask<Result<T>> Read()
{
if (_pipe._awaitBeginning)
{
throw new Exception("Double await on reader");
}
return this;
}
public void Advance(uint bytes)
{
var read = _pipe._readIdx;
var write = _pipe._writeIdx;
if (read <= write)
{
if (bytes > write - read)
{
throw new InvalidOperationException();
}
read += bytes;
}
else
{
var sz = Math.Min(bytes, _pipe.Size - read);
read += sz;
if (read > _pipe.Size - 1)
{
read = 0;
}
bytes -= sz;
if (bytes > 0)
{
if (bytes > write)
{
throw new InvalidOperationException();
}
read = bytes;
}
}
_pipe._readIdx = read;
}
#region Awaitable
// The following makes it possible to await the reader. Do not use any of this directly.
public IPipeTask<Result<T>> GetAwaiter() => this;
public bool IsCompleted
{
get
{
if (GetRemaining() > 0)
{
return true;
}
_pipe._awaitBeginning = true;
return false;
}
}
public Result<T> GetResult() => TryRead();
public void OnCompleted(Action continuation) => _pipe._readerContinuation = continuation;
#endregion
}
private readonly T[] _buffer;
private volatile uint _writeIdx;
private volatile uint _readIdx;
private bool _closed;
public PipeWriter<T> Writer { get; }
public PipeReader<T> Reader { get; }
public uint Size => (uint)_buffer.Length;
public Pipe(T[] buf)
{
_buffer = buf;
_writeIdx = 0;
_readIdx = 0;
_closed = false;
Writer = new PipeWriter<T>(this);
Reader = new PipeReader<T>(this);
}
#region Awaitable
private volatile bool _awaitBeginning;
private volatile Action _readerContinuation;
#endregion
}
}

View file

@ -1,91 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ServerConnectionHandler.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.Extensions.Logging;
namespace Server.Network
{
public class ServerConnectionHandler : ConnectionHandler
{
private readonly ILogger<ServerConnectionHandler> _logger;
private readonly IMessagePumpService _messagePumpService;
public ServerConnectionHandler(
IMessagePumpService messagePumpService,
ILogger<ServerConnectionHandler> logger
)
{
_messagePumpService = messagePumpService;
_logger = logger;
}
public override async Task OnConnectedAsync(ConnectionContext connection)
{
if (!VerifySocket(connection))
{
Release(connection);
return;
}
var ns = new NetState(connection);
TcpServer.Instances.Add(ns);
Console.WriteLine($"Client: {ns}: Connected. [{TcpServer.Instances.Count} Online]");
await ns.ProcessIncoming(_messagePumpService).ConfigureAwait(false);
}
private static bool VerifySocket(ConnectionContext connection)
{
try
{
var args = new SocketConnectEventArgs(connection);
EventSink.InvokeSocketConnect(args);
return args.AllowConnection;
}
catch (Exception ex)
{
NetState.TraceException(ex);
return false;
}
}
private static void Release(ConnectionContext connection)
{
try
{
connection.Abort(new ConnectionAbortedException("Failed socket verification."));
}
catch (Exception ex)
{
NetState.TraceException(ex);
}
try
{
// TODO: Is this needed?
connection.DisposeAsync();
}
catch (Exception ex)
{
NetState.TraceException(ex);
}
}
}
}

View file

@ -14,79 +14,211 @@
*************************************************************************/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Sockets;
using Server.Exceptions;
namespace Server.Network
{
public static class TcpServer
{
private static IPAddress[] m_ListeningAddresses;
private static NetworkState m_NetworkState = NetworkState.ResumeState;
// Make this thread safe
public static List<NetState> Instances { get; } = new List<NetState>();
// Sanity. 256 * 1024 * 5000 = ~1.3GB of ram
public static int MaxConnections { get; set; } = 5000;
public static IWebHostBuilder CreateWebHostBuilder(string[] args = null) =>
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "True")
.ConfigureServices(services => { services.AddSingleton<IMessagePumpService>(new MessagePumpService()); })
.UseKestrel(
options =>
{
foreach (var ipep in ServerConfiguration.Listeners)
{
options.Listen(ipep, builder => { builder.UseConnectionHandler<ServerConnectionHandler>(); });
m_ListeningAddresses = GetListeningAddresses(ipep);
DisplayListener(ipep);
}
private const long _listenerErrorMessageDelay = 10000; // 10 seconds
private static long _nextMaximumSocketsReachedMessage;
// Webservices here
}
)
.UseLibuv()
.UseStartup<ServerStartup>();
// AccountLoginReject BadComm
private static readonly byte[] socketRejected = { 0x82, 0xFF };
public static IPAddress[] GetListeningAddresses(IPEndPoint ipep)
public static IPEndPoint[] ListeningAddresses { get; private set; }
public static TcpListener[] Listeners { get; private set; }
public static List<NetState> Instances { get; } = new List<NetState>(128);
public static ConcurrentQueue<NetState> m_ConnectedQueue = new ConcurrentQueue<NetState>();
public static void Configure()
{
if (m_ListeningAddresses != null)
{
return m_ListeningAddresses;
}
var list = new List<IPAddress>();
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
{
var properties = adapter.GetIPProperties();
foreach (var unicast in properties.UnicastAddresses)
{
if (ipep.AddressFamily == unicast.Address.AddressFamily)
{
list.Add(unicast.Address);
}
}
}
return list.ToArray();
MaxConnections = ServerConfiguration.GetOrUpdateSetting("tcpServer.maxConnections", MaxConnections);
}
private static void DisplayListener(IPEndPoint ipep)
public static void Start()
{
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
HashSet<IPEndPoint> listeningAddresses = new HashSet<IPEndPoint>();
List<TcpListener> listeners = new List<TcpListener>();
foreach (var ipep in ServerConfiguration.Listeners)
{
foreach (var ip in m_ListeningAddresses)
var listener = CreateListener(ipep);
if (listener == null)
{
Console.WriteLine("Listening: {0}:{1}", ip, ipep.Port);
continue;
}
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
{
listeningAddresses.UnionWith(GetListeningAddresses(ipep));
}
else
{
listeningAddresses.Add(ipep);
}
listeners.Add(listener);
listener.BeginAcceptingSockets();
}
else
foreach (var ipep in listeningAddresses)
{
Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port);
}
ListeningAddresses = listeningAddresses.ToArray();
Listeners = listeners.ToArray();
}
public static IEnumerable<IPEndPoint> GetListeningAddresses(IPEndPoint ipep) =>
NetworkInterface.GetAllNetworkInterfaces().SelectMany(adapter =>
adapter.GetIPProperties().UnicastAddresses
.Where(uip => ipep.AddressFamily == uip.Address.AddressFamily)
.Select(uip => new IPEndPoint(uip.Address, ipep.Port))
);
public static TcpListener CreateListener(IPEndPoint ipep)
{
var listener = new TcpListener(ipep);
listener.Server.ExclusiveAddressUse = false;
try
{
listener.Start(8);
return listener;
}
catch (SocketException se)
{
// WSAEADDRINUSE
if (se.ErrorCode == 10048)
{
Console.WriteLine("Listener: {0}:{1}: Failed (In Use)", ipep.Address, ipep.Port);
}
// WSAEADDRNOTAVAIL
else if (se.ErrorCode == 10049)
{
Console.WriteLine("Listener {0}:{1}: Failed (Unavailable)", ipep.Address, ipep.Port);
}
else
{
Console.WriteLine("Listener Exception:");
Console.WriteLine(se);
}
}
return null;
}
/**
* Pauses the TcpServer and stops accepting new sockets.
* This is thread-safe without using locks.
*/
public static void Pause()
{
NetworkState.Pause(ref m_NetworkState);
}
/**
* Resumes accepting sockets on the TcpServer.
* This is thread-safe using a lock on the listeners
*/
public static void Resume()
{
if (!NetworkState.Resume(ref m_NetworkState))
{
return;
}
lock (Listeners)
{
foreach (var listener in Listeners)
{
listener.BeginAcceptingSockets();
}
}
}
public static void Slice()
{
int count = 0;
while (count++ < 250)
{
if (!m_ConnectedQueue.TryDequeue(out var ns))
{
break;
}
Instances.Add(ns);
ns.WriteConsole("Connected. [{0} Online]", Instances.Count);
}
}
private static async void BeginAcceptingSockets(this TcpListener listener)
{
while (true)
{
if (m_NetworkState.Paused)
{
return;
}
Socket socket = null;
try
{
socket = await listener.AcceptSocketAsync();
if (Instances.Count >= MaxConnections)
{
socket.Send(socketRejected, SocketFlags.None);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
throw new MaxConnectionsException();
}
var args = new SocketConnectEventArgs(socket);
EventSink.InvokeSocketConnect(args);
if (!args.AllowConnection)
{
socket.Send(socketRejected, SocketFlags.None);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}
catch (MaxConnectionsException)
{
var ticks = Core.TickCount;
if (_nextMaximumSocketsReachedMessage <= ticks)
{
var ipep = (IPEndPoint)socket!.RemoteEndPoint;
Console.WriteLine("Listener {0}:{1}: Failed (Maximum connections reached)", ipep.Address, ipep.Port);
_nextMaximumSocketsReachedMessage = ticks + _listenerErrorMessageDelay;
}
}
catch
{
// ignored
}
var ns = new NetState(socket);
m_ConnectedQueue.Enqueue(ns);
ns.Start();
}
}
}
}

View file

@ -1,42 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace System.IO.Pipelines
{
public class DuplexPipe : IDuplexPipe
{
public DuplexPipe(PipeReader reader, PipeWriter writer)
{
Input = reader;
Output = writer;
}
public PipeReader Input { get; }
public PipeWriter Output { get; }
public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions)
{
var input = new Pipe(inputOptions);
var output = new Pipe(outputOptions);
var transportToApplication = new DuplexPipe(output.Reader, input.Writer);
var applicationToTransport = new DuplexPipe(input.Reader, output.Writer);
return new DuplexPipePair(applicationToTransport, transportToApplication);
}
// This class exists to work around issues with value tuple on .NET Framework
public readonly struct DuplexPipePair
{
public IDuplexPipe Transport { get; }
public IDuplexPipe Application { get; }
public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application)
{
Transport = transport;
Application = application;
}
}
}
}

View file

@ -12,23 +12,10 @@
</PropertyGroup>
<Target Name="CleanPub" AfterTargets="Clean">
<Message Text="Removing distribution files..." />
<Delete Files="..\..\Distribution\libuv.dylib" ContinueOnError="true" />
<Delete Files="..\..\Distribution\libuv.so" ContinueOnError="true" />
<Delete Files="..\..\Distribution\libuv.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\zlib.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\libz.dylib" ContinueOnError="true" />
<Delete Files="..\..\Distribution\libz.so" ContinueOnError="true" />
<Delete Files="..\..\Distribution\ZLib.Bindings.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.AspNetCore.Connections.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.AspNetCore.Http.Features.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.Extensions.Configuration.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.Extensions.DependencyInjection.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.Extensions.FileProviders.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.Extensions.Hosting.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.Extensions.Logging.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.Extensions.Options.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Microsoft.Extensions.Primitives.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\$(AssemblyName)" ContinueOnError="true" />
<Delete Files="..\..\Distribution\$(AssemblyName).dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\$(AssemblyName).dll.config" ContinueOnError="true" />
@ -37,17 +24,12 @@
<Delete Files="..\..\Distribution\$(AssemblyName).pdb" ContinueOnError="true" />
<Delete Files="..\..\Distribution\$(AssemblyName).runtimeconfig.dev.json" ContinueOnError="true" />
<Delete Files="..\..\Distribution\$(AssemblyName).runtimeconfig.json" ContinueOnError="true" />
<Delete Files="..\..\Distribution\System.IO.Pipelines.dll" ContinueOnError="true" />
</Target>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.2.31">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
<PackageReference Include="Zlib.Bindings" Version="1.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Analyze'">

View file

@ -1,6 +1,21 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: HexStringConverter.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
namespace Server.Misc
namespace Server
{
public class HexStringConverter
{

View file

@ -14,7 +14,7 @@ namespace Server
{
public static class Utility
{
private static Encoding m_UTF8, m_UTF8WithEncoding;
private static Encoding m_UTF8, m_UTF8WithEncoding, m_Unicode, m_UnicodeLE;
private static Dictionary<IPAddress, IPAddress> _ipAddressTable;
@ -103,6 +103,8 @@ namespace Server
public static Encoding UTF8 => m_UTF8 ??= new UTF8Encoding(false, false);
public static Encoding UTF8WithEncoding => m_UTF8WithEncoding ??= new UTF8Encoding(true, false);
public static Encoding Unicode => m_Unicode ??= new UnicodeEncoding(true, false, false);
public static Encoding UnicodeLE => m_UnicodeLE ??= new UnicodeEncoding(false, false, false);
public static void Separate(StringBuilder sb, string value, string separator)
{
@ -126,6 +128,11 @@ namespace Server
public static IPAddress Intern(IPAddress ipAddress)
{
if (ipAddress == null)
{
return null;
}
_ipAddressTable ??= new Dictionary<IPAddress, IPAddress>();
if (!_ipAddressTable.TryGetValue(ipAddress, out var interned))

View file

@ -5,7 +5,7 @@ using Server.Misc;
namespace Server
{
public class AccessRestrictions
public static class AccessRestrictions
{
public static void Initialize()
{
@ -16,7 +16,7 @@ namespace Server
{
try
{
var ip = ((IPEndPoint)e.Context.RemoteEndPoint).Address;
var ip = ((IPEndPoint)e.Connection.RemoteEndPoint).Address;
if (Firewall.IsBlocked(ip))
{

View file

@ -252,7 +252,7 @@ namespace Server.Misc
}
else
{
Console.WriteLine("Client: {0}: Deleting character {1} (0x{2:X})", state, index, m.Serial.Value);
state.WriteConsole("Deleting character {0} (0x{1:X})", index, m.Serial.Value);
acct.Comments.Add(new AccountComment("System", $"Character #{index + 1} {m} deleted by {state}"));

View file

@ -73,15 +73,15 @@ namespace Server.Engines.Chat
return;
}
var lang = reader.ReadStringSafe(4);
var lang = reader.ReadAsciiSafe(4);
int actionID = reader.ReadInt16();
var param = reader.ReadUnicodeString();
var param = reader.ReadBigUniSafe();
var handler = ChatActionHandlers.GetHandler(actionID);
if (handler == null)
{
Console.WriteLine("Client: {0}: Unknown chat action 0x{1:X}: {2}", state, actionID, param);
state.WriteConsole("Unknown chat action 0x{0:X}: {1}", actionID, param);
return;
}

View file

@ -295,7 +295,12 @@ namespace Server.Items
{
var door = doors[i];
if (door?.Open == true)
if (door == null)
{
continue;
}
if (door.Open)
{
return AddonFitResult.DoorsNotClosed;
}

View file

@ -378,8 +378,8 @@ namespace Server.Items
reader.Seek(4, SeekOrigin.Current); // Skip flags and page count
var title = reader.ReadStringSafe(60);
var author = reader.ReadStringSafe(30);
var title = reader.ReadAsciiSafe(60);
var author = reader.ReadAsciiSafe(30);
book.Title = Utility.FixHtml(title);
book.Author = Utility.FixHtml(author);
@ -404,7 +404,7 @@ namespace Server.Items
return;
}
var title = reader.ReadUTF8StringSafe(titleLength);
var title = reader.ReadUTF8Safe(titleLength);
int authorLength = reader.ReadUInt16();
@ -413,7 +413,7 @@ namespace Server.Items
return;
}
var author = reader.ReadUTF8StringSafe(authorLength);
var author = reader.ReadUTF8Safe(authorLength);
book.Title = Utility.FixHtml(title);
book.Author = Utility.FixHtml(author);
@ -452,7 +452,7 @@ namespace Server.Items
for (var j = 0; j < lineCount; ++j)
{
if ((lines[j] = reader.ReadUTF8StringSafe()).Length >= 80)
if ((lines[j] = reader.ReadUTF8Safe()).Length >= 80)
{
return;
}

View file

@ -41,7 +41,7 @@ namespace Server.Engines.Mahjong
RegisterSubCommand(0x18, MoveDealerIndicator);
}
public static void OnPacket(NetState state, PacketReader reader)
public static void OnPacket(NetState state,PacketReader reader)
{
var game = World.FindItem(reader.ReadUInt32()) as MahjongGame;

View file

@ -315,7 +315,7 @@ namespace Server.Items
}
}
var subject = reader.ReadUTF8StringSafe(reader.ReadByte());
var subject = reader.ReadUTF8Safe(reader.ReadByte());
if (subject.Length == 0)
{
@ -331,7 +331,7 @@ namespace Server.Items
for (var i = 0; i < lines.Length; ++i)
{
lines[i] = reader.ReadUTF8StringSafe(reader.ReadByte());
lines[i] = reader.ReadUTF8Safe(reader.ReadByte());
}
board.PostMessage(from, thread, subject, lines);

View file

@ -171,7 +171,7 @@ namespace Server.Misc
{
if (ns.Connection != null)
{
Console.WriteLine("Client: {0}: Disconnecting, bad version", ns);
ns.WriteConsole("Disconnecting, bad version");
ns.Dispose();
}
}

View file

@ -29,7 +29,8 @@ namespace Server.Misc
public static void OnFastWalk(FastWalkEventArgs e)
{
e.Blocked = true; // disallow this fastwalk
Console.WriteLine("Client: {0}: Fast movement detected (name={1})", e.NetState, e.NetState.Mobile.Name);
var state = e.NetState;
state.WriteConsole("Fast movement detected (name={0})", state.Mobile.Name);
}
}
}

View file

@ -163,7 +163,7 @@ namespace Server
info.ScreenDepth = reader.ReadInt32();
info.DXMajor = reader.ReadInt16();
info.DXMinor = reader.ReadInt16();
info.VCDescription = reader.ReadUnicodeStringLESafe(64);
info.VCDescription = reader.ReadLittleUniSafe(64);
info.VCVendorID = reader.ReadInt32();
info.VCDeviceID = reader.ReadInt32();
info.VCMemory = reader.ReadInt32();
@ -171,8 +171,8 @@ namespace Server
info.ClientsRunning = reader.ReadByte();
info.ClientsInstalled = reader.ReadByte();
info.PartialInstalled = reader.ReadByte();
info.Language = reader.ReadUnicodeStringLESafe(4);
info.Unknown = reader.ReadStringSafe(64);
info.Language = reader.ReadLittleUniSafe(4);
info.Unknown = reader.ReadAsciiSafe(64);
info.TimeReceived = DateTime.UtcNow;

View file

@ -1,9 +1,8 @@
using System;
using Server.Network;
namespace Server.Misc
{
public class ProtocolExtensions
public static class ProtocolExtensions
{
private static readonly PacketHandler[] m_Handlers = new PacketHandler[0x100];
@ -17,15 +16,8 @@ namespace Server.Misc
m_Handlers[packetID] = new PacketHandler(packetID, 0, ingame, onReceive);
}
public static PacketHandler GetHandler(int packetID)
{
if (packetID >= 0 && packetID < m_Handlers.Length)
{
return m_Handlers[packetID];
}
return null;
}
public static PacketHandler GetHandler(int packetID) =>
packetID >= 0 && packetID < m_Handlers.Length ? m_Handlers[packetID] : null;
public static void DecodeBundledPacket(NetState state, PacketReader reader)
{
@ -37,9 +29,8 @@ namespace Server.Misc
{
if (ph.Ingame && state.Mobile == null)
{
Console.WriteLine(
"Client: {0}: Sent ingame packet (0xF0x{1:X2}) before having been attached to a mobile",
state,
state.WriteConsole(
"Sent ingame packet (0xF0x{0:X2}) before having been attached to a mobile",
packetID
);
state.Dispose();

View file

@ -5634,105 +5634,90 @@ namespace Server.Mobiles
protected override void OnTick()
{
if (DateTime.UtcNow >= m_NextHourlyCheck)
{
m_NextHourlyCheck = DateTime.UtcNow + TimeSpan.FromHours(1.0);
}
else
if (DateTime.UtcNow < m_NextHourlyCheck)
{
return;
}
m_NextHourlyCheck = DateTime.UtcNow + TimeSpan.FromHours(1.0);
var toRelease = new List<BaseCreature>();
// added array for wild creatures in house regions to be removed
var toRemove = new List<BaseCreature>();
Parallel.ForEach(
World.Mobiles.Values,
m =>
foreach (var m in World.Mobiles.Values)
{
if (!(m is BaseCreature c))
{
if (!(m is BaseCreature c))
{
return;
}
continue;
}
if (c is BaseMount mount && mount.Rider != null)
{
mount.OwnerAbandonTime = DateTime.MinValue;
return;
}
if (c is BaseMount mount && mount.Rider != null)
{
mount.OwnerAbandonTime = DateTime.MinValue;
continue;
}
if (c.IsDeadPet)
{
var owner = c.ControlMaster;
if (c.IsDeadPet)
{
var owner = c.ControlMaster;
if (!c.IsStabled && (owner?.Deleted != false || owner.Map != c.Map ||
!owner.InRange(c, 12) || !c.CanSee(owner) || !c.InLOS(owner)))
if (!c.IsStabled && (owner?.Deleted != false || owner.Map != c.Map ||
!owner.InRange(c, 12) || !c.CanSee(owner) || !c.InLOS(owner)))
{
if (c.OwnerAbandonTime == DateTime.MinValue)
{
if (c.OwnerAbandonTime == DateTime.MinValue)
{
c.OwnerAbandonTime = DateTime.UtcNow;
}
else if (c.OwnerAbandonTime + c.BondingAbandonDelay <= DateTime.UtcNow)
{
lock (toRemove)
{
toRemove.Add(c);
}
}
c.OwnerAbandonTime = DateTime.UtcNow;
}
else
else if (c.OwnerAbandonTime + c.BondingAbandonDelay <= DateTime.UtcNow)
{
c.OwnerAbandonTime = DateTime.MinValue;
}
}
else if (c.Controlled && c.Commandable)
{
c.OwnerAbandonTime = DateTime.MinValue;
if (c.Map != Map.Internal)
{
c.Loyalty -= BaseCreature.MaxLoyalty / 10;
if (c.Loyalty < BaseCreature.MaxLoyalty / 10)
{
c.Say(1043270, c.Name); // * ~1_NAME~ looks around desperately *
c.PlaySound(c.GetIdleSound());
}
if (c.Loyalty <= 0)
{
lock (toRelease)
{
toRelease.Add(c);
}
}
}
}
// added lines to check if a wild creature in a house region has to be removed or not
if (!c.Controlled && !c.IsStabled && (c.Region.IsPartOf<HouseRegion>() && c.CanBeDamaged() ||
c.RemoveIfUntamed && c.Spawner == null))
{
c.RemoveStep++;
if (c.RemoveStep >= 20)
{
lock (toRemove)
{
toRemove.Add(c);
}
toRemove.Add(c);
}
}
else
{
c.RemoveStep = 0;
c.OwnerAbandonTime = DateTime.MinValue;
}
}
);
else if (c.Controlled && c.Commandable)
{
c.OwnerAbandonTime = DateTime.MinValue;
if (c.Map != Map.Internal)
{
c.Loyalty -= BaseCreature.MaxLoyalty / 10;
if (c.Loyalty < BaseCreature.MaxLoyalty / 10)
{
c.Say(1043270, c.Name); // * ~1_NAME~ looks around desperately *
c.PlaySound(c.GetIdleSound());
}
if (c.Loyalty <= 0)
{
toRelease.Add(c);
}
}
}
// added lines to check if a wild creature in a house region has to be removed or not
if (!c.Controlled && !c.IsStabled && (c.Region.IsPartOf<HouseRegion>() && c.CanBeDamaged() ||
c.RemoveIfUntamed && c.Spawner == null))
{
c.RemoveStep++;
if (c.RemoveStep >= 20)
{
toRemove.Add(c);
}
}
else
{
c.RemoveStep = 0;
}
}
// TODO: Parallelize this
foreach (var c in toRelease)
{
c.Say(1043255, c.Name); // ~1_NAME~ appears to have decided that is better off without a master!

View file

@ -373,8 +373,7 @@ namespace Server.Multis
4 => new GenericHouseDoor(facing, 0x6B5, 0xEA, 0xF1),
5 => new GenericHouseDoor(facing, 0x6C5, 0xEC, 0xF3),
6 => new GenericHouseDoor(facing, 0x6D5, 0xEA, 0xF1),
7 => new GenericHouseDoor(facing, 0x6E5, 0xEA, 0xF1),
_ => door
_ => new GenericHouseDoor(facing, 0x6E5, 0xEA, 0xF1),
};
}
else if (itemID >= 0x314 && itemID < 0x364)
@ -484,8 +483,7 @@ namespace Server.Multis
door = type switch
{
0 => new GenericHouseDoor(facing, 0x367B, 0xED, 0xF4),
1 => new GenericHouseDoor(facing, 0x368B, 0xEC, 0x3E7),
_ => door
_ => new GenericHouseDoor(facing, 0x368B, 0xEC, 0x3E7),
};
}
else if (itemID >= 0x409B && itemID < 0x40A3)
@ -1978,14 +1976,9 @@ namespace Server.Multis
public void OnRevised()
{
lock (this)
{
Revision = ++Foundation.LastRevision;
m_PacketCache?.Release();
m_PacketCache = null;
}
Revision = ++Foundation.LastRevision;
m_PacketCache?.Release();
m_PacketCache = null;
}
public void SendGeneralInfoTo(NetState state)
@ -1995,19 +1988,18 @@ namespace Server.Multis
public void SendDetailedInfoTo(NetState state)
{
if (state != null)
if (state == null)
{
lock (this)
{
if (m_PacketCache == null)
{
DesignStateDetailed.SendDetails(state, Foundation, this);
}
else
{
state.Send(m_PacketCache);
}
}
return;
}
if (m_PacketCache == null)
{
DesignStateDetailed.SendDetails(state, Foundation, this);
}
else
{
state.Send(m_PacketCache);
}
}
@ -2444,22 +2436,8 @@ namespace Server.Multis
{
public const int MaxItemsPerStairBuffer = 750;
private static readonly ConcurrentQueue<SendQueueEntry> m_SendQueue;
private static readonly AutoResetEvent m_Sync;
private readonly byte[][] m_PlaneBuffers;
private readonly bool[] m_PlaneUsed = new bool[9];
private readonly byte[] m_PrimBuffer = new byte[4];
private readonly byte[][] m_StairBuffers;
static DesignStateDetailed()
{
m_SendQueue = new ConcurrentQueue<SendQueueEntry>();
m_Sync = new AutoResetEvent(false);
Task.Run(ProcessCompression);
}
public DesignStateDetailed(uint serial, int revision, int xMin, int yMin, int xMax, int yMax, MultiTileEntry[] tiles)
: base(0xD8)
@ -2479,26 +2457,26 @@ namespace Server.Multis
var width = xMax - xMin + 1;
var height = yMax - yMin + 1;
m_PlaneBuffers = new byte[9][];
var planeBuffers = new byte[9][];
for (var i = 0; i < m_PlaneBuffers.Length; ++i)
for (var i = 0; i < planeBuffers.Length; ++i)
{
m_PlaneBuffers[i] = ArrayPool<byte>.Shared.Rent(0x400);
planeBuffers[i] = ArrayPool<byte>.Shared.Rent(0x400);
}
m_StairBuffers = new byte[6][];
var stairBuffers = new byte[6][];
for (var i = 0; i < m_StairBuffers.Length; ++i)
for (var i = 0; i < stairBuffers.Length; ++i)
{
m_StairBuffers[i] = ArrayPool<byte>.Shared.Rent(MaxItemsPerStairBuffer * 5);
stairBuffers[i] = ArrayPool<byte>.Shared.Rent(MaxItemsPerStairBuffer * 5);
}
Clear(m_PlaneBuffers[0], width * height * 2);
Clear(planeBuffers[0], width * height * 2);
for (var i = 0; i < 4; ++i)
{
Clear(m_PlaneBuffers[1 + i], (width - 1) * (height - 2) * 2);
Clear(m_PlaneBuffers[5 + i], width * (height - 1) * 2);
Clear(planeBuffers[1 + i], (width - 1) * (height - 2) * 2);
Clear(planeBuffers[5 + i], width * (height - 1) * 2);
}
var totalStairsUsed = 0;
@ -2532,7 +2510,7 @@ namespace Server.Multis
default:
{
var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer;
var stairBuffer = m_StairBuffers[stairBufferIndex];
var stairBuffer = stairBuffers[stairBufferIndex];
var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5;
@ -2541,7 +2519,7 @@ namespace Server.Multis
stairBuffer[byteIndex++] = (byte)mte.OffsetX;
stairBuffer[byteIndex++] = (byte)mte.OffsetY;
stairBuffer[byteIndex++] = (byte)mte.OffsetZ;
stairBuffer[byteIndex] = (byte)mte.OffsetZ;
++totalStairsUsed;
@ -2570,7 +2548,7 @@ namespace Server.Multis
if (x < 0 || y < 0 || y >= size || index + 1 >= 0x400)
{
var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer;
var stairBuffer = m_StairBuffers[stairBufferIndex];
var stairBuffer = stairBuffers[stairBufferIndex];
var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5;
@ -2579,27 +2557,27 @@ namespace Server.Multis
stairBuffer[byteIndex++] = (byte)mte.OffsetX;
stairBuffer[byteIndex++] = (byte)mte.OffsetY;
stairBuffer[byteIndex++] = (byte)mte.OffsetZ;
stairBuffer[byteIndex] = (byte)mte.OffsetZ;
++totalStairsUsed;
}
else
{
m_PlaneUsed[plane] = true;
m_PlaneBuffers[plane][index] = (byte)(mte.ItemId >> 8);
m_PlaneBuffers[plane][index + 1] = (byte)mte.ItemId;
planeBuffers[plane][index] = (byte)(mte.ItemId >> 8);
planeBuffers[plane][index + 1] = (byte)mte.ItemId;
}
}
var planeCount = 0;
var m_DeflatedBuffer = ArrayPool<byte>.Shared.Rent(0x2000);
var deflatedBuffer = ArrayPool<byte>.Shared.Rent(0x2000);
for (var i = 0; i < m_PlaneBuffers.Length; ++i)
for (var i = 0; i < planeBuffers.Length; ++i)
{
if (!m_PlaneUsed[i])
{
ArrayPool<byte>.Shared.Return(m_PlaneBuffers[i]);
ArrayPool<byte>.Shared.Return(planeBuffers[i]);
continue;
}
@ -2620,11 +2598,11 @@ namespace Server.Multis
size = width * (height - 1) * 2;
}
var inflatedBuffer = m_PlaneBuffers[i];
var inflatedBuffer = planeBuffers[i];
var deflatedLength = m_DeflatedBuffer.Length;
var deflatedLength = deflatedBuffer.Length;
var ce = Zlib.Pack(
m_DeflatedBuffer,
deflatedBuffer,
ref deflatedLength,
inflatedBuffer,
size,
@ -2642,7 +2620,7 @@ namespace Server.Multis
Write((byte)size);
Write((byte)deflatedLength);
Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)));
Write(m_DeflatedBuffer, 0, deflatedLength);
Write(deflatedBuffer, 0, deflatedLength);
totalLength += 4 + deflatedLength;
ArrayPool<byte>.Shared.Return(inflatedBuffer);
@ -2663,11 +2641,11 @@ namespace Server.Multis
var size = count * 5;
var inflatedBuffer = m_StairBuffers[i];
var inflatedBuffer = stairBuffers[i];
var deflatedLength = m_DeflatedBuffer.Length;
var deflatedLength = deflatedBuffer.Length;
var ce = Zlib.Pack(
m_DeflatedBuffer,
deflatedBuffer,
ref deflatedLength,
inflatedBuffer,
size,
@ -2685,17 +2663,17 @@ namespace Server.Multis
Write((byte)size);
Write((byte)deflatedLength);
Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)));
Write(m_DeflatedBuffer, 0, deflatedLength);
Write(deflatedBuffer, 0, deflatedLength);
totalLength += 4 + deflatedLength;
}
for (var i = 0; i < m_StairBuffers.Length; ++i)
for (var i = 0; i < stairBuffers.Length; ++i)
{
ArrayPool<byte>.Shared.Return(m_StairBuffers[i]);
ArrayPool<byte>.Shared.Return(stairBuffers[i]);
}
ArrayPool<byte>.Shared.Return(m_DeflatedBuffer);
ArrayPool<byte>.Shared.Return(deflatedBuffer);
Stream.Seek(15, SeekOrigin.Begin);
@ -2749,106 +2727,28 @@ namespace Server.Multis
}
}
public static void ProcessCompression()
{
while (!Core.Closing)
{
m_Sync.WaitOne();
var count = m_SendQueue.Count;
while (count > 0 && m_SendQueue.TryDequeue(out var sqe))
{
try
{
Packet p;
lock (sqe.m_Root)
{
p = sqe.m_Root.PacketCache;
}
if (p == null)
{
p = new DesignStateDetailed(
sqe.m_Serial,
sqe.m_Revision,
sqe.m_xMin,
sqe.m_yMin,
sqe.m_xMax,
sqe.m_yMax,
sqe.m_Tiles
);
p.SetStatic();
lock (sqe.m_Root)
{
if (sqe.m_Revision == sqe.m_Root.Revision)
{
sqe.m_Root.PacketCache = p;
}
}
}
sqe.m_NetState.Send(p);
}
catch (Exception e)
{
Console.WriteLine(e);
try
{
using var op = new StreamWriter("dsd_exceptions.txt", true);
op.WriteLine(e);
}
catch
{
// ignored
}
}
finally
{
count = m_SendQueue.Count;
}
}
}
}
public static void SendDetails(NetState ns, HouseFoundation house, DesignState state)
{
m_SendQueue.Enqueue(new SendQueueEntry(ns, house, state));
var p = state.PacketCache;
m_Sync.Set();
}
private class SendQueueEntry
{
public readonly NetState m_NetState;
public readonly int m_Revision;
public readonly DesignState m_Root;
public readonly uint m_Serial;
public readonly MultiTileEntry[] m_Tiles;
public readonly int m_xMax;
public readonly int m_xMin;
public readonly int m_yMax;
public readonly int m_yMin;
public SendQueueEntry(NetState ns, HouseFoundation foundation, DesignState state)
if (p == null)
{
m_NetState = ns;
m_Serial = foundation.Serial;
m_Revision = state.Revision;
m_Root = state;
var mcl = state.Components;
p = new DesignStateDetailed(
house.Serial,
state.Revision,
mcl.Min.X,
mcl.Min.Y,
mcl.Max.X,
mcl.Max.Y,
mcl.List
);
m_xMin = mcl.Min.X;
m_yMin = mcl.Min.Y;
m_xMax = mcl.Max.X;
m_yMax = mcl.Max.Y;
m_Tiles = mcl.List;
p.SetStatic();
state.PacketCache = p;
}
ns.Send(p);
}
}
}

View file

@ -12,19 +12,10 @@
<Delete Files="..\..\Distribution\Assemblies\Argon2.Bindings.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\BouncyCastle.Crypto.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\MailKit.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\Microsoft.AspNetCore.Connections.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\Microsoft.AspNetCore.Http.Features.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\Microsoft.Extensions.Configuration.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\Microsoft.Extensions.DependencyInjection.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\Microsoft.Extensions.FileProviders.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\Microsoft.Extensions.Hosting.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\Microsoft.Extensions.Logging.Abstractions.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\Microsoft.Extensions.Primitives.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\MimeKit.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\$(AssemblyName).dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\$(AssemblyName).deps.json" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\$(AssemblyName).pdb" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\System.IO.Pipelines.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\libargon2.dll" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\libargon2.dylib" ContinueOnError="true" />
<Delete Files="..\..\Distribution\Assemblies\libargon2.so" ContinueOnError="true" />
@ -37,12 +28,6 @@
<ProjectReference Include="..\Server\Server.csproj" Private="false" PrivateAssets="All" IncludeAssets="None">
<IncludeInPackage>false</IncludeInPackage>
</ProjectReference>
<!-- Transient package version resolution -->
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.5" />
<!-- Direct packages -->
<PackageReference Include="MailKit" Version="2.8.0" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.2.31">
<PrivateAssets>all</PrivateAssets>

View file

@ -1,4 +1,4 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "0.6.3"
"version": "0.7.0"
}