Skip to content

Advanced: Adding new MonoBehaviours

nesrak1 edited this page Mar 21, 2023 · 5 revisions

Adding new MonoBehaviour assets

MonoBehaviours are a complicated issue. To add them depends on if your game is before Unity 5.5 or at/after and whether the script is being used in this file yet.

Adding MonoBehaviours U5.5+ (when a MonoBehaviour of the same class exists in file)

If a MonoBehaviour of the same class exists already, you're in a bit in luck. You can find the script index you want by looking in metadata.ScriptTypes or by using the helper method AssetHelper.GetAssetsFileScriptInfos. Whatever the index into that list is, that will be your script index. You'll also need to set the MonoBehaviour's m_Script field to the pointer to the MonoScript.

If you don't have a full type tree enabled, you'll need to manually call your temp generator for the template fields for your new MonoBehaviour. Here's some basic code to find a class called "Test123" and create a new MonoBehaviour of it.

var newMbPathId = 123456;

// game object to attach to
var gameObjectPathId = 12345;

var monoBehaviourClassId = (int)AssetClassID.MonoBehaviour;

var scriptTypeInfos = AssetHelper.GetAssetsFileScriptInfos(manager, afileInst);
var testScriptIndex = scriptTypeInfos.Values.ToList().FindIndex(s => s.ClassName == "Test123");
if (testScriptIndex == -1)
    throw new Exception("Couldn't find the script index of class Test123!");

var testMonoScriptPPtr = afile.Metadata.ScriptTypes[testScriptIndex];
    
var newBaseField = manager.CreateValueBaseField(afileInst, monoBehaviourClassId, (ushort)testScriptIndex);

newBaseField["m_GameObject.m_FileID"].AsInt = 0; // asset is in the same file, so 0 is fine
newBaseField["m_GameObject.m_PathID"].AsLong = gameObjectPathId;
newBaseField["m_Enabled"].AsByte = 1;
newBaseField["m_Script.m_FileID"].AsInt = testMonoScriptPPtr.FileId;
newBaseField["m_Script.m_PathID"].AsLong = testMonoScriptPPtr.PathId;

// ...

reps.Add(new AssetsReplacerFromMemory(newMbPathId, monoBehaviourClassId, (ushort)testScriptIndex, newBaseField.WriteToByteArray()));

To actually use the MonoBehaviour (assuming it's not a ScriptableObject that can be parentless), you'll also need to add it to a GameObject.

var gameObjectInfo = afile.GetAssetInfo(gameObjectPathId);
var gameObjectBaseField = assetsManager.GetBaseField(afileInst, gameObjectInfo);
var gameObjectCompArray = gameObjectBaseField["m_Component.Array"];

var mbCompEntry = ValueBuilder.DefaultValueFieldFromArrayTemplate(gameObjectCompArray);
mbCompEntry["component.m_FileID"].AsInt = 0; // asset is in the same file, so 0 is fine
mbCompEntry["component.m_PathID"].AsInt = newMbPathId;
gameObjectCompArray.Children.Add(mbCompEntry);

reps.Add(new AssetsReplacerFromMemory(afile, gameObjectInfo, gameObjectBaseField));

Adding MonoBehaviours U5.5+ (when a MonoBehaviour of the same class doesn't exist in file)

If a MonoBehaviour of the same class is not in the file you're editing, you'll have to add it (and this is not very easy).

If the script was originally in the game and the file is not from a standalone bundle, you can find the MonoScript you want to reference in the globalgamemanagers file. You'll need to manually iterate through the MonoScript assets to find the one that matches the class name and namespace and whatnot that you want. Take note of its path id. To find the relative file id to globalgamemanagers, check afile.Metadata.Externals for the index and add one. For example, if globalgamemanagers is the second external (afile.Metadata.Externals[1]) then its file id is 2. With those two values in mind, create a new entry in afile.Metadata.ScriptTypes with a new AssetPPtr with the file id and path id you just found. Then, do the previous method with the script index being the index into the ScriptTypes list.

If you want to add into a standalone bundle, you'll also need to make the additional step of creating the MonoScript. To do this, you need to fill out all the fields in it including the hash, which I do not yet know how to generate. Your best bet is to copy it from another MonoScript in a different bundle/globalgamemanagers.