return original[0];
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static short ReadInt16(this ref ReadOnlySpan<byte> input)
+ {
+ var original = Advance<short>(ref input);
+ return BinaryPrimitives.ReadInt16LittleEndian(original);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadInt32(this ref ReadOnlySpan<byte> input)
{
+++ /dev/null
-namespace Impostor.Api.Innersloth.Customization
-{
- public enum HatType : uint
- {
- NoHat = 0,
- Astronaut = 1,
- BaseballCap = 2,
- BrainSlug = 3,
- BushHat = 4,
- CaptainsHat = 5,
- DoubleTopHat = 6,
- Flowerpot = 7,
- Goggles = 8,
- HardHat = 9,
- Military = 10,
- PaperHat = 11,
- PartyHat = 12,
- Police = 13,
- Stethescope = 14,
- TopHat = 15,
- TowelWizard = 16,
- Ushanka = 17,
- Viking = 18,
- WallCap = 19,
- Snowman = 20,
- Reindeer = 21,
- Lights = 22,
- Santa = 23,
- Tree = 24,
- Present = 25,
- Candycanes = 26,
- ElfHat = 27,
- NewYears2018 = 28,
- WhiteHat = 29,
- Crown = 30,
- Eyebrows = 31,
- HaloHat = 32,
- HeroCap = 33,
- PipCap = 34,
- PlungerHat = 35,
- ScubaHat = 36,
- StickminHat = 37,
- StrawHat = 38,
- TenGallonHat = 39,
- ThirdEyeHat = 40,
- ToiletPaperHat = 41,
- Toppat = 42,
- BlackFedora = 43,
- SkiGoggles = 44,
- Headphones = 45,
- MaskHat = 46,
- PaperMask = 47,
- Security = 48,
- StrapHat = 49,
- Banana = 50,
- Beanie = 51,
- Bear = 52,
- Cheese = 53,
- Cherry = 54,
- Egg = 55,
- GreenFedora = 56,
- Flamingo = 57,
- FlowerPin = 58,
- Helmet = 59,
- Plant = 60,
- BatEyes = 61,
- BatWings = 62,
- Horns = 63,
- Mohawk = 64,
- Pumpkin = 65,
- ScaryBag = 66,
- Witch = 67,
- Wolf = 68,
- Pirate = 69,
- Plague = 70,
- Machete = 71,
- Fred = 72,
- MinerCap = 73,
- WinterHat = 74,
- Archae = 75,
- Antenna = 76,
- Balloon = 77,
- BirdNest = 78,
- BlackBelt = 79,
- Caution = 80,
- Chef = 81,
- CopHat = 82,
- DoRag = 83,
- DumSticker = 84,
- Fez = 85,
- GeneralHat = 86,
- GreyThing = 87,
- HunterCap = 88,
- JungleHat = 89,
- MiniCrewmate = 90,
- NinjaMask = 91,
- RamHorns = 92,
- SnowCrewmate = 93,
- GeoffHat = 94,
- DaveCap = 95,
- EllieRose = 96,
- SvenHat = 97,
- BurtHat = 98,
- EllryMohawk = 99,
- ThomasMonoc = 100,
- Wizard = 101,
- Fredrick = 102,
- MrMacbeth = 103,
- ToppatHenry = 104,
- ToppatEllie = 105,
- GeoffreyPlumb = 106,
- RightHandMan = 107,
- AngryEyebrows = 108,
- ChocolateIce = 109,
- HeartPin = 110,
- Ponytail = 111,
- RubberGlove = 112,
- UnicornHorn = 113,
- Zipper = 114,
- }
-}
+++ /dev/null
-namespace Impostor.Api.Innersloth.Customization
-{
- public enum PetType : uint
- {
- NoPet = 0,
- Alien = 1,
- Crewmate = 2,
- Doggy = 3,
- Stickmin = 4,
- Hamster = 5,
- Robot = 6,
- Ufo = 7,
- Ellie = 8,
- Squig = 9,
- Bedcrab = 10,
- Glitch = 11,
- }
-}
--- /dev/null
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Innersloth.Customization
+{
+ public class PlayerOutfit
+ {
+ public ColorType Color { get; internal set; } = (ColorType)(-1);
+
+ public string HatId { get; internal set; } = "missing";
+
+ public string PetId { get; internal set; } = "missing";
+
+ public string SkinId { get; internal set; } = "missing";
+
+ public string VisorId { get; internal set; } = "missing";
+
+ public string NamePlateId { get; internal set; } = "missing";
+
+ public string PlayerName { get; internal set; } = "missing";
+
+ public bool IsIncomplete
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(PlayerName) && Color != (ColorType)(-1) && HatId != "missing" && PetId != "missing" && SkinId != "missing" && VisorId != "missing")
+ {
+ return NamePlateId == "missing";
+ }
+
+ return true;
+ }
+ }
+
+ public void Serialize(IMessageWriter writer)
+ {
+ writer.Write(PlayerName);
+ writer.WritePacked((int)Color);
+ writer.Write(HatId);
+ writer.Write(PetId);
+ writer.Write(SkinId);
+ writer.Write(VisorId);
+ writer.Write(NamePlateId);
+ }
+
+ public void Deserialize(IMessageReader reader)
+ {
+ PlayerName = reader.ReadString();
+ Color = (ColorType)reader.ReadPackedInt32();
+ HatId = reader.ReadString();
+ PetId = reader.ReadString();
+ SkinId = reader.ReadString();
+ VisorId = reader.ReadString();
+ NamePlateId = reader.ReadString();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Innersloth.Customization
+{
+ public enum PlayerOutfitType
+ {
+ Default,
+ Shapeshifted,
+ }
+}
+++ /dev/null
-namespace Impostor.Api.Innersloth.Customization
-{
- public enum SkinType : uint
- {
- None = 0,
- Astro = 1,
- Capt = 2,
- Mech = 3,
- Military = 4,
- Police = 5,
- Science = 6,
- SuitB = 7,
- SuitW = 8,
- Wall = 9,
- Hazmat = 10,
- Security = 11,
- Tarmac = 12,
- Miner = 13,
- Winter = 14,
- Archae = 15,
- Prisoner = 16,
- CCC = 17,
- RightHandMan = 18,
- }
-}
/// <summary>
/// The latest major version of the game client.
/// </summary>
- public const int LatestVersion = 4;
+ public const int LatestVersion = 5;
/// <summary>
/// Gets or sets host's version of the game.
/// </summary>
public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always;
+ /// <summary>
+ /// Gets or sets role options.
+ /// </summary>
+ public RoleOptionsData RoleOptions { get; set; } = new RoleOptionsData();
+
/// <summary>
/// Gets or sets a value indicating whether the GameOptions are the default ones.
/// </summary>
}
if (version > 4)
+ {
+ RoleOptions.Serialize(writer);
+ }
+
+ if (version > 5)
{
throw new ImpostorException($"Unknown GameOptionsData version {Version}.");
}
}
if (Version > 4)
+ {
+ RoleOptions = RoleOptionsData.Deserialize(bytes);
+ }
+
+ if (Version > 5)
{
throw new ImpostorException($"Unknown GameOptionsData version {Version}.");
}
--- /dev/null
+using System;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Innersloth
+{
+ public class PlatformSpecificData
+ {
+ public PlatformSpecificData(IMessageReader reader)
+ {
+ Platform = (Platforms)reader.Tag;
+ PlatformName = reader.ReadString();
+
+ XboxPlatformId = Platform == Platforms.Xbox ? reader.ReadUInt64() : null;
+ PsnPlatformId = Platform == Platforms.Playstation ? reader.ReadUInt64() : null;
+ }
+
+ public Platforms Platform { get; }
+
+ public string PlatformName { get; }
+
+ public ulong? XboxPlatformId { get; }
+
+ public ulong? PsnPlatformId { get; }
+
+ public void Serialize(IMessageWriter writer)
+ {
+ writer.StartMessage((byte)Platform);
+ writer.Write(PlatformName);
+ switch (Platform)
+ {
+ case Platforms.Xbox:
+ writer.Write(XboxPlatformId ?? throw new NullReferenceException($"{nameof(XboxPlatformId)} shouldn't be null when {nameof(Platform)} is Xbox"));
+ break;
+ case Platforms.Playstation:
+ writer.Write(PsnPlatformId ?? throw new NullReferenceException($"{nameof(PsnPlatformId)} shouldn't be null when {nameof(Platform)} is Playstation"));
+ break;
+ }
+
+ writer.EndMessage();
+ }
+ }
+}
IPhone = 6,
Android = 7,
Switch = 8,
+ Xbox = 9,
+ Playstation = 10,
}
}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Innersloth
+{
+ public class RoleOptionsData
+ {
+ public bool ShapeshifterLeaveSkin { get; set; }
+
+ public float ShapeshifterCooldown { get; set; } = 10f;
+
+ public float ShapeshifterDuration { get; set; } = 30f;
+
+ public float ScientistCooldown { get; set; } = 15f;
+
+ public float ScientistBatteryCharge { get; set; } = 5f;
+
+ public float GuardianAngelCooldown { get; set; } = 60f;
+
+ public bool ImpostorsCanSeeProtect { get; set; }
+
+ public float ProtectionDurationSeconds { get; set; } = 10f;
+
+ public float EngineerCooldown { get; set; } = 30f;
+
+ public float EngineerInVentMaxTime { get; set; } = 15f;
+
+ public Dictionary<RoleTypes, RoleRate> RoleRates { get; } = new Dictionary<RoleTypes, RoleRate>();
+
+ public static RoleOptionsData Deserialize(ReadOnlySpan<byte> span)
+ {
+ var roleOptionsData = new RoleOptionsData();
+ var num = span.ReadInt32();
+ for (var i = 0; i < num; i++)
+ {
+ var key = (RoleTypes)span.ReadInt16();
+ var roleRate = new RoleRate(span.ReadByte(), span.ReadByte());
+ roleOptionsData.RoleRates[key] = roleRate;
+ }
+
+ roleOptionsData.ShapeshifterLeaveSkin = span.ReadBoolean();
+ roleOptionsData.ShapeshifterCooldown = span.ReadByte();
+ roleOptionsData.ShapeshifterDuration = span.ReadByte();
+ roleOptionsData.ScientistCooldown = span.ReadByte();
+ roleOptionsData.GuardianAngelCooldown = span.ReadByte();
+ roleOptionsData.EngineerCooldown = span.ReadByte();
+ roleOptionsData.EngineerInVentMaxTime = span.ReadByte();
+ roleOptionsData.ScientistBatteryCharge = span.ReadByte();
+ roleOptionsData.ProtectionDurationSeconds = span.ReadByte();
+ roleOptionsData.ImpostorsCanSeeProtect = span.ReadBoolean();
+ return roleOptionsData;
+ }
+
+ public static RoleOptionsData Deserialize(IMessageReader reader)
+ {
+ var roleOptionsData = new RoleOptionsData();
+ var num = reader.ReadPackedInt32();
+ for (var i = 0; i < num; i++)
+ {
+ var key = (RoleTypes)reader.ReadInt16();
+ var roleRate = new RoleRate(reader.ReadByte(), reader.ReadByte());
+ roleOptionsData.RoleRates[key] = roleRate;
+ }
+
+ roleOptionsData.ShapeshifterLeaveSkin = reader.ReadBoolean();
+ roleOptionsData.ShapeshifterCooldown = reader.ReadByte();
+ roleOptionsData.ShapeshifterDuration = reader.ReadByte();
+ roleOptionsData.ScientistCooldown = reader.ReadByte();
+ roleOptionsData.GuardianAngelCooldown = reader.ReadByte();
+ roleOptionsData.EngineerCooldown = reader.ReadByte();
+ roleOptionsData.EngineerInVentMaxTime = reader.ReadByte();
+ roleOptionsData.ScientistBatteryCharge = reader.ReadByte();
+ roleOptionsData.ProtectionDurationSeconds = reader.ReadByte();
+ roleOptionsData.ImpostorsCanSeeProtect = reader.ReadBoolean();
+ return roleOptionsData;
+ }
+
+ public void Serialize(IMessageWriter writer)
+ {
+ writer.WritePacked(RoleRates.Count);
+ foreach (var roleRate in RoleRates)
+ {
+ writer.Write((ushort)roleRate.Key);
+ writer.Write((byte)roleRate.Value.MaxCount);
+ writer.Write((byte)roleRate.Value.Chance);
+ }
+
+ writer.Write(ShapeshifterLeaveSkin);
+ writer.Write((byte)ShapeshifterCooldown);
+ writer.Write((byte)ShapeshifterDuration);
+ writer.Write((byte)ScientistCooldown);
+ writer.Write((byte)GuardianAngelCooldown);
+ writer.Write((byte)EngineerCooldown);
+ writer.Write((byte)EngineerInVentMaxTime);
+ writer.Write((byte)ScientistBatteryCharge);
+ writer.Write((byte)ProtectionDurationSeconds);
+ writer.Write(ImpostorsCanSeeProtect);
+ }
+
+ public void Serialize(BinaryWriter writer)
+ {
+ writer.Write(RoleRates.Count);
+ foreach (var roleRate in RoleRates)
+ {
+ writer.Write((ushort)roleRate.Key);
+ writer.Write((byte)roleRate.Value.MaxCount);
+ writer.Write((byte)roleRate.Value.Chance);
+ }
+
+ writer.Write(ShapeshifterLeaveSkin);
+ writer.Write((byte)ShapeshifterCooldown);
+ writer.Write((byte)ShapeshifterDuration);
+ writer.Write((byte)ScientistCooldown);
+ writer.Write((byte)GuardianAngelCooldown);
+ writer.Write((byte)EngineerCooldown);
+ writer.Write((byte)EngineerInVentMaxTime);
+ writer.Write((byte)ScientistBatteryCharge);
+ writer.Write((byte)ProtectionDurationSeconds);
+ writer.Write(ImpostorsCanSeeProtect);
+ }
+
+ public readonly struct RoleRate
+ {
+ public readonly int MaxCount;
+ public readonly int Chance;
+
+ public RoleRate(int maxCount, int chance)
+ {
+ MaxCount = maxCount;
+ Chance = chance;
+ }
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Innersloth
+{
+ public enum RoleTypes : ushort
+ {
+ Crewmate,
+ Impostor,
+ Scientist,
+ Engineer,
+ GuardianAngel,
+ Shapeshifter,
+ }
+}
/// <returns>Task that must be awaited.</returns>
ValueTask SetColorAsync(ColorType colorType);
- /// <summary>
- /// Sets the hat of the current <see cref="IInnerPlayerControl" />.
- /// Visible to all players.
- /// </summary>
- /// <param name="hatType">An hat for the player.</param>
- /// <returns>Task that must be awaited.</returns>
- ValueTask SetHatAsync(HatType hatType);
-
- /// <summary>
- /// Sets the pet of the current <see cref="IInnerPlayerControl" />.
- /// Visible to all players.
- /// </summary>
- /// <param name="petType">A pet for the player.</param>
- /// <returns>Task that must be awaited.</returns>
- ValueTask SetPetAsync(PetType petType);
-
- /// <summary>
- /// Sets the skin of the current <see cref="IInnerPlayerControl" />.
- /// Visible to all players.
- /// </summary>
- /// <param name="skinType">A skin for the player.</param>
- /// <returns>Task that must be awaited.</returns>
- ValueTask SetSkinAsync(SkinType skinType);
+ // /// <summary>
+ // /// Sets the hat of the current <see cref="IInnerPlayerControl" />.
+ // /// Visible to all players.
+ // /// </summary>
+ // /// <param name="hatId">An hat for the player.</param>
+ // /// <returns>Task that must be awaited.</returns>
+ // ValueTask SetHatAsync(string hatId);
+ //
+ // /// <summary>
+ // /// Sets the pet of the current <see cref="IInnerPlayerControl" />.
+ // /// Visible to all players.
+ // /// </summary>
+ // /// <param name="petId">A pet for the player.</param>
+ // /// <returns>Task that must be awaited.</returns>
+ // ValueTask SetPetAsync(string petId);
+ //
+ // /// <summary>
+ // /// Sets the skin of the current <see cref="IInnerPlayerControl" />.
+ // /// Visible to all players.
+ // /// </summary>
+ // /// <param name="skinId">A skin for the player.</param>
+ // /// <returns>Task that must be awaited.</returns>
+ // ValueTask SetSkinAsync(string skinId);
/// <summary>
/// Send a chat message as the current <see cref="IInnerPlayerControl" />.
/// </summary>
string PlayerName { get; }
- /// <summary>
- /// Gets the color of the player.
- /// </summary>
- ColorType Color { get; }
-
- /// <summary>
- /// Gets the hat of the player.
- /// </summary>
- HatType Hat { get; }
-
- /// <summary>
- /// Gets the pet of the player.
- /// </summary>
- PetType Pet { get; }
-
- /// <summary>
- /// Gets the skin of the player.
- /// </summary>
- SkinType Skin { get; }
-
/// <summary>
/// Gets a value indicating whether the player is an impostor.
/// </summary>
/// </summary>
bool IsDead { get; }
+ Dictionary<PlayerOutfitType, PlayerOutfit> Outfits { get; }
+
+ PlayerOutfitType CurrentOutfitType { get; set; }
+
+ PlayerOutfit CurrentOutfit { get; }
+
/// <summary>
/// Gets the reason why the player is dead in the current game.
/// </summary>
SetName = 6,
CheckColor = 7,
SetColor = 8,
- SetHat = 9,
- SetSkin = 10,
ReportDeadBody = 11,
MurderPlayer = 12,
SendChat = 13,
StartMeeting = 14,
SetScanner = 15,
SendChatNote = 16,
- SetPet = 17,
SetStartCounter = 18,
EnterVent = 19,
ExitVent = 20,
SendQuickChat = 33,
BootFromVent = 34,
UpdateSystem = 35,
+ SetLevel = 38,
+ SetHat = 39,
+ SetSkin = 40,
+ SetPet = 41,
+ SetVisor = 42,
+ SetNamePlate = 43,
+ SetRole = 44,
+ ProtectPlayer = 45,
+ Shapeshift = 46,
+ CheckMurder = 47,
+ CheckProtect = 48,
}
}
{
public static class HandshakeC2S
{
- public static void Deserialize(IMessageReader reader, out int clientVersion, out string name, out uint? lastNonceReceived, out Language language, out QuickChatModes chatMode)
+ public static void Deserialize(IMessageReader reader, out int clientVersion, out string name, out Language language, out QuickChatModes chatMode, out PlatformSpecificData? platformSpecificData)
{
clientVersion = reader.ReadInt32();
name = reader.ReadString();
if (clientVersion >= Version.V1)
{
- lastNonceReceived = reader.ReadUInt32();
- }
- else
- {
- lastNonceReceived = null;
+ reader.ReadUInt32(); // lastNonceReceived, always 0 since 2021.11.9
}
if (clientVersion >= Version.V2)
language = Language.English;
chatMode = QuickChatModes.FreeChatOrQuickChat;
}
+
+ if (clientVersion >= Version.V3)
+ {
+ using var platformReader = reader.ReadMessage();
+ platformSpecificData = new PlatformSpecificData(platformReader);
+ reader.ReadInt32(); // crossplayFlags, not used yet
+ }
+ else
+ {
+ platformSpecificData = null;
+ }
}
private static class Version
public static readonly int V1 = GameVersion.GetVersion(2021, 4, 25);
public static readonly int V2 = GameVersion.GetVersion(2021, 6, 30);
+
+ public static readonly int V3 = GameVersion.GetVersion(2021, 11, 9);
}
}
}
{
writer.StartMessage(MessageFlags.HostGame);
gameOptionsData.Serialize(writer);
+ writer.Write(int.MaxValue); // crossplayFlags
writer.EndMessage();
}
/// Deserialize a packet.
/// </summary>
/// <param name="reader"><see cref="IMessageReader" /> with <see cref="IMessageReader.Tag" /> 0.</param>
- /// <param name="chatMode">The chat type selected in the client of the player.</param>
/// <returns>Deserialized <see cref="GameOptionsData" />.</returns>
- public static GameOptionsData Deserialize(IMessageReader reader, out QuickChatModes chatMode)
+ public static GameOptionsData Deserialize(IMessageReader reader)
{
var gameOptionsData = GameOptionsData.DeserializeCreate(reader);
- chatMode = (QuickChatModes)reader.ReadByte();
+ reader.ReadInt32(); // crossplayFlags, not used yet
return gameOptionsData;
}
public static void Deserialize(IMessageReader reader, out GameCode gameCode)
{
gameCode = reader.ReadInt32();
+ reader.ReadBoolean(); // no crossplay
}
}
}
public static void Deserialize(IMessageReader reader, out GameOptionsData options, out QuickChatModes chatMode)
{
- reader.ReadPackedInt32(); // Hardcoded 0.
+ var version = reader.ReadPackedInt32();
+ if (version != 2)
+ {
+ throw new NotSupportedException($"Version {version} of {nameof(Message16GetGameListC2S)} is not supported");
+ }
+
options = GameOptionsData.DeserializeCreate(reader);
chatMode = (QuickChatModes)reader.ReadByte();
+ reader.ReadInt32(); // crossplayFlags, not used yet
}
}
}
int ReadInt32();
+ ulong ReadUInt64();
+
+ long ReadInt64();
+
float ReadSingle();
string ReadString(int length);
/// <param name="value">Value to write.</param>
void Write(int value);
+ /// <summary>
+ /// Writes an ulong to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ public void Write(ulong value);
+
+ /// <summary>
+ /// Writes an ulong to the message.
+ /// </summary>
+ /// <param name="value">Value to write.</param>
+ public void Write(long value);
+
/// <summary>
/// Writes a float to the message.
/// </summary>
public const byte WaitForHost = 12;
public const byte Redirect = 13;
public const byte ReselectServer = 14;
- public const byte GetGameList = 9;
public const byte GetGameListV2 = 16;
+ public const byte ReportPlayer = 17;
+ public const byte QuickMatch = 18;
+ public const byte QuickMatchHost = 19;
+ public const byte SetGameSession = 20;
+ public const byte SetActivePodType = 21;
+ public const byte QueryPlatformIds = 22;
+ public const byte QueryLobbyInfo = 23;
}
}
+++ /dev/null
-using Impostor.Api.Innersloth.Customization;
-
-namespace Impostor.Api.Net.Messages.Rpcs
-{
- public static class Rpc09SetHat
- {
- public static void Serialize(IMessageWriter writer, HatType hat)
- {
- writer.WritePacked((uint)hat);
- }
-
- public static void Deserialize(IMessageReader reader, out HatType hat)
- {
- hat = (HatType)reader.ReadPackedUInt32();
- }
- }
-}
+++ /dev/null
-using Impostor.Api.Innersloth.Customization;
-
-namespace Impostor.Api.Net.Messages.Rpcs
-{
- public static class Rpc10SetSkin
- {
- public static void Serialize(IMessageWriter writer, SkinType skin)
- {
- writer.WritePacked((uint)skin);
- }
-
- public static void Deserialize(IMessageReader reader, out SkinType skin)
- {
- skin = (SkinType)reader.ReadPackedUInt32();
- }
- }
-}
+++ /dev/null
-using Impostor.Api.Innersloth.Customization;
-
-namespace Impostor.Api.Net.Messages.Rpcs
-{
- public static class Rpc17SetPet
- {
- public static void Serialize(IMessageWriter writer, PetType pet)
- {
- writer.WritePacked((uint)pet);
- }
-
- public static void Deserialize(IMessageReader reader, out PetType pet)
- {
- pet = (PetType)reader.ReadPackedUInt32();
- }
- }
-}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc39SetHat
+ {
+ public static void Serialize(IMessageWriter writer, string hat)
+ {
+ writer.Write(hat);
+ }
+
+ public static void Deserialize(IMessageReader reader, out string hat)
+ {
+ hat = reader.ReadString();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc40SetSkin
+ {
+ public static void Serialize(IMessageWriter writer, string skin)
+ {
+ writer.Write(skin);
+ }
+
+ public static void Deserialize(IMessageReader reader, out string skin)
+ {
+ skin = reader.ReadString();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc41SetPet
+ {
+ public static void Serialize(IMessageWriter writer, string pet)
+ {
+ writer.Write(pet);
+ }
+
+ public static void Deserialize(IMessageReader reader, out string pet)
+ {
+ pet = reader.ReadString();
+ }
+ }
+}
if (hasReason)
{
- var inner = reader.ReadMessage();
+ using var inner = reader.ReadMessage();
reason = (DisconnectReason)inner.ReadByte();
message = reason == DisconnectReason.Custom ? inner.ReadString() : null;
}
-Subproject commit 8de027461e59af67fb9dc0d9b925a88ad4a01d26
+Subproject commit 74070b8c0798f9a8889d65d3bf5b54f2ef55f4c3
private readonly GameManager _gameManager;
private readonly ICustomMessageManager<ICustomRootMessage> _customMessageManager;
- public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, ICustomMessageManager<ICustomRootMessage> customMessageManager, string name, int gameVersion, Language language, QuickChatModes chatMode, IHazelConnection connection)
- : base(name, gameVersion, language, chatMode, connection)
+ public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, ICustomMessageManager<ICustomRootMessage> customMessageManager, string name, int gameVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData, IHazelConnection connection)
+ : base(name, gameVersion, language, chatMode, platformSpecificData, connection)
{
_logger = logger;
_antiCheatConfig = antiCheatOptions.Value;
case MessageFlags.HostGame:
{
// Read game settings.
- var gameInfo = Message00HostGameC2S.Deserialize(reader, out _);
+ var gameInfo = Message00HostGameC2S.Deserialize(reader);
// Create game.
var game = await _gameManager.CreateAsync(this, gameInfo);
{
internal abstract class ClientBase : IClient
{
- protected ClientBase(string name, int gameVersion, Language language, QuickChatModes chatMode, IHazelConnection connection)
+ protected ClientBase(string name, int gameVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData, IHazelConnection connection)
{
Name = name;
GameVersion = gameVersion;
Language = language;
ChatMode = chatMode;
+ PlatformSpecificData = platformSpecificData;
Connection = connection;
Items = new ConcurrentDictionary<object, object>();
}
public QuickChatModes ChatMode { get; }
+ public PlatformSpecificData PlatformSpecificData { get; }
+
public int GameVersion { get; }
public IHazelConnection Connection { get; }
_serviceProvider = serviceProvider;
}
- public ClientBase Create(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode)
+ public ClientBase Create(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData)
{
- var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, clientVersion, language, chatMode, connection);
+ var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, clientVersion, language, chatMode, platformSpecificData, connection);
connection.Client = client;
return client;
}
{
internal interface IClientFactory
{
- ClientBase Create(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode);
+ ClientBase Create(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData);
}
}
}
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
+ foreach (var messageReader in states)
+ {
+ messageReader.Dispose();
+ }
// This would be a nice place to implement an anti cheat.
// But for whatever reason host sends VotingComplete before sending his vote.
public async ValueTask SetColorAsync(ColorType color)
{
- PlayerInfo.Color = color;
+ PlayerInfo.CurrentOutfit.Color = color;
using var writer = Game.StartRpc(NetId, RpcCalls.SetColor);
Rpc08SetColor.Serialize(writer, color);
await Game.FinishRpcAsync(writer);
}
- public async ValueTask SetHatAsync(HatType hat)
- {
- PlayerInfo.Hat = hat;
-
- using var writer = Game.StartRpc(NetId, RpcCalls.SetHat);
- Rpc09SetHat.Serialize(writer, hat);
- await Game.FinishRpcAsync(writer);
- }
-
- public async ValueTask SetPetAsync(PetType pet)
- {
- PlayerInfo.Pet = pet;
-
- using var writer = Game.StartRpc(NetId, RpcCalls.SetPet);
- Rpc17SetPet.Serialize(writer, pet);
- await Game.FinishRpcAsync(writer);
- }
-
- public async ValueTask SetSkinAsync(SkinType skin)
- {
- PlayerInfo.Skin = skin;
-
- using var writer = Game.StartRpc(NetId, RpcCalls.SetSkin);
- Rpc10SetSkin.Serialize(writer, skin);
- await Game.FinishRpcAsync(writer);
- }
-
public async ValueTask SendChatAsync(string text)
{
using var writer = Game.StartRpc(NetId, RpcCalls.SendChat);
return false;
}
- Rpc09SetHat.Deserialize(reader, out var hat);
- return await HandleSetHat(sender, hat);
+ Rpc39SetHat.Deserialize(reader, out var hat);
+ return true;
}
case RpcCalls.SetSkin:
return false;
}
- Rpc10SetSkin.Deserialize(reader, out var skin);
- return await HandleSetSkin(sender, skin);
+ Rpc40SetSkin.Deserialize(reader, out var skin);
+ return true;
+ }
+
+ case RpcCalls.SetVisor:
+ {
+ if (!await ValidateOwnership(call, sender))
+ {
+ return false;
+ }
+
+ // Rpc42SetVistor.Deserialize(reader, out var visor);
+ return true;
+ }
+
+ case RpcCalls.SetNamePlate:
+ {
+ if (!await ValidateOwnership(call, sender))
+ {
+ return false;
+ }
+
+ // Rpc43SetNamePlate.Deserialize(reader, out var namePlate);
+ return true;
+ }
+
+ case RpcCalls.SetLevel:
+ {
+ if (!await ValidateOwnership(call, sender))
+ {
+ return false;
+ }
+
+ // Rpc38SetLevel.Deserialize(reader, out var level);
+ return true;
}
case RpcCalls.ReportDeadBody:
return false;
}
- Rpc17SetPet.Deserialize(reader, out var pet);
- return await HandleSetPet(sender, pet);
+ Rpc41SetPet.Deserialize(reader, out var pet);
+ return true;
+ // return await HandleSetPet(sender, pet);
}
case RpcCalls.SetStartCounter:
var player = Game.GameNet.GameData!.GetPlayerById(infectedIds.Span[i]);
if (player != null)
{
- player.IsImpostor = true;
+ // player.IsImpostor = true;
}
}
if (sender.IsOwner(this))
{
- if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.Color == color))
+ if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == color))
{
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client sent a color that is already used"))
{
var expected = RequestedColorId.Dequeue();
- while (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.Color == expected))
+ while (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == expected))
{
expected = (ColorType)(((byte)expected + 1) % ColorsCount);
}
}
}
- PlayerInfo.Color = color;
-
- return true;
- }
-
- private async ValueTask<bool> HandleSetHat(ClientPlayer sender, HatType hat)
- {
- if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetHat, "Client tried to change hat while not in lobby"))
- {
- return false;
- }
-
- PlayerInfo.Hat = hat;
+ PlayerInfo.CurrentOutfit.Color = color;
return true;
}
- private async ValueTask<bool> HandleSetSkin(ClientPlayer sender, SkinType skin)
- {
- if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetSkin, "Client tried to change skin while not in lobby"))
- {
- return false;
- }
-
- PlayerInfo.Skin = skin;
-
- return true;
- }
+ // private async ValueTask<bool> HandleSetHat(ClientPlayer sender, HatType hat)
+ // {
+ // if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetHat, "Client tried to change hat while not in lobby"))
+ // {
+ // return false;
+ // }
+ //
+ // PlayerInfo.Hat = hat;
+ //
+ // return true;
+ // }
+ //
+ // private async ValueTask<bool> HandleSetSkin(ClientPlayer sender, SkinType skin)
+ // {
+ // if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetSkin, "Client tried to change skin while not in lobby"))
+ // {
+ // return false;
+ // }
+ //
+ // PlayerInfo.Skin = skin;
+ //
+ // return true;
+ // }
private async ValueTask<bool> HandleMurderPlayer(ClientPlayer sender, IInnerPlayerControl? target)
{
await _eventManager.CallAsync(new PlayerStartMeetingEvent(Game, Game.GetClientPlayer(this.OwnerId)!, this, deadPlayer));
}
- private async ValueTask<bool> HandleSetPet(ClientPlayer sender, PetType pet)
- {
- if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetPet, "Client tried to change pet while not in lobby"))
- {
- return false;
- }
-
- PlayerInfo.Pet = pet;
-
- return true;
- }
+ // private async ValueTask<bool> HandleSetPet(ClientPlayer sender, PetType pet)
+ // {
+ // if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetPet, "Client tried to change pet while not in lobby"))
+ // {
+ // return false;
+ // }
+ //
+ // PlayerInfo.Pet = pet;
+ //
+ // return true;
+ // }
private async ValueTask<bool> HandleSetStartCounter(ClientPlayer sender, int sequenceId, sbyte startCounter)
{
public string PlayerName { get; internal set; } = string.Empty;
- public ColorType Color { get; internal set; } = (ColorType)(-1);
+ public Dictionary<PlayerOutfitType, PlayerOutfit> Outfits { get; } = new()
+ {
+ [PlayerOutfitType.Default] = new PlayerOutfit(),
+ };
- public HatType Hat { get; internal set; }
+ public PlayerOutfitType CurrentOutfitType { get; set; } = PlayerOutfitType.Default;
- public PetType Pet { get; internal set; }
+ public PlayerOutfit CurrentOutfit => Outfits[CurrentOutfitType];
- public SkinType Skin { get; internal set; }
+ public RoleTypes RoleType { get; internal set; }
public bool Disconnected { get; internal set; }
- public bool IsImpostor { get; internal set; }
+ public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter;
public bool IsDead { get; internal set; }
public void Deserialize(IMessageReader reader)
{
- PlayerName = reader.ReadString();
- Color = (ColorType)reader.ReadPackedInt32();
- Hat = (HatType)reader.ReadPackedUInt32();
- Pet = (PetType)reader.ReadPackedUInt32();
- Skin = (SkinType)reader.ReadPackedUInt32();
+ Outfits.Clear();
+ var b = reader.ReadByte();
+ for (var i = 0; i < b; i++)
+ {
+ var key = (PlayerOutfitType)reader.ReadByte();
+ Outfits[key] = new PlayerOutfit();
+ Outfits[key].Deserialize(reader);
+ }
+
+ var PlayerLevel = reader.ReadPackedUInt32();
var flag = reader.ReadByte();
- Disconnected = (flag & 1) > 0;
- IsImpostor = (flag & 2) > 0;
- IsDead = (flag & 4) > 0;
+ Disconnected = (flag & 1) != 0;
+ IsDead = (flag & 4) != 0;
+
+ var roleType = (RoleTypes)reader.ReadUInt16();
+ // Role = roleType;
var taskCount = reader.ReadByte();
// NOTE: when updating this array, keep the versions ordered from old to new, otherwise the version compare logic doesn't work properly
private static readonly int[] SupportedVersions =
{
- GameVersion.GetVersion(2021, 6, 30), // 2021.6.30
+ GameVersion.GetVersion(2021, 11, 9), // 2021.11.9
};
private readonly ILogger<ClientManager> _logger;
return clientId;
}
- public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode)
+ public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode, PlatformSpecificData? platformSpecificData)
{
var versionCompare = CompareVersion(clientVersion);
- if (versionCompare != VersionCompareResult.Compatible)
+ if (versionCompare != VersionCompareResult.Compatible || platformSpecificData == null)
{
GameVersion.ParseVersion(clientVersion, out var year, out var month, out var day, out var revision);
_logger.LogTrace("Client connected using unsupported version: {clientVersion} ({version})", clientVersion, $"{year}.{month}.{day}{(revision == 0 ? string.Empty : "." + revision)}");
return;
}
- var client = _clientFactory.Create(connection, name, clientVersion, language, chatMode);
+ var client = _clientFactory.Create(connection, name, clientVersion, language, chatMode, platformSpecificData);
var id = NextId();
client.Id = id;
private async ValueTask OnNewConnection(NewConnectionEventArgs e)
{
// Handshake.
- HandshakeC2S.Deserialize(e.HandshakeData, out var clientVersion, out var name, out _, out var language, out var chatMode);
+ HandshakeC2S.Deserialize(e.HandshakeData, out var clientVersion, out var name, out var language, out var chatMode, out var platformSpecificData);
var connection = new HazelConnection(e.Connection, _connectionLogger);
await _eventManager.CallAsync(new ClientConnectionEvent(connection, e.HandshakeData));
// Register client
- await _clientManager.RegisterConnectionAsync(connection, name, clientVersion, language, chatMode);
+ await _clientManager.RegisterConnectionAsync(connection, name, clientVersion, language, chatMode, platformSpecificData);
}
}
}
int gameVersion,
Language language,
QuickChatModes chatMode,
+ PlatformSpecificData platformSpecificData,
HazelConnection connection,
ClientManager clientManager,
INodeProvider nodeProvider,
INodeLocator nodeLocator)
- : base(name, gameVersion, language, chatMode, connection)
+ : base(name, gameVersion, language, chatMode, platformSpecificData, connection)
{
_clientManager = clientManager;
_nodeProvider = nodeProvider;
private bool _createdGame;
private bool _recordAfter;
- public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, ICustomMessageManager<ICustomRootMessage> customMessageManager, GameManager gameManager, string name, int gameVersion, Language language, QuickChatModes chatMode, HazelConnection connection, PacketRecorder recorder)
- : base(logger, antiCheatOptions, clientManager, gameManager, customMessageManager, name, gameVersion, language, chatMode, connection)
+ public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, ICustomMessageManager<ICustomRootMessage> customMessageManager, GameManager gameManager, string name, int gameVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData, HazelConnection connection, PacketRecorder recorder)
+ : base(logger, antiCheatOptions, clientManager, gameManager, customMessageManager, name, gameVersion, language, chatMode, platformSpecificData, connection)
{
_recorder = recorder;
_isFirst = true;