2023-08-24 01:33:50 +04:00

482 lines
16 KiB
C#

//! \file Asset.cs
//! \date Wed Apr 05 18:58:07 2017
//! \brief Unity asset class.
//
// Based on the [UnityPack](https://github.com/HearthSim/UnityPack)
//
// Copyright (c) Jerome Leclanche
//
// C# port copyright (C) 2017 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using GameRes.Utility;
namespace GameRes.Formats.Unity
{
internal class Asset
{
int m_format;
long m_data_offset;
bool m_is_little_endian;
UnityTypeData m_tree = new UnityTypeData();
Dictionary<long, int> m_adds;
List<AssetRef> m_refs;
Dictionary<int, TypeTree> m_types = new Dictionary<int, TypeTree>();
Dictionary<long, UnityObject> m_objects = new Dictionary<long, UnityObject>();
public int Format { get { return m_format; } }
public bool IsLittleEndian { get { return m_is_little_endian; } }
public long DataOffset { get { return m_data_offset; } }
public UnityTypeData Tree { get { return m_tree; } }
public IEnumerable<UnityObject> Objects { get { return m_objects.Values; } }
public void Load (AssetReader input)
{
input.ReadInt32(); // header_size
input.ReadUInt32(); // file_size
m_format = input.ReadInt32();
m_data_offset = input.ReadUInt32();
if (m_format >= 9)
m_is_little_endian = 0 == input.ReadInt32();
if (m_format >= 22)
{
input.ReadInt32(); // header_size
input.ReadInt64(); // file_size
m_data_offset = input.ReadInt64();
input.ReadInt64();
}
input.SetupReaders (this);
m_tree.Load (input);
bool long_ids = Format >= 14;
if (Format >= 7 && Format < 14)
long_ids = 0 != input.ReadInt32();
input.SetupReadId (long_ids);
int obj_count = input.ReadInt32();
for (int i = 0; i < obj_count; ++i)
{
input.Align();
var obj = new UnityObject (this);
obj.Load (input);
RegisterObject (obj);
}
if (Format >= 11)
{
int count = input.ReadInt32();
m_adds = new Dictionary<long, int> (count);
for (int i = 0; i < count; ++i)
{
input.Align();
var file_id = input.ReadInt32();
var id = input.ReadId();
m_adds[id] = file_id;
}
}
if (Format >= 6)
{
int count = input.ReadInt32();
m_refs = new List<AssetRef> (count);
for (int i = 0; i < count; ++i)
{
var asset_ref = AssetRef.Load (input);
m_refs.Add (asset_ref);
}
}
input.ReadCString();
}
void RegisterObject (UnityObject obj)
{
if (m_tree.TypeTrees.ContainsKey (obj.TypeId))
{
m_types[obj.TypeId] = m_tree.TypeTrees[obj.TypeId];
}
else if (!m_types.ContainsKey (obj.TypeId))
{
/*
var trees = TypeTree.Default (this).TypeTrees;
if (trees.ContainsKey (obj.ClassId))
{
m_types[obj.TypeId] = trees[obj.ClassId];
}
else
*/
{
Trace.WriteLine (string.Format ("Unknown type id {0}", obj.ClassId.ToString()), "[Unity.Asset]");
m_types[obj.TypeId] = null;
}
}
if (m_objects.ContainsKey (obj.PathId))
throw new ApplicationException (string.Format ("Duplicate asset object {0} (PathId: {1})", obj, obj.PathId));
m_objects[obj.PathId] = obj;
}
}
internal class AssetRef
{
public string AssetPath;
public Guid Guid;
public int Type;
public string FilePath;
public object Asset;
public static AssetRef Load (AssetReader reader)
{
var r = new AssetRef();
r.AssetPath = reader.ReadCString();
r.Guid = new Guid (reader.ReadBytes (16));
r.Type = reader.ReadInt32();
r.FilePath = reader.ReadCString();
r.Asset = null;
return r;
}
}
internal class UnityObject
{
public Asset Asset;
public long PathId;
public long Offset;
public uint Size;
public int TypeId;
public int ClassId;
public bool IsDestroyed;
public UnityObject (Asset owner)
{
Asset = owner;
}
public AssetReader Open (Stream input)
{
var stream = new StreamRegion (input, Offset, Size, true);
var reader = new AssetReader (stream, "");
reader.SetupReaders (Asset);
return reader;
}
public void Load (AssetReader reader)
{
PathId = reader.ReadId();
Offset = reader.ReadOffset();
Offset += Asset.DataOffset;
Size = reader.ReadUInt32();
if (Asset.Format < 17)
{
TypeId = reader.ReadInt32();
ClassId = reader.ReadInt16();
}
else
{
var type_id = reader.ReadInt32();
var class_id = Asset.Tree.ClassIds[type_id];
TypeId = class_id;
ClassId = class_id;
}
if (Asset.Format <= 10)
IsDestroyed = reader.ReadInt16() != 0;
if (Asset.Format >= 11 && Asset.Format < 17)
reader.ReadInt16();
if (Asset.Format >= 15 && Asset.Format < 17)
reader.ReadByte();
}
public string TypeName {
get {
var type = this.Type;
if (type != null)
return type.Type;
return string.Format ("[TypeId:{0}]", TypeId);
}
}
public TypeTree Type {
get {
TypeTree type;
Asset.Tree.TypeTrees.TryGetValue (TypeId, out type);
return type;
}
}
public override string ToString ()
{
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 ("unsigned int" == node.Type)
obj = input.ReadUInt32();
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
{
int m_format;
List<TypeTree> m_children = new List<TypeTree>();
public int Version;
public bool IsArray;
public string Type;
public string Name;
public int Size;
public uint Index;
public int Flags;
public IList<TypeTree> Children { get { return m_children; } }
public bool IsAligned { get { return (Flags & 0x4000) != 0; } }
static readonly string Null = "(null)";
static readonly Lazy<byte[]> StringsDat = new Lazy<byte[]> (() => LoadResource ("strings.dat"));
public TypeTree (int format)
{
m_format = format;
}
public void Load (AssetReader reader)
{
if (10 == m_format || m_format >= 12)
LoadBlob (reader);
else
LoadRaw (reader);
}
void LoadRaw (AssetReader reader)
{
Type = reader.ReadCString();
Name = reader.ReadCString();
Size = reader.ReadInt32();
Index = reader.ReadUInt32();
IsArray = reader.ReadInt32() != 0;
Version = reader.ReadInt32();
Flags = reader.ReadInt32();
int count = reader.ReadInt32();
for (int i = 0; i < count; ++i)
{
var child = new TypeTree (m_format);
child.Load (reader);
Children.Add (child);
}
}
byte[] m_data;
void LoadBlob (AssetReader reader)
{
int count = reader.ReadInt32();
int buffer_bytes = reader.ReadInt32();
int node_size = m_format >= 18 ? 32 : 24;
var node_data = reader.ReadBytes (node_size * count);
m_data = reader.ReadBytes (buffer_bytes);
if (m_format >= 21)
reader.Skip (4);
var parents = new Stack<TypeTree>();
parents.Push (this);
using (var buf = new BinMemoryStream (node_data))
{
for (int i = 0; i < count; ++i)
{
int version = buf.ReadInt16();
int depth = buf.ReadUInt8();
TypeTree current;
if (0 == depth)
{
current = this;
}
else
{
while (parents.Count > depth)
parents.Pop();
current = new TypeTree (m_format);
parents.Peek().Children.Add (current);
parents.Push (current);
}
current.Version = version;
current.IsArray = buf.ReadUInt8() != 0;
current.Type = GetString (buf.ReadInt32());
current.Name = GetString (buf.ReadInt32());
current.Size = buf.ReadInt32();
current.Index = buf.ReadUInt32();
current.Flags = buf.ReadInt32();
if (m_format >= 18)
buf.ReadInt64();
}
}
}
string GetString (int offset)
{
byte[] strings;
if (offset < 0)
{
offset &= 0x7FFFFFFF;
strings = StringsDat.Value;
}
else if (offset < m_data.Length)
strings = m_data;
else
return Null;
return Binary.GetCString (strings, offset, strings.Length-offset, Encoding.UTF8);
}
internal static byte[] LoadResource (string name)
{
var res = EmbeddedResource.Load (name, typeof(TypeTree));
if (null == res)
throw new FileNotFoundException ("Resource not found.", name);
return res;
}
}
internal class UnityTypeData
{
string m_version;
List<int> m_class_ids = new List<int> ();
Dictionary<int, byte[]> m_hashes = new Dictionary<int, byte[]> ();
Dictionary<int, TypeTree> m_type_trees = new Dictionary<int, TypeTree> ();
public string Version { get { return m_version; } }
public IList<int> ClassIds { get { return m_class_ids; } }
public IDictionary<int, byte[]> Hashes { get { return m_hashes; } }
public IDictionary<int, TypeTree> TypeTrees { get { return m_type_trees; } }
public void Load (AssetReader reader)
{
int format = reader.Format;
m_version = reader.ReadCString();
var platform = reader.ReadInt32 ();
if (format >= 13)
{
bool has_type_trees = reader.ReadBool ();
int count = reader.ReadInt32 ();
for (int i = 0; i < count; ++i)
{
int class_id = reader.ReadInt32 ();
if (format >= 17)
{
reader.ReadByte ();
int script_id = reader.ReadInt16 ();
if (114 == class_id)
{
if (script_id >= 0)
class_id = -2 - script_id;
else
class_id = -1;
}
}
m_class_ids.Add (class_id);
byte[] hash = reader.ReadBytes (class_id < 0 ? 0x20 : 0x10);
m_hashes[class_id] = hash;
if (has_type_trees)
{
var tree = new TypeTree (format);
tree.Load (reader);
m_type_trees[class_id] = tree;
}
}
}
else
{
int count = reader.ReadInt32 ();
for (int i = 0; i < count; ++i)
{
int class_id = reader.ReadInt32 ();
var tree = new TypeTree (format);
tree.Load (reader);
m_type_trees[class_id] = tree;
}
}
}
}
}