Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ public enum UpdateFlags

/// <summary>
/// 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 <see cref="CopyToNativeArray{T}"/> instead.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public NativeArray<T> ToNativeArray<T>(Allocator allocator) where T : unmanaged
Expand All @@ -42,13 +44,14 @@ public NativeArray<T> ToNativeArray<T>(Allocator allocator) where T : unmanaged
int elementSize = UnsafeUtility.SizeOf<T>();
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<T>(
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ public static class UnityExposed

/// <summary>
/// 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.
/// </summary>
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");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,27 @@ namespace ExposedBindings
{
/// <summary>
/// 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).
/// </summary>
#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();
Expand All @@ -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.");
}
}

Expand All @@ -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;
Expand All @@ -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))
Expand All @@ -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"
};
}

Expand All @@ -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;
}
}
}
}
4 changes: 2 additions & 2 deletions Assets/Packages/com.cosminb.unity-internals/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
41 changes: 26 additions & 15 deletions CecilProcessor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -154,14 +154,23 @@ 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)
{
Console.Error.WriteLine("Could not find GetPtrFromInstanceID method");
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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -485,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)
Expand Down
Loading