Tesearis.ArtifactInspectorForUnity.Core
2.0.0
dotnet add package Tesearis.ArtifactInspectorForUnity.Core --version 2.0.0
NuGet\Install-Package Tesearis.ArtifactInspectorForUnity.Core -Version 2.0.0
<PackageReference Include="Tesearis.ArtifactInspectorForUnity.Core" Version="2.0.0" />
<PackageVersion Include="Tesearis.ArtifactInspectorForUnity.Core" Version="2.0.0" />
<PackageReference Include="Tesearis.ArtifactInspectorForUnity.Core" />
paket add Tesearis.ArtifactInspectorForUnity.Core --version 2.0.0
#r "nuget: Tesearis.ArtifactInspectorForUnity.Core, 2.0.0"
#:package Tesearis.ArtifactInspectorForUnity.Core@2.0.0
#addin nuget:?package=Tesearis.ArtifactInspectorForUnity.Core&version=2.0.0
#tool nuget:?package=Tesearis.ArtifactInspectorForUnity.Core&version=2.0.0
Tesearis.ArtifactInspectorForUnity.Core
Typed, lazy access to the objects inside Unity's built artifacts (player builds or asset bundles) from inside the Unity Editor or as a standalone .NET library.
What it does
Tesearis.ArtifactInspectorForUnity.Core mounts a built archive using Unity's own UnityFileSystemApi and exposes the serialized objects inside it. Each object's
fields are described by its type tree and read lazily through a TypeTreeReader: nothing is bulk-decoded or copied until you actually ask for a
field's value or raw bytes, so inspecting a bundle full of large textures or meshes doesn't require loading them all into memory first.
Typed, tolerant views of specific known object types can be layered on top via the public ArtifactAdapter<T> mechanism (see
Custom adapters).
This is the core library: a plain .NET package with no compile-time dependency on Unity. Pairs with the separate
Tesearis.ArtifactInspectorForUnity.Editor package (adapters for UnityEngine.* types, etc., see the
artifact-inspector-for-unity-editor repository).
Requirements
- Targets Unity 6000.3+ only: bundles/player builds must be built by Unity 6000.3 or newer, and the
UnityFileSystemApilibrary loaded at runtime must come from a matching Unity Editor install. - Targets
netstandard2.0andnet8.0. No compile-time dependency on Unity. - At runtime, needs a local Unity Editor installation to load
UnityFileSystemApi(.dylib/.dll/.so) from. Auto-discovered when running inside the Editor process; outside it, point at the library explicitly (see Running outside the Editor). UnityFileSystemApiis an internal, undocumented Unity component: its location, exported function signatures, and behavior aren't guaranteed across Editor versions, and may change or be removed without notice. Account for that before relying on this in CI or production.ArtifactInspector.GetNativeLibraryVersion()/GetUnityEditorVersion()andSerializedFile.Versionlet you check what you're actually talking to at runtime (e.g. log or assert on them at startup) instead of only finding out when something breaks.
Install
dotnet add package Tesearis.ArtifactInspectorForUnity.Core
For use from Unity Editor tooling, add the package to an Editor-only assembly (e.g. via NuGetForUnity
or by vendoring the DLL). It works equally well as a plain dependency of a standalone .NET tool, as long as ArtifactInspector.SetupLibraryPath is
configured.
Quick start
using Tesearis.ArtifactInspectorForUnity.Core;
using var archive = ArtifactInspector.OpenAssetBundle("myasset.bundle");
foreach (var entryName in archive.EntryNames)
{
using var serializedFile = archive.OpenSerializedFile(entryName);
foreach (var objectRef in serializedFile.Objects)
{
if (objectRef.ClassName != "Texture2D") continue;
var reader = objectRef.GetReader();
var name = reader.Field("m_Name").AsString();
var width = reader.Field("m_Width").AsInt32();
var height = reader.Field("m_Height").AsInt32();
Console.WriteLine($"{name}: {width}x{height}");
}
}
Fields can be nested or array-valued:
var reader = objectRef.GetReader();
if (reader.TryGetField("m_Channels", out var channels))
{
foreach (var channel in channels.Elements())
{
var format = channel.Field("format").AsByte();
}
}
Loose Player Build files
Opening loose files directly from disk without an archive container (e.g. Player Build output like sharedassets0.assets or globalgamemanagers):
using var serializedFile = ArtifactInspector.OpenSerializedFile("path/to/sharedassets0.assets");
foreach (var objectRef in serializedFile.Objects)
{
var reader = objectRef.GetReader();
// ...
}
Repeated and cross-object field lookups (snapshots)
ObjectRef.GetReader() builds a fresh TypeTreeReader every call, which is the right choice for reading
an object's fields once. It's the wrong choice for reading the same object's fields repeatedly -- e.g.
several sibling MonoBehaviours each resolving their owning GameObject's name -- since each call re-walks
that object's type tree from scratch. ObjectRef.Snapshot() fixes that: the first call decodes the object's
fields once and caches the result on the owning SerializedFile; every later call for the same object, from
any caller, is a plain dictionary lookup with no further reads.
var snapshot = objectRef.Snapshot();
var name = snapshot.Field("m_Name").AsString();
// A later lookup of the same object -- from anywhere -- is served from the cache, not re-walked.
var sameSnapshot = objectRef.Snapshot();
A field whose own size exceeds MaterializeOptions.MaxInlineFieldSizeBytes (1024 bytes by default) is left
as a deferred reference instead of eagerly decoded -- large blobs (mesh/texture/audio-scale payloads, unusually
long strings/arrays) stay exactly as lazy as GetReader() already makes them; only the fields cheap enough to
be worth caching are decoded up front. SnapshotField.IsDeferred reports which; SnapshotField.ToReader() is
an explicit escape hatch back to a live, lazy reader for one.
PPtr.TryResolveSnapshot resolves a local reference (e.g. a Component's owning GameObject) straight to a
cached snapshot, so chasing the same reference repeatedly costs nothing after the first resolution:
var target = reader.Field("m_GameObject").AsPPtr();
if (target.TryResolveSnapshot(serializedFile, out var gameObject))
{
Console.WriteLine(gameObject.Field("m_Name").AsString());
}
For a consumer that knows upfront it will touch most or all of a file's objects -- walking a whole scene's
hierarchy, say -- SerializedFile.MaterializeAll(options) snapshots every (optionally filtered) object in one
pass, sorted by byte offset so the underlying reads stay forward/sequential instead of jumping around. This is
an explicit, opt-in call: opening a file never eagerly materializes anything on its own, since doing so for
every object in a huge scene could cost several times the file's own size in managed memory.
using Tesearis.ArtifactInspectorForUnity.Core.TypeTree;
// Only GameObject/Transform/MonoBehaviour (ClassIds 1/4/114) -- skips Mesh/Texture2D/AudioClip entirely.
var options = new MaterializeOptions { TypeIdFilter = typeId => typeId is 1 or 4 or 114 };
var result = serializedFile.MaterializeAll(options);
Console.WriteLine($"{result.SucceededCount} objects materialized, {result.Failures.Count} failed");
One object failing to materialize (e.g. an unsupported [SerializeReference] shape, see
Known limitations) is recorded in MaterializeResult.Failures, not thrown -- it doesn't
abort the rest of the pass.
Custom adapters
Adapters turn a raw type-tree reader into a typed, tolerant view of a known object type, via the public ArtifactAdapter<T> mechanism. Third
parties, including Unity Editor-side code living in a separate assembly (where real UnityEngine.* types are available), can register their own
adapters and dispatch across all of them with ArtifactAdapterRegistry.Inspect:
using Tesearis.ArtifactInspectorForUnity.Core;
using Tesearis.ArtifactInspectorForUnity.Core.Adapters;
var registry = new ArtifactAdapterRegistry()
.Register(new AudioClipAdapter())
.Register(new MonoBehaviourAdapter());
using var archive = ArtifactInspector.OpenAssetBundle("myasset.bundle");
foreach (var obj in registry.Inspect(archive))
{
switch (obj)
{
case AudioClipInfo clip:
Console.WriteLine($"{clip.Name}: {clip.Channels} channel(s)");
break;
case RawObject raw:
Console.WriteLine($"(unhandled) {raw.ClassName} #{raw.ObjectRef.PathId}");
break;
}
}
(AudioClipAdapter and MonoBehaviourAdapter are illustrative here - write your own following Writing an adapter.)
Objects that no registered adapter recognizes come back as a RawObject (its ClassName plus the original ObjectRef) instead of being silently
dropped, so you can log them or fall back to raw.ObjectRef.GetReader() yourself.
Inspect is a convenience on top of the manual loop from the quick start above: SerializedFile.Objects/ObjectRef.GetReader() are still there for
one-off or lower-level use, and dispatching through a registry additionally reuses a single TypeTreeReader per object across matching and reading,
instead of building one for ClassName and another for GetReader(). Inspect can also dispatch directly over loose files via registry.Inspect(serializedFile), and accepts an optional orderByOffset: true flag to sort objects by byte offset for forward-sequential reads.
Writing an adapter
Implement ArtifactAdapter<T>:
public sealed class AudioClipAdapter : ArtifactAdapter<AudioClipInfo>
{
protected override string ClassName => "AudioClip";
public override AudioClipInfo Read(ArtifactAdapterContext context)
{
var reader = context.Reader; // the same TypeTreeReader ObjectRef.GetReader() would give you
// ... decode fields, optionally using context.Archive / context.SerializedFile for streamed
// data or cross-references to other objects in the same file ...
}
}
- Override
Matchesinstead ofClassNamefor anything beyond a straight type-name check, e.g. only handling aMonoBehaviourwhosem_Scriptfield (.AsPPtr()) resolves to a particular script. context.SerializedFile.TryGetObject(pathId, out var objectRef)lets an adapter look up another object in the same file by path ID (PPtr resolution).context.Archivelets an adapter read from other archive entries, e.g. to resolve a streamed resource file named by a field in the object.- Adapters must not retain
context.Reader,context.SerializedFile, orcontext.ArchivepastReadreturning: copy whatever data you need into your own result type first.Inspectdisposes eachSerializedFileas it moves on to the next archive entry. - Implement
IArtifactAdapterdirectly instead ofArtifactAdapter<T>only if you need astructresult type, or want to skip theClassName-based defaultMatches. IArtifactAdapter.ResultType(alwaystypeof(T)onArtifactAdapter<T>) lets a caller discover what an adapter produces without invokingRead, e.g. to list the types a registry can dispatch to, or to filter which adapters apply before runningInspectover a large archive.
Registries are ordered, first-match-wins: register more specific adapters before more general ones. new ArtifactAdapterRegistry() always
starts empty; this library bundles no adapters of its own.
Streamed data
Some object types (textures, meshes, audio clips above a size threshold) don't store their payload inline in the SerializedFile; instead a field
names an offset/size into a separate, larger file alongside it (e.g. a texture's .resS streaming data file next to its .assets file). An
adapter for such a type reads that field itself and hands the caller a StreamingInfo (Offset, Size, Path) describing where the real bytes
live, rather than trying to inline megabytes of pixel/vertex/sample data into its result:
public sealed class Texture2DAdapter : ArtifactAdapter<Texture2DInfo>
{
protected override string ClassName => "Texture2D";
public override Texture2DInfo Read(ArtifactAdapterContext context)
{
var reader = context.Reader;
var streamData = reader.Field("m_StreamData");
var streaming = new StreamingInfo(
streamData.Field("offset").AsInt64(),
streamData.Field("size").AsInt32(),
streamData.Field("path").AsString());
return new Texture2DInfo(reader.Field("m_Name").AsString(), streaming);
}
}
// Caller resolves the streamed bytes through the same archive the object came from:
if (!string.IsNullOrEmpty(info.Streaming.Path))
{
var pixelBytes = info.Streaming.ReadBytes(context.Archive);
}
An empty Path means the payload is inline in the object itself instead (read it directly off reader); this library doesn't decide that for
you, since which field means "streamed" and what an empty path means is specific to each Unity type.
Public API
Quick reference; see the examples above for usage, and each type's XML doc comments for full member-level detail.
ArtifactInspector: entry point (OpenAssetBundle,OpenSerializedFile,SetupLibraryPath,AddTypeTreeSource/RemoveTypeTreeSource, native/editor version checks).ArtifactArchive,SerializedFile,ObjectRef,ExternalReference,PPtr,ArchiveEntryInfo,IRandomAccessByteSource,GuidFormatting: the archive and serialized file model.TypeTreeReader,TypeTreeNode,TypeTreeSummary: lazy, random access field reading.ObjectSnapshot,SnapshotField,MaterializeOptions,MaterializeResult: cached, mostly-eager field decoding for repeated/cross-object lookups, see Repeated and cross-object field lookups above.ArtifactAdapterRegistry,IArtifactAdapter,ArtifactAdapter<T>,ArtifactAdapterContext,RawObject: the adapter mechanism, see Custom adapters above.StreamingInfo: describes an out-of-line payload (offset/size/path) for adapters covering streamed asset types, withReadBytes/OpenByteSourcehelpers for resolving the payload through anArtifactArchive, see Streamed data above.BinaryFormat.SerializedFileDetector,SerializedFileInfo,StrippedObjectInfo,TypeIdRegistry,YamlSerializedFileDetector: stripped file (no TypeTree) support, see Stripped files below.ArtifactInspectorException,NativeCallException,SerializedFileOpenException,NativeFeatureNotSupportedException,UnsupportedManagedReferenceShapeException: the exception hierarchy, see Exceptions below.
Stripped files (no TypeTree)
Shipped Player builds are normally built with EnableTypeTree=false, which UFS_OpenSerializedFile refuses to open at all. BinaryFormat.SerializedFileDetector reads a SerializedFile's header, object list, and external references directly off bytes instead, with no native call and no TypeTree
needed for any of that. It works over the same IRandomAccessByteSource this library already uses internally, so it runs equally well against a
bare on-disk file and an archive-mounted entry:
using Tesearis.ArtifactInspectorForUnity.Core.BinaryFormat;
// A bare file on disk, no archive, no Unity Editor process needed:
if (SerializedFileDetector.TryDetect("Builds/StandaloneWindows64/mybundle_Data/sharedassets0.assets", out var info))
{
Console.WriteLine($"Unity {info.UnityVersion}, EnableTypeTree={info.EnableTypeTree}");
foreach (var obj in info.Objects)
{
Console.WriteLine($"{obj.PathId}: {obj.ClassName} ({obj.ByteSize} bytes)");
}
}
Or against an archive-mounted entry, including ones the native API can't open:
using var archive = ArtifactInspector.OpenAssetBundle("Builds/StandaloneWindows64/mybundle");
var byteSource = archive.OpenRawByteSource(archive.EntryNames[0]);
SerializedFileDetector.TryDetect(byteSource, out var info);
Field values are out of scope for a stripped file: there is no schema to read them from. What's available is the object list (id/offset/size,
labeled by class name via TypeIdRegistry) and the external-reference list, both of which don't actually require a TypeTree to read.
BinaryFormat.SerializedFileDetector:TryDetectdetects and parses a SerializedFile straight off bytes;IsMissingTypeTreesis a cheap fast path that checks only whetherEnableTypeTreeis false.SerializedFileInfo(readonly struct): the parsed header, metadata, object list, and external references, plusMetadataParsed/MetadataParseErrorfor versions this library can't fully parse (only format versions 22 and 23, Unity 6000.3.x, are supported).StrippedObjectInfo(readonly struct):PathId,TypeId,ByteOffset,ByteSize,ClassName; deliberately separate fromObjectRefsince there's no TypeTree to build a reader from, so it exposes noGetReader().Nameis also populated best-effort, with no TypeTree, for classes wherem_Name's byte position is known without one: directNamedObjectsubtypes (Texture2D,Mesh,AudioClip,Material, ...), where it's reliably the object's first field, andMonoBehaviour, at a fixed offset past itsPPtr/enabledprefix (seeBinaryFormat.StrippedObjectNameReader).nullfor every other class, and for a read that didn't look like a valid name -- this is deliberately not a general field decoder.TypeIdRegistry: static Unity ClassID to class name lookup, the only way to label a stripped object's type.YamlSerializedFileDetector: sniffs the%YAML 1.1magic to reject Editor-text-format.asset/.prefab/.unityfiles up front, as a fast triage before attempting a binary parse.
ArtifactArchive.OpenSerializedFile also uses this detector internally: when the native open fails and the entry's bytes positively confirm it has
no TypeTrees, it throws SerializedFileOpenException instead of the generic NativeCallException: see Exceptions.
Running outside the Editor
Inside the Editor process, the native UnityFileSystemApi library is located automatically from the running instance (via
EditorApplication.applicationPath) and calls are made against that instance's own bundled copy, with no setup call needed.
Outside the Editor (a standalone tool, a CI job, a test runner) there is no running Editor process to auto-detect one from. In that case,
ArtifactInspector.SetupLibraryPath(path) must be called once, before any other call into the library, otherwise those calls fail.
using Tesearis.ArtifactInspectorForUnity.Core;
// Pass exact location of UnityFileSystemApi (.dylib / .dll / .so).
ArtifactInspector.SetupLibraryPath("/path/to/UnityFileSystemApi.dylib");
using var archive = ArtifactInspector.OpenAssetBundle("Builds/StandaloneWindows64/mybundle");
ArtifactInspector.SetupLibraryPath(string path): points the library at an explicitUnityFileSystemApinative library path, for use when there is no running Editor process to auto-detect one from.
Newer SerializedFile formats (version ≥ 23) can have their TypeTree blobs extracted out-of-band at build time instead of stored inline; opening one
of those requires registering that external source first, via ArtifactInspector.AddTypeTreeSource(path). Like SetupLibraryPath, this is a
process-wide setup step, called once before opening a file that needs it, not per-archive/per-file. It requires a native library new enough to
support it (Unity 6.5+); on an older library it throws NativeFeatureNotSupportedException instead of failing silently or crashing.
ArtifactInspector.AddTypeTreeSource("/path/to/extracted/typetree.dat");
using var archive = ArtifactInspector.OpenAssetBundle("Builds/StandaloneWindows64/mybundle");
ArtifactInspector.AddTypeTreeSource(string filePath)/RemoveTypeTreeSource(string filePath): register or unregister an out-of-band TypeTree source, process-wide.
Exceptions
ArtifactInspectorException: base type for exceptions raised by this library.NativeCallException : ArtifactInspectorException: a call into the nativeUnityFileSystemApifailed; carries the underlyingReturnCode.SerializedFileOpenException : ArtifactInspectorException: thrown byArtifactArchive.OpenSerializedFilein place ofNativeCallExceptionspecifically when the failure is positively confirmed (viaSerializedFileDetector.IsMissingTypeTrees) to be caused by the entry having no TypeTrees; carriesEntryNameandMissingTypeTrees. Every otherOpenSerializedFilefailure mode still throwsNativeCallExceptionunchanged.NativeFeatureNotSupportedException : ArtifactInspectorException: thrown in place ofNativeCallExceptionwhen a call requires a native entry point that isn't present at all in the loadedUnityFileSystemApilibrary (e.g.AddTypeTreeSource/RemoveTypeTreeSourceagainst a native library older than Unity 6.5, orSerializedFile.TypeTrees/GetTypeTreeByIndexagainst a native library that doesn't exportUFS_GetTypeTreeCount/UFS_GetTypeTreeInfo/UFS_GetTypeTreeByIndex); carriesSymbolName.TypeTree.UnsupportedManagedReferenceShapeException : ArtifactInspectorException: thrown when a[SerializeReference]polymorphic field's offset or size is actually needed, e.g. reading that field directly, or a sibling positioned after it whose offset can't be resolved without it, see Known limitations below; carriesNameandTypeName. Merely containing such a field elsewhere in an object's schema doesn't throw:ObjectRef.GetReader()still succeeds, and every other field remains readable.ArtifactAdapterRegistry.Adapt/Inspectcatch this and fall back to aRawObjectinstead of propagating it.
Resource management
ArtifactArchive and SerializedFile both implement IDisposable. Dispose in nested order (innermost first), as in the quick start example above.
Disposing an archive also actively invalidates any SerializedFiles still open from it: any further call on one throws ObjectDisposedException,
the same as if you'd disposed that SerializedFile directly, rather than leaving it looking usable while its native calls silently target a mount
that no longer exists.
Testing
dotnet test runs the full suite, but the integration tests under tests/Tesearis.ArtifactInspectorForUnity.Core.Tests/Native/Interop/ that exercise the real
native UnityFileSystemApi library are skipped automatically unless the binary for the current OS is present.
To run them against a specific Unity Editor version, copy that Editor install's UnityFileSystemApi.dylib (macOS) / .dll (Windows) / .so
(Linux) into tests/Tesearis.ArtifactInspectorForUnity.Core.Tests/UnityFileSystemApiLibraries/. These are Unity's own undocumented native components and must not
be redistributed.
The tests under tests/Tesearis.ArtifactInspectorForUnity.Core.Tests/Model/RealTestDataIntegrationTests.cs go one step further and exercise the full pipeline,
mounting an archive, walking its objects, and dispatching them through the adapter registry, against real Unity-built output. They're skipped the
same way unless there's also something to run them against: drop any real file (an asset bundle, a player build's data file, ...) into
tests/Tesearis.ArtifactInspectorForUnity.Core.Tests/TestData/. No particular name, extension, or content is expected: every file found there is exercised, and
the assertions are structural (it opens, every object's fields are readable, nothing is silently dropped) rather than tied to specific expected
values, so any real data works. Neither of these folders' contents are committed (see their .gitkeeps) or redistributed.
Known limitations
A few boundaries are intentional, not oversights:
[SerializeReference]fields aren't decoded. Unity's polymorphic managed-reference shapes aren't readable via the type-tree walk this library uses; reading one such field (or a sibling positioned after it) throwsUnsupportedManagedReferenceShapeExceptioninstead of misreading it -- see Exceptions. Every other field remains readable.- No WebGL bundle support. Unity WebGL's separate
UnityWebData1.0container format isn't handled; this is new scope, not a gap in existing functionality.
Reading field-level data through an archive-mounted entry (ArtifactArchive.ReadRawEntry/OpenRawByteSource, or walking a
TypeTreeReader's fields via HasField/Field) was previously suspected to crash the host process (a native segfault) for some
entries, based on a standalone .NET host against real Addressables/AssetBundle output. It hasn't reproduced across multiple builds run
inside an actual Unity Editor process since, though the root cause was never isolated. If you hit a crash while reading archive-mounted
entry data, please open an issue with repro details.
See CHANGELOG.md for what's included in each release.
Acknowledgments
This library's approach to the UnityFileSystemApi native binding and type-tree reading was informed by studying
UnityDataTools as a reference for the shape of Unity's undocumented native ABI and
serialized-file layout, then independently written from scratch. One current exception: BinaryFormat/TypeIdRegistry.cs's ClassID lookup
table is sourced from UnityDataTools' own generated table and used here under its license rather than this project's own. See
ThirdPartyNotices.md for the full breakdown.
"Unity" and "Unity Technologies" are trademarks of Unity Technologies. This project is not affiliated with, endorsed by, or sponsored by Unity Technologies.
License
MIT, see LICENSE, with exceptions noted in ThirdPartyNotices.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Buffers (>= 4.5.1)
- System.Memory (>= 4.5.5)
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.