Added multiplayer plugin

This commit is contained in:
Anthony Berg
2020-11-30 08:12:07 +00:00
parent 9cb342dd42
commit f64cf54803
450 changed files with 33131 additions and 10 deletions

View File

@@ -0,0 +1,74 @@
// Unity 2019.3 has an experimental 'disable domain reload on play'
// feature. keeping any global state between sessions will break
// Mirror and most of our user's projects. don't allow it for now.
// https://blogs.unity3d.com/2019/11/05/enter-play-mode-faster-in-unity-2019-3/
using UnityEditor;
using UnityEngine;
namespace Mirror
{
public class EnterPlayModeSettingsCheck : MonoBehaviour
{
[InitializeOnLoadMethod]
static void OnInitializeOnLoad()
{
#if UNITY_2019_3_OR_NEWER
// We can't support experimental "Enter Play Mode Options" mode
// Check immediately on load, and before entering play mode, and warn the user
CheckPlayModeOptions();
#endif
// Hook this event to see if we have a good weave every time
// user attempts to enter play mode or tries to do a build
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
static void OnPlayModeStateChanged(PlayModeStateChange state)
{
// Per Unity docs, this fires "when exiting edit mode before the Editor is in play mode".
// This doesn't fire when closing the editor.
if (state == PlayModeStateChange.ExitingEditMode)
{
CheckSuccessfulWeave();
#if UNITY_2019_3_OR_NEWER
// We can't support experimental "Enter Play Mode Options" mode
// Check and prevent entering play mode if enabled
CheckPlayModeOptions();
#endif
}
}
static void CheckSuccessfulWeave()
{
// Check if last weave result was successful
if (!SessionState.GetBool("MIRROR_WEAVE_SUCCESS", false))
{
// Last weave result was a failure...try to weave again
// Faults will show in the console that may have been cleared by "Clear on Play"
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", true);
Weaver.CompilationFinishedHook.WeaveExistingAssemblies();
// Did that clear things up for us?
if (!SessionState.GetBool("MIRROR_WEAVE_SUCCESS", false))
{
// Nope, still failed, and console has the issues logged
Debug.LogError("Can't enter play mode until weaver issues are resolved.");
EditorApplication.isPlaying = false;
}
}
}
#if UNITY_2019_3_OR_NEWER
static void CheckPlayModeOptions()
{
// enabling the checkbox is enough. it controls all the other settings.
if (EditorSettings.enterPlayModeOptionsEnabled)
{
Debug.LogError("Enter Play Mode Options are not supported by Mirror. Please disable 'ProjectSettings -> Editor -> Enter Play Mode Settings (Experimental)'.");
EditorApplication.isPlaying = false;
}
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace Mirror
{
public static class InspectorHelper
{
/// <summary>
/// Gets all public and private fields for a type
/// </summary>
/// <param name="type"></param>
/// <param name="deepestBaseType">Stops at this base type (exclusive)</param>
/// <returns></returns>
public static IEnumerable<FieldInfo> GetAllFields(Type type, Type deepestBaseType)
{
const BindingFlags publicFields = BindingFlags.Public | BindingFlags.Instance;
const BindingFlags privateFields = BindingFlags.NonPublic | BindingFlags.Instance;
// get public fields (includes fields from base type)
FieldInfo[] allPublicFields = type.GetFields(publicFields);
foreach (FieldInfo field in allPublicFields)
{
yield return field;
}
// get private fields in current type, then move to base type
while (type != null)
{
FieldInfo[] allPrivateFields = type.GetFields(privateFields);
foreach (FieldInfo field in allPrivateFields)
{
yield return field;
}
type = type.BaseType;
// stop early
if (type == deepestBaseType)
{
break;
}
}
}
public static bool IsSyncVar(this FieldInfo field)
{
object[] fieldMarkers = field.GetCustomAttributes(typeof(SyncVarAttribute), true);
return fieldMarkers.Length > 0;
}
public static bool IsSerializeField(this FieldInfo field)
{
object[] fieldMarkers = field.GetCustomAttributes(typeof(SerializeField), true);
return fieldMarkers.Length > 0;
}
public static bool IsVisibleField(this FieldInfo field)
{
return field.IsPublic || IsSerializeField(field);
}
public static bool IsSyncObject(this FieldInfo field)
{
return typeof(SyncObject).IsAssignableFrom(field.FieldType);
}
public static bool HasShowInInspector(this FieldInfo field)
{
object[] fieldMarkers = field.GetCustomAttributes(typeof(ShowInInspectorAttribute), true);
return fieldMarkers.Length > 0;
}
public static bool IsVisibleSyncObject(this FieldInfo field)
{
return field.IsPublic || HasShowInInspector(field);
}
}
}

View File

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

View File

@@ -0,0 +1 @@
// File moved to Mirror/Editor/Logging/LogLevelWindow.cs

View File

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

View File

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

View File

@@ -0,0 +1,70 @@
using Mirror.Logging;
using UnityEditor;
using UnityEngine;
namespace Mirror.EditorScripts.Logging
{
public class LogLevelWindow : EditorWindow
{
[Header("Log Settings Asset")]
[SerializeField] LogSettings settings = null;
SerializedObject serializedObject;
SerializedProperty settingsProp;
Vector2 dictionaryScrollPosition;
void OnEnable()
{
serializedObject = new SerializedObject(this);
settingsProp = serializedObject.FindProperty(nameof(settings));
LogSettings existingSettings = EditorLogSettingsLoader.FindLogSettings();
if (existingSettings != null)
{
settingsProp.objectReferenceValue = existingSettings;
serializedObject.ApplyModifiedProperties();
}
}
void OnGUI()
{
using (EditorGUILayout.ScrollViewScope scrollScope = new EditorGUILayout.ScrollViewScope(dictionaryScrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar))
{
dictionaryScrollPosition = scrollScope.scrollPosition;
using (new EditorGUILayout.VerticalScope())
{
using (new EditorGUILayout.VerticalScope())
{
serializedObject.Update();
EditorGUILayout.PropertyField(settingsProp);
serializedObject.ApplyModifiedProperties();
if (settings == null)
{
LogSettings newSettings = LogLevelsGUI.DrawCreateNewButton();
if (newSettings != null)
{
settingsProp.objectReferenceValue = newSettings;
serializedObject.ApplyModifiedProperties();
}
}
else
{
LogLevelsGUI.DrawLogFactoryDictionary(settings);
}
}
}
}
}
[MenuItem("Window/Analysis/Mirror Log Levels", priority = 20002)]
public static void ShowWindow()
{
LogLevelWindow window = GetWindow<LogLevelWindow>();
window.minSize = new Vector2(200, 100);
window.titleContent = new GUIContent("Mirror Log Levels");
window.Show();
}
}
}

View File

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

View File

@@ -0,0 +1,61 @@
using System.Collections.Generic;
using Mirror.Logging;
using UnityEditor;
using UnityEngine;
namespace Mirror.EditorScripts.Logging
{
public static class LogLevelsGUI
{
public static LogSettings DrawCreateNewButton()
{
if (GUILayout.Button("Create New"))
{
return ScriptableObjectUtility.CreateAsset<LogSettings>(nameof(LogSettings));
}
return null;
}
public static void DrawLogFactoryDictionary(LogSettings settings)
{
using (EditorGUI.ChangeCheckScope scope = new EditorGUI.ChangeCheckScope())
{
if (LogFactory.loggers.Count == 0)
{
EditorGUILayout.LabelField("No Keys found in LogFactory.loggers\nPlay the game for default log values to be added to LogFactory", EditorStyles.wordWrappedLabel);
}
else
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Logging Components", EditorStyles.boldLabel);
foreach (KeyValuePair<string, ILogger> item in LogFactory.loggers)
{
DrawLoggerField(item);
}
if (scope.changed)
{
settings.SaveFromDictionary(LogFactory.loggers);
}
}
}
}
static void DrawLoggerField(KeyValuePair<string, ILogger> item)
{
ILogger logger = item.Value;
string name = item.Key;
const float fieldWidth = 100f;
const float inspectorMargin = 25f;
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField(new GUIContent(ObjectNames.NicifyVariableName(name)), GUILayout.MaxWidth(EditorGUIUtility.currentViewWidth - fieldWidth - inspectorMargin));
logger.filterLogType = (LogType)EditorGUILayout.EnumPopup(logger.filterLogType, GUILayout.Width(fieldWidth));
}
}
}
}

View File

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

View File

@@ -0,0 +1,24 @@
using Mirror.Logging;
using UnityEditor;
using UnityEngine;
namespace Mirror.EditorScripts.Logging
{
[CustomEditor(typeof(LogSettings))]
public class LogSettingsEditor : Editor
{
public override void OnInspectorGUI()
{
CurrentScriptField();
LogLevelsGUI.DrawLogFactoryDictionary(target as LogSettings);
}
public void CurrentScriptField()
{
GUI.enabled = false;
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));
GUI.enabled = true;
}
}
}

View File

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

View File

@@ -0,0 +1,31 @@
using Mirror.Logging;
using UnityEditor;
namespace Mirror.EditorScripts.Logging
{
[CustomEditor(typeof(NetworkLogSettings))]
public class NetworkLogSettingsEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
NetworkLogSettings target = this.target as NetworkLogSettings;
if (target.settings == null)
{
LogSettings newSettings = LogLevelsGUI.DrawCreateNewButton();
if (newSettings != null)
{
SerializedProperty settingsProp = serializedObject.FindProperty("settings");
settingsProp.objectReferenceValue = newSettings;
serializedObject.ApplyModifiedProperties();
}
}
else
{
LogLevelsGUI.DrawLogFactoryDictionary(target.settings);
}
}
}
}

View File

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

View File

@@ -0,0 +1,17 @@
{
"name": "Mirror.Editor",
"references": [
"Mirror",
"Mirror.Weaver"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1c7c33eb5480dd24c9e29a8250c1a775
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,186 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkBehaviour), true)]
[CanEditMultipleObjects]
public class NetworkBehaviourInspector : Editor
{
/// <summary>
/// List of all visible syncVars in target class
/// </summary>
protected List<string> syncVarNames = new List<string>();
bool syncsAnything;
SyncListDrawer syncListDrawer;
// does this type sync anything? otherwise we don't need to show syncInterval
bool SyncsAnything(Type scriptClass)
{
// check for all SyncVar fields, they don't have to be visible
foreach (FieldInfo field in InspectorHelper.GetAllFields(scriptClass, typeof(NetworkBehaviour)))
{
if (field.IsSyncVar())
{
return true;
}
}
// has OnSerialize that is not in NetworkBehaviour?
// then it either has a syncvar or custom OnSerialize. either way
// this means we have something to sync.
MethodInfo method = scriptClass.GetMethod("OnSerialize");
if (method != null && method.DeclaringType != typeof(NetworkBehaviour))
{
return true;
}
// SyncObjects are serialized in NetworkBehaviour.OnSerialize, which
// is always there even if we don't use SyncObjects. so we need to
// search for SyncObjects manually.
// Any SyncObject should be added to syncObjects when unity creates an
// object so we can cheeck length of list so see if sync objects exists
FieldInfo syncObjectsField = scriptClass.GetField("syncObjects", BindingFlags.NonPublic | BindingFlags.Instance);
List<SyncObject> syncObjects = (List<SyncObject>)syncObjectsField.GetValue(serializedObject.targetObject);
return syncObjects.Count > 0;
}
void OnEnable()
{
if (target == null) { Debug.LogWarning("NetworkBehaviourInspector had no target object"); return; }
// If target's base class is changed from NetworkBehaviour to MonoBehaviour
// then Unity temporarily keep using this Inspector causing things to break
if (!(target is NetworkBehaviour)) { return; }
Type scriptClass = target.GetType();
syncVarNames = new List<string>();
foreach (FieldInfo field in InspectorHelper.GetAllFields(scriptClass, typeof(NetworkBehaviour)))
{
if (field.IsSyncVar() && field.IsVisibleField())
{
syncVarNames.Add(field.Name);
}
}
syncListDrawer = new SyncListDrawer(serializedObject.targetObject);
syncsAnything = SyncsAnything(scriptClass);
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
DrawDefaultSyncLists();
DrawDefaultSyncSettings();
}
/// <summary>
/// Draws Sync Objects that are IEnumerable
/// </summary>
protected void DrawDefaultSyncLists()
{
// Need this check incase OnEnable returns early
if (syncListDrawer == null) { return; }
syncListDrawer.Draw();
}
/// <summary>
/// Draws SyncSettings if the NetworkBehaviour has anything to sync
/// </summary>
protected void DrawDefaultSyncSettings()
{
// does it sync anything? then show extra properties
// (no need to show it if the class only has Cmds/Rpcs and no sync)
if (!syncsAnything)
{
return;
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Sync Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncMode"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
// apply
serializedObject.ApplyModifiedProperties();
}
}
public class SyncListDrawer
{
readonly UnityEngine.Object targetObject;
readonly List<SyncListField> syncListFields;
public SyncListDrawer(UnityEngine.Object targetObject)
{
this.targetObject = targetObject;
syncListFields = new List<SyncListField>();
foreach (FieldInfo field in InspectorHelper.GetAllFields(targetObject.GetType(), typeof(NetworkBehaviour)))
{
if (field.IsSyncObject() && field.IsVisibleSyncObject())
{
syncListFields.Add(new SyncListField(field));
}
}
}
public void Draw()
{
if (syncListFields.Count == 0) { return; }
EditorGUILayout.Space();
EditorGUILayout.LabelField("Sync Lists", EditorStyles.boldLabel);
for (int i = 0; i < syncListFields.Count; i++)
{
DrawSyncList(syncListFields[i]);
}
}
void DrawSyncList(SyncListField syncListField)
{
syncListField.visible = EditorGUILayout.Foldout(syncListField.visible, syncListField.label);
if (syncListField.visible)
{
using (new EditorGUI.IndentLevelScope())
{
object fieldValue = syncListField.field.GetValue(targetObject);
if (fieldValue is IEnumerable synclist)
{
int index = 0;
foreach (object item in synclist)
{
string itemValue = item != null ? item.ToString() : "NULL";
string itemLabel = "Element " + index;
EditorGUILayout.LabelField(itemLabel, itemValue);
index++;
}
}
}
}
}
class SyncListField
{
public bool visible;
public readonly FieldInfo field;
public readonly string label;
public SyncListField(FieldInfo field)
{
this.field = field;
visible = false;
label = field.Name + " [" + field.FieldType.Name + "]";
}
}
}
} //namespace

View File

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

View File

@@ -0,0 +1,305 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomPreview(typeof(GameObject))]
class NetworkInformationPreview : ObjectPreview
{
struct NetworkIdentityInfo
{
public GUIContent name;
public GUIContent value;
}
struct NetworkBehaviourInfo
{
// This is here just so we can check if it's enabled/disabled
public NetworkBehaviour behaviour;
public GUIContent name;
}
class Styles
{
public GUIStyle labelStyle = new GUIStyle(EditorStyles.label);
public GUIStyle componentName = new GUIStyle(EditorStyles.boldLabel);
public GUIStyle disabledName = new GUIStyle(EditorStyles.miniLabel);
public Styles()
{
Color fontColor = new Color(0.7f, 0.7f, 0.7f);
labelStyle.padding.right += 20;
labelStyle.normal.textColor = fontColor;
labelStyle.active.textColor = fontColor;
labelStyle.focused.textColor = fontColor;
labelStyle.hover.textColor = fontColor;
labelStyle.onNormal.textColor = fontColor;
labelStyle.onActive.textColor = fontColor;
labelStyle.onFocused.textColor = fontColor;
labelStyle.onHover.textColor = fontColor;
componentName.normal.textColor = fontColor;
componentName.active.textColor = fontColor;
componentName.focused.textColor = fontColor;
componentName.hover.textColor = fontColor;
componentName.onNormal.textColor = fontColor;
componentName.onActive.textColor = fontColor;
componentName.onFocused.textColor = fontColor;
componentName.onHover.textColor = fontColor;
disabledName.normal.textColor = fontColor;
disabledName.active.textColor = fontColor;
disabledName.focused.textColor = fontColor;
disabledName.hover.textColor = fontColor;
disabledName.onNormal.textColor = fontColor;
disabledName.onActive.textColor = fontColor;
disabledName.onFocused.textColor = fontColor;
disabledName.onHover.textColor = fontColor;
}
}
GUIContent title;
Styles styles = new Styles();
public override GUIContent GetPreviewTitle()
{
if (title == null)
{
title = new GUIContent("Network Information");
}
return title;
}
public override bool HasPreviewGUI()
{
// need to check if target is null to stop MissingReferenceException
return target != null && target is GameObject gameObject && gameObject.GetComponent<NetworkIdentity>() != null;
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
if (Event.current.type != EventType.Repaint)
return;
if (target == null)
return;
GameObject targetGameObject = target as GameObject;
if (targetGameObject == null)
return;
NetworkIdentity identity = targetGameObject.GetComponent<NetworkIdentity>();
if (identity == null)
return;
if (styles == null)
styles = new Styles();
// padding
RectOffset previewPadding = new RectOffset(-5, -5, -5, -5);
Rect paddedr = previewPadding.Add(r);
//Centering
float initialX = paddedr.x + 10;
float Y = paddedr.y + 10;
Y = DrawNetworkIdentityInfo(identity, initialX, Y);
Y = DrawNetworkBehaviors(identity, initialX, Y);
Y = DrawObservers(identity, initialX, Y);
_ = DrawOwner(identity, initialX, Y);
}
float DrawNetworkIdentityInfo(NetworkIdentity identity, float initialX, float Y)
{
IEnumerable<NetworkIdentityInfo> infos = GetNetworkIdentityInfo(identity);
// Get required label size for the names of the information values we're going to show
// There are two columns, one with label for the name of the info and the next for the value
Vector2 maxNameLabelSize = new Vector2(140, 16);
Vector2 maxValueLabelSize = GetMaxNameLabelSize(infos);
Rect labelRect = new Rect(initialX, Y, maxNameLabelSize.x, maxNameLabelSize.y);
Rect idLabelRect = new Rect(maxNameLabelSize.x, Y, maxValueLabelSize.x, maxValueLabelSize.y);
foreach (NetworkIdentityInfo info in infos)
{
GUI.Label(labelRect, info.name, styles.labelStyle);
GUI.Label(idLabelRect, info.value, styles.componentName);
labelRect.y += labelRect.height;
labelRect.x = initialX;
idLabelRect.y += idLabelRect.height;
}
return labelRect.y;
}
float DrawNetworkBehaviors(NetworkIdentity identity, float initialX, float Y)
{
IEnumerable<NetworkBehaviourInfo> behavioursInfo = GetNetworkBehaviorInfo(identity);
// Show behaviours list in a different way than the name/value pairs above
Vector2 maxBehaviourLabelSize = GetMaxBehaviourLabelSize(behavioursInfo);
Rect behaviourRect = new Rect(initialX, Y + 10, maxBehaviourLabelSize.x, maxBehaviourLabelSize.y);
GUI.Label(behaviourRect, new GUIContent("Network Behaviours"), styles.labelStyle);
// indent names
behaviourRect.x += 20;
behaviourRect.y += behaviourRect.height;
foreach (NetworkBehaviourInfo info in behavioursInfo)
{
if (info.behaviour == null)
{
// could be the case in the editor after existing play mode.
continue;
}
GUI.Label(behaviourRect, info.name, info.behaviour.enabled ? styles.componentName : styles.disabledName);
behaviourRect.y += behaviourRect.height;
Y = behaviourRect.y;
}
return Y;
}
float DrawObservers(NetworkIdentity identity, float initialX, float Y)
{
if (identity.observers != null && identity.observers.Count > 0)
{
Rect observerRect = new Rect(initialX, Y + 10, 200, 20);
GUI.Label(observerRect, new GUIContent("Network observers"), styles.labelStyle);
// indent names
observerRect.x += 20;
observerRect.y += observerRect.height;
foreach (KeyValuePair<int, NetworkConnection> kvp in identity.observers)
{
GUI.Label(observerRect, kvp.Value.address + ":" + kvp.Value, styles.componentName);
observerRect.y += observerRect.height;
Y = observerRect.y;
}
}
return Y;
}
float DrawOwner(NetworkIdentity identity, float initialX, float Y)
{
if (identity.connectionToClient != null)
{
Rect ownerRect = new Rect(initialX, Y + 10, 400, 20);
GUI.Label(ownerRect, new GUIContent("Client Authority: " + identity.connectionToClient), styles.labelStyle);
Y += ownerRect.height;
}
return Y;
}
// Get the maximum size used by the value of information items
Vector2 GetMaxNameLabelSize(IEnumerable<NetworkIdentityInfo> infos)
{
Vector2 maxLabelSize = Vector2.zero;
foreach (NetworkIdentityInfo info in infos)
{
Vector2 labelSize = styles.labelStyle.CalcSize(info.value);
if (maxLabelSize.x < labelSize.x)
{
maxLabelSize.x = labelSize.x;
}
if (maxLabelSize.y < labelSize.y)
{
maxLabelSize.y = labelSize.y;
}
}
return maxLabelSize;
}
Vector2 GetMaxBehaviourLabelSize(IEnumerable<NetworkBehaviourInfo> behavioursInfo)
{
Vector2 maxLabelSize = Vector2.zero;
foreach (NetworkBehaviourInfo behaviour in behavioursInfo)
{
Vector2 labelSize = styles.labelStyle.CalcSize(behaviour.name);
if (maxLabelSize.x < labelSize.x)
{
maxLabelSize.x = labelSize.x;
}
if (maxLabelSize.y < labelSize.y)
{
maxLabelSize.y = labelSize.y;
}
}
return maxLabelSize;
}
IEnumerable<NetworkIdentityInfo> GetNetworkIdentityInfo(NetworkIdentity identity)
{
List<NetworkIdentityInfo> infos = new List<NetworkIdentityInfo>
{
GetAssetId(identity),
GetString("Scene ID", identity.sceneId.ToString("X"))
};
if (Application.isPlaying)
{
infos.Add(GetString("Network ID", identity.netId.ToString()));
infos.Add(GetBoolean("Is Client", identity.isClient));
infos.Add(GetBoolean("Is Server", identity.isServer));
infos.Add(GetBoolean("Has Authority", identity.hasAuthority));
infos.Add(GetBoolean("Is Local Player", identity.isLocalPlayer));
}
return infos;
}
IEnumerable<NetworkBehaviourInfo> GetNetworkBehaviorInfo(NetworkIdentity identity)
{
List<NetworkBehaviourInfo> behaviourInfos = new List<NetworkBehaviourInfo>();
NetworkBehaviour[] behaviours = identity.GetComponents<NetworkBehaviour>();
foreach (NetworkBehaviour behaviour in behaviours)
{
behaviourInfos.Add(new NetworkBehaviourInfo
{
name = new GUIContent(behaviour.GetType().FullName),
behaviour = behaviour
});
}
return behaviourInfos;
}
NetworkIdentityInfo GetAssetId(NetworkIdentity identity)
{
string assetId = identity.assetId.ToString();
if (string.IsNullOrEmpty(assetId))
{
assetId = "<object has no prefab>";
}
return GetString("Asset ID", assetId);
}
static NetworkIdentityInfo GetString(string name, string value)
{
return new NetworkIdentityInfo
{
name = new GUIContent(name),
value = new GUIContent(value)
};
}
static NetworkIdentityInfo GetBoolean(string name, bool value)
{
return new NetworkIdentityInfo
{
name = new GUIContent(name),
value = new GUIContent((value ? "Yes" : "No"))
};
}
}
}

View File

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

View File

@@ -0,0 +1,113 @@
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkManager), true)]
[CanEditMultipleObjects]
public class NetworkManagerEditor : Editor
{
SerializedProperty spawnListProperty;
ReorderableList spawnList;
protected NetworkManager networkManager;
protected void Init()
{
if (spawnList == null)
{
networkManager = target as NetworkManager;
spawnListProperty = serializedObject.FindProperty("spawnPrefabs");
spawnList = new ReorderableList(serializedObject, spawnListProperty)
{
drawHeaderCallback = DrawHeader,
drawElementCallback = DrawChild,
onReorderCallback = Changed,
onRemoveCallback = RemoveButton,
onChangedCallback = Changed,
onAddCallback = AddButton,
// this uses a 16x16 icon. other sizes make it stretch.
elementHeight = 16
};
}
}
public override void OnInspectorGUI()
{
Init();
DrawDefaultInspector();
EditorGUI.BeginChangeCheck();
spawnList.DoLayoutList();
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
static void DrawHeader(Rect headerRect)
{
GUI.Label(headerRect, "Registered Spawnable Prefabs:");
}
internal void DrawChild(Rect r, int index, bool isActive, bool isFocused)
{
SerializedProperty prefab = spawnListProperty.GetArrayElementAtIndex(index);
GameObject go = (GameObject)prefab.objectReferenceValue;
GUIContent label;
if (go == null)
{
label = new GUIContent("Empty", "Drag a prefab with a NetworkIdentity here");
}
else
{
NetworkIdentity identity = go.GetComponent<NetworkIdentity>();
label = new GUIContent(go.name, identity != null ? "AssetId: [" + identity.assetId + "]" : "No Network Identity");
}
GameObject newGameObject = (GameObject)EditorGUI.ObjectField(r, label, go, typeof(GameObject), false);
if (newGameObject != go)
{
if (newGameObject != null && !newGameObject.GetComponent<NetworkIdentity>())
{
Debug.LogError("Prefab " + newGameObject + " cannot be added as spawnable as it doesn't have a NetworkIdentity.");
return;
}
prefab.objectReferenceValue = newGameObject;
}
}
internal void Changed(ReorderableList list)
{
EditorUtility.SetDirty(target);
}
internal void AddButton(ReorderableList list)
{
spawnListProperty.arraySize += 1;
list.index = spawnListProperty.arraySize - 1;
SerializedProperty obj = spawnListProperty.GetArrayElementAtIndex(spawnListProperty.arraySize - 1);
obj.objectReferenceValue = null;
spawnList.index = spawnList.count - 1;
Changed(list);
}
internal void RemoveButton(ReorderableList list)
{
spawnListProperty.DeleteArrayElementAtIndex(spawnList.index);
if (list.index >= spawnListProperty.arraySize)
{
list.index = spawnListProperty.arraySize - 1;
}
}
}
}

View File

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

View File

@@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
namespace Mirror
{
public class NetworkScenePostProcess : MonoBehaviour
{
[PostProcessScene]
public static void OnPostProcessScene()
{
// find all NetworkIdentities in all scenes
// => can't limit it to GetActiveScene() because that wouldn't work
// for additive scene loads (the additively loaded scene is never
// the active scene)
// => ignore DontDestroyOnLoad scene! this avoids weird situations
// like in NetworkZones when we destroy the local player and
// load another scene afterwards, yet the local player is still
// in the FindObjectsOfType result with scene=DontDestroyOnLoad
// for some reason
// => OfTypeAll so disabled objects are included too
// => Unity 2019 returns prefabs here too, so filter them out.
IEnumerable<NetworkIdentity> identities = Resources.FindObjectsOfTypeAll<NetworkIdentity>()
.Where(identity => identity.gameObject.hideFlags != HideFlags.NotEditable &&
identity.gameObject.hideFlags != HideFlags.HideAndDontSave &&
identity.gameObject.scene.name != "DontDestroyOnLoad" &&
!PrefabUtility.IsPartOfPrefabAsset(identity.gameObject));
foreach (NetworkIdentity identity in identities)
{
// if we had a [ConflictComponent] attribute that would be better than this check.
// also there is no context about which scene this is in.
if (identity.GetComponent<NetworkManager>() != null)
{
Debug.LogError("NetworkManager has a NetworkIdentity component. This will cause the NetworkManager object to be disabled, so it is not recommended.");
}
// not spawned before?
// OnPostProcessScene is called after additive scene loads too,
// and we don't want to set main scene's objects inactive again
if (!identity.isClient && !identity.isServer)
{
// valid scene object?
// otherwise it might be an unopened scene that still has null
// sceneIds. builds are interrupted if they contain 0 sceneIds,
// but it's still possible that we call LoadScene in Editor
// for a previously unopened scene.
// (and only do SetActive if this was actually a scene object)
if (identity.sceneId != 0)
{
PrepareSceneObject(identity);
}
// throwing an exception would only show it for one object
// because this function would return afterwards.
else Debug.LogError("Scene " + identity.gameObject.scene.path + " needs to be opened and resaved, because the scene object " + identity.name + " has no valid sceneId yet.");
}
}
}
static void PrepareSceneObject(NetworkIdentity identity)
{
// set scene hash
identity.SetSceneIdSceneHashPartInternal();
// disable it
// note: NetworkIdentity.OnDisable adds itself to the
// spawnableObjects dictionary (only if sceneId != 0)
identity.gameObject.SetActive(false);
// safety check for prefabs with more than one NetworkIdentity
#if UNITY_2018_2_OR_NEWER
GameObject prefabGO = PrefabUtility.GetCorrespondingObjectFromSource(identity.gameObject);
#else
GameObject prefabGO = PrefabUtility.GetPrefabParent(identity.gameObject);
#endif
if (prefabGO)
{
#if UNITY_2018_3_OR_NEWER
GameObject prefabRootGO = prefabGO.transform.root.gameObject;
#else
GameObject prefabRootGO = PrefabUtility.FindPrefabRoot(prefabGO);
#endif
if (prefabRootGO != null && prefabRootGO.GetComponentsInChildren<NetworkIdentity>().Length > 1)
{
Debug.LogWarningFormat("Prefab '{0}' has several NetworkIdentity components attached to itself or its children, this is not supported.", prefabRootGO.name);
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,47 @@
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomPropertyDrawer(typeof(SceneAttribute))]
public class SceneDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.String)
{
SceneAsset sceneObject = AssetDatabase.LoadAssetAtPath<SceneAsset>(property.stringValue);
if (sceneObject == null && !string.IsNullOrEmpty(property.stringValue))
{
// try to load it from the build settings for legacy compatibility
sceneObject = GetBuildSettingsSceneObject(property.stringValue);
}
if (sceneObject == null && !string.IsNullOrEmpty(property.stringValue))
{
Debug.LogError($"Could not find scene {property.stringValue} in {property.propertyPath}, assign the proper scenes in your NetworkManager");
}
SceneAsset scene = (SceneAsset)EditorGUI.ObjectField(position, label, sceneObject, typeof(SceneAsset), true);
property.stringValue = AssetDatabase.GetAssetPath(scene);
}
else
{
EditorGUI.LabelField(position, label.text, "Use [Scene] with strings.");
}
}
protected SceneAsset GetBuildSettingsSceneObject(string sceneName)
{
foreach (EditorBuildSettingsScene buildScene in EditorBuildSettings.scenes)
{
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(buildScene.path);
if (sceneAsset.name == sceneName)
{
return sceneAsset;
}
}
return null;
}
}
}

View File

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

View File

@@ -0,0 +1,54 @@
using UnityEditor;
using UnityEngine;
namespace Mirror.EditorScripts
{
public static class ScriptableObjectUtility
{
/// <summary>
// This makes it easy to create, name and place unique new ScriptableObject asset files.
/// </summary>
public static T CreateAsset<T>(string defaultName) where T : ScriptableObject
{
string path = SavePanel(defaultName);
// user click cancel
if (string.IsNullOrEmpty(path)) { return null; }
T asset = ScriptableObject.CreateInstance<T>();
SaveAsset(path, asset);
return asset;
}
static string SavePanel(string name)
{
string path = EditorUtility.SaveFilePanel(
"Save ScriptableObject",
"Assets/Mirror/",
name + ".asset",
"asset");
// user click cancel, return early
if (string.IsNullOrEmpty(path)) { return path; }
// Unity only wants path from Assets
if (path.StartsWith(Application.dataPath))
{
path = "Assets" + path.Substring(Application.dataPath.Length);
}
return path;
}
static void SaveAsset(string path, ScriptableObject asset)
{
string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path);
AssetDatabase.CreateAsset(asset, assetPathAndName);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
}

View File

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

View File

@@ -0,0 +1,28 @@
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomPropertyDrawer(typeof(SyncVarAttribute))]
public class SyncVarAttributeDrawer : PropertyDrawer
{
static readonly GUIContent syncVarIndicatorContent = new GUIContent("SyncVar", "This variable has been marked with the [SyncVar] attribute.");
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Vector2 syncVarIndicatorRect = EditorStyles.miniLabel.CalcSize(syncVarIndicatorContent);
float valueWidth = position.width - syncVarIndicatorRect.x;
Rect valueRect = new Rect(position.x, position.y, valueWidth, position.height);
Rect labelRect = new Rect(position.x + valueWidth, position.y, syncVarIndicatorRect.x, position.height);
EditorGUI.PropertyField(valueRect, property, true);
GUI.Label(labelRect, syncVarIndicatorContent, EditorStyles.miniLabel);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property);
}
}
} //namespace

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Mirror.Tests")]

View File

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

View File

@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
using UnityAssembly = UnityEditor.Compilation.Assembly;
namespace Mirror.Weaver
{
public static class CompilationFinishedHook
{
const string MirrorRuntimeAssemblyName = "Mirror";
const string MirrorWeaverAssemblyName = "Mirror.Weaver";
// delegate for subscription to Weaver debug messages
public static Action<string> OnWeaverMessage;
// delegate for subscription to Weaver warning messages
public static Action<string> OnWeaverWarning;
// delete for subscription to Weaver error messages
public static Action<string> OnWeaverError;
// controls whether we weave any assemblies when CompilationPipeline delegates are invoked
public static bool WeaverEnabled { get; set; }
// controls weather Weaver errors are reported direct to the Unity console (tests enable this)
public static bool UnityLogEnabled = true;
// warning message handler that also calls OnWarningMethod delegate
static void HandleWarning(string msg)
{
if (UnityLogEnabled) Debug.LogWarning(msg);
if (OnWeaverWarning != null) OnWeaverWarning.Invoke(msg);
}
// error message handler that also calls OnErrorMethod delegate
static void HandleError(string msg)
{
if (UnityLogEnabled) Debug.LogError(msg);
if (OnWeaverError != null) OnWeaverError.Invoke(msg);
}
[InitializeOnLoadMethod]
public static void OnInitializeOnLoad()
{
CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
// We only need to run this once per session
// after that, all assemblies will be weaved by the event
if (!SessionState.GetBool("MIRROR_WEAVED", false))
{
// reset session flag
SessionState.SetBool("MIRROR_WEAVED", true);
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", true);
WeaveExistingAssemblies();
}
}
public static void WeaveExistingAssemblies()
{
foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
{
if (File.Exists(assembly.outputPath))
{
OnCompilationFinished(assembly.outputPath, new CompilerMessage[0]);
}
}
#if UNITY_2019_3_OR_NEWER
EditorUtility.RequestScriptReload();
#else
UnityEditorInternal.InternalEditorUtility.RequestScriptReload();
#endif
}
static string FindMirrorRuntime()
{
foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
{
if (assembly.name == MirrorRuntimeAssemblyName)
{
return assembly.outputPath;
}
}
return "";
}
static bool CompilerMessagesContainError(CompilerMessage[] messages)
{
return messages.Any(msg => msg.type == CompilerMessageType.Error);
}
static void OnCompilationFinished(string assemblyPath, CompilerMessage[] messages)
{
// Do nothing if there were compile errors on the target
if (CompilerMessagesContainError(messages))
{
Debug.Log("Weaver: stop because compile errors on target");
return;
}
// Should not run on the editor only assemblies
if (assemblyPath.Contains("-Editor") || assemblyPath.Contains(".Editor"))
{
return;
}
// don't weave mirror files
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
{
return;
}
// find Mirror.dll
string mirrorRuntimeDll = FindMirrorRuntime();
if (string.IsNullOrEmpty(mirrorRuntimeDll))
{
Debug.LogError("Failed to find Mirror runtime assembly");
return;
}
if (!File.Exists(mirrorRuntimeDll))
{
// this is normal, it happens with any assembly that is built before mirror
// such as unity packages or your own assemblies
// those don't need to be weaved
// if any assembly depends on mirror, then it will be built after
return;
}
// find UnityEngine.CoreModule.dll
string unityEngineCoreModuleDLL = UnityEditorInternal.InternalEditorUtility.GetEngineCoreModuleAssemblyPath();
if (string.IsNullOrEmpty(unityEngineCoreModuleDLL))
{
Debug.LogError("Failed to find UnityEngine assembly");
return;
}
HashSet<string> dependencyPaths = GetDependecyPaths(assemblyPath);
dependencyPaths.Add(Path.GetDirectoryName(mirrorRuntimeDll));
dependencyPaths.Add(Path.GetDirectoryName(unityEngineCoreModuleDLL));
Log.WarningMethod = HandleWarning;
Log.ErrorMethod = HandleError;
if (!Weaver.WeaveAssembly(assemblyPath, dependencyPaths.ToArray()))
{
// Set false...will be checked in \Editor\EnterPlayModeSettingsCheck.CheckSuccessfulWeave()
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", false);
if (UnityLogEnabled) Debug.LogError("Weaving failed for: " + assemblyPath);
}
}
static HashSet<string> GetDependecyPaths(string assemblyPath)
{
// build directory list for later asm/symbol resolving using CompilationPipeline refs
HashSet<string> dependencyPaths = new HashSet<string>
{
Path.GetDirectoryName(assemblyPath)
};
foreach (UnityAssembly unityAsm in CompilationPipeline.GetAssemblies())
{
if (unityAsm.outputPath != assemblyPath)
continue;
foreach (string unityAsmRef in unityAsm.compiledAssemblyReferences)
{
dependencyPaths.Add(Path.GetDirectoryName(unityAsmRef));
}
}
return dependencyPaths;
}
}
}

View File

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

View File

@@ -0,0 +1,266 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class Extensions
{
public static bool Is(this TypeReference td, Type t)
{
if (t.IsGenericType)
{
return td.GetElementType().FullName == t.FullName;
}
return td.FullName == t.FullName;
}
public static bool Is<T>(this TypeReference td) => Is(td, typeof(T));
public static bool IsDerivedFrom<T>(this TypeDefinition td) => IsDerivedFrom(td, typeof(T));
public static bool IsDerivedFrom(this TypeDefinition td, Type baseClass)
{
if (!td.IsClass)
return false;
// are ANY parent classes of baseClass?
TypeReference parent = td.BaseType;
if (parent == null)
return false;
if (parent.Is(baseClass))
return true;
if (parent.CanBeResolved())
return IsDerivedFrom(parent.Resolve(), baseClass);
return false;
}
public static TypeReference GetEnumUnderlyingType(this TypeDefinition td)
{
foreach (FieldDefinition field in td.Fields)
{
if (!field.IsStatic)
return field.FieldType;
}
throw new ArgumentException($"Invalid enum {td.FullName}");
}
public static bool ImplementsInterface<TInterface>(this TypeDefinition td)
{
TypeDefinition typedef = td;
while (typedef != null)
{
foreach (InterfaceImplementation iface in typedef.Interfaces)
{
if (iface.InterfaceType.Is<TInterface>())
return true;
}
try
{
TypeReference parent = typedef.BaseType;
typedef = parent?.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for pluins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
return false;
}
public static bool IsMultidimensionalArray(this TypeReference tr)
{
return tr is ArrayType arrayType && arrayType.Rank > 1;
}
public static bool CanBeResolved(this TypeReference parent)
{
while (parent != null)
{
if (parent.Scope.Name == "Windows")
{
return false;
}
if (parent.Scope.Name == "mscorlib")
{
TypeDefinition resolved = parent.Resolve();
return resolved != null;
}
try
{
parent = parent.Resolve().BaseType;
}
catch
{
return false;
}
}
return true;
}
/// <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 Weaver.CurrentAssembly.MainModule.ImportReference(reference);
}
/// <summary>
/// Given a field of a generic class such as Writer<T>.write,
/// 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">Generic Instance eg Writer<int></param>
/// <returns></returns>
public static FieldReference SpecializeField(this FieldReference self, GenericInstanceType instanceType)
{
FieldReference reference = new FieldReference(self.Name, self.FieldType, instanceType);
return Weaver.CurrentAssembly.MainModule.ImportReference(reference);
}
public static CustomAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider method)
{
foreach (CustomAttribute ca in method.CustomAttributes)
{
if (ca.AttributeType.Is<TAttribute>())
return ca;
}
return null;
}
public static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider)
{
// Linq allocations don't matter in weaver
return attributeProvider.CustomAttributes.Any(attr => attr.AttributeType.Is<TAttribute>());
}
public static T GetField<T>(this CustomAttribute ca, string field, T defaultValue)
{
foreach (CustomAttributeNamedArgument customField in ca.Fields)
{
if (customField.Name == field)
{
return (T)customField.Argument.Value;
}
}
return defaultValue;
}
public static MethodDefinition GetMethod(this TypeDefinition td, string methodName)
{
// Linq allocations don't matter in weaver
return td.Methods.FirstOrDefault(method => method.Name == methodName);
}
public static List<MethodDefinition> GetMethods(this TypeDefinition td, string methodName)
{
// Linq allocations don't matter in weaver
return td.Methods.Where(method => method.Name == methodName).ToList();
}
public static MethodDefinition GetMethodInBaseType(this TypeDefinition td, string methodName)
{
TypeDefinition typedef = td;
while (typedef != null)
{
foreach (MethodDefinition md in typedef.Methods)
{
if (md.Name == methodName)
return md;
}
try
{
TypeReference parent = typedef.BaseType;
typedef = parent?.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
break;
}
}
return null;
}
/// <summary>
/// Finds public fields in type and base type
/// </summary>
/// <param name="variable"></param>
/// <returns></returns>
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable)
{
return FindAllPublicFields(variable.Resolve());
}
/// <summary>
/// Finds public fields in type and base type
/// </summary>
/// <param name="variable"></param>
/// <returns></returns>
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDefinition)
{
while (typeDefinition != null)
{
foreach (FieldDefinition field in typeDefinition.Fields)
{
if (field.IsStatic || field.IsPrivate)
continue;
if (field.IsNotSerialized)
continue;
yield return field;
}
try
{
typeDefinition = typeDefinition.BaseType?.Resolve();
}
catch (AssemblyResolutionException)
{
break;
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,25 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace Mirror.Weaver
{
static class Helpers
{
// This code is taken from SerializationWeaver
public static string UnityEngineDllDirectoryName()
{
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
return directoryName?.Replace(@"file:\", "");
}
public static string DestinationFileFor(string outputDir, string assemblyPath)
{
string fileName = Path.GetFileName(assemblyPath);
Debug.Assert(fileName != null, "fileName != null");
return Path.Combine(outputDir, fileName);
}
}
}

View File

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

View File

@@ -0,0 +1,20 @@
using System;
namespace Mirror.Weaver
{
public static class Log
{
public static Action<string> WarningMethod;
public static Action<string> ErrorMethod;
public static void Warning(string msg)
{
WarningMethod(msg);
}
public static void Error(string msg)
{
ErrorMethod(msg);
}
}
}

View File

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

View File

@@ -0,0 +1,16 @@
{
"name": "Mirror.Weaver",
"references": [
"Mirror"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1d0b9d21c3ff546a4aa32399dfd33474
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,127 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
/// <summary>
/// Processes [Command] methods in NetworkBehaviour
/// </summary>
public static class CommandProcessor
{
/*
// generates code like:
public void CmdThrust(float thrusting, int spin)
{
NetworkWriter networkWriter = new NetworkWriter();
networkWriter.Write(thrusting);
networkWriter.WritePackedUInt32((uint)spin);
base.SendCommandInternal(cmdName, networkWriter, cmdName);
}
public void CallCmdThrust(float thrusting, int spin)
{
// whatever the user was doing before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to CmdTrust with CallCmdTrust
This method moves all the user's code into the "CallCmd" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr)
{
MethodDefinition cmd = MethodProcessor.SubstituteMethod(td, md);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker);
// NetworkWriter writer = new NetworkWriter();
NetworkBehaviourProcessor.WriteCreateWriter(worker);
// write all the arguments that the user passed to the Cmd call
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.Command))
return null;
string cmdName = md.Name;
int channel = commandAttr.GetField("channel", 0);
bool ignoreAuthority = commandAttr.GetField("ignoreAuthority", false);
// invoke internal send and return
// load 'base.' to call the SendCommand function with
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldtoken, td));
// invokerClass
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
worker.Append(worker.Create(OpCodes.Ldstr, cmdName));
// writer
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
worker.Append(worker.Create(ignoreAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.sendCommandInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Append(worker.Create(OpCodes.Ret));
return cmd;
}
/*
// generates code like:
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
{
if (!NetworkServer.active)
{
return;
}
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
}
*/
public static MethodDefinition ProcessCommandInvoke(TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc)
{
MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));
ILProcessor worker = cmd.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, method.Name, label, "Command");
// setup for reader
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Castclass, td));
if (!NetworkBehaviourProcessor.ReadArguments(method, worker, RemoteCallType.Command))
return null;
AddSenderConnection(method, worker);
// invoke actual command function
worker.Append(worker.Create(OpCodes.Callvirt, cmdCallFunc));
worker.Append(worker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
td.Methods.Add(cmd);
return cmd;
}
static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
{
foreach (ParameterDefinition param in method.Parameters)
{
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
{
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
// exmaple: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
worker.Append(worker.Create(OpCodes.Ldarg_2));
}
}
}
}
}

View File

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

View File

@@ -0,0 +1 @@
// Removed Oct 1 2020

View File

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

View File

@@ -0,0 +1 @@
// removed Oct 5 2020

View File

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

View File

@@ -0,0 +1,125 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class MethodProcessor
{
private const string RpcPrefix = "UserCode_";
// creates a method substitute
// For example, if we have this:
// public void CmdThrust(float thrusting, int spin)
// {
// xxxxx
// }
//
// it will substitute the method and move the code to a new method with a provided name
// for example:
//
// public void CmdTrust(float thrusting, int spin)
// {
// }
//
// public void <newName>(float thrusting, int spin)
// {
// xxxxx
// }
//
// Note that all the calls to the method remain untouched
//
// the original method definition loses all code
// this returns the newly created method with all the user provided code
public static MethodDefinition SubstituteMethod(TypeDefinition td, MethodDefinition md)
{
string newName = RpcPrefix + md.Name;
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
// add parameters
foreach (ParameterDefinition pd in md.Parameters)
{
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
// swap bodies
(cmd.Body, md.Body) = (md.Body, cmd.Body);
// Move over all the debugging information
foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints)
cmd.DebugInformation.SequencePoints.Add(sequencePoint);
md.DebugInformation.SequencePoints.Clear();
foreach (CustomDebugInformation customInfo in md.CustomDebugInformations)
cmd.CustomDebugInformations.Add(customInfo);
md.CustomDebugInformations.Clear();
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
td.Methods.Add(cmd);
FixRemoteCallToBaseMethod(td, cmd);
return cmd;
}
/// <summary>
/// Finds and fixes call to base methods within remote calls
/// <para>For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`</para>
/// </summary>
/// <param name="type"></param>
/// <param name="method"></param>
public static void FixRemoteCallToBaseMethod(TypeDefinition type, MethodDefinition method)
{
string callName = method.Name;
// Cmd/rpc start with Weaver.RpcPrefix
// eg CallCmdDoSomething
if (!callName.StartsWith(RpcPrefix))
return;
// eg CmdDoSomething
string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
foreach (Instruction instruction in method.Body.Instructions)
{
// if call to base.CmdDoSomething within this.CallCmdDoSomething
if (IsCallToMethod(instruction, out MethodDefinition calledMethod) &&
calledMethod.Name == baseRemoteCallName)
{
TypeDefinition baseType = type.BaseType.Resolve();
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
if (baseMethod == null)
{
Weaver.Error($"Could not find base method for {callName}", method);
return;
}
if (!baseMethod.IsVirtual)
{
Weaver.Error($"Could not find base method that was virutal {callName}", method);
return;
}
instruction.Operand = baseMethod;
Weaver.DLog(type, "Replacing call to '{0}' with '{1}' inside '{2}'", calledMethod.FullName, baseMethod.FullName, method.FullName);
}
}
}
static 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;
}
}
}
}

View File

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

View File

@@ -0,0 +1,45 @@
using Mono.CecilX;
namespace Mirror.Weaver
{
/// <summary>
/// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
/// </summary>
static class MonoBehaviourProcessor
{
public static void Process(TypeDefinition td)
{
ProcessSyncVars(td);
ProcessMethods(td);
}
static void ProcessSyncVars(TypeDefinition td)
{
// find syncvars
foreach (FieldDefinition fd in td.Fields)
{
if (fd.HasCustomAttribute<SyncVarAttribute>())
Weaver.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
{
Weaver.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
}
}
}
static void ProcessMethods(TypeDefinition td)
{
// find command and RPC functions
foreach (MethodDefinition md in td.Methods)
{
if (md.HasCustomAttribute<CommandAttribute>())
Weaver.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
if (md.HasCustomAttribute<ClientRpcAttribute>())
Weaver.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
if (md.HasCustomAttribute<TargetRpcAttribute>())
Weaver.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
}
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,160 @@
using System;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class PropertySiteProcessor
{
public static void Process(ModuleDefinition moduleDef)
{
DateTime startTime = DateTime.Now;
//Search through the types
foreach (TypeDefinition td in moduleDef.Types)
{
if (td.IsClass)
{
ProcessSiteClass(td);
}
}
Console.WriteLine(" ProcessSitesModule " + moduleDef.Name + " elapsed time:" + (DateTime.Now - startTime));
}
static void ProcessSiteClass(TypeDefinition td)
{
//Console.WriteLine(" ProcessSiteClass " + td);
foreach (MethodDefinition md in td.Methods)
{
ProcessSiteMethod(md);
}
foreach (TypeDefinition nested in td.NestedTypes)
{
ProcessSiteClass(nested);
}
}
static void ProcessSiteMethod(MethodDefinition md)
{
// process all references to replaced members with properties
//Weaver.DLog(td, " ProcessSiteMethod " + md);
if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
return;
if (md.IsAbstract)
{
return;
}
if (md.Body != null && md.Body.Instructions != null)
{
for (int iCount = 0; iCount < md.Body.Instructions.Count;)
{
Instruction instr = md.Body.Instructions[iCount];
iCount += ProcessInstruction(md, instr, iCount);
}
}
}
// replaces syncvar write access with the NetworkXYZ.get property calls
static void ProcessInstructionSetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
{
// dont replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//DLog(td, " replacing " + md.Name + ":" + i);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
// replaces syncvar read access with the NetworkXYZ.get property calls
static void ProcessInstructionGetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
{
// dont replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//DLog(td, " replacing " + md.Name + ":" + i);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
static int ProcessInstruction(MethodDefinition md, Instruction instr, int iCount)
{
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
{
// this instruction sets the value of a field. cache the field reference.
ProcessInstructionSetterField(md, instr, opFieldst);
}
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
{
// this instruction gets the value of a field. cache the field reference.
ProcessInstructionGetterField(md, instr, opFieldld);
}
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
{
// loading a field by reference, watch out for initobj instruction
// see https://github.com/vis2k/Mirror/issues/696
return ProcessInstructionLoadAddress(md, instr, opFieldlda, iCount);
}
return 1;
}
static int ProcessInstructionLoadAddress(MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
{
// dont replace property call sites in constructors
if (md.Name == ".ctor")
return 1;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
// we have a replacement for this property
// is the next instruction a initobj?
Instruction nextInstr = md.Body.Instructions[iCount + 1];
if (nextInstr.OpCode == OpCodes.Initobj)
{
// we need to replace this code with:
// var tmp = new MyStruct();
// this.set_Networkxxxx(tmp);
ILProcessor worker = md.Body.GetILProcessor();
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
md.Body.Variables.Add(tmpVariable);
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
worker.Remove(instr);
worker.Remove(nextInstr);
return 4;
}
}
return 1;
}
}
}

View File

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

View File

@@ -0,0 +1,175 @@
// finds all readers and writers and register them
using System;
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
namespace Mirror.Weaver
{
public static class ReaderWriterProcessor
{
public static bool Process(AssemblyDefinition CurrentAssembly)
{
Readers.Init();
Writers.Init();
foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
{
if (unityAsm.name == "Mirror")
{
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
{
ProcessAssemblyClasses(CurrentAssembly, assembly);
}
}
}
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
}
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
{
bool modified = false;
foreach (TypeDefinition klass in assembly.MainModule.Types)
{
// extension methods only live in static classes
// static classes are represented as sealed and abstract
if (klass.IsAbstract && klass.IsSealed)
{
// if asmebly has any declared writers then it is "modified"
modified |= LoadDeclaredWriters(CurrentAssembly, klass);
modified |= LoadDeclaredReaders(CurrentAssembly, klass);
}
}
foreach (TypeDefinition klass in assembly.MainModule.Types)
{
// if asmebly has any network message then it is modified
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, klass);
}
return modified;
}
static bool LoadMessageReadWriter(ModuleDefinition module, TypeDefinition klass)
{
bool modified = false;
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
{
Readers.GetReadFunc(module.ImportReference(klass));
Writers.GetWriteFunc(module.ImportReference(klass));
modified = true;
}
foreach (TypeDefinition td in klass.NestedTypes)
{
modified |= LoadMessageReadWriter(module, td);
}
return modified;
}
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
{
// register all the writers in this class. Skip the ones with wrong signature
bool modified = false;
foreach (MethodDefinition method in klass.Methods)
{
if (method.Parameters.Count != 2)
continue;
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
continue;
if (!method.ReturnType.Is(typeof(void)))
continue;
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
continue;
if (method.HasGenericParameters)
continue;
TypeReference dataType = method.Parameters[1].ParameterType;
Writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
modified = true;
}
return modified;
}
static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass)
{
// register all the reader in this class. Skip the ones with wrong signature
bool modified = false;
foreach (MethodDefinition method in klass.Methods)
{
if (method.Parameters.Count != 1)
continue;
if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
continue;
if (method.ReturnType.Is(typeof(void)))
continue;
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
continue;
if (method.HasGenericParameters)
continue;
Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
modified = true;
}
return modified;
}
static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
{
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
assemblyReference.Name == nameof(UnityEditor)
);
}
/// <summary>
/// Creates a method that will store all the readers and writers into
/// <see cref="Writer{T}.write"/> and <see cref="Reader{T}.read"/>
///
/// The method will be marked InitializeOnLoadMethodAttribute so it gets
/// executed before mirror runtime code
/// </summary>
/// <param name="currentAssembly"></param>
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly)
{
MethodDefinition rwInitializer = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
MethodAttributes.Static,
WeaverTypes.Import(typeof(void)));
System.Reflection.ConstructorInfo attributeconstructor = typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(RuntimeInitializeLoadType) });
CustomAttribute customAttributeRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(attributeconstructor));
customAttributeRef.ConstructorArguments.Add(new CustomAttributeArgument(WeaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
rwInitializer.CustomAttributes.Add(customAttributeRef);
if (IsEditorAssembly(currentAssembly))
{
// editor assembly, add InitializeOnLoadMethod too. Useful for the editor tests
System.Reflection.ConstructorInfo initializeOnLoadConstructor = typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[0]);
CustomAttribute initializeCustomConstructorRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(initializeOnLoadConstructor));
rwInitializer.CustomAttributes.Add(initializeCustomConstructorRef);
}
ILProcessor worker = rwInitializer.Body.GetILProcessor();
Writers.InitializeWriters(worker);
Readers.InitializeReaders(worker);
worker.Append(worker.Create(OpCodes.Ret));
TypeDefinition generateClass = Weaver.WeaveLists.generateContainerClass;
generateClass.Methods.Add(rwInitializer);
}
}
}

View File

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

View File

@@ -0,0 +1,105 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
/// <summary>
/// Processes [Rpc] methods in NetworkBehaviour
/// </summary>
public static class RpcProcessor
{
public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
{
MethodDefinition rpc = new MethodDefinition(
Weaver.InvokeRpcPrefix + md.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));
ILProcessor worker = rpc.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "RPC");
// setup for reader
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Castclass, td));
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.ClientRpc))
return null;
// invoke actual command function
worker.Append(worker.Create(OpCodes.Callvirt, rpcCallFunc));
worker.Append(worker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
td.Methods.Add(rpc);
return rpc;
}
/*
* generates code like:
public void RpcTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32((uint)param);
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
}
public void CallRpcTest (int param)
{
// whatever the user did before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to RpcTest with CallRpcTest
This method moves all the user's code into the "CallRpc" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr)
{
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker);
if (Weaver.GenerateLogErrors)
{
worker.Append(worker.Create(OpCodes.Ldstr, "Call ClientRpc function " + md.Name));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.logErrorReference));
}
NetworkBehaviourProcessor.WriteCreateWriter(worker);
// write all the arguments that the user passed to the Rpc call
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.ClientRpc))
return null;
string rpcName = md.Name;
int channel = clientRpcAttr.GetField("channel", 0);
bool excludeOwner = clientRpcAttr.GetField("excludeOwner", false);
// invoke SendInternal and return
// this
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldtoken, td));
// invokerClass
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
worker.Append(worker.Create(OpCodes.Ldstr, rpcName));
// writer
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
worker.Append(worker.Create(excludeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Callvirt, WeaverTypes.sendRpcInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Append(worker.Create(OpCodes.Ret));
return rpc;
}
}
}

View File

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

View File

@@ -0,0 +1,153 @@
// Injects server/client active checks for [Server/Client] attributes
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
static class ServerClientAttributeProcessor
{
public static bool Process(TypeDefinition td)
{
bool modified = false;
foreach (MethodDefinition md in td.Methods)
{
modified |= ProcessSiteMethod(md);
}
foreach (TypeDefinition nested in td.NestedTypes)
{
modified |= Process(nested);
}
return modified;
}
static bool ProcessSiteMethod(MethodDefinition md)
{
if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
return false;
if (md.IsAbstract)
{
if (HasServerClientAttribute(md))
{
Weaver.Error("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", md);
}
return false;
}
if (md.Body != null && md.Body.Instructions != null)
{
return ProcessMethodAttributes(md);
}
return false;
}
public static bool HasServerClientAttribute(MethodDefinition md)
{
foreach (CustomAttribute attr in md.CustomAttributes)
{
switch (attr.Constructor.DeclaringType.ToString())
{
case "Mirror.ServerAttribute":
case "Mirror.ServerCallbackAttribute":
case "Mirror.ClientAttribute":
case "Mirror.ClientCallbackAttribute":
return true;
default:
break;
}
}
return false;
}
public static bool ProcessMethodAttributes(MethodDefinition md)
{
if (md.HasCustomAttribute<ServerAttribute>())
InjectServerGuard(md, true);
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
InjectServerGuard(md, false);
else if (md.HasCustomAttribute<ClientAttribute>())
InjectClientGuard(md, true);
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
InjectClientGuard(md, false);
else
return false;
return true;
}
static void InjectServerGuard(MethodDefinition md, bool logWarning)
{
ILProcessor worker = md.Body.GetILProcessor();
Instruction top = md.Body.Instructions[0];
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.NetworkServerGetActive));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Server] function '{md.FullName}' called when server was not active"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.logWarningReference));
}
InjectGuardParameters(md, worker, top);
InjectGuardReturnValue(md, worker, top);
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
}
static void InjectClientGuard(MethodDefinition md, bool logWarning)
{
ILProcessor worker = md.Body.GetILProcessor();
Instruction top = md.Body.Instructions[0];
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.NetworkClientGetActive));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Client] function '{md.FullName}' called when client was not active"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.logWarningReference));
}
InjectGuardParameters(md, worker, top);
InjectGuardReturnValue(md, worker, top);
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
}
// this is required to early-out from a function with "ref" or "out" parameters
static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
{
int offset = md.Resolve().IsStatic ? 0 : 1;
for (int index = 0; index < md.Parameters.Count; index++)
{
ParameterDefinition param = md.Parameters[index];
if (param.IsOut)
{
TypeReference elementType = param.ParameterType.GetElementType();
md.Body.Variables.Add(new VariableDefinition(elementType));
md.Body.InitLocals = true;
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
}
}
}
// this is required to early-out from a function with a return value.
static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
{
if (!md.ReturnType.Is(typeof(void)))
{
md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
md.Body.InitLocals = true;
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
}
}
}
}

View File

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

View File

@@ -0,0 +1 @@
// Removed Oct 1 2020

View File

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

View File

@@ -0,0 +1 @@
// removed 2020-09

View File

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

View File

@@ -0,0 +1 @@
// Removed Oct 1 2020

View File

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

View File

@@ -0,0 +1,47 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncObjectInitializer
{
public static void GenerateSyncObjectInitializer(ILProcessor worker, FieldDefinition fd)
{
// register syncobject in network behaviour
GenerateSyncObjectRegistration(worker, fd);
}
public static bool ImplementsSyncObject(TypeReference typeRef)
{
try
{
// value types cant inherit from SyncObject
if (typeRef.IsValueType)
{
return false;
}
return typeRef.Resolve().ImplementsInterface<SyncObject>();
}
catch
{
// sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
}
return false;
}
/*
// generates code like:
this.InitSyncObject(m_sizes);
*/
static void GenerateSyncObjectRegistration(ILProcessor worker, FieldDefinition fd)
{
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, fd));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.InitSyncObjectReference));
}
}
}

View File

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

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class SyncObjectProcessor
{
/// <summary>
/// Finds SyncObjects fields in a type
/// <para>Type should be a NetworkBehaviour</para>
/// </summary>
/// <param name="td"></param>
/// <returns></returns>
public static List<FieldDefinition> FindSyncObjectsFields(TypeDefinition td)
{
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
foreach (FieldDefinition fd in td.Fields)
{
if (fd.FieldType.Resolve().ImplementsInterface<SyncObject>())
{
if (fd.IsStatic)
{
Weaver.Error($"{fd.Name} cannot be static", fd);
continue;
}
GenerateReadersAndWriters(fd.FieldType);
syncObjects.Add(fd);
}
}
return syncObjects;
}
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
/// <param name="mirrorBaseType">the base SyncObject td inherits from</param>
static void GenerateReadersAndWriters(TypeReference tr)
{
if (tr is GenericInstanceType genericInstance)
{
foreach (TypeReference argument in genericInstance.GenericArguments)
{
if (!argument.IsGenericParameter)
{
Readers.GetReadFunc(argument);
Writers.GetWriteFunc(argument);
}
}
}
if (tr != null)
{
GenerateReadersAndWriters(tr.Resolve().BaseType);
}
}
}
}

View File

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

View File

@@ -0,0 +1,434 @@
using System.Collections.Generic;
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
/// <summary>
/// Processes [SyncVar] in NetworkBehaviour
/// </summary>
public static class SyncVarProcessor
{
// ulong = 64 bytes
const int SyncVarLimit = 64;
static string HookParameterMessage(string hookName, TypeReference ValueType)
=> string.Format("void {0}({1} oldValue, {1} newValue)", hookName, ValueType);
// Get hook method if any
public static MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar)
{
CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
if (syncVarAttr == null)
return null;
string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
if (hookFunctionName == null)
return null;
return FindHookMethod(td, syncVar, hookFunctionName);
}
static MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName)
{
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
if (methodsWith2Param.Count == 0)
{
Weaver.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
syncVar);
return null;
}
foreach (MethodDefinition method in methodsWith2Param)
{
if (MatchesParameters(syncVar, method))
{
return method;
}
}
Weaver.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
syncVar);
return null;
}
static bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
{
// matches void onValueChange(T oldValue, T newValue)
return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
}
public static MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
{
//Create the get method
MethodDefinition get = new MethodDefinition(
"get_Network" + originalName, MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
fd.FieldType);
ILProcessor worker = get.Body.GetILProcessor();
// [SyncVar] GameObject?
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// return this.GetSyncVarGameObject(ref field, uint netId);
// this.
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, fd));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarGameObjectReference));
worker.Append(worker.Create(OpCodes.Ret));
}
// [SyncVar] NetworkIdentity?
else if (fd.FieldType.Is<NetworkIdentity>())
{
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
// this.
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, fd));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarNetworkIdentityReference));
worker.Append(worker.Create(OpCodes.Ret));
}
// [SyncVar] int, string, etc.
else
{
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, fd));
worker.Append(worker.Create(OpCodes.Ret));
}
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
get.Body.InitLocals = true;
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
return get;
}
public static MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId)
{
//Create the set method
MethodDefinition set = new MethodDefinition("set_Network" + originalName, MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));
ILProcessor worker = set.Body.GetILProcessor();
// if (!SyncVarEqual(value, ref playerData))
Instruction endOfMethod = worker.Create(OpCodes.Nop);
// this
worker.Append(worker.Create(OpCodes.Ldarg_0));
// new value to set
worker.Append(worker.Create(OpCodes.Ldarg_1));
// reference to field to set
// make generic version of SetSyncVar with field type
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// reference to netId Field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarGameObjectEqualReference));
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// reference to netId Field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarNetworkIdentityEqualReference));
}
else
{
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, fd));
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference);
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
worker.Append(worker.Create(OpCodes.Call, syncVarEqualGm));
}
worker.Append(worker.Create(OpCodes.Brtrue, endOfMethod));
// T oldValue = value;
// TODO for GO/NI we need to backup the netId don't we?
VariableDefinition oldValue = new VariableDefinition(fd.FieldType);
set.Body.Variables.Add(oldValue);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldfld, fd));
worker.Append(worker.Create(OpCodes.Stloc, oldValue));
// this
worker.Append(worker.Create(OpCodes.Ldarg_0));
// new value to set
worker.Append(worker.Create(OpCodes.Ldarg_1));
// reference to field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, fd));
// dirty bit
// 8 byte integer aka long
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// reference to netId Field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarGameObjectReference));
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// reference to netId Field to set
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference));
}
else
{
// make generic version of SetSyncVar with field type
GenericInstanceMethod gm = new GenericInstanceMethod(WeaverTypes.setSyncVarReference);
gm.GenericArguments.Add(fd.FieldType);
// invoke SetSyncVar
worker.Append(worker.Create(OpCodes.Call, gm));
}
MethodDefinition hookMethod = GetHookMethod(td, fd);
if (hookMethod != null)
{
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
Instruction label = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkServerGetLocalClientActive));
worker.Append(worker.Create(OpCodes.Brfalse, label));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarHookGuard));
worker.Append(worker.Create(OpCodes.Brtrue, label));
// setSyncVarHookGuard(dirtyBit, true);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard));
// call hook (oldValue, newValue)
// Generates: OnValueChanged(oldValue, value);
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
// setSyncVarHookGuard(dirtyBit, false);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard));
worker.Append(label);
}
worker.Append(endOfMethod);
worker.Append(worker.Create(OpCodes.Ret));
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
return set;
}
public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit)
{
string originalName = fd.Name;
Weaver.DLog(td, "Sync Var " + fd.Name + " " + fd.FieldType);
// GameObject/NetworkIdentity SyncVars have a new field for netId
FieldDefinition netIdField = null;
if (fd.FieldType.Is<UnityEngine.GameObject>() ||
fd.FieldType.Is<NetworkIdentity>())
{
netIdField = new FieldDefinition("___" + fd.Name + "NetId",
FieldAttributes.Private,
WeaverTypes.Import<uint>());
syncVarNetIds[fd] = netIdField;
}
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField);
//NOTE: is property even needed? Could just use a setter function?
//create the property
PropertyDefinition propertyDefinition = new PropertyDefinition("Network" + originalName, PropertyAttributes.None, fd.FieldType)
{
GetMethod = get,
SetMethod = set
};
//add the methods and property to the type.
td.Methods.Add(get);
td.Methods.Add(set);
td.Properties.Add(propertyDefinition);
Weaver.WeaveLists.replacementSetterProperties[fd] = set;
// replace getter field if GameObject/NetworkIdentity so it uses
// netId instead
// -> only for GameObjects, otherwise an int syncvar's getter would
// end up in recursion.
if (fd.FieldType.Is<UnityEngine.GameObject>() ||
fd.FieldType.Is<NetworkIdentity>())
{
Weaver.WeaveLists.replacementGetterProperties[fd] = get;
}
}
public static (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td)
{
List<FieldDefinition> syncVars = new List<FieldDefinition>();
Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
// start assigning syncvars at the place the base class stopped, if any
int dirtyBitCounter = Weaver.WeaveLists.GetSyncVarStart(td.BaseType.FullName);
// find syncvars
foreach (FieldDefinition fd in td.Fields)
{
if (fd.HasCustomAttribute<SyncVarAttribute>())
{
if ((fd.Attributes & FieldAttributes.Static) != 0)
{
Weaver.Error($"{fd.Name} cannot be static", fd);
continue;
}
if (fd.FieldType.IsArray)
{
Weaver.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
continue;
}
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
{
Weaver.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
}
else
{
syncVars.Add(fd);
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter);
dirtyBitCounter += 1;
if (dirtyBitCounter == SyncVarLimit)
{
Weaver.Error($"{td.Name} has too many SyncVars. Consider refactoring your class into multiple components", td);
continue;
}
}
}
}
// add all the new SyncVar __netId fields
foreach (FieldDefinition fd in syncVarNetIds.Values)
{
td.Fields.Add(fd);
}
Weaver.WeaveLists.SetNumSyncVars(td.FullName, syncVars.Count);
return (syncVars, syncVarNetIds);
}
public static void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
{
WriteCallHookMethod(worker, hookMethod, oldValue, null);
}
public static void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
{
if (newValue == null)
{
Weaver.Error("NewValue field was null when writing SyncVar hook");
}
WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
}
static void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
{
WriteStartFunctionCall();
// write args
WriteOldValue();
WriteNewValue();
WriteEndFunctionCall();
// *** Local functions used to write OpCodes ***
// Local functions have access to function variables, no need to pass in args
void WriteOldValue()
{
worker.Append(worker.Create(OpCodes.Ldloc, oldValue));
}
void WriteNewValue()
{
// write arg1 or this.field
if (newValue == null)
{
worker.Append(worker.Create(OpCodes.Ldarg_1));
}
else
{
// this.
worker.Append(worker.Create(OpCodes.Ldarg_0));
// syncvar.get
worker.Append(worker.Create(OpCodes.Ldfld, newValue));
}
}
// Writes this before method if it is not static
void WriteStartFunctionCall()
{
// dont add this (Ldarg_0) if method is static
if (!hookMethod.IsStatic)
{
// this before method call
// eg this.onValueChanged
worker.Append(worker.Create(OpCodes.Ldarg_0));
}
}
// Calls method
void WriteEndFunctionCall()
{
// only use Callvirt when not static
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
worker.Append(worker.Create(opcode, hookMethod));
}
}
}
}

View File

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

View File

@@ -0,0 +1,134 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
/// <summary>
/// Processes [TargetRpc] methods in NetworkBehaviour
/// </summary>
public static class TargetRpcProcessor
{
// helper functions to check if the method has a NetworkConnection parameter
public static bool HasNetworkConnectionParameter(MethodDefinition md)
{
return md.Parameters.Count > 0 &&
md.Parameters[0].ParameterType.Is<NetworkConnection>();
}
public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
{
MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));
ILProcessor worker = rpc.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "TargetRPC");
// setup for reader
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Castclass, td));
// NetworkConnection parameter is optional
if (HasNetworkConnectionParameter(md))
{
// if call has NetworkConnection write clients connection as first arg
//ClientScene.readyconnection
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.ReadyConnectionReference));
}
// process reader parameters and skip first one if first one is NetworkConnection
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.TargetRpc))
return null;
// invoke actual command function
worker.Append(worker.Create(OpCodes.Callvirt, rpcCallFunc));
worker.Append(worker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
td.Methods.Add(rpc);
return rpc;
}
/* generates code like:
public void TargetTest (NetworkConnection conn, int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32 ((uint)param);
base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
}
public void CallTargetTest (NetworkConnection conn, int param)
{
// whatever the user did before
}
or if optional:
public void TargetTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32 ((uint)param);
base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
}
public void CallTargetTest (int param)
{
// whatever the user did before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to TargetTest with CallTargetTest
This method moves all the user's code into the "CallTargetRpc" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessTargetRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr)
{
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker);
NetworkBehaviourProcessor.WriteCreateWriter(worker);
// write all the arguments that the user passed to the TargetRpc call
// (skip first one if first one is NetworkConnection)
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.TargetRpc))
return null;
string rpcName = md.Name;
// invoke SendInternal and return
// this
worker.Append(worker.Create(OpCodes.Ldarg_0));
if (HasNetworkConnectionParameter(md))
{
// connection
worker.Append(worker.Create(OpCodes.Ldarg_1));
}
else
{
// null
worker.Append(worker.Create(OpCodes.Ldnull));
}
worker.Append(worker.Create(OpCodes.Ldtoken, td));
// invokerClass
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
worker.Append(worker.Create(OpCodes.Ldstr, rpcName));
// writer
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0)));
worker.Append(worker.Create(OpCodes.Callvirt, WeaverTypes.sendTargetRpcInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
worker.Append(worker.Create(OpCodes.Ret));
return rpc;
}
}
}

View File

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

View File

@@ -0,0 +1 @@
// Removed 05/09/20

View File

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

View File

@@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
public static class Readers
{
static Dictionary<string, MethodReference> readFuncs;
public static void Init()
{
readFuncs = new Dictionary<string, MethodReference>();
}
internal static void Register(TypeReference dataType, MethodReference methodReference)
{
readFuncs[dataType.FullName] = methodReference;
}
public static MethodReference GetReadFunc(TypeReference variableReference)
{
if (readFuncs.TryGetValue(variableReference.FullName, out MethodReference foundFunc))
{
return foundFunc;
}
// Arrays are special, if we resolve them, we get teh element type,
// so the following ifs might choke on it for scriptable objects
// or other objects that require a custom serializer
// thus check if it is an array and skip all the checks.
if (variableReference.IsArray)
{
if (variableReference.IsMultidimensionalArray())
{
Weaver.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
return null;
}
return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray));
}
TypeDefinition variableDefinition = variableReference.Resolve();
if (variableDefinition == null)
{
Weaver.Error($"{variableReference.Name} is not a supported type", variableReference);
return null;
}
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
{
Weaver.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableReference.Is<UnityEngine.Object>())
{
Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableReference.Is<UnityEngine.ScriptableObject>())
{
Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableReference.IsByReference)
{
// error??
Weaver.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
return null;
}
if (variableDefinition.HasGenericParameters && !variableDefinition.Is(typeof(ArraySegment<>)) && !variableDefinition.Is(typeof(List<>)))
{
Weaver.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.IsInterface)
{
Weaver.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.IsAbstract)
{
Weaver.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
return null;
}
if (variableDefinition.IsEnum)
{
return GenerateEnumReadFunc(variableReference);
}
else if (variableDefinition.Is(typeof(ArraySegment<>)))
{
return GenerateArraySegmentReadFunc(variableReference);
}
else if (variableDefinition.Is(typeof(List<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList));
}
return GenerateClassOrStructReadFunction(variableReference);
}
static void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
{
readFuncs[typeReference.FullName] = newReaderFunc;
Weaver.WeaveLists.generateContainerClass.Methods.Add(newReaderFunc);
}
static MethodDefinition GenerateEnumReadFunc(TypeReference variable)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ldarg_0));
TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
MethodReference underlyingFunc = GetReadFunc(underlyingType);
worker.Append(worker.Create(OpCodes.Call, underlyingFunc));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];
MethodDefinition readerFunc = GenerateReaderFunction(variable);
ILProcessor worker = readerFunc.Body.GetILProcessor();
// $array = reader.Read<[T]>()
ArrayType arrayType = elementType.MakeArrayType();
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, GetReadFunc(arrayType)));
// return new ArraySegment<T>($array);
worker.Append(worker.Create(OpCodes.Newobj, WeaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(genericInstance)));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
private static MethodDefinition GenerateReaderFunction(TypeReference variable)
{
string functionName = "_Read_" + variable.FullName;
// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.CurrentAssembly.MainModule.ImportReference(variable));
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import<NetworkReader>()));
readerFunc.Body.InitLocals = true;
RegisterReadFunc(variable, readerFunc);
return readerFunc;
}
static MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
// generate readers for the element
GetReadFunc(elementType);
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, Weaver.CurrentAssembly, readerFunction);
GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
methodRef.GenericArguments.Add(elementType);
// generates
// return reader.ReadList<T>();
ILProcessor worker = readerFunc.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ldarg_0)); // reader
worker.Append(worker.Create(OpCodes.Call, methodRef)); // Read
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
static MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable)
{
MethodDefinition readerFunc = GenerateReaderFunction(variable);
// create local for return value
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
ILProcessor worker = readerFunc.Body.GetILProcessor();
TypeDefinition td = variable.Resolve();
if (!td.IsValueType)
GenerateNullCheck(worker);
CreateNew(variable, worker, td);
ReadAllFields(variable, worker);
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
private static void GenerateNullCheck(ILProcessor worker)
{
// if (!reader.ReadBoolean()) {
// return null;
// }
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, GetReadFunc(WeaverTypes.Import<bool>())));
Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Brtrue, labelEmptyArray));
// return null
worker.Append(worker.Create(OpCodes.Ldnull));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(labelEmptyArray);
}
// Initialize the local variable with a new instance
static void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td)
{
if (variable.IsValueType)
{
// structs are created with Initobj
worker.Append(worker.Create(OpCodes.Ldloca, 0));
worker.Append(worker.Create(OpCodes.Initobj, variable));
}
else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
{
GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod(WeaverTypes.ScriptableObjectCreateInstanceMethod);
genericInstanceMethod.GenericArguments.Add(variable);
worker.Append(worker.Create(OpCodes.Call, genericInstanceMethod));
worker.Append(worker.Create(OpCodes.Stloc_0));
}
else
{
// classes are created with their constructor
MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
if (ctor == null)
{
Weaver.Error($"{variable.Name} can't be deserialized because it has no default constructor", variable);
return;
}
MethodReference ctorRef = Weaver.CurrentAssembly.MainModule.ImportReference(ctor);
worker.Append(worker.Create(OpCodes.Newobj, ctorRef));
worker.Append(worker.Create(OpCodes.Stloc_0));
}
}
static void ReadAllFields(TypeReference variable, ILProcessor worker)
{
uint fields = 0;
foreach (FieldDefinition field in variable.FindAllPublicFields())
{
// mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
worker.Append(worker.Create(opcode, 0));
MethodReference readFunc = GetReadFunc(field.FieldType);
if (readFunc != null)
{
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, readFunc));
}
else
{
Weaver.Error($"{field.Name} has an unsupported type", field);
}
FieldReference fieldRef = Weaver.CurrentAssembly.MainModule.ImportReference(field);
worker.Append(worker.Create(OpCodes.Stfld, fieldRef));
fields++;
}
}
/// <summary>
/// Save a delegate for each one of the readers into <see cref="Reader{T}.read"/>
/// </summary>
/// <param name="worker"></param>
internal static void InitializeReaders(ILProcessor worker)
{
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>));
System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
FieldReference fieldRef = module.ImportReference(fieldInfo);
TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader));
TypeReference funcRef = module.ImportReference(typeof(Func<,>));
MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
foreach (MethodReference readFunc in readFuncs.Values)
{
TypeReference dataType = readFunc.ReturnType;
// create a Func<NetworkReader, T> delegate
worker.Append(worker.Create(OpCodes.Ldnull));
worker.Append(worker.Create(OpCodes.Ldftn, readFunc));
GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, dataType);
MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(funcGenericInstance);
worker.Append(worker.Create(OpCodes.Newobj, funcConstructorInstance));
// save it in Writer<T>.write
GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(dataType);
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
worker.Append(worker.Create(OpCodes.Stsfld, specializedField));
}
}
}
}

View File

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

View File

@@ -0,0 +1,86 @@
// all the resolve functions for the weaver
// NOTE: these functions should be made extensions, but right now they still
// make heavy use of Weaver.fail and we'd have to check each one's return
// value for null otherwise.
// (original FieldType.Resolve returns null if not found too, so
// exceptions would be a bit inconsistent here)
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class Resolvers
{
public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition scriptDef, string name)
{
if (tr == null)
{
Weaver.Error($"Cannot resolve method {name} without a class");
return null;
}
MethodReference method = ResolveMethod(tr, scriptDef, m => m.Name == name);
if (method == null)
{
Weaver.Error($"Method not found with name {name} in type {tr.Name}", tr);
}
return method;
}
public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition scriptDef, System.Func<MethodDefinition, bool> predicate)
{
foreach (MethodDefinition methodRef in t.Resolve().Methods)
{
if (predicate(methodRef))
{
return scriptDef.MainModule.ImportReference(methodRef);
}
}
Weaver.Error($"Method not found in type {t.Name}", t);
return null;
}
public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition scriptDef, string name)
{
if (tr == null)
{
return null;
}
foreach (MethodDefinition methodRef in tr.Resolve().Methods)
{
if (methodRef.Name == name)
{
return scriptDef.MainModule.ImportReference(methodRef);
}
}
// Could not find the method in this class, try the parent
return TryResolveMethodInParents(tr.Resolve().BaseType, scriptDef, name);
}
public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)
{
foreach (MethodDefinition methodRef in variable.Resolve().Methods)
{
if (methodRef.Name == ".ctor" &&
methodRef.Resolve().IsPublic &&
methodRef.Parameters.Count == 0)
{
return methodRef;
}
}
return null;
}
public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition scriptDef, string name)
{
foreach (PropertyDefinition pd in tr.Resolve().Properties)
{
if (pd.Name == name)
{
return scriptDef.MainModule.ImportReference(pd.GetMethod);
}
}
return null;
}
}
}

View File

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

View File

@@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
using System.IO;
using Mono.CecilX;
namespace Mirror.Weaver
{
// This data is flushed each time - if we are run multiple times in the same process/domain
class WeaverLists
{
// setter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties = new Dictionary<FieldDefinition, MethodDefinition>();
// getter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties = new Dictionary<FieldDefinition, MethodDefinition>();
public TypeDefinition generateContainerClass;
// amount of SyncVars per class. dict<className, amount>
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
public int GetSyncVarStart(string className)
{
return numSyncVars.ContainsKey(className)
? numSyncVars[className]
: 0;
}
public void SetNumSyncVars(string className, int num)
{
numSyncVars[className] = num;
}
public WeaverLists()
{
generateContainerClass = new TypeDefinition("Mirror", "GeneratedNetworkCode",
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
WeaverTypes.Import<object>());
}
}
internal static class Weaver
{
public static string InvokeRpcPrefix => "InvokeUserCode_";
public static WeaverLists WeaveLists { get; private set; }
public static AssemblyDefinition CurrentAssembly { get; private set; }
public static bool WeavingFailed { get; private set; }
public static bool GenerateLogErrors { get; set; }
// private properties
static readonly bool DebugLogEnabled = true;
public static void DLog(TypeDefinition td, string fmt, params object[] args)
{
if (!DebugLogEnabled)
return;
Console.WriteLine("[" + td.Name + "] " + string.Format(fmt, args));
}
// display weaver error
// and mark process as failed
public static void Error(string message)
{
Log.Error(message);
WeavingFailed = true;
}
public static void Error(string message, MemberReference mr)
{
Log.Error($"{message} (at {mr})");
WeavingFailed = true;
}
public static void Warning(string message, MemberReference mr)
{
Log.Warning($"{message} (at {mr})");
}
static void CheckMonoBehaviour(TypeDefinition td)
{
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
{
MonoBehaviourProcessor.Process(td);
}
}
static bool WeaveNetworkBehavior(TypeDefinition td)
{
if (!td.IsClass)
return false;
if (!td.IsDerivedFrom<NetworkBehaviour>())
{
CheckMonoBehaviour(td);
return false;
}
// process this and base classes from parent to child order
List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();
TypeDefinition parent = td;
while (parent != null)
{
if (parent.Is<NetworkBehaviour>())
{
break;
}
try
{
behaviourClasses.Insert(0, parent);
parent = parent.BaseType.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
bool modified = false;
foreach (TypeDefinition behaviour in behaviourClasses)
{
modified |= new NetworkBehaviourProcessor(behaviour).Process();
}
return modified;
}
static bool WeaveModule(ModuleDefinition moduleDefinition)
{
try
{
bool modified = false;
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
watch.Start();
foreach (TypeDefinition td in moduleDefinition.Types)
{
if (td.IsClass && td.BaseType.CanBeResolved())
{
modified |= WeaveNetworkBehavior(td);
modified |= ServerClientAttributeProcessor.Process(td);
}
}
watch.Stop();
Console.WriteLine("Weave behaviours and messages took" + watch.ElapsedMilliseconds + " milliseconds");
return modified;
}
catch (Exception ex)
{
Error(ex.ToString());
throw new Exception(ex.Message, ex);
}
}
static bool Weave(string assName, IEnumerable<string> dependencies)
{
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
{
asmResolver.AddSearchDirectory(Path.GetDirectoryName(assName));
asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName());
if (dependencies != null)
{
foreach (string path in dependencies)
{
asmResolver.AddSearchDirectory(path);
}
}
WeaverTypes.SetupTargetTypes(CurrentAssembly);
// WeaverList depends on WeaverTypes setup because it uses Import
WeaveLists = new WeaverLists();
System.Diagnostics.Stopwatch rwstopwatch = System.Diagnostics.Stopwatch.StartNew();
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
bool modified = ReaderWriterProcessor.Process(CurrentAssembly);
rwstopwatch.Stop();
Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
Console.WriteLine($"Script Module: {moduleDefinition.Name}");
modified |= WeaveModule(moduleDefinition);
if (WeavingFailed)
{
return false;
}
if (modified)
{
PropertySiteProcessor.Process(moduleDefinition);
// add class that holds read/write functions
moduleDefinition.Types.Add(WeaveLists.generateContainerClass);
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly);
// write to outputDir if specified, otherwise perform in-place write
WriterParameters writeParams = new WriterParameters { WriteSymbols = true };
CurrentAssembly.Write(writeParams);
}
}
return true;
}
public static bool WeaveAssembly(string assembly, IEnumerable<string> dependencies)
{
WeavingFailed = false;
try
{
return Weave(assembly, dependencies);
}
catch (Exception e)
{
Log.Error("Exception :" + e);
return false;
}
}
}
}

View File

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

View File

@@ -0,0 +1,25 @@
using System;
using Mono.CecilX;
namespace Mirror.Weaver
{
[Serializable]
public abstract class WeaverException : Exception
{
public MemberReference MemberReference { get; }
protected WeaverException(string message, MemberReference member) : base(message)
{
MemberReference = member;
}
protected WeaverException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) : base(serializationInfo, streamingContext) { }
}
[Serializable]
public class GenerateWriterException : WeaverException
{
public GenerateWriterException(string message, MemberReference member) : base(message, member) { }
protected GenerateWriterException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) : base(serializationInfo, streamingContext) { }
}
}

View File

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

View File

@@ -0,0 +1,132 @@
using System;
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class WeaverTypes
{
public static MethodReference ScriptableObjectCreateInstanceMethod;
public static MethodReference NetworkBehaviourDirtyBitsReference;
public static MethodReference GetPooledWriterReference;
public static MethodReference RecycleWriterReference;
public static MethodReference ReadyConnectionReference;
public static MethodReference CmdDelegateConstructor;
public static MethodReference NetworkServerGetActive;
public static MethodReference NetworkServerGetLocalClientActive;
public static MethodReference NetworkClientGetActive;
// custom attribute types
public static MethodReference InitSyncObjectReference;
// array segment
public static MethodReference ArraySegmentConstructorReference;
public static MethodReference ArraySegmentArrayReference;
public static MethodReference ArraySegmentOffsetReference;
public static MethodReference ArraySegmentCountReference;
// list
public static MethodReference ListConstructorReference;
public static MethodReference ListCountReference;
public static MethodReference ListGetItemReference;
public static MethodReference ListAddReference;
public static MethodReference syncVarEqualReference;
public static MethodReference syncVarNetworkIdentityEqualReference;
public static MethodReference syncVarGameObjectEqualReference;
public static MethodReference setSyncVarReference;
public static MethodReference setSyncVarHookGuard;
public static MethodReference getSyncVarHookGuard;
public static MethodReference setSyncVarGameObjectReference;
public static MethodReference getSyncVarGameObjectReference;
public static MethodReference setSyncVarNetworkIdentityReference;
public static MethodReference getSyncVarNetworkIdentityReference;
public static MethodReference registerCommandDelegateReference;
public static MethodReference registerRpcDelegateReference;
public static MethodReference getTypeReference;
public static MethodReference getTypeFromHandleReference;
public static MethodReference logErrorReference;
public static MethodReference logWarningReference;
public static MethodReference sendCommandInternal;
public static MethodReference sendRpcInternal;
public static MethodReference sendTargetRpcInternal;
private static AssemblyDefinition currentAssembly;
public static TypeReference Import<T>() => Import(typeof(T));
public static TypeReference Import(Type t) => currentAssembly.MainModule.ImportReference(t);
public static void SetupTargetTypes(AssemblyDefinition currentAssembly)
{
// system types
WeaverTypes.currentAssembly = currentAssembly;
TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
ArraySegmentArrayReference = Resolvers.ResolveProperty(ArraySegmentType, currentAssembly, "Array");
ArraySegmentCountReference = Resolvers.ResolveProperty(ArraySegmentType, currentAssembly, "Count");
ArraySegmentOffsetReference = Resolvers.ResolveProperty(ArraySegmentType, currentAssembly, "Offset");
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, currentAssembly, ".ctor");
TypeReference ListType = Import(typeof(System.Collections.Generic.List<>));
ListCountReference = Resolvers.ResolveProperty(ListType, currentAssembly, "Count");
ListGetItemReference = Resolvers.ResolveMethod(ListType, currentAssembly, "get_Item");
ListAddReference = Resolvers.ResolveMethod(ListType, currentAssembly, "Add");
ListConstructorReference = Resolvers.ResolveMethod(ListType, currentAssembly, ".ctor");
TypeReference NetworkServerType = Import(typeof(NetworkServer));
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, currentAssembly, "get_active");
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, currentAssembly, "get_localClientActive");
TypeReference NetworkClientType = Import(typeof(NetworkClient));
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, currentAssembly, "get_active");
TypeReference cmdDelegateReference = Import<RemoteCalls.CmdDelegate>();
CmdDelegateConstructor = Resolvers.ResolveMethod(cmdDelegateReference, currentAssembly, ".ctor");
TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
TypeReference RemoteCallHelperType = Import(typeof(RemoteCalls.RemoteCallHelper));
TypeReference ScriptableObjectType = Import<UnityEngine.ScriptableObject>();
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
ScriptableObjectType, currentAssembly,
md => md.Name == "CreateInstance" && md.HasGenericParameters);
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, currentAssembly, "syncVarDirtyBits");
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, currentAssembly, "GetWriter");
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, currentAssembly, "Recycle");
TypeReference ClientSceneType = Import(typeof(ClientScene));
ReadyConnectionReference = Resolvers.ResolveMethod(ClientSceneType, currentAssembly, "get_readyConnection");
syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarEqual");
syncVarNetworkIdentityEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarNetworkIdentityEqual");
syncVarGameObjectEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarGameObjectEqual");
setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVar");
setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "setSyncVarHookGuard");
getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "getSyncVarHookGuard");
setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarGameObject");
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarGameObject");
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarNetworkIdentity");
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarNetworkIdentity");
registerCommandDelegateReference = Resolvers.ResolveMethod(RemoteCallHelperType, currentAssembly, "RegisterCommandDelegate");
registerRpcDelegateReference = Resolvers.ResolveMethod(RemoteCallHelperType, currentAssembly, "RegisterRpcDelegate");
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
logErrorReference = Resolvers.ResolveMethod(unityDebug, currentAssembly, "LogError");
logWarningReference = Resolvers.ResolveMethod(unityDebug, currentAssembly, "LogWarning");
TypeReference typeType = Import(typeof(Type));
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, currentAssembly, "GetTypeFromHandle");
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendCommandInternal");
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendRPCInternal");
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendTargetRPCInternal");
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "InitSyncObject");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2585961bf7fe4c10a9143f4087efdf6f
timeCreated: 1596486854

View File

@@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
public static class Writers
{
static Dictionary<string, MethodReference> writeFuncs;
public static void Init()
{
writeFuncs = new Dictionary<string, MethodReference>();
}
public static void Register(TypeReference dataType, MethodReference methodReference)
{
writeFuncs[dataType.FullName] = methodReference;
}
static void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
{
writeFuncs[typeReference.FullName] = newWriterFunc;
Weaver.WeaveLists.generateContainerClass.Methods.Add(newWriterFunc);
}
/// <summary>
/// Finds existing writer for type, if non exists trys to create one
/// <para>This method is recursive</para>
/// </summary>
/// <param name="variable"></param>
/// <param name="recursionCount"></param>
/// <returns>Returns <see cref="MethodReference"/> or null</returns>
public static MethodReference GetWriteFunc(TypeReference variable)
{
if (writeFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc))
{
return foundFunc;
}
else
{
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
try
{
return GenerateWriter(variable);
}
catch (GenerateWriterException e)
{
Weaver.Error(e.Message, e.MemberReference);
return null;
}
}
}
/// <exception cref="GenerateWriterException">Throws when writer could not be generated for type</exception>
static MethodDefinition GenerateWriter(TypeReference variableReference)
{
if (variableReference.IsByReference)
{
throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference);
}
// Arrays are special, if we resolve them, we get the element type,
// eg int[] resolves to int
// therefore process this before checks below
if (variableReference.IsArray)
{
if (variableReference.IsMultidimensionalArray())
{
throw new GenerateWriterException($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
}
TypeReference elementType = variableReference.GetElementType();
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArray));
}
if (variableReference.Resolve()?.IsEnum ?? false)
{
// serialize enum as their base type
return GenerateEnumWriteFunc(variableReference);
}
// check for collections
if (variableReference.Is(typeof(ArraySegment<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment));
}
if (variableReference.Is(typeof(List<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList));
}
// check for invalid types
TypeDefinition variableDefinition = variableReference.Resolve();
if (variableDefinition == null)
{
throw new GenerateWriterException($"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
{
throw new GenerateWriterException($"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableReference.Is<UnityEngine.Object>())
{
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableReference.Is<UnityEngine.ScriptableObject>())
{
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.HasGenericParameters)
{
throw new GenerateWriterException($"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsInterface)
{
throw new GenerateWriterException($"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
if (variableDefinition.IsAbstract)
{
throw new GenerateWriterException($"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
// generate writer for class/struct
return GenerateClassOrStructWriterFunction(variableReference);
}
private static MethodDefinition GenerateEnumWriteFunc(TypeReference variable)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
ILProcessor worker = writerFunc.Body.GetILProcessor();
MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType());
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Call, underlyingWriter));
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
private static MethodDefinition GenerateWriterFunc(TypeReference variable)
{
string functionName = "_Write_" + variable.FullName;
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
WeaverTypes.Import(typeof(void)));
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, WeaverTypes.Import<NetworkWriter>()));
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable)));
writerFunc.Body.InitLocals = true;
RegisterWriteFunc(variable, writerFunc);
return writerFunc;
}
static MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
ILProcessor worker = writerFunc.Body.GetILProcessor();
if (!variable.Resolve().IsValueType)
WriteNullCheck(worker);
if (!WriteAllFields(variable, worker))
return null;
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
private static void WriteNullCheck(ILProcessor worker)
{
// if (value == null)
// {
// writer.WriteBoolean(false);
// return;
// }
//
Instruction labelNotNull = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Brtrue, labelNotNull));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.Import<bool>())));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(labelNotNull);
// write.WriteBoolean(true);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.Import<bool>())));
}
/// <summary>
/// Find all fields in type and write them
/// </summary>
/// <param name="variable"></param>
/// <param name="worker"></param>
/// <returns>false if fail</returns>
static bool WriteAllFields(TypeReference variable, ILProcessor worker)
{
uint fields = 0;
foreach (FieldDefinition field in variable.FindAllPublicFields())
{
MethodReference writeFunc = GetWriteFunc(field.FieldType);
// need this null check till later PR when GetWriteFunc throws exception instead
if (writeFunc == null) { return false; }
FieldReference fieldRef = Weaver.CurrentAssembly.MainModule.ImportReference(field);
fields++;
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldfld, fieldRef));
worker.Append(worker.Create(OpCodes.Call, writeFunc));
}
return true;
}
static MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
MethodReference elementWriteFunc = GetWriteFunc(elementType);
MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import<int>());
// need this null check till later PR when GetWriteFunc throws exception instead
if (elementWriteFunc == null)
{
Weaver.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
return writerFunc;
}
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, Weaver.CurrentAssembly, writerFunction);
GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter);
methodRef.GenericArguments.Add(elementType);
// generates
// reader.WriteArray<T>(array);
ILProcessor worker = writerFunc.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ldarg_0)); // writer
worker.Append(worker.Create(OpCodes.Ldarg_1)); // collection
worker.Append(worker.Create(OpCodes.Call, methodRef)); // WriteArray
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
/// <summary>
/// Save a delegate for each one of the writers into <see cref="Writer{T}.write"/>
/// </summary>
/// <param name="worker"></param>
internal static void InitializeWriters(ILProcessor worker)
{
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>));
System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write));
FieldReference fieldRef = module.ImportReference(fieldInfo);
TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter));
TypeReference actionRef = module.ImportReference(typeof(Action<,>));
MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
foreach (MethodReference writerMethod in writeFuncs.Values)
{
TypeReference dataType = writerMethod.Parameters[1].ParameterType;
// create a Action<NetworkWriter, T> delegate
worker.Append(worker.Create(OpCodes.Ldnull));
worker.Append(worker.Create(OpCodes.Ldftn, writerMethod));
GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, dataType);
MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(actionGenericInstance);
worker.Append(worker.Create(OpCodes.Newobj, actionRefInstance));
// save it in Writer<T>.write
GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(dataType);
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
worker.Append(worker.Create(OpCodes.Stsfld, specializedField));
}
}
}
}

View File

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