mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-08-20 00:49:25 +08:00
2830 lines
96 KiB
C#
2830 lines
96 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using TC = System.ComponentModel.TypeConverterAttribute;
|
|
using EXP = System.ComponentModel.ExpandableObjectConverter;
|
|
using System.Xml;
|
|
|
|
namespace CodeWalker.GameFiles
|
|
{
|
|
[TC(typeof(EXP))]public class AwcFile : PackedFile
|
|
{
|
|
public string Name { get; set; }
|
|
public RpfFileEntry FileEntry { get; set; }
|
|
|
|
public string ErrorMessage { get; set; }
|
|
|
|
public uint Magic { get; set; } = 0x54414441;
|
|
public ushort Version { get; set; }
|
|
public ushort Flags { get; set; } = 0xFF00;
|
|
public int StreamCount { get; set; }
|
|
public int DataOffset { get; set; }
|
|
|
|
public bool UnkUshortsFlag { get { return ((Flags & 1) == 1); } set { Flags = (ushort)((Flags & 0xFFFE) + (value ? 1 : 0)); } }
|
|
public bool SingleChannelEncryptFlag { get { return ((Flags & 2) == 2); } set { Flags = (ushort)((Flags & 0xFFFD) + (value ? 2 : 0)); } }
|
|
public bool MultiChannelFlag { get { return ((Flags & 4) == 4); } set { Flags = (ushort)((Flags & 0xFFFB) + (value ? 4 : 0)); } }
|
|
public bool MultiChannelEncryptFlag { get { return ((Flags & 8) == 8); } set { Flags = (ushort)((Flags & 0xFFF7) + (value ? 8 : 0)); } }
|
|
|
|
public ushort[] UnkUshorts { get; set; } //offsets of some sort?
|
|
|
|
|
|
public bool WholeFileEncrypted { get; set; }
|
|
|
|
public AwcStreamInfo[] StreamInfos { get; set; }
|
|
public AwcStream[] Streams { get; set; }
|
|
public AwcStream MultiChannelSource { get; set; }
|
|
|
|
static public void Decrypt_RSXXTEA(byte[] data, uint[] key)
|
|
{
|
|
// Rockstar's modified version of XXTEA
|
|
uint[] blocks = new uint[data.Length / 4];
|
|
Buffer.BlockCopy(data, 0, blocks, 0, data.Length);
|
|
|
|
int block_count = blocks.Length;
|
|
uint a, b = blocks[0], i;
|
|
|
|
i = (uint)(0x9E3779B9 * (6 + 52 / block_count));
|
|
do
|
|
{
|
|
for (int block_index = block_count - 1; block_index >= 0; --block_index)
|
|
{
|
|
a = blocks[(block_index > 0 ? block_index : block_count) - 1];
|
|
b = blocks[block_index] -= (a >> 5 ^ b << 2) + (b >> 3 ^ a << 4) ^ (i ^ b) + (key[block_index & 3 ^ (i >> 2 & 3)] ^ a ^ 0x7B3A207F);
|
|
}
|
|
i -= 0x9E3779B9;
|
|
} while (i != 0);
|
|
|
|
Buffer.BlockCopy(blocks, 0, data, 0, data.Length);
|
|
}
|
|
|
|
public void Load(byte[] data, RpfFileEntry entry)
|
|
{
|
|
Name = entry.Name;
|
|
FileEntry = entry;
|
|
|
|
if (!string.IsNullOrEmpty(Name))
|
|
{
|
|
var nl = Name.ToLowerInvariant();
|
|
var fn = Path.GetFileNameWithoutExtension(nl);
|
|
JenkIndex.Ensure(fn + "_left");
|
|
JenkIndex.Ensure(fn + "_right");
|
|
}
|
|
|
|
if ((data == null) || (data.Length < 8))
|
|
{
|
|
ErrorMessage = "Data null or too short!";
|
|
return; //nothing to do, not enough data...
|
|
}
|
|
|
|
Endianess endianess = Endianess.LittleEndian;
|
|
|
|
Magic = BitConverter.ToUInt32(data, 0);
|
|
if (Magic != 0x54414441 && Magic != 0x41444154)
|
|
{
|
|
if (data.Length % 4 == 0)
|
|
{
|
|
Decrypt_RSXXTEA(data, GTA5Keys.PC_AWC_KEY);
|
|
Magic = BitConverter.ToUInt32(data, 0);
|
|
WholeFileEncrypted = true;
|
|
}
|
|
else
|
|
{
|
|
ErrorMessage = "Corrupted data!";
|
|
}
|
|
}
|
|
|
|
switch (Magic)
|
|
{
|
|
default:
|
|
ErrorMessage = "Unexpected Magic 0x" + Magic.ToString("X");
|
|
return;
|
|
case 0x54414441:
|
|
endianess = Endianess.LittleEndian;
|
|
break;
|
|
case 0x41444154:
|
|
endianess = Endianess.BigEndian;
|
|
break;
|
|
}
|
|
|
|
using (MemoryStream ms = new MemoryStream(data))
|
|
{
|
|
DataReader r = new DataReader(ms, endianess);
|
|
|
|
Read(r);
|
|
}
|
|
}
|
|
|
|
public byte[] Save()
|
|
{
|
|
MemoryStream s = new MemoryStream();
|
|
DataWriter w = new DataWriter(s);
|
|
|
|
Write(w);
|
|
|
|
var buf = new byte[s.Length];
|
|
s.Position = 0;
|
|
s.Read(buf, 0, buf.Length);
|
|
return buf;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void Read(DataReader r)
|
|
{
|
|
Magic = r.ReadUInt32();
|
|
Version = r.ReadUInt16();
|
|
Flags = r.ReadUInt16();
|
|
StreamCount = r.ReadInt32();
|
|
DataOffset = r.ReadInt32();
|
|
|
|
|
|
//if ((Flags >> 8) != 0xFF)
|
|
//{ }//no hit
|
|
|
|
if (UnkUshortsFlag)
|
|
{
|
|
UnkUshorts = new ushort[StreamCount]; //offsets of some sort?
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
UnkUshorts[i] = r.ReadUInt16();
|
|
}
|
|
}
|
|
|
|
|
|
//var infoStart = 16 + (UnkUshortsFlag ? (StreamCount * 2) : 0);
|
|
//if (r.Position != infoStart)
|
|
//{ }//no hit
|
|
|
|
|
|
var infos = new List<AwcStreamInfo>();
|
|
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
var info = new AwcStreamInfo();
|
|
info.Read(r);
|
|
infos.Add(info);
|
|
}
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
var info = infos[i];
|
|
for (int j = 0; j < info.ChunkCount; j++)
|
|
{
|
|
var chunk = new AwcChunkInfo();
|
|
chunk.Read(r);
|
|
info.Chunks[j] = chunk;
|
|
}
|
|
}
|
|
StreamInfos = infos.ToArray();
|
|
|
|
|
|
//var dataOffset = infoStart + StreamCount * 4;
|
|
//foreach (var info in infos) dataOffset += (int)info.ChunkCount * 8;
|
|
//if (dataOffset != DataOffset)
|
|
//{ }//no hit
|
|
//if (r.Position != DataOffset)
|
|
//{ }//no hit
|
|
|
|
|
|
|
|
var streams = new List<AwcStream>();
|
|
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
var info = StreamInfos[i];
|
|
var stream = new AwcStream(info, this);
|
|
stream.Read(r);
|
|
streams.Add(stream);
|
|
|
|
stream.UnkUshort = (UnkUshorts != null) ? (ushort?)UnkUshorts[i] : null;
|
|
|
|
if (MultiChannelFlag && (stream.DataChunk != null))
|
|
{
|
|
MultiChannelSource = stream;
|
|
}
|
|
}
|
|
|
|
Streams = streams.ToArray();
|
|
|
|
MultiChannelSource?.AssignMultiChannelSources(Streams);
|
|
}
|
|
|
|
private void Write(DataWriter w)
|
|
{
|
|
StreamCount = StreamInfos?.Length ?? 0;
|
|
var infoStart = 16 + (UnkUshortsFlag ? (StreamCount * 2) : 0);
|
|
var dataOffset = infoStart + StreamCount * 4;
|
|
foreach (var info in StreamInfos) dataOffset += (int)info.ChunkCount * 8;
|
|
DataOffset = dataOffset;
|
|
|
|
w.Write(Magic);
|
|
w.Write(Version);
|
|
w.Write(Flags);
|
|
w.Write(StreamCount);
|
|
w.Write(DataOffset);
|
|
|
|
if (UnkUshortsFlag)
|
|
{
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
w.Write((i < (UnkUshorts?.Length ?? 0)) ? UnkUshorts[i] : (ushort)0);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
var info = StreamInfos[i];
|
|
info.Write(w);
|
|
}
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
var info = StreamInfos[i];
|
|
for (int j = 0; j < info.ChunkCount; j++)
|
|
{
|
|
var chunkinfo = info.Chunks[j];
|
|
chunkinfo.Write(w);
|
|
}
|
|
}
|
|
if (MultiChannelFlag)
|
|
{
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
var stream = Streams[i];
|
|
stream.Write(w);
|
|
}
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
var stream = Streams[i];
|
|
stream.WriteDataChunks(w);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < StreamCount; i++)
|
|
{
|
|
var stream = Streams[i];
|
|
stream.Write(w);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public void WriteXml(StringBuilder sb, int indent, string wavfolder)
|
|
{
|
|
AwcXml.ValueTag(sb, indent, "Version", Version.ToString());
|
|
if (MultiChannelFlag)
|
|
{
|
|
AwcXml.ValueTag(sb, indent, "MultiChannel", true.ToString());
|
|
}
|
|
if ((Streams?.Length ?? 0) > 0)
|
|
{
|
|
AwcXml.OpenTag(sb, indent, "Streams");
|
|
var strlist = Streams.ToList();
|
|
strlist.Sort((a, b) => a.Name.CompareTo(b.Name));
|
|
foreach (var stream in strlist)
|
|
{
|
|
AwcXml.OpenTag(sb, indent + 1, "Item");
|
|
stream.WriteXml(sb, indent + 2, wavfolder);
|
|
AwcXml.CloseTag(sb, indent + 1, "Item");
|
|
}
|
|
AwcXml.CloseTag(sb, indent, "Streams");
|
|
}
|
|
|
|
}
|
|
public void ReadXml(XmlNode node, string wavfolder)
|
|
{
|
|
Version = (ushort)Xml.GetChildUIntAttribute(node, "Version");
|
|
MultiChannelFlag = Xml.GetChildBoolAttribute(node, "MultiChannel");
|
|
|
|
var unkUshorts = new List<ushort>();
|
|
var hasUshorts = false;
|
|
|
|
var snode = node.SelectSingleNode("Streams");
|
|
if (snode != null)
|
|
{
|
|
var slist = new List<AwcStream>();
|
|
var inodes = snode.SelectNodes("Item");
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
var stream = new AwcStream(this);
|
|
stream.ReadXml(inode, wavfolder);
|
|
slist.Add(stream);
|
|
|
|
hasUshorts = hasUshorts || stream.UnkUshort.HasValue;
|
|
unkUshorts.Add(stream.UnkUshort ?? 0);
|
|
|
|
if (MultiChannelFlag && (stream.StreamFormatChunk != null) && (stream.Hash == 0))
|
|
{
|
|
MultiChannelSource = stream;
|
|
}
|
|
}
|
|
slist.Sort((a, b) => a.Hash.Hash.CompareTo(b.Hash.Hash));
|
|
Streams = slist.ToArray();
|
|
StreamCount = Streams?.Length ?? 0;
|
|
|
|
MultiChannelSource?.CompactMultiChannelSources(Streams);
|
|
|
|
}
|
|
|
|
if (hasUshorts)
|
|
{
|
|
UnkUshorts = unkUshorts.ToArray();
|
|
UnkUshortsFlag = true;
|
|
}
|
|
|
|
BuildStreamInfos();
|
|
|
|
}
|
|
public static void WriteXmlNode(AwcFile f, StringBuilder sb, int indent, string wavfolder, string name = "AudioWaveContainer")
|
|
{
|
|
if (f == null) return;
|
|
AwcXml.OpenTag(sb, indent, name);
|
|
f.WriteXml(sb, indent + 1, wavfolder);
|
|
AwcXml.CloseTag(sb, indent, name);
|
|
}
|
|
public static AwcFile ReadXmlNode(XmlNode node, string wavfolder)
|
|
{
|
|
if (node == null) return null;
|
|
var f = new AwcFile();
|
|
f.ReadXml(node, wavfolder);
|
|
return f;
|
|
}
|
|
|
|
|
|
|
|
public void BuildStreamInfos()
|
|
{
|
|
|
|
var streaminfos = new List<AwcStreamInfo>();
|
|
var chunkinfos = new List<AwcChunkInfo>();
|
|
if (Streams != null)
|
|
{
|
|
var streamCount = Streams?.Length ?? 0;
|
|
var infoStart = 16 + (UnkUshortsFlag ? (streamCount * 2) : 0);
|
|
var dataOffset = infoStart + streamCount * 4;
|
|
foreach (var stream in Streams)
|
|
{
|
|
dataOffset += (stream?.Chunks?.Length ?? 0) * 8;
|
|
}
|
|
if (MultiChannelFlag)
|
|
{
|
|
foreach (var stream in Streams)
|
|
{
|
|
if (stream.Chunks != null)
|
|
{
|
|
foreach (var chunk in stream.Chunks)
|
|
{
|
|
if (!(chunk is AwcDataChunk))
|
|
{
|
|
if (chunk is AwcMarkersChunk)
|
|
{
|
|
//align to 4 bytes
|
|
var padc = (4 - (dataOffset % 4)) % 4;
|
|
dataOffset += padc;
|
|
}
|
|
|
|
var chunkinfo = chunk.ChunkInfo;
|
|
var size = chunk.ChunkSize;
|
|
chunkinfo.Size = size;
|
|
chunkinfo.Offset = dataOffset;
|
|
dataOffset += size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach (var stream in Streams)
|
|
{
|
|
if (stream.Chunks != null)
|
|
{
|
|
foreach (var chunk in stream.Chunks)
|
|
{
|
|
if (chunk is AwcDataChunk)
|
|
{
|
|
//align to 16 bytes
|
|
var padc = (16 - (dataOffset % 16)) % 16;
|
|
dataOffset += padc;
|
|
|
|
var chunkinfo = chunk.ChunkInfo;
|
|
var size = chunk.ChunkSize;
|
|
chunkinfo.Size = size;
|
|
chunkinfo.Offset = dataOffset;
|
|
dataOffset += size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var stream in Streams)
|
|
{
|
|
if (stream.Chunks != null)
|
|
{
|
|
foreach (var chunk in stream.Chunks)
|
|
{
|
|
var chunkinfo = chunk.ChunkInfo;
|
|
var size = chunk.ChunkSize;
|
|
chunkinfo.Size = size;
|
|
chunkinfo.Offset = dataOffset;
|
|
dataOffset += size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach (var stream in Streams)
|
|
{
|
|
var streaminfo = stream.StreamInfo;
|
|
streaminfos.Add(streaminfo);
|
|
chunkinfos.Clear();
|
|
if (stream.Chunks != null)
|
|
{
|
|
foreach (var chunk in stream.Chunks)
|
|
{
|
|
var chunkinfo = chunk.ChunkInfo;
|
|
chunkinfos.Add(chunkinfo);
|
|
}
|
|
}
|
|
streaminfo.Chunks = chunkinfos.ToArray();
|
|
streaminfo.ChunkCount = (uint)chunkinfos.Count;
|
|
}
|
|
}
|
|
StreamInfos = streaminfos.ToArray();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public enum AwcCodecType
|
|
{
|
|
PCM = 0,
|
|
ADPCM = 4
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcStreamInfo
|
|
{
|
|
public uint RawVal { get; set; }
|
|
public uint ChunkCount { get; set; }
|
|
public uint Id { get; set; }
|
|
public AwcChunkInfo[] Chunks { get; set; }
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
RawVal = r.ReadUInt32();
|
|
ChunkCount = (RawVal >> 29);
|
|
Id = (RawVal & 0x1FFFFFFF);
|
|
Chunks = new AwcChunkInfo[ChunkCount];
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
ChunkCount = (uint)(Chunks?.Length ?? 0);
|
|
RawVal = (Id & 0x1FFFFFFF) + (ChunkCount << 29);
|
|
w.Write(RawVal);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Id.ToString("X") + ": " + ChunkCount.ToString() + " chunks";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcChunkInfo
|
|
{
|
|
public ulong RawVal { get; set; }
|
|
public AwcChunkType Type { get; set; }
|
|
public int Size { get; set; }
|
|
public int Offset { get; set; }
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
RawVal = r.ReadUInt64();
|
|
Type = (AwcChunkType)(RawVal >> 56);
|
|
Size = (int)((RawVal >> 28) & 0x0FFFFFFF);
|
|
Offset = (int)(RawVal & 0x0FFFFFFF);
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
RawVal = (((ulong)Offset) & 0x0FFFFFFF) + ((((ulong)Size) & 0x0FFFFFFF) << 28) + (((ulong)Type) << 56);
|
|
w.Write(RawVal);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Type.ToString() + ": " + Size.ToString() + ", " + Offset.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcStream
|
|
{
|
|
public AwcFile Awc { get; set; }
|
|
public AwcStreamInfo StreamInfo { get; set; }
|
|
public AwcChunk[] Chunks { get; set; }
|
|
public AwcFormatChunk FormatChunk { get; set; }
|
|
public AwcDataChunk DataChunk { get; set; }
|
|
public AwcAnimationChunk AnimationChunk { get; set; }
|
|
public AwcGestureChunk GestureChunk { get; set; }
|
|
public AwcPeakChunk PeakChunk { get; set; }
|
|
public AwcMIDIChunk MidiChunk { get; set; }
|
|
public AwcMarkersChunk MarkersChunk { get; set; }
|
|
public AwcGranularGrainsChunk GranularGrainsChunk { get; set; }
|
|
public AwcGranularLoopsChunk GranularLoopsChunk { get; set; }
|
|
public AwcStreamFormatChunk StreamFormatChunk { get; set; }
|
|
public AwcSeekTableChunk SeekTableChunk { get; set; }
|
|
public AwcStream StreamSource { get; set; }
|
|
public AwcStreamFormat StreamFormat { get; set; }
|
|
public AwcStreamDataBlock[] StreamBlocks { get; set; }
|
|
public int StreamChannelIndex { get; set; }
|
|
|
|
|
|
public int SamplesPerSecond
|
|
{
|
|
get
|
|
{
|
|
return FormatChunk?.SamplesPerSecond ?? StreamFormat?.SamplesPerSecond ?? 0;
|
|
}
|
|
}
|
|
public int SampleCount
|
|
{
|
|
get
|
|
{
|
|
return (int)(FormatChunk?.Samples ?? StreamFormat?.Samples ?? 0);
|
|
}
|
|
}
|
|
|
|
public ushort? UnkUshort { get; set; } // stored in root of AWC, will have value if present
|
|
|
|
public MetaHash Hash
|
|
{
|
|
get
|
|
{
|
|
return StreamInfo?.Id ?? 0;
|
|
}
|
|
set
|
|
{
|
|
if (StreamInfo != null)
|
|
{
|
|
StreamInfo.Id = value & 0x1FFFFFFF;
|
|
}
|
|
}
|
|
}
|
|
public MetaHash HashAdjusted
|
|
{
|
|
get
|
|
{
|
|
var h = (uint)Hash;
|
|
if (h == 0) return h;
|
|
for (uint i = 0; i < 8; i++)
|
|
{
|
|
var th = h + (i << 29);
|
|
if (!string.IsNullOrEmpty(JenkIndex.TryGetString(th))) return th;
|
|
if (MetaNames.TryGetString(th, out string str)) return th;
|
|
}
|
|
return h;
|
|
}
|
|
}
|
|
public string Name
|
|
{
|
|
get
|
|
{
|
|
if (CachedName != null) return CachedName;
|
|
var ha = HashAdjusted;
|
|
var str = JenkIndex.TryGetString(ha);
|
|
if (!string.IsNullOrEmpty(str)) CachedName = str;
|
|
else if (MetaNames.TryGetString(ha, out str)) CachedName = str;
|
|
else CachedName = "0x" + Hash.Hex;
|
|
return CachedName;
|
|
}
|
|
}
|
|
private string CachedName;
|
|
public string Type
|
|
{
|
|
get
|
|
{
|
|
if (MidiChunk != null)
|
|
{
|
|
return "MIDI";
|
|
}
|
|
|
|
var fc = AwcCodecType.PCM;
|
|
var hz = 0;
|
|
if (FormatChunk != null)
|
|
{
|
|
fc = FormatChunk.Codec;
|
|
hz = FormatChunk.SamplesPerSecond;
|
|
}
|
|
if (StreamFormat != null)
|
|
{
|
|
fc = StreamFormat.Codec;
|
|
hz = StreamFormat.SamplesPerSecond;
|
|
}
|
|
string codec = fc.ToString();
|
|
switch (fc)
|
|
{
|
|
case AwcCodecType.PCM:
|
|
case AwcCodecType.ADPCM:
|
|
break;
|
|
default:
|
|
codec = "Unknown";
|
|
break;
|
|
}
|
|
|
|
return codec + ((hz > 0) ? (", " + hz.ToString() + " Hz") : "");
|
|
}
|
|
}
|
|
|
|
public float Length
|
|
{
|
|
get
|
|
{
|
|
if (FormatChunk != null) return (float)FormatChunk.Samples / FormatChunk.SamplesPerSecond;
|
|
if (StreamFormat != null) return (float)StreamFormat.Samples / StreamFormat.SamplesPerSecond;
|
|
return 0;
|
|
}
|
|
}
|
|
public string LengthStr
|
|
{
|
|
get
|
|
{
|
|
return TimeSpan.FromSeconds(Length).ToString("m\\:ss");
|
|
}
|
|
}
|
|
|
|
public int ByteLength
|
|
{
|
|
get
|
|
{
|
|
if (MidiChunk?.Data != null)
|
|
{
|
|
return MidiChunk.Data.Length;
|
|
}
|
|
if (DataChunk?.Data != null)
|
|
{
|
|
return DataChunk.Data.Length;
|
|
}
|
|
if (StreamSource != null)
|
|
{
|
|
int c = 0;
|
|
if (StreamSource?.StreamBlocks != null)
|
|
{
|
|
foreach (var blk in StreamSource.StreamBlocks)
|
|
{
|
|
if (StreamChannelIndex < (blk?.Channels?.Length ?? 0))
|
|
{
|
|
var chan = blk.Channels[StreamChannelIndex];
|
|
c += chan?.Data?.Length ?? 0;
|
|
}
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
public AwcStream(AwcFile awc)
|
|
{
|
|
Awc = awc;
|
|
StreamInfo = new AwcStreamInfo();
|
|
}
|
|
public AwcStream(AwcStreamInfo s, AwcFile awc)
|
|
{
|
|
Awc = awc;
|
|
StreamInfo = s;
|
|
}
|
|
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
|
|
var chunklist = new List<AwcChunk>();
|
|
for (int j = 0; j < StreamInfo.Chunks?.Length; j++)
|
|
{
|
|
var cinfo = StreamInfo.Chunks[j];
|
|
r.Position = cinfo.Offset;
|
|
|
|
var chunk = CreateChunk(cinfo);
|
|
chunk?.Read(r);
|
|
chunklist.Add(chunk);
|
|
|
|
if ((r.Position - cinfo.Offset) != cinfo.Size)
|
|
{ }//make sure everything was read!
|
|
}
|
|
Chunks = chunklist.ToArray();
|
|
|
|
ExpandChunks();
|
|
DecodeData(r.Endianess);
|
|
}
|
|
|
|
public void Write(DataWriter w)
|
|
{
|
|
if (Chunks != null)
|
|
{
|
|
foreach (var chunk in Chunks)
|
|
{
|
|
if (!((chunk is AwcDataChunk) && Awc.MultiChannelFlag))
|
|
{
|
|
if (Awc.MultiChannelFlag && (chunk is AwcMarkersChunk))
|
|
{
|
|
//write padding to align to 4 bytes
|
|
var padc = (4 - (w.Position % 4)) % 4;
|
|
if (padc > 0) w.Write(new byte[padc]);
|
|
}
|
|
|
|
chunk.Write(w);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
public void WriteDataChunks(DataWriter w)
|
|
{
|
|
//for use by multichannel only, to write the data at the end
|
|
|
|
if (Chunks != null)
|
|
{
|
|
foreach (var chunk in Chunks)
|
|
{
|
|
if (chunk is AwcDataChunk)
|
|
{
|
|
//write padding to align to 16 bytes
|
|
var padc = (16 - (w.Position % 16)) % 16;
|
|
if (padc > 0) w.Write(new byte[padc]);
|
|
|
|
chunk.Write(w);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent, string wavfolder)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Name", AwcXml.HashString(HashAdjusted));
|
|
if (Hash != 0) //skip the wave file output for multichannel sources
|
|
{
|
|
var export = !string.IsNullOrEmpty(wavfolder);
|
|
var fname = Name?.Replace("/", "")?.Replace("\\", "") ?? "0x0";
|
|
byte[] fdata = null;
|
|
if (MidiChunk != null)
|
|
{
|
|
fname += ".midi";
|
|
fdata = export ? MidiChunk.Data : null;
|
|
}
|
|
else
|
|
{
|
|
fname += ".wav";
|
|
fdata = export ? GetWavFile() : null;
|
|
}
|
|
AwcXml.StringTag(sb, indent, "FileName", AwcXml.XmlEscape(fname));
|
|
try
|
|
{
|
|
if (export)
|
|
{
|
|
if (!Directory.Exists(wavfolder))
|
|
{
|
|
Directory.CreateDirectory(wavfolder);
|
|
}
|
|
var filepath = Path.Combine(wavfolder, fname);
|
|
File.WriteAllBytes(filepath, fdata);
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
if (UnkUshort.HasValue)
|
|
{
|
|
AwcXml.ValueTag(sb, indent, "UnkUshort", UnkUshort.Value.ToString());
|
|
}
|
|
if (StreamFormat != null)
|
|
{
|
|
AwcXml.OpenTag(sb, indent, "StreamFormat");
|
|
StreamFormat.WriteXml(sb, indent + 1);
|
|
AwcXml.CloseTag(sb, indent, "StreamFormat");
|
|
}
|
|
if ((Chunks?.Length ?? 0) > 0)
|
|
{
|
|
AwcXml.OpenTag(sb, indent, "Chunks");
|
|
for (int i = 0; i < Chunks.Length; i++)
|
|
{
|
|
AwcXml.OpenTag(sb, indent + 1, "Item");
|
|
Chunks[i].WriteXml(sb, indent + 2);
|
|
AwcXml.CloseTag(sb, indent + 1, "Item");
|
|
}
|
|
AwcXml.CloseTag(sb, indent, "Chunks");
|
|
}
|
|
}
|
|
|
|
public void ReadXml(XmlNode node, string wavfolder)
|
|
{
|
|
Hash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Name"));
|
|
var unode = node.SelectSingleNode("UnkUshort");
|
|
if (unode != null)
|
|
{
|
|
UnkUshort = (ushort)Xml.GetUIntAttribute(unode, "value");
|
|
}
|
|
var fnode = node.SelectSingleNode("StreamFormat");
|
|
if (fnode != null)
|
|
{
|
|
StreamFormat = new AwcStreamFormat();
|
|
StreamFormat.ReadXml(fnode);
|
|
StreamFormat.Id = Hash;
|
|
}
|
|
var cnode = node.SelectSingleNode("Chunks");
|
|
if (cnode != null)
|
|
{
|
|
var clist = new List<AwcChunk>();
|
|
var inodes = cnode.SelectNodes("Item");
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
var type = Xml.GetChildEnumInnerText<AwcChunkType>(inode, "Type");
|
|
var info = new AwcChunkInfo() { Type = type };
|
|
var chunk = CreateChunk(info);
|
|
chunk?.ReadXml(inode);
|
|
clist.Add(chunk);
|
|
}
|
|
Chunks = clist.ToArray();
|
|
}
|
|
|
|
ExpandChunks();
|
|
|
|
var filename = Xml.GetChildInnerText(node, "FileName")?.Replace("/", "")?.Replace("\\", "");
|
|
if (!string.IsNullOrEmpty(filename) && !string.IsNullOrEmpty(wavfolder))
|
|
{
|
|
try
|
|
{
|
|
var filepath = Path.Combine(wavfolder, filename);
|
|
if (File.Exists(filepath))
|
|
{
|
|
var fdata = File.ReadAllBytes(filepath);
|
|
if (MidiChunk != null)
|
|
{
|
|
MidiChunk.Data = fdata;
|
|
}
|
|
else
|
|
{
|
|
ParseWavFile(fdata);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public static AwcChunk CreateChunk(AwcChunkInfo info)
|
|
{
|
|
switch (info.Type)
|
|
{
|
|
case AwcChunkType.data: return new AwcDataChunk(info);
|
|
case AwcChunkType.format: return new AwcFormatChunk(info);
|
|
case AwcChunkType.animation: return new AwcAnimationChunk(info);
|
|
case AwcChunkType.peak: return new AwcPeakChunk(info);
|
|
case AwcChunkType.mid: return new AwcMIDIChunk(info);
|
|
case AwcChunkType.gesture: return new AwcGestureChunk(info);
|
|
case AwcChunkType.granulargrains: return new AwcGranularGrainsChunk(info);
|
|
case AwcChunkType.granularloops: return new AwcGranularLoopsChunk(info);
|
|
case AwcChunkType.markers: return new AwcMarkersChunk(info);
|
|
case AwcChunkType.streamformat: return new AwcStreamFormatChunk(info);
|
|
case AwcChunkType.seektable: return new AwcSeekTableChunk(info);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
public void ExpandChunks()
|
|
{
|
|
if (Chunks != null)
|
|
{
|
|
foreach (var chunk in Chunks)
|
|
{
|
|
if (chunk is AwcDataChunk dataChunk) DataChunk = dataChunk;
|
|
if (chunk is AwcFormatChunk formatChunk) FormatChunk = formatChunk;
|
|
if (chunk is AwcAnimationChunk animChunk) AnimationChunk = animChunk;
|
|
if (chunk is AwcMarkersChunk markersChunk) MarkersChunk = markersChunk;
|
|
if (chunk is AwcGestureChunk gestureChunk) GestureChunk = gestureChunk;
|
|
if (chunk is AwcPeakChunk peakChunk) PeakChunk = peakChunk;
|
|
if (chunk is AwcMIDIChunk midiChunk) MidiChunk = midiChunk;
|
|
if (chunk is AwcStreamFormatChunk streamformatChunk) StreamFormatChunk = streamformatChunk;
|
|
if (chunk is AwcSeekTableChunk seektableChunk) SeekTableChunk = seektableChunk;
|
|
if (chunk is AwcGranularGrainsChunk ggChunk) GranularGrainsChunk = ggChunk;
|
|
if (chunk is AwcGranularLoopsChunk glChunk) GranularLoopsChunk = glChunk;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void DecodeData(Endianess endianess)
|
|
{
|
|
//create multichannel blocks and decrypt data where necessary
|
|
if (DataChunk?.Data != null)
|
|
{
|
|
if (Awc.MultiChannelFlag)
|
|
{
|
|
var ocount = (int)(SeekTableChunk?.SeekTable?.Length ?? 0);
|
|
var ccount = (int)(StreamFormatChunk?.ChannelCount ?? 0);
|
|
var bcount = (int)(StreamFormatChunk?.BlockCount ?? 0);
|
|
var bsize = (int)(StreamFormatChunk?.BlockSize ?? 0);
|
|
var blist = new List<AwcStreamDataBlock>();
|
|
for (int b = 0; b < bcount; b++)
|
|
{
|
|
int srcoff = b * bsize;
|
|
int mcsoff = (b < ocount) ? (int)SeekTableChunk.SeekTable[b] : 0;
|
|
int blen = Math.Max(Math.Min(bsize, DataChunk.Data.Length - srcoff), 0);
|
|
var bdat = new byte[blen];
|
|
Buffer.BlockCopy(DataChunk.Data, srcoff, bdat, 0, blen);
|
|
if (Awc.MultiChannelEncryptFlag && !Awc.WholeFileEncrypted)
|
|
{
|
|
AwcFile.Decrypt_RSXXTEA(bdat, GTA5Keys.PC_AWC_KEY);
|
|
}
|
|
var blk = new AwcStreamDataBlock(bdat, StreamFormatChunk, endianess, mcsoff);
|
|
blist.Add(blk);
|
|
}
|
|
StreamBlocks = blist.ToArray();
|
|
}
|
|
else
|
|
{
|
|
if (Awc.SingleChannelEncryptFlag && !Awc.WholeFileEncrypted)
|
|
{
|
|
AwcFile.Decrypt_RSXXTEA(DataChunk.Data, GTA5Keys.PC_AWC_KEY);
|
|
}
|
|
}
|
|
}
|
|
//if ((Data != null) && awc.WholeFileEncrypted && awc.MultiChannelFlag)
|
|
//{ }//no hit
|
|
}
|
|
|
|
public void AssignMultiChannelSources(AwcStream[] streams)
|
|
{
|
|
for (int i = 0; i < (streams?.Length ?? 0); i++)
|
|
{
|
|
var stream = streams[i];
|
|
if (stream != this)
|
|
{
|
|
var id = stream.StreamInfo?.Id ?? 0;
|
|
var srcind = 0;
|
|
var chancnt = StreamFormatChunk?.Channels?.Length ?? 0;
|
|
var found = false;
|
|
for (int ind = 0; ind < chancnt; ind++)
|
|
{
|
|
var mchan = StreamFormatChunk.Channels[ind];
|
|
if (mchan.Id == id)
|
|
{
|
|
srcind = ind;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{ }//no hit
|
|
|
|
stream.StreamSource = this;
|
|
stream.StreamFormat = (srcind < chancnt) ? StreamFormatChunk.Channels[srcind] : null;
|
|
stream.StreamChannelIndex = srcind;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public void CompactMultiChannelSources(AwcStream[] streams)
|
|
{
|
|
var chanlist = new List<AwcStreamFormat>();
|
|
var chandatas = new List<byte[]>();
|
|
for (int i = 0; i < (streams?.Length ?? 0); i++)
|
|
{
|
|
var stream = streams[i];
|
|
if (stream != this)
|
|
{
|
|
chanlist.Add(stream.StreamFormat);
|
|
chandatas.Add(stream.DataChunk?.Data);
|
|
}
|
|
}
|
|
StreamFormatChunk.Channels = chanlist.ToArray();
|
|
StreamFormatChunk.ChannelCount = (uint)chanlist.Count;
|
|
|
|
//figure out how many smaller blocks fit in the larger block
|
|
var chancount = chanlist.Count;
|
|
var blocksize = (int)(StreamFormatChunk?.BlockSize ?? 1032192);
|
|
var hdrsize = 96 * chancount + (blocksize / 512) + 1024;
|
|
hdrsize += (0x800 - (hdrsize % 0x800)) % 0x800;
|
|
var smblockspace = (blocksize - hdrsize) / 2048;
|
|
var smblockcount = smblockspace / chancount;
|
|
|
|
//split the channel datas into their blocks
|
|
var streamblocks = new List<AwcStreamDataBlock>();
|
|
var seektable = new List<uint>();
|
|
var chansmpoffs = new List<int>();
|
|
for (int c = 0; c < chancount; c++)
|
|
{
|
|
var chaninfo = chanlist[c];
|
|
var chandata = chandatas[c];
|
|
var cdlen = chandata?.Length ?? 0;
|
|
var totsmblockcount = (cdlen / 2048) + (((cdlen % 2048) != 0) ? 1 : 0);
|
|
var totlgblockcount = (totsmblockcount / smblockcount) + (((totsmblockcount % smblockcount) != 0) ? 1 : 0);
|
|
for (int i = streamblocks.Count; i < totlgblockcount; i++)
|
|
{
|
|
var blk = new AwcStreamDataBlock();
|
|
blk.ChannelInfo = StreamFormatChunk;
|
|
blk.Channels = new AwcStreamDataChannel[chancount];
|
|
blk.ChannelCount = (uint)chancount;
|
|
blk.SampleOffset = i * smblockcount * 4088;
|
|
streamblocks.Add(blk);
|
|
seektable.Add((uint)blk.SampleOffset);
|
|
}
|
|
|
|
chansmpoffs.Clear();
|
|
var samplesrem = (int)chaninfo.Samples+1;
|
|
for (int i = 0; i < totsmblockcount; i++)
|
|
{
|
|
|
|
chansmpoffs.Add(i * 4088);
|
|
var blkcnt = chansmpoffs.Count;
|
|
if ((blkcnt == smblockcount) || (i == totsmblockcount-1))
|
|
{
|
|
var lgblockind = i / smblockcount;
|
|
var blkstart = lgblockind * smblockcount;
|
|
var blk = streamblocks[lgblockind];
|
|
var chan = new AwcStreamDataChannel();
|
|
var smpcnt = blkcnt * 4088;
|
|
var bytcnt = blkcnt * 2048;
|
|
var srcoff = blkstart * 2048;
|
|
var srccnt = Math.Min(bytcnt, cdlen - srcoff);
|
|
var data = new byte[bytcnt];
|
|
Buffer.BlockCopy(chandata, srcoff, data, 0, srccnt);
|
|
chan.Data = data;
|
|
chan.SampleOffsets = chansmpoffs.ToArray();
|
|
chan.SampleCount = Math.Min(smpcnt, samplesrem);
|
|
chan.BlockCount = blkcnt;
|
|
chan.StartBlock = (c * smblockcount);
|
|
|
|
blk.Channels[c] = chan;
|
|
chansmpoffs.Clear();
|
|
samplesrem -= smpcnt;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
StreamBlocks = streamblocks.ToArray();
|
|
|
|
StreamFormatChunk.BlockCount = (uint)streamblocks.Count;
|
|
|
|
if (streams != null)
|
|
{
|
|
foreach (var stream in streams)
|
|
{
|
|
if (stream.SeekTableChunk != null)
|
|
{
|
|
stream.SeekTableChunk.SeekTable = seektable.ToArray();
|
|
}
|
|
if (stream.StreamFormatChunk != null)
|
|
{
|
|
stream.StreamFormatChunk.BlockCount = StreamFormatChunk.BlockCount;
|
|
stream.StreamFormatChunk.BlockSize = StreamFormatChunk.BlockSize;
|
|
stream.StreamFormatChunk.ChannelCount = StreamFormatChunk.ChannelCount;
|
|
stream.StreamFormatChunk.Channels = StreamFormatChunk.Channels;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//build stream blocks into final data chunk
|
|
var ms = new MemoryStream();
|
|
var w = new DataWriter(ms);
|
|
foreach (var blk in streamblocks)
|
|
{
|
|
var start = w.Position;
|
|
blk.Write(w);
|
|
var offs = w.Position - start;
|
|
var padc = blocksize - offs;
|
|
if (padc < 0)
|
|
{ }
|
|
if (padc > 0)
|
|
{
|
|
w.Write(new byte[padc]);
|
|
}
|
|
}
|
|
var bytes = new byte[ms.Length];
|
|
ms.Position = 0;
|
|
ms.Read(bytes, 0, (int)ms.Length);
|
|
|
|
if (DataChunk == null)
|
|
{
|
|
DataChunk = new AwcDataChunk(new AwcChunkInfo() { Type = AwcChunkType.data });
|
|
}
|
|
DataChunk.Data = bytes;
|
|
|
|
}
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
var hash = "0x" + (StreamInfo?.Id.ToString("X") ?? "0").PadLeft(8, '0') + ": ";
|
|
if (FormatChunk != null)
|
|
{
|
|
return hash + FormatChunk?.ToString() ?? "AwcAudio";
|
|
}
|
|
if (StreamFormat != null)
|
|
{
|
|
return hash + StreamFormat?.ToString() ?? "AwcAudio";
|
|
}
|
|
if (MidiChunk != null)
|
|
{
|
|
return hash + MidiChunk.ToString();
|
|
}
|
|
return hash + "Unknown";
|
|
}
|
|
|
|
|
|
public byte[] GetWavData()
|
|
{
|
|
if (StreamFormat != null)
|
|
{
|
|
if (DataChunk?.Data == null)
|
|
{
|
|
var ms = new MemoryStream();
|
|
var bw = new BinaryWriter(ms);
|
|
if (StreamSource?.StreamBlocks != null)
|
|
{
|
|
foreach (var blk in StreamSource.StreamBlocks)
|
|
{
|
|
if (StreamChannelIndex < (blk?.Channels?.Length ?? 0))
|
|
{
|
|
var chan = blk.Channels[StreamChannelIndex];
|
|
var cdata = chan.Data;
|
|
bw.Write(cdata);
|
|
}
|
|
}
|
|
}
|
|
bw.Flush();
|
|
ms.Position = 0;
|
|
DataChunk = new AwcDataChunk(null);
|
|
DataChunk.Data = new byte[ms.Length];
|
|
ms.Read(DataChunk.Data, 0, (int)ms.Length);
|
|
}
|
|
}
|
|
return DataChunk.Data;
|
|
}
|
|
|
|
public byte[] GetWavFile()
|
|
{
|
|
var ms = GetWavStream();
|
|
var data = new byte[ms.Length];
|
|
ms.Read(data, 0, (int)ms.Length);
|
|
return data;
|
|
}
|
|
|
|
public Stream GetWavStream()
|
|
{
|
|
byte[] dataPCM = GetWavData();
|
|
var bitsPerSample = 16;
|
|
var channels = 1;
|
|
var codec = StreamFormat?.Codec ?? FormatChunk?.Codec ?? AwcCodecType.PCM;
|
|
|
|
if (codec == AwcCodecType.ADPCM)//just convert ADPCM to PCM for compatibility reasons
|
|
{
|
|
dataPCM = ADPCMCodec.DecodeADPCM(dataPCM, SampleCount);
|
|
}
|
|
|
|
short formatcodec = 1; // 1 = WAVE_FORMAT_PCM
|
|
int byteRate = SamplesPerSecond * channels * bitsPerSample / 8;
|
|
short blockAlign = (short)(channels * bitsPerSample / 8);
|
|
short samplesPerBlock = 0;
|
|
bool addextrafmt = false;
|
|
|
|
//if (codec == AwcCodecFormat.ADPCM)//can't seem to get ADPCM wav files to work :(
|
|
//{
|
|
// bitsPerSample = 4;
|
|
// formatcodec = 17;
|
|
// byteRate = (int)(SamplesPerSecond * 0.50685 * channels);
|
|
// blockAlign = 2048;// (short)(256 * (4 * channels));// (short)(36 * channels);//256;// 2048;//
|
|
// samplesPerBlock = 4088;// (short)(((blockAlign - (4 * channels)) * 8) / (bitsPerSample * channels) + 1); // 2044;//
|
|
// addextrafmt = true;
|
|
//}
|
|
|
|
|
|
MemoryStream stream = new MemoryStream();
|
|
BinaryWriter w = new BinaryWriter(stream);
|
|
int wavLength = 36 + dataPCM.Length;
|
|
if (addextrafmt)
|
|
{
|
|
wavLength += 4;
|
|
}
|
|
|
|
// RIFF chunk
|
|
w.Write("RIFF".ToCharArray());
|
|
w.Write((int)wavLength);
|
|
w.Write("WAVE".ToCharArray());
|
|
|
|
// fmt sub-chunk
|
|
w.Write("fmt ".ToCharArray());
|
|
w.Write((int)(addextrafmt ? 20 : 16)); // fmt size
|
|
w.Write((short)formatcodec);
|
|
w.Write((short)channels);
|
|
w.Write((int)SamplesPerSecond);
|
|
w.Write((int)byteRate);
|
|
w.Write((short)blockAlign);
|
|
w.Write((short)bitsPerSample);
|
|
if (addextrafmt)
|
|
{
|
|
w.Write((ushort)0x0002);
|
|
w.Write((ushort)samplesPerBlock);
|
|
}
|
|
|
|
// data sub-chunk
|
|
w.Write("data".ToCharArray());
|
|
w.Write((int)dataPCM.Length);
|
|
w.Write(dataPCM);
|
|
|
|
w.Flush();
|
|
stream.Position = 0;
|
|
return stream;
|
|
}
|
|
|
|
public void ParseWavFile(byte[] wav)
|
|
{
|
|
var ms = new MemoryStream(wav);
|
|
var r = new DataReader(ms);
|
|
|
|
var RIFF = r.ReadUInt32(); // 0x46464952
|
|
var wavLength = r.ReadInt32();
|
|
var WAVE = r.ReadUInt32(); // 0x45564157
|
|
var fmt_ = r.ReadUInt32(); // 0x20746D66
|
|
var fmtLength = r.ReadInt32();
|
|
var formatcodec = r.ReadInt16();
|
|
var channels = r.ReadInt16();
|
|
var sampleRate = r.ReadInt32();
|
|
var byteRate = r.ReadInt32();
|
|
var blockAlign = r.ReadInt16();
|
|
var bitsPerSample = r.ReadInt16();
|
|
var ext2 = (ushort)0;
|
|
var samplesPerBlock = (ushort)0;
|
|
if (fmtLength == 20)
|
|
{
|
|
ext2 = r.ReadUInt16();
|
|
samplesPerBlock = r.ReadUInt16();
|
|
}
|
|
var datatag = r.ReadUInt32(); // 0x61746164
|
|
var datalen = r.ReadInt32();
|
|
var dataPCM = r.ReadBytes(datalen);
|
|
|
|
if (r.Position != r.Length)
|
|
{ }
|
|
|
|
if (formatcodec != 1)
|
|
{
|
|
throw new Exception("Only PCM format .wav files supported!");
|
|
}
|
|
if (channels != 1)
|
|
{
|
|
throw new Exception("Only mono .wav files supported!");
|
|
}
|
|
|
|
var sampleCount = datalen * 2; //assume 16bits per sample PCM
|
|
|
|
var codec = StreamFormat?.Codec ?? FormatChunk?.Codec ?? AwcCodecType.PCM;
|
|
if (codec == AwcCodecType.ADPCM)// convert PCM wav to ADPCM where required
|
|
{
|
|
dataPCM = ADPCMCodec.EncodeADPCM(dataPCM, sampleCount);
|
|
bitsPerSample = 4;
|
|
}
|
|
|
|
|
|
if (Awc.MultiChannelFlag)
|
|
{
|
|
if (StreamFormat != null)
|
|
{
|
|
//StreamFormat.Samples = (uint)sampleCount;
|
|
StreamFormat.SamplesPerSecond = (ushort)sampleRate;
|
|
|
|
DataChunk = new AwcDataChunk(null);
|
|
DataChunk.Data = dataPCM;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (FormatChunk != null)
|
|
{
|
|
//FormatChunk.Samples = (uint)sampleCount;
|
|
FormatChunk.SamplesPerSecond = (ushort)sampleRate;
|
|
|
|
if (DataChunk == null) DataChunk = new AwcDataChunk(new AwcChunkInfo() { Type = AwcChunkType.data });
|
|
|
|
DataChunk.Data = dataPCM;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
public enum AwcChunkType : byte
|
|
{
|
|
//should be the last byte of the hash of the name.
|
|
data = 0x55, // 0x5EB5E655
|
|
format = 0xFA, // 0x6061D4FA
|
|
animation = 0x5C, // 0x938C925C not correct
|
|
peak = 0x36, // 0x8B946236
|
|
mid = 0x68, // 0x71DE4C68
|
|
gesture = 0x2B, // 0x23097A2B
|
|
granulargrains = 0x5A, // 0xE787895A
|
|
granularloops = 0xD9, // 0x252C20D9
|
|
markers = 0xBD, // 0xD4CB98BD
|
|
streamformat = 0x48, // 0x81F95048
|
|
seektable = 0xA3, // 0x021E86A3
|
|
}
|
|
|
|
[TC(typeof(EXP))] public abstract class AwcChunk : IMetaXmlItem
|
|
{
|
|
public abstract int ChunkSize { get; }
|
|
public AwcChunkInfo ChunkInfo { get; set; }
|
|
|
|
public AwcChunk(AwcChunkInfo info)
|
|
{
|
|
ChunkInfo = info;
|
|
}
|
|
|
|
public abstract void Read(DataReader r);
|
|
public abstract void Write(DataWriter w);
|
|
public abstract void WriteXml(StringBuilder sb, int indent);
|
|
public abstract void ReadXml(XmlNode node);
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcDataChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => Data?.Length ?? 0;
|
|
|
|
public byte[] Data { get; set; }
|
|
|
|
public AwcDataChunk(AwcChunkInfo info) : base(info)
|
|
{ }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
Data = r.ReadBytes(ChunkInfo.Size);
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
w.Write(Data);
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
//this is just a placeholder. in XML, channel data is written as WAV files
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "data: " + (Data?.Length ?? 0).ToString() + " bytes";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcFormatChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => UnkExtra.HasValue ? 24 : 20;
|
|
|
|
public uint Samples { get; set; }
|
|
public int LoopPoint { get; set; }
|
|
public ushort SamplesPerSecond { get; set; }
|
|
public short Headroom { get; set; }
|
|
public ushort LoopBegin { get; set; }
|
|
public ushort LoopEnd { get; set; }
|
|
public ushort PlayEnd { get; set; }
|
|
public byte PlayBegin { get; set; }
|
|
public AwcCodecType Codec { get; set; }
|
|
public uint? UnkExtra { get; set; }
|
|
|
|
|
|
public AwcFormatChunk(AwcChunkInfo info) : base(info)
|
|
{ }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
Samples = r.ReadUInt32();
|
|
LoopPoint = r.ReadInt32();
|
|
SamplesPerSecond = r.ReadUInt16();
|
|
Headroom = r.ReadInt16();
|
|
LoopBegin = r.ReadUInt16();
|
|
LoopEnd = r.ReadUInt16();
|
|
PlayEnd = r.ReadUInt16();
|
|
PlayBegin = r.ReadByte();
|
|
Codec = (AwcCodecType)r.ReadByte();
|
|
|
|
switch (ChunkInfo.Size) //Apparently sometimes this struct is longer?
|
|
{
|
|
case 20:
|
|
break;
|
|
case 24:
|
|
UnkExtra = r.ReadUInt32();
|
|
break;
|
|
default:
|
|
break;//no hit
|
|
}
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
w.Write(Samples);
|
|
w.Write(LoopPoint);
|
|
w.Write(SamplesPerSecond);
|
|
w.Write(Headroom);
|
|
w.Write(LoopBegin);
|
|
w.Write(LoopEnd);
|
|
w.Write(PlayEnd);
|
|
w.Write(PlayBegin);
|
|
w.Write((byte)Codec);
|
|
if (UnkExtra.HasValue)
|
|
{
|
|
w.Write(UnkExtra.Value);
|
|
}
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
AwcXml.StringTag(sb, indent, "Codec", Codec.ToString());
|
|
AwcXml.ValueTag(sb, indent, "Samples", Samples.ToString());
|
|
AwcXml.ValueTag(sb, indent, "SampleRate", SamplesPerSecond.ToString());
|
|
AwcXml.ValueTag(sb, indent, "Headroom", Headroom.ToString());
|
|
AwcXml.ValueTag(sb, indent, "PlayBegin", PlayBegin.ToString());
|
|
AwcXml.ValueTag(sb, indent, "PlayEnd", PlayEnd.ToString());
|
|
AwcXml.ValueTag(sb, indent, "LoopBegin", LoopBegin.ToString());
|
|
AwcXml.ValueTag(sb, indent, "LoopEnd", LoopEnd.ToString());
|
|
if (UnkExtra.HasValue)
|
|
{
|
|
AwcXml.ValueTag(sb, indent, "UnkExtra", UnkExtra.Value.ToString());
|
|
}
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
Codec = Xml.GetChildEnumInnerText<AwcCodecType>(node, "Codec");
|
|
Samples = Xml.GetChildUIntAttribute(node, "Samples");
|
|
SamplesPerSecond = (ushort)Xml.GetChildUIntAttribute(node, "SampleRate");
|
|
Headroom = (short)Xml.GetChildIntAttribute(node, "Headroom");
|
|
PlayBegin = (byte)Xml.GetChildUIntAttribute(node, "PlayBegin");
|
|
PlayEnd = (ushort)Xml.GetChildUIntAttribute(node, "PlayEnd");
|
|
LoopBegin = (ushort)Xml.GetChildUIntAttribute(node, "LoopBegin");
|
|
LoopEnd = (ushort)Xml.GetChildUIntAttribute(node, "LoopEnd");
|
|
var unode = node.SelectSingleNode("UnkExtra");
|
|
if (unode != null)
|
|
{
|
|
UnkExtra = Xml.GetUIntAttribute(unode, "value");
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "format: " + Codec.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec, headroom: " + Headroom.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcStreamFormatChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => 12 + (Channels?.Length ?? 0) * 16;
|
|
|
|
public uint BlockCount { get; set; }
|
|
public uint BlockSize { get; set; }
|
|
public uint ChannelCount { get; set; }
|
|
public AwcStreamFormat[] Channels { get; set; }
|
|
|
|
public AwcStreamFormatChunk(AwcChunkInfo info) : base(info)
|
|
{ }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
BlockCount = r.ReadUInt32();
|
|
BlockSize = r.ReadUInt32();
|
|
ChannelCount = r.ReadUInt32();
|
|
|
|
var channels = new List<AwcStreamFormat>();
|
|
for (int i = 0; i < ChannelCount; i++)
|
|
{
|
|
var itemInfo = new AwcStreamFormat();
|
|
itemInfo.Read(r);
|
|
channels.Add(itemInfo);
|
|
}
|
|
Channels = channels.ToArray();
|
|
|
|
|
|
//switch (BlockSize)
|
|
//{
|
|
// case 1032192:
|
|
// case 524288:
|
|
// case 7225344:
|
|
// case 307200:
|
|
// break;
|
|
// default:
|
|
// break;//no hit
|
|
//}
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
ChannelCount = (uint)(Channels?.Length ?? 0);
|
|
|
|
w.Write(BlockCount);
|
|
w.Write(BlockSize);
|
|
w.Write(ChannelCount);
|
|
for (int i = 0; i < ChannelCount; i++)
|
|
{
|
|
Channels[i].Write(w);
|
|
}
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
AwcXml.ValueTag(sb, indent, "BlockSize", BlockSize.ToString());
|
|
//this is mostly just a placeholder. in XML, channel format is written with each channel stream
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
BlockSize = Xml.GetChildUIntAttribute(node, "BlockSize");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "streamformat: " + ChannelCount.ToString() + " channels, " + BlockCount.ToString() + " blocks, " + BlockSize.ToString() + " bytes per block";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcStreamFormat
|
|
{
|
|
public uint Id { get; set; }
|
|
public uint Samples { get; set; }
|
|
public short Headroom { get; set; }
|
|
public ushort SamplesPerSecond { get; set; }
|
|
public AwcCodecType Codec { get; set; } = AwcCodecType.ADPCM;
|
|
public byte Unused1 { get; set; }
|
|
public ushort Unused2 { get; set; }
|
|
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
Id = r.ReadUInt32();
|
|
Samples = r.ReadUInt32();
|
|
Headroom = r.ReadInt16();
|
|
SamplesPerSecond = r.ReadUInt16();
|
|
Codec = (AwcCodecType)r.ReadByte();
|
|
Unused1 = r.ReadByte();
|
|
Unused2 = r.ReadUInt16();
|
|
|
|
#region test
|
|
//switch (Codec)
|
|
//{
|
|
// case AwcCodecFormat.ADPCM:
|
|
// break;
|
|
// default:
|
|
// break;//no hit
|
|
//}
|
|
//switch (Unused1)
|
|
//{
|
|
// case 0:
|
|
// break;
|
|
// default:
|
|
// break;//no hit
|
|
//}
|
|
//switch (Unused2)
|
|
//{
|
|
// case 0:
|
|
// break;
|
|
// default:
|
|
// break;//no hit
|
|
//}
|
|
#endregion
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(Id);
|
|
w.Write(Samples);
|
|
w.Write(Headroom);
|
|
w.Write(SamplesPerSecond);
|
|
w.Write((byte)Codec);
|
|
w.Write(Unused1);
|
|
w.Write(Unused2);
|
|
}
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Codec", Codec.ToString());
|
|
AwcXml.ValueTag(sb, indent, "Samples", Samples.ToString());
|
|
AwcXml.ValueTag(sb, indent, "SampleRate", SamplesPerSecond.ToString());
|
|
AwcXml.ValueTag(sb, indent, "Headroom", Headroom.ToString());
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Codec = Xml.GetChildEnumInnerText<AwcCodecType>(node, "Codec");
|
|
Samples = Xml.GetChildUIntAttribute(node, "Samples");
|
|
SamplesPerSecond = (ushort)Xml.GetChildUIntAttribute(node, "SampleRate");
|
|
Headroom = (short)Xml.GetChildIntAttribute(node, "Headroom");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Id.ToString() + ", " + Codec.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec, headroom: " + Headroom.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcAnimationChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => Data?.Length ?? 0;
|
|
|
|
public byte[] Data { get; set; }
|
|
public ClipDictionary ClipDict { get; set; }
|
|
|
|
public AwcAnimationChunk(AwcChunkInfo info) : base(info)
|
|
{ }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
|
|
Data = r.ReadBytes(ChunkInfo.Size);
|
|
|
|
if ((Data == null) || (Data.Length < 16)) return;
|
|
|
|
var data = Data;
|
|
|
|
RpfResourceFileEntry resentry = new RpfResourceFileEntry();
|
|
uint rsc7 = BitConverter.ToUInt32(data, 0);
|
|
int version = BitConverter.ToInt32(data, 4);
|
|
resentry.SystemFlags = BitConverter.ToUInt32(data, 8);
|
|
resentry.GraphicsFlags = BitConverter.ToUInt32(data, 12);
|
|
|
|
if (rsc7 != 0x37435352)
|
|
{ } //testing..
|
|
if (version != 46) //46 is Clip Dictionary...
|
|
{ }
|
|
|
|
int newlen = data.Length - 16; //trim the header from the data passed to the next step.
|
|
int arrlen = Math.Max(newlen, resentry.SystemSize + resentry.GraphicsSize);//expand it as necessary for the reader.
|
|
byte[] newdata = new byte[arrlen];
|
|
Buffer.BlockCopy(data, 16, newdata, 0, newlen);
|
|
data = newdata;
|
|
|
|
ResourceDataReader rd = new ResourceDataReader(resentry, data);
|
|
|
|
ClipDict = rd.ReadBlock<ClipDictionary>();
|
|
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
|
|
w.Write(Data);
|
|
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
if (ClipDict != null)
|
|
{
|
|
AwcXml.OpenTag(sb, indent, "ClipDictionary");
|
|
ClipDict.WriteXml(sb, indent + 1);
|
|
AwcXml.CloseTag(sb, indent, "ClipDictionary");
|
|
}
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
var dnode = node.SelectSingleNode("ClipDictionary");
|
|
if (dnode != null)
|
|
{
|
|
ClipDict = new ClipDictionary();
|
|
ClipDict.ReadXml(dnode);
|
|
|
|
Data = ResourceBuilder.Build(ClipDict, 46, false); //ycd is 46...
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "animation: " + (ClipDict?.ClipsMapEntries ?? 0).ToString() + " entries";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcPeakChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => (Data?.Length ?? 0) * 2;
|
|
|
|
public ushort[] Data { get; set; }
|
|
|
|
public AwcPeakChunk(AwcChunkInfo info) : base(info)
|
|
{ }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
|
|
//if ((ChunkInfo.Size % 2) != 0)
|
|
//{ }//no hit
|
|
var count = ChunkInfo.Size / 2;
|
|
Data = new ushort[count];
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Data[i] = r.ReadUInt16();
|
|
}
|
|
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
if (Data != null)
|
|
{
|
|
for (int i = 0; i < Data.Length; i++)
|
|
{
|
|
w.Write(Data[i]);
|
|
}
|
|
}
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
AwcXml.WriteRawArray(sb, Data, indent, "Data", "");
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
Data = Xml.GetChildRawUshortArray(node, "Data");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
if (Data == null) return "";
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0; i < Data.Length; i++)
|
|
{
|
|
if (sb.Length > 0) sb.Append(' ');
|
|
sb.Append(Data[i].ToString());
|
|
}
|
|
return "peak: " + sb.ToString();
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcGestureChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => (Gestures?.Length ?? 0) * 36;
|
|
|
|
public Gesture[] Gestures { get; set; }
|
|
|
|
public class Gesture : IMetaXmlItem
|
|
{
|
|
public MetaHash Name { get; set; }
|
|
public uint UnkUint1 { get; set; }
|
|
public float UnkFloat1 { get; set; }
|
|
public float UnkFloat2 { get; set; }
|
|
public float UnkFloat3 { get; set; }
|
|
public float UnkFloat4 { get; set; }
|
|
public float UnkFloat5 { get; set; }
|
|
public float UnkFloat6 { get; set; }
|
|
public uint UnkUint2 { get; set; }
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
Name = r.ReadUInt32();
|
|
UnkUint1 = r.ReadUInt32();
|
|
UnkFloat1 = r.ReadSingle();
|
|
UnkFloat2 = r.ReadSingle();
|
|
UnkFloat3 = r.ReadSingle();
|
|
UnkFloat4 = r.ReadSingle();
|
|
UnkFloat5 = r.ReadSingle();
|
|
UnkFloat6 = r.ReadSingle();
|
|
UnkUint2 = r.ReadUInt32();
|
|
|
|
//switch (Name)
|
|
//{
|
|
// case 0xceda50be: //
|
|
// case 0x452c06fc: //
|
|
// case 0xba377ce2: //
|
|
// case 0xbe4c6d06: //
|
|
// case 0x9db051b4: //
|
|
// case 0x8a726e9f: //
|
|
// case 0x1f60ea95: //
|
|
// case 0x14e63a65: //
|
|
// case 0x32b4abf4: //
|
|
// case 0xe2b1dd62: //
|
|
// case 0x482d3572: //
|
|
// case 0x32f8d7a7: //
|
|
// case 0x9144296b: //
|
|
// case 0x3c73a8a4: //
|
|
// case 0x057c10c5: //
|
|
// case 0x981a4da0: //
|
|
// case 0x519d5f74: //
|
|
// case 0x0de43bc8: //
|
|
// case 0x89c16359: //
|
|
// case 0xd8884a6b: //
|
|
// case 0xfec7eb20: //
|
|
// case 0x06f0f709: //
|
|
// case 0x788a8abd: //
|
|
// break;
|
|
// default:
|
|
// break;//and more...
|
|
//}
|
|
|
|
//switch (UnkUint2)
|
|
//{
|
|
// case 1:
|
|
// case 0:
|
|
// break;
|
|
// default:
|
|
// break;//no hit
|
|
//}
|
|
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(Name);
|
|
w.Write(UnkUint1);
|
|
w.Write(UnkFloat1);
|
|
w.Write(UnkFloat2);
|
|
w.Write(UnkFloat3);
|
|
w.Write(UnkFloat4);
|
|
w.Write(UnkFloat5);
|
|
w.Write(UnkFloat6);
|
|
w.Write(UnkUint2);
|
|
}
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Name", AwcXml.HashString(Name));
|
|
AwcXml.ValueTag(sb, indent, "UnkUint1", UnkUint1.ToString());
|
|
AwcXml.ValueTag(sb, indent, "UnkFloat1", FloatUtil.ToString(UnkFloat1));
|
|
AwcXml.ValueTag(sb, indent, "UnkFloat2", FloatUtil.ToString(UnkFloat2));
|
|
AwcXml.ValueTag(sb, indent, "UnkFloat3", FloatUtil.ToString(UnkFloat3));
|
|
AwcXml.ValueTag(sb, indent, "UnkFloat4", FloatUtil.ToString(UnkFloat4));
|
|
AwcXml.ValueTag(sb, indent, "UnkFloat5", FloatUtil.ToString(UnkFloat5));
|
|
AwcXml.ValueTag(sb, indent, "UnkFloat6", FloatUtil.ToString(UnkFloat6));
|
|
AwcXml.ValueTag(sb, indent, "UnkUint2", UnkUint2.ToString());
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Name = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Name"));
|
|
UnkUint1 = Xml.GetChildUIntAttribute(node, "UnkUint1");
|
|
UnkFloat1 = Xml.GetChildFloatAttribute(node, "UnkFloat1");
|
|
UnkFloat2 = Xml.GetChildFloatAttribute(node, "UnkFloat2");
|
|
UnkFloat3 = Xml.GetChildFloatAttribute(node, "UnkFloat3");
|
|
UnkFloat4 = Xml.GetChildFloatAttribute(node, "UnkFloat4");
|
|
UnkFloat5 = Xml.GetChildFloatAttribute(node, "UnkFloat5");
|
|
UnkFloat6 = Xml.GetChildFloatAttribute(node, "UnkFloat6");
|
|
UnkUint2 = Xml.GetChildUIntAttribute(node, "UnkUint2");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Name.ToString() + ": " + UnkUint1.ToString() + ", " + UnkFloat1.ToString() + ", " + UnkFloat2.ToString() + ", " + UnkFloat3.ToString() + ", " + UnkFloat4.ToString() + ", " + UnkFloat5.ToString() + ", " + UnkFloat6.ToString() + ", " + UnkUint2.ToString();
|
|
}
|
|
}
|
|
|
|
public AwcGestureChunk(AwcChunkInfo info) : base(info)
|
|
{ }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
|
|
// (hash, uint, 6x floats, uint) * n
|
|
|
|
//if ((ChunkInfo.Size % 36) != 0)
|
|
//{ }//no hit
|
|
var count = ChunkInfo.Size / 36;
|
|
Gestures = new Gesture[count];
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var g = new Gesture();
|
|
g.Read(r);
|
|
Gestures[i] = g;
|
|
}
|
|
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
for (int i = 0; i < (Gestures?.Length ?? 0); i++)
|
|
{
|
|
Gestures[i].Write(w);
|
|
}
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
AwcXml.WriteItemArray(sb, Gestures, indent, "Gestures");
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
Gestures = XmlMeta.ReadItemArray<Gesture>(node, "Gestures");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "gesture: " + (Gestures?.Length ?? 0).ToString() + " items";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcGranularGrainsChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => 4 + (GranularGrains?.Length ?? 0) * 12;
|
|
|
|
public GranularGrain[] GranularGrains { get; set; }
|
|
public float UnkFloat1 { get; set; }
|
|
|
|
public class GranularGrain : IMetaXmlItem
|
|
{
|
|
public uint UnkUint1 { get; set; }
|
|
public float UnkFloat1 { get; set; }
|
|
public ushort UnkUshort1 { get; set; }
|
|
public ushort UnkUshort2 { get; set; }
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
UnkUint1 = r.ReadUInt32();
|
|
UnkFloat1 = r.ReadSingle();
|
|
UnkUshort1 = r.ReadUInt16();
|
|
UnkUshort2 = r.ReadUInt16();
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(UnkUint1);
|
|
w.Write(UnkFloat1);
|
|
w.Write(UnkUshort1);
|
|
w.Write(UnkUshort2);
|
|
}
|
|
public void WriteLine(StringBuilder sb)
|
|
{
|
|
sb.Append(UnkUint1.ToString());
|
|
sb.Append(" ");
|
|
sb.Append(FloatUtil.ToString(UnkFloat1));
|
|
sb.Append(" ");
|
|
sb.Append(UnkUshort1.ToString());
|
|
sb.Append(" ");
|
|
sb.Append(UnkUshort2.ToString());
|
|
sb.AppendLine();
|
|
}
|
|
public void ReadLine(string s)
|
|
{
|
|
var split = s.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
|
var list = new List<string>();
|
|
foreach (var str in split)
|
|
{
|
|
var tstr = str.Trim();
|
|
if (!string.IsNullOrEmpty(tstr))
|
|
{
|
|
list.Add(tstr);
|
|
}
|
|
}
|
|
if (list.Count >= 4)
|
|
{
|
|
uint.TryParse(list[0], out uint u1);
|
|
FloatUtil.TryParse(list[1], out float f1);
|
|
ushort.TryParse(list[2], out ushort s1);
|
|
ushort.TryParse(list[3], out ushort s2);
|
|
UnkUint1 = u1;
|
|
UnkFloat1 = f1;
|
|
UnkUshort1 = s1;
|
|
UnkUshort2 = s2;
|
|
}
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
UnkUint1 = Xml.GetChildUIntAttribute(node, "UnkUint1");
|
|
UnkFloat1 = Xml.GetChildFloatAttribute(node, "UnkFloat1");
|
|
UnkUshort1 = (ushort)Xml.GetChildUIntAttribute(node, "UnkUshort1");
|
|
UnkUshort2 = (ushort)Xml.GetChildUIntAttribute(node, "UnkUshort2");
|
|
}
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.ValueTag(sb, indent, "UnkUint1", UnkUint1.ToString());
|
|
AwcXml.ValueTag(sb, indent, "UnkFloat1", FloatUtil.ToString(UnkFloat1));
|
|
AwcXml.ValueTag(sb, indent, "UnkUshort1", UnkUshort1.ToString());
|
|
AwcXml.ValueTag(sb, indent, "UnkUshort2", UnkUshort2.ToString());
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return UnkUint1.ToString() + ", " + UnkFloat1.ToString() + ", " + UnkUshort1.ToString() + ", " + UnkUshort2.ToString();
|
|
}
|
|
}
|
|
|
|
public AwcGranularGrainsChunk(AwcChunkInfo info) : base(info)
|
|
{
|
|
}
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
|
|
//int, (2x floats, int) * n ?
|
|
|
|
//if ((ChunkInfo.Size % 12) != 4)
|
|
//{ }//no hit
|
|
var count = (ChunkInfo.Size - 4) / 12;
|
|
GranularGrains = new GranularGrain[count];
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var g = new GranularGrain();
|
|
g.Read(r);
|
|
GranularGrains[i] = g;
|
|
}
|
|
UnkFloat1 = r.ReadSingle();
|
|
|
|
//if (UnkFloat1 > 1.0f)
|
|
//{ }//no hit
|
|
//if (UnkFloat1 < 0.45833f)
|
|
//{ }//no hit
|
|
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
for (int i = 0; i < (GranularGrains?.Length ?? 0); i++)
|
|
{
|
|
GranularGrains[i].Write(w);
|
|
}
|
|
w.Write(UnkFloat1);
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
AwcXml.ValueTag(sb, indent, "UnkFloat1", FloatUtil.ToString(UnkFloat1));
|
|
//AwcXml.WriteCustomItemArray(sb, GranularGrains, indent, "GranularGrains");
|
|
if (GranularGrains != null)
|
|
{
|
|
AwcXml.OpenTag(sb, indent, "GranularGrains");
|
|
var cind = indent + 1;
|
|
foreach (var grain in GranularGrains)
|
|
{
|
|
AwcXml.Indent(sb, cind);
|
|
grain.WriteLine(sb);
|
|
}
|
|
AwcXml.CloseTag(sb, indent, "GranularGrains");
|
|
}
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
UnkFloat1 = Xml.GetChildFloatAttribute(node, "UnkFloat1");
|
|
//GranularGrains = XmlMeta.ReadItemArray<GranularGrain>(node, "GranularGrains");
|
|
var ggnode = node.SelectSingleNode("GranularGrains");
|
|
if (ggnode != null)
|
|
{
|
|
var gglist = new List<GranularGrain>();
|
|
var ggstr = ggnode.InnerText.Trim();
|
|
var ggstrs = ggstr.Split('\n');
|
|
foreach (var ggrstr in ggstrs)
|
|
{
|
|
var rstr = ggrstr.Trim();
|
|
var ggr = new GranularGrain();
|
|
ggr.ReadLine(rstr);
|
|
gglist.Add(ggr);
|
|
}
|
|
GranularGrains = gglist.ToArray();
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "granulargrains: " + (GranularGrains?.Length ?? 0).ToString() + " items";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcGranularLoopsChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize
|
|
{
|
|
get
|
|
{
|
|
int size = 4 + (GranularLoops?.Length ?? 0) * 12;
|
|
if (GranularLoops != null)
|
|
{
|
|
foreach (var loop in GranularLoops)
|
|
{
|
|
size += (loop?.Grains?.Length ?? 0) * 4;
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
}
|
|
|
|
public uint GranularLoopsCount { get; set; }
|
|
public GranularLoop[] GranularLoops { get; set; }
|
|
|
|
public class GranularLoop : IMetaXmlItem
|
|
{
|
|
public uint UnkUint1 { get; set; } = 2;
|
|
public uint GrainCount { get; set; }
|
|
public MetaHash Hash { get; set; } = 0x4c633d07;
|
|
public uint[] Grains { get; set; }
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
UnkUint1 = r.ReadUInt32();
|
|
GrainCount = r.ReadUInt32();
|
|
Hash = r.ReadUInt32();
|
|
Grains = new uint[GrainCount];
|
|
for (int i = 0; i < GrainCount; i++)
|
|
{
|
|
Grains[i] = r.ReadUInt32();
|
|
}
|
|
|
|
//switch (UnkUint1)
|
|
//{
|
|
// case 2:
|
|
// break;
|
|
// default:
|
|
// break;//no hit
|
|
//}
|
|
//switch (Hash)
|
|
//{
|
|
// case 0x4c633d07:
|
|
// break;
|
|
// default:
|
|
// break;//no hit
|
|
//}
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
GrainCount = (uint)(Grains?.Length ?? 0);
|
|
w.Write(UnkUint1);
|
|
w.Write(GrainCount);
|
|
w.Write(Hash);
|
|
for (int i = 0; i < GrainCount; i++)
|
|
{
|
|
w.Write(Grains[i]);
|
|
}
|
|
}
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
//AwcXml.ValueTag(sb, indent, "UnkUint1", UnkUint1.ToString());
|
|
//AwcXml.StringTag(sb, indent, "Hash", AwcXml.HashString(Hash));
|
|
AwcXml.WriteRawArray(sb, Grains, indent, "Grains", "");
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
//UnkUint1 = Xml.GetChildUIntAttribute(node, "UnkUint1");
|
|
//Hash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Hash"));
|
|
Grains = Xml.GetChildRawUintArray(node, "Grains");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Hash.ToString() + ": " + UnkUint1.ToString() + ": " + GrainCount.ToString() + " items";
|
|
}
|
|
|
|
}
|
|
|
|
public AwcGranularLoopsChunk(AwcChunkInfo info) : base(info)
|
|
{
|
|
}
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
|
|
//uint count
|
|
// [count*items]: uint(type?), uint(count2), hash, [count2*uint]
|
|
|
|
GranularLoopsCount = r.ReadUInt32();
|
|
GranularLoops = new GranularLoop[GranularLoopsCount];
|
|
for (int i = 0; i < GranularLoopsCount; i++)
|
|
{
|
|
var g = new GranularLoop();
|
|
g.Read(r);
|
|
GranularLoops[i] = g;
|
|
}
|
|
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
GranularLoopsCount = (uint)(GranularLoops?.Length ?? 0);
|
|
w.Write(GranularLoopsCount);
|
|
for (int i = 0; i < GranularLoopsCount; i++)
|
|
{
|
|
GranularLoops[i].Write(w);
|
|
}
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
AwcXml.WriteItemArray(sb, GranularLoops, indent, "GranularLoops");
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
GranularLoops = XmlMeta.ReadItemArray<GranularLoop>(node, "GranularLoops");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "granularloops: " + (GranularLoops?.Length ?? 0).ToString() + " items";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcMarkersChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => (Markers?.Length ?? 0) * 16;
|
|
|
|
public Marker[] Markers { get; set; }
|
|
|
|
public class Marker : IMetaXmlItem
|
|
{
|
|
public MetaHash Name { get; set; }
|
|
public MetaHash Value { get; set; }//usually a float, but in some cases a hash, or other value
|
|
public uint SampleOffset { get; set; }
|
|
public uint Unused { get; set; }
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
Name = r.ReadUInt32();
|
|
Value = r.ReadUInt32();
|
|
SampleOffset = r.ReadUInt32();
|
|
Unused = r.ReadUInt32();
|
|
|
|
//switch (Name)
|
|
//{
|
|
// case 0:
|
|
// case 0xa6d93246: // trackid
|
|
// case 0xe89ae78c: // beat
|
|
// case 0xf31b4f6a: // rockout
|
|
// case 0x08dba0f8: // dj
|
|
// case 0x7a495db3: // tempo
|
|
// case 0x14d857be: // g_s
|
|
// break;
|
|
// case 0xcd171e55: //
|
|
// case 0x806b80c9: // 1
|
|
// case 0x91aa2346: // 2
|
|
// case 0x11976678: // r_p
|
|
// case 0x91be54cb: //
|
|
// case 0xab2238c0: //
|
|
// case 0xdb599288: //
|
|
// case 0x2ce40eb5: //
|
|
// case 0xa35e1092: // 01
|
|
// case 0x1332b405: // tank_jump
|
|
// case 0x2b20b891: //
|
|
// case 0x8aa726e7: // tank_jump_land
|
|
// case 0xe0bfba99: // tank_turret_move
|
|
// case 0x1d91339e: //
|
|
// case 0xa5344b07: //
|
|
// case 0x7a7cba39: // tank_weapon_main_cannon_hit
|
|
// case 0xd66a90c3: //
|
|
// case 0x1fd18857: // 14
|
|
// case 0x65a52c67: //
|
|
// case 0xd8846402: // uihit
|
|
// case 0x8958bce4: // m_p
|
|
// if (Value != 0)
|
|
// { }//no hit
|
|
// break;
|
|
// default:
|
|
// break;//no hit
|
|
//}
|
|
|
|
//if (Unused != 0)
|
|
//{ }//no hit
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(Name);
|
|
w.Write(Value);
|
|
w.Write(SampleOffset);
|
|
w.Write(Unused);
|
|
}
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Name", AwcXml.HashString(Name));
|
|
switch (Name)
|
|
{
|
|
case 0xf31b4f6a: // rockout
|
|
case 0x08dba0f8: // dj
|
|
case 0x14d857be: // g_s
|
|
AwcXml.StringTag(sb, indent, "Value", AwcXml.HashString(Value));
|
|
break;
|
|
default:
|
|
AwcXml.ValueTag(sb, indent, "Value", FloatUtil.ToString(Value.Float));
|
|
break;
|
|
}
|
|
AwcXml.ValueTag(sb, indent, "SampleOffset", SampleOffset.ToString());
|
|
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Name = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Name"));
|
|
switch (Name)
|
|
{
|
|
case 0xf31b4f6a: // rockout
|
|
case 0x08dba0f8: // dj
|
|
case 0x14d857be: // g_s
|
|
Value = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Value"));
|
|
break;
|
|
default:
|
|
var f = Xml.GetChildFloatAttribute(node, "Value");
|
|
Value = MetaTypes.ConvertData<uint>(BitConverter.GetBytes(f));
|
|
break;
|
|
}
|
|
SampleOffset = Xml.GetChildUIntAttribute(node, "SampleOffset");
|
|
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
var valstr = Value.Float.ToString();
|
|
switch (Name)
|
|
{
|
|
case 0xf31b4f6a: // rockout
|
|
case 0x08dba0f8: // dj
|
|
case 0x14d857be: // g_s
|
|
valstr = Value.ToString();
|
|
break;
|
|
}
|
|
|
|
return Name.ToString() + ": " + valstr + ", " + SampleOffset.ToString() + ", " + Unused.ToString();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public AwcMarkersChunk(AwcChunkInfo info) : base(info)
|
|
{ }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
|
|
//if ((ChunkInfo.Size % 16) != 0)
|
|
//{ }//no hit
|
|
var count = ChunkInfo.Size / 16;
|
|
Markers = new Marker[count];
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var m = new Marker();
|
|
m.Read(r);
|
|
Markers[i] = m;
|
|
}
|
|
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
for (int i = 0; i < (Markers?.Length ?? 0); i++)
|
|
{
|
|
Markers[i].Write(w);
|
|
}
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
AwcXml.WriteItemArray(sb, Markers, indent, "Markers");
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
Markers = XmlMeta.ReadItemArray<Marker>(node, "Markers");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "markers: " + (Markers?.Length ?? 0).ToString() + " markers";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcMIDIChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => Data?.Length ?? 0;
|
|
|
|
public byte[] Data { get; set; }
|
|
|
|
public AwcMIDIChunk(AwcChunkInfo info) : base(info)
|
|
{
|
|
}
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
Data = r.ReadBytes(ChunkInfo.Size);
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
w.Write(Data);
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
//this is just a placeholder, as midi data will be written as a midi file
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "mid: " + (Data?.Length??0).ToString() + " bytes";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcSeekTableChunk : AwcChunk
|
|
{
|
|
public override int ChunkSize => (SeekTable?.Length ?? 0) * 4;
|
|
|
|
public uint[] SeekTable { get; set; }
|
|
|
|
public AwcSeekTableChunk(AwcChunkInfo info) : base(info)
|
|
{ }
|
|
|
|
public override void Read(DataReader r)
|
|
{
|
|
|
|
//if ((ChunkInfo.Size % 4) != 0)
|
|
//{ }//no hit
|
|
var count = ChunkInfo.Size / 4;
|
|
SeekTable = new uint[count];
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
SeekTable[i] = r.ReadUInt32();
|
|
}
|
|
|
|
}
|
|
public override void Write(DataWriter w)
|
|
{
|
|
for (int i = 0; i < (SeekTable?.Length ?? 0); i++)
|
|
{
|
|
w.Write(SeekTable[i]);
|
|
}
|
|
}
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
AwcXml.StringTag(sb, indent, "Type", ChunkInfo?.Type.ToString());
|
|
//this is just a placeholder, since the seek table will be built dynamically by CW.
|
|
}
|
|
public override void ReadXml(XmlNode node)
|
|
{
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "seektable: " + (SeekTable?.Length ?? 0).ToString() + " items";
|
|
}
|
|
}
|
|
|
|
|
|
[TC(typeof(EXP))] public class AwcStreamDataBlock
|
|
{
|
|
public int DataLength { get; set; }//just for convenience
|
|
public int SampleOffset { get; set; }//just for convenience
|
|
public uint ChannelCount { get; set; }//just for convenience
|
|
public AwcStreamFormatChunk ChannelInfo { get; set; } //just for convenience
|
|
public AwcStreamDataChannel[] Channels { get; set; }
|
|
|
|
public AwcStreamDataBlock()
|
|
{ }
|
|
public AwcStreamDataBlock(byte[] data, AwcStreamFormatChunk channelInfo, Endianess endianess, int sampleOffset)
|
|
{
|
|
DataLength = data?.Length ?? 0;
|
|
SampleOffset = sampleOffset;
|
|
ChannelCount = channelInfo?.ChannelCount ?? 0;
|
|
ChannelInfo = channelInfo;
|
|
|
|
using (var ms = new MemoryStream(data))
|
|
{
|
|
var r = new DataReader(ms, endianess);
|
|
Read(r);
|
|
}
|
|
|
|
}
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
var ilist = new List<AwcStreamDataChannel>();
|
|
for (int i = 0; i < ChannelCount; i++)
|
|
{
|
|
var channel = new AwcStreamDataChannel();
|
|
channel.Read(r);
|
|
ilist.Add(channel);
|
|
}
|
|
Channels = ilist.ToArray();
|
|
|
|
foreach (var channel in Channels)
|
|
{
|
|
channel.ReadOffsets(r);
|
|
}
|
|
|
|
var padc = (0x800 - (r.Position % 0x800)) % 0x800;
|
|
var padb = r.ReadBytes((int)padc);
|
|
|
|
foreach (var channel in Channels)
|
|
{
|
|
channel.ReadData(r);
|
|
}
|
|
|
|
//if (r.Position != r.Length)
|
|
//{ }//still more, just padding?
|
|
|
|
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
foreach (var channel in Channels)
|
|
{
|
|
channel.Write(w);
|
|
}
|
|
foreach (var channel in Channels)
|
|
{
|
|
channel.WriteOffsets(w);
|
|
}
|
|
var padc = (0x800 - (w.Position % 0x800)) % 0x800;
|
|
if (padc > 0)
|
|
{
|
|
w.Write(new byte[padc]);
|
|
}
|
|
foreach (var channel in Channels)
|
|
{
|
|
channel.WriteData(w);
|
|
}
|
|
}
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
return DataLength.ToString() + " bytes";
|
|
}
|
|
}
|
|
|
|
[TC(typeof(EXP))] public class AwcStreamDataChannel
|
|
{
|
|
public int StartBlock { get; set; }
|
|
public int BlockCount { get; set; }
|
|
public int Unused1 { get; set; }
|
|
public int SampleCount { get; set; }
|
|
public int Unused2 { get; set; }
|
|
public int Unused3 { get; set; }
|
|
|
|
public int[] SampleOffsets { get; set; }
|
|
public byte[] Data { get; set; }
|
|
|
|
|
|
|
|
public void Read(DataReader r)
|
|
{
|
|
StartBlock = r.ReadInt32();
|
|
BlockCount = r.ReadInt32();
|
|
Unused1 = r.ReadInt32();
|
|
SampleCount = r.ReadInt32();
|
|
Unused2 = r.ReadInt32();
|
|
Unused3 = r.ReadInt32();
|
|
|
|
//if (Unused1 != 0)
|
|
//{ }//no hit
|
|
//if (Unused2 != 0)
|
|
//{ }//no hit
|
|
//if (Unused3 != 0)
|
|
//{ }//no hit
|
|
}
|
|
public void Write(DataWriter w)
|
|
{
|
|
w.Write(StartBlock);
|
|
w.Write(BlockCount);
|
|
w.Write(Unused1);
|
|
w.Write(SampleCount);
|
|
w.Write(Unused2);
|
|
w.Write(Unused3);
|
|
}
|
|
|
|
public void ReadOffsets(DataReader r)
|
|
{
|
|
var olist = new List<int>();
|
|
for (int i = 0; i < BlockCount; i++)
|
|
{
|
|
var v = r.ReadInt32();
|
|
olist.Add(v);
|
|
}
|
|
SampleOffsets = olist.ToArray();
|
|
}
|
|
public void WriteOffsets(DataWriter w)
|
|
{
|
|
var smpoc = SampleOffsets?.Length ?? 0;
|
|
for (int i = 0; i < BlockCount; i++)
|
|
{
|
|
w.Write((i < smpoc) ? SampleOffsets[i] : 0);
|
|
}
|
|
}
|
|
|
|
public void ReadData(DataReader r)
|
|
{
|
|
var bcnt = BlockCount * 2048;
|
|
Data = r.ReadBytes(bcnt);
|
|
}
|
|
public void WriteData(DataWriter w)
|
|
{
|
|
w.Write(Data);
|
|
}
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
return StartBlock.ToString() + ": " + BlockCount.ToString() + ", " + Unused1.ToString() + ", " + SampleCount.ToString() + ", " + Unused2.ToString() + ", " + Unused3.ToString();
|
|
}
|
|
}
|
|
|
|
|
|
public class ADPCMCodec
|
|
{
|
|
|
|
private static int[] ima_index_table =
|
|
{
|
|
-1, -1, -1, -1, 2, 4, 6, 8,
|
|
-1, -1, -1, -1, 2, 4, 6, 8
|
|
};
|
|
|
|
private static short[] ima_step_table =
|
|
{
|
|
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
|
|
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
|
|
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
|
|
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
|
|
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
|
|
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
|
|
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
|
|
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
|
|
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
|
|
};
|
|
|
|
private static int clip(int value, int min, int max)
|
|
{
|
|
if (value < min) return min;
|
|
if (value > max) return max;
|
|
return value;
|
|
}
|
|
|
|
public static byte[] DecodeADPCM(byte[] data, int sampleCount)
|
|
{
|
|
byte[] dataPCM = new byte[data.Length * 4];
|
|
int predictor = 0, stepIndex = 0;
|
|
int readingOffset = 0, writingOffset = 0, bytesInBlock = 0;
|
|
|
|
void parseNibble(byte nibble)
|
|
{
|
|
var step = ima_step_table[stepIndex];
|
|
int diff = ((((nibble & 7) << 1) + 1) * step) >> 3;
|
|
if ((nibble & 8) != 0) diff = -diff;
|
|
predictor = predictor + diff;
|
|
stepIndex = clip(stepIndex + ima_index_table[nibble], 0, 88);
|
|
int samplePCM = clip(predictor, -32768, 32767);
|
|
|
|
dataPCM[writingOffset] = (byte)(samplePCM & 0xFF);
|
|
dataPCM[writingOffset + 1] = (byte)((samplePCM >> 8) & 0xFF);
|
|
writingOffset += 2;
|
|
}
|
|
|
|
while ((readingOffset < data.Length) && (sampleCount > 0))
|
|
{
|
|
if (bytesInBlock == 0)
|
|
{
|
|
stepIndex = clip(data[readingOffset], 0, 88);
|
|
predictor = BitConverter.ToInt16(data, readingOffset + 2);
|
|
bytesInBlock = 2044;
|
|
readingOffset += 4;
|
|
}
|
|
else
|
|
{
|
|
parseNibble((byte)(data[readingOffset] & 0x0F));
|
|
parseNibble((byte)((data[readingOffset] >> 4) & 0x0F));
|
|
bytesInBlock--;
|
|
sampleCount -= 2;
|
|
readingOffset++;
|
|
}
|
|
}
|
|
|
|
return dataPCM;
|
|
}
|
|
|
|
public static byte[] EncodeADPCM(byte[] data, int sampleCount)
|
|
{
|
|
byte[] dataPCM = new byte[data.Length / 4];
|
|
|
|
int predictor = 0, stepIndex = 0;
|
|
int readingOffset = 0, writingOffset = 0, bytesInBlock = 0;
|
|
|
|
short readSample()
|
|
{
|
|
var s = BitConverter.ToInt16(data, readingOffset);
|
|
readingOffset += 2;
|
|
return s;
|
|
}
|
|
|
|
void writeInt16(short v)
|
|
{
|
|
var ba = BitConverter.GetBytes(v);
|
|
dataPCM[writingOffset++] = ba[0];
|
|
dataPCM[writingOffset++] = ba[1];
|
|
}
|
|
|
|
byte encodeNibble(int pcm16)
|
|
{
|
|
int delta = pcm16 - predictor;
|
|
uint value = 0;
|
|
if (delta < 0)
|
|
{
|
|
value = 8;
|
|
delta = -delta;
|
|
}
|
|
|
|
var step = ima_step_table[stepIndex];
|
|
var diff = step >> 3;
|
|
if (delta > step)
|
|
{
|
|
value |= 4;
|
|
delta -= step;
|
|
diff += step;
|
|
}
|
|
step >>= 1;
|
|
if (delta > step)
|
|
{
|
|
value |= 2;
|
|
delta -= step;
|
|
diff += step;
|
|
}
|
|
step >>= 1;
|
|
if (delta > step)
|
|
{
|
|
value |= 1;
|
|
diff += step;
|
|
}
|
|
|
|
predictor += (((value & 8) != 0) ? -diff : diff);
|
|
predictor = clip(predictor, short.MinValue, short.MaxValue);
|
|
|
|
stepIndex += ima_index_table[value & 7];
|
|
stepIndex = clip(stepIndex, 0, 88);
|
|
|
|
return (byte)value;
|
|
}
|
|
|
|
while ((writingOffset < dataPCM.Length) && (sampleCount > 0))
|
|
{
|
|
if (bytesInBlock == 0)
|
|
{
|
|
writeInt16((short)stepIndex);
|
|
writeInt16((short)predictor);
|
|
bytesInBlock = 2044;
|
|
}
|
|
else
|
|
{
|
|
var s0 = readSample();
|
|
var s1 = readSample();
|
|
var b0 = encodeNibble(s0);
|
|
var b1 = encodeNibble(s1);
|
|
var b = (b0 & 0x0F) + ((b1 & 0x0F) << 4);
|
|
dataPCM[writingOffset++] = (byte)b;
|
|
bytesInBlock--;
|
|
sampleCount -= 2;
|
|
}
|
|
}
|
|
|
|
return dataPCM;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class AwcXml : MetaXmlBase
|
|
{
|
|
|
|
public static string GetXml(AwcFile awc, string outputFolder = "")
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.AppendLine(XmlHeader);
|
|
|
|
if (awc != null)
|
|
{
|
|
AwcFile.WriteXmlNode(awc, sb, 0, outputFolder);
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
}
|
|
|
|
public class XmlAwc
|
|
{
|
|
|
|
public static AwcFile AwcYft(string xml, string inputFolder = "")
|
|
{
|
|
XmlDocument doc = new XmlDocument();
|
|
doc.LoadXml(xml);
|
|
return GetAwc(doc, inputFolder);
|
|
}
|
|
|
|
public static AwcFile GetAwc(XmlDocument doc, string inputFolder = "")
|
|
{
|
|
AwcFile r = null;
|
|
|
|
var node = doc.DocumentElement;
|
|
if (node != null)
|
|
{
|
|
r = AwcFile.ReadXmlNode(node, inputFolder);
|
|
}
|
|
|
|
r.Name = Path.GetFileName(inputFolder);
|
|
|
|
return r;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|