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:
parent
8603e31023
commit
369a27b800
47 changed files with 1780 additions and 1569 deletions
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
101
Projects/Server.Tests/Network/PacketReaderTests.cs
Normal file
101
Projects/Server.Tests/Network/PacketReaderTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
214
Projects/Server.Tests/Network/PipeTests.cs
Normal file
214
Projects/Server.Tests/Network/PipeTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Projects/Server/Exceptions/MaxConnectionsException.cs
Normal file
26
Projects/Server/Exceptions/MaxConnectionsException.cs
Normal 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")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
38
Projects/Server/Network/NetworkState.cs
Normal file
38
Projects/Server/Network/NetworkState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ??= "";
|
||||
|
|
|
|||
356
Projects/Server/Network/Pipe.cs
Normal file
356
Projects/Server/Network/Pipe.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}"));
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue