# How Type Preservation Prevents Code Stripping in Unity IL2CPP Builds

> Discover how type preservation stops IL2CPP code stripping in Unity builds. Learn how link.xml ensures vital code isn't removed, keeping your project functional and stable.

- Repository: [AkiKurisu/ceres](https://github.com/akikurisu/ceres)
- Tags: internals
- Published: 2026-02-24

---

**Type preservation prevents IL2CPP code stripping by generating a link.xml file that explicitly marks types and assemblies with `preserve="all"`, overriding the static analysis that would otherwise remove reflection-only or SerializeReference types.**

When Unity converts managed C# code to C++ using the IL2CPP backend, it strips unused types to optimize binary size, causing runtime `MissingReferenceException` or `NullReferenceException` for dynamically accessed classes. The open-source **Ceres** library (akikurisu/ceres) automates type preservation by orchestrating editor settings, runtime APIs, and build processors to safeguard critical metadata. This guide examines the exact mechanism using the Ceres source code.

## Why IL2CPP Strips Code and How to Stop It

The IL2CPP conversion process aggressively removes any managed types that static analysis cannot prove are used at runtime. While this reduces binary size, it eliminates types accessed only via reflection or polymorphic serialization. Unity provides a [`link.xml`](https://github.com/akikurisu/ceres/blob/main/link.xml) configuration file to override this behavior, but maintaining it manually is error-prone.

Ceres solves this by automatically generating the [`link.xml`](https://github.com/akikurisu/ceres/blob/main/link.xml) file during the build process, ensuring specified types survive the conversion.

## The Four Components of Ceres Type Preservation

The architecture consists of four coordinated components that handle registration, storage, XML generation, and build integration.

### CeresSettings: Persistent Type Storage

Located in [`Editor/Core/Editors/CeresSettings.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/Editors/CeresSettings.cs), this ScriptableObject maintains the **`preservedTypes`** list as strings. When you register a type programmatically via `CeresSettings.AddPreservedType(Type)`, the system stores the fully-qualified name using `SerializedType.ToString`. The Inspector UI in **Project → Ceres** also writes directly to this list.

### CeresLinker: Runtime Registration API

The [`Editor/Core/CeresLinker.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/CeresLinker.cs) file provides the public API for code-driven preservation. Call **`CeresLinker.LinkType(typeof(MyClass))`** to register a type programmatically. This method walks the type graph, skips obviously safe system types, and feeds the final list into `CeresSettings`. The `Save()` method persists these entries to the settings asset.

### LinkXmlGenerator: XML File Construction

Found in [`Editor/Core/LinkXmlGenerator.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/LinkXmlGenerator.cs), this component constructs the actual XML markup. For each assembly containing preserved types, it writes **`<assembly fullname="..." preserve="all"/>`** (lines 91-96). For individual types, it emits **`<type fullname="..." preserve="all"/>`** (lines 100-108). For SerializeReference placeholders, it uses **`preserve="nothing"`** combined with **`serialized="true"`** (lines 124-131) to mark types needed for deserialization without preserving unused methods.

### CeresBuildProcessor: Automated Build Hook

The [`Editor/Core/CeresBuildProcessor.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/CeresBuildProcessor.cs) implements Unity's `IPreprocessBuildWithReport` and `IPostprocessBuildWithReport` interfaces. Its **`PreprocessBuild`** method executes the generation step immediately before IL2CPP conversion, while **`PostprocessBuild`** deletes the temporary [`link.xml`](https://github.com/akikurisu/ceres/blob/main/link.xml) file and its directory (lines 27-30) to prevent project clutter.

## Step-by-Step: How Type Preservation Works

The process follows a precise pipeline from registration to IL2CPP consumption:

1. **Type Registration** – Users register types either via the Inspector (**Project → Ceres → Preserved Types**) or programmatically through `CeresLinker.LinkType()`. Both routes invoke `CeresSettings.AddPreservedType()`, storing the type string in `CeresSettings.preservedTypes`.

2. **Build Trigger** – When a build starts, `CeresBuildProcessor.PreprocessBuild` executes:

   ```csharp
   _linker.AddTypes(CeresSettings.GetPreservedTypes()
                                .Select(SerializedType.FromString));
   _linker.Save(XMLPath);
   ```

   The `LinkXmlGenerator` instance (`_linker`) converts the string representations back to types and prepares the XML structure.

3. **XML Generation** – The generator writes the [`link.xml`](https://github.com/akikurisu/ceres/blob/main/link.xml) file with specific preservation directives:

   ```xml
   <linker>
     <assembly fullname="MyGameAssembly" preserve="all">
       <type fullname="MyNamespace.MyCustomClass" preserve="all"/>
       <type fullname="MyNamespace.MySerializedClass" preserve="nothing" serialized="true"/>
     </assembly>
   </linker>
   ```

4. **IL2CPP Compliance** – Unity's IL2CPP driver reads the generated file and respects the `preserve="all"` attributes, retaining the specified metadata and method bodies in the final native binary even if static analysis detected no direct references.

5. **Cleanup** – After successful build completion, `PostprocessBuild` removes the temporary [`link.xml`](https://github.com/akikurisu/ceres/blob/main/link.xml) to avoid stale artifacts.

## Code Examples: Registering Types for Preservation

### Adding Types from Runtime Code

Use `CeresLinker` during initialization to protect types accessed via reflection:

```csharp
using Ceres.Editor;

public static class RuntimeInitializer
{
    static RuntimeInitializer()
    {
        var linker = new CeresLinker();
        linker.LinkType(typeof(MyNamespace.SpecialEffect));
        linker.Save(); // Persists to CeresSettings.preservedTypes
    }
}

```

This flow chains through `CeresLinker.LinkType` → `CeresSettings.AddPreservedType` → storage in the settings asset.

### Preserving SerializeReference Types

For polymorphic serialization scenarios, register concrete implementations:

```csharp
[Serializable]
public class Container
{
    [SerializeReference] public IMyInterface data;
}

// Register the concrete type
CeresLinker linker = new CeresLinker();
linker.LinkType(typeof(MyConcreteImplementation));
linker.Save();

```

`LinkXmlGenerator` emits `<type fullname="MyConcreteImplementation" preserve="nothing" serialized="true"/>`, ensuring the type survives for deserialization while avoiding unnecessary code bloat.

## Summary

- **IL2CPP stripping** removes unused managed types during C++ conversion, breaking reflection and SerializeReference patterns.
- **Ceres** automates preservation through four components: `CeresSettings` (storage), `CeresLinker` (API), `LinkXmlGenerator` (XML writer), and `CeresBuildProcessor` (build hook).
- The **[`link.xml`](https://github.com/akikurisu/ceres/blob/main/link.xml)** file uses `preserve="all"` on assemblies and types to override IL2CPP static analysis.
- **SerializeReference** types use `preserve="nothing" serialized="true"` to minimize binary impact while ensuring deserialization works.
- The system automatically cleans up temporary files after build completion to keep the project directory clean.

## Frequently Asked Questions

### What causes MissingReferenceException in IL2CPP builds only?

IL2CPP's static analysis removes types that appear unused in compile-time analysis but are accessed via reflection or `SerializeReference`. When code attempts to instantiate or deserialize these stripped types at runtime, Unity throws `MissingReferenceException` or `NullReferenceException` because the type metadata no longer exists in the native binary.

### How does link.xml prevent code stripping in Unity?

The [`link.xml`](https://github.com/akikurisu/ceres/blob/main/link.xml) file acts as a preservation manifest that IL2CPP reads before converting C# to C++. When a type or assembly is marked with `preserve="all"`, IL2CPP includes that metadata in the native build regardless of whether static analysis detected references. Ceres automates this by generating the XML from your registered type list.

### Can I preserve entire assemblies instead of individual types?

Yes. When you register types from a specific assembly, `LinkXmlGenerator` (lines 91-96) writes `<assembly fullname="AssemblyName" preserve="all"/>`, which keeps the entire assembly intact. This is useful for plugin libraries that rely heavily on reflection, such as scripting frameworks or serialization systems.

### What is the difference between preserve="all" and preserve="nothing" with serialized="true"?

`preserve="all"` retains the complete type including all methods and metadata, necessary for types instantiated via reflection. `preserve="nothing" serialized="true"` (lines 124-131) keeps only the type metadata required for deserialization without preserving methods, optimizing binary size for `SerializeReference` data containers that don't need runtime method execution.