public void Write(AssetsFileWriter writer, long filePos, List <AssetsReplacer> replacers, uint fileID = 0, ClassDatabaseFile typeMeta = null) { if (filePos == -1) { filePos = writer.Position; } else { writer.Position = filePos; } header.Write(writer); foreach (AssetsReplacer replacer in replacers) { int replacerClassId = replacer.GetClassID(); ushort replacerScriptIndex = replacer.GetMonoScriptID(); if (!typeTree.unity5Types.Any(t => t.classId == replacerClassId && t.scriptIndex == replacerScriptIndex)) { Type_0D type = null; if (typeMeta != null) { ClassDatabaseType cldbType = AssetHelper.FindAssetClassByID(typeMeta, (uint)replacerClassId); if (cldbType != null) { type = C2T5.Cldb2TypeTree(typeMeta, cldbType); //in original AssetsTools, if you tried to use a new monoId it would just try to use //the highest existing scriptIndex that existed without making a new one (unless there //were no monobehavours ofc) this isn't any better as we just assign a plain monobehaviour //typetree to a type that probably has more fields. I don't really know of a better way to //handle this at the moment as cldbs cannot differentiate monoids. type.scriptIndex = replacerScriptIndex; } } if (type == null) { type = new Type_0D { classId = replacerClassId, unknown16_1 = 0, scriptIndex = replacerScriptIndex, typeHash1 = 0, typeHash2 = 0, typeHash3 = 0, typeHash4 = 0, typeFieldsExCount = 0, stringTableLen = 0, stringTable = "" }; } typeTree.unity5Types.Add(type); } } typeTree.Write(writer, header.format); Dictionary <long, AssetFileInfo> oldAssetInfosByPathId = new Dictionary <long, AssetFileInfo>(); Dictionary <long, AssetsReplacer> replacersByPathId = replacers.ToDictionary(r => r.GetPathID()); List <AssetFileInfo> newAssetInfos = new List <AssetFileInfo>(); // Collect unchanged assets (that aren't getting removed) reader.Position = assetTablePos; for (int i = 0; i < assetCount; i++) { AssetFileInfo oldAssetInfo = new AssetFileInfo(); oldAssetInfo.Read(header.format, reader); oldAssetInfosByPathId.Add(oldAssetInfo.index, oldAssetInfo); if (replacersByPathId.ContainsKey(oldAssetInfo.index)) { continue; } AssetFileInfo newAssetInfo = new AssetFileInfo { index = oldAssetInfo.index, curFileTypeOrIndex = oldAssetInfo.curFileTypeOrIndex, inheritedUnityClass = oldAssetInfo.inheritedUnityClass, scriptIndex = oldAssetInfo.scriptIndex, unknown1 = oldAssetInfo.unknown1 }; newAssetInfos.Add(newAssetInfo); } // Collect modified and new assets foreach (AssetsReplacer replacer in replacers.Where(r => r.GetReplacementType() == AssetsReplacementType.AddOrModify)) { AssetFileInfo newAssetInfo = new AssetFileInfo { index = replacer.GetPathID(), inheritedUnityClass = (ushort)replacer.GetClassID(), //for older unity versions scriptIndex = replacer.GetMonoScriptID(), unknown1 = 0 }; if (header.format < 0x10) { newAssetInfo.curFileTypeOrIndex = replacer.GetClassID(); } else { if (replacer.GetMonoScriptID() == 0xFFFF) { newAssetInfo.curFileTypeOrIndex = typeTree.unity5Types.FindIndex(t => t.classId == replacer.GetClassID()); } else { newAssetInfo.curFileTypeOrIndex = typeTree.unity5Types.FindIndex(t => t.classId == replacer.GetClassID() && t.scriptIndex == replacer.GetMonoScriptID()); } } newAssetInfos.Add(newAssetInfo); } newAssetInfos.Sort((i1, i2) => i1.index.CompareTo(i2.index)); // Write asset infos (will write again later on to update the offsets and sizes) writer.Write(newAssetInfos.Count); writer.Align(); long newAssetTablePos = writer.Position; foreach (AssetFileInfo newAssetInfo in newAssetInfos) { newAssetInfo.Write(header.format, writer); } preloadTable.Write(writer); dependencies.Write(writer); // Temporary fix for secondaryTypeCount and friends if (header.format >= 0x14) { writer.Write(0); //secondaryTypeCount } uint newMetadataSize = (uint)(writer.Position - filePos - 0x13); //0x13 is header - "endianness byte"? (if that's what it even is) if (header.format >= 0x16) { // Remove larger variation fields as well newMetadataSize -= 0x1c; } // For padding only. if all initial data before assetData is more than 0x1000, this is skipped if (writer.Position < 0x1000) { while (writer.Position < 0x1000) { writer.Write((byte)0x00); } } else { if (writer.Position % 16 == 0) { writer.Position += 16; } else { writer.Align16(); } } long newFirstFileOffset = writer.Position; // Write all asset data for (int i = 0; i < newAssetInfos.Count; i++) { AssetFileInfo newAssetInfo = newAssetInfos[i]; newAssetInfo.curFileOffset = writer.Position - newFirstFileOffset; if (replacersByPathId.TryGetValue(newAssetInfo.index, out AssetsReplacer replacer)) { replacer.Write(writer); } else { AssetFileInfo oldAssetInfo = oldAssetInfosByPathId[newAssetInfo.index]; reader.Position = header.firstFileOffset + oldAssetInfo.curFileOffset; reader.BaseStream.CopyToCompat(writer.BaseStream, oldAssetInfo.curFileSize); } newAssetInfo.curFileSize = (uint)(writer.Position - (newFirstFileOffset + newAssetInfo.curFileOffset)); if (i != newAssetInfos.Count - 1) { writer.Align8(); } } long newFileSize = writer.Position - filePos; // Write new header AssetsFileHeader newHeader = new AssetsFileHeader { metadataSize = newMetadataSize, fileSize = newFileSize, format = header.format, firstFileOffset = newFirstFileOffset, endianness = header.endianness, unknown = header.unknown, unknown1 = header.unknown1, unknown2 = header.unknown2 }; writer.Position = filePos; newHeader.Write(writer); // Write new asset infos again (this time with offsets and sizes filled in) writer.Position = newAssetTablePos; foreach (AssetFileInfo newAssetInfo in newAssetInfos) { newAssetInfo.Write(header.format, writer); } // Set writer position back to end of file writer.Position = filePos + newFileSize; }
//set fileID to -1 if all replacers are for this .assets file but don't have the fileID set to the same one //typeMeta is used to add the type information (hash and type fields) for format >= 0x10 if necessary public void Write(AssetsFileWriter writer, ulong filePos, List <AssetsReplacer> replacers, uint fileID, ClassDatabaseFile typeMeta = null) { header.Write(writer); for (int i = 0; i < replacers.Count; i++) { AssetsReplacer replacer = replacers[i]; if (!typeTree.unity5Types.Any(t => t.classId == replacer.GetClassID())) { Type_0D type = new Type_0D() { classId = replacer.GetClassID(), unknown16_1 = 0, scriptIndex = 0xFFFF, typeHash1 = 0, typeHash2 = 0, typeHash3 = 0, typeHash4 = 0, typeFieldsExCount = 0, stringTableLen = 0, stringTable = "" }; typeTree.unity5Types.Concat(new Type_0D[] { type }); } } typeTree.Write(writer, header.format); int initialSize = (int)(AssetFileInfo.GetSize(header.format) * AssetCount); int newSize = (int)(AssetFileInfo.GetSize(header.format) * (AssetCount + replacers.Count)); int appendedSize = newSize - initialSize; reader.Position = AssetTablePos; List <AssetFileInfo> originalAssetInfos = new List <AssetFileInfo>(); List <AssetFileInfo> assetInfos = new List <AssetFileInfo>(); List <AssetsReplacer> currentReplacers = replacers.ToList(); uint currentOffset = 0; //-write all original assets, modify sizes if needed and skip those to be removed for (int i = 0; i < AssetCount; i++) { AssetFileInfo info = new AssetFileInfo(); info.Read(header.format, reader); originalAssetInfos.Add(info); AssetFileInfo newInfo = new AssetFileInfo() { index = info.index, curFileOffset = currentOffset, curFileSize = info.curFileSize, curFileTypeOrIndex = info.curFileTypeOrIndex, inheritedUnityClass = info.inheritedUnityClass, scriptIndex = info.scriptIndex, unknown1 = info.unknown1 }; AssetsReplacer replacer = currentReplacers.FirstOrDefault(n => n.GetPathID() == newInfo.index); if (replacer != null) { currentReplacers.Remove(replacer); if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_AddOrModify) { int classIndex; if (replacer.GetMonoScriptID() == 0xFFFF) { classIndex = typeTree.unity5Types.FindIndex(t => t.classId == replacer.GetClassID()); } else { classIndex = typeTree.unity5Types.FindIndex(t => t.classId == replacer.GetClassID() && t.scriptIndex == replacer.GetMonoScriptID()); } newInfo = new AssetFileInfo() { index = replacer.GetPathID(), curFileOffset = currentOffset, curFileSize = (uint)replacer.GetSize(), curFileTypeOrIndex = classIndex, inheritedUnityClass = (ushort)replacer.GetClassID(), //for older unity versions scriptIndex = replacer.GetMonoScriptID(), unknown1 = 0 }; } else if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_Remove) { continue; } } currentOffset += newInfo.curFileSize; uint pad = 8 - (currentOffset % 8); if (pad != 8) { currentOffset += pad; } assetInfos.Add(newInfo); } //-write new assets while (currentReplacers.Count > 0) { AssetsReplacer replacer = currentReplacers.First(); if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_AddOrModify) { int classIndex; if (replacer.GetMonoScriptID() == 0xFFFF) { classIndex = typeTree.unity5Types.FindIndex(t => t.classId == replacer.GetClassID()); } else { classIndex = typeTree.unity5Types.FindIndex(t => t.classId == replacer.GetClassID() && t.scriptIndex == replacer.GetMonoScriptID()); } AssetFileInfo info = new AssetFileInfo() { index = replacer.GetPathID(), curFileOffset = currentOffset, curFileSize = (uint)replacer.GetSize(), curFileTypeOrIndex = classIndex, inheritedUnityClass = (ushort)replacer.GetClassID(), scriptIndex = replacer.GetMonoScriptID(), unknown1 = 0 }; currentOffset += info.curFileSize; uint pad = 8 - (currentOffset % 8); if (pad != 8) { currentOffset += pad; } assetInfos.Add(info); } currentReplacers.Remove(replacer); } writer.Write(assetInfos.Count); writer.Align(); for (int i = 0; i < assetInfos.Count; i++) { assetInfos[i].Write(header.format, writer); } preloadTable.Write(writer); dependencies.Write(writer); //temporary fix for secondarytypecount and friends if (header.format >= 14) { writer.Write(0); //secondaryTypeCount writer.Write((byte)0); //unknownString length } uint metadataSize = (uint)writer.Position - 0x14; //-for padding only. if all initial data before assetData is more than 0x1000, this is skipped while (writer.Position < 0x1000 /*header.offs_firstFile*/) { writer.Write((byte)0x00); } writer.Align16(); uint offs_firstFile = (uint)writer.Position; for (int i = 0; i < assetInfos.Count; i++) { AssetFileInfo info = assetInfos[i]; AssetsReplacer replacer = replacers.FirstOrDefault(n => n.GetPathID() == info.index); if (replacer != null) { if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_AddOrModify) { replacer.Write(writer); if (i != assetInfos.Count - 1) { writer.Align8(); } } else if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_Remove) { continue; } } else { AssetFileInfo originalInfo = originalAssetInfos.FirstOrDefault(n => n.index == info.index); if (originalInfo != null) { reader.Position = header.firstFileOffset + originalInfo.curFileOffset; byte[] assetData = reader.ReadBytes((int)originalInfo.curFileSize); writer.Write(assetData); if (i != assetInfos.Count - 1) { writer.Align8(); } } } } header.firstFileOffset = offs_firstFile; long fileSizeMarker = writer.Position; reader.Position = header.firstFileOffset; writer.Position = 0; header.metadataSize = metadataSize; header.fileSize = (uint)fileSizeMarker; header.Write(writer); }
//set fileID to -1 if all replacers are for this .assets file but don't have the fileID set to the same one //typeMeta is used to add the type information (hash and type fields) for format >= 0x10 if necessary public ulong Write(AssetsFileWriter writer, ulong filePos, AssetsReplacer[] pReplacers, uint fileID, ClassDatabaseFile typeMeta = null) { header.Write(writer.Position, writer); for (int i = 0; i < pReplacers.Length; i++) { AssetsReplacer replacer = pReplacers[i]; if (!typeTree.pTypes_Unity5.Any(t => t.classId == replacer.GetClassID())) { Type_0D type = new Type_0D() { classId = replacer.GetClassID(), unknown16_1 = 0, scriptIndex = 0xFFFF, unknown5 = 0, unknown6 = 0, unknown7 = 0, unknown8 = 0, typeFieldsExCount = 0, stringTableLen = 0, pStringTable = "" }; typeTree.pTypes_Unity5.Concat(new Type_0D[] { type }); } } typeTree.Write(writer.Position, writer, header.format); int initialSize = (int)(AssetFileInfo.GetSize(header.format) * AssetCount); int newSize = (int)(AssetFileInfo.GetSize(header.format) * (AssetCount + pReplacers.Length)); int appendedSize = newSize - initialSize; reader.Position = AssetTablePos; List <AssetFileInfo> originalAssetInfos = new List <AssetFileInfo>(); List <AssetFileInfo> assetInfos = new List <AssetFileInfo>(); List <AssetsReplacer> currentReplacers = pReplacers.ToList(); uint currentOffset = 0; //-write all original assets, modify sizes if needed and skip those to be removed for (int i = 0; i < AssetCount; i++) { AssetFileInfo info = new AssetFileInfo(); info.Read(header.format, reader.Position, reader, reader.bigEndian); originalAssetInfos.Add(info); AssetsReplacer replacer = currentReplacers.FirstOrDefault(n => n.GetPathID() == info.index); if (replacer != null) { if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_AddOrModify) { int classIndex = Array.FindIndex(typeTree.pTypes_Unity5, t => t.classId == replacer.GetClassID()); info = new AssetFileInfo() { index = replacer.GetPathID(), offs_curFile = currentOffset, curFileSize = (uint)classIndex, curFileTypeOrIndex = (uint)replacer.GetClassID(), inheritedUnityClass = (ushort)replacer.GetClassID(), //-what is this scriptIndex = replacer.GetMonoScriptID(), unknown1 = 0 }; } else if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_Remove) { continue; } } currentOffset += info.curFileSize; uint pad = 8 - (currentOffset % 8); if (pad != 8) { currentOffset += pad; } assetInfos.Add(info); } //-write new assets while (currentReplacers.Count > 0) { AssetsReplacer replacer = currentReplacers.First(); if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_AddOrModify) { int classIndex = Array.FindIndex(typeTree.pTypes_Unity5, t => t.classId == replacer.GetClassID()); AssetFileInfo info = new AssetFileInfo() { index = replacer.GetPathID(), offs_curFile = currentOffset, curFileSize = (uint)replacer.GetSize(), curFileTypeOrIndex = (uint)classIndex, inheritedUnityClass = (ushort)replacer.GetClassID(), scriptIndex = replacer.GetMonoScriptID(), unknown1 = 0 }; currentOffset += info.curFileSize; uint pad = 8 - (currentOffset % 8); if (pad != 8) { currentOffset += pad; } assetInfos.Add(info); } currentReplacers.Remove(replacer); } writer.Write(assetInfos.Count); writer.Align(); for (int i = 0; i < assetInfos.Count; i++) { assetInfos[i].Write(header.format, writer.Position, writer); } preloadTable.Write(writer.Position, writer, header.format); dependencies.Write(writer.Position, writer, header.format); uint metadataSize = (uint)writer.Position - 0x13; //-for padding only. if all initial data before assetData is more than 0x1000, this is skipped while (writer.Position < 0x1000 /*header.offs_firstFile*/) { writer.Write((byte)0x00); } header.offs_firstFile = (uint)writer.Position; for (int i = 0; i < assetInfos.Count; i++) { AssetFileInfo info = assetInfos[i]; AssetsReplacer replacer = pReplacers.FirstOrDefault(n => n.GetPathID() == info.index); if (replacer != null) { if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_AddOrModify) { replacer.Write(writer.Position, writer); writer.Align8(); } else if (replacer.GetReplacementType() == AssetsReplacementType.AssetsReplacement_Remove) { continue; } } else { AssetFileInfo originalInfo = originalAssetInfos.FirstOrDefault(n => n.index == info.index); if (originalInfo != null) { reader.Position = header.offs_firstFile + originalInfo.offs_curFile; byte[] assetData = reader.ReadBytes((int)originalInfo.curFileSize); writer.Write(assetData); writer.Align8(); } } } ulong fileSizeMarker = writer.Position; reader.Position = header.offs_firstFile; writer.Position = 0; header.metadataSize = metadataSize; header.fileSize = (uint)fileSizeMarker; header.Write(writer.Position, writer); return(writer.Position); }