mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-23 19:34:15 +08:00
implemented 'UnityFS' archives and FSB5 audio.
This commit is contained in:
parent
31a01f2e5d
commit
3028c27a6d
@ -7,7 +7,7 @@
|
|||||||
<ProjectGuid>{60054FD9-4472-4BB4-9E3D-2F80D3D22468}</ProjectGuid>
|
<ProjectGuid>{60054FD9-4472-4BB4-9E3D-2F80D3D22468}</ProjectGuid>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
<RootNamespace>GameRes.Experimental</RootNamespace>
|
<RootNamespace>GameRes.Extra</RootNamespace>
|
||||||
<AssemblyName>ArcExtra</AssemblyName>
|
<AssemblyName>ArcExtra</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
@ -81,6 +81,13 @@
|
|||||||
<Compile Include="CellWorks\ArcDB.cs" />
|
<Compile Include="CellWorks\ArcDB.cs" />
|
||||||
<Compile Include="Opus\AudioOPUS.cs" />
|
<Compile Include="Opus\AudioOPUS.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Unity\ArcUnityFS.cs" />
|
||||||
|
<Compile Include="Unity\Asset.cs" />
|
||||||
|
<Compile Include="Unity\AssetReader.cs" />
|
||||||
|
<Compile Include="Unity\AudioFSB5.cs" />
|
||||||
|
<Compile Include="Unity\BundleStream.cs" />
|
||||||
|
<Compile Include="Unity\OggStream.cs" />
|
||||||
|
<Compile Include="Unity\Vorbis.cs" />
|
||||||
<Compile Include="WebP\ImageWEBP.cs" />
|
<Compile Include="WebP\ImageWEBP.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -97,6 +104,7 @@
|
|||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Unity\strings.dat" />
|
||||||
<None Include="x64\libwebp.dll">
|
<None Include="x64\libwebp.dll">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
302
Experimental/Unity/ArcUnityFS.cs
Normal file
302
Experimental/Unity/ArcUnityFS.cs
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
//! \file ArcUnityFS.cs
|
||||||
|
//! \date Tue Apr 04 22:27:22 2017
|
||||||
|
//! \brief Unity asset archive.
|
||||||
|
//
|
||||||
|
// 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.Generic;
|
||||||
|
using System.ComponentModel.Composition;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GameRes.Compression;
|
||||||
|
using GameRes.Utility;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.Unity
|
||||||
|
{
|
||||||
|
[Export(typeof(ArchiveFormat))]
|
||||||
|
public class UnityFSOpener : ArchiveFormat
|
||||||
|
{
|
||||||
|
public override string Tag { get { return "UNITY/FS"; } }
|
||||||
|
public override string Description { get { return "Unity game engine asset archive"; } }
|
||||||
|
public override uint Signature { get { return 0x74696E55; } } // 'UnityFS'
|
||||||
|
public override bool IsHierarchic { get { return true; } }
|
||||||
|
public override bool CanWrite { get { return false; } }
|
||||||
|
|
||||||
|
public UnityFSOpener ()
|
||||||
|
{
|
||||||
|
Extensions = new string[] { "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ArcFile TryOpen (ArcView file)
|
||||||
|
{
|
||||||
|
if (!file.View.AsciiEqual (0, "UnityFS\0"))
|
||||||
|
return null;
|
||||||
|
int arc_version = Binary.BigEndian (file.View.ReadInt32 (8));
|
||||||
|
if (arc_version != 6)
|
||||||
|
return null;
|
||||||
|
long data_offset;
|
||||||
|
byte[] index_data;
|
||||||
|
using (var input = file.CreateStream())
|
||||||
|
{
|
||||||
|
input.Position = 0xC;
|
||||||
|
input.ReadCString (Encoding.UTF8);
|
||||||
|
input.ReadCString (Encoding.UTF8);
|
||||||
|
long file_size = Binary.BigEndian (input.ReadInt64());
|
||||||
|
int packed_index_size = Binary.BigEndian (input.ReadInt32());
|
||||||
|
int index_size = Binary.BigEndian (input.ReadInt32());
|
||||||
|
int flags = Binary.BigEndian (input.ReadInt32());
|
||||||
|
long index_offset;
|
||||||
|
if (0 == (flags & 0x80))
|
||||||
|
{
|
||||||
|
index_offset = input.Position;
|
||||||
|
data_offset = index_offset + packed_index_size;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index_offset = file_size - packed_index_size;
|
||||||
|
data_offset = input.Position;
|
||||||
|
}
|
||||||
|
input.Position = index_offset;
|
||||||
|
var packed = input.ReadBytes (packed_index_size);
|
||||||
|
switch (flags & 0x3F)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
index_data = packed;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
index_data = UnpackLzma (packed, index_size);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
index_data = new byte[index_size];
|
||||||
|
Lz4Compressor.DecompressBlock (packed, packed.Length, index_data, index_data.Length);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var index = new AssetDeserializer (file, data_offset);
|
||||||
|
using (var input = new BinMemoryStream (index_data))
|
||||||
|
index.Parse (input);
|
||||||
|
var dir = index.LoadObjects();
|
||||||
|
return new UnityBundle (file, this, dir, index.Segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static byte[] UnpackLzma (byte[] input, int unpacked_size)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class BundleEntry : Entry
|
||||||
|
{
|
||||||
|
public uint Flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AssetEntry : Entry
|
||||||
|
{
|
||||||
|
public BundleEntry Bundle;
|
||||||
|
public UnityObject AssetObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class UnityBundle : ArcFile
|
||||||
|
{
|
||||||
|
public readonly List<BundleSegment> Segments;
|
||||||
|
|
||||||
|
public UnityBundle (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, List<BundleSegment> segments)
|
||||||
|
: base (arc, impl, dir)
|
||||||
|
{
|
||||||
|
Segments = segments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class BundleSegment
|
||||||
|
{
|
||||||
|
public long Offset;
|
||||||
|
public uint PackedSize;
|
||||||
|
public long UnpackedOffset;
|
||||||
|
public uint UnpackedSize;
|
||||||
|
public int Compression;
|
||||||
|
|
||||||
|
public bool IsCompressed { get { return Compression != 0; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AssetDeserializer
|
||||||
|
{
|
||||||
|
readonly ArcView m_file;
|
||||||
|
readonly long m_data_offset;
|
||||||
|
List<BundleSegment> m_segments;
|
||||||
|
List<BundleEntry> m_bundles;
|
||||||
|
|
||||||
|
public List<BundleSegment> Segments { get { return m_segments; } }
|
||||||
|
|
||||||
|
public AssetDeserializer (ArcView file, long data_offset)
|
||||||
|
{
|
||||||
|
m_file = file;
|
||||||
|
m_data_offset = data_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Parse (IBinaryStream index)
|
||||||
|
{
|
||||||
|
index.Position = 16;
|
||||||
|
int segment_count = Binary.BigEndian (index.ReadInt32());
|
||||||
|
m_segments = new List<BundleSegment> (segment_count);
|
||||||
|
long packed_offset = m_data_offset;
|
||||||
|
long unpacked_offset = 0;
|
||||||
|
for (int i = 0; i < segment_count; ++i)
|
||||||
|
{
|
||||||
|
var segment = new BundleSegment();
|
||||||
|
segment.Offset = packed_offset;
|
||||||
|
segment.UnpackedOffset = unpacked_offset;
|
||||||
|
segment.UnpackedSize = Binary.BigEndian (index.ReadUInt32());
|
||||||
|
segment.PackedSize = Binary.BigEndian (index.ReadUInt32());
|
||||||
|
segment.Compression = Binary.BigEndian (index.ReadUInt16());
|
||||||
|
m_segments.Add (segment);
|
||||||
|
packed_offset += segment.PackedSize;
|
||||||
|
unpacked_offset += segment.UnpackedSize;
|
||||||
|
}
|
||||||
|
int count = Binary.BigEndian (index.ReadInt32());
|
||||||
|
m_bundles = new List<BundleEntry> (count);
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
var entry = new BundleEntry();
|
||||||
|
entry.Offset = Binary.BigEndian (index.ReadInt64());
|
||||||
|
entry.Size = (uint)Binary.BigEndian (index.ReadInt64());
|
||||||
|
entry.Flags = Binary.BigEndian (index.ReadUInt32());
|
||||||
|
entry.Name = index.ReadCString (Encoding.UTF8);
|
||||||
|
m_bundles.Add (entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entry> LoadObjects ()
|
||||||
|
{
|
||||||
|
var dir = new List<Entry>();
|
||||||
|
using (var stream = new BundleStream (m_file, m_segments))
|
||||||
|
{
|
||||||
|
foreach (BundleEntry bundle in m_bundles)
|
||||||
|
{
|
||||||
|
if (bundle.Name.EndsWith (".resource"))
|
||||||
|
continue;
|
||||||
|
using (var asset_stream = new StreamRegion (stream, bundle.Offset, bundle.Size, true))
|
||||||
|
using (var reader = new AssetReader (asset_stream, bundle.Name))
|
||||||
|
{
|
||||||
|
var asset = new Asset();
|
||||||
|
asset.Load (reader);
|
||||||
|
var object_dir = ParseAsset (bundle, asset, stream);
|
||||||
|
dir.AddRange (object_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0 == dir.Count)
|
||||||
|
dir.AddRange (m_bundles);
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<Entry> ParseAsset (BundleEntry bundle, Asset asset, Stream file)
|
||||||
|
{
|
||||||
|
Dictionary<long, string> id_map = null;
|
||||||
|
var bundle_types = asset.Tree.TypeTrees.Where (t => t.Value.Type == "AssetBundle").Select (t => t.Key);
|
||||||
|
if (bundle_types.Any())
|
||||||
|
{
|
||||||
|
// try to read entry names from AssetBundle object
|
||||||
|
int bundle_type_id = bundle_types.First();
|
||||||
|
var asset_bundle = asset.Objects.FirstOrDefault (x => x.TypeId == bundle_type_id);
|
||||||
|
if (asset_bundle != null)
|
||||||
|
{
|
||||||
|
id_map = asset.ReadAssetBundle (file, asset_bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (null == id_map)
|
||||||
|
id_map = new Dictionary<long, string>();
|
||||||
|
foreach (var obj in asset.Objects)
|
||||||
|
{
|
||||||
|
string type = obj.Type;
|
||||||
|
AssetEntry entry = null;
|
||||||
|
if ("AudioClip" == type)
|
||||||
|
{
|
||||||
|
entry = ReadAudioClip (file, obj, asset);
|
||||||
|
}
|
||||||
|
if (null == entry)
|
||||||
|
{
|
||||||
|
entry = new AssetEntry {
|
||||||
|
Type = type,
|
||||||
|
Bundle = bundle,
|
||||||
|
AssetObject = obj,
|
||||||
|
Offset = obj.Offset,
|
||||||
|
Size = obj.Size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
string name;
|
||||||
|
if (!id_map.TryGetValue (obj.PathId, out name))
|
||||||
|
name = obj.PathId.ToString ("X16");
|
||||||
|
else
|
||||||
|
name = ShortenPath (name);
|
||||||
|
entry.Name = name;
|
||||||
|
yield return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetEntry ReadAudioClip (Stream input, UnityObject obj, Asset asset)
|
||||||
|
{
|
||||||
|
using (var stream = new StreamRegion (input, obj.Offset, obj.Size, true))
|
||||||
|
using (var reader = new AssetReader (stream, ""))
|
||||||
|
{
|
||||||
|
reader.SetupReaders (asset.Format, asset.IsLittleEndian);
|
||||||
|
var clip = new AudioClip();
|
||||||
|
clip.Load (reader);
|
||||||
|
var bundle_name = Path.GetFileName (clip.m_Source);
|
||||||
|
var bundle = m_bundles.FirstOrDefault (b => b.Name == bundle_name);
|
||||||
|
if (null == bundle)
|
||||||
|
return null;
|
||||||
|
return new AssetEntry {
|
||||||
|
Type = "audio",
|
||||||
|
Bundle = bundle,
|
||||||
|
AssetObject = obj,
|
||||||
|
Offset = bundle.Offset + clip.m_Offset,
|
||||||
|
Size = (uint)clip.m_Size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shorten asset path to contain only the bottom directory component.
|
||||||
|
/// </summary>
|
||||||
|
static string ShortenPath (string name)
|
||||||
|
{
|
||||||
|
int slash_pos = name.LastIndexOf ('/');
|
||||||
|
if (-1 == slash_pos)
|
||||||
|
return name;
|
||||||
|
slash_pos = name.LastIndexOf ('/', slash_pos-1);
|
||||||
|
if (-1 == slash_pos)
|
||||||
|
return name;
|
||||||
|
return name.Substring (slash_pos+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
454
Experimental/Unity/Asset.cs
Normal file
454
Experimental/Unity/Asset.cs
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
//! \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.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using GameRes.Utility;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.Unity
|
||||||
|
{
|
||||||
|
internal class Asset
|
||||||
|
{
|
||||||
|
int m_header_size;
|
||||||
|
int m_format;
|
||||||
|
uint 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)
|
||||||
|
{
|
||||||
|
m_header_size = input.ReadInt32();
|
||||||
|
input.ReadUInt32(); // file_size
|
||||||
|
m_format = input.ReadInt32();
|
||||||
|
m_data_offset = input.ReadUInt32();
|
||||||
|
if (m_format >= 9)
|
||||||
|
m_is_little_endian = 0 == input.ReadInt32();
|
||||||
|
input.SetupReaders (m_format, m_is_little_endian);
|
||||||
|
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 id = input.ReadId();
|
||||||
|
m_adds[id] = input.ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 ("Unknown type id", obj.ClassId.ToString());
|
||||||
|
m_types[obj.TypeId] = null;
|
||||||
|
}
|
||||||
|
throw new ApplicationException (string.Format ("Unknwon type id {0}", obj.ClassId));
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<long, string> ReadAssetBundle (Stream input, UnityObject bundle)
|
||||||
|
{
|
||||||
|
using (var stream = new StreamRegion (input, bundle.Offset, bundle.Size, true))
|
||||||
|
using (var reader = new AssetReader (stream, ""))
|
||||||
|
{
|
||||||
|
reader.SetupReaders (m_format, m_is_little_endian);
|
||||||
|
var name = reader.ReadString(); // m_Name
|
||||||
|
reader.Align();
|
||||||
|
var id_map = new Dictionary<long, string>();
|
||||||
|
id_map[bundle.PathId] = name;
|
||||||
|
int count = reader.ReadInt32(); // m_PreloadTable
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
reader.ReadInt32(); // m_FileID
|
||||||
|
reader.ReadInt64(); // m_PathID
|
||||||
|
}
|
||||||
|
count = reader.ReadInt32(); // m_Container
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
name = reader.ReadString();
|
||||||
|
reader.Align();
|
||||||
|
reader.ReadInt32(); // preloadIndex
|
||||||
|
reader.ReadInt32(); // preloadSize
|
||||||
|
reader.ReadInt32(); // m_FileID
|
||||||
|
long id = reader.ReadInt64();
|
||||||
|
id_map[id] = name;
|
||||||
|
}
|
||||||
|
return id_map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 void Load (AssetReader reader)
|
||||||
|
{
|
||||||
|
PathId = reader.ReadId();
|
||||||
|
Offset = reader.ReadUInt32() + 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 Type {
|
||||||
|
get {
|
||||||
|
var type_tree = Asset.Tree.TypeTrees;
|
||||||
|
if (type_tree.ContainsKey (TypeId))
|
||||||
|
return type_tree[TypeId].Type;
|
||||||
|
return string.Format ("[TypeId:{0}]", TypeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString ()
|
||||||
|
{
|
||||||
|
return string.Format ("<{0} {1}>", Type, ClassId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; } }
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] m_data;
|
||||||
|
|
||||||
|
void LoadBlob (AssetReader reader)
|
||||||
|
{
|
||||||
|
int count = reader.ReadInt32();
|
||||||
|
int buffer_bytes = reader.ReadInt32();
|
||||||
|
var node_data = reader.ReadBytes (24 * count);
|
||||||
|
m_data = reader.ReadBytes (buffer_bytes);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Stream OpenResource (string name)
|
||||||
|
{
|
||||||
|
var qualified_name = ".Unity." + name;
|
||||||
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
var res_name = assembly.GetManifestResourceNames().Single (r => r.EndsWith (qualified_name));
|
||||||
|
Stream stream = assembly.GetManifestResourceStream (res_name);
|
||||||
|
if (null == stream)
|
||||||
|
throw new FileNotFoundException ("Resource not found.", name);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static byte[] LoadResource (string name)
|
||||||
|
{
|
||||||
|
using (var stream = OpenResource (name))
|
||||||
|
{
|
||||||
|
var res = new byte[stream.Length];
|
||||||
|
stream.Read (res, 0, res.Length);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class UnityTypeData
|
||||||
|
{
|
||||||
|
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 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;
|
||||||
|
var 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AudioClip
|
||||||
|
{
|
||||||
|
public int m_LoadType;
|
||||||
|
public int m_Channels;
|
||||||
|
public int m_Frequency;
|
||||||
|
public int m_BitsPerSample;
|
||||||
|
public float m_Length;
|
||||||
|
public bool m_IsTrackerFormat;
|
||||||
|
public int m_SubsoundIndex;
|
||||||
|
public bool m_PreloadAudioData;
|
||||||
|
public bool m_LoadInBackground;
|
||||||
|
public bool m_Legacy3D;
|
||||||
|
public string m_Source;
|
||||||
|
public long m_Offset;
|
||||||
|
public long m_Size;
|
||||||
|
public int m_CompressionFormat;
|
||||||
|
|
||||||
|
public void Load (AssetReader reader)
|
||||||
|
{
|
||||||
|
var name = reader.ReadString();
|
||||||
|
reader.Align();
|
||||||
|
m_LoadType = reader.ReadInt32();
|
||||||
|
m_Channels = reader.ReadInt32();
|
||||||
|
m_Frequency = reader.ReadInt32();
|
||||||
|
m_BitsPerSample = reader.ReadInt32();
|
||||||
|
m_Length = reader.ReadFloat();
|
||||||
|
m_IsTrackerFormat = reader.ReadBool();
|
||||||
|
reader.Align();
|
||||||
|
m_SubsoundIndex = reader.ReadInt32();
|
||||||
|
m_PreloadAudioData = reader.ReadBool();
|
||||||
|
m_LoadInBackground = reader.ReadBool();
|
||||||
|
m_Legacy3D = reader.ReadBool();
|
||||||
|
reader.Align();
|
||||||
|
m_Source = reader.ReadString();
|
||||||
|
reader.Align();
|
||||||
|
m_Offset = reader.ReadInt64();
|
||||||
|
m_Size = reader.ReadInt64();
|
||||||
|
m_CompressionFormat = reader.ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
Experimental/Unity/AssetReader.cs
Normal file
194
Experimental/Unity/AssetReader.cs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
//! \file AssetReader.cs
|
||||||
|
//! \date Wed Apr 05 13:28:33 2017
|
||||||
|
//! \brief Unity asset reader class.
|
||||||
|
//
|
||||||
|
// 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.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using GameRes.Utility;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.Unity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// AssetReader provides access to a serialized stream of Unity assets.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class AssetReader : IDisposable
|
||||||
|
{
|
||||||
|
IBinaryStream m_input;
|
||||||
|
int m_format;
|
||||||
|
|
||||||
|
public int Format { get { return m_format; } }
|
||||||
|
public long Position {
|
||||||
|
get { return m_input.Position; }
|
||||||
|
set { m_input.Position = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetReader (Stream input, string name)
|
||||||
|
{
|
||||||
|
m_input = BinaryStream.FromStream (input, name);
|
||||||
|
SetupReaders (0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action Align;
|
||||||
|
public Func<ushort> ReadUInt16;
|
||||||
|
public Func<short> ReadInt16;
|
||||||
|
public Func<uint> ReadUInt32;
|
||||||
|
public Func<int> ReadInt32;
|
||||||
|
public Func<long> ReadInt64;
|
||||||
|
public Func<long> ReadId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup reader endianness accordingly.
|
||||||
|
/// </summary>
|
||||||
|
public void SetupReaders (int format, bool is_little_endian)
|
||||||
|
{
|
||||||
|
m_format = format;
|
||||||
|
if (is_little_endian)
|
||||||
|
{
|
||||||
|
ReadUInt16 = () => m_input.ReadUInt16();
|
||||||
|
ReadUInt32 = () => m_input.ReadUInt32();
|
||||||
|
ReadInt16 = () => m_input.ReadInt16();
|
||||||
|
ReadInt32 = () => m_input.ReadInt32();
|
||||||
|
ReadInt64 = () => m_input.ReadInt64();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReadUInt16 = () => Binary.BigEndian (m_input.ReadUInt16());
|
||||||
|
ReadUInt32 = () => Binary.BigEndian (m_input.ReadUInt32());
|
||||||
|
ReadInt16 = () => Binary.BigEndian (m_input.ReadInt16());
|
||||||
|
ReadInt32 = () => Binary.BigEndian (m_input.ReadInt32());
|
||||||
|
ReadInt64 = () => Binary.BigEndian (m_input.ReadInt64());
|
||||||
|
}
|
||||||
|
if (m_format >= 14)
|
||||||
|
{
|
||||||
|
Align = () => {
|
||||||
|
long pos = m_input.Position;
|
||||||
|
if (0 != (pos & 3))
|
||||||
|
m_input.Position = (pos + 3) & ~3L;
|
||||||
|
};
|
||||||
|
ReadId = ReadInt64;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Align = () => {};
|
||||||
|
ReadId = () => ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set asset ID length. If <paramref name="long_id"/> is <c>true</c> IDs are 64-bit, otherwise 32-bit.
|
||||||
|
/// </summary>
|
||||||
|
public void SetupReadId (bool long_ids)
|
||||||
|
{
|
||||||
|
if (long_ids)
|
||||||
|
ReadId = ReadInt64;
|
||||||
|
else
|
||||||
|
ReadId = () => ReadInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read bytes into specified buffer.
|
||||||
|
/// </summary>
|
||||||
|
public int Read (byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
return m_input.Read (buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read null-terminated UTF8 string.
|
||||||
|
/// </summary>
|
||||||
|
public string ReadCString ()
|
||||||
|
{
|
||||||
|
return m_input.ReadCString (Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read UTF8 string prefixed with length.
|
||||||
|
/// </summary>
|
||||||
|
public string ReadString ()
|
||||||
|
{
|
||||||
|
int length = ReadInt32();
|
||||||
|
if (0 == length)
|
||||||
|
return string.Empty;
|
||||||
|
var bytes = ReadBytes (length);
|
||||||
|
return Encoding.UTF8.GetString (bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read <paramref name="length"/> bytes from stream and return them in a byte array.
|
||||||
|
/// May return less than <paramref name="length"/> bytes if end of file was encountered.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] ReadBytes (int length)
|
||||||
|
{
|
||||||
|
return m_input.ReadBytes (length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read unsigned 8-bits byte from a stream.
|
||||||
|
/// </summary>
|
||||||
|
public byte ReadByte ()
|
||||||
|
{
|
||||||
|
return m_input.ReadUInt8();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read byte and interpret is as a bool value, non-zero resulting in <c>true</c>.
|
||||||
|
/// </summary>
|
||||||
|
public bool ReadBool ()
|
||||||
|
{
|
||||||
|
return ReadByte() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
struct Union
|
||||||
|
{
|
||||||
|
[FieldOffset (0)]
|
||||||
|
public uint u;
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public float f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read float value from a stream.
|
||||||
|
/// </summary>
|
||||||
|
public float ReadFloat ()
|
||||||
|
{
|
||||||
|
var buf = new Union();
|
||||||
|
buf.u = ReadUInt32();
|
||||||
|
return buf.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _disposed = false;
|
||||||
|
public void Dispose ()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
m_input.Dispose();
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
GC.SuppressFinalize (this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
402
Experimental/Unity/AudioFSB5.cs
Normal file
402
Experimental/Unity/AudioFSB5.cs
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
//! \file AudioFSB5.cs
|
||||||
|
//! \date Thu Apr 06 01:12:27 2017
|
||||||
|
//! \brief FMOD Sample Bank audio file.
|
||||||
|
//
|
||||||
|
// Based on [python-fsb5](https://github.com/HearthSim/python-fsb5)
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016 Simon Pinfold
|
||||||
|
//
|
||||||
|
// C# implementation 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.Generic;
|
||||||
|
using System.ComponentModel.Composition;
|
||||||
|
using System.IO;
|
||||||
|
using GameRes.Formats.Vorbis;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.Fmod
|
||||||
|
{
|
||||||
|
[Export(typeof(AudioFormat))]
|
||||||
|
public class Fsb5Audio : AudioFormat
|
||||||
|
{
|
||||||
|
public override string Tag { get { return "FSB5"; } }
|
||||||
|
public override string Description { get { return "FMOD Sample Bank audio format"; } }
|
||||||
|
public override uint Signature { get { return 0x35425346; } } // 'FSB5'
|
||||||
|
public override bool CanWrite { get { return false; } }
|
||||||
|
|
||||||
|
public override SoundInput TryOpen (IBinaryStream file)
|
||||||
|
{
|
||||||
|
var fsb = new Fsb5Decoder (file);
|
||||||
|
var sound = fsb.Convert();
|
||||||
|
file.Dispose();
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ResourceScheme Scheme
|
||||||
|
{
|
||||||
|
get { return new FmodScheme { VorbisHeaders = Fsb5Decoder.VorbisHeaders }; }
|
||||||
|
set { Fsb5Decoder.VorbisHeaders = ((FmodScheme)value).VorbisHeaders; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SoundFormat
|
||||||
|
{
|
||||||
|
// Order is crucial.
|
||||||
|
None,
|
||||||
|
Pcm8,
|
||||||
|
Pcm16,
|
||||||
|
Pcm24,
|
||||||
|
Pcm32,
|
||||||
|
PcmFloat,
|
||||||
|
GcAdpcm,
|
||||||
|
ImaAdpcm,
|
||||||
|
Vag,
|
||||||
|
Hevag,
|
||||||
|
Xma,
|
||||||
|
Mpeg,
|
||||||
|
Celt,
|
||||||
|
At9,
|
||||||
|
Xwma,
|
||||||
|
Vorbis,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ChunkType
|
||||||
|
{
|
||||||
|
Channels = 1,
|
||||||
|
SampleRate = 2,
|
||||||
|
Loop = 3,
|
||||||
|
VorbisData = 11,
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Sample
|
||||||
|
{
|
||||||
|
public int SampleRate;
|
||||||
|
public ushort Channels;
|
||||||
|
public long DataOffset;
|
||||||
|
public int SampleCount;
|
||||||
|
public byte[] Data;
|
||||||
|
public Dictionary<ChunkType, object> MetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Fsb5Decoder
|
||||||
|
{
|
||||||
|
IBinaryStream m_input;
|
||||||
|
int m_sample_header_size;
|
||||||
|
int m_name_table_size;
|
||||||
|
int m_header_size;
|
||||||
|
int m_data_size;
|
||||||
|
SoundFormat m_format;
|
||||||
|
|
||||||
|
static readonly HashSet<SoundFormat> Supported = new HashSet<SoundFormat> {
|
||||||
|
SoundFormat.Pcm8,
|
||||||
|
SoundFormat.Pcm16,
|
||||||
|
SoundFormat.Pcm32,
|
||||||
|
SoundFormat.PcmFloat,
|
||||||
|
SoundFormat.Vorbis,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Fsb5Decoder (IBinaryStream input)
|
||||||
|
{
|
||||||
|
m_input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Sample> ReadSamples ()
|
||||||
|
{
|
||||||
|
var header = m_input.ReadHeader (0x3C);
|
||||||
|
int version = header.ToInt32 (4);
|
||||||
|
int sample_count = header.ToInt32 (8);
|
||||||
|
m_sample_header_size = header.ToInt32 (0xC);
|
||||||
|
m_name_table_size = header.ToInt32 (0x10);
|
||||||
|
m_data_size = header.ToInt32 (0x14);
|
||||||
|
m_format = (SoundFormat)header.ToInt32 (0x18);
|
||||||
|
if (!Supported.Contains (m_format))
|
||||||
|
throw new NotSupportedException();
|
||||||
|
|
||||||
|
if (0 == version)
|
||||||
|
m_input.ReadInt32();
|
||||||
|
var samples = new List<Sample> (sample_count);
|
||||||
|
m_header_size = (int)m_input.Position;
|
||||||
|
for (int i = 0; i < sample_count; ++i)
|
||||||
|
{
|
||||||
|
long raw = m_input.ReadInt64();
|
||||||
|
bool next_chunk = 0 != (raw & 1);
|
||||||
|
int sample_rate = (int)((raw >> 1) & 0xF);
|
||||||
|
ushort channels = (ushort)(((raw >> 5) & 1) + 1);
|
||||||
|
long data_offset = ((raw >> 6) & 0xFFFFFFF) * 0x10;
|
||||||
|
int count = (int)((raw >> 34) & 0x3FFFFFFF);
|
||||||
|
var chunks = new Dictionary<ChunkType, object>();
|
||||||
|
while (next_chunk)
|
||||||
|
{
|
||||||
|
int d = m_input.ReadInt32();
|
||||||
|
next_chunk = 0 != (d & 1);
|
||||||
|
int chunk_size = (d >> 1) & 0xFFFFFF;
|
||||||
|
var chunk_type = (ChunkType)((d >> 25) & 0x7F);
|
||||||
|
object chunk;
|
||||||
|
switch (chunk_type)
|
||||||
|
{
|
||||||
|
case ChunkType.Channels:
|
||||||
|
chunk = m_input.ReadUInt8();
|
||||||
|
break;
|
||||||
|
case ChunkType.SampleRate:
|
||||||
|
chunk = m_input.ReadInt32();
|
||||||
|
break;
|
||||||
|
case ChunkType.Loop:
|
||||||
|
int v1 = m_input.ReadInt32();
|
||||||
|
int v2 = m_input.ReadInt32();
|
||||||
|
chunk = Tuple.Create (v1, v2);
|
||||||
|
break;
|
||||||
|
case ChunkType.VorbisData:
|
||||||
|
chunk = new VorbisData {
|
||||||
|
Crc32 = m_input.ReadUInt32(),
|
||||||
|
Data = m_input.ReadBytes (chunk_size-4) // XXX unused
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
chunk = m_input.ReadBytes (chunk_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chunks[chunk_type] = chunk;
|
||||||
|
}
|
||||||
|
if (chunks.ContainsKey (ChunkType.SampleRate))
|
||||||
|
sample_rate = (int)chunks[ChunkType.SampleRate];
|
||||||
|
else if (SampleRates.ContainsKey (sample_rate))
|
||||||
|
sample_rate = SampleRates[sample_rate];
|
||||||
|
else
|
||||||
|
throw new InvalidFormatException ("Invalid FSB5 sample rate.");
|
||||||
|
|
||||||
|
var sample = new Sample {
|
||||||
|
SampleRate = sample_rate,
|
||||||
|
Channels = channels,
|
||||||
|
DataOffset = data_offset,
|
||||||
|
SampleCount = count,
|
||||||
|
MetaData = chunks,
|
||||||
|
Data = null
|
||||||
|
};
|
||||||
|
samples.Add (sample);
|
||||||
|
}
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SoundInput Convert ()
|
||||||
|
{
|
||||||
|
var samples = ReadSamples();
|
||||||
|
var sample = samples[0];
|
||||||
|
int data_length;
|
||||||
|
if (samples.Count > 1)
|
||||||
|
data_length = (int)(samples[1].DataOffset - sample.DataOffset);
|
||||||
|
else
|
||||||
|
data_length = m_data_size;
|
||||||
|
m_input.Position = m_header_size + m_sample_header_size + m_name_table_size;
|
||||||
|
sample.Data = m_input.ReadBytes (data_length);
|
||||||
|
|
||||||
|
if (SoundFormat.Vorbis == m_format)
|
||||||
|
return RebuildVorbis (sample);
|
||||||
|
else
|
||||||
|
return RebuildPcm (sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundInput RebuildPcm (Sample sample)
|
||||||
|
{
|
||||||
|
var format = new WaveFormat
|
||||||
|
{
|
||||||
|
FormatTag = (ushort)(SoundFormat.PcmFloat == m_format ? 3 : 1),
|
||||||
|
Channels = sample.Channels,
|
||||||
|
SamplesPerSecond = (uint)sample.SampleRate,
|
||||||
|
};
|
||||||
|
switch (m_format)
|
||||||
|
{
|
||||||
|
case SoundFormat.Pcm8: format.BitsPerSample = 8; break;
|
||||||
|
case SoundFormat.Pcm16: format.BitsPerSample = 16; break;
|
||||||
|
case SoundFormat.PcmFloat:
|
||||||
|
case SoundFormat.Pcm32: format.BitsPerSample = 32; break;
|
||||||
|
default: throw new InvalidFormatException();
|
||||||
|
}
|
||||||
|
format.BlockAlign = (ushort)(format.Channels * format.BitsPerSample / 8);
|
||||||
|
format.SetBPS();
|
||||||
|
var pcm = new MemoryStream (sample.Data);
|
||||||
|
return new RawPcmInput (pcm, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundInput RebuildVorbis (Sample sample)
|
||||||
|
{
|
||||||
|
if (!sample.MetaData.ContainsKey (ChunkType.VorbisData))
|
||||||
|
throw new InvalidFormatException ("No VORBISDATA chunk in FSB5 Vorbis stream.");
|
||||||
|
var vorbis_data = sample.MetaData[ChunkType.VorbisData] as VorbisData;
|
||||||
|
var setup_data = GetVorbisHeader (vorbis_data.Crc32);
|
||||||
|
var state = new OggStreamState (1);
|
||||||
|
|
||||||
|
var id_packet = RebuildIdPacket (sample, 0x100, 0x800);
|
||||||
|
var comment_packet = RebuildCommentPacket();
|
||||||
|
var setup_packet = RebuildSetupPacket (setup_data);
|
||||||
|
var info = CreateVorbisInfo (sample, setup_packet);
|
||||||
|
|
||||||
|
var output = new MemoryStream();
|
||||||
|
state.PacketIn (id_packet);
|
||||||
|
state.Write (output);
|
||||||
|
state.PacketIn (comment_packet);
|
||||||
|
state.Write (output);
|
||||||
|
state.PacketIn (setup_packet);
|
||||||
|
state.Write (output);
|
||||||
|
state.Flush (output);
|
||||||
|
|
||||||
|
long packet_no = setup_packet.PacketNo + 1;
|
||||||
|
long granule_pos = 0;
|
||||||
|
int prev_block_size = 0;
|
||||||
|
using (var input = new BinMemoryStream (sample.Data))
|
||||||
|
{
|
||||||
|
var packet = new OggPacket();
|
||||||
|
int packet_size = ReadPacketSize (input);
|
||||||
|
while (packet_size > 0)
|
||||||
|
{
|
||||||
|
packet.SetPacket (packet_no++, input.ReadBytes (packet_size));
|
||||||
|
packet_size = ReadPacketSize (input);
|
||||||
|
packet.EoS = 0 == packet_size;
|
||||||
|
|
||||||
|
int block_size = info.PacketBlockSize (packet);
|
||||||
|
if (prev_block_size != 0)
|
||||||
|
granule_pos += (block_size + prev_block_size) / 4;
|
||||||
|
else
|
||||||
|
granule_pos = 0;
|
||||||
|
packet.GranulePos = granule_pos;
|
||||||
|
prev_block_size = block_size;
|
||||||
|
|
||||||
|
state.PacketIn (packet);
|
||||||
|
state.Write (output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.Position = 0;
|
||||||
|
return new OggInput (output);
|
||||||
|
}
|
||||||
|
|
||||||
|
VorbisInfo CreateVorbisInfo (Sample sample, OggPacket setup_packet)
|
||||||
|
{
|
||||||
|
var info = new VorbisInfo {
|
||||||
|
Channels = sample.Channels,
|
||||||
|
Rate = sample.SampleRate,
|
||||||
|
};
|
||||||
|
info.CodecSetup.BlockSizes[0] = 0x100;
|
||||||
|
info.CodecSetup.BlockSizes[1] = 0x800;
|
||||||
|
var comment = new VorbisComment { Vendor = VorbisComment.EncodeVendorString };
|
||||||
|
info.SynthesisHeaderin (comment, setup_packet);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadPacketSize (IBinaryStream input)
|
||||||
|
{
|
||||||
|
int lo = input.ReadByte();
|
||||||
|
if (-1 == lo)
|
||||||
|
return 0;
|
||||||
|
int hi = input.ReadByte();
|
||||||
|
if (-1 == hi)
|
||||||
|
return 0;
|
||||||
|
return hi << 8 | lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
OggPacket RebuildIdPacket (Sample sample, uint blocksize_short, uint blocksize_long)
|
||||||
|
{
|
||||||
|
using (var buf = new MemoryStream())
|
||||||
|
using (var output = new BinaryWriter (buf))
|
||||||
|
{
|
||||||
|
output.Write ((byte)1);
|
||||||
|
output.Write ("vorbis".ToCharArray());
|
||||||
|
output.Write (0);
|
||||||
|
output.Write ((byte)sample.Channels);
|
||||||
|
output.Write (sample.SampleRate);
|
||||||
|
output.Write (0);
|
||||||
|
output.Write (0);
|
||||||
|
output.Write (0);
|
||||||
|
int lo = VorbisInfo.CountBits (blocksize_short);
|
||||||
|
int hi = VorbisInfo.CountBits (blocksize_long);
|
||||||
|
int bits = hi << 4 | lo;
|
||||||
|
output.Write ((byte)bits);
|
||||||
|
output.Write ((byte)1);
|
||||||
|
output.Flush();
|
||||||
|
|
||||||
|
var packet = new OggPacket();
|
||||||
|
packet.SetPacket (0, buf.ToArray());
|
||||||
|
packet.BoS = true;
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OggPacket RebuildCommentPacket ()
|
||||||
|
{
|
||||||
|
var comment = new VorbisComment();
|
||||||
|
var packet = new OggPacket();
|
||||||
|
comment.HeaderOut (packet);
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
OggPacket RebuildSetupPacket (byte[] setup_packet)
|
||||||
|
{
|
||||||
|
var packet = new OggPacket();
|
||||||
|
packet.SetPacket (2, setup_packet);
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly Dictionary<int, int> SampleRates = new Dictionary<int,int> {
|
||||||
|
{ 1, 8000 },
|
||||||
|
{ 2, 11000 },
|
||||||
|
{ 3, 11025 },
|
||||||
|
{ 4, 16000 },
|
||||||
|
{ 5, 22050 },
|
||||||
|
{ 6, 24000 },
|
||||||
|
{ 7, 32000 },
|
||||||
|
{ 8, 44100 },
|
||||||
|
{ 9, 48000 },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static byte[] GetVorbisHeader (uint id)
|
||||||
|
{
|
||||||
|
FmodVorbisSetup setup;
|
||||||
|
if (!VorbisHeaders.TryGetValue (id, out setup))
|
||||||
|
throw new InvalidFormatException (string.Format ("Unknown FSB5 Vorbis encoding 0x{0:X8}.", id));
|
||||||
|
if (null == setup.PatchData || 0 == setup.PatchData.Length)
|
||||||
|
return setup.VorbisData;
|
||||||
|
var data = setup.VorbisData.Clone() as byte[];
|
||||||
|
Buffer.BlockCopy (setup.PatchData, 0, data, setup.PatchOffset, setup.PatchData.Length);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Dictionary<uint, FmodVorbisSetup> VorbisHeaders = new Dictionary<uint, FmodVorbisSetup>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class VorbisData
|
||||||
|
{
|
||||||
|
public uint Crc32;
|
||||||
|
public byte[] Data; // ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class FmodVorbisSetup
|
||||||
|
{
|
||||||
|
public byte[] VorbisData;
|
||||||
|
public int PatchOffset;
|
||||||
|
public byte[] PatchData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class FmodScheme : ResourceScheme
|
||||||
|
{
|
||||||
|
public Dictionary<uint, FmodVorbisSetup> VorbisHeaders;
|
||||||
|
}
|
||||||
|
}
|
201
Experimental/Unity/BundleStream.cs
Normal file
201
Experimental/Unity/BundleStream.cs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
//! \file BundleStream.cs
|
||||||
|
//! \date Wed Apr 05 13:30:19 2017
|
||||||
|
//! \brief Stream representing Unity bundle.
|
||||||
|
//
|
||||||
|
// 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.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using GameRes.Compression;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.Unity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stream representing Unity asset bundle.
|
||||||
|
/// </summary>
|
||||||
|
internal class BundleStream : Stream
|
||||||
|
{
|
||||||
|
readonly ArcViewStream m_input;
|
||||||
|
readonly long m_length;
|
||||||
|
IList<BundleSegment> m_segments;
|
||||||
|
long m_position;
|
||||||
|
int m_current_segment;
|
||||||
|
byte[] m_buffer;
|
||||||
|
int m_buffer_pos;
|
||||||
|
int m_buffer_len;
|
||||||
|
byte[] m_packed;
|
||||||
|
|
||||||
|
public BundleStream (ArcView file, IList<BundleSegment> segments)
|
||||||
|
{
|
||||||
|
if (null == segments || 0 == segments.Count)
|
||||||
|
throw new ArgumentException ("Segments list is empty.", "segments");
|
||||||
|
m_input = file.CreateStream();
|
||||||
|
m_segments = segments;
|
||||||
|
var last_segment = m_segments[m_segments.Count-1];
|
||||||
|
m_length = last_segment.UnpackedOffset + last_segment.UnpackedSize;
|
||||||
|
m_position = 0;
|
||||||
|
m_current_segment = 0;
|
||||||
|
m_input.Position = m_segments[0].Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead { get { return !m_disposed; } }
|
||||||
|
public override bool CanSeek { get { return !m_disposed; } }
|
||||||
|
public override bool CanWrite { get { return false; } }
|
||||||
|
public override long Length { get { return m_length; } }
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get { return m_position; }
|
||||||
|
set {
|
||||||
|
if (value == m_position)
|
||||||
|
return;
|
||||||
|
if (value < 0)
|
||||||
|
throw new ArgumentOutOfRangeException ("value", "Stream position is out of range.");
|
||||||
|
m_position = value;
|
||||||
|
int segment_index = 0;
|
||||||
|
for (int i = 1; i < m_segments.Count; ++i)
|
||||||
|
{
|
||||||
|
if (m_segments[i].UnpackedOffset > value)
|
||||||
|
break;
|
||||||
|
++segment_index;
|
||||||
|
}
|
||||||
|
var segment = m_segments[segment_index];
|
||||||
|
if (segment_index != m_current_segment)
|
||||||
|
{
|
||||||
|
m_current_segment = segment_index;
|
||||||
|
m_buffer_len = 0;
|
||||||
|
}
|
||||||
|
if (segment.IsCompressed)
|
||||||
|
{
|
||||||
|
m_buffer_pos = (int)(m_position - segment.UnpackedOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_buffer_pos = 0;
|
||||||
|
m_input.Position = segment.Offset + (m_position - segment.UnpackedOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadCompressedSegment (BundleSegment segment)
|
||||||
|
{
|
||||||
|
m_input.Position = segment.Offset;
|
||||||
|
if (null == m_packed || segment.PackedSize > m_packed.Length)
|
||||||
|
m_packed = new byte[segment.PackedSize];
|
||||||
|
int packed_size = m_input.Read (m_packed, 0, (int)segment.PackedSize);
|
||||||
|
if (null == m_buffer || segment.UnpackedSize > m_buffer.Length)
|
||||||
|
m_buffer = new byte[segment.UnpackedSize];
|
||||||
|
if (3 == segment.Compression)
|
||||||
|
m_buffer_len = Lz4Compressor.DecompressBlock (m_packed, packed_size, m_buffer, (int)segment.UnpackedSize);
|
||||||
|
else
|
||||||
|
throw new NotImplementedException ("Not supported Unity asset bundle compression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadFromSegment (BundleSegment segment, byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
Debug.Assert (m_position >= segment.UnpackedOffset && m_position <= segment.UnpackedOffset + segment.UnpackedSize);
|
||||||
|
if (!segment.IsCompressed)
|
||||||
|
{
|
||||||
|
int available = (int)Math.Min (count, (segment.UnpackedOffset + segment.UnpackedSize) - m_position);
|
||||||
|
return m_input.Read (buffer, offset, available);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (0 == m_buffer_len)
|
||||||
|
ReadCompressedSegment (segment);
|
||||||
|
int available = Math.Min (count, m_buffer_len - m_buffer_pos);
|
||||||
|
Buffer.BlockCopy (m_buffer, m_buffer_pos, buffer, offset, available);
|
||||||
|
m_buffer_pos += available;
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read (byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (m_position >= m_length)
|
||||||
|
return 0;
|
||||||
|
int total_read = 0;
|
||||||
|
while (count > 0)
|
||||||
|
{
|
||||||
|
var segment = m_segments[m_current_segment];
|
||||||
|
int read = ReadFromSegment (segment, buffer, offset, count);
|
||||||
|
m_position += read;
|
||||||
|
total_read += read;
|
||||||
|
offset += read;
|
||||||
|
count -= read;
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
if (m_current_segment+1 == m_segments.Count)
|
||||||
|
break;
|
||||||
|
++m_current_segment;
|
||||||
|
m_buffer_len = m_buffer_pos = 0;
|
||||||
|
m_input.Position = m_segments[m_current_segment].Offset;
|
||||||
|
Debug.Assert (m_position == m_segments[m_current_segment].UnpackedOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek (long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
switch (origin)
|
||||||
|
{
|
||||||
|
case SeekOrigin.Current: offset += m_position; break;
|
||||||
|
case SeekOrigin.End: offset += m_length; break;
|
||||||
|
}
|
||||||
|
Position = offset;
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength (long length)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException ("Stream.SetLength method is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write (byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException ("Stream.Write method is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteByte (byte value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException ("Stream.WriteByte method is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool m_disposed = false;
|
||||||
|
protected override void Dispose (bool disposing)
|
||||||
|
{
|
||||||
|
if (!m_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
m_input.Dispose();
|
||||||
|
m_disposed = true;
|
||||||
|
base.Dispose (disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
416
Experimental/Unity/OggStream.cs
Normal file
416
Experimental/Unity/OggStream.cs
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
//! \file OggStream.cs
|
||||||
|
//! \date Sat Apr 08 01:43:58 2017
|
||||||
|
//! \brief libogg partial implementation.
|
||||||
|
//
|
||||||
|
// 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.IO;
|
||||||
|
using System.Text;
|
||||||
|
using GameRes.Utility;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.Vorbis
|
||||||
|
{
|
||||||
|
internal sealed class OggBitStream : IDisposable
|
||||||
|
{
|
||||||
|
LsbBitStream m_input;
|
||||||
|
|
||||||
|
public OggBitStream (OggPacket input)
|
||||||
|
{
|
||||||
|
// certainly an overhead to create a new stream for every packet, but it's so convenient
|
||||||
|
var buf = new MemoryStream (input.Packet);
|
||||||
|
m_input = new LsbBitStream (buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Read <paramref name="count"/> bits from a stream.</summary>
|
||||||
|
/// <returns>-1 if there was not enough bits in a stream</returns>
|
||||||
|
public int ReadBits (int count)
|
||||||
|
{
|
||||||
|
if (count <= 24)
|
||||||
|
return m_input.GetBits (count);
|
||||||
|
else if (count > 32)
|
||||||
|
throw new ArgumentOutOfRangeException ("count", "Attempted to read more than 32 bits from OggBitStream.");
|
||||||
|
int lo = m_input.GetBits (24);
|
||||||
|
return m_input.GetBits (count - 24) << 24 | lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Read 8-bit integer from bitstream.</summary>
|
||||||
|
/// <returns>-1 if there was not enough bits in a stream</returns>
|
||||||
|
public int ReadByte ()
|
||||||
|
{
|
||||||
|
return ReadBits (8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Read 8-bit integer from bitstream.</summary>
|
||||||
|
/// <exception cref="EndOfStreamException">Thrown if there's not enough bits in a stream.</exception>
|
||||||
|
public byte ReadUInt8 ()
|
||||||
|
{
|
||||||
|
int b = ReadBits (8);
|
||||||
|
if (-1 == b)
|
||||||
|
throw new EndOfStreamException();
|
||||||
|
return (byte)b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Read 32-bit integer from bitstream.</summary>
|
||||||
|
/// <exception cref="EndOfStreamException">Thrown if there's not enough bits in a stream.</exception>
|
||||||
|
public int ReadInt32 ()
|
||||||
|
{
|
||||||
|
int lo = ReadBits (16);
|
||||||
|
int hi = ReadBits (16);
|
||||||
|
if (-1 == lo || -1 == hi)
|
||||||
|
throw new EndOfStreamException();
|
||||||
|
return hi << 16 | lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Attempt to read <paramref name="count"/> bytes from stream.</summary>
|
||||||
|
/// <exception cref="EndOfStreamException">Thrown if there's not enough bytes in a bitstream.</exception>
|
||||||
|
public byte[] ReadBytes (int count)
|
||||||
|
{
|
||||||
|
var buf = new byte[count];
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
buf[i] = ReadUInt8();
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool m_disposed = false;
|
||||||
|
public void Dispose ()
|
||||||
|
{
|
||||||
|
if (!m_disposed)
|
||||||
|
{
|
||||||
|
m_input.Dispose();
|
||||||
|
m_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct ogg_packet
|
||||||
|
// https://xiph.org/ogg/doc/libogg/ogg_packet.html
|
||||||
|
internal class OggPacket
|
||||||
|
{
|
||||||
|
public byte[] Packet;
|
||||||
|
public bool BoS;
|
||||||
|
public bool EoS;
|
||||||
|
|
||||||
|
public long GranulePos;
|
||||||
|
public long PacketNo;
|
||||||
|
|
||||||
|
public void SetPacket (long packet_no, byte[] packet)
|
||||||
|
{
|
||||||
|
PacketNo = packet_no;
|
||||||
|
Packet = packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct ogg_stream_state
|
||||||
|
// https://xiph.org/ogg/doc/libogg/ogg_stream_state.html
|
||||||
|
internal class OggStreamState
|
||||||
|
{
|
||||||
|
byte[] BodyData; // bytes from packet bodies
|
||||||
|
int BodyStorage; // storage elements allocated
|
||||||
|
int BodyFill; // elements stored; fill mark
|
||||||
|
int BodyReturned; // elements of fill returned
|
||||||
|
|
||||||
|
int[] LacingVals; // The values that will go to the segment table granulepos values for headers.
|
||||||
|
long[] GranuleVals; // Not compact this way, but it is simple coupled to the lacing fifo.
|
||||||
|
int LacingStorage;
|
||||||
|
int LacingFill;
|
||||||
|
|
||||||
|
byte[] Header; // working space for header encode
|
||||||
|
int HeaderFill;
|
||||||
|
|
||||||
|
bool EoS; // set when we have buffered the last packet in the logical bitstream
|
||||||
|
bool BoS; // set after we've written the initial page of a logical bitstream
|
||||||
|
int SerialNo;
|
||||||
|
int PageNo;
|
||||||
|
long PacketNo; // sequence number for decode; the framing knows where there's a hole in the data,
|
||||||
|
// but we need coupling so that the codec (which is in a seperate abstraction
|
||||||
|
// layer) also knows about the gap
|
||||||
|
long GranulePos;
|
||||||
|
|
||||||
|
// https://xiph.org/ogg/doc/libogg/ogg_stream_init.html
|
||||||
|
public OggStreamState (int serial_no)
|
||||||
|
{
|
||||||
|
BodyStorage = 0x4000;
|
||||||
|
LacingStorage = 0x400;
|
||||||
|
|
||||||
|
BodyData = new byte[BodyStorage];
|
||||||
|
LacingVals = new int[LacingStorage];
|
||||||
|
GranuleVals = new long[LacingStorage];
|
||||||
|
Header = new byte[282];
|
||||||
|
|
||||||
|
SerialNo = serial_no;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear ()
|
||||||
|
{
|
||||||
|
BodyStorage = 0;
|
||||||
|
BodyFill = 0;
|
||||||
|
BodyReturned = 0;
|
||||||
|
LacingStorage = 0;
|
||||||
|
LacingFill = 0;
|
||||||
|
HeaderFill = 0;
|
||||||
|
EoS = false;
|
||||||
|
BoS = false;
|
||||||
|
SerialNo = 0;
|
||||||
|
PageNo = 0;
|
||||||
|
PacketNo = 0;
|
||||||
|
GranulePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PacketIn (OggPacket op)
|
||||||
|
{
|
||||||
|
int bytes = op.Packet.Length;
|
||||||
|
int lacing_vals = bytes / 255 + 1;
|
||||||
|
|
||||||
|
if (BodyReturned > 0)
|
||||||
|
{
|
||||||
|
// advance packet data according to the body_returned pointer.
|
||||||
|
// We had to keep it around to return a pointer into the buffer last call.
|
||||||
|
|
||||||
|
BodyFill -= BodyReturned;
|
||||||
|
if (BodyFill > 0)
|
||||||
|
Buffer.BlockCopy (BodyData, BodyReturned, BodyData, 0, BodyFill);
|
||||||
|
BodyReturned = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we have the buffer storage
|
||||||
|
if(!BodyExpand (bytes) || !LacingExpand (lacing_vals))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Copy in the submitted packet.
|
||||||
|
Buffer.BlockCopy (op.Packet, 0, BodyData, BodyFill, op.Packet.Length);
|
||||||
|
BodyFill += op.Packet.Length;
|
||||||
|
|
||||||
|
// Store lacing vals for this packet
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < lacing_vals-1; ++i)
|
||||||
|
{
|
||||||
|
LacingVals[LacingFill + i] = 0xFF;
|
||||||
|
GranuleVals[LacingFill + i] = GranulePos;
|
||||||
|
}
|
||||||
|
LacingVals[LacingFill + i] = bytes % 0xFF;
|
||||||
|
GranulePos = GranuleVals[LacingFill+i] = GranulePos;
|
||||||
|
|
||||||
|
// flag the first segment as the beginning of the packet
|
||||||
|
LacingVals[LacingFill] |= 0x100;
|
||||||
|
|
||||||
|
LacingFill += lacing_vals;
|
||||||
|
PacketNo++;
|
||||||
|
EoS = op.EoS;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write (Stream output)
|
||||||
|
{
|
||||||
|
var page = new OggPage();
|
||||||
|
while (PageOut (page))
|
||||||
|
{
|
||||||
|
output.Write (page.Header, 0, page.HeaderLength);
|
||||||
|
output.Write (page.Body, page.BodyStart, page.BodyLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Flush (Stream output)
|
||||||
|
{
|
||||||
|
var page = new OggPage();
|
||||||
|
while (Flush (page, true, 0x1000))
|
||||||
|
{
|
||||||
|
output.Write (page.Header, 0, page.HeaderLength);
|
||||||
|
output.Write (page.Body, page.BodyStart, page.BodyLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PageOut (OggPage page)
|
||||||
|
{
|
||||||
|
bool force = EoS && (LacingFill > 0) || (LacingFill > 0 && !BoS);
|
||||||
|
return Flush (page, force, 0x1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BodyExpand (int needed)
|
||||||
|
{
|
||||||
|
if (BodyStorage - needed <= BodyFill)
|
||||||
|
{
|
||||||
|
if (BodyStorage > int.MaxValue - needed)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int body_storage = BodyStorage + needed;
|
||||||
|
if (body_storage < int.MaxValue - 1024)
|
||||||
|
body_storage += 1024;
|
||||||
|
Array.Resize (ref BodyData, body_storage);
|
||||||
|
BodyStorage = body_storage;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LacingExpand (int needed)
|
||||||
|
{
|
||||||
|
if (LacingStorage - needed <= LacingFill)
|
||||||
|
{
|
||||||
|
if (LacingStorage > int.MaxValue - needed)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int lacing_storage = LacingStorage + needed;
|
||||||
|
if (lacing_storage < int.MaxValue - 32)
|
||||||
|
lacing_storage += 32;
|
||||||
|
Array.Resize (ref LacingVals, lacing_storage);
|
||||||
|
Array.Resize (ref GranuleVals, lacing_storage);
|
||||||
|
LacingStorage = lacing_storage;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Flush (OggPage og, bool force, int fill)
|
||||||
|
{
|
||||||
|
int maxvals = Math.Min (LacingFill, 0xFF);
|
||||||
|
if (0 == maxvals)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// construct a page
|
||||||
|
// decide how many segments to include
|
||||||
|
|
||||||
|
int vals = 0;
|
||||||
|
int acc = 0;
|
||||||
|
long granule_pos = -1;
|
||||||
|
|
||||||
|
// If this is the initial header case, the first page must only include
|
||||||
|
// the initial header packet
|
||||||
|
if (!BoS) // 'initial header page' case
|
||||||
|
{
|
||||||
|
granule_pos = 0;
|
||||||
|
for (vals = 0; vals < maxvals; vals++)
|
||||||
|
{
|
||||||
|
if ((LacingVals[vals] & 0xFF) < 0xFF)
|
||||||
|
{
|
||||||
|
vals++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int packets_done = 0;
|
||||||
|
int packet_just_done = 0;
|
||||||
|
for (vals = 0; vals < maxvals; vals++)
|
||||||
|
{
|
||||||
|
if (acc > fill && packet_just_done >= 4)
|
||||||
|
{
|
||||||
|
force = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
acc += LacingVals[vals] & 0xFF;
|
||||||
|
if ((LacingVals[vals] & 0xFF) < 0xFF)
|
||||||
|
{
|
||||||
|
granule_pos = GranuleVals[vals];
|
||||||
|
packet_just_done = ++packets_done;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
packet_just_done = 0;
|
||||||
|
}
|
||||||
|
if (0xFF == vals)
|
||||||
|
force = true;
|
||||||
|
}
|
||||||
|
if (!force)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// construct the header in temp storage
|
||||||
|
Encoding.ASCII.GetBytes ("OggS", 0, 4, Header, 0);
|
||||||
|
|
||||||
|
// stream structure version
|
||||||
|
Header[4] = 0;
|
||||||
|
|
||||||
|
// continued packet flag?
|
||||||
|
Header[5] = 0;
|
||||||
|
if ((LacingVals[0] & 0x100) == 0)
|
||||||
|
Header[5] |= 1;
|
||||||
|
// first page flag?
|
||||||
|
if (!BoS)
|
||||||
|
Header[5] |= 2;
|
||||||
|
// last page flag?
|
||||||
|
if (EoS && LacingFill == vals)
|
||||||
|
Header[5] |= 4;
|
||||||
|
BoS = true;
|
||||||
|
|
||||||
|
// 64 bits of PCM position
|
||||||
|
LittleEndian.Pack (granule_pos, Header, 6);
|
||||||
|
|
||||||
|
// 32 bits of stream serial number
|
||||||
|
LittleEndian.Pack (SerialNo, Header, 14);
|
||||||
|
|
||||||
|
// 32 bits of page counter (we have both counter and page header because this
|
||||||
|
// val can roll over)
|
||||||
|
if (-1 == PageNo)
|
||||||
|
PageNo = 0;
|
||||||
|
LittleEndian.Pack (PageNo, Header, 18);
|
||||||
|
++PageNo;
|
||||||
|
|
||||||
|
int bytes = 0;
|
||||||
|
// segment table
|
||||||
|
Header[26] = (byte)vals;
|
||||||
|
for (int i = 0; i < vals; ++i)
|
||||||
|
bytes += Header[i+27] = (byte)LacingVals[i];
|
||||||
|
|
||||||
|
// set pointers in the ogg_page struct
|
||||||
|
og.Header = Header;
|
||||||
|
og.HeaderLength = HeaderFill = vals + 27;
|
||||||
|
og.Body = BodyData;
|
||||||
|
og.BodyStart = BodyReturned;
|
||||||
|
og.BodyLength = bytes;
|
||||||
|
|
||||||
|
// advance the lacing data and set the body_returned pointer
|
||||||
|
LacingFill -= vals;
|
||||||
|
Array.Copy (LacingVals, vals, LacingVals, 0, LacingFill);
|
||||||
|
Array.Copy (GranuleVals, vals, GranuleVals, 0, LacingFill);
|
||||||
|
BodyReturned += bytes;
|
||||||
|
|
||||||
|
// calculate the checksum
|
||||||
|
og.SetChecksum();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct ogg_page
|
||||||
|
// https://xiph.org/ogg/doc/libogg/ogg_page.html
|
||||||
|
internal class OggPage
|
||||||
|
{
|
||||||
|
public byte[] Header;
|
||||||
|
public int HeaderLength;
|
||||||
|
public byte[] Body;
|
||||||
|
public int BodyStart;
|
||||||
|
public int BodyLength;
|
||||||
|
|
||||||
|
public void SetChecksum ()
|
||||||
|
{
|
||||||
|
Header[22] = Header[23] = Header[24] = Header[25] = 0;
|
||||||
|
|
||||||
|
uint crc = Crc32Normal.UpdateCrc (0, Header, 0, HeaderLength);
|
||||||
|
crc = Crc32Normal.UpdateCrc (crc, Body, BodyStart, BodyLength);
|
||||||
|
|
||||||
|
LittleEndian.Pack (crc, Header, 22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
830
Experimental/Unity/Vorbis.cs
Normal file
830
Experimental/Unity/Vorbis.cs
Normal file
@ -0,0 +1,830 @@
|
|||||||
|
//! \file Vorbis.cs
|
||||||
|
//! \date Fri Apr 07 21:07:30 2017
|
||||||
|
//! \brief partial libvorbis port.
|
||||||
|
//
|
||||||
|
// Only parts crucial for FSB5 decoding got implemented.
|
||||||
|
// Parts that are not used in FSB5 decoder are left out completely or commented out.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// libvorbis Copyright (c) 2002-2008 Xiph.org Foundation
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions
|
||||||
|
// are met:
|
||||||
|
//
|
||||||
|
// - Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// - Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// - Neither the name of the Xiph.org Foundation nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
|
||||||
|
// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.Vorbis
|
||||||
|
{
|
||||||
|
// struct vorbis_info
|
||||||
|
// https://xiph.org/vorbis/doc/libvorbis/vorbis_info.html
|
||||||
|
class VorbisInfo
|
||||||
|
{
|
||||||
|
public int Version;
|
||||||
|
public int Channels;
|
||||||
|
public int Rate;
|
||||||
|
public int BitrateUpper;
|
||||||
|
public int BitrateNominal;
|
||||||
|
public int BitrateLower;
|
||||||
|
|
||||||
|
public CodecSetupInfo CodecSetup = new CodecSetupInfo();
|
||||||
|
|
||||||
|
const int TransormB = 1;
|
||||||
|
const int WindowB = 1;
|
||||||
|
const int TimeB = 1;
|
||||||
|
const int FloorB = 2;
|
||||||
|
const int ResB = 3;
|
||||||
|
const int MapB = 1;
|
||||||
|
|
||||||
|
Func<OggBitStream, VorbisInfoFloor>[] FloorMethods;
|
||||||
|
|
||||||
|
public VorbisInfo ()
|
||||||
|
{
|
||||||
|
FloorMethods = new Func<OggBitStream, VorbisInfoFloor>[] {
|
||||||
|
UnpackFloor0,
|
||||||
|
UnpackFloor1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://xiph.org/vorbis/doc/libvorbis/vorbis_synthesis_headerin.html
|
||||||
|
public void SynthesisHeaderin (VorbisComment vc, OggPacket op)
|
||||||
|
{
|
||||||
|
using (var input = new OggBitStream (op))
|
||||||
|
{
|
||||||
|
int packtype = input.ReadUInt8();
|
||||||
|
var buf = input.ReadBytes (6);
|
||||||
|
if (!buf.AsciiEqual ("vorbis"))
|
||||||
|
throw new InvalidDataException ("Not an Ogg/Vorbis stream.");
|
||||||
|
switch (packtype)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
if (!op.BoS)
|
||||||
|
throw InvalidHeader();
|
||||||
|
if (Rate != 0)
|
||||||
|
throw InvalidHeader();
|
||||||
|
UnpackInfo (input);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
if (0 == Rate)
|
||||||
|
throw InvalidHeader();
|
||||||
|
vc.UnpackComment (input);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
if (0 == Rate || null == vc.Vendor)
|
||||||
|
throw InvalidHeader();
|
||||||
|
UnpackBooks (input);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw InvalidHeader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static int iLog (uint num)
|
||||||
|
{
|
||||||
|
int bits = 0;
|
||||||
|
while (num != 0)
|
||||||
|
{
|
||||||
|
++bits;
|
||||||
|
num >>= 1;
|
||||||
|
}
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static int CountBits (uint num)
|
||||||
|
{
|
||||||
|
int bits = 0;
|
||||||
|
if (num != 0)
|
||||||
|
--num;
|
||||||
|
while (num != 0)
|
||||||
|
{
|
||||||
|
++bits;
|
||||||
|
num >>= 1;
|
||||||
|
}
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Count number of bits set in <paramref name="num"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal static int CountSetBits (uint num)
|
||||||
|
{
|
||||||
|
int bits = 0;
|
||||||
|
while (num != 0)
|
||||||
|
{
|
||||||
|
bits += (int)num & 1;
|
||||||
|
num >>= 1;
|
||||||
|
}
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.xiph.org/vorbis/doc/libvorbis/vorbis_packet_blocksize.html
|
||||||
|
public int PacketBlockSize (OggPacket op)
|
||||||
|
{
|
||||||
|
using (var input = new OggBitStream (op))
|
||||||
|
{
|
||||||
|
// Check the packet type
|
||||||
|
if (input.ReadBits (1) != 0)
|
||||||
|
throw new InvalidDataException ("Not an audio data packet.");
|
||||||
|
|
||||||
|
int modebits = 0;
|
||||||
|
for (int v = CodecSetup.Modes; v > 1; v >>= 1)
|
||||||
|
{
|
||||||
|
modebits++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read our mode and pre/post windowsize
|
||||||
|
int mode = input.ReadBits (modebits);
|
||||||
|
|
||||||
|
if (-1 == mode)
|
||||||
|
throw new InvalidDataException ("Invalid Ogg/Vorbis packet.");
|
||||||
|
|
||||||
|
return CodecSetup.BlockSizes[CodecSetup.ModeParam[mode].BlockFlag];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnpackInfo (OggBitStream input)
|
||||||
|
{
|
||||||
|
Version = input.ReadInt32();
|
||||||
|
if (Version != 0)
|
||||||
|
throw new InvalidDataException ("Invalid Vorbis encoder version.");
|
||||||
|
Channels = input.ReadUInt8();
|
||||||
|
Rate = input.ReadInt32();
|
||||||
|
|
||||||
|
BitrateUpper = input.ReadInt32();
|
||||||
|
BitrateNominal = input.ReadInt32();
|
||||||
|
BitrateLower = input.ReadInt32();
|
||||||
|
|
||||||
|
CodecSetup.BlockSizes[0] = 1 << input.ReadBits (4);
|
||||||
|
CodecSetup.BlockSizes[1] = 1 << input.ReadBits (4);
|
||||||
|
|
||||||
|
if (input.ReadBits (1) != 1)
|
||||||
|
throw InvalidHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnpackBooks (OggBitStream input)
|
||||||
|
{
|
||||||
|
// codebooks
|
||||||
|
CodecSetup.Books = input.ReadUInt8() + 1;
|
||||||
|
if (CodecSetup.Books <= 0)
|
||||||
|
throw InvalidHeader();
|
||||||
|
|
||||||
|
for (int i = 0; i < CodecSetup.Books; ++i)
|
||||||
|
{
|
||||||
|
var param = StaticBookUnpack (input);
|
||||||
|
if (null == param)
|
||||||
|
throw InvalidHeader();
|
||||||
|
CodecSetup.BookParam[i] = param;
|
||||||
|
}
|
||||||
|
|
||||||
|
// time backend settings; hooks are unused
|
||||||
|
int times = input.ReadBits (6) + 1;
|
||||||
|
if (times <= 0)
|
||||||
|
throw InvalidHeader();
|
||||||
|
for (int i = 0; i < times; ++i)
|
||||||
|
{
|
||||||
|
int test = input.ReadBits (16);
|
||||||
|
if (test < 0 || test >= TimeB)
|
||||||
|
throw InvalidHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// floor backend settings
|
||||||
|
CodecSetup.Floors = input.ReadBits (6) + 1;
|
||||||
|
if (CodecSetup.Floors <= 0)
|
||||||
|
throw InvalidHeader();
|
||||||
|
for (int i = 0; i < CodecSetup.Floors; i++)
|
||||||
|
{
|
||||||
|
int floor_type = input.ReadBits (16);
|
||||||
|
if (floor_type < 0 || floor_type >= FloorB)
|
||||||
|
throw InvalidHeader();
|
||||||
|
CodecSetup.FloorType[i] = floor_type;
|
||||||
|
var param = FloorMethods[floor_type] (input);
|
||||||
|
if (null == param)
|
||||||
|
throw InvalidHeader();
|
||||||
|
CodecSetup.FloorParam[i] = param;
|
||||||
|
}
|
||||||
|
|
||||||
|
// residue backend settings
|
||||||
|
CodecSetup.Residues = input.ReadBits (6) + 1;
|
||||||
|
if (CodecSetup.Residues <= 0)
|
||||||
|
throw InvalidHeader();
|
||||||
|
for (int i = 0; i < CodecSetup.Residues; ++i)
|
||||||
|
{
|
||||||
|
int residue_type = input.ReadBits (16);
|
||||||
|
if (residue_type < 0 || residue_type >= ResB)
|
||||||
|
throw InvalidHeader();
|
||||||
|
CodecSetup.ResidueType[i] = residue_type;
|
||||||
|
var param = UnpackResidue (input);
|
||||||
|
if (null == param)
|
||||||
|
throw InvalidHeader();
|
||||||
|
CodecSetup.ResidueParam[i] = param;
|
||||||
|
}
|
||||||
|
|
||||||
|
// map backend settings
|
||||||
|
CodecSetup.Maps = input.ReadBits (6) + 1;
|
||||||
|
if (CodecSetup.Maps <= 0)
|
||||||
|
throw InvalidHeader();
|
||||||
|
for (int i = 0; i < CodecSetup.Maps; ++i)
|
||||||
|
{
|
||||||
|
int map_type = input.ReadBits (16);
|
||||||
|
if (map_type < 0 || map_type >= MapB)
|
||||||
|
throw InvalidHeader();
|
||||||
|
CodecSetup.MapType[i] = map_type;
|
||||||
|
var param = UnpackMapping (input);
|
||||||
|
if (null == param)
|
||||||
|
throw InvalidHeader();
|
||||||
|
CodecSetup.MapParam[i] = param;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode settings
|
||||||
|
CodecSetup.Modes = input.ReadBits (6) + 1;
|
||||||
|
if (CodecSetup.Modes <= 0)
|
||||||
|
throw InvalidHeader();
|
||||||
|
for (int i = 0; i < CodecSetup.Modes; ++i)
|
||||||
|
{
|
||||||
|
CodecSetup.ModeParam[i].BlockFlag = input.ReadBits (1);
|
||||||
|
CodecSetup.ModeParam[i].WindowType = input.ReadBits (16);
|
||||||
|
CodecSetup.ModeParam[i].TransformType = input.ReadBits (16);
|
||||||
|
CodecSetup.ModeParam[i].Mapping = input.ReadBits (8);
|
||||||
|
|
||||||
|
if (CodecSetup.ModeParam[i].WindowType >= WindowB ||
|
||||||
|
CodecSetup.ModeParam[i].TransformType >= WindowB ||
|
||||||
|
CodecSetup.ModeParam[i].Mapping >= CodecSetup.Maps ||
|
||||||
|
CodecSetup.ModeParam[i].Mapping < 0)
|
||||||
|
throw InvalidHeader();
|
||||||
|
}
|
||||||
|
if (input.ReadBits (1) != 1)
|
||||||
|
throw InvalidHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticCodebook StaticBookUnpack (OggBitStream input)
|
||||||
|
{
|
||||||
|
// make sure alignment is correct
|
||||||
|
if (input.ReadBits (24) != 0x564342)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var s = new StaticCodebook();
|
||||||
|
|
||||||
|
// first the basic parameters
|
||||||
|
s.dim = input.ReadBits (16);
|
||||||
|
s.entries = input.ReadBits (24);
|
||||||
|
if (-1 == s.entries)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (iLog ((uint)s.dim) + iLog ((uint)s.entries) > 24)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// codeword ordering.... length ordered or unordered?
|
||||||
|
switch (input.ReadBits (1))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
// allocated but unused entries?
|
||||||
|
int unused = input.ReadBits (1);
|
||||||
|
// unordered
|
||||||
|
s.lengthlist = new byte[s.entries];
|
||||||
|
|
||||||
|
// allocated but unused entries?
|
||||||
|
if (unused > 0)
|
||||||
|
{
|
||||||
|
// yes, unused entries
|
||||||
|
for(int i = 0; i < s.entries; ++i)
|
||||||
|
{
|
||||||
|
if (input.ReadBits (1) > 0)
|
||||||
|
{
|
||||||
|
int num = input.ReadBits (5);
|
||||||
|
if (-1 == num)
|
||||||
|
return null;
|
||||||
|
s.lengthlist[i] = (byte)(num+1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
s.lengthlist[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// all entries used; no tagging
|
||||||
|
for (int i = 0; i < s.entries; ++i)
|
||||||
|
{
|
||||||
|
int num = input.ReadBits (5);
|
||||||
|
if (-1 == num)
|
||||||
|
return null;
|
||||||
|
s.lengthlist[i] = (byte)(num+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // ordered
|
||||||
|
int length = input.ReadBits (5) + 1;
|
||||||
|
if (0 == length)
|
||||||
|
return null;
|
||||||
|
s.lengthlist = new byte[s.entries];
|
||||||
|
|
||||||
|
for (int i = 0; i < s.entries; )
|
||||||
|
{
|
||||||
|
int num = input.ReadBits (iLog ((uint)(s.entries-i)));
|
||||||
|
if (-1 == num || length > 32 || num > s.entries-i
|
||||||
|
|| (num > 0 && ((num-1) >> (length-1)) > 1))
|
||||||
|
return null;
|
||||||
|
for (int j = 0; j < num; ++j, ++i)
|
||||||
|
s.lengthlist[i] = (byte)length;
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have a mapping to unpack?
|
||||||
|
switch((s.maptype = input.ReadBits (4)))
|
||||||
|
{
|
||||||
|
case 0: // no mapping
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: case 2:
|
||||||
|
// implicitly populated value mapping
|
||||||
|
// explicitly populated value mapping
|
||||||
|
|
||||||
|
s.q_min = input.ReadInt32();
|
||||||
|
s.q_delta = input.ReadInt32();
|
||||||
|
s.q_quant = input.ReadBits (4) + 1;
|
||||||
|
s.q_sequencep = input.ReadBits (1);
|
||||||
|
if (-1 == s.q_sequencep)
|
||||||
|
return null;
|
||||||
|
int quantvals = 0;
|
||||||
|
switch (s.maptype)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
quantvals = s.dim == 0 ? 0 : s.Maptype1Quantvals();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
quantvals = s.entries * s.dim;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// quantized values
|
||||||
|
s.quantlist = new int[quantvals];
|
||||||
|
for (int i = 0; i < quantvals; ++i)
|
||||||
|
s.quantlist[i] = input.ReadBits (s.q_quant);
|
||||||
|
|
||||||
|
if (quantvals > 0 && s.quantlist[quantvals-1] == -1)
|
||||||
|
return null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // EOF
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all set
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
VorbisInfoFloor UnpackFloor0 (OggBitStream input)
|
||||||
|
{
|
||||||
|
var info = new VorbisInfoFloor();
|
||||||
|
int order = input.ReadBits (8);
|
||||||
|
int rate = input.ReadBits (16);
|
||||||
|
int barkmap = input.ReadBits (16);
|
||||||
|
int ampbits = input.ReadBits (6);
|
||||||
|
int ampdB = input.ReadBits (8);
|
||||||
|
int numbooks = input.ReadBits (4) + 1;
|
||||||
|
|
||||||
|
if (order < 1 || rate < 1 || barkmap < 1 || numbooks < 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (int j = 0; j < numbooks; ++j)
|
||||||
|
{
|
||||||
|
int books = input.ReadByte();
|
||||||
|
if (books < 0 || books >= CodecSetup.Books)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
VorbisInfoFloor UnpackFloor1 (OggBitStream input)
|
||||||
|
{
|
||||||
|
int max_class = -1;
|
||||||
|
|
||||||
|
var info = new VorbisInfoFloor();
|
||||||
|
// read partitions
|
||||||
|
int partitions = input.ReadBits (5); // only 0 to 31 legal
|
||||||
|
var partition_class = new int[partitions];
|
||||||
|
for (int j = 0; j < partitions; ++j)
|
||||||
|
{
|
||||||
|
partition_class[j] = input.ReadBits (4); // only 0 to 15 legal
|
||||||
|
if (partition_class[j] < 0)
|
||||||
|
return null;
|
||||||
|
if (max_class < partition_class[j])
|
||||||
|
max_class = partition_class[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// read partition classes
|
||||||
|
var class_dim = new int[max_class+1];
|
||||||
|
for (int j = 0; j < max_class+1; ++j)
|
||||||
|
{
|
||||||
|
class_dim[j] = input.ReadBits (3) + 1; // 1 to 8
|
||||||
|
int class_subs = input.ReadBits (2); // 0,1,2,3 bits
|
||||||
|
if (class_subs < 0)
|
||||||
|
return null;
|
||||||
|
if (class_subs > 0)
|
||||||
|
input.ReadBits (8); // class_book
|
||||||
|
for (int k = 0; k < (1 << class_subs); ++k)
|
||||||
|
{
|
||||||
|
int class_subbook = input.ReadBits (8) - 1; // info.class_subbook[j][k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the post list
|
||||||
|
int mult = input.ReadBits (2) + 1; // only 1,2,3,4 legal now
|
||||||
|
int rangebits = input.ReadBits (4);
|
||||||
|
if (rangebits < 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
// var postlist = new int[VorbisInfoFloor.Posit + 2];
|
||||||
|
for (int j = 0, k = 0; j < partitions; ++j)
|
||||||
|
{
|
||||||
|
count += class_dim[partition_class[j]];
|
||||||
|
if (count > VorbisInfoFloor.Posit)
|
||||||
|
return null;
|
||||||
|
for (; k < count; ++k)
|
||||||
|
{
|
||||||
|
int t = input.ReadBits (rangebits);
|
||||||
|
if (t < 0 || t >= (1 << rangebits))
|
||||||
|
return null;
|
||||||
|
// postlist[k+2] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// postlist[0] = 0;
|
||||||
|
// postlist[1] = 1<<rangebits;
|
||||||
|
|
||||||
|
// don't allow repeated values in post list as they'd result in
|
||||||
|
// zero-length segments
|
||||||
|
/*
|
||||||
|
var indices = Enumerable.Range (0, count+2).OrderBy (i => postlist[i]).ToArray();
|
||||||
|
for (int j = 1; j < count+2; j++)
|
||||||
|
if(postlist[indices[j-1]] == postlist[indices[j]])
|
||||||
|
return null;
|
||||||
|
*/
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
object UnpackResidue (OggBitStream input)
|
||||||
|
{
|
||||||
|
var info = new VorbisInfoResidue();
|
||||||
|
|
||||||
|
info.begin = input.ReadBits (24);
|
||||||
|
info.end = input.ReadBits (24);
|
||||||
|
info.grouping = input.ReadBits (24) + 1;
|
||||||
|
info.partitions = input.ReadBits (6) + 1;
|
||||||
|
info.groupbook = input.ReadBits (8);
|
||||||
|
|
||||||
|
// check for premature EOP
|
||||||
|
if (info.groupbook < 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
int acc = 0;
|
||||||
|
for (int j = 0; j < info.partitions; ++j)
|
||||||
|
{
|
||||||
|
int cascade = input.ReadBits (3);
|
||||||
|
int cflag = input.ReadBits (1);
|
||||||
|
if (cflag < 0)
|
||||||
|
return null;
|
||||||
|
if (cflag > 0)
|
||||||
|
{
|
||||||
|
int c = input.ReadBits (5);
|
||||||
|
if (c < 0)
|
||||||
|
return null;
|
||||||
|
cascade |= c << 3;
|
||||||
|
}
|
||||||
|
// info.secondstages[j] = cascade;
|
||||||
|
|
||||||
|
acc += CountSetBits ((uint)cascade);
|
||||||
|
}
|
||||||
|
for (int j = 0; j < acc; ++j)
|
||||||
|
{
|
||||||
|
int book = input.ReadBits (8);
|
||||||
|
if (book < 0)
|
||||||
|
return null;
|
||||||
|
if (book >= CodecSetup.Books)
|
||||||
|
return null;
|
||||||
|
// if (CodecSetup.book_param[book].maptype == 0) return null;
|
||||||
|
// info.booklist[j] = book;
|
||||||
|
}
|
||||||
|
if (info.groupbook >= CodecSetup.Books)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
/*
|
||||||
|
int entries = CodecSetup.book_param[info.groupbook].entries;
|
||||||
|
int dim = CodecSetup.book_param[info.groupbook].dim;
|
||||||
|
int partvals = 1;
|
||||||
|
if (dim < 1)
|
||||||
|
return null;
|
||||||
|
while (dim > 0)
|
||||||
|
{
|
||||||
|
partvals *= info.partitions;
|
||||||
|
if (partvals > entries)
|
||||||
|
return null;
|
||||||
|
dim--;
|
||||||
|
}
|
||||||
|
info.partvals = partvals;
|
||||||
|
*/
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
VorbisInfoMapping UnpackMapping (OggBitStream input)
|
||||||
|
{
|
||||||
|
var info = new VorbisInfoMapping();
|
||||||
|
|
||||||
|
int b = input.ReadBits (1);
|
||||||
|
if (b < 0)
|
||||||
|
return null;
|
||||||
|
if (b > 0)
|
||||||
|
{
|
||||||
|
info.submaps = input.ReadBits (4) + 1;
|
||||||
|
if (info.submaps <= 0)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
info.submaps = 1;
|
||||||
|
|
||||||
|
b = input.ReadBits (1);
|
||||||
|
if (b < 0)
|
||||||
|
return null;
|
||||||
|
if (b > 0)
|
||||||
|
{
|
||||||
|
info.coupling_steps = input.ReadBits (8) + 1;
|
||||||
|
if (info.coupling_steps <= 0)
|
||||||
|
return null;
|
||||||
|
for (int i = 0; i < info.coupling_steps; ++i)
|
||||||
|
{
|
||||||
|
int bits = CountBits ((uint)Channels);
|
||||||
|
int testM = input.ReadBits (bits);
|
||||||
|
int testA = input.ReadBits (bits);
|
||||||
|
if (testM < 0 || testA < 0 || testM == testA
|
||||||
|
|| testM >= Channels || testA >= Channels)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.ReadBits (2) != 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (info.submaps > 1)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Channels; ++i)
|
||||||
|
{
|
||||||
|
int chmuxlist = input.ReadBits (4);
|
||||||
|
if (chmuxlist >= info.submaps || chmuxlist < 0)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < info.submaps; ++i)
|
||||||
|
{
|
||||||
|
input.ReadByte(); // time submap unused
|
||||||
|
int floorsubmap = input.ReadByte();
|
||||||
|
if (floorsubmap >= CodecSetup.Floors || floorsubmap < 0)
|
||||||
|
return null;
|
||||||
|
int residuesubmap = input.ReadByte();
|
||||||
|
if (residuesubmap >= CodecSetup.Residues || residuesubmap < 0)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static InvalidDataException InvalidHeader ()
|
||||||
|
{
|
||||||
|
return new InvalidDataException ("Invalid header in Ogg/Vorbis stream.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct vorbis_comment
|
||||||
|
// https://xiph.org/vorbis/doc/libvorbis/vorbis_comment.html
|
||||||
|
class VorbisComment
|
||||||
|
{
|
||||||
|
public List<byte[]> Comments;
|
||||||
|
public byte[] Vendor;
|
||||||
|
|
||||||
|
internal static readonly byte[] EncodeVendorString = Encoding.UTF8.GetBytes ("mørkt GARbro 20170407");
|
||||||
|
|
||||||
|
public VorbisComment ()
|
||||||
|
{
|
||||||
|
Comments = new List<byte[]>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://xiph.org/vorbis/doc/libvorbis/vorbis_commentheader_out.html
|
||||||
|
public void HeaderOut (OggPacket packet)
|
||||||
|
{
|
||||||
|
using (var buf = new MemoryStream())
|
||||||
|
using (var output = new BinaryWriter (buf))
|
||||||
|
{
|
||||||
|
// preamble
|
||||||
|
output.Write ((byte)3);
|
||||||
|
output.Write ("vorbis".ToCharArray());
|
||||||
|
|
||||||
|
// vendor
|
||||||
|
output.Write (EncodeVendorString.Length);
|
||||||
|
output.Write (EncodeVendorString);
|
||||||
|
|
||||||
|
// comments
|
||||||
|
output.Write (Comments.Count);
|
||||||
|
foreach (var comment in Comments)
|
||||||
|
{
|
||||||
|
if (comment != null && comment.Length > 0)
|
||||||
|
{
|
||||||
|
output.Write (comment.Length);
|
||||||
|
output.Write (comment);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
output.Write (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.Write ((byte)1);
|
||||||
|
output.Flush();
|
||||||
|
|
||||||
|
packet.SetPacket (1, buf.ToArray());
|
||||||
|
packet.BoS = false;
|
||||||
|
packet.EoS = false;
|
||||||
|
packet.GranulePos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void UnpackComment (OggBitStream input)
|
||||||
|
{
|
||||||
|
int vendor_len = input.ReadInt32();
|
||||||
|
if (vendor_len < 0)
|
||||||
|
throw VorbisInfo.InvalidHeader();
|
||||||
|
var vendor = input.ReadBytes (vendor_len);
|
||||||
|
|
||||||
|
int count = input.ReadInt32();
|
||||||
|
if (count < 0)
|
||||||
|
throw VorbisInfo.InvalidHeader();
|
||||||
|
|
||||||
|
var comments = new List<byte[]> (count);
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
int len = input.ReadInt32();
|
||||||
|
if (len < 0)
|
||||||
|
throw VorbisInfo.InvalidHeader();
|
||||||
|
var bytes = input.ReadBytes (len);
|
||||||
|
comments.Add (bytes);
|
||||||
|
}
|
||||||
|
if (input.ReadBits (1) != 1)
|
||||||
|
throw VorbisInfo.InvalidHeader();
|
||||||
|
|
||||||
|
this.Vendor = vendor;
|
||||||
|
this.Comments = comments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// codec_setup_info
|
||||||
|
class CodecSetupInfo
|
||||||
|
{
|
||||||
|
public int[] BlockSizes = new int[2];
|
||||||
|
|
||||||
|
public int Modes;
|
||||||
|
public int Maps;
|
||||||
|
public int Floors;
|
||||||
|
public int Residues;
|
||||||
|
public int Books;
|
||||||
|
|
||||||
|
internal VorbisInfoMode[] ModeParam = new VorbisInfoMode[64];
|
||||||
|
internal int[] ResidueType = new int[64];
|
||||||
|
internal object[] ResidueParam = new object[64];
|
||||||
|
internal int[] FloorType = new int[64];
|
||||||
|
internal object[] FloorParam = new object[64];
|
||||||
|
internal int[] MapType = new int[64];
|
||||||
|
internal VorbisInfoMapping[] MapParam = new VorbisInfoMapping[64];
|
||||||
|
internal StaticCodebook[] BookParam = new StaticCodebook[256];
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct vorbis_info_mode
|
||||||
|
struct VorbisInfoMode
|
||||||
|
{
|
||||||
|
public int BlockFlag;
|
||||||
|
public int WindowType;
|
||||||
|
public int TransformType;
|
||||||
|
public int Mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct static_codebook
|
||||||
|
class StaticCodebook
|
||||||
|
{
|
||||||
|
public int dim; // codebook dimensions (elements per vector)
|
||||||
|
public int entries; // codebook entries
|
||||||
|
public byte[] lengthlist; // codeword lengths in bits
|
||||||
|
|
||||||
|
// mapping ***************************************************************
|
||||||
|
public int maptype; // 0=none
|
||||||
|
// 1=implicitly populated values from map column
|
||||||
|
// 2=listed arbitrary values
|
||||||
|
|
||||||
|
// The below does a linear, single monotonic sequence mapping.
|
||||||
|
public int q_min; // packed 32 bit float; quant value 0 maps to minval
|
||||||
|
public int q_delta; // packed 32 bit float; val 1 - val 0 == delta
|
||||||
|
public int q_quant; // bits: 0 < quant <= 16
|
||||||
|
public int q_sequencep; // bitflag
|
||||||
|
|
||||||
|
public int[] quantlist; // map == 1: (int)(entries^(1/dim)) element column map
|
||||||
|
// map == 2: list of dim*entries quantized entry vals
|
||||||
|
|
||||||
|
internal int Maptype1Quantvals ()
|
||||||
|
{
|
||||||
|
int vals = (int)Math.Floor (Math.Pow ((float)entries, 1.0f / dim));
|
||||||
|
|
||||||
|
// the above *should* be reliable, but we'll not assume that FP is
|
||||||
|
// ever reliable when bitstream sync is at stake; verify via integer
|
||||||
|
// means that vals really is the greatest value of dim for which
|
||||||
|
// vals^b->bim <= b->entries.
|
||||||
|
// treat the above as an initial guess
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int acc = 1;
|
||||||
|
int acc1 = 1;
|
||||||
|
for (int i = 0; i < dim; ++i)
|
||||||
|
{
|
||||||
|
acc *= vals;
|
||||||
|
acc1 *= vals+1;
|
||||||
|
}
|
||||||
|
if (acc <= entries && acc1 > entries)
|
||||||
|
break;
|
||||||
|
if (acc > entries)
|
||||||
|
vals--;
|
||||||
|
else
|
||||||
|
vals++;
|
||||||
|
}
|
||||||
|
return vals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VorbisInfoMapping
|
||||||
|
{
|
||||||
|
public int submaps;
|
||||||
|
public int coupling_steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
class VorbisInfoFloor
|
||||||
|
{
|
||||||
|
public const int Posit = 63;
|
||||||
|
}
|
||||||
|
|
||||||
|
class VorbisInfoResidue
|
||||||
|
{
|
||||||
|
public int begin;
|
||||||
|
public int end;
|
||||||
|
public int grouping;
|
||||||
|
public int partitions;
|
||||||
|
public int groupbook;
|
||||||
|
}
|
||||||
|
}
|
BIN
Experimental/Unity/strings.dat
Normal file
BIN
Experimental/Unity/strings.dat
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user