GitHub 您所在的位置:网站首页 assetfiles GitHub

GitHub

#GitHub| 来源: 网络整理| 查看: 265

AssetsTools.NET v2

A .net library for reading and modifying unity assets and bundles based off of the AssetsTools library from UABE.

AssetsTools.NET v3 is currently in progress!

Rework progress is ongoing on the upd21-with-inst branch. If you're trying to use AT2 and having issues on newer engine games, you might want to try AT3.

Jump to a tool:

AssetsTools AssetsView

AssetsTools

Nuget Prereleases discord

Table of contents What is this Getting started Read an assets file Write an assets file Value builder (add assets/fields) Read a bundle file Write a bundle file Compress a bundle file Bundle creator (create assets/bundle files) Reading a MonoBehaviour Reading asset paths from resources.assets and bundles Exporting a Texture2D Class database Noooo it's not working!! What is this

This is a library for reading and writing unity assets files and bundle files. The original api design and file formats (class package) comes from UABE's C++ AssetsTools library, but it has been rewritten in C# with many added helper functions. Because of that, there are usually two ways to do something.

The Extra namespace contains classes and extension methods that aren't in the original library. For example, AssetsManager helps manage loading multiple files and their dependencies and AssetHelper/BundleHelper contains many useful miscellaneous methods. If there is a function here, you should prefer it over initializing things yourself. The Standard namespace contains classes and methods that are similar to the original library.

The library is very low-level, and even with helper functions, some features still take a bit of boilerplate. However, you can pretty much edit anything and everything you want. Any asset type is supported, both in assets files and bundles, but making sense of it is up to you. For example, there is no code to "extract sprites", but you can see the fields that store that information.

Getting started

To help write code, you'll want to open the file you want to read/write in a tool like AssetsView.NET (in this repo) so you can see what assets have what fields.

Read an assets file

Here's a full example to read all name strings from all GameObjects from resources.assets.

var am = new AssetsManager(); var inst = am.LoadAssetsFile("resources.assets", true); am.LoadClassPackage("classdata.tpk"); am.LoadClassDatabaseFromPackage(inst.file.typeTree.unityVersion); foreach (var inf in inst.table.GetAssetsOfType((int)AssetClassID.GameObject)) { var baseField = am.GetTypeInstance(inst, inf).GetBaseField(); var name = baseField.Get("m_Name").GetValue().AsString(); Console.WriteLine(name); } am.UnloadAllAssetsFiles();

Below will go into more about what each part does.

Load assets file

To load an assets file, use the LoadAssetsFile method with an AssetsManager.

var am = new AssetsManager(); var inst = am.LoadAssetsFile("resources.assets", true); //true = load dependencies

LoadAssetsFile returns an AssetsFileInstance which is a wrapper for two classes.

AssetsFile contains file version info, the type tree for storing info about asset types, and a list of dependencies. Most of the time, you should just use the dependencies list in AssetsFileInstance. AssetsFileTable which is a list of AssetFileInfoExs which contain pointers to the raw data in file and more.

You can think of Standard classes like AssetsFile and AssetsFileTable as structs directly representing the file format and AssetsManager classes like AssetsFileInstance as classes that help linking files together.

Load class database

Before going any further, type information needs to be loaded. This only applies to assets files and not bundles, which usually have type trees. The classdata.tpk file, which you can get from the releases page, contains type info that isn't included assets files. For more information about this file, see the classdata info section below.

am.LoadClassPackage("classdata.tpk"); am.LoadClassDatabaseFromPackage(inst.file.typeTree.unityVersion); Get asset info

Before we starting reading an asset, we need to look through the asset info table. Asset infos contain information such as the size of the asset, the position in the file, and the type of asset it is. There are many ways to get asset infos depending on what you're doing.

If you want to load a single asset by asset id or name, you can use inst.table.GetAssetInfo().

var table = inst.table; //if you know there is only one asset by the name RocketShip, you don't need to search by type var inf1 = table.GetAssetInfo("RocketShip"); //if there are multiple assets by the same name, you can use type to narrow it down var inf2 = table.GetAssetInfo("RocketShip", (int)AssetClassID.GameObject); //if you want to get an asset by id (not recommended since they change over versions) var inf3 = table.GetAssetInfo(428);

If you want to load all asset infos, loop over inst.table.assetFileInfo.

var table = inst.table; foreach (var inf in table.assetFileInfo) { //... }

Or if you want to load assets of a certain type, use inst.table.GetAssetsOfType().

var table = inst.table; foreach (var inf in table.GetAssetsOfType((int)AssetClassID.GameObject)) { //... }

You can get an asset's type id from the Type field in UABE or right click -> Properties menu in AssetsView.

Reading the asset's fields

Once you've done that, you will probably want to deserialize the asset. To do this, use AssetManager's GetTypeInstance method, then call GetBaseField on that.

var baseField = am.GetTypeInstance(inst, inf).GetBaseField();

Now you can browse the fields in this asset as if it were json. Use Get to get children fields and GetValue to get the actual value of field if it has one.

var m_Name = baseField.Get("m_Name").GetValue().AsString();

If you prefer the syntax, [] is supported as well.

var m_Name = baseField["m_Name"].value.AsString();

Arrays (or regular fields) can be iterated by field.GetChildrenList().

var names = baseField.Get("names"); foreach (var name in names.GetChildrenList()) { var nameStr = name.GetValue().AsString(); } Following asset pointers

You'll probably come across a PPtr field, a pointer to another asset that you might want to follow. Because assets can sometimes be in different files, it is best to use am.GetExtAsset() rather than table.GetAssetInfo() and am.GetTypeInstance() . GetExtAsset also returns the file it came from and the info of the asset in case you don't know the file or the type of the asset you're reading.

For example, if you wanted to get the Transform attached to a GameObject, you could use GetExtAsset.

var componentArray = gameObjectBf.Get("m_Component").Get("Array"); //get first component in gameobject, which is always transform var transformRef = componentArray[0].Get("component"); var transform = am.GetExtAsset(instance, transformRef); var transformBf = transform.instance.GetBaseField(); //... do something with transform fields Write an assets file

Writing assets files is a bit tricky. Instead of just saving the modified assets file, you must pass in changes you make when you write.

First, make the changes to fields you want to fields with field.GetValue().Set(). Then, call WriteToByteArray() on the base field to get the new raw bytes of the asset. To save the new changes back to an assets file, create an AssetsReplacer and pass it to the assets file's Write method.

var am = new AssetsManager(); am.LoadClassPackage("classdata.tpk"); //true to load dependencies. if you know you're only //reading this one asset, you can set to false to //speed things up a bit var inst = am.LoadAssetsFile("resources.assets", true); //load correct class database for this unity version am.LoadClassDatabaseFromPackage(inst.file.typeTree.unityVersion); var inf = inst.table.GetAssetInfo("MyBoringGameObject"); var baseField = am.GetTypeInstance(inst, inf).GetBaseField(); baseField.Get("m_Name").GetValue().Set("MyCoolGameObject"); var newGoBytes = baseField.WriteToByteArray(); var repl = new AssetsReplacerFromMemory(0, inf.index, (int)inf.curFileType, 0xffff, newGoBytes); using (var stream = File.OpenWrite("resources-modified.assets")) using (var writer = new AssetsFileWriter(stream)) { inst.file.Write(writer, 0, new List() { repl }, 0); } am.UnloadAllAssetsFiles();

The constructor for AssetsReplacers take a monoScriptIndex parameter. If you won't be touching MonoBehaviour assets, leave this as 0xffff like in this example (no script id) as a shortcut. If you are editing MonoBehaviours, use AssetHelper.GetScriptIndex() to get the correct script index. More information about this in the MonoBehaviour reading/writing section.

Value builder (add assets/fields)

With the above examples, you can change the value of existing fields, but you can't add new fields (like to add to an array or create an asset from scratch.) To do that, you can use the ValueBuilder which lets you create blank AssetTypeValueFields from AssetTypeTemplateFields.

Set array items //example for a GameObject var componentArray = baseField.Get("m_Component").Get("Array"); //create two blank pptr fields var transform = ValueBuilder.DefaultValueFieldFromArrayTemplate(componentArray); var rigidbody = ValueBuilder.DefaultValueFieldFromArrayTemplate(componentArray); transform.Get("m_FileID").GetValue().Set(0); transform.Get("m_PathID").GetValue().Set(123); rigidbody.Get("m_FileID").GetValue().Set(0); rigidbody.Get("m_PathID").GetValue().Set(456); AssetTypeValueField[] newChildren = new AssetTypeValueField[] { transform, rigidbody }; componentArray.SetChildrenList(newChildren); //... do replacer stuff

If you need to add items instead of set, you'll have to use array concat (I know, a little annoying)

componentArray.SetChildrenList(componentArray.children.Concat(newChildren)); Create new asset from scratch //example for TextAsset var templateField = new AssetTypeTemplateField(); //if from an assets file, use the class database var cldbType = AssetHelper.FindAssetClassByName(am.classFile, "TextAsset"); templateField.FromClassDatabase(am.classFile, cldbType, 0); //if from a bundle, use the type tree (more on this in the bundle loading section below) //var ttType = AssetHelper.FindTypeTreeTypeByName(inst.file.typeTree, "TextAsset"); //templateField.From0D(ttType, 0); var baseField = ValueBuilder.DefaultValueFieldFromTemplate(templateField); baseField.Get("m_Name").GetValue().Set("MyCoolTextAsset"); baseField.Get("m_Script").GetValue().Set("I have some sick text"); var nextAssetId = table.assetFileInfo.Max(i => i.index) + 1; replacers.Add(new AssetsReplacerFromMemory(0, nextAssetId, cldbType.classId, 0xffff, baseField.WriteToByteArray())); //... do other replacer stuff Read a bundle file

Use am.LoadBundleFile() to load bundles and am.LoadAssetsFileFromBundle() to load assets files.

var am = new AssetsManager(); var bun = am.LoadBundleFile("bundle.unity3d"); //load first asset from bundle var assetInst = am.LoadAssetsFileFromBundle(bun, 0, true); //if you're not sure the asset you want is first, //iterate over bun.file.bundleInf6.dirInf[x].name //(i.e. skip files that end with .resS/.resource)

If you want data files in the bundle like .resS or .resource, use BundleHelper.LoadAssetDataFromBundle() to get a byte array of that file.

If you want to read the files listing in a bundle but don't want to decompress the entire file yet, use BundleHelper.UnpackInfoOnly() to decompress only the file listing info block. Make sure to call am.LoadBundleFile() with unpackIfPacked set to false.

Notes Unity usually packs type information into bundles. That means you probably won't have to load class package files (classdata.tpk). There are exceptions, but most of the time you won't have to worry about it. Check assetInst.file.typeTree.hasTypeTree if you're not sure. If you are loading a huge bundle, consider writing it to file first. //set to false to prevent automatically decompressing to memory var bun = am.LoadBundleFile("bundle.unity3d", false); var bunDecompStream = File.OpenWrite("bun.decomp"); //load new bundle file from newly written stream bun.file = BundleHelper.UnpackBundleToStream(bun.file, bunDecompStream); Do not iterate over bun.loadedAssetsFiles to find assets. This list contains all loaded assets files (from when you call am.LoadAssetsFileFromBundle()) files from this bundle, not all files that are actually in the bundle. Instead, use either am.LoadAssetsFileFromBundle() or BundleHelper.LoadAssetFromBundle(). Write a bundle file

Bundle writing works similar to assets files where you use replacers to replace files in the bundle.

Note that when you create a BundleReplacer, you have the option of renaming the asset in the bundle, or you can use the same name (or make newName null) to not rename the asset at all.

var am = new AssetsManager(); am.LoadClassPackage("classdata.tpk"); var bunInst = am.LoadBundleFile("boringbundle.unity3d"); //read the boring file from the bundle var inst = am.LoadAssetsFileFromBundle(bunInst, "boring"); //load class database in the rare case this bundle has no type info if (!inst.file.typeTree.hasTypeTree) am.LoadClassDatabaseFromPackage(inst.file.typeTree.unityVersion); var inf = inst.table.GetAssetInfo("MyBoringAsset"); var baseField = am.GetTypeInstance(inst, inf).GetBaseField(); baseField.Get("m_Name").GetValue().Set("MyCoolAsset"); var newGoBytes = baseField.WriteToByteArray(); var repl = new AssetsReplacerFromMemory(0, inf.index, (int)inf.curFileType, 0xffff, newGoBytes); //write changes to memory byte[] newAssetData; using (var stream = new MemoryStream()) using (var writer = new AssetsFileWriter(stream)) { inst.file.Write(writer, 0, new List() { repl }, 0); newAssetData = stream.ToArray(); } //rename this asset name from boring to cool when saving var bunRepl = new BundleReplacerFromMemory("boring", "cool", true, newAssetData, -1); var bunWriter = new AssetsFileWriter(File.OpenWrite("coolbundle.unity3d")); bunInst.file.Write(bunWriter, new List() { bunRepl }); Compress a bundle file

You can also compress a bundle with LZMA or LZ4.

var am = new AssetsManager(); var bun = am.LoadBundleFile("uncompressedbundle.unity3d"); using (var stream = File.OpenWrite("compressedbundle.unity3d")) using (var writer = new AssetsFileWriter(stream)) { bun.file.Pack(bun.file.reader, writer, AssetBundleCompressionType.LZMA); }

The packers are using managed implementations so they may be slow (especially LZMA, whose c# implementation hasn't been updated in years). You may want to find a way to use native libraries instead.

Bundle creator (create assets/bundle files) (todo)

You can create a new assets file or bundle file with BundleCreator.

Creating assets file var am = new AssetsManager(); var ms = new MemoryStream(); var engineVer = "2019.4.18f1"; var formatVer = 0x15; var typeTreeVer = 0x13; BundleCreator.CreateBlankAssets(ms, engineVer, formatVer, typeTreeVer); ms.Position = 0; var inst = am.LoadAssetsFile(ms, "fakefilepath.assets", false); //...

To figure out the unity version string and format number for your game, open an assets file or bundle in AssetsView and open the Info->View Current Assets File Info menu item. You can find this programmatically with typeTreeVer = AssetsFile.typeTree.version and formatVer = AssetsFile.header.format.

Creating bundle file

The process for bundles is pretty much the same, just use BundleCreator.CreateBundleFile. Note that since the type tree is empty, you will have to add types from the class database to the bundle.

... todo Reading a MonoBehaviour

If you are reading a bundle file, most likely you can use the normal methods to read MonoBehaviours and skip this section. However, if you are reading an assets file or bundle with no type info, AssetsTools.NET needs to use the game's assemblies to deserialize MonoBehaviours. Only managed mono assemblies are read, so if your game uses il2cpp, you will need to dump the game's assemblies with il2cppdumper.

var managedFolder = Path.Combine(Path.GetDirectoryName(fileInst.path), "Managed"); var monoBf = MonoDeserializer.GetMonoBaseField(am, fileInst, monoInf, managedFolder);

If you are using a bundle with type info, GetTypeInstance() or GetExtAsset() will work fine without this.

Writing a MonoBehaviour

If you are adding a MonoBehaviour to a replacer, you'll need to give the replacer a mono id. Each unique script gets a unique mono id per file. For example, all MonoBehaviours using Script1.cs will be mono id 0 and all MonoBehaviours using Script2.cs will be mono id 1. To figure out which mono id your asset is, use AssetHelper.GetScriptIndex().

var repl = new AssetsReplacerFromMemory( 0, monoBehaviourInf.index, (int)monoInf.curFileType, AssetHelper.GetScriptIndex(inst.file, monoInf), newMonoBytes ); Full MonoBehaviour writing example


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有