Hello-Meow / ModTool

Mod support for Unity
MIT License
219 stars 36 forks source link

Unable to resolve the game Assembly from a mod #9

Closed Noxalus closed 4 years ago

Noxalus commented 4 years ago

Hello!

First, thank you for your work on this interesting project! 👍

I'm currently testing it with a small game prototype and it worked pretty easily until I tried to use code from the prototype in a mod. Now I have this error when I export the mod:

Warning: Failed to resolve assembly 'GamePrototype, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'

Error: Assembly 'Assets/ModTool/GamePrototypeMod-1.0-Assembly-CSharp.dll' will not be loaded due to errors:
Unable to resolve reference 'GamePrototype'. Is the assembly missing or incompatible with the current platform?

I didn't find how to do that from the documentation, and the example doesn't use existing code, so maybe I'm doing it wrong.

In the game prototype, I have a assembly definition (GamePrototype.asmdf) file that create a DLL in the "Managed" folder of my StandaloneWindows build. I took this DLL and copied it in a "Libs" folder of a new Unity project in which I imported the Unity package created by ModTool from my GamePrototype project.

Then I've created a new script that inherits from ModBehaviour in which I used GamePrototype' specific code. The code compile so the DLL is properly linked (and automatically) to the mod project assembly.

Did I do something wrong?

Thanks in advance for your help!

Hello-Meow commented 4 years ago

You have to add "GamePrototype" to API Assemblies in the ModTool settings. This tells ModTool to use it and adds the assembly to the exporter package. You shouldn't have to copy it manually.

Also, any code in GamePrototype should not reference any other scripts from the game. It should only reference things that the mod also can reference, so UnityEngine and System namespaces. Otherwise the mod can't load it, because there will be references it can't find.

Noxalus commented 4 years ago

Thank you for your answer! 👍

I've added the"GamePrototype" to API Assemblies and "Unity.TextMeshPro" as it's needed by the GamePrototype assembly:

image

But the exported package doesn't contain the listed DLLs:

image

(and I don't know why UnityEditor.VR.dll is here 🤔 )

And for your second sentence, you mean that if I create a new script in my GamePrototype assembly, it won't be accessible by the mod? If a mod can't use GamePrototype specific code, it's pretty limited. A lot of games that support modding provide the game DLL to modders and explain how to use it to create mod (it's the case for City Skylines or Kerbal Space Program for example).

Hello-Meow commented 4 years ago

Are there any warning or error messages when you create the exporter package? I wouldn't mind taking a look at your project if you're ok with that.

If you make a new script that's part of the GamePrototype assembly, it will be accessible to the mod. That's what API Assemblies are for. However there will be an issue if that new script references something that isn't included in GamePrototype, Unity or System. For example other scripts that aren't part of GamePrototype or a 3rd party plugin.

TextMesh Pro is already part of Unity, so you shouldn't have to include it.

Noxalus commented 4 years ago

Thank you for your answer!

No error during export: image

I've shared the GameProtype project with its associated mod project, you can find them here:

The "game" is simple, you have 2 buttons: "Cube" and "Sphere". When you click on one of them, you can move on the plane in the scene and instantiate the selected item by clicking.

With the "Mods" button at the top right corner, you can toggle mods list and if you load the "GamePrototype Mod", it will add a new "Cylinder" button to make you able to instantiate cylinders.

As you can see, at least for me, when you create the exporter, it won't include the GamePrototype.dll in the package.

Doing multiple tests, I've been able to make it work (kinda) copying manually the DLL in the ModTool folder but I had to remove the "Member Restrictions" from the CodeSettings.asset file as it checked the GamePrototype.dll code.

I export the mod in a "Mods" folder into build folder of the GamePrototype project and I've updated the provided ModMenu.cs class to take this folder into account to test it from the editor:

#if UNITY_EDITOR
    _modManager.AddSearchDirectory(Path.Combine(Application.dataPath, @"..\Builds\StandaloneWindows\Mods"));
#endif

And it's working, the new feature appears and works as expected.

But I still have multiple warnings:

Failed to resolve assembly: 'GamePrototype, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'

From the ModTool.Shared.Verification.Restriction:PresentInMethodRecursive method, so I suppose there is still something wrong.

Also, when I try to list mods launching the built .exe directly, the GamePrototypeMod doesn't appear, I don't really know why from now.

Thank you again for your help.

PS: I use .NET 4.X for both projects. PS2: Do you have an easy way to use the ModTool in a project using sources directly? I've quickly tried to do it replacing every .csproj + Properties folders by an Assembly Definition files with the exact same references between them but I had weird behaviours and error related to missing .DLL.

Hello-Meow commented 4 years ago

The Issue is really straightforward and kind of dumb on my part. You're using the Asset Store version or the unitypackage from the repository, which I haven't updated to the latest version that supports assembly definitions.

You can get the right version from here. I'll update the asset store version soon.

I should add that in the more recent versions of unity, creating an exporter gives an error about duplicate assemblies that can be ignored.

Noxalus commented 4 years ago

Ah, thank you for the new version, I will try that! 👍

How can I do to include the sources in my project instead of using the DLL? How do you do to test/debug it easily?

Hello-Meow commented 4 years ago

Unfortunately that would need a lot of changes to be made. The dlls are much easier to include in a package. Assembly definitions aren't supported by all Unity versions and would require the assemblies to be copied around to be included in a package.

If you easily want to test it, you can use the example projects. The solution is set up to copy the assemblies to the example projects after compilation.

Noxalus commented 4 years ago

I've just tested to use the .unitypackage linked above making sure to delete the previous ModTool folder from the GamePrototype project, I've also added the GamePrototype assembly in the settings, but I get these errors when I try to create the exporter:

Plugin 'Assets/ModTool/GamePrototype.dll' has the same filename as Assembly Definition File 'Assets/Scripts/GamePrototype.asmdef'. Rename the assemblies to avoid hard to diagnose issues and crashes.

IOException: Assets\ModTool\GamePrototype.dll already exists
System.IO.File.Copy (System.String sourceFileName, System.String destFileName, System.Boolean overwrite) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.IO.File.Copy (System.String sourceFileName, System.String destFileName) (at <437ba245d8404784b9fbab9b439ac908>:0)
ModTool.Editor.ExporterCreator.GetApiAssemblies (System.Collections.Generic.List`1[T] apiAssemblies) (at <4d2a3813e9cc40d88bc17c30ab8c5ddc>:0)
ModTool.Editor.ExporterCreator.CreateExporter (System.String path, System.Boolean revealPackage) (at <4d2a3813e9cc40d88bc17c30ab8c5ddc>:0)
ModTool.Editor.ExporterCreator.CreateExporter () (at <4d2a3813e9cc40d88bc17c30ab8c5ddc>:0)

And now I have options from both GamePrototype and GamePrototypeMod in the Tools menu:

image

And no .unitypackage is created 😢

Hello-Meow commented 4 years ago

The first error message can be ignored. Unity added this at some point. It happens because API Assembly dlls are copied into the ModTool folder to be included in the package. In a future update it will rename the assemblies.

The second message might happen because GamePrototype.dll exists in the ModTool folder. It should not be there. It might have stuck around after an earlier error. Try deleting that.

Noxalus commented 4 years ago

I've tried multiple times removing the ModTool and re-importing it using the .unitypackage (and unticking "Example" and "Plugins" folders), so the GamePrototype.dll doesn't exist at this time, but it always fails with the same error :/

I even tried to move the "Scripts/Modding" folder to "ModTool" folder as it contains scripts that use ModTool namespace and I had to create a specific Assembly Definition file to make it works as expected before, but it doesn't change anything, I get the same issues... 😢

Hello-Meow commented 4 years ago

Ah, I found the issue.

When looking for Api Assemblies to include, it looks through the whole project root folder. If you build a game inside of the project root, it will find multiple copies of the Api assemblies, so it will try to copy multiple files with the same name.

Please try it with the latest version. Now it will only look in places that make sense.

Noxalus commented 4 years ago

Thanks a lot for the new version!

As you said, I still have the first error, but the .unitypackage is properly exported, and it contains the GamePrototype.dll.

image

But now, when I try to export the mod from GamePrototypeMod project, I get this error:

image

Indeed, in the Start method of CylinderButton class I have this:

GameManager.Instance.ItemPlacer.OnItemPlaced += OnItemPlaced;
GameManager.Instance.ItemPlacer.OnItemChanged += OnItemChanged;

So I access to the GameManager instance using the GamePrototype.dll code, which is a singleton that will basically create a new GameObject if the instance is not already found in the scene:

public static T Instance
{
    get
    {
        if (_instance == null)
        {
            // Search for existing instance.
            _instance = (T)FindObjectOfType(typeof(T));

            // Create new instance if one doesn't already exist.
            if (_instance == null)
            {
                // Need to create a new GameObject to attach the singleton to.
                var singletonObject = new GameObject();
                _instance = singletonObject.AddComponent<T>();
                singletonObject.name = typeof(T).ToString() + " (Singleton)";

                // Make instance persistent.
                DontDestroyOnLoad(singletonObject);
            }
        }

        return _instance;
    }
}

In which I use the Unity's API AddComponent method. But as it's GamePrototype code, I don't expected the ModTool to check restrictions in it 🤔

Is this a normal behaviour for you?

Hello-Meow commented 4 years ago

Good to hear that it finally works.

In this case the validation fails, because the mod's script references code that could do something that's not allowed.

It is by design that this fails verification. It would be possible to ignore anything that references Api Assemblies, but that would have its own limitations. Obviously the GameManager is instantiated before the mod is loaded, but that's impossible to know by looking at the code alone.

The restrictions are set up to accommodate unloading mods. It forces a mod to use code that keeps track of instantiating and destroying unity objects. However, this isn't a very common use case and mostly gets in the way. If you don't need any of that, I'd suggest just removing those restrictions in the ModTool settings. I'm planning on removing this in a next version.