Skip to content

Struggling to get asset replacement to work #138

@SurDno

Description

@SurDno

Heya! So I am currently trying to get a simple tool that will iterate through mod folders (named the same way as actual assets files they are trying to modify) and for each file inside, will try to replace it in the assets file. I went through the docs but it did not cover asset replacement unfortunately, and most projects that use AssetTools.NET seem to use 2.0 or whatever version still had AssetsReplacer class. I decided to start with audioclips cause with textures I'll have to care about formatting and so many more things and it all seems unnecessarily complicated.

The code below works (hooray!) but it corrupts the assets file in the process and neither the game nor stuff like AssetStudio can open it after that. And I've got no idea what I am doing wrong.

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

namespace P2ModLoader.Helper {
    public static class AssetsFilePatcher {
        public static bool PatchAssetsFile(string assetsFilePath, string modAssetsFolder) {
            AssetsManager? manager = null;
            AssetsFileInstance? assetsFileInstance = null;

            // Create a copy for modification
            var newFile = assetsFilePath + ".temp";
            File.Copy(assetsFilePath, newFile, true);

            try {
                manager = new AssetsManager();
                manager.LoadClassPackage("C:/classdata.tpk");

                using var fs = new FileStream(
                    newFile,
                    FileMode.Open,
                    FileAccess.ReadWrite, 
                    FileShare.None 
                );
                assetsFileInstance = manager.LoadAssetsFile(fs, newFile);
                var assetsFile = assetsFileInstance.file;

                manager.LoadClassDatabaseFromPackage(assetsFile.Metadata.UnityVersion);

                var modAssetFiles = Directory.GetFiles(modAssetsFolder, "*.*", SearchOption.AllDirectories);
                foreach (var modAssetFile in modAssetFiles) {
                    var assetName = Path.GetFileNameWithoutExtension(modAssetFile);
                    var assetData = File.ReadAllBytes(modAssetFile);
                    var extension = Path.GetExtension(modAssetFile).ToLower();

                    switch (extension) {
                        case ".wav" or ".ogg" or ".mp3" or ".aif" or ".aiff": {
                            if (!ReplaceAudioAsset(manager, assetsFileInstance, assetName, assetData)) {
                                Console.WriteLine($"Failed to replace audio asset: {assetName}");
                                return false;
                            }
                            break;
                        }
                        default:
                            Console.WriteLine($"Unsupported asset type for file: {modAssetFile}");
                            break;
                    }
                }

                fs.Position = 0; 
                using (var writer = new AssetsFileWriter(fs)) {
                    assetsFileInstance.file.Write(writer, -1);
                }

                fs.Close();

                // Copy over original.
                File.Copy(newFile, assetsFilePath, true);
                Console.WriteLine($"Successfully patched {assetsFilePath}");
                return true;
            } catch (Exception ex) {
                Console.WriteLine($"Error patching assets file: {ex.Message} {ex.StackTrace}");
                return false;
            } finally {
                File.Delete(newFile);
                manager?.UnloadAll();
            }
        }

        private static bool ReplaceAudioAsset(
            AssetsManager manager,
            AssetsFileInstance assetsFileInstance,
            string assetName,
            byte[] assetData) {
            try {
                var afile = assetsFileInstance.file;
                var audioAssets = afile.GetAssetsOfType((int)AssetClassID.AudioClip);

                foreach (var assetInfo in audioAssets) {
                    var baseField = manager.GetBaseField(assetsFileInstance, assetInfo);
                    var name = baseField["m_Name"].AsString;

                    if (name != assetName) continue;
                    
                    var externalFilePath = assetsFileInstance.path.Replace(".temp", "") + ".resS";
                        
                    // Backup external file.
                    BackupManager.CreateBackup(externalFilePath);

                    long newOffset;
                    using (var stream = new FileStream(
                               externalFilePath,
                               FileMode.Open,
                               FileAccess.ReadWrite,
                               FileShare.None)) {
                        newOffset = stream.Length;
                        stream.Position = newOffset;
                        stream.Write(assetData, 0, assetData.Length);
                    }

                    var resourceField = baseField["m_Resource"];
                    resourceField["m_Offset"].AsULong = (ulong)newOffset;
                    resourceField["m_Size"].AsUInt = (uint)assetData.Length;

                    var replacer = new ContentReplacerFromBuffer(baseField.WriteToByteArray());
                    assetInfo.Replacer = replacer;

                    Console.WriteLine($"Successfully replaced asset {assetName}");
                    return true;
                }

                Console.WriteLine($"Audio asset with name {assetName} not found.");
                return false;
            } catch (Exception ex) {
                Console.WriteLine($"Error replacing audio asset: {ex.Message}");
                return false;
            }
        }
    }
}

I understand issues is probably not the best place to seek help but I've got no diea where to go. If I missed something in the doc or you have a working example with assets file patching I would really appreciate it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionQuestion, not a bug report or feature request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions