Skip to content

Instantly share code, notes, and snippets.

@miou-gh
Last active November 25, 2018 09:00
Show Gist options
  • Save miou-gh/4bf8c40045305f1776aee868192cfcdc to your computer and use it in GitHub Desktop.
Save miou-gh/4bf8c40045305f1776aee868192cfcdc to your computer and use it in GitHub Desktop.
A small proof-of-concept lightweight version of PlayerIOClient.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Collections;
using System.Net.Sockets;
using ProtoBuf;
using Flurl.Http;
using PlayerIOLite.Messages;
using static PlayerIOLite.BinaryDeserializer;
namespace PlayerIOLite
{
internal class PlayerIOChannel
{
public string Token { get; set; }
public (bool success, TResponse response, PlayerIOError error) Request<TRequest, TResponse>(int method, TRequest args)
{
var channel = new FlurlClient("https://api.playerio.com/");
var stream = new MemoryStream();
Serializer.Serialize(stream, args);
var response = channel.AllowHttpStatus(HttpStatusCode.OK)
.WithHeader("playertoken", this.Token)
.WithTimeout(seconds: 30)
.Request($"api/{method}")
.PostAsync(new ByteArrayContent(this.ReadAllBytes(stream))).ReceiveBytes().Result;
if (this.ReadHeader(new MemoryStream(response)))
{
return (true, Serializer.Deserialize<TResponse>(new MemoryStream(response.Skip(2).ToArray())), null);
}
else
{
var error = Serializer.Deserialize<Error>(new MemoryStream(response.Skip(2).ToArray()));
return (false, default(TResponse), new PlayerIOError(error.ErrorCode, error.Message));
}
}
internal byte[] ReadAllBytes(Stream stream)
{
if (stream is MemoryStream memory)
return memory.ToArray();
using (var temp = new MemoryStream())
{
stream.CopyTo(temp);
return temp.ToArray();
}
}
private bool ReadHeader(Stream responseStream)
{
if (responseStream.ReadByte() == 1)
{
var length = (ushort)(responseStream.ReadByte() << 8 | responseStream.ReadByte());
var array = new byte[length];
responseStream.Read(array, 0, array.Length);
this.Token = Encoding.UTF8.GetString(array, 0, array.Length);
}
return responseStream.ReadByte() == 1;
}
}
public class PlayerIOError : Exception
{
public ErrorCode ErrorCode { get; set; }
public override string Message { get; }
public PlayerIOError(ErrorCode errorCode, string message)
{
this.ErrorCode = errorCode;
this.Message = message;
}
}
public class PlayerIO
{
public static Client SimpleConnect(string gameId, string usernameOrEmail, string password)
{
var channel = new PlayerIOChannel();
var (success, response, error) = channel.Request<SimpleConnectArgs, SimpleConnectOutput>(400, new SimpleConnectArgs
{
GameId = gameId,
UsernameOrEmail = usernameOrEmail,
Password = password
});
if (!success)
throw new PlayerIOError(error.ErrorCode, error.Message);
return new Client(response.Token, response.UserId);
}
}
public class Client
{
public string Token { get; set; }
public string ConnectUserId { get; set; }
internal PlayerIOChannel Channel { get; set; }
internal Client(string token, string userId)
{
this.Token = token;
this.ConnectUserId = userId;
this.Channel = new PlayerIOChannel
{
Token = this.Token
};
}
public Connection CreateJoinRoom(string roomId, string serverType, bool visible = true, Dictionary<string, string> roomData = null, Dictionary<string, string> joinData = null)
{
var (success, response, error) = this.Channel.Request<CreateJoinRoomArgs, CreateJoinRoomOutput>(27, new CreateJoinRoomArgs
{
RoomId = roomId,
ServerType = serverType,
Visible = visible,
RoomData = roomData,
JoinData = joinData,
IsDevRoom = false
});
if (!success)
throw new PlayerIOError(error.ErrorCode, error.Message);
var endpoint = response.Endpoints.First();
return new Connection(endpoint.Address, endpoint.Port, response.JoinKey);
}
}
public delegate void MessageReceivedEventHandler(object sender, Message e);
public delegate void DisconnectEventHandler(object sender, string message);
public class Connection
{
public event MessageReceivedEventHandler OnMessage;
public event DisconnectEventHandler OnDisconnect;
private Socket Socket { get; set; }
private Stream Stream { get; set; }
private byte[] Buffer { get; set; } = new byte[ushort.MaxValue];
private BinarySerializer Serializer;
private BinaryDeserializer Deserializer;
internal Connection(string host, int port, string joinToken)
{
this.Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.Socket.Connect(host, port);
this.Stream = new NetworkStream(this.Socket);
this.Serializer = new BinarySerializer();
this.Deserializer = new BinaryDeserializer();
this.Stream.BeginRead(this.Buffer, 0, this.Buffer.Length, new AsyncCallback(this.ReceiveCallback), null);
this.Socket.Send(new byte[] { 0 });
this.Send(new Message("join", joinToken));
this.Deserializer.OnDeserializedMessage += (message) =>
{
OnMessage?.Invoke(this, message);
};
}
public void Send(string type, params object[] arguments)
{
if (this.Socket != null && this.Socket.Connected)
this.Socket.Send(Serializer.Serialize(new Message(type, arguments)));
}
public void Send(Message message)
{
if (this.Socket != null && this.Socket.Connected)
this.Socket.Send(Serializer.Serialize(message));
}
public void Disconnect()
{
if (this.Socket != null && this.Socket.Connected)
{
this.Socket.Disconnect(false);
this.OnDisconnect?.Invoke(this, "The connection was forcibly terminated by local client.");
this.Socket.Dispose();
this.Buffer = null;
this.Stream = null;
}
}
private void ReceiveCallback(IAsyncResult ar)
{
if (!this.Socket.Connected)
{
if (this.Stream != null)
this.OnDisconnect?.Invoke(this, "The connection was forcibly reset by peer.");
return;
}
var length = this.Stream.EndRead(ar);
var received = this.Buffer.Take(length).ToArray();
if (length == 0)
{
this.OnDisconnect?.Invoke(this, "The connection was forcibly reset by peer. (receivedBytes == 0)");
return;
}
this.Deserializer.AddBytes(received);
this.Stream.BeginRead(this.Buffer, 0, this.Buffer.Length, new AsyncCallback(this.ReceiveCallback), null);
}
}
public enum ErrorCode : byte
{
UnsupportedMethod = 0, GeneralError = 1, InternalError = 2, AccessDenied = 3, InvalidMessageFormat = 4, MissingValue = 5, GameRequired = 6,
ExternalError = 7, ArgumentOutOfRange = 8, InvalidType = 80, IndexOutOfBounds = 81, GameDisabled = 9, UnknownGame = 10, UnknownConnection = 11,
InvalidAuth = 12, NoAvailableServers = 13, RoomDataTooLarge = 14, RoomAlreadyExists = 15, UnknownServerType = 16, UnknownRoom = 17, MissingRoomId = 18,
RoomIsFull = 19, NotASearchColumn = 20, QuickConnectMethodNotEnabled = 21, UnknownUser = 22, InvalidPassword = 23, InvalidRegistrationData = 24,
InvalidBigDBKey = 25, BigDBObjectTooLarge = 26, BigDBObjectDoesNotExist = 27, UnknownTable = 28, UnknownIndex = 29, InvalidIndexValue = 30,
NotObjectCreator = 31, KeyAlreadyUsed = 32, StaleVersion = 33, CircularReference = 34, HeartbeatFailed = 40, InvalidGameCode = 41, VaultNotLoaded = 50,
UnknownPayVaultProvider = 51, DirectPurchaseNotSupportedByProvider = 52, BuyingCoinsNotSupportedByProvider = 54, NotEnoughCoins = 55, ItemNotInVault = 56,
InvalidPurchaseArguments = 57, InvalidPayVaultProviderSetup = 58, UnknownPartnerPayAction = 70
}
internal class BinaryDeserializer
{
internal enum EnumPattern
{
STRING_SHORT_PATTERN = 0xC0, STRING_PATTERN = 0x0C, BYTE_ARRAY_SHORT_PATTERN = 0x40, BYTE_ARRAY_PATTERN = 0x10,
UNSIGNED_LONG_SHORT_PATTERN = 0x38, UNSIGNED_LONG_PATTERN = 0x3C, LONG_SHORT_PATTERN = 0x30,LONG_PATTERN = 0x34,
UNSIGNED_INT_SHORT_PATTERN = 0x80, UNSIGNED_INT_PATTERN = 0x08, INT_PATTERN = 0x04, DOUBLE_PATTERN = 0x03,
FLOAT_PATTERN = 0x02, BOOLEAN_TRUE_PATTERN = 0x01, BOOLEAN_FALSE_PATTERN = 0x00, DOES_NOT_EXIST = -1
}
internal enum EnumState
{
Init, Header, Data
}
internal delegate void MessageDeserializedEventHandler(Message e);
internal delegate void ValueDeserializedEventHandler(object value);
public event MessageDeserializedEventHandler OnDeserializedMessage;
internal event ValueDeserializedEventHandler OnDeserializedValue;
internal EnumState State = EnumState.Init;
internal EnumPattern Pattern = EnumPattern.DOES_NOT_EXIST;
internal MemoryStream _buffer;
internal Message _message;
internal int _length;
internal int _partLength;
public BinaryDeserializer()
{
_buffer = new MemoryStream();
_message = null;
_length = -1;
_partLength = 0;
OnDeserializedValue += (value) => {
if (_length == -1)
{
_length = (int)value;
}
else
{
if (_message == null)
_message = new Message((string)value);
else
_message.Add(value);
if (_length == _message.Count)
{
OnDeserializedMessage?.Invoke(_message);
_message = null;
_length = -1;
}
}
State = EnumState.Init;
};
}
public void AddBytes(byte[] input)
{
foreach (var value in input)
DeserializeValue(value);
}
internal void DeserializeValue(byte value)
{
switch (State)
{
case EnumState.Init:
Pattern = value.RetrieveFlagPattern();
switch (Pattern)
{
case EnumPattern.STRING_SHORT_PATTERN:
_partLength = value.RetrievePartLength(Pattern);
if (_partLength > 0)
State = EnumState.Data;
else
OnDeserializedValue("");
break;
case EnumPattern.STRING_PATTERN:
_partLength = value.RetrievePartLength(Pattern) + 1;
State = EnumState.Header;
break;
case EnumPattern.BYTE_ARRAY_SHORT_PATTERN:
_partLength = value.RetrievePartLength(Pattern);
if (_partLength > 0)
State = EnumState.Data;
else
OnDeserializedValue(new byte[] { });
break;
case EnumPattern.BYTE_ARRAY_PATTERN:
_partLength = value.RetrievePartLength(Pattern) + 1;
State = EnumState.Header;
break;
case EnumPattern.UNSIGNED_INT_SHORT_PATTERN:
OnDeserializedValue(value.RetrievePartLength(Pattern));
break;
case EnumPattern.UNSIGNED_INT_PATTERN:
_partLength = value.RetrievePartLength(Pattern) + 1;
State = EnumState.Data;
break;
case EnumPattern.INT_PATTERN:
_partLength = value.RetrievePartLength(Pattern) + 1;
State = EnumState.Data;
break;
case EnumPattern.UNSIGNED_LONG_SHORT_PATTERN:
_partLength = 1;
State = EnumState.Data;
break;
case EnumPattern.UNSIGNED_LONG_PATTERN:
_partLength = 6;
State = EnumState.Data;
break;
case EnumPattern.LONG_SHORT_PATTERN:
_partLength = 1;
State = EnumState.Data;
break;
case EnumPattern.LONG_PATTERN:
_partLength = 6;
State = EnumState.Data;
break;
case EnumPattern.DOUBLE_PATTERN:
_partLength = 8;
State = EnumState.Data;
break;
case EnumPattern.FLOAT_PATTERN:
_partLength = 4;
State = EnumState.Data;
break;
case EnumPattern.BOOLEAN_TRUE_PATTERN:
OnDeserializedValue(true);
break;
case EnumPattern.BOOLEAN_FALSE_PATTERN:
OnDeserializedValue(false);
break;
}
break;
case EnumState.Header:
_buffer.WriteByte(value);
if (_buffer.Position == _partLength)
{
var buffer = _buffer.ToArray().Reverse().ToArray();
var length = new List<byte>(4) { 0, 0, 0, 0 }.Select((b, index) => index <= _partLength - 1 ? buffer[index] : (byte)0);
_partLength = BitConverter.ToInt32(length.ToArray(), 0);
State = EnumState.Data;
_buffer.Position = 0;
}
break;
case EnumState.Data:
_buffer.WriteByte(value);
if (_buffer.Position == _partLength)
{
var buffer = _buffer.ToArray();
var length = new List<byte>(8) { 0, 0, 0, 0, 0, 0, 0, 0 }.Select((v, index) => index <= _partLength - 1 ? buffer[index] : (byte)0);
Array.Reverse(buffer, 0, _partLength);
switch (Pattern)
{
case EnumPattern.STRING_SHORT_PATTERN:
case EnumPattern.STRING_PATTERN:
OnDeserializedValue(Encoding.UTF8.GetString(_buffer.ToArray()));
break;
case EnumPattern.UNSIGNED_INT_PATTERN:
OnDeserializedValue(BitConverter.ToUInt32(length.ToArray(), 0));
break;
case EnumPattern.INT_PATTERN:
OnDeserializedValue(BitConverter.ToInt32(length.ToArray(), 0));
break;
case EnumPattern.UNSIGNED_LONG_SHORT_PATTERN:
case EnumPattern.UNSIGNED_LONG_PATTERN:
OnDeserializedValue(BitConverter.ToUInt64(length.ToArray(), 0));
break;
case EnumPattern.LONG_SHORT_PATTERN:
case EnumPattern.LONG_PATTERN:
OnDeserializedValue(BitConverter.ToInt64(length.ToArray(), 0));
break;
case EnumPattern.DOUBLE_PATTERN:
OnDeserializedValue(BitConverter.ToDouble(length.ToArray(), 0));
break;
case EnumPattern.FLOAT_PATTERN:
OnDeserializedValue(BitConverter.ToSingle(length.ToArray(), 0));
break;
case EnumPattern.BYTE_ARRAY_SHORT_PATTERN:
case EnumPattern.BYTE_ARRAY_PATTERN:
OnDeserializedValue(_buffer.ToArray());
break;
}
_buffer = new MemoryStream();
}
break;
}
}
}
internal static class PatternHelper
{
internal static EnumPattern RetrieveFlagPattern(this byte input)
{
foreach (var pattern in ((EnumPattern[])Enum.GetValues(typeof(EnumPattern))).OrderByDescending(p => p))
if ((input & (byte)pattern) == (byte)pattern)
return pattern;
return EnumPattern.DOES_NOT_EXIST;
}
internal static int RetrievePartLength(this byte input, EnumPattern pattern) => input & ~(byte)pattern;
}
internal class BinarySerializer
{
internal static MemoryStream _buffer = new MemoryStream();
public byte[] Serialize(Message message)
{
byte[] output;
SerializeValue(message.Count);
SerializeValue(message.Type);
foreach (var value in message)
SerializeValue(value);
output = _buffer.ToArray();
_buffer = new MemoryStream();
return output;
}
private void SerializeValue(object value)
{
switch (Type.GetTypeCode(value.GetType()))
{
case TypeCode.String:
Writer.WriteTagWithLength(Encoding.UTF8.GetBytes(value as string).Length, EnumPattern.STRING_SHORT_PATTERN, EnumPattern.STRING_PATTERN);
Writer.Write(Encoding.UTF8.GetBytes(value as string));
break;
case TypeCode.Int32:
Writer.WriteTagWithLength((int)value, EnumPattern.UNSIGNED_INT_SHORT_PATTERN, EnumPattern.INT_PATTERN);
break;
case TypeCode.UInt32:
Writer.WriteBottomPatternAndBytes(EnumPattern.UNSIGNED_INT_PATTERN, LittleEndianToNetworkOrderBitConverter.GetBytes((uint)value));
break;
case TypeCode.Int64:
Writer.WriteLongPattern(EnumPattern.LONG_SHORT_PATTERN, EnumPattern.LONG_PATTERN, LittleEndianToNetworkOrderBitConverter.GetBytes((long)value));
break;
case TypeCode.UInt64:
Writer.WriteLongPattern(EnumPattern.UNSIGNED_LONG_SHORT_PATTERN, EnumPattern.UNSIGNED_LONG_PATTERN, LittleEndianToNetworkOrderBitConverter.GetBytes((ulong)value));
break;
case TypeCode.Double:
Writer.Write(EnumPattern.DOUBLE_PATTERN);
Writer.Write(LittleEndianToNetworkOrderBitConverter.GetBytes((double)value));
break;
case TypeCode.Single:
Writer.Write(EnumPattern.FLOAT_PATTERN);
Writer.Write(LittleEndianToNetworkOrderBitConverter.GetBytes((float)value));
break;
case TypeCode.Boolean:
Writer.Write((bool)value ? EnumPattern.BOOLEAN_TRUE_PATTERN : EnumPattern.BOOLEAN_FALSE_PATTERN);
break;
case TypeCode.Object:
if (!(value is byte[]))
break;
var array = (byte[])value;
Writer.WriteTagWithLength(array.Length, EnumPattern.BYTE_ARRAY_SHORT_PATTERN, EnumPattern.BYTE_ARRAY_PATTERN);
Writer.Write(array);
break;
}
}
private static class Writer
{
internal static void WriteTagWithLength(int length, EnumPattern topPattern, EnumPattern bottomPattern)
{
if (length > 63 || length < 0)
{
var bytes = LittleEndianToNetworkOrderBitConverter.GetBytes(length);
WriteBottomPatternAndBytes(bottomPattern, bytes);
}
else
{
_buffer.WriteByte((byte)((int)topPattern | length));
}
}
internal static void WriteBottomPatternAndBytes(EnumPattern pattern, byte[] bytes)
{
var counter = bytes[0] != 0 ? 3 : bytes[1] != 0 ? 2 : bytes[2] != 0 ? 1 : 0;
_buffer.WriteByte((byte)((int)pattern | counter));
_buffer.Write(bytes, bytes.Length - counter - 1, counter + 1);
}
internal static void WriteLongPattern(EnumPattern shortPattern, EnumPattern longPattern, byte[] bytes)
{
var counter = 0;
for (var nc = 0; nc != 7; nc++)
{
if (bytes[nc] != 0)
{
counter = 7 - nc;
break;
}
}
if (counter > 3)
_buffer.WriteByte((byte)((byte)longPattern | counter - 4));
else
_buffer.WriteByte((byte)((byte)shortPattern | counter));
_buffer.Write(bytes, bytes.Length - counter - 1, counter + 1);
}
internal static void Write(EnumPattern pattern) => Write((byte)pattern);
internal static void Write(byte value)
{
_buffer.WriteByte(value);
}
internal static void Write(byte[] value)
{
_buffer.Write(value, 0, value.Length);
}
}
}
internal static class LittleEndianToNetworkOrderBitConverter
{
public static byte[] GetBytes(int value) => ReverseArray(BitConverter.GetBytes(value), 4, 0);
public static byte[] GetBytes(uint value) => ReverseArray(BitConverter.GetBytes(value), 4, 0);
public static byte[] GetBytes(long value) => ReverseArray(BitConverter.GetBytes(value), 8, 0);
public static byte[] GetBytes(ushort value) => ReverseArray(BitConverter.GetBytes(value), 2, 0);
public static byte[] GetBytes(ulong value) => ReverseArray(BitConverter.GetBytes(value), 8, 0);
public static byte[] GetBytes(float value) => ReverseArray(BitConverter.GetBytes(value), 4, 0);
public static byte[] GetBytes(double value) => ReverseArray(BitConverter.GetBytes(value), 8, 0);
public static int ToInt32(byte[] value, int startIndex, int length) => BitConverter.ToInt32(ReverseArray(value, length, startIndex), startIndex);
public static uint ToUInt32(byte[] value, int startIndex, int length) => BitConverter.ToUInt32(ReverseArray(value, length, startIndex), startIndex);
public static long ToInt64(byte[] value, int startIndex, int length) => BitConverter.ToInt64(ReverseArray(value, length, startIndex), startIndex);
public static ulong ToUInt64(byte[] value, int startIndex, int length) => BitConverter.ToUInt64(ReverseArray(value, length, startIndex), startIndex);
public static float ToSingle(byte[] value, int startIndex, int length) => BitConverter.ToSingle(ReverseArray(value, length, startIndex), startIndex);
public static double ToDouble(byte[] value, int startIndex, int length) => BitConverter.ToDouble(ReverseArray(value, length, startIndex), startIndex);
private static byte[] ReverseArray(byte[] input, int length, int startIndex)
{
Array.Reverse(input, startIndex, length);
return input;
}
}
public class Message : IEnumerable<object>
{
public string Type { get; internal set; }
public List<object> Values { get; internal set; } = new List<object>();
public int Count => this.Values.Count;
public Message(string type)
{
if (string.IsNullOrEmpty(type))
throw new ArgumentNullException("type");
this.Type = type;
}
public Message(string type, params object[] parameters)
{
if (string.IsNullOrEmpty(type))
throw new ArgumentNullException("type");
if (parameters == null)
throw new ArgumentNullException("parameters");
this.Type = type;
this.Add(parameters);
}
public static Message Create(string type, params object[] parameters)
{
if (string.IsNullOrEmpty(type))
throw new Exception("You must specify a type for the PlayerIO message.");
var message = new Message(type);
message.Add(parameters);
return message;
}
public IEnumerator<object> GetEnumerator() => this.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
public override string ToString()
{
var sb = new StringBuilder("");
sb.AppendLine(string.Concat(" msg.Type= ", this.Type, ", ", this.Values.Count, " entries"));
for (var i = 0; i < this.Values.Count; i++)
sb.AppendLine(string.Concat(" msg[", i, "] = ", this.Values[i], " (", this.Values[i].GetType().Name, ")"));
return sb.ToString();
}
public object this[uint index] => this.Values[(int)index];
public string GetString(uint index) => (string)this[index];
public byte[] GetByteArray(uint index) => (byte[])this[index];
public bool GetBoolean(uint index) => (bool)this[index];
public double GetDouble(uint index) => (double)this[index];
public float GetFloat(uint index) => (float)this[index];
public int GetInteger(uint index) => (int)this[index];
public int GetInt(uint index) => (int)this[index];
public uint GetUInt(uint index) => (uint)this[index];
public uint GetUnsignedInteger(uint index) => (uint)this[index];
public long GetLong(uint index) => (long)this[index];
public ulong GetULong(uint index) => (ulong)this[index];
public ulong GetUnsignedLong(uint index) => (ulong)this[index];
public void Add(string value) => Add(value);
public void Add(int value) => Add(value);
public void Add(uint value) => Add(value);
public void Add(long value) => Add(value);
public void Add(ulong value) => Add(value);
public void Add(byte[] value) => Add(value);
public void Add(float value) => Add(value);
public void Add(double value) => Add(value);
public void Add(bool value) => Add(value);
public void Add(params object[] parameters)
{
if (parameters.Length == 0)
return;
var allowedTypes = new List<Type>() {
typeof(string), typeof(int), typeof(uint),
typeof(long), typeof(ulong), typeof(float),
typeof(double), typeof(bool), typeof(byte[])
};
foreach (var value in parameters)
{
if (value == null)
throw new Exception("PlayerIO messages do not support null objects.");
if (!allowedTypes.Contains(value.GetType()))
throw new Exception($"PlayerIO messages do not support objects of type '{value.GetType()}'");
this.Values.Add(value);
}
}
}
}
namespace PlayerIOLite.Messages
{
[ProtoContract]
internal class Error
{
[ProtoMember(1)]
public ErrorCode ErrorCode { get; set; } = ErrorCode.InternalError;
[ProtoMember(2)]
public string Message { get; set; }
}
[ProtoContract]
internal class SimpleConnectArgs
{
[ProtoMember(1)]
public string GameId { get; set; }
[ProtoMember(2)]
public string UsernameOrEmail { get; set; }
[ProtoMember(3)]
public string Password { get; set; }
}
[ProtoContract]
internal class SimpleConnectOutput
{
[ProtoMember(1)]
public string Token { get; set; }
[ProtoMember(2)]
public string UserId { get; set; }
[ProtoMember(3)]
public bool ShowBranding { get; set; }
[ProtoMember(4)]
public string GameFSRedirectMap { get; set; }
[ProtoMember(5)]
public string PartnerId { get; set; }
}
[ProtoContract]
internal class CreateJoinRoomArgs
{
[ProtoMember(1)]
public string RoomId { get; set; }
[ProtoMember(2)]
public string ServerType { get; set; }
[ProtoMember(3)]
public bool Visible { get; set; }
[ProtoMember(4)]
public Dictionary<string, string> RoomData { get; set; }
[ProtoMember(5)]
public Dictionary<string, string> JoinData { get; set; }
[ProtoMember(6)]
public bool IsDevRoom { get; set; }
}
[ProtoContract]
internal class CreateJoinRoomOutput
{
[ProtoMember(1)]
public string RoomId { get; set; }
[ProtoMember(2)]
public string JoinKey { get; set; }
[ProtoMember(3)]
public ServerEndpoint[] Endpoints { get; set; }
}
[ProtoContract]
internal class ServerEndpoint
{
[ProtoMember(1)]
public string Address { get; set; }
[ProtoMember(2)]
public int Port { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment