Running Hidden Unity Methods

Running internal Unity methods directly from the command line.

Lately I've been on a bit of a tooling binge, and have been exploring different avenues for improving our development workflow. Recently, I hit a bit of a snag with various C# analysis tools (such as Resharper): C# code tools nearly always need .sln files for analysis! This is a big problem if the tools are running on a CI server which only has access to the raw source code repository.

Being the good developers we are, we don't have our .sln project files in the repo, so we'll need to find some way to generate them instead. I wasn't able to find any information on this, and it seems to be a UnityVS proprietary feature. Rats! But clearly Unity can do it when we double-click on a file, though... hmm... time to get messy!

Our first goal is to access Unity at all from the CLI, which is easy enough with the batchmode flag, documented here. Let's try it out:

.\Unity.exe -batchmode -logFile -executeMethod MyClass.APublicStaticFunction -quit

This simply executes the MyClass.APublicStaticFunction method within your application. Easy enough! Since I'm using Powershell, by default it won't wait for the executable to finish, so I've wrapped it to wait for 60 seconds:

[Diagnostics.Process]::Start("C:\Program Files\Unity\Editor\Unity.exe", 
    "-batchmode -logFile -executeMethod MyClass.APublicStaticFunction -quit").WaitForExit(60000)

Here's where the fun starts. What the documentation won't tell you is that the executeMethod flag not only can call any code you define, but also any code in the entire Unity .NET assembly. This is is our in. If we can just figure out the right incantation, hypothetically we can invoke any feature of Unity we desire, all without opening the application.

But how to find the names of the VS project generation methods?

Enter ILSpy:

A screenshot of the app ILSpy.

choco install ilspy    #  with Chocolatey

ILSpy is a lovely tool that decompiles a .NET assembly back into C#, Visual Basic, or lets you browse the IL yourself directly. All those .dll files you see sitting around are .NET assemblies, packed up and ready to be loaded by any .NET application.

Load up any dll you see (or even .exe files), and you can start exploring the code structure. You'll occasionally get some weird names, but largely the decompilation does a fantastic job, and it's quite readable.

Let's try a Unity .dll for fun: Library\UnityAssemblies\UnityEditor.dll. Clicking on the root node, we can see properties of this assembly:

  • Runtime: .NET 2.0: fun fact, Unity at the base level provides only .NET 2.0 support (and by default only a subset of 2.0). Yes, this is horrendously outdated.

  • InternalsVisibleTo(...): These specify a whitelist for the other assemblies that this can be accessed from. This won't be a problem for us, but if you'd like to work with UnityEditor methods, you'll need to hack around this. Feel free to shoot me a message!

A screenshot of ILSpy with UnityEditor.dll open

The rest is general information. More interesting is the left panel, which shows all of the available public (and private) classes that are in the assembly. There's some neat stuff in there, particularly some hooks for the profiler and build system. Sweet.

Back to the task at hand: running VS project generation. Knowing that the project generation takes place in UnityVS, this is our likely target: Assets/UnityVS/Editor/SyntaxTree.VisualStudio.Unity.Bridge.dll. This is part of the plugin that comes bundled when you make use of UnityVS integration. (You can find this package manually in Program Files (x86)\Microsoft Visual Studio Tools for Unity).

Skimming around a bit.. and hey this looks promising:

ProjectFilesGenerator.GenerateProject in ILSpy

It's a nice clean static method with no arguments — in other words it's perfect. Let's give it a go:

[Diagnostics.Process]::Start("C:\Program Files\Unity\Editor\Unity.exe", 
    "-batchmode -logFile -projectPath $project_dir -executeMethod GenerateProjectFiles.generateProject -quit")
    .WaitForExit(60000)

... and success!

Final project and solution files.

Now, this is obviously not exactly an approved method, and it's subject to arbitrary breakage whenever Unity decides to rename their methods or assemblies. However, it seems unlikely this code will change much in the future (and wouldn't be too hard to update). Plus, this enables us to genearate .sln files for TeamCity from a fresh git pull!

I'll be digging into this more in the future. Potentially this allows us to automate anything within Unity: tests, lighting generation, builds, asset management (generating meta files perhaps?). And with all of this in a CI system, we can spend more time creating and less time fixing bugs.

I'll be exploring more of Unity's internals in a few blog posts down the road. Feel free to follow me on twitter to keep up to date, or drop me a line if you have any questions!

Comment on /r/gamedev

Comment on HN

TechnicalJohn Austin