Added audio.bag/audio.idx support used in RA2

This commit is contained in:
Benno van den Bogaard
2015-03-01 19:33:51 +01:00
parent bec56ee5b6
commit 8cf7c46c8f
6 changed files with 452 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using OpenRA.FileFormats;
using OpenRA.Primitives;
namespace OpenRA.FileSystem
{
public sealed class BagFile : IFolder, IDisposable
{
static readonly uint[] Nothing = { };
readonly string bagFilename;
readonly Stream s;
readonly int bagFilePriority;
readonly Dictionary<uint, IdxEntry> index;
public BagFile(string filename, int priority)
{
bagFilename = filename;
bagFilePriority = priority;
// A bag file is always accompanied with an .idx counterpart
// For example: audio.bag requires the audio.idx file
var indexFilename = Path.ChangeExtension(filename, ".idx");
s = GlobalFileSystem.Open(filename);
// Build the index and dispose the stream, it is no longer needed after this
List<IdxEntry> entries;
using (var indexStream = GlobalFileSystem.Open(indexFilename))
{
var reader = new IdxReader(indexStream);
entries = reader.Entries;
}
index = entries.ToDictionaryWithConflictLog(x => x.Hash,
"{0} (bag format)".F(filename),
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
}
public int Priority { get { return 1000 + bagFilePriority; } }
public string Name { get { return bagFilename; } }
public Stream GetContent(uint hash)
{
IdxEntry entry;
if (!index.TryGetValue(hash, out entry))
return null;
s.Seek(entry.Offset, SeekOrigin.Begin);
var waveHeaderMemoryStream = new MemoryStream();
var channels = (entry.Flags & 1) > 0 ? 2 : 1;
if ((entry.Flags & 2) > 0)
{
// PCM
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 36));
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(16));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavLoader.WaveType.Pcm));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(2 * channels * entry.SampleRate));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)(2 * channels)));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)16));
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
}
if ((entry.Flags & 8) > 0)
{
// IMA ADPCM
var samplesPerChunk = (2 * (entry.ChunkSize - 4)) + 1;
var bytesPerSec = (int)Math.Floor(((double)(2 * entry.ChunkSize) / samplesPerChunk) * ((double)entry.SampleRate / 2));
var chunkSize = entry.ChunkSize > entry.Length ? entry.Length : entry.ChunkSize;
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 52));
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(20));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavLoader.WaveType.ImaAdpcm));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(bytesPerSec));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)chunkSize));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)4));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)2));
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)samplesPerChunk));
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fact"));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4 * entry.Length));
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
}
waveHeaderMemoryStream.Seek(0, SeekOrigin.Begin);
// Construct a merged stream
var mergedStream = new MergedStream(waveHeaderMemoryStream, s);
mergedStream.SetLength(waveHeaderMemoryStream.Length + entry.Length);
return mergedStream;
}
uint? FindMatchingHash(string filename)
{
var hash = IdxEntry.HashFilename(filename, PackageHashType.CRC32);
if (index.ContainsKey(hash))
return hash;
// Maybe we were given a raw hash?
uint raw;
if (!uint.TryParse(filename, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out raw))
return null;
if ("{0:X}".F(raw) == filename && index.ContainsKey(raw))
return raw;
return null;
}
public Stream GetContent(string filename)
{
var hash = FindMatchingHash(filename);
return hash.HasValue ? GetContent(hash.Value) : null;
}
public bool Exists(string filename)
{
return FindMatchingHash(filename).HasValue;
}
public IEnumerable<uint> ClassicHashes()
{
return Nothing;
}
public IEnumerable<uint> CrcHashes()
{
return index.Keys;
}
public IEnumerable<string> AllFileNames()
{
var lookup = new Dictionary<uint, string>();
if (GlobalFileSystem.Exists("global mix database.dat"))
{
var db = new XccGlobalDatabase(GlobalFileSystem.Open("global mix database.dat"));
foreach (var e in db.Entries)
{
var hash = IdxEntry.HashFilename(e, PackageHashType.CRC32);
if (!lookup.ContainsKey(hash))
lookup.Add(hash, e);
}
}
return index.Keys.Select(k => lookup.ContainsKey(k) ? lookup[k] : "{0:X}".F(k));
}
public void Write(Dictionary<string, byte[]> contents)
{
GlobalFileSystem.Unmount(this);
throw new NotImplementedException("Updating bag files unsupported");
}
public void Dispose()
{
if (s != null)
s.Dispose();
}
}
}

View File

@@ -101,6 +101,8 @@ namespace OpenRA.FileSystem
return new PakFile(filename, order);
if (filename.EndsWith(".big", StringComparison.InvariantCultureIgnoreCase))
return new BigFile(filename, order);
if (filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
return new BagFile(filename, order);
return new Folder(filename, order);
}

View File

@@ -0,0 +1,94 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.IO;
namespace OpenRA.FileSystem
{
public class IdxEntry
{
public const string DefaultExtension = "wav";
public readonly uint Hash;
public readonly string Name;
public readonly string Extension;
public readonly uint Offset;
public readonly uint Length;
public readonly uint SampleRate;
public readonly uint Flags;
public readonly uint ChunkSize;
public IdxEntry(uint hash, uint offset, uint length, uint sampleRate, uint flags, uint chuckSize)
{
Hash = hash;
Offset = offset;
Length = length;
SampleRate = sampleRate;
Flags = flags;
ChunkSize = chuckSize;
}
public IdxEntry(Stream s)
{
var asciiname = s.ReadASCII(16);
var pos = asciiname.IndexOf('\0');
if (pos != 0)
asciiname = asciiname.Substring(0, pos);
Name = asciiname;
Extension = DefaultExtension;
Offset = s.ReadUInt32();
Length = s.ReadUInt32();
SampleRate = s.ReadUInt32();
Flags = s.ReadUInt32();
ChunkSize = s.ReadUInt32();
Hash = HashFilename(string.Concat(Name, ".", Extension), PackageHashType.CRC32);
}
public void Write(BinaryWriter w)
{
w.Write(Name.PadRight(16, '\0'));
w.Write(Offset);
w.Write(Length);
w.Write(SampleRate);
w.Write(Flags);
w.Write(ChunkSize);
}
public override string ToString()
{
string filename;
if (names.TryGetValue(Hash, out filename))
return "{0} - offset 0x{1:x8} - length 0x{2:x8}".F(filename, Offset, Length);
else
return "0x{0:x8} - offset 0x{1:x8} - length 0x{2:x8}".F(Hash, Offset, Length);
}
public static uint HashFilename(string name, PackageHashType type)
{
return PackageEntry.HashFilename(name, type);
}
static Dictionary<uint, string> names = new Dictionary<uint, string>();
public static void AddStandardName(string s)
{
// RA1 and TD
var hash = HashFilename(s, PackageHashType.Classic);
names.Add(hash, s);
// TS
var crcHash = HashFilename(s, PackageHashType.CRC32);
names.Add(crcHash, s);
}
}
}