AWC improvements

This commit is contained in:
dexy 2020-02-07 04:02:55 +11:00
parent 4020327e64
commit 668e8a2841
4 changed files with 490 additions and 223 deletions

View File

@ -23,9 +23,15 @@ namespace CodeWalker.GameFiles
public int StreamCount { get; set; } public int StreamCount { get; set; }
public int InfoOffset { get; set; } public int InfoOffset { get; set; }
public bool UnkUshortsFlag { get { return ((Flags & 1) == 1); } }
public bool SingleChannelEncryptFlag { get { return ((Flags & 2) == 2); } }
public bool MultiChannelFlag { get { return ((Flags & 4) == 4); } }
public bool MultiChannelEncryptFlag { get { return ((Flags & 8) == 8); } }
public ushort[] UnkUshorts { get; set; } //offsets of some sort? public ushort[] UnkUshorts { get; set; } //offsets of some sort?
public bool MultiChannel { get; set; }
public bool WholeFileEncrypted { get; set; }
public AwcStreamInfo[] StreamInfos { get; set; } public AwcStreamInfo[] StreamInfos { get; set; }
public uint[] AudioIds { get; set; } public uint[] AudioIds { get; set; }
@ -80,6 +86,7 @@ namespace CodeWalker.GameFiles
{ {
Decrypt_RSXXTEA(data, GTA5Keys.PC_AWC_KEY); Decrypt_RSXXTEA(data, GTA5Keys.PC_AWC_KEY);
Magic = BitConverter.ToUInt32(data, 0); Magic = BitConverter.ToUInt32(data, 0);
WholeFileEncrypted = true;
} }
else else
{ {
@ -111,27 +118,13 @@ namespace CodeWalker.GameFiles
InfoOffset = r.ReadInt32(); InfoOffset = r.ReadInt32();
//notes from libertyV:
// first bit - means that there are unknown word for each stream after this header
// second bit - I think that it means that not all the tags are in the start of the file, but all the tags of a stream are near the data tag
// third bit - Multi channel audio
if ((Flags >> 8) != 0xFF) if ((Flags >> 8) != 0xFF)
{ {
ErrorMessage = "Flags 0 not supported!"; ErrorMessage = "Flags 0 not supported!";
return; return;
} }
if ((Flags & 0xF8) != 0)
{
//ErrorMessage = "Flags 1 not supported!";
//return;
}
MultiChannel = ((Flags & 4) == 4); if (UnkUshortsFlag)
var flag0 = ((Flags & 1) == 1);
if (flag0)
{ {
UnkUshorts = new ushort[StreamCount]; //offsets of some sort? UnkUshorts = new ushort[StreamCount]; //offsets of some sort?
for (int i = 0; i < StreamCount; i++) for (int i = 0; i < StreamCount; i++)
@ -141,9 +134,9 @@ namespace CodeWalker.GameFiles
} }
var infoStart = 16 + (flag0 ? (StreamCount * 2) : 0); var infoStart = 16 + (UnkUshortsFlag ? (StreamCount * 2) : 0);
if (ms.Position != infoStart) if (ms.Position != infoStart)
{ } { }//no hit
ms.Position = infoStart; ms.Position = infoStart;
@ -169,8 +162,9 @@ namespace CodeWalker.GameFiles
StreamInfos = infos.ToArray(); StreamInfos = infos.ToArray();
//ReadAudiosOrig(infoDict, r);
ReadAudios(r); ReadAudios(r);
} }
} }
@ -179,6 +173,7 @@ namespace CodeWalker.GameFiles
List<uint> audioIds = new List<uint>(); List<uint> audioIds = new List<uint>();
List<AwcAudio> audios = new List<AwcAudio>(); List<AwcAudio> audios = new List<AwcAudio>();
AwcAudio multiaudio = null;
for (int i = 0; i < StreamCount; i++) for (int i = 0; i < StreamCount; i++)
{ {
@ -211,17 +206,17 @@ namespace CodeWalker.GameFiles
case 90: //unk5A case 90: //unk5A
audio.UnkData5A = new AwcAudioUnk(r, chunk); audio.UnkData5A = new AwcAudioUnk(r, chunk);
break; break;
case 104://unk68 case 104://MIDI
audio.UnkData68 = new AwcAudioUnk(r, chunk); audio.MIDIData = new AwcAudioMIDI(r, chunk);
break; break;
case 217://unkD9 - length 200+ case 217://unkD9 - length 200+
audio.UnkDataD9 = new AwcAudioUnk(r, chunk); audio.UnkDataD9 = new AwcAudioUnk(r, chunk);
break; break;
case 72: //multi channel info case 72: //multi channel info
audio.MultiChannelInfo = new AwcChannelChunkInfo(r); audio.MultiChannelInfo = new AwcChannelBlockInfo(r);
break; break;
case 163://multi unkA3 case 163://multi unkA3
audio.MultiChannelOffsets = new AwcChannelOffsetsInfo(r, chunk); audio.MultiChannelSampleOffsets = new AwcChannelOffsetsInfo(r, chunk);
break; break;
case 189://multi unkBD case 189://multi unkBD
audio.UnkDataBD = new AwcAudioUnk(r, chunk); audio.UnkDataBD = new AwcAudioUnk(r, chunk);
@ -233,75 +228,95 @@ namespace CodeWalker.GameFiles
{ }//make sure everything was read! { }//make sure everything was read!
} }
//decrypt data where necessary
if ((audio.Data != null) && (!WholeFileEncrypted))
{
if (MultiChannelFlag)
{
var ocount = (int)(audio.MultiChannelSampleOffsets?.SampleOffsets?.Length ?? 0);
var ccount = (int)(audio.MultiChannelInfo?.ChannelCount ?? 0);
var bcount = (int)(audio.MultiChannelInfo?.BlockCount ?? 0);
var bsize = (int)(audio.MultiChannelInfo?.BlockSize ?? 0);
var blist = new List<AwcChannelDataBlock>();
for (int b = 0; b < bcount; b++)
{
int srcoff = b * bsize;
int mcsoff = (b < ocount) ? (int)audio.MultiChannelSampleOffsets.SampleOffsets[b] : 0;
int blen = Math.Max(Math.Min(bsize, audio.Data.Length - srcoff), 0);
var bdat = new byte[blen];
Buffer.BlockCopy(audio.Data, srcoff, bdat, 0, blen);
if (MultiChannelEncryptFlag)
{
AwcFile.Decrypt_RSXXTEA(bdat, GTA5Keys.PC_AWC_KEY);
}
var blk = new AwcChannelDataBlock(bdat, audio.MultiChannelInfo, r.Endianess, b, mcsoff);
blist.Add(blk);
}
audio.MultiChannelBlocks = blist.ToArray();
multiaudio = audio;
}
else
{
if (SingleChannelEncryptFlag)
{
AwcFile.Decrypt_RSXXTEA(audio.Data, GTA5Keys.PC_AWC_KEY);
}
}
}
if ((audio.Data != null) && WholeFileEncrypted && MultiChannelFlag)
{ }//no hit
audios.Add(audio); audios.Add(audio);
audioIds.Add(info.Id); audioIds.Add(info.Id);
} }
if (multiaudio != null)
{
var multichaninfos = multiaudio.MultiChannelInfo;
for (int i = 0; i < audios.Count; i++)
{
var audio = audios[i];
if (audio != multiaudio)
{
var id = audio.StreamInfo?.Id ?? 0;
var srcind = 0;
var chancnt = multiaudio.MultiChannelInfo?.Channels?.Length ?? 0;
var found = false;
for (int ind = 0; ind < chancnt; ind++)
{
var mchan = multiaudio.MultiChannelInfo.Channels[ind];
if (mchan.Id == id)
{
srcind = ind;
found = true;
break;
}
}
if (!found)
{ }//no hit
audio.MultiChannelSource = multiaudio;
audio.MultiChannelFormat = (srcind < multichaninfos?.Channels?.Length) ? multichaninfos.Channels[srcind] : null;
audio.MultiChannelIndex = srcind;
}
}
}
Audios = audios.ToArray(); Audios = audios.ToArray();
AudioIds = audioIds.ToArray(); AudioIds = audioIds.ToArray();
} }
private void ReadAudiosOrig(Dictionary<uint, AwcStreamInfo> infoDict, DataReader r)
{
//just leaving this here temporarily for reference about how libertyV parsed multichannel data
//r.Position = cdata.Offset;
//var lastPos = cdata.Offset + cdata.Size;
////int chunkSize = 0x800;
//uint bigChunkSize = chanInfo.ChunkSize;
//var chanCount = chanInfo.ChannelCount;
//MultiChannelData = r.ReadBytes(cdata.Size);
//r.Position = cdata.Offset;
////this doesn't seem to work :(
//while (ms.Position < lastPos)
//{
// uint totalChunks = 0;
// var startPos = ms.Position;
// var curPos = startPos;
// //byte[] chunkdata = r.ReadBytes(chunkSize);
// //ms.Position = startPos;
// AwcChannelChunkHeader[] chanHeaders = new AwcChannelChunkHeader[chanCount];
// for (int i = 0; i < chanCount; i++)
// {
// var chanHeader = new AwcChannelChunkHeader(r);
// chanHeaders[i] = chanHeader;
// totalChunks += chanHeader.ChunkCount;
// }
// int headerSize = (int)(totalChunks * 4 + chanInfo.ChannelCount * AwcChannelChunkHeader.Size);
// headerSize += (((-headerSize) % chunkSize) + chunkSize) % chunkSize; //todo: simplify this!
// curPos += headerSize;
// AwcChannelChunk[] chanChunks = new AwcChannelChunk[chanCount];
// for (int i = 0; i < chanCount; i++)
// {
// var chanChunk = new AwcChannelChunk(r, chanHeaders[i], chunkItems[i]);
// chanChunks[i] = chanChunk;
// curPos += chanChunk.TotalDataSize;
// }
// if (curPos - startPos > chanInfo.ChunkSize)
// {
// ErrorMessage = "Chunk was bigger than the chunk size";
// break;
// }
// if ((totalChunks == 0) || ((startPos + chanInfo.ChunkSize) > lastPos))
// {
// ErrorMessage = "Unable to read chunk";
// break;
// }
// var newPos = startPos + bigChunkSize;
// if (newPos >= lastPos) break;
// ms.Position = newPos;
//}
}
} }
public enum AwcCodecFormat
{
PCM = 0,
ADPCM = 4
}
[TC(typeof(EXP))] public class AwcStreamInfo [TC(typeof(EXP))] public class AwcStreamInfo
{ {
@ -346,25 +361,25 @@ namespace CodeWalker.GameFiles
} }
} }
[TC(typeof(EXP))] public class AwcChannelChunkInfo [TC(typeof(EXP))] public class AwcChannelBlockInfo
{ {
public uint ChunkCount { get; set; } public uint BlockCount { get; set; }
public uint ChunkSize { get; set; } public uint BlockSize { get; set; }
public uint ChannelCount { get; set; } public uint ChannelCount { get; set; }
public AwcChannelChunkItemInfo[] Channels { get; set; } public AwcChannelBlockItemInfo[] Channels { get; set; }
public uint TotalSize { get { return ChunkCount * ChunkSize; } } public uint TotalSize { get { return BlockCount * BlockSize; } }
public AwcChannelChunkInfo(DataReader r) public AwcChannelBlockInfo(DataReader r)
{ {
ChunkCount = r.ReadUInt32(); BlockCount = r.ReadUInt32();
ChunkSize = r.ReadUInt32(); BlockSize = r.ReadUInt32();
ChannelCount = r.ReadUInt32(); ChannelCount = r.ReadUInt32();
List<AwcChannelChunkItemInfo> channels = new List<AwcChannelChunkItemInfo>(); List<AwcChannelBlockItemInfo> channels = new List<AwcChannelBlockItemInfo>();
for (int i = 0; i < ChannelCount; i++) for (int i = 0; i < ChannelCount; i++)
{ {
var itemInfo = new AwcChannelChunkItemInfo(r); var itemInfo = new AwcChannelBlockItemInfo(r);
channels.Add(itemInfo); channels.Add(itemInfo);
} }
Channels = channels.ToArray(); Channels = channels.ToArray();
@ -373,34 +388,107 @@ namespace CodeWalker.GameFiles
public override string ToString() public override string ToString()
{ {
return ChunkCount.ToString() + ": " + ChunkSize.ToString() + ", " + ChannelCount.ToString() + " channels"; return BlockCount.ToString() + ": " + BlockSize.ToString() + ", " + ChannelCount.ToString() + " channels";
} }
} }
[TC(typeof(EXP))] public class AwcChannelChunkItemInfo [TC(typeof(EXP))] public class AwcChannelBlockItemInfo
{ {
public uint Id { get; set; } public uint Id { get; set; }
public uint Samples { get; set; } public uint Samples { get; set; }
public ushort Unk0 { get; set; } public ushort Unk0 { get; set; }//flags?
public ushort SamplesPerSecond { get; set; } public ushort SamplesPerSecond { get; set; }
public byte Unk1 { get; set; } public AwcCodecFormat Codec { get; set; }
public byte RoundSize { get; set; } public byte RoundSize { get; set; }
public ushort Unk2 { get; set; } public ushort Unk2 { get; set; }
public AwcChannelChunkItemInfo(DataReader r) public AwcChannelBlockItemInfo(DataReader r)
{ {
Id = r.ReadUInt32(); Id = r.ReadUInt32();
Samples = r.ReadUInt32(); Samples = r.ReadUInt32();
Unk0 = r.ReadUInt16(); Unk0 = r.ReadUInt16();
SamplesPerSecond = r.ReadUInt16(); SamplesPerSecond = r.ReadUInt16();
Unk1 = r.ReadByte(); Codec = (AwcCodecFormat)r.ReadByte();
RoundSize = r.ReadByte(); RoundSize = r.ReadByte();
Unk2 = r.ReadUInt16(); Unk2 = r.ReadUInt16();
//switch (Unk0)
//{
// case 65395:
// case 65485:
// case 33:
// case 9:
// case 674:
// case 529:
// case 11:
// case 105:
// case 65396:
// case 437:
// case 477:
// case 65475:
// case 519:
// case 349:
// case 65517:
// case 106:
// case 219:
// case 58:
// case 63:
// case 65530:
// case 65511:
// case 191:
// case 65439:
// case 65345:
// case 560:
// case 640:
// case 65335:
// case 208:
// case 392:
// case 1968:
// case 2257:
// case 1101:
// case 906:
// case 1087:
// case 718:
// case 65362:
// case 259:
// case 234:
// case 299:
// case 307:
// case 75:
// case 67:
// case 4:
// case 290:
// case 587:
// case 65448:
// case 15:
// case 398:
// case 399:
// case 65375:
// case 65376:
// case 65336:
// break;
// default:
// break;//more... flags?
//}
//switch (Codec)
//{
// case AwcCodecFormat.ADPCM:
// break;
// default:
// break;//no hit
//}
//switch (Unk2)
//{
// case 0:
// break;
// default:
// break;//no hit
//}
} }
public override string ToString() public override string ToString()
{ {
return Id.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec, size: " + RoundSize.ToString(); return Id.ToString() + ", " + Codec.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec, size: " + RoundSize.ToString() + " unk0: " + Unk0.ToString();
} }
} }
@ -416,12 +504,7 @@ namespace CodeWalker.GameFiles
public byte PlayBegin { get; set; } public byte PlayBegin { get; set; }
public uint UnkExtra { get; set; } public uint UnkExtra { get; set; }
public enum CodecFormat public AwcCodecFormat Codec { get; set; }
{
PCM = 0,
ADPCM = 4
}
public CodecFormat Codec { get; set; }
public AwcFormatChunk(DataReader r, AwcChunkInfo info) public AwcFormatChunk(DataReader r, AwcChunkInfo info)
{ {
@ -433,7 +516,7 @@ namespace CodeWalker.GameFiles
LoopEnd = r.ReadUInt16(); LoopEnd = r.ReadUInt16();
PlayEnd = r.ReadUInt16(); PlayEnd = r.ReadUInt16();
PlayBegin = r.ReadByte(); PlayBegin = r.ReadByte();
Codec = (CodecFormat)r.ReadByte(); Codec = (AwcCodecFormat)r.ReadByte();
switch (info.Size) //Apparently sometimes this struct is longer? switch (info.Size) //Apparently sometimes this struct is longer?
{ {
@ -466,26 +549,31 @@ namespace CodeWalker.GameFiles
public AwcAudioUnk UnkData2B { get; set; } public AwcAudioUnk UnkData2B { get; set; }
public AwcAudioUnk UnkData36 { get; set; } public AwcAudioUnk UnkData36 { get; set; }
public AwcAudioUnk UnkData5A { get; set; } public AwcAudioUnk UnkData5A { get; set; }
public AwcAudioUnk UnkData68 { get; set; } public AwcAudioMIDI MIDIData { get; set; }
public AwcAudioUnk UnkDataD9 { get; set; } public AwcAudioUnk UnkDataD9 { get; set; }
public AwcChannelChunkInfo MultiChannelInfo { get; set; } public AwcChannelBlockInfo MultiChannelInfo { get; set; }
public AwcChannelOffsetsInfo MultiChannelOffsets { get; set; } public AwcChannelOffsetsInfo MultiChannelSampleOffsets { get; set; }
public AwcChannelDataBlock[] MultiChannelBlocks { get; set; }
public AwcAudioUnk UnkDataBD { get; set; } public AwcAudioUnk UnkDataBD { get; set; }
public AwcAudio MultiChannelSource { get; set; }
public AwcChannelBlockItemInfo MultiChannelFormat { get; set; }
public int MultiChannelIndex { get; set; }
public short Channels = 1; public short Channels = 1;
public short BitsPerSample = 16; public short BitsPerSample = 16;
public int SamplesPerSecond public int SamplesPerSecond
{ {
get get
{ {
return Format?.SamplesPerSecond ?? 0; return Format?.SamplesPerSecond ?? MultiChannelFormat?.SamplesPerSecond ?? 0;
} }
} }
public int SampleCount public int SampleCount
{ {
get get
{ {
return (int)(Format?.Samples ?? 0); return (int)(Format?.Samples ?? MultiChannelFormat?.Samples ?? 0);
} }
} }
@ -501,24 +589,34 @@ namespace CodeWalker.GameFiles
{ {
get get
{ {
if (Format == null) return "Unknown"; if (MIDIData != null)
string codec;
switch (Format.Codec)
{ {
case AwcFormatChunk.CodecFormat.PCM: return "MIDI";
codec = "PCM"; }
break;
case AwcFormatChunk.CodecFormat.ADPCM: var fc = AwcCodecFormat.PCM;
codec = "ADPCM"; var hz = 0;
if (Format != null)
{
fc = Format.Codec;
hz = Format.SamplesPerSecond;
}
if (MultiChannelFormat != null)
{
fc = MultiChannelFormat.Codec;
hz = MultiChannelFormat.SamplesPerSecond;
}
string codec = fc.ToString();
switch (fc)
{
case AwcCodecFormat.PCM:
case AwcCodecFormat.ADPCM:
break; break;
default: default:
codec = "Unknown"; codec = "Unknown";
break; break;
} }
var hz = Format.SamplesPerSecond;
return codec + ((hz > 0) ? (", " + hz.ToString() + " Hz") : ""); return codec + ((hz > 0) ? (", " + hz.ToString() + " Hz") : "");
} }
} }
@ -527,10 +625,11 @@ namespace CodeWalker.GameFiles
{ {
get get
{ {
return Format == null ? 0 : (float)Format.Samples / Format.SamplesPerSecond; if (Format != null) return (float)Format.Samples / Format.SamplesPerSecond;
if (MultiChannelFormat != null) return (float)MultiChannelFormat.Samples / MultiChannelFormat.SamplesPerSecond;
return 0;
} }
} }
public string LengthStr public string LengthStr
{ {
get get
@ -539,6 +638,39 @@ namespace CodeWalker.GameFiles
} }
} }
public int ByteLength
{
get
{
if (MIDIData?.Data != null)
{
return MIDIData.Data?.Length ?? 0;
}
if (Data != null)
{
return Data.Length;
}
if (MultiChannelSource != null)
{
int c = 0;
if (MultiChannelSource?.MultiChannelBlocks != null)
{
foreach (var blk in MultiChannelSource.MultiChannelBlocks)
{
if (MultiChannelIndex < (blk?.Channels?.Length ?? 0))
{
var chan = blk.Channels[MultiChannelIndex];
c += chan?.ChannelData?.Length ?? 0;
}
}
}
return c;
}
return 0;
}
}
public AwcAudio() public AwcAudio()
{ {
} }
@ -553,8 +685,20 @@ namespace CodeWalker.GameFiles
public override string ToString() public override string ToString()
{ {
var hash = (StreamInfo?.Id.ToString("X") ?? "0").PadLeft(8, '0'); var hash = "0x" + (StreamInfo?.Id.ToString("X") ?? "0").PadLeft(8, '0') + ": ";
return "0x" + hash + ": " + Format?.ToString() ?? "AwcAudio"; if (Format != null)
{
return hash + Format?.ToString() ?? "AwcAudio";
}
if (MultiChannelFormat != null)
{
return hash + MultiChannelFormat?.ToString() ?? "AwcAudio";
}
if (MIDIData != null)
{
return hash + MIDIData.ToString();
}
return hash + "Unknown";
} }
public byte[] DecodeADPCM(byte[] data, int sampleCount) public byte[] DecodeADPCM(byte[] data, int sampleCount)
@ -635,27 +779,56 @@ namespace CodeWalker.GameFiles
{ {
byte[] dataPCM = null; byte[] dataPCM = null;
int bitsPerSample = BitsPerSample; int bitsPerSample = BitsPerSample;
var channels = Channels;
switch (Format.Codec) if (MultiChannelFormat != null)
{ {
case AwcFormatChunk.CodecFormat.PCM: var ms = new MemoryStream();
dataPCM = Data; var bw = new BinaryWriter(ms);
break; if (MultiChannelSource?.MultiChannelBlocks != null)
case AwcFormatChunk.CodecFormat.ADPCM: {
dataPCM = new byte[Data.Length]; foreach (var blk in MultiChannelSource.MultiChannelBlocks)
Buffer.BlockCopy(Data, 0, dataPCM, 0, Data.Length); {
AwcFile.Decrypt_RSXXTEA(dataPCM, GTA5Keys.PC_AWC_KEY); if (MultiChannelIndex < (blk?.Channels?.Length ?? 0))
{
var chan = blk.Channels[MultiChannelIndex];
var cdata = chan.ChannelData;
bw.Write(cdata);
}
}
}
bw.Flush();
ms.Position = 0;
dataPCM = new byte[ms.Length];
ms.Read(dataPCM, 0, (int)ms.Length);
if (MultiChannelFormat.Codec == AwcCodecFormat.ADPCM)
{
dataPCM = DecodeADPCM(dataPCM, SampleCount); dataPCM = DecodeADPCM(dataPCM, SampleCount);
bitsPerSample = 16; }
break; channels = 1;
}
else
{
switch (Format.Codec)
{
case AwcCodecFormat.PCM:
dataPCM = Data;
break;
case AwcCodecFormat.ADPCM:
dataPCM = new byte[Data.Length];
Buffer.BlockCopy(Data, 0, dataPCM, 0, Data.Length);
dataPCM = DecodeADPCM(dataPCM, SampleCount);
bitsPerSample = 16;
break;
}
} }
int byteRate = SamplesPerSecond * Channels * bitsPerSample / 8; int byteRate = SamplesPerSecond * channels * bitsPerSample / 8;
short blockAlign = (short)(Channels * bitsPerSample / 8); short blockAlign = (short)(channels * bitsPerSample / 8);
MemoryStream stream = new MemoryStream(); MemoryStream stream = new MemoryStream();
BinaryWriter w = new BinaryWriter(stream); BinaryWriter w = new BinaryWriter(stream);
int wavLength = 12 + 24 + 8 + Data.Length; int wavLength = 12 + 24 + 8 + dataPCM.Length;
// RIFF chunk // RIFF chunk
w.Write("RIFF".ToCharArray()); w.Write("RIFF".ToCharArray());
@ -666,7 +839,7 @@ namespace CodeWalker.GameFiles
w.Write("fmt ".ToCharArray()); w.Write("fmt ".ToCharArray());
w.Write((int)16); // fmt size w.Write((int)16); // fmt size
w.Write((short)1); // 1 = WAVE_FORMAT_PCM w.Write((short)1); // 1 = WAVE_FORMAT_PCM
w.Write((short)Channels); w.Write((short)channels);
w.Write((int)SamplesPerSecond); w.Write((int)SamplesPerSecond);
w.Write((int)byteRate); w.Write((int)byteRate);
w.Write((short)blockAlign); w.Write((short)blockAlign);
@ -750,10 +923,28 @@ namespace CodeWalker.GameFiles
} }
} }
[TC(typeof(EXP))] public class AwcAudioMIDI
{
public AwcChunkInfo ChunkInfo { get; set; }
public byte[] Data { get; set; }
public AwcAudioMIDI(DataReader r, AwcChunkInfo info)
{
ChunkInfo = info;
Data = r.ReadBytes(info.Size);
}
public override string ToString()
{
return "MIDI - " + (Data?.Length??0).ToString() + " bytes";
}
}
[TC(typeof(EXP))] public class AwcChannelOffsetsInfo [TC(typeof(EXP))] public class AwcChannelOffsetsInfo
{ {
public AwcChunkInfo ChunkInfo { get; set; } public AwcChunkInfo ChunkInfo { get; set; }
public uint[] Offsets { get; set; } public uint[] SampleOffsets { get; set; }
public AwcChannelOffsetsInfo(DataReader r, AwcChunkInfo info) public AwcChannelOffsetsInfo(DataReader r, AwcChunkInfo info)
{ {
@ -762,71 +953,119 @@ namespace CodeWalker.GameFiles
var rem = info.Size % 4; var rem = info.Size % 4;
if (rem != 0) if (rem != 0)
{ } { }
Offsets = new uint[count]; SampleOffsets = new uint[count];
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
Offsets[i] = r.ReadUInt32(); SampleOffsets[i] = r.ReadUInt32();
} }
} }
public override string ToString() public override string ToString()
{ {
return (Offsets?.Length ?? 0).ToString() + " items"; return (SampleOffsets?.Length ?? 0).ToString() + " items";
} }
} }
[TC(typeof(EXP))] public class AwcChannelDataBlock
[TC(typeof(EXP))] public class AwcChannelChunkHeader
{ {
public static uint Size = 16; //24 for ps3...
public uint StartChunk { get; set; }
public uint ChunkCount { get; set; }
public uint SamplesToSkip { get; set; } //mostly 0
public uint SamplesPerChunk { get; set; }
public uint DataSize { get; set; }
public AwcChannelChunkHeader(DataReader r)
{
StartChunk = r.ReadUInt32();
ChunkCount = r.ReadUInt32();
SamplesToSkip = r.ReadUInt32();
SamplesPerChunk = r.ReadUInt32();
DataSize = ChunkCount * 0x800;
//for ps3, two extra ints:
//uint unk0 = r.ReadUint32();
//DataSize = r.ReadUint32();
}
}
[TC(typeof(EXP))] public class AwcChannelChunk
{
public AwcChannelChunkHeader Header { get; set; }
public AwcChannelChunkItemInfo Info { get; set; }
public byte[] Data { get; set; } public byte[] Data { get; set; }
public int Index { get; set; }
public int SampleOffset { get; set; }
public uint TotalDataSize { get; set; } public AwcChannelDataItem[] Channels { get; set; }
public AwcChannelChunk(DataReader r, AwcChannelChunkHeader h, AwcChannelChunkItemInfo i)
public AwcChannelDataBlock(byte[] data, AwcChannelBlockInfo channelInfo, Endianess endianess, int index, int sampleOffset)
{ {
Header = h; Data = data;
Info = i; Index = index;
SampleOffset = sampleOffset;
TotalDataSize = h.DataSize; using (var ms = new MemoryStream(data))
var rs = i?.RoundSize ?? 0;
int ds = (int)h.DataSize;
if (rs != 0)
{ {
TotalDataSize = (uint)(TotalDataSize + (((-ds) % rs) + rs) % rs); var r = new DataReader(ms, endianess);
var channels = channelInfo?.Channels;
var channelcount = channelInfo?.ChannelCount ?? 0;
var ilist = new List<AwcChannelDataItem>();
for (int i = 0; i < channelcount; i++)
{
var channel = new AwcChannelDataItem(r);
channel.ChannelInfo = ((channels != null) && (i < channels.Length)) ? channels[i] : null;
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)
{
var bcnt = channel.OffsetCount * 2048;
channel.ChannelData = r.ReadBytes(bcnt);
}
if (r.Position != r.Length)
{ }//still more, just padding?
} }
} }
public override string ToString()
{
return (Data?.Length ?? 0).ToString() + " bytes";
}
}
[TC(typeof(EXP))] public class AwcChannelDataItem
{
public int Unk0 { get; set; }
public int OffsetCount { get; set; }
public int Unk2 { get; set; }
public int SampleCount { get; set; }
public int Unk4 { get; set; }
public int Unk5 { get; set; }
public int[] SampleOffsets { get; set; }
public AwcChannelBlockItemInfo ChannelInfo { get; set; } //for convenience
public byte[] ChannelData { get; set; }
public AwcChannelDataItem(DataReader r)
{
Unk0 = r.ReadInt32();
OffsetCount = r.ReadInt32();
Unk2 = r.ReadInt32();
SampleCount = r.ReadInt32();
Unk4 = r.ReadInt32();
Unk5 = r.ReadInt32();
}
public void ReadOffsets(DataReader r)
{
var olist = new List<int>();
for (int i = 0; i < OffsetCount; i++)
{
var v = r.ReadInt32();
olist.Add(v);
}
SampleOffsets = olist.ToArray();
}
public override string ToString()
{
return Unk0.ToString() + ", " + OffsetCount.ToString() + ", " + Unk2.ToString() + ", " + SampleCount.ToString() + ", " + Unk4.ToString() + ", " + Unk5.ToString();
}
} }
} }

View File

@ -2844,8 +2844,8 @@ namespace CodeWalker.GameFiles
{ {
foreach (RpfEntry entry in file.AllEntries) foreach (RpfEntry entry in file.AllEntries)
{ {
try //try
{ //{
var n = entry.NameLower; var n = entry.NameLower;
if (n.EndsWith(".awc")) if (n.EndsWith(".awc"))
{ {
@ -2854,11 +2854,11 @@ namespace CodeWalker.GameFiles
if (awcfile != null) if (awcfile != null)
{ } { }
} }
} //}
catch (Exception ex) //catch (Exception ex)
{ //{
UpdateStatus("Error! " + ex.ToString()); // UpdateStatus("Error! " + ex.ToString());
} //}
} }
} }
} }

View File

@ -32,7 +32,6 @@
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AwcForm)); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AwcForm));
this.MainTabControl = new System.Windows.Forms.TabControl(); this.MainTabControl = new System.Windows.Forms.TabControl();
this.PlayerTabPage = new System.Windows.Forms.TabPage(); this.PlayerTabPage = new System.Windows.Forms.TabPage();
this.label1 = new System.Windows.Forms.Label();
this.LabelInfo = new System.Windows.Forms.Label(); this.LabelInfo = new System.Windows.Forms.Label();
this.LabelTime = new System.Windows.Forms.Label(); this.LabelTime = new System.Windows.Forms.Label();
this.StopButton = new System.Windows.Forms.Button(); this.StopButton = new System.Windows.Forms.Button();
@ -75,7 +74,6 @@
// //
// PlayerTabPage // PlayerTabPage
// //
this.PlayerTabPage.Controls.Add(this.label1);
this.PlayerTabPage.Controls.Add(this.LabelInfo); this.PlayerTabPage.Controls.Add(this.LabelInfo);
this.PlayerTabPage.Controls.Add(this.LabelTime); this.PlayerTabPage.Controls.Add(this.LabelTime);
this.PlayerTabPage.Controls.Add(this.StopButton); this.PlayerTabPage.Controls.Add(this.StopButton);
@ -95,16 +93,6 @@
this.PlayerTabPage.Text = "Player"; this.PlayerTabPage.Text = "Player";
this.PlayerTabPage.UseVisualStyleBackColor = true; this.PlayerTabPage.UseVisualStyleBackColor = true;
// //
// label1
//
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(244, 247);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(316, 13);
this.label1.TabIndex = 13;
this.label1.Text = "WARNING: Work in progress! Some audio may not play correctly!";
//
// LabelInfo // LabelInfo
// //
this.LabelInfo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.LabelInfo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
@ -133,6 +121,7 @@
this.StopButton.TabIndex = 10; this.StopButton.TabIndex = 10;
this.StopButton.Text = "◼"; this.StopButton.Text = "◼";
this.StopButton.UseVisualStyleBackColor = true; this.StopButton.UseVisualStyleBackColor = true;
this.StopButton.Click += new System.EventHandler(this.StopButton_Click);
// //
// VolumeLabel // VolumeLabel
// //
@ -208,6 +197,7 @@
this.PlayListView.TabIndex = 0; this.PlayListView.TabIndex = 0;
this.PlayListView.UseCompatibleStateImageBehavior = false; this.PlayListView.UseCompatibleStateImageBehavior = false;
this.PlayListView.View = System.Windows.Forms.View.Details; this.PlayListView.View = System.Windows.Forms.View.Details;
this.PlayListView.SelectedIndexChanged += new System.EventHandler(this.PlayListView_SelectedIndexChanged);
this.PlayListView.DoubleClick += new System.EventHandler(this.PlayListView_DoubleClick); this.PlayListView.DoubleClick += new System.EventHandler(this.PlayListView_DoubleClick);
// //
// PlaylistNameHeader // PlaylistNameHeader
@ -237,12 +227,12 @@
this.contextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.contextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.ExportAsWav}); this.ExportAsWav});
this.contextMenuStrip.Name = "contextMenuStrip1"; this.contextMenuStrip.Name = "contextMenuStrip1";
this.contextMenuStrip.Size = new System.Drawing.Size(149, 26); this.contextMenuStrip.Size = new System.Drawing.Size(150, 26);
// //
// ExportAsWav // ExportAsWav
// //
this.ExportAsWav.Name = "ExportAsWav"; this.ExportAsWav.Name = "ExportAsWav";
this.ExportAsWav.Size = new System.Drawing.Size(148, 22); this.ExportAsWav.Size = new System.Drawing.Size(149, 22);
this.ExportAsWav.Text = "Export as .wav"; this.ExportAsWav.Text = "Export as .wav";
this.ExportAsWav.Click += new System.EventHandler(this.ExportAsWav_Click); this.ExportAsWav.Click += new System.EventHandler(this.ExportAsWav_Click);
// //
@ -352,6 +342,5 @@
private System.Windows.Forms.ToolStripMenuItem ExportAsWav; private System.Windows.Forms.ToolStripMenuItem ExportAsWav;
private System.Windows.Forms.SaveFileDialog saveFileDialog; private System.Windows.Forms.SaveFileDialog saveFileDialog;
private System.Windows.Forms.ColumnHeader PlaylistSizeHeader; private System.Windows.Forms.ColumnHeader PlaylistSizeHeader;
private System.Windows.Forms.Label label1;
} }
} }

View File

@ -16,7 +16,7 @@ namespace CodeWalker.Forms
private XAudio2 xAudio2; private XAudio2 xAudio2;
private MasteringVoice masteringVoice; private MasteringVoice masteringVoice;
private AudioBuffer audioBuffer; private AudioBuffer audioBuffer;
private SourceVoice sourceVoice; private SourceVoice sourceVoice;
private string fileName; private string fileName;
public string FileName public string FileName
@ -60,7 +60,7 @@ namespace CodeWalker.Forms
{ {
fileName = awc?.FileEntry?.Name; fileName = awc?.FileEntry?.Name;
} }
PlayListView.Items.Clear(); PlayListView.Items.Clear();
float totalLength = 0; float totalLength = 0;
@ -68,16 +68,17 @@ namespace CodeWalker.Forms
{ {
foreach (var audio in awc.Audios) foreach (var audio in awc.Audios)
{ {
if (audio.MultiChannelBlocks != null) continue;//don't display multichannel source audios
var item = PlayListView.Items.Add(audio.Name); var item = PlayListView.Items.Add(audio.Name);
item.SubItems.Add(audio.Type); item.SubItems.Add(audio.Type);
item.SubItems.Add(audio.LengthStr); item.SubItems.Add(audio.LengthStr);
item.SubItems.Add(TextUtil.GetBytesReadable(audio.Data?.Length ?? 0)); item.SubItems.Add(TextUtil.GetBytesReadable(audio.ByteLength));
item.Tag = audio; item.Tag = audio;
totalLength += audio.Length; totalLength += audio.Length;
} }
} }
LabelInfo.Text = awc.Audios.Length.ToString() + " track(s), Length: " + TimeSpan.FromSeconds((float)totalLength).ToString("m\\:ss"); LabelInfo.Text = awc.Audios.Length.ToString() + " track(s), Length: " + TimeSpan.FromSeconds((float)totalLength).ToString("h\\:mm\\:ss");
UpdateFormTitle(); UpdateFormTitle();
} }
@ -119,7 +120,7 @@ namespace CodeWalker.Forms
LabelTime.Visible = false; LabelTime.Visible = false;
StopButton.Enabled = true; StopButton.Enabled = true;
break; break;
} }
playerState = newState; playerState = newState;
UpdateUI(); UpdateUI();
@ -129,7 +130,7 @@ namespace CodeWalker.Forms
private void InitializeAudio(AwcAudio audio, float playBegin = 0) private void InitializeAudio(AwcAudio audio, float playBegin = 0)
{ {
currentAudio = audio; currentAudio = audio;
trackLength = audio.Length; trackLength = audio.Length;
if (xAudio2 == null) if (xAudio2 == null)
{ {
@ -160,7 +161,7 @@ namespace CodeWalker.Forms
wavStream.Close(); wavStream.Close();
trackFinished = false; trackFinished = false;
sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true); sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true);
sourceVoice.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo); sourceVoice.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo);
sourceVoice.BufferEnd += (context) => trackFinished = true; sourceVoice.BufferEnd += (context) => trackFinished = true;
sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100); sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100);
@ -175,12 +176,16 @@ namespace CodeWalker.Forms
var item = PlayListView.SelectedItems[0]; var item = PlayListView.SelectedItems[0];
var audio = item.Tag as AwcAudio; var audio = item.Tag as AwcAudio;
if (audio != null) if ((audio?.Format != null) || (audio?.MultiChannelFormat != null))
{ {
InitializeAudio(audio); InitializeAudio(audio);
sourceVoice.Start(); sourceVoice.Start();
SetPlayerState(PlayerState.Playing); SetPlayerState(PlayerState.Playing);
} }
else if (audio.MIDIData != null)
{
//todo: play MIDI?
}
} }
} }
@ -217,7 +222,7 @@ namespace CodeWalker.Forms
private void Pause() private void Pause()
{ {
if (playerState == PlayerState.Playing) if (playerState == PlayerState.Playing)
{ {
sourceVoice.Stop(); sourceVoice.Stop();
SetPlayerState(PlayerState.Paused); SetPlayerState(PlayerState.Paused);
} }
@ -256,6 +261,11 @@ namespace CodeWalker.Forms
} }
} }
private void StopButton_Click(object sender, EventArgs e)
{
Stop();
}
private void PrevButton_Click(object sender, EventArgs e) private void PrevButton_Click(object sender, EventArgs e)
{ {
PlayPrevious(); PlayPrevious();
@ -327,16 +337,45 @@ namespace CodeWalker.Forms
var item = PlayListView.SelectedItems[0]; var item = PlayListView.SelectedItems[0];
var audio = item.Tag as AwcAudio; var audio = item.Tag as AwcAudio;
saveFileDialog.FileName = audio.Name + ".wav"; var ext = ".wav";
if (audio?.MIDIData != null)
{
ext = ".midi";
}
saveFileDialog.FileName = audio.Name + ext;
if (saveFileDialog.ShowDialog() == DialogResult.OK) if (saveFileDialog.ShowDialog() == DialogResult.OK)
{ {
Stream wavStream = audio.GetWavStream(); if (audio?.MIDIData != null)
FileStream stream = File.Create(saveFileDialog.FileName); {
wavStream.CopyTo(stream); File.WriteAllBytes(saveFileDialog.FileName, audio.MIDIData.Data);
stream.Close(); }
wavStream.Close(); else if ((audio?.Format != null) || (audio?.MultiChannelFormat != null))
{
Stream wavStream = audio.GetWavStream();
FileStream stream = File.Create(saveFileDialog.FileName);
wavStream.CopyTo(stream);
stream.Close();
wavStream.Close();
}
} }
} }
} }
private void PlayListView_SelectedIndexChanged(object sender, EventArgs e)
{
ExportAsWav.Text = "Export as .wav";
if (PlayListView.SelectedItems.Count == 1)
{
var item = PlayListView.SelectedItems[0];
var audio = item.Tag as AwcAudio;
if (audio?.MIDIData != null)
{
ExportAsWav.Text = "Export as .midi";
}
}
}
} }
} }