diff --git a/Experimental/Unity/ArcUnityFS.cs b/Experimental/Unity/ArcUnityFS.cs index cb673286..08d9541c 100644 --- a/Experimental/Unity/ArcUnityFS.cs +++ b/Experimental/Unity/ArcUnityFS.cs @@ -105,14 +105,34 @@ namespace GameRes.Formats.Unity public override Stream OpenEntry (ArcFile arc, Entry entry) { var uarc = (UnityBundle)arc; - var input = new BundleStream (uarc.File, uarc.Segments); - return new StreamRegion (input, entry.Offset, entry.Size); + Stream input = new BundleStream (uarc.File, uarc.Segments); + input = new StreamRegion (input, entry.Offset, entry.Size); + var aent = entry as AssetEntry; + if (null == aent || !aent.IsEncrypted) + return input; + using (input) + { + var data = new byte[entry.Size]; + input.Read (data, 0, data.Length); + DecryptAsset (data); + return new BinMemoryStream (data); + } } internal static byte[] UnpackLzma (byte[] input, int unpacked_size) { throw new NotImplementedException(); } + + internal void DecryptAsset (byte[] data) + { + uint key = 0xBF8766F5u; + for (int i = 0; i < data.Length; ++i) + { + key = ((0x343FD * key + 0x269EC3) >> 16) & 0x7FFF; // MSVC rand() + data[i] ^= (byte)key; + } + } } internal class BundleEntry : Entry @@ -124,6 +144,7 @@ namespace GameRes.Formats.Unity { public BundleEntry Bundle; public UnityObject AssetObject; + public bool IsEncrypted; } internal class UnityBundle : ArcFile @@ -209,7 +230,7 @@ namespace GameRes.Formats.Unity { var asset = new Asset(); asset.Load (reader); - var object_dir = ParseAsset (bundle, asset, stream); + var object_dir = ParseAsset (stream, bundle, asset); dir.AddRange (object_dir); } } @@ -219,7 +240,7 @@ namespace GameRes.Formats.Unity return dir; } - IEnumerable ParseAsset (BundleEntry bundle, Asset asset, Stream file) + IEnumerable ParseAsset (Stream file, BundleEntry bundle, Asset asset) { Dictionary id_map = null; var bundle_types = asset.Tree.TypeTrees.Where (t => t.Value.Type == "AssetBundle").Select (t => t.Key); @@ -241,7 +262,11 @@ namespace GameRes.Formats.Unity AssetEntry entry = null; if ("AudioClip" == type) { - entry = ReadAudioClip (file, obj, asset); + entry = ReadAudioClip (file, obj); + } + else if ("TextAsset" == type) + { + entry = ReadTextAsset (file, obj); } if (null == entry) { @@ -253,9 +278,11 @@ namespace GameRes.Formats.Unity Size = obj.Size, }; } + if (null == entry.Bundle) + entry.Bundle = bundle; string name; if (!id_map.TryGetValue (obj.PathId, out name)) - name = obj.PathId.ToString ("X16"); + name = GetObjectName (file, obj); else name = ShortenPath (name); entry.Name = name; @@ -263,12 +290,56 @@ namespace GameRes.Formats.Unity } } - AssetEntry ReadAudioClip (Stream input, UnityObject obj, Asset asset) + string GetObjectName (Stream input, UnityObject obj) { - using (var stream = new StreamRegion (input, obj.Offset, obj.Size, true)) - using (var reader = new AssetReader (stream, "")) + TypeTree type; + if (obj.Asset.Tree.TypeTrees.TryGetValue (obj.TypeId, out type) && type.Children.Count > 0) + { + var first_field = type.Children[0]; + if ("m_Name" == first_field.Name && "string" == first_field.Type) + { + using (var reader = obj.Open (input)) + { + var name = reader.ReadString(); + if (!string.IsNullOrEmpty (name)) + return name; + } + } + } + return obj.PathId.ToString ("X16"); + } + + AssetEntry ReadTextAsset (Stream input, UnityObject obj) + { + var type_def = obj.Asset.Tree.TypeTrees[obj.TypeId]; + var script = type_def.Children.FirstOrDefault (f => f.Name == "m_Script"); + if (null == script) + return null; + using (var reader = obj.Open (input)) + { + var name = reader.ReadString(); + reader.Align(); + uint size = reader.ReadUInt32(); + var entry = new AssetEntry { + AssetObject = obj, + Offset = obj.Offset + reader.Position, + Size = size, + IsEncrypted = 0 != (script.Flags & 0x04000000), + }; + if (entry.IsEncrypted) + { + uint signature = reader.ReadUInt32(); + if (0x0D15F641 == signature) + entry.Type = "image"; + } + return entry; + } + } + + AssetEntry ReadAudioClip (Stream input, UnityObject obj) + { + using (var reader = obj.Open (input)) { - reader.SetupReaders (asset.Format, asset.IsLittleEndian); var clip = new AudioClip(); clip.Load (reader); var bundle_name = Path.GetFileName (clip.m_Source); diff --git a/Experimental/Unity/Asset.cs b/Experimental/Unity/Asset.cs index 3e03f1f7..c7a0a103 100644 --- a/Experimental/Unity/Asset.cs +++ b/Experimental/Unity/Asset.cs @@ -198,6 +198,14 @@ namespace GameRes.Formats.Unity Asset = owner; } + public AssetReader Open (Stream input) + { + var stream = new StreamRegion (input, Offset, Size, true); + var reader = new AssetReader (stream, ""); + reader.SetupReaders (Asset.Format, Asset.IsLittleEndian); + return reader; + } + public void Load (AssetReader reader) { PathId = reader.ReadId(); @@ -451,4 +459,66 @@ namespace GameRes.Formats.Unity m_CompressionFormat = reader.ReadInt32(); } } + + enum TextureFormat : int + { + Alpha8 = 1, + ARGB4444 = 2, + RGB24 = 3, + RGBA32 = 4, + ARGB32 = 5, + R16 = 6, // A 16 bit color texture format that only has a red channel. + RGB565 = 7, + DXT1 = 10, + DXT5 = 12, + RGBA4444 = 13, + BGRA32 = 14, + } + + internal class Texture2D + { + public string m_Name; + public int m_Width; + public int m_Height; + public int m_CompleteImageSize; + public TextureFormat m_TextureFormat; + public int m_MipCount; + public bool m_IsReadable; + public bool m_ReadAllowed; + public int m_ImageCount; + public int m_TextureDimension; + public int m_FilterMode; + public int m_Aniso; + public int m_MipBias; + public int m_WrapMode; + public int m_LightFormat; + public int m_ColorSpace; + // byte[] m_Data + // StreamingInfo m_StreamData + // uint offset + // uint size + // string path + + public void Load (AssetReader reader) + { + m_Name = reader.ReadString(); + reader.Align(); + m_Width = reader.ReadInt32(); + m_Height = reader.ReadInt32(); + m_CompleteImageSize = reader.ReadInt32(); + m_TextureFormat = (TextureFormat)reader.ReadInt32(); + m_MipCount = reader.ReadInt32(); + m_IsReadable = reader.ReadBool(); + m_ReadAllowed = reader.ReadBool(); + reader.Align(); + m_ImageCount = reader.ReadInt32(); + m_TextureDimension = reader.ReadInt32(); + m_FilterMode = reader.ReadInt32(); + m_Aniso = reader.ReadInt32(); + m_MipBias = reader.ReadInt32(); + m_WrapMode = reader.ReadInt32(); + m_LightFormat = reader.ReadInt32(); + m_ColorSpace = reader.ReadInt32(); + } + } }