/build
/.vs
/src/Impostor.Plugins.Debugger/Properties/launchSettings.json
+
--- /dev/null
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+
+namespace Impostor.Api.Extensions
+{
+ /// <summary>
+ /// Priovides a StreamReader-like api throught extensions
+ /// </summary>
+ public static class SpanReaderExtensions
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadByte(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<byte>(ref input);
+ return original[0];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ReadInt32(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<int>(ref input);
+ return BinaryPrimitives.ReadInt32LittleEndian(original);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt32(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<uint>(ref input);
+ return BinaryPrimitives.ReadUInt32LittleEndian(original);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ReadSingle(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<float>(ref input);
+
+ // BitConverter.Int32BitsToSingle
+ // Doesn't exist in net 2.0 for some reason
+ return Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(original));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool ReadBoolean(this ref ReadOnlySpan<byte> input)
+ {
+ return input.ReadByte() != 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe float Int32BitsToSingle(int value)
+ {
+ return *((float*)&value);
+ }
+
+ /// <summary>
+ /// Advances the position of <see cref="input"/> by the size of <see cref="T"/>.
+ /// </summary>
+ /// <typeparam name="T">Type that will be read.</typeparam>
+ /// <param name="input">input "stream"/span.</param>
+ /// <returns>The original input</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe ReadOnlySpan<byte> Advance<T>(ref ReadOnlySpan<byte> input)
+ where T : unmanaged
+ {
+ var original = input;
+ input = input.Slice(sizeof(T));
+ return original;
+ }
+ }
+}
\ No newline at end of file
<LangVersion>9</LangVersion>
</PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0-rc.1.20451.14" />
-using System;
-using System.IO;
+using Impostor.Api.Extensions;
using Impostor.Api.Innersloth.Data;
+using System;
+using System.IO;
+
namespace Impostor.Api.Innersloth
{
public class GameOptionsData
public bool ConfirmImpostor { get; set; }
public bool VisualTasks { get; set; }
public bool IsDefaults { get; set; }
-
+
public void Serialize(BinaryWriter writer, byte version)
{
- writer.Write((byte) version);
- writer.Write((byte) MaxPlayers);
- writer.Write((uint) Keywords);
- writer.Write((byte) MapId);
- writer.Write((float) PlayerSpeedMod);
- writer.Write((float) CrewLightMod);
- writer.Write((float) ImpostorLightMod);
- writer.Write((float) KillCooldown);
- writer.Write((byte) NumCommonTasks);
- writer.Write((byte) NumLongTasks);
- writer.Write((byte) NumShortTasks);
- writer.Write((int) NumEmergencyMeetings);
- writer.Write((byte) NumImpostors);
- writer.Write((byte) KillDistance);
- writer.Write((uint) DiscussionTime);
- writer.Write((uint) VotingTime);
- writer.Write((bool) IsDefaults);
+ writer.Write((byte)version);
+ writer.Write((byte)MaxPlayers);
+ writer.Write((uint)Keywords);
+ writer.Write((byte)MapId);
+ writer.Write((float)PlayerSpeedMod);
+ writer.Write((float)CrewLightMod);
+ writer.Write((float)ImpostorLightMod);
+ writer.Write((float)KillCooldown);
+ writer.Write((byte)NumCommonTasks);
+ writer.Write((byte)NumLongTasks);
+ writer.Write((byte)NumShortTasks);
+ writer.Write((int)NumEmergencyMeetings);
+ writer.Write((byte)NumImpostors);
+ writer.Write((byte)KillDistance);
+ writer.Write((uint)DiscussionTime);
+ writer.Write((uint)VotingTime);
+ writer.Write((bool)IsDefaults);
if (version > 1)
{
- writer.Write((byte) EmergencyCooldown);
+ writer.Write((byte)EmergencyCooldown);
}
if (version > 2)
{
- writer.Write((bool) ConfirmImpostor);
- writer.Write((bool) VisualTasks);
+ writer.Write((bool)ConfirmImpostor);
+ writer.Write((bool)VisualTasks);
}
}
- public static GameOptionsData Deserialize(ReadOnlyMemory<byte> bytes)
+ public static GameOptionsData Deserialize(ReadOnlyMemory<byte> memory)
{
- // TODO: Remove memory allocation.
+ var bytes = memory.Span;
- using (var stream = new MemoryStream(bytes.ToArray()))
- using (var reader = new BinaryReader(stream))
- {
- var result = new GameOptionsData();
+ var result = new GameOptionsData();
+ result.Version = bytes.ReadByte();
+ result.MaxPlayers = bytes.ReadByte();
+ result.Keywords = (GameKeywords)bytes.ReadUInt32();
+ result.MapId = bytes.ReadByte();
+ result.PlayerSpeedMod = bytes.ReadSingle();
+
+ result.CrewLightMod = bytes.ReadSingle();
+ result.ImpostorLightMod = bytes.ReadSingle();
+ result.KillCooldown = bytes.ReadSingle();
+
+ result.NumCommonTasks = bytes.ReadByte();
+ result.NumLongTasks = bytes.ReadByte();
+ result.NumShortTasks = bytes.ReadByte();
- result.Version = reader.ReadByte();
- result.MaxPlayers = reader.ReadByte();
- result.Keywords = (GameKeywords) reader.ReadUInt32();
- result.MapId = reader.ReadByte();
- result.PlayerSpeedMod = reader.ReadSingle();
- result.CrewLightMod = reader.ReadSingle();
- result.ImpostorLightMod = reader.ReadSingle();
- result.KillCooldown = reader.ReadSingle();
- result.NumCommonTasks = reader.ReadByte();
- result.NumLongTasks = reader.ReadByte();
- result.NumShortTasks = reader.ReadByte();
- result.NumEmergencyMeetings = reader.ReadInt32();
- result.NumImpostors = reader.ReadByte();
- result.KillDistance = reader.ReadByte();
- result.DiscussionTime = reader.ReadInt32();
- result.VotingTime = reader.ReadInt32();
- result.IsDefaults = reader.ReadBoolean();
+ result.NumEmergencyMeetings = bytes.ReadInt32();
- if (result.Version > 1)
- {
- result.EmergencyCooldown = reader.ReadByte();
- }
+ result.NumImpostors = bytes.ReadByte();
+ result.KillDistance = bytes.ReadByte();
+ result.DiscussionTime = bytes.ReadInt32();
+ result.VotingTime = bytes.ReadInt32();
- if (result.Version > 2)
- {
- result.ConfirmImpostor = reader.ReadBoolean();
- result.VisualTasks = reader.ReadBoolean();
- }
-
- return result;
+ result.IsDefaults = bytes.ReadBoolean();
+
+ if (result.Version > 1)
+ {
+ result.EmergencyCooldown = bytes.ReadByte();
+ }
+
+ if (result.Version > 2)
+ {
+ result.ConfirmImpostor = bytes.ReadBoolean();
+ result.VisualTasks = bytes.ReadBoolean();
}
+
+ return result;
}
}
}
\ No newline at end of file
-namespace Impostor.Api.Net.Messages.C2S
+using System;
+using Impostor.Api.Extensions;
+
+namespace Impostor.Api.Net.Messages.C2S
{
public static class Message01JoinGameC2S
{
public static void Deserialize(IMessageReader reader, out int gameCode, out byte unknown)
{
- gameCode = reader.ReadInt32();
- unknown = reader.ReadByte();
+ var slice = reader.ReadBytes(sizeof(Int32) + sizeof(byte)).Span;
+
+ gameCode = slice.ReadInt32();
+ unknown = slice.ReadByte();
}
}
}
\ No newline at end of file
-using Impostor.Api.Innersloth.Data;
+using Impostor.Api.Extensions;
+using Impostor.Api.Innersloth.Data;
namespace Impostor.Api.Net.Messages.C2S
{
public static void Deserialize(IMessageReader reader, out AlterGameTags gameTag, out bool isPublic)
{
- gameTag = (AlterGameTags)reader.ReadByte();
- isPublic = reader.ReadBoolean();
+ var slice = reader.ReadBytes(sizeof(byte) + sizeof(byte)).Span;
+
+ gameTag = (AlterGameTags)slice.ReadByte();
+ isPublic = slice.ReadBoolean();
}
}
}
\ No newline at end of file
public IMessageReader ReadMessage()
{
var length = ReadUInt16();
- var tag = ReadByte();
+ var tag = FastByte();
var pos = Position;
Position += length;
while (readMore)
{
- byte b = ReadByte();
+ byte b = FastByte();
if (b >= 0x80)
{
readMore = true;
-using System;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Messages;
+
+using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
-using Impostor.Api.Games;
-using Impostor.Api.Net.Messages;
namespace Impostor.Hazel
{
public MessageType SendOption { get; private set; }
private Stack<int> messageStarts = new Stack<int>();
-
+
public MessageWriter(byte[] buffer)
{
this.Buffer = buffer;
System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1);
return output;
}
+ default:
+ throw new ArgumentOutOfRangeException();
}
}
case MessageType.Unreliable:
this.Length = this.Position = 1;
break;
+
case MessageType.Reliable:
this.Length = this.Position = 3;
break;
public void Write(ReadOnlyMemory<byte> data)
{
- this.Write(data.ToArray()); // TODO: Fix memory allocation.
+ Write(data.Span);
+ }
+
+ public void Write(ReadOnlySpan<byte> bytes)
+ {
+ bytes.CopyTo(this.Buffer.AsSpan(this.Position, bytes.Length));
+
+ this.Position += bytes.Length;
+ if (this.Position > this.Length) this.Length = this.Position;
}
public void Write(byte[] bytes)
value >>= 7;
} while (value > 0);
}
- #endregion
+
+ #endregion WriteMethods
public void Write(MessageWriter msg, bool includeHeader)
{
case MessageType.Unreliable:
offset = 1;
break;
+
case MessageType.Reliable:
offset = 3;
break;
Recycle();
}
}
-}
+}
\ No newline at end of file
-<Project Sdk="Microsoft.NET.Sdk.Web">
+<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <Folder Include="Properties\" />
+ </ItemGroup>
+
</Project>
\ No newline at end of file
public override void Deserialize(IMessageReader reader, bool initialState)
{
+ var votes = _votes;
var unknown = reader.ReadBoolean();
if (unknown)
{
{
var v4 = reader.ReadInt32();
- if (!_votes.TryGetValue(v4, out var v12))
+ if (!votes.TryGetValue(v4, out var v12))
{
v12 = new int[3];
- _votes[v4] = v12;
+ votes[v4] = v12;
}
for (var i = 0; i < 3; i++)
using Impostor.Api.Innersloth;
+
using Xunit;
namespace Impostor.Tests
{
const string code = "ABCD";
const int codeInt = 0x44434241;
-
+
Assert.Equal(code, GameCodeParser.IntToGameName(codeInt));
Assert.Equal(codeInt, GameCodeParser.GameNameToInt(code));
}
-
+
[Fact]
public void CodeV2()
{
const string code = "ABCDEF";
const int codeInt = -1943683525;
-
+
Assert.Equal(code, GameCodeParser.IntToGameName(codeInt));
Assert.Equal(codeInt, GameCodeParser.GameNameToInt(code));
}
--- /dev/null
+using System;
+using System.Linq;
+using Impostor.Hazel;
+using Xunit;
+
+namespace Impostor.Tests.Hazel
+{
+ public class MessageWriterTests
+ {
+ [Fact]
+ public void ReadOnlyMemoryWriteWorksTheSameAsArray()
+ {
+ var oldVer = new MessageWriter(1024);
+ var newVer = new MessageWriter(1024);
+
+ var data = Enumerable.Repeat
+ (
+ Enumerable.Range(0, byte.MaxValue)
+ .Select(x => (byte)x),
+ 2
+ ).SelectMany(x => x).ToArray();
+
+ WriteSomeData(oldVer);
+ WriteSomeData(newVer);
+
+ oldVer.Write(data);
+ newVer.Write(data.AsMemory());
+
+ Assert.True(oldVer.Buffer.AsSpan().SequenceEqual(newVer.Buffer.AsSpan()));
+
+ static void WriteSomeData(MessageWriter oldVer)
+ {
+ oldVer.WritePacked(99);
+ oldVer.WritePacked(101);
+ }
+ }
+ }
+}
\ No newline at end of file