Skip to content

Reference

Multiple assemblies & renaming across them

Obfuscate interdependent assemblies together so cross-assembly references stay valid — including how to rename public members shared between your own assemblies.

If your product ships more than one assembly and they reference each other — an app plus its libraries, or a set of libraries — obfuscate them together in one run. Nebula then keeps every cross-assembly reference consistent.

Why obfuscate them together

When assembly A calls a public type or member in assembly B, renaming that member in B is only safe if the reference in A is rewritten to the new name at the same time. Nebula can only rewrite references in assemblies that are part of the same run — so list every interdependent assembly in inputs:

{
  "inputs": [
    "bin/Release/MyApp.dll",
    "bin/Release/MyApp.Core.dll",
    "bin/Release/MyApp.Plugins.dll"
  ],
  "outputDirectory": "bin/Release/obf"
}

What gets renamed by default

  • Internal / private members are renamed — they aren’t visible outside the assembly, so this is always safe.
  • Public API is preserved (preservePublicApi defaults to true), so code outside the run that references your assemblies keeps working.
  • Even with preservePublicApi: false, Nebula auto-detects public members that a sibling input references and keeps those names stable — unless you explicitly enable cross-assembly rename (below).
  • autoDetect (on by default) also preserves names used via reflection, serialization, DI, and XAML, and honours [Obfuscation].

Renaming public members shared between your assemblies

This is the important case for multi-assembly projects: you want to rename a public method or type in B that A calls. The project config needs three things:

{
  "inputs": ["bin/Release/A.dll", "bin/Release/B.dll"],   // EVERY caller must be listed here
  "outputDirectory": "bin/Release/obf",
  "preservePublicApi": false,      // allow public members to be renamed
  "crossAssemblyRename": true      // rename shared public types AND rewrite the references in the siblings
}

With this in place:

  1. B’s public members are renamed.
  2. A’s references to them are rewritten to the new names, and A is obfuscated and written out too.
  3. You deploy the obfuscated A and B together — the original A would no longer resolve the renamed members.

The rule to remember

Every assembly that calls a public member you want renamed must be in inputs. If a caller is not in the run, it will still reference the old name and break at load/JIT time. Therefore:

  • You cannot rename public API that external or third-party code (which you can’t include in the run) depends on. Keep preservePublicApi: true for that surface, or carve it out with include/exclude below.
  • The default (preservePublicApi: true) exists precisely to protect those external callers — only internals get renamed.

Keeping specific public entry points stable

Even when renaming public members, some names must survive — a plugin interface, a public entry point, types created by reflection or DI, serialization contracts. Use:

  • exclude — force-preserve specific names. A type full name preserves the type and all its members; "Namespace.Type.Method" preserves a single member:
    "exclude": ["MyApp.Plugins.IPlugin", "MyApp.Api.PublicFacade"]
  • include — an allowlist: when non-empty, only the listed types (and their members) are eligible for renaming and everything else is preserved. Handy to rename just your internal libraries while leaving a public SDK surface untouched.
  • autoDetect already preserves reflection/serialization/[Obfuscation] targets automatically — you rarely need to list those by hand.

Virtual, interface, generic, field, property and event members

Nebula preserves override / interface / generic consistency across assemblies. An overridden method, an interface implementation, a generic type or method, and public fields, properties, events and overloads are all renamed consistently, and every cross-assembly reference to them is rewritten. This is covered by our test suite, which obfuscates two interdependent assemblies together (with crossAssemblyRename) and runs the referring app to confirm every reference — including all of those shapes — still resolves.

Strong-naming across the set

If you re-sign with your own key (strongNameKeyFile), Nebula updates each sibling’s assembly reference to carry the new public-key token, so the whole set stays consistent and loadable.

Worked example

Two assemblies — SampleLibA.dll (a library) and SampleAppB.dll (an app that calls it):

{
  "inputs": ["out/SampleLibA.dll", "out/SampleAppB.dll"],
  "outputDirectory": "out/obf",
  "preservePublicApi": false,
  "crossAssemblyRename": true,
  "controlFlowObfuscation": true,
  "encryptStrings": true
}
nebula --config nebula.config.json

Both assemblies come out obfuscated; the app’s references to the now-renamed library members are rewritten; you deploy both from out/obf.

Rule of thumb: include every assembly you control that references the renamed surface; keep preservePublicApi: true (or use exclude) for any API that outside code depends on.