diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 08b49e256..5e8c8ad9b 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "modernuoschemagenerator": { - "version": "2.12.20", + "version": "2.13.0", "commands": [ "ModernUOSchemaGenerator" ] diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c2f1c3b2f..1c9ea98a9 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - name: Install .NET 9 + - name: Install .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json @@ -54,20 +54,11 @@ jobs: - container: ubuntu:jammy name: Ubuntu 22 packageManager: apt - - container: ubuntu:focal - name: Ubuntu 20 - packageManager: apt - container: debian:bookworm name: Debian 12 packageManager: apt - - container: debian:bullseye - name: Debian 11 - packageManager: apt - - container: fedora:39 - name: Fedora 39 - packageManager: dnf - - container: fedora:40 - name: Fedora 40 + - container: fedora:42 + name: Fedora 42 packageManager: dnf - container: quay.io/centos/centos:stream9 name: CentOS 9 Stream @@ -86,7 +77,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - name: Install .NET 9 + - name: Install .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 0e8334e59..aa02e4489 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -14,7 +14,7 @@ jobs: with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - name: Install .NET 9 + - name: Install .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json diff --git a/Directory.Build.props b/Directory.Build.props index ae71e92d4..57f39a926 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,9 +3,9 @@ Kamron Batman ModernUO - 2019-2024 - net9.0 - 13 + 2019-2025 + net10.0 + 14 true true NU1603 @@ -64,9 +64,9 @@ - + - 3.7.115 + 3.9.50 all diff --git a/Projects/Server.Tests/Server.Tests.csproj b/Projects/Server.Tests/Server.Tests.csproj index 038575c90..0f913dd48 100644 --- a/Projects/Server.Tests/Server.Tests.csproj +++ b/Projects/Server.Tests/Server.Tests.csproj @@ -5,9 +5,9 @@ Server.Tests - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Projects/Server.Tests/Tests/Buffers/SpanReaderTests.cs b/Projects/Server.Tests/Tests/Buffers/SpanReaderTests.cs new file mode 100644 index 000000000..9a68576c8 --- /dev/null +++ b/Projects/Server.Tests/Tests/Buffers/SpanReaderTests.cs @@ -0,0 +1,597 @@ +using System; +using System.Buffers; +using System.IO; +using Xunit; + +namespace Server.Tests; + +public class SpanReaderTests +{ + [Fact] + public void TestReadByte() + { + ReadOnlySpan buffer = [0x12, 0x34]; + var reader = new SpanReader(buffer); + + Assert.Equal(0x12, reader.ReadByte()); + Assert.Equal(0x34, reader.ReadByte()); + Assert.Equal(2, reader.Position); + } + + [Fact] + public void TestReadByteAtEnd() + { + Assert.Throws( + () => + { + ReadOnlySpan buffer = [0x12]; + var reader = new SpanReader(buffer); + reader.ReadByte(); + reader.ReadByte(); + } + ); + } + + [Fact] + public void TestReadBoolean() + { + ReadOnlySpan buffer = [0, 1, 2, 255]; + var reader = new SpanReader(buffer); + + Assert.False(reader.ReadBoolean()); + Assert.True(reader.ReadBoolean()); + Assert.True(reader.ReadBoolean()); + Assert.True(reader.ReadBoolean()); + Assert.Equal(4, reader.Position); + } + + [Fact] + public void TestReadSByte() + { + ReadOnlySpan buffer = [0xFF, 0x7F]; + var reader = new SpanReader(buffer); + + Assert.Equal(-1, reader.ReadSByte()); + Assert.Equal(127, reader.ReadSByte()); + Assert.Equal(2, reader.Position); + } + + [Fact] + public void TestReadInt16BigEndian() + { + ReadOnlySpan buffer = [0x12, 0x34]; + var reader = new SpanReader(buffer); + + Assert.Equal(0x1234, reader.ReadInt16()); + Assert.Equal(2, reader.Position); + } + + [Fact] + public void TestReadInt16LittleEndian() + { + ReadOnlySpan buffer = [0x34, 0x12]; + var reader = new SpanReader(buffer); + + Assert.Equal(0x1234, reader.ReadInt16LE()); + Assert.Equal(2, reader.Position); + } + + [Fact] + public void TestReadInt16AtEnd() + { + Assert.Throws( + () => + { + ReadOnlySpan buffer = [0x12]; + var reader = new SpanReader(buffer); + reader.ReadInt16(); + } + ); + } + + [Fact] + public void TestReadUInt16BigEndian() + { + ReadOnlySpan buffer = [0x12, 0x34]; + var reader = new SpanReader(buffer); + + Assert.Equal((ushort)0x1234, reader.ReadUInt16()); + Assert.Equal(2, reader.Position); + } + + [Fact] + public void TestReadUInt16LittleEndian() + { + ReadOnlySpan buffer = [0x34, 0x12]; + var reader = new SpanReader(buffer); + + Assert.Equal((ushort)0x1234, reader.ReadUInt16LE()); + Assert.Equal(2, reader.Position); + } + + [Fact] + public void TestReadInt32BigEndian() + { + ReadOnlySpan buffer = [0x12, 0x34, 0x56, 0x78]; + var reader = new SpanReader(buffer); + + Assert.Equal(0x12345678, reader.ReadInt32()); + Assert.Equal(4, reader.Position); + } + + [Fact] + public void TestReadInt32AtEnd() + { + Assert.Throws( + () => + { + ReadOnlySpan buffer = [0x12, 0x34, 0x56]; + var reader = new SpanReader(buffer); + reader.ReadInt32(); + } + ); + } + + [Fact] + public void TestReadUInt32BigEndian() + { + ReadOnlySpan buffer = [0x12, 0x34, 0x56, 0x78]; + var reader = new SpanReader(buffer); + + Assert.Equal(0x12345678u, reader.ReadUInt32()); + Assert.Equal(4, reader.Position); + } + + [Fact] + public void TestReadUInt32LittleEndian() + { + ReadOnlySpan buffer = [0x78, 0x56, 0x34, 0x12]; + var reader = new SpanReader(buffer); + + Assert.Equal(0x12345678u, reader.ReadUInt32LE()); + Assert.Equal(4, reader.Position); + } + + [Fact] + public void TestReadInt64BigEndian() + { + ReadOnlySpan buffer = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]; + var reader = new SpanReader(buffer); + + Assert.Equal(0x123456789ABCDEF0L, reader.ReadInt64()); + Assert.Equal(8, reader.Position); + } + + [Fact] + public void TestReadInt64AtEnd() + { + Assert.Throws( + () => + { + ReadOnlySpan buffer = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE]; + var reader = new SpanReader(buffer); + reader.ReadInt64(); + } + ); + } + + [Fact] + public void TestReadUInt64BigEndian() + { + ReadOnlySpan buffer = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]; + var reader = new SpanReader(buffer); + + Assert.Equal(0x123456789ABCDEF0UL, reader.ReadUInt64()); + Assert.Equal(8, reader.Position); + } + + [Fact] + public void TestReadAsciiString() + { + ReadOnlySpan buffer = "Hello"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadAscii(); + + Assert.Equal("Hello", result); + Assert.Equal(5, reader.Position); + } + + [Fact] + public void TestReadAsciiStringWithNull() + { + ReadOnlySpan buffer = "Hel\0o"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadAscii(); + + Assert.Equal("Hel", result); + Assert.Equal(4, reader.Position); + } + + [Fact] + public void TestReadAsciiStringFixedLength() + { + ReadOnlySpan buffer = "Hello\0\0\0\0\0"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadAscii(10); + + Assert.Equal("Hello", result); + Assert.Equal(10, reader.Position); + } + + [Fact] + public void TestReadAsciiStringFixedLengthTooLarge() + { + Assert.Throws( + () => + { + ReadOnlySpan buffer = "Hel"u8; + var reader = new SpanReader(buffer); + reader.ReadAscii(10); + } + ); + } + + [Fact] + public void TestReadAsciiSafe() + { + ReadOnlySpan buffer = [(byte)'H', (byte)'e', 0xFF, (byte)'l', (byte)'o']; + var reader = new SpanReader(buffer); + + var result = reader.ReadAsciiSafe(); + + Assert.Equal("He?lo", result); + Assert.Equal(5, reader.Position); + } + + [Fact] + public void TestReadUTF8String() + { + ReadOnlySpan buffer = "Hello"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadUTF8(); + + Assert.Equal("Hello", result); + Assert.Equal(5, reader.Position); + } + + [Fact] + public void TestReadUTF8StringWithNull() + { + ReadOnlySpan buffer = "Hi\0Bye"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadUTF8(); + + Assert.Equal("Hi", result); + Assert.Equal(3, reader.Position); + } + + [Fact] + public void TestReadLittleUniString() + { + ReadOnlySpan buffer = "H\0i\0"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadLittleUni(); + + Assert.Equal("Hi", result); + Assert.Equal(4, reader.Position); + } + + [Fact] + public void TestReadLittleUniStringWithNull() + { + ReadOnlySpan buffer = "H\0i\0\0\0X\0"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadLittleUni(); + + Assert.Equal("Hi", result); + Assert.Equal(5, reader.Position); + } + + [Fact] + public void TestReadLittleUniStringFixedLength() + { + ReadOnlySpan buffer = "H\0i\0\0\0\0\0"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadLittleUni(4); + + Assert.Equal("Hi", result); + Assert.Equal(8, reader.Position); + } + + [Fact] + public void TestReadLittleUniSafe() + { + ReadOnlySpan buffer = [(byte)'H', 0, 0xFF, 0xD8, (byte)'i', 0]; + var reader = new SpanReader(buffer); + + var result = reader.ReadLittleUniSafe(); + + Assert.Equal("H\uFFFDi", result); + Assert.Equal(6, reader.Position); + } + + [Fact] + public void TestReadBigUniString() + { + ReadOnlySpan buffer = "\0H\0i"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadBigUni(); + + Assert.Equal("Hi", result); + Assert.Equal(4, reader.Position); + } + + [Fact] + public void TestReadBigUniStringWithNull() + { + ReadOnlySpan buffer = "\0H\0i\0\0\0X"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadBigUni(); + + Assert.Equal("Hi", result); + Assert.Equal(5, reader.Position); + } + + [Fact] + public void TestReadBigUniStringFixedLength() + { + ReadOnlySpan buffer = "\0H\0i\0\0\0\0"u8; + var reader = new SpanReader(buffer); + + var result = reader.ReadBigUni(4); + + Assert.Equal("Hi", result); + Assert.Equal(8, reader.Position); + } + + [Fact] + public void TestReadBigUniSafe() + { + ReadOnlySpan buffer = [0, (byte)'H', 0xD8, 0xFF, 0, (byte)'i']; + var reader = new SpanReader(buffer); + + var result = reader.ReadBigUniSafe(); + + Assert.Equal("H\uFFFDi", result); + Assert.Equal(6, reader.Position); + } + + [Fact] + public void TestSeekBegin() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03, 0x04, 0x05]; + var reader = new SpanReader(buffer); + + reader.ReadByte(); + reader.ReadByte(); + + var pos = reader.Seek(0, SeekOrigin.Begin); + + Assert.Equal(0, pos); + Assert.Equal(0, reader.Position); + Assert.Equal(0x01, reader.ReadByte()); + } + + [Fact] + public void TestSeekCurrent() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03, 0x04, 0x05]; + var reader = new SpanReader(buffer); + + reader.ReadByte(); + var pos = reader.Seek(2, SeekOrigin.Current); + + Assert.Equal(3, pos); + Assert.Equal(3, reader.Position); + Assert.Equal(0x04, reader.ReadByte()); + } + + [Fact] + public void TestSeekEnd() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03, 0x04, 0x05]; + var reader = new SpanReader(buffer); + + var pos = reader.Seek(-2, SeekOrigin.End); + + Assert.Equal(3, pos); + Assert.Equal(3, reader.Position); + Assert.Equal(0x04, reader.ReadByte()); + } + + [Fact] + public void TestSeekNegativeThrows() + { + Assert.Throws( + () => + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03]; + var reader = new SpanReader(buffer); + reader.Seek(-1, SeekOrigin.Begin); + } + ); + } + + [Fact] + public void TestSeekBeyondEndThrows() + { + Assert.Throws( + () => + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03]; + var reader = new SpanReader(buffer); + reader.Seek(10, SeekOrigin.Begin); + } + ); + } + + [Fact] + public void TestRead() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03, 0x04, 0x05]; + var reader = new SpanReader(buffer); + + Span dest = stackalloc byte[3]; + var bytesRead = reader.Read(dest); + + Assert.Equal(3, bytesRead); + Assert.Equal(3, reader.Position); + AssertThat.Equal(dest, [0x01, 0x02, 0x03]); + } + + [Fact] + public void TestReadPartial() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03]; + var reader = new SpanReader(buffer); + + Span dest = stackalloc byte[5]; + var bytesRead = reader.Read(dest); + + Assert.Equal(3, bytesRead); + Assert.Equal(3, reader.Position); + AssertThat.Equal(dest[..3], [0x01, 0x02, 0x03]); + } + + [Fact] + public void TestReadEmpty() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03]; + var reader = new SpanReader(buffer); + + Span dest = []; + var bytesRead = reader.Read(dest); + + Assert.Equal(0, bytesRead); + Assert.Equal(0, reader.Position); + } + + [Fact] + public void TestReadAtEnd() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03]; + var reader = new SpanReader(buffer); + + reader.Seek(3, SeekOrigin.Begin); + + Span dest = stackalloc byte[5]; + var bytesRead = reader.Read(dest); + + Assert.Equal(0, bytesRead); + Assert.Equal(3, reader.Position); + } + + [Fact] + public void TestLength() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03, 0x04, 0x05]; + var reader = new SpanReader(buffer); + + Assert.Equal(5, reader.Length); + } + + [Fact] + public void TestPosition() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03]; + var reader = new SpanReader(buffer); + + Assert.Equal(0, reader.Position); + + reader.ReadByte(); + Assert.Equal(1, reader.Position); + + reader.ReadUInt16(); + Assert.Equal(3, reader.Position); + } + + [Fact] + public void TestRemaining() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03, 0x04, 0x05]; + var reader = new SpanReader(buffer); + + Assert.Equal(5, reader.Remaining); + + reader.ReadByte(); + Assert.Equal(4, reader.Remaining); + + reader.ReadUInt16(); + Assert.Equal(2, reader.Remaining); + + reader.ReadUInt16(); + Assert.Equal(0, reader.Remaining); + } + + [Fact] + public void TestBuffer() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03]; + var reader = new SpanReader(buffer); + + var bufferProperty = reader.Buffer; + + Assert.Equal(3, bufferProperty.Length); + AssertThat.Equal(bufferProperty, buffer); + } + + [Fact] + public void TestReadStringEmptyFixedLength() + { + ReadOnlySpan buffer = [0x01, 0x02, 0x03]; + var reader = new SpanReader(buffer); + + var result = reader.ReadAscii(0); + + Assert.Equal("", result); + Assert.Equal(0, reader.Position); + } + + [Fact] + public void TestReadMultipleStringsWithNullTerminators() + { + ReadOnlySpan buffer = "AB\0CD\0EF"u8; + var reader = new SpanReader(buffer); + + Assert.Equal("AB", reader.ReadAscii()); + Assert.Equal("CD", reader.ReadAscii()); + Assert.Equal("EF", reader.ReadAscii()); + Assert.Equal(8, reader.Position); + } + + [Fact] + public void TestReadLittleUniOddByteCount() + { + // If buffer has odd number of bytes, the last byte should be ignored + ReadOnlySpan buffer = [(byte)'H', 0, (byte)'i', 0, 0xFF]; + var reader = new SpanReader(buffer); + + var result = reader.ReadLittleUni(); + + Assert.Equal("Hi", result); + Assert.Equal(4, reader.Position); + } + + [Fact] + public void TestReadBigUniOddByteCount() + { + // If buffer has odd number of bytes, the last byte should be ignored + ReadOnlySpan buffer = [0, (byte)'H', 0, (byte)'i', 0xFF]; + var reader = new SpanReader(buffer); + + var result = reader.ReadBigUni(); + + Assert.Equal("Hi", result); + Assert.Equal(4, reader.Position); + } +} diff --git a/Projects/Server.Tests/Tests/Buffers/SpanWriterTests.cs b/Projects/Server.Tests/Tests/Buffers/SpanWriterTests.cs index 0aeffe574..a0e7ed18c 100644 --- a/Projects/Server.Tests/Tests/Buffers/SpanWriterTests.cs +++ b/Projects/Server.Tests/Tests/Buffers/SpanWriterTests.cs @@ -1,63 +1,591 @@ using System; using System.Buffers; +using System.IO; using Xunit; -namespace Server.Tests +namespace Server.Tests; + +public class SpanWriterTests { - public class SpanWriterTests + [Fact] + public unsafe void TestSpanWriterResizes() { - [Fact] - public unsafe void TestSpanWriterResizes() - { - Span smallStack = stackalloc byte[8]; - using var writer = new SpanWriter(smallStack, true); - writer.Write(0x1024L); - writer.Write(0x1024L); + Span smallStack = stackalloc byte[8]; + var writer = new SpanWriter(smallStack, true); + writer.Write(0x1024L); + writer.Write(0x1024L); - var span = writer.RawBuffer; - fixed (byte* spanPtr = span) + var span = writer.RawBuffer; + fixed (byte* spanPtr = span) + { + fixed (byte* stackPtr = smallStack) { - fixed (byte* stackPtr = smallStack) - { - Assert.True(spanPtr != stackPtr); - } + Assert.True(spanPtr != stackPtr); } - - Assert.True(span.Length > smallStack.Length); } - [Fact] - public unsafe void TestSpanWriterOnlyStackAlloc() - { - Span smallStack = stackalloc byte[8]; - using var writer = new SpanWriter(smallStack, true); - writer.Write(0x1024L); + Assert.True(span.Length > smallStack.Length); + writer.Dispose(); + } - var span = writer.RawBuffer; - fixed (byte* spanPtr = span) + [Fact] + public unsafe void TestSpanWriterOnlyStackAlloc() + { + Span smallStack = stackalloc byte[8]; + var writer = new SpanWriter(smallStack, true); + writer.Write(0x1024L); + + var span = writer.RawBuffer; + fixed (byte* spanPtr = span) + { + fixed (byte* stackPtr = smallStack) { - fixed (byte* stackPtr = smallStack) - { - Assert.True(spanPtr == stackPtr); - } + Assert.True(spanPtr == stackPtr); } - - Assert.True(span.Length == smallStack.Length); - AssertThat.Equal(smallStack, stackalloc byte[] { 0, 0, 0, 0, 0, 0, 0x10, 0x24 }); } - [Fact] - public void TestSpanWriterNoResizeThrows() - { - Assert.Throws( - () => - { - Span smallStack = stackalloc byte[8]; - using var writer = new SpanWriter(smallStack); - writer.Write(0x1024L); - writer.Write(0x1024L); - } - ); - } + Assert.Equal(8, span.Length); + AssertThat.Equal(smallStack, [0, 0, 0, 0, 0, 0, 0x10, 0x24]); + } + + [Fact] + public void TestSpanWriterNoResizeThrows() + { + Assert.Throws( + () => + { + Span smallStack = stackalloc byte[8]; + var writer = new SpanWriter(smallStack); + writer.Write(0x1024L); + writer.Write(0x1024L); + } + ); + } + + [Fact] + public void TestWriteBool() + { + Span buffer = stackalloc byte[2]; + var writer = new SpanWriter(buffer); + + writer.Write(true); + writer.Write(false); + + Assert.Equal(2, writer.Position); + AssertThat.Equal(writer.Span, [1, 0]); + } + + [Fact] + public void TestWriteByte() + { + Span buffer = stackalloc byte[2]; + var writer = new SpanWriter(buffer); + + writer.Write((byte)0x12); + writer.Write((byte)0x34); + + Assert.Equal(2, writer.Position); + AssertThat.Equal(writer.Span, [0x12, 0x34]); + } + + [Fact] + public void TestWriteSByte() + { + Span buffer = stackalloc byte[2]; + var writer = new SpanWriter(buffer); + + writer.Write((sbyte)-1); + writer.Write((sbyte)127); + + Assert.Equal(2, writer.Position); + AssertThat.Equal(writer.Span, [0xFF, 0x7F]); + } + + [Fact] + public void TestWriteInt16BigEndian() + { + Span buffer = stackalloc byte[2]; + var writer = new SpanWriter(buffer); + + writer.Write((short)0x1234); + + Assert.Equal(2, writer.Position); + AssertThat.Equal(writer.Span, [0x12, 0x34]); + } + + [Fact] + public void TestWriteInt16LittleEndian() + { + Span buffer = stackalloc byte[2]; + var writer = new SpanWriter(buffer); + + writer.WriteLE((short)0x1234); + + Assert.Equal(2, writer.Position); + AssertThat.Equal(writer.Span, [0x34, 0x12]); + } + + [Fact] + public void TestWriteUInt16BigEndian() + { + Span buffer = stackalloc byte[2]; + var writer = new SpanWriter(buffer); + + writer.Write((ushort)0x1234); + + Assert.Equal(2, writer.Position); + AssertThat.Equal(writer.Span, [0x12, 0x34]); + } + + [Fact] + public void TestWriteUInt16LittleEndian() + { + Span buffer = stackalloc byte[2]; + var writer = new SpanWriter(buffer); + + writer.WriteLE((ushort)0x1234); + + Assert.Equal(2, writer.Position); + AssertThat.Equal(writer.Span, [0x34, 0x12]); + } + + [Fact] + public void TestWriteInt32BigEndian() + { + Span buffer = stackalloc byte[4]; + var writer = new SpanWriter(buffer); + + writer.Write(0x12345678); + + Assert.Equal(4, writer.Position); + AssertThat.Equal(writer.Span, [0x12, 0x34, 0x56, 0x78]); + } + + [Fact] + public void TestWriteInt32LittleEndian() + { + Span buffer = stackalloc byte[4]; + var writer = new SpanWriter(buffer); + + writer.WriteLE(0x12345678); + + Assert.Equal(4, writer.Position); + AssertThat.Equal(writer.Span, [0x78, 0x56, 0x34, 0x12]); + } + + [Fact] + public void TestWriteUInt32BigEndian() + { + Span buffer = stackalloc byte[4]; + var writer = new SpanWriter(buffer); + + writer.Write(0x12345678u); + + Assert.Equal(4, writer.Position); + AssertThat.Equal(writer.Span, [0x12, 0x34, 0x56, 0x78]); + } + + [Fact] + public void TestWriteUInt32LittleEndian() + { + Span buffer = stackalloc byte[4]; + var writer = new SpanWriter(buffer); + + writer.WriteLE(0x12345678u); + + Assert.Equal(4, writer.Position); + AssertThat.Equal(writer.Span, [0x78, 0x56, 0x34, 0x12]); + } + + [Fact] + public void TestWriteInt64BigEndian() + { + Span buffer = stackalloc byte[8]; + var writer = new SpanWriter(buffer); + + writer.Write(0x123456789ABCDEF0L); + + Assert.Equal(8, writer.Position); + AssertThat.Equal(writer.Span, [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); + } + + [Fact] + public void TestWriteUInt64BigEndian() + { + Span buffer = stackalloc byte[8]; + var writer = new SpanWriter(buffer); + + writer.Write(0x123456789ABCDEF0UL); + + Assert.Equal(8, writer.Position); + AssertThat.Equal(writer.Span, [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); + } + + [Fact] + public void TestWriteSpan() + { + Span buffer = stackalloc byte[6]; + var writer = new SpanWriter(buffer); + + ReadOnlySpan data = [0x01, 0x02, 0x03]; + writer.Write(data); + writer.Write(data); + + Assert.Equal(6, writer.Position); + AssertThat.Equal(writer.Span, [0x01, 0x02, 0x03, 0x01, 0x02, 0x03]); + } + + [Fact] + public void TestWriteAsciiChar() + { + Span buffer = stackalloc byte[2]; + var writer = new SpanWriter(buffer); + + writer.WriteAscii('A'); + writer.WriteAscii('B'); + + Assert.Equal(2, writer.Position); + AssertThat.Equal(writer.Span, "AB"u8); + } + + [Fact] + public void TestWriteAsciiString() + { + Span buffer = stackalloc byte[5]; + var writer = new SpanWriter(buffer); + + writer.WriteAscii("Hello"); + + Assert.Equal(5, writer.Position); + AssertThat.Equal(writer.Span, "Hello"u8); + } + + [Fact] + public void TestWriteAsciiStringNull() + { + Span buffer = stackalloc byte[6]; + var writer = new SpanWriter(buffer); + + writer.WriteAsciiNull("Hello"); + + Assert.Equal(6, writer.Position); + AssertThat.Equal(writer.Span, "Hello\0"u8); + } + + [Fact] + public void TestWriteAsciiStringFixedLength() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.WriteAscii("Hello", 10); + + Assert.Equal(10, writer.Position); + AssertThat.Equal(writer.Span, "Hello\0\0\0\0\0"u8); + } + + [Fact] + public void TestWriteUTF8String() + { + Span buffer = stackalloc byte[5]; + var writer = new SpanWriter(buffer); + + writer.WriteUTF8("Hello"); + + Assert.Equal(5, writer.Position); + AssertThat.Equal(writer.Span, "Hello"u8); + } + + [Fact] + public void TestWriteUTF8StringNull() + { + Span buffer = stackalloc byte[6]; + var writer = new SpanWriter(buffer); + + writer.WriteUTF8Null("Hello"); + + Assert.Equal(6, writer.Position); + AssertThat.Equal(writer.Span, "Hello\0"u8); + } + + [Fact] + public void TestWriteLittleUniString() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.WriteLittleUni("Hello"); + + Assert.Equal(10, writer.Position); + AssertThat.Equal(writer.Span, "H\0e\0l\0l\0o\0"u8); + } + + [Fact] + public void TestWriteLittleUniStringNull() + { + Span buffer = stackalloc byte[12]; + var writer = new SpanWriter(buffer); + + writer.WriteLittleUniNull("Hello"); + + Assert.Equal(12, writer.Position); + AssertThat.Equal(writer.Span, "H\0e\0l\0l\0o\0\0\0"u8); + } + + [Fact] + public void TestWriteBigUniString() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.WriteBigUni("Hello"); + + Assert.Equal(10, writer.Position); + AssertThat.Equal(writer.Span, "\0H\0e\0l\0l\0o"u8); + } + + [Fact] + public void TestWriteBigUniStringNull() + { + Span buffer = stackalloc byte[12]; + var writer = new SpanWriter(buffer); + + writer.WriteBigUniNull("Hello"); + + Assert.Equal(12, writer.Position); + AssertThat.Equal(writer.Span, "\0H\0e\0l\0l\0o\0\0"u8); + } + + [Fact] + public void TestClear() + { + Span buffer = stackalloc byte[10]; + buffer.Fill(0xFF); + var writer = new SpanWriter(buffer); + + writer.Write((byte)0x01); + writer.Clear(5); + writer.Write((byte)0x02); + + Assert.Equal(7, writer.Position); + AssertThat.Equal(writer.Span, [0x01, 0, 0, 0, 0, 0, 0x02]); + } + + [Fact] + public void TestSeekBegin() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.Write((byte)0x01); + writer.Write((byte)0x02); + writer.Write((byte)0x03); + + var pos = writer.Seek(1, SeekOrigin.Begin); + + Assert.Equal(1, pos); + Assert.Equal(1, writer.Position); + Assert.Equal(3, writer.BytesWritten); + } + + [Fact] + public void TestSeekCurrent() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.Write((byte)0x01); + writer.Write((byte)0x02); + + var pos = writer.Seek(2, SeekOrigin.Current); + + Assert.Equal(4, pos); + Assert.Equal(4, writer.Position); + } + + [Fact] + public void TestSeekEnd() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.Write((byte)0x01); + writer.Write((byte)0x02); + writer.Write((byte)0x03); + + var pos = writer.Seek(-1, SeekOrigin.End); + + Assert.Equal(2, pos); + Assert.Equal(2, writer.Position); + } + + [Fact] + public void TestSeekNegativeThrows() + { + Assert.Throws( + () => + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + writer.Seek(-1, SeekOrigin.Begin); + } + ); + } + + [Fact] + public void TestSeekBeyondCapacityNoResizeThrows() + { + Assert.Throws( + () => + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + writer.Seek(20, SeekOrigin.Begin); + } + ); + } + + [Fact] + public void TestSeekBeyondCapacityWithResize() + { + var writer = new SpanWriter(10, true); + + var pos = writer.Seek(20, SeekOrigin.Begin); + + Assert.Equal(20, pos); + Assert.True(writer.Capacity >= 20); + writer.Dispose(); + } + + [Fact] + public void TestEnsureCapacityNoResize() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.EnsureCapacity(10); + Assert.Equal(10, writer.Capacity); + } + + [Fact] + public void TestEnsureCapacityNoResizeThrows() + { + Assert.Throws( + () => + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + writer.EnsureCapacity(20); + } + ); + } + + [Fact] + public void TestEnsureCapacityWithResize() + { + var writer = new SpanWriter(10, true); + + writer.EnsureCapacity(50); + + Assert.True(writer.Capacity >= 50); + writer.Dispose(); + } + + [Fact] + public void TestToSpanWithStackAlloc() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.Write((byte)0x01); + writer.Write((byte)0x02); + writer.Write((byte)0x03); + + using var owner = writer.ToSpan(); + + Assert.Equal(3, owner.Span.Length); + AssertThat.Equal(owner.Span, [0x01, 0x02, 0x03]); + } + + [Fact] + public void TestToSpanWithRentedBuffer() + { + var writer = new SpanWriter(10); + + writer.Write((byte)0x01); + writer.Write((byte)0x02); + writer.Write((byte)0x03); + + using var owner = writer.ToSpan(); + + Assert.Equal(3, owner.Span.Length); + AssertThat.Equal(owner.Span, [0x01, 0x02, 0x03]); + } + + [Fact] + public void TestToSpanEmpty() + { + var writer = new SpanWriter(10); + + using var owner = writer.ToSpan(); + + Assert.Equal(0, owner.Span.Length); + } + + [Fact] + public void TestBytesWritten() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + Assert.Equal(0, writer.BytesWritten); + + writer.Write((byte)0x01); + Assert.Equal(1, writer.BytesWritten); + + writer.Write((ushort)0x0203); + Assert.Equal(3, writer.BytesWritten); + + writer.Seek(1, SeekOrigin.Begin); + Assert.Equal(3, writer.BytesWritten); + + writer.Write((byte)0xFF); + Assert.Equal(3, writer.BytesWritten); + } + + [Fact] + public void TestCapacity() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + Assert.Equal(10, writer.Capacity); + } + + [Fact] + public void TestRawBuffer() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.Write((byte)0x01); + + var raw = writer.RawBuffer; + Assert.Equal(10, raw.Length); + Assert.Equal(0x01, raw[0]); + } + + [Fact] + public void TestSpan() + { + Span buffer = stackalloc byte[10]; + var writer = new SpanWriter(buffer); + + writer.Write((byte)0x01); + writer.Write((byte)0x02); + + var span = writer.Span; + Assert.Equal(2, span.Length); + AssertThat.Equal(span, [0x01, 0x02]); } } diff --git a/Projects/Server/Buffers/SpanReader.cs b/Projects/Server/Buffers/SpanReader.cs index 31c5fc51d..dd35e8702 100644 --- a/Projects/Server/Buffers/SpanReader.cs +++ b/Projects/Server/Buffers/SpanReader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SpanReader.cs * * * @@ -14,7 +14,6 @@ *************************************************************************/ using System.Buffers.Binary; -using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Text; @@ -45,7 +44,7 @@ public ref struct SpanReader { if (Position >= Length) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } return _buffer[Position++]; @@ -62,7 +61,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadInt16BigEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 2; @@ -74,7 +73,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadInt16LittleEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 2; @@ -86,7 +85,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadUInt16BigEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 2; @@ -98,7 +97,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadUInt16LittleEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 2; @@ -110,7 +109,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadInt32BigEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 4; @@ -122,7 +121,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadUInt32BigEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 4; @@ -134,7 +133,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadUInt32LittleEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 4; @@ -146,7 +145,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadInt64BigEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 8; @@ -158,7 +157,7 @@ public ref struct SpanReader { if (!BinaryPrimitives.TryReadUInt64BigEndian(_buffer[Position..], out var value)) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 8; @@ -173,9 +172,9 @@ public ref struct SpanReader return ""; } - int byteLength = encoding.GetByteLengthForEncoding(); + var byteLength = encoding.GetByteLengthForEncoding(); - bool isFixedLength = fixedLength > -1; + var isFixedLength = fixedLength > -1; var remaining = Remaining; int size; @@ -184,7 +183,7 @@ public ref struct SpanReader size = fixedLength * byteLength; if (size > Remaining) { - throw new OutOfMemoryException(); + throw new EndOfStreamException("Cannot read past the end of the buffer."); } } else @@ -255,46 +254,28 @@ public ref struct SpanReader [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Seek(int offset, SeekOrigin origin) { - Debug.Assert( - origin != SeekOrigin.End || offset <= 0, - "Attempting to seek to a position beyond capacity using SeekOrigin.End" - ); - - Debug.Assert( - origin != SeekOrigin.End || offset >= -_buffer.Length, - "Attempting to seek to a negative position using SeekOrigin.End" - ); - - Debug.Assert( - origin != SeekOrigin.Begin || offset >= 0, - "Attempting to seek to a negative position using SeekOrigin.Begin" - ); - - Debug.Assert( - origin != SeekOrigin.Begin || offset <= _buffer.Length, - "Attempting to seek to a position beyond the capacity using SeekOrigin.Begin" - ); - - Debug.Assert( - origin != SeekOrigin.Current || Position + offset >= 0, - "Attempting to seek to a negative position using SeekOrigin.Current" - ); - - Debug.Assert( - origin != SeekOrigin.Current || Position + offset <= _buffer.Length, - "Attempting to seek to a position beyond the capacity using SeekOrigin.Current" - ); - - return Position = Math.Max(0, origin switch + var newPosition = origin switch { SeekOrigin.Current => Position + offset, SeekOrigin.End => _buffer.Length + offset, _ => offset // Begin - }); + }; + + if (newPosition < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Seek operation would result in a negative position."); + } + + if (newPosition > _buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset), $"Cannot seek to position {newPosition} beyond buffer length {_buffer.Length}."); + } + + return Position = newPosition; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Read(Span bytes) + public int Read(scoped Span bytes) { if (bytes.Length == 0) { diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs index 4ce423c5b..ccdaddd38 100644 --- a/Projects/Server/Buffers/SpanWriter.cs +++ b/Projects/Server/Buffers/SpanWriter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SpanWriter.cs * * * @@ -14,7 +14,6 @@ *************************************************************************/ using System.Buffers.Binary; -using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -56,14 +55,14 @@ public ref struct SpanWriter public Span RawBuffer => _buffer; /** - * Converts the writer to a Span using a SpanOwner. - * If the buffer was stackalloc, it will be copied to a rented buffer. - * Otherwise the existing rented buffer is used. - * - * Note: - * Do not use the SpanWriter after calling this method. - * This method will effectively dispose of the SpanWriter and is therefore considered terminal. - */ + * Converts the writer to a Span using a SpanOwner. + * If the buffer was stackalloc, it will be copied to a rented buffer. + * Otherwise the existing rented buffer is used. + * + * Note: + * Do not use the SpanWriter after calling this method. + * This method will effectively dispose of the SpanWriter and is therefore considered terminal. + */ [MethodImpl(MethodImplOptions.AggressiveInlining)] public SpanOwner ToSpan() { @@ -117,11 +116,11 @@ public ref struct SpanWriter private void Grow(int additionalCapacity) { var newSize = Math.Max(BytesWritten + additionalCapacity, _buffer.Length * 2); - byte[] poolArray = STArrayPool.Shared.Rent(newSize); + var poolArray = STArrayPool.Shared.Rent(newSize); _buffer[..BytesWritten].CopyTo(poolArray); - byte[] toReturn = _arrayToReturnToPool; + var toReturn = _arrayToReturnToPool; _buffer = _arrayToReturnToPool = poolArray; if (toReturn != null) { @@ -136,7 +135,7 @@ public ref struct SpanWriter { if (!_resize) { - throw new OutOfMemoryException(); + throw new InvalidOperationException("Buffer is full and resizing is disabled."); } Grow(count); @@ -151,7 +150,7 @@ public ref struct SpanWriter { if (!_resize) { - throw new OutOfMemoryException(); + throw new InvalidOperationException("Buffer is full and resizing is disabled."); } Grow(capacity - BytesWritten); @@ -400,46 +399,25 @@ public ref struct SpanWriter [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Seek(int offset, SeekOrigin origin) { - Debug.Assert( - origin != SeekOrigin.End || _resize || offset <= 0, - "Attempting to seek to a position beyond capacity using SeekOrigin.End without resize" - ); - - Debug.Assert( - origin != SeekOrigin.End || offset >= -_buffer.Length, - - "Attempting to seek to a negative position using SeekOrigin.End" - ); - - Debug.Assert( - origin != SeekOrigin.Begin || offset >= 0, - "Attempting to seek to a negative position using SeekOrigin.Begin" - ); - - Debug.Assert( - origin != SeekOrigin.Begin || _resize || offset <= _buffer.Length, - "Attempting to seek to a position beyond the capacity using SeekOrigin.Begin without resize" - ); - - Debug.Assert( - origin != SeekOrigin.Current || _position + offset >= 0, - "Attempting to seek to a negative position using SeekOrigin.Current" - ); - - Debug.Assert( - origin != SeekOrigin.Current || _resize || _position + offset <= _buffer.Length, - "Attempting to seek to a position beyond the capacity using SeekOrigin.Current without resize" - ); - - var newPosition = Math.Max(0, origin switch + var newPosition = origin switch { SeekOrigin.Current => _position + offset, SeekOrigin.End => BytesWritten + offset, _ => offset // Begin - }); + }; + + if (newPosition < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Seek operation would result in a negative position."); + } if (newPosition > _buffer.Length) { + if (!_resize) + { + throw new InvalidOperationException($"Cannot seek to position {newPosition} beyond buffer capacity {_buffer.Length} when resizing is disabled."); + } + Grow(newPosition - _buffer.Length + 1); } @@ -449,7 +427,7 @@ public ref struct SpanWriter [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { - byte[] toReturn = _arrayToReturnToPool; + var toReturn = _arrayToReturnToPool; this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again if (toReturn != null) { @@ -478,7 +456,7 @@ public ref struct SpanWriter [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { - byte[] toReturn = _arrayToReturnToPool; + var toReturn = _arrayToReturnToPool; this = default; if (_length > 0) { diff --git a/Projects/Server/Random/BuiltInSecureRng.cs b/Projects/Server/Random/BuiltInSecureRng.cs deleted file mode 100644 index dec408338..000000000 --- a/Projects/Server/Random/BuiltInSecureRng.cs +++ /dev/null @@ -1,28 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: SecureRandom.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 . * - *************************************************************************/ - -using System; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; - -namespace Server; - -public static class BuiltInSecureRng -{ - public static RandomNumberGenerator Generator { get; } = RandomNumberGenerator.Create(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void NextBytes(Span buffer) => Generator.GetBytes(buffer); -} diff --git a/Projects/Server/Server.csproj b/Projects/Server/Server.csproj index 5e16cde20..d9da8166d 100644 --- a/Projects/Server/Server.csproj +++ b/Projects/Server/Server.csproj @@ -37,10 +37,10 @@ - + - + diff --git a/Projects/UOContent.Tests/UOContent.Tests.csproj b/Projects/UOContent.Tests/UOContent.Tests.csproj index 77064faa1..dca5343eb 100644 --- a/Projects/UOContent.Tests/UOContent.Tests.csproj +++ b/Projects/UOContent.Tests/UOContent.Tests.csproj @@ -4,9 +4,9 @@ Debug;Release;Analyze - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Projects/UOContent/Accounting/Security/AccountSecurity.cs b/Projects/UOContent/Accounting/Security/AccountSecurity.cs index 22860d370..355137ab7 100644 --- a/Projects/UOContent/Accounting/Security/AccountSecurity.cs +++ b/Projects/UOContent/Accounting/Security/AccountSecurity.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: AccountSecurity.cs * * * @@ -15,56 +15,55 @@ using System; -namespace Server.Accounting.Security -{ - public enum PasswordProtectionAlgorithm - { - // Obsolete algorithms from RunUO. These are not secure! - // They are included for password upgrades only. - None, - MD5, - SHA1, +namespace Server.Accounting.Security; - // Supported algorithms - SHA2, // ServUO compatibility - PBKDF2, - Argon2 // Recommended algorithm for real security. +public enum PasswordProtectionAlgorithm +{ + // Obsolete algorithms from RunUO. These are not secure! + // They are included for password upgrades only. + None, + MD5, + SHA1, + + // Supported algorithms + SHA2, // ServUO compatibility + PBKDF2, + Argon2 // Recommended algorithm for real security. +} + +public static class AccountSecurity +{ + public static PasswordProtectionAlgorithm CurrentAlgorithm { get; set; } + + public static IPasswordProtection CurrentPasswordProtection => GetPasswordProtection(CurrentAlgorithm); + + public static void Configure() + { + CurrentAlgorithm = + ServerConfiguration.GetOrUpdateSetting( + "accountSecurity.encryptionAlgorithm", + PasswordProtectionAlgorithm.Argon2 + ); + + if (CurrentAlgorithm < PasswordProtectionAlgorithm.SHA2) + { + throw new Exception($"Security: {CurrentAlgorithm} is obsolete and not secure. Do not use it."); + } } - public static class AccountSecurity + public static IPasswordProtection GetPasswordProtection(PasswordProtectionAlgorithm algorithm) { - public static PasswordProtectionAlgorithm CurrentAlgorithm { get; set; } - - public static IPasswordProtection CurrentPasswordProtection => GetPasswordProtection(CurrentAlgorithm); - - public static void Configure() + var passwordProtection = algorithm switch { - CurrentAlgorithm = - ServerConfiguration.GetOrUpdateSetting( - "accountSecurity.encryptionAlgorithm", - PasswordProtectionAlgorithm.Argon2 - ); + PasswordProtectionAlgorithm.MD5 => HashAlgorithmPasswordProtection.MD5Instance, + PasswordProtectionAlgorithm.SHA1 => HashAlgorithmPasswordProtection.SHA1Instance, + PasswordProtectionAlgorithm.SHA2 => HashAlgorithmPasswordProtection.SHA2Instance, + PasswordProtectionAlgorithm.PBKDF2 => PBKDF2PasswordProtection.Instance, + PasswordProtectionAlgorithm.Argon2 => Argon2PasswordProtection.Instance, + PasswordProtectionAlgorithm.None => throw new Exception("Do not use PasswordProtectionAlgorithm.None"), + _ => throw new Exception("No algorithm") + }; - if (CurrentAlgorithm < PasswordProtectionAlgorithm.SHA2) - { - throw new Exception($"Security: {CurrentAlgorithm} is obsolete and not secure. Do not use it."); - } - } - - public static IPasswordProtection GetPasswordProtection(PasswordProtectionAlgorithm algorithm) - { - var passwordProtection = algorithm switch - { - PasswordProtectionAlgorithm.MD5 => HashAlgorithmPasswordProtection.MD5Instance, - PasswordProtectionAlgorithm.SHA1 => HashAlgorithmPasswordProtection.SHA1Instance, - PasswordProtectionAlgorithm.SHA2 => HashAlgorithmPasswordProtection.SHA2Instance, - PasswordProtectionAlgorithm.PBKDF2 => PBKDF2PasswordProtection.Instance, - PasswordProtectionAlgorithm.Argon2 => Argon2PasswordProtection.Instance, - PasswordProtectionAlgorithm.None => throw new Exception("Do not use PasswordProtectionAlgorithm.None"), - _ => throw new Exception("No algorithm") - }; - - return passwordProtection; - } + return passwordProtection; } } diff --git a/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs b/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs index f49d2a040..6d3214a48 100644 --- a/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs +++ b/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Argon2PasswordProtection.cs * * * @@ -15,18 +15,17 @@ using System.Security.Cryptography; -namespace Server.Accounting.Security +namespace Server.Accounting.Security; + +public class Argon2PasswordProtection : IPasswordProtection { - public class Argon2PasswordProtection : IPasswordProtection - { - public static IPasswordProtection Instance = new Argon2PasswordProtection(); + public static IPasswordProtection Instance = new Argon2PasswordProtection(); - private readonly Argon2PasswordHasher m_PasswordHasher = new(rng: BuiltInSecureRng.Generator); + private readonly Argon2PasswordHasher m_PasswordHasher = new(rng: RandomNumberGenerator.Create()); - public string EncryptPassword(string plainPassword) => - m_PasswordHasher.Hash(plainPassword); + public string EncryptPassword(string plainPassword) => + m_PasswordHasher.Hash(plainPassword); - public bool ValidatePassword(string encryptedPassword, string plainPassword) => - m_PasswordHasher.Verify(encryptedPassword, plainPassword); - } + public bool ValidatePassword(string encryptedPassword, string plainPassword) => + m_PasswordHasher.Verify(encryptedPassword, plainPassword); } diff --git a/Projects/UOContent/Accounting/Security/HashAlgorithmPasswordProtection.cs b/Projects/UOContent/Accounting/Security/HashAlgorithmPasswordProtection.cs index 978adbbf8..f50888675 100644 --- a/Projects/UOContent/Accounting/Security/HashAlgorithmPasswordProtection.cs +++ b/Projects/UOContent/Accounting/Security/HashAlgorithmPasswordProtection.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: HashAlgorithmPasswordProtection.cs * * * @@ -17,24 +17,23 @@ using System; using System.Security.Cryptography; using Server.Text; -namespace Server.Accounting.Security +namespace Server.Accounting.Security; + +public class HashAlgorithmPasswordProtection : IPasswordProtection { - public class HashAlgorithmPasswordProtection : IPasswordProtection + public static IPasswordProtection MD5Instance = new HashAlgorithmPasswordProtection(MD5.Create()); + public static IPasswordProtection SHA1Instance = new HashAlgorithmPasswordProtection(SHA1.Create()); + public static IPasswordProtection SHA2Instance = new HashAlgorithmPasswordProtection(SHA512.Create()); + private readonly HashAlgorithm _hashAlgorithm; + + public HashAlgorithmPasswordProtection(HashAlgorithm hashAlgorithm) => _hashAlgorithm = hashAlgorithm; + + public string EncryptPassword(string plainPassword) { - public static IPasswordProtection MD5Instance = new HashAlgorithmPasswordProtection(MD5.Create()); - public static IPasswordProtection SHA1Instance = new HashAlgorithmPasswordProtection(SHA1.Create()); - public static IPasswordProtection SHA2Instance = new HashAlgorithmPasswordProtection(SHA512.Create()); - private readonly HashAlgorithm _hashAlgorithm; - - public HashAlgorithmPasswordProtection(HashAlgorithm hashAlgorithm) => _hashAlgorithm = hashAlgorithm; - - public string EncryptPassword(string plainPassword) - { - byte[] bytes = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length)).GetBytesAscii(); - return _hashAlgorithm.ComputeHash(bytes).ToHexString(); - } - - public bool ValidatePassword(string encryptedPassword, string plainPassword) => - EncryptPassword(plainPassword) == encryptedPassword; + byte[] bytes = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length)).GetBytesAscii(); + return _hashAlgorithm.ComputeHash(bytes).ToHexString(); } + + public bool ValidatePassword(string encryptedPassword, string plainPassword) => + EncryptPassword(plainPassword) == encryptedPassword; } diff --git a/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs b/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs index e9fd8b05f..74f999a3f 100644 --- a/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs +++ b/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PBKDF2PasswordProtection.cs * * * @@ -18,42 +18,43 @@ using System.Buffers.Binary; using System.Security.Cryptography; using Server.Text; -namespace Server.Accounting.Security +namespace Server.Accounting.Security; + +public class PBKDF2PasswordProtection : IPasswordProtection { - public class PBKDF2PasswordProtection : IPasswordProtection + private const ushort m_MinIterations = 1024; + private const ushort m_MaxIterations = 1536; + private const int m_SaltSize = 8; + private const int m_HashSize = 32; + private const int m_OutputSize = 2 + m_SaltSize + m_HashSize; + public static readonly IPasswordProtection Instance = new PBKDF2PasswordProtection(); + + public string EncryptPassword(string plainPassword) { - private const ushort m_MinIterations = 1024; - private const ushort m_MaxIterations = 1536; - private const int m_SaltSize = 8; - private const int m_HashSize = 32; - private const int m_OutputSize = 2 + m_SaltSize + m_HashSize; - public static readonly IPasswordProtection Instance = new PBKDF2PasswordProtection(); + Span output = stackalloc byte[m_OutputSize]; + var iterations = Utility.RandomMinMax(m_MinIterations, m_MaxIterations); + BinaryPrimitives.WriteUInt16LittleEndian(output[..2], (ushort)iterations); - public string EncryptPassword(string plainPassword) - { - Span output = stackalloc byte[m_OutputSize]; - var iterations = Utility.RandomMinMax(m_MinIterations, m_MaxIterations); - BinaryPrimitives.WriteUInt16LittleEndian(output[..2], (ushort)iterations); + var salt = output.Slice(2, m_SaltSize); + RandomNumberGenerator.Fill(salt); - var rfc2898 = new Rfc2898DeriveBytes(plainPassword, m_SaltSize, iterations, HashAlgorithmName.SHA256); - rfc2898.Salt.CopyTo(output.Slice(2, m_SaltSize)); - rfc2898.GetBytes(m_HashSize).CopyTo(output[(m_SaltSize + 2)..]); + var hash = output.Slice(2 + m_SaltSize, m_HashSize); + Rfc2898DeriveBytes.Pbkdf2(plainPassword, salt, hash, iterations, HashAlgorithmName.SHA256); - return output.ToHexString(); - } + return output.ToHexString(); + } - public bool ValidatePassword(string encryptedPassword, string plainPassword) - { - Span encryptedBytes = stackalloc byte[m_OutputSize]; - encryptedPassword.GetBytes(encryptedBytes); + public bool ValidatePassword(string encryptedPassword, string plainPassword) + { + Span encryptedBytes = stackalloc byte[m_OutputSize]; + encryptedPassword.GetBytes(encryptedBytes); - var iterations = BinaryPrimitives.ReadUInt16LittleEndian(encryptedBytes[..2]); - var salt = encryptedBytes.Slice(2, m_SaltSize); + var iterations = BinaryPrimitives.ReadUInt16LittleEndian(encryptedBytes[..2]); + var salt = encryptedBytes.Slice(2, m_SaltSize); - ReadOnlySpan hash = - new Rfc2898DeriveBytes(plainPassword, salt.ToArray(), iterations, HashAlgorithmName.SHA256).GetBytes(m_HashSize); + Span hash = stackalloc byte[m_HashSize]; + Rfc2898DeriveBytes.Pbkdf2(plainPassword, salt, hash, iterations, HashAlgorithmName.SHA256); - return hash.SequenceEqual(encryptedBytes[(m_SaltSize + 2)..]); - } + return hash.SequenceEqual(encryptedBytes[(m_SaltSize + 2)..]); } } diff --git a/Projects/UOContent/Engines/Plants/PlantSystem.cs b/Projects/UOContent/Engines/Plants/PlantSystem.cs index 61d017c0f..cbbd0cb9c 100644 --- a/Projects/UOContent/Engines/Plants/PlantSystem.cs +++ b/Projects/UOContent/Engines/Plants/PlantSystem.cs @@ -320,7 +320,10 @@ namespace Server.Engines.Plants } [SerializableFieldSaveFlag(17)] - private bool ShouldSerializeLeftSeeds() => _leftSeeds != 0; + private bool ShouldSerializeLeftSeeds() => _leftSeeds != 8; + + [SerializableFieldDefault(17)] + private int LeftSeedsDefaultValue() => 8; [SerializableProperty(18)] public int AvailableResources @@ -340,7 +343,10 @@ namespace Server.Engines.Plants } [SerializableFieldSaveFlag(19)] - private bool ShouldSerializeLeftResources() => _leftResources != 0; + private bool ShouldSerializeLeftResources() => _leftResources != 8; + + [SerializableFieldDefault(19)] + private int LeftResourcesDefaultValue() => 8; public void Reset(bool potions) { diff --git a/Projects/UOContent/Multis/Camps/BaseCamp.cs b/Projects/UOContent/Multis/Camps/BaseCamp.cs index 3b9cece2a..e3d10dd0b 100644 --- a/Projects/UOContent/Multis/Camps/BaseCamp.cs +++ b/Projects/UOContent/Multis/Camps/BaseCamp.cs @@ -23,6 +23,7 @@ public abstract partial class BaseCamp : BaseMulti private TimeSpan _decayDelay; private Timer _decayTimer; + private Timer _initTimer; public BaseCamp(int multiID) : base(multiID) { @@ -31,7 +32,7 @@ public abstract partial class BaseCamp : BaseMulti _decayDelay = TimeSpan.FromMinutes(30.0); RefreshDecay(true); - Timer.StartTimer(CheckAddComponents); + _initTimer = Timer.DelayCall(TimeSpan.Zero, CheckAddComponents); } public virtual int EventRange => 10; @@ -50,6 +51,8 @@ public abstract partial class BaseCamp : BaseMulti public void CheckAddComponents() { + _initTimer = null; + if (Deleted) { return; @@ -138,16 +141,16 @@ public abstract partial class BaseCamp : BaseMulti for (var i = 0; i < _items.Count; ++i) { - _items[i].Delete(); + _items[i]?.Delete(); } for (var i = 0; i < _mobiles.Count; ++i) { var mob = _mobiles[i]; - if (mob.CantWalk || (mob as BaseCreature)?.IsPrisoner == false) + if (mob != null && (mob.CantWalk || (mob as BaseCreature)?.IsPrisoner == false)) { - _mobiles[i].Delete(); + mob.Delete(); } } @@ -156,6 +159,9 @@ public abstract partial class BaseCamp : BaseMulti _decayTimer?.Stop(); _decayTimer = null; + + _initTimer?.Stop(); + _initTimer = null; } private void Deserialize(IGenericReader reader, int version) @@ -168,7 +174,20 @@ public abstract partial class BaseCamp : BaseMulti [AfterDeserialization] private void AfterDeserialization() { - RefreshDecay(false); + var remaining = _decayTime - Core.Now; + + if (remaining > TimeSpan.Zero) + { + _decayDelay = remaining; + RefreshDecay(false); + } + else + { + Timer.DelayCall(TimeSpan.Zero, Delete); + return; + } + + _initTimer = Timer.DelayCall(TimeSpan.Zero, CheckAddComponents); } } diff --git a/Projects/UOContent/UOContent.csproj b/Projects/UOContent/UOContent.csproj index bea34e002..4ca0561f8 100644 --- a/Projects/UOContent/UOContent.csproj +++ b/Projects/UOContent/UOContent.csproj @@ -39,16 +39,16 @@ false - - + + - - + + diff --git a/README.md b/README.md index 4108f12c9..385219433 100644 --- a/README.md +++ b/README.md @@ -15,38 +15,38 @@ ModernUO [![Discord](https://img.shields.io/discord/751317910504603701?logo=disc ## Requirements #### Supported Operating Systems -[![Windows 11/2019/2022/2025](https://img.shields.io/badge/-server%202025-3c78d5?labelColor=222222&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHJvbGU9ImltZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGl0bGU+V2luZG93czwvdGl0bGU+PHBhdGggZD0iTTAsMEgxMS4zNzdWMTEuMzcySDBaTTEyLjYyMywwSDI0VjExLjM3MkgxMi42MjNaTTAsMTIuNjIzSDExLjM3N1YyNEgwWm0xMi42MjMsMEgyNFYyNEgxMi42MjMiIGZpbGw9IiMzYzc4ZDUiLz48L3N2Zz4=)](https://www.microsoft.com/en-US/evalcenter/evaluate-windows-server-2022) -![MacOS 13+](https://img.shields.io/badge/-sequoia-222222?logo=apple&logoColor=white&labelColor=222222) +[![Windows 10/11/2012/2016/2019/2022/2025](https://img.shields.io/badge/-server%202025-3c78d5?labelColor=222222&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHJvbGU9ImltZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGl0bGU+V2luZG93czwvdGl0bGU+PHBhdGggZD0iTTAsMEgxMS4zNzdWMTEuMzcySDBaTTEyLjYyMywwSDI0VjExLjM3MkgxMi42MjNaTTAsMTIuNjIzSDExLjM3N1YyNEgwWm0xMi42MjMsMEgyNFYyNEgxMi42MjMiIGZpbGw9IiMzYzc4ZDUiLz48L3N2Zz4=)](https://www.microsoft.com/en-US/evalcenter/evaluate-windows-server-2022) +![MacOS 14+](https://img.shields.io/badge/-sonoma-222222?logo=apple&logoColor=white&labelColor=222222) [![Debian 12+](https://img.shields.io/badge/-bookworm-A81D33?logo=debian&logoColor=A81D33&labelColor=222222)](https://www.debian.org/distrib/) [![Ubuntu 22+ LTS](https://img.shields.io/badge/-22LTS-E95420?logo=ubuntu&logoColor=E95420&labelColor=222222)](https://ubuntu.com/download/server)
-[![Alpine 3.19+](https://img.shields.io/badge/-3.21-0D597F?logo=alpinelinux&logoColor=0D597F&labelColor=222222)](https://alpinelinux.org/downloads/) -[![Fedora 40+](https://img.shields.io/badge/-40-51a2da?logo=fedora&logoColor=51a2da&labelColor=222222)](https://getfedora.org/en/server/download/) -[![RedHat 8+](https://img.shields.io/badge/-8-BE0000?logo=redhat&logoColor=BE0000&labelColor=222222)](https://access.redhat.com/downloads) -[![CentOS Stream 8+](https://img.shields.io/badge/-stream_8-262577?logo=centos&logoColor=white&labelColor=222222)](https://www.centos.org/download/) -[![openSUSE 15.5+](https://img.shields.io/badge/-15.5-73BA25?logo=openSUSE&logoColor=73BA25&labelColor=222222)](https://get.opensuse.org/) -[![SUSE Enterprise 12 SP2+](https://img.shields.io/badge/-12%20SP2-0C322C?logo=suse&logoColor=30BA78&labelColor=222222)](https://www.suse.com/download/sles/) -[![Linux Mint 17+](https://img.shields.io/badge/-20-87CF3E?logo=linux%20mint&logoColor=87CF3E&labelColor=222222)](https://linuxmint.com/download.php) +[![Alpine 3.22+](https://img.shields.io/badge/-3.22-0D597F?logo=alpinelinux&logoColor=0D597F&labelColor=222222)](https://alpinelinux.org/downloads/) +[![Fedora 42+](https://img.shields.io/badge/-42-51a2da?logo=fedora&logoColor=51a2da&labelColor=222222)](https://getfedora.org/en/server/download/) +[![RedHat 9+](https://img.shields.io/badge/-9-BE0000?logo=redhat&logoColor=BE0000&labelColor=222222)](https://access.redhat.com/downloads) +[![CentOS Stream 9+](https://img.shields.io/badge/-stream_9-262577?logo=centos&logoColor=white&labelColor=222222)](https://www.centos.org/download/) +[![openSUSE 15.6+](https://img.shields.io/badge/-15.6-73BA25?logo=openSUSE&logoColor=73BA25&labelColor=222222)](https://get.opensuse.org/) +[![SUSE Enterprise 15 SP6](https://img.shields.io/badge/-15%20SP6-0C322C?logo=suse&logoColor=30BA78&labelColor=222222)](https://www.suse.com/download/sles/) +[![Linux Mint 21+](https://img.shields.io/badge/-21-87CF3E?logo=linux%20mint&logoColor=87CF3E&labelColor=222222)](https://linuxmint.com/download.php) [![Arch](https://img.shields.io/badge/-Arch-1793D1?logo=archlinux&logoColor=1793D1&labelColor=222222)](https://archlinux.org/download/) #### Required Frameworks ##### All Operating Systems -[![.NET](https://img.shields.io/badge/-9.0.0-5C2D91?logo=.NET&logoColor=white&labelColor=222222)](https://dotnet.microsoft.com/download/dotnet/9.0) +[![.NET](https://img.shields.io/badge/-10.0.0-5C2D91?logo=.NET&logoColor=white&labelColor=222222)](https://dotnet.microsoft.com/download/dotnet/10.0) ##### Windows -[![VC++ Redistributable 17](https://img.shields.io/badge/-Redist%2017-00599C?logo=cplusplus&logoColor=white&labelColor=222222)](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022) +[![VC++ Redistributable v14](https://img.shields.io/badge/-Redist%20v14-00599C?logo=cplusplus&logoColor=white&labelColor=222222)](https://aka.ms/vc14/vc_redist.x64.exe) #### Development [![git](https://img.shields.io/badge/-git-F05032?logo=git&logoColor=F05032&labelColor=222222)](https://git-scm.com/downloads) -[![.NET](https://img.shields.io/badge/-%209.0.303%20SDK-5C2D91?logo=.NET&logoColor=white&labelColor=222222)](https://dotnet.microsoft.com/download/dotnet/9.0) +[![.NET](https://img.shields.io/badge/-%2010.0.100%20SDK-5C2D91?logo=.NET&logoColor=white&labelColor=222222)](https://dotnet.microsoft.com/download/dotnet/10.0) #### Supported IDEs

- Jetbrains Rider 2025+ + Jetbrains Rider 2025.3+ space VSCode space - Visual Studio 2022 v17.12+ + Visual Studio 2026

## Getting Started @@ -57,13 +57,13 @@ ModernUO [![Discord](https://img.shields.io/discord/751317910504603701?logo=disc ## Building/Publishing - Run `./publish.cmd [release|debug (default: release)] [os] [arch (default: x64)]` - - `os` - [Supported operating systems](https://github.com/dotnet/core/blob/main/release-notes/9.0/supported-os.md) - - `win` - Windows 10/11/2019/2022/2025 - - `osx` - MacOS 13/14/15 (Sequoia, Sonoma, Big Sur) - - `linux` - Linux + - `os` - [Supported operating systems](https://github.com/dotnet/core/blob/main/release-notes/10.0/supported-os.md) + - `win` - [Windows](https://learn.microsoft.com/en-us/dotnet/core/install/windows) + - `osx` - [MacOS](https://learn.microsoft.com/en-us/dotnet/core/install/macos) + - `linux` - [Linux](https://learn.microsoft.com/en-us/dotnet/core/install/linux) - `arch` - - `x64` - Intel 64-bit - - `arm64` - ARM 64-bit (Windows Arm64 not supported) + - `x64` - Intel/AMD 64-bit + - `arm64` - ARM 64-bit (Windows not supported) ## Linux Prerequisites ### Fedora, CentOS, RHEL, etc @@ -86,8 +86,8 @@ brew install icu4c libdeflate zstd argon2 ``` ## Running the Server -- Follow the [publish](https://github.com/modernuo/ModernUO#publishing-builds) instructions -- Run `ModernUO.exe` or `dotnet ModernUO.dll` from the `Distribution` directory on the +- Follow the [publish](https://github.com/modernuo/ModernUO#buildingpublishing) instructions +- Run `ModernUO.exe` or `dotnet ModernUO.dll` from the `Distribution` directory ## Troubleshooting / FAQ - See [FAQ](./FAQ.md) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 798907fc1..0a0fb6a25 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,7 +14,7 @@ jobs: steps: - task: UseDotNet@2 - displayName: 'Install .NET 9' + displayName: 'Install .NET' inputs: useGlobalJson: true - task: NuGetAuthenticate@1 diff --git a/docs/installation.md b/docs/installation.md index ff6d90c8b..2fefc1d58 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -6,7 +6,7 @@ title: Installation === "Windows" ### Prerequisites - 1. Download and install the latest [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) + 1. Download and install the latest [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) 1. Download and install from [here](https://git-scm.com/download/win) !!! Tip @@ -22,7 +22,7 @@ title: Installation === "OSX" ### Prerequisites - 1. Download and install the latest [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0). + 1. Download and install the latest [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). 1. Using _terminal_, install [homebrew](https://brew.sh) and git: ```bash /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" @@ -37,7 +37,7 @@ title: Installation === "Linux" ### Prerequisites - 1. Download and install the latest [.NET 8 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux). + 1. Download and install the latest [.NET 10 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux). 1. Using _bash_, install git: ```bash sudo apt update && sudo apt install git diff --git a/global.json b/global.json index 733b653c1..6a288505a 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100", + "version": "10.0.100", "rollForward": "latestMajor", "allowPrerelease": false } diff --git a/version.json b/version.json index d36382b04..de4574c38 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.15.1" + "version": "0.15.2" }