Skip to content

Getting Started: Assets file reading

nesrak1 edited this page Jan 13, 2024 · 12 revisions

This is a simple program to read every GameObject in a serialized file (assets file) and print its name and a list of components.

using AssetsTools.NET;
using AssetsTools.NET.Extra;

void LoadAssetsFile(string filePath)
{
    var manager = new AssetsManager();
    manager.LoadClassPackage("classdata.tpk");

    var afileInst = manager.LoadAssetsFile(filePath, true);
    var afile = afileInst.file;

    manager.LoadClassDatabaseFromPackage(afile.Metadata.UnityVersion);

    foreach (var goInfo in afile.GetAssetsOfType(AssetClassID.GameObject))
    {
        var goBase = manager.GetBaseField(afileInst, goInfo);
        var name = goBase["m_Name"].AsString;
        Console.WriteLine(name);

        var components = goBase["m_Component.Array"];
        foreach (var data in components)
        {
            var componentPointer = data["component"];
            var componentExtInfo = manager.GetExtAsset(afileInst, componentPointer);
            var componentType = (AssetClassID)componentExtInfo.info.TypeId;

            Console.WriteLine($"  {componentType}");
        }
    }
}

if (args.Length < 1) {
    Console.WriteLine("need a file argument");
    return;
}

LoadAssetsFile(args[0]);

Let's go over each part of the program.

var manager = new AssetsManager();
manager.LoadClassPackage("classdata.tpk");

This creates an AssetsManager and loads the classdata.tpk file. The AssetsManager handles loading and managing multiple assets files and bundles at the same time. The tpk file contains information about how to deserialize assets for various engine versions, so without this you can't deserialize any assets. Bundle files (.unity3d) are an exception since they embed this information most of the time. (More on this in the bundle loading section).

You can grab a tpk here: https://github.com/AssetRipper/Tpk/actions/workflows/type_tree_tpk.yml (do not use the brotli version.)

var afileInst = manager.LoadAssetsFile(filePath, true);
var afile = afileInst.file;

This loads a serialized file and its dependencies. The AssetsFileInstance is a wrapper around AssetsFile and is primarily used to help the AssetsManager load dependencies.

manager.LoadClassDatabaseFromPackage(afile.Metadata.UnityVersion);

Load the correct class database (cldb) version from the class package (tpk).

foreach (var goInfo in afile.GetAssetsOfType(AssetClassID.GameObject))
{
    var goBase = manager.GetBaseField(afileInst, goInfo);
    ...
}

Loop over all assets that are of type GameObject and get the base field. The base field is the root field or node of the asset. The structure of a GameObject asset looks like this:

GameObject Base <-- this is what we have so far
  vector m_Component
    Array Array
      ComponentPair data
        PPtr<Component> component
          int m_FileID
          long m_PathID
  unsigned int m_Layer
  string m_Name
  UInt16 m_Tag
  bool m_IsActive

You can find this structure by opening this type of asset in UABE(A) or AssetsView.

var name = goBase["m_Name"].AsString;
Console.WriteLine(name);

This gets the m_Name field and gets the value as a string. You should always use the .As version that corresponds to the type. For example, to get m_Tag you should use goBase["m_Tag"].AsUShort.

var components = goBase["m_Component.Array"];
foreach (var data in components)
{
    var componentPointer = data["component"];
    ...
}

Here we get m_Component.Array and loop through the elements. Each element returns ComponentPair data so we need to get the pointer field called component. Note that this could've also been written as goBase["m_Component"]["Array"]. The A.B syntax is shorthand for reading into multiple fields at once.

var componentExtInfo = manager.GetExtAsset(afileInst, componentPointer);
var componentType = (AssetClassID)componentExtInfo.info.TypeId;

Console.WriteLine($"  {componentType}");

The component is another asset, so we can use GetExtAsset to load that asset from the PPtr (asset pointer). AssetExternal returns three things, the AssetsFileInstance which the asset comes from (in this case of component assets, it's always the same file that the GameObject asset is in), the AssetFileInfo which has details on an asset's class/type id, file size, and file address, and the base field of that object (can be disabled by setting the onlyGetInfo argument to true).

After we have the info of the asset, we get the type with TypeId and cast it to an AssetClassID so we have the name. Then we print it.

That's all there is to it! If you want to load bundles, check out the bundle file reading page as well.