mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-11-22 23:12:59 +08:00
AWC improvements
This commit is contained in:
parent
4020327e64
commit
668e8a2841
@ -23,9 +23,15 @@ namespace CodeWalker.GameFiles
|
||||
public int StreamCount { 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 bool MultiChannel { get; set; }
|
||||
|
||||
public bool WholeFileEncrypted { get; set; }
|
||||
|
||||
public AwcStreamInfo[] StreamInfos { get; set; }
|
||||
public uint[] AudioIds { get; set; }
|
||||
@ -80,6 +86,7 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
Decrypt_RSXXTEA(data, GTA5Keys.PC_AWC_KEY);
|
||||
Magic = BitConverter.ToUInt32(data, 0);
|
||||
WholeFileEncrypted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -111,27 +118,13 @@ namespace CodeWalker.GameFiles
|
||||
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)
|
||||
{
|
||||
ErrorMessage = "Flags 0 not supported!";
|
||||
return;
|
||||
}
|
||||
if ((Flags & 0xF8) != 0)
|
||||
{
|
||||
//ErrorMessage = "Flags 1 not supported!";
|
||||
//return;
|
||||
}
|
||||
|
||||
MultiChannel = ((Flags & 4) == 4);
|
||||
|
||||
|
||||
var flag0 = ((Flags & 1) == 1);
|
||||
if (flag0)
|
||||
if (UnkUshortsFlag)
|
||||
{
|
||||
UnkUshorts = new ushort[StreamCount]; //offsets of some sort?
|
||||
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)
|
||||
{ }
|
||||
{ }//no hit
|
||||
ms.Position = infoStart;
|
||||
|
||||
|
||||
@ -169,8 +162,9 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
StreamInfos = infos.ToArray();
|
||||
|
||||
//ReadAudiosOrig(infoDict, r);
|
||||
ReadAudios(r);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,6 +173,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
List<uint> audioIds = new List<uint>();
|
||||
List<AwcAudio> audios = new List<AwcAudio>();
|
||||
AwcAudio multiaudio = null;
|
||||
|
||||
for (int i = 0; i < StreamCount; i++)
|
||||
{
|
||||
@ -211,17 +206,17 @@ namespace CodeWalker.GameFiles
|
||||
case 90: //unk5A
|
||||
audio.UnkData5A = new AwcAudioUnk(r, chunk);
|
||||
break;
|
||||
case 104://unk68
|
||||
audio.UnkData68 = new AwcAudioUnk(r, chunk);
|
||||
case 104://MIDI
|
||||
audio.MIDIData = new AwcAudioMIDI(r, chunk);
|
||||
break;
|
||||
case 217://unkD9 - length 200+
|
||||
audio.UnkDataD9 = new AwcAudioUnk(r, chunk);
|
||||
break;
|
||||
case 72: //multi channel info
|
||||
audio.MultiChannelInfo = new AwcChannelChunkInfo(r);
|
||||
audio.MultiChannelInfo = new AwcChannelBlockInfo(r);
|
||||
break;
|
||||
case 163://multi unkA3
|
||||
audio.MultiChannelOffsets = new AwcChannelOffsetsInfo(r, chunk);
|
||||
audio.MultiChannelSampleOffsets = new AwcChannelOffsetsInfo(r, chunk);
|
||||
break;
|
||||
case 189://multi unkBD
|
||||
audio.UnkDataBD = new AwcAudioUnk(r, chunk);
|
||||
@ -233,75 +228,95 @@ namespace CodeWalker.GameFiles
|
||||
{ }//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);
|
||||
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();
|
||||
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
|
||||
{
|
||||
@ -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 ChunkSize { get; set; }
|
||||
public uint BlockCount { get; set; }
|
||||
public uint BlockSize { 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();
|
||||
ChunkSize = r.ReadUInt32();
|
||||
BlockCount = r.ReadUInt32();
|
||||
BlockSize = r.ReadUInt32();
|
||||
ChannelCount = r.ReadUInt32();
|
||||
|
||||
List<AwcChannelChunkItemInfo> channels = new List<AwcChannelChunkItemInfo>();
|
||||
List<AwcChannelBlockItemInfo> channels = new List<AwcChannelBlockItemInfo>();
|
||||
for (int i = 0; i < ChannelCount; i++)
|
||||
{
|
||||
var itemInfo = new AwcChannelChunkItemInfo(r);
|
||||
var itemInfo = new AwcChannelBlockItemInfo(r);
|
||||
channels.Add(itemInfo);
|
||||
}
|
||||
Channels = channels.ToArray();
|
||||
@ -373,34 +388,107 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
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 Samples { get; set; }
|
||||
public ushort Unk0 { get; set; }
|
||||
public ushort Unk0 { get; set; }//flags?
|
||||
public ushort SamplesPerSecond { get; set; }
|
||||
public byte Unk1 { get; set; }
|
||||
public AwcCodecFormat Codec { get; set; }
|
||||
public byte RoundSize { get; set; }
|
||||
public ushort Unk2 { get; set; }
|
||||
|
||||
public AwcChannelChunkItemInfo(DataReader r)
|
||||
public AwcChannelBlockItemInfo(DataReader r)
|
||||
{
|
||||
Id = r.ReadUInt32();
|
||||
Samples = r.ReadUInt32();
|
||||
Unk0 = r.ReadUInt16();
|
||||
SamplesPerSecond = r.ReadUInt16();
|
||||
Unk1 = r.ReadByte();
|
||||
Codec = (AwcCodecFormat)r.ReadByte();
|
||||
RoundSize = r.ReadByte();
|
||||
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()
|
||||
{
|
||||
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 uint UnkExtra { get; set; }
|
||||
|
||||
public enum CodecFormat
|
||||
{
|
||||
PCM = 0,
|
||||
ADPCM = 4
|
||||
}
|
||||
public CodecFormat Codec { get; set; }
|
||||
public AwcCodecFormat Codec { get; set; }
|
||||
|
||||
public AwcFormatChunk(DataReader r, AwcChunkInfo info)
|
||||
{
|
||||
@ -433,7 +516,7 @@ namespace CodeWalker.GameFiles
|
||||
LoopEnd = r.ReadUInt16();
|
||||
PlayEnd = r.ReadUInt16();
|
||||
PlayBegin = r.ReadByte();
|
||||
Codec = (CodecFormat)r.ReadByte();
|
||||
Codec = (AwcCodecFormat)r.ReadByte();
|
||||
|
||||
switch (info.Size) //Apparently sometimes this struct is longer?
|
||||
{
|
||||
@ -466,26 +549,31 @@ namespace CodeWalker.GameFiles
|
||||
public AwcAudioUnk UnkData2B { get; set; }
|
||||
public AwcAudioUnk UnkData36 { get; set; }
|
||||
public AwcAudioUnk UnkData5A { get; set; }
|
||||
public AwcAudioUnk UnkData68 { get; set; }
|
||||
public AwcAudioMIDI MIDIData { get; set; }
|
||||
public AwcAudioUnk UnkDataD9 { get; set; }
|
||||
public AwcChannelChunkInfo MultiChannelInfo { get; set; }
|
||||
public AwcChannelOffsetsInfo MultiChannelOffsets { get; set; }
|
||||
public AwcChannelBlockInfo MultiChannelInfo { get; set; }
|
||||
public AwcChannelOffsetsInfo MultiChannelSampleOffsets { get; set; }
|
||||
public AwcChannelDataBlock[] MultiChannelBlocks { 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 BitsPerSample = 16;
|
||||
public int SamplesPerSecond
|
||||
{
|
||||
get
|
||||
{
|
||||
return Format?.SamplesPerSecond ?? 0;
|
||||
return Format?.SamplesPerSecond ?? MultiChannelFormat?.SamplesPerSecond ?? 0;
|
||||
}
|
||||
}
|
||||
public int SampleCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)(Format?.Samples ?? 0);
|
||||
return (int)(Format?.Samples ?? MultiChannelFormat?.Samples ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,24 +589,34 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Format == null) return "Unknown";
|
||||
|
||||
string codec;
|
||||
switch (Format.Codec)
|
||||
if (MIDIData != null)
|
||||
{
|
||||
case AwcFormatChunk.CodecFormat.PCM:
|
||||
codec = "PCM";
|
||||
break;
|
||||
case AwcFormatChunk.CodecFormat.ADPCM:
|
||||
codec = "ADPCM";
|
||||
return "MIDI";
|
||||
}
|
||||
|
||||
var fc = AwcCodecFormat.PCM;
|
||||
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;
|
||||
default:
|
||||
codec = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
var hz = Format.SamplesPerSecond;
|
||||
|
||||
return codec + ((hz > 0) ? (", " + hz.ToString() + " Hz") : "");
|
||||
}
|
||||
}
|
||||
@ -527,10 +625,11 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
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
|
||||
{
|
||||
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()
|
||||
{
|
||||
}
|
||||
@ -553,8 +685,20 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var hash = (StreamInfo?.Id.ToString("X") ?? "0").PadLeft(8, '0');
|
||||
return "0x" + hash + ": " + Format?.ToString() ?? "AwcAudio";
|
||||
var hash = "0x" + (StreamInfo?.Id.ToString("X") ?? "0").PadLeft(8, '0') + ": ";
|
||||
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)
|
||||
@ -635,27 +779,56 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
byte[] dataPCM = null;
|
||||
int bitsPerSample = BitsPerSample;
|
||||
var channels = Channels;
|
||||
|
||||
switch (Format.Codec)
|
||||
if (MultiChannelFormat != null)
|
||||
{
|
||||
case AwcFormatChunk.CodecFormat.PCM:
|
||||
dataPCM = Data;
|
||||
break;
|
||||
case AwcFormatChunk.CodecFormat.ADPCM:
|
||||
dataPCM = new byte[Data.Length];
|
||||
Buffer.BlockCopy(Data, 0, dataPCM, 0, Data.Length);
|
||||
AwcFile.Decrypt_RSXXTEA(dataPCM, GTA5Keys.PC_AWC_KEY);
|
||||
var ms = new MemoryStream();
|
||||
var bw = new BinaryWriter(ms);
|
||||
if (MultiChannelSource?.MultiChannelBlocks != null)
|
||||
{
|
||||
foreach (var blk in MultiChannelSource.MultiChannelBlocks)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
short blockAlign = (short)(Channels * bitsPerSample / 8);
|
||||
int byteRate = SamplesPerSecond * channels * bitsPerSample / 8;
|
||||
short blockAlign = (short)(channels * bitsPerSample / 8);
|
||||
|
||||
MemoryStream stream = new MemoryStream();
|
||||
BinaryWriter w = new BinaryWriter(stream);
|
||||
int wavLength = 12 + 24 + 8 + Data.Length;
|
||||
int wavLength = 12 + 24 + 8 + dataPCM.Length;
|
||||
|
||||
// RIFF chunk
|
||||
w.Write("RIFF".ToCharArray());
|
||||
@ -666,7 +839,7 @@ namespace CodeWalker.GameFiles
|
||||
w.Write("fmt ".ToCharArray());
|
||||
w.Write((int)16); // fmt size
|
||||
w.Write((short)1); // 1 = WAVE_FORMAT_PCM
|
||||
w.Write((short)Channels);
|
||||
w.Write((short)channels);
|
||||
w.Write((int)SamplesPerSecond);
|
||||
w.Write((int)byteRate);
|
||||
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
|
||||
{
|
||||
public AwcChunkInfo ChunkInfo { get; set; }
|
||||
public uint[] Offsets { get; set; }
|
||||
public uint[] SampleOffsets { get; set; }
|
||||
|
||||
public AwcChannelOffsetsInfo(DataReader r, AwcChunkInfo info)
|
||||
{
|
||||
@ -762,71 +953,119 @@ namespace CodeWalker.GameFiles
|
||||
var rem = info.Size % 4;
|
||||
if (rem != 0)
|
||||
{ }
|
||||
Offsets = new uint[count];
|
||||
SampleOffsets = new uint[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Offsets[i] = r.ReadUInt32();
|
||||
SampleOffsets[i] = r.ReadUInt32();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (Offsets?.Length ?? 0).ToString() + " items";
|
||||
return (SampleOffsets?.Length ?? 0).ToString() + " items";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TC(typeof(EXP))] public class AwcChannelChunkHeader
|
||||
[TC(typeof(EXP))] public class AwcChannelDataBlock
|
||||
{
|
||||
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 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;
|
||||
Info = i;
|
||||
Data = data;
|
||||
Index = index;
|
||||
SampleOffset = sampleOffset;
|
||||
|
||||
TotalDataSize = h.DataSize;
|
||||
|
||||
var rs = i?.RoundSize ?? 0;
|
||||
int ds = (int)h.DataSize;
|
||||
if (rs != 0)
|
||||
using (var ms = new MemoryStream(data))
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2844,8 +2844,8 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
foreach (RpfEntry entry in file.AllEntries)
|
||||
{
|
||||
try
|
||||
{
|
||||
//try
|
||||
//{
|
||||
var n = entry.NameLower;
|
||||
if (n.EndsWith(".awc"))
|
||||
{
|
||||
@ -2854,11 +2854,11 @@ namespace CodeWalker.GameFiles
|
||||
if (awcfile != null)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus("Error! " + ex.ToString());
|
||||
}
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// UpdateStatus("Error! " + ex.ToString());
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
Forms/AwcForm.Designer.cs
generated
19
Forms/AwcForm.Designer.cs
generated
@ -32,7 +32,6 @@
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AwcForm));
|
||||
this.MainTabControl = new System.Windows.Forms.TabControl();
|
||||
this.PlayerTabPage = new System.Windows.Forms.TabPage();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.LabelInfo = new System.Windows.Forms.Label();
|
||||
this.LabelTime = new System.Windows.Forms.Label();
|
||||
this.StopButton = new System.Windows.Forms.Button();
|
||||
@ -75,7 +74,6 @@
|
||||
//
|
||||
// PlayerTabPage
|
||||
//
|
||||
this.PlayerTabPage.Controls.Add(this.label1);
|
||||
this.PlayerTabPage.Controls.Add(this.LabelInfo);
|
||||
this.PlayerTabPage.Controls.Add(this.LabelTime);
|
||||
this.PlayerTabPage.Controls.Add(this.StopButton);
|
||||
@ -95,16 +93,6 @@
|
||||
this.PlayerTabPage.Text = "Player";
|
||||
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
|
||||
//
|
||||
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.Text = "◼";
|
||||
this.StopButton.UseVisualStyleBackColor = true;
|
||||
this.StopButton.Click += new System.EventHandler(this.StopButton_Click);
|
||||
//
|
||||
// VolumeLabel
|
||||
//
|
||||
@ -208,6 +197,7 @@
|
||||
this.PlayListView.TabIndex = 0;
|
||||
this.PlayListView.UseCompatibleStateImageBehavior = false;
|
||||
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);
|
||||
//
|
||||
// PlaylistNameHeader
|
||||
@ -237,12 +227,12 @@
|
||||
this.contextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.ExportAsWav});
|
||||
this.contextMenuStrip.Name = "contextMenuStrip1";
|
||||
this.contextMenuStrip.Size = new System.Drawing.Size(149, 26);
|
||||
this.contextMenuStrip.Size = new System.Drawing.Size(150, 26);
|
||||
//
|
||||
// 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.Click += new System.EventHandler(this.ExportAsWav_Click);
|
||||
//
|
||||
@ -352,6 +342,5 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem ExportAsWav;
|
||||
private System.Windows.Forms.SaveFileDialog saveFileDialog;
|
||||
private System.Windows.Forms.ColumnHeader PlaylistSizeHeader;
|
||||
private System.Windows.Forms.Label label1;
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ namespace CodeWalker.Forms
|
||||
private XAudio2 xAudio2;
|
||||
private MasteringVoice masteringVoice;
|
||||
private AudioBuffer audioBuffer;
|
||||
private SourceVoice sourceVoice;
|
||||
private SourceVoice sourceVoice;
|
||||
|
||||
private string fileName;
|
||||
public string FileName
|
||||
@ -60,7 +60,7 @@ namespace CodeWalker.Forms
|
||||
{
|
||||
fileName = awc?.FileEntry?.Name;
|
||||
}
|
||||
|
||||
|
||||
PlayListView.Items.Clear();
|
||||
|
||||
float totalLength = 0;
|
||||
@ -68,16 +68,17 @@ namespace CodeWalker.Forms
|
||||
{
|
||||
foreach (var audio in awc.Audios)
|
||||
{
|
||||
if (audio.MultiChannelBlocks != null) continue;//don't display multichannel source audios
|
||||
var item = PlayListView.Items.Add(audio.Name);
|
||||
item.SubItems.Add(audio.Type);
|
||||
item.SubItems.Add(audio.LengthStr);
|
||||
item.SubItems.Add(TextUtil.GetBytesReadable(audio.Data?.Length ?? 0));
|
||||
item.SubItems.Add(TextUtil.GetBytesReadable(audio.ByteLength));
|
||||
item.Tag = audio;
|
||||
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();
|
||||
}
|
||||
|
||||
@ -119,7 +120,7 @@ namespace CodeWalker.Forms
|
||||
LabelTime.Visible = false;
|
||||
StopButton.Enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
playerState = newState;
|
||||
UpdateUI();
|
||||
@ -129,7 +130,7 @@ namespace CodeWalker.Forms
|
||||
private void InitializeAudio(AwcAudio audio, float playBegin = 0)
|
||||
{
|
||||
currentAudio = audio;
|
||||
trackLength = audio.Length;
|
||||
trackLength = audio.Length;
|
||||
|
||||
if (xAudio2 == null)
|
||||
{
|
||||
@ -160,7 +161,7 @@ namespace CodeWalker.Forms
|
||||
wavStream.Close();
|
||||
|
||||
trackFinished = false;
|
||||
sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true);
|
||||
sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true);
|
||||
sourceVoice.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo);
|
||||
sourceVoice.BufferEnd += (context) => trackFinished = true;
|
||||
sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100);
|
||||
@ -175,12 +176,16 @@ namespace CodeWalker.Forms
|
||||
var item = PlayListView.SelectedItems[0];
|
||||
var audio = item.Tag as AwcAudio;
|
||||
|
||||
if (audio != null)
|
||||
if ((audio?.Format != null) || (audio?.MultiChannelFormat != null))
|
||||
{
|
||||
InitializeAudio(audio);
|
||||
sourceVoice.Start();
|
||||
SetPlayerState(PlayerState.Playing);
|
||||
}
|
||||
else if (audio.MIDIData != null)
|
||||
{
|
||||
//todo: play MIDI?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,7 +222,7 @@ namespace CodeWalker.Forms
|
||||
private void Pause()
|
||||
{
|
||||
if (playerState == PlayerState.Playing)
|
||||
{
|
||||
{
|
||||
sourceVoice.Stop();
|
||||
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)
|
||||
{
|
||||
PlayPrevious();
|
||||
@ -327,16 +337,45 @@ namespace CodeWalker.Forms
|
||||
var item = PlayListView.SelectedItems[0];
|
||||
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)
|
||||
{
|
||||
Stream wavStream = audio.GetWavStream();
|
||||
FileStream stream = File.Create(saveFileDialog.FileName);
|
||||
wavStream.CopyTo(stream);
|
||||
stream.Close();
|
||||
wavStream.Close();
|
||||
{
|
||||
if (audio?.MIDIData != null)
|
||||
{
|
||||
File.WriteAllBytes(saveFileDialog.FileName, audio.MIDIData.Data);
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user