CHANGED TO MIRROR

This commit is contained in:
DerTyp187
2021-10-25 09:20:01 +02:00
parent bd712107b7
commit e509a919b6
611 changed files with 38291 additions and 1216 deletions

View File

@@ -0,0 +1,31 @@
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Mirror
{
public static class EditorHelper
{
public static string FindPath<T>()
{
string typeName = typeof(T).Name;
string[] guidsFound = AssetDatabase.FindAssets($"t:Script {typeName}");
if (guidsFound.Length >= 1 && !string.IsNullOrEmpty(guidsFound[0]))
{
if (guidsFound.Length > 1)
{
Debug.LogWarning($"Found more than one{typeName}");
}
string path = AssetDatabase.GUIDToAssetPath(guidsFound[0]);
return Path.GetDirectoryName(path);
}
else
{
Debug.LogError($"Could not find path of {typeName}");
return string.Empty;
}
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 62c8dc5bb12bbc6428bb66ccbac57000
folderAsset: yes
DefaultImporter:
externalObjects: {}
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 @@
// removed 2021-02-16

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 @@
// removed 2021-02-16

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 @@
// removed 2021-02-16

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 @@
// removed 2021-02-16

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 @@
// removed 2021-02-16

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,52 @@
// 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)
{
#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
}
}
#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,77 @@
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>
// deepestBaseType: Stops at this base type (exclusive)
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 ImplementsInterface<T>(this FieldInfo field)
{
return typeof(T).IsAssignableFrom(field.FieldType);
}
public static bool HasShowInInspector(this FieldInfo field)
{
object[] fieldMarkers = field.GetCustomAttributes(typeof(ShowInInspectorAttribute), true);
return fieldMarkers.Length > 0;
}
// checks if SyncObject is public or has our custom [ShowInInspector] field
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,19 @@
{
"name": "Mirror.Editor",
"rootNamespace": "",
"references": [
"Mirror",
"Unity.Mirror.CodeGen"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

@@ -0,0 +1,95 @@
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkBehaviour), true)]
[CanEditMultipleObjects]
public class NetworkBehaviourInspector : Editor
{
bool syncsAnything;
SyncObjectCollectionsDrawer syncObjectCollectionsDrawer;
// 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 check length of list so see if sync objects exists
return ((NetworkBehaviour)serializedObject.targetObject).HasSyncObjects();
}
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();
syncObjectCollectionsDrawer = new SyncObjectCollectionsDrawer(serializedObject.targetObject);
syncsAnything = SyncsAnything(scriptClass);
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
DrawSyncObjectCollections();
DrawDefaultSyncSettings();
}
// Draws Sync Objects that are IEnumerable
protected void DrawSyncObjectCollections()
{
// Need this check in case OnEnable returns early
if (syncObjectCollectionsDrawer == null) return;
syncObjectCollectionsDrawer.Draw();
}
// Draws SyncSettings if the NetworkBehaviour has anything to sync
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();
}
}
}

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,108 @@
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,108 @@
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" &&
!Utils.IsPrefab(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
{
// there are two cases where sceneId == 0:
// * if we have a prefab open in the prefab scene
// * if an unopened scene needs resaving
// show a proper error message in both cases so the user
// knows what to do.
string path = identity.gameObject.scene.path;
if (string.IsNullOrWhiteSpace(path))
Debug.LogError($"{identity.name} is currently open in Prefab Edit Mode. Please open the actual scene before launching Mirror.");
else
Debug.LogError($"Scene {path} needs to be opened and resaved, because the scene object {identity.name} has no valid sceneId yet.");
// either way we shouldn't continue. nothing good will
// happen when trying to launch with invalid sceneIds.
EditorApplication.isPlaying = false;
}
}
}
}
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.LogWarning($"Prefab {prefabRootGO.name} has several NetworkIdentity components attached to itself or its children, this is not supported.");
}
}
}
}
}

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!= null && 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,83 @@
// helper class for NetworkBehaviourInspector to draw all enumerable SyncObjects
// (SyncList/Set/Dictionary)
// 'SyncObjectCollectionsDrawer' is a nicer name than 'IEnumerableSyncObjectsDrawer'
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
namespace Mirror
{
class SyncObjectCollectionField
{
public bool visible;
public readonly FieldInfo field;
public readonly string label;
public SyncObjectCollectionField(FieldInfo field)
{
this.field = field;
visible = false;
label = $"{field.Name} [{field.FieldType.Name}]";
}
}
public class SyncObjectCollectionsDrawer
{
readonly UnityEngine.Object targetObject;
readonly List<SyncObjectCollectionField> syncObjectCollectionFields;
public SyncObjectCollectionsDrawer(UnityEngine.Object targetObject)
{
this.targetObject = targetObject;
syncObjectCollectionFields = new List<SyncObjectCollectionField>();
foreach (FieldInfo field in InspectorHelper.GetAllFields(targetObject.GetType(), typeof(NetworkBehaviour)))
{
// only draw SyncObjects that are IEnumerable (SyncList/Set/Dictionary)
if (field.IsVisibleSyncObject() &&
field.ImplementsInterface<SyncObject>() &&
field.ImplementsInterface<IEnumerable>())
{
syncObjectCollectionFields.Add(new SyncObjectCollectionField(field));
}
}
}
public void Draw()
{
if (syncObjectCollectionFields.Count == 0) { return; }
EditorGUILayout.Space();
EditorGUILayout.LabelField("Sync Collections", EditorStyles.boldLabel);
for (int i = 0; i < syncObjectCollectionFields.Count; i++)
{
DrawSyncObjectCollection(syncObjectCollectionFields[i]);
}
}
void DrawSyncObjectCollection(SyncObjectCollectionField syncObjectCollectionField)
{
syncObjectCollectionField.visible = EditorGUILayout.Foldout(syncObjectCollectionField.visible, syncObjectCollectionField.label);
if (syncObjectCollectionField.visible)
{
using (new EditorGUI.IndentLevelScope())
{
object fieldValue = syncObjectCollectionField.field.GetValue(targetObject);
if (fieldValue is IEnumerable syncObject)
{
int index = 0;
foreach (object item in syncObject)
{
string itemValue = item != null ? item.ToString() : "NULL";
string itemLabel = $"Element {index}";
EditorGUILayout.LabelField(itemLabel, itemValue);
index++;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6f90afab12e04f0e945d83e9d38308a3
timeCreated: 1632556645

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, label, true);
GUI.Label(labelRect, syncVarIndicatorContent, EditorStyles.miniLabel);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property);
}
}
}

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,8 @@
fileFormatVersion: 2
guid: 30fc290f2ff9c29498f54f63de12ca6f
folderAsset: yes
DefaultImporter:
externalObjects: {}
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 @@
// 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 @@
// 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,3 @@
fileFormatVersion: 2
guid: 251338e67afb4cefa38da924f8c50a6e
timeCreated: 1628851818

View File

@@ -0,0 +1,189 @@
// for Unity 2020+ we use ILPostProcessor.
// only automatically invoke it for older versions.
#if !UNITY_2020_3_OR_NEWER
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.CecilX;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
using UnityAssembly = UnityEditor.Compilation.Assembly;
namespace Mirror.Weaver
{
public static class CompilationFinishedHook
{
// needs to be the same as Weaver.MirrorAssemblyName!
const string MirrorRuntimeAssemblyName = "Mirror";
const string MirrorWeaverAssemblyName = "Mirror.Weaver";
// global weaver define so that tests can use it
internal static Weaver weaver;
// 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 weather Weaver errors are reported direct to the Unity console (tests enable this)
public static bool UnityLogEnabled = true;
[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 Assembly FindCompilationPipelineAssembly(string assemblyName) =>
CompilationPipeline.GetAssemblies().First(assembly => assembly.name == assemblyName);
static bool CompilerMessagesContainError(CompilerMessage[] messages) =>
messages.Any(msg => msg.type == CompilerMessageType.Error);
public 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
Assembly mirrorAssembly = FindCompilationPipelineAssembly(MirrorRuntimeAssemblyName);
if (mirrorAssembly == null)
{
Debug.LogError("Failed to find Mirror runtime assembly");
return;
}
string mirrorRuntimeDll = mirrorAssembly.outputPath;
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 = GetDependencyPaths(assemblyPath);
dependencyPaths.Add(Path.GetDirectoryName(mirrorRuntimeDll));
dependencyPaths.Add(Path.GetDirectoryName(unityEngineCoreModuleDLL));
if (!WeaveFromFile(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> GetDependencyPaths(string assemblyPath)
{
// build directory list for later asm/symbol resolving using CompilationPipeline refs
HashSet<string> dependencyPaths = new HashSet<string>
{
Path.GetDirectoryName(assemblyPath)
};
foreach (Assembly assembly in CompilationPipeline.GetAssemblies())
{
if (assembly.outputPath == assemblyPath)
{
foreach (string reference in assembly.compiledAssemblyReferences)
{
dependencyPaths.Add(Path.GetDirectoryName(reference));
}
}
}
return dependencyPaths;
}
// helper function to invoke Weaver with an AssemblyDefinition from a
// file path, with dependencies added.
static bool WeaveFromFile(string assemblyPath, string[] dependencies)
{
// resolve assembly from stream
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters{ ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
{
// add this assembly's path and unity's assembly path
asmResolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath));
asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName());
// add dependencies
if (dependencies != null)
{
foreach (string path in dependencies)
{
asmResolver.AddSearchDirectory(path);
}
}
// create weaver with logger
weaver = new Weaver(new CompilationFinishedLogger());
if (weaver.Weave(assembly, asmResolver, out bool modified))
{
// write changes to file if modified
if (modified)
assembly.Write(new WriterParameters{WriteSymbols = true});
return true;
}
return false;
}
}
}
}
#endif

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,31 @@
// logger for compilation finished hook.
// where we need a callback and Debug.Log.
// for Unity 2020+ we use ILPostProcessor.
#if !UNITY_2020_3_OR_NEWER
using Mono.CecilX;
using UnityEngine;
namespace Mirror.Weaver
{
public class CompilationFinishedLogger : Logger
{
public void Warning(string message) => Warning(message, null);
public void Warning(string message, MemberReference mr)
{
if (mr != null) message = $"{message} (at {mr})";
if (CompilationFinishedHook.UnityLogEnabled) Debug.LogWarning(message);
CompilationFinishedHook.OnWeaverWarning?.Invoke(message);
}
public void Error(string message) => Error(message, null);
public void Error(string message, MemberReference mr)
{
if (mr != null) message = $"{message} (at {mr})";
if (CompilationFinishedHook.UnityLogEnabled) Debug.LogError(message);
CompilationFinishedHook.OnWeaverError?.Invoke(message);
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 47026732f0fa475c94bd1dd41f1de559
timeCreated: 1629379868

View File

@@ -0,0 +1,44 @@
#if !UNITY_2020_3_OR_NEWER
// make sure we weaved successfully when entering play mode.
using UnityEditor;
using UnityEngine;
namespace Mirror
{
public class EnterPlayModeSettingsCheck : MonoBehaviour
{
[InitializeOnLoadMethod]
static void OnInitializeOnLoad()
{
// 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)
{
// 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;
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b73d0f106ba84aa983baa5142b08a0a9
timeCreated: 1628851346

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 09082db63d1d48d9ab91320165c1b684
timeCreated: 1628859005

View File

@@ -0,0 +1,31 @@
// tests use WeaveAssembler, which uses AssemblyBuilder to Build().
// afterwards ILPostProcessor weaves the build.
// this works on windows, but build() does not run ILPP on mac atm.
// we need to manually invoke ILPP with an assembly from file.
//
// this is in Weaver folder becuase CompilationPipeline can only be accessed
// from assemblies with the name "Unity.*.CodeGen"
using System.IO;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Mirror.Weaver
{
public class CompiledAssemblyFromFile : ICompiledAssembly
{
readonly string assemblyPath;
public string Name => Path.GetFileNameWithoutExtension(assemblyPath);
public string[] References { get; set; }
public string[] Defines { get; set; }
public InMemoryAssembly InMemoryAssembly { get; }
public CompiledAssemblyFromFile(string assemblyPath)
{
this.assemblyPath = assemblyPath;
byte[] peData = File.ReadAllBytes(assemblyPath);
string pdbFileName = Path.GetFileNameWithoutExtension(assemblyPath) + ".pdb";
byte[] pdbData = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(assemblyPath), pdbFileName));
InMemoryAssembly = new InMemoryAssembly(peData, pdbData);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9009d1db4ed44f6694a92bf8ad7738e9
timeCreated: 1630129423

View File

@@ -0,0 +1,167 @@
// based on paul's resolver from
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
//
// an assembly resolver's job is to open an assembly in case we want to resolve
// a type from it.
//
// for example, while weaving MyGame.dll: if we want to resolve ArraySegment<T>,
// then we need to open and resolve from another assembly (CoreLib).
//
// using DefaultAssemblyResolver with ILPostProcessor throws Exceptions in
// WeaverTypes.cs when resolving anything, for example:
// ArraySegment<T> in Mirror.Tests.Dll.
//
// we need a custom resolver for ILPostProcessor.
#if UNITY_2020_3_OR_NEWER
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Mono.CecilX;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Mirror.Weaver
{
class ILPostProcessorAssemblyResolver : IAssemblyResolver
{
readonly string[] assemblyReferences;
readonly Dictionary<string, AssemblyDefinition> assemblyCache =
new Dictionary<string, AssemblyDefinition>();
readonly ICompiledAssembly compiledAssembly;
AssemblyDefinition selfAssembly;
Logger Log;
public ILPostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly, Logger Log)
{
this.compiledAssembly = compiledAssembly;
assemblyReferences = compiledAssembly.References;
this.Log = Log;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// Cleanup
}
public AssemblyDefinition Resolve(AssemblyNameReference name) =>
Resolve(name, new ReaderParameters(ReadingMode.Deferred));
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
lock (assemblyCache)
{
if (name.Name == compiledAssembly.Name)
return selfAssembly;
string fileName = FindFile(name);
if (fileName == null)
{
// returning null will throw exceptions in our weaver where.
// let's make it obvious why we returned null for easier debugging.
// NOTE: if this fails for "System.Private.CoreLib":
// ILPostProcessorReflectionImporter fixes it!
Log.Warning($"ILPostProcessorAssemblyResolver.Resolve: Failed to find file for {name}");
return null;
}
DateTime lastWriteTime = File.GetLastWriteTime(fileName);
string cacheKey = fileName + lastWriteTime;
if (assemblyCache.TryGetValue(cacheKey, out AssemblyDefinition result))
return result;
parameters.AssemblyResolver = this;
MemoryStream ms = MemoryStreamFor(fileName);
string pdb = fileName + ".pdb";
if (File.Exists(pdb))
parameters.SymbolStream = MemoryStreamFor(pdb);
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters);
assemblyCache.Add(cacheKey, assemblyDefinition);
return assemblyDefinition;
}
}
// find assemblyname in assembly's references
string FindFile(AssemblyNameReference name)
{
string fileName = assemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == name.Name + ".dll");
if (fileName != null)
return fileName;
// perhaps the type comes from an exe instead
fileName = assemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == name.Name + ".exe");
if (fileName != null)
return fileName;
// Unfortunately the current ICompiledAssembly API only provides direct references.
// It is very much possible that a postprocessor ends up investigating a type in a directly
// referenced assembly, that contains a field that is not in a directly referenced assembly.
// if we don't do anything special for that situation, it will fail to resolve. We should fix this
// in the ILPostProcessing API. As a workaround, we rely on the fact here that the indirect references
// are always located next to direct references, so we search in all directories of direct references we
// got passed, and if we find the file in there, we resolve to it.
foreach (string parentDir in assemblyReferences.Select(Path.GetDirectoryName).Distinct())
{
string candidate = Path.Combine(parentDir, name.Name + ".dll");
if (File.Exists(candidate))
return candidate;
}
return null;
}
// open file as MemoryStream
// attempts multiple times, not sure why..
static MemoryStream MemoryStreamFor(string fileName)
{
return Retry(10, TimeSpan.FromSeconds(1), () =>
{
byte[] byteArray;
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
byteArray = new byte[fs.Length];
int readLength = fs.Read(byteArray, 0, (int)fs.Length);
if (readLength != fs.Length)
throw new InvalidOperationException("File read length is not full length of file.");
}
return new MemoryStream(byteArray);
});
}
static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func)
{
try
{
return func();
}
catch (IOException)
{
if (retryCount == 0)
throw;
Console.WriteLine($"Caught IO Exception, trying {retryCount} more times");
Thread.Sleep(waitTime);
return Retry(retryCount - 1, waitTime, func);
}
}
// if the CompiledAssembly's AssemblyDefinition is known, we can add it
public void SetAssemblyDefinitionForCompiledAssembly(AssemblyDefinition assemblyDefinition)
{
selfAssembly = assemblyDefinition;
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0b3e94696e22440ead0b3a42411bbe14
timeCreated: 1629693784

View File

@@ -0,0 +1,53 @@
// helper function to use ILPostProcessor for an assembly from file.
// we keep this in Weaver folder because we can access CompilationPipleine here.
// in tests folder we can't, unless we rename to "Unity.*.CodeGen",
// but then tests wouldn't be weaved anymore.
#if UNITY_2020_3_OR_NEWER
using System;
using System.IO;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Mirror.Weaver
{
public static class ILPostProcessorFromFile
{
// read, weave, write file via ILPostProcessor
public static void ILPostProcessFile(string assemblyPath, string[] references, Action<string> OnWarning, Action<string> OnError)
{
// we COULD Weave() with a test logger manually.
// but for test result consistency on all platforms,
// let's invoke the ILPostProcessor here too.
CompiledAssemblyFromFile assembly = new CompiledAssemblyFromFile(assemblyPath);
assembly.References = references;
// create ILPP and check WillProcess like Unity would.
ILPostProcessorHook ilpp = new ILPostProcessorHook();
if (ilpp.WillProcess(assembly))
{
//Debug.Log($"Will Process: {assembly.Name}");
// process it like Unity would
ILPostProcessResult result = ilpp.Process(assembly);
// handle the error messages like Unity would
foreach (DiagnosticMessage message in result.Diagnostics)
{
if (message.DiagnosticType == DiagnosticType.Warning)
{
OnWarning(message.MessageData);
}
else if (message.DiagnosticType == DiagnosticType.Error)
{
OnError(message.MessageData);
}
}
// save the weaved assembly to file.
// some tests open it and check for certain IL code.
File.WriteAllBytes(assemblyPath, result.InMemoryAssembly.PeData);
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2a4b115486b74d27a9540f3c39ae2d46
timeCreated: 1630152191

View File

@@ -0,0 +1,143 @@
// hook via ILPostProcessor from Unity 2020.3+
// (2020.1 has errors https://github.com/vis2k/Mirror/issues/2912)
#if UNITY_2020_3_OR_NEWER
// Unity.CompilationPipeline reference is only resolved if assembly name is
// Unity.*.CodeGen:
// https://forum.unity.com/threads/how-does-unity-do-codegen-and-why-cant-i-do-it-myself.853867/#post-5646937
using System.IO;
using System.Linq;
// to use Mono.CecilX here, we need to 'override references' in the
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.
// otherwise we get a reflection exception with 'file not found: CecilX'.
using Mono.CecilX;
using Mono.CecilX.Cil;
using Unity.CompilationPipeline.Common.ILPostProcessing;
// IMPORTANT: 'using UnityEngine' does not work in here.
// Unity gives "(0,0): error System.Security.SecurityException: ECall methods must be packaged into a system module."
//using UnityEngine;
namespace Mirror.Weaver
{
public class ILPostProcessorHook : ILPostProcessor
{
// from CompilationFinishedHook
const string MirrorRuntimeAssemblyName = "Mirror";
// ILPostProcessor is invoked by Unity.
// we can not tell it to ignore certain assemblies before processing.
// add a 'ignore' define for convenience.
// => WeaverTests/WeaverAssembler need it to avoid Unity running it
public const string IgnoreDefine = "ILPP_IGNORE";
// we can't use Debug.Log in ILPP, so we need a custom logger
ILPostProcessorLogger Log = new ILPostProcessorLogger();
// ???
public override ILPostProcessor GetInstance() => this;
// check if assembly has the 'ignore' define
static bool HasDefine(ICompiledAssembly assembly, string define) =>
assembly.Defines != null &&
assembly.Defines.Contains(define);
// process Mirror, or anything that references Mirror
public override bool WillProcess(ICompiledAssembly compiledAssembly)
{
// compiledAssembly.References are file paths:
// Library/Bee/artifacts/200b0aE.dag/Mirror.CompilerSymbols.dll
// Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll
// /Applications/Unity/Hub/Editor/2021.2.0b6_apple_silicon/Unity.app/Contents/NetStandard/ref/2.1.0/netstandard.dll
//
// log them to see:
// foreach (string reference in compiledAssembly.References)
// LogDiagnostics($"{compiledAssembly.Name} references {reference}");
bool relevant = compiledAssembly.Name == MirrorRuntimeAssemblyName ||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == MirrorRuntimeAssemblyName);
bool ignore = HasDefine(compiledAssembly, IgnoreDefine);
return relevant && !ignore;
}
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
//Log.Warning($"Processing {compiledAssembly.Name}");
// load the InMemoryAssembly peData into a MemoryStream
byte[] peData = compiledAssembly.InMemoryAssembly.PeData;
//LogDiagnostics($" peData.Length={peData.Length} bytes");
using (MemoryStream stream = new MemoryStream(peData))
using (ILPostProcessorAssemblyResolver asmResolver = new ILPostProcessorAssemblyResolver(compiledAssembly, Log))
{
// we need to load symbols. otherwise we get:
// "(0,0): error Mono.CecilX.Cil.SymbolsNotFoundException: No symbol found for file: "
using (MemoryStream symbols = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData))
{
ReaderParameters readerParameters = new ReaderParameters{
SymbolStream = symbols,
ReadWrite = true,
ReadSymbols = true,
AssemblyResolver = asmResolver,
// custom reflection importer to fix System.Private.CoreLib
// not being found in custom assembly resolver above.
ReflectionImporterProvider = new ILPostProcessorReflectionImporterProvider()
};
using (AssemblyDefinition asmDef = AssemblyDefinition.ReadAssembly(stream, readerParameters))
{
// resolving a Mirror.dll type like NetworkServer while
// weaving Mirror.dll does not work. it throws a
// NullReferenceException in WeaverTypes.ctor
// when Resolve() is called on the first Mirror type.
// need to add the AssemblyDefinition itself to use.
asmResolver.SetAssemblyDefinitionForCompiledAssembly(asmDef);
// weave this assembly.
Weaver weaver = new Weaver(Log);
if (weaver.Weave(asmDef, asmResolver, out bool modified))
{
//Log.Warning($"Weaving succeeded for: {compiledAssembly.Name}");
// write if modified
if (modified)
{
// when weaving Mirror.dll with ILPostProcessor,
// Weave() -> WeaverTypes -> resolving the first
// type in Mirror.dll adds a reference to
// Mirror.dll even though we are in Mirror.dll.
// -> this would throw an exception:
// "Mirror references itself" and not compile
// -> need to detect and fix manually here
if (asmDef.MainModule.AssemblyReferences.Any(r => r.Name == asmDef.Name.Name))
{
asmDef.MainModule.AssemblyReferences.Remove(asmDef.MainModule.AssemblyReferences.First(r => r.Name == asmDef.Name.Name));
//Log.Warning($"fixed self referencing Assembly: {asmDef.Name.Name}");
}
MemoryStream peOut = new MemoryStream();
MemoryStream pdbOut = new MemoryStream();
WriterParameters writerParameters = new WriterParameters
{
SymbolWriterProvider = new PortablePdbWriterProvider(),
SymbolStream = pdbOut,
WriteSymbols = true
};
asmDef.Write(peOut, writerParameters);
InMemoryAssembly inMemory = new InMemoryAssembly(peOut.ToArray(), pdbOut.ToArray());
return new ILPostProcessResult(inMemory, Log.Logs);
}
}
// if anything during Weave() fails, we log an error.
// don't need to indicate 'weaving failed' again.
// in fact, this would break tests only expecting certain errors.
//else Log.Error($"Weaving failed for: {compiledAssembly.Name}");
}
}
}
// always return an ILPostProcessResult with Logs.
// otherwise we won't see Logs if weaving failed.
return new ILPostProcessResult(compiledAssembly.InMemoryAssembly, Log.Logs);
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5f113eb695b348b5b28cd85358c8959a
timeCreated: 1628859074

View File

@@ -0,0 +1,67 @@
using System.Collections.Generic;
using Mono.CecilX;
using Unity.CompilationPipeline.Common.Diagnostics;
namespace Mirror.Weaver
{
public class ILPostProcessorLogger : Logger
{
// can't Debug.Log in ILPostProcessor. need to add to this list.
internal List<DiagnosticMessage> Logs = new List<DiagnosticMessage>();
void Add(string message, DiagnosticType logType)
{
Logs.Add(new DiagnosticMessage
{
// TODO add file etc. for double click opening later?
DiagnosticType = logType, // doesn't have .Log
File = null,
Line = 0,
Column = 0,
MessageData = message
});
}
public void LogDiagnostics(string message, DiagnosticType logType = DiagnosticType.Warning)
{
// DiagnosticMessage can't display \n for some reason.
// it just cuts it off and we don't see any stack trace.
// so let's replace all line breaks so we get the stack trace.
// (Unity 2021.2.0b6 apple silicon)
//message = message.Replace("\n", "/");
// lets break it into several messages instead so it's easier readable
string[] lines = message.Split('\n');
// if it's just one line, simply log it
if (lines.Length == 1)
{
// tests assume exact message log.
// don't include 'Weaver: ...' or similar.
Add($"{message}", logType);
}
// for multiple lines, log each line separately with start/end indicators
else
{
// first line with Weaver: ... first
Add("----------------------------------------------", logType);
foreach (string line in lines) Add(line, logType);
Add("----------------------------------------------", logType);
}
}
public void Warning(string message) => Warning(message, null);
public void Warning(string message, MemberReference mr)
{
if (mr != null) message = $"{message} (at {mr})";
LogDiagnostics(message, DiagnosticType.Warning);
}
public void Error(string message) => Error(message, null);
public void Error(string message, MemberReference mr)
{
if (mr != null) message = $"{message} (at {mr})";
LogDiagnostics(message, DiagnosticType.Error);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e7b56e7826664e34a415e4b70d958f2a
timeCreated: 1629533154

View File

@@ -0,0 +1,36 @@
// based on paul's resolver from
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
//
// ILPostProcessorAssemblyRESOLVER does not find the .dll file for:
// "System.Private.CoreLib"
// we need this custom reflection importer to fix that.
using System.Linq;
using System.Reflection;
using Mono.CecilX;
namespace Mirror.Weaver
{
internal class ILPostProcessorReflectionImporter : DefaultReflectionImporter
{
const string SystemPrivateCoreLib = "System.Private.CoreLib";
readonly AssemblyNameReference fixedCoreLib;
public ILPostProcessorReflectionImporter(ModuleDefinition module) : base(module)
{
// find the correct library for "System.Private.CoreLib".
// either mscorlib or netstandard.
// defaults to System.Private.CoreLib if not found.
fixedCoreLib = module.AssemblyReferences.FirstOrDefault(a => a.Name == "mscorlib" || a.Name == "netstandard" || a.Name == SystemPrivateCoreLib);
}
public override AssemblyNameReference ImportReference(AssemblyName name)
{
// System.Private.CoreLib?
if (name.Name == SystemPrivateCoreLib && fixedCoreLib != null)
return fixedCoreLib;
// otherwise import as usual
return base.ImportReference(name);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6403a7e3b3ae4e009ae282f111d266e0
timeCreated: 1629709256

View File

@@ -0,0 +1,16 @@
// based on paul's resolver from
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
//
// ILPostProcessorAssemblyRESOLVER does not find the .dll file for:
// "System.Private.CoreLib"
// we need this custom reflection importer to fix that.
using Mono.CecilX;
namespace Mirror.Weaver
{
internal class ILPostProcessorReflectionImporterProvider : IReflectionImporterProvider
{
public IReflectionImporter GetReflectionImporter(ModuleDefinition module) =>
new ILPostProcessorReflectionImporter(module);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a1003b568bad4e69b961c4c81d5afd96
timeCreated: 1629709223

View File

@@ -0,0 +1,259 @@
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 TypeReference tr) => IsDerivedFrom(tr, typeof(T));
public static bool IsDerivedFrom(this TypeReference tr, Type baseClass)
{
TypeDefinition td = tr.Resolve();
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)
{
if (typedef.Interfaces.Any(iface => iface.InterfaceType.Is<TInterface>()))
return true;
try
{
TypeReference parent = typedef.BaseType;
typedef = parent?.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
return false;
}
public static bool IsMultidimensionalArray(this TypeReference tr) =>
tr is ArrayType arrayType && arrayType.Rank > 1;
// Does type use netId as backing field
public static bool IsNetworkIdentityField(this TypeReference tr) =>
tr.Is<UnityEngine.GameObject>() ||
tr.Is<NetworkIdentity>() ||
tr.IsDerivedFrom<NetworkBehaviour>();
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;
}
// Makes T => Variable and imports function
public static MethodReference MakeGeneric(this MethodReference generic, ModuleDefinition module, TypeReference variableReference)
{
GenericInstanceMethod instance = new GenericInstanceMethod(generic);
instance.GenericArguments.Add(variableReference);
MethodReference readFunc = module.ImportReference(instance);
return readFunc;
}
// 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
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, ModuleDefinition module, 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 module.ImportReference(reference);
}
// 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
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
public static FieldReference SpecializeField(this FieldReference self, ModuleDefinition module, GenericInstanceType instanceType)
{
FieldReference reference = new FieldReference(self.Name, self.FieldType, instanceType);
return module.ImportReference(reference);
}
public static CustomAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider method)
{
return method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Is<TAttribute>());
}
public static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider)
{
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)
{
return td.Methods.FirstOrDefault(method => method.Name == methodName);
}
public static List<MethodDefinition> GetMethods(this TypeDefinition td, string methodName)
{
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;
}
// Finds public fields in type and base type
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable)
{
return FindAllPublicFields(variable.Resolve());
}
// Finds public fields in type and base type
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;
}
}
}
public static bool ContainsClass(this ModuleDefinition module, string nameSpace, string className) =>
module.GetTypes().Any(td => td.Namespace == nameSpace &&
td.Name == className);
public static AssemblyNameReference FindReference(this ModuleDefinition module, string referenceName)
{
foreach (AssemblyNameReference reference in module.AssemblyReferences)
{
if (reference.Name == referenceName)
return reference;
}
return null;
}
}
}

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,26 @@
using System.IO;
using System.Linq;
using System.Reflection;
using Mono.CecilX;
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 bool IsEditorAssembly(AssemblyDefinition currentAssembly)
{
// we want to add the [InitializeOnLoad] attribute if it's available
// -> usually either 'UnityEditor' or 'UnityEditor.CoreModule'
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
assemblyReference.Name.StartsWith(nameof(UnityEditor))
);
}
}
}

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,13 @@
using Mono.CecilX;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
public interface Logger
{
void Warning(string message);
void Warning(string message, MemberReference mr);
void Error(string message);
void Error(string message, MemberReference mr);
}
}

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,8 @@
fileFormatVersion: 2
guid: e538d627280d2471b8c72fdea822ca49
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,125 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
// Processes [Command] methods in NetworkBehaviour
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(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed)
{
MethodDefinition cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
// NetworkWriter writer = new NetworkWriter();
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
// write all the arguments that the user passed to the Cmd call
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed))
return null;
string cmdName = md.Name;
int channel = commandAttr.GetField("channel", 0);
bool requiresAuthority = commandAttr.GetField("requiresAuthority", true);
// invoke internal send and return
// load 'base.' to call the SendCommand function with
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldtoken, td);
// invokerClass
worker.Emit(OpCodes.Call, weaverTypes.getTypeFromHandleReference);
worker.Emit(OpCodes.Ldstr, cmdName);
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel);
// requiresAuthority ? 1 : 0
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
worker.Emit(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(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc, ref bool WeavingFailed)
{
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, weaverTypes, method.Name, label, "Command");
// setup for reader
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td);
if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed))
return null;
AddSenderConnection(method, worker);
// invoke actual command function
worker.Emit(OpCodes.Callvirt, cmdCallFunc);
worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, 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)
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
worker.Emit(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,130 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class MethodProcessor
{
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(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed)
{
string newName = RpcPrefix + md.Name;
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
// force the substitute method to be protected.
// -> public would show in the Inspector for UnityEvents as
// User_CmdUsePotion() etc. but the user shouldn't use those.
// -> private would not allow inheriting classes to call it, see
// OverrideVirtualWithBaseCallsBothVirtualAndBase test.
// -> IL has no concept of 'protected', it's called IsFamily there.
cmd.IsPublic = false;
cmd.IsFamily = true;
// 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(Log, td, cmd, ref WeavingFailed);
return cmd;
}
// Finds and fixes call to base methods within remote calls
//For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`
public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed)
{
string callName = method.Name;
// Cmd/rpc start with Weaver.RpcPrefix
// e.g. CallCmdDoSomething
if (!callName.StartsWith(RpcPrefix))
return;
// e.g. 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)
{
Log.Error($"Could not find base method for {callName}", method);
WeavingFailed = true;
return;
}
if (!baseMethod.IsVirtual)
{
Log.Error($"Could not find base method that was virtual {callName}", method);
WeavingFailed = true;
return;
}
instruction.Operand = baseMethod;
}
}
}
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,56 @@
using Mono.CecilX;
namespace Mirror.Weaver
{
// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
static class MonoBehaviourProcessor
{
public static void Process(Logger Log, TypeDefinition td, ref bool WeavingFailed)
{
ProcessSyncVars(Log, td, ref WeavingFailed);
ProcessMethods(Log, td, ref WeavingFailed);
}
static void ProcessSyncVars(Logger Log, TypeDefinition td, ref bool WeavingFailed)
{
// find syncvars
foreach (FieldDefinition fd in td.Fields)
{
if (fd.HasCustomAttribute<SyncVarAttribute>())
{
Log.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
WeavingFailed = true;
}
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
{
Log.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
WeavingFailed = true;
}
}
}
static void ProcessMethods(Logger Log, TypeDefinition td, ref bool WeavingFailed)
{
// find command and RPC functions
foreach (MethodDefinition md in td.Methods)
{
if (md.HasCustomAttribute<CommandAttribute>())
{
Log.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
WeavingFailed = true;
}
if (md.HasCustomAttribute<ClientRpcAttribute>())
{
Log.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
WeavingFailed = true;
}
if (md.HasCustomAttribute<TargetRpcAttribute>())
{
Log.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
WeavingFailed = true;
}
}
}
}
}

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,216 @@
// finds all readers and writers and register them
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
using UnityEngine;
namespace Mirror.Weaver
{
public static class ReaderWriterProcessor
{
public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
{
// find NetworkReader/Writer extensions from Mirror.dll first.
// and NetworkMessage custom writer/reader extensions.
// NOTE: do not include this result in our 'modified' return value,
// otherwise Unity crashes when running tests
ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
// find readers/writers in the assembly we are in right now.
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
}
static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
{
// find Mirror.dll in assembly's references.
// those are guaranteed to be resolvable and correct.
// after all, it references them :)
AssemblyNameReference mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName);
if (mirrorAssemblyReference != null)
{
// resolve the assembly to load the AssemblyDefinition.
// we need to search all types in it.
// if we only were to resolve one known type like in WeaverTypes,
// then we wouldn't need it.
AssemblyDefinition mirrorAssembly = resolver.Resolve(mirrorAssemblyReference);
if (mirrorAssembly != null)
{
ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed);
}
else Log.Error($"Failed to resolve {mirrorAssemblyReference}");
}
else Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers.");
}
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed)
{
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 assembly has any declared writers then it is "modified"
modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers);
modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers);
}
}
foreach (TypeDefinition klass in assembly.MainModule.Types)
{
// if assembly has any network message then it is modified
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed);
}
return modified;
}
static bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed)
{
bool modified = false;
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
{
readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed);
writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed);
modified = true;
}
foreach (TypeDefinition td in klass.NestedTypes)
{
modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed);
}
return modified;
}
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers)
{
// 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, Readers readers)
{
// 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;
}
// helper function to add [RuntimeInitializeOnLoad] attribute to method
static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
{
// NOTE: previously we used reflection because according paul,
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
// order, which breaks rewired'
// it's not obvious why importing an attribute via reflection instead
// of cecil would break anything. let's use cecil.
// to add a CustomAttribute, we need the attribute's constructor.
// in this case, there are two: empty, and RuntimeInitializeOnLoadType.
// we want the last one, with the type parameter.
MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last();
//MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First();
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
// we need to import it first.
CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
// add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor
attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
method.CustomAttributes.Add(attribute);
}
// helper function to add [InitializeOnLoad] attribute to method
// (only works in Editor assemblies. check IsEditorAssembly first.)
static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
{
// NOTE: previously we used reflection because according paul,
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
// order, which breaks rewired'
// it's not obvious why importing an attribute via reflection instead
// of cecil would break anything. let's use cecil.
// to add a CustomAttribute, we need the attribute's constructor.
// in this case, there's only one - and it's an empty constructor.
MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First();
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
// we need to import it first.
CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
method.CustomAttributes.Add(attribute);
}
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
// registers all generated writers into Mirror.Writer<T> static class.
// -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
// -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
// in Editor and in tests too
//
// use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass)
{
MethodDefinition initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
MethodAttributes.Static,
weaverTypes.Import(typeof(void)));
// add [RuntimeInitializeOnLoad] in any case
AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
// add [InitializeOnLoad] if UnityEditor is referenced
if (Helpers.IsEditorAssembly(currentAssembly))
{
AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
}
// fill function body with reader/writer initializers
ILProcessor worker = initReadWriters.Body.GetILProcessor();
// for debugging: add a log to see if initialized on load
//worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
//worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference);
writers.InitializeWriters(worker);
readers.InitializeReaders(worker);
worker.Emit(OpCodes.Ret);
GeneratedCodeClass.Methods.Add(initReadWriters);
}
}
}

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,102 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
// Processes [Rpc] methods in NetworkBehaviour
public static class RpcProcessor
{
public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
{
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, weaverTypes, md.Name, label, "RPC");
// setup for reader
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td);
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, ref WeavingFailed))
return null;
// invoke actual command function
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, 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(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed)
{
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
// add a log message if needed for debugging
//worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}");
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
// write all the arguments that the user passed to the Rpc call
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed))
return null;
string rpcName = md.Name;
int channel = clientRpcAttr.GetField("channel", 0);
bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
// invoke SendInternal and return
// this
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldtoken, td);
// invokerClass
worker.Emit(OpCodes.Call, weaverTypes.getTypeFromHandleReference);
worker.Emit(OpCodes.Ldstr, rpcName);
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel);
// includeOwner ? 1 : 0
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
worker.Emit(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,154 @@
// 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(WeaverTypes weaverTypes, Logger Log, TypeDefinition td, ref bool WeavingFailed)
{
bool modified = false;
foreach (MethodDefinition md in td.Methods)
{
modified |= ProcessSiteMethod(weaverTypes, Log, md, ref WeavingFailed);
}
foreach (TypeDefinition nested in td.NestedTypes)
{
modified |= Process(weaverTypes, Log, nested, ref WeavingFailed);
}
return modified;
}
static bool ProcessSiteMethod(WeaverTypes weaverTypes, Logger Log, MethodDefinition md, ref bool WeavingFailed)
{
if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
return false;
if (md.IsAbstract)
{
if (HasServerClientAttribute(md))
{
Log.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);
WeavingFailed = true;
}
return false;
}
if (md.Body != null && md.Body.Instructions != null)
{
return ProcessMethodAttributes(weaverTypes, 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(WeaverTypes weaverTypes, MethodDefinition md)
{
if (md.HasCustomAttribute<ServerAttribute>())
InjectServerGuard(weaverTypes, md, true);
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
InjectServerGuard(weaverTypes, md, false);
else if (md.HasCustomAttribute<ClientAttribute>())
InjectClientGuard(weaverTypes, md, true);
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
InjectClientGuard(weaverTypes, md, false);
else
return false;
return true;
}
static void InjectServerGuard(WeaverTypes weaverTypes, 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(WeaverTypes weaverTypes, 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,39 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncObjectInitializer
{
// generates code like:
// this.InitSyncObject(m_sizes);
public static void GenerateSyncObjectInitializer(ILProcessor worker, WeaverTypes weaverTypes, FieldDefinition fd)
{
// register syncobject in network behaviour
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, fd);
worker.Emit(OpCodes.Call, weaverTypes.InitSyncObjectReference);
}
public static bool ImplementsSyncObject(TypeReference typeRef)
{
try
{
// value types cant inherit from SyncObject
if (typeRef.IsValueType)
{
return false;
}
return typeRef.Resolve().IsDerivedFrom<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;
}
}
}

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,84 @@
using System.Collections.Generic;
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class SyncObjectProcessor
{
// ulong = 64 bytes
const int SyncObjectsLimit = 64;
// Finds SyncObjects fields in a type
// Type should be a NetworkBehaviour
public static List<FieldDefinition> FindSyncObjectsFields(Writers writers, Readers readers, Logger Log, TypeDefinition td, ref bool WeavingFailed)
{
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
foreach (FieldDefinition fd in td.Fields)
{
if (fd.FieldType.Resolve().IsDerivedFrom<SyncObject>())
{
if (fd.IsStatic)
{
Log.Error($"{fd.Name} cannot be static", fd);
WeavingFailed = true;
continue;
}
// SyncObjects always needs to be readonly to guarantee.
// Weaver calls InitSyncObject on them for dirty bits etc.
// Reassigning at runtime would cause undefined behaviour.
// (C# 'readonly' is called 'initonly' in IL code.)
//
// NOTE: instead of forcing readonly, we could also scan all
// instructions for SyncObject assignments. this would
// make unit tests very difficult though.
if (!fd.IsInitOnly)
{
// just a warning for now.
// many people might still use non-readonly SyncObjects.
Log.Warning($"{fd.Name} should have a 'readonly' keyword in front of the variable because {typeof(SyncObject)}s always need to be initialized by the Weaver.", fd);
// only log, but keep weaving. no need to break projects.
//WeavingFailed = true;
}
GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed);
syncObjects.Add(fd);
}
}
// SyncObjects dirty mask is 64 bit. can't sync more than 64.
if (syncObjects.Count > 64)
{
Log.Error($"{td.Name} has > {SyncObjectsLimit} SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", td);
WeavingFailed = true;
}
return syncObjects;
}
// Generates serialization methods for synclists
static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed)
{
if (tr is GenericInstanceType genericInstance)
{
foreach (TypeReference argument in genericInstance.GenericArguments)
{
if (!argument.IsGenericParameter)
{
readers.GetReadFunc(argument, ref WeavingFailed);
writers.GetWriteFunc(argument, ref WeavingFailed);
}
}
}
if (tr != null)
{
GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed);
}
}
}
}

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,174 @@
// [SyncVar] int health;
// is replaced with:
// public int Networkhealth { get; set; } properties.
// this class processes all access to 'health' and replaces it with 'Networkhealth'
using System;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncVarAttributeAccessReplacer
{
// process the module
public static void Process(ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
{
DateTime startTime = DateTime.Now;
// process all classes in this module
foreach (TypeDefinition td in moduleDef.Types)
{
if (td.IsClass)
{
ProcessClass(syncVarAccessLists, td);
}
}
Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
}
static void ProcessClass(SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
{
//Console.WriteLine($" ProcessClass {td}");
// process all methods in this class
foreach (MethodDefinition md in td.Methods)
{
ProcessMethod(syncVarAccessLists, md);
}
// processes all nested classes in this class recursively
foreach (TypeDefinition nested in td.NestedTypes)
{
ProcessClass(syncVarAccessLists, nested);
}
}
static void ProcessMethod(SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
{
// process all references to replaced members with properties
//Log.Warning($" ProcessSiteMethod {md}");
// skip static constructor, "MirrorProcessed", "InvokeUserCode_"
if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
return;
// skip abstract
if (md.IsAbstract)
{
return;
}
// go through all instructions of this method
if (md.Body != null && md.Body.Instructions != null)
{
for (int i = 0; i < md.Body.Instructions.Count;)
{
Instruction instr = md.Body.Instructions[i];
i += ProcessInstruction(syncVarAccessLists, md, instr, i);
}
}
}
static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
{
// stfld (sets value of a field)?
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
{
ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
}
// ldfld (load value of a field)?
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
{
// this instruction gets the value of a field. cache the field reference.
ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
}
// ldflda (load field address aka reference)
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
{
// watch out for initobj instruction
// see https://github.com/vis2k/Mirror/issues/696
return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
}
// we processed one instruction (instr)
return 1;
}
// replaces syncvar write access with the NetworkXYZ.set property calls
static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//Log.Warning($" replacing {md.Name}:{i}", opField);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//Log.Warning($" replaced {md.Name}:{i}", opField);
}
}
// replaces syncvar read access with the NetworkXYZ.get property calls
static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//Log.Warning($" replacing {md.Name}:{i}");
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//Log.Warning($" replaced {md.Name}:{i}");
}
}
static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return 1;
// does it set a field that we replaced?
if (syncVarAccessLists.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;
}
}
}

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