Add files via upload
This commit is contained in:
parent
45f82b812d
commit
432b6d5270
9 changed files with 1259 additions and 0 deletions
51
BiDictionary.cs
Normal file
51
BiDictionary.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// Source: https://stackoverflow.com/questions/255341/getting-multiple-keys-of-specified-value-of-a-generic-dictionary#255630
|
||||||
|
//
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LightReflectiveMirror
|
||||||
|
{
|
||||||
|
class BiDictionary<TFirst, TSecond>
|
||||||
|
{
|
||||||
|
IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
|
||||||
|
IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();
|
||||||
|
|
||||||
|
public void Add(TFirst first, TSecond second)
|
||||||
|
{
|
||||||
|
if (firstToSecond.ContainsKey(first) ||
|
||||||
|
secondToFirst.ContainsKey(second))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Duplicate first or second");
|
||||||
|
}
|
||||||
|
firstToSecond.Add(first, second);
|
||||||
|
secondToFirst.Add(second, first);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetByFirst(TFirst first, out TSecond second)
|
||||||
|
{
|
||||||
|
return firstToSecond.TryGetValue(first, out second);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(TFirst first)
|
||||||
|
{
|
||||||
|
secondToFirst.Remove(firstToSecond[first]);
|
||||||
|
firstToSecond.Remove(first);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetBySecond(TSecond second, out TFirst first)
|
||||||
|
{
|
||||||
|
return secondToFirst.TryGetValue(second, out first);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TSecond GetByFirst(TFirst first)
|
||||||
|
{
|
||||||
|
return firstToSecond[first];
|
||||||
|
}
|
||||||
|
|
||||||
|
public TFirst GetBySecond(TSecond second)
|
||||||
|
{
|
||||||
|
return secondToFirst[second];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
Compression.cs
Normal file
60
Compression.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LightReflectiveMirror.Compression
|
||||||
|
{
|
||||||
|
internal static class StringCompressor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Compresses the string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The text.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string Compress(this string text)
|
||||||
|
{
|
||||||
|
byte[] buffer = Encoding.UTF8.GetBytes(text);
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
|
||||||
|
{
|
||||||
|
gZipStream.Write(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
|
var compressedData = new byte[memoryStream.Length];
|
||||||
|
memoryStream.Read(compressedData, 0, compressedData.Length);
|
||||||
|
|
||||||
|
var gZipBuffer = new byte[compressedData.Length + 4];
|
||||||
|
Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
|
||||||
|
Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
|
||||||
|
return Convert.ToBase64String(gZipBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decompresses the string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="compressedText">The compressed text.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string Decompress(this string compressedText)
|
||||||
|
{
|
||||||
|
byte[] gZipBuffer = Convert.FromBase64String(compressedText);
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
int dataLength = BitConverter.ToInt32(gZipBuffer, 0);
|
||||||
|
memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4);
|
||||||
|
|
||||||
|
var buffer = new byte[dataLength];
|
||||||
|
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
|
||||||
|
{
|
||||||
|
gZipStream.Read(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Config.cs
Normal file
20
Config.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LightReflectiveMirror
|
||||||
|
{
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
public string TransportDLL = "MultiCompiled.dll";
|
||||||
|
public string TransportClass = "Mirror.SimpleWebTransport";
|
||||||
|
public string AuthenticationKey = "Secret Auth Key";
|
||||||
|
public int UpdateLoopTime = 10;
|
||||||
|
public int UpdateHeartbeatInterval = 100;
|
||||||
|
public bool UseEndpoint = true;
|
||||||
|
public ushort EndpointPort = 8080;
|
||||||
|
public bool EndpointServerList = true;
|
||||||
|
public bool EnableNATPunchtroughServer = true;
|
||||||
|
public ushort NATPunchtroughPort = 7776;
|
||||||
|
}
|
||||||
|
}
|
||||||
120
DataHandler.cs
Normal file
120
DataHandler.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LightReflectiveMirror
|
||||||
|
{
|
||||||
|
public static class DataHandler
|
||||||
|
{
|
||||||
|
public static void WriteByte(this byte[] data, ref int position, byte value)
|
||||||
|
{
|
||||||
|
data[position] = value;
|
||||||
|
position += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte ReadByte(this byte[] data, ref int position)
|
||||||
|
{
|
||||||
|
byte value = data[position];
|
||||||
|
position += 1;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteBool(this byte[] data, ref int position, bool value)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed(byte* dataPtr = &data[position])
|
||||||
|
{
|
||||||
|
bool* valuePtr = (bool*)dataPtr;
|
||||||
|
*valuePtr = value;
|
||||||
|
position += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ReadBool(this byte[] data, ref int position)
|
||||||
|
{
|
||||||
|
bool value = BitConverter.ToBoolean(data, position);
|
||||||
|
position += 1;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteString(this byte[] data, ref int position, string value)
|
||||||
|
{
|
||||||
|
data.WriteInt(ref position, value.Length);
|
||||||
|
for (int i = 0; i < value.Length; i++)
|
||||||
|
data.WriteChar(ref position, value[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReadString(this byte[] data, ref int position)
|
||||||
|
{
|
||||||
|
string value = default;
|
||||||
|
|
||||||
|
int stringSize = data.ReadInt(ref position);
|
||||||
|
|
||||||
|
for (int i = 0; i < stringSize; i++)
|
||||||
|
value += data.ReadChar(ref position);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteBytes(this byte[] data, ref int position, byte[] value)
|
||||||
|
{
|
||||||
|
data.WriteInt(ref position, value.Length);
|
||||||
|
for (int i = 0; i < value.Length; i++)
|
||||||
|
data.WriteByte(ref position, value[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] ReadBytes(this byte[] data, ref int position)
|
||||||
|
{
|
||||||
|
int byteSize = data.ReadInt(ref position);
|
||||||
|
|
||||||
|
byte[] value = new byte[byteSize];
|
||||||
|
|
||||||
|
for (int i = 0; i < byteSize; i++)
|
||||||
|
value[i] = data.ReadByte(ref position);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteChar(this byte[] data, ref int position, char value)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* dataPtr = &data[position])
|
||||||
|
{
|
||||||
|
char* valuePtr = (char*)dataPtr;
|
||||||
|
*valuePtr = value;
|
||||||
|
position += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static char ReadChar(this byte[] data, ref int position)
|
||||||
|
{
|
||||||
|
char value = BitConverter.ToChar(data, position);
|
||||||
|
position += 2;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteInt(this byte[] data, ref int position, int value)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* dataPtr = &data[position])
|
||||||
|
{
|
||||||
|
int* valuePtr = (int*)dataPtr;
|
||||||
|
*valuePtr = value;
|
||||||
|
position += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ReadInt(this byte[] data, ref int position)
|
||||||
|
{
|
||||||
|
int value = BitConverter.ToInt32(data, position);
|
||||||
|
position += 4;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
Endpoint.cs
Normal file
101
Endpoint.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
using Grapevine;
|
||||||
|
using LightReflectiveMirror.Compression;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LightReflectiveMirror.Endpoints
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
struct RelayStats
|
||||||
|
{
|
||||||
|
public int ConnectedClients;
|
||||||
|
public int RoomCount;
|
||||||
|
public int PublicRoomCount;
|
||||||
|
public TimeSpan Uptime;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RestResource]
|
||||||
|
public class Endpoint
|
||||||
|
{
|
||||||
|
private List<Room> _rooms { get => Program.instance.GetRooms(); }
|
||||||
|
|
||||||
|
private RelayStats _stats { get => new RelayStats
|
||||||
|
{
|
||||||
|
ConnectedClients = Program.instance.GetConnections(),
|
||||||
|
RoomCount = Program.instance.GetRooms().Count,
|
||||||
|
PublicRoomCount = Program.instance.GetPublicRoomCount(),
|
||||||
|
Uptime = Program.instance.GetUptime()
|
||||||
|
}; }
|
||||||
|
|
||||||
|
[RestRoute("Get", "/api/stats")]
|
||||||
|
public async Task Stats(IHttpContext context)
|
||||||
|
{
|
||||||
|
string json = JsonConvert.SerializeObject(_stats, Formatting.Indented);
|
||||||
|
await context.Response.SendResponseAsync(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RestRoute("Get", "/api/servers")]
|
||||||
|
public async Task ServerList(IHttpContext context)
|
||||||
|
{
|
||||||
|
if (Program.conf.EndpointServerList)
|
||||||
|
{
|
||||||
|
string json = JsonConvert.SerializeObject(_rooms, Formatting.Indented);
|
||||||
|
await context.Response.SendResponseAsync(json);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await context.Response.SendResponseAsync(HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RestRoute("Get", "/api/compressed/servers")]
|
||||||
|
public async Task ServerListCompressed(IHttpContext context)
|
||||||
|
{
|
||||||
|
if (Program.conf.EndpointServerList)
|
||||||
|
{
|
||||||
|
string json = JsonConvert.SerializeObject(_rooms);
|
||||||
|
await context.Response.SendResponseAsync(json.Compress());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await context.Response.SendResponseAsync(HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EndpointServer
|
||||||
|
{
|
||||||
|
public bool Start(ushort port = 8080)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
|
||||||
|
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var server = new RestServerBuilder(new ServiceCollection(), config,
|
||||||
|
(services) =>
|
||||||
|
{
|
||||||
|
services.AddLogging(configure => configure.AddConsole());
|
||||||
|
services.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.None);
|
||||||
|
}, (server) =>
|
||||||
|
{
|
||||||
|
server.Prefixes.Add($"http://*:{port}/");
|
||||||
|
}).Build();
|
||||||
|
|
||||||
|
server.Router.Options.SendExceptionMessages = false;
|
||||||
|
server.Start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
295
Program.cs
Normal file
295
Program.cs
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LightReflectiveMirror.Endpoints;
|
||||||
|
using Mirror;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace LightReflectiveMirror
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
public static Transport transport;
|
||||||
|
public static Program instance;
|
||||||
|
public static Config conf;
|
||||||
|
|
||||||
|
private RelayHandler _relay;
|
||||||
|
private MethodInfo _awakeMethod;
|
||||||
|
private MethodInfo _startMethod;
|
||||||
|
private MethodInfo _updateMethod;
|
||||||
|
private MethodInfo _lateUpdateMethod;
|
||||||
|
|
||||||
|
private DateTime _startupTime;
|
||||||
|
|
||||||
|
private List<int> _currentConnections = new List<int>();
|
||||||
|
public Dictionary<int, IPEndPoint> NATConnections = new Dictionary<int, IPEndPoint>();
|
||||||
|
private BiDictionary<int, string> _pendingNATPunches = new BiDictionary<int, string>();
|
||||||
|
private int _currentHeartbeatTimer = 0;
|
||||||
|
|
||||||
|
private byte[] _NATRequest = new byte[500];
|
||||||
|
private int _NATRequestPosition = 0;
|
||||||
|
|
||||||
|
private UdpClient _punchServer;
|
||||||
|
|
||||||
|
private const string CONFIG_PATH = "config.json";
|
||||||
|
|
||||||
|
public static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
public int GetConnections() => _currentConnections.Count;
|
||||||
|
public TimeSpan GetUptime() => DateTime.Now - _startupTime;
|
||||||
|
public int GetPublicRoomCount() => _relay.rooms.Where(x => x.isPublic).Count();
|
||||||
|
public List<Room> GetRooms() => _relay.rooms;
|
||||||
|
|
||||||
|
public async Task MainAsync()
|
||||||
|
{
|
||||||
|
WriteTitle();
|
||||||
|
instance = this;
|
||||||
|
_startupTime = DateTime.Now;
|
||||||
|
|
||||||
|
if (!File.Exists(CONFIG_PATH))
|
||||||
|
{
|
||||||
|
File.WriteAllText(CONFIG_PATH, JsonConvert.SerializeObject(new Config(), Formatting.Indented));
|
||||||
|
WriteLogMessage("A config.json file was generated. Please configure it to the proper settings and re-run!", ConsoleColor.Yellow);
|
||||||
|
Console.ReadKey();
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
conf = JsonConvert.DeserializeObject<Config>(File.ReadAllText(CONFIG_PATH));
|
||||||
|
WriteLogMessage("Loading Assembly... ", ConsoleColor.White, true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var asm = Assembly.LoadFile(Directory.GetCurrentDirectory() + @"\" + conf.TransportDLL);
|
||||||
|
WriteLogMessage($"OK", ConsoleColor.Green);
|
||||||
|
|
||||||
|
WriteLogMessage("\nLoading Transport Class... ", ConsoleColor.White, true);
|
||||||
|
|
||||||
|
transport = asm.CreateInstance(conf.TransportClass) as Transport;
|
||||||
|
|
||||||
|
if (transport != null)
|
||||||
|
{
|
||||||
|
var transportClass = asm.GetType(conf.TransportClass);
|
||||||
|
WriteLogMessage("OK", ConsoleColor.Green);
|
||||||
|
|
||||||
|
WriteLogMessage("\nLoading Transport Methods... ", ConsoleColor.White, true);
|
||||||
|
CheckMethods(transportClass);
|
||||||
|
WriteLogMessage("OK", ConsoleColor.Green);
|
||||||
|
|
||||||
|
WriteLogMessage("\nInvoking Transport Methods...");
|
||||||
|
|
||||||
|
if (_awakeMethod != null)
|
||||||
|
_awakeMethod.Invoke(transport, null);
|
||||||
|
|
||||||
|
if (_startMethod != null)
|
||||||
|
_startMethod.Invoke(transport, null);
|
||||||
|
|
||||||
|
WriteLogMessage("\nStarting Transport... ", ConsoleColor.White, true);
|
||||||
|
|
||||||
|
transport.OnServerError = (clientID, error) =>
|
||||||
|
{
|
||||||
|
WriteLogMessage($"Transport Error, Client: {clientID}, Error: {error}", ConsoleColor.Red);
|
||||||
|
};
|
||||||
|
|
||||||
|
transport.OnServerConnected = (clientID) =>
|
||||||
|
{
|
||||||
|
WriteLogMessage($"Transport Connected, Client: {clientID}", ConsoleColor.Cyan);
|
||||||
|
_currentConnections.Add(clientID);
|
||||||
|
_relay.ClientConnected(clientID);
|
||||||
|
|
||||||
|
if (conf.EnableNATPunchtroughServer)
|
||||||
|
{
|
||||||
|
string natID = Guid.NewGuid().ToString();
|
||||||
|
_pendingNATPunches.Add(clientID, natID);
|
||||||
|
_NATRequestPosition = 0;
|
||||||
|
_NATRequest.WriteByte(ref _NATRequestPosition, (byte)OpCodes.RequestNATConnection);
|
||||||
|
_NATRequest.WriteString(ref _NATRequestPosition, natID);
|
||||||
|
transport.ServerSend(clientID, 0, new ArraySegment<byte>(_NATRequest, 0, _NATRequestPosition));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_relay = new RelayHandler(transport.GetMaxPacketSize(0));
|
||||||
|
|
||||||
|
transport.OnServerDataReceived = _relay.HandleMessage;
|
||||||
|
transport.OnServerDisconnected = (clientID) =>
|
||||||
|
{
|
||||||
|
_currentConnections.Remove(clientID);
|
||||||
|
_relay.HandleDisconnect(clientID);
|
||||||
|
|
||||||
|
if(NATConnections.ContainsKey(clientID))
|
||||||
|
NATConnections.Remove(clientID);
|
||||||
|
|
||||||
|
if(_pendingNATPunches.TryGetByFirst(clientID, out _))
|
||||||
|
_pendingNATPunches.Remove(clientID);
|
||||||
|
};
|
||||||
|
|
||||||
|
transport.ServerStart();
|
||||||
|
|
||||||
|
WriteLogMessage("OK", ConsoleColor.Green);
|
||||||
|
|
||||||
|
if (conf.UseEndpoint)
|
||||||
|
{
|
||||||
|
WriteLogMessage("\nStarting Endpoint Service... ", ConsoleColor.White, true);
|
||||||
|
var endpoint = new EndpointServer();
|
||||||
|
|
||||||
|
if (endpoint.Start(conf.EndpointPort))
|
||||||
|
{
|
||||||
|
WriteLogMessage("OK", ConsoleColor.Green);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLogMessage("FAILED\nPlease run as administrator or check if port is in use.", ConsoleColor.DarkRed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf.EnableNATPunchtroughServer)
|
||||||
|
{
|
||||||
|
WriteLogMessage("\nStarting NatPunchthrough Socket... ", ConsoleColor.White, true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_punchServer = new UdpClient(conf.NATPunchtroughPort);
|
||||||
|
|
||||||
|
WriteLogMessage("OK\n", ConsoleColor.Green, true);
|
||||||
|
|
||||||
|
WriteLogMessage("\nStarting NatPunchthrough Thread... ", ConsoleColor.White, true);
|
||||||
|
var natThread = new Thread(new ThreadStart(RunNATPunchLoop));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
natThread.Start();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
WriteLogMessage("FAILED\n" + e, ConsoleColor.DarkRed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
WriteLogMessage("FAILED\nCheck if port is in use.", ConsoleColor.DarkRed, true);
|
||||||
|
Console.WriteLine(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLogMessage("FAILED\nClass not found, make sure to included namespaces!", ConsoleColor.DarkRed);
|
||||||
|
Console.ReadKey();
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
WriteLogMessage("FAILED\nException: " + e, ConsoleColor.DarkRed);
|
||||||
|
Console.ReadKey();
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (_updateMethod != null) _updateMethod.Invoke(transport, null);
|
||||||
|
if (_lateUpdateMethod != null) _lateUpdateMethod.Invoke(transport, null);
|
||||||
|
|
||||||
|
_currentHeartbeatTimer++;
|
||||||
|
|
||||||
|
if(_currentHeartbeatTimer >= conf.UpdateHeartbeatInterval)
|
||||||
|
{
|
||||||
|
_currentHeartbeatTimer = 0;
|
||||||
|
|
||||||
|
for(int i = 0; i < _currentConnections.Count; i++)
|
||||||
|
transport.ServerSend(_currentConnections[i], 0, new ArraySegment<byte>(new byte[] { 200 }));
|
||||||
|
|
||||||
|
GC.Collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(conf.UpdateLoopTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RunNATPunchLoop()
|
||||||
|
{
|
||||||
|
WriteLogMessage("OK\n", ConsoleColor.Green);
|
||||||
|
IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, conf.NATPunchtroughPort);
|
||||||
|
|
||||||
|
// Stock Data server sends to everyone:
|
||||||
|
var serverResponse = new byte[1] { 1 };
|
||||||
|
|
||||||
|
byte[] readData;
|
||||||
|
bool isConnectionEstablishment;
|
||||||
|
int pos;
|
||||||
|
string connectionID;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
readData = _punchServer.Receive(ref remoteEndpoint);
|
||||||
|
pos = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
isConnectionEstablishment = readData.ReadBool(ref pos);
|
||||||
|
|
||||||
|
if (isConnectionEstablishment)
|
||||||
|
{
|
||||||
|
connectionID = readData.ReadString(ref pos);
|
||||||
|
|
||||||
|
if (_pendingNATPunches.TryGetBySecond(connectionID, out pos))
|
||||||
|
{
|
||||||
|
NATConnections.Add(pos, new IPEndPoint(remoteEndpoint.Address, remoteEndpoint.Port));
|
||||||
|
_pendingNATPunches.Remove(pos);
|
||||||
|
Console.WriteLine("Client Successfully Established Puncher Connection. " + remoteEndpoint.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_punchServer.Send(serverResponse, 1, remoteEndpoint);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore, packet got fucked up or something.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteLogMessage(string message, ConsoleColor color = ConsoleColor.White, bool oneLine = false)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = color;
|
||||||
|
if (oneLine)
|
||||||
|
Console.Write(message);
|
||||||
|
else
|
||||||
|
Console.WriteLine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckMethods(Type type)
|
||||||
|
{
|
||||||
|
_awakeMethod = type.GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
_startMethod = type.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
_updateMethod = type.GetMethod("Update", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
_lateUpdateMethod = type.GetMethod("LateUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteTitle()
|
||||||
|
{
|
||||||
|
string t = @"
|
||||||
|
w c(..)o (
|
||||||
|
_ _____ __ __ \__(-) __)
|
||||||
|
| | | __ \ | \/ | /\ (
|
||||||
|
| | | |__) || \ / | /(_)___)
|
||||||
|
| | | _ / | |\/| | w /|
|
||||||
|
| |____ | | \ \ | | | | | \
|
||||||
|
|______||_| \_\|_| |_| m m copyright monkesoft 2021
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
string load = $"Chimp Event Listener Initializing... OK" +
|
||||||
|
"\nHarambe Memorial Initializing... OK" +
|
||||||
|
"\nBananas Initializing... OK\n";
|
||||||
|
|
||||||
|
WriteLogMessage(t, ConsoleColor.Green);
|
||||||
|
WriteLogMessage(load, ConsoleColor.Cyan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
340
RelayHandler.cs
Normal file
340
RelayHandler.cs
Normal file
|
|
@ -0,0 +1,340 @@
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LightReflectiveMirror
|
||||||
|
{
|
||||||
|
public class RelayHandler
|
||||||
|
{
|
||||||
|
public List<Room> rooms = new List<Room>();
|
||||||
|
private List<int> _pendingAuthentication = new List<int>();
|
||||||
|
private ArrayPool<byte> _sendBuffers;
|
||||||
|
private int _maxPacketSize = 0;
|
||||||
|
|
||||||
|
public RelayHandler(int maxPacketSize)
|
||||||
|
{
|
||||||
|
this._maxPacketSize = maxPacketSize;
|
||||||
|
_sendBuffers = ArrayPool<byte>.Create(maxPacketSize, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClientConnected(int clientId)
|
||||||
|
{
|
||||||
|
_pendingAuthentication.Add(clientId);
|
||||||
|
var buffer = _sendBuffers.Rent(1);
|
||||||
|
int pos = 0;
|
||||||
|
buffer.WriteByte(ref pos, (byte)OpCodes.AuthenticationRequest);
|
||||||
|
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(buffer, 0, pos));
|
||||||
|
_sendBuffers.Return(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleMessage(int clientId, ArraySegment<byte> segmentData, int channel)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = segmentData.Array;
|
||||||
|
int pos = segmentData.Offset;
|
||||||
|
|
||||||
|
OpCodes opcode = (OpCodes)data.ReadByte(ref pos);
|
||||||
|
|
||||||
|
if (_pendingAuthentication.Contains(clientId))
|
||||||
|
{
|
||||||
|
if (opcode == OpCodes.AuthenticationResponse)
|
||||||
|
{
|
||||||
|
string authResponse = data.ReadString(ref pos);
|
||||||
|
if (authResponse == Program.conf.AuthenticationKey)
|
||||||
|
{
|
||||||
|
_pendingAuthentication.Remove(clientId);
|
||||||
|
int writePos = 0;
|
||||||
|
var sendBuffer = _sendBuffers.Rent(1);
|
||||||
|
sendBuffer.WriteByte(ref writePos, (byte)OpCodes.Authenticated);
|
||||||
|
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, writePos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (opcode)
|
||||||
|
{
|
||||||
|
case OpCodes.CreateRoom:
|
||||||
|
CreateRoom(clientId, data.ReadInt(ref pos), data.ReadString(ref pos), data.ReadBool(ref pos), data.ReadString(ref pos), data.ReadBool(ref pos), data.ReadString(ref pos), data.ReadBool(ref pos), data.ReadInt(ref pos));
|
||||||
|
break;
|
||||||
|
case OpCodes.RequestID:
|
||||||
|
SendClientID(clientId);
|
||||||
|
break;
|
||||||
|
case OpCodes.LeaveRoom:
|
||||||
|
LeaveRoom(clientId);
|
||||||
|
break;
|
||||||
|
case OpCodes.JoinServer:
|
||||||
|
JoinRoom(clientId, data.ReadInt(ref pos), data.ReadBool(ref pos), data.ReadString(ref pos));
|
||||||
|
break;
|
||||||
|
case OpCodes.KickPlayer:
|
||||||
|
LeaveRoom(data.ReadInt(ref pos), clientId);
|
||||||
|
break;
|
||||||
|
case OpCodes.SendData:
|
||||||
|
ProcessData(clientId, data.ReadBytes(ref pos), channel, data.ReadInt(ref pos));
|
||||||
|
break;
|
||||||
|
case OpCodes.UpdateRoomData:
|
||||||
|
var plyRoom = GetRoomForPlayer(clientId);
|
||||||
|
|
||||||
|
if (plyRoom == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool newName = data.ReadBool(ref pos);
|
||||||
|
if (newName)
|
||||||
|
plyRoom.serverName = data.ReadString(ref pos);
|
||||||
|
|
||||||
|
bool newData = data.ReadBool(ref pos);
|
||||||
|
if (newData)
|
||||||
|
plyRoom.serverData = data.ReadString(ref pos);
|
||||||
|
|
||||||
|
bool newPublicStatus = data.ReadBool(ref pos);
|
||||||
|
if (newPublicStatus)
|
||||||
|
plyRoom.isPublic = data.ReadBool(ref pos);
|
||||||
|
|
||||||
|
bool newPlayerCap = data.ReadBool(ref pos);
|
||||||
|
if (newPlayerCap)
|
||||||
|
plyRoom.maxPlayers = data.ReadInt(ref pos);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Do Nothing. Client probably sent some invalid data.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleDisconnect(int clientId) => LeaveRoom(clientId);
|
||||||
|
|
||||||
|
void ProcessData(int clientId, byte[] clientData, int channel, int sendTo = -1)
|
||||||
|
{
|
||||||
|
Room playersRoom = GetRoomForPlayer(clientId);
|
||||||
|
|
||||||
|
if(playersRoom != null)
|
||||||
|
{
|
||||||
|
Room room = playersRoom;
|
||||||
|
|
||||||
|
if(room.hostId == clientId)
|
||||||
|
{
|
||||||
|
if (room.clients.Contains(sendTo))
|
||||||
|
{
|
||||||
|
int pos = 0;
|
||||||
|
byte[] sendBuffer = _sendBuffers.Rent(_maxPacketSize);
|
||||||
|
|
||||||
|
sendBuffer.WriteByte(ref pos, (byte)OpCodes.GetData);
|
||||||
|
sendBuffer.WriteBytes(ref pos, clientData);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(sendTo, channel, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||||
|
_sendBuffers.Return(sendBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We are not the host, so send the data to the host.
|
||||||
|
int pos = 0;
|
||||||
|
byte[] sendBuffer = _sendBuffers.Rent(_maxPacketSize);
|
||||||
|
|
||||||
|
sendBuffer.WriteByte(ref pos, (byte)OpCodes.GetData);
|
||||||
|
sendBuffer.WriteBytes(ref pos, clientData);
|
||||||
|
sendBuffer.WriteInt(ref pos, clientId);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(room.hostId, channel, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||||
|
_sendBuffers.Return(sendBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Room GetRoomForPlayer(int clientId)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < rooms.Count; i++)
|
||||||
|
{
|
||||||
|
if (rooms[i].hostId == clientId)
|
||||||
|
return rooms[i];
|
||||||
|
|
||||||
|
if (rooms[i].clients.Contains(clientId))
|
||||||
|
return rooms[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoinRoom(int clientId, int serverId, bool canDirectConnect, string localIP)
|
||||||
|
{
|
||||||
|
LeaveRoom(clientId);
|
||||||
|
|
||||||
|
for(int i = 0; i < rooms.Count; i++)
|
||||||
|
{
|
||||||
|
if(rooms[i].serverId == serverId)
|
||||||
|
{
|
||||||
|
if(rooms[i].clients.Count < rooms[i].maxPlayers)
|
||||||
|
{
|
||||||
|
rooms[i].clients.Add(clientId);
|
||||||
|
|
||||||
|
int sendJoinPos = 0;
|
||||||
|
byte[] sendJoinBuffer = _sendBuffers.Rent(500);
|
||||||
|
|
||||||
|
if (canDirectConnect && Program.instance.NATConnections.ContainsKey(clientId))
|
||||||
|
{
|
||||||
|
sendJoinBuffer.WriteByte(ref sendJoinPos, (byte)OpCodes.DirectConnectIP);
|
||||||
|
|
||||||
|
if (Program.instance.NATConnections[clientId].Address.Equals(rooms[i].hostIP.Address))
|
||||||
|
sendJoinBuffer.WriteString(ref sendJoinPos, rooms[i].hostLocalIP == localIP ? "127.0.0.1" : rooms[i].hostLocalIP);
|
||||||
|
else
|
||||||
|
sendJoinBuffer.WriteString(ref sendJoinPos, rooms[i].hostIP.Address.ToString());
|
||||||
|
|
||||||
|
sendJoinBuffer.WriteInt(ref sendJoinPos, rooms[i].useNATPunch ? rooms[i].hostIP.Port : rooms[i].port);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendJoinBuffer, 0, sendJoinPos));
|
||||||
|
|
||||||
|
sendJoinPos = 0;
|
||||||
|
sendJoinBuffer.WriteByte(ref sendJoinPos, (byte)OpCodes.DirectConnectIP);
|
||||||
|
Console.WriteLine(Program.instance.NATConnections[clientId].Address.ToString());
|
||||||
|
sendJoinBuffer.WriteString(ref sendJoinPos, Program.instance.NATConnections[clientId].Address.ToString());
|
||||||
|
sendJoinBuffer.WriteInt(ref sendJoinPos, Program.instance.NATConnections[clientId].Port);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(rooms[i].hostId, 0, new ArraySegment<byte>(sendJoinBuffer, 0, sendJoinPos));
|
||||||
|
|
||||||
|
_sendBuffers.Return(sendJoinBuffer);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
sendJoinBuffer.WriteByte(ref sendJoinPos, (byte)OpCodes.ServerJoined);
|
||||||
|
sendJoinBuffer.WriteInt(ref sendJoinPos, clientId);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendJoinBuffer, 0, sendJoinPos));
|
||||||
|
Program.transport.ServerSend(rooms[i].hostId, 0, new ArraySegment<byte>(sendJoinBuffer, 0, sendJoinPos));
|
||||||
|
_sendBuffers.Return(sendJoinBuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it got to here, then the server was not found, or full. Tell the client.
|
||||||
|
int pos = 0;
|
||||||
|
byte[] sendBuffer = _sendBuffers.Rent(1);
|
||||||
|
|
||||||
|
sendBuffer.WriteByte(ref pos, (byte)OpCodes.ServerLeft);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||||
|
_sendBuffers.Return(sendBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateRoom(int clientId, int maxPlayers, string serverName, bool isPublic, string serverData, bool useDirectConnect, string hostLocalIP, bool useNatPunch, int port)
|
||||||
|
{
|
||||||
|
LeaveRoom(clientId);
|
||||||
|
|
||||||
|
IPEndPoint hostIP = null;
|
||||||
|
Program.instance.NATConnections.TryGetValue(clientId, out hostIP);
|
||||||
|
|
||||||
|
Room room = new Room
|
||||||
|
{
|
||||||
|
hostId = clientId,
|
||||||
|
maxPlayers = maxPlayers,
|
||||||
|
serverName = serverName,
|
||||||
|
isPublic = isPublic,
|
||||||
|
serverData = serverData,
|
||||||
|
clients = new List<int>(),
|
||||||
|
serverId = GetRandomServerID(),
|
||||||
|
hostIP = hostIP,
|
||||||
|
hostLocalIP = hostLocalIP,
|
||||||
|
supportsDirectConnect = hostIP == null ? false : useDirectConnect,
|
||||||
|
port = port,
|
||||||
|
useNATPunch = useNatPunch
|
||||||
|
};
|
||||||
|
|
||||||
|
rooms.Add(room);
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
byte[] sendBuffer = _sendBuffers.Rent(5);
|
||||||
|
|
||||||
|
sendBuffer.WriteByte(ref pos, (byte)OpCodes.RoomCreated);
|
||||||
|
sendBuffer.WriteInt(ref pos, clientId);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||||
|
_sendBuffers.Return(sendBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LeaveRoom(int clientId, int requiredHostId = -1)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < rooms.Count; i++)
|
||||||
|
{
|
||||||
|
if(rooms[i].hostId == clientId)
|
||||||
|
{
|
||||||
|
int pos = 0;
|
||||||
|
byte[] sendBuffer = _sendBuffers.Rent(1);
|
||||||
|
sendBuffer.WriteByte(ref pos, (byte)OpCodes.ServerLeft);
|
||||||
|
|
||||||
|
for(int x = 0; x < rooms[i].clients.Count; x++)
|
||||||
|
Program.transport.ServerSend(rooms[i].clients[x], 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||||
|
|
||||||
|
_sendBuffers.Return(sendBuffer);
|
||||||
|
rooms[i].clients.Clear();
|
||||||
|
rooms.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (requiredHostId >= 0 && rooms[i].hostId != requiredHostId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(rooms[i].clients.RemoveAll(x => x == clientId) > 0)
|
||||||
|
{
|
||||||
|
int pos = 0;
|
||||||
|
byte[] sendBuffer = _sendBuffers.Rent(5);
|
||||||
|
|
||||||
|
sendBuffer.WriteByte(ref pos, (byte)OpCodes.PlayerDisconnected);
|
||||||
|
sendBuffer.WriteInt(ref pos, clientId);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(rooms[i].hostId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||||
|
_sendBuffers.Return(sendBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendClientID(int clientId)
|
||||||
|
{
|
||||||
|
int pos = 0;
|
||||||
|
byte[] sendBuffer = _sendBuffers.Rent(5);
|
||||||
|
|
||||||
|
sendBuffer.WriteByte(ref pos, (byte)OpCodes.GetID);
|
||||||
|
sendBuffer.WriteInt(ref pos, clientId);
|
||||||
|
|
||||||
|
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
|
||||||
|
_sendBuffers.Return(sendBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetRandomServerID()
|
||||||
|
{
|
||||||
|
Random rand = new Random();
|
||||||
|
int temp = rand.Next(int.MinValue, int.MaxValue);
|
||||||
|
|
||||||
|
while (DoesServerIdExist(temp))
|
||||||
|
temp = rand.Next(int.MinValue, int.MaxValue);
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoesServerIdExist(int id)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < rooms.Count; i++)
|
||||||
|
if (rooms[i].serverId == id)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OpCodes
|
||||||
|
{
|
||||||
|
Default = 0, RequestID = 1, JoinServer = 2, SendData = 3, GetID = 4, ServerJoined = 5, GetData = 6, CreateRoom = 7, ServerLeft = 8, PlayerDisconnected = 9, RoomCreated = 10,
|
||||||
|
LeaveRoom = 11, KickPlayer = 12, AuthenticationRequest = 13, AuthenticationResponse = 14, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19, RequestNATConnection = 20,
|
||||||
|
DirectConnectIP = 21
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Room.cs
Normal file
28
Room.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace LightReflectiveMirror
|
||||||
|
{
|
||||||
|
[JsonObject(MemberSerialization.OptOut)]
|
||||||
|
public class Room
|
||||||
|
{
|
||||||
|
public int serverId;
|
||||||
|
public int hostId;
|
||||||
|
public string serverName;
|
||||||
|
public string serverData;
|
||||||
|
public bool isPublic;
|
||||||
|
public int maxPlayers;
|
||||||
|
public List<int> clients;
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool supportsDirectConnect = false;
|
||||||
|
[JsonIgnore]
|
||||||
|
public IPEndPoint hostIP;
|
||||||
|
[JsonIgnore]
|
||||||
|
public string hostLocalIP;
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool useNATPunch = false;
|
||||||
|
[JsonIgnore]
|
||||||
|
public int port;
|
||||||
|
}
|
||||||
|
}
|
||||||
244
Transport.cs
Normal file
244
Transport.cs
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Mirror
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract transport layer component
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <h2>
|
||||||
|
/// Transport Rules
|
||||||
|
/// </h2>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <listheader><description>
|
||||||
|
/// All transports should follow these rules so that they work correctly with mirror
|
||||||
|
/// </description></listheader>
|
||||||
|
/// <item><description>
|
||||||
|
/// When Monobehaviour is disabled the Transport should not invoke callbacks
|
||||||
|
/// </description></item>
|
||||||
|
/// <item><description>
|
||||||
|
/// Callbacks should be invoked on main thread. It is best to do this from LateUpdate
|
||||||
|
/// </description></item>
|
||||||
|
/// <item><description>
|
||||||
|
/// Callbacks can be invoked after <see cref="ServerStop"/> or <see cref="ClientDisconnect"/> as been called
|
||||||
|
/// </description></item>
|
||||||
|
/// <item><description>
|
||||||
|
/// <see cref="ServerStop"/> or <see cref="ClientDisconnect"/> can be called by mirror multiple times
|
||||||
|
/// </description></item>
|
||||||
|
/// <item><description>
|
||||||
|
/// <see cref="Available"/> should check the platform and 32 vs 64 bit if the transport only works on some of them
|
||||||
|
/// </description></item>
|
||||||
|
/// <item><description>
|
||||||
|
/// <see cref="GetMaxPacketSize"/> should return size even if transport is not running
|
||||||
|
/// </description></item>
|
||||||
|
/// <item><description>
|
||||||
|
/// Default channel should be reliable <see cref="Channels.DefaultReliable"/>
|
||||||
|
/// </description></item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
|
public abstract class Transport
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current transport used by Mirror.
|
||||||
|
/// </summary>
|
||||||
|
public static Transport activeTransport;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this transport available in the current platform?
|
||||||
|
/// <para>Some transports might only be available in mobile</para>
|
||||||
|
/// <para>Many will not work in webgl</para>
|
||||||
|
/// <para>Example usage: return Application.platform == RuntimePlatform.WebGLPlayer</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if this transport works in the current platform</returns>
|
||||||
|
public abstract bool Available();
|
||||||
|
|
||||||
|
#region Client
|
||||||
|
/// <summary>
|
||||||
|
/// Notify subscribers when when this client establish a successful connection to the server
|
||||||
|
/// <para>callback()</para>
|
||||||
|
/// </summary>
|
||||||
|
public Action OnClientConnected = () => Console.WriteLine("OnClientConnected called with no handler");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notify subscribers when this client receive data from the server
|
||||||
|
/// <para>callback(ArraySegment<byte> data, int channel)</para>
|
||||||
|
/// </summary>
|
||||||
|
public Action<ArraySegment<byte>, int> OnClientDataReceived = (data, channel) => Console.WriteLine("OnClientDataReceived called with no handler");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notify subscribers when this client encounters an error communicating with the server
|
||||||
|
/// <para>callback(Exception e)</para>
|
||||||
|
/// </summary>
|
||||||
|
public Action<Exception> OnClientError = (error) => Console.WriteLine("OnClientError called with no handler");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notify subscribers when this client disconnects from the server
|
||||||
|
/// <para>callback()</para>
|
||||||
|
/// </summary>
|
||||||
|
public Action OnClientDisconnected = () => Console.WriteLine("OnClientDisconnected called with no handler");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if we are currently connected to the server
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if a connection has been established to the server</returns>
|
||||||
|
public abstract bool ClientConnected();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Establish a connection to a server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The IP address or FQDN of the server we are trying to connect to</param>
|
||||||
|
public abstract void ClientConnect(string address);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Establish a connection to a server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uri">The address of the server we are trying to connect to</param>
|
||||||
|
public virtual void ClientConnect(Uri uri)
|
||||||
|
{
|
||||||
|
// By default, to keep backwards compatibility, just connect to the host
|
||||||
|
// in the uri
|
||||||
|
ClientConnect(uri.Host);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send data to the server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelId">The channel to use. 0 is the default channel,
|
||||||
|
/// but some transports might want to provide unreliable, encrypted, compressed, or any other feature
|
||||||
|
/// as new channels</param>
|
||||||
|
/// <param name="segment">The data to send to the server. Will be recycled after returning, so either use it directly or copy it internally. This allows for allocation-free sends!</param>
|
||||||
|
public abstract void ClientSend(int channelId, ArraySegment<byte> segment);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect this client from the server
|
||||||
|
/// </summary>
|
||||||
|
public abstract void ClientDisconnect();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Server
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the address of this server.
|
||||||
|
/// Useful for network discovery
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>the url at which this server can be reached</returns>
|
||||||
|
public abstract Uri ServerUri();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notify subscribers when a client connects to this server
|
||||||
|
/// <para>callback(int connId)</para>
|
||||||
|
/// </summary>
|
||||||
|
public Action<int> OnServerConnected = (connId) => Console.WriteLine("OnServerConnected called with no handler");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notify subscribers when this server receives data from the client
|
||||||
|
/// <para>callback(int connId, ArraySegment<byte> data, int channel)</para>
|
||||||
|
/// </summary>
|
||||||
|
public Action<int, ArraySegment<byte>, int> OnServerDataReceived = (connId, data, channel) => Console.WriteLine("OnServerDataReceived called with no handler");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notify subscribers when this server has some problem communicating with the client
|
||||||
|
/// <para>callback(int connId, Exception e)</para>
|
||||||
|
/// </summary>
|
||||||
|
public Action<int, Exception> OnServerError = (connId, error) => Console.WriteLine("OnServerError called with no handler");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notify subscribers when a client disconnects from this server
|
||||||
|
/// <para>callback(int connId)</para>
|
||||||
|
/// </summary>
|
||||||
|
public Action<int> OnServerDisconnected = (connId) => Console.WriteLine("OnServerDisconnected called with no handler");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the server is up and running
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if the transport is ready for connections from clients</returns>
|
||||||
|
public abstract bool ServerActive();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start listening for clients
|
||||||
|
/// </summary>
|
||||||
|
public abstract void ServerStart();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send data to a client.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionId">The client connection id to send the data to</param>
|
||||||
|
/// <param name="channelId">The channel to be used. Transports can use channels to implement
|
||||||
|
/// other features such as unreliable, encryption, compression, etc...</param>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
public abstract void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect a client from this server. Useful to kick people out.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionId">the id of the client to disconnect</param>
|
||||||
|
/// <returns>true if the client was kicked</returns>
|
||||||
|
public abstract bool ServerDisconnect(int connectionId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the client address
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionId">id of the client</param>
|
||||||
|
/// <returns>address of the client</returns>
|
||||||
|
public abstract string ServerGetClientAddress(int connectionId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop listening for clients and disconnect all existing clients
|
||||||
|
/// </summary>
|
||||||
|
public abstract void ServerStop();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum packet size for a given channel. Unreliable transports
|
||||||
|
/// usually can only deliver small packets. Reliable fragmented channels
|
||||||
|
/// can usually deliver large ones.
|
||||||
|
///
|
||||||
|
/// GetMaxPacketSize needs to return a value at all times. Even if the
|
||||||
|
/// Transport isn't running, or isn't Available(). This is because
|
||||||
|
/// Fallback and Multiplex transports need to find the smallest possible
|
||||||
|
/// packet size at runtime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelId">channel id</param>
|
||||||
|
/// <returns>the size in bytes that can be sent via the provided channel</returns>
|
||||||
|
public abstract int GetMaxPacketSize(int channelId = 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shut down the transport, both as client and server
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Shutdown();
|
||||||
|
|
||||||
|
// block Update() to force Transports to use LateUpdate to avoid race
|
||||||
|
// conditions. messages should be processed after all the game state
|
||||||
|
// was processed in Update.
|
||||||
|
// -> in other words: use LateUpdate!
|
||||||
|
// -> uMMORPG 480 CCU stress test: when bot machine stops, it causes
|
||||||
|
// 'Observer not ready for ...' log messages when using Update
|
||||||
|
// -> occupying a public Update() function will cause Warnings if a
|
||||||
|
// transport uses Update.
|
||||||
|
//
|
||||||
|
// IMPORTANT: set script execution order to >1000 to call Transport's
|
||||||
|
// LateUpdate after all others. Fixes race condition where
|
||||||
|
// e.g. in uSurvival Transport would apply Cmds before
|
||||||
|
// ShoulderRotation.LateUpdate, resulting in projectile
|
||||||
|
// spawns at the point before shoulder rotation.
|
||||||
|
#pragma warning disable UNT0001 // Empty Unity message
|
||||||
|
public void Update() { }
|
||||||
|
#pragma warning restore UNT0001 // Empty Unity message
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// called when quitting the application by closing the window / pressing stop in the editor
|
||||||
|
/// <para>virtual so that inheriting classes' OnApplicationQuit() can call base.OnApplicationQuit() too</para>
|
||||||
|
/// </summary>
|
||||||
|
public virtual void OnApplicationQuit()
|
||||||
|
{
|
||||||
|
// stop transport (e.g. to shut down threads)
|
||||||
|
// (when pressing Stop in the Editor, Unity keeps threads alive
|
||||||
|
// until we press Start again. so if Transports use threads, we
|
||||||
|
// really want them to end now and not after next start)
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue