From 0ba8fbebef08e625fb5adac3a65ac11c54ada7c6 Mon Sep 17 00:00:00 2001 From: Cosmin <38663153+Cosmin-B@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:14:36 -0800 Subject: [PATCH 1/2] =?UTF-8?q?Support=20Unity=206000.3=20(6.3)=20?= =?UTF-8?q?=E2=80=94=20handle=20GetPtrFromInstanceID=20signature=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unity 6000.3 removed the Type parameter from the internal UnityEngine.Object.GetPtrFromInstanceID method: Pre-6000.3: IntPtr GetPtrFromInstanceID(int, Type, out bool) 6000.3+: IntPtr GetPtrFromInstanceID(int, out bool) This caused a MissingMethodException at runtime when using a DLL built against 6000.0 on a 6000.3 project, since the IL referenced the old 3-parameter overload which no longer exists. Changes: CecilProcessor/Program.cs - AddMarshalUnityObjectMethod now inspects the parameter count of GetPtrFromInstanceID at processing time and conditionally emits the Ldnull (Type argument) only when the 3-param signature is present. This makes the same processor work against both 6000.0 and 6000.3 Unity installs without any manual toggle. - Added diagnostic output so the build log shows which signature variant was detected. ProcessAssembly.sh - Accept Unity editor path as a CLI argument or UNITY_PATH env var instead of hardcoding a single version. - Auto-detect macOS vs Windows managed-assembly layout. - Use the resolved paths when generating the temp .csproj so the build references match the actual editor install. UnityVersionChecker.cs - Replaced single-target version check with a min/max validated range (6000.0.31 through 6000.3.x). - Version parser now handles beta/alpha suffixes (b, a) in addition to release (f). package.json - Bumped version to 1.1.0. - Updated description to reflect 6000.0–6000.3 support. README.md - Updated badges, requirements, compatibility table with the signature difference, and build instructions showing how to pass a custom Unity path. Consumers on 6000.3 need to rebuild the assembly by running ProcessAssembly.sh against their 6000.3 editor install. The pre-built DLL in Plugins/ remains built against 6000.0.31f1 for backwards compat. --- .../Runtime~/UnityExposed.cs | 7 +- .../Runtime~/UnityVersionChecker.cs | 96 ++++++++++--------- .../com.cosminb.unity-internals/package.json | 4 +- CecilProcessor/Program.cs | 33 +++++-- ProcessAssembly.sh | 56 +++++++---- README.md | 40 +++++--- 6 files changed, 153 insertions(+), 83 deletions(-) diff --git a/Assets/Packages/com.cosminb.unity-internals/Runtime~/UnityExposed.cs b/Assets/Packages/com.cosminb.unity-internals/Runtime~/UnityExposed.cs index 4016316..f53415b 100644 --- a/Assets/Packages/com.cosminb.unity-internals/Runtime~/UnityExposed.cs +++ b/Assets/Packages/com.cosminb.unity-internals/Runtime~/UnityExposed.cs @@ -17,11 +17,16 @@ public static class UnityExposed /// /// Gets the marshalled Unity object pointer. This avoids allocations compared to reflection. + /// The Cecil processor detects whether the target Unity version uses the pre-6000.3 + /// signature (int, Type, out bool) or the 6000.3+ signature (int, out bool) for the + /// internal GetPtrFromInstanceID call and emits the correct IL accordingly. /// public static IntPtr MarshalUnityObject(UnityEngine.Object obj) { // This will be replaced by Cecil to access obj.m_CachedPtr directly - // and call Unity's internal GetPtrFromInstanceID if needed + // and call Unity's internal GetPtrFromInstanceID if needed. + // The Cecil processor auto-detects the GetPtrFromInstanceID signature at + // build time, so the same source works for both pre-6000.3 and 6000.3+. throw new NotImplementedException("Assembly not processed by Cecil. Run ProcessAssembly.sh"); } diff --git a/Assets/Packages/com.cosminb.unity-internals/Runtime~/UnityVersionChecker.cs b/Assets/Packages/com.cosminb.unity-internals/Runtime~/UnityVersionChecker.cs index 7e08f5d..2abda1e 100644 --- a/Assets/Packages/com.cosminb.unity-internals/Runtime~/UnityVersionChecker.cs +++ b/Assets/Packages/com.cosminb.unity-internals/Runtime~/UnityVersionChecker.cs @@ -8,17 +8,27 @@ namespace ExposedBindings { /// /// Checks Unity version compatibility for the internal bindings. + /// The Cecil processor auto-detects internal API signatures at build time, + /// so the DLL must be rebuilt via ProcessAssembly.sh when crossing major + /// version boundaries (e.g. 6000.0 -> 6000.3). /// #if UNITY_EDITOR [InitializeOnLoad] #endif public static class UnityVersionChecker { - private const string TARGET_UNITY_VERSION = "6000.0.31f1"; - private const int TARGET_MAJOR = 6000; - private const int TARGET_MINOR = 0; - private const int TARGET_PATCH = 31; - + // Minimum supported version — anything older is unsupported. + private const string MIN_UNITY_VERSION = "6000.0.31f1"; + private const int MIN_MAJOR = 6000; + private const int MIN_MINOR = 0; + private const int MIN_PATCH = 31; + + // Maximum version the Cecil processor has been validated against. + // Bump this when verifying a new Unity release. + private const string MAX_VALIDATED_VERSION = "6000.3.99f1"; + private const int MAX_MAJOR = 6000; + private const int MAX_MINOR = 3; + static UnityVersionChecker() { CheckUnityVersion(); @@ -30,33 +40,31 @@ static UnityVersionChecker() public static void CheckUnityVersion() { var currentVersion = Application.unityVersion; - + if (!ParseUnityVersion(currentVersion, out int major, out int minor, out int patch)) { Debug.LogWarning($"[ExposedBindings] Could not parse Unity version: {currentVersion}. " + - $"This library was built for Unity {TARGET_UNITY_VERSION}."); + $"Minimum supported: {MIN_UNITY_VERSION}."); return; } - // Check if version is older than target - if (major < TARGET_MAJOR || - (major == TARGET_MAJOR && minor < TARGET_MINOR) || - (major == TARGET_MAJOR && minor == TARGET_MINOR && patch < TARGET_PATCH)) + // Check if version is older than minimum + if (major < MIN_MAJOR || + (major == MIN_MAJOR && minor < MIN_MINOR) || + (major == MIN_MAJOR && minor == MIN_MINOR && patch < MIN_PATCH)) { - Debug.LogError($"[ExposedBindings] Unity version {currentVersion} is older than the minimum supported version {TARGET_UNITY_VERSION}. " + + Debug.LogError($"[ExposedBindings] Unity version {currentVersion} is older than the minimum supported version {MIN_UNITY_VERSION}. " + "The internal bindings may not work correctly."); return; } - // Warn if version is newer - if (major > TARGET_MAJOR || - (major == TARGET_MAJOR && minor > TARGET_MINOR) || - (major == TARGET_MAJOR && minor == TARGET_MINOR && patch > TARGET_PATCH)) + // Warn if version is beyond validated range + if (major > MAX_MAJOR || + (major == MAX_MAJOR && minor > MAX_MINOR)) { - Debug.LogWarning($"[ExposedBindings] This library was built for Unity {TARGET_UNITY_VERSION}, " + - $"but you are using {currentVersion}. " + - "Unity's internal signatures may have changed. " + - "Please test thoroughly and consider updating the bindings if issues occur."); + Debug.LogWarning($"[ExposedBindings] Unity {currentVersion} is newer than the last validated version ({MAX_VALIDATED_VERSION}). " + + "Internal signatures may have changed — rebuild with ProcessAssembly.sh against your Unity install " + + "and test thoroughly."); } } @@ -72,7 +80,7 @@ private static bool ParseUnityVersion(string versionString, out int major, out i if (string.IsNullOrEmpty(versionString)) return false; - // Unity version format: "6000.0.31f1" or "2022.3.10f1" + // Unity version format: "6000.0.31f1" or "6000.3.2f1" var parts = versionString.Split('.'); if (parts.Length < 3) return false; @@ -83,12 +91,12 @@ private static bool ParseUnityVersion(string versionString, out int major, out i if (!int.TryParse(parts[1], out minor)) return false; - // Extract patch number (remove 'f1' suffix) + // Extract patch number (remove 'f1', 'b1', 'a1' suffix) var patchStr = parts[2]; - var fIndex = patchStr.IndexOf('f'); - if (fIndex > 0) + var suffixIndex = patchStr.IndexOfAny(new[] { 'f', 'b', 'a' }); + if (suffixIndex > 0) { - patchStr = patchStr.Substring(0, fIndex); + patchStr = patchStr.Substring(0, suffixIndex); } if (!int.TryParse(patchStr, out patch)) @@ -103,38 +111,39 @@ private static bool ParseUnityVersion(string versionString, out int major, out i public static VersionCompatibility GetCompatibility() { var currentVersion = Application.unityVersion; - + if (!ParseUnityVersion(currentVersion, out int major, out int minor, out int patch)) { return new VersionCompatibility { IsCompatible = false, CurrentVersion = currentVersion, - TargetVersion = TARGET_UNITY_VERSION, + MinVersion = MIN_UNITY_VERSION, + MaxValidatedVersion = MAX_VALIDATED_VERSION, Message = "Could not parse Unity version" }; } - bool isOlder = major < TARGET_MAJOR || - (major == TARGET_MAJOR && minor < TARGET_MINOR) || - (major == TARGET_MAJOR && minor == TARGET_MINOR && patch < TARGET_PATCH); + bool isOlder = major < MIN_MAJOR || + (major == MIN_MAJOR && minor < MIN_MINOR) || + (major == MIN_MAJOR && minor == MIN_MINOR && patch < MIN_PATCH); - bool isNewer = major > TARGET_MAJOR || - (major == TARGET_MAJOR && minor > TARGET_MINOR) || - (major == TARGET_MAJOR && minor == TARGET_MINOR && patch > TARGET_PATCH); + bool isBeyondValidated = major > MAX_MAJOR || + (major == MAX_MAJOR && minor > MAX_MINOR); - bool isExactMatch = major == TARGET_MAJOR && minor == TARGET_MINOR && patch == TARGET_PATCH; + bool isInRange = !isOlder && !isBeyondValidated; return new VersionCompatibility { IsCompatible = !isOlder, - IsExactMatch = isExactMatch, - IsNewer = isNewer, + IsInValidatedRange = isInRange, + IsBeyondValidated = isBeyondValidated, CurrentVersion = currentVersion, - TargetVersion = TARGET_UNITY_VERSION, + MinVersion = MIN_UNITY_VERSION, + MaxValidatedVersion = MAX_VALIDATED_VERSION, Message = isOlder ? "Unity version is older than minimum supported version" : - isNewer ? "Unity version is newer than target version - internals may have changed" : - "Unity version matches target version" + isBeyondValidated ? "Unity version is newer than last validated version — rebuild recommended" : + "Unity version is within validated range" }; } @@ -144,11 +153,12 @@ public static VersionCompatibility GetCompatibility() public struct VersionCompatibility { public bool IsCompatible; - public bool IsExactMatch; - public bool IsNewer; + public bool IsInValidatedRange; + public bool IsBeyondValidated; public string CurrentVersion; - public string TargetVersion; + public string MinVersion; + public string MaxValidatedVersion; public string Message; } } -} \ No newline at end of file +} diff --git a/Assets/Packages/com.cosminb.unity-internals/package.json b/Assets/Packages/com.cosminb.unity-internals/package.json index 785398b..3651e3a 100644 --- a/Assets/Packages/com.cosminb.unity-internals/package.json +++ b/Assets/Packages/com.cosminb.unity-internals/package.json @@ -1,8 +1,8 @@ { "name": "com.cosminb.unity-internals", - "version": "1.0.0", + "version": "1.1.0", "displayName": "Exposed Bindings", - "description": "Exposes Unity internal bindings for high-performance native array operations. Built for Unity 6000.0.31f1. Source code is available in the Runtime~ folder for reference and modification.", + "description": "Exposes Unity internal bindings for high-performance native array operations. Supports Unity 6000.0.31f1 through 6000.3.x. Source code is available in the Runtime~ folder for reference and modification.", "unity": "6000.0", "unityRelease": "31f1", "dependencies": { diff --git a/CecilProcessor/Program.cs b/CecilProcessor/Program.cs index 02178bd..d80066d 100644 --- a/CecilProcessor/Program.cs +++ b/CecilProcessor/Program.cs @@ -154,7 +154,11 @@ static void AddMarshalUnityObjectMethod(AssemblyDefinition assembly, TypeDefinit return; } - // Find GetPtrFromInstanceID method + // Find GetPtrFromInstanceID method. + // The signature changed in Unity 6000.3: + // Pre-6000.3: IntPtr GetPtrFromInstanceID(int instanceID, Type objectType, out bool isMonoBehaviour) + // 6000.3+: IntPtr GetPtrFromInstanceID(int instanceID, out bool isMonoBehaviour) + // We detect the parameter count at processing time and emit the correct IL. var getPtrMethod = unityObjectType.Methods.FirstOrDefault(m => m.Name == "GetPtrFromInstanceID"); if (getPtrMethod == null) { @@ -162,6 +166,11 @@ static void AddMarshalUnityObjectMethod(AssemblyDefinition assembly, TypeDefinit return; } + bool hasTypeParameter = getPtrMethod.Parameters.Count == 3; + Console.WriteLine(hasTypeParameter + ? " Detected GetPtrFromInstanceID(int, Type, out bool) — pre-6000.3 signature" + : " Detected GetPtrFromInstanceID(int, out bool) — 6000.3+ signature"); + // Clear existing method body method.Body.Instructions.Clear(); method.Body.Variables.Clear(); @@ -195,38 +204,42 @@ static void AddMarshalUnityObjectMethod(AssemblyDefinition assembly, TypeDefinit il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Conv_I); il.Emit(OpCodes.Beq_S, needsInstanceIdLabel); - + // m_CachedPtr is valid, return it il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ret); // m_CachedPtr is zero, need to use instance ID il.Append(needsInstanceIdLabel); - + // Call GetInstanceID() to get the instance ID var instanceIdLocal = new VariableDefinition(assembly.MainModule.TypeSystem.Int32); method.Body.Variables.Add(instanceIdLocal); - + il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getInstanceIdMethod)); il.Emit(OpCodes.Stloc, instanceIdLocal); - + // Check if instance ID == 0 var instanceIdNotZeroLabel = il.Create(OpCodes.Nop); il.Emit(OpCodes.Ldloc, instanceIdLocal); il.Emit(OpCodes.Brtrue_S, instanceIdNotZeroLabel); - + // Instance ID is 0, return IntPtr.Zero il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Conv_I); il.Emit(OpCodes.Ret); - + // Instance ID is not 0, call GetPtrFromInstanceID il.Append(instanceIdNotZeroLabel); il.Emit(OpCodes.Ldloc, instanceIdLocal); - - // For simplicity, pass null for Type (Unity will handle it) - il.Emit(OpCodes.Ldnull); + + // Pre-6000.3: pass null for the Type parameter that was removed in 6000.3 + if (hasTypeParameter) + { + il.Emit(OpCodes.Ldnull); + } + il.Emit(OpCodes.Ldloca_S, isMonoBehaviourLocal); il.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getPtrMethod)); il.Emit(OpCodes.Ret); diff --git a/ProcessAssembly.sh b/ProcessAssembly.sh index 0410e26..657e818 100755 --- a/ProcessAssembly.sh +++ b/ProcessAssembly.sh @@ -1,8 +1,14 @@ #!/bin/bash # Build script for processing Unity internal bindings assembly with Cecil - -UNITY_PATH="/Applications/Unity/Hub/Editor/6000.0.31f1" +# +# Usage: +# ./ProcessAssembly.sh # uses default Unity path +# ./ProcessAssembly.sh /path/to/unity/editor # custom Unity install +# UNITY_PATH=/path/to/unity ./ProcessAssembly.sh # via env var + +# Accept Unity path as first argument, fall back to env var, then to default. +UNITY_PATH="${1:-${UNITY_PATH:-/Applications/Unity/Hub/Editor/6000.0.31f1}}" PROJECT_PATH="$(pwd)" ASSEMBLY_NAME="ExposedBindings" @@ -10,6 +16,23 @@ echo "=== ExposedBindings Assembly Processor ===" echo "Unity Path: $UNITY_PATH" echo "Project Path: $PROJECT_PATH" +# Resolve the managed assemblies directory (macOS vs Windows layout) +if [ -d "$UNITY_PATH/Unity.app/Contents/Managed/UnityEngine" ]; then + MANAGED_DIR="$UNITY_PATH/Unity.app/Contents/Managed" + ENGINE_DIR="$MANAGED_DIR/UnityEngine" +elif [ -d "$UNITY_PATH/Editor/Data/Managed/UnityEngine" ]; then + MANAGED_DIR="$UNITY_PATH/Editor/Data/Managed" + ENGINE_DIR="$MANAGED_DIR/UnityEngine" +else + echo "Error: Could not locate Unity managed assemblies under $UNITY_PATH" + echo "Tried:" + echo " $UNITY_PATH/Unity.app/Contents/Managed/UnityEngine (macOS)" + echo " $UNITY_PATH/Editor/Data/Managed/UnityEngine (Windows)" + exit 1 +fi + +echo "Managed Dir: $MANAGED_DIR" + # Step 1: Build the Cecil processor echo "" echo "Step 1: Building Cecil processor..." @@ -32,8 +55,8 @@ rm -rf "$TEMP_BUILD" mkdir -p "$TEMP_BUILD/TempAssembly" cd "$TEMP_BUILD/TempAssembly" -# Create project file -cat > TempAssembly.csproj << 'EOF' +# Create project file — references are resolved from the detected Unity install +cat > TempAssembly.csproj << CSPROJ_EOF netstandard2.1 @@ -43,29 +66,29 @@ cat > TempAssembly.csproj << 'EOF' - /Applications/Unity/Hub/Editor/6000.0.31f1/Unity.app/Contents/Managed/UnityEngine/UnityEngine.CoreModule.dll + ${ENGINE_DIR}/UnityEngine.CoreModule.dll - /Applications/Unity/Hub/Editor/6000.0.31f1/Unity.app/Contents/Managed/UnityEngine/UnityEngine.AssetBundleModule.dll + ${ENGINE_DIR}/UnityEngine.AssetBundleModule.dll - /Applications/Unity/Hub/Editor/6000.0.31f1/Unity.app/Contents/Managed/UnityEngine/UnityEngine.ImageConversionModule.dll + ${ENGINE_DIR}/UnityEngine.ImageConversionModule.dll - /Applications/Unity/Hub/Editor/6000.0.31f1/Unity.app/Contents/Managed/Unity.Collections.dll + ${MANAGED_DIR}/Unity.Collections.dll - /Applications/Unity/Hub/Editor/6000.0.31f1/Unity.app/Contents/Managed/mscorlib.dll + ${MANAGED_DIR}/mscorlib.dll - /Applications/Unity/Hub/Editor/6000.0.31f1/Unity.app/Contents/Managed/System.dll + ${MANAGED_DIR}/System.dll - /Applications/Unity/Hub/Editor/6000.0.31f1/Unity.app/Contents/Managed/System.Core.dll + ${MANAGED_DIR}/System.Core.dll -EOF +CSPROJ_EOF # Copy source files from package Runtime~ directory cp -r "$PROJECT_PATH/Assets/Packages/com.cosminb.unity-internals/Runtime~"/* . @@ -110,7 +133,8 @@ echo "2. Unity will automatically reimport it" echo "3. Test the functionality with the TestUnityInternalBindings MonoBehaviour" echo "" echo "All methods processed successfully:" -echo " ✓ MarshalUnityObject - Direct access to Unity object pointers without allocations" -echo " ✓ ImageConversion_LoadImage_Injected - Direct Span-based image loading" -echo " ✓ AssetBundle_LoadFromMemoryAsync_Internal_Injected - Async bundle loading from Span" -echo " ✓ AssetBundle_LoadFromMemory_Internal_Injected - Sync bundle loading from Span" \ No newline at end of file +echo " - MarshalUnityObject - Direct access to Unity object pointers without allocations" +echo " - ImageConversion_LoadImage_Injected - Direct Span-based image loading" +echo " - AssetBundle_LoadFromMemoryAsync_Internal_Injected - Async bundle loading from Span" +echo " - AssetBundle_LoadFromMemory_Internal_Injected - Sync bundle loading from Span" +echo " - Texture encoding methods (PNG, JPG, TGA, EXR)" diff --git a/README.md b/README.md index 2764028..6a2957c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### High-Performance Unity Internal Bindings for Zero-Allocation Operations - [![Unity](https://img.shields.io/badge/Unity-6000.0.31f1-black.svg?style=flat&logo=unity)](https://unity3d.com/) + [![Unity](https://img.shields.io/badge/Unity-6000.0_|_6000.3-black.svg?style=flat&logo=unity)](https://unity3d.com/) [![C#](https://img.shields.io/badge/C%23-11.0-239120.svg?style=flat&logo=c-sharp)](https://docs.microsoft.com/en-us/dotnet/csharp/) [![IL2CPP](https://img.shields.io/badge/IL2CPP-Supported-00D4AA.svg?style=flat)](https://docs.unity3d.com/Manual/IL2CPP.html) [![Cross Platform](https://img.shields.io/badge/Platform-iOS_Android_WebGL_Windows_macOS_Linux-blue.svg?style=flat)](https://unity.com/) @@ -42,7 +42,7 @@ The `Runtime~` folder (with `~` suffix) prevents Unity from compiling the source ## Requirements -- Unity 6000.0.31f1 or later +- Unity 6000.0.31f1 through 6000.3.x (Cecil processor auto-detects internal API signatures) - .NET 6.0 SDK (for Cecil processing) - Mono.Cecil NuGet package @@ -84,16 +84,18 @@ Alternatively, add directly to your `Packages/manifest.json`: ### Post-Installation: Processing the Assembly -**Note:** The package includes a pre-built `ExposedBindings.dll` that works with Unity 6000.0.31f1+. +**Note:** The package includes a pre-built `ExposedBindings.dll` that was processed against Unity 6000.0.31f1. -If you need to rebuild for a different Unity version: +If you are on Unity 6000.3.x or a different version, you **must** rebuild the assembly: 1. Copy `ProcessAssembly.sh` and `CecilProcessor/` from this repository to your project root 2. Run: ```bash chmod +x ProcessAssembly.sh - ./ProcessAssembly.sh + # Pass your Unity editor path (auto-detects macOS/Windows layout): + ./ProcessAssembly.sh "/Applications/Unity/Hub/Editor/6000.3.0f1" ``` + The Cecil processor will auto-detect which internal signatures your Unity version uses and emit the correct IL. ## Usage Examples @@ -123,7 +125,18 @@ The library uses Mono.Cecil to: - **Platforms**: Android, WebGL, macOS, Windows, Linux, iOS - **Build Types**: Mono and IL2CPP -- **Unity Version**: 6000.0.31f1 and later +- **Unity Version**: 6000.0.31f1 through 6000.3.x (validated) + +### Unity 6000.3 (Unity 6.3) Notes + +Unity 6000.3 changed the internal signature of `UnityEngine.Object.GetPtrFromInstanceID`: + +| Version | Signature | +|---------|-----------| +| 6000.0 - 6000.2 | `IntPtr GetPtrFromInstanceID(int instanceID, Type objectType, out bool isMonoBehaviour)` | +| 6000.3+ | `IntPtr GetPtrFromInstanceID(int instanceID, out bool isMonoBehaviour)` | + +The Cecil processor detects the parameter count at build time and emits the correct IL, so the same source works across both version ranges. If you upgrade Unity, just re-run `ProcessAssembly.sh` against the new editor install. ## Performance Benefits @@ -173,19 +186,24 @@ If you want to modify the library or rebuild for a different Unity version: # Make the script executable chmod +x ProcessAssembly.sh -# Run the processor +# Run the processor (defaults to 6000.0.31f1, or pass your Unity path): ./ProcessAssembly.sh +./ProcessAssembly.sh "/Applications/Unity/Hub/Editor/6000.3.0f1" + +# Or via environment variable: +UNITY_PATH="/path/to/editor" ./ProcessAssembly.sh ``` This will: - Build the CecilProcessor from source -- Compile the source code from `Runtime~/` -- Process the assembly with Cecil to inject IL code +- Compile the source code from `Runtime~/` +- Auto-detect internal API signatures from your Unity install +- Process the assembly with Cecil to inject the correct IL - Output `ExposedBindings.dll` to the `Plugins/` folder ### Customizing for Different Unity Versions -1. Update Unity path in `ProcessAssembly.sh` if needed -2. Modify `CecilProcessor/Program.cs` if internal signatures changed +1. Pass your Unity editor path to `ProcessAssembly.sh` (macOS and Windows layouts auto-detected) +2. The Cecil processor inspects method signatures at build time — no manual code changes needed for known API variations 3. Test thoroughly on your target Unity version ### Build and Distribution (For Maintainers) From 62dda828bd7a9d1fe848a42ac773f4fb865e5653 Mon Sep 17 00:00:00 2001 From: Cosmin <38663153+Cosmin-B@users.noreply.github.com> Date: Mon, 16 Feb 2026 05:37:27 -0800 Subject: [PATCH 2/2] Fix ToNativeArray allocator footgun and remove redundant CoreModule load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BlittableArrayWrapper.ToNativeArray: Use Allocator.None instead of the caller-supplied allocator when wrapping Unity-owned memory via ConvertExistingDataToNativeArray. The previous code let callers pass e.g. Allocator.Persistent, which meant Dispose() would try to free native memory we don't own — causing heap corruption or a native crash. Updated the doc comment to make the ownership semantics explicit and point callers toward CopyToNativeArray when they need a disposable copy. CecilProcessor/Program.cs: ReplaceEncodingMethods was opening a second AssemblyDefinition for UnityEngine.CoreModule.dll even though ProcessAssembly already loads it at the top of the pipeline. Pass the existing coreModule instance through instead, avoiding the redundant disk read and resource leak. --- .../Runtime~/Internal/BlittableArrayWrapper.cs | 11 +++++++---- CecilProcessor/Program.cs | 8 +++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Assets/Packages/com.cosminb.unity-internals/Runtime~/Internal/BlittableArrayWrapper.cs b/Assets/Packages/com.cosminb.unity-internals/Runtime~/Internal/BlittableArrayWrapper.cs index 5b3d224..a8fa25b 100644 --- a/Assets/Packages/com.cosminb.unity-internals/Runtime~/Internal/BlittableArrayWrapper.cs +++ b/Assets/Packages/com.cosminb.unity-internals/Runtime~/Internal/BlittableArrayWrapper.cs @@ -28,7 +28,9 @@ public enum UpdateFlags /// /// Converts the native data to a NativeArray without copying. - /// The caller is responsible for disposing the NativeArray. + /// WARNING: This wraps Unity-owned memory. The returned NativeArray must NOT + /// be disposed — use Allocator.None so that Dispose() is a no-op. If you need + /// a NativeArray you can safely dispose, use instead. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public NativeArray ToNativeArray(Allocator allocator) where T : unmanaged @@ -42,13 +44,14 @@ public NativeArray ToNativeArray(Allocator allocator) where T : unmanaged int elementSize = UnsafeUtility.SizeOf(); int elementCount = size / elementSize; - // Create NativeArray that wraps the data + // Wrap Unity-owned memory with Allocator.None so Dispose() won't + // attempt to free native memory we don't own. var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray( - data, elementCount, allocator); + data, elementCount, Allocator.None); #if ENABLE_UNITY_COLLECTIONS_CHECKS // Set safety handle for the native array - NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); #endif diff --git a/CecilProcessor/Program.cs b/CecilProcessor/Program.cs index d80066d..8f712c3 100644 --- a/CecilProcessor/Program.cs +++ b/CecilProcessor/Program.cs @@ -102,7 +102,7 @@ static void ProcessAssembly(string inputPath, string outputPath, AddMarshalUnityObjectMethod(assembly, exposedType, coreModule); ReplaceImageConversionMethod(assembly, exposedType, imageConversionModule, unitySpanWrapper, ourSpanWrapper); ReplaceAssetBundleMethods(assembly, exposedType, assetBundleModule, unitySpanWrapper, ourSpanWrapper); - ReplaceEncodingMethods(assembly, exposedType, imageConversionModule); + ReplaceEncodingMethods(assembly, exposedType, imageConversionModule, coreModule); // Remove System.Private.CoreLib reference (added by dotnet SDK but not needed in Unity) var coreLibRef = assembly.MainModule.AssemblyReferences.FirstOrDefault(r => r.Name == "System.Private.CoreLib"); @@ -498,11 +498,9 @@ static void ProcessAssetBundleMethod(AssemblyDefinition assembly, AssemblyDefini Console.WriteLine($"Processed {methodName}"); } - static void ReplaceEncodingMethods(AssemblyDefinition assembly, TypeDefinition exposedType, AssemblyDefinition imageConversionModule) + static void ReplaceEncodingMethods(AssemblyDefinition assembly, TypeDefinition exposedType, AssemblyDefinition imageConversionModule, AssemblyDefinition coreModule) { - // Find Unity's BlittableArrayWrapper type - it's in CoreModule - var coreModulePath = Path.Combine(Path.GetDirectoryName(imageConversionModule.MainModule.FileName), "UnityEngine.CoreModule.dll"); - var coreModule = AssemblyDefinition.ReadAssembly(coreModulePath); + // Reuse the already-loaded CoreModule to find BlittableArrayWrapper var unityBlittableWrapper = coreModule.MainModule.GetType("UnityEngine.Bindings.BlittableArrayWrapper"); if (unityBlittableWrapper == null)