diff --git a/Unity Studio/BundleFile.cs b/Unity Studio/BundleFile.cs index a91fdd2..4d89eed 100644 --- a/Unity Studio/BundleFile.cs +++ b/Unity Studio/BundleFile.cs @@ -8,10 +8,9 @@ using Lz4; namespace Unity_Studio { - public class BundleFile : IDisposable + public class BundleFile { - private EndianStream Stream; - public byte ver1; + public int ver1; public string ver2; public string ver3; public List MemoryAssetsFileList = new List(); @@ -52,84 +51,101 @@ namespace Unity_Studio } } - Stream = new EndianStream(new MemoryStream(filebuffer), EndianType.BigEndian); + using (var b_Stream = new EndianStream(new MemoryStream(filebuffer), EndianType.BigEndian)) + { + readBundle(b_Stream); + } } - else { Stream = new EndianStream(File.OpenRead(fileName), EndianType.BigEndian); } - - long magicHeader = Stream.ReadInt64(); - - if (magicHeader == -361700864190383366 || magicHeader == 6155973689634940258 || magicHeader == 6155973689634611575) + else { - int dummy = Stream.ReadInt32(); - ver1 = Stream.ReadByte(); - ver2 = Stream.ReadStringToNull(); - ver3 = Stream.ReadStringToNull(); + using (var b_Stream = new EndianStream(File.OpenRead(fileName), EndianType.BigEndian)) + { + readBundle(b_Stream); + } + } + } + + private void readBundle(EndianStream b_Stream) + { + var header = b_Stream.ReadStringToNull(); + + if (header == "UnityWeb" || header == "UnityRaw" || header == "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA") + { + ver1 = b_Stream.ReadInt32(); + ver2 = b_Stream.ReadStringToNull(); + ver3 = b_Stream.ReadStringToNull(); + if (ver1 < 6) { int bundleSize = b_Stream.ReadInt32(); } + else + { + long bundleSize = b_Stream.ReadInt64(); + return; + } + short dummy2 = b_Stream.ReadInt16(); + int offset = b_Stream.ReadInt16(); + int dummy3 = b_Stream.ReadInt32(); + int lzmaChunks = b_Stream.ReadInt32(); + int lzmaSize = 0; - int fileSize = Stream.ReadInt32(); - short dummy2 = Stream.ReadInt16(); - int offset = Stream.ReadInt16(); - int dummy3 = Stream.ReadInt32(); - int lzmaChunks = Stream.ReadInt32(); + long streamSize = 0; for (int i = 0; i < lzmaChunks; i++) { - lzmaSize = Stream.ReadInt32(); - fileSize = Stream.ReadInt32(); + lzmaSize = b_Stream.ReadInt32(); + streamSize = b_Stream.ReadInt32(); } - Stream.Position = offset; - switch (magicHeader) + b_Stream.Position = offset; + switch (header) { - case -361700864190383366: //.bytes - case 6155973689634940258: //UnityWeb + case "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA": //.bytes + case "UnityWeb": { byte[] lzmaBuffer = new byte[lzmaSize]; - Stream.Read(lzmaBuffer, 0, lzmaSize); - Stream.Close(); - Stream.Dispose(); + b_Stream.Read(lzmaBuffer, 0, lzmaSize); - Stream = new EndianStream(SevenZip.Compression.LZMA.SevenZipHelper.StreamDecompress(new MemoryStream(lzmaBuffer)), EndianType.BigEndian); - offset = 0; + using (var lzmaStream = new EndianStream(SevenZip.Compression.LZMA.SevenZipHelper.StreamDecompress(new MemoryStream(lzmaBuffer)), EndianType.BigEndian)) + { + getFiles(lzmaStream, 0); + } break; } - case 6155973689634611575: //UnityRaw + case "UnityRaw": { - + getFiles(b_Stream, offset); break; } } - int fileCount = Stream.ReadInt32(); - for (int i = 0; i < fileCount; i++) - { - MemoryAssetsFile memFile = new MemoryAssetsFile(); - memFile.fileName = Stream.ReadStringToNull(); - int fileOffset = Stream.ReadInt32(); - fileOffset += offset; - fileSize = Stream.ReadInt32(); - long nextFile = Stream.Position; - Stream.Position = fileOffset; - byte[] buffer = new byte[fileSize]; - Stream.Read(buffer, 0, fileSize); - memFile.memStream = new MemoryStream(buffer); - MemoryAssetsFileList.Add(memFile); - Stream.Position = nextFile; - } } - - Stream.Close(); + else if (header == "UnityFS") + { + ver1 = b_Stream.ReadInt32(); + ver2 = b_Stream.ReadStringToNull(); + ver3 = b_Stream.ReadStringToNull(); + long bundleSize = b_Stream.ReadInt64(); + } } - ~BundleFile() + private void getFiles(EndianStream f_Stream, int offset) { - Dispose(); - } + int fileCount = f_Stream.ReadInt32(); + for (int i = 0; i < fileCount; i++) + { + MemoryAssetsFile memFile = new MemoryAssetsFile(); + memFile.fileName = f_Stream.ReadStringToNull(); + int fileOffset = f_Stream.ReadInt32(); + fileOffset += offset; + int fileSize = f_Stream.ReadInt32(); + long nextFile = f_Stream.Position; + f_Stream.Position = fileOffset; - public void Dispose() - { - Stream.Dispose(); - GC.SuppressFinalize(this); + byte[] buffer = new byte[fileSize]; + f_Stream.Read(buffer, 0, fileSize); + memFile.memStream = new MemoryStream(buffer); + MemoryAssetsFileList.Add(memFile); + f_Stream.Position = nextFile; + } } } } diff --git a/Unity Studio/Unity Classes/Mesh.cs b/Unity Studio/Unity Classes/Mesh.cs index be11842..3d20210 100644 --- a/Unity Studio/Unity Classes/Mesh.cs +++ b/Unity Studio/Unity Classes/Mesh.cs @@ -583,6 +583,8 @@ namespace Unity_Studio BitArray m_CurrentChannels = new BitArray(new int[1] { a_Stream.ReadInt32() }); m_VertexCount = a_Stream.ReadInt32(); + //int singleStreamStride = 0;//used tor unity 5 + int streamCount = 0; #region streams for 3.5.0 - 3.5.7 if (version[0] < 4) @@ -608,9 +610,6 @@ namespace Unity_Studio #region channels and streams for 4.0.0 and later else { - //int singleStreamStride = 0;//used tor unity 5 - int streamCount = 0; - m_Channels = new ChannelInfo[a_Stream.ReadInt32()]; for (int c = 0; c < m_Channels.Length; c++) { @@ -639,28 +638,49 @@ namespace Unity_Studio m_Streams[s].frequency = a_Stream.ReadUInt16(); } } - else //create streams - { - m_Streams = new StreamInfo[streamCount]; - for (int s = 0; s < streamCount; s++) - { - m_Streams[s] = new StreamInfo(); - m_Streams[s].channelMask = new BitArray(new int[1] { 0 }); - m_Streams[s].offset = 0; - if (s > 0) { m_Streams[s].offset = m_Streams[s - 1].offset + m_Streams[s - 1].stride * m_VertexCount; } - m_Streams[s].stride = 0; - foreach (var m_Channel in m_Channels) - { - if (m_Channel.stream == s) { m_Streams[s].stride += m_Channel.dimension * (4 / (int)Math.Pow(2, m_Channel.format)); } - } - } - } } #endregion //actual Vertex Buffer byte[] m_DataSize = new byte[a_Stream.ReadInt32()]; a_Stream.Read(m_DataSize, 0, m_DataSize.Length); + + if (version[0] >= 5) //create streams + { + m_Streams = new StreamInfo[streamCount]; + for (int s = 0; s < streamCount; s++) + { + m_Streams[s] = new StreamInfo(); + m_Streams[s].channelMask = new BitArray(new int[1] { 0 }); + m_Streams[s].offset = 0; + m_Streams[s].stride = 0; + + foreach (var m_Channel in m_Channels) + { + if (m_Channel.stream == s) { m_Streams[s].stride += m_Channel.dimension * (4 / (int)Math.Pow(2, m_Channel.format)); } + } + + if (s > 0) + { + m_Streams[s].offset = m_Streams[s - 1].offset + m_Streams[s - 1].stride * m_VertexCount; + //sometimes there are 8 bytes between streams + //this is NOT an alignment, even if sometimes it may seem so + + if (streamCount == 2) { m_Streams[s].offset = m_DataSize.Length - m_Streams[s].stride * m_VertexCount; } + else + { + m_VertexCount = 0; + return; + } + + /*var absoluteOffset = a_Stream.Position + 4 + m_Streams[s].offset; + if ((absoluteOffset % m_Streams[s].stride) != 0) + { + m_Streams[s].offset += m_Streams[s].stride - (int)(absoluteOffset % m_Streams[s].stride); + }*/ + } + } + } #endregion #region compute FvF diff --git a/Unity Studio/UnityStudioForm.cs b/Unity Studio/UnityStudioForm.cs index 92e94cc..a269a24 100644 --- a/Unity Studio/UnityStudioForm.cs +++ b/Unity Studio/UnityStudioForm.cs @@ -18,6 +18,7 @@ using System.Web.Script.Serialization; Load parent nodes even if they are not selected to provide transformations? For extracting bundles, first check if file exists then decompress Double-check channelgroup argument in new FMOD Studio API system.playSound method +Font index error in Dreamfall Chapters */ namespace Unity_Studio @@ -33,7 +34,8 @@ namespace Unity_Studio //private AssetsFile mainDataFile = null; private string mainPath = ""; private string productName = ""; - + private string[] fileTypes = new string[7] { "maindata.", "level*.", "*.assets", "*.sharedAssets", "CustomAssetBundle-*", "CAB-*", "BuildPlayer-*" }; + Dictionary> jsonMats; Dictionary> AllClassStructures = new Dictionary>(); @@ -133,7 +135,6 @@ namespace Unity_Studio //TODO find a way to read data directly instead of merging files MergeSplitAssets(mainPath); - string[] fileTypes = new string[7] { "maindata.", "level*.", "*.assets", "*.sharedAssets", "CustomAssetBundle-*", "CAB-*", "BuildPlayer-*" }; for (int t = 0; t < fileTypes.Length; t++) { string[] fileNames = Directory.GetFiles(mainPath, fileTypes[t], SearchOption.AllDirectories); @@ -284,56 +285,60 @@ namespace Unity_Studio { StatusStripUpdate("Decompressing " + Path.GetFileName(bundleFileName) + "..."); - using (BundleFile b_File = new BundleFile(bundleFileName)) + BundleFile b_File = new BundleFile(bundleFileName); + + List b_assetsfileList = new List(); + + foreach (var memFile in b_File.MemoryAssetsFileList) //filter unity files { - List b_assetsfileList = new List(); - - foreach (var memFile in b_File.MemoryAssetsFileList) //filter unity files + bool validAssetsFile = false; + switch (Path.GetExtension(memFile.fileName)) { - bool validAssetsFile = false; - switch (Path.GetExtension(memFile.fileName)) - { - case ".assets": - case ".sharedAssets": - validAssetsFile = true; - break; - case "": - if (memFile.fileName == "mainData" || Regex.IsMatch(memFile.fileName, "level.*?") || Regex.IsMatch(memFile.fileName, "CustomAssetBundle-.*?") || Regex.IsMatch(memFile.fileName, "CAB-.*?") || Regex.IsMatch(memFile.fileName, "BuildPlayer-.*?")) { validAssetsFile = true; } - break; - } - - if (validAssetsFile) - { - StatusStripUpdate("Loading " + memFile.fileName); - memFile.fileName = Path.GetDirectoryName(bundleFileName) + "\\" + memFile.fileName; //add path for extract location - - AssetsFile assetsFile = new AssetsFile(memFile.fileName, new EndianStream(memFile.memStream, EndianType.BigEndian)); - if (assetsFile.fileGen == 6 && Path.GetFileName(bundleFileName) != "mainData") //2.6.x and earlier don't have a string version before the preload table - { - //make use of the bundle file version - assetsFile.m_Version = b_File.ver3; - assetsFile.version = Array.ConvertAll((b_File.ver3.Split(new string[] { ".", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "\n" }, StringSplitOptions.RemoveEmptyEntries)), int.Parse); - assetsFile.buildType = b_File.ver3.Split(new string[] { ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }, StringSplitOptions.RemoveEmptyEntries); - } - - b_assetsfileList.Add(assetsFile); - } - else - { - memFile.memStream.Close(); - } + case ".assets": + case ".sharedAssets": + validAssetsFile = true; + break; + case "": + validAssetsFile = (memFile.fileName == "mainData" || + Regex.IsMatch(memFile.fileName, "level.*?") || + Regex.IsMatch(memFile.fileName, "CustomAssetBundle-.*?") || + Regex.IsMatch(memFile.fileName, "CAB-.*?") || + Regex.IsMatch(memFile.fileName, "BuildPlayer-.*?")); + break; } - assetsfileList.AddRange(b_assetsfileList);//will the streams still be available for reading data? - - foreach (var assetsFile in b_assetsfileList) + if (validAssetsFile) { - foreach (var sharedFile in assetsFile.sharedAssetsList) + StatusStripUpdate("Loading " + memFile.fileName); + //create dummy path to be used for asset extraction + memFile.fileName = Path.GetDirectoryName(bundleFileName) + "\\" + memFile.fileName; + + AssetsFile assetsFile = new AssetsFile(memFile.fileName, new EndianStream(memFile.memStream, EndianType.BigEndian)); + if (assetsFile.fileGen == 6 && Path.GetFileName(bundleFileName) != "mainData") //2.6.x and earlier don't have a string version before the preload table { - sharedFile.fileName = Path.GetDirectoryName(bundleFileName) + "\\" + sharedFile.fileName; - var loadedSharedFile = b_assetsfileList.Find(aFile => aFile.filePath == sharedFile.fileName); - if (loadedSharedFile != null) { sharedFile.Index = assetsfileList.IndexOf(loadedSharedFile); } + //make use of the bundle file version + assetsFile.m_Version = b_File.ver3; + assetsFile.version = Array.ConvertAll((b_File.ver3.Split(new string[] { ".", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "\n" }, StringSplitOptions.RemoveEmptyEntries)), int.Parse); + assetsFile.buildType = b_File.ver3.Split(new string[] { ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }, StringSplitOptions.RemoveEmptyEntries); } + + b_assetsfileList.Add(assetsFile); + } + else + { + memFile.memStream.Close(); + } + } + + assetsfileList.AddRange(b_assetsfileList);//will the streams still be available for reading data? + + foreach (var assetsFile in b_assetsfileList) + { + foreach (var sharedFile in assetsFile.sharedAssetsList) + { + sharedFile.fileName = Path.GetDirectoryName(bundleFileName) + "\\" + sharedFile.fileName; + var loadedSharedFile = b_assetsfileList.Find(aFile => aFile.filePath == sharedFile.fileName); + if (loadedSharedFile != null) { sharedFile.Index = assetsfileList.IndexOf(loadedSharedFile); } } } } @@ -395,29 +400,29 @@ namespace Unity_Studio string extractPath = bundleFileName + "_unpacked\\"; Directory.CreateDirectory(extractPath); - using (BundleFile b_File = new BundleFile(bundleFileName)) + BundleFile b_File = new BundleFile(bundleFileName); + + foreach (var memFile in b_File.MemoryAssetsFileList) { - foreach (var memFile in b_File.MemoryAssetsFileList) + string filePath = extractPath + memFile.fileName.Replace('/', '\\'); + if (!Directory.Exists(Path.GetDirectoryName(filePath))) { - string filePath = extractPath + memFile.fileName.Replace('/','\\'); - if (!Directory.Exists(Path.GetDirectoryName(filePath))) - { - Directory.CreateDirectory(Path.GetDirectoryName(filePath)); + Directory.CreateDirectory(Path.GetDirectoryName(filePath)); - } - if (File.Exists(filePath)) - { - StatusStripUpdate("File " + memFile.fileName + " already exists"); - } - else - { - StatusStripUpdate("Extracting " + Path.GetFileName(memFile.fileName)); - extractedCount += 1; + } + if (File.Exists(filePath)) + { + StatusStripUpdate("File " + memFile.fileName + " already exists"); + } + else + { + StatusStripUpdate("Extracting " + Path.GetFileName(memFile.fileName)); + extractedCount += 1; - using (FileStream file = new FileStream(filePath, FileMode.Create, System.IO.FileAccess.Write)) - { - memFile.memStream.WriteTo(file); - } + using (FileStream file = new FileStream(filePath, FileMode.Create, System.IO.FileAccess.Write)) + { + memFile.memStream.WriteTo(file); + memFile.memStream.Close(); } } } @@ -1039,6 +1044,7 @@ namespace Unity_Studio { if (firstSortColumn != e.Column) { + //sorting column has been changed reverseSort = false; secondSortColumn = firstSortColumn; } @@ -1074,6 +1080,7 @@ namespace Unity_Studio }); break; } + assetListView.EndUpdate(); resizeAssetListColumns(); @@ -1859,6 +1866,11 @@ namespace Unity_Studio mb.AppendFormat("\n\t\t\tP: \"ShininessExponent\", \"Number\", \"\", \"A\",{0}", m_Float.second); mb.AppendFormat("\n\t\t\tP: \"Shininess\", \"Number\", \"\", \"A\",{0}", m_Float.second); break; + case "_Transparency": + mb.Append("\n\t\t\tP: \"TransparentColor\", \"Color\", \"\", \"A\",1,1,1"); + mb.AppendFormat("\n\t\t\tP: \"TransparencyFactor\", \"Number\", \"\", \"A\",{0}", m_Float.second); + mb.AppendFormat("\n\t\t\tP: \"Opacity\", \"Number\", \"\", \"A\",{0}", (1 - m_Float.second)); + break; default: mb.AppendFormat("\n;\t\t\tP: \"{0}\", \"Number\", \"\", \"A\",{1}", m_Float.first, m_Float.second); break; @@ -1914,10 +1926,12 @@ namespace Unity_Studio cb2.Append("SpecularColor\""); break; case "_NormalMap": - case "_BumpMap": case "gNormalSampler": cb2.Append("NormalMap\""); break; + case "_BumpMap": + cb2.Append("Bump\""); + break; default: cb2.AppendFormat("{0}\"", m_TexEnv.name); break; @@ -2143,6 +2157,10 @@ namespace Unity_Studio AssetPreloadData MeshPD; if (assetsfileList.TryGetGameObject(m_SkinnedMeshRenderer.m_GameObject, out m_GameObject) && assetsfileList.TryGetPD(m_SkinnedMeshRenderer.m_Mesh, out MeshPD)) { + //generate unique Geometry ID for instanced mesh objects + //I should find a way to preserve instances at least when exportDeformers is not selected + var keepID = MeshPD.uniqueID; + MeshPD.uniqueID = SkinnedMeshPD.uniqueID; Mesh m_Mesh = new Mesh(MeshPD); MeshFBX(m_Mesh, MeshPD.uniqueID, ob); @@ -2282,6 +2300,8 @@ namespace Unity_Studio bool stop = true; } } + + MeshPD.uniqueID = keepID; } }