diff --git a/AssetTools.NET/Standard/AssetTypeClass/AssetTypeInstance.cs b/AssetTools.NET/Standard/AssetTypeClass/AssetTypeInstance.cs index 6bbe5a8..f79ebe2 100644 --- a/AssetTools.NET/Standard/AssetTypeClass/AssetTypeInstance.cs +++ b/AssetTools.NET/Standard/AssetTypeClass/AssetTypeInstance.cs @@ -4,7 +4,7 @@ public class AssetTypeInstance { public int baseFieldCount; public AssetTypeValueField[] baseFields; - public byte[] memoryToClear; + public AssetTypeInstance(AssetTypeTemplateField[] baseFields, AssetsFileReader reader, long filePos) { reader.bigEndian = false; @@ -18,8 +18,11 @@ public AssetTypeInstance(AssetTypeTemplateField[] baseFields, AssetsFileReader r this.baseFields[i] = atvf; } } + public AssetTypeInstance(AssetTypeTemplateField baseField, AssetsFileReader reader, long filePos) - : this(new[] { baseField }, reader, filePos) { } + : this(new[] { baseField }, reader, filePos) + { + } public static AssetTypeValueField GetDummyAssetTypeField() { diff --git a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleEntry.cs b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleEntry.cs index 4a2fe75..1d61ccf 100644 --- a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleEntry.cs +++ b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleEntry.cs @@ -5,5 +5,19 @@ public class AssetsBundleEntry public uint offset; public uint length; public string name; + + public void Read(AssetsFileReader reader) + { + name = reader.ReadNullTerminated(); + offset = reader.ReadUInt32(); + length = reader.ReadUInt32(); + } + + public void Write(AssetsFileWriter writer) + { + writer.WriteNullTerminated(name); + writer.Write(offset); + writer.Write(length); + } } } diff --git a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleFile.cs b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleFile.cs index aed6dff..f7159af 100644 --- a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleFile.cs +++ b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleFile.cs @@ -30,17 +30,33 @@ public bool Read(AssetsFileReader reader, bool allowCompressed = false) this.reader = reader; reader.ReadNullTerminated(); uint version = reader.ReadUInt32(); - if (version == 6 || version == 7) + switch (version) { - reader.Position = 0; - bundleHeader6 = new AssetBundleHeader06(); - bundleHeader6.Read(reader); - if (bundleHeader6.fileVersion >= 7) - { - reader.Align16(); - } - if (bundleHeader6.signature == "UnityFS") - { + case 3: + reader.Position = 0; + bundleHeader3 = new AssetBundleHeader03(); + bundleHeader3.Read(reader); + if (bundleHeader3.signature != "UnityRaw") + throw new NotImplementedException("Non UnityRaw bundles are not supported yet."); + + if (bundleHeader3.blockList.Any(b => b.compressed != b.uncompressed)) + throw new NotImplementedException("Compressed UnityRaw bundles are not supported yet."); + + assetsLists3 = new AssetsList(); + assetsLists3.Read(reader); + return true; + + case 6: + case 7: + reader.Position = 0; + bundleHeader6 = new AssetBundleHeader06(); + bundleHeader6.Read(reader); + if (bundleHeader6.fileVersion >= 7) + reader.Align16(); + + if (bundleHeader6.signature != "UnityFS") + throw new NotImplementedException("Non UnityFS bundles are not supported yet."); + bundleInf6 = new AssetBundleBlockAndDirectoryList06(); if ((bundleHeader6.flags & 0x3F) != 0) { @@ -59,23 +75,103 @@ public bool Read(AssetsFileReader reader, bool allowCompressed = false) bundleInf6.Read(bundleHeader6.GetBundleInfoOffset(), reader); return true; } - } - else + + default: + throw new Exception("AssetsBundleFile.Read : Unknown file version!"); + } + } + + public bool Write(AssetsFileWriter writer, List replacers, ClassDatabaseFile typeMeta = null) + { + if (bundleHeader3 != null) + return Write03(writer, replacers); + + if (bundleHeader6 != null) + return Write06(writer, replacers); + + return false; + } + + private bool Write03(AssetsFileWriter writer, List replacers) + { + AssetBundleHeader03 newBundleHeader3 = bundleHeader3.Clone(); + newBundleHeader3.blockList = new[] { new AssetsBundleOffsetPair() }; + newBundleHeader3.Write(writer); + + newBundleHeader3.bundleDataOffs = (uint)writer.Position; + + Dictionary addingReplacers = + replacers.Where(r => r.GetReplacementType() == BundleReplacementType.AddOrModify) + .ToDictionary(r => r.GetOriginalEntryName()); + + List newEntries = new List(); + Dictionary newEntryToOldEntry = new Dictionary(); + Dictionary newEntryToReplacer = new Dictionary(); + foreach (AssetsBundleEntry entry in assetsLists3.entries) + { + addingReplacers.Remove(entry.name); + + BundleReplacer replacer = replacers.FirstOrDefault(r => r.GetOriginalEntryName() == entry.name); + if (replacer == null || + replacer.GetReplacementType() == BundleReplacementType.AddOrModify || + replacer.GetReplacementType() == BundleReplacementType.Rename) { - new NotImplementedException("Non UnityFS bundles are not supported yet."); + AssetsBundleEntry newEntry = new AssetsBundleEntry { name = replacer?.GetEntryName() ?? entry.name }; + newEntries.Add(newEntry); + newEntryToOldEntry.Add(newEntry, entry); + if (replacer != null && replacer.GetReplacementType() == BundleReplacementType.AddOrModify) + newEntryToReplacer.Add(newEntry, replacer); } } - else if (version == 3) + + foreach (BundleReplacer replacer in addingReplacers.Values) { - new NotImplementedException("Version 3 bundles are not supported yet."); + AssetsBundleEntry newEntry = new AssetsBundleEntry { name = replacer.GetEntryName() }; + newEntries.Add(newEntry); + newEntryToReplacer.Add(newEntry, replacer); } - else + + bundleHeader3.numberOfAssetsToDownload = (uint)newEntries.Count; + + AssetsList newAssetsList = new AssetsList { entries = newEntries.ToArray() }; + newAssetsList.Write(writer); + writer.Align16(); + + bundleHeader3.assetsListSize = (uint)writer.Position - bundleHeader3.bundleDataOffs; + + foreach (AssetsBundleEntry newEntry in newEntries) { - new Exception("AssetsBundleFile.Read : Unknown file version!"); + long newEntryPosition = writer.Position; + newEntry.offset = (uint)(writer.Position - bundleHeader3.bundleDataOffs); + + if (newEntryToReplacer.TryGetValue(newEntry, out BundleReplacer replacer)) + { + replacer.Write(writer); + } + else + { + AssetsBundleEntry oldEntry = newEntryToOldEntry[newEntry]; + reader.Position = bundleHeader3.bundleDataOffs + oldEntry.offset; + reader.BaseStream.CopyToCompat(writer.BaseStream, oldEntry.length); + } + + newEntry.length = (uint)(writer.Position - newEntryPosition); } - return false; + + newBundleHeader3.minimumStreamedBytes = (uint)writer.Position; + newBundleHeader3.blockList[0].compressed = (uint)writer.Position - newBundleHeader3.bundleDataOffs; + newBundleHeader3.blockList[0].uncompressed = (uint)writer.Position - newBundleHeader3.bundleDataOffs; + newBundleHeader3.fileSize2 = (uint)writer.Position; + + writer.Position = 0; + newBundleHeader3.Write(writer); + newAssetsList.Write(writer); + + writer.Position = newBundleHeader3.fileSize2; + return true; } - public bool Write(AssetsFileWriter writer, List replacers, ClassDatabaseFile typeMeta = null) + + private bool Write06(AssetsFileWriter writer, List replacers) { bundleHeader6.Write(writer); @@ -84,7 +180,7 @@ public bool Write(AssetsFileWriter writer, List replacers, Class writer.Align16(); } - AssetBundleBlockAndDirectoryList06 newBundleInf6 = new AssetBundleBlockAndDirectoryList06() + AssetBundleBlockAndDirectoryList06 newBundleInf6 = new AssetBundleBlockAndDirectoryList06 { checksumLow = 0, checksumHigh = 0 @@ -92,7 +188,7 @@ public bool Write(AssetsFileWriter writer, List replacers, Class //I could map the assets to their blocks but I don't //have any more-than-1-block files to test on //this should work just fine as far as I know - newBundleInf6.blockInf = new AssetBundleBlockInfo06[] + newBundleInf6.blockInf = new[] { new AssetBundleBlockInfo06 { @@ -108,19 +204,14 @@ public bool Write(AssetsFileWriter writer, List replacers, Class List originalDirInfos = new List(); List dirInfos = new List(); List currentReplacers = replacers.ToList(); - //this is kind of useless at the moment but leaving it here - //because if the AssetsFile size can be precalculated in the - //future, we can use this to skip rewriting sizes - long currentOffset = 0; //write all original files, modify sizes if needed and skip those to be removed for (int i = 0; i < bundleInf6.directoryCount; i++) { AssetBundleDirectoryInfo06 info = bundleInf6.dirInf[i]; originalDirInfos.Add(info); - AssetBundleDirectoryInfo06 newInfo = new AssetBundleDirectoryInfo06() + AssetBundleDirectoryInfo06 newInfo = new AssetBundleDirectoryInfo06 { - offset = currentOffset, decompressedSize = info.decompressedSize, flags = info.flags, name = info.name @@ -131,19 +222,16 @@ public bool Write(AssetsFileWriter writer, List replacers, Class currentReplacers.Remove(replacer); if (replacer.GetReplacementType() == BundleReplacementType.AddOrModify) { - newInfo = new AssetBundleDirectoryInfo06() + newInfo = new AssetBundleDirectoryInfo06 { - offset = currentOffset, - decompressedSize = replacer.GetSize(), flags = info.flags, name = replacer.GetEntryName() }; } else if (replacer.GetReplacementType() == BundleReplacementType.Rename) { - newInfo = new AssetBundleDirectoryInfo06() + newInfo = new AssetBundleDirectoryInfo06 { - offset = currentOffset, decompressedSize = info.decompressedSize, flags = info.flags, name = replacer.GetEntryName() @@ -160,11 +248,6 @@ public bool Write(AssetsFileWriter writer, List replacers, Class newToOriginalDirInfoLookup[newInfo] = info; } - if (newInfo.decompressedSize != -1) - { - currentOffset += newInfo.decompressedSize; - } - dirInfos.Add(newInfo); } @@ -176,12 +259,9 @@ public bool Write(AssetsFileWriter writer, List replacers, Class { AssetBundleDirectoryInfo06 info = new AssetBundleDirectoryInfo06() { - offset = currentOffset, - decompressedSize = replacer.GetSize(), flags = (uint)(replacer.HasSerializedData() ? 0x04 : 0x00), name = replacer.GetEntryName() }; - currentOffset += info.decompressedSize; dirInfos.Add(info); } @@ -595,12 +675,24 @@ public int NumFiles public bool IsAssetsFile(int index) { - GetFileRange(index, out long offset, out long length); - return AssetsFile.IsAssetsFile(reader, offset, length); + if (bundleHeader3 != null) + return IsAssetsFile(assetsLists3.entries[index]); + + if (bundleHeader6 != null) + return IsAssetsFile(bundleInf6.dirInf[index]); + + return false; + } + + [Obsolete] + public bool IsAssetsFile(AssetsBundleEntry entry) + { + long offset = bundleHeader3.bundleDataOffs + entry.offset; + return AssetsFile.IsAssetsFile(reader, offset, entry.length); } [Obsolete] - public bool IsAssetsFile(AssetsFileReader reader, AssetBundleDirectoryInfo06 entry) + public bool IsAssetsFile(AssetBundleDirectoryInfo06 entry) { long offset = bundleHeader6.GetFileDataOffset() + entry.offset; return AssetsFile.IsAssetsFile(reader, offset, entry.decompressedSize); diff --git a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleHeader03.cs b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleHeader03.cs index 580df5f..d669d9b 100644 --- a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleHeader03.cs +++ b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleHeader03.cs @@ -1,4 +1,6 @@ -namespace AssetsTools.NET +using System.Linq; + +namespace AssetsTools.NET { public class AssetBundleHeader03 { @@ -9,12 +11,79 @@ public class AssetBundleHeader03 public uint minimumStreamedBytes; public uint bundleDataOffs; public uint numberOfAssetsToDownload; - public uint levelCount; - public AssetsBundleOffsetPair[] levelList; + public AssetsBundleOffsetPair[] blockList; public uint fileSize2; - public uint unknown2; + public uint assetsListSize; public byte unknown3; - public uint bundleCount; + public void Read(AssetsFileReader reader) + { + reader.bigEndian = true; + signature = reader.ReadNullTerminated(); + fileVersion = reader.ReadUInt32(); + minPlayerVersion = reader.ReadNullTerminated(); + fileEngineVersion = reader.ReadNullTerminated(); + minimumStreamedBytes = reader.ReadUInt32(); + bundleDataOffs = reader.ReadUInt32(); + numberOfAssetsToDownload = reader.ReadUInt32(); + uint blockCount = reader.ReadUInt32(); + blockList = new AssetsBundleOffsetPair[blockCount]; + for (int i = 0; i < blockCount; i++) + { + blockList[i] = new AssetsBundleOffsetPair(); + blockList[i].Read(reader); + } + + if (fileVersion >= 2) + fileSize2 = reader.ReadUInt32(); + + if (fileVersion >= 3) + assetsListSize = reader.ReadUInt32(); + + unknown3 = reader.ReadByte(); + } + + public void Write(AssetsFileWriter writer) + { + writer.bigEndian = true; + writer.WriteNullTerminated(signature); + writer.Write(fileVersion); + writer.WriteNullTerminated(minPlayerVersion); + writer.WriteNullTerminated(fileEngineVersion); + writer.Write(minimumStreamedBytes); + writer.Write(bundleDataOffs); + writer.Write(numberOfAssetsToDownload); + writer.Write(blockList.Length); + foreach (AssetsBundleOffsetPair block in blockList) + { + block.Write(writer); + } + + if (fileVersion >= 2) + writer.Write(fileSize2); + + if (fileVersion >= 3) + writer.Write(assetsListSize); + + writer.Write(unknown3); + } + + public AssetBundleHeader03 Clone() + { + return new AssetBundleHeader03 + { + signature = signature, + fileVersion = fileVersion, + minPlayerVersion = minPlayerVersion, + fileEngineVersion = fileEngineVersion, + minimumStreamedBytes = minimumStreamedBytes, + bundleDataOffs = bundleDataOffs, + numberOfAssetsToDownload = numberOfAssetsToDownload, + blockList = blockList.Select(b => b.Clone()).ToArray(), + fileSize2 = fileSize2, + assetsListSize = assetsListSize, + unknown3 = unknown3 + }; + } } } diff --git a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleOffsetPair.cs b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleOffsetPair.cs index 558a4a2..0126faf 100644 --- a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleOffsetPair.cs +++ b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleOffsetPair.cs @@ -4,5 +4,26 @@ public class AssetsBundleOffsetPair { public uint compressed; public uint uncompressed; + + public void Read(AssetsFileReader reader) + { + compressed = reader.ReadUInt32(); + uncompressed = reader.ReadUInt32(); + } + + public void Write(AssetsFileWriter writer) + { + writer.Write(compressed); + writer.Write(uncompressed); + } + + public AssetsBundleOffsetPair Clone() + { + return new AssetsBundleOffsetPair + { + compressed = compressed, + uncompressed = uncompressed + }; + } } } diff --git a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsList.cs b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsList.cs index 8315dbf..8f61c53 100644 --- a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsList.cs +++ b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsList.cs @@ -2,9 +2,32 @@ { public class AssetsList { - public uint pos; - public uint count; public AssetsBundleEntry[] entries; - public uint allocatedCount; + + public void Read(AssetsFileReader reader) + { + uint count = reader.ReadUInt32(); + entries = new AssetsBundleEntry[count]; + for (int i = 0; i < count; i++) + { + entries[i] = new AssetsBundleEntry(); + entries[i].Read(reader); + } + } + + public void Write(AssetsFileWriter writer) + { + if (entries == null) + { + writer.Write(0); + return; + } + + writer.Write(entries.Length); + foreach (AssetsBundleEntry entry in entries) + { + entry.Write(writer); + } + } } }