diff --git a/ArcFormats/Unity/ArcUnityFS.cs b/ArcFormats/Unity/ArcUnityFS.cs index ed2e9e64..c64090f4 100644 --- a/ArcFormats/Unity/ArcUnityFS.cs +++ b/ArcFormats/Unity/ArcUnityFS.cs @@ -99,7 +99,7 @@ namespace GameRes.Formats.Unity using (var input = new BinMemoryStream (index_data)) index.Parse (input); var dir = index.LoadObjects(); - return new UnityBundle (file, this, dir, index.Segments); + return new UnityBundle (file, this, dir, index.Segments, index.Bundles); } public override Stream OpenEntry (ArcFile arc, Entry entry) @@ -126,12 +126,37 @@ namespace GameRes.Formats.Unity return base.OpenImage (arc, entry); var uarc = (UnityBundle)arc; var obj = aent.AssetObject; - Stream input = new BundleStream (uarc.File, uarc.Segments); - input = new StreamRegion (input, obj.Offset, obj.Size); + var bundles = new BundleStream (uarc.File, uarc.Segments); + var input = new StreamRegion (bundles, obj.Offset, obj.Size); var reader = new AssetReader (input, entry.Name); reader.SetupReaders (obj.Asset); - var tex = new Texture2D(); - tex.Load (reader); + Texture2D tex = null; + if (obj.Asset.Tree.TypeTrees.ContainsKey (obj.TypeId)) + { + var type = obj.Asset.Tree.TypeTrees[obj.TypeId]; + if (type.Children.Any (t => t.Type == "StreamingInfo")) + { + var fields = obj.Deserialize (reader); + tex = new Texture2D(); + tex.Import (fields); + var info = fields["m_StreamData"] as StreamingInfo; + if (info != null) + { + var bundle = uarc.Bundles.FirstOrDefault (b => VFS.IsPathEqualsToFileName (info.Path, b.Name)); + if (bundle != null) + { + tex.m_DataLength = (int)info.Size; + input = new StreamRegion (bundles, bundle.Offset+info.Offset, info.Size); + reader = new AssetReader (input, entry.Name); + } + } + } + } + if (null == tex) + { + tex = new Texture2D(); + tex.Load (reader); + } return new Texture2DDecoder (tex, reader); } @@ -166,11 +191,13 @@ namespace GameRes.Formats.Unity internal class UnityBundle : ArcFile { public readonly List Segments; + public readonly List Bundles; - public UnityBundle (ArcView arc, ArchiveFormat impl, ICollection dir, List segments) + public UnityBundle (ArcView arc, ArchiveFormat impl, ICollection dir, List segments, List bundles) : base (arc, impl, dir) { Segments = segments; + Bundles = bundles; } } @@ -193,6 +220,7 @@ namespace GameRes.Formats.Unity List m_bundles; public List Segments { get { return m_segments; } } + public List Bundles { get { return m_bundles; } } public AssetDeserializer (ArcView file, long data_offset) { @@ -239,8 +267,10 @@ namespace GameRes.Formats.Unity { foreach (BundleEntry bundle in m_bundles) { - if (bundle.Name.EndsWith (".resource")) + if (bundle.Name.HasAnyOfExtensions (".resource", ".resS")) continue; + var res_s_name = bundle.Name + ".resS"; + var res_s = m_bundles.FirstOrDefault (b => b.Name == res_s_name); using (var asset_stream = new StreamRegion (stream, bundle.Offset, bundle.Size, true)) using (var reader = new AssetReader (asset_stream, bundle.Name)) { diff --git a/ArcFormats/Unity/Asset.cs b/ArcFormats/Unity/Asset.cs index 9d4d60a8..21c9ddf0 100644 --- a/ArcFormats/Unity/Asset.cs +++ b/ArcFormats/Unity/Asset.cs @@ -28,9 +28,11 @@ // using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; using GameRes.Utility; @@ -209,6 +211,71 @@ namespace GameRes.Formats.Unity { return string.Format ("<{0} {1}>", Type, ClassId); } + + public IDictionary Deserialize (AssetReader input) + { + var type_tree = Asset.Tree.TypeTrees; + if (!type_tree.ContainsKey (TypeId)) + return null; + var type_map = new Hashtable(); + var type = type_tree[TypeId]; + foreach (var node in type.Children) + { + type_map[node.Name] = DeserializeType (input, node); + } + return type_map; + } + + object DeserializeType (AssetReader input, TypeTree node) + { + object obj = null; + if (node.IsArray) + { + int size = input.ReadInt32(); + var data_field = node.Children.FirstOrDefault (n => n.Name == "data"); + if (data_field != null) + { + if ("TypelessData" == node.Type) + obj = input.ReadBytes (size * data_field.Size); + else + obj = DeserializeArray (input, size, data_field); + } + } + else if (node.Size < 0) + { + if (node.Type == "string") + { + obj = input.ReadString(); + if (node.Children[0].IsAligned) + input.Align(); + } + else if (node.Type == "StreamingInfo") + { + var info = new StreamingInfo(); + info.Load (input); + obj = info; + } + else + throw new NotImplementedException ("Unknown class encountered in asset deserialzation."); + } + else if ("int" == node.Type) + obj = input.ReadInt32(); + else if ("bool" == node.Type) + obj = input.ReadBool(); + else + input.Position += node.Size; + if (node.IsAligned) + input.Align(); + return obj; + } + + object[] DeserializeArray (AssetReader input, int length, TypeTree elem) + { + var array = new object[length]; + for (int i = 0; i < length; ++i) + array[i] = DeserializeType (input, elem); + return array; + } } internal class TypeTree @@ -226,6 +293,8 @@ namespace GameRes.Formats.Unity public IList Children { get { return m_children; } } + public bool IsAligned { get { return (Flags & 0x4000) != 0; } } + static readonly string Null = "(null)"; static readonly Lazy StringsDat = new Lazy (() => LoadResource ("strings.dat")); diff --git a/ArcFormats/Unity/AssetReader.cs b/ArcFormats/Unity/AssetReader.cs index 338c3b00..ffedf5c5 100644 --- a/ArcFormats/Unity/AssetReader.cs +++ b/ArcFormats/Unity/AssetReader.cs @@ -39,6 +39,8 @@ namespace GameRes.Formats.Unity IBinaryStream m_input; int m_format; + const int MaxStringLength = 0x100000; + public Stream Source { get { return m_input.AsStream; } } public int Format { get { return m_format; } } public long Position { @@ -144,6 +146,8 @@ namespace GameRes.Formats.Unity int length = ReadInt32(); if (0 == length) return string.Empty; + if (length < 0 || length > MaxStringLength) + throw new InvalidFormatException(); var bytes = ReadBytes (length); return Encoding.UTF8.GetString (bytes); } diff --git a/ArcFormats/Unity/Texture2D.cs b/ArcFormats/Unity/Texture2D.cs index f938fa19..297c64ea 100644 --- a/ArcFormats/Unity/Texture2D.cs +++ b/ArcFormats/Unity/Texture2D.cs @@ -24,6 +24,7 @@ // using System; +using System.Collections; using System.IO; using System.Windows.Media; using GameRes.Formats.DirectDraw; @@ -100,6 +101,20 @@ namespace GameRes.Formats.Unity { m_Data = reader.ReadBytes (m_DataLength); } + + public void Import (IDictionary fields) + { + m_Name = fields["m_Name"] as string ?? ""; + m_Width = (int)(fields["m_Width"] ?? 0); + m_Height = (int)(fields["m_Height"] ?? 0); + m_CompleteImageSize = (int)(fields["m_CompleteImageSize"] ?? 0); + m_TextureFormat = (TextureFormat)(fields["m_TextureFormat"] ?? 0); + m_MipCount = (int)(fields["m_MipCount"] ?? 0); + m_ImageCount = (int)(fields["m_ImageCount"] ?? 0); + m_TextureDimension = (int)(fields["m_TextureDimension"] ?? 0); + m_IsReadable = (bool)(fields["m_IsReadable"] ?? false); + m_Data = fields["image data"] as byte[] ?? Array.Empty(); + } } internal class Texture2DDecoder : IImageDecoder