Skip to content

Getting Started: Bundle file writing

nesrak1 edited this page Sep 23, 2023 · 7 revisions

Let's say you've written code to modify the assets file in a bundle and want to insert the changes back into a bundle. There are two options: either to use ContentReplacerFromAssets or ContentReplacerFromMemory. If you use the FromAssets version, you can skip writing to a MemoryStream.

Example to add a string to the end of every TextAsset's name (this is pretty worthless but makes for an easy example).

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

void PatchBundle(string filePath)
{
    var manager = new AssetsManager();

    var fileIndex = 0;
    var bunInst = manager.LoadBundleFile(args[0], true);
    var bun = bunInst.file;
    var afileInst = manager.LoadAssetsFileFromBundle(bunInst, fileIndex, false);
    var afile = afileInst.file;

    foreach (var texInfo in afile.GetAssetsOfType(AssetClassID.TextAsset))
    {
        var texBase = manager.GetBaseField(afileInst, texInfo);
        texBase["m_Name"].AsString += " abcdefg";
        texInfo.SetNewData(texBase);
    }

    bun.BlockAndDirInfo.DirectoryInfos[fileIndex].SetNewData(afile);
    // or you can manually set a replacer
    // bun.BlockAndDirInfo.DirectoryInfos[fileIndex].Replacer = new ContentReplacerFromAssets(afile);

    using (AssetsFileWriter writer = new AssetsFileWriter(args[0] + ".mod"))
    {
        bun.Write(writer);
    }
}

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

PatchBundle(args[0]);

Looking at the code closer:

var manager = new AssetsManager();

var fileIndex = 0;
var bunInst = manager.LoadBundleFile(args[0], true);
var bun = bunInst.file;
var afileInst = manager.LoadAssetsFileFromBundle(bunInst, 0, false);
var afile = afileInst.file;

This is the usual setup. We load the first file from the bundle as an assets file.

foreach (var texInfo in afile.GetAssetsOfType(AssetClassID.TextAsset))
{
    var texBase = manager.GetBaseField(afileInst, texInfo);
    texBase["m_Name"].AsString += " abcdefg";
    texInfo.SetNewData(texBase);
}

This is some pretty basic editing code that adds onto a string field.

bun.BlockAndDirInfo.DirectoryInfos[fileIndex].SetNewData(afile);
// or you can manually set a replacer
// bun.BlockAndDirInfo.DirectoryInfos[fileIndex].Replacer = new ContentReplacerFromAssets(afile);

Now we set the data for the bundle's first file. Like assets files, SetNewData creates a new replacer, in this case ContentReplacerFromAssets. You can also manually create a replacer if you want.

using (AssetsFileWriter writer = new AssetsFileWriter(args[0] + ".mod"))
{
    bun.Write(writer);
}

Here we write the bundle with the changes made in the replacers to file. Pretty easy.


If you wanted to write to file first for whatever reason, you can do that as well. Note that ContentReplacerFromAssets replacers write directly to the bundle stream, so if you're concerned about loading the entire new assets file into memory, that replacer type won't do that.

var tmpFileName = args[0] + ".tmp";
using (AssetsFileWriter writer = new AssetsFileWriter(tmpFileName))
{
    afile.Write(writer);
}

// true: close stream on write
bun.BlockAndDirInfo.DirectoryInfos[fileIndex].Replacer = new ContentReplacerFromStream(File.OpenRead(tmpFileName), 0, -1, true);

using (AssetsFileWriter writer = new AssetsFileWriter(args[0] + ".mod"))
{
    bun.Write(writer);
}

Compressing the bundle

When you write a bundle, it will be uncompressed. You can compress the bundle after writing it to disk or memory. If the bundle is large, I suggest writing it to file first, reopening it, then packing it.

var uncompressedName = args[0] + ".mod.uncompressed";
using (AssetsFileWriter writer = new AssetsFileWriter(uncompressedName))
{
    bun.Write(writer);
}

var newUncompressedBundle = new AssetBundleFile();
newUncompressedBundle.Read(new AssetsFileReader(File.OpenRead(uncompressedName)));

using (AssetsFileWriter writer = new AssetsFileWriter(args[0] + ".mod.compressed"))
{
    newUncompressedBundle.Pack(writer, AssetBundleCompressionType.LZ4);
}

newUncompressedBundle.Close();