Languages.

VeilCC works on compiled objects and LLVM IR, not source, so anything that lowers through LLVM is in reach. Below is the exact path for C/C++, Rust, and C# through Unity IL2CPP.

The integration model

Every language follows the same two-step shape. You never hand us source.

  1. Name the crown jewel. Either annotate it in source (C/C++ and Rust can), or name its symbol at build time (any language). A symbol you name must have the C ABI so it survives with a stable name.
  2. Run the pass on the object or IR. The console, CLI, or GitHub Action sends the compiled object (sealing) or LLVM IR (obfuscation) to the Protection API; you link the returned artifact exactly as before.

Two inputs, two tiers. Obfuscation (veil:mba, veil:flatten, veil:poison, veil:vm, or the veil:max bundle) rewrites LLVM IR in place. Sealing (veil:seal) takes the compiled object and ships the function as ciphertext. See Security levels.

C / C++

The most direct path: annotate in source, build as usual, swap the object. Auto-discovery means you don't repeat yourself, mark the function and the pass finds it.

1 · Annotate the function

license.c
// Seal: ships as ciphertext, decrypted only at call time.
__attribute__((annotate("veil:seal")))
uint32_t check_license(const char *key) {
    /* your real algorithm */
}

// Obfuscate only (hot-path-safe, stays native): the max bundle
// = poison + mba + flatten with a fresh seed each build.
__attribute__((annotate("veil:max")))
int validate_token(const char *tok, size_t n) { /* … */ }

2 · Build, protect, link

terminal
clang -O2 -c license.c -o license.o          # build as you always do
veil protect --level seal license.o -o license.sealed.o
clang -O2 app.o license.sealed.o -o app      # link, nothing else changes

C++ works identically. Give the sealed function extern "C" so its symbol keeps a stable, un-mangled name, or pass the mangled name to --proto. Everything that isn't marked stays untouched and native.

Rust

Rust lowers through LLVM, so the same pass applies. The one requirement is a stable symbol: export the crown jewel with the C ABI.

1 · Export with the C ABI

src/license.rs
#[no_mangle]
pub extern "C" fn check_license(key: *const u8, len: usize) -> u32 {
    let key = unsafe { std::slice::from_raw_parts(key, len) };
    // your real algorithm
}

#[no_mangle] + extern "C" give the function a predictable name and calling convention, exactly what the sealer needs to lift it out and the loader needs to call it back.

2 · Seal the compiled object

Rust has no annotate attribute, so you name the symbol and its signature in C terms. (A #[veil::protect] helper macro that emits the annotation section is on the roadmap; naming the symbol works today.)

terminal
# produce an object (staticlib, or cargo's emitted objects)
cargo build --release

# seal by symbol + C prototype
veil protect --level seal \
    --symbols check_license \
    --proto "uint32_t check_license(const unsigned char*, size_t)" \
    target/release/liblicense.o -o license.sealed.o

For the obfuscate tier, emit IR with cargo rustc --release -- --emit=llvm-ir and target the function by name; the pass hardens the IR before lowering. Keep the crown jewel in its own crate/unit so its object is small and clean to seal.

C# via Unity IL2CPP

IL2CPP transpiles your C# to C++ and then to native code, which is where VeilCC meets it, the same object path as C. There are two ways in; the first is cleaner and the one we recommend.

Recommended · Protect a native plugin

Move the logic worth stealing, the license check, the anti-cheat core, the proprietary kernel, into a small native plugin (C/C++ or Rust), call it from C# over P/Invoke, and seal the plugin. The secret never exists in managed IL or in the IL2CPP output at all.

LicenseNative.cs + license.c
// C#: call the native crown jewel
static class LicenseNative {
    [DllImport("license")]                       // license.dylib / .so / .dll
    public static extern uint check_license(string key);
}

// license.c: the sealed function (built + sealed like any C object)
__attribute__((annotate("veil:seal")))
uint32_t check_license(const char *key) { /* … */ }

Build the plugin, seal it with veil protect --level seal, and drop it into Assets/Plugins/. Device-bound keys (secure enclave / TPM) are available here, which matters most for games and anti-cheat.

Advanced · Target the IL2CPP output directly

If the logic must stay in C#, protect the generated native code. Point IL2CPP at a C++ build, then protect the compiled objects by symbol before the final link.

  • Set the IL2CPP build to emit and keep its generated C++ (Il2CppOutput/) and its object files.
  • Name the generated symbol for your method, IL2CPP names it deterministically per build, or force it by name at protect time.
  • Seal or obfuscate that object, then let Unity finish the link.

Honest caveat: IL2CPP symbol names can shift between builds and Unity versions, so this path needs a name check wired into your build. C# via IL2CPP runs the same object path we verify for C, but we only mark a toolchain as verified once our suite proves it end-to-end; today it's listed as expected. The native-plugin route above is verified C and is what we'd ship on.

The C-ABI rule

One rule spans every language: a function you seal or name must present the C ABI.

  • Why. The sealer lifts the function's machine code and relocations out of the object and the loader calls it back by a stable symbol with a known calling convention. C++ overloads, Rust's default mangling, and generics don't give that.
  • How. extern "C" (C++), #[no_mangle] pub extern "C" (Rust), a plain exported C function (plugins). Pass the signature to --proto in C types.
  • Scope. Only the sealed boundary needs the C ABI. Everything it calls, internal helpers, your normal C++ or Rust, stays as-is and links normally.