fix(unrealengine): copy NuGet runtime DLLs into plugin bin so deploy ships them#26
Merged
Merged
Conversation
…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>
This was referenced May 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 whenGMConverter.UnrealEngine(a library) builds, only its own DLLs plusProjectReferenceoutputs (CUE4Parse, CUE4Parse-Conversion, CUE4Parse-Natives) land inbin/. NuGet package DLLs stay in the package cache.The host's
CopyBundledPluginsMSBuild target copies the plugin'sbin/verbatim intoplugins/unrealengine/. What's not inbin/is not deployed. The plugin's runtimeAssemblyLoadContextthen can't find these dependencies in its plugin folder.Why it surfaced where it did
PluginLoader.FindEntryTypecallsassembly.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 ofMessagePack.dll, which is missing from the deploy folder, which throwsFileNotFoundExceptionbefore any plugin code runs. Because the host doesn't wire a console logger toPluginHost.Initializeby 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 intobin/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-pluginsflag to the CLI that prints whatPluginHost.Initializeactually loaded with a console logger attached. (Flag was removed before this PR was opened.)Before:
After:
Plugin bin went from 5 DLLs to 39. Deployed folder same.
Test plan
dotnet build GMConverter.slnx -c Release→ 0 warnings, 0 errorsdotnet format --verify-no-changes→ cleanNotes
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 toPluginHost.Initializein 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