Ducktaped together a FishNet version of the Transport

Ducktaped together a FishNet version of the Transport, now I need to edit the LoadBalancer and Server so it connects and actually trades information
This commit is contained in:
NIMFER 2022-08-14 03:55:25 +02:00
parent db147ce221
commit bf403a8f97
1384 changed files with 119035 additions and 9373 deletions

View file

@ -1,39 +0,0 @@
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /lrm
COPY ./ServerProject-DONT-IMPORT-INTO-UNITY ./
RUN dotnet publish LRM.sln --runtime ubuntu.20.04-x64 -c Release -o /out/
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /lrm
COPY --from=build-env /out/ .
ENV NO_CONFIG="true"
ENV TRANSPORT_CLASS="kcp2k.KcpTransport"
ENV AUTH_KEY="Secret Auth Key"
ENV TRANSPORT_PORT="7777"
ENV UPDATE_LOOP_TIME="10"
ENV UPDATE_HEARTBEAT_INTERVAL="100"
ENV RANDOMLY_GENERATED_ID_LENGTH="5"
ENV USE_ENDPOINT="true"
ENV ENDPOINT_PORT="8080"
ENV ENDPOINT_SERVERLIST="true"
ENV ENABLE_NATPUNCH_SERVER="true"
ENV NAT_PUNCH_PORT="7776"
ENV USE_LOAD_BALANCER="false"
ENV LOAD_BALANCER_AUTH_KEY="AuthKey"
ENV LOAD_BALANCER_ADDRESS="127.0.0.1"
ENV LOAD_BALANCER_PORT="7070"
ENV LOAD_BALANCER_REGION="1"
ENV KCP_NODELAY="true"
ENV KCP_INTERVAL="10"
ENV KCP_FAST_RESEND="2"
ENV KCP_CONGESTION_WINDOW="false"
ENV KCP_SEND_WINDOW_SIZE="4096"
ENV KCP_RECEIVE_WINDOW_SIZE="4096"
ENV KCP_CONNECTION_TIMEOUT="10000"
EXPOSE 7777/udp
EXPOSE 7776/udp
EXPOSE 8080
ENTRYPOINT [ "./LRM" ]

BIN
FishBait.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

BIN
LRM.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View file

@ -12,6 +12,5 @@ namespace LightReflectiveMirror.LoadBalancing
public string AuthKey = "AuthKey";
public ushort EndpointPort = 7070;
public bool ShowDebugLogs = false;
public int RandomlyGeneratedIDLength = 5;
}
}

View file

@ -23,8 +23,6 @@ namespace LightReflectiveMirror.LoadBalancing
const string API_PATH = "/api/stats";
readonly string CONFIG_PATH = System.Environment.GetEnvironmentVariable("LRM_LB_CONFIG_PATH") ?? "config.json";
private Random _cachedRandom = new();
public static Config conf;
public static Program instance;

View file

@ -28,13 +28,14 @@ namespace LightReflectiveMirror.LoadBalancing
public string GenerateServerID()
{
const int LENGTH = 5;
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var randomID = "";
var random = _cachedRandom;
do
{
randomID = new string(Enumerable.Repeat(chars, conf.RandomlyGeneratedIDLength)
var random = new System.Random();
randomID = new string(Enumerable.Repeat(chars, LENGTH)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
while (cachedRooms.ContainsKey(randomID));

View file

@ -1,6 +1,6 @@
![Logo](LRM.png)
![Logo](FishBait.png)

View file

@ -0,0 +1,15 @@
FROM mcr.microsoft.com/dotnet/sdk:5.0-alpine AS builder
WORKDIR /lrm
COPY . .
ARG BUILD_MODE="Release"
RUN dotnet publish \
--output /build/ \
--configuration $BUILD_MODE \
--no-self-contained .
FROM mcr.microsoft.com/dotnet/runtime:5.0-alpine
WORKDIR /lrm
COPY --from=builder /build/ .
ENV LRM_CONFIG_PATH="/config/config.json"
CMD [ "./LRM" ]
ENTRYPOINT [ "./LRM" ]

View file

@ -1,7 +1,5 @@
using Newtonsoft.Json;
using System;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace LightReflectiveMirror
@ -11,16 +9,13 @@ namespace LightReflectiveMirror
//========================
// Required Settings
//========================
public string TransportDLL = "MultiCompiled.dll";
public string TransportClass = "kcp2k.KcpTransport";
public string AuthenticationKey = "Secret Auth Key";
public ushort TransportPort = 7777;
public int UpdateLoopTime = 10;
public int UpdateHeartbeatInterval = 100;
// this wont be used if you are using load balancer
// load balancer will generate instead.
public int RandomlyGeneratedIDLength = 5;
//========================
// Endpoint REST API Settings
//========================
@ -42,10 +37,5 @@ namespace LightReflectiveMirror
public string LoadBalancerAddress = "127.0.0.1";
public ushort LoadBalancerPort = 7070;
public LRMRegions LoadBalancerRegion = LRMRegions.NorthAmerica;
public static string GetTransportDLL()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "MultiCompiled.dll" : "MultiCompiled.dll";
}
}
}

View file

@ -25,9 +25,7 @@ namespace LightReflectiveMirror
GetPublicIP();
bool noConfig = bool.Parse(Environment.GetEnvironmentVariable("NO_CONFIG") ?? "false");
if (!File.Exists(CONFIG_PATH) && !noConfig)
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);
@ -35,37 +33,15 @@ namespace LightReflectiveMirror
Environment.Exit(0);
}
else
{
if (!noConfig)
{
conf = JsonConvert.DeserializeObject<Config>(File.ReadAllText(CONFIG_PATH));
ConfigureDocker();
}
else
{
conf = new Config();
conf.TransportClass = Environment.GetEnvironmentVariable("TRANSPORT_CLASS") ?? "kcp2k.KcpTransport";
conf.AuthenticationKey = Environment.GetEnvironmentVariable("AUTH_KEY") ?? "Secret Auth Key";
conf.TransportPort = ushort.Parse(Environment.GetEnvironmentVariable("TRANSPORT_PORT") ?? "7777");
conf.UpdateLoopTime = int.Parse(Environment.GetEnvironmentVariable("UPDATE_LOOP_TIME") ?? "10");
conf.UpdateHeartbeatInterval = int.Parse(Environment.GetEnvironmentVariable("UPDATE_HEARTBEAT_INTERVAL") ?? "100");
conf.RandomlyGeneratedIDLength = int.Parse(Environment.GetEnvironmentVariable("RANDOMLY_GENERATED_ID_LENGTH") ?? "5");
conf.UseEndpoint = bool.Parse(Environment.GetEnvironmentVariable("USE_ENDPOINT") ?? "true");
conf.EndpointPort = ushort.Parse(Environment.GetEnvironmentVariable("ENDPOINT_PORT") ?? "8080");
conf.EndpointServerList = bool.Parse(Environment.GetEnvironmentVariable("ENDPOINT_SERVERLIST") ?? "true");
conf.EnableNATPunchtroughServer = bool.Parse(Environment.GetEnvironmentVariable("ENABLE_NATPUNCH_SERVER") ?? "true");
conf.NATPunchtroughPort = ushort.Parse(Environment.GetEnvironmentVariable("NAT_PUNCH_PORT") ?? "7776");
conf.UseLoadBalancer = bool.Parse(Environment.GetEnvironmentVariable("USE_LOAD_BALANCER") ?? "false");
conf.LoadBalancerAuthKey = Environment.GetEnvironmentVariable("LOAD_BALANCER_AUTH_KEY") ?? "AuthKey";
conf.LoadBalancerAddress = Environment.GetEnvironmentVariable("LOAD_BALANCER_ADDRESS") ?? "127.0.0.1";
conf.LoadBalancerPort = ushort.Parse(Environment.GetEnvironmentVariable("LOAD_BALANCER_PORT") ?? "7070");
conf.LoadBalancerRegion = (LRMRegions)int.Parse(Environment.GetEnvironmentVariable("LOAD_BALANCER_REGION") ?? "1");
}
WriteLogMessage("Loading Assembly... ", ConsoleColor.White, true);
try
{
var asm = Assembly.LoadFile(Path.GetFullPath(Config.GetTransportDLL()));
var asm = Assembly.LoadFile(Path.GetFullPath(conf.TransportDLL));
WriteLogMessage($"OK", ConsoleColor.Green);
WriteLogMessage("\nLoading Transport Class... ", ConsoleColor.White, true);

View file

@ -9,7 +9,7 @@ namespace LightReflectiveMirror
// constructor for new relay handler
public RelayHandler(int maxPacketSize)
{
_maxPacketSize = maxPacketSize;
this._maxPacketSize = maxPacketSize;
_sendBuffers = ArrayPool<byte>.Create(maxPacketSize, 50);
}
@ -22,13 +22,14 @@ namespace LightReflectiveMirror
private string GenerateRoomID()
{
const int LENGTH = 5;
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var randomID = "";
var random = _cachedRandom;
do
{
randomID = new string(Enumerable.Repeat(chars, Program.conf.RandomlyGeneratedIDLength)
randomID = new string(Enumerable.Repeat(chars, LENGTH)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
while (DoesServerIdExist(randomID));
@ -73,7 +74,7 @@ namespace LightReflectiveMirror
{
if (room.clients.Contains(sendTo))
{
SendData(clientData, channel, sendTo, clientId);
SendData(clientData, channel, sendTo);
}
}
else
@ -83,15 +84,8 @@ namespace LightReflectiveMirror
}
}
private void SendData(byte[] clientData, int channel, int sendTo, int senderId)
private void SendData(byte[] clientData, int channel, int sendTo)
{
if (clientData.Length > _maxPacketSize)
{
Program.transport.ServerDisconnect(senderId);
Program.WriteLogMessage($"Client {senderId} tried to send more than max packet size! Disconnecting...");
return;
}
int pos = 0;
byte[] sendBuffer = _sendBuffers.Rent(_maxPacketSize);
@ -102,22 +96,15 @@ namespace LightReflectiveMirror
_sendBuffers.Return(sendBuffer);
}
private void SendDataToRoomHost(int senderId, byte[] clientData, int channel, Room room)
private void SendDataToRoomHost(int clientId, byte[] clientData, int channel, Room room)
{
if(clientData.Length > _maxPacketSize)
{
Program.transport.ServerDisconnect(senderId);
Program.WriteLogMessage($"Client {senderId} tried to send more than max packet size! Disconnecting...");
return;
}
// 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, senderId);
sendBuffer.WriteInt(ref pos, clientId);
Program.transport.ServerSend(room.hostId, channel, new ArraySegment<byte>(sendBuffer, 0, pos));
_sendBuffers.Return(sendBuffer);

View file

@ -46,8 +46,6 @@ namespace LightReflectiveMirror
var sendBuffer = _sendBuffers.Rent(1);
sendBuffer.WriteByte(ref writePos, (byte)OpCodes.Authenticated);
Program.transport.ServerSend(clientId, 0, new ArraySegment<byte>(sendBuffer, 0, writePos));
_sendBuffers.Return(sendBuffer);
}
else
{
@ -60,15 +58,8 @@ namespace LightReflectiveMirror
switch (opcode)
{
case OpCodes.CreateRoom: // bruh
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));
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);
@ -109,9 +100,7 @@ namespace LightReflectiveMirror
}
catch
{
// sent invalid data, boot them hehe
Program.WriteLogMessage($"Client {clientId} sent bad data! Removing from LRM node.");
Program.transport.ServerDisconnect(clientId);
// Do Nothing. Client probably sent some invalid data.
}
}

View file

@ -13,11 +13,11 @@ namespace LightReflectiveMirror
/// <param name="clientId">The client requesting to create a room</param>
/// <param name="maxPlayers">The maximum amount of players for this room</param>
/// <param name="serverName">The name for the server</param>
/// <param name="isPublic">Whether or not the server should show up on the server list</param>
/// <param name="isPublic">Weather or not the server should show up on the server list</param>
/// <param name="serverData">Extra data the host can include</param>
/// <param name="useDirectConnect">Whether or not, the host is capable of doing direct connections</param>
/// <param name="useDirectConnect">Weather or not, the host is capable of doing direct connections</param>
/// <param name="hostLocalIP">The hosts local IP</param>
/// <param name="useNatPunch">Whether or not, the host is supporting NAT Punch</param>
/// <param name="useNatPunch">Weather or not, the host is supporting NAT Punch</param>
/// <param name="port">The port of the direct connect transport on the host</param>
private void CreateRoom(int clientId, int maxPlayers, string serverName, bool isPublic, string serverData, bool useDirectConnect, string hostLocalIP, bool useNatPunch, int port)
{
@ -98,7 +98,7 @@ namespace LightReflectiveMirror
{
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);
sendJoinBuffer.WriteBool(ref sendJoinPos, true);
@ -145,7 +145,6 @@ namespace LightReflectiveMirror
{
for (int i = 0; i < rooms.Count; i++)
{
// if host left
if (rooms[i].hostId == clientId)
{
int pos = 0;
@ -168,7 +167,6 @@ namespace LightReflectiveMirror
}
else
{
// if the person that tried to kick wasnt host and it wasnt the client leaving on their own
if (requiredHostId != -1 && rooms[i].hostId != requiredHostId)
continue;
@ -182,19 +180,6 @@ namespace LightReflectiveMirror
Program.transport.ServerSend(rooms[i].hostId, 0, new ArraySegment<byte>(sendBuffer, 0, pos));
_sendBuffers.Return(sendBuffer);
// temporary solution to kicking bug
// this tells the local player that got kicked that he, well, got kicked.
pos = 0;
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);
//end temporary solution
Endpoint.RoomsModified();
_cachedClientRooms.Remove(clientId);
}

View file

@ -1,288 +0,0 @@
// Ignorance 1.4.x
// Ignorance. It really kicks the Unity LLAPIs ass.
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2020 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance Transport is licensed under the MIT license. Refer
// to the LICENSE file for more information.
using ENet;
// using NetStack.Buffers;
using System;
using System.Collections.Concurrent;
using System.Threading;
using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine
namespace IgnoranceTransport
{
public class IgnoranceClient
{
// Client connection address and port
public string ConnectAddress = "127.0.0.1";
public int ConnectPort = 7777;
// How many channels are expected
public int ExpectedChannels = 2;
// Native poll waiting time
public int PollTime = 1;
// Maximum Packet Size
public int MaximumPacketSize = 33554432;
// General Verbosity by default.
public int Verbosity = 1;
// Queues
public ConcurrentQueue<IgnoranceIncomingPacket> Incoming = new ConcurrentQueue<IgnoranceIncomingPacket>();
public ConcurrentQueue<IgnoranceOutgoingPacket> Outgoing = new ConcurrentQueue<IgnoranceOutgoingPacket>();
public ConcurrentQueue<IgnoranceCommandPacket> Commands = new ConcurrentQueue<IgnoranceCommandPacket>();
public ConcurrentQueue<IgnoranceConnectionEvent> ConnectionEvents = new ConcurrentQueue<IgnoranceConnectionEvent>();
public ConcurrentQueue<IgnoranceClientStats> StatusUpdates = new ConcurrentQueue<IgnoranceClientStats>();
public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive;
private volatile bool CeaseOperation = false;
private Thread WorkerThread;
public void Start()
{
// Debug.Log("IgnoranceClient.Start()");
if (WorkerThread != null && WorkerThread.IsAlive)
{
// Cannot do that.
// Debug.LogError("A worker thread is already running. Cannot start another.");
return;
}
CeaseOperation = false;
ThreadParamInfo threadParams = new ThreadParamInfo()
{
Address = ConnectAddress,
Port = ConnectPort,
Channels = ExpectedChannels,
PollTime = PollTime,
PacketSizeLimit = MaximumPacketSize,
Verbosity = Verbosity
};
// Drain queues.
if (Incoming != null) while (Incoming.TryDequeue(out _)) ;
if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ;
if (Commands != null) while (Commands.TryDequeue(out _)) ;
if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ;
if (StatusUpdates != null) while (StatusUpdates.TryDequeue(out _)) ;
WorkerThread = new Thread(ThreadWorker);
WorkerThread.Start(threadParams);
// Debug.Log("Client has dispatched worker thread.");
}
public void Stop()
{
// Debug.Log("Telling client thread to stop, this may take a while depending on network load");
CeaseOperation = true;
}
// This runs in a seperate thread, be careful accessing anything outside of it's thread
// or you may get an AccessViolation/crash.
private void ThreadWorker(Object parameters)
{
// if (Verbosity > 0)
// Debug.Log("Ignorance Client: Initializing. Please stand by...");
ThreadParamInfo setupInfo;
Address clientAddress = new Address();
Peer clientPeer;
Host clientENetHost;
Event clientENetEvent;
IgnoranceClientStats icsu = default;
// Grab the setup information.
if (parameters.GetType() == typeof(ThreadParamInfo))
{
setupInfo = (ThreadParamInfo)parameters;
}
else
{
// Debug.LogError("Ignorance Client: Startup failure: Invalid thread parameters. Aborting.");
return;
}
// Attempt to initialize ENet inside the thread.
if (Library.Initialize())
{
Console.WriteLine("Ignorance Client: ENet initialized.");
}
else
{
// Debug.LogError("Ignorance Client: Failed to initialize ENet. This threads' fucked.");
return;
}
// Attempt to connect to our target.
clientAddress.SetHost(setupInfo.Address);
clientAddress.Port = (ushort)setupInfo.Port;
using (clientENetHost = new Host())
{
// TODO: Maybe try catch this
clientENetHost.Create();
clientPeer = clientENetHost.Connect(clientAddress, setupInfo.Channels);
while (!CeaseOperation)
{
bool pollComplete = false;
// Step 0: Handle commands.
while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket))
{
switch (commandPacket.Type)
{
default:
break;
case IgnoranceCommandType.ClientWantsToStop:
CeaseOperation = true;
break;
case IgnoranceCommandType.ClientRequestsStatusUpdate:
// Respond with statistics so far.
if (!clientPeer.IsSet)
break;
icsu.RTT = clientPeer.RoundTripTime;
icsu.BytesReceived = clientPeer.BytesReceived;
icsu.BytesSent = clientPeer.BytesSent;
icsu.PacketsReceived = clientENetHost.PacketsReceived;
icsu.PacketsSent = clientPeer.PacketsSent;
icsu.PacketsLost = clientPeer.PacketsLost;
StatusUpdates.Enqueue(icsu);
break;
}
}
// Step 1: Send out data.
// ---> Sending to Server
while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket))
{
// TODO: Revise this, could we tell the Peer to disconnect right here?
// Stop early if we get a client stop packet.
// if (outgoingPacket.Type == IgnorancePacketType.ClientWantsToStop) break;
int ret = clientPeer.Send(outgoingPacket.Channel, ref outgoingPacket.Payload);
}
// Step 2:
// <----- Receive Data packets
// This loops until polling is completed. It may take a while, if it's
// a slow networking day.
while (!pollComplete)
{
Packet incomingPacket;
Peer incomingPeer;
int incomingPacketLength;
// Any events worth checking out?
if (clientENetHost.CheckEvents(out clientENetEvent) <= 0)
{
// If service time is met, break out of it.
if (clientENetHost.Service(setupInfo.PollTime, out clientENetEvent) <= 0) break;
// Poll is done.
pollComplete = true;
}
// Setup the packet references.
incomingPeer = clientENetEvent.Peer;
// Now, let's handle those events.
switch (clientENetEvent.Type)
{
case EventType.None:
default:
break;
case EventType.Connect:
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent()
{
NativePeerId = incomingPeer.ID,
IP = incomingPeer.IP,
Port = incomingPeer.Port
});
break;
case EventType.Disconnect:
case EventType.Timeout:
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent()
{
WasDisconnect = true
});
break;
case EventType.Receive:
// Receive event type usually includes a packet; so cache its reference.
incomingPacket = clientENetEvent.Packet;
if (!incomingPacket.IsSet)
{
break;
}
incomingPacketLength = incomingPacket.Length;
// Never consume more than we can have capacity for.
if (incomingPacketLength > setupInfo.PacketSizeLimit)
{
incomingPacket.Dispose();
break;
}
IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket
{
Channel = clientENetEvent.ChannelID,
NativePeerId = incomingPeer.ID,
Payload = incomingPacket
};
Incoming.Enqueue(incomingQueuePacket);
break;
}
}
}
Console.WriteLine("Ignorance Server: Shutdown commencing, disconnecting and flushing connection.");
// Flush the client and disconnect.
clientPeer.Disconnect(0);
clientENetHost.Flush();
// Fix for client stuck in limbo, since the disconnection event may not be fired until next loop.
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent()
{
WasDisconnect = true
});
}
// Deinitialize
Library.Deinitialize();
}
private struct ThreadParamInfo
{
public int Channels;
public int PollTime;
public int Port;
public int PacketSizeLimit;
public int Verbosity;
public string Address;
}
}
}

View file

@ -1,303 +0,0 @@
// Ignorance 1.4.x
// Ignorance. It really kicks the Unity LLAPIs ass.
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2020 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance Transport is licensed under the MIT license. Refer
// to the LICENSE file for more information.
using ENet;
// using NetStack.Buffers;
using System.Collections.Concurrent;
using System.Threading;
using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine
namespace IgnoranceTransport
{
public class IgnoranceServer
{
// Server Properties
// - Bind Settings
public string BindAddress = "127.0.0.1";
public int BindPort = 7777;
// - Maximum allowed channels, peers, etc.
public int MaximumChannels = 2;
public int MaximumPeers = 100;
public int MaximumPacketSize = 33554432; // ENet.cs: uint maxPacketSize = 32 * 1024 * 1024 = 33554432
// - Native poll waiting time
public int PollTime = 1;
public int Verbosity = 1;
public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive;
private volatile bool CeaseOperation = false;
// Queues
public ConcurrentQueue<IgnoranceIncomingPacket> Incoming = new ConcurrentQueue<IgnoranceIncomingPacket>();
public ConcurrentQueue<IgnoranceOutgoingPacket> Outgoing = new ConcurrentQueue<IgnoranceOutgoingPacket>();
public ConcurrentQueue<IgnoranceCommandPacket> Commands = new ConcurrentQueue<IgnoranceCommandPacket>();
public ConcurrentQueue<IgnoranceConnectionEvent> ConnectionEvents = new ConcurrentQueue<IgnoranceConnectionEvent>();
public ConcurrentQueue<IgnoranceConnectionEvent> DisconnectionEvents = new ConcurrentQueue<IgnoranceConnectionEvent>();
// Thread
private Thread WorkerThread;
public void Start()
{
if (WorkerThread != null && WorkerThread.IsAlive)
{
// Cannot do that.
return;
}
CeaseOperation = false;
ThreadParamInfo threadParams = new ThreadParamInfo()
{
Address = BindAddress,
Port = BindPort,
Peers = MaximumPeers,
Channels = MaximumChannels,
PollTime = PollTime,
PacketSizeLimit = MaximumPacketSize,
Verbosity = Verbosity
};
// Drain queues.
if (Incoming != null) while (Incoming.TryDequeue(out _)) ;
if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ;
if (Commands != null) while (Commands.TryDequeue(out _)) ;
if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ;
if (DisconnectionEvents != null) while (DisconnectionEvents.TryDequeue(out _)) ;
WorkerThread = new Thread(ThreadWorker);
WorkerThread.Start(threadParams);
}
public void Stop()
{
CeaseOperation = true;
}
private void ThreadWorker(Object parameters)
{
// Thread cache items
ThreadParamInfo setupInfo;
Address serverAddress = new Address();
Host serverENetHost;
Event serverENetEvent;
Peer[] serverPeerArray;
// Grab the setup information.
if (parameters.GetType() == typeof(ThreadParamInfo))
{
setupInfo = (ThreadParamInfo)parameters;
}
else
{
return;
}
// Attempt to initialize ENet inside the thread.
if (Library.Initialize())
{
}
else
{
return;
}
// Configure the server address.
serverAddress.SetHost(setupInfo.Address);
serverAddress.Port = (ushort)setupInfo.Port;
serverPeerArray = new Peer[setupInfo.Peers];
using (serverENetHost = new Host())
{
// Create the server object.
serverENetHost.Create(serverAddress, setupInfo.Peers, setupInfo.Channels);
// Loop until we're told to cease operations.
while (!CeaseOperation)
{
// Intermission: Command Handling
while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket))
{
switch (commandPacket.Type)
{
default:
break;
// Boot a Peer off the Server.
case IgnoranceCommandType.ServerKickPeer:
uint targetPeer = commandPacket.PeerId;
if (!serverPeerArray[targetPeer].IsSet) continue;
IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent()
{
WasDisconnect = true,
NativePeerId = targetPeer
};
DisconnectionEvents.Enqueue(iced);
// Disconnect and reset the peer array's entry for that peer.
serverPeerArray[targetPeer].DisconnectNow(0);
serverPeerArray[targetPeer] = default;
break;
}
}
// Step One:
// ---> Sending to peers
while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket))
{
// Only create a packet if the server knows the peer.
if (serverPeerArray[outgoingPacket.NativePeerId].IsSet)
{
int ret = serverPeerArray[outgoingPacket.NativePeerId].Send(outgoingPacket.Channel, ref outgoingPacket.Payload);
}
else
{
// A peer might have disconnected, this is OK - just log the packet if set to paranoid.
}
}
// Step 2
// <--- Receiving from peers
bool pollComplete = false;
while (!pollComplete)
{
Packet incomingPacket;
Peer incomingPeer;
int incomingPacketLength;
// Any events happening?
if (serverENetHost.CheckEvents(out serverENetEvent) <= 0)
{
// If service time is met, break out of it.
if (serverENetHost.Service(setupInfo.PollTime, out serverENetEvent) <= 0) break;
pollComplete = true;
}
// Setup the packet references.
incomingPeer = serverENetEvent.Peer;
switch (serverENetEvent.Type)
{
// Idle.
case EventType.None:
default:
break;
// Connection Event.
case EventType.Connect:
IgnoranceConnectionEvent ice = new IgnoranceConnectionEvent()
{
NativePeerId = incomingPeer.ID,
IP = incomingPeer.IP,
Port = incomingPeer.Port
};
ConnectionEvents.Enqueue(ice);
// Assign a reference to the Peer.
serverPeerArray[incomingPeer.ID] = incomingPeer;
break;
// Disconnect/Timeout. Mirror doesn't care if it's either, so we lump them together.
case EventType.Disconnect:
case EventType.Timeout:
if (!serverPeerArray[incomingPeer.ID].IsSet) break;
IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent()
{
WasDisconnect = true,
NativePeerId = incomingPeer.ID
};
DisconnectionEvents.Enqueue(iced);
// Reset the peer array's entry for that peer.
serverPeerArray[incomingPeer.ID] = default;
break;
case EventType.Receive:
// Receive event type usually includes a packet; so cache its reference.
incomingPacket = serverENetEvent.Packet;
if (!incomingPacket.IsSet)
{
break;
}
incomingPacketLength = incomingPacket.Length;
// Firstly check if the packet is too big. If it is, do not process it - drop it.
if (incomingPacketLength > setupInfo.PacketSizeLimit)
{
incomingPacket.Dispose();
break;
}
IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket
{
Channel = serverENetEvent.ChannelID,
NativePeerId = incomingPeer.ID,
Payload = incomingPacket,
};
// Enqueue.
Incoming.Enqueue(incomingQueuePacket);
break;
}
}
}
// Cleanup and flush everything.
serverENetHost.Flush();
// Kick everyone.
for (int i = 0; i < serverPeerArray.Length; i++)
{
if (!serverPeerArray[i].IsSet) continue;
serverPeerArray[i].DisconnectNow(0);
}
}
// Flush again to ensure ENet gets those Disconnection stuff out.
// May not be needed; better to err on side of caution
Library.Deinitialize();
}
private struct ThreadParamInfo
{
public int Channels;
public int Peers;
public int PollTime;
public int Port;
public int PacketSizeLimit;
public int Verbosity;
public string Address;
}
}
}

View file

@ -1,510 +0,0 @@
// Ignorance 1.4.x
// Ignorance. It really kicks the Unity LLAPIs ass.
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2020 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance Transport is licensed under the MIT license. Refer
// to the LICENSE file for more information.
// -----------------
// Ignorance Experimental (New) Version
// -----------------
using ENet;
using Mirror;
using System;
using System.Collections.Generic;
namespace IgnoranceTransport
{
public class Ignorance : Transport
{
public int port = 7777;
public IgnoranceLogType LogType = IgnoranceLogType.Standard;
public bool DebugDisplay = false;
public bool serverBindsAll = true;
public string serverBindAddress = string.Empty;
public int serverMaxPeerCapacity = 50;
public int serverMaxNativeWaitTime = 1;
public int clientMaxNativeWaitTime = 3;
public IgnoranceChannelTypes[] Channels = new[] { IgnoranceChannelTypes.Reliable, IgnoranceChannelTypes.Unreliable };
public int PacketBufferCapacity = 4096;
public int MaxAllowedPacketSize = 33554432;
public IgnoranceClientStats ClientStatistics;
public override bool Available()
{
return true;
}
public override void Awake()
{
if (LogType != IgnoranceLogType.Nothing)
Console.WriteLine($"Thanks for using Ignorance {IgnoranceInternals.Version}. Keep up to date, report bugs and support the developer at https://github.com/SoftwareGuy/Ignorance!");
}
public override string ToString()
{
return $"Ignorance v{IgnoranceInternals.Version}";
}
public override void ClientConnect(string address)
{
ClientState = ConnectionState.Connecting;
cachedConnectionAddress = address;
// Initialize.
InitializeClientBackend();
// Get going.
ignoreDataPackets = false;
// Start!
Client.Start();
}
public override void ClientConnect(Uri uri)
{
if (uri.Scheme != IgnoranceInternals.Scheme)
throw new ArgumentException($"You used an invalid URI: {uri}. Please use {IgnoranceInternals.Scheme}://host:port instead", nameof(uri));
if (!uri.IsDefaultPort)
// Set the communication port to the one specified.
port = uri.Port;
// Pass onwards to the proper handler.
ClientConnect(uri.Host);
}
public override bool ClientConnected() => ClientState == ConnectionState.Connected;
public override void ClientDisconnect()
{
if (Client != null)
Client.Stop();
// TODO: Figure this one out to see if it's related to a race condition.
// Maybe experiment with a while loop to pause main thread when disconnecting,
// since client might not stop on a dime.
// while(Client.IsAlive) ;
// v1.4.0b1: Probably fixed in IgnoranceClient.cs; need further testing.
// ignoreDataPackets = true;
ClientState = ConnectionState.Disconnected;
}
// v1.4.0b6: Mirror rearranged the ClientSend params, so we need to apply a fix for that or
// we end up using the obsoleted version. The obsolete version isn't a fatal error, but
// it's best to stick with the new structures.
public override void ClientSend(int channelId, ArraySegment<byte> segment)
{
if (Client == null)
{
return;
}
if (channelId < 0 || channelId > Channels.Length)
{
return;
}
// Create our struct...
Packet clientOutgoingPacket = default;
int byteCount = segment.Count;
int byteOffset = segment.Offset;
// Set our desired flags...
PacketFlags desiredFlags = (PacketFlags)Channels[channelId];
// Create the packet.
clientOutgoingPacket.Create(segment.Array, byteOffset, byteCount + byteOffset, desiredFlags);
// byteCount
// Enqueue the packet.
IgnoranceOutgoingPacket dispatchPacket = new IgnoranceOutgoingPacket
{
Channel = (byte)channelId,
Payload = clientOutgoingPacket
};
// Pass the packet onto the thread for dispatch.
Client.Outgoing.Enqueue(dispatchPacket);
}
public override bool ServerActive()
{
// Very simple check.
return Server != null && Server.IsAlive;
}
public override bool ServerDisconnect(int connectionId) => ServerDisconnectLegacy(connectionId);
public override string ServerGetClientAddress(int connectionId)
{
if (ConnectionLookupDict.TryGetValue(connectionId, out PeerConnectionData details))
return $"{details.IP}:{details.Port}";
return "(unavailable)";
}
public override void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
{
// Debug.Log($"ServerSend({connectionId}, {channelId}, <{segment.Count} byte segment>)");
if (Server == null)
{
// Debug.LogError("Cannot enqueue data packet; our Server object is null. Something has gone wrong.");
return;
}
if (channelId < 0 || channelId > Channels.Length)
{
// Debug.LogError("Channel ID is out of bounds.");
return;
}
// Packet Struct
Packet serverOutgoingPacket = default;
int byteCount = segment.Count;
int byteOffset = segment.Offset;
PacketFlags desiredFlags = (PacketFlags)Channels[channelId];
// Create the packet.
serverOutgoingPacket.Create(segment.Array, byteOffset, byteCount + byteOffset, desiredFlags);
// Enqueue the packet.
IgnoranceOutgoingPacket dispatchPacket = new IgnoranceOutgoingPacket
{
Channel = (byte)channelId,
NativePeerId = (uint)connectionId - 1, // ENet's native peer ID will be ConnID - 1
Payload = serverOutgoingPacket
};
Server.Outgoing.Enqueue(dispatchPacket);
}
public override void ServerStart(ushort _port)
{
if (LogType != IgnoranceLogType.Nothing)
Console.WriteLine("Ignorance Server Instance starting up...");
port = _port;
InitializeServerBackend();
Server.Start();
}
public override void ServerStop()
{
if (Server != null)
{
if (LogType != IgnoranceLogType.Nothing)
Console.WriteLine("Ignorance Server Instance shutting down...");
Server.Stop();
}
ConnectionLookupDict.Clear();
}
public override Uri ServerUri()
{
UriBuilder builder = new UriBuilder
{
Scheme = IgnoranceInternals.Scheme,
Host = serverBindAddress,
Port = port
};
return builder.Uri;
}
public override void Shutdown()
{
// TODO: Nothing needed here?
}
private void InitializeServerBackend()
{
if (Server == null)
{
// Debug.LogWarning("IgnoranceServer reference for Server mode was null. This shouldn't happen, but to be safe we'll reinitialize it.");
Server = new IgnoranceServer();
}
// Set up the new IgnoranceServer reference.
if (serverBindsAll)
// MacOS is special. It's also a massive thorn in my backside.
Server.BindAddress = IgnoranceInternals.BindAllMacs;
else
// Use the supplied bind address.
Server.BindAddress = serverBindAddress;
// Sets port, maximum peers, max channels, the server poll time, maximum packet size and verbosity.
Server.BindPort = port;
Server.MaximumPeers = serverMaxPeerCapacity;
Server.MaximumChannels = Channels.Length;
Server.PollTime = serverMaxNativeWaitTime;
Server.MaximumPacketSize = MaxAllowedPacketSize;
Server.Verbosity = (int)LogType;
// Initializes the packet buffer.
// Allocates once, that's it.
if (InternalPacketBuffer == null)
InternalPacketBuffer = new byte[PacketBufferCapacity];
}
private void InitializeClientBackend()
{
if (Client == null)
{
// Debug.LogWarning("Ignorance: IgnoranceClient reference for Client mode was null. This shouldn't happen, but to be safe we'll reinitialize it.");
Client = new IgnoranceClient();
}
// Sets address, port, channels to expect, verbosity, the server poll time and maximum packet size.
Client.ConnectAddress = cachedConnectionAddress;
Client.ConnectPort = port;
Client.ExpectedChannels = Channels.Length;
Client.PollTime = clientMaxNativeWaitTime;
Client.MaximumPacketSize = MaxAllowedPacketSize;
Client.Verbosity = (int)LogType;
// Initializes the packet buffer.
// Allocates once, that's it.
if (InternalPacketBuffer == null)
InternalPacketBuffer = new byte[PacketBufferCapacity];
}
private void ProcessServerPackets()
{
IgnoranceIncomingPacket incomingPacket;
IgnoranceConnectionEvent connectionEvent;
int adjustedConnectionId;
Packet payload;
// Incoming connection events.
while (Server.ConnectionEvents.TryDequeue(out connectionEvent))
{
adjustedConnectionId = (int)connectionEvent.NativePeerId + 1;
// TODO: Investigate ArgumentException: An item with the same key has already been added. Key: <id>
ConnectionLookupDict.Add(adjustedConnectionId, new PeerConnectionData
{
NativePeerId = connectionEvent.NativePeerId,
IP = connectionEvent.IP,
Port = connectionEvent.Port
});
OnServerConnected?.Invoke(adjustedConnectionId);
}
// Handle incoming data packets.
// Console.WriteLine($"Server Incoming Queue is {Server.Incoming.Count}");
while (Server.Incoming.TryDequeue(out incomingPacket))
{
adjustedConnectionId = (int)incomingPacket.NativePeerId + 1;
payload = incomingPacket.Payload;
int length = payload.Length;
ArraySegment<byte> dataSegment;
// Copy to working buffer and dispose of it.
if (length > InternalPacketBuffer.Length)
{
byte[] oneFreshNTastyGcAlloc = new byte[length];
payload.CopyTo(oneFreshNTastyGcAlloc);
dataSegment = new ArraySegment<byte>(oneFreshNTastyGcAlloc, 0, length);
}
else
{
payload.CopyTo(InternalPacketBuffer);
dataSegment = new ArraySegment<byte>(InternalPacketBuffer, 0, length);
}
payload.Dispose();
OnServerDataReceived?.Invoke(adjustedConnectionId, dataSegment, incomingPacket.Channel);
}
// Disconnection events.
while (Server.DisconnectionEvents.TryDequeue(out IgnoranceConnectionEvent disconnectionEvent))
{
adjustedConnectionId = (int)disconnectionEvent.NativePeerId + 1;
ConnectionLookupDict.Remove(adjustedConnectionId);
// Invoke Mirror handler.
OnServerDisconnected?.Invoke(adjustedConnectionId);
}
}
private void ProcessClientPackets()
{
Packet payload;
// Handle connection events.
while (Client.ConnectionEvents.TryDequeue(out IgnoranceConnectionEvent connectionEvent))
{
if (connectionEvent.WasDisconnect)
{
// Disconnected from server.
ClientState = ConnectionState.Disconnected;
ignoreDataPackets = true;
OnClientDisconnected?.Invoke();
}
else
{
// Connected to server.
ClientState = ConnectionState.Connected;
ignoreDataPackets = false;
OnClientConnected?.Invoke();
}
}
// Now handle the incoming messages.
while (Client.Incoming.TryDequeue(out IgnoranceIncomingPacket incomingPacket))
{
// Temporary fix: if ENet thread is too fast for Mirror, then ignore the packet.
// This is seen sometimes if you stop the client and there's still stuff in the queue.
if (ignoreDataPackets)
{
break;
}
// Otherwise client recieved data, advise Mirror.
// print($"Byte array: {incomingPacket.RentedByteArray.Length}. Packet Length: {incomingPacket.Length}");
payload = incomingPacket.Payload;
int length = payload.Length;
ArraySegment<byte> dataSegment;
// Copy to working buffer and dispose of it.
if (length > InternalPacketBuffer.Length)
{
// Unity's favourite: A fresh 'n' tasty GC Allocation!
byte[] oneFreshNTastyGcAlloc = new byte[length];
payload.CopyTo(oneFreshNTastyGcAlloc);
dataSegment = new ArraySegment<byte>(oneFreshNTastyGcAlloc, 0, length);
}
else
{
payload.CopyTo(InternalPacketBuffer);
dataSegment = new ArraySegment<byte>(InternalPacketBuffer, 0, length);
}
payload.Dispose();
OnClientDataReceived?.Invoke(dataSegment, incomingPacket.Channel);
}
// Step 3: Handle other commands.
while (Client.Commands.TryDequeue(out IgnoranceCommandPacket commandPacket))
{
switch (commandPacket.Type)
{
// ...
default:
break;
}
}
// Step 4: Handle status updates.
if (Client.StatusUpdates.TryDequeue(out IgnoranceClientStats clientStats))
{
ClientStatistics = clientStats;
}
}
// Ignorance 1.4.0b5: To use Mirror's polling or not use Mirror's polling, that is up to the developer to decide
// IMPORTANT: Set Ignorance' execution order before everything else. Yes, that's -32000 !!
// This ensures it has priority over other things.
// FixedUpdate can be called many times per frame.
// Once we've handled stuff, we set a flag so that we don't poll again for this frame.
private bool fixedUpdateCompletedWork;
public void FixedUpdate()
{
if (fixedUpdateCompletedWork) return;
ProcessAndExecuteAllPackets();
// Flip the bool to signal we've done our work.
fixedUpdateCompletedWork = true;
}
// Normally, Mirror blocks Update() due to poor design decisions...
// But thanks to Vincenzo, we've found a way to bypass that block.
// Update is called once per frame. We don't have to worry about this shit now.
public override void Update()
{
// Process what FixedUpdate missed, only if the boolean is not set.
if (!fixedUpdateCompletedWork)
ProcessAndExecuteAllPackets();
// Flip back the bool, so it can be reset.
fixedUpdateCompletedWork = false;
}
// Processes and Executes All Packets.
private void ProcessAndExecuteAllPackets()
{
// Process Server Events...
if (Server.IsAlive)
ProcessServerPackets();
// Process Client Events...
if (Client.IsAlive)
{
ProcessClientPackets();
}
}
public override int GetMaxPacketSize(int channelId = 0) => MaxAllowedPacketSize;
private bool ignoreDataPackets;
private string cachedConnectionAddress = string.Empty;
private IgnoranceServer Server = new IgnoranceServer();
private IgnoranceClient Client = new IgnoranceClient();
private Dictionary<int, PeerConnectionData> ConnectionLookupDict = new Dictionary<int, PeerConnectionData>();
private enum ConnectionState { Connecting, Connected, Disconnecting, Disconnected }
private ConnectionState ClientState = ConnectionState.Disconnected;
private byte[] InternalPacketBuffer;
public bool ServerDisconnectLegacy(int connectionId)
{
if (Server == null)
{
// Debug.LogError("Cannot enqueue kick packet; our Server object is null. Something has gone wrong.");
// Return here because otherwise we will get a NRE when trying to enqueue the kick packet.
return false;
}
IgnoranceCommandPacket kickPacket = new IgnoranceCommandPacket
{
Type = IgnoranceCommandType.ServerKickPeer,
PeerId = (uint)connectionId - 1 // ENet's native peer ID will be ConnID - 1
};
// Pass the packet onto the thread for dispatch.
Server.Commands.Enqueue(kickPacket);
return true;
}
}
}

View file

@ -1,94 +0,0 @@
using System;
using ENet;
namespace IgnoranceTransport
{
// Snipped from the transport files, as this will help
// me keep things up to date.
[Serializable]
public enum IgnoranceChannelTypes
{
Reliable = PacketFlags.Reliable, // TCP Emulation.
ReliableUnsequenced = PacketFlags.Reliable | PacketFlags.Unsequenced, // TCP Emulation, but no sequencing.
Unreliable = PacketFlags.Unsequenced, // Pure UDP.
UnreliableFragmented = PacketFlags.UnreliableFragmented, // Pure UDP, but fragmented.
UnreliableSequenced = PacketFlags.None, // Pure UDP, but sequenced.
Unthrottled = PacketFlags.Unthrottled, // Apparently ENet's version of Taco Bell.
}
public class IgnoranceInternals
{
public const string Version = "1.4.0b6";
public const string Scheme = "enet";
public const string BindAllIPv4 = "0.0.0.0";
public const string BindAllMacs = "::0";
}
public enum IgnoranceLogType
{
Nothing,
Standard,
Verbose
}
// Struct optimized for cache efficiency. (Thanks Vincenzo!)
public struct IgnoranceIncomingPacket
{
public byte Channel;
public uint NativePeerId;
public Packet Payload;
}
// Struct optimized for cache efficiency. (Thanks Vincenzo!)
public struct IgnoranceOutgoingPacket
{
public byte Channel;
public uint NativePeerId;
public Packet Payload;
}
// Struct optimized for cache efficiency. (Thanks Vincenzo!)
public struct IgnoranceConnectionEvent
{
public bool WasDisconnect;
public ushort Port;
public uint NativePeerId;
public string IP;
}
public struct IgnoranceCommandPacket
{
public IgnoranceCommandType Type;
public uint PeerId;
}
public struct IgnoranceClientStats
{
// Stats only - may not always be used!
public uint RTT;
public ulong BytesReceived;
public ulong BytesSent;
public ulong PacketsReceived;
public ulong PacketsSent;
public ulong PacketsLost;
}
public enum IgnoranceCommandType
{
// Client
ClientWantsToStop,
ClientRequestsStatusUpdate,
// ENet internal
ResponseToClientStatusRequest,
// Server
ServerKickPeer
}
// TODO: Optimize struct for Cache performance.
public struct PeerConnectionData
{
public ushort Port;
public uint NativePeerId;
public string IP;
}
}

View file

@ -1,3 +0,0 @@
This is a Windows x64 build of ENet-CSharp.
If you require a version of ENet for 32 Bit computer systems (ie. Windows 7/8/10 32Bit) then please get in touch, or you can install the requirements yourself and compile it using ENet-CSharp's MSBuild-based build system. Get in touch if you want me to compile them for you, but keep in mind that I do not support them when reporting bugs.

View file

@ -1,35 +0,0 @@
ENET Pre-compiled Binary Library Blobs
==========================
This folder contains pre-compiled binaries for a variety of different platforms.
A brief summary of these folders are as follows:
- Windows, Mac, Linux
-- 64bit (x64)
- Android (Kitkat 4.4 minimum target OS)
-- ARMv7 (armeabi-v7a), ARMv8/AArch64 (arm64-v8a)
- iOS
-- FAT Library (armv7 + arm64). Targeted for iOS 8 minimum. Unsigned library.
DEBUG VERSIONS
===============
Debug versions of the libraries can be obtained at https://github.com/SoftwareGuy/ENet-CSharp/releases.
Otherwise you can also compile the library yourself with Debug enabled.
DOT POINTS
===========
1. 32bit Support for Ignorance has been removed. Originally, I did not want to support 32bit operating systems,
however due to some countries in the world still stuck in the 32bit era (Brasil, some Russian areas, etc) I added them as a
goodwill gesture. However, since those who needed the libraries have now vanished, I have stopped building 32bit versions of ENet.
COMPILE THE CODE YOURSELF
=========================
If you don't trust the above binaries then git clone the ENET-CSharp repository (http://github.com/SoftwareGuy/ENet-CSharp) and read the readme.
EXCLUSION INSTRUCTIONS
======================
No need, the meta data will cover that for you.
Still don't know what to do with these? Drop by the Mirror discord and post in the Ignorance channel.

View file

@ -43,27 +43,12 @@ namespace kcp2k
{
KCPConfig conf = new KCPConfig();
bool noConfig = bool.Parse(Environment.GetEnvironmentVariable("NO_CONFIG") ?? "false");
if (!File.Exists("KCPConfig.json") && !noConfig)
if (!File.Exists("KCPConfig.json"))
{
File.WriteAllText("KCPConfig.json", JsonConvert.SerializeObject(conf, Formatting.Indented));
}
else
{
if (noConfig)
{
conf = new KCPConfig();
conf.NoDelay = bool.Parse(Environment.GetEnvironmentVariable("KCP_NODELAY") ?? "true");
conf.Interval = uint.Parse(Environment.GetEnvironmentVariable("KCP_INTERVAL") ?? "10");
conf.FastResend = int.Parse(Environment.GetEnvironmentVariable("KCP_FAST_RESEND") ?? "2");
conf.CongestionWindow = bool.Parse(Environment.GetEnvironmentVariable("KCP_CONGESTION_WINDOW") ?? "false");
conf.SendWindowSize = uint.Parse(Environment.GetEnvironmentVariable("KCP_SEND_WINDOW_SIZE") ?? "4096");
conf.ReceiveWindowSize = uint.Parse(Environment.GetEnvironmentVariable("KCP_RECEIVE_WINDOW_SIZE") ?? "4096");
conf.ConnectionTimeout = int.Parse(Environment.GetEnvironmentVariable("KCP_CONNECTION_TIMEOUT") ?? "10000");
}
else
conf = JsonConvert.DeserializeObject<KCPConfig>(File.ReadAllText("KCPConfig.json"));
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8e62b1f8d9ebd624a9fc425eda441bcd
guid: 546899055272faa4bbcc3514890bb897
folderAsset: yes
DefaultImporter:
externalObjects: {}

View file

@ -0,0 +1,56 @@
//
// Source: https://stackoverflow.com/questions/255341/getting-multiple-keys-of-specified-value-of-a-generic-dictionary#255630
//
using System;
using System.Collections.Generic;
namespace FishBait
{
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 ICollection<TFirst> GetAllKeys()
{
return secondToFirst.Values;
}
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];
}
}
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1069f42b88a4adb4ab1990cec4949343
guid: e602ddfa0d8a4484ca59bdfba7833fb4
MonoImporter:
externalObjects: {}
serializedVersion: 2

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0995e08af14888348b42ecaa6eb21544
guid: 40dd8a7d9b0c2a649a7989442ae66d1a
folderAsset: yes
DefaultImporter:
externalObjects: {}

View file

@ -0,0 +1,310 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Net;
using System.Reflection;
using System.Linq;
using System;
using FishNet.Transporting;
namespace FishBait
{
#if UNITY_EDITOR
[CustomEditor(typeof(FishBaitTransport))]
public class FishBaitInspector : Editor
{
int serverPort = 7777;
string serverIP;
float invalidServerIP = 0;
bool usingLLB = false;
FishBaitDirectConnectModule directModule;
string[] tabs = new string[] { "FishBait Settings", "NAT Punch", "Load Balancer", "Other" };
int currentTab = 0;
[HideInInspector]
public Transport transportHolder;
public override void OnInspectorGUI()
{
var fishBait = (FishBaitTransport)target;
directModule = fishBait.GetComponent<FishBaitDirectConnectModule>();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Label(Resources.Load<Texture>("FishBait"), GUILayout.Height(50), GUILayout.Width(100));
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
if (string.IsNullOrEmpty(fishBait.loadBalancerAddress))
{
// First setup screen, ask if they are using LLB or just a single FishBait node.
EditorGUILayout.HelpBox("Thank you for using FishBait!\nTo get started, please select which setup you are using.", MessageType.None);
if (GUILayout.Button("Load Balancer Setup"))
{
usingLLB = true;
fishBait.loadBalancerAddress = "127.0.0.1";
serverPort = 7070;
}
if (GUILayout.Button("Single FishBait Node Setup"))
{
fishBait.loadBalancerAddress = "127.0.0.1";
fishBait.useLoadBalancer = false;
usingLLB = false;
serverIP = "172.105.109.117";
}
}
else if (usingLLB)
{
// They said they are using LLB, configure it!
EditorGUILayout.HelpBox("The Load Balancer is another server that is different than the FishBait node. Please enter the IP address or domain name of your Load Balancer server, along with the port.", MessageType.None);
EditorGUILayout.HelpBox("Acceptable Examples: 127.0.0.1, mydomain.com", MessageType.Info);
if (Time.realtimeSinceStartup - invalidServerIP < 5)
EditorGUILayout.HelpBox("Invalid Server Address!", MessageType.Error);
serverIP = EditorGUILayout.TextField("Server Address", serverIP);
serverPort = Mathf.Clamp(EditorGUILayout.IntField("Server Port", serverPort), ushort.MinValue, ushort.MaxValue);
if (GUILayout.Button("Continue"))
{
if (IPAddress.TryParse(serverIP, out IPAddress serverAddr))
{
fishBait.loadBalancerAddress = serverAddr.ToString();
fishBait.loadBalancerPort = (ushort)serverPort;
fishBait.serverIP = "127.0.0.1";
fishBait.useLoadBalancer = true;
usingLLB = false;
serverIP = "";
}
else
{
try
{
if (Dns.GetHostEntry(serverIP).AddressList.Length > 0)
{
fishBait.loadBalancerAddress = serverIP;
fishBait.loadBalancerPort = (ushort)serverPort;
fishBait.serverIP = "127.0.0.1";
usingLLB = false;
serverIP = "";
}
else
invalidServerIP = Time.realtimeSinceStartup;
}
catch
{
invalidServerIP = Time.realtimeSinceStartup;
}
}
}
}
else if (fishBait.transport == null)
{
// next up, the actual transport. We are going to loop over all the transport types here and let them select one.
EditorGUILayout.HelpBox("We need to use the same transport used on the server. Please select the same transport used on your FishBait Node(s)", MessageType.None);
transportHolder = (Transport)EditorGUILayout.ObjectField(transportHolder, typeof(Transport), true);
if (GUILayout.Button("Continue"))
{
Type transportType = transportHolder.GetType();
var fishBaitConnectorGO = new GameObject("FishBait - Connector");
fishBaitConnectorGO.transform.SetParent(fishBait.transform);
var fishBaitConnectorAttachTransport = fishBaitConnectorGO.AddComponent(transportType);
Transport fishBaitConnectorTransport = fishBaitConnectorGO.GetComponent<Transport>();
fishBait.transport = fishBaitConnectorTransport;
DestroyImmediate(transportHolder);
transportHolder = null;
}
}
else if (string.IsNullOrEmpty(fishBait.serverIP))
{
// Empty server IP, this is pretty important! Lets show the UI to require it.
EditorGUILayout.HelpBox("For a single FishBait node setup, we need the IP address or domain name of your FishBait server.", MessageType.None);
EditorGUILayout.HelpBox("Acceptable Examples: 172.105.109.117, mydomain.com", MessageType.Info);
if (Time.realtimeSinceStartup - invalidServerIP < 5)
EditorGUILayout.HelpBox("Invalid Server Address!", MessageType.Error);
serverIP = EditorGUILayout.TextField("Server Address", serverIP);
serverPort = Mathf.Clamp(EditorGUILayout.IntField("Server Port", serverPort), ushort.MinValue, ushort.MaxValue);
if (GUILayout.Button("Continue"))
{
if (IPAddress.TryParse(serverIP, out IPAddress serverAddr))
{
fishBait.serverIP = serverAddr.ToString();
fishBait.SetTransportPort((ushort)serverPort);
}
else
{
try
{
if (Dns.GetHostEntry(serverIP).AddressList.Length > 0)
{
fishBait.serverIP = serverIP;
fishBait.SetTransportPort((ushort)serverPort);
}
else
invalidServerIP = Time.realtimeSinceStartup;
}
catch
{
invalidServerIP = Time.realtimeSinceStartup;
}
}
}
}
else if (fishBait.NATPunchtroughPort < 0)
{
// NAT Punchthrough configuration.
EditorGUILayout.HelpBox("Do you wish to use NAT Punchthrough? This can reduce load by up to 80% on your FishBait nodes, but exposes players IP's to other players.", MessageType.None);
if (GUILayout.Button("Use NAT Punchthrough"))
{
fishBait.NATPunchtroughPort = 1;
fishBait.useNATPunch = true;
fishBait.gameObject.AddComponent<FishBaitDirectConnectModule>();
}
if (GUILayout.Button("Do NOT use NAT Punchthrough"))
fishBait.NATPunchtroughPort = 1;
}
else if (directModule != null && directModule.directConnectTransport == null)
{
// NAT Punchthrough setup.
EditorGUILayout.HelpBox("To use direct connecting, we need a transport to communicate with the other clients. Please select a transport to use.", MessageType.None);
transportHolder = (Transport)EditorGUILayout.ObjectField(transportHolder, typeof(Transport), true);
if (GUILayout.Button("Continue"))
{
Type transportType = transportHolder.GetType();
var fishBaitDirectConnectGO = new GameObject("FishBait - Direct Connect");
fishBaitDirectConnectGO.transform.SetParent(fishBait.transform);
var fishBaitDirectConnectTransport = fishBaitDirectConnectGO.AddComponent(transportType);
Transport transport = fishBaitDirectConnectGO.GetComponent<Transport>();
directModule.directConnectTransport = transport;
DestroyImmediate(transportHolder);
transportHolder = null;
}
}
else
{
// They have completed the "setup guide" Show them the main UI
// Remove unused transports...
foreach (var transport in fishBait.GetComponentsInChildren<Transport>())
{
if (!(transport is FishBaitTransport))
{
if (transport != fishBait.transport && (directModule == null ? true : directModule.directConnectTransport != transport))
{
if (transport.gameObject == fishBait.gameObject)
DestroyImmediate(transport);
else
DestroyImmediate(transport.gameObject);
}
}
}
currentTab = GUILayout.Toolbar(currentTab, tabs);
EditorGUILayout.Space();
EditorGUILayout.BeginVertical("Window");
switch (currentTab)
{
case 0:
using (var change = new EditorGUI.ChangeCheckScope())
{
// They are in the FishBait Settings tab.
if (fishBait.useLoadBalancer)
{
EditorGUILayout.HelpBox("While using a Load Balancer, you don't set the FishBait node IP or port.", MessageType.Info);
GUI.enabled = false;
}
fishBait.serverIP = EditorGUILayout.TextField("FishBait Node IP", fishBait.serverIP);
fishBait.serverPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("FishBait Node Port", fishBait.serverPort), ushort.MinValue, ushort.MaxValue);
fishBait.endpointServerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Endpoint Port", fishBait.endpointServerPort), ushort.MinValue, ushort.MaxValue);
if (fishBait.useLoadBalancer)
{
GUI.enabled = true;
}
fishBait.authenticationKey = EditorGUILayout.TextField("FishBait Auth Key", fishBait.authenticationKey);
fishBait.heartBeatInterval = EditorGUILayout.Slider("Heartbeat Time", fishBait.heartBeatInterval, 0.1f, 5f);
fishBait.connectOnAwake = EditorGUILayout.Toggle("Connect on Awake", fishBait.connectOnAwake);
fishBait.transport = (Transport)EditorGUILayout.ObjectField("FishBait Transport", fishBait.transport, typeof(Transport), true);
if (change.changed)
{
EditorUtility.SetDirty(fishBait);
}
}
serializedObject.ApplyModifiedProperties();
break;
case 1:
// NAT punch tab.
if (directModule == null)
{
EditorGUILayout.HelpBox("NAT Punchthrough disabled, missing Direct Connect.", MessageType.Info);
if (GUILayout.Button("Add Direct Connect"))
fishBait.gameObject.AddComponent<FishBaitDirectConnectModule>();
}
else
{
fishBait.useNATPunch = EditorGUILayout.Toggle("Use NAT Punch", fishBait.useNATPunch);
GUI.enabled = true;
directModule.directConnectTransport = (Transport)EditorGUILayout.ObjectField("Direct Transport", directModule.directConnectTransport, typeof(Transport), true);
}
serializedObject.ApplyModifiedProperties();
break;
case 2:
// Load balancer tab
fishBait.useLoadBalancer = EditorGUILayout.Toggle("Use Load Balancer", fishBait.useLoadBalancer);
if (!fishBait.useLoadBalancer)
GUI.enabled = false;
fishBait.loadBalancerAddress = EditorGUILayout.TextField("Load Balancer Address", fishBait.loadBalancerAddress);
fishBait.loadBalancerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Load Balancer Port", fishBait.loadBalancerPort), ushort.MinValue, ushort.MaxValue);
fishBait.region = (FishBaitRegions)EditorGUILayout.EnumPopup("Node Region", fishBait.region);
if (!fishBait.useLoadBalancer)
GUI.enabled = true;
serializedObject.ApplyModifiedProperties();
break;
case 3:
// Other tab...
GUI.enabled = false;
EditorGUILayout.TextField("Server Status", fishBait.serverStatus);
EditorGUILayout.TextField("Server ID", string.IsNullOrEmpty(fishBait.serverId) ? "Not Hosting." : fishBait.serverId);
GUI.enabled = true;
EditorGUILayout.Space();
fishBait.serverName = EditorGUILayout.TextField("Server Name", fishBait.serverName);
fishBait.extraServerData = EditorGUILayout.TextField("Extra Server Data", fishBait.extraServerData);
fishBait.maxServerPlayers = EditorGUILayout.IntField("Max Server Players", fishBait.maxServerPlayers);
fishBait.isPublicServer = EditorGUILayout.Toggle("Is Public Server", fishBait.isPublicServer);
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.PropertyField(serializedObject.FindProperty("connectedToRelay"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("disconnectedFromRelay"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("serverListUpdated"));
serializedObject.ApplyModifiedProperties();
break;
}
EditorGUILayout.EndVertical();
}
}
}
#endif
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 12a7875e95f5ebb4a9b58390441dd933
guid: 0a8926e6ba59d18419fbc44b9ec2408c
MonoImporter:
externalObjects: {}
serializedVersion: 2

View file

@ -0,0 +1,180 @@
using FishNet.Transporting;
using System;
using System.Collections.Generic;
using UnityEngine;
using FishBait;
[RequireComponent(typeof(FishBaitTransport))]
public class FishBaitDirectConnectModule : MonoBehaviour
{
[HideInInspector]
public Transport directConnectTransport;
public bool showDebugLogs;
private FishBaitTransport fishBaitTransport;
private int connectionId;
void Awake()
{
fishBaitTransport = GetComponent<FishBaitTransport>();
if (directConnectTransport == null)
{
Debug.Log("Direct Connect Transport is null!");
return;
}
if (directConnectTransport is FishBaitTransport)
{
Debug.Log("Direct Connect Transport Cannot be the relay, silly. :P");
return;
}
directConnectTransport.OnServerConnectionState += ServerConnectionState;
directConnectTransport.OnServerReceivedData += ServerDataRecived;
directConnectTransport.OnClientConnectionState += ClientConnectionState;
directConnectTransport.OnClientReceivedData += ClientDataRecived;
}
void RemoteConnectionState(RemoteConnectionStateArgs args)
{
connectionId = args.ConnectionId;
}
void ServerConnectionState(ServerConnectionStateArgs args)
{
switch (args.ConnectionState)
{
case LocalConnectionState.Started:
OnServerConnected(connectionId);
break;
case LocalConnectionState.Stopped:
OnServerDisconnected(connectionId);
break;
}
}
void ServerDataRecived(ServerReceivedDataArgs args)
{
OnServerDataReceived(args.ConnectionId, args.Data, ((int)args.Channel));
}
void ClientConnectionState(ClientConnectionStateArgs args)
{
switch (args.ConnectionState)
{
case LocalConnectionState.Started:
OnClientConnected();
break;
case LocalConnectionState.Stopped:
OnClientDisconnected();
break;
}
}
void ClientDataRecived(ClientReceivedDataArgs args)
{
OnClientDataReceived(args.Data, ((int)args.Channel));
}
public void StartServer(int port)
{
if (port > 0)
SetTransportPort(port);
directConnectTransport.StartConnection(true);
if (showDebugLogs)
Debug.Log("Direct Connect Server Created!");
}
public void StopServer()
{
directConnectTransport.StopConnection(true);
}
public void JoinServer(string ip, int port)
{
if (SupportsNATPunch())
SetTransportPort(port);
directConnectTransport.SetClientAddress(ip);
directConnectTransport.StartConnection(false);
}
public void SetTransportPort(int port)
{
directConnectTransport.SetPort((ushort)port);
}
public int GetTransportPort()
{
return directConnectTransport.GetPort();
}
public bool SupportsNATPunch()
{
return directConnectTransport is kcp2k.KcpTransport;
}
public bool KickClient(int clientID)
{
if (showDebugLogs)
Debug.Log("Kicked direct connect client.");
directConnectTransport.StopConnection(clientID, true);
return true;
}
public void ClientDisconnect()
{
directConnectTransport.StopConnection(false);
}
public void ServerSend(int clientID, ArraySegment<byte> data, int channel)
{
directConnectTransport.SendToClient((byte)channel, data, clientID);
}
public void ClientSend(ArraySegment<byte> data, int channel)
{
directConnectTransport.SendToServer((byte)channel, data);
}
#region Transport Callbacks
void OnServerConnected(int clientID)
{
if (showDebugLogs)
Debug.Log("Direct Connect Client Connected");
fishBaitTransport.DirectAddClient(clientID);
}
void OnServerDataReceived(int clientID, ArraySegment<byte> data, int channel)
{
fishBaitTransport.DirectReceiveData(data, channel, clientID);
}
void OnServerDisconnected(int clientID)
{
fishBaitTransport.DirectRemoveClient(clientID);
}
void OnClientConnected()
{
if (showDebugLogs)
Debug.Log("Direct Connect Client Joined");
fishBaitTransport.DirectClientConnected();
}
void OnClientDisconnected()
{
fishBaitTransport.DirectDisconnected();
}
void OnClientDataReceived(ArraySegment<byte> data, int channel)
{
fishBaitTransport.DirectReceiveData(data, channel);
}
#endregion
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d0996ec573094c24890a4d4233ee871e
guid: 21eba6e2b0a7e964eb29db14a4eae4d6
MonoImporter:
externalObjects: {}
serializedVersion: 2

View file

@ -0,0 +1,201 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using UnityEngine;
namespace FishBait
{
public static class FishBaitTools
{
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)
{
if (string.IsNullOrWhiteSpace(value))
{
data.WriteInt(ref position, 0);
}
else
{
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;
}
}
internal static class CompressorExtensions
{
/// <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);
}
}
}
internal static class JsonUtilityHelper
{
public static bool IsJsonArray(string json)
{
return json.StartsWith("[") && json.EndsWith("]");
}
public static T[] FromJson<T>(string json)
{
if (!IsJsonArray(json))
{
throw new System.FormatException("The input json string is not a Json Array");
}
json = "{\"Items\":" + json + "}";
JsonWrapper<T> wrapper = JsonUtility.FromJson<JsonWrapper<T>>(json);
return wrapper.Items;
}
public static string ToJson<T>(T[] array)
{
JsonWrapper<T> wrapper = new JsonWrapper<T>();
wrapper.Items = array;
return JsonUtility.ToJson(wrapper);
}
public static string ToJson<T>(T[] array, bool prettyPrint)
{
JsonWrapper<T> wrapper = new JsonWrapper<T>();
wrapper.Items = array;
return JsonUtility.ToJson(wrapper, prettyPrint);
}
[Serializable]
private class JsonWrapper<T>
{
public T[] Items;
}
}
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b7b9e2c091c3d42439840a02fe700252
guid: 581bb3a7217af5e4bb8d4f514a4adc9b
MonoImporter:
externalObjects: {}
serializedVersion: 2

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0f3dc386b0074be4caf7b858e3189529
guid: 47e639efdf360c049b37e5c7e54cecbf
folderAsset: yes
DefaultImporter:
externalObjects: {}

View file

@ -0,0 +1,200 @@
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility.Extension;
using System;
using System.Net;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;
using FishNet.Transporting.Tugboat;
using FishNet.Managing.Transporting;
using FishNet.Transporting.Multipass;
namespace FishBait
{
[DefaultExecutionOrder(1001)]
public partial class FishBaitTransport : Transport
{
#region Forward everything to Transport
public override event Action<ClientConnectionStateArgs> OnClientConnectionState
{
add
{
transport.OnClientConnectionState += value;
}
remove
{
transport.OnClientConnectionState -= value;
}
}
public override event Action<ServerConnectionStateArgs> OnServerConnectionState
{
add
{
transport.OnServerConnectionState += value;
}
remove
{
transport.OnServerConnectionState -= value;
}
}
public override event Action<RemoteConnectionStateArgs> OnRemoteConnectionState
{
add
{
transport.OnRemoteConnectionState += value;
}
remove
{
transport.OnRemoteConnectionState -= value;
}
}
public override event Action<ClientReceivedDataArgs> OnClientReceivedData
{
add
{
transport.OnClientReceivedData += value;
}
remove
{
transport.OnClientReceivedData -= value;
}
}
public override event Action<ServerReceivedDataArgs> OnServerReceivedData
{
add
{
transport.OnServerReceivedData += value;
}
remove
{
transport.OnServerReceivedData -= value;
}
}
public override string GetConnectionAddress(int connectionId)
{
return transport.GetConnectionAddress(connectionId);
}
public override LocalConnectionState GetConnectionState(bool server)
{
return transport.GetConnectionState(server);
}
public override RemoteConnectionState GetConnectionState(int connectionId)
{
return transport.GetConnectionState(connectionId);
}
public override int GetMTU(byte channel)
{
return transport.GetMTU(channel);
}
public override void HandleClientConnectionState(ClientConnectionStateArgs connectionStateArgs)
{
transport.HandleClientConnectionState(connectionStateArgs);
}
public override void HandleClientReceivedDataArgs(ClientReceivedDataArgs receivedDataArgs)
{
transport.HandleClientReceivedDataArgs(receivedDataArgs);
}
public override void HandleRemoteConnectionState(RemoteConnectionStateArgs connectionStateArgs)
{
transport.HandleRemoteConnectionState(connectionStateArgs);
}
public override void HandleServerConnectionState(ServerConnectionStateArgs connectionStateArgs)
{
transport.HandleServerConnectionState(connectionStateArgs);
}
public override void HandleServerReceivedDataArgs(ServerReceivedDataArgs receivedDataArgs)
{
transport.HandleServerReceivedDataArgs(receivedDataArgs);
}
public override void IterateIncoming(bool server)
{
transport.IterateIncoming(server);
}
public override void IterateOutgoing(bool server)
{
transport.IterateOutgoing(server);
}
public override void SendToClient(byte channelId, ArraySegment<byte> segment, int connectionId)
{
transport.SendToClient(channelId, segment, connectionId);
}
public override void SendToServer(byte channelId, ArraySegment<byte> segment)
{
transport.SendToServer(channelId, segment);
}
public override void Shutdown()
{
transport.Shutdown();
}
public override bool StartConnection(bool server)
{
return transport.StartConnection(server);
}
public override bool StopConnection(bool server)
{
return transport.StopConnection(server);
}
public override bool StopConnection(int connectionId, bool immediately)
{
return transport.StopConnection(connectionId, immediately);
}
#endregion
/// <summary>Called by Transport when a new client connected to the server.</summary>
public Action<int> OnServerConnected = (connId) => Debug.LogWarning("OnServerConnected called with no handler");
/// <summary>Called by Transport when a client disconnected from the server.</summary>
public Action<int> OnServerDisconnected = (connId) => Debug.LogWarning("OnServerDisconnected called with no handler");
/// <summary>Called by Transport when the server received a message from a client.</summary>
public Action<int, ArraySegment<byte>, int> OnServerDataReceived = (connId, data, channel) => Debug.LogWarning("OnServerDataReceived called with no handler");
/// <summary>Called by Transport when the client received a message from the server.</summary>
public Action<ArraySegment<byte>, int> OnClientDataReceived = (data, channel) => Debug.LogWarning("OnClientDataReceived called with no handler");
/// <summary>Called by Transport when the client connected to the server.</summary>
public Action OnClientConnected = () => Debug.LogWarning("OnClientConnected called with no handler");
/// <summary>Called by Transport when the client disconnected from the server.</summary>
public Action OnClientDisconnected = () => Debug.LogWarning("OnClientDisconnected called with no handler");
public void SetTransportPort(ushort port)
{
transport.SetPort(port);
}
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
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 37bf248b7e575fe4592c1ec247b10573
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 95b2cfa2667f26845b384fc393df6c6b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,71 @@
using System;
using FishNet.Transporting;
namespace FishBait
{
public partial class FishBaitTransport : Transport
{
public void DirectAddClient(int clientID)
{
if (!_isServer)
return;
_connectedDirectClients.Add(clientID, _currentMemberId);
OnServerConnected?.Invoke(_currentMemberId);
_currentMemberId++;
}
public void DirectRemoveClient(int clientID)
{
if (!_isServer)
return;
OnServerDisconnected?.Invoke(_connectedDirectClients.GetByFirst(clientID));
_connectedDirectClients.Remove(clientID);
}
public void DirectReceiveData(ArraySegment<byte> data, int channel, int clientID = -1)
{
if (_isServer)
OnServerDataReceived?.Invoke(_connectedDirectClients.GetByFirst(clientID), data, channel);
if (_isClient)
OnClientDataReceived?.Invoke(data, channel);
}
public void DirectClientConnected()
{
_directConnected = true;
OnClientConnected?.Invoke();
}
public void DirectDisconnected()
{
if (_directConnected)
{
_isClient = false;
_directConnected = false;
OnClientDisconnected?.Invoke();
}
else
{
int pos = 0;
_directConnected = false;
_clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer);
_clientSendBuffer.WriteString(ref pos, _cachedHostID);
_clientSendBuffer.WriteBool(ref pos, false); // Direct failed, use relay
_isClient = true;
transport.SendToServer(0, new System.ArraySegment<byte>(_clientSendBuffer, 0, pos));
}
if (_clientProxy != null)
{
_clientProxy.Dispose();
_clientProxy = null;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f914ef01ff1129d47a315fea4ece04fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,77 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine.Events;
using UnityEngine;
using FishNet.Transporting;
namespace FishBait
{
public partial class FishBaitTransport : Transport
{
// Connection/auth variables
[Tooltip("Transport to use.")]
[SerializeField]
public Transport transport;
public string serverIP = null;
public ushort serverPort = 7777;
public ushort endpointServerPort = 8080;
public float heartBeatInterval = 3;
public bool connectOnAwake = true;
public string authenticationKey = "Secret Auth Key";
public UnityEvent disconnectedFromRelay;
public UnityEvent connectedToRelay;
// NAT Puncher variables
public bool useNATPunch = false;
public int NATPunchtroughPort = -1;
private const int NAT_PUNCH_ATTEMPTS = 3;
// LLB variables (LRM Load Balancer)
public bool useLoadBalancer = false;
public ushort loadBalancerPort = 7070;
public string loadBalancerAddress = null;
// Server hosting variables
public string serverName = "My awesome server!";
public string extraServerData = "Map 1";
public int maxServerPlayers = 10;
public bool isPublicServer = true;
private const string LOCALHOST = "127.0.0.1";
// Server list variables
public UnityEvent serverListUpdated;
public List<object> relayServerList { private set; get; } = new List<object>();
// Current Server Information
public string serverStatus = "Not Started.";
public string serverId = string.Empty;
private LRMDirectConnectModule _directConnectModule;
public FishBaitRegions region = FishBaitRegions.NorthAmerica;
private byte[] _clientSendBuffer;
private bool _connectedToRelay = false;
private bool _isClient = false;
private bool _isServer = false;
private bool _directConnected = false;
private bool _isAuthenticated = false;
private int _currentMemberId;
private bool _callbacksInitialized = false;
private string _cachedHostID;
private UdpClient _NATPuncher;
private IPEndPoint _NATIP;
private IPEndPoint _relayPuncherIP;
private byte[] _punchData = new byte[1] { 1 };
private IPEndPoint _directConnectEndpoint;
private SocketProxy _clientProxy;
private BiDictionary<IPEndPoint, SocketProxy> _serverProxies = new BiDictionary<IPEndPoint, SocketProxy>();
private BiDictionary<int, int> _connectedRelayClients = new BiDictionary<int, int>();
private BiDictionary<int, int> _connectedDirectClients = new BiDictionary<int, int>();
private bool _serverListUpdated = false;
}
public enum FishBaitRegions { Any, NorthAmerica, SouthAmerica, Europe, Asia, Africa, Oceania }
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3df9f15433ecfc549b36006c3a9627dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 634f781b33ac69147bff9edd5829cfd6
guid: 4d320dfa8847f3740bbb237370ab5895
folderAsset: yes
DefaultImporter:
externalObjects: {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

View file

@ -0,0 +1,92 @@
fileFormatVersion: 2
guid: 95b2cfa2667f26845b384fc393df6c6b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,68 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
namespace FishBait
{
// This class handles the proxying from punched socket to transport.
public class SocketProxy
{
public DateTime lastInteractionTime;
public Action<IPEndPoint, byte[]> dataReceived;
UdpClient _udpClient;
IPEndPoint _recvEndpoint = new IPEndPoint(IPAddress.Any, 0);
IPEndPoint _remoteEndpoint;
bool _clientInitialRecv = false;
public SocketProxy(int port, IPEndPoint remoteEndpoint)
{
_udpClient = new UdpClient();
_udpClient.Connect(new IPEndPoint(IPAddress.Loopback, port));
_udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient);
lastInteractionTime = DateTime.Now;
// Clone it so when main socket recvies new data, it wont switcheroo on us.
_remoteEndpoint = new IPEndPoint(remoteEndpoint.Address, remoteEndpoint.Port);
}
public SocketProxy(int port)
{
_udpClient = new UdpClient(port);
_udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient);
lastInteractionTime = DateTime.Now;
}
public void RelayData(byte[] data, int length)
{
_udpClient.Send(data, length);
lastInteractionTime = DateTime.Now;
}
public void ClientRelayData(byte[] data, int length)
{
if (_clientInitialRecv)
{
_udpClient.Send(data, length, _recvEndpoint);
lastInteractionTime = DateTime.Now;
}
}
public void Dispose()
{
_udpClient.Dispose();
}
void RecvData(IAsyncResult result)
{
byte[] data = _udpClient.EndReceive(result, ref _recvEndpoint);
_udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient);
_clientInitialRecv = true;
lastInteractionTime = DateTime.Now;
dataReceived?.Invoke(_remoteEndpoint, data);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 968c346ce63c10442aa4bae404eee2f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 546a3ff244764224486eff29cdfacabb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bbb4974b4302f435b9f4663c64d8f803
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 206db668838ebc34b90ae36be24ce3be
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,96 @@

using FishNet.CodeGenerating.Helping;
using FishNet.CodeGenerating.Helping.Extension;
using MonoFN.Cecil;
using UnityEngine;
namespace FishNet.CodeGenerating.Extension
{
internal static class TypeDefinitionExtensions
{
/// <summary>
/// Returns a method in the next base class.
/// </summary>
public static MethodReference GetMethodReferenceInBase(this TypeDefinition td, string methodName)
{
if (td == null)
{
CodegenSession.LogError($"TypeDefinition is null.");
return null;
}
if (td.BaseType == null)
{
CodegenSession.LogError($"BaseType for {td.FullName} is null.");
return null;
}
TypeDefinition baseTd = td.BaseType.CachedResolve();
MethodDefinition baseMd = baseTd.GetMethod(methodName);
//Not found.
if (baseMd == null)
return null;
//Is generic.
if (baseTd.HasGenericParameters)
{
TypeReference baseTr = td.BaseType;
GenericInstanceType baseGit = (GenericInstanceType)baseTr;
CodegenSession.ImportReference(baseMd.ReturnType);
MethodReference mr = new MethodReference(methodName, baseMd.ReturnType)
{
DeclaringType = baseGit,
CallingConvention = baseMd.CallingConvention,
HasThis = baseMd.HasThis,
ExplicitThis = baseMd.ExplicitThis,
};
return mr;
}
//Not generic.
else
{
return CodegenSession.ImportReference(baseMd);
}
}
/// <summary>
/// Returns a method in any inherited classes. The first found method is returned.
/// </summary>
public static MethodDefinition GetMethodDefinitionInAnyBase(this TypeDefinition td, string methodName)
{
while (td != null)
{
foreach (MethodDefinition md in td.Methods)
{
if (md.Name == methodName)
return md;
}
try
{
td = td.GetNextBaseTypeDefinition();
}
catch
{
return null;
}
}
return null;
}
/// <summary>
/// Returns the next base type.
/// </summary>
internal static TypeDefinition GetNextBaseTypeDefinition(this TypeDefinition typeDef)
{
return (typeDef.BaseType == null) ? null : typeDef.BaseType.CachedResolve();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9f00cf3dc8b90b469c3c9cb8b87fc88
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@

using FishNet.CodeGenerating.Helping;
using FishNet.CodeGenerating.Helping.Extension;
using MonoFN.Cecil;
using UnityEngine;
namespace FishNet.CodeGenerating.Extension
{
internal static class TypeReferenceExtensions
{
/// <summary>
/// Returns a method in the next base class.
/// </summary>
public static MethodReference GetMethodInBase(this TypeReference tr, string methodName)
{
return GetMethodInBase(tr.CachedResolve(), methodName);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 239b1b10db80c594d93b7ba4ee2c1ec5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,9 @@
After updating a custom Cecil to fix conflict with Unity.Burst in 2021 perform the following:
- Open cecil in it's own project; eg: do not place directly in FN.
- Rename namespace.Mono to namespace.MonoFN.
- Current project rename strings, "Mono to "MonoFN
- Replace current project #if INSIDE_ROCKS to #if UNITY_EDITOR
- Comment out `[assembly: AssemblyTitle ("MonoFN.Cecil.Rocks")]` within rocks\Mono.Cecil.Rocks\AssemblyInfo.cs.
- Delete obj/bin/tests folders.
- Copy into FN project.

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ad80075449f17c548877161f32a9841a
guid: 9133eb285bd7f3c4f89f4d7a2a079c6b
TextScriptImporter:
externalObjects: {}
userData:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: efeca2428bd9fe64d872a626b93ff0cf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,94 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Object;
using FishNet.Object.Helping;
using FishNet.Object.Prediction;
using FishNet.Object.Synchronizing;
using MonoFN.Cecil;
namespace FishNet.CodeGenerating.Helping
{
public class AttributeHelper
{
#region Reflection references.
internal string ReplicateAttribute_FullName;
internal string ReconcileAttribute_FullName;
private string ServerAttribute_FullName;
private string ClientAttribute_FullName;
private string ServerRpcAttribute_FullName;
private string ObserversRpcAttribute_FullName;
private string TargetRpcAttribute_FullName;
private string SyncVarAttribute_FullName;
private string SyncObjectAttribute_FullName;
#endregion
internal bool ImportReferences()
{
ServerAttribute_FullName = typeof(ServerAttribute).FullName;
ClientAttribute_FullName = typeof(ClientAttribute).FullName;
ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName;
ObserversRpcAttribute_FullName = typeof(ObserversRpcAttribute).FullName;
TargetRpcAttribute_FullName = typeof(TargetRpcAttribute).FullName;
SyncVarAttribute_FullName = typeof(SyncVarAttribute).FullName;
SyncObjectAttribute_FullName = typeof(SyncObjectAttribute).FullName;
ReplicateAttribute_FullName = typeof(ReplicateAttribute).FullName;
ReconcileAttribute_FullName = typeof(ReconcileAttribute).FullName;
return true;
}
/// <summary>
/// Returns type of Rpc attributeFullName is for.
/// </summary>
/// <param name="attributeFullName"></param>
/// <returns></returns>
public RpcType GetRpcAttributeType(CustomAttribute ca)
{
if (ca.Is(ServerRpcAttribute_FullName))
return RpcType.Server;
else if (ca.Is(ObserversRpcAttribute_FullName))
return RpcType.Observers;
else if (ca.Is(TargetRpcAttribute_FullName))
return RpcType.Target;
else
return RpcType.None;
}
/// <summary>
/// Returns type of Rpc attributeFullName is for.
/// </summary>
/// <param name="attributeFullName"></param>
/// <returns></returns>
internal QolAttributeType GetQolAttributeType(string attributeFullName)
{
if (attributeFullName == ServerAttribute_FullName)
return QolAttributeType.Server;
else if (attributeFullName == ClientAttribute_FullName)
return QolAttributeType.Client;
else
return QolAttributeType.None;
}
/// <summary>
/// Returns if attribute if a SyncVarAttribute.
/// </summary>
/// <param name="attributeFullName"></param>
/// <returns></returns>
public bool IsSyncVarAttribute(string attributeFullName)
{
return (attributeFullName == SyncVarAttribute_FullName);
}
/// <summary>
/// Returns if attribute if a SyncObjectAttribute.
/// </summary>
/// <param name="attributeFullName"></param>
/// <returns></returns>
public bool IsSyncObjectAttribute(string attributeFullName)
{
return (attributeFullName == SyncObjectAttribute_FullName);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d32f3dc23b55598429c5cfe6156e6243
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,221 @@
using FishNet.CodeGenerating.Processing;
using FishNet.CodeGenerating.Processing.Rpc;
using MonoFN.Cecil;
using System.Collections.Generic;
using Unity.CompilationPipeline.Common.Diagnostics;
using UnityEngine;
using SR = System.Reflection;
namespace FishNet.CodeGenerating.Helping
{
internal static class CodegenSession
{
[System.ThreadStatic]
internal static ModuleDefinition Module;
[System.ThreadStatic]
internal static List<DiagnosticMessage> Diagnostics;
[System.ThreadStatic]
internal static TimeManagerHelper TimeManagerHelper;
[System.ThreadStatic]
internal static AttributeHelper AttributeHelper;
[System.ThreadStatic]
internal static GeneralHelper GeneralHelper;
[System.ThreadStatic]
internal static GenericReaderHelper GenericReaderHelper;
[System.ThreadStatic]
internal static GenericWriterHelper GenericWriterHelper;
[System.ThreadStatic]
internal static ObjectHelper ObjectHelper;
[System.ThreadStatic]
internal static NetworkBehaviourHelper NetworkBehaviourHelper;
[System.ThreadStatic]
internal static ReaderGenerator ReaderGenerator;
[System.ThreadStatic]
internal static ReaderHelper ReaderHelper;
[System.ThreadStatic]
internal static CreatedSyncVarGenerator CreatedSyncVarGenerator;
[System.ThreadStatic]
internal static TransportHelper TransportHelper;
[System.ThreadStatic]
internal static WriterGenerator WriterGenerator;
[System.ThreadStatic]
internal static WriterHelper WriterHelper;
[System.ThreadStatic]
internal static CustomSerializerProcessor CustomSerializerProcessor;
[System.ThreadStatic]
internal static NetworkBehaviourProcessor NetworkBehaviourProcessor;
[System.ThreadStatic]
internal static QolAttributeProcessor QolAttributeProcessor;
[System.ThreadStatic]
internal static RpcProcessor RpcProcessor;
[System.ThreadStatic]
internal static NetworkBehaviourSyncProcessor NetworkBehaviourSyncProcessor;
[System.ThreadStatic]
internal static NetworkBehaviourPredictionProcessor NetworkBehaviourPredictionProcessor;
/// <summary>
/// SyncVars that are being accessed from an assembly other than the currently being processed one.
/// </summary>
[System.ThreadStatic]
internal static List<FieldDefinition> DifferentAssemblySyncVars;
/// <summary>
/// Logs a warning.
/// </summary>
/// <param name="msg"></param>
internal static void LogWarning(string msg)
{
#if UNITY_2020_1_OR_NEWER
Diagnostics.AddWarning(msg);
#else
Debug.LogWarning(msg);
#endif
}
/// <summary>
/// Logs an error.
/// </summary>
/// <param name="msg"></param>
internal static void LogError(string msg)
{
#if UNITY_2020_1_OR_NEWER
Diagnostics.AddError(msg);
#else
Debug.LogError(msg);
#endif
}
/// <summary>
/// Resets all helpers while importing any information needed by them.
/// </summary>
/// <param name="module"></param>
/// <returns></returns>
internal static bool Reset(ModuleDefinition module)
{
Module = module;
Diagnostics = new List<DiagnosticMessage>();
TimeManagerHelper = new TimeManagerHelper();
AttributeHelper = new AttributeHelper();
GeneralHelper = new GeneralHelper();
GenericReaderHelper = new GenericReaderHelper();
GenericWriterHelper = new GenericWriterHelper();
ObjectHelper = new ObjectHelper();
NetworkBehaviourHelper = new NetworkBehaviourHelper();
ReaderGenerator = new ReaderGenerator();
ReaderHelper = new ReaderHelper();
CreatedSyncVarGenerator = new CreatedSyncVarGenerator();
TransportHelper = new TransportHelper();
WriterGenerator = new WriterGenerator();
WriterHelper = new WriterHelper();
CustomSerializerProcessor = new CustomSerializerProcessor();
NetworkBehaviourProcessor = new NetworkBehaviourProcessor();
QolAttributeProcessor = new QolAttributeProcessor();
RpcProcessor = new RpcProcessor();
NetworkBehaviourSyncProcessor = new NetworkBehaviourSyncProcessor();
NetworkBehaviourPredictionProcessor = new NetworkBehaviourPredictionProcessor();
DifferentAssemblySyncVars = new List<FieldDefinition>();
if (!TimeManagerHelper.ImportReferences())
return false;
if (!NetworkBehaviourPredictionProcessor.ImportReferences())
return false;
if (!NetworkBehaviourSyncProcessor.ImportReferences())
return false;
if (!GeneralHelper.ImportReferences())
return false;
if (!AttributeHelper.ImportReferences())
return false;
if (!GenericReaderHelper.ImportReferences())
return false;
if (!GenericWriterHelper.ImportReferences())
return false;
if (!ObjectHelper.ImportReferences())
return false;
if (!NetworkBehaviourHelper.ImportReferences())
return false;
if (!ReaderGenerator.ImportReferences())
return false;
if (!ReaderHelper.ImportReferences())
return false;
if (!CreatedSyncVarGenerator.ImportReferences())
return false;
if (!TransportHelper.ImportReferences())
return false;
if (!WriterGenerator.ImportReferences())
return false;
if (!WriterHelper.ImportReferences())
return false;
return true;
}
#region ImportReference.
public static MethodReference ImportReference(SR.MethodBase method)
{
return Module.ImportReference(method);
}
public static MethodReference ImportReference(SR.MethodBase method, IGenericParameterProvider context)
{
return Module.ImportReference(method, context);
}
public static TypeReference ImportReference(TypeReference type)
{
return Module.ImportReference(type);
}
public static TypeReference ImportReference(TypeReference type, IGenericParameterProvider context)
{
return Module.ImportReference(type, context);
}
public static FieldReference ImportReference(FieldReference field)
{
return Module.ImportReference(field);
}
public static FieldReference ImportReference(FieldReference field, IGenericParameterProvider context)
{
return Module.ImportReference(field, context);
}
public static MethodReference ImportReference(MethodReference method)
{
return Module.ImportReference(method);
}
public static MethodReference ImportReference(MethodReference method, IGenericParameterProvider context)
{
return Module.ImportReference(method, context);
}
public static TypeReference ImportReference(System.Type type)
{
return ImportReference(type, null);
}
public static TypeReference ImportReference(System.Type type, IGenericParameterProvider context)
{
return Module.ImportReference(type, context);
}
public static FieldReference ImportReference(SR.FieldInfo field)
{
return Module.ImportReference(field);
}
public static FieldReference ImportReference(SR.FieldInfo field, IGenericParameterProvider context)
{
return Module.ImportReference(field, context);
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e8416eee3308f54fa942003de975420
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,121 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Object.Synchronizing;
using FishNet.Object.Synchronizing.Internal;
using MonoFN.Cecil;
using MonoFN.Cecil.Rocks;
using System;
using System.Collections.Generic;
namespace FishNet.CodeGenerating.Helping
{
internal class CreatedSyncVarGenerator
{
private readonly Dictionary<string, CreatedSyncVar> _createdSyncVars = new Dictionary<string, CreatedSyncVar>();
#region Relfection references.
private TypeReference _syncBase_TypeRef;
internal TypeReference SyncVar_TypeRef;
private MethodReference _syncVar_Constructor_MethodRef;
#endregion
#region Const.
private const string GETVALUE_NAME = "GetValue";
private const string SETVALUE_NAME = "SetValue";
#endregion
/* //feature add and test the dirty boolean changes
* eg... instead of base.Dirty()
* do if (!base.Dirty()) return false;
* See synclist for more info. */
/// <summary>
/// Imports references needed by this helper.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
internal bool ImportReferences()
{
SyncVar_TypeRef = CodegenSession.ImportReference(typeof(SyncVar<>));
MethodDefinition svConstructor = SyncVar_TypeRef.GetFirstConstructor(true);
_syncVar_Constructor_MethodRef = CodegenSession.ImportReference(svConstructor);
Type syncBaseType = typeof(SyncBase);
_syncBase_TypeRef = CodegenSession.ImportReference(syncBaseType);
return true;
}
/// <summary>
/// Gets and optionally creates data for SyncVar<typeOfField>
/// </summary>
/// <param name="dataTr"></param>
/// <returns></returns>
internal CreatedSyncVar GetCreatedSyncVar(FieldDefinition originalFd, bool createMissing)
{
TypeReference dataTr = originalFd.FieldType;
TypeDefinition dataTd = dataTr.CachedResolve();
string typeHash = dataTr.FullName + dataTr.IsArray.ToString();
if (_createdSyncVars.TryGetValue(typeHash, out CreatedSyncVar createdSyncVar))
{
return createdSyncVar;
}
else
{
if (!createMissing)
return null;
CodegenSession.ImportReference(dataTd);
GenericInstanceType syncVarGit = SyncVar_TypeRef.MakeGenericInstanceType(new TypeReference[] { dataTr });
TypeReference genericDataTr = syncVarGit.GenericArguments[0];
//Make sure can serialize.
bool canSerialize = CodegenSession.GeneralHelper.HasSerializerAndDeserializer(genericDataTr, true);
if (!canSerialize)
{
CodegenSession.LogError($"SyncVar {originalFd.Name} data type {genericDataTr.FullName} does not support serialization. Use a supported type or create a custom serializer.");
return null;
}
//Set needed methods from syncbase.
MethodReference setSyncIndexMr;
MethodReference genericSyncVarCtor = _syncVar_Constructor_MethodRef.MakeHostInstanceGeneric(syncVarGit);
if (!CodegenSession.NetworkBehaviourSyncProcessor.SetSyncBaseMethods(_syncBase_TypeRef.CachedResolve(), out setSyncIndexMr, out _))
return null;
MethodReference setValueMr = null;
MethodReference getValueMr = null;
foreach (MethodDefinition md in SyncVar_TypeRef.CachedResolve().Methods)
{
//GetValue.
if (md.Name == GETVALUE_NAME)
{
MethodReference mr = CodegenSession.ImportReference(md);
getValueMr = mr.MakeHostInstanceGeneric(syncVarGit);
}
//SetValue.
else if (md.Name == SETVALUE_NAME)
{
MethodReference mr = CodegenSession.ImportReference(md);
setValueMr = mr.MakeHostInstanceGeneric(syncVarGit);
}
}
if (setValueMr == null || getValueMr == null)
return null;
CreatedSyncVar csv = new CreatedSyncVar(syncVarGit, dataTd, getValueMr, setValueMr, setSyncIndexMr, null, genericSyncVarCtor);
_createdSyncVars.Add(typeHash, csv);
return csv;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 935ec97b96b35b94f8c6880c6908304c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: df90983b61081f84b990ac494ac8bdb6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,54 @@
using MonoFN.Cecil;
using System.Linq;
namespace FishNet.CodeGenerating.Helping.Extension
{
internal static class CustomAttributeExtensions
{
/// <summary>
/// Finds a field within an attribute.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="customAttr"></param>
/// <param name="field"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
internal static T GetField<T>(this CustomAttribute customAttr, string field, T defaultValue)
{
foreach (CustomAttributeNamedArgument customField in customAttr.Fields)
{
if (customField.Name == field)
{
return (T)customField.Argument.Value;
}
}
return defaultValue;
}
/// <summary>
/// Returns if any of the attributes match IAtrribute.
/// </summary>
/// <typeparam name="TAttribute"></typeparam>
/// <param name="attributeProvider"></param>
/// <returns></returns>
internal static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider)
{
return attributeProvider.CustomAttributes.Any(attr => attr.AttributeType.Is<TAttribute>());
}
/// <summary>
/// Returns if ca is of type target.
/// </summary>
/// <param name="ca"></param>
/// <param name="targetFullName"></param>
/// <returns></returns>
internal static bool Is(this CustomAttribute ca, string targetFullName)
{
return ca.AttributeType.FullName == targetFullName;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a66d771ab331fae408142a5c04abd74e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,41 @@
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Unity.CompilationPipeline.Common.Diagnostics;
namespace FishNet.CodeGenerating.Helping
{
internal static class Diagnostics
{
internal static void AddError(this List<DiagnosticMessage> diagnostics, string message)
{
diagnostics.AddMessage(DiagnosticType.Error, (SequencePoint)null, message);
}
internal static void AddWarning(this List<DiagnosticMessage> diagnostics, string message)
{
diagnostics.AddMessage(DiagnosticType.Warning, (SequencePoint)null, message);
}
internal static void AddError(this List<DiagnosticMessage> diagnostics, MethodDefinition methodDef, string message)
{
diagnostics.AddMessage(DiagnosticType.Error, methodDef.DebugInformation.SequencePoints.FirstOrDefault(), message);
}
internal static void AddMessage(this List<DiagnosticMessage> diagnostics, DiagnosticType diagnosticType, SequencePoint sequencePoint, string message)
{
diagnostics.Add(new DiagnosticMessage
{
DiagnosticType = diagnosticType,
File = sequencePoint?.Document.Url.Replace($"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}", ""),
Line = sequencePoint?.StartLine ?? 0,
Column = sequencePoint?.StartColumn ?? 0,
MessageData = $" - {message}"
});
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb7b65b572b01444cbe3c9d830cd3587
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,32 @@
using MonoFN.Cecil;
using System;
namespace FishNet.CodeGenerating.Helping.Extension
{
internal static class FieldReferenceExtensions
{
/// <summary>
/// Gets a Resolve favoring cached results first.
/// </summary>
internal static FieldDefinition CachedResolve(this FieldReference fieldRef)
{
return CodegenSession.GeneralHelper.GetFieldReferenceResolve(fieldRef);
}
public static FieldReference MakeHostGenericIfNeeded(this FieldReference fd)
{
if (fd.DeclaringType.HasGenericParameters)
{
return new FieldReference(fd.Name, fd.FieldType, fd.DeclaringType.CachedResolve().ConvertToGenericIfNeeded());
}
return fd;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d983ebd0c9e1b745902030c2f7a8e99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,191 @@
using FishNet.CodeGenerating.Helping.Extension;
using MonoFN.Cecil;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.CodeGenerating.Helping
{
public static class Constructors
{
/// <summary>
/// Gets the first constructor that optionally has, or doesn't have parameters.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static MethodDefinition GetFirstConstructor(this TypeReference typeRef, bool requireParameters)
{
return typeRef.CachedResolve().GetFirstConstructor(requireParameters);
}
/// <summary>
/// Gets the first constructor that optionally has, or doesn't have parameters.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static MethodDefinition GetFirstConstructor(this TypeDefinition typeDef, bool requireParameters)
{
foreach (MethodDefinition methodDef in typeDef.Methods)
{
if (methodDef.IsConstructor && methodDef.IsPublic)
{
if (requireParameters && methodDef.Parameters.Count > 0)
return methodDef;
else if (!requireParameters && methodDef.Parameters.Count == 0)
return methodDef;
}
}
return null;
}
/// <summary>
/// Gets the first public constructor with no parameters.
/// </summary>
/// <returns></returns>
public static MethodDefinition GetConstructor(this TypeReference typeRef)
{
return typeRef.CachedResolve().GetConstructor();
}
/// <summary>
/// Gets the first public constructor with no parameters.
/// </summary>
/// <returns></returns>
public static MethodDefinition GetConstructor(this TypeDefinition typeDef)
{
foreach (MethodDefinition methodDef in typeDef.Methods)
{
if (methodDef.IsConstructor && methodDef.IsPublic && methodDef.Parameters.Count == 0)
return methodDef;
}
return null;
}
/// <summary>
/// Gets all constructors on typeDef.
/// </summary>
/// <returns></returns>
public static List<MethodDefinition> GetConstructors(this TypeDefinition typeDef)
{
List<MethodDefinition> lst = new List<MethodDefinition>();
foreach (MethodDefinition methodDef in typeDef.Methods)
{
if (methodDef.IsConstructor)
lst.Add(methodDef);
}
return lst;
}
/// <summary>
/// Gets constructor which has arguments.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static MethodDefinition GetConstructor(this TypeReference typeRef, Type[] arguments)
{
return typeRef.CachedResolve().GetConstructor(arguments);
}
/// <summary>
/// Gets constructor which has arguments.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
public static MethodDefinition GetConstructor(this TypeDefinition typeDef, Type[] arguments)
{
Type[] argsCopy = (arguments == null) ? new Type[0] : arguments;
foreach (MethodDefinition methodDef in typeDef.Methods)
{
if (methodDef.IsConstructor && methodDef.IsPublic && methodDef.Parameters.Count == argsCopy.Length)
{
bool match = true;
for (int i = 0; i < argsCopy.Length; i++)
{
if (methodDef.Parameters[0].ParameterType.FullName != argsCopy[i].FullName)
{
match = false;
break;
}
}
if (match)
return methodDef;
}
}
return null;
}
/// <summary>
/// Gets constructor which has arguments.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static MethodDefinition GetConstructor(this TypeReference typeRef, TypeReference[] arguments)
{
return typeRef.CachedResolve().GetConstructor(arguments);
}
/// <summary>
/// Gets constructor which has arguments.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
public static MethodDefinition GetConstructor(this TypeDefinition typeDef, TypeReference[] arguments)
{
TypeReference[] argsCopy = (arguments == null) ? new TypeReference[0] : arguments;
foreach (MethodDefinition methodDef in typeDef.Methods)
{
if (methodDef.IsConstructor && methodDef.IsPublic && methodDef.Parameters.Count == argsCopy.Length)
{
bool match = true;
for (int i = 0; i < argsCopy.Length; i++)
{
if (methodDef.Parameters[0].ParameterType.FullName != argsCopy[i].FullName)
{
match = false;
break;
}
}
if (match)
return methodDef;
}
}
return null;
}
/// <summary>
/// Resolves the constructor with parameterCount for typeRef.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static MethodDefinition GetConstructor(this TypeReference typeRef, int parameterCount)
{
return typeRef.CachedResolve().GetConstructor(parameterCount);
}
/// <summary>
/// Resolves the constructor with parameterCount for typeRef.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
public static MethodDefinition GetConstructor(this TypeDefinition typeDef, int parameterCount)
{
foreach (MethodDefinition methodDef in typeDef.Methods)
{
if (methodDef.IsConstructor && methodDef.IsPublic && methodDef.Parameters.Count == parameterCount)
return methodDef;
}
return null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1a7e03137ca78704e999f3a3dc68b953
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,169 @@
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System.Collections.Generic;
namespace FishNet.CodeGenerating.Helping.Extension
{
public static class ILProcessorExtensions
{
/// <summary>
/// Creates a debug log for text without any conditions.
/// </summary>
public static void DebugLog(this ILProcessor processor, string txt)
{
processor.Emit(OpCodes.Ldstr, txt);
processor.Emit(OpCodes.Call, CodegenSession.GeneralHelper.Debug_LogCommon_MethodRef);
}
/// <summary>
/// Creates a debug log for vd without any conditions.
/// </summary>
public static void DebugLog(this ILProcessor processor, VariableDefinition vd)
{
processor.Emit(OpCodes.Ldloc, vd);
processor.Emit(OpCodes.Box, vd.VariableType);
processor.Emit(OpCodes.Call, CodegenSession.GeneralHelper.Debug_LogCommon_MethodRef);
}
/// <summary>
/// Creates a debug log for vd without any conditions.
/// </summary>
public static void DebugLog(this ILProcessor processor, FieldDefinition fd, bool loadArg0)
{
if (loadArg0)
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, fd);
processor.Emit(OpCodes.Box, fd.FieldType);
processor.Emit(OpCodes.Call, CodegenSession.GeneralHelper.Debug_LogCommon_MethodRef);
}
/// <summary>
/// Creates a debug log for pd without any conditions.
/// </summary>
public static void DebugLog(this ILProcessor processor, ParameterDefinition pd)
{
processor.Emit(OpCodes.Ldloc, pd);
processor.Emit(OpCodes.Box, pd.ParameterType);
processor.Emit(OpCodes.Call, CodegenSession.GeneralHelper.Debug_LogCommon_MethodRef);
}
///// <summary>
///// Creates a debug log for mr without any conditions.
///// </summary>
//public static void DebugLog(this ILProcessor processor, MethodReference mr)
//{
// processor.Emit(OpCodes.Call, mr);
// processor.Emit(OpCodes.Box, mr.ReturnType);
// processor.Emit(OpCodes.Call, CodegenSession.GeneralHelper.Debug_LogCommon_MethodRef);
//}
/// <summary>
/// Inserts instructions at the beginning.
/// </summary>
/// <param name="processor"></param>
/// <param name="instructions"></param>
public static void InsertAt(this ILProcessor processor, int target, List<Instruction> instructions)
{
for (int i = 0; i < instructions.Count; i++)
processor.Body.Instructions.Insert(i + target, instructions[i]);
}
/// <summary>
/// Inserts instructions at the beginning.
/// </summary>
/// <param name="processor"></param>
/// <param name="instructions"></param>
public static void InsertFirst(this ILProcessor processor, List<Instruction> instructions)
{
for (int i = 0; i < instructions.Count; i++)
processor.Body.Instructions.Insert(i, instructions[i]);
}
/// <summary>
/// Inserts instructions at the end while also moving Ret down.
/// </summary>
/// <param name="processor"></param>
/// <param name="instructions"></param>
public static void InsertLast(this ILProcessor processor, List<Instruction> instructions)
{
bool retRemoved = false;
int startingCount = processor.Body.Instructions.Count;
//Remove ret if it exist and add it back in later.
if (startingCount > 0)
{
if (processor.Body.Instructions[startingCount - 1].OpCode == OpCodes.Ret)
{
processor.Body.Instructions.RemoveAt(startingCount - 1);
retRemoved = true;
}
}
foreach (Instruction inst in instructions)
processor.Append(inst);
//Add ret back if it was removed.
if (retRemoved)
processor.Emit(OpCodes.Ret);
}
/// <summary>
/// Inserts instructions before target.
/// </summary>
/// <param name="processor"></param>
/// <param name="instructions"></param>
public static void InsertBefore(this ILProcessor processor, Instruction target, List<Instruction> instructions)
{
int index = processor.Body.Instructions.IndexOf(target);
for (int i = 0; i < instructions.Count; i++)
processor.Body.Instructions.Insert(index + i, instructions[i]);
}
/// <summary>
/// Adds instructions to the end of processor.
/// </summary>
/// <param name="processor"></param>
/// <param name="instructions"></param>
public static void Add(this ILProcessor processor, List<Instruction> instructions)
{
for (int i = 0; i < instructions.Count; i++)
processor.Body.Instructions.Add(instructions[i]);
}
/// <summary>
/// Inserts instructions before returns. Only works on void types.
/// </summary>
/// <param name="processor"></param>
/// <param name="instructions"></param>
public static void InsertBeforeReturns(this ILProcessor processor, List<Instruction> instructions)
{
if (processor.Body.Method.ReturnType.FullName != CodegenSession.Module.TypeSystem.Void.FullName)
{
CodegenSession.LogError($"Cannot insert instructions before returns on {processor.Body.Method.FullName} because it does not return void.");
return;
}
/* Insert at the end of the method
* and get the first instruction that was inserted.
* Any returns or breaks which would exit the method
* will jump to this instruction instead. */
processor.InsertLast(instructions);
Instruction startInst = processor.Body.Instructions[processor.Body.Instructions.Count - instructions.Count];
//Look for anything that jumps to rets.
for (int i = 0; i < processor.Body.Instructions.Count; i++)
{
Instruction inst = processor.Body.Instructions[i];
if (inst.Operand is Instruction operInst)
{
if (operInst.OpCode == OpCodes.Ret)
inst.Operand = startInst;
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 820cf8401d4d71c4196dda444559ef8a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,12 @@
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System;
using System.Collections.Generic;
namespace FishNet.CodeGenerating.Helping
{
public static class Instructions
{
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 360667149f16b6c4aba61fd05427cbfb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,55 @@
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
namespace FishNet.CodeGenerating.Helping.Extension
{
internal static class MethodDefinitionExtensions
{
/// <summary>
/// Clears the method content and returns ret.
/// </summary>
internal static void ClearMethodWithRet(this MethodDefinition md, ModuleDefinition importReturnModule = null)
{
md.Body.Instructions.Clear();
ILProcessor processor = md.Body.GetILProcessor();
processor.Add(CodegenSession.GeneralHelper.CreateRetDefault(md, importReturnModule));
}
/// <summary>
/// Returns the ParameterDefinition index from end of parameters.
/// </summary>
/// <param name="md"></param>
/// <param name="index"></param>
/// <returns></returns>
internal static ParameterDefinition GetEndParameter(this MethodDefinition md, int index)
{
//Not enough parameters.
if (md.Parameters.Count < (index + 1))
return null;
return md.Parameters[md.Parameters.Count - (index + 1)];
}
/// <summary>
/// Creates a variable type within the body and returns it's VariableDef.
/// </summary>
internal static VariableDefinition CreateVariable(this MethodDefinition methodDef, TypeReference variableTypeRef)
{
VariableDefinition variableDef = new VariableDefinition(variableTypeRef);
methodDef.Body.Variables.Add(variableDef);
return variableDef;
}
/// <summary>
/// Creates a variable type within the body and returns it's VariableDef.
/// </summary>
internal static VariableDefinition CreateVariable(this MethodDefinition methodDef, System.Type variableType)
{
return CreateVariable(methodDef, CodegenSession.GeneralHelper.GetTypeReference(variableType));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 999d4ae4862274f4ba50569c221976dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,123 @@
using MonoFN.Cecil;
using MonoFN.Cecil.Rocks;
using System;
namespace FishNet.CodeGenerating.Helping.Extension
{
public static class MethodReferenceExtensions
{
/// <summary>
/// Makes a generic method with specified arguments.
/// </summary>
/// <param name="method"></param>
/// <param name="genericArguments"></param>
/// <returns></returns>
public static GenericInstanceMethod MakeGenericMethod(this MethodReference method, params TypeReference[] genericArguments)
{
GenericInstanceMethod result = new GenericInstanceMethod(method);
foreach (TypeReference argument in genericArguments)
result.GenericArguments.Add(argument);
return result;
}
/// <summary>
/// Makes a generic method with the same arguments as the original.
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public static GenericInstanceMethod MakeGenericMethod(this MethodReference method)
{
GenericInstanceMethod result = new GenericInstanceMethod(method);
foreach (ParameterDefinition pd in method.Parameters)
result.GenericArguments.Add(pd.ParameterType);
return result;
}
/// <summary>
/// Gets a Resolve favoring cached results first.
/// </summary>
internal static MethodDefinition CachedResolve(this MethodReference methodRef)
{
return CodegenSession.GeneralHelper.GetMethodReferenceResolve(methodRef);
}
/// <summary>
/// Given a method of a generic class such as ArraySegment`T.get_Count,
/// and a generic instance such as ArraySegment`int
/// Creates a reference to the specialized method ArraySegment`int`.get_Count
/// <para> Note that calling ArraySegment`T.get_Count directly gives an invalid IL error </para>
/// </summary>
/// <param name="self"></param>
/// <param name="instanceType"></param>
/// <returns></returns>
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, GenericInstanceType instanceType)
{
MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
{
CallingConvention = self.CallingConvention,
HasThis = self.HasThis,
ExplicitThis = self.ExplicitThis
};
foreach (ParameterDefinition parameter in self.Parameters)
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
foreach (GenericParameter generic_parameter in self.GenericParameters)
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
return CodegenSession.ImportReference(reference);
}
/// <summary>
/// Given a method of a generic class such as ArraySegment`T.get_Count,
/// and a generic instance such as ArraySegment`int
/// Creates a reference to the specialized method ArraySegment`int`.get_Count
/// <para> Note that calling ArraySegment`T.get_Count directly gives an invalid IL error </para>
/// </summary>
/// <param name="self"></param>
/// <param name="instanceType"></param>
/// <returns></returns>
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, TypeReference typeRef, params TypeReference[] args)
{
GenericInstanceType git = typeRef.MakeGenericInstanceType(args);
MethodReference reference = new MethodReference(self.Name, self.ReturnType, git)
{
CallingConvention = self.CallingConvention,
HasThis = self.HasThis,
ExplicitThis = self.ExplicitThis
};
foreach (ParameterDefinition parameter in self.Parameters)
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
foreach (GenericParameter generic_parameter in self.GenericParameters)
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
return reference;
}
public static bool Is<T>(this MethodReference method, string name)
{
return method.DeclaringType.Is<T>() && method.Name == name;
}
public static bool Is<T>(this TypeReference td)
{
return Is(td, typeof(T));
}
public static bool Is(this TypeReference td, Type t)
{
if (t.IsGenericType)
{
return td.GetElementType().FullName == t.FullName;
}
return td.FullName == t.FullName;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee1c15a06ab386e439ec5aa41e3496f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,65 @@
using FishNet.CodeGenerating.ILCore;
using MonoFN.Cecil;
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace FishNet.CodeGenerating.Helping.Extension
{
public static class ModuleDefinitionExtensions
{
/// <summary>
/// Gets a class within CodegenSession.Module.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
public static TypeDefinition GetClass(this ModuleDefinition moduleDef, string className)
{
return CodegenSession.Module.GetType(FishNetILPP.RUNTIME_ASSEMBLY_NAME, className);
}
public static TypeReference ImportReference<T>(this ModuleDefinition moduleDef)
{
return CodegenSession.ImportReference(typeof(T));
}
public static MethodReference ImportReference(this ModuleDefinition moduleDef, Expression<Action> expression)
{
return ImportReference(moduleDef, (LambdaExpression)expression);
}
public static MethodReference ImportReference<T>(this ModuleDefinition module, Expression<Action<T>> expression)
{
return ImportReference(module, (LambdaExpression)expression);
}
public static MethodReference ImportReference(this ModuleDefinition module, LambdaExpression expression)
{
if (expression.Body is MethodCallExpression outermostExpression)
{
MethodInfo methodInfo = outermostExpression.Method;
return module.ImportReference(methodInfo);
}
if (expression.Body is NewExpression newExpression)
{
ConstructorInfo methodInfo = newExpression.Constructor;
// constructor is null when creating an ArraySegment<object>
methodInfo = methodInfo ?? newExpression.Type.GetConstructors()[0];
return module.ImportReference(methodInfo);
}
if (expression.Body is MemberExpression memberExpression)
{
var property = memberExpression.Member as PropertyInfo;
return module.ImportReference(property.GetMethod);
}
throw new ArgumentException($"Invalid Expression {expression.Body.GetType()}");
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 42648785493390646898f5fa13e1dfd6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@
using MonoFN.Cecil;
using System;
namespace FishNet.CodeGenerating.Helping.Extension
{
internal static class ParameterDefinitionExtensions
{
/// <summary>
/// Returns if parameterDef is Type.
/// </summary>
/// <param name="parameterDef"></param>
/// <param name="type"></param>
/// <returns></returns>
public static bool Is(this ParameterDefinition parameterDef, Type type)
{
return parameterDef.ParameterType.FullName == type.FullName;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 31071055a2e388141b8f11e1ba4e147e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,468 @@
using FishNet.CodeGenerating.Extension;
using MonoFN.Cecil;
using MonoFN.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace FishNet.CodeGenerating.Helping.Extension
{
internal static class TypeDefinitionExtensionsOld
{
/// <summary>
/// Creates a GenericInstanceType and adds parameters.
/// </summary>
internal static GenericInstanceType CreateGenericInstanceType(this TypeDefinition type, Collection<GenericParameter> parameters)
{
GenericInstanceType git = new GenericInstanceType(type);
foreach (GenericParameter gp in parameters)
git.GenericArguments.Add(gp);
return git;
}
/// <summary>
/// Finds public fields in type and base type
/// </summary>
/// <param name="variable"></param>
/// <returns></returns>
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDef, bool ignoreStatic, bool ignoreNonSerialized, System.Type[] excludedBaseTypes = null, string[] excludedAssemblyPrefixes = null)
{
while (typeDef != null)
{
if (IsExcluded(typeDef, excludedBaseTypes, excludedAssemblyPrefixes))
break;
foreach (FieldDefinition fd in typeDef.Fields)
{
if (ignoreStatic && fd.IsStatic)
continue;
if (fd.IsPrivate)
continue;
if (ignoreNonSerialized && fd.IsNotSerialized)
continue;
if (CodegenSession.GeneralHelper.CodegenExclude(fd))
continue;
yield return fd;
}
try { typeDef = typeDef.BaseType?.CachedResolve(); }
catch { break; }
}
}
/// <summary>
/// Finds public properties on typeDef and all base types which have a public get/set accessor.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
public static IEnumerable<PropertyDefinition> FindAllPublicProperties(this TypeDefinition typeDef, bool excludeGenerics = true, System.Type[] excludedBaseTypes = null, string[] excludedAssemblyPrefixes = null)
{
while (typeDef != null)
{
if (IsExcluded(typeDef, excludedBaseTypes, excludedAssemblyPrefixes))
break;
foreach (PropertyDefinition pd in typeDef.Properties)
{
//Missing get or set method.
if (pd.GetMethod == null || pd.SetMethod == null)
continue;
//Get or set is private.
if (pd.GetMethod.IsPrivate || pd.SetMethod.IsPrivate)
continue;
if (excludeGenerics && pd.GetMethod.ReturnType.IsGenericParameter)
continue;
if (CodegenSession.GeneralHelper.CodegenExclude(pd))
continue;
yield return pd;
}
try { typeDef = typeDef.BaseType?.CachedResolve(); }
catch { break; }
}
}
/// <summary>
/// Returns if typeDef is excluded.
/// </summary>
private static bool IsExcluded(TypeDefinition typeDef, System.Type[] excludedBaseTypes = null, string[] excludedAssemblyPrefixes = null)
{
if (excludedBaseTypes != null)
{
foreach (System.Type t in excludedBaseTypes)
{
if (typeDef.FullName == t.FullName)
return true;
}
}
if (excludedAssemblyPrefixes != null)
{
foreach (string s in excludedAssemblyPrefixes)
{
int len = s.Length;
string tdAsmName = typeDef.Module.Assembly.FullName;
if (tdAsmName.Length >= len && tdAsmName.Substring(0, len).ToLower() == s.ToLower())
return true;
}
}
//Fall through, not excluded.
return false;
}
/// <summary>
/// Returns if typeDef is excluded.
/// </summary>
public static bool IsExcluded(this TypeDefinition typeDef, string excludedAssemblyPrefix)
{
int len = excludedAssemblyPrefix.Length;
string tdAsmName = typeDef.Module.Assembly.FullName;
if (tdAsmName.Length >= len && tdAsmName.Substring(0, len).ToLower() == excludedAssemblyPrefix.ToLower())
return true;
//Fall through, not excluded.
return false;
}
/// <summary>
/// Returns if typeDef or any of it's parents inherit from NetworkBehaviour.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
internal static bool InheritsNetworkBehaviour(this TypeDefinition typeDef)
{
string nbFullName = CodegenSession.NetworkBehaviourHelper.FullName;
TypeDefinition copyTd = typeDef;
while (copyTd != null)
{
if (copyTd.FullName == nbFullName)
return true;
copyTd = copyTd.GetNextBaseTypeDefinition();
}
//Fall through, network behaviour not found.
return false;
}
/// <summary>
/// Returns a nested TypeDefinition of name.
/// </summary>
internal static TypeDefinition GetNestedType(this TypeDefinition typeDef, string name)
{
foreach (TypeDefinition nestedTd in typeDef.NestedTypes)
{
if (nestedTd.Name == name)
return nestedTd;
}
return null;
}
/// <summary>
/// Returns if the BaseType for TypeDef exist and is not NetworkBehaviour,
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
internal static bool CanProcessBaseType(this TypeDefinition typeDef)
{
return (typeDef != null && typeDef.BaseType != null && typeDef.BaseType.FullName != CodegenSession.NetworkBehaviourHelper.FullName);
}
/// <summary>
/// Returns if the BaseType for TypeDef exist and is not NetworkBehaviour,
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
internal static TypeDefinition GetNextBaseClassToProcess(this TypeDefinition typeDef)
{
if (typeDef.BaseType != null && typeDef.BaseType.FullName != CodegenSession.NetworkBehaviourHelper.FullName)
return typeDef.BaseType.CachedResolve();
else
return null;
}
internal static TypeDefinition GetLastBaseClass(this TypeDefinition typeDef)
{
TypeDefinition copyTd = typeDef;
while (copyTd.BaseType != null)
copyTd = copyTd.BaseType.CachedResolve();
return copyTd;
}
/// <summary>
/// Searches for a type in current and inherited types.
/// </summary>
internal static TypeDefinition GetClassInInheritance(this TypeDefinition typeDef, string typeFullName)
{
TypeDefinition copyTd = typeDef;
do
{
if (copyTd.FullName == typeFullName)
return copyTd;
if (copyTd.BaseType != null)
copyTd = copyTd.BaseType.CachedResolve();
else
copyTd = null;
} while (copyTd != null);
//Not found.
return null;
}
/// <summary>
/// Searches for a type in current and inherited types.
/// </summary>
internal static TypeDefinition GetClassInInheritance(this TypeDefinition typeDef, TypeDefinition targetTypeDef)
{
if (typeDef == null)
return null;
TypeDefinition copyTd = typeDef;
do
{
if (copyTd == targetTypeDef)
return copyTd;
if (copyTd.BaseType != null)
copyTd = copyTd.BaseType.CachedResolve();
else
copyTd = null;
} while (copyTd != null);
//Not found.
return null;
}
/// <summary>
/// Returns if typeDef is static (abstract, sealed).
/// </summary>
internal static bool IsStatic(this TypeDefinition typeDef)
{
//Combining flags in a single check some reason doesn't work right with HasFlag.
return (typeDef.Attributes.HasFlag(TypeAttributes.Abstract) && typeDef.Attributes.HasFlag(TypeAttributes.Sealed));
}
/// <summary>
/// Gets an enum underlying type for typeDef.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
internal static TypeReference GetEnumUnderlyingTypeReference(this TypeDefinition typeDef)
{
foreach (FieldDefinition field in typeDef.Fields)
{
if (!field.IsStatic)
return field.FieldType;
}
throw new ArgumentException($"Invalid enum {typeDef.FullName}");
}
/// <summary>
/// Returns if typeDef is derived from type.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="typeDef"></param>
/// <returns></returns>
internal static bool InheritsFrom<T>(this TypeDefinition typeDef)
{
return InheritsFrom(typeDef, typeof(T));
}
/// <summary>
/// Returns if typeDef is derived from type.
/// </summary>
/// <param name="typeDef"></param>
/// <param name="type"></param>
/// <returns></returns>
internal static bool InheritsFrom(this TypeDefinition typeDef, Type type)
{
if (!typeDef.IsClass)
return false;
TypeDefinition copyTd = typeDef;
while (copyTd.BaseType != null)
{
if (copyTd.BaseType.IsType(type))
return true;
copyTd = copyTd.GetNextBaseTypeDefinition();
}
//Fall through.
return false;
}
/// <summary>
/// Adds a method to typeDef.
/// </summary>
/// <param name="typDef"></param>
/// <param name="methodName"></param>
/// <param name="attributes"></param>
/// <returns></returns>
internal static MethodDefinition AddMethod(this TypeDefinition typDef, string methodName, MethodAttributes attributes)
{
return AddMethod(typDef, methodName, attributes, typDef.Module.ImportReference(typeof(void)));
}
/// <summary>
/// Adds a method to typeDef.
/// </summary>
/// <param name="typeDef"></param>
/// <param name="methodName"></param>
/// <param name="attributes"></param>
/// <param name="typeReference"></param>
/// <returns></returns>
internal static MethodDefinition AddMethod(this TypeDefinition typeDef, string methodName, MethodAttributes attributes, TypeReference typeReference)
{
var method = new MethodDefinition(methodName, attributes, typeReference);
typeDef.Methods.Add(method);
return method;
}
/// <summary>
/// Finds the first method by a given name.
/// </summary>
/// <param name="typeDef"></param>
/// <param name="methodName"></param>
/// <returns></returns>
internal static MethodDefinition GetMethod(this TypeDefinition typeDef, string methodName)
{
return typeDef.Methods.FirstOrDefault(method => method.Name == methodName);
}
/// <summary>
/// Finds the first method by a given name.
/// </summary>
/// <param name="typeDef"></param>
/// <param name="methodName"></param>
/// <returns></returns>
internal static MethodDefinition GetMethod(this TypeDefinition typeDef, string methodName, Type[] types)
{
throw new NotImplementedException();
}
/// <summary>
/// Returns if a type is a subclass of another.
/// </summary>
/// <param name="typeDef"></param>
/// <param name="ClassTypeFullName"></param>
/// <returns></returns>
internal static bool IsSubclassOf(this TypeDefinition typeDef, string ClassTypeFullName)
{
if (!typeDef.IsClass) return false;
TypeReference baseTypeRef = typeDef.BaseType;
while (baseTypeRef != null)
{
if (baseTypeRef.FullName == ClassTypeFullName)
{
return true;
}
try
{
baseTypeRef = baseTypeRef.CachedResolve().BaseType;
}
catch
{
return false;
}
}
return false;
}
/// <summary>
/// Gets a field reference by name.
/// </summary>
/// <param name="typeDef"></param>
/// <param name="fieldName"></param>
/// <returns></returns>
public static FieldReference GetField(this TypeDefinition typeDef, string fieldName)
{
if (typeDef.HasFields)
{
for (int i = 0; i < typeDef.Fields.Count; i++)
{
if (typeDef.Fields[i].Name == fieldName)
{
return typeDef.Fields[i];
}
}
}
return null;
}
/// <summary>
/// Returns if the TypeDefinition implements TInterface.
/// </summary>
/// <typeparam name="TInterface"></typeparam>
/// <param name="typeDef"></param>
/// <returns></returns>
public static bool ImplementsInterface<TInterface>(this TypeDefinition typeDef)
{
for (int i = 0; i < typeDef.Interfaces.Count; i++)
{
if (typeDef.Interfaces[i].InterfaceType.Is<TInterface>())
return true;
}
return false;
}
/// <summary>
/// Returns if the TypeDefinition implements TInterface.
/// </summary>
/// <typeparam name="TInterface"></typeparam>
/// <param name="typeDef"></param>
/// <returns></returns>
public static bool ImplementsInterfaceRecursive<TInterface>(this TypeDefinition typeDef)
{
TypeDefinition climbTypeDef = typeDef;
while (climbTypeDef != null)
{
if (climbTypeDef.Interfaces.Any(i => i.InterfaceType.Is<TInterface>()))
return true;
try
{
if (climbTypeDef.BaseType != null)
climbTypeDef = climbTypeDef.BaseType.CachedResolve();
else
climbTypeDef = null;
}
//Could not resolve assembly; can happen for assemblies being checked outside FishNet/csharp.
catch (AssemblyResolutionException)
{
break;
}
}
return false;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 645e49fe7eeff3a4e9eb65d77fc6e2ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,137 @@
using MonoFN.Cecil;
using MonoFN.Cecil.Rocks;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.CodeGenerating.Helping.Extension
{
internal static class TypeReferenceExtensionsOld
{
/// <summary>
/// Gets a Resolve favoring cached results first.
/// </summary>
internal static TypeDefinition CachedResolve(this TypeReference typeRef)
{
return CodegenSession.GeneralHelper.GetTypeReferenceResolve(typeRef);
}
/// <summary>
/// Returns if typeRef is a class or struct.
/// </summary>
internal static bool IsClassOrStruct(this TypeReference typeRef)
{
TypeDefinition typeDef = typeRef.CachedResolve();
return (!typeDef.IsPrimitive && (typeDef.IsClass || typeDef.IsValueType));
}
/// <summary>
/// Returns all properties on typeRef and all base types which have a public get/set accessor.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static IEnumerable<PropertyDefinition> FindAllPublicProperties(this TypeReference typeRef, bool excludeGenerics = true, System.Type[] excludedBaseTypes = null, string[] excludedAssemblyPrefixes = null)
{
return typeRef.CachedResolve().FindAllPublicProperties(excludeGenerics, excludedBaseTypes, excludedAssemblyPrefixes);
}
/// <summary>
/// Gets all public fields in typeRef and base type.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference typeRef, bool ignoreStatic, bool ignoreNonSerialized, System.Type[] excludedBaseTypes = null, string[] excludedAssemblyPrefixes = null)
{
return typeRef.Resolve().FindAllPublicFields(ignoreStatic, ignoreNonSerialized, excludedBaseTypes, excludedAssemblyPrefixes);
}
/// <summary>
/// Returns if a typeRef is type.
/// </summary>
/// <param name="typeRef"></param>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsType(this TypeReference typeRef, Type type)
{
if (type.IsGenericType)
return typeRef.GetElementType().FullName == type.FullName;
else
return typeRef.FullName == type.FullName;
}
/// <summary>
/// Returns if typeRef is a multidimensional array.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static bool IsMultidimensionalArray(this TypeReference typeRef)
{
return typeRef is ArrayType arrayType && arrayType.Rank > 1;
}
/// <summary>
/// Returns if typeRef can be resolved.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
public static bool CanBeResolved(this TypeReference typeRef)
{
while (typeRef != null)
{
if (typeRef.Scope.Name == "Windows")
{
return false;
}
if (typeRef.Scope.Name == "mscorlib")
{
TypeDefinition resolved = typeRef.CachedResolve();
return resolved != null;
}
try
{
typeRef = typeRef.CachedResolve().BaseType;
}
catch
{
return false;
}
}
return true;
}
/// <summary>
/// Creates a generic type out of another type, if needed.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static TypeReference ConvertToGenericIfNeeded(this TypeDefinition type)
{
if (type.HasGenericParameters)
{
// get all the generic parameters and make a generic instance out of it
var genericTypes = new TypeReference[type.GenericParameters.Count];
for (int i = 0; i < type.GenericParameters.Count; i++)
{
genericTypes[i] = type.GenericParameters[i].GetElementType();
}
return type.MakeGenericInstanceType(genericTypes);
}
else
{
return type;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2344f5ab0fda07b498c03fbe0e082c14
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,941 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.CodeGenerating.ILCore;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Object;
using FishNet.Object.Helping;
using FishNet.Serializing;
using FishNet.Serializing.Helping;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System;
using System.Collections.Generic;
using UnityEngine;
using SR = System.Reflection;
namespace FishNet.CodeGenerating.Helping
{
internal class GeneralHelper
{
#region Reflection references.
internal string CodegenExcludeAttribute_FullName;
internal MethodReference Queue_Enqueue_MethodRef;
internal MethodReference Queue_get_Count_MethodRef;
internal MethodReference Queue_Dequeue_MethodRef;
internal MethodReference Queue_Clear_MethodRef;
internal TypeReference List_TypeRef;
internal MethodReference List_Clear_MethodRef;
internal MethodReference List_get_Item_MethodRef;
internal MethodReference List_get_Count_MethodRef;
internal MethodReference List_Add_MethodRef;
internal MethodReference List_RemoveRange_MethodRef;
private MethodReference InstanceFinder_NetworkManager_MethodRef;
private MethodReference NetworkBehaviour_CanLog_MethodRef;
private MethodReference NetworkManager_CanLog_MethodRef;
private MethodReference NetworkBehaviour_NetworkManager_MethodRef;
private MethodReference NetworkManager_LogCommon_MethodRef;
private MethodReference NetworkManager_LogWarning_MethodRef;
private MethodReference NetworkManager_LogError_MethodRef;
internal MethodReference Debug_LogCommon_MethodRef;
internal MethodReference Debug_LogWarning_MethodRef;
internal MethodReference Debug_LogError_MethodRef;
internal MethodReference Comparers_EqualityCompare_MethodRef;
internal MethodReference Comparers_IsDefault_MethodRef;
internal MethodReference IsServer_MethodRef;
internal MethodReference IsClient_MethodRef;
internal MethodReference NetworkObject_Deinitializing_MethodRef;
internal MethodReference Application_IsPlaying_MethodRef;
private Dictionary<Type, TypeReference> _importedTypeReferences = new Dictionary<Type, TypeReference>();
private Dictionary<FieldDefinition, FieldReference> _importedFieldReferences = new Dictionary<FieldDefinition, FieldReference>();
private Dictionary<MethodReference, MethodDefinition> _methodReferenceResolves = new Dictionary<MethodReference, MethodDefinition>();
private Dictionary<TypeReference, TypeDefinition> _typeReferenceResolves = new Dictionary<TypeReference, TypeDefinition>();
private Dictionary<FieldReference, FieldDefinition> _fieldReferenceResolves = new Dictionary<FieldReference, FieldDefinition>();
private string NonSerialized_Attribute_FullName;
private string Single_FullName;
#endregion
#region Const.
public const string UNITYENGINE_ASSEMBLY_PREFIX = "UnityEngine.";
#endregion
internal bool ImportReferences()
{
Type tmpType;
SR.MethodInfo tmpMi;
SR.PropertyInfo tmpPi;
NonSerialized_Attribute_FullName = typeof(NonSerializedAttribute).FullName;
Single_FullName = typeof(float).FullName;
CodegenExcludeAttribute_FullName = typeof(CodegenExcludeAttribute).FullName;
tmpType = typeof(Queue<>);
CodegenSession.ImportReference(tmpType);
tmpMi = tmpType.GetMethod("get_Count");
Queue_get_Count_MethodRef = CodegenSession.ImportReference(tmpMi);
foreach (SR.MethodInfo mi in tmpType.GetMethods())
{
if (mi.Name == nameof(Queue<int>.Enqueue))
Queue_Enqueue_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(Queue<int>.Dequeue))
Queue_Dequeue_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(Queue<int>.Clear))
Queue_Clear_MethodRef = CodegenSession.ImportReference(mi);
}
Type comparers = typeof(Comparers);
foreach (SR.MethodInfo mi in comparers.GetMethods())
{
if (mi.Name == nameof(Comparers.EqualityCompare))
Comparers_EqualityCompare_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(Comparers.IsDefault))
Comparers_IsDefault_MethodRef = CodegenSession.ImportReference(mi);
}
//Misc.
tmpType = typeof(UnityEngine.Application);
tmpPi = tmpType.GetProperty(nameof(UnityEngine.Application.isPlaying));
if (tmpPi != null)
Application_IsPlaying_MethodRef = CodegenSession.ImportReference(tmpPi.GetMethod);
//Networkbehaviour.
Type networkBehaviourType = typeof(NetworkBehaviour);
foreach (SR.MethodInfo methodInfo in networkBehaviourType.GetMethods())
{
if (methodInfo.Name == nameof(NetworkBehaviour.CanLog))
NetworkBehaviour_CanLog_MethodRef = CodegenSession.ImportReference(methodInfo);
}
foreach (SR.PropertyInfo propertyInfo in networkBehaviourType.GetProperties())
{
if (propertyInfo.Name == nameof(NetworkBehaviour.NetworkManager))
NetworkBehaviour_NetworkManager_MethodRef = CodegenSession.ImportReference(propertyInfo.GetMethod);
}
//Instancefinder.
Type instanceFinderType = typeof(InstanceFinder);
SR.PropertyInfo getNetworkManagerPropertyInfo = instanceFinderType.GetProperty(nameof(InstanceFinder.NetworkManager));
InstanceFinder_NetworkManager_MethodRef = CodegenSession.ImportReference(getNetworkManagerPropertyInfo.GetMethod);
//NetworkManager debug logs.
Type networkManagerType = typeof(NetworkManager);
foreach (SR.MethodInfo methodInfo in networkManagerType.GetMethods())
{
if (methodInfo.Name == nameof(NetworkManager.Log))
NetworkManager_LogCommon_MethodRef = CodegenSession.ImportReference(methodInfo);
else if (methodInfo.Name == nameof(NetworkManager.LogWarning))
NetworkManager_LogWarning_MethodRef = CodegenSession.ImportReference(methodInfo);
else if (methodInfo.Name == nameof(NetworkManager.LogError))
NetworkManager_LogError_MethodRef = CodegenSession.ImportReference(methodInfo);
else if (methodInfo.Name == nameof(NetworkManager.CanLog))
NetworkManager_CanLog_MethodRef = CodegenSession.ImportReference(methodInfo);
}
//Lists.
tmpType = typeof(List<>);
List_TypeRef = CodegenSession.ImportReference(tmpType);
SR.MethodInfo lstMi;
lstMi = tmpType.GetMethod("Add");
List_Add_MethodRef = CodegenSession.ImportReference(lstMi);
lstMi = tmpType.GetMethod("RemoveRange");
List_RemoveRange_MethodRef = CodegenSession.ImportReference(lstMi);
lstMi = tmpType.GetMethod("get_Count");
List_get_Count_MethodRef = CodegenSession.ImportReference(lstMi);
lstMi = tmpType.GetMethod("get_Item");
List_get_Item_MethodRef = CodegenSession.ImportReference(lstMi);
lstMi = tmpType.GetMethod("Clear");
List_Clear_MethodRef = CodegenSession.ImportReference(lstMi);
//Unity debug logs.
Type debugType = typeof(UnityEngine.Debug);
foreach (SR.MethodInfo methodInfo in debugType.GetMethods())
{
if (methodInfo.Name == nameof(Debug.LogWarning) && methodInfo.GetParameters().Length == 1)
Debug_LogWarning_MethodRef = CodegenSession.ImportReference(methodInfo);
else if (methodInfo.Name == nameof(Debug.LogError) && methodInfo.GetParameters().Length == 1)
Debug_LogError_MethodRef = CodegenSession.ImportReference(methodInfo);
else if (methodInfo.Name == nameof(Debug.Log) && methodInfo.GetParameters().Length == 1)
Debug_LogCommon_MethodRef = CodegenSession.ImportReference(methodInfo);
}
Type codegenHelper = typeof(CodegenHelper);
foreach (SR.MethodInfo methodInfo in codegenHelper.GetMethods())
{
if (methodInfo.Name == nameof(CodegenHelper.NetworkObject_Deinitializing))
NetworkObject_Deinitializing_MethodRef = CodegenSession.ImportReference(methodInfo);
else if (methodInfo.Name == nameof(CodegenHelper.IsClient))
IsClient_MethodRef = CodegenSession.ImportReference(methodInfo);
else if (methodInfo.Name == nameof(CodegenHelper.IsServer))
IsServer_MethodRef = CodegenSession.ImportReference(methodInfo);
}
return true;
}
#region Resolves.
/// <summary>
/// Adds a typeRef to TypeReferenceResolves.
/// </summary>
internal void AddTypeReferenceResolve(TypeReference typeRef, TypeDefinition typeDef)
{
_typeReferenceResolves[typeRef] = typeDef;
}
/// <summary>
/// Gets a TypeDefinition for typeRef.
/// </summary>
internal TypeDefinition GetTypeReferenceResolve(TypeReference typeRef)
{
TypeDefinition result;
if (_typeReferenceResolves.TryGetValue(typeRef, out result))
{
return result;
}
else
{
result = typeRef.Resolve();
AddTypeReferenceResolve(typeRef, result);
}
return result;
}
/// <summary>
/// Adds a methodRef to MethodReferenceResolves.
/// </summary>
internal void AddMethodReferenceResolve(MethodReference methodRef, MethodDefinition methodDef)
{
_methodReferenceResolves[methodRef] = methodDef;
}
/// <summary>
/// Gets a TypeDefinition for typeRef.
/// </summary>
internal MethodDefinition GetMethodReferenceResolve(MethodReference methodRef)
{
MethodDefinition result;
if (_methodReferenceResolves.TryGetValue(methodRef, out result))
{
return result;
}
else
{
result = methodRef.Resolve();
AddMethodReferenceResolve(methodRef, result);
}
return result;
}
/// <summary>
/// Adds a fieldRef to FieldReferenceResolves.
/// </summary>
internal void AddFieldReferenceResolve(FieldReference fieldRef, FieldDefinition fieldDef)
{
_fieldReferenceResolves[fieldRef] = fieldDef;
}
/// <summary>
/// Gets a FieldDefinition for fieldRef.
/// </summary>
internal FieldDefinition GetFieldReferenceResolve(FieldReference fieldRef)
{
FieldDefinition result;
if (_fieldReferenceResolves.TryGetValue(fieldRef, out result))
{
return result;
}
else
{
result = fieldRef.Resolve();
AddFieldReferenceResolve(fieldRef, result);
}
return result;
}
#endregion
/// <summary>
/// Returns if typeDef should be ignored.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
internal bool IgnoreTypeDefinition(TypeDefinition typeDef)
{
//If FishNet assembly.
if (typeDef.Module.Assembly.Name.Name == FishNetILPP.RUNTIME_ASSEMBLY_NAME)
{
foreach (CustomAttribute item in typeDef.CustomAttributes)
{
if (item.AttributeType.FullName == typeof(CodegenIncludeInternalAttribute).FullName)
{
if (FishNetILPP.CODEGEN_THIS_NAMESPACE.Length > 0)
return !typeDef.FullName.Contains(FishNetILPP.CODEGEN_THIS_NAMESPACE);
else
return false;
}
}
return true;
}
//Not FishNet assembly.
else
{
if (FishNetILPP.CODEGEN_THIS_NAMESPACE.Length > 0)
return true;
foreach (CustomAttribute item in typeDef.CustomAttributes)
{
if (item.AttributeType.FullName == typeof(CodegenExcludeAttribute).FullName)
return true;
}
return false;
}
}
/// <summary>
/// Returns if type uses CodegenExcludeAttribute.
/// </summary>
internal bool CodegenExclude(SR.MethodInfo methodInfo)
{
foreach (SR.CustomAttributeData item in methodInfo.CustomAttributes)
{
if (item.AttributeType == typeof(CodegenExcludeAttribute))
return true;
}
return false;
}
/// <summary>
/// Returns if type uses CodegenExcludeAttribute.
/// </summary>
internal bool CodegenExclude(MethodDefinition methodDef)
{
foreach (CustomAttribute item in methodDef.CustomAttributes)
{
if (item.AttributeType.FullName == CodegenExcludeAttribute_FullName)
return true;
}
return false;
}
/// <summary>
/// Returns if type uses CodegenExcludeAttribute.
/// </summary>
internal bool CodegenExclude(FieldDefinition fieldDef)
{
foreach (CustomAttribute item in fieldDef.CustomAttributes)
{
if (item.AttributeType.FullName == CodegenExcludeAttribute_FullName)
return true;
}
return false;
}
/// <summary>
/// Returns if type uses CodegenExcludeAttribute.
/// </summary>
internal bool CodegenExclude(PropertyDefinition propDef)
{
foreach (CustomAttribute item in propDef.CustomAttributes)
{
if (item.AttributeType.FullName == CodegenExcludeAttribute_FullName)
return true;
}
return false;
}
/// <summary>
/// Calls copiedMd with the assumption md shares the same parameters.
/// </summary>
internal void CallCopiedMethod(MethodDefinition md, MethodDefinition copiedMd)
{
ILProcessor processor = md.Body.GetILProcessor();
processor.Emit(OpCodes.Ldarg_0);
foreach (var item in copiedMd.Parameters)
processor.Emit(OpCodes.Ldarg, item);
processor.Emit(OpCodes.Call, copiedMd);
}
/// <summary>
/// Copies one method to another while transferring diagnostic paths.
/// </summary>
internal MethodDefinition CopyMethod(MethodDefinition originalMd, string toName, out bool alreadyCreated)
{
TypeDefinition typeDef = originalMd.DeclaringType;
MethodDefinition copyMd = typeDef.GetMethod(toName);
//Already made.
if (copyMd != null)
{
alreadyCreated = true;
return copyMd;
}
else
{
alreadyCreated = false;
}
//Create the method body.
copyMd = new MethodDefinition(
toName, originalMd.Attributes, originalMd.ReturnType);
typeDef.Methods.Add(copyMd);
copyMd.Body.InitLocals = true;
//Copy parameter expecations into new method.
foreach (ParameterDefinition pd in originalMd.Parameters)
copyMd.Parameters.Add(pd);
//Swap bodies.
(copyMd.Body, originalMd.Body) = (originalMd.Body, copyMd.Body);
//Move over all the debugging information
foreach (SequencePoint sequencePoint in originalMd.DebugInformation.SequencePoints)
copyMd.DebugInformation.SequencePoints.Add(sequencePoint);
originalMd.DebugInformation.SequencePoints.Clear();
foreach (CustomDebugInformation customInfo in originalMd.CustomDebugInformations)
copyMd.CustomDebugInformations.Add(customInfo);
originalMd.CustomDebugInformations.Clear();
//Swap debuginformation scope.
(originalMd.DebugInformation.Scope, copyMd.DebugInformation.Scope) = (copyMd.DebugInformation.Scope, originalMd.DebugInformation.Scope);
return copyMd;
}
/// <summary>
/// Creates the RuntimeInitializeOnLoadMethod attribute for a method.
/// </summary>
internal void CreateRuntimeInitializeOnLoadMethodAttribute(MethodDefinition methodDef, string loadType = "")
{
TypeReference attTypeRef = GetTypeReference(typeof(RuntimeInitializeOnLoadMethodAttribute));
foreach (CustomAttribute item in methodDef.CustomAttributes)
{
//Already exist.
if (item.AttributeType.FullName == attTypeRef.FullName)
return;
}
int parameterRequirement = (loadType.Length == 0) ? 0 : 1;
MethodDefinition constructorMethodDef = attTypeRef.GetConstructor(parameterRequirement);
MethodReference constructorMethodRef = CodegenSession.ImportReference(constructorMethodDef);
CustomAttribute ca = new CustomAttribute(constructorMethodRef);
/* If load type isn't null then it
* has to be passed in as the first argument. */
if (loadType.Length > 0)
{
Type t = typeof(RuntimeInitializeLoadType);
foreach (UnityEngine.RuntimeInitializeLoadType value in t.GetEnumValues())
{
if (loadType == value.ToString())
{
TypeReference tr = CodegenSession.ImportReference(t);
CustomAttributeArgument arg = new CustomAttributeArgument(tr, value);
ca.ConstructorArguments.Add(arg);
}
}
}
methodDef.CustomAttributes.Add(ca);
}
/// <summary>
/// Gets the default AutoPackType to use for typeRef.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal AutoPackType GetDefaultAutoPackType(TypeReference typeRef)
{
//Singles are defauled to unpacked.
if (typeRef.FullName == Single_FullName)
return AutoPackType.Unpacked;
else
return AutoPackType.Packed;
}
/// <summary>
/// Gets the InitializeOnce method in typeDef or creates the method should it not exist.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
internal MethodDefinition GetOrCreateMethod(TypeDefinition typeDef, out bool created, MethodAttributes methodAttr, string methodName, TypeReference returnType)
{
MethodDefinition result = typeDef.GetMethod(methodName);
if (result == null)
{
created = true;
result = new MethodDefinition(methodName, methodAttr, returnType);
typeDef.Methods.Add(result);
}
else
{
created = false;
}
return result;
}
/// <summary>
/// Gets a class within moduleDef or creates and returns the class if it does not already exist.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
internal TypeDefinition GetOrCreateClass(out bool created, TypeAttributes typeAttr, string className, TypeReference baseTypeRef)
{
TypeDefinition type = CodegenSession.Module.GetClass(className);
if (type != null)
{
created = false;
return type;
}
else
{
created = true;
type = new TypeDefinition(FishNetILPP.RUNTIME_ASSEMBLY_NAME, className,
typeAttr, CodegenSession.ImportReference(typeof(object)));
//Add base class if specified.
if (baseTypeRef != null)
type.BaseType = CodegenSession.ImportReference(baseTypeRef);
CodegenSession.Module.Types.Add(type);
return type;
}
}
#region HasNonSerializableAttribute
/// <summary>
/// Returns if fieldDef has a NonSerialized attribute.
/// </summary>
/// <param name="fieldDef"></param>
/// <returns></returns>
internal bool HasNonSerializableAttribute(FieldDefinition fieldDef)
{
foreach (CustomAttribute customAttribute in fieldDef.CustomAttributes)
{
if (customAttribute.AttributeType.FullName == NonSerialized_Attribute_FullName)
return true;
}
//Fall through, no matches.
return false;
}
/// <summary>
/// Returns if typeDef has a NonSerialized attribute.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
internal bool HasNonSerializableAttribute(TypeDefinition typeDef)
{
foreach (CustomAttribute customAttribute in typeDef.CustomAttributes)
{
if (customAttribute.AttributeType.FullName == NonSerialized_Attribute_FullName)
return true;
}
//Fall through, no matches.
return false;
}
#endregion
/// <summary>
/// Gets a TypeReference for a type.
/// </summary>
/// <param name="type"></param>
internal TypeReference GetTypeReference(Type type)
{
TypeReference result;
if (!_importedTypeReferences.TryGetValue(type, out result))
{
result = CodegenSession.ImportReference(type);
_importedTypeReferences.Add(type, result);
}
return result;
}
/// <summary>
/// Gets a FieldReference for a type.
/// </summary>
/// <param name="type"></param>
internal FieldReference GetFieldReference(FieldDefinition fieldDef)
{
FieldReference result;
if (!_importedFieldReferences.TryGetValue(fieldDef, out result))
{
result = CodegenSession.ImportReference(fieldDef);
_importedFieldReferences.Add(fieldDef, result);
}
return result;
}
/// <summary>
/// Gets the current constructor for typeDef, or makes a new one if constructor doesn't exist.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
internal MethodDefinition GetOrCreateConstructor(TypeDefinition typeDef, out bool created, bool makeStatic)
{
// find constructor
MethodDefinition constructorMethodDef = typeDef.GetMethod(".cctor");
if (constructorMethodDef == null)
constructorMethodDef = typeDef.GetMethod(".ctor");
//Constructor already exist.
if (constructorMethodDef != null)
{
if (!makeStatic)
constructorMethodDef.Attributes &= ~MethodAttributes.Static;
created = false;
}
//Static constructor does not exist yet.
else
{
created = true;
MethodAttributes methodAttr = (MonoFN.Cecil.MethodAttributes.HideBySig |
MonoFN.Cecil.MethodAttributes.SpecialName |
MonoFN.Cecil.MethodAttributes.RTSpecialName);
if (makeStatic)
methodAttr |= MonoFN.Cecil.MethodAttributes.Static;
//Create a constructor.
constructorMethodDef = new MethodDefinition(".ctor", methodAttr,
typeDef.Module.TypeSystem.Void
);
typeDef.Methods.Add(constructorMethodDef);
//Add ret.
ILProcessor processor = constructorMethodDef.Body.GetILProcessor();
processor.Emit(OpCodes.Ret);
}
return constructorMethodDef;
}
/// <summary>
/// Creates a return of boolean type.
/// </summary>
/// <param name="processor"></param>
/// <param name="result"></param>
internal void CreateRetBoolean(ILProcessor processor, bool result)
{
OpCode code = (result) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0;
processor.Emit(code);
processor.Emit(OpCodes.Ret);
}
#region Debug logging.
/// <summary>
/// Creates a debug print if NetworkManager.CanLog is true.
/// </summary>
/// <param name="processor"></param>
/// <param name="loggingType"></param>
/// <param name="useStatic">True to use InstanceFinder, false to use base.</param>
/// <returns></returns>
internal List<Instruction> CreateDebugWithCanLogInstructions(ILProcessor processor, string message, LoggingType loggingType, bool useStatic, bool useNetworkManagerLog)
{
List<Instruction> instructions = new List<Instruction>();
if (loggingType == LoggingType.Off)
return instructions;
List<Instruction> debugPrint = CreateDebugInstructions(processor, message, loggingType, useNetworkManagerLog);
//Couldn't make debug print.
if (debugPrint.Count == 0)
return instructions;
VariableDefinition networkManagerVd = CreateVariable(processor.Body.Method, typeof(NetworkManager));
//Using InstanceFinder(static).
if (useStatic)
{
//Store instancefinder to nm variable.
instructions.Add(processor.Create(OpCodes.Call, InstanceFinder_NetworkManager_MethodRef));
instructions.Add(processor.Create(OpCodes.Stloc, networkManagerVd));
}
//Using networkBehaviour.
else
{
//Store nm reference.
instructions.Add(processor.Create(OpCodes.Ldarg_0));
instructions.Add(processor.Create(OpCodes.Call, NetworkBehaviour_NetworkManager_MethodRef));
instructions.Add(processor.Create(OpCodes.Stloc, networkManagerVd));
//If was set to null then try to log with instancefinder.
Instruction skipStaticSetInst = processor.Create(OpCodes.Nop);
//if (nmVd == null) nmVd = InstanceFinder.NetworkManager.
instructions.Add(processor.Create(OpCodes.Ldloc, networkManagerVd));
instructions.Add(processor.Create(OpCodes.Brtrue_S, skipStaticSetInst));
//Store instancefinder to nm variable.
instructions.Add(processor.Create(OpCodes.Call, InstanceFinder_NetworkManager_MethodRef));
instructions.Add(processor.Create(OpCodes.Stloc, networkManagerVd));
instructions.Add(skipStaticSetInst);
}
Instruction skipDebugInst = processor.Create(OpCodes.Nop);
//null check nm reference. If null then skip logging.
instructions.Add(processor.Create(OpCodes.Ldloc, networkManagerVd));
instructions.Add(processor.Create(OpCodes.Brfalse_S, skipDebugInst));
//Only need to call CanLog if not using networkmanager logging.
if (!useNetworkManagerLog)
{
//Call canlog.
instructions.Add(processor.Create(OpCodes.Ldarg_0));
instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)loggingType));
instructions.Add(processor.Create(OpCodes.Call, NetworkBehaviour_CanLog_MethodRef));
instructions.Add(processor.Create(OpCodes.Brfalse_S, skipDebugInst));
}
instructions.Add(processor.Create(OpCodes.Ldloc, networkManagerVd));
instructions.AddRange(debugPrint);
instructions.Add(skipDebugInst);
return instructions;
}
/// <summary>
/// Creates a debug print if NetworkManager.CanLog is true.
/// </summary>
/// <param name="processor"></param>
/// <param name="loggingType"></param>
/// <param name="useStatic">True to use InstanceFinder, false to use base.</param>
/// <returns></returns>
internal void CreateDebugWithCanLog(ILProcessor processor, string message, LoggingType loggingType, bool useStatic, bool useNetworkManagerLog)
{
List<Instruction> instructions = CreateDebugWithCanLogInstructions(processor, message, loggingType, useStatic, useNetworkManagerLog);
if (instructions.Count == 0)
return;
processor.Add(instructions);
}
/// <summary>
/// Creates a debug and returns instructions.
/// </summary>
/// <param name="processor"></param>
private List<Instruction> CreateDebugInstructions(ILProcessor processor, string message, LoggingType loggingType, bool useNetworkManagerLog)
{
List<Instruction> instructions = new List<Instruction>();
if (loggingType == LoggingType.Off)
{
CodegenSession.LogError($"CreateDebug called with LoggingType.Off.");
return instructions;
}
instructions.Add(processor.Create(OpCodes.Ldstr, message));
MethodReference methodRef;
if (loggingType == LoggingType.Common)
methodRef = (useNetworkManagerLog) ? NetworkManager_LogCommon_MethodRef : Debug_LogCommon_MethodRef;
else if (loggingType == LoggingType.Warning)
methodRef = (useNetworkManagerLog) ? NetworkManager_LogWarning_MethodRef : Debug_LogWarning_MethodRef;
else
methodRef = (useNetworkManagerLog) ? NetworkManager_LogError_MethodRef : Debug_LogError_MethodRef;
instructions.Add(processor.Create(OpCodes.Call, methodRef));
return instructions;
}
#endregion
#region CreateVariable / CreateParameter.
/// <summary>
/// Creates a parameter within methodDef and returns it's ParameterDefinition.
/// </summary>
/// <param name="methodDef"></param>
/// <param name="parameterTypeRef"></param>
/// <returns></returns>
internal ParameterDefinition CreateParameter(MethodDefinition methodDef, TypeDefinition parameterTypeDef, string name = "", ParameterAttributes attributes = ParameterAttributes.None, int index = -1)
{
TypeReference typeRef = methodDef.Module.ImportReference(parameterTypeDef);
return CreateParameter(methodDef, typeRef, name, attributes, index);
}
/// <summary>
/// Creates a parameter within methodDef and returns it's ParameterDefinition.
/// </summary>
/// <param name="methodDef"></param>
/// <param name="parameterTypeRef"></param>
/// <returns></returns>
internal ParameterDefinition CreateParameter(MethodDefinition methodDef, TypeReference parameterTypeRef, string name = "", ParameterAttributes attributes = ParameterAttributes.None, int index = -1)
{
ParameterDefinition parameterDef = new ParameterDefinition(name, attributes, parameterTypeRef);
if (index == -1)
methodDef.Parameters.Add(parameterDef);
else
methodDef.Parameters.Insert(index, parameterDef);
return parameterDef;
}
/// <summary>
/// Creates a parameter within methodDef and returns it's ParameterDefinition.
/// </summary>
/// <param name="methodDef"></param>
/// <param name="parameterTypeRef"></param>
/// <returns></returns>
internal ParameterDefinition CreateParameter(MethodDefinition methodDef, Type parameterType, string name = "", ParameterAttributes attributes = ParameterAttributes.None, int index = -1)
{
return CreateParameter(methodDef, GetTypeReference(parameterType), name, attributes, index);
}
/// <summary>
/// Creates a variable type within the body and returns it's VariableDef.
/// </summary>
/// <param name="methodDef"></param>
/// <param name="variableTypeRef"></param>
/// <returns></returns>
internal VariableDefinition CreateVariable(MethodDefinition methodDef, TypeReference variableTypeRef)
{
VariableDefinition variableDef = new VariableDefinition(variableTypeRef);
methodDef.Body.Variables.Add(variableDef);
return variableDef;
}
/// Creates a variable type within the body and returns it's VariableDef.
/// </summary>
/// <param name="processor"></param>
/// <param name="methodDef"></param>
/// <param name="variableTypeRef"></param>
/// <returns></returns>
internal VariableDefinition CreateVariable(MethodDefinition methodDef, Type variableType)
{
return CreateVariable(methodDef, GetTypeReference(variableType));
}
#endregion
#region SetVariableDef.
/// <summary>
/// Initializes variableDef as a new object or collection of typeDef.
/// </summary>
/// <param name="processor"></param>
/// <param name="variableDef"></param>
/// <param name="typeDef"></param>
internal void SetVariableDefinitionFromObject(ILProcessor processor, VariableDefinition variableDef, TypeDefinition typeDef)
{
TypeReference type = variableDef.VariableType;
if (type.IsValueType)
{
// structs are created with Initobj
processor.Emit(OpCodes.Ldloca, variableDef);
processor.Emit(OpCodes.Initobj, type);
}
else if (typeDef.InheritsFrom<UnityEngine.ScriptableObject>())
{
MethodReference soCreateInstanceMr = processor.Body.Method.Module.ImportReference(() => UnityEngine.ScriptableObject.CreateInstance<UnityEngine.ScriptableObject>());
GenericInstanceMethod genericInstanceMethod = soCreateInstanceMr.GetElementMethod().MakeGenericMethod(new TypeReference[] { type });
processor.Emit(OpCodes.Call, genericInstanceMethod);
processor.Emit(OpCodes.Stloc, variableDef);
}
else
{
MethodDefinition constructorMethodDef = type.GetConstructor();
if (constructorMethodDef == null)
{
CodegenSession.LogError($"{type.Name} can't be deserialized because a default constructor could not be found. Create a default constructor or a custom serializer/deserializer.");
return;
}
MethodReference constructorMethodRef = processor.Body.Method.Module.ImportReference(constructorMethodDef);
processor.Emit(OpCodes.Newobj, constructorMethodRef);
processor.Emit(OpCodes.Stloc, variableDef);
}
}
/// <summary>
/// Assigns value to a VariableDef.
/// </summary>
/// <param name="processor"></param>
/// <param name="variableDef"></param>
/// <param name="value"></param>
internal void SetVariableDefinitionFromInt(ILProcessor processor, VariableDefinition variableDef, int value)
{
processor.Emit(OpCodes.Ldc_I4, value);
processor.Emit(OpCodes.Stloc, variableDef);
}
/// <summary>
/// Assigns value to a VariableDef.
/// </summary>
/// <param name="processor"></param>
/// <param name="variableDef"></param>
/// <param name="value"></param>
internal void SetVariableDefinitionFromParameter(ILProcessor processor, VariableDefinition variableDef, ParameterDefinition value)
{
processor.Emit(OpCodes.Ldarg, value);
processor.Emit(OpCodes.Stloc, variableDef);
}
#endregion.
/// <summary>
/// Returns if an instruction is a call to a method.
/// </summary>
/// <param name="instruction"></param>
/// <param name="calledMethod"></param>
/// <returns></returns>
internal bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
{
if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodDefinition method)
{
calledMethod = method;
return true;
}
else
{
calledMethod = null;
return false;
}
}
/// <summary>
/// Returns if a serializer and deserializer exist for typeRef.
/// </summary>
/// <param name="typeRef"></param>
/// <param name="create">True to create if missing.</param>
/// <returns></returns>
internal bool HasSerializerAndDeserializer(TypeReference typeRef, bool create)
{
//Make sure it's imported into current module.
typeRef = CodegenSession.ImportReference(typeRef);
//Can be serialized/deserialized.
bool hasWriter = CodegenSession.WriterHelper.HasSerializer(typeRef, create);
bool hasReader = CodegenSession.ReaderHelper.HasDeserializer(typeRef, create);
return (hasWriter && hasReader);
}
/// <summary>
/// Creates a return of default value for methodDef.
/// </summary>
/// <returns></returns>
public List<Instruction> CreateRetDefault(MethodDefinition methodDef, ModuleDefinition importReturnModule = null)
{
ILProcessor processor = methodDef.Body.GetILProcessor();
List<Instruction> instructions = new List<Instruction>();
//If requires a value return.
if (methodDef.ReturnType != methodDef.Module.TypeSystem.Void)
{
//Import type first.
methodDef.Module.ImportReference(methodDef.ReturnType);
if (importReturnModule != null)
importReturnModule.ImportReference(methodDef.ReturnType);
VariableDefinition vd = CodegenSession.GeneralHelper.CreateVariable(methodDef, methodDef.ReturnType);
instructions.Add(processor.Create(OpCodes.Ldloca_S, vd));
instructions.Add(processor.Create(OpCodes.Initobj, vd.VariableType));
instructions.Add(processor.Create(OpCodes.Ldloc, vd));
}
instructions.Add(processor.Create(OpCodes.Ret));
return instructions;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6139ff104f3c24442b26dbc4e40d5ce5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,143 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Serializing;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using MonoFN.Cecil.Rocks;
using System;
using System.Collections.Generic;
namespace FishNet.CodeGenerating.Helping
{
internal class GenericReaderHelper
{
#region Reflection references.
private TypeReference _genericReaderTypeRef;
private TypeReference _readerTypeRef;
private MethodReference _readGetSetMethodRef;
private MethodReference _readAutoPackGetSetMethodRef;
private TypeReference _functionT2TypeRef;
private TypeReference _functionT3TypeRef;
private MethodReference _functionT2ConstructorMethodRef;
private MethodReference _functionT3ConstructorMethodRef;
private TypeDefinition _generatedReaderWriterClassTypeDef;
private MethodDefinition _generatedReaderWriterOnLoadMethodDef;
private TypeReference _autoPackTypeRef;
#endregion
#region Misc.
/// <summary>
/// TypeReferences which have already had delegates made for.
/// </summary>
private HashSet<TypeReference> _delegatedTypes = new HashSet<TypeReference>();
#endregion
#region Const.
internal const string INITIALIZEONCE_METHOD_NAME = GenericWriterHelper.INITIALIZEONCE_METHOD_NAME;
internal const MethodAttributes INITIALIZEONCE_METHOD_ATTRIBUTES = GenericWriterHelper.INITIALIZEONCE_METHOD_ATTRIBUTES;
#endregion
/// <summary>
/// Imports references needed by this helper.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
internal bool ImportReferences()
{
_genericReaderTypeRef = CodegenSession.ImportReference(typeof(GenericReader<>));
_readerTypeRef = CodegenSession.ImportReference(typeof(Reader));
_functionT2TypeRef = CodegenSession.ImportReference(typeof(Func<,>));
_functionT3TypeRef = CodegenSession.ImportReference(typeof(Func<,,>));
_functionT2ConstructorMethodRef = CodegenSession.ImportReference(typeof(Func<,>).GetConstructors()[0]);
_functionT3ConstructorMethodRef = CodegenSession.ImportReference(typeof(Func<,,>).GetConstructors()[0]);
_autoPackTypeRef = CodegenSession.ImportReference(typeof(AutoPackType));
System.Reflection.PropertyInfo writePropertyInfo;
writePropertyInfo = typeof(GenericReader<>).GetProperty(nameof(GenericReader<int>.Read));
_readGetSetMethodRef = CodegenSession.ImportReference(writePropertyInfo.GetSetMethod());
writePropertyInfo = typeof(GenericReader<>).GetProperty(nameof(GenericReader<int>.ReadAutoPack));
_readAutoPackGetSetMethodRef = CodegenSession.ImportReference(writePropertyInfo.GetSetMethod());
return true;
}
/// <summary>
/// Creates a Read delegate for readMethodRef and places it within the generated reader/writer constructor.
/// </summary>
/// <param name="readMethodRef"></param>
/// <param name="diagnostics"></param>
internal void CreateReadDelegate(MethodReference readMethodRef)
{
bool created;
/* If class for generated reader/writers isn't known yet.
* It's possible this is the case if the entry being added
* now is the first entry. That would mean the class was just
* generated. */
if (_generatedReaderWriterClassTypeDef == null)
_generatedReaderWriterClassTypeDef = CodegenSession.GeneralHelper.GetOrCreateClass(out _, ReaderGenerator.GENERATED_TYPE_ATTRIBUTES, ReaderGenerator.GENERATED_READERS_CLASS_NAME, null);
/* If constructor isn't set then try to get or create it
* and also add it to methods if were created. */
if (_generatedReaderWriterOnLoadMethodDef == null)
{
_generatedReaderWriterOnLoadMethodDef = CodegenSession.GeneralHelper.GetOrCreateMethod(_generatedReaderWriterClassTypeDef, out created, INITIALIZEONCE_METHOD_ATTRIBUTES, INITIALIZEONCE_METHOD_NAME, CodegenSession.Module.TypeSystem.Void);
if (created)
CodegenSession.GeneralHelper.CreateRuntimeInitializeOnLoadMethodAttribute(_generatedReaderWriterOnLoadMethodDef);
}
//Check if ret already exist, if so remove it; ret will be added on again in this method.
if (_generatedReaderWriterOnLoadMethodDef.Body.Instructions.Count != 0)
{
int lastIndex = (_generatedReaderWriterOnLoadMethodDef.Body.Instructions.Count - 1);
if (_generatedReaderWriterOnLoadMethodDef.Body.Instructions[lastIndex].OpCode == OpCodes.Ret)
_generatedReaderWriterOnLoadMethodDef.Body.Instructions.RemoveAt(lastIndex);
}
//Check if already exist.
ILProcessor processor = _generatedReaderWriterOnLoadMethodDef.Body.GetILProcessor();
TypeReference dataTypeRef = readMethodRef.ReturnType;
if (_delegatedTypes.Contains(dataTypeRef))
{
CodegenSession.LogError($"Generic read already created for {dataTypeRef.FullName}.");
return;
}
else
{
_delegatedTypes.Add(dataTypeRef);
}
//Create a Func<Reader, T> delegate
processor.Emit(OpCodes.Ldnull);
processor.Emit(OpCodes.Ldftn, readMethodRef);
GenericInstanceType functionGenericInstance;
MethodReference functionConstructorInstanceMethodRef;
bool isAutoPacked = CodegenSession.ReaderHelper.IsAutoPackedType(dataTypeRef);
//Generate for autopacktype.
if (isAutoPacked)
{
functionGenericInstance = _functionT3TypeRef.MakeGenericInstanceType(_readerTypeRef, _autoPackTypeRef, dataTypeRef);
functionConstructorInstanceMethodRef = _functionT3ConstructorMethodRef.MakeHostInstanceGeneric(functionGenericInstance);
}
//Not autopacked.
else
{
functionGenericInstance = _functionT2TypeRef.MakeGenericInstanceType(_readerTypeRef, dataTypeRef);
functionConstructorInstanceMethodRef = _functionT2ConstructorMethodRef.MakeHostInstanceGeneric(functionGenericInstance);
}
processor.Emit(OpCodes.Newobj, functionConstructorInstanceMethodRef);
//Call delegate to GeneratedReader<T>.Read
GenericInstanceType genericInstance = _genericReaderTypeRef.MakeGenericInstanceType(dataTypeRef);
MethodReference genericReaderMethodRef = (isAutoPacked) ?
_readAutoPackGetSetMethodRef.MakeHostInstanceGeneric(genericInstance) :
_readGetSetMethodRef.MakeHostInstanceGeneric(genericInstance);
processor.Emit(OpCodes.Call, genericReaderMethodRef);
processor.Emit(OpCodes.Ret);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f9d3654f5816c4409b88fa0602c6df4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,197 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Serializing;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using MonoFN.Cecil.Rocks;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.CodeGenerating.Helping
{
internal class GenericWriterHelper
{
#region Reflection references.
private TypeReference _genericWriterTypeRef;
private TypeReference _writerTypeRef;
private MethodReference _writeGetSetMethodRef;
private MethodReference _writeAutoPackGetSetMethodRef;
internal TypeReference ActionT2TypeRef;
internal TypeReference ActionT3TypeRef;
internal MethodReference ActionT2ConstructorMethodRef;
internal MethodReference ActionT3ConstructorMethodRef;
private TypeDefinition _generatedReaderWriterClassTypeDef;
private MethodDefinition _generatedReaderWriterOnLoadMethodDef;
private TypeReference _autoPackTypeRef;
#endregion
#region Misc.
/// <summary>
/// TypeReferences which have already had delegates made for.
/// </summary>
private HashSet<TypeReference> _delegatedTypes = new HashSet<TypeReference>();
#endregion
#region Const.
internal const string INITIALIZEONCE_METHOD_NAME = "InitializeOnce";
internal const MethodAttributes INITIALIZEONCE_METHOD_ATTRIBUTES = MethodAttributes.Static;
#endregion
/// <summary>
/// Imports references needed by this helper.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
internal bool ImportReferences()
{
_genericWriterTypeRef = CodegenSession.ImportReference(typeof(GenericWriter<>));
_writerTypeRef = CodegenSession.ImportReference(typeof(Writer));
ActionT2TypeRef = CodegenSession.ImportReference(typeof(Action<,>));
ActionT3TypeRef = CodegenSession.ImportReference(typeof(Action<,,>));
ActionT2ConstructorMethodRef = CodegenSession.ImportReference(typeof(Action<,>).GetConstructors()[0]);
ActionT3ConstructorMethodRef = CodegenSession.ImportReference(typeof(Action<,,>).GetConstructors()[0]);
_autoPackTypeRef = CodegenSession.ImportReference(typeof(AutoPackType));
System.Reflection.PropertyInfo writePropertyInfo;
writePropertyInfo = typeof(GenericWriter<>).GetProperty(nameof(GenericWriter<int>.Write));
_writeGetSetMethodRef = CodegenSession.ImportReference(writePropertyInfo.GetSetMethod());
writePropertyInfo = typeof(GenericWriter<>).GetProperty(nameof(GenericWriter<int>.WriteAutoPack));
_writeAutoPackGetSetMethodRef = CodegenSession.ImportReference(writePropertyInfo.GetSetMethod());
return true;
}
/// <summary>
/// Creates a variant of an instanced write method.
/// </summary>
/// <param name="writeMethodRef"></param>
/// <param name="diagnostics"></param>
internal void CreateInstancedStaticWrite(MethodReference writeMethodRef)
{
if (_generatedReaderWriterClassTypeDef == null)
_generatedReaderWriterClassTypeDef = CodegenSession.GeneralHelper.GetOrCreateClass(out _, WriterGenerator.GENERATED_TYPE_ATTRIBUTES, WriterGenerator.GENERATED_WRITERS_CLASS_NAME, null);
MethodDefinition writeMethodDef = writeMethodRef.CachedResolve();
MethodDefinition createdMethodDef = new MethodDefinition($"Static___{writeMethodRef.Name}",
(MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig),
_generatedReaderWriterClassTypeDef.Module.TypeSystem.Void);
_generatedReaderWriterClassTypeDef.Methods.Add(createdMethodDef);
TypeReference extensionAttributeTypeRef = CodegenSession.ImportReference(typeof(System.Runtime.CompilerServices.ExtensionAttribute));
MethodDefinition constructor = extensionAttributeTypeRef.GetConstructor();
MethodReference extensionAttributeConstructorMethodRef = CodegenSession.ImportReference(constructor);
CustomAttribute extensionCustomAttribute = new CustomAttribute(extensionAttributeConstructorMethodRef);
createdMethodDef.CustomAttributes.Add(extensionCustomAttribute);
/* Add parameters to new method. */
//First add extension.
ParameterDefinition extensionParameterDef = CodegenSession.GeneralHelper.CreateParameter(createdMethodDef, typeof(PooledWriter), "pooledWriter", ParameterAttributes.None);
//Then other types.
ParameterDefinition[] remainingParameterDefs = new ParameterDefinition[writeMethodDef.Parameters.Count];
for (int i = 0; i < writeMethodDef.Parameters.Count; i++)
{
remainingParameterDefs[i] = CodegenSession.GeneralHelper.CreateParameter(createdMethodDef, writeMethodDef.Parameters[i].ParameterType);
_generatedReaderWriterClassTypeDef.Module.ImportReference(remainingParameterDefs[i].ParameterType.CachedResolve());
}
ILProcessor processor = createdMethodDef.Body.GetILProcessor();
//Load all parameters.
foreach (ParameterDefinition pd in remainingParameterDefs)
processor.Emit(OpCodes.Ldarg, pd);
//Call instanced method.
processor.Emit(OpCodes.Ldarg, extensionParameterDef);
processor.Emit(OpCodes.Call, writeMethodRef);
processor.Emit(OpCodes.Ret);
}
/// <summary>
/// Creates a Write delegate for writeMethodRef and places it within the generated reader/writer constructor.
/// </summary>
/// <param name="writeMethodRef"></param>
internal void CreateWriteDelegate(MethodReference writeMethodRef, bool isStatic)
{
/* If class for generated reader/writers isn't known yet.
* It's possible this is the case if the entry being added
* now is the first entry. That would mean the class was just
* generated. */
bool created;
if (_generatedReaderWriterClassTypeDef == null)
_generatedReaderWriterClassTypeDef = CodegenSession.GeneralHelper.GetOrCreateClass(out created, WriterGenerator.GENERATED_TYPE_ATTRIBUTES, WriterGenerator.GENERATED_WRITERS_CLASS_NAME, null);
/* If constructor isn't set then try to get or create it
* and also add it to methods if were created. */
if (_generatedReaderWriterOnLoadMethodDef == null)
{
_generatedReaderWriterOnLoadMethodDef = CodegenSession.GeneralHelper.GetOrCreateMethod(_generatedReaderWriterClassTypeDef, out created, INITIALIZEONCE_METHOD_ATTRIBUTES, INITIALIZEONCE_METHOD_NAME, CodegenSession.Module.TypeSystem.Void);
if (created)
CodegenSession.GeneralHelper.CreateRuntimeInitializeOnLoadMethodAttribute(_generatedReaderWriterOnLoadMethodDef);
}
//Check if ret already exist, if so remove it; ret will be added on again in this method.
if (_generatedReaderWriterOnLoadMethodDef.Body.Instructions.Count != 0)
{
int lastIndex = (_generatedReaderWriterOnLoadMethodDef.Body.Instructions.Count - 1);
if (_generatedReaderWriterOnLoadMethodDef.Body.Instructions[lastIndex].OpCode == OpCodes.Ret)
_generatedReaderWriterOnLoadMethodDef.Body.Instructions.RemoveAt(lastIndex);
}
ILProcessor processor = _generatedReaderWriterOnLoadMethodDef.Body.GetILProcessor();
TypeReference dataTypeRef;
//Static methods will have the data type as the second parameter (1).
if (isStatic)
dataTypeRef = writeMethodRef.Parameters[1].ParameterType;
else
dataTypeRef = writeMethodRef.Parameters[0].ParameterType;
//Check if writer already exist.
if (_delegatedTypes.Contains(dataTypeRef))
{
CodegenSession.LogError($"Generic write already created for {dataTypeRef.FullName}.");
return;
}
else
{
_delegatedTypes.Add(dataTypeRef);
}
/* Create a Action<Writer, T> delegate.
* May also be Action<Writer, AutoPackType, T> delegate
* for packed types. */
processor.Emit(OpCodes.Ldnull);
processor.Emit(OpCodes.Ldftn, writeMethodRef);
GenericInstanceType actionGenericInstance;
MethodReference actionConstructorInstanceMethodRef;
bool isAutoPacked = CodegenSession.WriterHelper.IsAutoPackedType(dataTypeRef);
//Generate for auto pack type.
if (isAutoPacked)
{
actionGenericInstance = ActionT3TypeRef.MakeGenericInstanceType(_writerTypeRef, dataTypeRef, _autoPackTypeRef);
actionConstructorInstanceMethodRef = ActionT3ConstructorMethodRef.MakeHostInstanceGeneric(actionGenericInstance);
}
//Generate for normal type.
else
{
actionGenericInstance = ActionT2TypeRef.MakeGenericInstanceType(_writerTypeRef, dataTypeRef);
actionConstructorInstanceMethodRef = ActionT2ConstructorMethodRef.MakeHostInstanceGeneric(actionGenericInstance);
}
processor.Emit(OpCodes.Newobj, actionConstructorInstanceMethodRef);
//Call delegate to GenericWriter<T>.Write
GenericInstanceType genericInstance = _genericWriterTypeRef.MakeGenericInstanceType(dataTypeRef);
MethodReference genericrWriteMethodRef = (isAutoPacked) ?
_writeAutoPackGetSetMethodRef.MakeHostInstanceGeneric(genericInstance) :
_writeGetSetMethodRef.MakeHostInstanceGeneric(genericInstance);
processor.Emit(OpCodes.Call, genericrWriteMethodRef);
processor.Emit(OpCodes.Ret);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a4021bd44dc40f47abb494e0a4326f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,441 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.CodeGenerating.Processing;
using FishNet.Configuring;
using FishNet.Managing.Logging;
using FishNet.Object;
using FishNet.Object.Delegating;
using FishNet.Object.Helping;
using FishNet.Object.Prediction.Delegating;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace FishNet.CodeGenerating.Helping
{
internal class NetworkBehaviourHelper
{
#region Reflection references.
//Names.
internal string FullName;
//Prediction.
internal MethodReference ClearReplicateCache_MethodRef;
internal MethodReference SetLastReconcileTick_MethodRef;
internal MethodReference TransformMayChange_MethodRef;
internal MethodReference SendReplicateRpc_MethodRef;
internal MethodReference SendReconcileRpc_MethodRef;
internal MethodReference RegisterReplicateRpc_MethodRef;
internal MethodReference RegisterReconcileRpc_MethodRef;
internal MethodReference ReplicateRpcDelegateConstructor_MethodRef;
internal MethodReference ReconcileRpcDelegateConstructor_MethodRef;
//RPCs.
internal MethodReference SendServerRpc_MethodRef;
internal MethodReference SendObserversRpc_MethodRef;
internal MethodReference SendTargetRpc_MethodRef;
internal MethodReference DirtySyncType_MethodRef;
internal MethodReference RegisterServerRpc_MethodRef;
internal MethodReference RegisterObserversRpc_MethodRef;
internal MethodReference RegisterTargetRpc_MethodRef;
internal MethodReference ServerRpcDelegateConstructor_MethodRef;
internal MethodReference ClientRpcDelegateConstructor_MethodRef;
//Is checks.
internal MethodReference IsClient_MethodRef;
internal MethodReference IsOwner_MethodRef;
internal MethodReference IsServer_MethodRef;
internal MethodReference IsHost_MethodRef;
//Misc.
internal TypeReference TypeRef;
internal MethodReference CompareOwner_MethodRef;
internal MethodReference LocalConnection_MethodRef;
internal MethodReference Owner_MethodRef;
internal MethodReference ReadSyncVar_MethodRef;
internal MethodReference NetworkInitializeInternal_MethodRef;
//TimeManager.
internal MethodReference TimeManager_MethodRef;
#endregion
#region Const.
internal const uint MAX_RPC_ALLOWANCE = ushort.MaxValue;
internal const string AWAKE_METHOD_NAME = "Awake";
internal const string DISABLE_LOGGING_TEXT = "This message may be disabled by setting the Logging field in your attribute to LoggingType.Off";
#endregion
internal bool ImportReferences()
{
Type networkBehaviourType = typeof(NetworkBehaviour);
TypeRef = CodegenSession.ImportReference(networkBehaviourType);
FullName = networkBehaviourType.FullName;
CodegenSession.ImportReference(networkBehaviourType);
//ServerRpcDelegate and ClientRpcDelegate constructors.
ServerRpcDelegateConstructor_MethodRef = CodegenSession.ImportReference(typeof(ServerRpcDelegate).GetConstructors().First());
ClientRpcDelegateConstructor_MethodRef = CodegenSession.ImportReference(typeof(ClientRpcDelegate).GetConstructors().First());
//Prediction Rpc delegate constructors.
ReplicateRpcDelegateConstructor_MethodRef = CodegenSession.ImportReference(typeof(ReplicateRpcDelegate).GetConstructors().First());
ReconcileRpcDelegateConstructor_MethodRef = CodegenSession.ImportReference(typeof(ReconcileRpcDelegate).GetConstructors().First());
foreach (MethodInfo mi in networkBehaviourType.GetMethods((BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic)))
{
//CreateDelegates.
if (mi.Name == nameof(NetworkBehaviour.RegisterServerRpc))
RegisterServerRpc_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.RegisterObserversRpc))
RegisterObserversRpc_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.RegisterTargetRpc))
RegisterTargetRpc_MethodRef = CodegenSession.ImportReference(mi);
//SendPredictions.
else if (mi.Name == nameof(NetworkBehaviour.SendReplicateRpc))
SendReplicateRpc_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.SendReconcileRpc))
SendReconcileRpc_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.RegisterReplicateRpc))
RegisterReplicateRpc_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.RegisterReconcileRpc))
RegisterReconcileRpc_MethodRef = CodegenSession.ImportReference(mi);
//SendRpcs.
else if (mi.Name == nameof(NetworkBehaviour.SendServerRpc))
SendServerRpc_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.SendObserversRpc))
SendObserversRpc_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.SendTargetRpc))
SendTargetRpc_MethodRef = CodegenSession.ImportReference(mi);
//Prediction.
else if (mi.Name == nameof(NetworkBehaviour.SetLastReconcileTick))
SetLastReconcileTick_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.ClearReplicateCache))
ClearReplicateCache_MethodRef = CodegenSession.ImportReference(mi);
//Misc.
else if (mi.Name == nameof(NetworkBehaviour.TransformMayChange))
TransformMayChange_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.CompareOwner))
CompareOwner_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.ReadSyncVar))
ReadSyncVar_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.DirtySyncType))
DirtySyncType_MethodRef = CodegenSession.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.NetworkInitializeIfDisabledInternal))
NetworkInitializeInternal_MethodRef = CodegenSession.ImportReference(mi);
}
foreach (PropertyInfo pi in networkBehaviourType.GetProperties((BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic)))
{
//Server/Client states.
if (pi.Name == nameof(NetworkBehaviour.IsClient))
IsClient_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
else if (pi.Name == nameof(NetworkBehaviour.IsServer))
IsServer_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
else if (pi.Name == nameof(NetworkBehaviour.IsHost))
IsHost_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
else if (pi.Name == nameof(NetworkBehaviour.IsOwner))
IsOwner_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
//Owner.
else if (pi.Name == nameof(NetworkBehaviour.Owner))
Owner_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
else if (pi.Name == nameof(NetworkBehaviour.LocalConnection))
LocalConnection_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
//Misc.
else if (pi.Name == nameof(NetworkBehaviour.TimeManager))
TimeManager_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
}
return true;
}
/// <summary>
/// Returnsthe child most Awake by iterating up childMostTypeDef.
/// </summary>
/// <param name="childMostTypeDef"></param>
/// <param name="created"></param>
/// <returns></returns>
internal MethodDefinition GetAwakeMethodDefinition(TypeDefinition typeDef)
{
return typeDef.GetMethod(AWAKE_METHOD_NAME);
}
/// <summary>
/// Creates a replicate delegate.
/// </summary>
/// <param name="processor"></param>
/// <param name="originalMethodDef"></param>
/// <param name="readerMethodDef"></param>
/// <param name="rpcType"></param>
internal void CreateReplicateDelegate(MethodDefinition originalMethodDef, MethodDefinition readerMethodDef, uint methodHash)
{
MethodDefinition methodDef = originalMethodDef.DeclaringType.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
ILProcessor processor = methodDef.Body.GetILProcessor();
List<Instruction> insts = new List<Instruction>();
insts.Add(processor.Create(OpCodes.Ldarg_0));
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)methodHash));
/* Create delegate and call NetworkBehaviour method. */
insts.Add(processor.Create(OpCodes.Ldnull));
insts.Add(processor.Create(OpCodes.Ldftn, readerMethodDef));
/* Has to be done last. This allows the NetworkBehaviour to
* initialize it's fields first. */
processor.InsertLast(insts);
}
/// <summary>
/// Creates a RPC delegate for rpcType.
/// </summary>
/// <param name="processor"></param>
/// <param name="originalMethodDef"></param>
/// <param name="readerMethodDef"></param>
/// <param name="rpcType"></param>
internal void CreateRpcDelegate(bool runLocally, TypeDefinition typeDef, MethodDefinition readerMethodDef, RpcType rpcType, uint methodHash, CustomAttribute rpcAttribute)
{
MethodDefinition methodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
ILProcessor processor = methodDef.Body.GetILProcessor();
List<Instruction> insts = new List<Instruction>();
insts.Add(processor.Create(OpCodes.Ldarg_0));
//uint methodHash = originalMethodDef.FullName.GetStableHash32();
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)methodHash));
/* Create delegate and call NetworkBehaviour method. */
insts.Add(processor.Create(OpCodes.Ldnull));
insts.Add(processor.Create(OpCodes.Ldftn, readerMethodDef));
//Server.
if (rpcType == RpcType.Server)
{
insts.Add(processor.Create(OpCodes.Newobj, ServerRpcDelegateConstructor_MethodRef));
insts.Add(processor.Create(OpCodes.Call, RegisterServerRpc_MethodRef));
}
//Observers.
else if (rpcType == RpcType.Observers)
{
insts.Add(processor.Create(OpCodes.Newobj, ClientRpcDelegateConstructor_MethodRef));
insts.Add(processor.Create(OpCodes.Call, RegisterObserversRpc_MethodRef));
}
//Target
else if (rpcType == RpcType.Target)
{
insts.Add(processor.Create(OpCodes.Newobj, ClientRpcDelegateConstructor_MethodRef));
insts.Add(processor.Create(OpCodes.Call, RegisterTargetRpc_MethodRef));
}
/* Has to be done last. This allows the NetworkBehaviour to
* initialize it's fields first. */
processor.InsertLast(insts);
}
/// <summary>
/// Creates exit method condition if local client is not owner.
/// </summary>
/// <param name="retIfOwner">True if to ret when owner, false to ret when not owner.</param>
/// <returns>Returns Ret instruction.</returns>
internal Instruction CreateLocalClientIsOwnerCheck(MethodDefinition methodDef, LoggingType loggingType, bool canDisableLogging, bool retIfOwner, bool insertFirst)
{
List<Instruction> instructions = new List<Instruction>();
/* This is placed after the if check.
* Should the if check pass then code
* jumps to this instruction. */
ILProcessor processor = methodDef.Body.GetILProcessor();
Instruction endIf = processor.Create(OpCodes.Nop);
instructions.Add(processor.Create(OpCodes.Ldarg_0)); //argument: this
//If !base.IsOwner endIf.
instructions.Add(processor.Create(OpCodes.Call, IsOwner_MethodRef));
if (retIfOwner)
instructions.Add(processor.Create(OpCodes.Brfalse, endIf));
else
instructions.Add(processor.Create(OpCodes.Brtrue, endIf));
//If logging is not disabled.
if (loggingType != LoggingType.Off)
{
string disableLoggingText = (canDisableLogging) ? DISABLE_LOGGING_TEXT : string.Empty;
string msg = (retIfOwner) ?
$"Cannot complete action because you are the owner of this object. {disableLoggingText}." :
$"Cannot complete action because you are not the owner of this object. {disableLoggingText}.";
instructions.AddRange(
CodegenSession.GeneralHelper.CreateDebugWithCanLogInstructions(processor, msg, loggingType, false, true)
);
}
//Return block.
Instruction retInst = processor.Create(OpCodes.Ret);
instructions.Add(retInst);
//After if statement, jumped to when successful check.
instructions.Add(endIf);
if (insertFirst)
{
processor.InsertFirst(instructions);
}
else
{
foreach (Instruction inst in instructions)
processor.Append(inst);
}
return retInst;
}
/// <summary>
/// Creates exit method condition if remote client is not owner.
/// </summary>
/// <param name="processor"></param>
internal Instruction CreateRemoteClientIsOwnerCheck(ILProcessor processor, ParameterDefinition connectionParameterDef)
{
/* This is placed after the if check.
* Should the if check pass then code
* jumps to this instruction. */
Instruction endIf = processor.Create(OpCodes.Nop);
processor.Emit(OpCodes.Ldarg_0); //argument: this
//If !base.IsOwner endIf.
processor.Emit(OpCodes.Ldarg, connectionParameterDef);
processor.Emit(OpCodes.Call, CompareOwner_MethodRef);
processor.Emit(OpCodes.Brtrue, endIf);
//Return block.
Instruction retInst = processor.Create(OpCodes.Ret);
processor.Append(retInst);
//After if statement, jumped to when successful check.
processor.Append(endIf);
return retInst;
}
/// <summary>
/// Creates exit method condition if not client.
/// </summary>
/// <param name="processor"></param>
/// <param name="retInstruction"></param>
/// <param name="warn"></param>
internal void CreateIsClientCheck(MethodDefinition methodDef, LoggingType loggingType, bool useStatic, bool insertFirst)
{
/* This is placed after the if check.
* Should the if check pass then code
* jumps to this instruction. */
ILProcessor processor = methodDef.Body.GetILProcessor();
Instruction endIf = processor.Create(OpCodes.Nop);
List<Instruction> instructions = new List<Instruction>();
//Checking against the NetworkObject.
if (!useStatic)
{
instructions.Add(processor.Create(OpCodes.Ldarg_0)); //argument: this
//If (!base.IsClient)
instructions.Add(processor.Create(OpCodes.Call, IsClient_MethodRef));
}
//Checking instanceFinder.
else
{
instructions.Add(processor.Create(OpCodes.Call, CodegenSession.ObjectHelper.InstanceFinder_IsClient_MethodRef));
}
instructions.Add(processor.Create(OpCodes.Brtrue, endIf));
//If warning then also append warning text.
if (loggingType != LoggingType.Off)
{
string msg = $"Cannot complete action because client is not active. This may also occur if the object is not yet initialized or if it does not contain a NetworkObject component. {DISABLE_LOGGING_TEXT}.";
instructions.AddRange(
CodegenSession.GeneralHelper.CreateDebugWithCanLogInstructions(processor, msg, loggingType, useStatic, true)
);
}
//Add return.
instructions.AddRange(CreateRetDefault(methodDef));
//After if statement, jumped to when successful check.
instructions.Add(endIf);
if (insertFirst)
{
processor.InsertFirst(instructions);
}
else
{
foreach (Instruction inst in instructions)
processor.Append(inst);
}
}
/// <summary>
/// Creates exit method condition if not server.
/// </summary>
/// <param name="processor"></param>
/// <param name="warn"></param>
internal void CreateIsServerCheck(MethodDefinition methodDef, LoggingType loggingType, bool useStatic, bool insertFirst)
{
/* This is placed after the if check.
* Should the if check pass then code
* jumps to this instruction. */
ILProcessor processor = methodDef.Body.GetILProcessor();
Instruction endIf = processor.Create(OpCodes.Nop);
List<Instruction> instructions = new List<Instruction>();
if (!useStatic)
{
instructions.Add(processor.Create(OpCodes.Ldarg_0)); //argument: this
//If (!base.IsServer)
instructions.Add(processor.Create(OpCodes.Call, IsServer_MethodRef));
}
//Checking instanceFinder.
else
{
instructions.Add(processor.Create(OpCodes.Call, CodegenSession.ObjectHelper.InstanceFinder_IsServer_MethodRef));
}
instructions.Add(processor.Create(OpCodes.Brtrue, endIf));
//If warning then also append warning text.
if (loggingType != LoggingType.Off)
{
string msg = $"Cannot complete action because server is not active. This may also occur if the object is not yet initialized or if it does not contain a NetworkObject component. {DISABLE_LOGGING_TEXT}";
instructions.AddRange(
CodegenSession.GeneralHelper.CreateDebugWithCanLogInstructions(processor, msg, loggingType, useStatic, true)
);
}
//Add return.
instructions.AddRange(CreateRetDefault(methodDef));
//After if statement, jumped to when successful check.
instructions.Add(endIf);
if (insertFirst)
{
processor.InsertFirst(instructions);
}
else
{
foreach (Instruction inst in instructions)
processor.Append(inst);
}
}
/// <summary>
/// Creates a return using the ReturnType for methodDef.
/// </summary>
/// <param name="processor"></param>
/// <param name="methodDef"></param>
/// <returns></returns>
public List<Instruction> CreateRetDefault(MethodDefinition methodDef, ModuleDefinition importReturnModule = null)
{
ILProcessor processor = methodDef.Body.GetILProcessor();
List<Instruction> instructions = new List<Instruction>();
//If requires a value return.
if (methodDef.ReturnType != methodDef.Module.TypeSystem.Void)
{
//Import type first.
methodDef.Module.ImportReference(methodDef.ReturnType);
if (importReturnModule != null)
importReturnModule.ImportReference(methodDef.ReturnType);
VariableDefinition vd = CodegenSession.GeneralHelper.CreateVariable(methodDef, methodDef.ReturnType);
instructions.Add(processor.Create(OpCodes.Ldloca_S, vd));
instructions.Add(processor.Create(OpCodes.Initobj, vd.VariableType));
instructions.Add(processor.Create(OpCodes.Ldloc, vd));
}
instructions.Add(processor.Create(OpCodes.Ret));
return instructions;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c42e06349d6890459a155a2afe17d41
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,90 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Connection;
using FishNet.Object.Synchronizing;
using FishNet.Object.Synchronizing.Internal;
using MonoFN.Cecil;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace FishNet.CodeGenerating.Helping
{
internal class ObjectHelper
{
#region Reflection references.
//Fullnames.
internal string SyncList_Name;
internal string SyncDictionary_Name;
internal string SyncHashSet_Name;
//Is checks.
internal MethodReference InstanceFinder_IsServer_MethodRef;
internal MethodReference InstanceFinder_IsClient_MethodRef;
//Misc.
internal MethodReference NetworkConnection_IsValid_MethodRef;
internal MethodReference NetworkConnection_IsActive_MethodRef;
internal MethodReference Dictionary_Add_UShort_SyncBase_MethodRef;
internal MethodReference NetworkConnection_GetIsLocalClient_MethodRef;
#endregion
internal bool ImportReferences()
{
Type tmpType;
/* SyncObject names. */
//SyncList.
tmpType = typeof(SyncList<>);
CodegenSession.ImportReference(tmpType);
SyncList_Name = tmpType.Name;
//SyncDictionary.
tmpType = typeof(SyncDictionary<,>);
CodegenSession.ImportReference(tmpType);
SyncDictionary_Name = tmpType.Name;
//SyncHashSet.
tmpType = typeof(SyncHashSet<>);
CodegenSession.ImportReference(tmpType);
SyncHashSet_Name = tmpType.Name;
tmpType = typeof(NetworkConnection);
TypeReference networkConnectionTr = CodegenSession.ImportReference(tmpType);
foreach (PropertyDefinition item in networkConnectionTr.CachedResolve().Properties)
{
if (item.Name == nameof(NetworkConnection.IsLocalClient))
NetworkConnection_GetIsLocalClient_MethodRef = CodegenSession.ImportReference(item.GetMethod);
}
//Dictionary.Add(ushort, SyncBase).
Type dictType = typeof(Dictionary<ushort, SyncBase>);
TypeReference dictTypeRef = CodegenSession.ImportReference(dictType);
//Dictionary_Add_UShort_SyncBase_MethodRef = dictTypeRef.CachedResolve().GetMethod("add_Item", )
foreach (MethodDefinition item in dictTypeRef.CachedResolve().Methods)
{
if (item.Name == nameof(Dictionary<ushort, SyncBase>.Add))
{
Dictionary_Add_UShort_SyncBase_MethodRef = CodegenSession.ImportReference(item);
break;
}
}
//InstanceFinder infos.
Type instanceFinderType = typeof(InstanceFinder);
foreach (PropertyInfo pi in instanceFinderType.GetProperties())
{
if (pi.Name == nameof(InstanceFinder.IsClient))
InstanceFinder_IsClient_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
else if (pi.Name == nameof(InstanceFinder.IsServer))
InstanceFinder_IsServer_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
}
//NetworkConnection.
foreach (PropertyInfo pi in typeof(NetworkConnection).GetProperties((BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic)))
{
if (pi.Name == nameof(NetworkConnection.IsValid))
NetworkConnection_IsValid_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
else if (pi.Name == nameof(NetworkConnection.IsActive))
NetworkConnection_IsActive_MethodRef = CodegenSession.ImportReference(pi.GetMethod);
}
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 033da35314653aa4689b4582e7929295
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,494 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Object;
using FishNet.Serializing;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System;
using UnityEngine;
namespace FishNet.CodeGenerating.Helping
{
internal class ReaderGenerator
{
#region Const.
internal const string GENERATED_READERS_CLASS_NAME = "GeneratedReaders___FN";
public const TypeAttributes GENERATED_TYPE_ATTRIBUTES = WriterGenerator.GENERATED_TYPE_ATTRIBUTES;
private const string READ_PREFIX = "Read___";
#endregion
/// <summary>
/// Imports references needed by this helper.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
internal bool ImportReferences()
{
return true;
}
/// <summary>
/// Generates a reader for objectTypeReference if one does not already exist.
/// </summary>
/// <param name="objectTr"></param>
/// <returns></returns>
internal MethodReference CreateReader(TypeReference objectTr)
{
MethodReference resultMr = null;
TypeDefinition objectTypeDef;
SerializerType serializerType = GeneratorHelper.GetSerializerType(objectTr, false, out objectTypeDef);
if (serializerType != SerializerType.Invalid)
{
//Array.
if (serializerType == SerializerType.Array)
resultMr = CreateArrayReaderMethodReference(objectTr);
//Enum.
else if (serializerType == SerializerType.Enum)
resultMr = CreateEnumReaderMethodDefinition(objectTr);
else if (serializerType == SerializerType.Dictionary)
resultMr = CreateDictionaryReaderMethodReference(objectTr);
//List.
else if (serializerType == SerializerType.List)
resultMr = CreateListReaderMethodReference(objectTr);
//NetworkBehaviour.
else if (serializerType == SerializerType.NetworkBehaviour)
resultMr = GetNetworkBehaviourReaderMethodReference(objectTr);
//Nullable.
else if (serializerType == SerializerType.Nullable)
resultMr = CreateNullableReaderMethodReference(objectTr);
//Class or struct.
else if (serializerType == SerializerType.ClassOrStruct)
resultMr = CreateClassOrStructReaderMethodReference(objectTr);
}
//If was not created.
if (resultMr == null)
RemoveFromStaticReaders(objectTr);
return resultMr;
}
/// <summary>
/// Removes from static writers.
/// </summary>
private void RemoveFromStaticReaders(TypeReference tr)
{
CodegenSession.ReaderHelper.RemoveReaderMethod(tr, false);
}
/// <summary>
/// Adds to static writers.
/// </summary>
private void AddToStaticReaders(TypeReference tr, MethodReference mr)
{
CodegenSession.ReaderHelper.AddReaderMethod(tr, mr.CachedResolve(), false, true);
}
/// <summary>
/// Generates a reader for objectTypeReference if one does not already exist.
/// </summary>
/// <param name="objectTr"></param>
/// <returns></returns>
private MethodReference CreateEnumReaderMethodDefinition(TypeReference objectTr)
{
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
AddToStaticReaders(objectTr, createdReaderMd);
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
//Get type reference for enum type. eg byte int
TypeReference underlyingTypeRef = objectTr.CachedResolve().GetEnumUnderlyingTypeReference();
//Get read method for underlying type.
MethodReference readMethodRef = CodegenSession.ReaderHelper.GetOrCreateFavoredReadMethodReference(underlyingTypeRef, true);
if (readMethodRef == null)
return null;
ParameterDefinition readerParameterDef = createdReaderMd.Parameters[0];
//reader.ReadXXX().
processor.Emit(OpCodes.Ldarg, readerParameterDef);
if (CodegenSession.WriterHelper.IsAutoPackedType(underlyingTypeRef))
processor.Emit(OpCodes.Ldc_I4, (int)AutoPackType.Packed);
processor.Emit(OpCodes.Call, readMethodRef);
processor.Emit(OpCodes.Ret);
return CodegenSession.ImportReference(createdReaderMd);
}
/// <summary>
/// Creates a read for a class type which inherits NetworkBehaviour.
/// </summary>
/// <param name="objectTr"></param>
/// <returns></returns>
private MethodReference GetNetworkBehaviourReaderMethodReference(TypeReference objectTr)
{
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
AddToStaticReaders(objectTr, createdReaderMd);
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
TypeReference networkBehaviourTypeRef = CodegenSession.GeneralHelper.GetTypeReference(typeof(NetworkBehaviour));
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Call, CodegenSession.ReaderHelper.GetFavoredReadMethodReference(networkBehaviourTypeRef, true));
processor.Emit(OpCodes.Castclass, objectTr);
processor.Emit(OpCodes.Ret);
return CodegenSession.ImportReference(createdReaderMd);
}
/// <summary>
/// Create a reader for an array or list.
/// </summary>
private MethodReference CreateArrayReaderMethodReference(TypeReference objectTr)
{
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
AddToStaticReaders(objectTr, createdReaderMd);
/* Try to get instanced first for collection element type, if it doesn't exist then try to
* get/or make a one. */
TypeReference elementTypeRef = objectTr.GetElementType();
MethodReference readMethodRef = CodegenSession.ReaderHelper.GetOrCreateFavoredReadMethodReference(elementTypeRef, true);
if (readMethodRef == null)
return null;
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
ParameterDefinition readerParameterDef = createdReaderMd.Parameters[0];
VariableDefinition sizeVariableDef = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, typeof(int));
//Load packed whole value into sizeVariableDef, exit if null indicator.
CodegenSession.ReaderHelper.CreateRetOnNull(processor, readerParameterDef, sizeVariableDef, false);
//Make local variable of array type.
VariableDefinition collectionVariableDef = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, objectTr);
//Create new array/list of size.
processor.Emit(OpCodes.Ldloc, sizeVariableDef);
processor.Emit(OpCodes.Newarr, elementTypeRef);
//Store new object of arr/list into collection variable.
processor.Emit(OpCodes.Stloc, collectionVariableDef);
VariableDefinition loopIndex = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, typeof(int));
Instruction loopComparer = processor.Create(OpCodes.Ldloc, loopIndex);
//int i = 0
processor.Emit(OpCodes.Ldc_I4_0);
processor.Emit(OpCodes.Stloc, loopIndex);
processor.Emit(OpCodes.Br_S, loopComparer);
//Loop content.
//Collection[index]
Instruction contentStart = processor.Create(OpCodes.Ldloc, collectionVariableDef);
processor.Append(contentStart);
/* Only arrays load the index since we are setting to that index.
* List call lst.Add */
processor.Emit(OpCodes.Ldloc, loopIndex);
//Collection[index] = reader.
processor.Emit(OpCodes.Ldarg, readerParameterDef);
//Pass in AutoPackType default.
if (CodegenSession.ReaderHelper.IsAutoPackedType(elementTypeRef))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(elementTypeRef);
processor.Emit(OpCodes.Ldc_I4, (int)packType);
}
//Collection[index] = reader.ReadType().
processor.Emit(OpCodes.Call, readMethodRef);
//Set value to collection.
processor.Emit(OpCodes.Stelem_Any, elementTypeRef);
//i++
processor.Emit(OpCodes.Ldloc, loopIndex);
processor.Emit(OpCodes.Ldc_I4_1);
processor.Emit(OpCodes.Add);
processor.Emit(OpCodes.Stloc, loopIndex);
//if i < length jmp to content start.
processor.Append(loopComparer); //if i < size
processor.Emit(OpCodes.Ldloc, sizeVariableDef);
processor.Emit(OpCodes.Blt_S, contentStart);
processor.Emit(OpCodes.Ldloc, collectionVariableDef);
processor.Emit(OpCodes.Ret);
return CodegenSession.ImportReference(createdReaderMd);
}
/// <summary>
/// Creates a reader for a dictionary.
/// </summary>
private MethodReference CreateDictionaryReaderMethodReference(TypeReference objectTr)
{
GenericInstanceType genericInstance = (GenericInstanceType)objectTr;
CodegenSession.ImportReference(genericInstance);
TypeReference keyTr = genericInstance.GenericArguments[0];
TypeReference valueTr = genericInstance.GenericArguments[1];
/* Try to get instanced first for collection element type, if it doesn't exist then try to
* get/or make a one. */
MethodReference keyWriteMr = CodegenSession.ReaderHelper.GetOrCreateFavoredReadMethodReference(keyTr, true);
MethodReference valueWriteMr = CodegenSession.ReaderHelper.GetOrCreateFavoredReadMethodReference(valueTr, true);
if (keyWriteMr == null || valueWriteMr == null)
return null;
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
AddToStaticReaders(objectTr, createdReaderMd);
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
GenericInstanceMethod genericInstanceMethod = CodegenSession.ReaderHelper.Reader_ReadDictionary_MethodRef.MakeGenericMethod(new TypeReference[] { keyTr, valueTr });
ParameterDefinition readerPd = createdReaderMd.Parameters[0];
processor.Emit(OpCodes.Ldarg, readerPd);
processor.Emit(OpCodes.Callvirt, genericInstanceMethod);
processor.Emit(OpCodes.Ret);
return CodegenSession.ImportReference(createdReaderMd);
}
/// <summary>
/// Create a reader for a list.
/// </summary>
private MethodReference CreateListReaderMethodReference(TypeReference objectTr)
{
GenericInstanceType genericInstance = (GenericInstanceType)objectTr;
CodegenSession.ImportReference(genericInstance);
TypeReference elementTypeRef = genericInstance.GenericArguments[0];
/* Try to get instanced first for collection element type, if it doesn't exist then try to
* get/or make a one. */
MethodReference readMethodRef = CodegenSession.ReaderHelper.GetOrCreateFavoredReadMethodReference(elementTypeRef, true);
if (readMethodRef == null)
return null;
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
AddToStaticReaders(objectTr, createdReaderMd);
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
//Find constructor for new list.
MethodDefinition constructorMd = objectTr.CachedResolve().GetConstructor(new Type[] { typeof(int) });
MethodReference constructorMr = constructorMd.MakeHostInstanceGeneric(genericInstance);
//Find add method for list.
MethodReference lstAddMd = objectTr.CachedResolve().GetMethod("Add");
MethodReference lstAddMr = lstAddMd.MakeHostInstanceGeneric(genericInstance);
ParameterDefinition readerParameterDef = createdReaderMd.Parameters[0];
VariableDefinition sizeVariableDef = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, typeof(int));
//Load packed whole value into sizeVariableDef, exit if null indicator.
CodegenSession.ReaderHelper.CreateRetOnNull(processor, readerParameterDef, sizeVariableDef, false);
//Make variable of new list type, and create list object.
VariableDefinition collectionVariableDef = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, genericInstance);
processor.Emit(OpCodes.Ldloc, sizeVariableDef);
processor.Emit(OpCodes.Newobj, constructorMr);
processor.Emit(OpCodes.Stloc, collectionVariableDef);
VariableDefinition loopIndex = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, typeof(int));
Instruction loopComparer = processor.Create(OpCodes.Ldloc, loopIndex);
//int i = 0
processor.Emit(OpCodes.Ldc_I4_0);
processor.Emit(OpCodes.Stloc, loopIndex);
processor.Emit(OpCodes.Br_S, loopComparer);
//Loop content.
//Collection[index]
Instruction contentStart = processor.Create(OpCodes.Ldloc, collectionVariableDef);
processor.Append(contentStart);
//Collection[index] = reader.
processor.Emit(OpCodes.Ldarg, readerParameterDef);
//Pass in AutoPackType default.
if (CodegenSession.ReaderHelper.IsAutoPackedType(elementTypeRef))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(elementTypeRef);
processor.Emit(OpCodes.Ldc_I4, (int)packType);
}
//Collection[index] = reader.ReadType().
processor.Emit(OpCodes.Call, readMethodRef);
//Set value to collection.
processor.Emit(OpCodes.Callvirt, lstAddMr);
//i++
processor.Emit(OpCodes.Ldloc, loopIndex);
processor.Emit(OpCodes.Ldc_I4_1);
processor.Emit(OpCodes.Add);
processor.Emit(OpCodes.Stloc, loopIndex);
//if i < length jmp to content start.
processor.Append(loopComparer); //if i < size
processor.Emit(OpCodes.Ldloc, sizeVariableDef);
processor.Emit(OpCodes.Blt_S, contentStart);
processor.Emit(OpCodes.Ldloc, collectionVariableDef);
processor.Emit(OpCodes.Ret);
return CodegenSession.ImportReference(createdReaderMd);
}
/// <summary>
/// Creates a reader method for a struct or class objectTypeRef.
/// </summary>
/// <param name="objectTr"></param>
/// <returns></returns>
private MethodReference CreateNullableReaderMethodReference(TypeReference objectTr)
{
GenericInstanceType objectGit = objectTr as GenericInstanceType;
TypeReference valueTr = objectGit.GenericArguments[0];
//Make sure object has a ctor.
MethodDefinition objectCtorMd = objectTr.GetConstructor(1);
if (objectCtorMd == null)
{
CodegenSession.LogError($"{objectTr.Name} can't be deserialized because the nullable type does not have a constructor.");
return null;
}
//Get the reader for the value.
MethodReference valueReaderMr = CodegenSession.ReaderHelper.GetOrCreateFavoredReadMethodReference(valueTr, true);
if (valueReaderMr == null)
return null;
TypeDefinition objectTd = objectTr.CachedResolve();
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
AddToStaticReaders(objectTr, createdReaderMd);
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
ParameterDefinition readerPd = createdReaderMd.Parameters[0];
// create local for return value
VariableDefinition resultVd = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, objectTr);
//Read if null into boolean.
VariableDefinition nullBoolVd = createdReaderMd.CreateVariable(typeof(bool));
CodegenSession.ReaderHelper.CreateReadBool(processor, readerPd, nullBoolVd);
Instruction afterReturnNullInst = processor.Create(OpCodes.Nop);
processor.Emit(OpCodes.Ldloc, nullBoolVd);
processor.Emit(OpCodes.Brfalse, afterReturnNullInst);
//Return a null result.
CodegenSession.GeneralHelper.SetVariableDefinitionFromObject(processor, resultVd, objectTd);
processor.Emit(OpCodes.Ldloc, resultVd);
processor.Emit(OpCodes.Ret);
processor.Append(afterReturnNullInst);
MethodReference initMr = objectCtorMd.MakeHostInstanceGeneric(objectGit);
processor.Emit(OpCodes.Ldarg, readerPd);
//If an auto pack method then insert default value.
if (CodegenSession.ReaderHelper.IsAutoPackedType(valueTr))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(valueTr);
processor.Emit(OpCodes.Ldc_I4, (int)packType);
}
processor.Emit(OpCodes.Call, valueReaderMr);
processor.Emit(OpCodes.Newobj, initMr);
processor.Emit(OpCodes.Ret);
return CodegenSession.ImportReference(createdReaderMd);
}
/// <summary>
/// Creates a reader method for a struct or class objectTypeRef.
/// </summary>
/// <param name="objectTr"></param>
/// <returns></returns>
private MethodReference CreateClassOrStructReaderMethodReference(TypeReference objectTr)
{
MethodDefinition createdReaderMd = CreateStaticReaderStubMethodDefinition(objectTr);
AddToStaticReaders(objectTr, createdReaderMd);
TypeDefinition objectTypeDef = objectTr.CachedResolve();
ILProcessor processor = createdReaderMd.Body.GetILProcessor();
ParameterDefinition readerParameterDef = createdReaderMd.Parameters[0];
// create local for return value
VariableDefinition objectVariableDef = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, objectTr);
//If not a value type create a return null check.
if (!objectTypeDef.IsValueType)
{
VariableDefinition nullVariableDef = CodegenSession.GeneralHelper.CreateVariable(createdReaderMd, typeof(bool));
//Load packed whole value into sizeVariableDef, exit if null indicator.
CodegenSession.ReaderHelper.CreateRetOnNull(processor, readerParameterDef, nullVariableDef, true);
}
/* If here then not null. */
//Make a new instance of object type and set to objectVariableDef.
CodegenSession.GeneralHelper.SetVariableDefinitionFromObject(processor, objectVariableDef, objectTypeDef);
if (!ReadFieldsAndProperties(createdReaderMd, readerParameterDef, objectVariableDef, objectTr))
return null;
/* //codegen scriptableobjects seem to climb too high up to UnityEngine.Object when
* creating serializers/deserialized. Make sure this is not possible. */
//Load result and return it.
processor.Emit(OpCodes.Ldloc, objectVariableDef);
processor.Emit(OpCodes.Ret);
return CodegenSession.ImportReference(createdReaderMd);
}
/// <summary>
/// Reads all fields of objectTypeRef.
/// </summary>
private bool ReadFieldsAndProperties(MethodDefinition readerMd, ParameterDefinition readerPd, VariableDefinition objectVd, TypeReference objectTr)
{
//This probably isn't needed but I'm too afraid to remove it.
if (objectTr.Module != CodegenSession.Module)
objectTr = CodegenSession.ImportReference(objectTr.CachedResolve());
//Fields.
foreach (FieldDefinition fieldDef in objectTr.FindAllPublicFields(true, true,
ReaderHelper.EXCLUDED_AUTO_SERIALIZER_TYPES, ReaderHelper.EXCLUDED_ASSEMBLY_PREFIXES))
{
FieldReference importedFr = CodegenSession.ImportReference(fieldDef);
if (GetReadMethod(fieldDef.FieldType, out MethodReference readMr))
CodegenSession.ReaderHelper.CreateReadIntoClassOrStruct(readerMd, readerPd, readMr, objectVd, importedFr);
}
//Properties.
foreach (PropertyDefinition propertyDef in objectTr.FindAllPublicProperties(
true, ReaderHelper.EXCLUDED_AUTO_SERIALIZER_TYPES, ReaderHelper.EXCLUDED_ASSEMBLY_PREFIXES))
{
if (GetReadMethod(propertyDef.PropertyType, out MethodReference readMr))
{
MethodReference setMr = CodegenSession.Module.ImportReference(propertyDef.SetMethod);
CodegenSession.ReaderHelper.CreateReadIntoClassOrStruct(readerMd, readerPd, readMr, objectVd, setMr, propertyDef.PropertyType);
}
}
//Gets or creates writer method and outputs it. Returns true if method is found or created.
bool GetReadMethod(TypeReference tr, out MethodReference readMr)
{
tr = CodegenSession.ImportReference(tr);
readMr = CodegenSession.ReaderHelper.GetOrCreateFavoredReadMethodReference(tr, true);
return (readMr != null);
}
return true;
}
/// <summary>
/// Creates the stub for a new reader method.
/// </summary>
/// <param name="objectTypeRef"></param>
/// <returns></returns>
private MethodDefinition CreateStaticReaderStubMethodDefinition(TypeReference objectTypeRef, string nameExtension = "")
{
string methodName = $"{READ_PREFIX}{objectTypeRef.FullName}{nameExtension}s";
// create new reader for this type
TypeDefinition readerTypeDef = CodegenSession.GeneralHelper.GetOrCreateClass(out _, GENERATED_TYPE_ATTRIBUTES, GENERATED_READERS_CLASS_NAME, null);
MethodDefinition readerMethodDef = readerTypeDef.AddMethod(methodName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
objectTypeRef);
CodegenSession.GeneralHelper.CreateParameter(readerMethodDef, CodegenSession.ReaderHelper.Reader_TypeRef, "reader");
readerMethodDef.Body.InitLocals = true;
return readerMethodDef;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8afc0f62ceeaee45aa496ba5650d010
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,478 @@
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.CodeGenerating.ILCore;
using FishNet.Connection;
using FishNet.Serializing;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace FishNet.CodeGenerating.Helping
{
internal class ReaderHelper
{
#region Reflection references.
internal TypeReference PooledReader_TypeRef;
internal TypeReference Reader_TypeRef;
internal TypeReference NetworkConnection_TypeRef;
internal MethodReference PooledReader_ReadNetworkBehaviour_MethodRef;
private readonly Dictionary<TypeReference, MethodReference> _instancedReaderMethods = new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
private readonly Dictionary<TypeReference, MethodReference> _staticReaderMethods = new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
private HashSet<TypeReference> _autoPackedMethods = new HashSet<TypeReference>(new TypeReferenceComparer());
private MethodReference Reader_ReadPackedWhole_MethodRef;
internal MethodReference Reader_ReadDictionary_MethodRef;
internal MethodReference Reader_ReadToCollection_MethodRef;
#endregion
#region Const.
internal const string READ_PREFIX = "Read";
/// <summary>
/// Types to exclude from being scanned for auto serialization.
/// </summary>
public static System.Type[] EXCLUDED_AUTO_SERIALIZER_TYPES => WriterHelper.EXCLUDED_AUTO_SERIALIZER_TYPES;
/// <summary>
/// Types to exclude from being scanned for auto serialization.
/// </summary>
public static string[] EXCLUDED_ASSEMBLY_PREFIXES => WriterHelper.EXCLUDED_ASSEMBLY_PREFIXES;
#endregion
/// <summary>
/// Imports references needed by this helper.
/// </summary>
/// <param name="moduleDef"></param>
/// <returns></returns>
internal bool ImportReferences()
{
PooledReader_TypeRef = CodegenSession.ImportReference(typeof(PooledReader));
Reader_TypeRef = CodegenSession.ImportReference(typeof(Reader));
NetworkConnection_TypeRef = CodegenSession.ImportReference(typeof(NetworkConnection));
Type pooledReaderType = typeof(PooledReader);
foreach (MethodInfo methodInfo in pooledReaderType.GetMethods())
{
/* Special methods. */
//ReadPackedWhole.
if (methodInfo.Name == nameof(PooledReader.ReadPackedWhole))
{
Reader_ReadPackedWhole_MethodRef = CodegenSession.ImportReference(methodInfo);
continue;
}
//ReadToCollection.
else if (methodInfo.Name == nameof(PooledReader.ReadArray))
{
Reader_ReadToCollection_MethodRef = CodegenSession.ImportReference(methodInfo);
continue;
}
//ReadDictionary.
else if (methodInfo.Name == nameof(PooledReader.ReadDictionary))
{
Reader_ReadDictionary_MethodRef = CodegenSession.ImportReference(methodInfo);
continue;
}
else if (CodegenSession.GeneralHelper.CodegenExclude(methodInfo))
continue;
//Generic methods are not supported.
else if (methodInfo.IsGenericMethod)
continue;
//Not long enough to be a write method.
else if (methodInfo.Name.Length < READ_PREFIX.Length)
continue;
//Method name doesn't start with writePrefix.
else if (methodInfo.Name.Substring(0, READ_PREFIX.Length) != READ_PREFIX)
continue;
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
//Can have at most one parameter for packing.
if (parameterInfos.Length > 1)
continue;
//If has one parameter make sure it's a packing type.
bool autoPackMethod = false;
if (parameterInfos.Length == 1)
{
autoPackMethod = (parameterInfos[0].ParameterType == typeof(AutoPackType));
if (!autoPackMethod)
continue;
}
/* TypeReference for the return type
* of the read method. */
TypeReference typeRef = CodegenSession.ImportReference(methodInfo.ReturnType);
MethodReference methodRef = CodegenSession.ImportReference(methodInfo);
/* If here all checks pass. */
AddReaderMethod(typeRef, methodRef, true, true);
if (autoPackMethod)
_autoPackedMethods.Add(typeRef);
}
Type readerExtensionsType = typeof(ReaderExtensions);
foreach (MethodInfo methodInfo in readerExtensionsType.GetMethods())
{
if (CodegenSession.GeneralHelper.CodegenExclude(methodInfo))
continue;
//Generic methods are not supported.
if (methodInfo.IsGenericMethod)
continue;
//Not static.
if (!methodInfo.IsStatic)
continue;
//Not long enough to be a write method.
if (methodInfo.Name.Length < READ_PREFIX.Length)
continue;
//Method name doesn't start with writePrefix.
if (methodInfo.Name.Substring(0, READ_PREFIX.Length) != READ_PREFIX)
continue;
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
//Can have at most one parameter for packing.
if (parameterInfos.Length > 2)
continue;
//If has 2 parameters make sure it's a packing type.
bool autoPackMethod = false;
if (parameterInfos.Length == 2)
{
autoPackMethod = (parameterInfos[1].ParameterType == typeof(AutoPackType));
if (!autoPackMethod)
continue;
}
/* TypeReference for the return type
* of the read method. */
TypeReference typeRef = CodegenSession.ImportReference(methodInfo.ReturnType);
MethodReference methodRef = CodegenSession.ImportReference(methodInfo);
/* If here all checks pass. */
AddReaderMethod(typeRef, methodRef, false, true);
}
return true;
}
/// <summary>
/// Creates generic write delegates for all currently known write types.
/// </summary>
internal bool CreateGenericDelegates()
{
bool modified = false;
/* Only write statics. This will include extensions and generated. */
foreach (KeyValuePair<TypeReference, MethodReference> item in _staticReaderMethods)
{
if (FishNetILPP.CODEGEN_THIS_NAMESPACE.Length == 0 || item.Key.FullName.Contains(FishNetILPP.CODEGEN_THIS_NAMESPACE))
{
CodegenSession.GenericReaderHelper.CreateReadDelegate(item.Value);
modified = true;
}
}
return modified;
}
/// <summary>
/// Returns if typeRef has a deserializer.
/// </summary>
/// <param name="typeRef"></param>
/// <param name="createMissing"></param>
/// <returns></returns>
internal bool HasDeserializer(TypeReference typeRef, bool createMissing)
{
bool result = (GetInstancedReadMethodReference(typeRef) != null) ||
(GetStaticReadMethodReference(typeRef) != null);
if (!result && createMissing)
{
if (!CodegenSession.GeneralHelper.HasNonSerializableAttribute(typeRef.CachedResolve()))
{
MethodReference methodRef = CodegenSession.ReaderGenerator.CreateReader(typeRef);
result = (methodRef != null);
}
}
return result;
}
/// <summary>
/// Returns if typeRef supports auto packing.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal bool IsAutoPackedType(TypeReference typeRef)
{
return _autoPackedMethods.Contains(typeRef);
}
/// <summary>
/// Creates a null check on the first argument and returns a null object if result indicates to do so.
/// </summary>
internal void CreateRetOnNull(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition resultVariableDef, bool useBool)
{
Instruction endIf = processor.Create(OpCodes.Nop);
if (useBool)
CreateReadBool(processor, readerParameterDef, resultVariableDef);
else
CreateReadPackedWhole(processor, readerParameterDef, resultVariableDef);
//If (true or == -1) jmp to endIf. True is null.
processor.Emit(OpCodes.Ldloc, resultVariableDef);
if (useBool)
{
processor.Emit(OpCodes.Brfalse, endIf);
}
else
{
//-1
processor.Emit(OpCodes.Ldc_I4_M1);
processor.Emit(OpCodes.Bne_Un_S, endIf);
}
//Insert null.
processor.Emit(OpCodes.Ldnull);
//Exit method.
processor.Emit(OpCodes.Ret);
//End of if check.
processor.Append(endIf);
}
/// <summary>
/// Creates a call to WriteBoolean with value.
/// </summary>
/// <param name="processor"></param>
/// <param name="writerParameterDef"></param>
/// <param name="value"></param>
internal void CreateReadBool(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition localBoolVariableDef)
{
MethodReference readBoolMethodRef = GetFavoredReadMethodReference(CodegenSession.GeneralHelper.GetTypeReference(typeof(bool)), true);
processor.Emit(OpCodes.Ldarg, readerParameterDef);
processor.Emit(OpCodes.Callvirt, readBoolMethodRef);
processor.Emit(OpCodes.Stloc, localBoolVariableDef);
}
/// <summary>
/// Creates a call to WritePackWhole with value.
/// </summary>
/// <param name="processor"></param>
/// <param name="value"></param>
internal void CreateReadPackedWhole(ILProcessor processor, ParameterDefinition readerParameterDef, VariableDefinition resultVariableDef)
{
//Reader.
processor.Emit(OpCodes.Ldarg, readerParameterDef);
//Reader.ReadPackedWhole().
processor.Emit(OpCodes.Callvirt, Reader_ReadPackedWhole_MethodRef);
processor.Emit(OpCodes.Conv_I4);
processor.Emit(OpCodes.Stloc, resultVariableDef);
}
#region GetReaderMethodReference.
/// <summary>
/// Returns the MethodReference for typeRef.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal MethodReference GetInstancedReadMethodReference(TypeReference typeRef)
{
_instancedReaderMethods.TryGetValue(typeRef, out MethodReference methodRef);
return methodRef;
}
/// <summary>
/// Returns the MethodReference for typeRef.
/// </summary>
/// <param name="typeRef"></param>
/// <returns></returns>
internal MethodReference GetStaticReadMethodReference(TypeReference typeRef)
{
_staticReaderMethods.TryGetValue(typeRef, out MethodReference methodRef);
return methodRef;
}
/// <summary>
/// Returns the MethodReference for typeRef favoring instanced or static. Returns null if not found.
/// </summary>
/// <param name="typeRef"></param>
/// <param name="favorInstanced"></param>
/// <returns></returns>
internal MethodReference GetFavoredReadMethodReference(TypeReference typeRef, bool favorInstanced)
{
MethodReference result;
if (favorInstanced)
{
result = GetInstancedReadMethodReference(typeRef);
if (result == null)
result = GetStaticReadMethodReference(typeRef);
}
else
{
result = GetStaticReadMethodReference(typeRef);
if (result == null)
result = GetInstancedReadMethodReference(typeRef);
}
return result;
}
/// <summary>
/// Returns the MethodReference for typeRef favoring instanced or static.
/// </summary>
/// <param name="typeRef"></param>
/// <param name="favorInstanced"></param>
/// <returns></returns>
internal MethodReference GetOrCreateFavoredReadMethodReference(TypeReference typeRef, bool favorInstanced)
{
//Try to get existing writer, if not present make one.
MethodReference readMethodRef = GetFavoredReadMethodReference(typeRef, favorInstanced);
if (readMethodRef == null)
readMethodRef = CodegenSession.ReaderGenerator.CreateReader(typeRef);
if (readMethodRef == null)
CodegenSession.LogError($"Could not create deserializer for {typeRef.FullName}.");
return readMethodRef;
}
#endregion
/// <summary>
/// Adds typeRef, methodDef to instanced or readerMethods.
/// </summary>
/// <param name="typeRef"></param>
/// <param name="methodRef"></param>
/// <param name="useAdd"></param>
internal void AddReaderMethod(TypeReference typeRef, MethodReference methodRef, bool instanced, bool useAdd)
{
Dictionary<TypeReference, MethodReference> dict = (instanced) ?
_instancedReaderMethods : _staticReaderMethods;
if (useAdd)
dict.Add(typeRef, methodRef);
else
dict[typeRef] = methodRef;
}
/// <summary>
/// Removes typeRef from static/instanced reader methods.
/// </summary>
internal void RemoveReaderMethod(TypeReference typeRef, bool instanced)
{
Dictionary<TypeReference, MethodReference> dict = (instanced) ?
_instancedReaderMethods : _staticReaderMethods;
dict.Remove(typeRef);
}
/// <summary>
/// Creates read instructions returning instructions and outputing variable of read result.
/// </summary>
/// <param name="processor"></param>
/// <param name="methodDef"></param>
/// <param name="readerParameterDef"></param>
/// <param name="readTypeRef"></param>
/// <param name="diagnostics"></param>
/// <returns></returns>
internal List<Instruction> CreateRead(MethodDefinition methodDef, ParameterDefinition readerParameterDef, TypeReference readTypeRef, out VariableDefinition createdVariableDef)
{
ILProcessor processor = methodDef.Body.GetILProcessor();
List<Instruction> insts = new List<Instruction>();
MethodReference readerMethodRef = GetFavoredReadMethodReference(readTypeRef, true);
if (readerMethodRef != null)
{
//Make a local variable.
createdVariableDef = CodegenSession.GeneralHelper.CreateVariable(methodDef, readTypeRef);
//pooledReader.ReadBool();
insts.Add(processor.Create(OpCodes.Ldarg, readerParameterDef));
//If an auto pack method then insert default value.
if (_autoPackedMethods.Contains(readTypeRef))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(readTypeRef);
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)packType));
}
insts.Add(processor.Create(OpCodes.Call, readerMethodRef));
//Store into local variable.
insts.Add(processor.Create(OpCodes.Stloc, createdVariableDef));
return insts;
}
else
{
CodegenSession.LogError("Reader not found for " + readTypeRef.ToString());
createdVariableDef = null;
return null;
}
}
/// <summary>
/// Creates a read for fieldRef and populates it into a created variable of class or struct type.
/// </summary>
internal bool CreateReadIntoClassOrStruct(MethodDefinition readerMd, ParameterDefinition readerPd, MethodReference readMr, VariableDefinition objectVd, FieldReference valueFr)
{
if (readMr != null)
{
ILProcessor processor = readerMd.Body.GetILProcessor();
/* How to load object instance. If it's a structure
* then it must be loaded by address. Otherwise if
* class Ldloc can be used. */
OpCode loadOpCode = (objectVd.VariableType.IsValueType) ?
OpCodes.Ldloca : OpCodes.Ldloc;
processor.Emit(loadOpCode, objectVd);
//reader.
processor.Emit(OpCodes.Ldarg, readerPd);
if (IsAutoPackedType(valueFr.FieldType))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(valueFr.FieldType);
processor.Emit(OpCodes.Ldc_I4, (int)packType);
}
//reader.ReadXXXX().
processor.Emit(OpCodes.Call, readMr);
//obj.Field = result / reader.ReadXXXX().
processor.Emit(OpCodes.Stfld, valueFr);
return true;
}
else
{
CodegenSession.LogError($"Reader not found for {valueFr.FullName}.");
return false;
}
}
/// <summary>
/// Creates a read for fieldRef and populates it into a created variable of class or struct type.
/// </summary>
internal bool CreateReadIntoClassOrStruct(MethodDefinition methodDef, ParameterDefinition readerPd, MethodReference readMr, VariableDefinition objectVariableDef, MethodReference setMr, TypeReference readTr)
{
if (readMr != null)
{
ILProcessor processor = methodDef.Body.GetILProcessor();
/* How to load object instance. If it's a structure
* then it must be loaded by address. Otherwise if
* class Ldloc can be used. */
OpCode loadOpCode = (objectVariableDef.VariableType.IsValueType) ?
OpCodes.Ldloca : OpCodes.Ldloc;
processor.Emit(loadOpCode, objectVariableDef);
//reader.
processor.Emit(OpCodes.Ldarg, readerPd);
if (IsAutoPackedType(readTr))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(readTr);
processor.Emit(OpCodes.Ldc_I4, (int)packType);
}
//reader.ReadXXXX().
processor.Emit(OpCodes.Call, readMr);
//obj.Property = result / reader.ReadXXXX().
processor.Emit(OpCodes.Call, setMr);
return true;
}
else
{
CodegenSession.LogError($"Reader not found for {readTr.FullName}.");
return false;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 350861dcbcbabc447acd37e4310b0697
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show more