Skip to content

fix(unrealengine): copy NuGet runtime DLLs into plugin bin so deploy ships them#26

Merged
dhkatz merged 1 commit into
mainfrom
fix/plugin-deploy-missing-nuget-dlls
May 25, 2026
Merged

fix(unrealengine): copy NuGet runtime DLLs into plugin bin so deploy ships them#26
dhkatz merged 1 commit into
mainfrom
fix/plugin-deploy-missing-nuget-dlls

Conversation

@dhkatz
Copy link
Copy Markdown
Contributor

@dhkatz dhkatz commented May 25, 2026

Summary

The UE plugin doesn't load at runtime because its NuGet runtime DLLs (MessagePack, ImageSharp, Microsoft.Bcl.Memory, Microsoft.Extensions.{DependencyInjection,Logging}.Abstractions) never reach the deployed plugins/unrealengine/ folder. User-visible symptom: the UI's Explorer profile dropdown is missing "Unreal Engine 4/5 archives" and "Unreal Engine 2 packages".

Root cause

Library projects in the .NET SDK style don't copy NuGet runtime assemblies to bin/ by default — only the consuming executable does. So when GMConverter.UnrealEngine (a library) builds, only its own DLLs plus ProjectReference outputs (CUE4Parse, CUE4Parse-Conversion, CUE4Parse-Natives) land in bin/. NuGet package DLLs stay in the package cache.

The host's CopyBundledPlugins MSBuild target copies the plugin's bin/ verbatim into plugins/unrealengine/. What's not in bin/ is not deployed. The plugin's runtime AssemblyLoadContext then can't find these dependencies in its plugin folder.

Why it surfaced where it did

PluginLoader.FindEntryType calls assembly.GetCustomAttribute<PluginAttribute>(). Reflection enumerates every assembly-level custom attribute referenced by the plugin's metadata; MessagePack's source-generated attributes are in that set; resolving them triggers a load of MessagePack.dll, which is missing from the deploy folder, which throws FileNotFoundException before any plugin code runs. Because the host doesn't wire a console logger to PluginHost.Initialize by default, the error is logged but invisible — the plugin appears to "fail silently" from the UI's perspective.

Fix

One MSBuild property flip in the plugin's csproj: <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>. This asks the build to copy all lock-file assemblies into bin/ even for a library project. The csproj already had a long-winded comment explaining why the file layout matters for plugin deploy; the new property is described in the same spirit so future plugin authors see why it's needed.

Verification

Diagnosed by temporarily adding a --diag-plugins flag to the CLI that prints what PluginHost.Initialize actually loaded with a console logger attached. (Flag was removed before this PR was opened.)

Before:

=== LoadedPlugins: 0
=== Plugin explorers: 0
fail: GMConverter.Plugins.PluginLoader[2002]
      Failed to load plugin ...
      System.IO.FileNotFoundException: Could not load file or assembly 'MessagePack, Version=3.1.4.0, ...'
=== ExplorerService.Profiles: 2
   mow = Men of War
   generic = Generic supported files

After:

=== LoadedPlugins: 1
   gmconverter.unrealengine v0.1.0
=== Plugin explorers: 2
   ue4 = Unreal Engine 4/5 archives
   ue2 = Unreal Engine 2 packages
=== ExplorerService.Profiles: 4
   mow = Men of War
   ue4 = Unreal Engine 4/5 archives
   ue2 = Unreal Engine 2 packages
   generic = Generic supported files

Plugin bin went from 5 DLLs to 39. Deployed folder same.

Test plan

  • dotnet build GMConverter.slnx -c Release → 0 warnings, 0 errors
  • dotnet format --verify-no-changes → clean
  • Diagnostic confirms plugin loads and UE explorers register
  • Reviewer opens the UI, confirms "Unreal Engine 4/5 archives" and "Unreal Engine 2 packages" show in the Explorer profile dropdown

Notes

Worth flagging for follow-up: the host doesn't wire a console logger to PluginHost.Initialize, so plugin load failures (this one, and any future ones) are silent from the user's point of view. We should probably attach the host's existing logger factory to PluginHost.Initialize in both CLI and UI startup so plugin load errors surface to stderr / the UI log panel. Not in scope for this fix, but the silence is what made this take longer to diagnose than it should have.

🤖 Generated with Claude Code

…ships them

Without CopyLocalLockFileAssemblies=true, the GMConverter.UnrealEngine library
project's NuGet runtime assemblies (MessagePack, MessagePack.Annotations,
Microsoft.Bcl.Memory, Microsoft.Extensions.{DependencyInjection,Logging}.
Abstractions, SixLabors.ImageSharp, etc.) stay in the NuGet package cache and
never reach the plugin's bin/ folder. The host's CopyBundledPlugins target
copies bin/ verbatim into plugins/unrealengine/, so what's not in bin/ is not
deployed — and the runtime ALC can't find these assemblies in the plugin
folder.

The first symptom is reflection-driven: PluginLoader.FindEntryType calls
assembly.GetCustomAttribute<PluginAttribute>(), which forces the runtime to
resolve every assembly-level custom attribute referenced by the plugin's
metadata. MessagePack's source-generated attributes are among them; resolving
them triggers a load of MessagePack.dll, which is missing from the deploy
folder, which throws FileNotFoundException. The plugin load fails silently
(the loader catches and logs at error level, but the host doesn't wire a
console logger by default), so the UI's explorer dropdown is missing the
plugin-contributed UE4 and UE2 profiles even though the plugin built and
deployed successfully.

The fix is one MSBuild property flip in the plugin's csproj that asks the
build to copy all lock-file assemblies into bin/ even for a library project.
Verified with a diagnostic --diag-plugins flag on the CLI (now removed):
before the fix, LoadedPlugins=0 and ExplorerService.Profiles only had MOW +
Generic; after the fix, LoadedPlugins=1 (gmconverter.unrealengine v0.1.0) and
Profiles has all four (MOW, UE4, UE2, Generic).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dhkatz dhkatz merged commit 152286f into main May 25, 2026
4 checks passed
@dhkatz dhkatz deleted the fix/plugin-deploy-missing-nuget-dlls branch May 25, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant