BizHawk/BizHawk.Emulation.DiscSystem/Blobs/RiffMaster.cs

344 lines
8.4 KiB
C#

using System;
using System.IO;
using System.Collections.Generic;
using BizHawk.Common;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Parses a RIFF file into a live data structure.
/// References to large blobs remain mostly on disk in the file which RiffMaster keeps a reference too. Dispose it to close the file.
/// You can modify blobs however you want and write the file back out to a new path, if youre careful (that was the original point of this)
/// Please be sure to test round-tripping when you make any changes. This architecture is a bit tricky to use, but it works if youre careful.
/// </summary>
class RiffMaster : IDisposable
{
public RiffMaster() { }
public void WriteFile(string fname)
{
using (FileStream fs = new FileStream(fname, FileMode.Create, FileAccess.Write, FileShare.Read))
{
WriteStream(fs);
}
}
public Stream BaseStream;
public void LoadFile(string fname)
{
LoadStream(
new FileStream(fname, FileMode.Open, FileAccess.Read, FileShare.Read)
);
}
public void Dispose()
{
if (BaseStream != null) BaseStream.Dispose();
BaseStream = null;
}
private static string ReadTag(BinaryReader br)
{
return "" + br.ReadChar() + br.ReadChar() + br.ReadChar() + br.ReadChar();
}
protected static void WriteTag(BinaryWriter bw, string tag)
{
for (int i = 0; i < 4; i++)
bw.Write(tag[i]);
bw.Flush();
}
public abstract class RiffChunk
{
public string tag;
/// <summary>
/// writes this chunk to the stream, including padding
/// </summary>
public abstract void WriteStream(Stream s);
/// <summary>
/// distinct from a size or a length, the `volume` is the volume of bytes occupied by the chunk on disk (accounting for padding).
///
/// </summary>
public abstract long GetVolume();
/// <summary>
/// transforms into a derived class depending on tag
/// </summary>
public abstract RiffChunk Morph();
}
public class RiffSubchunk : RiffChunk
{
public long Position;
public uint Length;
public Stream Source;
public override void WriteStream(Stream s)
{
BinaryWriter bw = new BinaryWriter(s);
WriteTag(bw, tag);
bw.Write(Length);
bw.Flush();
Source.Position = Position;
Util.CopyStream(Source, s, Length);
//all chunks are supposed to be 16bit padded
if (Length % 2 != 0)
s.WriteByte(0);
}
public override long GetVolume()
{
long ret = Length;
if (ret % 2 != 0) ret++;
return ret;
}
public byte[] ReadAll()
{
int msSize = (int)Math.Min((long)int.MaxValue, Length);
MemoryStream ms = new MemoryStream(msSize);
Source.Position = Position;
Util.CopyStream(Source, ms, Length);
return ms.ToArray();
}
public override RiffChunk Morph()
{
switch (tag)
{
case "fmt ": return new RiffSubchunk_fmt(this);
}
return this;
}
}
public class RiffSubchunk_fmt : RiffSubchunk
{
public enum FORMAT_TAG : ushort
{
WAVE_FORMAT_UNKNOWN = (0x0000),
WAVE_FORMAT_PCM = (0x0001),
WAVE_FORMAT_ADPCM = (0x0002),
WAVE_FORMAT_ALAW = (0x0006),
WAVE_FORMAT_MULAW = (0x0007),
WAVE_FORMAT_OKI_ADPCM = (0x0010),
WAVE_FORMAT_DIGISTD = (0x0015),
WAVE_FORMAT_DIGIFIX = (0x0016),
IBM_FORMAT_MULAW = (0x0101),
IBM_FORMAT_ALAW = (0x0102),
IBM_FORMAT_ADPCM = (0x0103),
}
public FORMAT_TAG format_tag;
public ushort channels;
public uint samplesPerSec;
public uint avgBytesPerSec;
public ushort blockAlign;
public ushort bitsPerSample;
public RiffSubchunk_fmt(RiffSubchunk origin)
{
tag = "fmt ";
BinaryReader br = new BinaryReader(new MemoryStream(origin.ReadAll()));
format_tag = (FORMAT_TAG)br.ReadUInt16();
channels = br.ReadUInt16();
samplesPerSec = br.ReadUInt32();
avgBytesPerSec = br.ReadUInt32();
blockAlign = br.ReadUInt16();
bitsPerSample = br.ReadUInt16();
}
public override void WriteStream(Stream s)
{
Flush();
base.WriteStream(s);
}
void Flush()
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
bw.Write((ushort)format_tag);
bw.Write(channels);
bw.Write(samplesPerSec);
bw.Write(avgBytesPerSec);
bw.Write(blockAlign);
bw.Write(bitsPerSample);
bw.Flush();
Source = ms;
Position = 0;
Length = (uint)ms.Length;
}
public override long GetVolume()
{
Flush();
return base.GetVolume();
}
}
public class RiffContainer : RiffChunk
{
public RiffChunk GetSubchunk(string tag, string type)
{
foreach (RiffChunk rc in subchunks)
if (rc.tag == tag)
{
if (type == null) return rc;
RiffContainer cont = rc as RiffContainer;
if (cont != null && cont.type == type)
return rc;
}
return null;
}
public RiffContainer()
{
tag = "LIST";
}
public string type;
public List<RiffChunk> subchunks = new List<RiffChunk>();
public override void WriteStream(Stream s)
{
BinaryWriter bw = new BinaryWriter(s);
WriteTag(bw, tag);
long size = GetVolume();
if (size > uint.MaxValue) throw new FormatException("File too big to write out");
bw.Write((uint)size);
WriteTag(bw, type);
bw.Flush();
foreach (RiffChunk rc in subchunks)
rc.WriteStream(s);
if (size % 2 != 0)
s.WriteByte(0);
}
public override long GetVolume()
{
long len = 4;
foreach (RiffChunk rc in subchunks)
len += rc.GetVolume() + 8;
return len;
}
public override RiffChunk Morph()
{
switch (type)
{
case "INFO": return new RiffContainer_INFO(this);
}
return this;
}
}
public class RiffContainer_INFO : RiffContainer
{
public Dictionary<string, string> dictionary = new Dictionary<string, string>();
public RiffContainer_INFO() { type = "INFO"; }
public RiffContainer_INFO(RiffContainer rc)
{
subchunks = rc.subchunks;
type = "INFO";
foreach (RiffChunk chunk in subchunks)
{
RiffSubchunk rsc = chunk as RiffSubchunk;
if (chunk == null)
throw new FormatException("Invalid subchunk of INFO list");
dictionary[rsc.tag] = System.Text.Encoding.ASCII.GetString(rsc.ReadAll());
}
}
private void Flush()
{
subchunks.Clear();
foreach (KeyValuePair<string, string> kvp in dictionary)
{
RiffSubchunk rs = new RiffSubchunk
{
tag = kvp.Key,
Source = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(kvp.Value)),
Position = 0
};
rs.Length = (uint)rs.Source.Length;
subchunks.Add(rs);
}
}
public override long GetVolume()
{
Flush();
return base.GetVolume();
}
public override void WriteStream(Stream s)
{
Flush();
base.WriteStream(s);
}
}
public RiffContainer riff;
private long readCounter;
private RiffChunk ReadChunk(BinaryReader br)
{
RiffChunk ret;
string tag = ReadTag(br); readCounter += 4;
uint size = br.ReadUInt32(); readCounter += 4;
if (size > int.MaxValue)
throw new FormatException("chunk too big");
if (tag == "RIFF" || tag == "LIST")
{
RiffContainer rc = new RiffContainer
{
tag = tag,
type = ReadTag(br)
};
readCounter += 4;
long readEnd = readCounter - 4 + size;
while (readEnd > readCounter)
rc.subchunks.Add(ReadChunk(br));
ret = rc.Morph();
}
else
{
RiffSubchunk rsc = new RiffSubchunk
{
tag = tag,
Source = br.BaseStream,
Position = br.BaseStream.Position,
Length = size
};
readCounter += size;
ret = rsc.Morph();
}
if (size % 2 != 0)
{
br.ReadByte();
readCounter += 1;
}
return ret;
}
public void WriteStream(Stream s)
{
riff.WriteStream(s);
}
/// <summary>
/// takes posession of the supplied stream
/// </summary>
public void LoadStream(Stream s)
{
Dispose();
BaseStream = s;
readCounter = 0;
BinaryReader br = new BinaryReader(s);
RiffChunk chunk = ReadChunk(br);
if (chunk.tag != "RIFF") throw new FormatException("can't recognize riff chunk");
riff = (RiffContainer)chunk;
}
}
}