Skip to content

Methodology and Census Internals

clericall edited this page Aug 13, 2026 · 1 revision

Methodology and Census Internals

This document details the internal logic of tools/measure-bodies.py and tools/measure-owned.py, the four classifier bugs discovered during testing, and the complete benchmark data for Unity IL2CPP vs Mono builds.


Benchmark Findings

To verify whether a 0.00% survival rate is an IL2CPP characteristic or a decompiler defect, the census scripts were run across four commercial Unity titles spanning both scripting backends and Unity versions from 2017 to Unity 6 (2026).

Whole-Project Benchmark Results

Game Unity Version Scripting Backend Extractor Tool Files Total Methods Empty Stubs Live Bodies Live %
House Flipper 2019.4 Mono ilspycmd 4,528 231,243 5,120 6,448 219,675 95.00%
Car Mechanic Simulator 2018 2017.4 Mono ilspycmd 114 1,040 18 55 967 92.98%
MiSide 2021.3 IL2CPP AssetRipper 1,225 4,899 3,408 1,491 0 0.00%
Data Center 6000.4 (Unity 6) IL2CPP Cpp2IL 282 2,549 0 2,549 0 0.00%

Game-Owned Code vs Vendor Middleware Split

Whole-project numbers flatten studio code and third-party libraries together. In Mono games, middleware libraries (written for distribution) survive at higher rates than game code, inflating the overall score. In IL2CPP games, IL2CPP compiles everything to native code regardless of whether a library is open-source.

measure-owned.py separates studio code from vendor middleware (Steamworks, Rewired, Sirenix, Odin, I2.Loc, etc.):

Game Backend Category Files Methods Live Bodies Live %
House Flipper Mono Studio Owned 4,210 218,860 208,216 95.14%
Mono Vendor Middleware 318 12,383 11,459 92.54%
Car Mechanic Simulator 2018 Mono Studio Owned 22 130 108 83.08%
Mono Vendor Middleware 92 910 859 94.40%
MiSide IL2CPP Studio Owned 781 3,309 0 0.00%
IL2CPP Vendor Middleware 444 1,590 0 0.00%
Data Center IL2CPP Studio Owned 258 2,366 0 0.00%
IL2CPP Vendor Middleware 24 183 0 0.00%

Note: On Car Mechanic Simulator 2018 (Mono), studio code is 83.08% live while bundled middleware is 94.40%. Third-party libraries inflate whole-project averages.


How the Census Classifier Works (measure-bodies.py)

tools/measure-bodies.py uses regex parsing to classify every recovered method into one of three categories:

  1. Empty: 0 statements between braces {}.
  2. Trivial Stub: Contains only dummy default return values, throw null;, or out parameter default assignments.
  3. Real Body: Contains actionable programming logic.

1. Signature Recognition Regex

sig = re.compile(
    r'^[ \t]*(?:\[[^\]]*\][ \t]*)*'
    r'(?:public|private|protected|internal)[ \t]+'
    r'(?:(?:static|virtual|override|sealed|abstract|extern|async|new|unsafe)[ \t]+)*'
    r'(?![ \t]*(?:class|struct|enum|interface|delegate)\b)'
    r'[\w<>\[\],\.\?]+[ \t]+\w+[ \t]*\([^;{]*\)[ \t]*$'
)

2. Stub Statement Classifier

TRIVIAL_ZERO = (r"(?:0[uUlLfFdDmM]{0,2}|0\.0[fFdDmM]?|'\\0'"
                r'|""|string\.Empty|null|default|false|true)')

TRIVIAL_VALUE = (r'(?:default\([^()]*\)'
                 r'|\((?:[\w\.]+)\)\s*' + TRIVIAL_ZERO +
                 r'|' + TRIVIAL_ZERO +
                 r'|new [\w<>\[\],\. ]+\(\))')

TRIVIAL_STMT = re.compile(
    r'(?:return(?:\s+' + TRIVIAL_VALUE + r')?'
    r'|throw null'
    r'|throw new (?:System\.)?NotImplementedException\(\)'
    r'|[\w\.\[\]]+\s*=\s*' + TRIVIAL_VALUE + r')\s*;'
)

History of Four Classifier Corrections

Before reaching the validated 0.00% figure, the headline number was published wrong three times. Every error was caught by opening the decompiled files and reading them by hand rather than trusting script output:

1. 8.22% — Working Copy Leak

  • Cause: Measured a working project directory that already contained hand-written replacement code.
  • Correction: Always measure pristine, untouched decompiler exports directly out of AssetRipper or Cpp2IL.

2. 7.70% — Multi-Statement out Parameter Stubs

  • Cause: The original classifier only recognized single-statement method bodies. When IL2CPP strips a method with out parameters, C# decompilers emit stub assignments for every parameter before returning:
    public static bool GetAutoResolution(out int width, out int height)
    {
        width = default(int);
        height = default(int);
        return false;
    }
    Because this body contains three statements, single-statement matching misclassified it as real code.
  • Correction: Updated is_trivial(txt) to verify if all statements in the body match stub assignment/return patterns.

3. 3.04% — Missing Literal Stub Patterns

  • Cause: The original stub regex matched 0 and null, but missed type-casted zero values such as (IntPtr)0, 0uL, 0.0, or '\0'. This credited 149 Steamworks P/Invoke stub functions as live code.
  • Correction: Expanded TRIVIAL_ZERO to cover explicitly typed zero literals, unsigned longs, floats, null pointers, and empty strings.

4. Vendor Namespace Prefix Matching Bug

  • Cause: measure-owned.py originally checked string prefixes using segment.startswith(vendor). The entry gog (for GOG Galaxy) matched GogoGaga.OptimizedRopesAndCables, and properties matched every assembly's Properties/AssemblyInfo.cs.
  • Correction: Namespace matching was rewritten to enforce dot-separated namespace boundaries (comps[:len(vc)] == vc). unity now matches Unity.Entities but never a game namespace starting with those letters (like UnityChanFanGame).

Clone this wiki locally